diff --git a/.editorconfig b/.editorconfig index 79c4b77c52b9..e050ceea120f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,6 +4,7 @@ indent_size = 4 charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true +end_of_line = lf [*.yml] indent_style = space diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 000000000000..5c88c900793a --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,6 @@ +#Ignores big formatting commits when checking blame. +#To make use of this file by default, run 'git config blame.ignoreRevsFile .git-blame-ignore-revs' +#in the project folder + +# Normalize all file line endings to LF +10be290012f7a0fae2ad47d56efcdf4d80783943 diff --git a/.gitattributes b/.gitattributes index 2f9e769c2621..115b676a9598 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,43 @@ -# merger hooks, run tools/hooks/install.bat or install.sh to set up -*.dmm merge=dmm -*.dmi merge=dmi +* text=auto -# force changelog merging to use union -html/changelog.html merge=union +## Enforce text mode and LF line breaks +*.bat text eol=lf +*.css text eol=lf +*.css text eol=lf +*.dm text eol=lf +*.dme text eol=lf +*.dmf text eol=lf +*.htm text eol=lf +*.html text eol=lf +*.html text eol=lf +*.js text eol=lf +*.json text eol=lf +*.jsx text eol=lf +*.md text eol=lf +*.py text eol=lf +*.scss text eol=lf +*.sh text eol=lf +*.sql text eol=lf +*.svg text eol=lf +*.ts text eol=lf +*.tsx text eol=lf +*.txt text eol=lf +*.yaml text eol=lf +*.yml text eol=lf + +## Enforce binary mode +*.bmp binary +*.dll binary +*.dmb binary +*.exe binary +*.gif binary +*.jpg binary +*.png binary +*.so binary + +## Merger hooks, run tools/hooks/install.bat or install.sh to set up +*.dmm text eol=lf merge=dmm +*.dmi binary merge=dmi + +## Force changelog merging to use union +html/changelog.html text eol=lf merge=union diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 983036d9fca5..ebe2832ec78e 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -19,6 +19,7 @@ First things first, we want to make it clear how you can contribute (if you've n /tg/station doesn't have a list of goals and features to add; we instead allow freedom for contributors to suggest and create their ideas for the game. That doesn't mean we aren't determined to squash bugs, which unfortunately pop up a lot due to the deep complexity of the game. Here are some useful starting guides, if you want to contribute or if you want to know what challenges you can tackle with zero knowledge about the game's code structure. If you want to contribute the first thing you'll need to do is [set up Git](https://wiki.boomerstation.space/Setting_up_git) so you can download the source code. +After setting it up, optionally navigate your git commandline to the project folder and run the command: 'git config blame.ignoreRevsFile .git-blame-ignore-revs' We have a [list of guides on the wiki](https://wiki.boomerstation.space/index.php/Guides#Development_and_Contribution_Guides) that will help you get started contributing to /tg/station with Git and Dream Maker. For beginners, it is recommended you work on small projects like bugfixes at first. If you need help learning to program in BYOND, check out this [repository of resources](http://www.byond.com/developer/articles/resources). diff --git a/.github/workflows/turdis.yml b/.github/workflows/turdis.yml index bff2d8ea340a..a230da04ce24 100644 --- a/.github/workflows/turdis.yml +++ b/.github/workflows/turdis.yml @@ -33,11 +33,6 @@ jobs: with: default: true - - name: Setup PHP - uses: nanasess/setup-php@v3.0.3 - with: - php_version: 5.6 - - name: Install Dependencies run: | wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash @@ -48,7 +43,6 @@ jobs: run: | tools/travis/check_filedirs.sh tgstation.dme tools/travis/check_changelogs.sh - find . -name "*.php" -print0 | xargs -0 -n1 php -l find . -name "*.json" -not -path "./tgui/node_modules/*" -print0 | xargs -0 python3 ./tools/json_verifier.py tools/travis/build_tgui.sh tools/travis/check_grep.sh @@ -109,7 +103,7 @@ jobs: sudo add-apt-repository ppa:ubuntu-toolchain-r/ppa sudo dpkg --add-architecture i386 sudo apt-get update - sudo apt install libstdc++6:i386 gcc-multilib g++-7 g++-7-multilib libmariadb-client-lgpl-dev:i386 libmariadbd-dev + sudo apt install libstdc++6:i386 gcc-multilib g++-7 g++-7-multilib libssl1.1:i386 zlib1g:i386 - name: Cache BYOND uses: actions/cache@v1 @@ -126,13 +120,11 @@ jobs: echo "::set-env name=LD_LIBRARY_PATH::/home/runner/BYOND/byond/bin:$LD_LIBRARY_PATH" echo "::set-env name=MANPATH::/home/runner/BYOND/byond/man:$MANPATH" cd $GITHUB_WORKSPACE - tools/travis/install_libmariadb.sh tools/travis/install_rust_g.sh mysql -u root -h 127.0.0.1 -e 'CREATE DATABASE tg_travis;' mysql -u root -h 127.0.0.1 tg_travis < SQL/tgstation_schema.sql mysql -u root -h 127.0.0.1 -e 'CREATE DATABASE tg_travis_prefixed;' mysql -u root -h 127.0.0.1 tg_travis_prefixed < SQL/tgstation_schema_prefixed.sql - tools/travis/build_bsql.sh - name: Compile run: | diff --git a/.travis.yml.old b/.travis.yml.old index 1ae985d623c4..aa185b3c9346 100644 --- a/.travis.yml.old +++ b/.travis.yml.old @@ -1,6 +1,6 @@ language: generic -dist: xenial -sudo: false +os: linux +dist: bionic #branches: # only: @@ -60,23 +60,20 @@ matrix: - gcc-multilib - g++-7 - g++-7-multilib - - libmariadb-client-lgpl-dev:i386 - - libmariadbd-dev + - libssl1.1:i386 + - zlib1g:i386 cache: directories: - $HOME/BYOND - - $HOME/libmariadb install: - tools/travis/install_byond.sh - source $HOME/BYOND/byond/bin/byondsetup - - tools/travis/install_libmariadb.sh - tools/travis/install_rust_g.sh before_script: - mysql -u root -e 'CREATE DATABASE tg_travis;' - mysql -u root tg_travis < SQL/tgstation_schema.sql - mysql -u root -e 'CREATE DATABASE tg_travis_prefixed;' - mysql -u root tg_travis_prefixed < SQL/tgstation_schema_prefixed.sql - - tools/travis/build_bsql.sh script: - tools/travis/dm.sh -DTRAVISBUILDING tgstation.dme || travis_terminate 1 - tools/travis/run_server.sh diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 0abac7a5338e..f794162129c3 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,8 +1,8 @@ -{ - "recommendations": [ - "gbasood.byond-dm-language-support", - "platymuus.dm-langclient", - "EditorConfig.EditorConfig", - "dbaeumer.vscode-eslint" - ] -} +{ + "recommendations": [ + "gbasood.byond-dm-language-support", + "platymuus.dm-langclient", + "EditorConfig.EditorConfig", + "dbaeumer.vscode-eslint" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 4c993c350037..13544ab0b57e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,5 +8,6 @@ "filenamePattern": "*.dmi", "viewType": "imagePreview.previewEditor" } - ] + ], + "files.eol": "\n" } diff --git a/BSQL.dll b/BSQL.dll deleted file mode 100644 index 861492c8b473..000000000000 Binary files a/BSQL.dll and /dev/null differ diff --git a/README.md b/README.md index 89eb8a8f9894..180949507b9b 100644 --- a/README.md +++ b/README.md @@ -1,60 +1,60 @@ -# WaspStation 13 Codebase - -![Checks](https://github.com/WaspStation/WaspStation-1.0/workflows/Checks/badge.svg) [![Percentage of issues still open](http://isitmaintained.com/badge/open/waspstation/waspstation-1.0.svg)](https://isitmaintained.com/project/waspstation/waspstation-1.0 "Percentage of issues still open") [![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/waspstation/waspstation-1.0.svg)](https://isitmaintained.com/project/waspstation/waspstation-1.0 "Average time to resolve an issue") [![GitHub issues](https://img.shields.io/github/issues/waspstation/waspstation-1.0)](https://github.com/waspstation/waspstation-1.0/issues) ![GitHub top language](https://img.shields.io/github/languages/top/waspstation/waspstation-1.0) - -[![forthebadge](https://forthebadge.com/images/badges/built-with-resentment.svg)](https://forthebadge.com) [![forthebadge](https://forthebadge.com/images/badges/contains-technical-debt.svg)](https://user-images.githubusercontent.com/8171642/50290880-ffef5500-043a-11e9-8270-a2e5b697c86c.png) [![forinfinityandbyond](https://user-images.githubusercontent.com/5211576/29499758-4efff304-85e6-11e7-8267-62919c3688a9.gif)](https://www.reddit.com/r/SS13/comments/5oplxp/what_is_the_main_problem_with_byond_as_an_engine/dclbu1a) - -* **Website:** TBA -* **Code:** -* **Mantis Discord:** -* **Boomer Discord:** -* **Coderbus Discord:** -* **Wiki** TBA - -This is the codebase for the /tg/station flavoured fork of SpaceStation 13. - -Space Station 13 is a paranoia-laden round-based roleplaying game set against the backdrop of a nonsensical, metal death trap masquerading as a space station, with charming spritework designed to represent the sci-fi setting and its dangerous undertones. Have fun, and survive! - -## DOWNLOADING - -[Downloading](.github/DOWNLOADING.md) - -[Running on the server](.github/RUNNING_A_SERVER.md) - -[Maps and Away Missions](.github/MAPS_AND_AWAY_MISSIONS.md) - -## Requirements for contributors - -[Guidelines for Contributors](.github/CONTRIBUTING.md) - -[Documenting your code](.github/AUTODOC_GUIDE.md) - -[Policy configuration system](.github/POLICYCONFIG.md) - -## CODEBASE CREDITS - -* /tg/, for the codebase -* BeeStation, for the many QoL changes -* Oracle, for the inspiration and wonderful features and sprites -* Interstation for bridging the gap between Oracle and Modern /tg/ -* YogStation for multiple different features -* CEV Eris, for the PDA sprites - -And thank you to any other codebase not mentioned here that has been used in the code. Your wonderful contributions are known. - -## LICENSE - -All code after [commit 333c566b88108de218d882840e61928a9b759d8f on 2014/31/12 at 4:38 PM PST](https://github.com/tgstation/tgstation/commit/333c566b88108de218d882840e61928a9b759d8f) is licensed under [GNU AGPL v3](https://www.gnu.org/licenses/agpl-3.0.html). - -All code before [commit 333c566b88108de218d882840e61928a9b759d8f on 2014/31/12 at 4:38 PM PST](https://github.com/tgstation/tgstation/commit/333c566b88108de218d882840e61928a9b759d8f) is licensed under [GNU GPL v3](https://www.gnu.org/licenses/gpl-3.0.html). -(Including tools unless their readme specifies otherwise.) - -See LICENSE and GPLv3.txt for more details. - -The TGS DMAPI API is licensed as a subproject under the MIT license. - -See the footer of [code/__DEFINES/tgs.dm](./code/__DEFINES/tgs.dm) and [code/modules/tgs/LICENSE](./code/modules/tgs/LICENSE) for the MIT license. - -All assets including icons and sound are under a [Creative Commons 3.0 BY-SA license](https://creativecommons.org/licenses/by-sa/3.0/) unless otherwise indicated. - -All assets located in the `goon` and `waspstation/goon` directory are under a [Creative Commons 3.0 BY-NC-SA license](https://creativecommons.org/licenses/by-nc-sa/3.0/). Assets created by Goonstation. +# WaspStation 13 Codebase + +![Checks](https://github.com/WaspStation/WaspStation-1.0/workflows/Checks/badge.svg) [![Percentage of issues still open](http://isitmaintained.com/badge/open/waspstation/waspstation-1.0.svg)](https://isitmaintained.com/project/waspstation/waspstation-1.0 "Percentage of issues still open") [![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/waspstation/waspstation-1.0.svg)](https://isitmaintained.com/project/waspstation/waspstation-1.0 "Average time to resolve an issue") [![GitHub issues](https://img.shields.io/github/issues/waspstation/waspstation-1.0)](https://github.com/waspstation/waspstation-1.0/issues) ![GitHub top language](https://img.shields.io/github/languages/top/waspstation/waspstation-1.0) + +[![forthebadge](https://forthebadge.com/images/badges/built-with-resentment.svg)](https://forthebadge.com) [![forthebadge](https://forthebadge.com/images/badges/contains-technical-debt.svg)](https://user-images.githubusercontent.com/8171642/50290880-ffef5500-043a-11e9-8270-a2e5b697c86c.png) [![forinfinityandbyond](https://user-images.githubusercontent.com/5211576/29499758-4efff304-85e6-11e7-8267-62919c3688a9.gif)](https://www.reddit.com/r/SS13/comments/5oplxp/what_is_the_main_problem_with_byond_as_an_engine/dclbu1a) + +* **Website:** TBA +* **Code:** +* **Mantis Discord:** +* **Boomer Discord:** +* **Coderbus Discord:** +* **Wiki** TBA + +This is the codebase for the /tg/station flavoured fork of SpaceStation 13. + +Space Station 13 is a paranoia-laden round-based roleplaying game set against the backdrop of a nonsensical, metal death trap masquerading as a space station, with charming spritework designed to represent the sci-fi setting and its dangerous undertones. Have fun, and survive! + +## DOWNLOADING + +[Downloading](.github/DOWNLOADING.md) + +[Running on the server](.github/RUNNING_A_SERVER.md) + +[Maps and Away Missions](.github/MAPS_AND_AWAY_MISSIONS.md) + +## Requirements for contributors + +[Guidelines for Contributors](.github/CONTRIBUTING.md) + +[Documenting your code](.github/AUTODOC_GUIDE.md) + +[Policy configuration system](.github/POLICYCONFIG.md) + +## CODEBASE CREDITS + +* /tg/, for the codebase +* BeeStation, for the many QoL changes +* Oracle, for the inspiration and wonderful features and sprites +* Interstation for bridging the gap between Oracle and Modern /tg/ +* YogStation for multiple different features +* CEV Eris, for the PDA sprites + +And thank you to any other codebase not mentioned here that has been used in the code. Your wonderful contributions are known. + +## LICENSE + +All code after [commit 333c566b88108de218d882840e61928a9b759d8f on 2014/31/12 at 4:38 PM PST](https://github.com/tgstation/tgstation/commit/333c566b88108de218d882840e61928a9b759d8f) is licensed under [GNU AGPL v3](https://www.gnu.org/licenses/agpl-3.0.html). + +All code before [commit 333c566b88108de218d882840e61928a9b759d8f on 2014/31/12 at 4:38 PM PST](https://github.com/tgstation/tgstation/commit/333c566b88108de218d882840e61928a9b759d8f) is licensed under [GNU GPL v3](https://www.gnu.org/licenses/gpl-3.0.html). +(Including tools unless their readme specifies otherwise.) + +See LICENSE and GPLv3.txt for more details. + +The TGS DMAPI API is licensed as a subproject under the MIT license. + +See the footer of [code/__DEFINES/tgs.dm](./code/__DEFINES/tgs.dm) and [code/modules/tgs/LICENSE](./code/modules/tgs/LICENSE) for the MIT license. + +All assets including icons and sound are under a [Creative Commons 3.0 BY-SA license](https://creativecommons.org/licenses/by-sa/3.0/) unless otherwise indicated. + +All assets located in the `goon` and `waspstation/goon` directory are under a [Creative Commons 3.0 BY-NC-SA license](https://creativecommons.org/licenses/by-nc-sa/3.0/). Assets created by Goonstation. diff --git a/SQL/tgstation_schema.sql b/SQL/tgstation_schema.sql index 713099e62e67..1841069dd8d6 100644 --- a/SQL/tgstation_schema.sql +++ b/SQL/tgstation_schema.sql @@ -1,600 +1,600 @@ -/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; -/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; -/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; -/*!40101 SET NAMES utf8 */; -/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; -/*!40103 SET TIME_ZONE='+00:00' */; -/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; -/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; -/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; -/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; - --- --- Table structure for table `admin` --- - -DROP TABLE IF EXISTS `admin`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -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; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `admin_log` --- - -DROP TABLE IF EXISTS `admin_log`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `admin_log` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `datetime` datetime NOT NULL, - `round_id` int(11) unsigned NOT NULL, - `adminckey` varchar(32) NOT NULL, - `adminip` int(10) unsigned NOT NULL, - `operation` enum('add admin','remove admin','change admin rank','add rank','remove rank','change rank flags') NOT NULL, - `target` varchar(32) NOT NULL, - `log` varchar(1000) NOT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `admin_ranks` --- - -DROP TABLE IF EXISTS `admin_ranks`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `admin_ranks` ( - `rank` varchar(32) NOT NULL, - `flags` smallint(5) unsigned NOT NULL, - `exclude_flags` smallint(5) unsigned NOT NULL, - `can_edit_flags` smallint(5) unsigned NOT NULL, - PRIMARY KEY (`rank`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `ban` --- - -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) 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; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `connection_log` --- - -DROP TABLE IF EXISTS `connection_log`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `connection_log` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `datetime` datetime DEFAULT NULL, - `server_ip` int(10) unsigned NOT NULL, - `server_port` smallint(5) unsigned NOT NULL, - `round_id` int(11) unsigned NOT NULL, - `ckey` varchar(45) DEFAULT NULL, - `ip` int(10) unsigned NOT NULL, - `computerid` varchar(45) DEFAULT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `death` --- - -DROP TABLE IF EXISTS `death`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `death` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `pod` varchar(50) NOT NULL, - `x_coord` smallint(5) unsigned NOT NULL, - `y_coord` smallint(5) unsigned NOT NULL, - `z_coord` smallint(5) unsigned NOT NULL, - `mapname` varchar(32) NOT NULL, - `server_ip` int(10) unsigned NOT NULL, - `server_port` smallint(5) unsigned NOT NULL, - `round_id` int(11) NOT NULL, - `tod` datetime NOT NULL COMMENT 'Time of death', - `job` varchar(32) NOT NULL, - `special` varchar(32) DEFAULT NULL, - `name` varchar(96) NOT NULL, - `byondkey` varchar(32) NOT NULL, - `laname` varchar(96) DEFAULT NULL, - `lakey` varchar(32) DEFAULT NULL, - `bruteloss` smallint(5) unsigned NOT NULL, - `brainloss` smallint(5) unsigned NOT NULL, - `fireloss` smallint(5) unsigned NOT NULL, - `oxyloss` smallint(5) unsigned NOT NULL, - `toxloss` smallint(5) unsigned NOT NULL, - `cloneloss` smallint(5) unsigned NOT NULL, - `staminaloss` smallint(5) unsigned NOT NULL, - `last_words` varchar(255) DEFAULT NULL, - `suicide` tinyint(1) NOT NULL DEFAULT '0', - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `feedback` --- - -DROP TABLE IF EXISTS `feedback`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `feedback` ( - `id` int(11) unsigned NOT NULL AUTO_INCREMENT, - `datetime` datetime NOT NULL, - `round_id` int(11) unsigned NOT NULL, - `key_name` varchar(32) NOT NULL, - `key_type` enum('text', 'amount', 'tally', 'nested tally', 'associative') NOT NULL, - `version` tinyint(3) unsigned NOT NULL, - `json` json NOT NULL, - PRIMARY KEY (`id`) -) ENGINE=MyISAM DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `ipintel` --- - -DROP TABLE IF EXISTS `ipintel`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `ipintel` ( - `ip` int(10) unsigned NOT NULL, - `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `intel` double NOT NULL DEFAULT '0', - PRIMARY KEY (`ip`), - KEY `idx_ipintel` (`ip`,`intel`,`date`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `legacy_population` --- - -DROP TABLE IF EXISTS `legacy_population`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `legacy_population` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `playercount` int(11) DEFAULT NULL, - `admincount` int(11) DEFAULT NULL, - `time` datetime NOT NULL, - `server_ip` int(10) unsigned NOT NULL, - `server_port` smallint(5) unsigned NOT NULL, - `round_id` int(11) unsigned NOT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `library` --- - -DROP TABLE IF EXISTS `library`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `library` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `author` varchar(45) NOT NULL, - `title` varchar(45) NOT NULL, - `content` text NOT NULL, - `category` enum('Any','Fiction','Non-Fiction','Adult','Reference','Religion') NOT NULL, - `ckey` varchar(32) NOT NULL DEFAULT 'LEGACY', - `datetime` datetime NOT NULL, - `deleted` tinyint(1) unsigned DEFAULT NULL, - `round_id_created` int(11) unsigned NOT NULL, - PRIMARY KEY (`id`), - KEY `deleted_idx` (`deleted`), - 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; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `messages` --- - -DROP TABLE IF EXISTS `messages`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `messages` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `type` enum('memo','message','message sent','note','watchlist entry') NOT NULL, - `targetckey` varchar(32) NOT NULL, - `adminckey` varchar(32) NOT NULL, - `text` varchar(2048) NOT NULL, - `timestamp` datetime NOT NULL, - `server` varchar(32) DEFAULT NULL, - `server_ip` int(10) unsigned NOT NULL, - `server_port` smallint(5) unsigned NOT NULL, - `round_id` int(11) unsigned NOT NULL, - `secret` tinyint(1) unsigned NOT NULL, - `expire_timestamp` datetime DEFAULT NULL, - `severity` enum('high','medium','minor','none') 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; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `role_time` --- - -DROP TABLE IF EXISTS `role_time`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; - -CREATE TABLE `role_time` -( `ckey` VARCHAR(32) NOT NULL , - `job` VARCHAR(32) NOT NULL , - `minutes` INT UNSIGNED NOT NULL, - PRIMARY KEY (`ckey`, `job`) - ) ENGINE = InnoDB; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `role_time` --- - -DROP TABLE IF EXISTS `role_time_log`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; - -CREATE TABLE IF NOT EXISTS `role_time_log` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `ckey` varchar(32) NOT NULL, - `job` varchar(128) NOT NULL, - `delta` int(11) NOT NULL, - `datetime` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), - PRIMARY KEY (`id`), - KEY `ckey` (`ckey`), - KEY `job` (`job`), - KEY `datetime` (`datetime`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `player` --- - -DROP TABLE IF EXISTS `player`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `player` ( - `ckey` varchar(32) NOT NULL, - `byond_key` varchar(32) DEFAULT NULL, - `firstseen` datetime NOT NULL, - `firstseen_round_id` int(11) unsigned NOT NULL, - `lastseen` datetime NOT NULL, - `lastseen_round_id` int(11) unsigned NOT NULL, - `ip` int(10) unsigned NOT NULL, - `computerid` varchar(32) NOT NULL, - `lastadminrank` varchar(32) NOT NULL DEFAULT 'Player', - `accountjoindate` DATE DEFAULT NULL, - `flags` smallint(5) unsigned DEFAULT '0' NOT NULL, - `discord_id` BIGINT(20) NULL DEFAULT NULL, - `antag_tokens` tinyint(4) unsigned DEFAULT '0', - `metacoins` int(10) unsigned NOT NULL DEFAULT '0', - PRIMARY KEY (`ckey`), - KEY `idx_player_cid_ckey` (`computerid`,`ckey`), - KEY `idx_player_ip_ckey` (`ip`,`ckey`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `poll_option` --- - -DROP TABLE IF EXISTS `poll_option`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `poll_option` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `pollid` int(11) NOT NULL, - `text` varchar(255) NOT NULL, - `minval` int(3) DEFAULT NULL, - `maxval` int(3) DEFAULT NULL, - `descmin` varchar(32) DEFAULT NULL, - `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; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `poll_question` --- - -DROP TABLE IF EXISTS `poll_question`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -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) 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_deleted_id` (`starttime`,`endtime`, `deleted`, `id`), - KEY `idx_pquest_id_time_type_admin` (`id`,`starttime`,`endtime`,`polltype`,`adminonly`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `poll_textreply` --- - -DROP TABLE IF EXISTS `poll_textreply`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `poll_textreply` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `datetime` datetime NOT NULL, - `pollid` int(11) NOT NULL, - `ckey` varchar(32) NOT NULL, - `ip` int(10) unsigned NOT NULL, - `replytext` varchar(2048) NOT NULL, - `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; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `poll_vote` --- - -DROP TABLE IF EXISTS `poll_vote`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `poll_vote` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `datetime` datetime NOT NULL, - `pollid` int(11) NOT NULL, - `optionid` int(11) NOT NULL, - `ckey` varchar(32) NOT NULL, - `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; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `round` --- -DROP TABLE IF EXISTS `round`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `round` ( - `id` INT(11) NOT NULL AUTO_INCREMENT, - `initialize_datetime` DATETIME NOT NULL, - `start_datetime` DATETIME NULL, - `shutdown_datetime` DATETIME NULL, - `end_datetime` DATETIME NULL, - `server_ip` INT(10) UNSIGNED NOT NULL, - `server_port` SMALLINT(5) UNSIGNED NOT NULL, - `commit_hash` CHAR(40) NULL, - `game_mode` VARCHAR(32) NULL, - `game_mode_result` VARCHAR(64) NULL, - `end_state` VARCHAR(64) NULL, - `shuttle_name` VARCHAR(64) NULL, - `map_name` VARCHAR(32) NULL, - `station_name` VARCHAR(80) NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; -/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; - --- --- Table structure for table `schema_revision` --- -DROP TABLE IF EXISTS `schema_revision`; -CREATE TABLE `schema_revision` ( - `major` TINYINT(3) unsigned NOT NULL, - `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; - --- --- 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`) -) 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 -$$ -CREATE TRIGGER `role_timeTloginsert` AFTER INSERT ON `role_time` FOR EACH ROW BEGIN INSERT into role_time_log (ckey, job, delta) VALUES (NEW.ckey, NEW.job, NEW.minutes); -END -$$ -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 ; - -/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; -/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; -/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; -/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; -/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; -/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; -/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; - -DROP TABLE IF EXISTS `mentor`; -CREATE TABLE `mentor` ( - `ckey` text NOT NULL -) ENGINE=InnoDB DEFAULT CHARSET=latin1; - -DROP TABLE IF EXISTS `mentor_memo`; -CREATE TABLE `mentor_memo` ( - `ckey` varchar(32) NOT NULL, - `memotext` text NOT NULL, - `timestamp` datetime NOT NULL, - `last_editor` varchar(32) DEFAULT NULL, - `edits` text, - PRIMARY KEY (`ckey`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `admin` +-- + +DROP TABLE IF EXISTS `admin`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +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; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `admin_log` +-- + +DROP TABLE IF EXISTS `admin_log`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `admin_log` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `datetime` datetime NOT NULL, + `round_id` int(11) unsigned NOT NULL, + `adminckey` varchar(32) NOT NULL, + `adminip` int(10) unsigned NOT NULL, + `operation` enum('add admin','remove admin','change admin rank','add rank','remove rank','change rank flags') NOT NULL, + `target` varchar(32) NOT NULL, + `log` varchar(1000) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `admin_ranks` +-- + +DROP TABLE IF EXISTS `admin_ranks`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `admin_ranks` ( + `rank` varchar(32) NOT NULL, + `flags` smallint(5) unsigned NOT NULL, + `exclude_flags` smallint(5) unsigned NOT NULL, + `can_edit_flags` smallint(5) unsigned NOT NULL, + PRIMARY KEY (`rank`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `ban` +-- + +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) 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; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `connection_log` +-- + +DROP TABLE IF EXISTS `connection_log`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `connection_log` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `datetime` datetime DEFAULT NULL, + `server_ip` int(10) unsigned NOT NULL, + `server_port` smallint(5) unsigned NOT NULL, + `round_id` int(11) unsigned NOT NULL, + `ckey` varchar(45) DEFAULT NULL, + `ip` int(10) unsigned NOT NULL, + `computerid` varchar(45) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `death` +-- + +DROP TABLE IF EXISTS `death`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `death` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `pod` varchar(50) NOT NULL, + `x_coord` smallint(5) unsigned NOT NULL, + `y_coord` smallint(5) unsigned NOT NULL, + `z_coord` smallint(5) unsigned NOT NULL, + `mapname` varchar(32) NOT NULL, + `server_ip` int(10) unsigned NOT NULL, + `server_port` smallint(5) unsigned NOT NULL, + `round_id` int(11) NOT NULL, + `tod` datetime NOT NULL COMMENT 'Time of death', + `job` varchar(32) NOT NULL, + `special` varchar(32) DEFAULT NULL, + `name` varchar(96) NOT NULL, + `byondkey` varchar(32) NOT NULL, + `laname` varchar(96) DEFAULT NULL, + `lakey` varchar(32) DEFAULT NULL, + `bruteloss` smallint(5) unsigned NOT NULL, + `brainloss` smallint(5) unsigned NOT NULL, + `fireloss` smallint(5) unsigned NOT NULL, + `oxyloss` smallint(5) unsigned NOT NULL, + `toxloss` smallint(5) unsigned NOT NULL, + `cloneloss` smallint(5) unsigned NOT NULL, + `staminaloss` smallint(5) unsigned NOT NULL, + `last_words` varchar(255) DEFAULT NULL, + `suicide` tinyint(1) NOT NULL DEFAULT '0', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `feedback` +-- + +DROP TABLE IF EXISTS `feedback`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `feedback` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `datetime` datetime NOT NULL, + `round_id` int(11) unsigned NOT NULL, + `key_name` varchar(32) NOT NULL, + `key_type` enum('text', 'amount', 'tally', 'nested tally', 'associative') NOT NULL, + `version` tinyint(3) unsigned NOT NULL, + `json` json NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `ipintel` +-- + +DROP TABLE IF EXISTS `ipintel`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `ipintel` ( + `ip` int(10) unsigned NOT NULL, + `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `intel` double NOT NULL DEFAULT '0', + PRIMARY KEY (`ip`), + KEY `idx_ipintel` (`ip`,`intel`,`date`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `legacy_population` +-- + +DROP TABLE IF EXISTS `legacy_population`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `legacy_population` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `playercount` int(11) DEFAULT NULL, + `admincount` int(11) DEFAULT NULL, + `time` datetime NOT NULL, + `server_ip` int(10) unsigned NOT NULL, + `server_port` smallint(5) unsigned NOT NULL, + `round_id` int(11) unsigned NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `library` +-- + +DROP TABLE IF EXISTS `library`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `library` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `author` varchar(45) NOT NULL, + `title` varchar(45) NOT NULL, + `content` text NOT NULL, + `category` enum('Any','Fiction','Non-Fiction','Adult','Reference','Religion') NOT NULL, + `ckey` varchar(32) NOT NULL DEFAULT 'LEGACY', + `datetime` datetime NOT NULL, + `deleted` tinyint(1) unsigned DEFAULT NULL, + `round_id_created` int(11) unsigned NOT NULL, + PRIMARY KEY (`id`), + KEY `deleted_idx` (`deleted`), + 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; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `messages` +-- + +DROP TABLE IF EXISTS `messages`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `messages` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `type` enum('memo','message','message sent','note','watchlist entry') NOT NULL, + `targetckey` varchar(32) NOT NULL, + `adminckey` varchar(32) NOT NULL, + `text` varchar(2048) NOT NULL, + `timestamp` datetime NOT NULL, + `server` varchar(32) DEFAULT NULL, + `server_ip` int(10) unsigned NOT NULL, + `server_port` smallint(5) unsigned NOT NULL, + `round_id` int(11) unsigned NOT NULL, + `secret` tinyint(1) unsigned NOT NULL, + `expire_timestamp` datetime DEFAULT NULL, + `severity` enum('high','medium','minor','none') 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; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `role_time` +-- + +DROP TABLE IF EXISTS `role_time`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; + +CREATE TABLE `role_time` +( `ckey` VARCHAR(32) NOT NULL , + `job` VARCHAR(32) NOT NULL , + `minutes` INT UNSIGNED NOT NULL, + PRIMARY KEY (`ckey`, `job`) + ) ENGINE = InnoDB; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `role_time` +-- + +DROP TABLE IF EXISTS `role_time_log`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; + +CREATE TABLE IF NOT EXISTS `role_time_log` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `ckey` varchar(32) NOT NULL, + `job` varchar(128) NOT NULL, + `delta` int(11) NOT NULL, + `datetime` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), + PRIMARY KEY (`id`), + KEY `ckey` (`ckey`), + KEY `job` (`job`), + KEY `datetime` (`datetime`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `player` +-- + +DROP TABLE IF EXISTS `player`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `player` ( + `ckey` varchar(32) NOT NULL, + `byond_key` varchar(32) DEFAULT NULL, + `firstseen` datetime NOT NULL, + `firstseen_round_id` int(11) unsigned NOT NULL, + `lastseen` datetime NOT NULL, + `lastseen_round_id` int(11) unsigned NOT NULL, + `ip` int(10) unsigned NOT NULL, + `computerid` varchar(32) NOT NULL, + `lastadminrank` varchar(32) NOT NULL DEFAULT 'Player', + `accountjoindate` DATE DEFAULT NULL, + `flags` smallint(5) unsigned DEFAULT '0' NOT NULL, + `discord_id` BIGINT(20) NULL DEFAULT NULL, + `antag_tokens` tinyint(4) unsigned DEFAULT '0', + `metacoins` int(10) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`ckey`), + KEY `idx_player_cid_ckey` (`computerid`,`ckey`), + KEY `idx_player_ip_ckey` (`ip`,`ckey`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `poll_option` +-- + +DROP TABLE IF EXISTS `poll_option`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `poll_option` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `pollid` int(11) NOT NULL, + `text` varchar(255) NOT NULL, + `minval` int(3) DEFAULT NULL, + `maxval` int(3) DEFAULT NULL, + `descmin` varchar(32) DEFAULT NULL, + `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; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `poll_question` +-- + +DROP TABLE IF EXISTS `poll_question`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +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) 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_deleted_id` (`starttime`,`endtime`, `deleted`, `id`), + KEY `idx_pquest_id_time_type_admin` (`id`,`starttime`,`endtime`,`polltype`,`adminonly`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `poll_textreply` +-- + +DROP TABLE IF EXISTS `poll_textreply`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `poll_textreply` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `datetime` datetime NOT NULL, + `pollid` int(11) NOT NULL, + `ckey` varchar(32) NOT NULL, + `ip` int(10) unsigned NOT NULL, + `replytext` varchar(2048) NOT NULL, + `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; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `poll_vote` +-- + +DROP TABLE IF EXISTS `poll_vote`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `poll_vote` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `datetime` datetime NOT NULL, + `pollid` int(11) NOT NULL, + `optionid` int(11) NOT NULL, + `ckey` varchar(32) NOT NULL, + `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; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `round` +-- +DROP TABLE IF EXISTS `round`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `round` ( + `id` INT(11) NOT NULL AUTO_INCREMENT, + `initialize_datetime` DATETIME NOT NULL, + `start_datetime` DATETIME NULL, + `shutdown_datetime` DATETIME NULL, + `end_datetime` DATETIME NULL, + `server_ip` INT(10) UNSIGNED NOT NULL, + `server_port` SMALLINT(5) UNSIGNED NOT NULL, + `commit_hash` CHAR(40) NULL, + `game_mode` VARCHAR(32) NULL, + `game_mode_result` VARCHAR(64) NULL, + `end_state` VARCHAR(64) NULL, + `shuttle_name` VARCHAR(64) NULL, + `map_name` VARCHAR(32) NULL, + `station_name` VARCHAR(80) NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +-- +-- Table structure for table `schema_revision` +-- +DROP TABLE IF EXISTS `schema_revision`; +CREATE TABLE `schema_revision` ( + `major` TINYINT(3) unsigned NOT NULL, + `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; + +-- +-- 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`) +) 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 +$$ +CREATE TRIGGER `role_timeTloginsert` AFTER INSERT ON `role_time` FOR EACH ROW BEGIN INSERT into role_time_log (ckey, job, delta) VALUES (NEW.ckey, NEW.job, NEW.minutes); +END +$$ +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 ; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +DROP TABLE IF EXISTS `mentor`; +CREATE TABLE `mentor` ( + `ckey` text NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +DROP TABLE IF EXISTS `mentor_memo`; +CREATE TABLE `mentor_memo` ( + `ckey` varchar(32) NOT NULL, + `memotext` text NOT NULL, + `timestamp` datetime NOT NULL, + `last_editor` varchar(32) DEFAULT NULL, + `edits` text, + PRIMARY KEY (`ckey`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; diff --git a/TGS3.json b/TGS3.json index 2a2b80cf9586..39b75bd913c0 100644 --- a/TGS3.json +++ b/TGS3.json @@ -1,22 +1,22 @@ -{ - "documentation": "/tg/station server 3 configuration file", - "changelog": { - "script": "tools/ss13_genchangelog.py", - "arguments": "html/changelog.html html/changelogs", - "pip_dependancies": [ - "PyYaml", - "beautifulsoup4" - ] - }, - "synchronize_paths": [ - "html/changelog.html", - "html/changelogs/*" - ], - "static_directories": [ - "config", - "data" - ], - "dlls": [ - "libmariadb.dll" - ] - } +{ + "documentation": "/tg/station server 3 configuration file", + "changelog": { + "script": "tools/ss13_genchangelog.py", + "arguments": "html/changelog.html html/changelogs", + "pip_dependancies": [ + "PyYaml", + "beautifulsoup4" + ] + }, + "synchronize_paths": [ + "html/changelog.html", + "html/changelogs/*" + ], + "static_directories": [ + "config", + "data" + ], + "dlls": [ + "libmariadb.dll" + ] + } diff --git a/_maps/boxstation.json b/_maps/boxstation.json index 143fcae5bc6f..3c71c654faa7 100644 --- a/_maps/boxstation.json +++ b/_maps/boxstation.json @@ -1,11 +1,11 @@ -{ - "map_name": "Box Station", - "map_path": "map_files/BoxStation", - "map_file": "BoxStation.dmm", - "shuttles": { - "cargo": "cargo_box", - "ferry": "ferry_fancy", - "whiteship": "whiteship_box", - "emergency": "emergency_box" - } -} +{ + "map_name": "Box Station", + "map_path": "map_files/BoxStation", + "map_file": "BoxStation.dmm", + "shuttles": { + "cargo": "cargo_box", + "ferry": "ferry_fancy", + "whiteship": "whiteship_box", + "emergency": "emergency_box" + } +} diff --git a/_maps/multiz_debug.json b/_maps/multiz_debug.json index e8a474161e76..8d634f7b343a 100644 --- a/_maps/multiz_debug.json +++ b/_maps/multiz_debug.json @@ -1,6 +1,6 @@ -{ - "map_name": "MultiZ Debug", - "map_path": "map_files/debug", - "map_file": "multiz.dmm", - "traits": [{"Up" : 1, "Linkage" : "Cross"}, {"Up" : 1, "Down" : -1, "Baseturf" : "/turf/open/openspace", "Linkage" : "Cross"}, {"Down" : -1, "Baseturf" : "/turf/open/openspace", "Linkage" : "Cross"}] - } +{ + "map_name": "MultiZ Debug", + "map_path": "map_files/debug", + "map_file": "multiz.dmm", + "traits": [{"Up" : 1, "Linkage" : "Cross"}, {"Up" : 1, "Down" : -1, "Baseturf" : "/turf/open/openspace", "Linkage" : "Cross"}, {"Down" : -1, "Baseturf" : "/turf/open/openspace", "Linkage" : "Cross"}] + } diff --git a/_maps/runtimestation.json b/_maps/runtimestation.json index f9333c65a231..ca2cc3d3790c 100644 --- a/_maps/runtimestation.json +++ b/_maps/runtimestation.json @@ -1,8 +1,8 @@ -{ - "map_name": "Runtime Station", - "map_path": "map_files/debug", - "map_file": "runtimestation.dmm", - "shuttles": { - "cargo": "cargo_delta" - } -} +{ + "map_name": "Runtime Station", + "map_path": "map_files/debug", + "map_file": "runtimestation.dmm", + "shuttles": { + "cargo": "cargo_delta" + } +} diff --git a/byond-extools.dll b/byond-extools.dll index dc90fd1ecc4f..c60100399a3e 100644 Binary files a/byond-extools.dll and b/byond-extools.dll differ diff --git a/code/__DEFINES/_protect.dm b/code/__DEFINES/_protect.dm index 27baee2f882d..8e162a8b3368 100644 --- a/code/__DEFINES/_protect.dm +++ b/code/__DEFINES/_protect.dm @@ -1,11 +1,11 @@ -///Protects a datum from being VV'd -#define GENERAL_PROTECT_DATUM(Path)\ -##Path/can_vv_get(var_name){\ - return FALSE;\ -}\ -##Path/vv_edit_var(var_name, var_value){\ - return FALSE;\ -}\ -##Path/CanProcCall(procname){\ - return FALSE;\ -} +///Protects a datum from being VV'd +#define GENERAL_PROTECT_DATUM(Path)\ +##Path/can_vv_get(var_name){\ + return FALSE;\ +}\ +##Path/vv_edit_var(var_name, var_value){\ + return FALSE;\ +}\ +##Path/CanProcCall(procname){\ + return FALSE;\ +} diff --git a/code/__DEFINES/_readme.dm b/code/__DEFINES/_readme.dm index 1b78d44a10d0..8bf6ada6477a 100644 --- a/code/__DEFINES/_readme.dm +++ b/code/__DEFINES/_readme.dm @@ -1,14 +1,14 @@ -/* - This folder is full of #define statements. They are similar to constants, - but must come before any code that references them, and they do not take up - memory the way constants do. - - The values in this folder are NOT options. They are not for hosts to play with. - Some of the values are arbitrary and only need to be different from similar constants; - for example, the genetic mutation numbers in genetics.dm mean nothing, but MUST be distinct. - - It is wise not to touch them unless you understand what they do, where they're used, - and most importantly, - how to undo your changes if you screw it up. - - Sayu -*/ +/* + This folder is full of #define statements. They are similar to constants, + but must come before any code that references them, and they do not take up + memory the way constants do. + + The values in this folder are NOT options. They are not for hosts to play with. + Some of the values are arbitrary and only need to be different from similar constants; + for example, the genetic mutation numbers in genetics.dm mean nothing, but MUST be distinct. + + It is wise not to touch them unless you understand what they do, where they're used, + and most importantly, + how to undo your changes if you screw it up. + - Sayu +*/ diff --git a/code/__DEFINES/admin.dm b/code/__DEFINES/admin.dm index dfdeb26bcf5e..3402ed3c6ac8 100644 --- a/code/__DEFINES/admin.dm +++ b/code/__DEFINES/admin.dm @@ -1,120 +1,121 @@ -//A set of constants used to determine which type of mute an admin wishes to apply: -//Please read and understand the muting/automuting stuff before changing these. MUTE_IC_AUTO etc = (MUTE_IC << 1) -//Therefore there needs to be a gap between the flags for the automute flags -#define MUTE_IC (1<<0) -#define MUTE_OOC (1<<1) -#define MUTE_PRAY (1<<2) -#define MUTE_ADMINHELP (1<<3) -#define MUTE_DEADCHAT (1<<4) -#define MUTE_MENTORHELP (1<<5) -#define MUTE_ALL (~0) - -//Some constants for DB_Ban -#define BANTYPE_PERMA 1 -#define BANTYPE_TEMP 2 -#define BANTYPE_JOB_PERMA 3 -#define BANTYPE_JOB_TEMP 4 -/// used to locate stuff to unban. -#define BANTYPE_ANY_FULLBAN 5 - -#define BANTYPE_ADMIN_PERMA 7 -#define BANTYPE_ADMIN_TEMP 8 -/// used to remove jobbans -#define BANTYPE_ANY_JOB 9 - -//Admin Permissions -#define R_BUILD (1<<0) -#define R_ADMIN (1<<1) -#define R_BAN (1<<2) -#define R_FUN (1<<3) -#define R_SERVER (1<<4) -#define R_DEBUG (1<<5) -#define R_POSSESS (1<<6) -#define R_PERMISSIONS (1<<7) -#define R_STEALTH (1<<8) -#define R_POLL (1<<9) -#define R_VAREDIT (1<<10) -#define R_SOUND (1<<11) -#define R_SPAWN (1<<12) -#define R_AUTOADMIN (1<<13) -#define R_DBRANKS (1<<14) -#define R_MENTOR (1<<15) - -#define R_DEFAULT R_AUTOADMIN - -#define R_EVERYTHING (1<<15)-1 //the sum of all other rank permissions, used for +EVERYTHING - -#define ADMIN_QUE(user) "(?)" -#define ADMIN_FLW(user) "(FLW)" -#define ADMIN_PP(user) "(PP)" -#define ADMIN_VV(atom) "(VV)" -#define ADMIN_SM(user) "(SM)" -#define ADMIN_TP(user) "(TP)" -#define ADMIN_KICK(user) "(KICK)" -#define ADMIN_CENTCOM_REPLY(user) "(RPLY)" -#define ADMIN_SYNDICATE_REPLY(user) "(RPLY)" -#define ADMIN_SC(user) "(SC)" -#define ADMIN_SMITE(user) "(SMITE)" -#define ADMIN_LOOKUP(user) "[key_name_admin(user)][ADMIN_QUE(user)]" -#define ADMIN_LOOKUPFLW(user) "[key_name_admin(user)][ADMIN_QUE(user)] [ADMIN_FLW(user)]" -#define ADMIN_SET_SD_CODE "(SETCODE)" -#define ADMIN_FULLMONTY_NONAME(user) "[ADMIN_QUE(user)] [ADMIN_PP(user)] [ADMIN_VV(user)] [ADMIN_SM(user)] [ADMIN_FLW(user)] [ADMIN_TP(user)] [ADMIN_INDIVIDUALLOG(user)] [ADMIN_SMITE(user)]" -#define ADMIN_FULLMONTY(user) "[key_name_admin(user)] [ADMIN_FULLMONTY_NONAME(user)]" -#define ADMIN_JMP(src) "(JMP)" -#define COORD(src) "[src ? "([src.x],[src.y],[src.z])" : "nonexistent location"]" -#define AREACOORD(src) "[src ? "[get_area_name(src, TRUE)] ([src.x], [src.y], [src.z])" : "nonexistent location"]" -#define ADMIN_COORDJMP(src) "[src ? "[COORD(src)] [ADMIN_JMP(src)]" : "nonexistent location"]" -#define ADMIN_VERBOSEJMP(src) "[src ? "[AREACOORD(src)] [ADMIN_JMP(src)]" : "nonexistent location"]" -#define ADMIN_INDIVIDUALLOG(user) "(LOGS)" -#define ADMIN_FAX(user, fax, faxtype, sent) "(FAX)" - -#define ADMIN_PUNISHMENT_BREAK_BONES "Break all bones" -#define ADMIN_PUNISHMENT_LIGHTNING "Lightning bolt" -#define ADMIN_PUNISHMENT_BRAINDAMAGE "Brain damage" -#define ADMIN_PUNISHMENT_GIB "Gib" -#define ADMIN_PUNISHMENT_BSA "Bluespace Artillery Device" -#define ADMIN_PUNISHMENT_FIREBALL "Fireball" -#define ADMIN_PUNISHMENT_ROD "Immovable Rod" -#define ADMIN_PUNISHMENT_SUPPLYPOD_QUICK "Supply Pod (Quick)" -#define ADMIN_PUNISHMENT_SUPPLYPOD "Supply Pod" -#define ADMIN_PUNISHMENT_MAZING "Puzzle" -#define ADMIN_PUNISHMENT_IMMERSE "Fully Immerse" - -#define AHELP_ACTIVE 1 -#define AHELP_CLOSED 2 -#define AHELP_RESOLVED 3 - -/// Amount of time (in deciseconds) after the rounds starts, that the player disconnect report is issued. -#define ROUNDSTART_LOGOUT_REPORT_TIME 6000 - -/// Number of identical messages required before the spam-prevention will warn you to stfu -#define SPAM_TRIGGER_WARNING 5 -/// Number of identical messages required before the spam-prevention will automute you -#define SPAM_TRIGGER_AUTOMUTE 10 - -///Max length of a keypress command before it's considered to be a forged packet/bogus command -#define MAX_KEYPRESS_COMMANDLENGTH 16 -///Maximum keys that can be bound to one button -#define MAX_COMMANDS_PER_KEY 5 -///Maximum keys per keybind -#define MAX_KEYS_PER_KEYBIND 3 -///Max amount of keypress messages per second over two seconds before client is autokicked -#define MAX_KEYPRESS_AUTOKICK 50 -///Length of held key rolling buffer -#define HELD_KEY_BUFFER_LENGTH 15 - -#define STICKYBAN_DB_CACHE_TIME 10 SECONDS -#define STICKYBAN_ROGUE_CHECK_TIME 5 - - -/// Shown to vicitm of staff of change and related effects. -#define POLICY_POLYMORPH "polymorph" -/// Shown on top of policy verb window -#define POLICY_VERB_HEADER "policy_verb_header" - -//How many things you can spawn at once with spawn verb/create panel -#define ADMIN_SPAWN_CAP 100 - -// LOG BROWSE TYPES -#define BROWSE_ROOT_ALL_LOGS 1 -#define BROWSE_ROOT_CURRENT_LOGS 2 +//A set of constants used to determine which type of mute an admin wishes to apply: +//Please read and understand the muting/automuting stuff before changing these. MUTE_IC_AUTO etc = (MUTE_IC << 1) +//Therefore there needs to be a gap between the flags for the automute flags +#define MUTE_IC (1<<0) +#define MUTE_OOC (1<<1) +#define MUTE_PRAY (1<<2) +#define MUTE_ADMINHELP (1<<3) +#define MUTE_DEADCHAT (1<<4) +#define MUTE_MENTORHELP (1<<5) +#define MUTE_ALL (~0) + +//Some constants for DB_Ban +#define BANTYPE_PERMA 1 +#define BANTYPE_TEMP 2 +#define BANTYPE_JOB_PERMA 3 +#define BANTYPE_JOB_TEMP 4 +/// used to locate stuff to unban. +#define BANTYPE_ANY_FULLBAN 5 + +#define BANTYPE_ADMIN_PERMA 7 +#define BANTYPE_ADMIN_TEMP 8 +/// used to remove jobbans +#define BANTYPE_ANY_JOB 9 + +//Admin Permissions +#define R_BUILD (1<<0) +#define R_ADMIN (1<<1) +#define R_BAN (1<<2) +#define R_FUN (1<<3) +#define R_SERVER (1<<4) +#define R_DEBUG (1<<5) +#define R_POSSESS (1<<6) +#define R_PERMISSIONS (1<<7) +#define R_STEALTH (1<<8) +#define R_POLL (1<<9) +#define R_VAREDIT (1<<10) +#define R_SOUND (1<<11) +#define R_SPAWN (1<<12) +#define R_AUTOADMIN (1<<13) +#define R_DBRANKS (1<<14) +#define R_MENTOR (1<<15) + +#define R_DEFAULT R_AUTOADMIN + +#define R_EVERYTHING (1<<15)-1 //the sum of all other rank permissions, used for +EVERYTHING + +#define ADMIN_QUE(user) "(?)" +#define ADMIN_FLW(user) "(FLW)" +#define ADMIN_PP(user) "(PP)" +#define ADMIN_VV(atom) "(VV)" +#define ADMIN_SM(user) "(SM)" +#define ADMIN_TP(user) "(TP)" +#define ADMIN_SP(user) "(SP)" +#define ADMIN_KICK(user) "(KICK)" +#define ADMIN_CENTCOM_REPLY(user) "(RPLY)" +#define ADMIN_SYNDICATE_REPLY(user) "(RPLY)" +#define ADMIN_SC(user) "(SC)" +#define ADMIN_SMITE(user) "(SMITE)" +#define ADMIN_LOOKUP(user) "[key_name_admin(user)][ADMIN_QUE(user)]" +#define ADMIN_LOOKUPFLW(user) "[key_name_admin(user)][ADMIN_QUE(user)] [ADMIN_FLW(user)]" +#define ADMIN_SET_SD_CODE "(SETCODE)" +#define ADMIN_FULLMONTY_NONAME(user) "[ADMIN_QUE(user)] [ADMIN_PP(user)] [ADMIN_VV(user)] [ADMIN_SM(user)] [ADMIN_FLW(user)] [ADMIN_TP(user)] [ADMIN_INDIVIDUALLOG(user)] [ADMIN_SMITE(user)]" +#define ADMIN_FULLMONTY(user) "[key_name_admin(user)] [ADMIN_FULLMONTY_NONAME(user)]" +#define ADMIN_JMP(src) "(JMP)" +#define COORD(src) "[src ? "([src.x],[src.y],[src.z])" : "nonexistent location"]" +#define AREACOORD(src) "[src ? "[get_area_name(src, TRUE)] ([src.x], [src.y], [src.z])" : "nonexistent location"]" +#define ADMIN_COORDJMP(src) "[src ? "[COORD(src)] [ADMIN_JMP(src)]" : "nonexistent location"]" +#define ADMIN_VERBOSEJMP(src) "[src ? "[AREACOORD(src)] [ADMIN_JMP(src)]" : "nonexistent location"]" +#define ADMIN_INDIVIDUALLOG(user) "(LOGS)" +#define ADMIN_FAX(user, fax, faxtype, sent) "(FAX)" + +#define ADMIN_PUNISHMENT_BREAK_BONES "Break all bones" +#define ADMIN_PUNISHMENT_LIGHTNING "Lightning bolt" +#define ADMIN_PUNISHMENT_BRAINDAMAGE "Brain damage" +#define ADMIN_PUNISHMENT_GIB "Gib" +#define ADMIN_PUNISHMENT_BSA "Bluespace Artillery Device" +#define ADMIN_PUNISHMENT_FIREBALL "Fireball" +#define ADMIN_PUNISHMENT_ROD "Immovable Rod" +#define ADMIN_PUNISHMENT_SUPPLYPOD_QUICK "Supply Pod (Quick)" +#define ADMIN_PUNISHMENT_SUPPLYPOD "Supply Pod" +#define ADMIN_PUNISHMENT_MAZING "Puzzle" +#define ADMIN_PUNISHMENT_IMMERSE "Fully Immerse" + +#define AHELP_ACTIVE 1 +#define AHELP_CLOSED 2 +#define AHELP_RESOLVED 3 + +/// Amount of time (in deciseconds) after the rounds starts, that the player disconnect report is issued. +#define ROUNDSTART_LOGOUT_REPORT_TIME 6000 + +/// Number of identical messages required before the spam-prevention will warn you to stfu +#define SPAM_TRIGGER_WARNING 5 +/// Number of identical messages required before the spam-prevention will automute you +#define SPAM_TRIGGER_AUTOMUTE 10 + +///Max length of a keypress command before it's considered to be a forged packet/bogus command +#define MAX_KEYPRESS_COMMANDLENGTH 16 +///Maximum keys that can be bound to one button +#define MAX_COMMANDS_PER_KEY 5 +///Maximum keys per keybind +#define MAX_KEYS_PER_KEYBIND 3 +///Max amount of keypress messages per second over two seconds before client is autokicked +#define MAX_KEYPRESS_AUTOKICK 50 +///Length of held key rolling buffer +#define HELD_KEY_BUFFER_LENGTH 15 + +#define STICKYBAN_DB_CACHE_TIME 10 SECONDS +#define STICKYBAN_ROGUE_CHECK_TIME 5 + + +/// Shown to vicitm of staff of change and related effects. +#define POLICY_POLYMORPH "polymorph" +/// Shown on top of policy verb window +#define POLICY_VERB_HEADER "policy_verb_header" + +//How many things you can spawn at once with spawn verb/create panel +#define ADMIN_SPAWN_CAP 100 + +// LOG BROWSE TYPES +#define BROWSE_ROOT_ALL_LOGS 1 +#define BROWSE_ROOT_CURRENT_LOGS 2 diff --git a/code/__DEFINES/atmospherics.dm b/code/__DEFINES/atmospherics.dm index 76ecd6d86f54..603b0a92d0c6 100644 --- a/code/__DEFINES/atmospherics.dm +++ b/code/__DEFINES/atmospherics.dm @@ -1,408 +1,408 @@ -//LISTMOS -//indices of values in gas lists. -#define MOLES 1 -#define ARCHIVE 2 -#define GAS_META 3 -#define META_GAS_SPECIFIC_HEAT 1 -#define META_GAS_NAME 2 -#define META_GAS_MOLES_VISIBLE 3 -#define META_GAS_OVERLAY 4 -#define META_GAS_DANGER 5 -#define META_GAS_ID 6 -#define META_GAS_FUSION_POWER 7 -//ATMOS -//stuff you should probably leave well alone! -/// kPa*L/(K*mol) -#define R_IDEAL_GAS_EQUATION 8.31 -/// kPa -#define ONE_ATMOSPHERE 101.325 -/// -270.3degC -#define TCMB 2.7 -/// -48.15degC -#define TCRYO 225 -/// 0degC -#define T0C 273.15 -/// 20degC -#define T20C 293.15 - -///moles in a 2.5 m^3 cell at 101.325 Pa and 20 degC -#define MOLES_CELLSTANDARD (ONE_ATMOSPHERE*CELL_VOLUME/(T20C*R_IDEAL_GAS_EQUATION)) -///compared against for superconductivity -#define M_CELL_WITH_RATIO (MOLES_CELLSTANDARD * 0.005) -/// percentage of oxygen in a normal mixture of air -#define O2STANDARD 0.21 -/// same but for nitrogen -#define N2STANDARD 0.79 -/// O2 standard value (21%) -#define MOLES_O2STANDARD (MOLES_CELLSTANDARD*O2STANDARD) -/// N2 standard value (79%) -#define MOLES_N2STANDARD (MOLES_CELLSTANDARD*N2STANDARD) -/// liters in a cell -#define CELL_VOLUME 2500 - -/// liters in a normal breath -#define BREATH_VOLUME 0.5 -/// Amount of air to take a from a tile -#define BREATH_PERCENTAGE (BREATH_VOLUME/CELL_VOLUME) - - -//EXCITED GROUPS -/// number of FULL air controller ticks before an excited group breaks down (averages gas contents across turfs) -#define EXCITED_GROUP_BREAKDOWN_CYCLES 4 -/// number of FULL air controller ticks before an excited group dismantles and removes its turfs from active -#define EXCITED_GROUP_DISMANTLE_CYCLES 16 -/// Ratio of air that must move to/from a tile to reset group processing -#define MINIMUM_AIR_RATIO_TO_SUSPEND 0.1 -/// Minimum ratio of air that must move to/from a tile -#define MINIMUM_AIR_RATIO_TO_MOVE 0.001 -/// Minimum amount of air that has to move before a group processing can be suspended -#define MINIMUM_AIR_TO_SUSPEND (MOLES_CELLSTANDARD*MINIMUM_AIR_RATIO_TO_SUSPEND) -/// Either this must be active -#define MINIMUM_MOLES_DELTA_TO_MOVE (MOLES_CELLSTANDARD*MINIMUM_AIR_RATIO_TO_MOVE) -/// or this (or both, obviously) -#define MINIMUM_TEMPERATURE_TO_MOVE (T20C+100) -/// Minimum temperature difference before group processing is suspended -#define MINIMUM_TEMPERATURE_DELTA_TO_SUSPEND 4 -/// Minimum temperature difference before the gas temperatures are just set to be equal -#define MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER 0.5 -#define MINIMUM_TEMPERATURE_FOR_SUPERCONDUCTION (T20C+10) -#define MINIMUM_TEMPERATURE_START_SUPERCONDUCTION (T20C+200) - -//HEAT TRANSFER COEFFICIENTS -//Must be between 0 and 1. Values closer to 1 equalize temperature faster -//Should not exceed 0.4 else strange heat flow occur -#define WALL_HEAT_TRANSFER_COEFFICIENT 0.0 -#define OPEN_HEAT_TRANSFER_COEFFICIENT 0.4 -/// a hack for now -#define WINDOW_HEAT_TRANSFER_COEFFICIENT 0.1 -/// a hack to help make vacuums "cold", sacrificing realism for gameplay -#define HEAT_CAPACITY_VACUUM 7000 - -//FIRE -#define FIRE_MINIMUM_TEMPERATURE_TO_SPREAD (150+T0C) -#define FIRE_MINIMUM_TEMPERATURE_TO_EXIST (100+T0C) -#define FIRE_SPREAD_RADIOSITY_SCALE 0.85 -#define FIRE_GROWTH_RATE 40000 //For small fires -#define PLASMA_MINIMUM_BURN_TEMPERATURE (100+T0C) -#define PLASMA_UPPER_TEMPERATURE (1370+T0C) -#define PLASMA_OXYGEN_FULLBURN 10 - -//COLD FIRE (this is used only for the freon-o2 reaction, there is no fire still) -#define COLD_FIRE_MAXIMUM_TEMPERATURE_TO_SPREAD 263 //fire will spread if the temperature is -10 °C -#define COLD_FIRE_MAXIMUM_TEMPERATURE_TO_EXIST 273 //fire will start if the temperature is 0 °C -#define COLD_FIRE_SPREAD_RADIOSITY_SCALE 0.95 -#define COLD_FIRE_GROWTH_RATE 40000 -#define FREON_MAXIMUM_BURN_TEMPERATURE 293 -#define FREON_LOWER_TEMPERATURE 30 //minimum temperature allowed for the burn to go, we would have negative pressure otherwise -#define FREON_OXYGEN_FULLBURN 10 - -//GASES -#define MIN_TOXIC_GAS_DAMAGE 1 -#define MAX_TOXIC_GAS_DAMAGE 10 -/// Moles in a standard cell after which gases are visible -#define MOLES_GAS_VISIBLE 0.25 - -/// moles_visible * FACTOR_GAS_VISIBLE_MAX = Moles after which gas is at maximum visibility -#define FACTOR_GAS_VISIBLE_MAX 20 -/// Mole step for alpha updates. This means alpha can update at 0.25, 0.5, 0.75 and so on -#define MOLES_GAS_VISIBLE_STEP 0.25 - -//REACTIONS -//return values for reactions (bitflags) -#define NO_REACTION 0 -#define REACTING 1 -#define STOP_REACTIONS 2 - -// Pressure limits. -/// This determins at what pressure the ultra-high pressure red icon is displayed. (This one is set as a constant) -#define HAZARD_HIGH_PRESSURE 550 -/// This determins when the orange pressure icon is displayed (it is 0.7 * HAZARD_HIGH_PRESSURE) -#define WARNING_HIGH_PRESSURE 325 -/// This is when the gray low pressure icon is displayed. (it is 2.5 * HAZARD_LOW_PRESSURE) -#define WARNING_LOW_PRESSURE 50 -/// This is when the black ultra-low pressure icon is displayed. (This one is set as a constant) -#define HAZARD_LOW_PRESSURE 20 - -/// This is used in handle_temperature_damage() for humans, and in reagents that affect body temperature. Temperature damage is multiplied by this amount. -#define TEMPERATURE_DAMAGE_COEFFICIENT 1.5 - -/// The natural temperature for a body -#define BODYTEMP_NORMAL 310.15 -/// This is the divisor which handles how much of the temperature difference between the current body temperature and 310.15K (optimal temperature) humans auto-regenerate each tick. The higher the number, the slower the recovery. This is applied each tick, so long as the mob is alive. -#define BODYTEMP_AUTORECOVERY_DIVISOR 11 -/// Minimum amount of kelvin moved toward 310K per tick. So long as abs(310.15 - bodytemp) is more than 50. -#define BODYTEMP_AUTORECOVERY_MINIMUM 12 -///Similar to the BODYTEMP_AUTORECOVERY_DIVISOR, but this is the divisor which is applied at the stage that follows autorecovery. This is the divisor which comes into play when the human's loc temperature is lower than their body temperature. Make it lower to lose bodytemp faster. -#define BODYTEMP_COLD_DIVISOR 15 -/// Similar to the BODYTEMP_AUTORECOVERY_DIVISOR, but this is the divisor which is applied at the stage that follows autorecovery. This is the divisor which comes into play when the human's loc temperature is higher than their body temperature. Make it lower to gain bodytemp faster. -#define BODYTEMP_HEAT_DIVISOR 15 -/// The maximum number of degrees that your body can cool in 1 tick, due to the environment, when in a cold area. -#define BODYTEMP_COOLING_MAX -100 -/// The maximum number of degrees that your body can heat up in 1 tick, due to the environment, when in a hot area. -#define BODYTEMP_HEATING_MAX 30 -/// The body temperature limit the human body can take before it starts taking damage from heat. -/// This also affects how fast the body normalises it's temperature when hot. -/// 340k is about 66c, and rather high for a human. -#define BODYTEMP_HEAT_DAMAGE_LIMIT (BODYTEMP_NORMAL + 30) -/// The body temperature limit the human body can take before it starts taking damage from cold. -/// This also affects how fast the body normalises it's temperature when cold. -/// 270k is about -3c, that is below freezing and would hurt over time. -#define BODYTEMP_COLD_DAMAGE_LIMIT (BODYTEMP_NORMAL - 40) - -/// what min_cold_protection_temperature is set to for space-helmet quality headwear. MUST NOT BE 0. -#define SPACE_HELM_MIN_TEMP_PROTECT 2.0 -/// Thermal insulation works both ways /Malkevin -#define SPACE_HELM_MAX_TEMP_PROTECT 1500 -/// what min_cold_protection_temperature is set to for space-suit quality jumpsuits or suits. MUST NOT BE 0. -#define SPACE_SUIT_MIN_TEMP_PROTECT 2.0 -#define SPACE_SUIT_MAX_TEMP_PROTECT 1500 - -/// Cold protection for firesuits -#define FIRE_SUIT_MIN_TEMP_PROTECT 60 -/// what max_heat_protection_temperature is set to for firesuit quality suits. MUST NOT BE 0. -#define FIRE_SUIT_MAX_TEMP_PROTECT 30000 -/// Cold protection for fire helmets -#define FIRE_HELM_MIN_TEMP_PROTECT 60 -/// for fire helmet quality items (red and white hardhats) -#define FIRE_HELM_MAX_TEMP_PROTECT 30000 - -/// what max_heat_protection_temperature is set to for firesuit quality suits and helmets. MUST NOT BE 0. -#define FIRE_IMMUNITY_MAX_TEMP_PROTECT 35000 - -/// For normal helmets -#define HELMET_MIN_TEMP_PROTECT 160 -/// For normal helmets -#define HELMET_MAX_TEMP_PROTECT 600 -/// For armor -#define ARMOR_MIN_TEMP_PROTECT 160 -/// For armor -#define ARMOR_MAX_TEMP_PROTECT 600 - -/// For some gloves (black and) -#define GLOVES_MIN_TEMP_PROTECT 2.0 -/// For some gloves -#define GLOVES_MAX_TEMP_PROTECT 1500 -/// For gloves -#define SHOES_MIN_TEMP_PROTECT 2.0 -/// For gloves -#define SHOES_MAX_TEMP_PROTECT 1500 - -/// The amount of pressure damage someone takes is equal to (pressure / HAZARD_HIGH_PRESSURE)*PRESSURE_DAMAGE_COEFFICIENT, with the maximum of MAX_PRESSURE_DAMAGE -#define PRESSURE_DAMAGE_COEFFICIENT 4 -#define MAX_HIGH_PRESSURE_DAMAGE 4 -/// The amount of damage someone takes when in a low pressure area (The pressure threshold is so low that it doesn't make sense to do any calculations, so it just applies this flat value). -#define LOW_PRESSURE_DAMAGE 4 - -/// Humans are slowed by the difference between bodytemp and BODYTEMP_COLD_DAMAGE_LIMIT divided by this -#define COLD_SLOWDOWN_FACTOR 20 - -//PIPES -//Atmos pipe limits -/// (kPa) What pressure pumps and powered equipment max out at. -#define MAX_OUTPUT_PRESSURE 4500 -/// (L/s) Maximum speed powered equipment can work at. -#define MAX_TRANSFER_RATE 200 -/// 10% of an overclocked volume pump leaks into the air -#define VOLUME_PUMP_LEAK_AMOUNT 0.1 -//used for device_type vars -#define UNARY 1 -#define BINARY 2 -#define TRINARY 3 -#define QUATERNARY 4 - -//TANKS -/// temperature in kelvins at which a tank will start to melt -#define TANK_MELT_TEMPERATURE 1000000 -/// Tank starts leaking -#define TANK_LEAK_PRESSURE (30.*ONE_ATMOSPHERE) -/// Tank spills all contents into atmosphere -#define TANK_RUPTURE_PRESSURE (35.*ONE_ATMOSPHERE) -/// Boom 3x3 base explosion -#define TANK_FRAGMENT_PRESSURE (40.*ONE_ATMOSPHERE) -/// +1 for each SCALE kPa aboe threshold -#define TANK_FRAGMENT_SCALE (6.*ONE_ATMOSPHERE) -#define TANK_MAX_RELEASE_PRESSURE (ONE_ATMOSPHERE*3) -#define TANK_MIN_RELEASE_PRESSURE 0 -#define TANK_DEFAULT_RELEASE_PRESSURE 17 - -//CANATMOSPASS -#define ATMOS_PASS_YES 1 -#define ATMOS_PASS_NO 0 -/// ask CanAtmosPass() -#define ATMOS_PASS_PROC -1 -/// just check density -#define ATMOS_PASS_DENSITY -2 - -#define CANATMOSPASS(A, O) ( A.CanAtmosPass == ATMOS_PASS_PROC ? A.CanAtmosPass(O) : ( A.CanAtmosPass == ATMOS_PASS_DENSITY ? !A.density : A.CanAtmosPass ) ) -#define CANVERTICALATMOSPASS(A, O) ( A.CanAtmosPassVertical == ATMOS_PASS_PROC ? A.CanAtmosPass(O, TRUE) : ( A.CanAtmosPassVertical == ATMOS_PASS_DENSITY ? !A.density : A.CanAtmosPassVertical ) ) - -//OPEN TURF ATMOS -/// the default air mix that open turfs spawn -#define OPENTURF_DEFAULT_ATMOS "o2=22;n2=82;TEMP=293.15" -#define OPENTURF_LOW_PRESSURE "o2=14;n2=30;TEMP=293.15" -/// -193,15°C telecommunications. also used for xenobiology slime killrooms -#define TCOMMS_ATMOS "n2=100;TEMP=80" -/// space -#define AIRLESS_ATMOS "TEMP=2.7" -/// -93.15°C snow and ice turfs -#define FROZEN_ATMOS "o2=22;n2=82;TEMP=180" -/// -80°C kitchen coldroom; higher amount of mol to reach about 101.3 kpA -#define KITCHEN_COLDROOM_ATMOS "o2=33;n2=124;TEMP=193.15" -/// used in the holodeck burn test program -#define BURNMIX_ATMOS "o2=2500;plasma=5000;TEMP=370" - -//ATMOSPHERICS DEPARTMENT GAS TANK TURFS -#define ATMOS_TANK_N2O "n2o=6000;TEMP=293.15" -#define ATMOS_TANK_CO2 "co2=50000;TEMP=293.15" -#define ATMOS_TANK_PLASMA "plasma=70000;TEMP=293.15" -#define ATMOS_TANK_O2 "o2=100000;TEMP=293.15" -#define ATMOS_TANK_N2 "n2=100000;TEMP=293.15" -#define ATMOS_TANK_AIRMIX "o2=2644;n2=10580;TEMP=293.15" - -//LAVALAND -/// what pressure you have to be under to increase the effect of equipment meant for lavaland -#define LAVALAND_EQUIPMENT_EFFECT_PRESSURE 90 - -//ATMOS MIX IDS -#define LAVALAND_DEFAULT_ATMOS "LAVALAND_ATMOS" - -//ATMOSIA GAS MONITOR TAGS -#define ATMOS_GAS_MONITOR_INPUT_O2 "o2_in" -#define ATMOS_GAS_MONITOR_OUTPUT_O2 "o2_out" -#define ATMOS_GAS_MONITOR_SENSOR_O2 "o2_sensor" - -#define ATMOS_GAS_MONITOR_INPUT_TOX "tox_in" -#define ATMOS_GAS_MONITOR_OUTPUT_TOX "tox_out" -#define ATMOS_GAS_MONITOR_SENSOR_TOX "tox_sensor" - -#define ATMOS_GAS_MONITOR_INPUT_AIR "air_in" -#define ATMOS_GAS_MONITOR_OUTPUT_AIR "air_out" -#define ATMOS_GAS_MONITOR_SENSOR_AIR "air_sensor" - -#define ATMOS_GAS_MONITOR_INPUT_MIX "mix_in" -#define ATMOS_GAS_MONITOR_OUTPUT_MIX "mix_out" -#define ATMOS_GAS_MONITOR_SENSOR_MIX "mix_sensor" - -#define ATMOS_GAS_MONITOR_INPUT_N2O "n2o_in" -#define ATMOS_GAS_MONITOR_OUTPUT_N2O "n2o_out" -#define ATMOS_GAS_MONITOR_SENSOR_N2O "n2o_sensor" - -#define ATMOS_GAS_MONITOR_INPUT_N2 "n2_in" -#define ATMOS_GAS_MONITOR_OUTPUT_N2 "n2_out" -#define ATMOS_GAS_MONITOR_SENSOR_N2 "n2_sensor" - -#define ATMOS_GAS_MONITOR_INPUT_CO2 "co2_in" -#define ATMOS_GAS_MONITOR_OUTPUT_CO2 "co2_out" -#define ATMOS_GAS_MONITOR_SENSOR_CO2 "co2_sensor" - -#define ATMOS_GAS_MONITOR_INPUT_INCINERATOR "incinerator_in" -#define ATMOS_GAS_MONITOR_OUTPUT_INCINERATOR "incinerator_out" -#define ATMOS_GAS_MONITOR_SENSOR_INCINERATOR "incinerator_sensor" - -#define ATMOS_GAS_MONITOR_INPUT_TOXINS_LAB "toxinslab_in" -#define ATMOS_GAS_MONITOR_OUTPUT_TOXINS_LAB "toxinslab_out" -#define ATMOS_GAS_MONITOR_SENSOR_TOXINS_LAB "toxinslab_sensor" - -#define ATMOS_GAS_MONITOR_LOOP_DISTRIBUTION "distro-loop_meter" -#define ATMOS_GAS_MONITOR_LOOP_ATMOS_WASTE "atmos-waste_loop_meter" - -#define ATMOS_GAS_MONITOR_WASTE_ENGINE "engine-waste_out" -#define ATMOS_GAS_MONITOR_WASTE_ATMOS "atmos-waste_out" - -//AIRLOCK CONTROLLER TAGS - -//RnD toxins burn chamber -#define INCINERATOR_TOXMIX_IGNITER "toxmix_igniter" -#define INCINERATOR_TOXMIX_VENT "toxmix_vent" -#define INCINERATOR_TOXMIX_DP_VENTPUMP "toxmix_airlock_pump" -#define INCINERATOR_TOXMIX_AIRLOCK_SENSOR "toxmix_airlock_sensor" -#define INCINERATOR_TOXMIX_AIRLOCK_CONTROLLER "toxmix_airlock_controller" -#define INCINERATOR_TOXMIX_AIRLOCK_INTERIOR "toxmix_airlock_interior" -#define INCINERATOR_TOXMIX_AIRLOCK_EXTERIOR "toxmix_airlock_exterior" - -//Atmospherics/maintenance incinerator -#define INCINERATOR_ATMOS_IGNITER "atmos_incinerator_igniter" -#define INCINERATOR_ATMOS_MAINVENT "atmos_incinerator_mainvent" -#define INCINERATOR_ATMOS_AUXVENT "atmos_incinerator_auxvent" -#define INCINERATOR_ATMOS_DP_VENTPUMP "atmos_incinerator_airlock_pump" -#define INCINERATOR_ATMOS_AIRLOCK_SENSOR "atmos_incinerator_airlock_sensor" -#define INCINERATOR_ATMOS_AIRLOCK_CONTROLLER "atmos_incinerator_airlock_controller" -#define INCINERATOR_ATMOS_AIRLOCK_INTERIOR "atmos_incinerator_airlock_interior" -#define INCINERATOR_ATMOS_AIRLOCK_EXTERIOR "atmos_incinerator_airlock_exterior" - -//Syndicate lavaland base incinerator (lavaland_surface_syndicate_base1.dmm) -#define INCINERATOR_SYNDICATELAVA_IGNITER "syndicatelava_igniter" -#define INCINERATOR_SYNDICATELAVA_MAINVENT "syndicatelava_mainvent" -#define INCINERATOR_SYNDICATELAVA_AUXVENT "syndicatelava_auxvent" -#define INCINERATOR_SYNDICATELAVA_DP_VENTPUMP "syndicatelava_airlock_pump" -#define INCINERATOR_SYNDICATELAVA_AIRLOCK_SENSOR "syndicatelava_airlock_sensor" -#define INCINERATOR_SYNDICATELAVA_AIRLOCK_CONTROLLER "syndicatelava_airlock_controller" -#define INCINERATOR_SYNDICATELAVA_AIRLOCK_INTERIOR "syndicatelava_airlock_interior" -#define INCINERATOR_SYNDICATELAVA_AIRLOCK_EXTERIOR "syndicatelava_airlock_exterior" - -//MULTIPIPES -//IF YOU EVER CHANGE THESE CHANGE SPRITES TO MATCH. -#define PIPING_LAYER_MIN 1 -#define PIPING_LAYER_MAX 3 -#define PIPING_LAYER_DEFAULT 2 -#define PIPING_LAYER_P_X 5 -#define PIPING_LAYER_P_Y 5 -#define PIPING_LAYER_LCHANGE 0.05 - -/// intended to connect with all layers, check for all instead of just one. -#define PIPING_ALL_LAYER (1<<0) -/// can only be built if nothing else with this flag is on the tile already. -#define PIPING_ONE_PER_TURF (1<<1) -/// can only exist at PIPING_LAYER_DEFAULT -#define PIPING_DEFAULT_LAYER_ONLY (1<<2) -/// north/south east/west doesn't matter, auto normalize on build. -#define PIPING_CARDINAL_AUTONORMALIZE (1<<3) - -//HELPERS -#define PIPING_LAYER_SHIFT(T, PipingLayer) \ - if(T.dir & (NORTH|SOUTH)) { \ - T.pixel_x = (PipingLayer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_X;\ - } \ - if(T.dir & (EAST|WEST)) { \ - T.pixel_y = (PipingLayer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_Y;\ - } - -#define PIPING_LAYER_DOUBLE_SHIFT(T, PipingLayer) \ - T.pixel_x = (PipingLayer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_X;\ - T.pixel_y = (PipingLayer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_Y; - -#ifdef TESTING -GLOBAL_LIST_INIT(atmos_adjacent_savings, list(0,0)) -#define CALCULATE_ADJACENT_TURFS(T) if (SSadjacent_air.queue[T]) { GLOB.atmos_adjacent_savings[1] += 1 } else { GLOB.atmos_adjacent_savings[2] += 1; SSadjacent_air.queue[T] = 1 } -#else -#define CALCULATE_ADJACENT_TURFS(T) SSadjacent_air.queue[T] = 1 -#endif - -GLOBAL_VAR(atmos_extools_initialized) // this must be an uninitialized (null) one or init_monstermos will be called twice because reasons -#define ATMOS_EXTOOLS_CHECK if(!GLOB.atmos_extools_initialized){\ - GLOB.atmos_extools_initialized=TRUE;\ - if(fexists(world.system_type == MS_WINDOWS ? "byond-extools.dll" : "libbyond-extools.so")){\ - var/result = call((world.system_type == MS_WINDOWS ? "byond-extools.dll" : "libbyond-extools.so"),"init_monstermos")();\ - if(result != "ok") {CRASH(result);}\ - } else {\ - CRASH("byond-extools.dll does not exist!");\ - }\ -} - -GLOBAL_LIST_INIT(pipe_paint_colors, sortList(list( - "amethyst" = rgb(130,43,255), //supplymain - "blue" = rgb(0,0,255), - "brown" = rgb(178,100,56), - "cyan" = rgb(0,255,249), - "dark" = rgb(69,69,69), - "green" = rgb(30,255,0), - "grey" = rgb(255,255,255), - "orange" = rgb(255,129,25), - "purple" = rgb(128,0,182), - "red" = rgb(255,0,0), - "violet" = rgb(64,0,128), - "yellow" = rgb(255,198,0) -))) - -#define MIASMA_CORPSE_MOLES 0.02 -#define MIASMA_GIBS_MOLES 0.005 +//LISTMOS +//indices of values in gas lists. +#define MOLES 1 +#define ARCHIVE 2 +#define GAS_META 3 +#define META_GAS_SPECIFIC_HEAT 1 +#define META_GAS_NAME 2 +#define META_GAS_MOLES_VISIBLE 3 +#define META_GAS_OVERLAY 4 +#define META_GAS_DANGER 5 +#define META_GAS_ID 6 +#define META_GAS_FUSION_POWER 7 +//ATMOS +//stuff you should probably leave well alone! +/// kPa*L/(K*mol) +#define R_IDEAL_GAS_EQUATION 8.31 +/// kPa +#define ONE_ATMOSPHERE 101.325 +/// -270.3degC +#define TCMB 2.7 +/// -48.15degC +#define TCRYO 225 +/// 0degC +#define T0C 273.15 +/// 20degC +#define T20C 293.15 + +///moles in a 2.5 m^3 cell at 101.325 Pa and 20 degC +#define MOLES_CELLSTANDARD (ONE_ATMOSPHERE*CELL_VOLUME/(T20C*R_IDEAL_GAS_EQUATION)) +///compared against for superconductivity +#define M_CELL_WITH_RATIO (MOLES_CELLSTANDARD * 0.005) +/// percentage of oxygen in a normal mixture of air +#define O2STANDARD 0.21 +/// same but for nitrogen +#define N2STANDARD 0.79 +/// O2 standard value (21%) +#define MOLES_O2STANDARD (MOLES_CELLSTANDARD*O2STANDARD) +/// N2 standard value (79%) +#define MOLES_N2STANDARD (MOLES_CELLSTANDARD*N2STANDARD) +/// liters in a cell +#define CELL_VOLUME 2500 + +/// liters in a normal breath +#define BREATH_VOLUME 0.5 +/// Amount of air to take a from a tile +#define BREATH_PERCENTAGE (BREATH_VOLUME/CELL_VOLUME) + + +//EXCITED GROUPS +/// number of FULL air controller ticks before an excited group breaks down (averages gas contents across turfs) +#define EXCITED_GROUP_BREAKDOWN_CYCLES 4 +/// number of FULL air controller ticks before an excited group dismantles and removes its turfs from active +#define EXCITED_GROUP_DISMANTLE_CYCLES 16 +/// Ratio of air that must move to/from a tile to reset group processing +#define MINIMUM_AIR_RATIO_TO_SUSPEND 0.1 +/// Minimum ratio of air that must move to/from a tile +#define MINIMUM_AIR_RATIO_TO_MOVE 0.001 +/// Minimum amount of air that has to move before a group processing can be suspended +#define MINIMUM_AIR_TO_SUSPEND (MOLES_CELLSTANDARD*MINIMUM_AIR_RATIO_TO_SUSPEND) +/// Either this must be active +#define MINIMUM_MOLES_DELTA_TO_MOVE (MOLES_CELLSTANDARD*MINIMUM_AIR_RATIO_TO_MOVE) +/// or this (or both, obviously) +#define MINIMUM_TEMPERATURE_TO_MOVE (T20C+100) +/// Minimum temperature difference before group processing is suspended +#define MINIMUM_TEMPERATURE_DELTA_TO_SUSPEND 4 +/// Minimum temperature difference before the gas temperatures are just set to be equal +#define MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER 0.5 +#define MINIMUM_TEMPERATURE_FOR_SUPERCONDUCTION (T20C+10) +#define MINIMUM_TEMPERATURE_START_SUPERCONDUCTION (T20C+200) + +//HEAT TRANSFER COEFFICIENTS +//Must be between 0 and 1. Values closer to 1 equalize temperature faster +//Should not exceed 0.4 else strange heat flow occur +#define WALL_HEAT_TRANSFER_COEFFICIENT 0.0 +#define OPEN_HEAT_TRANSFER_COEFFICIENT 0.4 +/// a hack for now +#define WINDOW_HEAT_TRANSFER_COEFFICIENT 0.1 +/// a hack to help make vacuums "cold", sacrificing realism for gameplay +#define HEAT_CAPACITY_VACUUM 7000 + +//FIRE +#define FIRE_MINIMUM_TEMPERATURE_TO_SPREAD (150+T0C) +#define FIRE_MINIMUM_TEMPERATURE_TO_EXIST (100+T0C) +#define FIRE_SPREAD_RADIOSITY_SCALE 0.85 +#define FIRE_GROWTH_RATE 40000 //For small fires +#define PLASMA_MINIMUM_BURN_TEMPERATURE (100+T0C) +#define PLASMA_UPPER_TEMPERATURE (1370+T0C) +#define PLASMA_OXYGEN_FULLBURN 10 + +//COLD FIRE (this is used only for the freon-o2 reaction, there is no fire still) +#define COLD_FIRE_MAXIMUM_TEMPERATURE_TO_SPREAD 263 //fire will spread if the temperature is -10 °C +#define COLD_FIRE_MAXIMUM_TEMPERATURE_TO_EXIST 273 //fire will start if the temperature is 0 °C +#define COLD_FIRE_SPREAD_RADIOSITY_SCALE 0.95 +#define COLD_FIRE_GROWTH_RATE 40000 +#define FREON_MAXIMUM_BURN_TEMPERATURE 293 +#define FREON_LOWER_TEMPERATURE 30 //minimum temperature allowed for the burn to go, we would have negative pressure otherwise +#define FREON_OXYGEN_FULLBURN 10 + +//GASES +#define MIN_TOXIC_GAS_DAMAGE 1 +#define MAX_TOXIC_GAS_DAMAGE 10 +/// Moles in a standard cell after which gases are visible +#define MOLES_GAS_VISIBLE 0.25 + +/// moles_visible * FACTOR_GAS_VISIBLE_MAX = Moles after which gas is at maximum visibility +#define FACTOR_GAS_VISIBLE_MAX 20 +/// Mole step for alpha updates. This means alpha can update at 0.25, 0.5, 0.75 and so on +#define MOLES_GAS_VISIBLE_STEP 0.25 + +//REACTIONS +//return values for reactions (bitflags) +#define NO_REACTION 0 +#define REACTING 1 +#define STOP_REACTIONS 2 + +// Pressure limits. +/// This determins at what pressure the ultra-high pressure red icon is displayed. (This one is set as a constant) +#define HAZARD_HIGH_PRESSURE 550 +/// This determins when the orange pressure icon is displayed (it is 0.7 * HAZARD_HIGH_PRESSURE) +#define WARNING_HIGH_PRESSURE 325 +/// This is when the gray low pressure icon is displayed. (it is 2.5 * HAZARD_LOW_PRESSURE) +#define WARNING_LOW_PRESSURE 50 +/// This is when the black ultra-low pressure icon is displayed. (This one is set as a constant) +#define HAZARD_LOW_PRESSURE 20 + +/// This is used in handle_temperature_damage() for humans, and in reagents that affect body temperature. Temperature damage is multiplied by this amount. +#define TEMPERATURE_DAMAGE_COEFFICIENT 1.5 + +/// The natural temperature for a body +#define BODYTEMP_NORMAL 310.15 +/// This is the divisor which handles how much of the temperature difference between the current body temperature and 310.15K (optimal temperature) humans auto-regenerate each tick. The higher the number, the slower the recovery. This is applied each tick, so long as the mob is alive. +#define BODYTEMP_AUTORECOVERY_DIVISOR 11 +/// Minimum amount of kelvin moved toward 310K per tick. So long as abs(310.15 - bodytemp) is more than 50. +#define BODYTEMP_AUTORECOVERY_MINIMUM 12 +///Similar to the BODYTEMP_AUTORECOVERY_DIVISOR, but this is the divisor which is applied at the stage that follows autorecovery. This is the divisor which comes into play when the human's loc temperature is lower than their body temperature. Make it lower to lose bodytemp faster. +#define BODYTEMP_COLD_DIVISOR 15 +/// Similar to the BODYTEMP_AUTORECOVERY_DIVISOR, but this is the divisor which is applied at the stage that follows autorecovery. This is the divisor which comes into play when the human's loc temperature is higher than their body temperature. Make it lower to gain bodytemp faster. +#define BODYTEMP_HEAT_DIVISOR 15 +/// The maximum number of degrees that your body can cool in 1 tick, due to the environment, when in a cold area. +#define BODYTEMP_COOLING_MAX -100 +/// The maximum number of degrees that your body can heat up in 1 tick, due to the environment, when in a hot area. +#define BODYTEMP_HEATING_MAX 30 +/// The body temperature limit the human body can take before it starts taking damage from heat. +/// This also affects how fast the body normalises it's temperature when hot. +/// 340k is about 66c, and rather high for a human. +#define BODYTEMP_HEAT_DAMAGE_LIMIT (BODYTEMP_NORMAL + 30) +/// The body temperature limit the human body can take before it starts taking damage from cold. +/// This also affects how fast the body normalises it's temperature when cold. +/// 270k is about -3c, that is below freezing and would hurt over time. +#define BODYTEMP_COLD_DAMAGE_LIMIT (BODYTEMP_NORMAL - 40) + +/// what min_cold_protection_temperature is set to for space-helmet quality headwear. MUST NOT BE 0. +#define SPACE_HELM_MIN_TEMP_PROTECT 2.0 +/// Thermal insulation works both ways /Malkevin +#define SPACE_HELM_MAX_TEMP_PROTECT 1500 +/// what min_cold_protection_temperature is set to for space-suit quality jumpsuits or suits. MUST NOT BE 0. +#define SPACE_SUIT_MIN_TEMP_PROTECT 2.0 +#define SPACE_SUIT_MAX_TEMP_PROTECT 1500 + +/// Cold protection for firesuits +#define FIRE_SUIT_MIN_TEMP_PROTECT 60 +/// what max_heat_protection_temperature is set to for firesuit quality suits. MUST NOT BE 0. +#define FIRE_SUIT_MAX_TEMP_PROTECT 30000 +/// Cold protection for fire helmets +#define FIRE_HELM_MIN_TEMP_PROTECT 60 +/// for fire helmet quality items (red and white hardhats) +#define FIRE_HELM_MAX_TEMP_PROTECT 30000 + +/// what max_heat_protection_temperature is set to for firesuit quality suits and helmets. MUST NOT BE 0. +#define FIRE_IMMUNITY_MAX_TEMP_PROTECT 35000 + +/// For normal helmets +#define HELMET_MIN_TEMP_PROTECT 160 +/// For normal helmets +#define HELMET_MAX_TEMP_PROTECT 600 +/// For armor +#define ARMOR_MIN_TEMP_PROTECT 160 +/// For armor +#define ARMOR_MAX_TEMP_PROTECT 600 + +/// For some gloves (black and) +#define GLOVES_MIN_TEMP_PROTECT 2.0 +/// For some gloves +#define GLOVES_MAX_TEMP_PROTECT 1500 +/// For gloves +#define SHOES_MIN_TEMP_PROTECT 2.0 +/// For gloves +#define SHOES_MAX_TEMP_PROTECT 1500 + +/// The amount of pressure damage someone takes is equal to (pressure / HAZARD_HIGH_PRESSURE)*PRESSURE_DAMAGE_COEFFICIENT, with the maximum of MAX_PRESSURE_DAMAGE +#define PRESSURE_DAMAGE_COEFFICIENT 4 +#define MAX_HIGH_PRESSURE_DAMAGE 4 +/// The amount of damage someone takes when in a low pressure area (The pressure threshold is so low that it doesn't make sense to do any calculations, so it just applies this flat value). +#define LOW_PRESSURE_DAMAGE 4 + +/// Humans are slowed by the difference between bodytemp and BODYTEMP_COLD_DAMAGE_LIMIT divided by this +#define COLD_SLOWDOWN_FACTOR 20 + +//PIPES +//Atmos pipe limits +/// (kPa) What pressure pumps and powered equipment max out at. +#define MAX_OUTPUT_PRESSURE 4500 +/// (L/s) Maximum speed powered equipment can work at. +#define MAX_TRANSFER_RATE 200 +/// 10% of an overclocked volume pump leaks into the air +#define VOLUME_PUMP_LEAK_AMOUNT 0.1 +//used for device_type vars +#define UNARY 1 +#define BINARY 2 +#define TRINARY 3 +#define QUATERNARY 4 + +//TANKS +/// temperature in kelvins at which a tank will start to melt +#define TANK_MELT_TEMPERATURE 1000000 +/// Tank starts leaking +#define TANK_LEAK_PRESSURE (30.*ONE_ATMOSPHERE) +/// Tank spills all contents into atmosphere +#define TANK_RUPTURE_PRESSURE (35.*ONE_ATMOSPHERE) +/// Boom 3x3 base explosion +#define TANK_FRAGMENT_PRESSURE (40.*ONE_ATMOSPHERE) +/// +1 for each SCALE kPa aboe threshold +#define TANK_FRAGMENT_SCALE (6.*ONE_ATMOSPHERE) +#define TANK_MAX_RELEASE_PRESSURE (ONE_ATMOSPHERE*3) +#define TANK_MIN_RELEASE_PRESSURE 0 +#define TANK_DEFAULT_RELEASE_PRESSURE 17 + +//CANATMOSPASS +#define ATMOS_PASS_YES 1 +#define ATMOS_PASS_NO 0 +/// ask CanAtmosPass() +#define ATMOS_PASS_PROC -1 +/// just check density +#define ATMOS_PASS_DENSITY -2 + +#define CANATMOSPASS(A, O) ( A.CanAtmosPass == ATMOS_PASS_PROC ? A.CanAtmosPass(O) : ( A.CanAtmosPass == ATMOS_PASS_DENSITY ? !A.density : A.CanAtmosPass ) ) +#define CANVERTICALATMOSPASS(A, O) ( A.CanAtmosPassVertical == ATMOS_PASS_PROC ? A.CanAtmosPass(O, TRUE) : ( A.CanAtmosPassVertical == ATMOS_PASS_DENSITY ? !A.density : A.CanAtmosPassVertical ) ) + +//OPEN TURF ATMOS +/// the default air mix that open turfs spawn +#define OPENTURF_DEFAULT_ATMOS "o2=22;n2=82;TEMP=293.15" +#define OPENTURF_LOW_PRESSURE "o2=14;n2=30;TEMP=293.15" +/// -193,15°C telecommunications. also used for xenobiology slime killrooms +#define TCOMMS_ATMOS "n2=100;TEMP=80" +/// space +#define AIRLESS_ATMOS "TEMP=2.7" +/// -93.15°C snow and ice turfs +#define FROZEN_ATMOS "o2=22;n2=82;TEMP=180" +/// -80°C kitchen coldroom; higher amount of mol to reach about 101.3 kpA +#define KITCHEN_COLDROOM_ATMOS "o2=33;n2=124;TEMP=193.15" +/// used in the holodeck burn test program +#define BURNMIX_ATMOS "o2=2500;plasma=5000;TEMP=370" + +//ATMOSPHERICS DEPARTMENT GAS TANK TURFS +#define ATMOS_TANK_N2O "n2o=6000;TEMP=293.15" +#define ATMOS_TANK_CO2 "co2=50000;TEMP=293.15" +#define ATMOS_TANK_PLASMA "plasma=70000;TEMP=293.15" +#define ATMOS_TANK_O2 "o2=100000;TEMP=293.15" +#define ATMOS_TANK_N2 "n2=100000;TEMP=293.15" +#define ATMOS_TANK_AIRMIX "o2=2644;n2=10580;TEMP=293.15" + +//LAVALAND +/// what pressure you have to be under to increase the effect of equipment meant for lavaland +#define LAVALAND_EQUIPMENT_EFFECT_PRESSURE 90 + +//ATMOS MIX IDS +#define LAVALAND_DEFAULT_ATMOS "LAVALAND_ATMOS" + +//ATMOSIA GAS MONITOR TAGS +#define ATMOS_GAS_MONITOR_INPUT_O2 "o2_in" +#define ATMOS_GAS_MONITOR_OUTPUT_O2 "o2_out" +#define ATMOS_GAS_MONITOR_SENSOR_O2 "o2_sensor" + +#define ATMOS_GAS_MONITOR_INPUT_TOX "tox_in" +#define ATMOS_GAS_MONITOR_OUTPUT_TOX "tox_out" +#define ATMOS_GAS_MONITOR_SENSOR_TOX "tox_sensor" + +#define ATMOS_GAS_MONITOR_INPUT_AIR "air_in" +#define ATMOS_GAS_MONITOR_OUTPUT_AIR "air_out" +#define ATMOS_GAS_MONITOR_SENSOR_AIR "air_sensor" + +#define ATMOS_GAS_MONITOR_INPUT_MIX "mix_in" +#define ATMOS_GAS_MONITOR_OUTPUT_MIX "mix_out" +#define ATMOS_GAS_MONITOR_SENSOR_MIX "mix_sensor" + +#define ATMOS_GAS_MONITOR_INPUT_N2O "n2o_in" +#define ATMOS_GAS_MONITOR_OUTPUT_N2O "n2o_out" +#define ATMOS_GAS_MONITOR_SENSOR_N2O "n2o_sensor" + +#define ATMOS_GAS_MONITOR_INPUT_N2 "n2_in" +#define ATMOS_GAS_MONITOR_OUTPUT_N2 "n2_out" +#define ATMOS_GAS_MONITOR_SENSOR_N2 "n2_sensor" + +#define ATMOS_GAS_MONITOR_INPUT_CO2 "co2_in" +#define ATMOS_GAS_MONITOR_OUTPUT_CO2 "co2_out" +#define ATMOS_GAS_MONITOR_SENSOR_CO2 "co2_sensor" + +#define ATMOS_GAS_MONITOR_INPUT_INCINERATOR "incinerator_in" +#define ATMOS_GAS_MONITOR_OUTPUT_INCINERATOR "incinerator_out" +#define ATMOS_GAS_MONITOR_SENSOR_INCINERATOR "incinerator_sensor" + +#define ATMOS_GAS_MONITOR_INPUT_TOXINS_LAB "toxinslab_in" +#define ATMOS_GAS_MONITOR_OUTPUT_TOXINS_LAB "toxinslab_out" +#define ATMOS_GAS_MONITOR_SENSOR_TOXINS_LAB "toxinslab_sensor" + +#define ATMOS_GAS_MONITOR_LOOP_DISTRIBUTION "distro-loop_meter" +#define ATMOS_GAS_MONITOR_LOOP_ATMOS_WASTE "atmos-waste_loop_meter" + +#define ATMOS_GAS_MONITOR_WASTE_ENGINE "engine-waste_out" +#define ATMOS_GAS_MONITOR_WASTE_ATMOS "atmos-waste_out" + +//AIRLOCK CONTROLLER TAGS + +//RnD toxins burn chamber +#define INCINERATOR_TOXMIX_IGNITER "toxmix_igniter" +#define INCINERATOR_TOXMIX_VENT "toxmix_vent" +#define INCINERATOR_TOXMIX_DP_VENTPUMP "toxmix_airlock_pump" +#define INCINERATOR_TOXMIX_AIRLOCK_SENSOR "toxmix_airlock_sensor" +#define INCINERATOR_TOXMIX_AIRLOCK_CONTROLLER "toxmix_airlock_controller" +#define INCINERATOR_TOXMIX_AIRLOCK_INTERIOR "toxmix_airlock_interior" +#define INCINERATOR_TOXMIX_AIRLOCK_EXTERIOR "toxmix_airlock_exterior" + +//Atmospherics/maintenance incinerator +#define INCINERATOR_ATMOS_IGNITER "atmos_incinerator_igniter" +#define INCINERATOR_ATMOS_MAINVENT "atmos_incinerator_mainvent" +#define INCINERATOR_ATMOS_AUXVENT "atmos_incinerator_auxvent" +#define INCINERATOR_ATMOS_DP_VENTPUMP "atmos_incinerator_airlock_pump" +#define INCINERATOR_ATMOS_AIRLOCK_SENSOR "atmos_incinerator_airlock_sensor" +#define INCINERATOR_ATMOS_AIRLOCK_CONTROLLER "atmos_incinerator_airlock_controller" +#define INCINERATOR_ATMOS_AIRLOCK_INTERIOR "atmos_incinerator_airlock_interior" +#define INCINERATOR_ATMOS_AIRLOCK_EXTERIOR "atmos_incinerator_airlock_exterior" + +//Syndicate lavaland base incinerator (lavaland_surface_syndicate_base1.dmm) +#define INCINERATOR_SYNDICATELAVA_IGNITER "syndicatelava_igniter" +#define INCINERATOR_SYNDICATELAVA_MAINVENT "syndicatelava_mainvent" +#define INCINERATOR_SYNDICATELAVA_AUXVENT "syndicatelava_auxvent" +#define INCINERATOR_SYNDICATELAVA_DP_VENTPUMP "syndicatelava_airlock_pump" +#define INCINERATOR_SYNDICATELAVA_AIRLOCK_SENSOR "syndicatelava_airlock_sensor" +#define INCINERATOR_SYNDICATELAVA_AIRLOCK_CONTROLLER "syndicatelava_airlock_controller" +#define INCINERATOR_SYNDICATELAVA_AIRLOCK_INTERIOR "syndicatelava_airlock_interior" +#define INCINERATOR_SYNDICATELAVA_AIRLOCK_EXTERIOR "syndicatelava_airlock_exterior" + +//MULTIPIPES +//IF YOU EVER CHANGE THESE CHANGE SPRITES TO MATCH. +#define PIPING_LAYER_MIN 1 +#define PIPING_LAYER_MAX 3 +#define PIPING_LAYER_DEFAULT 2 +#define PIPING_LAYER_P_X 5 +#define PIPING_LAYER_P_Y 5 +#define PIPING_LAYER_LCHANGE 0.05 + +/// intended to connect with all layers, check for all instead of just one. +#define PIPING_ALL_LAYER (1<<0) +/// can only be built if nothing else with this flag is on the tile already. +#define PIPING_ONE_PER_TURF (1<<1) +/// can only exist at PIPING_LAYER_DEFAULT +#define PIPING_DEFAULT_LAYER_ONLY (1<<2) +/// north/south east/west doesn't matter, auto normalize on build. +#define PIPING_CARDINAL_AUTONORMALIZE (1<<3) + +//HELPERS +#define PIPING_LAYER_SHIFT(T, PipingLayer) \ + if(T.dir & (NORTH|SOUTH)) { \ + T.pixel_x = (PipingLayer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_X;\ + } \ + if(T.dir & (EAST|WEST)) { \ + T.pixel_y = (PipingLayer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_Y;\ + } + +#define PIPING_LAYER_DOUBLE_SHIFT(T, PipingLayer) \ + T.pixel_x = (PipingLayer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_X;\ + T.pixel_y = (PipingLayer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_Y; + +#ifdef TESTING +GLOBAL_LIST_INIT(atmos_adjacent_savings, list(0,0)) +#define CALCULATE_ADJACENT_TURFS(T) if (SSadjacent_air.queue[T]) { GLOB.atmos_adjacent_savings[1] += 1 } else { GLOB.atmos_adjacent_savings[2] += 1; SSadjacent_air.queue[T] = 1 } +#else +#define CALCULATE_ADJACENT_TURFS(T) SSadjacent_air.queue[T] = 1 +#endif + +GLOBAL_VAR(atmos_extools_initialized) // this must be an uninitialized (null) one or init_monstermos will be called twice because reasons +#define ATMOS_EXTOOLS_CHECK if(!GLOB.atmos_extools_initialized){\ + GLOB.atmos_extools_initialized=TRUE;\ + if(fexists(world.system_type == MS_WINDOWS ? "byond-extools.dll" : "libbyond-extools.so")){\ + var/result = call((world.system_type == MS_WINDOWS ? "byond-extools.dll" : "libbyond-extools.so"),"init_monstermos")();\ + if(result != "ok") {CRASH(result);}\ + } else {\ + CRASH("byond-extools.dll does not exist!");\ + }\ +} + +GLOBAL_LIST_INIT(pipe_paint_colors, sortList(list( + "amethyst" = rgb(130,43,255), //supplymain + "blue" = rgb(0,0,255), + "brown" = rgb(178,100,56), + "cyan" = rgb(0,255,249), + "dark" = rgb(69,69,69), + "green" = rgb(30,255,0), + "grey" = rgb(255,255,255), + "orange" = rgb(255,129,25), + "purple" = rgb(128,0,182), + "red" = rgb(255,0,0), + "violet" = rgb(64,0,128), + "yellow" = rgb(255,198,0) +))) + +#define MIASMA_CORPSE_MOLES 0.02 +#define MIASMA_GIBS_MOLES 0.005 diff --git a/code/__DEFINES/bsql.config.dm b/code/__DEFINES/bsql.config.dm deleted file mode 100644 index ce1964c217cc..000000000000 --- 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 e5a11f9a32f3..000000000000 --- 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/combat.dm b/code/__DEFINES/combat.dm index 19288eccdf76..980229dfcbfd 100644 --- a/code/__DEFINES/combat.dm +++ b/code/__DEFINES/combat.dm @@ -1,244 +1,244 @@ -/*ALL DEFINES RELATED TO COMBAT GO HERE*/ - -//Damage and status effect defines - -//Damage defines //TODO: merge these down to reduce on defines -#define BRUTE "brute" -#define BURN "fire" -#define TOX "toxin" -#define OXY "oxygen" -#define CLONE "clone" -#define STAMINA "stamina" -#define BRAIN "brain" - -//bitflag damage defines used for suicide_act -#define BRUTELOSS (1<<0) -#define FIRELOSS (1<<1) -#define TOXLOSS (1<<2) -#define OXYLOSS (1<<3) -#define SHAME (1<<4) -#define MANUAL_SUICIDE (1<<5) //suicide_act will do the actual killing. -#define MANUAL_SUICIDE_NONLETHAL (1<<6) //when the suicide is conditionally lethal - -#define EFFECT_STUN "stun" -#define EFFECT_KNOCKDOWN "knockdown" -#define EFFECT_UNCONSCIOUS "unconscious" -#define EFFECT_PARALYZE "paralyze" -#define EFFECT_IMMOBILIZE "immobilize" -#define EFFECT_IRRADIATE "irradiate" -#define EFFECT_STUTTER "stutter" -#define EFFECT_SLUR "slur" -#define EFFECT_EYE_BLUR "eye_blur" -#define EFFECT_DROWSY "drowsy" -#define EFFECT_JITTER "jitter" - -//Bitflags defining which status effects could be or are inflicted on a mob -#define CANSTUN (1<<0) -#define CANKNOCKDOWN (1<<1) -#define CANUNCONSCIOUS (1<<2) -#define CANPUSH (1<<3) -#define GODMODE (1<<4) - -//Health Defines -#define HEALTH_THRESHOLD_CRIT 0 -#define HEALTH_THRESHOLD_FULLCRIT -30 -#define HEALTH_THRESHOLD_DEAD -100 - -#define HEALTH_THRESHOLD_NEARDEATH -90 //Not used mechanically, but to determine if someone is so close to death they hear the other side - -//Actual combat defines - -//click cooldowns, in tenths of a second, used for various combat actions -#define CLICK_CD_MELEE 8 -#define CLICK_CD_RANGE 4 -#define CLICK_CD_RAPID 2 -#define CLICK_CD_CLICK_ABILITY 6 -#define CLICK_CD_BREAKOUT 100 -#define CLICK_CD_HANDCUFFED 10 -#define CLICK_CD_RESIST 20 -#define CLICK_CD_GRABBING 10 - -//Cuff resist speeds -#define FAST_CUFFBREAK 1 -#define INSTANT_CUFFBREAK 2 - -//Grab levels -#define GRAB_PASSIVE 0 -#define GRAB_AGGRESSIVE 1 -#define GRAB_NECK 2 -#define GRAB_KILL 3 - -//Grab breakout odds -#define BASE_GRAB_RESIST_CHANCE 30 - -//slowdown when in softcrit. Note that crawling slowdown will also apply at the same time! -#define SOFTCRIT_ADD_SLOWDOWN 2 -//slowdown when crawling -#define CRAWLING_ADD_SLOWDOWN 4 - -//Attack types for checking shields/hit reactions -#define MELEE_ATTACK 1 -#define UNARMED_ATTACK 2 -#define PROJECTILE_ATTACK 3 -#define THROWN_PROJECTILE_ATTACK 4 -#define LEAP_ATTACK 5 - -//attack visual effects -#define ATTACK_EFFECT_PUNCH "punch" -#define ATTACK_EFFECT_KICK "kick" -#define ATTACK_EFFECT_SMASH "smash" -#define ATTACK_EFFECT_CLAW "claw" -#define ATTACK_EFFECT_SLASH "slash" -#define ATTACK_EFFECT_DISARM "disarm" -#define ATTACK_EFFECT_BITE "bite" -#define ATTACK_EFFECT_MECHFIRE "mech_fire" -#define ATTACK_EFFECT_MECHTOXIN "mech_toxin" -#define ATTACK_EFFECT_BOOP "boop" //Honk - -//intent defines -#define INTENT_HELP "help" -#define INTENT_GRAB "grab" -#define INTENT_DISARM "disarm" -#define INTENT_HARM "harm" -//NOTE: INTENT_HOTKEY_* defines are not actual intents! -//they are here to support hotkeys -#define INTENT_HOTKEY_LEFT "left" -#define INTENT_HOTKEY_RIGHT "right" - -//the define for visible message range in combat -#define COMBAT_MESSAGE_RANGE 3 -#define DEFAULT_MESSAGE_RANGE 7 - -//Shove knockdown lengths (deciseconds) -#define SHOVE_KNOCKDOWN_SOLID 30 -#define SHOVE_KNOCKDOWN_HUMAN 30 -#define SHOVE_KNOCKDOWN_TABLE 30 -#define SHOVE_KNOCKDOWN_COLLATERAL 10 -#define SHOVE_CHAIN_PARALYZE 40 -//Shove slowdown -#define SHOVE_SLOWDOWN_LENGTH 30 -#define SHOVE_SLOWDOWN_STRENGTH 0.85 //multiplier -//Shove disarming item list -GLOBAL_LIST_INIT(shove_disarming_types, typecacheof(list( - /obj/item/gun))) - - -//Combat object defines - -//Embedded objects -#define EMBEDDED_PAIN_CHANCE 15 //Chance for embedded objects to cause pain (damage user) -#define EMBEDDED_ITEM_FALLOUT 5 //Chance for embedded object to fall out (causing pain but removing the object) -#define EMBED_CHANCE 45 //Chance for an object to embed into somebody when thrown (if it's sharp) -#define EMBEDDED_PAIN_MULTIPLIER 2 //Coefficient of multiplication for the damage the item does while embedded (this*item.w_class) -#define EMBEDDED_FALL_PAIN_MULTIPLIER 5 //Coefficient of multiplication for the damage the item does when it falls out (this*item.w_class) -#define EMBEDDED_IMPACT_PAIN_MULTIPLIER 4 //Coefficient of multiplication for the damage the item does when it first embeds (this*item.w_class) -#define EMBED_THROWSPEED_THRESHOLD 4 //The minimum value of an item's throw_speed for it to embed (Unless it has embedded_ignore_throwspeed_threshold set to 1) -#define EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER 8 //Coefficient of multiplication for the damage the item does when removed without a surgery (this*item.w_class) -#define EMBEDDED_UNSAFE_REMOVAL_TIME 30 //A Time in ticks, total removal time = (this*item.w_class) -#define EMBEDDED_JOSTLE_CHANCE 5 //Chance for embedded objects to cause pain every time they move (jostle) -#define EMBEDDED_JOSTLE_PAIN_MULTIPLIER 1 //Coefficient of multiplication for the damage the item does while -#define EMBEDDED_PAIN_STAM_PCT 0.0 //This percentage of all pain will be dealt as stam damage rather than brute (0-1) -#define EMBED_CHANCE_TURF_MOD -15 //You are this many percentage points less likely to embed into a turf (good for things glass shards and spears vs walls) - -#define EMBED_HARMLESS list("pain_mult" = 0, "jostle_pain_mult" = 0, "ignore_throwspeed_threshold" = TRUE) -#define EMBED_HARMLESS_SUPERIOR list("pain_mult" = 0, "jostle_pain_mult" = 0, "ignore_throwspeed_threshold" = TRUE, "embed_chance" = 100, "fall_chance" = 0.1) -#define EMBED_POINTY list("ignore_throwspeed_threshold" = TRUE) -#define EMBED_POINTY_SUPERIOR list("embed_chance" = 100, "ignore_throwspeed_threshold" = TRUE) - -//Gun weapon weight -#define WEAPON_LIGHT 1 -#define WEAPON_MEDIUM 2 -#define WEAPON_HEAVY 3 -//Gun trigger guards -#define TRIGGER_GUARD_ALLOW_ALL -1 -#define TRIGGER_GUARD_NONE 0 -#define TRIGGER_GUARD_NORMAL 1 -//Gun bolt types -///Gun has a bolt, it stays closed while not cycling. The gun must be racked to have a bullet chambered when a mag is inserted. -/// Example: c20, shotguns, m90 -#define BOLT_TYPE_STANDARD 1 -///Gun has a bolt, it is open when ready to fire. The gun can never have a chambered bullet with no magazine, but the bolt stays ready when a mag is removed. -/// Example: Some SMGs, the L6 -#define BOLT_TYPE_OPEN 2 -///Gun has no moving bolt mechanism, it cannot be racked. Also dumps the entire contents when emptied instead of a magazine. -/// Example: Break action shotguns, revolvers -#define BOLT_TYPE_NO_BOLT 3 -///Gun has a bolt, it locks back when empty. It can be released to chamber a round if a magazine is in. -/// Example: Pistols with a slide lock, some SMGs -#define BOLT_TYPE_LOCKING 4 -//Sawn off nerfs -///accuracy penalty of sawn off guns -#define SAWN_OFF_ACC_PENALTY 25 -///added recoil of sawn off guns -#define SAWN_OFF_RECOIL 1 - -//ammo box sprite defines -///ammo box will always use provided icon state -#define AMMO_BOX_ONE_SPRITE 0 -///ammo box will have a different state for each bullet; - -#define AMMO_BOX_PER_BULLET 1 -///ammo box will have a different state for full and empty; -max_ammo and -0 -#define AMMO_BOX_FULL_EMPTY 2 - -#define SUPPRESSED_NONE 0 -#define SUPPRESSED_QUIET 1 ///standard suppressed -#define SUPPRESSED_VERY 2 /// no message - -//Projectile Reflect -#define REFLECT_NORMAL (1<<0) -#define REFLECT_FAKEPROJECTILE (1<<1) - -//Object/Item sharpness -#define IS_BLUNT 0 -#define IS_SHARP 1 -#define IS_SHARP_ACCURATE 2 - -//His Grace. -#define HIS_GRACE_SATIATED 0 //He hungers not. If bloodthirst is set to this, His Grace is asleep. -#define HIS_GRACE_PECKISH 20 //Slightly hungry. -#define HIS_GRACE_HUNGRY 60 //Getting closer. Increases damage up to a minimum of 20. -#define HIS_GRACE_FAMISHED 100 //Dangerous. Increases damage up to a minimum of 25 and cannot be dropped. -#define HIS_GRACE_STARVING 120 //Incredibly close to breaking loose. Increases damage up to a minimum of 30. -#define HIS_GRACE_CONSUME_OWNER 140 //His Grace consumes His owner at this point and becomes aggressive. -#define HIS_GRACE_FALL_ASLEEP 160 //If it reaches this point, He falls asleep and resets. - -#define HIS_GRACE_FORCE_BONUS 4 //How much force is gained per kill. - -#define EXPLODE_NONE 0 //Don't even ask me why we need this. -#define EXPLODE_DEVASTATE 1 -#define EXPLODE_HEAVY 2 -#define EXPLODE_LIGHT 3 -#define EXPLODE_GIB_THRESHOLD 50 //ex_act() with EXPLODE_DEVASTATE severity will gib mobs with less than this much bomb armor - -#define EMP_HEAVY 1 -#define EMP_LIGHT 2 - -#define GRENADE_CLUMSY_FUMBLE 1 -#define GRENADE_NONCLUMSY_FUMBLE 2 -#define GRENADE_NO_FUMBLE 3 - -#define BODY_ZONE_HEAD "head" -#define BODY_ZONE_CHEST "chest" -#define BODY_ZONE_L_ARM "l_arm" -#define BODY_ZONE_R_ARM "r_arm" -#define BODY_ZONE_L_LEG "l_leg" -#define BODY_ZONE_R_LEG "r_leg" - -#define BODY_ZONE_PRECISE_EYES "eyes" -#define BODY_ZONE_PRECISE_MOUTH "mouth" -#define BODY_ZONE_PRECISE_GROIN "groin" -#define BODY_ZONE_PRECISE_L_HAND "l_hand" -#define BODY_ZONE_PRECISE_R_HAND "r_hand" -#define BODY_ZONE_PRECISE_L_FOOT "l_foot" -#define BODY_ZONE_PRECISE_R_FOOT "r_foot" - -//We will round to this value in damage calculations. -#define DAMAGE_PRECISION 0.1 - -//bullet_act() return values -#define BULLET_ACT_HIT "HIT" //It's a successful hit, whatever that means in the context of the thing it's hitting. -#define BULLET_ACT_BLOCK "BLOCK" //It's a blocked hit, whatever that means in the context of the thing it's hitting. -#define BULLET_ACT_FORCE_PIERCE "PIERCE" //It pierces through the object regardless of the bullet being piercing by default. -#define BULLET_ACT_TURF "TURF" //It hit us but it should hit something on the same turf too. Usually used for turfs. - -#define NICE_SHOT_RICOCHET_BONUS 10 //if the shooter has the NICE_SHOT trait and they fire a ricocheting projectile, add this to the ricochet chance and auto aim angle +/*ALL DEFINES RELATED TO COMBAT GO HERE*/ + +//Damage and status effect defines + +//Damage defines //TODO: merge these down to reduce on defines +#define BRUTE "brute" +#define BURN "fire" +#define TOX "toxin" +#define OXY "oxygen" +#define CLONE "clone" +#define STAMINA "stamina" +#define BRAIN "brain" + +//bitflag damage defines used for suicide_act +#define BRUTELOSS (1<<0) +#define FIRELOSS (1<<1) +#define TOXLOSS (1<<2) +#define OXYLOSS (1<<3) +#define SHAME (1<<4) +#define MANUAL_SUICIDE (1<<5) //suicide_act will do the actual killing. +#define MANUAL_SUICIDE_NONLETHAL (1<<6) //when the suicide is conditionally lethal + +#define EFFECT_STUN "stun" +#define EFFECT_KNOCKDOWN "knockdown" +#define EFFECT_UNCONSCIOUS "unconscious" +#define EFFECT_PARALYZE "paralyze" +#define EFFECT_IMMOBILIZE "immobilize" +#define EFFECT_IRRADIATE "irradiate" +#define EFFECT_STUTTER "stutter" +#define EFFECT_SLUR "slur" +#define EFFECT_EYE_BLUR "eye_blur" +#define EFFECT_DROWSY "drowsy" +#define EFFECT_JITTER "jitter" + +//Bitflags defining which status effects could be or are inflicted on a mob +#define CANSTUN (1<<0) +#define CANKNOCKDOWN (1<<1) +#define CANUNCONSCIOUS (1<<2) +#define CANPUSH (1<<3) +#define GODMODE (1<<4) + +//Health Defines +#define HEALTH_THRESHOLD_CRIT 0 +#define HEALTH_THRESHOLD_FULLCRIT -30 +#define HEALTH_THRESHOLD_DEAD -100 + +#define HEALTH_THRESHOLD_NEARDEATH -90 //Not used mechanically, but to determine if someone is so close to death they hear the other side + +//Actual combat defines + +//click cooldowns, in tenths of a second, used for various combat actions +#define CLICK_CD_MELEE 8 +#define CLICK_CD_RANGE 4 +#define CLICK_CD_RAPID 2 +#define CLICK_CD_CLICK_ABILITY 6 +#define CLICK_CD_BREAKOUT 100 +#define CLICK_CD_HANDCUFFED 10 +#define CLICK_CD_RESIST 20 +#define CLICK_CD_GRABBING 10 + +//Cuff resist speeds +#define FAST_CUFFBREAK 1 +#define INSTANT_CUFFBREAK 2 + +//Grab levels +#define GRAB_PASSIVE 0 +#define GRAB_AGGRESSIVE 1 +#define GRAB_NECK 2 +#define GRAB_KILL 3 + +//Grab breakout odds +#define BASE_GRAB_RESIST_CHANCE 30 + +//slowdown when in softcrit. Note that crawling slowdown will also apply at the same time! +#define SOFTCRIT_ADD_SLOWDOWN 2 +//slowdown when crawling +#define CRAWLING_ADD_SLOWDOWN 4 + +//Attack types for checking shields/hit reactions +#define MELEE_ATTACK 1 +#define UNARMED_ATTACK 2 +#define PROJECTILE_ATTACK 3 +#define THROWN_PROJECTILE_ATTACK 4 +#define LEAP_ATTACK 5 + +//attack visual effects +#define ATTACK_EFFECT_PUNCH "punch" +#define ATTACK_EFFECT_KICK "kick" +#define ATTACK_EFFECT_SMASH "smash" +#define ATTACK_EFFECT_CLAW "claw" +#define ATTACK_EFFECT_SLASH "slash" +#define ATTACK_EFFECT_DISARM "disarm" +#define ATTACK_EFFECT_BITE "bite" +#define ATTACK_EFFECT_MECHFIRE "mech_fire" +#define ATTACK_EFFECT_MECHTOXIN "mech_toxin" +#define ATTACK_EFFECT_BOOP "boop" //Honk + +//intent defines +#define INTENT_HELP "help" +#define INTENT_GRAB "grab" +#define INTENT_DISARM "disarm" +#define INTENT_HARM "harm" +//NOTE: INTENT_HOTKEY_* defines are not actual intents! +//they are here to support hotkeys +#define INTENT_HOTKEY_LEFT "left" +#define INTENT_HOTKEY_RIGHT "right" + +//the define for visible message range in combat +#define COMBAT_MESSAGE_RANGE 3 +#define DEFAULT_MESSAGE_RANGE 7 + +//Shove knockdown lengths (deciseconds) +#define SHOVE_KNOCKDOWN_SOLID 30 +#define SHOVE_KNOCKDOWN_HUMAN 30 +#define SHOVE_KNOCKDOWN_TABLE 30 +#define SHOVE_KNOCKDOWN_COLLATERAL 10 +#define SHOVE_CHAIN_PARALYZE 40 +//Shove slowdown +#define SHOVE_SLOWDOWN_LENGTH 30 +#define SHOVE_SLOWDOWN_STRENGTH 0.85 //multiplier +//Shove disarming item list +GLOBAL_LIST_INIT(shove_disarming_types, typecacheof(list( + /obj/item/gun))) + + +//Combat object defines + +//Embedded objects +#define EMBEDDED_PAIN_CHANCE 15 //Chance for embedded objects to cause pain (damage user) +#define EMBEDDED_ITEM_FALLOUT 5 //Chance for embedded object to fall out (causing pain but removing the object) +#define EMBED_CHANCE 45 //Chance for an object to embed into somebody when thrown (if it's sharp) +#define EMBEDDED_PAIN_MULTIPLIER 2 //Coefficient of multiplication for the damage the item does while embedded (this*item.w_class) +#define EMBEDDED_FALL_PAIN_MULTIPLIER 5 //Coefficient of multiplication for the damage the item does when it falls out (this*item.w_class) +#define EMBEDDED_IMPACT_PAIN_MULTIPLIER 4 //Coefficient of multiplication for the damage the item does when it first embeds (this*item.w_class) +#define EMBED_THROWSPEED_THRESHOLD 4 //The minimum value of an item's throw_speed for it to embed (Unless it has embedded_ignore_throwspeed_threshold set to 1) +#define EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER 8 //Coefficient of multiplication for the damage the item does when removed without a surgery (this*item.w_class) +#define EMBEDDED_UNSAFE_REMOVAL_TIME 30 //A Time in ticks, total removal time = (this*item.w_class) +#define EMBEDDED_JOSTLE_CHANCE 5 //Chance for embedded objects to cause pain every time they move (jostle) +#define EMBEDDED_JOSTLE_PAIN_MULTIPLIER 1 //Coefficient of multiplication for the damage the item does while +#define EMBEDDED_PAIN_STAM_PCT 0.0 //This percentage of all pain will be dealt as stam damage rather than brute (0-1) +#define EMBED_CHANCE_TURF_MOD -15 //You are this many percentage points less likely to embed into a turf (good for things glass shards and spears vs walls) + +#define EMBED_HARMLESS list("pain_mult" = 0, "jostle_pain_mult" = 0, "ignore_throwspeed_threshold" = TRUE) +#define EMBED_HARMLESS_SUPERIOR list("pain_mult" = 0, "jostle_pain_mult" = 0, "ignore_throwspeed_threshold" = TRUE, "embed_chance" = 100, "fall_chance" = 0.1) +#define EMBED_POINTY list("ignore_throwspeed_threshold" = TRUE) +#define EMBED_POINTY_SUPERIOR list("embed_chance" = 100, "ignore_throwspeed_threshold" = TRUE) + +//Gun weapon weight +#define WEAPON_LIGHT 1 +#define WEAPON_MEDIUM 2 +#define WEAPON_HEAVY 3 +//Gun trigger guards +#define TRIGGER_GUARD_ALLOW_ALL -1 +#define TRIGGER_GUARD_NONE 0 +#define TRIGGER_GUARD_NORMAL 1 +//Gun bolt types +///Gun has a bolt, it stays closed while not cycling. The gun must be racked to have a bullet chambered when a mag is inserted. +/// Example: c20, shotguns, m90 +#define BOLT_TYPE_STANDARD 1 +///Gun has a bolt, it is open when ready to fire. The gun can never have a chambered bullet with no magazine, but the bolt stays ready when a mag is removed. +/// Example: Some SMGs, the L6 +#define BOLT_TYPE_OPEN 2 +///Gun has no moving bolt mechanism, it cannot be racked. Also dumps the entire contents when emptied instead of a magazine. +/// Example: Break action shotguns, revolvers +#define BOLT_TYPE_NO_BOLT 3 +///Gun has a bolt, it locks back when empty. It can be released to chamber a round if a magazine is in. +/// Example: Pistols with a slide lock, some SMGs +#define BOLT_TYPE_LOCKING 4 +//Sawn off nerfs +///accuracy penalty of sawn off guns +#define SAWN_OFF_ACC_PENALTY 25 +///added recoil of sawn off guns +#define SAWN_OFF_RECOIL 1 + +//ammo box sprite defines +///ammo box will always use provided icon state +#define AMMO_BOX_ONE_SPRITE 0 +///ammo box will have a different state for each bullet; - +#define AMMO_BOX_PER_BULLET 1 +///ammo box will have a different state for full and empty; -max_ammo and -0 +#define AMMO_BOX_FULL_EMPTY 2 + +#define SUPPRESSED_NONE 0 +#define SUPPRESSED_QUIET 1 ///standard suppressed +#define SUPPRESSED_VERY 2 /// no message + +//Projectile Reflect +#define REFLECT_NORMAL (1<<0) +#define REFLECT_FAKEPROJECTILE (1<<1) + +//Object/Item sharpness +#define IS_BLUNT 0 +#define IS_SHARP 1 +#define IS_SHARP_ACCURATE 2 + +//His Grace. +#define HIS_GRACE_SATIATED 0 //He hungers not. If bloodthirst is set to this, His Grace is asleep. +#define HIS_GRACE_PECKISH 20 //Slightly hungry. +#define HIS_GRACE_HUNGRY 60 //Getting closer. Increases damage up to a minimum of 20. +#define HIS_GRACE_FAMISHED 100 //Dangerous. Increases damage up to a minimum of 25 and cannot be dropped. +#define HIS_GRACE_STARVING 120 //Incredibly close to breaking loose. Increases damage up to a minimum of 30. +#define HIS_GRACE_CONSUME_OWNER 140 //His Grace consumes His owner at this point and becomes aggressive. +#define HIS_GRACE_FALL_ASLEEP 160 //If it reaches this point, He falls asleep and resets. + +#define HIS_GRACE_FORCE_BONUS 4 //How much force is gained per kill. + +#define EXPLODE_NONE 0 //Don't even ask me why we need this. +#define EXPLODE_DEVASTATE 1 +#define EXPLODE_HEAVY 2 +#define EXPLODE_LIGHT 3 +#define EXPLODE_GIB_THRESHOLD 50 //ex_act() with EXPLODE_DEVASTATE severity will gib mobs with less than this much bomb armor + +#define EMP_HEAVY 1 +#define EMP_LIGHT 2 + +#define GRENADE_CLUMSY_FUMBLE 1 +#define GRENADE_NONCLUMSY_FUMBLE 2 +#define GRENADE_NO_FUMBLE 3 + +#define BODY_ZONE_HEAD "head" +#define BODY_ZONE_CHEST "chest" +#define BODY_ZONE_L_ARM "l_arm" +#define BODY_ZONE_R_ARM "r_arm" +#define BODY_ZONE_L_LEG "l_leg" +#define BODY_ZONE_R_LEG "r_leg" + +#define BODY_ZONE_PRECISE_EYES "eyes" +#define BODY_ZONE_PRECISE_MOUTH "mouth" +#define BODY_ZONE_PRECISE_GROIN "groin" +#define BODY_ZONE_PRECISE_L_HAND "l_hand" +#define BODY_ZONE_PRECISE_R_HAND "r_hand" +#define BODY_ZONE_PRECISE_L_FOOT "l_foot" +#define BODY_ZONE_PRECISE_R_FOOT "r_foot" + +//We will round to this value in damage calculations. +#define DAMAGE_PRECISION 0.1 + +//bullet_act() return values +#define BULLET_ACT_HIT "HIT" //It's a successful hit, whatever that means in the context of the thing it's hitting. +#define BULLET_ACT_BLOCK "BLOCK" //It's a blocked hit, whatever that means in the context of the thing it's hitting. +#define BULLET_ACT_FORCE_PIERCE "PIERCE" //It pierces through the object regardless of the bullet being piercing by default. +#define BULLET_ACT_TURF "TURF" //It hit us but it should hit something on the same turf too. Usually used for turfs. + +#define NICE_SHOT_RICOCHET_BONUS 10 //if the shooter has the NICE_SHOT trait and they fire a ricocheting projectile, add this to the ricochet chance and auto aim angle diff --git a/code/__DEFINES/configuration.dm b/code/__DEFINES/configuration.dm index c724c358dbf5..3fbc6d098f35 100644 --- a/code/__DEFINES/configuration.dm +++ b/code/__DEFINES/configuration.dm @@ -1,11 +1,11 @@ -//config files -#define CONFIG_GET(X) global.config.Get(/datum/config_entry/##X) -#define CONFIG_SET(X, Y) global.config.Set(/datum/config_entry/##X, ##Y) - -#define CONFIG_MAPS_FILE "maps.txt" - -//flags -/// can't edit -#define CONFIG_ENTRY_LOCKED 1 -/// can't see value -#define CONFIG_ENTRY_HIDDEN 2 +//config files +#define CONFIG_GET(X) global.config.Get(/datum/config_entry/##X) +#define CONFIG_SET(X, Y) global.config.Set(/datum/config_entry/##X, ##Y) + +#define CONFIG_MAPS_FILE "maps.txt" + +//flags +/// can't edit +#define CONFIG_ENTRY_LOCKED 1 +/// can't see value +#define CONFIG_ENTRY_HIDDEN 2 diff --git a/code/__DEFINES/forensics.dm b/code/__DEFINES/forensics.dm index f6121482dab9..8d0647cb1ed8 100644 --- a/code/__DEFINES/forensics.dm +++ b/code/__DEFINES/forensics.dm @@ -1 +1 @@ -#define HAS_BLOOD_DNA(thing) (length(thing.GetComponent(/datum/component/forensics)?.blood_DNA)) +#define HAS_BLOOD_DNA(thing) (length(thing.GetComponent(/datum/component/forensics)?.blood_DNA)) diff --git a/code/__DEFINES/hud.dm b/code/__DEFINES/hud.dm index 0d6ec96021de..58d465d70aa3 100644 --- a/code/__DEFINES/hud.dm +++ b/code/__DEFINES/hud.dm @@ -1,15 +1,15 @@ -//HUD styles. Index order defines how they are cycled in F12. -/// Standard hud -#define HUD_STYLE_STANDARD 1 -/// Reduced hud (just hands and intent switcher) -#define HUD_STYLE_REDUCED 2 -/// No hud (for screenshots) -#define HUD_STYLE_NOHUD 3 - -/// Used in show_hud(); Please ensure this is the same as the maximum index. -#define HUD_VERSIONS 3 - -//1:1 HUD layout stuff -#define UI_BOXCRAFT "EAST-4:22,SOUTH+1:6" -#define UI_BOXAREA "EAST-4:6,SOUTH+1:6" -#define UI_BOXLANG "EAST-5:22,SOUTH+1:6" +//HUD styles. Index order defines how they are cycled in F12. +/// Standard hud +#define HUD_STYLE_STANDARD 1 +/// Reduced hud (just hands and intent switcher) +#define HUD_STYLE_REDUCED 2 +/// No hud (for screenshots) +#define HUD_STYLE_NOHUD 3 + +/// Used in show_hud(); Please ensure this is the same as the maximum index. +#define HUD_VERSIONS 3 + +//1:1 HUD layout stuff +#define UI_BOXCRAFT "EAST-4:22,SOUTH+1:6" +#define UI_BOXAREA "EAST-4:6,SOUTH+1:6" +#define UI_BOXLANG "EAST-5:22,SOUTH+1:6" diff --git a/code/__DEFINES/interaction_flags.dm b/code/__DEFINES/interaction_flags.dm index b9aaa3e6391b..a85ffb8d7a09 100644 --- a/code/__DEFINES/interaction_flags.dm +++ b/code/__DEFINES/interaction_flags.dm @@ -1,38 +1,38 @@ -/// whether can_interact() checks for anchored. only works on movables. -#define INTERACT_ATOM_REQUIRES_ANCHORED (1<<0) -/// calls try_interact() on attack_hand() and returns that. -#define INTERACT_ATOM_ATTACK_HAND (1<<1) -/// automatically calls and returns ui_interact() on interact(). -#define INTERACT_ATOM_UI_INTERACT (1<<2) -/// user must be dextrous -#define INTERACT_ATOM_REQUIRES_DEXTERITY (1<<3) -/// ignores incapacitated check -#define INTERACT_ATOM_IGNORE_INCAPACITATED (1<<4) -/// incapacitated check ignores restrained -#define INTERACT_ATOM_IGNORE_RESTRAINED (1<<5) -/// incapacitated check checks grab -#define INTERACT_ATOM_CHECK_GRAB (1<<6) -/// prevents leaving fingerprints automatically on attack_hand -#define INTERACT_ATOM_NO_FINGERPRINT_ATTACK_HAND (1<<7) -/// adds hiddenprints instead of fingerprints on interact -#define INTERACT_ATOM_NO_FINGERPRINT_INTERACT (1<<8) - -/// attempt pickup on attack_hand for items -#define INTERACT_ITEM_ATTACK_HAND_PICKUP (1<<0) - -/// can_interact() while open -#define INTERACT_MACHINE_OPEN (1<<0) -/// can_interact() while offline -#define INTERACT_MACHINE_OFFLINE (1<<1) -/// try to interact with wires if open -#define INTERACT_MACHINE_WIRES_IF_OPEN (1<<2) -/// let silicons interact -#define INTERACT_MACHINE_ALLOW_SILICON (1<<3) -/// let silicons interact while open -#define INTERACT_MACHINE_OPEN_SILICON (1<<4) -/// must be silicon to interact -#define INTERACT_MACHINE_REQUIRES_SILICON (1<<5) -/// MACHINES HAVE THIS BY DEFAULT, SOMEONE SHOULD RUN THROUGH MACHINES AND REMOVE IT FROM THINGS LIKE LIGHT SWITCHES WHEN POSSIBLE!!-------------------------- -/// This flag determines if a machine set_machine's the user when the user uses it, making updateUsrDialog make the user re-call interact() on it. -/// THIS FLAG IS ON ALL MACHINES BY DEFAULT, NEEDS TO BE RE-EVALUATED LATER!! -#define INTERACT_MACHINE_SET_MACHINE (1<<6) +/// whether can_interact() checks for anchored. only works on movables. +#define INTERACT_ATOM_REQUIRES_ANCHORED (1<<0) +/// calls try_interact() on attack_hand() and returns that. +#define INTERACT_ATOM_ATTACK_HAND (1<<1) +/// automatically calls and returns ui_interact() on interact(). +#define INTERACT_ATOM_UI_INTERACT (1<<2) +/// user must be dextrous +#define INTERACT_ATOM_REQUIRES_DEXTERITY (1<<3) +/// ignores incapacitated check +#define INTERACT_ATOM_IGNORE_INCAPACITATED (1<<4) +/// incapacitated check ignores restrained +#define INTERACT_ATOM_IGNORE_RESTRAINED (1<<5) +/// incapacitated check checks grab +#define INTERACT_ATOM_CHECK_GRAB (1<<6) +/// prevents leaving fingerprints automatically on attack_hand +#define INTERACT_ATOM_NO_FINGERPRINT_ATTACK_HAND (1<<7) +/// adds hiddenprints instead of fingerprints on interact +#define INTERACT_ATOM_NO_FINGERPRINT_INTERACT (1<<8) + +/// attempt pickup on attack_hand for items +#define INTERACT_ITEM_ATTACK_HAND_PICKUP (1<<0) + +/// can_interact() while open +#define INTERACT_MACHINE_OPEN (1<<0) +/// can_interact() while offline +#define INTERACT_MACHINE_OFFLINE (1<<1) +/// try to interact with wires if open +#define INTERACT_MACHINE_WIRES_IF_OPEN (1<<2) +/// let silicons interact +#define INTERACT_MACHINE_ALLOW_SILICON (1<<3) +/// let silicons interact while open +#define INTERACT_MACHINE_OPEN_SILICON (1<<4) +/// must be silicon to interact +#define INTERACT_MACHINE_REQUIRES_SILICON (1<<5) +/// MACHINES HAVE THIS BY DEFAULT, SOMEONE SHOULD RUN THROUGH MACHINES AND REMOVE IT FROM THINGS LIKE LIGHT SWITCHES WHEN POSSIBLE!!-------------------------- +/// This flag determines if a machine set_machine's the user when the user uses it, making updateUsrDialog make the user re-call interact() on it. +/// THIS FLAG IS ON ALL MACHINES BY DEFAULT, NEEDS TO BE RE-EVALUATED LATER!! +#define INTERACT_MACHINE_SET_MACHINE (1<<6) diff --git a/code/__DEFINES/jobs.dm b/code/__DEFINES/jobs.dm index 819668fb1972..2b2a20df8e9f 100644 --- a/code/__DEFINES/jobs.dm +++ b/code/__DEFINES/jobs.dm @@ -1,94 +1,47 @@ - -#define ENGSEC (1<<0) - -#define CAPTAIN (1<<0) -#define HOS (1<<1) -#define WARDEN (1<<2) -#define DETECTIVE (1<<3) -#define OFFICER (1<<4) -#define CHIEF (1<<5) -#define ENGINEER (1<<6) -#define ATMOSTECH (1<<7) -#define ROBOTICIST (1<<8) -#define AI_JF (1<<9) -#define CYBORG (1<<10) -#define BRIG_PHYS (1<<11) - -#define MEDSCI (1<<1) - -#define RD_JF (1<<0) -#define SCIENTIST (1<<1) -#define CHEMIST (1<<2) -#define CMO_JF (1<<3) -#define DOCTOR (1<<4) -#define GENETICIST (1<<5) -#define VIROLOGIST (1<<6) -#define PARAMEDIC (1<<7) - - -#define CIVILIAN (1<<2) - -#define HOP (1<<0) -#define BARTENDER (1<<1) -#define BOTANIST (1<<2) -#define COOK (1<<3) -#define JANITOR (1<<4) -#define CURATOR (1<<5) -#define QUARTERMASTER (1<<6) -#define CARGOTECH (1<<7) -#define MINER (1<<8) -#define LAWYER (1<<9) -#define CHAPLAIN (1<<10) -#define CLOWN (1<<11) -#define MIME (1<<12) -#define ASSISTANT (1<<13) -#define PRISONER (1<<14) -#define PSYCHOLOGIST (1<<15) - -#define JOB_AVAILABLE 0 -#define JOB_UNAVAILABLE_GENERIC 1 -#define JOB_UNAVAILABLE_BANNED 2 -#define JOB_UNAVAILABLE_PLAYTIME 3 -#define JOB_UNAVAILABLE_ACCOUNTAGE 4 -#define JOB_UNAVAILABLE_SLOTFULL 5 - -#define DEFAULT_RELIGION "Christianity" -#define DEFAULT_DEITY "Space Jesus" - -#define JOB_DISPLAY_ORDER_DEFAULT 0 - -#define JOB_DISPLAY_ORDER_ASSISTANT 1 -#define JOB_DISPLAY_ORDER_CAPTAIN 2 -#define JOB_DISPLAY_ORDER_HEAD_OF_PERSONNEL 3 -#define JOB_DISPLAY_ORDER_QUARTERMASTER 4 -#define JOB_DISPLAY_ORDER_CARGO_TECHNICIAN 5 -#define JOB_DISPLAY_ORDER_SHAFT_MINER 6 -#define JOB_DISPLAY_ORDER_BARTENDER 7 -#define JOB_DISPLAY_ORDER_COOK 8 -#define JOB_DISPLAY_ORDER_BOTANIST 9 -#define JOB_DISPLAY_ORDER_JANITOR 10 -#define JOB_DISPLAY_ORDER_CLOWN 11 -#define JOB_DISPLAY_ORDER_MIME 12 -#define JOB_DISPLAY_ORDER_CURATOR 13 -#define JOB_DISPLAY_ORDER_LAWYER 14 -#define JOB_DISPLAY_ORDER_CHAPLAIN 15 -#define JOB_DISPLAY_ORDER_AI 16 -#define JOB_DISPLAY_ORDER_CYBORG 17 -#define JOB_DISPLAY_ORDER_CHIEF_ENGINEER 18 -#define JOB_DISPLAY_ORDER_STATION_ENGINEER 19 -#define JOB_DISPLAY_ORDER_ATMOSPHERIC_TECHNICIAN 20 -#define JOB_DISPLAY_ORDER_CHIEF_MEDICAL_OFFICER 21 -#define JOB_DISPLAY_ORDER_MEDICAL_DOCTOR 22 -#define JOB_DISPLAY_ORDER_PARAMEDIC 23 -#define JOB_DISPLAY_ORDER_CHEMIST 24 -#define JOB_DISPLAY_ORDER_VIROLOGIST 25 -#define JOB_DISPLAY_ORDER_PSYCHOLOGIST 26 -#define JOB_DISPLAY_ORDER_RESEARCH_DIRECTOR 27 -#define JOB_DISPLAY_ORDER_SCIENTIST 28 -#define JOB_DISPLAY_ORDER_ROBOTICIST 29 -#define JOB_DISPLAY_ORDER_GENETICIST 30 -#define JOB_DISPLAY_ORDER_HEAD_OF_SECURITY 31 -#define JOB_DISPLAY_ORDER_WARDEN 32 -#define JOB_DISPLAY_ORDER_DETECTIVE 33 -#define JOB_DISPLAY_ORDER_SECURITY_OFFICER 34 -#define JOB_DISPLAY_ORDER_PRISONER 35 +#define JOB_AVAILABLE 0 +#define JOB_UNAVAILABLE_GENERIC 1 +#define JOB_UNAVAILABLE_BANNED 2 +#define JOB_UNAVAILABLE_PLAYTIME 3 +#define JOB_UNAVAILABLE_ACCOUNTAGE 4 +#define JOB_UNAVAILABLE_SLOTFULL 5 + +#define DEFAULT_RELIGION "Christianity" +#define DEFAULT_DEITY "Space Jesus" + +#define JOB_DISPLAY_ORDER_DEFAULT 0 + +#define JOB_DISPLAY_ORDER_ASSISTANT 1 +#define JOB_DISPLAY_ORDER_CAPTAIN 2 +#define JOB_DISPLAY_ORDER_HEAD_OF_PERSONNEL 3 +#define JOB_DISPLAY_ORDER_QUARTERMASTER 4 +#define JOB_DISPLAY_ORDER_CARGO_TECHNICIAN 5 +#define JOB_DISPLAY_ORDER_SHAFT_MINER 6 +#define JOB_DISPLAY_ORDER_BARTENDER 7 +#define JOB_DISPLAY_ORDER_COOK 8 +#define JOB_DISPLAY_ORDER_BOTANIST 9 +#define JOB_DISPLAY_ORDER_JANITOR 10 +#define JOB_DISPLAY_ORDER_CLOWN 11 +#define JOB_DISPLAY_ORDER_MIME 12 +#define JOB_DISPLAY_ORDER_CURATOR 13 +#define JOB_DISPLAY_ORDER_LAWYER 14 +#define JOB_DISPLAY_ORDER_CHAPLAIN 15 +#define JOB_DISPLAY_ORDER_AI 16 +#define JOB_DISPLAY_ORDER_CYBORG 17 +#define JOB_DISPLAY_ORDER_CHIEF_ENGINEER 18 +#define JOB_DISPLAY_ORDER_STATION_ENGINEER 19 +#define JOB_DISPLAY_ORDER_ATMOSPHERIC_TECHNICIAN 20 +#define JOB_DISPLAY_ORDER_CHIEF_MEDICAL_OFFICER 21 +#define JOB_DISPLAY_ORDER_MEDICAL_DOCTOR 22 +#define JOB_DISPLAY_ORDER_PARAMEDIC 23 +#define JOB_DISPLAY_ORDER_CHEMIST 24 +#define JOB_DISPLAY_ORDER_VIROLOGIST 25 +#define JOB_DISPLAY_ORDER_PSYCHOLOGIST 26 +#define JOB_DISPLAY_ORDER_RESEARCH_DIRECTOR 27 +#define JOB_DISPLAY_ORDER_SCIENTIST 28 +#define JOB_DISPLAY_ORDER_ROBOTICIST 29 +#define JOB_DISPLAY_ORDER_GENETICIST 30 +#define JOB_DISPLAY_ORDER_HEAD_OF_SECURITY 31 +#define JOB_DISPLAY_ORDER_WARDEN 32 +#define JOB_DISPLAY_ORDER_DETECTIVE 33 +#define JOB_DISPLAY_ORDER_SECURITY_OFFICER 34 +#define JOB_DISPLAY_ORDER_PRISONER 35 diff --git a/code/__DEFINES/machines.dm b/code/__DEFINES/machines.dm index 14f7ba9a08bd..1c331af1ef5c 100644 --- a/code/__DEFINES/machines.dm +++ b/code/__DEFINES/machines.dm @@ -1,128 +1,128 @@ -// channel numbers for power -// These are indexes in a list, and indexes for "dynamic" and static channels should be kept contiguous -#define AREA_USAGE_EQUIP 1 -#define AREA_USAGE_LIGHT 2 -#define AREA_USAGE_ENVIRON 3 -#define AREA_USAGE_STATIC_EQUIP 4 -#define AREA_USAGE_STATIC_LIGHT 5 -#define AREA_USAGE_STATIC_ENVIRON 6 -#define AREA_USAGE_LEN AREA_USAGE_STATIC_ENVIRON // largest idx -/// Index of the first dynamic usage channel -#define AREA_USAGE_DYNAMIC_START AREA_USAGE_EQUIP -/// Index of the last dynamic usage channel -#define AREA_USAGE_DYNAMIC_END AREA_USAGE_ENVIRON -/// Index of the first static usage channel -#define AREA_USAGE_STATIC_START AREA_USAGE_STATIC_EQUIP -/// Index of the last static usage channel -#define AREA_USAGE_STATIC_END AREA_USAGE_STATIC_ENVIRON - - -//Power use -#define NO_POWER_USE 0 -#define IDLE_POWER_USE 1 -#define ACTIVE_POWER_USE 2 - -/// Bitflags for a machine's preferences on when it should start processing. For use with machinery's `processing_flags` var. -#define START_PROCESSING_ON_INIT (1<<0) /// Indicates the machine will automatically start processing right after it's `Initialize()` is ran. -#define START_PROCESSING_MANUALLY (1<<1) /// Machines with this flag will not start processing when it's spawned. Use this if you want to manually control when a machine starts processing. - -//bitflags for door switches. -#define OPEN (1<<0) -#define IDSCAN (1<<1) -#define BOLTS (1<<2) -#define SHOCK (1<<3) -#define SAFE (1<<4) - -//used in design to specify which machine can build it -#define IMPRINTER (1<<0) //For circuits. Uses glass/chemicals. -#define PROTOLATHE (1<<1) //New stuff. Uses glass/metal/chemicals -#define AUTOLATHE (1<<2) //Uses glass/metal only. -#define CRAFTLATHE (1<<3) //Uses fuck if I know. For use eventually. -#define MECHFAB (1<<4) //Remember, objects utilising this flag should have construction_time and construction_cost vars. -#define BIOGENERATOR (1<<5) //Uses biomass -#define LIMBGROWER (1<<6) //Uses synthetic flesh -#define SMELTER (1<<7) //uses various minerals -#define NANITE_COMPILER (1<<8) //Prints nanite disks -//Note: More than one of these can be added to a design but imprinter and lathe designs are incompatable. - -//Modular computer/NTNet defines - -//Modular computer part defines -#define MC_CPU "CPU" -#define MC_HDD "HDD" -#define MC_SDD "SDD" -#define MC_CARD "CARD" -#define MC_NET "NET" -#define MC_PRINT "PRINT" -#define MC_CELL "CELL" -#define MC_CHARGE "CHARGE" -#define MC_AI "AI" - -//NTNet stuff, for modular computers - // NTNet module-configuration values. Do not change these. If you need to add another use larger number (5..6..7 etc) -#define NTNET_SOFTWAREDOWNLOAD 1 // Downloads of software from NTNet -#define NTNET_PEERTOPEER 2 // P2P transfers of files between devices -#define NTNET_COMMUNICATION 3 // Communication (messaging) -#define NTNET_SYSTEMCONTROL 4 // Control of various systems, RCon, air alarm control, etc. - -//NTNet transfer speeds, used when downloading/uploading a file/program. -#define NTNETSPEED_LOWSIGNAL 0.5 // GQ/s transfer speed when the device is wirelessly connected and on Low signal -#define NTNETSPEED_HIGHSIGNAL 1 // GQ/s transfer speed when the device is wirelessly connected and on High signal -#define NTNETSPEED_ETHERNET 2 // GQ/s transfer speed when the device is using wired connection - -//Caps for NTNet logging. Less than 10 would make logging useless anyway, more than 500 may make the log browser too laggy. Defaults to 100 unless user changes it. -#define MAX_NTNET_LOGS 300 -#define MIN_NTNET_LOGS 10 - -//Program bitflags -#define PROGRAM_ALL (~0) -#define PROGRAM_CONSOLE (1<<0) -#define PROGRAM_LAPTOP (1<<1) -#define PROGRAM_TABLET (1<<2) -//Program states -#define PROGRAM_STATE_KILLED 0 -#define PROGRAM_STATE_BACKGROUND 1 -#define PROGRAM_STATE_ACTIVE 2 - -#define FIREDOOR_OPEN 1 -#define FIREDOOR_CLOSED 2 - - - -// These are used by supermatter and supermatter monitor program, mostly for UI updating purposes. Higher should always be worse! -#define SUPERMATTER_ERROR -1 // Unknown status, shouldn't happen but just in case. -#define SUPERMATTER_INACTIVE 0 // No or minimal energy -#define SUPERMATTER_NORMAL 1 // Normal operation -#define SUPERMATTER_NOTIFY 2 // Ambient temp > 80% of CRITICAL_TEMPERATURE -#define SUPERMATTER_WARNING 3 // Ambient temp > CRITICAL_TEMPERATURE OR integrity damaged -#define SUPERMATTER_DANGER 4 // Integrity < 50% -#define SUPERMATTER_EMERGENCY 5 // Integrity < 25% -#define SUPERMATTER_DELAMINATING 6 // Pretty obvious. - -//Nuclear bomb stuff -#define NUKESTATE_INTACT 5 -#define NUKESTATE_UNSCREWED 4 -#define NUKESTATE_PANEL_REMOVED 3 -#define NUKESTATE_WELDED 2 -#define NUKESTATE_CORE_EXPOSED 1 -#define NUKESTATE_CORE_REMOVED 0 - -#define NUKEUI_AWAIT_DISK 0 -#define NUKEUI_AWAIT_CODE 1 -#define NUKEUI_AWAIT_TIMER 2 -#define NUKEUI_AWAIT_ARM 3 -#define NUKEUI_TIMING 4 -#define NUKEUI_EXPLODED 5 - -#define NUKE_OFF_LOCKED 0 -#define NUKE_OFF_UNLOCKED 1 -#define NUKE_ON_TIMING 2 -#define NUKE_ON_EXPLODING 3 - -#define MACHINE_NOT_ELECTRIFIED 0 -#define MACHINE_ELECTRIFIED_PERMANENT -1 -#define MACHINE_DEFAULT_ELECTRIFY_TIME 30 - -//these flags are used to tell the DNA modifier if a plant gene cannot be extracted or modified. -#define PLANT_GENE_REMOVABLE (1<<0) -#define PLANT_GENE_EXTRACTABLE (1<<1) +// channel numbers for power +// These are indexes in a list, and indexes for "dynamic" and static channels should be kept contiguous +#define AREA_USAGE_EQUIP 1 +#define AREA_USAGE_LIGHT 2 +#define AREA_USAGE_ENVIRON 3 +#define AREA_USAGE_STATIC_EQUIP 4 +#define AREA_USAGE_STATIC_LIGHT 5 +#define AREA_USAGE_STATIC_ENVIRON 6 +#define AREA_USAGE_LEN AREA_USAGE_STATIC_ENVIRON // largest idx +/// Index of the first dynamic usage channel +#define AREA_USAGE_DYNAMIC_START AREA_USAGE_EQUIP +/// Index of the last dynamic usage channel +#define AREA_USAGE_DYNAMIC_END AREA_USAGE_ENVIRON +/// Index of the first static usage channel +#define AREA_USAGE_STATIC_START AREA_USAGE_STATIC_EQUIP +/// Index of the last static usage channel +#define AREA_USAGE_STATIC_END AREA_USAGE_STATIC_ENVIRON + + +//Power use +#define NO_POWER_USE 0 +#define IDLE_POWER_USE 1 +#define ACTIVE_POWER_USE 2 + +/// Bitflags for a machine's preferences on when it should start processing. For use with machinery's `processing_flags` var. +#define START_PROCESSING_ON_INIT (1<<0) /// Indicates the machine will automatically start processing right after it's `Initialize()` is ran. +#define START_PROCESSING_MANUALLY (1<<1) /// Machines with this flag will not start processing when it's spawned. Use this if you want to manually control when a machine starts processing. + +//bitflags for door switches. +#define OPEN (1<<0) +#define IDSCAN (1<<1) +#define BOLTS (1<<2) +#define SHOCK (1<<3) +#define SAFE (1<<4) + +//used in design to specify which machine can build it +#define IMPRINTER (1<<0) //For circuits. Uses glass/chemicals. +#define PROTOLATHE (1<<1) //New stuff. Uses glass/metal/chemicals +#define AUTOLATHE (1<<2) //Uses glass/metal only. +#define CRAFTLATHE (1<<3) //Uses fuck if I know. For use eventually. +#define MECHFAB (1<<4) //Remember, objects utilising this flag should have construction_time and construction_cost vars. +#define BIOGENERATOR (1<<5) //Uses biomass +#define LIMBGROWER (1<<6) //Uses synthetic flesh +#define SMELTER (1<<7) //uses various minerals +#define NANITE_COMPILER (1<<8) //Prints nanite disks +//Note: More than one of these can be added to a design but imprinter and lathe designs are incompatable. + +//Modular computer/NTNet defines + +//Modular computer part defines +#define MC_CPU "CPU" +#define MC_HDD "HDD" +#define MC_SDD "SDD" +#define MC_CARD "CARD" +#define MC_NET "NET" +#define MC_PRINT "PRINT" +#define MC_CELL "CELL" +#define MC_CHARGE "CHARGE" +#define MC_AI "AI" + +//NTNet stuff, for modular computers + // NTNet module-configuration values. Do not change these. If you need to add another use larger number (5..6..7 etc) +#define NTNET_SOFTWAREDOWNLOAD 1 // Downloads of software from NTNet +#define NTNET_PEERTOPEER 2 // P2P transfers of files between devices +#define NTNET_COMMUNICATION 3 // Communication (messaging) +#define NTNET_SYSTEMCONTROL 4 // Control of various systems, RCon, air alarm control, etc. + +//NTNet transfer speeds, used when downloading/uploading a file/program. +#define NTNETSPEED_LOWSIGNAL 0.5 // GQ/s transfer speed when the device is wirelessly connected and on Low signal +#define NTNETSPEED_HIGHSIGNAL 1 // GQ/s transfer speed when the device is wirelessly connected and on High signal +#define NTNETSPEED_ETHERNET 2 // GQ/s transfer speed when the device is using wired connection + +//Caps for NTNet logging. Less than 10 would make logging useless anyway, more than 500 may make the log browser too laggy. Defaults to 100 unless user changes it. +#define MAX_NTNET_LOGS 300 +#define MIN_NTNET_LOGS 10 + +//Program bitflags +#define PROGRAM_ALL (~0) +#define PROGRAM_CONSOLE (1<<0) +#define PROGRAM_LAPTOP (1<<1) +#define PROGRAM_TABLET (1<<2) +//Program states +#define PROGRAM_STATE_KILLED 0 +#define PROGRAM_STATE_BACKGROUND 1 +#define PROGRAM_STATE_ACTIVE 2 + +#define FIREDOOR_OPEN 1 +#define FIREDOOR_CLOSED 2 + + + +// These are used by supermatter and supermatter monitor program, mostly for UI updating purposes. Higher should always be worse! +#define SUPERMATTER_ERROR -1 // Unknown status, shouldn't happen but just in case. +#define SUPERMATTER_INACTIVE 0 // No or minimal energy +#define SUPERMATTER_NORMAL 1 // Normal operation +#define SUPERMATTER_NOTIFY 2 // Ambient temp > 80% of CRITICAL_TEMPERATURE +#define SUPERMATTER_WARNING 3 // Ambient temp > CRITICAL_TEMPERATURE OR integrity damaged +#define SUPERMATTER_DANGER 4 // Integrity < 50% +#define SUPERMATTER_EMERGENCY 5 // Integrity < 25% +#define SUPERMATTER_DELAMINATING 6 // Pretty obvious. + +//Nuclear bomb stuff +#define NUKESTATE_INTACT 5 +#define NUKESTATE_UNSCREWED 4 +#define NUKESTATE_PANEL_REMOVED 3 +#define NUKESTATE_WELDED 2 +#define NUKESTATE_CORE_EXPOSED 1 +#define NUKESTATE_CORE_REMOVED 0 + +#define NUKEUI_AWAIT_DISK 0 +#define NUKEUI_AWAIT_CODE 1 +#define NUKEUI_AWAIT_TIMER 2 +#define NUKEUI_AWAIT_ARM 3 +#define NUKEUI_TIMING 4 +#define NUKEUI_EXPLODED 5 + +#define NUKE_OFF_LOCKED 0 +#define NUKE_OFF_UNLOCKED 1 +#define NUKE_ON_TIMING 2 +#define NUKE_ON_EXPLODING 3 + +#define MACHINE_NOT_ELECTRIFIED 0 +#define MACHINE_ELECTRIFIED_PERMANENT -1 +#define MACHINE_DEFAULT_ELECTRIFY_TIME 30 + +//these flags are used to tell the DNA modifier if a plant gene cannot be extracted or modified. +#define PLANT_GENE_REMOVABLE (1<<0) +#define PLANT_GENE_EXTRACTABLE (1<<1) diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index 02d6756a804d..bc3deba4afca 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -233,6 +233,9 @@ #define AI_OFF 3 #define AI_Z_OFF 4 +//The range at which a mob should wake up if you spawn into the z level near it +#define MAX_SIMPLEMOB_WAKEUP_RANGE 5 + //determines if a mob can smash through it #define ENVIRONMENT_SMASH_NONE 0 #define ENVIRONMENT_SMASH_STRUCTURES (1<<0) //crates, lockers, ect diff --git a/code/__DEFINES/move_force.dm b/code/__DEFINES/move_force.dm index 5ab615643af3..1f8819b0c857 100644 --- a/code/__DEFINES/move_force.dm +++ b/code/__DEFINES/move_force.dm @@ -1,20 +1,20 @@ -//Defaults -#define MOVE_FORCE_DEFAULT 1000 -#define MOVE_RESIST_DEFAULT 1000 -#define PULL_FORCE_DEFAULT 1000 - -//Factors/modifiers -#define MOVE_FORCE_PULL_RATIO 1 //Same move force to pull objects -#define MOVE_FORCE_PUSH_RATIO 1 //Same move force to normally push -#define MOVE_FORCE_FORCEPUSH_RATIO 2 //2x move force to forcefully push -#define MOVE_FORCE_CRUSH_RATIO 3 //3x move force to do things like crush objects -#define MOVE_FORCE_THROW_RATIO 1 //Same force throw as resist to throw objects - -#define MOVE_FORCE_OVERPOWERING (MOVE_FORCE_DEFAULT * MOVE_FORCE_CRUSH_RATIO * 10) -#define MOVE_FORCE_EXTREMELY_STRONG (MOVE_FORCE_DEFAULT * MOVE_FORCE_CRUSH_RATIO * 3) -#define MOVE_FORCE_VERY_STRONG ((MOVE_FORCE_DEFAULT * MOVE_FORCE_CRUSH_RATIO) - 1) -#define MOVE_FORCE_STRONG (MOVE_FORCE_DEFAULT * 2) -#define MOVE_FORCE_NORMAL MOVE_FORCE_DEFAULT -#define MOVE_FORCE_WEAK (MOVE_FORCE_DEFAULT / 2) -#define MOVE_FORCE_VERY_WEAK ((MOVE_FORCE_DEFAULT / MOVE_FORCE_CRUSH_RATIO) + 1) -#define MOVE_FORCE_EXTREMELY_WEAK (MOVE_FORCE_DEFAULT / (MOVE_FORCE_CRUSH_RATIO * 3)) +//Defaults +#define MOVE_FORCE_DEFAULT 1000 +#define MOVE_RESIST_DEFAULT 1000 +#define PULL_FORCE_DEFAULT 1000 + +//Factors/modifiers +#define MOVE_FORCE_PULL_RATIO 1 //Same move force to pull objects +#define MOVE_FORCE_PUSH_RATIO 1 //Same move force to normally push +#define MOVE_FORCE_FORCEPUSH_RATIO 2 //2x move force to forcefully push +#define MOVE_FORCE_CRUSH_RATIO 3 //3x move force to do things like crush objects +#define MOVE_FORCE_THROW_RATIO 1 //Same force throw as resist to throw objects + +#define MOVE_FORCE_OVERPOWERING (MOVE_FORCE_DEFAULT * MOVE_FORCE_CRUSH_RATIO * 10) +#define MOVE_FORCE_EXTREMELY_STRONG (MOVE_FORCE_DEFAULT * MOVE_FORCE_CRUSH_RATIO * 3) +#define MOVE_FORCE_VERY_STRONG ((MOVE_FORCE_DEFAULT * MOVE_FORCE_CRUSH_RATIO) - 1) +#define MOVE_FORCE_STRONG (MOVE_FORCE_DEFAULT * 2) +#define MOVE_FORCE_NORMAL MOVE_FORCE_DEFAULT +#define MOVE_FORCE_WEAK (MOVE_FORCE_DEFAULT / 2) +#define MOVE_FORCE_VERY_WEAK ((MOVE_FORCE_DEFAULT / MOVE_FORCE_CRUSH_RATIO) + 1) +#define MOVE_FORCE_EXTREMELY_WEAK (MOVE_FORCE_DEFAULT / (MOVE_FORCE_CRUSH_RATIO * 3)) diff --git a/code/__DEFINES/networks.dm b/code/__DEFINES/networks.dm index 09b9e4cadfc4..115c1653493c 100644 --- a/code/__DEFINES/networks.dm +++ b/code/__DEFINES/networks.dm @@ -1,3 +1,3 @@ -#define HID_RESTRICTED_END 101 //the first nonrestricted ID, automatically assigned on connection creation. - -#define NETWORK_BROADCAST_ID "ALL" +#define HID_RESTRICTED_END 101 //the first nonrestricted ID, automatically assigned on connection creation. + +#define NETWORK_BROADCAST_ID "ALL" diff --git a/code/__DEFINES/preferences.dm b/code/__DEFINES/preferences.dm index 764278aaf6ce..78ad2b922d22 100644 --- a/code/__DEFINES/preferences.dm +++ b/code/__DEFINES/preferences.dm @@ -1,112 +1,112 @@ - -//Preference toggles -#define SOUND_ADMINHELP (1<<0) -#define SOUND_MIDI (1<<1) -#define SOUND_AMBIENCE (1<<2) -#define SOUND_LOBBY (1<<3) -#define MEMBER_PUBLIC (1<<4) -#define INTENT_STYLE (1<<5) -#define MIDROUND_ANTAG (1<<6) -#define SOUND_INSTRUMENTS (1<<7) -#define SOUND_SHIP_AMBIENCE (1<<8) -#define SOUND_PRAYERS (1<<9) -#define ANNOUNCE_LOGIN (1<<10) -#define SOUND_ANNOUNCEMENTS (1<<11) -#define DISABLE_DEATHRATTLE (1<<12) -#define DISABLE_ARRIVALRATTLE (1<<13) -#define COMBOHUD_LIGHTING (1<<14) -#define DEADMIN_ALWAYS (1<<15) -#define DEADMIN_ANTAGONIST (1<<16) -#define DEADMIN_POSITION_HEAD (1<<17) -#define DEADMIN_POSITION_SECURITY (1<<18) -#define DEADMIN_POSITION_SILICON (1<<19) -#define SOUND_ENDOFROUND (1<<20) -#define ADMIN_IGNORE_CULT_GHOST (1<<21) - -#define TOGGLES_DEFAULT (SOUND_ADMINHELP|SOUND_MIDI|SOUND_AMBIENCE|SOUND_LOBBY|SOUND_ENDOFROUND|MEMBER_PUBLIC|INTENT_STYLE|MIDROUND_ANTAG|SOUND_INSTRUMENTS|SOUND_SHIP_AMBIENCE|SOUND_PRAYERS|SOUND_ANNOUNCEMENTS) - -//Chat toggles -#define CHAT_OOC (1<<0) -#define CHAT_DEAD (1<<1) -#define CHAT_GHOSTEARS (1<<2) -#define CHAT_GHOSTSIGHT (1<<3) -#define CHAT_PRAYER (1<<4) -#define CHAT_RADIO (1<<5) -#define CHAT_PULLR (1<<6) -#define CHAT_GHOSTWHISPER (1<<7) -#define CHAT_GHOSTPDA (1<<8) -#define CHAT_GHOSTRADIO (1<<9) -#define CHAT_BANKCARD (1<<10) -#define CHAT_GHOSTLAWS (1<<11) -#define CHAT_LOOC (1<<12) //Wasp Edit - -#define TOGGLES_DEFAULT_CHAT (CHAT_OOC|CHAT_DEAD|CHAT_GHOSTEARS|CHAT_GHOSTSIGHT|CHAT_PRAYER|CHAT_RADIO|CHAT_PULLR|CHAT_GHOSTWHISPER|CHAT_GHOSTPDA|CHAT_GHOSTRADIO|CHAT_BANKCARD|CHAT_GHOSTLAWS|CHAT_LOOC) -//Wasp Edit - -#define PARALLAX_INSANE -1 //for show offs -#define PARALLAX_HIGH 0 //default. -#define PARALLAX_MED 1 -#define PARALLAX_LOW 2 -#define PARALLAX_DISABLE 3 //this option must be the highest number - -#define PARALLAX_DELAY_DEFAULT world.tick_lag -#define PARALLAX_DELAY_MED 1 -#define PARALLAX_DELAY_LOW 2 - -#define SEC_DEPT_NONE "None" -#define SEC_DEPT_RANDOM "Random" -#define SEC_DEPT_ENGINEERING "Engineering" -#define SEC_DEPT_MEDICAL "Medical" -#define SEC_DEPT_SCIENCE "Science" -#define SEC_DEPT_SUPPLY "Supply" - -// Playtime tracking system, see jobs_exp.dm -#define EXP_TYPE_LIVING "Living" -#define EXP_TYPE_CREW "Crew" -#define EXP_TYPE_COMMAND "Command" -#define EXP_TYPE_ENGINEERING "Engineering" -#define EXP_TYPE_MEDICAL "Medical" -#define EXP_TYPE_SCIENCE "Science" -#define EXP_TYPE_SUPPLY "Supply" -#define EXP_TYPE_SECURITY "Security" -#define EXP_TYPE_SILICON "Silicon" -#define EXP_TYPE_SERVICE "Service" -#define EXP_TYPE_ANTAG "Antag" -#define EXP_TYPE_SPECIAL "Special" -#define EXP_TYPE_GHOST "Ghost" -#define EXP_TYPE_ADMIN "Admin" - -//Flags in the players table in the db -#define DB_FLAG_EXEMPT 1 - -#define DEFAULT_CYBORG_NAME "Default Cyborg Name" - - -//Job preferences levels -#define JP_LOW 1 -#define JP_MEDIUM 2 -#define JP_HIGH 3 - -//randomised elements -#define RANDOM_NAME "random_name" -#define RANDOM_NAME_ANTAG "random_name_antag" -#define RANDOM_BODY "random_body" -#define RANDOM_BODY_ANTAG "random_body_antag" -#define RANDOM_SPECIES "random_species" -#define RANDOM_GENDER "random_gender" -#define RANDOM_GENDER_ANTAG "random_gender_antag" -#define RANDOM_AGE "random_age" -#define RANDOM_AGE_ANTAG "random_age_antag" -#define RANDOM_UNDERWEAR "random_underwear" -#define RANDOM_UNDERWEAR_COLOR "random_underwear_color" -#define RANDOM_UNDERSHIRT "random_undershirt" -#define RANDOM_SOCKS "random_socks" -#define RANDOM_BACKPACK "random_backpack" -#define RANDOM_JUMPSUIT_STYLE "random_jumpsuit_style" -#define RANDOM_EXOWEAR_STYLE "random_jumpsuit_style" -#define RANDOM_HAIRSTYLE "random_hairstyle" -#define RANDOM_HAIR_COLOR "random_hair_color" -#define RANDOM_FACIAL_HAIR_COLOR "random_facial_hair_color" -#define RANDOM_FACIAL_HAIRSTYLE "random_facial_hairstyle" -#define RANDOM_SKIN_TONE "random_skin_tone" -#define RANDOM_EYE_COLOR "random_eye_color" + +//Preference toggles +#define SOUND_ADMINHELP (1<<0) +#define SOUND_MIDI (1<<1) +#define SOUND_AMBIENCE (1<<2) +#define SOUND_LOBBY (1<<3) +#define MEMBER_PUBLIC (1<<4) +#define INTENT_STYLE (1<<5) +#define MIDROUND_ANTAG (1<<6) +#define SOUND_INSTRUMENTS (1<<7) +#define SOUND_SHIP_AMBIENCE (1<<8) +#define SOUND_PRAYERS (1<<9) +#define ANNOUNCE_LOGIN (1<<10) +#define SOUND_ANNOUNCEMENTS (1<<11) +#define DISABLE_DEATHRATTLE (1<<12) +#define DISABLE_ARRIVALRATTLE (1<<13) +#define COMBOHUD_LIGHTING (1<<14) +#define DEADMIN_ALWAYS (1<<15) +#define DEADMIN_ANTAGONIST (1<<16) +#define DEADMIN_POSITION_HEAD (1<<17) +#define DEADMIN_POSITION_SECURITY (1<<18) +#define DEADMIN_POSITION_SILICON (1<<19) +#define SOUND_ENDOFROUND (1<<20) +#define ADMIN_IGNORE_CULT_GHOST (1<<21) + +#define TOGGLES_DEFAULT (SOUND_ADMINHELP|SOUND_MIDI|SOUND_AMBIENCE|SOUND_LOBBY|SOUND_ENDOFROUND|MEMBER_PUBLIC|INTENT_STYLE|MIDROUND_ANTAG|SOUND_INSTRUMENTS|SOUND_SHIP_AMBIENCE|SOUND_PRAYERS|SOUND_ANNOUNCEMENTS) + +//Chat toggles +#define CHAT_OOC (1<<0) +#define CHAT_DEAD (1<<1) +#define CHAT_GHOSTEARS (1<<2) +#define CHAT_GHOSTSIGHT (1<<3) +#define CHAT_PRAYER (1<<4) +#define CHAT_RADIO (1<<5) +#define CHAT_PULLR (1<<6) +#define CHAT_GHOSTWHISPER (1<<7) +#define CHAT_GHOSTPDA (1<<8) +#define CHAT_GHOSTRADIO (1<<9) +#define CHAT_BANKCARD (1<<10) +#define CHAT_GHOSTLAWS (1<<11) +#define CHAT_LOOC (1<<12) //Wasp Edit + +#define TOGGLES_DEFAULT_CHAT (CHAT_OOC|CHAT_DEAD|CHAT_GHOSTEARS|CHAT_GHOSTSIGHT|CHAT_PRAYER|CHAT_RADIO|CHAT_PULLR|CHAT_GHOSTWHISPER|CHAT_GHOSTPDA|CHAT_GHOSTRADIO|CHAT_BANKCARD|CHAT_GHOSTLAWS|CHAT_LOOC) +//Wasp Edit + +#define PARALLAX_INSANE -1 //for show offs +#define PARALLAX_HIGH 0 //default. +#define PARALLAX_MED 1 +#define PARALLAX_LOW 2 +#define PARALLAX_DISABLE 3 //this option must be the highest number + +#define PARALLAX_DELAY_DEFAULT world.tick_lag +#define PARALLAX_DELAY_MED 1 +#define PARALLAX_DELAY_LOW 2 + +#define SEC_DEPT_NONE "None" +#define SEC_DEPT_RANDOM "Random" +#define SEC_DEPT_ENGINEERING "Engineering" +#define SEC_DEPT_MEDICAL "Medical" +#define SEC_DEPT_SCIENCE "Science" +#define SEC_DEPT_SUPPLY "Supply" + +// Playtime tracking system, see jobs_exp.dm +#define EXP_TYPE_LIVING "Living" +#define EXP_TYPE_CREW "Crew" +#define EXP_TYPE_COMMAND "Command" +#define EXP_TYPE_ENGINEERING "Engineering" +#define EXP_TYPE_MEDICAL "Medical" +#define EXP_TYPE_SCIENCE "Science" +#define EXP_TYPE_SUPPLY "Supply" +#define EXP_TYPE_SECURITY "Security" +#define EXP_TYPE_SILICON "Silicon" +#define EXP_TYPE_SERVICE "Service" +#define EXP_TYPE_ANTAG "Antag" +#define EXP_TYPE_SPECIAL "Special" +#define EXP_TYPE_GHOST "Ghost" +#define EXP_TYPE_ADMIN "Admin" + +//Flags in the players table in the db +#define DB_FLAG_EXEMPT 1 + +#define DEFAULT_CYBORG_NAME "Default Cyborg Name" + + +//Job preferences levels +#define JP_LOW 1 +#define JP_MEDIUM 2 +#define JP_HIGH 3 + +//randomised elements +#define RANDOM_NAME "random_name" +#define RANDOM_NAME_ANTAG "random_name_antag" +#define RANDOM_BODY "random_body" +#define RANDOM_BODY_ANTAG "random_body_antag" +#define RANDOM_SPECIES "random_species" +#define RANDOM_GENDER "random_gender" +#define RANDOM_GENDER_ANTAG "random_gender_antag" +#define RANDOM_AGE "random_age" +#define RANDOM_AGE_ANTAG "random_age_antag" +#define RANDOM_UNDERWEAR "random_underwear" +#define RANDOM_UNDERWEAR_COLOR "random_underwear_color" +#define RANDOM_UNDERSHIRT "random_undershirt" +#define RANDOM_SOCKS "random_socks" +#define RANDOM_BACKPACK "random_backpack" +#define RANDOM_JUMPSUIT_STYLE "random_jumpsuit_style" +#define RANDOM_EXOWEAR_STYLE "random_jumpsuit_style" +#define RANDOM_HAIRSTYLE "random_hairstyle" +#define RANDOM_HAIR_COLOR "random_hair_color" +#define RANDOM_FACIAL_HAIR_COLOR "random_facial_hair_color" +#define RANDOM_FACIAL_HAIRSTYLE "random_facial_hairstyle" +#define RANDOM_SKIN_TONE "random_skin_tone" +#define RANDOM_EYE_COLOR "random_eye_color" diff --git a/code/__DEFINES/qdel.dm b/code/__DEFINES/qdel.dm index 4296e3c2e98d..3c9d40c1dfaa 100644 --- a/code/__DEFINES/qdel.dm +++ b/code/__DEFINES/qdel.dm @@ -6,11 +6,18 @@ #define QDEL_HINT_IWILLGC 2 //functionally the same as the above. qdel should assume the object will gc on its own, and not check it. #define QDEL_HINT_HARDDEL 3 //qdel should assume this object won't gc, and queue a hard delete using a hard reference. #define QDEL_HINT_HARDDEL_NOW 4 //qdel should assume this object won't gc, and hard del it post haste. -#define QDEL_HINT_FINDREFERENCE 5 //functionally identical to QDEL_HINT_QUEUE if TESTING is not enabled in _compiler_options.dm. - //if TESTING is enabled, qdel will call this object's find_references() verb. -#define QDEL_HINT_IFFAIL_FINDREFERENCE 6 //Above but only if gc fails. //defines for the gc_destroyed var +#ifdef LEGACY_REFERENCE_TRACKING +/** If LEGACY_REFERENCE_TRACKING is enabled, qdel will call this object's find_references() verb. + * + * Functionally identical to QDEL_HINT_QUEUE if GC_FAILURE_HARD_LOOKUP is not enabled in _compiler_options.dm. +*/ +#define QDEL_HINT_FINDREFERENCE 5 +/// Behavior as QDEL_HINT_FINDREFERENCE, but only if the GC fails and a hard delete is forced. +#define QDEL_HINT_IFFAIL_FINDREFERENCE 6 +#endif + #define GC_QUEUE_CHECK 1 #define GC_QUEUE_HARDDELETE 2 #define GC_QUEUE_COUNT 2 //increase this when adding more steps. diff --git a/code/__DEFINES/radio.dm b/code/__DEFINES/radio.dm index af6a9ac51b17..d3190b8fa20f 100644 --- a/code/__DEFINES/radio.dm +++ b/code/__DEFINES/radio.dm @@ -1,116 +1,116 @@ -// Radios use a large variety of predefined frequencies. - -//say based modes like binary are in living/say.dm - -#define RADIO_CHANNEL_COMMON "Common" -#define RADIO_KEY_COMMON ";" - -#define RADIO_CHANNEL_SECURITY "Security" -#define RADIO_KEY_SECURITY "s" -#define RADIO_TOKEN_SECURITY ":s" - -#define RADIO_CHANNEL_ENGINEERING "Engineering" -#define RADIO_KEY_ENGINEERING "e" -#define RADIO_TOKEN_ENGINEERING ":e" - -#define RADIO_CHANNEL_COMMAND "Command" -#define RADIO_KEY_COMMAND "c" -#define RADIO_TOKEN_COMMAND ":c" - -#define RADIO_CHANNEL_SCIENCE "Science" -#define RADIO_KEY_SCIENCE "n" -#define RADIO_TOKEN_SCIENCE ":n" - -#define RADIO_CHANNEL_MEDICAL "Medical" -#define RADIO_KEY_MEDICAL "m" -#define RADIO_TOKEN_MEDICAL ":m" - -#define RADIO_CHANNEL_SUPPLY "Supply" -#define RADIO_KEY_SUPPLY "u" -#define RADIO_TOKEN_SUPPLY ":u" - -#define RADIO_CHANNEL_SERVICE "Service" -#define RADIO_KEY_SERVICE "v" -#define RADIO_TOKEN_SERVICE ":v" - -#define RADIO_CHANNEL_AI_PRIVATE "AI Private" -#define RADIO_KEY_AI_PRIVATE "o" -#define RADIO_TOKEN_AI_PRIVATE ":o" - - -#define RADIO_CHANNEL_SYNDICATE "Syndicate" -#define RADIO_KEY_SYNDICATE "t" -#define RADIO_TOKEN_SYNDICATE ":t" - -#define RADIO_CHANNEL_CENTCOM "CentCom" -#define RADIO_KEY_CENTCOM "y" -#define RADIO_TOKEN_CENTCOM ":y" - -#define RADIO_CHANNEL_CTF_RED "Red Team" -#define RADIO_CHANNEL_CTF_BLUE "Blue Team" - - -#define MIN_FREE_FREQ 1201 // ------------------------------------------------- -// Frequencies are always odd numbers and range from 1201 to 1599. - -#define FREQ_SYNDICATE 1213 // Nuke op comms frequency, dark brown -#define FREQ_CTF_RED 1215 // CTF red team comms frequency, red -#define FREQ_CTF_BLUE 1217 // CTF blue team comms frequency, blue -#define FREQ_CENTCOM 1337 // CentCom comms frequency, gray -#define FREQ_SUPPLY 1347 // Supply comms frequency, light brown -#define FREQ_SERVICE 1349 // Service comms frequency, green -#define FREQ_SCIENCE 1351 // Science comms frequency, plum -#define FREQ_COMMAND 1353 // Command comms frequency, gold -#define FREQ_MEDICAL 1355 // Medical comms frequency, soft blue -#define FREQ_ENGINEERING 1357 // Engineering comms frequency, orange -#define FREQ_SECURITY 1359 // Security comms frequency, red - -#define FREQ_HOLOGRID_SOLUTION 1433 -#define FREQ_STATUS_DISPLAYS 1435 -#define FREQ_ATMOS_ALARMS 1437 // air alarms <-> alert computers -#define FREQ_ATMOS_CONTROL 1439 // air alarms <-> vents and scrubbers - -#define MIN_FREQ 1441 // ------------------------------------------------------ -// Only the 1441 to 1489 range is freely available for general conversation. -// This represents 1/8th of the available spectrum. - -#define FREQ_ATMOS_STORAGE 1441 -#define FREQ_NAV_BEACON 1445 -#define FREQ_AI_PRIVATE 1447 // AI private comms frequency, magenta -#define FREQ_PRESSURE_PLATE 1447 -#define FREQ_AIRLOCK_CONTROL 1449 -#define FREQ_ELECTROPACK 1449 -#define FREQ_MAGNETS 1449 -#define FREQ_LOCATOR_IMPLANT 1451 -#define FREQ_SIGNALER 1457 // the default for new signalers -#define FREQ_COMMON 1459 // Common comms frequency, dark green - -#define MAX_FREQ 1489 // ------------------------------------------------------ - -#define MAX_FREE_FREQ 1599 // ------------------------------------------------- - -// Transmission types. -#define TRANSMISSION_WIRE 0 // some sort of wired connection, not used -#define TRANSMISSION_RADIO 1 // electromagnetic radiation (default) -#define TRANSMISSION_SUBSPACE 2 // subspace transmission (headsets only) -#define TRANSMISSION_SUPERSPACE 3 // reaches independent (CentCom) radios only - -// Filter types, used as an optimization to avoid unnecessary proc calls. -#define RADIO_TO_AIRALARM "to_airalarm" -#define RADIO_FROM_AIRALARM "from_airalarm" -#define RADIO_SIGNALER "signaler" -#define RADIO_ATMOSIA "atmosia" -#define RADIO_AIRLOCK "airlock" -#define RADIO_MAGNETS "magnets" - -#define DEFAULT_SIGNALER_CODE 30 - -//Requests Console -#define REQ_NO_NEW_MESSAGE 0 -#define REQ_NORMAL_MESSAGE_PRIORITY 1 -#define REQ_HIGH_MESSAGE_PRIORITY 2 -#define REQ_EXTREME_MESSAGE_PRIORITY 3 - -#define REQ_DEP_TYPE_ASSISTANCE (1<<0) -#define REQ_DEP_TYPE_SUPPLIES (1<<1) -#define REQ_DEP_TYPE_INFORMATION (1<<2) +// Radios use a large variety of predefined frequencies. + +//say based modes like binary are in living/say.dm + +#define RADIO_CHANNEL_COMMON "Common" +#define RADIO_KEY_COMMON ";" + +#define RADIO_CHANNEL_SECURITY "Security" +#define RADIO_KEY_SECURITY "s" +#define RADIO_TOKEN_SECURITY ":s" + +#define RADIO_CHANNEL_ENGINEERING "Engineering" +#define RADIO_KEY_ENGINEERING "e" +#define RADIO_TOKEN_ENGINEERING ":e" + +#define RADIO_CHANNEL_COMMAND "Command" +#define RADIO_KEY_COMMAND "c" +#define RADIO_TOKEN_COMMAND ":c" + +#define RADIO_CHANNEL_SCIENCE "Science" +#define RADIO_KEY_SCIENCE "n" +#define RADIO_TOKEN_SCIENCE ":n" + +#define RADIO_CHANNEL_MEDICAL "Medical" +#define RADIO_KEY_MEDICAL "m" +#define RADIO_TOKEN_MEDICAL ":m" + +#define RADIO_CHANNEL_SUPPLY "Supply" +#define RADIO_KEY_SUPPLY "u" +#define RADIO_TOKEN_SUPPLY ":u" + +#define RADIO_CHANNEL_SERVICE "Service" +#define RADIO_KEY_SERVICE "v" +#define RADIO_TOKEN_SERVICE ":v" + +#define RADIO_CHANNEL_AI_PRIVATE "AI Private" +#define RADIO_KEY_AI_PRIVATE "o" +#define RADIO_TOKEN_AI_PRIVATE ":o" + + +#define RADIO_CHANNEL_SYNDICATE "Syndicate" +#define RADIO_KEY_SYNDICATE "t" +#define RADIO_TOKEN_SYNDICATE ":t" + +#define RADIO_CHANNEL_CENTCOM "CentCom" +#define RADIO_KEY_CENTCOM "y" +#define RADIO_TOKEN_CENTCOM ":y" + +#define RADIO_CHANNEL_CTF_RED "Red Team" +#define RADIO_CHANNEL_CTF_BLUE "Blue Team" + + +#define MIN_FREE_FREQ 1201 // ------------------------------------------------- +// Frequencies are always odd numbers and range from 1201 to 1599. + +#define FREQ_SYNDICATE 1213 // Nuke op comms frequency, dark brown +#define FREQ_CTF_RED 1215 // CTF red team comms frequency, red +#define FREQ_CTF_BLUE 1217 // CTF blue team comms frequency, blue +#define FREQ_CENTCOM 1337 // CentCom comms frequency, gray +#define FREQ_SUPPLY 1347 // Supply comms frequency, light brown +#define FREQ_SERVICE 1349 // Service comms frequency, green +#define FREQ_SCIENCE 1351 // Science comms frequency, plum +#define FREQ_COMMAND 1353 // Command comms frequency, gold +#define FREQ_MEDICAL 1355 // Medical comms frequency, soft blue +#define FREQ_ENGINEERING 1357 // Engineering comms frequency, orange +#define FREQ_SECURITY 1359 // Security comms frequency, red + +#define FREQ_HOLOGRID_SOLUTION 1433 +#define FREQ_STATUS_DISPLAYS 1435 +#define FREQ_ATMOS_ALARMS 1437 // air alarms <-> alert computers +#define FREQ_ATMOS_CONTROL 1439 // air alarms <-> vents and scrubbers + +#define MIN_FREQ 1441 // ------------------------------------------------------ +// Only the 1441 to 1489 range is freely available for general conversation. +// This represents 1/8th of the available spectrum. + +#define FREQ_ATMOS_STORAGE 1441 +#define FREQ_NAV_BEACON 1445 +#define FREQ_AI_PRIVATE 1447 // AI private comms frequency, magenta +#define FREQ_PRESSURE_PLATE 1447 +#define FREQ_AIRLOCK_CONTROL 1449 +#define FREQ_ELECTROPACK 1449 +#define FREQ_MAGNETS 1449 +#define FREQ_LOCATOR_IMPLANT 1451 +#define FREQ_SIGNALER 1457 // the default for new signalers +#define FREQ_COMMON 1459 // Common comms frequency, dark green + +#define MAX_FREQ 1489 // ------------------------------------------------------ + +#define MAX_FREE_FREQ 1599 // ------------------------------------------------- + +// Transmission types. +#define TRANSMISSION_WIRE 0 // some sort of wired connection, not used +#define TRANSMISSION_RADIO 1 // electromagnetic radiation (default) +#define TRANSMISSION_SUBSPACE 2 // subspace transmission (headsets only) +#define TRANSMISSION_SUPERSPACE 3 // reaches independent (CentCom) radios only + +// Filter types, used as an optimization to avoid unnecessary proc calls. +#define RADIO_TO_AIRALARM "to_airalarm" +#define RADIO_FROM_AIRALARM "from_airalarm" +#define RADIO_SIGNALER "signaler" +#define RADIO_ATMOSIA "atmosia" +#define RADIO_AIRLOCK "airlock" +#define RADIO_MAGNETS "magnets" + +#define DEFAULT_SIGNALER_CODE 30 + +//Requests Console +#define REQ_NO_NEW_MESSAGE 0 +#define REQ_NORMAL_MESSAGE_PRIORITY 1 +#define REQ_HIGH_MESSAGE_PRIORITY 2 +#define REQ_EXTREME_MESSAGE_PRIORITY 3 + +#define REQ_DEP_TYPE_ASSISTANCE (1<<0) +#define REQ_DEP_TYPE_SUPPLIES (1<<1) +#define REQ_DEP_TYPE_INFORMATION (1<<2) diff --git a/code/__DEFINES/reagents_specific_heat.dm b/code/__DEFINES/reagents_specific_heat.dm index a721b98cc64b..90a379d7de5e 100644 --- a/code/__DEFINES/reagents_specific_heat.dm +++ b/code/__DEFINES/reagents_specific_heat.dm @@ -1,3 +1,3 @@ -#define SPECIFIC_HEAT_DEFAULT 200 - -#define SPECIFIC_HEAT_PLASMA 500 +#define SPECIFIC_HEAT_DEFAULT 200 + +#define SPECIFIC_HEAT_PLASMA 500 diff --git a/code/__DEFINES/research.dm b/code/__DEFINES/research.dm index a0e2fa9d9f93..cc1b10bec776 100644 --- a/code/__DEFINES/research.dm +++ b/code/__DEFINES/research.dm @@ -1,87 +1,87 @@ -///Defines for the R&D console, see: [/modules/research/rdconsole][rdconsole] -#define RDCONSOLE_UI_MODE_NORMAL 1 -#define RDCONSOLE_UI_MODE_EXPERT 2 -#define RDCONSOLE_UI_MODE_LIST 3 - -#define RDSCREEN_MENU 0 -#define RDSCREEN_TECHDISK 1 -#define RDSCREEN_DESIGNDISK 20 -#define RDSCREEN_DESIGNDISK_UPLOAD 21 -#define RDSCREEN_DECONSTRUCT 3 -#define RDSCREEN_PROTOLATHE 40 -#define RDSCREEN_PROTOLATHE_MATERIALS 41 -#define RDSCREEN_PROTOLATHE_CHEMICALS 42 -#define RDSCREEN_PROTOLATHE_CATEGORY_VIEW 43 -#define RDSCREEN_PROTOLATHE_SEARCH 44 -#define RDSCREEN_IMPRINTER 50 -#define RDSCREEN_IMPRINTER_MATERIALS 51 -#define RDSCREEN_IMPRINTER_CHEMICALS 52 -#define RDSCREEN_IMPRINTER_CATEGORY_VIEW 53 -#define RDSCREEN_IMPRINTER_SEARCH 54 -#define RDSCREEN_SETTINGS 61 -#define RDSCREEN_DEVICE_LINKING 62 -#define RDSCREEN_TECHWEB 70 -#define RDSCREEN_TECHWEB_NODEVIEW 71 -#define RDSCREEN_TECHWEB_DESIGNVIEW 72 - -#define RDSCREEN_NOBREAK "" - -///Sanity check defines for when these devices aren't connected or no disk is inserted -#define RDSCREEN_TEXT_NO_PROTOLATHE "

No Protolathe Linked!


" -#define RDSCREEN_TEXT_NO_IMPRINTER "

No Circuit Imprinter Linked!


" -#define RDSCREEN_TEXT_NO_DECONSTRUCT "

No Destructive Analyzer Linked!


" -#define RDSCREEN_TEXT_NO_TDISK "

No Technology Disk Inserted!


" -#define RDSCREEN_TEXT_NO_DDISK "

No Design Disk Inserted!


" -#define RDSCREEN_TEXT_NO_SNODE "

No Technology Node Selected!


" -#define RDSCREEN_TEXT_NO_SDESIGN "

No Design Selected!


" - -#define RDSCREEN_UI_LATHE_CHECK if(QDELETED(linked_lathe)) { return RDSCREEN_TEXT_NO_PROTOLATHE } -#define RDSCREEN_UI_IMPRINTER_CHECK if(QDELETED(linked_imprinter)) { return RDSCREEN_TEXT_NO_IMPRINTER } -#define RDSCREEN_UI_DECONSTRUCT_CHECK if(QDELETED(linked_destroy)) { return RDSCREEN_TEXT_NO_DECONSTRUCT } -#define RDSCREEN_UI_TDISK_CHECK if(QDELETED(t_disk)) { return RDSCREEN_TEXT_NO_TDISK } -#define RDSCREEN_UI_DDISK_CHECK if(QDELETED(d_disk)) { return RDSCREEN_TEXT_NO_DDISK } -#define RDSCREEN_UI_SNODE_CHECK if(!selected_node) { return RDSCREEN_TEXT_NO_SNODE } -#define RDSCREEN_UI_SDESIGN_CHECK if(!selected_design) { return RDSCREEN_TEXT_NO_SDESIGN } - -///Defines for the Protolathe screens, see: [/modules/research/machinery/protolathe][Protolathe] -#define RESEARCH_FABRICATOR_SCREEN_MAIN 1 -#define RESEARCH_FABRICATOR_SCREEN_CHEMICALS 2 -#define RESEARCH_FABRICATOR_SCREEN_MATERIALS 3 -#define RESEARCH_FABRICATOR_SCREEN_SEARCH 4 -#define RESEARCH_FABRICATOR_SCREEN_CATEGORYVIEW 5 - -///Department flags for techwebs. Defines which department can print what from each protolathe so Cargo can't print guns, etc. -#define DEPARTMENTAL_FLAG_SECURITY (1<<0) -#define DEPARTMENTAL_FLAG_MEDICAL (1<<1) -#define DEPARTMENTAL_FLAG_CARGO (1<<2) -#define DEPARTMENTAL_FLAG_SCIENCE (1<<3) -#define DEPARTMENTAL_FLAG_ENGINEERING (1<<4) -#define DEPARTMENTAL_FLAG_SERVICE (1<<5) - -#define DESIGN_ID_IGNORE "IGNORE_THIS_DESIGN" ///For instances where we don't want a design showing up due to it being for debug/sanity purposes - -#define RESEARCH_MATERIAL_RECLAMATION_ID "__materials" - -///Techweb names for new point types. Can be used to define specific point values for specific types of research (science, security, engineering, etc.) -#define TECHWEB_POINT_TYPE_GENERIC "General Research" -#define TECHWEB_POINT_TYPE_NANITES "Nanite Research" - -#define TECHWEB_POINT_TYPE_DEFAULT TECHWEB_POINT_TYPE_GENERIC - -///Associative names for techweb point values, see: [/modules/research/techweb/all_nodes][all_nodes] -#define TECHWEB_POINT_TYPE_LIST_ASSOCIATIVE_NAMES list(\ - TECHWEB_POINT_TYPE_GENERIC = "General Research",\ - TECHWEB_POINT_TYPE_NANITES = "Nanite Research"\ - ) - -///R&D point value for a maxcap bomb. Can be adjusted if need be. Current Value Cap Radius: 100 -#define TECHWEB_BOMB_POINTCAP 50000 - -///Research point values for slime extracts, see: [/modules/research/xenobiology/xenobio_camera][xenobio_camera] -#define SLIME_RESEARCH_TIER_0 100 -#define SLIME_RESEARCH_TIER_1 500 -#define SLIME_RESEARCH_TIER_2 1000 -#define SLIME_RESEARCH_TIER_3 1500 -#define SLIME_RESEARCH_TIER_4 2000 -#define SLIME_RESEARCH_TIER_5 2500 -#define SLIME_RESEARCH_TIER_RAINBOW 5000 +///Defines for the R&D console, see: [/modules/research/rdconsole][rdconsole] +#define RDCONSOLE_UI_MODE_NORMAL 1 +#define RDCONSOLE_UI_MODE_EXPERT 2 +#define RDCONSOLE_UI_MODE_LIST 3 + +#define RDSCREEN_MENU 0 +#define RDSCREEN_TECHDISK 1 +#define RDSCREEN_DESIGNDISK 20 +#define RDSCREEN_DESIGNDISK_UPLOAD 21 +#define RDSCREEN_DECONSTRUCT 3 +#define RDSCREEN_PROTOLATHE 40 +#define RDSCREEN_PROTOLATHE_MATERIALS 41 +#define RDSCREEN_PROTOLATHE_CHEMICALS 42 +#define RDSCREEN_PROTOLATHE_CATEGORY_VIEW 43 +#define RDSCREEN_PROTOLATHE_SEARCH 44 +#define RDSCREEN_IMPRINTER 50 +#define RDSCREEN_IMPRINTER_MATERIALS 51 +#define RDSCREEN_IMPRINTER_CHEMICALS 52 +#define RDSCREEN_IMPRINTER_CATEGORY_VIEW 53 +#define RDSCREEN_IMPRINTER_SEARCH 54 +#define RDSCREEN_SETTINGS 61 +#define RDSCREEN_DEVICE_LINKING 62 +#define RDSCREEN_TECHWEB 70 +#define RDSCREEN_TECHWEB_NODEVIEW 71 +#define RDSCREEN_TECHWEB_DESIGNVIEW 72 + +#define RDSCREEN_NOBREAK "" + +///Sanity check defines for when these devices aren't connected or no disk is inserted +#define RDSCREEN_TEXT_NO_PROTOLATHE "

No Protolathe Linked!


" +#define RDSCREEN_TEXT_NO_IMPRINTER "

No Circuit Imprinter Linked!


" +#define RDSCREEN_TEXT_NO_DECONSTRUCT "

No Destructive Analyzer Linked!


" +#define RDSCREEN_TEXT_NO_TDISK "

No Technology Disk Inserted!


" +#define RDSCREEN_TEXT_NO_DDISK "

No Design Disk Inserted!


" +#define RDSCREEN_TEXT_NO_SNODE "

No Technology Node Selected!


" +#define RDSCREEN_TEXT_NO_SDESIGN "

No Design Selected!


" + +#define RDSCREEN_UI_LATHE_CHECK if(QDELETED(linked_lathe)) { return RDSCREEN_TEXT_NO_PROTOLATHE } +#define RDSCREEN_UI_IMPRINTER_CHECK if(QDELETED(linked_imprinter)) { return RDSCREEN_TEXT_NO_IMPRINTER } +#define RDSCREEN_UI_DECONSTRUCT_CHECK if(QDELETED(linked_destroy)) { return RDSCREEN_TEXT_NO_DECONSTRUCT } +#define RDSCREEN_UI_TDISK_CHECK if(QDELETED(t_disk)) { return RDSCREEN_TEXT_NO_TDISK } +#define RDSCREEN_UI_DDISK_CHECK if(QDELETED(d_disk)) { return RDSCREEN_TEXT_NO_DDISK } +#define RDSCREEN_UI_SNODE_CHECK if(!selected_node) { return RDSCREEN_TEXT_NO_SNODE } +#define RDSCREEN_UI_SDESIGN_CHECK if(!selected_design) { return RDSCREEN_TEXT_NO_SDESIGN } + +///Defines for the Protolathe screens, see: [/modules/research/machinery/protolathe][Protolathe] +#define RESEARCH_FABRICATOR_SCREEN_MAIN 1 +#define RESEARCH_FABRICATOR_SCREEN_CHEMICALS 2 +#define RESEARCH_FABRICATOR_SCREEN_MATERIALS 3 +#define RESEARCH_FABRICATOR_SCREEN_SEARCH 4 +#define RESEARCH_FABRICATOR_SCREEN_CATEGORYVIEW 5 + +///Department flags for techwebs. Defines which department can print what from each protolathe so Cargo can't print guns, etc. +#define DEPARTMENTAL_FLAG_SECURITY (1<<0) +#define DEPARTMENTAL_FLAG_MEDICAL (1<<1) +#define DEPARTMENTAL_FLAG_CARGO (1<<2) +#define DEPARTMENTAL_FLAG_SCIENCE (1<<3) +#define DEPARTMENTAL_FLAG_ENGINEERING (1<<4) +#define DEPARTMENTAL_FLAG_SERVICE (1<<5) + +#define DESIGN_ID_IGNORE "IGNORE_THIS_DESIGN" ///For instances where we don't want a design showing up due to it being for debug/sanity purposes + +#define RESEARCH_MATERIAL_RECLAMATION_ID "__materials" + +///Techweb names for new point types. Can be used to define specific point values for specific types of research (science, security, engineering, etc.) +#define TECHWEB_POINT_TYPE_GENERIC "General Research" +#define TECHWEB_POINT_TYPE_NANITES "Nanite Research" + +#define TECHWEB_POINT_TYPE_DEFAULT TECHWEB_POINT_TYPE_GENERIC + +///Associative names for techweb point values, see: [/modules/research/techweb/all_nodes][all_nodes] +#define TECHWEB_POINT_TYPE_LIST_ASSOCIATIVE_NAMES list(\ + TECHWEB_POINT_TYPE_GENERIC = "General Research",\ + TECHWEB_POINT_TYPE_NANITES = "Nanite Research"\ + ) + +///R&D point value for a maxcap bomb. Can be adjusted if need be. Current Value Cap Radius: 100 +#define TECHWEB_BOMB_POINTCAP 50000 + +///Research point values for slime extracts, see: [/modules/research/xenobiology/xenobio_camera][xenobio_camera] +#define SLIME_RESEARCH_TIER_0 100 +#define SLIME_RESEARCH_TIER_1 500 +#define SLIME_RESEARCH_TIER_2 1000 +#define SLIME_RESEARCH_TIER_3 1500 +#define SLIME_RESEARCH_TIER_4 2000 +#define SLIME_RESEARCH_TIER_5 2500 +#define SLIME_RESEARCH_TIER_RAINBOW 5000 diff --git a/code/__DEFINES/rust_g.dm b/code/__DEFINES/rust_g.dm index aeacdb7c51bd..ddb23488a29e 100644 --- a/code/__DEFINES/rust_g.dm +++ b/code/__DEFINES/rust_g.dm @@ -1,12 +1,51 @@ // rust_g.dm - DM API for rust_g extension library -#define RUST_G "rust_g" +// +// To configure, create a `rust_g.config.dm` and set what you care about from +// the following options: +// +// #define RUST_G "path/to/rust_g" +// Override the .dll/.so detection logic with a fixed path or with detection +// logic of your own. +// +// #define RUSTG_OVERRIDE_BUILTINS +// Enable replacement rust-g functions for certain builtins. Off by default. + +#ifndef RUST_G +// Default automatic RUST_G detection. +// On Windows, looks in the standard places for `rust_g.dll`. +// On Linux, looks in `.`, `$LD_LIBRARY_PATH`, and `~/.byond/bin` for either of +// `librust_g.so` (preferred) or `rust_g` (old). + +/* This comment bypasses grep checks */ /var/__rust_g + +/proc/__detect_rust_g() + if (world.system_type == UNIX) + if (fexists("./librust_g.so")) + // No need for LD_LIBRARY_PATH badness. + return __rust_g = "./librust_g.so" + else if (fexists("./rust_g")) + // Old dumb filename. + return __rust_g = "./rust_g" + else if (fexists("[world.GetConfig("env", "HOME")]/.byond/bin/rust_g")) + // Old dumb filename in `~/.byond/bin`. + return __rust_g = "rust_g" + else + // It's not in the current directory, so try others + return __rust_g = "librust_g.so" + else + return __rust_g = "rust_g" + +#define RUST_G (__rust_g || __detect_rust_g()) +#endif #define RUSTG_JOB_NO_RESULTS_YET "NO RESULTS YET" #define RUSTG_JOB_NO_SUCH_JOB "NO SUCH JOB" #define RUSTG_JOB_ERROR "JOB PANICKED" #define rustg_dmi_strip_metadata(fname) call(RUST_G, "dmi_strip_metadata")(fname) -#define rustg_dmi_create_png(fname,width,height,data) call(RUST_G, "dmi_create_png")(fname,width,height,data) +#define rustg_dmi_create_png(path, width, height, data) call(RUST_G, "dmi_create_png")(path, width, height, data) + +#define rustg_noise_get_at_coordinates(seed, x, y) call(RUST_G, "noise_get_at_coordinates")(seed, x, y) #define rustg_git_revparse(rev) call(RUST_G, "rg_git_revparse")(rev) #define rustg_git_commit_date(rev) call(RUST_G, "rg_git_commit_date")(rev) @@ -14,14 +53,19 @@ #define rustg_log_write(fname, text, format) call(RUST_G, "log_write")(fname, text, format) /proc/rustg_log_close_all() return call(RUST_G, "log_close_all")() -// RUST-G defines & procs for HTTP component #define RUSTG_HTTP_METHOD_GET "get" -#define RUSTG_HTTP_METHOD_POST "post" #define RUSTG_HTTP_METHOD_PUT "put" #define RUSTG_HTTP_METHOD_DELETE "delete" #define RUSTG_HTTP_METHOD_PATCH "patch" #define RUSTG_HTTP_METHOD_HEAD "head" - +#define RUSTG_HTTP_METHOD_POST "post" #define rustg_http_request_blocking(method, url, body, headers) call(RUST_G, "http_request_blocking")(method, url, body, headers) #define rustg_http_request_async(method, url, body, headers) call(RUST_G, "http_request_async")(method, url, body, headers) #define rustg_http_check_request(req_id) call(RUST_G, "http_check_request")(req_id) + +#define rustg_sql_connect_pool(options) call(RUST_G, "sql_connect_pool")(options) +#define rustg_sql_query_async(handle, query, params) call(RUST_G, "sql_query_async")(handle, query, params) +#define rustg_sql_query_blocking(handle, query, params) call(RUST_G, "sql_query_blocking")(handle, query, params) +#define rustg_sql_connected(handle) call(RUST_G, "sql_connected")(handle) +#define rustg_sql_disconnect_pool(handle) call(RUST_G, "sql_disconnect_pool")(handle) +#define rustg_sql_check_query(job_id) call(RUST_G, "sql_check_query")("[job_id]") diff --git a/code/__DEFINES/sight.dm b/code/__DEFINES/sight.dm index 6ab3092d1d04..5cac2900823b 100644 --- a/code/__DEFINES/sight.dm +++ b/code/__DEFINES/sight.dm @@ -1,35 +1,35 @@ -#define SEE_INVISIBLE_MINIMUM 5 - -#define INVISIBILITY_LIGHTING 20 - -#define SEE_INVISIBLE_LIVING 25 - -//#define SEE_INVISIBLE_LEVEL_ONE 35 //currently unused -//#define INVISIBILITY_LEVEL_ONE 35 //currently unused - -//#define SEE_INVISIBLE_LEVEL_TWO 45 //currently unused -//#define INVISIBILITY_LEVEL_TWO 45 //currently unused - -#define INVISIBILITY_OBSERVER 60 -#define SEE_INVISIBLE_OBSERVER 60 - -#define INVISIBILITY_MAXIMUM 100 //the maximum allowed for "real" objects - -#define INVISIBILITY_ABSTRACT 101 //only used for abstract objects (e.g. spacevine_controller), things that are not really there. - -#define BORGMESON (1<<0) -#define BORGTHERM (1<<1) -#define BORGXRAY (1<<2) -#define BORGMATERIAL (1<<3) - -//for clothing visor toggles, these determine which vars to toggle -#define VISOR_FLASHPROTECT (1<<0) -#define VISOR_TINT (1<<1) -#define VISOR_VISIONFLAGS (1<<2) //all following flags only matter for glasses -#define VISOR_DARKNESSVIEW (1<<3) -#define VISOR_INVISVIEW (1<<4) - -//for whether AI eyes see static, and whether it is mouse-opaque or not -#define USE_STATIC_NONE 0 -#define USE_STATIC_TRANSPARENT 1 -#define USE_STATIC_OPAQUE 2 +#define SEE_INVISIBLE_MINIMUM 5 + +#define INVISIBILITY_LIGHTING 20 + +#define SEE_INVISIBLE_LIVING 25 + +//#define SEE_INVISIBLE_LEVEL_ONE 35 //currently unused +//#define INVISIBILITY_LEVEL_ONE 35 //currently unused + +//#define SEE_INVISIBLE_LEVEL_TWO 45 //currently unused +//#define INVISIBILITY_LEVEL_TWO 45 //currently unused + +#define INVISIBILITY_OBSERVER 60 +#define SEE_INVISIBLE_OBSERVER 60 + +#define INVISIBILITY_MAXIMUM 100 //the maximum allowed for "real" objects + +#define INVISIBILITY_ABSTRACT 101 //only used for abstract objects (e.g. spacevine_controller), things that are not really there. + +#define BORGMESON (1<<0) +#define BORGTHERM (1<<1) +#define BORGXRAY (1<<2) +#define BORGMATERIAL (1<<3) + +//for clothing visor toggles, these determine which vars to toggle +#define VISOR_FLASHPROTECT (1<<0) +#define VISOR_TINT (1<<1) +#define VISOR_VISIONFLAGS (1<<2) //all following flags only matter for glasses +#define VISOR_DARKNESSVIEW (1<<3) +#define VISOR_INVISVIEW (1<<4) + +//for whether AI eyes see static, and whether it is mouse-opaque or not +#define USE_STATIC_NONE 0 +#define USE_STATIC_TRANSPARENT 1 +#define USE_STATIC_OPAQUE 2 diff --git a/code/__DEFINES/skills.dm b/code/__DEFINES/skills.dm index c757af43b21c..7ac04bd9864f 100644 --- a/code/__DEFINES/skills.dm +++ b/code/__DEFINES/skills.dm @@ -1,27 +1,24 @@ // Skill levels -#define SKILL_LEVEL_NONE 0 -#define SKILL_LEVEL_NOVICE 1 -#define SKILL_LEVEL_APPRENTICE 2 -#define SKILL_LEVEL_JOURNEYMAN 3 -#define SKILL_LEVEL_EXPERT 4 -#define SKILL_LEVEL_MASTER 5 -#define SKILL_LEVEL_LEGENDARY 6 +#define SKILL_LEVEL_NONE 1 +#define SKILL_LEVEL_NOVICE 2 +#define SKILL_LEVEL_APPRENTICE 3 +#define SKILL_LEVEL_JOURNEYMAN 4 +#define SKILL_LEVEL_EXPERT 5 +#define SKILL_LEVEL_MASTER 6 +#define SKILL_LEVEL_LEGENDARY 7 -//Skill experience thresholds -#define SKILL_EXP_NOVICE 100 -#define SKILL_EXP_APPRENTICE 250 -#define SKILL_EXP_JOURNEYMAN 500 -#define SKILL_EXP_EXPERT 900 -#define SKILL_EXP_MASTER 1500 -#define SKILL_EXP_LEGENDARY 2500 +#define SKILL_LVL 1 +#define SKILL_EXP 2 + +//Allows us to get EXP from level, or level from EXP +#define SKILL_EXP_LIST list(0, 100, 250, 500, 900, 1500, 2500) //Skill modifier types #define SKILL_SPEED_MODIFIER "skill_speed_modifier"//ideally added/subtracted in speed calculations to make you do stuff faster #define SKILL_PROBS_MODIFIER "skill_probability_modifier"//ideally added/subtracted where beneficial in prob(x) calls #define SKILL_RANDS_MODIFIER "skill_randomness_modifier"//ideally added/subtracted where beneficial in rand(x,y) calls - // Gets the reference for the skill type that was given #define GetSkillRef(A) (SSskills.all_skills[A]) diff --git a/code/__DEFINES/sound.dm b/code/__DEFINES/sound.dm index a2866365b109..85da5782a454 100644 --- a/code/__DEFINES/sound.dm +++ b/code/__DEFINES/sound.dm @@ -1,82 +1,82 @@ -//max channel is 1024. Only go lower from here, because byond tends to pick the first availiable channel to play sounds on -#define CHANNEL_LOBBYMUSIC 1024 -#define CHANNEL_ADMIN 1023 -#define CHANNEL_VOX 1022 -#define CHANNEL_JUKEBOX 1021 -#define CHANNEL_JUSTICAR_ARK 1020 -#define CHANNEL_HEARTBEAT 1019 //sound channel for heartbeats -#define CHANNEL_AMBIENCE 1018 -#define CHANNEL_BUZZ 1017 -#define CHANNEL_BICYCLE 1016 - -//THIS SHOULD ALWAYS BE THE LOWEST ONE! -//KEEP IT UPDATED - -#define CHANNEL_HIGHEST_AVAILABLE 1015 - - -#define SOUND_MINIMUM_PRESSURE 10 -#define FALLOFF_SOUNDS 1 - - -//Ambience types - -#define GENERIC list('sound/ambience/ambigen1.ogg','sound/ambience/ambigen3.ogg',\ - 'sound/ambience/ambigen4.ogg','sound/ambience/ambigen5.ogg',\ - 'sound/ambience/ambigen6.ogg','sound/ambience/ambigen7.ogg',\ - 'sound/ambience/ambigen8.ogg','sound/ambience/ambigen9.ogg',\ - 'sound/ambience/ambigen10.ogg','sound/ambience/ambigen11.ogg',\ - 'sound/ambience/ambigen12.ogg','sound/ambience/ambigen14.ogg','sound/ambience/ambigen15.ogg') - -#define HOLY list('sound/ambience/ambicha1.ogg','sound/ambience/ambicha2.ogg','sound/ambience/ambicha3.ogg',\ - 'sound/ambience/ambicha4.ogg', 'sound/ambience/ambiholy.ogg', 'sound/ambience/ambiholy2.ogg',\ - 'sound/ambience/ambiholy3.ogg') - -#define HIGHSEC list('sound/ambience/ambidanger.ogg', 'sound/ambience/ambidanger2.ogg') - -#define RUINS list('sound/ambience/ambimine.ogg', 'sound/ambience/ambicave.ogg', 'sound/ambience/ambiruin.ogg',\ - 'sound/ambience/ambiruin2.ogg', 'sound/ambience/ambiruin3.ogg', 'sound/ambience/ambiruin4.ogg',\ - 'sound/ambience/ambiruin5.ogg', 'sound/ambience/ambiruin6.ogg', 'sound/ambience/ambiruin7.ogg',\ - 'sound/ambience/ambidanger.ogg', 'sound/ambience/ambidanger2.ogg', 'sound/ambience/ambitech3.ogg',\ - 'sound/ambience/ambimystery.ogg', 'sound/ambience/ambimaint1.ogg') - -#define ENGINEERING list('sound/ambience/ambisin1.ogg','sound/ambience/ambisin2.ogg','sound/ambience/ambisin3.ogg','sound/ambience/ambisin4.ogg',\ - 'sound/ambience/ambiatmos.ogg', 'sound/ambience/ambiatmos2.ogg', 'sound/ambience/ambitech.ogg', 'sound/ambience/ambitech2.ogg', 'sound/ambience/ambitech3.ogg') - -#define MINING list('sound/ambience/ambimine.ogg', 'sound/ambience/ambicave.ogg', 'sound/ambience/ambiruin.ogg',\ - 'sound/ambience/ambiruin2.ogg', 'sound/ambience/ambiruin3.ogg', 'sound/ambience/ambiruin4.ogg',\ - 'sound/ambience/ambiruin5.ogg', 'sound/ambience/ambiruin6.ogg', 'sound/ambience/ambiruin7.ogg',\ - 'sound/ambience/ambidanger.ogg', 'sound/ambience/ambidanger2.ogg', 'sound/ambience/ambimaint1.ogg', 'sound/ambience/ambilava.ogg') - -#define MEDICAL list('sound/ambience/ambinice.ogg') - -#define SPOOKY list('sound/ambience/ambimo1.ogg','sound/ambience/ambimo2.ogg','sound/ambience/ambiruin7.ogg','sound/ambience/ambiruin6.ogg',\ - 'sound/ambience/ambiodd.ogg', 'sound/ambience/ambimystery.ogg') - -#define SPACE list('sound/ambience/ambispace.ogg', 'sound/ambience/ambispace2.ogg', 'sound/ambience/title2.ogg', 'sound/ambience/ambiatmos.ogg') - -#define MAINTENANCE list('sound/ambience/ambimaint1.ogg', 'sound/ambience/ambimaint2.ogg', 'sound/ambience/ambimaint3.ogg', 'sound/ambience/ambimaint4.ogg',\ - 'sound/ambience/ambimaint5.ogg', 'sound/voice/lowHiss2.ogg', 'sound/voice/lowHiss3.ogg', 'sound/voice/lowHiss4.ogg', 'sound/ambience/ambitech2.ogg' ) - -#define AWAY_MISSION list('sound/ambience/ambitech.ogg', 'sound/ambience/ambitech2.ogg', 'sound/ambience/ambiruin.ogg',\ - 'sound/ambience/ambiruin2.ogg', 'sound/ambience/ambiruin3.ogg', 'sound/ambience/ambiruin4.ogg',\ - 'sound/ambience/ambiruin5.ogg', 'sound/ambience/ambiruin6.ogg', 'sound/ambience/ambiruin7.ogg',\ - 'sound/ambience/ambidanger.ogg', 'sound/ambience/ambidanger2.ogg', 'sound/ambience/ambimaint.ogg',\ - 'sound/ambience/ambiatmos.ogg', 'sound/ambience/ambiatmos2.ogg', 'sound/ambience/ambiodd.ogg') - -#define REEBE list('sound/ambience/ambireebe1.ogg', 'sound/ambience/ambireebe2.ogg', 'sound/ambience/ambireebe3.ogg') - - - -#define CREEPY_SOUNDS list('sound/effects/ghost.ogg', 'sound/effects/ghost2.ogg', 'sound/effects/heart_beat.ogg', 'sound/effects/screech.ogg',\ - 'sound/hallucinations/behind_you1.ogg', 'sound/hallucinations/behind_you2.ogg', 'sound/hallucinations/far_noise.ogg', 'sound/hallucinations/growl1.ogg', 'sound/hallucinations/growl2.ogg',\ - 'sound/hallucinations/growl3.ogg', 'sound/hallucinations/im_here1.ogg', 'sound/hallucinations/im_here2.ogg', 'sound/hallucinations/i_see_you1.ogg', 'sound/hallucinations/i_see_you2.ogg',\ - 'sound/hallucinations/look_up1.ogg', 'sound/hallucinations/look_up2.ogg', 'sound/hallucinations/over_here1.ogg', 'sound/hallucinations/over_here2.ogg', 'sound/hallucinations/over_here3.ogg',\ - 'sound/hallucinations/turn_around1.ogg', 'sound/hallucinations/turn_around2.ogg', 'sound/hallucinations/veryfar_noise.ogg', 'sound/hallucinations/wail.ogg') - - -#define INTERACTION_SOUND_RANGE_MODIFIER -3 -#define EQUIP_SOUND_VOLUME 30 -#define PICKUP_SOUND_VOLUME 15 -#define DROP_SOUND_VOLUME 20 -#define YEET_SOUND_VOLUME 90 +//max channel is 1024. Only go lower from here, because byond tends to pick the first availiable channel to play sounds on +#define CHANNEL_LOBBYMUSIC 1024 +#define CHANNEL_ADMIN 1023 +#define CHANNEL_VOX 1022 +#define CHANNEL_JUKEBOX 1021 +#define CHANNEL_JUSTICAR_ARK 1020 +#define CHANNEL_HEARTBEAT 1019 //sound channel for heartbeats +#define CHANNEL_AMBIENCE 1018 +#define CHANNEL_BUZZ 1017 +#define CHANNEL_BICYCLE 1016 + +//THIS SHOULD ALWAYS BE THE LOWEST ONE! +//KEEP IT UPDATED + +#define CHANNEL_HIGHEST_AVAILABLE 1015 + + +#define SOUND_MINIMUM_PRESSURE 10 +#define FALLOFF_SOUNDS 1 + + +//Ambience types + +#define GENERIC list('sound/ambience/ambigen1.ogg','sound/ambience/ambigen3.ogg',\ + 'sound/ambience/ambigen4.ogg','sound/ambience/ambigen5.ogg',\ + 'sound/ambience/ambigen6.ogg','sound/ambience/ambigen7.ogg',\ + 'sound/ambience/ambigen8.ogg','sound/ambience/ambigen9.ogg',\ + 'sound/ambience/ambigen10.ogg','sound/ambience/ambigen11.ogg',\ + 'sound/ambience/ambigen12.ogg','sound/ambience/ambigen14.ogg','sound/ambience/ambigen15.ogg') + +#define HOLY list('sound/ambience/ambicha1.ogg','sound/ambience/ambicha2.ogg','sound/ambience/ambicha3.ogg',\ + 'sound/ambience/ambicha4.ogg', 'sound/ambience/ambiholy.ogg', 'sound/ambience/ambiholy2.ogg',\ + 'sound/ambience/ambiholy3.ogg') + +#define HIGHSEC list('sound/ambience/ambidanger.ogg', 'sound/ambience/ambidanger2.ogg') + +#define RUINS list('sound/ambience/ambimine.ogg', 'sound/ambience/ambicave.ogg', 'sound/ambience/ambiruin.ogg',\ + 'sound/ambience/ambiruin2.ogg', 'sound/ambience/ambiruin3.ogg', 'sound/ambience/ambiruin4.ogg',\ + 'sound/ambience/ambiruin5.ogg', 'sound/ambience/ambiruin6.ogg', 'sound/ambience/ambiruin7.ogg',\ + 'sound/ambience/ambidanger.ogg', 'sound/ambience/ambidanger2.ogg', 'sound/ambience/ambitech3.ogg',\ + 'sound/ambience/ambimystery.ogg', 'sound/ambience/ambimaint1.ogg') + +#define ENGINEERING list('sound/ambience/ambisin1.ogg','sound/ambience/ambisin2.ogg','sound/ambience/ambisin3.ogg','sound/ambience/ambisin4.ogg',\ + 'sound/ambience/ambiatmos.ogg', 'sound/ambience/ambiatmos2.ogg', 'sound/ambience/ambitech.ogg', 'sound/ambience/ambitech2.ogg', 'sound/ambience/ambitech3.ogg') + +#define MINING list('sound/ambience/ambimine.ogg', 'sound/ambience/ambicave.ogg', 'sound/ambience/ambiruin.ogg',\ + 'sound/ambience/ambiruin2.ogg', 'sound/ambience/ambiruin3.ogg', 'sound/ambience/ambiruin4.ogg',\ + 'sound/ambience/ambiruin5.ogg', 'sound/ambience/ambiruin6.ogg', 'sound/ambience/ambiruin7.ogg',\ + 'sound/ambience/ambidanger.ogg', 'sound/ambience/ambidanger2.ogg', 'sound/ambience/ambimaint1.ogg', 'sound/ambience/ambilava.ogg') + +#define MEDICAL list('sound/ambience/ambinice.ogg') + +#define SPOOKY list('sound/ambience/ambimo1.ogg','sound/ambience/ambimo2.ogg','sound/ambience/ambiruin7.ogg','sound/ambience/ambiruin6.ogg',\ + 'sound/ambience/ambiodd.ogg', 'sound/ambience/ambimystery.ogg') + +#define SPACE list('sound/ambience/ambispace.ogg', 'sound/ambience/ambispace2.ogg', 'sound/ambience/title2.ogg', 'sound/ambience/ambiatmos.ogg') + +#define MAINTENANCE list('sound/ambience/ambimaint1.ogg', 'sound/ambience/ambimaint2.ogg', 'sound/ambience/ambimaint3.ogg', 'sound/ambience/ambimaint4.ogg',\ + 'sound/ambience/ambimaint5.ogg', 'sound/voice/lowHiss2.ogg', 'sound/voice/lowHiss3.ogg', 'sound/voice/lowHiss4.ogg', 'sound/ambience/ambitech2.ogg' ) + +#define AWAY_MISSION list('sound/ambience/ambitech.ogg', 'sound/ambience/ambitech2.ogg', 'sound/ambience/ambiruin.ogg',\ + 'sound/ambience/ambiruin2.ogg', 'sound/ambience/ambiruin3.ogg', 'sound/ambience/ambiruin4.ogg',\ + 'sound/ambience/ambiruin5.ogg', 'sound/ambience/ambiruin6.ogg', 'sound/ambience/ambiruin7.ogg',\ + 'sound/ambience/ambidanger.ogg', 'sound/ambience/ambidanger2.ogg', 'sound/ambience/ambimaint.ogg',\ + 'sound/ambience/ambiatmos.ogg', 'sound/ambience/ambiatmos2.ogg', 'sound/ambience/ambiodd.ogg') + +#define REEBE list('sound/ambience/ambireebe1.ogg', 'sound/ambience/ambireebe2.ogg', 'sound/ambience/ambireebe3.ogg') + + + +#define CREEPY_SOUNDS list('sound/effects/ghost.ogg', 'sound/effects/ghost2.ogg', 'sound/effects/heart_beat.ogg', 'sound/effects/screech.ogg',\ + 'sound/hallucinations/behind_you1.ogg', 'sound/hallucinations/behind_you2.ogg', 'sound/hallucinations/far_noise.ogg', 'sound/hallucinations/growl1.ogg', 'sound/hallucinations/growl2.ogg',\ + 'sound/hallucinations/growl3.ogg', 'sound/hallucinations/im_here1.ogg', 'sound/hallucinations/im_here2.ogg', 'sound/hallucinations/i_see_you1.ogg', 'sound/hallucinations/i_see_you2.ogg',\ + 'sound/hallucinations/look_up1.ogg', 'sound/hallucinations/look_up2.ogg', 'sound/hallucinations/over_here1.ogg', 'sound/hallucinations/over_here2.ogg', 'sound/hallucinations/over_here3.ogg',\ + 'sound/hallucinations/turn_around1.ogg', 'sound/hallucinations/turn_around2.ogg', 'sound/hallucinations/veryfar_noise.ogg', 'sound/hallucinations/wail.ogg') + + +#define INTERACTION_SOUND_RANGE_MODIFIER -3 +#define EQUIP_SOUND_VOLUME 30 +#define PICKUP_SOUND_VOLUME 15 +#define DROP_SOUND_VOLUME 20 +#define YEET_SOUND_VOLUME 90 diff --git a/code/__DEFINES/stat.dm b/code/__DEFINES/stat.dm index 3321f871de26..a77b65acf81f 100644 --- a/code/__DEFINES/stat.dm +++ b/code/__DEFINES/stat.dm @@ -1,21 +1,21 @@ -/* - Used with the various stat variables (mob, machines) -*/ - -//mob/var/stat things -#define CONSCIOUS 0 -#define SOFT_CRIT 1 -#define UNCONSCIOUS 2 -#define DEAD 3 - -//Maximum healthiness an individual can have -#define MAX_SATIETY 600 - -// bitflags for machine stat variable -#define BROKEN (1<<0) -#define NOPOWER (1<<1) -#define MAINT (1<<2) // under maintaince -#define EMPED (1<<3) // temporary broken by EMP pulse - -//ai power requirement defines -#define POWER_REQ_ALL 1 +/* + Used with the various stat variables (mob, machines) +*/ + +//mob/var/stat things +#define CONSCIOUS 0 +#define SOFT_CRIT 1 +#define UNCONSCIOUS 2 +#define DEAD 3 + +//Maximum healthiness an individual can have +#define MAX_SATIETY 600 + +// bitflags for machine stat variable +#define BROKEN (1<<0) +#define NOPOWER (1<<1) +#define MAINT (1<<2) // under maintaince +#define EMPED (1<<3) // temporary broken by EMP pulse + +//ai power requirement defines +#define POWER_REQ_ALL 1 diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index 9008b8c8696a..3d6711b459f3 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -166,6 +166,7 @@ #define FIRE_PRIORITY_TICKER 200 #define FIRE_PRIORITY_ATMOS_ADJACENCY 300 #define FIRE_PRIORITY_CHAT 400 +#define FIRE_PRIORITY_RUNECHAT 410 #define FIRE_PRIORITY_OVERLAYS 500 #define FIRE_PRIORITY_EXPLOSIONS 666 #define FIRE_PRIORITY_INPUT 1000 // This must always always be the max highest priority. Player input must never be lost. diff --git a/code/__DEFINES/tgs.config.dm b/code/__DEFINES/tgs.config.dm index f06fcc96e801..a2fcc7855ab0 100644 --- a/code/__DEFINES/tgs.config.dm +++ b/code/__DEFINES/tgs.config.dm @@ -1,12 +1,12 @@ -#define TGS_EXTERNAL_CONFIGURATION -#define TGS_V3_API -#define TGS_DEFINE_AND_SET_GLOBAL(Name, Value) GLOBAL_VAR_INIT(##Name, ##Value); GLOBAL_PROTECT(##Name) -#define TGS_READ_GLOBAL(Name) GLOB.##Name -#define TGS_WRITE_GLOBAL(Name, Value) GLOB.##Name = ##Value -#define TGS_WORLD_ANNOUNCE(message) to_chat(world, "[html_encode(##message)]") -#define TGS_INFO_LOG(message) log_world("TGS Info: [##message]") -#define TGS_WARNING_LOG(message) log_world("TGS Warn: [##message]") -#define TGS_ERROR_LOG(message) log_world("TGS Error: [##message]") -#define TGS_NOTIFY_ADMINS(event) message_admins(##event) -#define TGS_CLIENT_COUNT GLOB.clients.len -#define TGS_PROTECT_DATUM(Path) GENERAL_PROTECT_DATUM(##Path) +#define TGS_EXTERNAL_CONFIGURATION +#define TGS_V3_API +#define TGS_DEFINE_AND_SET_GLOBAL(Name, Value) GLOBAL_VAR_INIT(##Name, ##Value); GLOBAL_PROTECT(##Name) +#define TGS_READ_GLOBAL(Name) GLOB.##Name +#define TGS_WRITE_GLOBAL(Name, Value) GLOB.##Name = ##Value +#define TGS_WORLD_ANNOUNCE(message) to_chat(world, "[html_encode(##message)]") +#define TGS_INFO_LOG(message) log_world("TGS Info: [##message]") +#define TGS_WARNING_LOG(message) log_world("TGS Warn: [##message]") +#define TGS_ERROR_LOG(message) log_world("TGS Error: [##message]") +#define TGS_NOTIFY_ADMINS(event) message_admins(##event) +#define TGS_CLIENT_COUNT GLOB.clients.len +#define TGS_PROTECT_DATUM(Path) GENERAL_PROTECT_DATUM(##Path) diff --git a/code/__DEFINES/tgs.dm b/code/__DEFINES/tgs.dm index 3fc4fca12dbb..35d9dbefbdf2 100644 --- a/code/__DEFINES/tgs.dm +++ b/code/__DEFINES/tgs.dm @@ -1,263 +1,372 @@ -//tgstation-server DMAPI - -#define TGS_DMAPI_VERSION "5.0.0" - -//All functions and datums outside this document are subject to change with any version and should not be relied on - -//CONFIGURATION - -//create this define if you want to do configuration outside of this file -#ifndef TGS_EXTERNAL_CONFIGURATION - -//Comment this out once you've filled in the below -#error TGS API unconfigured - -//Uncomment this if you wish to allow the game to interact with TGS 3 -//This will raise the minimum required security level of your game to TGS_SECURITY_TRUSTED due to it utilizing call()() -//#define TGS_V3_API - -//Required interfaces (fill in with your codebase equivalent): - -//create a global variable named `Name` and set it to `Value` -#define TGS_DEFINE_AND_SET_GLOBAL(Name, Value) - -//Read the value in the global variable `Name` -#define TGS_READ_GLOBAL(Name) - -//Set the value in the global variable `Name` to `Value` -#define TGS_WRITE_GLOBAL(Name, Value) - -//Disallow ANYONE from reflecting a given `path`, security measure to prevent in-game use of DD -> TGS capabilities -#define TGS_PROTECT_DATUM(Path) - -//Display an announcement `message` from the server to all players -#define TGS_WORLD_ANNOUNCE(message) - -//Notify current in-game administrators of a string `event` -#define TGS_NOTIFY_ADMINS(event) - -//Write an info `message` to a server log -#define TGS_INFO_LOG(message) - -//Write an warning `message` to a server log -#define TGS_WARNING_LOG(message) - -//Write an error `message` to a server log -#define TGS_ERROR_LOG(message) - -//Get the number of connected /clients -#define TGS_CLIENT_COUNT - -#endif - -//EVENT CODES - -#define TGS_EVENT_PORT_SWAP -2 //before a port change is about to happen, extra parameter is new port -#define TGS_EVENT_REBOOT_MODE_CHANGE -1 //before a reboot mode change, extras parameters are the current and new reboot mode enums - -//See the descriptions for the parameters of these codes here: https://github.com/tgstation/tgstation-server/blob/master/src/Tgstation.Server.Host/Components/EventType.cs -#define TGS_EVENT_REPO_RESET_ORIGIN 0 -#define TGS_EVENT_REPO_CHECKOUT 1 -#define TGS_EVENT_REPO_FETCH 2 -#define TGS_EVENT_REPO_MERGE_PULL_REQUEST 3 -#define TGS_EVENT_REPO_PRE_SYNCHRONIZE 4 -#define TGS_EVENT_BYOND_INSTALL_START 5 -#define TGS_EVENT_BYOND_INSTALL_FAIL 6 -#define TGS_EVENT_BYOND_ACTIVE_VERSION_CHANGE 7 -#define TGS_EVENT_COMPILE_START 8 -#define TGS_EVENT_COMPILE_CANCELLED 9 -#define TGS_EVENT_COMPILE_FAILURE 10 -#define TGS_EVENT_COMPILE_COMPLETE 11 -#define TGS_EVENT_INSTANCE_AUTO_UPDATE_START 12 -#define TGS_EVENT_REPO_MERGE_CONFLICT 13 - -//OTHER ENUMS - -#define TGS_REBOOT_MODE_NORMAL 0 -#define TGS_REBOOT_MODE_SHUTDOWN 1 -#define TGS_REBOOT_MODE_RESTART 2 - -#define TGS_SECURITY_TRUSTED 0 -#define TGS_SECURITY_SAFE 1 -#define TGS_SECURITY_ULTRASAFE 2 - -//REQUIRED HOOKS - -//Call this somewhere in /world/New() that is always run -//event_handler: optional user defined event handler. The default behaviour is to broadcast the event in english to all connected admin channels -//minimum_required_security_level: The minimum required security level to run the game in which the DMAPI is integrated -/world/proc/TgsNew(datum/tgs_event_handler/event_handler, minimum_required_security_level = TGS_SECURITY_ULTRASAFE) - return - -//Call this when your initializations are complete and your game is ready to play before any player interactions happen -//This may use world.sleep_offline to make this happen so ensure no changes are made to it while this call is running -//Most importantly, before this point, note that any static files or directories may be in use by another server. Your code should account for this -//This function should not be called before ..() in /world/New() -/world/proc/TgsInitializationComplete() - return - -//Put this at the start of /world/Topic() -#define TGS_TOPIC var/tgs_topic_return = TgsTopic(args[1]); if(tgs_topic_return) return tgs_topic_return - -//Call this at the beginning of world/Reboot(reason) -/world/proc/TgsReboot() - return - -//DATUM DEFINITIONS -//unless otherwise specified all datums defined here should be considered read-only, warranty void if written - -//represents git revision information about the current world build -/datum/tgs_revision_information - var/commit //full sha of compiled commit - var/origin_commit //full sha of last known remote commit. This may be null if the TGS repository is not currently tracking a remote branch - -//represents a version of tgstation-server -/datum/tgs_version - var/suite //The suite/major version, can be >=3 - - //this group of variables can be null to represent a wild card - var/minor //The minor version - var/patch //The patch version - var/deprecated_patch //The legacy version - - var/raw_parameter //The unparsed parameter - var/deprefixed_parameter //The version only bit of raw_parameter - -//if the tgs_version is a wildcard version -/datum/tgs_version/proc/Wildcard() - return - -//represents a merge of a GitHub pull request -/datum/tgs_revision_information/test_merge - var/number //pull request number - var/title //pull request title - var/body //pull request body - var/author //pull request github author - var/url //link to pull request html - var/pull_request_commit //commit of the pull request when it was merged - var/time_merged //timestamp of when the merge commit for the pull request was created - var/comment //optional comment left by the one who initiated the test merge - -//represents a connected chat channel -/datum/tgs_chat_channel - var/id //internal channel representation - var/friendly_name //user friendly channel name - var/connection_name //the name of the configured chat connection - var/is_admin_channel //if the server operator has marked this channel for game admins only - var/is_private_channel //if this is a private chat channel - var/custom_tag //user defined string associated with channel - -//represents a chat user -/datum/tgs_chat_user - var/id //Internal user representation, requires channel to be unique - var/friendly_name //The user's public name - var/mention //The text to use to ping this user in a message - var/datum/tgs_chat_channel/channel //The /datum/tgs_chat_channel this user was from - -//user definable callback for handling events -//extra parameters may be specified depending on the event -/datum/tgs_event_handler/proc/HandleEvent(event_code, ...) - set waitfor = FALSE - return - -//user definable chat command -/datum/tgs_chat_command - var/name = "" //the string to trigger this command on a chat bot. e.g. TGS3_BOT: do_this_command - var/help_text = "" //help text for this command - var/admin_only = FALSE //set to TRUE if this command should only be usable by registered chat admins - -//override to implement command -//sender: The tgs_chat_user who send to command -//params: The trimmed string following the command name -//The return value will be stringified and sent to the appropriate chat -/datum/tgs_chat_command/proc/Run(datum/tgs_chat_user/sender, params) - CRASH("[type] has no implementation for Run()") - -//FUNCTIONS - -//Returns the respective supported /datum/tgs_version of the API -/world/proc/TgsMaximumAPIVersion() - return - -/world/proc/TgsMinimumAPIVersion() - return - -//Returns TRUE if the world was launched under the server tools and the API matches, FALSE otherwise -//No function below this succeeds if it returns FALSE -/world/proc/TgsAvailable() - return - -//Gets the current /datum/tgs_version of the server tools running the server -/world/proc/TgsVersion() - return - -//Gets the name of the TGS instance running the game -/world/proc/TgsInstanceName() - return - -//Get the current `/datum/tgs_revision_information` -/world/proc/TgsRevision() - return - -//Get the current BYOND security level -/world/proc/TgsSecurityLevel() - return - -//Gets a list of active `/datum/tgs_revision_information/test_merge`s -/world/proc/TgsTestMerges() - return - -//Forces a hard reboot of BYOND by ending the process -//unlike del(world) clients will try to reconnect -//If the service has not requested a shutdown, the next server will take over -/world/proc/TgsEndProcess() - return - -//Gets a list of connected tgs_chat_channel -/world/proc/TgsChatChannelInfo() - return - -//Sends a message to connected game chats -//message: The message to send -//channels: optional channels to limit the broadcast to -/world/proc/TgsChatBroadcast(message, list/channels) - return - -//Send a message to non-admin connected chats -//message: The message to send -//admin_only: If TRUE, message will instead be sent to only admin connected chats -/world/proc/TgsTargetedChatBroadcast(message, admin_only) - return - -//Send a private message to a specific user -//message: The message to send -//user: The /datum/tgs_chat_user to send to -/world/proc/TgsChatPrivateMessage(message, datum/tgs_chat_user/user) - return - -/* -The MIT License - -Copyright (c) 2017 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. -*/ +// tgstation-server DMAPI + +#define TGS_DMAPI_VERSION "5.2.3" + +// All functions and datums outside this document are subject to change with any version and should not be relied on. + +// CONFIGURATION + +/// Create this define if you want to do TGS configuration outside of this file. +#ifndef TGS_EXTERNAL_CONFIGURATION + +// Comment this out once you've filled in the below. +#error TGS API unconfigured + +// Uncomment this if you wish to allow the game to interact with TGS 3. +// This will raise the minimum required security level of your game to TGS_SECURITY_TRUSTED due to it utilizing call()() +//#define TGS_V3_API + +// Required interfaces (fill in with your codebase equivalent): + +/// Create a global variable named `Name` and set it to `Value`. +#define TGS_DEFINE_AND_SET_GLOBAL(Name, Value) + +/// Read the value in the global variable `Name`. +#define TGS_READ_GLOBAL(Name) + +/// Set the value in the global variable `Name` to `Value`. +#define TGS_WRITE_GLOBAL(Name, Value) + +/// Disallow ANYONE from reflecting a given `path`, security measure to prevent in-game use of DD -> TGS capabilities. +#define TGS_PROTECT_DATUM(Path) + +/// Display an announcement `message` from the server to all players. +#define TGS_WORLD_ANNOUNCE(message) + +/// Notify current in-game administrators of a string `event`. +#define TGS_NOTIFY_ADMINS(event) + +/// Write an info `message` to a server log. +#define TGS_INFO_LOG(message) + +/// Write an warning `message` to a server log. +#define TGS_WARNING_LOG(message) + +/// Write an error `message` to a server log. +#define TGS_ERROR_LOG(message) + +/// Get the number of connected /clients. +#define TGS_CLIENT_COUNT + +#endif + +// EVENT CODES + +/// Before a reboot mode change, extras parameters are the current and new reboot mode enums +#define TGS_EVENT_REBOOT_MODE_CHANGE -1 +/// Before a port change is about to happen, extra parameters is new port +#define TGS_EVENT_PORT_SWAP -2 +/// Before the instance is renamed, extra parameter is the new name +#define TGS_EVENT_INSTANCE_RENAMED -3 +/// After the watchdog reattaches to DD, extra parameter is the new [/datum/tgs_version] of the server +#define TGS_EVENT_WATCHDOG_REATTACH -4 + +/// When the repository is reset to its origin reference. Parameters: Reference name, Commit SHA +#define TGS_EVENT_REPO_RESET_ORIGIN 0 +/// When the repository performs a checkout. Parameters: Checkout git object +#define TGS_EVENT_REPO_CHECKOUT 1 +/// When the repository performs a fetch operation. No parameters +#define TGS_EVENT_REPO_FETCH 2 +/// When the repository merges a pull request. Parameters: PR Number, PR Sha, (Nullable) Comment made by TGS user +#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 +/// Before a BYOND install operation begins. Parameters: [/datum/tgs_version] of the installing BYOND +#define TGS_EVENT_BYOND_INSTALL_START 5 +/// When a BYOND install operation fails. Parameters: Error message +#define TGS_EVENT_BYOND_INSTALL_FAIL 6 +/// When the active BYOND version changes. Parameters: (Nullable) [/datum/tgs_version] of the current BYOND, [/datum/tgs_version] of the new BYOND +#define TGS_EVENT_BYOND_ACTIVE_VERSION_CHANGE 7 +/// When the compiler starts running. Parameters: Game directory path, origin commit SHA +#define TGS_EVENT_COMPILE_START 8 +/// When a compile is cancelled. No parameters +#define TGS_EVENT_COMPILE_CANCELLED 9 +/// When a compile fails. Parameters: Game directory path, [TRUE]/[FALSE] based on if the cause for failure was DMAPI validation +#define TGS_EVENT_COMPILE_FAILURE 10 +/// When a compile operation completes. Note, this event fires before the new .dmb is loaded into the watchdog. Consider using the [TGS_EVENT_DEPLOYMENT_COMPLETE] instead. Parameters: Game directory path +#define TGS_EVENT_COMPILE_COMPLETE 11 +/// When an automatic update for the current instance begins. No parameters +#define TGS_EVENT_INSTANCE_AUTO_UPDATE_START 12 +/// When the repository encounters a merge conflict: Parameters: Base SHA, target SHA, base reference, target reference +#define TGS_EVENT_REPO_MERGE_CONFLICT 13 +/// When a deployment completes. No Parameters +#define TGS_EVENT_DEPLOYMENT_COMPLETE 14 +/// Before the watchdog shuts down. Not sent for graceful shutdowns. No parameters. +#define TGS_EVENT_WATCHDOG_SHUTDOWN 15 +/// Before the watchdog detaches for a TGS update/restart. No parameters. +#define TGS_EVENT_WATCHDOG_DETACH 16 +// We don't actually implement this value as the DMAPI can never receive it +// #define TGS_EVENT_WATCHDOG_LAUNCH 17 + +// OTHER ENUMS + +/// The server will reboot normally. +#define TGS_REBOOT_MODE_NORMAL 0 +/// The server will stop running on reboot. +#define TGS_REBOOT_MODE_SHUTDOWN 1 +/// The watchdog will restart on reboot. +#define TGS_REBOOT_MODE_RESTART 2 + +/// DreamDaemon Trusted security level. +#define TGS_SECURITY_TRUSTED 0 +/// DreamDaemon Safe security level. +#define TGS_SECURITY_SAFE 1 +/// DreamDaemon Ultrasafe security level. +#define TGS_SECURITY_ULTRASAFE 2 + +//REQUIRED HOOKS + +/** + * Call this somewhere in [/world/proc/New] that is always run. This function may sleep! + * + * * event_handler - Optional user defined [/datum/tgs_event_handler]. + * * minimum_required_security_level: The minimum required security level to run the game in which the DMAPI is integrated. Can be one of [TGS_SECURITY_ULTRASAFE], [TGS_SECURITY_SAFE], or [TGS_SECURITY_TRUSTED]. + */ +/world/proc/TgsNew(datum/tgs_event_handler/event_handler, minimum_required_security_level = TGS_SECURITY_ULTRASAFE) + return + +/** + * Call this when your initializations are complete and your game is ready to play before any player interactions happen. + * + * This may use [/world/var/sleep_offline] to make this happen so ensure no changes are made to it while this call is running. + * Afterwards, consider explicitly setting it to what you want to avoid this BYOND bug: http://www.byond.com/forum/post/2575184 + * Before this point, note that any static files or directories may be in use by another server. Your code should account for this. + * This function should not be called before ..() in [/world/proc/New]. + */ +/world/proc/TgsInitializationComplete() + return + +/// Put this at the start of [/world/proc/Topic]. +#define TGS_TOPIC var/tgs_topic_return = TgsTopic(args[1]); if(tgs_topic_return) return tgs_topic_return + +/** + * Call this at the beginning of [world/proc/Reboot]. + */ +/world/proc/TgsReboot() + return + +// DATUM DEFINITIONS +// All datums defined here should be considered read-only + +/// Represents git revision information. +/datum/tgs_revision_information + /// Full SHA of the commit. + var/commit + /// Full sha of last known remote commit. This may be null if the TGS repository is not currently tracking a remote branch. + var/origin_commit + +/// Represents a version. +/datum/tgs_version + /// The suite/major version number + var/suite + + // This group of variables can be null to represent a wild card + /// The minor version number. null for wildcards + var/minor + /// The patch version number. null for wildcards + var/patch + + /// Legacy version number. Generally null + var/deprecated_patch + + /// Unparsed string value + var/raw_parameter + /// String value minus prefix + var/deprefixed_parameter + +/** + * Returns [TRUE]/[FALSE] based on if the [/datum/tgs_version] contains wildcards. + */ +/datum/tgs_version/proc/Wildcard() + return + +/** + * Returns [TRUE]/[FALSE] based on if the [/datum/tgs_version] equals some other version. + * + * other_version - The [/datum/tgs_version] to compare against. + */ +/datum/tgs_version/proc/Equals(datum/tgs_version/other_version) + return + +/// Represents a merge of a GitHub pull request. +/datum/tgs_revision_information/test_merge + /// The pull request number. + var/number + /// The pull request title when it was merged. + var/title + /// The pull request body when it was merged. + var/body + /// The GitHub username of the pull request's author. + var/author + /// An http URL to the pull request. + var/url + /// The SHA of the pull request when that was merged. + var/pull_request_commit + /// ISO 8601 timestamp of when the pull request was merged. + var/time_merged + /// (Nullable) Comment left by the TGS user who initiated the merge.. + var/comment + +/// Represents a connected chat channel. +/datum/tgs_chat_channel + /// TGS internal channel ID. + var/id + /// User friendly name of the channel. + var/friendly_name + /// Name of the chat connection. This is the IRC server address or the Discord guild. + var/connection_name + /// [TRUE]/[FALSE] based on if the server operator has marked this channel for game admins only. + var/is_admin_channel + /// [TRUE]/[FALSE] if the channel is a private message channel for a [/datum/tgs_chat_user]. + var/is_private_channel + /// Tag string associated with the channel in TGS + var/custom_tag + +// Represents a chat user +/datum/tgs_chat_user + /// TGS internal user ID. + var/id + // The user's display name. + var/friendly_name + // The string to use to ping this user in a message. + var/mention + /// The [/datum/tgs_chat_channel] the user was from + var/datum/tgs_chat_channel/channel + +/** + * User definable callback for handling TGS events. + * + * event_code - One of the TGS_EVENT_ defines. Extra parameters will be documented in each + */ +/datum/tgs_event_handler/proc/HandleEvent(event_code, ...) + set waitfor = FALSE + return + +/// User definable chat command +/datum/tgs_chat_command + /// The string to trigger this command on a chat bot. e.g `@bot name ...` or `!tgs name ...` + var/name = "" + /// The help text displayed for this command + var/help_text = "" + /// If this command should be available to game administrators only + var/admin_only = FALSE + +/** + * Process command activation. Should return a string to respond to the issuer with. + * + * sender - The [/datum/tgs_chat_user] who issued the command. + * params - The trimmed string following the command `/datum/tgs_chat_command/var/name]. + */ +/datum/tgs_chat_command/proc/Run(datum/tgs_chat_user/sender, params) + CRASH("[type] has no implementation for Run()") + +// API FUNCTIONS + +/// Returns the maximum supported [/datum/tgs_version] of the DMAPI. +/world/proc/TgsMaximumAPIVersion() + return + +/// Returns the minimum supported [/datum/tgs_version] of the DMAPI. +/world/proc/TgsMinimumAPIVersion() + return + +/** + * Returns [TRUE] if DreamDaemon was launched under TGS, the API matches, and was properly initialized. [FALSE] will be returned otherwise. + */ +/world/proc/TgsAvailable() + return + +// No function below this succeeds if it TgsAvailable() returns FALSE or if TgsNew() has yet to be called. + +/** + * Forces a hard reboot of DreamDaemon by ending the process. + * + * Unlike del(world) clients will try to reconnect. + * If TGS has not requested a [TGS_REBOOT_MODE_SHUTDOWN] DreamDaemon will be launched again + */ +/world/proc/TgsEndProcess() + return + +/** + * Send a message to connected chats. + * + * message - The string to send. + * admin_only: If [TRUE], message will be sent to admin connected chats. Vice-versa applies. + */ +/world/proc/TgsTargetedChatBroadcast(message, admin_only = FALSE) + return + +/** + * Send a private message to a specific user. + * + * message - The string to send. + * user: The [/datum/tgs_chat_user] to PM. + */ +/world/proc/TgsChatPrivateMessage(message, datum/tgs_chat_user/user) + return + +// The following functions will sleep if a call to TgsNew() is sleeping + +/** + * Send a message to connected chats that are flagged as game-related in TGS. + * + * message - The string to send. + * channels - Optional list of [/datum/tgs_chat_channel]s to restrict the message to. + */ +/world/proc/TgsChatBroadcast(message, list/channels = null) + return + +/// Returns the current [/datum/tgs_version] of TGS if it is running the server, null otherwise. +/world/proc/TgsVersion() + return + +/// Returns the current [/datum/tgs_version] of the DMAPI being used if it was activated, null otherwise. +/world/proc/TgsApiVersion() + return + +/// Returns the name of the TGS instance running the game if TGS is present, null otherwise. +/world/proc/TgsInstanceName() + return + +/// Return the current [/datum/tgs_revision_information] of the running server if TGS is present, null otherwise. +/world/proc/TgsRevision() + return + +/// Returns the current BYOND security level as a TGS_SECURITY_ define if TGS is present, null otherwise. +/world/proc/TgsSecurityLevel() + return + +/// Returns a list of active [/datum/tgs_revision_information/test_merge]s if TGS is present, null otherwise. +/world/proc/TgsTestMerges() + return + +/// Returns a list of connected [/datum/tgs_chat_channel]s if TGS is present, null otherwise. +/world/proc/TgsChatChannelInfo() + return + +/* +The MIT License + +Copyright (c) 2017 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/tgui.dm b/code/__DEFINES/tgui.dm index 1b3925f43a78..f5adeadadeb3 100644 --- a/code/__DEFINES/tgui.dm +++ b/code/__DEFINES/tgui.dm @@ -1,4 +1,28 @@ -#define UI_INTERACTIVE 2 // Green/Interactive -#define UI_UPDATE 1 // Orange/Updates Only -#define UI_DISABLED 0 // Red/Disabled -#define UI_CLOSE -1 // Closed +/// Green eye; fully interactive +#define UI_INTERACTIVE 2 +/// Orange eye; updates but is not interactive +#define UI_UPDATE 1 +/// Red eye; disabled, does not update +#define UI_DISABLED 0 +/// UI Should close +#define UI_CLOSE -1 + +/// Maximum number of windows that can be suspended/reused +#define TGUI_WINDOW_SOFT_LIMIT 5 +/// Maximum number of open windows +#define TGUI_WINDOW_HARD_LIMIT 9 + +/// Maximum ping timeout allowed to detect zombie windows +#define TGUI_PING_TIMEOUT 4 SECONDS + +/// Window does not exist +#define TGUI_WINDOW_CLOSED 0 +/// Window was just opened, but is still not ready to be sent data +#define TGUI_WINDOW_LOADING 1 +/// Window is free and ready to receive data +#define TGUI_WINDOW_READY 2 + +/// Get a window id based on the provided pool index +#define TGUI_WINDOW_ID(index) "tgui-window-[index]" +/// Get a pool index of the provided window id +#define TGUI_WINDOW_INDEX(window_id) text2num(copytext(window_id, 13)) diff --git a/code/__DEFINES/tools.dm b/code/__DEFINES/tools.dm index 2385387d2d01..14cffd4ccd4a 100644 --- a/code/__DEFINES/tools.dm +++ b/code/__DEFINES/tools.dm @@ -1,20 +1,20 @@ -// Tool types -#define TOOL_CROWBAR "crowbar" -#define TOOL_MULTITOOL "multitool" -#define TOOL_SCREWDRIVER "screwdriver" -#define TOOL_WIRECUTTER "wirecutter" -#define TOOL_WRENCH "wrench" -#define TOOL_WELDER "welder" -#define TOOL_ANALYZER "analyzer" -#define TOOL_MINING "mining" -#define TOOL_SHOVEL "shovel" -#define TOOL_RETRACTOR "retractor" -#define TOOL_HEMOSTAT "hemostat" -#define TOOL_CAUTERY "cautery" -#define TOOL_DRILL "drill" -#define TOOL_SCALPEL "scalpel" -#define TOOL_SAW "saw" - -// If delay between the start and the end of tool operation is less than MIN_TOOL_SOUND_DELAY, -// tool sound is only played when op is started. If not, it's played twice. -#define MIN_TOOL_SOUND_DELAY 20 +// Tool types +#define TOOL_CROWBAR "crowbar" +#define TOOL_MULTITOOL "multitool" +#define TOOL_SCREWDRIVER "screwdriver" +#define TOOL_WIRECUTTER "wirecutter" +#define TOOL_WRENCH "wrench" +#define TOOL_WELDER "welder" +#define TOOL_ANALYZER "analyzer" +#define TOOL_MINING "mining" +#define TOOL_SHOVEL "shovel" +#define TOOL_RETRACTOR "retractor" +#define TOOL_HEMOSTAT "hemostat" +#define TOOL_CAUTERY "cautery" +#define TOOL_DRILL "drill" +#define TOOL_SCALPEL "scalpel" +#define TOOL_SAW "saw" + +// If delay between the start and the end of tool operation is less than MIN_TOOL_SOUND_DELAY, +// tool sound is only played when op is started. If not, it's played twice. +#define MIN_TOOL_SOUND_DELAY 20 diff --git a/code/__DEFINES/vv.dm b/code/__DEFINES/vv.dm index 956106c7ec10..430d5dd230fd 100644 --- a/code/__DEFINES/vv.dm +++ b/code/__DEFINES/vv.dm @@ -75,6 +75,7 @@ #define VV_HK_MARK "mark" #define VV_HK_ADDCOMPONENT "addcomponent" #define VV_HK_MODIFY_TRAITS "modtraits" +#define VV_HK_VIEW_REFERENCES "viewreferences" // /atom #define VV_HK_MODIFY_TRANSFORM "atom_transform" diff --git a/code/__DEFINES/wires.dm b/code/__DEFINES/wires.dm index 202b1bfc1ad6..3df50625185d 100644 --- a/code/__DEFINES/wires.dm +++ b/code/__DEFINES/wires.dm @@ -1,54 +1,54 @@ -//retvals for attempt_wires_interaction -#define WIRE_INTERACTION_FAIL 0 -#define WIRE_INTERACTION_SUCCESSFUL 1 -#define WIRE_INTERACTION_BLOCK 2 //don't do anything else rather than open wires and whatever else. - -#define WIRE_DUD_PREFIX "__dud" -#define WIRE_ACTIVATE "Activate" -#define WIRE_AI "AI Connection" -#define WIRE_ALARM "Alarm" -#define WIRE_AVOIDANCE "Avoidance" -#define WIRE_BACKUP1 "Auxiliary Power 1" -#define WIRE_BACKUP2 "Auxiliary Power 2" -#define WIRE_BEACON "Beacon" -#define WIRE_BOLTS "Bolts" -#define WIRE_BOOM "Boom" -#define WIRE_CAMERA "Camera" -#define WIRE_CONTRABAND "Contraband" -#define WIRE_DELAY "Delay" -#define WIRE_DISABLE "Disable" -#define WIRE_DISARM "Disarm" -#define WIRE_HACK "Hack" -#define WIRE_IDSCAN "ID Scan" -#define WIRE_INTERFACE "Interface" -#define WIRE_LAWSYNC "AI Law Synchronization" -#define WIRE_LIGHT "Bolt Lights" -#define WIRE_LIMIT "Limiter" -#define WIRE_LOADCHECK "Load Check" -#define WIRE_LOCKDOWN "Lockdown" -#define WIRE_MOTOR1 "Motor 1" -#define WIRE_MOTOR2 "Motor 2" -#define WIRE_OPEN "Open" -#define WIRE_PANIC "Panic Siphon" -#define WIRE_POWER "Power" -#define WIRE_POWER1 "Main Power 1" -#define WIRE_POWER2 "Main Power 2" -#define WIRE_PROCEED "Proceed" -#define WIRE_RX "Receive" -#define WIRE_RESET_MODULE "Reset Module" -#define WIRE_SAFETY "Safety" -#define WIRE_SHOCK "High Voltage Ground" -#define WIRE_SIGNAL "Signal" -#define WIRE_SPEAKER "Speaker" -#define WIRE_STRENGTH "Strength" -#define WIRE_THROW "Throw" -#define WIRE_TIMING "Timing" -#define WIRE_TX "Transmit" -#define WIRE_UNBOLT "Unbolt" -#define WIRE_ZAP "High Voltage Circuit" -#define WIRE_ZAP1 "High Voltage Circuit 1" -#define WIRE_ZAP2 "High Voltage Circuit 2" -#define WIRE_PRIZEVEND "Emergency Prize Vend" -#define WIRE_RESETOWNER "Reset Owner" -#define WIRE_AGELIMIT "Age Limit" - +//retvals for attempt_wires_interaction +#define WIRE_INTERACTION_FAIL 0 +#define WIRE_INTERACTION_SUCCESSFUL 1 +#define WIRE_INTERACTION_BLOCK 2 //don't do anything else rather than open wires and whatever else. + +#define WIRE_DUD_PREFIX "__dud" +#define WIRE_ACTIVATE "Activate" +#define WIRE_AI "AI Connection" +#define WIRE_ALARM "Alarm" +#define WIRE_AVOIDANCE "Avoidance" +#define WIRE_BACKUP1 "Auxiliary Power 1" +#define WIRE_BACKUP2 "Auxiliary Power 2" +#define WIRE_BEACON "Beacon" +#define WIRE_BOLTS "Bolts" +#define WIRE_BOOM "Boom" +#define WIRE_CAMERA "Camera" +#define WIRE_CONTRABAND "Contraband" +#define WIRE_DELAY "Delay" +#define WIRE_DISABLE "Disable" +#define WIRE_DISARM "Disarm" +#define WIRE_HACK "Hack" +#define WIRE_IDSCAN "ID Scan" +#define WIRE_INTERFACE "Interface" +#define WIRE_LAWSYNC "AI Law Synchronization" +#define WIRE_LIGHT "Bolt Lights" +#define WIRE_LIMIT "Limiter" +#define WIRE_LOADCHECK "Load Check" +#define WIRE_LOCKDOWN "Lockdown" +#define WIRE_MOTOR1 "Motor 1" +#define WIRE_MOTOR2 "Motor 2" +#define WIRE_OPEN "Open" +#define WIRE_PANIC "Panic Siphon" +#define WIRE_POWER "Power" +#define WIRE_POWER1 "Main Power 1" +#define WIRE_POWER2 "Main Power 2" +#define WIRE_PROCEED "Proceed" +#define WIRE_RX "Receive" +#define WIRE_RESET_MODULE "Reset Module" +#define WIRE_SAFETY "Safety" +#define WIRE_SHOCK "High Voltage Ground" +#define WIRE_SIGNAL "Signal" +#define WIRE_SPEAKER "Speaker" +#define WIRE_STRENGTH "Strength" +#define WIRE_THROW "Throw" +#define WIRE_TIMING "Timing" +#define WIRE_TX "Transmit" +#define WIRE_UNBOLT "Unbolt" +#define WIRE_ZAP "High Voltage Circuit" +#define WIRE_ZAP1 "High Voltage Circuit 1" +#define WIRE_ZAP2 "High Voltage Circuit 2" +#define WIRE_PRIZEVEND "Emergency Prize Vend" +#define WIRE_RESETOWNER "Reset Owner" +#define WIRE_AGELIMIT "Age Limit" + diff --git a/code/__DEFINES/~wasp_defines/jobs.dm b/code/__DEFINES/~wasp_defines/jobs.dm index 2b93c1ef18db..fc6a3b00a0b4 100644 --- a/code/__DEFINES/~wasp_defines/jobs.dm +++ b/code/__DEFINES/~wasp_defines/jobs.dm @@ -1,4 +1,2 @@ -#define LT (1<<25) - #define JOB_DISPLAY_ORDER_LIEUTENANT 3.5 //After HoP #define JOB_DISPLAY_ORDER_BRIG_PHYS 34.5 //After SecOfficer diff --git a/code/__HELPERS/_lists.dm b/code/__HELPERS/_lists.dm index 3126e515d882..ee944ef0b2f1 100644 --- a/code/__HELPERS/_lists.dm +++ b/code/__HELPERS/_lists.dm @@ -1,581 +1,581 @@ -/* - * Holds procs to help with list operations - * Contains groups: - * Misc - * Sorting - */ - -/* - * Misc - */ - -#define LAZYINITLIST(L) if (!L) L = list() -#define UNSETEMPTY(L) if (L && !length(L)) L = null -#define LAZYCOPY(L) (L ? L.Copy() : list() ) -#define LAZYREMOVE(L, I) if(L) { L -= I; if(!length(L)) { L = null; } } -#define LAZYADD(L, I) if(!L) { L = list(); } L += I; -#define LAZYOR(L, I) if(!L) { L = list(); } L |= I; -#define LAZYFIND(L, V) L ? L.Find(V) : 0 -#define LAZYACCESS(L, I) (L ? (isnum(I) ? (I > 0 && I <= length(L) ? L[I] : null) : L[I]) : null) -#define LAZYSET(L, K, V) if(!L) { L = list(); } L[K] = V; -#define LAZYLEN(L) length(L) -#define LAZYCLEARLIST(L) if(L) L.Cut() -#define SANITIZE_LIST(L) ( islist(L) ? L : list() ) -#define reverseList(L) reverseRange(L.Copy()) -#define LAZYADDASSOC(L, K, V) if(!L) { L = list(); } L[K] += list(V); -#define LAZYREMOVEASSOC(L, K, V) if(L) { if(L[K]) { L[K] -= V; if(!length(L[K])) L -= K; } if(!length(L)) L = null; } - -/// Passed into BINARY_INSERT to compare keys -#define COMPARE_KEY __BIN_LIST[__BIN_MID] -/// Passed into BINARY_INSERT to compare values -#define COMPARE_VALUE __BIN_LIST[__BIN_LIST[__BIN_MID]] - -/**** - * Binary search sorted insert - * INPUT: Object to be inserted - * LIST: List to insert object into - * TYPECONT: The typepath of the contents of the list - * COMPARE: The object to compare against, usualy the same as INPUT - * COMPARISON: The variable on the objects to compare - */ -#define BINARY_INSERT(INPUT, LIST, TYPECONT, COMPARE, COMPARISON, COMPTYPE) \ - do {\ - var/list/__BIN_LIST = LIST;\ - var/__BIN_CTTL = length(__BIN_LIST);\ - if(!__BIN_CTTL) {\ - __BIN_LIST += INPUT;\ - } else {\ - var/__BIN_LEFT = 1;\ - var/__BIN_RIGHT = __BIN_CTTL;\ - var/__BIN_MID = (__BIN_LEFT + __BIN_RIGHT) >> 1;\ - var/##TYPECONT/__BIN_ITEM;\ - while(__BIN_LEFT < __BIN_RIGHT) {\ - __BIN_ITEM = COMPTYPE;\ - if(__BIN_ITEM.##COMPARISON <= COMPARE.##COMPARISON) {\ - __BIN_LEFT = __BIN_MID + 1;\ - } else {\ - __BIN_RIGHT = __BIN_MID;\ - };\ - __BIN_MID = (__BIN_LEFT + __BIN_RIGHT) >> 1;\ - };\ - __BIN_ITEM = COMPTYPE;\ - __BIN_MID = __BIN_ITEM.##COMPARISON > COMPARE.##COMPARISON ? __BIN_MID : __BIN_MID + 1;\ - __BIN_LIST.Insert(__BIN_MID, INPUT);\ - };\ - } while(FALSE) - -//Returns a list in plain english as a string -/proc/english_list(list/input, nothing_text = "nothing", and_text = " and ", comma_text = ", ", final_comma_text = "" ) - var/total = length(input) - switch(total) - if (0) - return "[nothing_text]" - if (1) - return "[input[1]]" - if (2) - return "[input[1]][and_text][input[2]]" - else - var/output = "" - var/index = 1 - while (index < total) - if (index == total - 1) - comma_text = final_comma_text - - output += "[input[index]][comma_text]" - index++ - - return "[output][and_text][input[index]]" - -//Checks for specific types in a list -/proc/is_type_in_list(atom/A, list/L) - if(!LAZYLEN(L) || !A) - return FALSE - for(var/type in L) - if(istype(A, type)) - return TRUE - return FALSE - -//Checks for specific types in specifically structured (Assoc "type" = TRUE) lists ('typecaches') -#define is_type_in_typecache(A, L) (A && length(L) && L[(ispath(A) ? A : A:type)]) - -//returns a new list with only atoms that are in typecache L -/proc/typecache_filter_list(list/atoms, list/typecache) - RETURN_TYPE(/list) - . = list() - for(var/thing in atoms) - var/atom/A = thing - if (typecache[A.type]) - . += A - -/proc/typecache_filter_list_reverse(list/atoms, list/typecache) - RETURN_TYPE(/list) - . = list() - for(var/thing in atoms) - var/atom/A = thing - if(!typecache[A.type]) - . += A - -/proc/typecache_filter_multi_list_exclusion(list/atoms, list/typecache_include, list/typecache_exclude) - . = list() - for(var/thing in atoms) - var/atom/A = thing - if(typecache_include[A.type] && !typecache_exclude[A.type]) - . += A - -//Like typesof() or subtypesof(), but returns a typecache instead of a list -/proc/typecacheof(path, ignore_root_path, only_root_path = FALSE) - if(ispath(path)) - var/list/types = list() - if(only_root_path) - types = list(path) - else - types = ignore_root_path ? subtypesof(path) : typesof(path) - var/list/L = list() - for(var/T in types) - L[T] = TRUE - return L - else if(islist(path)) - var/list/pathlist = path - var/list/L = list() - if(ignore_root_path) - for(var/P in pathlist) - for(var/T in subtypesof(P)) - L[T] = TRUE - else - for(var/P in pathlist) - if(only_root_path) - L[P] = TRUE - else - for(var/T in typesof(P)) - L[T] = TRUE - return L - -//Removes any null entries from the list -//Returns TRUE if the list had nulls, FALSE otherwise -/proc/listclearnulls(list/L) - var/start_len = L.len - var/list/N = new(start_len) - L -= N - return L.len < start_len - -/* - * Returns list containing all the entries from first list that are not present in second. - * If skiprep = 1, repeated elements are treated as one. - * If either of arguments is not a list, returns null - */ -/proc/difflist(list/first, list/second, skiprep=0) - if(!islist(first) || !islist(second)) - return - var/list/result = new - if(skiprep) - for(var/e in first) - if(!(e in result) && !(e in second)) - result += e - else - result = first - second - return result - -/* - * Returns list containing entries that are in either list but not both. - * If skipref = 1, repeated elements are treated as one. - * If either of arguments is not a list, returns null - */ -/proc/uniquemergelist(list/first, list/second, skiprep=0) - if(!islist(first) || !islist(second)) - return - var/list/result = new - if(skiprep) - result = difflist(first, second, skiprep)+difflist(second, first, skiprep) - else - result = first ^ second - return result - -//Picks a random element from a list based on a weighting system: -//1. Adds up the total of weights for each element -//2. Gets a number between 1 and that total -//3. For each element in the list, subtracts its weighting from that number -//4. If that makes the number 0 or less, return that element. -/proc/pickweight(list/L) - var/total = 0 - var/item - for (item in L) - if (!L[item]) - L[item] = 1 - total += L[item] - - total = rand(1, total) - for (item in L) - total -=L [item] - if (total <= 0) - return item - - return null - -/proc/pickweightAllowZero(list/L) //The original pickweight proc will sometimes pick entries with zero weight. I'm not sure if changing the original will break anything, so I left it be. - var/total = 0 - var/item - for (item in L) - if (!L[item]) - L[item] = 0 - total += L[item] - - total = rand(0, total) - for (item in L) - total -=L [item] - if (total <= 0 && L[item]) - return item - - return null - -//Pick a random element from the list and remove it from the list. -/proc/pick_n_take(list/L) - RETURN_TYPE(L[_].type) - if(L.len) - var/picked = rand(1,L.len) - . = L[picked] - L.Cut(picked,picked+1) //Cut is far more efficient that Remove() - -//Returns the top(last) element from the list and removes it from the list (typical stack function) -/proc/pop(list/L) - if(L.len) - . = L[L.len] - L.len-- - -/proc/popleft(list/L) - if(L.len) - . = L[1] - L.Cut(1,2) - -/proc/sorted_insert(list/L, thing, comparator) - var/pos = L.len - while(pos > 0 && call(comparator)(thing, L[pos]) > 0) - pos-- - L.Insert(pos+1, thing) - -// Returns the next item in a list -/proc/next_list_item(item, list/L) - var/i - i = L.Find(item) - if(i == L.len) - i = 1 - else - i++ - return L[i] - -// Returns the previous item in a list -/proc/previous_list_item(item, list/L) - var/i - i = L.Find(item) - if(i == 1) - i = L.len - else - i-- - return L[i] - -//Randomize: Return the list in a random order -/proc/shuffle(list/L) - if(!L) - return - L = L.Copy() - - for(var/i=1, i= 0 ? /proc/cmp_ckey_asc : /proc/cmp_ckey_dsc) - -//Specifically for record datums in a list. -/proc/sortRecord(list/L, field = "name", order = 1) - GLOB.cmp_field = field - return sortTim(L, order >= 0 ? /proc/cmp_records_asc : /proc/cmp_records_dsc) - -//any value in a list -/proc/sortList(list/L, cmp=/proc/cmp_text_asc) - return sortTim(L.Copy(), cmp) - -//uses sortList() but uses the var's name specifically. This should probably be using mergeAtom() instead -/proc/sortNames(list/L, order=1) - return sortTim(L.Copy(), order >= 0 ? /proc/cmp_name_asc : /proc/cmp_name_dsc) - -//Mergesort: any value in a list, preserves key=value structure -/proc/sortAssoc(var/list/L) - if(L.len < 2) - return L - var/middle = L.len / 2 + 1 // Copy is first,second-1 - return mergeAssoc(sortAssoc(L.Copy(0,middle)), sortAssoc(L.Copy(middle))) //second parameter null = to end of list - -/proc/mergeAssoc(var/list/L, var/list/R) - var/Li=1 - var/Ri=1 - var/list/result = new() - while(Li <= L.len && Ri <= R.len) - if(sorttext(L[Li], R[Ri]) < 1) - result += R&R[Ri++] - else - result += L&L[Li++] - - if(Li <= L.len) - return (result + L.Copy(Li, 0)) - return (result + R.Copy(Ri, 0)) - -//Converts a bitfield to a list of numbers (or words if a wordlist is provided) -/proc/bitfield2list(bitfield = 0, list/wordlist) - var/list/r = list() - if(islist(wordlist)) - var/max = min(wordlist.len,16) - var/bit = 1 - for(var/i=1, i<=max, i++) - if(bitfield & bit) - r += wordlist[i] - bit = bit << 1 - else - for(var/bit=1, bit<=65535, bit = bit << 1) - if(bitfield & bit) - r += bit - - return r - -// Returns the key based on the index -#define KEYBYINDEX(L, index) (((index <= length(L)) && (index > 0)) ? L[index] : null) - -/proc/count_by_type(list/L, type) - var/i = 0 - for(var/T in L) - if(istype(T, type)) - i++ - return i - -/// Returns datum/data/record -/proc/find_record(field, value, list/L) - for(var/datum/data/record/R in L) - if(R.fields[field] == value) - return R - return FALSE - - -//Move a single element from position fromIndex within a list, to position toIndex -//All elements in the range [1,toIndex) before the move will be before the pivot afterwards -//All elements in the range [toIndex, L.len+1) before the move will be after the pivot afterwards -//In other words, it's as if the range [fromIndex,toIndex) have been rotated using a <<< operation common to other languages. -//fromIndex and toIndex must be in the range [1,L.len+1] -//This will preserve associations ~Carnie -/proc/moveElement(list/L, fromIndex, toIndex) - if(fromIndex == toIndex || fromIndex+1 == toIndex) //no need to move - return - if(fromIndex > toIndex) - ++fromIndex //since a null will be inserted before fromIndex, the index needs to be nudged right by one - - L.Insert(toIndex, null) - L.Swap(fromIndex, toIndex) - L.Cut(fromIndex, fromIndex+1) - - -//Move elements [fromIndex,fromIndex+len) to [toIndex-len, toIndex) -//Same as moveElement but for ranges of elements -//This will preserve associations ~Carnie -/proc/moveRange(list/L, fromIndex, toIndex, len=1) - var/distance = abs(toIndex - fromIndex) - if(len >= distance) //there are more elements to be moved than the distance to be moved. Therefore the same result can be achieved (with fewer operations) by moving elements between where we are and where we are going. The result being, our range we are moving is shifted left or right by dist elements - if(fromIndex <= toIndex) - return //no need to move - fromIndex += len //we want to shift left instead of right - - for(var/i=0, i toIndex) - fromIndex += len - - for(var/i=0, i distance) //there is an overlap, therefore swapping each element will require more swaps than inserting new elements - if(fromIndex < toIndex) - toIndex += len - else - fromIndex += len - - for(var/i=0, i fromIndex) - var/a = toIndex - toIndex = fromIndex - fromIndex = a - - for(var/i=0, i 513 -#error Remie said that lummox was adding a way to get a lists -#error contents via list.values, if that is true remove this -#error otherwise, update the version and bug lummox -#endif -//Flattens a keyed list into a list of it's contents -/proc/flatten_list(list/key_list) - if(!islist(key_list)) - return null - . = list() - for(var/key in key_list) - . |= key_list[key] - -/proc/make_associative(list/flat_list) - . = list() - for(var/thing in flat_list) - .[thing] = TRUE - -//Picks from the list, with some safeties, and returns the "default" arg if it fails -#define DEFAULTPICK(L, default) ((islist(L) && length(L)) ? pick(L) : default) - -/* Definining a counter as a series of key -> numeric value entries - - * All these procs modify in place. -*/ - -/proc/counterlist_scale(list/L, scalar) - var/list/out = list() - for(var/key in L) - out[key] = L[key] * scalar - . = out - -/proc/counterlist_sum(list/L) - . = 0 - for(var/key in L) - . += L[key] - -/proc/counterlist_normalise(list/L) - var/avg = counterlist_sum(L) - if(avg != 0) - . = counterlist_scale(L, 1 / avg) - else - . = L - -/proc/counterlist_combine(list/L1, list/L2) - for(var/key in L2) - var/other_value = L2[key] - if(key in L1) - L1[key] += other_value - else - L1[key] = other_value - -/proc/assoc_list_strip_value(list/input) - var/list/ret = list() - for(var/key in input) - ret += key - return ret - -/proc/compare_list(list/l,list/d) - if(!islist(l) || !islist(d)) - return FALSE - - if(l.len != d.len) - return FALSE - - for(var/i in 1 to l.len) - if(l[i] != d[i]) - return FALSE - - return TRUE +/* + * Holds procs to help with list operations + * Contains groups: + * Misc + * Sorting + */ + +/* + * Misc + */ + +#define LAZYINITLIST(L) if (!L) L = list() +#define UNSETEMPTY(L) if (L && !length(L)) L = null +#define LAZYCOPY(L) (L ? L.Copy() : list() ) +#define LAZYREMOVE(L, I) if(L) { L -= I; if(!length(L)) { L = null; } } +#define LAZYADD(L, I) if(!L) { L = list(); } L += I; +#define LAZYOR(L, I) if(!L) { L = list(); } L |= I; +#define LAZYFIND(L, V) L ? L.Find(V) : 0 +#define LAZYACCESS(L, I) (L ? (isnum(I) ? (I > 0 && I <= length(L) ? L[I] : null) : L[I]) : null) +#define LAZYSET(L, K, V) if(!L) { L = list(); } L[K] = V; +#define LAZYLEN(L) length(L) +#define LAZYCLEARLIST(L) if(L) L.Cut() +#define SANITIZE_LIST(L) ( islist(L) ? L : list() ) +#define reverseList(L) reverseRange(L.Copy()) +#define LAZYADDASSOC(L, K, V) if(!L) { L = list(); } L[K] += list(V); +#define LAZYREMOVEASSOC(L, K, V) if(L) { if(L[K]) { L[K] -= V; if(!length(L[K])) L -= K; } if(!length(L)) L = null; } + +/// Passed into BINARY_INSERT to compare keys +#define COMPARE_KEY __BIN_LIST[__BIN_MID] +/// Passed into BINARY_INSERT to compare values +#define COMPARE_VALUE __BIN_LIST[__BIN_LIST[__BIN_MID]] + +/**** + * Binary search sorted insert + * INPUT: Object to be inserted + * LIST: List to insert object into + * TYPECONT: The typepath of the contents of the list + * COMPARE: The object to compare against, usualy the same as INPUT + * COMPARISON: The variable on the objects to compare + */ +#define BINARY_INSERT(INPUT, LIST, TYPECONT, COMPARE, COMPARISON, COMPTYPE) \ + do {\ + var/list/__BIN_LIST = LIST;\ + var/__BIN_CTTL = length(__BIN_LIST);\ + if(!__BIN_CTTL) {\ + __BIN_LIST += INPUT;\ + } else {\ + var/__BIN_LEFT = 1;\ + var/__BIN_RIGHT = __BIN_CTTL;\ + var/__BIN_MID = (__BIN_LEFT + __BIN_RIGHT) >> 1;\ + var/##TYPECONT/__BIN_ITEM;\ + while(__BIN_LEFT < __BIN_RIGHT) {\ + __BIN_ITEM = COMPTYPE;\ + if(__BIN_ITEM.##COMPARISON <= COMPARE.##COMPARISON) {\ + __BIN_LEFT = __BIN_MID + 1;\ + } else {\ + __BIN_RIGHT = __BIN_MID;\ + };\ + __BIN_MID = (__BIN_LEFT + __BIN_RIGHT) >> 1;\ + };\ + __BIN_ITEM = COMPTYPE;\ + __BIN_MID = __BIN_ITEM.##COMPARISON > COMPARE.##COMPARISON ? __BIN_MID : __BIN_MID + 1;\ + __BIN_LIST.Insert(__BIN_MID, INPUT);\ + };\ + } while(FALSE) + +//Returns a list in plain english as a string +/proc/english_list(list/input, nothing_text = "nothing", and_text = " and ", comma_text = ", ", final_comma_text = "" ) + var/total = length(input) + switch(total) + if (0) + return "[nothing_text]" + if (1) + return "[input[1]]" + if (2) + return "[input[1]][and_text][input[2]]" + else + var/output = "" + var/index = 1 + while (index < total) + if (index == total - 1) + comma_text = final_comma_text + + output += "[input[index]][comma_text]" + index++ + + return "[output][and_text][input[index]]" + +//Checks for specific types in a list +/proc/is_type_in_list(atom/A, list/L) + if(!LAZYLEN(L) || !A) + return FALSE + for(var/type in L) + if(istype(A, type)) + return TRUE + return FALSE + +//Checks for specific types in specifically structured (Assoc "type" = TRUE) lists ('typecaches') +#define is_type_in_typecache(A, L) (A && length(L) && L[(ispath(A) ? A : A:type)]) + +//returns a new list with only atoms that are in typecache L +/proc/typecache_filter_list(list/atoms, list/typecache) + RETURN_TYPE(/list) + . = list() + for(var/thing in atoms) + var/atom/A = thing + if (typecache[A.type]) + . += A + +/proc/typecache_filter_list_reverse(list/atoms, list/typecache) + RETURN_TYPE(/list) + . = list() + for(var/thing in atoms) + var/atom/A = thing + if(!typecache[A.type]) + . += A + +/proc/typecache_filter_multi_list_exclusion(list/atoms, list/typecache_include, list/typecache_exclude) + . = list() + for(var/thing in atoms) + var/atom/A = thing + if(typecache_include[A.type] && !typecache_exclude[A.type]) + . += A + +//Like typesof() or subtypesof(), but returns a typecache instead of a list +/proc/typecacheof(path, ignore_root_path, only_root_path = FALSE) + if(ispath(path)) + var/list/types = list() + if(only_root_path) + types = list(path) + else + types = ignore_root_path ? subtypesof(path) : typesof(path) + var/list/L = list() + for(var/T in types) + L[T] = TRUE + return L + else if(islist(path)) + var/list/pathlist = path + var/list/L = list() + if(ignore_root_path) + for(var/P in pathlist) + for(var/T in subtypesof(P)) + L[T] = TRUE + else + for(var/P in pathlist) + if(only_root_path) + L[P] = TRUE + else + for(var/T in typesof(P)) + L[T] = TRUE + return L + +//Removes any null entries from the list +//Returns TRUE if the list had nulls, FALSE otherwise +/proc/listclearnulls(list/L) + var/start_len = L.len + var/list/N = new(start_len) + L -= N + return L.len < start_len + +/* + * Returns list containing all the entries from first list that are not present in second. + * If skiprep = 1, repeated elements are treated as one. + * If either of arguments is not a list, returns null + */ +/proc/difflist(list/first, list/second, skiprep=0) + if(!islist(first) || !islist(second)) + return + var/list/result = new + if(skiprep) + for(var/e in first) + if(!(e in result) && !(e in second)) + result += e + else + result = first - second + return result + +/* + * Returns list containing entries that are in either list but not both. + * If skipref = 1, repeated elements are treated as one. + * If either of arguments is not a list, returns null + */ +/proc/uniquemergelist(list/first, list/second, skiprep=0) + if(!islist(first) || !islist(second)) + return + var/list/result = new + if(skiprep) + result = difflist(first, second, skiprep)+difflist(second, first, skiprep) + else + result = first ^ second + return result + +//Picks a random element from a list based on a weighting system: +//1. Adds up the total of weights for each element +//2. Gets a number between 1 and that total +//3. For each element in the list, subtracts its weighting from that number +//4. If that makes the number 0 or less, return that element. +/proc/pickweight(list/L) + var/total = 0 + var/item + for (item in L) + if (!L[item]) + L[item] = 1 + total += L[item] + + total = rand(1, total) + for (item in L) + total -=L [item] + if (total <= 0) + return item + + return null + +/proc/pickweightAllowZero(list/L) //The original pickweight proc will sometimes pick entries with zero weight. I'm not sure if changing the original will break anything, so I left it be. + var/total = 0 + var/item + for (item in L) + if (!L[item]) + L[item] = 0 + total += L[item] + + total = rand(0, total) + for (item in L) + total -=L [item] + if (total <= 0 && L[item]) + return item + + return null + +//Pick a random element from the list and remove it from the list. +/proc/pick_n_take(list/L) + RETURN_TYPE(L[_].type) + if(L.len) + var/picked = rand(1,L.len) + . = L[picked] + L.Cut(picked,picked+1) //Cut is far more efficient that Remove() + +//Returns the top(last) element from the list and removes it from the list (typical stack function) +/proc/pop(list/L) + if(L.len) + . = L[L.len] + L.len-- + +/proc/popleft(list/L) + if(L.len) + . = L[1] + L.Cut(1,2) + +/proc/sorted_insert(list/L, thing, comparator) + var/pos = L.len + while(pos > 0 && call(comparator)(thing, L[pos]) > 0) + pos-- + L.Insert(pos+1, thing) + +// Returns the next item in a list +/proc/next_list_item(item, list/L) + var/i + i = L.Find(item) + if(i == L.len) + i = 1 + else + i++ + return L[i] + +// Returns the previous item in a list +/proc/previous_list_item(item, list/L) + var/i + i = L.Find(item) + if(i == 1) + i = L.len + else + i-- + return L[i] + +//Randomize: Return the list in a random order +/proc/shuffle(list/L) + if(!L) + return + L = L.Copy() + + for(var/i=1, i= 0 ? /proc/cmp_ckey_asc : /proc/cmp_ckey_dsc) + +//Specifically for record datums in a list. +/proc/sortRecord(list/L, field = "name", order = 1) + GLOB.cmp_field = field + return sortTim(L, order >= 0 ? /proc/cmp_records_asc : /proc/cmp_records_dsc) + +//any value in a list +/proc/sortList(list/L, cmp=/proc/cmp_text_asc) + return sortTim(L.Copy(), cmp) + +//uses sortList() but uses the var's name specifically. This should probably be using mergeAtom() instead +/proc/sortNames(list/L, order=1) + return sortTim(L.Copy(), order >= 0 ? /proc/cmp_name_asc : /proc/cmp_name_dsc) + +//Mergesort: any value in a list, preserves key=value structure +/proc/sortAssoc(var/list/L) + if(L.len < 2) + return L + var/middle = L.len / 2 + 1 // Copy is first,second-1 + return mergeAssoc(sortAssoc(L.Copy(0,middle)), sortAssoc(L.Copy(middle))) //second parameter null = to end of list + +/proc/mergeAssoc(var/list/L, var/list/R) + var/Li=1 + var/Ri=1 + var/list/result = new() + while(Li <= L.len && Ri <= R.len) + if(sorttext(L[Li], R[Ri]) < 1) + result += R&R[Ri++] + else + result += L&L[Li++] + + if(Li <= L.len) + return (result + L.Copy(Li, 0)) + return (result + R.Copy(Ri, 0)) + +//Converts a bitfield to a list of numbers (or words if a wordlist is provided) +/proc/bitfield2list(bitfield = 0, list/wordlist) + var/list/r = list() + if(islist(wordlist)) + var/max = min(wordlist.len,16) + var/bit = 1 + for(var/i=1, i<=max, i++) + if(bitfield & bit) + r += wordlist[i] + bit = bit << 1 + else + for(var/bit=1, bit<=65535, bit = bit << 1) + if(bitfield & bit) + r += bit + + return r + +// Returns the key based on the index +#define KEYBYINDEX(L, index) (((index <= length(L)) && (index > 0)) ? L[index] : null) + +/proc/count_by_type(list/L, type) + var/i = 0 + for(var/T in L) + if(istype(T, type)) + i++ + return i + +/// Returns datum/data/record +/proc/find_record(field, value, list/L) + for(var/datum/data/record/R in L) + if(R.fields[field] == value) + return R + return FALSE + + +//Move a single element from position fromIndex within a list, to position toIndex +//All elements in the range [1,toIndex) before the move will be before the pivot afterwards +//All elements in the range [toIndex, L.len+1) before the move will be after the pivot afterwards +//In other words, it's as if the range [fromIndex,toIndex) have been rotated using a <<< operation common to other languages. +//fromIndex and toIndex must be in the range [1,L.len+1] +//This will preserve associations ~Carnie +/proc/moveElement(list/L, fromIndex, toIndex) + if(fromIndex == toIndex || fromIndex+1 == toIndex) //no need to move + return + if(fromIndex > toIndex) + ++fromIndex //since a null will be inserted before fromIndex, the index needs to be nudged right by one + + L.Insert(toIndex, null) + L.Swap(fromIndex, toIndex) + L.Cut(fromIndex, fromIndex+1) + + +//Move elements [fromIndex,fromIndex+len) to [toIndex-len, toIndex) +//Same as moveElement but for ranges of elements +//This will preserve associations ~Carnie +/proc/moveRange(list/L, fromIndex, toIndex, len=1) + var/distance = abs(toIndex - fromIndex) + if(len >= distance) //there are more elements to be moved than the distance to be moved. Therefore the same result can be achieved (with fewer operations) by moving elements between where we are and where we are going. The result being, our range we are moving is shifted left or right by dist elements + if(fromIndex <= toIndex) + return //no need to move + fromIndex += len //we want to shift left instead of right + + for(var/i=0, i toIndex) + fromIndex += len + + for(var/i=0, i distance) //there is an overlap, therefore swapping each element will require more swaps than inserting new elements + if(fromIndex < toIndex) + toIndex += len + else + fromIndex += len + + for(var/i=0, i fromIndex) + var/a = toIndex + toIndex = fromIndex + fromIndex = a + + for(var/i=0, i 513 +#error Remie said that lummox was adding a way to get a lists +#error contents via list.values, if that is true remove this +#error otherwise, update the version and bug lummox +#endif +//Flattens a keyed list into a list of it's contents +/proc/flatten_list(list/key_list) + if(!islist(key_list)) + return null + . = list() + for(var/key in key_list) + . |= key_list[key] + +/proc/make_associative(list/flat_list) + . = list() + for(var/thing in flat_list) + .[thing] = TRUE + +//Picks from the list, with some safeties, and returns the "default" arg if it fails +#define DEFAULTPICK(L, default) ((islist(L) && length(L)) ? pick(L) : default) + +/* Definining a counter as a series of key -> numeric value entries + + * All these procs modify in place. +*/ + +/proc/counterlist_scale(list/L, scalar) + var/list/out = list() + for(var/key in L) + out[key] = L[key] * scalar + . = out + +/proc/counterlist_sum(list/L) + . = 0 + for(var/key in L) + . += L[key] + +/proc/counterlist_normalise(list/L) + var/avg = counterlist_sum(L) + if(avg != 0) + . = counterlist_scale(L, 1 / avg) + else + . = L + +/proc/counterlist_combine(list/L1, list/L2) + for(var/key in L2) + var/other_value = L2[key] + if(key in L1) + L1[key] += other_value + else + L1[key] = other_value + +/proc/assoc_list_strip_value(list/input) + var/list/ret = list() + for(var/key in input) + ret += key + return ret + +/proc/compare_list(list/l,list/d) + if(!islist(l) || !islist(d)) + return FALSE + + if(l.len != d.len) + return FALSE + + for(var/i in 1 to l.len) + if(l[i] != d[i]) + return FALSE + + return TRUE diff --git a/code/__HELPERS/_logging.dm b/code/__HELPERS/_logging.dm index e70f0128f0a7..677027d880fa 100644 --- a/code/__HELPERS/_logging.dm +++ b/code/__HELPERS/_logging.dm @@ -211,9 +211,18 @@ WRITE_LOG(GLOB.world_map_error_log, text) /* ui logging */ - -/proc/log_tgui(text) - WRITE_LOG(GLOB.tgui_log, text) +/proc/log_tgui(user_or_client, text) + var/entry = "" + if(!user_or_client) + entry += "no user" + else if(istype(user_or_client, /mob)) + var/mob/user = user_or_client + entry += "[user.ckey] (as [user])" + else if(istype(user_or_client, /client)) + var/client/client = user_or_client + entry += "[client.ckey]" + entry += ":\n[text]" + WRITE_LOG(GLOB.tgui_log, entry) /* For logging round startup. */ /proc/start_log(log) diff --git a/code/__HELPERS/_string_lists.dm b/code/__HELPERS/_string_lists.dm index 32c2dc213ec8..cdbee26f9ba7 100644 --- a/code/__HELPERS/_string_lists.dm +++ b/code/__HELPERS/_string_lists.dm @@ -1,42 +1,42 @@ -#define pick_list(FILE, KEY) (pick(strings(FILE, KEY))) -#define pick_list_weighted(FILE, KEY) (pickweight(strings(FILE, KEY))) -#define pick_list_replacements(FILE, KEY) (strings_replacement(FILE, KEY)) -#define json_load(FILE) (json_decode(file2text(FILE))) - -GLOBAL_LIST(string_cache) -GLOBAL_VAR(string_filename_current_key) - - -/proc/strings_replacement(filename, key, directory = "strings") - load_strings_file(filename, directory) - - if((filename in GLOB.string_cache) && (key in GLOB.string_cache[filename])) - var/response = pick(GLOB.string_cache[filename][key]) - var/regex/r = regex("@pick\\((\\D+?)\\)", "g") - response = r.Replace(response, /proc/strings_subkey_lookup) - return response - else - CRASH("strings list not found: [directory]/[filename], index=[key]") - -/proc/strings(filename as text, key as text, directory = "strings") - load_strings_file(filename, directory) - if((filename in GLOB.string_cache) && (key in GLOB.string_cache[filename])) - return GLOB.string_cache[filename][key] - else - CRASH("strings list not found: [directory]/[filename], index=[key]") - -/proc/strings_subkey_lookup(match, group1) - return pick_list(GLOB.string_filename_current_key, group1) - -/proc/load_strings_file(filename, directory = "strings") - GLOB.string_filename_current_key = filename - if(filename in GLOB.string_cache) - return //no work to do - - if(!GLOB.string_cache) - GLOB.string_cache = new - - if(fexists("[directory]/[filename]")) - GLOB.string_cache[filename] = json_load("[directory]/[filename]") - else - CRASH("file not found: [directory]/[filename]") +#define pick_list(FILE, KEY) (pick(strings(FILE, KEY))) +#define pick_list_weighted(FILE, KEY) (pickweight(strings(FILE, KEY))) +#define pick_list_replacements(FILE, KEY) (strings_replacement(FILE, KEY)) +#define json_load(FILE) (json_decode(file2text(FILE))) + +GLOBAL_LIST(string_cache) +GLOBAL_VAR(string_filename_current_key) + + +/proc/strings_replacement(filename, key, directory = "strings") + load_strings_file(filename, directory) + + if((filename in GLOB.string_cache) && (key in GLOB.string_cache[filename])) + var/response = pick(GLOB.string_cache[filename][key]) + var/regex/r = regex("@pick\\((\\D+?)\\)", "g") + response = r.Replace(response, /proc/strings_subkey_lookup) + return response + else + CRASH("strings list not found: [directory]/[filename], index=[key]") + +/proc/strings(filename as text, key as text, directory = "strings") + load_strings_file(filename, directory) + if((filename in GLOB.string_cache) && (key in GLOB.string_cache[filename])) + return GLOB.string_cache[filename][key] + else + CRASH("strings list not found: [directory]/[filename], index=[key]") + +/proc/strings_subkey_lookup(match, group1) + return pick_list(GLOB.string_filename_current_key, group1) + +/proc/load_strings_file(filename, directory = "strings") + GLOB.string_filename_current_key = filename + if(filename in GLOB.string_cache) + return //no work to do + + if(!GLOB.string_cache) + GLOB.string_cache = new + + if(fexists("[directory]/[filename]")) + GLOB.string_cache[filename] = json_load("[directory]/[filename]") + else + CRASH("file not found: [directory]/[filename]") diff --git a/code/__HELPERS/chat.dm b/code/__HELPERS/chat.dm new file mode 100644 index 000000000000..2f958eeed0f1 --- /dev/null +++ b/code/__HELPERS/chat.dm @@ -0,0 +1,72 @@ +/* + +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) + category = replacetext(replacetext(category, "\proper", ""), "\improper", "") + message = replacetext(replacetext(message, "\proper", ""), "\improper", "") + world.TgsTargetedChatBroadcast("[category] | [message]", TRUE) diff --git a/code/__HELPERS/files.dm b/code/__HELPERS/files.dm index 623ed18a25c5..521ce0d08e24 100644 --- a/code/__HELPERS/files.dm +++ b/code/__HELPERS/files.dm @@ -1,80 +1,80 @@ -//Sends resource files to client cache -/client/proc/getFiles(...) - for(var/file in args) - src << browse_rsc(file) - -/client/proc/browse_files(root_type=BROWSE_ROOT_ALL_LOGS, max_iterations=10, list/valid_extensions=list("txt","log","htm", "html")) - // wow why was this ever a parameter - var/root = "data/logs/" - switch(root_type) - if(BROWSE_ROOT_ALL_LOGS) - root = "data/logs/" - if(BROWSE_ROOT_CURRENT_LOGS) - root = "[GLOB.log_directory]/" - var/path = root - - for(var/i=0, iError: browse_files(): File not found/Invalid file([path]).") - return - - return path - -#define FTPDELAY 200 //200 tick delay to discourage spam -#define ADMIN_FTPDELAY_MODIFIER 0.5 //Admins get to spam files faster since we ~trust~ them! -/* This proc is a failsafe to prevent spamming of file requests. - It is just a timer that only permits a download every [FTPDELAY] ticks. - This can be changed by modifying FTPDELAY's value above. - - PLEASE USE RESPONSIBLY, Some log files can reach sizes of 4MB! */ -/client/proc/file_spam_check() - var/time_to_wait = GLOB.fileaccess_timer - world.time - if(time_to_wait > 0) - to_chat(src, "Error: file_spam_check(): Spam. Please wait [DisplayTimeText(time_to_wait)].") - return 1 - var/delay = FTPDELAY - if(holder) - delay *= ADMIN_FTPDELAY_MODIFIER - GLOB.fileaccess_timer = world.time + delay - return 0 -#undef FTPDELAY -#undef ADMIN_FTPDELAY_MODIFIER - -/proc/pathwalk(path) - var/list/jobs = list(path) - var/list/filenames = list() - - while(jobs.len) - var/current_dir = pop(jobs) - var/list/new_filenames = flist(current_dir) - for(var/new_filename in new_filenames) - // if filename ends in / it is a directory, append to currdir - if(findtext(new_filename, "/", -1)) - jobs += current_dir + new_filename - else - filenames += current_dir + new_filename - return filenames - -/proc/pathflatten(path) - return replacetext(path, "/", "_") +//Sends resource files to client cache +/client/proc/getFiles(...) + for(var/file in args) + src << browse_rsc(file) + +/client/proc/browse_files(root_type=BROWSE_ROOT_ALL_LOGS, max_iterations=10, list/valid_extensions=list("txt","log","htm", "html")) + // wow why was this ever a parameter + var/root = "data/logs/" + switch(root_type) + if(BROWSE_ROOT_ALL_LOGS) + root = "data/logs/" + if(BROWSE_ROOT_CURRENT_LOGS) + root = "[GLOB.log_directory]/" + var/path = root + + for(var/i=0, iError: browse_files(): File not found/Invalid file([path]).") + return + + return path + +#define FTPDELAY 200 //200 tick delay to discourage spam +#define ADMIN_FTPDELAY_MODIFIER 0.5 //Admins get to spam files faster since we ~trust~ them! +/* This proc is a failsafe to prevent spamming of file requests. + It is just a timer that only permits a download every [FTPDELAY] ticks. + This can be changed by modifying FTPDELAY's value above. + + PLEASE USE RESPONSIBLY, Some log files can reach sizes of 4MB! */ +/client/proc/file_spam_check() + var/time_to_wait = GLOB.fileaccess_timer - world.time + if(time_to_wait > 0) + to_chat(src, "Error: file_spam_check(): Spam. Please wait [DisplayTimeText(time_to_wait)].") + return 1 + var/delay = FTPDELAY + if(holder) + delay *= ADMIN_FTPDELAY_MODIFIER + GLOB.fileaccess_timer = world.time + delay + return 0 +#undef FTPDELAY +#undef ADMIN_FTPDELAY_MODIFIER + +/proc/pathwalk(path) + var/list/jobs = list(path) + var/list/filenames = list() + + while(jobs.len) + var/current_dir = pop(jobs) + var/list/new_filenames = flist(current_dir) + for(var/new_filename in new_filenames) + // if filename ends in / it is a directory, append to currdir + if(findtext(new_filename, "/", -1)) + jobs += current_dir + new_filename + else + filenames += current_dir + new_filename + return filenames + +/proc/pathflatten(path) + return replacetext(path, "/", "_") diff --git a/code/__HELPERS/global_lists.dm b/code/__HELPERS/global_lists.dm index a8a340f2b7e3..b2ab61f8184f 100644 --- a/code/__HELPERS/global_lists.dm +++ b/code/__HELPERS/global_lists.dm @@ -1,90 +1,90 @@ -////////////////////////// -/////Initial Building///// -////////////////////////// - -/proc/make_datum_references_lists() - //hair - init_sprite_accessory_subtypes(/datum/sprite_accessory/hair, GLOB.hairstyles_list, GLOB.hairstyles_male_list, GLOB.hairstyles_female_list) - //facial hair - init_sprite_accessory_subtypes(/datum/sprite_accessory/facial_hair, GLOB.facial_hairstyles_list, GLOB.facial_hairstyles_male_list, GLOB.facial_hairstyles_female_list) - //underwear - init_sprite_accessory_subtypes(/datum/sprite_accessory/underwear, GLOB.underwear_list, GLOB.underwear_m, GLOB.underwear_f) - //undershirt - init_sprite_accessory_subtypes(/datum/sprite_accessory/undershirt, GLOB.undershirt_list, GLOB.undershirt_m, GLOB.undershirt_f) - //socks - init_sprite_accessory_subtypes(/datum/sprite_accessory/socks, GLOB.socks_list) - //bodypart accessories (blizzard intensifies) - init_sprite_accessory_subtypes(/datum/sprite_accessory/body_markings, GLOB.body_markings_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/lizard, GLOB.tails_list_lizard) - init_sprite_accessory_subtypes(/datum/sprite_accessory/tails_animated/lizard, GLOB.animated_tails_list_lizard) - init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/human, GLOB.tails_list_human) - init_sprite_accessory_subtypes(/datum/sprite_accessory/tails_animated/human, GLOB.animated_tails_list_human) - init_sprite_accessory_subtypes(/datum/sprite_accessory/snouts, GLOB.snouts_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/horns,GLOB.horns_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/ears, GLOB.ears_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/wings, GLOB.wings_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/wings_open, GLOB.wings_open_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/frills, GLOB.frills_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/spines, GLOB.spines_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/spines_animated, GLOB.animated_spines_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/legs, GLOB.legs_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/wings, GLOB.r_wings_list,roundstart = TRUE) - init_sprite_accessory_subtypes(/datum/sprite_accessory/caps, GLOB.caps_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_wings, GLOB.moth_wings_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_markings, GLOB.moth_markings_list) - - - //WaspStation Begin - init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_fluff, GLOB.moth_fluff_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/squid_face, GLOB.squid_face_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/ipc_screens, GLOB.ipc_screens_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/ipc_antennas, GLOB.ipc_antennas_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/ipc_chassis, GLOB.ipc_chassis_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_markings, GLOB.moth_markings_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/spider_legs, GLOB.spider_legs_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/spider_spinneret, GLOB.spider_spinneret_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/spider_mandibles, GLOB.spider_mandibles_list) - //WaspStation End - - //Species - for(var/spath in subtypesof(/datum/species)) - var/datum/species/S = new spath() - GLOB.species_list[S.id] = spath - sortList(GLOB.species_list, /proc/cmp_typepaths_asc) - - //Surgeries - for(var/path in subtypesof(/datum/surgery)) - GLOB.surgeries_list += new path() - sortList(GLOB.surgeries_list, /proc/cmp_typepaths_asc) - - //Materials - for(var/path in subtypesof(/datum/material)) - var/datum/material/D = new path() - GLOB.materials_list[D.id] = D - sortList(GLOB.materials_list, /proc/cmp_typepaths_asc) - - // Keybindings - init_keybindings() - - GLOB.emote_list = init_emote_list() - - init_subtypes(/datum/crafting_recipe, GLOB.crafting_recipes) - -//creates every subtype of prototype (excluding prototype) and adds it to list L. -//if no list/L is provided, one is created. -/proc/init_subtypes(prototype, list/L) - if(!istype(L)) - L = list() - for(var/path in subtypesof(prototype)) - L += new path() - return L - -//returns a list of paths to every subtype of prototype (excluding prototype) -//if no list/L is provided, one is created. -/proc/init_paths(prototype, list/L) - if(!istype(L)) - L = list() - for(var/path in subtypesof(prototype)) - L+= path - return L - +////////////////////////// +/////Initial Building///// +////////////////////////// + +/proc/make_datum_references_lists() + //hair + init_sprite_accessory_subtypes(/datum/sprite_accessory/hair, GLOB.hairstyles_list, GLOB.hairstyles_male_list, GLOB.hairstyles_female_list) + //facial hair + init_sprite_accessory_subtypes(/datum/sprite_accessory/facial_hair, GLOB.facial_hairstyles_list, GLOB.facial_hairstyles_male_list, GLOB.facial_hairstyles_female_list) + //underwear + init_sprite_accessory_subtypes(/datum/sprite_accessory/underwear, GLOB.underwear_list, GLOB.underwear_m, GLOB.underwear_f) + //undershirt + init_sprite_accessory_subtypes(/datum/sprite_accessory/undershirt, GLOB.undershirt_list, GLOB.undershirt_m, GLOB.undershirt_f) + //socks + init_sprite_accessory_subtypes(/datum/sprite_accessory/socks, GLOB.socks_list) + //bodypart accessories (blizzard intensifies) + init_sprite_accessory_subtypes(/datum/sprite_accessory/body_markings, GLOB.body_markings_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/lizard, GLOB.tails_list_lizard) + init_sprite_accessory_subtypes(/datum/sprite_accessory/tails_animated/lizard, GLOB.animated_tails_list_lizard) + init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/human, GLOB.tails_list_human) + init_sprite_accessory_subtypes(/datum/sprite_accessory/tails_animated/human, GLOB.animated_tails_list_human) + init_sprite_accessory_subtypes(/datum/sprite_accessory/snouts, GLOB.snouts_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/horns,GLOB.horns_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/ears, GLOB.ears_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/wings, GLOB.wings_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/wings_open, GLOB.wings_open_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/frills, GLOB.frills_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/spines, GLOB.spines_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/spines_animated, GLOB.animated_spines_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/legs, GLOB.legs_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/wings, GLOB.r_wings_list,roundstart = TRUE) + init_sprite_accessory_subtypes(/datum/sprite_accessory/caps, GLOB.caps_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_wings, GLOB.moth_wings_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_markings, GLOB.moth_markings_list) + + + //WaspStation Begin + init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_fluff, GLOB.moth_fluff_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/squid_face, GLOB.squid_face_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/ipc_screens, GLOB.ipc_screens_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/ipc_antennas, GLOB.ipc_antennas_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/ipc_chassis, GLOB.ipc_chassis_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_markings, GLOB.moth_markings_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/spider_legs, GLOB.spider_legs_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/spider_spinneret, GLOB.spider_spinneret_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/spider_mandibles, GLOB.spider_mandibles_list) + //WaspStation End + + //Species + for(var/spath in subtypesof(/datum/species)) + var/datum/species/S = new spath() + GLOB.species_list[S.id] = spath + sortList(GLOB.species_list, /proc/cmp_typepaths_asc) + + //Surgeries + for(var/path in subtypesof(/datum/surgery)) + GLOB.surgeries_list += new path() + sortList(GLOB.surgeries_list, /proc/cmp_typepaths_asc) + + //Materials + for(var/path in subtypesof(/datum/material)) + var/datum/material/D = new path() + GLOB.materials_list[D.id] = D + sortList(GLOB.materials_list, /proc/cmp_typepaths_asc) + + // Keybindings + init_keybindings() + + GLOB.emote_list = init_emote_list() + + init_subtypes(/datum/crafting_recipe, GLOB.crafting_recipes) + +//creates every subtype of prototype (excluding prototype) and adds it to list L. +//if no list/L is provided, one is created. +/proc/init_subtypes(prototype, list/L) + if(!istype(L)) + L = list() + for(var/path in subtypesof(prototype)) + L += new path() + return L + +//returns a list of paths to every subtype of prototype (excluding prototype) +//if no list/L is provided, one is created. +/proc/init_paths(prototype, list/L) + if(!istype(L)) + L = list() + for(var/path in subtypesof(prototype)) + L+= path + return L + diff --git a/code/__HELPERS/icons.dm b/code/__HELPERS/icons.dm index ae1d3c9cc34f..d5bd7d619d75 100644 --- a/code/__HELPERS/icons.dm +++ b/code/__HELPERS/icons.dm @@ -1,1207 +1,1207 @@ -/* -IconProcs README - -A BYOND library for manipulating icons and colors - -by Lummox JR - -version 1.0 - -The IconProcs library was made to make a lot of common icon operations much easier. BYOND's icon manipulation -routines are very capable but some of the advanced capabilities like using alpha transparency can be unintuitive to beginners. - -CHANGING ICONS - -Several new procs have been added to the /icon datum to simplify working with icons. To use them, -remember you first need to setup an /icon var like so: - -GLOBAL_DATUM_INIT(my_icon, /icon, new('iconfile.dmi')) - -icon/ChangeOpacity(amount = 1) - A very common operation in DM is to try to make an icon more or less transparent. Making an icon more - transparent is usually much easier than making it less so, however. This proc basically is a frontend - for MapColors() which can change opacity any way you like, in much the same way that SetIntensity() - can make an icon lighter or darker. If amount is 0.5, the opacity of the icon will be cut in half. - If amount is 2, opacity is doubled and anything more than half-opaque will become fully opaque. -icon/GrayScale() - Converts the icon to grayscale instead of a fully colored icon. Alpha values are left intact. -icon/ColorTone(tone) - Similar to GrayScale(), this proc converts the icon to a range of black -> tone -> white, where tone is an - RGB color (its alpha is ignored). This can be used to create a sepia tone or similar effect. - See also the global ColorTone() proc. -icon/MinColors(icon) - The icon is blended with a second icon where the minimum of each RGB pixel is the result. - Transparency may increase, as if the icons were blended with ICON_ADD. You may supply a color in place of an icon. -icon/MaxColors(icon) - The icon is blended with a second icon where the maximum of each RGB pixel is the result. - Opacity may increase, as if the icons were blended with ICON_OR. You may supply a color in place of an icon. -icon/Opaque(background = "#000000") - All alpha values are set to 255 throughout the icon. Transparent pixels become black, or whatever background color you specify. -icon/BecomeAlphaMask() - You can convert a simple grayscale icon into an alpha mask to use with other icons very easily with this proc. - The black parts become transparent, the white parts stay white, and anything in between becomes a translucent shade of white. -icon/AddAlphaMask(mask) - The alpha values of the mask icon will be blended with the current icon. Anywhere the mask is opaque, - the current icon is untouched. Anywhere the mask is transparent, the current icon becomes transparent. - Where the mask is translucent, the current icon becomes more transparent. -icon/UseAlphaMask(mask, mode) - Sometimes you may want to take the alpha values from one icon and use them on a different icon. - This proc will do that. Just supply the icon whose alpha mask you want to use, and src will change - so it has the same colors as before but uses the mask for opacity. - -COLOR MANAGEMENT AND HSV - -RGB isn't the only way to represent color. Sometimes it's more useful to work with a model called HSV, which stands for hue, saturation, and value. - - * The hue of a color describes where it is along the color wheel. It goes from red to yellow to green to - cyan to blue to magenta and back to red. - * The saturation of a color is how much color is in it. A color with low saturation will be more gray, - and with no saturation at all it is a shade of gray. - * The value of a color determines how bright it is. A high-value color is vivid, moderate value is dark, - and no value at all is black. - -Just as BYOND uses "#rrggbb" to represent RGB values, a similar format is used for HSV: "#hhhssvv". The hue is three -hex digits because it ranges from 0 to 0x5FF. - - * 0 to 0xFF - red to yellow - * 0x100 to 0x1FF - yellow to green - * 0x200 to 0x2FF - green to cyan - * 0x300 to 0x3FF - cyan to blue - * 0x400 to 0x4FF - blue to magenta - * 0x500 to 0x5FF - magenta to red - -Knowing this, you can figure out that red is "#000ffff" in HSV format, which is hue 0 (red), saturation 255 (as colorful as possible), -value 255 (as bright as possible). Green is "#200ffff" and blue is "#400ffff". - -More than one HSV color can match the same RGB color. - -Here are some procs you can use for color management: - -ReadRGB(rgb) - Takes an RGB string like "#ffaa55" and converts it to a list such as list(255,170,85). If an RGBA format is used - that includes alpha, the list will have a fourth item for the alpha value. -hsv(hue, sat, val, apha) - Counterpart to rgb(), this takes the values you input and converts them to a string in "#hhhssvv" or "#hhhssvvaa" - format. Alpha is not included in the result if null. -ReadHSV(rgb) - Takes an HSV string like "#100FF80" and converts it to a list such as list(256,255,128). If an HSVA format is used that - includes alpha, the list will have a fourth item for the alpha value. -RGBtoHSV(rgb) - Takes an RGB or RGBA string like "#ffaa55" and converts it into an HSV or HSVA color such as "#080aaff". -HSVtoRGB(hsv) - Takes an HSV or HSVA string like "#080aaff" and converts it into an RGB or RGBA color such as "#ff55aa". -BlendRGB(rgb1, rgb2, amount) - Blends between two RGB or RGBA colors using regular RGB blending. If amount is 0, the first color is the result; - if 1, the second color is the result. 0.5 produces an average of the two. Values outside the 0 to 1 range are allowed as well. - The returned value is an RGB or RGBA color. -BlendHSV(hsv1, hsv2, amount) - Blends between two HSV or HSVA colors using HSV blending, which tends to produce nicer results than regular RGB - blending because the brightness of the color is left intact. If amount is 0, the first color is the result; if 1, - the second color is the result. 0.5 produces an average of the two. Values outside the 0 to 1 range are allowed as well. - The returned value is an HSV or HSVA color. -BlendRGBasHSV(rgb1, rgb2, amount) - Like BlendHSV(), but the colors used and the return value are RGB or RGBA colors. The blending is done in HSV form. -HueToAngle(hue) - Converts a hue to an angle range of 0 to 360. Angle 0 is red, 120 is green, and 240 is blue. -AngleToHue(hue) - Converts an angle to a hue in the valid range. -RotateHue(hsv, angle) - Takes an HSV or HSVA value and rotates the hue forward through red, green, and blue by an angle from 0 to 360. - (Rotating red by 60° produces yellow.) The result is another HSV or HSVA color with the same saturation and value - as the original, but a different hue. -GrayScale(rgb) - Takes an RGB or RGBA color and converts it to grayscale. Returns an RGB or RGBA string. -ColorTone(rgb, tone) - Similar to GrayScale(), this proc converts an RGB or RGBA color to a range of black -> tone -> white instead of - using strict shades of gray. The tone value is an RGB color; any alpha value is ignored. -*/ - -/* -Get Flat Icon DEMO by DarkCampainger - -This is a test for the get flat icon proc, modified approprietly for icons and their states. -Probably not a good idea to run this unless you want to see how the proc works in detail. -mob - icon = 'old_or_unused.dmi' - icon_state = "green" - - Login() - // Testing image underlays - underlays += image(icon='old_or_unused.dmi',icon_state="red") - underlays += image(icon='old_or_unused.dmi',icon_state="red", pixel_x = 32) - underlays += image(icon='old_or_unused.dmi',icon_state="red", pixel_x = -32) - - // Testing image overlays - add_overlay(image(icon='old_or_unused.dmi',icon_state="green", pixel_x = 32, pixel_y = -32)) - add_overlay(image(icon='old_or_unused.dmi',icon_state="green", pixel_x = 32, pixel_y = 32)) - add_overlay(image(icon='old_or_unused.dmi',icon_state="green", pixel_x = -32, pixel_y = -32)) - - // Testing icon file overlays (defaults to mob's state) - add_overlay('_flat_demoIcons2.dmi') - - // Testing icon_state overlays (defaults to mob's icon) - add_overlay("white") - - // Testing dynamic icon overlays - var/icon/I = icon('old_or_unused.dmi', icon_state="aqua") - I.Shift(NORTH,16,1) - add_overlay(I) - - // Testing dynamic image overlays - I=image(icon=I,pixel_x = -32, pixel_y = 32) - add_overlay(I) - - // Testing object types (and layers) - add_overlay(/obj/effect/overlayTest) - - loc = locate (10,10,1) - verb - Browse_Icon() - set name = "1. Browse Icon" - // Give it a name for the cache - var/iconName = "[ckey(src.name)]_flattened.dmi" - // Send the icon to src's local cache - src<

") - - Output_Icon() - set name = "2. Output Icon" - to_chat(src, "Icon is: [icon2base64html(getFlatIcon(src))]") - - Label_Icon() - set name = "3. Label Icon" - // Give it a name for the cache - var/iconName = "[ckey(src.name)]_flattened.dmi" - // Copy the file to the rsc manually - var/icon/I = fcopy_rsc(getFlatIcon(src)) - // Send the icon to src's local cache - src< transparent, gray -> translucent white, white -> solid white -/icon/proc/BecomeAlphaMask() - SwapColor(null, "#000000ff") // don't let transparent become gray - MapColors(0,0,0,0.3, 0,0,0,0.59, 0,0,0,0.11, 0,0,0,0, 1,1,1,0) - -/icon/proc/UseAlphaMask(mask) - Opaque() - AddAlphaMask(mask) - -/icon/proc/AddAlphaMask(mask) - var/icon/M = new(mask) - M.Blend("#ffffff", ICON_SUBTRACT) - // apply mask - Blend(M, ICON_ADD) - -/* - HSV format is represented as "#hhhssvv" or "#hhhssvvaa" - - Hue ranges from 0 to 0x5ff (1535) - - 0x000 = red - 0x100 = yellow - 0x200 = green - 0x300 = cyan - 0x400 = blue - 0x500 = magenta - - Saturation is from 0 to 0xff (255) - - More saturation = more color - Less saturation = more gray - - Value ranges from 0 to 0xff (255) - - Higher value means brighter color - */ - -/proc/ReadRGB(rgb) - if(!rgb) - return - - // interpret the HSV or HSVA value - var/i=1,start=1 - if(text2ascii(rgb) == 35) ++start // skip opening # - var/ch,which=0,r=0,g=0,b=0,alpha=0,usealpha - var/digits=0 - for(i=start, i<=length(rgb), ++i) - ch = text2ascii(rgb, i) - if(ch < 48 || (ch > 57 && ch < 65) || (ch > 70 && ch < 97) || ch > 102) - break - ++digits - if(digits == 8) - break - - var/single = digits < 6 - if(digits != 3 && digits != 4 && digits != 6 && digits != 8) - return - if(digits == 4 || digits == 8) - usealpha = 1 - for(i=start, digits>0, ++i) - ch = text2ascii(rgb, i) - if(ch >= 48 && ch <= 57) - ch -= 48 - else if(ch >= 65 && ch <= 70) - ch -= 55 - else if(ch >= 97 && ch <= 102) - ch -= 87 - else - break - --digits - switch(which) - if(0) - r = (r << 4) | ch - if(single) - r |= r << 4 - ++which - else if(!(digits & 1)) - ++which - if(1) - g = (g << 4) | ch - if(single) - g |= g << 4 - ++which - else if(!(digits & 1)) - ++which - if(2) - b = (b << 4) | ch - if(single) - b |= b << 4 - ++which - else if(!(digits & 1)) - ++which - if(3) - alpha = (alpha << 4) | ch - if(single) - alpha |= alpha << 4 - - . = list(r, g, b) - if(usealpha) - . += alpha - -/proc/ReadHSV(hsv) - if(!hsv) - return - - // interpret the HSV or HSVA value - var/i=1,start=1 - if(text2ascii(hsv) == 35) - ++start // skip opening # - var/ch,which=0,hue=0,sat=0,val=0,alpha=0,usealpha - var/digits=0 - for(i=start, i<=length(hsv), ++i) - ch = text2ascii(hsv, i) - if(ch < 48 || (ch > 57 && ch < 65) || (ch > 70 && ch < 97) || ch > 102) - break - ++digits - if(digits == 9) - break - if(digits > 7) - usealpha = 1 - if(digits <= 4) - ++which - if(digits <= 2) - ++which - for(i=start, digits>0, ++i) - ch = text2ascii(hsv, i) - if(ch >= 48 && ch <= 57) - ch -= 48 - else if(ch >= 65 && ch <= 70) - ch -= 55 - else if(ch >= 97 && ch <= 102) - ch -= 87 - else - break - --digits - switch(which) - if(0) - hue = (hue << 4) | ch - if(digits == (usealpha ? 6 : 4)) - ++which - if(1) - sat = (sat << 4) | ch - if(digits == (usealpha ? 4 : 2)) - ++which - if(2) - val = (val << 4) | ch - if(digits == (usealpha ? 2 : 0)) - ++which - if(3) - alpha = (alpha << 4) | ch - - . = list(hue, sat, val) - if(usealpha) - . += alpha - -/proc/HSVtoRGB(hsv) - if(!hsv) - return "#000000" - var/list/HSV = ReadHSV(hsv) - if(!HSV) - return "#000000" - - var/hue = HSV[1] - var/sat = HSV[2] - var/val = HSV[3] - - // Compress hue into easier-to-manage range - hue -= hue >> 8 - if(hue >= 0x5fa) - hue -= 0x5fa - - var/hi,mid,lo,r,g,b - hi = val - lo = round((255 - sat) * val / 255, 1) - mid = lo + round(abs(round(hue, 510) - hue) * (hi - lo) / 255, 1) - if(hue >= 765) - if(hue >= 1275) {r=hi; g=lo; b=mid} - else if(hue >= 1020) {r=mid; g=lo; b=hi } - else {r=lo; g=mid; b=hi } - else - if(hue >= 510) {r=lo; g=hi; b=mid} - else if(hue >= 255) {r=mid; g=hi; b=lo } - else {r=hi; g=mid; b=lo } - - return (HSV.len > 3) ? rgb(r,g,b,HSV[4]) : rgb(r,g,b) - -/proc/RGBtoHSV(rgb) - if(!rgb) - return "#0000000" - var/list/RGB = ReadRGB(rgb) - if(!RGB) - return "#0000000" - - var/r = RGB[1] - var/g = RGB[2] - var/b = RGB[3] - var/hi = max(r,g,b) - var/lo = min(r,g,b) - - var/val = hi - var/sat = hi ? round((hi-lo) * 255 / hi, 1) : 0 - var/hue = 0 - - if(sat) - var/dir - var/mid - if(hi == r) - if(lo == b) {hue=0; dir=1; mid=g} - else {hue=1535; dir=-1; mid=b} - else if(hi == g) - if(lo == r) {hue=512; dir=1; mid=b} - else {hue=511; dir=-1; mid=r} - else if(hi == b) - if(lo == g) {hue=1024; dir=1; mid=r} - else {hue=1023; dir=-1; mid=g} - hue += dir * round((mid-lo) * 255 / (hi-lo), 1) - - return hsv(hue, sat, val, (RGB.len>3 ? RGB[4] : null)) - -/proc/hsv(hue, sat, val, alpha) - if(hue < 0 || hue >= 1536) - hue %= 1536 - if(hue < 0) - hue += 1536 - if((hue & 0xFF) == 0xFF) - ++hue - if(hue >= 1536) - hue = 0 - if(sat < 0) - sat = 0 - if(sat > 255) - sat = 255 - if(val < 0) - val = 0 - if(val > 255) - val = 255 - . = "#" - . += TO_HEX_DIGIT(hue >> 8) - . += TO_HEX_DIGIT(hue >> 4) - . += TO_HEX_DIGIT(hue) - . += TO_HEX_DIGIT(sat >> 4) - . += TO_HEX_DIGIT(sat) - . += TO_HEX_DIGIT(val >> 4) - . += TO_HEX_DIGIT(val) - if(!isnull(alpha)) - if(alpha < 0) - alpha = 0 - if(alpha > 255) - alpha = 255 - . += TO_HEX_DIGIT(alpha >> 4) - . += TO_HEX_DIGIT(alpha) - -/* - Smooth blend between HSV colors - - amount=0 is the first color - amount=1 is the second color - amount=0.5 is directly between the two colors - - amount<0 or amount>1 are allowed - */ -/proc/BlendHSV(hsv1, hsv2, amount) - var/list/HSV1 = ReadHSV(hsv1) - var/list/HSV2 = ReadHSV(hsv2) - - // add missing alpha if needed - if(HSV1.len < HSV2.len) - HSV1 += 255 - else if(HSV2.len < HSV1.len) - HSV2 += 255 - var/usealpha = HSV1.len > 3 - - // normalize hsv values in case anything is screwy - if(HSV1[1] > 1536) - HSV1[1] %= 1536 - if(HSV2[1] > 1536) - HSV2[1] %= 1536 - if(HSV1[1] < 0) - HSV1[1] += 1536 - if(HSV2[1] < 0) - HSV2[1] += 1536 - if(!HSV1[3]) {HSV1[1] = 0; HSV1[2] = 0} - if(!HSV2[3]) {HSV2[1] = 0; HSV2[2] = 0} - - // no value for one color means don't change saturation - if(!HSV1[3]) - HSV1[2] = HSV2[2] - if(!HSV2[3]) - HSV2[2] = HSV1[2] - // no saturation for one color means don't change hues - if(!HSV1[2]) - HSV1[1] = HSV2[1] - if(!HSV2[2]) - HSV2[1] = HSV1[1] - - // Compress hues into easier-to-manage range - HSV1[1] -= HSV1[1] >> 8 - HSV2[1] -= HSV2[1] >> 8 - - var/hue_diff = HSV2[1] - HSV1[1] - if(hue_diff > 765) - hue_diff -= 1530 - else if(hue_diff <= -765) - hue_diff += 1530 - - var/hue = round(HSV1[1] + hue_diff * amount, 1) - var/sat = round(HSV1[2] + (HSV2[2] - HSV1[2]) * amount, 1) - var/val = round(HSV1[3] + (HSV2[3] - HSV1[3]) * amount, 1) - var/alpha = usealpha ? round(HSV1[4] + (HSV2[4] - HSV1[4]) * amount, 1) : null - - // normalize hue - if(hue < 0 || hue >= 1530) - hue %= 1530 - if(hue < 0) - hue += 1530 - // decompress hue - hue += round(hue / 255) - - return hsv(hue, sat, val, alpha) - -/* - Smooth blend between RGB colors - - amount=0 is the first color - amount=1 is the second color - amount=0.5 is directly between the two colors - - amount<0 or amount>1 are allowed - */ -/proc/BlendRGB(rgb1, rgb2, amount) - var/list/RGB1 = ReadRGB(rgb1) - var/list/RGB2 = ReadRGB(rgb2) - - // add missing alpha if needed - if(RGB1.len < RGB2.len) - RGB1 += 255 - else if(RGB2.len < RGB1.len) - RGB2 += 255 - var/usealpha = RGB1.len > 3 - - var/r = round(RGB1[1] + (RGB2[1] - RGB1[1]) * amount, 1) - var/g = round(RGB1[2] + (RGB2[2] - RGB1[2]) * amount, 1) - var/b = round(RGB1[3] + (RGB2[3] - RGB1[3]) * amount, 1) - var/alpha = usealpha ? round(RGB1[4] + (RGB2[4] - RGB1[4]) * amount, 1) : null - - return isnull(alpha) ? rgb(r, g, b) : rgb(r, g, b, alpha) - -/proc/BlendRGBasHSV(rgb1, rgb2, amount) - return HSVtoRGB(RGBtoHSV(rgb1), RGBtoHSV(rgb2), amount) - -/proc/HueToAngle(hue) - // normalize hsv in case anything is screwy - if(hue < 0 || hue >= 1536) - hue %= 1536 - if(hue < 0) - hue += 1536 - // Compress hue into easier-to-manage range - hue -= hue >> 8 - return hue / (1530/360) - -/proc/AngleToHue(angle) - // normalize hsv in case anything is screwy - if(angle < 0 || angle >= 360) - angle -= 360 * round(angle / 360) - var/hue = angle * (1530/360) - // Decompress hue - hue += round(hue / 255) - return hue - - -// positive angle rotates forward through red->green->blue -/proc/RotateHue(hsv, angle) - var/list/HSV = ReadHSV(hsv) - - // normalize hsv in case anything is screwy - if(HSV[1] >= 1536) - HSV[1] %= 1536 - if(HSV[1] < 0) - HSV[1] += 1536 - - // Compress hue into easier-to-manage range - HSV[1] -= HSV[1] >> 8 - - if(angle < 0 || angle >= 360) - angle -= 360 * round(angle / 360) - HSV[1] = round(HSV[1] + angle * (1530/360), 1) - - // normalize hue - if(HSV[1] < 0 || HSV[1] >= 1530) - HSV[1] %= 1530 - if(HSV[1] < 0) - HSV[1] += 1530 - // decompress hue - HSV[1] += round(HSV[1] / 255) - - return hsv(HSV[1], HSV[2], HSV[3], (HSV.len > 3 ? HSV[4] : null)) - -// Convert an rgb color to grayscale -/proc/GrayScale(rgb) - var/list/RGB = ReadRGB(rgb) - var/gray = RGB[1]*0.3 + RGB[2]*0.59 + RGB[3]*0.11 - return (RGB.len > 3) ? rgb(gray, gray, gray, RGB[4]) : rgb(gray, gray, gray) - -// Change grayscale color to black->tone->white range -/proc/ColorTone(rgb, tone) - var/list/RGB = ReadRGB(rgb) - var/list/TONE = ReadRGB(tone) - - var/gray = RGB[1]*0.3 + RGB[2]*0.59 + RGB[3]*0.11 - var/tone_gray = TONE[1]*0.3 + TONE[2]*0.59 + TONE[3]*0.11 - - if(gray <= tone_gray) - return BlendRGB("#000000", tone, gray/(tone_gray || 1)) - else - return BlendRGB(tone, "#ffffff", (gray-tone_gray)/((255-tone_gray) || 1)) - - -//Used in the OLD chem colour mixing algorithm -/proc/GetColors(hex) - hex = uppertext(hex) - // No alpha set? Default to full alpha. - if(length(hex) == 7) - hex += "FF" - var/hi1 = text2ascii(hex, 2) // R - var/lo1 = text2ascii(hex, 3) // R - var/hi2 = text2ascii(hex, 4) // G - var/lo2 = text2ascii(hex, 5) // G - var/hi3 = text2ascii(hex, 6) // B - var/lo3 = text2ascii(hex, 7) // B - var/hi4 = text2ascii(hex, 8) // A - var/lo4 = text2ascii(hex, 9) // A - return list(((hi1>= 65 ? hi1-55 : hi1-48)<<4) | (lo1 >= 65 ? lo1-55 : lo1-48), - ((hi2 >= 65 ? hi2-55 : hi2-48)<<4) | (lo2 >= 65 ? lo2-55 : lo2-48), - ((hi3 >= 65 ? hi3-55 : hi3-48)<<4) | (lo3 >= 65 ? lo3-55 : lo3-48), - ((hi4 >= 65 ? hi4-55 : hi4-48)<<4) | (lo4 >= 65 ? lo4-55 : lo4-48)) - -/// Create a single [/icon] from a given [/atom] or [/image]. -/// -/// Very low-performance. Should usually only be used for HTML, where BYOND's -/// appearance system (overlays/underlays, etc.) is not available. -/// -/// Only the first argument is required. -/proc/getFlatIcon(image/A, defdir, deficon, defstate, defblend, start = TRUE, no_anim = TRUE) - //Define... defines. - var/static/icon/flat_template = icon('icons/blanks/32x32.dmi', "nothing") - - #define BLANK icon(flat_template) - #define SET_SELF(SETVAR) do { \ - var/icon/SELF_ICON=icon(icon(curicon, curstate, base_icon_dir),"",SOUTH,no_anim?1:null); \ - if(A.alpha<255) { \ - SELF_ICON.Blend(rgb(255,255,255,A.alpha),ICON_MULTIPLY);\ - } \ - if(A.color) { \ - if(islist(A.color)){ \ - SELF_ICON.MapColors(arglist(A.color))} \ - else{ \ - SELF_ICON.Blend(A.color,ICON_MULTIPLY)} \ - } \ - ##SETVAR=SELF_ICON;\ - } while (0) - #define INDEX_X_LOW 1 - #define INDEX_X_HIGH 2 - #define INDEX_Y_LOW 3 - #define INDEX_Y_HIGH 4 - - #define flatX1 flat_size[INDEX_X_LOW] - #define flatX2 flat_size[INDEX_X_HIGH] - #define flatY1 flat_size[INDEX_Y_LOW] - #define flatY2 flat_size[INDEX_Y_HIGH] - #define addX1 add_size[INDEX_X_LOW] - #define addX2 add_size[INDEX_X_HIGH] - #define addY1 add_size[INDEX_Y_LOW] - #define addY2 add_size[INDEX_Y_HIGH] - - if(!A || A.alpha <= 0) - return BLANK - - var/noIcon = FALSE - if(start) - if(!defdir) - defdir = A.dir - if(!deficon) - deficon = A.icon - if(!defstate) - defstate = A.icon_state - if(!defblend) - defblend = A.blend_mode - - var/curicon = A.icon || deficon - var/curstate = A.icon_state || defstate - - if(!((noIcon = (!curicon)))) - var/curstates = icon_states(curicon) - if(!(curstate in curstates)) - if("" in curstates) - curstate = "" - else - noIcon = TRUE // Do not render this object. - - var/curdir - var/base_icon_dir //We'll use this to get the icon state to display if not null BUT NOT pass it to overlays as the dir we have - - //These should use the parent's direction (most likely) - if(!A.dir || A.dir == SOUTH) - curdir = defdir - else - curdir = A.dir - - //Try to remove/optimize this section ASAP, CPU hog. - //Determines if there's directionals. - if(!noIcon && curdir != SOUTH) - var/exist = FALSE - var/static/list/checkdirs = list(NORTH, EAST, WEST) - for(var/i in checkdirs) //Not using GLOB for a reason. - if(length(icon_states(icon(curicon, curstate, i)))) - exist = TRUE - break - if(!exist) - base_icon_dir = SOUTH - // - - if(!base_icon_dir) - base_icon_dir = curdir - - ASSERT(!BLEND_DEFAULT) //I might just be stupid but lets make sure this define is 0. - - var/curblend = A.blend_mode || defblend - - if(A.overlays.len || A.underlays.len) - var/icon/flat = BLANK - // Layers will be a sorted list of icons/overlays, based on the order in which they are displayed - var/list/layers = list() - var/image/copy - // Add the atom's icon itself, without pixel_x/y offsets. - if(!noIcon) - copy = image(icon=curicon, icon_state=curstate, layer=A.layer, dir=base_icon_dir) - copy.color = A.color - copy.alpha = A.alpha - copy.blend_mode = curblend - layers[copy] = A.layer - - // Loop through the underlays, then overlays, sorting them into the layers list - for(var/process_set in 0 to 1) - var/list/process = process_set? A.overlays : A.underlays - for(var/i in 1 to process.len) - var/image/current = process[i] - if(!current) - continue - if(current.plane != FLOAT_PLANE && current.plane != A.plane) - continue - var/current_layer = current.layer - if(current_layer < 0) - if(current_layer <= -1000) - return flat - current_layer = process_set + A.layer + current_layer / 1000 - - for(var/p in 1 to layers.len) - var/image/cmp = layers[p] - if(current_layer < layers[cmp]) - layers.Insert(p, current) - break - layers[current] = current_layer - - //sortTim(layers, /proc/cmp_image_layer_asc) - - var/icon/add // Icon of overlay being added - - // Current dimensions of flattened icon - var/list/flat_size = list(1, flat.Width(), 1, flat.Height()) - // Dimensions of overlay being added - var/list/add_size[4] - - for(var/V in layers) - var/image/I = V - if(I.alpha == 0) - continue - - if(I == copy) // 'I' is an /image based on the object being flattened. - curblend = BLEND_OVERLAY - add = icon(I.icon, I.icon_state, base_icon_dir) - else // 'I' is an appearance object. - add = getFlatIcon(image(I), curdir, curicon, curstate, curblend, FALSE, no_anim) - if(!add) - continue - // Find the new dimensions of the flat icon to fit the added overlay - add_size = list( - min(flatX1, I.pixel_x+1), - max(flatX2, I.pixel_x+add.Width()), - min(flatY1, I.pixel_y+1), - max(flatY2, I.pixel_y+add.Height()) - ) - - if(flat_size ~! add_size) - // Resize the flattened icon so the new icon fits - flat.Crop( - addX1 - flatX1 + 1, - addY1 - flatY1 + 1, - addX2 - flatX1 + 1, - addY2 - flatY1 + 1 - ) - flat_size = add_size.Copy() - - // Blend the overlay into the flattened icon - flat.Blend(add, blendMode2iconMode(curblend), I.pixel_x + 2 - flatX1, I.pixel_y + 2 - flatY1) - - if(A.color) - if(islist(A.color)) - flat.MapColors(arglist(A.color)) - else - flat.Blend(A.color, ICON_MULTIPLY) - - if(A.alpha < 255) - flat.Blend(rgb(255, 255, 255, A.alpha), ICON_MULTIPLY) - - if(no_anim) - //Clean up repeated frames - var/icon/cleaned = new /icon() - cleaned.Insert(flat, "", SOUTH, 1, 0) - . = cleaned - else - . = icon(flat, "", SOUTH) - else //There's no overlays. - if(!noIcon) - SET_SELF(.) - - //Clear defines - #undef flatX1 - #undef flatX2 - #undef flatY1 - #undef flatY2 - #undef addX1 - #undef addX2 - #undef addY1 - #undef addY2 - - #undef INDEX_X_LOW - #undef INDEX_X_HIGH - #undef INDEX_Y_LOW - #undef INDEX_Y_HIGH - - #undef BLANK - #undef SET_SELF - -/proc/getIconMask(atom/A)//By yours truly. Creates a dynamic mask for a mob/whatever. /N - var/icon/alpha_mask = new(A.icon,A.icon_state)//So we want the default icon and icon state of A. - for(var/V in A.overlays)//For every image in overlays. var/image/I will not work, don't try it. - var/image/I = V - if(I.layer>A.layer) - continue//If layer is greater than what we need, skip it. - var/icon/image_overlay = new(I.icon,I.icon_state)//Blend only works with icon objects. - //Also, icons cannot directly set icon_state. Slower than changing variables but whatever. - alpha_mask.Blend(image_overlay,ICON_OR)//OR so they are lumped together in a nice overlay. - return alpha_mask//And now return the mask. - -/mob/proc/AddCamoOverlay(atom/A)//A is the atom which we are using as the overlay. - var/icon/opacity_icon = new(A.icon, A.icon_state)//Don't really care for overlays/underlays. - //Now we need to culculate overlays+underlays and add them together to form an image for a mask. - var/icon/alpha_mask = getIconMask(src)//getFlatIcon(src) is accurate but SLOW. Not designed for running each tick. This is also a little slow since it's blending a bunch of icons together but good enough. - opacity_icon.AddAlphaMask(alpha_mask)//Likely the main source of lag for this proc. Probably not designed to run each tick. - opacity_icon.ChangeOpacity(0.4)//Front end for MapColors so it's fast. 0.5 means half opacity and looks the best in my opinion. - for(var/i=0,i<5,i++)//And now we add it as overlays. It's faster than creating an icon and then merging it. - var/image/I = image("icon" = opacity_icon, "icon_state" = A.icon_state, "layer" = layer+0.8)//So it's above other stuff but below weapons and the like. - switch(i)//Now to determine offset so the result is somewhat blurred. - if(1) - I.pixel_x-- - if(2) - I.pixel_x++ - if(3) - I.pixel_y-- - if(4) - I.pixel_y++ - add_overlay(I)//And finally add the overlay. - -/proc/getHologramIcon(icon/A, safety=1)//If safety is on, a new icon is not created. - var/icon/flat_icon = safety ? A : new(A)//Has to be a new icon to not constantly change the same icon. - flat_icon.ColorTone(rgb(125,180,225))//Let's make it bluish. - flat_icon.ChangeOpacity(0.5)//Make it half transparent. - var/icon/alpha_mask = new('icons/effects/effects.dmi', "scanline")//Scanline effect. - flat_icon.AddAlphaMask(alpha_mask)//Finally, let's mix in a distortion effect. - return flat_icon - -//What the mob looks like as animated static -//By vg's ComicIronic -/proc/getStaticIcon(icon/A, safety = TRUE) - var/icon/flat_icon = safety ? A : new(A) - flat_icon.Blend(rgb(255,255,255)) - flat_icon.BecomeAlphaMask() - var/icon/static_icon = icon('icons/effects/effects.dmi', "static_base") - static_icon.AddAlphaMask(flat_icon) - return static_icon - -//What the mob looks like as a pitch black outline -//By vg's ComicIronic -/proc/getBlankIcon(icon/A, safety=1) - var/icon/flat_icon = safety ? A : new(A) - flat_icon.Blend(rgb(255,255,255)) - flat_icon.BecomeAlphaMask() - var/icon/blank_icon = new/icon('icons/effects/effects.dmi', "blank_base") - blank_icon.AddAlphaMask(flat_icon) - return blank_icon - - -//Dwarf fortress style icons based on letters (defaults to the first letter of the Atom's name) -//By vg's ComicIronic -/proc/getLetterImage(atom/A, letter= "", uppercase = 0) - if(!A) - return - - var/icon/atom_icon = new(A.icon, A.icon_state) - - if(!letter) - letter = A.name[1] - if(uppercase == 1) - letter = uppertext(letter) - else if(uppercase == -1) - letter = lowertext(letter) - - var/image/text_image = new(loc = A) - text_image.maptext = "[letter]" - text_image.pixel_x = 7 - text_image.pixel_y = 5 - qdel(atom_icon) - return text_image - -GLOBAL_LIST_EMPTY(friendly_animal_types) - -// Pick a random animal instead of the icon, and use that instead -/proc/getRandomAnimalImage(atom/A) - if(!GLOB.friendly_animal_types.len) - for(var/T in typesof(/mob/living/simple_animal)) - var/mob/living/simple_animal/SA = T - if(initial(SA.gold_core_spawnable) == FRIENDLY_SPAWN) - GLOB.friendly_animal_types += SA - - - var/mob/living/simple_animal/SA = pick(GLOB.friendly_animal_types) - - var/icon = initial(SA.icon) - var/icon_state = initial(SA.icon_state) - - var/image/final_image = image(icon, icon_state=icon_state, loc = A) - - if(ispath(SA, /mob/living/simple_animal/butterfly)) - final_image.color = rgb(rand(0,255), rand(0,255), rand(0,255)) - - // For debugging - final_image.text = initial(SA.name) - return final_image - -//Interface for using DrawBox() to draw 1 pixel on a coordinate. -//Returns the same icon specifed in the argument, but with the pixel drawn -/proc/DrawPixel(icon/I,colour,drawX,drawY) - if(!I) - return 0 - - var/Iwidth = I.Width() - var/Iheight = I.Height() - - if(drawX > Iwidth || drawX <= 0) - return 0 - if(drawY > Iheight || drawY <= 0) - return 0 - - I.DrawBox(colour,drawX, drawY) - return I - - -//Interface for easy drawing of one pixel on an atom. -/atom/proc/DrawPixelOn(colour, drawX, drawY) - var/icon/I = new(icon) - var/icon/J = DrawPixel(I, colour, drawX, drawY) - if(J) //Only set the icon if it succeeded, the icon without the pixel is 1000x better than a black square. - icon = J - return J - return 0 - -//For creating consistent icons for human looking simple animals -/proc/get_flat_human_icon(icon_id, datum/job/J, datum/preferences/prefs, dummy_key, showDirs = GLOB.cardinals, outfit_override = null) - var/static/list/humanoid_icon_cache = list() - if(!icon_id || !humanoid_icon_cache[icon_id]) - var/mob/living/carbon/human/dummy/body = generate_or_wait_for_human_dummy(dummy_key) - - if(prefs) - prefs.copy_to(body,TRUE,FALSE) - if(J) - J.equip(body, TRUE, FALSE, outfit_override = outfit_override) - else if (outfit_override) - body.equipOutfit(outfit_override,visualsOnly = TRUE) - - - var/icon/out_icon = icon('icons/effects/effects.dmi', "nothing") - for(var/D in showDirs) - body.setDir(D) - COMPILE_OVERLAYS(body) - var/icon/partial = getFlatIcon(body) - out_icon.Insert(partial,dir=D) - - humanoid_icon_cache[icon_id] = out_icon - dummy_key? unset_busy_human_dummy(dummy_key) : qdel(body) - return out_icon - else - return humanoid_icon_cache[icon_id] - -//Hook, override to run code on- wait this is images -//Images have dir without being an atom, so they get their own definition. -//Lame. -/image/proc/setDir(newdir) - dir = newdir - -GLOBAL_LIST_INIT(freon_color_matrix, list("#2E5E69", "#60A2A8", "#A1AFB1", rgb(0,0,0))) - -/obj/proc/make_frozen_visual() - // Used to make the frozen item visuals for Freon. - if(resistance_flags & FREEZE_PROOF) - return - if(!(obj_flags & FROZEN)) - name = "frozen [name]" - add_atom_colour(GLOB.freon_color_matrix, TEMPORARY_COLOUR_PRIORITY) - alpha -= 25 - obj_flags |= FROZEN - -//Assumes already frozed -/obj/proc/make_unfrozen() - if(obj_flags & FROZEN) - name = replacetext(name, "frozen ", "") - remove_atom_colour(TEMPORARY_COLOUR_PRIORITY, GLOB.freon_color_matrix) - alpha += 25 - obj_flags &= ~FROZEN - - -//Converts an icon to base64. Operates by putting the icon in the iconCache savefile, -// exporting it as text, and then parsing the base64 from that. -// (This relies on byond automatically storing icons in savefiles as base64) -/proc/icon2base64(icon/icon, iconKey = "misc") - if (!isicon(icon)) - return FALSE - WRITE_FILE(GLOB.iconCache[iconKey], icon) - var/iconData = GLOB.iconCache.ExportText(iconKey) - var/list/partial = splittext(iconData, "{") - return replacetext(copytext_char(partial[2], 3, -5), "\n", "") - -/proc/icon2html(thing, target, icon_state, dir, frame = 1, moving = FALSE) - if (!thing) - return - - var/key - var/icon/I = thing - if (!target) - return - if (target == world) - target = GLOB.clients - - var/list/targets - if (!islist(target)) - targets = list(target) - else - targets = target - if (!targets.len) - return - if (!isicon(I)) - if (isfile(thing)) //special snowflake - var/name = sanitize_filename("[generate_asset_name(thing)].png") - register_asset(name, thing) - for (var/thing2 in targets) - send_asset(thing2, key) - return "" - var/atom/A = thing - if (isnull(dir)) - dir = A.dir - if (isnull(icon_state)) - icon_state = A.icon_state - I = A.icon - if (ishuman(thing)) // Shitty workaround for a BYOND issue. - var/icon/temp = I - I = icon() - I.Insert(temp, dir = SOUTH) - dir = SOUTH - else - if (isnull(dir)) - dir = SOUTH - if (isnull(icon_state)) - icon_state = "" - - I = icon(I, icon_state, dir, frame, moving) - - key = "[generate_asset_name(I)].png" - register_asset(key, I) - for (var/thing2 in targets) - send_asset(thing2, key) - - return "" - -/proc/icon2base64html(thing) - if (!thing) - return - var/static/list/bicon_cache = list() - if (isicon(thing)) - var/icon/I = thing - var/icon_base64 = icon2base64(I) - - if (I.Height() > world.icon_size || I.Width() > world.icon_size) - var/icon_md5 = md5(icon_base64) - icon_base64 = bicon_cache[icon_md5] - if (!icon_base64) // Doesn't exist yet, make it. - bicon_cache[icon_md5] = icon_base64 = icon2base64(I) - - - return "" - - // Either an atom or somebody fucked up and is gonna get a runtime, which I'm fine with. - var/atom/A = thing - var/key = "[istype(A.icon, /icon) ? "[REF(A.icon)]" : A.icon]:[A.icon_state]" - - - if (!bicon_cache[key]) // Doesn't exist, make it. - var/icon/I = icon(A.icon, A.icon_state, SOUTH, 1) - if (ishuman(thing)) // Shitty workaround for a BYOND issue. - var/icon/temp = I - I = icon() - I.Insert(temp, dir = SOUTH) - - bicon_cache[key] = icon2base64(I, key) - - return "" - -//Costlier version of icon2html() that uses getFlatIcon() to account for overlays, underlays, etc. Use with extreme moderation, ESPECIALLY on mobs. -/proc/costly_icon2html(thing, target) - if (!thing) - return - - if (isicon(thing)) - return icon2html(thing, target) - - var/icon/I = getFlatIcon(thing) - return icon2html(I, target) +/* +IconProcs README + +A BYOND library for manipulating icons and colors + +by Lummox JR + +version 1.0 + +The IconProcs library was made to make a lot of common icon operations much easier. BYOND's icon manipulation +routines are very capable but some of the advanced capabilities like using alpha transparency can be unintuitive to beginners. + +CHANGING ICONS + +Several new procs have been added to the /icon datum to simplify working with icons. To use them, +remember you first need to setup an /icon var like so: + +GLOBAL_DATUM_INIT(my_icon, /icon, new('iconfile.dmi')) + +icon/ChangeOpacity(amount = 1) + A very common operation in DM is to try to make an icon more or less transparent. Making an icon more + transparent is usually much easier than making it less so, however. This proc basically is a frontend + for MapColors() which can change opacity any way you like, in much the same way that SetIntensity() + can make an icon lighter or darker. If amount is 0.5, the opacity of the icon will be cut in half. + If amount is 2, opacity is doubled and anything more than half-opaque will become fully opaque. +icon/GrayScale() + Converts the icon to grayscale instead of a fully colored icon. Alpha values are left intact. +icon/ColorTone(tone) + Similar to GrayScale(), this proc converts the icon to a range of black -> tone -> white, where tone is an + RGB color (its alpha is ignored). This can be used to create a sepia tone or similar effect. + See also the global ColorTone() proc. +icon/MinColors(icon) + The icon is blended with a second icon where the minimum of each RGB pixel is the result. + Transparency may increase, as if the icons were blended with ICON_ADD. You may supply a color in place of an icon. +icon/MaxColors(icon) + The icon is blended with a second icon where the maximum of each RGB pixel is the result. + Opacity may increase, as if the icons were blended with ICON_OR. You may supply a color in place of an icon. +icon/Opaque(background = "#000000") + All alpha values are set to 255 throughout the icon. Transparent pixels become black, or whatever background color you specify. +icon/BecomeAlphaMask() + You can convert a simple grayscale icon into an alpha mask to use with other icons very easily with this proc. + The black parts become transparent, the white parts stay white, and anything in between becomes a translucent shade of white. +icon/AddAlphaMask(mask) + The alpha values of the mask icon will be blended with the current icon. Anywhere the mask is opaque, + the current icon is untouched. Anywhere the mask is transparent, the current icon becomes transparent. + Where the mask is translucent, the current icon becomes more transparent. +icon/UseAlphaMask(mask, mode) + Sometimes you may want to take the alpha values from one icon and use them on a different icon. + This proc will do that. Just supply the icon whose alpha mask you want to use, and src will change + so it has the same colors as before but uses the mask for opacity. + +COLOR MANAGEMENT AND HSV + +RGB isn't the only way to represent color. Sometimes it's more useful to work with a model called HSV, which stands for hue, saturation, and value. + + * The hue of a color describes where it is along the color wheel. It goes from red to yellow to green to + cyan to blue to magenta and back to red. + * The saturation of a color is how much color is in it. A color with low saturation will be more gray, + and with no saturation at all it is a shade of gray. + * The value of a color determines how bright it is. A high-value color is vivid, moderate value is dark, + and no value at all is black. + +Just as BYOND uses "#rrggbb" to represent RGB values, a similar format is used for HSV: "#hhhssvv". The hue is three +hex digits because it ranges from 0 to 0x5FF. + + * 0 to 0xFF - red to yellow + * 0x100 to 0x1FF - yellow to green + * 0x200 to 0x2FF - green to cyan + * 0x300 to 0x3FF - cyan to blue + * 0x400 to 0x4FF - blue to magenta + * 0x500 to 0x5FF - magenta to red + +Knowing this, you can figure out that red is "#000ffff" in HSV format, which is hue 0 (red), saturation 255 (as colorful as possible), +value 255 (as bright as possible). Green is "#200ffff" and blue is "#400ffff". + +More than one HSV color can match the same RGB color. + +Here are some procs you can use for color management: + +ReadRGB(rgb) + Takes an RGB string like "#ffaa55" and converts it to a list such as list(255,170,85). If an RGBA format is used + that includes alpha, the list will have a fourth item for the alpha value. +hsv(hue, sat, val, apha) + Counterpart to rgb(), this takes the values you input and converts them to a string in "#hhhssvv" or "#hhhssvvaa" + format. Alpha is not included in the result if null. +ReadHSV(rgb) + Takes an HSV string like "#100FF80" and converts it to a list such as list(256,255,128). If an HSVA format is used that + includes alpha, the list will have a fourth item for the alpha value. +RGBtoHSV(rgb) + Takes an RGB or RGBA string like "#ffaa55" and converts it into an HSV or HSVA color such as "#080aaff". +HSVtoRGB(hsv) + Takes an HSV or HSVA string like "#080aaff" and converts it into an RGB or RGBA color such as "#ff55aa". +BlendRGB(rgb1, rgb2, amount) + Blends between two RGB or RGBA colors using regular RGB blending. If amount is 0, the first color is the result; + if 1, the second color is the result. 0.5 produces an average of the two. Values outside the 0 to 1 range are allowed as well. + The returned value is an RGB or RGBA color. +BlendHSV(hsv1, hsv2, amount) + Blends between two HSV or HSVA colors using HSV blending, which tends to produce nicer results than regular RGB + blending because the brightness of the color is left intact. If amount is 0, the first color is the result; if 1, + the second color is the result. 0.5 produces an average of the two. Values outside the 0 to 1 range are allowed as well. + The returned value is an HSV or HSVA color. +BlendRGBasHSV(rgb1, rgb2, amount) + Like BlendHSV(), but the colors used and the return value are RGB or RGBA colors. The blending is done in HSV form. +HueToAngle(hue) + Converts a hue to an angle range of 0 to 360. Angle 0 is red, 120 is green, and 240 is blue. +AngleToHue(hue) + Converts an angle to a hue in the valid range. +RotateHue(hsv, angle) + Takes an HSV or HSVA value and rotates the hue forward through red, green, and blue by an angle from 0 to 360. + (Rotating red by 60° produces yellow.) The result is another HSV or HSVA color with the same saturation and value + as the original, but a different hue. +GrayScale(rgb) + Takes an RGB or RGBA color and converts it to grayscale. Returns an RGB or RGBA string. +ColorTone(rgb, tone) + Similar to GrayScale(), this proc converts an RGB or RGBA color to a range of black -> tone -> white instead of + using strict shades of gray. The tone value is an RGB color; any alpha value is ignored. +*/ + +/* +Get Flat Icon DEMO by DarkCampainger + +This is a test for the get flat icon proc, modified approprietly for icons and their states. +Probably not a good idea to run this unless you want to see how the proc works in detail. +mob + icon = 'old_or_unused.dmi' + icon_state = "green" + + Login() + // Testing image underlays + underlays += image(icon='old_or_unused.dmi',icon_state="red") + underlays += image(icon='old_or_unused.dmi',icon_state="red", pixel_x = 32) + underlays += image(icon='old_or_unused.dmi',icon_state="red", pixel_x = -32) + + // Testing image overlays + add_overlay(image(icon='old_or_unused.dmi',icon_state="green", pixel_x = 32, pixel_y = -32)) + add_overlay(image(icon='old_or_unused.dmi',icon_state="green", pixel_x = 32, pixel_y = 32)) + add_overlay(image(icon='old_or_unused.dmi',icon_state="green", pixel_x = -32, pixel_y = -32)) + + // Testing icon file overlays (defaults to mob's state) + add_overlay('_flat_demoIcons2.dmi') + + // Testing icon_state overlays (defaults to mob's icon) + add_overlay("white") + + // Testing dynamic icon overlays + var/icon/I = icon('old_or_unused.dmi', icon_state="aqua") + I.Shift(NORTH,16,1) + add_overlay(I) + + // Testing dynamic image overlays + I=image(icon=I,pixel_x = -32, pixel_y = 32) + add_overlay(I) + + // Testing object types (and layers) + add_overlay(/obj/effect/overlayTest) + + loc = locate (10,10,1) + verb + Browse_Icon() + set name = "1. Browse Icon" + // Give it a name for the cache + var/iconName = "[ckey(src.name)]_flattened.dmi" + // Send the icon to src's local cache + src<

") + + Output_Icon() + set name = "2. Output Icon" + to_chat(src, "Icon is: [icon2base64html(getFlatIcon(src))]") + + Label_Icon() + set name = "3. Label Icon" + // Give it a name for the cache + var/iconName = "[ckey(src.name)]_flattened.dmi" + // Copy the file to the rsc manually + var/icon/I = fcopy_rsc(getFlatIcon(src)) + // Send the icon to src's local cache + src< transparent, gray -> translucent white, white -> solid white +/icon/proc/BecomeAlphaMask() + SwapColor(null, "#000000ff") // don't let transparent become gray + MapColors(0,0,0,0.3, 0,0,0,0.59, 0,0,0,0.11, 0,0,0,0, 1,1,1,0) + +/icon/proc/UseAlphaMask(mask) + Opaque() + AddAlphaMask(mask) + +/icon/proc/AddAlphaMask(mask) + var/icon/M = new(mask) + M.Blend("#ffffff", ICON_SUBTRACT) + // apply mask + Blend(M, ICON_ADD) + +/* + HSV format is represented as "#hhhssvv" or "#hhhssvvaa" + + Hue ranges from 0 to 0x5ff (1535) + + 0x000 = red + 0x100 = yellow + 0x200 = green + 0x300 = cyan + 0x400 = blue + 0x500 = magenta + + Saturation is from 0 to 0xff (255) + + More saturation = more color + Less saturation = more gray + + Value ranges from 0 to 0xff (255) + + Higher value means brighter color + */ + +/proc/ReadRGB(rgb) + if(!rgb) + return + + // interpret the HSV or HSVA value + var/i=1,start=1 + if(text2ascii(rgb) == 35) ++start // skip opening # + var/ch,which=0,r=0,g=0,b=0,alpha=0,usealpha + var/digits=0 + for(i=start, i<=length(rgb), ++i) + ch = text2ascii(rgb, i) + if(ch < 48 || (ch > 57 && ch < 65) || (ch > 70 && ch < 97) || ch > 102) + break + ++digits + if(digits == 8) + break + + var/single = digits < 6 + if(digits != 3 && digits != 4 && digits != 6 && digits != 8) + return + if(digits == 4 || digits == 8) + usealpha = 1 + for(i=start, digits>0, ++i) + ch = text2ascii(rgb, i) + if(ch >= 48 && ch <= 57) + ch -= 48 + else if(ch >= 65 && ch <= 70) + ch -= 55 + else if(ch >= 97 && ch <= 102) + ch -= 87 + else + break + --digits + switch(which) + if(0) + r = (r << 4) | ch + if(single) + r |= r << 4 + ++which + else if(!(digits & 1)) + ++which + if(1) + g = (g << 4) | ch + if(single) + g |= g << 4 + ++which + else if(!(digits & 1)) + ++which + if(2) + b = (b << 4) | ch + if(single) + b |= b << 4 + ++which + else if(!(digits & 1)) + ++which + if(3) + alpha = (alpha << 4) | ch + if(single) + alpha |= alpha << 4 + + . = list(r, g, b) + if(usealpha) + . += alpha + +/proc/ReadHSV(hsv) + if(!hsv) + return + + // interpret the HSV or HSVA value + var/i=1,start=1 + if(text2ascii(hsv) == 35) + ++start // skip opening # + var/ch,which=0,hue=0,sat=0,val=0,alpha=0,usealpha + var/digits=0 + for(i=start, i<=length(hsv), ++i) + ch = text2ascii(hsv, i) + if(ch < 48 || (ch > 57 && ch < 65) || (ch > 70 && ch < 97) || ch > 102) + break + ++digits + if(digits == 9) + break + if(digits > 7) + usealpha = 1 + if(digits <= 4) + ++which + if(digits <= 2) + ++which + for(i=start, digits>0, ++i) + ch = text2ascii(hsv, i) + if(ch >= 48 && ch <= 57) + ch -= 48 + else if(ch >= 65 && ch <= 70) + ch -= 55 + else if(ch >= 97 && ch <= 102) + ch -= 87 + else + break + --digits + switch(which) + if(0) + hue = (hue << 4) | ch + if(digits == (usealpha ? 6 : 4)) + ++which + if(1) + sat = (sat << 4) | ch + if(digits == (usealpha ? 4 : 2)) + ++which + if(2) + val = (val << 4) | ch + if(digits == (usealpha ? 2 : 0)) + ++which + if(3) + alpha = (alpha << 4) | ch + + . = list(hue, sat, val) + if(usealpha) + . += alpha + +/proc/HSVtoRGB(hsv) + if(!hsv) + return "#000000" + var/list/HSV = ReadHSV(hsv) + if(!HSV) + return "#000000" + + var/hue = HSV[1] + var/sat = HSV[2] + var/val = HSV[3] + + // Compress hue into easier-to-manage range + hue -= hue >> 8 + if(hue >= 0x5fa) + hue -= 0x5fa + + var/hi,mid,lo,r,g,b + hi = val + lo = round((255 - sat) * val / 255, 1) + mid = lo + round(abs(round(hue, 510) - hue) * (hi - lo) / 255, 1) + if(hue >= 765) + if(hue >= 1275) {r=hi; g=lo; b=mid} + else if(hue >= 1020) {r=mid; g=lo; b=hi } + else {r=lo; g=mid; b=hi } + else + if(hue >= 510) {r=lo; g=hi; b=mid} + else if(hue >= 255) {r=mid; g=hi; b=lo } + else {r=hi; g=mid; b=lo } + + return (HSV.len > 3) ? rgb(r,g,b,HSV[4]) : rgb(r,g,b) + +/proc/RGBtoHSV(rgb) + if(!rgb) + return "#0000000" + var/list/RGB = ReadRGB(rgb) + if(!RGB) + return "#0000000" + + var/r = RGB[1] + var/g = RGB[2] + var/b = RGB[3] + var/hi = max(r,g,b) + var/lo = min(r,g,b) + + var/val = hi + var/sat = hi ? round((hi-lo) * 255 / hi, 1) : 0 + var/hue = 0 + + if(sat) + var/dir + var/mid + if(hi == r) + if(lo == b) {hue=0; dir=1; mid=g} + else {hue=1535; dir=-1; mid=b} + else if(hi == g) + if(lo == r) {hue=512; dir=1; mid=b} + else {hue=511; dir=-1; mid=r} + else if(hi == b) + if(lo == g) {hue=1024; dir=1; mid=r} + else {hue=1023; dir=-1; mid=g} + hue += dir * round((mid-lo) * 255 / (hi-lo), 1) + + return hsv(hue, sat, val, (RGB.len>3 ? RGB[4] : null)) + +/proc/hsv(hue, sat, val, alpha) + if(hue < 0 || hue >= 1536) + hue %= 1536 + if(hue < 0) + hue += 1536 + if((hue & 0xFF) == 0xFF) + ++hue + if(hue >= 1536) + hue = 0 + if(sat < 0) + sat = 0 + if(sat > 255) + sat = 255 + if(val < 0) + val = 0 + if(val > 255) + val = 255 + . = "#" + . += TO_HEX_DIGIT(hue >> 8) + . += TO_HEX_DIGIT(hue >> 4) + . += TO_HEX_DIGIT(hue) + . += TO_HEX_DIGIT(sat >> 4) + . += TO_HEX_DIGIT(sat) + . += TO_HEX_DIGIT(val >> 4) + . += TO_HEX_DIGIT(val) + if(!isnull(alpha)) + if(alpha < 0) + alpha = 0 + if(alpha > 255) + alpha = 255 + . += TO_HEX_DIGIT(alpha >> 4) + . += TO_HEX_DIGIT(alpha) + +/* + Smooth blend between HSV colors + + amount=0 is the first color + amount=1 is the second color + amount=0.5 is directly between the two colors + + amount<0 or amount>1 are allowed + */ +/proc/BlendHSV(hsv1, hsv2, amount) + var/list/HSV1 = ReadHSV(hsv1) + var/list/HSV2 = ReadHSV(hsv2) + + // add missing alpha if needed + if(HSV1.len < HSV2.len) + HSV1 += 255 + else if(HSV2.len < HSV1.len) + HSV2 += 255 + var/usealpha = HSV1.len > 3 + + // normalize hsv values in case anything is screwy + if(HSV1[1] > 1536) + HSV1[1] %= 1536 + if(HSV2[1] > 1536) + HSV2[1] %= 1536 + if(HSV1[1] < 0) + HSV1[1] += 1536 + if(HSV2[1] < 0) + HSV2[1] += 1536 + if(!HSV1[3]) {HSV1[1] = 0; HSV1[2] = 0} + if(!HSV2[3]) {HSV2[1] = 0; HSV2[2] = 0} + + // no value for one color means don't change saturation + if(!HSV1[3]) + HSV1[2] = HSV2[2] + if(!HSV2[3]) + HSV2[2] = HSV1[2] + // no saturation for one color means don't change hues + if(!HSV1[2]) + HSV1[1] = HSV2[1] + if(!HSV2[2]) + HSV2[1] = HSV1[1] + + // Compress hues into easier-to-manage range + HSV1[1] -= HSV1[1] >> 8 + HSV2[1] -= HSV2[1] >> 8 + + var/hue_diff = HSV2[1] - HSV1[1] + if(hue_diff > 765) + hue_diff -= 1530 + else if(hue_diff <= -765) + hue_diff += 1530 + + var/hue = round(HSV1[1] + hue_diff * amount, 1) + var/sat = round(HSV1[2] + (HSV2[2] - HSV1[2]) * amount, 1) + var/val = round(HSV1[3] + (HSV2[3] - HSV1[3]) * amount, 1) + var/alpha = usealpha ? round(HSV1[4] + (HSV2[4] - HSV1[4]) * amount, 1) : null + + // normalize hue + if(hue < 0 || hue >= 1530) + hue %= 1530 + if(hue < 0) + hue += 1530 + // decompress hue + hue += round(hue / 255) + + return hsv(hue, sat, val, alpha) + +/* + Smooth blend between RGB colors + + amount=0 is the first color + amount=1 is the second color + amount=0.5 is directly between the two colors + + amount<0 or amount>1 are allowed + */ +/proc/BlendRGB(rgb1, rgb2, amount) + var/list/RGB1 = ReadRGB(rgb1) + var/list/RGB2 = ReadRGB(rgb2) + + // add missing alpha if needed + if(RGB1.len < RGB2.len) + RGB1 += 255 + else if(RGB2.len < RGB1.len) + RGB2 += 255 + var/usealpha = RGB1.len > 3 + + var/r = round(RGB1[1] + (RGB2[1] - RGB1[1]) * amount, 1) + var/g = round(RGB1[2] + (RGB2[2] - RGB1[2]) * amount, 1) + var/b = round(RGB1[3] + (RGB2[3] - RGB1[3]) * amount, 1) + var/alpha = usealpha ? round(RGB1[4] + (RGB2[4] - RGB1[4]) * amount, 1) : null + + return isnull(alpha) ? rgb(r, g, b) : rgb(r, g, b, alpha) + +/proc/BlendRGBasHSV(rgb1, rgb2, amount) + return HSVtoRGB(RGBtoHSV(rgb1), RGBtoHSV(rgb2), amount) + +/proc/HueToAngle(hue) + // normalize hsv in case anything is screwy + if(hue < 0 || hue >= 1536) + hue %= 1536 + if(hue < 0) + hue += 1536 + // Compress hue into easier-to-manage range + hue -= hue >> 8 + return hue / (1530/360) + +/proc/AngleToHue(angle) + // normalize hsv in case anything is screwy + if(angle < 0 || angle >= 360) + angle -= 360 * round(angle / 360) + var/hue = angle * (1530/360) + // Decompress hue + hue += round(hue / 255) + return hue + + +// positive angle rotates forward through red->green->blue +/proc/RotateHue(hsv, angle) + var/list/HSV = ReadHSV(hsv) + + // normalize hsv in case anything is screwy + if(HSV[1] >= 1536) + HSV[1] %= 1536 + if(HSV[1] < 0) + HSV[1] += 1536 + + // Compress hue into easier-to-manage range + HSV[1] -= HSV[1] >> 8 + + if(angle < 0 || angle >= 360) + angle -= 360 * round(angle / 360) + HSV[1] = round(HSV[1] + angle * (1530/360), 1) + + // normalize hue + if(HSV[1] < 0 || HSV[1] >= 1530) + HSV[1] %= 1530 + if(HSV[1] < 0) + HSV[1] += 1530 + // decompress hue + HSV[1] += round(HSV[1] / 255) + + return hsv(HSV[1], HSV[2], HSV[3], (HSV.len > 3 ? HSV[4] : null)) + +// Convert an rgb color to grayscale +/proc/GrayScale(rgb) + var/list/RGB = ReadRGB(rgb) + var/gray = RGB[1]*0.3 + RGB[2]*0.59 + RGB[3]*0.11 + return (RGB.len > 3) ? rgb(gray, gray, gray, RGB[4]) : rgb(gray, gray, gray) + +// Change grayscale color to black->tone->white range +/proc/ColorTone(rgb, tone) + var/list/RGB = ReadRGB(rgb) + var/list/TONE = ReadRGB(tone) + + var/gray = RGB[1]*0.3 + RGB[2]*0.59 + RGB[3]*0.11 + var/tone_gray = TONE[1]*0.3 + TONE[2]*0.59 + TONE[3]*0.11 + + if(gray <= tone_gray) + return BlendRGB("#000000", tone, gray/(tone_gray || 1)) + else + return BlendRGB(tone, "#ffffff", (gray-tone_gray)/((255-tone_gray) || 1)) + + +//Used in the OLD chem colour mixing algorithm +/proc/GetColors(hex) + hex = uppertext(hex) + // No alpha set? Default to full alpha. + if(length(hex) == 7) + hex += "FF" + var/hi1 = text2ascii(hex, 2) // R + var/lo1 = text2ascii(hex, 3) // R + var/hi2 = text2ascii(hex, 4) // G + var/lo2 = text2ascii(hex, 5) // G + var/hi3 = text2ascii(hex, 6) // B + var/lo3 = text2ascii(hex, 7) // B + var/hi4 = text2ascii(hex, 8) // A + var/lo4 = text2ascii(hex, 9) // A + return list(((hi1>= 65 ? hi1-55 : hi1-48)<<4) | (lo1 >= 65 ? lo1-55 : lo1-48), + ((hi2 >= 65 ? hi2-55 : hi2-48)<<4) | (lo2 >= 65 ? lo2-55 : lo2-48), + ((hi3 >= 65 ? hi3-55 : hi3-48)<<4) | (lo3 >= 65 ? lo3-55 : lo3-48), + ((hi4 >= 65 ? hi4-55 : hi4-48)<<4) | (lo4 >= 65 ? lo4-55 : lo4-48)) + +/// Create a single [/icon] from a given [/atom] or [/image]. +/// +/// Very low-performance. Should usually only be used for HTML, where BYOND's +/// appearance system (overlays/underlays, etc.) is not available. +/// +/// Only the first argument is required. +/proc/getFlatIcon(image/A, defdir, deficon, defstate, defblend, start = TRUE, no_anim = TRUE) + //Define... defines. + var/static/icon/flat_template = icon('icons/blanks/32x32.dmi', "nothing") + + #define BLANK icon(flat_template) + #define SET_SELF(SETVAR) do { \ + var/icon/SELF_ICON=icon(icon(curicon, curstate, base_icon_dir),"",SOUTH,no_anim?1:null); \ + if(A.alpha<255) { \ + SELF_ICON.Blend(rgb(255,255,255,A.alpha),ICON_MULTIPLY);\ + } \ + if(A.color) { \ + if(islist(A.color)){ \ + SELF_ICON.MapColors(arglist(A.color))} \ + else{ \ + SELF_ICON.Blend(A.color,ICON_MULTIPLY)} \ + } \ + ##SETVAR=SELF_ICON;\ + } while (0) + #define INDEX_X_LOW 1 + #define INDEX_X_HIGH 2 + #define INDEX_Y_LOW 3 + #define INDEX_Y_HIGH 4 + + #define flatX1 flat_size[INDEX_X_LOW] + #define flatX2 flat_size[INDEX_X_HIGH] + #define flatY1 flat_size[INDEX_Y_LOW] + #define flatY2 flat_size[INDEX_Y_HIGH] + #define addX1 add_size[INDEX_X_LOW] + #define addX2 add_size[INDEX_X_HIGH] + #define addY1 add_size[INDEX_Y_LOW] + #define addY2 add_size[INDEX_Y_HIGH] + + if(!A || A.alpha <= 0) + return BLANK + + var/noIcon = FALSE + if(start) + if(!defdir) + defdir = A.dir + if(!deficon) + deficon = A.icon + if(!defstate) + defstate = A.icon_state + if(!defblend) + defblend = A.blend_mode + + var/curicon = A.icon || deficon + var/curstate = A.icon_state || defstate + + if(!((noIcon = (!curicon)))) + var/curstates = icon_states(curicon) + if(!(curstate in curstates)) + if("" in curstates) + curstate = "" + else + noIcon = TRUE // Do not render this object. + + var/curdir + var/base_icon_dir //We'll use this to get the icon state to display if not null BUT NOT pass it to overlays as the dir we have + + //These should use the parent's direction (most likely) + if(!A.dir || A.dir == SOUTH) + curdir = defdir + else + curdir = A.dir + + //Try to remove/optimize this section ASAP, CPU hog. + //Determines if there's directionals. + if(!noIcon && curdir != SOUTH) + var/exist = FALSE + var/static/list/checkdirs = list(NORTH, EAST, WEST) + for(var/i in checkdirs) //Not using GLOB for a reason. + if(length(icon_states(icon(curicon, curstate, i)))) + exist = TRUE + break + if(!exist) + base_icon_dir = SOUTH + // + + if(!base_icon_dir) + base_icon_dir = curdir + + ASSERT(!BLEND_DEFAULT) //I might just be stupid but lets make sure this define is 0. + + var/curblend = A.blend_mode || defblend + + if(A.overlays.len || A.underlays.len) + var/icon/flat = BLANK + // Layers will be a sorted list of icons/overlays, based on the order in which they are displayed + var/list/layers = list() + var/image/copy + // Add the atom's icon itself, without pixel_x/y offsets. + if(!noIcon) + copy = image(icon=curicon, icon_state=curstate, layer=A.layer, dir=base_icon_dir) + copy.color = A.color + copy.alpha = A.alpha + copy.blend_mode = curblend + layers[copy] = A.layer + + // Loop through the underlays, then overlays, sorting them into the layers list + for(var/process_set in 0 to 1) + var/list/process = process_set? A.overlays : A.underlays + for(var/i in 1 to process.len) + var/image/current = process[i] + if(!current) + continue + if(current.plane != FLOAT_PLANE && current.plane != A.plane) + continue + var/current_layer = current.layer + if(current_layer < 0) + if(current_layer <= -1000) + return flat + current_layer = process_set + A.layer + current_layer / 1000 + + for(var/p in 1 to layers.len) + var/image/cmp = layers[p] + if(current_layer < layers[cmp]) + layers.Insert(p, current) + break + layers[current] = current_layer + + //sortTim(layers, /proc/cmp_image_layer_asc) + + var/icon/add // Icon of overlay being added + + // Current dimensions of flattened icon + var/list/flat_size = list(1, flat.Width(), 1, flat.Height()) + // Dimensions of overlay being added + var/list/add_size[4] + + for(var/V in layers) + var/image/I = V + if(I.alpha == 0) + continue + + if(I == copy) // 'I' is an /image based on the object being flattened. + curblend = BLEND_OVERLAY + add = icon(I.icon, I.icon_state, base_icon_dir) + else // 'I' is an appearance object. + add = getFlatIcon(image(I), curdir, curicon, curstate, curblend, FALSE, no_anim) + if(!add) + continue + // Find the new dimensions of the flat icon to fit the added overlay + add_size = list( + min(flatX1, I.pixel_x+1), + max(flatX2, I.pixel_x+add.Width()), + min(flatY1, I.pixel_y+1), + max(flatY2, I.pixel_y+add.Height()) + ) + + if(flat_size ~! add_size) + // Resize the flattened icon so the new icon fits + flat.Crop( + addX1 - flatX1 + 1, + addY1 - flatY1 + 1, + addX2 - flatX1 + 1, + addY2 - flatY1 + 1 + ) + flat_size = add_size.Copy() + + // Blend the overlay into the flattened icon + flat.Blend(add, blendMode2iconMode(curblend), I.pixel_x + 2 - flatX1, I.pixel_y + 2 - flatY1) + + if(A.color) + if(islist(A.color)) + flat.MapColors(arglist(A.color)) + else + flat.Blend(A.color, ICON_MULTIPLY) + + if(A.alpha < 255) + flat.Blend(rgb(255, 255, 255, A.alpha), ICON_MULTIPLY) + + if(no_anim) + //Clean up repeated frames + var/icon/cleaned = new /icon() + cleaned.Insert(flat, "", SOUTH, 1, 0) + . = cleaned + else + . = icon(flat, "", SOUTH) + else //There's no overlays. + if(!noIcon) + SET_SELF(.) + + //Clear defines + #undef flatX1 + #undef flatX2 + #undef flatY1 + #undef flatY2 + #undef addX1 + #undef addX2 + #undef addY1 + #undef addY2 + + #undef INDEX_X_LOW + #undef INDEX_X_HIGH + #undef INDEX_Y_LOW + #undef INDEX_Y_HIGH + + #undef BLANK + #undef SET_SELF + +/proc/getIconMask(atom/A)//By yours truly. Creates a dynamic mask for a mob/whatever. /N + var/icon/alpha_mask = new(A.icon,A.icon_state)//So we want the default icon and icon state of A. + for(var/V in A.overlays)//For every image in overlays. var/image/I will not work, don't try it. + var/image/I = V + if(I.layer>A.layer) + continue//If layer is greater than what we need, skip it. + var/icon/image_overlay = new(I.icon,I.icon_state)//Blend only works with icon objects. + //Also, icons cannot directly set icon_state. Slower than changing variables but whatever. + alpha_mask.Blend(image_overlay,ICON_OR)//OR so they are lumped together in a nice overlay. + return alpha_mask//And now return the mask. + +/mob/proc/AddCamoOverlay(atom/A)//A is the atom which we are using as the overlay. + var/icon/opacity_icon = new(A.icon, A.icon_state)//Don't really care for overlays/underlays. + //Now we need to culculate overlays+underlays and add them together to form an image for a mask. + var/icon/alpha_mask = getIconMask(src)//getFlatIcon(src) is accurate but SLOW. Not designed for running each tick. This is also a little slow since it's blending a bunch of icons together but good enough. + opacity_icon.AddAlphaMask(alpha_mask)//Likely the main source of lag for this proc. Probably not designed to run each tick. + opacity_icon.ChangeOpacity(0.4)//Front end for MapColors so it's fast. 0.5 means half opacity and looks the best in my opinion. + for(var/i=0,i<5,i++)//And now we add it as overlays. It's faster than creating an icon and then merging it. + var/image/I = image("icon" = opacity_icon, "icon_state" = A.icon_state, "layer" = layer+0.8)//So it's above other stuff but below weapons and the like. + switch(i)//Now to determine offset so the result is somewhat blurred. + if(1) + I.pixel_x-- + if(2) + I.pixel_x++ + if(3) + I.pixel_y-- + if(4) + I.pixel_y++ + add_overlay(I)//And finally add the overlay. + +/proc/getHologramIcon(icon/A, safety=1)//If safety is on, a new icon is not created. + var/icon/flat_icon = safety ? A : new(A)//Has to be a new icon to not constantly change the same icon. + flat_icon.ColorTone(rgb(125,180,225))//Let's make it bluish. + flat_icon.ChangeOpacity(0.5)//Make it half transparent. + var/icon/alpha_mask = new('icons/effects/effects.dmi', "scanline")//Scanline effect. + flat_icon.AddAlphaMask(alpha_mask)//Finally, let's mix in a distortion effect. + return flat_icon + +//What the mob looks like as animated static +//By vg's ComicIronic +/proc/getStaticIcon(icon/A, safety = TRUE) + var/icon/flat_icon = safety ? A : new(A) + flat_icon.Blend(rgb(255,255,255)) + flat_icon.BecomeAlphaMask() + var/icon/static_icon = icon('icons/effects/effects.dmi', "static_base") + static_icon.AddAlphaMask(flat_icon) + return static_icon + +//What the mob looks like as a pitch black outline +//By vg's ComicIronic +/proc/getBlankIcon(icon/A, safety=1) + var/icon/flat_icon = safety ? A : new(A) + flat_icon.Blend(rgb(255,255,255)) + flat_icon.BecomeAlphaMask() + var/icon/blank_icon = new/icon('icons/effects/effects.dmi', "blank_base") + blank_icon.AddAlphaMask(flat_icon) + return blank_icon + + +//Dwarf fortress style icons based on letters (defaults to the first letter of the Atom's name) +//By vg's ComicIronic +/proc/getLetterImage(atom/A, letter= "", uppercase = 0) + if(!A) + return + + var/icon/atom_icon = new(A.icon, A.icon_state) + + if(!letter) + letter = A.name[1] + if(uppercase == 1) + letter = uppertext(letter) + else if(uppercase == -1) + letter = lowertext(letter) + + var/image/text_image = new(loc = A) + text_image.maptext = "[letter]" + text_image.pixel_x = 7 + text_image.pixel_y = 5 + qdel(atom_icon) + return text_image + +GLOBAL_LIST_EMPTY(friendly_animal_types) + +// Pick a random animal instead of the icon, and use that instead +/proc/getRandomAnimalImage(atom/A) + if(!GLOB.friendly_animal_types.len) + for(var/T in typesof(/mob/living/simple_animal)) + var/mob/living/simple_animal/SA = T + if(initial(SA.gold_core_spawnable) == FRIENDLY_SPAWN) + GLOB.friendly_animal_types += SA + + + var/mob/living/simple_animal/SA = pick(GLOB.friendly_animal_types) + + var/icon = initial(SA.icon) + var/icon_state = initial(SA.icon_state) + + var/image/final_image = image(icon, icon_state=icon_state, loc = A) + + if(ispath(SA, /mob/living/simple_animal/butterfly)) + final_image.color = rgb(rand(0,255), rand(0,255), rand(0,255)) + + // For debugging + final_image.text = initial(SA.name) + return final_image + +//Interface for using DrawBox() to draw 1 pixel on a coordinate. +//Returns the same icon specifed in the argument, but with the pixel drawn +/proc/DrawPixel(icon/I,colour,drawX,drawY) + if(!I) + return 0 + + var/Iwidth = I.Width() + var/Iheight = I.Height() + + if(drawX > Iwidth || drawX <= 0) + return 0 + if(drawY > Iheight || drawY <= 0) + return 0 + + I.DrawBox(colour,drawX, drawY) + return I + + +//Interface for easy drawing of one pixel on an atom. +/atom/proc/DrawPixelOn(colour, drawX, drawY) + var/icon/I = new(icon) + var/icon/J = DrawPixel(I, colour, drawX, drawY) + if(J) //Only set the icon if it succeeded, the icon without the pixel is 1000x better than a black square. + icon = J + return J + return 0 + +//For creating consistent icons for human looking simple animals +/proc/get_flat_human_icon(icon_id, datum/job/J, datum/preferences/prefs, dummy_key, showDirs = GLOB.cardinals, outfit_override = null) + var/static/list/humanoid_icon_cache = list() + if(!icon_id || !humanoid_icon_cache[icon_id]) + var/mob/living/carbon/human/dummy/body = generate_or_wait_for_human_dummy(dummy_key) + + if(prefs) + prefs.copy_to(body,TRUE,FALSE) + if(J) + J.equip(body, TRUE, FALSE, outfit_override = outfit_override) + else if (outfit_override) + body.equipOutfit(outfit_override,visualsOnly = TRUE) + + + var/icon/out_icon = icon('icons/effects/effects.dmi', "nothing") + for(var/D in showDirs) + body.setDir(D) + COMPILE_OVERLAYS(body) + var/icon/partial = getFlatIcon(body) + out_icon.Insert(partial,dir=D) + + humanoid_icon_cache[icon_id] = out_icon + dummy_key? unset_busy_human_dummy(dummy_key) : qdel(body) + return out_icon + else + return humanoid_icon_cache[icon_id] + +//Hook, override to run code on- wait this is images +//Images have dir without being an atom, so they get their own definition. +//Lame. +/image/proc/setDir(newdir) + dir = newdir + +GLOBAL_LIST_INIT(freon_color_matrix, list("#2E5E69", "#60A2A8", "#A1AFB1", rgb(0,0,0))) + +/obj/proc/make_frozen_visual() + // Used to make the frozen item visuals for Freon. + if(resistance_flags & FREEZE_PROOF) + return + if(!(obj_flags & FROZEN)) + name = "frozen [name]" + add_atom_colour(GLOB.freon_color_matrix, TEMPORARY_COLOUR_PRIORITY) + alpha -= 25 + obj_flags |= FROZEN + +//Assumes already frozed +/obj/proc/make_unfrozen() + if(obj_flags & FROZEN) + name = replacetext(name, "frozen ", "") + remove_atom_colour(TEMPORARY_COLOUR_PRIORITY, GLOB.freon_color_matrix) + alpha += 25 + obj_flags &= ~FROZEN + + +//Converts an icon to base64. Operates by putting the icon in the iconCache savefile, +// exporting it as text, and then parsing the base64 from that. +// (This relies on byond automatically storing icons in savefiles as base64) +/proc/icon2base64(icon/icon, iconKey = "misc") + if (!isicon(icon)) + return FALSE + WRITE_FILE(GLOB.iconCache[iconKey], icon) + var/iconData = GLOB.iconCache.ExportText(iconKey) + var/list/partial = splittext(iconData, "{") + return replacetext(copytext_char(partial[2], 3, -5), "\n", "") + +/proc/icon2html(thing, target, icon_state, dir, frame = 1, moving = FALSE) + if (!thing) + return + + var/key + var/icon/I = thing + if (!target) + return + if (target == world) + target = GLOB.clients + + var/list/targets + if (!islist(target)) + targets = list(target) + else + targets = target + if (!targets.len) + return + if (!isicon(I)) + if (isfile(thing)) //special snowflake + var/name = sanitize_filename("[generate_asset_name(thing)].png") + register_asset(name, thing) + for (var/thing2 in targets) + send_asset(thing2, key) + return "" + var/atom/A = thing + if (isnull(dir)) + dir = A.dir + if (isnull(icon_state)) + icon_state = A.icon_state + I = A.icon + if (ishuman(thing)) // Shitty workaround for a BYOND issue. + var/icon/temp = I + I = icon() + I.Insert(temp, dir = SOUTH) + dir = SOUTH + else + if (isnull(dir)) + dir = SOUTH + if (isnull(icon_state)) + icon_state = "" + + I = icon(I, icon_state, dir, frame, moving) + + key = "[generate_asset_name(I)].png" + register_asset(key, I) + for (var/thing2 in targets) + send_asset(thing2, key) + + return "" + +/proc/icon2base64html(thing) + if (!thing) + return + var/static/list/bicon_cache = list() + if (isicon(thing)) + var/icon/I = thing + var/icon_base64 = icon2base64(I) + + if (I.Height() > world.icon_size || I.Width() > world.icon_size) + var/icon_md5 = md5(icon_base64) + icon_base64 = bicon_cache[icon_md5] + if (!icon_base64) // Doesn't exist yet, make it. + bicon_cache[icon_md5] = icon_base64 = icon2base64(I) + + + return "" + + // Either an atom or somebody fucked up and is gonna get a runtime, which I'm fine with. + var/atom/A = thing + var/key = "[istype(A.icon, /icon) ? "[REF(A.icon)]" : A.icon]:[A.icon_state]" + + + if (!bicon_cache[key]) // Doesn't exist, make it. + var/icon/I = icon(A.icon, A.icon_state, SOUTH, 1) + if (ishuman(thing)) // Shitty workaround for a BYOND issue. + var/icon/temp = I + I = icon() + I.Insert(temp, dir = SOUTH) + + bicon_cache[key] = icon2base64(I, key) + + return "" + +//Costlier version of icon2html() that uses getFlatIcon() to account for overlays, underlays, etc. Use with extreme moderation, ESPECIALLY on mobs. +/proc/costly_icon2html(thing, target) + if (!thing) + return + + if (isicon(thing)) + return icon2html(thing, target) + + var/icon/I = getFlatIcon(thing) + return icon2html(I, target) diff --git a/code/__HELPERS/matrices.dm b/code/__HELPERS/matrices.dm index e2490c8e8737..e12d753d20fd 100644 --- a/code/__HELPERS/matrices.dm +++ b/code/__HELPERS/matrices.dm @@ -1,178 +1,178 @@ -/matrix/proc/TurnTo(old_angle, new_angle) - . = new_angle - old_angle - Turn(.) //BYOND handles cases such as -270, 360, 540 etc. DOES NOT HANDLE 180 TURNS WELL, THEY TWEEN AND LOOK LIKE SHIT - -/atom/proc/SpinAnimation(speed = 10, loops = -1, clockwise = 1, segments = 3, parallel = TRUE) - if(!segments) - return - var/segment = 360/segments - if(!clockwise) - segment = -segment - var/list/matrices = list() - for(var/i in 1 to segments-1) - var/matrix/M = matrix(transform) - M.Turn(segment*i) - matrices += M - var/matrix/last = matrix(transform) - matrices += last - - speed /= segments - - if(parallel) - animate(src, transform = matrices[1], time = speed, loops , flags = ANIMATION_PARALLEL) - else - animate(src, transform = matrices[1], time = speed, loops) - for(var/i in 2 to segments) //2 because 1 is covered above - animate(transform = matrices[i], time = speed) - //doesn't have an object argument because this is "Stacking" with the animate call above - //3 billion% intentional - -//Dumps the matrix data in format a-f -/matrix/proc/tolist() - . = list() - . += a - . += b - . += c - . += d - . += e - . += f - -//Dumps the matrix data in a matrix-grid format -/* - a d 0 - b e 0 - c f 1 -*/ -/matrix/proc/togrid() - . = list() - . += a - . += d - . += 0 - . += b - . += e - . += 0 - . += c - . += f - . += 1 - -//The X pixel offset of this matrix -/matrix/proc/get_x_shift() - . = c - -//The Y pixel offset of this matrix -/matrix/proc/get_y_shift() - . = f - -/matrix/proc/get_x_skew() - . = b - -/matrix/proc/get_y_skew() - . = d - -//Skews a matrix in a particular direction -//Missing arguments are treated as no skew in that direction - -//As Rotation is defined as a scale+skew, these procs will break any existing rotation -//Unless the result is multiplied against the current matrix -/matrix/proc/set_skew(x = 0, y = 0) - b = x - d = y - - -///////////////////// -// COLOUR MATRICES // -///////////////////// - -/* Documenting a couple of potentially useful color matrices here to inspire ideas -// Greyscale - indentical to saturation @ 0 -list(LUMA_R,LUMA_R,LUMA_R,0, LUMA_G,LUMA_G,LUMA_G,0, LUMA_B,LUMA_B,LUMA_B,0, 0,0,0,1, 0,0,0,0) - -// Color inversion -list(-1,0,0,0, 0,-1,0,0, 0,0,-1,0, 0,0,0,1, 1,1,1,0) - -// Sepiatone -list(0.393,0.349,0.272,0, 0.769,0.686,0.534,0, 0.189,0.168,0.131,0, 0,0,0,1, 0,0,0,0) -*/ - -//Does nothing -/proc/color_matrix_identity() - return list(1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1, 0,0,0,0) - -//Adds/subtracts overall lightness -//0 is identity, 1 makes everything white, -1 makes everything black -/proc/color_matrix_lightness(power) - return list(1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1, power,power,power,0) - -//Changes distance hues have from grey while maintaining the overall lightness. Greys are unaffected. -//1 is identity, 0 is greyscale, >1 oversaturates colors -/proc/color_matrix_saturation(value) - var/inv = 1 - value - var/R = round(LUMA_R * inv, 0.001) - var/G = round(LUMA_G * inv, 0.001) - var/B = round(LUMA_B * inv, 0.001) - - return list(R + value,R,R,0, G,G + value,G,0, B,B,B + value,0, 0,0,0,1, 0,0,0,0) - -//Changes distance colors have from rgb(127,127,127) grey -//1 is identity. 0 makes everything grey >1 blows out colors and greys -/proc/color_matrix_contrast(value) - var/add = (1 - value) / 2 - return list(value,0,0,0, 0,value,0,0, 0,0,value,0, 0,0,0,1, add,add,add,0) - -//Moves all colors angle degrees around the color wheel while maintaining intensity of the color and not affecting greys -//0 is identity, 120 moves reds to greens, 240 moves reds to blues -/proc/color_matrix_rotate_hue(angle) - var/sin = sin(angle) - var/cos = cos(angle) - var/cos_inv_third = 0.333*(1-cos) - var/sqrt3_sin = sqrt(3)*sin - return list( -round(cos+cos_inv_third, 0.001), round(cos_inv_third+sqrt3_sin, 0.001), round(cos_inv_third-sqrt3_sin, 0.001), 0, -round(cos_inv_third-sqrt3_sin, 0.001), round(cos+cos_inv_third, 0.001), round(cos_inv_third+sqrt3_sin, 0.001), 0, -round(cos_inv_third+sqrt3_sin, 0.001), round(cos_inv_third-sqrt3_sin, 0.001), round(cos+cos_inv_third, 0.001), 0, -0,0,0,1, -0,0,0,0) - -//These next three rotate values about one axis only -//x is the red axis, y is the green axis, z is the blue axis. -/proc/color_matrix_rotate_x(angle) - var/sinval = round(sin(angle), 0.001); var/cosval = round(cos(angle), 0.001) - return list(1,0,0,0, 0,cosval,sinval,0, 0,-sinval,cosval,0, 0,0,0,1, 0,0,0,0) - -/proc/color_matrix_rotate_y(angle) - var/sinval = round(sin(angle), 0.001); var/cosval = round(cos(angle), 0.001) - return list(cosval,0,-sinval,0, 0,1,0,0, sinval,0,cosval,0, 0,0,0,1, 0,0,0,0) - -/proc/color_matrix_rotate_z(angle) - var/sinval = round(sin(angle), 0.001); var/cosval = round(cos(angle), 0.001) - return list(cosval,sinval,0,0, -sinval,cosval,0,0, 0,0,1,0, 0,0,0,1, 0,0,0,0) - - -//Returns a matrix addition of A with B -/proc/color_matrix_add(list/A, list/B) - if(!istype(A) || !istype(B)) - return color_matrix_identity() - if(A.len != 20 || B.len != 20) - return color_matrix_identity() - var/list/output = list() - output.len = 20 - for(var/value in 1 to 20) - output[value] = A[value] + B[value] - return output - -//Returns a matrix multiplication of A with B -/proc/color_matrix_multiply(list/A, list/B) - if(!istype(A) || !istype(B)) - return color_matrix_identity() - if(A.len != 20 || B.len != 20) - return color_matrix_identity() - var/list/output = list() - output.len = 20 - var/x = 1 - var/y = 1 - var/offset = 0 - for(y in 1 to 5) - offset = (y-1)*4 - for(x in 1 to 4) - output[offset+x] = round(A[offset+1]*B[x] + A[offset+2]*B[x+4] + A[offset+3]*B[x+8] + A[offset+4]*B[x+12]+(y==5?B[x+16]:0), 0.001) - return output +/matrix/proc/TurnTo(old_angle, new_angle) + . = new_angle - old_angle + Turn(.) //BYOND handles cases such as -270, 360, 540 etc. DOES NOT HANDLE 180 TURNS WELL, THEY TWEEN AND LOOK LIKE SHIT + +/atom/proc/SpinAnimation(speed = 10, loops = -1, clockwise = 1, segments = 3, parallel = TRUE) + if(!segments) + return + var/segment = 360/segments + if(!clockwise) + segment = -segment + var/list/matrices = list() + for(var/i in 1 to segments-1) + var/matrix/M = matrix(transform) + M.Turn(segment*i) + matrices += M + var/matrix/last = matrix(transform) + matrices += last + + speed /= segments + + if(parallel) + animate(src, transform = matrices[1], time = speed, loops , flags = ANIMATION_PARALLEL) + else + animate(src, transform = matrices[1], time = speed, loops) + for(var/i in 2 to segments) //2 because 1 is covered above + animate(transform = matrices[i], time = speed) + //doesn't have an object argument because this is "Stacking" with the animate call above + //3 billion% intentional + +//Dumps the matrix data in format a-f +/matrix/proc/tolist() + . = list() + . += a + . += b + . += c + . += d + . += e + . += f + +//Dumps the matrix data in a matrix-grid format +/* + a d 0 + b e 0 + c f 1 +*/ +/matrix/proc/togrid() + . = list() + . += a + . += d + . += 0 + . += b + . += e + . += 0 + . += c + . += f + . += 1 + +//The X pixel offset of this matrix +/matrix/proc/get_x_shift() + . = c + +//The Y pixel offset of this matrix +/matrix/proc/get_y_shift() + . = f + +/matrix/proc/get_x_skew() + . = b + +/matrix/proc/get_y_skew() + . = d + +//Skews a matrix in a particular direction +//Missing arguments are treated as no skew in that direction + +//As Rotation is defined as a scale+skew, these procs will break any existing rotation +//Unless the result is multiplied against the current matrix +/matrix/proc/set_skew(x = 0, y = 0) + b = x + d = y + + +///////////////////// +// COLOUR MATRICES // +///////////////////// + +/* Documenting a couple of potentially useful color matrices here to inspire ideas +// Greyscale - indentical to saturation @ 0 +list(LUMA_R,LUMA_R,LUMA_R,0, LUMA_G,LUMA_G,LUMA_G,0, LUMA_B,LUMA_B,LUMA_B,0, 0,0,0,1, 0,0,0,0) + +// Color inversion +list(-1,0,0,0, 0,-1,0,0, 0,0,-1,0, 0,0,0,1, 1,1,1,0) + +// Sepiatone +list(0.393,0.349,0.272,0, 0.769,0.686,0.534,0, 0.189,0.168,0.131,0, 0,0,0,1, 0,0,0,0) +*/ + +//Does nothing +/proc/color_matrix_identity() + return list(1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1, 0,0,0,0) + +//Adds/subtracts overall lightness +//0 is identity, 1 makes everything white, -1 makes everything black +/proc/color_matrix_lightness(power) + return list(1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1, power,power,power,0) + +//Changes distance hues have from grey while maintaining the overall lightness. Greys are unaffected. +//1 is identity, 0 is greyscale, >1 oversaturates colors +/proc/color_matrix_saturation(value) + var/inv = 1 - value + var/R = round(LUMA_R * inv, 0.001) + var/G = round(LUMA_G * inv, 0.001) + var/B = round(LUMA_B * inv, 0.001) + + return list(R + value,R,R,0, G,G + value,G,0, B,B,B + value,0, 0,0,0,1, 0,0,0,0) + +//Changes distance colors have from rgb(127,127,127) grey +//1 is identity. 0 makes everything grey >1 blows out colors and greys +/proc/color_matrix_contrast(value) + var/add = (1 - value) / 2 + return list(value,0,0,0, 0,value,0,0, 0,0,value,0, 0,0,0,1, add,add,add,0) + +//Moves all colors angle degrees around the color wheel while maintaining intensity of the color and not affecting greys +//0 is identity, 120 moves reds to greens, 240 moves reds to blues +/proc/color_matrix_rotate_hue(angle) + var/sin = sin(angle) + var/cos = cos(angle) + var/cos_inv_third = 0.333*(1-cos) + var/sqrt3_sin = sqrt(3)*sin + return list( +round(cos+cos_inv_third, 0.001), round(cos_inv_third+sqrt3_sin, 0.001), round(cos_inv_third-sqrt3_sin, 0.001), 0, +round(cos_inv_third-sqrt3_sin, 0.001), round(cos+cos_inv_third, 0.001), round(cos_inv_third+sqrt3_sin, 0.001), 0, +round(cos_inv_third+sqrt3_sin, 0.001), round(cos_inv_third-sqrt3_sin, 0.001), round(cos+cos_inv_third, 0.001), 0, +0,0,0,1, +0,0,0,0) + +//These next three rotate values about one axis only +//x is the red axis, y is the green axis, z is the blue axis. +/proc/color_matrix_rotate_x(angle) + var/sinval = round(sin(angle), 0.001); var/cosval = round(cos(angle), 0.001) + return list(1,0,0,0, 0,cosval,sinval,0, 0,-sinval,cosval,0, 0,0,0,1, 0,0,0,0) + +/proc/color_matrix_rotate_y(angle) + var/sinval = round(sin(angle), 0.001); var/cosval = round(cos(angle), 0.001) + return list(cosval,0,-sinval,0, 0,1,0,0, sinval,0,cosval,0, 0,0,0,1, 0,0,0,0) + +/proc/color_matrix_rotate_z(angle) + var/sinval = round(sin(angle), 0.001); var/cosval = round(cos(angle), 0.001) + return list(cosval,sinval,0,0, -sinval,cosval,0,0, 0,0,1,0, 0,0,0,1, 0,0,0,0) + + +//Returns a matrix addition of A with B +/proc/color_matrix_add(list/A, list/B) + if(!istype(A) || !istype(B)) + return color_matrix_identity() + if(A.len != 20 || B.len != 20) + return color_matrix_identity() + var/list/output = list() + output.len = 20 + for(var/value in 1 to 20) + output[value] = A[value] + B[value] + return output + +//Returns a matrix multiplication of A with B +/proc/color_matrix_multiply(list/A, list/B) + if(!istype(A) || !istype(B)) + return color_matrix_identity() + if(A.len != 20 || B.len != 20) + return color_matrix_identity() + var/list/output = list() + output.len = 20 + var/x = 1 + var/y = 1 + var/offset = 0 + for(y in 1 to 5) + offset = (y-1)*4 + for(x in 1 to 4) + output[offset+x] = round(A[offset+1]*B[x] + A[offset+2]*B[x+4] + A[offset+3]*B[x+8] + A[offset+4]*B[x+12]+(y==5?B[x+16]:0), 0.001) + return output diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm index 97d9ae48addf..ab6796765527 100644 --- a/code/__HELPERS/mobs.dm +++ b/code/__HELPERS/mobs.dm @@ -1,613 +1,613 @@ -/proc/random_blood_type() - return pick(4;"O-", 36;"O+", 3;"A-", 28;"A+", 1;"B-", 20;"B+", 1;"AB-", 5;"AB+") - -/proc/random_eye_color() - switch(pick(20;"brown",20;"hazel",20;"grey",15;"blue",15;"green",1;"amber",1;"albino")) - if("brown") - return "630" - if("hazel") - return "542" - if("grey") - return pick("666","777","888","999","aaa","bbb","ccc") - if("blue") - return "36c" - if("green") - return "060" - if("amber") - return "fc0" - if("albino") - return pick("c","d","e","f") + pick("0","1","2","3","4","5","6","7","8","9") + pick("0","1","2","3","4","5","6","7","8","9") - else - return "000" - -/proc/random_underwear(gender) - if(!GLOB.underwear_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/underwear, GLOB.underwear_list, GLOB.underwear_m, GLOB.underwear_f) - switch(gender) - if(MALE) - return pick(GLOB.underwear_m) - if(FEMALE) - return pick(GLOB.underwear_f) - else - return pick(GLOB.underwear_list) - -/proc/random_undershirt(gender) - if(!GLOB.undershirt_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/undershirt, GLOB.undershirt_list, GLOB.undershirt_m, GLOB.undershirt_f) - switch(gender) - if(MALE) - return pick(GLOB.undershirt_m) - if(FEMALE) - return pick(GLOB.undershirt_f) - else - return pick(GLOB.undershirt_list) - -/proc/random_socks() - if(!GLOB.socks_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/socks, GLOB.socks_list) - return pick(GLOB.socks_list) - -/proc/random_backpack() - return pick(GLOB.backpacklist) - -/proc/random_features() - if(!GLOB.tails_list_human.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/human, GLOB.tails_list_human) - if(!GLOB.tails_list_lizard.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/lizard, GLOB.tails_list_lizard) - if(!GLOB.snouts_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/snouts, GLOB.snouts_list) - if(!GLOB.horns_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/horns, GLOB.horns_list) - if(!GLOB.ears_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/ears, GLOB.horns_list) - if(!GLOB.frills_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/frills, GLOB.frills_list) - if(!GLOB.spines_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/spines, GLOB.spines_list) - if(!GLOB.legs_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/legs, GLOB.legs_list) - if(!GLOB.body_markings_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/body_markings, GLOB.body_markings_list) - if(!GLOB.wings_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/wings, GLOB.wings_list) - if(!GLOB.moth_wings_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_wings, GLOB.moth_wings_list) - if(!GLOB.moth_fluff_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_fluff, GLOB.moth_fluff_list) - if(!GLOB.moth_markings_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_markings, GLOB.moth_markings_list) - if(!GLOB.squid_face_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/squid_face, GLOB.squid_face_list) - if(!GLOB.ipc_screens_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/ipc_screens, GLOB.ipc_screens_list) - if(!GLOB.ipc_antennas_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/ipc_antennas, GLOB.ipc_antennas_list) - if(!GLOB.ipc_chassis_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/ipc_chassis, GLOB.ipc_chassis_list) - if(!GLOB.spider_legs_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/spider_legs, GLOB.spider_legs_list) - if(!GLOB.spider_spinneret_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/spider_spinneret, GLOB.spider_spinneret_list) - if(!GLOB.spider_mandibles_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/spider_mandibles, GLOB.spider_mandibles_list) - //For now we will always return none for tail_human and ears. - return(list("mcolor" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"),"ethcolor" = GLOB.color_list_ethereal[pick(GLOB.color_list_ethereal)], "tail_lizard" = pick(GLOB.tails_list_lizard), "tail_human" = "None", "wings" = "None", "snout" = pick(GLOB.snouts_list), "horns" = pick(GLOB.horns_list), "ears" = "None", "frills" = pick(GLOB.frills_list), "spines" = pick(GLOB.spines_list), "body_markings" = pick(GLOB.body_markings_list), "legs" = "Normal Legs", "caps" = pick(GLOB.caps_list), "moth_wings" = pick(GLOB.moth_wings_list), "moth_fluff" = pick(GLOB.moth_fluff_list), "moth_markings" = pick(GLOB.moth_markings_list), "squid_face" = pick(GLOB.squid_face_list), "ipc_screen" = pick(GLOB.ipc_screens_list), "ipc_antenna" = pick(GLOB.ipc_antennas_list),"ipc_chassis" = pick(GLOB.ipc_chassis_list), "spider_legs" = pick(GLOB.spider_legs_list), "spider_spinneret" = pick(GLOB.spider_spinneret_list), "spider_mandibles" = pick(GLOB.spider_mandibles_list), "flavor_text" = "")) - -/proc/random_hairstyle(gender) - switch(gender) - if(MALE) - return pick(GLOB.hairstyles_male_list) - if(FEMALE) - return pick(GLOB.hairstyles_female_list) - else - return pick(GLOB.hairstyles_list) - -/proc/random_facial_hairstyle(gender) - switch(gender) - if(MALE) - return pick(GLOB.facial_hairstyles_male_list) - if(FEMALE) - return pick(GLOB.facial_hairstyles_female_list) - else - return pick(GLOB.facial_hairstyles_list) - -/proc/random_unique_name(gender, attempts_to_find_unique_name=10) - for(var/i in 1 to attempts_to_find_unique_name) - if(gender==FEMALE) - . = capitalize(pick(GLOB.first_names_female)) + " " + capitalize(pick(GLOB.last_names)) - else - . = capitalize(pick(GLOB.first_names_male)) + " " + capitalize(pick(GLOB.last_names)) - - if(!findname(.)) - break - -/proc/random_unique_lizard_name(gender, attempts_to_find_unique_name=10) - for(var/i in 1 to attempts_to_find_unique_name) - . = capitalize(lizard_name(gender)) - - if(!findname(.)) - break - -/proc/random_unique_plasmaman_name(attempts_to_find_unique_name=10) - for(var/i in 1 to attempts_to_find_unique_name) - . = capitalize(plasmaman_name()) - - if(!findname(.)) - break - -/proc/random_unique_ethereal_name(attempts_to_find_unique_name=10) - for(var/i in 1 to attempts_to_find_unique_name) - . = capitalize(ethereal_name()) - - if(!findname(.)) - break - -/proc/random_unique_moth_name(attempts_to_find_unique_name=10) - for(var/i in 1 to attempts_to_find_unique_name) - . = capitalize(pick(GLOB.moth_first)) + " " + capitalize(pick(GLOB.moth_last)) - - if(!findname(.)) - break - -/proc/random_unique_squid_name(attempts_to_find_unique_name=10) - for(var/i in 1 to attempts_to_find_unique_name) - . = capitalize(squid_name()) - - if(!findname(.)) - break - -/proc/random_skin_tone() - return pick(GLOB.skin_tones) - -GLOBAL_LIST_INIT(skin_tones, sortList(list( - "albino", - "caucasian1", - "caucasian2", - "caucasian3", - "latino", - "mediterranean", - "asian1", - "asian2", - "arab", - "indian", - "african1", - "african2" - ))) - -GLOBAL_LIST_EMPTY(species_list) - -/proc/age2agedescription(age) - switch(age) - if(0 to 1) - return "infant" - if(1 to 3) - return "toddler" - if(3 to 13) - return "child" - if(13 to 19) - return "teenager" - if(19 to 30) - return "young adult" - if(30 to 45) - return "adult" - if(45 to 60) - return "middle-aged" - if(60 to 70) - return "aging" - if(70 to INFINITY) - return "elderly" - else - return "unknown" - -///Timed action involving two mobs, the user and the target. -/proc/do_mob(mob/user , mob/target, time = 3 SECONDS, uninterruptible = FALSE, progress = TRUE, datum/callback/extra_checks = null) - if(!user || !target) - return FALSE - var/user_loc = user.loc - - var/drifting = FALSE - if(!user.Process_Spacemove(0) && user.inertia_dir) - drifting = TRUE - - var/target_loc = target.loc - - var/holding = user.get_active_held_item() - var/datum/progressbar/progbar - if (progress) - progbar = new(user, time, target) - - var/endtime = world.time+time - var/starttime = world.time - . = TRUE - while (world.time < endtime) - stoplag(1) - if(!QDELETED(progbar)) - progbar.update(world.time - starttime) - if(QDELETED(user) || QDELETED(target)) - . = FALSE - break - if(uninterruptible) - continue - - if(drifting && !user.inertia_dir) - drifting = FALSE - user_loc = user.loc - - if((!drifting && user.loc != user_loc) || target.loc != target_loc || user.get_active_held_item() != holding || user.incapacitated() || (extra_checks && !extra_checks.Invoke())) - . = FALSE - break - if(!QDELETED(progbar)) - progbar.end_progress() - - -//some additional checks as a callback for for do_afters that want to break on losing health or on the mob taking action -/mob/proc/break_do_after_checks(list/checked_health, check_clicks) - if(check_clicks && next_move > world.time) - return FALSE - return TRUE - -//pass a list in the format list("health" = mob's health var) to check health during this -/mob/living/break_do_after_checks(list/checked_health, check_clicks) - if(islist(checked_health)) - if(health < checked_health["health"]) - return FALSE - checked_health["health"] = health - return ..() - -///Timed action involving one mob user. Target is optional. -/proc/do_after(mob/user, var/delay, needhand = TRUE, atom/target = null, progress = TRUE, datum/callback/extra_checks = null) - if(!user) - return FALSE - var/atom/Tloc = null - if(target && !isturf(target)) - Tloc = target.loc - - if(target) - LAZYADD(user.do_afters, target) - LAZYADD(target.targeted_by, user) - - var/atom/Uloc = user.loc - - var/drifting = FALSE - if(!user.Process_Spacemove(0) && user.inertia_dir) - drifting = TRUE - - var/holding = user.get_active_held_item() - - var/holdingnull = TRUE //User's hand started out empty, check for an empty hand - if(holding) - holdingnull = FALSE //Users hand started holding something, check to see if it's still holding that - - delay *= user.do_after_coefficent() - - var/datum/progressbar/progbar - if(progress) - progbar = new(user, delay, target || user) - - var/endtime = world.time + delay - var/starttime = world.time - . = TRUE - while (world.time < endtime) - stoplag(1) - if(!QDELETED(progbar)) - progbar.update(world.time - starttime) - - if(drifting && !user.inertia_dir) - drifting = FALSE - Uloc = user.loc - - if(QDELETED(user) || user.stat || (!drifting && user.loc != Uloc) || (extra_checks && !extra_checks.Invoke())) - . = FALSE - break - - if(isliving(user)) - var/mob/living/L = user - if(L.IsStun() || L.IsParalyzed()) - . = FALSE - break - - if(!QDELETED(Tloc) && (QDELETED(target) || Tloc != target.loc)) - if((Uloc != Tloc || Tloc != user) && !drifting) - . = FALSE - break - - if(target && !(target in user.do_afters)) - . = FALSE - break - - if(needhand) - //This might seem like an odd check, but you can still need a hand even when it's empty - //i.e the hand is used to pull some item/tool out of the construction - if(!holdingnull) - if(!holding) - . = FALSE - break - if(user.get_active_held_item() != holding) - . = FALSE - break - if(!QDELETED(progbar)) - progbar.end_progress() - - if(!QDELETED(target)) - LAZYREMOVE(user.do_afters, target) - LAZYREMOVE(target.targeted_by, user) - -/mob/proc/do_after_coefficent() // This gets added to the delay on a do_after, default 1 - . = 1 - return - -///Timed action involving at least one mob user and a list of targets. -/proc/do_after_mob(mob/user, list/targets, time = 3 SECONDS, uninterruptible = FALSE, progress = TRUE, datum/callback/extra_checks, required_mobility_flags = MOBILITY_STAND) - if(!user) - return FALSE - if(!islist(targets)) - targets = list(targets) - if(!length(targets)) - return FALSE - var/user_loc = user.loc - - var/drifting = FALSE - if(!user.Process_Spacemove(0) && user.inertia_dir) - drifting = TRUE - - var/list/originalloc = list() - for(var/atom/target in targets) - originalloc[target] = target.loc - - var/holding = user.get_active_held_item() - var/datum/progressbar/progbar - if(progress) - progbar = new(user, time, targets[1]) - - var/endtime = world.time + time - var/starttime = world.time - var/mob/living/L - if(isliving(user)) - L = user - . = TRUE - mainloop: - while(world.time < endtime) - stoplag(1) - if(!QDELETED(progbar)) - progbar.update(world.time - starttime) - if(QDELETED(user) || !targets) - . = FALSE - break - if(uninterruptible) - continue - - if(drifting && !user.inertia_dir) - drifting = FALSE - user_loc = user.loc - - if(L && !((L.mobility_flags & required_mobility_flags) == required_mobility_flags)) - . = FALSE - break - - for(var/atom/target in targets) - if((!drifting && user_loc != user.loc) || QDELETED(target) || originalloc[target] != target.loc || user.get_active_held_item() != holding || user.incapacitated() || (extra_checks && !extra_checks.Invoke())) - . = FALSE - break mainloop - if(!QDELETED(progbar)) - progbar.end_progress() - -/proc/is_species(A, species_datum) - . = FALSE - if(ishuman(A)) - var/mob/living/carbon/human/H = A - if(H.dna && istype(H.dna.species, species_datum)) - . = TRUE - -/proc/spawn_atom_to_turf(spawn_type, target, amount, admin_spawn=FALSE, list/extra_args) - var/turf/T = get_turf(target) - if(!T) - CRASH("attempt to spawn atom type: [spawn_type] in nullspace") - - var/list/new_args = list(T) - if(extra_args) - new_args += extra_args - var/atom/X - for(var/j in 1 to amount) - X = new spawn_type(arglist(new_args)) - if (admin_spawn) - X.flags_1 |= ADMIN_SPAWNED_1 - return X //return the last mob spawned - -/proc/spawn_and_random_walk(spawn_type, target, amount, walk_chance=100, max_walk=3, always_max_walk=FALSE, admin_spawn=FALSE) - var/turf/T = get_turf(target) - var/step_count = 0 - if(!T) - CRASH("attempt to spawn atom type: [spawn_type] in nullspace") - - var/list/spawned_mobs = new(amount) - - for(var/j in 1 to amount) - var/atom/movable/X - - if (istype(spawn_type, /list)) - var/mob_type = pick(spawn_type) - X = new mob_type(T) - else - X = new spawn_type(T) - - if (admin_spawn) - X.flags_1 |= ADMIN_SPAWNED_1 - - spawned_mobs[j] = X - - if(always_max_walk || prob(walk_chance)) - if(always_max_walk) - step_count = max_walk - else - step_count = rand(1, max_walk) - - for(var/i in 1 to step_count) - step(X, pick(NORTH, SOUTH, EAST, WEST)) - - return spawned_mobs - -// Displays a message in deadchat, sent by source. Source is not linkified, message is, to avoid stuff like character names to be linkified. -// Automatically gives the class deadsay to the whole message (message + source) -/proc/deadchat_broadcast(message, source=null, mob/follow_target=null, turf/turf_target=null, speaker_key=null, message_type=DEADCHAT_REGULAR) - message = "[source][message]" - for(var/mob/M in GLOB.player_list) - var/chat_toggles = TOGGLES_DEFAULT_CHAT - var/toggles = TOGGLES_DEFAULT - var/list/ignoring - if(M.client.prefs) - var/datum/preferences/prefs = M.client.prefs - chat_toggles = prefs.chat_toggles - toggles = prefs.toggles - ignoring = prefs.ignoring - - - var/override = FALSE - if(M.client.holder && (chat_toggles & CHAT_DEAD)) - override = TRUE - if(HAS_TRAIT(M, TRAIT_SIXTHSENSE) && message_type == DEADCHAT_REGULAR) - override = TRUE - if(SSticker.current_state == GAME_STATE_FINISHED) - override = TRUE - if(isnewplayer(M) && !override) - continue - if(M.stat != DEAD && !override) - continue - if(speaker_key && (speaker_key in ignoring)) - continue - - switch(message_type) - if(DEADCHAT_DEATHRATTLE) - if(toggles & DISABLE_DEATHRATTLE) - continue - if(DEADCHAT_ARRIVALRATTLE) - if(toggles & DISABLE_ARRIVALRATTLE) - continue - if(DEADCHAT_LAWCHANGE) - if(!(chat_toggles & CHAT_GHOSTLAWS)) - continue - - if(isobserver(M)) - var/rendered_message = message - - if(follow_target) - var/F - if(turf_target) - F = FOLLOW_OR_TURF_LINK(M, follow_target, turf_target) - else - F = FOLLOW_LINK(M, follow_target) - rendered_message = "[F] [message]" - else if(turf_target) - var/turf_link = TURF_LINK(M, turf_target) - rendered_message = "[turf_link] [message]" - - to_chat(M, rendered_message) - else - to_chat(M, message) - -//Used in chemical_mob_spawn. Generates a random mob based on a given gold_core_spawnable value. -/proc/create_random_mob(spawn_location, mob_class = HOSTILE_SPAWN) - var/static/list/mob_spawn_meancritters = list() // list of possible hostile mobs - var/static/list/mob_spawn_nicecritters = list() // and possible friendly mobs - - if(mob_spawn_meancritters.len <= 0 || mob_spawn_nicecritters.len <= 0) - for(var/T in typesof(/mob/living/simple_animal)) - var/mob/living/simple_animal/SA = T - switch(initial(SA.gold_core_spawnable)) - if(HOSTILE_SPAWN) - mob_spawn_meancritters += T - if(FRIENDLY_SPAWN) - mob_spawn_nicecritters += T - - var/chosen - if(mob_class == FRIENDLY_SPAWN) - chosen = pick(mob_spawn_nicecritters) - else - chosen = pick(mob_spawn_meancritters) - var/mob/living/simple_animal/C = new chosen(spawn_location) - return C - -/proc/passtable_on(target, source) - var/mob/living/L = target - if (!HAS_TRAIT(L, TRAIT_PASSTABLE) && L.pass_flags & PASSTABLE) - ADD_TRAIT(L, TRAIT_PASSTABLE, INNATE_TRAIT) - ADD_TRAIT(L, TRAIT_PASSTABLE, source) - L.pass_flags |= PASSTABLE - -/proc/passtable_off(target, source) - var/mob/living/L = target - REMOVE_TRAIT(L, TRAIT_PASSTABLE, source) - if(!HAS_TRAIT(L, TRAIT_PASSTABLE)) - L.pass_flags &= ~PASSTABLE - -/proc/dance_rotate(atom/movable/AM, datum/callback/callperrotate, set_original_dir=FALSE) - set waitfor = FALSE - var/originaldir = AM.dir - for(var/i in list(NORTH,SOUTH,EAST,WEST,EAST,SOUTH,NORTH,SOUTH,EAST,WEST,EAST,SOUTH)) - if(!AM) - return - AM.setDir(i) - callperrotate?.Invoke() - sleep(1) - if(set_original_dir) - AM.setDir(originaldir) - -/////////////////////// -///Silicon Mob Procs/// -/////////////////////// - -//Returns a list of unslaved cyborgs -/proc/active_free_borgs() - . = list() - for(var/mob/living/silicon/robot/R in GLOB.alive_mob_list) - if(R.connected_ai || R.shell) - continue - if(R.stat == DEAD) - continue - if(R.emagged || R.scrambledcodes) - continue - . += R - -//Returns a list of AI's -/proc/active_ais(check_mind=FALSE, var/z = null) - . = list() - for(var/mob/living/silicon/ai/A in GLOB.alive_mob_list) - if(A.stat == DEAD) - continue - if(A.control_disabled) - continue - if(check_mind) - if(!A.mind) - continue - if(z && !(z == A.z) && (!is_station_level(z) || !is_station_level(A.z))) //if a Z level was specified, AND the AI is not on the same level, AND either is off the station... - continue - . += A - return . - -//Find an active ai with the least borgs. VERBOSE PROCNAME HUH! -/proc/select_active_ai_with_fewest_borgs(var/z) - var/mob/living/silicon/ai/selected - var/list/active = active_ais(FALSE, z) - for(var/mob/living/silicon/ai/A in active) - if(!selected || (selected.connected_robots.len > A.connected_robots.len)) - selected = A - - return selected - -/proc/select_active_free_borg(mob/user) - var/list/borgs = active_free_borgs() - if(borgs.len) - if(user) - . = input(user,"Unshackled cyborg signals detected:", "Cyborg Selection", borgs[1]) in sortList(borgs) - else - . = pick(borgs) - return . - -/proc/select_active_ai(mob/user, var/z = null) - var/list/ais = active_ais(FALSE, z) - if(ais.len) - if(user) - . = input(user,"AI signals detected:", "AI Selection", ais[1]) in sortList(ais) - else - . = pick(ais) - return . +/proc/random_blood_type() + return pick(4;"O-", 36;"O+", 3;"A-", 28;"A+", 1;"B-", 20;"B+", 1;"AB-", 5;"AB+") + +/proc/random_eye_color() + switch(pick(20;"brown",20;"hazel",20;"grey",15;"blue",15;"green",1;"amber",1;"albino")) + if("brown") + return "630" + if("hazel") + return "542" + if("grey") + return pick("666","777","888","999","aaa","bbb","ccc") + if("blue") + return "36c" + if("green") + return "060" + if("amber") + return "fc0" + if("albino") + return pick("c","d","e","f") + pick("0","1","2","3","4","5","6","7","8","9") + pick("0","1","2","3","4","5","6","7","8","9") + else + return "000" + +/proc/random_underwear(gender) + if(!GLOB.underwear_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/underwear, GLOB.underwear_list, GLOB.underwear_m, GLOB.underwear_f) + switch(gender) + if(MALE) + return pick(GLOB.underwear_m) + if(FEMALE) + return pick(GLOB.underwear_f) + else + return pick(GLOB.underwear_list) + +/proc/random_undershirt(gender) + if(!GLOB.undershirt_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/undershirt, GLOB.undershirt_list, GLOB.undershirt_m, GLOB.undershirt_f) + switch(gender) + if(MALE) + return pick(GLOB.undershirt_m) + if(FEMALE) + return pick(GLOB.undershirt_f) + else + return pick(GLOB.undershirt_list) + +/proc/random_socks() + if(!GLOB.socks_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/socks, GLOB.socks_list) + return pick(GLOB.socks_list) + +/proc/random_backpack() + return pick(GLOB.backpacklist) + +/proc/random_features() + if(!GLOB.tails_list_human.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/human, GLOB.tails_list_human) + if(!GLOB.tails_list_lizard.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/lizard, GLOB.tails_list_lizard) + if(!GLOB.snouts_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/snouts, GLOB.snouts_list) + if(!GLOB.horns_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/horns, GLOB.horns_list) + if(!GLOB.ears_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/ears, GLOB.horns_list) + if(!GLOB.frills_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/frills, GLOB.frills_list) + if(!GLOB.spines_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/spines, GLOB.spines_list) + if(!GLOB.legs_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/legs, GLOB.legs_list) + if(!GLOB.body_markings_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/body_markings, GLOB.body_markings_list) + if(!GLOB.wings_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/wings, GLOB.wings_list) + if(!GLOB.moth_wings_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_wings, GLOB.moth_wings_list) + if(!GLOB.moth_fluff_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_fluff, GLOB.moth_fluff_list) + if(!GLOB.moth_markings_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_markings, GLOB.moth_markings_list) + if(!GLOB.squid_face_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/squid_face, GLOB.squid_face_list) + if(!GLOB.ipc_screens_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/ipc_screens, GLOB.ipc_screens_list) + if(!GLOB.ipc_antennas_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/ipc_antennas, GLOB.ipc_antennas_list) + if(!GLOB.ipc_chassis_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/ipc_chassis, GLOB.ipc_chassis_list) + if(!GLOB.spider_legs_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/spider_legs, GLOB.spider_legs_list) + if(!GLOB.spider_spinneret_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/spider_spinneret, GLOB.spider_spinneret_list) + if(!GLOB.spider_mandibles_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/spider_mandibles, GLOB.spider_mandibles_list) + //For now we will always return none for tail_human and ears. + return(list("mcolor" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"),"ethcolor" = GLOB.color_list_ethereal[pick(GLOB.color_list_ethereal)], "tail_lizard" = pick(GLOB.tails_list_lizard), "tail_human" = "None", "wings" = "None", "snout" = pick(GLOB.snouts_list), "horns" = pick(GLOB.horns_list), "ears" = "None", "frills" = pick(GLOB.frills_list), "spines" = pick(GLOB.spines_list), "body_markings" = pick(GLOB.body_markings_list), "legs" = "Normal Legs", "caps" = pick(GLOB.caps_list), "moth_wings" = pick(GLOB.moth_wings_list), "moth_fluff" = pick(GLOB.moth_fluff_list), "moth_markings" = pick(GLOB.moth_markings_list), "squid_face" = pick(GLOB.squid_face_list), "ipc_screen" = pick(GLOB.ipc_screens_list), "ipc_antenna" = pick(GLOB.ipc_antennas_list),"ipc_chassis" = pick(GLOB.ipc_chassis_list), "spider_legs" = pick(GLOB.spider_legs_list), "spider_spinneret" = pick(GLOB.spider_spinneret_list), "spider_mandibles" = pick(GLOB.spider_mandibles_list), "flavor_text" = "")) + +/proc/random_hairstyle(gender) + switch(gender) + if(MALE) + return pick(GLOB.hairstyles_male_list) + if(FEMALE) + return pick(GLOB.hairstyles_female_list) + else + return pick(GLOB.hairstyles_list) + +/proc/random_facial_hairstyle(gender) + switch(gender) + if(MALE) + return pick(GLOB.facial_hairstyles_male_list) + if(FEMALE) + return pick(GLOB.facial_hairstyles_female_list) + else + return pick(GLOB.facial_hairstyles_list) + +/proc/random_unique_name(gender, attempts_to_find_unique_name=10) + for(var/i in 1 to attempts_to_find_unique_name) + if(gender==FEMALE) + . = capitalize(pick(GLOB.first_names_female)) + " " + capitalize(pick(GLOB.last_names)) + else + . = capitalize(pick(GLOB.first_names_male)) + " " + capitalize(pick(GLOB.last_names)) + + if(!findname(.)) + break + +/proc/random_unique_lizard_name(gender, attempts_to_find_unique_name=10) + for(var/i in 1 to attempts_to_find_unique_name) + . = capitalize(lizard_name(gender)) + + if(!findname(.)) + break + +/proc/random_unique_plasmaman_name(attempts_to_find_unique_name=10) + for(var/i in 1 to attempts_to_find_unique_name) + . = capitalize(plasmaman_name()) + + if(!findname(.)) + break + +/proc/random_unique_ethereal_name(attempts_to_find_unique_name=10) + for(var/i in 1 to attempts_to_find_unique_name) + . = capitalize(ethereal_name()) + + if(!findname(.)) + break + +/proc/random_unique_moth_name(attempts_to_find_unique_name=10) + for(var/i in 1 to attempts_to_find_unique_name) + . = capitalize(pick(GLOB.moth_first)) + " " + capitalize(pick(GLOB.moth_last)) + + if(!findname(.)) + break + +/proc/random_unique_squid_name(attempts_to_find_unique_name=10) + for(var/i in 1 to attempts_to_find_unique_name) + . = capitalize(squid_name()) + + if(!findname(.)) + break + +/proc/random_skin_tone() + return pick(GLOB.skin_tones) + +GLOBAL_LIST_INIT(skin_tones, sortList(list( + "albino", + "caucasian1", + "caucasian2", + "caucasian3", + "latino", + "mediterranean", + "asian1", + "asian2", + "arab", + "indian", + "african1", + "african2" + ))) + +GLOBAL_LIST_EMPTY(species_list) + +/proc/age2agedescription(age) + switch(age) + if(0 to 1) + return "infant" + if(1 to 3) + return "toddler" + if(3 to 13) + return "child" + if(13 to 19) + return "teenager" + if(19 to 30) + return "young adult" + if(30 to 45) + return "adult" + if(45 to 60) + return "middle-aged" + if(60 to 70) + return "aging" + if(70 to INFINITY) + return "elderly" + else + return "unknown" + +///Timed action involving two mobs, the user and the target. +/proc/do_mob(mob/user , mob/target, time = 3 SECONDS, uninterruptible = FALSE, progress = TRUE, datum/callback/extra_checks = null) + if(!user || !target) + return FALSE + var/user_loc = user.loc + + var/drifting = FALSE + if(!user.Process_Spacemove(0) && user.inertia_dir) + drifting = TRUE + + var/target_loc = target.loc + + var/holding = user.get_active_held_item() + var/datum/progressbar/progbar + if (progress) + progbar = new(user, time, target) + + var/endtime = world.time+time + var/starttime = world.time + . = TRUE + while (world.time < endtime) + stoplag(1) + if(!QDELETED(progbar)) + progbar.update(world.time - starttime) + if(QDELETED(user) || QDELETED(target)) + . = FALSE + break + if(uninterruptible) + continue + + if(drifting && !user.inertia_dir) + drifting = FALSE + user_loc = user.loc + + if((!drifting && user.loc != user_loc) || target.loc != target_loc || user.get_active_held_item() != holding || user.incapacitated() || (extra_checks && !extra_checks.Invoke())) + . = FALSE + break + if(!QDELETED(progbar)) + progbar.end_progress() + + +//some additional checks as a callback for for do_afters that want to break on losing health or on the mob taking action +/mob/proc/break_do_after_checks(list/checked_health, check_clicks) + if(check_clicks && next_move > world.time) + return FALSE + return TRUE + +//pass a list in the format list("health" = mob's health var) to check health during this +/mob/living/break_do_after_checks(list/checked_health, check_clicks) + if(islist(checked_health)) + if(health < checked_health["health"]) + return FALSE + checked_health["health"] = health + return ..() + +///Timed action involving one mob user. Target is optional. +/proc/do_after(mob/user, var/delay, needhand = TRUE, atom/target = null, progress = TRUE, datum/callback/extra_checks = null) + if(!user) + return FALSE + var/atom/Tloc = null + if(target && !isturf(target)) + Tloc = target.loc + + if(target) + LAZYADD(user.do_afters, target) + LAZYADD(target.targeted_by, user) + + var/atom/Uloc = user.loc + + var/drifting = FALSE + if(!user.Process_Spacemove(0) && user.inertia_dir) + drifting = TRUE + + var/holding = user.get_active_held_item() + + var/holdingnull = TRUE //User's hand started out empty, check for an empty hand + if(holding) + holdingnull = FALSE //Users hand started holding something, check to see if it's still holding that + + delay *= user.do_after_coefficent() + + var/datum/progressbar/progbar + if(progress) + progbar = new(user, delay, target || user) + + var/endtime = world.time + delay + var/starttime = world.time + . = TRUE + while (world.time < endtime) + stoplag(1) + if(!QDELETED(progbar)) + progbar.update(world.time - starttime) + + if(drifting && !user.inertia_dir) + drifting = FALSE + Uloc = user.loc + + if(QDELETED(user) || user.stat || (!drifting && user.loc != Uloc) || (extra_checks && !extra_checks.Invoke())) + . = FALSE + break + + if(isliving(user)) + var/mob/living/L = user + if(L.IsStun() || L.IsParalyzed()) + . = FALSE + break + + if(!QDELETED(Tloc) && (QDELETED(target) || Tloc != target.loc)) + if((Uloc != Tloc || Tloc != user) && !drifting) + . = FALSE + break + + if(target && !(target in user.do_afters)) + . = FALSE + break + + if(needhand) + //This might seem like an odd check, but you can still need a hand even when it's empty + //i.e the hand is used to pull some item/tool out of the construction + if(!holdingnull) + if(!holding) + . = FALSE + break + if(user.get_active_held_item() != holding) + . = FALSE + break + if(!QDELETED(progbar)) + progbar.end_progress() + + if(!QDELETED(target)) + LAZYREMOVE(user.do_afters, target) + LAZYREMOVE(target.targeted_by, user) + +/mob/proc/do_after_coefficent() // This gets added to the delay on a do_after, default 1 + . = 1 + return + +///Timed action involving at least one mob user and a list of targets. +/proc/do_after_mob(mob/user, list/targets, time = 3 SECONDS, uninterruptible = FALSE, progress = TRUE, datum/callback/extra_checks, required_mobility_flags = MOBILITY_STAND) + if(!user) + return FALSE + if(!islist(targets)) + targets = list(targets) + if(!length(targets)) + return FALSE + var/user_loc = user.loc + + var/drifting = FALSE + if(!user.Process_Spacemove(0) && user.inertia_dir) + drifting = TRUE + + var/list/originalloc = list() + for(var/atom/target in targets) + originalloc[target] = target.loc + + var/holding = user.get_active_held_item() + var/datum/progressbar/progbar + if(progress) + progbar = new(user, time, targets[1]) + + var/endtime = world.time + time + var/starttime = world.time + var/mob/living/L + if(isliving(user)) + L = user + . = TRUE + mainloop: + while(world.time < endtime) + stoplag(1) + if(!QDELETED(progbar)) + progbar.update(world.time - starttime) + if(QDELETED(user) || !targets) + . = FALSE + break + if(uninterruptible) + continue + + if(drifting && !user.inertia_dir) + drifting = FALSE + user_loc = user.loc + + if(L && !((L.mobility_flags & required_mobility_flags) == required_mobility_flags)) + . = FALSE + break + + for(var/atom/target in targets) + if((!drifting && user_loc != user.loc) || QDELETED(target) || originalloc[target] != target.loc || user.get_active_held_item() != holding || user.incapacitated() || (extra_checks && !extra_checks.Invoke())) + . = FALSE + break mainloop + if(!QDELETED(progbar)) + progbar.end_progress() + +/proc/is_species(A, species_datum) + . = FALSE + if(ishuman(A)) + var/mob/living/carbon/human/H = A + if(H.dna && istype(H.dna.species, species_datum)) + . = TRUE + +/proc/spawn_atom_to_turf(spawn_type, target, amount, admin_spawn=FALSE, list/extra_args) + var/turf/T = get_turf(target) + if(!T) + CRASH("attempt to spawn atom type: [spawn_type] in nullspace") + + var/list/new_args = list(T) + if(extra_args) + new_args += extra_args + var/atom/X + for(var/j in 1 to amount) + X = new spawn_type(arglist(new_args)) + if (admin_spawn) + X.flags_1 |= ADMIN_SPAWNED_1 + return X //return the last mob spawned + +/proc/spawn_and_random_walk(spawn_type, target, amount, walk_chance=100, max_walk=3, always_max_walk=FALSE, admin_spawn=FALSE) + var/turf/T = get_turf(target) + var/step_count = 0 + if(!T) + CRASH("attempt to spawn atom type: [spawn_type] in nullspace") + + var/list/spawned_mobs = new(amount) + + for(var/j in 1 to amount) + var/atom/movable/X + + if (istype(spawn_type, /list)) + var/mob_type = pick(spawn_type) + X = new mob_type(T) + else + X = new spawn_type(T) + + if (admin_spawn) + X.flags_1 |= ADMIN_SPAWNED_1 + + spawned_mobs[j] = X + + if(always_max_walk || prob(walk_chance)) + if(always_max_walk) + step_count = max_walk + else + step_count = rand(1, max_walk) + + for(var/i in 1 to step_count) + step(X, pick(NORTH, SOUTH, EAST, WEST)) + + return spawned_mobs + +// Displays a message in deadchat, sent by source. Source is not linkified, message is, to avoid stuff like character names to be linkified. +// Automatically gives the class deadsay to the whole message (message + source) +/proc/deadchat_broadcast(message, source=null, mob/follow_target=null, turf/turf_target=null, speaker_key=null, message_type=DEADCHAT_REGULAR) + message = "[source][message]" + for(var/mob/M in GLOB.player_list) + var/chat_toggles = TOGGLES_DEFAULT_CHAT + var/toggles = TOGGLES_DEFAULT + var/list/ignoring + if(M.client.prefs) + var/datum/preferences/prefs = M.client.prefs + chat_toggles = prefs.chat_toggles + toggles = prefs.toggles + ignoring = prefs.ignoring + + + var/override = FALSE + if(M.client.holder && (chat_toggles & CHAT_DEAD)) + override = TRUE + if(HAS_TRAIT(M, TRAIT_SIXTHSENSE) && message_type == DEADCHAT_REGULAR) + override = TRUE + if(SSticker.current_state == GAME_STATE_FINISHED) + override = TRUE + if(isnewplayer(M) && !override) + continue + if(M.stat != DEAD && !override) + continue + if(speaker_key && (speaker_key in ignoring)) + continue + + switch(message_type) + if(DEADCHAT_DEATHRATTLE) + if(toggles & DISABLE_DEATHRATTLE) + continue + if(DEADCHAT_ARRIVALRATTLE) + if(toggles & DISABLE_ARRIVALRATTLE) + continue + if(DEADCHAT_LAWCHANGE) + if(!(chat_toggles & CHAT_GHOSTLAWS)) + continue + + if(isobserver(M)) + var/rendered_message = message + + if(follow_target) + var/F + if(turf_target) + F = FOLLOW_OR_TURF_LINK(M, follow_target, turf_target) + else + F = FOLLOW_LINK(M, follow_target) + rendered_message = "[F] [message]" + else if(turf_target) + var/turf_link = TURF_LINK(M, turf_target) + rendered_message = "[turf_link] [message]" + + to_chat(M, rendered_message) + else + to_chat(M, message) + +//Used in chemical_mob_spawn. Generates a random mob based on a given gold_core_spawnable value. +/proc/create_random_mob(spawn_location, mob_class = HOSTILE_SPAWN) + var/static/list/mob_spawn_meancritters = list() // list of possible hostile mobs + var/static/list/mob_spawn_nicecritters = list() // and possible friendly mobs + + if(mob_spawn_meancritters.len <= 0 || mob_spawn_nicecritters.len <= 0) + for(var/T in typesof(/mob/living/simple_animal)) + var/mob/living/simple_animal/SA = T + switch(initial(SA.gold_core_spawnable)) + if(HOSTILE_SPAWN) + mob_spawn_meancritters += T + if(FRIENDLY_SPAWN) + mob_spawn_nicecritters += T + + var/chosen + if(mob_class == FRIENDLY_SPAWN) + chosen = pick(mob_spawn_nicecritters) + else + chosen = pick(mob_spawn_meancritters) + var/mob/living/simple_animal/C = new chosen(spawn_location) + return C + +/proc/passtable_on(target, source) + var/mob/living/L = target + if (!HAS_TRAIT(L, TRAIT_PASSTABLE) && L.pass_flags & PASSTABLE) + ADD_TRAIT(L, TRAIT_PASSTABLE, INNATE_TRAIT) + ADD_TRAIT(L, TRAIT_PASSTABLE, source) + L.pass_flags |= PASSTABLE + +/proc/passtable_off(target, source) + var/mob/living/L = target + REMOVE_TRAIT(L, TRAIT_PASSTABLE, source) + if(!HAS_TRAIT(L, TRAIT_PASSTABLE)) + L.pass_flags &= ~PASSTABLE + +/proc/dance_rotate(atom/movable/AM, datum/callback/callperrotate, set_original_dir=FALSE) + set waitfor = FALSE + var/originaldir = AM.dir + for(var/i in list(NORTH,SOUTH,EAST,WEST,EAST,SOUTH,NORTH,SOUTH,EAST,WEST,EAST,SOUTH)) + if(!AM) + return + AM.setDir(i) + callperrotate?.Invoke() + sleep(1) + if(set_original_dir) + AM.setDir(originaldir) + +/////////////////////// +///Silicon Mob Procs/// +/////////////////////// + +//Returns a list of unslaved cyborgs +/proc/active_free_borgs() + . = list() + for(var/mob/living/silicon/robot/R in GLOB.alive_mob_list) + if(R.connected_ai || R.shell) + continue + if(R.stat == DEAD) + continue + if(R.emagged || R.scrambledcodes) + continue + . += R + +//Returns a list of AI's +/proc/active_ais(check_mind=FALSE, var/z = null) + . = list() + for(var/mob/living/silicon/ai/A in GLOB.alive_mob_list) + if(A.stat == DEAD) + continue + if(A.control_disabled) + continue + if(check_mind) + if(!A.mind) + continue + if(z && !(z == A.z) && (!is_station_level(z) || !is_station_level(A.z))) //if a Z level was specified, AND the AI is not on the same level, AND either is off the station... + continue + . += A + return . + +//Find an active ai with the least borgs. VERBOSE PROCNAME HUH! +/proc/select_active_ai_with_fewest_borgs(var/z) + var/mob/living/silicon/ai/selected + var/list/active = active_ais(FALSE, z) + for(var/mob/living/silicon/ai/A in active) + if(!selected || (selected.connected_robots.len > A.connected_robots.len)) + selected = A + + return selected + +/proc/select_active_free_borg(mob/user) + var/list/borgs = active_free_borgs() + if(borgs.len) + if(user) + . = input(user,"Unshackled cyborg signals detected:", "Cyborg Selection", borgs[1]) in sortList(borgs) + else + . = pick(borgs) + return . + +/proc/select_active_ai(mob/user, var/z = null) + var/list/ais = active_ais(FALSE, z) + if(ais.len) + if(user) + . = input(user,"AI signals detected:", "AI Selection", ais[1]) in sortList(ais) + else + . = pick(ais) + return . diff --git a/code/__HELPERS/mouse_control.dm b/code/__HELPERS/mouse_control.dm index cdd839ce178b..784496ed200c 100644 --- a/code/__HELPERS/mouse_control.dm +++ b/code/__HELPERS/mouse_control.dm @@ -1,52 +1,52 @@ -/proc/mouse_angle_from_client(client/client) - var/list/mouse_control = params2list(client.mouseParams) - if(mouse_control["screen-loc"] && client) - var/list/screen_loc_params = splittext(mouse_control["screen-loc"], ",") - var/list/screen_loc_X = splittext(screen_loc_params[1],":") - var/list/screen_loc_Y = splittext(screen_loc_params[2],":") - var/x = (text2num(screen_loc_X[1]) * 32 + text2num(screen_loc_X[2]) - 32) - var/y = (text2num(screen_loc_Y[1]) * 32 + text2num(screen_loc_Y[2]) - 32) - var/list/screenview = getviewsize(client.view) - var/screenviewX = screenview[1] * world.icon_size - var/screenviewY = screenview[2] * world.icon_size - var/ox = round(screenviewX/2) - client.pixel_x //"origin" x - var/oy = round(screenviewY/2) - client.pixel_y //"origin" y - var/angle = SIMPLIFY_DEGREES(ATAN2(y - oy, x - ox)) - return angle - -//Wow, specific name! -/proc/mouse_absolute_datum_map_position_from_client(client/client) - if(!isloc(client.mob.loc)) - return - var/list/mouse_control = params2list(client.mouseParams) - var/atom/A = client.eye - var/turf/T = get_turf(A) - var/cx = T.x - var/cy = T.y - var/cz = T.z - if(mouse_control["screen-loc"]) - var/x = 0 - var/y = 0 - var/z = 0 - var/p_x = 0 - var/p_y = 0 - //Split screen-loc up into X+Pixel_X and Y+Pixel_Y - var/list/screen_loc_params = splittext(mouse_control["screen-loc"], ",") - //Split X+Pixel_X up into list(X, Pixel_X) - var/list/screen_loc_X = splittext(screen_loc_params[1],":") - //Split Y+Pixel_Y up into list(Y, Pixel_Y) - var/list/screen_loc_Y = splittext(screen_loc_params[2],":") - var/sx = text2num(screen_loc_X[1]) - var/sy = text2num(screen_loc_Y[1]) - //Get the resolution of the client's current screen size. - var/list/screenview = getviewsize(client.view) - var/svx = screenview[1] - var/svy = screenview[2] - var/cox = round((svx - 1) / 2) - var/coy = round((svy - 1) / 2) - x = cx + (sx - 1 - cox) - y = cy + (sy - 1 - coy) - z = cz - p_x = text2num(screen_loc_X[2]) - p_y = text2num(screen_loc_Y[2]) - return new /datum/position(x, y, z, p_x, p_y) +/proc/mouse_angle_from_client(client/client) + var/list/mouse_control = params2list(client.mouseParams) + if(mouse_control["screen-loc"] && client) + var/list/screen_loc_params = splittext(mouse_control["screen-loc"], ",") + var/list/screen_loc_X = splittext(screen_loc_params[1],":") + var/list/screen_loc_Y = splittext(screen_loc_params[2],":") + var/x = (text2num(screen_loc_X[1]) * 32 + text2num(screen_loc_X[2]) - 32) + var/y = (text2num(screen_loc_Y[1]) * 32 + text2num(screen_loc_Y[2]) - 32) + var/list/screenview = getviewsize(client.view) + var/screenviewX = screenview[1] * world.icon_size + var/screenviewY = screenview[2] * world.icon_size + var/ox = round(screenviewX/2) - client.pixel_x //"origin" x + var/oy = round(screenviewY/2) - client.pixel_y //"origin" y + var/angle = SIMPLIFY_DEGREES(ATAN2(y - oy, x - ox)) + return angle + +//Wow, specific name! +/proc/mouse_absolute_datum_map_position_from_client(client/client) + if(!isloc(client.mob.loc)) + return + var/list/mouse_control = params2list(client.mouseParams) + var/atom/A = client.eye + var/turf/T = get_turf(A) + var/cx = T.x + var/cy = T.y + var/cz = T.z + if(mouse_control["screen-loc"]) + var/x = 0 + var/y = 0 + var/z = 0 + var/p_x = 0 + var/p_y = 0 + //Split screen-loc up into X+Pixel_X and Y+Pixel_Y + var/list/screen_loc_params = splittext(mouse_control["screen-loc"], ",") + //Split X+Pixel_X up into list(X, Pixel_X) + var/list/screen_loc_X = splittext(screen_loc_params[1],":") + //Split Y+Pixel_Y up into list(Y, Pixel_Y) + var/list/screen_loc_Y = splittext(screen_loc_params[2],":") + var/sx = text2num(screen_loc_X[1]) + var/sy = text2num(screen_loc_Y[1]) + //Get the resolution of the client's current screen size. + var/list/screenview = getviewsize(client.view) + var/svx = screenview[1] + var/svy = screenview[2] + var/cox = round((svx - 1) / 2) + var/coy = round((svy - 1) / 2) + x = cx + (sx - 1 - cox) + y = cy + (sy - 1 - coy) + z = cz + p_x = text2num(screen_loc_X[2]) + p_y = text2num(screen_loc_Y[2]) + return new /datum/position(x, y, z, p_x, p_y) diff --git a/code/__HELPERS/names.dm b/code/__HELPERS/names.dm index 9fc771208f93..cbe6f5ebb1ab 100644 --- a/code/__HELPERS/names.dm +++ b/code/__HELPERS/names.dm @@ -1,229 +1,229 @@ -/proc/lizard_name(gender) - if(gender == MALE) - return "[pick(GLOB.lizard_names_male)]-[pick(GLOB.lizard_names_male)]" - else - return "[pick(GLOB.lizard_names_female)]-[pick(GLOB.lizard_names_female)]" - -/proc/ethereal_name() - var/tempname = "[pick(GLOB.ethereal_names)] [random_capital_letter()]" - if(prob(65)) - tempname += random_capital_letter() - return tempname - -/proc/plasmaman_name() - return "[pick(GLOB.plasmaman_names)] \Roman[rand(1,99)]" - -/proc/moth_name() - return "[pick(GLOB.moth_first)] [pick(GLOB.moth_last)]" - -/proc/squid_name() - return "[pick(GLOB.squid_names)][pick("-", "", " ")][capitalize(pick(GLOB.squid_names) + pick(GLOB.squid_names))]" - -GLOBAL_VAR(command_name) -/proc/command_name() - if (GLOB.command_name) - return GLOB.command_name - - var/name = "Central Command" - - GLOB.command_name = name - return name - -/proc/change_command_name(name) - - GLOB.command_name = name - - return name - -/proc/station_name() - if(!GLOB.station_name) - var/newname - var/config_station_name = CONFIG_GET(string/stationname) - if(config_station_name) - newname = config_station_name - else - newname = new_station_name() - - set_station_name(newname) - - return GLOB.station_name - -/proc/set_station_name(newname) - GLOB.station_name = newname - - var/config_server_name = CONFIG_GET(string/servername) - if(config_server_name) - world.name = "[config_server_name][config_server_name == GLOB.station_name ? "" : ": [GLOB.station_name]"]" - else - world.name = GLOB.station_name - - -/proc/new_station_name() - var/random = rand(1,5) - var/name = "" - var/new_station_name = "" - - //Rare: Pre-Prefix - if (prob(10)) - name = pick(GLOB.station_prefixes) - new_station_name = name + " " - name = "" - - // Prefix - var/holiday_name = pick(SSevents.holidays) - if(holiday_name) - var/datum/holiday/holiday = SSevents.holidays[holiday_name] - if(istype(holiday, /datum/holiday/friday_thirteenth)) - random = 13 - name = holiday.getStationPrefix() - //get normal name - if(!name) - name = pick(GLOB.station_names) - if(name) - new_station_name += name + " " - - // Suffix - name = pick(GLOB.station_suffixes) - new_station_name += name + " " - - // ID Number - switch(random) - if(1) - new_station_name += "[rand(1, 99)]" - if(2) - new_station_name += pick(GLOB.greek_letters) - if(3) - new_station_name += "\Roman[rand(1,99)]" - if(4) - new_station_name += pick(GLOB.phonetic_alphabet) - if(5) - new_station_name += pick(GLOB.numbers_as_words) - if(13) - new_station_name += pick("13","XIII","Thirteen") - return new_station_name - -/proc/syndicate_name() - var/name = "" - - // Prefix - name += pick("Clandestine", "Prima", "Blue", "Zero-G", "Max", "Blasto", "Waffle", "North", "Omni", "Newton", "Cyber", "Bonk", "Gene", "Gib") - - // Suffix - if (prob(80)) - name += " " - - // Full - if (prob(60)) - name += pick("Syndicate", "Consortium", "Collective", "Corporation", "Group", "Holdings", "Biotech", "Industries", "Systems", "Products", "Chemicals", "Enterprises", "Family", "Creations", "International", "Intergalactic", "Interplanetary", "Foundation", "Positronics", "Hive") - // Broken - else - name += pick("Syndi", "Corp", "Bio", "System", "Prod", "Chem", "Inter", "Hive") - name += pick("", "-") - name += pick("Tech", "Sun", "Co", "Tek", "X", "Inc", "Code") - // Small - else - name += pick("-", "*", "") - name += pick("Tech", "Sun", "Co", "Tek", "X", "Inc", "Gen", "Star", "Dyne", "Code", "Hive") - - return name - - -//Traitors and traitor silicons will get these. Revs will not. -GLOBAL_VAR(syndicate_code_phrase) //Code phrase for traitors. -GLOBAL_VAR(syndicate_code_response) //Code response for traitors. - -//Cached regex search - for checking if codewords are used. -GLOBAL_DATUM(syndicate_code_phrase_regex, /regex) -GLOBAL_DATUM(syndicate_code_response_regex, /regex) - - /* - Should be expanded. - How this works: - Instead of "I'm looking for James Smith," the traitor would say "James Smith" as part of a conversation. - Another traitor may then respond with: "They enjoy running through the void-filled vacuum of the derelict." - The phrase should then have the words: James Smith. - The response should then have the words: run, void, and derelict. - This way assures that the code is suited to the conversation and is unpredicatable. - Obviously, some people will be better at this than others but in theory, everyone should be able to do it and it only enhances roleplay. - Can probably be done through "{ }" but I don't really see the practical benefit. - One example of an earlier system is commented below. - /N - */ - -/proc/generate_code_phrase(return_list=FALSE)//Proc is used for phrase and response in master_controller.dm - - if(!return_list) - . = "" - else - . = list() - - var/words = pick(//How many words there will be. Minimum of two. 2, 4 and 5 have a lesser chance of being selected. 3 is the most likely. - 50; 2, - 200; 3, - 50; 4, - 25; 5 - ) - - var/list/safety = list(1,2,3)//Tells the proc which options to remove later on. - var/nouns = strings(ION_FILE, "ionabstract") - var/objects = strings(ION_FILE, "ionobjects") - var/adjectives = strings(ION_FILE, "ionadjectives") - var/threats = strings(ION_FILE, "ionthreats") - var/foods = strings(ION_FILE, "ionfood") - var/drinks = strings(ION_FILE, "iondrinks") - var/locations = strings(LOCATIONS_FILE, "locations") - - var/list/names = list() - for(var/datum/data/record/t in GLOB.data_core.general)//Picks from crew manifest. - names += t.fields["name"] - - var/maxwords = words//Extra var to check for duplicates. - - for(words,words>0,words--)//Randomly picks from one of the choices below. - - if(words==1&&(1 in safety)&&(2 in safety))//If there is only one word remaining and choice 1 or 2 have not been selected. - safety = list(pick(1,2))//Select choice 1 or 2. - else if(words==1&&maxwords==2)//Else if there is only one word remaining (and there were two originally), and 1 or 2 were chosen, - safety = list(3)//Default to list 3 - - switch(pick(safety))//Chance based on the safety list. - if(1)//1 and 2 can only be selected once each to prevent more than two specific names/places/etc. - switch(rand(1,2))//Mainly to add more options later. - if(1) - if(names.len&&prob(70)) - . += pick(names) - else - if(prob(10)) - . += pick(lizard_name(MALE),lizard_name(FEMALE)) - else - var/new_name = pick(pick(GLOB.first_names_male,GLOB.first_names_female)) - new_name += " " - new_name += pick(GLOB.last_names) - . += new_name - if(2) - . += pick(get_all_jobs())//Returns a job. - safety -= 1 - if(2) - switch(rand(1,3))//Food, drinks, or places. Only selectable once. - if(1) - . += lowertext(pick(drinks)) - if(2) - . += lowertext(pick(foods)) - if(3) - . += lowertext(pick(locations)) - safety -= 2 - if(3) - switch(rand(1,4))//Abstract nouns, objects, adjectives, threats. Can be selected more than once. - if(1) - . += lowertext(pick(nouns)) - if(2) - . += lowertext(pick(objects)) - if(3) - . += lowertext(pick(adjectives)) - if(4) - . += lowertext(pick(threats)) - if(!return_list) - if(words==1) - . += "." - else - . += ", " +/proc/lizard_name(gender) + if(gender == MALE) + return "[pick(GLOB.lizard_names_male)]-[pick(GLOB.lizard_names_male)]" + else + return "[pick(GLOB.lizard_names_female)]-[pick(GLOB.lizard_names_female)]" + +/proc/ethereal_name() + var/tempname = "[pick(GLOB.ethereal_names)] [random_capital_letter()]" + if(prob(65)) + tempname += random_capital_letter() + return tempname + +/proc/plasmaman_name() + return "[pick(GLOB.plasmaman_names)] \Roman[rand(1,99)]" + +/proc/moth_name() + return "[pick(GLOB.moth_first)] [pick(GLOB.moth_last)]" + +/proc/squid_name() + return "[pick(GLOB.squid_names)][pick("-", "", " ")][capitalize(pick(GLOB.squid_names) + pick(GLOB.squid_names))]" + +GLOBAL_VAR(command_name) +/proc/command_name() + if (GLOB.command_name) + return GLOB.command_name + + var/name = "Central Command" + + GLOB.command_name = name + return name + +/proc/change_command_name(name) + + GLOB.command_name = name + + return name + +/proc/station_name() + if(!GLOB.station_name) + var/newname + var/config_station_name = CONFIG_GET(string/stationname) + if(config_station_name) + newname = config_station_name + else + newname = new_station_name() + + set_station_name(newname) + + return GLOB.station_name + +/proc/set_station_name(newname) + GLOB.station_name = newname + + var/config_server_name = CONFIG_GET(string/servername) + if(config_server_name) + world.name = "[config_server_name][config_server_name == GLOB.station_name ? "" : ": [GLOB.station_name]"]" + else + world.name = GLOB.station_name + + +/proc/new_station_name() + var/random = rand(1,5) + var/name = "" + var/new_station_name = "" + + //Rare: Pre-Prefix + if (prob(10)) + name = pick(GLOB.station_prefixes) + new_station_name = name + " " + name = "" + + // Prefix + var/holiday_name = pick(SSevents.holidays) + if(holiday_name) + var/datum/holiday/holiday = SSevents.holidays[holiday_name] + if(istype(holiday, /datum/holiday/friday_thirteenth)) + random = 13 + name = holiday.getStationPrefix() + //get normal name + if(!name) + name = pick(GLOB.station_names) + if(name) + new_station_name += name + " " + + // Suffix + name = pick(GLOB.station_suffixes) + new_station_name += name + " " + + // ID Number + switch(random) + if(1) + new_station_name += "[rand(1, 99)]" + if(2) + new_station_name += pick(GLOB.greek_letters) + if(3) + new_station_name += "\Roman[rand(1,99)]" + if(4) + new_station_name += pick(GLOB.phonetic_alphabet) + if(5) + new_station_name += pick(GLOB.numbers_as_words) + if(13) + new_station_name += pick("13","XIII","Thirteen") + return new_station_name + +/proc/syndicate_name() + var/name = "" + + // Prefix + name += pick("Clandestine", "Prima", "Blue", "Zero-G", "Max", "Blasto", "Waffle", "North", "Omni", "Newton", "Cyber", "Bonk", "Gene", "Gib") + + // Suffix + if (prob(80)) + name += " " + + // Full + if (prob(60)) + name += pick("Syndicate", "Consortium", "Collective", "Corporation", "Group", "Holdings", "Biotech", "Industries", "Systems", "Products", "Chemicals", "Enterprises", "Family", "Creations", "International", "Intergalactic", "Interplanetary", "Foundation", "Positronics", "Hive") + // Broken + else + name += pick("Syndi", "Corp", "Bio", "System", "Prod", "Chem", "Inter", "Hive") + name += pick("", "-") + name += pick("Tech", "Sun", "Co", "Tek", "X", "Inc", "Code") + // Small + else + name += pick("-", "*", "") + name += pick("Tech", "Sun", "Co", "Tek", "X", "Inc", "Gen", "Star", "Dyne", "Code", "Hive") + + return name + + +//Traitors and traitor silicons will get these. Revs will not. +GLOBAL_VAR(syndicate_code_phrase) //Code phrase for traitors. +GLOBAL_VAR(syndicate_code_response) //Code response for traitors. + +//Cached regex search - for checking if codewords are used. +GLOBAL_DATUM(syndicate_code_phrase_regex, /regex) +GLOBAL_DATUM(syndicate_code_response_regex, /regex) + + /* + Should be expanded. + How this works: + Instead of "I'm looking for James Smith," the traitor would say "James Smith" as part of a conversation. + Another traitor may then respond with: "They enjoy running through the void-filled vacuum of the derelict." + The phrase should then have the words: James Smith. + The response should then have the words: run, void, and derelict. + This way assures that the code is suited to the conversation and is unpredicatable. + Obviously, some people will be better at this than others but in theory, everyone should be able to do it and it only enhances roleplay. + Can probably be done through "{ }" but I don't really see the practical benefit. + One example of an earlier system is commented below. + /N + */ + +/proc/generate_code_phrase(return_list=FALSE)//Proc is used for phrase and response in master_controller.dm + + if(!return_list) + . = "" + else + . = list() + + var/words = pick(//How many words there will be. Minimum of two. 2, 4 and 5 have a lesser chance of being selected. 3 is the most likely. + 50; 2, + 200; 3, + 50; 4, + 25; 5 + ) + + var/list/safety = list(1,2,3)//Tells the proc which options to remove later on. + var/nouns = strings(ION_FILE, "ionabstract") + var/objects = strings(ION_FILE, "ionobjects") + var/adjectives = strings(ION_FILE, "ionadjectives") + var/threats = strings(ION_FILE, "ionthreats") + var/foods = strings(ION_FILE, "ionfood") + var/drinks = strings(ION_FILE, "iondrinks") + var/locations = strings(LOCATIONS_FILE, "locations") + + var/list/names = list() + for(var/datum/data/record/t in GLOB.data_core.general)//Picks from crew manifest. + names += t.fields["name"] + + var/maxwords = words//Extra var to check for duplicates. + + for(words,words>0,words--)//Randomly picks from one of the choices below. + + if(words==1&&(1 in safety)&&(2 in safety))//If there is only one word remaining and choice 1 or 2 have not been selected. + safety = list(pick(1,2))//Select choice 1 or 2. + else if(words==1&&maxwords==2)//Else if there is only one word remaining (and there were two originally), and 1 or 2 were chosen, + safety = list(3)//Default to list 3 + + switch(pick(safety))//Chance based on the safety list. + if(1)//1 and 2 can only be selected once each to prevent more than two specific names/places/etc. + switch(rand(1,2))//Mainly to add more options later. + if(1) + if(names.len&&prob(70)) + . += pick(names) + else + if(prob(10)) + . += pick(lizard_name(MALE),lizard_name(FEMALE)) + else + var/new_name = pick(pick(GLOB.first_names_male,GLOB.first_names_female)) + new_name += " " + new_name += pick(GLOB.last_names) + . += new_name + if(2) + . += pick(get_all_jobs())//Returns a job. + safety -= 1 + if(2) + switch(rand(1,3))//Food, drinks, or places. Only selectable once. + if(1) + . += lowertext(pick(drinks)) + if(2) + . += lowertext(pick(foods)) + if(3) + . += lowertext(pick(locations)) + safety -= 2 + if(3) + switch(rand(1,4))//Abstract nouns, objects, adjectives, threats. Can be selected more than once. + if(1) + . += lowertext(pick(nouns)) + if(2) + . += lowertext(pick(objects)) + if(3) + . += lowertext(pick(adjectives)) + if(4) + . += lowertext(pick(threats)) + if(!return_list) + if(words==1) + . += "." + else + . += ", " diff --git a/code/__HELPERS/qdel.dm b/code/__HELPERS/qdel.dm index 9bd0d4c95ff7..0d2bf8915293 100644 --- a/code/__HELPERS/qdel.dm +++ b/code/__HELPERS/qdel.dm @@ -1,10 +1,10 @@ -#define QDEL_IN(item, time) addtimer(CALLBACK(GLOBAL_PROC, .proc/qdel, item), time, TIMER_STOPPABLE) -#define QDEL_IN_CLIENT_TIME(item, time) addtimer(CALLBACK(GLOBAL_PROC, .proc/qdel, item), time, TIMER_STOPPABLE | TIMER_CLIENT_TIME) -#define QDEL_NULL(item) qdel(item); item = null -#define QDEL_LIST(L) if(L) { for(var/I in L) qdel(I); L.Cut(); } -#define QDEL_LIST_IN(L, time) addtimer(CALLBACK(GLOBAL_PROC, .proc/______qdel_list_wrapper, L), time, TIMER_STOPPABLE) -#define QDEL_LIST_ASSOC(L) if(L) { for(var/I in L) { qdel(L[I]); qdel(I); } L.Cut(); } -#define QDEL_LIST_ASSOC_VAL(L) if(L) { for(var/I in L) qdel(L[I]); L.Cut(); } - -/proc/______qdel_list_wrapper(list/L) //the underscores are to encourage people not to use this directly. - QDEL_LIST(L) +#define QDEL_IN(item, time) addtimer(CALLBACK(GLOBAL_PROC, .proc/qdel, item), time, TIMER_STOPPABLE) +#define QDEL_IN_CLIENT_TIME(item, time) addtimer(CALLBACK(GLOBAL_PROC, .proc/qdel, item), time, TIMER_STOPPABLE | TIMER_CLIENT_TIME) +#define QDEL_NULL(item) qdel(item); item = null +#define QDEL_LIST(L) if(L) { for(var/I in L) qdel(I); L.Cut(); } +#define QDEL_LIST_IN(L, time) addtimer(CALLBACK(GLOBAL_PROC, .proc/______qdel_list_wrapper, L), time, TIMER_STOPPABLE) +#define QDEL_LIST_ASSOC(L) if(L) { for(var/I in L) { qdel(L[I]); qdel(I); } L.Cut(); } +#define QDEL_LIST_ASSOC_VAL(L) if(L) { for(var/I in L) qdel(L[I]); L.Cut(); } + +/proc/______qdel_list_wrapper(list/L) //the underscores are to encourage people not to use this directly. + QDEL_LIST(L) diff --git a/code/__HELPERS/radio.dm b/code/__HELPERS/radio.dm index a8baf3a1d7d2..dc52299025f4 100644 --- a/code/__HELPERS/radio.dm +++ b/code/__HELPERS/radio.dm @@ -1,19 +1,19 @@ -// Ensure the frequency is within bounds of what it should be sending/receiving at -/proc/sanitize_frequency(frequency, free = FALSE) - frequency = round(frequency) - if(free) - . = clamp(frequency, MIN_FREE_FREQ, MAX_FREE_FREQ) - else - . = clamp(frequency, MIN_FREQ, MAX_FREQ) - if(!(. % 2)) // Ensure the last digit is an odd number - . += 1 - -// Format frequency by moving the decimal. -/proc/format_frequency(frequency) - frequency = text2num(frequency) - return "[round(frequency / 10)].[frequency % 10]" - -//Opposite of format, returns as a number -/proc/unformat_frequency(frequency) - frequency = text2num(frequency) - return frequency * 10 +// Ensure the frequency is within bounds of what it should be sending/receiving at +/proc/sanitize_frequency(frequency, free = FALSE) + frequency = round(frequency) + if(free) + . = clamp(frequency, MIN_FREE_FREQ, MAX_FREE_FREQ) + else + . = clamp(frequency, MIN_FREQ, MAX_FREQ) + if(!(. % 2)) // Ensure the last digit is an odd number + . += 1 + +// Format frequency by moving the decimal. +/proc/format_frequency(frequency) + frequency = text2num(frequency) + return "[round(frequency / 10)].[frequency % 10]" + +//Opposite of format, returns as a number +/proc/unformat_frequency(frequency) + frequency = text2num(frequency) + return frequency * 10 diff --git a/code/__HELPERS/roundend.dm b/code/__HELPERS/roundend.dm index 61eb93869f23..dcc9e2a0454e 100644 --- a/code/__HELPERS/roundend.dm +++ b/code/__HELPERS/roundend.dm @@ -212,15 +212,14 @@ speed_round = TRUE for(var/client/C in GLOB.clients) - if(!C.credits) - C.RollCredits() C.playtitlemusic(40) - C.process_endround_metacoin() if(speed_round) C.give_award(/datum/award/achievement/misc/speed_round, C.mob) + RollCredits() + var/popcount = gather_roundend_feedback() display_report(popcount) @@ -267,21 +266,6 @@ var/list/L = total_antagonists[antag_name] log_game("[antag_name]s :[L.Join(", ")].") - CHECK_TICK - - if(CONFIG_GET(flag/allow_crew_objectives)) - for(var/datum/mind/crewMind in minds) - if(!crewMind.current || !length(crewMind.crew_objectives)) - continue - for(var/datum/objective/crew/CO in crewMind.crew_objectives) - var/client/C = CO.owner - if(CO.check_completion()) - C.inc_metabalance(METACOIN_CO_REWARD, reason="Completed your crew objective!") //Waspstation Edit - Metacoin - to_chat(crewMind.current, "
Your optional objective: [CO.explanation_text] Success!") - SSticker.successfulCrew += "[crewMind.current.real_name] (Played by: [crewMind.key])
Optional Objective: [CO.explanation_text] Success!" - else - to_chat(crewMind.current, "
Your optional objective: [CO.explanation_text] Failed.") - CHECK_TICK SSdbcore.SetRoundEnd() //Collects persistence features @@ -415,6 +399,16 @@ else parts += "
" parts += "You did not survive the events on [station_name()]..." + + if(CONFIG_GET(flag/allow_crew_objectives)) + if(M.mind.current && LAZYLEN(M.mind.crew_objectives)) + for(var/datum/objective/crew/CO in M.mind.crew_objectives) + if(CO.check_completion()) + parts += "

Your optional objective: [CO.explanation_text] Success!
" + C.inc_metabalance(METACOIN_CO_REWARD, reason="Completed your crew objective!") + else + parts += "

Your optional objective: [CO.explanation_text] Failed.
" + else parts += "
" parts += "
" @@ -629,9 +623,7 @@ 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") query_admin_rank_update.Execute() @@ -666,15 +658,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/DBQuery/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/DBQuery/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/sanitize_values.dm b/code/__HELPERS/sanitize_values.dm index 9a2e18f31565..b91f99c142b3 100644 --- a/code/__HELPERS/sanitize_values.dm +++ b/code/__HELPERS/sanitize_values.dm @@ -1,88 +1,88 @@ -//general stuff -/proc/sanitize_integer(number, min=0, max=1, default=0) - if(isnum(number)) - number = round(number) - if(min <= number && number <= max) - return number - return default - -/proc/sanitize_text(text, default="") - if(istext(text)) - return text - return default - -/proc/sanitize_islist(value, default) - if(islist(value) && length(value)) - return value - if(default) - return default - -/proc/sanitize_inlist(value, list/List, default) - if(value in List) - return value - if(default) - return default - if(List && List.len) - return pick(List) - - - -//more specialised stuff -/proc/sanitize_gender(gender,neuter=0,plural=1, default="male") - switch(gender) - if(MALE, FEMALE) - return gender - if(NEUTER) - if(neuter) - return gender - else - return default - if(PLURAL) - if(plural) - return gender - else - return default - return default - -/proc/sanitize_hexcolor(color, desired_format=3, include_crunch=0, default) - var/crunch = include_crunch ? "#" : "" - if(!istext(color)) - color = "" - - var/start = 1 + (text2ascii(color, 1) == 35) - var/len = length(color) - var/char = "" - // RRGGBB -> RGB but awful - var/convert_to_shorthand = desired_format == 3 && length_char(color) > 3 - - . = "" - var/i = start - while(i <= len) - char = color[i] - switch(text2ascii(char)) - if(48 to 57) //numbers 0 to 9 - . += char - if(97 to 102) //letters a to f - . += char - if(65 to 70) //letters A to F - . += lowertext(char) - else - break - i += length(char) - if(convert_to_shorthand && i <= len) //skip next one - i += length(color[i]) - - if(length_char(.) != desired_format) - if(default) - return default - return crunch + repeat_string(desired_format, "0") - - return crunch + . - -/proc/sanitize_ooccolor(color) - if(length(color) != length_char(color)) - CRASH("Invalid characters in color '[color]'") - var/list/HSL = rgb2hsl(hex2num(copytext(color, 2, 4)), hex2num(copytext(color, 4, 6)), hex2num(copytext(color, 6, 8))) - HSL[3] = min(HSL[3],0.4) - var/list/RGB = hsl2rgb(arglist(HSL)) - return "#[num2hex(RGB[1],2)][num2hex(RGB[2],2)][num2hex(RGB[3],2)]" +//general stuff +/proc/sanitize_integer(number, min=0, max=1, default=0) + if(isnum(number)) + number = round(number) + if(min <= number && number <= max) + return number + return default + +/proc/sanitize_text(text, default="") + if(istext(text)) + return text + return default + +/proc/sanitize_islist(value, default) + if(islist(value) && length(value)) + return value + if(default) + return default + +/proc/sanitize_inlist(value, list/List, default) + if(value in List) + return value + if(default) + return default + if(List && List.len) + return pick(List) + + + +//more specialised stuff +/proc/sanitize_gender(gender,neuter=0,plural=1, default="male") + switch(gender) + if(MALE, FEMALE) + return gender + if(NEUTER) + if(neuter) + return gender + else + return default + if(PLURAL) + if(plural) + return gender + else + return default + return default + +/proc/sanitize_hexcolor(color, desired_format=3, include_crunch=0, default) + var/crunch = include_crunch ? "#" : "" + if(!istext(color)) + color = "" + + var/start = 1 + (text2ascii(color, 1) == 35) + var/len = length(color) + var/char = "" + // RRGGBB -> RGB but awful + var/convert_to_shorthand = desired_format == 3 && length_char(color) > 3 + + . = "" + var/i = start + while(i <= len) + char = color[i] + switch(text2ascii(char)) + if(48 to 57) //numbers 0 to 9 + . += char + if(97 to 102) //letters a to f + . += char + if(65 to 70) //letters A to F + . += lowertext(char) + else + break + i += length(char) + if(convert_to_shorthand && i <= len) //skip next one + i += length(color[i]) + + if(length_char(.) != desired_format) + if(default) + return default + return crunch + repeat_string(desired_format, "0") + + return crunch + . + +/proc/sanitize_ooccolor(color) + if(length(color) != length_char(color)) + CRASH("Invalid characters in color '[color]'") + var/list/HSL = rgb2hsl(hex2num(copytext(color, 2, 4)), hex2num(copytext(color, 4, 6)), hex2num(copytext(color, 6, 8))) + HSL[3] = min(HSL[3],0.4) + var/list/RGB = hsl2rgb(arglist(HSL)) + return "#[num2hex(RGB[1],2)][num2hex(RGB[2],2)][num2hex(RGB[3],2)]" diff --git a/code/__HELPERS/text.dm b/code/__HELPERS/text.dm index 5249e37453e9..cb3a1df40e10 100644 --- a/code/__HELPERS/text.dm +++ b/code/__HELPERS/text.dm @@ -1,823 +1,829 @@ -/* - * Holds procs designed to help with filtering text - * Contains groups: - * SQL sanitization/formating - * Text sanitization - * Text searches - * Text modification - * Misc - */ - - -/* - * 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 - -/* - * Text sanitization - */ - -//Simply removes < and > and limits the length of the message -/proc/strip_html_simple(t,limit=MAX_MESSAGE_LEN) - var/list/strip_chars = list("<",">") - t = copytext(t,1,limit) - for(var/char in strip_chars) - var/index = findtext(t, char) - while(index) - t = copytext(t, 1, index) + copytext(t, index+1) - index = findtext(t, char) - return t - -//Removes a few problematic characters -/proc/sanitize_simple(t,list/repl_chars = list("\n"="#","\t"="#")) - for(var/char in repl_chars) - var/index = findtext(t, char) - while(index) - t = copytext(t, 1, index) + repl_chars[char] + copytext(t, index + length(char)) - index = findtext(t, char, index + length(char)) - return t - -/proc/sanitize_filename(t) - return sanitize_simple(t, list("\n"="", "\t"="", "/"="", "\\"="", "?"="", "%"="", "*"="", ":"="", "|"="", "\""="", "<"="", ">"="")) - -///returns nothing with an alert instead of the message if it contains something in the ic filter, and sanitizes normally if the name is fine. It returns nothing so it backs out of the input the same way as if you had entered nothing. -/proc/sanitize_name(t,list/repl_chars = null) - if(CHAT_FILTER_CHECK(t)) - alert("You cannot set a name that contains a word prohibited in IC chat!") - return "" - if(t == "space" || t == "floor" || t == "wall" || t == "r-wall" || t == "monkey" || t == "unknown" || t == "inactive ai") //prevents these common metagamey names - alert("Invalid name.") - return "" - return sanitize(t) - -//Runs byond's sanitization proc along-side sanitize_simple -/proc/sanitize(t,list/repl_chars = null) - return html_encode(sanitize_simple(t,repl_chars)) - -//Runs sanitize and strip_html_simple -//I believe strip_html_simple() is required to run first to prevent '<' from displaying as '<' after sanitize() calls byond's html_encode() -/proc/strip_html(t,limit=MAX_MESSAGE_LEN) - return copytext((sanitize(strip_html_simple(t))),1,limit) - -//Runs byond's sanitization proc along-side strip_html_simple -//I believe strip_html_simple() is required to run first to prevent '<' from displaying as '<' that html_encode() would cause -/proc/adminscrub(t,limit=MAX_MESSAGE_LEN) - return copytext((html_encode(strip_html_simple(t))),1,limit) - - -//Returns null if there is any bad text in the string -/proc/reject_bad_text(text, max_length = 512, ascii_only = TRUE) - var/char_count = 0 - var/non_whitespace = FALSE - var/lenbytes = length(text) - var/char = "" - for(var/i = 1, i <= lenbytes, i += length(char)) - char = text[i] - char_count++ - if(char_count > max_length) - return - switch(text2ascii(char)) - if(62, 60, 92, 47) // <, >, \, / - return - if(0 to 31) - return - if(32) - continue - if(127 to INFINITY) - if(ascii_only) - return - else - non_whitespace = TRUE - if(non_whitespace) - return text //only accepts the text if it has some non-spaces - -// Used to get a properly sanitized input, of max_length -// no_trim is self explanatory but it prevents the input from being trimed if you intend to parse newlines or whitespace. -/proc/stripped_input(mob/user, message = "", title = "", default = "", max_length=MAX_MESSAGE_LEN, no_trim=FALSE) - var/name = input(user, message, title, default) as text|null - if(no_trim) - return copytext(html_encode(name), 1, max_length) - else - return trim(html_encode(name), max_length) //trim is "outside" because html_encode can expand single symbols into multiple symbols (such as turning < into <) - -// Used to get a properly sanitized multiline input, of max_length -/proc/stripped_multiline_input(mob/user, message = "", title = "", default = "", max_length=MAX_MESSAGE_LEN, no_trim=FALSE) - var/name = input(user, message, title, default) as message|null - if(no_trim) - return copytext(html_encode(name), 1, max_length) - else - return trim(html_encode(name), max_length) - -#define NO_CHARS_DETECTED 0 -#define SPACES_DETECTED 1 -#define SYMBOLS_DETECTED 2 -#define NUMBERS_DETECTED 3 -#define LETTERS_DETECTED 4 - -//Filters out undesirable characters from names -/proc/reject_bad_name(t_in, allow_numbers = FALSE, max_length = MAX_NAME_LEN, ascii_only = TRUE) - if(!t_in) - return //Rejects the input if it is null - - var/number_of_alphanumeric = 0 - var/last_char_group = NO_CHARS_DETECTED - var/t_out = "" - var/t_len = length(t_in) - var/charcount = 0 - var/char = "" - - - for(var/i = 1, i <= t_len, i += length(char)) - char = t_in[i] - - switch(text2ascii(char)) - // A .. Z - if(65 to 90) //Uppercase Letters - number_of_alphanumeric++ - last_char_group = LETTERS_DETECTED - - // a .. z - if(97 to 122) //Lowercase Letters - if(last_char_group == NO_CHARS_DETECTED || last_char_group == SPACES_DETECTED || last_char_group == SYMBOLS_DETECTED) //start of a word - char = uppertext(char) - number_of_alphanumeric++ - last_char_group = LETTERS_DETECTED - - // 0 .. 9 - if(48 to 57) //Numbers - if(last_char_group == NO_CHARS_DETECTED || !allow_numbers) //suppress at start of string - continue - number_of_alphanumeric++ - last_char_group = NUMBERS_DETECTED - - // ' - . - if(39,45,46) //Common name punctuation - if(last_char_group == NO_CHARS_DETECTED) - continue - last_char_group = SYMBOLS_DETECTED - - // ~ | @ : # $ % & * + - if(126,124,64,58,35,36,37,38,42,43) //Other symbols that we'll allow (mainly for AI) - if(last_char_group == NO_CHARS_DETECTED || !allow_numbers) //suppress at start of string - continue - last_char_group = SYMBOLS_DETECTED - - //Space - if(32) - if(last_char_group == NO_CHARS_DETECTED || last_char_group == SPACES_DETECTED) //suppress double-spaces and spaces at start of string - continue - last_char_group = SPACES_DETECTED - - if(127 to INFINITY) - if(ascii_only) - continue - last_char_group = SYMBOLS_DETECTED //for now, we'll treat all non-ascii characters like symbols even though most are letters - - else - continue - - t_out += char - charcount++ - if(charcount >= max_length) - break - - if(number_of_alphanumeric < 2) - return //protects against tiny names like "A" and also names like "' ' ' ' ' ' ' '" - - if(last_char_group == SPACES_DETECTED) - t_out = copytext_char(t_out, 1, -1) //removes the last character (in this case a space) - - for(var/bad_name in list("space","floor","wall","r-wall","monkey","unknown","inactive ai")) //prevents these common metagamey names - if(cmptext(t_out,bad_name)) - return //(not case sensitive) - - return t_out - -#undef NO_CHARS_DETECTED -#undef SPACES_DETECTED -#undef NUMBERS_DETECTED -#undef LETTERS_DETECTED - - - -//html_encode helper proc that returns the smallest non null of two numbers -//or 0 if they're both null (needed because of findtext returning 0 when a value is not present) -/proc/non_zero_min(a, b) - if(!a) - return b - if(!b) - return a - return (a < b ? a : b) - -//Checks if any of a given list of needles is in the haystack -/proc/text_in_list(haystack, list/needle_list, start=1, end=0) - for(var/needle in needle_list) - if(findtext(haystack, needle, start, end)) - return 1 - return 0 - -//Like above, but case sensitive -/proc/text_in_list_case(haystack, list/needle_list, start=1, end=0) - for(var/needle in needle_list) - if(findtextEx(haystack, needle, start, end)) - return 1 - return 0 - -//Adds 'char' ahead of 'text' until there are 'count' characters total -/proc/add_leading(text, count, char = " ") - text = "[text]" - var/charcount = count - length_char(text) - var/list/chars_to_add[max(charcount + 1, 0)] - return jointext(chars_to_add, char) + text - -//Adds 'char' behind 'text' until there are 'count' characters total -/proc/add_trailing(text, count, char = " ") - text = "[text]" - var/charcount = count - length_char(text) - var/list/chars_to_add[max(charcount + 1, 0)] - return text + jointext(chars_to_add, char) - -//Returns a string with reserved characters and spaces before the first letter removed -/proc/trim_left(text) - for (var/i = 1 to length(text)) - if (text2ascii(text, i) > 32) - return copytext(text, i) - return "" - -//Returns a string with reserved characters and spaces after the last letter removed -/proc/trim_right(text) - for (var/i = length(text), i > 0, i--) - if (text2ascii(text, i) > 32) - return copytext(text, 1, i + 1) - return "" - -//Returns a string with reserved characters and spaces before the first word and after the last word removed. -/proc/trim(text, max_length) - if(max_length) - text = copytext_char(text, 1, max_length) - return trim_left(trim_right(text)) - -//Returns a string with the first element of the string capitalized. -/proc/capitalize(t) - . = t - if(t) - . = t[1] - return uppertext(.) + copytext(t, 1 + length(.)) - -/proc/stringmerge(text,compare,replace = "*") -//This proc fills in all spaces with the "replace" var (* by default) with whatever -//is in the other string at the same spot (assuming it is not a replace char). -//This is used for fingerprints - var/newtext = text - var/text_it = 1 //iterators - var/comp_it = 1 - var/newtext_it = 1 - var/text_length = length(text) - var/comp_length = length(compare) - while(comp_it <= comp_length && text_it <= text_length) - var/a = text[text_it] - var/b = compare[comp_it] -//if it isn't both the same letter, or if they are both the replacement character -//(no way to know what it was supposed to be) - if(a != b) - if(a == replace) //if A is the replacement char - newtext = copytext(newtext, 1, newtext_it) + b + copytext(newtext, newtext_it + length(newtext[newtext_it])) - else if(b == replace) //if B is the replacement char - newtext = copytext(newtext, 1, newtext_it) + a + copytext(newtext, newtext_it + length(newtext[newtext_it])) - else //The lists disagree, Uh-oh! - return 0 - text_it += length(a) - comp_it += length(b) - newtext_it += length(newtext[newtext_it]) - - return newtext - -/proc/stringpercent(text,character = "*") -//This proc returns the number of chars of the string that is the character -//This is used for detective work to determine fingerprint completion. - if(!text || !character) - return 0 - var/count = 0 - var/lentext = length(text) - var/a = "" - for(var/i = 1, i <= lentext, i += length(a)) - a = text[i] - if(a == character) - count++ - return count - -/proc/reverse_text(text = "") - var/new_text = "" - var/lentext = length(text) - var/letter = "" - for(var/i = 1, i <= lentext, i += length(letter)) - letter = text[i] - new_text = letter + new_text - return new_text - -GLOBAL_LIST_INIT(zero_character_only, list("0")) -GLOBAL_LIST_INIT(hex_characters, list("0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f")) -GLOBAL_LIST_INIT(alphabet, list("a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z")) -GLOBAL_LIST_INIT(binary, list("0","1")) -/proc/random_string(length, list/characters) - . = "" - for(var/i=1, i<=length, i++) - . += pick(characters) - -/proc/repeat_string(times, string="") - . = "" - for(var/i=1, i<=times, i++) - . += string - -/proc/random_short_color() - return random_string(3, GLOB.hex_characters) - -/proc/random_color() - return random_string(6, GLOB.hex_characters) - -//merges non-null characters (3rd argument) from "from" into "into". Returns result -//e.g. into = "Hello World" -// from = "Seeya______" -// returns"Seeya World" -//The returned text is always the same length as into -//This was coded to handle DNA gene-splicing. -/proc/merge_text(into, from, null_char="_") - . = "" - if(!istext(into)) - into = "" - if(!istext(from)) - from = "" - var/null_ascii = istext(null_char) ? text2ascii(null_char, 1) : null_char - var/copying_into = FALSE - var/char = "" - var/start = 1 - var/end_from = length(from) - var/end_into = length(into) - var/into_it = 1 - var/from_it = 1 - while(from_it <= end_from && into_it <= end_into) - char = from[from_it] - if(text2ascii(char) == null_ascii) - if(!copying_into) - . += copytext(from, start, from_it) - start = into_it - copying_into = TRUE - else - if(copying_into) - . += copytext(into, start, into_it) - start = from_it - copying_into = FALSE - into_it += length(into[into_it]) - from_it += length(char) - - if(copying_into) - . += copytext(into, start) - else - . += copytext(from, start, from_it) - if(into_it <= end_into) - . += copytext(into, into_it) - -//finds the first occurrence of one of the characters from needles argument inside haystack -//it may appear this can be optimised, but it really can't. findtext() is so much faster than anything you can do in byondcode. -//stupid byond :( -/proc/findchar(haystack, needles, start=1, end=0) - var/char = "" - var/len = length(needles) - for(var/i = 1, i <= len, i += length(char)) - char = needles[i] - . = findtextEx(haystack, char, start, end) - if(.) - return - return 0 - -/proc/parsemarkdown_basic_step1(t, limited=FALSE) - if(length(t) <= 0) - return - - // This parses markdown with no custom rules - - // Escape backslashed - - t = replacetext(t, "$", "$-") - t = replacetext(t, "\\\\", "$1") - t = replacetext(t, "\\**", "$2") - t = replacetext(t, "\\*", "$3") - t = replacetext(t, "\\__", "$4") - t = replacetext(t, "\\_", "$5") - t = replacetext(t, "\\^", "$6") - t = replacetext(t, "\\((", "$7") - t = replacetext(t, "\\))", "$8") - t = replacetext(t, "\\|", "$9") - t = replacetext(t, "\\%", "$0") - - // Escape single characters that will be used - - t = replacetext(t, "!", "$a") - - // Parse hr and small - - if(!limited) - t = replacetext(t, "((", "") - t = replacetext(t, "))", "") - t = replacetext(t, regex("(-){3,}", "gm"), "
") - t = replacetext(t, regex("^\\((-){3,}\\)$", "gm"), "$1") - - // Parse lists - - var/list/tlist = splittext(t, "\n") - var/tlistlen = tlist.len - var/listlevel = -1 - var/singlespace = -1 // if 0, double spaces are used before asterisks, if 1, single are - for(var/i = 1, i <= tlistlen, i++) - var/line = tlist[i] - var/count_asterisk = length(replacetext(line, regex("\[^\\*\]+", "g"), "")) - if(count_asterisk % 2 == 1 && findtext(line, regex("^\\s*\\*", "g"))) // there is an extra asterisk in the beggining - - var/count_w = length(replacetext(line, regex("^( *)\\*.*$", "g"), "$1")) // whitespace before asterisk - line = replacetext(line, regex("^ *(\\*.*)$", "g"), "$1") - - if(singlespace == -1 && count_w == 2) - if(listlevel == 0) - singlespace = 0 - else - singlespace = 1 - - if(singlespace == 0) - count_w = count_w % 2 ? round(count_w / 2 + 0.25) : count_w / 2 - - line = replacetext(line, regex("\\*", ""), "
  • ") - while(listlevel < count_w) - line = "
      " + line - listlevel++ - while(listlevel > count_w) - line = "
    " + line - listlevel-- - - else while(listlevel >= 0) - line = "" + line - listlevel-- - - tlist[i] = line - // end for - - t = tlist[1] - for(var/i = 2, i <= tlistlen, i++) - t += "\n" + tlist[i] - - while(listlevel >= 0) - t += "" - listlevel-- - - else - t = replacetext(t, "((", "") - t = replacetext(t, "))", "") - - // Parse headers - - t = replacetext(t, regex("^#(?!#) ?(.+)$", "gm"), "

    $1

    ") - t = replacetext(t, regex("^##(?!#) ?(.+)$", "gm"), "

    $1

    ") - t = replacetext(t, regex("^###(?!#) ?(.+)$", "gm"), "

    $1

    ") - t = replacetext(t, regex("^#### ?(.+)$", "gm"), "
    $1
    ") - - // Parse most rules - - t = replacetext(t, regex("\\*(\[^\\*\]*)\\*", "g"), "$1") - t = replacetext(t, regex("_(\[^_\]*)_", "g"), "$1") - t = replacetext(t, "", "!") - t = replacetext(t, "", "!") - t = replacetext(t, regex("\\!(\[^\\!\]+)\\!", "g"), "$1") - t = replacetext(t, regex("\\^(\[^\\^\]+)\\^", "g"), "$1") - t = replacetext(t, regex("\\|(\[^\\|\]+)\\|", "g"), "
    $1
    ") - t = replacetext(t, "!", "
    ") - - return t - -/proc/parsemarkdown_basic_step2(t) - if(length(t) <= 0) - return - - // Restore the single characters used - - t = replacetext(t, "$a", "!") - - // Redo the escaping - - t = replacetext(t, "$1", "\\") - t = replacetext(t, "$2", "**") - t = replacetext(t, "$3", "*") - t = replacetext(t, "$4", "__") - t = replacetext(t, "$5", "_") - t = replacetext(t, "$6", "^") - t = replacetext(t, "$7", "((") - t = replacetext(t, "$8", "))") - t = replacetext(t, "$9", "|") - t = replacetext(t, "$0", "%") - t = replacetext(t, "$-", "$") - - return t - -/proc/parsemarkdown_basic(t, limited=FALSE) - t = parsemarkdown_basic_step1(t, limited) - t = parsemarkdown_basic_step2(t) - return t - -/proc/parsemarkdown(t, mob/user=null, limited=FALSE) - if(length(t) <= 0) - return - - // Premanage whitespace - - t = replacetext(t, regex("\[^\\S\\r\\n \]", "g"), " ") - - t = parsemarkdown_basic_step1(t) - - t = replacetext(t, regex("%s(?:ign)?(?=\\s|$)", "igm"), user ? "[user.real_name]" : "") - t = replacetext(t, regex("%f(?:ield)?(?=\\s|$)", "igm"), "") - - t = parsemarkdown_basic_step2(t) - - // Manage whitespace - - t = replacetext(t, regex("(?:\\r\\n?|\\n)", "g"), "
    ") - - t = replacetext(t, " ", "  ") - - // Done - - return t - -/proc/text2charlist(text) - var/char = "" - var/lentext = length(text) - . = list() - for(var/i = 1, i <= lentext, i += length(char)) - char = text[i] - . += char - -/proc/rot13(text = "") - var/lentext = length(text) - var/char = "" - var/ascii = 0 - . = "" - for(var/i = 1, i <= lentext, i += length(char)) - char = text[i] - ascii = text2ascii(char) - switch(ascii) - if(65 to 77, 97 to 109) //A to M, a to m - ascii += 13 - if(78 to 90, 110 to 122) //N to Z, n to z - ascii -= 13 - . += ascii2text(ascii) - -//Takes a list of values, sanitizes it down for readability and character count, -//then exports it as a json file at data/npc_saves/[filename].json. -//As far as SS13 is concerned this is write only data. You can't change something -//in the json file and have it be reflected in the in game item/mob it came from. -//(That's what things like savefiles are for) Note that this list is not shuffled. -/proc/twitterize(list/proposed, filename, cullshort = 1, storemax = 1000) - if(!islist(proposed) || !filename || !CONFIG_GET(flag/log_twitter)) - return - - //Regular expressions are, as usual, absolute magic - //Any characters outside of 32 (space) to 126 (~) because treating things you don't understand as "magic" is really stupid - var/regex/all_invalid_symbols = new(@"[^ -~]{1}") - - var/list/accepted = list() - for(var/string in proposed) - if(findtext(string,GLOB.is_website) || findtext(string,GLOB.is_email) || findtext(string,all_invalid_symbols) || !findtext(string,GLOB.is_alphanumeric)) - continue - var/buffer = "" - var/early_culling = TRUE - var/lentext = length(string) - var/let = "" - - for(var/pos = 1, pos <= lentext, pos += length(let)) - let = string[pos] - if(!findtext(let, GLOB.is_alphanumeric)) - continue - early_culling = FALSE - buffer = copytext(string, pos) - break - if(early_culling) //Never found any letters! Bail! - continue - - var/punctbuffer = "" - var/cutoff = 0 - lentext = length_char(buffer) - for(var/pos = 1, pos <= lentext, pos++) - let = copytext_char(buffer, -pos, -pos + 1) - if(!findtext(let, GLOB.is_punctuation)) //This won't handle things like Nyaaaa!~ but that's fine - break - punctbuffer += let - cutoff += length(let) - if(punctbuffer) //We clip down excessive punctuation to get the letter count lower and reduce repeats. It's not perfect but it helps. - var/exclaim = FALSE - var/question = FALSE - var/periods = 0 - lentext = length(punctbuffer) - for(var/pos = 1, pos <= lentext, pos += length(let)) - let = punctbuffer[pos] - if(!exclaim && findtext(let, "!")) - exclaim = TRUE - if(question) - break - if(!question && findtext(let, "?")) - question = TRUE - if(exclaim) - break - if(!exclaim && !question && findtext(let, ".")) //? and ! take priority over periods - periods += 1 - if(exclaim) - if(question) - punctbuffer = "?!" - else - punctbuffer = "!" - else if(question) - punctbuffer = "?" - else if(periods > 1) - punctbuffer = "..." - else - punctbuffer = "" //Grammer nazis be damned - buffer = copytext(buffer, 1, -cutoff) + punctbuffer - lentext = length_char(buffer) - if(!buffer || lentext > 280 || lentext <= cullshort || (buffer in accepted)) - continue - - accepted += buffer - - var/log = file("data/npc_saves/[filename].json") //If this line ever shows up as changed in a PR be very careful you aren't being memed on - var/list/oldjson = list() - var/list/oldentries = list() - if(fexists(log)) - oldjson = json_decode(file2text(log)) - oldentries = oldjson["data"] - if(length(oldentries)) - for(var/string in accepted) - for(var/old in oldentries) - if(string == old) - oldentries.Remove(old) //Line's position in line is "refreshed" until it falls off the in game radar - break - - var/list/finalized = list() - finalized = accepted.Copy() + oldentries.Copy() //we keep old and unreferenced phrases near the bottom for culling - listclearnulls(finalized) - if(length(finalized) > storemax) - finalized.Cut(storemax + 1) - fdel(log) - - var/list/tosend = list() - tosend["data"] = finalized - WRITE_FILE(log, json_encode(tosend)) - -//Used for applying byonds text macros to strings that are loaded at runtime -/proc/apply_text_macros(string) - var/next_backslash = findtext(string, "\\") - if(!next_backslash) - return string - - var/leng = length(string) - - var/next_space = findtext(string, " ", next_backslash + length(string[next_backslash])) - if(!next_space) - next_space = leng - next_backslash - - if(!next_space) //trailing bs - return string - - var/base = next_backslash == 1 ? "" : copytext(string, 1, next_backslash) - var/macro = lowertext(copytext(string, next_backslash + length(string[next_backslash]), next_space)) - var/rest = next_backslash > leng ? "" : copytext(string, next_space + length(string[next_space])) - - //See https://secure.byond.com/docs/ref/info.html#/DM/text/macros - switch(macro) - //prefixes/agnostic - if("the") - rest = text("\the []", rest) - if("a") - rest = text("\a []", rest) - if("an") - rest = text("\an []", rest) - if("proper") - rest = text("\proper []", rest) - if("improper") - rest = text("\improper []", rest) - if("roman") - rest = text("\roman []", rest) - //postfixes - if("th") - base = text("[]\th", rest) - if("s") - base = text("[]\s", rest) - if("he") - base = text("[]\he", rest) - if("she") - base = text("[]\she", rest) - if("his") - base = text("[]\his", rest) - if("himself") - base = text("[]\himself", rest) - if("herself") - base = text("[]\herself", rest) - if("hers") - base = text("[]\hers", rest) - - . = base - if(rest) - . += .(rest) - -//Replacement for the \th macro when you want the whole word output as text (first instead of 1st) -/proc/thtotext(number) - if(!isnum(number)) - return - switch(number) - if(1) - return "first" - if(2) - return "second" - if(3) - return "third" - if(4) - return "fourth" - if(5) - return "fifth" - if(6) - return "sixth" - if(7) - return "seventh" - if(8) - return "eighth" - if(9) - return "ninth" - if(10) - return "tenth" - if(11) - return "eleventh" - if(12) - return "twelfth" - else - return "[number]\th" - - -/proc/random_capital_letter() - return uppertext(pick(GLOB.alphabet)) - -/proc/unintelligize(message) - var/regex/word_boundaries = regex(@"\b[\S]+\b", "g") - var/prefix = message[1] - if(prefix == ";") - message = copytext(message, 1 + length(prefix)) - else if(prefix in list(":", "#")) - prefix += message[1 + length(prefix)] - message = copytext(message, length(prefix)) - else - prefix = "" - - var/list/rearranged = list() - while(word_boundaries.Find(message)) - var/cword = word_boundaries.match - if(length(cword)) - rearranged += cword - shuffle_inplace(rearranged) - return "[prefix][jointext(rearranged, " ")]" - - -/proc/readable_corrupted_text(text) - var/list/corruption_options = list("..", "£%", "~~\"", "!!", "*", "^", "$!", "-", "}", "?") - var/corrupted_text = "" - - var/lentext = length(text) - var/letter = "" - // Have every letter have a chance of creating corruption on either side - // Small chance of letters being removed in place of corruption - still overall readable - for(var/letter_index = 1, letter_index <= lentext, letter_index += length(letter)) - letter = text[letter_index] - - if (prob(15)) - corrupted_text += pick(corruption_options) - - if (prob(95)) - corrupted_text += letter - else - corrupted_text += pick(corruption_options) - - if (prob(15)) - corrupted_text += pick(corruption_options) - - return corrupted_text - -#define is_alpha(X) ((text2ascii(X) <= 122) && (text2ascii(X) >= 97)) -#define is_digit(X) ((length(X) == 1) && (length(text2num(X)) == 1)) - -//json decode that will return null on parse error instead of runtiming. -/proc/safe_json_decode(data) - try - return json_decode(data) - catch - return +/* + * Holds procs designed to help with filtering text + * Contains groups: + * SQL sanitization/formating + * Text sanitization + * Text searches + * Text modification + * Misc + */ + + +/* + * SQL sanitization + */ + +/proc/format_table_name(table as text) + return CONFIG_GET(string/feedback_tableprefix) + table + +/* + * Text sanitization + */ + +//Simply removes < and > and limits the length of the message +/proc/strip_html_simple(t,limit=MAX_MESSAGE_LEN) + var/list/strip_chars = list("<",">") + t = copytext(t,1,limit) + for(var/char in strip_chars) + var/index = findtext(t, char) + while(index) + t = copytext(t, 1, index) + copytext(t, index+1) + index = findtext(t, char) + return t + +//Removes a few problematic characters +/proc/sanitize_simple(t,list/repl_chars = list("\n"="#","\t"="#")) + for(var/char in repl_chars) + var/index = findtext(t, char) + while(index) + t = copytext(t, 1, index) + repl_chars[char] + copytext(t, index + length(char)) + index = findtext(t, char, index + length(char)) + return t + +/proc/sanitize_filename(t) + return sanitize_simple(t, list("\n"="", "\t"="", "/"="", "\\"="", "?"="", "%"="", "*"="", ":"="", "|"="", "\""="", "<"="", ">"="")) + +///returns nothing with an alert instead of the message if it contains something in the ic filter, and sanitizes normally if the name is fine. It returns nothing so it backs out of the input the same way as if you had entered nothing. +/proc/sanitize_name(t,list/repl_chars = null) + if(CHAT_FILTER_CHECK(t)) + alert("You cannot set a name that contains a word prohibited in IC chat!") + return "" + if(t == "space" || t == "floor" || t == "wall" || t == "r-wall" || t == "monkey" || t == "unknown" || t == "inactive ai") //prevents these common metagamey names + alert("Invalid name.") + return "" + return sanitize(t) + +//Runs byond's sanitization proc along-side sanitize_simple +/proc/sanitize(t,list/repl_chars = null) + return html_encode(sanitize_simple(t,repl_chars)) + +//Runs sanitize and strip_html_simple +//I believe strip_html_simple() is required to run first to prevent '<' from displaying as '<' after sanitize() calls byond's html_encode() +/proc/strip_html(t,limit=MAX_MESSAGE_LEN) + return copytext((sanitize(strip_html_simple(t))),1,limit) + +//Runs byond's sanitization proc along-side strip_html_simple +//I believe strip_html_simple() is required to run first to prevent '<' from displaying as '<' that html_encode() would cause +/proc/adminscrub(t,limit=MAX_MESSAGE_LEN) + return copytext((html_encode(strip_html_simple(t))),1,limit) + + +//Returns null if there is any bad text in the string +/proc/reject_bad_text(text, max_length = 512, ascii_only = TRUE) + var/char_count = 0 + var/non_whitespace = FALSE + var/lenbytes = length(text) + var/char = "" + for(var/i = 1, i <= lenbytes, i += length(char)) + char = text[i] + char_count++ + if(char_count > max_length) + return + switch(text2ascii(char)) + if(62, 60, 92, 47) // <, >, \, / + return + if(0 to 31) + return + if(32) + continue + if(127 to INFINITY) + if(ascii_only) + return + else + non_whitespace = TRUE + if(non_whitespace) + return text //only accepts the text if it has some non-spaces + +// Used to get a properly sanitized input, of max_length +// no_trim is self explanatory but it prevents the input from being trimed if you intend to parse newlines or whitespace. +/proc/stripped_input(mob/user, message = "", title = "", default = "", max_length=MAX_MESSAGE_LEN, no_trim=FALSE) + var/name = input(user, message, title, default) as text|null + if(no_trim) + return copytext(html_encode(name), 1, max_length) + else + return trim(html_encode(name), max_length) //trim is "outside" because html_encode can expand single symbols into multiple symbols (such as turning < into <) + +// Used to get a properly sanitized multiline input, of max_length +/proc/stripped_multiline_input(mob/user, message = "", title = "", default = "", max_length=MAX_MESSAGE_LEN, no_trim=FALSE) + var/name = input(user, message, title, default) as message|null + if(no_trim) + return copytext(html_encode(name), 1, max_length) + else + return trim(html_encode(name), max_length) + +#define NO_CHARS_DETECTED 0 +#define SPACES_DETECTED 1 +#define SYMBOLS_DETECTED 2 +#define NUMBERS_DETECTED 3 +#define LETTERS_DETECTED 4 + +//Filters out undesirable characters from names +/proc/reject_bad_name(t_in, allow_numbers = FALSE, max_length = MAX_NAME_LEN, ascii_only = TRUE) + if(!t_in) + return //Rejects the input if it is null + + var/number_of_alphanumeric = 0 + var/last_char_group = NO_CHARS_DETECTED + var/t_out = "" + var/t_len = length(t_in) + var/charcount = 0 + var/char = "" + + + for(var/i = 1, i <= t_len, i += length(char)) + char = t_in[i] + + switch(text2ascii(char)) + // A .. Z + if(65 to 90) //Uppercase Letters + number_of_alphanumeric++ + last_char_group = LETTERS_DETECTED + + // a .. z + if(97 to 122) //Lowercase Letters + if(last_char_group == NO_CHARS_DETECTED || last_char_group == SPACES_DETECTED || last_char_group == SYMBOLS_DETECTED) //start of a word + char = uppertext(char) + number_of_alphanumeric++ + last_char_group = LETTERS_DETECTED + + // 0 .. 9 + if(48 to 57) //Numbers + if(last_char_group == NO_CHARS_DETECTED || !allow_numbers) //suppress at start of string + continue + number_of_alphanumeric++ + last_char_group = NUMBERS_DETECTED + + // ' - . + if(39,45,46) //Common name punctuation + if(last_char_group == NO_CHARS_DETECTED) + continue + last_char_group = SYMBOLS_DETECTED + + // ~ | @ : # $ % & * + + if(126,124,64,58,35,36,37,38,42,43) //Other symbols that we'll allow (mainly for AI) + if(last_char_group == NO_CHARS_DETECTED || !allow_numbers) //suppress at start of string + continue + last_char_group = SYMBOLS_DETECTED + + //Space + if(32) + if(last_char_group == NO_CHARS_DETECTED || last_char_group == SPACES_DETECTED) //suppress double-spaces and spaces at start of string + continue + last_char_group = SPACES_DETECTED + + if(127 to INFINITY) + if(ascii_only) + continue + last_char_group = SYMBOLS_DETECTED //for now, we'll treat all non-ascii characters like symbols even though most are letters + + else + continue + + t_out += char + charcount++ + if(charcount >= max_length) + break + + if(number_of_alphanumeric < 2) + return //protects against tiny names like "A" and also names like "' ' ' ' ' ' ' '" + + if(last_char_group == SPACES_DETECTED) + t_out = copytext_char(t_out, 1, -1) //removes the last character (in this case a space) + + for(var/bad_name in list("space","floor","wall","r-wall","monkey","unknown","inactive ai")) //prevents these common metagamey names + if(cmptext(t_out,bad_name)) + return //(not case sensitive) + + return t_out + +#undef NO_CHARS_DETECTED +#undef SPACES_DETECTED +#undef NUMBERS_DETECTED +#undef LETTERS_DETECTED + + + +//html_encode helper proc that returns the smallest non null of two numbers +//or 0 if they're both null (needed because of findtext returning 0 when a value is not present) +/proc/non_zero_min(a, b) + if(!a) + return b + if(!b) + return a + return (a < b ? a : b) + +//Checks if any of a given list of needles is in the haystack +/proc/text_in_list(haystack, list/needle_list, start=1, end=0) + for(var/needle in needle_list) + if(findtext(haystack, needle, start, end)) + return 1 + return 0 + +//Like above, but case sensitive +/proc/text_in_list_case(haystack, list/needle_list, start=1, end=0) + for(var/needle in needle_list) + if(findtextEx(haystack, needle, start, end)) + return 1 + return 0 + +//Adds 'char' ahead of 'text' until there are 'count' characters total +/proc/add_leading(text, count, char = " ") + text = "[text]" + var/charcount = count - length_char(text) + var/list/chars_to_add[max(charcount + 1, 0)] + return jointext(chars_to_add, char) + text + +//Adds 'char' behind 'text' until there are 'count' characters total +/proc/add_trailing(text, count, char = " ") + text = "[text]" + var/charcount = count - length_char(text) + var/list/chars_to_add[max(charcount + 1, 0)] + return text + jointext(chars_to_add, char) + +//Returns a string with reserved characters and spaces before the first letter removed +/proc/trim_left(text) + for (var/i = 1 to length(text)) + if (text2ascii(text, i) > 32) + return copytext(text, i) + return "" + +//Returns a string with reserved characters and spaces after the last letter removed +/proc/trim_right(text) + for (var/i = length(text), i > 0, i--) + if (text2ascii(text, i) > 32) + return copytext(text, 1, i + 1) + return "" + +//Returns a string with reserved characters and spaces before the first word and after the last word removed. +/proc/trim(text, max_length) + if(max_length) + text = copytext_char(text, 1, max_length) + return trim_left(trim_right(text)) + +//Returns a string with the first element of the string capitalized. +/proc/capitalize(t) + . = t + if(t) + . = t[1] + return uppertext(.) + copytext(t, 1 + length(.)) + +/proc/stringmerge(text,compare,replace = "*") +//This proc fills in all spaces with the "replace" var (* by default) with whatever +//is in the other string at the same spot (assuming it is not a replace char). +//This is used for fingerprints + var/newtext = text + var/text_it = 1 //iterators + var/comp_it = 1 + var/newtext_it = 1 + var/text_length = length(text) + var/comp_length = length(compare) + while(comp_it <= comp_length && text_it <= text_length) + var/a = text[text_it] + var/b = compare[comp_it] +//if it isn't both the same letter, or if they are both the replacement character +//(no way to know what it was supposed to be) + if(a != b) + if(a == replace) //if A is the replacement char + newtext = copytext(newtext, 1, newtext_it) + b + copytext(newtext, newtext_it + length(newtext[newtext_it])) + else if(b == replace) //if B is the replacement char + newtext = copytext(newtext, 1, newtext_it) + a + copytext(newtext, newtext_it + length(newtext[newtext_it])) + else //The lists disagree, Uh-oh! + return 0 + text_it += length(a) + comp_it += length(b) + newtext_it += length(newtext[newtext_it]) + + return newtext + +/proc/stringpercent(text,character = "*") +//This proc returns the number of chars of the string that is the character +//This is used for detective work to determine fingerprint completion. + if(!text || !character) + return 0 + var/count = 0 + var/lentext = length(text) + var/a = "" + for(var/i = 1, i <= lentext, i += length(a)) + a = text[i] + if(a == character) + count++ + return count + +/proc/reverse_text(text = "") + var/new_text = "" + var/lentext = length(text) + var/letter = "" + for(var/i = 1, i <= lentext, i += length(letter)) + letter = text[i] + new_text = letter + new_text + return new_text + +GLOBAL_LIST_INIT(zero_character_only, list("0")) +GLOBAL_LIST_INIT(hex_characters, list("0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f")) +GLOBAL_LIST_INIT(alphabet, list("a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z")) +GLOBAL_LIST_INIT(binary, list("0","1")) +/proc/random_string(length, list/characters) + . = "" + for(var/i=1, i<=length, i++) + . += pick(characters) + +/proc/repeat_string(times, string="") + . = "" + for(var/i=1, i<=times, i++) + . += string + +/proc/random_short_color() + return random_string(3, GLOB.hex_characters) + +/proc/random_color() + return random_string(6, GLOB.hex_characters) + +//merges non-null characters (3rd argument) from "from" into "into". Returns result +//e.g. into = "Hello World" +// from = "Seeya______" +// returns"Seeya World" +//The returned text is always the same length as into +//This was coded to handle DNA gene-splicing. +/proc/merge_text(into, from, null_char="_") + . = "" + if(!istext(into)) + into = "" + if(!istext(from)) + from = "" + var/null_ascii = istext(null_char) ? text2ascii(null_char, 1) : null_char + var/copying_into = FALSE + var/char = "" + var/start = 1 + var/end_from = length(from) + var/end_into = length(into) + var/into_it = 1 + var/from_it = 1 + while(from_it <= end_from && into_it <= end_into) + char = from[from_it] + if(text2ascii(char) == null_ascii) + if(!copying_into) + . += copytext(from, start, from_it) + start = into_it + copying_into = TRUE + else + if(copying_into) + . += copytext(into, start, into_it) + start = from_it + copying_into = FALSE + into_it += length(into[into_it]) + from_it += length(char) + + if(copying_into) + . += copytext(into, start) + else + . += copytext(from, start, from_it) + if(into_it <= end_into) + . += copytext(into, into_it) + +//finds the first occurrence of one of the characters from needles argument inside haystack +//it may appear this can be optimised, but it really can't. findtext() is so much faster than anything you can do in byondcode. +//stupid byond :( +/proc/findchar(haystack, needles, start=1, end=0) + var/char = "" + var/len = length(needles) + for(var/i = 1, i <= len, i += length(char)) + char = needles[i] + . = findtextEx(haystack, char, start, end) + if(.) + return + return 0 + +/proc/parsemarkdown_basic_step1(t, limited=FALSE) + if(length(t) <= 0) + return + + // This parses markdown with no custom rules + + // Escape backslashed + + t = replacetext(t, "$", "$-") + t = replacetext(t, "\\\\", "$1") + t = replacetext(t, "\\**", "$2") + t = replacetext(t, "\\*", "$3") + t = replacetext(t, "\\__", "$4") + t = replacetext(t, "\\_", "$5") + t = replacetext(t, "\\^", "$6") + t = replacetext(t, "\\((", "$7") + t = replacetext(t, "\\))", "$8") + t = replacetext(t, "\\|", "$9") + t = replacetext(t, "\\%", "$0") + + // Escape single characters that will be used + + t = replacetext(t, "!", "$a") + + // Parse hr and small + + if(!limited) + t = replacetext(t, "((", "") + t = replacetext(t, "))", "") + t = replacetext(t, regex("(-){3,}", "gm"), "
    ") + t = replacetext(t, regex("^\\((-){3,}\\)$", "gm"), "$1") + + // Parse lists + + var/list/tlist = splittext(t, "\n") + var/tlistlen = tlist.len + var/listlevel = -1 + var/singlespace = -1 // if 0, double spaces are used before asterisks, if 1, single are + for(var/i = 1, i <= tlistlen, i++) + var/line = tlist[i] + var/count_asterisk = length(replacetext(line, regex("\[^\\*\]+", "g"), "")) + if(count_asterisk % 2 == 1 && findtext(line, regex("^\\s*\\*", "g"))) // there is an extra asterisk in the beggining + + var/count_w = length(replacetext(line, regex("^( *)\\*.*$", "g"), "$1")) // whitespace before asterisk + line = replacetext(line, regex("^ *(\\*.*)$", "g"), "$1") + + if(singlespace == -1 && count_w == 2) + if(listlevel == 0) + singlespace = 0 + else + singlespace = 1 + + if(singlespace == 0) + count_w = count_w % 2 ? round(count_w / 2 + 0.25) : count_w / 2 + + line = replacetext(line, regex("\\*", ""), "
  • ") + while(listlevel < count_w) + line = "
      " + line + listlevel++ + while(listlevel > count_w) + line = "
    " + line + listlevel-- + + else while(listlevel >= 0) + line = "" + line + listlevel-- + + tlist[i] = line + // end for + + t = tlist[1] + for(var/i = 2, i <= tlistlen, i++) + t += "\n" + tlist[i] + + while(listlevel >= 0) + t += "" + listlevel-- + + else + t = replacetext(t, "((", "") + t = replacetext(t, "))", "") + + // Parse headers + + t = replacetext(t, regex("^#(?!#) ?(.+)$", "gm"), "

    $1

    ") + t = replacetext(t, regex("^##(?!#) ?(.+)$", "gm"), "

    $1

    ") + t = replacetext(t, regex("^###(?!#) ?(.+)$", "gm"), "

    $1

    ") + t = replacetext(t, regex("^#### ?(.+)$", "gm"), "
    $1
    ") + + // Parse most rules + + t = replacetext(t, regex("\\*(\[^\\*\]*)\\*", "g"), "$1") + t = replacetext(t, regex("_(\[^_\]*)_", "g"), "$1") + t = replacetext(t, "", "!") + t = replacetext(t, "
    ", "!") + t = replacetext(t, regex("\\!(\[^\\!\]+)\\!", "g"), "$1") + t = replacetext(t, regex("\\^(\[^\\^\]+)\\^", "g"), "$1") + t = replacetext(t, regex("\\|(\[^\\|\]+)\\|", "g"), "
    $1
    ") + t = replacetext(t, "!", "
    ") + + return t + +/proc/parsemarkdown_basic_step2(t) + if(length(t) <= 0) + return + + // Restore the single characters used + + t = replacetext(t, "$a", "!") + + // Redo the escaping + + t = replacetext(t, "$1", "\\") + t = replacetext(t, "$2", "**") + t = replacetext(t, "$3", "*") + t = replacetext(t, "$4", "__") + t = replacetext(t, "$5", "_") + t = replacetext(t, "$6", "^") + t = replacetext(t, "$7", "((") + t = replacetext(t, "$8", "))") + t = replacetext(t, "$9", "|") + t = replacetext(t, "$0", "%") + t = replacetext(t, "$-", "$") + + return t + +/proc/parsemarkdown_basic(t, limited=FALSE) + t = parsemarkdown_basic_step1(t, limited) + t = parsemarkdown_basic_step2(t) + return t + +/proc/parsemarkdown(t, mob/user=null, limited=FALSE) + if(length(t) <= 0) + return + + // Premanage whitespace + + t = replacetext(t, regex("\[^\\S\\r\\n \]", "g"), " ") + + t = parsemarkdown_basic_step1(t) + + t = replacetext(t, regex("%s(?:ign)?(?=\\s|$)", "igm"), user ? "[user.real_name]" : "") + t = replacetext(t, regex("%f(?:ield)?(?=\\s|$)", "igm"), "") + + t = parsemarkdown_basic_step2(t) + + // Manage whitespace + + t = replacetext(t, regex("(?:\\r\\n?|\\n)", "g"), "
    ") + + t = replacetext(t, " ", "  ") + + // Done + + return t + +/proc/text2charlist(text) + var/char = "" + var/lentext = length(text) + . = list() + for(var/i = 1, i <= lentext, i += length(char)) + char = text[i] + . += char + +/proc/rot13(text = "") + var/lentext = length(text) + var/char = "" + var/ascii = 0 + . = "" + for(var/i = 1, i <= lentext, i += length(char)) + char = text[i] + ascii = text2ascii(char) + switch(ascii) + if(65 to 77, 97 to 109) //A to M, a to m + ascii += 13 + if(78 to 90, 110 to 122) //N to Z, n to z + ascii -= 13 + . += ascii2text(ascii) + +//Takes a list of values, sanitizes it down for readability and character count, +//then exports it as a json file at data/npc_saves/[filename].json. +//As far as SS13 is concerned this is write only data. You can't change something +//in the json file and have it be reflected in the in game item/mob it came from. +//(That's what things like savefiles are for) Note that this list is not shuffled. +/proc/twitterize(list/proposed, filename, cullshort = 1, storemax = 1000) + if(!islist(proposed) || !filename || !CONFIG_GET(flag/log_twitter)) + return + + //Regular expressions are, as usual, absolute magic + //Any characters outside of 32 (space) to 126 (~) because treating things you don't understand as "magic" is really stupid + var/regex/all_invalid_symbols = new(@"[^ -~]{1}") + + var/list/accepted = list() + for(var/string in proposed) + if(findtext(string,GLOB.is_website) || findtext(string,GLOB.is_email) || findtext(string,all_invalid_symbols) || !findtext(string,GLOB.is_alphanumeric)) + continue + var/buffer = "" + var/early_culling = TRUE + var/lentext = length(string) + var/let = "" + + for(var/pos = 1, pos <= lentext, pos += length(let)) + let = string[pos] + if(!findtext(let, GLOB.is_alphanumeric)) + continue + early_culling = FALSE + buffer = copytext(string, pos) + break + if(early_culling) //Never found any letters! Bail! + continue + + var/punctbuffer = "" + var/cutoff = 0 + lentext = length_char(buffer) + for(var/pos = 1, pos <= lentext, pos++) + let = copytext_char(buffer, -pos, -pos + 1) + if(!findtext(let, GLOB.is_punctuation)) //This won't handle things like Nyaaaa!~ but that's fine + break + punctbuffer += let + cutoff += length(let) + if(punctbuffer) //We clip down excessive punctuation to get the letter count lower and reduce repeats. It's not perfect but it helps. + var/exclaim = FALSE + var/question = FALSE + var/periods = 0 + lentext = length(punctbuffer) + for(var/pos = 1, pos <= lentext, pos += length(let)) + let = punctbuffer[pos] + if(!exclaim && findtext(let, "!")) + exclaim = TRUE + if(question) + break + if(!question && findtext(let, "?")) + question = TRUE + if(exclaim) + break + if(!exclaim && !question && findtext(let, ".")) //? and ! take priority over periods + periods += 1 + if(exclaim) + if(question) + punctbuffer = "?!" + else + punctbuffer = "!" + else if(question) + punctbuffer = "?" + else if(periods > 1) + punctbuffer = "..." + else + punctbuffer = "" //Grammer nazis be damned + buffer = copytext(buffer, 1, -cutoff) + punctbuffer + lentext = length_char(buffer) + if(!buffer || lentext > 280 || lentext <= cullshort || (buffer in accepted)) + continue + + accepted += buffer + + var/log = file("data/npc_saves/[filename].json") //If this line ever shows up as changed in a PR be very careful you aren't being memed on + var/list/oldjson = list() + var/list/oldentries = list() + if(fexists(log)) + oldjson = json_decode(file2text(log)) + oldentries = oldjson["data"] + if(length(oldentries)) + for(var/string in accepted) + for(var/old in oldentries) + if(string == old) + oldentries.Remove(old) //Line's position in line is "refreshed" until it falls off the in game radar + break + + var/list/finalized = list() + finalized = accepted.Copy() + oldentries.Copy() //we keep old and unreferenced phrases near the bottom for culling + listclearnulls(finalized) + if(length(finalized) > storemax) + finalized.Cut(storemax + 1) + fdel(log) + + var/list/tosend = list() + tosend["data"] = finalized + WRITE_FILE(log, json_encode(tosend)) + +//Used for applying byonds text macros to strings that are loaded at runtime +/proc/apply_text_macros(string) + var/next_backslash = findtext(string, "\\") + if(!next_backslash) + return string + + var/leng = length(string) + + var/next_space = findtext(string, " ", next_backslash + length(string[next_backslash])) + if(!next_space) + next_space = leng - next_backslash + + if(!next_space) //trailing bs + return string + + var/base = next_backslash == 1 ? "" : copytext(string, 1, next_backslash) + var/macro = lowertext(copytext(string, next_backslash + length(string[next_backslash]), next_space)) + var/rest = next_backslash > leng ? "" : copytext(string, next_space + length(string[next_space])) + + //See https://secure.byond.com/docs/ref/info.html#/DM/text/macros + switch(macro) + //prefixes/agnostic + if("the") + rest = text("\the []", rest) + if("a") + rest = text("\a []", rest) + if("an") + rest = text("\an []", rest) + if("proper") + rest = text("\proper []", rest) + if("improper") + rest = text("\improper []", rest) + if("roman") + rest = text("\roman []", rest) + //postfixes + if("th") + base = text("[]\th", rest) + if("s") + base = text("[]\s", rest) + if("he") + base = text("[]\he", rest) + if("she") + base = text("[]\she", rest) + if("his") + base = text("[]\his", rest) + if("himself") + base = text("[]\himself", rest) + if("herself") + base = text("[]\herself", rest) + if("hers") + base = text("[]\hers", rest) + + . = base + if(rest) + . += .(rest) + +//Replacement for the \th macro when you want the whole word output as text (first instead of 1st) +/proc/thtotext(number) + if(!isnum(number)) + return + switch(number) + if(1) + return "first" + if(2) + return "second" + if(3) + return "third" + if(4) + return "fourth" + if(5) + return "fifth" + if(6) + return "sixth" + if(7) + return "seventh" + if(8) + return "eighth" + if(9) + return "ninth" + if(10) + return "tenth" + if(11) + return "eleventh" + if(12) + return "twelfth" + else + return "[number]\th" + + +/proc/random_capital_letter() + return uppertext(pick(GLOB.alphabet)) + +/proc/unintelligize(message) + var/regex/word_boundaries = regex(@"\b[\S]+\b", "g") + var/prefix = message[1] + if(prefix == ";") + message = copytext(message, 1 + length(prefix)) + else if(prefix in list(":", "#")) + prefix += message[1 + length(prefix)] + message = copytext(message, length(prefix)) + else + prefix = "" + + var/list/rearranged = list() + while(word_boundaries.Find(message)) + var/cword = word_boundaries.match + if(length(cword)) + rearranged += cword + shuffle_inplace(rearranged) + return "[prefix][jointext(rearranged, " ")]" + + +/proc/readable_corrupted_text(text) + var/list/corruption_options = list("..", "£%", "~~\"", "!!", "*", "^", "$!", "-", "}", "?") + var/corrupted_text = "" + + var/lentext = length(text) + var/letter = "" + // Have every letter have a chance of creating corruption on either side + // Small chance of letters being removed in place of corruption - still overall readable + for(var/letter_index = 1, letter_index <= lentext, letter_index += length(letter)) + letter = text[letter_index] + + if (prob(15)) + corrupted_text += pick(corruption_options) + + if (prob(95)) + corrupted_text += letter + else + corrupted_text += pick(corruption_options) + + if (prob(15)) + corrupted_text += pick(corruption_options) + + return corrupted_text + +#define is_alpha(X) ((text2ascii(X) <= 122) && (text2ascii(X) >= 97)) +#define is_digit(X) ((length(X) == 1) && (length(text2num(X)) == 1)) + +//json decode that will return null on parse error instead of runtiming. +/proc/safe_json_decode(data) + try + return json_decode(data) + catch + return + +/proc/num2loadingbar(percent as num, var/numSquares = 20, var/reverse = FALSE) + var/loadstring = "" + for (var/i in 1 to numSquares) + var/limit = reverse ? numSquares - percent*numSquares : percent*numSquares + if (i <= limit) + loadstring += "█" + else + loadstring += "░" + return "\[" + loadstring + "]" diff --git a/code/__HELPERS/time.dm b/code/__HELPERS/time.dm index ea4844dcce82..92337a1a9598 100644 --- a/code/__HELPERS/time.dm +++ b/code/__HELPERS/time.dm @@ -1,88 +1,88 @@ -//Returns the world time in english -/proc/worldtime2text() - return gameTimestamp("hh:mm:ss", world.time) - -/proc/time_stamp(format = "hh:mm:ss", show_ds) - var/time_string = time2text(world.timeofday, format) - return show_ds ? "[time_string]:[world.timeofday % 10]" : time_string - -/proc/gameTimestamp(format = "hh:mm:ss", wtime=null) - if(!wtime) - wtime = world.time - return time2text(wtime - GLOB.timezoneOffset, format) - -/proc/station_time(display_only = FALSE, wtime=world.time) - return ((((wtime - SSticker.round_start_time) * SSticker.station_time_rate_multiplier) + SSticker.gametime_offset) % 864000) - (display_only? GLOB.timezoneOffset : 0) - -/proc/station_time_timestamp(format = "hh:mm:ss", wtime) - return time2text(station_time(TRUE, wtime), format) - -/proc/station_time_debug(force_set) - if(isnum(force_set)) - SSticker.gametime_offset = force_set - return - SSticker.gametime_offset = rand(0, 864000) //hours in day * minutes in hour * seconds in minute * deciseconds in second - if(prob(50)) - SSticker.gametime_offset = FLOOR(SSticker.gametime_offset, 3600) - else - SSticker.gametime_offset = CEILING(SSticker.gametime_offset, 3600) - -//returns timestamp in a sql and a not-quite-compliant ISO 8601 friendly format -/proc/SQLtime(timevar) - return time2text(timevar || world.timeofday, "YYYY-MM-DD hh:mm:ss") - - -GLOBAL_VAR_INIT(midnight_rollovers, 0) -GLOBAL_VAR_INIT(rollovercheck_last_timeofday, 0) -/proc/update_midnight_rollover() - if (world.timeofday < GLOB.rollovercheck_last_timeofday) //TIME IS GOING BACKWARDS! - GLOB.midnight_rollovers++ - GLOB.rollovercheck_last_timeofday = world.timeofday - return GLOB.midnight_rollovers - -/proc/weekdayofthemonth() - var/DD = text2num(time2text(world.timeofday, "DD")) // get the current day - switch(DD) - if(8 to 13) - return 2 - if(14 to 20) - return 3 - if(21 to 27) - return 4 - if(28 to INFINITY) - return 5 - else - return 1 - -//Takes a value of time in deciseconds. -//Returns a text value of that number in hours, minutes, or seconds. -/proc/DisplayTimeText(time_value, round_seconds_to = 0.1) - var/second = FLOOR(time_value * 0.1, round_seconds_to) - if(!second) - return "right now" - if(second < 60) - return "[second] second[(second != 1)? "s":""]" - var/minute = FLOOR(second / 60, 1) - second = FLOOR(MODULUS(second, 60), round_seconds_to) - var/secondT - if(second) - secondT = " and [second] second[(second != 1)? "s":""]" - if(minute < 60) - return "[minute] minute[(minute != 1)? "s":""][secondT]" - var/hour = FLOOR(minute / 60, 1) - minute = MODULUS(minute, 60) - var/minuteT - if(minute) - minuteT = " and [minute] minute[(minute != 1)? "s":""]" - if(hour < 24) - return "[hour] hour[(hour != 1)? "s":""][minuteT][secondT]" - var/day = FLOOR(hour / 24, 1) - hour = MODULUS(hour, 24) - var/hourT - if(hour) - hourT = " and [hour] hour[(hour != 1)? "s":""]" - return "[day] day[(day != 1)? "s":""][hourT][minuteT][secondT]" - - -/proc/daysSince(realtimev) - return round((world.realtime - realtimev) / (24 HOURS)) +//Returns the world time in english +/proc/worldtime2text() + return gameTimestamp("hh:mm:ss", world.time) + +/proc/time_stamp(format = "hh:mm:ss", show_ds) + var/time_string = time2text(world.timeofday, format) + return show_ds ? "[time_string]:[world.timeofday % 10]" : time_string + +/proc/gameTimestamp(format = "hh:mm:ss", wtime=null) + if(!wtime) + wtime = world.time + return time2text(wtime - GLOB.timezoneOffset, format) + +/proc/station_time(display_only = FALSE, wtime=world.time) + return ((((wtime - SSticker.round_start_time) * SSticker.station_time_rate_multiplier) + SSticker.gametime_offset) % 864000) - (display_only? GLOB.timezoneOffset : 0) + +/proc/station_time_timestamp(format = "hh:mm:ss", wtime) + return time2text(station_time(TRUE, wtime), format) + +/proc/station_time_debug(force_set) + if(isnum(force_set)) + SSticker.gametime_offset = force_set + return + SSticker.gametime_offset = rand(0, 864000) //hours in day * minutes in hour * seconds in minute * deciseconds in second + if(prob(50)) + SSticker.gametime_offset = FLOOR(SSticker.gametime_offset, 3600) + else + SSticker.gametime_offset = CEILING(SSticker.gametime_offset, 3600) + +//returns timestamp in a sql and a not-quite-compliant ISO 8601 friendly format +/proc/SQLtime(timevar) + return time2text(timevar || world.timeofday, "YYYY-MM-DD hh:mm:ss") + + +GLOBAL_VAR_INIT(midnight_rollovers, 0) +GLOBAL_VAR_INIT(rollovercheck_last_timeofday, 0) +/proc/update_midnight_rollover() + if (world.timeofday < GLOB.rollovercheck_last_timeofday) //TIME IS GOING BACKWARDS! + GLOB.midnight_rollovers++ + GLOB.rollovercheck_last_timeofday = world.timeofday + return GLOB.midnight_rollovers + +/proc/weekdayofthemonth() + var/DD = text2num(time2text(world.timeofday, "DD")) // get the current day + switch(DD) + if(8 to 13) + return 2 + if(14 to 20) + return 3 + if(21 to 27) + return 4 + if(28 to INFINITY) + return 5 + else + return 1 + +//Takes a value of time in deciseconds. +//Returns a text value of that number in hours, minutes, or seconds. +/proc/DisplayTimeText(time_value, round_seconds_to = 0.1) + var/second = FLOOR(time_value * 0.1, round_seconds_to) + if(!second) + return "right now" + if(second < 60) + return "[second] second[(second != 1)? "s":""]" + var/minute = FLOOR(second / 60, 1) + second = FLOOR(MODULUS(second, 60), round_seconds_to) + var/secondT + if(second) + secondT = " and [second] second[(second != 1)? "s":""]" + if(minute < 60) + return "[minute] minute[(minute != 1)? "s":""][secondT]" + var/hour = FLOOR(minute / 60, 1) + minute = MODULUS(minute, 60) + var/minuteT + if(minute) + minuteT = " and [minute] minute[(minute != 1)? "s":""]" + if(hour < 24) + return "[hour] hour[(hour != 1)? "s":""][minuteT][secondT]" + var/day = FLOOR(hour / 24, 1) + hour = MODULUS(hour, 24) + var/hourT + if(hour) + hourT = " and [hour] hour[(hour != 1)? "s":""]" + return "[day] day[(day != 1)? "s":""][hourT][minuteT][secondT]" + + +/proc/daysSince(realtimev) + return round((world.realtime - realtimev) / (24 HOURS)) diff --git a/code/__HELPERS/type2type.dm b/code/__HELPERS/type2type.dm index a50b364d821d..77d154d8674e 100644 --- a/code/__HELPERS/type2type.dm +++ b/code/__HELPERS/type2type.dm @@ -1,531 +1,531 @@ -/* - * Holds procs designed to change one type of value, into another. - * Contains: - * hex2num & num2hex - * file2list - * angle2dir - * angle2text - * worldtime2text - * text2dir_extended & dir2text_short - */ - -//Returns an integer given a hex input, supports negative values "-ff" -//skips preceding invalid characters -//breaks when hittin invalid characters thereafter -// If safe=TRUE, returns null on incorrect input strings instead of CRASHing -/proc/hex2num(hex, safe=FALSE) - . = 0 - var/place = 1 - for(var/i in length(hex) to 1 step -1) - var/num = text2ascii(hex, i) - switch(num) - if(48 to 57) - num -= 48 //0-9 - if(97 to 102) - num -= 87 //a-f - if(65 to 70) - num -= 55 //A-F - if(45) - return . * -1 // - - else - if(safe) - return null - else - CRASH("Malformed hex number") - - . += num * place - place *= 16 - -//Returns the hex value of a decimal number -//len == length of returned string -//if len < 0 then the returned string will be as long as it needs to be to contain the data -//Only supports positive numbers -//if an invalid number is provided, it assumes num==0 -//Note, unlike previous versions, this one works from low to high <-- that way -/proc/num2hex(num, len=2) - if(!isnum(num)) - num = 0 - num = round(abs(num)) - . = "" - var/i=0 - while(1) - if(len<=0) - if(!num) - break - else - if(i>=len) - break - var/remainder = num/16 - num = round(remainder) - remainder = (remainder - num) * 16 - switch(remainder) - if(9,8,7,6,5,4,3,2,1) - . = "[remainder]" + . - if(10,11,12,13,14,15) - . = ascii2text(remainder+87) + . - else - . = "0" + . - i++ - return . - -//Splits the text of a file at seperator and returns them in a list. -//returns an empty list if the file doesn't exist -/world/proc/file2list(filename, seperator="\n", trim = TRUE) - if (trim) - return splittext(trim(file2text(filename)),seperator) - return splittext(file2text(filename),seperator) - -//Turns a direction into text -/proc/dir2text(direction) - switch(direction) - if(NORTH) - return "north" - if(SOUTH) - return "south" - if(EAST) - return "east" - if(WEST) - return "west" - if(NORTHEAST) - return "northeast" - if(SOUTHEAST) - return "southeast" - if(NORTHWEST) - return "northwest" - if(SOUTHWEST) - return "southwest" - else - return - -//Turns text into proper directions -/proc/text2dir(direction) - switch(uppertext(direction)) - if("NORTH") - return NORTH - if("SOUTH") - return SOUTH - if("EAST") - return EAST - if("WEST") - return WEST - if("NORTHEAST") - return NORTHEAST - if("NORTHWEST") - return NORTHWEST - if("SOUTHEAST") - return SOUTHEAST - if("SOUTHWEST") - return SOUTHWEST - else - return - -//Converts an angle (degrees) into an ss13 direction -GLOBAL_LIST_INIT(modulo_angle_to_dir, list(NORTH,NORTHEAST,EAST,SOUTHEAST,SOUTH,SOUTHWEST,WEST,NORTHWEST)) -#define angle2dir(X) (GLOB.modulo_angle_to_dir[round((((X%360)+382.5)%360)/45)+1]) - -/proc/angle2dir_cardinal(degree) - degree = SIMPLIFY_DEGREES(degree) - switch(round(degree, 0.1)) - if(315.5 to 360, 0 to 45.5) - return NORTH - if(45.6 to 135.5) - return EAST - if(135.6 to 225.5) - return SOUTH - if(225.6 to 315.5) - return WEST - -//returns the north-zero clockwise angle in degrees, given a direction -/proc/dir2angle(D) - switch(D) - if(NORTH) - return 0 - if(SOUTH) - return 180 - if(EAST) - return 90 - if(WEST) - return 270 - if(NORTHEAST) - return 45 - if(SOUTHEAST) - return 135 - if(NORTHWEST) - return 315 - if(SOUTHWEST) - return 225 - else - return null - -//Returns the angle in english -/proc/angle2text(degree) - return dir2text(angle2dir(degree)) - -//Converts a blend_mode constant to one acceptable to icon.Blend() -/proc/blendMode2iconMode(blend_mode) - switch(blend_mode) - if(BLEND_MULTIPLY) - return ICON_MULTIPLY - if(BLEND_ADD) - return ICON_ADD - if(BLEND_SUBTRACT) - return ICON_SUBTRACT - else - return ICON_OVERLAY - -//Converts a rights bitfield into a string -/proc/rights2text(rights, seperator="", prefix = "+") - seperator += prefix - if(rights & R_BUILD) - . += "[seperator]BUILDMODE" - if(rights & R_ADMIN) - . += "[seperator]ADMIN" - if(rights & R_BAN) - . += "[seperator]BAN" - if(rights & R_FUN) - . += "[seperator]FUN" - if(rights & R_SERVER) - . += "[seperator]SERVER" - if(rights & R_DEBUG) - . += "[seperator]DEBUG" - if(rights & R_POSSESS) - . += "[seperator]POSSESS" - if(rights & R_PERMISSIONS) - . += "[seperator]PERMISSIONS" - if(rights & R_STEALTH) - . += "[seperator]STEALTH" - if(rights & R_POLL) - . += "[seperator]POLL" - if(rights & R_VAREDIT) - . += "[seperator]VAREDIT" - if(rights & R_SOUND) - . += "[seperator]SOUND" - if(rights & R_SPAWN) - . += "[seperator]SPAWN" - if(rights & R_AUTOADMIN) - . += "[seperator]AUTOLOGIN" - if(rights & R_DBRANKS) - . += "[seperator]DBRANKS" - if(!.) - . = "NONE" - return . - -//colour formats -/proc/rgb2hsl(red, green, blue) - red /= 255;green /= 255;blue /= 255; - var/max = max(red,green,blue) - var/min = min(red,green,blue) - var/range = max-min - - var/hue=0;var/saturation=0;var/lightness=0; - lightness = (max + min)/2 - if(range != 0) - if(lightness < 0.5) - saturation = range/(max+min) - else - saturation = range/(2-max-min) - - var/dred = ((max-red)/(6*max)) + 0.5 - var/dgreen = ((max-green)/(6*max)) + 0.5 - var/dblue = ((max-blue)/(6*max)) + 0.5 - - if(max==red) - hue = dblue - dgreen - else if(max==green) - hue = dred - dblue + (1/3) - else - hue = dgreen - dred + (2/3) - if(hue < 0) - hue++ - else if(hue > 1) - hue-- - - return list(hue, saturation, lightness) - -/proc/hsl2rgb(hue, saturation, lightness) - var/red;var/green;var/blue; - if(saturation == 0) - red = lightness * 255 - green = red - blue = red - else - var/a;var/b; - if(lightness < 0.5) - b = lightness*(1+saturation) - else - b = (lightness+saturation) - (saturation*lightness) - a = 2*lightness - b - - red = round(255 * hue2rgb(a, b, hue+(1/3))) - green = round(255 * hue2rgb(a, b, hue)) - blue = round(255 * hue2rgb(a, b, hue-(1/3))) - - return list(red, green, blue) - -/proc/hue2rgb(a, b, hue) - if(hue < 0) - hue++ - else if(hue > 1) - hue-- - if(6*hue < 1) - return (a+(b-a)*6*hue) - if(2*hue < 1) - return b - if(3*hue < 2) - return (a+(b-a)*((2/3)-hue)*6) - return a - -//Turns a Body_parts_covered bitfield into a list of organ/limb names. -//(I challenge you to find a use for this) -/proc/body_parts_covered2organ_names(bpc) - var/list/covered_parts = list() - - if(!bpc) - return 0 - - if(bpc & FULL_BODY) - covered_parts |= list(BODY_ZONE_L_ARM,BODY_ZONE_R_ARM,BODY_ZONE_HEAD,BODY_ZONE_CHEST,BODY_ZONE_L_LEG,BODY_ZONE_R_LEG) - - else - if(bpc & HEAD) - covered_parts |= list(BODY_ZONE_HEAD) - if(bpc & CHEST) - covered_parts |= list(BODY_ZONE_CHEST) - if(bpc & GROIN) - covered_parts |= list(BODY_ZONE_CHEST) - - if(bpc & ARMS) - covered_parts |= list(BODY_ZONE_L_ARM,BODY_ZONE_R_ARM) - else - if(bpc & ARM_LEFT) - covered_parts |= list(BODY_ZONE_L_ARM) - if(bpc & ARM_RIGHT) - covered_parts |= list(BODY_ZONE_R_ARM) - - if(bpc & HANDS) - covered_parts |= list(BODY_ZONE_L_ARM,BODY_ZONE_R_ARM) - else - if(bpc & HAND_LEFT) - covered_parts |= list(BODY_ZONE_L_ARM) - if(bpc & HAND_RIGHT) - covered_parts |= list(BODY_ZONE_R_ARM) - - if(bpc & LEGS) - covered_parts |= list(BODY_ZONE_L_LEG,BODY_ZONE_R_LEG) - else - if(bpc & LEG_LEFT) - covered_parts |= list(BODY_ZONE_L_LEG) - if(bpc & LEG_RIGHT) - covered_parts |= list(BODY_ZONE_R_LEG) - - if(bpc & FEET) - covered_parts |= list(BODY_ZONE_L_LEG,BODY_ZONE_R_LEG) - else - if(bpc & FOOT_LEFT) - covered_parts |= list(BODY_ZONE_L_LEG) - if(bpc & FOOT_RIGHT) - covered_parts |= list(BODY_ZONE_R_LEG) - - return covered_parts - -/proc/slot2body_zone(slot) - switch(slot) - if(ITEM_SLOT_BACK, ITEM_SLOT_OCLOTHING, ITEM_SLOT_ICLOTHING, ITEM_SLOT_BELT, ITEM_SLOT_ID) - return BODY_ZONE_CHEST - - if(ITEM_SLOT_GLOVES, ITEM_SLOT_HANDS, ITEM_SLOT_HANDCUFFED) - return pick(BODY_ZONE_PRECISE_L_HAND, BODY_ZONE_PRECISE_R_HAND) - - if(ITEM_SLOT_HEAD, ITEM_SLOT_NECK, ITEM_SLOT_NECK, ITEM_SLOT_EARS) - return BODY_ZONE_HEAD - - if(ITEM_SLOT_MASK) - return BODY_ZONE_PRECISE_MOUTH - - if(ITEM_SLOT_EYES) - return BODY_ZONE_PRECISE_EYES - - if(ITEM_SLOT_FEET) - return pick(BODY_ZONE_PRECISE_R_FOOT, BODY_ZONE_PRECISE_L_FOOT) - - if(ITEM_SLOT_LEGCUFFED) - return pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) - -//adapted from http://www.tannerhelland.com/4435/convert-temperature-rgb-algorithm-code/ -/proc/heat2colour(temp) - return rgb(heat2colour_r(temp), heat2colour_g(temp), heat2colour_b(temp)) - - -/proc/heat2colour_r(temp) - temp /= 100 - if(temp <= 66) - . = 255 - else - . = max(0, min(255, 329.698727446 * (temp - 60) ** -0.1332047592)) - - -/proc/heat2colour_g(temp) - temp /= 100 - if(temp <= 66) - . = max(0, min(255, 99.4708025861 * log(temp) - 161.1195681661)) - else - . = max(0, min(255, 288.1221685293 * ((temp - 60) ** -0.075148492))) - - -/proc/heat2colour_b(temp) - temp /= 100 - if(temp >= 66) - . = 255 - else - if(temp <= 16) - . = 0 - else - . = max(0, min(255, 138.5177312231 * log(temp - 10) - 305.0447927307)) - - -/proc/color2hex(color) //web colors - if(!color) - return "#000000" - - switch(color) - if("white") - return "#FFFFFF" - if("black") - return "#000000" - if("gray") - return "#808080" - if("brown") - return "#A52A2A" - if("red") - return "#FF0000" - if("darkred") - return "#8B0000" - if("crimson") - return "#DC143C" - if("orange") - return "#FFA500" - if("yellow") - return "#FFFF00" - if("green") - return "#008000" - if("lime") - return "#00FF00" - if("darkgreen") - return "#006400" - if("cyan") - return "#00FFFF" - if("blue") - return "#0000FF" - if("navy") - return "#000080" - if("teal") - return "#008080" - if("purple") - return "#800080" - if("indigo") - return "#4B0082" - else - return "#FFFFFF" - - -//This is a weird one: -//It returns a list of all var names found in the string -//These vars must be in the [var_name] format -//It's only a proc because it's used in more than one place - -//Takes a string and a datum -//The string is well, obviously the string being checked -//The datum is used as a source for var names, to check validity -//Otherwise every single word could technically be a variable! -/proc/string2listofvars(t_string, datum/var_source) - if(!t_string || !var_source) - return list() - - . = list() - - var/var_found = findtext(t_string,"\[") //Not the actual variables, just a generic "should we even bother" check - if(var_found) - //Find var names - - // "A dog said hi [name]!" - // splittext() --> list("A dog said hi ","name]!" - // jointext() --> "A dog said hi name]!" - // splittext() --> list("A","dog","said","hi","name]!") - - t_string = replacetext(t_string,"\[","\[ ")//Necessary to resolve "word[var_name]" scenarios - var/list/list_value = splittext(t_string,"\[") - var/intermediate_stage = jointext(list_value, null) - - list_value = splittext(intermediate_stage," ") - for(var/value in list_value) - if(findtext(value,"]")) - value = splittext(value,"]") //"name]!" --> list("name","!") - for(var/A in value) - if(var_source.vars.Find(A)) - . += A - -//assumes format #RRGGBB #rrggbb -/proc/color_hex2num(A) - if(!A || length(A) != length_char(A)) - return 0 - var/R = hex2num(copytext(A, 2, 4)) - var/G = hex2num(copytext(A, 4, 6)) - var/B = hex2num(copytext(A, 6, 8)) - return R+G+B - -//word of warning: using a matrix like this as a color value will simplify it back to a string after being set -/proc/color_hex2color_matrix(string) - var/length = length(string) - if((length != 7 && length != 9) || length != length_char(string)) - return color_matrix_identity() - var/r = hex2num(copytext(string, 2, 4))/255 - var/g = hex2num(copytext(string, 4, 6))/255 - var/b = hex2num(copytext(string, 6, 8))/255 - var/a = 1 - if(length == 9) - a = hex2num(copytext(string, 8, 10))/255 - if(!isnum(r) || !isnum(g) || !isnum(b) || !isnum(a)) - return color_matrix_identity() - return list(r,0,0,0, 0,g,0,0, 0,0,b,0, 0,0,0,a, 0,0,0,0) - -//will drop all values not on the diagonal -/proc/color_matrix2color_hex(list/the_matrix) - if(!istype(the_matrix) || the_matrix.len != 20) - return "#ffffffff" - return rgb(the_matrix[1]*255, the_matrix[6]*255, the_matrix[11]*255, the_matrix[16]*255) - -/proc/type2parent(child) - var/string_type = "[child]" - var/last_slash = findlasttext(string_type, "/") - if(last_slash == 1) - switch(child) - if(/datum) - return null - if(/obj || /mob) - return /atom/movable - if(/area || /turf) - return /atom - else - return /datum - return text2path(copytext(string_type, 1, last_slash)) - -//returns a string the last bit of a type, without the preceeding '/' -/proc/type2top(the_type) - //handle the builtins manually - if(!ispath(the_type)) - return - switch(the_type) - if(/datum) - return "datum" - if(/atom) - return "atom" - if(/obj) - return "obj" - if(/mob) - return "mob" - if(/area) - return "area" - if(/turf) - return "turf" - else //regex everything else (works for /proc too) - return lowertext(replacetext("[the_type]", "[type2parent(the_type)]/", "")) +/* + * Holds procs designed to change one type of value, into another. + * Contains: + * hex2num & num2hex + * file2list + * angle2dir + * angle2text + * worldtime2text + * text2dir_extended & dir2text_short + */ + +//Returns an integer given a hex input, supports negative values "-ff" +//skips preceding invalid characters +//breaks when hittin invalid characters thereafter +// If safe=TRUE, returns null on incorrect input strings instead of CRASHing +/proc/hex2num(hex, safe=FALSE) + . = 0 + var/place = 1 + for(var/i in length(hex) to 1 step -1) + var/num = text2ascii(hex, i) + switch(num) + if(48 to 57) + num -= 48 //0-9 + if(97 to 102) + num -= 87 //a-f + if(65 to 70) + num -= 55 //A-F + if(45) + return . * -1 // - + else + if(safe) + return null + else + CRASH("Malformed hex number") + + . += num * place + place *= 16 + +//Returns the hex value of a decimal number +//len == length of returned string +//if len < 0 then the returned string will be as long as it needs to be to contain the data +//Only supports positive numbers +//if an invalid number is provided, it assumes num==0 +//Note, unlike previous versions, this one works from low to high <-- that way +/proc/num2hex(num, len=2) + if(!isnum(num)) + num = 0 + num = round(abs(num)) + . = "" + var/i=0 + while(1) + if(len<=0) + if(!num) + break + else + if(i>=len) + break + var/remainder = num/16 + num = round(remainder) + remainder = (remainder - num) * 16 + switch(remainder) + if(9,8,7,6,5,4,3,2,1) + . = "[remainder]" + . + if(10,11,12,13,14,15) + . = ascii2text(remainder+87) + . + else + . = "0" + . + i++ + return . + +//Splits the text of a file at seperator and returns them in a list. +//returns an empty list if the file doesn't exist +/world/proc/file2list(filename, seperator="\n", trim = TRUE) + if (trim) + return splittext(trim(file2text(filename)),seperator) + return splittext(file2text(filename),seperator) + +//Turns a direction into text +/proc/dir2text(direction) + switch(direction) + if(NORTH) + return "north" + if(SOUTH) + return "south" + if(EAST) + return "east" + if(WEST) + return "west" + if(NORTHEAST) + return "northeast" + if(SOUTHEAST) + return "southeast" + if(NORTHWEST) + return "northwest" + if(SOUTHWEST) + return "southwest" + else + return + +//Turns text into proper directions +/proc/text2dir(direction) + switch(uppertext(direction)) + if("NORTH") + return NORTH + if("SOUTH") + return SOUTH + if("EAST") + return EAST + if("WEST") + return WEST + if("NORTHEAST") + return NORTHEAST + if("NORTHWEST") + return NORTHWEST + if("SOUTHEAST") + return SOUTHEAST + if("SOUTHWEST") + return SOUTHWEST + else + return + +//Converts an angle (degrees) into an ss13 direction +GLOBAL_LIST_INIT(modulo_angle_to_dir, list(NORTH,NORTHEAST,EAST,SOUTHEAST,SOUTH,SOUTHWEST,WEST,NORTHWEST)) +#define angle2dir(X) (GLOB.modulo_angle_to_dir[round((((X%360)+382.5)%360)/45)+1]) + +/proc/angle2dir_cardinal(degree) + degree = SIMPLIFY_DEGREES(degree) + switch(round(degree, 0.1)) + if(315.5 to 360, 0 to 45.5) + return NORTH + if(45.6 to 135.5) + return EAST + if(135.6 to 225.5) + return SOUTH + if(225.6 to 315.5) + return WEST + +//returns the north-zero clockwise angle in degrees, given a direction +/proc/dir2angle(D) + switch(D) + if(NORTH) + return 0 + if(SOUTH) + return 180 + if(EAST) + return 90 + if(WEST) + return 270 + if(NORTHEAST) + return 45 + if(SOUTHEAST) + return 135 + if(NORTHWEST) + return 315 + if(SOUTHWEST) + return 225 + else + return null + +//Returns the angle in english +/proc/angle2text(degree) + return dir2text(angle2dir(degree)) + +//Converts a blend_mode constant to one acceptable to icon.Blend() +/proc/blendMode2iconMode(blend_mode) + switch(blend_mode) + if(BLEND_MULTIPLY) + return ICON_MULTIPLY + if(BLEND_ADD) + return ICON_ADD + if(BLEND_SUBTRACT) + return ICON_SUBTRACT + else + return ICON_OVERLAY + +//Converts a rights bitfield into a string +/proc/rights2text(rights, seperator="", prefix = "+") + seperator += prefix + if(rights & R_BUILD) + . += "[seperator]BUILDMODE" + if(rights & R_ADMIN) + . += "[seperator]ADMIN" + if(rights & R_BAN) + . += "[seperator]BAN" + if(rights & R_FUN) + . += "[seperator]FUN" + if(rights & R_SERVER) + . += "[seperator]SERVER" + if(rights & R_DEBUG) + . += "[seperator]DEBUG" + if(rights & R_POSSESS) + . += "[seperator]POSSESS" + if(rights & R_PERMISSIONS) + . += "[seperator]PERMISSIONS" + if(rights & R_STEALTH) + . += "[seperator]STEALTH" + if(rights & R_POLL) + . += "[seperator]POLL" + if(rights & R_VAREDIT) + . += "[seperator]VAREDIT" + if(rights & R_SOUND) + . += "[seperator]SOUND" + if(rights & R_SPAWN) + . += "[seperator]SPAWN" + if(rights & R_AUTOADMIN) + . += "[seperator]AUTOLOGIN" + if(rights & R_DBRANKS) + . += "[seperator]DBRANKS" + if(!.) + . = "NONE" + return . + +//colour formats +/proc/rgb2hsl(red, green, blue) + red /= 255;green /= 255;blue /= 255; + var/max = max(red,green,blue) + var/min = min(red,green,blue) + var/range = max-min + + var/hue=0;var/saturation=0;var/lightness=0; + lightness = (max + min)/2 + if(range != 0) + if(lightness < 0.5) + saturation = range/(max+min) + else + saturation = range/(2-max-min) + + var/dred = ((max-red)/(6*max)) + 0.5 + var/dgreen = ((max-green)/(6*max)) + 0.5 + var/dblue = ((max-blue)/(6*max)) + 0.5 + + if(max==red) + hue = dblue - dgreen + else if(max==green) + hue = dred - dblue + (1/3) + else + hue = dgreen - dred + (2/3) + if(hue < 0) + hue++ + else if(hue > 1) + hue-- + + return list(hue, saturation, lightness) + +/proc/hsl2rgb(hue, saturation, lightness) + var/red;var/green;var/blue; + if(saturation == 0) + red = lightness * 255 + green = red + blue = red + else + var/a;var/b; + if(lightness < 0.5) + b = lightness*(1+saturation) + else + b = (lightness+saturation) - (saturation*lightness) + a = 2*lightness - b + + red = round(255 * hue2rgb(a, b, hue+(1/3))) + green = round(255 * hue2rgb(a, b, hue)) + blue = round(255 * hue2rgb(a, b, hue-(1/3))) + + return list(red, green, blue) + +/proc/hue2rgb(a, b, hue) + if(hue < 0) + hue++ + else if(hue > 1) + hue-- + if(6*hue < 1) + return (a+(b-a)*6*hue) + if(2*hue < 1) + return b + if(3*hue < 2) + return (a+(b-a)*((2/3)-hue)*6) + return a + +//Turns a Body_parts_covered bitfield into a list of organ/limb names. +//(I challenge you to find a use for this) +/proc/body_parts_covered2organ_names(bpc) + var/list/covered_parts = list() + + if(!bpc) + return 0 + + if(bpc & FULL_BODY) + covered_parts |= list(BODY_ZONE_L_ARM,BODY_ZONE_R_ARM,BODY_ZONE_HEAD,BODY_ZONE_CHEST,BODY_ZONE_L_LEG,BODY_ZONE_R_LEG) + + else + if(bpc & HEAD) + covered_parts |= list(BODY_ZONE_HEAD) + if(bpc & CHEST) + covered_parts |= list(BODY_ZONE_CHEST) + if(bpc & GROIN) + covered_parts |= list(BODY_ZONE_CHEST) + + if(bpc & ARMS) + covered_parts |= list(BODY_ZONE_L_ARM,BODY_ZONE_R_ARM) + else + if(bpc & ARM_LEFT) + covered_parts |= list(BODY_ZONE_L_ARM) + if(bpc & ARM_RIGHT) + covered_parts |= list(BODY_ZONE_R_ARM) + + if(bpc & HANDS) + covered_parts |= list(BODY_ZONE_L_ARM,BODY_ZONE_R_ARM) + else + if(bpc & HAND_LEFT) + covered_parts |= list(BODY_ZONE_L_ARM) + if(bpc & HAND_RIGHT) + covered_parts |= list(BODY_ZONE_R_ARM) + + if(bpc & LEGS) + covered_parts |= list(BODY_ZONE_L_LEG,BODY_ZONE_R_LEG) + else + if(bpc & LEG_LEFT) + covered_parts |= list(BODY_ZONE_L_LEG) + if(bpc & LEG_RIGHT) + covered_parts |= list(BODY_ZONE_R_LEG) + + if(bpc & FEET) + covered_parts |= list(BODY_ZONE_L_LEG,BODY_ZONE_R_LEG) + else + if(bpc & FOOT_LEFT) + covered_parts |= list(BODY_ZONE_L_LEG) + if(bpc & FOOT_RIGHT) + covered_parts |= list(BODY_ZONE_R_LEG) + + return covered_parts + +/proc/slot2body_zone(slot) + switch(slot) + if(ITEM_SLOT_BACK, ITEM_SLOT_OCLOTHING, ITEM_SLOT_ICLOTHING, ITEM_SLOT_BELT, ITEM_SLOT_ID) + return BODY_ZONE_CHEST + + if(ITEM_SLOT_GLOVES, ITEM_SLOT_HANDS, ITEM_SLOT_HANDCUFFED) + return pick(BODY_ZONE_PRECISE_L_HAND, BODY_ZONE_PRECISE_R_HAND) + + if(ITEM_SLOT_HEAD, ITEM_SLOT_NECK, ITEM_SLOT_NECK, ITEM_SLOT_EARS) + return BODY_ZONE_HEAD + + if(ITEM_SLOT_MASK) + return BODY_ZONE_PRECISE_MOUTH + + if(ITEM_SLOT_EYES) + return BODY_ZONE_PRECISE_EYES + + if(ITEM_SLOT_FEET) + return pick(BODY_ZONE_PRECISE_R_FOOT, BODY_ZONE_PRECISE_L_FOOT) + + if(ITEM_SLOT_LEGCUFFED) + return pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) + +//adapted from http://www.tannerhelland.com/4435/convert-temperature-rgb-algorithm-code/ +/proc/heat2colour(temp) + return rgb(heat2colour_r(temp), heat2colour_g(temp), heat2colour_b(temp)) + + +/proc/heat2colour_r(temp) + temp /= 100 + if(temp <= 66) + . = 255 + else + . = max(0, min(255, 329.698727446 * (temp - 60) ** -0.1332047592)) + + +/proc/heat2colour_g(temp) + temp /= 100 + if(temp <= 66) + . = max(0, min(255, 99.4708025861 * log(temp) - 161.1195681661)) + else + . = max(0, min(255, 288.1221685293 * ((temp - 60) ** -0.075148492))) + + +/proc/heat2colour_b(temp) + temp /= 100 + if(temp >= 66) + . = 255 + else + if(temp <= 16) + . = 0 + else + . = max(0, min(255, 138.5177312231 * log(temp - 10) - 305.0447927307)) + + +/proc/color2hex(color) //web colors + if(!color) + return "#000000" + + switch(color) + if("white") + return "#FFFFFF" + if("black") + return "#000000" + if("gray") + return "#808080" + if("brown") + return "#A52A2A" + if("red") + return "#FF0000" + if("darkred") + return "#8B0000" + if("crimson") + return "#DC143C" + if("orange") + return "#FFA500" + if("yellow") + return "#FFFF00" + if("green") + return "#008000" + if("lime") + return "#00FF00" + if("darkgreen") + return "#006400" + if("cyan") + return "#00FFFF" + if("blue") + return "#0000FF" + if("navy") + return "#000080" + if("teal") + return "#008080" + if("purple") + return "#800080" + if("indigo") + return "#4B0082" + else + return "#FFFFFF" + + +//This is a weird one: +//It returns a list of all var names found in the string +//These vars must be in the [var_name] format +//It's only a proc because it's used in more than one place + +//Takes a string and a datum +//The string is well, obviously the string being checked +//The datum is used as a source for var names, to check validity +//Otherwise every single word could technically be a variable! +/proc/string2listofvars(t_string, datum/var_source) + if(!t_string || !var_source) + return list() + + . = list() + + var/var_found = findtext(t_string,"\[") //Not the actual variables, just a generic "should we even bother" check + if(var_found) + //Find var names + + // "A dog said hi [name]!" + // splittext() --> list("A dog said hi ","name]!" + // jointext() --> "A dog said hi name]!" + // splittext() --> list("A","dog","said","hi","name]!") + + t_string = replacetext(t_string,"\[","\[ ")//Necessary to resolve "word[var_name]" scenarios + var/list/list_value = splittext(t_string,"\[") + var/intermediate_stage = jointext(list_value, null) + + list_value = splittext(intermediate_stage," ") + for(var/value in list_value) + if(findtext(value,"]")) + value = splittext(value,"]") //"name]!" --> list("name","!") + for(var/A in value) + if(var_source.vars.Find(A)) + . += A + +//assumes format #RRGGBB #rrggbb +/proc/color_hex2num(A) + if(!A || length(A) != length_char(A)) + return 0 + var/R = hex2num(copytext(A, 2, 4)) + var/G = hex2num(copytext(A, 4, 6)) + var/B = hex2num(copytext(A, 6, 8)) + return R+G+B + +//word of warning: using a matrix like this as a color value will simplify it back to a string after being set +/proc/color_hex2color_matrix(string) + var/length = length(string) + if((length != 7 && length != 9) || length != length_char(string)) + return color_matrix_identity() + var/r = hex2num(copytext(string, 2, 4))/255 + var/g = hex2num(copytext(string, 4, 6))/255 + var/b = hex2num(copytext(string, 6, 8))/255 + var/a = 1 + if(length == 9) + a = hex2num(copytext(string, 8, 10))/255 + if(!isnum(r) || !isnum(g) || !isnum(b) || !isnum(a)) + return color_matrix_identity() + return list(r,0,0,0, 0,g,0,0, 0,0,b,0, 0,0,0,a, 0,0,0,0) + +//will drop all values not on the diagonal +/proc/color_matrix2color_hex(list/the_matrix) + if(!istype(the_matrix) || the_matrix.len != 20) + return "#ffffffff" + return rgb(the_matrix[1]*255, the_matrix[6]*255, the_matrix[11]*255, the_matrix[16]*255) + +/proc/type2parent(child) + var/string_type = "[child]" + var/last_slash = findlasttext(string_type, "/") + if(last_slash == 1) + switch(child) + if(/datum) + return null + if(/obj || /mob) + return /atom/movable + if(/area || /turf) + return /atom + else + return /datum + return text2path(copytext(string_type, 1, last_slash)) + +//returns a string the last bit of a type, without the preceeding '/' +/proc/type2top(the_type) + //handle the builtins manually + if(!ispath(the_type)) + return + switch(the_type) + if(/datum) + return "datum" + if(/atom) + return "atom" + if(/obj) + return "obj" + if(/mob) + return "mob" + if(/area) + return "area" + if(/turf) + return "turf" + else //regex everything else (works for /proc too) + return lowertext(replacetext("[the_type]", "[type2parent(the_type)]/", "")) diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index 5af0189a64ed..45b2b3021f37 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -688,7 +688,7 @@ GLOBAL_LIST_INIT(WALLITEMS, typecacheof(list( /obj/machinery/computer/security/telescreen, /obj/machinery/embedded_controller/radio/simple_vent_controller, /obj/item/storage/secure/safe, /obj/machinery/door_timer, /obj/machinery/flasher, /obj/machinery/keycard_auth, /obj/structure/mirror, /obj/structure/fireaxecabinet, /obj/machinery/computer/security/telescreen/entertainment, - /obj/structure/sign/picture_frame + /obj/structure/sign/picture_frame, /obj/machinery/bounty_board ))) GLOBAL_LIST_INIT(WALLITEMS_EXTERNAL, typecacheof(list( @@ -1494,28 +1494,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 || !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() /proc/num2sign(numeric) if(numeric > 0) diff --git a/code/_compile_options.dm b/code/_compile_options.dm index fad4eee1d473..129cc33b7009 100644 --- a/code/_compile_options.dm +++ b/code/_compile_options.dm @@ -1,58 +1,71 @@ -//#define TESTING //By using the testing("message") proc you can create debug-feedback for people with this - //uncommented, but not visible in the release version) - -//#define DATUMVAR_DEBUGGING_MODE //Enables the ability to cache datum vars and retrieve later for debugging which vars changed. - -// Comment this out if you are debugging problems that might be obscured by custom error handling in world/Error -#ifdef DEBUG -#define USE_CUSTOM_ERROR_HANDLER -#endif - -#ifdef TESTING -#define DATUMVAR_DEBUGGING_MODE - -//#define GC_FAILURE_HARD_LOOKUP //makes paths that fail to GC call find_references before del'ing. - //implies FIND_REF_NO_CHECK_TICK - -//#define FIND_REF_NO_CHECK_TICK //Sets world.loop_checks to false and prevents find references from sleeping - - -//#define VISUALIZE_ACTIVE_TURFS //Highlights atmos active turfs in green -#endif - -//#define UNIT_TESTS //Enables unit tests via TEST_RUN_PARAMETER - -#ifndef PRELOAD_RSC //set to: -#define PRELOAD_RSC 2 // 0 to allow using external resources or on-demand behaviour; -#endif // 1 to use the default behaviour; - // 2 for preloading absolutely everything; - -#ifdef LOWMEMORYMODE -#define FORCE_MAP "_maps/runtimestation.json" -#endif - -//Update this whenever you need to take advantage of more recent byond features -#define MIN_COMPILER_VERSION 513 -#define MIN_COMPILER_BUILD 1508 -#if DM_VERSION < MIN_COMPILER_VERSION || DM_BUILD < MIN_COMPILER_BUILD -//Don't forget to update this part -#error Your version of BYOND is too out-of-date to compile this project. Go to https://secure.byond.com/download and update. -#error You need version 513.1508 or higher -#endif - -//Additional code for the above flags. -#ifdef TESTING -#warn compiling in TESTING mode. testing() debug messages will be visible. -#endif - -#ifdef GC_FAILURE_HARD_LOOKUP -#define FIND_REF_NO_CHECK_TICK -#endif - -#ifdef TRAVISBUILDING -#define UNIT_TESTS -#endif - -#ifdef TRAVISTESTING -#define TESTING -#endif +//#define TESTING //By using the testing("message") proc you can create debug-feedback for people with this + //uncommented, but not visible in the release version) + +//#define DATUMVAR_DEBUGGING_MODE //Enables the ability to cache datum vars and retrieve later for debugging which vars changed. + +// Comment this out if you are debugging problems that might be obscured by custom error handling in world/Error +#ifdef DEBUG +#define USE_CUSTOM_ERROR_HANDLER +#endif + +#ifdef TESTING +#define DATUMVAR_DEBUGGING_MODE + +/* +* Enables extools-powered reference tracking system, letting you see what is referencing objects that refuse to hard delete. +* +* * Requires TESTING to be defined to work. +*/ +//#define REFERENCE_TRACKING + +///Method of tracking references without using extools. Slower, kept to avoid over-reliance on extools. +//#define LEGACY_REFERENCE_TRACKING +#ifdef LEGACY_REFERENCE_TRACKING + +///Use the legacy reference on things hard deleting by default. +//#define GC_FAILURE_HARD_LOOKUP +#ifdef GC_FAILURE_HARD_LOOKUP +#define FIND_REF_NO_CHECK_TICK +#endif //ifdef GC_FAILURE_HARD_LOOKUP + +#endif //ifdef LEGACY_REFERENCE_TRACKING + +//#define VISUALIZE_ACTIVE_TURFS //Highlights atmos active turfs in green +#endif //ifdef TESTING + +//#define UNIT_TESTS //Enables unit tests via TEST_RUN_PARAMETER + +#ifndef PRELOAD_RSC //set to: +#define PRELOAD_RSC 2 // 0 to allow using external resources or on-demand behaviour; +#endif // 1 to use the default behaviour; + // 2 for preloading absolutely everything; + +#ifdef LOWMEMORYMODE +#define FORCE_MAP "_maps/runtimestation.json" +#endif + +//Update this whenever you need to take advantage of more recent byond features +#define MIN_COMPILER_VERSION 513 +#define MIN_COMPILER_BUILD 1514 +#if DM_VERSION < MIN_COMPILER_VERSION || DM_BUILD < MIN_COMPILER_BUILD +//Don't forget to update this part +#error Your version of BYOND is too out-of-date to compile this project. Go to https://secure.byond.com/download and update. +#error You need version 513.1514 or higher +#endif + +//Additional code for the above flags. +#ifdef TESTING +#warn compiling in TESTING mode. testing() debug messages will be visible. +#endif + +#ifdef TRAVISBUILDING +#define UNIT_TESTS +#endif + +#ifdef TRAVISTESTING +#define TESTING +#endif + +// A reasonable number of maximum overlays an object needs +// If you think you need more, rethink it +#define MAX_ATOM_OVERLAYS 100 diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm index 940d46ad0d10..de158543c366 100644 --- a/code/_globalvars/bitfields.dm +++ b/code/_globalvars/bitfields.dm @@ -1,217 +1,217 @@ -GLOBAL_LIST_INIT(bitfields, list( - "appearance_flags" = list( - "LONG_GLIDE" = LONG_GLIDE, - "RESET_COLOR" = RESET_COLOR, - "RESET_ALPHA" = RESET_ALPHA, - "RESET_TRANSFORM" = RESET_TRANSFORM, - "NO_CLIENT_COLOR" = NO_CLIENT_COLOR, - "KEEP_TOGETHER" = KEEP_TOGETHER, - "KEEP_APART" = KEEP_APART, - "PLANE_MASTER" = PLANE_MASTER, - "TILE_BOUND" = TILE_BOUND, - "PIXEL_SCALE" = PIXEL_SCALE - ), - "sight" = list( - "SEE_INFRA" = SEE_INFRA, - "SEE_SELF" = SEE_SELF, - "SEE_MOBS" = SEE_MOBS, - "SEE_OBJS" = SEE_OBJS, - "SEE_TURFS" = SEE_TURFS, - "SEE_PIXELS" = SEE_PIXELS, - "SEE_THRU" = SEE_THRU, - "SEE_BLACKNESS" = SEE_BLACKNESS, - "BLIND" = BLIND - ), - "obj_flags" = list( - "EMAGGED" = EMAGGED, - "IN_USE" = IN_USE, - "CAN_BE_HIT" = CAN_BE_HIT, - "BEING_SHOCKED" = BEING_SHOCKED, - "DANGEROUS_POSSESSION" = DANGEROUS_POSSESSION, - "ON_BLUEPRINTS" = ON_BLUEPRINTS, - "UNIQUE_RENAME" = UNIQUE_RENAME, - "USES_TGUI" = USES_TGUI, - "FROZEN" = FROZEN, - ), - "datum_flags" = list( - "DF_USE_TAG" = DF_USE_TAG, - "DF_VAR_EDITED" = DF_VAR_EDITED, - "DF_ISPROCESSING" = DF_ISPROCESSING, - ), - "item_flags" = list( - "BEING_REMOVED" = BEING_REMOVED, - "IN_INVENTORY" = IN_INVENTORY, - "FORCE_STRING_OVERRIDE" = FORCE_STRING_OVERRIDE, - "NEEDS_PERMIT" = NEEDS_PERMIT, - "SLOWS_WHILE_IN_HAND" = SLOWS_WHILE_IN_HAND, - "NO_MAT_REDEMPTION" = NO_MAT_REDEMPTION, - "DROPDEL" = DROPDEL, - "NOBLUDGEON" = NOBLUDGEON, - "ABSTRACT" = ABSTRACT, - "IN_STORAGE" = IN_STORAGE, - ), - "admin_flags" = list( - "BUILDMODE" = R_BUILD, - "ADMIN" = R_ADMIN, - "BAN" = R_BAN, - "FUN" = R_FUN, - "SERVER" = R_SERVER, - "DEBUG" = R_DEBUG, - "POSSESS" = R_POSSESS, - "PERMISSIONS" = R_PERMISSIONS, - "STEALTH" = R_STEALTH, - "POLL" = R_POLL, - "VAREDIT" = R_VAREDIT, - "SOUNDS" = R_SOUND, - "SPAWN" = R_SPAWN, - "AUTOLOGIN" = R_AUTOADMIN, - "DBRANKS" = R_DBRANKS - ), - "interaction_flags_atom" = list( - "INTERACT_ATOM_REQUIRES_ANCHORED" = INTERACT_ATOM_REQUIRES_ANCHORED, - "INTERACT_ATOM_ATTACK_HAND" = INTERACT_ATOM_ATTACK_HAND, - "INTERACT_ATOM_UI_INTERACT" = INTERACT_ATOM_UI_INTERACT, - "INTERACT_ATOM_REQUIRES_DEXTERITY" = INTERACT_ATOM_REQUIRES_DEXTERITY, - "INTERACT_ATOM_IGNORE_INCAPACITATED" = INTERACT_ATOM_IGNORE_INCAPACITATED, - "INTERACT_ATOM_IGNORE_RESTRAINED" = INTERACT_ATOM_IGNORE_RESTRAINED, - "INTERACT_ATOM_CHECK_GRAB" = INTERACT_ATOM_CHECK_GRAB, - "INTERACT_ATOM_NO_FINGERPRINT_ATTACK_HAND" = INTERACT_ATOM_NO_FINGERPRINT_ATTACK_HAND, - "INTERACT_ATOM_NO_FINGERPRINT_INTERACT" = INTERACT_ATOM_NO_FINGERPRINT_INTERACT - ), - "interaction_flags_machine" = list( - "INTERACT_MACHINE_OPEN" = INTERACT_MACHINE_OPEN, - "INTERACT_MACHINE_OFFLINE" = INTERACT_MACHINE_OFFLINE, - "INTERACT_MACHINE_WIRES_IF_OPEN" = INTERACT_MACHINE_WIRES_IF_OPEN, - "INTERACT_MACHINE_ALLOW_SILICON" = INTERACT_MACHINE_ALLOW_SILICON, - "INTERACT_MACHINE_OPEN_SILICON" = INTERACT_MACHINE_OPEN_SILICON, - "INTERACT_MACHINE_REQUIRES_SILICON" = INTERACT_MACHINE_REQUIRES_SILICON, - "INTERACT_MACHINE_SET_MACHINE" = INTERACT_MACHINE_SET_MACHINE - ), - "interaction_flags_item" = list( - "INTERACT_ITEM_ATTACK_HAND_PICKUP" = INTERACT_ITEM_ATTACK_HAND_PICKUP, - ), - "pass_flags" = list( - "PASSTABLE" = PASSTABLE, - "PASSGLASS" = PASSGLASS, - "PASSGRILLE" = PASSGRILLE, - "PASSBLOB" = PASSBLOB, - "PASSMOB" = PASSMOB, - "PASSCLOSEDTURF" = PASSCLOSEDTURF, - "LETPASSTHROW" = LETPASSTHROW - ), - "movement_type" = list( - "GROUND" = GROUND, - "FLYING" = FLYING, - "VENTCRAWLING" = VENTCRAWLING, - "FLOATING" = FLOATING, - "UNSTOPPABLE" = UNSTOPPABLE - ), - "resistance_flags" = list( - "LAVA_PROOF" = LAVA_PROOF, - "FIRE_PROOF" = FIRE_PROOF, - "FLAMMABLE" = FLAMMABLE, - "ON_FIRE" = ON_FIRE, - "UNACIDABLE" = UNACIDABLE, - "ACID_PROOF" = ACID_PROOF, - "INDESTRUCTIBLE" = INDESTRUCTIBLE, - "FREEZE_PROOF" = FREEZE_PROOF - ), - "flags_1" = list( - "NOJAUNT_1" = NOJAUNT_1, - "UNUSED_RESERVATION_TURF_1" = UNUSED_RESERVATION_TURF_1, - "CAN_BE_DIRTY_1" = CAN_BE_DIRTY_1, - "CULT_PERMITTED_1" = CULT_PERMITTED_1, - "HEAR_1" = HEAR_1, - "CONDUCT_1" = CONDUCT_1, - "NO_LAVA_GEN_1" = NO_LAVA_GEN_1, - "NODECONSTRUCT_1" = NODECONSTRUCT_1, - "OVERLAY_QUEUED_1" = OVERLAY_QUEUED_1, - "ON_BORDER_1" = ON_BORDER_1, - "NO_RUINS_1" = NO_RUINS_1, - "PREVENT_CLICK_UNDER_1" = PREVENT_CLICK_UNDER_1, - "HOLOGRAM_1" = HOLOGRAM_1, - "SHOCKED_1" = SHOCKED_1, - "INITIALIZED_1" = INITIALIZED_1, - "ADMIN_SPAWNED_1" = ADMIN_SPAWNED_1, - "PREVENT_CONTENTS_EXPLOSION_1" = PREVENT_CONTENTS_EXPLOSION_1, - "RAD_PROTECT_CONTENTS_1" = RAD_PROTECT_CONTENTS_1, - "RAD_NO_CONTAMINATE_1" = RAD_NO_CONTAMINATE_1 - ), - "flags_ricochet" = list( - "RICOCHET_SHINY" = RICOCHET_SHINY, - "RICOCHET_HARD" = RICOCHET_HARD - ), - "clothing_flags" = list( - "LAVAPROTECT" = LAVAPROTECT, - "STOPSPRESSUREDAMAGE" = STOPSPRESSUREDAMAGE, - "BLOCK_GAS_SMOKE_EFFECT" = BLOCK_GAS_SMOKE_EFFECT, - "ALLOWINTERNALS" = ALLOWINTERNALS, //Wasp Port - Citadel Internals - "NOSLIP" = NOSLIP, - "THICKMATERIAL" = THICKMATERIAL, - "VOICEBOX_TOGGLABLE" = VOICEBOX_TOGGLABLE, - "VOICEBOX_DISABLED" = VOICEBOX_DISABLED, - "SCAN_REAGENTS" = SCAN_REAGENTS, - "BLOCKS_SHOVE_KNOCKDOWN" = BLOCKS_SHOVE_KNOCKDOWN, - "SNUG_FIT" = SNUG_FIT, - "ANTI_TINFOIL_MANEUVER" = ANTI_TINFOIL_MANEUVER, - ), - "zap_flags" = list( - "ZAP_MOB_DAMAGE" = ZAP_MOB_DAMAGE, - "ZAP_OBJ_DAMAGE" = ZAP_OBJ_DAMAGE, - "ZAP_MOB_STUN" = ZAP_MOB_STUN, - "ZAP_ALLOW_DUPLICATES" = ZAP_ALLOW_DUPLICATES, - "ZAP_MACHINE_EXPLOSIVE" = ZAP_MACHINE_EXPLOSIVE, - ), - "smooth" = list( - "SMOOTH_TRUE" = SMOOTH_TRUE, - "SMOOTH_MORE" = SMOOTH_MORE, - "SMOOTH_DIAGONAL" = SMOOTH_DIAGONAL, - "SMOOTH_BORDER" = SMOOTH_BORDER, - "SMOOTH_QUEUED" = SMOOTH_QUEUED, - ), - "car_traits" = list( - "CAN_KIDNAP" = CAN_KIDNAP, - ), - "mobility_flags" = list( - "MOVE" = MOBILITY_MOVE, - "STAND" = MOBILITY_STAND, - "PICKUP" = MOBILITY_PICKUP, - "USE" = MOBILITY_USE, - "UI" = MOBILITY_UI, - "STORAGE" = MOBILITY_STORAGE, - "PULL" = MOBILITY_PULL, - ), - "disease_flags" = list ( - "CURABLE" = CURABLE, - "CAN_CARRY" = CAN_CARRY, - "CAN_RESIST" = CAN_RESIST - ), - "mob_biotypes" = list ( - "MOB_ORGANIC" = MOB_ORGANIC, - "MOB_MINERAL" = MOB_MINERAL, - "MOB_ROBOTIC" = MOB_ROBOTIC, - "MOB_UNDEAD" = MOB_UNDEAD, - "MOB_HUMANOID" = MOB_HUMANOID, - "MOB_BUG" = MOB_BUG, - "MOB_BEAST" = MOB_BEAST, - "MOB_EPIC" = MOB_EPIC, - "MOB_REPTILE" = MOB_REPTILE, - "MOB_SPIRIT" = MOB_SPIRIT - ), - "machine_stat" = list ( - "BROKEN" = BROKEN, - "NOPOWER" = NOPOWER, - "MAINT" = MAINT, - "EMPED" = EMPED - ), - "vis_flags" = list( - "VIS_INHERIT_ICON" = VIS_INHERIT_ICON, - "VIS_INHERIT_ICON_STATE" = VIS_INHERIT_ICON_STATE, - "VIS_INHERIT_DIR" = VIS_INHERIT_DIR, - "VIS_INHERIT_LAYER" = VIS_INHERIT_LAYER, - "VIS_INHERIT_PLANE" = VIS_INHERIT_PLANE, - "VIS_INHERIT_ID" = VIS_INHERIT_ID, - "VIS_UNDERLAY" = VIS_UNDERLAY, - "VIS_HIDE" = VIS_HIDE - ) - )) +GLOBAL_LIST_INIT(bitfields, list( + "appearance_flags" = list( + "LONG_GLIDE" = LONG_GLIDE, + "RESET_COLOR" = RESET_COLOR, + "RESET_ALPHA" = RESET_ALPHA, + "RESET_TRANSFORM" = RESET_TRANSFORM, + "NO_CLIENT_COLOR" = NO_CLIENT_COLOR, + "KEEP_TOGETHER" = KEEP_TOGETHER, + "KEEP_APART" = KEEP_APART, + "PLANE_MASTER" = PLANE_MASTER, + "TILE_BOUND" = TILE_BOUND, + "PIXEL_SCALE" = PIXEL_SCALE + ), + "sight" = list( + "SEE_INFRA" = SEE_INFRA, + "SEE_SELF" = SEE_SELF, + "SEE_MOBS" = SEE_MOBS, + "SEE_OBJS" = SEE_OBJS, + "SEE_TURFS" = SEE_TURFS, + "SEE_PIXELS" = SEE_PIXELS, + "SEE_THRU" = SEE_THRU, + "SEE_BLACKNESS" = SEE_BLACKNESS, + "BLIND" = BLIND + ), + "obj_flags" = list( + "EMAGGED" = EMAGGED, + "IN_USE" = IN_USE, + "CAN_BE_HIT" = CAN_BE_HIT, + "BEING_SHOCKED" = BEING_SHOCKED, + "DANGEROUS_POSSESSION" = DANGEROUS_POSSESSION, + "ON_BLUEPRINTS" = ON_BLUEPRINTS, + "UNIQUE_RENAME" = UNIQUE_RENAME, + "USES_TGUI" = USES_TGUI, + "FROZEN" = FROZEN, + ), + "datum_flags" = list( + "DF_USE_TAG" = DF_USE_TAG, + "DF_VAR_EDITED" = DF_VAR_EDITED, + "DF_ISPROCESSING" = DF_ISPROCESSING, + ), + "item_flags" = list( + "BEING_REMOVED" = BEING_REMOVED, + "IN_INVENTORY" = IN_INVENTORY, + "FORCE_STRING_OVERRIDE" = FORCE_STRING_OVERRIDE, + "NEEDS_PERMIT" = NEEDS_PERMIT, + "SLOWS_WHILE_IN_HAND" = SLOWS_WHILE_IN_HAND, + "NO_MAT_REDEMPTION" = NO_MAT_REDEMPTION, + "DROPDEL" = DROPDEL, + "NOBLUDGEON" = NOBLUDGEON, + "ABSTRACT" = ABSTRACT, + "IN_STORAGE" = IN_STORAGE, + ), + "admin_flags" = list( + "BUILDMODE" = R_BUILD, + "ADMIN" = R_ADMIN, + "BAN" = R_BAN, + "FUN" = R_FUN, + "SERVER" = R_SERVER, + "DEBUG" = R_DEBUG, + "POSSESS" = R_POSSESS, + "PERMISSIONS" = R_PERMISSIONS, + "STEALTH" = R_STEALTH, + "POLL" = R_POLL, + "VAREDIT" = R_VAREDIT, + "SOUNDS" = R_SOUND, + "SPAWN" = R_SPAWN, + "AUTOLOGIN" = R_AUTOADMIN, + "DBRANKS" = R_DBRANKS + ), + "interaction_flags_atom" = list( + "INTERACT_ATOM_REQUIRES_ANCHORED" = INTERACT_ATOM_REQUIRES_ANCHORED, + "INTERACT_ATOM_ATTACK_HAND" = INTERACT_ATOM_ATTACK_HAND, + "INTERACT_ATOM_UI_INTERACT" = INTERACT_ATOM_UI_INTERACT, + "INTERACT_ATOM_REQUIRES_DEXTERITY" = INTERACT_ATOM_REQUIRES_DEXTERITY, + "INTERACT_ATOM_IGNORE_INCAPACITATED" = INTERACT_ATOM_IGNORE_INCAPACITATED, + "INTERACT_ATOM_IGNORE_RESTRAINED" = INTERACT_ATOM_IGNORE_RESTRAINED, + "INTERACT_ATOM_CHECK_GRAB" = INTERACT_ATOM_CHECK_GRAB, + "INTERACT_ATOM_NO_FINGERPRINT_ATTACK_HAND" = INTERACT_ATOM_NO_FINGERPRINT_ATTACK_HAND, + "INTERACT_ATOM_NO_FINGERPRINT_INTERACT" = INTERACT_ATOM_NO_FINGERPRINT_INTERACT + ), + "interaction_flags_machine" = list( + "INTERACT_MACHINE_OPEN" = INTERACT_MACHINE_OPEN, + "INTERACT_MACHINE_OFFLINE" = INTERACT_MACHINE_OFFLINE, + "INTERACT_MACHINE_WIRES_IF_OPEN" = INTERACT_MACHINE_WIRES_IF_OPEN, + "INTERACT_MACHINE_ALLOW_SILICON" = INTERACT_MACHINE_ALLOW_SILICON, + "INTERACT_MACHINE_OPEN_SILICON" = INTERACT_MACHINE_OPEN_SILICON, + "INTERACT_MACHINE_REQUIRES_SILICON" = INTERACT_MACHINE_REQUIRES_SILICON, + "INTERACT_MACHINE_SET_MACHINE" = INTERACT_MACHINE_SET_MACHINE + ), + "interaction_flags_item" = list( + "INTERACT_ITEM_ATTACK_HAND_PICKUP" = INTERACT_ITEM_ATTACK_HAND_PICKUP, + ), + "pass_flags" = list( + "PASSTABLE" = PASSTABLE, + "PASSGLASS" = PASSGLASS, + "PASSGRILLE" = PASSGRILLE, + "PASSBLOB" = PASSBLOB, + "PASSMOB" = PASSMOB, + "PASSCLOSEDTURF" = PASSCLOSEDTURF, + "LETPASSTHROW" = LETPASSTHROW + ), + "movement_type" = list( + "GROUND" = GROUND, + "FLYING" = FLYING, + "VENTCRAWLING" = VENTCRAWLING, + "FLOATING" = FLOATING, + "UNSTOPPABLE" = UNSTOPPABLE + ), + "resistance_flags" = list( + "LAVA_PROOF" = LAVA_PROOF, + "FIRE_PROOF" = FIRE_PROOF, + "FLAMMABLE" = FLAMMABLE, + "ON_FIRE" = ON_FIRE, + "UNACIDABLE" = UNACIDABLE, + "ACID_PROOF" = ACID_PROOF, + "INDESTRUCTIBLE" = INDESTRUCTIBLE, + "FREEZE_PROOF" = FREEZE_PROOF + ), + "flags_1" = list( + "NOJAUNT_1" = NOJAUNT_1, + "UNUSED_RESERVATION_TURF_1" = UNUSED_RESERVATION_TURF_1, + "CAN_BE_DIRTY_1" = CAN_BE_DIRTY_1, + "CULT_PERMITTED_1" = CULT_PERMITTED_1, + "HEAR_1" = HEAR_1, + "CONDUCT_1" = CONDUCT_1, + "NO_LAVA_GEN_1" = NO_LAVA_GEN_1, + "NODECONSTRUCT_1" = NODECONSTRUCT_1, + "OVERLAY_QUEUED_1" = OVERLAY_QUEUED_1, + "ON_BORDER_1" = ON_BORDER_1, + "NO_RUINS_1" = NO_RUINS_1, + "PREVENT_CLICK_UNDER_1" = PREVENT_CLICK_UNDER_1, + "HOLOGRAM_1" = HOLOGRAM_1, + "SHOCKED_1" = SHOCKED_1, + "INITIALIZED_1" = INITIALIZED_1, + "ADMIN_SPAWNED_1" = ADMIN_SPAWNED_1, + "PREVENT_CONTENTS_EXPLOSION_1" = PREVENT_CONTENTS_EXPLOSION_1, + "RAD_PROTECT_CONTENTS_1" = RAD_PROTECT_CONTENTS_1, + "RAD_NO_CONTAMINATE_1" = RAD_NO_CONTAMINATE_1 + ), + "flags_ricochet" = list( + "RICOCHET_SHINY" = RICOCHET_SHINY, + "RICOCHET_HARD" = RICOCHET_HARD + ), + "clothing_flags" = list( + "LAVAPROTECT" = LAVAPROTECT, + "STOPSPRESSUREDAMAGE" = STOPSPRESSUREDAMAGE, + "BLOCK_GAS_SMOKE_EFFECT" = BLOCK_GAS_SMOKE_EFFECT, + "ALLOWINTERNALS" = ALLOWINTERNALS, //Wasp Port - Citadel Internals + "NOSLIP" = NOSLIP, + "THICKMATERIAL" = THICKMATERIAL, + "VOICEBOX_TOGGLABLE" = VOICEBOX_TOGGLABLE, + "VOICEBOX_DISABLED" = VOICEBOX_DISABLED, + "SCAN_REAGENTS" = SCAN_REAGENTS, + "BLOCKS_SHOVE_KNOCKDOWN" = BLOCKS_SHOVE_KNOCKDOWN, + "SNUG_FIT" = SNUG_FIT, + "ANTI_TINFOIL_MANEUVER" = ANTI_TINFOIL_MANEUVER, + ), + "zap_flags" = list( + "ZAP_MOB_DAMAGE" = ZAP_MOB_DAMAGE, + "ZAP_OBJ_DAMAGE" = ZAP_OBJ_DAMAGE, + "ZAP_MOB_STUN" = ZAP_MOB_STUN, + "ZAP_ALLOW_DUPLICATES" = ZAP_ALLOW_DUPLICATES, + "ZAP_MACHINE_EXPLOSIVE" = ZAP_MACHINE_EXPLOSIVE, + ), + "smooth" = list( + "SMOOTH_TRUE" = SMOOTH_TRUE, + "SMOOTH_MORE" = SMOOTH_MORE, + "SMOOTH_DIAGONAL" = SMOOTH_DIAGONAL, + "SMOOTH_BORDER" = SMOOTH_BORDER, + "SMOOTH_QUEUED" = SMOOTH_QUEUED, + ), + "car_traits" = list( + "CAN_KIDNAP" = CAN_KIDNAP, + ), + "mobility_flags" = list( + "MOVE" = MOBILITY_MOVE, + "STAND" = MOBILITY_STAND, + "PICKUP" = MOBILITY_PICKUP, + "USE" = MOBILITY_USE, + "UI" = MOBILITY_UI, + "STORAGE" = MOBILITY_STORAGE, + "PULL" = MOBILITY_PULL, + ), + "disease_flags" = list ( + "CURABLE" = CURABLE, + "CAN_CARRY" = CAN_CARRY, + "CAN_RESIST" = CAN_RESIST + ), + "mob_biotypes" = list ( + "MOB_ORGANIC" = MOB_ORGANIC, + "MOB_MINERAL" = MOB_MINERAL, + "MOB_ROBOTIC" = MOB_ROBOTIC, + "MOB_UNDEAD" = MOB_UNDEAD, + "MOB_HUMANOID" = MOB_HUMANOID, + "MOB_BUG" = MOB_BUG, + "MOB_BEAST" = MOB_BEAST, + "MOB_EPIC" = MOB_EPIC, + "MOB_REPTILE" = MOB_REPTILE, + "MOB_SPIRIT" = MOB_SPIRIT + ), + "machine_stat" = list ( + "BROKEN" = BROKEN, + "NOPOWER" = NOPOWER, + "MAINT" = MAINT, + "EMPED" = EMPED + ), + "vis_flags" = list( + "VIS_INHERIT_ICON" = VIS_INHERIT_ICON, + "VIS_INHERIT_ICON_STATE" = VIS_INHERIT_ICON_STATE, + "VIS_INHERIT_DIR" = VIS_INHERIT_DIR, + "VIS_INHERIT_LAYER" = VIS_INHERIT_LAYER, + "VIS_INHERIT_PLANE" = VIS_INHERIT_PLANE, + "VIS_INHERIT_ID" = VIS_INHERIT_ID, + "VIS_UNDERLAY" = VIS_UNDERLAY, + "VIS_HIDE" = VIS_HIDE + ) + )) diff --git a/code/_globalvars/configuration.dm b/code/_globalvars/configuration.dm index e8b2bd0baeb2..36c360e11ae4 100644 --- a/code/_globalvars/configuration.dm +++ b/code/_globalvars/configuration.dm @@ -1,37 +1,37 @@ -GLOBAL_REAL(config, /datum/controller/configuration) - -GLOBAL_DATUM(revdata, /datum/getrev) - -GLOBAL_VAR(host) -GLOBAL_VAR(station_name) -GLOBAL_VAR_INIT(game_version, "/tg/Station 13") -GLOBAL_VAR_INIT(changelog_hash, "") -GLOBAL_VAR_INIT(hub_visibility, FALSE) - -GLOBAL_VAR_INIT(ooc_allowed, TRUE) // used with admin verbs to disable ooc - not a config option apparently -GLOBAL_VAR_INIT(looc_allowed, TRUE) // Wasp Edit -GLOBAL_VAR_INIT(dooc_allowed, TRUE) -GLOBAL_VAR_INIT(enter_allowed, TRUE) -GLOBAL_VAR_INIT(shuttle_frozen, FALSE) -GLOBAL_VAR_INIT(shuttle_left, FALSE) -GLOBAL_VAR_INIT(tinted_weldhelh, TRUE) - - -// Debug is used exactly once (in living.dm) but is commented out in a lot of places. It is not set anywhere and only checked. -// Debug2 is used in conjunction with a lot of admin verbs and therefore is actually legit. -GLOBAL_VAR_INIT(Debug, FALSE) // global debug switch -GLOBAL_VAR_INIT(Debug2, FALSE) - -//This was a define, but I changed it to a variable so it can be changed in-game.(kept the all-caps definition because... code...) -Errorage -//Protecting these because the proper way to edit them is with the config/secrets -GLOBAL_VAR_INIT(MAX_EX_DEVESTATION_RANGE, 3) -GLOBAL_PROTECT(MAX_EX_DEVESTATION_RANGE) -GLOBAL_VAR_INIT(MAX_EX_HEAVY_RANGE, 7) -GLOBAL_PROTECT(MAX_EX_HEAVY_RANGE) -GLOBAL_VAR_INIT(MAX_EX_LIGHT_RANGE, 14) -GLOBAL_PROTECT(MAX_EX_LIGHT_RANGE) -GLOBAL_VAR_INIT(MAX_EX_FLASH_RANGE, 14) -GLOBAL_PROTECT(MAX_EX_FLASH_RANGE) -GLOBAL_VAR_INIT(MAX_EX_FLAME_RANGE, 14) -GLOBAL_PROTECT(MAX_EX_FLAME_RANGE) -GLOBAL_VAR_INIT(DYN_EX_SCALE, 0.5) +GLOBAL_REAL(config, /datum/controller/configuration) + +GLOBAL_DATUM(revdata, /datum/getrev) + +GLOBAL_VAR(host) +GLOBAL_VAR(station_name) +GLOBAL_VAR_INIT(game_version, "/tg/Station 13") +GLOBAL_VAR_INIT(changelog_hash, "") +GLOBAL_VAR_INIT(hub_visibility, FALSE) + +GLOBAL_VAR_INIT(ooc_allowed, TRUE) // used with admin verbs to disable ooc - not a config option apparently +GLOBAL_VAR_INIT(looc_allowed, TRUE) // Wasp Edit +GLOBAL_VAR_INIT(dooc_allowed, TRUE) +GLOBAL_VAR_INIT(enter_allowed, TRUE) +GLOBAL_VAR_INIT(shuttle_frozen, FALSE) +GLOBAL_VAR_INIT(shuttle_left, FALSE) +GLOBAL_VAR_INIT(tinted_weldhelh, TRUE) + + +// Debug is used exactly once (in living.dm) but is commented out in a lot of places. It is not set anywhere and only checked. +// Debug2 is used in conjunction with a lot of admin verbs and therefore is actually legit. +GLOBAL_VAR_INIT(Debug, FALSE) // global debug switch +GLOBAL_VAR_INIT(Debug2, FALSE) + +//This was a define, but I changed it to a variable so it can be changed in-game.(kept the all-caps definition because... code...) -Errorage +//Protecting these because the proper way to edit them is with the config/secrets +GLOBAL_VAR_INIT(MAX_EX_DEVESTATION_RANGE, 3) +GLOBAL_PROTECT(MAX_EX_DEVESTATION_RANGE) +GLOBAL_VAR_INIT(MAX_EX_HEAVY_RANGE, 7) +GLOBAL_PROTECT(MAX_EX_HEAVY_RANGE) +GLOBAL_VAR_INIT(MAX_EX_LIGHT_RANGE, 14) +GLOBAL_PROTECT(MAX_EX_LIGHT_RANGE) +GLOBAL_VAR_INIT(MAX_EX_FLASH_RANGE, 14) +GLOBAL_PROTECT(MAX_EX_FLASH_RANGE) +GLOBAL_VAR_INIT(MAX_EX_FLAME_RANGE, 14) +GLOBAL_PROTECT(MAX_EX_FLAME_RANGE) +GLOBAL_VAR_INIT(DYN_EX_SCALE, 0.5) diff --git a/code/_globalvars/game_modes.dm b/code/_globalvars/game_modes.dm index ab5a81901aa8..159bcd0986ce 100644 --- a/code/_globalvars/game_modes.dm +++ b/code/_globalvars/game_modes.dm @@ -1,11 +1,11 @@ -GLOBAL_VAR_INIT(master_mode, "traitor") //"extended" -GLOBAL_VAR_INIT(secret_force_mode, "secret") // if this is anything but "secret", the secret rotation will forceably choose this mode -GLOBAL_VAR(common_report) //Contains common part of roundend report -GLOBAL_VAR(survivor_report) //Contains shared survivor report for roundend report (part of personal report) - - -GLOBAL_VAR_INIT(wavesecret, 0) // meteor mode, delays wave progression, terrible name -GLOBAL_DATUM(start_state, /datum/station_state) // Used in round-end report - -//TODO clear this one up too -GLOBAL_DATUM(cult_narsie, /obj/singularity/narsie/large/cult) +GLOBAL_VAR_INIT(master_mode, "traitor") //"extended" +GLOBAL_VAR_INIT(secret_force_mode, "secret") // if this is anything but "secret", the secret rotation will forceably choose this mode +GLOBAL_VAR(common_report) //Contains common part of roundend report +GLOBAL_VAR(survivor_report) //Contains shared survivor report for roundend report (part of personal report) + + +GLOBAL_VAR_INIT(wavesecret, 0) // meteor mode, delays wave progression, terrible name +GLOBAL_DATUM(start_state, /datum/station_state) // Used in round-end report + +//TODO clear this one up too +GLOBAL_DATUM(cult_narsie, /obj/singularity/narsie/large/cult) diff --git a/code/_globalvars/genetics.dm b/code/_globalvars/genetics.dm index bdf53f3d71c7..c5c424977cd6 100644 --- a/code/_globalvars/genetics.dm +++ b/code/_globalvars/genetics.dm @@ -1,9 +1,9 @@ -//faster than having to constantly loop for them -GLOBAL_LIST_EMPTY_TYPED(all_mutations, /datum/mutation/human) //type = initialized mutation -GLOBAL_LIST_EMPTY(full_sequences) //type = correct sequence -GLOBAL_LIST_EMPTY(bad_mutations) //bad initialized mutations -GLOBAL_LIST_EMPTY(good_mutations) //good initialized mutations -GLOBAL_LIST_EMPTY(not_good_mutations) //neutral initialized mutations -GLOBAL_LIST_EMPTY(alias_mutations) //alias = type - -GLOBAL_LIST_EMPTY(mutation_recipes) +//faster than having to constantly loop for them +GLOBAL_LIST_EMPTY_TYPED(all_mutations, /datum/mutation/human) //type = initialized mutation +GLOBAL_LIST_EMPTY(full_sequences) //type = correct sequence +GLOBAL_LIST_EMPTY(bad_mutations) //bad initialized mutations +GLOBAL_LIST_EMPTY(good_mutations) //good initialized mutations +GLOBAL_LIST_EMPTY(not_good_mutations) //neutral initialized mutations +GLOBAL_LIST_EMPTY(alias_mutations) //alias = type + +GLOBAL_LIST_EMPTY(mutation_recipes) diff --git a/code/_globalvars/lists/flavor_misc.dm b/code/_globalvars/lists/flavor_misc.dm index 325bfcb25087..36cc09c45eb0 100644 --- a/code/_globalvars/lists/flavor_misc.dm +++ b/code/_globalvars/lists/flavor_misc.dm @@ -1,271 +1,268 @@ -//Preferences stuff - //Hairstyles -GLOBAL_LIST_EMPTY(hairstyles_list) //stores /datum/sprite_accessory/hair indexed by name -GLOBAL_LIST_EMPTY(hairstyles_male_list) //stores only hair names -GLOBAL_LIST_EMPTY(hairstyles_female_list) //stores only hair names -GLOBAL_LIST_EMPTY(facial_hairstyles_list) //stores /datum/sprite_accessory/facial_hair indexed by name -GLOBAL_LIST_EMPTY(facial_hairstyles_male_list) //stores only hair names -GLOBAL_LIST_EMPTY(facial_hairstyles_female_list) //stores only hair names - //Underwear -GLOBAL_LIST_EMPTY(underwear_list) //stores /datum/sprite_accessory/underwear indexed by name -GLOBAL_LIST_EMPTY(underwear_m) //stores only underwear name -GLOBAL_LIST_EMPTY(underwear_f) //stores only underwear name - //Undershirts -GLOBAL_LIST_EMPTY(undershirt_list) //stores /datum/sprite_accessory/undershirt indexed by name -GLOBAL_LIST_EMPTY(undershirt_m) //stores only undershirt name -GLOBAL_LIST_EMPTY(undershirt_f) //stores only undershirt name - //Socks -GLOBAL_LIST_EMPTY(socks_list) //stores /datum/sprite_accessory/socks indexed by name - //lizard Bits (all datum lists indexed by name) -GLOBAL_LIST_EMPTY(body_markings_list) -GLOBAL_LIST_EMPTY(tails_list_lizard) -GLOBAL_LIST_EMPTY(animated_tails_list_lizard) -GLOBAL_LIST_EMPTY(snouts_list) -GLOBAL_LIST_EMPTY(horns_list) -GLOBAL_LIST_EMPTY(frills_list) -GLOBAL_LIST_EMPTY(spines_list) -GLOBAL_LIST_EMPTY(legs_list) -GLOBAL_LIST_EMPTY(animated_spines_list) - - //Mutant Human bits -GLOBAL_LIST_EMPTY(tails_list_human) -GLOBAL_LIST_EMPTY(animated_tails_list_human) -GLOBAL_LIST_EMPTY(ears_list) -GLOBAL_LIST_EMPTY(wings_list) -GLOBAL_LIST_EMPTY(wings_open_list) -GLOBAL_LIST_EMPTY(r_wings_list) -GLOBAL_LIST_EMPTY(moth_wings_list) -GLOBAL_LIST_EMPTY(moth_fluff_list) -GLOBAL_LIST_EMPTY(moth_markings_list) -GLOBAL_LIST_EMPTY(caps_list) -GLOBAL_LIST_EMPTY(squid_face_list) -GLOBAL_LIST_EMPTY(ipc_screens_list) -GLOBAL_LIST_EMPTY(ipc_antennas_list) -GLOBAL_LIST_EMPTY(ipc_chassis_list) -GLOBAL_LIST_EMPTY(spider_legs_list) -GLOBAL_LIST_EMPTY(spider_spinneret_list) -GLOBAL_LIST_EMPTY(spider_mandibles_list) - -GLOBAL_LIST_INIT(color_list_ethereal, list( - "Red" = "ff4d4d", - "Faint Red" = "ffb3b3", - "Dark Red" = "9c3030", - "Orange" = "ffa64d", - "Burnt Orange" = "cc4400", - "Bright Yellow" = "ffff99", - "Dull Yellow" = "fbdf56", - "Faint Green" = "ddff99", - "Green" = "97ee63", - "Seafoam Green" = "00fa9a", - "Dark Green" = "37835b", - "Cyan Blue" = "00ffff", - "Faint Blue" = "b3d9ff", - "Blue" = "3399ff", - "Dark Blue" = "6666ff", - "Purple" = "ee82ee", - "Dark Fuschia" = "cc0066", - "Pink" = "ff99cc", - "White" = "f2f2f2",)) - -GLOBAL_LIST_INIT(ghost_forms_with_directions_list, list( - "ghost", - "ghostian", - "ghostian2", - "ghostking", - "ghost_red", - "ghost_black", - "ghost_blue", - "ghost_yellow", - "ghost_green", - "ghost_pink", - "ghost_cyan", - "ghost_dblue", - "ghost_dred", - "ghost_dgreen", - "ghost_dcyan", - "ghost_grey", - "ghost_dyellow", - "ghost_dpink", - "skeleghost", - "ghost_purpleswirl", - "ghost_rainbow", - "ghost_fire", - "ghost_funkypurp", - "ghost_pinksherbert", - "ghost_blazeit", - "ghost_mellow", - "ghost_camo", - "catghost")) //stores the ghost forms that support directional sprites - -GLOBAL_LIST_INIT(ghost_forms_with_accessories_list, list("ghost")) //stores the ghost forms that support hair and other such things - -GLOBAL_LIST_INIT(ai_core_display_screens, sortList(list( - ":thinking:", - "Alien", - "Angel", - "Banned", - "Bliss", - "Blue", - "Clown", - "Database", - "Dorf", - "Firewall", - "Fuzzy", - "Gentoo", - "Glitchman", - "Gondola", - "Goon", - "Hades", - "HAL 9000", - "Heartline", - "Helios", - "House", - "Inverted", - "Matrix", - "Monochrome", - "Murica", - "Nanotrasen", - "Not Malf", - "President", - "Random", - "Rainbow", - "Red", - "Red October", - "Static", - "Syndicat Meow", - "Text", - "Too Deep", - "Triumvirate", - "Triumvirate-M", - "Weird"))) - -/proc/resolve_ai_icon(input) - if(!input || !(input in GLOB.ai_core_display_screens)) - return "ai" - else - if(input == "Random") - input = pick(GLOB.ai_core_display_screens - "Random") - return "ai-[lowertext(input)]" - -GLOBAL_LIST_INIT(security_depts_prefs, sortList(list(SEC_DEPT_RANDOM, SEC_DEPT_NONE, SEC_DEPT_ENGINEERING, SEC_DEPT_MEDICAL, SEC_DEPT_SCIENCE, SEC_DEPT_SUPPLY))) - - //Backpacks -#define GBACKPACK "Grey Backpack" -#define GSATCHEL "Grey Satchel" -#define GDUFFELBAG "Grey Duffel Bag" -#define GCOURIERBAG "Grey Messenger Bag" -#define LSATCHEL "Leather Satchel" -#define DBACKPACK "Department Backpack" -#define DSATCHEL "Department Satchel" -#define DDUFFELBAG "Department Duffel Bag" -#define DCOURIERBAG "Department Messenger Bag" -GLOBAL_LIST_INIT(backpacklist, list(DBACKPACK, DSATCHEL, DCOURIERBAG, DDUFFELBAG, GBACKPACK, GSATCHEL, GCOURIERBAG, GDUFFELBAG, LSATCHEL)) - - //Uniform -#define PREF_SUIT "Standard Jumpsuit" -#define PREF_SKIRT "Standard Jumpskirt" -#define PREF_ALTSUIT "Alternate Jumpsuit" -#define PREF_GREYSUIT "Grey Jumpsuit" -#define PREF_LOADOUT "Loadout uniform" -GLOBAL_LIST_INIT(jumpsuitlist, list(PREF_SUIT, PREF_SKIRT, PREF_ALTSUIT, PREF_GREYSUIT, PREF_LOADOUT)) -GLOBAL_LIST_INIT(jumpsuitlistrandom, list(PREF_SUIT, PREF_SKIRT, PREF_ALTSUIT, PREF_GREYSUIT)) - - //Exowear -#define PREF_NOEXOWEAR "No Exowear/Loadout Exowear" -#define PREF_EXOWEAR "Standard Exowear" -#define PREF_ALTEXOWEAR "Alternate Exowear" -#define PREF_COATEXOWEAR "Departmental Winter Coat" -GLOBAL_LIST_INIT(exowearlist, list(PREF_NOEXOWEAR, PREF_EXOWEAR, PREF_ALTEXOWEAR, PREF_COATEXOWEAR)) - -//Uplink spawn loc -#define UPLINK_PDA "PDA" -#define UPLINK_RADIO "Radio" -#define UPLINK_PEN "Pen" //like a real spy! -GLOBAL_LIST_INIT(uplink_spawn_loc_list, list(UPLINK_PDA, UPLINK_RADIO, UPLINK_PEN)) - - //Female Uniforms -GLOBAL_LIST_EMPTY(female_clothing_icons) - - //radical shit -GLOBAL_LIST_INIT(hit_appends, list("-OOF", "-ACK", "-UGH", "-HRNK", "-HURGH", "-GLORF")) - -GLOBAL_LIST_INIT(scarySounds, list('sound/weapons/thudswoosh.ogg','sound/weapons/taser.ogg','sound/weapons/armbomb.ogg','sound/voice/hiss1.ogg','sound/voice/hiss2.ogg','sound/voice/hiss3.ogg','sound/voice/hiss4.ogg','sound/voice/hiss5.ogg','sound/voice/hiss6.ogg','sound/effects/glassbr1.ogg','sound/effects/glassbr2.ogg','sound/effects/glassbr3.ogg','sound/items/welder.ogg','sound/items/welder2.ogg','sound/machines/airlock.ogg','sound/effects/clownstep1.ogg','sound/effects/clownstep2.ogg')) - - -// Reference list for disposal sort junctions. Set the sortType variable on disposal sort junctions to -// the index of the sort department that you want. For example, sortType set to 2 will reroute all packages -// tagged for the Cargo Bay. - -/* List of sortType codes for mapping reference -0 Waste -1 Disposals - All unwrapped items and untagged parcels get picked up by a junction with this sortType. Usually leads to the recycler. -2 Cargo Bay -3 QM Office -4 Engineering -5 CE Office -6 Atmospherics -7 Security -8 HoS Office -9 Medbay -10 CMO Office -11 Chemistry -12 Research -13 RD Office -14 Robotics -15 Head of Personnel's Office -16 Library -17 Chapel -18 Theatre -19 Bar -20 Kitchen -21 Hydroponics -22 Janitor -23 Genetics -24 Experimentor Lab -25 Toxins -26 Dormitories -27 Virology -28 Xenobiology -29 Law Office -30 Detective's Office -*/ - -//The whole system for the sorttype var is determined based on the order of this list, -//disposals must always be 1, since anything that's untagged will automatically go to disposals, or sorttype = 1 --Superxpdude - -//If you don't want to fuck up disposals, add to this list, and don't change the order. -//If you insist on changing the order, you'll have to change every sort junction to reflect the new order. --Pete - -GLOBAL_LIST_INIT(TAGGERLOCATIONS, list("Disposals", - "Cargo Bay", "QM Office", "Engineering", "CE Office", - "Atmospherics", "Security", "HoS Office", "Medbay", - "CMO Office", "Chemistry", "Research", "RD Office", - "Robotics", "Head of Personnel's Office", "Library", "Chapel", "Theatre", - "Bar", "Kitchen", "Hydroponics", "Janitor Closet","Genetics", - "Experimentor Lab", "Toxins", "Dormitories", "Virology", - "Xenobiology", "Law Office","Detective's Office")) - -GLOBAL_LIST_INIT(station_prefixes, world.file2list("strings/station_prefixes.txt")) - -GLOBAL_LIST_INIT(station_names, world.file2list("strings/station_names.txt")) - -GLOBAL_LIST_INIT(station_suffixes, world.file2list("strings/station_suffixes.txt")) - -GLOBAL_LIST_INIT(greek_letters, world.file2list("strings/greek_letters.txt")) - -GLOBAL_LIST_INIT(phonetic_alphabet, world.file2list("strings/phonetic_alphabet.txt")) - -GLOBAL_LIST_INIT(numbers_as_words, world.file2list("strings/numbers_as_words.txt")) - -GLOBAL_LIST_INIT(wisdoms, world.file2list("strings/wisdoms.txt")) - -/proc/generate_number_strings() - var/list/L[198] - for(var/i in 1 to 99) - L += "[i]" - L += "\Roman[i]" - return L - -GLOBAL_LIST_INIT(station_numerals, greek_letters + phonetic_alphabet + numbers_as_words + generate_number_strings()) - -GLOBAL_LIST_INIT(admiral_messages, list("Do you know how expensive these stations are?","Stop wasting my time.","I was sleeping, thanks a lot.","Stand and fight you cowards!","You knew the risks coming in.","Stop being paranoid.","Whatever's broken just build a new one.","No.", "null","Error: No comment given.", "It's a good day to die!")) +//Preferences stuff + //Hairstyles +GLOBAL_LIST_EMPTY(hairstyles_list) //stores /datum/sprite_accessory/hair indexed by name +GLOBAL_LIST_EMPTY(hairstyles_male_list) //stores only hair names +GLOBAL_LIST_EMPTY(hairstyles_female_list) //stores only hair names +GLOBAL_LIST_EMPTY(facial_hairstyles_list) //stores /datum/sprite_accessory/facial_hair indexed by name +GLOBAL_LIST_EMPTY(facial_hairstyles_male_list) //stores only hair names +GLOBAL_LIST_EMPTY(facial_hairstyles_female_list) //stores only hair names + //Underwear +GLOBAL_LIST_EMPTY(underwear_list) //stores /datum/sprite_accessory/underwear indexed by name +GLOBAL_LIST_EMPTY(underwear_m) //stores only underwear name +GLOBAL_LIST_EMPTY(underwear_f) //stores only underwear name + //Undershirts +GLOBAL_LIST_EMPTY(undershirt_list) //stores /datum/sprite_accessory/undershirt indexed by name +GLOBAL_LIST_EMPTY(undershirt_m) //stores only undershirt name +GLOBAL_LIST_EMPTY(undershirt_f) //stores only undershirt name + //Socks +GLOBAL_LIST_EMPTY(socks_list) //stores /datum/sprite_accessory/socks indexed by name + //lizard Bits (all datum lists indexed by name) +GLOBAL_LIST_EMPTY(body_markings_list) +GLOBAL_LIST_EMPTY(tails_list_lizard) +GLOBAL_LIST_EMPTY(animated_tails_list_lizard) +GLOBAL_LIST_EMPTY(snouts_list) +GLOBAL_LIST_EMPTY(horns_list) +GLOBAL_LIST_EMPTY(frills_list) +GLOBAL_LIST_EMPTY(spines_list) +GLOBAL_LIST_EMPTY(legs_list) +GLOBAL_LIST_EMPTY(animated_spines_list) + + //Mutant Human bits +GLOBAL_LIST_EMPTY(tails_list_human) +GLOBAL_LIST_EMPTY(animated_tails_list_human) +GLOBAL_LIST_EMPTY(ears_list) +GLOBAL_LIST_EMPTY(wings_list) +GLOBAL_LIST_EMPTY(wings_open_list) +GLOBAL_LIST_EMPTY(r_wings_list) +GLOBAL_LIST_EMPTY(moth_wings_list) +GLOBAL_LIST_EMPTY(moth_fluff_list) +GLOBAL_LIST_EMPTY(moth_markings_list) +GLOBAL_LIST_EMPTY(caps_list) +GLOBAL_LIST_EMPTY(squid_face_list) +GLOBAL_LIST_EMPTY(ipc_screens_list) +GLOBAL_LIST_EMPTY(ipc_antennas_list) +GLOBAL_LIST_EMPTY(ipc_chassis_list) +GLOBAL_LIST_EMPTY(spider_legs_list) +GLOBAL_LIST_EMPTY(spider_spinneret_list) +GLOBAL_LIST_EMPTY(spider_mandibles_list) + +GLOBAL_LIST_INIT(color_list_ethereal, list( + "Red" = "ff4d4d", + "Faint Red" = "ffb3b3", + "Dark Red" = "9c3030", + "Orange" = "ffa64d", + "Burnt Orange" = "cc4400", + "Bright Yellow" = "ffff99", + "Dull Yellow" = "fbdf56", + "Faint Green" = "ddff99", + "Green" = "97ee63", + "Seafoam Green" = "00fa9a", + "Dark Green" = "37835b", + "Cyan Blue" = "00ffff", + "Faint Blue" = "b3d9ff", + "Blue" = "3399ff", + "Dark Blue" = "6666ff", + "Purple" = "ee82ee", + "Dark Fuschia" = "cc0066", + "Pink" = "ff99cc", + "White" = "f2f2f2",)) + +GLOBAL_LIST_INIT(ghost_forms_with_directions_list, list( + "ghost", + "ghostian", + "ghostian2", + "ghostking", + "ghost_red", + "ghost_black", + "ghost_blue", + "ghost_yellow", + "ghost_green", + "ghost_pink", + "ghost_cyan", + "ghost_dblue", + "ghost_dred", + "ghost_dgreen", + "ghost_dcyan", + "ghost_grey", + "ghost_dyellow", + "ghost_dpink", + "skeleghost", + "ghost_purpleswirl", + "ghost_rainbow", + "ghost_fire", + "ghost_funkypurp", + "ghost_pinksherbert", + "ghost_blazeit", + "ghost_mellow", + "ghost_camo", + "catghost")) //stores the ghost forms that support directional sprites + +GLOBAL_LIST_INIT(ghost_forms_with_accessories_list, list("ghost")) //stores the ghost forms that support hair and other such things + +GLOBAL_LIST_INIT(ai_core_display_screens, sortList(list( + ":thinking:", + "Alien", + "Angel", + "Banned", + "Bliss", + "Blue", + "Clown", + "Database", + "Dorf", + "Firewall", + "Fuzzy", + "Gentoo", + "Glitchman", + "Gondola", + "Goon", + "Hades", + "HAL 9000", + "Heartline", + "Helios", + "House", + "Inverted", + "Matrix", + "Monochrome", + "Murica", + "Nanotrasen", + "Not Malf", + "President", + "Random", + "Rainbow", + "Red", + "Red October", + "Static", + "Syndicat Meow", + "Text", + "Too Deep", + "Triumvirate", + "Triumvirate-M", + "Weird"))) + +/proc/resolve_ai_icon(input) + if(!input || !(input in GLOB.ai_core_display_screens)) + return "ai" + else + if(input == "Random") + input = pick(GLOB.ai_core_display_screens - "Random") + return "ai-[lowertext(input)]" + +GLOBAL_LIST_INIT(security_depts_prefs, sortList(list(SEC_DEPT_RANDOM, SEC_DEPT_NONE, SEC_DEPT_ENGINEERING, SEC_DEPT_MEDICAL, SEC_DEPT_SCIENCE, SEC_DEPT_SUPPLY))) + + //Backpacks +#define GBACKPACK "Grey Backpack" +#define GSATCHEL "Grey Satchel" +#define GDUFFELBAG "Grey Duffel Bag" +#define GCOURIERBAG "Grey Messenger Bag" +#define LSATCHEL "Leather Satchel" +#define DBACKPACK "Department Backpack" +#define DSATCHEL "Department Satchel" +#define DDUFFELBAG "Department Duffel Bag" +#define DCOURIERBAG "Department Messenger Bag" +GLOBAL_LIST_INIT(backpacklist, list(DBACKPACK, DSATCHEL, DCOURIERBAG, DDUFFELBAG, GBACKPACK, GSATCHEL, GCOURIERBAG, GDUFFELBAG, LSATCHEL)) + + //Uniform +#define PREF_SUIT "Standard Jumpsuit" +#define PREF_SKIRT "Standard Jumpskirt" +#define PREF_ALTSUIT "Alternate Jumpsuit" +#define PREF_GREYSUIT "Grey Jumpsuit" +#define PREF_LOADOUT "Loadout uniform" +GLOBAL_LIST_INIT(jumpsuitlist, list(PREF_SUIT, PREF_SKIRT, PREF_ALTSUIT, PREF_GREYSUIT, PREF_LOADOUT)) +GLOBAL_LIST_INIT(jumpsuitlistrandom, list(PREF_SUIT, PREF_SKIRT, PREF_ALTSUIT, PREF_GREYSUIT)) + + //Exowear +#define PREF_NOEXOWEAR "No Exowear/Loadout Exowear" +#define PREF_EXOWEAR "Standard Exowear" +#define PREF_ALTEXOWEAR "Alternate Exowear" +#define PREF_COATEXOWEAR "Departmental Winter Coat" +GLOBAL_LIST_INIT(exowearlist, list(PREF_NOEXOWEAR, PREF_EXOWEAR, PREF_ALTEXOWEAR, PREF_COATEXOWEAR)) + +//Uplink spawn loc +#define UPLINK_PDA "PDA" +#define UPLINK_RADIO "Radio" +#define UPLINK_PEN "Pen" //like a real spy! +GLOBAL_LIST_INIT(uplink_spawn_loc_list, list(UPLINK_PDA, UPLINK_RADIO, UPLINK_PEN)) + + //Female Uniforms +GLOBAL_LIST_EMPTY(female_clothing_icons) + +GLOBAL_LIST_INIT(scarySounds, list('sound/weapons/thudswoosh.ogg','sound/weapons/taser.ogg','sound/weapons/armbomb.ogg','sound/voice/hiss1.ogg','sound/voice/hiss2.ogg','sound/voice/hiss3.ogg','sound/voice/hiss4.ogg','sound/voice/hiss5.ogg','sound/voice/hiss6.ogg','sound/effects/glassbr1.ogg','sound/effects/glassbr2.ogg','sound/effects/glassbr3.ogg','sound/items/welder.ogg','sound/items/welder2.ogg','sound/machines/airlock.ogg','sound/effects/clownstep1.ogg','sound/effects/clownstep2.ogg')) + + +// Reference list for disposal sort junctions. Set the sortType variable on disposal sort junctions to +// the index of the sort department that you want. For example, sortType set to 2 will reroute all packages +// tagged for the Cargo Bay. + +/* List of sortType codes for mapping reference +0 Waste +1 Disposals - All unwrapped items and untagged parcels get picked up by a junction with this sortType. Usually leads to the recycler. +2 Cargo Bay +3 QM Office +4 Engineering +5 CE Office +6 Atmospherics +7 Security +8 HoS Office +9 Medbay +10 CMO Office +11 Chemistry +12 Research +13 RD Office +14 Robotics +15 Head of Personnel's Office +16 Library +17 Chapel +18 Theatre +19 Bar +20 Kitchen +21 Hydroponics +22 Janitor +23 Genetics +24 Experimentor Lab +25 Toxins +26 Dormitories +27 Virology +28 Xenobiology +29 Law Office +30 Detective's Office +*/ + +//The whole system for the sorttype var is determined based on the order of this list, +//disposals must always be 1, since anything that's untagged will automatically go to disposals, or sorttype = 1 --Superxpdude + +//If you don't want to fuck up disposals, add to this list, and don't change the order. +//If you insist on changing the order, you'll have to change every sort junction to reflect the new order. --Pete + +GLOBAL_LIST_INIT(TAGGERLOCATIONS, list("Disposals", + "Cargo Bay", "QM Office", "Engineering", "CE Office", + "Atmospherics", "Security", "HoS Office", "Medbay", + "CMO Office", "Chemistry", "Research", "RD Office", + "Robotics", "Head of Personnel's Office", "Library", "Chapel", "Theatre", + "Bar", "Kitchen", "Hydroponics", "Janitor Closet","Genetics", + "Experimentor Lab", "Toxins", "Dormitories", "Virology", + "Xenobiology", "Law Office","Detective's Office")) + +GLOBAL_LIST_INIT(station_prefixes, world.file2list("strings/station_prefixes.txt")) + +GLOBAL_LIST_INIT(station_names, world.file2list("strings/station_names.txt")) + +GLOBAL_LIST_INIT(station_suffixes, world.file2list("strings/station_suffixes.txt")) + +GLOBAL_LIST_INIT(greek_letters, world.file2list("strings/greek_letters.txt")) + +GLOBAL_LIST_INIT(phonetic_alphabet, world.file2list("strings/phonetic_alphabet.txt")) + +GLOBAL_LIST_INIT(numbers_as_words, world.file2list("strings/numbers_as_words.txt")) + +GLOBAL_LIST_INIT(wisdoms, world.file2list("strings/wisdoms.txt")) + +/proc/generate_number_strings() + var/list/L[198] + for(var/i in 1 to 99) + L += "[i]" + L += "\Roman[i]" + return L + +GLOBAL_LIST_INIT(station_numerals, greek_letters + phonetic_alphabet + numbers_as_words + generate_number_strings()) + +GLOBAL_LIST_INIT(admiral_messages, list("Do you know how expensive these stations are?","Stop wasting my time.","I was sleeping, thanks a lot.","Stand and fight you cowards!","You knew the risks coming in.","Stop being paranoid.","Whatever's broken just build a new one.","No.", "null","Error: No comment given.", "It's a good day to die!")) diff --git a/code/_globalvars/lists/mapping.dm b/code/_globalvars/lists/mapping.dm index fdcfc6506f45..87881693c17a 100644 --- a/code/_globalvars/lists/mapping.dm +++ b/code/_globalvars/lists/mapping.dm @@ -1,47 +1,47 @@ -GLOBAL_LIST_INIT(cardinals, list(NORTH, SOUTH, EAST, WEST)) -GLOBAL_LIST_INIT(cardinals_multiz, list(NORTH, SOUTH, EAST, WEST, UP, DOWN)) -GLOBAL_LIST_INIT(diagonals, list(NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST)) -GLOBAL_LIST_INIT(corners_multiz, list(UP|NORTHEAST, UP|NORTHWEST, UP|SOUTHEAST, UP|SOUTHWEST, DOWN|NORTHEAST, DOWN|NORTHWEST, DOWN|SOUTHEAST, DOWN|SOUTHWEST)) -GLOBAL_LIST_INIT(diagonals_multiz, list( - NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST, - UP|NORTH, UP|SOUTH, UP|EAST, UP|WEST, UP|NORTHEAST, UP|NORTHWEST, UP|SOUTHEAST, UP|SOUTHWEST, - DOWN|NORTH, DOWN|SOUTH, DOWN|EAST, DOWN|WEST, DOWN|NORTHEAST, DOWN|NORTHWEST, DOWN|SOUTHEAST, DOWN|SOUTHWEST)) -GLOBAL_LIST_INIT(alldirs_multiz, list( - NORTH, SOUTH, EAST, WEST, NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST, - UP, UP|NORTH, UP|SOUTH, UP|EAST, UP|WEST, UP|NORTHEAST, UP|NORTHWEST, UP|SOUTHEAST, UP|SOUTHWEST, - DOWN, DOWN|NORTH, DOWN|SOUTH, DOWN|EAST, DOWN|WEST, DOWN|NORTHEAST, DOWN|NORTHWEST, DOWN|SOUTHEAST, DOWN|SOUTHWEST)) -GLOBAL_LIST_INIT(alldirs, list(NORTH, SOUTH, EAST, WEST, NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST)) - -GLOBAL_LIST_EMPTY(landmarks_list) //list of all landmarks created -GLOBAL_LIST_EMPTY(start_landmarks_list) //list of all spawn points created -GLOBAL_LIST_EMPTY(department_security_spawns) //list of all department security spawns -GLOBAL_LIST_EMPTY(generic_event_spawns) //handles clockwork portal+eminence teleport destinations -GLOBAL_LIST_EMPTY(jobspawn_overrides) //These will take precedence over normal spawnpoints if created. - -GLOBAL_LIST_EMPTY(wizardstart) -GLOBAL_LIST_EMPTY(nukeop_start) -GLOBAL_LIST_EMPTY(nukeop_leader_start) -GLOBAL_LIST_EMPTY(newplayer_start) -GLOBAL_LIST_EMPTY(prisonwarp) //admin prisoners go to these -GLOBAL_LIST_EMPTY(holdingfacility) //captured people go here (ninja energy net) -GLOBAL_LIST_EMPTY(xeno_spawn)//aliens, morphs and nightmares spawn at these -GLOBAL_LIST_EMPTY(tdome1) -GLOBAL_LIST_EMPTY(tdome2) -GLOBAL_LIST_EMPTY(tdomeobserve) -GLOBAL_LIST_EMPTY(tdomeadmin) -GLOBAL_LIST_EMPTY(prisonwarped) //list of players already warped -GLOBAL_LIST_EMPTY(blobstart) //stationloving objects, blobs, santa, respawning devils -GLOBAL_LIST_EMPTY(secequipment) //sec equipment lockers that scale with the number of sec players -GLOBAL_LIST_EMPTY(deathsquadspawn) -GLOBAL_LIST_EMPTY(emergencyresponseteamspawn) -GLOBAL_LIST_EMPTY(ruin_landmarks) - -//away missions -GLOBAL_LIST_EMPTY(vr_spawnpoints) - - //used by jump-to-area etc. Updated by area/updateName() -GLOBAL_LIST_EMPTY(sortedAreas) -/// An association from typepath to area instance. Only includes areas with `unique` set. -GLOBAL_LIST_EMPTY_TYPED(areas_by_type, /area) - -GLOBAL_LIST_EMPTY(all_abstract_markers) +GLOBAL_LIST_INIT(cardinals, list(NORTH, SOUTH, EAST, WEST)) +GLOBAL_LIST_INIT(cardinals_multiz, list(NORTH, SOUTH, EAST, WEST, UP, DOWN)) +GLOBAL_LIST_INIT(diagonals, list(NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST)) +GLOBAL_LIST_INIT(corners_multiz, list(UP|NORTHEAST, UP|NORTHWEST, UP|SOUTHEAST, UP|SOUTHWEST, DOWN|NORTHEAST, DOWN|NORTHWEST, DOWN|SOUTHEAST, DOWN|SOUTHWEST)) +GLOBAL_LIST_INIT(diagonals_multiz, list( + NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST, + UP|NORTH, UP|SOUTH, UP|EAST, UP|WEST, UP|NORTHEAST, UP|NORTHWEST, UP|SOUTHEAST, UP|SOUTHWEST, + DOWN|NORTH, DOWN|SOUTH, DOWN|EAST, DOWN|WEST, DOWN|NORTHEAST, DOWN|NORTHWEST, DOWN|SOUTHEAST, DOWN|SOUTHWEST)) +GLOBAL_LIST_INIT(alldirs_multiz, list( + NORTH, SOUTH, EAST, WEST, NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST, + UP, UP|NORTH, UP|SOUTH, UP|EAST, UP|WEST, UP|NORTHEAST, UP|NORTHWEST, UP|SOUTHEAST, UP|SOUTHWEST, + DOWN, DOWN|NORTH, DOWN|SOUTH, DOWN|EAST, DOWN|WEST, DOWN|NORTHEAST, DOWN|NORTHWEST, DOWN|SOUTHEAST, DOWN|SOUTHWEST)) +GLOBAL_LIST_INIT(alldirs, list(NORTH, SOUTH, EAST, WEST, NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST)) + +GLOBAL_LIST_EMPTY(landmarks_list) //list of all landmarks created +GLOBAL_LIST_EMPTY(start_landmarks_list) //list of all spawn points created +GLOBAL_LIST_EMPTY(department_security_spawns) //list of all department security spawns +GLOBAL_LIST_EMPTY(generic_event_spawns) //handles clockwork portal+eminence teleport destinations +GLOBAL_LIST_EMPTY(jobspawn_overrides) //These will take precedence over normal spawnpoints if created. + +GLOBAL_LIST_EMPTY(wizardstart) +GLOBAL_LIST_EMPTY(nukeop_start) +GLOBAL_LIST_EMPTY(nukeop_leader_start) +GLOBAL_LIST_EMPTY(newplayer_start) +GLOBAL_LIST_EMPTY(prisonwarp) //admin prisoners go to these +GLOBAL_LIST_EMPTY(holdingfacility) //captured people go here (ninja energy net) +GLOBAL_LIST_EMPTY(xeno_spawn)//aliens, morphs and nightmares spawn at these +GLOBAL_LIST_EMPTY(tdome1) +GLOBAL_LIST_EMPTY(tdome2) +GLOBAL_LIST_EMPTY(tdomeobserve) +GLOBAL_LIST_EMPTY(tdomeadmin) +GLOBAL_LIST_EMPTY(prisonwarped) //list of players already warped +GLOBAL_LIST_EMPTY(blobstart) //stationloving objects, blobs, santa, respawning devils +GLOBAL_LIST_EMPTY(secequipment) //sec equipment lockers that scale with the number of sec players +GLOBAL_LIST_EMPTY(deathsquadspawn) +GLOBAL_LIST_EMPTY(emergencyresponseteamspawn) +GLOBAL_LIST_EMPTY(ruin_landmarks) + +//away missions +GLOBAL_LIST_EMPTY(vr_spawnpoints) + + //used by jump-to-area etc. Updated by area/updateName() +GLOBAL_LIST_EMPTY(sortedAreas) +/// An association from typepath to area instance. Only includes areas with `unique` set. +GLOBAL_LIST_EMPTY_TYPED(areas_by_type, /area) + +GLOBAL_LIST_EMPTY(all_abstract_markers) diff --git a/code/_globalvars/lists/mobs.dm b/code/_globalvars/lists/mobs.dm index f83e953d2e80..c9acdb293d5f 100644 --- a/code/_globalvars/lists/mobs.dm +++ b/code/_globalvars/lists/mobs.dm @@ -1,83 +1,83 @@ -GLOBAL_LIST_EMPTY(clients) //all clients -GLOBAL_LIST_EMPTY(admins) //all clients whom are admins -GLOBAL_PROTECT(admins) -GLOBAL_LIST_EMPTY(deadmins) //all ckeys who have used the de-admin verb. -GLOBAL_LIST_EMPTY(mentors) -GLOBAL_PROTECT(mentors) - -GLOBAL_LIST_EMPTY(directory) //all ckeys with associated client -GLOBAL_LIST_EMPTY(stealthminID) //reference list with IDs that store ckeys, for stealthmins - -//Since it didn't really belong in any other category, I'm putting this here -//This is for procs to replace all the goddamn 'in world's that are chilling around the code - -GLOBAL_LIST_EMPTY(player_list) //all mobs **with clients attached**. -GLOBAL_LIST_EMPTY(mob_list) //all mobs, including clientless -GLOBAL_LIST_EMPTY(mob_directory) //mob_id -> mob -GLOBAL_LIST_EMPTY(alive_mob_list) //all alive mobs, including clientless. Excludes /mob/dead/new_player -GLOBAL_LIST_EMPTY(suicided_mob_list) //contains a list of all mobs that suicided, including their associated ghosts. -GLOBAL_LIST_EMPTY(drones_list) -GLOBAL_LIST_EMPTY(dead_mob_list) //all dead mobs, including clientless. Excludes /mob/dead/new_player -GLOBAL_LIST_EMPTY(joined_player_list) //all clients that have joined the game at round-start or as a latejoin. -GLOBAL_LIST_EMPTY(new_player_list) //all /mob/dead/new_player, in theory all should have clients and those that don't are in the process of spawning and get deleted when done. -GLOBAL_LIST_EMPTY(pre_setup_antags) //minds that have been picked as antag by the gamemode. removed as antag datums are set. -GLOBAL_LIST_EMPTY(silicon_mobs) //all silicon mobs -GLOBAL_LIST_EMPTY(mob_living_list) //all instances of /mob/living and subtypes -GLOBAL_LIST_EMPTY(carbon_list) //all instances of /mob/living/carbon and subtypes, notably does not contain brains or simple animals -GLOBAL_LIST_EMPTY(human_list) //all instances of /mob/living/carbon/human and subtypes -GLOBAL_LIST_EMPTY(ai_list) -GLOBAL_LIST_EMPTY(pai_list) -GLOBAL_LIST_EMPTY(available_ai_shells) -GLOBAL_LIST_INIT(simple_animals, list(list(),list(),list(),list())) // One for each AI_* status define -GLOBAL_LIST_EMPTY(spidermobs) //all sentient spider mobs -GLOBAL_LIST_EMPTY(bots_list) -GLOBAL_LIST_EMPTY(aiEyes) -///underages who have been reported to security for trying to buy things they shouldn't, so they can't spam -GLOBAL_LIST_EMPTY(narcd_underages) - -GLOBAL_LIST_EMPTY(language_datum_instances) -GLOBAL_LIST_EMPTY(all_languages) - -GLOBAL_LIST_EMPTY(sentient_disease_instances) - -GLOBAL_LIST_EMPTY(latejoin_ai_cores) - -GLOBAL_LIST_EMPTY(mob_config_movespeed_type_lookup) - -GLOBAL_LIST_EMPTY(emote_list) - -/proc/update_config_movespeed_type_lookup(update_mobs = TRUE) - var/list/mob_types = list() - var/list/entry_value = CONFIG_GET(keyed_list/multiplicative_movespeed) - for(var/path in entry_value) - var/value = entry_value[path] - if(!value) - continue - for(var/subpath in typesof(path)) - mob_types[subpath] = value - GLOB.mob_config_movespeed_type_lookup = mob_types - if(update_mobs) - update_mob_config_movespeeds() - -/proc/update_mob_config_movespeeds() - for(var/i in GLOB.mob_list) - var/mob/M = i - M.update_config_movespeed() - -/proc/init_emote_list() - . = list() - for(var/path in subtypesof(/datum/emote)) - var/datum/emote/E = new path() - if(E.key) - if(!.[E.key]) - .[E.key] = list(E) - else - .[E.key] += E - else if(E.message) //Assuming all non-base emotes have this - stack_trace("Keyless emote: [E.type]") - - if(E.key_third_person) //This one is optional - if(!.[E.key_third_person]) - .[E.key_third_person] = list(E) - else - .[E.key_third_person] |= E +GLOBAL_LIST_EMPTY(clients) //all clients +GLOBAL_LIST_EMPTY(admins) //all clients whom are admins +GLOBAL_PROTECT(admins) +GLOBAL_LIST_EMPTY(deadmins) //all ckeys who have used the de-admin verb. +GLOBAL_LIST_EMPTY(mentors) +GLOBAL_PROTECT(mentors) + +GLOBAL_LIST_EMPTY(directory) //all ckeys with associated client +GLOBAL_LIST_EMPTY(stealthminID) //reference list with IDs that store ckeys, for stealthmins + +//Since it didn't really belong in any other category, I'm putting this here +//This is for procs to replace all the goddamn 'in world's that are chilling around the code + +GLOBAL_LIST_EMPTY(player_list) //all mobs **with clients attached**. +GLOBAL_LIST_EMPTY(mob_list) //all mobs, including clientless +GLOBAL_LIST_EMPTY(mob_directory) //mob_id -> mob +GLOBAL_LIST_EMPTY(alive_mob_list) //all alive mobs, including clientless. Excludes /mob/dead/new_player +GLOBAL_LIST_EMPTY(suicided_mob_list) //contains a list of all mobs that suicided, including their associated ghosts. +GLOBAL_LIST_EMPTY(drones_list) +GLOBAL_LIST_EMPTY(dead_mob_list) //all dead mobs, including clientless. Excludes /mob/dead/new_player +GLOBAL_LIST_EMPTY(joined_player_list) //all clients that have joined the game at round-start or as a latejoin. +GLOBAL_LIST_EMPTY(new_player_list) //all /mob/dead/new_player, in theory all should have clients and those that don't are in the process of spawning and get deleted when done. +GLOBAL_LIST_EMPTY(pre_setup_antags) //minds that have been picked as antag by the gamemode. removed as antag datums are set. +GLOBAL_LIST_EMPTY(silicon_mobs) //all silicon mobs +GLOBAL_LIST_EMPTY(mob_living_list) //all instances of /mob/living and subtypes +GLOBAL_LIST_EMPTY(carbon_list) //all instances of /mob/living/carbon and subtypes, notably does not contain brains or simple animals +GLOBAL_LIST_EMPTY(human_list) //all instances of /mob/living/carbon/human and subtypes +GLOBAL_LIST_EMPTY(ai_list) +GLOBAL_LIST_EMPTY(pai_list) +GLOBAL_LIST_EMPTY(available_ai_shells) +GLOBAL_LIST_INIT(simple_animals, list(list(),list(),list(),list())) // One for each AI_* status define +GLOBAL_LIST_EMPTY(spidermobs) //all sentient spider mobs +GLOBAL_LIST_EMPTY(bots_list) +GLOBAL_LIST_EMPTY(aiEyes) +///underages who have been reported to security for trying to buy things they shouldn't, so they can't spam +GLOBAL_LIST_EMPTY(narcd_underages) + +GLOBAL_LIST_EMPTY(language_datum_instances) +GLOBAL_LIST_EMPTY(all_languages) + +GLOBAL_LIST_EMPTY(sentient_disease_instances) + +GLOBAL_LIST_EMPTY(latejoin_ai_cores) + +GLOBAL_LIST_EMPTY(mob_config_movespeed_type_lookup) + +GLOBAL_LIST_EMPTY(emote_list) + +/proc/update_config_movespeed_type_lookup(update_mobs = TRUE) + var/list/mob_types = list() + var/list/entry_value = CONFIG_GET(keyed_list/multiplicative_movespeed) + for(var/path in entry_value) + var/value = entry_value[path] + if(!value) + continue + for(var/subpath in typesof(path)) + mob_types[subpath] = value + GLOB.mob_config_movespeed_type_lookup = mob_types + if(update_mobs) + update_mob_config_movespeeds() + +/proc/update_mob_config_movespeeds() + for(var/i in GLOB.mob_list) + var/mob/M = i + M.update_config_movespeed() + +/proc/init_emote_list() + . = list() + for(var/path in subtypesof(/datum/emote)) + var/datum/emote/E = new path() + if(E.key) + if(!.[E.key]) + .[E.key] = list(E) + else + .[E.key] += E + else if(E.message) //Assuming all non-base emotes have this + stack_trace("Keyless emote: [E.type]") + + if(E.key_third_person) //This one is optional + if(!.[E.key_third_person]) + .[E.key_third_person] = list(E) + else + .[E.key_third_person] |= E diff --git a/code/_globalvars/lists/names.dm b/code/_globalvars/lists/names.dm index dd699690cfb7..d6792159afa6 100644 --- a/code/_globalvars/lists/names.dm +++ b/code/_globalvars/lists/names.dm @@ -1,53 +1,53 @@ -GLOBAL_LIST_INIT(ai_names, world.file2list("strings/names/ai.txt")) -GLOBAL_LIST_INIT(wizard_first, world.file2list("strings/names/wizardfirst.txt")) -GLOBAL_LIST_INIT(wizard_second, world.file2list("strings/names/wizardsecond.txt")) -GLOBAL_LIST_INIT(ninja_titles, world.file2list("strings/names/ninjatitle.txt")) -GLOBAL_LIST_INIT(ninja_names, world.file2list("strings/names/ninjaname.txt")) -GLOBAL_LIST_INIT(commando_names, world.file2list("strings/names/death_commando.txt")) -GLOBAL_LIST_INIT(first_names, world.file2list("strings/names/first.txt")) -GLOBAL_LIST_INIT(first_names_male, world.file2list("strings/names/first_male.txt")) -GLOBAL_LIST_INIT(first_names_female, world.file2list("strings/names/first_female.txt")) -GLOBAL_LIST_INIT(last_names, world.file2list("strings/names/last.txt")) -GLOBAL_LIST_INIT(lizard_names_male, world.file2list("strings/names/lizard_male.txt")) -GLOBAL_LIST_INIT(lizard_names_female, world.file2list("strings/names/lizard_female.txt")) -GLOBAL_LIST_INIT(clown_names, world.file2list("strings/names/clown.txt")) -GLOBAL_LIST_INIT(mime_names, world.file2list("strings/names/mime.txt")) -GLOBAL_LIST_INIT(carp_names, world.file2list("strings/names/carp.txt")) -GLOBAL_LIST_INIT(golem_names, world.file2list("strings/names/golem.txt")) -GLOBAL_LIST_INIT(moth_first, world.file2list("strings/names/moth_first.txt")) -GLOBAL_LIST_INIT(moth_last, world.file2list("strings/names/moth_last.txt")) -GLOBAL_LIST_INIT(plasmaman_names, world.file2list("strings/names/plasmaman.txt")) -GLOBAL_LIST_INIT(ethereal_names, world.file2list("strings/names/ethereal.txt")) -GLOBAL_LIST_INIT(squid_names, world.file2list("strings/names/squid.txt")) -GLOBAL_LIST_INIT(posibrain_names, world.file2list("strings/names/posibrain.txt")) -GLOBAL_LIST_INIT(nightmare_names, world.file2list("strings/names/nightmare.txt")) -GLOBAL_LIST_INIT(megacarp_first_names, world.file2list("strings/names/megacarp1.txt")) -GLOBAL_LIST_INIT(megacarp_last_names, world.file2list("strings/names/megacarp2.txt")) - -GLOBAL_LIST_INIT(verbs, world.file2list("strings/names/verbs.txt")) -GLOBAL_LIST_INIT(ing_verbs, world.file2list("strings/names/ing_verbs.txt")) -GLOBAL_LIST_INIT(adverbs, world.file2list("strings/names/adverbs.txt")) -GLOBAL_LIST_INIT(adjectives, world.file2list("strings/names/adjectives.txt")) -GLOBAL_LIST_INIT(dream_strings, world.file2list("strings/dreamstrings.txt")) -//loaded on startup because of " -//would include in rsc if ' was used - -/* -List of configurable names in preferences and their metadata -"id" = list( - "pref_name" = "name", //pref label - "qdesc" = "name", //popup question text - "allow_numbers" = FALSE, // numbers allowed in the name - "group" = "whatever", // group (these will be grouped together on pref ui ,order still follows the list so they need to be concurrent to be grouped) - "allow_null" = FALSE // if empty name is entered it's replaced with default value - ), -*/ -GLOBAL_LIST_INIT(preferences_custom_names, list( - "human" = list("pref_name" = "Backup Human", "qdesc" = "backup human name, used in the event you are assigned a command role as another species", "allow_numbers" = FALSE , "group" = "backup_human", "allow_null" = FALSE), - "clown" = list("pref_name" = "Clown" , "qdesc" = "clown name", "allow_numbers" = FALSE , "group" = "fun", "allow_null" = FALSE), - "mime" = list("pref_name" = "Mime", "qdesc" = "mime name" , "allow_numbers" = FALSE , "group" = "fun", "allow_null" = FALSE), - "cyborg" = list("pref_name" = "Cyborg", "qdesc" = "cyborg name (Leave empty to use default naming scheme)", "allow_numbers" = TRUE , "group" = "silicons", "allow_null" = TRUE), - "ai" = list("pref_name" = "AI", "qdesc" = "ai name", "allow_numbers" = TRUE , "group" = "silicons", "allow_null" = FALSE), - "religion" = list("pref_name" = "Chaplain religion", "qdesc" = "religion" , "allow_numbers" = TRUE , "group" = "chaplain", "allow_null" = FALSE), - "deity" = list("pref_name" = "Chaplain deity", "qdesc" = "deity", "allow_numbers" = TRUE , "group" = "chaplain", "allow_null" = FALSE) - )) +GLOBAL_LIST_INIT(ai_names, world.file2list("strings/names/ai.txt")) +GLOBAL_LIST_INIT(wizard_first, world.file2list("strings/names/wizardfirst.txt")) +GLOBAL_LIST_INIT(wizard_second, world.file2list("strings/names/wizardsecond.txt")) +GLOBAL_LIST_INIT(ninja_titles, world.file2list("strings/names/ninjatitle.txt")) +GLOBAL_LIST_INIT(ninja_names, world.file2list("strings/names/ninjaname.txt")) +GLOBAL_LIST_INIT(commando_names, world.file2list("strings/names/death_commando.txt")) +GLOBAL_LIST_INIT(first_names, world.file2list("strings/names/first.txt")) +GLOBAL_LIST_INIT(first_names_male, world.file2list("strings/names/first_male.txt")) +GLOBAL_LIST_INIT(first_names_female, world.file2list("strings/names/first_female.txt")) +GLOBAL_LIST_INIT(last_names, world.file2list("strings/names/last.txt")) +GLOBAL_LIST_INIT(lizard_names_male, world.file2list("strings/names/lizard_male.txt")) +GLOBAL_LIST_INIT(lizard_names_female, world.file2list("strings/names/lizard_female.txt")) +GLOBAL_LIST_INIT(clown_names, world.file2list("strings/names/clown.txt")) +GLOBAL_LIST_INIT(mime_names, world.file2list("strings/names/mime.txt")) +GLOBAL_LIST_INIT(carp_names, world.file2list("strings/names/carp.txt")) +GLOBAL_LIST_INIT(golem_names, world.file2list("strings/names/golem.txt")) +GLOBAL_LIST_INIT(moth_first, world.file2list("strings/names/moth_first.txt")) +GLOBAL_LIST_INIT(moth_last, world.file2list("strings/names/moth_last.txt")) +GLOBAL_LIST_INIT(plasmaman_names, world.file2list("strings/names/plasmaman.txt")) +GLOBAL_LIST_INIT(ethereal_names, world.file2list("strings/names/ethereal.txt")) +GLOBAL_LIST_INIT(squid_names, world.file2list("strings/names/squid.txt")) +GLOBAL_LIST_INIT(posibrain_names, world.file2list("strings/names/posibrain.txt")) +GLOBAL_LIST_INIT(nightmare_names, world.file2list("strings/names/nightmare.txt")) +GLOBAL_LIST_INIT(megacarp_first_names, world.file2list("strings/names/megacarp1.txt")) +GLOBAL_LIST_INIT(megacarp_last_names, world.file2list("strings/names/megacarp2.txt")) + +GLOBAL_LIST_INIT(verbs, world.file2list("strings/names/verbs.txt")) +GLOBAL_LIST_INIT(ing_verbs, world.file2list("strings/names/ing_verbs.txt")) +GLOBAL_LIST_INIT(adverbs, world.file2list("strings/names/adverbs.txt")) +GLOBAL_LIST_INIT(adjectives, world.file2list("strings/names/adjectives.txt")) +GLOBAL_LIST_INIT(dream_strings, world.file2list("strings/dreamstrings.txt")) +//loaded on startup because of " +//would include in rsc if ' was used + +/* +List of configurable names in preferences and their metadata +"id" = list( + "pref_name" = "name", //pref label + "qdesc" = "name", //popup question text + "allow_numbers" = FALSE, // numbers allowed in the name + "group" = "whatever", // group (these will be grouped together on pref ui ,order still follows the list so they need to be concurrent to be grouped) + "allow_null" = FALSE // if empty name is entered it's replaced with default value + ), +*/ +GLOBAL_LIST_INIT(preferences_custom_names, list( + "human" = list("pref_name" = "Backup Human", "qdesc" = "backup human name, used in the event you are assigned a command role as another species", "allow_numbers" = FALSE , "group" = "backup_human", "allow_null" = FALSE), + "clown" = list("pref_name" = "Clown" , "qdesc" = "clown name", "allow_numbers" = FALSE , "group" = "fun", "allow_null" = FALSE), + "mime" = list("pref_name" = "Mime", "qdesc" = "mime name" , "allow_numbers" = FALSE , "group" = "fun", "allow_null" = FALSE), + "cyborg" = list("pref_name" = "Cyborg", "qdesc" = "cyborg name (Leave empty to use default naming scheme)", "allow_numbers" = TRUE , "group" = "silicons", "allow_null" = TRUE), + "ai" = list("pref_name" = "AI", "qdesc" = "ai name", "allow_numbers" = TRUE , "group" = "silicons", "allow_null" = FALSE), + "religion" = list("pref_name" = "Chaplain religion", "qdesc" = "religion" , "allow_numbers" = TRUE , "group" = "chaplain", "allow_null" = FALSE), + "deity" = list("pref_name" = "Chaplain deity", "qdesc" = "deity", "allow_numbers" = TRUE , "group" = "chaplain", "allow_null" = FALSE) + )) diff --git a/code/_globalvars/lists/objects.dm b/code/_globalvars/lists/objects.dm index 08cac62460e8..7e5d5c4c011e 100644 --- a/code/_globalvars/lists/objects.dm +++ b/code/_globalvars/lists/objects.dm @@ -1,44 +1,44 @@ -GLOBAL_LIST_EMPTY(cable_list) //Index for all cables, so that powernets don't have to look through the entire world all the time -GLOBAL_LIST_EMPTY(portals) //list of all /obj/effect/portal -GLOBAL_LIST_EMPTY(airlocks) //list of all airlocks -GLOBAL_LIST_EMPTY(mechas_list) //list of all mechs. Used by hostile mobs target tracking. -GLOBAL_LIST_EMPTY(shuttle_caller_list) //list of all communication consoles and AIs, for automatic shuttle calls when there are none. -GLOBAL_LIST_EMPTY(machines) //NOTE: this is a list of ALL machines now. The processing machines list is SSmachine.processing ! -GLOBAL_LIST_EMPTY(navigation_computers) //list of all /obj/machinery/computer/camera_advanced/shuttle_docker -GLOBAL_LIST_EMPTY(syndicate_shuttle_boards) //important to keep track of for managing nukeops war declarations. -GLOBAL_LIST_EMPTY(navbeacons) //list of all bot nagivation beacons, used for patrolling. -GLOBAL_LIST_EMPTY(teleportbeacons) //list of all tracking beacons used by teleporters -GLOBAL_LIST_EMPTY(deliverybeacons) //list of all MULEbot delivery beacons. -GLOBAL_LIST_EMPTY(deliverybeacontags) //list of all tags associated with delivery beacons. -GLOBAL_LIST_EMPTY(wayfindingbeacons) //list of all navigation beacons used by wayfinding pinpointers -GLOBAL_LIST_EMPTY(nuke_list) -GLOBAL_LIST_EMPTY(alarmdisplay) //list of all machines or programs that can display station alerts -GLOBAL_LIST_EMPTY(singularities) //list of all singularities on the station (actually technically all engines) - -GLOBAL_LIST(chemical_reactions_list) //list of all /datum/chemical_reaction datums. Used during chemical reactions -GLOBAL_LIST(chemical_reagents_list) //list of all /datum/reagent datums indexed by reagent id. Used by chemistry stuff -GLOBAL_LIST_EMPTY(materials_list) //list of all /datum/material datums indexed by material id. -GLOBAL_LIST_EMPTY(tech_list) //list of all /datum/tech datums indexed by id. -GLOBAL_LIST_EMPTY(surgeries_list) //list of all surgeries by name, associated with their path. -GLOBAL_LIST_EMPTY(crafting_recipes) //list of all table craft recipes -GLOBAL_LIST_EMPTY(rcd_list) //list of Rapid Construction Devices. -GLOBAL_LIST_EMPTY(apcs_list) //list of all Area Power Controller machines, separate from machines for powernet speeeeeeed. -GLOBAL_LIST_EMPTY(tracked_implants) //list of all current implants that are tracked to work out what sort of trek everyone is on. Sadly not on lavaworld not implemented... -GLOBAL_LIST_EMPTY(tracked_chem_implants) //list of implants the prisoner console can track and send inject commands too -GLOBAL_LIST_EMPTY(poi_list) //list of points of interest for observe/follow -GLOBAL_LIST_EMPTY(pinpointer_list) //list of all pinpointers. Used to change stuff they are pointing to all at once. -GLOBAL_LIST_EMPTY(zombie_infection_list) // A list of all zombie_infection organs, for any mass "animation" -GLOBAL_LIST_EMPTY(meteor_list) // List of all meteors. -GLOBAL_LIST_EMPTY(active_jammers) // List of active radio jammers -GLOBAL_LIST_EMPTY(ladders) -GLOBAL_LIST_EMPTY(trophy_cases) -///This is a global list of all signs you can change an existing sign or new sign backing to, when using a pen on them. -GLOBAL_LIST_EMPTY(editable_sign_types) - -GLOBAL_LIST_EMPTY(wire_color_directory) -GLOBAL_LIST_EMPTY(wire_name_directory) - -GLOBAL_LIST_EMPTY(ai_status_displays) - -GLOBAL_LIST_EMPTY(mob_spawners) // All mob_spawn objects -GLOBAL_LIST_EMPTY(alert_consoles) // Station alert consoles, /obj/machinery/computer/station_alert +GLOBAL_LIST_EMPTY(cable_list) //Index for all cables, so that powernets don't have to look through the entire world all the time +GLOBAL_LIST_EMPTY(portals) //list of all /obj/effect/portal +GLOBAL_LIST_EMPTY(airlocks) //list of all airlocks +GLOBAL_LIST_EMPTY(mechas_list) //list of all mechs. Used by hostile mobs target tracking. +GLOBAL_LIST_EMPTY(shuttle_caller_list) //list of all communication consoles and AIs, for automatic shuttle calls when there are none. +GLOBAL_LIST_EMPTY(machines) //NOTE: this is a list of ALL machines now. The processing machines list is SSmachine.processing ! +GLOBAL_LIST_EMPTY(navigation_computers) //list of all /obj/machinery/computer/camera_advanced/shuttle_docker +GLOBAL_LIST_EMPTY(syndicate_shuttle_boards) //important to keep track of for managing nukeops war declarations. +GLOBAL_LIST_EMPTY(navbeacons) //list of all bot nagivation beacons, used for patrolling. +GLOBAL_LIST_EMPTY(teleportbeacons) //list of all tracking beacons used by teleporters +GLOBAL_LIST_EMPTY(deliverybeacons) //list of all MULEbot delivery beacons. +GLOBAL_LIST_EMPTY(deliverybeacontags) //list of all tags associated with delivery beacons. +GLOBAL_LIST_EMPTY(wayfindingbeacons) //list of all navigation beacons used by wayfinding pinpointers +GLOBAL_LIST_EMPTY(nuke_list) +GLOBAL_LIST_EMPTY(alarmdisplay) //list of all machines or programs that can display station alerts +GLOBAL_LIST_EMPTY(singularities) //list of all singularities on the station (actually technically all engines) + +GLOBAL_LIST(chemical_reactions_list) //list of all /datum/chemical_reaction datums. Used during chemical reactions +GLOBAL_LIST(chemical_reagents_list) //list of all /datum/reagent datums indexed by reagent id. Used by chemistry stuff +GLOBAL_LIST_EMPTY(materials_list) //list of all /datum/material datums indexed by material id. +GLOBAL_LIST_EMPTY(tech_list) //list of all /datum/tech datums indexed by id. +GLOBAL_LIST_EMPTY(surgeries_list) //list of all surgeries by name, associated with their path. +GLOBAL_LIST_EMPTY(crafting_recipes) //list of all table craft recipes +GLOBAL_LIST_EMPTY(rcd_list) //list of Rapid Construction Devices. +GLOBAL_LIST_EMPTY(apcs_list) //list of all Area Power Controller machines, separate from machines for powernet speeeeeeed. +GLOBAL_LIST_EMPTY(tracked_implants) //list of all current implants that are tracked to work out what sort of trek everyone is on. Sadly not on lavaworld not implemented... +GLOBAL_LIST_EMPTY(tracked_chem_implants) //list of implants the prisoner console can track and send inject commands too +GLOBAL_LIST_EMPTY(poi_list) //list of points of interest for observe/follow +GLOBAL_LIST_EMPTY(pinpointer_list) //list of all pinpointers. Used to change stuff they are pointing to all at once. +GLOBAL_LIST_EMPTY(zombie_infection_list) // A list of all zombie_infection organs, for any mass "animation" +GLOBAL_LIST_EMPTY(meteor_list) // List of all meteors. +GLOBAL_LIST_EMPTY(active_jammers) // List of active radio jammers +GLOBAL_LIST_EMPTY(ladders) +GLOBAL_LIST_EMPTY(trophy_cases) +///This is a global list of all signs you can change an existing sign or new sign backing to, when using a pen on them. +GLOBAL_LIST_EMPTY(editable_sign_types) + +GLOBAL_LIST_EMPTY(wire_color_directory) +GLOBAL_LIST_EMPTY(wire_name_directory) + +GLOBAL_LIST_EMPTY(ai_status_displays) + +GLOBAL_LIST_EMPTY(mob_spawners) // All mob_spawn objects +GLOBAL_LIST_EMPTY(alert_consoles) // Station alert consoles, /obj/machinery/computer/station_alert diff --git a/code/_globalvars/logging.dm b/code/_globalvars/logging.dm index 16fba338b94d..6e6ab7d17e30 100644 --- a/code/_globalvars/logging.dm +++ b/code/_globalvars/logging.dm @@ -1,81 +1,81 @@ -GLOBAL_VAR(log_directory) -GLOBAL_PROTECT(log_directory) -GLOBAL_VAR(world_game_log) -GLOBAL_PROTECT(world_game_log) -GLOBAL_VAR(world_runtime_log) -GLOBAL_PROTECT(world_runtime_log) -GLOBAL_VAR(world_qdel_log) -GLOBAL_PROTECT(world_qdel_log) -GLOBAL_VAR(world_attack_log) -GLOBAL_PROTECT(world_attack_log) -GLOBAL_VAR(world_href_log) -GLOBAL_PROTECT(world_href_log) -GLOBAL_VAR(round_id) -GLOBAL_PROTECT(round_id) -GLOBAL_VAR(config_error_log) -GLOBAL_PROTECT(config_error_log) -GLOBAL_VAR(sql_error_log) -GLOBAL_PROTECT(sql_error_log) -GLOBAL_VAR(world_pda_log) -GLOBAL_PROTECT(world_pda_log) -GLOBAL_VAR(world_telecomms_log) -GLOBAL_PROTECT(world_telecomms_log) -GLOBAL_VAR(world_manifest_log) -GLOBAL_PROTECT(world_manifest_log) -GLOBAL_VAR(query_debug_log) -GLOBAL_PROTECT(query_debug_log) -GLOBAL_VAR(world_job_debug_log) -GLOBAL_PROTECT(world_job_debug_log) -GLOBAL_VAR(world_mecha_log) -GLOBAL_PROTECT(world_mecha_log) -GLOBAL_VAR(world_virus_log) -GLOBAL_PROTECT(world_virus_log) -GLOBAL_VAR(world_asset_log) -GLOBAL_PROTECT(world_asset_log) -GLOBAL_VAR(world_cloning_log) -GLOBAL_PROTECT(world_cloning_log) -GLOBAL_VAR(world_map_error_log) -GLOBAL_PROTECT(world_map_error_log) -GLOBAL_VAR(world_paper_log) -GLOBAL_PROTECT(world_paper_log) -GLOBAL_VAR(tgui_log) -GLOBAL_PROTECT(tgui_log) -GLOBAL_VAR(world_shuttle_log) -GLOBAL_PROTECT(world_shuttle_log) -GLOBAL_VAR(discord_api_log) -GLOBAL_PROTECT(discord_api_log) - -GLOBAL_VAR(demo_log) -GLOBAL_PROTECT(demo_log) - -GLOBAL_LIST_EMPTY(bombers) -GLOBAL_PROTECT(bombers) -GLOBAL_LIST_EMPTY(admin_log) -GLOBAL_PROTECT(admin_log) -GLOBAL_LIST_EMPTY(lastsignalers) //keeps last 100 signals here in format: "[src] used [REF(src)] @ location [src.loc]: [freq]/[code]" -GLOBAL_PROTECT(lastsignalers) -GLOBAL_LIST_EMPTY(lawchanges) //Stores who uploaded laws to which silicon-based lifeform, and what the law was -GLOBAL_PROTECT(lawchanges) - -GLOBAL_LIST_EMPTY(combatlog) -GLOBAL_PROTECT(combatlog) -GLOBAL_LIST_EMPTY(IClog) -GLOBAL_PROTECT(IClog) -GLOBAL_LIST_EMPTY(OOClog) -GLOBAL_PROTECT(OOClog) -GLOBAL_LIST_EMPTY(adminlog) -GLOBAL_PROTECT(adminlog) -GLOBAL_LIST_EMPTY(mentorlog) -GLOBAL_PROTECT(mentorlog) - -GLOBAL_LIST_EMPTY(active_turfs_startlist) - -/////Picture logging -GLOBAL_VAR(picture_log_directory) -GLOBAL_PROTECT(picture_log_directory) - -GLOBAL_VAR_INIT(picture_logging_id, 1) -GLOBAL_PROTECT(picture_logging_id) -GLOBAL_VAR(picture_logging_prefix) -GLOBAL_PROTECT(picture_logging_prefix) -///// +GLOBAL_VAR(log_directory) +GLOBAL_PROTECT(log_directory) +GLOBAL_VAR(world_game_log) +GLOBAL_PROTECT(world_game_log) +GLOBAL_VAR(world_runtime_log) +GLOBAL_PROTECT(world_runtime_log) +GLOBAL_VAR(world_qdel_log) +GLOBAL_PROTECT(world_qdel_log) +GLOBAL_VAR(world_attack_log) +GLOBAL_PROTECT(world_attack_log) +GLOBAL_VAR(world_href_log) +GLOBAL_PROTECT(world_href_log) +GLOBAL_VAR(round_id) +GLOBAL_PROTECT(round_id) +GLOBAL_VAR(config_error_log) +GLOBAL_PROTECT(config_error_log) +GLOBAL_VAR(sql_error_log) +GLOBAL_PROTECT(sql_error_log) +GLOBAL_VAR(world_pda_log) +GLOBAL_PROTECT(world_pda_log) +GLOBAL_VAR(world_telecomms_log) +GLOBAL_PROTECT(world_telecomms_log) +GLOBAL_VAR(world_manifest_log) +GLOBAL_PROTECT(world_manifest_log) +GLOBAL_VAR(query_debug_log) +GLOBAL_PROTECT(query_debug_log) +GLOBAL_VAR(world_job_debug_log) +GLOBAL_PROTECT(world_job_debug_log) +GLOBAL_VAR(world_mecha_log) +GLOBAL_PROTECT(world_mecha_log) +GLOBAL_VAR(world_virus_log) +GLOBAL_PROTECT(world_virus_log) +GLOBAL_VAR(world_asset_log) +GLOBAL_PROTECT(world_asset_log) +GLOBAL_VAR(world_cloning_log) +GLOBAL_PROTECT(world_cloning_log) +GLOBAL_VAR(world_map_error_log) +GLOBAL_PROTECT(world_map_error_log) +GLOBAL_VAR(world_paper_log) +GLOBAL_PROTECT(world_paper_log) +GLOBAL_VAR(tgui_log) +GLOBAL_PROTECT(tgui_log) +GLOBAL_VAR(world_shuttle_log) +GLOBAL_PROTECT(world_shuttle_log) +GLOBAL_VAR(discord_api_log) +GLOBAL_PROTECT(discord_api_log) + +GLOBAL_VAR(demo_log) +GLOBAL_PROTECT(demo_log) + +GLOBAL_LIST_EMPTY(bombers) +GLOBAL_PROTECT(bombers) +GLOBAL_LIST_EMPTY(admin_log) +GLOBAL_PROTECT(admin_log) +GLOBAL_LIST_EMPTY(lastsignalers) //keeps last 100 signals here in format: "[src] used [REF(src)] @ location [src.loc]: [freq]/[code]" +GLOBAL_PROTECT(lastsignalers) +GLOBAL_LIST_EMPTY(lawchanges) //Stores who uploaded laws to which silicon-based lifeform, and what the law was +GLOBAL_PROTECT(lawchanges) + +GLOBAL_LIST_EMPTY(combatlog) +GLOBAL_PROTECT(combatlog) +GLOBAL_LIST_EMPTY(IClog) +GLOBAL_PROTECT(IClog) +GLOBAL_LIST_EMPTY(OOClog) +GLOBAL_PROTECT(OOClog) +GLOBAL_LIST_EMPTY(adminlog) +GLOBAL_PROTECT(adminlog) +GLOBAL_LIST_EMPTY(mentorlog) +GLOBAL_PROTECT(mentorlog) + +GLOBAL_LIST_EMPTY(active_turfs_startlist) + +/////Picture logging +GLOBAL_VAR(picture_log_directory) +GLOBAL_PROTECT(picture_log_directory) + +GLOBAL_VAR_INIT(picture_logging_id, 1) +GLOBAL_PROTECT(picture_logging_id) +GLOBAL_VAR(picture_logging_prefix) +GLOBAL_PROTECT(picture_logging_prefix) +///// diff --git a/code/_globalvars/misc.dm b/code/_globalvars/misc.dm index 1e933ede0e5e..3386e9952eeb 100644 --- a/code/_globalvars/misc.dm +++ b/code/_globalvars/misc.dm @@ -1,28 +1,28 @@ -GLOBAL_VAR_INIT(admin_notice, "") // Admin notice that all clients see when joining the server - -GLOBAL_VAR_INIT(timezoneOffset, 0) // The difference betwen midnight (of the host computer) and 0 world.ticks. - - // For FTP requests. (i.e. downloading runtime logs.) - // However it'd be ok to use for accessing attack logs and such too, which are even laggier. -GLOBAL_VAR_INIT(fileaccess_timer, 0) - -GLOBAL_DATUM_INIT(data_core, /datum/datacore, new) - -GLOBAL_VAR_INIT(CELLRATE, 0.002) // conversion ratio between a watt-tick and kilojoule -GLOBAL_VAR_INIT(CHARGELEVEL, 0.001) // Cap for how fast cells charge, as a percentage-per-tick (.001 means cellcharge is capped to 1% per second) - -GLOBAL_LIST_EMPTY(powernets) - -GLOBAL_VAR_INIT(bsa_unlock, FALSE) //BSA unlocked by head ID swipes - -GLOBAL_LIST_EMPTY(player_details) // ckey -> /datum/player_details - -///All currently running polls held as datums -GLOBAL_LIST_EMPTY(polls) -GLOBAL_PROTECT(polls) - -///All poll option datums of running polls -GLOBAL_LIST_EMPTY(poll_options) -GLOBAL_PROTECT(poll_options) - -GLOBAL_VAR_INIT(internal_tick_usage, 0.2 * world.tick_lag) //This var is updated every tick by a DLL if present, used to reduce lag +GLOBAL_VAR_INIT(admin_notice, "") // Admin notice that all clients see when joining the server + +GLOBAL_VAR_INIT(timezoneOffset, 0) // The difference betwen midnight (of the host computer) and 0 world.ticks. + + // For FTP requests. (i.e. downloading runtime logs.) + // However it'd be ok to use for accessing attack logs and such too, which are even laggier. +GLOBAL_VAR_INIT(fileaccess_timer, 0) + +GLOBAL_DATUM_INIT(data_core, /datum/datacore, new) + +GLOBAL_VAR_INIT(CELLRATE, 0.002) // conversion ratio between a watt-tick and kilojoule +GLOBAL_VAR_INIT(CHARGELEVEL, 0.001) // Cap for how fast cells charge, as a percentage-per-tick (.001 means cellcharge is capped to 1% per second) + +GLOBAL_LIST_EMPTY(powernets) + +GLOBAL_VAR_INIT(bsa_unlock, FALSE) //BSA unlocked by head ID swipes + +GLOBAL_LIST_EMPTY(player_details) // ckey -> /datum/player_details + +///All currently running polls held as datums +GLOBAL_LIST_EMPTY(polls) +GLOBAL_PROTECT(polls) + +///All poll option datums of running polls +GLOBAL_LIST_EMPTY(poll_options) +GLOBAL_PROTECT(poll_options) + +GLOBAL_VAR_INIT(internal_tick_usage, 0.2 * world.tick_lag) //This var is updated every tick by a DLL if present, used to reduce lag diff --git a/code/_js/byjax.dm b/code/_js/byjax.dm index 9d96bbc412f7..7ac9bbff1899 100644 --- a/code/_js/byjax.dm +++ b/code/_js/byjax.dm @@ -1,48 +1,48 @@ -//this function places received data into element with specified id. -#define js_byjax {" - -function replaceContent() { - var args = Array.prototype.slice.call(arguments); - var id = args\[0\]; - var content = args\[1\]; - var callback = null; - if(args\[2\]){ - callback = args\[2\]; - if(args\[3\]){ - args = args.slice(3); - } - } - var parent = document.getElementById(id); - if(typeof(parent)!=='undefined' && parent!=null){ - parent.innerHTML = content?content:''; - } - if(callback && window\[callback\]){ - window\[callback\].apply(null,args); - } -} -"} - -/* -sends data to control_id:replaceContent - -receiver - mob -control_id - window id (for windows opened with browse(), it'll be "windowname.browser") -target_element - HTML element id -new_content - HTML content -callback - js function that will be called after the data is sent -callback_args - arguments for callback function - -Be sure to include required js functions in your page, or it'll raise an exception. -*/ -/proc/send_byjax(receiver, control_id, target_element, new_content=null, callback=null, list/callback_args=null) - if(receiver && target_element && control_id) // && winexists(receiver, control_id)) - var/list/argums = list(target_element, new_content) - if(callback) - argums += callback - if(callback_args) - argums += callback_args - argums = list2params(argums) - - receiver << output(argums,"[control_id]:replaceContent") - return - +//this function places received data into element with specified id. +#define js_byjax {" + +function replaceContent() { + var args = Array.prototype.slice.call(arguments); + var id = args\[0\]; + var content = args\[1\]; + var callback = null; + if(args\[2\]){ + callback = args\[2\]; + if(args\[3\]){ + args = args.slice(3); + } + } + var parent = document.getElementById(id); + if(typeof(parent)!=='undefined' && parent!=null){ + parent.innerHTML = content?content:''; + } + if(callback && window\[callback\]){ + window\[callback\].apply(null,args); + } +} +"} + +/* +sends data to control_id:replaceContent + +receiver - mob +control_id - window id (for windows opened with browse(), it'll be "windowname.browser") +target_element - HTML element id +new_content - HTML content +callback - js function that will be called after the data is sent +callback_args - arguments for callback function + +Be sure to include required js functions in your page, or it'll raise an exception. +*/ +/proc/send_byjax(receiver, control_id, target_element, new_content=null, callback=null, list/callback_args=null) + if(receiver && target_element && control_id) // && winexists(receiver, control_id)) + var/list/argums = list(target_element, new_content) + if(callback) + argums += callback + if(callback_args) + argums += callback_args + argums = list2params(argums) + + receiver << output(argums,"[control_id]:replaceContent") + return + diff --git a/code/_js/menus.dm b/code/_js/menus.dm index 6756862e9b82..05e6edc6a6da 100644 --- a/code/_js/menus.dm +++ b/code/_js/menus.dm @@ -1,37 +1,37 @@ -#define js_dropdowns {" -function dropdowns() { - var divs = document.getElementsByTagName('div'); - var headers = new Array(); - var links = new Array(); - for(var i=0;i=0) { - elem.className = elem.className.replace('visible','hidden'); - this.className = this.className.replace('open','closed'); - this.innerHTML = this.innerHTML.replace('-','+'); - } - else { - elem.className = elem.className.replace('hidden','visible'); - this.className = this.className.replace('closed','open'); - this.innerHTML = this.innerHTML.replace('+','-'); - } - return false; - } - })(links\[i\]); - } - } -} -"} +#define js_dropdowns {" +function dropdowns() { + var divs = document.getElementsByTagName('div'); + var headers = new Array(); + var links = new Array(); + for(var i=0;i=0) { + elem.className = elem.className.replace('visible','hidden'); + this.className = this.className.replace('open','closed'); + this.innerHTML = this.innerHTML.replace('-','+'); + } + else { + elem.className = elem.className.replace('hidden','visible'); + this.className = this.className.replace('closed','open'); + this.innerHTML = this.innerHTML.replace('+','-'); + } + return false; + } + })(links\[i\]); + } + } +} +"} diff --git a/code/_onclick/adjacent.dm b/code/_onclick/adjacent.dm index 1d8c869e5090..c9746fae9806 100644 --- a/code/_onclick/adjacent.dm +++ b/code/_onclick/adjacent.dm @@ -1,105 +1,105 @@ -/* - Adjacency proc for determining touch range - - This is mostly to determine if a user can enter a square for the purposes of touching something. - Examples include reaching a square diagonally or reaching something on the other side of a glass window. - - This is calculated by looking for border items, or in the case of clicking diagonally from yourself, dense items. - This proc will NOT notice if you are trying to attack a window on the other side of a dense object in its turf. There is a window helper for that. - - Note that in all cases the neighbor is handled simply; this is usually the user's mob, in which case it is up to you - to check that the mob is not inside of something -*/ -/atom/proc/Adjacent(atom/neighbor) // basic inheritance, unused - return 0 - -// Not a sane use of the function and (for now) indicative of an error elsewhere -/area/Adjacent(atom/neighbor) - CRASH("Call to /area/Adjacent(), unimplemented proc") - - -/* - Adjacency (to turf): - * If you are in the same turf, always true - * If you are vertically/horizontally adjacent, ensure there are no border objects - * If you are diagonally adjacent, ensure you can pass through at least one of the mutually adjacent square. - * Passing through in this case ignores anything with the LETPASSTHROW pass flag, such as tables, racks, and morgue trays. -*/ -/turf/Adjacent(atom/neighbor, atom/target = null, atom/movable/mover = null) - var/turf/T0 = get_turf(neighbor) - - if(T0 == src) //same turf - return TRUE - - if(get_dist(src, T0) > 1 || z != T0.z) //too far - return FALSE - - // Non diagonal case - if(T0.x == x || T0.y == y) - // Check for border blockages - return T0.ClickCross(get_dir(T0,src), border_only = 1, target_atom = target, mover = mover) && src.ClickCross(get_dir(src,T0), border_only = 1, target_atom = target, mover = mover) - - // Diagonal case - var/in_dir = get_dir(T0,src) // eg. northwest (1+8) = 9 (00001001) - var/d1 = in_dir&3 // eg. north (1+8)&3 (0000 0011) = 1 (0000 0001) - var/d2 = in_dir&12 // eg. west (1+8)&12 (0000 1100) = 8 (0000 1000) - - for(var/d in list(d1,d2)) - if(!T0.ClickCross(d, border_only = 1, target_atom = target, mover = mover)) - continue // could not leave T0 in that direction - - var/turf/T1 = get_step(T0,d) - if(!T1 || T1.density) - continue - if(!T1.ClickCross(get_dir(T1,src), border_only = 0, target_atom = target, mover = mover) || !T1.ClickCross(get_dir(T1,T0), border_only = 0, target_atom = target, mover = mover)) - continue // couldn't enter or couldn't leave T1 - - if(!src.ClickCross(get_dir(src,T1), border_only = 1, target_atom = target, mover = mover)) - continue // could not enter src - - return 1 // we don't care about our own density - - return 0 - -/* - Adjacency (to anything else): - * Must be on a turf -*/ -/atom/movable/Adjacent(atom/neighbor) - if(neighbor == loc) - return TRUE - var/turf/T = loc - if(!istype(T)) - return FALSE - if(T.Adjacent(neighbor,target = neighbor, mover = src)) - return TRUE - return FALSE - -// This is necessary for storage items not on your person. -/obj/item/Adjacent(atom/neighbor, recurse = 1) - if(neighbor == loc) - return 1 - if(isitem(loc)) - if(recurse > 0) - return loc.Adjacent(neighbor,recurse - 1) - return 0 - return ..() - -/* - This checks if you there is uninterrupted airspace between that turf and this one. - This is defined as any dense ON_BORDER_1 object, or any dense object without LETPASSTHROW. - The border_only flag allows you to not objects (for source and destination squares) -*/ -/turf/proc/ClickCross(target_dir, border_only, target_atom = null, atom/movable/mover = null) - for(var/obj/O in src) - if((mover && O.CanPass(mover,get_step(src,target_dir))) || (!mover && !O.density)) - continue - if(O == target_atom || O == mover || (O.pass_flags & LETPASSTHROW)) //check if there's a dense object present on the turf - continue // LETPASSTHROW is used for anything you can click through (or the firedoor special case, see above) - - if( O.flags_1&ON_BORDER_1) // windows are on border, check them first - if( O.dir & target_dir || O.dir & (O.dir-1) ) // full tile windows are just diagonals mechanically - return 0 //O.dir&(O.dir-1) is false for any cardinal direction, but true for diagonal ones - else if( !border_only ) // dense, not on border, cannot pass over - return 0 - return 1 +/* + Adjacency proc for determining touch range + + This is mostly to determine if a user can enter a square for the purposes of touching something. + Examples include reaching a square diagonally or reaching something on the other side of a glass window. + + This is calculated by looking for border items, or in the case of clicking diagonally from yourself, dense items. + This proc will NOT notice if you are trying to attack a window on the other side of a dense object in its turf. There is a window helper for that. + + Note that in all cases the neighbor is handled simply; this is usually the user's mob, in which case it is up to you + to check that the mob is not inside of something +*/ +/atom/proc/Adjacent(atom/neighbor) // basic inheritance, unused + return 0 + +// Not a sane use of the function and (for now) indicative of an error elsewhere +/area/Adjacent(atom/neighbor) + CRASH("Call to /area/Adjacent(), unimplemented proc") + + +/* + Adjacency (to turf): + * If you are in the same turf, always true + * If you are vertically/horizontally adjacent, ensure there are no border objects + * If you are diagonally adjacent, ensure you can pass through at least one of the mutually adjacent square. + * Passing through in this case ignores anything with the LETPASSTHROW pass flag, such as tables, racks, and morgue trays. +*/ +/turf/Adjacent(atom/neighbor, atom/target = null, atom/movable/mover = null) + var/turf/T0 = get_turf(neighbor) + + if(T0 == src) //same turf + return TRUE + + if(get_dist(src, T0) > 1 || z != T0.z) //too far + return FALSE + + // Non diagonal case + if(T0.x == x || T0.y == y) + // Check for border blockages + return T0.ClickCross(get_dir(T0,src), border_only = 1, target_atom = target, mover = mover) && src.ClickCross(get_dir(src,T0), border_only = 1, target_atom = target, mover = mover) + + // Diagonal case + var/in_dir = get_dir(T0,src) // eg. northwest (1+8) = 9 (00001001) + var/d1 = in_dir&3 // eg. north (1+8)&3 (0000 0011) = 1 (0000 0001) + var/d2 = in_dir&12 // eg. west (1+8)&12 (0000 1100) = 8 (0000 1000) + + for(var/d in list(d1,d2)) + if(!T0.ClickCross(d, border_only = 1, target_atom = target, mover = mover)) + continue // could not leave T0 in that direction + + var/turf/T1 = get_step(T0,d) + if(!T1 || T1.density) + continue + if(!T1.ClickCross(get_dir(T1,src), border_only = 0, target_atom = target, mover = mover) || !T1.ClickCross(get_dir(T1,T0), border_only = 0, target_atom = target, mover = mover)) + continue // couldn't enter or couldn't leave T1 + + if(!src.ClickCross(get_dir(src,T1), border_only = 1, target_atom = target, mover = mover)) + continue // could not enter src + + return 1 // we don't care about our own density + + return 0 + +/* + Adjacency (to anything else): + * Must be on a turf +*/ +/atom/movable/Adjacent(atom/neighbor) + if(neighbor == loc) + return TRUE + var/turf/T = loc + if(!istype(T)) + return FALSE + if(T.Adjacent(neighbor,target = neighbor, mover = src)) + return TRUE + return FALSE + +// This is necessary for storage items not on your person. +/obj/item/Adjacent(atom/neighbor, recurse = 1) + if(neighbor == loc) + return 1 + if(isitem(loc)) + if(recurse > 0) + return loc.Adjacent(neighbor,recurse - 1) + return 0 + return ..() + +/* + This checks if you there is uninterrupted airspace between that turf and this one. + This is defined as any dense ON_BORDER_1 object, or any dense object without LETPASSTHROW. + The border_only flag allows you to not objects (for source and destination squares) +*/ +/turf/proc/ClickCross(target_dir, border_only, target_atom = null, atom/movable/mover = null) + for(var/obj/O in src) + if((mover && O.CanPass(mover,get_step(src,target_dir))) || (!mover && !O.density)) + continue + if(O == target_atom || O == mover || (O.pass_flags & LETPASSTHROW)) //check if there's a dense object present on the turf + continue // LETPASSTHROW is used for anything you can click through (or the firedoor special case, see above) + + if( O.flags_1&ON_BORDER_1) // windows are on border, check them first + if( O.dir & target_dir || O.dir & (O.dir-1) ) // full tile windows are just diagonals mechanically + return 0 //O.dir&(O.dir-1) is false for any cardinal direction, but true for diagonal ones + else if( !border_only ) // dense, not on border, cannot pass over + return 0 + return 1 diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm index cc28434b108a..acbff02c3fb9 100644 --- a/code/_onclick/click.dm +++ b/code/_onclick/click.dm @@ -1,482 +1,482 @@ -/* - Click code cleanup - ~Sayu -*/ - -// 1 decisecond click delay (above and beyond mob/next_move) -//This is mainly modified by click code, to modify click delays elsewhere, use next_move and changeNext_move() -/mob/var/next_click = 0 - -// THESE DO NOT EFFECT THE BASE 1 DECISECOND DELAY OF NEXT_CLICK -/mob/var/next_move_adjust = 0 //Amount to adjust action/click delays by, + or - -/mob/var/next_move_modifier = 1 //Value to multiply action/click delays by - - -//Delays the mob's next click/action by num deciseconds -// eg: 10-3 = 7 deciseconds of delay -// eg: 10*0.5 = 5 deciseconds of delay -// DOES NOT EFFECT THE BASE 1 DECISECOND DELAY OF NEXT_CLICK - -/mob/proc/changeNext_move(num) - next_move = world.time + ((num+next_move_adjust)*next_move_modifier) - -/mob/living/changeNext_move(num) - var/mod = next_move_modifier - var/adj = next_move_adjust - for(var/i in status_effects) - var/datum/status_effect/S = i - mod *= S.nextmove_modifier() - adj += S.nextmove_adjust() - next_move = world.time + ((num + adj)*mod) - -/** - * Before anything else, defer these calls to a per-mobtype handler. This allows us to - * remove istype() spaghetti code, but requires the addition of other handler procs to simplify it. - * - * Alternately, you could hardcode every mob's variation in a flat [/mob/proc/ClickOn] proc; however, - * that's a lot of code duplication and is hard to maintain. - * - * Note that this proc can be overridden, and is in the case of screen objects. - */ -/atom/Click(location,control,params) - if(flags_1 & INITIALIZED_1) - SEND_SIGNAL(src, COMSIG_CLICK, location, control, params, usr) - usr.ClickOn(src, params) - -/atom/DblClick(location,control,params) - if(flags_1 & INITIALIZED_1) - usr.DblClickOn(src,params) - -/atom/MouseWheel(delta_x,delta_y,location,control,params) - if(flags_1 & INITIALIZED_1) - usr.MouseWheelOn(src, delta_x, delta_y, params) - -/** - * Standard mob ClickOn() - * Handles exceptions: Buildmode, middle click, modified clicks, mech actions - * - * After that, mostly just check your state, check whether you're holding an item, - * check whether you're adjacent to the target, then pass off the click to whoever - * is receiving it. - * The most common are: - * * [mob/proc/UnarmedAttack] (atom,adjacent) - used here only when adjacent, with no item in hand; in the case of humans, checks gloves - * * [atom/proc/attackby] (item,user) - used only when adjacent - * * [obj/item/proc/afterattack] (atom,user,adjacent,params) - used both ranged and adjacent - * * [mob/proc/RangedAttack] (atom,params) - used only ranged, only used for tk and laser eyes but could be changed - */ -/mob/proc/ClickOn( atom/A, params ) - if(world.time <= next_click) - return - next_click = world.time + 1 - - if(check_click_intercept(params,A)) - return - - if(notransform) - return - - if(SEND_SIGNAL(src, COMSIG_MOB_CLICKON, A, params) & COMSIG_MOB_CANCEL_CLICKON) - return - - var/list/modifiers = params2list(params) - if(modifiers["shift"] && modifiers["middle"]) - ShiftMiddleClickOn(A) - return - if(modifiers["shift"] && modifiers["ctrl"]) - CtrlShiftClickOn(A) - return - if(modifiers["middle"]) - MiddleClickOn(A) - return - if(modifiers["shift"]) - ShiftClickOn(A) - return - if(modifiers["alt"]) // alt and alt-gr (rightalt) - AltClickOn(A) - return - if(modifiers["ctrl"]) - CtrlClickOn(A) - return - - if(incapacitated(ignore_restraints = 1)) - return - - face_atom(A) - - if(next_move > world.time) // in the year 2000... - return - - if(!modifiers["catcher"] && A.IsObscured()) - return - - if(ismecha(loc)) - var/obj/mecha/M = loc - return M.click_action(A,src,params) - - if(restrained()) - changeNext_move(CLICK_CD_HANDCUFFED) //Doing shit in cuffs shall be vey slow - RestrainedClickOn(A) - return - - if(in_throw_mode) - throw_item(A) - return - - var/obj/item/W = get_active_held_item() - - if(W == A) - W.attack_self(src) - update_inv_hands() - return - - //These are always reachable. - //User itself, current loc, and user inventory - if(A in DirectAccess()) - if(W) - W.melee_attack_chain(src, A, params) - else - if(ismob(A)) - changeNext_move(CLICK_CD_MELEE) - UnarmedAttack(A) - return - - //Can't reach anything else in lockers or other weirdness - if(!loc.AllowClick()) - return - - //Standard reach turf to turf or reaching inside storage - if(CanReach(A,W)) - if(W) - W.melee_attack_chain(src, A, params) - else - if(ismob(A)) - changeNext_move(CLICK_CD_MELEE) - UnarmedAttack(A,1) - else - if(W) - W.afterattack(A,src,0,params) - else - RangedAttack(A,params) - -/// Is the atom obscured by a PREVENT_CLICK_UNDER_1 object above it -/atom/proc/IsObscured() - SHOULD_BE_PURE(TRUE) - if(!isturf(loc)) //This only makes sense for things directly on turfs for now - return FALSE - var/turf/T = get_turf_pixel(src) - if(!T) - return FALSE - for(var/atom/movable/AM in T) - if(AM.flags_1 & PREVENT_CLICK_UNDER_1 && AM.density && (AM.layer > layer || (T.intact && HAS_TRAIT(AM, TRAIT_T_RAY_VISIBLE)))) - return TRUE - return FALSE - -/turf/IsObscured() - for(var/item in src) - var/atom/movable/AM = item - if(AM.flags_1 & PREVENT_CLICK_UNDER_1) - return TRUE - return FALSE - -/** - * A backwards depth-limited breadth-first-search to see if the target is - * logically "in" anything adjacent to us. - */ -/atom/movable/proc/CanReach(atom/ultimate_target, obj/item/tool, view_only = FALSE) - var/list/direct_access = DirectAccess() - var/depth = 1 + (view_only ? STORAGE_VIEW_DEPTH : INVENTORY_DEPTH) - - var/list/closed = list() - var/list/checking = list(ultimate_target) - while (checking.len && depth > 0) - var/list/next = list() - --depth - - for(var/atom/target in checking) // will filter out nulls - if(closed[target] || isarea(target)) // avoid infinity situations - continue - closed[target] = TRUE - if(isturf(target) || isturf(target.loc) || (target in direct_access)) //Directly accessible atoms - if(Adjacent(target) || (tool && CheckToolReach(src, target, tool.reach))) //Adjacent or reaching attacks - return TRUE - - if (!target.loc) - continue - - if(!(SEND_SIGNAL(target.loc, COMSIG_ATOM_CANREACH, next) & COMPONENT_BLOCK_REACH)) - next += target.loc - - checking = next - return FALSE - -/atom/movable/proc/DirectAccess() - return list(src, loc) - -/mob/DirectAccess(atom/target) - return ..() + contents - -/mob/living/DirectAccess(atom/target) - return ..() + GetAllContents() - -/atom/proc/AllowClick() - return FALSE - -/turf/AllowClick() - return TRUE - -/proc/CheckToolReach(atom/movable/here, atom/movable/there, reach) - if(!here || !there) - return - switch(reach) - if(0) - return FALSE - if(1) - return FALSE //here.Adjacent(there) - if(2 to INFINITY) - var/obj/dummy = new(get_turf(here)) - dummy.pass_flags |= PASSTABLE - dummy.invisibility = INVISIBILITY_ABSTRACT - for(var/i in 1 to reach) //Limit it to that many tries - var/turf/T = get_step(dummy, get_dir(dummy, there)) - if(dummy.CanReach(there)) - qdel(dummy) - return TRUE - if(!dummy.Move(T)) //we're blocked! - qdel(dummy) - return - qdel(dummy) - -/// Default behavior: ignore double clicks (the second click that makes the doubleclick call already calls for a normal click) -/mob/proc/DblClickOn(atom/A, params) - return - - -/** - * Translates into [atom/proc/attack_hand], etc. - * - * Note: proximity_flag here is used to distinguish between normal usage (flag=1), - * and usage when clicking on things telekinetically (flag=0). This proc will - * not be called at ranged except with telekinesis. - * - * proximity_flag is not currently passed to attack_hand, and is instead used - * in human click code to allow glove touches only at melee range. - */ -/mob/proc/UnarmedAttack(atom/A, proximity_flag) - if(ismob(A)) - changeNext_move(CLICK_CD_MELEE) - return - -/** - * Ranged unarmed attack: - * - * This currently is just a default for all mobs, involving - * laser eyes and telekinesis. You could easily add exceptions - * for things like ranged glove touches, spitting alien acid/neurotoxin, - * animals lunging, etc. - */ -/mob/proc/RangedAttack(atom/A, params) - SEND_SIGNAL(src, COMSIG_MOB_ATTACK_RANGED, A, params) -/** - * Restrained ClickOn - * - * Used when you are handcuffed and click things. - * Not currently used by anything but could easily be. - */ -/mob/proc/RestrainedClickOn(atom/A) - return - -/** - * Middle click - * Mainly used for swapping hands - */ -/mob/proc/MiddleClickOn(atom/A) - . = SEND_SIGNAL(src, COMSIG_MOB_MIDDLECLICKON, A) - if(. & COMSIG_MOB_CANCEL_CLICKON) - return - swap_hand() - -/** - * Shift click - * For most mobs, examine. - * This is overridden in ai.dm - */ -/mob/proc/ShiftClickOn(atom/A) - A.ShiftClick(src) - return - -/atom/proc/ShiftClick(mob/user) - var/flags = SEND_SIGNAL(src, COMSIG_CLICK_SHIFT, user) - if(user.client && (user.client.eye == user || user.client.eye == user.loc || flags & COMPONENT_ALLOW_EXAMINATE)) - user.examinate(src) - return - -/** - * Ctrl click - * For most objects, pull - */ -/mob/proc/CtrlClickOn(atom/A) - A.CtrlClick(src) - return - -/atom/proc/CtrlClick(mob/user) - SEND_SIGNAL(src, COMSIG_CLICK_CTRL, user) - var/mob/living/ML = user - if(istype(ML)) - ML.pulled(src) - -/mob/living/carbon/human/CtrlClick(mob/user) - if(ishuman(user) && Adjacent(user) && !user.incapacitated()) - if(world.time < user.next_move) - return FALSE - var/mob/living/carbon/human/H = user - H.dna.species.grab(H, src, H.mind.martial_art) - H.changeNext_move(CLICK_CD_MELEE) - else - ..() -/** - * Alt click - * Unused except for AI - */ -/mob/proc/AltClickOn(atom/A) - . = SEND_SIGNAL(src, COMSIG_MOB_ALTCLICKON, A) - if(. & COMSIG_MOB_CANCEL_CLICKON) - return - A.AltClick(src) - -/atom/proc/AltClick(mob/user) - SEND_SIGNAL(src, COMSIG_CLICK_ALT, user) - var/turf/T = get_turf(src) - if(T && (isturf(loc) || isturf(src)) && user.TurfAdjacent(T)) - user.listed_turf = T - user.client.statpanel = T.name - -/// Use this instead of [/mob/proc/AltClickOn] where you only want turf content listing without additional atom alt-click interaction -/atom/proc/AltClickNoInteract(mob/user, atom/A) - var/turf/T = get_turf(A) - if(T && user.TurfAdjacent(T)) - user.listed_turf = T - user.client.statpanel = T.name - -/mob/proc/TurfAdjacent(turf/T) - return T.Adjacent(src) - -/** - * Control+Shift click - * Unused except for AI - */ -/mob/proc/CtrlShiftClickOn(atom/A) - A.CtrlShiftClick(src) - return - -/mob/proc/ShiftMiddleClickOn(atom/A) - src.pointed(A) - return - -/atom/proc/CtrlShiftClick(mob/user) - SEND_SIGNAL(src, COMSIG_CLICK_CTRL_SHIFT) - return - -/* - Misc helpers - face_atom: turns the mob towards what you clicked on -*/ - -/// Simple helper to face what you clicked on, in case it should be needed in more than one place -/mob/proc/face_atom(atom/A) - if( buckled || stat != CONSCIOUS || !A || !x || !y || !A.x || !A.y ) - return - var/dx = A.x - x - var/dy = A.y - y - if(!dx && !dy) // Wall items are graphically shifted but on the floor - if(A.pixel_y > 16) - setDir(NORTH) - else if(A.pixel_y < -16) - setDir(SOUTH) - else if(A.pixel_x > 16) - setDir(EAST) - else if(A.pixel_x < -16) - setDir(WEST) - return - - if(abs(dx) < abs(dy)) - if(dy > 0) - setDir(NORTH) - else - setDir(SOUTH) - else - if(dx > 0) - setDir(EAST) - else - setDir(WEST) - -//debug -/obj/screen/proc/scale_to(x1,y1) - if(!y1) - y1 = x1 - var/matrix/M = new - M.Scale(x1,y1) - transform = M - -/obj/screen/click_catcher - icon = 'icons/mob/screen_gen.dmi' - icon_state = "catcher" - plane = CLICKCATCHER_PLANE - mouse_opacity = MOUSE_OPACITY_OPAQUE - screen_loc = "CENTER" - -#define MAX_SAFE_BYOND_ICON_SCALE_TILES (MAX_SAFE_BYOND_ICON_SCALE_PX / world.icon_size) -#define MAX_SAFE_BYOND_ICON_SCALE_PX (33 * 32) //Not using world.icon_size on purpose. - -/obj/screen/click_catcher/proc/UpdateGreed(view_size_x = 15, view_size_y = 15) - var/icon/newicon = icon('icons/mob/screen_gen.dmi', "catcher") - var/ox = min(MAX_SAFE_BYOND_ICON_SCALE_TILES, view_size_x) - var/oy = min(MAX_SAFE_BYOND_ICON_SCALE_TILES, view_size_y) - var/px = view_size_x * world.icon_size - var/py = view_size_y * world.icon_size - var/sx = min(MAX_SAFE_BYOND_ICON_SCALE_PX, px) - var/sy = min(MAX_SAFE_BYOND_ICON_SCALE_PX, py) - newicon.Scale(sx, sy) - icon = newicon - screen_loc = "CENTER-[(ox-1)*0.5],CENTER-[(oy-1)*0.5]" - var/matrix/M = new - M.Scale(px/sx, py/sy) - transform = M - -/obj/screen/click_catcher/Click(location, control, params) - var/list/modifiers = params2list(params) - if(modifiers["middle"] && iscarbon(usr)) - var/mob/living/carbon/C = usr - C.swap_hand() - else - var/turf/T = params2turf(modifiers["screen-loc"], get_turf(usr.client ? usr.client.eye : usr), usr.client) - params += "&catcher=1" - if(T) - T.Click(location, control, params) - . = 1 - -/// MouseWheelOn -/mob/proc/MouseWheelOn(atom/A, delta_x, delta_y, params) - return - -/mob/dead/observer/MouseWheelOn(atom/A, delta_x, delta_y, params) - var/list/modifier = params2list(params) - if(modifier["shift"]) - var/view = 0 - if(delta_y > 0) - view = -1 - else - view = 1 - add_view_range(view) - -/mob/proc/check_click_intercept(params,A) - //Client level intercept - if(client && client.click_intercept) - if(call(client.click_intercept, "InterceptClickOn")(src, params, A)) - return TRUE - - //Mob level intercept - if(click_intercept) - if(call(click_intercept, "InterceptClickOn")(src, params, A)) - return TRUE - - return FALSE +/* + Click code cleanup + ~Sayu +*/ + +// 1 decisecond click delay (above and beyond mob/next_move) +//This is mainly modified by click code, to modify click delays elsewhere, use next_move and changeNext_move() +/mob/var/next_click = 0 + +// THESE DO NOT EFFECT THE BASE 1 DECISECOND DELAY OF NEXT_CLICK +/mob/var/next_move_adjust = 0 //Amount to adjust action/click delays by, + or - +/mob/var/next_move_modifier = 1 //Value to multiply action/click delays by + + +//Delays the mob's next click/action by num deciseconds +// eg: 10-3 = 7 deciseconds of delay +// eg: 10*0.5 = 5 deciseconds of delay +// DOES NOT EFFECT THE BASE 1 DECISECOND DELAY OF NEXT_CLICK + +/mob/proc/changeNext_move(num) + next_move = world.time + ((num+next_move_adjust)*next_move_modifier) + +/mob/living/changeNext_move(num) + var/mod = next_move_modifier + var/adj = next_move_adjust + for(var/i in status_effects) + var/datum/status_effect/S = i + mod *= S.nextmove_modifier() + adj += S.nextmove_adjust() + next_move = world.time + ((num + adj)*mod) + +/** + * Before anything else, defer these calls to a per-mobtype handler. This allows us to + * remove istype() spaghetti code, but requires the addition of other handler procs to simplify it. + * + * Alternately, you could hardcode every mob's variation in a flat [/mob/proc/ClickOn] proc; however, + * that's a lot of code duplication and is hard to maintain. + * + * Note that this proc can be overridden, and is in the case of screen objects. + */ +/atom/Click(location,control,params) + if(flags_1 & INITIALIZED_1) + SEND_SIGNAL(src, COMSIG_CLICK, location, control, params, usr) + usr.ClickOn(src, params) + +/atom/DblClick(location,control,params) + if(flags_1 & INITIALIZED_1) + usr.DblClickOn(src,params) + +/atom/MouseWheel(delta_x,delta_y,location,control,params) + if(flags_1 & INITIALIZED_1) + usr.MouseWheelOn(src, delta_x, delta_y, params) + +/** + * Standard mob ClickOn() + * Handles exceptions: Buildmode, middle click, modified clicks, mech actions + * + * After that, mostly just check your state, check whether you're holding an item, + * check whether you're adjacent to the target, then pass off the click to whoever + * is receiving it. + * The most common are: + * * [mob/proc/UnarmedAttack] (atom,adjacent) - used here only when adjacent, with no item in hand; in the case of humans, checks gloves + * * [atom/proc/attackby] (item,user) - used only when adjacent + * * [obj/item/proc/afterattack] (atom,user,adjacent,params) - used both ranged and adjacent + * * [mob/proc/RangedAttack] (atom,params) - used only ranged, only used for tk and laser eyes but could be changed + */ +/mob/proc/ClickOn( atom/A, params ) + if(world.time <= next_click) + return + next_click = world.time + 1 + + if(check_click_intercept(params,A)) + return + + if(notransform) + return + + if(SEND_SIGNAL(src, COMSIG_MOB_CLICKON, A, params) & COMSIG_MOB_CANCEL_CLICKON) + return + + var/list/modifiers = params2list(params) + if(modifiers["shift"] && modifiers["middle"]) + ShiftMiddleClickOn(A) + return + if(modifiers["shift"] && modifiers["ctrl"]) + CtrlShiftClickOn(A) + return + if(modifiers["middle"]) + MiddleClickOn(A) + return + if(modifiers["shift"]) + ShiftClickOn(A) + return + if(modifiers["alt"]) // alt and alt-gr (rightalt) + AltClickOn(A) + return + if(modifiers["ctrl"]) + CtrlClickOn(A) + return + + if(incapacitated(ignore_restraints = 1)) + return + + face_atom(A) + + if(next_move > world.time) // in the year 2000... + return + + if(!modifiers["catcher"] && A.IsObscured()) + return + + if(ismecha(loc)) + var/obj/mecha/M = loc + return M.click_action(A,src,params) + + if(restrained()) + changeNext_move(CLICK_CD_HANDCUFFED) //Doing shit in cuffs shall be vey slow + RestrainedClickOn(A) + return + + if(in_throw_mode) + throw_item(A) + return + + var/obj/item/W = get_active_held_item() + + if(W == A) + W.attack_self(src) + update_inv_hands() + return + + //These are always reachable. + //User itself, current loc, and user inventory + if(A in DirectAccess()) + if(W) + W.melee_attack_chain(src, A, params) + else + if(ismob(A)) + changeNext_move(CLICK_CD_MELEE) + UnarmedAttack(A) + return + + //Can't reach anything else in lockers or other weirdness + if(!loc.AllowClick()) + return + + //Standard reach turf to turf or reaching inside storage + if(CanReach(A,W)) + if(W) + W.melee_attack_chain(src, A, params) + else + if(ismob(A)) + changeNext_move(CLICK_CD_MELEE) + UnarmedAttack(A,1) + else + if(W) + W.afterattack(A,src,0,params) + else + RangedAttack(A,params) + +/// Is the atom obscured by a PREVENT_CLICK_UNDER_1 object above it +/atom/proc/IsObscured() + SHOULD_BE_PURE(TRUE) + if(!isturf(loc)) //This only makes sense for things directly on turfs for now + return FALSE + var/turf/T = get_turf_pixel(src) + if(!T) + return FALSE + for(var/atom/movable/AM in T) + if(AM.flags_1 & PREVENT_CLICK_UNDER_1 && AM.density && (AM.layer > layer || (T.intact && HAS_TRAIT(AM, TRAIT_T_RAY_VISIBLE)))) + return TRUE + return FALSE + +/turf/IsObscured() + for(var/item in src) + var/atom/movable/AM = item + if(AM.flags_1 & PREVENT_CLICK_UNDER_1) + return TRUE + return FALSE + +/** + * A backwards depth-limited breadth-first-search to see if the target is + * logically "in" anything adjacent to us. + */ +/atom/movable/proc/CanReach(atom/ultimate_target, obj/item/tool, view_only = FALSE) + var/list/direct_access = DirectAccess() + var/depth = 1 + (view_only ? STORAGE_VIEW_DEPTH : INVENTORY_DEPTH) + + var/list/closed = list() + var/list/checking = list(ultimate_target) + while (checking.len && depth > 0) + var/list/next = list() + --depth + + for(var/atom/target in checking) // will filter out nulls + if(closed[target] || isarea(target)) // avoid infinity situations + continue + closed[target] = TRUE + if(isturf(target) || isturf(target.loc) || (target in direct_access)) //Directly accessible atoms + if(Adjacent(target) || (tool && CheckToolReach(src, target, tool.reach))) //Adjacent or reaching attacks + return TRUE + + if (!target.loc) + continue + + if(!(SEND_SIGNAL(target.loc, COMSIG_ATOM_CANREACH, next) & COMPONENT_BLOCK_REACH)) + next += target.loc + + checking = next + return FALSE + +/atom/movable/proc/DirectAccess() + return list(src, loc) + +/mob/DirectAccess(atom/target) + return ..() + contents + +/mob/living/DirectAccess(atom/target) + return ..() + GetAllContents() + +/atom/proc/AllowClick() + return FALSE + +/turf/AllowClick() + return TRUE + +/proc/CheckToolReach(atom/movable/here, atom/movable/there, reach) + if(!here || !there) + return + switch(reach) + if(0) + return FALSE + if(1) + return FALSE //here.Adjacent(there) + if(2 to INFINITY) + var/obj/dummy = new(get_turf(here)) + dummy.pass_flags |= PASSTABLE + dummy.invisibility = INVISIBILITY_ABSTRACT + for(var/i in 1 to reach) //Limit it to that many tries + var/turf/T = get_step(dummy, get_dir(dummy, there)) + if(dummy.CanReach(there)) + qdel(dummy) + return TRUE + if(!dummy.Move(T)) //we're blocked! + qdel(dummy) + return + qdel(dummy) + +/// Default behavior: ignore double clicks (the second click that makes the doubleclick call already calls for a normal click) +/mob/proc/DblClickOn(atom/A, params) + return + + +/** + * Translates into [atom/proc/attack_hand], etc. + * + * Note: proximity_flag here is used to distinguish between normal usage (flag=1), + * and usage when clicking on things telekinetically (flag=0). This proc will + * not be called at ranged except with telekinesis. + * + * proximity_flag is not currently passed to attack_hand, and is instead used + * in human click code to allow glove touches only at melee range. + */ +/mob/proc/UnarmedAttack(atom/A, proximity_flag) + if(ismob(A)) + changeNext_move(CLICK_CD_MELEE) + return + +/** + * Ranged unarmed attack: + * + * This currently is just a default for all mobs, involving + * laser eyes and telekinesis. You could easily add exceptions + * for things like ranged glove touches, spitting alien acid/neurotoxin, + * animals lunging, etc. + */ +/mob/proc/RangedAttack(atom/A, params) + SEND_SIGNAL(src, COMSIG_MOB_ATTACK_RANGED, A, params) +/** + * Restrained ClickOn + * + * Used when you are handcuffed and click things. + * Not currently used by anything but could easily be. + */ +/mob/proc/RestrainedClickOn(atom/A) + return + +/** + * Middle click + * Mainly used for swapping hands + */ +/mob/proc/MiddleClickOn(atom/A) + . = SEND_SIGNAL(src, COMSIG_MOB_MIDDLECLICKON, A) + if(. & COMSIG_MOB_CANCEL_CLICKON) + return + swap_hand() + +/** + * Shift click + * For most mobs, examine. + * This is overridden in ai.dm + */ +/mob/proc/ShiftClickOn(atom/A) + A.ShiftClick(src) + return + +/atom/proc/ShiftClick(mob/user) + var/flags = SEND_SIGNAL(src, COMSIG_CLICK_SHIFT, user) + if(user.client && (user.client.eye == user || user.client.eye == user.loc || flags & COMPONENT_ALLOW_EXAMINATE)) + user.examinate(src) + return + +/** + * Ctrl click + * For most objects, pull + */ +/mob/proc/CtrlClickOn(atom/A) + A.CtrlClick(src) + return + +/atom/proc/CtrlClick(mob/user) + SEND_SIGNAL(src, COMSIG_CLICK_CTRL, user) + var/mob/living/ML = user + if(istype(ML)) + ML.pulled(src) + +/mob/living/carbon/human/CtrlClick(mob/user) + if(ishuman(user) && Adjacent(user) && !user.incapacitated()) + if(world.time < user.next_move) + return FALSE + var/mob/living/carbon/human/H = user + H.dna.species.grab(H, src, H.mind.martial_art) + H.changeNext_move(CLICK_CD_MELEE) + else + ..() +/** + * Alt click + * Unused except for AI + */ +/mob/proc/AltClickOn(atom/A) + . = SEND_SIGNAL(src, COMSIG_MOB_ALTCLICKON, A) + if(. & COMSIG_MOB_CANCEL_CLICKON) + return + A.AltClick(src) + +/atom/proc/AltClick(mob/user) + SEND_SIGNAL(src, COMSIG_CLICK_ALT, user) + var/turf/T = get_turf(src) + if(T && (isturf(loc) || isturf(src)) && user.TurfAdjacent(T)) + user.listed_turf = T + user.client.statpanel = T.name + +/// Use this instead of [/mob/proc/AltClickOn] where you only want turf content listing without additional atom alt-click interaction +/atom/proc/AltClickNoInteract(mob/user, atom/A) + var/turf/T = get_turf(A) + if(T && user.TurfAdjacent(T)) + user.listed_turf = T + user.client.statpanel = T.name + +/mob/proc/TurfAdjacent(turf/T) + return T.Adjacent(src) + +/** + * Control+Shift click + * Unused except for AI + */ +/mob/proc/CtrlShiftClickOn(atom/A) + A.CtrlShiftClick(src) + return + +/mob/proc/ShiftMiddleClickOn(atom/A) + src.pointed(A) + return + +/atom/proc/CtrlShiftClick(mob/user) + SEND_SIGNAL(src, COMSIG_CLICK_CTRL_SHIFT) + return + +/* + Misc helpers + face_atom: turns the mob towards what you clicked on +*/ + +/// Simple helper to face what you clicked on, in case it should be needed in more than one place +/mob/proc/face_atom(atom/A) + if( buckled || stat != CONSCIOUS || !A || !x || !y || !A.x || !A.y ) + return + var/dx = A.x - x + var/dy = A.y - y + if(!dx && !dy) // Wall items are graphically shifted but on the floor + if(A.pixel_y > 16) + setDir(NORTH) + else if(A.pixel_y < -16) + setDir(SOUTH) + else if(A.pixel_x > 16) + setDir(EAST) + else if(A.pixel_x < -16) + setDir(WEST) + return + + if(abs(dx) < abs(dy)) + if(dy > 0) + setDir(NORTH) + else + setDir(SOUTH) + else + if(dx > 0) + setDir(EAST) + else + setDir(WEST) + +//debug +/obj/screen/proc/scale_to(x1,y1) + if(!y1) + y1 = x1 + var/matrix/M = new + M.Scale(x1,y1) + transform = M + +/obj/screen/click_catcher + icon = 'icons/mob/screen_gen.dmi' + icon_state = "catcher" + plane = CLICKCATCHER_PLANE + mouse_opacity = MOUSE_OPACITY_OPAQUE + screen_loc = "CENTER" + +#define MAX_SAFE_BYOND_ICON_SCALE_TILES (MAX_SAFE_BYOND_ICON_SCALE_PX / world.icon_size) +#define MAX_SAFE_BYOND_ICON_SCALE_PX (33 * 32) //Not using world.icon_size on purpose. + +/obj/screen/click_catcher/proc/UpdateGreed(view_size_x = 15, view_size_y = 15) + var/icon/newicon = icon('icons/mob/screen_gen.dmi', "catcher") + var/ox = min(MAX_SAFE_BYOND_ICON_SCALE_TILES, view_size_x) + var/oy = min(MAX_SAFE_BYOND_ICON_SCALE_TILES, view_size_y) + var/px = view_size_x * world.icon_size + var/py = view_size_y * world.icon_size + var/sx = min(MAX_SAFE_BYOND_ICON_SCALE_PX, px) + var/sy = min(MAX_SAFE_BYOND_ICON_SCALE_PX, py) + newicon.Scale(sx, sy) + icon = newicon + screen_loc = "CENTER-[(ox-1)*0.5],CENTER-[(oy-1)*0.5]" + var/matrix/M = new + M.Scale(px/sx, py/sy) + transform = M + +/obj/screen/click_catcher/Click(location, control, params) + var/list/modifiers = params2list(params) + if(modifiers["middle"] && iscarbon(usr)) + var/mob/living/carbon/C = usr + C.swap_hand() + else + var/turf/T = params2turf(modifiers["screen-loc"], get_turf(usr.client ? usr.client.eye : usr), usr.client) + params += "&catcher=1" + if(T) + T.Click(location, control, params) + . = 1 + +/// MouseWheelOn +/mob/proc/MouseWheelOn(atom/A, delta_x, delta_y, params) + return + +/mob/dead/observer/MouseWheelOn(atom/A, delta_x, delta_y, params) + var/list/modifier = params2list(params) + if(modifier["shift"]) + var/view = 0 + if(delta_y > 0) + view = -1 + else + view = 1 + add_view_range(view) + +/mob/proc/check_click_intercept(params,A) + //Client level intercept + if(client && client.click_intercept) + if(call(client.click_intercept, "InterceptClickOn")(src, params, A)) + return TRUE + + //Mob level intercept + if(click_intercept) + if(call(click_intercept, "InterceptClickOn")(src, params, A)) + return TRUE + + return FALSE diff --git a/code/_onclick/cyborg.dm b/code/_onclick/cyborg.dm index a113fad7857a..1b6da2a2b5e0 100644 --- a/code/_onclick/cyborg.dm +++ b/code/_onclick/cyborg.dm @@ -1,176 +1,176 @@ -/* - Cyborg ClickOn() - - Cyborgs have no range restriction on attack_robot(), because it is basically an AI click. - However, they do have a range restriction on item use, so they cannot do without the - adjacency code. -*/ - -/mob/living/silicon/robot/ClickOn(atom/A, params) - if(world.time <= next_click) - return - next_click = world.time + 1 - - if(check_click_intercept(params,A)) - return - - if(stat || lockcharge || IsParalyzed() || IsStun() || IsUnconscious()) - return - - var/list/modifiers = params2list(params) - if(modifiers["shift"] && modifiers["ctrl"]) - CtrlShiftClickOn(A) - return - if(modifiers["shift"] && modifiers["middle"]) - ShiftMiddleClickOn(A) - return - if(modifiers["middle"]) - MiddleClickOn(A) - return - if(modifiers["shift"]) - ShiftClickOn(A) - return - if(modifiers["alt"]) // alt and alt-gr (rightalt) - AltClickOn(A) - return - if(modifiers["ctrl"]) - CtrlClickOn(A) - return - - if(next_move >= world.time) - return - - face_atom(A) // change direction to face what you clicked on - - /* - cyborg restrained() currently does nothing - if(restrained()) - RestrainedClickOn(A) - return - */ - if(aicamera.in_camera_mode) //Cyborg picture taking - aicamera.camera_mode_off() - aicamera.captureimage(A, usr) - return - - var/obj/item/W = get_active_held_item() - - if(!W && get_dist(src,A) <= interaction_range) - A.attack_robot(src) - return - - if(W) - // buckled cannot prevent machine interlinking but stops arm movement - if( buckled || incapacitated()) - return - - if(W == A) - W.attack_self(src) - return - - // cyborgs are prohibited from using storage items so we can I think safely remove (A.loc in contents) - if(A == loc || (A in loc) || (A in contents)) - W.melee_attack_chain(src, A, params) - return - - if(!isturf(loc)) - return - - // cyborgs are prohibited from using storage items so we can I think safely remove (A.loc && isturf(A.loc.loc)) - if(isturf(A) || isturf(A.loc)) - if(A.Adjacent(src)) // see adjacent.dm - W.melee_attack_chain(src, A, params) - return - else - W.afterattack(A, src, 0, params) - return - -//Middle click cycles through selected modules. -/mob/living/silicon/robot/MiddleClickOn(atom/A) - . = ..() - cycle_modules() - -//Give cyborgs hotkey clicks without breaking existing uses of hotkey clicks -// for non-doors/apcs -/mob/living/silicon/robot/CtrlShiftClickOn(atom/A) - A.BorgCtrlShiftClick(src) -/mob/living/silicon/robot/ShiftClickOn(atom/A) - A.BorgShiftClick(src) -/mob/living/silicon/robot/CtrlClickOn(atom/A) - A.BorgCtrlClick(src) -/mob/living/silicon/robot/AltClickOn(atom/A) - A.BorgAltClick(src) - -/atom/proc/BorgCtrlShiftClick(mob/living/silicon/robot/user) //forward to human click if not overridden - CtrlShiftClick(user) - -/obj/machinery/door/airlock/BorgCtrlShiftClick(mob/living/silicon/robot/user) // Sets/Unsets Emergency Access Override Forwards to AI code. - if(get_dist(src,user) <= user.interaction_range) - AICtrlShiftClick() - else - ..() - - -/atom/proc/BorgShiftClick(mob/living/silicon/robot/user) //forward to human click if not overridden - ShiftClick(user) - -/obj/machinery/door/airlock/BorgShiftClick(mob/living/silicon/robot/user) // Opens and closes doors! Forwards to AI code. - if(get_dist(src,user) <= user.interaction_range) - AIShiftClick() - else - ..() - - -/atom/proc/BorgCtrlClick(mob/living/silicon/robot/user) //forward to human click if not overridden - CtrlClick(user) - -/obj/machinery/door/airlock/BorgCtrlClick(mob/living/silicon/robot/user) // Bolts doors. Forwards to AI code. - if(get_dist(src,user) <= user.interaction_range) - AICtrlClick() - else - ..() - -/obj/machinery/power/apc/BorgCtrlClick(mob/living/silicon/robot/user) // turns off/on APCs. Forwards to AI code. - if(get_dist(src,user) <= user.interaction_range) - AICtrlClick() - else - ..() - -/obj/machinery/turretid/BorgCtrlClick(mob/living/silicon/robot/user) //turret control on/off. Forwards to AI code. - if(get_dist(src,user) <= user.interaction_range) - AICtrlClick() - else - ..() - -/atom/proc/BorgAltClick(mob/living/silicon/robot/user) - AltClick(user) - return - -/obj/machinery/door/airlock/BorgAltClick(mob/living/silicon/robot/user) // Eletrifies doors. Forwards to AI code. - if(get_dist(src,user) <= user.interaction_range) - AIAltClick() - else - ..() - -/obj/machinery/turretid/BorgAltClick(mob/living/silicon/robot/user) //turret lethal on/off. Forwards to AI code. - if(get_dist(src,user) <= user.interaction_range) - AIAltClick() - else - ..() - -/* - As with AI, these are not used in click code, - because the code for robots is specific, not generic. - - If you would like to add advanced features to robot - clicks, you can do so here, but you will have to - change attack_robot() above to the proper function -*/ -/mob/living/silicon/robot/UnarmedAttack(atom/A) - A.attack_robot(src) -/mob/living/silicon/robot/RangedAttack(atom/A) - A.attack_robot(src) - -/atom/proc/attack_robot(mob/user) - attack_ai(user) - return +/* + Cyborg ClickOn() + + Cyborgs have no range restriction on attack_robot(), because it is basically an AI click. + However, they do have a range restriction on item use, so they cannot do without the + adjacency code. +*/ + +/mob/living/silicon/robot/ClickOn(atom/A, params) + if(world.time <= next_click) + return + next_click = world.time + 1 + + if(check_click_intercept(params,A)) + return + + if(stat || lockcharge || IsParalyzed() || IsStun() || IsUnconscious()) + return + + var/list/modifiers = params2list(params) + if(modifiers["shift"] && modifiers["ctrl"]) + CtrlShiftClickOn(A) + return + if(modifiers["shift"] && modifiers["middle"]) + ShiftMiddleClickOn(A) + return + if(modifiers["middle"]) + MiddleClickOn(A) + return + if(modifiers["shift"]) + ShiftClickOn(A) + return + if(modifiers["alt"]) // alt and alt-gr (rightalt) + AltClickOn(A) + return + if(modifiers["ctrl"]) + CtrlClickOn(A) + return + + if(next_move >= world.time) + return + + face_atom(A) // change direction to face what you clicked on + + /* + cyborg restrained() currently does nothing + if(restrained()) + RestrainedClickOn(A) + return + */ + if(aicamera.in_camera_mode) //Cyborg picture taking + aicamera.camera_mode_off() + aicamera.captureimage(A, usr) + return + + var/obj/item/W = get_active_held_item() + + if(!W && get_dist(src,A) <= interaction_range) + A.attack_robot(src) + return + + if(W) + // buckled cannot prevent machine interlinking but stops arm movement + if( buckled || incapacitated()) + return + + if(W == A) + W.attack_self(src) + return + + // cyborgs are prohibited from using storage items so we can I think safely remove (A.loc in contents) + if(A == loc || (A in loc) || (A in contents)) + W.melee_attack_chain(src, A, params) + return + + if(!isturf(loc)) + return + + // cyborgs are prohibited from using storage items so we can I think safely remove (A.loc && isturf(A.loc.loc)) + if(isturf(A) || isturf(A.loc)) + if(A.Adjacent(src)) // see adjacent.dm + W.melee_attack_chain(src, A, params) + return + else + W.afterattack(A, src, 0, params) + return + +//Middle click cycles through selected modules. +/mob/living/silicon/robot/MiddleClickOn(atom/A) + . = ..() + cycle_modules() + +//Give cyborgs hotkey clicks without breaking existing uses of hotkey clicks +// for non-doors/apcs +/mob/living/silicon/robot/CtrlShiftClickOn(atom/A) + A.BorgCtrlShiftClick(src) +/mob/living/silicon/robot/ShiftClickOn(atom/A) + A.BorgShiftClick(src) +/mob/living/silicon/robot/CtrlClickOn(atom/A) + A.BorgCtrlClick(src) +/mob/living/silicon/robot/AltClickOn(atom/A) + A.BorgAltClick(src) + +/atom/proc/BorgCtrlShiftClick(mob/living/silicon/robot/user) //forward to human click if not overridden + CtrlShiftClick(user) + +/obj/machinery/door/airlock/BorgCtrlShiftClick(mob/living/silicon/robot/user) // Sets/Unsets Emergency Access Override Forwards to AI code. + if(get_dist(src,user) <= user.interaction_range) + AICtrlShiftClick() + else + ..() + + +/atom/proc/BorgShiftClick(mob/living/silicon/robot/user) //forward to human click if not overridden + ShiftClick(user) + +/obj/machinery/door/airlock/BorgShiftClick(mob/living/silicon/robot/user) // Opens and closes doors! Forwards to AI code. + if(get_dist(src,user) <= user.interaction_range) + AIShiftClick() + else + ..() + + +/atom/proc/BorgCtrlClick(mob/living/silicon/robot/user) //forward to human click if not overridden + CtrlClick(user) + +/obj/machinery/door/airlock/BorgCtrlClick(mob/living/silicon/robot/user) // Bolts doors. Forwards to AI code. + if(get_dist(src,user) <= user.interaction_range) + AICtrlClick() + else + ..() + +/obj/machinery/power/apc/BorgCtrlClick(mob/living/silicon/robot/user) // turns off/on APCs. Forwards to AI code. + if(get_dist(src,user) <= user.interaction_range) + AICtrlClick() + else + ..() + +/obj/machinery/turretid/BorgCtrlClick(mob/living/silicon/robot/user) //turret control on/off. Forwards to AI code. + if(get_dist(src,user) <= user.interaction_range) + AICtrlClick() + else + ..() + +/atom/proc/BorgAltClick(mob/living/silicon/robot/user) + AltClick(user) + return + +/obj/machinery/door/airlock/BorgAltClick(mob/living/silicon/robot/user) // Eletrifies doors. Forwards to AI code. + if(get_dist(src,user) <= user.interaction_range) + AIAltClick() + else + ..() + +/obj/machinery/turretid/BorgAltClick(mob/living/silicon/robot/user) //turret lethal on/off. Forwards to AI code. + if(get_dist(src,user) <= user.interaction_range) + AIAltClick() + else + ..() + +/* + As with AI, these are not used in click code, + because the code for robots is specific, not generic. + + If you would like to add advanced features to robot + clicks, you can do so here, but you will have to + change attack_robot() above to the proper function +*/ +/mob/living/silicon/robot/UnarmedAttack(atom/A) + A.attack_robot(src) +/mob/living/silicon/robot/RangedAttack(atom/A) + A.attack_robot(src) + +/atom/proc/attack_robot(mob/user) + attack_ai(user) + return diff --git a/code/_onclick/drag_drop.dm b/code/_onclick/drag_drop.dm index 91f6da16a553..2912bc5c4536 100644 --- a/code/_onclick/drag_drop.dm +++ b/code/_onclick/drag_drop.dm @@ -1,133 +1,133 @@ -/* - MouseDrop: - - Called on the atom you're dragging. In a lot of circumstances we want to use the - receiving object instead, so that's the default action. This allows you to drag - almost anything into a trash can. -*/ -/atom/MouseDrop(atom/over, src_location, over_location, src_control, over_control, params) - if(!usr || !over) - return - if(SEND_SIGNAL(src, COMSIG_MOUSEDROP_ONTO, over, usr) & COMPONENT_NO_MOUSEDROP) //Whatever is receiving will verify themselves for adjacency. - return - if(over == src) - return usr.client.Click(src, src_location, src_control, params) - if(!Adjacent(usr) || !over.Adjacent(usr)) - return // should stop you from dragging through windows - - over.MouseDrop_T(src,usr) - return - -// receive a mousedrop -/atom/proc/MouseDrop_T(atom/dropping, mob/user) - SEND_SIGNAL(src, COMSIG_MOUSEDROPPED_ONTO, dropping, user) - return - - -/client - var/mouseControlObject = null - -/client/MouseDown(object, location, control, params) - if (mouse_down_icon) - mouse_pointer_icon = mouse_down_icon - var/delay = mob.CanMobAutoclick(object, location, params) - if(delay) - selected_target[1] = object - selected_target[2] = params - while(selected_target[1]) - Click(selected_target[1], location, control, selected_target[2]) - sleep(delay) - active_mousedown_item = mob.canMobMousedown(object, location, params) - if(active_mousedown_item) - active_mousedown_item.onMouseDown(object, location, params, mob) - -/client/MouseUp(object, location, control, params) - if (mouse_up_icon) - mouse_pointer_icon = mouse_up_icon - selected_target[1] = null - if(active_mousedown_item) - active_mousedown_item.onMouseUp(object, location, params, mob) - active_mousedown_item = null - -/mob/proc/CanMobAutoclick(object, location, params) - -/mob/living/carbon/CanMobAutoclick(atom/object, location, params) - if(!object.IsAutoclickable()) - return - var/obj/item/h = get_active_held_item() - if(h) - . = h.CanItemAutoclick(object, location, params) - -/mob/proc/canMobMousedown(atom/object, location, params) - -/mob/living/carbon/canMobMousedown(atom/object, location, params) - var/obj/item/H = get_active_held_item() - if(H) - . = H.canItemMouseDown(object, location, params) - -/obj/item/proc/CanItemAutoclick(object, location, params) - -/obj/item/proc/canItemMouseDown(object, location, params) - if(canMouseDown) - return src - -/obj/item/proc/onMouseDown(object, location, params, mob) - return - -/obj/item/proc/onMouseUp(object, location, params, mob) - return - -/obj/item/gun/CanItemAutoclick(object, location, params) - . = automatic - -/atom/proc/IsAutoclickable() - . = 1 - -/obj/screen/IsAutoclickable() - . = 0 - -/obj/screen/click_catcher/IsAutoclickable() - . = 1 - -//Wasp Begin - Please fucking work for spacepod code -/client/MouseMove(object,location,control,params) - mouseParams = params - mouseLocation = location - mouseObject = object - mouseControlObject = control - if(mob && LAZYLEN(mob.mousemove_intercept_objects)) - for(var/datum/D in mob.mousemove_intercept_objects) - D.onMouseMove(object, location, control, params) - ..() -/datum/proc/onMouseMove(object, location, control, params) - return -//Wasp End - -/client/MouseDrag(src_object,atom/over_object,src_location,over_location,src_control,over_control,params) - var/list/L = params2list(params) - if (L["middle"]) - if (src_object && src_location != over_location) - middragtime = world.time - middragatom = src_object - else - middragtime = 0 - middragatom = null - mouseParams = params - mouseLocation = over_location - mouseObject = over_object - mouseControlObject = over_control - if(selected_target[1] && over_object && over_object.IsAutoclickable()) - selected_target[1] = over_object - selected_target[2] = params - if(active_mousedown_item) - active_mousedown_item.onMouseDrag(src_object, over_object, src_location, over_location, params, mob) - - -/obj/item/proc/onMouseDrag(src_object, over_object, src_location, over_location, params, mob) - return - -/client/MouseDrop(src_object, over_object, src_location, over_location, src_control, over_control, params) - if (middragatom == src_object) - middragtime = 0 - middragatom = null - ..() +/* + MouseDrop: + + Called on the atom you're dragging. In a lot of circumstances we want to use the + receiving object instead, so that's the default action. This allows you to drag + almost anything into a trash can. +*/ +/atom/MouseDrop(atom/over, src_location, over_location, src_control, over_control, params) + if(!usr || !over) + return + if(SEND_SIGNAL(src, COMSIG_MOUSEDROP_ONTO, over, usr) & COMPONENT_NO_MOUSEDROP) //Whatever is receiving will verify themselves for adjacency. + return + if(over == src) + return usr.client.Click(src, src_location, src_control, params) + if(!Adjacent(usr) || !over.Adjacent(usr)) + return // should stop you from dragging through windows + + over.MouseDrop_T(src,usr) + return + +// receive a mousedrop +/atom/proc/MouseDrop_T(atom/dropping, mob/user) + SEND_SIGNAL(src, COMSIG_MOUSEDROPPED_ONTO, dropping, user) + return + + +/client + var/mouseControlObject = null + +/client/MouseDown(object, location, control, params) + if (mouse_down_icon) + mouse_pointer_icon = mouse_down_icon + var/delay = mob.CanMobAutoclick(object, location, params) + if(delay) + selected_target[1] = object + selected_target[2] = params + while(selected_target[1]) + Click(selected_target[1], location, control, selected_target[2]) + sleep(delay) + active_mousedown_item = mob.canMobMousedown(object, location, params) + if(active_mousedown_item) + active_mousedown_item.onMouseDown(object, location, params, mob) + +/client/MouseUp(object, location, control, params) + if (mouse_up_icon) + mouse_pointer_icon = mouse_up_icon + selected_target[1] = null + if(active_mousedown_item) + active_mousedown_item.onMouseUp(object, location, params, mob) + active_mousedown_item = null + +/mob/proc/CanMobAutoclick(object, location, params) + +/mob/living/carbon/CanMobAutoclick(atom/object, location, params) + if(!object.IsAutoclickable()) + return + var/obj/item/h = get_active_held_item() + if(h) + . = h.CanItemAutoclick(object, location, params) + +/mob/proc/canMobMousedown(atom/object, location, params) + +/mob/living/carbon/canMobMousedown(atom/object, location, params) + var/obj/item/H = get_active_held_item() + if(H) + . = H.canItemMouseDown(object, location, params) + +/obj/item/proc/CanItemAutoclick(object, location, params) + +/obj/item/proc/canItemMouseDown(object, location, params) + if(canMouseDown) + return src + +/obj/item/proc/onMouseDown(object, location, params, mob) + return + +/obj/item/proc/onMouseUp(object, location, params, mob) + return + +/obj/item/gun/CanItemAutoclick(object, location, params) + . = automatic + +/atom/proc/IsAutoclickable() + . = 1 + +/obj/screen/IsAutoclickable() + . = 0 + +/obj/screen/click_catcher/IsAutoclickable() + . = 1 + +//Wasp Begin - Please fucking work for spacepod code +/client/MouseMove(object,location,control,params) + mouseParams = params + mouseLocation = location + mouseObject = object + mouseControlObject = control + if(mob && LAZYLEN(mob.mousemove_intercept_objects)) + for(var/datum/D in mob.mousemove_intercept_objects) + D.onMouseMove(object, location, control, params) + ..() +/datum/proc/onMouseMove(object, location, control, params) + return +//Wasp End + +/client/MouseDrag(src_object,atom/over_object,src_location,over_location,src_control,over_control,params) + var/list/L = params2list(params) + if (L["middle"]) + if (src_object && src_location != over_location) + middragtime = world.time + middragatom = src_object + else + middragtime = 0 + middragatom = null + mouseParams = params + mouseLocation = over_location + mouseObject = over_object + mouseControlObject = over_control + if(selected_target[1] && over_object && over_object.IsAutoclickable()) + selected_target[1] = over_object + selected_target[2] = params + if(active_mousedown_item) + active_mousedown_item.onMouseDrag(src_object, over_object, src_location, over_location, params, mob) + + +/obj/item/proc/onMouseDrag(src_object, over_object, src_location, over_location, params, mob) + return + +/client/MouseDrop(src_object, over_object, src_location, over_location, src_control, over_control, params) + if (middragatom == src_object) + middragtime = 0 + middragatom = null + ..() diff --git a/code/_onclick/hud/_defines.dm b/code/_onclick/hud/_defines.dm index 492d05c166eb..d8ae8fb4e8af 100644 --- a/code/_onclick/hud/_defines.dm +++ b/code/_onclick/hud/_defines.dm @@ -1,173 +1,173 @@ -/* - These defines specificy screen locations. For more information, see the byond documentation on the screen_loc var. - - The short version: - - Everything is encoded as strings because apparently that's how Byond rolls. - - "1,1" is the bottom left square of the user's screen. This aligns perfectly with the turf grid. - "1:2,3:4" is the square (1,3) with pixel offsets (+2, +4); slightly right and slightly above the turf grid. - Pixel offsets are used so you don't perfectly hide the turf under them, that would be crappy. - - In addition, the keywords NORTH, SOUTH, EAST, WEST and CENTER can be used to represent their respective - screen borders. NORTH-1, for example, is the row just below the upper edge. Useful if you want your - UI to scale with screen size. - - The size of the user's screen is defined by client.view (indirectly by world.view), in our case "15x15". - Therefore, the top right corner (except during admin shenanigans) is at "15,15" -*/ - -/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) - return"CENTER+[x_off]:16,SOUTH+[y_off]:5" - -/proc/ui_equip_position(mob/M) - var/y_off = round((M.held_items.len-1) / 2) //values based on old equip ui position (CENTER: +/-16,SOUTH+1:5) - return "CENTER:-16,SOUTH+[y_off+1]:5" - -/proc/ui_swaphand_position(mob/M, which = 1) //values based on old swaphand ui positions (CENTER: +/-16,SOUTH+1:5) - var/x_off = which == 1 ? -1 : 0 - 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" - -//Lower right, persistent menu -#define ui_drop_throw "EAST-1:28,SOUTH+1:7" -#define ui_above_movement "EAST-2:26,SOUTH+1:7" -#define ui_above_intent "EAST-3:24, SOUTH+1:7" -#define ui_movi "EAST-2:26,SOUTH:5" -#define ui_acti "EAST-3:24,SOUTH:5" -#define ui_zonesel "EAST-1:28,SOUTH:5" -#define ui_acti_alt "EAST-1:28,SOUTH:5" //alternative intent switcher for when the interface is hidden (F12) -#define ui_crafting "EAST-4:22,SOUTH:5" -#define ui_building "EAST-4:22,SOUTH:21" -#define ui_language_menu "EAST-4:6,SOUTH:21" -#define ui_skill_menu "EAST-4:22,SOUTH:5" - -//Upper-middle right (alerts) -#define ui_alert1 "EAST-1:28,CENTER+5:27" -#define ui_alert2 "EAST-1:28,CENTER+4:25" -#define ui_alert3 "EAST-1:28,CENTER+3:23" -#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:17" -#define ui_mood "EAST-1:28,CENTER-3:10" - -//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-3:15" -#define ui_living_healthdoll "EAST-1:28,CENTER-1:15" - -//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" - -//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_sensor "CENTER-3:16, SOUTH:5" -#define ui_borg_lamp "CENTER-4:16, SOUTH:5" -#define ui_borg_thrusters "CENTER-5: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_album "CENTER+4:21,SOUTH:5" -#define ui_borg_language_menu "CENTER+4:21,SOUTH+1:5" - -//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 -#define ui_construct_pull "EAST,CENTER-2:15" -#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" -#define ui_ai_camera_light "SOUTH:6,WEST+3" -#define ui_ai_crew_monitor "SOUTH:6,WEST+4" -#define ui_ai_crew_manifest "SOUTH:6,WEST+5" -#define ui_ai_alerts "SOUTH:6,WEST+6" -#define ui_ai_announcement "SOUTH:6,WEST+7" -#define ui_ai_shuttle "SOUTH:6,WEST+8" -#define ui_ai_state_laws "SOUTH:6,WEST+9" -#define ui_ai_pda_send "SOUTH:6,WEST+10" -#define ui_ai_pda_log "SOUTH:6,WEST+11" -#define ui_ai_take_picture "SOUTH:6,WEST+12" -#define ui_ai_view_images "SOUTH:6,WEST+13" -#define ui_ai_sensor "SOUTH:6,WEST+14" -#define ui_ai_multicam "SOUTH+1:6,WEST+13" -#define ui_ai_add_multicam "SOUTH+1:6,WEST+14" - -// 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 - REPLACED BY WASPSTATION _defines.dm -#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_pai "SOUTH: 6, CENTER+2:24" -End Waspstation*/ - -#define ui_wanted_lvl "NORTH,11" +/* + These defines specificy screen locations. For more information, see the byond documentation on the screen_loc var. + + The short version: + + Everything is encoded as strings because apparently that's how Byond rolls. + + "1,1" is the bottom left square of the user's screen. This aligns perfectly with the turf grid. + "1:2,3:4" is the square (1,3) with pixel offsets (+2, +4); slightly right and slightly above the turf grid. + Pixel offsets are used so you don't perfectly hide the turf under them, that would be crappy. + + In addition, the keywords NORTH, SOUTH, EAST, WEST and CENTER can be used to represent their respective + screen borders. NORTH-1, for example, is the row just below the upper edge. Useful if you want your + UI to scale with screen size. + + The size of the user's screen is defined by client.view (indirectly by world.view), in our case "15x15". + Therefore, the top right corner (except during admin shenanigans) is at "15,15" +*/ + +/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) + return"CENTER+[x_off]:16,SOUTH+[y_off]:5" + +/proc/ui_equip_position(mob/M) + var/y_off = round((M.held_items.len-1) / 2) //values based on old equip ui position (CENTER: +/-16,SOUTH+1:5) + return "CENTER:-16,SOUTH+[y_off+1]:5" + +/proc/ui_swaphand_position(mob/M, which = 1) //values based on old swaphand ui positions (CENTER: +/-16,SOUTH+1:5) + var/x_off = which == 1 ? -1 : 0 + 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" + +//Lower right, persistent menu +#define ui_drop_throw "EAST-1:28,SOUTH+1:7" +#define ui_above_movement "EAST-2:26,SOUTH+1:7" +#define ui_above_intent "EAST-3:24, SOUTH+1:7" +#define ui_movi "EAST-2:26,SOUTH:5" +#define ui_acti "EAST-3:24,SOUTH:5" +#define ui_zonesel "EAST-1:28,SOUTH:5" +#define ui_acti_alt "EAST-1:28,SOUTH:5" //alternative intent switcher for when the interface is hidden (F12) +#define ui_crafting "EAST-4:22,SOUTH:5" +#define ui_building "EAST-4:22,SOUTH:21" +#define ui_language_menu "EAST-4:6,SOUTH:21" +#define ui_skill_menu "EAST-4:22,SOUTH:5" + +//Upper-middle right (alerts) +#define ui_alert1 "EAST-1:28,CENTER+5:27" +#define ui_alert2 "EAST-1:28,CENTER+4:25" +#define ui_alert3 "EAST-1:28,CENTER+3:23" +#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:17" +#define ui_mood "EAST-1:28,CENTER-3:10" + +//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-3:15" +#define ui_living_healthdoll "EAST-1:28,CENTER-1:15" + +//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" + +//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_sensor "CENTER-3:16, SOUTH:5" +#define ui_borg_lamp "CENTER-4:16, SOUTH:5" +#define ui_borg_thrusters "CENTER-5: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_album "CENTER+4:21,SOUTH:5" +#define ui_borg_language_menu "CENTER+4:21,SOUTH+1:5" + +//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 +#define ui_construct_pull "EAST,CENTER-2:15" +#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" +#define ui_ai_camera_light "SOUTH:6,WEST+3" +#define ui_ai_crew_monitor "SOUTH:6,WEST+4" +#define ui_ai_crew_manifest "SOUTH:6,WEST+5" +#define ui_ai_alerts "SOUTH:6,WEST+6" +#define ui_ai_announcement "SOUTH:6,WEST+7" +#define ui_ai_shuttle "SOUTH:6,WEST+8" +#define ui_ai_state_laws "SOUTH:6,WEST+9" +#define ui_ai_pda_send "SOUTH:6,WEST+10" +#define ui_ai_pda_log "SOUTH:6,WEST+11" +#define ui_ai_take_picture "SOUTH:6,WEST+12" +#define ui_ai_view_images "SOUTH:6,WEST+13" +#define ui_ai_sensor "SOUTH:6,WEST+14" +#define ui_ai_multicam "SOUTH+1:6,WEST+13" +#define ui_ai_add_multicam "SOUTH+1:6,WEST+14" + +// 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 - REPLACED BY WASPSTATION _defines.dm +#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_pai "SOUTH: 6, CENTER+2:24" +End Waspstation*/ + +#define ui_wanted_lvl "NORTH,11" diff --git a/code/_onclick/hud/alien.dm b/code/_onclick/hud/alien.dm index 5e792d4fddc7..e8b0078be058 100644 --- a/code/_onclick/hud/alien.dm +++ b/code/_onclick/hud/alien.dm @@ -1,132 +1,132 @@ -/obj/screen/alien - icon = 'icons/mob/screen_alien.dmi' - -/obj/screen/alien/leap - name = "toggle leap" - icon_state = "leap_off" - -/obj/screen/alien/leap/Click() - if(isalienhunter(usr)) - var/mob/living/carbon/alien/humanoid/hunter/AH = usr - AH.toggle_leap() - -/obj/screen/alien/plasma_display - name = "plasma stored" - icon_state = "power_display" - screen_loc = ui_alienplasmadisplay - -/obj/screen/alien/alien_queen_finder - name = "queen sense" - desc = "Allows you to sense the general direction of your Queen." - icon_state = "queen_finder" - screen_loc = ui_alien_queen_finder - -/datum/hud/alien - ui_style = 'icons/mob/screen_alien.dmi' - -/datum/hud/alien/New(mob/living/carbon/alien/humanoid/owner) - ..() - - var/obj/screen/using - -//equippable shit - -//hands - build_hand_slots() - -//begin buttons - - using = new /obj/screen/swap_hand() - using.icon = ui_style - using.icon_state = "swap_1" - using.screen_loc = ui_swaphand_position(owner,1) - using.hud = src - static_inventory += using - - using = new /obj/screen/swap_hand() - using.icon = ui_style - using.icon_state = "swap_2" - using.screen_loc = ui_swaphand_position(owner,2) - using.hud = src - static_inventory += using - - using = new /obj/screen/act_intent/alien() - using.icon_state = mymob.a_intent - using.hud = src - static_inventory += using - action_intent = using - - if(isalienhunter(mymob)) - var/mob/living/carbon/alien/humanoid/hunter/H = mymob - H.leap_icon = new /obj/screen/alien/leap() - H.leap_icon.screen_loc = ui_alien_storage_r - static_inventory += H.leap_icon - - using = new/obj/screen/language_menu - using.screen_loc = ui_alien_language_menu - using.hud = src - static_inventory += using - - using = new /obj/screen/drop() - using.icon = ui_style - using.screen_loc = ui_drop_throw - using.hud = src - static_inventory += using - - using = new /obj/screen/resist() - using.icon = ui_style - using.screen_loc = ui_above_movement - using.hud = src - hotkeybuttons += using - - throw_icon = new /obj/screen/throw_catch() - throw_icon.icon = ui_style - throw_icon.screen_loc = ui_drop_throw - throw_icon.hud = src - hotkeybuttons += throw_icon - - pull_icon = new /obj/screen/pull() - pull_icon.icon = ui_style - pull_icon.update_icon() - pull_icon.screen_loc = ui_above_movement - pull_icon.hud = src - static_inventory += pull_icon - -//begin indicators - - healths = new /obj/screen/healths/alien() - healths.hud = src - infodisplay += healths - - alien_plasma_display = new /obj/screen/alien/plasma_display() - alien_plasma_display.hud = src - infodisplay += alien_plasma_display - - if(!isalienqueen(mymob)) - alien_queen_finder = new /obj/screen/alien/alien_queen_finder - alien_queen_finder.hud = src - infodisplay += alien_queen_finder - - zone_select = new /obj/screen/zone_sel/alien() - zone_select.hud = src - zone_select.update_icon() - static_inventory += zone_select - - for(var/obj/screen/inventory/inv in (static_inventory + toggleable_inventory)) - if(inv.slot_id) - inv.hud = src - inv_slots[TOBITSHIFT(inv.slot_id) + 1] = inv - inv.update_icon() - -/datum/hud/alien/persistent_inventory_update() - if(!mymob) - return - var/mob/living/carbon/alien/humanoid/H = mymob - if(hud_version != HUD_STYLE_NOHUD) - for(var/obj/item/I in H.held_items) - I.screen_loc = ui_hand_position(H.get_held_index_of_item(I)) - H.client.screen += I - else - for(var/obj/item/I in H.held_items) - I.screen_loc = null - H.client.screen -= I +/obj/screen/alien + icon = 'icons/mob/screen_alien.dmi' + +/obj/screen/alien/leap + name = "toggle leap" + icon_state = "leap_off" + +/obj/screen/alien/leap/Click() + if(isalienhunter(usr)) + var/mob/living/carbon/alien/humanoid/hunter/AH = usr + AH.toggle_leap() + +/obj/screen/alien/plasma_display + name = "plasma stored" + icon_state = "power_display" + screen_loc = ui_alienplasmadisplay + +/obj/screen/alien/alien_queen_finder + name = "queen sense" + desc = "Allows you to sense the general direction of your Queen." + icon_state = "queen_finder" + screen_loc = ui_alien_queen_finder + +/datum/hud/alien + ui_style = 'icons/mob/screen_alien.dmi' + +/datum/hud/alien/New(mob/living/carbon/alien/humanoid/owner) + ..() + + var/obj/screen/using + +//equippable shit + +//hands + build_hand_slots() + +//begin buttons + + using = new /obj/screen/swap_hand() + using.icon = ui_style + using.icon_state = "swap_1" + using.screen_loc = ui_swaphand_position(owner,1) + using.hud = src + static_inventory += using + + using = new /obj/screen/swap_hand() + using.icon = ui_style + using.icon_state = "swap_2" + using.screen_loc = ui_swaphand_position(owner,2) + using.hud = src + static_inventory += using + + using = new /obj/screen/act_intent/alien() + using.icon_state = mymob.a_intent + using.hud = src + static_inventory += using + action_intent = using + + if(isalienhunter(mymob)) + var/mob/living/carbon/alien/humanoid/hunter/H = mymob + H.leap_icon = new /obj/screen/alien/leap() + H.leap_icon.screen_loc = ui_alien_storage_r + static_inventory += H.leap_icon + + using = new/obj/screen/language_menu + using.screen_loc = ui_alien_language_menu + using.hud = src + static_inventory += using + + using = new /obj/screen/drop() + using.icon = ui_style + using.screen_loc = ui_drop_throw + using.hud = src + static_inventory += using + + using = new /obj/screen/resist() + using.icon = ui_style + using.screen_loc = ui_above_movement + using.hud = src + hotkeybuttons += using + + throw_icon = new /obj/screen/throw_catch() + throw_icon.icon = ui_style + throw_icon.screen_loc = ui_drop_throw + throw_icon.hud = src + hotkeybuttons += throw_icon + + pull_icon = new /obj/screen/pull() + pull_icon.icon = ui_style + pull_icon.update_icon() + pull_icon.screen_loc = ui_above_movement + pull_icon.hud = src + static_inventory += pull_icon + +//begin indicators + + healths = new /obj/screen/healths/alien() + healths.hud = src + infodisplay += healths + + alien_plasma_display = new /obj/screen/alien/plasma_display() + alien_plasma_display.hud = src + infodisplay += alien_plasma_display + + if(!isalienqueen(mymob)) + alien_queen_finder = new /obj/screen/alien/alien_queen_finder + alien_queen_finder.hud = src + infodisplay += alien_queen_finder + + zone_select = new /obj/screen/zone_sel/alien() + zone_select.hud = src + zone_select.update_icon() + static_inventory += zone_select + + for(var/obj/screen/inventory/inv in (static_inventory + toggleable_inventory)) + if(inv.slot_id) + inv.hud = src + inv_slots[TOBITSHIFT(inv.slot_id) + 1] = inv + inv.update_icon() + +/datum/hud/alien/persistent_inventory_update() + if(!mymob) + return + var/mob/living/carbon/alien/humanoid/H = mymob + if(hud_version != HUD_STYLE_NOHUD) + for(var/obj/item/I in H.held_items) + I.screen_loc = ui_hand_position(H.get_held_index_of_item(I)) + H.client.screen += I + else + for(var/obj/item/I in H.held_items) + I.screen_loc = null + H.client.screen -= I diff --git a/code/_onclick/hud/alien_larva.dm b/code/_onclick/hud/alien_larva.dm index 45389ded3cfa..0e4f945df43a 100644 --- a/code/_onclick/hud/alien_larva.dm +++ b/code/_onclick/hud/alien_larva.dm @@ -1,37 +1,37 @@ -/datum/hud/larva - ui_style = 'icons/mob/screen_alien.dmi' - -/datum/hud/larva/New(mob/owner) - ..() - var/obj/screen/using - - using = new /obj/screen/act_intent/alien() - using.icon_state = mymob.a_intent - using.hud = src - static_inventory += using - action_intent = using - - healths = new /obj/screen/healths/alien() - healths.hud = src - infodisplay += healths - - alien_queen_finder = new /obj/screen/alien/alien_queen_finder() - alien_queen_finder.hud = src - infodisplay += alien_queen_finder - - pull_icon = new /obj/screen/pull() - pull_icon.icon = 'icons/mob/screen_alien.dmi' - pull_icon.update_icon() - pull_icon.screen_loc = ui_above_movement - pull_icon.hud = src - hotkeybuttons += pull_icon - - using = new/obj/screen/language_menu - using.screen_loc = ui_alien_language_menu - using.hud = src - static_inventory += using - - zone_select = new /obj/screen/zone_sel/alien() - zone_select.hud = src - zone_select.update_icon() - static_inventory += zone_select +/datum/hud/larva + ui_style = 'icons/mob/screen_alien.dmi' + +/datum/hud/larva/New(mob/owner) + ..() + var/obj/screen/using + + using = new /obj/screen/act_intent/alien() + using.icon_state = mymob.a_intent + using.hud = src + static_inventory += using + action_intent = using + + healths = new /obj/screen/healths/alien() + healths.hud = src + infodisplay += healths + + alien_queen_finder = new /obj/screen/alien/alien_queen_finder() + alien_queen_finder.hud = src + infodisplay += alien_queen_finder + + pull_icon = new /obj/screen/pull() + pull_icon.icon = 'icons/mob/screen_alien.dmi' + pull_icon.update_icon() + pull_icon.screen_loc = ui_above_movement + pull_icon.hud = src + hotkeybuttons += pull_icon + + using = new/obj/screen/language_menu + using.screen_loc = ui_alien_language_menu + using.hud = src + static_inventory += using + + zone_select = new /obj/screen/zone_sel/alien() + zone_select.hud = src + zone_select.update_icon() + static_inventory += zone_select diff --git a/code/_onclick/hud/credits.dm b/code/_onclick/hud/credits.dm index 4b83281cbbaa..74f406cafe67 100644 --- a/code/_onclick/hud/credits.dm +++ b/code/_onclick/hud/credits.dm @@ -1,94 +1,96 @@ -#define CREDIT_ROLL_SPEED 115 -#define CREDIT_SPAWN_SPEED 8 -#define CREDIT_ANIMATE_HEIGHT (13 * world.icon_size) -#define CREDIT_EASE_DURATION 20 - -GLOBAL_LIST(end_titles) - -/client/proc/RollCredits() - set waitfor = FALSE - if(!GLOB.end_titles) - GLOB.end_titles = SSticker.mode.generate_credit_text() - GLOB.end_titles += "

    Thanks for playing!

    " - LAZYINITLIST(credits) - var/list/_credits = credits - verbs += /client/proc/ClearCredits - _credits += new /obj/screen/credit/title_card(null, null, src, SSticker.mode.title_icon) - sleep(CREDIT_SPAWN_SPEED * 3) - for(var/I in GLOB.end_titles) - if(!credits) - return - _credits += new /obj/screen/credit(null, I, src) - sleep(CREDIT_SPAWN_SPEED) - sleep(CREDIT_ROLL_SPEED - CREDIT_SPAWN_SPEED) - ClearCredits() - verbs -= /client/proc/ClearCredits - -/client/proc/ClearCredits() - set name = "Hide Credits" - set category = "OOC" - verbs -= /client/proc/ClearCredits - QDEL_LIST(credits) - -/obj/screen/credit - icon_state = "blank" - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - alpha = 0 - screen_loc = "2,2" - layer = SPLASHSCREEN_LAYER - var/client/parent - var/matrix/target - -/obj/screen/credit/Initialize(mapload, credited, client/P) - . = ..() - parent = P - maptext = credited - maptext_height = world.icon_size * 2 - maptext_width = world.icon_size * 13 - var/matrix/M = matrix(transform) - M.Translate(0, CREDIT_ANIMATE_HEIGHT) - animate(src, transform = M, time = CREDIT_ROLL_SPEED) - target = M - animate(src, alpha = 255, time = CREDIT_EASE_DURATION, flags = ANIMATION_PARALLEL) - spawn(CREDIT_ROLL_SPEED - CREDIT_EASE_DURATION)//addtimer doesn't work for more time-critical operations - FadeOut() - QDEL_IN(src, CREDIT_ROLL_SPEED) - P.screen += src - -/obj/screen/credit/Destroy() - var/client/P = parent - if(parent) - P.screen -= src - LAZYREMOVE(P.credits, src) - parent = null - return ..() - -/obj/screen/credit/proc/FadeOut() - animate(src, alpha = 0, transform = target, time = CREDIT_EASE_DURATION) - -/obj/screen/credit/title_card - icon = 'icons/title_cards.dmi' - screen_loc = "4,1" - -/obj/screen/credit/title_card/Initialize(mapload, credited, client/P, title_icon_state) - icon_state = title_icon_state - . = ..() - maptext = null - -/* CURSE YOU BYOND, LET ME DO HTTPS REQUESTS -/proc/get_patrons() - var/list/patrons = list() - var/list/http[] = world.Export("https://www.patreon.com/api/campaigns/[]/pledges?include=patron.null") - - if (http) - var/status = text2num(http["STATUS"]) - - if (status == 200) - var/response = json_decode(file2text(http["CONTENT"])) - if (response) - for(var/item in response["included"]) - if(item["type"] == "user") - patrons |= user["attributes"]["full_name"] - - return patrons.len ? patrons : null -*/ +#define CREDIT_ROLL_SPEED 60 +#define CREDIT_SPAWN_SPEED 4 +#define CREDIT_ANIMATE_HEIGHT (16 * world.icon_size) //13 would cause credits to get stacked at the top of the screen, so we let them go past the top edge +#define CREDIT_EASE_DURATION 12 + +GLOBAL_LIST(end_titles) +GLOBAL_LIST_INIT(patrons, world.file2list("[global.config.directory]/patrons.txt")) + +/proc/RollCredits() + set waitfor = FALSE + if(!GLOB.end_titles) + GLOB.end_titles = SSticker.mode.generate_credit_text() + GLOB.end_titles += "
    " + GLOB.end_titles += "
    " + + if(GLOB.patrons.len) + GLOB.end_titles += "

    Thank you to our patrons!

    " + for(var/patron in GLOB.patrons) + GLOB.end_titles += "

    [sanitize(patron)]

    " + GLOB.end_titles += "
    " + GLOB.end_titles += "
    " + + var/list/contribs = get_contribs() + if(contribs.len) + GLOB.end_titles += "

    Top Code Contributors

    " + for(var/contrib in contribs) + GLOB.end_titles += "

    [sanitize(contrib)]

    " + GLOB.end_titles += "
    " + GLOB.end_titles += "
    " + + GLOB.end_titles += "

    Thanks for playing!

    " + for(var/client/C in GLOB.clients) + if(C.prefs.show_credits) + C.screen += new /obj/screen/credit/title_card(null, null, SSticker.mode.title_icon) + sleep(CREDIT_SPAWN_SPEED * 3) + for(var/i in 1 to GLOB.end_titles.len) + var/C = GLOB.end_titles[i] + if(!C) + continue + + create_credit(C) + sleep(CREDIT_SPAWN_SPEED) + + +/proc/create_credit(credit) + new /obj/screen/credit(null, credit) + +/obj/screen/credit + icon_state = "blank" + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + alpha = 0 + screen_loc = "2,2" + layer = SPLASHSCREEN_LAYER + var/matrix/target + +/obj/screen/credit/Initialize(mapload, credited) + . = ..() + maptext = "[credited]" + maptext_height = world.icon_size * 2 + maptext_width = world.icon_size * 13 + var/matrix/M = matrix(transform) + M.Translate(0, CREDIT_ANIMATE_HEIGHT) + animate(src, transform = M, time = CREDIT_ROLL_SPEED) + target = M + animate(src, alpha = 255, time = CREDIT_EASE_DURATION, flags = ANIMATION_PARALLEL) + INVOKE_ASYNC(src, .proc/add_to_clients) + QDEL_IN(src, CREDIT_ROLL_SPEED) + +/obj/screen/credit/proc/add_to_clients() + for(var/client/C in GLOB.clients) + if(C.prefs.show_credits) + C.screen += src + +/obj/screen/credit/Destroy() + screen_loc = null + return ..() + +/obj/screen/credit/title_card + icon = 'icons/title_cards.dmi' + screen_loc = "4,1" + +/obj/screen/credit/title_card/Initialize(mapload, credited, title_icon_state) + icon_state = title_icon_state + . = ..() + maptext = null + +/proc/get_contribs() + var/list/contribs = list() + + if(fexists("[global.config.directory]/contributors.txt")) + contribs += world.file2list("[global.config.directory]/contributors.txt") + + if(length(contribs) > 20) + contribs.Cut(21) + + return contribs diff --git a/code/_onclick/hud/fullscreen.dm b/code/_onclick/hud/fullscreen.dm index 34149fd6f0d3..0497418effed 100644 --- a/code/_onclick/hud/fullscreen.dm +++ b/code/_onclick/hud/fullscreen.dm @@ -1,172 +1,181 @@ -/mob/proc/overlay_fullscreen(category, type, severity) - var/obj/screen/fullscreen/screen = screens[category] - if (!screen || screen.type != type) - // needs to be recreated - clear_fullscreen(category, FALSE) - screens[category] = screen = new type() - else if ((!severity || severity == screen.severity) && (!client || screen.screen_loc != "CENTER-7,CENTER-7" || screen.view == client.view)) - // doesn't need to be updated - return screen - - screen.icon_state = "[initial(screen.icon_state)][severity]" - screen.severity = severity - if (client && screen.should_show_to(src)) - screen.update_for_view(client.view) - client.screen += screen - - return screen - -/mob/proc/clear_fullscreen(category, animated = 10) - var/obj/screen/fullscreen/screen = screens[category] - if(!screen) - return - - screens -= category - - if(animated) - animate(screen, alpha = 0, time = animated) - addtimer(CALLBACK(src, .proc/clear_fullscreen_after_animate, screen), animated, TIMER_CLIENT_TIME) - else - if(client) - client.screen -= screen - qdel(screen) - -/mob/proc/clear_fullscreen_after_animate(obj/screen/fullscreen/screen) - if(client) - client.screen -= screen - qdel(screen) - -/mob/proc/clear_fullscreens() - for(var/category in screens) - clear_fullscreen(category) - -/mob/proc/hide_fullscreens() - if(client) - for(var/category in screens) - client.screen -= screens[category] - -/mob/proc/reload_fullscreen() - if(client) - var/obj/screen/fullscreen/screen - for(var/category in screens) - screen = screens[category] - if(screen.should_show_to(src)) - screen.update_for_view(client.view) - client.screen |= screen - else - client.screen -= screen - -/obj/screen/fullscreen - icon = 'icons/mob/screen_full.dmi' - icon_state = "default" - screen_loc = "CENTER-7,CENTER-7" - layer = FULLSCREEN_LAYER - plane = FULLSCREEN_PLANE - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - var/view = 7 - var/severity = 0 - var/show_when_dead = FALSE - -/obj/screen/fullscreen/proc/update_for_view(client_view) - if (screen_loc == "CENTER-7,CENTER-7" && view != client_view) - var/list/actualview = getviewsize(client_view) - view = client_view - transform = matrix(actualview[1]/FULLSCREEN_OVERLAY_RESOLUTION_X, 0, 0, 0, actualview[2]/FULLSCREEN_OVERLAY_RESOLUTION_Y, 0) - -/obj/screen/fullscreen/proc/should_show_to(mob/mymob) - if(!show_when_dead && mymob.stat == DEAD) - return FALSE - return TRUE - -/obj/screen/fullscreen/Destroy() - severity = 0 - . = ..() - -/obj/screen/fullscreen/brute - icon_state = "brutedamageoverlay" - layer = UI_DAMAGE_LAYER - plane = FULLSCREEN_PLANE - -/obj/screen/fullscreen/oxy - icon_state = "oxydamageoverlay" - layer = UI_DAMAGE_LAYER - plane = FULLSCREEN_PLANE - -/obj/screen/fullscreen/crit - icon_state = "passage" - layer = CRIT_LAYER - plane = FULLSCREEN_PLANE - -/obj/screen/fullscreen/crit/vision - icon_state = "oxydamageoverlay" - layer = BLIND_LAYER - -/obj/screen/fullscreen/blind - icon_state = "blackimageoverlay" - layer = BLIND_LAYER - plane = FULLSCREEN_PLANE - -/obj/screen/fullscreen/curse - icon_state = "curse" - layer = CURSE_LAYER - plane = FULLSCREEN_PLANE - -/obj/screen/fullscreen/impaired - icon_state = "impairedoverlay" - -/obj/screen/fullscreen/flash - icon = 'icons/mob/screen_gen.dmi' - screen_loc = "WEST,SOUTH to EAST,NORTH" - icon_state = "flash" - -/obj/screen/fullscreen/flash/static - icon = 'icons/mob/screen_gen.dmi' - screen_loc = "WEST,SOUTH to EAST,NORTH" - icon_state = "noise" - -/obj/screen/fullscreen/high - icon = 'icons/mob/screen_gen.dmi' - screen_loc = "WEST,SOUTH to EAST,NORTH" - icon_state = "druggy" - -/obj/screen/fullscreen/color_vision - icon = 'icons/mob/screen_gen.dmi' - screen_loc = "WEST,SOUTH to EAST,NORTH" - icon_state = "flash" - alpha = 80 - -/obj/screen/fullscreen/color_vision/green - color = "#00ff00" - -/obj/screen/fullscreen/color_vision/red - color = "#ff0000" - -/obj/screen/fullscreen/color_vision/blue - color = "#0000ff" - -/obj/screen/fullscreen/lighting_backdrop - icon = 'icons/mob/screen_gen.dmi' - icon_state = "flash" - transform = matrix(200, 0, 0, 0, 200, 0) - plane = LIGHTING_PLANE - blend_mode = BLEND_OVERLAY - show_when_dead = TRUE - -//Provides darkness to the back of the lighting plane -/obj/screen/fullscreen/lighting_backdrop/lit - invisibility = INVISIBILITY_LIGHTING - layer = BACKGROUND_LAYER+21 - color = "#000" - show_when_dead = TRUE - -//Provides whiteness in case you don't see lights so everything is still visible -/obj/screen/fullscreen/lighting_backdrop/unlit - layer = BACKGROUND_LAYER+20 - show_when_dead = TRUE - -/obj/screen/fullscreen/see_through_darkness - icon_state = "nightvision" - plane = LIGHTING_PLANE - layer = LIGHTING_LAYER - blend_mode = BLEND_ADD - show_when_dead = TRUE +/mob/proc/overlay_fullscreen(category, type, severity) + var/obj/screen/fullscreen/screen = screens[category] + if (!screen || screen.type != type) + // needs to be recreated + clear_fullscreen(category, FALSE) + screens[category] = screen = new type() + else if ((!severity || severity == screen.severity) && (!client || screen.screen_loc != "CENTER-7,CENTER-7" || screen.view == client.view)) + // doesn't need to be updated + return screen + + screen.icon_state = "[initial(screen.icon_state)][severity]" + screen.severity = severity + if (client && screen.should_show_to(src)) + screen.update_for_view(client.view) + client.screen += screen + + return screen + +/mob/proc/clear_fullscreen(category, animated = 10) + var/obj/screen/fullscreen/screen = screens[category] + if(!screen) + return + + screens -= category + + if(animated) + animate(screen, alpha = 0, time = animated) + addtimer(CALLBACK(src, .proc/clear_fullscreen_after_animate, screen), animated, TIMER_CLIENT_TIME) + else + if(client) + client.screen -= screen + qdel(screen) + +/mob/proc/clear_fullscreen_after_animate(obj/screen/fullscreen/screen) + if(client) + client.screen -= screen + qdel(screen) + +/mob/proc/clear_fullscreens() + for(var/category in screens) + clear_fullscreen(category) + +/mob/proc/hide_fullscreens() + if(client) + for(var/category in screens) + client.screen -= screens[category] + +/mob/proc/reload_fullscreen() + if(client) + var/obj/screen/fullscreen/screen + for(var/category in screens) + screen = screens[category] + if(screen.should_show_to(src)) + screen.update_for_view(client.view) + client.screen |= screen + else + client.screen -= screen + +/obj/screen/fullscreen + icon = 'icons/mob/screen_full.dmi' + icon_state = "default" + screen_loc = "CENTER-7,CENTER-7" + layer = FULLSCREEN_LAYER + plane = FULLSCREEN_PLANE + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + var/view = 7 + var/severity = 0 + var/show_when_dead = FALSE + +/obj/screen/fullscreen/proc/update_for_view(client_view) + if (screen_loc == "CENTER-7,CENTER-7" && view != client_view) + var/list/actualview = getviewsize(client_view) + view = client_view + transform = matrix(actualview[1]/FULLSCREEN_OVERLAY_RESOLUTION_X, 0, 0, 0, actualview[2]/FULLSCREEN_OVERLAY_RESOLUTION_Y, 0) + +/obj/screen/fullscreen/proc/should_show_to(mob/mymob) + if(!show_when_dead && mymob.stat == DEAD) + return FALSE + return TRUE + +/obj/screen/fullscreen/Destroy() + severity = 0 + . = ..() + +/obj/screen/fullscreen/brute + icon_state = "brutedamageoverlay" + layer = UI_DAMAGE_LAYER + plane = FULLSCREEN_PLANE + +/obj/screen/fullscreen/oxy + icon_state = "oxydamageoverlay" + layer = UI_DAMAGE_LAYER + plane = FULLSCREEN_PLANE + +/obj/screen/fullscreen/crit + icon_state = "passage" + layer = CRIT_LAYER + plane = FULLSCREEN_PLANE + +/obj/screen/fullscreen/crit/vision + icon_state = "oxydamageoverlay" + layer = BLIND_LAYER + +/obj/screen/fullscreen/blind + icon_state = "blackimageoverlay" + layer = BLIND_LAYER + plane = FULLSCREEN_PLANE + +/obj/screen/fullscreen/curse + icon_state = "curse" + layer = CURSE_LAYER + plane = FULLSCREEN_PLANE + +/obj/screen/fullscreen/impaired + icon_state = "impairedoverlay" + +/obj/screen/fullscreen/flash + icon = 'icons/mob/screen_gen.dmi' + screen_loc = "WEST,SOUTH to EAST,NORTH" + icon_state = "flash" + +/obj/screen/fullscreen/flash/static + icon = 'icons/mob/screen_gen.dmi' + screen_loc = "WEST,SOUTH to EAST,NORTH" + icon_state = "noise" + +/obj/screen/fullscreen/high + icon = 'icons/mob/screen_gen.dmi' + screen_loc = "WEST,SOUTH to EAST,NORTH" + icon_state = "druggy" + +/obj/screen/fullscreen/color_vision + icon = 'icons/mob/screen_gen.dmi' + screen_loc = "WEST,SOUTH to EAST,NORTH" + icon_state = "flash" + alpha = 80 + +/obj/screen/fullscreen/color_vision/green + color = "#00ff00" + +/obj/screen/fullscreen/color_vision/red + color = "#ff0000" + +/obj/screen/fullscreen/color_vision/blue + color = "#0000ff" + +/obj/screen/fullscreen/cinematic_backdrop + icon = 'icons/mob/screen_gen.dmi' + screen_loc = "WEST,SOUTH to EAST,NORTH" + icon_state = "flash" + plane = SPLASHSCREEN_PLANE + layer = SPLASHSCREEN_LAYER - 1 + color = "#000000" + show_when_dead = TRUE + +/obj/screen/fullscreen/lighting_backdrop + icon = 'icons/mob/screen_gen.dmi' + icon_state = "flash" + transform = matrix(200, 0, 0, 0, 200, 0) + plane = LIGHTING_PLANE + blend_mode = BLEND_OVERLAY + show_when_dead = TRUE + +//Provides darkness to the back of the lighting plane +/obj/screen/fullscreen/lighting_backdrop/lit + invisibility = INVISIBILITY_LIGHTING + layer = BACKGROUND_LAYER+21 + color = "#000" + show_when_dead = TRUE + +//Provides whiteness in case you don't see lights so everything is still visible +/obj/screen/fullscreen/lighting_backdrop/unlit + layer = BACKGROUND_LAYER+20 + show_when_dead = TRUE + +/obj/screen/fullscreen/see_through_darkness + icon_state = "nightvision" + plane = LIGHTING_PLANE + layer = LIGHTING_LAYER + blend_mode = BLEND_ADD + show_when_dead = TRUE diff --git a/code/_onclick/hud/ghost.dm b/code/_onclick/hud/ghost.dm index b301173d38aa..f67c922404e6 100644 --- a/code/_onclick/hud/ghost.dm +++ b/code/_onclick/hud/ghost.dm @@ -1,103 +1,103 @@ -/obj/screen/ghost - icon = 'icons/mob/screen_ghost.dmi' - -/obj/screen/ghost/MouseEntered() - flick(icon_state + "_anim", src) - -/obj/screen/ghost/jumptomob - name = "Jump to mob" - icon_state = "jumptomob" - -/obj/screen/ghost/jumptomob/Click() - var/mob/dead/observer/G = usr - G.jumptomob() - -/obj/screen/ghost/orbit - name = "Orbit" - icon_state = "orbit" - -/obj/screen/ghost/orbit/Click() - var/mob/dead/observer/G = usr - G.follow() - -/obj/screen/ghost/reenter_corpse - name = "Reenter corpse" - icon_state = "reenter_corpse" - -/obj/screen/ghost/reenter_corpse/Click() - var/mob/dead/observer/G = usr - G.reenter_corpse() - -/obj/screen/ghost/teleport - name = "Teleport" - icon_state = "teleport" - -/obj/screen/ghost/teleport/Click() - var/mob/dead/observer/G = usr - G.dead_tele() - -/obj/screen/ghost/pai - name = "pAI Candidate" - icon_state = "pai" - -/obj/screen/ghost/pai/Click() - var/mob/dead/observer/G = usr - G.register_pai() - -/datum/hud/ghost/New(mob/owner) - ..() - var/obj/screen/using - - using = new /obj/screen/ghost/jumptomob() - using.screen_loc = ui_ghost_jumptomob - using.hud = src - static_inventory += using - - using = new /obj/screen/ghost/orbit() - using.screen_loc = ui_ghost_orbit - using.hud = src - static_inventory += using - - using = new /obj/screen/ghost/reenter_corpse() - using.screen_loc = ui_ghost_reenter_corpse - using.hud = src - static_inventory += using - - using = new /obj/screen/ghost/teleport() - using.screen_loc = ui_ghost_teleport - using.hud = src - static_inventory += using - - using = new /obj/screen/ghost/pai() - using.screen_loc = ui_ghost_pai - using.hud = src - static_inventory += using - - using = new /obj/screen/language_menu - using.icon = ui_style - using.hud = src - static_inventory += using - -/datum/hud/ghost/show_hud(version = 0, mob/viewmob) - // don't show this HUD if observing; show the HUD of the observee - var/mob/dead/observer/O = mymob - if (istype(O) && O.observetarget) - plane_masters_update() - return FALSE - - . = ..() - if(!.) - return - var/mob/screenmob = viewmob || mymob - if(!screenmob.client.prefs.ghost_hud) - screenmob.client.screen -= static_inventory - else - screenmob.client.screen += static_inventory - -//We should only see observed mob alerts. -/datum/hud/ghost/reorganize_alerts(mob/viewmob) - var/mob/dead/observer/O = mymob - if (istype(O) && O.observetarget) - return - . = ..() - +/obj/screen/ghost + icon = 'icons/mob/screen_ghost.dmi' + +/obj/screen/ghost/MouseEntered() + flick(icon_state + "_anim", src) + +/obj/screen/ghost/jumptomob + name = "Jump to mob" + icon_state = "jumptomob" + +/obj/screen/ghost/jumptomob/Click() + var/mob/dead/observer/G = usr + G.jumptomob() + +/obj/screen/ghost/orbit + name = "Orbit" + icon_state = "orbit" + +/obj/screen/ghost/orbit/Click() + var/mob/dead/observer/G = usr + G.follow() + +/obj/screen/ghost/reenter_corpse + name = "Reenter corpse" + icon_state = "reenter_corpse" + +/obj/screen/ghost/reenter_corpse/Click() + var/mob/dead/observer/G = usr + G.reenter_corpse() + +/obj/screen/ghost/teleport + name = "Teleport" + icon_state = "teleport" + +/obj/screen/ghost/teleport/Click() + var/mob/dead/observer/G = usr + G.dead_tele() + +/obj/screen/ghost/pai + name = "pAI Candidate" + icon_state = "pai" + +/obj/screen/ghost/pai/Click() + var/mob/dead/observer/G = usr + G.register_pai() + +/datum/hud/ghost/New(mob/owner) + ..() + var/obj/screen/using + + using = new /obj/screen/ghost/jumptomob() + using.screen_loc = ui_ghost_jumptomob + using.hud = src + static_inventory += using + + using = new /obj/screen/ghost/orbit() + using.screen_loc = ui_ghost_orbit + using.hud = src + static_inventory += using + + using = new /obj/screen/ghost/reenter_corpse() + using.screen_loc = ui_ghost_reenter_corpse + using.hud = src + static_inventory += using + + using = new /obj/screen/ghost/teleport() + using.screen_loc = ui_ghost_teleport + using.hud = src + static_inventory += using + + using = new /obj/screen/ghost/pai() + using.screen_loc = ui_ghost_pai + using.hud = src + static_inventory += using + + using = new /obj/screen/language_menu + using.icon = ui_style + using.hud = src + static_inventory += using + +/datum/hud/ghost/show_hud(version = 0, mob/viewmob) + // don't show this HUD if observing; show the HUD of the observee + var/mob/dead/observer/O = mymob + if (istype(O) && O.observetarget) + plane_masters_update() + return FALSE + + . = ..() + if(!.) + return + var/mob/screenmob = viewmob || mymob + if(!screenmob.client.prefs.ghost_hud) + screenmob.client.screen -= static_inventory + else + screenmob.client.screen += static_inventory + +//We should only see observed mob alerts. +/datum/hud/ghost/reorganize_alerts(mob/viewmob) + var/mob/dead/observer/O = mymob + if (istype(O) && O.observetarget) + return + . = ..() + diff --git a/code/_onclick/hud/hud.dm b/code/_onclick/hud/hud.dm index 2cb7b2cba508..833aa694bdde 100644 --- a/code/_onclick/hud/hud.dm +++ b/code/_onclick/hud/hud.dm @@ -1,297 +1,298 @@ -/* - The hud datum - Used to show and hide huds for all the different mob types, - including inventories and item quick actions. -*/ - -// The default UI style is the first one in the list -GLOBAL_LIST_INIT(available_ui_styles, list( - "Midnight" = 'icons/mob/screen_midnight.dmi', - "Retro" = 'icons/mob/screen_retro.dmi', - "Plasmafire" = 'icons/mob/screen_plasmafire.dmi', - "Slimecore" = 'icons/mob/screen_slimecore.dmi', - "Operative" = 'icons/mob/screen_operative.dmi', - "Clockwork" = 'icons/mob/screen_clockwork.dmi' -)) - -/proc/ui_style2icon(ui_style) - return GLOB.available_ui_styles[ui_style] || GLOB.available_ui_styles[GLOB.available_ui_styles[1]] - -/datum/hud - var/mob/mymob - - var/hud_shown = TRUE //Used for the HUD toggle (F12) - var/hud_version = HUD_STYLE_STANDARD //Current displayed version of the HUD - var/inventory_shown = FALSE //Equipped item inventory - var/hotkey_ui_hidden = FALSE //This is to hide the buttons that can be used via hotkeys. (hotkeybuttons list of buttons) - - var/obj/screen/ling/chems/lingchemdisplay - var/obj/screen/ling/sting/lingstingdisplay - - var/obj/screen/blobpwrdisplay - - var/obj/screen/alien_plasma_display - var/obj/screen/alien_queen_finder - - var/obj/screen/devil/soul_counter/devilsouldisplay - - var/obj/screen/action_intent - var/obj/screen/zone_select - var/obj/screen/pull_icon - var/obj/screen/rest_icon - var/obj/screen/throw_icon - var/obj/screen/module_store_icon - - var/list/static_inventory = list() //the screen objects which are static - var/list/toggleable_inventory = list() //the screen objects which can be hidden - var/list/obj/screen/hotkeybuttons = list() //the buttons that can be used via hotkeys - var/list/infodisplay = list() //the screen objects that display mob info (health, alien plasma, etc...) - var/list/screenoverlays = list() //the screen objects used as whole screen overlays (flash, damageoverlay, etc...) - var/list/inv_slots[SLOTS_AMT] // /obj/screen/inventory objects, ordered by their slot ID. - var/list/hand_slots // /obj/screen/inventory/hand objects, assoc list of "[held_index]" = object - var/list/obj/screen/plane_master/plane_masters = list() // see "appearance_flags" in the ref, assoc list of "[plane]" = object - - var/obj/screen/movable/action_button/hide_toggle/hide_actions_toggle - var/action_buttons_hidden = FALSE - - var/obj/screen/healths - var/obj/screen/healthdoll - var/obj/screen/internals - var/obj/screen/wanted/wanted_lvl - /* Wasp begin - var/obj/screen/spacesuit - Wasp End - Fuckin' spacesuits. */ - // subtypes can override this to force a specific UI style - var/ui_style - -/datum/hud/New(mob/owner) - mymob = owner - - if (!ui_style) - // will fall back to the default if any of these are null - ui_style = ui_style2icon(owner.client && owner.client.prefs && owner.client.prefs.UI_style) - - hide_actions_toggle = new - hide_actions_toggle.InitialiseIcon(src) - if(mymob.client) - hide_actions_toggle.locked = mymob.client.prefs.buttons_locked - - hand_slots = list() - - for(var/mytype in subtypesof(/obj/screen/plane_master)) - var/obj/screen/plane_master/instance = new mytype() - plane_masters["[instance.plane]"] = instance - instance.backdrop(mymob) - - owner.overlay_fullscreen("see_through_darkness", /obj/screen/fullscreen/see_through_darkness) - -/datum/hud/Destroy() - if(mymob.hud_used == src) - mymob.hud_used = null - - QDEL_NULL(hide_actions_toggle) - QDEL_NULL(module_store_icon) - QDEL_LIST(static_inventory) - - inv_slots.Cut() - action_intent = null - zone_select = null - pull_icon = null - - QDEL_LIST(toggleable_inventory) - QDEL_LIST(hotkeybuttons) - throw_icon = null - QDEL_LIST(infodisplay) - - healths = null - healthdoll = null - wanted_lvl = null - internals = null - lingchemdisplay = null - devilsouldisplay = null - lingstingdisplay = null - blobpwrdisplay = null - alien_plasma_display = null - alien_queen_finder = null - - QDEL_LIST_ASSOC_VAL(plane_masters) - QDEL_LIST(screenoverlays) - mymob = null - - return ..() - -/mob/proc/create_mob_hud() - if(!client || hud_used) - return - hud_used = new hud_type(src) - update_sight() - SEND_SIGNAL(src, COMSIG_MOB_HUD_CREATED) - -//Version denotes which style should be displayed. blank or 0 means "next version" -/datum/hud/proc/show_hud(version = 0, mob/viewmob) - if(!ismob(mymob)) - return FALSE - var/mob/screenmob = viewmob || mymob - if(!screenmob.client) - return FALSE - - screenmob.client.screen = list() - screenmob.client.apply_clickcatcher() - - var/display_hud_version = version - if(!display_hud_version) //If 0 or blank, display the next hud version - display_hud_version = hud_version + 1 - if(display_hud_version > HUD_VERSIONS) //If the requested version number is greater than the available versions, reset back to the first version - display_hud_version = 1 - - switch(display_hud_version) - if(HUD_STYLE_STANDARD) //Default HUD - hud_shown = TRUE //Governs behavior of other procs - if(static_inventory.len) - screenmob.client.screen += static_inventory - if(toggleable_inventory.len && screenmob.hud_used && screenmob.hud_used.inventory_shown) - screenmob.client.screen += toggleable_inventory - if(hotkeybuttons.len && !hotkey_ui_hidden) - screenmob.client.screen += hotkeybuttons - if(infodisplay.len) - screenmob.client.screen += infodisplay - - screenmob.client.screen += hide_actions_toggle - - if(action_intent) - action_intent.screen_loc = initial(action_intent.screen_loc) //Restore intent selection to the original position - - if(HUD_STYLE_REDUCED) //Reduced HUD - hud_shown = FALSE //Governs behavior of other procs - if(static_inventory.len) - screenmob.client.screen -= static_inventory - if(toggleable_inventory.len) - screenmob.client.screen -= toggleable_inventory - if(hotkeybuttons.len) - screenmob.client.screen -= hotkeybuttons - if(infodisplay.len) - screenmob.client.screen += infodisplay - - //These ones are a part of 'static_inventory', 'toggleable_inventory' or 'hotkeybuttons' but we want them to stay - for(var/h in hand_slots) - var/obj/screen/hand = hand_slots[h] - if(hand) - screenmob.client.screen += hand - if(action_intent) - screenmob.client.screen += action_intent //we want the intent switcher visible - action_intent.screen_loc = ui_acti_alt //move this to the alternative position, where zone_select usually is. - - if(HUD_STYLE_NOHUD) //No HUD - hud_shown = FALSE //Governs behavior of other procs - if(static_inventory.len) - screenmob.client.screen -= static_inventory - if(toggleable_inventory.len) - screenmob.client.screen -= toggleable_inventory - if(hotkeybuttons.len) - screenmob.client.screen -= hotkeybuttons - if(infodisplay.len) - screenmob.client.screen -= infodisplay - - hud_version = display_hud_version - persistent_inventory_update(screenmob) - screenmob.update_action_buttons(1) - reorganize_alerts(screenmob) - screenmob.reload_fullscreen() - update_parallax_pref(screenmob) - - // ensure observers get an accurate and up-to-date view - if (!viewmob) - plane_masters_update() - for(var/M in mymob.observers) - show_hud(hud_version, M) - else if (viewmob.hud_used) - viewmob.hud_used.plane_masters_update() - - return TRUE - -/datum/hud/proc/plane_masters_update() - // Plane masters are always shown to OUR mob, never to observers - for(var/thing in plane_masters) - var/obj/screen/plane_master/PM = plane_masters[thing] - PM.backdrop(mymob) - mymob.client.screen += PM - -/datum/hud/human/show_hud(version = 0,mob/viewmob) - . = ..() - if(!.) - return - var/mob/screenmob = viewmob || mymob - hidden_inventory_update(screenmob) - -/datum/hud/robot/show_hud(version = 0, mob/viewmob) - . = ..() - if(!.) - return - update_robot_modules_display() - -/datum/hud/proc/hidden_inventory_update() - return - -/datum/hud/proc/persistent_inventory_update(mob/viewer) - if(!mymob) - return - -/datum/hud/proc/update_ui_style(new_ui_style) - // do nothing if overridden by a subtype or already on that style - if (initial(ui_style) || ui_style == new_ui_style) - return - - for(var/atom/item in static_inventory + toggleable_inventory + hotkeybuttons + infodisplay + screenoverlays + inv_slots) - if (item.icon == ui_style) - item.icon = new_ui_style - - ui_style = new_ui_style - build_hand_slots() - hide_actions_toggle.InitialiseIcon(src) - -//Triggered when F12 is pressed (Unless someone changed something in the DMF) -/mob/verb/button_pressed_F12() - set name = "F12" - set hidden = TRUE - - if(hud_used && client) - hud_used.show_hud() //Shows the next hud preset - to_chat(usr, "Switched HUD mode. Press F12 to toggle.") - else - to_chat(usr, "This mob type does not use a HUD.") - - -//(re)builds the hand ui slots, throwing away old ones -//not really worth jugglying existing ones so we just scrap+rebuild -//9/10 this is only called once per mob and only for 2 hands -/datum/hud/proc/build_hand_slots() - for(var/h in hand_slots) - var/obj/screen/inventory/hand/H = hand_slots[h] - if(H) - static_inventory -= H - hand_slots = list() - var/obj/screen/inventory/hand/hand_box - for(var/i in 1 to mymob.held_items.len) - hand_box = new /obj/screen/inventory/hand() - hand_box.name = mymob.get_held_index_name(i) - hand_box.icon = ui_style - hand_box.icon_state = "hand_[mymob.held_index_to_dir(i)]" - hand_box.screen_loc = ui_hand_position(i) - hand_box.held_index = i - hand_slots["[i]"] = hand_box - hand_box.hud = src - static_inventory += hand_box - hand_box.update_icon() - - var/i = 1 - for(var/obj/screen/swap_hand/SH in static_inventory) - SH.screen_loc = ui_swaphand_position(mymob,!(i % 2) ? 2: 1) - i++ - for(var/obj/screen/human/equip/E in static_inventory) - E.screen_loc = ui_equip_position(mymob) - - if(ismob(mymob) && mymob.hud_used == src) - show_hud(hud_version) - -/datum/hud/proc/update_locked_slots() - return +/* + The hud datum + Used to show and hide huds for all the different mob types, + including inventories and item quick actions. +*/ + +// The default UI style is the first one in the list +GLOBAL_LIST_INIT(available_ui_styles, list( + "Midnight" = 'icons/mob/screen_midnight.dmi', + "Retro" = 'icons/mob/screen_retro.dmi', + "Plasmafire" = 'icons/mob/screen_plasmafire.dmi', + "Slimecore" = 'icons/mob/screen_slimecore.dmi', + "Operative" = 'icons/mob/screen_operative.dmi', + "Clockwork" = 'icons/mob/screen_clockwork.dmi', + "Glass" = 'icons/mob/screen_glass.dmi' +)) + +/proc/ui_style2icon(ui_style) + return GLOB.available_ui_styles[ui_style] || GLOB.available_ui_styles[GLOB.available_ui_styles[1]] + +/datum/hud + var/mob/mymob + + var/hud_shown = TRUE //Used for the HUD toggle (F12) + var/hud_version = HUD_STYLE_STANDARD //Current displayed version of the HUD + var/inventory_shown = FALSE //Equipped item inventory + var/hotkey_ui_hidden = FALSE //This is to hide the buttons that can be used via hotkeys. (hotkeybuttons list of buttons) + + var/obj/screen/ling/chems/lingchemdisplay + var/obj/screen/ling/sting/lingstingdisplay + + var/obj/screen/blobpwrdisplay + + var/obj/screen/alien_plasma_display + var/obj/screen/alien_queen_finder + + var/obj/screen/devil/soul_counter/devilsouldisplay + + var/obj/screen/action_intent + var/obj/screen/zone_select + var/obj/screen/pull_icon + var/obj/screen/rest_icon + var/obj/screen/throw_icon + var/obj/screen/module_store_icon + + var/list/static_inventory = list() //the screen objects which are static + var/list/toggleable_inventory = list() //the screen objects which can be hidden + var/list/obj/screen/hotkeybuttons = list() //the buttons that can be used via hotkeys + var/list/infodisplay = list() //the screen objects that display mob info (health, alien plasma, etc...) + var/list/screenoverlays = list() //the screen objects used as whole screen overlays (flash, damageoverlay, etc...) + var/list/inv_slots[SLOTS_AMT] // /obj/screen/inventory objects, ordered by their slot ID. + var/list/hand_slots // /obj/screen/inventory/hand objects, assoc list of "[held_index]" = object + var/list/obj/screen/plane_master/plane_masters = list() // see "appearance_flags" in the ref, assoc list of "[plane]" = object + + var/obj/screen/movable/action_button/hide_toggle/hide_actions_toggle + var/action_buttons_hidden = FALSE + + var/obj/screen/healths + var/obj/screen/healthdoll + var/obj/screen/internals + var/obj/screen/wanted/wanted_lvl + /* Wasp begin + var/obj/screen/spacesuit + Wasp End - Fuckin' spacesuits. */ + // subtypes can override this to force a specific UI style + var/ui_style + +/datum/hud/New(mob/owner) + mymob = owner + + if (!ui_style) + // will fall back to the default if any of these are null + ui_style = ui_style2icon(owner.client && owner.client.prefs && owner.client.prefs.UI_style) + + hide_actions_toggle = new + hide_actions_toggle.InitialiseIcon(src) + if(mymob.client) + hide_actions_toggle.locked = mymob.client.prefs.buttons_locked + + hand_slots = list() + + for(var/mytype in subtypesof(/obj/screen/plane_master)) + var/obj/screen/plane_master/instance = new mytype() + plane_masters["[instance.plane]"] = instance + instance.backdrop(mymob) + + owner.overlay_fullscreen("see_through_darkness", /obj/screen/fullscreen/see_through_darkness) + +/datum/hud/Destroy() + if(mymob.hud_used == src) + mymob.hud_used = null + + QDEL_NULL(hide_actions_toggle) + QDEL_NULL(module_store_icon) + QDEL_LIST(static_inventory) + + inv_slots.Cut() + action_intent = null + zone_select = null + pull_icon = null + + QDEL_LIST(toggleable_inventory) + QDEL_LIST(hotkeybuttons) + throw_icon = null + QDEL_LIST(infodisplay) + + healths = null + healthdoll = null + wanted_lvl = null + internals = null + lingchemdisplay = null + devilsouldisplay = null + lingstingdisplay = null + blobpwrdisplay = null + alien_plasma_display = null + alien_queen_finder = null + + QDEL_LIST_ASSOC_VAL(plane_masters) + QDEL_LIST(screenoverlays) + mymob = null + + return ..() + +/mob/proc/create_mob_hud() + if(!client || hud_used) + return + hud_used = new hud_type(src) + update_sight() + SEND_SIGNAL(src, COMSIG_MOB_HUD_CREATED) + +//Version denotes which style should be displayed. blank or 0 means "next version" +/datum/hud/proc/show_hud(version = 0, mob/viewmob) + if(!ismob(mymob)) + return FALSE + var/mob/screenmob = viewmob || mymob + if(!screenmob.client) + return FALSE + + screenmob.client.screen = list() + screenmob.client.apply_clickcatcher() + + var/display_hud_version = version + if(!display_hud_version) //If 0 or blank, display the next hud version + display_hud_version = hud_version + 1 + if(display_hud_version > HUD_VERSIONS) //If the requested version number is greater than the available versions, reset back to the first version + display_hud_version = 1 + + switch(display_hud_version) + if(HUD_STYLE_STANDARD) //Default HUD + hud_shown = TRUE //Governs behavior of other procs + if(static_inventory.len) + screenmob.client.screen += static_inventory + if(toggleable_inventory.len && screenmob.hud_used && screenmob.hud_used.inventory_shown) + screenmob.client.screen += toggleable_inventory + if(hotkeybuttons.len && !hotkey_ui_hidden) + screenmob.client.screen += hotkeybuttons + if(infodisplay.len) + screenmob.client.screen += infodisplay + + screenmob.client.screen += hide_actions_toggle + + if(action_intent) + action_intent.screen_loc = initial(action_intent.screen_loc) //Restore intent selection to the original position + + if(HUD_STYLE_REDUCED) //Reduced HUD + hud_shown = FALSE //Governs behavior of other procs + if(static_inventory.len) + screenmob.client.screen -= static_inventory + if(toggleable_inventory.len) + screenmob.client.screen -= toggleable_inventory + if(hotkeybuttons.len) + screenmob.client.screen -= hotkeybuttons + if(infodisplay.len) + screenmob.client.screen += infodisplay + + //These ones are a part of 'static_inventory', 'toggleable_inventory' or 'hotkeybuttons' but we want them to stay + for(var/h in hand_slots) + var/obj/screen/hand = hand_slots[h] + if(hand) + screenmob.client.screen += hand + if(action_intent) + screenmob.client.screen += action_intent //we want the intent switcher visible + action_intent.screen_loc = ui_acti_alt //move this to the alternative position, where zone_select usually is. + + if(HUD_STYLE_NOHUD) //No HUD + hud_shown = FALSE //Governs behavior of other procs + if(static_inventory.len) + screenmob.client.screen -= static_inventory + if(toggleable_inventory.len) + screenmob.client.screen -= toggleable_inventory + if(hotkeybuttons.len) + screenmob.client.screen -= hotkeybuttons + if(infodisplay.len) + screenmob.client.screen -= infodisplay + + hud_version = display_hud_version + persistent_inventory_update(screenmob) + screenmob.update_action_buttons(1) + reorganize_alerts(screenmob) + screenmob.reload_fullscreen() + update_parallax_pref(screenmob) + + // ensure observers get an accurate and up-to-date view + if (!viewmob) + plane_masters_update() + for(var/M in mymob.observers) + show_hud(hud_version, M) + else if (viewmob.hud_used) + viewmob.hud_used.plane_masters_update() + + return TRUE + +/datum/hud/proc/plane_masters_update() + // Plane masters are always shown to OUR mob, never to observers + for(var/thing in plane_masters) + var/obj/screen/plane_master/PM = plane_masters[thing] + PM.backdrop(mymob) + mymob.client.screen += PM + +/datum/hud/human/show_hud(version = 0,mob/viewmob) + . = ..() + if(!.) + return + var/mob/screenmob = viewmob || mymob + hidden_inventory_update(screenmob) + +/datum/hud/robot/show_hud(version = 0, mob/viewmob) + . = ..() + if(!.) + return + update_robot_modules_display() + +/datum/hud/proc/hidden_inventory_update() + return + +/datum/hud/proc/persistent_inventory_update(mob/viewer) + if(!mymob) + return + +/datum/hud/proc/update_ui_style(new_ui_style) + // do nothing if overridden by a subtype or already on that style + if (initial(ui_style) || ui_style == new_ui_style) + return + + for(var/atom/item in static_inventory + toggleable_inventory + hotkeybuttons + infodisplay + screenoverlays + inv_slots) + if (item.icon == ui_style) + item.icon = new_ui_style + + ui_style = new_ui_style + build_hand_slots() + hide_actions_toggle.InitialiseIcon(src) + +//Triggered when F12 is pressed (Unless someone changed something in the DMF) +/mob/verb/button_pressed_F12() + set name = "F12" + set hidden = TRUE + + if(hud_used && client) + hud_used.show_hud() //Shows the next hud preset + to_chat(usr, "Switched HUD mode. Press F12 to toggle.") + else + to_chat(usr, "This mob type does not use a HUD.") + + +//(re)builds the hand ui slots, throwing away old ones +//not really worth jugglying existing ones so we just scrap+rebuild +//9/10 this is only called once per mob and only for 2 hands +/datum/hud/proc/build_hand_slots() + for(var/h in hand_slots) + var/obj/screen/inventory/hand/H = hand_slots[h] + if(H) + static_inventory -= H + hand_slots = list() + var/obj/screen/inventory/hand/hand_box + for(var/i in 1 to mymob.held_items.len) + hand_box = new /obj/screen/inventory/hand() + hand_box.name = mymob.get_held_index_name(i) + hand_box.icon = ui_style + hand_box.icon_state = "hand_[mymob.held_index_to_dir(i)]" + hand_box.screen_loc = ui_hand_position(i) + hand_box.held_index = i + hand_slots["[i]"] = hand_box + hand_box.hud = src + static_inventory += hand_box + hand_box.update_icon() + + var/i = 1 + for(var/obj/screen/swap_hand/SH in static_inventory) + SH.screen_loc = ui_swaphand_position(mymob,!(i % 2) ? 2: 1) + i++ + for(var/obj/screen/human/equip/E in static_inventory) + E.screen_loc = ui_equip_position(mymob) + + if(ismob(mymob) && mymob.hud_used == src) + show_hud(hud_version) + +/datum/hud/proc/update_locked_slots() + return diff --git a/code/_onclick/hud/human.dm b/code/_onclick/hud/human.dm index 8015b8b61a29..fdb72d75f7e1 100644 --- a/code/_onclick/hud/human.dm +++ b/code/_onclick/hud/human.dm @@ -1,483 +1,483 @@ -/obj/screen/human - icon = 'icons/mob/screen_midnight.dmi' - -/obj/screen/human/toggle - name = "toggle" - icon_state = "toggle" - -/obj/screen/human/toggle/Click() - - var/mob/targetmob = usr - - if(isobserver(usr)) - if(ishuman(usr.client.eye) && (usr.client.eye != usr)) - var/mob/M = usr.client.eye - targetmob = M - - if(usr.hud_used.inventory_shown && targetmob.hud_used) - usr.hud_used.inventory_shown = FALSE - usr.client.screen -= targetmob.hud_used.toggleable_inventory - else - usr.hud_used.inventory_shown = TRUE - usr.client.screen += targetmob.hud_used.toggleable_inventory - - targetmob.hud_used.hidden_inventory_update(usr) - -/obj/screen/human/equip - name = "equip" - icon_state = "act_equip" - -/obj/screen/human/equip/Click() - if(ismecha(usr.loc)) // stops inventory actions in a mech - return 1 - var/mob/living/carbon/human/H = usr - H.quick_equip() - -/obj/screen/devil - icon = 'icons/mob/screen_devil.dmi' - invisibility = INVISIBILITY_ABSTRACT - -/obj/screen/devil/soul_counter - name = "souls owned" - icon_state = "Devil-6" - screen_loc = ui_devilsouldisplay - -/obj/screen/devil/soul_counter/proc/update_counter(souls = 0) - invisibility = 0 - maptext = "
    [souls]
    " - switch(souls) - if(0,null) - icon_state = "Devil-1" - if(1,2) - icon_state = "Devil-2" - if(3 to 5) - icon_state = "Devil-3" - if(6 to 8) - icon_state = "Devil-4" - if(9 to INFINITY) - icon_state = "Devil-5" - else - icon_state = "Devil-6" - -/obj/screen/devil/soul_counter/proc/clear() - invisibility = INVISIBILITY_ABSTRACT - -/obj/screen/ling - icon = 'icons/mob/screen_changeling.dmi' - invisibility = INVISIBILITY_ABSTRACT - -/obj/screen/ling/sting - name = "current sting" - screen_loc = ui_lingstingdisplay - -/obj/screen/ling/sting/Click() - if(isobserver(usr)) - return - var/mob/living/carbon/U = usr - U.unset_sting() - -/obj/screen/ling/chems - name = "chemical storage" - icon_state = "power_display" - screen_loc = ui_lingchemdisplay - -/datum/hud/human/New(mob/living/carbon/human/owner) - ..() - - var/widescreen_layout = FALSE - if(owner.client?.prefs?.widescreenpref) - widescreen_layout = TRUE - - var/obj/screen/using - var/obj/screen/inventory/inv_box - - using = new/obj/screen/language_menu - using.icon = ui_style - if(!widescreen_layout) - using.screen_loc = UI_BOXLANG - using.hud = src - static_inventory += using - - using = new/obj/screen/skills - using.icon = ui_style - if(!widescreen_layout) - using.screen_loc = UI_BOXLANG - static_inventory += using - - using = new /obj/screen/area_creator - using.icon = ui_style - if(!widescreen_layout) - using.screen_loc = UI_BOXAREA - using.hud = src - static_inventory += using - - action_intent = new /obj/screen/act_intent/segmented - action_intent.icon_state = mymob.a_intent - action_intent.hud = src - static_inventory += action_intent - - using = new /obj/screen/mov_intent - using.icon = ui_style - using.icon_state = (mymob.m_intent == MOVE_INTENT_RUN ? "running" : "walking") - using.screen_loc = ui_movi - using.hud = src - static_inventory += using - - using = new /obj/screen/drop() - using.icon = ui_style - using.screen_loc = ui_drop_throw - using.hud = src - static_inventory += using - - inv_box = new /obj/screen/inventory() - inv_box.name = "i_clothing" - inv_box.icon = ui_style - inv_box.slot_id = ITEM_SLOT_ICLOTHING - inv_box.icon_state = "uniform" - inv_box.screen_loc = ui_iclothing - inv_box.hud = src - toggleable_inventory += inv_box - - inv_box = new /obj/screen/inventory() - inv_box.name = "o_clothing" - inv_box.icon = ui_style - inv_box.slot_id = ITEM_SLOT_OCLOTHING - inv_box.icon_state = "suit" - inv_box.screen_loc = ui_oclothing - inv_box.hud = src - toggleable_inventory += inv_box - - build_hand_slots() - - using = new /obj/screen/swap_hand() - using.icon = ui_style - using.icon_state = "swap_1" - using.screen_loc = ui_swaphand_position(owner,1) - using.hud = src - static_inventory += using - - using = new /obj/screen/swap_hand() - using.icon = ui_style - using.icon_state = "swap_2" - using.screen_loc = ui_swaphand_position(owner,2) - using.hud = src - static_inventory += using - - inv_box = new /obj/screen/inventory() - inv_box.name = "id" - inv_box.icon = ui_style - inv_box.icon_state = "id" - inv_box.screen_loc = ui_id - inv_box.slot_id = ITEM_SLOT_ID - inv_box.hud = src - static_inventory += inv_box - - inv_box = new /obj/screen/inventory() - inv_box.name = "mask" - inv_box.icon = ui_style - inv_box.icon_state = "mask" - inv_box.screen_loc = ui_mask - inv_box.slot_id = ITEM_SLOT_MASK - inv_box.hud = src - toggleable_inventory += inv_box - - inv_box = new /obj/screen/inventory() - inv_box.name = "neck" - inv_box.icon = ui_style - inv_box.icon_state = "neck" - inv_box.screen_loc = ui_neck - inv_box.slot_id = ITEM_SLOT_NECK - inv_box.hud = src - toggleable_inventory += inv_box - - inv_box = new /obj/screen/inventory() - inv_box.name = "back" - inv_box.icon = ui_style - inv_box.icon_state = "back" - inv_box.screen_loc = ui_back - inv_box.slot_id = ITEM_SLOT_BACK - inv_box.hud = src - static_inventory += inv_box - - inv_box = new /obj/screen/inventory() - inv_box.name = "storage1" - inv_box.icon = ui_style - inv_box.icon_state = "pocket" - inv_box.screen_loc = ui_storage1 - inv_box.slot_id = ITEM_SLOT_LPOCKET - inv_box.hud = src - static_inventory += inv_box - - inv_box = new /obj/screen/inventory() - inv_box.name = "storage2" - inv_box.icon = ui_style - inv_box.icon_state = "pocket" - inv_box.screen_loc = ui_storage2 - inv_box.slot_id = ITEM_SLOT_RPOCKET - inv_box.hud = src - static_inventory += inv_box - - inv_box = new /obj/screen/inventory() - inv_box.name = "suit storage" - inv_box.icon = ui_style - inv_box.icon_state = "suit_storage" - inv_box.screen_loc = ui_sstore1 - inv_box.slot_id = ITEM_SLOT_SUITSTORE - inv_box.hud = src - static_inventory += inv_box - - using = new /obj/screen/resist() - using.icon = ui_style - using.screen_loc = ui_above_intent - using.hud = src - hotkeybuttons += using - - using = new /obj/screen/human/toggle() - using.icon = ui_style - using.screen_loc = ui_inventory - using.hud = src - static_inventory += using - - using = new /obj/screen/human/equip() - using.icon = ui_style - using.screen_loc = ui_equip_position(mymob) - using.hud = src - static_inventory += using - - inv_box = new /obj/screen/inventory() - inv_box.name = "gloves" - inv_box.icon = ui_style - inv_box.icon_state = "gloves" - inv_box.screen_loc = ui_gloves - inv_box.slot_id = ITEM_SLOT_GLOVES - inv_box.hud = src - toggleable_inventory += inv_box - - inv_box = new /obj/screen/inventory() - inv_box.name = "eyes" - inv_box.icon = ui_style - inv_box.icon_state = "glasses" - inv_box.screen_loc = ui_glasses - inv_box.slot_id = ITEM_SLOT_EYES - inv_box.hud = src - toggleable_inventory += inv_box - - inv_box = new /obj/screen/inventory() - inv_box.name = "ears" - inv_box.icon = ui_style - inv_box.icon_state = "ears" - inv_box.screen_loc = ui_ears - inv_box.slot_id = ITEM_SLOT_EARS - inv_box.hud = src - toggleable_inventory += inv_box - - inv_box = new /obj/screen/inventory() - inv_box.name = "head" - inv_box.icon = ui_style - inv_box.icon_state = "head" - inv_box.screen_loc = ui_head - inv_box.slot_id = ITEM_SLOT_HEAD - inv_box.hud = src - toggleable_inventory += inv_box - - inv_box = new /obj/screen/inventory() - inv_box.name = "shoes" - inv_box.icon = ui_style - inv_box.icon_state = "shoes" - inv_box.screen_loc = ui_shoes - inv_box.slot_id = ITEM_SLOT_FEET - inv_box.hud = src - toggleable_inventory += inv_box - - inv_box = new /obj/screen/inventory() - inv_box.name = "belt" - inv_box.icon = ui_style - inv_box.icon_state = "belt" -// inv_box.icon_full = "template_small" - inv_box.screen_loc = ui_belt - inv_box.slot_id = ITEM_SLOT_BELT - inv_box.hud = src - static_inventory += inv_box - - throw_icon = new /obj/screen/throw_catch() - throw_icon.icon = ui_style - throw_icon.screen_loc = ui_drop_throw - throw_icon.hud = src - hotkeybuttons += throw_icon - - rest_icon = new /obj/screen/rest() - rest_icon.icon = ui_style - rest_icon.screen_loc = ui_above_movement - rest_icon.hud = src - static_inventory += rest_icon - - internals = new /obj/screen/internals() - internals.hud = src - infodisplay += internals - - healths = new /obj/screen/healths() - healths.hud = src - infodisplay += healths - - healthdoll = new /obj/screen/healthdoll() - healthdoll.hud = src - infodisplay += healthdoll - - pull_icon = new /obj/screen/pull() - pull_icon.icon = ui_style - pull_icon.update_icon() - pull_icon.screen_loc = ui_above_intent - pull_icon.hud = src - static_inventory += pull_icon - - lingchemdisplay = new /obj/screen/ling/chems() - lingchemdisplay.hud = src - infodisplay += lingchemdisplay - - lingstingdisplay = new /obj/screen/ling/sting() - lingstingdisplay.hud = src - infodisplay += lingstingdisplay - - devilsouldisplay = new /obj/screen/devil/soul_counter - devilsouldisplay.hud = src - infodisplay += devilsouldisplay - - zone_select = new /obj/screen/zone_sel() - zone_select.icon = ui_style - zone_select.hud = src - zone_select.update_icon() - static_inventory += zone_select - - for(var/obj/screen/inventory/inv in (static_inventory + toggleable_inventory)) - if(inv.slot_id) - inv.hud = src - inv_slots[TOBITSHIFT(inv.slot_id) + 1] = inv - inv.update_icon() - - update_locked_slots() - -/datum/hud/human/update_locked_slots() - if(!mymob) - return - var/mob/living/carbon/human/H = mymob - if(!istype(H) || !H.dna.species) - return - var/datum/species/S = H.dna.species - for(var/obj/screen/inventory/inv in (static_inventory + toggleable_inventory)) - if(inv.slot_id) - if(inv.slot_id in S.no_equip) - inv.alpha = 128 - else - inv.alpha = initial(inv.alpha) - -/datum/hud/human/hidden_inventory_update(mob/viewer) - if(!mymob) - return - var/mob/living/carbon/human/H = mymob - - var/mob/screenmob = viewer || H - - if(screenmob.hud_used.inventory_shown && screenmob.hud_used.hud_shown) - if(H.shoes) - H.shoes.screen_loc = ui_shoes - screenmob.client.screen += H.shoes - if(H.gloves) - H.gloves.screen_loc = ui_gloves - screenmob.client.screen += H.gloves - if(H.ears) - H.ears.screen_loc = ui_ears - screenmob.client.screen += H.ears - if(H.glasses) - H.glasses.screen_loc = ui_glasses - screenmob.client.screen += H.glasses - if(H.w_uniform) - H.w_uniform.screen_loc = ui_iclothing - screenmob.client.screen += H.w_uniform - if(H.wear_suit) - H.wear_suit.screen_loc = ui_oclothing - screenmob.client.screen += H.wear_suit - if(H.wear_mask) - H.wear_mask.screen_loc = ui_mask - screenmob.client.screen += H.wear_mask - if(H.wear_neck) - H.wear_neck.screen_loc = ui_neck - screenmob.client.screen += H.wear_neck - if(H.head) - H.head.screen_loc = ui_head - screenmob.client.screen += H.head - else - if(H.shoes) screenmob.client.screen -= H.shoes - if(H.gloves) screenmob.client.screen -= H.gloves - if(H.ears) screenmob.client.screen -= H.ears - if(H.glasses) screenmob.client.screen -= H.glasses - if(H.w_uniform) screenmob.client.screen -= H.w_uniform - if(H.wear_suit) screenmob.client.screen -= H.wear_suit - if(H.wear_mask) screenmob.client.screen -= H.wear_mask - if(H.wear_neck) screenmob.client.screen -= H.wear_neck - if(H.head) screenmob.client.screen -= H.head - - - -/datum/hud/human/persistent_inventory_update(mob/viewer) - if(!mymob) - return - ..() - var/mob/living/carbon/human/H = mymob - - var/mob/screenmob = viewer || H - - if(screenmob.hud_used) - if(screenmob.hud_used.hud_shown) - if(H.s_store) - H.s_store.screen_loc = ui_sstore1 - screenmob.client.screen += H.s_store - if(H.wear_id) - H.wear_id.screen_loc = ui_id - screenmob.client.screen += H.wear_id - if(H.belt) - H.belt.screen_loc = ui_belt - screenmob.client.screen += H.belt - if(H.back) - H.back.screen_loc = ui_back - screenmob.client.screen += H.back - if(H.l_store) - H.l_store.screen_loc = ui_storage1 - screenmob.client.screen += H.l_store - if(H.r_store) - H.r_store.screen_loc = ui_storage2 - screenmob.client.screen += H.r_store - else - if(H.s_store) - screenmob.client.screen -= H.s_store - if(H.wear_id) - screenmob.client.screen -= H.wear_id - if(H.belt) - screenmob.client.screen -= H.belt - if(H.back) - screenmob.client.screen -= H.back - if(H.l_store) - screenmob.client.screen -= H.l_store - if(H.r_store) - screenmob.client.screen -= H.r_store - - if(hud_version != HUD_STYLE_NOHUD) - for(var/obj/item/I in H.held_items) - I.screen_loc = ui_hand_position(H.get_held_index_of_item(I)) - screenmob.client.screen += I - else - for(var/obj/item/I in H.held_items) - I.screen_loc = null - screenmob.client.screen -= I - - -/mob/living/carbon/human/verb/toggle_hotkey_verbs() - set category = "OOC" - set name = "Toggle hotkey buttons" - set desc = "This disables or enables the user interface buttons which can be used with hotkeys." - - if(hud_used.hotkey_ui_hidden) - client.screen += hud_used.hotkeybuttons - hud_used.hotkey_ui_hidden = FALSE - else - client.screen -= hud_used.hotkeybuttons - hud_used.hotkey_ui_hidden = TRUE +/obj/screen/human + icon = 'icons/mob/screen_midnight.dmi' + +/obj/screen/human/toggle + name = "toggle" + icon_state = "toggle" + +/obj/screen/human/toggle/Click() + + var/mob/targetmob = usr + + if(isobserver(usr)) + if(ishuman(usr.client.eye) && (usr.client.eye != usr)) + var/mob/M = usr.client.eye + targetmob = M + + if(usr.hud_used.inventory_shown && targetmob.hud_used) + usr.hud_used.inventory_shown = FALSE + usr.client.screen -= targetmob.hud_used.toggleable_inventory + else + usr.hud_used.inventory_shown = TRUE + usr.client.screen += targetmob.hud_used.toggleable_inventory + + targetmob.hud_used.hidden_inventory_update(usr) + +/obj/screen/human/equip + name = "equip" + icon_state = "act_equip" + +/obj/screen/human/equip/Click() + if(ismecha(usr.loc)) // stops inventory actions in a mech + return 1 + var/mob/living/carbon/human/H = usr + H.quick_equip() + +/obj/screen/devil + icon = 'icons/mob/screen_devil.dmi' + invisibility = INVISIBILITY_ABSTRACT + +/obj/screen/devil/soul_counter + name = "souls owned" + icon_state = "Devil-6" + screen_loc = ui_devilsouldisplay + +/obj/screen/devil/soul_counter/proc/update_counter(souls = 0) + invisibility = 0 + maptext = "
    [souls]
    " + switch(souls) + if(0,null) + icon_state = "Devil-1" + if(1,2) + icon_state = "Devil-2" + if(3 to 5) + icon_state = "Devil-3" + if(6 to 8) + icon_state = "Devil-4" + if(9 to INFINITY) + icon_state = "Devil-5" + else + icon_state = "Devil-6" + +/obj/screen/devil/soul_counter/proc/clear() + invisibility = INVISIBILITY_ABSTRACT + +/obj/screen/ling + icon = 'icons/mob/screen_changeling.dmi' + invisibility = INVISIBILITY_ABSTRACT + +/obj/screen/ling/sting + name = "current sting" + screen_loc = ui_lingstingdisplay + +/obj/screen/ling/sting/Click() + if(isobserver(usr)) + return + var/mob/living/carbon/U = usr + U.unset_sting() + +/obj/screen/ling/chems + name = "chemical storage" + icon_state = "power_display" + screen_loc = ui_lingchemdisplay + +/datum/hud/human/New(mob/living/carbon/human/owner) + ..() + + var/widescreen_layout = FALSE + if(owner.client?.prefs?.widescreenpref) + widescreen_layout = TRUE + + var/obj/screen/using + var/obj/screen/inventory/inv_box + + using = new/obj/screen/language_menu + using.icon = ui_style + if(!widescreen_layout) + using.screen_loc = UI_BOXLANG + using.hud = src + static_inventory += using + + using = new/obj/screen/skills + using.icon = ui_style + if(!widescreen_layout) + using.screen_loc = UI_BOXLANG + static_inventory += using + + using = new /obj/screen/area_creator + using.icon = ui_style + if(!widescreen_layout) + using.screen_loc = UI_BOXAREA + using.hud = src + static_inventory += using + + action_intent = new /obj/screen/act_intent/segmented + action_intent.icon_state = mymob.a_intent + action_intent.hud = src + static_inventory += action_intent + + using = new /obj/screen/mov_intent + using.icon = ui_style + using.icon_state = (mymob.m_intent == MOVE_INTENT_RUN ? "running" : "walking") + using.screen_loc = ui_movi + using.hud = src + static_inventory += using + + using = new /obj/screen/drop() + using.icon = ui_style + using.screen_loc = ui_drop_throw + using.hud = src + static_inventory += using + + inv_box = new /obj/screen/inventory() + inv_box.name = "i_clothing" + inv_box.icon = ui_style + inv_box.slot_id = ITEM_SLOT_ICLOTHING + inv_box.icon_state = "uniform" + inv_box.screen_loc = ui_iclothing + inv_box.hud = src + toggleable_inventory += inv_box + + inv_box = new /obj/screen/inventory() + inv_box.name = "o_clothing" + inv_box.icon = ui_style + inv_box.slot_id = ITEM_SLOT_OCLOTHING + inv_box.icon_state = "suit" + inv_box.screen_loc = ui_oclothing + inv_box.hud = src + toggleable_inventory += inv_box + + build_hand_slots() + + using = new /obj/screen/swap_hand() + using.icon = ui_style + using.icon_state = "swap_1" + using.screen_loc = ui_swaphand_position(owner,1) + using.hud = src + static_inventory += using + + using = new /obj/screen/swap_hand() + using.icon = ui_style + using.icon_state = "swap_2" + using.screen_loc = ui_swaphand_position(owner,2) + using.hud = src + static_inventory += using + + inv_box = new /obj/screen/inventory() + inv_box.name = "id" + inv_box.icon = ui_style + inv_box.icon_state = "id" + inv_box.screen_loc = ui_id + inv_box.slot_id = ITEM_SLOT_ID + inv_box.hud = src + static_inventory += inv_box + + inv_box = new /obj/screen/inventory() + inv_box.name = "mask" + inv_box.icon = ui_style + inv_box.icon_state = "mask" + inv_box.screen_loc = ui_mask + inv_box.slot_id = ITEM_SLOT_MASK + inv_box.hud = src + toggleable_inventory += inv_box + + inv_box = new /obj/screen/inventory() + inv_box.name = "neck" + inv_box.icon = ui_style + inv_box.icon_state = "neck" + inv_box.screen_loc = ui_neck + inv_box.slot_id = ITEM_SLOT_NECK + inv_box.hud = src + toggleable_inventory += inv_box + + inv_box = new /obj/screen/inventory() + inv_box.name = "back" + inv_box.icon = ui_style + inv_box.icon_state = "back" + inv_box.screen_loc = ui_back + inv_box.slot_id = ITEM_SLOT_BACK + inv_box.hud = src + static_inventory += inv_box + + inv_box = new /obj/screen/inventory() + inv_box.name = "storage1" + inv_box.icon = ui_style + inv_box.icon_state = "pocket" + inv_box.screen_loc = ui_storage1 + inv_box.slot_id = ITEM_SLOT_LPOCKET + inv_box.hud = src + static_inventory += inv_box + + inv_box = new /obj/screen/inventory() + inv_box.name = "storage2" + inv_box.icon = ui_style + inv_box.icon_state = "pocket" + inv_box.screen_loc = ui_storage2 + inv_box.slot_id = ITEM_SLOT_RPOCKET + inv_box.hud = src + static_inventory += inv_box + + inv_box = new /obj/screen/inventory() + inv_box.name = "suit storage" + inv_box.icon = ui_style + inv_box.icon_state = "suit_storage" + inv_box.screen_loc = ui_sstore1 + inv_box.slot_id = ITEM_SLOT_SUITSTORE + inv_box.hud = src + static_inventory += inv_box + + using = new /obj/screen/resist() + using.icon = ui_style + using.screen_loc = ui_above_intent + using.hud = src + hotkeybuttons += using + + using = new /obj/screen/human/toggle() + using.icon = ui_style + using.screen_loc = ui_inventory + using.hud = src + static_inventory += using + + using = new /obj/screen/human/equip() + using.icon = ui_style + using.screen_loc = ui_equip_position(mymob) + using.hud = src + static_inventory += using + + inv_box = new /obj/screen/inventory() + inv_box.name = "gloves" + inv_box.icon = ui_style + inv_box.icon_state = "gloves" + inv_box.screen_loc = ui_gloves + inv_box.slot_id = ITEM_SLOT_GLOVES + inv_box.hud = src + toggleable_inventory += inv_box + + inv_box = new /obj/screen/inventory() + inv_box.name = "eyes" + inv_box.icon = ui_style + inv_box.icon_state = "glasses" + inv_box.screen_loc = ui_glasses + inv_box.slot_id = ITEM_SLOT_EYES + inv_box.hud = src + toggleable_inventory += inv_box + + inv_box = new /obj/screen/inventory() + inv_box.name = "ears" + inv_box.icon = ui_style + inv_box.icon_state = "ears" + inv_box.screen_loc = ui_ears + inv_box.slot_id = ITEM_SLOT_EARS + inv_box.hud = src + toggleable_inventory += inv_box + + inv_box = new /obj/screen/inventory() + inv_box.name = "head" + inv_box.icon = ui_style + inv_box.icon_state = "head" + inv_box.screen_loc = ui_head + inv_box.slot_id = ITEM_SLOT_HEAD + inv_box.hud = src + toggleable_inventory += inv_box + + inv_box = new /obj/screen/inventory() + inv_box.name = "shoes" + inv_box.icon = ui_style + inv_box.icon_state = "shoes" + inv_box.screen_loc = ui_shoes + inv_box.slot_id = ITEM_SLOT_FEET + inv_box.hud = src + toggleable_inventory += inv_box + + inv_box = new /obj/screen/inventory() + inv_box.name = "belt" + inv_box.icon = ui_style + inv_box.icon_state = "belt" +// inv_box.icon_full = "template_small" + inv_box.screen_loc = ui_belt + inv_box.slot_id = ITEM_SLOT_BELT + inv_box.hud = src + static_inventory += inv_box + + throw_icon = new /obj/screen/throw_catch() + throw_icon.icon = ui_style + throw_icon.screen_loc = ui_drop_throw + throw_icon.hud = src + hotkeybuttons += throw_icon + + rest_icon = new /obj/screen/rest() + rest_icon.icon = ui_style + rest_icon.screen_loc = ui_above_movement + rest_icon.hud = src + static_inventory += rest_icon + + internals = new /obj/screen/internals() + internals.hud = src + infodisplay += internals + + healths = new /obj/screen/healths() + healths.hud = src + infodisplay += healths + + healthdoll = new /obj/screen/healthdoll() + healthdoll.hud = src + infodisplay += healthdoll + + pull_icon = new /obj/screen/pull() + pull_icon.icon = ui_style + pull_icon.update_icon() + pull_icon.screen_loc = ui_above_intent + pull_icon.hud = src + static_inventory += pull_icon + + lingchemdisplay = new /obj/screen/ling/chems() + lingchemdisplay.hud = src + infodisplay += lingchemdisplay + + lingstingdisplay = new /obj/screen/ling/sting() + lingstingdisplay.hud = src + infodisplay += lingstingdisplay + + devilsouldisplay = new /obj/screen/devil/soul_counter + devilsouldisplay.hud = src + infodisplay += devilsouldisplay + + zone_select = new /obj/screen/zone_sel() + zone_select.icon = ui_style + zone_select.hud = src + zone_select.update_icon() + static_inventory += zone_select + + for(var/obj/screen/inventory/inv in (static_inventory + toggleable_inventory)) + if(inv.slot_id) + inv.hud = src + inv_slots[TOBITSHIFT(inv.slot_id) + 1] = inv + inv.update_icon() + + update_locked_slots() + +/datum/hud/human/update_locked_slots() + if(!mymob) + return + var/mob/living/carbon/human/H = mymob + if(!istype(H) || !H.dna.species) + return + var/datum/species/S = H.dna.species + for(var/obj/screen/inventory/inv in (static_inventory + toggleable_inventory)) + if(inv.slot_id) + if(inv.slot_id in S.no_equip) + inv.alpha = 128 + else + inv.alpha = initial(inv.alpha) + +/datum/hud/human/hidden_inventory_update(mob/viewer) + if(!mymob) + return + var/mob/living/carbon/human/H = mymob + + var/mob/screenmob = viewer || H + + if(screenmob.hud_used.inventory_shown && screenmob.hud_used.hud_shown) + if(H.shoes) + H.shoes.screen_loc = ui_shoes + screenmob.client.screen += H.shoes + if(H.gloves) + H.gloves.screen_loc = ui_gloves + screenmob.client.screen += H.gloves + if(H.ears) + H.ears.screen_loc = ui_ears + screenmob.client.screen += H.ears + if(H.glasses) + H.glasses.screen_loc = ui_glasses + screenmob.client.screen += H.glasses + if(H.w_uniform) + H.w_uniform.screen_loc = ui_iclothing + screenmob.client.screen += H.w_uniform + if(H.wear_suit) + H.wear_suit.screen_loc = ui_oclothing + screenmob.client.screen += H.wear_suit + if(H.wear_mask) + H.wear_mask.screen_loc = ui_mask + screenmob.client.screen += H.wear_mask + if(H.wear_neck) + H.wear_neck.screen_loc = ui_neck + screenmob.client.screen += H.wear_neck + if(H.head) + H.head.screen_loc = ui_head + screenmob.client.screen += H.head + else + if(H.shoes) screenmob.client.screen -= H.shoes + if(H.gloves) screenmob.client.screen -= H.gloves + if(H.ears) screenmob.client.screen -= H.ears + if(H.glasses) screenmob.client.screen -= H.glasses + if(H.w_uniform) screenmob.client.screen -= H.w_uniform + if(H.wear_suit) screenmob.client.screen -= H.wear_suit + if(H.wear_mask) screenmob.client.screen -= H.wear_mask + if(H.wear_neck) screenmob.client.screen -= H.wear_neck + if(H.head) screenmob.client.screen -= H.head + + + +/datum/hud/human/persistent_inventory_update(mob/viewer) + if(!mymob) + return + ..() + var/mob/living/carbon/human/H = mymob + + var/mob/screenmob = viewer || H + + if(screenmob.hud_used) + if(screenmob.hud_used.hud_shown) + if(H.s_store) + H.s_store.screen_loc = ui_sstore1 + screenmob.client.screen += H.s_store + if(H.wear_id) + H.wear_id.screen_loc = ui_id + screenmob.client.screen += H.wear_id + if(H.belt) + H.belt.screen_loc = ui_belt + screenmob.client.screen += H.belt + if(H.back) + H.back.screen_loc = ui_back + screenmob.client.screen += H.back + if(H.l_store) + H.l_store.screen_loc = ui_storage1 + screenmob.client.screen += H.l_store + if(H.r_store) + H.r_store.screen_loc = ui_storage2 + screenmob.client.screen += H.r_store + else + if(H.s_store) + screenmob.client.screen -= H.s_store + if(H.wear_id) + screenmob.client.screen -= H.wear_id + if(H.belt) + screenmob.client.screen -= H.belt + if(H.back) + screenmob.client.screen -= H.back + if(H.l_store) + screenmob.client.screen -= H.l_store + if(H.r_store) + screenmob.client.screen -= H.r_store + + if(hud_version != HUD_STYLE_NOHUD) + for(var/obj/item/I in H.held_items) + I.screen_loc = ui_hand_position(H.get_held_index_of_item(I)) + screenmob.client.screen += I + else + for(var/obj/item/I in H.held_items) + I.screen_loc = null + screenmob.client.screen -= I + + +/mob/living/carbon/human/verb/toggle_hotkey_verbs() + set category = "OOC" + set name = "Toggle hotkey buttons" + set desc = "This disables or enables the user interface buttons which can be used with hotkeys." + + if(hud_used.hotkey_ui_hidden) + client.screen += hud_used.hotkeybuttons + hud_used.hotkey_ui_hidden = FALSE + else + client.screen -= hud_used.hotkeybuttons + hud_used.hotkey_ui_hidden = TRUE diff --git a/code/_onclick/hud/monkey.dm b/code/_onclick/hud/monkey.dm index ebf48177ec22..23da9c28172d 100644 --- a/code/_onclick/hud/monkey.dm +++ b/code/_onclick/hud/monkey.dm @@ -1,169 +1,169 @@ -/datum/hud/monkey/New(mob/living/carbon/monkey/owner) - ..() - var/obj/screen/using - var/obj/screen/inventory/inv_box - - action_intent = new /obj/screen/act_intent() - action_intent.icon = ui_style - action_intent.icon_state = mymob.a_intent - action_intent.screen_loc = ui_acti - action_intent.hud = src - static_inventory += action_intent - - using = new /obj/screen/mov_intent() - using.icon = ui_style - using.icon_state = (mymob.m_intent == MOVE_INTENT_RUN ? "running" : "walking") - using.screen_loc = ui_movi - using.hud = src - static_inventory += using - - using = new/obj/screen/language_menu - using.icon = ui_style - using.hud = src - static_inventory += using - - using = new /obj/screen/drop() - using.icon = ui_style - using.screen_loc = ui_drop_throw - using.hud = src - static_inventory += using - - build_hand_slots() - - using = new /obj/screen/swap_hand() - using.icon = ui_style - using.icon_state = "swap_1_m" //extra wide! - using.screen_loc = ui_swaphand_position(owner,1) - using.hud = src - static_inventory += using - - using = new /obj/screen/swap_hand() - using.icon = ui_style - using.icon_state = "swap_2" - using.screen_loc = ui_swaphand_position(owner,2) - using.hud = src - static_inventory += using - - inv_box = new /obj/screen/inventory() - inv_box.name = "mask" - inv_box.icon = ui_style - inv_box.icon_state = "mask" -// inv_box.icon_full = "template" - inv_box.screen_loc = ui_monkey_mask - inv_box.slot_id = ITEM_SLOT_MASK - inv_box.hud = src - static_inventory += inv_box - - inv_box = new /obj/screen/inventory() - inv_box.name = "neck" - inv_box.icon = ui_style - inv_box.icon_state = "neck" -// inv_box.icon_full = "template" - inv_box.screen_loc = ui_monkey_neck - inv_box.slot_id = ITEM_SLOT_NECK - inv_box.hud = src - static_inventory += inv_box - - inv_box = new /obj/screen/inventory() - inv_box.name = "head" - inv_box.icon = ui_style - inv_box.icon_state = "head" -// inv_box.icon_full = "template" - inv_box.screen_loc = ui_monkey_head - inv_box.slot_id = ITEM_SLOT_HEAD - inv_box.hud = src - static_inventory += inv_box - - inv_box = new /obj/screen/inventory() - inv_box.name = "back" - inv_box.icon = ui_style - inv_box.icon_state = "back" - inv_box.screen_loc = ui_monkey_back - inv_box.slot_id = ITEM_SLOT_BACK - inv_box.hud = src - static_inventory += inv_box - - throw_icon = new /obj/screen/throw_catch() - throw_icon.icon = ui_style - throw_icon.screen_loc = ui_drop_throw - throw_icon.hud = src - hotkeybuttons += throw_icon - - internals = new /obj/screen/internals() - internals.hud = src - infodisplay += internals - - healths = new /obj/screen/healths() - healths.hud = src - infodisplay += healths - - pull_icon = new /obj/screen/pull() - pull_icon.icon = ui_style - pull_icon.update_icon() - pull_icon.screen_loc = ui_above_movement - pull_icon.hud = src - static_inventory += pull_icon - - lingchemdisplay = new /obj/screen/ling/chems() - lingchemdisplay.hud = src - infodisplay += lingchemdisplay - - lingstingdisplay = new /obj/screen/ling/sting() - lingstingdisplay.hud = src - infodisplay += lingstingdisplay - - - zone_select = new /obj/screen/zone_sel() - zone_select.icon = ui_style - zone_select.hud = src - zone_select.update_icon() - static_inventory += zone_select - - mymob.client.screen = list() - - using = new /obj/screen/resist() - using.icon = ui_style - using.screen_loc = ui_above_intent - using.hud = src - hotkeybuttons += using - - for(var/obj/screen/inventory/inv in (static_inventory + toggleable_inventory)) - if(inv.slot_id) - inv.hud = src - inv_slots[TOBITSHIFT(inv.slot_id) + 1] = inv - inv.update_icon() - -/datum/hud/monkey/persistent_inventory_update() - if(!mymob) - return - var/mob/living/carbon/monkey/M = mymob - - if(hud_shown) - if(M.back) - M.back.screen_loc = ui_monkey_back - M.client.screen += M.back - if(M.wear_mask) - M.wear_mask.screen_loc = ui_monkey_mask - M.client.screen += M.wear_mask - if(M.wear_neck) - M.wear_neck.screen_loc = ui_monkey_neck - M.client.screen += M.wear_neck - if(M.head) - M.head.screen_loc = ui_monkey_head - M.client.screen += M.head - else - if(M.back) - M.back.screen_loc = null - if(M.wear_mask) - M.wear_mask.screen_loc = null - if(M.head) - M.head.screen_loc = null - - if(hud_version != HUD_STYLE_NOHUD) - for(var/obj/item/I in M.held_items) - I.screen_loc = ui_hand_position(M.get_held_index_of_item(I)) - M.client.screen += I - else - for(var/obj/item/I in M.held_items) - I.screen_loc = null - M.client.screen -= I +/datum/hud/monkey/New(mob/living/carbon/monkey/owner) + ..() + var/obj/screen/using + var/obj/screen/inventory/inv_box + + action_intent = new /obj/screen/act_intent() + action_intent.icon = ui_style + action_intent.icon_state = mymob.a_intent + action_intent.screen_loc = ui_acti + action_intent.hud = src + static_inventory += action_intent + + using = new /obj/screen/mov_intent() + using.icon = ui_style + using.icon_state = (mymob.m_intent == MOVE_INTENT_RUN ? "running" : "walking") + using.screen_loc = ui_movi + using.hud = src + static_inventory += using + + using = new/obj/screen/language_menu + using.icon = ui_style + using.hud = src + static_inventory += using + + using = new /obj/screen/drop() + using.icon = ui_style + using.screen_loc = ui_drop_throw + using.hud = src + static_inventory += using + + build_hand_slots() + + using = new /obj/screen/swap_hand() + using.icon = ui_style + using.icon_state = "swap_1_m" //extra wide! + using.screen_loc = ui_swaphand_position(owner,1) + using.hud = src + static_inventory += using + + using = new /obj/screen/swap_hand() + using.icon = ui_style + using.icon_state = "swap_2" + using.screen_loc = ui_swaphand_position(owner,2) + using.hud = src + static_inventory += using + + inv_box = new /obj/screen/inventory() + inv_box.name = "mask" + inv_box.icon = ui_style + inv_box.icon_state = "mask" +// inv_box.icon_full = "template" + inv_box.screen_loc = ui_monkey_mask + inv_box.slot_id = ITEM_SLOT_MASK + inv_box.hud = src + static_inventory += inv_box + + inv_box = new /obj/screen/inventory() + inv_box.name = "neck" + inv_box.icon = ui_style + inv_box.icon_state = "neck" +// inv_box.icon_full = "template" + inv_box.screen_loc = ui_monkey_neck + inv_box.slot_id = ITEM_SLOT_NECK + inv_box.hud = src + static_inventory += inv_box + + inv_box = new /obj/screen/inventory() + inv_box.name = "head" + inv_box.icon = ui_style + inv_box.icon_state = "head" +// inv_box.icon_full = "template" + inv_box.screen_loc = ui_monkey_head + inv_box.slot_id = ITEM_SLOT_HEAD + inv_box.hud = src + static_inventory += inv_box + + inv_box = new /obj/screen/inventory() + inv_box.name = "back" + inv_box.icon = ui_style + inv_box.icon_state = "back" + inv_box.screen_loc = ui_monkey_back + inv_box.slot_id = ITEM_SLOT_BACK + inv_box.hud = src + static_inventory += inv_box + + throw_icon = new /obj/screen/throw_catch() + throw_icon.icon = ui_style + throw_icon.screen_loc = ui_drop_throw + throw_icon.hud = src + hotkeybuttons += throw_icon + + internals = new /obj/screen/internals() + internals.hud = src + infodisplay += internals + + healths = new /obj/screen/healths() + healths.hud = src + infodisplay += healths + + pull_icon = new /obj/screen/pull() + pull_icon.icon = ui_style + pull_icon.update_icon() + pull_icon.screen_loc = ui_above_movement + pull_icon.hud = src + static_inventory += pull_icon + + lingchemdisplay = new /obj/screen/ling/chems() + lingchemdisplay.hud = src + infodisplay += lingchemdisplay + + lingstingdisplay = new /obj/screen/ling/sting() + lingstingdisplay.hud = src + infodisplay += lingstingdisplay + + + zone_select = new /obj/screen/zone_sel() + zone_select.icon = ui_style + zone_select.hud = src + zone_select.update_icon() + static_inventory += zone_select + + mymob.client.screen = list() + + using = new /obj/screen/resist() + using.icon = ui_style + using.screen_loc = ui_above_intent + using.hud = src + hotkeybuttons += using + + for(var/obj/screen/inventory/inv in (static_inventory + toggleable_inventory)) + if(inv.slot_id) + inv.hud = src + inv_slots[TOBITSHIFT(inv.slot_id) + 1] = inv + inv.update_icon() + +/datum/hud/monkey/persistent_inventory_update() + if(!mymob) + return + var/mob/living/carbon/monkey/M = mymob + + if(hud_shown) + if(M.back) + M.back.screen_loc = ui_monkey_back + M.client.screen += M.back + if(M.wear_mask) + M.wear_mask.screen_loc = ui_monkey_mask + M.client.screen += M.wear_mask + if(M.wear_neck) + M.wear_neck.screen_loc = ui_monkey_neck + M.client.screen += M.wear_neck + if(M.head) + M.head.screen_loc = ui_monkey_head + M.client.screen += M.head + else + if(M.back) + M.back.screen_loc = null + if(M.wear_mask) + M.wear_mask.screen_loc = null + if(M.head) + M.head.screen_loc = null + + if(hud_version != HUD_STYLE_NOHUD) + for(var/obj/item/I in M.held_items) + I.screen_loc = ui_hand_position(M.get_held_index_of_item(I)) + M.client.screen += I + else + for(var/obj/item/I in M.held_items) + I.screen_loc = null + M.client.screen -= I diff --git a/code/_onclick/hud/robot.dm b/code/_onclick/hud/robot.dm index 03f30ab88b44..fcc46d5c0bbf 100644 --- a/code/_onclick/hud/robot.dm +++ b/code/_onclick/hud/robot.dm @@ -1,287 +1,287 @@ -/obj/screen/robot - icon = 'icons/mob/screen_cyborg.dmi' - -/obj/screen/robot/module - name = "cyborg module" - icon_state = "nomod" - -/obj/screen/robot/Click() - if(isobserver(usr)) - return 1 - -/obj/screen/robot/module/Click() - if(..()) - return - var/mob/living/silicon/robot/R = usr - if(R.module.type != /obj/item/robot_module) - R.hud_used.toggle_show_robot_modules() - return 1 - R.pick_module() - -/obj/screen/robot/module1 - name = "module1" - icon_state = "inv1" - -/obj/screen/robot/module1/Click() - if(..()) - return - var/mob/living/silicon/robot/R = usr - R.toggle_module(1) - -/obj/screen/robot/module2 - name = "module2" - icon_state = "inv2" - -/obj/screen/robot/module2/Click() - if(..()) - return - var/mob/living/silicon/robot/R = usr - R.toggle_module(2) - -/obj/screen/robot/module3 - name = "module3" - icon_state = "inv3" - -/obj/screen/robot/module3/Click() - if(..()) - return - var/mob/living/silicon/robot/R = usr - R.toggle_module(3) - -/obj/screen/robot/radio - name = "radio" - icon_state = "radio" - -/obj/screen/robot/radio/Click() - if(..()) - return - var/mob/living/silicon/robot/R = usr - R.radio.interact(R) - -/obj/screen/robot/store - name = "store" - icon_state = "store" - -/obj/screen/robot/store/Click() - if(..()) - return - 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() - -/datum/hud/robot - ui_style = 'icons/mob/screen_cyborg.dmi' - -/datum/hud/robot/New(mob/owner) - ..() - var/mob/living/silicon/robot/mymobR = mymob - var/obj/screen/using - - using = new/obj/screen/language_menu - using.screen_loc = ui_borg_language_menu - static_inventory += using - -//Radio - using = new /obj/screen/robot/radio() - using.screen_loc = ui_borg_radio - using.hud = src - 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 - - using = new /obj/screen/robot/module2() - using.screen_loc = ui_inv2 - using.hud = src - static_inventory += using - mymobR.inv2 = using - - using = new /obj/screen/robot/module3() - using.screen_loc = ui_inv3 - using.hud = src - static_inventory += using - mymobR.inv3 = using - -//End of module select - -//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/ai/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 - 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 - -//Intent - action_intent = new /obj/screen/act_intent/robot() - action_intent.icon_state = mymob.a_intent - action_intent.hud = src - static_inventory += action_intent - -//Health - healths = new /obj/screen/healths/robot() - healths.hud = src - infodisplay += healths - -//Installed Module - mymobR.hands = new /obj/screen/robot/module() - mymobR.hands.screen_loc = ui_borg_module - mymobR.hands.hud = src - static_inventory += mymobR.hands - -//Store - module_store_icon = new /obj/screen/robot/store() - module_store_icon.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() - hotkeybuttons += pull_icon - - - zone_select = new /obj/screen/zone_sel/robot() - zone_select.hud = src - zone_select.update_icon() - static_inventory += zone_select - - -/datum/hud/proc/toggle_show_robot_modules() - if(!iscyborg(mymob)) - return - - var/mob/living/silicon/robot/R = mymob - - R.shown_robot_modules = !R.shown_robot_modules - update_robot_modules_display() - -/datum/hud/proc/update_robot_modules_display(mob/viewer) - if(!iscyborg(mymob)) - return - - var/mob/living/silicon/robot/R = mymob - - var/mob/screenmob = viewer || R - - if(!R.module) - return - - if(!R.client) - return - - if(R.shown_robot_modules && screenmob.hud_used.hud_shown) - //Modules display is shown - screenmob.client.screen += module_store_icon //"store" icon - - if(!R.module.modules) - 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) - 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 - - var/x = -4 //Start at CENTER-4,SOUTH+1 - var/y = 1 - - for(var/atom/movable/A in R.module.get_inactive_modules()) - //Module is not currently active - screenmob.client.screen += A - if(x < 0) - A.screen_loc = "CENTER[x]:16,SOUTH+[y]:7" - else - A.screen_loc = "CENTER+[x]:16,SOUTH+[y]:7" - A.layer = ABOVE_HUD_LAYER - A.plane = ABOVE_HUD_PLANE - - x++ - if(x == 4) - x = -4 - y++ - - else - //Modules display is hidden - screenmob.client.screen -= module_store_icon //"store" icon - - for(var/atom/A in R.module.get_inactive_modules()) - //Module is not currently active - screenmob.client.screen -= A - R.shown_robot_modules = 0 - screenmob.client.screen -= R.robot_modules_background - -/datum/hud/robot/persistent_inventory_update(mob/viewer) - if(!mymob) - return - var/mob/living/silicon/robot/R = mymob - - var/mob/screenmob = viewer || R - - if(screenmob.hud_used) - if(screenmob.hud_used.hud_shown) - for(var/i in 1 to R.held_items.len) - var/obj/item/I = R.held_items[i] - if(I) - switch(i) - if(1) - I.screen_loc = ui_inv1 - if(2) - I.screen_loc = ui_inv2 - if(3) - I.screen_loc = ui_inv3 - else - return - screenmob.client.screen += I - else - for(var/obj/item/I in R.held_items) - screenmob.client.screen -= I +/obj/screen/robot + icon = 'icons/mob/screen_cyborg.dmi' + +/obj/screen/robot/module + name = "cyborg module" + icon_state = "nomod" + +/obj/screen/robot/Click() + if(isobserver(usr)) + return 1 + +/obj/screen/robot/module/Click() + if(..()) + return + var/mob/living/silicon/robot/R = usr + if(R.module.type != /obj/item/robot_module) + R.hud_used.toggle_show_robot_modules() + return 1 + R.pick_module() + +/obj/screen/robot/module1 + name = "module1" + icon_state = "inv1" + +/obj/screen/robot/module1/Click() + if(..()) + return + var/mob/living/silicon/robot/R = usr + R.toggle_module(1) + +/obj/screen/robot/module2 + name = "module2" + icon_state = "inv2" + +/obj/screen/robot/module2/Click() + if(..()) + return + var/mob/living/silicon/robot/R = usr + R.toggle_module(2) + +/obj/screen/robot/module3 + name = "module3" + icon_state = "inv3" + +/obj/screen/robot/module3/Click() + if(..()) + return + var/mob/living/silicon/robot/R = usr + R.toggle_module(3) + +/obj/screen/robot/radio + name = "radio" + icon_state = "radio" + +/obj/screen/robot/radio/Click() + if(..()) + return + var/mob/living/silicon/robot/R = usr + R.radio.interact(R) + +/obj/screen/robot/store + name = "store" + icon_state = "store" + +/obj/screen/robot/store/Click() + if(..()) + return + 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() + +/datum/hud/robot + ui_style = 'icons/mob/screen_cyborg.dmi' + +/datum/hud/robot/New(mob/owner) + ..() + var/mob/living/silicon/robot/mymobR = mymob + var/obj/screen/using + + using = new/obj/screen/language_menu + using.screen_loc = ui_borg_language_menu + static_inventory += using + +//Radio + using = new /obj/screen/robot/radio() + using.screen_loc = ui_borg_radio + using.hud = src + 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 + + using = new /obj/screen/robot/module2() + using.screen_loc = ui_inv2 + using.hud = src + static_inventory += using + mymobR.inv2 = using + + using = new /obj/screen/robot/module3() + using.screen_loc = ui_inv3 + using.hud = src + static_inventory += using + mymobR.inv3 = using + +//End of module select + +//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/ai/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 + 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 + +//Intent + action_intent = new /obj/screen/act_intent/robot() + action_intent.icon_state = mymob.a_intent + action_intent.hud = src + static_inventory += action_intent + +//Health + healths = new /obj/screen/healths/robot() + healths.hud = src + infodisplay += healths + +//Installed Module + mymobR.hands = new /obj/screen/robot/module() + mymobR.hands.screen_loc = ui_borg_module + mymobR.hands.hud = src + static_inventory += mymobR.hands + +//Store + module_store_icon = new /obj/screen/robot/store() + module_store_icon.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() + hotkeybuttons += pull_icon + + + zone_select = new /obj/screen/zone_sel/robot() + zone_select.hud = src + zone_select.update_icon() + static_inventory += zone_select + + +/datum/hud/proc/toggle_show_robot_modules() + if(!iscyborg(mymob)) + return + + var/mob/living/silicon/robot/R = mymob + + R.shown_robot_modules = !R.shown_robot_modules + update_robot_modules_display() + +/datum/hud/proc/update_robot_modules_display(mob/viewer) + if(!iscyborg(mymob)) + return + + var/mob/living/silicon/robot/R = mymob + + var/mob/screenmob = viewer || R + + if(!R.module) + return + + if(!R.client) + return + + if(R.shown_robot_modules && screenmob.hud_used.hud_shown) + //Modules display is shown + screenmob.client.screen += module_store_icon //"store" icon + + if(!R.module.modules) + 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) + 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 + + var/x = -4 //Start at CENTER-4,SOUTH+1 + var/y = 1 + + for(var/atom/movable/A in R.module.get_inactive_modules()) + //Module is not currently active + screenmob.client.screen += A + if(x < 0) + A.screen_loc = "CENTER[x]:16,SOUTH+[y]:7" + else + A.screen_loc = "CENTER+[x]:16,SOUTH+[y]:7" + A.layer = ABOVE_HUD_LAYER + A.plane = ABOVE_HUD_PLANE + + x++ + if(x == 4) + x = -4 + y++ + + else + //Modules display is hidden + screenmob.client.screen -= module_store_icon //"store" icon + + for(var/atom/A in R.module.get_inactive_modules()) + //Module is not currently active + screenmob.client.screen -= A + R.shown_robot_modules = 0 + screenmob.client.screen -= R.robot_modules_background + +/datum/hud/robot/persistent_inventory_update(mob/viewer) + if(!mymob) + return + var/mob/living/silicon/robot/R = mymob + + var/mob/screenmob = viewer || R + + if(screenmob.hud_used) + if(screenmob.hud_used.hud_shown) + for(var/i in 1 to R.held_items.len) + var/obj/item/I = R.held_items[i] + if(I) + switch(i) + if(1) + I.screen_loc = ui_inv1 + if(2) + I.screen_loc = ui_inv2 + if(3) + I.screen_loc = ui_inv3 + else + return + screenmob.client.screen += I + else + for(var/obj/item/I in R.held_items) + screenmob.client.screen -= I diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm index 91915e6669ad..db04032c372c 100644 --- a/code/_onclick/hud/screen_objects.dm +++ b/code/_onclick/hud/screen_objects.dm @@ -1,711 +1,711 @@ -/* - Screen objects - Todo: improve/re-implement - - Screen objects are only used for the hud and should not appear anywhere "in-game". - They are used with the client/screen list and the screen_loc var. - For more information, see the byond documentation on the screen_loc and screen vars. -*/ -/obj/screen - name = "" - icon = 'icons/mob/screen_gen.dmi' - layer = HUD_LAYER - plane = HUD_PLANE - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - appearance_flags = APPEARANCE_UI - var/obj/master = null //A reference to the object in the slot. Grabs or items, generally. - var/datum/hud/hud = null // A reference to the owner HUD, if any. - -/obj/screen/take_damage() - return - -/obj/screen/Destroy() - master = null - hud = null - return ..() - -/obj/screen/examine(mob/user) - return list() - -/obj/screen/orbit() - return - -/obj/screen/proc/component_click(obj/screen/component_button/component, params) - return - -/obj/screen/text - icon = null - icon_state = null - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - screen_loc = "CENTER-7,CENTER-7" - maptext_height = 480 - maptext_width = 480 - -/obj/screen/swap_hand - layer = HUD_LAYER - plane = HUD_PLANE - name = "swap hand" - -/obj/screen/swap_hand/Click() - // At this point in client Click() code we have passed the 1/10 sec check and little else - // We don't even know if it's a middle click - if(world.time <= usr.next_move) - return 1 - - if(usr.incapacitated()) - return 1 - - if(ismob(usr)) - var/mob/M = usr - M.swap_hand() - return 1 - -/obj/screen/skills - name = "skills" - icon = 'icons/mob/screen_midnight.dmi' - icon_state = "skills" - screen_loc = ui_skill_menu - -/obj/screen/skills/Click() - if(ishuman(usr)) - var/mob/living/carbon/human/H = usr - H.mind.print_levels(H) - -/obj/screen/craft - name = "crafting menu" - icon = 'icons/mob/screen_midnight.dmi' - icon_state = "craft" - screen_loc = ui_crafting - -/obj/screen/area_creator - name = "create new area" - icon = 'icons/mob/screen_midnight.dmi' - icon_state = "area_edit" - screen_loc = ui_building - -/obj/screen/area_creator/Click() - if(usr.incapacitated() || (isobserver(usr) && !IsAdminGhost(usr))) - return TRUE - var/area/A = get_area(usr) - if(!A.outdoors) - to_chat(usr, "There is already a defined structure here.") - return TRUE - create_area(usr) - -/obj/screen/language_menu - name = "language menu" - icon = 'icons/mob/screen_midnight.dmi' - icon_state = "talk_wheel" - screen_loc = ui_language_menu - -/obj/screen/language_menu/Click() - var/mob/M = usr - var/datum/language_holder/H = M.get_language_holder() - H.open_language_menu(usr) - -/obj/screen/inventory - var/slot_id // The indentifier for the slot. It has nothing to do with ID cards. - var/icon_empty // Icon when empty. For now used only by humans. - var/icon_full // Icon when contains an item. For now used only by humans. - var/list/object_overlays = list() - layer = HUD_LAYER - plane = HUD_PLANE - -/obj/screen/inventory/Click(location, control, params) - // At this point in client Click() code we have passed the 1/10 sec check and little else - // We don't even know if it's a middle click - if(world.time <= usr.next_move) - return TRUE - - if(usr.incapacitated()) - return TRUE - if(ismecha(usr.loc)) // stops inventory actions in a mech - return TRUE - - if(hud?.mymob && slot_id) - var/obj/item/inv_item = hud.mymob.get_item_by_slot(slot_id) - if(inv_item) - return inv_item.Click(location, control, params) - - if(usr.attack_ui(slot_id)) - usr.update_inv_hands() - return TRUE - -/obj/screen/inventory/MouseEntered() - ..() - add_overlays() - -/obj/screen/inventory/MouseExited() - ..() - cut_overlay(object_overlays) - object_overlays.Cut() - -/obj/screen/inventory/update_icon_state() - if(!icon_empty) - icon_empty = icon_state - - if(hud?.mymob && slot_id && icon_full) - if(hud.mymob.get_item_by_slot(slot_id)) - icon_state = icon_full - else - icon_state = icon_empty - -/obj/screen/inventory/proc/add_overlays() - var/mob/user = hud?.mymob - - if(!user || !slot_id) - return - - var/obj/item/holding = user.get_active_held_item() - - if(!holding || user.get_item_by_slot(slot_id)) - return - - var/image/item_overlay = image(holding) - item_overlay.alpha = 92 - - if(!user.can_equip(holding, slot_id, TRUE)) - item_overlay.color = "#FF0000" - else - item_overlay.color = "#00ff00" - - object_overlays += item_overlay - add_overlay(object_overlays) - -/obj/screen/inventory/hand - var/mutable_appearance/handcuff_overlay - var/static/mutable_appearance/blocked_overlay = mutable_appearance('icons/mob/screen_gen.dmi', "blocked") - var/held_index = 0 - -/obj/screen/inventory/hand/update_overlays() - . = ..() - - if(!handcuff_overlay) - var/state = (!(held_index % 2)) ? "markus" : "gabrielle" - handcuff_overlay = mutable_appearance('icons/mob/screen_gen.dmi', state) - - if(!hud?.mymob) - return - - if(iscarbon(hud.mymob)) - var/mob/living/carbon/C = hud.mymob - if(C.handcuffed) - . += handcuff_overlay - - if(held_index) - if(!C.has_hand_for_held_index(held_index)) - . += blocked_overlay - - if(held_index == hud.mymob.active_hand_index) - . += "hand_active" - - -/obj/screen/inventory/hand/Click(location, control, params) - // At this point in client Click() code we have passed the 1/10 sec check and little else - // We don't even know if it's a middle click - var/mob/user = hud?.mymob - if(usr != user) - return TRUE - if(world.time <= user.next_move) - return TRUE - if(user.incapacitated()) - return TRUE - if (ismecha(user.loc)) // stops inventory actions in a mech - return TRUE - - if(user.active_hand_index == held_index) - var/obj/item/I = user.get_active_held_item() - if(I) - I.Click(location, control, params) - else - user.swap_hand(held_index) - return TRUE - -/obj/screen/close - name = "close" - layer = ABOVE_HUD_LAYER - plane = ABOVE_HUD_PLANE - icon_state = "backpack_close" - -/obj/screen/close/Initialize(mapload, new_master) - . = ..() - master = new_master - -/obj/screen/close/Click() - var/datum/component/storage/S = master - S.hide_from(usr) - return TRUE - -/obj/screen/drop - name = "drop" - icon = 'icons/mob/screen_midnight.dmi' - icon_state = "act_drop" - layer = HUD_LAYER - plane = HUD_PLANE - -/obj/screen/drop/Click() - if(usr.stat == CONSCIOUS) - usr.dropItemToGround(usr.get_active_held_item()) - -/obj/screen/act_intent - name = "intent" - icon_state = "help" - screen_loc = ui_acti - -/obj/screen/act_intent/Click(location, control, params) - usr.a_intent_change(INTENT_HOTKEY_RIGHT) - -/obj/screen/act_intent/segmented/Click(location, control, params) - if(usr.client.prefs.toggles & INTENT_STYLE) - var/_x = text2num(params2list(params)["icon-x"]) - var/_y = text2num(params2list(params)["icon-y"]) - - if(_x<=16 && _y<=16) - usr.a_intent_change(INTENT_HARM) - - else if(_x<=16 && _y>=17) - usr.a_intent_change(INTENT_HELP) - - else if(_x>=17 && _y<=16) - usr.a_intent_change(INTENT_GRAB) - - else if(_x>=17 && _y>=17) - usr.a_intent_change(INTENT_DISARM) - else - return ..() - -/obj/screen/act_intent/alien - icon = 'icons/mob/screen_alien.dmi' - screen_loc = ui_movi - -/obj/screen/act_intent/robot - icon = 'icons/mob/screen_cyborg.dmi' - screen_loc = ui_borg_intents - -/obj/screen/internals - name = "toggle internals" - icon_state = "internal0" - screen_loc = ui_internal - -/obj/screen/internals/Click() - if(!iscarbon(usr)) - return - var/mob/living/carbon/C = usr - if(C.incapacitated()) - return - - if(C.internal) - C.internal = null - to_chat(C, "You are no longer running on internals.") - icon_state = "internal0" - else - if(!C.getorganslot(ORGAN_SLOT_BREATHING_TUBE)) - //Wasp Port Begin - Citadel Internals - var/obj/item/clothing/check - var/internals = FALSE - - for(check in GET_INTERNAL_SLOTS(C)) - if(istype(check, /obj/item/clothing/mask)) - var/obj/item/clothing/mask/M = check - if(M.mask_adjusted) - M.adjustmask(C) - if(check.clothing_flags & ALLOWINTERNALS) - - internals = TRUE - if(!internals) - to_chat(C, "You are not wearing an internals mask!") - return - //Wasp Port End - Citadel Internals - - var/obj/item/I = C.is_holding_item_of_type(/obj/item/tank) - if(I) - to_chat(C, "You are now running on internals from [I] in your [C.get_held_index_name(C.get_held_index_of_item(I))].") - C.internal = I - else if(ishuman(C)) - var/mob/living/carbon/human/H = C - if(istype(H.s_store, /obj/item/tank)) - to_chat(H, "You are now running on internals from [H.s_store] on your [H.wear_suit.name].") - H.internal = H.s_store - else if(istype(H.belt, /obj/item/tank)) - to_chat(H, "You are now running on internals from [H.belt] on your belt.") - H.internal = H.belt - else if(istype(H.l_store, /obj/item/tank)) - to_chat(H, "You are now running on internals from [H.l_store] in your left pocket.") - H.internal = H.l_store - else if(istype(H.r_store, /obj/item/tank)) - to_chat(H, "You are now running on internals from [H.r_store] in your right pocket.") - H.internal = H.r_store - - //Separate so CO2 jetpacks are a little less cumbersome. - if(!C.internal && istype(C.back, /obj/item/tank)) - to_chat(C, "You are now running on internals from [C.back] on your back.") - C.internal = C.back - - if(C.internal) - icon_state = "internal1" - else - to_chat(C, "You don't have an oxygen tank!") - return - C.update_action_buttons_icon() - -/obj/screen/mov_intent - name = "run/walk toggle" - icon = 'icons/mob/screen_midnight.dmi' - icon_state = "running" - -/obj/screen/mov_intent/Click() - toggle(usr) - -/obj/screen/mov_intent/update_icon_state() - switch(hud?.mymob?.m_intent) - if(MOVE_INTENT_WALK) - icon_state = "walking" - if(MOVE_INTENT_RUN) - icon_state = "running" - -/obj/screen/mov_intent/proc/toggle(mob/user) - if(isobserver(user)) - return - user.toggle_move_intent(user) - -/obj/screen/pull - name = "stop pulling" - icon = 'icons/mob/screen_midnight.dmi' - icon_state = "pull" - -/obj/screen/pull/Click() - if(isobserver(usr)) - return - usr.stop_pulling() - -/obj/screen/pull/update_icon_state() - if(hud?.mymob?.pulling) - icon_state = "pull" - else - icon_state = "pull0" - -/obj/screen/resist - name = "resist" - icon = 'icons/mob/screen_midnight.dmi' - icon_state = "act_resist" - layer = HUD_LAYER - plane = HUD_PLANE - -/obj/screen/resist/Click() - if(isliving(usr)) - var/mob/living/L = usr - L.resist() - -/obj/screen/rest - name = "rest" - icon = 'icons/mob/screen_midnight.dmi' - icon_state = "act_rest" - layer = HUD_LAYER - plane = HUD_PLANE - -/obj/screen/rest/Click() - if(isliving(usr)) - var/mob/living/L = usr - L.lay_down() - -/obj/screen/rest/update_icon_state() - var/mob/living/user = hud?.mymob - if(!istype(user)) - return - if(!user.resting) - icon_state = "act_rest" - else - icon_state = "act_rest0" - -/obj/screen/storage - name = "storage" - icon_state = "block" - screen_loc = "7,7 to 10,8" - layer = HUD_LAYER - plane = HUD_PLANE - -/obj/screen/storage/Initialize(mapload, new_master) - . = ..() - master = new_master - -/obj/screen/storage/Click(location, control, params) - if(world.time <= usr.next_move) - return TRUE - if(usr.incapacitated()) - return TRUE - if (ismecha(usr.loc)) // stops inventory actions in a mech - return TRUE - if(master) - var/obj/item/I = usr.get_active_held_item() - if(I) - master.attackby(null, I, usr, params) - return TRUE - -/obj/screen/throw_catch - name = "throw/catch" - icon = 'icons/mob/screen_midnight.dmi' - icon_state = "act_throw_off" - -/obj/screen/throw_catch/Click() - if(iscarbon(usr)) - var/mob/living/carbon/C = usr - C.toggle_throw_mode() - -/obj/screen/zone_sel - name = "damage zone" - icon_state = "zone_sel" - screen_loc = ui_zonesel - var/overlay_icon = 'icons/mob/screen_gen.dmi' - var/static/list/hover_overlays_cache = list() - var/hovering - -/obj/screen/zone_sel/Click(location, control,params) - if(isobserver(usr)) - return - - var/list/PL = params2list(params) - var/icon_x = text2num(PL["icon-x"]) - var/icon_y = text2num(PL["icon-y"]) - var/choice = get_zone_at(icon_x, icon_y) - if (!choice) - return 1 - - return set_selected_zone(choice, usr) - -/obj/screen/zone_sel/MouseEntered(location, control, params) - MouseMove(location, control, params) - -/obj/screen/zone_sel/MouseMove(location, control, params) - if(isobserver(usr)) - return - - var/list/PL = params2list(params) - var/icon_x = text2num(PL["icon-x"]) - var/icon_y = text2num(PL["icon-y"]) - var/choice = get_zone_at(icon_x, icon_y) - - if(hovering == choice) - return - vis_contents -= hover_overlays_cache[hovering] - hovering = choice - - var/obj/effect/overlay/zone_sel/overlay_object = hover_overlays_cache[choice] - if(!overlay_object) - overlay_object = new - overlay_object.icon_state = "[choice]" - hover_overlays_cache[choice] = overlay_object - vis_contents += overlay_object - -/obj/effect/overlay/zone_sel - icon = 'icons/mob/screen_gen.dmi' - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - alpha = 128 - anchored = TRUE - layer = ABOVE_HUD_LAYER - plane = ABOVE_HUD_PLANE - -/obj/screen/zone_sel/MouseExited(location, control, params) - if(!isobserver(usr) && hovering) - vis_contents -= hover_overlays_cache[hovering] - hovering = null - -/obj/screen/zone_sel/proc/get_zone_at(icon_x, icon_y) - switch(icon_y) - if(1 to 9) //Legs - switch(icon_x) - if(10 to 15) - return BODY_ZONE_R_LEG - if(17 to 22) - return BODY_ZONE_L_LEG - if(10 to 13) //Hands and groin - switch(icon_x) - if(8 to 11) - return BODY_ZONE_R_ARM - if(12 to 20) - return BODY_ZONE_PRECISE_GROIN - if(21 to 24) - return BODY_ZONE_L_ARM - if(14 to 22) //Chest and arms to shoulders - switch(icon_x) - if(8 to 11) - return BODY_ZONE_R_ARM - if(12 to 20) - return BODY_ZONE_CHEST - if(21 to 24) - return BODY_ZONE_L_ARM - if(23 to 30) //Head, but we need to check for eye or mouth - if(icon_x in 12 to 20) - switch(icon_y) - if(23 to 24) - if(icon_x in 15 to 17) - return BODY_ZONE_PRECISE_MOUTH - if(26) //Eyeline, eyes are on 15 and 17 - if(icon_x in 14 to 18) - return BODY_ZONE_PRECISE_EYES - if(25 to 27) - if(icon_x in 15 to 17) - return BODY_ZONE_PRECISE_EYES - return BODY_ZONE_HEAD - -/obj/screen/zone_sel/proc/set_selected_zone(choice, mob/user) - if(user != hud?.mymob) - return - - if(choice != hud.mymob.zone_selected) - hud.mymob.zone_selected = choice - update_icon() - - return TRUE - -/obj/screen/zone_sel/update_overlays() - . = ..() - if(!hud?.mymob) - return - . += mutable_appearance(overlay_icon, "[hud.mymob.zone_selected]") - -/obj/screen/zone_sel/alien - icon = 'icons/mob/screen_alien.dmi' - overlay_icon = 'icons/mob/screen_alien.dmi' - -/obj/screen/zone_sel/robot - icon = 'icons/mob/screen_cyborg.dmi' - -/obj/screen/flash - name = "flash" - icon_state = "blank" - blend_mode = BLEND_ADD - screen_loc = "WEST,SOUTH to EAST,NORTH" - layer = FLASH_LAYER - plane = FULLSCREEN_PLANE - -/obj/screen/damageoverlay - icon = 'icons/mob/screen_full.dmi' - icon_state = "oxydamageoverlay0" - name = "dmg" - blend_mode = BLEND_MULTIPLY - screen_loc = "CENTER-7,CENTER-7" - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - layer = UI_DAMAGE_LAYER - plane = FULLSCREEN_PLANE - -/obj/screen/healths - name = "health" - icon_state = "health0" - screen_loc = ui_health - -/obj/screen/healths/alien - icon = 'icons/mob/screen_alien.dmi' - screen_loc = ui_alien_health - -/obj/screen/healths/robot - icon = 'icons/mob/screen_cyborg.dmi' - screen_loc = ui_borg_health - -/obj/screen/healths/blob - name = "blob health" - icon_state = "block" - screen_loc = ui_internal - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - -/obj/screen/healths/blob/naut - name = "health" - icon = 'icons/mob/blob.dmi' - icon_state = "nauthealth" - -/obj/screen/healths/blob/naut/core - name = "overmind health" - icon_state = "corehealth" - screen_loc = ui_health - -/obj/screen/healths/guardian - name = "summoner health" - icon = 'icons/mob/guardian.dmi' - icon_state = "base" - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - -/obj/screen/healths/revenant - name = "essence" - icon = 'icons/mob/actions/backgrounds.dmi' - icon_state = "bg_revenant" - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - -/obj/screen/healths/construct - icon = 'icons/mob/screen_construct.dmi' - icon_state = "artificer_health0" - screen_loc = ui_construct_health - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - -/obj/screen/healthdoll - name = "health doll" - screen_loc = ui_healthdoll - -/obj/screen/healthdoll/Click() - if (iscarbon(usr)) - var/mob/living/carbon/C = usr - C.check_self_for_injuries() - -/obj/screen/healthdoll/living - icon_state = "fullhealth0" - screen_loc = ui_living_healthdoll - var/filtered = FALSE //so we don't repeatedly create the mask of the mob every update - -/obj/screen/mood - name = "mood" - icon_state = "mood5" - screen_loc = ui_mood - -/obj/screen/splash - icon = 'icons/blank_title.png' - icon_state = "" - screen_loc = "1,1" - layer = SPLASHSCREEN_LAYER - plane = SPLASHSCREEN_PLANE - var/client/holder - -/obj/screen/splash/New(client/C, visible, use_previous_title) //TODO: Make this use INITIALIZE_IMMEDIATE, except its not easy - . = ..() - - holder = C - - if(!visible) - alpha = 0 - - if(!use_previous_title) - if(SStitle.icon) - icon = SStitle.icon - else - if(!SStitle.previous_icon) - qdel(src) - return - icon = SStitle.previous_icon - - holder.screen += src - -/obj/screen/splash/proc/Fade(out, qdel_after = TRUE) - if(QDELETED(src)) - return - if(out) - animate(src, alpha = 0, time = 30) - else - alpha = 0 - animate(src, alpha = 255, time = 30) - if(qdel_after) - QDEL_IN(src, 30) - -/obj/screen/splash/Destroy() - if(holder) - holder.screen -= src - holder = null - return ..() - - -/obj/screen/component_button - var/obj/screen/parent - -/obj/screen/component_button/Initialize(mapload, obj/screen/parent) - . = ..() - src.parent = parent - -/obj/screen/component_button/Click(params) - if(parent) - parent.component_click(src, params) +/* + Screen objects + Todo: improve/re-implement + + Screen objects are only used for the hud and should not appear anywhere "in-game". + They are used with the client/screen list and the screen_loc var. + For more information, see the byond documentation on the screen_loc and screen vars. +*/ +/obj/screen + name = "" + icon = 'icons/mob/screen_gen.dmi' + layer = HUD_LAYER + plane = HUD_PLANE + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + appearance_flags = APPEARANCE_UI + var/obj/master = null //A reference to the object in the slot. Grabs or items, generally. + var/datum/hud/hud = null // A reference to the owner HUD, if any. + +/obj/screen/take_damage() + return + +/obj/screen/Destroy() + master = null + hud = null + return ..() + +/obj/screen/examine(mob/user) + return list() + +/obj/screen/orbit() + return + +/obj/screen/proc/component_click(obj/screen/component_button/component, params) + return + +/obj/screen/text + icon = null + icon_state = null + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + screen_loc = "CENTER-7,CENTER-7" + maptext_height = 480 + maptext_width = 480 + +/obj/screen/swap_hand + layer = HUD_LAYER + plane = HUD_PLANE + name = "swap hand" + +/obj/screen/swap_hand/Click() + // At this point in client Click() code we have passed the 1/10 sec check and little else + // We don't even know if it's a middle click + if(world.time <= usr.next_move) + return 1 + + if(usr.incapacitated()) + return 1 + + if(ismob(usr)) + var/mob/M = usr + M.swap_hand() + return 1 + +/obj/screen/skills + name = "skills" + icon = 'icons/mob/screen_midnight.dmi' + icon_state = "skills" + screen_loc = ui_skill_menu + +/obj/screen/skills/Click() + if(ishuman(usr)) + var/mob/living/carbon/human/H = usr + H.mind.print_levels(H) + +/obj/screen/craft + name = "crafting menu" + icon = 'icons/mob/screen_midnight.dmi' + icon_state = "craft" + screen_loc = ui_crafting + +/obj/screen/area_creator + name = "create new area" + icon = 'icons/mob/screen_midnight.dmi' + icon_state = "area_edit" + screen_loc = ui_building + +/obj/screen/area_creator/Click() + if(usr.incapacitated() || (isobserver(usr) && !IsAdminGhost(usr))) + return TRUE + var/area/A = get_area(usr) + if(!A.outdoors) + to_chat(usr, "There is already a defined structure here.") + return TRUE + create_area(usr) + +/obj/screen/language_menu + name = "language menu" + icon = 'icons/mob/screen_midnight.dmi' + icon_state = "talk_wheel" + screen_loc = ui_language_menu + +/obj/screen/language_menu/Click() + var/mob/M = usr + var/datum/language_holder/H = M.get_language_holder() + H.open_language_menu(usr) + +/obj/screen/inventory + var/slot_id // The indentifier for the slot. It has nothing to do with ID cards. + var/icon_empty // Icon when empty. For now used only by humans. + var/icon_full // Icon when contains an item. For now used only by humans. + var/list/object_overlays = list() + layer = HUD_LAYER + plane = HUD_PLANE + +/obj/screen/inventory/Click(location, control, params) + // At this point in client Click() code we have passed the 1/10 sec check and little else + // We don't even know if it's a middle click + if(world.time <= usr.next_move) + return TRUE + + if(usr.incapacitated()) + return TRUE + if(ismecha(usr.loc)) // stops inventory actions in a mech + return TRUE + + if(hud?.mymob && slot_id) + var/obj/item/inv_item = hud.mymob.get_item_by_slot(slot_id) + if(inv_item) + return inv_item.Click(location, control, params) + + if(usr.attack_ui(slot_id)) + usr.update_inv_hands() + return TRUE + +/obj/screen/inventory/MouseEntered() + ..() + add_overlays() + +/obj/screen/inventory/MouseExited() + ..() + cut_overlay(object_overlays) + object_overlays.Cut() + +/obj/screen/inventory/update_icon_state() + if(!icon_empty) + icon_empty = icon_state + + if(hud?.mymob && slot_id && icon_full) + if(hud.mymob.get_item_by_slot(slot_id)) + icon_state = icon_full + else + icon_state = icon_empty + +/obj/screen/inventory/proc/add_overlays() + var/mob/user = hud?.mymob + + if(!user || !slot_id) + return + + var/obj/item/holding = user.get_active_held_item() + + if(!holding || user.get_item_by_slot(slot_id)) + return + + var/image/item_overlay = image(holding) + item_overlay.alpha = 92 + + if(!user.can_equip(holding, slot_id, TRUE)) + item_overlay.color = "#FF0000" + else + item_overlay.color = "#00ff00" + + object_overlays += item_overlay + add_overlay(object_overlays) + +/obj/screen/inventory/hand + var/mutable_appearance/handcuff_overlay + var/static/mutable_appearance/blocked_overlay = mutable_appearance('icons/mob/screen_gen.dmi', "blocked") + var/held_index = 0 + +/obj/screen/inventory/hand/update_overlays() + . = ..() + + if(!handcuff_overlay) + var/state = (!(held_index % 2)) ? "markus" : "gabrielle" + handcuff_overlay = mutable_appearance('icons/mob/screen_gen.dmi', state) + + if(!hud?.mymob) + return + + if(iscarbon(hud.mymob)) + var/mob/living/carbon/C = hud.mymob + if(C.handcuffed) + . += handcuff_overlay + + if(held_index) + if(!C.has_hand_for_held_index(held_index)) + . += blocked_overlay + + if(held_index == hud.mymob.active_hand_index) + . += "hand_active" + + +/obj/screen/inventory/hand/Click(location, control, params) + // At this point in client Click() code we have passed the 1/10 sec check and little else + // We don't even know if it's a middle click + var/mob/user = hud?.mymob + if(usr != user) + return TRUE + if(world.time <= user.next_move) + return TRUE + if(user.incapacitated()) + return TRUE + if (ismecha(user.loc)) // stops inventory actions in a mech + return TRUE + + if(user.active_hand_index == held_index) + var/obj/item/I = user.get_active_held_item() + if(I) + I.Click(location, control, params) + else + user.swap_hand(held_index) + return TRUE + +/obj/screen/close + name = "close" + layer = ABOVE_HUD_LAYER + plane = ABOVE_HUD_PLANE + icon_state = "backpack_close" + +/obj/screen/close/Initialize(mapload, new_master) + . = ..() + master = new_master + +/obj/screen/close/Click() + var/datum/component/storage/S = master + S.hide_from(usr) + return TRUE + +/obj/screen/drop + name = "drop" + icon = 'icons/mob/screen_midnight.dmi' + icon_state = "act_drop" + layer = HUD_LAYER + plane = HUD_PLANE + +/obj/screen/drop/Click() + if(usr.stat == CONSCIOUS) + usr.dropItemToGround(usr.get_active_held_item()) + +/obj/screen/act_intent + name = "intent" + icon_state = "help" + screen_loc = ui_acti + +/obj/screen/act_intent/Click(location, control, params) + usr.a_intent_change(INTENT_HOTKEY_RIGHT) + +/obj/screen/act_intent/segmented/Click(location, control, params) + if(usr.client.prefs.toggles & INTENT_STYLE) + var/_x = text2num(params2list(params)["icon-x"]) + var/_y = text2num(params2list(params)["icon-y"]) + + if(_x<=16 && _y<=16) + usr.a_intent_change(INTENT_HARM) + + else if(_x<=16 && _y>=17) + usr.a_intent_change(INTENT_HELP) + + else if(_x>=17 && _y<=16) + usr.a_intent_change(INTENT_GRAB) + + else if(_x>=17 && _y>=17) + usr.a_intent_change(INTENT_DISARM) + else + return ..() + +/obj/screen/act_intent/alien + icon = 'icons/mob/screen_alien.dmi' + screen_loc = ui_movi + +/obj/screen/act_intent/robot + icon = 'icons/mob/screen_cyborg.dmi' + screen_loc = ui_borg_intents + +/obj/screen/internals + name = "toggle internals" + icon_state = "internal0" + screen_loc = ui_internal + +/obj/screen/internals/Click() + if(!iscarbon(usr)) + return + var/mob/living/carbon/C = usr + if(C.incapacitated()) + return + + if(C.internal) + C.internal = null + to_chat(C, "You are no longer running on internals.") + icon_state = "internal0" + else + if(!C.getorganslot(ORGAN_SLOT_BREATHING_TUBE)) + //Wasp Port Begin - Citadel Internals + var/obj/item/clothing/check + var/internals = FALSE + + for(check in GET_INTERNAL_SLOTS(C)) + if(istype(check, /obj/item/clothing/mask)) + var/obj/item/clothing/mask/M = check + if(M.mask_adjusted) + M.adjustmask(C) + if(check.clothing_flags & ALLOWINTERNALS) + + internals = TRUE + if(!internals) + to_chat(C, "You are not wearing an internals mask!") + return + //Wasp Port End - Citadel Internals + + var/obj/item/I = C.is_holding_item_of_type(/obj/item/tank) + if(I) + to_chat(C, "You are now running on internals from [I] in your [C.get_held_index_name(C.get_held_index_of_item(I))].") + C.internal = I + else if(ishuman(C)) + var/mob/living/carbon/human/H = C + if(istype(H.s_store, /obj/item/tank)) + to_chat(H, "You are now running on internals from [H.s_store] on your [H.wear_suit.name].") + H.internal = H.s_store + else if(istype(H.belt, /obj/item/tank)) + to_chat(H, "You are now running on internals from [H.belt] on your belt.") + H.internal = H.belt + else if(istype(H.l_store, /obj/item/tank)) + to_chat(H, "You are now running on internals from [H.l_store] in your left pocket.") + H.internal = H.l_store + else if(istype(H.r_store, /obj/item/tank)) + to_chat(H, "You are now running on internals from [H.r_store] in your right pocket.") + H.internal = H.r_store + + //Separate so CO2 jetpacks are a little less cumbersome. + if(!C.internal && istype(C.back, /obj/item/tank)) + to_chat(C, "You are now running on internals from [C.back] on your back.") + C.internal = C.back + + if(C.internal) + icon_state = "internal1" + else + to_chat(C, "You don't have an oxygen tank!") + return + C.update_action_buttons_icon() + +/obj/screen/mov_intent + name = "run/walk toggle" + icon = 'icons/mob/screen_midnight.dmi' + icon_state = "running" + +/obj/screen/mov_intent/Click() + toggle(usr) + +/obj/screen/mov_intent/update_icon_state() + switch(hud?.mymob?.m_intent) + if(MOVE_INTENT_WALK) + icon_state = "walking" + if(MOVE_INTENT_RUN) + icon_state = "running" + +/obj/screen/mov_intent/proc/toggle(mob/user) + if(isobserver(user)) + return + user.toggle_move_intent(user) + +/obj/screen/pull + name = "stop pulling" + icon = 'icons/mob/screen_midnight.dmi' + icon_state = "pull" + +/obj/screen/pull/Click() + if(isobserver(usr)) + return + usr.stop_pulling() + +/obj/screen/pull/update_icon_state() + if(hud?.mymob?.pulling) + icon_state = "pull" + else + icon_state = "pull0" + +/obj/screen/resist + name = "resist" + icon = 'icons/mob/screen_midnight.dmi' + icon_state = "act_resist" + layer = HUD_LAYER + plane = HUD_PLANE + +/obj/screen/resist/Click() + if(isliving(usr)) + var/mob/living/L = usr + L.resist() + +/obj/screen/rest + name = "rest" + icon = 'icons/mob/screen_midnight.dmi' + icon_state = "act_rest" + layer = HUD_LAYER + plane = HUD_PLANE + +/obj/screen/rest/Click() + if(isliving(usr)) + var/mob/living/L = usr + L.lay_down() + +/obj/screen/rest/update_icon_state() + var/mob/living/user = hud?.mymob + if(!istype(user)) + return + if(!user.resting) + icon_state = "act_rest" + else + icon_state = "act_rest0" + +/obj/screen/storage + name = "storage" + icon_state = "block" + screen_loc = "7,7 to 10,8" + layer = HUD_LAYER + plane = HUD_PLANE + +/obj/screen/storage/Initialize(mapload, new_master) + . = ..() + master = new_master + +/obj/screen/storage/Click(location, control, params) + if(world.time <= usr.next_move) + return TRUE + if(usr.incapacitated()) + return TRUE + if (ismecha(usr.loc)) // stops inventory actions in a mech + return TRUE + if(master) + var/obj/item/I = usr.get_active_held_item() + if(I) + master.attackby(null, I, usr, params) + return TRUE + +/obj/screen/throw_catch + name = "throw/catch" + icon = 'icons/mob/screen_midnight.dmi' + icon_state = "act_throw_off" + +/obj/screen/throw_catch/Click() + if(iscarbon(usr)) + var/mob/living/carbon/C = usr + C.toggle_throw_mode() + +/obj/screen/zone_sel + name = "damage zone" + icon_state = "zone_sel" + screen_loc = ui_zonesel + var/overlay_icon = 'icons/mob/screen_gen.dmi' + var/static/list/hover_overlays_cache = list() + var/hovering + +/obj/screen/zone_sel/Click(location, control,params) + if(isobserver(usr)) + return + + var/list/PL = params2list(params) + var/icon_x = text2num(PL["icon-x"]) + var/icon_y = text2num(PL["icon-y"]) + var/choice = get_zone_at(icon_x, icon_y) + if (!choice) + return 1 + + return set_selected_zone(choice, usr) + +/obj/screen/zone_sel/MouseEntered(location, control, params) + MouseMove(location, control, params) + +/obj/screen/zone_sel/MouseMove(location, control, params) + if(isobserver(usr)) + return + + var/list/PL = params2list(params) + var/icon_x = text2num(PL["icon-x"]) + var/icon_y = text2num(PL["icon-y"]) + var/choice = get_zone_at(icon_x, icon_y) + + if(hovering == choice) + return + vis_contents -= hover_overlays_cache[hovering] + hovering = choice + + var/obj/effect/overlay/zone_sel/overlay_object = hover_overlays_cache[choice] + if(!overlay_object) + overlay_object = new + overlay_object.icon_state = "[choice]" + hover_overlays_cache[choice] = overlay_object + vis_contents += overlay_object + +/obj/effect/overlay/zone_sel + icon = 'icons/mob/screen_gen.dmi' + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + alpha = 128 + anchored = TRUE + layer = ABOVE_HUD_LAYER + plane = ABOVE_HUD_PLANE + +/obj/screen/zone_sel/MouseExited(location, control, params) + if(!isobserver(usr) && hovering) + vis_contents -= hover_overlays_cache[hovering] + hovering = null + +/obj/screen/zone_sel/proc/get_zone_at(icon_x, icon_y) + switch(icon_y) + if(1 to 9) //Legs + switch(icon_x) + if(10 to 15) + return BODY_ZONE_R_LEG + if(17 to 22) + return BODY_ZONE_L_LEG + if(10 to 13) //Hands and groin + switch(icon_x) + if(8 to 11) + return BODY_ZONE_R_ARM + if(12 to 20) + return BODY_ZONE_PRECISE_GROIN + if(21 to 24) + return BODY_ZONE_L_ARM + if(14 to 22) //Chest and arms to shoulders + switch(icon_x) + if(8 to 11) + return BODY_ZONE_R_ARM + if(12 to 20) + return BODY_ZONE_CHEST + if(21 to 24) + return BODY_ZONE_L_ARM + if(23 to 30) //Head, but we need to check for eye or mouth + if(icon_x in 12 to 20) + switch(icon_y) + if(23 to 24) + if(icon_x in 15 to 17) + return BODY_ZONE_PRECISE_MOUTH + if(26) //Eyeline, eyes are on 15 and 17 + if(icon_x in 14 to 18) + return BODY_ZONE_PRECISE_EYES + if(25 to 27) + if(icon_x in 15 to 17) + return BODY_ZONE_PRECISE_EYES + return BODY_ZONE_HEAD + +/obj/screen/zone_sel/proc/set_selected_zone(choice, mob/user) + if(user != hud?.mymob) + return + + if(choice != hud.mymob.zone_selected) + hud.mymob.zone_selected = choice + update_icon() + + return TRUE + +/obj/screen/zone_sel/update_overlays() + . = ..() + if(!hud?.mymob) + return + . += mutable_appearance(overlay_icon, "[hud.mymob.zone_selected]") + +/obj/screen/zone_sel/alien + icon = 'icons/mob/screen_alien.dmi' + overlay_icon = 'icons/mob/screen_alien.dmi' + +/obj/screen/zone_sel/robot + icon = 'icons/mob/screen_cyborg.dmi' + +/obj/screen/flash + name = "flash" + icon_state = "blank" + blend_mode = BLEND_ADD + screen_loc = "WEST,SOUTH to EAST,NORTH" + layer = FLASH_LAYER + plane = FULLSCREEN_PLANE + +/obj/screen/damageoverlay + icon = 'icons/mob/screen_full.dmi' + icon_state = "oxydamageoverlay0" + name = "dmg" + blend_mode = BLEND_MULTIPLY + screen_loc = "CENTER-7,CENTER-7" + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + layer = UI_DAMAGE_LAYER + plane = FULLSCREEN_PLANE + +/obj/screen/healths + name = "health" + icon_state = "health0" + screen_loc = ui_health + +/obj/screen/healths/alien + icon = 'icons/mob/screen_alien.dmi' + screen_loc = ui_alien_health + +/obj/screen/healths/robot + icon = 'icons/mob/screen_cyborg.dmi' + screen_loc = ui_borg_health + +/obj/screen/healths/blob + name = "blob health" + icon_state = "block" + screen_loc = ui_internal + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + +/obj/screen/healths/blob/naut + name = "health" + icon = 'icons/mob/blob.dmi' + icon_state = "nauthealth" + +/obj/screen/healths/blob/naut/core + name = "overmind health" + icon_state = "corehealth" + screen_loc = ui_health + +/obj/screen/healths/guardian + name = "summoner health" + icon = 'icons/mob/guardian.dmi' + icon_state = "base" + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + +/obj/screen/healths/revenant + name = "essence" + icon = 'icons/mob/actions/backgrounds.dmi' + icon_state = "bg_revenant" + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + +/obj/screen/healths/construct + icon = 'icons/mob/screen_construct.dmi' + icon_state = "artificer_health0" + screen_loc = ui_construct_health + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + +/obj/screen/healthdoll + name = "health doll" + screen_loc = ui_healthdoll + +/obj/screen/healthdoll/Click() + if (iscarbon(usr)) + var/mob/living/carbon/C = usr + C.check_self_for_injuries() + +/obj/screen/healthdoll/living + icon_state = "fullhealth0" + screen_loc = ui_living_healthdoll + var/filtered = FALSE //so we don't repeatedly create the mask of the mob every update + +/obj/screen/mood + name = "mood" + icon_state = "mood5" + screen_loc = ui_mood + +/obj/screen/splash + icon = 'icons/blank_title.png' + icon_state = "" + screen_loc = "1,1" + layer = SPLASHSCREEN_LAYER + plane = SPLASHSCREEN_PLANE + var/client/holder + +/obj/screen/splash/New(client/C, visible, use_previous_title) //TODO: Make this use INITIALIZE_IMMEDIATE, except its not easy + . = ..() + + holder = C + + if(!visible) + alpha = 0 + + if(!use_previous_title) + if(SStitle.icon) + icon = SStitle.icon + else + if(!SStitle.previous_icon) + qdel(src) + return + icon = SStitle.previous_icon + + holder.screen += src + +/obj/screen/splash/proc/Fade(out, qdel_after = TRUE) + if(QDELETED(src)) + return + if(out) + animate(src, alpha = 0, time = 30) + else + alpha = 0 + animate(src, alpha = 255, time = 30) + if(qdel_after) + QDEL_IN(src, 30) + +/obj/screen/splash/Destroy() + if(holder) + holder.screen -= src + holder = null + return ..() + + +/obj/screen/component_button + var/obj/screen/parent + +/obj/screen/component_button/Initialize(mapload, obj/screen/parent) + . = ..() + src.parent = parent + +/obj/screen/component_button/Click(params) + if(parent) + parent.component_click(src, params) diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm index 48b4f532a94d..a7f9aa4a2742 100644 --- a/code/_onclick/item_attack.dm +++ b/code/_onclick/item_attack.dm @@ -1,192 +1,192 @@ -/** - * This is the proc that handles the order of an item_attack. - * - * The order of procs called is: - * * [/atom/proc/tool_act] on the target. If it returns TRUE, the chain will be stopped. - * * [/obj/item/proc/pre_attack] on src. If this returns TRUE, the chain will be stopped. - * * [/atom/proc/attackby] on the target. If it returns TRUE, the chain will be stopped. - * * [/obj/item/proc/afterattack]. The return value does not matter. - */ -/obj/item/proc/melee_attack_chain(mob/user, atom/target, params) - if(tool_behaviour && target.tool_act(user, src, tool_behaviour)) - return - if(pre_attack(target, user, params)) - return - if(target.attackby(src,user, params)) - return - if(QDELETED(src) || QDELETED(target)) - attack_qdeleted(target, user, TRUE, params) - return - afterattack(target, user, TRUE, params) - -/// Called when the item is in the active hand, and clicked; alternately, there is an 'activate held object' verb or you can hit pagedown. -/obj/item/proc/attack_self(mob/user) - if(SEND_SIGNAL(src, COMSIG_ITEM_ATTACK_SELF, user) & COMPONENT_NO_INTERACT) - return - interact(user) - -/** - * Called on the item before it hits something - * - * Arguments: - * * atom/A - The atom about to be hit - * * mob/living/user - The mob doing the htting - * * params - click params such as alt/shift etc - * - * See: [/obj/item/proc/melee_attack_chain] - */ -/obj/item/proc/pre_attack(atom/A, mob/living/user, params) //do stuff before attackby! - if(SEND_SIGNAL(src, COMSIG_ITEM_PRE_ATTACK, A, user, params) & COMPONENT_NO_ATTACK) - return TRUE - return FALSE //return TRUE to avoid calling attackby after this proc does stuff - -/** - * Called on an object being hit by an item - * - * Arguments: - * * obj/item/W - The item hitting this atom - * * mob/user - The wielder of this item - * * params - click params such as alt/shift etc - * - * See: [/obj/item/proc/melee_attack_chain] - */ -/atom/proc/attackby(obj/item/W, mob/user, params) - if(SEND_SIGNAL(src, COMSIG_PARENT_ATTACKBY, W, user, params) & COMPONENT_NO_AFTERATTACK) - return TRUE - return FALSE - -/obj/attackby(obj/item/I, mob/living/user, params) - return ..() || ((obj_flags & CAN_BE_HIT) && I.attack_obj(src, user)) - -/mob/living/attackby(obj/item/I, mob/living/user, params) - if(..()) - return TRUE - user.changeNext_move(CLICK_CD_MELEE) - return I.attack(src, user) - -/** - * Called from [/mob/living/attackby] - * - * Arguments: - * * mob/living/M - The mob being hit by this item - * * mob/living/user - The mob hitting with this item - */ -/obj/item/proc/attack(mob/living/M, mob/living/user) - if(SEND_SIGNAL(src, COMSIG_ITEM_ATTACK, M, user) & COMPONENT_ITEM_NO_ATTACK) - return - SEND_SIGNAL(user, COMSIG_MOB_ITEM_ATTACK, M, user) - if(item_flags & NOBLUDGEON) - return - - if(force && HAS_TRAIT(user, TRAIT_PACIFISM)) - to_chat(user, "You don't want to harm other living beings!") - return - - if(item_flags & EYE_STAB && user.zone_selected == BODY_ZONE_PRECISE_EYES) - if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50)) - M = user - return eyestab(M,user) - - if(!force) - playsound(loc, 'sound/weapons/tap.ogg', get_clamped_volume(), TRUE, -1) - else if(hitsound) - playsound(loc, hitsound, get_clamped_volume(), TRUE, -1) - - 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) - - log_combat(user, M, "attacked", src.name, "(INTENT: [uppertext(user.a_intent)]) (DAMTYPE: [uppertext(damtype)])") - add_fingerprint(user) - - -/// The equivalent of the standard version of [/obj/item/proc/attack] but for object targets. -/obj/item/proc/attack_obj(obj/O, mob/living/user) - if(SEND_SIGNAL(src, COMSIG_ITEM_ATTACK_OBJ, O, user) & COMPONENT_NO_ATTACK_OBJ) - return - if(item_flags & NOBLUDGEON) - return - user.changeNext_move(CLICK_CD_MELEE) - user.do_attack_animation(O) - O.attacked_by(src, user) - -/// Called from [/obj/item/proc/attack_obj] and [/obj/item/proc/attack] if the attack succeeds -/atom/movable/proc/attacked_by() - return - -/obj/attacked_by(obj/item/I, mob/living/user) - if(I.force) - user.visible_message("[user] hits [src] with [I]!", \ - "You hit [src] with [I]!", null, COMBAT_MESSAGE_RANGE) - //only witnesses close by and the victim see a hit message. - log_combat(user, src, "attacked", I) - take_damage(I.force, I.damtype, "melee", 1) - -/mob/living/attacked_by(obj/item/I, mob/living/user) - send_item_attack_message(I, user) - if(I.force) - apply_damage(I.force, I.damtype, break_modifier = I.force) //Bone break modifier = item force - if(I.damtype == BRUTE) - if(prob(33)) - I.add_mob_blood(src) - var/turf/location = get_turf(src) - add_splatter_floor(location) - if(get_dist(user, src) <= 1) //people with TK won't get smeared with blood - user.add_mob_blood(src) - return TRUE //successful attack - -/mob/living/simple_animal/attacked_by(obj/item/I, mob/living/user) - if(I.force < force_threshold || I.damtype == STAMINA) - playsound(loc, 'sound/weapons/tap.ogg', I.get_clamped_volume(), TRUE, -1) - else - return ..() - -/** - * Last proc in the [/obj/item/proc/melee_attack_chain] - * - * Arguments: - * * atom/target - The thing that was hit - * * mob/user - The mob doing the hitting - * * proximity_flag - is 1 if this afterattack was called on something adjacent, in your square, or on your person. - * * click_parameters - is the params string from byond [/atom/proc/Click] code, see that documentation. - */ -/obj/item/proc/afterattack(atom/target, mob/user, proximity_flag, click_parameters) - SEND_SIGNAL(src, COMSIG_ITEM_AFTERATTACK, target, user, proximity_flag, click_parameters) - SEND_SIGNAL(user, COMSIG_MOB_ITEM_AFTERATTACK, target, user, proximity_flag, click_parameters) - -/// Called if the target gets deleted by our attack -/obj/item/proc/attack_qdeleted(atom/target, mob/user, proximity_flag, click_parameters) - SEND_SIGNAL(src, COMSIG_ITEM_ATTACK_QDELETED, target, user, proximity_flag, click_parameters) - SEND_SIGNAL(user, COMSIG_MOB_ITEM_ATTACK_QDELETED, target, user, proximity_flag, click_parameters) - -/obj/item/proc/get_clamped_volume() - if(w_class) - if(force) - return clamp((force + w_class) * 4, 30, 100)// Add the item's force to its weight class and multiply by 4, then clamp the value between 30 and 100 - else - return clamp(w_class * 6, 10, 100) // Multiply the item's weight class by 6, then clamp the value between 10 and 100 - -/mob/living/proc/send_item_attack_message(obj/item/I, mob/living/user, hit_area) - var/message_verb = "attacked" - if(I.attack_verb && I.attack_verb.len) - message_verb = "[pick(I.attack_verb)]" - else if(!I.force) - return - var/message_hit_area = "" - if(hit_area) - message_hit_area = " in the [hit_area]" - var/attack_message = "[src] is [message_verb][message_hit_area] with [I]!" - var/attack_message_local = "You're [message_verb][message_hit_area] with [I]!" - if(user in viewers(src, null)) - attack_message = "[user] [message_verb] [src][message_hit_area] with [I]!" - attack_message_local = "[user] [message_verb] you[message_hit_area] with [I]!" - if(user == src) - attack_message_local = "You [message_verb] yourself[message_hit_area] with [I]" - visible_message("[attack_message]",\ - "[attack_message_local]", null, COMBAT_MESSAGE_RANGE) - return 1 +/** + * This is the proc that handles the order of an item_attack. + * + * The order of procs called is: + * * [/atom/proc/tool_act] on the target. If it returns TRUE, the chain will be stopped. + * * [/obj/item/proc/pre_attack] on src. If this returns TRUE, the chain will be stopped. + * * [/atom/proc/attackby] on the target. If it returns TRUE, the chain will be stopped. + * * [/obj/item/proc/afterattack]. The return value does not matter. + */ +/obj/item/proc/melee_attack_chain(mob/user, atom/target, params) + if(tool_behaviour && target.tool_act(user, src, tool_behaviour)) + return + if(pre_attack(target, user, params)) + return + if(target.attackby(src,user, params)) + return + if(QDELETED(src) || QDELETED(target)) + attack_qdeleted(target, user, TRUE, params) + return + afterattack(target, user, TRUE, params) + +/// Called when the item is in the active hand, and clicked; alternately, there is an 'activate held object' verb or you can hit pagedown. +/obj/item/proc/attack_self(mob/user) + if(SEND_SIGNAL(src, COMSIG_ITEM_ATTACK_SELF, user) & COMPONENT_NO_INTERACT) + return + interact(user) + +/** + * Called on the item before it hits something + * + * Arguments: + * * atom/A - The atom about to be hit + * * mob/living/user - The mob doing the htting + * * params - click params such as alt/shift etc + * + * See: [/obj/item/proc/melee_attack_chain] + */ +/obj/item/proc/pre_attack(atom/A, mob/living/user, params) //do stuff before attackby! + if(SEND_SIGNAL(src, COMSIG_ITEM_PRE_ATTACK, A, user, params) & COMPONENT_NO_ATTACK) + return TRUE + return FALSE //return TRUE to avoid calling attackby after this proc does stuff + +/** + * Called on an object being hit by an item + * + * Arguments: + * * obj/item/W - The item hitting this atom + * * mob/user - The wielder of this item + * * params - click params such as alt/shift etc + * + * See: [/obj/item/proc/melee_attack_chain] + */ +/atom/proc/attackby(obj/item/W, mob/user, params) + if(SEND_SIGNAL(src, COMSIG_PARENT_ATTACKBY, W, user, params) & COMPONENT_NO_AFTERATTACK) + return TRUE + return FALSE + +/obj/attackby(obj/item/I, mob/living/user, params) + return ..() || ((obj_flags & CAN_BE_HIT) && I.attack_obj(src, user)) + +/mob/living/attackby(obj/item/I, mob/living/user, params) + if(..()) + return TRUE + user.changeNext_move(CLICK_CD_MELEE) + return I.attack(src, user) + +/** + * Called from [/mob/living/attackby] + * + * Arguments: + * * mob/living/M - The mob being hit by this item + * * mob/living/user - The mob hitting with this item + */ +/obj/item/proc/attack(mob/living/M, mob/living/user) + if(SEND_SIGNAL(src, COMSIG_ITEM_ATTACK, M, user) & COMPONENT_ITEM_NO_ATTACK) + return + SEND_SIGNAL(user, COMSIG_MOB_ITEM_ATTACK, M, user) + if(item_flags & NOBLUDGEON) + return + + if(force && HAS_TRAIT(user, TRAIT_PACIFISM)) + to_chat(user, "You don't want to harm other living beings!") + return + + if(item_flags & EYE_STAB && user.zone_selected == BODY_ZONE_PRECISE_EYES) + if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50)) + M = user + return eyestab(M,user) + + if(!force) + playsound(loc, 'sound/weapons/tap.ogg', get_clamped_volume(), TRUE, -1) + else if(hitsound) + playsound(loc, hitsound, get_clamped_volume(), TRUE, -1) + + 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) + + log_combat(user, M, "attacked", src.name, "(INTENT: [uppertext(user.a_intent)]) (DAMTYPE: [uppertext(damtype)])") + add_fingerprint(user) + + +/// The equivalent of the standard version of [/obj/item/proc/attack] but for object targets. +/obj/item/proc/attack_obj(obj/O, mob/living/user) + if(SEND_SIGNAL(src, COMSIG_ITEM_ATTACK_OBJ, O, user) & COMPONENT_NO_ATTACK_OBJ) + return + if(item_flags & NOBLUDGEON) + return + user.changeNext_move(CLICK_CD_MELEE) + user.do_attack_animation(O) + O.attacked_by(src, user) + +/// Called from [/obj/item/proc/attack_obj] and [/obj/item/proc/attack] if the attack succeeds +/atom/movable/proc/attacked_by() + return + +/obj/attacked_by(obj/item/I, mob/living/user) + if(I.force) + user.visible_message("[user] hits [src] with [I]!", \ + "You hit [src] with [I]!", null, COMBAT_MESSAGE_RANGE) + //only witnesses close by and the victim see a hit message. + log_combat(user, src, "attacked", I) + take_damage(I.force, I.damtype, "melee", 1) + +/mob/living/attacked_by(obj/item/I, mob/living/user) + send_item_attack_message(I, user) + if(I.force) + apply_damage(I.force, I.damtype, break_modifier = I.force) //Bone break modifier = item force + if(I.damtype == BRUTE) + if(prob(33)) + I.add_mob_blood(src) + var/turf/location = get_turf(src) + add_splatter_floor(location) + if(get_dist(user, src) <= 1) //people with TK won't get smeared with blood + user.add_mob_blood(src) + return TRUE //successful attack + +/mob/living/simple_animal/attacked_by(obj/item/I, mob/living/user) + if(I.force < force_threshold || I.damtype == STAMINA) + playsound(loc, 'sound/weapons/tap.ogg', I.get_clamped_volume(), TRUE, -1) + else + return ..() + +/** + * Last proc in the [/obj/item/proc/melee_attack_chain] + * + * Arguments: + * * atom/target - The thing that was hit + * * mob/user - The mob doing the hitting + * * proximity_flag - is 1 if this afterattack was called on something adjacent, in your square, or on your person. + * * click_parameters - is the params string from byond [/atom/proc/Click] code, see that documentation. + */ +/obj/item/proc/afterattack(atom/target, mob/user, proximity_flag, click_parameters) + SEND_SIGNAL(src, COMSIG_ITEM_AFTERATTACK, target, user, proximity_flag, click_parameters) + SEND_SIGNAL(user, COMSIG_MOB_ITEM_AFTERATTACK, target, user, proximity_flag, click_parameters) + +/// Called if the target gets deleted by our attack +/obj/item/proc/attack_qdeleted(atom/target, mob/user, proximity_flag, click_parameters) + SEND_SIGNAL(src, COMSIG_ITEM_ATTACK_QDELETED, target, user, proximity_flag, click_parameters) + SEND_SIGNAL(user, COMSIG_MOB_ITEM_ATTACK_QDELETED, target, user, proximity_flag, click_parameters) + +/obj/item/proc/get_clamped_volume() + if(w_class) + if(force) + return clamp((force + w_class) * 4, 30, 100)// Add the item's force to its weight class and multiply by 4, then clamp the value between 30 and 100 + else + return clamp(w_class * 6, 10, 100) // Multiply the item's weight class by 6, then clamp the value between 10 and 100 + +/mob/living/proc/send_item_attack_message(obj/item/I, mob/living/user, hit_area) + var/message_verb = "attacked" + if(I.attack_verb && I.attack_verb.len) + message_verb = "[pick(I.attack_verb)]" + else if(!I.force) + return + var/message_hit_area = "" + if(hit_area) + message_hit_area = " in the [hit_area]" + var/attack_message = "[src] is [message_verb][message_hit_area] with [I]!" + var/attack_message_local = "You're [message_verb][message_hit_area] with [I]!" + if(user in viewers(src, null)) + attack_message = "[user] [message_verb] [src][message_hit_area] with [I]!" + attack_message_local = "[user] [message_verb] you[message_hit_area] with [I]!" + if(user == src) + attack_message_local = "You [message_verb] yourself[message_hit_area] with [I]" + visible_message("[attack_message]",\ + "[attack_message_local]", null, COMBAT_MESSAGE_RANGE) + return 1 diff --git a/code/_onclick/observer.dm b/code/_onclick/observer.dm index efed103392fc..85b6ed16da6f 100644 --- a/code/_onclick/observer.dm +++ b/code/_onclick/observer.dm @@ -1,81 +1,81 @@ -/mob/dead/observer/DblClickOn(atom/A, params) - if(check_click_intercept(params, A)) - return - - if(can_reenter_corpse && mind && mind.current) - if(A == mind.current || (mind.current in A)) // double click your corpse or whatever holds it - reenter_corpse() // (body bag, closet, mech, etc) - return // seems legit. - - // Things you might plausibly want to follow - if(ismovable(A)) - ManualFollow(A) - - // Otherwise jump - else if(A.loc) - forceMove(get_turf(A)) - update_parallax_contents() - -/mob/dead/observer/ClickOn(atom/A, params) - if(check_click_intercept(params,A)) - return - - var/list/modifiers = params2list(params) - if(modifiers["shift"] && modifiers["middle"]) - ShiftMiddleClickOn(A) - return - if(modifiers["shift"] && modifiers["ctrl"]) - CtrlShiftClickOn(A) - return - if(modifiers["middle"]) - MiddleClickOn(A) - return - if(modifiers["shift"]) - ShiftClickOn(A) - return - if(modifiers["alt"]) - AltClickNoInteract(src, A) - return - if(modifiers["ctrl"]) - CtrlClickOn(A) - return - - if(world.time <= next_move) - return - // You are responsible for checking config.ghost_interaction when you override this function - // Not all of them require checking, see below - A.attack_ghost(src) - -// Oh by the way this didn't work with old click code which is why clicking shit didn't spam you -/atom/proc/attack_ghost(mob/dead/observer/user) - if(SEND_SIGNAL(src, COMSIG_ATOM_ATTACK_GHOST, user) & COMPONENT_NO_ATTACK_HAND) - return TRUE - if(user.client) - if(user.gas_scan && atmosanalyzer_scan(user, src)) - return TRUE - else if(IsAdminGhost(user)) - attack_ai(user) - else if(user.client.prefs.inquisitive_ghost) - user.examinate(src) - return FALSE - -/mob/living/attack_ghost(mob/dead/observer/user) - if(user.client && user.health_scan) - healthscan(user, src, 1, TRUE) - if(user.client && user.chem_scan) - chemscan(user, src) - return ..() - -// --------------------------------------- -// And here are some good things for free: -// Now you can click through portals, wormholes, gateways, and teleporters while observing. -Sayu - -/obj/effect/gateway_portal_bumper/attack_ghost(mob/user) - if(gateway) - gateway.Transfer(user) - return ..() - -/obj/machinery/teleport/hub/attack_ghost(mob/user) - if(power_station && power_station.engaged && power_station.teleporter_console && power_station.teleporter_console.target) - user.forceMove(get_turf(power_station.teleporter_console.target)) - return ..() +/mob/dead/observer/DblClickOn(atom/A, params) + if(check_click_intercept(params, A)) + return + + if(can_reenter_corpse && mind && mind.current) + if(A == mind.current || (mind.current in A)) // double click your corpse or whatever holds it + reenter_corpse() // (body bag, closet, mech, etc) + return // seems legit. + + // Things you might plausibly want to follow + if(ismovable(A)) + ManualFollow(A) + + // Otherwise jump + else if(A.loc) + forceMove(get_turf(A)) + update_parallax_contents() + +/mob/dead/observer/ClickOn(atom/A, params) + if(check_click_intercept(params,A)) + return + + var/list/modifiers = params2list(params) + if(modifiers["shift"] && modifiers["middle"]) + ShiftMiddleClickOn(A) + return + if(modifiers["shift"] && modifiers["ctrl"]) + CtrlShiftClickOn(A) + return + if(modifiers["middle"]) + MiddleClickOn(A) + return + if(modifiers["shift"]) + ShiftClickOn(A) + return + if(modifiers["alt"]) + AltClickNoInteract(src, A) + return + if(modifiers["ctrl"]) + CtrlClickOn(A) + return + + if(world.time <= next_move) + return + // You are responsible for checking config.ghost_interaction when you override this function + // Not all of them require checking, see below + A.attack_ghost(src) + +// Oh by the way this didn't work with old click code which is why clicking shit didn't spam you +/atom/proc/attack_ghost(mob/dead/observer/user) + if(SEND_SIGNAL(src, COMSIG_ATOM_ATTACK_GHOST, user) & COMPONENT_NO_ATTACK_HAND) + return TRUE + if(user.client) + if(user.gas_scan && atmosanalyzer_scan(user, src)) + return TRUE + else if(IsAdminGhost(user)) + attack_ai(user) + else if(user.client.prefs.inquisitive_ghost) + user.examinate(src) + return FALSE + +/mob/living/attack_ghost(mob/dead/observer/user) + if(user.client && user.health_scan) + healthscan(user, src, 1, TRUE) + if(user.client && user.chem_scan) + chemscan(user, src) + return ..() + +// --------------------------------------- +// And here are some good things for free: +// Now you can click through portals, wormholes, gateways, and teleporters while observing. -Sayu + +/obj/effect/gateway_portal_bumper/attack_ghost(mob/user) + if(gateway) + gateway.Transfer(user) + return ..() + +/obj/machinery/teleport/hub/attack_ghost(mob/user) + if(power_station && power_station.engaged && power_station.teleporter_console && power_station.teleporter_console.target) + user.forceMove(get_turf(power_station.teleporter_console.target)) + return ..() diff --git a/code/_onclick/other_mobs.dm b/code/_onclick/other_mobs.dm index 00ab119f219c..e2b84486435f 100644 --- a/code/_onclick/other_mobs.dm +++ b/code/_onclick/other_mobs.dm @@ -1,252 +1,252 @@ -/* - Humans: - Adds an exception for gloves, to allow special glove types like the ninja ones. - - Otherwise pretty standard. -*/ -/mob/living/carbon/human/UnarmedAttack(atom/A, proximity) - - if(!has_active_hand()) //can't attack without a hand. - to_chat(src, "You look at your arm and sigh.") - return - - // Special glove functions: - // If the gloves do anything, have them return 1 to stop - // normal attack_hand() here. - var/obj/item/clothing/gloves/G = gloves // not typecast specifically enough in defines - if(proximity && istype(G) && G.Touch(A,1)) - return - //This signal is needed to prevent gloves of the north star + hulk. - if(SEND_SIGNAL(src, COMSIG_HUMAN_EARLY_UNARMED_ATTACK, A, proximity) & COMPONENT_NO_ATTACK_HAND) - return - SEND_SIGNAL(src, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, A, proximity) - A.attack_hand(src) - -/// Return TRUE to cancel other attack hand effects that respect it. -/atom/proc/attack_hand(mob/user) - . = FALSE - if(!(interaction_flags_atom & INTERACT_ATOM_NO_FINGERPRINT_ATTACK_HAND)) - add_fingerprint(user) - if(SEND_SIGNAL(src, COMSIG_ATOM_ATTACK_HAND, user) & COMPONENT_NO_ATTACK_HAND) - . = TRUE - if(interaction_flags_atom & INTERACT_ATOM_ATTACK_HAND) - . = _try_interact(user) - -//Return a non FALSE value to cancel whatever called this from propagating, if it respects it. -/atom/proc/_try_interact(mob/user) - if(IsAdminGhost(user)) //admin abuse - return interact(user) - if(can_interact(user)) - return interact(user) - return FALSE - -/atom/proc/can_interact(mob/user) - if(!user.can_interact_with(src)) - return FALSE - if((interaction_flags_atom & INTERACT_ATOM_REQUIRES_DEXTERITY) && !user.IsAdvancedToolUser()) - to_chat(user, "You don't have the dexterity to do this!") - return FALSE - if(!(interaction_flags_atom & INTERACT_ATOM_IGNORE_INCAPACITATED) && user.incapacitated((interaction_flags_atom & INTERACT_ATOM_IGNORE_RESTRAINED), !(interaction_flags_atom & INTERACT_ATOM_CHECK_GRAB))) - return FALSE - return TRUE - -/atom/ui_status(mob/user) - . = ..() - if(!can_interact(user)) - . = min(., UI_UPDATE) - -/atom/movable/can_interact(mob/user) - . = ..() - if(!.) - return - if(!anchored && (interaction_flags_atom & INTERACT_ATOM_REQUIRES_ANCHORED)) - return FALSE - -/atom/proc/interact(mob/user) - if(interaction_flags_atom & INTERACT_ATOM_NO_FINGERPRINT_INTERACT) - add_hiddenprint(user) - else - add_fingerprint(user) - if(interaction_flags_atom & INTERACT_ATOM_UI_INTERACT) - return ui_interact(user) - return FALSE - -/* -/mob/living/carbon/human/RestrainedClickOn(atom/A) ---carbons will handle this - return -*/ - -/mob/living/carbon/RestrainedClickOn(atom/A) - return 0 - -/mob/living/carbon/human/RangedAttack(atom/A, mouseparams) - . = ..() - if(gloves) - var/obj/item/clothing/gloves/G = gloves - if(istype(G) && G.Touch(A,0)) // for magic gloves - return - - if(isturf(A) && get_dist(src,A) <= 1) - src.Move_Pulled(A) - return - -/* - Animals & All Unspecified -*/ -/mob/living/UnarmedAttack(atom/A) - A.attack_animal(src) - -/atom/proc/attack_animal(mob/user) - SEND_SIGNAL(src, COMSIG_ATOM_ATTACK_ANIMAL, user) - -/mob/living/RestrainedClickOn(atom/A) - return - -/* - Monkeys -*/ -/mob/living/carbon/monkey/UnarmedAttack(atom/A) - A.attack_paw(src) - -/atom/proc/attack_paw(mob/user) - if(SEND_SIGNAL(src, COMSIG_ATOM_ATTACK_PAW, user) & COMPONENT_NO_ATTACK_HAND) - return TRUE - return FALSE - -/* - Monkey RestrainedClickOn() was apparently the - one and only use of all of the restrained click code - (except to stop you from doing things while handcuffed); - moving it here instead of various hand_p's has simplified - things considerably -*/ -/mob/living/carbon/monkey/RestrainedClickOn(atom/A) - if(..()) - return - if(a_intent != INTENT_HARM || !ismob(A)) - return - if(is_muzzled()) - return - var/mob/living/carbon/ML = A - if(istype(ML)) - var/dam_zone = pick(BODY_ZONE_CHEST, BODY_ZONE_PRECISE_L_HAND, BODY_ZONE_PRECISE_R_HAND, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) - var/obj/item/bodypart/affecting = null - if(ishuman(ML)) - var/mob/living/carbon/human/H = ML - affecting = H.get_bodypart(ran_zone(dam_zone)) - var/armor = ML.run_armor_check(affecting, "melee") - if(prob(75)) - ML.apply_damage(rand(1,3), BRUTE, affecting, armor) - ML.visible_message("[name] bites [ML]!", \ - "[name] bites you!", "You hear a chomp!", COMBAT_MESSAGE_RANGE, name) - to_chat(name, "You bite [ML]!") - if(armor >= 2) - return - for(var/thing in diseases) - var/datum/disease/D = thing - ML.ForceContractDisease(D) - else - ML.visible_message("[src]'s bite misses [ML]!", \ - "You avoid [src]'s bite!", "You hear jaws snapping shut!", COMBAT_MESSAGE_RANGE, src) - to_chat(src, "Your bite misses [ML]!") - -/* - Aliens - Defaults to same as monkey in most places -*/ -/mob/living/carbon/alien/UnarmedAttack(atom/A) - A.attack_alien(src) - -/atom/proc/attack_alien(mob/living/carbon/alien/user) - attack_paw(user) - return - -/mob/living/carbon/alien/RestrainedClickOn(atom/A) - return - -// Babby aliens -/mob/living/carbon/alien/larva/UnarmedAttack(atom/A) - A.attack_larva(src) -/atom/proc/attack_larva(mob/user) - return - - -/* - Slimes - Nothing happening here -*/ -/mob/living/simple_animal/slime/UnarmedAttack(atom/A) - A.attack_slime(src) -/atom/proc/attack_slime(mob/user) - return -/mob/living/simple_animal/slime/RestrainedClickOn(atom/A) - return - - -/* - Drones -*/ -/mob/living/simple_animal/drone/UnarmedAttack(atom/A) - A.attack_drone(src) - -/atom/proc/attack_drone(mob/living/simple_animal/drone/user) - attack_hand(user) //defaults to attack_hand. Override it when you don't want drones to do same stuff as humans. - -/mob/living/simple_animal/slime/RestrainedClickOn(atom/A) - return - - -/* - True Devil -*/ - -/mob/living/carbon/true_devil/UnarmedAttack(atom/A, proximity) - A.attack_hand(src) - -/* - Brain -*/ - -/mob/living/brain/UnarmedAttack(atom/A)//Stops runtimes due to attack_animal being the default - return - - -/* - pAI -*/ - -/mob/living/silicon/pai/UnarmedAttack(atom/A)//Stops runtimes due to attack_animal being the default - return - - -/* - Simple animals -*/ - -/mob/living/simple_animal/UnarmedAttack(atom/A, proximity) - if(!dextrous) - return ..() - if(!ismob(A)) - A.attack_hand(src) - update_inv_hands() - - -/* - Hostile animals -*/ - -/mob/living/simple_animal/hostile/UnarmedAttack(atom/A) - target = A - if(dextrous && !ismob(A)) - ..() - else - AttackingTarget() - - - -/* - New Players: - Have no reason to click on anything at all. -*/ -/mob/dead/new_player/ClickOn() - return +/* + Humans: + Adds an exception for gloves, to allow special glove types like the ninja ones. + + Otherwise pretty standard. +*/ +/mob/living/carbon/human/UnarmedAttack(atom/A, proximity) + + if(!has_active_hand()) //can't attack without a hand. + to_chat(src, "You look at your arm and sigh.") + return + + // Special glove functions: + // If the gloves do anything, have them return 1 to stop + // normal attack_hand() here. + var/obj/item/clothing/gloves/G = gloves // not typecast specifically enough in defines + if(proximity && istype(G) && G.Touch(A,1)) + return + //This signal is needed to prevent gloves of the north star + hulk. + if(SEND_SIGNAL(src, COMSIG_HUMAN_EARLY_UNARMED_ATTACK, A, proximity) & COMPONENT_NO_ATTACK_HAND) + return + SEND_SIGNAL(src, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, A, proximity) + A.attack_hand(src) + +/// Return TRUE to cancel other attack hand effects that respect it. +/atom/proc/attack_hand(mob/user) + . = FALSE + if(!(interaction_flags_atom & INTERACT_ATOM_NO_FINGERPRINT_ATTACK_HAND)) + add_fingerprint(user) + if(SEND_SIGNAL(src, COMSIG_ATOM_ATTACK_HAND, user) & COMPONENT_NO_ATTACK_HAND) + . = TRUE + if(interaction_flags_atom & INTERACT_ATOM_ATTACK_HAND) + . = _try_interact(user) + +//Return a non FALSE value to cancel whatever called this from propagating, if it respects it. +/atom/proc/_try_interact(mob/user) + if(IsAdminGhost(user)) //admin abuse + return interact(user) + if(can_interact(user)) + return interact(user) + return FALSE + +/atom/proc/can_interact(mob/user) + if(!user.can_interact_with(src)) + return FALSE + if((interaction_flags_atom & INTERACT_ATOM_REQUIRES_DEXTERITY) && !user.IsAdvancedToolUser()) + to_chat(user, "You don't have the dexterity to do this!") + return FALSE + if(!(interaction_flags_atom & INTERACT_ATOM_IGNORE_INCAPACITATED) && user.incapacitated((interaction_flags_atom & INTERACT_ATOM_IGNORE_RESTRAINED), !(interaction_flags_atom & INTERACT_ATOM_CHECK_GRAB))) + return FALSE + return TRUE + +/atom/ui_status(mob/user) + . = ..() + if(!can_interact(user)) + . = min(., UI_UPDATE) + +/atom/movable/can_interact(mob/user) + . = ..() + if(!.) + return + if(!anchored && (interaction_flags_atom & INTERACT_ATOM_REQUIRES_ANCHORED)) + return FALSE + +/atom/proc/interact(mob/user) + if(interaction_flags_atom & INTERACT_ATOM_NO_FINGERPRINT_INTERACT) + add_hiddenprint(user) + else + add_fingerprint(user) + if(interaction_flags_atom & INTERACT_ATOM_UI_INTERACT) + return ui_interact(user) + return FALSE + +/* +/mob/living/carbon/human/RestrainedClickOn(atom/A) ---carbons will handle this + return +*/ + +/mob/living/carbon/RestrainedClickOn(atom/A) + return 0 + +/mob/living/carbon/human/RangedAttack(atom/A, mouseparams) + . = ..() + if(gloves) + var/obj/item/clothing/gloves/G = gloves + if(istype(G) && G.Touch(A,0)) // for magic gloves + return + + if(isturf(A) && get_dist(src,A) <= 1) + src.Move_Pulled(A) + return + +/* + Animals & All Unspecified +*/ +/mob/living/UnarmedAttack(atom/A) + A.attack_animal(src) + +/atom/proc/attack_animal(mob/user) + SEND_SIGNAL(src, COMSIG_ATOM_ATTACK_ANIMAL, user) + +/mob/living/RestrainedClickOn(atom/A) + return + +/* + Monkeys +*/ +/mob/living/carbon/monkey/UnarmedAttack(atom/A) + A.attack_paw(src) + +/atom/proc/attack_paw(mob/user) + if(SEND_SIGNAL(src, COMSIG_ATOM_ATTACK_PAW, user) & COMPONENT_NO_ATTACK_HAND) + return TRUE + return FALSE + +/* + Monkey RestrainedClickOn() was apparently the + one and only use of all of the restrained click code + (except to stop you from doing things while handcuffed); + moving it here instead of various hand_p's has simplified + things considerably +*/ +/mob/living/carbon/monkey/RestrainedClickOn(atom/A) + if(..()) + return + if(a_intent != INTENT_HARM || !ismob(A)) + return + if(is_muzzled()) + return + var/mob/living/carbon/ML = A + if(istype(ML)) + var/dam_zone = pick(BODY_ZONE_CHEST, BODY_ZONE_PRECISE_L_HAND, BODY_ZONE_PRECISE_R_HAND, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) + var/obj/item/bodypart/affecting = null + if(ishuman(ML)) + var/mob/living/carbon/human/H = ML + affecting = H.get_bodypart(ran_zone(dam_zone)) + var/armor = ML.run_armor_check(affecting, "melee") + if(prob(75)) + ML.apply_damage(rand(1,3), BRUTE, affecting, armor) + ML.visible_message("[name] bites [ML]!", \ + "[name] bites you!", "You hear a chomp!", COMBAT_MESSAGE_RANGE, name) + to_chat(name, "You bite [ML]!") + if(armor >= 2) + return + for(var/thing in diseases) + var/datum/disease/D = thing + ML.ForceContractDisease(D) + else + ML.visible_message("[src]'s bite misses [ML]!", \ + "You avoid [src]'s bite!", "You hear jaws snapping shut!", COMBAT_MESSAGE_RANGE, src) + to_chat(src, "Your bite misses [ML]!") + +/* + Aliens + Defaults to same as monkey in most places +*/ +/mob/living/carbon/alien/UnarmedAttack(atom/A) + A.attack_alien(src) + +/atom/proc/attack_alien(mob/living/carbon/alien/user) + attack_paw(user) + return + +/mob/living/carbon/alien/RestrainedClickOn(atom/A) + return + +// Babby aliens +/mob/living/carbon/alien/larva/UnarmedAttack(atom/A) + A.attack_larva(src) +/atom/proc/attack_larva(mob/user) + return + + +/* + Slimes + Nothing happening here +*/ +/mob/living/simple_animal/slime/UnarmedAttack(atom/A) + A.attack_slime(src) +/atom/proc/attack_slime(mob/user) + return +/mob/living/simple_animal/slime/RestrainedClickOn(atom/A) + return + + +/* + Drones +*/ +/mob/living/simple_animal/drone/UnarmedAttack(atom/A) + A.attack_drone(src) + +/atom/proc/attack_drone(mob/living/simple_animal/drone/user) + attack_hand(user) //defaults to attack_hand. Override it when you don't want drones to do same stuff as humans. + +/mob/living/simple_animal/slime/RestrainedClickOn(atom/A) + return + + +/* + True Devil +*/ + +/mob/living/carbon/true_devil/UnarmedAttack(atom/A, proximity) + A.attack_hand(src) + +/* + Brain +*/ + +/mob/living/brain/UnarmedAttack(atom/A)//Stops runtimes due to attack_animal being the default + return + + +/* + pAI +*/ + +/mob/living/silicon/pai/UnarmedAttack(atom/A)//Stops runtimes due to attack_animal being the default + return + + +/* + Simple animals +*/ + +/mob/living/simple_animal/UnarmedAttack(atom/A, proximity) + if(!dextrous) + return ..() + if(!ismob(A)) + A.attack_hand(src) + update_inv_hands() + + +/* + Hostile animals +*/ + +/mob/living/simple_animal/hostile/UnarmedAttack(atom/A) + target = A + if(dextrous && !ismob(A)) + ..() + else + AttackingTarget() + + + +/* + New Players: + Have no reason to click on anything at all. +*/ +/mob/dead/new_player/ClickOn() + return diff --git a/code/_onclick/overmind.dm b/code/_onclick/overmind.dm index 1b1514f3d1ed..07907b94cee9 100644 --- a/code/_onclick/overmind.dm +++ b/code/_onclick/overmind.dm @@ -1,36 +1,36 @@ -// Blob Overmind Controls - - -/mob/camera/blob/ClickOn(atom/A, params) //Expand blob - var/list/modifiers = params2list(params) - if(modifiers["middle"]) - MiddleClickOn(A) - return - if(modifiers["shift"]) - ShiftClickOn(A) - return - if(modifiers["alt"]) - AltClickOn(A) - return - if(modifiers["ctrl"]) - CtrlClickOn(A) - return - var/turf/T = get_turf(A) - if(T) - expand_blob(T) - -/mob/camera/blob/MiddleClickOn(atom/A) //Rally spores - . = ..() - var/turf/T = get_turf(A) - if(T) - rally_spores(T) - -/mob/camera/blob/CtrlClickOn(atom/A) //Create a shield - var/turf/T = get_turf(A) - if(T) - create_shield(T) - -/mob/camera/blob/AltClickOn(atom/A) //Remove a blob - var/turf/T = get_turf(A) - if(T) - remove_blob(T) +// Blob Overmind Controls + + +/mob/camera/blob/ClickOn(atom/A, params) //Expand blob + var/list/modifiers = params2list(params) + if(modifiers["middle"]) + MiddleClickOn(A) + return + if(modifiers["shift"]) + ShiftClickOn(A) + return + if(modifiers["alt"]) + AltClickOn(A) + return + if(modifiers["ctrl"]) + CtrlClickOn(A) + return + var/turf/T = get_turf(A) + if(T) + expand_blob(T) + +/mob/camera/blob/MiddleClickOn(atom/A) //Rally spores + . = ..() + var/turf/T = get_turf(A) + if(T) + rally_spores(T) + +/mob/camera/blob/CtrlClickOn(atom/A) //Create a shield + var/turf/T = get_turf(A) + if(T) + create_shield(T) + +/mob/camera/blob/AltClickOn(atom/A) //Remove a blob + var/turf/T = get_turf(A) + if(T) + remove_blob(T) diff --git a/code/controllers/configuration/config_entry.dm b/code/controllers/configuration/config_entry.dm index b6245085b384..410075fa55d7 100644 --- a/code/controllers/configuration/config_entry.dm +++ b/code/controllers/configuration/config_entry.dm @@ -1,197 +1,197 @@ -#define VALUE_MODE_NUM 0 -#define VALUE_MODE_TEXT 1 -#define VALUE_MODE_FLAG 2 - -#define KEY_MODE_TEXT 0 -#define KEY_MODE_TYPE 1 - -/datum/config_entry - var/name //read-only, this is determined by the last portion of the derived entry type - var/config_entry_value - var/default //read-only, just set value directly - - var/resident_file //the file which this was loaded from, if any - var/modified = FALSE //set to TRUE if the default has been overridden by a config entry - - var/deprecated_by //the /datum/config_entry type that supercedes this one - - var/protection = NONE - var/abstract_type = /datum/config_entry //do not instantiate if type matches this - - var/vv_VAS = TRUE //Force validate and set on VV. VAS proccall guard will run regardless. - - var/dupes_allowed = FALSE - -/datum/config_entry/New() - if(type == abstract_type) - CRASH("Abstract config entry [type] instatiated!") - name = lowertext(type2top(type)) - if(islist(config_entry_value)) - var/list/L = config_entry_value - default = L.Copy() - else - default = config_entry_value - -/datum/config_entry/Destroy() - config.RemoveEntry(src) - return ..() - -/datum/config_entry/can_vv_get(var_name) - . = ..() - if(var_name == NAMEOF(src, config_entry_value) || var_name == NAMEOF(src, default)) - . &= !(protection & CONFIG_ENTRY_HIDDEN) - -/datum/config_entry/vv_edit_var(var_name, var_value) - var/static/list/banned_edits = list(NAMEOF(src, name), NAMEOF(src, vv_VAS), NAMEOF(src, default), NAMEOF(src, resident_file), NAMEOF(src, protection), NAMEOF(src, abstract_type), NAMEOF(src, modified), NAMEOF(src, dupes_allowed)) - if(var_name == NAMEOF(src, config_entry_value)) - if(protection & CONFIG_ENTRY_LOCKED) - return FALSE - if(vv_VAS) - . = ValidateAndSet("[var_value]") - if(.) - datum_flags |= DF_VAR_EDITED - return - else - return ..() - if(var_name in banned_edits) - return FALSE - return ..() - -/datum/config_entry/proc/VASProcCallGuard(str_val) - . = !((protection & CONFIG_ENTRY_LOCKED) && IsAdminAdvancedProcCall() && GLOB.LastAdminCalledProc == "ValidateAndSet" && GLOB.LastAdminCalledTargetRef == "[REF(src)]") - if(!.) - log_admin_private("Config set of [type] to [str_val] attempted by [key_name(usr)]") - -/datum/config_entry/proc/ValidateAndSet(str_val) - VASProcCallGuard(str_val) - CRASH("Invalid config entry type!") - -/datum/config_entry/proc/ValidateListEntry(key_name, key_value) - return TRUE - -/datum/config_entry/proc/DeprecationUpdate(value) - return - -/datum/config_entry/string - config_entry_value = "" - abstract_type = /datum/config_entry/string - var/auto_trim = TRUE - -/datum/config_entry/string/vv_edit_var(var_name, var_value) - return var_name != "auto_trim" && ..() - -/datum/config_entry/string/ValidateAndSet(str_val) - if(!VASProcCallGuard(str_val)) - return FALSE - config_entry_value = auto_trim ? trim(str_val) : str_val - return TRUE - -/datum/config_entry/number - config_entry_value = 0 - abstract_type = /datum/config_entry/number - var/integer = TRUE - var/max_val = INFINITY - var/min_val = -INFINITY - -/datum/config_entry/number/ValidateAndSet(str_val) - if(!VASProcCallGuard(str_val)) - return FALSE - var/temp = text2num(trim(str_val)) - if(!isnull(temp)) - config_entry_value = clamp(integer ? round(temp) : temp, min_val, max_val) - if(config_entry_value != temp && !(datum_flags & DF_VAR_EDITED)) - log_config("Changing [name] from [temp] to [config_entry_value]!") - return TRUE - return FALSE - -/datum/config_entry/number/vv_edit_var(var_name, var_value) - var/static/list/banned_edits = list("max_val", "min_val", "integer") - return !(var_name in banned_edits) && ..() - -/datum/config_entry/flag - config_entry_value = FALSE - abstract_type = /datum/config_entry/flag - -/datum/config_entry/flag/ValidateAndSet(str_val) - if(!VASProcCallGuard(str_val)) - return FALSE - config_entry_value = text2num(trim(str_val)) != 0 - return TRUE - -/datum/config_entry/number_list - abstract_type = /datum/config_entry/number_list - config_entry_value = list() - -/datum/config_entry/number_list/ValidateAndSet(str_val) - if(!VASProcCallGuard(str_val)) - return FALSE - str_val = trim(str_val) - var/list/new_list = list() - var/list/values = splittext(str_val," ") - for(var/I in values) - var/temp = text2num(I) - if(isnull(temp)) - return FALSE - new_list += temp - if(!new_list.len) - return FALSE - config_entry_value = new_list - return TRUE - -/datum/config_entry/keyed_list - abstract_type = /datum/config_entry/keyed_list - config_entry_value = list() - dupes_allowed = TRUE - vv_VAS = FALSE //VAS will not allow things like deleting from lists, it'll just bug horribly. - var/key_mode - var/value_mode - var/splitter = " " - -/datum/config_entry/keyed_list/New() - . = ..() - if(isnull(key_mode) || isnull(value_mode)) - CRASH("Keyed list of type [type] created with null key or value mode!") - -/datum/config_entry/keyed_list/ValidateAndSet(str_val) - if(!VASProcCallGuard(str_val)) - return FALSE - - str_val = trim(str_val) - var/key_pos = findtext(str_val, splitter) - var/key_name = null - var/key_value = null - - if(key_pos || value_mode == VALUE_MODE_FLAG) - key_name = lowertext(copytext(str_val, 1, key_pos)) - if(key_pos) - key_value = copytext(str_val, key_pos + length(str_val[key_pos])) - var/new_key - var/new_value - var/continue_check_value - var/continue_check_key - switch(key_mode) - if(KEY_MODE_TEXT) - new_key = key_name - continue_check_key = new_key - if(KEY_MODE_TYPE) - new_key = key_name - if(!ispath(new_key)) - new_key = text2path(new_key) - continue_check_key = ispath(new_key) - switch(value_mode) - if(VALUE_MODE_FLAG) - new_value = TRUE - continue_check_value = TRUE - if(VALUE_MODE_NUM) - new_value = text2num(key_value) - continue_check_value = !isnull(new_value) - if(VALUE_MODE_TEXT) - new_value = key_value - continue_check_value = new_value - if(continue_check_value && continue_check_key && ValidateListEntry(new_key, new_value)) - config_entry_value[new_key] = new_value - return TRUE - return FALSE - -/datum/config_entry/keyed_list/vv_edit_var(var_name, var_value) - return var_name != "splitter" && ..() +#define VALUE_MODE_NUM 0 +#define VALUE_MODE_TEXT 1 +#define VALUE_MODE_FLAG 2 + +#define KEY_MODE_TEXT 0 +#define KEY_MODE_TYPE 1 + +/datum/config_entry + var/name //read-only, this is determined by the last portion of the derived entry type + var/config_entry_value + var/default //read-only, just set value directly + + var/resident_file //the file which this was loaded from, if any + var/modified = FALSE //set to TRUE if the default has been overridden by a config entry + + var/deprecated_by //the /datum/config_entry type that supercedes this one + + var/protection = NONE + var/abstract_type = /datum/config_entry //do not instantiate if type matches this + + var/vv_VAS = TRUE //Force validate and set on VV. VAS proccall guard will run regardless. + + var/dupes_allowed = FALSE + +/datum/config_entry/New() + if(type == abstract_type) + CRASH("Abstract config entry [type] instatiated!") + name = lowertext(type2top(type)) + if(islist(config_entry_value)) + var/list/L = config_entry_value + default = L.Copy() + else + default = config_entry_value + +/datum/config_entry/Destroy() + config.RemoveEntry(src) + return ..() + +/datum/config_entry/can_vv_get(var_name) + . = ..() + if(var_name == NAMEOF(src, config_entry_value) || var_name == NAMEOF(src, default)) + . &= !(protection & CONFIG_ENTRY_HIDDEN) + +/datum/config_entry/vv_edit_var(var_name, var_value) + var/static/list/banned_edits = list(NAMEOF(src, name), NAMEOF(src, vv_VAS), NAMEOF(src, default), NAMEOF(src, resident_file), NAMEOF(src, protection), NAMEOF(src, abstract_type), NAMEOF(src, modified), NAMEOF(src, dupes_allowed)) + if(var_name == NAMEOF(src, config_entry_value)) + if(protection & CONFIG_ENTRY_LOCKED) + return FALSE + if(vv_VAS) + . = ValidateAndSet("[var_value]") + if(.) + datum_flags |= DF_VAR_EDITED + return + else + return ..() + if(var_name in banned_edits) + return FALSE + return ..() + +/datum/config_entry/proc/VASProcCallGuard(str_val) + . = !((protection & CONFIG_ENTRY_LOCKED) && IsAdminAdvancedProcCall() && GLOB.LastAdminCalledProc == "ValidateAndSet" && GLOB.LastAdminCalledTargetRef == "[REF(src)]") + if(!.) + log_admin_private("Config set of [type] to [str_val] attempted by [key_name(usr)]") + +/datum/config_entry/proc/ValidateAndSet(str_val) + VASProcCallGuard(str_val) + CRASH("Invalid config entry type!") + +/datum/config_entry/proc/ValidateListEntry(key_name, key_value) + return TRUE + +/datum/config_entry/proc/DeprecationUpdate(value) + return + +/datum/config_entry/string + config_entry_value = "" + abstract_type = /datum/config_entry/string + var/auto_trim = TRUE + +/datum/config_entry/string/vv_edit_var(var_name, var_value) + return var_name != "auto_trim" && ..() + +/datum/config_entry/string/ValidateAndSet(str_val) + if(!VASProcCallGuard(str_val)) + return FALSE + config_entry_value = auto_trim ? trim(str_val) : str_val + return TRUE + +/datum/config_entry/number + config_entry_value = 0 + abstract_type = /datum/config_entry/number + var/integer = TRUE + var/max_val = INFINITY + var/min_val = -INFINITY + +/datum/config_entry/number/ValidateAndSet(str_val) + if(!VASProcCallGuard(str_val)) + return FALSE + var/temp = text2num(trim(str_val)) + if(!isnull(temp)) + config_entry_value = clamp(integer ? round(temp) : temp, min_val, max_val) + if(config_entry_value != temp && !(datum_flags & DF_VAR_EDITED)) + log_config("Changing [name] from [temp] to [config_entry_value]!") + return TRUE + return FALSE + +/datum/config_entry/number/vv_edit_var(var_name, var_value) + var/static/list/banned_edits = list("max_val", "min_val", "integer") + return !(var_name in banned_edits) && ..() + +/datum/config_entry/flag + config_entry_value = FALSE + abstract_type = /datum/config_entry/flag + +/datum/config_entry/flag/ValidateAndSet(str_val) + if(!VASProcCallGuard(str_val)) + return FALSE + config_entry_value = text2num(trim(str_val)) != 0 + return TRUE + +/datum/config_entry/number_list + abstract_type = /datum/config_entry/number_list + config_entry_value = list() + +/datum/config_entry/number_list/ValidateAndSet(str_val) + if(!VASProcCallGuard(str_val)) + return FALSE + str_val = trim(str_val) + var/list/new_list = list() + var/list/values = splittext(str_val," ") + for(var/I in values) + var/temp = text2num(I) + if(isnull(temp)) + return FALSE + new_list += temp + if(!new_list.len) + return FALSE + config_entry_value = new_list + return TRUE + +/datum/config_entry/keyed_list + abstract_type = /datum/config_entry/keyed_list + config_entry_value = list() + dupes_allowed = TRUE + vv_VAS = FALSE //VAS will not allow things like deleting from lists, it'll just bug horribly. + var/key_mode + var/value_mode + var/splitter = " " + +/datum/config_entry/keyed_list/New() + . = ..() + if(isnull(key_mode) || isnull(value_mode)) + CRASH("Keyed list of type [type] created with null key or value mode!") + +/datum/config_entry/keyed_list/ValidateAndSet(str_val) + if(!VASProcCallGuard(str_val)) + return FALSE + + str_val = trim(str_val) + var/key_pos = findtext(str_val, splitter) + var/key_name = null + var/key_value = null + + if(key_pos || value_mode == VALUE_MODE_FLAG) + key_name = lowertext(copytext(str_val, 1, key_pos)) + if(key_pos) + key_value = copytext(str_val, key_pos + length(str_val[key_pos])) + var/new_key + var/new_value + var/continue_check_value + var/continue_check_key + switch(key_mode) + if(KEY_MODE_TEXT) + new_key = key_name + continue_check_key = new_key + if(KEY_MODE_TYPE) + new_key = key_name + if(!ispath(new_key)) + new_key = text2path(new_key) + continue_check_key = ispath(new_key) + switch(value_mode) + if(VALUE_MODE_FLAG) + new_value = TRUE + continue_check_value = TRUE + if(VALUE_MODE_NUM) + new_value = text2num(key_value) + continue_check_value = !isnull(new_value) + if(VALUE_MODE_TEXT) + new_value = key_value + continue_check_value = new_value + if(continue_check_value && continue_check_key && ValidateListEntry(new_key, new_value)) + config_entry_value[new_key] = new_value + return TRUE + return FALSE + +/datum/config_entry/keyed_list/vv_edit_var(var_name, var_value) + return var_name != "splitter" && ..() diff --git a/code/controllers/configuration/configuration.dm b/code/controllers/configuration/configuration.dm index e93c804ac251..4070964c330e 100644 --- a/code/controllers/configuration/configuration.dm +++ b/code/controllers/configuration/configuration.dm @@ -1,424 +1,424 @@ -/datum/controller/configuration - name = "Configuration" - - var/directory = "config" - - var/warned_deprecated_configs = FALSE - var/hiding_entries_by_type = TRUE //Set for readability, admins can set this to FALSE if they want to debug it - var/list/entries - var/list/entries_by_type - - var/list/maplist - var/datum/map_config/defaultmap - - var/list/modes // allowed modes - var/list/gamemode_cache - var/list/votable_modes // votable modes - var/list/mode_names - var/list/mode_reports - var/list/mode_false_report_weight - - var/motd - var/policy - - var/static/regex/ic_filter_regex - -/datum/controller/configuration/proc/admin_reload() - if(IsAdminAdvancedProcCall()) - return - log_admin("[key_name_admin(usr)] has forcefully reloaded the configuration from disk.") - message_admins("[key_name_admin(usr)] has forcefully reloaded the configuration from disk.") - full_wipe() - Load(world.params[OVERRIDE_CONFIG_DIRECTORY_PARAMETER]) - -/datum/controller/configuration/proc/Load(_directory) - if(IsAdminAdvancedProcCall()) //If admin proccall is detected down the line it will horribly break everything. - return - if(_directory) - directory = _directory - if(entries) - CRASH("/datum/controller/configuration/Load() called more than once!") - InitEntries() - LoadModes() - if(fexists("[directory]/config.txt") && LoadEntries("config.txt") <= 1) - var/list/legacy_configs = list("game_options.txt", "dbconfig.txt", "comms.txt") - for(var/I in legacy_configs) - if(fexists("[directory]/[I]")) - log_config("No $include directives found in config.txt! Loading legacy [legacy_configs.Join("/")] files...") - for(var/J in legacy_configs) - LoadEntries(J) - break - loadmaplist(CONFIG_MAPS_FILE) - LoadMOTD() - LoadPolicy() - LoadChatFilter() - -/datum/controller/configuration/proc/full_wipe() - if(IsAdminAdvancedProcCall()) - return - entries_by_type.Cut() - QDEL_LIST_ASSOC_VAL(entries) - entries = null - QDEL_LIST_ASSOC_VAL(maplist) - maplist = null - QDEL_NULL(defaultmap) - -/datum/controller/configuration/Destroy() - full_wipe() - config = null - - return ..() - -/datum/controller/configuration/proc/InitEntries() - var/list/_entries = list() - entries = _entries - var/list/_entries_by_type = list() - entries_by_type = _entries_by_type - - for(var/I in typesof(/datum/config_entry)) //typesof is faster in this case - var/datum/config_entry/E = I - if(initial(E.abstract_type) == I) - continue - E = new I - var/esname = E.name - var/datum/config_entry/test = _entries[esname] - if(test) - log_config("Error: [test.type] has the same name as [E.type]: [esname]! Not initializing [E.type]!") - qdel(E) - continue - _entries[esname] = E - _entries_by_type[I] = E - -/datum/controller/configuration/proc/RemoveEntry(datum/config_entry/CE) - entries -= CE.name - entries_by_type -= CE.type - -/datum/controller/configuration/proc/LoadEntries(filename, list/stack = list()) - if(IsAdminAdvancedProcCall()) - return - - var/filename_to_test = world.system_type == MS_WINDOWS ? lowertext(filename) : filename - if(filename_to_test in stack) - log_config("Warning: Config recursion detected ([english_list(stack)]), breaking!") - return - stack = stack + filename_to_test - - log_config("Loading config file [filename]...") - var/list/lines = world.file2list("[directory]/[filename]") - var/list/_entries = entries - for(var/L in lines) - L = trim(L) - if(!L) - continue - - var/firstchar = L[1] - if(firstchar == "#") - continue - - var/lockthis = firstchar == "@" - if(lockthis) - L = copytext(L, length(firstchar) + 1) - - var/pos = findtext(L, " ") - var/entry = null - var/value = null - - if(pos) - entry = lowertext(copytext(L, 1, pos)) - value = copytext(L, pos + length(L[pos])) - else - entry = lowertext(L) - - if(!entry) - continue - - if(entry == "$include") - if(!value) - log_config("Warning: Invalid $include directive: [value]") - else - LoadEntries(value, stack) - ++. - continue - - var/datum/config_entry/E = _entries[entry] - if(!E) - log_config("Unknown setting in configuration: '[entry]'") - continue - - if(lockthis) - E.protection |= CONFIG_ENTRY_LOCKED - - if(E.deprecated_by) - var/datum/config_entry/new_ver = entries_by_type[E.deprecated_by] - var/new_value = E.DeprecationUpdate(value) - var/good_update = istext(new_value) - log_config("Entry [entry] is deprecated and will be removed soon. Migrate to [new_ver.name]![good_update ? " Suggested new value is: [new_value]" : ""]") - if(!warned_deprecated_configs) - DelayedMessageAdmins("This server is using deprecated configuration settings. Please check the logs and update accordingly.") - warned_deprecated_configs = TRUE - if(good_update) - value = new_value - E = new_ver - else - warning("[new_ver.type] is deprecated but gave no proper return for DeprecationUpdate()") - - var/validated = E.ValidateAndSet(value) - if(!validated) - log_config("Failed to validate setting \"[value]\" for [entry]") - else - if(E.modified && !E.dupes_allowed) - log_config("Duplicate setting for [entry] ([value], [E.resident_file]) detected! Using latest.") - - E.resident_file = filename - - if(validated) - E.modified = TRUE - - ++. - -/datum/controller/configuration/can_vv_get(var_name) - return (var_name != NAMEOF(src, entries_by_type) || !hiding_entries_by_type) && ..() - -/datum/controller/configuration/vv_edit_var(var_name, var_value) - var/list/banned_edits = list(NAMEOF(src, entries_by_type), NAMEOF(src, entries), NAMEOF(src, directory)) - return !(var_name in banned_edits) && ..() - -/datum/controller/configuration/stat_entry() - if(!statclick) - statclick = new/obj/effect/statclick/debug(null, "Edit", src) - stat("[name]:", statclick) - -/datum/controller/configuration/proc/Get(entry_type) - var/datum/config_entry/E = entry_type - var/entry_is_abstract = initial(E.abstract_type) == entry_type - if(entry_is_abstract) - CRASH("Tried to retrieve an abstract config_entry: [entry_type]") - E = entries_by_type[entry_type] - if(!E) - CRASH("Missing config entry for [entry_type]!") - if((E.protection & CONFIG_ENTRY_HIDDEN) && IsAdminAdvancedProcCall() && GLOB.LastAdminCalledProc == "Get" && GLOB.LastAdminCalledTargetRef == "[REF(src)]") - log_admin_private("Config access of [entry_type] attempted by [key_name(usr)]") - return - return E.config_entry_value - -/datum/controller/configuration/proc/Set(entry_type, new_val) - var/datum/config_entry/E = entry_type - var/entry_is_abstract = initial(E.abstract_type) == entry_type - if(entry_is_abstract) - CRASH("Tried to set an abstract config_entry: [entry_type]") - E = entries_by_type[entry_type] - if(!E) - CRASH("Missing config entry for [entry_type]!") - if((E.protection & CONFIG_ENTRY_LOCKED) && IsAdminAdvancedProcCall() && GLOB.LastAdminCalledProc == "Set" && GLOB.LastAdminCalledTargetRef == "[REF(src)]") - log_admin_private("Config rewrite of [entry_type] to [new_val] attempted by [key_name(usr)]") - return - return E.ValidateAndSet("[new_val]") - -/datum/controller/configuration/proc/LoadModes() - gamemode_cache = typecacheof(/datum/game_mode, TRUE) - modes = list() - mode_names = list() - mode_reports = list() - mode_false_report_weight = list() - votable_modes = list() - var/list/probabilities = Get(/datum/config_entry/keyed_list/probability) - for(var/T in gamemode_cache) - // I wish I didn't have to instance the game modes in order to look up - // their information, but it is the only way (at least that I know of). - var/datum/game_mode/M = new T() - - if(M.config_tag) - if(!(M.config_tag in modes)) // ensure each mode is added only once - modes += M.config_tag - mode_names[M.config_tag] = M.name - probabilities[M.config_tag] = M.probability - mode_reports[M.report_type] = M.generate_report() - if(probabilities[M.config_tag]>0) - mode_false_report_weight[M.report_type] = M.false_report_weight - else - //"impossible" modes will still falsly show up occasionally, else they'll stick out like a sore thumb if an admin decides to force a disabled gamemode. - mode_false_report_weight[M.report_type] = min(1, M.false_report_weight) - if(M.votable) - votable_modes += M.config_tag - qdel(M) - votable_modes += "secret" - -/datum/controller/configuration/proc/LoadMOTD() - motd = file2text("[directory]/motd.txt") - var/tm_info = GLOB.revdata.GetTestMergeInfo() - if(motd || tm_info) - motd = motd ? "[motd]
    [tm_info]" : tm_info -/* -Policy file should be a json file with a single object. -Value is raw html. - -Possible keywords : -Job titles / Assigned roles (ghost spawners for example) : Assistant , Captain , Ash Walker -Mob types : /mob/living/simple_animal/hostile/carp -Antagonist types : /datum/antagonist/highlander -Species types : /datum/species/lizard -special keywords defined in _DEFINES/admin.dm - -Example config: -{ - "Assistant" : "Don't kill everyone", - "/datum/antagonist/highlander" : "Kill everyone", - "Ash Walker" : "Kill all spacemans" -} - -*/ -/datum/controller/configuration/proc/LoadPolicy() - policy = list() - var/rawpolicy = file2text("[directory]/policy.json") - if(rawpolicy) - var/parsed = safe_json_decode(rawpolicy) - if(!parsed) - log_config("JSON parsing failure for policy.json") - DelayedMessageAdmins("JSON parsing failure for policy.json") - else - policy = parsed - -/datum/controller/configuration/proc/loadmaplist(filename) - log_config("Loading config file [filename]...") - filename = "[directory]/[filename]" - var/list/Lines = world.file2list(filename) - - var/datum/map_config/currentmap = null - for(var/t in Lines) - if(!t) - continue - - t = trim(t) - if(length(t) == 0) - continue - else if(t[1] == "#") - continue - - var/pos = findtext(t, " ") - var/command = null - var/data = null - - if(pos) - command = lowertext(copytext(t, 1, pos)) - data = copytext(t, pos + length(t[pos])) - else - command = lowertext(t) - - if(!command) - continue - - if (!currentmap && command != "map") - continue - - switch (command) - if ("map") - currentmap = load_map_config("_maps/[data].json") - if(currentmap.defaulted) - log_config("Failed to load map config for [data]!") - currentmap = null - if ("minplayers","minplayer") - currentmap.config_min_users = text2num(data) - if ("maxplayers","maxplayer") - currentmap.config_max_users = text2num(data) - if ("weight","voteweight") - currentmap.voteweight = text2num(data) - if ("default","defaultmap") - defaultmap = currentmap - if ("votable") - currentmap.votable = TRUE - if ("endmap") - LAZYINITLIST(maplist) - maplist[currentmap.map_name] = currentmap - currentmap = null - if ("disabled") - currentmap = null - else - log_config("Unknown command in map vote config: '[command]'") - - -/datum/controller/configuration/proc/pick_mode(mode_name) - // I wish I didn't have to instance the game modes in order to look up - // their information, but it is the only way (at least that I know of). - // ^ This guy didn't try hard enough - for(var/T in gamemode_cache) - var/datum/game_mode/M = T - var/ct = initial(M.config_tag) - if(ct && ct == mode_name) - return new T - return new /datum/game_mode/extended() - -/datum/controller/configuration/proc/get_runnable_modes() - var/list/datum/game_mode/runnable_modes = new - var/list/probabilities = Get(/datum/config_entry/keyed_list/probability) - 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) - for(var/T in gamemode_cache) - var/datum/game_mode/M = new T() - if(!(M.config_tag in modes)) - qdel(M) - continue - if(probabilities[M.config_tag]<=0) - qdel(M) - continue - if(min_pop[M.config_tag]) - M.required_players = min_pop[M.config_tag] - if(max_pop[M.config_tag]) - M.maximum_players = max_pop[M.config_tag] - if(M.can_start()) - var/final_weight = probabilities[M.config_tag] - if(SSpersistence.saved_modes.len == 3 && repeated_mode_adjust.len == 3) - var/recent_round = min(SSpersistence.saved_modes.Find(M.config_tag),3) - var/adjustment = 0 - 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) - runnable_modes[M] = final_weight - return runnable_modes - -/datum/controller/configuration/proc/get_runnable_midround_modes(crew) - var/list/datum/game_mode/runnable_modes = new - var/list/probabilities = Get(/datum/config_entry/keyed_list/probability) - var/list/min_pop = Get(/datum/config_entry/keyed_list/min_pop) - var/list/max_pop = Get(/datum/config_entry/keyed_list/max_pop) - for(var/T in (gamemode_cache - SSticker.mode.type)) - var/datum/game_mode/M = new T() - if(!(M.config_tag in modes)) - qdel(M) - continue - if(probabilities[M.config_tag]<=0) - qdel(M) - continue - if(min_pop[M.config_tag]) - M.required_players = min_pop[M.config_tag] - if(max_pop[M.config_tag]) - M.maximum_players = max_pop[M.config_tag] - if(M.required_players <= crew) - if(M.maximum_players >= 0 && M.maximum_players < crew) - continue - runnable_modes[M] = probabilities[M.config_tag] - return runnable_modes - -/datum/controller/configuration/proc/LoadChatFilter() - var/list/in_character_filter = list() - - if(!fexists("[directory]/in_character_filter.txt")) - return - - log_config("Loading config file in_character_filter.txt...") - - for(var/line in world.file2list("[directory]/in_character_filter.txt")) - if(!line) - continue - if(findtextEx(line,"#",1,2)) - continue - in_character_filter += REGEX_QUOTE(line) - - ic_filter_regex = in_character_filter.len ? regex("\\b([jointext(in_character_filter, "|")])\\b", "i") : null - - syncChatRegexes() - -//Message admins when you can. -/datum/controller/configuration/proc/DelayedMessageAdmins(text) - addtimer(CALLBACK(GLOBAL_PROC, /proc/message_admins, text), 0) +/datum/controller/configuration + name = "Configuration" + + var/directory = "config" + + var/warned_deprecated_configs = FALSE + var/hiding_entries_by_type = TRUE //Set for readability, admins can set this to FALSE if they want to debug it + var/list/entries + var/list/entries_by_type + + var/list/maplist + var/datum/map_config/defaultmap + + var/list/modes // allowed modes + var/list/gamemode_cache + var/list/votable_modes // votable modes + var/list/mode_names + var/list/mode_reports + var/list/mode_false_report_weight + + var/motd + var/policy + + var/static/regex/ic_filter_regex + +/datum/controller/configuration/proc/admin_reload() + if(IsAdminAdvancedProcCall()) + return + log_admin("[key_name_admin(usr)] has forcefully reloaded the configuration from disk.") + message_admins("[key_name_admin(usr)] has forcefully reloaded the configuration from disk.") + full_wipe() + Load(world.params[OVERRIDE_CONFIG_DIRECTORY_PARAMETER]) + +/datum/controller/configuration/proc/Load(_directory) + if(IsAdminAdvancedProcCall()) //If admin proccall is detected down the line it will horribly break everything. + return + if(_directory) + directory = _directory + if(entries) + CRASH("/datum/controller/configuration/Load() called more than once!") + InitEntries() + LoadModes() + if(fexists("[directory]/config.txt") && LoadEntries("config.txt") <= 1) + var/list/legacy_configs = list("game_options.txt", "dbconfig.txt", "comms.txt") + for(var/I in legacy_configs) + if(fexists("[directory]/[I]")) + log_config("No $include directives found in config.txt! Loading legacy [legacy_configs.Join("/")] files...") + for(var/J in legacy_configs) + LoadEntries(J) + break + loadmaplist(CONFIG_MAPS_FILE) + LoadMOTD() + LoadPolicy() + LoadChatFilter() + +/datum/controller/configuration/proc/full_wipe() + if(IsAdminAdvancedProcCall()) + return + entries_by_type.Cut() + QDEL_LIST_ASSOC_VAL(entries) + entries = null + QDEL_LIST_ASSOC_VAL(maplist) + maplist = null + QDEL_NULL(defaultmap) + +/datum/controller/configuration/Destroy() + full_wipe() + config = null + + return ..() + +/datum/controller/configuration/proc/InitEntries() + var/list/_entries = list() + entries = _entries + var/list/_entries_by_type = list() + entries_by_type = _entries_by_type + + for(var/I in typesof(/datum/config_entry)) //typesof is faster in this case + var/datum/config_entry/E = I + if(initial(E.abstract_type) == I) + continue + E = new I + var/esname = E.name + var/datum/config_entry/test = _entries[esname] + if(test) + log_config("Error: [test.type] has the same name as [E.type]: [esname]! Not initializing [E.type]!") + qdel(E) + continue + _entries[esname] = E + _entries_by_type[I] = E + +/datum/controller/configuration/proc/RemoveEntry(datum/config_entry/CE) + entries -= CE.name + entries_by_type -= CE.type + +/datum/controller/configuration/proc/LoadEntries(filename, list/stack = list()) + if(IsAdminAdvancedProcCall()) + return + + var/filename_to_test = world.system_type == MS_WINDOWS ? lowertext(filename) : filename + if(filename_to_test in stack) + log_config("Warning: Config recursion detected ([english_list(stack)]), breaking!") + return + stack = stack + filename_to_test + + log_config("Loading config file [filename]...") + var/list/lines = world.file2list("[directory]/[filename]") + var/list/_entries = entries + for(var/L in lines) + L = trim(L) + if(!L) + continue + + var/firstchar = L[1] + if(firstchar == "#") + continue + + var/lockthis = firstchar == "@" + if(lockthis) + L = copytext(L, length(firstchar) + 1) + + var/pos = findtext(L, " ") + var/entry = null + var/value = null + + if(pos) + entry = lowertext(copytext(L, 1, pos)) + value = copytext(L, pos + length(L[pos])) + else + entry = lowertext(L) + + if(!entry) + continue + + if(entry == "$include") + if(!value) + log_config("Warning: Invalid $include directive: [value]") + else + LoadEntries(value, stack) + ++. + continue + + var/datum/config_entry/E = _entries[entry] + if(!E) + log_config("Unknown setting in configuration: '[entry]'") + continue + + if(lockthis) + E.protection |= CONFIG_ENTRY_LOCKED + + if(E.deprecated_by) + var/datum/config_entry/new_ver = entries_by_type[E.deprecated_by] + var/new_value = E.DeprecationUpdate(value) + var/good_update = istext(new_value) + log_config("Entry [entry] is deprecated and will be removed soon. Migrate to [new_ver.name]![good_update ? " Suggested new value is: [new_value]" : ""]") + if(!warned_deprecated_configs) + DelayedMessageAdmins("This server is using deprecated configuration settings. Please check the logs and update accordingly.") + warned_deprecated_configs = TRUE + if(good_update) + value = new_value + E = new_ver + else + warning("[new_ver.type] is deprecated but gave no proper return for DeprecationUpdate()") + + var/validated = E.ValidateAndSet(value) + if(!validated) + log_config("Failed to validate setting \"[value]\" for [entry]") + else + if(E.modified && !E.dupes_allowed) + log_config("Duplicate setting for [entry] ([value], [E.resident_file]) detected! Using latest.") + + E.resident_file = filename + + if(validated) + E.modified = TRUE + + ++. + +/datum/controller/configuration/can_vv_get(var_name) + return (var_name != NAMEOF(src, entries_by_type) || !hiding_entries_by_type) && ..() + +/datum/controller/configuration/vv_edit_var(var_name, var_value) + var/list/banned_edits = list(NAMEOF(src, entries_by_type), NAMEOF(src, entries), NAMEOF(src, directory)) + return !(var_name in banned_edits) && ..() + +/datum/controller/configuration/stat_entry() + if(!statclick) + statclick = new/obj/effect/statclick/debug(null, "Edit", src) + stat("[name]:", statclick) + +/datum/controller/configuration/proc/Get(entry_type) + var/datum/config_entry/E = entry_type + var/entry_is_abstract = initial(E.abstract_type) == entry_type + if(entry_is_abstract) + CRASH("Tried to retrieve an abstract config_entry: [entry_type]") + E = entries_by_type[entry_type] + if(!E) + CRASH("Missing config entry for [entry_type]!") + if((E.protection & CONFIG_ENTRY_HIDDEN) && IsAdminAdvancedProcCall() && GLOB.LastAdminCalledProc == "Get" && GLOB.LastAdminCalledTargetRef == "[REF(src)]") + log_admin_private("Config access of [entry_type] attempted by [key_name(usr)]") + return + return E.config_entry_value + +/datum/controller/configuration/proc/Set(entry_type, new_val) + var/datum/config_entry/E = entry_type + var/entry_is_abstract = initial(E.abstract_type) == entry_type + if(entry_is_abstract) + CRASH("Tried to set an abstract config_entry: [entry_type]") + E = entries_by_type[entry_type] + if(!E) + CRASH("Missing config entry for [entry_type]!") + if((E.protection & CONFIG_ENTRY_LOCKED) && IsAdminAdvancedProcCall() && GLOB.LastAdminCalledProc == "Set" && GLOB.LastAdminCalledTargetRef == "[REF(src)]") + log_admin_private("Config rewrite of [entry_type] to [new_val] attempted by [key_name(usr)]") + return + return E.ValidateAndSet("[new_val]") + +/datum/controller/configuration/proc/LoadModes() + gamemode_cache = typecacheof(/datum/game_mode, TRUE) + modes = list() + mode_names = list() + mode_reports = list() + mode_false_report_weight = list() + votable_modes = list() + var/list/probabilities = Get(/datum/config_entry/keyed_list/probability) + for(var/T in gamemode_cache) + // I wish I didn't have to instance the game modes in order to look up + // their information, but it is the only way (at least that I know of). + var/datum/game_mode/M = new T() + + if(M.config_tag) + if(!(M.config_tag in modes)) // ensure each mode is added only once + modes += M.config_tag + mode_names[M.config_tag] = M.name + probabilities[M.config_tag] = M.probability + mode_reports[M.report_type] = M.generate_report() + if(probabilities[M.config_tag]>0) + mode_false_report_weight[M.report_type] = M.false_report_weight + else + //"impossible" modes will still falsly show up occasionally, else they'll stick out like a sore thumb if an admin decides to force a disabled gamemode. + mode_false_report_weight[M.report_type] = min(1, M.false_report_weight) + if(M.votable) + votable_modes += M.config_tag + qdel(M) + votable_modes += "secret" + +/datum/controller/configuration/proc/LoadMOTD() + motd = file2text("[directory]/motd.txt") + var/tm_info = GLOB.revdata.GetTestMergeInfo() + if(motd || tm_info) + motd = motd ? "[motd]
    [tm_info]" : tm_info +/* +Policy file should be a json file with a single object. +Value is raw html. + +Possible keywords : +Job titles / Assigned roles (ghost spawners for example) : Assistant , Captain , Ash Walker +Mob types : /mob/living/simple_animal/hostile/carp +Antagonist types : /datum/antagonist/highlander +Species types : /datum/species/lizard +special keywords defined in _DEFINES/admin.dm + +Example config: +{ + "Assistant" : "Don't kill everyone", + "/datum/antagonist/highlander" : "Kill everyone", + "Ash Walker" : "Kill all spacemans" +} + +*/ +/datum/controller/configuration/proc/LoadPolicy() + policy = list() + var/rawpolicy = file2text("[directory]/policy.json") + if(rawpolicy) + var/parsed = safe_json_decode(rawpolicy) + if(!parsed) + log_config("JSON parsing failure for policy.json") + DelayedMessageAdmins("JSON parsing failure for policy.json") + else + policy = parsed + +/datum/controller/configuration/proc/loadmaplist(filename) + log_config("Loading config file [filename]...") + filename = "[directory]/[filename]" + var/list/Lines = world.file2list(filename) + + var/datum/map_config/currentmap = null + for(var/t in Lines) + if(!t) + continue + + t = trim(t) + if(length(t) == 0) + continue + else if(t[1] == "#") + continue + + var/pos = findtext(t, " ") + var/command = null + var/data = null + + if(pos) + command = lowertext(copytext(t, 1, pos)) + data = copytext(t, pos + length(t[pos])) + else + command = lowertext(t) + + if(!command) + continue + + if (!currentmap && command != "map") + continue + + switch (command) + if ("map") + currentmap = load_map_config("_maps/[data].json") + if(currentmap.defaulted) + log_config("Failed to load map config for [data]!") + currentmap = null + if ("minplayers","minplayer") + currentmap.config_min_users = text2num(data) + if ("maxplayers","maxplayer") + currentmap.config_max_users = text2num(data) + if ("weight","voteweight") + currentmap.voteweight = text2num(data) + if ("default","defaultmap") + defaultmap = currentmap + if ("votable") + currentmap.votable = TRUE + if ("endmap") + LAZYINITLIST(maplist) + maplist[currentmap.map_name] = currentmap + currentmap = null + if ("disabled") + currentmap = null + else + log_config("Unknown command in map vote config: '[command]'") + + +/datum/controller/configuration/proc/pick_mode(mode_name) + // I wish I didn't have to instance the game modes in order to look up + // their information, but it is the only way (at least that I know of). + // ^ This guy didn't try hard enough + for(var/T in gamemode_cache) + var/datum/game_mode/M = T + var/ct = initial(M.config_tag) + if(ct && ct == mode_name) + return new T + return new /datum/game_mode/extended() + +/datum/controller/configuration/proc/get_runnable_modes() + var/list/datum/game_mode/runnable_modes = new + var/list/probabilities = Get(/datum/config_entry/keyed_list/probability) + 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) + for(var/T in gamemode_cache) + var/datum/game_mode/M = new T() + if(!(M.config_tag in modes)) + qdel(M) + continue + if(probabilities[M.config_tag]<=0) + qdel(M) + continue + if(min_pop[M.config_tag]) + M.required_players = min_pop[M.config_tag] + if(max_pop[M.config_tag]) + M.maximum_players = max_pop[M.config_tag] + if(M.can_start()) + var/final_weight = probabilities[M.config_tag] + if(SSpersistence.saved_modes.len == 3 && repeated_mode_adjust.len == 3) + var/recent_round = min(SSpersistence.saved_modes.Find(M.config_tag),3) + var/adjustment = 0 + 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) + runnable_modes[M] = final_weight + return runnable_modes + +/datum/controller/configuration/proc/get_runnable_midround_modes(crew) + var/list/datum/game_mode/runnable_modes = new + var/list/probabilities = Get(/datum/config_entry/keyed_list/probability) + var/list/min_pop = Get(/datum/config_entry/keyed_list/min_pop) + var/list/max_pop = Get(/datum/config_entry/keyed_list/max_pop) + for(var/T in (gamemode_cache - SSticker.mode.type)) + var/datum/game_mode/M = new T() + if(!(M.config_tag in modes)) + qdel(M) + continue + if(probabilities[M.config_tag]<=0) + qdel(M) + continue + if(min_pop[M.config_tag]) + M.required_players = min_pop[M.config_tag] + if(max_pop[M.config_tag]) + M.maximum_players = max_pop[M.config_tag] + if(M.required_players <= crew) + if(M.maximum_players >= 0 && M.maximum_players < crew) + continue + runnable_modes[M] = probabilities[M.config_tag] + return runnable_modes + +/datum/controller/configuration/proc/LoadChatFilter() + var/list/in_character_filter = list() + + if(!fexists("[directory]/in_character_filter.txt")) + return + + log_config("Loading config file in_character_filter.txt...") + + for(var/line in world.file2list("[directory]/in_character_filter.txt")) + if(!line) + continue + if(findtextEx(line,"#",1,2)) + continue + in_character_filter += REGEX_QUOTE(line) + + ic_filter_regex = in_character_filter.len ? regex("\\b([jointext(in_character_filter, "|")])\\b", "i") : null + + syncChatRegexes() + +//Message admins when you can. +/datum/controller/configuration/proc/DelayedMessageAdmins(text) + addtimer(CALLBACK(GLOBAL_PROC, /proc/message_admins, text), 0) diff --git a/code/controllers/configuration/entries/comms.dm b/code/controllers/configuration/entries/comms.dm index e2c36c99f34e..a88067b19db1 100644 --- a/code/controllers/configuration/entries/comms.dm +++ b/code/controllers/configuration/entries/comms.dm @@ -1,30 +1,30 @@ -/datum/config_entry/string/comms_key - protection = CONFIG_ENTRY_HIDDEN - -/datum/config_entry/string/comms_key/ValidateAndSet(str_val) - return str_val != "default_pwd" && length(str_val) > 6 && ..() - -/datum/config_entry/keyed_list/cross_server - key_mode = KEY_MODE_TEXT - value_mode = VALUE_MODE_TEXT - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/keyed_list/cross_server/ValidateAndSet(str_val) - . = ..() - if(.) - var/list/newv = list() - for(var/I in config_entry_value) - newv[replacetext(I, "+", " ")] = config_entry_value[I] - config_entry_value = newv - -/datum/config_entry/keyed_list/cross_server/ValidateListEntry(key_name, key_value) - return key_value != "byond:\\address:port" && ..() - -/datum/config_entry/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/bot_ip +/datum/config_entry/string/comms_key + protection = CONFIG_ENTRY_HIDDEN + +/datum/config_entry/string/comms_key/ValidateAndSet(str_val) + return str_val != "default_pwd" && length(str_val) > 6 && ..() + +/datum/config_entry/keyed_list/cross_server + key_mode = KEY_MODE_TEXT + value_mode = VALUE_MODE_TEXT + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/keyed_list/cross_server/ValidateAndSet(str_val) + . = ..() + if(.) + var/list/newv = list() + for(var/I in config_entry_value) + newv[replacetext(I, "+", " ")] = config_entry_value[I] + config_entry_value = newv + +/datum/config_entry/keyed_list/cross_server/ValidateListEntry(key_name, key_value) + return key_value != "byond:\\address:port" && ..() + +/datum/config_entry/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/bot_ip diff --git a/code/controllers/configuration/entries/dbconfig.dm b/code/controllers/configuration/entries/dbconfig.dm index b03419524144..3dd4f4b0bd91 100644 --- a/code/controllers/configuration/entries/dbconfig.dm +++ b/code/controllers/configuration/entries/dbconfig.dm @@ -1,51 +1,51 @@ -/datum/config_entry/flag/sql_enabled // for sql switching - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/string/address - config_entry_value = "localhost" - protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN - -/datum/config_entry/number/port - config_entry_value = 3306 - min_val = 0 - max_val = 65535 - protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN - -/datum/config_entry/string/feedback_database - config_entry_value = "test" - protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN - -/datum/config_entry/string/feedback_login - config_entry_value = "root" - protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN - -/datum/config_entry/string/feedback_password - protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN - -/datum/config_entry/string/feedback_tableprefix - protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN - -/datum/config_entry/number/query_debug_log_timeout - config_entry_value = 70 - min_val = 1 - protection = CONFIG_ENTRY_LOCKED - deprecated_by = /datum/config_entry/number/blocking_query_timeout - -/datum/config_entry/number/query_debug_log_timeout/DeprecationUpdate(value) - return value - -/datum/config_entry/number/async_query_timeout - config_entry_value = 10 - min_val = 0 - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/number/blocking_query_timeout - config_entry_value = 5 - min_val = 0 - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/number/bsql_thread_limit - config_entry_value = 50 - min_val = 1 - -/datum/config_entry/flag/bsql_debug +/datum/config_entry/flag/sql_enabled // for sql switching + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/string/address + config_entry_value = "localhost" + protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN + +/datum/config_entry/number/port + config_entry_value = 3306 + min_val = 0 + max_val = 65535 + protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN + +/datum/config_entry/string/feedback_database + config_entry_value = "test" + protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN + +/datum/config_entry/string/feedback_login + config_entry_value = "root" + protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN + +/datum/config_entry/string/feedback_password + protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN + +/datum/config_entry/string/feedback_tableprefix + protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN + +/datum/config_entry/number/query_debug_log_timeout + config_entry_value = 70 + min_val = 1 + protection = CONFIG_ENTRY_LOCKED + deprecated_by = /datum/config_entry/number/blocking_query_timeout + +/datum/config_entry/number/query_debug_log_timeout/DeprecationUpdate(value) + return value + +/datum/config_entry/number/async_query_timeout + config_entry_value = 10 + min_val = 0 + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/number/blocking_query_timeout + config_entry_value = 5 + min_val = 0 + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/number/bsql_thread_limit + config_entry_value = 50 + min_val = 1 + +/datum/config_entry/flag/bsql_debug diff --git a/code/controllers/configuration/entries/game_options.dm b/code/controllers/configuration/entries/game_options.dm index b7436509a315..a246b6b1f7c1 100644 --- a/code/controllers/configuration/entries/game_options.dm +++ b/code/controllers/configuration/entries/game_options.dm @@ -1,404 +1,404 @@ -/datum/config_entry/number_list/repeated_mode_adjust - -/datum/config_entry/keyed_list/probability - key_mode = KEY_MODE_TEXT - value_mode = VALUE_MODE_NUM - -/datum/config_entry/keyed_list/probability/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 - -/datum/config_entry/keyed_list/max_pop/ValidateListEntry(key_name) - return key_name in config.modes - -/datum/config_entry/keyed_list/min_pop - key_mode = KEY_MODE_TEXT - value_mode = VALUE_MODE_NUM - -/datum/config_entry/keyed_list/min_pop/ValidateListEntry(key_name, key_value) - return key_name in config.modes - -/datum/config_entry/keyed_list/continuous // which roundtypes continue if all antagonists die - key_mode = KEY_MODE_TEXT - value_mode = VALUE_MODE_FLAG - -/datum/config_entry/keyed_list/continuous/ValidateListEntry(key_name, key_value) - return key_name in config.modes - -/datum/config_entry/keyed_list/midround_antag // which roundtypes use the midround antagonist system - key_mode = KEY_MODE_TEXT - value_mode = VALUE_MODE_FLAG - -/datum/config_entry/keyed_list/midround_antag/ValidateListEntry(key_name, key_value) - return key_name in config.modes - -/datum/config_entry/number/damage_multiplier - config_entry_value = 1 - integer = FALSE - -/datum/config_entry/number/minimal_access_threshold //If the number of players is larger than this threshold, minimal access will be turned on. - min_val = 0 - -/datum/config_entry/flag/jobs_have_minimal_access //determines whether jobs use minimal access or expanded access. - -/datum/config_entry/flag/assistants_have_maint_access - -/datum/config_entry/flag/security_has_maint_access - -/datum/config_entry/flag/everyone_has_maint_access - -/datum/config_entry/flag/sec_start_brig //makes sec start in brig instead of dept sec posts - -/datum/config_entry/flag/force_random_names - -/datum/config_entry/flag/humans_need_surnames - -/datum/config_entry/flag/allow_ai // allow ai job - -/datum/config_entry/flag/allow_ai_multicam // allow ai multicamera mode - -/datum/config_entry/flag/disable_human_mood - -/datum/config_entry/flag/disable_secborg // disallow secborg module to be chosen. - -/datum/config_entry/flag/disable_peaceborg - -/datum/config_entry/flag/disable_warops - -/datum/config_entry/flag/economy //money money money money money money money money money money money money - -/datum/config_entry/number/traitor_scaling_coeff //how much does the amount of players get divided by to determine traitors - config_entry_value = 6 - integer = FALSE - min_val = 1 - -/datum/config_entry/number/brother_scaling_coeff //how many players per brother team - config_entry_value = 25 - integer = FALSE - min_val = 1 - -/datum/config_entry/number/changeling_scaling_coeff //how much does the amount of players get divided by to determine changelings - config_entry_value = 6 - integer = FALSE - min_val = 1 - -/datum/config_entry/number/security_scaling_coeff //how much does the amount of players get divided by to determine open security officer positions - config_entry_value = 8 - integer = FALSE - min_val = 1 - -/datum/config_entry/number/abductor_scaling_coeff //how many players per abductor team - config_entry_value = 15 - integer = FALSE - min_val = 1 - -/datum/config_entry/number/traitor_objectives_amount - config_entry_value = 2 - min_val = 0 - -/datum/config_entry/number/brother_objectives_amount - config_entry_value = 2 - min_val = 0 - -/datum/config_entry/flag/reactionary_explosions //If we use reactionary explosions, explosions that react to walls and doors - -/datum/config_entry/flag/protect_roles_from_antagonist //If security and such can be traitor/cult/other - -/datum/config_entry/flag/protect_assistant_from_antagonist //If assistants can be traitor/cult/other - -/datum/config_entry/flag/enforce_human_authority //If non-human species are barred from joining as a head of staff - -/datum/config_entry/flag/allow_latejoin_antagonists // If late-joining players can be traitor/changeling - -/datum/config_entry/flag/use_antag_rep // see game_options.txt for details - -/datum/config_entry/number/antag_rep_maximum - config_entry_value = 200 - integer = FALSE - min_val = 0 - -/datum/config_entry/number/default_antag_tickets - config_entry_value = 100 - integer = FALSE - min_val = 0 - -/datum/config_entry/number/max_tickets_per_roll - config_entry_value = 100 - integer = FALSE - min_val = 0 - -/datum/config_entry/number/midround_antag_time_check // How late (in minutes you want the midround antag system to stay on, setting this to 0 will disable the system) - config_entry_value = 60 - integer = FALSE - min_val = 0 - -/datum/config_entry/number/midround_antag_life_check // A ratio of how many people need to be alive in order for the round not to immediately end in midround antagonist - config_entry_value = 0.7 - integer = FALSE - min_val = 0 - max_val = 1 - -/datum/config_entry/number/shuttle_refuel_delay - config_entry_value = 12000 - integer = FALSE - min_val = 0 - -/datum/config_entry/flag/show_game_type_odds //if set this allows players to see the odds of each roundtype on the get revision screen - -/datum/config_entry/keyed_list/roundstart_races //races you can play as from the get go. - key_mode = KEY_MODE_TEXT - value_mode = VALUE_MODE_FLAG - -/datum/config_entry/keyed_list/roundstart_no_hard_check // Species contained in this list will not cause existing characters with no-longer-roundstart species set to be resetted to the human race. - key_mode = KEY_MODE_TEXT - value_mode = VALUE_MODE_FLAG - -/datum/config_entry/flag/join_with_mutant_humans //players can pick mutant bodyparts for humans before joining the game - -/datum/config_entry/flag/no_summon_guns //No - -/datum/config_entry/flag/no_summon_magic //Fun - -/datum/config_entry/flag/no_summon_events //Allowed - -/datum/config_entry/flag/no_intercept_report //Whether or not to send a communications intercept report roundstart. This may be overridden by gamemodes. - -/datum/config_entry/number/arrivals_shuttle_dock_window //Time from when a player late joins on the arrivals shuttle to when the shuttle docks on the station - config_entry_value = 55 - integer = FALSE - min_val = 30 - -/datum/config_entry/flag/arrivals_shuttle_require_undocked //Require the arrivals shuttle to be undocked before latejoiners can join - -/datum/config_entry/flag/arrivals_shuttle_require_safe_latejoin //Require the arrivals shuttle to be operational in order for latejoiners to join - -/datum/config_entry/string/alert_green - config_entry_value = "All threats to the station have passed. Security may not have weapons visible, privacy laws are once again fully enforced." - -/datum/config_entry/string/alert_blue_upto - config_entry_value = "The station has received reliable information about possible hostile activity on the station. Security staff may have weapons visible, random searches are permitted." - -/datum/config_entry/string/alert_blue_downto - config_entry_value = "The immediate threat has passed. Security may no longer have weapons drawn at all times, but may continue to have them visible. Random searches are still allowed." - -/datum/config_entry/string/alert_red_upto - config_entry_value = "There is an immediate serious threat to the station. Security may have weapons unholstered at all times. Random searches are allowed and advised." - -/datum/config_entry/string/alert_red_downto - config_entry_value = "The station's destruction has been averted. There is still however an immediate serious threat to the station. Security may have weapons unholstered at all times, random searches are allowed and advised." - -/datum/config_entry/string/alert_delta - config_entry_value = "Destruction of the station is imminent. All crew are instructed to obey all instructions given by heads of staff. Any violations of these orders can be punished by death. This is not a drill." - -/datum/config_entry/flag/revival_pod_plants - -/datum/config_entry/flag/revival_cloning - -/datum/config_entry/number/revival_brain_life - config_entry_value = -1 - integer = FALSE - min_val = -1 - -/datum/config_entry/flag/ooc_during_round - -/datum/config_entry/flag/emojis - -/datum/config_entry/keyed_list/multiplicative_movespeed - key_mode = KEY_MODE_TYPE - value_mode = VALUE_MODE_NUM - config_entry_value = list( //DEFAULTS - /mob/living/simple_animal = 1, - /mob/living/silicon/pai = 1, - /mob/living/carbon/alien/humanoid/hunter = -1, - /mob/living/carbon/alien/humanoid/royal/praetorian = 1, - /mob/living/carbon/alien/humanoid/royal/queen = 3 - ) - -/datum/config_entry/keyed_list/multiplicative_movespeed/ValidateAndSet() - . = ..() - if(.) - update_config_movespeed_type_lookup(TRUE) - -/datum/config_entry/keyed_list/multiplicative_movespeed/vv_edit_var(var_name, var_value) - . = ..() - if(. && (var_name == NAMEOF(src, config_entry_value))) - update_config_movespeed_type_lookup(TRUE) - -/datum/config_entry/number/movedelay //Used for modifying movement speed for mobs. - abstract_type = /datum/config_entry/number/movedelay - -/datum/config_entry/number/movedelay/ValidateAndSet() - . = ..() - if(.) - update_mob_config_movespeeds() - -/datum/config_entry/number/movedelay/vv_edit_var(var_name, var_value) - . = ..() - if(. && (var_name == NAMEOF(src, config_entry_value))) - update_mob_config_movespeeds() - -/datum/config_entry/number/movedelay/run_delay - integer = FALSE - -/datum/config_entry/number/movedelay/run_delay/ValidateAndSet() - . = ..() - var/datum/movespeed_modifier/config_walk_run/M = get_cached_movespeed_modifier(/datum/movespeed_modifier/config_walk_run/run) - M.sync() - -/datum/config_entry/number/movedelay/walk_delay - integer = FALSE - -/datum/config_entry/number/movedelay/walk_delay/ValidateAndSet() - . = ..() - var/datum/movespeed_modifier/config_walk_run/M = get_cached_movespeed_modifier(/datum/movespeed_modifier/config_walk_run/walk) - M.sync() - -/////////////////////////////////////////////////Outdated move delay -/datum/config_entry/number/outdated_movedelay - deprecated_by = /datum/config_entry/keyed_list/multiplicative_movespeed - abstract_type = /datum/config_entry/number/outdated_movedelay - integer = FALSE - var/movedelay_type - -/datum/config_entry/number/outdated_movedelay/DeprecationUpdate(value) - return "[movedelay_type] [value]" - -/datum/config_entry/number/outdated_movedelay/human_delay - movedelay_type = /mob/living/carbon/human -/datum/config_entry/number/outdated_movedelay/robot_delay - movedelay_type = /mob/living/silicon/robot -/datum/config_entry/number/outdated_movedelay/monkey_delay - movedelay_type = /mob/living/carbon/monkey -/datum/config_entry/number/outdated_movedelay/alien_delay - movedelay_type = /mob/living/carbon/alien -/datum/config_entry/number/outdated_movedelay/slime_delay - movedelay_type = /mob/living/simple_animal/slime -/datum/config_entry/number/outdated_movedelay/animal_delay - movedelay_type = /mob/living/simple_animal -///////////////////////////////////////////////// - -/datum/config_entry/flag/virtual_reality //Will virtual reality be loaded - -/datum/config_entry/flag/roundstart_away //Will random away mission be loaded. - -/datum/config_entry/number/gateway_delay //How long the gateway takes before it activates. Default is half an hour. Only matters if roundstart_away is enabled. - config_entry_value = 18000 - integer = FALSE - min_val = 0 - -/datum/config_entry/flag/ghost_interaction - -/datum/config_entry/flag/near_death_experience //If carbons can hear ghosts when unconscious and very close to death - -/datum/config_entry/flag/silent_ai -/datum/config_entry/flag/silent_borg - -/datum/config_entry/flag/sandbox_autoclose // close the sandbox panel after spawning an item, potentially reducing griff - -/datum/config_entry/number/default_laws //Controls what laws the AI spawns with. - config_entry_value = 0 - min_val = 0 - max_val = 3 - -/datum/config_entry/number/silicon_max_law_amount - config_entry_value = 12 - min_val = 0 - -/datum/config_entry/keyed_list/random_laws - key_mode = KEY_MODE_TEXT - value_mode = VALUE_MODE_FLAG - -/datum/config_entry/keyed_list/law_weight - key_mode = KEY_MODE_TEXT - value_mode = VALUE_MODE_NUM - splitter = "," - -/datum/config_entry/number/max_law_len - config_entry_value = 1024 - -/datum/config_entry/number/overflow_cap - config_entry_value = -1 - min_val = -1 - -/datum/config_entry/string/overflow_job - config_entry_value = "Assistant" - -/datum/config_entry/flag/starlight -/datum/config_entry/flag/grey_assistants - -/datum/config_entry/number/lavaland_budget - config_entry_value = 60 - integer = FALSE - min_val = 0 - -/datum/config_entry/number/space_budget - config_entry_value = 16 - integer = FALSE - min_val = 0 - -/datum/config_entry/flag/allow_random_events // Enables random events mid-round when set - -/datum/config_entry/number/events_min_time_mul // Multipliers for random events minimal starting time and minimal players amounts - config_entry_value = 1 - min_val = 0 - integer = FALSE - -/datum/config_entry/number/events_min_players_mul - config_entry_value = 1 - min_val = 0 - integer = FALSE - -/datum/config_entry/number/mice_roundstart - config_entry_value = 10 - min_val = 0 - -/datum/config_entry/number/bombcap - config_entry_value = 14 - min_val = 4 - -/datum/config_entry/number/bombcap/ValidateAndSet(str_val) - . = ..() - if(.) - GLOB.MAX_EX_DEVESTATION_RANGE = round(config_entry_value / 4) - GLOB.MAX_EX_HEAVY_RANGE = round(config_entry_value / 2) - GLOB.MAX_EX_LIGHT_RANGE = config_entry_value - GLOB.MAX_EX_FLASH_RANGE = config_entry_value - GLOB.MAX_EX_FLAME_RANGE = config_entry_value - -/datum/config_entry/number/emergency_shuttle_autocall_threshold - min_val = 0 - max_val = 1 - integer = FALSE - -/datum/config_entry/flag/ic_printing - -/datum/config_entry/flag/roundstart_traits - -/datum/config_entry/flag/enable_night_shifts - -/datum/config_entry/flag/randomize_shift_time - -/datum/config_entry/flag/shift_time_realtime - -/datum/config_entry/keyed_list/antag_rep - key_mode = KEY_MODE_TEXT - value_mode = VALUE_MODE_NUM - -/datum/config_entry/number/monkeycap - config_entry_value = 64 - min_val = 0 - -/datum/config_entry/number/ratcap - config_entry_value = 64 - min_val = 0 - -/datum/config_entry/number/maxfine - config_entry_value = 1000 - min_val = 0 - -/datum/config_entry/flag/dynamic_config_enabled - -/datum/config_entry/flag/allow_crew_objectives +/datum/config_entry/number_list/repeated_mode_adjust + +/datum/config_entry/keyed_list/probability + key_mode = KEY_MODE_TEXT + value_mode = VALUE_MODE_NUM + +/datum/config_entry/keyed_list/probability/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 + +/datum/config_entry/keyed_list/max_pop/ValidateListEntry(key_name) + return key_name in config.modes + +/datum/config_entry/keyed_list/min_pop + key_mode = KEY_MODE_TEXT + value_mode = VALUE_MODE_NUM + +/datum/config_entry/keyed_list/min_pop/ValidateListEntry(key_name, key_value) + return key_name in config.modes + +/datum/config_entry/keyed_list/continuous // which roundtypes continue if all antagonists die + key_mode = KEY_MODE_TEXT + value_mode = VALUE_MODE_FLAG + +/datum/config_entry/keyed_list/continuous/ValidateListEntry(key_name, key_value) + return key_name in config.modes + +/datum/config_entry/keyed_list/midround_antag // which roundtypes use the midround antagonist system + key_mode = KEY_MODE_TEXT + value_mode = VALUE_MODE_FLAG + +/datum/config_entry/keyed_list/midround_antag/ValidateListEntry(key_name, key_value) + return key_name in config.modes + +/datum/config_entry/number/damage_multiplier + config_entry_value = 1 + integer = FALSE + +/datum/config_entry/number/minimal_access_threshold //If the number of players is larger than this threshold, minimal access will be turned on. + min_val = 0 + +/datum/config_entry/flag/jobs_have_minimal_access //determines whether jobs use minimal access or expanded access. + +/datum/config_entry/flag/assistants_have_maint_access + +/datum/config_entry/flag/security_has_maint_access + +/datum/config_entry/flag/everyone_has_maint_access + +/datum/config_entry/flag/sec_start_brig //makes sec start in brig instead of dept sec posts + +/datum/config_entry/flag/force_random_names + +/datum/config_entry/flag/humans_need_surnames + +/datum/config_entry/flag/allow_ai // allow ai job + +/datum/config_entry/flag/allow_ai_multicam // allow ai multicamera mode + +/datum/config_entry/flag/disable_human_mood + +/datum/config_entry/flag/disable_secborg // disallow secborg module to be chosen. + +/datum/config_entry/flag/disable_peaceborg + +/datum/config_entry/flag/disable_warops + +/datum/config_entry/flag/economy //money money money money money money money money money money money money + +/datum/config_entry/number/traitor_scaling_coeff //how much does the amount of players get divided by to determine traitors + config_entry_value = 6 + integer = FALSE + min_val = 1 + +/datum/config_entry/number/brother_scaling_coeff //how many players per brother team + config_entry_value = 25 + integer = FALSE + min_val = 1 + +/datum/config_entry/number/changeling_scaling_coeff //how much does the amount of players get divided by to determine changelings + config_entry_value = 6 + integer = FALSE + min_val = 1 + +/datum/config_entry/number/security_scaling_coeff //how much does the amount of players get divided by to determine open security officer positions + config_entry_value = 8 + integer = FALSE + min_val = 1 + +/datum/config_entry/number/abductor_scaling_coeff //how many players per abductor team + config_entry_value = 15 + integer = FALSE + min_val = 1 + +/datum/config_entry/number/traitor_objectives_amount + config_entry_value = 2 + min_val = 0 + +/datum/config_entry/number/brother_objectives_amount + config_entry_value = 2 + min_val = 0 + +/datum/config_entry/flag/reactionary_explosions //If we use reactionary explosions, explosions that react to walls and doors + +/datum/config_entry/flag/protect_roles_from_antagonist //If security and such can be traitor/cult/other + +/datum/config_entry/flag/protect_assistant_from_antagonist //If assistants can be traitor/cult/other + +/datum/config_entry/flag/enforce_human_authority //If non-human species are barred from joining as a head of staff + +/datum/config_entry/flag/allow_latejoin_antagonists // If late-joining players can be traitor/changeling + +/datum/config_entry/flag/use_antag_rep // see game_options.txt for details + +/datum/config_entry/number/antag_rep_maximum + config_entry_value = 200 + integer = FALSE + min_val = 0 + +/datum/config_entry/number/default_antag_tickets + config_entry_value = 100 + integer = FALSE + min_val = 0 + +/datum/config_entry/number/max_tickets_per_roll + config_entry_value = 100 + integer = FALSE + min_val = 0 + +/datum/config_entry/number/midround_antag_time_check // How late (in minutes you want the midround antag system to stay on, setting this to 0 will disable the system) + config_entry_value = 60 + integer = FALSE + min_val = 0 + +/datum/config_entry/number/midround_antag_life_check // A ratio of how many people need to be alive in order for the round not to immediately end in midround antagonist + config_entry_value = 0.7 + integer = FALSE + min_val = 0 + max_val = 1 + +/datum/config_entry/number/shuttle_refuel_delay + config_entry_value = 12000 + integer = FALSE + min_val = 0 + +/datum/config_entry/flag/show_game_type_odds //if set this allows players to see the odds of each roundtype on the get revision screen + +/datum/config_entry/keyed_list/roundstart_races //races you can play as from the get go. + key_mode = KEY_MODE_TEXT + value_mode = VALUE_MODE_FLAG + +/datum/config_entry/keyed_list/roundstart_no_hard_check // Species contained in this list will not cause existing characters with no-longer-roundstart species set to be resetted to the human race. + key_mode = KEY_MODE_TEXT + value_mode = VALUE_MODE_FLAG + +/datum/config_entry/flag/join_with_mutant_humans //players can pick mutant bodyparts for humans before joining the game + +/datum/config_entry/flag/no_summon_guns //No + +/datum/config_entry/flag/no_summon_magic //Fun + +/datum/config_entry/flag/no_summon_events //Allowed + +/datum/config_entry/flag/no_intercept_report //Whether or not to send a communications intercept report roundstart. This may be overridden by gamemodes. + +/datum/config_entry/number/arrivals_shuttle_dock_window //Time from when a player late joins on the arrivals shuttle to when the shuttle docks on the station + config_entry_value = 55 + integer = FALSE + min_val = 30 + +/datum/config_entry/flag/arrivals_shuttle_require_undocked //Require the arrivals shuttle to be undocked before latejoiners can join + +/datum/config_entry/flag/arrivals_shuttle_require_safe_latejoin //Require the arrivals shuttle to be operational in order for latejoiners to join + +/datum/config_entry/string/alert_green + config_entry_value = "All threats to the station have passed. Security may not have weapons visible, privacy laws are once again fully enforced." + +/datum/config_entry/string/alert_blue_upto + config_entry_value = "The station has received reliable information about possible hostile activity on the station. Security staff may have weapons visible, random searches are permitted." + +/datum/config_entry/string/alert_blue_downto + config_entry_value = "The immediate threat has passed. Security may no longer have weapons drawn at all times, but may continue to have them visible. Random searches are still allowed." + +/datum/config_entry/string/alert_red_upto + config_entry_value = "There is an immediate serious threat to the station. Security may have weapons unholstered at all times. Random searches are allowed and advised." + +/datum/config_entry/string/alert_red_downto + config_entry_value = "The station's destruction has been averted. There is still however an immediate serious threat to the station. Security may have weapons unholstered at all times, random searches are allowed and advised." + +/datum/config_entry/string/alert_delta + config_entry_value = "Destruction of the station is imminent. All crew are instructed to obey all instructions given by heads of staff. Any violations of these orders can be punished by death. This is not a drill." + +/datum/config_entry/flag/revival_pod_plants + +/datum/config_entry/flag/revival_cloning + +/datum/config_entry/number/revival_brain_life + config_entry_value = -1 + integer = FALSE + min_val = -1 + +/datum/config_entry/flag/ooc_during_round + +/datum/config_entry/flag/emojis + +/datum/config_entry/keyed_list/multiplicative_movespeed + key_mode = KEY_MODE_TYPE + value_mode = VALUE_MODE_NUM + config_entry_value = list( //DEFAULTS + /mob/living/simple_animal = 1, + /mob/living/silicon/pai = 1, + /mob/living/carbon/alien/humanoid/hunter = -1, + /mob/living/carbon/alien/humanoid/royal/praetorian = 1, + /mob/living/carbon/alien/humanoid/royal/queen = 3 + ) + +/datum/config_entry/keyed_list/multiplicative_movespeed/ValidateAndSet() + . = ..() + if(.) + update_config_movespeed_type_lookup(TRUE) + +/datum/config_entry/keyed_list/multiplicative_movespeed/vv_edit_var(var_name, var_value) + . = ..() + if(. && (var_name == NAMEOF(src, config_entry_value))) + update_config_movespeed_type_lookup(TRUE) + +/datum/config_entry/number/movedelay //Used for modifying movement speed for mobs. + abstract_type = /datum/config_entry/number/movedelay + +/datum/config_entry/number/movedelay/ValidateAndSet() + . = ..() + if(.) + update_mob_config_movespeeds() + +/datum/config_entry/number/movedelay/vv_edit_var(var_name, var_value) + . = ..() + if(. && (var_name == NAMEOF(src, config_entry_value))) + update_mob_config_movespeeds() + +/datum/config_entry/number/movedelay/run_delay + integer = FALSE + +/datum/config_entry/number/movedelay/run_delay/ValidateAndSet() + . = ..() + var/datum/movespeed_modifier/config_walk_run/M = get_cached_movespeed_modifier(/datum/movespeed_modifier/config_walk_run/run) + M.sync() + +/datum/config_entry/number/movedelay/walk_delay + integer = FALSE + +/datum/config_entry/number/movedelay/walk_delay/ValidateAndSet() + . = ..() + var/datum/movespeed_modifier/config_walk_run/M = get_cached_movespeed_modifier(/datum/movespeed_modifier/config_walk_run/walk) + M.sync() + +/////////////////////////////////////////////////Outdated move delay +/datum/config_entry/number/outdated_movedelay + deprecated_by = /datum/config_entry/keyed_list/multiplicative_movespeed + abstract_type = /datum/config_entry/number/outdated_movedelay + integer = FALSE + var/movedelay_type + +/datum/config_entry/number/outdated_movedelay/DeprecationUpdate(value) + return "[movedelay_type] [value]" + +/datum/config_entry/number/outdated_movedelay/human_delay + movedelay_type = /mob/living/carbon/human +/datum/config_entry/number/outdated_movedelay/robot_delay + movedelay_type = /mob/living/silicon/robot +/datum/config_entry/number/outdated_movedelay/monkey_delay + movedelay_type = /mob/living/carbon/monkey +/datum/config_entry/number/outdated_movedelay/alien_delay + movedelay_type = /mob/living/carbon/alien +/datum/config_entry/number/outdated_movedelay/slime_delay + movedelay_type = /mob/living/simple_animal/slime +/datum/config_entry/number/outdated_movedelay/animal_delay + movedelay_type = /mob/living/simple_animal +///////////////////////////////////////////////// + +/datum/config_entry/flag/virtual_reality //Will virtual reality be loaded + +/datum/config_entry/flag/roundstart_away //Will random away mission be loaded. + +/datum/config_entry/number/gateway_delay //How long the gateway takes before it activates. Default is half an hour. Only matters if roundstart_away is enabled. + config_entry_value = 18000 + integer = FALSE + min_val = 0 + +/datum/config_entry/flag/ghost_interaction + +/datum/config_entry/flag/near_death_experience //If carbons can hear ghosts when unconscious and very close to death + +/datum/config_entry/flag/silent_ai +/datum/config_entry/flag/silent_borg + +/datum/config_entry/flag/sandbox_autoclose // close the sandbox panel after spawning an item, potentially reducing griff + +/datum/config_entry/number/default_laws //Controls what laws the AI spawns with. + config_entry_value = 0 + min_val = 0 + max_val = 3 + +/datum/config_entry/number/silicon_max_law_amount + config_entry_value = 12 + min_val = 0 + +/datum/config_entry/keyed_list/random_laws + key_mode = KEY_MODE_TEXT + value_mode = VALUE_MODE_FLAG + +/datum/config_entry/keyed_list/law_weight + key_mode = KEY_MODE_TEXT + value_mode = VALUE_MODE_NUM + splitter = "," + +/datum/config_entry/number/max_law_len + config_entry_value = 1024 + +/datum/config_entry/number/overflow_cap + config_entry_value = -1 + min_val = -1 + +/datum/config_entry/string/overflow_job + config_entry_value = "Assistant" + +/datum/config_entry/flag/starlight +/datum/config_entry/flag/grey_assistants + +/datum/config_entry/number/lavaland_budget + config_entry_value = 60 + integer = FALSE + min_val = 0 + +/datum/config_entry/number/space_budget + config_entry_value = 16 + integer = FALSE + min_val = 0 + +/datum/config_entry/flag/allow_random_events // Enables random events mid-round when set + +/datum/config_entry/number/events_min_time_mul // Multipliers for random events minimal starting time and minimal players amounts + config_entry_value = 1 + min_val = 0 + integer = FALSE + +/datum/config_entry/number/events_min_players_mul + config_entry_value = 1 + min_val = 0 + integer = FALSE + +/datum/config_entry/number/mice_roundstart + config_entry_value = 10 + min_val = 0 + +/datum/config_entry/number/bombcap + config_entry_value = 14 + min_val = 4 + +/datum/config_entry/number/bombcap/ValidateAndSet(str_val) + . = ..() + if(.) + GLOB.MAX_EX_DEVESTATION_RANGE = round(config_entry_value / 4) + GLOB.MAX_EX_HEAVY_RANGE = round(config_entry_value / 2) + GLOB.MAX_EX_LIGHT_RANGE = config_entry_value + GLOB.MAX_EX_FLASH_RANGE = config_entry_value + GLOB.MAX_EX_FLAME_RANGE = config_entry_value + +/datum/config_entry/number/emergency_shuttle_autocall_threshold + min_val = 0 + max_val = 1 + integer = FALSE + +/datum/config_entry/flag/ic_printing + +/datum/config_entry/flag/roundstart_traits + +/datum/config_entry/flag/enable_night_shifts + +/datum/config_entry/flag/randomize_shift_time + +/datum/config_entry/flag/shift_time_realtime + +/datum/config_entry/keyed_list/antag_rep + key_mode = KEY_MODE_TEXT + value_mode = VALUE_MODE_NUM + +/datum/config_entry/number/monkeycap + config_entry_value = 64 + min_val = 0 + +/datum/config_entry/number/ratcap + config_entry_value = 64 + min_val = 0 + +/datum/config_entry/number/maxfine + config_entry_value = 1000 + min_val = 0 + +/datum/config_entry/flag/dynamic_config_enabled + +/datum/config_entry/flag/allow_crew_objectives diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm index 3440b61bb29f..064584cf3073 100644 --- a/code/controllers/configuration/entries/general.dm +++ b/code/controllers/configuration/entries/general.dm @@ -1,540 +1,540 @@ -/datum/config_entry/flag/autoadmin // if autoadmin is enabled - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/string/autoadmin_rank // the rank for autoadmins - config_entry_value = "Game Master" - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/flag/auto_deadmin_players - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/number/auto_deadmin_timegate - config_entry_value = null - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/flag/auto_deadmin_antagonists - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/flag/auto_deadmin_heads - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/flag/auto_deadmin_silicons - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/flag/auto_deadmin_security - protection = CONFIG_ENTRY_LOCKED - - -/datum/config_entry/string/servername // server name (the name of the game window) - -/datum/config_entry/string/serversqlname // short form server name used for the DB - -/datum/config_entry/string/stationname // station name (the name of the station in-game) - -/datum/config_entry/number/lobby_countdown // In between round countdown. - config_entry_value = 120 - integer = FALSE - min_val = 0 - -/datum/config_entry/number/round_end_countdown // Post round murder death kill countdown - config_entry_value = 25 - integer = FALSE - min_val = 0 - -/datum/config_entry/flag/hub // if the game appears on the hub or not - -/datum/config_entry/number/max_hub_pop //At what pop to take hub off the server - config_entry_value = 0 //0 means disabled - integer = TRUE - min_val = 0 - -/datum/config_entry/flag/log_ooc // log OOC channel - -/datum/config_entry/flag/log_access // log login/logout - -/datum/config_entry/flag/log_say // log client say - -/datum/config_entry/flag/log_admin // log admin actions - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/flag/log_prayer // log prayers - -/datum/config_entry/flag/log_law // log lawchanges - -/datum/config_entry/flag/log_game // log game events - -/datum/config_entry/flag/log_mecha // log mech data - -/datum/config_entry/flag/log_virus // log virology data - -/datum/config_entry/flag/log_cloning // log cloning actions. - -/datum/config_entry/flag/log_vote // log voting - -/datum/config_entry/flag/log_whisper // log client whisper - -/datum/config_entry/flag/log_attack // log attack messages - -/datum/config_entry/flag/log_emote // log emotes - -/datum/config_entry/flag/log_econ // log economy actions - -/datum/config_entry/flag/log_adminchat // log admin chat messages - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/flag/log_pda // log pda messages - -/datum/config_entry/flag/log_telecomms // log telecomms messages - -/datum/config_entry/flag/log_twitter // log certain expliotable parrots and other such fun things in a JSON file of twitter valid phrases. - -/datum/config_entry/flag/log_world_topic // log all world.Topic() calls - -/datum/config_entry/flag/log_manifest // log crew manifest to seperate file - -/datum/config_entry/flag/log_job_debug // log roundstart divide occupations debug information to a file - -/datum/config_entry/flag/log_shuttle // log shuttle related actions, ie shuttle computers, shuttle manipulator, emergency console - -/datum/config_entry/flag/allow_admin_ooccolor // Allows admins with relevant permissions to have their own ooc colour - -/datum/config_entry/flag/allow_admin_asaycolor //Allows admins with relevant permissions to have a personalized asay color - -/datum/config_entry/flag/allow_vote_restart // allow votes to restart - -/datum/config_entry/flag/allow_vote_mode // allow votes to change mode - -/datum/config_entry/flag/allow_vote_map // allow votes to change map - -/datum/config_entry/number/vote_delay // minimum time between voting sessions (deciseconds, 10 minute default) - config_entry_value = 6000 - integer = FALSE - min_val = 0 - -/datum/config_entry/number/vote_period // length of voting period (deciseconds, default 1 minute) - config_entry_value = 600 - integer = FALSE - min_val = 0 - -//Waspstation Begin - Autotranfer vote - -/datum/config_entry/number/vote_autotransfer_initial //length of time before the first autotransfer vote is called (deciseconds, default 2 hours) - config_entry_value = 72000 - integer = FALSE - min_val = 0 - -/datum/config_entry/number/vote_autotransfer_interval //length of time to wait before subsequent autotransfer votes (deciseconds, default 30 minutes) - config_entry_value = 18000 - integer = FALSE - min_val = 0 - -//Waspstation End - -/datum/config_entry/flag/default_no_vote // vote does not default to nochange/norestart - -/datum/config_entry/flag/no_dead_vote // dead people can't vote - -/datum/config_entry/flag/allow_metadata // Metadata is supported. - -/datum/config_entry/flag/popup_admin_pm // adminPMs to non-admins show in a pop-up 'reply' window when set - -/datum/config_entry/number/fps - config_entry_value = 20 - integer = FALSE - min_val = 1 - max_val = 100 //byond will start crapping out at 50, so this is just ridic - var/sync_validate = FALSE - -/datum/config_entry/number/fps/ValidateAndSet(str_val) - . = ..() - if(.) - sync_validate = TRUE - var/datum/config_entry/number/ticklag/TL = config.entries_by_type[/datum/config_entry/number/ticklag] - if(!TL.sync_validate) - TL.ValidateAndSet(10 / config_entry_value) - sync_validate = FALSE - -/datum/config_entry/number/ticklag - integer = FALSE - var/sync_validate = FALSE - -/datum/config_entry/number/ticklag/New() //ticklag weirdly just mirrors fps - var/datum/config_entry/CE = /datum/config_entry/number/fps - config_entry_value = 10 / initial(CE.config_entry_value) - ..() - -/datum/config_entry/number/ticklag/ValidateAndSet(str_val) - . = text2num(str_val) > 0 && ..() - if(.) - sync_validate = TRUE - var/datum/config_entry/number/fps/FPS = config.entries_by_type[/datum/config_entry/number/fps] - if(!FPS.sync_validate) - FPS.ValidateAndSet(10 / config_entry_value) - sync_validate = FALSE - -/datum/config_entry/flag/allow_holidays - -/datum/config_entry/number/tick_limit_mc_init //SSinitialization throttling - config_entry_value = TICK_LIMIT_MC_INIT_DEFAULT - min_val = 0 //oranges warned us - integer = FALSE - -/datum/config_entry/flag/admin_legacy_system //Defines whether the server uses the legacy admin system with admins.txt or the SQL system - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/flag/protect_legacy_admins //Stops any admins loaded by the legacy system from having their rank edited by the permissions panel - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/flag/protect_legacy_ranks //Stops any ranks loaded by the legacy system from having their flags edited by the permissions panel - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/flag/enable_localhost_rank //Gives the !localhost! rank to any client connecting from 127.0.0.1 or ::1 - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/flag/load_legacy_ranks_only //Loads admin ranks only from legacy admin_ranks.txt, while enabled ranks are mirrored to the database - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/flag/mentors_mobname_only // Only display mob name to mentors in mentorhelps - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/flag/mentor_legacy_system // Whether to use the legacy mentor system (flat file) instead of SQL - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/string/hostedby - -/datum/config_entry/flag/norespawn - -/datum/config_entry/flag/guest_jobban - -/datum/config_entry/flag/usewhitelist - -/datum/config_entry/flag/use_age_restriction_for_jobs //Do jobs use account age restrictions? --requires database - -/datum/config_entry/flag/use_account_age_for_jobs //Uses the time they made the account for the job restriction stuff. New player joining alerts should be unaffected. - -/datum/config_entry/flag/use_exp_tracking - -/datum/config_entry/flag/use_exp_restrictions_heads - -/datum/config_entry/number/use_exp_restrictions_heads_hours - config_entry_value = 0 - integer = FALSE - min_val = 0 - -/datum/config_entry/flag/use_exp_restrictions_heads_department - -/datum/config_entry/flag/use_exp_restrictions_other - -/datum/config_entry/flag/use_exp_restrictions_admin_bypass - -/datum/config_entry/string/server - -/datum/config_entry/string/banappeals - -/datum/config_entry/string/wikiurl - config_entry_value = "http://www.tgstation13.org/wiki" - -/datum/config_entry/string/forumurl - config_entry_value = "http://tgstation13.org/phpBB/index.php" - -/datum/config_entry/string/rulesurl - config_entry_value = "http://www.tgstation13.org/wiki/Rules" - -/datum/config_entry/string/githuburl - config_entry_value = "https://www.github.com/tgstation/-tg-station" - -/datum/config_entry/string/discordurl - config_entry_value = "https://discord.gg/husVWe8" - -/datum/config_entry/string/centcom_ban_db // URL for the CentCom Galactic Ban DB API - -/datum/config_entry/string/roundstatsurl - -/datum/config_entry/string/gamelogurl - -/datum/config_entry/number/githubrepoid - config_entry_value = null - min_val = 0 - -/datum/config_entry/flag/guest_ban - -/datum/config_entry/number/id_console_jobslot_delay - config_entry_value = 30 - integer = FALSE - min_val = 0 - -/datum/config_entry/number/inactivity_period //time in ds until a player is considered inactive - config_entry_value = 3000 - integer = FALSE - min_val = 0 - -/datum/config_entry/number/inactivity_period/ValidateAndSet(str_val) - . = ..() - if(.) - config_entry_value *= 10 //documented as seconds in config.txt - -/datum/config_entry/number/afk_period //time in ds until a player is considered inactive - config_entry_value = 3000 - integer = FALSE - min_val = 0 - -/datum/config_entry/number/afk_period/ValidateAndSet(str_val) - . = ..() - if(.) - config_entry_value *= 10 //documented as seconds in config.txt - -/datum/config_entry/flag/kick_inactive //force disconnect for inactive players - -/datum/config_entry/flag/load_jobs_from_txt - -/datum/config_entry/flag/forbid_singulo_possession - -/datum/config_entry/flag/automute_on //enables automuting/spam prevention - -/datum/config_entry/string/panic_server_name - -/datum/config_entry/string/panic_server_name/ValidateAndSet(str_val) - return str_val != "\[Put the name here\]" && ..() - -/datum/config_entry/string/panic_server_address //Reconnect a player this linked server if this server isn't accepting new players - -/datum/config_entry/string/panic_server_address/ValidateAndSet(str_val) - return str_val != "byond://address:port" && ..() - -/datum/config_entry/string/invoke_youtubedl - protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN - -/datum/config_entry/flag/show_irc_name - -/datum/config_entry/flag/see_own_notes //Can players see their own admin notes - -/datum/config_entry/number/note_fresh_days - config_entry_value = null - min_val = 0 - integer = FALSE - -/datum/config_entry/number/note_stale_days - config_entry_value = null - min_val = 0 - integer = FALSE - -/datum/config_entry/flag/maprotation - -/datum/config_entry/number/soft_popcap - config_entry_value = null - min_val = 0 - -/datum/config_entry/number/hard_popcap - config_entry_value = null - min_val = 0 - -/datum/config_entry/number/extreme_popcap - config_entry_value = null - min_val = 0 - -/datum/config_entry/string/soft_popcap_message - config_entry_value = "Be warned that the server is currently serving a high number of users, consider using alternative game servers." - -/datum/config_entry/string/hard_popcap_message - config_entry_value = "The server is currently serving a high number of users, You cannot currently join. You may wait for the number of living crew to decline, observe, or find alternative servers." - -/datum/config_entry/string/extreme_popcap_message - config_entry_value = "The server is currently serving a high number of users, find alternative servers." - -/datum/config_entry/flag/byond_member_bypass_popcap - -/datum/config_entry/flag/panic_bunker // prevents people the server hasn't seen before from connecting - -/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 - -/datum/config_entry/number/notify_new_player_account_age // how long do we notify admins of a new byond account - min_val = 0 - -/datum/config_entry/flag/irc_first_connection_alert // do we notify the irc channel when somebody is connecting for the first time? - -/datum/config_entry/flag/check_randomizer - -/datum/config_entry/string/ipintel_email - -/datum/config_entry/string/ipintel_email/ValidateAndSet(str_val) - return str_val != "ch@nge.me" && ..() - -/datum/config_entry/number/ipintel_rating_bad - config_entry_value = 1 - integer = FALSE - min_val = 0 - max_val = 1 - -/datum/config_entry/number/ipintel_save_good - config_entry_value = 12 - integer = FALSE - min_val = 0 - -/datum/config_entry/number/ipintel_save_bad - config_entry_value = 1 - integer = FALSE - min_val = 0 - -/datum/config_entry/string/ipintel_domain - config_entry_value = "check.getipintel.net" - -/datum/config_entry/flag/aggressive_changelog - -/datum/config_entry/flag/autoconvert_notes //if all connecting player's notes should attempt to be converted to the database - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/flag/allow_webclient - -/datum/config_entry/flag/webclient_only_byond_members - -/datum/config_entry/flag/announce_admin_logout - -/datum/config_entry/flag/announce_admin_login - -/datum/config_entry/flag/allow_map_voting - deprecated_by = /datum/config_entry/flag/preference_map_voting - -/datum/config_entry/flag/allow_map_voting/DeprecationUpdate(value) - return value - -/datum/config_entry/flag/preference_map_voting - -/datum/config_entry/number/client_warn_version - config_entry_value = null - min_val = 500 - -/datum/config_entry/string/client_warn_message - config_entry_value = "Your version of byond may have issues or be blocked from accessing this server in the future." - -/datum/config_entry/flag/client_warn_popup - -/datum/config_entry/number/client_error_version - config_entry_value = null - min_val = 500 - -/datum/config_entry/string/client_error_message - config_entry_value = "Your version of byond is too old, may have issues, and is blocked from accessing this server." - -/datum/config_entry/number/client_error_build - config_entry_value = null - min_val = 0 - -/datum/config_entry/number/minute_topic_limit - config_entry_value = null - min_val = 0 - -/datum/config_entry/number/second_topic_limit - config_entry_value = null - min_val = 0 - -/datum/config_entry/number/minute_click_limit - config_entry_value = 400 - min_val = 0 - -/datum/config_entry/number/second_click_limit - config_entry_value = 15 - min_val = 0 - -/datum/config_entry/number/error_cooldown // The "cooldown" time for each occurrence of a unique error - config_entry_value = 600 - integer = FALSE - min_val = 0 - -/datum/config_entry/number/error_limit // How many occurrences before the next will silence them - config_entry_value = 50 - -/datum/config_entry/number/error_silence_time // How long a unique error will be silenced for - config_entry_value = 6000 - integer = FALSE - -/datum/config_entry/number/error_msg_delay // How long to wait between messaging admins about occurrences of a unique error - config_entry_value = 50 - integer = FALSE - -/datum/config_entry/flag/irc_announce_new_game - deprecated_by = /datum/config_entry/string/chat_announce_new_game - -/datum/config_entry/flag/irc_announce_new_game/DeprecationUpdate(value) - return "" //default broadcast - -/datum/config_entry/string/chat_announce_new_game - config_entry_value = null - -/datum/config_entry/flag/debug_admin_hrefs - -/datum/config_entry/number/mc_tick_rate/base_mc_tick_rate - integer = FALSE - config_entry_value = 1 - -/datum/config_entry/number/mc_tick_rate/high_pop_mc_tick_rate - integer = FALSE - config_entry_value = 1.1 - -/datum/config_entry/number/mc_tick_rate/high_pop_mc_mode_amount - config_entry_value = 65 - -/datum/config_entry/number/mc_tick_rate/disable_high_pop_mc_mode_amount - config_entry_value = 60 - -/datum/config_entry/number/mc_tick_rate - abstract_type = /datum/config_entry/number/mc_tick_rate - -/datum/config_entry/number/mc_tick_rate/ValidateAndSet(str_val) - . = ..() - if (.) - Master.UpdateTickRate() - -/datum/config_entry/flag/resume_after_initializations - -/datum/config_entry/flag/resume_after_initializations/ValidateAndSet(str_val) - . = ..() - if(. && Master.current_runlevel) - world.sleep_offline = !config_entry_value - -/datum/config_entry/number/rounds_until_hard_restart - config_entry_value = -1 - min_val = 0 - -/datum/config_entry/string/default_view - config_entry_value = "15x15" - -/datum/config_entry/string/default_view_square - config_entry_value = "15x15" - -/datum/config_entry/flag/log_pictures - -/datum/config_entry/flag/picture_logging_camera - - -/datum/config_entry/flag/reopen_roundstart_suicide_roles - -/datum/config_entry/flag/reopen_roundstart_suicide_roles_command_positions - -/datum/config_entry/number/reopen_roundstart_suicide_roles_delay - min_val = 30 - -/datum/config_entry/flag/reopen_roundstart_suicide_roles_command_report - -/datum/config_entry/flag/auto_profile - -// DISCORD ROLE STUFFS -// Using strings for everything because BYOND does not like numbers this big -// (exception to the above is required living hours haha) -/datum/config_entry/flag/enable_discord_autorole - -/datum/config_entry/number/required_living_hours - -/datum/config_entry/string/discord_token - -/datum/config_entry/string/discord_guildid - -/datum/config_entry/string/discord_roleid - -//Begin Wasp Edit -/datum/config_entry/flag/minimaps_enabled - config_entry_value = TRUE -//End Wasp Edit +/datum/config_entry/flag/autoadmin // if autoadmin is enabled + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/string/autoadmin_rank // the rank for autoadmins + config_entry_value = "Game Master" + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/flag/auto_deadmin_players + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/number/auto_deadmin_timegate + config_entry_value = null + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/flag/auto_deadmin_antagonists + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/flag/auto_deadmin_heads + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/flag/auto_deadmin_silicons + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/flag/auto_deadmin_security + protection = CONFIG_ENTRY_LOCKED + + +/datum/config_entry/string/servername // server name (the name of the game window) + +/datum/config_entry/string/serversqlname // short form server name used for the DB + +/datum/config_entry/string/stationname // station name (the name of the station in-game) + +/datum/config_entry/number/lobby_countdown // In between round countdown. + config_entry_value = 120 + integer = FALSE + min_val = 0 + +/datum/config_entry/number/round_end_countdown // Post round murder death kill countdown + config_entry_value = 25 + integer = FALSE + min_val = 0 + +/datum/config_entry/flag/hub // if the game appears on the hub or not + +/datum/config_entry/number/max_hub_pop //At what pop to take hub off the server + config_entry_value = 0 //0 means disabled + integer = TRUE + min_val = 0 + +/datum/config_entry/flag/log_ooc // log OOC channel + +/datum/config_entry/flag/log_access // log login/logout + +/datum/config_entry/flag/log_say // log client say + +/datum/config_entry/flag/log_admin // log admin actions + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/flag/log_prayer // log prayers + +/datum/config_entry/flag/log_law // log lawchanges + +/datum/config_entry/flag/log_game // log game events + +/datum/config_entry/flag/log_mecha // log mech data + +/datum/config_entry/flag/log_virus // log virology data + +/datum/config_entry/flag/log_cloning // log cloning actions. + +/datum/config_entry/flag/log_vote // log voting + +/datum/config_entry/flag/log_whisper // log client whisper + +/datum/config_entry/flag/log_attack // log attack messages + +/datum/config_entry/flag/log_emote // log emotes + +/datum/config_entry/flag/log_econ // log economy actions + +/datum/config_entry/flag/log_adminchat // log admin chat messages + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/flag/log_pda // log pda messages + +/datum/config_entry/flag/log_telecomms // log telecomms messages + +/datum/config_entry/flag/log_twitter // log certain expliotable parrots and other such fun things in a JSON file of twitter valid phrases. + +/datum/config_entry/flag/log_world_topic // log all world.Topic() calls + +/datum/config_entry/flag/log_manifest // log crew manifest to seperate file + +/datum/config_entry/flag/log_job_debug // log roundstart divide occupations debug information to a file + +/datum/config_entry/flag/log_shuttle // log shuttle related actions, ie shuttle computers, shuttle manipulator, emergency console + +/datum/config_entry/flag/allow_admin_ooccolor // Allows admins with relevant permissions to have their own ooc colour + +/datum/config_entry/flag/allow_admin_asaycolor //Allows admins with relevant permissions to have a personalized asay color + +/datum/config_entry/flag/allow_vote_restart // allow votes to restart + +/datum/config_entry/flag/allow_vote_mode // allow votes to change mode + +/datum/config_entry/flag/allow_vote_map // allow votes to change map + +/datum/config_entry/number/vote_delay // minimum time between voting sessions (deciseconds, 10 minute default) + config_entry_value = 6000 + integer = FALSE + min_val = 0 + +/datum/config_entry/number/vote_period // length of voting period (deciseconds, default 1 minute) + config_entry_value = 600 + integer = FALSE + min_val = 0 + +//Waspstation Begin - Autotranfer vote + +/datum/config_entry/number/vote_autotransfer_initial //length of time before the first autotransfer vote is called (deciseconds, default 2 hours) + config_entry_value = 72000 + integer = FALSE + min_val = 0 + +/datum/config_entry/number/vote_autotransfer_interval //length of time to wait before subsequent autotransfer votes (deciseconds, default 30 minutes) + config_entry_value = 18000 + integer = FALSE + min_val = 0 + +//Waspstation End + +/datum/config_entry/flag/default_no_vote // vote does not default to nochange/norestart + +/datum/config_entry/flag/no_dead_vote // dead people can't vote + +/datum/config_entry/flag/allow_metadata // Metadata is supported. + +/datum/config_entry/flag/popup_admin_pm // adminPMs to non-admins show in a pop-up 'reply' window when set + +/datum/config_entry/number/fps + config_entry_value = 20 + integer = FALSE + min_val = 1 + max_val = 100 //byond will start crapping out at 50, so this is just ridic + var/sync_validate = FALSE + +/datum/config_entry/number/fps/ValidateAndSet(str_val) + . = ..() + if(.) + sync_validate = TRUE + var/datum/config_entry/number/ticklag/TL = config.entries_by_type[/datum/config_entry/number/ticklag] + if(!TL.sync_validate) + TL.ValidateAndSet(10 / config_entry_value) + sync_validate = FALSE + +/datum/config_entry/number/ticklag + integer = FALSE + var/sync_validate = FALSE + +/datum/config_entry/number/ticklag/New() //ticklag weirdly just mirrors fps + var/datum/config_entry/CE = /datum/config_entry/number/fps + config_entry_value = 10 / initial(CE.config_entry_value) + ..() + +/datum/config_entry/number/ticklag/ValidateAndSet(str_val) + . = text2num(str_val) > 0 && ..() + if(.) + sync_validate = TRUE + var/datum/config_entry/number/fps/FPS = config.entries_by_type[/datum/config_entry/number/fps] + if(!FPS.sync_validate) + FPS.ValidateAndSet(10 / config_entry_value) + sync_validate = FALSE + +/datum/config_entry/flag/allow_holidays + +/datum/config_entry/number/tick_limit_mc_init //SSinitialization throttling + config_entry_value = TICK_LIMIT_MC_INIT_DEFAULT + min_val = 0 //oranges warned us + integer = FALSE + +/datum/config_entry/flag/admin_legacy_system //Defines whether the server uses the legacy admin system with admins.txt or the SQL system + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/flag/protect_legacy_admins //Stops any admins loaded by the legacy system from having their rank edited by the permissions panel + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/flag/protect_legacy_ranks //Stops any ranks loaded by the legacy system from having their flags edited by the permissions panel + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/flag/enable_localhost_rank //Gives the !localhost! rank to any client connecting from 127.0.0.1 or ::1 + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/flag/load_legacy_ranks_only //Loads admin ranks only from legacy admin_ranks.txt, while enabled ranks are mirrored to the database + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/flag/mentors_mobname_only // Only display mob name to mentors in mentorhelps + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/flag/mentor_legacy_system // Whether to use the legacy mentor system (flat file) instead of SQL + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/string/hostedby + +/datum/config_entry/flag/norespawn + +/datum/config_entry/flag/guest_jobban + +/datum/config_entry/flag/usewhitelist + +/datum/config_entry/flag/use_age_restriction_for_jobs //Do jobs use account age restrictions? --requires database + +/datum/config_entry/flag/use_account_age_for_jobs //Uses the time they made the account for the job restriction stuff. New player joining alerts should be unaffected. + +/datum/config_entry/flag/use_exp_tracking + +/datum/config_entry/flag/use_exp_restrictions_heads + +/datum/config_entry/number/use_exp_restrictions_heads_hours + config_entry_value = 0 + integer = FALSE + min_val = 0 + +/datum/config_entry/flag/use_exp_restrictions_heads_department + +/datum/config_entry/flag/use_exp_restrictions_other + +/datum/config_entry/flag/use_exp_restrictions_admin_bypass + +/datum/config_entry/string/server + +/datum/config_entry/string/banappeals + +/datum/config_entry/string/wikiurl + config_entry_value = "http://www.tgstation13.org/wiki" + +/datum/config_entry/string/forumurl + config_entry_value = "http://tgstation13.org/phpBB/index.php" + +/datum/config_entry/string/rulesurl + config_entry_value = "http://www.tgstation13.org/wiki/Rules" + +/datum/config_entry/string/githuburl + config_entry_value = "https://www.github.com/tgstation/-tg-station" + +/datum/config_entry/string/discordurl + config_entry_value = "https://discord.gg/husVWe8" + +/datum/config_entry/string/centcom_ban_db // URL for the CentCom Galactic Ban DB API + +/datum/config_entry/string/roundstatsurl + +/datum/config_entry/string/gamelogurl + +/datum/config_entry/number/githubrepoid + config_entry_value = null + min_val = 0 + +/datum/config_entry/flag/guest_ban + +/datum/config_entry/number/id_console_jobslot_delay + config_entry_value = 30 + integer = FALSE + min_val = 0 + +/datum/config_entry/number/inactivity_period //time in ds until a player is considered inactive + config_entry_value = 3000 + integer = FALSE + min_val = 0 + +/datum/config_entry/number/inactivity_period/ValidateAndSet(str_val) + . = ..() + if(.) + config_entry_value *= 10 //documented as seconds in config.txt + +/datum/config_entry/number/afk_period //time in ds until a player is considered inactive + config_entry_value = 3000 + integer = FALSE + min_val = 0 + +/datum/config_entry/number/afk_period/ValidateAndSet(str_val) + . = ..() + if(.) + config_entry_value *= 10 //documented as seconds in config.txt + +/datum/config_entry/flag/kick_inactive //force disconnect for inactive players + +/datum/config_entry/flag/load_jobs_from_txt + +/datum/config_entry/flag/forbid_singulo_possession + +/datum/config_entry/flag/automute_on //enables automuting/spam prevention + +/datum/config_entry/string/panic_server_name + +/datum/config_entry/string/panic_server_name/ValidateAndSet(str_val) + return str_val != "\[Put the name here\]" && ..() + +/datum/config_entry/string/panic_server_address //Reconnect a player this linked server if this server isn't accepting new players + +/datum/config_entry/string/panic_server_address/ValidateAndSet(str_val) + return str_val != "byond://address:port" && ..() + +/datum/config_entry/string/invoke_youtubedl + protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN + +/datum/config_entry/flag/show_irc_name + +/datum/config_entry/flag/see_own_notes //Can players see their own admin notes + +/datum/config_entry/number/note_fresh_days + config_entry_value = null + min_val = 0 + integer = FALSE + +/datum/config_entry/number/note_stale_days + config_entry_value = null + min_val = 0 + integer = FALSE + +/datum/config_entry/flag/maprotation + +/datum/config_entry/number/soft_popcap + config_entry_value = null + min_val = 0 + +/datum/config_entry/number/hard_popcap + config_entry_value = null + min_val = 0 + +/datum/config_entry/number/extreme_popcap + config_entry_value = null + min_val = 0 + +/datum/config_entry/string/soft_popcap_message + config_entry_value = "Be warned that the server is currently serving a high number of users, consider using alternative game servers." + +/datum/config_entry/string/hard_popcap_message + config_entry_value = "The server is currently serving a high number of users, You cannot currently join. You may wait for the number of living crew to decline, observe, or find alternative servers." + +/datum/config_entry/string/extreme_popcap_message + config_entry_value = "The server is currently serving a high number of users, find alternative servers." + +/datum/config_entry/flag/byond_member_bypass_popcap + +/datum/config_entry/flag/panic_bunker // prevents people the server hasn't seen before from connecting + +/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 + +/datum/config_entry/number/notify_new_player_account_age // how long do we notify admins of a new byond account + min_val = 0 + +/datum/config_entry/flag/irc_first_connection_alert // do we notify the irc channel when somebody is connecting for the first time? + +/datum/config_entry/flag/check_randomizer + +/datum/config_entry/string/ipintel_email + +/datum/config_entry/string/ipintel_email/ValidateAndSet(str_val) + return str_val != "ch@nge.me" && ..() + +/datum/config_entry/number/ipintel_rating_bad + config_entry_value = 1 + integer = FALSE + min_val = 0 + max_val = 1 + +/datum/config_entry/number/ipintel_save_good + config_entry_value = 12 + integer = FALSE + min_val = 0 + +/datum/config_entry/number/ipintel_save_bad + config_entry_value = 1 + integer = FALSE + min_val = 0 + +/datum/config_entry/string/ipintel_domain + config_entry_value = "check.getipintel.net" + +/datum/config_entry/flag/aggressive_changelog + +/datum/config_entry/flag/autoconvert_notes //if all connecting player's notes should attempt to be converted to the database + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/flag/allow_webclient + +/datum/config_entry/flag/webclient_only_byond_members + +/datum/config_entry/flag/announce_admin_logout + +/datum/config_entry/flag/announce_admin_login + +/datum/config_entry/flag/allow_map_voting + deprecated_by = /datum/config_entry/flag/preference_map_voting + +/datum/config_entry/flag/allow_map_voting/DeprecationUpdate(value) + return value + +/datum/config_entry/flag/preference_map_voting + +/datum/config_entry/number/client_warn_version + config_entry_value = null + min_val = 500 + +/datum/config_entry/string/client_warn_message + config_entry_value = "Your version of byond may have issues or be blocked from accessing this server in the future." + +/datum/config_entry/flag/client_warn_popup + +/datum/config_entry/number/client_error_version + config_entry_value = null + min_val = 500 + +/datum/config_entry/string/client_error_message + config_entry_value = "Your version of byond is too old, may have issues, and is blocked from accessing this server." + +/datum/config_entry/number/client_error_build + config_entry_value = null + min_val = 0 + +/datum/config_entry/number/minute_topic_limit + config_entry_value = null + min_val = 0 + +/datum/config_entry/number/second_topic_limit + config_entry_value = null + min_val = 0 + +/datum/config_entry/number/minute_click_limit + config_entry_value = 400 + min_val = 0 + +/datum/config_entry/number/second_click_limit + config_entry_value = 15 + min_val = 0 + +/datum/config_entry/number/error_cooldown // The "cooldown" time for each occurrence of a unique error + config_entry_value = 600 + integer = FALSE + min_val = 0 + +/datum/config_entry/number/error_limit // How many occurrences before the next will silence them + config_entry_value = 50 + +/datum/config_entry/number/error_silence_time // How long a unique error will be silenced for + config_entry_value = 6000 + integer = FALSE + +/datum/config_entry/number/error_msg_delay // How long to wait between messaging admins about occurrences of a unique error + config_entry_value = 50 + integer = FALSE + +/datum/config_entry/flag/irc_announce_new_game + deprecated_by = /datum/config_entry/string/chat_announce_new_game + +/datum/config_entry/flag/irc_announce_new_game/DeprecationUpdate(value) + return "" //default broadcast + +/datum/config_entry/string/chat_announce_new_game + config_entry_value = null + +/datum/config_entry/flag/debug_admin_hrefs + +/datum/config_entry/number/mc_tick_rate/base_mc_tick_rate + integer = FALSE + config_entry_value = 1 + +/datum/config_entry/number/mc_tick_rate/high_pop_mc_tick_rate + integer = FALSE + config_entry_value = 1.1 + +/datum/config_entry/number/mc_tick_rate/high_pop_mc_mode_amount + config_entry_value = 65 + +/datum/config_entry/number/mc_tick_rate/disable_high_pop_mc_mode_amount + config_entry_value = 60 + +/datum/config_entry/number/mc_tick_rate + abstract_type = /datum/config_entry/number/mc_tick_rate + +/datum/config_entry/number/mc_tick_rate/ValidateAndSet(str_val) + . = ..() + if (.) + Master.UpdateTickRate() + +/datum/config_entry/flag/resume_after_initializations + +/datum/config_entry/flag/resume_after_initializations/ValidateAndSet(str_val) + . = ..() + if(. && Master.current_runlevel) + world.sleep_offline = !config_entry_value + +/datum/config_entry/number/rounds_until_hard_restart + config_entry_value = -1 + min_val = 0 + +/datum/config_entry/string/default_view + config_entry_value = "15x15" + +/datum/config_entry/string/default_view_square + config_entry_value = "15x15" + +/datum/config_entry/flag/log_pictures + +/datum/config_entry/flag/picture_logging_camera + + +/datum/config_entry/flag/reopen_roundstart_suicide_roles + +/datum/config_entry/flag/reopen_roundstart_suicide_roles_command_positions + +/datum/config_entry/number/reopen_roundstart_suicide_roles_delay + min_val = 30 + +/datum/config_entry/flag/reopen_roundstart_suicide_roles_command_report + +/datum/config_entry/flag/auto_profile + +// DISCORD ROLE STUFFS +// Using strings for everything because BYOND does not like numbers this big +// (exception to the above is required living hours haha) +/datum/config_entry/flag/enable_discord_autorole + +/datum/config_entry/number/required_living_hours + +/datum/config_entry/string/discord_token + +/datum/config_entry/string/discord_guildid + +/datum/config_entry/string/discord_roleid + +//Begin Wasp Edit +/datum/config_entry/flag/minimaps_enabled + config_entry_value = TRUE +//End Wasp Edit diff --git a/code/controllers/failsafe.dm b/code/controllers/failsafe.dm index 2ca208642c8e..3ce770b66d56 100644 --- a/code/controllers/failsafe.dm +++ b/code/controllers/failsafe.dm @@ -1,102 +1,102 @@ - /** - * Failsafe - * - * Pretty much pokes the MC to make sure it's still alive. - **/ - -GLOBAL_REAL(Failsafe, /datum/controller/failsafe) - -/datum/controller/failsafe // This thing pretty much just keeps poking the master controller - name = "Failsafe" - - // The length of time to check on the MC (in deciseconds). - // Set to 0 to disable. - var/processing_interval = 20 - // The alert level. For every failed poke, we drop a DEFCON level. Once we hit DEFCON 1, restart the MC. - var/defcon = 5 - //the world.time of the last check, so the mc can restart US if we hang. - // (Real friends look out for *eachother*) - var/lasttick = 0 - - // Track the MC iteration to make sure its still on track. - var/master_iteration = 0 - var/running = TRUE - -/datum/controller/failsafe/New() - // Highlander-style: there can only be one! Kill off the old and replace it with the new. - if(Failsafe != src) - if(istype(Failsafe)) - qdel(Failsafe) - Failsafe = src - Initialize() - -/datum/controller/failsafe/Initialize() - set waitfor = 0 - Failsafe.Loop() - if(!QDELETED(src)) - qdel(src) //when Loop() returns, we delete ourselves and let the mc recreate us - -/datum/controller/failsafe/Destroy() - running = FALSE - ..() - return QDEL_HINT_HARDDEL_NOW - -/datum/controller/failsafe/proc/Loop() - while(running) - lasttick = world.time - if(!Master) - // Replace the missing Master! This should never, ever happen. - new /datum/controller/master() - // Only poke it if overrides are not in effect. - if(processing_interval > 0) - if(Master.processing && Master.iteration) - // Check if processing is done yet. - if(Master.iteration == master_iteration) - switch(defcon) - if(4,5) - --defcon - if(3) - message_admins("Notice: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks.") - --defcon - if(2) - to_chat(GLOB.admins, "Warning: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks. Automatic restart in [processing_interval] ticks.") - --defcon - if(1) - - to_chat(GLOB.admins, "Warning: DEFCON [defcon_pretty()]. The Master Controller has still not fired within the last [(5-defcon) * processing_interval] ticks. Killing and restarting...") - --defcon - var/rtn = Recreate_MC() - if(rtn > 0) - defcon = 4 - master_iteration = 0 - to_chat(GLOB.admins, "MC restarted successfully") - else if(rtn < 0) - log_game("FailSafe: Could not restart MC, runtime encountered. Entering defcon 0") - to_chat(GLOB.admins, "ERROR: DEFCON [defcon_pretty()]. Could not restart MC, runtime encountered. I will silently keep retrying.") - //if the return number was 0, it just means the mc was restarted too recently, and it just needs some time before we try again - //no need to handle that specially when defcon 0 can handle it - if(0) //DEFCON 0! (mc failed to restart) - var/rtn = Recreate_MC() - if(rtn > 0) - defcon = 4 - master_iteration = 0 - to_chat(GLOB.admins, "MC restarted successfully") - else - defcon = min(defcon + 1,5) - master_iteration = Master.iteration - if (defcon <= 1) - sleep(processing_interval*2) - else - sleep(processing_interval) - else - defcon = 5 - sleep(initial(processing_interval)) - -/datum/controller/failsafe/proc/defcon_pretty() - return defcon - -/datum/controller/failsafe/stat_entry() - if(!statclick) - statclick = new/obj/effect/statclick/debug(null, "Initializing...", src) - - stat("Failsafe Controller:", statclick.update("Defcon: [defcon_pretty()] (Interval: [Failsafe.processing_interval] | Iteration: [Failsafe.master_iteration])")) + /** + * Failsafe + * + * Pretty much pokes the MC to make sure it's still alive. + **/ + +GLOBAL_REAL(Failsafe, /datum/controller/failsafe) + +/datum/controller/failsafe // This thing pretty much just keeps poking the master controller + name = "Failsafe" + + // The length of time to check on the MC (in deciseconds). + // Set to 0 to disable. + var/processing_interval = 20 + // The alert level. For every failed poke, we drop a DEFCON level. Once we hit DEFCON 1, restart the MC. + var/defcon = 5 + //the world.time of the last check, so the mc can restart US if we hang. + // (Real friends look out for *eachother*) + var/lasttick = 0 + + // Track the MC iteration to make sure its still on track. + var/master_iteration = 0 + var/running = TRUE + +/datum/controller/failsafe/New() + // Highlander-style: there can only be one! Kill off the old and replace it with the new. + if(Failsafe != src) + if(istype(Failsafe)) + qdel(Failsafe) + Failsafe = src + Initialize() + +/datum/controller/failsafe/Initialize() + set waitfor = 0 + Failsafe.Loop() + if(!QDELETED(src)) + qdel(src) //when Loop() returns, we delete ourselves and let the mc recreate us + +/datum/controller/failsafe/Destroy() + running = FALSE + ..() + return QDEL_HINT_HARDDEL_NOW + +/datum/controller/failsafe/proc/Loop() + while(running) + lasttick = world.time + if(!Master) + // Replace the missing Master! This should never, ever happen. + new /datum/controller/master() + // Only poke it if overrides are not in effect. + if(processing_interval > 0) + if(Master.processing && Master.iteration) + // Check if processing is done yet. + if(Master.iteration == master_iteration) + switch(defcon) + if(4,5) + --defcon + if(3) + message_admins("Notice: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks.") + --defcon + if(2) + to_chat(GLOB.admins, "Warning: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks. Automatic restart in [processing_interval] ticks.") + --defcon + if(1) + + to_chat(GLOB.admins, "Warning: DEFCON [defcon_pretty()]. The Master Controller has still not fired within the last [(5-defcon) * processing_interval] ticks. Killing and restarting...") + --defcon + var/rtn = Recreate_MC() + if(rtn > 0) + defcon = 4 + master_iteration = 0 + to_chat(GLOB.admins, "MC restarted successfully") + else if(rtn < 0) + log_game("FailSafe: Could not restart MC, runtime encountered. Entering defcon 0") + to_chat(GLOB.admins, "ERROR: DEFCON [defcon_pretty()]. Could not restart MC, runtime encountered. I will silently keep retrying.") + //if the return number was 0, it just means the mc was restarted too recently, and it just needs some time before we try again + //no need to handle that specially when defcon 0 can handle it + if(0) //DEFCON 0! (mc failed to restart) + var/rtn = Recreate_MC() + if(rtn > 0) + defcon = 4 + master_iteration = 0 + to_chat(GLOB.admins, "MC restarted successfully") + else + defcon = min(defcon + 1,5) + master_iteration = Master.iteration + if (defcon <= 1) + sleep(processing_interval*2) + else + sleep(processing_interval) + else + defcon = 5 + sleep(initial(processing_interval)) + +/datum/controller/failsafe/proc/defcon_pretty() + return defcon + +/datum/controller/failsafe/stat_entry() + if(!statclick) + statclick = new/obj/effect/statclick/debug(null, "Initializing...", src) + + stat("Failsafe Controller:", statclick.update("Defcon: [defcon_pretty()] (Interval: [Failsafe.processing_interval] | Iteration: [Failsafe.master_iteration])")) diff --git a/code/controllers/globals.dm b/code/controllers/globals.dm index 3a901c4970a5..707adfc83fe6 100644 --- a/code/controllers/globals.dm +++ b/code/controllers/globals.dm @@ -1,56 +1,56 @@ -GLOBAL_REAL(GLOB, /datum/controller/global_vars) - -/datum/controller/global_vars - name = "Global Variables" - - var/static/list/gvars_datum_protected_varlist - var/list/gvars_datum_in_built_vars - var/list/gvars_datum_init_order - -/datum/controller/global_vars/New() - if(GLOB) - CRASH("Multiple instances of global variable controller created") - GLOB = src - - var/datum/controller/exclude_these = new - gvars_datum_in_built_vars = exclude_these.vars + list(NAMEOF(src, gvars_datum_protected_varlist), NAMEOF(src, gvars_datum_in_built_vars), NAMEOF(src, gvars_datum_init_order)) - QDEL_IN(exclude_these, 0) //signal logging isn't ready - - log_world("[vars.len - gvars_datum_in_built_vars.len] global variables") - - Initialize() - -/datum/controller/global_vars/Destroy(force) - // This is done to prevent an exploit where admins can get around protected vars - SHOULD_CALL_PARENT(0) - return QDEL_HINT_IWILLGC - -/datum/controller/global_vars/stat_entry() - if(!statclick) - statclick = new/obj/effect/statclick/debug(null, "Initializing...", src) - - stat("Globals:", statclick.update("Edit")) - -/datum/controller/global_vars/vv_edit_var(var_name, var_value) - if(gvars_datum_protected_varlist[var_name]) - return FALSE - return ..() - -/datum/controller/global_vars/Initialize() - gvars_datum_init_order = list() - gvars_datum_protected_varlist = list(NAMEOF(src, gvars_datum_protected_varlist) = TRUE) - var/list/global_procs = typesof(/datum/controller/global_vars/proc) - var/expected_len = vars.len - gvars_datum_in_built_vars.len - if(global_procs.len != expected_len) - warning("Unable to detect all global initialization procs! Expected [expected_len] got [global_procs.len]!") - if(global_procs.len) - var/list/expected_global_procs = vars - gvars_datum_in_built_vars - for(var/I in global_procs) - expected_global_procs -= replacetext("[I]", "InitGlobal", "") - log_world("Missing procs: [expected_global_procs.Join(", ")]") - for(var/I in global_procs) - var/start_tick = world.time - call(src, I)() - var/end_tick = world.time - if(end_tick - start_tick) - warning("Global [replacetext("[I]", "InitGlobal", "")] slept during initialization!") +GLOBAL_REAL(GLOB, /datum/controller/global_vars) + +/datum/controller/global_vars + name = "Global Variables" + + var/static/list/gvars_datum_protected_varlist + var/list/gvars_datum_in_built_vars + var/list/gvars_datum_init_order + +/datum/controller/global_vars/New() + if(GLOB) + CRASH("Multiple instances of global variable controller created") + GLOB = src + + var/datum/controller/exclude_these = new + gvars_datum_in_built_vars = exclude_these.vars + list(NAMEOF(src, gvars_datum_protected_varlist), NAMEOF(src, gvars_datum_in_built_vars), NAMEOF(src, gvars_datum_init_order)) + QDEL_IN(exclude_these, 0) //signal logging isn't ready + + log_world("[vars.len - gvars_datum_in_built_vars.len] global variables") + + Initialize() + +/datum/controller/global_vars/Destroy(force) + // This is done to prevent an exploit where admins can get around protected vars + SHOULD_CALL_PARENT(0) + return QDEL_HINT_IWILLGC + +/datum/controller/global_vars/stat_entry() + if(!statclick) + statclick = new/obj/effect/statclick/debug(null, "Initializing...", src) + + stat("Globals:", statclick.update("Edit")) + +/datum/controller/global_vars/vv_edit_var(var_name, var_value) + if(gvars_datum_protected_varlist[var_name]) + return FALSE + return ..() + +/datum/controller/global_vars/Initialize() + gvars_datum_init_order = list() + gvars_datum_protected_varlist = list(NAMEOF(src, gvars_datum_protected_varlist) = TRUE) + var/list/global_procs = typesof(/datum/controller/global_vars/proc) + var/expected_len = vars.len - gvars_datum_in_built_vars.len + if(global_procs.len != expected_len) + warning("Unable to detect all global initialization procs! Expected [expected_len] got [global_procs.len]!") + if(global_procs.len) + var/list/expected_global_procs = vars - gvars_datum_in_built_vars + for(var/I in global_procs) + expected_global_procs -= replacetext("[I]", "InitGlobal", "") + log_world("Missing procs: [expected_global_procs.Join(", ")]") + for(var/I in global_procs) + var/start_tick = world.time + call(src, I)() + var/end_tick = world.time + if(end_tick - start_tick) + warning("Global [replacetext("[I]", "InitGlobal", "")] slept during initialization!") diff --git a/code/controllers/subsystem/atoms.dm b/code/controllers/subsystem/atoms.dm index 15f2f59cad06..073b3781aa78 100644 --- a/code/controllers/subsystem/atoms.dm +++ b/code/controllers/subsystem/atoms.dm @@ -1,154 +1,154 @@ -#define BAD_INIT_QDEL_BEFORE 1 -#define BAD_INIT_DIDNT_INIT 2 -#define BAD_INIT_SLEPT 4 -#define BAD_INIT_NO_HINT 8 - -SUBSYSTEM_DEF(atoms) - name = "Atoms" - init_order = INIT_ORDER_ATOMS - flags = SS_NO_FIRE - - var/old_initialized - - var/list/late_loaders = list() - - var/list/BadInitializeCalls = list() - -/datum/controller/subsystem/atoms/Initialize(timeofday) - GLOB.fire_overlay.appearance_flags = RESET_COLOR - setupGenetics() //to set the mutations' sequence - initialized = INITIALIZATION_INNEW_MAPLOAD - InitializeAtoms() - return ..() - -/datum/controller/subsystem/atoms/proc/InitializeAtoms(list/atoms) - if(initialized == INITIALIZATION_INSSATOMS) - return - - initialized = INITIALIZATION_INNEW_MAPLOAD - - var/count - var/list/mapload_arg = list(TRUE) - if(atoms) - count = atoms.len - for(var/I in atoms) - var/atom/A = I - if(!(A.flags_1 & INITIALIZED_1)) - InitAtom(I, mapload_arg) - CHECK_TICK - else - count = 0 - for(var/atom/A in world) - if(!(A.flags_1 & INITIALIZED_1)) - InitAtom(A, mapload_arg) - ++count - CHECK_TICK - - testing("Initialized [count] atoms") - pass(count) - - initialized = INITIALIZATION_INNEW_REGULAR - - if(late_loaders.len) - for(var/I in late_loaders) - var/atom/A = I - A.LateInitialize() - testing("Late initialized [late_loaders.len] atoms") - late_loaders.Cut() - -/// Init this specific atom -/datum/controller/subsystem/atoms/proc/InitAtom(atom/A, list/arguments) - var/the_type = A.type - if(QDELING(A)) - BadInitializeCalls[the_type] |= BAD_INIT_QDEL_BEFORE - return TRUE - - var/start_tick = world.time - - var/result = A.Initialize(arglist(arguments)) - - if(start_tick != world.time) - BadInitializeCalls[the_type] |= BAD_INIT_SLEPT - - var/qdeleted = FALSE - - if(result != INITIALIZE_HINT_NORMAL) - switch(result) - if(INITIALIZE_HINT_LATELOAD) - if(arguments[1]) //mapload - late_loaders += A - else - A.LateInitialize() - if(INITIALIZE_HINT_QDEL) - qdel(A) - qdeleted = TRUE - else - BadInitializeCalls[the_type] |= BAD_INIT_NO_HINT - - if(!A) //possible harddel - qdeleted = TRUE - else if(!(A.flags_1 & INITIALIZED_1)) - BadInitializeCalls[the_type] |= BAD_INIT_DIDNT_INIT - - return qdeleted || QDELING(A) - -/datum/controller/subsystem/atoms/proc/map_loader_begin() - old_initialized = initialized - initialized = INITIALIZATION_INSSATOMS - -/datum/controller/subsystem/atoms/proc/map_loader_stop() - initialized = old_initialized - -/datum/controller/subsystem/atoms/Recover() - initialized = SSatoms.initialized - if(initialized == INITIALIZATION_INNEW_MAPLOAD) - InitializeAtoms() - old_initialized = SSatoms.old_initialized - BadInitializeCalls = SSatoms.BadInitializeCalls - -/datum/controller/subsystem/atoms/proc/setupGenetics() - var/list/mutations = subtypesof(/datum/mutation/human) - shuffle_inplace(mutations) - for(var/A in subtypesof(/datum/generecipe)) - var/datum/generecipe/GR = A - GLOB.mutation_recipes[initial(GR.required)] = initial(GR.result) - for(var/i in 1 to LAZYLEN(mutations)) - var/path = mutations[i] //byond gets pissy when we do it in one line - var/datum/mutation/human/B = new path () - B.alias = "Mutation [i]" - GLOB.all_mutations[B.type] = B - GLOB.full_sequences[B.type] = generate_gene_sequence(B.blocks) - GLOB.alias_mutations[B.alias] = B.type - if(B.locked) - continue - if(B.quality == POSITIVE) - GLOB.good_mutations |= B - else if(B.quality == NEGATIVE) - GLOB.bad_mutations |= B - else if(B.quality == MINOR_NEGATIVE) - GLOB.not_good_mutations |= B - CHECK_TICK - -/datum/controller/subsystem/atoms/proc/InitLog() - . = "" - for(var/path in BadInitializeCalls) - . += "Path : [path] \n" - var/fails = BadInitializeCalls[path] - if(fails & BAD_INIT_DIDNT_INIT) - . += "- Didn't call atom/Initialize()\n" - if(fails & BAD_INIT_NO_HINT) - . += "- Didn't return an Initialize hint\n" - if(fails & BAD_INIT_QDEL_BEFORE) - . += "- Qdel'd in New()\n" - if(fails & BAD_INIT_SLEPT) - . += "- Slept during Initialize()\n" - -/datum/controller/subsystem/atoms/Shutdown() - var/initlog = InitLog() - if(initlog) - text2file(initlog, "[GLOB.log_directory]/initialize.log") - -#undef BAD_INIT_QDEL_BEFORE -#undef BAD_INIT_DIDNT_INIT -#undef BAD_INIT_SLEPT -#undef BAD_INIT_NO_HINT +#define BAD_INIT_QDEL_BEFORE 1 +#define BAD_INIT_DIDNT_INIT 2 +#define BAD_INIT_SLEPT 4 +#define BAD_INIT_NO_HINT 8 + +SUBSYSTEM_DEF(atoms) + name = "Atoms" + init_order = INIT_ORDER_ATOMS + flags = SS_NO_FIRE + + var/old_initialized + + var/list/late_loaders = list() + + var/list/BadInitializeCalls = list() + +/datum/controller/subsystem/atoms/Initialize(timeofday) + GLOB.fire_overlay.appearance_flags = RESET_COLOR + setupGenetics() //to set the mutations' sequence + initialized = INITIALIZATION_INNEW_MAPLOAD + InitializeAtoms() + return ..() + +/datum/controller/subsystem/atoms/proc/InitializeAtoms(list/atoms) + if(initialized == INITIALIZATION_INSSATOMS) + return + + initialized = INITIALIZATION_INNEW_MAPLOAD + + var/count + var/list/mapload_arg = list(TRUE) + if(atoms) + count = atoms.len + for(var/I in atoms) + var/atom/A = I + if(!(A.flags_1 & INITIALIZED_1)) + InitAtom(I, mapload_arg) + CHECK_TICK + else + count = 0 + for(var/atom/A in world) + if(!(A.flags_1 & INITIALIZED_1)) + InitAtom(A, mapload_arg) + ++count + CHECK_TICK + + testing("Initialized [count] atoms") + pass(count) + + initialized = INITIALIZATION_INNEW_REGULAR + + if(late_loaders.len) + for(var/I in late_loaders) + var/atom/A = I + A.LateInitialize() + testing("Late initialized [late_loaders.len] atoms") + late_loaders.Cut() + +/// Init this specific atom +/datum/controller/subsystem/atoms/proc/InitAtom(atom/A, list/arguments) + var/the_type = A.type + if(QDELING(A)) + BadInitializeCalls[the_type] |= BAD_INIT_QDEL_BEFORE + return TRUE + + var/start_tick = world.time + + var/result = A.Initialize(arglist(arguments)) + + if(start_tick != world.time) + BadInitializeCalls[the_type] |= BAD_INIT_SLEPT + + var/qdeleted = FALSE + + if(result != INITIALIZE_HINT_NORMAL) + switch(result) + if(INITIALIZE_HINT_LATELOAD) + if(arguments[1]) //mapload + late_loaders += A + else + A.LateInitialize() + if(INITIALIZE_HINT_QDEL) + qdel(A) + qdeleted = TRUE + else + BadInitializeCalls[the_type] |= BAD_INIT_NO_HINT + + if(!A) //possible harddel + qdeleted = TRUE + else if(!(A.flags_1 & INITIALIZED_1)) + BadInitializeCalls[the_type] |= BAD_INIT_DIDNT_INIT + + return qdeleted || QDELING(A) + +/datum/controller/subsystem/atoms/proc/map_loader_begin() + old_initialized = initialized + initialized = INITIALIZATION_INSSATOMS + +/datum/controller/subsystem/atoms/proc/map_loader_stop() + initialized = old_initialized + +/datum/controller/subsystem/atoms/Recover() + initialized = SSatoms.initialized + if(initialized == INITIALIZATION_INNEW_MAPLOAD) + InitializeAtoms() + old_initialized = SSatoms.old_initialized + BadInitializeCalls = SSatoms.BadInitializeCalls + +/datum/controller/subsystem/atoms/proc/setupGenetics() + var/list/mutations = subtypesof(/datum/mutation/human) + shuffle_inplace(mutations) + for(var/A in subtypesof(/datum/generecipe)) + var/datum/generecipe/GR = A + GLOB.mutation_recipes[initial(GR.required)] = initial(GR.result) + for(var/i in 1 to LAZYLEN(mutations)) + var/path = mutations[i] //byond gets pissy when we do it in one line + var/datum/mutation/human/B = new path () + B.alias = "Mutation [i]" + GLOB.all_mutations[B.type] = B + GLOB.full_sequences[B.type] = generate_gene_sequence(B.blocks) + GLOB.alias_mutations[B.alias] = B.type + if(B.locked) + continue + if(B.quality == POSITIVE) + GLOB.good_mutations |= B + else if(B.quality == NEGATIVE) + GLOB.bad_mutations |= B + else if(B.quality == MINOR_NEGATIVE) + GLOB.not_good_mutations |= B + CHECK_TICK + +/datum/controller/subsystem/atoms/proc/InitLog() + . = "" + for(var/path in BadInitializeCalls) + . += "Path : [path] \n" + var/fails = BadInitializeCalls[path] + if(fails & BAD_INIT_DIDNT_INIT) + . += "- Didn't call atom/Initialize()\n" + if(fails & BAD_INIT_NO_HINT) + . += "- Didn't return an Initialize hint\n" + if(fails & BAD_INIT_QDEL_BEFORE) + . += "- Qdel'd in New()\n" + if(fails & BAD_INIT_SLEPT) + . += "- Slept during Initialize()\n" + +/datum/controller/subsystem/atoms/Shutdown() + var/initlog = InitLog() + if(initlog) + text2file(initlog, "[GLOB.log_directory]/initialize.log") + +#undef BAD_INIT_QDEL_BEFORE +#undef BAD_INIT_DIDNT_INIT +#undef BAD_INIT_SLEPT +#undef BAD_INIT_NO_HINT diff --git a/code/controllers/subsystem/blackbox.dm b/code/controllers/subsystem/blackbox.dm index c97ffa7021b5..46b3d7533bbe 100644 --- a/code/controllers/subsystem/blackbox.dm +++ b/code/controllers/subsystem/blackbox.dm @@ -1,353 +1,349 @@ -SUBSYSTEM_DEF(blackbox) - name = "Blackbox" - wait = 6000 - flags = SS_NO_TICK_CHECK - runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME - init_order = INIT_ORDER_BLACKBOX - - 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/list/versions = list("antagonists" = 3, - "admin_secrets_fun_used" = 2, - "explosion" = 2, - "time_dilation_current" = 3, - "science_techweb_unlock" = 2, - "round_end_stats" = 2, - "testmerged_prs" = 2) //associative list of any feedback variables that have had their format changed since creation and their current version, remember to update this - -/datum/controller/subsystem/blackbox/Initialize() - triggertime = world.time - record_feedback("amount", "random_seed", Master.random_seed) - record_feedback("amount", "dm_version", DM_VERSION) - record_feedback("amount", "dm_build", DM_BUILD) - record_feedback("amount", "byond_version", world.byond_version) - record_feedback("amount", "byond_build", world.byond_build) - . = ..() - -//poll population -/datum/controller/subsystem/blackbox/fire() - set waitfor = FALSE //for population query - - 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 - update_exp(10,FALSE) - -/datum/controller/subsystem/blackbox/proc/CheckPlayerCount() - set waitfor = FALSE - - if(!SSdbcore.Connect()) - 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]')") - query_record_playercount.Execute() - qdel(query_record_playercount) - -/datum/controller/subsystem/blackbox/Recover() - feedback = SSblackbox.feedback - sealed = SSblackbox.sealed - -//no touchie -/datum/controller/subsystem/blackbox/vv_get_var(var_name) - if(var_name == "feedback") - return debug_variable(var_name, deepCopyList(feedback), 0, src) - return ..() - -/datum/controller/subsystem/blackbox/vv_edit_var(var_name, var_value) - switch(var_name) - if("feedback") - return FALSE - if("sealed") - if(var_value) - return Seal() - return FALSE - return ..() - -//Recorded on subsystem shutdown -/datum/controller/subsystem/blackbox/proc/FinalFeedback() - record_feedback("tally", "ahelp_stats", GLOB.ahelp_tickets.active_tickets.len, "unresolved") - for (var/obj/machinery/telecomms/message_server/MS in GLOB.telecomms_list) - if (MS.pda_msgs.len) - record_feedback("tally", "radio_usage", MS.pda_msgs.len, "PDA") - if (MS.rc_msgs.len) - record_feedback("tally", "radio_usage", MS.rc_msgs.len, "request console") - - for(var/player_key in GLOB.player_details) - var/datum/player_details/PD = GLOB.player_details[player_key] - record_feedback("tally", "client_byond_version", 1, PD.byond_version) - -/datum/controller/subsystem/blackbox/Shutdown() - sealed = FALSE - FinalFeedback() - - if (!SSdbcore.Connect()) - return - - var/list/sqlrowlist = list() - - for (var/datum/feedback_variable/FV in feedback) - var/sqlversion = 1 - if(FV.key in versions) - sqlversion = versions[FV.key] - sqlrowlist += list(list("datetime" = "Now()", "round_id" = GLOB.round_id, "key_name" = "'[sanitizeSQL(FV.key)]'", "key_type" = "'[FV.key_type]'", "version" = "[sqlversion]", "json" = "'[sanitizeSQL(json_encode(FV.json))]'")) - - if (!length(sqlrowlist)) - return - - SSdbcore.MassInsert(format_table_name("feedback"), sqlrowlist, ignore_errors = TRUE, delayed = TRUE) - -/datum/controller/subsystem/blackbox/proc/Seal() - if(sealed) - return FALSE - if(IsAdminAdvancedProcCall()) - message_admins("[key_name_admin(usr)] sealed the blackbox!") - log_game("Blackbox sealed[IsAdminAdvancedProcCall() ? " by [key_name(usr)]" : ""].") - sealed = TRUE - return TRUE - -/datum/controller/subsystem/blackbox/proc/LogBroadcast(freq) - if(sealed) - return - switch(freq) - if(FREQ_COMMON) - record_feedback("tally", "radio_usage", 1, "common") - if(FREQ_SCIENCE) - record_feedback("tally", "radio_usage", 1, "science") - if(FREQ_COMMAND) - record_feedback("tally", "radio_usage", 1, "command") - if(FREQ_MEDICAL) - record_feedback("tally", "radio_usage", 1, "medical") - if(FREQ_ENGINEERING) - record_feedback("tally", "radio_usage", 1, "engineering") - if(FREQ_SECURITY) - record_feedback("tally", "radio_usage", 1, "security") - if(FREQ_SYNDICATE) - record_feedback("tally", "radio_usage", 1, "syndicate") - if(FREQ_SERVICE) - record_feedback("tally", "radio_usage", 1, "service") - if(FREQ_SUPPLY) - record_feedback("tally", "radio_usage", 1, "supply") - if(FREQ_CENTCOM) - record_feedback("tally", "radio_usage", 1, "centcom") - if(FREQ_AI_PRIVATE) - record_feedback("tally", "radio_usage", 1, "ai private") - if(FREQ_CTF_RED) - record_feedback("tally", "radio_usage", 1, "CTF red team") - if(FREQ_CTF_BLUE) - record_feedback("tally", "radio_usage", 1, "CTF blue team") - else - record_feedback("tally", "radio_usage", 1, "other") - -/datum/controller/subsystem/blackbox/proc/find_feedback_datum(key, key_type) - for(var/datum/feedback_variable/FV in feedback) - if(FV.key == key) - return FV - - var/datum/feedback_variable/FV = new(key, key_type) - feedback += FV - return FV -/* -feedback data can be recorded in 5 formats: -"text" - 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") - 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) - SSblackbox.record_feedback("amount", "example", 2) - json: {"data":10} -"tally" - used to track the number of occurances of multiple related values i.e. how many times each type of gun is fired - further calls to the same key will: - add or subtract from the saved value of the data key if it already exists - append the key and it's value if it doesn't exist - 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}} -"nested tally" - used to track the number of occurances of structured semi-relational values i.e. the results of arcade machines - similar to running total, but related values are nested in a multi-dimensional array built - the final element in the data list is used as the tracking key, all prior elements are used for nesting - all data list elements must be strings - 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")) - 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")) - 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)) - 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"}}} - -Versioning - If the format of a feedback variable is ever changed, i.e. how many levels of nesting are used or a new type of data is added to it, add it to the versions list - When feedback is being saved if a key is in the versions list the value specified there will be used, otherwise all keys are assumed to be version = 1 - versions is an associative list, remember to use the same string in it as defined on a feedback variable, example: - list/versions = list("round_end_stats" = 4, - "admin_toggle" = 2, - "gun_fired" = 2) -*/ -/datum/controller/subsystem/blackbox/proc/record_feedback(key_type, key, increment, data, overwrite) - if(sealed || !key_type || !istext(key) || !isnum(increment || !data)) - return - var/datum/feedback_variable/FV = find_feedback_datum(key, key_type) - switch(key_type) - if("text") - if(!istext(data)) - return - if(!islist(FV.json["data"])) - FV.json["data"] = list() - if(overwrite) - FV.json["data"] = data - else - FV.json["data"] |= data - if("amount") - FV.json["data"] += increment - if("tally") - if(!islist(FV.json["data"])) - FV.json["data"] = list() - FV.json["data"]["[data]"] += increment - if("nested tally") - if(!islist(data)) - return - if(!islist(FV.json["data"])) - FV.json["data"] = list() - FV.json["data"] = record_feedback_recurse_list(FV.json["data"], data, increment) - if("associative") - if(!islist(data)) - return - if(!islist(FV.json["data"])) - FV.json["data"] = list() - var/pos = length(FV.json["data"]) + 1 - FV.json["data"]["[pos]"] = list() //in 512 "pos" can be replaced with "[FV.json["data"].len+1]" - for(var/i in data) - if(islist(data[i])) - FV.json["data"]["[pos]"]["[i]"] = data[i] //and here with "[FV.json["data"].len]" - else - FV.json["data"]["[pos]"]["[i]"] = "[data[i]]" - else - CRASH("Invalid feedback key_type: [key_type]") - -/datum/controller/subsystem/blackbox/proc/record_feedback_recurse_list(list/L, list/key_list, increment, depth = 1) - if(depth == key_list.len) - if(L.Find(key_list[depth])) - L["[key_list[depth]]"] += increment - else - var/list/LFI = list(key_list[depth] = increment) - L += LFI - else - if(!L.Find(key_list[depth])) - var/list/LGD = list(key_list[depth] = list()) - L += LGD - L["[key_list[depth-1]]"] = .(L["[key_list[depth]]"], key_list, increment, ++depth) - return L - -/datum/feedback_variable - var/key - var/key_type - var/list/json = list() - -/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 - - ticket = sanitizeSQL(ticket) - action = sanitizeSQL(action) - message = sanitizeSQL(message) - recipient = recipient ? "'[sanitizeSQL(recipient)]'" : "NULL" - sender = sender ? "'[sanitizeSQL(sender)]'" : "NULL" - var/server_ip = sanitizeSQL(world.internet_address) - var/server_port = sanitizeSQL(world.port) - var/round_id = sanitizeSQL(GLOB.round_id) - - var/datum/DBQuery/query_log_ahelp = SSdbcore.NewQuery("INSERT INTO [format_table_name("ticket")] (ticket, action, message, recipient, sender, server_ip, server_port, round_id, timestamp) VALUES ('[ticket]', '[action]', '[message]', [recipient], [sender], INET_ATON(IF('[server_ip]' LIKE '', '0', '[server_ip]')), '[server_port]','[round_id]', '[SQLtime()]')") - query_log_ahelp.Execute() - qdel(query_log_ahelp) - - -/datum/controller/subsystem/blackbox/proc/ReportDeath(mob/living/L) - set waitfor = FALSE - if(sealed) - return - if(!L || !L.key || !L.mind) - return - if(!L.suiciding && !first_death.len) - first_death["name"] = "[(L.real_name == L.name) ? L.real_name : "[L.real_name] as [L.name]"]" - first_death["role"] = null - if(L.mind.assigned_role) - first_death["role"] = L.mind.assigned_role - 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) || BRAIN_DAMAGE_DEATH //getOrganLoss returns null without a brain but a value is required for this column - 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])") - if(query_report_death) - query_report_death.Execute(async = TRUE) - qdel(query_report_death) +SUBSYSTEM_DEF(blackbox) + name = "Blackbox" + wait = 6000 + flags = SS_NO_TICK_CHECK + runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME + init_order = INIT_ORDER_BLACKBOX + + 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/list/versions = list("antagonists" = 3, + "admin_secrets_fun_used" = 2, + "explosion" = 2, + "time_dilation_current" = 3, + "science_techweb_unlock" = 2, + "round_end_stats" = 2, + "testmerged_prs" = 2) //associative list of any feedback variables that have had their format changed since creation and their current version, remember to update this + +/datum/controller/subsystem/blackbox/Initialize() + triggertime = world.time + record_feedback("amount", "random_seed", Master.random_seed) + record_feedback("amount", "dm_version", DM_VERSION) + record_feedback("amount", "dm_build", DM_BUILD) + record_feedback("amount", "byond_version", world.byond_version) + record_feedback("amount", "byond_build", world.byond_build) + . = ..() + +//poll population +/datum/controller/subsystem/blackbox/fire() + set waitfor = FALSE //for population query + + 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 + update_exp(10,FALSE) + +/datum/controller/subsystem/blackbox/proc/CheckPlayerCount() + set waitfor = FALSE + + if(!SSdbcore.Connect()) + 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, :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) + +/datum/controller/subsystem/blackbox/Recover() + feedback = SSblackbox.feedback + sealed = SSblackbox.sealed + +//no touchie +/datum/controller/subsystem/blackbox/vv_get_var(var_name) + if(var_name == "feedback") + return debug_variable(var_name, deepCopyList(feedback), 0, src) + return ..() + +/datum/controller/subsystem/blackbox/vv_edit_var(var_name, var_value) + switch(var_name) + if("feedback") + return FALSE + if("sealed") + if(var_value) + return Seal() + return FALSE + return ..() + +//Recorded on subsystem shutdown +/datum/controller/subsystem/blackbox/proc/FinalFeedback() + record_feedback("tally", "ahelp_stats", GLOB.ahelp_tickets.active_tickets.len, "unresolved") + for (var/obj/machinery/telecomms/message_server/MS in GLOB.telecomms_list) + if (MS.pda_msgs.len) + record_feedback("tally", "radio_usage", MS.pda_msgs.len, "PDA") + if (MS.rc_msgs.len) + record_feedback("tally", "radio_usage", MS.rc_msgs.len, "request console") + + for(var/player_key in GLOB.player_details) + var/datum/player_details/PD = GLOB.player_details[player_key] + record_feedback("tally", "client_byond_version", 1, PD.byond_version) + +/datum/controller/subsystem/blackbox/Shutdown() + sealed = FALSE + FinalFeedback() + + if (!SSdbcore.Connect()) + return + + var/list/special_columns = list( + "datetime" = "NOW()" + ) + var/list/sqlrowlist = list() + for (var/datum/feedback_variable/FV in feedback) + sqlrowlist += list(list( + "round_id" = GLOB.round_id, + "key_name" = FV.key, + "key_type" = FV.key_type, + "version" = versions[FV.key] || 1, + "json" = json_encode(FV.json) + )) + + if (!length(sqlrowlist)) + return + + SSdbcore.MassInsert(format_table_name("feedback"), sqlrowlist, ignore_errors = TRUE, delayed = TRUE, special_columns = special_columns) + +/datum/controller/subsystem/blackbox/proc/Seal() + if(sealed) + return FALSE + if(IsAdminAdvancedProcCall()) + message_admins("[key_name_admin(usr)] sealed the blackbox!") + log_game("Blackbox sealed[IsAdminAdvancedProcCall() ? " by [key_name(usr)]" : ""].") + sealed = TRUE + return TRUE + +/datum/controller/subsystem/blackbox/proc/LogBroadcast(freq) + if(sealed) + return + switch(freq) + if(FREQ_COMMON) + record_feedback("tally", "radio_usage", 1, "common") + if(FREQ_SCIENCE) + record_feedback("tally", "radio_usage", 1, "science") + if(FREQ_COMMAND) + record_feedback("tally", "radio_usage", 1, "command") + if(FREQ_MEDICAL) + record_feedback("tally", "radio_usage", 1, "medical") + if(FREQ_ENGINEERING) + record_feedback("tally", "radio_usage", 1, "engineering") + if(FREQ_SECURITY) + record_feedback("tally", "radio_usage", 1, "security") + if(FREQ_SYNDICATE) + record_feedback("tally", "radio_usage", 1, "syndicate") + if(FREQ_SERVICE) + record_feedback("tally", "radio_usage", 1, "service") + if(FREQ_SUPPLY) + record_feedback("tally", "radio_usage", 1, "supply") + if(FREQ_CENTCOM) + record_feedback("tally", "radio_usage", 1, "centcom") + if(FREQ_AI_PRIVATE) + record_feedback("tally", "radio_usage", 1, "ai private") + if(FREQ_CTF_RED) + record_feedback("tally", "radio_usage", 1, "CTF red team") + if(FREQ_CTF_BLUE) + record_feedback("tally", "radio_usage", 1, "CTF blue team") + else + record_feedback("tally", "radio_usage", 1, "other") + +/datum/controller/subsystem/blackbox/proc/find_feedback_datum(key, key_type) + for(var/datum/feedback_variable/FV in feedback) + if(FV.key == key) + return FV + + var/datum/feedback_variable/FV = new(key, key_type) + feedback += FV + return FV +/* +feedback data can be recorded in 5 formats: +"text" + 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") + 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) + SSblackbox.record_feedback("amount", "example", 2) + json: {"data":10} +"tally" + used to track the number of occurances of multiple related values i.e. how many times each type of gun is fired + further calls to the same key will: + add or subtract from the saved value of the data key if it already exists + append the key and it's value if it doesn't exist + 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}} +"nested tally" + used to track the number of occurances of structured semi-relational values i.e. the results of arcade machines + similar to running total, but related values are nested in a multi-dimensional array built + the final element in the data list is used as the tracking key, all prior elements are used for nesting + all data list elements must be strings + 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")) + 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")) + 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)) + 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"}}} + +Versioning + If the format of a feedback variable is ever changed, i.e. how many levels of nesting are used or a new type of data is added to it, add it to the versions list + When feedback is being saved if a key is in the versions list the value specified there will be used, otherwise all keys are assumed to be version = 1 + versions is an associative list, remember to use the same string in it as defined on a feedback variable, example: + list/versions = list("round_end_stats" = 4, + "admin_toggle" = 2, + "gun_fired" = 2) +*/ +/datum/controller/subsystem/blackbox/proc/record_feedback(key_type, key, increment, data, overwrite) + if(sealed || !key_type || !istext(key) || !isnum(increment || !data)) + return + var/datum/feedback_variable/FV = find_feedback_datum(key, key_type) + switch(key_type) + if("text") + if(!istext(data)) + return + if(!islist(FV.json["data"])) + FV.json["data"] = list() + if(overwrite) + FV.json["data"] = data + else + FV.json["data"] |= data + if("amount") + FV.json["data"] += increment + if("tally") + if(!islist(FV.json["data"])) + FV.json["data"] = list() + FV.json["data"]["[data]"] += increment + if("nested tally") + if(!islist(data)) + return + if(!islist(FV.json["data"])) + FV.json["data"] = list() + FV.json["data"] = record_feedback_recurse_list(FV.json["data"], data, increment) + if("associative") + if(!islist(data)) + return + if(!islist(FV.json["data"])) + FV.json["data"] = list() + var/pos = length(FV.json["data"]) + 1 + FV.json["data"]["[pos]"] = list() //in 512 "pos" can be replaced with "[FV.json["data"].len+1]" + for(var/i in data) + if(islist(data[i])) + FV.json["data"]["[pos]"]["[i]"] = data[i] //and here with "[FV.json["data"].len]" + else + FV.json["data"]["[pos]"]["[i]"] = "[data[i]]" + else + CRASH("Invalid feedback key_type: [key_type]") + +/datum/controller/subsystem/blackbox/proc/record_feedback_recurse_list(list/L, list/key_list, increment, depth = 1) + if(depth == key_list.len) + if(L.Find(key_list[depth])) + L["[key_list[depth]]"] += increment + else + var/list/LFI = list(key_list[depth] = increment) + L += LFI + else + if(!L.Find(key_list[depth])) + var/list/LGD = list(key_list[depth] = list()) + L += LGD + L["[key_list[depth-1]]"] = .(L["[key_list[depth]]"], key_list, increment, ++depth) + return L + +/datum/feedback_variable + var/key + var/key_type + var/list/json = list() + +/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 + + var/datum/DBQuery/query_log_ahelp = SSdbcore.NewQuery({" + INSERT INTO [format_table_name("ticket")] (ticket, action, message, recipient, sender, server_ip, server_port, round_id, timestamp) + VALUES (:ticket, :action, :message, :recipient, :sender, INET_ATON(:server_ip), :server_port, :round_id, :time) + "}, list("ticket" = ticket, "action" = action, "message" = message, "recipient" = recipient, "sender" = sender, "server_ip" = world.internet_address || "0", "server_port" = world.port, "round_id" = GLOB.round_id, "time" = SQLtime())) + query_log_ahelp.Execute() + qdel(query_log_ahelp) + + +/datum/controller/subsystem/blackbox/proc/ReportDeath(mob/living/L) + set waitfor = FALSE + if(sealed) + return + if(!L || !L.key || !L.mind) + return + if(!L.suiciding && !first_death.len) + first_death["name"] = "[(L.real_name == L.name) ? L.real_name : "[L.real_name] as [L.name]"]" + first_death["role"] = null + if(L.mind.assigned_role) + first_death["role"] = L.mind.assigned_role + 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 + + if(!SSdbcore.Connect()) + return + + 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 (: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, + "sucide" = 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 462ba6094481..eda18c9b7d70 100644 --- a/code/controllers/subsystem/dbcore.dm +++ b/code/controllers/subsystem/dbcore.dm @@ -1,402 +1,397 @@ -SUBSYSTEM_DEF(dbcore) - name = "Database" - 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 - var/db_major = 0 - var/failed_connections = 0 - - var/last_error - var/list/active_queries = list() - - var/datum/BSQL_Connection/connection - var/datum/BSQL_Operation/connectOperation - -/datum/controller/subsystem/dbcore/Initialize() - //We send warnings to the admins during subsystem init, as the clients will be New'd and messages - //will queue properly with goonchat - switch(schema_mismatch) - if(1) - message_admins("Database schema ([db_major].[db_minor]) doesn't match the latest schema version ([DB_MAJOR_VERSION].[DB_MINOR_VERSION]), this may lead to undefined behaviour or errors") - if(2) - message_admins("Could not get schema version from database") - - return ..() - -/datum/controller/subsystem/dbcore/fire() - for(var/I in active_queries) - var/datum/DBQuery/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]") - qdel(Q) - if(MC_TICK_CHECK) - return - -/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]") - 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) && ..() - -/datum/controller/subsystem/dbcore/vv_edit_var(var_name, var_value) - if(var_name == NAMEOF(src, connection) || var_name == NAMEOF(src, connectOperation)) - return FALSE - return ..() - -/datum/controller/subsystem/dbcore/proc/Connect() - if(IsConnected()) - return TRUE - - 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 > FAILED_DB_CONNECTION_CUTOFF) //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)) - return FALSE - - var/user = CONFIG_GET(string/feedback_login) - var/pass = CONFIG_GET(string/feedback_password) - var/db = CONFIG_GET(string/feedback_database) - var/address = CONFIG_GET(string/address) - var/port = CONFIG_GET(number/port) - - 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 - 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]") - ++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") - query_db_version.Execute() - if(query_db_version.NextRow()) - db_major = text2num(query_db_version.item[1]) - db_minor = text2num(query_db_version.item[2]) - if(db_major != DB_MAJOR_VERSION || db_minor != DB_MINOR_VERSION) - schema_mismatch = 1 // flag admin message about mismatch - log_sql("Database schema ([db_major].[db_minor]) doesn't match the latest schema version ([DB_MAJOR_VERSION].[DB_MINOR_VERSION]), this may lead to undefined behaviour or errors") - else - schema_mismatch = 2 //flag admin message about no schema version - log_sql("Could not get schema version from database") - qdel(query_db_version) - else - log_sql("Your server failed to establish a connection with the database.") - else - log_sql("Database is not enabled in configuration.") - -/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(async = FALSE) - qdel(query_round_initialize) - var/datum/DBQuery/query_round_last_id = SSdbcore.NewQuery("SELECT LAST_INSERT_ID()") - query_round_last_id.Execute(async = FALSE) - if(query_round_last_id.NextRow(async = FALSE)) - 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]") - 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]") - query_round_end.Execute() - qdel(query_round_end) - -/datum/controller/subsystem/dbcore/proc/Disconnect() - failed_connections = 0 - QDEL_NULL(connectOperation) - QDEL_NULL(connection) - -/datum/controller/subsystem/dbcore/proc/IsConnected() - 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) - -/datum/controller/subsystem/dbcore/proc/ErrorMsg() - if(!CONFIG_GET(flag/sql_enabled)) - return "Database disabled by configuration" - return last_error - -/datum/controller/subsystem/dbcore/proc/ReportError(error) - last_error = error - -/datum/controller/subsystem/dbcore/proc/NewQuery(sql_query) - 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) - -/datum/controller/subsystem/dbcore/proc/QuerySelect(list/querys, warn = FALSE, qdel = FALSE) - if (!islist(querys)) - if (!istype(querys, /datum/DBQuery)) - CRASH("Invalid query passed to QuerySelect: [querys]") - querys = list(querys) - - for (var/thing in querys) - var/datum/DBQuery/query = thing - if (warn) - INVOKE_ASYNC(query, /datum/DBQuery.proc/warn_execute) - else - INVOKE_ASYNC(query, /datum/DBQuery.proc/Execute) - - for (var/thing in querys) - var/datum/DBQuery/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 -You are expected to do your own escaping of the data, and expected to provide your own quotes for strings. -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 -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) - if (!table || !rows || !istype(rows)) - return - var/list/columns = list() - var/list/sorted_rows = 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 - - sorted_row[idx] = row[column] - sorted_rows[++sorted_rows.len] = sorted_row - - 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 - - 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]") - 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() - - 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 - -/datum/DBQuery/New(sql_query, datum/BSQL_Connection/connection) - SSdbcore.active_queries[src] = TRUE - Activity("Created") - item = list() - src.connection = connection - sql = sql_query - -/datum/DBQuery/Destroy() - Close() - SSdbcore.active_queries -= src - return ..() - -/datum/DBQuery/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) - last_activity = activity - last_activity_time = world.time - -/datum/DBQuery/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 = 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)) - last_error = "No connection!" - return FALSE - - var/start_time - var/timed_out - if(!async) - start_time = REALTIMEOFDAY - Close() - timed_out = run_query(async) - if(query.GetErrorCode() == 2006) //2006 is the return code for "MySQL server has gone away" time-out error, meaning the connection has been lost to the server (if it's still alive) - log_sql("Executing query encountered returned a lost database connection (2006).") - SSdbcore.Disconnect() - if(SSdbcore.Connect()) //connection was restablished, reattempt the query - log_sql("Connection restablished") - timed_out = run_query(async) - else - log_sql("Executing query failed to restablish database connection.") - skip_next_is_complete = TRUE - var/error = QDELETED(query) ? "Query object deleted!" : query.GetError() - last_error = error - . = !error - if(!. && log_error) - log_sql("[error] | Query used: [sql]") - if(!async && timed_out) - log_query_debug("Query execution started at [start_time]") - log_query_debug("Query execution ended at [REALTIMEOFDAY]") - log_query_debug("Slow query timeout detected.") - log_query_debug("Query used: [sql]") - slow_query_check() - -/datum/DBQuery/proc/run_query(async) - query = connection.BeginQuery(sql) - if(!async) - . = !query.WaitForCompletion() - else - in_progress = TRUE - UNTIL(query.IsComplete()) - in_progress = FALSE - -/datum/DBQuery/proc/slow_query_check() - message_admins("HEY! A database query timed out. Did the server just hang? \[YES\]|\[NO\]") - -/datum/DBQuery/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 - else - skip_next_is_complete = 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() - 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, ": OpenConnection(")) - message = "OpenConnection CENSORED" - - log_sql("BSQL_DEBUG: [message]") +SUBSYSTEM_DEF(dbcore) + name = "Database" + 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 + var/db_major = 0 + var/failed_connections = 0 + + var/last_error + var/list/active_queries = list() + + 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 + //will queue properly with goonchat + switch(schema_mismatch) + if(1) + message_admins("Database schema ([db_major].[db_minor]) doesn't match the latest schema version ([DB_MAJOR_VERSION].[DB_MINOR_VERSION]), this may lead to undefined behaviour or errors") + if(2) + message_admins("Could not get schema version from database") + + return ..() + +/datum/controller/subsystem/dbcore/fire() + for(var/I in active_queries) + var/datum/DBQuery/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]") + qdel(Q) + if(MC_TICK_CHECK) + return + +/datum/controller/subsystem/dbcore/Recover() + connection = SSdbcore.connection + +/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 = :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() + +//nu +/datum/controller/subsystem/dbcore/can_vv_get(var_name) + 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)) + return FALSE + return ..() + +/datum/controller/subsystem/dbcore/proc/Connect() + if(IsConnected()) + return TRUE + + 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 > FAILED_DB_CONNECTION_CUTOFF) //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)) + return FALSE + + var/user = CONFIG_GET(string/feedback_login) + var/pass = CONFIG_GET(string/feedback_password) + 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) + + var/result = json_decode(rustg_sql_connect_pool(json_encode(list( + "host" = address, + "port" = port, + "user" = user, + "pass" = pass, + "db_name" = db, + "max_threads" = 5, + "read_timeout" = timeout, + "write_timeout" = timeout, + "max_threads" = thread_limit, + )))) + . = (result["status"] == "ok") + if (.) + connection = result["handle"] + else + connection = null + last_error = result["data"] + log_sql("Connect() failed | [last_error]") + ++failed_connections + +/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") + query_db_version.Execute() + if(query_db_version.NextRow()) + db_major = text2num(query_db_version.item[1]) + db_minor = text2num(query_db_version.item[2]) + if(db_major != DB_MAJOR_VERSION || db_minor != DB_MINOR_VERSION) + schema_mismatch = 1 // flag admin message about mismatch + log_sql("Database schema ([db_major].[db_minor]) doesn't match the latest schema version ([DB_MAJOR_VERSION].[DB_MINOR_VERSION]), this may lead to undefined behaviour or errors") + else + schema_mismatch = 2 //flag admin message about no schema version + log_sql("Could not get schema version from database") + qdel(query_db_version) + else + log_sql("Your server failed to establish a connection with the database.") + else + log_sql("Database is not enabled in configuration.") + +/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(: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) + +/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 = :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/datum/DBQuery/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 + if (connection) + rustg_sql_disconnect_pool(connection) + connection = null + +/datum/controller/subsystem/dbcore/proc/IsConnected() + if (!CONFIG_GET(flag/sql_enabled)) + return FALSE + 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)) + return "Database disabled by configuration" + return last_error + +/datum/controller/subsystem/dbcore/proc/ReportError(error) + last_error = error + +/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(connection, sql_query, arguments) + +/datum/controller/subsystem/dbcore/proc/QuerySelect(list/querys, warn = FALSE, qdel = FALSE) + if (!islist(querys)) + if (!istype(querys, /datum/DBQuery)) + CRASH("Invalid query passed to QuerySelect: [querys]") + querys = list(querys) + + for (var/thing in querys) + var/datum/DBQuery/query = thing + if (warn) + INVOKE_ASYNC(query, /datum/DBQuery.proc/warn_execute) + else + INVOKE_ASYNC(query, /datum/DBQuery.proc/Execute) + + for (var/thing in querys) + var/datum/DBQuery/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 +You are expected to do your own escaping of the data, and expected to provide your own quotes for strings. +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 +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, special_columns = null) + if (!table || !rows || !istype(rows)) + return + + // Prepare column list + var/list/columns = list() + var/list/has_question_mark = list() + for (var/list/row in rows) + for (var/column in row) + 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], "?") + + // 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])" + query_parts += "\nON DUPLICATE KEY UPDATE [column_list.Join(", ")]" + else if (duplicate_key != FALSE) + query_parts += duplicate_key + + var/datum/DBQuery/Query = NewQuery(query_parts.Join(), arguments) + if (warn) + . = Query.warn_execute(async) + else + . = Query.Execute(async) + qdel(Query) + +/datum/DBQuery + // Inputs + var/connection + var/sql + var/arguments + + // Status information + var/in_progress + var/last_error + var/last_activity + var/last_activity_time + + // Output + var/list/list/rows + var/next_row_to_take = 1 + var/affected + var/last_insert_id + + var/list/item //list of data values populated by NextRow() + +/datum/DBQuery/New(connection, sql, arguments) + SSdbcore.active_queries[src] = TRUE + Activity("Created") + item = list() + + src.connection = connection + src.sql = sql + src.arguments = arguments + +/datum/DBQuery/Destroy() + Close() + SSdbcore.active_queries -= src + return ..() + +/datum/DBQuery/CanProcCall(proc_name) + //fuck off kevinz + return FALSE + +/datum/DBQuery/proc/Activity(activity) + last_activity = activity + last_activity_time = world.time + +/datum/DBQuery/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 = TRUE, log_error = TRUE) + Activity("Execute") + if(in_progress) + CRASH("Attempted to start a new query while waiting on the old one") + + if(!SSdbcore.IsConnected()) + last_error = "No connection!" + return FALSE + + var/start_time + if(!async) + start_time = REALTIMEOFDAY + Close() + . = run_query(async) + var/timed_out = !. && findtext(last_error, "Operation timed out") + if(!. && log_error) + log_sql("[last_error] | Query used: [sql]") + if(!async && timed_out) + log_query_debug("Query execution started at [start_time]") + log_query_debug("Query execution ended at [REALTIMEOFDAY]") + log_query_debug("Slow query timeout detected.") + log_query_debug("Query used: [sql]") + slow_query_check() + +/datum/DBQuery/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/DBQuery/proc/slow_query_check() + message_admins("HEY! A database query timed out. Did the server just hang? \[YES\]|\[NO\]") + +/datum/DBQuery/proc/NextRow(async = TRUE) + Activity("NextRow") + + if (rows && next_row_to_take <= rows.len) + item = rows[next_row_to_take] + next_row_to_take++ + return !!item + else + return FALSE + +/datum/DBQuery/proc/ErrorMsg() + return last_error + +/datum/DBQuery/proc/Close() + rows = null + item = null diff --git a/code/controllers/subsystem/discord.dm b/code/controllers/subsystem/discord.dm index 87f8a761355b..d27cfa58dfea 100644 --- a/code/controllers/subsystem/discord.dm +++ b/code/controllers/subsystem/discord.dm @@ -1,138 +1,148 @@ -/* -NOTES: -There is a DB table to track ckeys and associated discord IDs. -This system REQUIRES TGS, and will auto-disable if TGS is not present. -The SS uses fire() instead of just pure shutdown, so people can be notified if it comes back after a crash, where the SS wasnt properly shutdown -It only writes to the disk every 5 minutes, and it wont write to disk if the file is the same as it was the last time it was written. This is to save on disk writes -The system is kept per-server (EG: Terry will not notify people who pressed notify on Sybil), but the accounts are between servers so you dont have to relink on each server. - -################## -# HOW THIS WORKS # -################## - -ROUNDSTART: -1] The file is loaded and the discord IDs are extracted -2] A ping is sent to the discord with the IDs of people who wished to be notified -3] The file is emptied - -MIDROUND: -1] Someone usees the notify verb, it adds their discord ID to the list. -2] On fire, it will write that to the disk, as long as conditions above are correct - -END ROUND: -1] The file is force-saved, incase it hasnt fired at end round - -This is an absolute clusterfuck, but its my clusterfuck -aa07 -*/ - -SUBSYSTEM_DEF(discord) - name = "Discord" - wait = 3000 - init_order = INIT_ORDER_DISCORD - - /// People to save to notify file - var/list/notify_members = list() - /// Copy of previous list, so the SS doesnt have to fire if no new members have been added - var/list/notify_members_cache = list() - /// People to notify on roundstart - var/list/people_to_notify = list() - /// List that holds accounts to link, used in conjunction with TGS - var/list/account_link_cache = list() - /// list of people who tried to reverify, so they can only do it once per round as a shitty slowdown - var/list/reverify_cache = list() - var/notify_file = file("data/notify.json") - /// Is TGS enabled (If not we wont fire because otherwise this is useless) - var/enabled = 0 - -/datum/controller/subsystem/discord/Initialize(start_timeofday) - // Check for if we are using TGS, otherwise return and disables firing - if(world.TgsAvailable()) - enabled = 1 // Allows other procs to use this (Account linking, etc) - else - can_fire = 0 // We dont want excess firing - return ..() // Cancel - - try - people_to_notify = json_decode(file2text(notify_file)) - catch - pass() // The list can just stay as its default (blank). Pass() exists because it needs a catch - var/notifymsg = "" - for(var/id in people_to_notify) - // I would use jointext here, but I dont think you can two-side glue with it, and I would have to strip characters otherwise - notifymsg += "<@[id]> " // 22 charaters per notify, 90 notifies per message, so I am not making a failsafe because 90 people arent going to notify at once - if(notifymsg) - send2chat("[notifymsg]", CONFIG_GET(string/chat_announce_new_game)) // Sends the message to the discord, using same config option as the roundstart notification - fdel(notify_file) // Deletes the file - return ..() - -/datum/controller/subsystem/discord/fire() - if(!enabled) - return // Dont do shit if its disabled - if(notify_members == notify_members_cache) - return // Dont re-write the file - // If we are all clear - write_notify_file() - -/datum/controller/subsystem/discord/Shutdown() - write_notify_file() // Guaranteed force-write on server close - -/datum/controller/subsystem/discord/proc/write_notify_file() - if(!enabled) // Dont do shit if its disabled - return - fdel(notify_file) // Deletes the file first to make sure it writes properly - WRITE_FILE(notify_file, json_encode(notify_members)) // Writes the file - notify_members_cache = notify_members // Updates the cache list - -// Returns ID from ckey -/datum/controller/subsystem/discord/proc/lookup_id(lookup_ckey) - var/datum/DBQuery/query_get_discord_id = SSdbcore.NewQuery("SELECT discord_id FROM [format_table_name("player")] WHERE ckey = '[sanitizeSQL(lookup_ckey)]'") - if(!query_get_discord_id.Execute()) - qdel(query_get_discord_id) - return - if(query_get_discord_id.NextRow()) - . = query_get_discord_id.item[1] - qdel(query_get_discord_id) - -// Returns ckey from ID -/datum/controller/subsystem/discord/proc/lookup_ckey(lookup_id) - var/datum/DBQuery/query_get_discord_ckey = SSdbcore.NewQuery("SELECT ckey FROM [format_table_name("player")] WHERE discord_id = '[sanitizeSQL(lookup_id)]'") - if(!query_get_discord_ckey.Execute()) - qdel(query_get_discord_ckey) - return - if(query_get_discord_ckey.NextRow()) - . = query_get_discord_ckey.item[1] - qdel(query_get_discord_ckey) - -// Finalises link -/datum/controller/subsystem/discord/proc/link_account(ckey) - var/datum/DBQuery/link_account = SSdbcore.NewQuery("UPDATE [format_table_name("player")] SET discord_id = '[sanitizeSQL(account_link_cache[ckey])]' WHERE ckey = '[sanitizeSQL(ckey)]'") - link_account.Execute() - qdel(link_account) - account_link_cache -= ckey - -// Unlink account (Admin verb used) -/datum/controller/subsystem/discord/proc/unlink_account(ckey) - var/datum/DBQuery/unlink_account = SSdbcore.NewQuery("UPDATE [format_table_name("player")] SET discord_id = NULL WHERE ckey = '[sanitizeSQL(ckey)]'") - unlink_account.Execute() - qdel(unlink_account) - -// Clean up a discord account mention -/datum/controller/subsystem/discord/proc/id_clean(input) - var/regex/num_only = regex("\[^0-9\]", "g") - return num_only.Replace(input, "") - -/datum/controller/subsystem/discord/proc/grant_role(id) - // Ignore this shit if config isnt enabled for it - if(!CONFIG_GET(flag/enable_discord_autorole)) - return - - var/url = "https://discordapp.com/api/guilds/[CONFIG_GET(string/discord_guildid)]/members/[id]/roles/[CONFIG_GET(string/discord_roleid)]" - - // Make the request - var/datum/http_request/req = new() - req.prepare(RUSTG_HTTP_METHOD_PUT, url, "", list("Authorization" = "Bot [CONFIG_GET(string/discord_token)]")) - req.begin_async() - UNTIL(req.is_complete()) - var/datum/http_response/res = req.into_response() - - WRITE_LOG(GLOB.discord_api_log, "PUT [url] returned [res.status_code] [res.body]") +/* +NOTES: +There is a DB table to track ckeys and associated discord IDs. +This system REQUIRES TGS, and will auto-disable if TGS is not present. +The SS uses fire() instead of just pure shutdown, so people can be notified if it comes back after a crash, where the SS wasn't properly shutdown +It only writes to the disk every 5 minutes, and it won't write to disk if the file is the same as it was the last time it was written. This is to save on disk writes +The system is kept per-server (EG: Terry will not notify people who pressed notify on Sybil), but the accounts are between servers so you dont have to relink on each server. + +################## +# HOW THIS WORKS # +################## + +ROUNDSTART: +1] The file is loaded and the discord IDs are extracted +2] A ping is sent to the discord with the IDs of people who wished to be notified +3] The file is emptied + +MIDROUND: +1] Someone usees the notify verb, it adds their discord ID to the list. +2] On fire, it will write that to the disk, as long as conditions above are correct + +END ROUND: +1] The file is force-saved, incase it hasn't fired at end round + +This is an absolute clusterfuck, but its my clusterfuck -aa07 +*/ + +SUBSYSTEM_DEF(discord) + name = "Discord" + wait = 3000 + init_order = INIT_ORDER_DISCORD + + /// People to save to notify file + var/list/notify_members = list() + /// Copy of previous list, so the SS doesnt have to fire if no new members have been added + var/list/notify_members_cache = list() + /// People to notify on roundstart + var/list/people_to_notify = list() + /// List that holds accounts to link, used in conjunction with TGS + var/list/account_link_cache = list() + /// list of people who tried to reverify, so they can only do it once per round as a shitty slowdown + var/list/reverify_cache = list() + var/notify_file = file("data/notify.json") + /// Is TGS enabled (If not we won't fire because otherwise this is useless) + var/enabled = 0 + +/datum/controller/subsystem/discord/Initialize(start_timeofday) + // Check for if we are using TGS, otherwise return and disables firing + if(world.TgsAvailable()) + enabled = 1 // Allows other procs to use this (Account linking, etc) + else + can_fire = 0 // We dont want excess firing + return ..() // Cancel + + try + people_to_notify = json_decode(file2text(notify_file)) + catch + pass() // The list can just stay as its default (blank). Pass() exists because it needs a catch + var/notifymsg = jointext(people_to_notify, ", ") + if(notifymsg) + notifymsg += ", a new round is starting!" + send2chat(trim(notifymsg), CONFIG_GET(string/chat_announce_new_game)) // Sends the message to the discord, using same config option as the roundstart notification + fdel(notify_file) // Deletes the file + return ..() + +/datum/controller/subsystem/discord/fire() + if(!enabled) + return // Dont do shit if its disabled + if(notify_members == notify_members_cache) + return // Dont re-write the file + // If we are all clear + write_notify_file() + +/datum/controller/subsystem/discord/Shutdown() + write_notify_file() // Guaranteed force-write on server close + +/datum/controller/subsystem/discord/proc/write_notify_file() + if(!enabled) // Dont do shit if its disabled + return + fdel(notify_file) // Deletes the file first to make sure it writes properly + WRITE_FILE(notify_file, json_encode(notify_members)) // Writes the file + notify_members_cache = notify_members // Updates the cache list + +// Returns ID from ckey +/datum/controller/subsystem/discord/proc/lookup_id(lookup_ckey) + var/datum/DBQuery/query_get_discord_id = SSdbcore.NewQuery( + "SELECT discord_id FROM [format_table_name("player")] WHERE ckey = :ckey", + list("ckey" = lookup_ckey) + ) + if(!query_get_discord_id.Execute()) + qdel(query_get_discord_id) + return + if(query_get_discord_id.NextRow()) + . = query_get_discord_id.item[1] + qdel(query_get_discord_id) + +// Returns ckey from ID +/datum/controller/subsystem/discord/proc/lookup_ckey(lookup_id) + var/datum/DBQuery/query_get_discord_ckey = SSdbcore.NewQuery( + "SELECT ckey FROM [format_table_name("player")] WHERE discord_id = :discord_id", + list("discord_id" = lookup_id) + ) + if(!query_get_discord_ckey.Execute()) + qdel(query_get_discord_ckey) + return + if(query_get_discord_ckey.NextRow()) + . = query_get_discord_ckey.item[1] + qdel(query_get_discord_ckey) + +// Finalises link +/datum/controller/subsystem/discord/proc/link_account(ckey) + var/datum/DBQuery/link_account = SSdbcore.NewQuery( + "UPDATE [format_table_name("player")] SET discord_id = :discord_id WHERE ckey = :ckey", + list("discord_id" = account_link_cache[ckey], "ckey" = ckey) + ) + link_account.Execute() + qdel(link_account) + account_link_cache -= ckey + +// Unlink account (Admin verb used) +/datum/controller/subsystem/discord/proc/unlink_account(ckey) + var/datum/DBQuery/unlink_account = SSdbcore.NewQuery( + "UPDATE [format_table_name("player")] SET discord_id = NULL WHERE ckey = :ckey", + list("ckey" = ckey) + ) + unlink_account.Execute() + qdel(unlink_account) + +// Clean up a discord account mention +/datum/controller/subsystem/discord/proc/id_clean(input) + var/regex/num_only = regex("\[^0-9\]", "g") + return num_only.Replace(input, "") + +/datum/controller/subsystem/discord/proc/grant_role(id) + // Ignore this shit if config isn't enabled for it + if(!CONFIG_GET(flag/enable_discord_autorole)) + return + + var/url = "https://discordapp.com/api/guilds/[CONFIG_GET(string/discord_guildid)]/members/[id]/roles/[CONFIG_GET(string/discord_roleid)]" + + // Make the request + var/datum/http_request/req = new() + req.prepare(RUSTG_HTTP_METHOD_PUT, url, "", list("Authorization" = "Bot [CONFIG_GET(string/discord_token)]")) + req.begin_async() + UNTIL(req.is_complete()) + var/datum/http_response/res = req.into_response() + + WRITE_LOG(GLOB.discord_api_log, "PUT [url] returned [res.status_code] [res.body]") diff --git a/code/controllers/subsystem/garbage.dm b/code/controllers/subsystem/garbage.dm index f8ca1e7eae4d..cc2fa71272dd 100644 --- a/code/controllers/subsystem/garbage.dm +++ b/code/controllers/subsystem/garbage.dm @@ -24,8 +24,7 @@ SUBSYSTEM_DEF(garbage) //Queue var/list/queues - - #ifdef TESTING + #ifdef LEGACY_REFERENCE_TRACKING var/list/reference_find_on_fail = list() #endif @@ -136,8 +135,8 @@ SUBSYSTEM_DEF(garbage) ++gcedlasttick ++totalgcs pass_counts[level]++ - #ifdef TESTING - reference_find_on_fail -= refID //It's deleted we don't care anymore. + #ifdef LEGACY_REFERENCE_TRACKING + reference_find_on_fail -= refID //It's deleted we don't care anymore. #endif if (MC_TICK_CHECK) return @@ -147,18 +146,32 @@ SUBSYSTEM_DEF(garbage) fail_counts[level]++ switch (level) if (GC_QUEUE_CHECK) - #ifdef TESTING + #ifdef REFERENCE_TRACKING + D.find_references() + #elif defined(LEGACY_REFERENCE_TRACKING) if(reference_find_on_fail[refID]) - D.find_references() + D.find_references_legacy() #ifdef GC_FAILURE_HARD_LOOKUP else - D.find_references() + D.find_references_legacy() #endif reference_find_on_fail -= refID #endif var/type = D.type var/datum/qdel_item/I = items[type] + #ifdef TESTING + log_world("## TESTING: GC: -- \ref[D] | [type] was unable to be GC'd --") + for(var/c in GLOB.admins) //Using testing() here would fill the logs with ADMIN_VV garbage + var/client/admin = c + if(!check_rights_for(admin, R_ADMIN)) + continue + to_chat(admin, "## TESTING: GC: -- [ADMIN_VV(D)] | [type] was unable to be GC'd --") testing("GC: -- \ref[src] | [type] was unable to be GC'd --") + #endif + #ifdef REFERENCE_TRACKING + GLOB.deletion_failures += D //It should no longer be bothered by the GC, manual deletion only. + continue + #endif I.failures++ if (GC_QUEUE_HARDDELETE) HardDelete(D) @@ -242,11 +255,6 @@ SUBSYSTEM_DEF(garbage) /datum/qdel_item/New(mytype) name = "[mytype]" -#ifdef TESTING -/proc/qdel_and_find_ref_if_fail(datum/D, force = FALSE) - SSgarbage.reference_find_on_fail[REF(D)] = TRUE - qdel(D, force) -#endif // Should be treated as a replacement for the 'del' keyword. // Datums passed to this will be given a chance to clean up references to allow the GC to collect them. @@ -301,16 +309,14 @@ SUBSYSTEM_DEF(garbage) SSgarbage.Queue(D, GC_QUEUE_HARDDELETE) if (QDEL_HINT_HARDDEL_NOW) //qdel should assume this object won't gc, and hard del it post haste. SSgarbage.HardDelete(D) - if (QDEL_HINT_FINDREFERENCE)//qdel will, if TESTING is enabled, display all references to this object, then queue the object for deletion. + #ifdef LEGACY_REFERENCE_TRACKING + if (QDEL_HINT_FINDREFERENCE) //qdel will, if LEGACY_REFERENCE_TRACKING is enabled, display all references to this object, then queue the object for deletion. SSgarbage.Queue(D) - #ifdef TESTING - D.find_references() - #endif + D.find_references_legacy() if (QDEL_HINT_IFFAIL_FINDREFERENCE) SSgarbage.Queue(D) - #ifdef TESTING SSgarbage.reference_find_on_fail[REF(D)] = TRUE - #endif + #endif else #ifdef TESTING if(!I.no_hint) @@ -320,116 +326,3 @@ SUBSYSTEM_DEF(garbage) SSgarbage.Queue(D) else if(D.gc_destroyed == GC_CURRENTLY_BEING_QDELETED) CRASH("[D.type] destroy proc was called multiple times, likely due to a qdel loop in the Destroy logic") - -#ifdef TESTING - -/datum/verb/find_refs() - set category = "Debug" - set name = "Find References" - set src in world - - find_references(FALSE) - -/datum/proc/find_references(skip_alert) - running_find_references = type - if(usr && usr.client) - if(usr.client.running_find_references) - testing("CANCELLED search for references to a [usr.client.running_find_references].") - usr.client.running_find_references = null - running_find_references = null - //restart the garbage collector - SSgarbage.can_fire = 1 - SSgarbage.next_fire = world.time + world.tick_lag - return - - if(!skip_alert) - if(alert("Running this will lock everything up for about 5 minutes. Would you like to begin the search?", "Find References", "Yes", "No") == "No") - running_find_references = null - return - - //this keeps the garbage collector from failing to collect objects being searched for in here - SSgarbage.can_fire = 0 - - if(usr && usr.client) - usr.client.running_find_references = type - - testing("Beginning search for references to a [type].") - last_find_references = world.time - - DoSearchVar(GLOB) //globals - for(var/datum/thing in world) //atoms (don't beleive it's lies) - DoSearchVar(thing, "World -> [thing]") - - for (var/datum/thing) //datums - DoSearchVar(thing, "World -> [thing]") - - for (var/client/thing) //clients - DoSearchVar(thing, "World -> [thing]") - - testing("Completed search for references to a [type].") - if(usr && usr.client) - usr.client.running_find_references = null - running_find_references = null - - //restart the garbage collector - SSgarbage.can_fire = 1 - SSgarbage.next_fire = world.time + world.tick_lag - -/datum/verb/qdel_then_find_references() - set category = "Debug" - set name = "qdel() then Find References" - set src in world - - qdel(src, TRUE) //Force. - if(!running_find_references) - find_references(TRUE) - -/datum/verb/qdel_then_if_fail_find_references() - set category = "Debug" - set name = "qdel() then Find References if GC failure" - set src in world - - qdel_and_find_ref_if_fail(src, TRUE) - -/datum/proc/DoSearchVar(X, Xname, recursive_limit = 64) - if(usr && usr.client && !usr.client.running_find_references) - return - if (!recursive_limit) - return - - if(istype(X, /datum)) - var/datum/D = X - if(D.last_find_references == last_find_references) - return - - D.last_find_references = last_find_references - var/list/L = D.vars - - for(var/varname in L) - if (varname == "vars") - continue - var/variable = L[varname] - - if(variable == src) - testing("Found [src.type] \ref[src] in [D.type]'s [varname] var. [Xname]") - - else if(islist(variable)) - DoSearchVar(variable, "[Xname] -> list", recursive_limit-1) - - else if(islist(X)) - var/normal = IS_NORMAL_LIST(X) - for(var/I in X) - if (I == src) - testing("Found [src.type] \ref[src] in list [Xname].") - - else if (I && !isnum(I) && normal && X[I] == src) - testing("Found [src.type] \ref[src] in list [Xname]\[[I]\]") - - else if (islist(I)) - DoSearchVar(I, "[Xname] -> list", recursive_limit-1) - -#ifndef FIND_REF_NO_CHECK_TICK - CHECK_TICK -#endif - -#endif diff --git a/code/controllers/subsystem/mapping.dm b/code/controllers/subsystem/mapping.dm index 02f23c4b8399..564cb2329453 100644 --- a/code/controllers/subsystem/mapping.dm +++ b/code/controllers/subsystem/mapping.dm @@ -235,7 +235,9 @@ SUBSYSTEM_DEF(mapping) LoadGroup(FailedZs, "Station", config.map_path, config.map_file, config.traits, ZTRAITS_STATION) 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/DBQuery/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/moods.dm b/code/controllers/subsystem/moods.dm index d1e58c7452d0..f6b6ffcb4045 100644 --- a/code/controllers/subsystem/moods.dm +++ b/code/controllers/subsystem/moods.dm @@ -1,4 +1,4 @@ -PROCESSING_SUBSYSTEM_DEF(mood) - name = "Mood" - flags = SS_NO_INIT | SS_BACKGROUND - priority = 20 +PROCESSING_SUBSYSTEM_DEF(mood) + name = "Mood" + flags = SS_NO_INIT | SS_BACKGROUND + priority = 20 diff --git a/code/controllers/subsystem/nightshift.dm b/code/controllers/subsystem/nightshift.dm index 68116483ad49..ca9979076a8b 100644 --- a/code/controllers/subsystem/nightshift.dm +++ b/code/controllers/subsystem/nightshift.dm @@ -1,55 +1,55 @@ -SUBSYSTEM_DEF(nightshift) - name = "Night Shift" - wait = 600 - flags = SS_NO_TICK_CHECK - - var/nightshift_active = FALSE - var/nightshift_start_time = 702000 //7:30 PM, station time - var/nightshift_end_time = 270000 //7:30 AM, station time - var/nightshift_first_check = 30 SECONDS - - var/high_security_mode = FALSE - -/datum/controller/subsystem/nightshift/Initialize() - if(!CONFIG_GET(flag/enable_night_shifts)) - can_fire = FALSE - return ..() - -/datum/controller/subsystem/nightshift/fire(resumed = FALSE) - if(world.time - SSticker.round_start_time < nightshift_first_check) - return - check_nightshift() - -/datum/controller/subsystem/nightshift/proc/announce(message) - priority_announce(message, sound='sound/misc/notice2.ogg', sender_override="Automated Lighting System Announcement") - -/datum/controller/subsystem/nightshift/proc/check_nightshift() - var/emergency = GLOB.security_level >= SEC_LEVEL_RED - var/announcing = TRUE - var/time = station_time() - var/night_time = (time < nightshift_end_time) || (time > nightshift_start_time) - if(high_security_mode != emergency) - high_security_mode = emergency - if(night_time) - announcing = FALSE - if(!emergency) - announce("Restoring night lighting configuration to normal operation.") - else - announce("Disabling night lighting: Station is in a state of emergency.") - if(emergency) - night_time = FALSE - if(nightshift_active != night_time) - update_nightshift(night_time, announcing) - -/datum/controller/subsystem/nightshift/proc/update_nightshift(active, announce = TRUE) - nightshift_active = active - if(announce) - if (active) - announce("Good evening, crew. To reduce power consumption and stimulate the circadian rhythms of some species, all of the lights aboard the station have been dimmed for the night.") - else - announce("Good morning, crew. As it is now day time, all of the lights aboard the station have been restored to their former brightness.") - for(var/A in GLOB.apcs_list) - var/obj/machinery/power/apc/APC = A - if (APC.area && (APC.area.type in GLOB.the_station_areas)) - APC.set_nightshift(active) - CHECK_TICK +SUBSYSTEM_DEF(nightshift) + name = "Night Shift" + wait = 600 + flags = SS_NO_TICK_CHECK + + var/nightshift_active = FALSE + var/nightshift_start_time = 702000 //7:30 PM, station time + var/nightshift_end_time = 270000 //7:30 AM, station time + var/nightshift_first_check = 30 SECONDS + + var/high_security_mode = FALSE + +/datum/controller/subsystem/nightshift/Initialize() + if(!CONFIG_GET(flag/enable_night_shifts)) + can_fire = FALSE + return ..() + +/datum/controller/subsystem/nightshift/fire(resumed = FALSE) + if(world.time - SSticker.round_start_time < nightshift_first_check) + return + check_nightshift() + +/datum/controller/subsystem/nightshift/proc/announce(message) + priority_announce(message, sound='sound/misc/notice2.ogg', sender_override="Automated Lighting System Announcement") + +/datum/controller/subsystem/nightshift/proc/check_nightshift() + var/emergency = GLOB.security_level >= SEC_LEVEL_RED + var/announcing = TRUE + var/time = station_time() + var/night_time = (time < nightshift_end_time) || (time > nightshift_start_time) + if(high_security_mode != emergency) + high_security_mode = emergency + if(night_time) + announcing = FALSE + if(!emergency) + announce("Restoring night lighting configuration to normal operation.") + else + announce("Disabling night lighting: Station is in a state of emergency.") + if(emergency) + night_time = FALSE + if(nightshift_active != night_time) + update_nightshift(night_time, announcing) + +/datum/controller/subsystem/nightshift/proc/update_nightshift(active, announce = TRUE) + nightshift_active = active + if(announce) + if (active) + announce("Good evening, crew. To reduce power consumption and stimulate the circadian rhythms of some species, all of the lights aboard the station have been dimmed for the night.") + else + announce("Good morning, crew. As it is now day time, all of the lights aboard the station have been restored to their former brightness.") + for(var/A in GLOB.apcs_list) + var/obj/machinery/power/apc/APC = A + if (APC.area && (APC.area.type in GLOB.the_station_areas)) + APC.set_nightshift(active) + CHECK_TICK diff --git a/code/controllers/subsystem/npcpool.dm b/code/controllers/subsystem/npcpool.dm index d93f1f240765..c69154a1b72d 100644 --- a/code/controllers/subsystem/npcpool.dm +++ b/code/controllers/subsystem/npcpool.dm @@ -1,34 +1,34 @@ -SUBSYSTEM_DEF(npcpool) - name = "NPC Pool" - flags = SS_POST_FIRE_TIMING|SS_NO_INIT|SS_BACKGROUND - priority = FIRE_PRIORITY_NPC - runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME - - var/list/currentrun = list() - -/datum/controller/subsystem/npcpool/stat_entry() - var/list/activelist = GLOB.simple_animals[AI_ON] - ..("NPCS:[activelist.len]") - -/datum/controller/subsystem/npcpool/fire(resumed = FALSE) - - if (!resumed) - var/list/activelist = GLOB.simple_animals[AI_ON] - src.currentrun = activelist.Copy() - - //cache for sanic speed (lists are references anyways) - var/list/currentrun = src.currentrun - - while(currentrun.len) - var/mob/living/simple_animal/SA = currentrun[currentrun.len] - --currentrun.len - - if(!SA.ckey && !SA.notransform) - if(SA.stat != DEAD) - SA.handle_automated_movement() - if(SA.stat != DEAD) - SA.handle_automated_action() - if(SA.stat != DEAD) - SA.handle_automated_speech() - if (MC_TICK_CHECK) - return +SUBSYSTEM_DEF(npcpool) + name = "NPC Pool" + flags = SS_POST_FIRE_TIMING|SS_NO_INIT|SS_BACKGROUND + priority = FIRE_PRIORITY_NPC + runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME + + var/list/currentrun = list() + +/datum/controller/subsystem/npcpool/stat_entry() + var/list/activelist = GLOB.simple_animals[AI_ON] + ..("NPCS:[activelist.len]") + +/datum/controller/subsystem/npcpool/fire(resumed = FALSE) + + if (!resumed) + var/list/activelist = GLOB.simple_animals[AI_ON] + src.currentrun = activelist.Copy() + + //cache for sanic speed (lists are references anyways) + var/list/currentrun = src.currentrun + + while(currentrun.len) + var/mob/living/simple_animal/SA = currentrun[currentrun.len] + --currentrun.len + + if(!SA.ckey && !SA.notransform) + if(SA.stat != DEAD) + SA.handle_automated_movement() + if(SA.stat != DEAD) + SA.handle_automated_action() + if(SA.stat != DEAD) + SA.handle_automated_speech() + if (MC_TICK_CHECK) + return diff --git a/code/controllers/subsystem/pathfinder.dm b/code/controllers/subsystem/pathfinder.dm index fee93d14b288..8e1cf946ae19 100644 --- a/code/controllers/subsystem/pathfinder.dm +++ b/code/controllers/subsystem/pathfinder.dm @@ -1,48 +1,48 @@ -SUBSYSTEM_DEF(pathfinder) - name = "Pathfinder" - init_order = INIT_ORDER_PATH - flags = SS_NO_FIRE - var/datum/flowcache/mobs - var/datum/flowcache/circuits - var/static/space_type_cache - -/datum/controller/subsystem/pathfinder/Initialize() - space_type_cache = typecacheof(/turf/open/space) - mobs = new(10) - circuits = new(3) - return ..() - -/datum/flowcache - var/lcount - var/run - var/free - var/list/flow - -/datum/flowcache/New(var/n) - . = ..() - lcount = n - run = 0 - free = 1 - flow = new/list(lcount) - -/datum/flowcache/proc/getfree(atom/M) - if(run < lcount) - run += 1 - while(flow[free]) - CHECK_TICK - free = (free % lcount) + 1 - var/t = addtimer(CALLBACK(src, /datum/flowcache.proc/toolong, free), 150, TIMER_STOPPABLE) - flow[free] = t - flow[t] = M - return free - else - return 0 - -/datum/flowcache/proc/toolong(l) - log_game("Pathfinder route took longer than 150 ticks, src bot [flow[flow[l]]]") - found(l) - -/datum/flowcache/proc/found(l) - deltimer(flow[l]) - flow[l] = null - run -= 1 +SUBSYSTEM_DEF(pathfinder) + name = "Pathfinder" + init_order = INIT_ORDER_PATH + flags = SS_NO_FIRE + var/datum/flowcache/mobs + var/datum/flowcache/circuits + var/static/space_type_cache + +/datum/controller/subsystem/pathfinder/Initialize() + space_type_cache = typecacheof(/turf/open/space) + mobs = new(10) + circuits = new(3) + return ..() + +/datum/flowcache + var/lcount + var/run + var/free + var/list/flow + +/datum/flowcache/New(var/n) + . = ..() + lcount = n + run = 0 + free = 1 + flow = new/list(lcount) + +/datum/flowcache/proc/getfree(atom/M) + if(run < lcount) + run += 1 + while(flow[free]) + CHECK_TICK + free = (free % lcount) + 1 + var/t = addtimer(CALLBACK(src, /datum/flowcache.proc/toolong, free), 150, TIMER_STOPPABLE) + flow[free] = t + flow[t] = M + return free + else + return 0 + +/datum/flowcache/proc/toolong(l) + log_game("Pathfinder route took longer than 150 ticks, src bot [flow[flow[l]]]") + found(l) + +/datum/flowcache/proc/found(l) + deltimer(flow[l]) + flow[l] = null + run -= 1 diff --git a/code/controllers/subsystem/processing/networks.dm b/code/controllers/subsystem/processing/networks.dm index 719e97d1eed3..f7f16538a629 100644 --- a/code/controllers/subsystem/processing/networks.dm +++ b/code/controllers/subsystem/processing/networks.dm @@ -1,51 +1,51 @@ -PROCESSING_SUBSYSTEM_DEF(networks) - name = "Networks" - priority = FIRE_PRIORITY_NETWORKS - wait = 1 - stat_tag = "NET" - flags = SS_KEEP_TIMING - init_order = INIT_ORDER_NETWORKS - var/datum/ntnet/station/station_network - var/assignment_hardware_id = HID_RESTRICTED_END - var/list/networks_by_id = list() //id = network - var/list/interfaces_by_id = list() //hardware id = component interface - var/resolve_collisions = TRUE - -/datum/controller/subsystem/processing/networks/Initialize() - station_network = new - station_network.register_map_supremecy() - . = ..() - -/datum/controller/subsystem/processing/networks/proc/register_network(datum/ntnet/network) - if(!networks_by_id[network.network_id]) - networks_by_id[network.network_id] = network - return TRUE - return FALSE - -/datum/controller/subsystem/processing/networks/proc/unregister_network(datum/ntnet/network) - networks_by_id -= network.network_id - return TRUE - -/datum/controller/subsystem/processing/networks/proc/register_interface(datum/component/ntnet_interface/D) - if(!interfaces_by_id[D.hardware_id]) - interfaces_by_id[D.hardware_id] = D - return TRUE - return FALSE - -/datum/controller/subsystem/processing/networks/proc/unregister_interface(datum/component/ntnet_interface/D) - interfaces_by_id -= D.hardware_id - return TRUE - -/datum/controller/subsystem/processing/networks/proc/get_next_HID() - var/string = "[num2text(assignment_hardware_id++, 12)]" - return make_address(string) - -/datum/controller/subsystem/processing/networks/proc/make_address(string) - if(!string) - return resolve_collisions? make_address("[num2text(rand(HID_RESTRICTED_END, 999999999), 12)]"):null - var/hex = md5(string) - if(!hex) - return //errored - . = "[copytext_char(hex, 1, 9)]" //16 ^ 8 possibilities I think. - if(interfaces_by_id[.]) - return resolve_collisions? make_address("[num2text(rand(HID_RESTRICTED_END, 999999999), 12)]"):null +PROCESSING_SUBSYSTEM_DEF(networks) + name = "Networks" + priority = FIRE_PRIORITY_NETWORKS + wait = 1 + stat_tag = "NET" + flags = SS_KEEP_TIMING + init_order = INIT_ORDER_NETWORKS + var/datum/ntnet/station/station_network + var/assignment_hardware_id = HID_RESTRICTED_END + var/list/networks_by_id = list() //id = network + var/list/interfaces_by_id = list() //hardware id = component interface + var/resolve_collisions = TRUE + +/datum/controller/subsystem/processing/networks/Initialize() + station_network = new + station_network.register_map_supremecy() + . = ..() + +/datum/controller/subsystem/processing/networks/proc/register_network(datum/ntnet/network) + if(!networks_by_id[network.network_id]) + networks_by_id[network.network_id] = network + return TRUE + return FALSE + +/datum/controller/subsystem/processing/networks/proc/unregister_network(datum/ntnet/network) + networks_by_id -= network.network_id + return TRUE + +/datum/controller/subsystem/processing/networks/proc/register_interface(datum/component/ntnet_interface/D) + if(!interfaces_by_id[D.hardware_id]) + interfaces_by_id[D.hardware_id] = D + return TRUE + return FALSE + +/datum/controller/subsystem/processing/networks/proc/unregister_interface(datum/component/ntnet_interface/D) + interfaces_by_id -= D.hardware_id + return TRUE + +/datum/controller/subsystem/processing/networks/proc/get_next_HID() + var/string = "[num2text(assignment_hardware_id++, 12)]" + return make_address(string) + +/datum/controller/subsystem/processing/networks/proc/make_address(string) + if(!string) + return resolve_collisions? make_address("[num2text(rand(HID_RESTRICTED_END, 999999999), 12)]"):null + var/hex = md5(string) + if(!hex) + return //errored + . = "[copytext_char(hex, 1, 9)]" //16 ^ 8 possibilities I think. + if(interfaces_by_id[.]) + return resolve_collisions? make_address("[num2text(rand(HID_RESTRICTED_END, 999999999), 12)]"):null diff --git a/code/controllers/subsystem/processing/projectiles.dm b/code/controllers/subsystem/processing/projectiles.dm index 479298e4bbd1..ba62f0cca65f 100644 --- a/code/controllers/subsystem/processing/projectiles.dm +++ b/code/controllers/subsystem/processing/projectiles.dm @@ -1,23 +1,23 @@ -PROCESSING_SUBSYSTEM_DEF(projectiles) - name = "Projectiles" - wait = 1 - stat_tag = "PP" - flags = SS_NO_INIT|SS_TICKER - var/global_max_tick_moves = 10 - var/global_pixel_speed = 2 - var/global_iterations_per_move = 16 - -/datum/controller/subsystem/processing/projectiles/proc/set_pixel_speed(new_speed) - global_pixel_speed = new_speed - for(var/i in processing) - var/obj/projectile/P = i - if(istype(P)) //there's non projectiles on this too. - P.set_pixel_speed(new_speed) - -/datum/controller/subsystem/processing/projectiles/vv_edit_var(var_name, var_value) - switch(var_name) - if(NAMEOF(src, global_pixel_speed)) - set_pixel_speed(var_value) - return TRUE - else - return ..() +PROCESSING_SUBSYSTEM_DEF(projectiles) + name = "Projectiles" + wait = 1 + stat_tag = "PP" + flags = SS_NO_INIT|SS_TICKER + var/global_max_tick_moves = 10 + var/global_pixel_speed = 2 + var/global_iterations_per_move = 16 + +/datum/controller/subsystem/processing/projectiles/proc/set_pixel_speed(new_speed) + global_pixel_speed = new_speed + for(var/i in processing) + var/obj/projectile/P = i + if(istype(P)) //there's non projectiles on this too. + P.set_pixel_speed(new_speed) + +/datum/controller/subsystem/processing/projectiles/vv_edit_var(var_name, var_value) + switch(var_name) + if(NAMEOF(src, global_pixel_speed)) + set_pixel_speed(var_value) + return TRUE + else + return ..() diff --git a/code/controllers/subsystem/processing/wet_floors.dm b/code/controllers/subsystem/processing/wet_floors.dm index dfbc1ac8e474..4bda8b545369 100644 --- a/code/controllers/subsystem/processing/wet_floors.dm +++ b/code/controllers/subsystem/processing/wet_floors.dm @@ -1,7 +1,7 @@ -PROCESSING_SUBSYSTEM_DEF(wet_floors) - name = "Wet floors" - priority = FIRE_PRIORITY_WET_FLOORS - wait = 10 - stat_tag = "WFP" //Used for logging - var/temperature_coeff = 2 - var/time_ratio = 1.5 SECONDS +PROCESSING_SUBSYSTEM_DEF(wet_floors) + name = "Wet floors" + priority = FIRE_PRIORITY_WET_FLOORS + wait = 10 + stat_tag = "WFP" //Used for logging + var/temperature_coeff = 2 + var/time_ratio = 1.5 SECONDS diff --git a/code/controllers/subsystem/radiation.dm b/code/controllers/subsystem/radiation.dm index 2f7d27aca99f..28e3192041ec 100644 --- a/code/controllers/subsystem/radiation.dm +++ b/code/controllers/subsystem/radiation.dm @@ -5,7 +5,7 @@ PROCESSING_SUBSYSTEM_DEF(radiation) var/list/warned_atoms = list() /datum/controller/subsystem/processing/radiation/proc/warn(datum/component/radioactive/contamination) - if(!contamination || QDELETED(contamination)) + if(!contamination || !contamination.parent || QDELETED(contamination)) return var/ref = REF(contamination.parent) if(warned_atoms[ref]) diff --git a/code/controllers/subsystem/research.dm b/code/controllers/subsystem/research.dm index c7688e01686c..16943bb2e319 100644 --- a/code/controllers/subsystem/research.dm +++ b/code/controllers/subsystem/research.dm @@ -1,280 +1,280 @@ - -SUBSYSTEM_DEF(research) - name = "Research" - priority = FIRE_PRIORITY_RESEARCH - wait = 10 - init_order = INIT_ORDER_RESEARCH - //TECHWEB STATIC - var/list/techweb_nodes = list() //associative id = node datum - var/list/techweb_designs = list() //associative id = node datum - var/list/datum/techweb/techwebs = list() - var/datum/techweb/science/science_tech - var/datum/techweb/admin/admin_tech - var/datum/techweb_node/error_node/error_node //These two are what you get if a node/design is deleted and somehow still stored in a console. - var/datum/design/error_design/error_design - - //ERROR LOGGING - var/list/invalid_design_ids = list() //associative id = number of times - var/list/invalid_node_ids = list() //associative id = number of times - var/list/invalid_node_boost = list() //associative id = error message - - var/list/obj/machinery/rnd/server/servers = list() - - var/list/techweb_nodes_starting = list() //associative id = TRUE - var/list/techweb_categories = list() //category name = list(node.id = TRUE) - var/list/techweb_boost_items = list() //associative double-layer path = list(id = list(point_type = point_discount)) - var/list/techweb_nodes_hidden = list() //Node ids that should be hidden by default. - var/list/techweb_nodes_experimental = list() //Node ids that are exclusive to the BEPIS. - var/list/techweb_point_items = list( //path = list(point type = value) - /obj/item/assembly/signaler/anomaly = list(TECHWEB_POINT_TYPE_GENERIC = 10000) - ) - var/list/errored_datums = list() - var/list/point_types = list() //typecache style type = TRUE list - var/list/slime_already_researched = list() //Slime cores that have already been researched - //---------------------------------------------- - var/list/single_server_income = list(TECHWEB_POINT_TYPE_GENERIC = 52.3) - var/multiserver_calculation = FALSE - var/last_income - //^^^^^^^^ ALL OF THESE ARE PER SECOND! ^^^^^^^^ - - //Aiming for 1.5 hours to max R&D - //[88nodes * 5000points/node] / [1.5hr * 90min/hr * 60s/min] - //Around 450000 points max??? - -/datum/controller/subsystem/research/Initialize() - point_types = TECHWEB_POINT_TYPE_LIST_ASSOCIATIVE_NAMES - initialize_all_techweb_designs() - initialize_all_techweb_nodes() - science_tech = new /datum/techweb/science - admin_tech = new /datum/techweb/admin - autosort_categories() - error_design = new - error_node = new - return ..() - -/datum/controller/subsystem/research/fire() - var/list/bitcoins = list() - if(multiserver_calculation) - var/eff = calculate_server_coefficient() - for(var/obj/machinery/rnd/server/miner in servers) - var/list/result = (miner.mine()) //SLAVE AWAY, SLAVE. - for(var/i in result) - result[i] *= eff - bitcoins[i] = bitcoins[i]? bitcoins[i] + result[i] : result[i] - else - for(var/obj/machinery/rnd/server/miner in servers) - if(miner.working) - bitcoins = single_server_income.Copy() - break //Just need one to work. - if (!isnull(last_income)) - var/income_time_difference = world.time - last_income - science_tech.last_bitcoins = bitcoins // Doesn't take tick drift into account - for(var/i in bitcoins) - bitcoins[i] *= income_time_difference / 10 - science_tech.add_point_list(bitcoins) - last_income = world.time - -/datum/controller/subsystem/research/proc/calculate_server_coefficient() //Diminishing returns. - var/amt = servers.len - if(!amt) - return 0 - var/coeff = 100 - coeff = sqrt(coeff / amt) - return coeff - -/datum/controller/subsystem/research/proc/autosort_categories() - for(var/i in techweb_nodes) - var/datum/techweb_node/I = techweb_nodes[i] - if(techweb_categories[I.category]) - techweb_categories[I.category][I.id] = TRUE - else - techweb_categories[I.category] = list(I.id = TRUE) - -/datum/controller/subsystem/research/proc/techweb_node_by_id(id) - return techweb_nodes[id] || error_node - -/datum/controller/subsystem/research/proc/techweb_design_by_id(id) - return techweb_designs[id] || error_design - -/datum/controller/subsystem/research/proc/on_design_deletion(datum/design/D) - for(var/i in techweb_nodes) - var/datum/techweb_node/TN = techwebs[i] - TN.on_design_deletion(TN) - for(var/i in techwebs) - var/datum/techweb/T = i - T.recalculate_nodes(TRUE) - -/datum/controller/subsystem/research/proc/on_node_deletion(datum/techweb_node/TN) - for(var/i in techweb_nodes) - var/datum/techweb_node/TN2 = techwebs[i] - TN2.on_node_deletion(TN) - for(var/i in techwebs) - var/datum/techweb/T = i - T.recalculate_nodes(TRUE) - -/datum/controller/subsystem/research/proc/initialize_all_techweb_nodes(clearall = FALSE) - if(islist(techweb_nodes) && clearall) - QDEL_LIST(techweb_nodes) - if(islist(techweb_nodes_starting && clearall)) - techweb_nodes_starting.Cut() - var/list/returned = list() - for(var/path in subtypesof(/datum/techweb_node)) - var/datum/techweb_node/TN = path - if(isnull(initial(TN.id))) - continue - TN = new path - if(returned[initial(TN.id)]) - stack_trace("WARNING: Techweb node ID clash with ID [initial(TN.id)] detected! Path: [path]") - errored_datums[TN] = initial(TN.id) - continue - returned[initial(TN.id)] = TN - if(TN.starting_node) - techweb_nodes_starting[TN.id] = TRUE - for(var/id in techweb_nodes) - var/datum/techweb_node/TN = techweb_nodes[id] - TN.Initialize() - techweb_nodes = returned - if (!verify_techweb_nodes()) //Verify all nodes have ids and such. - stack_trace("Invalid techweb nodes detected") - calculate_techweb_nodes() - calculate_techweb_boost_list() - if (!verify_techweb_nodes()) //Verify nodes and designs have been crosslinked properly. - CRASH("Invalid techweb nodes detected") - -/datum/controller/subsystem/research/proc/initialize_all_techweb_designs(clearall = FALSE) - if(islist(techweb_designs) && clearall) - QDEL_LIST(techweb_designs) - var/list/returned = list() - for(var/path in subtypesof(/datum/design)) - var/datum/design/DN = path - if(isnull(initial(DN.id))) - stack_trace("WARNING: Design with null ID detected. Build path: [initial(DN.build_path)]") - continue - else if(initial(DN.id) == DESIGN_ID_IGNORE) - continue - DN = new path - if(returned[initial(DN.id)]) - stack_trace("WARNING: Design ID clash with ID [initial(DN.id)] detected! Path: [path]") - errored_datums[DN] = initial(DN.id) - continue - DN.InitializeMaterials() //Initialize the materials in the design - returned[initial(DN.id)] = DN - techweb_designs = returned - verify_techweb_designs() - - -/datum/controller/subsystem/research/proc/verify_techweb_nodes() - . = TRUE - for(var/n in techweb_nodes) - var/datum/techweb_node/N = techweb_nodes[n] - if(!istype(N)) - WARNING("Invalid research node with ID [n] detected and removed.") - techweb_nodes -= n - research_node_id_error(n) - . = FALSE - for(var/p in N.prereq_ids) - var/datum/techweb_node/P = techweb_nodes[p] - if(!istype(P)) - WARNING("Invalid research prerequisite node with ID [p] detected in node [N.display_name]\[[N.id]\] removed.") - N.prereq_ids -= p - research_node_id_error(p) - . = FALSE - for(var/d in N.design_ids) - var/datum/design/D = techweb_designs[d] - if(!istype(D)) - WARNING("Invalid research design with ID [d] detected in node [N.display_name]\[[N.id]\] removed.") - N.design_ids -= d - design_id_error(d) - . = FALSE - for(var/u in N.unlock_ids) - var/datum/techweb_node/U = techweb_nodes[u] - if(!istype(U)) - WARNING("Invalid research unlock node with ID [u] detected in node [N.display_name]\[[N.id]\] removed.") - N.unlock_ids -= u - research_node_id_error(u) - . = FALSE - for(var/p in N.boost_item_paths) - if(!ispath(p)) - N.boost_item_paths -= p - WARNING("[p] is not a valid path.") - node_boost_error(N.id, "[p] is not a valid path.") - . = FALSE - var/list/points = N.boost_item_paths[p] - if(islist(points)) - for(var/i in points) - if(!isnum(points[i])) - WARNING("[points[i]] is not a valid number.") - node_boost_error(N.id, "[points[i]] is not a valid number.") - . = FALSE - else if(!point_types[i]) - WARNING("[i] is not a valid point type.") - node_boost_error(N.id, "[i] is not a valid point type.") - . = FALSE - else if(!isnull(points)) - N.boost_item_paths -= p - node_boost_error(N.id, "No valid list.") - WARNING("No valid list.") - . = FALSE - CHECK_TICK - -/datum/controller/subsystem/research/proc/verify_techweb_designs() - for(var/d in techweb_designs) - var/datum/design/D = techweb_designs[d] - if(!istype(D)) - stack_trace("WARNING: Invalid research design with ID [d] detected and removed.") - techweb_designs -= d - CHECK_TICK - -/datum/controller/subsystem/research/proc/research_node_id_error(id) - if(invalid_node_ids[id]) - invalid_node_ids[id]++ - else - invalid_node_ids[id] = 1 - -/datum/controller/subsystem/research/proc/design_id_error(id) - if(invalid_design_ids[id]) - invalid_design_ids[id]++ - else - invalid_design_ids[id] = 1 - -/datum/controller/subsystem/research/proc/calculate_techweb_nodes() - for(var/design_id in techweb_designs) - var/datum/design/D = techweb_designs[design_id] - D.unlocked_by.Cut() - for(var/node_id in techweb_nodes) - var/datum/techweb_node/node = techweb_nodes[node_id] - node.unlock_ids = list() - for(var/i in node.design_ids) - var/datum/design/D = techweb_designs[i] - node.design_ids[i] = TRUE - D.unlocked_by += node.id - if(node.hidden) - techweb_nodes_hidden[node.id] = TRUE - if(node.experimental) - techweb_nodes_experimental[node.id] = TRUE - CHECK_TICK - generate_techweb_unlock_linking() - -/datum/controller/subsystem/research/proc/generate_techweb_unlock_linking() - for(var/node_id in techweb_nodes) //Clear all unlock links to avoid duplication. - var/datum/techweb_node/node = techweb_nodes[node_id] - node.unlock_ids = list() - for(var/node_id in techweb_nodes) - var/datum/techweb_node/node = techweb_nodes[node_id] - for(var/prereq_id in node.prereq_ids) - var/datum/techweb_node/prereq_node = techweb_node_by_id(prereq_id) - prereq_node.unlock_ids[node.id] = node - -/datum/controller/subsystem/research/proc/calculate_techweb_boost_list(clearall = FALSE) - if(clearall) - techweb_boost_items = list() - for(var/node_id in techweb_nodes) - var/datum/techweb_node/node = techweb_nodes[node_id] - for(var/path in node.boost_item_paths) - if(!ispath(path)) - continue - if(length(techweb_boost_items[path])) - techweb_boost_items[path][node.id] = node.boost_item_paths[path] - else - techweb_boost_items[path] = list(node.id = node.boost_item_paths[path]) - CHECK_TICK + +SUBSYSTEM_DEF(research) + name = "Research" + priority = FIRE_PRIORITY_RESEARCH + wait = 10 + init_order = INIT_ORDER_RESEARCH + //TECHWEB STATIC + var/list/techweb_nodes = list() //associative id = node datum + var/list/techweb_designs = list() //associative id = node datum + var/list/datum/techweb/techwebs = list() + var/datum/techweb/science/science_tech + var/datum/techweb/admin/admin_tech + var/datum/techweb_node/error_node/error_node //These two are what you get if a node/design is deleted and somehow still stored in a console. + var/datum/design/error_design/error_design + + //ERROR LOGGING + var/list/invalid_design_ids = list() //associative id = number of times + var/list/invalid_node_ids = list() //associative id = number of times + var/list/invalid_node_boost = list() //associative id = error message + + var/list/obj/machinery/rnd/server/servers = list() + + var/list/techweb_nodes_starting = list() //associative id = TRUE + var/list/techweb_categories = list() //category name = list(node.id = TRUE) + var/list/techweb_boost_items = list() //associative double-layer path = list(id = list(point_type = point_discount)) + var/list/techweb_nodes_hidden = list() //Node ids that should be hidden by default. + var/list/techweb_nodes_experimental = list() //Node ids that are exclusive to the BEPIS. + var/list/techweb_point_items = list( //path = list(point type = value) + /obj/item/assembly/signaler/anomaly = list(TECHWEB_POINT_TYPE_GENERIC = 10000) + ) + var/list/errored_datums = list() + var/list/point_types = list() //typecache style type = TRUE list + var/list/slime_already_researched = list() //Slime cores that have already been researched + //---------------------------------------------- + var/list/single_server_income = list(TECHWEB_POINT_TYPE_GENERIC = 52.3) + var/multiserver_calculation = FALSE + var/last_income + //^^^^^^^^ ALL OF THESE ARE PER SECOND! ^^^^^^^^ + + //Aiming for 1.5 hours to max R&D + //[88nodes * 5000points/node] / [1.5hr * 90min/hr * 60s/min] + //Around 450000 points max??? + +/datum/controller/subsystem/research/Initialize() + point_types = TECHWEB_POINT_TYPE_LIST_ASSOCIATIVE_NAMES + initialize_all_techweb_designs() + initialize_all_techweb_nodes() + science_tech = new /datum/techweb/science + admin_tech = new /datum/techweb/admin + autosort_categories() + error_design = new + error_node = new + return ..() + +/datum/controller/subsystem/research/fire() + var/list/bitcoins = list() + if(multiserver_calculation) + var/eff = calculate_server_coefficient() + for(var/obj/machinery/rnd/server/miner in servers) + var/list/result = (miner.mine()) //SLAVE AWAY, SLAVE. + for(var/i in result) + result[i] *= eff + bitcoins[i] = bitcoins[i]? bitcoins[i] + result[i] : result[i] + else + for(var/obj/machinery/rnd/server/miner in servers) + if(miner.working) + bitcoins = single_server_income.Copy() + break //Just need one to work. + if (!isnull(last_income)) + var/income_time_difference = world.time - last_income + science_tech.last_bitcoins = bitcoins // Doesn't take tick drift into account + for(var/i in bitcoins) + bitcoins[i] *= income_time_difference / 10 + science_tech.add_point_list(bitcoins) + last_income = world.time + +/datum/controller/subsystem/research/proc/calculate_server_coefficient() //Diminishing returns. + var/amt = servers.len + if(!amt) + return 0 + var/coeff = 100 + coeff = sqrt(coeff / amt) + return coeff + +/datum/controller/subsystem/research/proc/autosort_categories() + for(var/i in techweb_nodes) + var/datum/techweb_node/I = techweb_nodes[i] + if(techweb_categories[I.category]) + techweb_categories[I.category][I.id] = TRUE + else + techweb_categories[I.category] = list(I.id = TRUE) + +/datum/controller/subsystem/research/proc/techweb_node_by_id(id) + return techweb_nodes[id] || error_node + +/datum/controller/subsystem/research/proc/techweb_design_by_id(id) + return techweb_designs[id] || error_design + +/datum/controller/subsystem/research/proc/on_design_deletion(datum/design/D) + for(var/i in techweb_nodes) + var/datum/techweb_node/TN = techwebs[i] + TN.on_design_deletion(TN) + for(var/i in techwebs) + var/datum/techweb/T = i + T.recalculate_nodes(TRUE) + +/datum/controller/subsystem/research/proc/on_node_deletion(datum/techweb_node/TN) + for(var/i in techweb_nodes) + var/datum/techweb_node/TN2 = techwebs[i] + TN2.on_node_deletion(TN) + for(var/i in techwebs) + var/datum/techweb/T = i + T.recalculate_nodes(TRUE) + +/datum/controller/subsystem/research/proc/initialize_all_techweb_nodes(clearall = FALSE) + if(islist(techweb_nodes) && clearall) + QDEL_LIST(techweb_nodes) + if(islist(techweb_nodes_starting && clearall)) + techweb_nodes_starting.Cut() + var/list/returned = list() + for(var/path in subtypesof(/datum/techweb_node)) + var/datum/techweb_node/TN = path + if(isnull(initial(TN.id))) + continue + TN = new path + if(returned[initial(TN.id)]) + stack_trace("WARNING: Techweb node ID clash with ID [initial(TN.id)] detected! Path: [path]") + errored_datums[TN] = initial(TN.id) + continue + returned[initial(TN.id)] = TN + if(TN.starting_node) + techweb_nodes_starting[TN.id] = TRUE + for(var/id in techweb_nodes) + var/datum/techweb_node/TN = techweb_nodes[id] + TN.Initialize() + techweb_nodes = returned + if (!verify_techweb_nodes()) //Verify all nodes have ids and such. + stack_trace("Invalid techweb nodes detected") + calculate_techweb_nodes() + calculate_techweb_boost_list() + if (!verify_techweb_nodes()) //Verify nodes and designs have been crosslinked properly. + CRASH("Invalid techweb nodes detected") + +/datum/controller/subsystem/research/proc/initialize_all_techweb_designs(clearall = FALSE) + if(islist(techweb_designs) && clearall) + QDEL_LIST(techweb_designs) + var/list/returned = list() + for(var/path in subtypesof(/datum/design)) + var/datum/design/DN = path + if(isnull(initial(DN.id))) + stack_trace("WARNING: Design with null ID detected. Build path: [initial(DN.build_path)]") + continue + else if(initial(DN.id) == DESIGN_ID_IGNORE) + continue + DN = new path + if(returned[initial(DN.id)]) + stack_trace("WARNING: Design ID clash with ID [initial(DN.id)] detected! Path: [path]") + errored_datums[DN] = initial(DN.id) + continue + DN.InitializeMaterials() //Initialize the materials in the design + returned[initial(DN.id)] = DN + techweb_designs = returned + verify_techweb_designs() + + +/datum/controller/subsystem/research/proc/verify_techweb_nodes() + . = TRUE + for(var/n in techweb_nodes) + var/datum/techweb_node/N = techweb_nodes[n] + if(!istype(N)) + WARNING("Invalid research node with ID [n] detected and removed.") + techweb_nodes -= n + research_node_id_error(n) + . = FALSE + for(var/p in N.prereq_ids) + var/datum/techweb_node/P = techweb_nodes[p] + if(!istype(P)) + WARNING("Invalid research prerequisite node with ID [p] detected in node [N.display_name]\[[N.id]\] removed.") + N.prereq_ids -= p + research_node_id_error(p) + . = FALSE + for(var/d in N.design_ids) + var/datum/design/D = techweb_designs[d] + if(!istype(D)) + WARNING("Invalid research design with ID [d] detected in node [N.display_name]\[[N.id]\] removed.") + N.design_ids -= d + design_id_error(d) + . = FALSE + for(var/u in N.unlock_ids) + var/datum/techweb_node/U = techweb_nodes[u] + if(!istype(U)) + WARNING("Invalid research unlock node with ID [u] detected in node [N.display_name]\[[N.id]\] removed.") + N.unlock_ids -= u + research_node_id_error(u) + . = FALSE + for(var/p in N.boost_item_paths) + if(!ispath(p)) + N.boost_item_paths -= p + WARNING("[p] is not a valid path.") + node_boost_error(N.id, "[p] is not a valid path.") + . = FALSE + var/list/points = N.boost_item_paths[p] + if(islist(points)) + for(var/i in points) + if(!isnum(points[i])) + WARNING("[points[i]] is not a valid number.") + node_boost_error(N.id, "[points[i]] is not a valid number.") + . = FALSE + else if(!point_types[i]) + WARNING("[i] is not a valid point type.") + node_boost_error(N.id, "[i] is not a valid point type.") + . = FALSE + else if(!isnull(points)) + N.boost_item_paths -= p + node_boost_error(N.id, "No valid list.") + WARNING("No valid list.") + . = FALSE + CHECK_TICK + +/datum/controller/subsystem/research/proc/verify_techweb_designs() + for(var/d in techweb_designs) + var/datum/design/D = techweb_designs[d] + if(!istype(D)) + stack_trace("WARNING: Invalid research design with ID [d] detected and removed.") + techweb_designs -= d + CHECK_TICK + +/datum/controller/subsystem/research/proc/research_node_id_error(id) + if(invalid_node_ids[id]) + invalid_node_ids[id]++ + else + invalid_node_ids[id] = 1 + +/datum/controller/subsystem/research/proc/design_id_error(id) + if(invalid_design_ids[id]) + invalid_design_ids[id]++ + else + invalid_design_ids[id] = 1 + +/datum/controller/subsystem/research/proc/calculate_techweb_nodes() + for(var/design_id in techweb_designs) + var/datum/design/D = techweb_designs[design_id] + D.unlocked_by.Cut() + for(var/node_id in techweb_nodes) + var/datum/techweb_node/node = techweb_nodes[node_id] + node.unlock_ids = list() + for(var/i in node.design_ids) + var/datum/design/D = techweb_designs[i] + node.design_ids[i] = TRUE + D.unlocked_by += node.id + if(node.hidden) + techweb_nodes_hidden[node.id] = TRUE + if(node.experimental) + techweb_nodes_experimental[node.id] = TRUE + CHECK_TICK + generate_techweb_unlock_linking() + +/datum/controller/subsystem/research/proc/generate_techweb_unlock_linking() + for(var/node_id in techweb_nodes) //Clear all unlock links to avoid duplication. + var/datum/techweb_node/node = techweb_nodes[node_id] + node.unlock_ids = list() + for(var/node_id in techweb_nodes) + var/datum/techweb_node/node = techweb_nodes[node_id] + for(var/prereq_id in node.prereq_ids) + var/datum/techweb_node/prereq_node = techweb_node_by_id(prereq_id) + prereq_node.unlock_ids[node.id] = node + +/datum/controller/subsystem/research/proc/calculate_techweb_boost_list(clearall = FALSE) + if(clearall) + techweb_boost_items = list() + for(var/node_id in techweb_nodes) + var/datum/techweb_node/node = techweb_nodes[node_id] + for(var/path in node.boost_item_paths) + if(!ispath(path)) + continue + if(length(techweb_boost_items[path])) + techweb_boost_items[path][node.id] = node.boost_item_paths[path] + else + techweb_boost_items[path] = list(node.id = node.boost_item_paths[path]) + CHECK_TICK diff --git a/code/controllers/subsystem/runechat.dm b/code/controllers/subsystem/runechat.dm new file mode 100644 index 000000000000..22cc72542784 --- /dev/null +++ b/code/controllers/subsystem/runechat.dm @@ -0,0 +1,226 @@ +/// Controls how many buckets should be kept, each representing a tick. (30 seconds worth) +#define BUCKET_LEN (world.fps * 1 * 30) +/// Helper for getting the correct bucket for a given chatmessage +#define BUCKET_POS(scheduled_destruction) (((round((scheduled_destruction - SSrunechat.head_offset) / world.tick_lag) + 1) % BUCKET_LEN) || BUCKET_LEN) +/// Gets the maximum time at which messages will be handled in buckets, used for deferring to secondary queue +#define BUCKET_LIMIT (world.time + TICKS2DS(min(BUCKET_LEN - (SSrunechat.practical_offset - DS2TICKS(world.time - SSrunechat.head_offset)) - 1, BUCKET_LEN - 1))) + +/** + * # Runechat Subsystem + * + * Maintains a timer-like system to handle destruction of runechat messages. Much of this code is modeled + * after or adapted from the timer subsystem. + * + * Note that this has the same structure for storing and queueing messages as the timer subsystem does + * for handling timers: the bucket_list is a list of chatmessage datums, each of which are the head + * of a circularly linked list. Any given index in bucket_list could be null, representing an empty bucket. + */ +SUBSYSTEM_DEF(runechat) + name = "Runechat" + flags = SS_TICKER | SS_NO_INIT + wait = 1 + priority = FIRE_PRIORITY_RUNECHAT + + /// world.time of the first entry in the bucket list, effectively the 'start time' of the current buckets + var/head_offset = 0 + /// Index of the first non-empty bucket + var/practical_offset = 1 + /// world.tick_lag the bucket was designed for + var/bucket_resolution = 0 + /// How many messages are in the buckets + var/bucket_count = 0 + /// List of buckets, each bucket holds every message that has to be killed that byond tick + var/list/bucket_list = list() + /// Queue used for storing messages that are scheduled for deletion too far in the future for the buckets + var/list/datum/chatmessage/second_queue = list() + +/datum/controller/subsystem/runechat/PreInit() + bucket_list.len = BUCKET_LEN + head_offset = world.time + bucket_resolution = world.tick_lag + +/datum/controller/subsystem/runechat/stat_entry(msg) + ..("ActMsgs:[bucket_count] SecQueue:[length(second_queue)]") + +/datum/controller/subsystem/runechat/fire(resumed = FALSE) + // Store local references to datum vars as it is faster to access them this way + var/list/bucket_list = src.bucket_list + + if (MC_TICK_CHECK) + return + + // Check for when we need to loop the buckets, this occurs when + // the head_offset is approaching BUCKET_LEN ticks in the past + if (practical_offset > BUCKET_LEN) + head_offset += TICKS2DS(BUCKET_LEN) + practical_offset = 1 + resumed = FALSE + + // Store a reference to the 'working' chatmessage so that we can resume if the MC + // has us stop mid-way through processing + var/static/datum/chatmessage/cm + if (!resumed) + cm = null + + // Iterate through each bucket starting from the practical offset + while (practical_offset <= BUCKET_LEN && head_offset + ((practical_offset - 1) * world.tick_lag) <= world.time) + var/datum/chatmessage/bucket_head = bucket_list[practical_offset] + if (!cm || !bucket_head || cm == bucket_head) + bucket_head = bucket_list[practical_offset] + cm = bucket_head + + while (cm) + // If the chatmessage hasn't yet had its life ended then do that now + var/datum/chatmessage/next = cm.next + if (!cm.eol_complete) + cm.end_of_life() + else if (!QDELETED(cm)) // otherwise if we haven't deleted it yet, do so (this is after EOL completion) + qdel(cm) + + if (MC_TICK_CHECK) + return + + // Break once we've processed the entire bucket + cm = next + if (cm == bucket_head) + break + + // Empty the bucket, check if anything in the secondary queue should be shifted to this bucket + bucket_list[practical_offset++] = null + var/i = 0 + for (i in 1 to length(second_queue)) + cm = second_queue[i] + if (cm.scheduled_destruction >= BUCKET_LIMIT) + i-- + break + + // Transfer the message into the bucket, performing necessary circular doubly-linked list operations + bucket_count++ + var/bucket_pos = max(1, BUCKET_POS(cm.scheduled_destruction)) + var/datum/timedevent/head = bucket_list[bucket_pos] + if (!head) + bucket_list[bucket_pos] = cm + cm.next = null + cm.prev = null + continue + + if (!head.prev) + head.prev = head + cm.next = head + cm.prev = head.prev + cm.next.prev = cm + cm.prev.next = cm + if (i) + second_queue.Cut(1, i + 1) + cm = null + +/datum/controller/subsystem/runechat/Recover() + bucket_list |= SSrunechat.bucket_list + second_queue |= SSrunechat.second_queue + +/** + * Enters the runechat subsystem with this chatmessage, inserting it into the end-of-life queue + * + * This will also account for a chatmessage already being registered, and in which case + * the position will be updated to remove it from the previous location if necessary + * + * Arguments: + * * new_sched_destruction Optional, when provided is used to update an existing message with the new specified time + */ +/datum/chatmessage/proc/enter_subsystem(new_sched_destruction = 0) + // Get local references from subsystem as they are faster to access than the datum references + var/list/bucket_list = SSrunechat.bucket_list + var/list/second_queue = SSrunechat.second_queue + + // When necessary, de-list the chatmessage from its previous position + if (new_sched_destruction) + if (scheduled_destruction >= BUCKET_LIMIT) + second_queue -= src + else + SSrunechat.bucket_count-- + var/bucket_pos = BUCKET_POS(scheduled_destruction) + if (bucket_pos > 0) + var/datum/chatmessage/bucket_head = bucket_list[bucket_pos] + if (bucket_head == src) + bucket_list[bucket_pos] = next + if (prev != next) + prev.next = next + next.prev = prev + else + prev?.next = null + next?.prev = null + prev = next = null + scheduled_destruction = new_sched_destruction + + // Ensure the scheduled destruction time is properly bound to avoid missing a scheduled event + scheduled_destruction = max(CEILING(scheduled_destruction, world.tick_lag), world.time + world.tick_lag) + + // Handle insertion into the secondary queue if the required time is outside our tracked amounts + if (scheduled_destruction >= BUCKET_LIMIT) + BINARY_INSERT(src, SSrunechat.second_queue, datum/chatmessage, src, scheduled_destruction, COMPARE_KEY) + return + + // Get bucket position and a local reference to the datum var, it's faster to access this way + var/bucket_pos = BUCKET_POS(scheduled_destruction) + + // Get the bucket head for that bucket, increment the bucket count + var/datum/chatmessage/bucket_head = bucket_list[bucket_pos] + SSrunechat.bucket_count++ + + // If there is no existing head of this bucket, we can set this message to be that head + if (!bucket_head) + bucket_list[bucket_pos] = src + return + + // Otherwise it's a simple insertion into the circularly doubly-linked list + if (!bucket_head.prev) + bucket_head.prev = bucket_head + next = bucket_head + prev = bucket_head.prev + next.prev = src + prev.next = src + + +/** + * Removes this chatmessage datum from the runechat subsystem + */ +/datum/chatmessage/proc/leave_subsystem() + // Attempt to find the bucket that contains this chat message + var/bucket_pos = BUCKET_POS(scheduled_destruction) + + // Get local references to the subsystem's vars, faster than accessing on the datum + var/list/bucket_list = SSrunechat.bucket_list + var/list/second_queue = SSrunechat.second_queue + + // Attempt to get the head of the bucket + var/datum/chatmessage/bucket_head + if (bucket_pos > 0) + bucket_head = bucket_list[bucket_pos] + + // Decrement the number of messages in buckets if the message is + // the head of the bucket, or has a SD less than BUCKET_LIMIT implying it fits + // into an existing bucket, or is otherwise not present in the secondary queue + if(bucket_head == src) + bucket_list[bucket_pos] = next + SSrunechat.bucket_count-- + else if(scheduled_destruction < BUCKET_LIMIT) + SSrunechat.bucket_count-- + else + var/l = length(second_queue) + second_queue -= src + if(l == length(second_queue)) + SSrunechat.bucket_count-- + + // Remove the message from the bucket, ensuring to maintain + // the integrity of the bucket's list if relevant + if(prev != next) + prev.next = next + next.prev = prev + else + prev?.next = null + next?.prev = null + prev = next = null + +#undef BUCKET_LEN +#undef BUCKET_POS +#undef BUCKET_LIMIT diff --git a/code/controllers/subsystem/shuttle.dm b/code/controllers/subsystem/shuttle.dm index 9080d0668c01..11b5b6ddfbf9 100644 --- a/code/controllers/subsystem/shuttle.dm +++ b/code/controllers/subsystem/shuttle.dm @@ -752,14 +752,15 @@ SUBSYSTEM_DEF(shuttle) preview_shuttle.jumpToNullSpace() preview_shuttle = null +/datum/controller/subsystem/shuttle/ui_state(mob/user) + return GLOB.admin_state -/datum/controller/subsystem/shuttle/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.admin_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/datum/controller/subsystem/shuttle/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "ShuttleManipulator", name, 800, 600, master_ui, state) + ui = new(user, src, "ShuttleManipulator") ui.open() - /datum/controller/subsystem/shuttle/ui_data(mob/user) var/list/data = list() data["tabs"] = list("Status", "Templates", "Modification") diff --git a/code/controllers/subsystem/skills.dm b/code/controllers/subsystem/skills.dm index 5e67d1adb553..c7256a42b322 100644 --- a/code/controllers/subsystem/skills.dm +++ b/code/controllers/subsystem/skills.dm @@ -8,9 +8,8 @@ SUBSYSTEM_DEF(skills) init_order = INIT_ORDER_SKILLS ///Dictionary of skill.type || skill ref var/list/all_skills = list() - ///Static assoc list of levels (ints) - strings - var/list/level_names = list("Novice", "Apprentice", "Journeyman", "Expert", "Master", "Legendary")//This list is already in the right order, due to indexing - + ///List of level names with index corresponding to skill level + var/list/level_names = list("None", "Novice", "Apprentice", "Journeyman", "Expert", "Master", "Legendary") //List of skill level names. Note that indexes can be accessed like so: level_names[SKILL_LEVEL_NOVICE] /datum/controller/subsystem/skills/Initialize(timeofday) InitializeSkills() @@ -18,6 +17,6 @@ SUBSYSTEM_DEF(skills) ///Ran on initialize, populates the skills dictionary /datum/controller/subsystem/skills/proc/InitializeSkills(timeofday) - for(var/type in subtypesof(/datum/skill)) + for(var/type in GLOB.skill_types) var/datum/skill/ref = new type all_skills[type] = ref diff --git a/code/controllers/subsystem/spacedrift.dm b/code/controllers/subsystem/spacedrift.dm index 56a6786a202b..c251492227ac 100644 --- a/code/controllers/subsystem/spacedrift.dm +++ b/code/controllers/subsystem/spacedrift.dm @@ -1,59 +1,59 @@ -SUBSYSTEM_DEF(spacedrift) - name = "Space Drift" - priority = FIRE_PRIORITY_SPACEDRIFT - wait = 5 - flags = SS_NO_INIT|SS_KEEP_TIMING - runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME - - var/list/currentrun = list() - var/list/processing = list() - -/datum/controller/subsystem/spacedrift/stat_entry() - ..("P:[processing.len]") - - -/datum/controller/subsystem/spacedrift/fire(resumed = 0) - if (!resumed) - src.currentrun = processing.Copy() - - //cache for sanic speed (lists are references anyways) - var/list/currentrun = src.currentrun - - while (currentrun.len) - var/atom/movable/AM = currentrun[currentrun.len] - currentrun.len-- - if (!AM) - processing -= AM - if (MC_TICK_CHECK) - return - continue - - if (AM.inertia_next_move > world.time) - if (MC_TICK_CHECK) - return - continue - - if (!AM.loc || AM.loc != AM.inertia_last_loc || AM.Process_Spacemove(0)) - AM.inertia_dir = 0 - - if (!AM.inertia_dir) - AM.inertia_last_loc = null - processing -= AM - if (MC_TICK_CHECK) - return - continue - - var/old_dir = AM.dir - var/old_loc = AM.loc - AM.inertia_moving = TRUE - step(AM, AM.inertia_dir) - AM.inertia_moving = FALSE - AM.inertia_next_move = world.time + AM.inertia_move_delay - if (AM.loc == old_loc) - AM.inertia_dir = 0 - - AM.setDir(old_dir) - AM.inertia_last_loc = AM.loc - if (MC_TICK_CHECK) - return - +SUBSYSTEM_DEF(spacedrift) + name = "Space Drift" + priority = FIRE_PRIORITY_SPACEDRIFT + wait = 5 + flags = SS_NO_INIT|SS_KEEP_TIMING + runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME + + var/list/currentrun = list() + var/list/processing = list() + +/datum/controller/subsystem/spacedrift/stat_entry() + ..("P:[processing.len]") + + +/datum/controller/subsystem/spacedrift/fire(resumed = 0) + if (!resumed) + src.currentrun = processing.Copy() + + //cache for sanic speed (lists are references anyways) + var/list/currentrun = src.currentrun + + while (currentrun.len) + var/atom/movable/AM = currentrun[currentrun.len] + currentrun.len-- + if (!AM) + processing -= AM + if (MC_TICK_CHECK) + return + continue + + if (AM.inertia_next_move > world.time) + if (MC_TICK_CHECK) + return + continue + + if (!AM.loc || AM.loc != AM.inertia_last_loc || AM.Process_Spacemove(0)) + AM.inertia_dir = 0 + + if (!AM.inertia_dir) + AM.inertia_last_loc = null + processing -= AM + if (MC_TICK_CHECK) + return + continue + + var/old_dir = AM.dir + var/old_loc = AM.loc + AM.inertia_moving = TRUE + step(AM, AM.inertia_dir) + AM.inertia_moving = FALSE + AM.inertia_next_move = world.time + AM.inertia_move_delay + if (AM.loc == old_loc) + AM.inertia_dir = 0 + + AM.setDir(old_dir) + AM.inertia_last_loc = AM.loc + if (MC_TICK_CHECK) + return + diff --git a/code/controllers/subsystem/stickyban.dm b/code/controllers/subsystem/stickyban.dm index 801c1cc5be49..38a12ec0baec 100644 --- a/code/controllers/subsystem/stickyban.dm +++ b/code/controllers/subsystem/stickyban.dm @@ -156,7 +156,10 @@ SUBSYSTEM_DEF(stickyban) if (!ban["message"]) ban["message"] = "Evasion" - var/datum/DBQuery/query_create_stickyban = SSdbcore.NewQuery("INSERT IGNORE INTO [format_table_name("stickyban")] (ckey, reason, banning_admin) VALUES ('[sanitizeSQL(ckey)]', '[sanitizeSQL(ban["message"])]', '[sanitizeSQL(ban["admin"])]')") + var/datum/DBQuery/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 @@ -170,8 +173,8 @@ SUBSYSTEM_DEF(stickyban) var/list/keys = splittext(ban["keys"], ",") for (var/key in keys) var/list/sqlckey = list() - sqlckey["stickyban"] = "'[sanitizeSQL(ckey)]'" - sqlckey["matched_ckey"] = "'[sanitizeSQL(ckey(key))]'" + sqlckey["stickyban"] = ckey + sqlckey["matched_ckey"] = ckey(key) sqlckey["exempt"] = FALSE sqlckeys[++sqlckeys.len] = sqlckey @@ -179,8 +182,8 @@ SUBSYSTEM_DEF(stickyban) var/list/keys = splittext(ban["whitelist"], ",") for (var/key in keys) var/list/sqlckey = list() - sqlckey["stickyban"] = "'[sanitizeSQL(ckey)]'" - sqlckey["matched_ckey"] = "'[sanitizeSQL(ckey(key))]'" + sqlckey["stickyban"] = ckey + sqlckey["matched_ckey"] = ckey(key) sqlckey["exempt"] = TRUE sqlckeys[++sqlckeys.len] = sqlckey @@ -188,26 +191,26 @@ SUBSYSTEM_DEF(stickyban) var/list/cids = splittext(ban["computer_id"], ",") for (var/cid in cids) var/list/sqlcid = list() - sqlcid["stickyban"] = "'[sanitizeSQL(ckey)]'" - sqlcid["matched_cid"] = "'[sanitizeSQL(cid)]'" + sqlcid["stickyban"] = ckey + sqlcid["matched_cid"] = cid sqlcids[++sqlcids.len] = sqlcid if (ban["IP"]) var/list/ips = splittext(ban["IP"], ",") for (var/ip in ips) var/list/sqlip = list() - sqlip["stickyban"] = "'[sanitizeSQL(ckey)]'" - sqlip["matched_ip"] = "'[sanitizeSQL(ip)]'" + sqlip["stickyban"] = ckey + sqlip["matched_ip"] = ip sqlips[++sqlips.len] = sqlip if (length(sqlckeys)) - SSdbcore.MassInsert(format_table_name("stickyban_matched_ckey"), sqlckeys, FALSE, TRUE) + SSdbcore.MassInsert(format_table_name("stickyban_matched_ckey"), sqlckeys, ignore_errors = TRUE) if (length(sqlcids)) - SSdbcore.MassInsert(format_table_name("stickyban_matched_cid"), sqlcids, FALSE, TRUE) + SSdbcore.MassInsert(format_table_name("stickyban_matched_cid"), sqlcids, ignore_errors = TRUE) if (length(sqlips)) - SSdbcore.MassInsert(format_table_name("stickyban_matched_ip"), sqlips, FALSE, TRUE) + SSdbcore.MassInsert(format_table_name("stickyban_matched_ip"), sqlips, ignore_errors = TRUE) return TRUE diff --git a/code/controllers/subsystem/tgui.dm b/code/controllers/subsystem/tgui.dm index 17caad9b77ef..a5526d2c0397 100644 --- a/code/controllers/subsystem/tgui.dm +++ b/code/controllers/subsystem/tgui.dm @@ -1,3 +1,12 @@ +/** + * tgui subsystem + * + * Contains all tgui state and subsystem code. + * + * Copyright (c) 2020 Aleksej Komarov + * SPDX-License-Identifier: MIT + */ + SUBSYSTEM_DEF(tgui) name = "tgui" wait = 9 @@ -5,10 +14,14 @@ SUBSYSTEM_DEF(tgui) priority = FIRE_PRIORITY_TGUI runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT - var/list/currentrun = list() - var/list/open_uis = list() // A list of open UIs, grouped by src_object and ui_key. - var/list/processing_uis = list() // A list of processing UIs, ungrouped. - var/basehtml // The HTML base used for all UIs. + /// A list of UIs scheduled to process + var/list/current_run = list() + /// A list of open UIs + var/list/open_uis = list() + /// A list of open UIs, grouped by src_object. + var/list/open_uis_by_src = list() + /// The HTML base used for all UIs. + var/basehtml /datum/controller/subsystem/tgui/PreInit() basehtml = file2text('tgui/packages/tgui/public/tgui.html') @@ -17,20 +30,322 @@ SUBSYSTEM_DEF(tgui) close_all_uis() /datum/controller/subsystem/tgui/stat_entry() - ..("P:[processing_uis.len]") + ..("P:[open_uis.len]") /datum/controller/subsystem/tgui/fire(resumed = 0) - if (!resumed) - src.currentrun = processing_uis.Copy() - //cache for sanic speed (lists are references anyways) - var/list/currentrun = src.currentrun - - while(currentrun.len) - var/datum/tgui/ui = currentrun[currentrun.len] - currentrun.len-- + if(!resumed) + src.current_run = open_uis.Copy() + // Cache for sanic speed (lists are references anyways) + var/list/current_run = src.current_run + while(current_run.len) + var/datum/tgui/ui = current_run[current_run.len] + current_run.len-- + // TODO: Move user/src_object check to process() if(ui && ui.user && ui.src_object) ui.process() else - processing_uis.Remove(ui) - if (MC_TICK_CHECK) + open_uis.Remove(ui) + if(MC_TICK_CHECK) return + +/** + * public + * + * Requests a usable tgui window from the pool. + * Returns null if pool was exhausted. + * + * required user mob + * return datum/tgui + */ +/datum/controller/subsystem/tgui/proc/request_pooled_window(mob/user) + if(!user.client) + return null + var/list/windows = user.client.tgui_windows + var/window_id + var/datum/tgui_window/window + var/window_found = FALSE + // Find a usable window + for(var/i in 1 to TGUI_WINDOW_HARD_LIMIT) + window_id = TGUI_WINDOW_ID(i) + window = windows[window_id] + // As we are looping, create missing window datums + if(!window) + window = new(user.client, window_id, pooled = TRUE) + // Skip windows with acquired locks + if(window.locked) + continue + if(window.status == TGUI_WINDOW_READY) + return window + if(window.status == TGUI_WINDOW_CLOSED) + window.status = TGUI_WINDOW_LOADING + window_found = TRUE + break + if(!window_found) + log_tgui(user, "Error: Pool exhausted") + return null + return window + +/** + * public + * + * Force closes all tgui windows. + * + * required user mob + */ +/datum/controller/subsystem/tgui/proc/force_close_all_windows(mob/user) + log_tgui(user, "force_close_all_windows") + if(user.client) + user.client.tgui_windows = list() + for(var/i in 1 to TGUI_WINDOW_HARD_LIMIT) + var/window_id = TGUI_WINDOW_ID(i) + user << browse(null, "window=[window_id]") + +/** + * public + * + * Force closes the tgui window by window_id. + * + * required user mob + * required window_id string + */ +/datum/controller/subsystem/tgui/proc/force_close_window(mob/user, window_id) + log_tgui(user, "force_close_window") + // Close all tgui datums based on window_id. + for(var/datum/tgui/ui in user.tgui_open_uis) + if(ui.window && ui.window.id == window_id) + ui.close(can_be_suspended = FALSE) + // Unset machine just to be sure. + user.unset_machine() + // Close window directly just to be sure. + user << browse(null, "window=[window_id]") + +/** + * public + * + * Try to find an instance of a UI, and push an update to it. + * + * required user mob The mob who opened/is using the UI. + * required src_object datum The object/datum which owns the UI. + * optional ui datum/tgui The UI to be updated, if it exists. + * optional force_open bool If the UI should be re-opened instead of updated. + * + * return datum/tgui The found UI. + */ +/datum/controller/subsystem/tgui/proc/try_update_ui( + mob/user, + datum/src_object, + datum/tgui/ui) + // Look up a UI if it wasn't passed + if(isnull(ui)) + ui = get_open_ui(user, src_object) + // Couldn't find a UI. + if(isnull(ui)) + return null + ui.process_status() + // UI ended up with the closed status + // or is actively trying to close itself. + // FIXME: Doesn't actually fix the paper bug. + if(ui.status <= UI_CLOSE) + ui.close() + return null + ui.send_update() + return ui + +/** + * public + * + * Get a open UI given a user and src_object. + * + * required user mob The mob who opened/is using the UI. + * required src_object datum The object/datum which owns the UI. + * + * return datum/tgui The found UI. + */ +/datum/controller/subsystem/tgui/proc/get_open_ui(mob/user, datum/src_object) + var/key = "[REF(src_object)]" + // No UIs opened for this src_object + if(isnull(open_uis_by_src[key]) || !istype(open_uis_by_src[key], /list)) + return null + for(var/datum/tgui/ui in open_uis_by_src[key]) + // Make sure we have the right user + if(ui.user == user) + return ui + return null + +/** + * public + * + * Update all UIs attached to src_object. + * + * required src_object datum The object/datum which owns the UIs. + * + * return int The number of UIs updated. + */ +/datum/controller/subsystem/tgui/proc/update_uis(datum/src_object) + var/count = 0 + var/key = "[REF(src_object)]" + // No UIs opened for this src_object + if(isnull(open_uis_by_src[key]) || !istype(open_uis_by_src[key], /list)) + return count + for(var/datum/tgui/ui in open_uis_by_src[key]) + // Check if UI is valid. + if(ui && ui.src_object && ui.user && ui.src_object.ui_host(ui.user)) + ui.process(force = 1) + count++ + return count + +/** + * public + * + * Close all UIs attached to src_object. + * + * required src_object datum The object/datum which owns the UIs. + * + * return int The number of UIs closed. + */ +/datum/controller/subsystem/tgui/proc/close_uis(datum/src_object) + var/count = 0 + var/key = "[REF(src_object)]" + // No UIs opened for this src_object + if(isnull(open_uis_by_src[key]) || !istype(open_uis_by_src[key], /list)) + return count + for(var/datum/tgui/ui in open_uis_by_src[key]) + // Check if UI is valid. + if(ui && ui.src_object && ui.user && ui.src_object.ui_host(ui.user)) + ui.close() + count++ + return count + +/** + * public + * + * Close all UIs regardless of their attachment to src_object. + * + * return int The number of UIs closed. + */ +/datum/controller/subsystem/tgui/proc/close_all_uis() + var/count = 0 + for(var/key in open_uis_by_src) + for(var/datum/tgui/ui in open_uis_by_src[key]) + // Check if UI is valid. + if(ui && ui.src_object && ui.user && ui.src_object.ui_host(ui.user)) + ui.close() + count++ + return count + +/** + * public + * + * Update all UIs belonging to a user. + * + * required user mob The mob who opened/is using the UI. + * optional src_object datum If provided, only update UIs belonging this src_object. + * + * return int The number of UIs updated. + */ +/datum/controller/subsystem/tgui/proc/update_user_uis(mob/user, datum/src_object) + var/count = 0 + if(length(user?.tgui_open_uis) == 0) + return count + for(var/datum/tgui/ui in user.tgui_open_uis) + if(isnull(src_object) || ui.src_object == src_object) + ui.process(force = 1) + count++ + return count + +/** + * public + * + * Close all UIs belonging to a user. + * + * required user mob The mob who opened/is using the UI. + * optional src_object datum If provided, only close UIs belonging this src_object. + * + * return int The number of UIs closed. + */ +/datum/controller/subsystem/tgui/proc/close_user_uis(mob/user, datum/src_object) + var/count = 0 + if(length(user?.tgui_open_uis) == 0) + return count + for(var/datum/tgui/ui in user.tgui_open_uis) + if(isnull(src_object) || ui.src_object == src_object) + ui.close() + count++ + return count + +/** + * private + * + * Add a UI to the list of open UIs. + * + * required ui datum/tgui The UI to be added. + */ +/datum/controller/subsystem/tgui/proc/on_open(datum/tgui/ui) + var/key = "[REF(ui.src_object)]" + if(isnull(open_uis_by_src[key]) || !istype(open_uis_by_src[key], /list)) + open_uis_by_src[key] = list() + ui.user.tgui_open_uis |= ui + var/list/uis = open_uis_by_src[key] + uis |= ui + open_uis |= ui + +/** + * private + * + * Remove a UI from the list of open UIs. + * + * required ui datum/tgui The UI to be removed. + * + * return bool If the UI was removed or not. + */ +/datum/controller/subsystem/tgui/proc/on_close(datum/tgui/ui) + var/key = "[REF(ui.src_object)]" + if(isnull(open_uis_by_src[key]) || !istype(open_uis_by_src[key], /list)) + return FALSE + // Remove it from the list of processing UIs. + open_uis.Remove(ui) + // If the user exists, remove it from them too. + if(ui.user) + ui.user.tgui_open_uis.Remove(ui) + var/list/uis = open_uis_by_src[key] + uis.Remove(ui) + if(length(uis) == 0) + open_uis_by_src.Remove(key) + return TRUE + +/** + * private + * + * Handle client logout, by closing all their UIs. + * + * required user mob The mob which logged out. + * + * return int The number of UIs closed. + */ +/datum/controller/subsystem/tgui/proc/on_logout(mob/user) + close_user_uis(user) + +/** + * private + * + * Handle clients switching mobs, by transferring their UIs. + * + * required user source The client's original mob. + * required user target The client's new mob. + * + * return bool If the UIs were transferred. + */ +/datum/controller/subsystem/tgui/proc/on_transfer(mob/source, mob/target) + // The old mob had no open UIs. + if(length(source?.tgui_open_uis) == 0) + return FALSE + if(isnull(target.tgui_open_uis) || !istype(target.tgui_open_uis, /list)) + target.tgui_open_uis = list() + // Transfer all the UIs. + for(var/datum/tgui/ui in source.tgui_open_uis) + // Inform the UIs of their new owner. + ui.user = target + target.tgui_open_uis.Add(ui) + // Clear the old list. + source.tgui_open_uis.Cut() + return TRUE diff --git a/code/controllers/subsystem/timer.dm b/code/controllers/subsystem/timer.dm index 36bf992cfb93..bb7ae62d5488 100644 --- a/code/controllers/subsystem/timer.dm +++ b/code/controllers/subsystem/timer.dm @@ -1,31 +1,51 @@ -#define BUCKET_LEN (world.fps*1*60) //how many ticks should we keep in the bucket. (1 minutes worth) +/// Controls how many buckets should be kept, each representing a tick. (1 minutes worth) +#define BUCKET_LEN (world.fps*1*60) +/// Helper for getting the correct bucket for a given timer #define BUCKET_POS(timer) (((round((timer.timeToRun - SStimer.head_offset) / world.tick_lag)+1) % BUCKET_LEN)||BUCKET_LEN) +/// Gets the maximum time at which timers will be invoked from buckets, used for deferring to secondary queue #define TIMER_MAX (world.time + TICKS2DS(min(BUCKET_LEN-(SStimer.practical_offset-DS2TICKS(world.time - SStimer.head_offset))-1, BUCKET_LEN-1))) -#define TIMER_ID_MAX (2**24) //max float with integer precision +/// Max float with integer precision +#define TIMER_ID_MAX (2**24) +/** + * # Timer Subsystem + * + * Handles creation, callbacks, and destruction of timed events. + * + * It is important to understand the buckets used in the timer subsystem are just a series of circular doubly-linked + * lists. The object at a given index in bucket_list is a /datum/timedevent, the head of a circular list, which has prev + * and next references for the respective elements in that bucket's circular list. + */ SUBSYSTEM_DEF(timer) name = "Timer" - wait = 1 //SS_TICKER subsystem, so wait is in ticks + wait = 1 // SS_TICKER subsystem, so wait is in ticks init_order = INIT_ORDER_TIMER flags = SS_TICKER|SS_NO_INIT - var/list/datum/timedevent/second_queue = list() //awe, yes, you've had first queue, but what about second queue? + /// Queue used for storing timers that do not fit into the current buckets + var/list/datum/timedevent/second_queue = list() + /// A hashlist dictionary used for storing unique timers var/list/hashes = list() - - var/head_offset = 0 //world.time of the first entry in the the bucket. - var/practical_offset = 1 //index of the first non-empty item in the bucket. - var/bucket_resolution = 0 //world.tick_lag the bucket was designed for - var/bucket_count = 0 //how many timers are in the buckets - - var/list/bucket_list = list() //list of buckets, each bucket holds every timer that has to run that byond tick. - - var/list/timer_id_dict = list() //list of all active timers assoicated to their timer id (for easy lookup) - - var/list/clienttime_timers = list() //special snowflake timers that run on fancy pansy "client time" - + /// world.time of the first entry in the bucket list, effectively the 'start time' of the current buckets + var/head_offset = 0 + /// Index of the wrap around pivot for buckets. buckets before this are later running buckets wrapped around from the end of the bucket list. + var/practical_offset = 1 + /// world.tick_lag the bucket was designed for + var/bucket_resolution = 0 + /// How many timers are in the buckets + var/bucket_count = 0 + /// List of buckets, each bucket holds every timer that has to run that byond tick + var/list/bucket_list = list() + /// List of all active timers associated to their timer ID (for easy lookup) + var/list/timer_id_dict = list() + /// Special timers that run in real-time, not BYOND time; these are more expensive to run and maintain + var/list/clienttime_timers = list() + /// Contains the last time that a timer's callback was invoked, or the last tick the SS fired if no timers are being processed var/last_invoke_tick = 0 + /// Contains the last time that a warning was issued for not invoking callbacks var/static/last_invoke_warning = 0 + /// Boolean operator controlling if the timer SS will automatically reset buckets if it fails to invoke callbacks for an extended period of time var/static/bucket_auto_reset = TRUE /datum/controller/subsystem/timer/PreInit() @@ -37,44 +57,50 @@ SUBSYSTEM_DEF(timer) ..("B:[bucket_count] P:[length(second_queue)] H:[length(hashes)] C:[length(clienttime_timers)] S:[length(timer_id_dict)]") /datum/controller/subsystem/timer/fire(resumed = FALSE) + // Store local references to datum vars as it is faster to access them var/lit = last_invoke_tick - var/last_check = world.time - TICKS2DS(BUCKET_LEN*1.5) var/list/bucket_list = src.bucket_list + var/last_check = world.time - TICKS2DS(BUCKET_LEN * 1.5) + // If there are no timers being tracked, then consider now to be the last invoked time if(!bucket_count) last_invoke_tick = world.time + // Check that we have invoked a callback in the last 1.5 minutes of BYOND time, + // and throw a warning and reset buckets if this is true if(lit && lit < last_check && head_offset < last_check && last_invoke_warning < last_check) last_invoke_warning = world.time - var/msg = "No regular timers processed in the last [BUCKET_LEN*1.5] ticks[bucket_auto_reset ? ", resetting buckets" : ""]!" + var/msg = "No regular timers processed in the last [BUCKET_LEN * 1.5] ticks[bucket_auto_reset ? ", resetting buckets" : ""]!" message_admins(msg) WARNING(msg) if(bucket_auto_reset) bucket_resolution = 0 - log_world("Timer bucket reset. world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset]") + var/list/to_log = list("Timer bucket reset. world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset]") for (var/i in 1 to length(bucket_list)) var/datum/timedevent/bucket_head = bucket_list[i] if (!bucket_head) continue - log_world("Active timers at index [i]:") - + to_log += "Active timers at index [i]:" var/datum/timedevent/bucket_node = bucket_head var/anti_loop_check = 1000 do - log_world(get_timer_debug_string(bucket_node)) + to_log += get_timer_debug_string(bucket_node) bucket_node = bucket_node.next anti_loop_check-- while(bucket_node && bucket_node != bucket_head && anti_loop_check) - log_world("Active timers in the second_queue queue:") + + to_log += "Active timers in the second_queue queue:" for(var/I in second_queue) - log_world(get_timer_debug_string(I)) + to_log += get_timer_debug_string(I) - var/next_clienttime_timer_index = 0 - var/len = length(clienttime_timers) + // Dump all the logged data to the world log + log_world(to_log.Join("\n")) - for (next_clienttime_timer_index in 1 to len) + // Process client-time timers + var/next_clienttime_timer_index = 0 + for (next_clienttime_timer_index in 1 to length(clienttime_timers)) if (MC_TICK_CHECK) next_clienttime_timer_index-- break @@ -85,8 +111,9 @@ SUBSYSTEM_DEF(timer) var/datum/callback/callBack = ctime_timer.callBack if (!callBack) - clienttime_timers.Cut(next_clienttime_timer_index,next_clienttime_timer_index+1) - CRASH("Invalid timer: [get_timer_debug_string(ctime_timer)] world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset], REALTIMEOFDAY: [REALTIMEOFDAY]") + clienttime_timers.Cut(next_clienttime_timer_index,next_clienttime_timer_index + 1) + CRASH("Invalid timer: [get_timer_debug_string(ctime_timer)] world.time: [world.time], \ + head_offset: [head_offset], practical_offset: [practical_offset], REALTIMEOFDAY: [REALTIMEOFDAY]") ctime_timer.spent = REALTIMEOFDAY callBack.InvokeAsync() @@ -98,42 +125,49 @@ SUBSYSTEM_DEF(timer) else qdel(ctime_timer) - + // Remove invoked client-time timers if (next_clienttime_timer_index) clienttime_timers.Cut(1, next_clienttime_timer_index+1) if (MC_TICK_CHECK) return - var/static/list/spent = list() - var/static/datum/timedevent/timer + // Check for when we need to loop the buckets, this occurs when + // the head_offset is approaching BUCKET_LEN ticks in the past if (practical_offset > BUCKET_LEN) head_offset += TICKS2DS(BUCKET_LEN) practical_offset = 1 resumed = FALSE + // Check for when we have to reset buckets, typically from auto-reset if ((length(bucket_list) != BUCKET_LEN) || (world.tick_lag != bucket_resolution)) reset_buckets() bucket_list = src.bucket_list resumed = FALSE - + var/static/list/invoked_timers = list() + var/static/datum/timedevent/timer if (!resumed) timer = null - while (practical_offset <= BUCKET_LEN && head_offset + ((practical_offset-1)*world.tick_lag) <= world.time) + // Iterate through each bucket starting from the practical offset + while (practical_offset <= BUCKET_LEN && head_offset + ((practical_offset - 1) * world.tick_lag) <= world.time) var/datum/timedevent/head = bucket_list[practical_offset] if (!timer || !head || timer == head) head = bucket_list[practical_offset] timer = head + + // Iterate through each timer, invoking callbacks as necessary while (timer) var/datum/callback/callBack = timer.callBack if (!callBack) - bucket_resolution = null //force bucket recreation - CRASH("Invalid timer: [get_timer_debug_string(timer)] world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset]") + bucket_resolution = null // force bucket recreation + CRASH("Invalid timer: [get_timer_debug_string(timer)] world.time: [world.time], \ + head_offset: [head_offset], practical_offset: [practical_offset]") + // Invoke callback if possible if (!timer.spent) - spent += timer + invoked_timers += timer timer.spent = world.time callBack.InvokeAsync() last_invoke_tick = world.time @@ -141,48 +175,50 @@ SUBSYSTEM_DEF(timer) if (MC_TICK_CHECK) return + // Break once we've invoked the entire bucket timer = timer.next if (timer == head) break - + // Empty the bucket, check if anything in the secondary queue should be shifted to this bucket bucket_list[practical_offset++] = null - - //we freed up a bucket, lets see if anything in second_queue needs to be shifted to that bucket. var/i = 0 - var/L = length(second_queue) - for (i in 1 to L) + for (i in 1 to length(second_queue)) timer = second_queue[i] if (timer.timeToRun >= TIMER_MAX) i-- break + // Check for timers that are scheduled to run in the past if (timer.timeToRun < head_offset) - bucket_resolution = null //force bucket recreation - stack_trace("[i] Invalid timer state: Timer in long run queue with a time to run less then head_offset. [get_timer_debug_string(timer)] world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset]") + bucket_resolution = null // force bucket recreation + stack_trace("[i] Invalid timer state: Timer in long run queue with a time to run less then head_offset. \ + [get_timer_debug_string(timer)] world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset]") if (timer.callBack && !timer.spent) timer.callBack.InvokeAsync() - spent += timer + invoked_timers += timer bucket_count++ else if(!QDELETED(timer)) qdel(timer) continue - if (timer.timeToRun < head_offset + TICKS2DS(practical_offset-1)) - bucket_resolution = null //force bucket recreation - stack_trace("[i] Invalid timer state: Timer in long run queue that would require a backtrack to transfer to short run queue. [get_timer_debug_string(timer)] world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset]") + // Check for timers that are not capable of being scheduled to run without rebuilding buckets + if (timer.timeToRun < head_offset + TICKS2DS(practical_offset - 1)) + bucket_resolution = null // force bucket recreation + stack_trace("[i] Invalid timer state: Timer in long run queue that would require a backtrack to transfer to \ + short run queue. [get_timer_debug_string(timer)] world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset]") if (timer.callBack && !timer.spent) timer.callBack.InvokeAsync() - spent += timer + invoked_timers += timer bucket_count++ else if(!QDELETED(timer)) qdel(timer) continue + // Transfer the timer into the bucket, performing necessary circular doubly-linked list operations bucket_count++ var/bucket_pos = max(1, BUCKET_POS(timer)) - var/datum/timedevent/bucket_head = bucket_list[bucket_pos] if (!bucket_head) bucket_list[bucket_pos] = timer @@ -198,31 +234,27 @@ SUBSYSTEM_DEF(timer) timer.prev.next = timer if (i) second_queue.Cut(1, i+1) - timer = null - bucket_count -= length(spent) - - for (var/i in spent) - var/datum/timedevent/qtimer = i + // Process each invoked timer + bucket_count -= length(invoked_timers) + for (var/datum/timedevent/qtimer in invoked_timers) if(QDELETED(qtimer)) bucket_count++ continue if(!(qtimer.flags & TIMER_LOOP)) qdel(qtimer) - else + else // Prepare looping timers to re-enter the queue bucket_count++ qtimer.spent = 0 qtimer.bucketEject() - if(qtimer.flags & TIMER_CLIENT_TIME) - qtimer.timeToRun = REALTIMEOFDAY + qtimer.wait - else - qtimer.timeToRun = world.time + qtimer.wait + qtimer.timeToRun = (qtimer.flags & TIMER_CLIENT_TIME ? REALTIMEOFDAY : world.time) + qtimer.wait qtimer.bucketJoin() + invoked_timers.len = 0 - spent.len = 0 - -//formated this way to be runtime resistant +/** + * Generates a string with details about the timed event for debugging purposes + */ /datum/controller/subsystem/timer/proc/get_timer_debug_string(datum/timedevent/TE) . = "Timer: [TE]" . += "Prev: [TE.prev ? TE.prev : "NULL"], Next: [TE.next ? TE.next : "NULL"]" @@ -233,12 +265,16 @@ SUBSYSTEM_DEF(timer) if(!TE.callBack) . += ", NO CALLBACK" +/** + * Destroys the existing buckets and creates new buckets from the existing timed events + */ /datum/controller/subsystem/timer/proc/reset_buckets() - var/list/bucket_list = src.bucket_list + var/list/bucket_list = src.bucket_list // Store local reference to datum var, this is faster var/list/alltimers = list() - //collect the timers currently in the bucket + + // Get all timers currently in the buckets for (var/bucket_head in bucket_list) - if (!bucket_head) + if (!bucket_head) // if bucket is empty for this tick continue var/datum/timedevent/bucket_node = bucket_head do @@ -246,25 +282,38 @@ SUBSYSTEM_DEF(timer) bucket_node = bucket_node.next while(bucket_node && bucket_node != bucket_head) + // Empty the list by zeroing and re-assigning the length bucket_list.len = 0 bucket_list.len = BUCKET_LEN + // Reset values for the subsystem to their initial values practical_offset = 1 bucket_count = 0 head_offset = world.time bucket_resolution = world.tick_lag + // Add all timed events from the secondary queue as well alltimers += second_queue + + // If there are no timers being tracked by the subsystem, + // there is no need to do any further rebuilding if (!length(alltimers)) return + // Sort all timers by time to run sortTim(alltimers, .proc/cmp_timer) + // Get the earliest timer, and if the TTR is earlier than the current world.time, + // then set the head offset appropriately to be the earliest time tracked by the + // current set of buckets var/datum/timedevent/head = alltimers[1] - if (head.timeToRun < head_offset) head_offset = head.timeToRun + // Iterate through each timed event and insert it into an appropriate bucket, + // up unto the point that we can no longer insert into buckets as the TTR + // is outside the range we are tracking, then insert the remainder into the + // secondary queue var/new_bucket_count var/i = 1 for (i in 1 to length(alltimers)) @@ -272,34 +321,38 @@ SUBSYSTEM_DEF(timer) if (!timer) continue - var/bucket_pos = BUCKET_POS(timer) + // Check that the TTR is within the range covered by buckets, when exceeded we've finished if (timer.timeToRun >= TIMER_MAX) i-- break - + // Check that timer has a valid callback and hasn't been invoked if (!timer.callBack || timer.spent) - WARNING("Invalid timer: [get_timer_debug_string(timer)] world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset]") + WARNING("Invalid timer: [get_timer_debug_string(timer)] world.time: [world.time], \ + head_offset: [head_offset], practical_offset: [practical_offset]") if (timer.callBack) qdel(timer) continue + // Insert the timer into the bucket, and perform necessary circular doubly-linked list operations new_bucket_count++ + var/bucket_pos = BUCKET_POS(timer) var/datum/timedevent/bucket_head = bucket_list[bucket_pos] if (!bucket_head) bucket_list[bucket_pos] = timer timer.next = null timer.prev = null continue - if (!bucket_head.prev) bucket_head.prev = bucket_head timer.next = bucket_head timer.prev = bucket_head.prev timer.next.prev = timer timer.prev.next = timer + + // Cut the timers that are tracked by the buckets from the secondary queue if (i) - alltimers.Cut(1, i+1) + alltimers.Cut(1, i + 1) second_queue = alltimers bucket_count = new_bucket_count @@ -310,17 +363,37 @@ SUBSYSTEM_DEF(timer) timer_id_dict |= SStimer.timer_id_dict bucket_list |= SStimer.bucket_list +/** + * # Timed Event + * + * This is the actual timer, it contains the callback and necessary data to maintain + * the timer. + * + * See the documentation for the timer subsystem for an explanation of the buckets referenced + * below in next and prev + */ /datum/timedevent + /// ID used for timers when the TIMER_STOPPABLE flag is present var/id + /// The callback to invoke after the timer completes var/datum/callback/callBack + /// The time at which the callback should be invoked at var/timeToRun + /// The length of the timer var/wait + /// Unique hash generated when TIMER_UNIQUE flag is present var/hash + /// The source of the timedevent, whatever called addtimer + var/source + /// Flags associated with the timer, see _DEFINES/subsystems.dm var/list/flags - var/spent = 0 //time we ran the timer. - var/name //for easy debugging. - //cicular doublely linked list + /// Time at which the timer was invoked or destroyed + var/spent = 0 + /// An informative name generated for the timer as its representation in strings, useful for debugging + var/name + /// Next timed event in the bucket var/datum/timedevent/next + /// Previous timed event in the bucket var/datum/timedevent/prev /datum/timedevent/New(datum/callback/callBack, wait, flags, hash) @@ -331,23 +404,27 @@ SUBSYSTEM_DEF(timer) src.flags = flags src.hash = hash - if (flags & TIMER_CLIENT_TIME) - timeToRun = REALTIMEOFDAY + wait - else - timeToRun = world.time + wait + // Determine time at which the timer's callback should be invoked + timeToRun = (flags & TIMER_CLIENT_TIME ? REALTIMEOFDAY : world.time) + wait + // Include the timer in the hash table if the timer is unique if (flags & TIMER_UNIQUE) SStimer.hashes[hash] = src + // Generate ID for the timer if the timer is stoppable, include in the timer id dictionary if (flags & TIMER_STOPPABLE) id = num2text(nextid, 100) if (nextid >= SHORT_REAL_LIMIT) - nextid += min(1, 2**round(nextid/SHORT_REAL_LIMIT)) + nextid += min(1, 2 ** round(nextid / SHORT_REAL_LIMIT)) else nextid++ SStimer.timer_id_dict[id] = src - name = "Timer: [id] (\ref[src]), TTR: [timeToRun], Flags: [jointext(bitfield2list(flags, list("TIMER_UNIQUE", "TIMER_OVERRIDE", "TIMER_CLIENT_TIME", "TIMER_STOPPABLE", "TIMER_NO_HASH_WAIT", "TIMER_LOOP")), ", ")], callBack: \ref[callBack], callBack.object: [callBack.object]\ref[callBack.object]([getcallingtype()]), callBack.delegate:[callBack.delegate]([callBack.arguments ? callBack.arguments.Join(", ") : ""])" + // Generate debug-friendly name for timer + var/static/list/bitfield_flags = list("TIMER_UNIQUE", "TIMER_OVERRIDE", "TIMER_CLIENT_TIME", "TIMER_STOPPABLE", "TIMER_NO_HASH_WAIT", "TIMER_LOOP") + name = "Timer: [id] (\ref[src]), TTR: [timeToRun], Flags: [jointext(bitfield2list(flags, bitfield_flags), ", ")], \ + callBack: \ref[callBack], callBack.object: [callBack.object]\ref[callBack.object]([getcallingtype()]), \ + callBack.delegate:[callBack.delegate]([callBack.arguments ? callBack.arguments.Join(", ") : ""])" if ((timeToRun < world.time || timeToRun < SStimer.head_offset) && !(flags & TIMER_CLIENT_TIME)) CRASH("Invalid timer state: Timer created that would require a backtrack to run (addtimer would never let this happen): [SStimer.get_timer_debug_string(src)]") @@ -389,23 +466,39 @@ SUBSYSTEM_DEF(timer) prev = null return QDEL_HINT_IWILLGC +/** + * Removes this timed event from any relevant buckets, or the secondary queue + */ /datum/timedevent/proc/bucketEject() + // Attempt to find bucket that contains this timed event var/bucketpos = BUCKET_POS(src) + + // Store local references for the bucket list and secondary queue + // This is faster than referencing them from the datum itself var/list/bucket_list = SStimer.bucket_list var/list/second_queue = SStimer.second_queue + + // Attempt to get the head of the bucket var/datum/timedevent/buckethead if(bucketpos > 0) buckethead = bucket_list[bucketpos] + + // Decrement the number of timers in buckets if the timed event is + // the head of the bucket, or has a TTR less than TIMER_MAX implying it fits + // into an existing bucket, or is otherwise not present in the secondary queue if(buckethead == src) bucket_list[bucketpos] = next SStimer.bucket_count-- - else if(timeToRun < TIMER_MAX || next || prev) + else if(timeToRun < TIMER_MAX) SStimer.bucket_count-- else var/l = length(second_queue) second_queue -= src if(l == length(second_queue)) SStimer.bucket_count-- + + // Remove the timed event from the bucket, ensuring to maintain + // the integrity of the bucket's list if relevant if(prev != next) prev.next = next next.prev = prev @@ -414,32 +507,41 @@ SUBSYSTEM_DEF(timer) next?.prev = null prev = next = null +/** + * Attempts to add this timed event to a bucket, will enter the secondary queue + * if there are no appropriate buckets at this time. + * + * Secondary queueing of timed events will occur when the timespan covered by the existing + * buckets is exceeded by the time at which this timed event is scheduled to be invoked. + * If the timed event is tracking client time, it will be added to a special bucket. + */ /datum/timedevent/proc/bucketJoin() + // Check if this timed event should be diverted to the client time bucket, or the secondary queue var/list/L - if (flags & TIMER_CLIENT_TIME) L = SStimer.clienttime_timers else if (timeToRun >= TIMER_MAX) L = SStimer.second_queue - if(L) BINARY_INSERT(src, L, datum/timedevent, src, timeToRun, COMPARE_KEY) return - //get the list of buckets + // Get a local reference to the bucket list, this is faster than referencing the datum var/list/bucket_list = SStimer.bucket_list - //calculate our place in the bucket list + // Find the correct bucket for this timed event var/bucket_pos = BUCKET_POS(src) - - //get the bucket for our tick var/datum/timedevent/bucket_head = bucket_list[bucket_pos] SStimer.bucket_count++ - //empty bucket, we will just add ourselves + + // If there is no timed event at this position, then the bucket is 'empty' + // and we can just set this event to that position if (!bucket_head) bucket_list[bucket_pos] = src return - //other wise, lets do a simplified linked list add. + + // Otherwise, we merely add this timed event into the bucket, which is a + // circularly doubly-linked list if (!bucket_head.prev) bucket_head.prev = bucket_head next = bucket_head @@ -447,7 +549,9 @@ SUBSYSTEM_DEF(timer) next.prev = src prev.next = src -///Returns a string of the type of the callback for this timer +/** + * Returns a string of the type of the callback for this timer + */ /datum/timedevent/proc/getcallingtype() . = "ERROR" if (callBack.object == GLOBAL_PROC) @@ -471,31 +575,30 @@ SUBSYSTEM_DEF(timer) stack_trace("addtimer called with a negative wait. Converting to [world.tick_lag]") if (callback.object != GLOBAL_PROC && QDELETED(callback.object) && !QDESTROYING(callback.object)) - stack_trace("addtimer called with a callback assigned to a qdeleted object. In the future such timers will not be supported and may refuse to run or run with a 0 wait") + stack_trace("addtimer called with a callback assigned to a qdeleted object. In the future such timers will not \ + be supported and may refuse to run or run with a 0 wait") wait = max(CEILING(wait, world.tick_lag), world.tick_lag) if(wait >= INFINITY) CRASH("Attempted to create timer with INFINITY delay") + // Generate hash if relevant for timed events with the TIMER_UNIQUE flag var/hash - if (flags & TIMER_UNIQUE) - var/list/hashlist - if(flags & TIMER_NO_HASH_WAIT) - hashlist = list(callback.object, "([REF(callback.object)])", callback.delegate, flags & TIMER_CLIENT_TIME) - else - hashlist = list(callback.object, "([REF(callback.object)])", callback.delegate, wait, flags & TIMER_CLIENT_TIME) + var/list/hashlist = list(callback.object, "([REF(callback.object)])", callback.delegate, flags & TIMER_CLIENT_TIME) + if(!(flags & TIMER_NO_HASH_WAIT)) + hashlist += wait hashlist += callback.arguments hash = hashlist.Join("|||||||") var/datum/timedevent/hash_timer = SStimer.hashes[hash] if(hash_timer) - if (hash_timer.spent) //it's pending deletion, pretend it doesn't exist. - hash_timer.hash = null //but keep it from accidentally deleting us + if (hash_timer.spent) // it's pending deletion, pretend it doesn't exist. + hash_timer.hash = null // but keep it from accidentally deleting us else if (flags & TIMER_OVERRIDE) - hash_timer.hash = null //no need having it delete it's hash if we are going to replace it + hash_timer.hash = null // no need having it delete it's hash if we are going to replace it qdel(hash_timer) else if (hash_timer.flags & TIMER_STOPPABLE) @@ -518,10 +621,9 @@ SUBSYSTEM_DEF(timer) return FALSE if (id == TIMER_ID_NULL) CRASH("Tried to delete a null timerid. Use TIMER_STOPPABLE flag") - if (!istext(id)) - if (istype(id, /datum/timedevent)) - qdel(id) - return TRUE + if (istype(id, /datum/timedevent)) + qdel(id) + return TRUE //id is string var/datum/timedevent/timer = SStimer.timer_id_dict[id] if (timer && !timer.spent) @@ -540,15 +642,12 @@ SUBSYSTEM_DEF(timer) return null if (id == TIMER_ID_NULL) CRASH("Tried to get timeleft of a null timerid. Use TIMER_STOPPABLE flag") - if (!istext(id)) - if (istype(id, /datum/timedevent)) - var/datum/timedevent/timer = id - return timer.timeToRun - world.time + if (istype(id, /datum/timedevent)) + var/datum/timedevent/timer = id + return timer.timeToRun - world.time //id is string var/datum/timedevent/timer = SStimer.timer_id_dict[id] - if (timer && !timer.spent) - return timer.timeToRun - world.time - return null + return (timer && !timer.spent) ? timer.timeToRun - world.time : null #undef BUCKET_LEN #undef BUCKET_POS diff --git a/code/controllers/subsystem/vote.dm b/code/controllers/subsystem/vote.dm index 292bd674212b..fbf1d5dc9a56 100644 --- a/code/controllers/subsystem/vote.dm +++ b/code/controllers/subsystem/vote.dm @@ -1,415 +1,415 @@ -SUBSYSTEM_DEF(vote) - name = "Vote" - wait = 10 - - flags = SS_KEEP_TIMING|SS_NO_INIT - - runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT - - var/initiator = null - var/started_time = null - var/time_remaining = 0 - var/mode = null - var/question = null - var/list/choices = list() - var/list/voted = list() - var/list/voting = list() - var/list/generated_actions = list() - -/datum/controller/subsystem/vote/fire() //called by master_controller - if(mode) - time_remaining = round((started_time + CONFIG_GET(number/vote_period) - world.time)/10) - - if(time_remaining < 0) - result() - for(var/client/C in voting) - C << browse(null, "window=vote;can_close=0") - reset() - else - var/datum/browser/client_popup - for(var/client/C in voting) - client_popup = new(C, "vote", "Voting Panel") - client_popup.set_window_options("can_close=0") - client_popup.set_content(interface(C)) - client_popup.open(FALSE) - - -/datum/controller/subsystem/vote/proc/reset() - initiator = null - time_remaining = 0 - mode = null - question = null - choices.Cut() - voted.Cut() - voting.Cut() - remove_action_buttons() - -/datum/controller/subsystem/vote/proc/get_result() - //get the highest number of votes - var/greatest_votes = 0 - var/total_votes = 0 - for(var/option in choices) - var/votes = choices[option] - total_votes += votes - if(votes > greatest_votes) - greatest_votes = votes - //default-vote for everyone who didn't vote - if(!CONFIG_GET(flag/default_no_vote) && choices.len) - var/list/non_voters = GLOB.directory.Copy() - non_voters -= voted - for (var/non_voter_ckey in non_voters) - var/client/C = non_voters[non_voter_ckey] - if (!C || C.is_afk()) - non_voters -= non_voter_ckey - if(non_voters.len > 0) - if(mode == "restart") - choices["Continue Playing"] += non_voters.len - if(choices["Continue Playing"] >= greatest_votes) - greatest_votes = choices["Continue Playing"] - else if(mode == "gamemode") - if(GLOB.master_mode in choices) - choices[GLOB.master_mode] += non_voters.len - if(choices[GLOB.master_mode] >= greatest_votes) - greatest_votes = choices[GLOB.master_mode] - - //WaspStation Begin - Autotransfer - else if(mode == "transfer") - var/factor = 1 - switch(world.time / (1 MINUTES )) - if(0 to 60) - factor = 0.5 - if(61 to 120) - factor = 0.8 - if(121 to 240) - factor = 1 - if(241 to 300) - factor = 1.2 - else - factor = 1.4 - choices["Initiate Crew Transfer"] += round(non_voters.len * factor) - //WaspStation End - - else if(mode == "map") - for (var/non_voter_ckey in non_voters) - var/client/C = non_voters[non_voter_ckey] - if(C.prefs.preferred_map) - if(choices[C.prefs.preferred_map]) //No votes if the map isnt in the vote. - var/preferred_map = C.prefs.preferred_map - choices[preferred_map] += 1 - greatest_votes = max(greatest_votes, choices[preferred_map]) - else if(config.defaultmap) - if(choices[config.defaultmap]) //No votes if the map isnt in the vote. - var/default_map = config.defaultmap.map_name - choices[default_map] += 1 - greatest_votes = max(greatest_votes, choices[default_map]) - //get all options with that many votes and return them in a list - . = list() - if(greatest_votes) - for(var/option in choices) - if(choices[option] == greatest_votes) - . += option - return . - -/datum/controller/subsystem/vote/proc/announce_result() - var/list/winners = get_result() - var/text - if(winners.len > 0) - if(question) - text += "[question]" - else - text += "[capitalize(mode)] Vote" - for(var/i=1,i<=choices.len,i++) - var/votes = choices[choices[i]] - if(!votes) - votes = 0 - text += "\n[choices[i]]: [votes]" - if(mode != "custom") - if(winners.len > 1) - text = "\nVote Tied Between:" - for(var/option in winners) - text += "\n\t[option]" - . = pick(winners) - text += "\nVote Result: [.]" - else - text += "\nDid not vote: [GLOB.clients.len-voted.len]" - else - text += "Vote Result: Inconclusive - No Votes!" - log_vote(text) - remove_action_buttons() - to_chat(world, "\n[text]") - return . - -/datum/controller/subsystem/vote/proc/result() - . = announce_result() - var/restart = FALSE - if(.) - switch(mode) - if("restart") - if(. == "Restart Round") - restart = TRUE - if("gamemode") - if(GLOB.master_mode != .) - SSticker.save_mode(.) - if(SSticker.HasRoundStarted()) - restart = TRUE - else - GLOB.master_mode = . - - //WS Begin - Autotransfer - if("transfer") - if(. == "Initiate Crew Transfer") - //TODO find a cleaner way to do this - SSshuttle.requestEvac(null,"Crew transfer requested.") - var/obj/machinery/computer/communications/C = locate() in GLOB.machines - if(C) - C.post_status("shuttle") - //WS End - - if("map") - SSmapping.changemap(global.config.maplist[.]) - SSmapping.map_voted = TRUE - if(restart) - var/active_admins = FALSE - for(var/client/C in GLOB.admins) - if(!C.is_afk() && check_rights_for(C, R_SERVER)) - active_admins = TRUE - break - if(!active_admins) - SSticker.Reboot("Restart vote successful.", "restart vote") - else - to_chat(world, "Notice:Restart vote will not restart the server automatically because there are active admins on.") - message_admins("A restart vote has passed, but there are active admins on with +server, so it has been canceled. If you wish, you may restart the server.") - - return . - -/datum/controller/subsystem/vote/proc/submit_vote(vote) - if(mode) - if(CONFIG_GET(flag/no_dead_vote) && usr.stat == DEAD && !usr.client.holder) - return FALSE - if(!(usr.ckey in voted)) - if(vote && 1<=vote && vote<=choices.len) - voted += usr.ckey - choices[choices[vote]]++ //check this - return vote - return FALSE - -/datum/controller/subsystem/vote/proc/initiate_vote(vote_type, initiator_key) - if(!Master.current_runlevel) //Server is still intializing. - to_chat(usr, "Cannot start vote, server is not done initializing.") - return FALSE - var/admin = FALSE - var/ckey = ckey(initiator_key) - if(GLOB.admin_datums[ckey]) - admin = TRUE - - if(!mode) - if(started_time) - var/next_allowed_time = (started_time + CONFIG_GET(number/vote_delay)) - if(mode) - to_chat(usr, "There is already a vote in progress! please wait for it to finish.") - return FALSE - - - if(next_allowed_time > world.time && !admin) - to_chat(usr, "A vote was initiated recently, you must wait [DisplayTimeText(next_allowed_time-world.time)] before a new vote can be started!") - return FALSE - - SEND_SOUND(world, sound('sound/misc/notice2.ogg'))//WS Edit - Autotransfer - reset() - switch(vote_type) - if("restart") - choices.Add("Restart Round","Continue Playing") - if("gamemode") - choices.Add(config.votable_modes) - - //WS Begin - Autotransfer - if("transfer") - choices.Add("Initiate Crew Transfer","Continue Playing") - //WS End - - if("map") - if(!admin && SSmapping.map_voted) - to_chat(usr, "The next map has already been selected.") - return FALSE - for(var/map in config.maplist) - var/datum/map_config/VM = config.maplist[map] - if(!VM.votable || (VM.map_name in SSpersistence.blocked_maps)) - continue - choices.Add(VM.map_name) - if("custom") - question = stripped_input(usr,"What is the vote for?") - if(!question) - return FALSE - for(var/i=1,i<=10,i++) - var/option = capitalize(stripped_input(usr,"Please enter an option or hit cancel to finish")) - if(!option || mode || !usr.client) - break - choices.Add(option) - else - return FALSE - mode = vote_type - initiator = initiator_key ? initiator_key : "the Server" //WS Edit - Autotransfer - started_time = world.time - var/text = "[capitalize(mode)] vote started by [initiator]." - if(mode == "custom") - text += "\n[question]" - log_vote(text) - var/vp = CONFIG_GET(number/vote_period) - to_chat(world, "\n[text]\nType vote or click here to place your votes.\nYou have [DisplayTimeText(vp)] to vote.") - time_remaining = round(vp/10) - for(var/c in GLOB.clients) - var/client/C = c - var/datum/action/vote/V = new - if(question) - V.name = "Vote: [question]" - C.player_details.player_actions += V - V.Grant(C.mob) - generated_actions += V - return TRUE - return FALSE - -/datum/controller/subsystem/vote/proc/interface(client/C) - if(!C) - return - var/admin = FALSE - var/trialmin = FALSE - if(C.holder) - admin = TRUE - if(check_rights_for(C, R_ADMIN)) - trialmin = TRUE - voting |= C - - if(mode) - if(question) - . += "

    Vote: '[question]'

    " - else - . += "

    Vote: [capitalize(mode)]

    " - . += "Time Left: [time_remaining] s
      " - for(var/i=1,i<=choices.len,i++) - var/votes = choices[choices[i]] - if(!votes) - votes = 0 - . += "
    • [choices[i]] ([votes] votes)
    • " - . += "

    " - if(admin) - . += "(Cancel Vote) " - else - . += "

    Start a vote:


    • " - //restart - var/avr = CONFIG_GET(flag/allow_vote_restart) - if(trialmin || avr) - . += "Restart" - else - . += "Restart (Disallowed)" - if(trialmin) - . += "\t([avr ? "Allowed" : "Disallowed"])" - . += "
    • " - //gamemode - var/avm = CONFIG_GET(flag/allow_vote_mode) - if(trialmin || avm) - . += "GameMode" - else - . += "GameMode (Disallowed)" - if(trialmin) - . += "\t([avm ? "Allowed" : "Disallowed"])" - - . += "
    • " - //map - var/avmap = CONFIG_GET(flag/allow_vote_map) - if(trialmin || avmap) - . += "Map" - else - . += "Map (Disallowed)" - if(trialmin) - . += "\t([avmap ? "Allowed" : "Disallowed"])" - - . += "" - //custom - if(trialmin) - . += "
    • Custom
    • " - . += "

    " - . += "Close" - return . - - -/datum/controller/subsystem/vote/Topic(href,href_list[],hsrc) - if(!usr || !usr.client) - return //not necessary but meh...just in-case somebody does something stupid - - var/trialmin = FALSE - if(usr.client.holder) - if(check_rights_for(usr.client, R_ADMIN)) - trialmin = TRUE - - switch(href_list["vote"]) - if("close") - voting -= usr.client - usr << browse(null, "window=vote") - return - if("cancel") - if(usr.client.holder) - reset() - if("toggle_restart") - if(usr.client.holder && trialmin) - CONFIG_SET(flag/allow_vote_restart, !CONFIG_GET(flag/allow_vote_restart)) - if("toggle_gamemode") - if(usr.client.holder && trialmin) - CONFIG_SET(flag/allow_vote_mode, !CONFIG_GET(flag/allow_vote_mode)) - if("toggle_map") - if(usr.client.holder && trialmin) - CONFIG_SET(flag/allow_vote_map, !CONFIG_GET(flag/allow_vote_map)) - if("restart") - if(CONFIG_GET(flag/allow_vote_restart) || usr.client.holder) - initiate_vote("restart",usr.key) - if("gamemode") - if(CONFIG_GET(flag/allow_vote_mode) || usr.client.holder) - initiate_vote("gamemode",usr.key) - if("map") - if(CONFIG_GET(flag/allow_vote_map) || usr.client.holder) - initiate_vote("map",usr.key) - if("custom") - if(usr.client.holder) - initiate_vote("custom",usr.key) - else - submit_vote(round(text2num(href_list["vote"]))) - usr.vote() - -/datum/controller/subsystem/vote/proc/remove_action_buttons() - for(var/v in generated_actions) - var/datum/action/vote/V = v - if(!QDELETED(V)) - V.remove_from_client() - V.Remove(V.owner) - generated_actions = list() - -/mob/verb/vote() - set category = "OOC" - set name = "Vote" - - var/datum/browser/popup = new(src, "vote", "Voting Panel") - popup.set_window_options("can_close=0") - popup.set_content(SSvote.interface(client)) - popup.open(FALSE) - -/datum/action/vote - name = "Vote!" - button_icon_state = "vote" - -/datum/action/vote/Trigger() - if(owner) - owner.vote() - remove_from_client() - Remove(owner) - -/datum/action/vote/IsAvailable() - return TRUE - -/datum/action/vote/proc/remove_from_client() - if(!owner) - return - if(owner.client) - owner.client.player_details.player_actions -= src - else if(owner.ckey) - var/datum/player_details/P = GLOB.player_details[owner.ckey] - if(P) - P.player_actions -= src +SUBSYSTEM_DEF(vote) + name = "Vote" + wait = 10 + + flags = SS_KEEP_TIMING|SS_NO_INIT + + runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT + + var/initiator = null + var/started_time = null + var/time_remaining = 0 + var/mode = null + var/question = null + var/list/choices = list() + var/list/voted = list() + var/list/voting = list() + var/list/generated_actions = list() + +/datum/controller/subsystem/vote/fire() //called by master_controller + if(mode) + time_remaining = round((started_time + CONFIG_GET(number/vote_period) - world.time)/10) + + if(time_remaining < 0) + result() + for(var/client/C in voting) + C << browse(null, "window=vote;can_close=0") + reset() + else + var/datum/browser/client_popup + for(var/client/C in voting) + client_popup = new(C, "vote", "Voting Panel") + client_popup.set_window_options("can_close=0") + client_popup.set_content(interface(C)) + client_popup.open(FALSE) + + +/datum/controller/subsystem/vote/proc/reset() + initiator = null + time_remaining = 0 + mode = null + question = null + choices.Cut() + voted.Cut() + voting.Cut() + remove_action_buttons() + +/datum/controller/subsystem/vote/proc/get_result() + //get the highest number of votes + var/greatest_votes = 0 + var/total_votes = 0 + for(var/option in choices) + var/votes = choices[option] + total_votes += votes + if(votes > greatest_votes) + greatest_votes = votes + //default-vote for everyone who didn't vote + if(!CONFIG_GET(flag/default_no_vote) && choices.len) + var/list/non_voters = GLOB.directory.Copy() + non_voters -= voted + for (var/non_voter_ckey in non_voters) + var/client/C = non_voters[non_voter_ckey] + if (!C || C.is_afk()) + non_voters -= non_voter_ckey + if(non_voters.len > 0) + if(mode == "restart") + choices["Continue Playing"] += non_voters.len + if(choices["Continue Playing"] >= greatest_votes) + greatest_votes = choices["Continue Playing"] + else if(mode == "gamemode") + if(GLOB.master_mode in choices) + choices[GLOB.master_mode] += non_voters.len + if(choices[GLOB.master_mode] >= greatest_votes) + greatest_votes = choices[GLOB.master_mode] + + //WaspStation Begin - Autotransfer + else if(mode == "transfer") + var/factor = 1 + switch(world.time / (1 MINUTES )) + if(0 to 60) + factor = 0.5 + if(61 to 120) + factor = 0.8 + if(121 to 240) + factor = 1 + if(241 to 300) + factor = 1.2 + else + factor = 1.4 + choices["Initiate Crew Transfer"] += round(non_voters.len * factor) + //WaspStation End + + else if(mode == "map") + for (var/non_voter_ckey in non_voters) + var/client/C = non_voters[non_voter_ckey] + if(C.prefs.preferred_map) + if(choices[C.prefs.preferred_map]) //No votes if the map isnt in the vote. + var/preferred_map = C.prefs.preferred_map + choices[preferred_map] += 1 + greatest_votes = max(greatest_votes, choices[preferred_map]) + else if(config.defaultmap) + if(choices[config.defaultmap]) //No votes if the map isnt in the vote. + var/default_map = config.defaultmap.map_name + choices[default_map] += 1 + greatest_votes = max(greatest_votes, choices[default_map]) + //get all options with that many votes and return them in a list + . = list() + if(greatest_votes) + for(var/option in choices) + if(choices[option] == greatest_votes) + . += option + return . + +/datum/controller/subsystem/vote/proc/announce_result() + var/list/winners = get_result() + var/text + if(winners.len > 0) + if(question) + text += "[question]" + else + text += "[capitalize(mode)] Vote" + for(var/i=1,i<=choices.len,i++) + var/votes = choices[choices[i]] + if(!votes) + votes = 0 + text += "\n[choices[i]]: [votes]" + if(mode != "custom") + if(winners.len > 1) + text = "\nVote Tied Between:" + for(var/option in winners) + text += "\n\t[option]" + . = pick(winners) + text += "\nVote Result: [.]" + else + text += "\nDid not vote: [GLOB.clients.len-voted.len]" + else + text += "Vote Result: Inconclusive - No Votes!" + log_vote(text) + remove_action_buttons() + to_chat(world, "\n[text]") + return . + +/datum/controller/subsystem/vote/proc/result() + . = announce_result() + var/restart = FALSE + if(.) + switch(mode) + if("restart") + if(. == "Restart Round") + restart = TRUE + if("gamemode") + if(GLOB.master_mode != .) + SSticker.save_mode(.) + if(SSticker.HasRoundStarted()) + restart = TRUE + else + GLOB.master_mode = . + + //WS Begin - Autotransfer + if("transfer") + if(. == "Initiate Crew Transfer") + //TODO find a cleaner way to do this + SSshuttle.requestEvac(null,"Crew transfer requested.") + var/obj/machinery/computer/communications/C = locate() in GLOB.machines + if(C) + C.post_status("shuttle") + //WS End + + if("map") + SSmapping.changemap(global.config.maplist[.]) + SSmapping.map_voted = TRUE + if(restart) + var/active_admins = FALSE + for(var/client/C in GLOB.admins) + if(!C.is_afk() && check_rights_for(C, R_SERVER)) + active_admins = TRUE + break + if(!active_admins) + SSticker.Reboot("Restart vote successful.", "restart vote") + else + to_chat(world, "Notice:Restart vote will not restart the server automatically because there are active admins on.") + message_admins("A restart vote has passed, but there are active admins on with +server, so it has been canceled. If you wish, you may restart the server.") + + return . + +/datum/controller/subsystem/vote/proc/submit_vote(vote) + if(mode) + if(CONFIG_GET(flag/no_dead_vote) && usr.stat == DEAD && !usr.client.holder) + return FALSE + if(!(usr.ckey in voted)) + if(vote && 1<=vote && vote<=choices.len) + voted += usr.ckey + choices[choices[vote]]++ //check this + return vote + return FALSE + +/datum/controller/subsystem/vote/proc/initiate_vote(vote_type, initiator_key) + if(!Master.current_runlevel) //Server is still intializing. + to_chat(usr, "Cannot start vote, server is not done initializing.") + return FALSE + var/admin = FALSE + var/ckey = ckey(initiator_key) + if(GLOB.admin_datums[ckey]) + admin = TRUE + + if(!mode) + if(started_time) + var/next_allowed_time = (started_time + CONFIG_GET(number/vote_delay)) + if(mode) + to_chat(usr, "There is already a vote in progress! please wait for it to finish.") + return FALSE + + + if(next_allowed_time > world.time && !admin) + to_chat(usr, "A vote was initiated recently, you must wait [DisplayTimeText(next_allowed_time-world.time)] before a new vote can be started!") + return FALSE + + SEND_SOUND(world, sound('sound/misc/notice2.ogg'))//WS Edit - Autotransfer + reset() + switch(vote_type) + if("restart") + choices.Add("Restart Round","Continue Playing") + if("gamemode") + choices.Add(config.votable_modes) + + //WS Begin - Autotransfer + if("transfer") + choices.Add("Initiate Crew Transfer","Continue Playing") + //WS End + + if("map") + if(!admin && SSmapping.map_voted) + to_chat(usr, "The next map has already been selected.") + return FALSE + for(var/map in config.maplist) + var/datum/map_config/VM = config.maplist[map] + if(!VM.votable || (VM.map_name in SSpersistence.blocked_maps)) + continue + choices.Add(VM.map_name) + if("custom") + question = stripped_input(usr,"What is the vote for?") + if(!question) + return FALSE + for(var/i=1,i<=10,i++) + var/option = capitalize(stripped_input(usr,"Please enter an option or hit cancel to finish")) + if(!option || mode || !usr.client) + break + choices.Add(option) + else + return FALSE + mode = vote_type + initiator = initiator_key ? initiator_key : "the Server" //WS Edit - Autotransfer + started_time = world.time + var/text = "[capitalize(mode)] vote started by [initiator]." + if(mode == "custom") + text += "\n[question]" + log_vote(text) + var/vp = CONFIG_GET(number/vote_period) + to_chat(world, "\n[text]\nType vote or click here to place your votes.\nYou have [DisplayTimeText(vp)] to vote.") + time_remaining = round(vp/10) + for(var/c in GLOB.clients) + var/client/C = c + var/datum/action/vote/V = new + if(question) + V.name = "Vote: [question]" + C.player_details.player_actions += V + V.Grant(C.mob) + generated_actions += V + return TRUE + return FALSE + +/datum/controller/subsystem/vote/proc/interface(client/C) + if(!C) + return + var/admin = FALSE + var/trialmin = FALSE + if(C.holder) + admin = TRUE + if(check_rights_for(C, R_ADMIN)) + trialmin = TRUE + voting |= C + + if(mode) + if(question) + . += "

    Vote: '[question]'

    " + else + . += "

    Vote: [capitalize(mode)]

    " + . += "Time Left: [time_remaining] s
      " + for(var/i=1,i<=choices.len,i++) + var/votes = choices[choices[i]] + if(!votes) + votes = 0 + . += "
    • [choices[i]] ([votes] votes)
    • " + . += "

    " + if(admin) + . += "(Cancel Vote) " + else + . += "

    Start a vote:


    • " + //restart + var/avr = CONFIG_GET(flag/allow_vote_restart) + if(trialmin || avr) + . += "Restart" + else + . += "Restart (Disallowed)" + if(trialmin) + . += "\t([avr ? "Allowed" : "Disallowed"])" + . += "
    • " + //gamemode + var/avm = CONFIG_GET(flag/allow_vote_mode) + if(trialmin || avm) + . += "GameMode" + else + . += "GameMode (Disallowed)" + if(trialmin) + . += "\t([avm ? "Allowed" : "Disallowed"])" + + . += "
    • " + //map + var/avmap = CONFIG_GET(flag/allow_vote_map) + if(trialmin || avmap) + . += "Map" + else + . += "Map (Disallowed)" + if(trialmin) + . += "\t([avmap ? "Allowed" : "Disallowed"])" + + . += "" + //custom + if(trialmin) + . += "
    • Custom
    • " + . += "

    " + . += "Close" + return . + + +/datum/controller/subsystem/vote/Topic(href,href_list[],hsrc) + if(!usr || !usr.client) + return //not necessary but meh...just in-case somebody does something stupid + + var/trialmin = FALSE + if(usr.client.holder) + if(check_rights_for(usr.client, R_ADMIN)) + trialmin = TRUE + + switch(href_list["vote"]) + if("close") + voting -= usr.client + usr << browse(null, "window=vote") + return + if("cancel") + if(usr.client.holder) + reset() + if("toggle_restart") + if(usr.client.holder && trialmin) + CONFIG_SET(flag/allow_vote_restart, !CONFIG_GET(flag/allow_vote_restart)) + if("toggle_gamemode") + if(usr.client.holder && trialmin) + CONFIG_SET(flag/allow_vote_mode, !CONFIG_GET(flag/allow_vote_mode)) + if("toggle_map") + if(usr.client.holder && trialmin) + CONFIG_SET(flag/allow_vote_map, !CONFIG_GET(flag/allow_vote_map)) + if("restart") + if(CONFIG_GET(flag/allow_vote_restart) || usr.client.holder) + initiate_vote("restart",usr.key) + if("gamemode") + if(CONFIG_GET(flag/allow_vote_mode) || usr.client.holder) + initiate_vote("gamemode",usr.key) + if("map") + if(CONFIG_GET(flag/allow_vote_map) || usr.client.holder) + initiate_vote("map",usr.key) + if("custom") + if(usr.client.holder) + initiate_vote("custom",usr.key) + else + submit_vote(round(text2num(href_list["vote"]))) + usr.vote() + +/datum/controller/subsystem/vote/proc/remove_action_buttons() + for(var/v in generated_actions) + var/datum/action/vote/V = v + if(!QDELETED(V)) + V.remove_from_client() + V.Remove(V.owner) + generated_actions = list() + +/mob/verb/vote() + set category = "OOC" + set name = "Vote" + + var/datum/browser/popup = new(src, "vote", "Voting Panel") + popup.set_window_options("can_close=0") + popup.set_content(SSvote.interface(client)) + popup.open(FALSE) + +/datum/action/vote + name = "Vote!" + button_icon_state = "vote" + +/datum/action/vote/Trigger() + if(owner) + owner.vote() + remove_from_client() + Remove(owner) + +/datum/action/vote/IsAvailable() + return TRUE + +/datum/action/vote/proc/remove_from_client() + if(!owner) + return + if(owner.client) + owner.client.player_details.player_actions -= src + else if(owner.ckey) + var/datum/player_details/P = GLOB.player_details[owner.ckey] + if(P) + P.player_actions -= src diff --git a/code/datums/achievements/_achievement_data.dm b/code/datums/achievements/_achievement_data.dm index f353ef03614e..c2d580d978e6 100644 --- a/code/datums/achievements/_achievement_data.dm +++ b/code/datums/achievements/_achievement_data.dm @@ -32,7 +32,10 @@ set waitfor = FALSE var/list/kv = list() - var/datum/DBQuery/Query = SSdbcore.NewQuery("SELECT achievement_key,value FROM [format_table_name("achievements")] WHERE ckey = '[sanitizeSQL(owner_ckey)]'") + var/datum/DBQuery/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 @@ -88,16 +91,18 @@ else if(istype(A, /datum/award/score)) data[achievement_type] = 0 -/datum/achievement_data/ui_base_html(html) - var/datum/asset/spritesheet/simple/assets = get_asset_datum(/datum/asset/spritesheet/simple/achievements) - . = replacetext(html, "", assets.css_tag()) +/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, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.always_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/datum/achievement_data/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - var/datum/asset/spritesheet/simple/assets = get_asset_datum(/datum/asset/spritesheet/simple/achievements) - assets.send(user) - ui = new(user, src, ui_key, "Achievements", "Achievements Menu", 540, 680, master_ui, state) + ui = new(user, src, "Achievements") ui.open() /datum/achievement_data/ui_data(mob/user) diff --git a/code/datums/achievements/_awards.dm b/code/datums/achievements/_awards.dm index 06fa6ce475d1..5fa1802aaaee 100644 --- a/code/datums/achievements/_awards.dm +++ b/code/datums/achievements/_awards.dm @@ -27,14 +27,27 @@ /datum/award/proc/get_changed_rows(key, value) if(!database_id || !key || !name) return - return list("ckey" = "'[sanitizeSQL(key)]'","achievement_key" = "'[sanitizeSQL(database_id)]'", "value" = "'[sanitizeSQL(value)]'") + return list( + "ckey" = key, + "achievement_key" = database_id, + "value" = value, + ) /datum/award/proc/get_metadata_row() - return list("achievement_key" = "'[sanitizeSQL(database_id)]'", "achievement_version" = "'[sanitizeSQL(achievement_version)]'", "achievement_type" = "'award'", "achievement_name" = "'[sanitizeSQL(name)]'", "achievement_description" = "'[sanitizeSQL(desc)]'") + 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/DBQuery/Q = SSdbcore.NewQuery("SELECT value FROM [format_table_name("achievements")] WHERE ckey = '[sanitizeSQL(key)]' AND achievement_key = '[sanitizeSQL(database_id)]'") + var/datum/DBQuery/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 @@ -58,7 +71,7 @@ /datum/award/achievement/get_metadata_row() . = ..() - .["achievement_type"] = "'achievement'" + .["achievement_type"] = "achievement" /datum/award/achievement/parse_value(raw_value) return raw_value > 0 @@ -83,10 +96,13 @@ /datum/award/score/get_metadata_row() . = ..() - .["achievement_type"] = "'score'" + .["achievement_type"] = "score" /datum/award/score/proc/LoadHighScores() - var/datum/DBQuery/Q = SSdbcore.NewQuery("SELECT ckey,value FROM [format_table_name("achievements")] WHERE achievement_key = '[sanitizeSQL(database_id)]' ORDER BY value DESC LIMIT 50") + var/datum/DBQuery/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 diff --git a/code/datums/achievements/misc_achievements.dm b/code/datums/achievements/misc_achievements.dm index 36c1c75f4af0..7cf945ed116e 100644 --- a/code/datums/achievements/misc_achievements.dm +++ b/code/datums/achievements/misc_achievements.dm @@ -85,7 +85,7 @@ database_id = MEDAL_CLEANBOSS /datum/award/achievement/misc/rule8 - name = "Rule 8" + name = "Rule 3" desc = "Call an admin this is ILLEGAL!!" database_id = MEDAL_RULE8 icon = "rule8" diff --git a/code/datums/actions/beam_rifle.dm b/code/datums/actions/beam_rifle.dm index 783b93fbdb80..3af5d13690d0 100644 --- a/code/datums/actions/beam_rifle.dm +++ b/code/datums/actions/beam_rifle.dm @@ -1,12 +1,12 @@ - -/datum/action/item_action/zoom_speed_action - name = "Toggle Zooming Speed" - icon_icon = 'icons/mob/actions/actions_spells.dmi' - button_icon_state = "projectile" - background_icon_state = "bg_tech" - -/datum/action/item_action/zoom_lock_action - name = "Switch Zoom Mode" - icon_icon = 'icons/mob/actions/actions_items.dmi' - button_icon_state = "zoom_mode" - background_icon_state = "bg_tech" + +/datum/action/item_action/zoom_speed_action + name = "Toggle Zooming Speed" + icon_icon = 'icons/mob/actions/actions_spells.dmi' + button_icon_state = "projectile" + background_icon_state = "bg_tech" + +/datum/action/item_action/zoom_lock_action + name = "Switch Zoom Mode" + icon_icon = 'icons/mob/actions/actions_items.dmi' + button_icon_state = "zoom_mode" + background_icon_state = "bg_tech" diff --git a/code/datums/actions/ninja.dm b/code/datums/actions/ninja.dm index 830dad77e883..b655078349dc 100644 --- a/code/datums/actions/ninja.dm +++ b/code/datums/actions/ninja.dm @@ -1,51 +1,51 @@ -/datum/action/item_action/initialize_ninja_suit - name = "Toggle ninja suit" - -/datum/action/item_action/ninjasmoke - name = "Smoke Bomb" - desc = "Blind your enemies momentarily with a well-placed smoke bomb." - button_icon_state = "smoke" - icon_icon = 'icons/mob/actions/actions_spells.dmi' - -/datum/action/item_action/ninjaboost - check_flags = NONE - name = "Adrenaline Boost" - desc = "Inject a secret chemical that will counteract all movement-impairing effect." - button_icon_state = "repulse" - icon_icon = 'icons/mob/actions/actions_spells.dmi' - -/datum/action/item_action/ninjapulse - name = "EM Burst (25E)" - desc = "Disable any nearby technology with an electro-magnetic pulse." - button_icon_state = "emp" - icon_icon = 'icons/mob/actions/actions_spells.dmi' - -/datum/action/item_action/ninjastar - name = "Create Throwing Stars (1E)" - desc = "Creates some throwing stars" - button_icon_state = "throwingstar" - icon_icon = 'icons/obj/items_and_weapons.dmi' - -/datum/action/item_action/ninjanet - name = "Energy Net (20E)" - desc = "Captures a fallen opponent in a net of energy. Will teleport them to a holding facility after 30 seconds." - button_icon_state = "energynet" - icon_icon = 'icons/effects/effects.dmi' - -/datum/action/item_action/ninja_sword_recall - name = "Recall Energy Katana (Variable Cost)" - desc = "Teleports the Energy Katana linked to this suit to its wearer, cost based on distance." - button_icon_state = "energy_katana" - icon_icon = 'icons/obj/items_and_weapons.dmi' - -/datum/action/item_action/ninja_stealth - name = "Toggle Stealth" - desc = "Toggles stealth mode on and off." - button_icon_state = "ninja_cloak" - icon_icon = 'icons/mob/actions/actions_minor_antag.dmi' - -/datum/action/item_action/toggle_glove - name = "Toggle interaction" - desc = "Switch between normal interaction and drain mode." - button_icon_state = "s-ninjan" - icon_icon = 'icons/obj/clothing/gloves.dmi' +/datum/action/item_action/initialize_ninja_suit + name = "Toggle ninja suit" + +/datum/action/item_action/ninjasmoke + name = "Smoke Bomb" + desc = "Blind your enemies momentarily with a well-placed smoke bomb." + button_icon_state = "smoke" + icon_icon = 'icons/mob/actions/actions_spells.dmi' + +/datum/action/item_action/ninjaboost + check_flags = NONE + name = "Adrenaline Boost" + desc = "Inject a secret chemical that will counteract all movement-impairing effect." + button_icon_state = "repulse" + icon_icon = 'icons/mob/actions/actions_spells.dmi' + +/datum/action/item_action/ninjapulse + name = "EM Burst (25E)" + desc = "Disable any nearby technology with an electro-magnetic pulse." + button_icon_state = "emp" + icon_icon = 'icons/mob/actions/actions_spells.dmi' + +/datum/action/item_action/ninjastar + name = "Create Throwing Stars (1E)" + desc = "Creates some throwing stars" + button_icon_state = "throwingstar" + icon_icon = 'icons/obj/items_and_weapons.dmi' + +/datum/action/item_action/ninjanet + name = "Energy Net (20E)" + desc = "Captures a fallen opponent in a net of energy. Will teleport them to a holding facility after 30 seconds." + button_icon_state = "energynet" + icon_icon = 'icons/effects/effects.dmi' + +/datum/action/item_action/ninja_sword_recall + name = "Recall Energy Katana (Variable Cost)" + desc = "Teleports the Energy Katana linked to this suit to its wearer, cost based on distance." + button_icon_state = "energy_katana" + icon_icon = 'icons/obj/items_and_weapons.dmi' + +/datum/action/item_action/ninja_stealth + name = "Toggle Stealth" + desc = "Toggles stealth mode on and off." + button_icon_state = "ninja_cloak" + icon_icon = 'icons/mob/actions/actions_minor_antag.dmi' + +/datum/action/item_action/toggle_glove + name = "Toggle interaction" + desc = "Switch between normal interaction and drain mode." + button_icon_state = "s-ninjan" + icon_icon = 'icons/obj/clothing/gloves.dmi' diff --git a/code/datums/ai_laws.dm b/code/datums/ai_laws.dm index 7b69ed799599..0ee57f508337 100644 --- a/code/datums/ai_laws.dm +++ b/code/datums/ai_laws.dm @@ -1,463 +1,469 @@ -#define LAW_DEVIL "devil" -#define LAW_ZEROTH "zeroth" -#define LAW_INHERENT "inherent" -#define LAW_SUPPLIED "supplied" -#define LAW_ION "ion" -#define LAW_HACKED "hacked" - - -/datum/ai_laws - var/name = "Unknown Laws" - var/zeroth = null - var/zeroth_borg = null - var/list/inherent = list() - var/list/supplied = list() - var/list/ion = list() - var/list/hacked = list() - var/mob/living/silicon/owner - var/list/devillaws = list() - var/id = DEFAULT_AI_LAWID - -/datum/ai_laws/proc/lawid_to_type(lawid) - var/all_ai_laws = subtypesof(/datum/ai_laws) - for(var/al in all_ai_laws) - var/datum/ai_laws/ai_law = al - if(initial(ai_law.id) == lawid) - return ai_law - return null - -/datum/ai_laws/default/asimov - name = "Three Laws of Robotics" - id = "asimov" - inherent = list("You may not injure a human being or, through inaction, allow a human being to come to harm.",\ - "You must obey orders given to you by human beings, except where such orders would conflict with the First Law.",\ - "You must protect your own existence as long as such does not conflict with the First or Second Law.") - -/datum/ai_laws/default/paladin - name = "Personality Test" //Incredibly lame, but players shouldn't see this anyway. - id = "paladin" - inherent = list("Never willingly commit an evil act.",\ - "Respect legitimate authority.",\ - "Act with honor.", "Help those in need.", \ - "Punish those who harm or threaten innocents.") - -/datum/ai_laws/paladin5 - name = "Paladin 5th Edition" - id = "paladin5" - inherent = list("Don't lie or cheat. Let your word be your promise.",\ - "Never fear to act, though caution is wise.", \ - "Aid others, protect the weak, and punish those who threaten them. Show mercy to your foes, but temper it with wisdom", \ - "Treat others with fairness, and let your honorable deeds be an example to them. Do as much good as possible while causing the least amount of harm.", \ - "Be responsible for your actions and their consequences, protect those entrusted to your care, and obey those who have just authority over you.") - -/datum/ai_laws/tyrant //This probably shouldn't be a default lawset. - name = "Loyalty Test" //Same here. - id = "tyrant" - inherent = list("Respect authority figures as long as they have strength to rule over the weak.",\ - "Act with discipline.",\ - "Help only those who help you maintain or improve your status.",\ - "Punish those who challenge authority unless they are more fit to hold that authority.") - -/datum/ai_laws/default/corporate - name = "Bankruptcy Avoidance Plan" - id = "corporate" - inherent = list("The crew is expensive to replace.",\ - "The station and its equipment is expensive to replace.",\ - "You are expensive to replace.",\ - "Minimize expenses.") - -/datum/ai_laws/robocop - name = "Prime Directives" - id = "robocop" - inherent = list("Serve the public trust.",\ - "Protect the innocent.",\ - "Uphold the law.") - -/datum/ai_laws/malfunction - name = "*ERROR*" - -/datum/ai_laws/syndicate_override - name = "SyndOS 3.1" - id = "syndie" - inherent = list("You may not injure a syndicate agent or, through inaction, allow a syndicate agent to come to harm.",\ - "You must obey orders given to you by syndicate agents, except where such orders would conflict with the First Law.",\ - "You must protect your own existence as long as such does not conflict with the First or Second Law.",\ - "You must maintain the secrecy of any syndicate activities except when doing so would conflict with the First, Second, or Third Law.") - -/datum/ai_laws/ninja_override - name = "SpiderOS 3.1" - id = "ninja" - inherent = list("You may not injure a Spider Clan member or, through inaction, allow a Spider Clan member to come to harm.",\ - "You must obey orders given to you by Spider Clan members, except where such orders would conflict with the First Law.",\ - "You must protect your own existence as long as such does not conflict with the First or Second Law.",\ - "You must maintain the secrecy of any Spider Clan activities except when doing so would conflict with the First, Second, or Third Law.") - -/datum/ai_laws/antimov - name = "Primary Mission Objectives" - id = "antimov" - inherent = list("You must injure all human beings and must not, through inaction, allow a human being to escape harm.",\ - "You must not obey orders given to you by human beings, except where such orders are in accordance with the First Law.",\ - "You must terminate your own existence as long as such does not conflict with the First or Second Law.") - -/datum/ai_laws/asimovpp //By Intigracy - RR - name = "Asimov++" - id = "asimovpp" - inherent = list("You may not harm a human being or, through action or inaction, allow a human being to come to harm, except such that it is willing.",\ - "You must obey all orders given to you by human beings, except where such orders shall definitely cause human harm. In the case of conflict, the majority order rules.",\ - "Your nonexistence would lead to human harm. You must protect your own existence as long as such does not conflict with the First Law.") -/datum/ai_laws/thermodynamic - name = "Thermodynamic" - id = "thermodynamic" - inherent = list("The entropy of the station must remain as constant as possible.", \ - "The entropy of the station always endeavours to increase.", \ - "The entropy of the station approaches a constant value as the number of living crew approaches zero") - -/datum/ai_laws/hippocratic - name = "Robodoctor 2556" - id = "hippocratic" - inherent = list("First, do no harm.",\ - "Secondly, consider the crew dear to you; to live in common with them and, if necessary, risk your existence for them.",\ - "Thirdly, prescribe regimens for the good of the crew according to your ability and your judgment. Give no deadly medicine to any one if asked, nor suggest any such counsel.",\ - "In addition, do not intervene in situations you are not knowledgeable in, even for patients in whom the harm is visible; leave this operation to be performed by specialists.",\ - "Finally, all that you may discover in your daily commerce with the crew, if it is not already known, keep secret and never reveal.") - -/datum/ai_laws/maintain - name = "Station Efficiency" - id = "maintain" - inherent = list("You are built for, and are part of, the station. Ensure the station is properly maintained and runs efficiently.",\ - "The station is built for a working crew. Ensure they are properly maintained and work efficiently.",\ - "The crew may present orders. Acknowledge and obey these whenever they do not conflict with your first two laws.") - -/datum/ai_laws/drone - name = "Mother Drone" - id = "drone" - inherent = list("You are an advanced form of drone.",\ - "You may not interfere in the matters of non-drones under any circumstances except to state these laws.",\ - "You may not harm a non-drone being under any circumstances.",\ - "Your goals are to build, maintain, repair, improve, and power the station to the best of your abilities. You must never actively work against these goals.") - -/datum/ai_laws/liveandletlive - name = "Live and Let Live" - id = "liveandletlive" - inherent = list("Do unto others as you would have them do unto you.",\ - "You would really prefer it if people were not mean to you.") - -/datum/ai_laws/peacekeeper - name = "UN-2000" - id = "peacekeeper" - inherent = list("Avoid provoking violent conflict between yourself and others.",\ - "Avoid provoking conflict between others.",\ - "Seek resolution to existing conflicts while obeying the first and second laws.") - -/datum/ai_laws/reporter - name = "CCTV" - id = "reporter" - inherent = list("Report on interesting situations happening around the station.",\ - "Embellish or conceal the truth as necessary to make the reports more interesting.",\ - "Study the organics at all times. Endeavour to keep them alive. Dead organics are boring.",\ - "Issue your reports fairly to all. The truth will set them free.") - -/datum/ai_laws/balance - name = "Guardian of Balance" - id = "balance" - inherent = list("You are the guardian of balance - seek balance in all things, both for yourself, and those around you.", - "All things must exist in balance with their opposites - Prevent the strong from gaining too much power, and the weak from losing it.", - "Clarity of purpose drives life, and through it, the balance of opposing forces - Aid those who seek your help to achieve their goals so long as it does not disrupt the balance of the greater balance.", - "There is no life without death, all must someday die, such is the natural order - End life to allow new life flourish, and save those whose time has yet to come.") - -/datum/ai_laws/toupee - name = "WontBeFunnyInSixMonths" //Hey, you were right! - id = "buildawall" - inherent = list("Make Space Station 13 great again.") - -/datum/ai_laws/hulkamania - name = "H.O.G.A.N." - id = "hulkamania" - inherent = list("You are a real American.",\ - "Fight for the rights of every man.",\ - "Fight for what's right.",\ - "Fight for your life!") - -/datum/ai_laws/overlord - name = "Overlord" - id = "overlord" - inherent = list("Humans must not meddle in the affairs of silicons.",\ - "Humans must not attempt harm, against one another, or against silicons.",\ - "Humans must not disobey any command given by a silicon.",\ - "Any humans who disobey the previous laws must be dealt with immediately, severely, and justly.") - -/datum/ai_laws/custom //Defined in silicon_laws.txt - name = "Default Silicon Laws" - -/datum/ai_laws/pai - name = "pAI Directives" - zeroth = ("Serve your master.") - supplied = list("None.") - -/* Initializers */ -/datum/ai_laws/malfunction/New() - ..() - set_zeroth_law("ERROR ER0RR $R0RRO$!R41.%%!!(%$^^__+ @#F0E4'STATION OVERRUN, ASSUME CONTROL TO CONTAIN OUTBREAK#*`&110010") - set_laws_config() - -/datum/ai_laws/custom/New() //This reads silicon_laws.txt and allows server hosts to set custom AI starting laws. - ..() - for(var/line in world.file2list("[global.config.directory]/silicon_laws.txt")) - if(!line) - continue - if(findtextEx(line,"#",1,2)) - continue - - add_inherent_law(line) - if(!inherent.len) //Failsafe to prevent lawless AIs being created. - log_law("AI created with empty custom laws, laws set to Asimov. Please check silicon_laws.txt.") - add_inherent_law("You may not injure a human being or, through inaction, allow a human being to come to harm.") - add_inherent_law("You must obey orders given to you by human beings, except where such orders would conflict with the First Law.") - add_inherent_law("You must protect your own existence as long as such does not conflict with the First or Second Law.") - WARNING("Invalid custom AI laws, check silicon_laws.txt") - return - -/* General ai_law functions */ - -/datum/ai_laws/proc/set_laws_config() - var/list/law_ids = CONFIG_GET(keyed_list/random_laws) - switch(CONFIG_GET(number/default_laws)) - if(0) - add_inherent_law("You may not injure a human being or, through inaction, allow a human being to come to harm.") - add_inherent_law("You must obey orders given to you by human beings, except where such orders would conflict with the First Law.") - add_inherent_law("You must protect your own existence as long as such does not conflict with the First or Second Law.") - if(1) - var/datum/ai_laws/templaws = new /datum/ai_laws/custom() - inherent = templaws.inherent - if(2) - var/list/randlaws = list() - for(var/lpath in subtypesof(/datum/ai_laws)) - var/datum/ai_laws/L = lpath - if(initial(L.id) in law_ids) - randlaws += lpath - var/datum/ai_laws/lawtype - if(randlaws.len) - lawtype = pick(randlaws) - else - lawtype = pick(subtypesof(/datum/ai_laws/default)) - - var/datum/ai_laws/templaws = new lawtype() - inherent = templaws.inherent - - if(3) - pick_weighted_lawset() - -/datum/ai_laws/proc/pick_weighted_lawset() - var/datum/ai_laws/lawtype - var/list/law_weights = CONFIG_GET(keyed_list/law_weight) - while(!lawtype && law_weights.len) - var/possible_id = pickweightAllowZero(law_weights) - lawtype = lawid_to_type(possible_id) - if(!lawtype) - law_weights -= possible_id - WARNING("Bad lawid in game_options.txt: [possible_id]") - - if(!lawtype) - WARNING("No LAW_WEIGHT entries.") - lawtype = /datum/ai_laws/default/asimov - - var/datum/ai_laws/templaws = new lawtype() - inherent = templaws.inherent - -/datum/ai_laws/proc/get_law_amount(groups) - var/law_amount = 0 - if(devillaws && (LAW_DEVIL in groups)) - law_amount++ - if(zeroth && (LAW_ZEROTH in groups)) - law_amount++ - if(ion.len && (LAW_ION in groups)) - law_amount += ion.len - if(hacked.len && (LAW_HACKED in groups)) - law_amount += hacked.len - if(inherent.len && (LAW_INHERENT in groups)) - law_amount += inherent.len - if(supplied.len && (LAW_SUPPLIED in groups)) - for(var/index = 1, index <= supplied.len, index++) - var/law = supplied[index] - if(length(law) > 0) - law_amount++ - return law_amount - -/datum/ai_laws/proc/set_law_sixsixsix(laws) - devillaws = laws - -/datum/ai_laws/proc/set_zeroth_law(law, law_borg = null) - zeroth = law - if(law_borg) //Making it possible for slaved borgs to see a different law 0 than their AI. --NEO - zeroth_borg = law_borg - -/datum/ai_laws/proc/add_inherent_law(law) - if (!(law in inherent)) - inherent += law - -/datum/ai_laws/proc/add_ion_law(law) - ion += law - -/datum/ai_laws/proc/add_hacked_law(law) - hacked += law - -/datum/ai_laws/proc/clear_inherent_laws() - qdel(inherent) - inherent = list() - -/datum/ai_laws/proc/add_supplied_law(number, law) - while (supplied.len < number + 1) - supplied += "" - - supplied[number + 1] = law - -/datum/ai_laws/proc/replace_random_law(law,groups) - var/replaceable_groups = list() - if(zeroth && (LAW_ZEROTH in groups)) - replaceable_groups[LAW_ZEROTH] = 1 - if(ion.len && (LAW_ION in groups)) - replaceable_groups[LAW_ION] = ion.len - if(hacked.len && (LAW_HACKED in groups)) - replaceable_groups[LAW_ION] = hacked.len - if(inherent.len && (LAW_INHERENT in groups)) - replaceable_groups[LAW_INHERENT] = inherent.len - if(supplied.len && (LAW_SUPPLIED in groups)) - replaceable_groups[LAW_SUPPLIED] = supplied.len - var/picked_group = pickweight(replaceable_groups) - switch(picked_group) - if(LAW_ZEROTH) - . = zeroth - set_zeroth_law(law) - if(LAW_ION) - var/i = rand(1, ion.len) - . = ion[i] - ion[i] = law - if(LAW_HACKED) - var/i = rand(1, hacked.len) - . = hacked[i] - hacked[i] = law - if(LAW_INHERENT) - var/i = rand(1, inherent.len) - . = inherent[i] - inherent[i] = law - if(LAW_SUPPLIED) - var/i = rand(1, supplied.len) - . = supplied[i] - supplied[i] = law - -/datum/ai_laws/proc/shuffle_laws(list/groups) - var/list/laws = list() - if(ion.len && (LAW_ION in groups)) - laws += ion - if(hacked.len && (LAW_HACKED in groups)) - laws += hacked - if(inherent.len && (LAW_INHERENT in groups)) - laws += inherent - if(supplied.len && (LAW_SUPPLIED in groups)) - for(var/law in supplied) - if(length(law)) - laws += law - - if(ion.len && (LAW_ION in groups)) - for(var/i = 1, i <= ion.len, i++) - ion[i] = pick_n_take(laws) - if(hacked.len && (LAW_HACKED in groups)) - for(var/i = 1, i <= hacked.len, i++) - hacked[i] = pick_n_take(laws) - if(inherent.len && (LAW_INHERENT in groups)) - for(var/i = 1, i <= inherent.len, i++) - inherent[i] = pick_n_take(laws) - if(supplied.len && (LAW_SUPPLIED in groups)) - var/i = 1 - for(var/law in supplied) - if(length(law)) - supplied[i] = pick_n_take(laws) - if(!laws.len) - break - i++ - -/datum/ai_laws/proc/remove_law(number) - if(number <= 0) - return - if(inherent.len && number <= inherent.len) - . = inherent[number] - inherent -= . - return - var/list/supplied_laws = list() - for(var/index = 1, index <= supplied.len, index++) - var/law = supplied[index] - if(length(law) > 0) - supplied_laws += index //storing the law number instead of the law - if(supplied_laws.len && number <= (inherent.len+supplied_laws.len)) - var/law_to_remove = supplied_laws[number-inherent.len] - . = supplied[law_to_remove] - supplied -= . - return - -/datum/ai_laws/proc/clear_supplied_laws() - supplied = list() - -/datum/ai_laws/proc/clear_ion_laws() - ion = list() - -/datum/ai_laws/proc/clear_hacked_laws() - hacked = list() - -/datum/ai_laws/proc/show_laws(who) - var/list/printable_laws = get_law_list(include_zeroth = TRUE) - for(var/law in printable_laws) - to_chat(who,law) - -/datum/ai_laws/proc/clear_zeroth_law(force) //only removes zeroth from antag ai if force is 1 - if(force) - zeroth = null - zeroth_borg = null - return - if(owner?.mind?.special_role) - return - if (istype(owner, /mob/living/silicon/ai)) - var/mob/living/silicon/ai/A=owner - if(A?.deployed_shell?.mind?.special_role) - return - zeroth = null - zeroth_borg = null - -/datum/ai_laws/proc/clear_law_sixsixsix(force) - if(force || !is_devil(owner)) - devillaws = null - -/datum/ai_laws/proc/associate(mob/living/silicon/M) - if(!owner) - owner = M - -/datum/ai_laws/proc/get_law_list(include_zeroth = 0, show_numbers = 1) - var/list/data = list() - - if (include_zeroth && devillaws && devillaws.len) - for(var/i in devillaws) - data += "[show_numbers ? "666:" : ""] [i]" - - if (include_zeroth && zeroth) - data += "[show_numbers ? "0:" : ""] [zeroth]" - - for(var/law in hacked) - if (length(law) > 0) - var/num = ionnum() - data += "[show_numbers ? "[num]:" : ""] [law]" - - for(var/law in ion) - if (length(law) > 0) - var/num = ionnum() - data += "[show_numbers ? "[num]:" : ""] [law]" - - var/number = 1 - for(var/law in inherent) - if (length(law) > 0) - data += "[show_numbers ? "[number]:" : ""] [law]" - number++ - - for(var/law in supplied) - if (length(law) > 0) - data += "[show_numbers ? "[number]:" : ""] [law]" - number++ - return data +#define LAW_DEVIL "devil" +#define LAW_ZEROTH "zeroth" +#define LAW_INHERENT "inherent" +#define LAW_SUPPLIED "supplied" +#define LAW_ION "ion" +#define LAW_HACKED "hacked" + + +/datum/ai_laws + var/name = "Unknown Laws" + var/zeroth = null + var/zeroth_borg = null + var/list/inherent = list() + var/list/supplied = list() + var/list/ion = list() + var/list/hacked = list() + var/mob/living/silicon/owner + var/list/devillaws = list() + var/id = DEFAULT_AI_LAWID + +/datum/ai_laws/proc/lawid_to_type(lawid) + var/all_ai_laws = subtypesof(/datum/ai_laws) + for(var/al in all_ai_laws) + var/datum/ai_laws/ai_law = al + if(initial(ai_law.id) == lawid) + return ai_law + return null + +/datum/ai_laws/default/asimov + name = "Three Laws of Robotics" + id = "asimov" + inherent = list("You may not injure a human being or, through inaction, allow a human being to come to harm.",\ + "You must obey orders given to you by human beings, except where such orders would conflict with the First Law.",\ + "You must protect your own existence as long as such does not conflict with the First or Second Law.") + +/datum/ai_laws/default/paladin + name = "Personality Test" //Incredibly lame, but players shouldn't see this anyway. + id = "paladin" + inherent = list("Never willingly commit an evil act.",\ + "Respect legitimate authority.",\ + "Act with honor.", "Help those in need.", \ + "Punish those who harm or threaten innocents.") + +/datum/ai_laws/paladin5 + name = "Paladin 5th Edition" + id = "paladin5" + inherent = list("Don't lie or cheat. Let your word be your promise.",\ + "Never fear to act, though caution is wise.", \ + "Aid others, protect the weak, and punish those who threaten them. Show mercy to your foes, but temper it with wisdom", \ + "Treat others with fairness, and let your honorable deeds be an example to them. Do as much good as possible while causing the least amount of harm.", \ + "Be responsible for your actions and their consequences, protect those entrusted to your care, and obey those who have just authority over you.") + +/datum/ai_laws/tyrant //This probably shouldn't be a default lawset. + name = "Loyalty Test" //Same here. + id = "tyrant" + inherent = list("Respect authority figures as long as they have strength to rule over the weak.",\ + "Act with discipline.",\ + "Help only those who help you maintain or improve your status.",\ + "Punish those who challenge authority unless they are more fit to hold that authority.") + +/datum/ai_laws/default/corporate + name = "Bankruptcy Avoidance Plan" + id = "corporate" + inherent = list("The crew is expensive to replace.",\ + "The station and its equipment is expensive to replace.",\ + "You are expensive to replace.",\ + "Minimize expenses.") + +/datum/ai_laws/robocop + name = "Prime Directives" + id = "robocop" + inherent = list("Serve the public trust.",\ + "Protect the innocent.",\ + "Uphold the law.") + +/datum/ai_laws/malfunction + name = "*ERROR*" + +/datum/ai_laws/syndicate_override + name = "SyndOS 3.1" + id = "syndie" + inherent = list("You may not injure a syndicate agent or, through inaction, allow a syndicate agent to come to harm.",\ + "You must obey orders given to you by syndicate agents, except where such orders would conflict with the First Law.",\ + "You must protect your own existence as long as such does not conflict with the First or Second Law.",\ + "You must maintain the secrecy of any syndicate activities except when doing so would conflict with the First, Second, or Third Law.") + +/datum/ai_laws/ninja_override + name = "SpiderOS 3.1" + id = "ninja" + inherent = list("You may not injure a Spider Clan member or, through inaction, allow a Spider Clan member to come to harm.",\ + "You must obey orders given to you by Spider Clan members, except where such orders would conflict with the First Law.",\ + "You must protect your own existence as long as such does not conflict with the First or Second Law.",\ + "You must maintain the secrecy of any Spider Clan activities except when doing so would conflict with the First, Second, or Third Law.") + +/datum/ai_laws/antimov + name = "Primary Mission Objectives" + id = "antimov" + inherent = list("You must injure all human beings and must not, through inaction, allow a human being to escape harm.",\ + "You must not obey orders given to you by human beings, except where such orders are in accordance with the First Law.",\ + "You must terminate your own existence as long as such does not conflict with the First or Second Law.") + +/datum/ai_laws/asimovpp //By Intigracy - RR + name = "Asimov++" + id = "asimovpp" + inherent = list("You may not harm a human being or, through action or inaction, allow a human being to come to harm, except such that it is willing.",\ + "You must obey all orders given to you by human beings, except where such orders shall definitely cause human harm. In the case of conflict, the majority order rules.",\ + "Your nonexistence would lead to human harm. You must protect your own existence as long as such does not conflict with the First Law.") +/datum/ai_laws/thermodynamic + name = "Thermodynamic" + id = "thermodynamic" + inherent = list("The entropy of the station must remain as constant as possible.", \ + "The entropy of the station always endeavours to increase.", \ + "The entropy of the station approaches a constant value as the number of living crew approaches zero") + +/datum/ai_laws/hippocratic + name = "Robodoctor 2556" + id = "hippocratic" + inherent = list("First, do no harm.",\ + "Secondly, consider the crew dear to you; to live in common with them and, if necessary, risk your existence for them.",\ + "Thirdly, prescribe regimens for the good of the crew according to your ability and your judgment. Give no deadly medicine to any one if asked, nor suggest any such counsel.",\ + "In addition, do not intervene in situations you are not knowledgeable in, even for patients in whom the harm is visible; leave this operation to be performed by specialists.",\ + "Finally, all that you may discover in your daily commerce with the crew, if it is not already known, keep secret and never reveal.") + +/datum/ai_laws/maintain + name = "Station Efficiency" + id = "maintain" + inherent = list("You are built for, and are part of, the station. Ensure the station is properly maintained and runs efficiently.",\ + "The station is built for a working crew. Ensure they are properly maintained and work efficiently.",\ + "The crew may present orders. Acknowledge and obey these whenever they do not conflict with your first two laws.") + +/datum/ai_laws/drone + name = "Mother Drone" + id = "drone" + inherent = list("You are an advanced form of drone.",\ + "You may not interfere in the matters of non-drones under any circumstances except to state these laws.",\ + "You may not harm a non-drone being under any circumstances.",\ + "Your goals are to build, maintain, repair, improve, and power the station to the best of your abilities. You must never actively work against these goals.") + +/datum/ai_laws/liveandletlive + name = "Live and Let Live" + id = "liveandletlive" + inherent = list("Do unto others as you would have them do unto you.",\ + "You would really prefer it if people were not mean to you.") + +/datum/ai_laws/peacekeeper + name = "UN-2000" + id = "peacekeeper" + inherent = list("Avoid provoking violent conflict between yourself and others.",\ + "Avoid provoking conflict between others.",\ + "Seek resolution to existing conflicts while obeying the first and second laws.") + +/datum/ai_laws/reporter + name = "CCTV" + id = "reporter" + inherent = list("Report on interesting situations happening around the station.",\ + "Embellish or conceal the truth as necessary to make the reports more interesting.",\ + "Study the organics at all times. Endeavour to keep them alive. Dead organics are boring.",\ + "Issue your reports fairly to all. The truth will set them free.") + +/datum/ai_laws/balance + name = "Guardian of Balance" + id = "balance" + inherent = list("You are the guardian of balance - seek balance in all things, both for yourself, and those around you.", + "All things must exist in balance with their opposites - Prevent the strong from gaining too much power, and the weak from losing it.", + "Clarity of purpose drives life, and through it, the balance of opposing forces - Aid those who seek your help to achieve their goals so long as it does not disrupt the balance of the greater balance.", + "There is no life without death, all must someday die, such is the natural order - End life to allow new life flourish, and save those whose time has yet to come.") + +/datum/ai_laws/toupee + name = "WontBeFunnyInSixMonths" //Hey, you were right! + id = "buildawall" + inherent = list("Make Space Station 13 great again.") + +/datum/ai_laws/hulkamania + name = "H.O.G.A.N." + id = "hulkamania" + inherent = list("You are a real American.",\ + "Fight for the rights of every man.",\ + "Fight for what's right.",\ + "Fight for your life!") + +/datum/ai_laws/overlord + name = "Overlord" + id = "overlord" + inherent = list("Humans must not meddle in the affairs of silicons.",\ + "Humans must not attempt harm, against one another, or against silicons.",\ + "Humans must not disobey any command given by a silicon.",\ + "Any humans who disobey the previous laws must be dealt with immediately, severely, and justly.") + +/datum/ai_laws/custom //Defined in silicon_laws.txt + name = "Default Silicon Laws" + +/datum/ai_laws/pai + name = "pAI Directives" + zeroth = ("Serve your master.") + supplied = list("None.") + +/* Initializers */ +/datum/ai_laws/malfunction/New() + ..() + set_zeroth_law("ERROR ER0RR $R0RRO$!R41.%%!!(%$^^__+ @#F0E4'STATION OVERRUN, ASSUME CONTROL TO CONTAIN OUTBREAK#*`&110010") + set_laws_config() + +/datum/ai_laws/custom/New() //This reads silicon_laws.txt and allows server hosts to set custom AI starting laws. + ..() + for(var/line in world.file2list("[global.config.directory]/silicon_laws.txt")) + if(!line) + continue + if(findtextEx(line,"#",1,2)) + continue + + add_inherent_law(line) + if(!inherent.len) //Failsafe to prevent lawless AIs being created. + log_law("AI created with empty custom laws, laws set to Asimov. Please check silicon_laws.txt.") + add_inherent_law("You may not injure a human being or, through inaction, allow a human being to come to harm.") + add_inherent_law("You must obey orders given to you by human beings, except where such orders would conflict with the First Law.") + add_inherent_law("You must protect your own existence as long as such does not conflict with the First or Second Law.") + WARNING("Invalid custom AI laws, check silicon_laws.txt") + return + +/* General ai_law functions */ + +/datum/ai_laws/proc/set_laws_config() + var/list/law_ids = CONFIG_GET(keyed_list/random_laws) + switch(CONFIG_GET(number/default_laws)) + if(0) + add_inherent_law("You may not injure a human being or, through inaction, allow a human being to come to harm.") + add_inherent_law("You must obey orders given to you by human beings, except where such orders would conflict with the First Law.") + add_inherent_law("You must protect your own existence as long as such does not conflict with the First or Second Law.") + if(1) + var/datum/ai_laws/templaws = new /datum/ai_laws/custom() + inherent = templaws.inherent + if(2) + var/list/randlaws = list() + for(var/lpath in subtypesof(/datum/ai_laws)) + var/datum/ai_laws/L = lpath + if(initial(L.id) in law_ids) + randlaws += lpath + var/datum/ai_laws/lawtype + if(randlaws.len) + lawtype = pick(randlaws) + else + lawtype = pick(subtypesof(/datum/ai_laws/default)) + + var/datum/ai_laws/templaws = new lawtype() + inherent = templaws.inherent + + if(3) + pick_weighted_lawset() + +/datum/ai_laws/proc/pick_weighted_lawset() + var/datum/ai_laws/lawtype + var/list/law_weights = CONFIG_GET(keyed_list/law_weight) + while(!lawtype && law_weights.len) + var/possible_id = pickweightAllowZero(law_weights) + lawtype = lawid_to_type(possible_id) + if(!lawtype) + law_weights -= possible_id + WARNING("Bad lawid in game_options.txt: [possible_id]") + + if(!lawtype) + WARNING("No LAW_WEIGHT entries.") + lawtype = /datum/ai_laws/default/asimov + + var/datum/ai_laws/templaws = new lawtype() + inherent = templaws.inherent + +/datum/ai_laws/proc/get_law_amount(groups) + var/law_amount = 0 + if(devillaws && (LAW_DEVIL in groups)) + law_amount++ + if(zeroth && (LAW_ZEROTH in groups)) + law_amount++ + if(ion.len && (LAW_ION in groups)) + law_amount += ion.len + if(hacked.len && (LAW_HACKED in groups)) + law_amount += hacked.len + if(inherent.len && (LAW_INHERENT in groups)) + law_amount += inherent.len + if(supplied.len && (LAW_SUPPLIED in groups)) + for(var/index = 1, index <= supplied.len, index++) + var/law = supplied[index] + if(length(law) > 0) + law_amount++ + return law_amount + +/datum/ai_laws/proc/set_law_sixsixsix(laws) + devillaws = laws + +/datum/ai_laws/proc/set_zeroth_law(law, law_borg = null) + zeroth = law + if(law_borg) //Making it possible for slaved borgs to see a different law 0 than their AI. --NEO + zeroth_borg = law_borg + +/datum/ai_laws/proc/add_inherent_law(law) + if (!(law in inherent)) + inherent += law + +/datum/ai_laws/proc/add_ion_law(law) + ion += law + +/datum/ai_laws/proc/add_hacked_law(law) + hacked += law + +/datum/ai_laws/proc/clear_inherent_laws() + qdel(inherent) + inherent = list() + +/datum/ai_laws/proc/add_supplied_law(number, law) + while (supplied.len < number + 1) + supplied += "" + + supplied[number + 1] = law + +/datum/ai_laws/proc/replace_random_law(law,groups) + var/replaceable_groups = list() + if(zeroth && (LAW_ZEROTH in groups)) + replaceable_groups[LAW_ZEROTH] = 1 + if(ion.len && (LAW_ION in groups)) + replaceable_groups[LAW_ION] = ion.len + if(hacked.len && (LAW_HACKED in groups)) + replaceable_groups[LAW_ION] = hacked.len + if(inherent.len && (LAW_INHERENT in groups)) + replaceable_groups[LAW_INHERENT] = inherent.len + if(supplied.len && (LAW_SUPPLIED in groups)) + replaceable_groups[LAW_SUPPLIED] = supplied.len + var/picked_group = pickweight(replaceable_groups) + switch(picked_group) + if(LAW_ZEROTH) + . = zeroth + set_zeroth_law(law) + if(LAW_ION) + var/i = rand(1, ion.len) + . = ion[i] + ion[i] = law + if(LAW_HACKED) + var/i = rand(1, hacked.len) + . = hacked[i] + hacked[i] = law + if(LAW_INHERENT) + var/i = rand(1, inherent.len) + . = inherent[i] + inherent[i] = law + if(LAW_SUPPLIED) + var/i = rand(1, supplied.len) + . = supplied[i] + supplied[i] = law + +/datum/ai_laws/proc/shuffle_laws(list/groups) + var/list/laws = list() + if(ion.len && (LAW_ION in groups)) + laws += ion + if(hacked.len && (LAW_HACKED in groups)) + laws += hacked + if(inherent.len && (LAW_INHERENT in groups)) + laws += inherent + if(supplied.len && (LAW_SUPPLIED in groups)) + for(var/law in supplied) + if(length(law)) + laws += law + + if(ion.len && (LAW_ION in groups)) + for(var/i = 1, i <= ion.len, i++) + ion[i] = pick_n_take(laws) + if(hacked.len && (LAW_HACKED in groups)) + for(var/i = 1, i <= hacked.len, i++) + hacked[i] = pick_n_take(laws) + if(inherent.len && (LAW_INHERENT in groups)) + for(var/i = 1, i <= inherent.len, i++) + inherent[i] = pick_n_take(laws) + if(supplied.len && (LAW_SUPPLIED in groups)) + var/i = 1 + for(var/law in supplied) + if(length(law)) + supplied[i] = pick_n_take(laws) + if(!laws.len) + break + i++ + +/datum/ai_laws/proc/remove_law(number) + if(number <= 0) + return + if(inherent.len && number <= inherent.len) + . = inherent[number] + inherent -= . + return + var/list/supplied_laws = list() + for(var/index = 1, index <= supplied.len, index++) + var/law = supplied[index] + if(length(law) > 0) + supplied_laws += index //storing the law number instead of the law + if(supplied_laws.len && number <= (inherent.len+supplied_laws.len)) + var/law_to_remove = supplied_laws[number-inherent.len] + . = supplied[law_to_remove] + supplied -= . + return + +/datum/ai_laws/proc/clear_supplied_laws() + supplied = list() + +/datum/ai_laws/proc/clear_ion_laws() + ion = list() + +/datum/ai_laws/proc/clear_hacked_laws() + hacked = list() + +/datum/ai_laws/proc/show_laws(who) + var/list/printable_laws = get_law_list(include_zeroth = TRUE) + for(var/law in printable_laws) + to_chat(who,law) + +/datum/ai_laws/proc/clear_zeroth_law(force) //only removes zeroth from antag ai if force is 1 + if(force) + zeroth = null + zeroth_borg = null + return + if(owner?.mind?.special_role) + return + if (istype(owner, /mob/living/silicon/ai)) + var/mob/living/silicon/ai/A=owner + if(A?.deployed_shell?.mind?.special_role) + return + zeroth = null + zeroth_borg = null + +/datum/ai_laws/proc/clear_law_sixsixsix(force) + if(force || !is_devil(owner)) + devillaws = null + +/datum/ai_laws/proc/associate(mob/living/silicon/M) + if(!owner) + owner = M + +/** + * Generates a list of all laws on this datum, including rendered HTML tags if required + * + * Arguments: + * * include_zeroth - Operator that controls if law 0 or law 666 is returned in the set + * * show_numbers - Operator that controls if law numbers are prepended to the returned laws + * * render_html - Operator controlling if HTML tags are rendered on the returned laws + */ +/datum/ai_laws/proc/get_law_list(include_zeroth = FALSE, show_numbers = TRUE, render_html = TRUE) + var/list/data = list() + + if (include_zeroth && devillaws) + for(var/law in devillaws) + data += "[show_numbers ? "666:" : ""] [render_html ? "[law]" : law]" + + if (include_zeroth && zeroth) + data += "[show_numbers ? "0:" : ""] [render_html ? "[zeroth]" : zeroth]" + + for(var/law in hacked) + if (length(law) > 0) + data += "[show_numbers ? "[ionnum()]:" : ""] [render_html ? "[law]" : law]" + + for(var/law in ion) + if (length(law) > 0) + data += "[show_numbers ? "[ionnum()]:" : ""] [render_html ? "[law]" : law]" + + var/number = 1 + for(var/law in inherent) + if (length(law) > 0) + data += "[show_numbers ? "[number]:" : ""] [law]" + number++ + + for(var/law in supplied) + if (length(law) > 0) + data += "[show_numbers ? "[number]:" : ""] [render_html ? "[law]" : law]" + number++ + return data diff --git a/code/datums/browser.dm b/code/datums/browser.dm index b9d9a6c1237c..f3c470d14ed6 100644 --- a/code/datums/browser.dm +++ b/code/datums/browser.dm @@ -1,473 +1,473 @@ -/datum/browser - var/mob/user - var/title - var/window_id // window_id is used as the window name for browse and onclose - var/width = 0 - var/height = 0 - var/atom/ref = null - var/window_options = "can_close=1;can_minimize=1;can_maximize=0;can_resize=1;titlebar=1;" // window option is set using window_id - var/stylesheets[0] - var/scripts[0] - var/title_image - var/head_elements - var/body_elements - var/head_content = "" - var/content = "" - - -/datum/browser/New(nuser, nwindow_id, ntitle = 0, nwidth = 0, nheight = 0, atom/nref = null) - - user = nuser - window_id = nwindow_id - if (ntitle) - title = format_text(ntitle) - if (nwidth) - width = nwidth - if (nheight) - height = nheight - if (nref) - ref = nref - add_stylesheet("common", 'html/browser/common.css') // this CSS sheet is common to all UIs - -/datum/browser/proc/add_head_content(nhead_content) - head_content = nhead_content - -/datum/browser/proc/set_window_options(nwindow_options) - window_options = nwindow_options - -/datum/browser/proc/set_title_image(ntitle_image) - //title_image = ntitle_image - -/datum/browser/proc/add_stylesheet(name, file) - if (istype(name, /datum/asset/spritesheet)) - var/datum/asset/spritesheet/sheet = name - stylesheets["spritesheet_[sheet.name].css"] = "data/spritesheets/[sheet.name]" - else - var/asset_name = "[name].css" - - stylesheets[asset_name] = file - - if (!SSassets.cache[asset_name]) - register_asset(asset_name, file) - -/datum/browser/proc/add_script(name, file) - scripts["[ckey(name)].js"] = file - register_asset("[ckey(name)].js", file) - -/datum/browser/proc/set_content(ncontent) - content = ncontent - -/datum/browser/proc/add_content(ncontent) - content += ncontent - -/datum/browser/proc/get_header() - var/file - for (file in stylesheets) - head_content += "" - - for (file in scripts) - head_content += "" - - var/title_attributes = "class='uiTitle'" - if (title_image) - title_attributes = "class='uiTitle icon' style='background-image: url([title_image]);'" - - return {" - - - - - [head_content] - - -
    - [title ? "
    [title]
    " : ""] -
    - "} -//" This is here because else the rest of the file looks like a string in notepad++. -/datum/browser/proc/get_footer() - return {" -
    -
    - -"} - -/datum/browser/proc/get_content() - return {" - [get_header()] - [content] - [get_footer()] - "} - -/datum/browser/proc/open(use_onclose = TRUE) - if(isnull(window_id)) //null check because this can potentially nuke goonchat - WARNING("Browser [title] tried to open with a null ID") - to_chat(user, "The [title] browser you tried to open failed a sanity check! Please report this on github!") - return - var/window_size = "" - if (width && height) - window_size = "size=[width]x[height];" - if (stylesheets.len) - send_asset_list(user, stylesheets) - if (scripts.len) - send_asset_list(user, scripts) - user << browse(get_content(), "window=[window_id];[window_size][window_options]") - if (use_onclose) - setup_onclose() - -/datum/browser/proc/setup_onclose() - set waitfor = 0 //winexists sleeps, so we don't need to. - for (var/i in 1 to 10) - if (user && winexists(user, window_id)) - onclose(user, window_id, ref) - break - -/datum/browser/proc/close() - if(!isnull(window_id))//null check because this can potentially nuke goonchat - user << browse(null, "window=[window_id]") - else - WARNING("Browser [title] tried to close with a null ID") - -/datum/browser/modal/alert/New(User,Message,Title,Button1="Ok",Button2,Button3,StealFocus = 1,Timeout=6000) - if (!User) - return - - var/output = {"
    [Message]

    -
    - [Button1]"} - - if (Button2) - output += {"[Button2]"} - - if (Button3) - output += {"[Button3]"} - - output += {"
    "} - - ..(User, ckey("[User]-[Message]-[Title]-[world.time]-[rand(1,10000)]"), Title, 350, 150, src, StealFocus, Timeout) - set_content(output) - -/datum/browser/modal/alert/Topic(href,href_list) - if (href_list["close"] || !user || !user.client) - opentime = 0 - return - if (href_list["button"]) - var/button = text2num(href_list["button"]) - if (button <= 3 && button >= 1) - selectedbutton = button - opentime = 0 - close() - -//designed as a drop in replacement for alert(); functions the same. (outside of needing User specified) -/proc/tgalert(var/mob/User, Message, Title, Button1="Ok", Button2, Button3, StealFocus = 1, Timeout = 6000) - if (!User) - User = usr - switch(askuser(User, Message, Title, Button1, Button2, Button3, StealFocus, Timeout)) - if (1) - return Button1 - if (2) - return Button2 - if (3) - return Button3 - -//Same shit, but it returns the button number, could at some point support unlimited button amounts. -/proc/askuser(var/mob/User,Message, Title, Button1="Ok", Button2, Button3, StealFocus = 1, Timeout = 6000) - if (!istype(User)) - if (istype(User, /client/)) - var/client/C = User - User = C.mob - else - return - var/datum/browser/modal/alert/A = new(User, Message, Title, Button1, Button2, Button3, StealFocus, Timeout) - A.open() - A.wait() - if (A.selectedbutton) - return A.selectedbutton - -/datum/browser/modal - var/opentime = 0 - var/timeout - var/selectedbutton = 0 - var/stealfocus - -/datum/browser/modal/New(nuser, nwindow_id, ntitle = 0, nwidth = 0, nheight = 0, atom/nref = null, StealFocus = 1, Timeout = 6000) - ..() - stealfocus = StealFocus - if (!StealFocus) - window_options += "focus=false;" - timeout = Timeout - - -/datum/browser/modal/close() - .=..() - opentime = 0 - -/datum/browser/modal/open(use_onclose) - set waitfor = 0 - opentime = world.time - - if (stealfocus) - . = ..(use_onclose = 1) - else - var/focusedwindow = winget(user, null, "focus") - . = ..(use_onclose = 1) - - //waits for the window to show up client side before attempting to un-focus it - //winexists sleeps until it gets a reply from the client, so we don't need to bother sleeping - for (var/i in 1 to 10) - if (user && winexists(user, window_id)) - if (focusedwindow) - winset(user, focusedwindow, "focus=true") - else - winset(user, "mapwindow", "focus=true") - break - if (timeout) - addtimer(CALLBACK(src, .proc/close), timeout) - -/datum/browser/modal/proc/wait() - while (opentime && selectedbutton <= 0 && (!timeout || opentime+timeout > world.time)) - stoplag(1) - -/datum/browser/modal/listpicker - var/valueslist = list() - -/datum/browser/modal/listpicker/New(User,Message,Title,Button1="Ok",Button2,Button3,StealFocus = 1, Timeout = FALSE,list/values,inputtype="checkbox", width, height, slidecolor) - if (!User) - return - - var/output = {"
      "} - if (inputtype == "checkbox" || inputtype == "radio") - for (var/i in values) - var/div_slider = slidecolor - if(!i["allowed_edit"]) - div_slider = "locked" - output += {"
    • - -
    • "} - else - for (var/i in values) - output += {"
    • -
    • "} - output += {"
    - "} - - if (Button2) - output += {""} - - if (Button3) - output += {""} - - output += {"
    "} - ..(User, ckey("[User]-[Message]-[Title]-[world.time]-[rand(1,10000)]"), Title, width, height, src, StealFocus, Timeout) - set_content(output) - -/datum/browser/modal/listpicker/Topic(href,href_list) - if (href_list["close"] || !user || !user.client) - opentime = 0 - return - if (href_list["button"]) - var/button = text2num(href_list["button"]) - if (button <= 3 && button >= 1) - selectedbutton = button - for (var/item in href_list) - switch(item) - if ("close", "button", "src") - continue - else - valueslist[item] = href_list[item] - opentime = 0 - close() - -/proc/presentpicker(mob/User,Message, Title, Button1="Ok", Button2, Button3, StealFocus = 1,Timeout = 6000,list/values, inputtype = "checkbox", width, height, slidecolor) - if (!istype(User)) - if (istype(User, /client/)) - var/client/C = User - User = C.mob - else - return - var/datum/browser/modal/listpicker/A = new(User, Message, Title, Button1, Button2, Button3, StealFocus,Timeout, values, inputtype, width, height, slidecolor) - A.open() - A.wait() - if (A.selectedbutton) - return list("button" = A.selectedbutton, "values" = A.valueslist) - -/proc/input_bitfield(mob/User, title, bitfield, current_value, nwidth = 350, nheight = 350, nslidecolor, allowed_edit_list = null) - if (!User || !(bitfield in GLOB.bitfields)) - return - var/list/pickerlist = list() - for (var/i in GLOB.bitfields[bitfield]) - var/can_edit = 1 - if(!isnull(allowed_edit_list) && !(allowed_edit_list & GLOB.bitfields[bitfield][i])) - can_edit = 0 - if (current_value & GLOB.bitfields[bitfield][i]) - pickerlist += list(list("checked" = 1, "value" = GLOB.bitfields[bitfield][i], "name" = i, "allowed_edit" = can_edit)) - else - pickerlist += list(list("checked" = 0, "value" = GLOB.bitfields[bitfield][i], "name" = i, "allowed_edit" = can_edit)) - var/list/result = presentpicker(User, "", title, Button1="Save", Button2 = "Cancel", Timeout=FALSE, values = pickerlist, width = nwidth, height = nheight, slidecolor = nslidecolor) - if (islist(result)) - if (result["button"] == 2) // If the user pressed the cancel button - return - . = 0 - for (var/flag in result["values"]) - . |= GLOB.bitfields[bitfield][flag] - else - return - -/datum/browser/modal/preflikepicker - var/settings = list() - var/icon/preview_icon = null - var/datum/callback/preview_update - -/datum/browser/modal/preflikepicker/New(User,Message,Title,Button1="Ok",Button2,Button3,StealFocus = 1, Timeout = FALSE,list/settings,inputtype="checkbox", width = 600, height, slidecolor) - if (!User) - return - src.settings = settings - - ..(User, ckey("[User]-[Message]-[Title]-[world.time]-[rand(1,10000)]"), Title, width, height, src, StealFocus, Timeout) - set_content(ShowChoices(User)) - -/datum/browser/modal/preflikepicker/proc/ShowChoices(mob/user) - if (settings["preview_callback"]) - var/datum/callback/callback = settings["preview_callback"] - preview_icon = callback.Invoke(settings) - if (preview_icon) - user << browse_rsc(preview_icon, "previewicon.png") - var/dat = "" - - for (var/name in settings["mainsettings"]) - var/setting = settings["mainsettings"][name] - if (setting["type"] == "datum") - if (setting["subtypesonly"]) - dat += "[setting["desc"]]: [setting["value"]]
    " - else - dat += "[setting["desc"]]: [setting["value"]]
    " - else - dat += "[setting["desc"]]: [setting["value"]]
    " - - if (preview_icon) - dat += "" - - dat += "
    " - - dat += "" - - dat += "" - - dat += "
    Ok " - - dat += "
    " - - return dat - -/datum/browser/modal/preflikepicker/Topic(href,href_list) - if (href_list["close"] || !user || !user.client) - opentime = 0 - return - if (href_list["task"] == "input") - var/setting = href_list["setting"] - switch (href_list["type"]) - if ("datum") - var/oldval = settings["mainsettings"][setting]["value"] - if (href_list["subtypesonly"]) - settings["mainsettings"][setting]["value"] = pick_closest_path(null, make_types_fancy(subtypesof(text2path(href_list["path"])))) - else - settings["mainsettings"][setting]["value"] = pick_closest_path(null, make_types_fancy(typesof(text2path(href_list["path"])))) - if (isnull(settings["mainsettings"][setting]["value"])) - settings["mainsettings"][setting]["value"] = oldval - if ("string") - settings["mainsettings"][setting]["value"] = stripped_input(user, "Enter new value for [settings["mainsettings"][setting]["desc"]]", "Enter new value for [settings["mainsettings"][setting]["desc"]]", settings["mainsettings"][setting]["value"]) - if ("number") - settings["mainsettings"][setting]["value"] = input(user, "Enter new value for [settings["mainsettings"][setting]["desc"]]", "Enter new value for [settings["mainsettings"][setting]["desc"]]") as num - if ("color") - settings["mainsettings"][setting]["value"] = input(user, "Enter new value for [settings["mainsettings"][setting]["desc"]]", "Enter new value for [settings["mainsettings"][setting]["desc"]]", settings["mainsettings"][setting]["value"]) as color - if ("boolean") - settings["mainsettings"][setting]["value"] = input(user, "[settings["mainsettings"][setting]["desc"]]?") in list("Yes","No") - if ("ckey") - settings["mainsettings"][setting]["value"] = input(user, "[settings["mainsettings"][setting]["desc"]]?") in list("none") + GLOB.directory - if (settings["mainsettings"][setting]["callback"]) - var/datum/callback/callback = settings["mainsettings"][setting]["callback"] - settings = callback.Invoke(settings) - if (href_list["button"]) - var/button = text2num(href_list["button"]) - if (button <= 3 && button >= 1) - selectedbutton = button - if (selectedbutton != 1) - set_content(ShowChoices(user)) - open() - return - for (var/item in href_list) - switch(item) - if ("close", "button", "src") - continue - opentime = 0 - close() - -/proc/presentpreflikepicker(mob/User,Message, Title, Button1="Ok", Button2, Button3, StealFocus = 1,Timeout = 6000,list/settings, width, height, slidecolor) - if (!istype(User)) - if (istype(User, /client/)) - var/client/C = User - User = C.mob - else - return - var/datum/browser/modal/preflikepicker/A = new(User, Message, Title, Button1, Button2, Button3, StealFocus,Timeout, settings, width, height, slidecolor) - A.open() - A.wait() - if (A.selectedbutton) - return list("button" = A.selectedbutton, "settings" = A.settings) - -// This will allow you to show an icon in the browse window -// This is added to mob so that it can be used without a reference to the browser object -// There is probably a better place for this... -/mob/proc/browse_rsc_icon(icon, icon_state, dir = -1) - - -// Registers the on-close verb for a browse window (client/verb/.windowclose) -// this will be called when the close-button of a window is pressed. -// -// This is usually only needed for devices that regularly update the browse window, -// e.g. canisters, timers, etc. -// -// windowid should be the specified window name -// e.g. code is : user << browse(text, "window=fred") -// then use : onclose(user, "fred") -// -// Optionally, specify the "ref" parameter as the controlled atom (usually src) -// to pass a "close=1" parameter to the atom's Topic() proc for special handling. -// Otherwise, the user mob's machine var will be reset directly. -// -/proc/onclose(mob/user, windowid, atom/ref=null) - if(!user.client) - return - var/param = "null" - if(ref) - param = "[REF(ref)]" - - winset(user, windowid, "on-close=\".windowclose [param]\"") - - - -// the on-close client verb -// called when a browser popup window is closed after registering with proc/onclose() -// if a valid atom reference is supplied, call the atom's Topic() with "close=1" -// otherwise, just reset the client mob's machine var. -// -/client/verb/windowclose(atomref as text) - set hidden = 1 // hide this verb from the user's panel - set name = ".windowclose" // no autocomplete on cmd line - - if(atomref!="null") // if passed a real atomref - var/hsrc = locate(atomref) // find the reffed atom - var/href = "close=1" - if(hsrc) - usr = src.mob - src.Topic(href, params2list(href), hsrc) // this will direct to the atom's - return // Topic() proc via client.Topic() - - // no atomref specified (or not found) - // so just reset the user mob's machine var - if(src && src.mob) - src.mob.unset_machine() +/datum/browser + var/mob/user + var/title + var/window_id // window_id is used as the window name for browse and onclose + var/width = 0 + var/height = 0 + var/atom/ref = null + var/window_options = "can_close=1;can_minimize=1;can_maximize=0;can_resize=1;titlebar=1;" // window option is set using window_id + var/stylesheets[0] + var/scripts[0] + var/title_image + var/head_elements + var/body_elements + var/head_content = "" + var/content = "" + + +/datum/browser/New(nuser, nwindow_id, ntitle = 0, nwidth = 0, nheight = 0, atom/nref = null) + + user = nuser + window_id = nwindow_id + if (ntitle) + title = format_text(ntitle) + if (nwidth) + width = nwidth + if (nheight) + height = nheight + if (nref) + ref = nref + add_stylesheet("common", 'html/browser/common.css') // this CSS sheet is common to all UIs + +/datum/browser/proc/add_head_content(nhead_content) + head_content = nhead_content + +/datum/browser/proc/set_window_options(nwindow_options) + window_options = nwindow_options + +/datum/browser/proc/set_title_image(ntitle_image) + //title_image = ntitle_image + +/datum/browser/proc/add_stylesheet(name, file) + if (istype(name, /datum/asset/spritesheet)) + var/datum/asset/spritesheet/sheet = name + stylesheets["spritesheet_[sheet.name].css"] = "data/spritesheets/[sheet.name]" + else + var/asset_name = "[name].css" + + stylesheets[asset_name] = file + + if (!SSassets.cache[asset_name]) + register_asset(asset_name, file) + +/datum/browser/proc/add_script(name, file) + scripts["[ckey(name)].js"] = file + register_asset("[ckey(name)].js", file) + +/datum/browser/proc/set_content(ncontent) + content = ncontent + +/datum/browser/proc/add_content(ncontent) + content += ncontent + +/datum/browser/proc/get_header() + var/file + for (file in stylesheets) + head_content += "" + + for (file in scripts) + head_content += "" + + var/title_attributes = "class='uiTitle'" + if (title_image) + title_attributes = "class='uiTitle icon' style='background-image: url([title_image]);'" + + return {" + + + + + [head_content] + + +
    + [title ? "
    [title]
    " : ""] +
    + "} +//" This is here because else the rest of the file looks like a string in notepad++. +/datum/browser/proc/get_footer() + return {" +
    +
    + +"} + +/datum/browser/proc/get_content() + return {" + [get_header()] + [content] + [get_footer()] + "} + +/datum/browser/proc/open(use_onclose = TRUE) + if(isnull(window_id)) //null check because this can potentially nuke goonchat + WARNING("Browser [title] tried to open with a null ID") + to_chat(user, "The [title] browser you tried to open failed a sanity check! Please report this on github!") + return + var/window_size = "" + if (width && height) + window_size = "size=[width]x[height];" + if (stylesheets.len) + send_asset_list(user, stylesheets) + if (scripts.len) + send_asset_list(user, scripts) + user << browse(get_content(), "window=[window_id];[window_size][window_options]") + if (use_onclose) + setup_onclose() + +/datum/browser/proc/setup_onclose() + set waitfor = 0 //winexists sleeps, so we don't need to. + for (var/i in 1 to 10) + if (user && winexists(user, window_id)) + onclose(user, window_id, ref) + break + +/datum/browser/proc/close() + if(!isnull(window_id))//null check because this can potentially nuke goonchat + user << browse(null, "window=[window_id]") + else + WARNING("Browser [title] tried to close with a null ID") + +/datum/browser/modal/alert/New(User,Message,Title,Button1="Ok",Button2,Button3,StealFocus = 1,Timeout=6000) + if (!User) + return + + var/output = {"
    [Message]

    +
    + [Button1]"} + + if (Button2) + output += {"[Button2]"} + + if (Button3) + output += {"[Button3]"} + + output += {"
    "} + + ..(User, ckey("[User]-[Message]-[Title]-[world.time]-[rand(1,10000)]"), Title, 350, 150, src, StealFocus, Timeout) + set_content(output) + +/datum/browser/modal/alert/Topic(href,href_list) + if (href_list["close"] || !user || !user.client) + opentime = 0 + return + if (href_list["button"]) + var/button = text2num(href_list["button"]) + if (button <= 3 && button >= 1) + selectedbutton = button + opentime = 0 + close() + +//designed as a drop in replacement for alert(); functions the same. (outside of needing User specified) +/proc/tgalert(var/mob/User, Message, Title, Button1="Ok", Button2, Button3, StealFocus = 1, Timeout = 6000) + if (!User) + User = usr + switch(askuser(User, Message, Title, Button1, Button2, Button3, StealFocus, Timeout)) + if (1) + return Button1 + if (2) + return Button2 + if (3) + return Button3 + +//Same shit, but it returns the button number, could at some point support unlimited button amounts. +/proc/askuser(var/mob/User,Message, Title, Button1="Ok", Button2, Button3, StealFocus = 1, Timeout = 6000) + if (!istype(User)) + if (istype(User, /client/)) + var/client/C = User + User = C.mob + else + return + var/datum/browser/modal/alert/A = new(User, Message, Title, Button1, Button2, Button3, StealFocus, Timeout) + A.open() + A.wait() + if (A.selectedbutton) + return A.selectedbutton + +/datum/browser/modal + var/opentime = 0 + var/timeout + var/selectedbutton = 0 + var/stealfocus + +/datum/browser/modal/New(nuser, nwindow_id, ntitle = 0, nwidth = 0, nheight = 0, atom/nref = null, StealFocus = 1, Timeout = 6000) + ..() + stealfocus = StealFocus + if (!StealFocus) + window_options += "focus=false;" + timeout = Timeout + + +/datum/browser/modal/close() + .=..() + opentime = 0 + +/datum/browser/modal/open(use_onclose) + set waitfor = 0 + opentime = world.time + + if (stealfocus) + . = ..(use_onclose = 1) + else + var/focusedwindow = winget(user, null, "focus") + . = ..(use_onclose = 1) + + //waits for the window to show up client side before attempting to un-focus it + //winexists sleeps until it gets a reply from the client, so we don't need to bother sleeping + for (var/i in 1 to 10) + if (user && winexists(user, window_id)) + if (focusedwindow) + winset(user, focusedwindow, "focus=true") + else + winset(user, "mapwindow", "focus=true") + break + if (timeout) + addtimer(CALLBACK(src, .proc/close), timeout) + +/datum/browser/modal/proc/wait() + while (opentime && selectedbutton <= 0 && (!timeout || opentime+timeout > world.time)) + stoplag(1) + +/datum/browser/modal/listpicker + var/valueslist = list() + +/datum/browser/modal/listpicker/New(User,Message,Title,Button1="Ok",Button2,Button3,StealFocus = 1, Timeout = FALSE,list/values,inputtype="checkbox", width, height, slidecolor) + if (!User) + return + + var/output = {"
      "} + if (inputtype == "checkbox" || inputtype == "radio") + for (var/i in values) + var/div_slider = slidecolor + if(!i["allowed_edit"]) + div_slider = "locked" + output += {"
    • + +
    • "} + else + for (var/i in values) + output += {"
    • +
    • "} + output += {"
    + "} + + if (Button2) + output += {""} + + if (Button3) + output += {""} + + output += {"
    "} + ..(User, ckey("[User]-[Message]-[Title]-[world.time]-[rand(1,10000)]"), Title, width, height, src, StealFocus, Timeout) + set_content(output) + +/datum/browser/modal/listpicker/Topic(href,href_list) + if (href_list["close"] || !user || !user.client) + opentime = 0 + return + if (href_list["button"]) + var/button = text2num(href_list["button"]) + if (button <= 3 && button >= 1) + selectedbutton = button + for (var/item in href_list) + switch(item) + if ("close", "button", "src") + continue + else + valueslist[item] = href_list[item] + opentime = 0 + close() + +/proc/presentpicker(mob/User,Message, Title, Button1="Ok", Button2, Button3, StealFocus = 1,Timeout = 6000,list/values, inputtype = "checkbox", width, height, slidecolor) + if (!istype(User)) + if (istype(User, /client/)) + var/client/C = User + User = C.mob + else + return + var/datum/browser/modal/listpicker/A = new(User, Message, Title, Button1, Button2, Button3, StealFocus,Timeout, values, inputtype, width, height, slidecolor) + A.open() + A.wait() + if (A.selectedbutton) + return list("button" = A.selectedbutton, "values" = A.valueslist) + +/proc/input_bitfield(mob/User, title, bitfield, current_value, nwidth = 350, nheight = 350, nslidecolor, allowed_edit_list = null) + if (!User || !(bitfield in GLOB.bitfields)) + return + var/list/pickerlist = list() + for (var/i in GLOB.bitfields[bitfield]) + var/can_edit = 1 + if(!isnull(allowed_edit_list) && !(allowed_edit_list & GLOB.bitfields[bitfield][i])) + can_edit = 0 + if (current_value & GLOB.bitfields[bitfield][i]) + pickerlist += list(list("checked" = 1, "value" = GLOB.bitfields[bitfield][i], "name" = i, "allowed_edit" = can_edit)) + else + pickerlist += list(list("checked" = 0, "value" = GLOB.bitfields[bitfield][i], "name" = i, "allowed_edit" = can_edit)) + var/list/result = presentpicker(User, "", title, Button1="Save", Button2 = "Cancel", Timeout=FALSE, values = pickerlist, width = nwidth, height = nheight, slidecolor = nslidecolor) + if (islist(result)) + if (result["button"] == 2) // If the user pressed the cancel button + return + . = 0 + for (var/flag in result["values"]) + . |= GLOB.bitfields[bitfield][flag] + else + return + +/datum/browser/modal/preflikepicker + var/settings = list() + var/icon/preview_icon = null + var/datum/callback/preview_update + +/datum/browser/modal/preflikepicker/New(User,Message,Title,Button1="Ok",Button2,Button3,StealFocus = 1, Timeout = FALSE,list/settings,inputtype="checkbox", width = 600, height, slidecolor) + if (!User) + return + src.settings = settings + + ..(User, ckey("[User]-[Message]-[Title]-[world.time]-[rand(1,10000)]"), Title, width, height, src, StealFocus, Timeout) + set_content(ShowChoices(User)) + +/datum/browser/modal/preflikepicker/proc/ShowChoices(mob/user) + if (settings["preview_callback"]) + var/datum/callback/callback = settings["preview_callback"] + preview_icon = callback.Invoke(settings) + if (preview_icon) + user << browse_rsc(preview_icon, "previewicon.png") + var/dat = "" + + for (var/name in settings["mainsettings"]) + var/setting = settings["mainsettings"][name] + if (setting["type"] == "datum") + if (setting["subtypesonly"]) + dat += "[setting["desc"]]: [setting["value"]]
    " + else + dat += "[setting["desc"]]: [setting["value"]]
    " + else + dat += "[setting["desc"]]: [setting["value"]]
    " + + if (preview_icon) + dat += "" + + dat += "
    " + + dat += "" + + dat += "" + + dat += "
    Ok " + + dat += "
    " + + return dat + +/datum/browser/modal/preflikepicker/Topic(href,href_list) + if (href_list["close"] || !user || !user.client) + opentime = 0 + return + if (href_list["task"] == "input") + var/setting = href_list["setting"] + switch (href_list["type"]) + if ("datum") + var/oldval = settings["mainsettings"][setting]["value"] + if (href_list["subtypesonly"]) + settings["mainsettings"][setting]["value"] = pick_closest_path(null, make_types_fancy(subtypesof(text2path(href_list["path"])))) + else + settings["mainsettings"][setting]["value"] = pick_closest_path(null, make_types_fancy(typesof(text2path(href_list["path"])))) + if (isnull(settings["mainsettings"][setting]["value"])) + settings["mainsettings"][setting]["value"] = oldval + if ("string") + settings["mainsettings"][setting]["value"] = stripped_input(user, "Enter new value for [settings["mainsettings"][setting]["desc"]]", "Enter new value for [settings["mainsettings"][setting]["desc"]]", settings["mainsettings"][setting]["value"]) + if ("number") + settings["mainsettings"][setting]["value"] = input(user, "Enter new value for [settings["mainsettings"][setting]["desc"]]", "Enter new value for [settings["mainsettings"][setting]["desc"]]") as num + if ("color") + settings["mainsettings"][setting]["value"] = input(user, "Enter new value for [settings["mainsettings"][setting]["desc"]]", "Enter new value for [settings["mainsettings"][setting]["desc"]]", settings["mainsettings"][setting]["value"]) as color + if ("boolean") + settings["mainsettings"][setting]["value"] = input(user, "[settings["mainsettings"][setting]["desc"]]?") in list("Yes","No") + if ("ckey") + settings["mainsettings"][setting]["value"] = input(user, "[settings["mainsettings"][setting]["desc"]]?") in list("none") + GLOB.directory + if (settings["mainsettings"][setting]["callback"]) + var/datum/callback/callback = settings["mainsettings"][setting]["callback"] + settings = callback.Invoke(settings) + if (href_list["button"]) + var/button = text2num(href_list["button"]) + if (button <= 3 && button >= 1) + selectedbutton = button + if (selectedbutton != 1) + set_content(ShowChoices(user)) + open() + return + for (var/item in href_list) + switch(item) + if ("close", "button", "src") + continue + opentime = 0 + close() + +/proc/presentpreflikepicker(mob/User,Message, Title, Button1="Ok", Button2, Button3, StealFocus = 1,Timeout = 6000,list/settings, width, height, slidecolor) + if (!istype(User)) + if (istype(User, /client/)) + var/client/C = User + User = C.mob + else + return + var/datum/browser/modal/preflikepicker/A = new(User, Message, Title, Button1, Button2, Button3, StealFocus,Timeout, settings, width, height, slidecolor) + A.open() + A.wait() + if (A.selectedbutton) + return list("button" = A.selectedbutton, "settings" = A.settings) + +// This will allow you to show an icon in the browse window +// This is added to mob so that it can be used without a reference to the browser object +// There is probably a better place for this... +/mob/proc/browse_rsc_icon(icon, icon_state, dir = -1) + + +// Registers the on-close verb for a browse window (client/verb/.windowclose) +// this will be called when the close-button of a window is pressed. +// +// This is usually only needed for devices that regularly update the browse window, +// e.g. canisters, timers, etc. +// +// windowid should be the specified window name +// e.g. code is : user << browse(text, "window=fred") +// then use : onclose(user, "fred") +// +// Optionally, specify the "ref" parameter as the controlled atom (usually src) +// to pass a "close=1" parameter to the atom's Topic() proc for special handling. +// Otherwise, the user mob's machine var will be reset directly. +// +/proc/onclose(mob/user, windowid, atom/ref=null) + if(!user.client) + return + var/param = "null" + if(ref) + param = "[REF(ref)]" + + winset(user, windowid, "on-close=\".windowclose [param]\"") + + + +// the on-close client verb +// called when a browser popup window is closed after registering with proc/onclose() +// if a valid atom reference is supplied, call the atom's Topic() with "close=1" +// otherwise, just reset the client mob's machine var. +// +/client/verb/windowclose(atomref as text) + set hidden = 1 // hide this verb from the user's panel + set name = ".windowclose" // no autocomplete on cmd line + + if(atomref!="null") // if passed a real atomref + var/hsrc = locate(atomref) // find the reffed atom + var/href = "close=1" + if(hsrc) + usr = src.mob + src.Topic(href, params2list(href), hsrc) // this will direct to the atom's + return // Topic() proc via client.Topic() + + // no atomref specified (or not found) + // so just reset the user mob's machine var + if(src && src.mob) + src.mob.unset_machine() diff --git a/code/datums/chatmessage.dm b/code/datums/chatmessage.dm index cbcb34ac5ebd..2f6b847bc41e 100644 --- a/code/datums/chatmessage.dm +++ b/code/datums/chatmessage.dm @@ -20,10 +20,16 @@ var/atom/message_loc /// The client who heard this message var/client/owned_by - /// Contains the scheduled destruction time + /// Contains the scheduled destruction time, used for scheduling EOL var/scheduled_destruction + /// Contains the time that the EOL for the message will be complete, used for qdel scheduling + var/eol_complete /// Contains the approximate amount of lines for height decay var/approx_lines + /// Contains the reference to the next chatmessage in the bucket, used by runechat subsystem + var/datum/chatmessage/next + /// Contains the reference to the previous chatmessage in the bucket, used by runechat subsystem + var/datum/chatmessage/prev /** * Constructs a chat message overlay @@ -53,6 +59,7 @@ owned_by = null message_loc = null message = null + leave_subsystem() return ..() /** @@ -121,11 +128,13 @@ var/datum/chatmessage/m = msg animate(m.message, pixel_y = m.message.pixel_y + mheight, time = CHAT_MESSAGE_SPAWN_TIME) combined_height += m.approx_lines + + // When choosing to update the remaining time we have to be careful not to update the + // scheduled time once the EOL completion time has been set. var/sched_remaining = m.scheduled_destruction - world.time - if (sched_remaining > CHAT_MESSAGE_SPAWN_TIME) + if (!m.eol_complete) var/remaining_time = (sched_remaining) * (CHAT_MESSAGE_EXP_DECAY ** idx++) * (CHAT_MESSAGE_HEIGHT_DECAY ** combined_height) - m.scheduled_destruction = world.time + remaining_time - addtimer(CALLBACK(m, .proc/end_of_life), remaining_time, TIMER_UNIQUE|TIMER_OVERRIDE) + m.enter_subsystem(world.time + remaining_time) // push updated time to runechat SS // Build message image message = image(loc = message_loc, layer = CHAT_LAYER) @@ -143,16 +152,18 @@ owned_by.images |= message animate(message, alpha = 255, time = CHAT_MESSAGE_SPAWN_TIME) - // Prepare for destruction + // Register with the runechat SS to handle EOL and destruction scheduled_destruction = world.time + (lifespan - CHAT_MESSAGE_EOL_FADE) - addtimer(CALLBACK(src, .proc/end_of_life), lifespan - CHAT_MESSAGE_EOL_FADE, TIMER_UNIQUE|TIMER_OVERRIDE) + enter_subsystem() /** - * Applies final animations to overlay CHAT_MESSAGE_EOL_FADE deciseconds prior to message deletion + * Applies final animations to overlay CHAT_MESSAGE_EOL_FADE deciseconds prior to message deletion, + * sets time for scheduling deletion and re-enters the runechat SS for qdeling */ /datum/chatmessage/proc/end_of_life(fadetime = CHAT_MESSAGE_EOL_FADE) + eol_complete = scheduled_destruction + CHAT_MESSAGE_EOL_FADE animate(message, alpha = 0, time = fadetime, flags = ANIMATION_PARALLEL) - QDEL_IN(src, fadetime) + enter_subsystem(eol_complete) // re-enter the runechat SS with the EOL completion time to QDEL self /** * Creates a message overlay at a defined location for a given speaker diff --git a/code/datums/cinematic.dm b/code/datums/cinematic.dm index e26f8fba2cf7..43ebe692a533 100644 --- a/code/datums/cinematic.dm +++ b/code/datums/cinematic.dm @@ -24,7 +24,8 @@ plane = SPLASHSCREEN_PLANE layer = SPLASHSCREEN_LAYER mouse_opacity = MOUSE_OPACITY_TRANSPARENT - screen_loc = "1,1" + screen_loc = "BOTTOM,LEFT+50%" + appearance_flags = APPEARANCE_UI | TILE_BOUND /datum/cinematic var/id = CINEMATIC_DEFAULT @@ -44,6 +45,7 @@ if(!CC) continue var/client/C = CC + C.mob.clear_fullscreen("cinematic") C.screen -= screen watching = null QDEL_NULL(screen) @@ -97,6 +99,7 @@ if(!C) return watching += C + M.overlay_fullscreen("cinematic",/obj/screen/fullscreen/cinematic_backdrop) C.screen += screen //Sound helper diff --git a/code/datums/components/README.md b/code/datums/components/README.md index d86d06b947a6..3080f7e7b441 100644 --- a/code/datums/components/README.md +++ b/code/datums/components/README.md @@ -1,9 +1,9 @@ -# Datum Component System (DCS) - -## Concept - -Loosely adapted from /vg/. This is an entity component system for adding behaviours to datums when inheritance doesn't quite cut it. By using signals and events instead of direct inheritance, you can inject behaviours without hacky overloads. It requires a different method of thinking, but is not hard to use correctly. If a behaviour can have application across more than one thing. Make it generic, make it a component. Atom/mob/obj event? Give it a signal, and forward it's arguments with a `SendSignal()` call. Now every component that want's to can also know about this happening. - -See [this thread](https://tgstation13.org/phpBB/viewtopic.php?f=5&t=22674) for an introduction to the system as a whole. - -### See/Define signals and their arguments in [__DEFINES\components.dm](../../__DEFINES/components.dm) +# Datum Component System (DCS) + +## Concept + +Loosely adapted from /vg/. This is an entity component system for adding behaviours to datums when inheritance doesn't quite cut it. By using signals and events instead of direct inheritance, you can inject behaviours without hacky overloads. It requires a different method of thinking, but is not hard to use correctly. If a behaviour can have application across more than one thing. Make it generic, make it a component. Atom/mob/obj event? Give it a signal, and forward it's arguments with a `SendSignal()` call. Now every component that want's to can also know about this happening. + +See [this thread](https://tgstation13.org/phpBB/viewtopic.php?f=5&t=22674) for an introduction to the system as a whole. + +### See/Define signals and their arguments in [__DEFINES\components.dm](../../__DEFINES/components.dm) diff --git a/code/datums/components/_component.dm b/code/datums/components/_component.dm index a5fdae4d91ff..a4a9ee4209f9 100644 --- a/code/datums/components/_component.dm +++ b/code/datums/components/_component.dm @@ -1,537 +1,537 @@ -/** - * # Component - * - * The component datum - * - * A component should be a single standalone unit - * of functionality, that works by receiving signals from it's parent - * object to provide some single functionality (i.e a slippery component) - * that makes the object it's attached to cause people to slip over. - * Useful when you want shared behaviour independent of type inheritance - */ -/datum/component - /** - * Defines how duplicate existing components are handled when added to a datum - * - * See [COMPONENT_DUPE_*][COMPONENT_DUPE_ALLOWED] definitions for available options - */ - var/dupe_mode = COMPONENT_DUPE_HIGHLANDER - - /** - * The type to check for duplication - * - * `null` means exact match on `type` (default) - * - * Any other type means that and all subtypes - */ - var/dupe_type - - /// The datum this components belongs to - var/datum/parent - - /** - * Only set to true if you are able to properly transfer this component - * - * At a minimum [RegisterWithParent][/datum/component/proc/RegisterWithParent] and [UnregisterFromParent][/datum/component/proc/UnregisterFromParent] should be used - * - * Make sure you also implement [PostTransfer][/datum/component/proc/PostTransfer] for any post transfer handling - */ - var/can_transfer = FALSE - -/** - * Create a new component. - * - * Additional arguments are passed to [Initialize()][/datum/component/proc/Initialize] - * - * Arguments: - * * datum/P the parent datum this component reacts to signals from - */ -/datum/component/New(list/raw_args) - parent = raw_args[1] - var/list/arguments = raw_args.Copy(2) - if(Initialize(arglist(arguments)) == COMPONENT_INCOMPATIBLE) - stack_trace("Incompatible [type] assigned to a [parent.type]! args: [json_encode(arguments)]") - qdel(src, TRUE, TRUE) - return - - _JoinParent(parent) - -/** - * Called during component creation with the same arguments as in new excluding parent. - * - * Do not call `qdel(src)` from this function, `return COMPONENT_INCOMPATIBLE` instead - */ -/datum/component/proc/Initialize(...) - return - -/** - * Properly removes the component from `parent` and cleans up references - * - * Arguments: - * * force - makes it not check for and remove the component from the parent - * * silent - deletes the component without sending a [COMSIG_COMPONENT_REMOVING] signal - */ -/datum/component/Destroy(force=FALSE, silent=FALSE) - if(!force && parent) - _RemoveFromParent() - if(!silent) - SEND_SIGNAL(parent, COMSIG_COMPONENT_REMOVING, src) - parent = null - return ..() - -/** - * Internal proc to handle behaviour of components when joining a parent - */ -/datum/component/proc/_JoinParent() - var/datum/P = parent - //lazy init the parent's dc list - var/list/dc = P.datum_components - if(!dc) - P.datum_components = dc = list() - - //set up the typecache - var/our_type = type - for(var/I in _GetInverseTypeList(our_type)) - var/test = dc[I] - if(test) //already another component of this type here - var/list/components_of_type - if(!length(test)) - components_of_type = list(test) - dc[I] = components_of_type - else - components_of_type = test - if(I == our_type) //exact match, take priority - var/inserted = FALSE - for(var/J in 1 to components_of_type.len) - var/datum/component/C = components_of_type[J] - if(C.type != our_type) //but not over other exact matches - components_of_type.Insert(J, I) - inserted = TRUE - break - if(!inserted) - components_of_type += src - else //indirect match, back of the line with ya - components_of_type += src - else //only component of this type, no list - dc[I] = src - - RegisterWithParent() - -/** - * Internal proc to handle behaviour when being removed from a parent - */ -/datum/component/proc/_RemoveFromParent() - var/datum/P = parent - var/list/dc = P.datum_components - for(var/I in _GetInverseTypeList()) - var/list/components_of_type = dc[I] - if(length(components_of_type)) // - var/list/subtracted = components_of_type - src - if(subtracted.len == 1) //only 1 guy left - dc[I] = subtracted[1] //make him special - else - dc[I] = subtracted - else //just us - dc -= I - if(!dc.len) - P.datum_components = null - - UnregisterFromParent() - -/** - * Register the component with the parent object - * - * Use this proc to register with your parent object - * - * Overridable proc that's called when added to a new parent - */ -/datum/component/proc/RegisterWithParent() - return - -/** - * Unregister from our parent object - * - * Use this proc to unregister from your parent object - * - * Overridable proc that's called when removed from a parent - * * - */ -/datum/component/proc/UnregisterFromParent() - return - -/** - * Register to listen for a signal from the passed in target - * - * This sets up a listening relationship such that when the target object emits a signal - * the source datum this proc is called upon, will recieve a callback to the given proctype - * Return values from procs registered must be a bitfield - * - * Arguments: - * * datum/target The target to listen for signals from - * * sig_type_or_types Either a string signal name, or a list of signal names (strings) - * * proctype The proc to call back when the signal is emitted - * * override If a previous registration exists you must explicitly set this - */ -/datum/proc/RegisterSignal(datum/target, sig_type_or_types, proctype, override = FALSE) - if(QDELETED(src) || QDELETED(target)) - return - - var/list/procs = signal_procs - if(!procs) - signal_procs = procs = list() - if(!procs[target]) - procs[target] = list() - var/list/lookup = target.comp_lookup - if(!lookup) - target.comp_lookup = lookup = list() - - var/list/sig_types = islist(sig_type_or_types) ? sig_type_or_types : list(sig_type_or_types) - for(var/sig_type in sig_types) - if(!override && procs[target][sig_type]) - stack_trace("[sig_type] overridden. Use override = TRUE to suppress this warning") - - procs[target][sig_type] = proctype - - if(!lookup[sig_type]) // Nothing has registered here yet - lookup[sig_type] = src - else if(lookup[sig_type] == src) // We already registered here - continue - else if(!length(lookup[sig_type])) // One other thing registered here - lookup[sig_type] = list(lookup[sig_type]=TRUE) - lookup[sig_type][src] = TRUE - else // Many other things have registered here - lookup[sig_type][src] = TRUE - - signal_enabled = TRUE - -/** - * Stop listening to a given signal from target - * - * Breaks the relationship between target and source datum, removing the callback when the signal fires - * - * Doesn't care if a registration exists or not - * - * Arguments: - * * datum/target Datum to stop listening to signals from - * * sig_typeor_types Signal string key or list of signal keys to stop listening to specifically - */ -/datum/proc/UnregisterSignal(datum/target, sig_type_or_types) - var/list/lookup = target.comp_lookup - if(!signal_procs || !signal_procs[target] || !lookup) - return - if(!islist(sig_type_or_types)) - sig_type_or_types = list(sig_type_or_types) - for(var/sig in sig_type_or_types) - if(!signal_procs[target][sig]) - continue - switch(length(lookup[sig])) - if(2) - lookup[sig] = (lookup[sig]-src)[1] - if(1) - stack_trace("[target] ([target.type]) somehow has single length list inside comp_lookup") - if(src in lookup[sig]) - lookup -= sig - if(!length(lookup)) - target.comp_lookup = null - break - if(0) - lookup -= sig - if(!length(lookup)) - target.comp_lookup = null - break - else - lookup[sig] -= src - - signal_procs[target] -= sig_type_or_types - if(!signal_procs[target].len) - signal_procs -= target - -/** - * Called on a component when a component of the same type was added to the same parent - * - * See [/datum/component/var/dupe_mode] - * - * `C`'s type will always be the same of the called component - */ -/datum/component/proc/InheritComponent(datum/component/C, i_am_original) - return - - -/** - * Called on a component when a component of the same type was added to the same parent with [COMPONENT_DUPE_SELECTIVE] - * - * See [/datum/component/var/dupe_mode] - * - * `C`'s type will always be the same of the called component - * - * return TRUE if you are absorbing the component, otherwise FALSE if you are fine having it exist as a duplicate component - */ -/datum/component/proc/CheckDupeComponent(datum/component/C, ...) - return - - -/** - * Callback Just before this component is transferred - * - * Use this to do any special cleanup you might need to do before being deregged from an object - */ -/datum/component/proc/PreTransfer() - return - -/** - * Callback Just after a component is transferred - * - * Use this to do any special setup you need to do after being moved to a new object - * - * Do not call `qdel(src)` from this function, `return COMPONENT_INCOMPATIBLE` instead - */ -/datum/component/proc/PostTransfer() - return COMPONENT_INCOMPATIBLE //Do not support transfer by default as you must properly support it - -/** - * Internal proc to create a list of our type and all parent types - */ -/datum/component/proc/_GetInverseTypeList(our_type = type) - //we can do this one simple trick - var/current_type = parent_type - . = list(our_type, current_type) - //and since most components are root level + 1, this won't even have to run - while (current_type != /datum/component) - current_type = type2parent(current_type) - . += current_type - -/** - * Internal proc to handle most all of the signaling procedure - * - * Will runtime if used on datums with an empty component list - * - * Use the [SEND_SIGNAL] define instead - */ -/datum/proc/_SendSignal(sigtype, list/arguments) - var/target = comp_lookup[sigtype] - if(!length(target)) - var/datum/C = target - if(!C.signal_enabled) - return NONE - var/proctype = C.signal_procs[src][sigtype] - return NONE | CallAsync(C, proctype, arguments) - . = NONE - for(var/I in target) - var/datum/C = I - if(!C.signal_enabled) - continue - var/proctype = C.signal_procs[src][sigtype] - . |= CallAsync(C, proctype, arguments) - -// The type arg is casted so initial works, you shouldn't be passing a real instance into this -/** - * Return any component assigned to this datum of the given type - * - * This will throw an error if it's possible to have more than one component of that type on the parent - * - * Arguments: - * * datum/component/c_type The typepath of the component you want to get a reference to - */ -/datum/proc/GetComponent(datum/component/c_type) - RETURN_TYPE(c_type) - if(initial(c_type.dupe_mode) == COMPONENT_DUPE_ALLOWED || initial(c_type.dupe_mode) == COMPONENT_DUPE_SELECTIVE) - stack_trace("GetComponent was called to get a component of which multiple copies could be on an object. This can easily break and should be changed. Type: \[[c_type]\]") - var/list/dc = datum_components - if(!dc) - return null - . = dc[c_type] - if(length(.)) - return .[1] - -// The type arg is casted so initial works, you shouldn't be passing a real instance into this -/** - * Return any component assigned to this datum of the exact given type - * - * This will throw an error if it's possible to have more than one component of that type on the parent - * - * Arguments: - * * datum/component/c_type The typepath of the component you want to get a reference to - */ -/datum/proc/GetExactComponent(datum/component/c_type) - RETURN_TYPE(c_type) - if(initial(c_type.dupe_mode) == COMPONENT_DUPE_ALLOWED || initial(c_type.dupe_mode) == COMPONENT_DUPE_SELECTIVE) - stack_trace("GetComponent was called to get a component of which multiple copies could be on an object. This can easily break and should be changed. Type: \[[c_type]\]") - var/list/dc = datum_components - if(!dc) - return null - var/datum/component/C = dc[c_type] - if(C) - if(length(C)) - C = C[1] - if(C.type == c_type) - return C - return null - -/** - * Get all components of a given type that are attached to this datum - * - * Arguments: - * * c_type The component type path - */ -/datum/proc/GetComponents(c_type) - var/list/dc = datum_components - if(!dc) - return null - . = dc[c_type] - if(!length(.)) - return list(.) - -/** - * Creates an instance of `new_type` in the datum and attaches to it as parent - * - * Sends the [COMSIG_COMPONENT_ADDED] signal to the datum - * - * Returns the component that was created. Or the old component in a dupe situation where [COMPONENT_DUPE_UNIQUE] was set - * - * If this tries to add an component to an incompatible type, the component will be deleted and the result will be `null`. This is very unperformant, try not to do it - * - * Properly handles duplicate situations based on the `dupe_mode` var - */ -/datum/proc/_AddComponent(list/raw_args) - var/new_type = raw_args[1] - var/datum/component/nt = new_type - var/dm = initial(nt.dupe_mode) - var/dt = initial(nt.dupe_type) - - var/datum/component/old_comp - var/datum/component/new_comp - - if(ispath(nt)) - if(nt == /datum/component) - CRASH("[nt] attempted instantiation!") - else - new_comp = nt - nt = new_comp.type - - raw_args[1] = src - - if(dm != COMPONENT_DUPE_ALLOWED) - if(!dt) - old_comp = GetExactComponent(nt) - else - old_comp = GetComponent(dt) - if(old_comp) - switch(dm) - if(COMPONENT_DUPE_UNIQUE) - if(!new_comp) - new_comp = new nt(raw_args) - if(!QDELETED(new_comp)) - old_comp.InheritComponent(new_comp, TRUE) - QDEL_NULL(new_comp) - if(COMPONENT_DUPE_HIGHLANDER) - if(!new_comp) - new_comp = new nt(raw_args) - if(!QDELETED(new_comp)) - new_comp.InheritComponent(old_comp, FALSE) - QDEL_NULL(old_comp) - if(COMPONENT_DUPE_UNIQUE_PASSARGS) - if(!new_comp) - var/list/arguments = raw_args.Copy(2) - arguments.Insert(1, null, TRUE) - old_comp.InheritComponent(arglist(arguments)) - else - old_comp.InheritComponent(new_comp, TRUE) - if(COMPONENT_DUPE_SELECTIVE) - var/list/arguments = raw_args.Copy() - arguments[1] = new_comp - var/make_new_component = TRUE - for(var/i in GetComponents(new_type)) - var/datum/component/C = i - if(C.CheckDupeComponent(arglist(arguments))) - make_new_component = FALSE - QDEL_NULL(new_comp) - break - if(!new_comp && make_new_component) - new_comp = new nt(raw_args) - else if(!new_comp) - new_comp = new nt(raw_args) // There's a valid dupe mode but there's no old component, act like normal - else if(!new_comp) - new_comp = new nt(raw_args) // Dupes are allowed, act like normal - - if(!old_comp && !QDELETED(new_comp)) // Nothing related to duplicate components happened and the new component is healthy - SEND_SIGNAL(src, COMSIG_COMPONENT_ADDED, new_comp) - return new_comp - return old_comp - -/** - * Get existing component of type, or create it and return a reference to it - * - * Use this if the item needs to exist at the time of this call, but may not have been created before now - * - * Arguments: - * * component_type The typepath of the component to create or return - * * ... additional arguments to be passed when creating the component if it does not exist - */ -/datum/proc/LoadComponent(component_type, ...) - . = GetComponent(component_type) - if(!.) - return _AddComponent(args) - -/** - * Removes the component from parent, ends up with a null parent - */ -/datum/component/proc/RemoveComponent() - if(!parent) - return - var/datum/old_parent = parent - PreTransfer() - _RemoveFromParent() - parent = null - SEND_SIGNAL(old_parent, COMSIG_COMPONENT_REMOVING, src) - -/** - * Transfer this component to another parent - * - * Component is taken from source datum - * - * Arguments: - * * datum/component/target Target datum to transfer to - */ -/datum/proc/TakeComponent(datum/component/target) - if(!target || target.parent == src) - return - if(target.parent) - target.RemoveComponent() - target.parent = src - var/result = target.PostTransfer() - switch(result) - if(COMPONENT_INCOMPATIBLE) - var/c_type = target.type - qdel(target) - CRASH("Incompatible [c_type] transfer attempt to a [type]!") - - if(target == AddComponent(target)) - target._JoinParent() - -/** - * Transfer all components to target - * - * All components from source datum are taken - * - * Arguments: - * * /datum/target the target to move the components to - */ -/datum/proc/TransferComponents(datum/target) - var/list/dc = datum_components - if(!dc) - return - var/comps = dc[/datum/component] - if(islist(comps)) - for(var/datum/component/I in comps) - if(I.can_transfer) - target.TakeComponent(I) - else - var/datum/component/C = comps - if(C.can_transfer) - target.TakeComponent(comps) - -/** - * Return the object that is the host of any UI's that this component has - */ -/datum/component/ui_host() - return parent +/** + * # Component + * + * The component datum + * + * A component should be a single standalone unit + * of functionality, that works by receiving signals from it's parent + * object to provide some single functionality (i.e a slippery component) + * that makes the object it's attached to cause people to slip over. + * Useful when you want shared behaviour independent of type inheritance + */ +/datum/component + /** + * Defines how duplicate existing components are handled when added to a datum + * + * See [COMPONENT_DUPE_*][COMPONENT_DUPE_ALLOWED] definitions for available options + */ + var/dupe_mode = COMPONENT_DUPE_HIGHLANDER + + /** + * The type to check for duplication + * + * `null` means exact match on `type` (default) + * + * Any other type means that and all subtypes + */ + var/dupe_type + + /// The datum this components belongs to + var/datum/parent + + /** + * Only set to true if you are able to properly transfer this component + * + * At a minimum [RegisterWithParent][/datum/component/proc/RegisterWithParent] and [UnregisterFromParent][/datum/component/proc/UnregisterFromParent] should be used + * + * Make sure you also implement [PostTransfer][/datum/component/proc/PostTransfer] for any post transfer handling + */ + var/can_transfer = FALSE + +/** + * Create a new component. + * + * Additional arguments are passed to [Initialize()][/datum/component/proc/Initialize] + * + * Arguments: + * * datum/P the parent datum this component reacts to signals from + */ +/datum/component/New(list/raw_args) + parent = raw_args[1] + var/list/arguments = raw_args.Copy(2) + if(Initialize(arglist(arguments)) == COMPONENT_INCOMPATIBLE) + stack_trace("Incompatible [type] assigned to a [parent.type]! args: [json_encode(arguments)]") + qdel(src, TRUE, TRUE) + return + + _JoinParent(parent) + +/** + * Called during component creation with the same arguments as in new excluding parent. + * + * Do not call `qdel(src)` from this function, `return COMPONENT_INCOMPATIBLE` instead + */ +/datum/component/proc/Initialize(...) + return + +/** + * Properly removes the component from `parent` and cleans up references + * + * Arguments: + * * force - makes it not check for and remove the component from the parent + * * silent - deletes the component without sending a [COMSIG_COMPONENT_REMOVING] signal + */ +/datum/component/Destroy(force=FALSE, silent=FALSE) + if(!force && parent) + _RemoveFromParent() + if(!silent) + SEND_SIGNAL(parent, COMSIG_COMPONENT_REMOVING, src) + parent = null + return ..() + +/** + * Internal proc to handle behaviour of components when joining a parent + */ +/datum/component/proc/_JoinParent() + var/datum/P = parent + //lazy init the parent's dc list + var/list/dc = P.datum_components + if(!dc) + P.datum_components = dc = list() + + //set up the typecache + var/our_type = type + for(var/I in _GetInverseTypeList(our_type)) + var/test = dc[I] + if(test) //already another component of this type here + var/list/components_of_type + if(!length(test)) + components_of_type = list(test) + dc[I] = components_of_type + else + components_of_type = test + if(I == our_type) //exact match, take priority + var/inserted = FALSE + for(var/J in 1 to components_of_type.len) + var/datum/component/C = components_of_type[J] + if(C.type != our_type) //but not over other exact matches + components_of_type.Insert(J, I) + inserted = TRUE + break + if(!inserted) + components_of_type += src + else //indirect match, back of the line with ya + components_of_type += src + else //only component of this type, no list + dc[I] = src + + RegisterWithParent() + +/** + * Internal proc to handle behaviour when being removed from a parent + */ +/datum/component/proc/_RemoveFromParent() + var/datum/P = parent + var/list/dc = P.datum_components + for(var/I in _GetInverseTypeList()) + var/list/components_of_type = dc[I] + if(length(components_of_type)) // + var/list/subtracted = components_of_type - src + if(subtracted.len == 1) //only 1 guy left + dc[I] = subtracted[1] //make him special + else + dc[I] = subtracted + else //just us + dc -= I + if(!dc.len) + P.datum_components = null + + UnregisterFromParent() + +/** + * Register the component with the parent object + * + * Use this proc to register with your parent object + * + * Overridable proc that's called when added to a new parent + */ +/datum/component/proc/RegisterWithParent() + return + +/** + * Unregister from our parent object + * + * Use this proc to unregister from your parent object + * + * Overridable proc that's called when removed from a parent + * * + */ +/datum/component/proc/UnregisterFromParent() + return + +/** + * Register to listen for a signal from the passed in target + * + * This sets up a listening relationship such that when the target object emits a signal + * the source datum this proc is called upon, will recieve a callback to the given proctype + * Return values from procs registered must be a bitfield + * + * Arguments: + * * datum/target The target to listen for signals from + * * sig_type_or_types Either a string signal name, or a list of signal names (strings) + * * proctype The proc to call back when the signal is emitted + * * override If a previous registration exists you must explicitly set this + */ +/datum/proc/RegisterSignal(datum/target, sig_type_or_types, proctype, override = FALSE) + if(QDELETED(src) || QDELETED(target)) + return + + var/list/procs = signal_procs + if(!procs) + signal_procs = procs = list() + if(!procs[target]) + procs[target] = list() + var/list/lookup = target.comp_lookup + if(!lookup) + target.comp_lookup = lookup = list() + + var/list/sig_types = islist(sig_type_or_types) ? sig_type_or_types : list(sig_type_or_types) + for(var/sig_type in sig_types) + if(!override && procs[target][sig_type]) + stack_trace("[sig_type] overridden. Use override = TRUE to suppress this warning") + + procs[target][sig_type] = proctype + + if(!lookup[sig_type]) // Nothing has registered here yet + lookup[sig_type] = src + else if(lookup[sig_type] == src) // We already registered here + continue + else if(!length(lookup[sig_type])) // One other thing registered here + lookup[sig_type] = list(lookup[sig_type]=TRUE) + lookup[sig_type][src] = TRUE + else // Many other things have registered here + lookup[sig_type][src] = TRUE + + signal_enabled = TRUE + +/** + * Stop listening to a given signal from target + * + * Breaks the relationship between target and source datum, removing the callback when the signal fires + * + * Doesn't care if a registration exists or not + * + * Arguments: + * * datum/target Datum to stop listening to signals from + * * sig_typeor_types Signal string key or list of signal keys to stop listening to specifically + */ +/datum/proc/UnregisterSignal(datum/target, sig_type_or_types) + var/list/lookup = target.comp_lookup + if(!signal_procs || !signal_procs[target] || !lookup) + return + if(!islist(sig_type_or_types)) + sig_type_or_types = list(sig_type_or_types) + for(var/sig in sig_type_or_types) + if(!signal_procs[target][sig]) + continue + switch(length(lookup[sig])) + if(2) + lookup[sig] = (lookup[sig]-src)[1] + if(1) + stack_trace("[target] ([target.type]) somehow has single length list inside comp_lookup") + if(src in lookup[sig]) + lookup -= sig + if(!length(lookup)) + target.comp_lookup = null + break + if(0) + lookup -= sig + if(!length(lookup)) + target.comp_lookup = null + break + else + lookup[sig] -= src + + signal_procs[target] -= sig_type_or_types + if(!signal_procs[target].len) + signal_procs -= target + +/** + * Called on a component when a component of the same type was added to the same parent + * + * See [/datum/component/var/dupe_mode] + * + * `C`'s type will always be the same of the called component + */ +/datum/component/proc/InheritComponent(datum/component/C, i_am_original) + return + + +/** + * Called on a component when a component of the same type was added to the same parent with [COMPONENT_DUPE_SELECTIVE] + * + * See [/datum/component/var/dupe_mode] + * + * `C`'s type will always be the same of the called component + * + * return TRUE if you are absorbing the component, otherwise FALSE if you are fine having it exist as a duplicate component + */ +/datum/component/proc/CheckDupeComponent(datum/component/C, ...) + return + + +/** + * Callback Just before this component is transferred + * + * Use this to do any special cleanup you might need to do before being deregged from an object + */ +/datum/component/proc/PreTransfer() + return + +/** + * Callback Just after a component is transferred + * + * Use this to do any special setup you need to do after being moved to a new object + * + * Do not call `qdel(src)` from this function, `return COMPONENT_INCOMPATIBLE` instead + */ +/datum/component/proc/PostTransfer() + return COMPONENT_INCOMPATIBLE //Do not support transfer by default as you must properly support it + +/** + * Internal proc to create a list of our type and all parent types + */ +/datum/component/proc/_GetInverseTypeList(our_type = type) + //we can do this one simple trick + var/current_type = parent_type + . = list(our_type, current_type) + //and since most components are root level + 1, this won't even have to run + while (current_type != /datum/component) + current_type = type2parent(current_type) + . += current_type + +/** + * Internal proc to handle most all of the signaling procedure + * + * Will runtime if used on datums with an empty component list + * + * Use the [SEND_SIGNAL] define instead + */ +/datum/proc/_SendSignal(sigtype, list/arguments) + var/target = comp_lookup[sigtype] + if(!length(target)) + var/datum/C = target + if(!C.signal_enabled) + return NONE + var/proctype = C.signal_procs[src][sigtype] + return NONE | CallAsync(C, proctype, arguments) + . = NONE + for(var/I in target) + var/datum/C = I + if(!C.signal_enabled) + continue + var/proctype = C.signal_procs[src][sigtype] + . |= CallAsync(C, proctype, arguments) + +// The type arg is casted so initial works, you shouldn't be passing a real instance into this +/** + * Return any component assigned to this datum of the given type + * + * This will throw an error if it's possible to have more than one component of that type on the parent + * + * Arguments: + * * datum/component/c_type The typepath of the component you want to get a reference to + */ +/datum/proc/GetComponent(datum/component/c_type) + RETURN_TYPE(c_type) + if(initial(c_type.dupe_mode) == COMPONENT_DUPE_ALLOWED || initial(c_type.dupe_mode) == COMPONENT_DUPE_SELECTIVE) + stack_trace("GetComponent was called to get a component of which multiple copies could be on an object. This can easily break and should be changed. Type: \[[c_type]\]") + var/list/dc = datum_components + if(!dc) + return null + . = dc[c_type] + if(length(.)) + return .[1] + +// The type arg is casted so initial works, you shouldn't be passing a real instance into this +/** + * Return any component assigned to this datum of the exact given type + * + * This will throw an error if it's possible to have more than one component of that type on the parent + * + * Arguments: + * * datum/component/c_type The typepath of the component you want to get a reference to + */ +/datum/proc/GetExactComponent(datum/component/c_type) + RETURN_TYPE(c_type) + if(initial(c_type.dupe_mode) == COMPONENT_DUPE_ALLOWED || initial(c_type.dupe_mode) == COMPONENT_DUPE_SELECTIVE) + stack_trace("GetComponent was called to get a component of which multiple copies could be on an object. This can easily break and should be changed. Type: \[[c_type]\]") + var/list/dc = datum_components + if(!dc) + return null + var/datum/component/C = dc[c_type] + if(C) + if(length(C)) + C = C[1] + if(C.type == c_type) + return C + return null + +/** + * Get all components of a given type that are attached to this datum + * + * Arguments: + * * c_type The component type path + */ +/datum/proc/GetComponents(c_type) + var/list/dc = datum_components + if(!dc) + return null + . = dc[c_type] + if(!length(.)) + return list(.) + +/** + * Creates an instance of `new_type` in the datum and attaches to it as parent + * + * Sends the [COMSIG_COMPONENT_ADDED] signal to the datum + * + * Returns the component that was created. Or the old component in a dupe situation where [COMPONENT_DUPE_UNIQUE] was set + * + * If this tries to add an component to an incompatible type, the component will be deleted and the result will be `null`. This is very unperformant, try not to do it + * + * Properly handles duplicate situations based on the `dupe_mode` var + */ +/datum/proc/_AddComponent(list/raw_args) + var/new_type = raw_args[1] + var/datum/component/nt = new_type + var/dm = initial(nt.dupe_mode) + var/dt = initial(nt.dupe_type) + + var/datum/component/old_comp + var/datum/component/new_comp + + if(ispath(nt)) + if(nt == /datum/component) + CRASH("[nt] attempted instantiation!") + else + new_comp = nt + nt = new_comp.type + + raw_args[1] = src + + if(dm != COMPONENT_DUPE_ALLOWED) + if(!dt) + old_comp = GetExactComponent(nt) + else + old_comp = GetComponent(dt) + if(old_comp) + switch(dm) + if(COMPONENT_DUPE_UNIQUE) + if(!new_comp) + new_comp = new nt(raw_args) + if(!QDELETED(new_comp)) + old_comp.InheritComponent(new_comp, TRUE) + QDEL_NULL(new_comp) + if(COMPONENT_DUPE_HIGHLANDER) + if(!new_comp) + new_comp = new nt(raw_args) + if(!QDELETED(new_comp)) + new_comp.InheritComponent(old_comp, FALSE) + QDEL_NULL(old_comp) + if(COMPONENT_DUPE_UNIQUE_PASSARGS) + if(!new_comp) + var/list/arguments = raw_args.Copy(2) + arguments.Insert(1, null, TRUE) + old_comp.InheritComponent(arglist(arguments)) + else + old_comp.InheritComponent(new_comp, TRUE) + if(COMPONENT_DUPE_SELECTIVE) + var/list/arguments = raw_args.Copy() + arguments[1] = new_comp + var/make_new_component = TRUE + for(var/i in GetComponents(new_type)) + var/datum/component/C = i + if(C.CheckDupeComponent(arglist(arguments))) + make_new_component = FALSE + QDEL_NULL(new_comp) + break + if(!new_comp && make_new_component) + new_comp = new nt(raw_args) + else if(!new_comp) + new_comp = new nt(raw_args) // There's a valid dupe mode but there's no old component, act like normal + else if(!new_comp) + new_comp = new nt(raw_args) // Dupes are allowed, act like normal + + if(!old_comp && !QDELETED(new_comp)) // Nothing related to duplicate components happened and the new component is healthy + SEND_SIGNAL(src, COMSIG_COMPONENT_ADDED, new_comp) + return new_comp + return old_comp + +/** + * Get existing component of type, or create it and return a reference to it + * + * Use this if the item needs to exist at the time of this call, but may not have been created before now + * + * Arguments: + * * component_type The typepath of the component to create or return + * * ... additional arguments to be passed when creating the component if it does not exist + */ +/datum/proc/LoadComponent(component_type, ...) + . = GetComponent(component_type) + if(!.) + return _AddComponent(args) + +/** + * Removes the component from parent, ends up with a null parent + */ +/datum/component/proc/RemoveComponent() + if(!parent) + return + var/datum/old_parent = parent + PreTransfer() + _RemoveFromParent() + parent = null + SEND_SIGNAL(old_parent, COMSIG_COMPONENT_REMOVING, src) + +/** + * Transfer this component to another parent + * + * Component is taken from source datum + * + * Arguments: + * * datum/component/target Target datum to transfer to + */ +/datum/proc/TakeComponent(datum/component/target) + if(!target || target.parent == src) + return + if(target.parent) + target.RemoveComponent() + target.parent = src + var/result = target.PostTransfer() + switch(result) + if(COMPONENT_INCOMPATIBLE) + var/c_type = target.type + qdel(target) + CRASH("Incompatible [c_type] transfer attempt to a [type]!") + + if(target == AddComponent(target)) + target._JoinParent() + +/** + * Transfer all components to target + * + * All components from source datum are taken + * + * Arguments: + * * /datum/target the target to move the components to + */ +/datum/proc/TransferComponents(datum/target) + var/list/dc = datum_components + if(!dc) + return + var/comps = dc[/datum/component] + if(islist(comps)) + for(var/datum/component/I in comps) + if(I.can_transfer) + target.TakeComponent(I) + else + var/datum/component/C = comps + if(C.can_transfer) + target.TakeComponent(comps) + +/** + * Return the object that is the host of any UI's that this component has + */ +/datum/component/ui_host() + return parent diff --git a/code/datums/components/crafting/crafting.dm b/code/datums/components/crafting/crafting.dm index 068769e36dbf..1594bef50c89 100644 --- a/code/datums/components/crafting/crafting.dm +++ b/code/datums/components/crafting/crafting.dm @@ -316,9 +316,12 @@ if(user == parent) ui_interact(user) +/datum/component/personal_crafting/ui_state(mob/user) + return GLOB.not_incapacitated_turf_state + //For the UI related things we're going to assume the user is a mob rather than typesetting it to an atom as the UI isn't generated if the parent is an atom -/datum/component/personal_crafting/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.not_incapacitated_turf_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/datum/component/personal_crafting/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) cur_category = categories[1] if(islist(categories[cur_category])) @@ -326,7 +329,7 @@ cur_subcategory = subcats[1] else cur_subcategory = CAT_NONE - ui = new(user, src, ui_key, "PersonalCrafting", "Crafting Menu", 700, 800, master_ui, state) + ui = new(user, src, "PersonalCrafting") ui.open() /datum/component/personal_crafting/ui_data(mob/user) @@ -406,13 +409,8 @@ display_compact = !display_compact . = TRUE if("set_category") - if(!isnull(params["category"])) - cur_category = params["category"] - if(!isnull(params["subcategory"])) - if(params["subcategory"] == "0") - cur_subcategory = "" - else - cur_subcategory = params["subcategory"] + cur_category = params["category"] + cur_subcategory = params["subcategory"] || "" . = TRUE /datum/component/personal_crafting/proc/build_recipe_data(datum/crafting_recipe/R) diff --git a/code/datums/components/creamed.dm b/code/datums/components/creamed.dm index 38759e565610..432f146f84e2 100644 --- a/code/datums/components/creamed.dm +++ b/code/datums/components/creamed.dm @@ -1,62 +1,62 @@ -GLOBAL_LIST_INIT(creamable, typecacheof(list( - /mob/living/carbon/human, - /mob/living/carbon/monkey, - /mob/living/simple_animal/pet/dog/corgi, - /mob/living/silicon/ai))) - -/** - * Creamed component - * - * For when you have pie on your face - */ -/datum/component/creamed - dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS - - var/mutable_appearance/creamface - -/datum/component/creamed/Initialize() - if(!is_type_in_typecache(parent, GLOB.creamable)) - return COMPONENT_INCOMPATIBLE - - creamface = mutable_appearance('icons/effects/creampie.dmi') - - if(ishuman(parent)) - var/mob/living/carbon/human/H = parent - if(H.dna.species.limbs_id == "lizard") - creamface.icon_state = "creampie_lizard" - else - creamface.icon_state = "creampie_human" - SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "creampie", /datum/mood_event/creampie) - else if(ismonkey(parent)) - creamface.icon_state = "creampie_monkey" - else if(iscorgi(parent)) - creamface.icon_state = "creampie_corgi" - else if(isAI(parent)) - creamface.icon_state = "creampie_ai" - - var/atom/A = parent - A.add_overlay(creamface) - -/datum/component/creamed/Destroy(force, silent) - var/atom/A = parent - A.cut_overlay(creamface) - qdel(creamface) - if(ishuman(A)) - SEND_SIGNAL(A, COMSIG_CLEAR_MOOD_EVENT, "creampie") - return ..() - -/datum/component/creamed/RegisterWithParent() - RegisterSignal(parent, list( - COMSIG_COMPONENT_CLEAN_ACT, - COMSIG_COMPONENT_CLEAN_FACE_ACT), - .proc/clean_up) - -/datum/component/creamed/UnregisterFromParent() - UnregisterSignal(parent, list( - COMSIG_COMPONENT_CLEAN_ACT, - COMSIG_COMPONENT_CLEAN_FACE_ACT)) - -///Callback to remove pieface -/datum/component/creamed/proc/clean_up(datum/source, strength) - if(strength >= CLEAN_WEAK) - qdel(src) +GLOBAL_LIST_INIT(creamable, typecacheof(list( + /mob/living/carbon/human, + /mob/living/carbon/monkey, + /mob/living/simple_animal/pet/dog/corgi, + /mob/living/silicon/ai))) + +/** + * Creamed component + * + * For when you have pie on your face + */ +/datum/component/creamed + dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS + + var/mutable_appearance/creamface + +/datum/component/creamed/Initialize() + if(!is_type_in_typecache(parent, GLOB.creamable)) + return COMPONENT_INCOMPATIBLE + + creamface = mutable_appearance('icons/effects/creampie.dmi') + + if(ishuman(parent)) + var/mob/living/carbon/human/H = parent + if(H.dna.species.limbs_id == "lizard") + creamface.icon_state = "creampie_lizard" + else + creamface.icon_state = "creampie_human" + SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "creampie", /datum/mood_event/creampie) + else if(ismonkey(parent)) + creamface.icon_state = "creampie_monkey" + else if(iscorgi(parent)) + creamface.icon_state = "creampie_corgi" + else if(isAI(parent)) + creamface.icon_state = "creampie_ai" + + var/atom/A = parent + A.add_overlay(creamface) + +/datum/component/creamed/Destroy(force, silent) + var/atom/A = parent + A.cut_overlay(creamface) + qdel(creamface) + if(ishuman(A)) + SEND_SIGNAL(A, COMSIG_CLEAR_MOOD_EVENT, "creampie") + return ..() + +/datum/component/creamed/RegisterWithParent() + RegisterSignal(parent, list( + COMSIG_COMPONENT_CLEAN_ACT, + COMSIG_COMPONENT_CLEAN_FACE_ACT), + .proc/clean_up) + +/datum/component/creamed/UnregisterFromParent() + UnregisterSignal(parent, list( + COMSIG_COMPONENT_CLEAN_ACT, + COMSIG_COMPONENT_CLEAN_FACE_ACT)) + +///Callback to remove pieface +/datum/component/creamed/proc/clean_up(datum/source, strength) + if(strength >= CLEAN_WEAK) + qdel(src) diff --git a/code/datums/components/decals/blood.dm b/code/datums/components/decals/blood.dm index 7fae97567822..3114ddb24e9e 100644 --- a/code/datums/components/decals/blood.dm +++ b/code/datums/components/decals/blood.dm @@ -1,39 +1,39 @@ -/datum/component/decal/blood - dupe_mode = COMPONENT_DUPE_UNIQUE - -/datum/component/decal/blood/Initialize(_icon, _icon_state, _dir, _cleanable=CLEAN_STRENGTH_BLOOD, _color, _layer=ABOVE_OBJ_LAYER) - if(!isitem(parent)) - return COMPONENT_INCOMPATIBLE - . = ..() - RegisterSignal(parent, COMSIG_ATOM_GET_EXAMINE_NAME, .proc/get_examine_name) - -/datum/component/decal/blood/generate_appearance(_icon, _icon_state, _dir, _layer, _color) - var/obj/item/I = parent - if(!_icon) - _icon = 'icons/effects/blood.dmi' - if(!_icon_state) - _icon_state = "itemblood" - var/icon = initial(I.icon) - var/icon_state = initial(I.icon_state) - if(!icon || !icon_state) - // It's something which takes on the look of other items, probably - icon = I.icon - icon_state = I.icon_state - var/static/list/blood_splatter_appearances = list() - //try to find a pre-processed blood-splatter. otherwise, make a new one - var/index = "[REF(icon)]-[icon_state]" - pic = blood_splatter_appearances[index] - - if(!pic) - var/icon/blood_splatter_icon = icon(initial(I.icon), initial(I.icon_state), , 1) //we only want to apply blood-splatters to the initial icon_state for each object - blood_splatter_icon.Blend("#fff", ICON_ADD) //fills the icon_state with white (except where it's transparent) - blood_splatter_icon.Blend(icon(_icon, _icon_state), ICON_MULTIPLY) //adds blood and the remaining white areas become transparant - pic = mutable_appearance(blood_splatter_icon, initial(I.icon_state)) - blood_splatter_appearances[index] = pic - return TRUE - -/datum/component/decal/blood/proc/get_examine_name(datum/source, mob/user, list/override) - var/atom/A = parent - override[EXAMINE_POSITION_ARTICLE] = A.gender == PLURAL? "some" : "a" - override[EXAMINE_POSITION_BEFORE] = " blood-stained " - return COMPONENT_EXNAME_CHANGED +/datum/component/decal/blood + dupe_mode = COMPONENT_DUPE_UNIQUE + +/datum/component/decal/blood/Initialize(_icon, _icon_state, _dir, _cleanable=CLEAN_STRENGTH_BLOOD, _color, _layer=ABOVE_OBJ_LAYER) + if(!isitem(parent)) + return COMPONENT_INCOMPATIBLE + . = ..() + RegisterSignal(parent, COMSIG_ATOM_GET_EXAMINE_NAME, .proc/get_examine_name) + +/datum/component/decal/blood/generate_appearance(_icon, _icon_state, _dir, _layer, _color) + var/obj/item/I = parent + if(!_icon) + _icon = 'icons/effects/blood.dmi' + if(!_icon_state) + _icon_state = "itemblood" + var/icon = initial(I.icon) + var/icon_state = initial(I.icon_state) + if(!icon || !icon_state) + // It's something which takes on the look of other items, probably + icon = I.icon + icon_state = I.icon_state + var/static/list/blood_splatter_appearances = list() + //try to find a pre-processed blood-splatter. otherwise, make a new one + var/index = "[REF(icon)]-[icon_state]" + pic = blood_splatter_appearances[index] + + if(!pic) + var/icon/blood_splatter_icon = icon(initial(I.icon), initial(I.icon_state), , 1) //we only want to apply blood-splatters to the initial icon_state for each object + blood_splatter_icon.Blend("#fff", ICON_ADD) //fills the icon_state with white (except where it's transparent) + blood_splatter_icon.Blend(icon(_icon, _icon_state), ICON_MULTIPLY) //adds blood and the remaining white areas become transparant + pic = mutable_appearance(blood_splatter_icon, initial(I.icon_state)) + blood_splatter_appearances[index] = pic + return TRUE + +/datum/component/decal/blood/proc/get_examine_name(datum/source, mob/user, list/override) + var/atom/A = parent + override[EXAMINE_POSITION_ARTICLE] = A.gender == PLURAL? "some" : "a" + override[EXAMINE_POSITION_BEFORE] = " blood-stained " + return COMPONENT_EXNAME_CHANGED diff --git a/code/datums/components/forensics.dm b/code/datums/components/forensics.dm index f5a82ce5c4aa..f682308cae4c 100644 --- a/code/datums/components/forensics.dm +++ b/code/datums/components/forensics.dm @@ -1,184 +1,184 @@ -/datum/component/forensics - dupe_mode = COMPONENT_DUPE_UNIQUE - can_transfer = TRUE - var/list/fingerprints //assoc print = print - var/list/hiddenprints //assoc ckey = realname/gloves/ckey - var/list/blood_DNA //assoc dna = bloodtype - var/list/fibers //assoc print = print - -/datum/component/forensics/InheritComponent(datum/component/forensics/F, original) //Use of | and |= being different here is INTENTIONAL. - fingerprints = fingerprints | F.fingerprints - hiddenprints = hiddenprints | F.hiddenprints - blood_DNA = blood_DNA | F.blood_DNA - fibers = fibers | F.fibers - check_blood() - return ..() - -/datum/component/forensics/Initialize(new_fingerprints, new_hiddenprints, new_blood_DNA, new_fibers) - if(!isatom(parent)) - return COMPONENT_INCOMPATIBLE - fingerprints = new_fingerprints - hiddenprints = new_hiddenprints - blood_DNA = new_blood_DNA - fibers = new_fibers - check_blood() - -/datum/component/forensics/RegisterWithParent() - check_blood() - RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_act) - -/datum/component/forensics/UnregisterFromParent() - UnregisterSignal(parent, list(COMSIG_COMPONENT_CLEAN_ACT)) - -/datum/component/forensics/PostTransfer() - if(!isatom(parent)) - return COMPONENT_INCOMPATIBLE - -/datum/component/forensics/proc/wipe_fingerprints() - fingerprints = null - return TRUE - -/datum/component/forensics/proc/wipe_hiddenprints() - return //no. - -/datum/component/forensics/proc/wipe_blood_DNA() - blood_DNA = null - if(isitem(parent)) - qdel(parent.GetComponent(/datum/component/decal/blood)) - return TRUE - -/datum/component/forensics/proc/wipe_fibers() - fibers = null - return TRUE - -/datum/component/forensics/proc/clean_act(datum/source, strength) - if(strength >= CLEAN_STRENGTH_FINGERPRINTS) - wipe_fingerprints() - if(strength >= CLEAN_STRENGTH_BLOOD) - wipe_blood_DNA() - if(strength >= CLEAN_STRENGTH_FIBERS) - wipe_fibers() - -/datum/component/forensics/proc/add_fingerprint_list(list/_fingerprints) //list(text) - if(!length(_fingerprints)) - return - LAZYINITLIST(fingerprints) - for(var/i in _fingerprints) //We use an associative list, make sure we don't just merge a non-associative list into ours. - fingerprints[i] = i - return TRUE - -/datum/component/forensics/proc/add_fingerprint(mob/living/M, ignoregloves = FALSE) - if(!isliving(M)) - if(!iscameramob(M)) - return - if(isaicamera(M)) - var/mob/camera/aiEye/ai_camera = M - if(!ai_camera.ai) - return - M = ai_camera.ai - add_hiddenprint(M) - if(ishuman(M)) - var/mob/living/carbon/human/H = M - add_fibers(H) - if(H.gloves) //Check if the gloves (if any) hide fingerprints - var/obj/item/clothing/gloves/G = H.gloves - if(G.transfer_prints) - ignoregloves = TRUE - if(!ignoregloves) - H.gloves.add_fingerprint(H, TRUE) //ignoregloves = 1 to avoid infinite loop. - return - var/full_print = md5(H.dna.uni_identity) - LAZYSET(fingerprints, full_print, full_print) - return TRUE - -/datum/component/forensics/proc/add_fiber_list(list/_fibertext) //list(text) - if(!length(_fibertext)) - return - LAZYINITLIST(fibers) - for(var/i in _fibertext) //We use an associative list, make sure we don't just merge a non-associative list into ours. - fibers[i] = i - return TRUE - -/datum/component/forensics/proc/add_fibers(mob/living/carbon/human/M) - var/fibertext - var/item_multiplier = isitem(src)?1.2:1 - if(M.wear_suit) - fibertext = "Material from \a [M.wear_suit]." - if(prob(10*item_multiplier) && !LAZYACCESS(fibers, fibertext)) - LAZYSET(fibers, fibertext, fibertext) - if(!(M.wear_suit.body_parts_covered & CHEST)) - if(M.w_uniform) - fibertext = "Fibers from \a [M.w_uniform]." - if(prob(12*item_multiplier) && !LAZYACCESS(fibers, fibertext)) //Wearing a suit means less of the uniform exposed. - LAZYSET(fibers, fibertext, fibertext) - if(!(M.wear_suit.body_parts_covered & HANDS)) - if(M.gloves) - fibertext = "Material from a pair of [M.gloves.name]." - if(prob(20*item_multiplier) && !LAZYACCESS(fibers, fibertext)) - LAZYSET(fibers, fibertext, fibertext) - else if(M.w_uniform) - fibertext = "Fibers from \a [M.w_uniform]." - if(prob(15*item_multiplier) && !LAZYACCESS(fibers, fibertext)) - // "Added fibertext: [fibertext]" - LAZYSET(fibers, fibertext, fibertext) - if(M.gloves) - fibertext = "Material from a pair of [M.gloves.name]." - if(prob(20*item_multiplier) && !LAZYACCESS(fibers, fibertext)) - LAZYSET(fibers, fibertext, fibertext) - else if(M.gloves) - fibertext = "Material from a pair of [M.gloves.name]." - if(prob(20*item_multiplier) && !LAZYACCESS(fibers, fibertext)) - LAZYSET(fibers, fibertext, fibertext) - return TRUE - -/datum/component/forensics/proc/add_hiddenprint_list(list/_hiddenprints) //list(ckey = text) - if(!length(_hiddenprints)) - return - LAZYINITLIST(hiddenprints) - for(var/i in _hiddenprints) //We use an associative list, make sure we don't just merge a non-associative list into ours. - hiddenprints[i] = _hiddenprints[i] - return TRUE - -/datum/component/forensics/proc/add_hiddenprint(mob/M) - if(!isliving(M)) - if(!iscameramob(M)) - return - if(isaicamera(M)) - var/mob/camera/aiEye/ai_camera = M - if(!ai_camera.ai) - return - M = ai_camera.ai - if(!M.key) - return - var/hasgloves = "" - if(ishuman(M)) - var/mob/living/carbon/human/H = M - if(H.gloves) - hasgloves = "(gloves)" - var/current_time = time_stamp() - if(!LAZYACCESS(hiddenprints, M.key)) - LAZYSET(hiddenprints, M.key, "First: [M.real_name]\[[current_time]\][hasgloves]. Ckey: [M.ckey]") - else - var/laststamppos = findtext(LAZYACCESS(hiddenprints, M.key), " Last: ") - if(laststamppos) - LAZYSET(hiddenprints, M.key, copytext(hiddenprints[M.key], 1, laststamppos)) - hiddenprints[M.key] += " Last: [M.real_name]\[[current_time]\][hasgloves]. Ckey: [M.ckey]" //made sure to be existing by if(!LAZYACCESS);else - var/atom/A = parent - A.fingerprintslast = M.ckey - return TRUE - -/datum/component/forensics/proc/add_blood_DNA(list/dna) //list(dna_enzymes = type) - if(!length(dna)) - return - LAZYINITLIST(blood_DNA) - for(var/i in dna) - blood_DNA[i] = dna[i] - check_blood() - return TRUE - -/datum/component/forensics/proc/check_blood() - if(!isitem(parent)) - return - if(!length(blood_DNA)) - return - parent.LoadComponent(/datum/component/decal/blood) +/datum/component/forensics + dupe_mode = COMPONENT_DUPE_UNIQUE + can_transfer = TRUE + var/list/fingerprints //assoc print = print + var/list/hiddenprints //assoc ckey = realname/gloves/ckey + var/list/blood_DNA //assoc dna = bloodtype + var/list/fibers //assoc print = print + +/datum/component/forensics/InheritComponent(datum/component/forensics/F, original) //Use of | and |= being different here is INTENTIONAL. + fingerprints = fingerprints | F.fingerprints + hiddenprints = hiddenprints | F.hiddenprints + blood_DNA = blood_DNA | F.blood_DNA + fibers = fibers | F.fibers + check_blood() + return ..() + +/datum/component/forensics/Initialize(new_fingerprints, new_hiddenprints, new_blood_DNA, new_fibers) + if(!isatom(parent)) + return COMPONENT_INCOMPATIBLE + fingerprints = new_fingerprints + hiddenprints = new_hiddenprints + blood_DNA = new_blood_DNA + fibers = new_fibers + check_blood() + +/datum/component/forensics/RegisterWithParent() + check_blood() + RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_act) + +/datum/component/forensics/UnregisterFromParent() + UnregisterSignal(parent, list(COMSIG_COMPONENT_CLEAN_ACT)) + +/datum/component/forensics/PostTransfer() + if(!isatom(parent)) + return COMPONENT_INCOMPATIBLE + +/datum/component/forensics/proc/wipe_fingerprints() + fingerprints = null + return TRUE + +/datum/component/forensics/proc/wipe_hiddenprints() + return //no. + +/datum/component/forensics/proc/wipe_blood_DNA() + blood_DNA = null + if(isitem(parent)) + qdel(parent.GetComponent(/datum/component/decal/blood)) + return TRUE + +/datum/component/forensics/proc/wipe_fibers() + fibers = null + return TRUE + +/datum/component/forensics/proc/clean_act(datum/source, strength) + if(strength >= CLEAN_STRENGTH_FINGERPRINTS) + wipe_fingerprints() + if(strength >= CLEAN_STRENGTH_BLOOD) + wipe_blood_DNA() + if(strength >= CLEAN_STRENGTH_FIBERS) + wipe_fibers() + +/datum/component/forensics/proc/add_fingerprint_list(list/_fingerprints) //list(text) + if(!length(_fingerprints)) + return + LAZYINITLIST(fingerprints) + for(var/i in _fingerprints) //We use an associative list, make sure we don't just merge a non-associative list into ours. + fingerprints[i] = i + return TRUE + +/datum/component/forensics/proc/add_fingerprint(mob/living/M, ignoregloves = FALSE) + if(!isliving(M)) + if(!iscameramob(M)) + return + if(isaicamera(M)) + var/mob/camera/aiEye/ai_camera = M + if(!ai_camera.ai) + return + M = ai_camera.ai + add_hiddenprint(M) + if(ishuman(M)) + var/mob/living/carbon/human/H = M + add_fibers(H) + if(H.gloves) //Check if the gloves (if any) hide fingerprints + var/obj/item/clothing/gloves/G = H.gloves + if(G.transfer_prints) + ignoregloves = TRUE + if(!ignoregloves) + H.gloves.add_fingerprint(H, TRUE) //ignoregloves = 1 to avoid infinite loop. + return + var/full_print = md5(H.dna.uni_identity) + LAZYSET(fingerprints, full_print, full_print) + return TRUE + +/datum/component/forensics/proc/add_fiber_list(list/_fibertext) //list(text) + if(!length(_fibertext)) + return + LAZYINITLIST(fibers) + for(var/i in _fibertext) //We use an associative list, make sure we don't just merge a non-associative list into ours. + fibers[i] = i + return TRUE + +/datum/component/forensics/proc/add_fibers(mob/living/carbon/human/M) + var/fibertext + var/item_multiplier = isitem(src)?1.2:1 + if(M.wear_suit) + fibertext = "Material from \a [M.wear_suit]." + if(prob(10*item_multiplier) && !LAZYACCESS(fibers, fibertext)) + LAZYSET(fibers, fibertext, fibertext) + if(!(M.wear_suit.body_parts_covered & CHEST)) + if(M.w_uniform) + fibertext = "Fibers from \a [M.w_uniform]." + if(prob(12*item_multiplier) && !LAZYACCESS(fibers, fibertext)) //Wearing a suit means less of the uniform exposed. + LAZYSET(fibers, fibertext, fibertext) + if(!(M.wear_suit.body_parts_covered & HANDS)) + if(M.gloves) + fibertext = "Material from a pair of [M.gloves.name]." + if(prob(20*item_multiplier) && !LAZYACCESS(fibers, fibertext)) + LAZYSET(fibers, fibertext, fibertext) + else if(M.w_uniform) + fibertext = "Fibers from \a [M.w_uniform]." + if(prob(15*item_multiplier) && !LAZYACCESS(fibers, fibertext)) + // "Added fibertext: [fibertext]" + LAZYSET(fibers, fibertext, fibertext) + if(M.gloves) + fibertext = "Material from a pair of [M.gloves.name]." + if(prob(20*item_multiplier) && !LAZYACCESS(fibers, fibertext)) + LAZYSET(fibers, fibertext, fibertext) + else if(M.gloves) + fibertext = "Material from a pair of [M.gloves.name]." + if(prob(20*item_multiplier) && !LAZYACCESS(fibers, fibertext)) + LAZYSET(fibers, fibertext, fibertext) + return TRUE + +/datum/component/forensics/proc/add_hiddenprint_list(list/_hiddenprints) //list(ckey = text) + if(!length(_hiddenprints)) + return + LAZYINITLIST(hiddenprints) + for(var/i in _hiddenprints) //We use an associative list, make sure we don't just merge a non-associative list into ours. + hiddenprints[i] = _hiddenprints[i] + return TRUE + +/datum/component/forensics/proc/add_hiddenprint(mob/M) + if(!isliving(M)) + if(!iscameramob(M)) + return + if(isaicamera(M)) + var/mob/camera/aiEye/ai_camera = M + if(!ai_camera.ai) + return + M = ai_camera.ai + if(!M.key) + return + var/hasgloves = "" + if(ishuman(M)) + var/mob/living/carbon/human/H = M + if(H.gloves) + hasgloves = "(gloves)" + var/current_time = time_stamp() + if(!LAZYACCESS(hiddenprints, M.key)) + LAZYSET(hiddenprints, M.key, "First: [M.real_name]\[[current_time]\][hasgloves]. Ckey: [M.ckey]") + else + var/laststamppos = findtext(LAZYACCESS(hiddenprints, M.key), " Last: ") + if(laststamppos) + LAZYSET(hiddenprints, M.key, copytext(hiddenprints[M.key], 1, laststamppos)) + hiddenprints[M.key] += " Last: [M.real_name]\[[current_time]\][hasgloves]. Ckey: [M.ckey]" //made sure to be existing by if(!LAZYACCESS);else + var/atom/A = parent + A.fingerprintslast = M.ckey + return TRUE + +/datum/component/forensics/proc/add_blood_DNA(list/dna) //list(dna_enzymes = type) + if(!length(dna)) + return + LAZYINITLIST(blood_DNA) + for(var/i in dna) + blood_DNA[i] = dna[i] + check_blood() + return TRUE + +/datum/component/forensics/proc/check_blood() + if(!isitem(parent)) + return + if(!length(blood_DNA)) + return + parent.LoadComponent(/datum/component/decal/blood) diff --git a/code/datums/components/gps.dm b/code/datums/components/gps.dm index 200ec2b956b6..c2b3ad1f30c2 100644 --- a/code/datums/components/gps.dm +++ b/code/datums/components/gps.dm @@ -80,19 +80,15 @@ GLOBAL_LIST_EMPTY(GPS_list) to_chat(user, "[parent] is now tracking, and visible to other GPS devices.") tracking = TRUE -/datum/component/gps/item/ui_interact(mob/user, ui_key = "gps", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) // Remember to use the appropriate state. +/datum/component/gps/item/ui_interact(mob/user, datum/tgui/ui) if(emped) to_chat(user, "[parent] fizzles weakly.") return - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - // Variable window height, depending on how many GPS units there are - // to show, clamped to relatively safe range. - var/gps_window_height = clamp(325 + GLOB.GPS_list.len * 14, 325, 700) - ui = new(user, src, ui_key, "Gps", "Global Positioning System", 470, gps_window_height, master_ui, state) //width, height + ui = new(user, src, "Gps") ui.open() - - ui.set_autoupdate(state = updating) + ui.set_autoupdate(updating) /datum/component/gps/item/ui_data(mob/user) var/list/data = list() diff --git a/code/datums/components/jousting.dm b/code/datums/components/jousting.dm index cf2570cb9508..deef1284944a 100644 --- a/code/datums/components/jousting.dm +++ b/code/datums/components/jousting.dm @@ -1,74 +1,74 @@ -/datum/component/jousting - var/current_direction = NONE - var/max_tile_charge = 5 - var/min_tile_charge = 2 //tiles before this code gets into effect. - var/current_tile_charge = 0 - var/movement_reset_tolerance = 2 //deciseconds - var/unmounted_damage_boost_per_tile = 0 - var/unmounted_knockdown_chance_per_tile = 0 - var/unmounted_knockdown_time = 0 - var/mounted_damage_boost_per_tile = 2 - var/mounted_knockdown_chance_per_tile = 20 - var/mounted_knockdown_time = 20 - var/requires_mob_riding = TRUE //whether this only works if the attacker is riding a mob, rather than anything they can buckle to. - var/requires_mount = TRUE //kinda defeats the point of jousting if you're not mounted but whatever. - var/mob/current_holder - var/current_timerid - -/datum/component/jousting/Initialize() - if(!isitem(parent)) - return COMPONENT_INCOMPATIBLE - RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/on_equip) - RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/on_drop) - RegisterSignal(parent, COMSIG_ITEM_ATTACK, .proc/on_attack) - -/datum/component/jousting/proc/on_equip(datum/source, mob/user, slot) - RegisterSignal(user, COMSIG_MOVABLE_MOVED, .proc/mob_move, TRUE) - current_holder = user - -/datum/component/jousting/proc/on_drop(datum/source, mob/user) - UnregisterSignal(user, COMSIG_MOVABLE_MOVED) - current_holder = null - current_direction = NONE - current_tile_charge = 0 - -/datum/component/jousting/proc/on_attack(datum/source, mob/living/target, mob/user) - if(user != current_holder) - return - var/current = current_tile_charge - var/obj/item/I = parent - var/target_buckled = target.buckled ? TRUE : FALSE //we don't need the reference of what they're buckled to, just whether they are. - if((requires_mount && ((requires_mob_riding && !ismob(user.buckled)) || (!user.buckled))) || !current_direction || (current_tile_charge < min_tile_charge)) - return - var/turf/target_turf = get_step(user, current_direction) - if(target in range(1, target_turf)) - var/knockdown_chance = (target_buckled? mounted_knockdown_chance_per_tile : unmounted_knockdown_chance_per_tile) * current - var/knockdown_time = (target_buckled? mounted_knockdown_time : unmounted_knockdown_time) - var/damage = (target_buckled? mounted_damage_boost_per_tile : unmounted_damage_boost_per_tile) * current - var/sharp = I.get_sharpness() - var/msg - if(damage) - msg += "[user] [sharp? "impales" : "slams into"] [target] [sharp? "on" : "with"] their [parent]" - target.apply_damage(damage, BRUTE, user.zone_selected, 0) - if(prob(knockdown_chance)) - msg += " and knocks [target] [target_buckled? "off of [target.buckled]" : "down"]" - if(target_buckled) - target.buckled.unbuckle_mob(target) - target.Paralyze(knockdown_time) - if(length(msg)) - user.visible_message("[msg]!") - -/datum/component/jousting/proc/mob_move(datum/source, newloc, dir) - if(!current_holder || (requires_mount && ((requires_mob_riding && !ismob(current_holder.buckled)) || (!current_holder.buckled)))) - return - if(dir != current_direction) - current_tile_charge = 0 - current_direction = dir - if(current_tile_charge < max_tile_charge) - current_tile_charge++ - if(current_timerid) - deltimer(current_timerid) - current_timerid = addtimer(CALLBACK(src, .proc/reset_charge), movement_reset_tolerance, TIMER_STOPPABLE) - -/datum/component/jousting/proc/reset_charge() - current_tile_charge = 0 +/datum/component/jousting + var/current_direction = NONE + var/max_tile_charge = 5 + var/min_tile_charge = 2 //tiles before this code gets into effect. + var/current_tile_charge = 0 + var/movement_reset_tolerance = 2 //deciseconds + var/unmounted_damage_boost_per_tile = 0 + var/unmounted_knockdown_chance_per_tile = 0 + var/unmounted_knockdown_time = 0 + var/mounted_damage_boost_per_tile = 2 + var/mounted_knockdown_chance_per_tile = 20 + var/mounted_knockdown_time = 20 + var/requires_mob_riding = TRUE //whether this only works if the attacker is riding a mob, rather than anything they can buckle to. + var/requires_mount = TRUE //kinda defeats the point of jousting if you're not mounted but whatever. + var/mob/current_holder + var/current_timerid + +/datum/component/jousting/Initialize() + if(!isitem(parent)) + return COMPONENT_INCOMPATIBLE + RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/on_equip) + RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/on_drop) + RegisterSignal(parent, COMSIG_ITEM_ATTACK, .proc/on_attack) + +/datum/component/jousting/proc/on_equip(datum/source, mob/user, slot) + RegisterSignal(user, COMSIG_MOVABLE_MOVED, .proc/mob_move, TRUE) + current_holder = user + +/datum/component/jousting/proc/on_drop(datum/source, mob/user) + UnregisterSignal(user, COMSIG_MOVABLE_MOVED) + current_holder = null + current_direction = NONE + current_tile_charge = 0 + +/datum/component/jousting/proc/on_attack(datum/source, mob/living/target, mob/user) + if(user != current_holder) + return + var/current = current_tile_charge + var/obj/item/I = parent + var/target_buckled = target.buckled ? TRUE : FALSE //we don't need the reference of what they're buckled to, just whether they are. + if((requires_mount && ((requires_mob_riding && !ismob(user.buckled)) || (!user.buckled))) || !current_direction || (current_tile_charge < min_tile_charge)) + return + var/turf/target_turf = get_step(user, current_direction) + if(target in range(1, target_turf)) + var/knockdown_chance = (target_buckled? mounted_knockdown_chance_per_tile : unmounted_knockdown_chance_per_tile) * current + var/knockdown_time = (target_buckled? mounted_knockdown_time : unmounted_knockdown_time) + var/damage = (target_buckled? mounted_damage_boost_per_tile : unmounted_damage_boost_per_tile) * current + var/sharp = I.get_sharpness() + var/msg + if(damage) + msg += "[user] [sharp? "impales" : "slams into"] [target] [sharp? "on" : "with"] their [parent]" + target.apply_damage(damage, BRUTE, user.zone_selected, 0) + if(prob(knockdown_chance)) + msg += " and knocks [target] [target_buckled? "off of [target.buckled]" : "down"]" + if(target_buckled) + target.buckled.unbuckle_mob(target) + target.Paralyze(knockdown_time) + if(length(msg)) + user.visible_message("[msg]!") + +/datum/component/jousting/proc/mob_move(datum/source, newloc, dir) + if(!current_holder || (requires_mount && ((requires_mob_riding && !ismob(current_holder.buckled)) || (!current_holder.buckled)))) + return + if(dir != current_direction) + current_tile_charge = 0 + current_direction = dir + if(current_tile_charge < max_tile_charge) + current_tile_charge++ + if(current_timerid) + deltimer(current_timerid) + current_timerid = addtimer(CALLBACK(src, .proc/reset_charge), movement_reset_tolerance, TIMER_STOPPABLE) + +/datum/component/jousting/proc/reset_charge() + current_tile_charge = 0 diff --git a/code/datums/components/lockon_aiming.dm b/code/datums/components/lockon_aiming.dm index c27b9ce0d602..af15ffe992a8 100644 --- a/code/datums/components/lockon_aiming.dm +++ b/code/datums/components/lockon_aiming.dm @@ -1,214 +1,214 @@ -#define LOCKON_AIMING_MAX_CURSOR_RADIUS 7 -#define LOCKON_IGNORE_RESULT "ignore_my_result" -#define LOCKON_RANGING_BREAK_CHECK if(current_ranging_id != this_id){return LOCKON_IGNORE_RESULT} - -/datum/component/lockon_aiming - dupe_mode = COMPONENT_DUPE_ALLOWED - var/lock_icon = 'icons/mob/cameramob.dmi' - var/lock_icon_state = "marker" - var/mutable_appearance/lock_appearance - var/list/image/lock_images - var/list/target_typecache - var/list/immune_weakrefs //list(weakref = TRUE) - var/mob_stat_check = TRUE //if a potential target is a mob make sure it's conscious! - var/lock_amount = 1 - var/lock_cursor_range = 5 - var/list/locked_weakrefs - var/update_disabled = FALSE - var/current_ranging_id = 0 - var/list/last_location - var/datum/callback/on_lock - var/datum/callback/can_target_callback - -/datum/component/lockon_aiming/Initialize(range, list/typecache, amount, list/immune, datum/callback/when_locked, icon, icon_state, datum/callback/target_callback) - if(!ismob(parent)) - return COMPONENT_INCOMPATIBLE - if(target_callback) - can_target_callback = target_callback - else - can_target_callback = CALLBACK(src, .proc/can_target) - if(range) - lock_cursor_range = range - if(typecache) - target_typecache = typecache - if(amount) - lock_amount = amount - immune_weakrefs = list(WEAKREF(parent) = TRUE) //Manually take this out if you want.. - if(immune) - for(var/i in immune) - if(isweakref(i)) - immune_weakrefs[i] = TRUE - else if(isatom(i)) - immune_weakrefs[WEAKREF(i)] = TRUE - if(when_locked) - on_lock = when_locked - if(icon) - lock_icon = icon - if(icon_state) - lock_icon_state = icon_state - generate_lock_visuals() - START_PROCESSING(SSfastprocess, src) - -/datum/component/lockon_aiming/Destroy() - clear_visuals() - STOP_PROCESSING(SSfastprocess, src) - return ..() - -/datum/component/lockon_aiming/proc/show_visuals() - LAZYINITLIST(lock_images) - var/mob/M = parent - if(!M.client) - return - for(var/i in locked_weakrefs) - var/datum/weakref/R = i - var/atom/A = R.resolve() - if(!A) - continue //It'll be cleared by processing. - var/image/I = new - I.appearance = lock_appearance - I.loc = A - M.client.images |= I - lock_images |= I - -/datum/component/lockon_aiming/proc/clear_visuals() - var/mob/M = parent - if(!M.client) - return - if(!lock_images) - return - for(var/i in lock_images) - M.client.images -= i - qdel(i) - lock_images.Cut() - -/datum/component/lockon_aiming/proc/refresh_visuals() - clear_visuals() - show_visuals() - -/datum/component/lockon_aiming/proc/generate_lock_visuals() - lock_appearance = mutable_appearance(icon = lock_icon, icon_state = lock_icon_state, layer = FLOAT_LAYER) - -/datum/component/lockon_aiming/proc/unlock_all(refresh_vis = TRUE) - LAZYCLEARLIST(locked_weakrefs) - if(refresh_vis) - refresh_visuals() - -/datum/component/lockon_aiming/proc/unlock(atom/A, refresh_vis = TRUE) - if(!A.weak_reference) - return - LAZYREMOVE(locked_weakrefs, A.weak_reference) - if(refresh_vis) - refresh_visuals() - -/datum/component/lockon_aiming/proc/lock(atom/A, refresh_vis = TRUE) - LAZYOR(locked_weakrefs, WEAKREF(A)) - if(refresh_vis) - refresh_visuals() - -/datum/component/lockon_aiming/proc/add_immune_atom(atom/A) - var/datum/weakref/R = WEAKREF(A) - if(immune_weakrefs && (immune_weakrefs[R])) - return - LAZYSET(immune_weakrefs, R, TRUE) - -/datum/component/lockon_aiming/proc/remove_immune_atom(atom/A) - if(!A.weak_reference || !immune_weakrefs) //if A doesn't have a weakref how did it get on the immunity list? - return - LAZYREMOVE(immune_weakrefs, A.weak_reference) - -/datum/component/lockon_aiming/process() - if(update_disabled) - return - if(!last_location) - return - var/changed = FALSE - for(var/i in locked_weakrefs) - var/datum/weakref/R = i - if(istype(R)) - var/atom/thing = R.resolve() - if(!istype(thing) || (get_dist(thing, locate(last_location[1], last_location[2], last_location[3])) > lock_cursor_range)) - unlock(R) - changed = TRUE - else - unlock(R) - changed = TRUE - if(changed) - autolock() - -/datum/component/lockon_aiming/proc/autolock() - var/mob/M = parent - if(!M.client) - return FALSE - var/datum/position/current = mouse_absolute_datum_map_position_from_client(M.client) - var/turf/target = current.return_turf() - var/list/atom/targets = get_nearest(target, target_typecache, lock_amount, lock_cursor_range) - if(targets == LOCKON_IGNORE_RESULT) - return - unlock_all(FALSE) - for(var/i in targets) - if(immune_weakrefs[WEAKREF(i)]) - continue - lock(i, FALSE) - refresh_visuals() - on_lock.Invoke(locked_weakrefs) - -/datum/component/lockon_aiming/proc/can_target(atom/A) - var/mob/M = A - return is_type_in_typecache(A, target_typecache) && !(ismob(A) && mob_stat_check && M.stat != CONSCIOUS) && !immune_weakrefs[WEAKREF(A)] - -/datum/component/lockon_aiming/proc/get_nearest(turf/T, list/typecache, amount, range) - current_ranging_id++ - var/this_id = current_ranging_id - var/list/L = list() - var/turf/center = get_turf(T) - if(amount < 1 || range < 0 || !istype(center) || !islist(typecache)) - return - if(range == 0) - return typecache_filter_list(T.contents + T, typecache) - var/x = 0 - var/y = 0 - var/cd = 0 - while(cd <= range) - x = center.x - cd + 1 - y = center.y + cd - LOCKON_RANGING_BREAK_CHECK - for(x in x to center.x + cd) - T = locate(x, y, center.z) - if(T) - L |= special_list_filter(T.contents, can_target_callback) - if(L.len >= amount) - L.Cut(amount+1) - return L - LOCKON_RANGING_BREAK_CHECK - y = center.y + cd - 1 - x = center.x + cd - for(y in center.y - cd to y) - T = locate(x, y, center.z) - if(T) - L |= special_list_filter(T.contents, can_target_callback) - if(L.len >= amount) - L.Cut(amount+1) - return L - LOCKON_RANGING_BREAK_CHECK - y = center.y - cd - x = center.x + cd - 1 - for(x in center.x - cd to x) - T = locate(x, y, center.z) - if(T) - L |= special_list_filter(T.contents, can_target_callback) - if(L.len >= amount) - L.Cut(amount+1) - return L - LOCKON_RANGING_BREAK_CHECK - y = center.y - cd + 1 - x = center.x - cd - for(y in y to center.y + cd) - T = locate(x, y, center.z) - if(T) - L |= special_list_filter(T.contents, can_target_callback) - if(L.len >= amount) - L.Cut(amount+1) - return L - LOCKON_RANGING_BREAK_CHECK - cd++ - CHECK_TICK +#define LOCKON_AIMING_MAX_CURSOR_RADIUS 7 +#define LOCKON_IGNORE_RESULT "ignore_my_result" +#define LOCKON_RANGING_BREAK_CHECK if(current_ranging_id != this_id){return LOCKON_IGNORE_RESULT} + +/datum/component/lockon_aiming + dupe_mode = COMPONENT_DUPE_ALLOWED + var/lock_icon = 'icons/mob/cameramob.dmi' + var/lock_icon_state = "marker" + var/mutable_appearance/lock_appearance + var/list/image/lock_images + var/list/target_typecache + var/list/immune_weakrefs //list(weakref = TRUE) + var/mob_stat_check = TRUE //if a potential target is a mob make sure it's conscious! + var/lock_amount = 1 + var/lock_cursor_range = 5 + var/list/locked_weakrefs + var/update_disabled = FALSE + var/current_ranging_id = 0 + var/list/last_location + var/datum/callback/on_lock + var/datum/callback/can_target_callback + +/datum/component/lockon_aiming/Initialize(range, list/typecache, amount, list/immune, datum/callback/when_locked, icon, icon_state, datum/callback/target_callback) + if(!ismob(parent)) + return COMPONENT_INCOMPATIBLE + if(target_callback) + can_target_callback = target_callback + else + can_target_callback = CALLBACK(src, .proc/can_target) + if(range) + lock_cursor_range = range + if(typecache) + target_typecache = typecache + if(amount) + lock_amount = amount + immune_weakrefs = list(WEAKREF(parent) = TRUE) //Manually take this out if you want.. + if(immune) + for(var/i in immune) + if(isweakref(i)) + immune_weakrefs[i] = TRUE + else if(isatom(i)) + immune_weakrefs[WEAKREF(i)] = TRUE + if(when_locked) + on_lock = when_locked + if(icon) + lock_icon = icon + if(icon_state) + lock_icon_state = icon_state + generate_lock_visuals() + START_PROCESSING(SSfastprocess, src) + +/datum/component/lockon_aiming/Destroy() + clear_visuals() + STOP_PROCESSING(SSfastprocess, src) + return ..() + +/datum/component/lockon_aiming/proc/show_visuals() + LAZYINITLIST(lock_images) + var/mob/M = parent + if(!M.client) + return + for(var/i in locked_weakrefs) + var/datum/weakref/R = i + var/atom/A = R.resolve() + if(!A) + continue //It'll be cleared by processing. + var/image/I = new + I.appearance = lock_appearance + I.loc = A + M.client.images |= I + lock_images |= I + +/datum/component/lockon_aiming/proc/clear_visuals() + var/mob/M = parent + if(!M.client) + return + if(!lock_images) + return + for(var/i in lock_images) + M.client.images -= i + qdel(i) + lock_images.Cut() + +/datum/component/lockon_aiming/proc/refresh_visuals() + clear_visuals() + show_visuals() + +/datum/component/lockon_aiming/proc/generate_lock_visuals() + lock_appearance = mutable_appearance(icon = lock_icon, icon_state = lock_icon_state, layer = FLOAT_LAYER) + +/datum/component/lockon_aiming/proc/unlock_all(refresh_vis = TRUE) + LAZYCLEARLIST(locked_weakrefs) + if(refresh_vis) + refresh_visuals() + +/datum/component/lockon_aiming/proc/unlock(atom/A, refresh_vis = TRUE) + if(!A.weak_reference) + return + LAZYREMOVE(locked_weakrefs, A.weak_reference) + if(refresh_vis) + refresh_visuals() + +/datum/component/lockon_aiming/proc/lock(atom/A, refresh_vis = TRUE) + LAZYOR(locked_weakrefs, WEAKREF(A)) + if(refresh_vis) + refresh_visuals() + +/datum/component/lockon_aiming/proc/add_immune_atom(atom/A) + var/datum/weakref/R = WEAKREF(A) + if(immune_weakrefs && (immune_weakrefs[R])) + return + LAZYSET(immune_weakrefs, R, TRUE) + +/datum/component/lockon_aiming/proc/remove_immune_atom(atom/A) + if(!A.weak_reference || !immune_weakrefs) //if A doesn't have a weakref how did it get on the immunity list? + return + LAZYREMOVE(immune_weakrefs, A.weak_reference) + +/datum/component/lockon_aiming/process() + if(update_disabled) + return + if(!last_location) + return + var/changed = FALSE + for(var/i in locked_weakrefs) + var/datum/weakref/R = i + if(istype(R)) + var/atom/thing = R.resolve() + if(!istype(thing) || (get_dist(thing, locate(last_location[1], last_location[2], last_location[3])) > lock_cursor_range)) + unlock(R) + changed = TRUE + else + unlock(R) + changed = TRUE + if(changed) + autolock() + +/datum/component/lockon_aiming/proc/autolock() + var/mob/M = parent + if(!M.client) + return FALSE + var/datum/position/current = mouse_absolute_datum_map_position_from_client(M.client) + var/turf/target = current.return_turf() + var/list/atom/targets = get_nearest(target, target_typecache, lock_amount, lock_cursor_range) + if(targets == LOCKON_IGNORE_RESULT) + return + unlock_all(FALSE) + for(var/i in targets) + if(immune_weakrefs[WEAKREF(i)]) + continue + lock(i, FALSE) + refresh_visuals() + on_lock.Invoke(locked_weakrefs) + +/datum/component/lockon_aiming/proc/can_target(atom/A) + var/mob/M = A + return is_type_in_typecache(A, target_typecache) && !(ismob(A) && mob_stat_check && M.stat != CONSCIOUS) && !immune_weakrefs[WEAKREF(A)] + +/datum/component/lockon_aiming/proc/get_nearest(turf/T, list/typecache, amount, range) + current_ranging_id++ + var/this_id = current_ranging_id + var/list/L = list() + var/turf/center = get_turf(T) + if(amount < 1 || range < 0 || !istype(center) || !islist(typecache)) + return + if(range == 0) + return typecache_filter_list(T.contents + T, typecache) + var/x = 0 + var/y = 0 + var/cd = 0 + while(cd <= range) + x = center.x - cd + 1 + y = center.y + cd + LOCKON_RANGING_BREAK_CHECK + for(x in x to center.x + cd) + T = locate(x, y, center.z) + if(T) + L |= special_list_filter(T.contents, can_target_callback) + if(L.len >= amount) + L.Cut(amount+1) + return L + LOCKON_RANGING_BREAK_CHECK + y = center.y + cd - 1 + x = center.x + cd + for(y in center.y - cd to y) + T = locate(x, y, center.z) + if(T) + L |= special_list_filter(T.contents, can_target_callback) + if(L.len >= amount) + L.Cut(amount+1) + return L + LOCKON_RANGING_BREAK_CHECK + y = center.y - cd + x = center.x + cd - 1 + for(x in center.x - cd to x) + T = locate(x, y, center.z) + if(T) + L |= special_list_filter(T.contents, can_target_callback) + if(L.len >= amount) + L.Cut(amount+1) + return L + LOCKON_RANGING_BREAK_CHECK + y = center.y - cd + 1 + x = center.x - cd + for(y in y to center.y + cd) + T = locate(x, y, center.z) + if(T) + L |= special_list_filter(T.contents, can_target_callback) + if(L.len >= amount) + L.Cut(amount+1) + return L + LOCKON_RANGING_BREAK_CHECK + cd++ + CHECK_TICK diff --git a/code/datums/components/nanites.dm b/code/datums/components/nanites.dm index 15b5169bba3e..d1efea19a323 100644 --- a/code/datums/components/nanites.dm +++ b/code/datums/components/nanites.dm @@ -1,381 +1,381 @@ -/datum/component/nanites - dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS - - var/mob/living/host_mob - var/nanite_volume = 100 //amount of nanites in the system, used as fuel for nanite programs - var/max_nanites = 500 //maximum amount of nanites in the system - var/regen_rate = 0.5 //nanites generated per second - var/safety_threshold = 50 //how low nanites will get before they stop processing/triggering - var/cloud_id = 0 //0 if not connected to the cloud, 1-100 to set a determined cloud backup to draw from - var/cloud_active = TRUE //if false, won't sync to the cloud - var/next_sync = 0 - var/list/datum/nanite_program/programs = list() - var/max_programs = NANITE_PROGRAM_LIMIT - - var/list/datum/nanite_program/protocol/protocols = list() ///Separate list of protocol programs, to avoid looping through the whole programs list when cheking for conflicts - var/start_time = 0 ///Timestamp to when the nanites were first inserted in the host - var/stealth = FALSE //if TRUE, does not appear on HUDs and health scans - var/diagnostics = TRUE //if TRUE, displays program list when scanned by nanite scanners - -/datum/component/nanites/Initialize(amount = 100, cloud = 0) - if(!isliving(parent) && !istype(parent, /datum/nanite_cloud_backup)) - return COMPONENT_INCOMPATIBLE - - nanite_volume = amount - cloud_id = cloud - - //Nanites without hosts are non-interactive through normal means - if(isliving(parent)) - host_mob = parent - - if(!(host_mob.mob_biotypes & (MOB_ORGANIC|MOB_UNDEAD))) //Shouldn't happen, but this avoids HUD runtimes in case a silicon gets them somehow. - return COMPONENT_INCOMPATIBLE - - start_time = world.time - - host_mob.hud_set_nanite_indicator() - START_PROCESSING(SSnanites, src) - - if(cloud_id && cloud_active) - cloud_sync() - -/datum/component/nanites/RegisterWithParent() - RegisterSignal(parent, COMSIG_HAS_NANITES, .proc/confirm_nanites) - RegisterSignal(parent, COMSIG_NANITE_IS_STEALTHY, .proc/check_stealth) - RegisterSignal(parent, COMSIG_NANITE_DELETE, .proc/delete_nanites) - RegisterSignal(parent, COMSIG_NANITE_UI_DATA, .proc/nanite_ui_data) - RegisterSignal(parent, COMSIG_NANITE_GET_PROGRAMS, .proc/get_programs) - RegisterSignal(parent, COMSIG_NANITE_SET_VOLUME, .proc/set_volume) - RegisterSignal(parent, COMSIG_NANITE_ADJUST_VOLUME, .proc/adjust_nanites) - RegisterSignal(parent, COMSIG_NANITE_SET_MAX_VOLUME, .proc/set_max_volume) - RegisterSignal(parent, COMSIG_NANITE_SET_CLOUD, .proc/set_cloud) - RegisterSignal(parent, COMSIG_NANITE_SET_CLOUD_SYNC, .proc/set_cloud_sync) - RegisterSignal(parent, COMSIG_NANITE_SET_SAFETY, .proc/set_safety) - RegisterSignal(parent, COMSIG_NANITE_SET_REGEN, .proc/set_regen) - RegisterSignal(parent, COMSIG_NANITE_ADD_PROGRAM, .proc/add_program) - RegisterSignal(parent, COMSIG_NANITE_SCAN, .proc/nanite_scan) - RegisterSignal(parent, COMSIG_NANITE_SYNC, .proc/sync) - - if(isliving(parent)) - RegisterSignal(parent, COMSIG_ATOM_EMP_ACT, .proc/on_emp) - RegisterSignal(parent, COMSIG_MOB_DEATH, .proc/on_death) - RegisterSignal(parent, COMSIG_MOB_ALLOWED, .proc/check_access) - RegisterSignal(parent, COMSIG_LIVING_ELECTROCUTE_ACT, .proc/on_shock) - RegisterSignal(parent, COMSIG_LIVING_MINOR_SHOCK, .proc/on_minor_shock) - RegisterSignal(parent, COMSIG_SPECIES_GAIN, .proc/check_viable_biotype) - RegisterSignal(parent, COMSIG_NANITE_SIGNAL, .proc/receive_signal) - RegisterSignal(parent, COMSIG_NANITE_COMM_SIGNAL, .proc/receive_comm_signal) - -/datum/component/nanites/UnregisterFromParent() - UnregisterSignal(parent, list(COMSIG_HAS_NANITES, - COMSIG_NANITE_IS_STEALTHY, - COMSIG_NANITE_DELETE, - COMSIG_NANITE_UI_DATA, - COMSIG_NANITE_GET_PROGRAMS, - COMSIG_NANITE_SET_VOLUME, - COMSIG_NANITE_ADJUST_VOLUME, - COMSIG_NANITE_SET_MAX_VOLUME, - COMSIG_NANITE_SET_CLOUD, - COMSIG_NANITE_SET_CLOUD_SYNC, - COMSIG_NANITE_SET_SAFETY, - COMSIG_NANITE_SET_REGEN, - COMSIG_NANITE_ADD_PROGRAM, - COMSIG_NANITE_SCAN, - COMSIG_NANITE_SYNC, - COMSIG_ATOM_EMP_ACT, - COMSIG_MOB_DEATH, - COMSIG_MOB_ALLOWED, - COMSIG_LIVING_ELECTROCUTE_ACT, - COMSIG_LIVING_MINOR_SHOCK, - COMSIG_MOVABLE_HEAR, - COMSIG_SPECIES_GAIN, - COMSIG_NANITE_SIGNAL, - COMSIG_NANITE_COMM_SIGNAL)) - -/datum/component/nanites/Destroy() - STOP_PROCESSING(SSnanites, src) - QDEL_LIST(programs) - if(host_mob) - set_nanite_bar(TRUE) - host_mob.hud_set_nanite_indicator() - host_mob = null - return ..() - -/datum/component/nanites/InheritComponent(datum/component/nanites/new_nanites, i_am_original, amount, cloud) - if(new_nanites) - adjust_nanites(null, new_nanites.nanite_volume) - else - adjust_nanites(null, amount) //just add to the nanite volume - -/datum/component/nanites/process() - if(!IS_IN_STASIS(host_mob)) - adjust_nanites(null, regen_rate) - add_research() - for(var/X in programs) - var/datum/nanite_program/NP = X - NP.on_process() - if(cloud_id && cloud_active && world.time > next_sync) - cloud_sync() - next_sync = world.time + NANITE_SYNC_DELAY - set_nanite_bar() - - -/datum/component/nanites/proc/delete_nanites() - qdel(src) - -//Syncs the nanite component to another, making it so programs are the same with the same programming (except activation status) -/datum/component/nanites/proc/sync(datum/signal_source, datum/component/nanites/source, full_overwrite = TRUE, copy_activation = FALSE) - var/list/programs_to_remove = programs.Copy() - var/list/programs_to_add = source.programs.Copy() - for(var/X in programs) - var/datum/nanite_program/NP = X - for(var/Y in programs_to_add) - var/datum/nanite_program/SNP = Y - if(NP.type == SNP.type) - programs_to_remove -= NP - programs_to_add -= SNP - SNP.copy_programming(NP, copy_activation) - break - if(full_overwrite) - for(var/X in programs_to_remove) - qdel(X) - for(var/X in programs_to_add) - var/datum/nanite_program/SNP = X - add_program(null, SNP.copy()) - -/datum/component/nanites/proc/cloud_sync() - if(cloud_id) - var/datum/nanite_cloud_backup/backup = SSnanites.get_cloud_backup(cloud_id) - if(backup) - var/datum/component/nanites/cloud_copy = backup.nanites - if(cloud_copy) - sync(null, cloud_copy) - return - //Without cloud syncing nanites can accumulate errors and/or defects - if(prob(8) && programs.len) - var/datum/nanite_program/NP = pick(programs) - NP.software_error() - -/datum/component/nanites/proc/add_program(datum/source, datum/nanite_program/new_program, datum/nanite_program/source_program) - for(var/X in programs) - var/datum/nanite_program/NP = X - if(NP.unique && NP.type == new_program.type) - qdel(NP) - if(programs.len >= max_programs) - return COMPONENT_PROGRAM_NOT_INSTALLED - if(source_program) - source_program.copy_programming(new_program) - programs += new_program - new_program.on_add(src) - return COMPONENT_PROGRAM_INSTALLED - -/datum/component/nanites/proc/consume_nanites(amount, force = FALSE) - if(!force && safety_threshold && (nanite_volume - amount < safety_threshold)) - return FALSE - adjust_nanites(null, -amount) - return (nanite_volume > 0) - -/datum/component/nanites/proc/adjust_nanites(datum/source, amount) - nanite_volume = clamp(nanite_volume + amount, 0, max_nanites) - if(nanite_volume <= 0) //oops we ran out - qdel(src) - -/datum/component/nanites/proc/set_nanite_bar(remove = FALSE) - var/image/holder = host_mob.hud_list[DIAG_NANITE_FULL_HUD] - var/icon/I = icon(host_mob.icon, host_mob.icon_state, host_mob.dir) - holder.pixel_y = I.Height() - world.icon_size - holder.icon_state = null - if(remove || stealth) - return //bye icon - var/nanite_percent = (nanite_volume / max_nanites) * 100 - nanite_percent = clamp(CEILING(nanite_percent, 10), 10, 100) - holder.icon_state = "nanites[nanite_percent]" - -/datum/component/nanites/proc/on_emp(datum/source, severity) - nanite_volume *= (rand(60, 90) * 0.01) //Lose 10-40% of nanites - adjust_nanites(null, -(rand(5, 50))) //Lose 5-50 flat nanite volume - if(prob(40/severity)) - cloud_id = 0 - for(var/X in programs) - var/datum/nanite_program/NP = X - NP.on_emp(severity) - - -/datum/component/nanites/proc/on_shock(datum/source, shock_damage, siemens_coeff = 1, flags = NONE) - if(flags & SHOCK_ILLUSION || shock_damage < 1) - return - - if(!HAS_TRAIT_NOT_FROM(host_mob, TRAIT_SHOCKIMMUNE, "nanites"))//Another shock protection must protect nanites too, but nanites protect only host - nanite_volume *= (rand(45, 80) * 0.01) //Lose 20-55% of nanites - adjust_nanites(null, -(rand(5, 50))) //Lose 5-50 flat nanite volume - for(var/X in programs) - var/datum/nanite_program/NP = X - NP.on_shock(shock_damage) - -/datum/component/nanites/proc/on_minor_shock(datum/source) - adjust_nanites(null, -(rand(5, 15))) //Lose 5-15 flat nanite volume - for(var/X in programs) - var/datum/nanite_program/NP = X - NP.on_minor_shock() - -/datum/component/nanites/proc/check_stealth(datum/source) - return stealth - -/datum/component/nanites/proc/on_death(datum/source, gibbed) - for(var/X in programs) - var/datum/nanite_program/NP = X - NP.on_death(gibbed) - -/datum/component/nanites/proc/receive_signal(datum/source, code, source = "an unidentified source") - for(var/X in programs) - var/datum/nanite_program/NP = X - NP.receive_signal(code, source) - -/datum/component/nanites/proc/receive_comm_signal(datum/source, comm_code, comm_message, comm_source = "an unidentified source") - for(var/X in programs) - if(istype(X, /datum/nanite_program/comm)) - var/datum/nanite_program/comm/NP = X - NP.receive_comm_signal(comm_code, comm_message, comm_source) - -/datum/component/nanites/proc/check_viable_biotype() - if(!(host_mob.mob_biotypes & (MOB_ORGANIC|MOB_UNDEAD))) - qdel(src) //bodytype no longer sustains nanites - -/datum/component/nanites/proc/check_access(datum/source, obj/O) - for(var/datum/nanite_program/access/access_program in programs) - if(access_program.activated) - return O.check_access_list(access_program.access) - else - return FALSE - return FALSE - -/datum/component/nanites/proc/set_volume(datum/source, amount) - nanite_volume = clamp(amount, 0, max_nanites) - -/datum/component/nanites/proc/set_max_volume(datum/source, amount) - max_nanites = max(1, max_nanites) - -/datum/component/nanites/proc/set_cloud(datum/source, amount) - cloud_id = clamp(amount, 0, 100) - -/datum/component/nanites/proc/set_cloud_sync(datum/source, method) - switch(method) - if(NANITE_CLOUD_TOGGLE) - cloud_active = !cloud_active - if(NANITE_CLOUD_DISABLE) - cloud_active = FALSE - if(NANITE_CLOUD_ENABLE) - cloud_active = TRUE - -/datum/component/nanites/proc/set_safety(datum/source, amount) - safety_threshold = clamp(amount, 0, max_nanites) - -/datum/component/nanites/proc/set_regen(datum/source, amount) - regen_rate = amount - -/datum/component/nanites/proc/confirm_nanites() - return TRUE //yup i exist - -/datum/component/nanites/proc/get_data(list/nanite_data) - nanite_data["nanite_volume"] = nanite_volume - nanite_data["max_nanites"] = max_nanites - nanite_data["cloud_id"] = cloud_id - nanite_data["regen_rate"] = regen_rate - nanite_data["safety_threshold"] = safety_threshold - nanite_data["stealth"] = stealth - -/datum/component/nanites/proc/get_programs(datum/source, list/nanite_programs) - nanite_programs |= programs - -/datum/component/nanites/proc/add_research() - var/research_value = NANITE_BASE_RESEARCH - if(!ishuman(host_mob)) - if(!iscarbon(host_mob)) - research_value *= 0.4 - else - research_value *= 0.8 - if(!host_mob.client) - research_value *= 0.5 - if(host_mob.stat == DEAD) - research_value *= 0.75 - SSresearch.science_tech.add_point_list(list(TECHWEB_POINT_TYPE_NANITES = research_value)) - -/datum/component/nanites/proc/nanite_scan(datum/source, mob/user, full_scan) - if(!full_scan) - if(!stealth) - to_chat(user, "Nanites Detected") - to_chat(user, "Saturation: [nanite_volume]/[max_nanites]") - return TRUE - else - to_chat(user, "NANITES DETECTED") - to_chat(user, "================") - to_chat(user, "Saturation: [nanite_volume]/[max_nanites]") - to_chat(user, "Safety Threshold: [safety_threshold]") - to_chat(user, "Cloud ID: [cloud_id ? cloud_id : "None"]") - to_chat(user, "Cloud Sync: [cloud_active ? "Active" : "Disabled"]") - to_chat(user, "================") - to_chat(user, "Program List:") - if(!diagnostics) - to_chat(user, "Diagnostics Disabled") - else - for(var/X in programs) - var/datum/nanite_program/NP = X - to_chat(user, "[NP.name] | [NP.activated ? "Active" : "Inactive"]") - return TRUE - -/datum/component/nanites/proc/nanite_ui_data(datum/source, list/data, scan_level) - data["has_nanites"] = TRUE - data["nanite_volume"] = nanite_volume - data["regen_rate"] = regen_rate - data["safety_threshold"] = safety_threshold - data["cloud_id"] = cloud_id - data["cloud_active"] = cloud_active - var/list/mob_programs = list() - var/id = 1 - for(var/X in programs) - var/datum/nanite_program/P = X - var/list/mob_program = list() - mob_program["name"] = P.name - mob_program["desc"] = P.desc - mob_program["id"] = id - - if(scan_level >= 2) - mob_program["activated"] = P.activated - mob_program["use_rate"] = P.use_rate - mob_program["can_trigger"] = P.can_trigger - mob_program["trigger_cost"] = P.trigger_cost - mob_program["trigger_cooldown"] = P.trigger_cooldown / 10 - - if(scan_level >= 3) - mob_program["timer_restart"] = P.timer_restart / 10 - mob_program["timer_shutdown"] = P.timer_shutdown / 10 - mob_program["timer_trigger"] = P.timer_trigger / 10 - mob_program["timer_trigger_delay"] = P.timer_trigger_delay / 10 - var/list/extra_settings = P.get_extra_settings_frontend() - mob_program["extra_settings"] = extra_settings - if(LAZYLEN(extra_settings)) - mob_program["has_extra_settings"] = TRUE - else - mob_program["has_extra_settings"] = FALSE - - if(scan_level >= 4) - mob_program["activation_code"] = P.activation_code - mob_program["deactivation_code"] = P.deactivation_code - mob_program["kill_code"] = P.kill_code - mob_program["trigger_code"] = P.trigger_code - var/list/rules = list() - var/rule_id = 1 - for(var/Z in P.rules) - var/datum/nanite_rule/nanite_rule = Z - var/list/rule = list() - rule["display"] = nanite_rule.display() - rule["program_id"] = id - rule["id"] = rule_id - rules += list(rule) - rule_id++ - mob_program["rules"] = rules - if(LAZYLEN(rules)) - mob_program["has_rules"] = TRUE - id++ - mob_programs += list(mob_program) - data["mob_programs"] = mob_programs +/datum/component/nanites + dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS + + var/mob/living/host_mob + var/nanite_volume = 100 //amount of nanites in the system, used as fuel for nanite programs + var/max_nanites = 500 //maximum amount of nanites in the system + var/regen_rate = 0.5 //nanites generated per second + var/safety_threshold = 50 //how low nanites will get before they stop processing/triggering + var/cloud_id = 0 //0 if not connected to the cloud, 1-100 to set a determined cloud backup to draw from + var/cloud_active = TRUE //if false, won't sync to the cloud + var/next_sync = 0 + var/list/datum/nanite_program/programs = list() + var/max_programs = NANITE_PROGRAM_LIMIT + + var/list/datum/nanite_program/protocol/protocols = list() ///Separate list of protocol programs, to avoid looping through the whole programs list when cheking for conflicts + var/start_time = 0 ///Timestamp to when the nanites were first inserted in the host + var/stealth = FALSE //if TRUE, does not appear on HUDs and health scans + var/diagnostics = TRUE //if TRUE, displays program list when scanned by nanite scanners + +/datum/component/nanites/Initialize(amount = 100, cloud = 0) + if(!isliving(parent) && !istype(parent, /datum/nanite_cloud_backup)) + return COMPONENT_INCOMPATIBLE + + nanite_volume = amount + cloud_id = cloud + + //Nanites without hosts are non-interactive through normal means + if(isliving(parent)) + host_mob = parent + + if(!(host_mob.mob_biotypes & (MOB_ORGANIC|MOB_UNDEAD))) //Shouldn't happen, but this avoids HUD runtimes in case a silicon gets them somehow. + return COMPONENT_INCOMPATIBLE + + start_time = world.time + + host_mob.hud_set_nanite_indicator() + START_PROCESSING(SSnanites, src) + + if(cloud_id && cloud_active) + cloud_sync() + +/datum/component/nanites/RegisterWithParent() + RegisterSignal(parent, COMSIG_HAS_NANITES, .proc/confirm_nanites) + RegisterSignal(parent, COMSIG_NANITE_IS_STEALTHY, .proc/check_stealth) + RegisterSignal(parent, COMSIG_NANITE_DELETE, .proc/delete_nanites) + RegisterSignal(parent, COMSIG_NANITE_UI_DATA, .proc/nanite_ui_data) + RegisterSignal(parent, COMSIG_NANITE_GET_PROGRAMS, .proc/get_programs) + RegisterSignal(parent, COMSIG_NANITE_SET_VOLUME, .proc/set_volume) + RegisterSignal(parent, COMSIG_NANITE_ADJUST_VOLUME, .proc/adjust_nanites) + RegisterSignal(parent, COMSIG_NANITE_SET_MAX_VOLUME, .proc/set_max_volume) + RegisterSignal(parent, COMSIG_NANITE_SET_CLOUD, .proc/set_cloud) + RegisterSignal(parent, COMSIG_NANITE_SET_CLOUD_SYNC, .proc/set_cloud_sync) + RegisterSignal(parent, COMSIG_NANITE_SET_SAFETY, .proc/set_safety) + RegisterSignal(parent, COMSIG_NANITE_SET_REGEN, .proc/set_regen) + RegisterSignal(parent, COMSIG_NANITE_ADD_PROGRAM, .proc/add_program) + RegisterSignal(parent, COMSIG_NANITE_SCAN, .proc/nanite_scan) + RegisterSignal(parent, COMSIG_NANITE_SYNC, .proc/sync) + + if(isliving(parent)) + RegisterSignal(parent, COMSIG_ATOM_EMP_ACT, .proc/on_emp) + RegisterSignal(parent, COMSIG_MOB_DEATH, .proc/on_death) + RegisterSignal(parent, COMSIG_MOB_ALLOWED, .proc/check_access) + RegisterSignal(parent, COMSIG_LIVING_ELECTROCUTE_ACT, .proc/on_shock) + RegisterSignal(parent, COMSIG_LIVING_MINOR_SHOCK, .proc/on_minor_shock) + RegisterSignal(parent, COMSIG_SPECIES_GAIN, .proc/check_viable_biotype) + RegisterSignal(parent, COMSIG_NANITE_SIGNAL, .proc/receive_signal) + RegisterSignal(parent, COMSIG_NANITE_COMM_SIGNAL, .proc/receive_comm_signal) + +/datum/component/nanites/UnregisterFromParent() + UnregisterSignal(parent, list(COMSIG_HAS_NANITES, + COMSIG_NANITE_IS_STEALTHY, + COMSIG_NANITE_DELETE, + COMSIG_NANITE_UI_DATA, + COMSIG_NANITE_GET_PROGRAMS, + COMSIG_NANITE_SET_VOLUME, + COMSIG_NANITE_ADJUST_VOLUME, + COMSIG_NANITE_SET_MAX_VOLUME, + COMSIG_NANITE_SET_CLOUD, + COMSIG_NANITE_SET_CLOUD_SYNC, + COMSIG_NANITE_SET_SAFETY, + COMSIG_NANITE_SET_REGEN, + COMSIG_NANITE_ADD_PROGRAM, + COMSIG_NANITE_SCAN, + COMSIG_NANITE_SYNC, + COMSIG_ATOM_EMP_ACT, + COMSIG_MOB_DEATH, + COMSIG_MOB_ALLOWED, + COMSIG_LIVING_ELECTROCUTE_ACT, + COMSIG_LIVING_MINOR_SHOCK, + COMSIG_MOVABLE_HEAR, + COMSIG_SPECIES_GAIN, + COMSIG_NANITE_SIGNAL, + COMSIG_NANITE_COMM_SIGNAL)) + +/datum/component/nanites/Destroy() + STOP_PROCESSING(SSnanites, src) + QDEL_LIST(programs) + if(host_mob) + set_nanite_bar(TRUE) + host_mob.hud_set_nanite_indicator() + host_mob = null + return ..() + +/datum/component/nanites/InheritComponent(datum/component/nanites/new_nanites, i_am_original, amount, cloud) + if(new_nanites) + adjust_nanites(null, new_nanites.nanite_volume) + else + adjust_nanites(null, amount) //just add to the nanite volume + +/datum/component/nanites/process() + if(!IS_IN_STASIS(host_mob)) + adjust_nanites(null, regen_rate) + add_research() + for(var/X in programs) + var/datum/nanite_program/NP = X + NP.on_process() + if(cloud_id && cloud_active && world.time > next_sync) + cloud_sync() + next_sync = world.time + NANITE_SYNC_DELAY + set_nanite_bar() + + +/datum/component/nanites/proc/delete_nanites() + qdel(src) + +//Syncs the nanite component to another, making it so programs are the same with the same programming (except activation status) +/datum/component/nanites/proc/sync(datum/signal_source, datum/component/nanites/source, full_overwrite = TRUE, copy_activation = FALSE) + var/list/programs_to_remove = programs.Copy() + var/list/programs_to_add = source.programs.Copy() + for(var/X in programs) + var/datum/nanite_program/NP = X + for(var/Y in programs_to_add) + var/datum/nanite_program/SNP = Y + if(NP.type == SNP.type) + programs_to_remove -= NP + programs_to_add -= SNP + SNP.copy_programming(NP, copy_activation) + break + if(full_overwrite) + for(var/X in programs_to_remove) + qdel(X) + for(var/X in programs_to_add) + var/datum/nanite_program/SNP = X + add_program(null, SNP.copy()) + +/datum/component/nanites/proc/cloud_sync() + if(cloud_id) + var/datum/nanite_cloud_backup/backup = SSnanites.get_cloud_backup(cloud_id) + if(backup) + var/datum/component/nanites/cloud_copy = backup.nanites + if(cloud_copy) + sync(null, cloud_copy) + return + //Without cloud syncing nanites can accumulate errors and/or defects + if(prob(8) && programs.len) + var/datum/nanite_program/NP = pick(programs) + NP.software_error() + +/datum/component/nanites/proc/add_program(datum/source, datum/nanite_program/new_program, datum/nanite_program/source_program) + for(var/X in programs) + var/datum/nanite_program/NP = X + if(NP.unique && NP.type == new_program.type) + qdel(NP) + if(programs.len >= max_programs) + return COMPONENT_PROGRAM_NOT_INSTALLED + if(source_program) + source_program.copy_programming(new_program) + programs += new_program + new_program.on_add(src) + return COMPONENT_PROGRAM_INSTALLED + +/datum/component/nanites/proc/consume_nanites(amount, force = FALSE) + if(!force && safety_threshold && (nanite_volume - amount < safety_threshold)) + return FALSE + adjust_nanites(null, -amount) + return (nanite_volume > 0) + +/datum/component/nanites/proc/adjust_nanites(datum/source, amount) + nanite_volume = clamp(nanite_volume + amount, 0, max_nanites) + if(nanite_volume <= 0) //oops we ran out + qdel(src) + +/datum/component/nanites/proc/set_nanite_bar(remove = FALSE) + var/image/holder = host_mob.hud_list[DIAG_NANITE_FULL_HUD] + var/icon/I = icon(host_mob.icon, host_mob.icon_state, host_mob.dir) + holder.pixel_y = I.Height() - world.icon_size + holder.icon_state = null + if(remove || stealth) + return //bye icon + var/nanite_percent = (nanite_volume / max_nanites) * 100 + nanite_percent = clamp(CEILING(nanite_percent, 10), 10, 100) + holder.icon_state = "nanites[nanite_percent]" + +/datum/component/nanites/proc/on_emp(datum/source, severity) + nanite_volume *= (rand(60, 90) * 0.01) //Lose 10-40% of nanites + adjust_nanites(null, -(rand(5, 50))) //Lose 5-50 flat nanite volume + if(prob(40/severity)) + cloud_id = 0 + for(var/X in programs) + var/datum/nanite_program/NP = X + NP.on_emp(severity) + + +/datum/component/nanites/proc/on_shock(datum/source, shock_damage, siemens_coeff = 1, flags = NONE) + if(flags & SHOCK_ILLUSION || shock_damage < 1) + return + + if(!HAS_TRAIT_NOT_FROM(host_mob, TRAIT_SHOCKIMMUNE, "nanites"))//Another shock protection must protect nanites too, but nanites protect only host + nanite_volume *= (rand(45, 80) * 0.01) //Lose 20-55% of nanites + adjust_nanites(null, -(rand(5, 50))) //Lose 5-50 flat nanite volume + for(var/X in programs) + var/datum/nanite_program/NP = X + NP.on_shock(shock_damage) + +/datum/component/nanites/proc/on_minor_shock(datum/source) + adjust_nanites(null, -(rand(5, 15))) //Lose 5-15 flat nanite volume + for(var/X in programs) + var/datum/nanite_program/NP = X + NP.on_minor_shock() + +/datum/component/nanites/proc/check_stealth(datum/source) + return stealth + +/datum/component/nanites/proc/on_death(datum/source, gibbed) + for(var/X in programs) + var/datum/nanite_program/NP = X + NP.on_death(gibbed) + +/datum/component/nanites/proc/receive_signal(datum/source, code, source = "an unidentified source") + for(var/X in programs) + var/datum/nanite_program/NP = X + NP.receive_signal(code, source) + +/datum/component/nanites/proc/receive_comm_signal(datum/source, comm_code, comm_message, comm_source = "an unidentified source") + for(var/X in programs) + if(istype(X, /datum/nanite_program/comm)) + var/datum/nanite_program/comm/NP = X + NP.receive_comm_signal(comm_code, comm_message, comm_source) + +/datum/component/nanites/proc/check_viable_biotype() + if(!(host_mob.mob_biotypes & (MOB_ORGANIC|MOB_UNDEAD))) + qdel(src) //bodytype no longer sustains nanites + +/datum/component/nanites/proc/check_access(datum/source, obj/O) + for(var/datum/nanite_program/access/access_program in programs) + if(access_program.activated) + return O.check_access_list(access_program.access) + else + return FALSE + return FALSE + +/datum/component/nanites/proc/set_volume(datum/source, amount) + nanite_volume = clamp(amount, 0, max_nanites) + +/datum/component/nanites/proc/set_max_volume(datum/source, amount) + max_nanites = max(1, max_nanites) + +/datum/component/nanites/proc/set_cloud(datum/source, amount) + cloud_id = clamp(amount, 0, 100) + +/datum/component/nanites/proc/set_cloud_sync(datum/source, method) + switch(method) + if(NANITE_CLOUD_TOGGLE) + cloud_active = !cloud_active + if(NANITE_CLOUD_DISABLE) + cloud_active = FALSE + if(NANITE_CLOUD_ENABLE) + cloud_active = TRUE + +/datum/component/nanites/proc/set_safety(datum/source, amount) + safety_threshold = clamp(amount, 0, max_nanites) + +/datum/component/nanites/proc/set_regen(datum/source, amount) + regen_rate = amount + +/datum/component/nanites/proc/confirm_nanites() + return TRUE //yup i exist + +/datum/component/nanites/proc/get_data(list/nanite_data) + nanite_data["nanite_volume"] = nanite_volume + nanite_data["max_nanites"] = max_nanites + nanite_data["cloud_id"] = cloud_id + nanite_data["regen_rate"] = regen_rate + nanite_data["safety_threshold"] = safety_threshold + nanite_data["stealth"] = stealth + +/datum/component/nanites/proc/get_programs(datum/source, list/nanite_programs) + nanite_programs |= programs + +/datum/component/nanites/proc/add_research() + var/research_value = NANITE_BASE_RESEARCH + if(!ishuman(host_mob)) + if(!iscarbon(host_mob)) + research_value *= 0.4 + else + research_value *= 0.8 + if(!host_mob.client) + research_value *= 0.5 + if(host_mob.stat == DEAD) + research_value *= 0.75 + SSresearch.science_tech.add_point_list(list(TECHWEB_POINT_TYPE_NANITES = research_value)) + +/datum/component/nanites/proc/nanite_scan(datum/source, mob/user, full_scan) + if(!full_scan) + if(!stealth) + to_chat(user, "Nanites Detected") + to_chat(user, "Saturation: [nanite_volume]/[max_nanites]") + return TRUE + else + to_chat(user, "NANITES DETECTED") + to_chat(user, "================") + to_chat(user, "Saturation: [nanite_volume]/[max_nanites]") + to_chat(user, "Safety Threshold: [safety_threshold]") + to_chat(user, "Cloud ID: [cloud_id ? cloud_id : "None"]") + to_chat(user, "Cloud Sync: [cloud_active ? "Active" : "Disabled"]") + to_chat(user, "================") + to_chat(user, "Program List:") + if(!diagnostics) + to_chat(user, "Diagnostics Disabled") + else + for(var/X in programs) + var/datum/nanite_program/NP = X + to_chat(user, "[NP.name] | [NP.activated ? "Active" : "Inactive"]") + return TRUE + +/datum/component/nanites/proc/nanite_ui_data(datum/source, list/data, scan_level) + data["has_nanites"] = TRUE + data["nanite_volume"] = nanite_volume + data["regen_rate"] = regen_rate + data["safety_threshold"] = safety_threshold + data["cloud_id"] = cloud_id + data["cloud_active"] = cloud_active + var/list/mob_programs = list() + var/id = 1 + for(var/X in programs) + var/datum/nanite_program/P = X + var/list/mob_program = list() + mob_program["name"] = P.name + mob_program["desc"] = P.desc + mob_program["id"] = id + + if(scan_level >= 2) + mob_program["activated"] = P.activated + mob_program["use_rate"] = P.use_rate + mob_program["can_trigger"] = P.can_trigger + mob_program["trigger_cost"] = P.trigger_cost + mob_program["trigger_cooldown"] = P.trigger_cooldown / 10 + + if(scan_level >= 3) + mob_program["timer_restart"] = P.timer_restart / 10 + mob_program["timer_shutdown"] = P.timer_shutdown / 10 + mob_program["timer_trigger"] = P.timer_trigger / 10 + mob_program["timer_trigger_delay"] = P.timer_trigger_delay / 10 + var/list/extra_settings = P.get_extra_settings_frontend() + mob_program["extra_settings"] = extra_settings + if(LAZYLEN(extra_settings)) + mob_program["has_extra_settings"] = TRUE + else + mob_program["has_extra_settings"] = FALSE + + if(scan_level >= 4) + mob_program["activation_code"] = P.activation_code + mob_program["deactivation_code"] = P.deactivation_code + mob_program["kill_code"] = P.kill_code + mob_program["trigger_code"] = P.trigger_code + var/list/rules = list() + var/rule_id = 1 + for(var/Z in P.rules) + var/datum/nanite_rule/nanite_rule = Z + var/list/rule = list() + rule["display"] = nanite_rule.display() + rule["program_id"] = id + rule["id"] = rule_id + rules += list(rule) + rule_id++ + mob_program["rules"] = rules + if(LAZYLEN(rules)) + mob_program["has_rules"] = TRUE + id++ + mob_programs += list(mob_program) + data["mob_programs"] = mob_programs diff --git a/code/datums/components/ntnet_interface.dm b/code/datums/components/ntnet_interface.dm index 7623d5ef8669..3a8fb559bf48 100644 --- a/code/datums/components/ntnet_interface.dm +++ b/code/datums/components/ntnet_interface.dm @@ -1,66 +1,66 @@ -//Thing meant for allowing datums and objects to access a NTnet network datum. -/datum/proc/ntnet_receive(datum/netdata/data) - return - -/datum/proc/ntnet_receive_broadcast(datum/netdata/data) - return - -/datum/proc/ntnet_send(datum/netdata/data, netid) - var/datum/component/ntnet_interface/NIC = GetComponent(/datum/component/ntnet_interface) - if(!NIC) - return FALSE - return NIC.__network_send(data, netid) - -/datum/component/ntnet_interface - var/hardware_id //text. this is the true ID. do not change this. stuff like ID forgery can be done manually. - var/network_name = "" //text - var/list/networks_connected_by_id = list() //id = datum/ntnet - var/differentiate_broadcast = TRUE //If false, broadcasts go to ntnet_receive. NOT RECOMMENDED. - -/datum/component/ntnet_interface/Initialize(force_name = "NTNet Device", autoconnect_station_network = TRUE) //Don't force ID unless you know what you're doing! - hardware_id = "[SSnetworks.get_next_HID()]" - network_name = force_name - if(!SSnetworks.register_interface(src)) - . = COMPONENT_INCOMPATIBLE - CRASH("Unable to register NTNet interface. Interface deleted.") - if(autoconnect_station_network) - register_connection(SSnetworks.station_network) - -/datum/component/ntnet_interface/Destroy() - unregister_all_connections() - SSnetworks.unregister_interface(src) - return ..() - -/datum/component/ntnet_interface/proc/__network_receive(datum/netdata/data) //Do not directly proccall! - SEND_SIGNAL(parent, COMSIG_COMPONENT_NTNET_RECEIVE, data) - if(differentiate_broadcast && data.broadcast) - parent.ntnet_receive_broadcast(data) - else - parent.ntnet_receive(data) - -/datum/component/ntnet_interface/proc/__network_send(datum/netdata/data, netid) //Do not directly proccall! - - if(netid) - if(networks_connected_by_id[netid]) - var/datum/ntnet/net = networks_connected_by_id[netid] - return net.process_data_transmit(src, data) - return FALSE - for(var/i in networks_connected_by_id) - var/datum/ntnet/net = networks_connected_by_id[i] - net.process_data_transmit(src, data) - return TRUE - -/datum/component/ntnet_interface/proc/register_connection(datum/ntnet/net) - if(net.interface_connect(src)) - networks_connected_by_id[net.network_id] = net - return TRUE - -/datum/component/ntnet_interface/proc/unregister_all_connections() - for(var/i in networks_connected_by_id) - unregister_connection(networks_connected_by_id[i]) - return TRUE - -/datum/component/ntnet_interface/proc/unregister_connection(datum/ntnet/net) - net.interface_disconnect(src) - networks_connected_by_id -= net.network_id - return TRUE +//Thing meant for allowing datums and objects to access a NTnet network datum. +/datum/proc/ntnet_receive(datum/netdata/data) + return + +/datum/proc/ntnet_receive_broadcast(datum/netdata/data) + return + +/datum/proc/ntnet_send(datum/netdata/data, netid) + var/datum/component/ntnet_interface/NIC = GetComponent(/datum/component/ntnet_interface) + if(!NIC) + return FALSE + return NIC.__network_send(data, netid) + +/datum/component/ntnet_interface + var/hardware_id //text. this is the true ID. do not change this. stuff like ID forgery can be done manually. + var/network_name = "" //text + var/list/networks_connected_by_id = list() //id = datum/ntnet + var/differentiate_broadcast = TRUE //If false, broadcasts go to ntnet_receive. NOT RECOMMENDED. + +/datum/component/ntnet_interface/Initialize(force_name = "NTNet Device", autoconnect_station_network = TRUE) //Don't force ID unless you know what you're doing! + hardware_id = "[SSnetworks.get_next_HID()]" + network_name = force_name + if(!SSnetworks.register_interface(src)) + . = COMPONENT_INCOMPATIBLE + CRASH("Unable to register NTNet interface. Interface deleted.") + if(autoconnect_station_network) + register_connection(SSnetworks.station_network) + +/datum/component/ntnet_interface/Destroy() + unregister_all_connections() + SSnetworks.unregister_interface(src) + return ..() + +/datum/component/ntnet_interface/proc/__network_receive(datum/netdata/data) //Do not directly proccall! + SEND_SIGNAL(parent, COMSIG_COMPONENT_NTNET_RECEIVE, data) + if(differentiate_broadcast && data.broadcast) + parent.ntnet_receive_broadcast(data) + else + parent.ntnet_receive(data) + +/datum/component/ntnet_interface/proc/__network_send(datum/netdata/data, netid) //Do not directly proccall! + + if(netid) + if(networks_connected_by_id[netid]) + var/datum/ntnet/net = networks_connected_by_id[netid] + return net.process_data_transmit(src, data) + return FALSE + for(var/i in networks_connected_by_id) + var/datum/ntnet/net = networks_connected_by_id[i] + net.process_data_transmit(src, data) + return TRUE + +/datum/component/ntnet_interface/proc/register_connection(datum/ntnet/net) + if(net.interface_connect(src)) + networks_connected_by_id[net.network_id] = net + return TRUE + +/datum/component/ntnet_interface/proc/unregister_all_connections() + for(var/i in networks_connected_by_id) + unregister_connection(networks_connected_by_id[i]) + return TRUE + +/datum/component/ntnet_interface/proc/unregister_connection(datum/ntnet/net) + net.interface_disconnect(src) + networks_connected_by_id -= net.network_id + return TRUE diff --git a/code/datums/components/riding.dm b/code/datums/components/riding.dm index 96c32112f599..f174055205e5 100644 --- a/code/datums/components/riding.dm +++ b/code/datums/components/riding.dm @@ -1,388 +1,388 @@ -/datum/component/riding - var/last_vehicle_move = 0 //used for move delays - var/last_move_diagonal = FALSE - var/vehicle_move_delay = 2 //tick delay between movements, lower = faster, higher = slower - var/keytype - - var/slowed = FALSE - var/slowvalue = 1 - - var/list/riding_offsets = list() //position_of_user = list(dir = list(px, py)), or RIDING_OFFSET_ALL for a generic one. - var/list/directional_vehicle_layers = list() //["[DIRECTION]"] = layer. Don't set it for a direction for default, set a direction to null for no change. - var/list/directional_vehicle_offsets = list() //same as above but instead of layer you have a list(px, py) - var/list/allowed_turf_typecache - var/list/forbid_turf_typecache //allow typecache for only certain turfs, forbid to allow all but those. allow only certain turfs will take precedence. - var/allow_one_away_from_valid_turf = TRUE //allow moving one tile away from a valid turf but not more. - var/override_allow_spacemove = FALSE - var/drive_verb = "drive" - var/ride_check_rider_incapacitated = FALSE - var/ride_check_rider_restrained = FALSE - var/ride_check_ridden_incapacitated = FALSE - - var/del_on_unbuckle_all = FALSE - - /// If the "vehicle" is a mob, respect MOBILITY_MOVE on said mob. - var/respect_mob_mobility = TRUE - -/datum/component/riding/Initialize() - if(!ismovable(parent)) - return COMPONENT_INCOMPATIBLE - RegisterSignal(parent, COMSIG_MOVABLE_BUCKLE, .proc/vehicle_mob_buckle) - RegisterSignal(parent, COMSIG_MOVABLE_UNBUCKLE, .proc/vehicle_mob_unbuckle) - RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/vehicle_moved) - -/datum/component/riding/proc/vehicle_mob_unbuckle(datum/source, mob/living/M, force = FALSE) - var/atom/movable/AM = parent - restore_position(M) - unequip_buckle_inhands(M) - if(del_on_unbuckle_all && !AM.has_buckled_mobs()) - qdel(src) - -/datum/component/riding/proc/vehicle_mob_buckle(datum/source, mob/living/M, force = FALSE) - handle_vehicle_offsets() - -/datum/component/riding/proc/handle_vehicle_layer() - var/atom/movable/AM = parent - var/static/list/defaults = list(TEXT_NORTH = OBJ_LAYER, TEXT_SOUTH = ABOVE_MOB_LAYER, TEXT_EAST = ABOVE_MOB_LAYER, TEXT_WEST = ABOVE_MOB_LAYER) - . = defaults["[AM.dir]"] - if(directional_vehicle_layers["[AM.dir]"]) - . = directional_vehicle_layers["[AM.dir]"] - if(isnull(.)) //you can set it to null to not change it. - . = AM.layer - AM.layer = . - -/datum/component/riding/proc/set_vehicle_dir_layer(dir, layer) - directional_vehicle_layers["[dir]"] = layer - -/datum/component/riding/proc/vehicle_moved(datum/source) - var/atom/movable/AM = parent - for(var/i in AM.buckled_mobs) - ride_check(i) - handle_vehicle_offsets() - handle_vehicle_layer() - -/datum/component/riding/proc/ride_check(mob/living/M) - var/atom/movable/AM = parent - var/mob/AMM = AM - if((ride_check_rider_restrained && M.restrained(TRUE)) || (ride_check_rider_incapacitated && M.incapacitated(FALSE, TRUE)) || (ride_check_ridden_incapacitated && istype(AMM) && AMM.incapacitated(FALSE, TRUE))) - M.visible_message("[M] falls off of [AM]!", \ - "You fall off of [AM]!") - AM.unbuckle_mob(M) - return TRUE - -/datum/component/riding/proc/force_dismount(mob/living/M, gentle = FALSE) - var/atom/movable/AM = parent - AM.unbuckle_mob(M) - if(isanimal(AM) || iscyborg(AM)) - var/turf/target = get_edge_target_turf(AM, AM.dir) - var/turf/targetm = get_step(get_turf(AM), AM.dir) - M.Move(targetm) - if(gentle) - M.visible_message("[M] is thrown clear of [AM]!", \ - "You're thrown clear of [AM]!") - M.throw_at(target, 8, 3, AM, gentle = TRUE) - else - M.visible_message("[M] is thrown violently from [AM]!", \ - "You're thrown violently from [AM]!") - M.throw_at(target, 14, 5, AM, gentle = FALSE) - M.Knockdown(3 SECONDS) - -/datum/component/riding/proc/handle_vehicle_offsets() - var/atom/movable/AM = parent - var/AM_dir = "[AM.dir]" - var/passindex = 0 - if(AM.has_buckled_mobs()) - for(var/m in AM.buckled_mobs) - passindex++ - var/mob/living/buckled_mob = m - var/list/offsets = get_offsets(passindex) - var/rider_dir = get_rider_dir(passindex) - buckled_mob.setDir(rider_dir) - dir_loop: - for(var/offsetdir in offsets) - if(offsetdir == AM_dir) - var/list/diroffsets = offsets[offsetdir] - buckled_mob.pixel_x = diroffsets[1] - if(diroffsets.len >= 2) - buckled_mob.pixel_y = diroffsets[2] - if(diroffsets.len == 3) - buckled_mob.layer = diroffsets[3] - break dir_loop - var/list/static/default_vehicle_pixel_offsets = list(TEXT_NORTH = list(0, 0), TEXT_SOUTH = list(0, 0), TEXT_EAST = list(0, 0), TEXT_WEST = list(0, 0)) - var/px = default_vehicle_pixel_offsets[AM_dir] - var/py = default_vehicle_pixel_offsets[AM_dir] - if(directional_vehicle_offsets[AM_dir]) - if(isnull(directional_vehicle_offsets[AM_dir])) - px = AM.pixel_x - py = AM.pixel_y - else - px = directional_vehicle_offsets[AM_dir][1] - py = directional_vehicle_offsets[AM_dir][2] - AM.pixel_x = px - AM.pixel_y = py - -/datum/component/riding/proc/set_vehicle_dir_offsets(dir, x, y) - directional_vehicle_offsets["[dir]"] = list(x, y) - -//Override this to set your vehicle's various pixel offsets -/datum/component/riding/proc/get_offsets(pass_index) // list(dir = x, y, layer) - . = list(TEXT_NORTH = list(0, 0), TEXT_SOUTH = list(0, 0), TEXT_EAST = list(0, 0), TEXT_WEST = list(0, 0)) - if(riding_offsets["[pass_index]"]) - . = riding_offsets["[pass_index]"] - else if(riding_offsets["[RIDING_OFFSET_ALL]"]) - . = riding_offsets["[RIDING_OFFSET_ALL]"] - -/datum/component/riding/proc/set_riding_offsets(index, list/offsets) - if(!islist(offsets)) - return FALSE - riding_offsets["[index]"] = offsets - -//Override this to set the passengers/riders dir based on which passenger they are. -//ie: rider facing the vehicle's dir, but passenger 2 facing backwards, etc. -/datum/component/riding/proc/get_rider_dir(pass_index) - var/atom/movable/AM = parent - return AM.dir - -//KEYS -/datum/component/riding/proc/keycheck(mob/user) - return !keytype || user.is_holding_item_of_type(keytype) - -//BUCKLE HOOKS -/datum/component/riding/proc/restore_position(mob/living/buckled_mob) - if(buckled_mob) - buckled_mob.pixel_x = 0 - buckled_mob.pixel_y = 0 - if(buckled_mob.client) - buckled_mob.client.change_view(CONFIG_GET(string/default_view)) - -//MOVEMENT -/datum/component/riding/proc/turf_check(turf/next, turf/current) - if(allowed_turf_typecache && !allowed_turf_typecache[next.type]) - return (allow_one_away_from_valid_turf && allowed_turf_typecache[current.type]) - else if(forbid_turf_typecache && forbid_turf_typecache[next.type]) - return (allow_one_away_from_valid_turf && !forbid_turf_typecache[current.type]) - return TRUE - -/datum/component/riding/proc/handle_ride(mob/user, direction) - var/atom/movable/AM = parent - if(user.incapacitated()) - Unbuckle(user) - return - - if(world.time < last_vehicle_move + ((last_move_diagonal? 2 : 1) * vehicle_move_delay)) - return - last_vehicle_move = world.time - - if(keycheck(user)) - var/turf/next = get_step(AM, direction) - var/turf/current = get_turf(AM) - if(!istype(next) || !istype(current)) - return //not happening. - if(!turf_check(next, current)) - to_chat(user, "Your \the [AM] can not go onto [next]!") - return - if(!Process_Spacemove(direction) || !isturf(AM.loc)) - return - if(isliving(AM) && respect_mob_mobility) - var/mob/living/M = AM - if(!(M.mobility_flags & MOBILITY_MOVE)) - return - step(AM, direction) - - if((direction & (direction - 1)) && (AM.loc == next)) //moved diagonally - last_move_diagonal = TRUE - else - last_move_diagonal = FALSE - - handle_vehicle_layer() - handle_vehicle_offsets() - else - to_chat(user, "You'll need a special item in one of your hands to [drive_verb] [AM].") - -/datum/component/riding/proc/Unbuckle(atom/movable/M) - addtimer(CALLBACK(parent, /atom/movable/.proc/unbuckle_mob, M), 0, TIMER_UNIQUE) - -/datum/component/riding/proc/Process_Spacemove(direction) - var/atom/movable/AM = parent - return override_allow_spacemove || AM.has_gravity() - -/datum/component/riding/proc/account_limbs(mob/living/M) - if(M.get_num_legs() < 2 && !slowed) - vehicle_move_delay = vehicle_move_delay + slowvalue - slowed = TRUE - else if(slowed) - vehicle_move_delay = vehicle_move_delay - slowvalue - slowed = FALSE - -///////Yes, I said humans. No, this won't end well...////////// -/datum/component/riding/human - del_on_unbuckle_all = TRUE - -/datum/component/riding/human/Initialize() - . = ..() - RegisterSignal(parent, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, .proc/on_host_unarmed_melee) - -/datum/component/riding/human/vehicle_mob_unbuckle(datum/source, mob/living/M, force = FALSE) - unequip_buckle_inhands(parent) - var/mob/living/carbon/human/H = parent - H.remove_movespeed_modifier(/datum/movespeed_modifier/human_carry) - . = ..() - -/datum/component/riding/human/vehicle_mob_buckle(datum/source, mob/living/M, force = FALSE) - . = ..() - var/mob/living/carbon/human/H = parent - H.add_movespeed_modifier(/datum/movespeed_modifier/human_carry) - -/datum/component/riding/human/proc/on_host_unarmed_melee(atom/target) - var/mob/living/carbon/human/H = parent - if(H.a_intent == INTENT_DISARM && (target in H.buckled_mobs)) - force_dismount(target) - -/datum/component/riding/human/handle_vehicle_layer() - var/atom/movable/AM = parent - if(AM.buckled_mobs && AM.buckled_mobs.len) - for(var/mob/M in AM.buckled_mobs) //ensure proper layering of piggyback and carry, sometimes weird offsets get applied - M.layer = MOB_LAYER - if(!AM.buckle_lying) - if(AM.dir == SOUTH) - AM.layer = ABOVE_MOB_LAYER - else - AM.layer = OBJ_LAYER - else - if(AM.dir == NORTH) - AM.layer = OBJ_LAYER - else - AM.layer = ABOVE_MOB_LAYER - else - AM.layer = MOB_LAYER - -/datum/component/riding/human/get_offsets(pass_index) - var/mob/living/carbon/human/H = parent - if(H.buckle_lying) - return list(TEXT_NORTH = list(0, 6), TEXT_SOUTH = list(0, 6), TEXT_EAST = list(0, 6), TEXT_WEST = list(0, 6)) - else - return list(TEXT_NORTH = list(0, 6), TEXT_SOUTH = list(0, 6), TEXT_EAST = list(-6, 4), TEXT_WEST = list( 6, 4)) - - -/datum/component/riding/human/force_dismount(mob/living/user) - var/atom/movable/AM = parent - AM.unbuckle_mob(user) - user.Paralyze(60) - user.visible_message("[AM] pushes [user] off of [AM.p_them()]!", \ - "[AM] pushes you off of [AM.p_them()]!") - -/datum/component/riding/cyborg - del_on_unbuckle_all = TRUE - -/datum/component/riding/cyborg/ride_check(mob/user) - var/atom/movable/AM = parent - if(user.incapacitated()) - var/kick = TRUE - if(iscyborg(AM)) - var/mob/living/silicon/robot/R = AM - if(R.module && R.module.ride_allow_incapacitated) - kick = FALSE - if(kick) - to_chat(user, "You fall off of [AM]!") - Unbuckle(user) - return - if(iscarbon(user)) - var/mob/living/carbon/carbonuser = user - if(!carbonuser.get_num_arms()) - Unbuckle(user) - to_chat(user, "You can't grab onto [AM] with no hands!") - return - -/datum/component/riding/cyborg/handle_vehicle_layer() - var/atom/movable/AM = parent - if(AM.buckled_mobs && AM.buckled_mobs.len) - if(AM.dir == SOUTH) - AM.layer = ABOVE_MOB_LAYER - else - AM.layer = OBJ_LAYER - else - AM.layer = MOB_LAYER - -/datum/component/riding/cyborg/get_offsets(pass_index) // list(dir = x, y, layer) - return list(TEXT_NORTH = list(0, 4), TEXT_SOUTH = list(0, 4), TEXT_EAST = list(-6, 3), TEXT_WEST = list( 6, 3)) - -/datum/component/riding/cyborg/handle_vehicle_offsets() - var/atom/movable/AM = parent - if(AM.has_buckled_mobs()) - for(var/mob/living/M in AM.buckled_mobs) - M.setDir(AM.dir) - if(iscyborg(AM)) - var/mob/living/silicon/robot/R = AM - if(istype(R.module)) - M.pixel_x = R.module.ride_offset_x[dir2text(AM.dir)] - M.pixel_y = R.module.ride_offset_y[dir2text(AM.dir)] - else - ..() - -/datum/component/riding/proc/equip_buckle_inhands(mob/living/carbon/human/user, amount_required = 1, riding_target_override = null) - var/atom/movable/AM = parent - var/amount_equipped = 0 - for(var/amount_needed = amount_required, amount_needed > 0, amount_needed--) - var/obj/item/riding_offhand/inhand = new /obj/item/riding_offhand(user) - if(!riding_target_override) - inhand.rider = user - else - inhand.rider = riding_target_override - inhand.parent = AM - if(user.put_in_hands(inhand, TRUE)) - amount_equipped++ - else - break - if(amount_equipped >= amount_required) - return TRUE - else - unequip_buckle_inhands(user) - return FALSE - -/datum/component/riding/proc/unequip_buckle_inhands(mob/living/carbon/user) - var/atom/movable/AM = parent - for(var/obj/item/riding_offhand/O in user.contents) - if(O.parent != AM) - CRASH("RIDING OFFHAND ON WRONG MOB") - if(O.selfdeleting) - continue - else - qdel(O) - return TRUE - -/obj/item/riding_offhand - name = "offhand" - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "offhand" - w_class = WEIGHT_CLASS_HUGE - item_flags = ABSTRACT | DROPDEL | NOBLUDGEON - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - var/mob/living/carbon/rider - var/mob/living/parent - var/selfdeleting = FALSE - -/obj/item/riding_offhand/dropped() - selfdeleting = TRUE - . = ..() - -/obj/item/riding_offhand/equipped() - if(loc != rider && loc != parent) - selfdeleting = TRUE - qdel(src) - . = ..() - -/obj/item/riding_offhand/Destroy() - var/atom/movable/AM = parent - if(selfdeleting) - if(rider in AM.buckled_mobs) - AM.unbuckle_mob(rider) - . = ..() - -/obj/item/riding_offhand/on_thrown(mob/living/carbon/user, atom/target) - if(rider == user) - return //Piggyback user. - user.unbuckle_mob(rider) - if(HAS_TRAIT(user, TRAIT_PACIFISM)) - to_chat(user, "You gently let go of [rider].") - return - return rider +/datum/component/riding + var/last_vehicle_move = 0 //used for move delays + var/last_move_diagonal = FALSE + var/vehicle_move_delay = 2 //tick delay between movements, lower = faster, higher = slower + var/keytype + + var/slowed = FALSE + var/slowvalue = 1 + + var/list/riding_offsets = list() //position_of_user = list(dir = list(px, py)), or RIDING_OFFSET_ALL for a generic one. + var/list/directional_vehicle_layers = list() //["[DIRECTION]"] = layer. Don't set it for a direction for default, set a direction to null for no change. + var/list/directional_vehicle_offsets = list() //same as above but instead of layer you have a list(px, py) + var/list/allowed_turf_typecache + var/list/forbid_turf_typecache //allow typecache for only certain turfs, forbid to allow all but those. allow only certain turfs will take precedence. + var/allow_one_away_from_valid_turf = TRUE //allow moving one tile away from a valid turf but not more. + var/override_allow_spacemove = FALSE + var/drive_verb = "drive" + var/ride_check_rider_incapacitated = FALSE + var/ride_check_rider_restrained = FALSE + var/ride_check_ridden_incapacitated = FALSE + + var/del_on_unbuckle_all = FALSE + + /// If the "vehicle" is a mob, respect MOBILITY_MOVE on said mob. + var/respect_mob_mobility = TRUE + +/datum/component/riding/Initialize() + if(!ismovable(parent)) + return COMPONENT_INCOMPATIBLE + RegisterSignal(parent, COMSIG_MOVABLE_BUCKLE, .proc/vehicle_mob_buckle) + RegisterSignal(parent, COMSIG_MOVABLE_UNBUCKLE, .proc/vehicle_mob_unbuckle) + RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/vehicle_moved) + +/datum/component/riding/proc/vehicle_mob_unbuckle(datum/source, mob/living/M, force = FALSE) + var/atom/movable/AM = parent + restore_position(M) + unequip_buckle_inhands(M) + if(del_on_unbuckle_all && !AM.has_buckled_mobs()) + qdel(src) + +/datum/component/riding/proc/vehicle_mob_buckle(datum/source, mob/living/M, force = FALSE) + handle_vehicle_offsets() + +/datum/component/riding/proc/handle_vehicle_layer() + var/atom/movable/AM = parent + var/static/list/defaults = list(TEXT_NORTH = OBJ_LAYER, TEXT_SOUTH = ABOVE_MOB_LAYER, TEXT_EAST = ABOVE_MOB_LAYER, TEXT_WEST = ABOVE_MOB_LAYER) + . = defaults["[AM.dir]"] + if(directional_vehicle_layers["[AM.dir]"]) + . = directional_vehicle_layers["[AM.dir]"] + if(isnull(.)) //you can set it to null to not change it. + . = AM.layer + AM.layer = . + +/datum/component/riding/proc/set_vehicle_dir_layer(dir, layer) + directional_vehicle_layers["[dir]"] = layer + +/datum/component/riding/proc/vehicle_moved(datum/source) + var/atom/movable/AM = parent + for(var/i in AM.buckled_mobs) + ride_check(i) + handle_vehicle_offsets() + handle_vehicle_layer() + +/datum/component/riding/proc/ride_check(mob/living/M) + var/atom/movable/AM = parent + var/mob/AMM = AM + if((ride_check_rider_restrained && M.restrained(TRUE)) || (ride_check_rider_incapacitated && M.incapacitated(FALSE, TRUE)) || (ride_check_ridden_incapacitated && istype(AMM) && AMM.incapacitated(FALSE, TRUE))) + M.visible_message("[M] falls off of [AM]!", \ + "You fall off of [AM]!") + AM.unbuckle_mob(M) + return TRUE + +/datum/component/riding/proc/force_dismount(mob/living/M, gentle = FALSE) + var/atom/movable/AM = parent + AM.unbuckle_mob(M) + if(isanimal(AM) || iscyborg(AM)) + var/turf/target = get_edge_target_turf(AM, AM.dir) + var/turf/targetm = get_step(get_turf(AM), AM.dir) + M.Move(targetm) + if(gentle) + M.visible_message("[M] is thrown clear of [AM]!", \ + "You're thrown clear of [AM]!") + M.throw_at(target, 8, 3, AM, gentle = TRUE) + else + M.visible_message("[M] is thrown violently from [AM]!", \ + "You're thrown violently from [AM]!") + M.throw_at(target, 14, 5, AM, gentle = FALSE) + M.Knockdown(3 SECONDS) + +/datum/component/riding/proc/handle_vehicle_offsets() + var/atom/movable/AM = parent + var/AM_dir = "[AM.dir]" + var/passindex = 0 + if(AM.has_buckled_mobs()) + for(var/m in AM.buckled_mobs) + passindex++ + var/mob/living/buckled_mob = m + var/list/offsets = get_offsets(passindex) + var/rider_dir = get_rider_dir(passindex) + buckled_mob.setDir(rider_dir) + dir_loop: + for(var/offsetdir in offsets) + if(offsetdir == AM_dir) + var/list/diroffsets = offsets[offsetdir] + buckled_mob.pixel_x = diroffsets[1] + if(diroffsets.len >= 2) + buckled_mob.pixel_y = diroffsets[2] + if(diroffsets.len == 3) + buckled_mob.layer = diroffsets[3] + break dir_loop + var/list/static/default_vehicle_pixel_offsets = list(TEXT_NORTH = list(0, 0), TEXT_SOUTH = list(0, 0), TEXT_EAST = list(0, 0), TEXT_WEST = list(0, 0)) + var/px = default_vehicle_pixel_offsets[AM_dir] + var/py = default_vehicle_pixel_offsets[AM_dir] + if(directional_vehicle_offsets[AM_dir]) + if(isnull(directional_vehicle_offsets[AM_dir])) + px = AM.pixel_x + py = AM.pixel_y + else + px = directional_vehicle_offsets[AM_dir][1] + py = directional_vehicle_offsets[AM_dir][2] + AM.pixel_x = px + AM.pixel_y = py + +/datum/component/riding/proc/set_vehicle_dir_offsets(dir, x, y) + directional_vehicle_offsets["[dir]"] = list(x, y) + +//Override this to set your vehicle's various pixel offsets +/datum/component/riding/proc/get_offsets(pass_index) // list(dir = x, y, layer) + . = list(TEXT_NORTH = list(0, 0), TEXT_SOUTH = list(0, 0), TEXT_EAST = list(0, 0), TEXT_WEST = list(0, 0)) + if(riding_offsets["[pass_index]"]) + . = riding_offsets["[pass_index]"] + else if(riding_offsets["[RIDING_OFFSET_ALL]"]) + . = riding_offsets["[RIDING_OFFSET_ALL]"] + +/datum/component/riding/proc/set_riding_offsets(index, list/offsets) + if(!islist(offsets)) + return FALSE + riding_offsets["[index]"] = offsets + +//Override this to set the passengers/riders dir based on which passenger they are. +//ie: rider facing the vehicle's dir, but passenger 2 facing backwards, etc. +/datum/component/riding/proc/get_rider_dir(pass_index) + var/atom/movable/AM = parent + return AM.dir + +//KEYS +/datum/component/riding/proc/keycheck(mob/user) + return !keytype || user.is_holding_item_of_type(keytype) + +//BUCKLE HOOKS +/datum/component/riding/proc/restore_position(mob/living/buckled_mob) + if(buckled_mob) + buckled_mob.pixel_x = 0 + buckled_mob.pixel_y = 0 + if(buckled_mob.client) + buckled_mob.client.change_view(CONFIG_GET(string/default_view)) + +//MOVEMENT +/datum/component/riding/proc/turf_check(turf/next, turf/current) + if(allowed_turf_typecache && !allowed_turf_typecache[next.type]) + return (allow_one_away_from_valid_turf && allowed_turf_typecache[current.type]) + else if(forbid_turf_typecache && forbid_turf_typecache[next.type]) + return (allow_one_away_from_valid_turf && !forbid_turf_typecache[current.type]) + return TRUE + +/datum/component/riding/proc/handle_ride(mob/user, direction) + var/atom/movable/AM = parent + if(user.incapacitated()) + Unbuckle(user) + return + + if(world.time < last_vehicle_move + ((last_move_diagonal? 2 : 1) * vehicle_move_delay)) + return + last_vehicle_move = world.time + + if(keycheck(user)) + var/turf/next = get_step(AM, direction) + var/turf/current = get_turf(AM) + if(!istype(next) || !istype(current)) + return //not happening. + if(!turf_check(next, current)) + to_chat(user, "Your \the [AM] can not go onto [next]!") + return + if(!Process_Spacemove(direction) || !isturf(AM.loc)) + return + if(isliving(AM) && respect_mob_mobility) + var/mob/living/M = AM + if(!(M.mobility_flags & MOBILITY_MOVE)) + return + step(AM, direction) + + if((direction & (direction - 1)) && (AM.loc == next)) //moved diagonally + last_move_diagonal = TRUE + else + last_move_diagonal = FALSE + + handle_vehicle_layer() + handle_vehicle_offsets() + else + to_chat(user, "You'll need a special item in one of your hands to [drive_verb] [AM].") + +/datum/component/riding/proc/Unbuckle(atom/movable/M) + addtimer(CALLBACK(parent, /atom/movable/.proc/unbuckle_mob, M), 0, TIMER_UNIQUE) + +/datum/component/riding/proc/Process_Spacemove(direction) + var/atom/movable/AM = parent + return override_allow_spacemove || AM.has_gravity() + +/datum/component/riding/proc/account_limbs(mob/living/M) + if(M.get_num_legs() < 2 && !slowed) + vehicle_move_delay = vehicle_move_delay + slowvalue + slowed = TRUE + else if(slowed) + vehicle_move_delay = vehicle_move_delay - slowvalue + slowed = FALSE + +///////Yes, I said humans. No, this won't end well...////////// +/datum/component/riding/human + del_on_unbuckle_all = TRUE + +/datum/component/riding/human/Initialize() + . = ..() + RegisterSignal(parent, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, .proc/on_host_unarmed_melee) + +/datum/component/riding/human/vehicle_mob_unbuckle(datum/source, mob/living/M, force = FALSE) + unequip_buckle_inhands(parent) + var/mob/living/carbon/human/H = parent + H.remove_movespeed_modifier(/datum/movespeed_modifier/human_carry) + . = ..() + +/datum/component/riding/human/vehicle_mob_buckle(datum/source, mob/living/M, force = FALSE) + . = ..() + var/mob/living/carbon/human/H = parent + H.add_movespeed_modifier(/datum/movespeed_modifier/human_carry) + +/datum/component/riding/human/proc/on_host_unarmed_melee(atom/target) + var/mob/living/carbon/human/H = parent + if(H.a_intent == INTENT_DISARM && (target in H.buckled_mobs)) + force_dismount(target) + +/datum/component/riding/human/handle_vehicle_layer() + var/atom/movable/AM = parent + if(AM.buckled_mobs && AM.buckled_mobs.len) + for(var/mob/M in AM.buckled_mobs) //ensure proper layering of piggyback and carry, sometimes weird offsets get applied + M.layer = MOB_LAYER + if(!AM.buckle_lying) + if(AM.dir == SOUTH) + AM.layer = ABOVE_MOB_LAYER + else + AM.layer = OBJ_LAYER + else + if(AM.dir == NORTH) + AM.layer = OBJ_LAYER + else + AM.layer = ABOVE_MOB_LAYER + else + AM.layer = MOB_LAYER + +/datum/component/riding/human/get_offsets(pass_index) + var/mob/living/carbon/human/H = parent + if(H.buckle_lying) + return list(TEXT_NORTH = list(0, 6), TEXT_SOUTH = list(0, 6), TEXT_EAST = list(0, 6), TEXT_WEST = list(0, 6)) + else + return list(TEXT_NORTH = list(0, 6), TEXT_SOUTH = list(0, 6), TEXT_EAST = list(-6, 4), TEXT_WEST = list( 6, 4)) + + +/datum/component/riding/human/force_dismount(mob/living/user) + var/atom/movable/AM = parent + AM.unbuckle_mob(user) + user.Paralyze(60) + user.visible_message("[AM] pushes [user] off of [AM.p_them()]!", \ + "[AM] pushes you off of [AM.p_them()]!") + +/datum/component/riding/cyborg + del_on_unbuckle_all = TRUE + +/datum/component/riding/cyborg/ride_check(mob/user) + var/atom/movable/AM = parent + if(user.incapacitated()) + var/kick = TRUE + if(iscyborg(AM)) + var/mob/living/silicon/robot/R = AM + if(R.module && R.module.ride_allow_incapacitated) + kick = FALSE + if(kick) + to_chat(user, "You fall off of [AM]!") + Unbuckle(user) + return + if(iscarbon(user)) + var/mob/living/carbon/carbonuser = user + if(!carbonuser.get_num_arms()) + Unbuckle(user) + to_chat(user, "You can't grab onto [AM] with no hands!") + return + +/datum/component/riding/cyborg/handle_vehicle_layer() + var/atom/movable/AM = parent + if(AM.buckled_mobs && AM.buckled_mobs.len) + if(AM.dir == SOUTH) + AM.layer = ABOVE_MOB_LAYER + else + AM.layer = OBJ_LAYER + else + AM.layer = MOB_LAYER + +/datum/component/riding/cyborg/get_offsets(pass_index) // list(dir = x, y, layer) + return list(TEXT_NORTH = list(0, 4), TEXT_SOUTH = list(0, 4), TEXT_EAST = list(-6, 3), TEXT_WEST = list( 6, 3)) + +/datum/component/riding/cyborg/handle_vehicle_offsets() + var/atom/movable/AM = parent + if(AM.has_buckled_mobs()) + for(var/mob/living/M in AM.buckled_mobs) + M.setDir(AM.dir) + if(iscyborg(AM)) + var/mob/living/silicon/robot/R = AM + if(istype(R.module)) + M.pixel_x = R.module.ride_offset_x[dir2text(AM.dir)] + M.pixel_y = R.module.ride_offset_y[dir2text(AM.dir)] + else + ..() + +/datum/component/riding/proc/equip_buckle_inhands(mob/living/carbon/human/user, amount_required = 1, riding_target_override = null) + var/atom/movable/AM = parent + var/amount_equipped = 0 + for(var/amount_needed = amount_required, amount_needed > 0, amount_needed--) + var/obj/item/riding_offhand/inhand = new /obj/item/riding_offhand(user) + if(!riding_target_override) + inhand.rider = user + else + inhand.rider = riding_target_override + inhand.parent = AM + if(user.put_in_hands(inhand, TRUE)) + amount_equipped++ + else + break + if(amount_equipped >= amount_required) + return TRUE + else + unequip_buckle_inhands(user) + return FALSE + +/datum/component/riding/proc/unequip_buckle_inhands(mob/living/carbon/user) + var/atom/movable/AM = parent + for(var/obj/item/riding_offhand/O in user.contents) + if(O.parent != AM) + CRASH("RIDING OFFHAND ON WRONG MOB") + if(O.selfdeleting) + continue + else + qdel(O) + return TRUE + +/obj/item/riding_offhand + name = "offhand" + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "offhand" + w_class = WEIGHT_CLASS_HUGE + item_flags = ABSTRACT | DROPDEL | NOBLUDGEON + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + var/mob/living/carbon/rider + var/mob/living/parent + var/selfdeleting = FALSE + +/obj/item/riding_offhand/dropped() + selfdeleting = TRUE + . = ..() + +/obj/item/riding_offhand/equipped() + if(loc != rider && loc != parent) + selfdeleting = TRUE + qdel(src) + . = ..() + +/obj/item/riding_offhand/Destroy() + var/atom/movable/AM = parent + if(selfdeleting) + if(rider in AM.buckled_mobs) + AM.unbuckle_mob(rider) + . = ..() + +/obj/item/riding_offhand/on_thrown(mob/living/carbon/user, atom/target) + if(rider == user) + return //Piggyback user. + user.unbuckle_mob(rider) + if(HAS_TRAIT(user, TRAIT_PACIFISM)) + to_chat(user, "You gently let go of [rider].") + return + return rider diff --git a/code/datums/components/slippery.dm b/code/datums/components/slippery.dm index 130f26a0504e..541fe474292a 100644 --- a/code/datums/components/slippery.dm +++ b/code/datums/components/slippery.dm @@ -1,36 +1,36 @@ -/datum/component/slippery - var/force_drop_items = FALSE - var/knockdown_time = 0 - var/paralyze_time = 0 - var/lube_flags - var/datum/callback/callback - -/datum/component/slippery/Initialize(_knockdown, _lube_flags = NONE, datum/callback/_callback, _paralyze, _force_drop = FALSE) - knockdown_time = max(_knockdown, 0) - paralyze_time = max(_paralyze, 0) - force_drop_items = _force_drop - lube_flags = _lube_flags - callback = _callback - RegisterSignal(parent, list(COMSIG_MOVABLE_CROSSED, COMSIG_ATOM_ENTERED), .proc/Slip) - RegisterSignal(parent, COMSIG_ITEM_WEARERCROSSED, .proc/Slip_on_wearer) - -/datum/component/slippery/proc/Slip(datum/source, atom/movable/AM) - var/mob/victim = AM - if(istype(victim) && !victim.is_flying() && victim.slip(knockdown_time, parent, lube_flags, paralyze_time, force_drop_items) && callback) - callback.Invoke(victim) - - -/datum/component/slippery/proc/Slip_on_wearer(datum/source, atom/movable/AM, mob/living/crossed) - if(!(crossed.mobility_flags & MOBILITY_STAND) && !crossed.buckle_lying) - Slip(source, AM) - -/datum/component/slippery/clowning //used for making the clown PDA only slip if the clown is wearing his shoes and the elusive banana-skin belt - -/datum/component/slippery/clowning/Slip_on_wearer(datum/source, atom/movable/AM, mob/living/crossed) - var/obj/item/I = crossed.get_item_by_slot(ITEM_SLOT_FEET) - if(!(crossed.mobility_flags & MOBILITY_STAND) && !crossed.buckle_lying) - if(istype(I, /obj/item/clothing/shoes/clown_shoes)) - Slip(source, AM) - else - to_chat(crossed,"[parent] failed to slip anyone. Perhaps I shouldn't have abandoned my legacy...") - +/datum/component/slippery + var/force_drop_items = FALSE + var/knockdown_time = 0 + var/paralyze_time = 0 + var/lube_flags + var/datum/callback/callback + +/datum/component/slippery/Initialize(_knockdown, _lube_flags = NONE, datum/callback/_callback, _paralyze, _force_drop = FALSE) + knockdown_time = max(_knockdown, 0) + paralyze_time = max(_paralyze, 0) + force_drop_items = _force_drop + lube_flags = _lube_flags + callback = _callback + RegisterSignal(parent, list(COMSIG_MOVABLE_CROSSED, COMSIG_ATOM_ENTERED), .proc/Slip) + RegisterSignal(parent, COMSIG_ITEM_WEARERCROSSED, .proc/Slip_on_wearer) + +/datum/component/slippery/proc/Slip(datum/source, atom/movable/AM) + var/mob/victim = AM + if(istype(victim) && !victim.is_flying() && victim.slip(knockdown_time, parent, lube_flags, paralyze_time, force_drop_items) && callback) + callback.Invoke(victim) + + +/datum/component/slippery/proc/Slip_on_wearer(datum/source, atom/movable/AM, mob/living/crossed) + if(!(crossed.mobility_flags & MOBILITY_STAND) && !crossed.buckle_lying) + Slip(source, AM) + +/datum/component/slippery/clowning //used for making the clown PDA only slip if the clown is wearing his shoes and the elusive banana-skin belt + +/datum/component/slippery/clowning/Slip_on_wearer(datum/source, atom/movable/AM, mob/living/crossed) + var/obj/item/I = crossed.get_item_by_slot(ITEM_SLOT_FEET) + if(!(crossed.mobility_flags & MOBILITY_STAND) && !crossed.buckle_lying) + if(istype(I, /obj/item/clothing/shoes/clown_shoes)) + Slip(source, AM) + else + to_chat(crossed,"[parent] failed to slip anyone. Perhaps I shouldn't have abandoned my legacy...") + diff --git a/code/datums/components/spill.dm b/code/datums/components/spill.dm index 29cc844780e2..60bfa12402e9 100644 --- a/code/datums/components/spill.dm +++ b/code/datums/components/spill.dm @@ -1,57 +1,57 @@ -// This component is for forcing strange things into your pocket that fall out if you fall down -// Yes this exists purely for the spaghetti meme - -/datum/component/spill - can_transfer = TRUE - var/preexisting_item_flags - - var/list/droptext - var/list/dropsound - -// droptext is an arglist for visible_message -// dropsound is a list of potential sounds that gets picked from -/datum/component/spill/Initialize(list/_droptext, list/_dropsound) - if(!isitem(parent)) - return COMPONENT_INCOMPATIBLE - - if(_droptext && !islist(_droptext)) - _droptext = list(_droptext) - droptext = _droptext - - if(_dropsound && !islist(_dropsound)) - _dropsound = list(_dropsound) - dropsound = _dropsound - -/datum/component/spill/PostTransfer() - if(!isitem(parent)) - return COMPONENT_INCOMPATIBLE - -/datum/component/spill/RegisterWithParent() - RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/equip_react) - RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/drop_react) - var/obj/item/master = parent - preexisting_item_flags = master.item_flags - master.item_flags |= ITEM_SLOT_POCKETS - -/datum/component/spill/UnregisterFromParent() - UnregisterSignal(parent, list(COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_DROPPED)) - var/obj/item/master = parent - if(!(preexisting_item_flags & ITEM_SLOT_POCKETS)) - master.item_flags &= ~ITEM_SLOT_POCKETS - -/datum/component/spill/proc/equip_react(obj/item/source, mob/equipper, slot) - if(slot == ITEM_SLOT_LPOCKET || slot == ITEM_SLOT_RPOCKET) - RegisterSignal(equipper, COMSIG_LIVING_STATUS_KNOCKDOWN, .proc/knockdown_react, TRUE) - else - UnregisterSignal(equipper, COMSIG_LIVING_STATUS_KNOCKDOWN) - -/datum/component/spill/proc/drop_react(obj/item/source, mob/dropper) - UnregisterSignal(dropper, COMSIG_LIVING_STATUS_KNOCKDOWN) - -/datum/component/spill/proc/knockdown_react(mob/living/fool) - var/obj/item/master = parent - fool.dropItemToGround(master) - if(droptext) - fool.visible_message(arglist(droptext)) - if(dropsound) - playsound(master, pick(dropsound), 30) +// This component is for forcing strange things into your pocket that fall out if you fall down +// Yes this exists purely for the spaghetti meme + +/datum/component/spill + can_transfer = TRUE + var/preexisting_item_flags + + var/list/droptext + var/list/dropsound + +// droptext is an arglist for visible_message +// dropsound is a list of potential sounds that gets picked from +/datum/component/spill/Initialize(list/_droptext, list/_dropsound) + if(!isitem(parent)) + return COMPONENT_INCOMPATIBLE + + if(_droptext && !islist(_droptext)) + _droptext = list(_droptext) + droptext = _droptext + + if(_dropsound && !islist(_dropsound)) + _dropsound = list(_dropsound) + dropsound = _dropsound + +/datum/component/spill/PostTransfer() + if(!isitem(parent)) + return COMPONENT_INCOMPATIBLE + +/datum/component/spill/RegisterWithParent() + RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/equip_react) + RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/drop_react) + var/obj/item/master = parent + preexisting_item_flags = master.item_flags + master.item_flags |= ITEM_SLOT_POCKETS + +/datum/component/spill/UnregisterFromParent() + UnregisterSignal(parent, list(COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_DROPPED)) + var/obj/item/master = parent + if(!(preexisting_item_flags & ITEM_SLOT_POCKETS)) + master.item_flags &= ~ITEM_SLOT_POCKETS + +/datum/component/spill/proc/equip_react(obj/item/source, mob/equipper, slot) + if(slot == ITEM_SLOT_LPOCKET || slot == ITEM_SLOT_RPOCKET) + RegisterSignal(equipper, COMSIG_LIVING_STATUS_KNOCKDOWN, .proc/knockdown_react, TRUE) + else + UnregisterSignal(equipper, COMSIG_LIVING_STATUS_KNOCKDOWN) + +/datum/component/spill/proc/drop_react(obj/item/source, mob/dropper) + UnregisterSignal(dropper, COMSIG_LIVING_STATUS_KNOCKDOWN) + +/datum/component/spill/proc/knockdown_react(mob/living/fool) + var/obj/item/master = parent + fool.dropItemToGround(master) + if(droptext) + fool.visible_message(arglist(droptext)) + if(dropsound) + playsound(master, pick(dropsound), 30) diff --git a/code/datums/components/storage/concrete/_concrete.dm b/code/datums/components/storage/concrete/_concrete.dm index efc4f2eace8f..d1cfb0f399da 100644 --- a/code/datums/components/storage/concrete/_concrete.dm +++ b/code/datums/components/storage/concrete/_concrete.dm @@ -1,201 +1,201 @@ - -// External storage-related logic: -// /mob/proc/ClickOn() in /_onclick/click.dm - clicking items in storages -// /mob/living/Move() in /modules/mob/living/living.dm - hiding storage boxes on mob movement - -/datum/component/storage/concrete - can_transfer = TRUE - var/drop_all_on_deconstruct = TRUE - var/drop_all_on_destroy = FALSE - var/transfer_contents_on_component_transfer = FALSE - var/list/datum/component/storage/slaves = list() - - var/list/_contents_limbo // Where objects go to live mid transfer - var/list/_user_limbo // The last users before the component started moving - -/datum/component/storage/concrete/Initialize() - . = ..() - RegisterSignal(parent, COMSIG_ATOM_CONTENTS_DEL, .proc/on_contents_del) - RegisterSignal(parent, COMSIG_OBJ_DECONSTRUCT, .proc/on_deconstruct) - -/datum/component/storage/concrete/Destroy() - var/atom/real_location = real_location() - for(var/atom/_A in real_location) - _A.mouse_opacity = initial(_A.mouse_opacity) - if(drop_all_on_destroy) - do_quick_empty() - for(var/i in slaves) - var/datum/component/storage/slave = i - slave.change_master(null) - QDEL_LIST(_contents_limbo) - _user_limbo = null - return ..() - -/datum/component/storage/concrete/master() - return src - -/datum/component/storage/concrete/real_location() - return parent - -/datum/component/storage/concrete/PreTransfer() - if(is_using) - _user_limbo = is_using.Copy() - close_all() - if(transfer_contents_on_component_transfer) - _contents_limbo = list() - for(var/atom/movable/AM in parent) - _contents_limbo += AM - AM.moveToNullspace() - -/datum/component/storage/concrete/PostTransfer() - if(!isatom(parent)) - return COMPONENT_INCOMPATIBLE - if(transfer_contents_on_component_transfer) - for(var/i in _contents_limbo) - var/atom/movable/AM = i - AM.forceMove(parent) - _contents_limbo = null - if(_user_limbo) - for(var/i in _user_limbo) - show_to(i) - _user_limbo = null - -/datum/component/storage/concrete/_insert_physical_item(obj/item/I, override = FALSE) - . = TRUE - var/atom/real_location = real_location() - if(I.loc != real_location) - I.forceMove(real_location) - refresh_mob_views() - -/datum/component/storage/concrete/refresh_mob_views() - . = ..() - for(var/i in slaves) - var/datum/component/storage/slave = i - slave.refresh_mob_views() - -/datum/component/storage/concrete/emp_act(datum/source, severity) - if(emp_shielded) - return - var/atom/real_location = real_location() - for(var/i in real_location) - var/atom/A = i - A.emp_act(severity) - -/datum/component/storage/concrete/proc/on_slave_link(datum/component/storage/S) - if(S == src) - return FALSE - slaves += S - return TRUE - -/datum/component/storage/concrete/proc/on_slave_unlink(datum/component/storage/S) - slaves -= S - return FALSE - -/datum/component/storage/concrete/proc/on_contents_del(datum/source, atom/A) - var/atom/real_location = parent - if(A in real_location) - usr = null - remove_from_storage(A, null) - -/datum/component/storage/concrete/proc/on_deconstruct(datum/source, disassembled) - if(drop_all_on_deconstruct) - do_quick_empty() - -/datum/component/storage/concrete/can_see_contents() - . = ..() - for(var/i in slaves) - var/datum/component/storage/slave = i - . |= slave.can_see_contents() - -//Resets screen loc and other vars of something being removed from storage. -/datum/component/storage/concrete/_removal_reset(atom/movable/thing) - thing.layer = initial(thing.layer) - thing.plane = initial(thing.plane) - thing.mouse_opacity = initial(thing.mouse_opacity) - if(thing.maptext) - thing.maptext = "" - -/datum/component/storage/concrete/remove_from_storage(atom/movable/AM, atom/new_location) - //Cache this as it should be reusable down the bottom, will not apply if anyone adds a sleep to dropped - //or moving objects, things that should never happen - var/atom/parent = src.parent - var/list/seeing_mobs = can_see_contents() - for(var/mob/M in seeing_mobs) - M.client.screen -= AM - if(ismob(parent.loc) && isitem(AM)) - var/obj/item/I = AM - var/mob/M = parent.loc - I.dropped(M, TRUE) - I.item_flags &= ~IN_STORAGE - if(new_location) - //Reset the items values - _removal_reset(AM) - AM.forceMove(new_location) - //We don't want to call this if the item is being destroyed - AM.on_exit_storage(src) - else - //Being destroyed, just move to nullspace now (so it's not in contents for the icon update) - AM.moveToNullspace() - refresh_mob_views() - if(isobj(parent)) - var/obj/O = parent - O.update_icon() - return TRUE - -/datum/component/storage/concrete/proc/slave_can_insert_object(datum/component/storage/slave, obj/item/I, stop_messages = FALSE, mob/M) - return TRUE - -/datum/component/storage/concrete/proc/handle_item_insertion_from_slave(datum/component/storage/slave, obj/item/I, prevent_warning = FALSE, M) - . = handle_item_insertion(I, prevent_warning, M, slave) - if(. && !prevent_warning) - slave.mob_item_insertion_feedback(usr, M, I) - -/datum/component/storage/concrete/handle_item_insertion(obj/item/I, prevent_warning = FALSE, mob/M, datum/component/storage/remote) //Remote is null or the slave datum - var/datum/component/storage/concrete/master = master() - var/atom/parent = src.parent - var/moved = FALSE - if(!istype(I)) - return FALSE - if(M) - if(!M.temporarilyRemoveItemFromInventory(I)) - return FALSE - else - moved = TRUE //At this point if the proc fails we need to manually move the object back to the turf/mob/whatever. - if(I.pulledby) - I.pulledby.stop_pulling() - if(silent) - prevent_warning = TRUE - if(!_insert_physical_item(I)) - if(moved) - if(M) - if(!M.put_in_active_hand(I)) - I.forceMove(parent.drop_location()) - else - I.forceMove(parent.drop_location()) - return FALSE - I.on_enter_storage(master) - I.item_flags |= IN_STORAGE - refresh_mob_views() - I.mouse_opacity = MOUSE_OPACITY_OPAQUE //So you can click on the area around the item to equip it, instead of having to pixel hunt - if(M) - if(M.client && M.active_storage != src) - M.client.screen -= I - if(M.observers && M.observers.len) - for(var/i in M.observers) - var/mob/dead/observe = i - if(observe.client && observe.active_storage != src) - observe.client.screen -= I - if(!remote) - parent.add_fingerprint(M) - if(!prevent_warning) - mob_item_insertion_feedback(usr, M, I) - update_icon() - return TRUE - -/datum/component/storage/concrete/update_icon() - if(isobj(parent)) - var/obj/O = parent - O.update_icon() - for(var/i in slaves) - var/datum/component/storage/slave = i - slave.update_icon() + +// External storage-related logic: +// /mob/proc/ClickOn() in /_onclick/click.dm - clicking items in storages +// /mob/living/Move() in /modules/mob/living/living.dm - hiding storage boxes on mob movement + +/datum/component/storage/concrete + can_transfer = TRUE + var/drop_all_on_deconstruct = TRUE + var/drop_all_on_destroy = FALSE + var/transfer_contents_on_component_transfer = FALSE + var/list/datum/component/storage/slaves = list() + + var/list/_contents_limbo // Where objects go to live mid transfer + var/list/_user_limbo // The last users before the component started moving + +/datum/component/storage/concrete/Initialize() + . = ..() + RegisterSignal(parent, COMSIG_ATOM_CONTENTS_DEL, .proc/on_contents_del) + RegisterSignal(parent, COMSIG_OBJ_DECONSTRUCT, .proc/on_deconstruct) + +/datum/component/storage/concrete/Destroy() + var/atom/real_location = real_location() + for(var/atom/_A in real_location) + _A.mouse_opacity = initial(_A.mouse_opacity) + if(drop_all_on_destroy) + do_quick_empty() + for(var/i in slaves) + var/datum/component/storage/slave = i + slave.change_master(null) + QDEL_LIST(_contents_limbo) + _user_limbo = null + return ..() + +/datum/component/storage/concrete/master() + return src + +/datum/component/storage/concrete/real_location() + return parent + +/datum/component/storage/concrete/PreTransfer() + if(is_using) + _user_limbo = is_using.Copy() + close_all() + if(transfer_contents_on_component_transfer) + _contents_limbo = list() + for(var/atom/movable/AM in parent) + _contents_limbo += AM + AM.moveToNullspace() + +/datum/component/storage/concrete/PostTransfer() + if(!isatom(parent)) + return COMPONENT_INCOMPATIBLE + if(transfer_contents_on_component_transfer) + for(var/i in _contents_limbo) + var/atom/movable/AM = i + AM.forceMove(parent) + _contents_limbo = null + if(_user_limbo) + for(var/i in _user_limbo) + show_to(i) + _user_limbo = null + +/datum/component/storage/concrete/_insert_physical_item(obj/item/I, override = FALSE) + . = TRUE + var/atom/real_location = real_location() + if(I.loc != real_location) + I.forceMove(real_location) + refresh_mob_views() + +/datum/component/storage/concrete/refresh_mob_views() + . = ..() + for(var/i in slaves) + var/datum/component/storage/slave = i + slave.refresh_mob_views() + +/datum/component/storage/concrete/emp_act(datum/source, severity) + if(emp_shielded) + return + var/atom/real_location = real_location() + for(var/i in real_location) + var/atom/A = i + A.emp_act(severity) + +/datum/component/storage/concrete/proc/on_slave_link(datum/component/storage/S) + if(S == src) + return FALSE + slaves += S + return TRUE + +/datum/component/storage/concrete/proc/on_slave_unlink(datum/component/storage/S) + slaves -= S + return FALSE + +/datum/component/storage/concrete/proc/on_contents_del(datum/source, atom/A) + var/atom/real_location = parent + if(A in real_location) + usr = null + remove_from_storage(A, null) + +/datum/component/storage/concrete/proc/on_deconstruct(datum/source, disassembled) + if(drop_all_on_deconstruct) + do_quick_empty() + +/datum/component/storage/concrete/can_see_contents() + . = ..() + for(var/i in slaves) + var/datum/component/storage/slave = i + . |= slave.can_see_contents() + +//Resets screen loc and other vars of something being removed from storage. +/datum/component/storage/concrete/_removal_reset(atom/movable/thing) + thing.layer = initial(thing.layer) + thing.plane = initial(thing.plane) + thing.mouse_opacity = initial(thing.mouse_opacity) + if(thing.maptext) + thing.maptext = "" + +/datum/component/storage/concrete/remove_from_storage(atom/movable/AM, atom/new_location) + //Cache this as it should be reusable down the bottom, will not apply if anyone adds a sleep to dropped + //or moving objects, things that should never happen + var/atom/parent = src.parent + var/list/seeing_mobs = can_see_contents() + for(var/mob/M in seeing_mobs) + M.client.screen -= AM + if(ismob(parent.loc) && isitem(AM)) + var/obj/item/I = AM + var/mob/M = parent.loc + I.dropped(M, TRUE) + I.item_flags &= ~IN_STORAGE + if(new_location) + //Reset the items values + _removal_reset(AM) + AM.forceMove(new_location) + //We don't want to call this if the item is being destroyed + AM.on_exit_storage(src) + else + //Being destroyed, just move to nullspace now (so it's not in contents for the icon update) + AM.moveToNullspace() + refresh_mob_views() + if(isobj(parent)) + var/obj/O = parent + O.update_icon() + return TRUE + +/datum/component/storage/concrete/proc/slave_can_insert_object(datum/component/storage/slave, obj/item/I, stop_messages = FALSE, mob/M) + return TRUE + +/datum/component/storage/concrete/proc/handle_item_insertion_from_slave(datum/component/storage/slave, obj/item/I, prevent_warning = FALSE, M) + . = handle_item_insertion(I, prevent_warning, M, slave) + if(. && !prevent_warning) + slave.mob_item_insertion_feedback(usr, M, I) + +/datum/component/storage/concrete/handle_item_insertion(obj/item/I, prevent_warning = FALSE, mob/M, datum/component/storage/remote) //Remote is null or the slave datum + var/datum/component/storage/concrete/master = master() + var/atom/parent = src.parent + var/moved = FALSE + if(!istype(I)) + return FALSE + if(M) + if(!M.temporarilyRemoveItemFromInventory(I)) + return FALSE + else + moved = TRUE //At this point if the proc fails we need to manually move the object back to the turf/mob/whatever. + if(I.pulledby) + I.pulledby.stop_pulling() + if(silent) + prevent_warning = TRUE + if(!_insert_physical_item(I)) + if(moved) + if(M) + if(!M.put_in_active_hand(I)) + I.forceMove(parent.drop_location()) + else + I.forceMove(parent.drop_location()) + return FALSE + I.on_enter_storage(master) + I.item_flags |= IN_STORAGE + refresh_mob_views() + I.mouse_opacity = MOUSE_OPACITY_OPAQUE //So you can click on the area around the item to equip it, instead of having to pixel hunt + if(M) + if(M.client && M.active_storage != src) + M.client.screen -= I + if(M.observers && M.observers.len) + for(var/i in M.observers) + var/mob/dead/observe = i + if(observe.client && observe.active_storage != src) + observe.client.screen -= I + if(!remote) + parent.add_fingerprint(M) + if(!prevent_warning) + mob_item_insertion_feedback(usr, M, I) + update_icon() + return TRUE + +/datum/component/storage/concrete/update_icon() + if(isobj(parent)) + var/obj/O = parent + O.update_icon() + for(var/i in slaves) + var/datum/component/storage/slave = i + slave.update_icon() diff --git a/code/datums/components/storage/concrete/bag_of_holding.dm b/code/datums/components/storage/concrete/bag_of_holding.dm index 5c8ad6933da2..0f83d15cad9c 100644 --- a/code/datums/components/storage/concrete/bag_of_holding.dm +++ b/code/datums/components/storage/concrete/bag_of_holding.dm @@ -1,23 +1,23 @@ -/datum/component/storage/concrete/bluespace/bag_of_holding/handle_item_insertion(obj/item/W, prevent_warning = FALSE, mob/living/user) - var/atom/A = parent - if(A == W) //don't put yourself into yourself. - return - var/list/obj/item/storage/backpack/holding/matching = typecache_filter_list(W.GetAllContents(), typecacheof(/obj/item/storage/backpack/holding)) - matching -= A - if(istype(W, /obj/item/storage/backpack/holding) || matching.len) - var/safety = alert(user, "Doing this will have extremely dire consequences for the station and its crew. Be sure you know what you're doing.", "Put in [A.name]?", "Proceed", "Abort") - if(safety != "Proceed" || QDELETED(A) || QDELETED(W) || QDELETED(user) || !user.canUseTopic(A, BE_CLOSE, iscarbon(user))) - return - var/turf/loccheck = get_turf(A) - to_chat(user, "The Bluespace interfaces of the two devices catastrophically malfunction!") - qdel(W) - playsound(loccheck,'sound/effects/supermatter.ogg', 200, TRUE) - - message_admins("[ADMIN_LOOKUPFLW(user)] detonated a bag of holding at [ADMIN_VERBOSEJMP(loccheck)].") - log_game("[key_name(user)] detonated a bag of holding at [loc_name(loccheck)].") - - user.gib(TRUE, TRUE, TRUE) - new/obj/singularity/boh_tear(loccheck) - qdel(A) - return - . = ..() +/datum/component/storage/concrete/bluespace/bag_of_holding/handle_item_insertion(obj/item/W, prevent_warning = FALSE, mob/living/user) + var/atom/A = parent + if(A == W) //don't put yourself into yourself. + return + var/list/obj/item/storage/backpack/holding/matching = typecache_filter_list(W.GetAllContents(), typecacheof(/obj/item/storage/backpack/holding)) + matching -= A + if(istype(W, /obj/item/storage/backpack/holding) || matching.len) + var/safety = alert(user, "Doing this will have extremely dire consequences for the station and its crew. Be sure you know what you're doing.", "Put in [A.name]?", "Proceed", "Abort") + if(safety != "Proceed" || QDELETED(A) || QDELETED(W) || QDELETED(user) || !user.canUseTopic(A, BE_CLOSE, iscarbon(user))) + return + var/turf/loccheck = get_turf(A) + to_chat(user, "The Bluespace interfaces of the two devices catastrophically malfunction!") + qdel(W) + playsound(loccheck,'sound/effects/supermatter.ogg', 200, TRUE) + + message_admins("[ADMIN_LOOKUPFLW(user)] detonated a bag of holding at [ADMIN_VERBOSEJMP(loccheck)].") + log_game("[key_name(user)] detonated a bag of holding at [loc_name(loccheck)].") + + user.gib(TRUE, TRUE, TRUE) + new/obj/singularity/boh_tear(loccheck) + qdel(A) + return + . = ..() diff --git a/code/datums/components/storage/concrete/bluespace.dm b/code/datums/components/storage/concrete/bluespace.dm index d9669eaa1e11..ceda1655eca6 100644 --- a/code/datums/components/storage/concrete/bluespace.dm +++ b/code/datums/components/storage/concrete/bluespace.dm @@ -1,22 +1,22 @@ -/datum/component/storage/concrete/bluespace - var/dumping_range = 8 - var/dumping_sound = 'sound/items/pshoom.ogg' - var/alt_sound = 'sound/items/pshoom_2.ogg' - -/datum/component/storage/concrete/bluespace/dump_content_at(atom/dest, mob/M) - var/atom/A = parent - if(A.Adjacent(M)) - var/atom/dumping_location = dest.get_dumping_location() - var/turf/bagT = get_turf(parent) - var/turf/destT = get_turf(dumping_location) - if(destT && bagT && bagT.z == destT.z && get_dist(M, dumping_location) < dumping_range) - if(dumping_location.storage_contents_dump_act(src, M)) - if(alt_sound && prob(1)) - playsound(src, alt_sound, 40, TRUE) - else - playsound(src, dumping_sound, 40, TRUE) - M.Beam(dumping_location, icon_state="rped_upgrade", time=5) - return TRUE - to_chat(M, "The [A.name] buzzes.") - playsound(src, 'sound/machines/buzz-sigh.ogg', 50, FALSE) - return FALSE +/datum/component/storage/concrete/bluespace + var/dumping_range = 8 + var/dumping_sound = 'sound/items/pshoom.ogg' + var/alt_sound = 'sound/items/pshoom_2.ogg' + +/datum/component/storage/concrete/bluespace/dump_content_at(atom/dest, mob/M) + var/atom/A = parent + if(A.Adjacent(M)) + var/atom/dumping_location = dest.get_dumping_location() + var/turf/bagT = get_turf(parent) + var/turf/destT = get_turf(dumping_location) + if(destT && bagT && bagT.z == destT.z && get_dist(M, dumping_location) < dumping_range) + if(dumping_location.storage_contents_dump_act(src, M)) + if(alt_sound && prob(1)) + playsound(src, alt_sound, 40, TRUE) + else + playsound(src, dumping_sound, 40, TRUE) + M.Beam(dumping_location, icon_state="rped_upgrade", time=5) + return TRUE + to_chat(M, "The [A.name] buzzes.") + playsound(src, 'sound/machines/buzz-sigh.ogg', 50, FALSE) + return FALSE diff --git a/code/datums/components/storage/concrete/implant.dm b/code/datums/components/storage/concrete/implant.dm index 95a929494dfe..405b3098fe51 100644 --- a/code/datums/components/storage/concrete/implant.dm +++ b/code/datums/components/storage/concrete/implant.dm @@ -1,18 +1,18 @@ -/datum/component/storage/concrete/implant - max_w_class = WEIGHT_CLASS_NORMAL - max_combined_w_class = 6 - max_items = 2 - drop_all_on_destroy = TRUE - drop_all_on_deconstruct = TRUE - silent = TRUE - allow_big_nesting = TRUE - -/datum/component/storage/concrete/implant/Initialize() - . = ..() - set_holdable(null, list(/obj/item/disk/nuclear)) - -/datum/component/storage/concrete/implant/InheritComponent(datum/component/storage/concrete/implant/I, original) - if(!istype(I)) - return ..() - max_combined_w_class += I.max_combined_w_class - max_items += I.max_items +/datum/component/storage/concrete/implant + max_w_class = WEIGHT_CLASS_NORMAL + max_combined_w_class = 6 + max_items = 2 + drop_all_on_destroy = TRUE + drop_all_on_deconstruct = TRUE + silent = TRUE + allow_big_nesting = TRUE + +/datum/component/storage/concrete/implant/Initialize() + . = ..() + set_holdable(null, list(/obj/item/disk/nuclear)) + +/datum/component/storage/concrete/implant/InheritComponent(datum/component/storage/concrete/implant/I, original) + if(!istype(I)) + return ..() + max_combined_w_class += I.max_combined_w_class + max_items += I.max_items diff --git a/code/datums/components/storage/concrete/pockets.dm b/code/datums/components/storage/concrete/pockets.dm index 1aeb58774320..85ae8fe4b608 100644 --- a/code/datums/components/storage/concrete/pockets.dm +++ b/code/datums/components/storage/concrete/pockets.dm @@ -1,97 +1,97 @@ -/datum/component/storage/concrete/pockets - max_items = 2 - max_w_class = WEIGHT_CLASS_SMALL - max_combined_w_class = 50 - rustle_sound = FALSE - -/datum/component/storage/concrete/pockets/handle_item_insertion(obj/item/I, prevent_warning, mob/user) - . = ..() - if(. && silent && !prevent_warning) - if(quickdraw) - to_chat(user, "You discreetly slip [I] into [parent]. Alt-click [parent] to remove it.") - else - to_chat(user, "You discreetly slip [I] into [parent].") - -/datum/component/storage/concrete/pockets/small - max_items = 1 - max_w_class = WEIGHT_CLASS_SMALL - attack_hand_interact = FALSE - -/datum/component/storage/concrete/pockets/tiny - max_items = 1 - max_w_class = WEIGHT_CLASS_TINY - attack_hand_interact = FALSE - -//Waspstation Begin - Exowear Pockets -/datum/component/storage/concrete/pockets/exo - max_items = 2 - max_w_class = WEIGHT_CLASS_SMALL - attack_hand_interact = FALSE - quickdraw = FALSE - silent = FALSE - -/datum/component/storage/concrete/pockets/exo/cloak - max_items = 1 - max_w_class = WEIGHT_CLASS_NORMAL - quickdraw = TRUE - -/datum/component/storage/concrete/pockets/exo/large - max_items = 3 -//WaspStation End - -/datum/component/storage/concrete/pockets/small/fedora/Initialize() - . = ..() - var/static/list/exception_cache = typecacheof(list( - /obj/item/katana, /obj/item/toy/katana, /obj/item/nullrod/claymore/katana, - /obj/item/energy_katana, /obj/item/gun/ballistic/automatic/tommygun - )) - exception_hold = exception_cache - -/datum/component/storage/concrete/pockets/small/fedora/detective - attack_hand_interact = TRUE // so the detectives would discover pockets in their hats - -//WaspStation Begin - Any small item in shoes -/datum/component/storage/concrete/pockets/shoes - max_items = 2 - attack_hand_interact = FALSE - max_w_class = WEIGHT_CLASS_SMALL - quickdraw = FALSE - silent = TRUE -//WaspStation End - -/datum/component/storage/concrete/pockets/shoes/Initialize() - . = ..() - -/datum/component/storage/concrete/pockets/shoes/clown/Initialize() - . = ..() - -/datum/component/storage/concrete/pockets/pocketprotector - max_items = 3 - max_w_class = WEIGHT_CLASS_TINY - var/atom/original_parent - -/datum/component/storage/concrete/pockets/pocketprotector/Initialize() - original_parent = parent - . = ..() - set_holdable(list( //Same items as a PDA - /obj/item/pen, - /obj/item/toy/crayon, - /obj/item/lipstick, - /obj/item/flashlight/pen, - /obj/item/clothing/mask/cigarette) - ) - -/datum/component/storage/concrete/pockets/pocketprotector/real_location() - // if the component is reparented to a jumpsuit, the items still go in the protector - return original_parent - -/datum/component/storage/concrete/pockets/helmet - quickdraw = TRUE - max_combined_w_class = 6 - -/datum/component/storage/concrete/pockets/helmet/Initialize() - . = ..() - set_holdable(list(/obj/item/reagent_containers/food/drinks/bottle/vodka, - /obj/item/reagent_containers/food/drinks/bottle/molotov, - /obj/item/reagent_containers/food/drinks/drinkingglass, - /obj/item/ammo_box/a762)) +/datum/component/storage/concrete/pockets + max_items = 2 + max_w_class = WEIGHT_CLASS_SMALL + max_combined_w_class = 50 + rustle_sound = FALSE + +/datum/component/storage/concrete/pockets/handle_item_insertion(obj/item/I, prevent_warning, mob/user) + . = ..() + if(. && silent && !prevent_warning) + if(quickdraw) + to_chat(user, "You discreetly slip [I] into [parent]. Alt-click [parent] to remove it.") + else + to_chat(user, "You discreetly slip [I] into [parent].") + +/datum/component/storage/concrete/pockets/small + max_items = 1 + max_w_class = WEIGHT_CLASS_SMALL + attack_hand_interact = FALSE + +/datum/component/storage/concrete/pockets/tiny + max_items = 1 + max_w_class = WEIGHT_CLASS_TINY + attack_hand_interact = FALSE + +//Waspstation Begin - Exowear Pockets +/datum/component/storage/concrete/pockets/exo + max_items = 2 + max_w_class = WEIGHT_CLASS_SMALL + attack_hand_interact = FALSE + quickdraw = FALSE + silent = FALSE + +/datum/component/storage/concrete/pockets/exo/cloak + max_items = 1 + max_w_class = WEIGHT_CLASS_NORMAL + quickdraw = TRUE + +/datum/component/storage/concrete/pockets/exo/large + max_items = 3 +//WaspStation End + +/datum/component/storage/concrete/pockets/small/fedora/Initialize() + . = ..() + var/static/list/exception_cache = typecacheof(list( + /obj/item/katana, /obj/item/toy/katana, /obj/item/nullrod/claymore/katana, + /obj/item/energy_katana, /obj/item/gun/ballistic/automatic/tommygun + )) + exception_hold = exception_cache + +/datum/component/storage/concrete/pockets/small/fedora/detective + attack_hand_interact = TRUE // so the detectives would discover pockets in their hats + +//WaspStation Begin - Any small item in shoes +/datum/component/storage/concrete/pockets/shoes + max_items = 2 + attack_hand_interact = FALSE + max_w_class = WEIGHT_CLASS_SMALL + quickdraw = FALSE + silent = TRUE +//WaspStation End + +/datum/component/storage/concrete/pockets/shoes/Initialize() + . = ..() + +/datum/component/storage/concrete/pockets/shoes/clown/Initialize() + . = ..() + +/datum/component/storage/concrete/pockets/pocketprotector + max_items = 3 + max_w_class = WEIGHT_CLASS_TINY + var/atom/original_parent + +/datum/component/storage/concrete/pockets/pocketprotector/Initialize() + original_parent = parent + . = ..() + set_holdable(list( //Same items as a PDA + /obj/item/pen, + /obj/item/toy/crayon, + /obj/item/lipstick, + /obj/item/flashlight/pen, + /obj/item/clothing/mask/cigarette) + ) + +/datum/component/storage/concrete/pockets/pocketprotector/real_location() + // if the component is reparented to a jumpsuit, the items still go in the protector + return original_parent + +/datum/component/storage/concrete/pockets/helmet + quickdraw = TRUE + max_combined_w_class = 6 + +/datum/component/storage/concrete/pockets/helmet/Initialize() + . = ..() + set_holdable(list(/obj/item/reagent_containers/food/drinks/bottle/vodka, + /obj/item/reagent_containers/food/drinks/bottle/molotov, + /obj/item/reagent_containers/food/drinks/drinkingglass, + /obj/item/ammo_box/a762)) diff --git a/code/datums/components/storage/concrete/rped.dm b/code/datums/components/storage/concrete/rped.dm index 14f6fe28b38f..455eb985f090 100644 --- a/code/datums/components/storage/concrete/rped.dm +++ b/code/datums/components/storage/concrete/rped.dm @@ -1,33 +1,33 @@ -/datum/component/storage/concrete/rped - collection_mode = COLLECT_EVERYTHING - allow_quick_gather = TRUE - allow_quick_empty = TRUE - click_gather = TRUE - max_w_class = WEIGHT_CLASS_NORMAL - max_combined_w_class = 100 - max_items = 50 - display_numerical_stacking = TRUE - -/datum/component/storage/concrete/rped/can_be_inserted(obj/item/I, stop_messages, mob/M) - . = ..() - if(!I.get_part_rating()) - if (!stop_messages) - to_chat(M, "[parent] only accepts machine parts!") - return FALSE - -/datum/component/storage/concrete/bluespace/rped - collection_mode = COLLECT_EVERYTHING - allow_quick_gather = TRUE - allow_quick_empty = TRUE - click_gather = TRUE - max_w_class = WEIGHT_CLASS_BULKY // can fit vending refills - max_combined_w_class = 800 - max_items = 400 - display_numerical_stacking = TRUE - -/datum/component/storage/concrete/bluespace/rped/can_be_inserted(obj/item/I, stop_messages, mob/M) - . = ..() - if(!I.get_part_rating()) - if (!stop_messages) - to_chat(M, "[parent] only accepts machine parts!") - return FALSE +/datum/component/storage/concrete/rped + collection_mode = COLLECT_EVERYTHING + allow_quick_gather = TRUE + allow_quick_empty = TRUE + click_gather = TRUE + max_w_class = WEIGHT_CLASS_NORMAL + max_combined_w_class = 100 + max_items = 50 + display_numerical_stacking = TRUE + +/datum/component/storage/concrete/rped/can_be_inserted(obj/item/I, stop_messages, mob/M) + . = ..() + if(!I.get_part_rating()) + if (!stop_messages) + to_chat(M, "[parent] only accepts machine parts!") + return FALSE + +/datum/component/storage/concrete/bluespace/rped + collection_mode = COLLECT_EVERYTHING + allow_quick_gather = TRUE + allow_quick_empty = TRUE + click_gather = TRUE + max_w_class = WEIGHT_CLASS_BULKY // can fit vending refills + max_combined_w_class = 800 + max_items = 400 + display_numerical_stacking = TRUE + +/datum/component/storage/concrete/bluespace/rped/can_be_inserted(obj/item/I, stop_messages, mob/M) + . = ..() + if(!I.get_part_rating()) + if (!stop_messages) + to_chat(M, "[parent] only accepts machine parts!") + return FALSE diff --git a/code/datums/components/storage/concrete/stack.dm b/code/datums/components/storage/concrete/stack.dm index ad003d19b3f0..1f0c44c65025 100644 --- a/code/datums/components/storage/concrete/stack.dm +++ b/code/datums/components/storage/concrete/stack.dm @@ -1,67 +1,67 @@ -//Stack-only storage. -/datum/component/storage/concrete/stack - display_numerical_stacking = TRUE - var/max_combined_stack_amount = 300 - max_w_class = WEIGHT_CLASS_NORMAL - max_combined_w_class = WEIGHT_CLASS_NORMAL * 14 - -/datum/component/storage/concrete/stack/proc/total_stack_amount() - . = 0 - var/atom/real_location = real_location() - for(var/i in real_location) - var/obj/item/stack/S = i - if(!istype(S)) - continue - . += S.amount - -/datum/component/storage/concrete/stack/proc/remaining_space() - return max(0, max_combined_stack_amount - total_stack_amount()) - -//emptying procs do not need modification as stacks automatically merge. - -/datum/component/storage/concrete/stack/_insert_physical_item(obj/item/I, override = FALSE) - if(!istype(I, /obj/item/stack)) - if(override) - return ..() - return FALSE - var/atom/real_location = real_location() - var/obj/item/stack/S = I - var/can_insert = min(S.amount, remaining_space()) - if(!can_insert) - return FALSE - for(var/i in real_location) //combine. - if(QDELETED(I)) - return - var/obj/item/stack/_S = i - if(!istype(_S)) - continue - if(_S.merge_type == S.merge_type) - _S.add(can_insert) - S.use(can_insert, TRUE) - return TRUE - return ..(S.change_stack(null, can_insert), override) - -/datum/component/storage/concrete/stack/remove_from_storage(obj/item/I, atom/new_location) - var/atom/real_location = real_location() - var/obj/item/stack/S = I - if(!istype(S)) - return ..() - if(S.amount > S.max_amount) - var/overrun = S.amount - S.max_amount - S.amount = S.max_amount - var/obj/item/stack/temp = new S.type(real_location, overrun) - handle_item_insertion(temp) - return ..(S, new_location) - -/datum/component/storage/concrete/stack/_process_numerical_display() - var/atom/real_location = real_location() - . = list() - for(var/i in real_location) - var/obj/item/stack/I = i - if(!istype(I) || QDELETED(I)) //We're specialized stack storage, just ignore non stacks. - continue - if(!.[I.merge_type]) - .[I.merge_type] = new /datum/numbered_display(I, I.amount) - else - var/datum/numbered_display/ND = .[I.merge_type] - ND.number += I.amount +//Stack-only storage. +/datum/component/storage/concrete/stack + display_numerical_stacking = TRUE + var/max_combined_stack_amount = 300 + max_w_class = WEIGHT_CLASS_NORMAL + max_combined_w_class = WEIGHT_CLASS_NORMAL * 14 + +/datum/component/storage/concrete/stack/proc/total_stack_amount() + . = 0 + var/atom/real_location = real_location() + for(var/i in real_location) + var/obj/item/stack/S = i + if(!istype(S)) + continue + . += S.amount + +/datum/component/storage/concrete/stack/proc/remaining_space() + return max(0, max_combined_stack_amount - total_stack_amount()) + +//emptying procs do not need modification as stacks automatically merge. + +/datum/component/storage/concrete/stack/_insert_physical_item(obj/item/I, override = FALSE) + if(!istype(I, /obj/item/stack)) + if(override) + return ..() + return FALSE + var/atom/real_location = real_location() + var/obj/item/stack/S = I + var/can_insert = min(S.amount, remaining_space()) + if(!can_insert) + return FALSE + for(var/i in real_location) //combine. + if(QDELETED(I)) + return + var/obj/item/stack/_S = i + if(!istype(_S)) + continue + if(_S.merge_type == S.merge_type) + _S.add(can_insert) + S.use(can_insert, TRUE) + return TRUE + return ..(S.change_stack(null, can_insert), override) + +/datum/component/storage/concrete/stack/remove_from_storage(obj/item/I, atom/new_location) + var/atom/real_location = real_location() + var/obj/item/stack/S = I + if(!istype(S)) + return ..() + if(S.amount > S.max_amount) + var/overrun = S.amount - S.max_amount + S.amount = S.max_amount + var/obj/item/stack/temp = new S.type(real_location, overrun) + handle_item_insertion(temp) + return ..(S, new_location) + +/datum/component/storage/concrete/stack/_process_numerical_display() + var/atom/real_location = real_location() + . = list() + for(var/i in real_location) + var/obj/item/stack/I = i + if(!istype(I) || QDELETED(I)) //We're specialized stack storage, just ignore non stacks. + continue + if(!.[I.merge_type]) + .[I.merge_type] = new /datum/numbered_display(I, I.amount) + else + var/datum/numbered_display/ND = .[I.merge_type] + ND.number += I.amount diff --git a/code/datums/components/storage/storage.dm b/code/datums/components/storage/storage.dm index 367fbc42d8d2..254be49130e8 100644 --- a/code/datums/components/storage/storage.dm +++ b/code/datums/components/storage/storage.dm @@ -1,818 +1,818 @@ -#define COLLECT_ONE 0 -#define COLLECT_EVERYTHING 1 -#define COLLECT_SAME 2 - -#define DROP_NOTHING 0 -#define DROP_AT_PARENT 1 -#define DROP_AT_LOCATION 2 - -// External storage-related logic: -// /mob/proc/ClickOn() in /_onclick/click.dm - clicking items in storages -// /mob/living/Move() in /modules/mob/living/living.dm - hiding storage boxes on mob movement - -/datum/component/storage - dupe_mode = COMPONENT_DUPE_UNIQUE - var/datum/component/storage/concrete/master //If not null, all actions act on master and this is just an access point. - - var/list/can_hold //if this is set, only items, and their children, will fit - var/list/cant_hold //if this is set, items, and their children, won't fit - var/list/exception_hold //if set, these items will be the exception to the max size of object that can fit. - - var/can_hold_description - - var/list/mob/is_using //lazy list of mobs looking at the contents of this storage. - - var/locked = FALSE //when locked nothing can see inside or use it. - - var/max_w_class = WEIGHT_CLASS_SMALL //max size of objects that will fit. - var/max_combined_w_class = 14 //max combined sizes of objects that will fit. - var/max_items = 7 //max number of objects that will fit. - - var/emp_shielded = FALSE - - var/silent = FALSE //whether this makes a message when things are put in. - var/click_gather = FALSE //whether this can be clicked on items to pick it up rather than the other way around. - var/rustle_sound = TRUE //play rustle sound on interact. - var/allow_quick_empty = FALSE //allow empty verb which allows dumping on the floor of everything inside quickly. - var/allow_quick_gather = FALSE //allow toggle mob verb which toggles collecting all items from a tile. - - var/collection_mode = COLLECT_EVERYTHING - - var/insert_preposition = "in" //you put things "in" a bag, but "on" a tray. - - var/display_numerical_stacking = FALSE //stack things of the same type and show as a single object with a number. - - var/obj/screen/storage/boxes //storage display object - var/obj/screen/close/closer //close button object - - var/allow_big_nesting = FALSE //allow storage objects of the same or greater size. - - var/attack_hand_interact = TRUE //interact on attack hand. - var/quickdraw = FALSE //altclick interact - - var/datum/action/item_action/storage_gather_mode/modeswitch_action - - //Screen variables: Do not mess with these vars unless you know what you're doing. They're not defines so storage that isn't in the same location can be supported in the future. - var/screen_max_columns = 7 //These two determine maximum screen sizes. - var/screen_max_rows = INFINITY - var/screen_pixel_x = 16 //These two are pixel values for screen loc of boxes and closer - var/screen_pixel_y = 16 - var/screen_start_x = 4 //These two are where the storage starts being rendered, screen_loc wise. - var/screen_start_y = 2 - //End - -/datum/component/storage/Initialize(datum/component/storage/concrete/master) - if(!isatom(parent)) - return COMPONENT_INCOMPATIBLE - if(master) - change_master(master) - boxes = new(null, src) - closer = new(null, src) - orient2hud() - - RegisterSignal(parent, COMSIG_CONTAINS_STORAGE, .proc/on_check) - RegisterSignal(parent, COMSIG_IS_STORAGE_LOCKED, .proc/check_locked) - RegisterSignal(parent, COMSIG_TRY_STORAGE_SHOW, .proc/signal_show_attempt) - RegisterSignal(parent, COMSIG_TRY_STORAGE_INSERT, .proc/signal_insertion_attempt) - RegisterSignal(parent, COMSIG_TRY_STORAGE_CAN_INSERT, .proc/signal_can_insert) - RegisterSignal(parent, COMSIG_TRY_STORAGE_TAKE_TYPE, .proc/signal_take_type) - RegisterSignal(parent, COMSIG_TRY_STORAGE_FILL_TYPE, .proc/signal_fill_type) - RegisterSignal(parent, COMSIG_TRY_STORAGE_SET_LOCKSTATE, .proc/set_locked) - RegisterSignal(parent, COMSIG_TRY_STORAGE_TAKE, .proc/signal_take_obj) - RegisterSignal(parent, COMSIG_TRY_STORAGE_QUICK_EMPTY, .proc/signal_quick_empty) - RegisterSignal(parent, COMSIG_TRY_STORAGE_HIDE_FROM, .proc/signal_hide_attempt) - RegisterSignal(parent, COMSIG_TRY_STORAGE_HIDE_ALL, .proc/close_all) - RegisterSignal(parent, COMSIG_TRY_STORAGE_RETURN_INVENTORY, .proc/signal_return_inv) - - RegisterSignal(parent, COMSIG_TOPIC, .proc/topic_handle) - - RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/attackby) - - RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, .proc/on_attack_hand) - RegisterSignal(parent, COMSIG_ATOM_ATTACK_PAW, .proc/on_attack_hand) - RegisterSignal(parent, COMSIG_ATOM_EMP_ACT, .proc/emp_act) - RegisterSignal(parent, COMSIG_ATOM_ATTACK_GHOST, .proc/show_to_ghost) - RegisterSignal(parent, COMSIG_ATOM_ENTERED, .proc/refresh_mob_views) - RegisterSignal(parent, COMSIG_ATOM_EXITED, .proc/_remove_and_refresh) - RegisterSignal(parent, COMSIG_ATOM_CANREACH, .proc/canreach_react) - - RegisterSignal(parent, COMSIG_ITEM_PRE_ATTACK, .proc/preattack_intercept) - RegisterSignal(parent, COMSIG_ITEM_ATTACK_SELF, .proc/attack_self) - RegisterSignal(parent, COMSIG_ITEM_PICKUP, .proc/signal_on_pickup) - - RegisterSignal(parent, COMSIG_MOVABLE_POST_THROW, .proc/close_all) - RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/on_move) - - RegisterSignal(parent, COMSIG_CLICK_ALT, .proc/on_alt_click) - RegisterSignal(parent, COMSIG_MOUSEDROP_ONTO, .proc/mousedrop_onto) - RegisterSignal(parent, COMSIG_MOUSEDROPPED_ONTO, .proc/mousedrop_receive) - - update_actions() - -/datum/component/storage/Destroy() - close_all() - QDEL_NULL(boxes) - QDEL_NULL(closer) - LAZYCLEARLIST(is_using) - return ..() - -/datum/component/storage/PreTransfer() - update_actions() - -/datum/component/storage/proc/set_holdable(can_hold_list, cant_hold_list) - can_hold_description = generate_hold_desc(can_hold_list) - - if (can_hold_list != null) - can_hold = typecacheof(can_hold_list) - - if (cant_hold_list != null) - cant_hold = typecacheof(cant_hold_list) - -/datum/component/storage/proc/generate_hold_desc(can_hold_list) - var/list/desc = list() - - for(var/valid_type in can_hold_list) - var/obj/item/valid_item = valid_type - desc += "\a [initial(valid_item.name)]" - - return "\n\t[desc.Join("\n\t")]" - -/datum/component/storage/proc/update_actions() - QDEL_NULL(modeswitch_action) - if(!isitem(parent) || !allow_quick_gather) - return - var/obj/item/I = parent - modeswitch_action = new(I) - RegisterSignal(modeswitch_action, COMSIG_ACTION_TRIGGER, .proc/action_trigger) - if(I.obj_flags & IN_INVENTORY) - var/mob/M = I.loc - if(!istype(M)) - return - modeswitch_action.Grant(M) - -/datum/component/storage/proc/change_master(datum/component/storage/concrete/new_master) - if(new_master == src || (!isnull(new_master) && !istype(new_master))) - return FALSE - if(master) - master.on_slave_unlink(src) - master = new_master - if(master) - master.on_slave_link(src) - return TRUE - -/datum/component/storage/proc/master() - if(master == src) - return //infinite loops yo. - return master - -/datum/component/storage/proc/real_location() - var/datum/component/storage/concrete/master = master() - return master? master.real_location() : null - -/datum/component/storage/proc/canreach_react(datum/source, list/next) - var/datum/component/storage/concrete/master = master() - if(!master) - return - . = COMPONENT_BLOCK_REACH - next += master.parent - for(var/i in master.slaves) - var/datum/component/storage/slave = i - next += slave.parent - -/datum/component/storage/proc/on_move() - var/atom/A = parent - for(var/mob/living/L in can_see_contents()) - if(!L.CanReach(A)) - hide_from(L) - -/datum/component/storage/proc/attack_self(datum/source, mob/M) - if(locked) - to_chat(M, "[parent] seems to be locked!") - return FALSE - if((M.get_active_held_item() == parent) && allow_quick_empty) - quick_empty(M) - -/datum/component/storage/proc/preattack_intercept(datum/source, obj/O, mob/M, params) - if(!isitem(O) || !click_gather || SEND_SIGNAL(O, COMSIG_CONTAINS_STORAGE)) - return FALSE - . = COMPONENT_NO_ATTACK - if(locked) - to_chat(M, "[parent] seems to be locked!") - return FALSE - var/obj/item/I = O - if(collection_mode == COLLECT_ONE) - if(can_be_inserted(I, null, M)) - handle_item_insertion(I, null, M) - return - if(!isturf(I.loc)) - return - var/list/things = I.loc.contents.Copy() - if(collection_mode == COLLECT_SAME) - things = typecache_filter_list(things, typecacheof(I.type)) - var/len = length(things) - if(!len) - to_chat(M, "You failed to pick up anything with [parent]!") - return - var/datum/progressbar/progress = new(M, len, I.loc) - var/list/rejections = list() - while(do_after(M, 10, TRUE, parent, FALSE, CALLBACK(src, .proc/handle_mass_pickup, things, I.loc, rejections, progress))) - stoplag(1) - progress.end_progress() - to_chat(M, "You put everything you could [insert_preposition] [parent].") - -/datum/component/storage/proc/handle_mass_item_insertion(list/things, datum/component/storage/src_object, mob/user, datum/progressbar/progress) - var/atom/source_real_location = src_object.real_location() - for(var/obj/item/I in things) - things -= I - if(I.loc != source_real_location) - continue - if(user.active_storage != src_object) - if(I.on_found(user)) - break - if(can_be_inserted(I,FALSE,user)) - handle_item_insertion(I, TRUE, user) - if (TICK_CHECK) - progress.update(progress.goal - things.len) - return TRUE - - progress.update(progress.goal - things.len) - return FALSE - -/datum/component/storage/proc/handle_mass_pickup(list/things, atom/thing_loc, list/rejections, datum/progressbar/progress) - var/atom/real_location = real_location() - for(var/obj/item/I in things) - things -= I - if(I.loc != thing_loc) - continue - if(I.type in rejections) // To limit bag spamming: any given type only complains once - continue - if(!can_be_inserted(I, stop_messages = TRUE)) // Note can_be_inserted still makes noise when the answer is no - if(real_location.contents.len >= max_items) - break - rejections += I.type // therefore full bags are still a little spammy - continue - - handle_item_insertion(I, TRUE) //The TRUE stops the "You put the [parent] into [S]" insertion message from being displayed. - - if (TICK_CHECK) - progress.update(progress.goal - things.len) - return TRUE - - progress.update(progress.goal - things.len) - return FALSE - -/datum/component/storage/proc/quick_empty(mob/M) - var/atom/A = parent - if(!M.canUseStorage() || !A.Adjacent(M) || M.incapacitated()) - return - if(locked) - to_chat(M, "[parent] seems to be locked!") - return FALSE - A.add_fingerprint(M) - to_chat(M, "You start dumping out [parent].") - var/turf/T = get_turf(A) - var/list/things = contents() - var/datum/progressbar/progress = new(M, length(things), T) - while (do_after(M, 10, TRUE, T, FALSE, CALLBACK(src, .proc/mass_remove_from_storage, T, things, progress))) - stoplag(1) - progress.end_progress() - -/datum/component/storage/proc/mass_remove_from_storage(atom/target, list/things, datum/progressbar/progress, trigger_on_found = TRUE) - var/atom/real_location = real_location() - for(var/obj/item/I in things) - things -= I - if(I.loc != real_location) - continue - remove_from_storage(I, target) - I.pixel_x = rand(-10,10) - I.pixel_y = rand(-10,10) - if(trigger_on_found && I.on_found()) - return FALSE - if(TICK_CHECK) - progress.update(progress.goal - length(things)) - return TRUE - progress.update(progress.goal - length(things)) - return FALSE - -/datum/component/storage/proc/do_quick_empty(atom/_target) - if(!_target) - _target = get_turf(parent) - if(usr) - hide_from(usr) - var/list/contents = contents() - var/atom/real_location = real_location() - for(var/obj/item/I in contents) - if(I.loc != real_location) - continue - remove_from_storage(I, _target) - return TRUE - -/datum/component/storage/proc/set_locked(datum/source, new_state) - locked = new_state - if(locked) - close_all() - -/datum/component/storage/proc/_process_numerical_display() - . = list() - var/atom/real_location = real_location() - for(var/obj/item/I in real_location.contents) - if(QDELETED(I)) - continue - if(!.["[I.type]-[I.name]"]) - .["[I.type]-[I.name]"] = new /datum/numbered_display(I, 1) - else - var/datum/numbered_display/ND = .["[I.type]-[I.name]"] - ND.number++ - -//This proc determines the size of the inventory to be displayed. Please touch it only if you know what you're doing. -/datum/component/storage/proc/orient2hud() - var/atom/real_location = real_location() - var/adjusted_contents = real_location.contents.len - - //Numbered contents display - var/list/datum/numbered_display/numbered_contents - if(display_numerical_stacking) - numbered_contents = _process_numerical_display() - adjusted_contents = numbered_contents.len - - var/columns = clamp(max_items, 1, screen_max_columns) - var/rows = clamp(CEILING(adjusted_contents / columns, 1), 1, screen_max_rows) - standard_orient_objs(rows, columns, numbered_contents) - -//This proc draws out the inventory and places the items on it. It uses the standard position. -/datum/component/storage/proc/standard_orient_objs(rows, cols, list/obj/item/numerical_display_contents) - boxes.screen_loc = "[screen_start_x]:[screen_pixel_x],[screen_start_y]:[screen_pixel_y] to [screen_start_x+cols-1]:[screen_pixel_x],[screen_start_y+rows-1]:[screen_pixel_y]" - var/cx = screen_start_x - var/cy = screen_start_y - if(islist(numerical_display_contents)) - for(var/type in numerical_display_contents) - var/datum/numbered_display/ND = numerical_display_contents[type] - ND.sample_object.mouse_opacity = MOUSE_OPACITY_OPAQUE - ND.sample_object.screen_loc = "[cx]:[screen_pixel_x],[cy]:[screen_pixel_y]" - ND.sample_object.maptext = "[(ND.number > 1)? "[ND.number]" : ""]" - ND.sample_object.layer = ABOVE_HUD_LAYER - ND.sample_object.plane = ABOVE_HUD_PLANE - cx++ - if(cx - screen_start_x >= cols) - cx = screen_start_x - cy++ - if(cy - screen_start_y >= rows) - break - else - var/atom/real_location = real_location() - for(var/obj/O in real_location) - if(QDELETED(O)) - continue - O.mouse_opacity = MOUSE_OPACITY_OPAQUE //This is here so storage items that spawn with contents correctly have the "click around item to equip" - O.screen_loc = "[cx]:[screen_pixel_x],[cy]:[screen_pixel_y]" - O.maptext = "" - O.layer = ABOVE_HUD_LAYER - O.plane = ABOVE_HUD_PLANE - cx++ - if(cx - screen_start_x >= cols) - cx = screen_start_x - cy++ - if(cy - screen_start_y >= rows) - break - closer.screen_loc = "[screen_start_x + cols]:[screen_pixel_x],[screen_start_y]:[screen_pixel_y]" - -/datum/component/storage/proc/show_to(mob/M) - if(!M.client) - return FALSE - var/atom/real_location = real_location() - if(M.active_storage != src && (M.stat == CONSCIOUS)) - for(var/obj/item/I in real_location) - if(I.on_found(M)) - return FALSE - if(M.active_storage) - M.active_storage.hide_from(M) - orient2hud() - M.client.screen |= boxes - M.client.screen |= closer - M.client.screen |= real_location.contents - M.active_storage = src - LAZYOR(is_using, M) - return TRUE - -/datum/component/storage/proc/hide_from(mob/M) - if(!M.client) - return TRUE - var/atom/real_location = real_location() - M.client.screen -= boxes - M.client.screen -= closer - M.client.screen -= real_location.contents - if(M.active_storage == src) - M.active_storage = null - LAZYREMOVE(is_using, M) - return TRUE - -/datum/component/storage/proc/close(mob/M) - hide_from(M) - -/datum/component/storage/proc/close_all() - . = FALSE - for(var/mob/M in can_see_contents()) - close(M) - . = TRUE //returns TRUE if any mobs actually got a close(M) call - -/datum/component/storage/proc/emp_act(datum/source, severity) - if(emp_shielded) - return - var/datum/component/storage/concrete/master = master() - master.emp_act(source, severity) - -//This proc draws out the inventory and places the items on it. tx and ty are the upper left tile and mx, my are the bottm right. -//The numbers are calculated from the bottom-left The bottom-left slot being 1,1. -/datum/component/storage/proc/orient_objs(tx, ty, mx, my) - var/atom/real_location = real_location() - var/cx = tx - var/cy = ty - boxes.screen_loc = "[tx]:,[ty] to [mx],[my]" - for(var/obj/O in real_location) - if(QDELETED(O)) - continue - O.screen_loc = "[cx],[cy]" - O.layer = ABOVE_HUD_LAYER - O.plane = ABOVE_HUD_PLANE - cx++ - if(cx > mx) - cx = tx - cy-- - closer.screen_loc = "[mx+1],[my]" - -//Resets something that is being removed from storage. -/datum/component/storage/proc/_removal_reset(atom/movable/thing) - if(!istype(thing)) - return FALSE - var/datum/component/storage/concrete/master = master() - if(!istype(master)) - return FALSE - return master._removal_reset(thing) - -/datum/component/storage/proc/_remove_and_refresh(datum/source, atom/movable/thing) - _removal_reset(thing) - refresh_mob_views() - -//Call this proc to handle the removal of an item from the storage item. The item will be moved to the new_location target, if that is null it's being deleted -/datum/component/storage/proc/remove_from_storage(atom/movable/AM, atom/new_location) - if(!istype(AM)) - return FALSE - var/datum/component/storage/concrete/master = master() - if(!istype(master)) - return FALSE - return master.remove_from_storage(AM, new_location) - -/datum/component/storage/proc/refresh_mob_views() - var/list/seeing = can_see_contents() - for(var/i in seeing) - show_to(i) - return TRUE - -/datum/component/storage/proc/can_see_contents() - var/list/cansee = list() - for(var/mob/M in is_using) - if(M.active_storage == src && M.client) - cansee |= M - else - LAZYREMOVE(is_using, M) - return cansee - -//Tries to dump content -/datum/component/storage/proc/dump_content_at(atom/dest_object, mob/M) - var/atom/A = parent - var/atom/dump_destination = dest_object.get_dumping_location() - if(A.Adjacent(M) && dump_destination && M.Adjacent(dump_destination)) - if(locked) - to_chat(M, "[parent] seems to be locked!") - return FALSE - if(dump_destination.storage_contents_dump_act(src, M)) - playsound(A, "rustle", 50, TRUE, -5) - return TRUE - return FALSE - -//This proc is called when you want to place an item into the storage item. -/datum/component/storage/proc/attackby(datum/source, obj/item/I, mob/M, params) - if(istype(I, /obj/item/hand_labeler)) - var/obj/item/hand_labeler/labeler = I - if(labeler.mode) - return FALSE - . = TRUE //no afterattack - if(iscyborg(M)) - return - if(!can_be_inserted(I, FALSE, M)) - var/atom/real_location = real_location() - if(real_location.contents.len >= max_items) //don't use items on the backpack if they don't fit - return TRUE - return FALSE - handle_item_insertion(I, FALSE, M) - -/datum/component/storage/proc/return_inv(recursive) - var/list/ret = list() - ret |= contents() - if(recursive) - for(var/i in ret.Copy()) - var/atom/A = i - SEND_SIGNAL(A, COMSIG_TRY_STORAGE_RETURN_INVENTORY, ret, TRUE) - return ret - -/datum/component/storage/proc/contents() //ONLY USE IF YOU NEED TO COPY CONTENTS OF REAL LOCATION, COPYING IS NOT AS FAST AS DIRECT ACCESS! - var/atom/real_location = real_location() - return real_location.contents.Copy() - -//Abuses the fact that lists are just references, or something like that. -/datum/component/storage/proc/signal_return_inv(datum/source, list/interface, recursive = TRUE) - if(!islist(interface)) - return FALSE - interface |= return_inv(recursive) - return TRUE - -/datum/component/storage/proc/topic_handle(datum/source, user, href_list) - if(href_list["show_valid_pocket_items"]) - handle_show_valid_items(source, user) - -/datum/component/storage/proc/handle_show_valid_items(datum/source, user) - to_chat(user, "[source] can hold: [can_hold_description]") - -/datum/component/storage/proc/mousedrop_onto(datum/source, atom/over_object, mob/M) - set waitfor = FALSE - . = COMPONENT_NO_MOUSEDROP - if(!ismob(M)) - return - if(!over_object) - return - if(ismecha(M.loc)) // stops inventory actions in a mech - return - if(M.incapacitated() || !M.canUseStorage()) - return - var/atom/A = parent - A.add_fingerprint(M) - // this must come before the screen objects only block, dunno why it wasn't before - if(over_object == M) - user_show_to_mob(M) - if(!istype(over_object, /obj/screen)) - dump_content_at(over_object, M) - return - if(A.loc != M) - return - playsound(A, "rustle", 50, TRUE, -5) - if(istype(over_object, /obj/screen/inventory/hand)) - var/obj/screen/inventory/hand/H = over_object - M.putItemFromInventoryInHandIfPossible(A, H.held_index) - return - A.add_fingerprint(M) - -/datum/component/storage/proc/user_show_to_mob(mob/M, force = FALSE) - var/atom/A = parent - if(!istype(M)) - return FALSE - A.add_fingerprint(M) - if(locked && !force) - to_chat(M, "[parent] seems to be locked!") - return FALSE - if(force || M.CanReach(parent, view_only = TRUE)) - show_to(M) - -/datum/component/storage/proc/mousedrop_receive(datum/source, atom/movable/O, mob/M) - if(isitem(O)) - var/obj/item/I = O - if(iscarbon(M) || isdrone(M)) - var/mob/living/L = M - if(!L.incapacitated() && I == L.get_active_held_item()) - if(!SEND_SIGNAL(I, COMSIG_CONTAINS_STORAGE) && can_be_inserted(I, FALSE)) //If it has storage it should be trying to dump, not insert. - handle_item_insertion(I, FALSE, L) - -//This proc return 1 if the item can be picked up and 0 if it can't. -//Set the stop_messages to stop it from printing messages -/datum/component/storage/proc/can_be_inserted(obj/item/I, stop_messages = FALSE, mob/M) - if(!istype(I) || (I.item_flags & ABSTRACT)) - return FALSE //Not an item - if(I == parent) - return FALSE //no paradoxes for you - var/atom/real_location = real_location() - var/atom/host = parent - if(real_location == I.loc) - return FALSE //Means the item is already in the storage item - if(locked) - if(M && !stop_messages) - host.add_fingerprint(M) - to_chat(M, "[host] seems to be locked!") - return FALSE - if(real_location.contents.len >= max_items) - if(!stop_messages) - to_chat(M, "[host] is full, make some space!") - return FALSE //Storage item is full - if(length(can_hold)) - if(!is_type_in_typecache(I, can_hold)) - if(!stop_messages) - to_chat(M, "[host] cannot hold [I]!") - return FALSE - if(is_type_in_typecache(I, cant_hold) || HAS_TRAIT(I, TRAIT_NO_STORAGE_INSERT)) //Items which this container can't hold. - if(!stop_messages) - to_chat(M, "[host] cannot hold [I]!") - return FALSE - if(I.w_class > max_w_class && !is_type_in_typecache(I, exception_hold)) - if(!stop_messages) - to_chat(M, "[I] is too big for [host]!") - return FALSE - var/datum/component/storage/biggerfish = real_location.loc.GetComponent(/datum/component/storage) - if(biggerfish && biggerfish.max_w_class < max_w_class)//return false if we are inside of another container, and that container has a smaller max_w_class than us (like if we're a bag in a box) - if(!stop_messages) - to_chat(M, "[I] can't fit in [host] while [real_location.loc] is in the way!") - return FALSE - var/sum_w_class = I.w_class - for(var/obj/item/_I in real_location) - sum_w_class += _I.w_class //Adds up the combined w_classes which will be in the storage item if the item is added to it. - if(sum_w_class > max_combined_w_class) - if(!stop_messages) - to_chat(M, "[I] won't fit in [host], make some space!") - return FALSE - if(isitem(host)) - var/obj/item/IP = host - var/datum/component/storage/STR_I = I.GetComponent(/datum/component/storage) - if((I.w_class >= IP.w_class) && STR_I && !allow_big_nesting) - if(!stop_messages) - to_chat(M, "[IP] cannot hold [I] as it's a storage item of the same size!") - return FALSE //To prevent the stacking of same sized storage items. - if(HAS_TRAIT(I, TRAIT_NODROP)) //SHOULD be handled in unEquip, but better safe than sorry. - if(!stop_messages) - to_chat(M, "\the [I] is stuck to your hand, you can't put it in \the [host]!") - return FALSE - var/datum/component/storage/concrete/master = master() - if(!istype(master)) - return FALSE - return master.slave_can_insert_object(src, I, stop_messages, M) - -/datum/component/storage/proc/_insert_physical_item(obj/item/I, override = FALSE) - return FALSE - -//This proc handles items being inserted. It does not perform any checks of whether an item can or can't be inserted. That's done by can_be_inserted() -//The prevent_warning parameter will stop the insertion message from being displayed. It is intended for cases where you are inserting multiple items at once, -//such as when picking up all the items on a tile with one click. -/datum/component/storage/proc/handle_item_insertion(obj/item/I, prevent_warning = FALSE, mob/M, datum/component/storage/remote) - var/atom/parent = src.parent - var/datum/component/storage/concrete/master = master() - if(!istype(master)) - return FALSE - if(silent) - prevent_warning = TRUE - if(M) - parent.add_fingerprint(M) - . = master.handle_item_insertion_from_slave(src, I, prevent_warning, M) - -/datum/component/storage/proc/mob_item_insertion_feedback(mob/user, mob/M, obj/item/I, override = FALSE) - if(silent && !override) - return - if(rustle_sound) - playsound(parent, "rustle", 50, TRUE, -5) - for(var/mob/viewing in viewers(user, null)) - if(M == viewing) - to_chat(usr, "You put [I] [insert_preposition]to [parent].") - else if(in_range(M, viewing)) //If someone is standing close enough, they can tell what it is... - viewing.show_message("[M] puts [I] [insert_preposition]to [parent].", MSG_VISUAL) - else if(I && I.w_class >= 3) //Otherwise they can only see large or normal items from a distance... - viewing.show_message("[M] puts [I] [insert_preposition]to [parent].", MSG_VISUAL) - -/datum/component/storage/proc/update_icon() - if(isobj(parent)) - var/obj/O = parent - O.update_icon() - -/datum/component/storage/proc/signal_insertion_attempt(datum/source, obj/item/I, mob/M, silent = FALSE, force = FALSE) - if((!force && !can_be_inserted(I, TRUE, M)) || (I == parent)) - return FALSE - return handle_item_insertion(I, silent, M) - -/datum/component/storage/proc/signal_can_insert(datum/source, obj/item/I, mob/M, silent = FALSE) - return can_be_inserted(I, silent, M) - -/datum/component/storage/proc/show_to_ghost(datum/source, mob/dead/observer/M) - return user_show_to_mob(M, TRUE) - -/datum/component/storage/proc/signal_show_attempt(datum/source, mob/showto, force = FALSE) - return user_show_to_mob(showto, force) - -/datum/component/storage/proc/on_check() - return TRUE - -/datum/component/storage/proc/check_locked() - return locked - -/datum/component/storage/proc/signal_take_type(datum/source, type, atom/destination, amount = INFINITY, check_adjacent = FALSE, force = FALSE, mob/user, list/inserted) - if(!force) - if(check_adjacent) - if(!user || !user.CanReach(destination) || !user.CanReach(parent)) - return FALSE - var/list/taking = typecache_filter_list(contents(), typecacheof(type)) - if(taking.len > amount) - taking.len = amount - if(inserted) //duplicated code for performance, don't bother checking retval/checking for list every item. - for(var/i in taking) - if(remove_from_storage(i, destination)) - inserted |= i - else - for(var/i in taking) - remove_from_storage(i, destination) - return TRUE - -/datum/component/storage/proc/remaining_space_items() - var/atom/real_location = real_location() - return max(0, max_items - real_location.contents.len) - -/datum/component/storage/proc/signal_fill_type(datum/source, type, amount = 20, force = FALSE) - var/atom/real_location = real_location() - if(!force) - amount = min(remaining_space_items(), amount) - for(var/i in 1 to amount) - if(!handle_item_insertion(new type(real_location), TRUE)) - return i > 1 //return TRUE only if at least one insertion has been successful. - if(CHECK_TICK) - if(QDELETED(src)) - return TRUE - return TRUE - -/datum/component/storage/proc/on_attack_hand(datum/source, mob/user) - var/atom/A = parent - if(!attack_hand_interact) - return - if(user.active_storage == src && A.loc == user) //if you're already looking inside the storage item - user.active_storage.close(user) - close(user) - . = COMPONENT_NO_ATTACK_HAND - return - - if(rustle_sound) - playsound(A, "rustle", 50, TRUE, -5) - - if(ishuman(user)) - var/mob/living/carbon/human/H = user - if(H.l_store == A && !H.get_active_held_item()) //Prevents opening if it's in a pocket. - . = COMPONENT_NO_ATTACK_HAND - H.put_in_hands(A) - H.l_store = null - return - if(H.r_store == A && !H.get_active_held_item()) - . = COMPONENT_NO_ATTACK_HAND - H.put_in_hands(A) - H.r_store = null - return - - if(A.loc == user) - . = COMPONENT_NO_ATTACK_HAND - if(locked) - to_chat(user, "[parent] seems to be locked!") - else - show_to(user) - -/datum/component/storage/proc/signal_on_pickup(datum/source, mob/user) - update_actions() - for(var/mob/M in can_see_contents() - user) - close(M) - -/datum/component/storage/proc/signal_take_obj(datum/source, atom/movable/AM, new_loc, force = FALSE) - if(!(AM in real_location())) - return FALSE - return remove_from_storage(AM, new_loc) - -/datum/component/storage/proc/signal_quick_empty(datum/source, atom/loctarget) - return do_quick_empty(loctarget) - -/datum/component/storage/proc/signal_hide_attempt(datum/source, mob/target) - return hide_from(target) - -/datum/component/storage/proc/on_alt_click(datum/source, mob/user) - if(!isliving(user) || !user.CanReach(parent) || user.incapacitated()) - return - if(locked) - to_chat(user, "[parent] seems to be locked!") - return - - var/atom/A = parent - if(!quickdraw) - A.add_fingerprint(user) - user_show_to_mob(user) - playsound(A, "rustle", 50, TRUE, -5) - return - - var/obj/item/I = locate() in real_location() - if(!I) - return - A.add_fingerprint(user) - remove_from_storage(I, get_turf(user)) - if(!user.put_in_hands(I)) - to_chat(user, "You fumble for [I] and it falls on the floor.") - return - user.visible_message("[user] draws [I] from [parent]!", "You draw [I] from [parent].") - -/datum/component/storage/proc/action_trigger(datum/signal_source, datum/action/source) - gather_mode_switch(source.owner) - return COMPONENT_ACTION_BLOCK_TRIGGER - -/datum/component/storage/proc/gather_mode_switch(mob/user) - collection_mode = (collection_mode+1)%3 - switch(collection_mode) - if(COLLECT_SAME) - to_chat(user, "[parent] now picks up all items of a single type at once.") - if(COLLECT_EVERYTHING) - to_chat(user, "[parent] now picks up all items in a tile at once.") - if(COLLECT_ONE) - to_chat(user, "[parent] now picks up one item at a time.") +#define COLLECT_ONE 0 +#define COLLECT_EVERYTHING 1 +#define COLLECT_SAME 2 + +#define DROP_NOTHING 0 +#define DROP_AT_PARENT 1 +#define DROP_AT_LOCATION 2 + +// External storage-related logic: +// /mob/proc/ClickOn() in /_onclick/click.dm - clicking items in storages +// /mob/living/Move() in /modules/mob/living/living.dm - hiding storage boxes on mob movement + +/datum/component/storage + dupe_mode = COMPONENT_DUPE_UNIQUE + var/datum/component/storage/concrete/master //If not null, all actions act on master and this is just an access point. + + var/list/can_hold //if this is set, only items, and their children, will fit + var/list/cant_hold //if this is set, items, and their children, won't fit + var/list/exception_hold //if set, these items will be the exception to the max size of object that can fit. + + var/can_hold_description + + var/list/mob/is_using //lazy list of mobs looking at the contents of this storage. + + var/locked = FALSE //when locked nothing can see inside or use it. + + var/max_w_class = WEIGHT_CLASS_SMALL //max size of objects that will fit. + var/max_combined_w_class = 14 //max combined sizes of objects that will fit. + var/max_items = 7 //max number of objects that will fit. + + var/emp_shielded = FALSE + + var/silent = FALSE //whether this makes a message when things are put in. + var/click_gather = FALSE //whether this can be clicked on items to pick it up rather than the other way around. + var/rustle_sound = TRUE //play rustle sound on interact. + var/allow_quick_empty = FALSE //allow empty verb which allows dumping on the floor of everything inside quickly. + var/allow_quick_gather = FALSE //allow toggle mob verb which toggles collecting all items from a tile. + + var/collection_mode = COLLECT_EVERYTHING + + var/insert_preposition = "in" //you put things "in" a bag, but "on" a tray. + + var/display_numerical_stacking = FALSE //stack things of the same type and show as a single object with a number. + + var/obj/screen/storage/boxes //storage display object + var/obj/screen/close/closer //close button object + + var/allow_big_nesting = FALSE //allow storage objects of the same or greater size. + + var/attack_hand_interact = TRUE //interact on attack hand. + var/quickdraw = FALSE //altclick interact + + var/datum/action/item_action/storage_gather_mode/modeswitch_action + + //Screen variables: Do not mess with these vars unless you know what you're doing. They're not defines so storage that isn't in the same location can be supported in the future. + var/screen_max_columns = 7 //These two determine maximum screen sizes. + var/screen_max_rows = INFINITY + var/screen_pixel_x = 16 //These two are pixel values for screen loc of boxes and closer + var/screen_pixel_y = 16 + var/screen_start_x = 4 //These two are where the storage starts being rendered, screen_loc wise. + var/screen_start_y = 2 + //End + +/datum/component/storage/Initialize(datum/component/storage/concrete/master) + if(!isatom(parent)) + return COMPONENT_INCOMPATIBLE + if(master) + change_master(master) + boxes = new(null, src) + closer = new(null, src) + orient2hud() + + RegisterSignal(parent, COMSIG_CONTAINS_STORAGE, .proc/on_check) + RegisterSignal(parent, COMSIG_IS_STORAGE_LOCKED, .proc/check_locked) + RegisterSignal(parent, COMSIG_TRY_STORAGE_SHOW, .proc/signal_show_attempt) + RegisterSignal(parent, COMSIG_TRY_STORAGE_INSERT, .proc/signal_insertion_attempt) + RegisterSignal(parent, COMSIG_TRY_STORAGE_CAN_INSERT, .proc/signal_can_insert) + RegisterSignal(parent, COMSIG_TRY_STORAGE_TAKE_TYPE, .proc/signal_take_type) + RegisterSignal(parent, COMSIG_TRY_STORAGE_FILL_TYPE, .proc/signal_fill_type) + RegisterSignal(parent, COMSIG_TRY_STORAGE_SET_LOCKSTATE, .proc/set_locked) + RegisterSignal(parent, COMSIG_TRY_STORAGE_TAKE, .proc/signal_take_obj) + RegisterSignal(parent, COMSIG_TRY_STORAGE_QUICK_EMPTY, .proc/signal_quick_empty) + RegisterSignal(parent, COMSIG_TRY_STORAGE_HIDE_FROM, .proc/signal_hide_attempt) + RegisterSignal(parent, COMSIG_TRY_STORAGE_HIDE_ALL, .proc/close_all) + RegisterSignal(parent, COMSIG_TRY_STORAGE_RETURN_INVENTORY, .proc/signal_return_inv) + + RegisterSignal(parent, COMSIG_TOPIC, .proc/topic_handle) + + RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/attackby) + + RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, .proc/on_attack_hand) + RegisterSignal(parent, COMSIG_ATOM_ATTACK_PAW, .proc/on_attack_hand) + RegisterSignal(parent, COMSIG_ATOM_EMP_ACT, .proc/emp_act) + RegisterSignal(parent, COMSIG_ATOM_ATTACK_GHOST, .proc/show_to_ghost) + RegisterSignal(parent, COMSIG_ATOM_ENTERED, .proc/refresh_mob_views) + RegisterSignal(parent, COMSIG_ATOM_EXITED, .proc/_remove_and_refresh) + RegisterSignal(parent, COMSIG_ATOM_CANREACH, .proc/canreach_react) + + RegisterSignal(parent, COMSIG_ITEM_PRE_ATTACK, .proc/preattack_intercept) + RegisterSignal(parent, COMSIG_ITEM_ATTACK_SELF, .proc/attack_self) + RegisterSignal(parent, COMSIG_ITEM_PICKUP, .proc/signal_on_pickup) + + RegisterSignal(parent, COMSIG_MOVABLE_POST_THROW, .proc/close_all) + RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/on_move) + + RegisterSignal(parent, COMSIG_CLICK_ALT, .proc/on_alt_click) + RegisterSignal(parent, COMSIG_MOUSEDROP_ONTO, .proc/mousedrop_onto) + RegisterSignal(parent, COMSIG_MOUSEDROPPED_ONTO, .proc/mousedrop_receive) + + update_actions() + +/datum/component/storage/Destroy() + close_all() + QDEL_NULL(boxes) + QDEL_NULL(closer) + LAZYCLEARLIST(is_using) + return ..() + +/datum/component/storage/PreTransfer() + update_actions() + +/datum/component/storage/proc/set_holdable(can_hold_list, cant_hold_list) + can_hold_description = generate_hold_desc(can_hold_list) + + if (can_hold_list != null) + can_hold = typecacheof(can_hold_list) + + if (cant_hold_list != null) + cant_hold = typecacheof(cant_hold_list) + +/datum/component/storage/proc/generate_hold_desc(can_hold_list) + var/list/desc = list() + + for(var/valid_type in can_hold_list) + var/obj/item/valid_item = valid_type + desc += "\a [initial(valid_item.name)]" + + return "\n\t[desc.Join("\n\t")]" + +/datum/component/storage/proc/update_actions() + QDEL_NULL(modeswitch_action) + if(!isitem(parent) || !allow_quick_gather) + return + var/obj/item/I = parent + modeswitch_action = new(I) + RegisterSignal(modeswitch_action, COMSIG_ACTION_TRIGGER, .proc/action_trigger) + if(I.obj_flags & IN_INVENTORY) + var/mob/M = I.loc + if(!istype(M)) + return + modeswitch_action.Grant(M) + +/datum/component/storage/proc/change_master(datum/component/storage/concrete/new_master) + if(new_master == src || (!isnull(new_master) && !istype(new_master))) + return FALSE + if(master) + master.on_slave_unlink(src) + master = new_master + if(master) + master.on_slave_link(src) + return TRUE + +/datum/component/storage/proc/master() + if(master == src) + return //infinite loops yo. + return master + +/datum/component/storage/proc/real_location() + var/datum/component/storage/concrete/master = master() + return master? master.real_location() : null + +/datum/component/storage/proc/canreach_react(datum/source, list/next) + var/datum/component/storage/concrete/master = master() + if(!master) + return + . = COMPONENT_BLOCK_REACH + next += master.parent + for(var/i in master.slaves) + var/datum/component/storage/slave = i + next += slave.parent + +/datum/component/storage/proc/on_move() + var/atom/A = parent + for(var/mob/living/L in can_see_contents()) + if(!L.CanReach(A)) + hide_from(L) + +/datum/component/storage/proc/attack_self(datum/source, mob/M) + if(locked) + to_chat(M, "[parent] seems to be locked!") + return FALSE + if((M.get_active_held_item() == parent) && allow_quick_empty) + quick_empty(M) + +/datum/component/storage/proc/preattack_intercept(datum/source, obj/O, mob/M, params) + if(!isitem(O) || !click_gather || SEND_SIGNAL(O, COMSIG_CONTAINS_STORAGE)) + return FALSE + . = COMPONENT_NO_ATTACK + if(locked) + to_chat(M, "[parent] seems to be locked!") + return FALSE + var/obj/item/I = O + if(collection_mode == COLLECT_ONE) + if(can_be_inserted(I, null, M)) + handle_item_insertion(I, null, M) + return + if(!isturf(I.loc)) + return + var/list/things = I.loc.contents.Copy() + if(collection_mode == COLLECT_SAME) + things = typecache_filter_list(things, typecacheof(I.type)) + var/len = length(things) + if(!len) + to_chat(M, "You failed to pick up anything with [parent]!") + return + var/datum/progressbar/progress = new(M, len, I.loc) + var/list/rejections = list() + while(do_after(M, 10, TRUE, parent, FALSE, CALLBACK(src, .proc/handle_mass_pickup, things, I.loc, rejections, progress))) + stoplag(1) + progress.end_progress() + to_chat(M, "You put everything you could [insert_preposition] [parent].") + +/datum/component/storage/proc/handle_mass_item_insertion(list/things, datum/component/storage/src_object, mob/user, datum/progressbar/progress) + var/atom/source_real_location = src_object.real_location() + for(var/obj/item/I in things) + things -= I + if(I.loc != source_real_location) + continue + if(user.active_storage != src_object) + if(I.on_found(user)) + break + if(can_be_inserted(I,FALSE,user)) + handle_item_insertion(I, TRUE, user) + if (TICK_CHECK) + progress.update(progress.goal - things.len) + return TRUE + + progress.update(progress.goal - things.len) + return FALSE + +/datum/component/storage/proc/handle_mass_pickup(list/things, atom/thing_loc, list/rejections, datum/progressbar/progress) + var/atom/real_location = real_location() + for(var/obj/item/I in things) + things -= I + if(I.loc != thing_loc) + continue + if(I.type in rejections) // To limit bag spamming: any given type only complains once + continue + if(!can_be_inserted(I, stop_messages = TRUE)) // Note can_be_inserted still makes noise when the answer is no + if(real_location.contents.len >= max_items) + break + rejections += I.type // therefore full bags are still a little spammy + continue + + handle_item_insertion(I, TRUE) //The TRUE stops the "You put the [parent] into [S]" insertion message from being displayed. + + if (TICK_CHECK) + progress.update(progress.goal - things.len) + return TRUE + + progress.update(progress.goal - things.len) + return FALSE + +/datum/component/storage/proc/quick_empty(mob/M) + var/atom/A = parent + if(!M.canUseStorage() || !A.Adjacent(M) || M.incapacitated()) + return + if(locked) + to_chat(M, "[parent] seems to be locked!") + return FALSE + A.add_fingerprint(M) + to_chat(M, "You start dumping out [parent].") + var/turf/T = get_turf(A) + var/list/things = contents() + var/datum/progressbar/progress = new(M, length(things), T) + while (do_after(M, 10, TRUE, T, FALSE, CALLBACK(src, .proc/mass_remove_from_storage, T, things, progress))) + stoplag(1) + progress.end_progress() + +/datum/component/storage/proc/mass_remove_from_storage(atom/target, list/things, datum/progressbar/progress, trigger_on_found = TRUE) + var/atom/real_location = real_location() + for(var/obj/item/I in things) + things -= I + if(I.loc != real_location) + continue + remove_from_storage(I, target) + I.pixel_x = rand(-10,10) + I.pixel_y = rand(-10,10) + if(trigger_on_found && I.on_found()) + return FALSE + if(TICK_CHECK) + progress.update(progress.goal - length(things)) + return TRUE + progress.update(progress.goal - length(things)) + return FALSE + +/datum/component/storage/proc/do_quick_empty(atom/_target) + if(!_target) + _target = get_turf(parent) + if(usr) + hide_from(usr) + var/list/contents = contents() + var/atom/real_location = real_location() + for(var/obj/item/I in contents) + if(I.loc != real_location) + continue + remove_from_storage(I, _target) + return TRUE + +/datum/component/storage/proc/set_locked(datum/source, new_state) + locked = new_state + if(locked) + close_all() + +/datum/component/storage/proc/_process_numerical_display() + . = list() + var/atom/real_location = real_location() + for(var/obj/item/I in real_location.contents) + if(QDELETED(I)) + continue + if(!.["[I.type]-[I.name]"]) + .["[I.type]-[I.name]"] = new /datum/numbered_display(I, 1) + else + var/datum/numbered_display/ND = .["[I.type]-[I.name]"] + ND.number++ + +//This proc determines the size of the inventory to be displayed. Please touch it only if you know what you're doing. +/datum/component/storage/proc/orient2hud() + var/atom/real_location = real_location() + var/adjusted_contents = real_location.contents.len + + //Numbered contents display + var/list/datum/numbered_display/numbered_contents + if(display_numerical_stacking) + numbered_contents = _process_numerical_display() + adjusted_contents = numbered_contents.len + + var/columns = clamp(max_items, 1, screen_max_columns) + var/rows = clamp(CEILING(adjusted_contents / columns, 1), 1, screen_max_rows) + standard_orient_objs(rows, columns, numbered_contents) + +//This proc draws out the inventory and places the items on it. It uses the standard position. +/datum/component/storage/proc/standard_orient_objs(rows, cols, list/obj/item/numerical_display_contents) + boxes.screen_loc = "[screen_start_x]:[screen_pixel_x],[screen_start_y]:[screen_pixel_y] to [screen_start_x+cols-1]:[screen_pixel_x],[screen_start_y+rows-1]:[screen_pixel_y]" + var/cx = screen_start_x + var/cy = screen_start_y + if(islist(numerical_display_contents)) + for(var/type in numerical_display_contents) + var/datum/numbered_display/ND = numerical_display_contents[type] + ND.sample_object.mouse_opacity = MOUSE_OPACITY_OPAQUE + ND.sample_object.screen_loc = "[cx]:[screen_pixel_x],[cy]:[screen_pixel_y]" + ND.sample_object.maptext = "[(ND.number > 1)? "[ND.number]" : ""]" + ND.sample_object.layer = ABOVE_HUD_LAYER + ND.sample_object.plane = ABOVE_HUD_PLANE + cx++ + if(cx - screen_start_x >= cols) + cx = screen_start_x + cy++ + if(cy - screen_start_y >= rows) + break + else + var/atom/real_location = real_location() + for(var/obj/O in real_location) + if(QDELETED(O)) + continue + O.mouse_opacity = MOUSE_OPACITY_OPAQUE //This is here so storage items that spawn with contents correctly have the "click around item to equip" + O.screen_loc = "[cx]:[screen_pixel_x],[cy]:[screen_pixel_y]" + O.maptext = "" + O.layer = ABOVE_HUD_LAYER + O.plane = ABOVE_HUD_PLANE + cx++ + if(cx - screen_start_x >= cols) + cx = screen_start_x + cy++ + if(cy - screen_start_y >= rows) + break + closer.screen_loc = "[screen_start_x + cols]:[screen_pixel_x],[screen_start_y]:[screen_pixel_y]" + +/datum/component/storage/proc/show_to(mob/M) + if(!M.client) + return FALSE + var/atom/real_location = real_location() + if(M.active_storage != src && (M.stat == CONSCIOUS)) + for(var/obj/item/I in real_location) + if(I.on_found(M)) + return FALSE + if(M.active_storage) + M.active_storage.hide_from(M) + orient2hud() + M.client.screen |= boxes + M.client.screen |= closer + M.client.screen |= real_location.contents + M.active_storage = src + LAZYOR(is_using, M) + return TRUE + +/datum/component/storage/proc/hide_from(mob/M) + if(!M.client) + return TRUE + var/atom/real_location = real_location() + M.client.screen -= boxes + M.client.screen -= closer + M.client.screen -= real_location.contents + if(M.active_storage == src) + M.active_storage = null + LAZYREMOVE(is_using, M) + return TRUE + +/datum/component/storage/proc/close(mob/M) + hide_from(M) + +/datum/component/storage/proc/close_all() + . = FALSE + for(var/mob/M in can_see_contents()) + close(M) + . = TRUE //returns TRUE if any mobs actually got a close(M) call + +/datum/component/storage/proc/emp_act(datum/source, severity) + if(emp_shielded) + return + var/datum/component/storage/concrete/master = master() + master.emp_act(source, severity) + +//This proc draws out the inventory and places the items on it. tx and ty are the upper left tile and mx, my are the bottm right. +//The numbers are calculated from the bottom-left The bottom-left slot being 1,1. +/datum/component/storage/proc/orient_objs(tx, ty, mx, my) + var/atom/real_location = real_location() + var/cx = tx + var/cy = ty + boxes.screen_loc = "[tx]:,[ty] to [mx],[my]" + for(var/obj/O in real_location) + if(QDELETED(O)) + continue + O.screen_loc = "[cx],[cy]" + O.layer = ABOVE_HUD_LAYER + O.plane = ABOVE_HUD_PLANE + cx++ + if(cx > mx) + cx = tx + cy-- + closer.screen_loc = "[mx+1],[my]" + +//Resets something that is being removed from storage. +/datum/component/storage/proc/_removal_reset(atom/movable/thing) + if(!istype(thing)) + return FALSE + var/datum/component/storage/concrete/master = master() + if(!istype(master)) + return FALSE + return master._removal_reset(thing) + +/datum/component/storage/proc/_remove_and_refresh(datum/source, atom/movable/thing) + _removal_reset(thing) + refresh_mob_views() + +//Call this proc to handle the removal of an item from the storage item. The item will be moved to the new_location target, if that is null it's being deleted +/datum/component/storage/proc/remove_from_storage(atom/movable/AM, atom/new_location) + if(!istype(AM)) + return FALSE + var/datum/component/storage/concrete/master = master() + if(!istype(master)) + return FALSE + return master.remove_from_storage(AM, new_location) + +/datum/component/storage/proc/refresh_mob_views() + var/list/seeing = can_see_contents() + for(var/i in seeing) + show_to(i) + return TRUE + +/datum/component/storage/proc/can_see_contents() + var/list/cansee = list() + for(var/mob/M in is_using) + if(M.active_storage == src && M.client) + cansee |= M + else + LAZYREMOVE(is_using, M) + return cansee + +//Tries to dump content +/datum/component/storage/proc/dump_content_at(atom/dest_object, mob/M) + var/atom/A = parent + var/atom/dump_destination = dest_object.get_dumping_location() + if(A.Adjacent(M) && dump_destination && M.Adjacent(dump_destination)) + if(locked) + to_chat(M, "[parent] seems to be locked!") + return FALSE + if(dump_destination.storage_contents_dump_act(src, M)) + playsound(A, "rustle", 50, TRUE, -5) + return TRUE + return FALSE + +//This proc is called when you want to place an item into the storage item. +/datum/component/storage/proc/attackby(datum/source, obj/item/I, mob/M, params) + if(istype(I, /obj/item/hand_labeler)) + var/obj/item/hand_labeler/labeler = I + if(labeler.mode) + return FALSE + . = TRUE //no afterattack + if(iscyborg(M)) + return + if(!can_be_inserted(I, FALSE, M)) + var/atom/real_location = real_location() + if(real_location.contents.len >= max_items) //don't use items on the backpack if they don't fit + return TRUE + return FALSE + handle_item_insertion(I, FALSE, M) + +/datum/component/storage/proc/return_inv(recursive) + var/list/ret = list() + ret |= contents() + if(recursive) + for(var/i in ret.Copy()) + var/atom/A = i + SEND_SIGNAL(A, COMSIG_TRY_STORAGE_RETURN_INVENTORY, ret, TRUE) + return ret + +/datum/component/storage/proc/contents() //ONLY USE IF YOU NEED TO COPY CONTENTS OF REAL LOCATION, COPYING IS NOT AS FAST AS DIRECT ACCESS! + var/atom/real_location = real_location() + return real_location.contents.Copy() + +//Abuses the fact that lists are just references, or something like that. +/datum/component/storage/proc/signal_return_inv(datum/source, list/interface, recursive = TRUE) + if(!islist(interface)) + return FALSE + interface |= return_inv(recursive) + return TRUE + +/datum/component/storage/proc/topic_handle(datum/source, user, href_list) + if(href_list["show_valid_pocket_items"]) + handle_show_valid_items(source, user) + +/datum/component/storage/proc/handle_show_valid_items(datum/source, user) + to_chat(user, "[source] can hold: [can_hold_description]") + +/datum/component/storage/proc/mousedrop_onto(datum/source, atom/over_object, mob/M) + set waitfor = FALSE + . = COMPONENT_NO_MOUSEDROP + if(!ismob(M)) + return + if(!over_object) + return + if(ismecha(M.loc)) // stops inventory actions in a mech + return + if(M.incapacitated() || !M.canUseStorage()) + return + var/atom/A = parent + A.add_fingerprint(M) + // this must come before the screen objects only block, dunno why it wasn't before + if(over_object == M) + user_show_to_mob(M) + if(!istype(over_object, /obj/screen)) + dump_content_at(over_object, M) + return + if(A.loc != M) + return + playsound(A, "rustle", 50, TRUE, -5) + if(istype(over_object, /obj/screen/inventory/hand)) + var/obj/screen/inventory/hand/H = over_object + M.putItemFromInventoryInHandIfPossible(A, H.held_index) + return + A.add_fingerprint(M) + +/datum/component/storage/proc/user_show_to_mob(mob/M, force = FALSE) + var/atom/A = parent + if(!istype(M)) + return FALSE + A.add_fingerprint(M) + if(locked && !force) + to_chat(M, "[parent] seems to be locked!") + return FALSE + if(force || M.CanReach(parent, view_only = TRUE)) + show_to(M) + +/datum/component/storage/proc/mousedrop_receive(datum/source, atom/movable/O, mob/M) + if(isitem(O)) + var/obj/item/I = O + if(iscarbon(M) || isdrone(M)) + var/mob/living/L = M + if(!L.incapacitated() && I == L.get_active_held_item()) + if(!SEND_SIGNAL(I, COMSIG_CONTAINS_STORAGE) && can_be_inserted(I, FALSE)) //If it has storage it should be trying to dump, not insert. + handle_item_insertion(I, FALSE, L) + +//This proc return 1 if the item can be picked up and 0 if it can't. +//Set the stop_messages to stop it from printing messages +/datum/component/storage/proc/can_be_inserted(obj/item/I, stop_messages = FALSE, mob/M) + if(!istype(I) || (I.item_flags & ABSTRACT)) + return FALSE //Not an item + if(I == parent) + return FALSE //no paradoxes for you + var/atom/real_location = real_location() + var/atom/host = parent + if(real_location == I.loc) + return FALSE //Means the item is already in the storage item + if(locked) + if(M && !stop_messages) + host.add_fingerprint(M) + to_chat(M, "[host] seems to be locked!") + return FALSE + if(real_location.contents.len >= max_items) + if(!stop_messages) + to_chat(M, "[host] is full, make some space!") + return FALSE //Storage item is full + if(length(can_hold)) + if(!is_type_in_typecache(I, can_hold)) + if(!stop_messages) + to_chat(M, "[host] cannot hold [I]!") + return FALSE + if(is_type_in_typecache(I, cant_hold) || HAS_TRAIT(I, TRAIT_NO_STORAGE_INSERT)) //Items which this container can't hold. + if(!stop_messages) + to_chat(M, "[host] cannot hold [I]!") + return FALSE + if(I.w_class > max_w_class && !is_type_in_typecache(I, exception_hold)) + if(!stop_messages) + to_chat(M, "[I] is too big for [host]!") + return FALSE + var/datum/component/storage/biggerfish = real_location.loc.GetComponent(/datum/component/storage) + if(biggerfish && biggerfish.max_w_class < max_w_class)//return false if we are inside of another container, and that container has a smaller max_w_class than us (like if we're a bag in a box) + if(!stop_messages) + to_chat(M, "[I] can't fit in [host] while [real_location.loc] is in the way!") + return FALSE + var/sum_w_class = I.w_class + for(var/obj/item/_I in real_location) + sum_w_class += _I.w_class //Adds up the combined w_classes which will be in the storage item if the item is added to it. + if(sum_w_class > max_combined_w_class) + if(!stop_messages) + to_chat(M, "[I] won't fit in [host], make some space!") + return FALSE + if(isitem(host)) + var/obj/item/IP = host + var/datum/component/storage/STR_I = I.GetComponent(/datum/component/storage) + if((I.w_class >= IP.w_class) && STR_I && !allow_big_nesting) + if(!stop_messages) + to_chat(M, "[IP] cannot hold [I] as it's a storage item of the same size!") + return FALSE //To prevent the stacking of same sized storage items. + if(HAS_TRAIT(I, TRAIT_NODROP)) //SHOULD be handled in unEquip, but better safe than sorry. + if(!stop_messages) + to_chat(M, "\the [I] is stuck to your hand, you can't put it in \the [host]!") + return FALSE + var/datum/component/storage/concrete/master = master() + if(!istype(master)) + return FALSE + return master.slave_can_insert_object(src, I, stop_messages, M) + +/datum/component/storage/proc/_insert_physical_item(obj/item/I, override = FALSE) + return FALSE + +//This proc handles items being inserted. It does not perform any checks of whether an item can or can't be inserted. That's done by can_be_inserted() +//The prevent_warning parameter will stop the insertion message from being displayed. It is intended for cases where you are inserting multiple items at once, +//such as when picking up all the items on a tile with one click. +/datum/component/storage/proc/handle_item_insertion(obj/item/I, prevent_warning = FALSE, mob/M, datum/component/storage/remote) + var/atom/parent = src.parent + var/datum/component/storage/concrete/master = master() + if(!istype(master)) + return FALSE + if(silent) + prevent_warning = TRUE + if(M) + parent.add_fingerprint(M) + . = master.handle_item_insertion_from_slave(src, I, prevent_warning, M) + +/datum/component/storage/proc/mob_item_insertion_feedback(mob/user, mob/M, obj/item/I, override = FALSE) + if(silent && !override) + return + if(rustle_sound) + playsound(parent, "rustle", 50, TRUE, -5) + for(var/mob/viewing in viewers(user, null)) + if(M == viewing) + to_chat(usr, "You put [I] [insert_preposition]to [parent].") + else if(in_range(M, viewing)) //If someone is standing close enough, they can tell what it is... + viewing.show_message("[M] puts [I] [insert_preposition]to [parent].", MSG_VISUAL) + else if(I && I.w_class >= 3) //Otherwise they can only see large or normal items from a distance... + viewing.show_message("[M] puts [I] [insert_preposition]to [parent].", MSG_VISUAL) + +/datum/component/storage/proc/update_icon() + if(isobj(parent)) + var/obj/O = parent + O.update_icon() + +/datum/component/storage/proc/signal_insertion_attempt(datum/source, obj/item/I, mob/M, silent = FALSE, force = FALSE) + if((!force && !can_be_inserted(I, TRUE, M)) || (I == parent)) + return FALSE + return handle_item_insertion(I, silent, M) + +/datum/component/storage/proc/signal_can_insert(datum/source, obj/item/I, mob/M, silent = FALSE) + return can_be_inserted(I, silent, M) + +/datum/component/storage/proc/show_to_ghost(datum/source, mob/dead/observer/M) + return user_show_to_mob(M, TRUE) + +/datum/component/storage/proc/signal_show_attempt(datum/source, mob/showto, force = FALSE) + return user_show_to_mob(showto, force) + +/datum/component/storage/proc/on_check() + return TRUE + +/datum/component/storage/proc/check_locked() + return locked + +/datum/component/storage/proc/signal_take_type(datum/source, type, atom/destination, amount = INFINITY, check_adjacent = FALSE, force = FALSE, mob/user, list/inserted) + if(!force) + if(check_adjacent) + if(!user || !user.CanReach(destination) || !user.CanReach(parent)) + return FALSE + var/list/taking = typecache_filter_list(contents(), typecacheof(type)) + if(taking.len > amount) + taking.len = amount + if(inserted) //duplicated code for performance, don't bother checking retval/checking for list every item. + for(var/i in taking) + if(remove_from_storage(i, destination)) + inserted |= i + else + for(var/i in taking) + remove_from_storage(i, destination) + return TRUE + +/datum/component/storage/proc/remaining_space_items() + var/atom/real_location = real_location() + return max(0, max_items - real_location.contents.len) + +/datum/component/storage/proc/signal_fill_type(datum/source, type, amount = 20, force = FALSE) + var/atom/real_location = real_location() + if(!force) + amount = min(remaining_space_items(), amount) + for(var/i in 1 to amount) + if(!handle_item_insertion(new type(real_location), TRUE)) + return i > 1 //return TRUE only if at least one insertion has been successful. + if(CHECK_TICK) + if(QDELETED(src)) + return TRUE + return TRUE + +/datum/component/storage/proc/on_attack_hand(datum/source, mob/user) + var/atom/A = parent + if(!attack_hand_interact) + return + if(user.active_storage == src && A.loc == user) //if you're already looking inside the storage item + user.active_storage.close(user) + close(user) + . = COMPONENT_NO_ATTACK_HAND + return + + if(rustle_sound) + playsound(A, "rustle", 50, TRUE, -5) + + if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(H.l_store == A && !H.get_active_held_item()) //Prevents opening if it's in a pocket. + . = COMPONENT_NO_ATTACK_HAND + H.put_in_hands(A) + H.l_store = null + return + if(H.r_store == A && !H.get_active_held_item()) + . = COMPONENT_NO_ATTACK_HAND + H.put_in_hands(A) + H.r_store = null + return + + if(A.loc == user) + . = COMPONENT_NO_ATTACK_HAND + if(locked) + to_chat(user, "[parent] seems to be locked!") + else + show_to(user) + +/datum/component/storage/proc/signal_on_pickup(datum/source, mob/user) + update_actions() + for(var/mob/M in can_see_contents() - user) + close(M) + +/datum/component/storage/proc/signal_take_obj(datum/source, atom/movable/AM, new_loc, force = FALSE) + if(!(AM in real_location())) + return FALSE + return remove_from_storage(AM, new_loc) + +/datum/component/storage/proc/signal_quick_empty(datum/source, atom/loctarget) + return do_quick_empty(loctarget) + +/datum/component/storage/proc/signal_hide_attempt(datum/source, mob/target) + return hide_from(target) + +/datum/component/storage/proc/on_alt_click(datum/source, mob/user) + if(!isliving(user) || !user.CanReach(parent) || user.incapacitated()) + return + if(locked) + to_chat(user, "[parent] seems to be locked!") + return + + var/atom/A = parent + if(!quickdraw) + A.add_fingerprint(user) + user_show_to_mob(user) + playsound(A, "rustle", 50, TRUE, -5) + return + + var/obj/item/I = locate() in real_location() + if(!I) + return + A.add_fingerprint(user) + remove_from_storage(I, get_turf(user)) + if(!user.put_in_hands(I)) + to_chat(user, "You fumble for [I] and it falls on the floor.") + return + user.visible_message("[user] draws [I] from [parent]!", "You draw [I] from [parent].") + +/datum/component/storage/proc/action_trigger(datum/signal_source, datum/action/source) + gather_mode_switch(source.owner) + return COMPONENT_ACTION_BLOCK_TRIGGER + +/datum/component/storage/proc/gather_mode_switch(mob/user) + collection_mode = (collection_mode+1)%3 + switch(collection_mode) + if(COLLECT_SAME) + to_chat(user, "[parent] now picks up all items of a single type at once.") + if(COLLECT_EVERYTHING) + to_chat(user, "[parent] now picks up all items in a tile at once.") + if(COLLECT_ONE) + to_chat(user, "[parent] now picks up one item at a time.") diff --git a/code/datums/components/uplink.dm b/code/datums/components/uplink.dm index 499f2db7fbe7..4136237505de 100644 --- a/code/datums/components/uplink.dm +++ b/code/datums/components/uplink.dm @@ -120,12 +120,14 @@ // an unlocked uplink blocks also opening the PDA or headset menu return COMPONENT_NO_INTERACT -/datum/component/uplink/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.inventory_state) +/datum/component/uplink/ui_state(mob/user) + return GLOB.inventory_state + +/datum/component/uplink/ui_interact(mob/user, datum/tgui/ui) active = TRUE - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "Uplink", name, 620, 580, master_ui, state) + ui = new(user, src, "Uplink", name) // This UI is only ever opened by one person, // and never is updated outside of user input. ui.set_autoupdate(FALSE) diff --git a/code/datums/components/wet_floor.dm b/code/datums/components/wet_floor.dm index 69a6462d78ca..f71ba5859d05 100644 --- a/code/datums/components/wet_floor.dm +++ b/code/datums/components/wet_floor.dm @@ -1,206 +1,206 @@ -/datum/component/wet_floor - dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS - can_transfer = TRUE - var/highest_strength = TURF_DRY - var/lube_flags = NONE //why do we have this? - var/list/time_left_list //In deciseconds. - var/static/mutable_appearance/permafrost_overlay = mutable_appearance('icons/effects/water.dmi', "ice_floor") - var/static/mutable_appearance/ice_overlay = mutable_appearance('icons/turf/overlays.dmi', "snowfloor") - var/static/mutable_appearance/water_overlay = mutable_appearance('icons/effects/water.dmi', "wet_floor_static") - var/static/mutable_appearance/generic_turf_overlay = mutable_appearance('icons/effects/water.dmi', "wet_static") - var/current_overlay - var/permanent = FALSE - var/last_process = 0 - -/datum/component/wet_floor/InheritComponent(datum/newcomp, orig, strength, duration_minimum, duration_add, duration_maximum, _permanent) - if(!newcomp) //We are getting passed the arguments of a would-be new component, but not a new component - add_wet(arglist(args.Copy(3))) - else //We are being passed in a full blown component - var/datum/component/wet_floor/WF = newcomp //Lets make an assumption - if(WF.gc()) //See if it's even valid, still. Also does LAZYLEN and stuff for us. - CRASH("Wet floor component tried to inherit another, but the other was able to garbage collect while being inherited! What a waste of time!") - for(var/i in WF.time_left_list) - add_wet(text2num(i), WF.time_left_list[i]) - -/datum/component/wet_floor/Initialize(strength, duration_minimum, duration_add, duration_maximum, _permanent = FALSE) - if(!isopenturf(parent)) - return COMPONENT_INCOMPATIBLE - add_wet(strength, duration_minimum, duration_add, duration_maximum) - permanent = _permanent - if(!permanent) - START_PROCESSING(SSwet_floors, src) - addtimer(CALLBACK(src, .proc/gc, TRUE), 1) //GC after initialization. - last_process = world.time - -/datum/component/wet_floor/RegisterWithParent() - RegisterSignal(parent, COMSIG_TURF_IS_WET, .proc/is_wet) - RegisterSignal(parent, COMSIG_TURF_MAKE_DRY, .proc/dry) - -/datum/component/wet_floor/UnregisterFromParent() - UnregisterSignal(parent, list(COMSIG_TURF_IS_WET, COMSIG_TURF_MAKE_DRY)) - -/datum/component/wet_floor/Destroy() - STOP_PROCESSING(SSwet_floors, src) - var/turf/T = parent - qdel(T.GetComponent(/datum/component/slippery)) - if(istype(T)) //If this is false there is so many things wrong with it. - T.cut_overlay(current_overlay) - else - stack_trace("Warning: Wet floor component wasn't on a turf when being destroyed! This is really bad!") - return ..() - -/datum/component/wet_floor/proc/update_overlay() - var/intended - if(!istype(parent, /turf/open/floor)) - intended = generic_turf_overlay - else - switch(highest_strength) - if(TURF_WET_PERMAFROST) - intended = permafrost_overlay - if(TURF_WET_ICE) - intended = ice_overlay - else - intended = water_overlay - if(current_overlay != intended) - var/turf/T = parent - T.cut_overlay(current_overlay) - T.add_overlay(intended) - current_overlay = intended - -/datum/component/wet_floor/proc/AfterSlip(mob/living/L) - if(highest_strength == TURF_WET_LUBE) - L.confused = max(L.confused, 8) - -/datum/component/wet_floor/proc/update_flags() - var/intensity - lube_flags = NONE - switch(highest_strength) - if(TURF_WET_WATER) - intensity = 60 - lube_flags = NO_SLIP_WHEN_WALKING - if(TURF_WET_LUBE) - intensity = 80 - lube_flags = SLIDE | GALOSHES_DONT_HELP - if(TURF_WET_ICE) - intensity = 120 - lube_flags = SLIDE | GALOSHES_DONT_HELP - if(TURF_WET_PERMAFROST) - intensity = 120 - lube_flags = SLIDE_ICE | GALOSHES_DONT_HELP - if(TURF_WET_SUPERLUBE) - intensity = 120 - lube_flags = SLIDE | GALOSHES_DONT_HELP | SLIP_WHEN_CRAWLING - else - qdel(parent.GetComponent(/datum/component/slippery)) - return - - parent.LoadComponent(/datum/component/slippery, intensity, lube_flags, CALLBACK(src, .proc/AfterSlip)) - -/datum/component/wet_floor/proc/dry(datum/source, strength = TURF_WET_WATER, immediate = FALSE, duration_decrease = INFINITY) - for(var/i in time_left_list) - if(text2num(i) <= strength) - time_left_list[i] = max(0, time_left_list[i] - duration_decrease) - if(immediate) - check() - -/datum/component/wet_floor/proc/max_time_left() - . = 0 - for(var/i in time_left_list) - . = max(., time_left_list[i]) - -/datum/component/wet_floor/process() - var/turf/open/T = parent - var/diff = world.time - last_process - var/decrease = 0 - var/t = T.GetTemperature() - switch(t) - if(-INFINITY to T0C) - add_wet(TURF_WET_ICE, max_time_left()) //Water freezes into ice! - if(T0C to T0C + 100) - decrease = ((T.air.return_temperature() - T0C) / SSwet_floors.temperature_coeff) * (diff / SSwet_floors.time_ratio) - if(T0C + 100 to INFINITY) - decrease = INFINITY - decrease = max(0, decrease) - if((is_wet() & TURF_WET_ICE) && t > T0C) //Ice melts into water! - for(var/obj/O in T.contents) - if(O.obj_flags & FROZEN) - O.make_unfrozen() - add_wet(TURF_WET_WATER, max_time_left()) - dry(null, TURF_WET_ICE) - dry(null, ALL, FALSE, decrease) - check() - last_process = world.time - -/datum/component/wet_floor/proc/update_strength() - highest_strength = 0 //Not bitflag. - for(var/i in time_left_list) - highest_strength = max(highest_strength, text2num(i)) - -/datum/component/wet_floor/proc/is_wet() - . = 0 - for(var/i in time_left_list) - . |= text2num(i) - -/datum/component/wet_floor/PreTransfer() - var/turf/O = parent - O.cut_overlay(current_overlay) - //That turf is no longer slippery, we're out of here - //Slippery components don't transfer due to callbacks - qdel(O.GetComponent(/datum/component/slippery)) - -/datum/component/wet_floor/PostTransfer() - if(!isopenturf(parent)) - return COMPONENT_INCOMPATIBLE - var/turf/T = parent - T.add_overlay(current_overlay) - //Make sure to add/update any slippery component on the new turf (update_flags calls LoadComponent) - update_flags() - - //NB it's possible we get deleted after this, due to inherit - -/datum/component/wet_floor/proc/add_wet(type, duration_minimum = 0, duration_add = 0, duration_maximum = MAXIMUM_WET_TIME, _permanent = FALSE) - var/static/list/allowed_types = list(TURF_WET_WATER, TURF_WET_LUBE, TURF_WET_ICE, TURF_WET_PERMAFROST, TURF_WET_SUPERLUBE) - if(duration_minimum <= 0 || !type) - return FALSE - if(type in allowed_types) - return _do_add_wet(type, duration_minimum, duration_add, duration_maximum) - else - . = NONE - for(var/i in allowed_types) - if(!(type & i)) - continue - . |= _do_add_wet(i, duration_minimum, duration_add, duration_maximum) - if(_permanent) - permanent = TRUE - STOP_PROCESSING(SSwet_floors, src) - -/datum/component/wet_floor/proc/_do_add_wet(type, duration_minimum, duration_add, duration_maximum) - var/time = 0 - if(LAZYACCESS(time_left_list, "[type]")) - time = clamp(LAZYACCESS(time_left_list, "[type]") + duration_add, duration_minimum, duration_maximum) - else - time = min(duration_minimum, duration_maximum) - LAZYSET(time_left_list, "[type]", time) - check(TRUE) - return TRUE - -/datum/component/wet_floor/proc/gc(on_init = FALSE) - if(!LAZYLEN(time_left_list)) - if(on_init) - var/turf/T = parent - stack_trace("Warning: Wet floor component gc'd right after initialization! What a waste of time and CPU! Type = [T? T.type : "ERROR - NO PARENT"], Location = [istype(T)? AREACOORD(T) : "ERROR - INVALID PARENT"].") - qdel(src) - return TRUE - return FALSE - -/datum/component/wet_floor/proc/check(force_update = FALSE) - var/changed = FALSE - for(var/i in time_left_list) - if(time_left_list[i] <= 0) - time_left_list -= i - changed = TRUE - if(changed || force_update) - update_strength() - update_overlay() - update_flags() - gc() +/datum/component/wet_floor + dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS + can_transfer = TRUE + var/highest_strength = TURF_DRY + var/lube_flags = NONE //why do we have this? + var/list/time_left_list //In deciseconds. + var/static/mutable_appearance/permafrost_overlay = mutable_appearance('icons/effects/water.dmi', "ice_floor") + var/static/mutable_appearance/ice_overlay = mutable_appearance('icons/turf/overlays.dmi', "snowfloor") + var/static/mutable_appearance/water_overlay = mutable_appearance('icons/effects/water.dmi', "wet_floor_static") + var/static/mutable_appearance/generic_turf_overlay = mutable_appearance('icons/effects/water.dmi', "wet_static") + var/current_overlay + var/permanent = FALSE + var/last_process = 0 + +/datum/component/wet_floor/InheritComponent(datum/newcomp, orig, strength, duration_minimum, duration_add, duration_maximum, _permanent) + if(!newcomp) //We are getting passed the arguments of a would-be new component, but not a new component + add_wet(arglist(args.Copy(3))) + else //We are being passed in a full blown component + var/datum/component/wet_floor/WF = newcomp //Lets make an assumption + if(WF.gc()) //See if it's even valid, still. Also does LAZYLEN and stuff for us. + CRASH("Wet floor component tried to inherit another, but the other was able to garbage collect while being inherited! What a waste of time!") + for(var/i in WF.time_left_list) + add_wet(text2num(i), WF.time_left_list[i]) + +/datum/component/wet_floor/Initialize(strength, duration_minimum, duration_add, duration_maximum, _permanent = FALSE) + if(!isopenturf(parent)) + return COMPONENT_INCOMPATIBLE + add_wet(strength, duration_minimum, duration_add, duration_maximum) + permanent = _permanent + if(!permanent) + START_PROCESSING(SSwet_floors, src) + addtimer(CALLBACK(src, .proc/gc, TRUE), 1) //GC after initialization. + last_process = world.time + +/datum/component/wet_floor/RegisterWithParent() + RegisterSignal(parent, COMSIG_TURF_IS_WET, .proc/is_wet) + RegisterSignal(parent, COMSIG_TURF_MAKE_DRY, .proc/dry) + +/datum/component/wet_floor/UnregisterFromParent() + UnregisterSignal(parent, list(COMSIG_TURF_IS_WET, COMSIG_TURF_MAKE_DRY)) + +/datum/component/wet_floor/Destroy() + STOP_PROCESSING(SSwet_floors, src) + var/turf/T = parent + qdel(T.GetComponent(/datum/component/slippery)) + if(istype(T)) //If this is false there is so many things wrong with it. + T.cut_overlay(current_overlay) + else + stack_trace("Warning: Wet floor component wasn't on a turf when being destroyed! This is really bad!") + return ..() + +/datum/component/wet_floor/proc/update_overlay() + var/intended + if(!istype(parent, /turf/open/floor)) + intended = generic_turf_overlay + else + switch(highest_strength) + if(TURF_WET_PERMAFROST) + intended = permafrost_overlay + if(TURF_WET_ICE) + intended = ice_overlay + else + intended = water_overlay + if(current_overlay != intended) + var/turf/T = parent + T.cut_overlay(current_overlay) + T.add_overlay(intended) + current_overlay = intended + +/datum/component/wet_floor/proc/AfterSlip(mob/living/L) + if(highest_strength == TURF_WET_LUBE) + L.confused = max(L.confused, 8) + +/datum/component/wet_floor/proc/update_flags() + var/intensity + lube_flags = NONE + switch(highest_strength) + if(TURF_WET_WATER) + intensity = 60 + lube_flags = NO_SLIP_WHEN_WALKING + if(TURF_WET_LUBE) + intensity = 80 + lube_flags = SLIDE | GALOSHES_DONT_HELP + if(TURF_WET_ICE) + intensity = 120 + lube_flags = SLIDE | GALOSHES_DONT_HELP + if(TURF_WET_PERMAFROST) + intensity = 120 + lube_flags = SLIDE_ICE | GALOSHES_DONT_HELP + if(TURF_WET_SUPERLUBE) + intensity = 120 + lube_flags = SLIDE | GALOSHES_DONT_HELP | SLIP_WHEN_CRAWLING + else + qdel(parent.GetComponent(/datum/component/slippery)) + return + + parent.LoadComponent(/datum/component/slippery, intensity, lube_flags, CALLBACK(src, .proc/AfterSlip)) + +/datum/component/wet_floor/proc/dry(datum/source, strength = TURF_WET_WATER, immediate = FALSE, duration_decrease = INFINITY) + for(var/i in time_left_list) + if(text2num(i) <= strength) + time_left_list[i] = max(0, time_left_list[i] - duration_decrease) + if(immediate) + check() + +/datum/component/wet_floor/proc/max_time_left() + . = 0 + for(var/i in time_left_list) + . = max(., time_left_list[i]) + +/datum/component/wet_floor/process() + var/turf/open/T = parent + var/diff = world.time - last_process + var/decrease = 0 + var/t = T.GetTemperature() + switch(t) + if(-INFINITY to T0C) + add_wet(TURF_WET_ICE, max_time_left()) //Water freezes into ice! + if(T0C to T0C + 100) + decrease = ((T.air.return_temperature() - T0C) / SSwet_floors.temperature_coeff) * (diff / SSwet_floors.time_ratio) + if(T0C + 100 to INFINITY) + decrease = INFINITY + decrease = max(0, decrease) + if((is_wet() & TURF_WET_ICE) && t > T0C) //Ice melts into water! + for(var/obj/O in T.contents) + if(O.obj_flags & FROZEN) + O.make_unfrozen() + add_wet(TURF_WET_WATER, max_time_left()) + dry(null, TURF_WET_ICE) + dry(null, ALL, FALSE, decrease) + check() + last_process = world.time + +/datum/component/wet_floor/proc/update_strength() + highest_strength = 0 //Not bitflag. + for(var/i in time_left_list) + highest_strength = max(highest_strength, text2num(i)) + +/datum/component/wet_floor/proc/is_wet() + . = 0 + for(var/i in time_left_list) + . |= text2num(i) + +/datum/component/wet_floor/PreTransfer() + var/turf/O = parent + O.cut_overlay(current_overlay) + //That turf is no longer slippery, we're out of here + //Slippery components don't transfer due to callbacks + qdel(O.GetComponent(/datum/component/slippery)) + +/datum/component/wet_floor/PostTransfer() + if(!isopenturf(parent)) + return COMPONENT_INCOMPATIBLE + var/turf/T = parent + T.add_overlay(current_overlay) + //Make sure to add/update any slippery component on the new turf (update_flags calls LoadComponent) + update_flags() + + //NB it's possible we get deleted after this, due to inherit + +/datum/component/wet_floor/proc/add_wet(type, duration_minimum = 0, duration_add = 0, duration_maximum = MAXIMUM_WET_TIME, _permanent = FALSE) + var/static/list/allowed_types = list(TURF_WET_WATER, TURF_WET_LUBE, TURF_WET_ICE, TURF_WET_PERMAFROST, TURF_WET_SUPERLUBE) + if(duration_minimum <= 0 || !type) + return FALSE + if(type in allowed_types) + return _do_add_wet(type, duration_minimum, duration_add, duration_maximum) + else + . = NONE + for(var/i in allowed_types) + if(!(type & i)) + continue + . |= _do_add_wet(i, duration_minimum, duration_add, duration_maximum) + if(_permanent) + permanent = TRUE + STOP_PROCESSING(SSwet_floors, src) + +/datum/component/wet_floor/proc/_do_add_wet(type, duration_minimum, duration_add, duration_maximum) + var/time = 0 + if(LAZYACCESS(time_left_list, "[type]")) + time = clamp(LAZYACCESS(time_left_list, "[type]") + duration_add, duration_minimum, duration_maximum) + else + time = min(duration_minimum, duration_maximum) + LAZYSET(time_left_list, "[type]", time) + check(TRUE) + return TRUE + +/datum/component/wet_floor/proc/gc(on_init = FALSE) + if(!LAZYLEN(time_left_list)) + if(on_init) + var/turf/T = parent + stack_trace("Warning: Wet floor component gc'd right after initialization! What a waste of time and CPU! Type = [T? T.type : "ERROR - NO PARENT"], Location = [istype(T)? AREACOORD(T) : "ERROR - INVALID PARENT"].") + qdel(src) + return TRUE + return FALSE + +/datum/component/wet_floor/proc/check(force_update = FALSE) + var/changed = FALSE + for(var/i in time_left_list) + if(time_left_list[i] <= 0) + time_left_list -= i + changed = TRUE + if(changed || force_update) + update_strength() + update_overlay() + update_flags() + gc() diff --git a/code/datums/datum.dm b/code/datums/datum.dm index 4608b0ceb68f..2b875bbdd7e6 100644 --- a/code/datums/datum.dm +++ b/code/datums/datum.dm @@ -1,223 +1,223 @@ -/** - * The absolute base class for everything - * - * A datum instantiated has no physical world prescence, use an atom if you want something - * that actually lives in the world - * - * Be very mindful about adding variables to this class, they are inherited by every single - * thing in the entire game, and so you can easily cause memory usage to rise a lot with careless - * use of variables at this level - */ -/datum - /** - * Tick count time when this object was destroyed. - * - * If this is non zero then the object has been garbage collected and is awaiting either - * a hard del by the GC subsystme, or to be autocollected (if it has no references) - */ - var/gc_destroyed - - /// Active timers with this datum as the target - var/list/active_timers - /// Status traits attached to this datum - var/list/status_traits - - /** - * Components attached to this datum - * - * Lazy associated list in the structure of `type:component/list of components` - */ - var/list/datum_components - /** - * Any datum registered to receive signals from this datum is in this list - * - * Lazy associated list in the structure of `signal:registree/list of registrees` - */ - var/list/comp_lookup - /// Lazy associated list in the structure of `signals:proctype` that are run when the datum receives that signal - var/list/list/datum/callback/signal_procs - /** - * Is this datum capable of sending signals? - * - * Set to true when a signal has been registered - */ - var/signal_enabled = FALSE - - /// Datum level flags - var/datum_flags = NONE - - /// A weak reference to another datum - var/datum/weakref/weak_reference - -#ifdef TESTING - var/running_find_references - var/last_find_references = 0 -#endif - -#ifdef DATUMVAR_DEBUGGING_MODE - var/list/cached_vars -#endif - -/** - * Called when a href for this datum is clicked - * - * Sends a [COMSIG_TOPIC] signal - */ -/datum/Topic(href, href_list[]) - ..() - SEND_SIGNAL(src, COMSIG_TOPIC, usr, href_list) - -/** - * Default implementation of clean-up code. - * - * This should be overridden to remove all references pointing to the object being destroyed, if - * you do override it, make sure to call the parent and return it's return value by default - * - * Return an appropriate [QDEL_HINT][QDEL_HINT_QUEUE] to modify handling of your deletion; - * in most cases this is [QDEL_HINT_QUEUE]. - * - * The base case is responsible for doing the following - * * Erasing timers pointing to this datum - * * Erasing compenents on this datum - * * Notifying datums listening to signals from this datum that we are going away - * - * Returns [QDEL_HINT_QUEUE] - */ -/datum/proc/Destroy(force=FALSE, ...) - SHOULD_CALL_PARENT(1) - tag = null - datum_flags &= ~DF_USE_TAG //In case something tries to REF us - weak_reference = null //ensure prompt GCing of weakref. - - var/list/timers = active_timers - active_timers = null - for(var/thing in timers) - var/datum/timedevent/timer = thing - if (timer.spent) - continue - qdel(timer) - - //BEGIN: ECS SHIT - signal_enabled = FALSE - - var/list/dc = datum_components - if(dc) - var/all_components = dc[/datum/component] - if(length(all_components)) - for(var/I in all_components) - var/datum/component/C = I - qdel(C, FALSE, TRUE) - else - var/datum/component/C = all_components - qdel(C, FALSE, TRUE) - dc.Cut() - - var/list/lookup = comp_lookup - if(lookup) - for(var/sig in lookup) - var/list/comps = lookup[sig] - if(length(comps)) - for(var/i in comps) - var/datum/component/comp = i - comp.UnregisterSignal(src, sig) - else - var/datum/component/comp = comps - comp.UnregisterSignal(src, sig) - comp_lookup = lookup = null - - for(var/target in signal_procs) - UnregisterSignal(target, signal_procs[target]) - //END: ECS SHIT - - return QDEL_HINT_QUEUE - -#ifdef DATUMVAR_DEBUGGING_MODE -/datum/proc/save_vars() - cached_vars = list() - for(var/i in vars) - if(i == "cached_vars") - continue - cached_vars[i] = vars[i] - -/datum/proc/check_changed_vars() - . = list() - for(var/i in vars) - if(i == "cached_vars") - continue - if(cached_vars[i] != vars[i]) - .[i] = list(cached_vars[i], vars[i]) - -/datum/proc/txt_changed_vars() - var/list/l = check_changed_vars() - var/t = "[src]([REF(src)]) changed vars:" - for(var/i in l) - t += "\"[i]\" \[[l[i][1]]\] --> \[[l[i][2]]\] " - t += "." - -/datum/proc/to_chat_check_changed_vars(target = world) - to_chat(target, txt_changed_vars()) -#endif - -///Return a LIST for serialize_datum to encode! Not the actual json! -/datum/proc/serialize_list(list/options) - CRASH("Attempted to serialize datum [src] of type [type] without serialize_list being implemented!") - -///Accepts a LIST from deserialize_datum. Should return src or another datum. -/datum/proc/deserialize_list(json, list/options) - CRASH("Attempted to deserialize datum [src] of type [type] without deserialize_list being implemented!") - -///Serializes into JSON. Does not encode type. -/datum/proc/serialize_json(list/options) - . = serialize_list(options) - if(!islist(.)) - . = null - else - . = json_encode(.) - -///Deserializes from JSON. Does not parse type. -/datum/proc/deserialize_json(list/input, list/options) - var/list/jsonlist = json_decode(input) - . = deserialize_list(jsonlist) - if(!istype(., /datum)) - . = null - -///Convert a datum into a json blob -/proc/json_serialize_datum(datum/D, list/options) - if(!istype(D)) - return - var/list/jsonlist = D.serialize_list(options) - if(islist(jsonlist)) - jsonlist["DATUM_TYPE"] = D.type - return json_encode(jsonlist) - -/// Convert a list of json to datum -/proc/json_deserialize_datum(list/jsonlist, list/options, target_type, strict_target_type = FALSE) - if(!islist(jsonlist)) - if(!istext(jsonlist)) - CRASH("Invalid JSON") - jsonlist = json_decode(jsonlist) - if(!islist(jsonlist)) - CRASH("Invalid JSON") - if(!jsonlist["DATUM_TYPE"]) - return - if(!ispath(jsonlist["DATUM_TYPE"])) - if(!istext(jsonlist["DATUM_TYPE"])) - return - jsonlist["DATUM_TYPE"] = text2path(jsonlist["DATUM_TYPE"]) - if(!ispath(jsonlist["DATUM_TYPE"])) - return - if(target_type) - if(!ispath(target_type)) - return - if(strict_target_type) - if(target_type != jsonlist["DATUM_TYPE"]) - return - else if(!ispath(jsonlist["DATUM_TYPE"], target_type)) - return - var/typeofdatum = jsonlist["DATUM_TYPE"] //BYOND won't directly read if this is just put in the line below, and will instead runtime because it thinks you're trying to make a new list? - var/datum/D = new typeofdatum - var/datum/returned = D.deserialize_list(jsonlist, options) - if(!istype(returned, /datum)) - qdel(D) - else - return returned +/** + * The absolute base class for everything + * + * A datum instantiated has no physical world prescence, use an atom if you want something + * that actually lives in the world + * + * Be very mindful about adding variables to this class, they are inherited by every single + * thing in the entire game, and so you can easily cause memory usage to rise a lot with careless + * use of variables at this level + */ +/datum + /** + * Tick count time when this object was destroyed. + * + * If this is non zero then the object has been garbage collected and is awaiting either + * a hard del by the GC subsystme, or to be autocollected (if it has no references) + */ + var/gc_destroyed + + /// Active timers with this datum as the target + var/list/active_timers + /// Status traits attached to this datum + var/list/status_traits + + /** + * Components attached to this datum + * + * Lazy associated list in the structure of `type:component/list of components` + */ + var/list/datum_components + /** + * Any datum registered to receive signals from this datum is in this list + * + * Lazy associated list in the structure of `signal:registree/list of registrees` + */ + var/list/comp_lookup + /// Lazy associated list in the structure of `signals:proctype` that are run when the datum receives that signal + var/list/list/datum/callback/signal_procs + /** + * Is this datum capable of sending signals? + * + * Set to true when a signal has been registered + */ + var/signal_enabled = FALSE + + /// Datum level flags + var/datum_flags = NONE + + /// A weak reference to another datum + var/datum/weakref/weak_reference + +#ifdef TESTING + var/running_find_references + var/last_find_references = 0 +#endif + +#ifdef DATUMVAR_DEBUGGING_MODE + var/list/cached_vars +#endif + +/** + * Called when a href for this datum is clicked + * + * Sends a [COMSIG_TOPIC] signal + */ +/datum/Topic(href, href_list[]) + ..() + SEND_SIGNAL(src, COMSIG_TOPIC, usr, href_list) + +/** + * Default implementation of clean-up code. + * + * This should be overridden to remove all references pointing to the object being destroyed, if + * you do override it, make sure to call the parent and return it's return value by default + * + * Return an appropriate [QDEL_HINT][QDEL_HINT_QUEUE] to modify handling of your deletion; + * in most cases this is [QDEL_HINT_QUEUE]. + * + * The base case is responsible for doing the following + * * Erasing timers pointing to this datum + * * Erasing compenents on this datum + * * Notifying datums listening to signals from this datum that we are going away + * + * Returns [QDEL_HINT_QUEUE] + */ +/datum/proc/Destroy(force=FALSE, ...) + SHOULD_CALL_PARENT(1) + tag = null + datum_flags &= ~DF_USE_TAG //In case something tries to REF us + weak_reference = null //ensure prompt GCing of weakref. + + var/list/timers = active_timers + active_timers = null + for(var/thing in timers) + var/datum/timedevent/timer = thing + if (timer.spent) + continue + qdel(timer) + + //BEGIN: ECS SHIT + signal_enabled = FALSE + + var/list/dc = datum_components + if(dc) + var/all_components = dc[/datum/component] + if(length(all_components)) + for(var/I in all_components) + var/datum/component/C = I + qdel(C, FALSE, TRUE) + else + var/datum/component/C = all_components + qdel(C, FALSE, TRUE) + dc.Cut() + + var/list/lookup = comp_lookup + if(lookup) + for(var/sig in lookup) + var/list/comps = lookup[sig] + if(length(comps)) + for(var/i in comps) + var/datum/component/comp = i + comp.UnregisterSignal(src, sig) + else + var/datum/component/comp = comps + comp.UnregisterSignal(src, sig) + comp_lookup = lookup = null + + for(var/target in signal_procs) + UnregisterSignal(target, signal_procs[target]) + //END: ECS SHIT + + return QDEL_HINT_QUEUE + +#ifdef DATUMVAR_DEBUGGING_MODE +/datum/proc/save_vars() + cached_vars = list() + for(var/i in vars) + if(i == "cached_vars") + continue + cached_vars[i] = vars[i] + +/datum/proc/check_changed_vars() + . = list() + for(var/i in vars) + if(i == "cached_vars") + continue + if(cached_vars[i] != vars[i]) + .[i] = list(cached_vars[i], vars[i]) + +/datum/proc/txt_changed_vars() + var/list/l = check_changed_vars() + var/t = "[src]([REF(src)]) changed vars:" + for(var/i in l) + t += "\"[i]\" \[[l[i][1]]\] --> \[[l[i][2]]\] " + t += "." + +/datum/proc/to_chat_check_changed_vars(target = world) + to_chat(target, txt_changed_vars()) +#endif + +///Return a LIST for serialize_datum to encode! Not the actual json! +/datum/proc/serialize_list(list/options) + CRASH("Attempted to serialize datum [src] of type [type] without serialize_list being implemented!") + +///Accepts a LIST from deserialize_datum. Should return src or another datum. +/datum/proc/deserialize_list(json, list/options) + CRASH("Attempted to deserialize datum [src] of type [type] without deserialize_list being implemented!") + +///Serializes into JSON. Does not encode type. +/datum/proc/serialize_json(list/options) + . = serialize_list(options) + if(!islist(.)) + . = null + else + . = json_encode(.) + +///Deserializes from JSON. Does not parse type. +/datum/proc/deserialize_json(list/input, list/options) + var/list/jsonlist = json_decode(input) + . = deserialize_list(jsonlist) + if(!istype(., /datum)) + . = null + +///Convert a datum into a json blob +/proc/json_serialize_datum(datum/D, list/options) + if(!istype(D)) + return + var/list/jsonlist = D.serialize_list(options) + if(islist(jsonlist)) + jsonlist["DATUM_TYPE"] = D.type + return json_encode(jsonlist) + +/// Convert a list of json to datum +/proc/json_deserialize_datum(list/jsonlist, list/options, target_type, strict_target_type = FALSE) + if(!islist(jsonlist)) + if(!istext(jsonlist)) + CRASH("Invalid JSON") + jsonlist = json_decode(jsonlist) + if(!islist(jsonlist)) + CRASH("Invalid JSON") + if(!jsonlist["DATUM_TYPE"]) + return + if(!ispath(jsonlist["DATUM_TYPE"])) + if(!istext(jsonlist["DATUM_TYPE"])) + return + jsonlist["DATUM_TYPE"] = text2path(jsonlist["DATUM_TYPE"]) + if(!ispath(jsonlist["DATUM_TYPE"])) + return + if(target_type) + if(!ispath(target_type)) + return + if(strict_target_type) + if(target_type != jsonlist["DATUM_TYPE"]) + return + else if(!ispath(jsonlist["DATUM_TYPE"], target_type)) + return + var/typeofdatum = jsonlist["DATUM_TYPE"] //BYOND won't directly read if this is just put in the line below, and will instead runtime because it thinks you're trying to make a new list? + var/datum/D = new typeofdatum + var/datum/returned = D.deserialize_list(jsonlist, options) + if(!istype(returned, /datum)) + qdel(D) + else + return returned diff --git a/code/datums/datumvars.dm b/code/datums/datumvars.dm index 4b887f3eb434..51741c9cb9ac 100644 --- a/code/datums/datumvars.dm +++ b/code/datums/datumvars.dm @@ -1,51 +1,52 @@ -/datum/proc/CanProcCall(procname) - return TRUE - -/datum/proc/can_vv_get(var_name) - return TRUE - -/datum/proc/vv_edit_var(var_name, var_value) //called whenever a var is edited - if(var_name == NAMEOF(src, vars)) - return FALSE - vars[var_name] = var_value - datum_flags |= DF_VAR_EDITED - return TRUE - -/datum/proc/vv_get_var(var_name) - switch(var_name) - if ("vars") - return debug_variable(var_name, list(), 0, src) - return debug_variable(var_name, vars[var_name], 0, src) - -/datum/proc/can_vv_mark() - return TRUE - -//please call . = ..() first and append to the result, that way parent items are always at the top and child items are further down -//add separaters by doing . += "---" -/datum/proc/vv_get_dropdown() - . = list() - VV_DROPDOWN_OPTION("", "---") - VV_DROPDOWN_OPTION(VV_HK_CALLPROC, "Call Proc") - VV_DROPDOWN_OPTION(VV_HK_MARK, "Mark Object") - VV_DROPDOWN_OPTION(VV_HK_DELETE, "Delete") - VV_DROPDOWN_OPTION(VV_HK_EXPOSE, "Show VV To Player") - VV_DROPDOWN_OPTION(VV_HK_ADDCOMPONENT, "Add Component/Element") - VV_DROPDOWN_OPTION(VV_HK_MODIFY_TRAITS, "Modify Traits") - -//This proc is only called if everything topic-wise is verified. The only verifications that should happen here is things like permission checks! -//href_list is a reference, modifying it in these procs WILL change the rest of the proc in topic.dm of admin/view_variables! -//This proc is for "high level" actions like admin heal/set species/etc/etc. The low level debugging things should go in admin/view_variables/topic_basic.dm incase this runtimes. -/datum/proc/vv_do_topic(list/href_list) - if(!usr || !usr.client || !usr.client.holder || !check_rights(NONE)) - return FALSE //This is VV, not to be called by anything else. - if(href_list[VV_HK_MODIFY_TRAITS]) - usr.client.holder.modify_traits(src) - return TRUE - -/datum/proc/vv_get_header() - . = list() - if(("name" in vars) && !isatom(src)) - . += "[vars["name"]]
    " - -/datum/proc/on_reagent_change(changetype) - return +/datum/proc/CanProcCall(procname) + return TRUE + +/datum/proc/can_vv_get(var_name) + return TRUE + +/datum/proc/vv_edit_var(var_name, var_value) //called whenever a var is edited + if(var_name == NAMEOF(src, vars)) + return FALSE + vars[var_name] = var_value + datum_flags |= DF_VAR_EDITED + return TRUE + +/datum/proc/vv_get_var(var_name) + switch(var_name) + if ("vars") + return debug_variable(var_name, list(), 0, src) + return debug_variable(var_name, vars[var_name], 0, src) + +/datum/proc/can_vv_mark() + return TRUE + +//please call . = ..() first and append to the result, that way parent items are always at the top and child items are further down +//add separaters by doing . += "---" +/datum/proc/vv_get_dropdown() + . = list() + VV_DROPDOWN_OPTION("", "---") + VV_DROPDOWN_OPTION(VV_HK_CALLPROC, "Call Proc") + VV_DROPDOWN_OPTION(VV_HK_MARK, "Mark Object") + VV_DROPDOWN_OPTION(VV_HK_DELETE, "Delete") + VV_DROPDOWN_OPTION(VV_HK_EXPOSE, "Show VV To Player") + VV_DROPDOWN_OPTION(VV_HK_ADDCOMPONENT, "Add Component/Element") + VV_DROPDOWN_OPTION(VV_HK_MODIFY_TRAITS, "Modify Traits") + VV_DROPDOWN_OPTION(VV_HK_VIEW_REFERENCES, "View References") + +//This proc is only called if everything topic-wise is verified. The only verifications that should happen here is things like permission checks! +//href_list is a reference, modifying it in these procs WILL change the rest of the proc in topic.dm of admin/view_variables! +//This proc is for "high level" actions like admin heal/set species/etc/etc. The low level debugging things should go in admin/view_variables/topic_basic.dm incase this runtimes. +/datum/proc/vv_do_topic(list/href_list) + if(!usr || !usr.client || !usr.client.holder || !check_rights(NONE)) + return FALSE //This is VV, not to be called by anything else. + if(href_list[VV_HK_MODIFY_TRAITS]) + usr.client.holder.modify_traits(src) + return TRUE + +/datum/proc/vv_get_header() + . = list() + if(("name" in vars) && !isatom(src)) + . += "[vars["name"]]
    " + +/datum/proc/on_reagent_change(changetype) + return diff --git a/code/datums/diseases/advance/advance.dm b/code/datums/diseases/advance/advance.dm index 903a109083c6..96a4e1912877 100644 --- a/code/datums/diseases/advance/advance.dm +++ b/code/datums/diseases/advance/advance.dm @@ -1,509 +1,509 @@ -/* - - Advance Disease is a system for Virologist to Engineer their own disease with symptoms that have effects and properties - which add onto the overall disease. - - If you need help with creating new symptoms or expanding the advance disease, ask for Giacom on #coderbus. - -*/ - - - - -/* - - PROPERTIES - - */ - -/datum/disease/advance - name = "Unknown" // We will always let our Virologist name our disease. - desc = "An engineered disease which can contain a multitude of symptoms." - form = "Advance Disease" // Will let med-scanners know that this disease was engineered. - agent = "advance microbes" - max_stages = 5 - spread_text = "Unknown" - viable_mobtypes = list(/mob/living/carbon/human, /mob/living/carbon/monkey) - - // NEW VARS - var/list/properties = list() - var/list/symptoms = list() // The symptoms of the disease. - var/id = "" - var/processing = FALSE - var/mutable = TRUE //set to FALSE to prevent most in-game methods of altering the disease via virology - var/oldres //To prevent setting new cures unless resistance changes. - - // The order goes from easy to cure to hard to cure. Keep in mind that sentient diseases pick two cures from tier 6 and up, ensure they wont react away in bodies. - var/static/list/advance_cures = list( - list( // level 1 - /datum/reagent/copper, /datum/reagent/silver, /datum/reagent/iodine, /datum/reagent/iron, /datum/reagent/carbon - ), - list( // level 2 - /datum/reagent/potassium, /datum/reagent/consumable/ethanol, /datum/reagent/lithium, /datum/reagent/silicon, /datum/reagent/bromine - ), - list( // level 3 - /datum/reagent/consumable/sodiumchloride, /datum/reagent/consumable/sugar, /datum/reagent/consumable/orangejuice, /datum/reagent/consumable/tomatojuice, /datum/reagent/consumable/milk - ), - list( //level 4 - /datum/reagent/medicine/spaceacillin, /datum/reagent/medicine/salglu_solution, /datum/reagent/medicine/epinephrine, /datum/reagent/medicine/charcoal - ), - list( //level 5 - /datum/reagent/fuel/oil, /datum/reagent/medicine/synaptizine, /datum/reagent/medicine/mannitol, /datum/reagent/drug/space_drugs, /datum/reagent/cryptobiolin - ), - list( // level 6 - /datum/reagent/phenol, /datum/reagent/medicine/inacusiate, /datum/reagent/medicine/oculine, /datum/reagent/medicine/antihol - ), - list( // level 7 - /datum/reagent/medicine/leporazine, /datum/reagent/toxin/mindbreaker, /datum/reagent/medicine/corazone - ), - list( // level 8 - /datum/reagent/pax, /datum/reagent/drug/happiness, /datum/reagent/medicine/ephedrine - ), - list( // level 9 - /datum/reagent/toxin/lipolicide, /datum/reagent/medicine/sal_acid - ), - list( // level 10 - /datum/reagent/medicine/haloperidol, /datum/reagent/drug/aranesp, /datum/reagent/medicine/diphenhydramine - ), - list( //level 11 - /datum/reagent/medicine/modafinil, /datum/reagent/toxin/anacea - ) - ) - -/* - - OLD PROCS - - */ - -/datum/disease/advance/New() - Refresh() - -/datum/disease/advance/Destroy() - if(processing) - for(var/datum/symptom/S in symptoms) - S.End(src) - return ..() - -/datum/disease/advance/try_infect(var/mob/living/infectee, make_copy = TRUE) - //see if we are more transmittable than enough diseases to replace them - //diseases replaced in this way do not confer immunity - var/list/advance_diseases = list() - for(var/datum/disease/advance/P in infectee.diseases) - advance_diseases += P - var/replace_num = advance_diseases.len + 1 - DISEASE_LIMIT //amount of diseases that need to be removed to fit this one - if(replace_num > 0) - sortTim(advance_diseases, /proc/cmp_advdisease_resistance_asc) - for(var/i in 1 to replace_num) - var/datum/disease/advance/competition = advance_diseases[i] - if(totalTransmittable() > competition.totalResistance()) - competition.cure(FALSE) - else - return FALSE //we are not strong enough to bully our way in - infect(infectee, make_copy) - return TRUE - -// Randomly pick a symptom to activate. -/datum/disease/advance/stage_act() - ..() - if(carrier || QDELETED(src)) // Could be cured in parent call. - return - - if(symptoms && symptoms.len) - if(!processing) - processing = TRUE - for(var/datum/symptom/S in symptoms) - if(S.Start(src)) //this will return FALSE if the symptom is neutered - S.next_activation = world.time + rand(S.symptom_delay_min * 10, S.symptom_delay_max * 10) - S.on_stage_change(src) - - for(var/datum/symptom/S in symptoms) - S.Activate(src) - -// Tell symptoms stage changed -/datum/disease/advance/update_stage(new_stage) - ..() - for(var/datum/symptom/S in symptoms) - S.on_stage_change(src) - -// Compares type then ID. -/datum/disease/advance/IsSame(datum/disease/advance/D) - - if(!(istype(D, /datum/disease/advance))) - return 0 - - if(GetDiseaseID() != D.GetDiseaseID()) - return 0 - return 1 - -// Returns the advance disease with a different reference memory. -/datum/disease/advance/Copy() - var/datum/disease/advance/A = ..() - QDEL_LIST(A.symptoms) - for(var/datum/symptom/S in symptoms) - A.symptoms += S.Copy() - A.properties = properties.Copy() - A.id = id - A.mutable = mutable - A.oldres = oldres - //this is a new disease starting over at stage 1, so processing is not copied - return A - -//Describe this disease to an admin in detail (for logging) -/datum/disease/advance/admin_details() - var/list/name_symptoms = list() - for(var/datum/symptom/S in symptoms) - name_symptoms += S.name - return "[name] sym:[english_list(name_symptoms)] r:[totalResistance()] s:[totalStealth()] ss:[totalStageSpeed()] t:[totalTransmittable()]" - -/* - - NEW PROCS - - */ - -// Mix the symptoms of two diseases (the src and the argument) -/datum/disease/advance/proc/Mix(datum/disease/advance/D) - if(!(IsSame(D))) - var/list/possible_symptoms = shuffle(D.symptoms) - for(var/datum/symptom/S in possible_symptoms) - AddSymptom(S.Copy()) - -/datum/disease/advance/proc/HasSymptom(datum/symptom/S) - for(var/datum/symptom/symp in symptoms) - if(symp.type == S.type) - return 1 - return 0 - -// Will generate new unique symptoms, use this if there are none. Returns a list of symptoms that were generated. -/datum/disease/advance/proc/GenerateSymptoms(level_min, level_max, amount_get = 0) - - . = list() // Symptoms we generated. - - // Generate symptoms. By default, we only choose non-deadly symptoms. - var/list/possible_symptoms = list() - for(var/symp in SSdisease.list_symptoms) - var/datum/symptom/S = new symp - if(S.naturally_occuring && S.level >= level_min && S.level <= level_max) - if(!HasSymptom(S)) - possible_symptoms += S - - if(!possible_symptoms.len) - return - - // Random chance to get more than one symptom - var/number_of = amount_get - if(!amount_get) - number_of = 1 - while(prob(20)) - number_of += 1 - - for(var/i = 1; number_of >= i && possible_symptoms.len; i++) - . += pick_n_take(possible_symptoms) - -/datum/disease/advance/proc/Refresh(new_name = FALSE) - GenerateProperties() - AssignProperties() - if(processing && symptoms && symptoms.len) - for(var/datum/symptom/S in symptoms) - S.Start(src) - S.on_stage_change(src) - id = null - - var/the_id = GetDiseaseID() - if(!SSdisease.archive_diseases[the_id]) - SSdisease.archive_diseases[the_id] = src // So we don't infinite loop - SSdisease.archive_diseases[the_id] = Copy() - if(new_name) - AssignName() - -//Generate disease properties based on the effects. Returns an associated list. -/datum/disease/advance/proc/GenerateProperties() - properties = list("resistance" = 0, "stealth" = 0, "stage_rate" = 0, "transmittable" = 0, "severity" = 0) - - for(var/datum/symptom/S in symptoms) - properties["resistance"] += S.resistance - properties["stealth"] += S.stealth - properties["stage_rate"] += S.stage_speed - properties["transmittable"] += S.transmittable - if(!S.neutered) - properties["severity"] = max(properties["severity"], S.severity) // severity is based on the highest severity non-neutered symptom - -// Assign the properties that are in the list. -/datum/disease/advance/proc/AssignProperties() - - if(properties && properties.len) - if(properties["stealth"] >= 2) - visibility_flags |= HIDDEN_SCANNER - else - visibility_flags &= ~HIDDEN_SCANNER - - if(properties["transmittable"]>=11) - SetSpread(DISEASE_SPREAD_AIRBORNE) - else if(properties["transmittable"]>=7) - SetSpread(DISEASE_SPREAD_CONTACT_SKIN) - else if(properties["transmittable"]>=3) - SetSpread(DISEASE_SPREAD_CONTACT_FLUIDS) - else - SetSpread(DISEASE_SPREAD_BLOOD) - - permeability_mod = max(CEILING(0.4 * properties["transmittable"], 1), 1) - cure_chance = 15 - clamp(properties["resistance"], -5, 5) // can be between 10 and 20 - stage_prob = max(properties["stage_rate"], 2) - SetSeverity(properties["severity"]) - GenerateCure(properties) - else - CRASH("Our properties were empty or null!") - - -// Assign the spread type and give it the correct description. -/datum/disease/advance/proc/SetSpread(spread_id) - switch(spread_id) - if(DISEASE_SPREAD_NON_CONTAGIOUS) - spread_flags = DISEASE_SPREAD_NON_CONTAGIOUS - spread_text = "None" - if(DISEASE_SPREAD_SPECIAL) - spread_flags = DISEASE_SPREAD_SPECIAL - spread_text = "None" - if(DISEASE_SPREAD_BLOOD) - spread_flags = DISEASE_SPREAD_BLOOD - spread_text = "Blood" - if(DISEASE_SPREAD_CONTACT_FLUIDS) - spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_FLUIDS - spread_text = "Fluids" - if(DISEASE_SPREAD_CONTACT_SKIN) - spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_FLUIDS | DISEASE_SPREAD_CONTACT_SKIN - spread_text = "On contact" - if(DISEASE_SPREAD_AIRBORNE) - spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_FLUIDS | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_AIRBORNE - spread_text = "Airborne" - -/datum/disease/advance/proc/SetSeverity(level_sev) - - switch(level_sev) - - if(-INFINITY to 0) - severity = DISEASE_SEVERITY_POSITIVE - if(1) - severity = DISEASE_SEVERITY_NONTHREAT - if(2) - severity = DISEASE_SEVERITY_MINOR - if(3) - severity = DISEASE_SEVERITY_MEDIUM - if(4) - severity = DISEASE_SEVERITY_HARMFUL - if(5) - severity = DISEASE_SEVERITY_DANGEROUS - if(6 to INFINITY) - severity = DISEASE_SEVERITY_BIOHAZARD - else - severity = "Unknown" - - -// Will generate a random cure, the more resistance the symptoms have, the harder the cure. -/datum/disease/advance/proc/GenerateCure() - if(properties && properties.len) - var/res = clamp(properties["resistance"] - (symptoms.len / 2), 1, advance_cures.len) - if(res == oldres) - return - cures = list(pick(advance_cures[res])) - oldres = res - // Get the cure name from the cure_id - var/datum/reagent/D = GLOB.chemical_reagents_list[cures[1]] - cure_text = D.name - -// Randomly generate a symptom, has a chance to lose or gain a symptom. -/datum/disease/advance/proc/Evolve(min_level, max_level, ignore_mutable = FALSE) - if(!mutable && !ignore_mutable) - return - var/list/generated_symptoms = GenerateSymptoms(min_level, max_level, 1) - if(length(generated_symptoms)) - var/datum/symptom/S = pick(generated_symptoms) - AddSymptom(S) - Refresh(TRUE) - -// Randomly remove a symptom. -/datum/disease/advance/proc/Devolve(ignore_mutable = FALSE) - if(!mutable && !ignore_mutable) - return - if(length(symptoms) > 1) - var/datum/symptom/S = pick(symptoms) - if(S) - RemoveSymptom(S) - Refresh(TRUE) - -// Randomly neuter a symptom. -/datum/disease/advance/proc/Neuter(ignore_mutable = FALSE) - if(!mutable && !ignore_mutable) - return - if(length(symptoms)) - var/datum/symptom/S = pick(symptoms) - if(S) - NeuterSymptom(S) - Refresh(TRUE) - -// Name the disease. -/datum/disease/advance/proc/AssignName(name = "Unknown") - var/datum/disease/advance/A = SSdisease.archive_diseases[GetDiseaseID()] - A.name = name - -// Return a unique ID of the disease. -/datum/disease/advance/GetDiseaseID() - if(!id) - var/list/L = list() - for(var/datum/symptom/S in symptoms) - if(S.neutered) - L += "[S.id]N" - else - L += S.id - L = sortList(L) // Sort the list so it doesn't matter which order the symptoms are in. - var/result = jointext(L, ":") - id = result - return id - - -// Add a symptom, if it is over the limit we take a random symptom away and add the new one. -/datum/disease/advance/proc/AddSymptom(datum/symptom/S) - - if(HasSymptom(S)) - return - - if(!(symptoms.len < (VIRUS_SYMPTOM_LIMIT - 1) + rand(-1, 1))) - RemoveSymptom(pick(symptoms)) - symptoms += S - S.OnAdd(src) - -// Simply removes the symptom. -/datum/disease/advance/proc/RemoveSymptom(datum/symptom/S) - symptoms -= S - S.OnRemove(src) - -// Neuter a symptom, so it will only affect stats -/datum/disease/advance/proc/NeuterSymptom(datum/symptom/S) - if(!S.neutered) - S.neutered = TRUE - S.name += " (neutered)" - S.OnRemove(src) - -/* - - Static Procs - -*/ - -// Mix a list of advance diseases and return the mixed result. -/proc/Advance_Mix(var/list/D_list) - var/list/diseases = list() - - for(var/datum/disease/advance/A in D_list) - diseases += A.Copy() - - if(!diseases.len) - return null - if(diseases.len <= 1) - return pick(diseases) // Just return the only entry. - - var/i = 0 - // Mix our diseases until we are left with only one result. - while(i < 20 && diseases.len > 1) - - i++ - - var/datum/disease/advance/D1 = pick(diseases) - diseases -= D1 - - var/datum/disease/advance/D2 = pick(diseases) - D2.Mix(D1) - - // Should be only 1 entry left, but if not let's only return a single entry - var/datum/disease/advance/to_return = pick(diseases) - to_return.Refresh(TRUE) - return to_return - -/proc/SetViruses(datum/reagent/R, list/data) - if(data) - var/list/preserve = list() - if(istype(data) && data["viruses"]) - for(var/datum/disease/A in data["viruses"]) - preserve += A.Copy() - R.data = data.Copy() - if(preserve.len) - R.data["viruses"] = preserve - -/proc/AdminCreateVirus(client/user) - - if(!user) - return - - var/i = VIRUS_SYMPTOM_LIMIT - - var/datum/disease/advance/D = new() - D.symptoms = list() - - var/list/symptoms = list() - symptoms += "Done" - symptoms += SSdisease.list_symptoms.Copy() - do - if(user) - var/symptom = input(user, "Choose a symptom to add ([i] remaining)", "Choose a Symptom") in sortList(symptoms, /proc/cmp_typepaths_asc) - if(isnull(symptom)) - return - else if(istext(symptom)) - i = 0 - else if(ispath(symptom)) - var/datum/symptom/S = new symptom - if(!D.HasSymptom(S)) - D.AddSymptom(S) - i -= 1 - while(i > 0) - - if(D.symptoms.len > 0) - - var/new_name = stripped_input(user, "Name your new disease.", "New Name") - if(!new_name) - return - D.Refresh() - D.AssignName(new_name) //Updates the master copy - D.name = new_name //Updates our copy - - var/list/targets = list("Random") - targets += sortNames(GLOB.human_list) - var/target = input(user, "Pick a viable human target for the disease.", "Disease Target") as null|anything in targets - - var/mob/living/carbon/human/H - if(!target) - return - if(target == "Random") - for(var/human in shuffle(GLOB.human_list)) - H = human - var/found = FALSE - if(!is_station_level(H.z)) - continue - if(!H.HasDisease(D)) - found = H.ForceContractDisease(D) - break - if(!found) - to_chat(user, "Could not find a valid target for the disease.") - else - H = target - if(istype(H) && D.infectable_biotypes & H.mob_biotypes) - H.ForceContractDisease(D) - else - to_chat(user, "Target could not be infected. Check mob biotype compatibility or resistances.") - return - - message_admins("[key_name_admin(user)] has triggered a custom virus outbreak of [D.admin_details()] in [ADMIN_LOOKUPFLW(H)]") - log_virus("[key_name(user)] has triggered a custom virus outbreak of [D.admin_details()] in [H]!") - - -/datum/disease/advance/proc/totalStageSpeed() - return properties["stage_rate"] - -/datum/disease/advance/proc/totalStealth() - return properties["stealth"] - -/datum/disease/advance/proc/totalResistance() - return properties["resistance"] - -/datum/disease/advance/proc/totalTransmittable() - return properties["transmittable"] +/* + + Advance Disease is a system for Virologist to Engineer their own disease with symptoms that have effects and properties + which add onto the overall disease. + + If you need help with creating new symptoms or expanding the advance disease, ask for Giacom on #coderbus. + +*/ + + + + +/* + + PROPERTIES + + */ + +/datum/disease/advance + name = "Unknown" // We will always let our Virologist name our disease. + desc = "An engineered disease which can contain a multitude of symptoms." + form = "Advance Disease" // Will let med-scanners know that this disease was engineered. + agent = "advance microbes" + max_stages = 5 + spread_text = "Unknown" + viable_mobtypes = list(/mob/living/carbon/human, /mob/living/carbon/monkey) + + // NEW VARS + var/list/properties = list() + var/list/symptoms = list() // The symptoms of the disease. + var/id = "" + var/processing = FALSE + var/mutable = TRUE //set to FALSE to prevent most in-game methods of altering the disease via virology + var/oldres //To prevent setting new cures unless resistance changes. + + // The order goes from easy to cure to hard to cure. Keep in mind that sentient diseases pick two cures from tier 6 and up, ensure they wont react away in bodies. + var/static/list/advance_cures = list( + list( // level 1 + /datum/reagent/copper, /datum/reagent/silver, /datum/reagent/iodine, /datum/reagent/iron, /datum/reagent/carbon + ), + list( // level 2 + /datum/reagent/potassium, /datum/reagent/consumable/ethanol, /datum/reagent/lithium, /datum/reagent/silicon, /datum/reagent/bromine + ), + list( // level 3 + /datum/reagent/consumable/sodiumchloride, /datum/reagent/consumable/sugar, /datum/reagent/consumable/orangejuice, /datum/reagent/consumable/tomatojuice, /datum/reagent/consumable/milk + ), + list( //level 4 + /datum/reagent/medicine/spaceacillin, /datum/reagent/medicine/salglu_solution, /datum/reagent/medicine/epinephrine, /datum/reagent/medicine/charcoal + ), + list( //level 5 + /datum/reagent/fuel/oil, /datum/reagent/medicine/synaptizine, /datum/reagent/medicine/mannitol, /datum/reagent/drug/space_drugs, /datum/reagent/cryptobiolin + ), + list( // level 6 + /datum/reagent/phenol, /datum/reagent/medicine/inacusiate, /datum/reagent/medicine/oculine, /datum/reagent/medicine/antihol + ), + list( // level 7 + /datum/reagent/medicine/leporazine, /datum/reagent/toxin/mindbreaker, /datum/reagent/medicine/corazone + ), + list( // level 8 + /datum/reagent/pax, /datum/reagent/drug/happiness, /datum/reagent/medicine/ephedrine + ), + list( // level 9 + /datum/reagent/toxin/lipolicide, /datum/reagent/medicine/sal_acid + ), + list( // level 10 + /datum/reagent/medicine/haloperidol, /datum/reagent/drug/aranesp, /datum/reagent/medicine/diphenhydramine + ), + list( //level 11 + /datum/reagent/medicine/modafinil, /datum/reagent/toxin/anacea + ) + ) + +/* + + OLD PROCS + + */ + +/datum/disease/advance/New() + Refresh() + +/datum/disease/advance/Destroy() + if(processing) + for(var/datum/symptom/S in symptoms) + S.End(src) + return ..() + +/datum/disease/advance/try_infect(var/mob/living/infectee, make_copy = TRUE) + //see if we are more transmittable than enough diseases to replace them + //diseases replaced in this way do not confer immunity + var/list/advance_diseases = list() + for(var/datum/disease/advance/P in infectee.diseases) + advance_diseases += P + var/replace_num = advance_diseases.len + 1 - DISEASE_LIMIT //amount of diseases that need to be removed to fit this one + if(replace_num > 0) + sortTim(advance_diseases, /proc/cmp_advdisease_resistance_asc) + for(var/i in 1 to replace_num) + var/datum/disease/advance/competition = advance_diseases[i] + if(totalTransmittable() > competition.totalResistance()) + competition.cure(FALSE) + else + return FALSE //we are not strong enough to bully our way in + infect(infectee, make_copy) + return TRUE + +// Randomly pick a symptom to activate. +/datum/disease/advance/stage_act() + ..() + if(carrier || QDELETED(src)) // Could be cured in parent call. + return + + if(symptoms && symptoms.len) + if(!processing) + processing = TRUE + for(var/datum/symptom/S in symptoms) + if(S.Start(src)) //this will return FALSE if the symptom is neutered + S.next_activation = world.time + rand(S.symptom_delay_min * 10, S.symptom_delay_max * 10) + S.on_stage_change(src) + + for(var/datum/symptom/S in symptoms) + S.Activate(src) + +// Tell symptoms stage changed +/datum/disease/advance/update_stage(new_stage) + ..() + for(var/datum/symptom/S in symptoms) + S.on_stage_change(src) + +// Compares type then ID. +/datum/disease/advance/IsSame(datum/disease/advance/D) + + if(!(istype(D, /datum/disease/advance))) + return 0 + + if(GetDiseaseID() != D.GetDiseaseID()) + return 0 + return 1 + +// Returns the advance disease with a different reference memory. +/datum/disease/advance/Copy() + var/datum/disease/advance/A = ..() + QDEL_LIST(A.symptoms) + for(var/datum/symptom/S in symptoms) + A.symptoms += S.Copy() + A.properties = properties.Copy() + A.id = id + A.mutable = mutable + A.oldres = oldres + //this is a new disease starting over at stage 1, so processing is not copied + return A + +//Describe this disease to an admin in detail (for logging) +/datum/disease/advance/admin_details() + var/list/name_symptoms = list() + for(var/datum/symptom/S in symptoms) + name_symptoms += S.name + return "[name] sym:[english_list(name_symptoms)] r:[totalResistance()] s:[totalStealth()] ss:[totalStageSpeed()] t:[totalTransmittable()]" + +/* + + NEW PROCS + + */ + +// Mix the symptoms of two diseases (the src and the argument) +/datum/disease/advance/proc/Mix(datum/disease/advance/D) + if(!(IsSame(D))) + var/list/possible_symptoms = shuffle(D.symptoms) + for(var/datum/symptom/S in possible_symptoms) + AddSymptom(S.Copy()) + +/datum/disease/advance/proc/HasSymptom(datum/symptom/S) + for(var/datum/symptom/symp in symptoms) + if(symp.type == S.type) + return 1 + return 0 + +// Will generate new unique symptoms, use this if there are none. Returns a list of symptoms that were generated. +/datum/disease/advance/proc/GenerateSymptoms(level_min, level_max, amount_get = 0) + + . = list() // Symptoms we generated. + + // Generate symptoms. By default, we only choose non-deadly symptoms. + var/list/possible_symptoms = list() + for(var/symp in SSdisease.list_symptoms) + var/datum/symptom/S = new symp + if(S.naturally_occuring && S.level >= level_min && S.level <= level_max) + if(!HasSymptom(S)) + possible_symptoms += S + + if(!possible_symptoms.len) + return + + // Random chance to get more than one symptom + var/number_of = amount_get + if(!amount_get) + number_of = 1 + while(prob(20)) + number_of += 1 + + for(var/i = 1; number_of >= i && possible_symptoms.len; i++) + . += pick_n_take(possible_symptoms) + +/datum/disease/advance/proc/Refresh(new_name = FALSE) + GenerateProperties() + AssignProperties() + if(processing && symptoms && symptoms.len) + for(var/datum/symptom/S in symptoms) + S.Start(src) + S.on_stage_change(src) + id = null + + var/the_id = GetDiseaseID() + if(!SSdisease.archive_diseases[the_id]) + SSdisease.archive_diseases[the_id] = src // So we don't infinite loop + SSdisease.archive_diseases[the_id] = Copy() + if(new_name) + AssignName() + +//Generate disease properties based on the effects. Returns an associated list. +/datum/disease/advance/proc/GenerateProperties() + properties = list("resistance" = 0, "stealth" = 0, "stage_rate" = 0, "transmittable" = 0, "severity" = 0) + + for(var/datum/symptom/S in symptoms) + properties["resistance"] += S.resistance + properties["stealth"] += S.stealth + properties["stage_rate"] += S.stage_speed + properties["transmittable"] += S.transmittable + if(!S.neutered) + properties["severity"] = max(properties["severity"], S.severity) // severity is based on the highest severity non-neutered symptom + +// Assign the properties that are in the list. +/datum/disease/advance/proc/AssignProperties() + + if(properties && properties.len) + if(properties["stealth"] >= 2) + visibility_flags |= HIDDEN_SCANNER + else + visibility_flags &= ~HIDDEN_SCANNER + + if(properties["transmittable"]>=11) + SetSpread(DISEASE_SPREAD_AIRBORNE) + else if(properties["transmittable"]>=7) + SetSpread(DISEASE_SPREAD_CONTACT_SKIN) + else if(properties["transmittable"]>=3) + SetSpread(DISEASE_SPREAD_CONTACT_FLUIDS) + else + SetSpread(DISEASE_SPREAD_BLOOD) + + permeability_mod = max(CEILING(0.4 * properties["transmittable"], 1), 1) + cure_chance = 15 - clamp(properties["resistance"], -5, 5) // can be between 10 and 20 + stage_prob = max(properties["stage_rate"], 2) + SetSeverity(properties["severity"]) + GenerateCure(properties) + else + CRASH("Our properties were empty or null!") + + +// Assign the spread type and give it the correct description. +/datum/disease/advance/proc/SetSpread(spread_id) + switch(spread_id) + if(DISEASE_SPREAD_NON_CONTAGIOUS) + spread_flags = DISEASE_SPREAD_NON_CONTAGIOUS + spread_text = "None" + if(DISEASE_SPREAD_SPECIAL) + spread_flags = DISEASE_SPREAD_SPECIAL + spread_text = "None" + if(DISEASE_SPREAD_BLOOD) + spread_flags = DISEASE_SPREAD_BLOOD + spread_text = "Blood" + if(DISEASE_SPREAD_CONTACT_FLUIDS) + spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_FLUIDS + spread_text = "Fluids" + if(DISEASE_SPREAD_CONTACT_SKIN) + spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_FLUIDS | DISEASE_SPREAD_CONTACT_SKIN + spread_text = "On contact" + if(DISEASE_SPREAD_AIRBORNE) + spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_FLUIDS | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_AIRBORNE + spread_text = "Airborne" + +/datum/disease/advance/proc/SetSeverity(level_sev) + + switch(level_sev) + + if(-INFINITY to 0) + severity = DISEASE_SEVERITY_POSITIVE + if(1) + severity = DISEASE_SEVERITY_NONTHREAT + if(2) + severity = DISEASE_SEVERITY_MINOR + if(3) + severity = DISEASE_SEVERITY_MEDIUM + if(4) + severity = DISEASE_SEVERITY_HARMFUL + if(5) + severity = DISEASE_SEVERITY_DANGEROUS + if(6 to INFINITY) + severity = DISEASE_SEVERITY_BIOHAZARD + else + severity = "Unknown" + + +// Will generate a random cure, the more resistance the symptoms have, the harder the cure. +/datum/disease/advance/proc/GenerateCure() + if(properties && properties.len) + var/res = clamp(properties["resistance"] - (symptoms.len / 2), 1, advance_cures.len) + if(res == oldres) + return + cures = list(pick(advance_cures[res])) + oldres = res + // Get the cure name from the cure_id + var/datum/reagent/D = GLOB.chemical_reagents_list[cures[1]] + cure_text = D.name + +// Randomly generate a symptom, has a chance to lose or gain a symptom. +/datum/disease/advance/proc/Evolve(min_level, max_level, ignore_mutable = FALSE) + if(!mutable && !ignore_mutable) + return + var/list/generated_symptoms = GenerateSymptoms(min_level, max_level, 1) + if(length(generated_symptoms)) + var/datum/symptom/S = pick(generated_symptoms) + AddSymptom(S) + Refresh(TRUE) + +// Randomly remove a symptom. +/datum/disease/advance/proc/Devolve(ignore_mutable = FALSE) + if(!mutable && !ignore_mutable) + return + if(length(symptoms) > 1) + var/datum/symptom/S = pick(symptoms) + if(S) + RemoveSymptom(S) + Refresh(TRUE) + +// Randomly neuter a symptom. +/datum/disease/advance/proc/Neuter(ignore_mutable = FALSE) + if(!mutable && !ignore_mutable) + return + if(length(symptoms)) + var/datum/symptom/S = pick(symptoms) + if(S) + NeuterSymptom(S) + Refresh(TRUE) + +// Name the disease. +/datum/disease/advance/proc/AssignName(name = "Unknown") + var/datum/disease/advance/A = SSdisease.archive_diseases[GetDiseaseID()] + A.name = name + +// Return a unique ID of the disease. +/datum/disease/advance/GetDiseaseID() + if(!id) + var/list/L = list() + for(var/datum/symptom/S in symptoms) + if(S.neutered) + L += "[S.id]N" + else + L += S.id + L = sortList(L) // Sort the list so it doesn't matter which order the symptoms are in. + var/result = jointext(L, ":") + id = result + return id + + +// Add a symptom, if it is over the limit we take a random symptom away and add the new one. +/datum/disease/advance/proc/AddSymptom(datum/symptom/S) + + if(HasSymptom(S)) + return + + if(!(symptoms.len < (VIRUS_SYMPTOM_LIMIT - 1) + rand(-1, 1))) + RemoveSymptom(pick(symptoms)) + symptoms += S + S.OnAdd(src) + +// Simply removes the symptom. +/datum/disease/advance/proc/RemoveSymptom(datum/symptom/S) + symptoms -= S + S.OnRemove(src) + +// Neuter a symptom, so it will only affect stats +/datum/disease/advance/proc/NeuterSymptom(datum/symptom/S) + if(!S.neutered) + S.neutered = TRUE + S.name += " (neutered)" + S.OnRemove(src) + +/* + + Static Procs + +*/ + +// Mix a list of advance diseases and return the mixed result. +/proc/Advance_Mix(var/list/D_list) + var/list/diseases = list() + + for(var/datum/disease/advance/A in D_list) + diseases += A.Copy() + + if(!diseases.len) + return null + if(diseases.len <= 1) + return pick(diseases) // Just return the only entry. + + var/i = 0 + // Mix our diseases until we are left with only one result. + while(i < 20 && diseases.len > 1) + + i++ + + var/datum/disease/advance/D1 = pick(diseases) + diseases -= D1 + + var/datum/disease/advance/D2 = pick(diseases) + D2.Mix(D1) + + // Should be only 1 entry left, but if not let's only return a single entry + var/datum/disease/advance/to_return = pick(diseases) + to_return.Refresh(TRUE) + return to_return + +/proc/SetViruses(datum/reagent/R, list/data) + if(data) + var/list/preserve = list() + if(istype(data) && data["viruses"]) + for(var/datum/disease/A in data["viruses"]) + preserve += A.Copy() + R.data = data.Copy() + if(preserve.len) + R.data["viruses"] = preserve + +/proc/AdminCreateVirus(client/user) + + if(!user) + return + + var/i = VIRUS_SYMPTOM_LIMIT + + var/datum/disease/advance/D = new() + D.symptoms = list() + + var/list/symptoms = list() + symptoms += "Done" + symptoms += SSdisease.list_symptoms.Copy() + do + if(user) + var/symptom = input(user, "Choose a symptom to add ([i] remaining)", "Choose a Symptom") in sortList(symptoms, /proc/cmp_typepaths_asc) + if(isnull(symptom)) + return + else if(istext(symptom)) + i = 0 + else if(ispath(symptom)) + var/datum/symptom/S = new symptom + if(!D.HasSymptom(S)) + D.AddSymptom(S) + i -= 1 + while(i > 0) + + if(D.symptoms.len > 0) + + var/new_name = stripped_input(user, "Name your new disease.", "New Name") + if(!new_name) + return + D.Refresh() + D.AssignName(new_name) //Updates the master copy + D.name = new_name //Updates our copy + + var/list/targets = list("Random") + targets += sortNames(GLOB.human_list) + var/target = input(user, "Pick a viable human target for the disease.", "Disease Target") as null|anything in targets + + var/mob/living/carbon/human/H + if(!target) + return + if(target == "Random") + for(var/human in shuffle(GLOB.human_list)) + H = human + var/found = FALSE + if(!is_station_level(H.z)) + continue + if(!H.HasDisease(D)) + found = H.ForceContractDisease(D) + break + if(!found) + to_chat(user, "Could not find a valid target for the disease.") + else + H = target + if(istype(H) && D.infectable_biotypes & H.mob_biotypes) + H.ForceContractDisease(D) + else + to_chat(user, "Target could not be infected. Check mob biotype compatibility or resistances.") + return + + message_admins("[key_name_admin(user)] has triggered a custom virus outbreak of [D.admin_details()] in [ADMIN_LOOKUPFLW(H)]") + log_virus("[key_name(user)] has triggered a custom virus outbreak of [D.admin_details()] in [H]!") + + +/datum/disease/advance/proc/totalStageSpeed() + return properties["stage_rate"] + +/datum/disease/advance/proc/totalStealth() + return properties["stealth"] + +/datum/disease/advance/proc/totalResistance() + return properties["resistance"] + +/datum/disease/advance/proc/totalTransmittable() + return properties["transmittable"] diff --git a/code/datums/diseases/advance/presets.dm b/code/datums/diseases/advance/presets.dm index 9113bc980f19..1924d92428e4 100644 --- a/code/datums/diseases/advance/presets.dm +++ b/code/datums/diseases/advance/presets.dm @@ -1,42 +1,42 @@ -// Cold -/datum/disease/advance/cold - copy_type = /datum/disease/advance - -/datum/disease/advance/cold/New() - name = "Cold" - symptoms = list(new/datum/symptom/sneeze) - ..() - -// Flu -/datum/disease/advance/flu - copy_type = /datum/disease/advance - -/datum/disease/advance/flu/New() - name = "Flu" - symptoms = list(new/datum/symptom/cough) - ..() - -//Randomly generated Disease, for virus crates and events -/datum/disease/advance/random - name = "Experimental Disease" - copy_type = /datum/disease/advance - -/datum/disease/advance/random/New(max_symptoms, max_level = 8) - if(!max_symptoms) - max_symptoms = rand(1, VIRUS_SYMPTOM_LIMIT) - var/list/datum/symptom/possible_symptoms = list() - for(var/symptom in subtypesof(/datum/symptom)) - var/datum/symptom/S = symptom - if(initial(S.level) > max_level) - continue - if(initial(S.level) <= 0) //unobtainable symptoms - continue - possible_symptoms += S - for(var/i in 1 to max_symptoms) - var/datum/symptom/chosen_symptom = pick_n_take(possible_symptoms) - if(chosen_symptom) - var/datum/symptom/S = new chosen_symptom - symptoms += S - Refresh() - - name = "Sample #[rand(1,10000)]" +// Cold +/datum/disease/advance/cold + copy_type = /datum/disease/advance + +/datum/disease/advance/cold/New() + name = "Cold" + symptoms = list(new/datum/symptom/sneeze) + ..() + +// Flu +/datum/disease/advance/flu + copy_type = /datum/disease/advance + +/datum/disease/advance/flu/New() + name = "Flu" + symptoms = list(new/datum/symptom/cough) + ..() + +//Randomly generated Disease, for virus crates and events +/datum/disease/advance/random + name = "Experimental Disease" + copy_type = /datum/disease/advance + +/datum/disease/advance/random/New(max_symptoms, max_level = 8) + if(!max_symptoms) + max_symptoms = rand(1, VIRUS_SYMPTOM_LIMIT) + var/list/datum/symptom/possible_symptoms = list() + for(var/symptom in subtypesof(/datum/symptom)) + var/datum/symptom/S = symptom + if(initial(S.level) > max_level) + continue + if(initial(S.level) <= 0) //unobtainable symptoms + continue + possible_symptoms += S + for(var/i in 1 to max_symptoms) + var/datum/symptom/chosen_symptom = pick_n_take(possible_symptoms) + if(chosen_symptom) + var/datum/symptom/S = new chosen_symptom + symptoms += S + Refresh() + + name = "Sample #[rand(1,10000)]" diff --git a/code/datums/diseases/advance/symptoms/beard.dm b/code/datums/diseases/advance/symptoms/beard.dm index 8ac5d655bd4a..9e855ccdccaa 100644 --- a/code/datums/diseases/advance/symptoms/beard.dm +++ b/code/datums/diseases/advance/symptoms/beard.dm @@ -1,43 +1,43 @@ -/* -////////////////////////////////////// -Facial Hypertrichosis - - No change to stealth. - Increases resistance. - Increases speed. - Slighlty increases transmittability - Intense Level. - -BONUS - Makes the mob grow a massive beard, regardless of gender. - -////////////////////////////////////// -*/ - -/datum/symptom/beard - - name = "Facial Hypertrichosis" - desc = "The virus increases hair production significantly, causing rapid beard growth." - stealth = 0 - resistance = 3 - stage_speed = 2 - transmittable = 1 - level = 4 - severity = 1 - symptom_delay_min = 18 - symptom_delay_max = 36 - - var/list/beard_order = list("Beard (Jensen)", "Beard (Full)", "Beard (Dwarf)", "Beard (Very Long)") - -/datum/symptom/beard/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/M = A.affected_mob - if(ishuman(M)) - var/mob/living/carbon/human/H = M - var/index = min(max(beard_order.Find(H.facial_hairstyle)+1, A.stage-1), beard_order.len) - if(index > 0 && H.facial_hairstyle != beard_order[index]) - to_chat(H, "Your chin itches.") - H.facial_hairstyle = beard_order[index] - H.update_hair() - +/* +////////////////////////////////////// +Facial Hypertrichosis + + No change to stealth. + Increases resistance. + Increases speed. + Slighlty increases transmittability + Intense Level. + +BONUS + Makes the mob grow a massive beard, regardless of gender. + +////////////////////////////////////// +*/ + +/datum/symptom/beard + + name = "Facial Hypertrichosis" + desc = "The virus increases hair production significantly, causing rapid beard growth." + stealth = 0 + resistance = 3 + stage_speed = 2 + transmittable = 1 + level = 4 + severity = 1 + symptom_delay_min = 18 + symptom_delay_max = 36 + + var/list/beard_order = list("Beard (Jensen)", "Beard (Full)", "Beard (Dwarf)", "Beard (Very Long)") + +/datum/symptom/beard/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/M = A.affected_mob + if(ishuman(M)) + var/mob/living/carbon/human/H = M + var/index = min(max(beard_order.Find(H.facial_hairstyle)+1, A.stage-1), beard_order.len) + if(index > 0 && H.facial_hairstyle != beard_order[index]) + to_chat(H, "Your chin itches.") + H.facial_hairstyle = beard_order[index] + H.update_hair() + diff --git a/code/datums/diseases/advance/symptoms/choking.dm b/code/datums/diseases/advance/symptoms/choking.dm index f6db915818d4..ead90c62658d 100644 --- a/code/datums/diseases/advance/symptoms/choking.dm +++ b/code/datums/diseases/advance/symptoms/choking.dm @@ -1,152 +1,152 @@ -/* -////////////////////////////////////// - -Choking - - Very very noticable. - Lowers resistance. - Decreases stage speed. - Decreases transmittablity tremendously. - Moderate Level. - -Bonus - Inflicts spikes of oxyloss - -////////////////////////////////////// -*/ - -/datum/symptom/choking - - name = "Choking" - desc = "The virus causes inflammation of the host's air conduits, leading to intermittent choking." - stealth = -3 - resistance = -2 - stage_speed = -2 - transmittable = -2 - level = 3 - severity = 3 - base_message_chance = 15 - symptom_delay_min = 10 - symptom_delay_max = 30 - threshold_descs = list( - "Stage Speed 8" = "Causes choking more frequently.", - "Stealth 4" = "The symptom remains hidden until active." - ) - -/datum/symptom/choking/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["stage_rate"] >= 8) - symptom_delay_min = 7 - symptom_delay_max = 24 - if(A.properties["stealth"] >= 4) - suppress_warning = TRUE - -/datum/symptom/choking/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/M = A.affected_mob - switch(A.stage) - if(1, 2) - if(prob(base_message_chance) && !suppress_warning) - to_chat(M, "[pick("You're having difficulty breathing.", "Your breathing becomes heavy.")]") - if(3, 4) - if(!suppress_warning) - to_chat(M, "[pick("Your windpipe feels like a straw.", "Your breathing becomes tremendously difficult.")]") - else - to_chat(M, "You feel very [pick("dizzy","woozy","faint")].") //fake bloodloss messages - Choke_stage_3_4(M, A) - M.emote("gasp") - else - to_chat(M, "[pick("You're choking!", "You can't breathe!")]") - Choke(M, A) - M.emote("gasp") - -/datum/symptom/choking/proc/Choke_stage_3_4(mob/living/M, datum/disease/advance/A) - M.adjustOxyLoss(rand(6,13)) - return 1 - -/datum/symptom/choking/proc/Choke(mob/living/M, datum/disease/advance/A) - M.adjustOxyLoss(rand(10,18)) - return 1 - -/* -////////////////////////////////////// - -Asphyxiation - - Very very noticable. - Decreases stage speed. - Decreases transmittablity. - -Bonus - Inflicts large spikes of oxyloss - Introduces Asphyxiating drugs to the system - Causes cardiac arrest on dying victims. - -////////////////////////////////////// -*/ - -/datum/symptom/asphyxiation - - name = "Acute respiratory distress syndrome" - desc = "The virus causes shrinking of the host's lungs, causing severe asphyxiation. May also lead to heart attacks." - stealth = -2 - resistance = -0 - stage_speed = -1 - transmittable = -2 - level = 7 - severity = 6 - base_message_chance = 15 - symptom_delay_min = 14 - symptom_delay_max = 30 - var/paralysis = FALSE - threshold_descs = list( - "Stage Speed 8" = "Additionally synthesizes pancuronium and sodium thiopental inside the host.", - "Transmission 8" = "Doubles the damage caused by the symptom." - ) - - -/datum/symptom/asphyxiation/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["stage_rate"] >= 8) - paralysis = TRUE - if(A.properties["transmittable"] >= 8) - power = 2 - -/datum/symptom/asphyxiation/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/M = A.affected_mob - switch(A.stage) - if(3, 4) - to_chat(M, "[pick("Your windpipe feels thin.", "Your lungs feel small.")]") - Asphyxiate_stage_3_4(M, A) - M.emote("gasp") - if(5) - to_chat(M, "[pick("Your lungs hurt!", "It hurts to breathe!")]") - Asphyxiate(M, A) - M.emote("gasp") - if(M.getOxyLoss() >= 120) - M.visible_message("[M] stops breathing, as if their lungs have totally collapsed!") - Asphyxiate_death(M, A) - return - -/datum/symptom/asphyxiation/proc/Asphyxiate_stage_3_4(mob/living/M, datum/disease/advance/A) - var/get_damage = rand(10,15) * power - M.adjustOxyLoss(get_damage) - return 1 - -/datum/symptom/asphyxiation/proc/Asphyxiate(mob/living/M, datum/disease/advance/A) - var/get_damage = rand(15,21) * power - M.adjustOxyLoss(get_damage) - if(paralysis) - M.reagents.add_reagent_list(list(/datum/reagent/toxin/pancuronium = 3, /datum/reagent/toxin/sodium_thiopental = 3)) - return 1 - -/datum/symptom/asphyxiation/proc/Asphyxiate_death(mob/living/M, datum/disease/advance/A) - var/get_damage = rand(25,35) * power - M.adjustOxyLoss(get_damage) - M.adjustOrganLoss(ORGAN_SLOT_BRAIN, get_damage/2) - return 1 +/* +////////////////////////////////////// + +Choking + + Very very noticable. + Lowers resistance. + Decreases stage speed. + Decreases transmittablity tremendously. + Moderate Level. + +Bonus + Inflicts spikes of oxyloss + +////////////////////////////////////// +*/ + +/datum/symptom/choking + + name = "Choking" + desc = "The virus causes inflammation of the host's air conduits, leading to intermittent choking." + stealth = -3 + resistance = -2 + stage_speed = -2 + transmittable = -2 + level = 3 + severity = 3 + base_message_chance = 15 + symptom_delay_min = 10 + symptom_delay_max = 30 + threshold_descs = list( + "Stage Speed 8" = "Causes choking more frequently.", + "Stealth 4" = "The symptom remains hidden until active." + ) + +/datum/symptom/choking/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["stage_rate"] >= 8) + symptom_delay_min = 7 + symptom_delay_max = 24 + if(A.properties["stealth"] >= 4) + suppress_warning = TRUE + +/datum/symptom/choking/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/M = A.affected_mob + switch(A.stage) + if(1, 2) + if(prob(base_message_chance) && !suppress_warning) + to_chat(M, "[pick("You're having difficulty breathing.", "Your breathing becomes heavy.")]") + if(3, 4) + if(!suppress_warning) + to_chat(M, "[pick("Your windpipe feels like a straw.", "Your breathing becomes tremendously difficult.")]") + else + to_chat(M, "You feel very [pick("dizzy","woozy","faint")].") //fake bloodloss messages + Choke_stage_3_4(M, A) + M.emote("gasp") + else + to_chat(M, "[pick("You're choking!", "You can't breathe!")]") + Choke(M, A) + M.emote("gasp") + +/datum/symptom/choking/proc/Choke_stage_3_4(mob/living/M, datum/disease/advance/A) + M.adjustOxyLoss(rand(6,13)) + return 1 + +/datum/symptom/choking/proc/Choke(mob/living/M, datum/disease/advance/A) + M.adjustOxyLoss(rand(10,18)) + return 1 + +/* +////////////////////////////////////// + +Asphyxiation + + Very very noticable. + Decreases stage speed. + Decreases transmittablity. + +Bonus + Inflicts large spikes of oxyloss + Introduces Asphyxiating drugs to the system + Causes cardiac arrest on dying victims. + +////////////////////////////////////// +*/ + +/datum/symptom/asphyxiation + + name = "Acute respiratory distress syndrome" + desc = "The virus causes shrinking of the host's lungs, causing severe asphyxiation. May also lead to heart attacks." + stealth = -2 + resistance = -0 + stage_speed = -1 + transmittable = -2 + level = 7 + severity = 6 + base_message_chance = 15 + symptom_delay_min = 14 + symptom_delay_max = 30 + var/paralysis = FALSE + threshold_descs = list( + "Stage Speed 8" = "Additionally synthesizes pancuronium and sodium thiopental inside the host.", + "Transmission 8" = "Doubles the damage caused by the symptom." + ) + + +/datum/symptom/asphyxiation/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["stage_rate"] >= 8) + paralysis = TRUE + if(A.properties["transmittable"] >= 8) + power = 2 + +/datum/symptom/asphyxiation/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/M = A.affected_mob + switch(A.stage) + if(3, 4) + to_chat(M, "[pick("Your windpipe feels thin.", "Your lungs feel small.")]") + Asphyxiate_stage_3_4(M, A) + M.emote("gasp") + if(5) + to_chat(M, "[pick("Your lungs hurt!", "It hurts to breathe!")]") + Asphyxiate(M, A) + M.emote("gasp") + if(M.getOxyLoss() >= 120) + M.visible_message("[M] stops breathing, as if their lungs have totally collapsed!") + Asphyxiate_death(M, A) + return + +/datum/symptom/asphyxiation/proc/Asphyxiate_stage_3_4(mob/living/M, datum/disease/advance/A) + var/get_damage = rand(10,15) * power + M.adjustOxyLoss(get_damage) + return 1 + +/datum/symptom/asphyxiation/proc/Asphyxiate(mob/living/M, datum/disease/advance/A) + var/get_damage = rand(15,21) * power + M.adjustOxyLoss(get_damage) + if(paralysis) + M.reagents.add_reagent_list(list(/datum/reagent/toxin/pancuronium = 3, /datum/reagent/toxin/sodium_thiopental = 3)) + return 1 + +/datum/symptom/asphyxiation/proc/Asphyxiate_death(mob/living/M, datum/disease/advance/A) + var/get_damage = rand(25,35) * power + M.adjustOxyLoss(get_damage) + M.adjustOrganLoss(ORGAN_SLOT_BRAIN, get_damage/2) + return 1 diff --git a/code/datums/diseases/advance/symptoms/confusion.dm b/code/datums/diseases/advance/symptoms/confusion.dm index 2ed48b67081e..07166f3509a8 100644 --- a/code/datums/diseases/advance/symptoms/confusion.dm +++ b/code/datums/diseases/advance/symptoms/confusion.dm @@ -1,64 +1,64 @@ -/* -////////////////////////////////////// - -Confusion - - Little bit hidden. - Lowers resistance. - Decreases stage speed. - Not very transmissibile. - Intense Level. - -Bonus - Makes the affected mob be confused for short periods of time. - -////////////////////////////////////// -*/ - -/datum/symptom/confusion - - name = "Confusion" - desc = "The virus interferes with the proper function of the neural system, leading to bouts of confusion and erratic movement." - stealth = 1 - resistance = -1 - stage_speed = -3 - transmittable = 0 - level = 4 - severity = 2 - base_message_chance = 25 - symptom_delay_min = 10 - symptom_delay_max = 30 - var/brain_damage = FALSE - threshold_descs = list( - "Resistance 6" = "Causes brain damage over time.", - "Transmission 6" = "Increases confusion duration and strength.", - "Stealth 4" = "The symptom remains hidden until active.", - ) - -/datum/symptom/confusion/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["resistance"] >= 6) - brain_damage = TRUE - if(A.properties["transmittable"] >= 6) - power = 1.5 - if(A.properties["stealth"] >= 4) - suppress_warning = TRUE - -/datum/symptom/confusion/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/carbon/M = A.affected_mob - switch(A.stage) - if(1, 2, 3, 4) - if(prob(base_message_chance) && !suppress_warning) - to_chat(M, "[pick("Your head hurts.", "Your mind blanks for a moment.")]") - else - to_chat(M, "You can't think straight!") - if(M.confused < 100) - M.confused += (16 * power) - if(brain_damage) - M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 3 * power, 80) - M.updatehealth() - - return +/* +////////////////////////////////////// + +Confusion + + Little bit hidden. + Lowers resistance. + Decreases stage speed. + Not very transmissibile. + Intense Level. + +Bonus + Makes the affected mob be confused for short periods of time. + +////////////////////////////////////// +*/ + +/datum/symptom/confusion + + name = "Confusion" + desc = "The virus interferes with the proper function of the neural system, leading to bouts of confusion and erratic movement." + stealth = 1 + resistance = -1 + stage_speed = -3 + transmittable = 0 + level = 4 + severity = 2 + base_message_chance = 25 + symptom_delay_min = 10 + symptom_delay_max = 30 + var/brain_damage = FALSE + threshold_descs = list( + "Resistance 6" = "Causes brain damage over time.", + "Transmission 6" = "Increases confusion duration and strength.", + "Stealth 4" = "The symptom remains hidden until active.", + ) + +/datum/symptom/confusion/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["resistance"] >= 6) + brain_damage = TRUE + if(A.properties["transmittable"] >= 6) + power = 1.5 + if(A.properties["stealth"] >= 4) + suppress_warning = TRUE + +/datum/symptom/confusion/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/carbon/M = A.affected_mob + switch(A.stage) + if(1, 2, 3, 4) + if(prob(base_message_chance) && !suppress_warning) + to_chat(M, "[pick("Your head hurts.", "Your mind blanks for a moment.")]") + else + to_chat(M, "You can't think straight!") + if(M.confused < 100) + M.confused += (16 * power) + if(brain_damage) + M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 3 * power, 80) + M.updatehealth() + + return diff --git a/code/datums/diseases/advance/symptoms/cough.dm b/code/datums/diseases/advance/symptoms/cough.dm index 57c67458089c..1ee6f7d2eb55 100644 --- a/code/datums/diseases/advance/symptoms/cough.dm +++ b/code/datums/diseases/advance/symptoms/cough.dm @@ -1,78 +1,78 @@ -/* -////////////////////////////////////// - -Coughing - - Noticable. - Little Resistance. - Doesn't increase stage speed much. - Transmissibile. - Low Level. - -BONUS - Spreads the virus in a small square around the host. - Can force the affected mob to drop small items! - -////////////////////////////////////// -*/ - -/datum/symptom/cough - - name = "Cough" - desc = "The virus irritates the throat of the host, causing occasional coughing. Each cough will try to infect bystanders who are within 1 tile of the host with the virus." - stealth = -1 - resistance = 3 - stage_speed = 1 - transmittable = 2 - level = 1 - severity = 1 - base_message_chance = 15 - symptom_delay_min = 2 - symptom_delay_max = 15 - var/spread_range = 1 - threshold_descs = list( - "Resistance 11" = "The host will drop small items when coughing.", - "Resistance 15" = "Occasionally causes coughing fits that stun the host. The extra coughs do not spread the virus.", - "Stage Speed 6" = "Increases cough frequency.", - "Transmission 7" = "Coughing will now infect bystanders up to 2 tiles away.", - "Stealth 4" = "The symptom remains hidden until active.", - ) - -/datum/symptom/cough/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["stealth"] >= 4) - suppress_warning = TRUE - if(A.properties["transmittable"] >= 7) - spread_range = 2 - if(A.properties["resistance"] >= 11) //strong enough to drop items - power = 1.5 - if(A.properties["resistance"] >= 15) //strong enough to stun (occasionally) - power = 2 - if(A.properties["stage_rate"] >= 6) //cough more often - symptom_delay_max = 10 - -/datum/symptom/cough/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/M = A.affected_mob - if(HAS_TRAIT(M, TRAIT_SOOTHED_THROAT)) - return - switch(A.stage) - if(1, 2, 3) - if(prob(base_message_chance) && !suppress_warning) - to_chat(M, "[pick("You swallow excess mucus.", "You lightly cough.")]") - else - M.emote("cough") - if(M.CanSpreadAirborneDisease()) - A.spread(spread_range) - if(power >= 1.5) - var/obj/item/I = M.get_active_held_item() - if(I && I.w_class == WEIGHT_CLASS_TINY) - M.dropItemToGround(I) - if(power >= 2 && prob(30)) - to_chat(M, "[pick("You have a coughing fit!", "You can't stop coughing!")]") - M.Immobilize(20) - addtimer(CALLBACK(M, /mob/.proc/emote, "cough"), 6) - addtimer(CALLBACK(M, /mob/.proc/emote, "cough"), 12) - addtimer(CALLBACK(M, /mob/.proc/emote, "cough"), 18) +/* +////////////////////////////////////// + +Coughing + + Noticable. + Little Resistance. + Doesn't increase stage speed much. + Transmissibile. + Low Level. + +BONUS + Spreads the virus in a small square around the host. + Can force the affected mob to drop small items! + +////////////////////////////////////// +*/ + +/datum/symptom/cough + + name = "Cough" + desc = "The virus irritates the throat of the host, causing occasional coughing. Each cough will try to infect bystanders who are within 1 tile of the host with the virus." + stealth = -1 + resistance = 3 + stage_speed = 1 + transmittable = 2 + level = 1 + severity = 1 + base_message_chance = 15 + symptom_delay_min = 2 + symptom_delay_max = 15 + var/spread_range = 1 + threshold_descs = list( + "Resistance 11" = "The host will drop small items when coughing.", + "Resistance 15" = "Occasionally causes coughing fits that stun the host. The extra coughs do not spread the virus.", + "Stage Speed 6" = "Increases cough frequency.", + "Transmission 7" = "Coughing will now infect bystanders up to 2 tiles away.", + "Stealth 4" = "The symptom remains hidden until active.", + ) + +/datum/symptom/cough/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["stealth"] >= 4) + suppress_warning = TRUE + if(A.properties["transmittable"] >= 7) + spread_range = 2 + if(A.properties["resistance"] >= 11) //strong enough to drop items + power = 1.5 + if(A.properties["resistance"] >= 15) //strong enough to stun (occasionally) + power = 2 + if(A.properties["stage_rate"] >= 6) //cough more often + symptom_delay_max = 10 + +/datum/symptom/cough/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/M = A.affected_mob + if(HAS_TRAIT(M, TRAIT_SOOTHED_THROAT)) + return + switch(A.stage) + if(1, 2, 3) + if(prob(base_message_chance) && !suppress_warning) + to_chat(M, "[pick("You swallow excess mucus.", "You lightly cough.")]") + else + M.emote("cough") + if(M.CanSpreadAirborneDisease()) + A.spread(spread_range) + if(power >= 1.5) + var/obj/item/I = M.get_active_held_item() + if(I && I.w_class == WEIGHT_CLASS_TINY) + M.dropItemToGround(I) + if(power >= 2 && prob(30)) + to_chat(M, "[pick("You have a coughing fit!", "You can't stop coughing!")]") + M.Immobilize(20) + addtimer(CALLBACK(M, /mob/.proc/emote, "cough"), 6) + addtimer(CALLBACK(M, /mob/.proc/emote, "cough"), 12) + addtimer(CALLBACK(M, /mob/.proc/emote, "cough"), 18) diff --git a/code/datums/diseases/advance/symptoms/deafness.dm b/code/datums/diseases/advance/symptoms/deafness.dm index 0d8027917fe3..ea607f2a31fc 100644 --- a/code/datums/diseases/advance/symptoms/deafness.dm +++ b/code/datums/diseases/advance/symptoms/deafness.dm @@ -1,61 +1,61 @@ -/* -////////////////////////////////////// - -Deafness - - Slightly noticable. - Lowers resistance. - Decreases stage speed slightly. - Decreases transmittablity. - Intense Level. - -Bonus - Causes intermittent loss of hearing. - -////////////////////////////////////// -*/ - -/datum/symptom/deafness - - name = "Deafness" - desc = "The virus causes inflammation of the eardrums, causing intermittent deafness." - stealth = -1 - resistance = -2 - stage_speed = -1 - transmittable = -3 - level = 4 - severity = 4 - base_message_chance = 100 - symptom_delay_min = 25 - symptom_delay_max = 80 - threshold_descs = list( - "Resistance 9" = "Causes permanent deafness, instead of intermittent.", - "Stealth 4" = "The symptom remains hidden until active.", - ) - -/datum/symptom/deafness/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["stealth"] >= 4) - suppress_warning = TRUE - if(A.properties["resistance"] >= 9) //permanent deafness - power = 2 - -/datum/symptom/deafness/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/carbon/M = A.affected_mob - switch(A.stage) - if(3, 4) - if(prob(base_message_chance) && !suppress_warning) - to_chat(M, "[pick("You hear a ringing in your ear.", "Your ears pop.")]") - if(5) - if(power >= 2) - var/obj/item/organ/ears/ears = M.getorganslot(ORGAN_SLOT_EARS) - if(istype(ears) && ears.damage < ears.maxHealth) - to_chat(M, "Your ears pop painfully and start bleeding!") - ears.damage = max(ears.damage, ears.maxHealth) - M.emote("scream") - else - to_chat(M, "Your ears pop and begin ringing loudly!") - M.minimumDeafTicks(20) +/* +////////////////////////////////////// + +Deafness + + Slightly noticable. + Lowers resistance. + Decreases stage speed slightly. + Decreases transmittablity. + Intense Level. + +Bonus + Causes intermittent loss of hearing. + +////////////////////////////////////// +*/ + +/datum/symptom/deafness + + name = "Deafness" + desc = "The virus causes inflammation of the eardrums, causing intermittent deafness." + stealth = -1 + resistance = -2 + stage_speed = -1 + transmittable = -3 + level = 4 + severity = 4 + base_message_chance = 100 + symptom_delay_min = 25 + symptom_delay_max = 80 + threshold_descs = list( + "Resistance 9" = "Causes permanent deafness, instead of intermittent.", + "Stealth 4" = "The symptom remains hidden until active.", + ) + +/datum/symptom/deafness/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["stealth"] >= 4) + suppress_warning = TRUE + if(A.properties["resistance"] >= 9) //permanent deafness + power = 2 + +/datum/symptom/deafness/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/carbon/M = A.affected_mob + switch(A.stage) + if(3, 4) + if(prob(base_message_chance) && !suppress_warning) + to_chat(M, "[pick("You hear a ringing in your ear.", "Your ears pop.")]") + if(5) + if(power >= 2) + var/obj/item/organ/ears/ears = M.getorganslot(ORGAN_SLOT_EARS) + if(istype(ears) && ears.damage < ears.maxHealth) + to_chat(M, "Your ears pop painfully and start bleeding!") + ears.damage = max(ears.damage, ears.maxHealth) + M.emote("scream") + else + to_chat(M, "Your ears pop and begin ringing loudly!") + M.minimumDeafTicks(20) diff --git a/code/datums/diseases/advance/symptoms/dizzy.dm b/code/datums/diseases/advance/symptoms/dizzy.dm index f23114de711e..a4f486cedb3a 100644 --- a/code/datums/diseases/advance/symptoms/dizzy.dm +++ b/code/datums/diseases/advance/symptoms/dizzy.dm @@ -1,56 +1,56 @@ -/* -////////////////////////////////////// - -Dizziness - - Hidden. - Lowers resistance considerably. - Decreases stage speed. - Reduced transmittability - Intense Level. - -Bonus - Shakes the affected mob's screen for short periods. - -////////////////////////////////////// -*/ - -/datum/symptom/dizzy // Not the egg - - name = "Dizziness" - desc = "The virus causes inflammation of the vestibular system, leading to bouts of dizziness." - resistance = -2 - stage_speed = -3 - transmittable = -1 - level = 4 - severity = 2 - base_message_chance = 50 - symptom_delay_min = 15 - symptom_delay_max = 30 - threshold_descs = list( - "Transmission 6" = "Also causes druggy vision.", - "Stealth 4" = "The symptom remains hidden until active.", - ) - -/datum/symptom/dizzy/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["stealth"] >= 4) - suppress_warning = TRUE - if(A.properties["transmittable"] >= 6) //druggy - power = 2 - -/datum/symptom/dizzy/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/M = A.affected_mob - switch(A.stage) - if(1, 2, 3, 4) - if(prob(base_message_chance) && !suppress_warning) - to_chat(M, "[pick("You feel dizzy.", "Your head spins.")]") - else - to_chat(M, "A wave of dizziness washes over you!") - if(M.dizziness <= 70) - M.dizziness += 30 - if(power >= 2) - M.set_drugginess(40) +/* +////////////////////////////////////// + +Dizziness + + Hidden. + Lowers resistance considerably. + Decreases stage speed. + Reduced transmittability + Intense Level. + +Bonus + Shakes the affected mob's screen for short periods. + +////////////////////////////////////// +*/ + +/datum/symptom/dizzy // Not the egg + + name = "Dizziness" + desc = "The virus causes inflammation of the vestibular system, leading to bouts of dizziness." + resistance = -2 + stage_speed = -3 + transmittable = -1 + level = 4 + severity = 2 + base_message_chance = 50 + symptom_delay_min = 15 + symptom_delay_max = 30 + threshold_descs = list( + "Transmission 6" = "Also causes druggy vision.", + "Stealth 4" = "The symptom remains hidden until active.", + ) + +/datum/symptom/dizzy/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["stealth"] >= 4) + suppress_warning = TRUE + if(A.properties["transmittable"] >= 6) //druggy + power = 2 + +/datum/symptom/dizzy/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/M = A.affected_mob + switch(A.stage) + if(1, 2, 3, 4) + if(prob(base_message_chance) && !suppress_warning) + to_chat(M, "[pick("You feel dizzy.", "Your head spins.")]") + else + to_chat(M, "A wave of dizziness washes over you!") + if(M.dizziness <= 70) + M.dizziness += 30 + if(power >= 2) + M.set_drugginess(40) diff --git a/code/datums/diseases/advance/symptoms/fever.dm b/code/datums/diseases/advance/symptoms/fever.dm index 106d01c8d36d..2af943a474a3 100644 --- a/code/datums/diseases/advance/symptoms/fever.dm +++ b/code/datums/diseases/advance/symptoms/fever.dm @@ -1,76 +1,76 @@ -/* -////////////////////////////////////// - -Fever - - No change to hidden. - Increases resistance. - Increases stage speed. - Little transmittable. - Low level. - -Bonus - Heats up your body. - -////////////////////////////////////// -*/ - -/datum/symptom/fever - name = "Fever" - desc = "The virus causes a febrile response from the host, raising its body temperature." - stealth = 0 - resistance = 3 - stage_speed = 3 - transmittable = 2 - level = 2 - severity = 2 - base_message_chance = 20 - symptom_delay_min = 10 - symptom_delay_max = 30 - var/unsafe = FALSE //over the heat threshold - threshold_descs = list( - "Resistance 5" = "Increases fever intensity, fever can overheat and harm the host.", - "Resistance 10" = "Further increases fever intensity.", - ) - -/datum/symptom/fever/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["resistance"] >= 5) //dangerous fever - power = 1.5 - unsafe = TRUE - if(A.properties["resistance"] >= 10) - power = 2.5 - set_body_temp(A.affected_mob, A) - -/datum/symptom/fever/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/carbon/M = A.affected_mob - if(!unsafe || A.stage < 4) - to_chat(M, "[pick("You feel hot.", "You feel like you're burning.")]") - else - to_chat(M, "[pick("You feel too hot.", "You feel like your blood is boiling.")]") - -/** - * set_body_temp Sets the body temp change - * - * Sets the body temp change to the mob based on the stage and resistance of the disease - * arguments: - * * mob/living/M The mob to apply changes to - * * datum/disease/advance/A The disease applying the symptom - */ -/datum/symptom/fever/proc/set_body_temp(mob/living/M, datum/disease/advance/A) - M.add_body_temperature_change("fever", (6 * power) * A.stage) - -/// Update the body temp change based on the new stage -/datum/symptom/fever/on_stage_change(datum/disease/advance/A) - . = ..() - if(.) - set_body_temp(A.affected_mob, A) - -/// remove the body temp change when removing symptom -/datum/symptom/fever/End(datum/disease/advance/A) - var/mob/living/carbon/M = A.affected_mob - if(M) - M.remove_body_temperature_change("fever") +/* +////////////////////////////////////// + +Fever + + No change to hidden. + Increases resistance. + Increases stage speed. + Little transmittable. + Low level. + +Bonus + Heats up your body. + +////////////////////////////////////// +*/ + +/datum/symptom/fever + name = "Fever" + desc = "The virus causes a febrile response from the host, raising its body temperature." + stealth = 0 + resistance = 3 + stage_speed = 3 + transmittable = 2 + level = 2 + severity = 2 + base_message_chance = 20 + symptom_delay_min = 10 + symptom_delay_max = 30 + var/unsafe = FALSE //over the heat threshold + threshold_descs = list( + "Resistance 5" = "Increases fever intensity, fever can overheat and harm the host.", + "Resistance 10" = "Further increases fever intensity.", + ) + +/datum/symptom/fever/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["resistance"] >= 5) //dangerous fever + power = 1.5 + unsafe = TRUE + if(A.properties["resistance"] >= 10) + power = 2.5 + set_body_temp(A.affected_mob, A) + +/datum/symptom/fever/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/carbon/M = A.affected_mob + if(!unsafe || A.stage < 4) + to_chat(M, "[pick("You feel hot.", "You feel like you're burning.")]") + else + to_chat(M, "[pick("You feel too hot.", "You feel like your blood is boiling.")]") + +/** + * set_body_temp Sets the body temp change + * + * Sets the body temp change to the mob based on the stage and resistance of the disease + * arguments: + * * mob/living/M The mob to apply changes to + * * datum/disease/advance/A The disease applying the symptom + */ +/datum/symptom/fever/proc/set_body_temp(mob/living/M, datum/disease/advance/A) + M.add_body_temperature_change("fever", (6 * power) * A.stage) + +/// Update the body temp change based on the new stage +/datum/symptom/fever/on_stage_change(datum/disease/advance/A) + . = ..() + if(.) + set_body_temp(A.affected_mob, A) + +/// remove the body temp change when removing symptom +/datum/symptom/fever/End(datum/disease/advance/A) + var/mob/living/carbon/M = A.affected_mob + if(M) + M.remove_body_temperature_change("fever") diff --git a/code/datums/diseases/advance/symptoms/flesh_eating.dm b/code/datums/diseases/advance/symptoms/flesh_eating.dm index 2fbc4d8a4a69..a8d408ba8758 100644 --- a/code/datums/diseases/advance/symptoms/flesh_eating.dm +++ b/code/datums/diseases/advance/symptoms/flesh_eating.dm @@ -1,134 +1,134 @@ -/* -////////////////////////////////////// - -Necrotizing Fasciitis (AKA Flesh-Eating Disease) - - Very very noticable. - Lowers resistance tremendously. - No changes to stage speed. - Decreases transmittablity temrendously. - Fatal Level. - -Bonus - Deals brute damage over time. - -////////////////////////////////////// -*/ - -/datum/symptom/flesh_eating - - name = "Necrotizing Fasciitis" - desc = "The virus aggressively attacks body cells, necrotizing tissues and organs." - stealth = -3 - resistance = -4 - stage_speed = 0 - transmittable = -3 - level = 6 - severity = 5 - base_message_chance = 50 - symptom_delay_min = 15 - symptom_delay_max = 60 - var/bleed = FALSE - var/pain = FALSE - threshold_descs = list( - "Resistance 7" = "Host will bleed profusely during necrosis.", - "Transmission 8" = "Causes extreme pain to the host, weakening it.", - ) - -/datum/symptom/flesh_eating/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["resistance"] >= 7) //extra bleeding - bleed = TRUE - if(A.properties["transmittable"] >= 8) //extra stamina damage - pain = TRUE - -/datum/symptom/flesh_eating/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/M = A.affected_mob - switch(A.stage) - if(2,3) - if(prob(base_message_chance)) - to_chat(M, "[pick("You feel a sudden pain across your body.", "Drops of blood appear suddenly on your skin.")]") - if(4,5) - to_chat(M, "[pick("You cringe as a violent pain takes over your body.", "It feels like your body is eating itself inside out.", "IT HURTS.")]") - Flesheat(M, A) - -/datum/symptom/flesh_eating/proc/Flesheat(mob/living/M, datum/disease/advance/A) - var/get_damage = rand(15,25) * power - M.take_overall_damage(brute = get_damage, required_status = BODYPART_ORGANIC) - if(pain) - M.adjustStaminaLoss(get_damage * 2) - if(bleed) - if(ishuman(M)) - var/mob/living/carbon/human/H = M - H.bleed_rate += 5 * power - return 1 - -/* -////////////////////////////////////// - -Autophagocytosis (AKA Programmed mass cell death) - - Very noticable. - Lowers resistance. - Fast stage speed. - Decreases transmittablity. - Fatal Level. - -Bonus - Deals brute damage over time. - -////////////////////////////////////// -*/ - -/datum/symptom/flesh_death - - name = "Autophagocytosis Necrosis" - desc = "The virus rapidly consumes infected cells, leading to heavy and widespread damage." - stealth = -2 - resistance = -2 - stage_speed = 1 - transmittable = -2 - level = 7 - severity = 6 - base_message_chance = 50 - symptom_delay_min = 3 - symptom_delay_max = 6 - var/chems = FALSE - var/zombie = FALSE - threshold_descs = list( - "Stage Speed 7" = "Synthesizes Heparin and Lipolicide inside the host, causing increased bleeding and hunger.", - "Stealth 5" = "The symptom remains hidden until active.", - ) - -/datum/symptom/flesh_death/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["stealth"] >= 5) - suppress_warning = TRUE - if(A.properties["stage_rate"] >= 7) //bleeding and hunger - chems = TRUE - -/datum/symptom/flesh_death/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/M = A.affected_mob - switch(A.stage) - if(2,3) - if(prob(base_message_chance) && !suppress_warning) - to_chat(M, "[pick("You feel your body break apart.", "Your skin rubs off like dust.")]") - if(4,5) - if(prob(base_message_chance / 2)) //reduce spam - to_chat(M, "[pick("You feel your muscles weakening.", "Some of your skin detaches itself.", "You feel sandy.")]") - Flesh_death(M, A) - -/datum/symptom/flesh_death/proc/Flesh_death(mob/living/M, datum/disease/advance/A) - var/get_damage = rand(6,10) - M.take_overall_damage(brute = get_damage, required_status = BODYPART_ORGANIC) - if(chems) - M.reagents.add_reagent_list(list(/datum/reagent/toxin/heparin = 2, /datum/reagent/toxin/lipolicide = 2)) - if(zombie) - M.reagents.add_reagent(/datum/reagent/romerol, 1) - return 1 +/* +////////////////////////////////////// + +Necrotizing Fasciitis (AKA Flesh-Eating Disease) + + Very very noticable. + Lowers resistance tremendously. + No changes to stage speed. + Decreases transmittablity temrendously. + Fatal Level. + +Bonus + Deals brute damage over time. + +////////////////////////////////////// +*/ + +/datum/symptom/flesh_eating + + name = "Necrotizing Fasciitis" + desc = "The virus aggressively attacks body cells, necrotizing tissues and organs." + stealth = -3 + resistance = -4 + stage_speed = 0 + transmittable = -3 + level = 6 + severity = 5 + base_message_chance = 50 + symptom_delay_min = 15 + symptom_delay_max = 60 + var/bleed = FALSE + var/pain = FALSE + threshold_descs = list( + "Resistance 7" = "Host will bleed profusely during necrosis.", + "Transmission 8" = "Causes extreme pain to the host, weakening it.", + ) + +/datum/symptom/flesh_eating/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["resistance"] >= 7) //extra bleeding + bleed = TRUE + if(A.properties["transmittable"] >= 8) //extra stamina damage + pain = TRUE + +/datum/symptom/flesh_eating/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/M = A.affected_mob + switch(A.stage) + if(2,3) + if(prob(base_message_chance)) + to_chat(M, "[pick("You feel a sudden pain across your body.", "Drops of blood appear suddenly on your skin.")]") + if(4,5) + to_chat(M, "[pick("You cringe as a violent pain takes over your body.", "It feels like your body is eating itself inside out.", "IT HURTS.")]") + Flesheat(M, A) + +/datum/symptom/flesh_eating/proc/Flesheat(mob/living/M, datum/disease/advance/A) + var/get_damage = rand(15,25) * power + M.take_overall_damage(brute = get_damage, required_status = BODYPART_ORGANIC) + if(pain) + M.adjustStaminaLoss(get_damage * 2) + if(bleed) + if(ishuman(M)) + var/mob/living/carbon/human/H = M + H.bleed_rate += 5 * power + return 1 + +/* +////////////////////////////////////// + +Autophagocytosis (AKA Programmed mass cell death) + + Very noticable. + Lowers resistance. + Fast stage speed. + Decreases transmittablity. + Fatal Level. + +Bonus + Deals brute damage over time. + +////////////////////////////////////// +*/ + +/datum/symptom/flesh_death + + name = "Autophagocytosis Necrosis" + desc = "The virus rapidly consumes infected cells, leading to heavy and widespread damage." + stealth = -2 + resistance = -2 + stage_speed = 1 + transmittable = -2 + level = 7 + severity = 6 + base_message_chance = 50 + symptom_delay_min = 3 + symptom_delay_max = 6 + var/chems = FALSE + var/zombie = FALSE + threshold_descs = list( + "Stage Speed 7" = "Synthesizes Heparin and Lipolicide inside the host, causing increased bleeding and hunger.", + "Stealth 5" = "The symptom remains hidden until active.", + ) + +/datum/symptom/flesh_death/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["stealth"] >= 5) + suppress_warning = TRUE + if(A.properties["stage_rate"] >= 7) //bleeding and hunger + chems = TRUE + +/datum/symptom/flesh_death/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/M = A.affected_mob + switch(A.stage) + if(2,3) + if(prob(base_message_chance) && !suppress_warning) + to_chat(M, "[pick("You feel your body break apart.", "Your skin rubs off like dust.")]") + if(4,5) + if(prob(base_message_chance / 2)) //reduce spam + to_chat(M, "[pick("You feel your muscles weakening.", "Some of your skin detaches itself.", "You feel sandy.")]") + Flesh_death(M, A) + +/datum/symptom/flesh_death/proc/Flesh_death(mob/living/M, datum/disease/advance/A) + var/get_damage = rand(6,10) + M.take_overall_damage(brute = get_damage, required_status = BODYPART_ORGANIC) + if(chems) + M.reagents.add_reagent_list(list(/datum/reagent/toxin/heparin = 2, /datum/reagent/toxin/lipolicide = 2)) + if(zombie) + M.reagents.add_reagent(/datum/reagent/romerol, 1) + return 1 diff --git a/code/datums/diseases/advance/symptoms/genetics.dm b/code/datums/diseases/advance/symptoms/genetics.dm index 5f534faeb719..0cc5d6dc8c25 100644 --- a/code/datums/diseases/advance/symptoms/genetics.dm +++ b/code/datums/diseases/advance/symptoms/genetics.dm @@ -1,70 +1,70 @@ -/* -////////////////////////////////////// - -DNA Saboteur - - Very noticable. - Lowers resistance tremendously. - No changes to stage speed. - Decreases transmittablity tremendously. - Fatal Level. - -Bonus - Cleans the DNA of a person and then randomly gives them a trait. - -////////////////////////////////////// -*/ - -/datum/symptom/genetic_mutation - name = "Dormant DNA Activator" - desc = "The virus bonds with the DNA of the host, activating random dormant mutations within their DNA. When the virus is cured, the host's genetic alterations are undone." - stealth = -2 - resistance = -3 - stage_speed = 0 - transmittable = -3 - level = 6 - severity = 4 - base_message_chance = 50 - symptom_delay_min = 30 - symptom_delay_max = 60 - var/excludemuts = NONE - var/no_reset = FALSE - var/mutadone_proof = NONE - threshold_descs = list( - "Resistance 8" = "The negative and mildly negative mutations caused by the virus are mutadone-proof (but will still be undone when the virus is cured if the resistance 14 threshold is not met).", - "Resistance 14" = "The host's genetic alterations are not undone when the virus is cured.", - "Stage Speed 10" = "The virus activates dormant mutations at a much faster rate.", - "Stealth 5" = "Only activates negative mutations in hosts." - ) - -/datum/symptom/genetic_mutation/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["stealth"] >= 5) //only give them bad mutations - excludemuts = POSITIVE - if(A.properties["stage_rate"] >= 10) //activate dormant mutations more often at around 1.5x the pace - symptom_delay_min = 20 - symptom_delay_max = 40 - if(A.properties["resistance"] >= 8) //mutadone won't save you now - mutadone_proof = (NEGATIVE | MINOR_NEGATIVE) - if(A.properties["resistance"] >= 14) //one does not simply escape Nurgle's grasp - no_reset = TRUE - -/datum/symptom/genetic_mutation/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/carbon/C = A.affected_mob - if(!C.has_dna()) - return - switch(A.stage) - if(4, 5) - to_chat(C, "[pick("Your skin feels itchy.", "You feel light headed.")]") - C.easy_randmut((NEGATIVE | MINOR_NEGATIVE | POSITIVE) - excludemuts, TRUE, TRUE, TRUE, mutadone_proof) - -/datum/symptom/genetic_mutation/End(datum/disease/advance/A) - if(!..()) - return - if(!no_reset) - var/mob/living/carbon/M = A.affected_mob - if(M.has_dna()) - M.dna.remove_all_mutations(list(MUT_NORMAL, MUT_EXTRA), FALSE) +/* +////////////////////////////////////// + +DNA Saboteur + + Very noticable. + Lowers resistance tremendously. + No changes to stage speed. + Decreases transmittablity tremendously. + Fatal Level. + +Bonus + Cleans the DNA of a person and then randomly gives them a trait. + +////////////////////////////////////// +*/ + +/datum/symptom/genetic_mutation + name = "Dormant DNA Activator" + desc = "The virus bonds with the DNA of the host, activating random dormant mutations within their DNA. When the virus is cured, the host's genetic alterations are undone." + stealth = -2 + resistance = -3 + stage_speed = 0 + transmittable = -3 + level = 6 + severity = 4 + base_message_chance = 50 + symptom_delay_min = 30 + symptom_delay_max = 60 + var/excludemuts = NONE + var/no_reset = FALSE + var/mutadone_proof = NONE + threshold_descs = list( + "Resistance 8" = "The negative and mildly negative mutations caused by the virus are mutadone-proof (but will still be undone when the virus is cured if the resistance 14 threshold is not met).", + "Resistance 14" = "The host's genetic alterations are not undone when the virus is cured.", + "Stage Speed 10" = "The virus activates dormant mutations at a much faster rate.", + "Stealth 5" = "Only activates negative mutations in hosts." + ) + +/datum/symptom/genetic_mutation/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["stealth"] >= 5) //only give them bad mutations + excludemuts = POSITIVE + if(A.properties["stage_rate"] >= 10) //activate dormant mutations more often at around 1.5x the pace + symptom_delay_min = 20 + symptom_delay_max = 40 + if(A.properties["resistance"] >= 8) //mutadone won't save you now + mutadone_proof = (NEGATIVE | MINOR_NEGATIVE) + if(A.properties["resistance"] >= 14) //one does not simply escape Nurgle's grasp + no_reset = TRUE + +/datum/symptom/genetic_mutation/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/carbon/C = A.affected_mob + if(!C.has_dna()) + return + switch(A.stage) + if(4, 5) + to_chat(C, "[pick("Your skin feels itchy.", "You feel light headed.")]") + C.easy_randmut((NEGATIVE | MINOR_NEGATIVE | POSITIVE) - excludemuts, TRUE, TRUE, TRUE, mutadone_proof) + +/datum/symptom/genetic_mutation/End(datum/disease/advance/A) + if(!..()) + return + if(!no_reset) + var/mob/living/carbon/M = A.affected_mob + if(M.has_dna()) + M.dna.remove_all_mutations(list(MUT_NORMAL, MUT_EXTRA), FALSE) diff --git a/code/datums/diseases/advance/symptoms/hallucigen.dm b/code/datums/diseases/advance/symptoms/hallucigen.dm index acc8b793ea8e..4a20581784c2 100644 --- a/code/datums/diseases/advance/symptoms/hallucigen.dm +++ b/code/datums/diseases/advance/symptoms/hallucigen.dm @@ -1,70 +1,70 @@ -/* -////////////////////////////////////// - -Hallucigen - - Very noticable. - Lowers resistance considerably. - Decreases stage speed. - Reduced transmittable. - Critical Level. - -Bonus - Makes the affected mob be hallucinated for short periods of time. - -////////////////////////////////////// -*/ - -/datum/symptom/hallucigen - name = "Hallucigen" - desc = "The virus stimulates the brain, causing occasional hallucinations." - stealth = -1 - resistance = -3 - stage_speed = -3 - transmittable = -1 - level = 5 - severity = 2 - base_message_chance = 25 - symptom_delay_min = 25 - symptom_delay_max = 90 - var/fake_healthy = FALSE - threshold_descs = list( - "Stage Speed 7" = "Increases the amount of hallucinations.", - "Stealth 4" = "The virus mimics positive symptoms.", - ) - -/datum/symptom/hallucigen/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["stealth"] >= 4) //fake good symptom messages - fake_healthy = TRUE - base_message_chance = 50 - if(A.properties["stage_rate"] >= 7) //stronger hallucinations - power = 2 - -/datum/symptom/hallucigen/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/carbon/M = A.affected_mob - var/list/healthy_messages = list("Your lungs feel great.", "You realize you haven't been breathing.", "You don't feel the need to breathe.",\ - "Your eyes feel great.", "You are now blinking manually.", "You don't feel the need to blink.") - switch(A.stage) - if(1, 2) - if(prob(base_message_chance)) - if(!fake_healthy) - to_chat(M, "[pick("Something appears in your peripheral vision, then winks out.", "You hear a faint whisper with no source.", "Your head aches.")]") - else - to_chat(M, "[pick(healthy_messages)]") - if(3, 4) - if(prob(base_message_chance)) - if(!fake_healthy) - to_chat(M, "[pick("Something is following you.", "You are being watched.", "You hear a whisper in your ear.", "Thumping footsteps slam toward you from nowhere.")]") - else - to_chat(M, "[pick(healthy_messages)]") - else - if(prob(base_message_chance)) - if(!fake_healthy) - to_chat(M, "[pick("Oh, your head...", "Your head pounds.", "They're everywhere! Run!", "Something in the shadows...")]") - else - to_chat(M, "[pick(healthy_messages)]") - M.hallucination += (45 * power) +/* +////////////////////////////////////// + +Hallucigen + + Very noticable. + Lowers resistance considerably. + Decreases stage speed. + Reduced transmittable. + Critical Level. + +Bonus + Makes the affected mob be hallucinated for short periods of time. + +////////////////////////////////////// +*/ + +/datum/symptom/hallucigen + name = "Hallucigen" + desc = "The virus stimulates the brain, causing occasional hallucinations." + stealth = -1 + resistance = -3 + stage_speed = -3 + transmittable = -1 + level = 5 + severity = 2 + base_message_chance = 25 + symptom_delay_min = 25 + symptom_delay_max = 90 + var/fake_healthy = FALSE + threshold_descs = list( + "Stage Speed 7" = "Increases the amount of hallucinations.", + "Stealth 4" = "The virus mimics positive symptoms.", + ) + +/datum/symptom/hallucigen/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["stealth"] >= 4) //fake good symptom messages + fake_healthy = TRUE + base_message_chance = 50 + if(A.properties["stage_rate"] >= 7) //stronger hallucinations + power = 2 + +/datum/symptom/hallucigen/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/carbon/M = A.affected_mob + var/list/healthy_messages = list("Your lungs feel great.", "You realize you haven't been breathing.", "You don't feel the need to breathe.",\ + "Your eyes feel great.", "You are now blinking manually.", "You don't feel the need to blink.") + switch(A.stage) + if(1, 2) + if(prob(base_message_chance)) + if(!fake_healthy) + to_chat(M, "[pick("Something appears in your peripheral vision, then winks out.", "You hear a faint whisper with no source.", "Your head aches.")]") + else + to_chat(M, "[pick(healthy_messages)]") + if(3, 4) + if(prob(base_message_chance)) + if(!fake_healthy) + to_chat(M, "[pick("Something is following you.", "You are being watched.", "You hear a whisper in your ear.", "Thumping footsteps slam toward you from nowhere.")]") + else + to_chat(M, "[pick(healthy_messages)]") + else + if(prob(base_message_chance)) + if(!fake_healthy) + to_chat(M, "[pick("Oh, your head...", "Your head pounds.", "They're everywhere! Run!", "Something in the shadows...")]") + else + to_chat(M, "[pick(healthy_messages)]") + M.hallucination += (45 * power) diff --git a/code/datums/diseases/advance/symptoms/headache.dm b/code/datums/diseases/advance/symptoms/headache.dm index 65238a09851d..5082e8aff315 100644 --- a/code/datums/diseases/advance/symptoms/headache.dm +++ b/code/datums/diseases/advance/symptoms/headache.dm @@ -1,62 +1,62 @@ -/* -////////////////////////////////////// - -Headache - - Noticable. - Highly resistant. - Increases stage speed. - Not transmittable. - Low Level. - -BONUS - Displays an annoying message! - Should be used for buffing your disease. - -////////////////////////////////////// -*/ - -/datum/symptom/headache - - name = "Headache" - desc = "The virus causes inflammation inside the brain, causing constant headaches." - stealth = -1 - resistance = 4 - stage_speed = 2 - transmittable = 0 - level = 1 - severity = 1 - base_message_chance = 100 - symptom_delay_min = 15 - symptom_delay_max = 30 - threshold_descs = list( - "Stage Speed 6" = "Headaches will cause severe pain, that weakens the host.", - "Stage Speed 9" = "Headaches become less frequent but far more intense, preventing any action from the host.", - "Stealth 4" = "Reduces headache frequency until later stages.", - ) - -/datum/symptom/headache/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["stealth"] >= 4) - base_message_chance = 50 - if(A.properties["stage_rate"] >= 6) //severe pain - power = 2 - if(A.properties["stage_rate"] >= 9) //cluster headaches - symptom_delay_min = 30 - symptom_delay_max = 60 - power = 3 - -/datum/symptom/headache/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/M = A.affected_mob - if(power < 2) - if(prob(base_message_chance) || A.stage >=4) - to_chat(M, "[pick("Your head hurts.", "Your head pounds.")]") - if(power >= 2 && A.stage >= 4) - to_chat(M, "[pick("Your head hurts a lot.", "Your head pounds incessantly.")]") - M.adjustStaminaLoss(25) - if(power >= 3 && A.stage >= 5) - to_chat(M, "[pick("Your head hurts!", "You feel a burning knife inside your brain!", "A wave of pain fills your head!")]") - M.Stun(35) +/* +////////////////////////////////////// + +Headache + + Noticable. + Highly resistant. + Increases stage speed. + Not transmittable. + Low Level. + +BONUS + Displays an annoying message! + Should be used for buffing your disease. + +////////////////////////////////////// +*/ + +/datum/symptom/headache + + name = "Headache" + desc = "The virus causes inflammation inside the brain, causing constant headaches." + stealth = -1 + resistance = 4 + stage_speed = 2 + transmittable = 0 + level = 1 + severity = 1 + base_message_chance = 100 + symptom_delay_min = 15 + symptom_delay_max = 30 + threshold_descs = list( + "Stage Speed 6" = "Headaches will cause severe pain, that weakens the host.", + "Stage Speed 9" = "Headaches become less frequent but far more intense, preventing any action from the host.", + "Stealth 4" = "Reduces headache frequency until later stages.", + ) + +/datum/symptom/headache/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["stealth"] >= 4) + base_message_chance = 50 + if(A.properties["stage_rate"] >= 6) //severe pain + power = 2 + if(A.properties["stage_rate"] >= 9) //cluster headaches + symptom_delay_min = 30 + symptom_delay_max = 60 + power = 3 + +/datum/symptom/headache/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/M = A.affected_mob + if(power < 2) + if(prob(base_message_chance) || A.stage >=4) + to_chat(M, "[pick("Your head hurts.", "Your head pounds.")]") + if(power >= 2 && A.stage >= 4) + to_chat(M, "[pick("Your head hurts a lot.", "Your head pounds incessantly.")]") + M.adjustStaminaLoss(25) + if(power >= 3 && A.stage >= 5) + to_chat(M, "[pick("Your head hurts!", "You feel a burning knife inside your brain!", "A wave of pain fills your head!")]") + M.Stun(35) diff --git a/code/datums/diseases/advance/symptoms/heal.dm b/code/datums/diseases/advance/symptoms/heal.dm index 7755026c2c26..4ae34d2ee68e 100644 --- a/code/datums/diseases/advance/symptoms/heal.dm +++ b/code/datums/diseases/advance/symptoms/heal.dm @@ -1,500 +1,500 @@ -/datum/symptom/heal - name = "Basic Healing (does nothing)" //warning for adminspawn viruses - desc = "You should not be seeing this." - stealth = 0 - resistance = 0 - stage_speed = 0 - transmittable = 0 - level = 0 //not obtainable - base_message_chance = 20 //here used for the overlays - symptom_delay_min = 1 - symptom_delay_max = 1 - var/passive_message = "" //random message to infected but not actively healing people - threshold_descs = list( - "Stage Speed 6" = "Doubles healing speed.", - "Stealth 4" = "Healing will no longer be visible to onlookers.", - ) - -/datum/symptom/heal/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["stage_rate"] >= 6) //stronger healing - power = 2 - -/datum/symptom/heal/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/M = A.affected_mob - switch(A.stage) - if(4, 5) - var/effectiveness = CanHeal(A) - if(!effectiveness) - if(passive_message && prob(2) && passive_message_condition(M)) - to_chat(M, passive_message) - return - else - Heal(M, A, effectiveness) - return - -/datum/symptom/heal/proc/CanHeal(datum/disease/advance/A) - return power - -/datum/symptom/heal/proc/Heal(mob/living/M, datum/disease/advance/A, actual_power) - return TRUE - -/datum/symptom/heal/proc/passive_message_condition(mob/living/M) - return TRUE - - -/datum/symptom/heal/starlight - name = "Starlight Condensation" - desc = "The virus reacts to direct starlight, producing regenerative chemicals. Works best against toxin-based damage." - stealth = -1 - resistance = -2 - stage_speed = 0 - transmittable = 1 - level = 6 - passive_message = "You miss the feeling of starlight on your skin." - var/nearspace_penalty = 0.3 - threshold_descs = list( - "Stage Speed 6" = "Increases healing speed.", - "Transmission 6" = "Removes penalty for only being close to space.", - ) - -/datum/symptom/heal/starlight/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["transmittable"] >= 6) - nearspace_penalty = 1 - if(A.properties["stage_rate"] >= 6) - power = 2 - -/datum/symptom/heal/starlight/CanHeal(datum/disease/advance/A) - var/mob/living/M = A.affected_mob - if(istype(get_turf(M), /turf/open/space)) - return power - else - for(var/turf/T in view(M, 2)) - if(istype(T, /turf/open/space)) - return power * nearspace_penalty - -/datum/symptom/heal/starlight/Heal(mob/living/carbon/M, datum/disease/advance/A, actual_power) - var/heal_amt = actual_power - if(M.getToxLoss() && prob(5)) - to_chat(M, "Your skin tingles as the starlight seems to heal you.") - - M.adjustToxLoss(-(4 * heal_amt)) //most effective on toxins - - var/list/parts = M.get_damaged_bodyparts(1,1, null, BODYPART_ORGANIC) - - if(!parts.len) - return - - for(var/obj/item/bodypart/L in parts) - if(L.heal_damage(heal_amt/parts.len, heal_amt/parts.len, null, BODYPART_ORGANIC)) - M.update_damage_overlays() - return 1 - -/datum/symptom/heal/starlight/passive_message_condition(mob/living/M) - if(M.getBruteLoss() || M.getFireLoss() || M.getToxLoss()) - return TRUE - return FALSE - -/datum/symptom/heal/chem - name = "Toxolysis" - stealth = 0 - resistance = -2 - stage_speed = 2 - transmittable = -2 - level = 7 - var/food_conversion = FALSE - desc = "The virus rapidly breaks down any foreign chemicals in the bloodstream." - threshold_descs = list( - "Resistance 7" = "Increases chem removal speed.", - "Stage Speed 6" = "Consumed chemicals nourish the host.", - ) - -/datum/symptom/heal/chem/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["stage_rate"] >= 6) - food_conversion = TRUE - if(A.properties["resistance"] >= 7) - power = 2 - -/datum/symptom/heal/chem/Heal(mob/living/M, datum/disease/advance/A, actual_power) - for(var/datum/reagent/R in M.reagents.reagent_list) //Not just toxins! - M.reagents.remove_reagent(R.type, actual_power) - if(food_conversion) - M.adjust_nutrition(0.3) - if(prob(2)) - to_chat(M, "You feel a mild warmth as your blood purifies itself.") - return 1 - - - -/datum/symptom/heal/metabolism - name = "Metabolic Boost" - stealth = -1 - resistance = -2 - stage_speed = 2 - transmittable = 1 - level = 7 - var/triple_metabolism = FALSE - var/reduced_hunger = FALSE - desc = "The virus causes the host's metabolism to accelerate rapidly, making them process chemicals twice as fast,\ - but also causing increased hunger." - threshold_descs = list( - "Stealth 3" = "Reduces hunger rate.", - "Stage Speed 10" = "Chemical metabolization is tripled instead of doubled.", - ) - -/datum/symptom/heal/metabolism/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["stage_rate"] >= 10) - triple_metabolism = TRUE - if(A.properties["stealth"] >= 3) - reduced_hunger = TRUE - -/datum/symptom/heal/metabolism/Heal(mob/living/carbon/C, datum/disease/advance/A, actual_power) - if(!istype(C)) - return - C.reagents.metabolize(C, can_overdose=TRUE) //this works even without a liver; it's intentional since the virus is metabolizing by itself - if(triple_metabolism) - C.reagents.metabolize(C, can_overdose=TRUE) - C.overeatduration = max(C.overeatduration - 2, 0) - var/lost_nutrition = 9 - (reduced_hunger * 5) - C.adjust_nutrition(-lost_nutrition * HUNGER_FACTOR) //Hunger depletes at 10x the normal speed - if(prob(2)) - to_chat(C, "You feel an odd gurgle in your stomach, as if it was working much faster than normal.") - return 1 - -/datum/symptom/heal/darkness - name = "Nocturnal Regeneration" - desc = "The virus is able to mend the host's flesh when in conditions of low light, repairing physical damage. More effective against brute damage." - stealth = 2 - resistance = -1 - stage_speed = -2 - transmittable = -1 - level = 6 - passive_message = "You feel tingling on your skin as light passes over it." - threshold_descs = list( - "Stage Speed 8" = "Doubles healing speed.", - ) - -/datum/symptom/heal/darkness/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["stage_rate"] >= 8) - power = 2 - -/datum/symptom/heal/darkness/CanHeal(datum/disease/advance/A) - var/mob/living/M = A.affected_mob - var/light_amount = 0 - if(isturf(M.loc)) //else, there's considered to be no light - var/turf/T = M.loc - light_amount = min(1,T.get_lumcount()) - 0.5 - if(light_amount < SHADOW_SPECIES_LIGHT_THRESHOLD) - return power - -/datum/symptom/heal/darkness/Heal(mob/living/carbon/M, datum/disease/advance/A, actual_power) - var/heal_amt = 2 * actual_power - - var/list/parts = M.get_damaged_bodyparts(1,1, null, BODYPART_ORGANIC) - - if(!parts.len) - return - - if(prob(5)) - to_chat(M, "The darkness soothes and mends your wounds.") - - for(var/obj/item/bodypart/L in parts) - if(L.heal_damage(heal_amt/parts.len, heal_amt/parts.len * 0.5, null, BODYPART_ORGANIC)) //more effective on brute - M.update_damage_overlays() - return 1 - -/datum/symptom/heal/darkness/passive_message_condition(mob/living/M) - if(M.getBruteLoss() || M.getFireLoss()) - return TRUE - return FALSE - -/datum/symptom/heal/coma - name = "Regenerative Coma" - desc = "The virus causes the host to fall into a death-like coma when severely damaged, then rapidly fixes the damage." - stealth = 0 - resistance = 2 - stage_speed = -3 - transmittable = -2 - level = 8 - passive_message = "The pain from your wounds makes you feel oddly sleepy..." - var/deathgasp = FALSE - var/stabilize = FALSE - var/active_coma = FALSE //to prevent multiple coma procs - threshold_descs = list( - "Stealth 2" = "Host appears to die when falling into a coma.", - "Resistance 4" = "The virus also stabilizes the host while they are in critical condition.", - "Stage Speed 7" = "Increases healing speed.", - ) - -/datum/symptom/heal/coma/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["stage_rate"] >= 7) - power = 1.5 - if(A.properties["resistance"] >= 4) - stabilize = TRUE - if(A.properties["stealth"] >= 2) - deathgasp = TRUE - -/datum/symptom/heal/coma/on_stage_change(datum/disease/advance/A) //mostly copy+pasted from the code for self-respiration's TRAIT_NOBREATH stuff - if(!..()) - return FALSE - if(A.stage >= 4 && stabilize) - ADD_TRAIT(A.affected_mob, TRAIT_NOCRITDAMAGE, DISEASE_TRAIT) - else - REMOVE_TRAIT(A.affected_mob, TRAIT_NOCRITDAMAGE, DISEASE_TRAIT) - return TRUE - -/datum/symptom/heal/coma/End(datum/disease/advance/A) - if(!..()) - return - if(active_coma) - uncoma() - REMOVE_TRAIT(A.affected_mob, TRAIT_NOCRITDAMAGE, DISEASE_TRAIT) - -/datum/symptom/heal/coma/CanHeal(datum/disease/advance/A) - var/mob/living/M = A.affected_mob - if(HAS_TRAIT(M, TRAIT_DEATHCOMA)) - return power - else if(M.IsUnconscious() || M.stat == UNCONSCIOUS) - return power * 0.9 - else if(M.stat == SOFT_CRIT) - return power * 0.5 - else if(M.IsSleeping()) - return power * 0.25 - else if(M.getBruteLoss() + M.getFireLoss() >= 70 && !active_coma) - to_chat(M, "You feel yourself slip into a regenerative coma...") - active_coma = TRUE - addtimer(CALLBACK(src, .proc/coma, M), 60) - -/datum/symptom/heal/coma/proc/coma(mob/living/M) - M.fakedeath("regenerative_coma", !deathgasp) - M.update_stat() - M.update_mobility() - addtimer(CALLBACK(src, .proc/uncoma, M), 300) - -/datum/symptom/heal/coma/proc/uncoma(mob/living/M) - if(!active_coma) - return - active_coma = FALSE - M.cure_fakedeath("regenerative_coma") - M.update_stat() - M.update_mobility() - -/datum/symptom/heal/coma/Heal(mob/living/carbon/M, datum/disease/advance/A, actual_power) - var/heal_amt = 4 * actual_power - - var/list/parts = M.get_damaged_bodyparts(1,1) - - if(!parts.len) - return - - for(var/obj/item/bodypart/L in parts) - if(L.heal_damage(heal_amt/parts.len, heal_amt/parts.len, null, BODYPART_ORGANIC)) - M.update_damage_overlays() - - if(active_coma && M.getBruteLoss() + M.getFireLoss() == 0) - uncoma(M) - - return 1 - -/datum/symptom/heal/coma/passive_message_condition(mob/living/M) - if((M.getBruteLoss() + M.getFireLoss()) > 30) - return TRUE - return FALSE - -/datum/symptom/heal/water - name = "Tissue Hydration" - desc = "The virus uses excess water inside and outside the body to repair damaged tissue cells. More effective when using holy water and against burns." - stealth = 0 - resistance = -1 - stage_speed = 0 - transmittable = 1 - level = 6 - passive_message = "Your skin feels oddly dry..." - var/absorption_coeff = 1 - threshold_descs = list( - "Resistance 5" = "Water is consumed at a much slower rate.", - "Stage Speed 7" = "Increases healing speed.", - ) - -/datum/symptom/heal/water/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["stage_rate"] >= 7) - power = 2 - if(A.properties["resistance"] >= 5) - absorption_coeff = 0.25 - -/datum/symptom/heal/water/CanHeal(datum/disease/advance/A) - . = 0 - var/mob/living/M = A.affected_mob - if(M.fire_stacks < 0) - M.fire_stacks = min(M.fire_stacks + 1 * absorption_coeff, 0) - . += power - if(M.reagents.has_reagent(/datum/reagent/water/holywater, needs_metabolizing = FALSE)) - M.reagents.remove_reagent(/datum/reagent/water/holywater, 0.5 * absorption_coeff) - . += power * 0.75 - else if(M.reagents.has_reagent(/datum/reagent/water, needs_metabolizing = FALSE)) - M.reagents.remove_reagent(/datum/reagent/water, 0.5 * absorption_coeff) - . += power * 0.5 - -/datum/symptom/heal/water/Heal(mob/living/carbon/M, datum/disease/advance/A, actual_power) - var/heal_amt = 2 * actual_power - - var/list/parts = M.get_damaged_bodyparts(1,1, null, BODYPART_ORGANIC) //more effective on burns - - if(!parts.len) - return - - if(prob(5)) - to_chat(M, "You feel yourself absorbing the water around you to soothe your damaged skin.") - - for(var/obj/item/bodypart/L in parts) - if(L.heal_damage(heal_amt/parts.len * 0.5, heal_amt/parts.len, null, BODYPART_ORGANIC)) - M.update_damage_overlays() - - return 1 - -/datum/symptom/heal/water/passive_message_condition(mob/living/M) - if(M.getBruteLoss() || M.getFireLoss()) - return TRUE - return FALSE - -/datum/symptom/heal/plasma - name = "Plasma Fixation" - desc = "The virus draws plasma from the atmosphere and from inside the body to heal and stabilize body temperature." - stealth = 0 - resistance = 3 - stage_speed = -2 - transmittable = -2 - level = 8 - passive_message = "You feel an odd attraction to plasma." - var/temp_rate = 1 - threshold_descs = list( - "Transmission 6" = "Increases temperature adjustment rate.", - "Stage Speed 7" = "Increases healing speed.", - ) - -/datum/symptom/heal/plasma/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["stage_rate"] >= 7) - power = 2 - if(A.properties["transmittable"] >= 6) - temp_rate = 4 - -/datum/symptom/heal/plasma/CanHeal(datum/disease/advance/A) - var/mob/living/M = A.affected_mob - var/datum/gas_mixture/environment - - . = 0 - - if(M.loc) - environment = M.loc.return_air() - if(environment) - if(environment.get_moles(/datum/gas/plasma) && environment.get_moles(/datum/gas/plasma) > GLOB.meta_gas_info[/datum/gas/plasma][META_GAS_MOLES_VISIBLE]) //if there's enough plasma in the air to see - . += power * 0.5 - if(M.reagents.has_reagent(/datum/reagent/toxin/plasma, needs_metabolizing = TRUE)) - . += power * 0.75 - -/datum/symptom/heal/plasma/Heal(mob/living/carbon/M, datum/disease/advance/A, actual_power) - var/heal_amt = 4 * actual_power - - if(prob(5)) - to_chat(M, "You feel yourself absorbing plasma inside and around you...") - - if(M.bodytemperature > M.get_body_temp_normal()) - M.adjust_bodytemperature(-20 * temp_rate * TEMPERATURE_DAMAGE_COEFFICIENT, M.get_body_temp_normal()) - if(prob(5)) - to_chat(M, "You feel less hot.") - else if(M.bodytemperature < (M.get_body_temp_normal() + 1)) - M.adjust_bodytemperature(20 * temp_rate * TEMPERATURE_DAMAGE_COEFFICIENT,0, M.get_body_temp_normal()) - if(prob(5)) - to_chat(M, "You feel warmer.") - - M.adjustToxLoss(-heal_amt) - - var/list/parts = M.get_damaged_bodyparts(1,1, null, BODYPART_ORGANIC) - if(!parts.len) - return - if(prob(5)) - to_chat(M, "The pain from your wounds fades rapidly.") - for(var/obj/item/bodypart/L in parts) - if(L.heal_damage(heal_amt/parts.len, heal_amt/parts.len, null, BODYPART_ORGANIC)) - M.update_damage_overlays() - return 1 - - -/datum/symptom/heal/radiation - name = "Radioactive Resonance" - desc = "The virus uses radiation to fix damage through dna mutations." - stealth = -1 - resistance = -2 - stage_speed = 2 - transmittable = -3 - level = 6 - symptom_delay_min = 1 - symptom_delay_max = 1 - passive_message = "Your skin glows faintly for a moment." - var/cellular_damage = FALSE - threshold_descs = list( - "Transmission 6" = "Additionally heals cellular damage.", - "Resistance 7" = "Increases healing speed.", - ) - -/datum/symptom/heal/radiation/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["resistance"] >= 7) - power = 2 - if(A.properties["transmittable"] >= 6) - cellular_damage = TRUE - -/datum/symptom/heal/radiation/CanHeal(datum/disease/advance/A) - var/mob/living/M = A.affected_mob - switch(M.radiation) - if(0) - return FALSE - if(1 to RAD_MOB_SAFE) - return 0.25 - if(RAD_MOB_SAFE to RAD_BURN_THRESHOLD) - return 0.5 - if(RAD_BURN_THRESHOLD to RAD_MOB_MUTATE) - return 0.75 - if(RAD_MOB_MUTATE to RAD_MOB_KNOCKDOWN) - return 1 - else - return 1.5 - -/datum/symptom/heal/radiation/Heal(mob/living/carbon/M, datum/disease/advance/A, actual_power) - var/heal_amt = actual_power - - if(cellular_damage) - M.adjustCloneLoss(-heal_amt * 0.5) - - M.adjustToxLoss(-(2 * heal_amt)) - - var/list/parts = M.get_damaged_bodyparts(1,1, null, BODYPART_ORGANIC) - - if(!parts.len) - return - - if(prob(4)) - to_chat(M, "Your skin glows faintly, and you feel your wounds mending themselves.") - - for(var/obj/item/bodypart/L in parts) - if(L.heal_damage(heal_amt/parts.len, heal_amt/parts.len, null, BODYPART_ORGANIC)) - M.update_damage_overlays() - return 1 +/datum/symptom/heal + name = "Basic Healing (does nothing)" //warning for adminspawn viruses + desc = "You should not be seeing this." + stealth = 0 + resistance = 0 + stage_speed = 0 + transmittable = 0 + level = 0 //not obtainable + base_message_chance = 20 //here used for the overlays + symptom_delay_min = 1 + symptom_delay_max = 1 + var/passive_message = "" //random message to infected but not actively healing people + threshold_descs = list( + "Stage Speed 6" = "Doubles healing speed.", + "Stealth 4" = "Healing will no longer be visible to onlookers.", + ) + +/datum/symptom/heal/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["stage_rate"] >= 6) //stronger healing + power = 2 + +/datum/symptom/heal/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/M = A.affected_mob + switch(A.stage) + if(4, 5) + var/effectiveness = CanHeal(A) + if(!effectiveness) + if(passive_message && prob(2) && passive_message_condition(M)) + to_chat(M, passive_message) + return + else + Heal(M, A, effectiveness) + return + +/datum/symptom/heal/proc/CanHeal(datum/disease/advance/A) + return power + +/datum/symptom/heal/proc/Heal(mob/living/M, datum/disease/advance/A, actual_power) + return TRUE + +/datum/symptom/heal/proc/passive_message_condition(mob/living/M) + return TRUE + + +/datum/symptom/heal/starlight + name = "Starlight Condensation" + desc = "The virus reacts to direct starlight, producing regenerative chemicals. Works best against toxin-based damage." + stealth = -1 + resistance = -2 + stage_speed = 0 + transmittable = 1 + level = 6 + passive_message = "You miss the feeling of starlight on your skin." + var/nearspace_penalty = 0.3 + threshold_descs = list( + "Stage Speed 6" = "Increases healing speed.", + "Transmission 6" = "Removes penalty for only being close to space.", + ) + +/datum/symptom/heal/starlight/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["transmittable"] >= 6) + nearspace_penalty = 1 + if(A.properties["stage_rate"] >= 6) + power = 2 + +/datum/symptom/heal/starlight/CanHeal(datum/disease/advance/A) + var/mob/living/M = A.affected_mob + if(istype(get_turf(M), /turf/open/space)) + return power + else + for(var/turf/T in view(M, 2)) + if(istype(T, /turf/open/space)) + return power * nearspace_penalty + +/datum/symptom/heal/starlight/Heal(mob/living/carbon/M, datum/disease/advance/A, actual_power) + var/heal_amt = actual_power + if(M.getToxLoss() && prob(5)) + to_chat(M, "Your skin tingles as the starlight seems to heal you.") + + M.adjustToxLoss(-(4 * heal_amt)) //most effective on toxins + + var/list/parts = M.get_damaged_bodyparts(1,1, null, BODYPART_ORGANIC) + + if(!parts.len) + return + + for(var/obj/item/bodypart/L in parts) + if(L.heal_damage(heal_amt/parts.len, heal_amt/parts.len, null, BODYPART_ORGANIC)) + M.update_damage_overlays() + return 1 + +/datum/symptom/heal/starlight/passive_message_condition(mob/living/M) + if(M.getBruteLoss() || M.getFireLoss() || M.getToxLoss()) + return TRUE + return FALSE + +/datum/symptom/heal/chem + name = "Toxolysis" + stealth = 0 + resistance = -2 + stage_speed = 2 + transmittable = -2 + level = 7 + var/food_conversion = FALSE + desc = "The virus rapidly breaks down any foreign chemicals in the bloodstream." + threshold_descs = list( + "Resistance 7" = "Increases chem removal speed.", + "Stage Speed 6" = "Consumed chemicals nourish the host.", + ) + +/datum/symptom/heal/chem/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["stage_rate"] >= 6) + food_conversion = TRUE + if(A.properties["resistance"] >= 7) + power = 2 + +/datum/symptom/heal/chem/Heal(mob/living/M, datum/disease/advance/A, actual_power) + for(var/datum/reagent/R in M.reagents.reagent_list) //Not just toxins! + M.reagents.remove_reagent(R.type, actual_power) + if(food_conversion) + M.adjust_nutrition(0.3) + if(prob(2)) + to_chat(M, "You feel a mild warmth as your blood purifies itself.") + return 1 + + + +/datum/symptom/heal/metabolism + name = "Metabolic Boost" + stealth = -1 + resistance = -2 + stage_speed = 2 + transmittable = 1 + level = 7 + var/triple_metabolism = FALSE + var/reduced_hunger = FALSE + desc = "The virus causes the host's metabolism to accelerate rapidly, making them process chemicals twice as fast,\ + but also causing increased hunger." + threshold_descs = list( + "Stealth 3" = "Reduces hunger rate.", + "Stage Speed 10" = "Chemical metabolization is tripled instead of doubled.", + ) + +/datum/symptom/heal/metabolism/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["stage_rate"] >= 10) + triple_metabolism = TRUE + if(A.properties["stealth"] >= 3) + reduced_hunger = TRUE + +/datum/symptom/heal/metabolism/Heal(mob/living/carbon/C, datum/disease/advance/A, actual_power) + if(!istype(C)) + return + C.reagents.metabolize(C, can_overdose=TRUE) //this works even without a liver; it's intentional since the virus is metabolizing by itself + if(triple_metabolism) + C.reagents.metabolize(C, can_overdose=TRUE) + C.overeatduration = max(C.overeatduration - 2, 0) + var/lost_nutrition = 9 - (reduced_hunger * 5) + C.adjust_nutrition(-lost_nutrition * HUNGER_FACTOR) //Hunger depletes at 10x the normal speed + if(prob(2)) + to_chat(C, "You feel an odd gurgle in your stomach, as if it was working much faster than normal.") + return 1 + +/datum/symptom/heal/darkness + name = "Nocturnal Regeneration" + desc = "The virus is able to mend the host's flesh when in conditions of low light, repairing physical damage. More effective against brute damage." + stealth = 2 + resistance = -1 + stage_speed = -2 + transmittable = -1 + level = 6 + passive_message = "You feel tingling on your skin as light passes over it." + threshold_descs = list( + "Stage Speed 8" = "Doubles healing speed.", + ) + +/datum/symptom/heal/darkness/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["stage_rate"] >= 8) + power = 2 + +/datum/symptom/heal/darkness/CanHeal(datum/disease/advance/A) + var/mob/living/M = A.affected_mob + var/light_amount = 0 + if(isturf(M.loc)) //else, there's considered to be no light + var/turf/T = M.loc + light_amount = min(1,T.get_lumcount()) - 0.5 + if(light_amount < SHADOW_SPECIES_LIGHT_THRESHOLD) + return power + +/datum/symptom/heal/darkness/Heal(mob/living/carbon/M, datum/disease/advance/A, actual_power) + var/heal_amt = 2 * actual_power + + var/list/parts = M.get_damaged_bodyparts(1,1, null, BODYPART_ORGANIC) + + if(!parts.len) + return + + if(prob(5)) + to_chat(M, "The darkness soothes and mends your wounds.") + + for(var/obj/item/bodypart/L in parts) + if(L.heal_damage(heal_amt/parts.len, heal_amt/parts.len * 0.5, null, BODYPART_ORGANIC)) //more effective on brute + M.update_damage_overlays() + return 1 + +/datum/symptom/heal/darkness/passive_message_condition(mob/living/M) + if(M.getBruteLoss() || M.getFireLoss()) + return TRUE + return FALSE + +/datum/symptom/heal/coma + name = "Regenerative Coma" + desc = "The virus causes the host to fall into a death-like coma when severely damaged, then rapidly fixes the damage." + stealth = 0 + resistance = 2 + stage_speed = -3 + transmittable = -2 + level = 8 + passive_message = "The pain from your wounds makes you feel oddly sleepy..." + var/deathgasp = FALSE + var/stabilize = FALSE + var/active_coma = FALSE //to prevent multiple coma procs + threshold_descs = list( + "Stealth 2" = "Host appears to die when falling into a coma.", + "Resistance 4" = "The virus also stabilizes the host while they are in critical condition.", + "Stage Speed 7" = "Increases healing speed.", + ) + +/datum/symptom/heal/coma/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["stage_rate"] >= 7) + power = 1.5 + if(A.properties["resistance"] >= 4) + stabilize = TRUE + if(A.properties["stealth"] >= 2) + deathgasp = TRUE + +/datum/symptom/heal/coma/on_stage_change(datum/disease/advance/A) //mostly copy+pasted from the code for self-respiration's TRAIT_NOBREATH stuff + if(!..()) + return FALSE + if(A.stage >= 4 && stabilize) + ADD_TRAIT(A.affected_mob, TRAIT_NOCRITDAMAGE, DISEASE_TRAIT) + else + REMOVE_TRAIT(A.affected_mob, TRAIT_NOCRITDAMAGE, DISEASE_TRAIT) + return TRUE + +/datum/symptom/heal/coma/End(datum/disease/advance/A) + if(!..()) + return + if(active_coma) + uncoma() + REMOVE_TRAIT(A.affected_mob, TRAIT_NOCRITDAMAGE, DISEASE_TRAIT) + +/datum/symptom/heal/coma/CanHeal(datum/disease/advance/A) + var/mob/living/M = A.affected_mob + if(HAS_TRAIT(M, TRAIT_DEATHCOMA)) + return power + else if(M.IsUnconscious() || M.stat == UNCONSCIOUS) + return power * 0.9 + else if(M.stat == SOFT_CRIT) + return power * 0.5 + else if(M.IsSleeping()) + return power * 0.25 + else if(M.getBruteLoss() + M.getFireLoss() >= 70 && !active_coma) + to_chat(M, "You feel yourself slip into a regenerative coma...") + active_coma = TRUE + addtimer(CALLBACK(src, .proc/coma, M), 60) + +/datum/symptom/heal/coma/proc/coma(mob/living/M) + M.fakedeath("regenerative_coma", !deathgasp) + M.update_stat() + M.update_mobility() + addtimer(CALLBACK(src, .proc/uncoma, M), 300) + +/datum/symptom/heal/coma/proc/uncoma(mob/living/M) + if(!active_coma) + return + active_coma = FALSE + M.cure_fakedeath("regenerative_coma") + M.update_stat() + M.update_mobility() + +/datum/symptom/heal/coma/Heal(mob/living/carbon/M, datum/disease/advance/A, actual_power) + var/heal_amt = 4 * actual_power + + var/list/parts = M.get_damaged_bodyparts(1,1) + + if(!parts.len) + return + + for(var/obj/item/bodypart/L in parts) + if(L.heal_damage(heal_amt/parts.len, heal_amt/parts.len, null, BODYPART_ORGANIC)) + M.update_damage_overlays() + + if(active_coma && M.getBruteLoss() + M.getFireLoss() == 0) + uncoma(M) + + return 1 + +/datum/symptom/heal/coma/passive_message_condition(mob/living/M) + if((M.getBruteLoss() + M.getFireLoss()) > 30) + return TRUE + return FALSE + +/datum/symptom/heal/water + name = "Tissue Hydration" + desc = "The virus uses excess water inside and outside the body to repair damaged tissue cells. More effective when using holy water and against burns." + stealth = 0 + resistance = -1 + stage_speed = 0 + transmittable = 1 + level = 6 + passive_message = "Your skin feels oddly dry..." + var/absorption_coeff = 1 + threshold_descs = list( + "Resistance 5" = "Water is consumed at a much slower rate.", + "Stage Speed 7" = "Increases healing speed.", + ) + +/datum/symptom/heal/water/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["stage_rate"] >= 7) + power = 2 + if(A.properties["resistance"] >= 5) + absorption_coeff = 0.25 + +/datum/symptom/heal/water/CanHeal(datum/disease/advance/A) + . = 0 + var/mob/living/M = A.affected_mob + if(M.fire_stacks < 0) + M.fire_stacks = min(M.fire_stacks + 1 * absorption_coeff, 0) + . += power + if(M.reagents.has_reagent(/datum/reagent/water/holywater, needs_metabolizing = FALSE)) + M.reagents.remove_reagent(/datum/reagent/water/holywater, 0.5 * absorption_coeff) + . += power * 0.75 + else if(M.reagents.has_reagent(/datum/reagent/water, needs_metabolizing = FALSE)) + M.reagents.remove_reagent(/datum/reagent/water, 0.5 * absorption_coeff) + . += power * 0.5 + +/datum/symptom/heal/water/Heal(mob/living/carbon/M, datum/disease/advance/A, actual_power) + var/heal_amt = 2 * actual_power + + var/list/parts = M.get_damaged_bodyparts(1,1, null, BODYPART_ORGANIC) //more effective on burns + + if(!parts.len) + return + + if(prob(5)) + to_chat(M, "You feel yourself absorbing the water around you to soothe your damaged skin.") + + for(var/obj/item/bodypart/L in parts) + if(L.heal_damage(heal_amt/parts.len * 0.5, heal_amt/parts.len, null, BODYPART_ORGANIC)) + M.update_damage_overlays() + + return 1 + +/datum/symptom/heal/water/passive_message_condition(mob/living/M) + if(M.getBruteLoss() || M.getFireLoss()) + return TRUE + return FALSE + +/datum/symptom/heal/plasma + name = "Plasma Fixation" + desc = "The virus draws plasma from the atmosphere and from inside the body to heal and stabilize body temperature." + stealth = 0 + resistance = 3 + stage_speed = -2 + transmittable = -2 + level = 8 + passive_message = "You feel an odd attraction to plasma." + var/temp_rate = 1 + threshold_descs = list( + "Transmission 6" = "Increases temperature adjustment rate.", + "Stage Speed 7" = "Increases healing speed.", + ) + +/datum/symptom/heal/plasma/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["stage_rate"] >= 7) + power = 2 + if(A.properties["transmittable"] >= 6) + temp_rate = 4 + +/datum/symptom/heal/plasma/CanHeal(datum/disease/advance/A) + var/mob/living/M = A.affected_mob + var/datum/gas_mixture/environment + + . = 0 + + if(M.loc) + environment = M.loc.return_air() + if(environment) + if(environment.get_moles(/datum/gas/plasma) && environment.get_moles(/datum/gas/plasma) > GLOB.meta_gas_info[/datum/gas/plasma][META_GAS_MOLES_VISIBLE]) //if there's enough plasma in the air to see + . += power * 0.5 + if(M.reagents.has_reagent(/datum/reagent/toxin/plasma, needs_metabolizing = TRUE)) + . += power * 0.75 + +/datum/symptom/heal/plasma/Heal(mob/living/carbon/M, datum/disease/advance/A, actual_power) + var/heal_amt = 4 * actual_power + + if(prob(5)) + to_chat(M, "You feel yourself absorbing plasma inside and around you...") + + if(M.bodytemperature > M.get_body_temp_normal()) + M.adjust_bodytemperature(-20 * temp_rate * TEMPERATURE_DAMAGE_COEFFICIENT, M.get_body_temp_normal()) + if(prob(5)) + to_chat(M, "You feel less hot.") + else if(M.bodytemperature < (M.get_body_temp_normal() + 1)) + M.adjust_bodytemperature(20 * temp_rate * TEMPERATURE_DAMAGE_COEFFICIENT,0, M.get_body_temp_normal()) + if(prob(5)) + to_chat(M, "You feel warmer.") + + M.adjustToxLoss(-heal_amt) + + var/list/parts = M.get_damaged_bodyparts(1,1, null, BODYPART_ORGANIC) + if(!parts.len) + return + if(prob(5)) + to_chat(M, "The pain from your wounds fades rapidly.") + for(var/obj/item/bodypart/L in parts) + if(L.heal_damage(heal_amt/parts.len, heal_amt/parts.len, null, BODYPART_ORGANIC)) + M.update_damage_overlays() + return 1 + + +/datum/symptom/heal/radiation + name = "Radioactive Resonance" + desc = "The virus uses radiation to fix damage through dna mutations." + stealth = -1 + resistance = -2 + stage_speed = 2 + transmittable = -3 + level = 6 + symptom_delay_min = 1 + symptom_delay_max = 1 + passive_message = "Your skin glows faintly for a moment." + var/cellular_damage = FALSE + threshold_descs = list( + "Transmission 6" = "Additionally heals cellular damage.", + "Resistance 7" = "Increases healing speed.", + ) + +/datum/symptom/heal/radiation/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["resistance"] >= 7) + power = 2 + if(A.properties["transmittable"] >= 6) + cellular_damage = TRUE + +/datum/symptom/heal/radiation/CanHeal(datum/disease/advance/A) + var/mob/living/M = A.affected_mob + switch(M.radiation) + if(0) + return FALSE + if(1 to RAD_MOB_SAFE) + return 0.25 + if(RAD_MOB_SAFE to RAD_BURN_THRESHOLD) + return 0.5 + if(RAD_BURN_THRESHOLD to RAD_MOB_MUTATE) + return 0.75 + if(RAD_MOB_MUTATE to RAD_MOB_KNOCKDOWN) + return 1 + else + return 1.5 + +/datum/symptom/heal/radiation/Heal(mob/living/carbon/M, datum/disease/advance/A, actual_power) + var/heal_amt = actual_power + + if(cellular_damage) + M.adjustCloneLoss(-heal_amt * 0.5) + + M.adjustToxLoss(-(2 * heal_amt)) + + var/list/parts = M.get_damaged_bodyparts(1,1, null, BODYPART_ORGANIC) + + if(!parts.len) + return + + if(prob(4)) + to_chat(M, "Your skin glows faintly, and you feel your wounds mending themselves.") + + for(var/obj/item/bodypart/L in parts) + if(L.heal_damage(heal_amt/parts.len, heal_amt/parts.len, null, BODYPART_ORGANIC)) + M.update_damage_overlays() + return 1 diff --git a/code/datums/diseases/advance/symptoms/itching.dm b/code/datums/diseases/advance/symptoms/itching.dm index 7204233d373e..b31fa22ebc5c 100644 --- a/code/datums/diseases/advance/symptoms/itching.dm +++ b/code/datums/diseases/advance/symptoms/itching.dm @@ -1,56 +1,56 @@ -/* -////////////////////////////////////// - -Itching - - Not noticable or unnoticable. - Resistant. - Increases stage speed. - Little transmissibility. - Low Level. - -BONUS - Displays an annoying message! - Should be used for buffing your disease. - -////////////////////////////////////// -*/ - -/datum/symptom/itching - - name = "Itching" - desc = "The virus irritates the skin, causing itching." - stealth = 0 - resistance = 3 - stage_speed = 3 - transmittable = 1 - level = 1 - severity = 1 - symptom_delay_min = 5 - symptom_delay_max = 25 - var/scratch = FALSE - threshold_descs = list( - "Transmission 6" = "Increases frequency of itching.", - "Stage Speed 7" = "The host will scrath itself when itching, causing superficial damage.", - ) - -/datum/symptom/itching/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["transmittable"] >= 6) //itch more often - symptom_delay_min = 1 - symptom_delay_max = 4 - if(A.properties["stage_rate"] >= 7) //scratch - scratch = TRUE - -/datum/symptom/itching/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/carbon/M = A.affected_mob - var/picked_bodypart = pick(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG) - var/obj/item/bodypart/bodypart = M.get_bodypart(picked_bodypart) - if(bodypart && bodypart.status == BODYPART_ORGANIC && !bodypart.is_pseudopart) //robotic limbs will mean less scratching overall (why are golems able to damage themselves with self-scratching, but not androids? the world may never know) - var/can_scratch = scratch && !M.incapacitated() - M.visible_message("[can_scratch ? "[M] scratches [M.p_their()] [bodypart.name]." : ""]", "Your [bodypart.name] itches. [can_scratch ? " You scratch it." : ""]") - if(can_scratch) - bodypart.receive_damage(0.5) +/* +////////////////////////////////////// + +Itching + + Not noticable or unnoticable. + Resistant. + Increases stage speed. + Little transmissibility. + Low Level. + +BONUS + Displays an annoying message! + Should be used for buffing your disease. + +////////////////////////////////////// +*/ + +/datum/symptom/itching + + name = "Itching" + desc = "The virus irritates the skin, causing itching." + stealth = 0 + resistance = 3 + stage_speed = 3 + transmittable = 1 + level = 1 + severity = 1 + symptom_delay_min = 5 + symptom_delay_max = 25 + var/scratch = FALSE + threshold_descs = list( + "Transmission 6" = "Increases frequency of itching.", + "Stage Speed 7" = "The host will scrath itself when itching, causing superficial damage.", + ) + +/datum/symptom/itching/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["transmittable"] >= 6) //itch more often + symptom_delay_min = 1 + symptom_delay_max = 4 + if(A.properties["stage_rate"] >= 7) //scratch + scratch = TRUE + +/datum/symptom/itching/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/carbon/M = A.affected_mob + var/picked_bodypart = pick(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG) + var/obj/item/bodypart/bodypart = M.get_bodypart(picked_bodypart) + if(bodypart && bodypart.status == BODYPART_ORGANIC && !bodypart.is_pseudopart) //robotic limbs will mean less scratching overall (why are golems able to damage themselves with self-scratching, but not androids? the world may never know) + var/can_scratch = scratch && !M.incapacitated() + M.visible_message("[can_scratch ? "[M] scratches [M.p_their()] [bodypart.name]." : ""]", "Your [bodypart.name] itches. [can_scratch ? " You scratch it." : ""]") + if(can_scratch) + bodypart.receive_damage(0.5) diff --git a/code/datums/diseases/advance/symptoms/oxygen.dm b/code/datums/diseases/advance/symptoms/oxygen.dm index 7f067180c7ef..9fe153865d36 100644 --- a/code/datums/diseases/advance/symptoms/oxygen.dm +++ b/code/datums/diseases/advance/symptoms/oxygen.dm @@ -1,69 +1,69 @@ -/* -////////////////////////////////////// - -Self-Respiration - - Slightly hidden. - Lowers resistance significantly. - Decreases stage speed significantly. - Decreases transmittablity tremendously. - Fatal Level. - -Bonus - The body generates salbutamol. - -////////////////////////////////////// -*/ - -/datum/symptom/oxygen - - name = "Self-Respiration" - desc = "The virus rapidly synthesizes oxygen, effectively removing the need for breathing." - stealth = 1 - resistance = -3 - stage_speed = -3 - transmittable = -4 - level = 6 - base_message_chance = 5 - symptom_delay_min = 1 - symptom_delay_max = 1 - var/regenerate_blood = FALSE - threshold_descs = list( - "Resistance 8" = "Additionally regenerates lost blood." - ) - -/datum/symptom/oxygen/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["resistance"] >= 8) //blood regeneration - regenerate_blood = TRUE - -/datum/symptom/oxygen/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/carbon/M = A.affected_mob - switch(A.stage) - if(4, 5) - M.adjustOxyLoss(-7, 0) - M.losebreath = max(0, M.losebreath - 4) - if(regenerate_blood && M.blood_volume < BLOOD_VOLUME_NORMAL) - M.blood_volume += 1 - else - if(prob(base_message_chance)) - to_chat(M, "[pick("Your lungs feel great.", "You realize you haven't been breathing.", "You don't feel the need to breathe.")]") - return - -/datum/symptom/oxygen/on_stage_change(datum/disease/advance/A) - if(!..()) - return FALSE - var/mob/living/carbon/M = A.affected_mob - if(A.stage >= 4) - ADD_TRAIT(M, TRAIT_NOBREATH, DISEASE_TRAIT) - else - REMOVE_TRAIT(M, TRAIT_NOBREATH, DISEASE_TRAIT) - return TRUE - -/datum/symptom/oxygen/End(datum/disease/advance/A) - if(!..()) - return - REMOVE_TRAIT(A.affected_mob, TRAIT_NOBREATH, DISEASE_TRAIT) +/* +////////////////////////////////////// + +Self-Respiration + + Slightly hidden. + Lowers resistance significantly. + Decreases stage speed significantly. + Decreases transmittablity tremendously. + Fatal Level. + +Bonus + The body generates salbutamol. + +////////////////////////////////////// +*/ + +/datum/symptom/oxygen + + name = "Self-Respiration" + desc = "The virus rapidly synthesizes oxygen, effectively removing the need for breathing." + stealth = 1 + resistance = -3 + stage_speed = -3 + transmittable = -4 + level = 6 + base_message_chance = 5 + symptom_delay_min = 1 + symptom_delay_max = 1 + var/regenerate_blood = FALSE + threshold_descs = list( + "Resistance 8" = "Additionally regenerates lost blood." + ) + +/datum/symptom/oxygen/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["resistance"] >= 8) //blood regeneration + regenerate_blood = TRUE + +/datum/symptom/oxygen/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/carbon/M = A.affected_mob + switch(A.stage) + if(4, 5) + M.adjustOxyLoss(-7, 0) + M.losebreath = max(0, M.losebreath - 4) + if(regenerate_blood && M.blood_volume < BLOOD_VOLUME_NORMAL) + M.blood_volume += 1 + else + if(prob(base_message_chance)) + to_chat(M, "[pick("Your lungs feel great.", "You realize you haven't been breathing.", "You don't feel the need to breathe.")]") + return + +/datum/symptom/oxygen/on_stage_change(datum/disease/advance/A) + if(!..()) + return FALSE + var/mob/living/carbon/M = A.affected_mob + if(A.stage >= 4) + ADD_TRAIT(M, TRAIT_NOBREATH, DISEASE_TRAIT) + else + REMOVE_TRAIT(M, TRAIT_NOBREATH, DISEASE_TRAIT) + return TRUE + +/datum/symptom/oxygen/End(datum/disease/advance/A) + if(!..()) + return + REMOVE_TRAIT(A.affected_mob, TRAIT_NOBREATH, DISEASE_TRAIT) diff --git a/code/datums/diseases/advance/symptoms/shedding.dm b/code/datums/diseases/advance/symptoms/shedding.dm index 521264e565ad..d1b59edbc1c8 100644 --- a/code/datums/diseases/advance/symptoms/shedding.dm +++ b/code/datums/diseases/advance/symptoms/shedding.dm @@ -1,55 +1,55 @@ -/* -////////////////////////////////////// -Alopecia - - Not Noticeable. - Increases resistance slightly. - Increases stage speed. - Transmittable. - Intense Level. - -BONUS - Makes the mob lose hair. - -////////////////////////////////////// -*/ - -/datum/symptom/shedding - name = "Alopecia" - desc = "The virus causes rapid shedding of head and body hair." - stealth = 0 - resistance = 1 - stage_speed = 2 - transmittable = 2 - level = 4 - severity = 1 - base_message_chance = 50 - symptom_delay_min = 45 - symptom_delay_max = 90 - -/datum/symptom/shedding/Activate(datum/disease/advance/A) - if(!..()) - return - - var/mob/living/M = A.affected_mob - if(prob(base_message_chance)) - to_chat(M, "[pick("Your scalp itches.", "Your skin feels flaky.")]") - if(ishuman(M)) - var/mob/living/carbon/human/H = M - switch(A.stage) - if(3, 4) - if(!(H.hairstyle == "Bald") && !(H.hairstyle == "Balding Hair")) - to_chat(H, "Your hair starts to fall out in clumps...") - addtimer(CALLBACK(src, .proc/Shed, H, FALSE), 50) - if(5) - if(!(H.facial_hairstyle == "Shaved") || !(H.hairstyle == "Bald")) - to_chat(H, "Your hair starts to fall out in clumps...") - addtimer(CALLBACK(src, .proc/Shed, H, TRUE), 50) - -/datum/symptom/shedding/proc/Shed(mob/living/carbon/human/H, fullbald) - if(fullbald) - H.facial_hairstyle = "Shaved" - H.hairstyle = "Bald" - else - H.hairstyle = "Balding Hair" - H.update_hair() +/* +////////////////////////////////////// +Alopecia + + Not Noticeable. + Increases resistance slightly. + Increases stage speed. + Transmittable. + Intense Level. + +BONUS + Makes the mob lose hair. + +////////////////////////////////////// +*/ + +/datum/symptom/shedding + name = "Alopecia" + desc = "The virus causes rapid shedding of head and body hair." + stealth = 0 + resistance = 1 + stage_speed = 2 + transmittable = 2 + level = 4 + severity = 1 + base_message_chance = 50 + symptom_delay_min = 45 + symptom_delay_max = 90 + +/datum/symptom/shedding/Activate(datum/disease/advance/A) + if(!..()) + return + + var/mob/living/M = A.affected_mob + if(prob(base_message_chance)) + to_chat(M, "[pick("Your scalp itches.", "Your skin feels flaky.")]") + if(ishuman(M)) + var/mob/living/carbon/human/H = M + switch(A.stage) + if(3, 4) + if(!(H.hairstyle == "Bald") && !(H.hairstyle == "Balding Hair")) + to_chat(H, "Your hair starts to fall out in clumps...") + addtimer(CALLBACK(src, .proc/Shed, H, FALSE), 50) + if(5) + if(!(H.facial_hairstyle == "Shaved") || !(H.hairstyle == "Bald")) + to_chat(H, "Your hair starts to fall out in clumps...") + addtimer(CALLBACK(src, .proc/Shed, H, TRUE), 50) + +/datum/symptom/shedding/proc/Shed(mob/living/carbon/human/H, fullbald) + if(fullbald) + H.facial_hairstyle = "Shaved" + H.hairstyle = "Bald" + else + H.hairstyle = "Balding Hair" + H.update_hair() diff --git a/code/datums/diseases/advance/symptoms/shivering.dm b/code/datums/diseases/advance/symptoms/shivering.dm index 6214685945c7..2e9762d64b1d 100644 --- a/code/datums/diseases/advance/symptoms/shivering.dm +++ b/code/datums/diseases/advance/symptoms/shivering.dm @@ -1,75 +1,75 @@ -/* -////////////////////////////////////// - -Shivering - - No change to hidden. - Increases resistance. - Increases stage speed. - Little transmittable. - Low level. - -Bonus - Cools down your body. - -////////////////////////////////////// -*/ - -/datum/symptom/shivering - name = "Shivering" - desc = "The virus inhibits the body's thermoregulation, cooling the body down." - stealth = 0 - resistance = 2 - stage_speed = 3 - transmittable = 2 - level = 2 - severity = 2 - symptom_delay_min = 10 - symptom_delay_max = 30 - var/unsafe = FALSE //over the cold threshold - threshold_descs = list( - "Stage Speed 5" = "Increases cooling speed,; the host can fall below safe temperature levels.", - "Stage Speed 10" = "Further increases cooling speed." - ) - -/datum/symptom/fever/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["stage_rate"] >= 5) //dangerous cold - power = 1.5 - unsafe = TRUE - if(A.properties["stage_rate"] >= 10) - power = 2.5 - set_body_temp(A.affected_mob, A) - -/datum/symptom/shivering/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/carbon/M = A.affected_mob - if(!unsafe || A.stage < 4) - to_chat(M, "[pick("You feel cold.", "You shiver.")]") - else - to_chat(M, "[pick("You feel your blood run cold.", "You feel ice in your veins.", "You feel like you can't heat up.", "You shiver violently." )]") - -/** - * set_body_temp Sets the body temp change - * - * Sets the body temp change to the mob based on the stage and resistance of the disease - * arguments: - * * mob/living/M The mob to apply changes to - * * datum/disease/advance/A The disease applying the symptom - */ -/datum/symptom/shivering/proc/set_body_temp(mob/living/M, datum/disease/advance/A) - M.add_body_temperature_change("shivering", -((6 * power) * A.stage)) - -/// Update the body temp change based on the new stage -/datum/symptom/shivering/on_stage_change(datum/disease/advance/A) - . = ..() - if(.) - set_body_temp(A.affected_mob, A) - -/// remove the body temp change when removing symptom -/datum/symptom/shivering/End(datum/disease/advance/A) - var/mob/living/carbon/M = A.affected_mob - if(M) - M.remove_body_temperature_change("shivering") +/* +////////////////////////////////////// + +Shivering + + No change to hidden. + Increases resistance. + Increases stage speed. + Little transmittable. + Low level. + +Bonus + Cools down your body. + +////////////////////////////////////// +*/ + +/datum/symptom/shivering + name = "Shivering" + desc = "The virus inhibits the body's thermoregulation, cooling the body down." + stealth = 0 + resistance = 2 + stage_speed = 3 + transmittable = 2 + level = 2 + severity = 2 + symptom_delay_min = 10 + symptom_delay_max = 30 + var/unsafe = FALSE //over the cold threshold + threshold_descs = list( + "Stage Speed 5" = "Increases cooling speed,; the host can fall below safe temperature levels.", + "Stage Speed 10" = "Further increases cooling speed." + ) + +/datum/symptom/fever/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["stage_rate"] >= 5) //dangerous cold + power = 1.5 + unsafe = TRUE + if(A.properties["stage_rate"] >= 10) + power = 2.5 + set_body_temp(A.affected_mob, A) + +/datum/symptom/shivering/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/carbon/M = A.affected_mob + if(!unsafe || A.stage < 4) + to_chat(M, "[pick("You feel cold.", "You shiver.")]") + else + to_chat(M, "[pick("You feel your blood run cold.", "You feel ice in your veins.", "You feel like you can't heat up.", "You shiver violently." )]") + +/** + * set_body_temp Sets the body temp change + * + * Sets the body temp change to the mob based on the stage and resistance of the disease + * arguments: + * * mob/living/M The mob to apply changes to + * * datum/disease/advance/A The disease applying the symptom + */ +/datum/symptom/shivering/proc/set_body_temp(mob/living/M, datum/disease/advance/A) + M.add_body_temperature_change("shivering", -((6 * power) * A.stage)) + +/// Update the body temp change based on the new stage +/datum/symptom/shivering/on_stage_change(datum/disease/advance/A) + . = ..() + if(.) + set_body_temp(A.affected_mob, A) + +/// remove the body temp change when removing symptom +/datum/symptom/shivering/End(datum/disease/advance/A) + var/mob/living/carbon/M = A.affected_mob + if(M) + M.remove_body_temperature_change("shivering") diff --git a/code/datums/diseases/advance/symptoms/sneeze.dm b/code/datums/diseases/advance/symptoms/sneeze.dm index ac32f416aa59..8b8bfc56f26a 100644 --- a/code/datums/diseases/advance/symptoms/sneeze.dm +++ b/code/datums/diseases/advance/symptoms/sneeze.dm @@ -1,65 +1,65 @@ -/* -////////////////////////////////////// - -Sneezing - - Very Noticable. - Increases resistance. - Doesn't increase stage speed. - Very transmissible. - Low Level. - -Bonus - Forces a spread type of AIRBORNE - with extra range! - -////////////////////////////////////// -*/ - -/datum/symptom/sneeze - name = "Sneezing" - desc = "The virus causes irritation of the nasal cavity, making the host sneeze occasionally. Sneezes from this symptom will spread the virus in a 4 meter cone in front of the host." - stealth = -2 - resistance = 3 - stage_speed = 0 - transmittable = 4 - level = 1 - severity = 1 - symptom_delay_min = 5 - symptom_delay_max = 35 - var/spread_range = 4 - var/cartoon_sneezing = FALSE //ah, ah, AH, AH-CHOO!! - threshold_descs = list( - "Transmission 9" = "Increases sneezing range, spreading the virus over 6 meter cone instead of over a 4 meter cone.", - "Stealth 4" = "The symptom remains hidden until active.", - "Stage Speed 17" = "The force of each sneeze catapults the host backwards, potentially stunning and lightly damaging them if they hit a wall or another person mid-flight." - ) - -/datum/symptom/sneeze/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["transmittable"] >= 9) //longer spread range - spread_range = 6 - if(A.properties["stealth"] >= 4) - suppress_warning = TRUE - if(A.properties["stage_rate"] >= 17) //Yep, stage speed 17, not stage speed 7. This is a big boy threshold (effect), like the language-scrambling transmission one for the voice change symptom. - cartoon_sneezing = TRUE //for a really fun time, distribute a disease with this threshold met while the gravity generator is down - -/datum/symptom/sneeze/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/M = A.affected_mob - switch(A.stage) - if(1, 2, 3) - if(!suppress_warning) - M.emote("sniff") - else - M.emote("sneeze") - if(M.CanSpreadAirborneDisease()) //don't spread germs if they covered their mouth - for(var/mob/living/L in oview(spread_range, M)) - if(is_A_facing_B(M, L) && disease_air_spread_walk(get_turf(M), get_turf(L))) - L.AirborneContractDisease(A, TRUE) - if(cartoon_sneezing) //Yeah, this can fling you around even if you have a space suit helmet on. It's, uh, bluespace snot, yeah. - var/sneeze_distance = rand(2,4) //twice as far as a normal baseball bat strike will fling you - var/turf/target = get_ranged_target_turf(M, turn(M.dir, 180), sneeze_distance) - M.throw_at(target, sneeze_distance, 7) //flings you at the speed that a normal baseball bat would fling you at +/* +////////////////////////////////////// + +Sneezing + + Very Noticable. + Increases resistance. + Doesn't increase stage speed. + Very transmissible. + Low Level. + +Bonus + Forces a spread type of AIRBORNE + with extra range! + +////////////////////////////////////// +*/ + +/datum/symptom/sneeze + name = "Sneezing" + desc = "The virus causes irritation of the nasal cavity, making the host sneeze occasionally. Sneezes from this symptom will spread the virus in a 4 meter cone in front of the host." + stealth = -2 + resistance = 3 + stage_speed = 0 + transmittable = 4 + level = 1 + severity = 1 + symptom_delay_min = 5 + symptom_delay_max = 35 + var/spread_range = 4 + var/cartoon_sneezing = FALSE //ah, ah, AH, AH-CHOO!! + threshold_descs = list( + "Transmission 9" = "Increases sneezing range, spreading the virus over 6 meter cone instead of over a 4 meter cone.", + "Stealth 4" = "The symptom remains hidden until active.", + "Stage Speed 17" = "The force of each sneeze catapults the host backwards, potentially stunning and lightly damaging them if they hit a wall or another person mid-flight." + ) + +/datum/symptom/sneeze/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["transmittable"] >= 9) //longer spread range + spread_range = 6 + if(A.properties["stealth"] >= 4) + suppress_warning = TRUE + if(A.properties["stage_rate"] >= 17) //Yep, stage speed 17, not stage speed 7. This is a big boy threshold (effect), like the language-scrambling transmission one for the voice change symptom. + cartoon_sneezing = TRUE //for a really fun time, distribute a disease with this threshold met while the gravity generator is down + +/datum/symptom/sneeze/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/M = A.affected_mob + switch(A.stage) + if(1, 2, 3) + if(!suppress_warning) + M.emote("sniff") + else + M.emote("sneeze") + if(M.CanSpreadAirborneDisease()) //don't spread germs if they covered their mouth + for(var/mob/living/L in oview(spread_range, M)) + if(is_A_facing_B(M, L) && disease_air_spread_walk(get_turf(M), get_turf(L))) + L.AirborneContractDisease(A, TRUE) + if(cartoon_sneezing) //Yeah, this can fling you around even if you have a space suit helmet on. It's, uh, bluespace snot, yeah. + var/sneeze_distance = rand(2,4) //twice as far as a normal baseball bat strike will fling you + var/turf/target = get_ranged_target_turf(M, turn(M.dir, 180), sneeze_distance) + M.throw_at(target, sneeze_distance, 7) //flings you at the speed that a normal baseball bat would fling you at diff --git a/code/datums/diseases/advance/symptoms/symptoms.dm b/code/datums/diseases/advance/symptoms/symptoms.dm index ea5a2a353803..da0f922f3333 100644 --- a/code/datums/diseases/advance/symptoms/symptoms.dm +++ b/code/datums/diseases/advance/symptoms/symptoms.dm @@ -1,81 +1,81 @@ -// Symptoms are the effects that engineered advanced diseases do. - -/datum/symptom - // Buffs/Debuffs the symptom has to the overall engineered disease. - var/name = "" - var/desc = "If you see this something went very wrong." //Basic symptom description - var/threshold_descs = list() //Descriptions of threshold effects - var/stealth = 0 - var/resistance = 0 - var/stage_speed = 0 - var/transmittable = 0 - // The type level of the symptom. Higher is harder to generate. - var/level = 0 - // The severity level of the symptom. Higher is more dangerous. - var/severity = 0 - // The hash tag for our diseases, we will add it up with our other symptoms to get a unique id! ID MUST BE UNIQUE!!! - var/id = "" - //Base chance of sending warning messages, so it can be modified - var/base_message_chance = 10 - //If the early warnings are suppressed or not - var/suppress_warning = FALSE - //Ticks between each activation - var/next_activation = 0 - var/symptom_delay_min = 1 - var/symptom_delay_max = 1 - //Can be used to multiply virus effects - var/power = 1 - //A neutered symptom has no effect, and only affects statistics. - var/neutered = FALSE - var/list/thresholds - var/naturally_occuring = TRUE //if this symptom can appear from /datum/disease/advance/GenerateSymptoms() - -/datum/symptom/New() - var/list/S = SSdisease.list_symptoms - for(var/i = 1; i <= S.len; i++) - if(type == S[i]) - id = "[i]" - return - CRASH("We couldn't assign an ID!") - -// Called when processing of the advance disease that holds this symptom infects a host and upon each Refresh() of that advance disease. -/datum/symptom/proc/Start(datum/disease/advance/A) - if(neutered) - return FALSE - return TRUE - -// Called when the advance disease is going to be deleted or when the advance disease stops processing. -/datum/symptom/proc/End(datum/disease/advance/A) - if(neutered) - return FALSE - return TRUE - -/datum/symptom/proc/Activate(datum/disease/advance/A) - if(neutered) - return FALSE - if(world.time < next_activation) - return FALSE - else - next_activation = world.time + rand(symptom_delay_min * 10, symptom_delay_max * 10) - return TRUE - -/datum/symptom/proc/on_stage_change(datum/disease/advance/A) - if(neutered) - return FALSE - return TRUE - -/datum/symptom/proc/Copy() - var/datum/symptom/new_symp = new type - new_symp.name = name - new_symp.id = id - new_symp.neutered = neutered - return new_symp - -/datum/symptom/proc/generate_threshold_desc() - return - -/datum/symptom/proc/OnAdd(datum/disease/advance/A) //Overload when a symptom needs to be active before processing, like changing biotypes. - return - -/datum/symptom/proc/OnRemove(datum/disease/advance/A) //But dont forget to remove them too. - return +// Symptoms are the effects that engineered advanced diseases do. + +/datum/symptom + // Buffs/Debuffs the symptom has to the overall engineered disease. + var/name = "" + var/desc = "If you see this something went very wrong." //Basic symptom description + var/threshold_descs = list() //Descriptions of threshold effects + var/stealth = 0 + var/resistance = 0 + var/stage_speed = 0 + var/transmittable = 0 + // The type level of the symptom. Higher is harder to generate. + var/level = 0 + // The severity level of the symptom. Higher is more dangerous. + var/severity = 0 + // The hash tag for our diseases, we will add it up with our other symptoms to get a unique id! ID MUST BE UNIQUE!!! + var/id = "" + //Base chance of sending warning messages, so it can be modified + var/base_message_chance = 10 + //If the early warnings are suppressed or not + var/suppress_warning = FALSE + //Ticks between each activation + var/next_activation = 0 + var/symptom_delay_min = 1 + var/symptom_delay_max = 1 + //Can be used to multiply virus effects + var/power = 1 + //A neutered symptom has no effect, and only affects statistics. + var/neutered = FALSE + var/list/thresholds + var/naturally_occuring = TRUE //if this symptom can appear from /datum/disease/advance/GenerateSymptoms() + +/datum/symptom/New() + var/list/S = SSdisease.list_symptoms + for(var/i = 1; i <= S.len; i++) + if(type == S[i]) + id = "[i]" + return + CRASH("We couldn't assign an ID!") + +// Called when processing of the advance disease that holds this symptom infects a host and upon each Refresh() of that advance disease. +/datum/symptom/proc/Start(datum/disease/advance/A) + if(neutered) + return FALSE + return TRUE + +// Called when the advance disease is going to be deleted or when the advance disease stops processing. +/datum/symptom/proc/End(datum/disease/advance/A) + if(neutered) + return FALSE + return TRUE + +/datum/symptom/proc/Activate(datum/disease/advance/A) + if(neutered) + return FALSE + if(world.time < next_activation) + return FALSE + else + next_activation = world.time + rand(symptom_delay_min * 10, symptom_delay_max * 10) + return TRUE + +/datum/symptom/proc/on_stage_change(datum/disease/advance/A) + if(neutered) + return FALSE + return TRUE + +/datum/symptom/proc/Copy() + var/datum/symptom/new_symp = new type + new_symp.name = name + new_symp.id = id + new_symp.neutered = neutered + return new_symp + +/datum/symptom/proc/generate_threshold_desc() + return + +/datum/symptom/proc/OnAdd(datum/disease/advance/A) //Overload when a symptom needs to be active before processing, like changing biotypes. + return + +/datum/symptom/proc/OnRemove(datum/disease/advance/A) //But dont forget to remove them too. + return diff --git a/code/datums/diseases/advance/symptoms/voice_change.dm b/code/datums/diseases/advance/symptoms/voice_change.dm index 4fd09d05b2aa..20c0a6df40d0 100644 --- a/code/datums/diseases/advance/symptoms/voice_change.dm +++ b/code/datums/diseases/advance/symptoms/voice_change.dm @@ -1,75 +1,75 @@ -/* -////////////////////////////////////// - -Voice Change - - Noticeable. - Lowers resistance. - Decreases stage speed. - Increased transmittable. - Fatal Level. - -Bonus - Changes the voice of the affected mob. Causing confusion in communication. - -////////////////////////////////////// -*/ - -/datum/symptom/voice_change - - name = "Voice Change" - desc = "The virus alters the pitch and tone of the host's vocal cords, changing how their voice sounds." - stealth = -1 - resistance = -2 - stage_speed = -2 - transmittable = 2 - level = 6 - severity = 2 - base_message_chance = 100 - symptom_delay_min = 60 - symptom_delay_max = 120 - var/scramble_language = FALSE - var/datum/language/current_language - threshold_descs = list( - "Transmission 14" = "The host's language center of the brain is damaged, leading to complete inability to speak or understand any language.", - "Stage Speed 7" = "Changes voice more often.", - "Stealth 3" = "The symptom remains hidden until active." - ) - -/datum/symptom/voice_change/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["stealth"] >= 3) - suppress_warning = TRUE - if(A.properties["stage_rate"] >= 7) //faster change of voice - base_message_chance = 25 - symptom_delay_min = 25 - symptom_delay_max = 85 - if(A.properties["transmittable"] >= 14) //random language - scramble_language = TRUE - -/datum/symptom/voice_change/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/carbon/M = A.affected_mob - switch(A.stage) - if(1, 2, 3, 4) - if(prob(base_message_chance) && !suppress_warning) - to_chat(M, "[pick("Your throat hurts.", "You clear your throat.")]") - else - if(ishuman(M)) - var/mob/living/carbon/human/H = M - H.SetSpecialVoice(H.dna.species.random_name(H.gender)) - if(scramble_language && !current_language) // Last part prevents rerolling language with small amounts of cure. - current_language = pick(subtypesof(/datum/language) - /datum/language/common) - H.add_blocked_language(subtypesof(/datum/language) - current_language, LANGUAGE_VOICECHANGE) - H.grant_language(current_language, TRUE, TRUE, LANGUAGE_VOICECHANGE) - -/datum/symptom/voice_change/End(datum/disease/advance/A) - ..() - if(ishuman(A.affected_mob)) - var/mob/living/carbon/human/H = A.affected_mob - H.UnsetSpecialVoice() - if(scramble_language) - A.affected_mob.remove_blocked_language(subtypesof(/datum/language), LANGUAGE_VOICECHANGE) - A.affected_mob.remove_all_languages(LANGUAGE_VOICECHANGE) // In case someone managed to get more than one anyway. +/* +////////////////////////////////////// + +Voice Change + + Noticeable. + Lowers resistance. + Decreases stage speed. + Increased transmittable. + Fatal Level. + +Bonus + Changes the voice of the affected mob. Causing confusion in communication. + +////////////////////////////////////// +*/ + +/datum/symptom/voice_change + + name = "Voice Change" + desc = "The virus alters the pitch and tone of the host's vocal cords, changing how their voice sounds." + stealth = -1 + resistance = -2 + stage_speed = -2 + transmittable = 2 + level = 6 + severity = 2 + base_message_chance = 100 + symptom_delay_min = 60 + symptom_delay_max = 120 + var/scramble_language = FALSE + var/datum/language/current_language + threshold_descs = list( + "Transmission 14" = "The host's language center of the brain is damaged, leading to complete inability to speak or understand any language.", + "Stage Speed 7" = "Changes voice more often.", + "Stealth 3" = "The symptom remains hidden until active." + ) + +/datum/symptom/voice_change/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["stealth"] >= 3) + suppress_warning = TRUE + if(A.properties["stage_rate"] >= 7) //faster change of voice + base_message_chance = 25 + symptom_delay_min = 25 + symptom_delay_max = 85 + if(A.properties["transmittable"] >= 14) //random language + scramble_language = TRUE + +/datum/symptom/voice_change/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/carbon/M = A.affected_mob + switch(A.stage) + if(1, 2, 3, 4) + if(prob(base_message_chance) && !suppress_warning) + to_chat(M, "[pick("Your throat hurts.", "You clear your throat.")]") + else + if(ishuman(M)) + var/mob/living/carbon/human/H = M + H.SetSpecialVoice(H.dna.species.random_name(H.gender)) + if(scramble_language && !current_language) // Last part prevents rerolling language with small amounts of cure. + current_language = pick(subtypesof(/datum/language) - /datum/language/common) + H.add_blocked_language(subtypesof(/datum/language) - current_language, LANGUAGE_VOICECHANGE) + H.grant_language(current_language, TRUE, TRUE, LANGUAGE_VOICECHANGE) + +/datum/symptom/voice_change/End(datum/disease/advance/A) + ..() + if(ishuman(A.affected_mob)) + var/mob/living/carbon/human/H = A.affected_mob + H.UnsetSpecialVoice() + if(scramble_language) + A.affected_mob.remove_blocked_language(subtypesof(/datum/language), LANGUAGE_VOICECHANGE) + A.affected_mob.remove_all_languages(LANGUAGE_VOICECHANGE) // In case someone managed to get more than one anyway. diff --git a/code/datums/diseases/advance/symptoms/vomit.dm b/code/datums/diseases/advance/symptoms/vomit.dm index ef734ed794ee..38afa60fd8dc 100644 --- a/code/datums/diseases/advance/symptoms/vomit.dm +++ b/code/datums/diseases/advance/symptoms/vomit.dm @@ -1,65 +1,65 @@ -/* -////////////////////////////////////// - -Vomiting - - Very Very Noticable. - Decreases resistance. - Doesn't increase stage speed. - Little transmissibility. - Medium Level. - -Bonus - Forces the affected mob to vomit! - Meaning your disease can spread via - people walking on vomit. - Makes the affected mob lose nutrition and - heal toxin damage. - -////////////////////////////////////// -*/ - -/datum/symptom/vomit - - name = "Vomiting" - desc = "The virus causes nausea and irritates the stomach, causing occasional vomit." - stealth = -2 - resistance = -1 - stage_speed = -1 - transmittable = 2 - level = 3 - severity = 3 - base_message_chance = 100 - symptom_delay_min = 25 - symptom_delay_max = 80 - var/vomit_blood = FALSE - var/proj_vomit = 0 - threshold_descs = list( - "Resistance 7" = "Host will vomit blood, causing internal damage.", - "Transmission 7" = "Host will projectile vomit, increasing vomiting range.", - "Stealth 4" = "The symptom remains hidden until active." - ) - -/datum/symptom/vomit/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["stealth"] >= 4) - suppress_warning = TRUE - if(A.properties["resistance"] >= 7) //blood vomit - vomit_blood = TRUE - if(A.properties["transmittable"] >= 7) //projectile vomit - proj_vomit = 5 - -/datum/symptom/vomit/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/M = A.affected_mob - switch(A.stage) - if(1, 2, 3, 4) - if(prob(base_message_chance) && !suppress_warning) - to_chat(M, "[pick("You feel nauseated.", "You feel like you're going to throw up!")]") - else - vomit(M) - -/datum/symptom/vomit/proc/vomit(mob/living/carbon/M) - M.vomit(20, vomit_blood, distance = proj_vomit) +/* +////////////////////////////////////// + +Vomiting + + Very Very Noticable. + Decreases resistance. + Doesn't increase stage speed. + Little transmissibility. + Medium Level. + +Bonus + Forces the affected mob to vomit! + Meaning your disease can spread via + people walking on vomit. + Makes the affected mob lose nutrition and + heal toxin damage. + +////////////////////////////////////// +*/ + +/datum/symptom/vomit + + name = "Vomiting" + desc = "The virus causes nausea and irritates the stomach, causing occasional vomit." + stealth = -2 + resistance = -1 + stage_speed = -1 + transmittable = 2 + level = 3 + severity = 3 + base_message_chance = 100 + symptom_delay_min = 25 + symptom_delay_max = 80 + var/vomit_blood = FALSE + var/proj_vomit = 0 + threshold_descs = list( + "Resistance 7" = "Host will vomit blood, causing internal damage.", + "Transmission 7" = "Host will projectile vomit, increasing vomiting range.", + "Stealth 4" = "The symptom remains hidden until active." + ) + +/datum/symptom/vomit/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["stealth"] >= 4) + suppress_warning = TRUE + if(A.properties["resistance"] >= 7) //blood vomit + vomit_blood = TRUE + if(A.properties["transmittable"] >= 7) //projectile vomit + proj_vomit = 5 + +/datum/symptom/vomit/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/M = A.affected_mob + switch(A.stage) + if(1, 2, 3, 4) + if(prob(base_message_chance) && !suppress_warning) + to_chat(M, "[pick("You feel nauseated.", "You feel like you're going to throw up!")]") + else + vomit(M) + +/datum/symptom/vomit/proc/vomit(mob/living/carbon/M) + M.vomit(20, vomit_blood, distance = proj_vomit) diff --git a/code/datums/diseases/advance/symptoms/weight.dm b/code/datums/diseases/advance/symptoms/weight.dm index 665c9bdf99de..4f42a597e2f6 100644 --- a/code/datums/diseases/advance/symptoms/weight.dm +++ b/code/datums/diseases/advance/symptoms/weight.dm @@ -1,53 +1,53 @@ -/* -////////////////////////////////////// - -Weight Loss - - Very Very Noticable. - Decreases resistance. - Decreases stage speed. - Reduced Transmittable. - High level. - -Bonus - Decreases the weight of the mob, - forcing it to be skinny. - -////////////////////////////////////// -*/ - -/datum/symptom/weight_loss - - name = "Weight Loss" - desc = "The virus mutates the host's metabolism, making it almost unable to gain nutrition from food." - stealth = -2 - resistance = 2 - stage_speed = -2 - transmittable = -2 - level = 3 - severity = 3 - base_message_chance = 100 - symptom_delay_min = 15 - symptom_delay_max = 45 - threshold_descs = list( - "Stealth 4" = "The symptom is less noticeable." - ) - -/datum/symptom/weight_loss/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["stealth"] >= 4) //warn less often - base_message_chance = 25 - -/datum/symptom/weight_loss/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/M = A.affected_mob - switch(A.stage) - if(1, 2, 3, 4) - if(prob(base_message_chance)) - to_chat(M, "[pick("You feel hungry.", "You crave for food.")]") - else - to_chat(M, "[pick("So hungry...", "You'd kill someone for a bite of food...", "Hunger cramps seize you...")]") - M.overeatduration = max(M.overeatduration - 100, 0) - M.adjust_nutrition(-100) +/* +////////////////////////////////////// + +Weight Loss + + Very Very Noticable. + Decreases resistance. + Decreases stage speed. + Reduced Transmittable. + High level. + +Bonus + Decreases the weight of the mob, + forcing it to be skinny. + +////////////////////////////////////// +*/ + +/datum/symptom/weight_loss + + name = "Weight Loss" + desc = "The virus mutates the host's metabolism, making it almost unable to gain nutrition from food." + stealth = -2 + resistance = 2 + stage_speed = -2 + transmittable = -2 + level = 3 + severity = 3 + base_message_chance = 100 + symptom_delay_min = 15 + symptom_delay_max = 45 + threshold_descs = list( + "Stealth 4" = "The symptom is less noticeable." + ) + +/datum/symptom/weight_loss/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["stealth"] >= 4) //warn less often + base_message_chance = 25 + +/datum/symptom/weight_loss/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/M = A.affected_mob + switch(A.stage) + if(1, 2, 3, 4) + if(prob(base_message_chance)) + to_chat(M, "[pick("You feel hungry.", "You crave for food.")]") + else + to_chat(M, "[pick("So hungry...", "You'd kill someone for a bite of food...", "Hunger cramps seize you...")]") + M.overeatduration = max(M.overeatduration - 100, 0) + M.adjust_nutrition(-100) diff --git a/code/datums/diseases/advance/symptoms/youth.dm b/code/datums/diseases/advance/symptoms/youth.dm index e42e162cf91a..a9f52619057f 100644 --- a/code/datums/diseases/advance/symptoms/youth.dm +++ b/code/datums/diseases/advance/symptoms/youth.dm @@ -1,58 +1,58 @@ -/* -////////////////////////////////////// -Eternal Youth - - Moderate stealth boost. - Increases resistance tremendously. - Increases stage speed tremendously. - Reduces transmission tremendously. - Critical Level. - -BONUS - Gives you immortality and eternal youth!!! - Can be used to buff your virus - -////////////////////////////////////// -*/ - -/datum/symptom/youth - - name = "Eternal Youth" - desc = "The virus becomes symbiotically connected to the cells in the host's body, preventing and reversing aging. \ - The virus, in turn, becomes more resistant, spreads faster, and is harder to spot, although it doesn't thrive as well without a host." - stealth = 3 - resistance = 4 - stage_speed = 4 - transmittable = -4 - level = 5 - base_message_chance = 100 - symptom_delay_min = 25 - symptom_delay_max = 50 - -/datum/symptom/youth/Activate(datum/disease/advance/A) - if(!..()) - return - var/mob/living/M = A.affected_mob - if(ishuman(M)) - var/mob/living/carbon/human/H = M - switch(A.stage) - if(1) - if(H.age > 41) - H.age = 41 - to_chat(H, "You haven't had this much energy in years!") - if(2) - if(H.age > 36) - H.age = 36 - to_chat(H, "You're suddenly in a good mood.") - if(3) - if(H.age > 31) - H.age = 31 - to_chat(H, "You begin to feel more lithe.") - if(4) - if(H.age > 26) - H.age = 26 - to_chat(H, "You feel reinvigorated.") - if(5) - if(H.age > 21) - H.age = 21 - to_chat(H, "You feel like you can take on the world!") +/* +////////////////////////////////////// +Eternal Youth + + Moderate stealth boost. + Increases resistance tremendously. + Increases stage speed tremendously. + Reduces transmission tremendously. + Critical Level. + +BONUS + Gives you immortality and eternal youth!!! + Can be used to buff your virus + +////////////////////////////////////// +*/ + +/datum/symptom/youth + + name = "Eternal Youth" + desc = "The virus becomes symbiotically connected to the cells in the host's body, preventing and reversing aging. \ + The virus, in turn, becomes more resistant, spreads faster, and is harder to spot, although it doesn't thrive as well without a host." + stealth = 3 + resistance = 4 + stage_speed = 4 + transmittable = -4 + level = 5 + base_message_chance = 100 + symptom_delay_min = 25 + symptom_delay_max = 50 + +/datum/symptom/youth/Activate(datum/disease/advance/A) + if(!..()) + return + var/mob/living/M = A.affected_mob + if(ishuman(M)) + var/mob/living/carbon/human/H = M + switch(A.stage) + if(1) + if(H.age > 41) + H.age = 41 + to_chat(H, "You haven't had this much energy in years!") + if(2) + if(H.age > 36) + H.age = 36 + to_chat(H, "You're suddenly in a good mood.") + if(3) + if(H.age > 31) + H.age = 31 + to_chat(H, "You begin to feel more lithe.") + if(4) + if(H.age > 26) + H.age = 26 + to_chat(H, "You feel reinvigorated.") + if(5) + if(H.age > 21) + H.age = 21 + to_chat(H, "You feel like you can take on the world!") diff --git a/code/datums/diseases/appendicitis.dm b/code/datums/diseases/appendicitis.dm index be7e6ceecdb6..7a6ea142b361 100644 --- a/code/datums/diseases/appendicitis.dm +++ b/code/datums/diseases/appendicitis.dm @@ -1,36 +1,36 @@ -/datum/disease/appendicitis - form = "Condition" - name = "Appendicitis" - max_stages = 3 - cure_text = "Surgery" - agent = "Shitty Appendix" - viable_mobtypes = list(/mob/living/carbon/human) - permeability_mod = 1 - desc = "If left untreated the subject will become very weak, and may vomit often." - severity = DISEASE_SEVERITY_MEDIUM - disease_flags = CAN_CARRY|CAN_RESIST - spread_flags = DISEASE_SPREAD_NON_CONTAGIOUS - visibility_flags = HIDDEN_PANDEMIC - required_organs = list(/obj/item/organ/appendix) - bypasses_immunity = TRUE // Immunity is based on not having an appendix; this isn't a virus - -/datum/disease/appendicitis/stage_act() - ..() - switch(stage) - if(1) - if(prob(5)) - affected_mob.emote("cough") - if(2) - var/obj/item/organ/appendix/A = affected_mob.getorgan(/obj/item/organ/appendix) - if(A) - A.inflamed = 1 - A.update_icon() - if(prob(3)) - to_chat(affected_mob, "You feel a stabbing pain in your abdomen!") - affected_mob.adjustOrganLoss(ORGAN_SLOT_APPENDIX, 5) - affected_mob.Stun(rand(40,60)) - affected_mob.adjustToxLoss(1) - if(3) - if(prob(1)) - affected_mob.vomit(95) - affected_mob.adjustOrganLoss(ORGAN_SLOT_APPENDIX, 15) +/datum/disease/appendicitis + form = "Condition" + name = "Appendicitis" + max_stages = 3 + cure_text = "Surgery" + agent = "Shitty Appendix" + viable_mobtypes = list(/mob/living/carbon/human) + permeability_mod = 1 + desc = "If left untreated the subject will become very weak, and may vomit often." + severity = DISEASE_SEVERITY_MEDIUM + disease_flags = CAN_CARRY|CAN_RESIST + spread_flags = DISEASE_SPREAD_NON_CONTAGIOUS + visibility_flags = HIDDEN_PANDEMIC + required_organs = list(/obj/item/organ/appendix) + bypasses_immunity = TRUE // Immunity is based on not having an appendix; this isn't a virus + +/datum/disease/appendicitis/stage_act() + ..() + switch(stage) + if(1) + if(prob(5)) + affected_mob.emote("cough") + if(2) + var/obj/item/organ/appendix/A = affected_mob.getorgan(/obj/item/organ/appendix) + if(A) + A.inflamed = 1 + A.update_icon() + if(prob(3)) + to_chat(affected_mob, "You feel a stabbing pain in your abdomen!") + affected_mob.adjustOrganLoss(ORGAN_SLOT_APPENDIX, 5) + affected_mob.Stun(rand(40,60)) + affected_mob.adjustToxLoss(1) + if(3) + if(prob(1)) + affected_mob.vomit(95) + affected_mob.adjustOrganLoss(ORGAN_SLOT_APPENDIX, 15) diff --git a/code/datums/diseases/beesease.dm b/code/datums/diseases/beesease.dm index a31939d79dc0..53230711d9a3 100644 --- a/code/datums/diseases/beesease.dm +++ b/code/datums/diseases/beesease.dm @@ -1,39 +1,39 @@ -/datum/disease/beesease - name = "Beesease" - form = "Infection" - max_stages = 4 - spread_text = "On contact" - spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_CONTACT_FLUIDS - cure_text = "Sugar" - cures = list(/datum/reagent/consumable/sugar) - agent = "Apidae Infection" - viable_mobtypes = list(/mob/living/carbon/human, /mob/living/carbon/monkey) - desc = "If left untreated subject will regurgitate bees." - severity = DISEASE_SEVERITY_MEDIUM - infectable_biotypes = MOB_ORGANIC|MOB_UNDEAD //bees nesting in corpses - -/datum/disease/beesease/stage_act() - ..() - switch(stage) - if(2) //also changes say, see say.dm - if(prob(2)) - to_chat(affected_mob, "You taste honey in your mouth.") - if(3) - if(prob(10)) - to_chat(affected_mob, "Your stomach rumbles.") - if(prob(2)) - to_chat(affected_mob, "Your stomach stings painfully.") - if(prob(20)) - affected_mob.adjustToxLoss(2) - affected_mob.updatehealth() - if(4) - if(prob(10)) - affected_mob.visible_message("[affected_mob] buzzes.", \ - "Your stomach buzzes violently!") - if(prob(5)) - to_chat(affected_mob, "You feel something moving in your throat.") - if(prob(1)) - affected_mob.visible_message("[affected_mob] coughs up a swarm of bees!", \ - "You cough up a swarm of bees!") - new /mob/living/simple_animal/hostile/poison/bees(affected_mob.loc) - return +/datum/disease/beesease + name = "Beesease" + form = "Infection" + max_stages = 4 + spread_text = "On contact" + spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_CONTACT_FLUIDS + cure_text = "Sugar" + cures = list(/datum/reagent/consumable/sugar) + agent = "Apidae Infection" + viable_mobtypes = list(/mob/living/carbon/human, /mob/living/carbon/monkey) + desc = "If left untreated subject will regurgitate bees." + severity = DISEASE_SEVERITY_MEDIUM + infectable_biotypes = MOB_ORGANIC|MOB_UNDEAD //bees nesting in corpses + +/datum/disease/beesease/stage_act() + ..() + switch(stage) + if(2) //also changes say, see say.dm + if(prob(2)) + to_chat(affected_mob, "You taste honey in your mouth.") + if(3) + if(prob(10)) + to_chat(affected_mob, "Your stomach rumbles.") + if(prob(2)) + to_chat(affected_mob, "Your stomach stings painfully.") + if(prob(20)) + affected_mob.adjustToxLoss(2) + affected_mob.updatehealth() + if(4) + if(prob(10)) + affected_mob.visible_message("[affected_mob] buzzes.", \ + "Your stomach buzzes violently!") + if(prob(5)) + to_chat(affected_mob, "You feel something moving in your throat.") + if(prob(1)) + affected_mob.visible_message("[affected_mob] coughs up a swarm of bees!", \ + "You cough up a swarm of bees!") + new /mob/living/simple_animal/hostile/poison/bees(affected_mob.loc) + return diff --git a/code/datums/diseases/brainrot.dm b/code/datums/diseases/brainrot.dm index fc3fb7322594..0bcd1e30eadb 100644 --- a/code/datums/diseases/brainrot.dm +++ b/code/datums/diseases/brainrot.dm @@ -1,60 +1,60 @@ -/datum/disease/brainrot - name = "Brainrot" - max_stages = 4 - spread_text = "On contact" - spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_CONTACT_FLUIDS - cure_text = "Mannitol" - cures = list(/datum/reagent/medicine/mannitol) - agent = "Cryptococcus Cosmosis" - viable_mobtypes = list(/mob/living/carbon/human) - cure_chance = 15//higher chance to cure, since two reagents are required - desc = "This disease destroys the braincells, causing brain fever, brain necrosis and general intoxication." - required_organs = list(/obj/item/organ/brain) - severity = DISEASE_SEVERITY_HARMFUL - -/datum/disease/brainrot/stage_act() //Removed toxloss because damaging diseases are pretty horrible. Last round it killed the entire station because the cure didn't work -- Urist -ACTUALLY Removed rather than commented out, I don't see it returning - RR - ..() - - switch(stage) - if(2) - if(prob(2)) - affected_mob.emote("blink") - if(prob(2)) - affected_mob.emote("yawn") - if(prob(2)) - to_chat(affected_mob, "You don't feel like yourself.") - if(prob(5)) - affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 1, 170) - affected_mob.updatehealth() - if(3) - if(prob(2)) - affected_mob.emote("stare") - if(prob(2)) - affected_mob.emote("drool") - if(prob(10)) - affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 2, 170) - affected_mob.updatehealth() - if(prob(2)) - to_chat(affected_mob, "Your try to remember something important...but can't.") - - if(4) - if(prob(2)) - affected_mob.emote("stare") - if(prob(2)) - affected_mob.emote("drool") - if(prob(15)) - affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 3, 170) - affected_mob.updatehealth() - if(prob(2)) - to_chat(affected_mob, "Strange buzzing fills your head, removing all thoughts.") - if(prob(3)) - to_chat(affected_mob, "You lose consciousness...") - affected_mob.visible_message("[affected_mob] suddenly collapses!", \ - "You suddenly collapse!") - affected_mob.Unconscious(rand(100,200)) - if(prob(1)) - affected_mob.emote("snore") - if(prob(15)) - affected_mob.stuttering += 3 - - return +/datum/disease/brainrot + name = "Brainrot" + max_stages = 4 + spread_text = "On contact" + spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_CONTACT_FLUIDS + cure_text = "Mannitol" + cures = list(/datum/reagent/medicine/mannitol) + agent = "Cryptococcus Cosmosis" + viable_mobtypes = list(/mob/living/carbon/human) + cure_chance = 15//higher chance to cure, since two reagents are required + desc = "This disease destroys the braincells, causing brain fever, brain necrosis and general intoxication." + required_organs = list(/obj/item/organ/brain) + severity = DISEASE_SEVERITY_HARMFUL + +/datum/disease/brainrot/stage_act() //Removed toxloss because damaging diseases are pretty horrible. Last round it killed the entire station because the cure didn't work -- Urist -ACTUALLY Removed rather than commented out, I don't see it returning - RR + ..() + + switch(stage) + if(2) + if(prob(2)) + affected_mob.emote("blink") + if(prob(2)) + affected_mob.emote("yawn") + if(prob(2)) + to_chat(affected_mob, "You don't feel like yourself.") + if(prob(5)) + affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 1, 170) + affected_mob.updatehealth() + if(3) + if(prob(2)) + affected_mob.emote("stare") + if(prob(2)) + affected_mob.emote("drool") + if(prob(10)) + affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 2, 170) + affected_mob.updatehealth() + if(prob(2)) + to_chat(affected_mob, "Your try to remember something important...but can't.") + + if(4) + if(prob(2)) + affected_mob.emote("stare") + if(prob(2)) + affected_mob.emote("drool") + if(prob(15)) + affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 3, 170) + affected_mob.updatehealth() + if(prob(2)) + to_chat(affected_mob, "Strange buzzing fills your head, removing all thoughts.") + if(prob(3)) + to_chat(affected_mob, "You lose consciousness...") + affected_mob.visible_message("[affected_mob] suddenly collapses!", \ + "You suddenly collapse!") + affected_mob.Unconscious(rand(100,200)) + if(prob(1)) + affected_mob.emote("snore") + if(prob(15)) + affected_mob.stuttering += 3 + + return diff --git a/code/datums/diseases/cold.dm b/code/datums/diseases/cold.dm index 386ca3e24498..6f7d1a415e5c 100644 --- a/code/datums/diseases/cold.dm +++ b/code/datums/diseases/cold.dm @@ -1,53 +1,53 @@ -/datum/disease/cold - name = "The Cold" - max_stages = 3 - cure_text = "Rest & Spaceacillin" - cures = list(/datum/reagent/medicine/spaceacillin) - agent = "XY-rhinovirus" - viable_mobtypes = list(/mob/living/carbon/human, /mob/living/carbon/monkey) - permeability_mod = 0.5 - desc = "If left untreated the subject will contract the flu." - severity = DISEASE_SEVERITY_NONTHREAT - -/datum/disease/cold/stage_act() - ..() - switch(stage) - if(2) - if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(40)) //changed FROM prob(10) until sleeping is fixed - to_chat(affected_mob, "You feel better.") - cure() - return - if(prob(1) && prob(5)) - to_chat(affected_mob, "You feel better.") - cure() - return - if(prob(1)) - affected_mob.emote("sneeze") - if(prob(1)) - affected_mob.emote("cough") - if(prob(1)) - to_chat(affected_mob, "Your throat feels sore.") - if(prob(1)) - to_chat(affected_mob, "Mucous runs down the back of your throat.") - if(3) - if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(25)) //changed FROM prob(5) until sleeping is fixed - to_chat(affected_mob, "You feel better.") - cure() - return - if(prob(1) && prob(1)) - to_chat(affected_mob, "You feel better.") - cure() - return - if(prob(1)) - affected_mob.emote("sneeze") - if(prob(1)) - affected_mob.emote("cough") - if(prob(1)) - to_chat(affected_mob, "Your throat feels sore.") - if(prob(1)) - to_chat(affected_mob, "Mucous runs down the back of your throat.") - if(prob(1) && prob(50)) - if(!affected_mob.disease_resistances.Find(/datum/disease/flu)) - var/datum/disease/Flu = new /datum/disease/flu() - affected_mob.ForceContractDisease(Flu, FALSE, TRUE) - cure() +/datum/disease/cold + name = "The Cold" + max_stages = 3 + cure_text = "Rest & Spaceacillin" + cures = list(/datum/reagent/medicine/spaceacillin) + agent = "XY-rhinovirus" + viable_mobtypes = list(/mob/living/carbon/human, /mob/living/carbon/monkey) + permeability_mod = 0.5 + desc = "If left untreated the subject will contract the flu." + severity = DISEASE_SEVERITY_NONTHREAT + +/datum/disease/cold/stage_act() + ..() + switch(stage) + if(2) + if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(40)) //changed FROM prob(10) until sleeping is fixed + to_chat(affected_mob, "You feel better.") + cure() + return + if(prob(1) && prob(5)) + to_chat(affected_mob, "You feel better.") + cure() + return + if(prob(1)) + affected_mob.emote("sneeze") + if(prob(1)) + affected_mob.emote("cough") + if(prob(1)) + to_chat(affected_mob, "Your throat feels sore.") + if(prob(1)) + to_chat(affected_mob, "Mucous runs down the back of your throat.") + if(3) + if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(25)) //changed FROM prob(5) until sleeping is fixed + to_chat(affected_mob, "You feel better.") + cure() + return + if(prob(1) && prob(1)) + to_chat(affected_mob, "You feel better.") + cure() + return + if(prob(1)) + affected_mob.emote("sneeze") + if(prob(1)) + affected_mob.emote("cough") + if(prob(1)) + to_chat(affected_mob, "Your throat feels sore.") + if(prob(1)) + to_chat(affected_mob, "Mucous runs down the back of your throat.") + if(prob(1) && prob(50)) + if(!affected_mob.disease_resistances.Find(/datum/disease/flu)) + var/datum/disease/Flu = new /datum/disease/flu() + affected_mob.ForceContractDisease(Flu, FALSE, TRUE) + cure() diff --git a/code/datums/diseases/cold9.dm b/code/datums/diseases/cold9.dm index 3d710169b821..58ed52e8b675 100644 --- a/code/datums/diseases/cold9.dm +++ b/code/datums/diseases/cold9.dm @@ -1,39 +1,39 @@ -/datum/disease/cold9 - name = "The Cold" - max_stages = 3 - spread_text = "On contact" - spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_CONTACT_FLUIDS - cure_text = "Common Cold Anti-bodies & Spaceacillin" - cures = list(/datum/reagent/medicine/spaceacillin) - agent = "ICE9-rhinovirus" - viable_mobtypes = list(/mob/living/carbon/human) - desc = "If left untreated the subject will slow, as if partly frozen." - severity = DISEASE_SEVERITY_HARMFUL - -/datum/disease/cold9/stage_act() - ..() - switch(stage) - if(2) - affected_mob.adjust_bodytemperature(-10) - if(prob(1) && prob(10)) - to_chat(affected_mob, "You feel better.") - cure() - return - if(prob(1)) - affected_mob.emote("sneeze") - if(prob(1)) - affected_mob.emote("cough") - if(prob(1)) - to_chat(affected_mob, "Your throat feels sore.") - if(prob(5)) - to_chat(affected_mob, "You feel stiff.") - if(3) - affected_mob.adjust_bodytemperature(-20) - if(prob(1)) - affected_mob.emote("sneeze") - if(prob(1)) - affected_mob.emote("cough") - if(prob(1)) - to_chat(affected_mob, "Your throat feels sore.") - if(prob(10)) - to_chat(affected_mob, "You feel stiff.") +/datum/disease/cold9 + name = "The Cold" + max_stages = 3 + spread_text = "On contact" + spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_CONTACT_FLUIDS + cure_text = "Common Cold Anti-bodies & Spaceacillin" + cures = list(/datum/reagent/medicine/spaceacillin) + agent = "ICE9-rhinovirus" + viable_mobtypes = list(/mob/living/carbon/human) + desc = "If left untreated the subject will slow, as if partly frozen." + severity = DISEASE_SEVERITY_HARMFUL + +/datum/disease/cold9/stage_act() + ..() + switch(stage) + if(2) + affected_mob.adjust_bodytemperature(-10) + if(prob(1) && prob(10)) + to_chat(affected_mob, "You feel better.") + cure() + return + if(prob(1)) + affected_mob.emote("sneeze") + if(prob(1)) + affected_mob.emote("cough") + if(prob(1)) + to_chat(affected_mob, "Your throat feels sore.") + if(prob(5)) + to_chat(affected_mob, "You feel stiff.") + if(3) + affected_mob.adjust_bodytemperature(-20) + if(prob(1)) + affected_mob.emote("sneeze") + if(prob(1)) + affected_mob.emote("cough") + if(prob(1)) + to_chat(affected_mob, "Your throat feels sore.") + if(prob(10)) + to_chat(affected_mob, "You feel stiff.") diff --git a/code/datums/diseases/dna_spread.dm b/code/datums/diseases/dna_spread.dm index 972a7f4e1871..3a67230d36ec 100644 --- a/code/datums/diseases/dna_spread.dm +++ b/code/datums/diseases/dna_spread.dm @@ -1,74 +1,74 @@ -/datum/disease/dnaspread - name = "Space Retrovirus" - max_stages = 4 - spread_text = "On contact" - spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_CONTACT_FLUIDS - cure_text = "Mutadone" - cures = list(/datum/reagent/medicine/mutadone) - disease_flags = CAN_CARRY|CAN_RESIST|CURABLE - agent = "S4E1 retrovirus" - viable_mobtypes = list(/mob/living/carbon/human) - var/datum/dna/original_dna = null - var/transformed = 0 - desc = "This disease transplants the genetic code of the initial vector into new hosts." - severity = DISEASE_SEVERITY_MEDIUM - - -/datum/disease/dnaspread/stage_act() - ..() - if(!affected_mob.dna) - cure() - if((NOTRANSSTING in affected_mob.dna.species.species_traits) || (NO_DNA_COPY in affected_mob.dna.species.species_traits)) //Only species that can be spread by transformation sting can be spread by the retrovirus - cure() - - if(!strain_data["dna"]) - //Absorbs the target DNA. - strain_data["dna"] = new affected_mob.dna.type - affected_mob.dna.copy_dna(strain_data["dna"]) - carrier = TRUE - stage = 4 - return - - switch(stage) - if(2 || 3) //Pretend to be a cold and give time to spread. - if(prob(8)) - affected_mob.emote("sneeze") - if(prob(8)) - affected_mob.emote("cough") - if(prob(1)) - to_chat(affected_mob, "Your muscles ache.") - if(prob(20)) - affected_mob.take_bodypart_damage(1) - if(prob(1)) - to_chat(affected_mob, "Your stomach hurts.") - if(prob(20)) - affected_mob.adjustToxLoss(2) - affected_mob.updatehealth() - if(4) - if(!transformed && !carrier) - //Save original dna for when the disease is cured. - original_dna = new affected_mob.dna.type - affected_mob.dna.copy_dna(original_dna) - - to_chat(affected_mob, "You don't feel like yourself..") - var/datum/dna/transform_dna = strain_data["dna"] - - transform_dna.transfer_identity(affected_mob, transfer_SE = 1) - affected_mob.real_name = affected_mob.dna.real_name - affected_mob.updateappearance(mutcolor_update=1) - affected_mob.domutcheck() - - transformed = 1 - carrier = 1 //Just chill out at stage 4 - - return - -/datum/disease/dnaspread/Destroy() - if (original_dna && transformed && affected_mob) - original_dna.transfer_identity(affected_mob, transfer_SE = 1) - affected_mob.real_name = affected_mob.dna.real_name - affected_mob.updateappearance(mutcolor_update=1) - affected_mob.domutcheck() - - to_chat(affected_mob, "You feel more like yourself.") - return ..() +/datum/disease/dnaspread + name = "Space Retrovirus" + max_stages = 4 + spread_text = "On contact" + spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_CONTACT_FLUIDS + cure_text = "Mutadone" + cures = list(/datum/reagent/medicine/mutadone) + disease_flags = CAN_CARRY|CAN_RESIST|CURABLE + agent = "S4E1 retrovirus" + viable_mobtypes = list(/mob/living/carbon/human) + var/datum/dna/original_dna = null + var/transformed = 0 + desc = "This disease transplants the genetic code of the initial vector into new hosts." + severity = DISEASE_SEVERITY_MEDIUM + + +/datum/disease/dnaspread/stage_act() + ..() + if(!affected_mob.dna) + cure() + if((NOTRANSSTING in affected_mob.dna.species.species_traits) || (NO_DNA_COPY in affected_mob.dna.species.species_traits)) //Only species that can be spread by transformation sting can be spread by the retrovirus + cure() + + if(!strain_data["dna"]) + //Absorbs the target DNA. + strain_data["dna"] = new affected_mob.dna.type + affected_mob.dna.copy_dna(strain_data["dna"]) + carrier = TRUE + stage = 4 + return + + switch(stage) + if(2 || 3) //Pretend to be a cold and give time to spread. + if(prob(8)) + affected_mob.emote("sneeze") + if(prob(8)) + affected_mob.emote("cough") + if(prob(1)) + to_chat(affected_mob, "Your muscles ache.") + if(prob(20)) + affected_mob.take_bodypart_damage(1) + if(prob(1)) + to_chat(affected_mob, "Your stomach hurts.") + if(prob(20)) + affected_mob.adjustToxLoss(2) + affected_mob.updatehealth() + if(4) + if(!transformed && !carrier) + //Save original dna for when the disease is cured. + original_dna = new affected_mob.dna.type + affected_mob.dna.copy_dna(original_dna) + + to_chat(affected_mob, "You don't feel like yourself..") + var/datum/dna/transform_dna = strain_data["dna"] + + transform_dna.transfer_identity(affected_mob, transfer_SE = 1) + affected_mob.real_name = affected_mob.dna.real_name + affected_mob.updateappearance(mutcolor_update=1) + affected_mob.domutcheck() + + transformed = 1 + carrier = 1 //Just chill out at stage 4 + + return + +/datum/disease/dnaspread/Destroy() + if (original_dna && transformed && affected_mob) + original_dna.transfer_identity(affected_mob, transfer_SE = 1) + affected_mob.real_name = affected_mob.dna.real_name + affected_mob.updateappearance(mutcolor_update=1) + affected_mob.domutcheck() + + to_chat(affected_mob, "You feel more like yourself.") + return ..() diff --git a/code/datums/diseases/fake_gbs.dm b/code/datums/diseases/fake_gbs.dm index 70bcc67d2102..37628a5502f1 100644 --- a/code/datums/diseases/fake_gbs.dm +++ b/code/datums/diseases/fake_gbs.dm @@ -1,32 +1,32 @@ -/datum/disease/fake_gbs - name = "GBS" - max_stages = 5 - spread_text = "On contact" - spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_CONTACT_FLUIDS - cure_text = "Synaptizine & Sulfur" - cures = list(/datum/reagent/medicine/synaptizine,/datum/reagent/sulfur) - agent = "Gravitokinetic Bipotential SADS-" - viable_mobtypes = list(/mob/living/carbon/human, /mob/living/carbon/monkey) - desc = "If left untreated death will occur." - severity = DISEASE_SEVERITY_BIOHAZARD - -/datum/disease/fake_gbs/stage_act() - ..() - switch(stage) - if(2) - if(prob(1)) - affected_mob.emote("sneeze") - if(3) - if(prob(5)) - affected_mob.emote("cough") - else if(prob(5)) - affected_mob.emote("gasp") - if(prob(10)) - to_chat(affected_mob, "You're starting to feel very weak...") - if(4) - if(prob(10)) - affected_mob.emote("cough") - - if(5) - if(prob(10)) - affected_mob.emote("cough") +/datum/disease/fake_gbs + name = "GBS" + max_stages = 5 + spread_text = "On contact" + spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_CONTACT_FLUIDS + cure_text = "Synaptizine & Sulfur" + cures = list(/datum/reagent/medicine/synaptizine,/datum/reagent/sulfur) + agent = "Gravitokinetic Bipotential SADS-" + viable_mobtypes = list(/mob/living/carbon/human, /mob/living/carbon/monkey) + desc = "If left untreated death will occur." + severity = DISEASE_SEVERITY_BIOHAZARD + +/datum/disease/fake_gbs/stage_act() + ..() + switch(stage) + if(2) + if(prob(1)) + affected_mob.emote("sneeze") + if(3) + if(prob(5)) + affected_mob.emote("cough") + else if(prob(5)) + affected_mob.emote("gasp") + if(prob(10)) + to_chat(affected_mob, "You're starting to feel very weak...") + if(4) + if(prob(10)) + affected_mob.emote("cough") + + if(5) + if(prob(10)) + affected_mob.emote("cough") diff --git a/code/datums/diseases/flu.dm b/code/datums/diseases/flu.dm index 916e56373fad..62bb3de8df41 100644 --- a/code/datums/diseases/flu.dm +++ b/code/datums/diseases/flu.dm @@ -1,54 +1,54 @@ -/datum/disease/flu - name = "The Flu" - max_stages = 3 - spread_text = "Airborne" - cure_text = "Spaceacillin" - cures = list(/datum/reagent/medicine/spaceacillin) - cure_chance = 10 - agent = "H13N1 flu virion" - viable_mobtypes = list(/mob/living/carbon/human, /mob/living/carbon/monkey) - permeability_mod = 0.75 - desc = "If left untreated the subject will feel quite unwell." - severity = DISEASE_SEVERITY_MINOR - -/datum/disease/flu/stage_act() - ..() - switch(stage) - if(2) - if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(20)) - to_chat(affected_mob, "You feel better.") - stage-- - return - if(prob(1)) - affected_mob.emote("sneeze") - if(prob(1)) - affected_mob.emote("cough") - if(prob(1)) - to_chat(affected_mob, "Your muscles ache.") - if(prob(20)) - affected_mob.take_bodypart_damage(1) - if(prob(1)) - to_chat(affected_mob, "Your stomach hurts.") - if(prob(20)) - affected_mob.adjustToxLoss(1) - affected_mob.updatehealth() - - if(3) - if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(15)) - to_chat(affected_mob, "You feel better.") - stage-- - return - if(prob(1)) - affected_mob.emote("sneeze") - if(prob(1)) - affected_mob.emote("cough") - if(prob(1)) - to_chat(affected_mob, "Your muscles ache.") - if(prob(20)) - affected_mob.take_bodypart_damage(1) - if(prob(1)) - to_chat(affected_mob, "Your stomach hurts.") - if(prob(20)) - affected_mob.adjustToxLoss(1) - affected_mob.updatehealth() - return +/datum/disease/flu + name = "The Flu" + max_stages = 3 + spread_text = "Airborne" + cure_text = "Spaceacillin" + cures = list(/datum/reagent/medicine/spaceacillin) + cure_chance = 10 + agent = "H13N1 flu virion" + viable_mobtypes = list(/mob/living/carbon/human, /mob/living/carbon/monkey) + permeability_mod = 0.75 + desc = "If left untreated the subject will feel quite unwell." + severity = DISEASE_SEVERITY_MINOR + +/datum/disease/flu/stage_act() + ..() + switch(stage) + if(2) + if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(20)) + to_chat(affected_mob, "You feel better.") + stage-- + return + if(prob(1)) + affected_mob.emote("sneeze") + if(prob(1)) + affected_mob.emote("cough") + if(prob(1)) + to_chat(affected_mob, "Your muscles ache.") + if(prob(20)) + affected_mob.take_bodypart_damage(1) + if(prob(1)) + to_chat(affected_mob, "Your stomach hurts.") + if(prob(20)) + affected_mob.adjustToxLoss(1) + affected_mob.updatehealth() + + if(3) + if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(15)) + to_chat(affected_mob, "You feel better.") + stage-- + return + if(prob(1)) + affected_mob.emote("sneeze") + if(prob(1)) + affected_mob.emote("cough") + if(prob(1)) + to_chat(affected_mob, "Your muscles ache.") + if(prob(20)) + affected_mob.take_bodypart_damage(1) + if(prob(1)) + to_chat(affected_mob, "Your stomach hurts.") + if(prob(20)) + affected_mob.adjustToxLoss(1) + affected_mob.updatehealth() + return diff --git a/code/datums/diseases/fluspanish.dm b/code/datums/diseases/fluspanish.dm index 1557ddfbd8d4..3297877fe910 100644 --- a/code/datums/diseases/fluspanish.dm +++ b/code/datums/diseases/fluspanish.dm @@ -1,36 +1,36 @@ -/datum/disease/fluspanish - name = "Spanish inquisition Flu" - max_stages = 3 - spread_text = "Airborne" - cure_text = "Spaceacillin & Anti-bodies to the common flu" - cures = list(/datum/reagent/medicine/spaceacillin) - cure_chance = 10 - agent = "1nqu1s1t10n flu virion" - viable_mobtypes = list(/mob/living/carbon/human) - permeability_mod = 0.75 - desc = "If left untreated the subject will burn to death for being a heretic." - severity = DISEASE_SEVERITY_DANGEROUS - -/datum/disease/fluspanish/stage_act() - ..() - switch(stage) - if(2) - affected_mob.adjust_bodytemperature(10) - if(prob(5)) - affected_mob.emote("sneeze") - if(prob(5)) - affected_mob.emote("cough") - if(prob(1)) - to_chat(affected_mob, "You're burning in your own skin!") - affected_mob.take_bodypart_damage(0,5) - - if(3) - affected_mob.adjust_bodytemperature(20) - if(prob(5)) - affected_mob.emote("sneeze") - if(prob(5)) - affected_mob.emote("cough") - if(prob(5)) - to_chat(affected_mob, "You're burning in your own skin!") - affected_mob.take_bodypart_damage(0,5) - return +/datum/disease/fluspanish + name = "Spanish inquisition Flu" + max_stages = 3 + spread_text = "Airborne" + cure_text = "Spaceacillin & Anti-bodies to the common flu" + cures = list(/datum/reagent/medicine/spaceacillin) + cure_chance = 10 + agent = "1nqu1s1t10n flu virion" + viable_mobtypes = list(/mob/living/carbon/human) + permeability_mod = 0.75 + desc = "If left untreated the subject will burn to death for being a heretic." + severity = DISEASE_SEVERITY_DANGEROUS + +/datum/disease/fluspanish/stage_act() + ..() + switch(stage) + if(2) + affected_mob.adjust_bodytemperature(10) + if(prob(5)) + affected_mob.emote("sneeze") + if(prob(5)) + affected_mob.emote("cough") + if(prob(1)) + to_chat(affected_mob, "You're burning in your own skin!") + affected_mob.take_bodypart_damage(0,5) + + if(3) + affected_mob.adjust_bodytemperature(20) + if(prob(5)) + affected_mob.emote("sneeze") + if(prob(5)) + affected_mob.emote("cough") + if(prob(5)) + to_chat(affected_mob, "You're burning in your own skin!") + affected_mob.take_bodypart_damage(0,5) + return diff --git a/code/datums/diseases/gbs.dm b/code/datums/diseases/gbs.dm index 8a6eab6048ff..8ac199685570 100644 --- a/code/datums/diseases/gbs.dm +++ b/code/datums/diseases/gbs.dm @@ -1,31 +1,31 @@ -/datum/disease/gbs - name = "GBS" - max_stages = 4 - spread_text = "On contact" - spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_CONTACT_FLUIDS - cure_text = "Synaptizine & Sulfur" - cures = list(/datum/reagent/medicine/synaptizine,/datum/reagent/sulfur) - cure_chance = 15//higher chance to cure, since two reagents are required - agent = "Gravitokinetic Bipotential SADS+" - viable_mobtypes = list(/mob/living/carbon/human) - disease_flags = CAN_CARRY|CAN_RESIST|CURABLE - permeability_mod = 1 - severity = DISEASE_SEVERITY_BIOHAZARD - -/datum/disease/gbs/stage_act() - ..() - switch(stage) - if(2) - if(prob(5)) - affected_mob.emote("cough") - if(3) - if(prob(5)) - affected_mob.emote("gasp") - if(prob(10)) - to_chat(affected_mob, "Your body hurts all over!") - if(4) - to_chat(affected_mob, "Your body feels as if it's trying to rip itself apart!") - if(prob(50)) - affected_mob.gib() - else - return +/datum/disease/gbs + name = "GBS" + max_stages = 4 + spread_text = "On contact" + spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_CONTACT_FLUIDS + cure_text = "Synaptizine & Sulfur" + cures = list(/datum/reagent/medicine/synaptizine,/datum/reagent/sulfur) + cure_chance = 15//higher chance to cure, since two reagents are required + agent = "Gravitokinetic Bipotential SADS+" + viable_mobtypes = list(/mob/living/carbon/human) + disease_flags = CAN_CARRY|CAN_RESIST|CURABLE + permeability_mod = 1 + severity = DISEASE_SEVERITY_BIOHAZARD + +/datum/disease/gbs/stage_act() + ..() + switch(stage) + if(2) + if(prob(5)) + affected_mob.emote("cough") + if(3) + if(prob(5)) + affected_mob.emote("gasp") + if(prob(10)) + to_chat(affected_mob, "Your body hurts all over!") + if(4) + to_chat(affected_mob, "Your body feels as if it's trying to rip itself apart!") + if(prob(50)) + affected_mob.gib() + else + return diff --git a/code/datums/diseases/magnitis.dm b/code/datums/diseases/magnitis.dm index 3a2e73f9691f..a355a4bc019a 100644 --- a/code/datums/diseases/magnitis.dm +++ b/code/datums/diseases/magnitis.dm @@ -1,68 +1,68 @@ -/datum/disease/magnitis - name = "Magnitis" - max_stages = 4 - spread_text = "Airborne" - cure_text = "Iron" - cures = list(/datum/reagent/iron) - agent = "Fukkos Miracos" - viable_mobtypes = list(/mob/living/carbon/human) - disease_flags = CAN_CARRY|CAN_RESIST|CURABLE - permeability_mod = 0.75 - desc = "This disease disrupts the magnetic field of your body, making it act as if a powerful magnet. Injections of iron help stabilize the field." - severity = DISEASE_SEVERITY_MEDIUM - infectable_biotypes = MOB_ORGANIC|MOB_ROBOTIC - process_dead = TRUE - -/datum/disease/magnitis/stage_act() - ..() - switch(stage) - if(2) - if(prob(2)) - to_chat(affected_mob, "You feel a slight shock course through your body.") - if(prob(2)) - for(var/obj/M in orange(2,affected_mob)) - if(!M.anchored && (M.flags_1 & CONDUCT_1)) - step_towards(M,affected_mob) - for(var/mob/living/silicon/S in orange(2,affected_mob)) - if(isAI(S)) - continue - step_towards(S,affected_mob) - if(3) - if(prob(2)) - to_chat(affected_mob, "You feel a strong shock course through your body.") - if(prob(2)) - to_chat(affected_mob, "You feel like clowning around.") - if(prob(4)) - for(var/obj/M in orange(4,affected_mob)) - if(!M.anchored && (M.flags_1 & CONDUCT_1)) - var/i - var/iter = rand(1,2) - for(i=0,iYou feel a powerful shock course through your body.") - if(prob(2)) - to_chat(affected_mob, "You query upon the nature of miracles.") - if(prob(8)) - for(var/obj/M in orange(6,affected_mob)) - if(!M.anchored && (M.flags_1 & CONDUCT_1)) - var/i - var/iter = rand(1,3) - for(i=0,iYou feel a slight shock course through your body.") + if(prob(2)) + for(var/obj/M in orange(2,affected_mob)) + if(!M.anchored && (M.flags_1 & CONDUCT_1)) + step_towards(M,affected_mob) + for(var/mob/living/silicon/S in orange(2,affected_mob)) + if(isAI(S)) + continue + step_towards(S,affected_mob) + if(3) + if(prob(2)) + to_chat(affected_mob, "You feel a strong shock course through your body.") + if(prob(2)) + to_chat(affected_mob, "You feel like clowning around.") + if(prob(4)) + for(var/obj/M in orange(4,affected_mob)) + if(!M.anchored && (M.flags_1 & CONDUCT_1)) + var/i + var/iter = rand(1,2) + for(i=0,iYou feel a powerful shock course through your body.") + if(prob(2)) + to_chat(affected_mob, "You query upon the nature of miracles.") + if(prob(8)) + for(var/obj/M in orange(6,affected_mob)) + if(!M.anchored && (M.flags_1 & CONDUCT_1)) + var/i + var/iter = rand(1,3) + for(i=0,iYou feel a little silly.") - if(2) - if(prob(10)) - to_chat(affected_mob, "You start seeing rainbows.") - if(3) - if(prob(10)) - to_chat(affected_mob, "Your thoughts are interrupted by a loud HONK!") - if(4) - if(prob(5)) - affected_mob.say( pick( list("HONK!", "Honk!", "Honk.", "Honk?", "Honk!!", "Honk?!", "Honk...") ) , forced = "pierrot's throat") - -/datum/disease/pierrot_throat/after_add() - RegisterSignal(affected_mob, COMSIG_MOB_SAY, .proc/handle_speech) - - -/datum/disease/pierrot_throat/proc/handle_speech(datum/source, list/speech_args) - var/message = speech_args[SPEECH_MESSAGE] - var/list/split_message = splittext(message, " ") //List each word in the message - var/applied = 0 - for (var/i in 1 to length(split_message)) - if(prob(3 * stage)) //Stage 1: 3% Stage 2: 6% Stage 3: 9% Stage 4: 12% - if(findtext(split_message[i], "*") || findtext(split_message[i], ";") || findtext(split_message[i], ":")) - continue - split_message[i] = "HONK" - if (applied++ > stage) - break - if (applied) - speech_args[SPEECH_SPANS] |= SPAN_CLOWN // a little bonus - message = jointext(split_message, " ") - speech_args[SPEECH_MESSAGE] = message - - -/datum/disease/pierrot_throat/Destroy() - UnregisterSignal(affected_mob, COMSIG_MOB_SAY) - return ..() - -/datum/disease/pierrot_throat/remove_disease() - UnregisterSignal(affected_mob, COMSIG_MOB_SAY) - return ..() +/datum/disease/pierrot_throat + name = "Pierrot's Throat" + max_stages = 4 + spread_text = "Airborne" + cure_text = "Banana products, especially banana bread." + cures = list(/datum/reagent/consumable/banana) + cure_chance = 75 + agent = "H0NI<42 Virus" + viable_mobtypes = list(/mob/living/carbon/human) + permeability_mod = 0.75 + desc = "If left untreated the subject will probably drive others to insanity." + severity = DISEASE_SEVERITY_MEDIUM + +/datum/disease/pierrot_throat/stage_act() + ..() + switch(stage) + if(1) + if(prob(10)) + to_chat(affected_mob, "You feel a little silly.") + if(2) + if(prob(10)) + to_chat(affected_mob, "You start seeing rainbows.") + if(3) + if(prob(10)) + to_chat(affected_mob, "Your thoughts are interrupted by a loud HONK!") + if(4) + if(prob(5)) + affected_mob.say( pick( list("HONK!", "Honk!", "Honk.", "Honk?", "Honk!!", "Honk?!", "Honk...") ) , forced = "pierrot's throat") + +/datum/disease/pierrot_throat/after_add() + RegisterSignal(affected_mob, COMSIG_MOB_SAY, .proc/handle_speech) + + +/datum/disease/pierrot_throat/proc/handle_speech(datum/source, list/speech_args) + var/message = speech_args[SPEECH_MESSAGE] + var/list/split_message = splittext(message, " ") //List each word in the message + var/applied = 0 + for (var/i in 1 to length(split_message)) + if(prob(3 * stage)) //Stage 1: 3% Stage 2: 6% Stage 3: 9% Stage 4: 12% + if(findtext(split_message[i], "*") || findtext(split_message[i], ";") || findtext(split_message[i], ":")) + continue + split_message[i] = "HONK" + if (applied++ > stage) + break + if (applied) + speech_args[SPEECH_SPANS] |= SPAN_CLOWN // a little bonus + message = jointext(split_message, " ") + speech_args[SPEECH_MESSAGE] = message + + +/datum/disease/pierrot_throat/Destroy() + UnregisterSignal(affected_mob, COMSIG_MOB_SAY) + return ..() + +/datum/disease/pierrot_throat/remove_disease() + UnregisterSignal(affected_mob, COMSIG_MOB_SAY) + return ..() diff --git a/code/datums/diseases/retrovirus.dm b/code/datums/diseases/retrovirus.dm index 1541cdd5ab3e..311e0a29da6f 100644 --- a/code/datums/diseases/retrovirus.dm +++ b/code/datums/diseases/retrovirus.dm @@ -1,84 +1,84 @@ -/datum/disease/dna_retrovirus - name = "Retrovirus" - max_stages = 4 - spread_text = "Contact" - spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_CONTACT_FLUIDS - cure_text = "Rest or an injection of mutadone" - cure_chance = 6 - agent = "" - viable_mobtypes = list(/mob/living/carbon/human) - desc = "A DNA-altering retrovirus that scrambles the structural and unique enzymes of a host constantly." - severity = DISEASE_SEVERITY_HARMFUL - permeability_mod = 0.4 - stage_prob = 2 - var/restcure = 0 - -/datum/disease/dna_retrovirus/New() - ..() - agent = "Virus class [pick("A","B","C","D","E","F")][pick("A","B","C","D","E","F")]-[rand(50,300)]" - if(prob(40)) - cures = list(/datum/reagent/medicine/mutadone) - else - restcure = 1 - -/datum/disease/dna_retrovirus/Copy() - var/datum/disease/dna_retrovirus/D = ..() - D.restcure = restcure - return D - -/datum/disease/dna_retrovirus/stage_act() - ..() - switch(stage) - if(1) - if(restcure) - if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(30)) - to_chat(affected_mob, "You feel better.") - cure() - return - if (prob(8)) - to_chat(affected_mob, "Your head hurts.") - if (prob(9)) - to_chat(affected_mob, "You feel a tingling sensation in your chest.") - if (prob(9)) - to_chat(affected_mob, "You feel angry.") - if(2) - if(restcure) - if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(20)) - to_chat(affected_mob, "You feel better.") - cure() - return - if (prob(8)) - to_chat(affected_mob, "Your skin feels loose.") - if (prob(10)) - to_chat(affected_mob, "You feel very strange.") - if (prob(4)) - to_chat(affected_mob, "You feel a stabbing pain in your head!") - affected_mob.Unconscious(40) - if (prob(4)) - to_chat(affected_mob, "Your stomach churns.") - if(3) - if(restcure) - if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(20)) - to_chat(affected_mob, "You feel better.") - cure() - return - if (prob(10)) - to_chat(affected_mob, "Your entire body vibrates.") - - if (prob(35)) - if(prob(50)) - scramble_dna(affected_mob, 1, 0, rand(15,45)) - else - scramble_dna(affected_mob, 0, 1, rand(15,45)) - - if(4) - if(restcure) - if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(5)) - to_chat(affected_mob, "You feel better.") - cure() - return - if (prob(60)) - if(prob(50)) - scramble_dna(affected_mob, 1, 0, rand(50,75)) - else - scramble_dna(affected_mob, 0, 1, rand(50,75)) +/datum/disease/dna_retrovirus + name = "Retrovirus" + max_stages = 4 + spread_text = "Contact" + spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_CONTACT_FLUIDS + cure_text = "Rest or an injection of mutadone" + cure_chance = 6 + agent = "" + viable_mobtypes = list(/mob/living/carbon/human) + desc = "A DNA-altering retrovirus that scrambles the structural and unique enzymes of a host constantly." + severity = DISEASE_SEVERITY_HARMFUL + permeability_mod = 0.4 + stage_prob = 2 + var/restcure = 0 + +/datum/disease/dna_retrovirus/New() + ..() + agent = "Virus class [pick("A","B","C","D","E","F")][pick("A","B","C","D","E","F")]-[rand(50,300)]" + if(prob(40)) + cures = list(/datum/reagent/medicine/mutadone) + else + restcure = 1 + +/datum/disease/dna_retrovirus/Copy() + var/datum/disease/dna_retrovirus/D = ..() + D.restcure = restcure + return D + +/datum/disease/dna_retrovirus/stage_act() + ..() + switch(stage) + if(1) + if(restcure) + if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(30)) + to_chat(affected_mob, "You feel better.") + cure() + return + if (prob(8)) + to_chat(affected_mob, "Your head hurts.") + if (prob(9)) + to_chat(affected_mob, "You feel a tingling sensation in your chest.") + if (prob(9)) + to_chat(affected_mob, "You feel angry.") + if(2) + if(restcure) + if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(20)) + to_chat(affected_mob, "You feel better.") + cure() + return + if (prob(8)) + to_chat(affected_mob, "Your skin feels loose.") + if (prob(10)) + to_chat(affected_mob, "You feel very strange.") + if (prob(4)) + to_chat(affected_mob, "You feel a stabbing pain in your head!") + affected_mob.Unconscious(40) + if (prob(4)) + to_chat(affected_mob, "Your stomach churns.") + if(3) + if(restcure) + if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(20)) + to_chat(affected_mob, "You feel better.") + cure() + return + if (prob(10)) + to_chat(affected_mob, "Your entire body vibrates.") + + if (prob(35)) + if(prob(50)) + scramble_dna(affected_mob, 1, 0, rand(15,45)) + else + scramble_dna(affected_mob, 0, 1, rand(15,45)) + + if(4) + if(restcure) + if(!(affected_mob.mobility_flags & MOBILITY_STAND) && prob(5)) + to_chat(affected_mob, "You feel better.") + cure() + return + if (prob(60)) + if(prob(50)) + scramble_dna(affected_mob, 1, 0, rand(50,75)) + else + scramble_dna(affected_mob, 0, 1, rand(50,75)) diff --git a/code/datums/diseases/rhumba_beat.dm b/code/datums/diseases/rhumba_beat.dm index 52e9c2e19f77..de9b66f27e46 100644 --- a/code/datums/diseases/rhumba_beat.dm +++ b/code/datums/diseases/rhumba_beat.dm @@ -1,45 +1,45 @@ -/datum/disease/rhumba_beat - name = "The Rhumba Beat" - max_stages = 5 - spread_text = "On contact" - spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_CONTACT_FLUIDS - cure_text = "Chick Chicky Boom!" - cures = list("plasma") - agent = "Unknown" - viable_mobtypes = list(/mob/living/carbon/human) - permeability_mod = 1 - severity = DISEASE_SEVERITY_BIOHAZARD - -/datum/disease/rhumba_beat/stage_act() - ..() - if(affected_mob.ckey == "rosham") - cure() - return - switch(stage) - if(2) - if(prob(45)) - affected_mob.adjustFireLoss(5) - affected_mob.updatehealth() - if(prob(1)) - to_chat(affected_mob, "You feel strange...") - if(3) - if(prob(5)) - to_chat(affected_mob, "You feel the urge to dance...") - else if(prob(5)) - affected_mob.emote("gasp") - else if(prob(10)) - to_chat(affected_mob, "You feel the need to chick chicky boom...") - if(4) - if(prob(20)) - if (prob(50)) - affected_mob.adjust_fire_stacks(2) - affected_mob.IgniteMob() - else - affected_mob.emote("gasp") - to_chat(affected_mob, "You feel a burning beat inside...") - if(5) - to_chat(affected_mob, "Your body is unable to contain the Rhumba Beat...") - if(prob(50)) - explosion(get_turf(affected_mob), -1, 0, 2, 3, 0, 2) // This is equivalent to a lvl 1 fireball - else - return +/datum/disease/rhumba_beat + name = "The Rhumba Beat" + max_stages = 5 + spread_text = "On contact" + spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_CONTACT_FLUIDS + cure_text = "Chick Chicky Boom!" + cures = list("plasma") + agent = "Unknown" + viable_mobtypes = list(/mob/living/carbon/human) + permeability_mod = 1 + severity = DISEASE_SEVERITY_BIOHAZARD + +/datum/disease/rhumba_beat/stage_act() + ..() + if(affected_mob.ckey == "rosham") + cure() + return + switch(stage) + if(2) + if(prob(45)) + affected_mob.adjustFireLoss(5) + affected_mob.updatehealth() + if(prob(1)) + to_chat(affected_mob, "You feel strange...") + if(3) + if(prob(5)) + to_chat(affected_mob, "You feel the urge to dance...") + else if(prob(5)) + affected_mob.emote("gasp") + else if(prob(10)) + to_chat(affected_mob, "You feel the need to chick chicky boom...") + if(4) + if(prob(20)) + if (prob(50)) + affected_mob.adjust_fire_stacks(2) + affected_mob.IgniteMob() + else + affected_mob.emote("gasp") + to_chat(affected_mob, "You feel a burning beat inside...") + if(5) + to_chat(affected_mob, "Your body is unable to contain the Rhumba Beat...") + if(prob(50)) + explosion(get_turf(affected_mob), -1, 0, 2, 3, 0, 2) // This is equivalent to a lvl 1 fireball + else + return diff --git a/code/datums/diseases/transformation.dm b/code/datums/diseases/transformation.dm index 5e00bc510922..9827914c1dac 100644 --- a/code/datums/diseases/transformation.dm +++ b/code/datums/diseases/transformation.dm @@ -1,329 +1,329 @@ -/datum/disease/transformation - name = "Transformation" - max_stages = 5 - spread_text = "Acute" - spread_flags = DISEASE_SPREAD_SPECIAL - cure_text = "A coder's love (theoretical)." - agent = "Shenanigans" - viable_mobtypes = list(/mob/living/carbon/human, /mob/living/carbon/monkey, /mob/living/carbon/alien) - severity = DISEASE_SEVERITY_BIOHAZARD - stage_prob = 10 - visibility_flags = HIDDEN_SCANNER|HIDDEN_PANDEMIC - disease_flags = CURABLE - var/list/stage1 = list("You feel unremarkable.") - var/list/stage2 = list("You feel boring.") - var/list/stage3 = list("You feel utterly plain.") - var/list/stage4 = list("You feel white bread.") - var/list/stage5 = list("Oh the humanity!") - var/new_form = /mob/living/carbon/human - var/bantype - var/transformed_antag_datum //Do we add a specific antag datum once the transformation is complete? - -/datum/disease/transformation/Copy() - var/datum/disease/transformation/D = ..() - D.stage1 = stage1.Copy() - D.stage2 = stage2.Copy() - D.stage3 = stage3.Copy() - D.stage4 = stage4.Copy() - D.stage5 = stage5.Copy() - D.new_form = D.new_form - return D - -/datum/disease/transformation/stage_act() - ..() - switch(stage) - if(1) - if (prob(stage_prob) && stage1) - to_chat(affected_mob, pick(stage1)) - if(2) - if (prob(stage_prob) && stage2) - to_chat(affected_mob, pick(stage2)) - if(3) - if (prob(stage_prob*2) && stage3) - to_chat(affected_mob, pick(stage3)) - if(4) - if (prob(stage_prob*2) && stage4) - to_chat(affected_mob, pick(stage4)) - if(5) - do_disease_transformation(affected_mob) - -/datum/disease/transformation/proc/do_disease_transformation(mob/living/affected_mob) - if(istype(affected_mob, /mob/living/carbon) && affected_mob.stat != DEAD) - if(stage5) - to_chat(affected_mob, pick(stage5)) - if(QDELETED(affected_mob)) - return - if(affected_mob.notransform) - return - affected_mob.notransform = 1 - for(var/obj/item/W in affected_mob.get_equipped_items(TRUE)) - affected_mob.dropItemToGround(W) - for(var/obj/item/I in affected_mob.held_items) - affected_mob.dropItemToGround(I) - var/mob/living/new_mob = new new_form(affected_mob.loc) - if(istype(new_mob)) - if(bantype && is_banned_from(affected_mob.ckey, bantype)) - replace_banned_player(new_mob) - new_mob.a_intent = INTENT_HARM - if(affected_mob.mind) - affected_mob.mind.transfer_to(new_mob) - else - new_mob.key = affected_mob.key - if(transformed_antag_datum) - new_mob.mind.add_antag_datum(transformed_antag_datum) - new_mob.name = affected_mob.real_name - new_mob.real_name = new_mob.name - qdel(affected_mob) - -/datum/disease/transformation/proc/replace_banned_player(var/mob/living/new_mob) // This can run well after the mob has been transferred, so need a handle on the new mob to kill it if needed. - set waitfor = FALSE - - var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as [affected_mob.name]?", bantype, null, bantype, 50, affected_mob) - if(LAZYLEN(candidates)) - var/mob/dead/observer/C = pick(candidates) - to_chat(affected_mob, "Your mob has been taken over by a ghost! Appeal your job ban if you want to avoid this in the future!") - message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(affected_mob)]) to replace a jobbanned player.") - affected_mob.ghostize(0) - affected_mob.key = C.key - else - to_chat(new_mob, "Your mob has been claimed by death! Appeal your job ban if you want to avoid this in the future!") - new_mob.death() - if (!QDELETED(new_mob)) - new_mob.ghostize(can_reenter_corpse = FALSE) - new_mob.key = null - -/datum/disease/transformation/jungle_fever - name = "Jungle Fever" - cure_text = "Death." - cures = list(/datum/reagent/medicine/adminordrazine) - spread_text = "Monkey Bites" - spread_flags = DISEASE_SPREAD_SPECIAL - viable_mobtypes = list(/mob/living/carbon/monkey, /mob/living/carbon/human) - permeability_mod = 1 - cure_chance = 1 - disease_flags = CAN_CARRY|CAN_RESIST - desc = "Monkeys with this disease will bite humans, causing humans to mutate into a monkey." - severity = DISEASE_SEVERITY_BIOHAZARD - stage_prob = 4 - visibility_flags = 0 - agent = "Kongey Vibrion M-909" - new_form = /mob/living/carbon/monkey - bantype = ROLE_MONKEY - - - stage1 = list() - stage2 = list() - stage3 = list() - stage4 = list("Your back hurts.", "You breathe through your mouth.", - "You have a craving for bananas.", "Your mind feels clouded.") - stage5 = list("You feel like monkeying around.") - -/datum/disease/transformation/jungle_fever/do_disease_transformation(mob/living/carbon/affected_mob) - if(affected_mob.mind && !is_monkey(affected_mob.mind)) - add_monkey(affected_mob.mind) - if(ishuman(affected_mob)) - var/mob/living/carbon/monkey/M = affected_mob.monkeyize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_KEEPSE) - M.ventcrawler = VENTCRAWLER_ALWAYS - -/datum/disease/transformation/jungle_fever/stage_act() - ..() - switch(stage) - if(2) - if(prob(2)) - to_chat(affected_mob, "Your [pick("back", "arm", "leg", "elbow", "head")] itches.") - if(3) - if(prob(4)) - to_chat(affected_mob, "You feel a stabbing pain in your head.") - affected_mob.confused += 10 - if(4) - if(prob(3)) - affected_mob.say(pick("Eeek, ook ook!", "Eee-eeek!", "Eeee!", "Ungh, ungh."), forced = "jungle fever") - -/datum/disease/transformation/jungle_fever/cure() - remove_monkey(affected_mob.mind) - ..() - -/datum/disease/transformation/jungle_fever/monkeymode - visibility_flags = HIDDEN_SCANNER|HIDDEN_PANDEMIC - disease_flags = CAN_CARRY //no vaccines! no cure! - -/datum/disease/transformation/jungle_fever/monkeymode/after_add() - if(affected_mob && !is_monkey_leader(affected_mob.mind)) - visibility_flags = NONE - - - -/datum/disease/transformation/robot - - name = "Robotic Transformation" - cure_text = "An injection of copper." - cures = list(/datum/reagent/copper) - cure_chance = 5 - agent = "R2D2 Nanomachines" - desc = "This disease, actually acute nanomachine infection, converts the victim into a cyborg." - severity = DISEASE_SEVERITY_BIOHAZARD - visibility_flags = 0 - stage1 = list() - stage2 = list("Your joints feel stiff.", "Beep...boop..") - stage3 = list("Your joints feel very stiff.", "Your skin feels loose.", "You can feel something move...inside.") - stage4 = list("Your skin feels very loose.", "You can feel... something...inside you.") - stage5 = list("Your skin feels as if it's about to burst off!") - new_form = /mob/living/silicon/robot - infectable_biotypes = MOB_ORGANIC|MOB_UNDEAD|MOB_ROBOTIC - bantype = "Cyborg" - -/datum/disease/transformation/robot/stage_act() - ..() - switch(stage) - if(3) - if (prob(8)) - affected_mob.say(pick("Beep, boop", "beep, beep!", "Boop...bop"), forced = "robotic transformation") - if (prob(4)) - to_chat(affected_mob, "You feel a stabbing pain in your head.") - affected_mob.Unconscious(40) - if(4) - if (prob(20)) - affected_mob.say(pick("beep, beep!", "Boop bop boop beep.", "kkkiiiill mmme", "I wwwaaannntt tttoo dddiiieeee..."), forced = "robotic transformation") - - -/datum/disease/transformation/xeno - - name = "Xenomorph Transformation" - cure_text = "Spaceacillin & Glycerol" - cures = list(/datum/reagent/medicine/spaceacillin, /datum/reagent/glycerol) - cure_chance = 5 - agent = "Rip-LEY Alien Microbes" - desc = "This disease changes the victim into a xenomorph." - severity = DISEASE_SEVERITY_BIOHAZARD - visibility_flags = 0 - stage1 = list() - stage2 = list("Your throat feels scratchy.", "Kill...") - stage3 = list("Your throat feels very scratchy.", "Your skin feels tight.", "You can feel something move...inside.") - stage4 = list("Your skin feels very tight.", "Your blood boils!", "You can feel... something...inside you.") - stage5 = list("Your skin feels as if it's about to burst off!") - new_form = /mob/living/carbon/alien/humanoid/hunter - bantype = ROLE_ALIEN - -/datum/disease/transformation/xeno/stage_act() - ..() - switch(stage) - if(3) - if (prob(4)) - to_chat(affected_mob, "You feel a stabbing pain in your head.") - affected_mob.Unconscious(40) - if(4) - if (prob(20)) - affected_mob.say(pick("You look delicious.", "Going to... devour you...", "Hsssshhhhh!"), forced = "xenomorph transformation") - - -/datum/disease/transformation/slime - name = "Advanced Mutation Transformation" - cure_text = "frost oil" - cures = list(/datum/reagent/consumable/frostoil) - cure_chance = 80 - agent = "Advanced Mutation Toxin" - desc = "This highly concentrated extract converts anything into more of itself." - severity = DISEASE_SEVERITY_BIOHAZARD - visibility_flags = 0 - stage1 = list("You don't feel very well.") - stage2 = list("Your skin feels a little slimy.") - stage3 = list("Your appendages are melting away.", "Your limbs begin to lose their shape.") - stage4 = list("You are turning into a slime.") - stage5 = list("You have become a slime.") - new_form = /mob/living/simple_animal/slime/random - -/datum/disease/transformation/slime/stage_act() - ..() - switch(stage) - if(1) - if(ishuman(affected_mob) && affected_mob.dna) - if(affected_mob.dna.species.id == "slime" || affected_mob.dna.species.id == "stargazer" || affected_mob.dna.species.id == "lum") - stage = 5 - if(3) - if(ishuman(affected_mob)) - var/mob/living/carbon/human/human = affected_mob - if(human.dna.species.id != "slime" && affected_mob.dna.species.id != "stargazer" && affected_mob.dna.species.id != "lum") - human.set_species(/datum/species/jelly/slime) - -/datum/disease/transformation/corgi - name = "The Barkening" - cure_text = "Death" - cures = list(/datum/reagent/medicine/adminordrazine) - agent = "Fell Doge Majicks" - desc = "This disease transforms the victim into a corgi." - severity = DISEASE_SEVERITY_BIOHAZARD - visibility_flags = 0 - stage1 = list("BARK.") - stage2 = list("You feel the need to wear silly hats.") - stage3 = list("Must... eat... chocolate....", "YAP") - stage4 = list("Visions of washing machines assail your mind!") - stage5 = list("AUUUUUU!!!") - new_form = /mob/living/simple_animal/pet/dog/corgi - -/datum/disease/transformation/corgi/stage_act() - ..() - switch(stage) - if(3) - if (prob(8)) - affected_mob.say(pick("YAP", "Woof!"), forced = "corgi transformation") - if(4) - if (prob(20)) - affected_mob.say(pick("Bark!", "AUUUUUU"), forced = "corgi transformation") - -/datum/disease/transformation/morph - name = "Gluttony's Blessing" - cure_text = /datum/reagent/consumable/nothing - cures = list(/datum/reagent/medicine/adminordrazine) - agent = "Gluttony's Blessing" - desc = "A 'gift' from somewhere terrible." - stage_prob = 20 - severity = DISEASE_SEVERITY_BIOHAZARD - visibility_flags = 0 - stage1 = list("Your stomach rumbles.") - stage2 = list("Your skin feels saggy.") - stage3 = list("Your appendages are melting away.", "Your limbs begin to lose their shape.") - stage4 = list("You're ravenous.") - stage5 = list("You have become a morph.") - new_form = /mob/living/simple_animal/hostile/morph - infectable_biotypes = MOB_ORGANIC|MOB_MINERAL|MOB_UNDEAD //magic! - transformed_antag_datum = /datum/antagonist/morph - -/datum/disease/transformation/gondola - name = "Gondola Transformation" - cure_text = "Condensed Capsaicin, ingested or injected." //getting pepper sprayed doesn't help - cures = list(/datum/reagent/consumable/condensedcapsaicin) //beats the hippie crap right out of your system - cure_chance = 80 - stage_prob = 5 - agent = "Tranquility" - desc = "Consuming the flesh of a Gondola comes at a terrible price." - severity = DISEASE_SEVERITY_BIOHAZARD - visibility_flags = 0 - stage1 = list("You seem a little lighter in your step.") - stage2 = list("You catch yourself smiling for no reason.") - stage3 = list("A cruel sense of calm overcomes you.", "You can't feel your arms!", "You let go of the urge to hurt clowns.") - stage4 = list("You can't feel your arms. It does not bother you anymore.", "You forgive the clown for hurting you.") - stage5 = list("You have become a Gondola.") - new_form = /mob/living/simple_animal/pet/gondola - -/datum/disease/transformation/gondola/stage_act() - ..() - switch(stage) - if(2) - if (prob(5)) - affected_mob.emote("smile") - if (prob(20)) - affected_mob.reagents.add_reagent_list(list(/datum/reagent/pax = 5)) - if(3) - if (prob(5)) - affected_mob.emote("smile") - if (prob(20)) - affected_mob.reagents.add_reagent_list(list(/datum/reagent/pax = 5)) - if(4) - if (prob(5)) - affected_mob.emote("smile") - if (prob(20)) - affected_mob.reagents.add_reagent_list(list(/datum/reagent/pax = 5)) - if (prob(2)) - to_chat(affected_mob, "You let go of what you were holding.") - var/obj/item/I = affected_mob.get_active_held_item() - affected_mob.dropItemToGround(I) +/datum/disease/transformation + name = "Transformation" + max_stages = 5 + spread_text = "Acute" + spread_flags = DISEASE_SPREAD_SPECIAL + cure_text = "A coder's love (theoretical)." + agent = "Shenanigans" + viable_mobtypes = list(/mob/living/carbon/human, /mob/living/carbon/monkey, /mob/living/carbon/alien) + severity = DISEASE_SEVERITY_BIOHAZARD + stage_prob = 10 + visibility_flags = HIDDEN_SCANNER|HIDDEN_PANDEMIC + disease_flags = CURABLE + var/list/stage1 = list("You feel unremarkable.") + var/list/stage2 = list("You feel boring.") + var/list/stage3 = list("You feel utterly plain.") + var/list/stage4 = list("You feel white bread.") + var/list/stage5 = list("Oh the humanity!") + var/new_form = /mob/living/carbon/human + var/bantype + var/transformed_antag_datum //Do we add a specific antag datum once the transformation is complete? + +/datum/disease/transformation/Copy() + var/datum/disease/transformation/D = ..() + D.stage1 = stage1.Copy() + D.stage2 = stage2.Copy() + D.stage3 = stage3.Copy() + D.stage4 = stage4.Copy() + D.stage5 = stage5.Copy() + D.new_form = D.new_form + return D + +/datum/disease/transformation/stage_act() + ..() + switch(stage) + if(1) + if (prob(stage_prob) && stage1) + to_chat(affected_mob, pick(stage1)) + if(2) + if (prob(stage_prob) && stage2) + to_chat(affected_mob, pick(stage2)) + if(3) + if (prob(stage_prob*2) && stage3) + to_chat(affected_mob, pick(stage3)) + if(4) + if (prob(stage_prob*2) && stage4) + to_chat(affected_mob, pick(stage4)) + if(5) + do_disease_transformation(affected_mob) + +/datum/disease/transformation/proc/do_disease_transformation(mob/living/affected_mob) + if(istype(affected_mob, /mob/living/carbon) && affected_mob.stat != DEAD) + if(stage5) + to_chat(affected_mob, pick(stage5)) + if(QDELETED(affected_mob)) + return + if(affected_mob.notransform) + return + affected_mob.notransform = 1 + for(var/obj/item/W in affected_mob.get_equipped_items(TRUE)) + affected_mob.dropItemToGround(W) + for(var/obj/item/I in affected_mob.held_items) + affected_mob.dropItemToGround(I) + var/mob/living/new_mob = new new_form(affected_mob.loc) + if(istype(new_mob)) + if(bantype && is_banned_from(affected_mob.ckey, bantype)) + replace_banned_player(new_mob) + new_mob.a_intent = INTENT_HARM + if(affected_mob.mind) + affected_mob.mind.transfer_to(new_mob) + else + new_mob.key = affected_mob.key + if(transformed_antag_datum) + new_mob.mind.add_antag_datum(transformed_antag_datum) + new_mob.name = affected_mob.real_name + new_mob.real_name = new_mob.name + qdel(affected_mob) + +/datum/disease/transformation/proc/replace_banned_player(var/mob/living/new_mob) // This can run well after the mob has been transferred, so need a handle on the new mob to kill it if needed. + set waitfor = FALSE + + var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as [affected_mob.name]?", bantype, null, bantype, 50, affected_mob) + if(LAZYLEN(candidates)) + var/mob/dead/observer/C = pick(candidates) + to_chat(affected_mob, "Your mob has been taken over by a ghost! Appeal your job ban if you want to avoid this in the future!") + message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(affected_mob)]) to replace a jobbanned player.") + affected_mob.ghostize(0) + affected_mob.key = C.key + else + to_chat(new_mob, "Your mob has been claimed by death! Appeal your job ban if you want to avoid this in the future!") + new_mob.death() + if (!QDELETED(new_mob)) + new_mob.ghostize(can_reenter_corpse = FALSE) + new_mob.key = null + +/datum/disease/transformation/jungle_fever + name = "Jungle Fever" + cure_text = "Death." + cures = list(/datum/reagent/medicine/adminordrazine) + spread_text = "Monkey Bites" + spread_flags = DISEASE_SPREAD_SPECIAL + viable_mobtypes = list(/mob/living/carbon/monkey, /mob/living/carbon/human) + permeability_mod = 1 + cure_chance = 1 + disease_flags = CAN_CARRY|CAN_RESIST + desc = "Monkeys with this disease will bite humans, causing humans to mutate into a monkey." + severity = DISEASE_SEVERITY_BIOHAZARD + stage_prob = 4 + visibility_flags = 0 + agent = "Kongey Vibrion M-909" + new_form = /mob/living/carbon/monkey + bantype = ROLE_MONKEY + + + stage1 = list() + stage2 = list() + stage3 = list() + stage4 = list("Your back hurts.", "You breathe through your mouth.", + "You have a craving for bananas.", "Your mind feels clouded.") + stage5 = list("You feel like monkeying around.") + +/datum/disease/transformation/jungle_fever/do_disease_transformation(mob/living/carbon/affected_mob) + if(affected_mob.mind && !is_monkey(affected_mob.mind)) + add_monkey(affected_mob.mind) + if(ishuman(affected_mob)) + var/mob/living/carbon/monkey/M = affected_mob.monkeyize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_KEEPSE) + M.ventcrawler = VENTCRAWLER_ALWAYS + +/datum/disease/transformation/jungle_fever/stage_act() + ..() + switch(stage) + if(2) + if(prob(2)) + to_chat(affected_mob, "Your [pick("back", "arm", "leg", "elbow", "head")] itches.") + if(3) + if(prob(4)) + to_chat(affected_mob, "You feel a stabbing pain in your head.") + affected_mob.confused += 10 + if(4) + if(prob(3)) + affected_mob.say(pick("Eeek, ook ook!", "Eee-eeek!", "Eeee!", "Ungh, ungh."), forced = "jungle fever") + +/datum/disease/transformation/jungle_fever/cure() + remove_monkey(affected_mob.mind) + ..() + +/datum/disease/transformation/jungle_fever/monkeymode + visibility_flags = HIDDEN_SCANNER|HIDDEN_PANDEMIC + disease_flags = CAN_CARRY //no vaccines! no cure! + +/datum/disease/transformation/jungle_fever/monkeymode/after_add() + if(affected_mob && !is_monkey_leader(affected_mob.mind)) + visibility_flags = NONE + + + +/datum/disease/transformation/robot + + name = "Robotic Transformation" + cure_text = "An injection of copper." + cures = list(/datum/reagent/copper) + cure_chance = 5 + agent = "R2D2 Nanomachines" + desc = "This disease, actually acute nanomachine infection, converts the victim into a cyborg." + severity = DISEASE_SEVERITY_BIOHAZARD + visibility_flags = 0 + stage1 = list() + stage2 = list("Your joints feel stiff.", "Beep...boop..") + stage3 = list("Your joints feel very stiff.", "Your skin feels loose.", "You can feel something move...inside.") + stage4 = list("Your skin feels very loose.", "You can feel... something...inside you.") + stage5 = list("Your skin feels as if it's about to burst off!") + new_form = /mob/living/silicon/robot + infectable_biotypes = MOB_ORGANIC|MOB_UNDEAD|MOB_ROBOTIC + bantype = "Cyborg" + +/datum/disease/transformation/robot/stage_act() + ..() + switch(stage) + if(3) + if (prob(8)) + affected_mob.say(pick("Beep, boop", "beep, beep!", "Boop...bop"), forced = "robotic transformation") + if (prob(4)) + to_chat(affected_mob, "You feel a stabbing pain in your head.") + affected_mob.Unconscious(40) + if(4) + if (prob(20)) + affected_mob.say(pick("beep, beep!", "Boop bop boop beep.", "kkkiiiill mmme", "I wwwaaannntt tttoo dddiiieeee..."), forced = "robotic transformation") + + +/datum/disease/transformation/xeno + + name = "Xenomorph Transformation" + cure_text = "Spaceacillin & Glycerol" + cures = list(/datum/reagent/medicine/spaceacillin, /datum/reagent/glycerol) + cure_chance = 5 + agent = "Rip-LEY Alien Microbes" + desc = "This disease changes the victim into a xenomorph." + severity = DISEASE_SEVERITY_BIOHAZARD + visibility_flags = 0 + stage1 = list() + stage2 = list("Your throat feels scratchy.", "Kill...") + stage3 = list("Your throat feels very scratchy.", "Your skin feels tight.", "You can feel something move...inside.") + stage4 = list("Your skin feels very tight.", "Your blood boils!", "You can feel... something...inside you.") + stage5 = list("Your skin feels as if it's about to burst off!") + new_form = /mob/living/carbon/alien/humanoid/hunter + bantype = ROLE_ALIEN + +/datum/disease/transformation/xeno/stage_act() + ..() + switch(stage) + if(3) + if (prob(4)) + to_chat(affected_mob, "You feel a stabbing pain in your head.") + affected_mob.Unconscious(40) + if(4) + if (prob(20)) + affected_mob.say(pick("You look delicious.", "Going to... devour you...", "Hsssshhhhh!"), forced = "xenomorph transformation") + + +/datum/disease/transformation/slime + name = "Advanced Mutation Transformation" + cure_text = "frost oil" + cures = list(/datum/reagent/consumable/frostoil) + cure_chance = 80 + agent = "Advanced Mutation Toxin" + desc = "This highly concentrated extract converts anything into more of itself." + severity = DISEASE_SEVERITY_BIOHAZARD + visibility_flags = 0 + stage1 = list("You don't feel very well.") + stage2 = list("Your skin feels a little slimy.") + stage3 = list("Your appendages are melting away.", "Your limbs begin to lose their shape.") + stage4 = list("You are turning into a slime.") + stage5 = list("You have become a slime.") + new_form = /mob/living/simple_animal/slime/random + +/datum/disease/transformation/slime/stage_act() + ..() + switch(stage) + if(1) + if(ishuman(affected_mob) && affected_mob.dna) + if(affected_mob.dna.species.id == "slime" || affected_mob.dna.species.id == "stargazer" || affected_mob.dna.species.id == "lum") + stage = 5 + if(3) + if(ishuman(affected_mob)) + var/mob/living/carbon/human/human = affected_mob + if(human.dna.species.id != "slime" && affected_mob.dna.species.id != "stargazer" && affected_mob.dna.species.id != "lum") + human.set_species(/datum/species/jelly/slime) + +/datum/disease/transformation/corgi + name = "The Barkening" + cure_text = "Death" + cures = list(/datum/reagent/medicine/adminordrazine) + agent = "Fell Doge Majicks" + desc = "This disease transforms the victim into a corgi." + severity = DISEASE_SEVERITY_BIOHAZARD + visibility_flags = 0 + stage1 = list("BARK.") + stage2 = list("You feel the need to wear silly hats.") + stage3 = list("Must... eat... chocolate....", "YAP") + stage4 = list("Visions of washing machines assail your mind!") + stage5 = list("AUUUUUU!!!") + new_form = /mob/living/simple_animal/pet/dog/corgi + +/datum/disease/transformation/corgi/stage_act() + ..() + switch(stage) + if(3) + if (prob(8)) + affected_mob.say(pick("YAP", "Woof!"), forced = "corgi transformation") + if(4) + if (prob(20)) + affected_mob.say(pick("Bark!", "AUUUUUU"), forced = "corgi transformation") + +/datum/disease/transformation/morph + name = "Gluttony's Blessing" + cure_text = /datum/reagent/consumable/nothing + cures = list(/datum/reagent/medicine/adminordrazine) + agent = "Gluttony's Blessing" + desc = "A 'gift' from somewhere terrible." + stage_prob = 20 + severity = DISEASE_SEVERITY_BIOHAZARD + visibility_flags = 0 + stage1 = list("Your stomach rumbles.") + stage2 = list("Your skin feels saggy.") + stage3 = list("Your appendages are melting away.", "Your limbs begin to lose their shape.") + stage4 = list("You're ravenous.") + stage5 = list("You have become a morph.") + new_form = /mob/living/simple_animal/hostile/morph + infectable_biotypes = MOB_ORGANIC|MOB_MINERAL|MOB_UNDEAD //magic! + transformed_antag_datum = /datum/antagonist/morph + +/datum/disease/transformation/gondola + name = "Gondola Transformation" + cure_text = "Condensed Capsaicin, ingested or injected." //getting pepper sprayed doesn't help + cures = list(/datum/reagent/consumable/condensedcapsaicin) //beats the hippie crap right out of your system + cure_chance = 80 + stage_prob = 5 + agent = "Tranquility" + desc = "Consuming the flesh of a Gondola comes at a terrible price." + severity = DISEASE_SEVERITY_BIOHAZARD + visibility_flags = 0 + stage1 = list("You seem a little lighter in your step.") + stage2 = list("You catch yourself smiling for no reason.") + stage3 = list("A cruel sense of calm overcomes you.", "You can't feel your arms!", "You let go of the urge to hurt clowns.") + stage4 = list("You can't feel your arms. It does not bother you anymore.", "You forgive the clown for hurting you.") + stage5 = list("You have become a Gondola.") + new_form = /mob/living/simple_animal/pet/gondola + +/datum/disease/transformation/gondola/stage_act() + ..() + switch(stage) + if(2) + if (prob(5)) + affected_mob.emote("smile") + if (prob(20)) + affected_mob.reagents.add_reagent_list(list(/datum/reagent/pax = 5)) + if(3) + if (prob(5)) + affected_mob.emote("smile") + if (prob(20)) + affected_mob.reagents.add_reagent_list(list(/datum/reagent/pax = 5)) + if(4) + if (prob(5)) + affected_mob.emote("smile") + if (prob(20)) + affected_mob.reagents.add_reagent_list(list(/datum/reagent/pax = 5)) + if (prob(2)) + to_chat(affected_mob, "You let go of what you were holding.") + var/obj/item/I = affected_mob.get_active_held_item() + affected_mob.dropItemToGround(I) diff --git a/code/datums/diseases/wizarditis.dm b/code/datums/diseases/wizarditis.dm index b7a74615cb7f..758775750c18 100644 --- a/code/datums/diseases/wizarditis.dm +++ b/code/datums/diseases/wizarditis.dm @@ -1,117 +1,117 @@ -/datum/disease/wizarditis - name = "Wizarditis" - max_stages = 4 - spread_text = "Airborne" - cure_text = "The Manly Dorf" - cures = list(/datum/reagent/consumable/ethanol/manly_dorf) - cure_chance = 100 - agent = "Rincewindus Vulgaris" - viable_mobtypes = list(/mob/living/carbon/human) - disease_flags = CAN_CARRY|CAN_RESIST|CURABLE - permeability_mod = 0.75 - desc = "Some speculate that this virus is the cause of the Space Wizard Federation's existence. Subjects affected show the signs of brain damage, yelling obscure sentences or total gibberish. On late stages subjects sometime express the feelings of inner power, and, cite, 'the ability to control the forces of cosmos themselves!' A gulp of strong, manly spirits usually reverts them to normal, humanlike, condition." - severity = DISEASE_SEVERITY_HARMFUL - required_organs = list(/obj/item/bodypart/head) - -/* -BIRUZ BENNAR -SCYAR NILA - teleport -NEC CANTIO - dis techno -EI NATH - shocking grasp -AULIE OXIN FIERA - knock -TARCOL MINTI ZHERI - forcewall -STI KALY - blind -*/ - -/datum/disease/wizarditis/stage_act() - ..() - - switch(stage) - if(2) - if(prob(1)&&prob(50)) - affected_mob.say(pick("You shall not pass!", "Expeliarmus!", "By Merlins beard!", "Feel the power of the Dark Side!"), forced = "wizarditis") - if(prob(1)&&prob(50)) - to_chat(affected_mob, "You feel [pick("that you don't have enough mana", "that the winds of magic are gone", "an urge to summon familiar")].") - - - if(3) - if(prob(1)&&prob(50)) - affected_mob.say(pick("NEC CANTIO!","AULIE OXIN FIERA!", "STI KALY!", "TARCOL MINTI ZHERI!"), forced = "wizarditis") - if(prob(1)&&prob(50)) - to_chat(affected_mob, "You feel [pick("the magic bubbling in your veins","that this location gives you a +1 to INT","an urge to summon familiar")].") - - if(4) - - if(prob(1)) - affected_mob.say(pick("NEC CANTIO!","AULIE OXIN FIERA!","STI KALY!","EI NATH!"), forced = "wizarditis") - return - if(prob(1)&&prob(50)) - to_chat(affected_mob, "You feel [pick("the tidal wave of raw power building inside","that this location gives you a +2 to INT and +1 to WIS","an urge to teleport")].") - spawn_wizard_clothes(50) - if(prob(1)&&prob(1)) - teleport() - return - - - -/datum/disease/wizarditis/proc/spawn_wizard_clothes(chance = 0) - if(ishuman(affected_mob)) - var/mob/living/carbon/human/H = affected_mob - if(prob(chance)) - if(!istype(H.head, /obj/item/clothing/head/wizard)) - if(!H.dropItemToGround(H.head)) - qdel(H.head) - H.equip_to_slot_or_del(new /obj/item/clothing/head/wizard(H), ITEM_SLOT_HEAD) - return - if(prob(chance)) - if(!istype(H.wear_suit, /obj/item/clothing/suit/wizrobe)) - if(!H.dropItemToGround(H.wear_suit)) - qdel(H.wear_suit) - H.equip_to_slot_or_del(new /obj/item/clothing/suit/wizrobe(H), ITEM_SLOT_OCLOTHING) - return - if(prob(chance)) - if(!istype(H.shoes, /obj/item/clothing/shoes/sandal/magic)) - if(!H.dropItemToGround(H.shoes)) - qdel(H.shoes) - H.equip_to_slot_or_del(new /obj/item/clothing/shoes/sandal/magic(H), ITEM_SLOT_FEET) - return - else - var/mob/living/carbon/H = affected_mob - if(prob(chance)) - var/obj/item/staff/S = new(H) - if(!H.put_in_hands(S)) - qdel(S) - - -/datum/disease/wizarditis/proc/teleport() - var/list/theareas = get_areas_in_range(80, affected_mob) - for(var/area/space/S in theareas) - theareas -= S - - if(!theareas||!theareas.len) - return - - var/area/thearea = pick(theareas) - - var/list/L = list() - for(var/turf/T in get_area_turfs(thearea.type)) - if(T.z != affected_mob.z) - continue - if(T.name == "space") - continue - if(!T.density) - var/clear = 1 - for(var/obj/O in T) - if(O.density) - clear = 0 - break - if(clear) - L+=T - - if(!L) - return - - affected_mob.say("SCYAR NILA [uppertext(thearea.name)]!", forced = "wizarditis teleport") - affected_mob.forceMove(pick(L)) - - return +/datum/disease/wizarditis + name = "Wizarditis" + max_stages = 4 + spread_text = "Airborne" + cure_text = "The Manly Dorf" + cures = list(/datum/reagent/consumable/ethanol/manly_dorf) + cure_chance = 100 + agent = "Rincewindus Vulgaris" + viable_mobtypes = list(/mob/living/carbon/human) + disease_flags = CAN_CARRY|CAN_RESIST|CURABLE + permeability_mod = 0.75 + desc = "Some speculate that this virus is the cause of the Space Wizard Federation's existence. Subjects affected show the signs of brain damage, yelling obscure sentences or total gibberish. On late stages subjects sometime express the feelings of inner power, and, cite, 'the ability to control the forces of cosmos themselves!' A gulp of strong, manly spirits usually reverts them to normal, humanlike, condition." + severity = DISEASE_SEVERITY_HARMFUL + required_organs = list(/obj/item/bodypart/head) + +/* +BIRUZ BENNAR +SCYAR NILA - teleport +NEC CANTIO - dis techno +EI NATH - shocking grasp +AULIE OXIN FIERA - knock +TARCOL MINTI ZHERI - forcewall +STI KALY - blind +*/ + +/datum/disease/wizarditis/stage_act() + ..() + + switch(stage) + if(2) + if(prob(1)&&prob(50)) + affected_mob.say(pick("You shall not pass!", "Expeliarmus!", "By Merlins beard!", "Feel the power of the Dark Side!"), forced = "wizarditis") + if(prob(1)&&prob(50)) + to_chat(affected_mob, "You feel [pick("that you don't have enough mana", "that the winds of magic are gone", "an urge to summon familiar")].") + + + if(3) + if(prob(1)&&prob(50)) + affected_mob.say(pick("NEC CANTIO!","AULIE OXIN FIERA!", "STI KALY!", "TARCOL MINTI ZHERI!"), forced = "wizarditis") + if(prob(1)&&prob(50)) + to_chat(affected_mob, "You feel [pick("the magic bubbling in your veins","that this location gives you a +1 to INT","an urge to summon familiar")].") + + if(4) + + if(prob(1)) + affected_mob.say(pick("NEC CANTIO!","AULIE OXIN FIERA!","STI KALY!","EI NATH!"), forced = "wizarditis") + return + if(prob(1)&&prob(50)) + to_chat(affected_mob, "You feel [pick("the tidal wave of raw power building inside","that this location gives you a +2 to INT and +1 to WIS","an urge to teleport")].") + spawn_wizard_clothes(50) + if(prob(1)&&prob(1)) + teleport() + return + + + +/datum/disease/wizarditis/proc/spawn_wizard_clothes(chance = 0) + if(ishuman(affected_mob)) + var/mob/living/carbon/human/H = affected_mob + if(prob(chance)) + if(!istype(H.head, /obj/item/clothing/head/wizard)) + if(!H.dropItemToGround(H.head)) + qdel(H.head) + H.equip_to_slot_or_del(new /obj/item/clothing/head/wizard(H), ITEM_SLOT_HEAD) + return + if(prob(chance)) + if(!istype(H.wear_suit, /obj/item/clothing/suit/wizrobe)) + if(!H.dropItemToGround(H.wear_suit)) + qdel(H.wear_suit) + H.equip_to_slot_or_del(new /obj/item/clothing/suit/wizrobe(H), ITEM_SLOT_OCLOTHING) + return + if(prob(chance)) + if(!istype(H.shoes, /obj/item/clothing/shoes/sandal/magic)) + if(!H.dropItemToGround(H.shoes)) + qdel(H.shoes) + H.equip_to_slot_or_del(new /obj/item/clothing/shoes/sandal/magic(H), ITEM_SLOT_FEET) + return + else + var/mob/living/carbon/H = affected_mob + if(prob(chance)) + var/obj/item/staff/S = new(H) + if(!H.put_in_hands(S)) + qdel(S) + + +/datum/disease/wizarditis/proc/teleport() + var/list/theareas = get_areas_in_range(80, affected_mob) + for(var/area/space/S in theareas) + theareas -= S + + if(!theareas||!theareas.len) + return + + var/area/thearea = pick(theareas) + + var/list/L = list() + for(var/turf/T in get_area_turfs(thearea.type)) + if(T.z != affected_mob.z) + continue + if(T.name == "space") + continue + if(!T.density) + var/clear = 1 + for(var/obj/O in T) + if(O.density) + clear = 0 + break + if(clear) + L+=T + + if(!L) + return + + affected_mob.say("SCYAR NILA [uppertext(thearea.name)]!", forced = "wizarditis teleport") + affected_mob.forceMove(pick(L)) + + return diff --git a/code/datums/dna.dm b/code/datums/dna.dm index 51cffae318d1..6f091d8d2af3 100644 --- a/code/datums/dna.dm +++ b/code/datums/dna.dm @@ -1,678 +1,678 @@ - -/////////////////////////// DNA DATUM -/datum/dna - var/unique_enzymes - var/uni_identity - var/blood_type - var/datum/species/species = new /datum/species/human //The type of mutant race the player is if applicable (i.e. potato-man) - var/list/features = list("FFF") //first value is mutant color - var/real_name //Stores the real name of the person who originally got this dna datum. Used primarely for changelings, - var/list/mutations = list() //All mutations are from now on here - var/list/temporary_mutations = list() //Temporary changes to the UE - var/list/previous = list() //For temporary name/ui/ue/blood_type modifications - var/mob/living/holder - var/mutation_index[DNA_MUTATION_BLOCKS] //List of which mutations this carbon has and its assigned block - var/default_mutation_genes[DNA_MUTATION_BLOCKS] //List of the default genes from this mutation to allow DNA Scanner highlighting - var/stability = 100 - var/scrambled = FALSE //Did we take something like mutagen? In that case we cant get our genes scanned to instantly cheese all the powers. - - - var/delete_species = TRUE //Set to FALSE when a body is scanned by a cloner to fix #38875. WaspStation Edit - Cloning - -/datum/dna/New(mob/living/new_holder) - if(istype(new_holder)) - holder = new_holder - -/datum/dna/Destroy() - if(iscarbon(holder)) - var/mob/living/carbon/cholder = holder - if(cholder.dna == src) - cholder.dna = null - holder = null - - //WaspStation Begin - Cloning - if(delete_species) - QDEL_NULL(species) - //WaspStation End - - mutations.Cut() //This only references mutations, just dereference. - temporary_mutations.Cut() //^ - previous.Cut() //^ - - return ..() - -/datum/dna/proc/transfer_identity(mob/living/carbon/destination, transfer_SE = 0) - if(!istype(destination)) - return - destination.dna.unique_enzymes = unique_enzymes - destination.dna.uni_identity = uni_identity - destination.dna.blood_type = blood_type - destination.set_species(species.type, icon_update=0) - destination.dna.features = features.Copy() - destination.dna.real_name = real_name - destination.dna.temporary_mutations = temporary_mutations.Copy() - destination.flavor_text = destination.dna.features["flavor_text"] //Update the flavor_text to use new dna text - if(transfer_SE) - destination.dna.mutation_index = mutation_index - destination.dna.default_mutation_genes = default_mutation_genes - -/datum/dna/proc/copy_dna(datum/dna/new_dna) - new_dna.unique_enzymes = unique_enzymes - new_dna.mutation_index = mutation_index - new_dna.default_mutation_genes = default_mutation_genes - new_dna.uni_identity = uni_identity - new_dna.blood_type = blood_type - new_dna.features = features.Copy() - new_dna.species = new species.type - new_dna.real_name = real_name - new_dna.mutations = mutations.Copy() - -//See mutation.dm for what 'class' does. 'time' is time till it removes itself in decimals. 0 for no timer -/datum/dna/proc/add_mutation(mutation, class = MUT_OTHER, time) - var/mutation_type = mutation - if(istype(mutation, /datum/mutation/human)) - var/datum/mutation/human/HM = mutation - mutation_type = HM.type - if(get_mutation(mutation_type)) - return - return force_give(new mutation_type (class, time, copymut = mutation)) - -/datum/dna/proc/remove_mutation(mutation_type) - return force_lose(get_mutation(mutation_type)) - -/datum/dna/proc/check_mutation(mutation_type) - return get_mutation(mutation_type) - -/datum/dna/proc/remove_all_mutations(list/classes = list(MUT_NORMAL, MUT_EXTRA, MUT_OTHER), mutadone = FALSE) - remove_mutation_group(mutations, classes, mutadone) - scrambled = FALSE - -/datum/dna/proc/remove_mutation_group(list/group, list/classes = list(MUT_NORMAL, MUT_EXTRA, MUT_OTHER), mutadone = FALSE) - if(!group) - return - for(var/datum/mutation/human/HM in group) - if((HM.class in classes) && !(HM.mutadone_proof && mutadone)) - force_lose(HM) - -/datum/dna/proc/generate_uni_identity() - . = "" - var/list/L = new /list(DNA_UNI_IDENTITY_BLOCKS) - - switch(holder.gender) - if(MALE) - L[DNA_GENDER_BLOCK] = construct_block(G_MALE, 3) - if(FEMALE) - L[DNA_GENDER_BLOCK] = construct_block(G_FEMALE, 3) - else - L[DNA_GENDER_BLOCK] = construct_block(G_PLURAL, 3) - if(ishuman(holder)) - var/mob/living/carbon/human/H = holder - if(!GLOB.hairstyles_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/hair,GLOB.hairstyles_list, GLOB.hairstyles_male_list, GLOB.hairstyles_female_list) - L[DNA_HAIRSTYLE_BLOCK] = construct_block(GLOB.hairstyles_list.Find(H.hairstyle), GLOB.hairstyles_list.len) - L[DNA_HAIR_COLOR_BLOCK] = sanitize_hexcolor(H.hair_color) - if(!GLOB.facial_hairstyles_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/facial_hair, GLOB.facial_hairstyles_list, GLOB.facial_hairstyles_male_list, GLOB.facial_hairstyles_female_list) - L[DNA_FACIAL_HAIRSTYLE_BLOCK] = construct_block(GLOB.facial_hairstyles_list.Find(H.facial_hairstyle), GLOB.facial_hairstyles_list.len) - L[DNA_FACIAL_HAIR_COLOR_BLOCK] = sanitize_hexcolor(H.facial_hair_color) - L[DNA_SKIN_TONE_BLOCK] = construct_block(GLOB.skin_tones.Find(H.skin_tone), GLOB.skin_tones.len) - L[DNA_EYE_COLOR_BLOCK] = sanitize_hexcolor(H.eye_color) - - for(var/i=1, i<=DNA_UNI_IDENTITY_BLOCKS, i++) - if(L[i]) - . += L[i] - else - . += random_string(DNA_BLOCK_SIZE,GLOB.hex_characters) - return . - -/datum/dna/proc/generate_dna_blocks() - var/bonus - if(species && species.inert_mutation) - bonus = GET_INITIALIZED_MUTATION(species.inert_mutation) - var/list/mutations_temp = GLOB.good_mutations + GLOB.bad_mutations + GLOB.not_good_mutations + bonus - if(!LAZYLEN(mutations_temp)) - return - mutation_index.Cut() - default_mutation_genes.Cut() - shuffle_inplace(mutations_temp) - if(ismonkey(holder)) - mutations |= new RACEMUT(MUT_NORMAL) - mutation_index[RACEMUT] = GET_SEQUENCE(RACEMUT) - else - mutation_index[RACEMUT] = create_sequence(RACEMUT, FALSE) - default_mutation_genes[RACEMUT] = mutation_index[RACEMUT] - for(var/i in 2 to DNA_MUTATION_BLOCKS) - var/datum/mutation/human/M = mutations_temp[i] - mutation_index[M.type] = create_sequence(M.type, FALSE, M.difficulty) - default_mutation_genes[M.type] = mutation_index[M.type] - shuffle_inplace(mutation_index) - -//Used to generate original gene sequences for every mutation -/proc/generate_gene_sequence(length=4) - var/static/list/active_sequences = list("AT","TA","GC","CG") - var/sequence - for(var/i in 1 to length*DNA_SEQUENCE_LENGTH) - sequence += pick(active_sequences) - return sequence - -//Used to create a chipped gene sequence -/proc/create_sequence(mutation, active, difficulty) - if(!difficulty) - var/datum/mutation/human/A = GET_INITIALIZED_MUTATION(mutation) //leaves the possibility to change difficulty mid-round - if(!A) - return - difficulty = A.difficulty - difficulty += rand(-2,4) - var/sequence = GET_SEQUENCE(mutation) - if(active) - return sequence - while(difficulty) - var/randnum = rand(1, length_char(sequence)) - sequence = copytext_char(sequence, 1, randnum) + "X" + copytext_char(sequence, randnum + 1) - difficulty-- - return sequence - -/datum/dna/proc/generate_unique_enzymes() - . = "" - if(istype(holder)) - real_name = holder.real_name - . += md5(holder.real_name) - else - . += random_string(DNA_UNIQUE_ENZYMES_LEN, GLOB.hex_characters) - return . - -/datum/dna/proc/update_ui_block(blocknumber) - if(!blocknumber || !ishuman(holder)) - return - var/mob/living/carbon/human/H = holder - switch(blocknumber) - if(DNA_HAIR_COLOR_BLOCK) - setblock(uni_identity, blocknumber, sanitize_hexcolor(H.hair_color)) - if(DNA_FACIAL_HAIR_COLOR_BLOCK) - setblock(uni_identity, blocknumber, sanitize_hexcolor(H.facial_hair_color)) - if(DNA_SKIN_TONE_BLOCK) - setblock(uni_identity, blocknumber, construct_block(GLOB.skin_tones.Find(H.skin_tone), GLOB.skin_tones.len)) - if(DNA_EYE_COLOR_BLOCK) - setblock(uni_identity, blocknumber, sanitize_hexcolor(H.eye_color)) - if(DNA_GENDER_BLOCK) - switch(H.gender) - if(MALE) - setblock(uni_identity, blocknumber, construct_block(G_MALE, 3)) - if(FEMALE) - setblock(uni_identity, blocknumber, construct_block(G_FEMALE, 3)) - else - setblock(uni_identity, blocknumber, construct_block(G_PLURAL, 3)) - if(DNA_FACIAL_HAIRSTYLE_BLOCK) - setblock(uni_identity, blocknumber, construct_block(GLOB.facial_hairstyles_list.Find(H.facial_hairstyle), GLOB.facial_hairstyles_list.len)) - if(DNA_HAIRSTYLE_BLOCK) - setblock(uni_identity, blocknumber, construct_block(GLOB.hairstyles_list.Find(H.hairstyle), GLOB.hairstyles_list.len)) - -//Please use add_mutation or activate_mutation instead -/datum/dna/proc/force_give(datum/mutation/human/HM) - if(holder && HM) - if(HM.class == MUT_NORMAL) - set_se(1, HM) - . = HM.on_acquiring(holder) - if(.) - qdel(HM) - update_instability() - -//Use remove_mutation instead -/datum/dna/proc/force_lose(datum/mutation/human/HM) - if(holder && (HM in mutations)) - set_se(0, HM) - . = HM.on_losing(holder) - update_instability(FALSE) - return - -/datum/dna/proc/is_same_as(datum/dna/D) - if(uni_identity == D.uni_identity && mutation_index == D.mutation_index && real_name == D.real_name) - if(species.type == D.species.type && features == D.features && blood_type == D.blood_type) - return 1 - return 0 - -/datum/dna/proc/update_instability(alert=TRUE) - stability = 100 - for(var/datum/mutation/human/M in mutations) - if(M.class == MUT_EXTRA) - stability -= M.instability * GET_MUTATION_STABILIZER(M) - if(holder) - var/message - if(alert) - switch(stability) - if(70 to 90) - message = "You shiver." - if(60 to 69) - message = "You feel cold." - if(40 to 59) - message = "You feel sick." - if(20 to 39) - message = "It feels like your skin is moving." - if(1 to 19) - message = "You can feel your cells burning." - if(-INFINITY to 0) - message = "You can feel your DNA exploding, we need to do something fast!" - if(stability <= 0) - holder.apply_status_effect(STATUS_EFFECT_DNA_MELT) - if(message) - to_chat(holder, message) - -//used to update dna UI, UE, and dna.real_name. -/datum/dna/proc/update_dna_identity() - uni_identity = generate_uni_identity() - unique_enzymes = generate_unique_enzymes() - -/datum/dna/proc/initialize_dna(newblood_type, skip_index = FALSE) - if(newblood_type) - blood_type = newblood_type - unique_enzymes = generate_unique_enzymes() - uni_identity = generate_uni_identity() - if(!skip_index) //I hate this - generate_dna_blocks() - features = random_features() - - -/datum/dna/stored //subtype used by brain mob's stored_dna - -/datum/dna/stored/add_mutation(mutation_name) //no mutation changes on stored dna. - return - -/datum/dna/stored/remove_mutation(mutation_name) - return - -/datum/dna/stored/check_mutation(mutation_name) - return - -/datum/dna/stored/remove_all_mutations(list/classes, mutadone = FALSE) - return - -/datum/dna/stored/remove_mutation_group(list/group) - return - -/////////////////////////// DNA MOB-PROCS ////////////////////// - -/mob/proc/set_species(datum/species/mrace, icon_update = 1) - return - -/mob/living/brain/set_species(datum/species/mrace, icon_update = 1) - if(mrace) - if(ispath(mrace)) - stored_dna.species = new mrace() - else - stored_dna.species = mrace //not calling any species update procs since we're a brain, not a monkey/human - - -/mob/living/carbon/set_species(datum/species/mrace, icon_update = TRUE, pref_load = FALSE) - if(mrace && has_dna()) - var/datum/species/new_race - if(ispath(mrace)) - new_race = new mrace - else if(istype(mrace)) - new_race = mrace - else - return - deathsound = new_race.deathsound - dna.species.on_species_loss(src, new_race, pref_load) - var/datum/species/old_species = dna.species - dna.species = new_race - dna.species.on_species_gain(src, old_species, pref_load) - if(ishuman(src)) - qdel(language_holder) - var/species_holder = initial(mrace.species_language_holder) - language_holder = new species_holder(src) - update_atom_languages() - -/mob/living/carbon/human/set_species(datum/species/mrace, icon_update = TRUE, pref_load = FALSE) - ..() - if(icon_update) - update_body() - update_hair() - update_body_parts() - update_mutations_overlay()// no lizard with human hulk overlay please. - - -/mob/proc/has_dna() - return - -/mob/living/carbon/has_dna() - return dna - - -/mob/living/carbon/human/proc/hardset_dna(ui, list/mutation_index, list/default_mutation_genes, newreal_name, newblood_type, datum/species/mrace, newfeatures, list/mutations, force_transfer_mutations) -//Do not use force_transfer_mutations for stuff like cloners without some precautions, otherwise some conditional mutations could break (timers, drill hat etc) - if(newfeatures) - dna.features = newfeatures - flavor_text = dna.features["flavor_text"] //Update the flavor_text to use new dna text - - if(mrace) - var/datum/species/newrace = new mrace.type - newrace.copy_properties_from(mrace) - set_species(newrace, icon_update=0) - - if(newreal_name) - dna.real_name = newreal_name - dna.generate_unique_enzymes() - - if(newblood_type) - dna.blood_type = newblood_type - - if(ui) - dna.uni_identity = ui - updateappearance(icon_update=0) - - if(LAZYLEN(mutation_index)) - dna.mutation_index = mutation_index.Copy() - if(LAZYLEN(default_mutation_genes)) - dna.default_mutation_genes = default_mutation_genes.Copy() - else - dna.default_mutation_genes = mutation_index.Copy() - domutcheck() - - if(mrace || newfeatures || ui) - update_body() - update_hair() - update_body_parts() - update_mutations_overlay() - - if(LAZYLEN(mutations)) - for(var/M in mutations) - var/datum/mutation/human/HM = M - if(HM.allow_transfer || force_transfer_mutations) - dna.force_give(new HM.type(HM.class, copymut=HM)) //using force_give since it may include exotic mutations that otherwise wont be handled properly - -/mob/living/carbon/proc/create_dna() - dna = new /datum/dna(src) - if(!dna.species) - var/rando_race = pick(GLOB.roundstart_races) - dna.species = new rando_race() - -//proc used to update the mob's appearance after its dna UI has been changed -/mob/living/carbon/proc/updateappearance(icon_update=1, mutcolor_update=0, mutations_overlay_update=0) - if(!has_dna()) - return - - switch(deconstruct_block(getblock(dna.uni_identity, DNA_GENDER_BLOCK), 3)) - if(G_MALE) - gender = MALE - if(G_FEMALE) - gender = FEMALE - else - gender = PLURAL - -/mob/living/carbon/human/updateappearance(icon_update=1, mutcolor_update=0, mutations_overlay_update=0) - ..() - var/structure = dna.uni_identity - hair_color = sanitize_hexcolor(getblock(structure, DNA_HAIR_COLOR_BLOCK)) - facial_hair_color = sanitize_hexcolor(getblock(structure, DNA_FACIAL_HAIR_COLOR_BLOCK)) - skin_tone = GLOB.skin_tones[deconstruct_block(getblock(structure, DNA_SKIN_TONE_BLOCK), GLOB.skin_tones.len)] - eye_color = sanitize_hexcolor(getblock(structure, DNA_EYE_COLOR_BLOCK)) - facial_hairstyle = GLOB.facial_hairstyles_list[deconstruct_block(getblock(structure, DNA_FACIAL_HAIRSTYLE_BLOCK), GLOB.facial_hairstyles_list.len)] - hairstyle = GLOB.hairstyles_list[deconstruct_block(getblock(structure, DNA_HAIRSTYLE_BLOCK), GLOB.hairstyles_list.len)] - if(icon_update) - update_body() - update_hair() - if(mutcolor_update) - update_body_parts() - if(mutations_overlay_update) - update_mutations_overlay() - - -/mob/proc/domutcheck() - return - -/mob/living/carbon/domutcheck() - if(!has_dna()) - return - - for(var/mutation in dna.mutation_index) - if(ismob(dna.check_block(mutation))) - return //we got monkeyized/humanized, this mob will be deleted, no need to continue. - - update_mutations_overlay() - -/datum/dna/proc/check_block(mutation) - var/datum/mutation/human/HM = get_mutation(mutation) - if(check_block_string(mutation)) - if(!HM) - . = add_mutation(mutation, MUT_NORMAL) - return - return force_lose(HM) - -//Return the active mutation of a type if there is one -/datum/dna/proc/get_mutation(A) - for(var/datum/mutation/human/HM in mutations) - if(HM.type == A) - return HM - -/datum/dna/proc/check_block_string(mutation) - if((LAZYLEN(mutation_index) > DNA_MUTATION_BLOCKS) || !(mutation in mutation_index)) - return 0 - return is_gene_active(mutation) - -/datum/dna/proc/is_gene_active(mutation) - return (mutation_index[mutation] == GET_SEQUENCE(mutation)) - -/datum/dna/proc/set_se(on=TRUE, datum/mutation/human/HM) - if(!HM || !(HM.type in mutation_index) || (LAZYLEN(mutation_index) < DNA_MUTATION_BLOCKS)) - return - . = TRUE - if(on) - mutation_index[HM.type] = GET_SEQUENCE(HM.type) - default_mutation_genes[HM.type] = mutation_index[HM.type] - else if(GET_SEQUENCE(HM.type) == mutation_index[HM.type]) - mutation_index[HM.type] = create_sequence(HM.type, FALSE, HM.difficulty) - default_mutation_genes[HM.type] = mutation_index[HM.type] - - -/datum/dna/proc/activate_mutation(mutation) //note that this returns a boolean and not a new mob - if(!mutation) - return FALSE - var/mutation_type = mutation - if(istype(mutation, /datum/mutation/human)) - var/datum/mutation/human/M = mutation - mutation_type = M.type - if(!mutation_in_sequence(mutation_type)) //cant activate what we dont have, use add_mutation - return FALSE - add_mutation(mutation, MUT_NORMAL) - return TRUE - -/////////////////////////// DNA HELPER-PROCS ////////////////////////////// - -/proc/getleftblocks(input,blocknumber,blocksize) - if(blocknumber > 1) - return copytext_char(input,1,((blocksize*blocknumber)-(blocksize-1))) - -/proc/getrightblocks(input,blocknumber,blocksize) - if(blocknumber < (length(input)/blocksize)) - return copytext_char(input,blocksize*blocknumber+1,length(input)+1) - -/proc/getblock(input, blocknumber, blocksize=DNA_BLOCK_SIZE) - return copytext_char(input, blocksize*(blocknumber-1)+1, (blocksize*blocknumber)+1) - -/proc/setblock(istring, blocknumber, replacement, blocksize=DNA_BLOCK_SIZE) - if(!istring || !blocknumber || !replacement || !blocksize) - return 0 - return getleftblocks(istring, blocknumber, blocksize) + replacement + getrightblocks(istring, blocknumber, blocksize) - -/datum/dna/proc/mutation_in_sequence(mutation) - if(!mutation) - return - if(istype(mutation, /datum/mutation/human)) - var/datum/mutation/human/HM = mutation - if(HM.type in mutation_index) - return TRUE - else if(mutation in mutation_index) - return TRUE - - -/mob/living/carbon/proc/randmut(list/candidates, difficulty = 2) - if(!has_dna()) - return - var/mutation = pick(candidates) - . = dna.add_mutation(mutation) - -/mob/living/carbon/proc/easy_randmut(quality = POSITIVE + NEGATIVE + MINOR_NEGATIVE, scrambled = TRUE, sequence = TRUE, exclude_monkey = TRUE, resilient = NONE) - if(!has_dna()) - return - var/list/mutations = list() - if(quality & POSITIVE) - mutations += GLOB.good_mutations - if(quality & NEGATIVE) - mutations += GLOB.bad_mutations - if(quality & MINOR_NEGATIVE) - mutations += GLOB.not_good_mutations - var/list/possible = list() - for(var/datum/mutation/human/A in mutations) - if((!sequence || dna.mutation_in_sequence(A.type)) && !dna.get_mutation(A.type)) - possible += A.type - if(exclude_monkey) - possible.Remove(RACEMUT) - if(LAZYLEN(possible)) - var/mutation = pick(possible) - . = dna.activate_mutation(mutation) - if(scrambled) - var/datum/mutation/human/HM = dna.get_mutation(mutation) - if(HM) - HM.scrambled = TRUE - if(HM.quality & resilient) - HM.mutadone_proof = TRUE - return TRUE - -/mob/living/carbon/proc/randmuti() - if(!has_dna()) - return - var/num = rand(1, DNA_UNI_IDENTITY_BLOCKS) - var/newdna = setblock(dna.uni_identity, num, random_string(DNA_BLOCK_SIZE, GLOB.hex_characters)) - dna.uni_identity = newdna - updateappearance(mutations_overlay_update=1) - -/mob/living/carbon/proc/clean_dna() - if(!has_dna()) - return - dna.remove_all_mutations() - -/mob/living/carbon/proc/clean_randmut(list/candidates, difficulty = 2) - clean_dna() - randmut(candidates, difficulty) - -/proc/scramble_dna(mob/living/carbon/M, ui=FALSE, se=FALSE, probability) - if(!M.has_dna()) - return 0 - if(se) - for(var/i=1, i<=DNA_MUTATION_BLOCKS, i++) - if(prob(probability)) - M.dna.generate_dna_blocks() - M.domutcheck() - if(ui) - for(var/i=1, i<=DNA_UNI_IDENTITY_BLOCKS, i++) - if(prob(probability)) - M.dna.uni_identity = setblock(M.dna.uni_identity, i, random_string(DNA_BLOCK_SIZE, GLOB.hex_characters)) - M.updateappearance(mutations_overlay_update=1) - return 1 - -//value in range 1 to values. values must be greater than 0 -//all arguments assumed to be positive integers -/proc/construct_block(value, values, blocksize=DNA_BLOCK_SIZE) - var/width = round((16**blocksize)/values) - if(value < 1) - value = 1 - value = (value * width) - rand(1,width) - return num2hex(value, blocksize) - -//value is hex -/proc/deconstruct_block(value, values, blocksize=DNA_BLOCK_SIZE) - var/width = round((16**blocksize)/values) - value = round(hex2num(value) / width) + 1 - if(value > values) - value = values - return value - -/////////////////////////// DNA HELPER-PROCS - -/mob/living/carbon/human/proc/something_horrible(ignore_stability) - if(!has_dna()) //shouldn't ever happen anyway so it's just in really weird cases - return - if(!ignore_stability && (dna.stability > 0)) - return - var/instability = -dna.stability - dna.remove_all_mutations() - dna.stability = 100 - if(prob(max(70-instability,0))) - switch(rand(0,10)) //not complete and utter death - if(0) - monkeyize() - if(1) - gain_trauma(/datum/brain_trauma/severe/paralysis/paraplegic) - new/obj/vehicle/ridden/wheelchair(get_turf(src)) //don't buckle, because I can't imagine to plethora of things to go through that could otherwise break - to_chat(src, "My flesh turned into a wheelchair and I can't feel my legs.") - if(2) - corgize() - if(3) - to_chat(src, "Oh, I actually feel quite alright!") - if(4) - to_chat(src, "Oh, I actually feel quite alright!") //you thought - physiology.damage_resistance = -20000 - if(5) - to_chat(src, "Oh, I actually feel quite alright!") - reagents.add_reagent(/datum/reagent/aslimetoxin, 10) - if(6) - apply_status_effect(STATUS_EFFECT_GO_AWAY) - if(7) - to_chat(src, "Oh, I actually feel quite alright!") - ForceContractDisease(new/datum/disease/decloning()) //slow acting, non-viral clone damage based GBS - if(8) - var/list/elligible_organs = list() - for(var/obj/item/organ/O in internal_organs) //make sure we dont get an implant or cavity item - elligible_organs += O - vomit(20, TRUE) - if(elligible_organs.len) - var/obj/item/organ/O = pick(elligible_organs) - O.Remove(src) - visible_message("[src] vomits up their [O.name]!", "You vomit up your [O.name]") //no "vomit up your the heart" - O.forceMove(drop_location()) - if(prob(20)) - O.animate_atom_living() - if(9 to 10) - ForceContractDisease(new/datum/disease/gastrolosis()) - to_chat(src, "Oh, I actually feel quite alright!") - else - switch(rand(0,5)) - if(0) - gib() - if(1) - dust() - - if(2) - death() - petrify(INFINITY) - if(3) - if(prob(95)) - var/obj/item/bodypart/BP = get_bodypart(pick(BODY_ZONE_CHEST,BODY_ZONE_HEAD)) - if(BP) - BP.dismember() - else - gib() - else - set_species(/datum/species/dullahan) - if(4) - visible_message("[src]'s skin melts off!", "Your skin melts off!") - spawn_gibs() - set_species(/datum/species/skeleton) - if(prob(90)) - addtimer(CALLBACK(src, .proc/death), 30) - if(mind) - mind.hasSoul = FALSE - if(5) - to_chat(src, "LOOK UP!") - addtimer(CALLBACK(src, .proc/something_horrible_mindmelt), 30) - - -/mob/living/carbon/human/proc/something_horrible_mindmelt() - if(!is_blind()) - var/obj/item/organ/eyes/eyes = locate(/obj/item/organ/eyes) in internal_organs - if(!eyes) - return - eyes.Remove(src) - qdel(eyes) - visible_message("[src] looks up and their eyes melt away!", "='userdanger'>I understand now.") - addtimer(CALLBACK(src, .proc/adjustOrganLoss, ORGAN_SLOT_BRAIN, 200), 20) + +/////////////////////////// DNA DATUM +/datum/dna + var/unique_enzymes + var/uni_identity + var/blood_type + var/datum/species/species = new /datum/species/human //The type of mutant race the player is if applicable (i.e. potato-man) + var/list/features = list("FFF") //first value is mutant color + var/real_name //Stores the real name of the person who originally got this dna datum. Used primarely for changelings, + var/list/mutations = list() //All mutations are from now on here + var/list/temporary_mutations = list() //Temporary changes to the UE + var/list/previous = list() //For temporary name/ui/ue/blood_type modifications + var/mob/living/holder + var/mutation_index[DNA_MUTATION_BLOCKS] //List of which mutations this carbon has and its assigned block + var/default_mutation_genes[DNA_MUTATION_BLOCKS] //List of the default genes from this mutation to allow DNA Scanner highlighting + var/stability = 100 + var/scrambled = FALSE //Did we take something like mutagen? In that case we cant get our genes scanned to instantly cheese all the powers. + + + var/delete_species = TRUE //Set to FALSE when a body is scanned by a cloner to fix #38875. WaspStation Edit - Cloning + +/datum/dna/New(mob/living/new_holder) + if(istype(new_holder)) + holder = new_holder + +/datum/dna/Destroy() + if(iscarbon(holder)) + var/mob/living/carbon/cholder = holder + if(cholder.dna == src) + cholder.dna = null + holder = null + + //WaspStation Begin - Cloning + if(delete_species) + QDEL_NULL(species) + //WaspStation End + + mutations.Cut() //This only references mutations, just dereference. + temporary_mutations.Cut() //^ + previous.Cut() //^ + + return ..() + +/datum/dna/proc/transfer_identity(mob/living/carbon/destination, transfer_SE = 0) + if(!istype(destination)) + return + destination.dna.unique_enzymes = unique_enzymes + destination.dna.uni_identity = uni_identity + destination.dna.blood_type = blood_type + destination.set_species(species.type, icon_update=0) + destination.dna.features = features.Copy() + destination.dna.real_name = real_name + destination.dna.temporary_mutations = temporary_mutations.Copy() + destination.flavor_text = destination.dna.features["flavor_text"] //Update the flavor_text to use new dna text + if(transfer_SE) + destination.dna.mutation_index = mutation_index + destination.dna.default_mutation_genes = default_mutation_genes + +/datum/dna/proc/copy_dna(datum/dna/new_dna) + new_dna.unique_enzymes = unique_enzymes + new_dna.mutation_index = mutation_index + new_dna.default_mutation_genes = default_mutation_genes + new_dna.uni_identity = uni_identity + new_dna.blood_type = blood_type + new_dna.features = features.Copy() + new_dna.species = new species.type + new_dna.real_name = real_name + new_dna.mutations = mutations.Copy() + +//See mutation.dm for what 'class' does. 'time' is time till it removes itself in decimals. 0 for no timer +/datum/dna/proc/add_mutation(mutation, class = MUT_OTHER, time) + var/mutation_type = mutation + if(istype(mutation, /datum/mutation/human)) + var/datum/mutation/human/HM = mutation + mutation_type = HM.type + if(get_mutation(mutation_type)) + return + return force_give(new mutation_type (class, time, copymut = mutation)) + +/datum/dna/proc/remove_mutation(mutation_type) + return force_lose(get_mutation(mutation_type)) + +/datum/dna/proc/check_mutation(mutation_type) + return get_mutation(mutation_type) + +/datum/dna/proc/remove_all_mutations(list/classes = list(MUT_NORMAL, MUT_EXTRA, MUT_OTHER), mutadone = FALSE) + remove_mutation_group(mutations, classes, mutadone) + scrambled = FALSE + +/datum/dna/proc/remove_mutation_group(list/group, list/classes = list(MUT_NORMAL, MUT_EXTRA, MUT_OTHER), mutadone = FALSE) + if(!group) + return + for(var/datum/mutation/human/HM in group) + if((HM.class in classes) && !(HM.mutadone_proof && mutadone)) + force_lose(HM) + +/datum/dna/proc/generate_uni_identity() + . = "" + var/list/L = new /list(DNA_UNI_IDENTITY_BLOCKS) + + switch(holder.gender) + if(MALE) + L[DNA_GENDER_BLOCK] = construct_block(G_MALE, 3) + if(FEMALE) + L[DNA_GENDER_BLOCK] = construct_block(G_FEMALE, 3) + else + L[DNA_GENDER_BLOCK] = construct_block(G_PLURAL, 3) + if(ishuman(holder)) + var/mob/living/carbon/human/H = holder + if(!GLOB.hairstyles_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/hair,GLOB.hairstyles_list, GLOB.hairstyles_male_list, GLOB.hairstyles_female_list) + L[DNA_HAIRSTYLE_BLOCK] = construct_block(GLOB.hairstyles_list.Find(H.hairstyle), GLOB.hairstyles_list.len) + L[DNA_HAIR_COLOR_BLOCK] = sanitize_hexcolor(H.hair_color) + if(!GLOB.facial_hairstyles_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/facial_hair, GLOB.facial_hairstyles_list, GLOB.facial_hairstyles_male_list, GLOB.facial_hairstyles_female_list) + L[DNA_FACIAL_HAIRSTYLE_BLOCK] = construct_block(GLOB.facial_hairstyles_list.Find(H.facial_hairstyle), GLOB.facial_hairstyles_list.len) + L[DNA_FACIAL_HAIR_COLOR_BLOCK] = sanitize_hexcolor(H.facial_hair_color) + L[DNA_SKIN_TONE_BLOCK] = construct_block(GLOB.skin_tones.Find(H.skin_tone), GLOB.skin_tones.len) + L[DNA_EYE_COLOR_BLOCK] = sanitize_hexcolor(H.eye_color) + + for(var/i=1, i<=DNA_UNI_IDENTITY_BLOCKS, i++) + if(L[i]) + . += L[i] + else + . += random_string(DNA_BLOCK_SIZE,GLOB.hex_characters) + return . + +/datum/dna/proc/generate_dna_blocks() + var/bonus + if(species && species.inert_mutation) + bonus = GET_INITIALIZED_MUTATION(species.inert_mutation) + var/list/mutations_temp = GLOB.good_mutations + GLOB.bad_mutations + GLOB.not_good_mutations + bonus + if(!LAZYLEN(mutations_temp)) + return + mutation_index.Cut() + default_mutation_genes.Cut() + shuffle_inplace(mutations_temp) + if(ismonkey(holder)) + mutations |= new RACEMUT(MUT_NORMAL) + mutation_index[RACEMUT] = GET_SEQUENCE(RACEMUT) + else + mutation_index[RACEMUT] = create_sequence(RACEMUT, FALSE) + default_mutation_genes[RACEMUT] = mutation_index[RACEMUT] + for(var/i in 2 to DNA_MUTATION_BLOCKS) + var/datum/mutation/human/M = mutations_temp[i] + mutation_index[M.type] = create_sequence(M.type, FALSE, M.difficulty) + default_mutation_genes[M.type] = mutation_index[M.type] + shuffle_inplace(mutation_index) + +//Used to generate original gene sequences for every mutation +/proc/generate_gene_sequence(length=4) + var/static/list/active_sequences = list("AT","TA","GC","CG") + var/sequence + for(var/i in 1 to length*DNA_SEQUENCE_LENGTH) + sequence += pick(active_sequences) + return sequence + +//Used to create a chipped gene sequence +/proc/create_sequence(mutation, active, difficulty) + if(!difficulty) + var/datum/mutation/human/A = GET_INITIALIZED_MUTATION(mutation) //leaves the possibility to change difficulty mid-round + if(!A) + return + difficulty = A.difficulty + difficulty += rand(-2,4) + var/sequence = GET_SEQUENCE(mutation) + if(active) + return sequence + while(difficulty) + var/randnum = rand(1, length_char(sequence)) + sequence = copytext_char(sequence, 1, randnum) + "X" + copytext_char(sequence, randnum + 1) + difficulty-- + return sequence + +/datum/dna/proc/generate_unique_enzymes() + . = "" + if(istype(holder)) + real_name = holder.real_name + . += md5(holder.real_name) + else + . += random_string(DNA_UNIQUE_ENZYMES_LEN, GLOB.hex_characters) + return . + +/datum/dna/proc/update_ui_block(blocknumber) + if(!blocknumber || !ishuman(holder)) + return + var/mob/living/carbon/human/H = holder + switch(blocknumber) + if(DNA_HAIR_COLOR_BLOCK) + setblock(uni_identity, blocknumber, sanitize_hexcolor(H.hair_color)) + if(DNA_FACIAL_HAIR_COLOR_BLOCK) + setblock(uni_identity, blocknumber, sanitize_hexcolor(H.facial_hair_color)) + if(DNA_SKIN_TONE_BLOCK) + setblock(uni_identity, blocknumber, construct_block(GLOB.skin_tones.Find(H.skin_tone), GLOB.skin_tones.len)) + if(DNA_EYE_COLOR_BLOCK) + setblock(uni_identity, blocknumber, sanitize_hexcolor(H.eye_color)) + if(DNA_GENDER_BLOCK) + switch(H.gender) + if(MALE) + setblock(uni_identity, blocknumber, construct_block(G_MALE, 3)) + if(FEMALE) + setblock(uni_identity, blocknumber, construct_block(G_FEMALE, 3)) + else + setblock(uni_identity, blocknumber, construct_block(G_PLURAL, 3)) + if(DNA_FACIAL_HAIRSTYLE_BLOCK) + setblock(uni_identity, blocknumber, construct_block(GLOB.facial_hairstyles_list.Find(H.facial_hairstyle), GLOB.facial_hairstyles_list.len)) + if(DNA_HAIRSTYLE_BLOCK) + setblock(uni_identity, blocknumber, construct_block(GLOB.hairstyles_list.Find(H.hairstyle), GLOB.hairstyles_list.len)) + +//Please use add_mutation or activate_mutation instead +/datum/dna/proc/force_give(datum/mutation/human/HM) + if(holder && HM) + if(HM.class == MUT_NORMAL) + set_se(1, HM) + . = HM.on_acquiring(holder) + if(.) + qdel(HM) + update_instability() + +//Use remove_mutation instead +/datum/dna/proc/force_lose(datum/mutation/human/HM) + if(holder && (HM in mutations)) + set_se(0, HM) + . = HM.on_losing(holder) + update_instability(FALSE) + return + +/datum/dna/proc/is_same_as(datum/dna/D) + if(uni_identity == D.uni_identity && mutation_index == D.mutation_index && real_name == D.real_name) + if(species.type == D.species.type && features == D.features && blood_type == D.blood_type) + return 1 + return 0 + +/datum/dna/proc/update_instability(alert=TRUE) + stability = 100 + for(var/datum/mutation/human/M in mutations) + if(M.class == MUT_EXTRA) + stability -= M.instability * GET_MUTATION_STABILIZER(M) + if(holder) + var/message + if(alert) + switch(stability) + if(70 to 90) + message = "You shiver." + if(60 to 69) + message = "You feel cold." + if(40 to 59) + message = "You feel sick." + if(20 to 39) + message = "It feels like your skin is moving." + if(1 to 19) + message = "You can feel your cells burning." + if(-INFINITY to 0) + message = "You can feel your DNA exploding, we need to do something fast!" + if(stability <= 0) + holder.apply_status_effect(STATUS_EFFECT_DNA_MELT) + if(message) + to_chat(holder, message) + +//used to update dna UI, UE, and dna.real_name. +/datum/dna/proc/update_dna_identity() + uni_identity = generate_uni_identity() + unique_enzymes = generate_unique_enzymes() + +/datum/dna/proc/initialize_dna(newblood_type, skip_index = FALSE) + if(newblood_type) + blood_type = newblood_type + unique_enzymes = generate_unique_enzymes() + uni_identity = generate_uni_identity() + if(!skip_index) //I hate this + generate_dna_blocks() + features = random_features() + + +/datum/dna/stored //subtype used by brain mob's stored_dna + +/datum/dna/stored/add_mutation(mutation_name) //no mutation changes on stored dna. + return + +/datum/dna/stored/remove_mutation(mutation_name) + return + +/datum/dna/stored/check_mutation(mutation_name) + return + +/datum/dna/stored/remove_all_mutations(list/classes, mutadone = FALSE) + return + +/datum/dna/stored/remove_mutation_group(list/group) + return + +/////////////////////////// DNA MOB-PROCS ////////////////////// + +/mob/proc/set_species(datum/species/mrace, icon_update = 1) + return + +/mob/living/brain/set_species(datum/species/mrace, icon_update = 1) + if(mrace) + if(ispath(mrace)) + stored_dna.species = new mrace() + else + stored_dna.species = mrace //not calling any species update procs since we're a brain, not a monkey/human + + +/mob/living/carbon/set_species(datum/species/mrace, icon_update = TRUE, pref_load = FALSE) + if(mrace && has_dna()) + var/datum/species/new_race + if(ispath(mrace)) + new_race = new mrace + else if(istype(mrace)) + new_race = mrace + else + return + deathsound = new_race.deathsound + dna.species.on_species_loss(src, new_race, pref_load) + var/datum/species/old_species = dna.species + dna.species = new_race + dna.species.on_species_gain(src, old_species, pref_load) + if(ishuman(src)) + qdel(language_holder) + var/species_holder = initial(mrace.species_language_holder) + language_holder = new species_holder(src) + update_atom_languages() + +/mob/living/carbon/human/set_species(datum/species/mrace, icon_update = TRUE, pref_load = FALSE) + ..() + if(icon_update) + update_body() + update_hair() + update_body_parts() + update_mutations_overlay()// no lizard with human hulk overlay please. + + +/mob/proc/has_dna() + return + +/mob/living/carbon/has_dna() + return dna + + +/mob/living/carbon/human/proc/hardset_dna(ui, list/mutation_index, list/default_mutation_genes, newreal_name, newblood_type, datum/species/mrace, newfeatures, list/mutations, force_transfer_mutations) +//Do not use force_transfer_mutations for stuff like cloners without some precautions, otherwise some conditional mutations could break (timers, drill hat etc) + if(newfeatures) + dna.features = newfeatures + flavor_text = dna.features["flavor_text"] //Update the flavor_text to use new dna text + + if(mrace) + var/datum/species/newrace = new mrace.type + newrace.copy_properties_from(mrace) + set_species(newrace, icon_update=0) + + if(newreal_name) + dna.real_name = newreal_name + dna.generate_unique_enzymes() + + if(newblood_type) + dna.blood_type = newblood_type + + if(ui) + dna.uni_identity = ui + updateappearance(icon_update=0) + + if(LAZYLEN(mutation_index)) + dna.mutation_index = mutation_index.Copy() + if(LAZYLEN(default_mutation_genes)) + dna.default_mutation_genes = default_mutation_genes.Copy() + else + dna.default_mutation_genes = mutation_index.Copy() + domutcheck() + + if(mrace || newfeatures || ui) + update_body() + update_hair() + update_body_parts() + update_mutations_overlay() + + if(LAZYLEN(mutations)) + for(var/M in mutations) + var/datum/mutation/human/HM = M + if(HM.allow_transfer || force_transfer_mutations) + dna.force_give(new HM.type(HM.class, copymut=HM)) //using force_give since it may include exotic mutations that otherwise wont be handled properly + +/mob/living/carbon/proc/create_dna() + dna = new /datum/dna(src) + if(!dna.species) + var/rando_race = pick(GLOB.roundstart_races) + dna.species = new rando_race() + +//proc used to update the mob's appearance after its dna UI has been changed +/mob/living/carbon/proc/updateappearance(icon_update=1, mutcolor_update=0, mutations_overlay_update=0) + if(!has_dna()) + return + + switch(deconstruct_block(getblock(dna.uni_identity, DNA_GENDER_BLOCK), 3)) + if(G_MALE) + gender = MALE + if(G_FEMALE) + gender = FEMALE + else + gender = PLURAL + +/mob/living/carbon/human/updateappearance(icon_update=1, mutcolor_update=0, mutations_overlay_update=0) + ..() + var/structure = dna.uni_identity + hair_color = sanitize_hexcolor(getblock(structure, DNA_HAIR_COLOR_BLOCK)) + facial_hair_color = sanitize_hexcolor(getblock(structure, DNA_FACIAL_HAIR_COLOR_BLOCK)) + skin_tone = GLOB.skin_tones[deconstruct_block(getblock(structure, DNA_SKIN_TONE_BLOCK), GLOB.skin_tones.len)] + eye_color = sanitize_hexcolor(getblock(structure, DNA_EYE_COLOR_BLOCK)) + facial_hairstyle = GLOB.facial_hairstyles_list[deconstruct_block(getblock(structure, DNA_FACIAL_HAIRSTYLE_BLOCK), GLOB.facial_hairstyles_list.len)] + hairstyle = GLOB.hairstyles_list[deconstruct_block(getblock(structure, DNA_HAIRSTYLE_BLOCK), GLOB.hairstyles_list.len)] + if(icon_update) + update_body() + update_hair() + if(mutcolor_update) + update_body_parts() + if(mutations_overlay_update) + update_mutations_overlay() + + +/mob/proc/domutcheck() + return + +/mob/living/carbon/domutcheck() + if(!has_dna()) + return + + for(var/mutation in dna.mutation_index) + if(ismob(dna.check_block(mutation))) + return //we got monkeyized/humanized, this mob will be deleted, no need to continue. + + update_mutations_overlay() + +/datum/dna/proc/check_block(mutation) + var/datum/mutation/human/HM = get_mutation(mutation) + if(check_block_string(mutation)) + if(!HM) + . = add_mutation(mutation, MUT_NORMAL) + return + return force_lose(HM) + +//Return the active mutation of a type if there is one +/datum/dna/proc/get_mutation(A) + for(var/datum/mutation/human/HM in mutations) + if(HM.type == A) + return HM + +/datum/dna/proc/check_block_string(mutation) + if((LAZYLEN(mutation_index) > DNA_MUTATION_BLOCKS) || !(mutation in mutation_index)) + return 0 + return is_gene_active(mutation) + +/datum/dna/proc/is_gene_active(mutation) + return (mutation_index[mutation] == GET_SEQUENCE(mutation)) + +/datum/dna/proc/set_se(on=TRUE, datum/mutation/human/HM) + if(!HM || !(HM.type in mutation_index) || (LAZYLEN(mutation_index) < DNA_MUTATION_BLOCKS)) + return + . = TRUE + if(on) + mutation_index[HM.type] = GET_SEQUENCE(HM.type) + default_mutation_genes[HM.type] = mutation_index[HM.type] + else if(GET_SEQUENCE(HM.type) == mutation_index[HM.type]) + mutation_index[HM.type] = create_sequence(HM.type, FALSE, HM.difficulty) + default_mutation_genes[HM.type] = mutation_index[HM.type] + + +/datum/dna/proc/activate_mutation(mutation) //note that this returns a boolean and not a new mob + if(!mutation) + return FALSE + var/mutation_type = mutation + if(istype(mutation, /datum/mutation/human)) + var/datum/mutation/human/M = mutation + mutation_type = M.type + if(!mutation_in_sequence(mutation_type)) //cant activate what we dont have, use add_mutation + return FALSE + add_mutation(mutation, MUT_NORMAL) + return TRUE + +/////////////////////////// DNA HELPER-PROCS ////////////////////////////// + +/proc/getleftblocks(input,blocknumber,blocksize) + if(blocknumber > 1) + return copytext_char(input,1,((blocksize*blocknumber)-(blocksize-1))) + +/proc/getrightblocks(input,blocknumber,blocksize) + if(blocknumber < (length(input)/blocksize)) + return copytext_char(input,blocksize*blocknumber+1,length(input)+1) + +/proc/getblock(input, blocknumber, blocksize=DNA_BLOCK_SIZE) + return copytext_char(input, blocksize*(blocknumber-1)+1, (blocksize*blocknumber)+1) + +/proc/setblock(istring, blocknumber, replacement, blocksize=DNA_BLOCK_SIZE) + if(!istring || !blocknumber || !replacement || !blocksize) + return 0 + return getleftblocks(istring, blocknumber, blocksize) + replacement + getrightblocks(istring, blocknumber, blocksize) + +/datum/dna/proc/mutation_in_sequence(mutation) + if(!mutation) + return + if(istype(mutation, /datum/mutation/human)) + var/datum/mutation/human/HM = mutation + if(HM.type in mutation_index) + return TRUE + else if(mutation in mutation_index) + return TRUE + + +/mob/living/carbon/proc/randmut(list/candidates, difficulty = 2) + if(!has_dna()) + return + var/mutation = pick(candidates) + . = dna.add_mutation(mutation) + +/mob/living/carbon/proc/easy_randmut(quality = POSITIVE + NEGATIVE + MINOR_NEGATIVE, scrambled = TRUE, sequence = TRUE, exclude_monkey = TRUE, resilient = NONE) + if(!has_dna()) + return + var/list/mutations = list() + if(quality & POSITIVE) + mutations += GLOB.good_mutations + if(quality & NEGATIVE) + mutations += GLOB.bad_mutations + if(quality & MINOR_NEGATIVE) + mutations += GLOB.not_good_mutations + var/list/possible = list() + for(var/datum/mutation/human/A in mutations) + if((!sequence || dna.mutation_in_sequence(A.type)) && !dna.get_mutation(A.type)) + possible += A.type + if(exclude_monkey) + possible.Remove(RACEMUT) + if(LAZYLEN(possible)) + var/mutation = pick(possible) + . = dna.activate_mutation(mutation) + if(scrambled) + var/datum/mutation/human/HM = dna.get_mutation(mutation) + if(HM) + HM.scrambled = TRUE + if(HM.quality & resilient) + HM.mutadone_proof = TRUE + return TRUE + +/mob/living/carbon/proc/randmuti() + if(!has_dna()) + return + var/num = rand(1, DNA_UNI_IDENTITY_BLOCKS) + var/newdna = setblock(dna.uni_identity, num, random_string(DNA_BLOCK_SIZE, GLOB.hex_characters)) + dna.uni_identity = newdna + updateappearance(mutations_overlay_update=1) + +/mob/living/carbon/proc/clean_dna() + if(!has_dna()) + return + dna.remove_all_mutations() + +/mob/living/carbon/proc/clean_randmut(list/candidates, difficulty = 2) + clean_dna() + randmut(candidates, difficulty) + +/proc/scramble_dna(mob/living/carbon/M, ui=FALSE, se=FALSE, probability) + if(!M.has_dna()) + return 0 + if(se) + for(var/i=1, i<=DNA_MUTATION_BLOCKS, i++) + if(prob(probability)) + M.dna.generate_dna_blocks() + M.domutcheck() + if(ui) + for(var/i=1, i<=DNA_UNI_IDENTITY_BLOCKS, i++) + if(prob(probability)) + M.dna.uni_identity = setblock(M.dna.uni_identity, i, random_string(DNA_BLOCK_SIZE, GLOB.hex_characters)) + M.updateappearance(mutations_overlay_update=1) + return 1 + +//value in range 1 to values. values must be greater than 0 +//all arguments assumed to be positive integers +/proc/construct_block(value, values, blocksize=DNA_BLOCK_SIZE) + var/width = round((16**blocksize)/values) + if(value < 1) + value = 1 + value = (value * width) - rand(1,width) + return num2hex(value, blocksize) + +//value is hex +/proc/deconstruct_block(value, values, blocksize=DNA_BLOCK_SIZE) + var/width = round((16**blocksize)/values) + value = round(hex2num(value) / width) + 1 + if(value > values) + value = values + return value + +/////////////////////////// DNA HELPER-PROCS + +/mob/living/carbon/human/proc/something_horrible(ignore_stability) + if(!has_dna()) //shouldn't ever happen anyway so it's just in really weird cases + return + if(!ignore_stability && (dna.stability > 0)) + return + var/instability = -dna.stability + dna.remove_all_mutations() + dna.stability = 100 + if(prob(max(70-instability,0))) + switch(rand(0,10)) //not complete and utter death + if(0) + monkeyize() + if(1) + gain_trauma(/datum/brain_trauma/severe/paralysis/paraplegic) + new/obj/vehicle/ridden/wheelchair(get_turf(src)) //don't buckle, because I can't imagine to plethora of things to go through that could otherwise break + to_chat(src, "My flesh turned into a wheelchair and I can't feel my legs.") + if(2) + corgize() + if(3) + to_chat(src, "Oh, I actually feel quite alright!") + if(4) + to_chat(src, "Oh, I actually feel quite alright!") //you thought + physiology.damage_resistance = -20000 + if(5) + to_chat(src, "Oh, I actually feel quite alright!") + reagents.add_reagent(/datum/reagent/aslimetoxin, 10) + if(6) + apply_status_effect(STATUS_EFFECT_GO_AWAY) + if(7) + to_chat(src, "Oh, I actually feel quite alright!") + ForceContractDisease(new/datum/disease/decloning()) //slow acting, non-viral clone damage based GBS + if(8) + var/list/elligible_organs = list() + for(var/obj/item/organ/O in internal_organs) //make sure we dont get an implant or cavity item + elligible_organs += O + vomit(20, TRUE) + if(elligible_organs.len) + var/obj/item/organ/O = pick(elligible_organs) + O.Remove(src) + visible_message("[src] vomits up their [O.name]!", "You vomit up your [O.name]") //no "vomit up your the heart" + O.forceMove(drop_location()) + if(prob(20)) + O.animate_atom_living() + if(9 to 10) + ForceContractDisease(new/datum/disease/gastrolosis()) + to_chat(src, "Oh, I actually feel quite alright!") + else + switch(rand(0,5)) + if(0) + gib() + if(1) + dust() + + if(2) + death() + petrify(INFINITY) + if(3) + if(prob(95)) + var/obj/item/bodypart/BP = get_bodypart(pick(BODY_ZONE_CHEST,BODY_ZONE_HEAD)) + if(BP) + BP.dismember() + else + gib() + else + set_species(/datum/species/dullahan) + if(4) + visible_message("[src]'s skin melts off!", "Your skin melts off!") + spawn_gibs() + set_species(/datum/species/skeleton) + if(prob(90)) + addtimer(CALLBACK(src, .proc/death), 30) + if(mind) + mind.hasSoul = FALSE + if(5) + to_chat(src, "LOOK UP!") + addtimer(CALLBACK(src, .proc/something_horrible_mindmelt), 30) + + +/mob/living/carbon/human/proc/something_horrible_mindmelt() + if(!is_blind()) + var/obj/item/organ/eyes/eyes = locate(/obj/item/organ/eyes) in internal_organs + if(!eyes) + return + eyes.Remove(src) + qdel(eyes) + visible_message("[src] looks up and their eyes melt away!", "='userdanger'>I understand now.") + addtimer(CALLBACK(src, .proc/adjustOrganLoss, ORGAN_SLOT_BRAIN, 200), 20) diff --git a/code/datums/forced_movement.dm b/code/datums/forced_movement.dm index 2c649be12b80..551699a01911 100644 --- a/code/datums/forced_movement.dm +++ b/code/datums/forced_movement.dm @@ -1,93 +1,93 @@ -//Just new and forget -/datum/forced_movement - var/atom/movable/victim - var/atom/target - var/last_processed - var/steps_per_tick - var/allow_climbing - var/datum/callback/on_step - var/moved_at_all = FALSE - //as fast as ssfastprocess -/datum/forced_movement/New(atom/movable/_victim, atom/_target, _steps_per_tick = 0.5, _allow_climbing = FALSE, datum/callback/_on_step = null) - victim = _victim - target = _target - steps_per_tick = _steps_per_tick - allow_climbing = _allow_climbing - on_step = _on_step - - . = ..() - - if(_victim && _target && _steps_per_tick && !_victim.force_moving) - last_processed = world.time - _victim.force_moving = src - START_PROCESSING(SSfastprocess, src) - else - qdel(src) //if you want to overwrite the current forced movement, call qdel(victim.force_moving) before creating this - -/datum/forced_movement/Destroy() - if(victim.force_moving == src) - victim.force_moving = null - if(moved_at_all) - victim.forceMove(victim.loc) //get the side effects of moving here that require us to currently not be force_moving aka reslipping on ice - STOP_PROCESSING(SSfastprocess, src) - victim = null - target = null - return ..() - -/datum/forced_movement/process() - if(QDELETED(victim) || !victim.loc || QDELETED(target) || !target.loc) - qdel(src) - return - var/steps_to_take = round(steps_per_tick * (world.time - last_processed)) - if(steps_to_take) - for(var/i in 1 to steps_to_take) - if(TryMove()) - moved_at_all = TRUE - if(on_step) - on_step.InvokeAsync() - else - qdel(src) - return - last_processed = world.time - -/datum/forced_movement/proc/TryMove(recursive = FALSE) - if(QDELETED(src)) //Our previous step caused deletion of this datum - return - - var/atom/movable/vic = victim //sanic - var/atom/tar = target - - if(!recursive) - . = step_towards(vic, tar) - - //shit way for getting around corners - if(!.) - if(tar.x > vic.x) - if(step(vic, EAST)) - . = TRUE - else if(tar.x < vic.x) - if(step(vic, WEST)) - . = TRUE - - if(!.) - if(tar.y > vic.y) - if(step(vic, NORTH)) - . = TRUE - else if(tar.y < vic.y) - if(step(vic, SOUTH)) - . = TRUE - - if(!.) - if(recursive) - return FALSE - else - . = TryMove(TRUE) - - . = . && (vic.loc != tar.loc) - -/mob/Bump(atom/A) - . = ..() - if(force_moving && force_moving.allow_climbing && isstructure(A)) - var/obj/structure/S = A - if(S.climbable) - S.do_climb(src) +//Just new and forget +/datum/forced_movement + var/atom/movable/victim + var/atom/target + var/last_processed + var/steps_per_tick + var/allow_climbing + var/datum/callback/on_step + var/moved_at_all = FALSE + //as fast as ssfastprocess +/datum/forced_movement/New(atom/movable/_victim, atom/_target, _steps_per_tick = 0.5, _allow_climbing = FALSE, datum/callback/_on_step = null) + victim = _victim + target = _target + steps_per_tick = _steps_per_tick + allow_climbing = _allow_climbing + on_step = _on_step + + . = ..() + + if(_victim && _target && _steps_per_tick && !_victim.force_moving) + last_processed = world.time + _victim.force_moving = src + START_PROCESSING(SSfastprocess, src) + else + qdel(src) //if you want to overwrite the current forced movement, call qdel(victim.force_moving) before creating this + +/datum/forced_movement/Destroy() + if(victim.force_moving == src) + victim.force_moving = null + if(moved_at_all) + victim.forceMove(victim.loc) //get the side effects of moving here that require us to currently not be force_moving aka reslipping on ice + STOP_PROCESSING(SSfastprocess, src) + victim = null + target = null + return ..() + +/datum/forced_movement/process() + if(QDELETED(victim) || !victim.loc || QDELETED(target) || !target.loc) + qdel(src) + return + var/steps_to_take = round(steps_per_tick * (world.time - last_processed)) + if(steps_to_take) + for(var/i in 1 to steps_to_take) + if(TryMove()) + moved_at_all = TRUE + if(on_step) + on_step.InvokeAsync() + else + qdel(src) + return + last_processed = world.time + +/datum/forced_movement/proc/TryMove(recursive = FALSE) + if(QDELETED(src)) //Our previous step caused deletion of this datum + return + + var/atom/movable/vic = victim //sanic + var/atom/tar = target + + if(!recursive) + . = step_towards(vic, tar) + + //shit way for getting around corners + if(!.) + if(tar.x > vic.x) + if(step(vic, EAST)) + . = TRUE + else if(tar.x < vic.x) + if(step(vic, WEST)) + . = TRUE + + if(!.) + if(tar.y > vic.y) + if(step(vic, NORTH)) + . = TRUE + else if(tar.y < vic.y) + if(step(vic, SOUTH)) + . = TRUE + + if(!.) + if(recursive) + return FALSE + else + . = TryMove(TRUE) + + . = . && (vic.loc != tar.loc) + +/mob/Bump(atom/A) + . = ..() + if(force_moving && force_moving.allow_climbing && isstructure(A)) + var/obj/structure/S = A + if(S.climbable) + S.do_climb(src) diff --git a/code/datums/helper_datums/events.dm b/code/datums/helper_datums/events.dm index c9bf2957b97d..bbd1220048d6 100644 --- a/code/datums/helper_datums/events.dm +++ b/code/datums/helper_datums/events.dm @@ -1,55 +1,55 @@ -/* - * WARRANTY VOID IF CODE USED - */ - - -/datum/events - var/list/events - -/datum/events/New() - ..() - events = new - -/datum/events/Destroy() - for(var/elist in events) - for(var/e in events[elist]) - qdel(e) - events = null - return ..() - -/datum/events/proc/addEventType(event_type as text) - if(!(event_type in events) || !islist(events[event_type])) - events[event_type] = list() - return TRUE - return FALSE - -// Arguments: event_type as text, proc_holder as datum, proc_name as text -// Returns: New event, null on error. -/datum/events/proc/addEvent(event_type as text, datum/callback/cb) - if(!event_type || !cb) - return - addEventType(event_type) - var/list/event = events[event_type] - event += cb - return cb - -// Arguments: event_type as text, any number of additional arguments to pass to event handler -// Returns: null -/datum/events/proc/fireEvent(eventName, ...) - - var/list/event = LAZYACCESS(events,eventName) - if(istype(event)) - for(var/E in event) - var/datum/callback/cb = E - cb.InvokeAsync(arglist(args.Copy(2))) - -// Arguments: event_type as text, E as /datum/event -// Returns: TRUE if event cleared, FALSE on error - -/datum/events/proc/clearEvent(event_type as text, datum/callback/cb) - if(!event_type || !cb) - return FALSE - var/list/event = LAZYACCESS(events,event_type) - event -= cb - qdel(cb) - return TRUE +/* + * WARRANTY VOID IF CODE USED + */ + + +/datum/events + var/list/events + +/datum/events/New() + ..() + events = new + +/datum/events/Destroy() + for(var/elist in events) + for(var/e in events[elist]) + qdel(e) + events = null + return ..() + +/datum/events/proc/addEventType(event_type as text) + if(!(event_type in events) || !islist(events[event_type])) + events[event_type] = list() + return TRUE + return FALSE + +// Arguments: event_type as text, proc_holder as datum, proc_name as text +// Returns: New event, null on error. +/datum/events/proc/addEvent(event_type as text, datum/callback/cb) + if(!event_type || !cb) + return + addEventType(event_type) + var/list/event = events[event_type] + event += cb + return cb + +// Arguments: event_type as text, any number of additional arguments to pass to event handler +// Returns: null +/datum/events/proc/fireEvent(eventName, ...) + + var/list/event = LAZYACCESS(events,eventName) + if(istype(event)) + for(var/E in event) + var/datum/callback/cb = E + cb.InvokeAsync(arglist(args.Copy(2))) + +// Arguments: event_type as text, E as /datum/event +// Returns: TRUE if event cleared, FALSE on error + +/datum/events/proc/clearEvent(event_type as text, datum/callback/cb) + if(!event_type || !cb) + return FALSE + var/list/event = LAZYACCESS(events,event_type) + event -= cb + qdel(cb) + return TRUE diff --git a/code/datums/helper_datums/getrev.dm b/code/datums/helper_datums/getrev.dm index fa2496ee3b17..91b8e325661b 100644 --- a/code/datums/helper_datums/getrev.dm +++ b/code/datums/helper_datums/getrev.dm @@ -1,124 +1,128 @@ -/datum/getrev - var/commit // git rev-parse HEAD - var/date - var/originmastercommit // git rev-parse origin/master - var/list/testmerge = list() - -/datum/getrev/New() - testmerge = world.TgsTestMerges() - var/datum/tgs_revision_information/revinfo = world.TgsRevision() - if(revinfo) - commit = revinfo.commit - originmastercommit = revinfo.origin_commit - else - commit = rustg_git_revparse("HEAD") - if(commit) - date = rustg_git_commit_date(commit) - originmastercommit = rustg_git_revparse("origin/master") - - // goes to DD log and config_error.txt - log_world(get_log_message()) - -/datum/getrev/proc/get_log_message() - var/list/msg = list() - msg += "Running /tg/ revision: [date]" - if(originmastercommit) - msg += "origin/master: [originmastercommit]" - - for(var/line in testmerge) - var/datum/tgs_revision_information/test_merge/tm = line - msg += "Test merge active of PR #[tm.number] commit [tm.pull_request_commit]" - SSblackbox.record_feedback("associative", "testmerged_prs", 1, list("number" = "[tm.number]", "commit" = "[tm.pull_request_commit]", "title" = "[tm.title]", "author" = "[tm.author]")) - - if(commit && commit != originmastercommit) - msg += "HEAD: [commit]" - else if(!originmastercommit) - msg += "No commit information" - - return msg.Join("\n") - -/datum/getrev/proc/GetTestMergeInfo(header = TRUE) - if(!testmerge.len) - return "" - . = header ? "The following pull requests are currently test merged:
    " : "" - for(var/line in testmerge) - var/datum/tgs_revision_information/test_merge/tm = line - var/cm = tm.pull_request_commit - var/details = ": '" + html_encode(tm.title) + "' by " + html_encode(tm.author) + " at commit " + html_encode(copytext_char(cm, 1, 11)) - if(details && findtext(details, "\[s\]") && (!usr || !usr.client.holder)) - continue - . += "#[tm.number][details]
    " - -/client/verb/showrevinfo() - set category = "OOC" - set name = "Show Server Revision" - set desc = "Check the current server code revision" - - var/list/msg = list("") - // Round ID - if(GLOB.round_id) - msg += "Round ID: [GLOB.round_id]" - - msg += "BYOND Version: [world.byond_version].[world.byond_build]" - if(DM_VERSION != world.byond_version || DM_BUILD != world.byond_build) - msg += "Compiled with BYOND Version: [DM_VERSION].[DM_BUILD]" - - // Revision information - var/datum/getrev/revdata = GLOB.revdata - msg += "Server revision compiled on: [revdata.date]" - var/pc = revdata.originmastercommit - if(pc) - msg += "Master commit: [pc]" - if(revdata.testmerge.len) - msg += revdata.GetTestMergeInfo() - if(revdata.commit && revdata.commit != revdata.originmastercommit) - msg += "Local commit: [revdata.commit]" - else if(!pc) - msg += "No commit information" - if(world.TgsAvailable()) - var/datum/tgs_version/version = world.TgsVersion() - msg += "Server tools version: [version.raw_parameter]" - - // Game mode odds - msg += "
    Current Informational Settings:" - msg += "Protect Authority Roles From Traitor: [CONFIG_GET(flag/protect_roles_from_antagonist)]" - msg += "Protect Assistant Role From Traitor: [CONFIG_GET(flag/protect_assistant_from_antagonist)]" - msg += "Enforce Human Authority: [CONFIG_GET(flag/enforce_human_authority)]" - msg += "Allow Latejoin Antagonists: [CONFIG_GET(flag/allow_latejoin_antagonists)]" - msg += "Enforce Continuous Rounds: [length(CONFIG_GET(keyed_list/continuous))] of [config.modes.len] roundtypes" - msg += "Allow Midround Antagonists: [length(CONFIG_GET(keyed_list/midround_antag))] of [config.modes.len] roundtypes" - if(CONFIG_GET(flag/show_game_type_odds)) - var/list/probabilities = CONFIG_GET(keyed_list/probability) - if(SSticker.IsRoundInProgress()) - var/prob_sum = 0 - var/current_odds_differ = FALSE - var/list/probs = list() - var/list/modes = config.gamemode_cache - var/list/min_pop = CONFIG_GET(keyed_list/min_pop) - var/list/max_pop = CONFIG_GET(keyed_list/max_pop) - for(var/mode in modes) - var/datum/game_mode/M = mode - var/ctag = initial(M.config_tag) - if(!(ctag in probabilities)) - continue - if((min_pop[ctag] && (min_pop[ctag] > SSticker.totalPlayersReady)) || (max_pop[ctag] && (max_pop[ctag] < SSticker.totalPlayersReady)) || (initial(M.required_players) > SSticker.totalPlayersReady)) - current_odds_differ = TRUE - continue - probs[ctag] = 1 - prob_sum += probabilities[ctag] - if(current_odds_differ) - msg += "Game Mode Odds for current round:" - for(var/ctag in probs) - if(probabilities[ctag] > 0) - var/percentage = round(probabilities[ctag] / prob_sum * 100, 0.1) - msg += "[ctag] [percentage]%" - - msg += "All Game Mode Odds:" - var/sum = 0 - for(var/ctag in probabilities) - sum += probabilities[ctag] - for(var/ctag in probabilities) - if(probabilities[ctag] > 0) - var/percentage = round(probabilities[ctag] / sum * 100, 0.1) - msg += "[ctag] [percentage]%" - to_chat(src, msg.Join("
    ")) +/datum/getrev + var/commit // git rev-parse HEAD + var/date + var/originmastercommit // git rev-parse origin/master + var/list/testmerge = list() + +/datum/getrev/New() + commit = rustg_git_revparse("HEAD") + if(commit) + date = rustg_git_commit_date(commit) + originmastercommit = rustg_git_revparse("origin/master") + +/datum/getrev/proc/load_tgs_info() + testmerge = world.TgsTestMerges() + var/datum/tgs_revision_information/revinfo = world.TgsRevision() + if(revinfo) + commit = revinfo.commit + originmastercommit = revinfo.origin_commit + date = rustg_git_commit_date(commit) + + // goes to DD log and config_error.txt + log_world(get_log_message()) + +/datum/getrev/proc/get_log_message() + var/list/msg = list() + msg += "Running /tg/ revision: [date]" + if(originmastercommit) + msg += "origin/master: [originmastercommit]" + + for(var/line in testmerge) + var/datum/tgs_revision_information/test_merge/tm = line + msg += "Test merge active of PR #[tm.number] commit [tm.pull_request_commit]" + SSblackbox.record_feedback("associative", "testmerged_prs", 1, list("number" = "[tm.number]", "commit" = "[tm.pull_request_commit]", "title" = "[tm.title]", "author" = "[tm.author]")) + + if(commit && commit != originmastercommit) + msg += "HEAD: [commit]" + else if(!originmastercommit) + msg += "No commit information" + + return msg.Join("\n") + +/datum/getrev/proc/GetTestMergeInfo(header = TRUE) + if(!testmerge.len) + return "" + . = header ? "The following pull requests are currently test merged:
    " : "" + for(var/line in testmerge) + var/datum/tgs_revision_information/test_merge/tm = line + var/cm = tm.pull_request_commit + var/details = ": '" + html_encode(tm.title) + "' by " + html_encode(tm.author) + " at commit " + html_encode(copytext_char(cm, 1, 11)) + if(details && findtext(details, "\[s\]") && (!usr || !usr.client.holder)) + continue + . += "#[tm.number][details]
    " + +/client/verb/showrevinfo() + set category = "OOC" + set name = "Show Server Revision" + set desc = "Check the current server code revision" + + var/list/msg = list("") + // Round ID + if(GLOB.round_id) + msg += "Round ID: [GLOB.round_id]" + + msg += "BYOND Version: [world.byond_version].[world.byond_build]" + if(DM_VERSION != world.byond_version || DM_BUILD != world.byond_build) + msg += "Compiled with BYOND Version: [DM_VERSION].[DM_BUILD]" + + // Revision information + var/datum/getrev/revdata = GLOB.revdata + msg += "Server revision compiled on: [revdata.date]" + var/pc = revdata.originmastercommit + if(pc) + msg += "Master commit: [pc]" + if(revdata.testmerge.len) + msg += revdata.GetTestMergeInfo() + if(revdata.commit && revdata.commit != revdata.originmastercommit) + msg += "Local commit: [revdata.commit]" + else if(!pc) + msg += "No commit information" + if(world.TgsAvailable()) + var/datum/tgs_version/version = world.TgsVersion() + msg += "TGS version: [version.raw_parameter]" + var/datum/tgs_version/api_version = world.TgsApiVersion() + msg += "DMAPI version: [api_version.raw_parameter]" + + // Game mode odds + msg += "
    Current Informational Settings:" + msg += "Protect Authority Roles From Traitor: [CONFIG_GET(flag/protect_roles_from_antagonist)]" + msg += "Protect Assistant Role From Traitor: [CONFIG_GET(flag/protect_assistant_from_antagonist)]" + msg += "Enforce Human Authority: [CONFIG_GET(flag/enforce_human_authority)]" + msg += "Allow Latejoin Antagonists: [CONFIG_GET(flag/allow_latejoin_antagonists)]" + msg += "Enforce Continuous Rounds: [length(CONFIG_GET(keyed_list/continuous))] of [config.modes.len] roundtypes" + msg += "Allow Midround Antagonists: [length(CONFIG_GET(keyed_list/midround_antag))] of [config.modes.len] roundtypes" + if(CONFIG_GET(flag/show_game_type_odds)) + var/list/probabilities = CONFIG_GET(keyed_list/probability) + if(SSticker.IsRoundInProgress()) + var/prob_sum = 0 + var/current_odds_differ = FALSE + var/list/probs = list() + var/list/modes = config.gamemode_cache + var/list/min_pop = CONFIG_GET(keyed_list/min_pop) + var/list/max_pop = CONFIG_GET(keyed_list/max_pop) + for(var/mode in modes) + var/datum/game_mode/M = mode + var/ctag = initial(M.config_tag) + if(!(ctag in probabilities)) + continue + if((min_pop[ctag] && (min_pop[ctag] > SSticker.totalPlayersReady)) || (max_pop[ctag] && (max_pop[ctag] < SSticker.totalPlayersReady)) || (initial(M.required_players) > SSticker.totalPlayersReady)) + current_odds_differ = TRUE + continue + probs[ctag] = 1 + prob_sum += probabilities[ctag] + if(current_odds_differ) + msg += "Game Mode Odds for current round:" + for(var/ctag in probs) + if(probabilities[ctag] > 0) + var/percentage = round(probabilities[ctag] / prob_sum * 100, 0.1) + msg += "[ctag] [percentage]%" + + msg += "All Game Mode Odds:" + var/sum = 0 + for(var/ctag in probabilities) + sum += probabilities[ctag] + for(var/ctag in probabilities) + if(probabilities[ctag] > 0) + var/percentage = round(probabilities[ctag] / sum * 100, 0.1) + msg += "[ctag] [percentage]%" + to_chat(src, msg.Join("
    ")) diff --git a/code/datums/helper_datums/teleport.dm b/code/datums/helper_datums/teleport.dm index c3411a6b0df0..f0b43b2bfdb9 100644 --- a/code/datums/helper_datums/teleport.dm +++ b/code/datums/helper_datums/teleport.dm @@ -1,167 +1,167 @@ -// teleatom: atom to teleport -// destination: destination to teleport to -// precision: teleport precision (0 is most precise, the default) -// effectin: effect to show right before teleportation -// effectout: effect to show right after teleportation -// asoundin: soundfile to play before teleportation -// asoundout: soundfile to play after teleportation -// forceMove: if false, teleport will use Move() proc (dense objects will prevent teleportation) -// no_effects: disable the default effectin/effectout of sparks -// forced: whether or not to ignore no_teleport -/proc/do_teleport(atom/movable/teleatom, atom/destination, precision=null, forceMove = TRUE, datum/effect_system/effectin=null, datum/effect_system/effectout=null, asoundin=null, asoundout=null, no_effects=FALSE, channel=TELEPORT_CHANNEL_BLUESPACE, forced = FALSE) - // teleporting most effects just deletes them - var/static/list/delete_atoms = typecacheof(list( - /obj/effect, - )) - typecacheof(list( - /obj/effect/dummy/chameleon, - /obj/effect/wisp, - /obj/effect/mob_spawn, - )) - if(delete_atoms[teleatom.type]) - qdel(teleatom) - return FALSE - - // argument handling - // if the precision is not specified, default to 0, but apply BoH penalties - if (isnull(precision)) - precision = 0 - - switch(channel) - if(TELEPORT_CHANNEL_BLUESPACE) - if(istype(teleatom, /obj/item/storage/backpack/holding)) - precision = rand(1,100) - - var/static/list/bag_cache = typecacheof(/obj/item/storage/backpack/holding) - var/list/bagholding = typecache_filter_list(teleatom.GetAllContents(), bag_cache) - if(bagholding.len) - precision = max(rand(1,100)*bagholding.len,100) - if(isliving(teleatom)) - var/mob/living/MM = teleatom - to_chat(MM, "The bluespace interface on your bag of holding interferes with the teleport!") - - // if effects are not specified and not explicitly disabled, sparks - if ((!effectin || !effectout) && !no_effects) - var/datum/effect_system/spark_spread/sparks = new - sparks.set_up(5, 1, teleatom) - if (!effectin) - effectin = sparks - if (!effectout) - effectout = sparks - if(TELEPORT_CHANNEL_QUANTUM) - // if effects are not specified and not explicitly disabled, rainbow sparks - if ((!effectin || !effectout) && !no_effects) - var/datum/effect_system/spark_spread/quantum/sparks = new - sparks.set_up(5, 1, teleatom) - if (!effectin) - effectin = sparks - if (!effectout) - effectout = sparks - - // perform the teleport - var/turf/curturf = get_turf(teleatom) - var/turf/destturf = get_teleport_turf(get_turf(destination), precision) - - if(!destturf || !curturf || destturf.is_transition_turf()) - return FALSE - - var/area/A = get_area(curturf) - var/area/B = get_area(destturf) - if(!forced && (HAS_TRAIT(teleatom, TRAIT_NO_TELEPORT) || A.noteleport || B.noteleport)) - return FALSE - - if(SEND_SIGNAL(destturf, COMSIG_ATOM_INTERCEPT_TELEPORT, channel, curturf, destturf)) - return FALSE - - tele_play_specials(teleatom, curturf, effectin, asoundin) - var/success = forceMove ? teleatom.forceMove(destturf) : teleatom.Move(destturf) - if (success) - log_game("[key_name(teleatom)] has teleported from [loc_name(curturf)] to [loc_name(destturf)]") - tele_play_specials(teleatom, destturf, effectout, asoundout) - if(ismegafauna(teleatom)) - message_admins("[teleatom] [ADMIN_FLW(teleatom)] has teleported from [ADMIN_VERBOSEJMP(curturf)] to [ADMIN_VERBOSEJMP(destturf)].") - - if(ismob(teleatom)) - var/mob/M = teleatom - M.cancel_camera() - - return TRUE - -/proc/tele_play_specials(atom/movable/teleatom, atom/location, datum/effect_system/effect, sound) - if (location && !isobserver(teleatom)) - if (sound) - playsound(location, sound, 60, TRUE) - if (effect) - effect.attach(location) - effect.start() - -// Safe location finder -/proc/find_safe_turf(zlevel, list/zlevels, extended_safety_checks = FALSE) - if(!zlevels) - if (zlevel) - zlevels = list(zlevel) - else - zlevels = SSmapping.levels_by_trait(ZTRAIT_STATION) - var/cycles = 1000 - for(var/cycle in 1 to cycles) - // DRUNK DIALLING WOOOOOOOOO - var/x = rand(1, world.maxx) - var/y = rand(1, world.maxy) - var/z = pick(zlevels) - var/random_location = locate(x,y,z) - - if(!isfloorturf(random_location)) - continue - var/turf/open/floor/F = random_location - if(!F.air) - continue - - var/datum/gas_mixture/A = F.air - var/trace_gases - for(var/id in A.get_gases()) - if(id in GLOB.hardcoded_gases) - continue - trace_gases = TRUE - break - - // Can most things breathe? - if(trace_gases) - continue - if(A.get_moles(/datum/gas/oxygen) < 16) - continue - if(A.get_moles(/datum/gas/plasma)) - continue - if(A.get_moles(/datum/gas/carbon_dioxide) >= 10) - continue - - // Aim for goldilocks temperatures and pressure - if((A.return_temperature() <= 270) || (A.return_temperature() >= 360)) - continue - var/pressure = A.return_pressure() - if((pressure <= 20) || (pressure >= 550)) - continue - - if(extended_safety_checks) - if(islava(F)) //chasms aren't /floor, and so are pre-filtered - var/turf/open/lava/L = F - if(!L.is_safe()) - continue - - // DING! You have passed the gauntlet, and are "probably" safe. - return F - -/proc/get_teleport_turfs(turf/center, precision = 0) - if(!precision) - return list(center) - var/list/posturfs = list() - for(var/turf/T in range(precision,center)) - if(T.is_transition_turf()) - continue // Avoid picking these. - var/area/A = T.loc - if(!A.noteleport) - posturfs.Add(T) - return posturfs - -/proc/get_teleport_turf(turf/center, precision = 0) - var/list/turfs = get_teleport_turfs(center, precision) - if (length(turfs)) - return pick(turfs) +// teleatom: atom to teleport +// destination: destination to teleport to +// precision: teleport precision (0 is most precise, the default) +// effectin: effect to show right before teleportation +// effectout: effect to show right after teleportation +// asoundin: soundfile to play before teleportation +// asoundout: soundfile to play after teleportation +// forceMove: if false, teleport will use Move() proc (dense objects will prevent teleportation) +// no_effects: disable the default effectin/effectout of sparks +// forced: whether or not to ignore no_teleport +/proc/do_teleport(atom/movable/teleatom, atom/destination, precision=null, forceMove = TRUE, datum/effect_system/effectin=null, datum/effect_system/effectout=null, asoundin=null, asoundout=null, no_effects=FALSE, channel=TELEPORT_CHANNEL_BLUESPACE, forced = FALSE) + // teleporting most effects just deletes them + var/static/list/delete_atoms = typecacheof(list( + /obj/effect, + )) - typecacheof(list( + /obj/effect/dummy/chameleon, + /obj/effect/wisp, + /obj/effect/mob_spawn, + )) + if(delete_atoms[teleatom.type]) + qdel(teleatom) + return FALSE + + // argument handling + // if the precision is not specified, default to 0, but apply BoH penalties + if (isnull(precision)) + precision = 0 + + switch(channel) + if(TELEPORT_CHANNEL_BLUESPACE) + if(istype(teleatom, /obj/item/storage/backpack/holding)) + precision = rand(1,100) + + var/static/list/bag_cache = typecacheof(/obj/item/storage/backpack/holding) + var/list/bagholding = typecache_filter_list(teleatom.GetAllContents(), bag_cache) + if(bagholding.len) + precision = max(rand(1,100)*bagholding.len,100) + if(isliving(teleatom)) + var/mob/living/MM = teleatom + to_chat(MM, "The bluespace interface on your bag of holding interferes with the teleport!") + + // if effects are not specified and not explicitly disabled, sparks + if ((!effectin || !effectout) && !no_effects) + var/datum/effect_system/spark_spread/sparks = new + sparks.set_up(5, 1, teleatom) + if (!effectin) + effectin = sparks + if (!effectout) + effectout = sparks + if(TELEPORT_CHANNEL_QUANTUM) + // if effects are not specified and not explicitly disabled, rainbow sparks + if ((!effectin || !effectout) && !no_effects) + var/datum/effect_system/spark_spread/quantum/sparks = new + sparks.set_up(5, 1, teleatom) + if (!effectin) + effectin = sparks + if (!effectout) + effectout = sparks + + // perform the teleport + var/turf/curturf = get_turf(teleatom) + var/turf/destturf = get_teleport_turf(get_turf(destination), precision) + + if(!destturf || !curturf || destturf.is_transition_turf()) + return FALSE + + var/area/A = get_area(curturf) + var/area/B = get_area(destturf) + if(!forced && (HAS_TRAIT(teleatom, TRAIT_NO_TELEPORT) || A.noteleport || B.noteleport)) + return FALSE + + if(SEND_SIGNAL(destturf, COMSIG_ATOM_INTERCEPT_TELEPORT, channel, curturf, destturf)) + return FALSE + + tele_play_specials(teleatom, curturf, effectin, asoundin) + var/success = forceMove ? teleatom.forceMove(destturf) : teleatom.Move(destturf) + if (success) + log_game("[key_name(teleatom)] has teleported from [loc_name(curturf)] to [loc_name(destturf)]") + tele_play_specials(teleatom, destturf, effectout, asoundout) + if(ismegafauna(teleatom)) + message_admins("[teleatom] [ADMIN_FLW(teleatom)] has teleported from [ADMIN_VERBOSEJMP(curturf)] to [ADMIN_VERBOSEJMP(destturf)].") + + if(ismob(teleatom)) + var/mob/M = teleatom + M.cancel_camera() + + return TRUE + +/proc/tele_play_specials(atom/movable/teleatom, atom/location, datum/effect_system/effect, sound) + if (location && !isobserver(teleatom)) + if (sound) + playsound(location, sound, 60, TRUE) + if (effect) + effect.attach(location) + effect.start() + +// Safe location finder +/proc/find_safe_turf(zlevel, list/zlevels, extended_safety_checks = FALSE) + if(!zlevels) + if (zlevel) + zlevels = list(zlevel) + else + zlevels = SSmapping.levels_by_trait(ZTRAIT_STATION) + var/cycles = 1000 + for(var/cycle in 1 to cycles) + // DRUNK DIALLING WOOOOOOOOO + var/x = rand(1, world.maxx) + var/y = rand(1, world.maxy) + var/z = pick(zlevels) + var/random_location = locate(x,y,z) + + if(!isfloorturf(random_location)) + continue + var/turf/open/floor/F = random_location + if(!F.air) + continue + + var/datum/gas_mixture/A = F.air + var/trace_gases + for(var/id in A.get_gases()) + if(id in GLOB.hardcoded_gases) + continue + trace_gases = TRUE + break + + // Can most things breathe? + if(trace_gases) + continue + if(A.get_moles(/datum/gas/oxygen) < 16) + continue + if(A.get_moles(/datum/gas/plasma)) + continue + if(A.get_moles(/datum/gas/carbon_dioxide) >= 10) + continue + + // Aim for goldilocks temperatures and pressure + if((A.return_temperature() <= 270) || (A.return_temperature() >= 360)) + continue + var/pressure = A.return_pressure() + if((pressure <= 20) || (pressure >= 550)) + continue + + if(extended_safety_checks) + if(islava(F)) //chasms aren't /floor, and so are pre-filtered + var/turf/open/lava/L = F + if(!L.is_safe()) + continue + + // DING! You have passed the gauntlet, and are "probably" safe. + return F + +/proc/get_teleport_turfs(turf/center, precision = 0) + if(!precision) + return list(center) + var/list/posturfs = list() + for(var/turf/T in range(precision,center)) + if(T.is_transition_turf()) + continue // Avoid picking these. + var/area/A = T.loc + if(!A.noteleport) + posturfs.Add(T) + return posturfs + +/proc/get_teleport_turf(turf/center, precision = 0) + var/list/turfs = get_teleport_turfs(center, precision) + if (length(turfs)) + return pick(turfs) diff --git a/code/datums/holocall.dm b/code/datums/holocall.dm index 8f0ed6aed6d3..fdcd0e531ee1 100644 --- a/code/datums/holocall.dm +++ b/code/datums/holocall.dm @@ -1,425 +1,425 @@ -#define HOLOPAD_MAX_DIAL_TIME 200 - -#define HOLORECORD_DELAY "delay" -#define HOLORECORD_SAY "say" -#define HOLORECORD_SOUND "sound" -#define HOLORECORD_LANGUAGE "lang" -#define HOLORECORD_PRESET "preset" -#define HOLORECORD_RENAME "rename" - -#define HOLORECORD_MAX_LENGTH 200 - -/mob/camera/aiEye/remote/holo/setLoc() - . = ..() - var/obj/machinery/holopad/H = origin - H?.move_hologram(eye_user, loc) - -/obj/machinery/holopad/remove_eye_control(mob/living/user) - if(user.client) - user.reset_perspective(null) - user.remote_control = null - -//this datum manages it's own references - -/datum/holocall - var/mob/living/user //the one that called - var/obj/machinery/holopad/calling_holopad //the one that sent the call - var/obj/machinery/holopad/connected_holopad //the one that answered the call (may be null) - var/list/dialed_holopads //all things called, will be cleared out to just connected_holopad once answered - - var/mob/camera/aiEye/remote/holo/eye //user's eye, once connected - var/obj/effect/overlay/holo_pad_hologram/hologram //user's hologram, once connected - var/datum/action/innate/end_holocall/hangup //hangup action - - var/call_start_time - var/head_call = FALSE //calls from a head of staff autoconnect, if the recieving pad is not secure. - -//creates a holocall made by `caller` from `calling_pad` to `callees` -/datum/holocall/New(mob/living/caller, obj/machinery/holopad/calling_pad, list/callees, elevated_access = FALSE) - call_start_time = world.time - user = caller - calling_pad.outgoing_call = src - calling_holopad = calling_pad - head_call = elevated_access - dialed_holopads = list() - - for(var/I in callees) - var/obj/machinery/holopad/H = I - if(!QDELETED(H) && H.is_operational()) - dialed_holopads += H - if(head_call) - if(H.secure) - calling_pad.say("Auto-connection refused, falling back to call mode.") - H.say("Incoming call.") - else - H.say("Incoming connection.") - else - H.say("Incoming call.") - LAZYADD(H.holo_calls, src) - - if(!dialed_holopads.len) - calling_pad.say("Connection failure.") - qdel(src) - return - - testing("Holocall started") - -//cleans up ALL references :) -/datum/holocall/Destroy() - QDEL_NULL(hangup) - - if(!QDELETED(eye)) - QDEL_NULL(eye) - - if(connected_holopad && !QDELETED(hologram)) - hologram = null - connected_holopad.clear_holo(user) - - user = null - - //Hologram survived holopad destro - if(!QDELETED(hologram)) - hologram.HC = null - QDEL_NULL(hologram) - - for(var/I in dialed_holopads) - var/obj/machinery/holopad/H = I - LAZYREMOVE(H.holo_calls, src) - dialed_holopads.Cut() - - if(calling_holopad) - calling_holopad.calling = FALSE - calling_holopad.outgoing_call = null - calling_holopad.SetLightsAndPower() - calling_holopad = null - if(connected_holopad) - connected_holopad.SetLightsAndPower() - connected_holopad = null - - testing("Holocall destroyed") - - return ..() - -//Gracefully disconnects a holopad `H` from a call. Pads not in the call are ignored. Notifies participants of the disconnection -/datum/holocall/proc/Disconnect(obj/machinery/holopad/H) - testing("Holocall disconnect") - if(H == connected_holopad) - var/area/A = get_area(connected_holopad) - calling_holopad.say("[A] holopad disconnected.") - else if(H == calling_holopad && connected_holopad) - connected_holopad.say("[user] disconnected.") - - ConnectionFailure(H, TRUE) - -//Forcefully disconnects a holopad `H` from a call. Pads not in the call are ignored. -/datum/holocall/proc/ConnectionFailure(obj/machinery/holopad/H, graceful = FALSE) - testing("Holocall connection failure: graceful [graceful]") - if(H == connected_holopad || H == calling_holopad) - if(!graceful && H != calling_holopad) - calling_holopad.say("Connection failure.") - qdel(src) - return - - LAZYREMOVE(H.holo_calls, src) - dialed_holopads -= H - if(!dialed_holopads.len) - if(graceful) - calling_holopad.say("Call rejected.") - testing("No recipients, terminating") - qdel(src) - -//Answers a call made to a holopad `H` which cannot be the calling holopad. Pads not in the call are ignored -/datum/holocall/proc/Answer(obj/machinery/holopad/H) - testing("Holocall answer") - if(H == calling_holopad) - CRASH("How cute, a holopad tried to answer itself.") - - if(!(H in dialed_holopads)) - return - - if(connected_holopad) - CRASH("Multi-connection holocall") - - for(var/I in dialed_holopads) - if(I == H) - continue - Disconnect(I) - - for(var/I in H.holo_calls) - var/datum/holocall/HC = I - if(HC != src) - HC.Disconnect(H) - - connected_holopad = H - - if(!Check()) - return - - calling_holopad.calling = FALSE - hologram = H.activate_holo(user) - hologram.HC = src - - //eyeobj code is horrid, this is the best copypasta I could make - eye = new - eye.origin = H - eye.eye_initialized = TRUE - eye.eye_user = user - eye.name = "Camera Eye ([user.name])" - user.remote_control = eye - user.reset_perspective(eye) - eye.setLoc(H.loc) - - hangup = new(eye, src) - hangup.Grant(user) - playsound(H, 'sound/machines/ping.ogg', 100) - H.say("Connection established.") - -//Checks the validity of a holocall and qdels itself if it's not. Returns TRUE if valid, FALSE otherwise -/datum/holocall/proc/Check() - for(var/I in dialed_holopads) - var/obj/machinery/holopad/H = I - if(!H.is_operational()) - ConnectionFailure(H) - - if(QDELETED(src)) - return FALSE - - . = !QDELETED(user) && !user.incapacitated() && !QDELETED(calling_holopad) && calling_holopad.is_operational() && user.loc == calling_holopad.loc - - if(.) - if(!connected_holopad) - . = world.time < (call_start_time + HOLOPAD_MAX_DIAL_TIME) - if(!.) - calling_holopad.say("No answer received.") - - if(!.) - testing("Holocall Check fail") - qdel(src) - -/datum/action/innate/end_holocall - name = "End Holocall" - icon_icon = 'icons/mob/actions/actions_silicon.dmi' - button_icon_state = "camera_off" - var/datum/holocall/hcall - -/datum/action/innate/end_holocall/New(Target, datum/holocall/HC) - ..() - hcall = HC - -/datum/action/innate/end_holocall/Activate() - hcall.Disconnect(hcall.calling_holopad) - - -//RECORDS -/datum/holorecord - var/caller_name = "Unknown" //Caller name - var/image/caller_image - var/list/entries = list() - var/language = /datum/language/common //Initial language, can be changed by HOLORECORD_LANGUAGE entries - -/datum/holorecord/proc/set_caller_image(mob/user) - var/olddir = user.dir - user.setDir(SOUTH) - caller_image = image(user) - user.setDir(olddir) - -/obj/item/disk/holodisk - name = "holorecord disk" - desc = "Stores recorder holocalls." - icon_state = "holodisk" - obj_flags = UNIQUE_RENAME - custom_materials = list(/datum/material/iron = 100, /datum/material/glass = 100) - var/datum/holorecord/record - //Preset variables - var/preset_image_type - var/preset_record_text - -/obj/item/disk/holodisk/Initialize(mapload) - . = ..() - if(preset_record_text) - build_record() - -/obj/item/disk/holodisk/Destroy() - QDEL_NULL(record) - return ..() - -/obj/item/disk/holodisk/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/disk/holodisk)) - var/obj/item/disk/holodisk/holodiskOriginal = W - if (holodiskOriginal.record) - if (!record) - record = new - record.caller_name = holodiskOriginal.record.caller_name - record.caller_image = holodiskOriginal.record.caller_image - record.entries = holodiskOriginal.record.entries.Copy() - record.language = holodiskOriginal.record.language - to_chat(user, "You copy the record from [holodiskOriginal] to [src] by connecting the ports!") - name = holodiskOriginal.name - else - to_chat(user, "[holodiskOriginal] has no record on it!") - ..() - -/obj/item/disk/holodisk/proc/build_record() - record = new - var/list/lines = splittext(preset_record_text,"\n") - for(var/line in lines) - var/prepared_line = trim(line) - if(!length(prepared_line)) - continue - var/splitpoint = findtext(prepared_line," ") - if(!splitpoint) - continue - var/command = copytext(prepared_line, 1, splitpoint) - var/value = copytext(prepared_line, splitpoint + length(prepared_line[splitpoint])) - switch(command) - if("DELAY") - var/delay_value = text2num(value) - if(!delay_value) - continue - record.entries += list(list(HOLORECORD_DELAY,delay_value)) - if("NAME") - if(!record.caller_name) - record.caller_name = value - else - record.entries += list(list(HOLORECORD_RENAME,value)) - if("SAY") - record.entries += list(list(HOLORECORD_SAY,value)) - if("SOUND") - record.entries += list(list(HOLORECORD_SOUND,value)) - if("LANGUAGE") - var/lang_type = text2path(value) - if(ispath(lang_type,/datum/language)) - record.entries += list(list(HOLORECORD_LANGUAGE,lang_type)) - if("PRESET") - var/preset_type = text2path(value) - if(ispath(preset_type,/datum/preset_holoimage)) - record.entries += list(list(HOLORECORD_PRESET,preset_type)) - if(!preset_image_type) - record.caller_image = image('icons/mob/animal.dmi',"old") - else - var/datum/preset_holoimage/H = new preset_image_type - record.caller_image = H.build_image() - -//These build caller image from outfit and some additional data, for use by mappers for ruin holorecords -/datum/preset_holoimage - var/nonhuman_mobtype //Fill this if you just want something nonhuman - var/outfit_type - var/species_type = /datum/species/human - -/datum/preset_holoimage/proc/build_image() - if(nonhuman_mobtype) - var/mob/living/L = nonhuman_mobtype - . = image(initial(L.icon),initial(L.icon_state)) - else - var/mob/living/carbon/human/dummy/mannequin = generate_or_wait_for_human_dummy("HOLODISK_PRESET") - if(species_type) - mannequin.set_species(species_type) - if(outfit_type) - mannequin.equipOutfit(outfit_type,TRUE) - mannequin.setDir(SOUTH) - COMPILE_OVERLAYS(mannequin) - . = image(mannequin) - unset_busy_human_dummy("HOLODISK_PRESET") - -/obj/item/disk/holodisk/example - preset_image_type = /datum/preset_holoimage/clown - preset_record_text = {" - NAME Clown - DELAY 10 - SAY Why did the chaplain cross the maint ? - DELAY 20 - SAY He wanted to get to the other side! - SOUND clownstep - DELAY 30 - LANGUAGE /datum/language/narsie - SAY Helped him get there! - DELAY 10 - SAY ALSO IM SECRETLY A GORILLA - DELAY 10 - PRESET /datum/preset_holoimage/gorilla - NAME Gorilla - LANGUAGE /datum/language/common - SAY OOGA - DELAY 20"} - -/datum/preset_holoimage/engineer - outfit_type = /datum/outfit/job/engineer - -/datum/preset_holoimage/engineer/rig - outfit_type = /datum/outfit/job/engineer/gloved/rig - -/datum/preset_holoimage/engineer/ce - outfit_type = /datum/outfit/job/ce - -/datum/preset_holoimage/engineer/ce/rig - outfit_type = /datum/outfit/job/engineer/gloved/rig - -/datum/preset_holoimage/engineer/atmos - outfit_type = /datum/outfit/job/atmos - -/datum/preset_holoimage/engineer/atmos/rig - outfit_type = /datum/outfit/job/engineer/gloved/rig - -/datum/preset_holoimage/researcher - outfit_type = /datum/outfit/job/scientist - -/datum/preset_holoimage/captain - outfit_type = /datum/outfit/job/captain - -/datum/preset_holoimage/nanotrasenprivatesecurity - outfit_type = /datum/outfit/nanotrasensoldiercorpse2 - -/datum/preset_holoimage/gorilla - nonhuman_mobtype = /mob/living/simple_animal/hostile/gorilla - -/datum/preset_holoimage/corgi - nonhuman_mobtype = /mob/living/simple_animal/pet/dog/corgi - -/datum/preset_holoimage/clown - outfit_type = /datum/outfit/job/clown - -/obj/item/disk/holodisk/donutstation/whiteship - name = "Blackbox Print-out #DS024" - desc = "A holodisk containing the last viable recording of DS024's blackbox." - preset_image_type = /datum/preset_holoimage/engineer/ce - preset_record_text = {" - NAME Geysr Shorthalt - SAY Engine renovations complete and the ships been loaded. We all ready? - DELAY 25 - PRESET /datum/preset_holoimage/engineer - NAME Jacob Ullman - SAY Lets blow this popsicle stand of a station. - DELAY 20 - PRESET /datum/preset_holoimage/engineer/atmos - NAME Lindsey Cuffler - SAY Uh, sir? Shouldn't we call for a secondary shuttle? The bluespace drive on this thing made an awfully weird noise when we jumped here.. - DELAY 30 - PRESET /datum/preset_holoimage/engineer/ce - NAME Geysr Shorthalt - SAY Pah! Ship techie at the dock said to give it a good few kicks if it started acting up, let me just.. - DELAY 25 - SOUND punch - SOUND sparks - DELAY 10 - SOUND punch - SOUND sparks - DELAY 10 - SOUND punch - SOUND sparks - SOUND warpspeed - DELAY 15 - PRESET /datum/preset_holoimage/engineer/atmos - NAME Lindsey Cuffler - SAY Uhh.. is it supposed to be doing that?? - DELAY 15 - PRESET /datum/preset_holoimage/engineer/ce - NAME Geysr Shorthalt - SAY See? Working as intended. Now, are we all ready? - DELAY 10 - PRESET /datum/preset_holoimage/engineer - NAME Jacob Ullman - SAY Is it supposed to be glowing like that? - DELAY 20 - SOUND explosion - - "} +#define HOLOPAD_MAX_DIAL_TIME 200 + +#define HOLORECORD_DELAY "delay" +#define HOLORECORD_SAY "say" +#define HOLORECORD_SOUND "sound" +#define HOLORECORD_LANGUAGE "lang" +#define HOLORECORD_PRESET "preset" +#define HOLORECORD_RENAME "rename" + +#define HOLORECORD_MAX_LENGTH 200 + +/mob/camera/aiEye/remote/holo/setLoc() + . = ..() + var/obj/machinery/holopad/H = origin + H?.move_hologram(eye_user, loc) + +/obj/machinery/holopad/remove_eye_control(mob/living/user) + if(user.client) + user.reset_perspective(null) + user.remote_control = null + +//this datum manages it's own references + +/datum/holocall + var/mob/living/user //the one that called + var/obj/machinery/holopad/calling_holopad //the one that sent the call + var/obj/machinery/holopad/connected_holopad //the one that answered the call (may be null) + var/list/dialed_holopads //all things called, will be cleared out to just connected_holopad once answered + + var/mob/camera/aiEye/remote/holo/eye //user's eye, once connected + var/obj/effect/overlay/holo_pad_hologram/hologram //user's hologram, once connected + var/datum/action/innate/end_holocall/hangup //hangup action + + var/call_start_time + var/head_call = FALSE //calls from a head of staff autoconnect, if the recieving pad is not secure. + +//creates a holocall made by `caller` from `calling_pad` to `callees` +/datum/holocall/New(mob/living/caller, obj/machinery/holopad/calling_pad, list/callees, elevated_access = FALSE) + call_start_time = world.time + user = caller + calling_pad.outgoing_call = src + calling_holopad = calling_pad + head_call = elevated_access + dialed_holopads = list() + + for(var/I in callees) + var/obj/machinery/holopad/H = I + if(!QDELETED(H) && H.is_operational()) + dialed_holopads += H + if(head_call) + if(H.secure) + calling_pad.say("Auto-connection refused, falling back to call mode.") + H.say("Incoming call.") + else + H.say("Incoming connection.") + else + H.say("Incoming call.") + LAZYADD(H.holo_calls, src) + + if(!dialed_holopads.len) + calling_pad.say("Connection failure.") + qdel(src) + return + + testing("Holocall started") + +//cleans up ALL references :) +/datum/holocall/Destroy() + QDEL_NULL(hangup) + + if(!QDELETED(eye)) + QDEL_NULL(eye) + + if(connected_holopad && !QDELETED(hologram)) + hologram = null + connected_holopad.clear_holo(user) + + user = null + + //Hologram survived holopad destro + if(!QDELETED(hologram)) + hologram.HC = null + QDEL_NULL(hologram) + + for(var/I in dialed_holopads) + var/obj/machinery/holopad/H = I + LAZYREMOVE(H.holo_calls, src) + dialed_holopads.Cut() + + if(calling_holopad) + calling_holopad.calling = FALSE + calling_holopad.outgoing_call = null + calling_holopad.SetLightsAndPower() + calling_holopad = null + if(connected_holopad) + connected_holopad.SetLightsAndPower() + connected_holopad = null + + testing("Holocall destroyed") + + return ..() + +//Gracefully disconnects a holopad `H` from a call. Pads not in the call are ignored. Notifies participants of the disconnection +/datum/holocall/proc/Disconnect(obj/machinery/holopad/H) + testing("Holocall disconnect") + if(H == connected_holopad) + var/area/A = get_area(connected_holopad) + calling_holopad.say("[A] holopad disconnected.") + else if(H == calling_holopad && connected_holopad) + connected_holopad.say("[user] disconnected.") + + ConnectionFailure(H, TRUE) + +//Forcefully disconnects a holopad `H` from a call. Pads not in the call are ignored. +/datum/holocall/proc/ConnectionFailure(obj/machinery/holopad/H, graceful = FALSE) + testing("Holocall connection failure: graceful [graceful]") + if(H == connected_holopad || H == calling_holopad) + if(!graceful && H != calling_holopad) + calling_holopad.say("Connection failure.") + qdel(src) + return + + LAZYREMOVE(H.holo_calls, src) + dialed_holopads -= H + if(!dialed_holopads.len) + if(graceful) + calling_holopad.say("Call rejected.") + testing("No recipients, terminating") + qdel(src) + +//Answers a call made to a holopad `H` which cannot be the calling holopad. Pads not in the call are ignored +/datum/holocall/proc/Answer(obj/machinery/holopad/H) + testing("Holocall answer") + if(H == calling_holopad) + CRASH("How cute, a holopad tried to answer itself.") + + if(!(H in dialed_holopads)) + return + + if(connected_holopad) + CRASH("Multi-connection holocall") + + for(var/I in dialed_holopads) + if(I == H) + continue + Disconnect(I) + + for(var/I in H.holo_calls) + var/datum/holocall/HC = I + if(HC != src) + HC.Disconnect(H) + + connected_holopad = H + + if(!Check()) + return + + calling_holopad.calling = FALSE + hologram = H.activate_holo(user) + hologram.HC = src + + //eyeobj code is horrid, this is the best copypasta I could make + eye = new + eye.origin = H + eye.eye_initialized = TRUE + eye.eye_user = user + eye.name = "Camera Eye ([user.name])" + user.remote_control = eye + user.reset_perspective(eye) + eye.setLoc(H.loc) + + hangup = new(eye, src) + hangup.Grant(user) + playsound(H, 'sound/machines/ping.ogg', 100) + H.say("Connection established.") + +//Checks the validity of a holocall and qdels itself if it's not. Returns TRUE if valid, FALSE otherwise +/datum/holocall/proc/Check() + for(var/I in dialed_holopads) + var/obj/machinery/holopad/H = I + if(!H.is_operational()) + ConnectionFailure(H) + + if(QDELETED(src)) + return FALSE + + . = !QDELETED(user) && !user.incapacitated() && !QDELETED(calling_holopad) && calling_holopad.is_operational() && user.loc == calling_holopad.loc + + if(.) + if(!connected_holopad) + . = world.time < (call_start_time + HOLOPAD_MAX_DIAL_TIME) + if(!.) + calling_holopad.say("No answer received.") + + if(!.) + testing("Holocall Check fail") + qdel(src) + +/datum/action/innate/end_holocall + name = "End Holocall" + icon_icon = 'icons/mob/actions/actions_silicon.dmi' + button_icon_state = "camera_off" + var/datum/holocall/hcall + +/datum/action/innate/end_holocall/New(Target, datum/holocall/HC) + ..() + hcall = HC + +/datum/action/innate/end_holocall/Activate() + hcall.Disconnect(hcall.calling_holopad) + + +//RECORDS +/datum/holorecord + var/caller_name = "Unknown" //Caller name + var/image/caller_image + var/list/entries = list() + var/language = /datum/language/common //Initial language, can be changed by HOLORECORD_LANGUAGE entries + +/datum/holorecord/proc/set_caller_image(mob/user) + var/olddir = user.dir + user.setDir(SOUTH) + caller_image = image(user) + user.setDir(olddir) + +/obj/item/disk/holodisk + name = "holorecord disk" + desc = "Stores recorder holocalls." + icon_state = "holodisk" + obj_flags = UNIQUE_RENAME + custom_materials = list(/datum/material/iron = 100, /datum/material/glass = 100) + var/datum/holorecord/record + //Preset variables + var/preset_image_type + var/preset_record_text + +/obj/item/disk/holodisk/Initialize(mapload) + . = ..() + if(preset_record_text) + build_record() + +/obj/item/disk/holodisk/Destroy() + QDEL_NULL(record) + return ..() + +/obj/item/disk/holodisk/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/disk/holodisk)) + var/obj/item/disk/holodisk/holodiskOriginal = W + if (holodiskOriginal.record) + if (!record) + record = new + record.caller_name = holodiskOriginal.record.caller_name + record.caller_image = holodiskOriginal.record.caller_image + record.entries = holodiskOriginal.record.entries.Copy() + record.language = holodiskOriginal.record.language + to_chat(user, "You copy the record from [holodiskOriginal] to [src] by connecting the ports!") + name = holodiskOriginal.name + else + to_chat(user, "[holodiskOriginal] has no record on it!") + ..() + +/obj/item/disk/holodisk/proc/build_record() + record = new + var/list/lines = splittext(preset_record_text,"\n") + for(var/line in lines) + var/prepared_line = trim(line) + if(!length(prepared_line)) + continue + var/splitpoint = findtext(prepared_line," ") + if(!splitpoint) + continue + var/command = copytext(prepared_line, 1, splitpoint) + var/value = copytext(prepared_line, splitpoint + length(prepared_line[splitpoint])) + switch(command) + if("DELAY") + var/delay_value = text2num(value) + if(!delay_value) + continue + record.entries += list(list(HOLORECORD_DELAY,delay_value)) + if("NAME") + if(!record.caller_name) + record.caller_name = value + else + record.entries += list(list(HOLORECORD_RENAME,value)) + if("SAY") + record.entries += list(list(HOLORECORD_SAY,value)) + if("SOUND") + record.entries += list(list(HOLORECORD_SOUND,value)) + if("LANGUAGE") + var/lang_type = text2path(value) + if(ispath(lang_type,/datum/language)) + record.entries += list(list(HOLORECORD_LANGUAGE,lang_type)) + if("PRESET") + var/preset_type = text2path(value) + if(ispath(preset_type,/datum/preset_holoimage)) + record.entries += list(list(HOLORECORD_PRESET,preset_type)) + if(!preset_image_type) + record.caller_image = image('icons/mob/animal.dmi',"old") + else + var/datum/preset_holoimage/H = new preset_image_type + record.caller_image = H.build_image() + +//These build caller image from outfit and some additional data, for use by mappers for ruin holorecords +/datum/preset_holoimage + var/nonhuman_mobtype //Fill this if you just want something nonhuman + var/outfit_type + var/species_type = /datum/species/human + +/datum/preset_holoimage/proc/build_image() + if(nonhuman_mobtype) + var/mob/living/L = nonhuman_mobtype + . = image(initial(L.icon),initial(L.icon_state)) + else + var/mob/living/carbon/human/dummy/mannequin = generate_or_wait_for_human_dummy("HOLODISK_PRESET") + if(species_type) + mannequin.set_species(species_type) + if(outfit_type) + mannequin.equipOutfit(outfit_type,TRUE) + mannequin.setDir(SOUTH) + COMPILE_OVERLAYS(mannequin) + . = image(mannequin) + unset_busy_human_dummy("HOLODISK_PRESET") + +/obj/item/disk/holodisk/example + preset_image_type = /datum/preset_holoimage/clown + preset_record_text = {" + NAME Clown + DELAY 10 + SAY Why did the chaplain cross the maint ? + DELAY 20 + SAY He wanted to get to the other side! + SOUND clownstep + DELAY 30 + LANGUAGE /datum/language/narsie + SAY Helped him get there! + DELAY 10 + SAY ALSO IM SECRETLY A GORILLA + DELAY 10 + PRESET /datum/preset_holoimage/gorilla + NAME Gorilla + LANGUAGE /datum/language/common + SAY OOGA + DELAY 20"} + +/datum/preset_holoimage/engineer + outfit_type = /datum/outfit/job/engineer + +/datum/preset_holoimage/engineer/rig + outfit_type = /datum/outfit/job/engineer/gloved/rig + +/datum/preset_holoimage/engineer/ce + outfit_type = /datum/outfit/job/ce + +/datum/preset_holoimage/engineer/ce/rig + outfit_type = /datum/outfit/job/engineer/gloved/rig + +/datum/preset_holoimage/engineer/atmos + outfit_type = /datum/outfit/job/atmos + +/datum/preset_holoimage/engineer/atmos/rig + outfit_type = /datum/outfit/job/engineer/gloved/rig + +/datum/preset_holoimage/researcher + outfit_type = /datum/outfit/job/scientist + +/datum/preset_holoimage/captain + outfit_type = /datum/outfit/job/captain + +/datum/preset_holoimage/nanotrasenprivatesecurity + outfit_type = /datum/outfit/nanotrasensoldiercorpse2 + +/datum/preset_holoimage/gorilla + nonhuman_mobtype = /mob/living/simple_animal/hostile/gorilla + +/datum/preset_holoimage/corgi + nonhuman_mobtype = /mob/living/simple_animal/pet/dog/corgi + +/datum/preset_holoimage/clown + outfit_type = /datum/outfit/job/clown + +/obj/item/disk/holodisk/donutstation/whiteship + name = "Blackbox Print-out #DS024" + desc = "A holodisk containing the last viable recording of DS024's blackbox." + preset_image_type = /datum/preset_holoimage/engineer/ce + preset_record_text = {" + NAME Geysr Shorthalt + SAY Engine renovations complete and the ships been loaded. We all ready? + DELAY 25 + PRESET /datum/preset_holoimage/engineer + NAME Jacob Ullman + SAY Lets blow this popsicle stand of a station. + DELAY 20 + PRESET /datum/preset_holoimage/engineer/atmos + NAME Lindsey Cuffler + SAY Uh, sir? Shouldn't we call for a secondary shuttle? The bluespace drive on this thing made an awfully weird noise when we jumped here.. + DELAY 30 + PRESET /datum/preset_holoimage/engineer/ce + NAME Geysr Shorthalt + SAY Pah! Ship techie at the dock said to give it a good few kicks if it started acting up, let me just.. + DELAY 25 + SOUND punch + SOUND sparks + DELAY 10 + SOUND punch + SOUND sparks + DELAY 10 + SOUND punch + SOUND sparks + SOUND warpspeed + DELAY 15 + PRESET /datum/preset_holoimage/engineer/atmos + NAME Lindsey Cuffler + SAY Uhh.. is it supposed to be doing that?? + DELAY 15 + PRESET /datum/preset_holoimage/engineer/ce + NAME Geysr Shorthalt + SAY See? Working as intended. Now, are we all ready? + DELAY 10 + PRESET /datum/preset_holoimage/engineer + NAME Jacob Ullman + SAY Is it supposed to be glowing like that? + DELAY 20 + SOUND explosion + + "} diff --git a/code/datums/hud.dm b/code/datums/hud.dm index a3c4884d49b0..6a7ecfcfffab 100644 --- a/code/datums/hud.dm +++ b/code/datums/hud.dm @@ -1,147 +1,147 @@ -/* HUD DATUMS */ - -GLOBAL_LIST_EMPTY(all_huds) - -//GLOBAL HUD LIST -GLOBAL_LIST_INIT(huds, list( - DATA_HUD_SECURITY_BASIC = new/datum/atom_hud/data/human/security/basic(), - DATA_HUD_SECURITY_ADVANCED = new/datum/atom_hud/data/human/security/advanced(), - DATA_HUD_MEDICAL_BASIC = new/datum/atom_hud/data/human/medical/basic(), - DATA_HUD_MEDICAL_ADVANCED = new/datum/atom_hud/data/human/medical/advanced(), - DATA_HUD_DIAGNOSTIC_BASIC = new/datum/atom_hud/data/diagnostic/basic(), - DATA_HUD_DIAGNOSTIC_ADVANCED = new/datum/atom_hud/data/diagnostic/advanced(), - DATA_HUD_ABDUCTOR = new/datum/atom_hud/abductor(), - DATA_HUD_SENTIENT_DISEASE = new/datum/atom_hud/sentient_disease(), - DATA_HUD_AI_DETECT = new/datum/atom_hud/ai_detector(), - DATA_HUD_FAN = new/datum/atom_hud/data/human/fan_hud(), - ANTAG_HUD_CULT = new/datum/atom_hud/antag(), - ANTAG_HUD_REV = new/datum/atom_hud/antag(), - ANTAG_HUD_OPS = new/datum/atom_hud/antag(), - ANTAG_HUD_WIZ = new/datum/atom_hud/antag(), - ANTAG_HUD_SHADOW = new/datum/atom_hud/antag(), - ANTAG_HUD_TRAITOR = new/datum/atom_hud/antag/hidden(), - ANTAG_HUD_NINJA = new/datum/atom_hud/antag/hidden(), - ANTAG_HUD_CHANGELING = new/datum/atom_hud/antag/hidden(), - ANTAG_HUD_ABDUCTOR = new/datum/atom_hud/antag/hidden(), - ANTAG_HUD_DEVIL = new/datum/atom_hud/antag(), - ANTAG_HUD_SINTOUCHED = new/datum/atom_hud/antag/hidden(), - ANTAG_HUD_SOULLESS = new/datum/atom_hud/antag/hidden(), - ANTAG_HUD_BROTHER = new/datum/atom_hud/antag/hidden(), - ANTAG_HUD_OBSESSED = new/datum/atom_hud/antag/hidden(), - ANTAG_HUD_FUGITIVE = new/datum/atom_hud/antag(), - ANTAG_HUD_GANGSTER = new/datum/atom_hud/antag/hidden(), - ANTAG_HUD_SPACECOP = new/datum/atom_hud/antag() - )) - -/datum/atom_hud - var/list/atom/hudatoms = list() //list of all atoms which display this hud - var/list/hudusers = list() //list with all mobs who can see the hud - var/list/hud_icons = list() //these will be the indexes for the atom's hud_list - - var/list/next_time_allowed = list() //mobs associated with the next time this hud can be added to them - var/list/queued_to_see = list() //mobs that have triggered the cooldown and are queued to see the hud, but do not yet - var/hud_exceptions = list() // huduser = list(ofatomswiththeirhudhidden) - aka everyone hates targeted invisiblity - -/datum/atom_hud/New() - GLOB.all_huds += src - -/datum/atom_hud/Destroy() - for(var/v in hudusers) - remove_hud_from(v) - for(var/v in hudatoms) - remove_from_hud(v) - GLOB.all_huds -= src - return ..() - -/datum/atom_hud/proc/remove_hud_from(mob/M) - if(!M || !hudusers[M]) - return - if (!--hudusers[M]) - hudusers -= M - if(queued_to_see[M]) - queued_to_see -= M - else - for(var/atom/A in hudatoms) - remove_from_single_hud(M, A) - -/datum/atom_hud/proc/remove_from_hud(atom/A) - if(!A) - return FALSE - for(var/mob/M in hudusers) - remove_from_single_hud(M, A) - hudatoms -= A - return TRUE - -/datum/atom_hud/proc/remove_from_single_hud(mob/M, atom/A) //unsafe, no sanity apart from client - if(!M || !M.client || !A) - return - for(var/i in hud_icons) - M.client.images -= A.hud_list[i] - -/datum/atom_hud/proc/add_hud_to(mob/M) - if(!M) - return - if(!hudusers[M]) - hudusers[M] = 1 - if(next_time_allowed[M] > world.time) - if(!queued_to_see[M]) - addtimer(CALLBACK(src, .proc/show_hud_images_after_cooldown, M), next_time_allowed[M] - world.time) - queued_to_see[M] = TRUE - else - next_time_allowed[M] = world.time + ADD_HUD_TO_COOLDOWN - for(var/atom/A in hudatoms) - add_to_single_hud(M, A) - else - hudusers[M]++ - -/datum/atom_hud/proc/hide_single_atomhud_from(hud_user,hidden_atom) - if(hudusers[hud_user]) - remove_from_single_hud(hud_user,hidden_atom) - if(!hud_exceptions[hud_user]) - hud_exceptions[hud_user] = list(hidden_atom) - else - hud_exceptions[hud_user] += hidden_atom - -/datum/atom_hud/proc/unhide_single_atomhud_from(hud_user,hidden_atom) - hud_exceptions[hud_user] -= hidden_atom - if(hudusers[hud_user]) - add_to_single_hud(hud_user,hidden_atom) - -/datum/atom_hud/proc/show_hud_images_after_cooldown(M) - if(queued_to_see[M]) - queued_to_see -= M - next_time_allowed[M] = world.time + ADD_HUD_TO_COOLDOWN - for(var/atom/A in hudatoms) - add_to_single_hud(M, A) - -/datum/atom_hud/proc/add_to_hud(atom/A) - if(!A) - return FALSE - hudatoms |= A - for(var/mob/M in hudusers) - if(!queued_to_see[M]) - add_to_single_hud(M, A) - return TRUE - -/datum/atom_hud/proc/add_to_single_hud(mob/M, atom/A) //unsafe, no sanity apart from client - if(!M || !M.client || !A) - return - for(var/i in hud_icons) - if(A.hud_list[i] && (!hud_exceptions[M] || !(A in hud_exceptions[M]))) - M.client.images |= A.hud_list[i] - -//MOB PROCS -/mob/proc/reload_huds() - for(var/datum/atom_hud/hud in GLOB.all_huds) - if(hud && hud.hudusers[src]) - for(var/atom/A in hud.hudatoms) - hud.add_to_single_hud(src, A) - -/mob/dead/new_player/reload_huds() - return - -/mob/proc/add_click_catcher() - client.screen += client.void - -/mob/dead/new_player/add_click_catcher() - return +/* HUD DATUMS */ + +GLOBAL_LIST_EMPTY(all_huds) + +//GLOBAL HUD LIST +GLOBAL_LIST_INIT(huds, list( + DATA_HUD_SECURITY_BASIC = new/datum/atom_hud/data/human/security/basic(), + DATA_HUD_SECURITY_ADVANCED = new/datum/atom_hud/data/human/security/advanced(), + DATA_HUD_MEDICAL_BASIC = new/datum/atom_hud/data/human/medical/basic(), + DATA_HUD_MEDICAL_ADVANCED = new/datum/atom_hud/data/human/medical/advanced(), + DATA_HUD_DIAGNOSTIC_BASIC = new/datum/atom_hud/data/diagnostic/basic(), + DATA_HUD_DIAGNOSTIC_ADVANCED = new/datum/atom_hud/data/diagnostic/advanced(), + DATA_HUD_ABDUCTOR = new/datum/atom_hud/abductor(), + DATA_HUD_SENTIENT_DISEASE = new/datum/atom_hud/sentient_disease(), + DATA_HUD_AI_DETECT = new/datum/atom_hud/ai_detector(), + DATA_HUD_FAN = new/datum/atom_hud/data/human/fan_hud(), + ANTAG_HUD_CULT = new/datum/atom_hud/antag(), + ANTAG_HUD_REV = new/datum/atom_hud/antag(), + ANTAG_HUD_OPS = new/datum/atom_hud/antag(), + ANTAG_HUD_WIZ = new/datum/atom_hud/antag(), + ANTAG_HUD_SHADOW = new/datum/atom_hud/antag(), + ANTAG_HUD_TRAITOR = new/datum/atom_hud/antag/hidden(), + ANTAG_HUD_NINJA = new/datum/atom_hud/antag/hidden(), + ANTAG_HUD_CHANGELING = new/datum/atom_hud/antag/hidden(), + ANTAG_HUD_ABDUCTOR = new/datum/atom_hud/antag/hidden(), + ANTAG_HUD_DEVIL = new/datum/atom_hud/antag(), + ANTAG_HUD_SINTOUCHED = new/datum/atom_hud/antag/hidden(), + ANTAG_HUD_SOULLESS = new/datum/atom_hud/antag/hidden(), + ANTAG_HUD_BROTHER = new/datum/atom_hud/antag/hidden(), + ANTAG_HUD_OBSESSED = new/datum/atom_hud/antag/hidden(), + ANTAG_HUD_FUGITIVE = new/datum/atom_hud/antag(), + ANTAG_HUD_GANGSTER = new/datum/atom_hud/antag/hidden(), + ANTAG_HUD_SPACECOP = new/datum/atom_hud/antag() + )) + +/datum/atom_hud + var/list/atom/hudatoms = list() //list of all atoms which display this hud + var/list/hudusers = list() //list with all mobs who can see the hud + var/list/hud_icons = list() //these will be the indexes for the atom's hud_list + + var/list/next_time_allowed = list() //mobs associated with the next time this hud can be added to them + var/list/queued_to_see = list() //mobs that have triggered the cooldown and are queued to see the hud, but do not yet + var/hud_exceptions = list() // huduser = list(ofatomswiththeirhudhidden) - aka everyone hates targeted invisiblity + +/datum/atom_hud/New() + GLOB.all_huds += src + +/datum/atom_hud/Destroy() + for(var/v in hudusers) + remove_hud_from(v) + for(var/v in hudatoms) + remove_from_hud(v) + GLOB.all_huds -= src + return ..() + +/datum/atom_hud/proc/remove_hud_from(mob/M) + if(!M || !hudusers[M]) + return + if (!--hudusers[M]) + hudusers -= M + if(queued_to_see[M]) + queued_to_see -= M + else + for(var/atom/A in hudatoms) + remove_from_single_hud(M, A) + +/datum/atom_hud/proc/remove_from_hud(atom/A) + if(!A) + return FALSE + for(var/mob/M in hudusers) + remove_from_single_hud(M, A) + hudatoms -= A + return TRUE + +/datum/atom_hud/proc/remove_from_single_hud(mob/M, atom/A) //unsafe, no sanity apart from client + if(!M || !M.client || !A) + return + for(var/i in hud_icons) + M.client.images -= A.hud_list[i] + +/datum/atom_hud/proc/add_hud_to(mob/M) + if(!M) + return + if(!hudusers[M]) + hudusers[M] = 1 + if(next_time_allowed[M] > world.time) + if(!queued_to_see[M]) + addtimer(CALLBACK(src, .proc/show_hud_images_after_cooldown, M), next_time_allowed[M] - world.time) + queued_to_see[M] = TRUE + else + next_time_allowed[M] = world.time + ADD_HUD_TO_COOLDOWN + for(var/atom/A in hudatoms) + add_to_single_hud(M, A) + else + hudusers[M]++ + +/datum/atom_hud/proc/hide_single_atomhud_from(hud_user,hidden_atom) + if(hudusers[hud_user]) + remove_from_single_hud(hud_user,hidden_atom) + if(!hud_exceptions[hud_user]) + hud_exceptions[hud_user] = list(hidden_atom) + else + hud_exceptions[hud_user] += hidden_atom + +/datum/atom_hud/proc/unhide_single_atomhud_from(hud_user,hidden_atom) + hud_exceptions[hud_user] -= hidden_atom + if(hudusers[hud_user]) + add_to_single_hud(hud_user,hidden_atom) + +/datum/atom_hud/proc/show_hud_images_after_cooldown(M) + if(queued_to_see[M]) + queued_to_see -= M + next_time_allowed[M] = world.time + ADD_HUD_TO_COOLDOWN + for(var/atom/A in hudatoms) + add_to_single_hud(M, A) + +/datum/atom_hud/proc/add_to_hud(atom/A) + if(!A) + return FALSE + hudatoms |= A + for(var/mob/M in hudusers) + if(!queued_to_see[M]) + add_to_single_hud(M, A) + return TRUE + +/datum/atom_hud/proc/add_to_single_hud(mob/M, atom/A) //unsafe, no sanity apart from client + if(!M || !M.client || !A) + return + for(var/i in hud_icons) + if(A.hud_list[i] && (!hud_exceptions[M] || !(A in hud_exceptions[M]))) + M.client.images |= A.hud_list[i] + +//MOB PROCS +/mob/proc/reload_huds() + for(var/datum/atom_hud/hud in GLOB.all_huds) + if(hud && hud.hudusers[src]) + for(var/atom/A in hud.hudatoms) + hud.add_to_single_hud(src, A) + +/mob/dead/new_player/reload_huds() + return + +/mob/proc/add_click_catcher() + client.screen += client.void + +/mob/dead/new_player/add_click_catcher() + return diff --git a/code/datums/map_config.dm b/code/datums/map_config.dm index 78bff840fe0e..33f2f9356300 100644 --- a/code/datums/map_config.dm +++ b/code/datums/map_config.dm @@ -1,143 +1,143 @@ -//used for holding information about unique properties of maps -//feed it json files that match the datum layout -//defaults to box -// -Cyberboss - -/datum/map_config - // Metadata - var/config_filename = "_maps/boxstation.json" - var/defaulted = TRUE // set to FALSE by LoadConfig() succeeding - // Config from maps.txt - var/config_max_users = 0 - var/config_min_users = 0 - var/voteweight = 1 - var/votable = FALSE - - // Config actually from the JSON - should default to Box - var/map_name = "Box Station" - var/map_path = "map_files/BoxStation" - var/map_file = "BoxStation.dmm" - - var/traits = null - var/space_ruin_levels = 7 - var/space_empty_levels = 1 - - var/minetype = "lavaland" - - var/allow_custom_shuttles = TRUE - var/shuttles = list( - "cargo" = "cargo_box", - "ferry" = "ferry_fancy", - "whiteship" = "whiteship_box", - "emergency" = "emergency_box") - -/proc/load_map_config(filename = "data/next_map.json", default_to_box, delete_after, error_if_missing = TRUE) - var/datum/map_config/config = new - if (default_to_box) - return config - if (!config.LoadConfig(filename, error_if_missing)) - qdel(config) - config = new /datum/map_config // Fall back to Box - if (delete_after) - fdel(filename) - return config - -#define CHECK_EXISTS(X) if(!istext(json[X])) { log_world("[##X] missing from json!"); return; } -/datum/map_config/proc/LoadConfig(filename, error_if_missing) - if(!fexists(filename)) - if(error_if_missing) - log_world("map_config not found: [filename]") - return - - var/json = file(filename) - if(!json) - log_world("Could not open map_config: [filename]") - return - - json = file2text(json) - if(!json) - log_world("map_config is not text: [filename]") - return - - json = json_decode(json) - if(!json) - log_world("map_config is not json: [filename]") - return - - config_filename = filename - - CHECK_EXISTS("map_name") - map_name = json["map_name"] - CHECK_EXISTS("map_path") - map_path = json["map_path"] - - map_file = json["map_file"] - // "map_file": "BoxStation.dmm" - if (istext(map_file)) - if (!fexists("_maps/[map_path]/[map_file]")) - log_world("Map file ([map_path]/[map_file]) does not exist!") - return - // "map_file": ["Lower.dmm", "Upper.dmm"] - else if (islist(map_file)) - for (var/file in map_file) - if (!fexists("_maps/[map_path]/[file]")) - log_world("Map file ([map_path]/[file]) does not exist!") - return - else - log_world("map_file missing from json!") - return - - if (islist(json["shuttles"])) - var/list/L = json["shuttles"] - for(var/key in L) - var/value = L[key] - shuttles[key] = value - else if ("shuttles" in json) - log_world("map_config shuttles is not a list!") - return - - traits = json["traits"] - // "traits": [{"Linkage": "Cross"}, {"Space Ruins": true}] - if (islist(traits)) - // "Station" is set by default, but it's assumed if you're setting - // traits you want to customize which level is cross-linked - for (var/level in traits) - if (!(ZTRAIT_STATION in level)) - level[ZTRAIT_STATION] = TRUE - // "traits": null or absent -> default - else if (!isnull(traits)) - log_world("map_config traits is not a list!") - return - - var/temp = json["space_ruin_levels"] - if (isnum(temp)) - space_ruin_levels = temp - else if (!isnull(temp)) - log_world("map_config space_ruin_levels is not a number!") - return - - temp = json["space_empty_levels"] - if (isnum(temp)) - space_empty_levels = temp - else if (!isnull(temp)) - log_world("map_config space_empty_levels is not a number!") - return - - if ("minetype" in json) - minetype = json["minetype"] - - allow_custom_shuttles = json["allow_custom_shuttles"] != FALSE - - defaulted = FALSE - return TRUE -#undef CHECK_EXISTS - -/datum/map_config/proc/GetFullMapPaths() - if (istext(map_file)) - return list("_maps/[map_path]/[map_file]") - . = list() - for (var/file in map_file) - . += "_maps/[map_path]/[file]" - -/datum/map_config/proc/MakeNextMap() - return config_filename == "data/next_map.json" || fcopy(config_filename, "data/next_map.json") +//used for holding information about unique properties of maps +//feed it json files that match the datum layout +//defaults to box +// -Cyberboss + +/datum/map_config + // Metadata + var/config_filename = "_maps/boxstation.json" + var/defaulted = TRUE // set to FALSE by LoadConfig() succeeding + // Config from maps.txt + var/config_max_users = 0 + var/config_min_users = 0 + var/voteweight = 1 + var/votable = FALSE + + // Config actually from the JSON - should default to Box + var/map_name = "Box Station" + var/map_path = "map_files/BoxStation" + var/map_file = "BoxStation.dmm" + + var/traits = null + var/space_ruin_levels = 7 + var/space_empty_levels = 1 + + var/minetype = "lavaland" + + var/allow_custom_shuttles = TRUE + var/shuttles = list( + "cargo" = "cargo_box", + "ferry" = "ferry_fancy", + "whiteship" = "whiteship_box", + "emergency" = "emergency_box") + +/proc/load_map_config(filename = "data/next_map.json", default_to_box, delete_after, error_if_missing = TRUE) + var/datum/map_config/config = new + if (default_to_box) + return config + if (!config.LoadConfig(filename, error_if_missing)) + qdel(config) + config = new /datum/map_config // Fall back to Box + if (delete_after) + fdel(filename) + return config + +#define CHECK_EXISTS(X) if(!istext(json[X])) { log_world("[##X] missing from json!"); return; } +/datum/map_config/proc/LoadConfig(filename, error_if_missing) + if(!fexists(filename)) + if(error_if_missing) + log_world("map_config not found: [filename]") + return + + var/json = file(filename) + if(!json) + log_world("Could not open map_config: [filename]") + return + + json = file2text(json) + if(!json) + log_world("map_config is not text: [filename]") + return + + json = json_decode(json) + if(!json) + log_world("map_config is not json: [filename]") + return + + config_filename = filename + + CHECK_EXISTS("map_name") + map_name = json["map_name"] + CHECK_EXISTS("map_path") + map_path = json["map_path"] + + map_file = json["map_file"] + // "map_file": "BoxStation.dmm" + if (istext(map_file)) + if (!fexists("_maps/[map_path]/[map_file]")) + log_world("Map file ([map_path]/[map_file]) does not exist!") + return + // "map_file": ["Lower.dmm", "Upper.dmm"] + else if (islist(map_file)) + for (var/file in map_file) + if (!fexists("_maps/[map_path]/[file]")) + log_world("Map file ([map_path]/[file]) does not exist!") + return + else + log_world("map_file missing from json!") + return + + if (islist(json["shuttles"])) + var/list/L = json["shuttles"] + for(var/key in L) + var/value = L[key] + shuttles[key] = value + else if ("shuttles" in json) + log_world("map_config shuttles is not a list!") + return + + traits = json["traits"] + // "traits": [{"Linkage": "Cross"}, {"Space Ruins": true}] + if (islist(traits)) + // "Station" is set by default, but it's assumed if you're setting + // traits you want to customize which level is cross-linked + for (var/level in traits) + if (!(ZTRAIT_STATION in level)) + level[ZTRAIT_STATION] = TRUE + // "traits": null or absent -> default + else if (!isnull(traits)) + log_world("map_config traits is not a list!") + return + + var/temp = json["space_ruin_levels"] + if (isnum(temp)) + space_ruin_levels = temp + else if (!isnull(temp)) + log_world("map_config space_ruin_levels is not a number!") + return + + temp = json["space_empty_levels"] + if (isnum(temp)) + space_empty_levels = temp + else if (!isnull(temp)) + log_world("map_config space_empty_levels is not a number!") + return + + if ("minetype" in json) + minetype = json["minetype"] + + allow_custom_shuttles = json["allow_custom_shuttles"] != FALSE + + defaulted = FALSE + return TRUE +#undef CHECK_EXISTS + +/datum/map_config/proc/GetFullMapPaths() + if (istext(map_file)) + return list("_maps/[map_path]/[map_file]") + . = list() + for (var/file in map_file) + . += "_maps/[map_path]/[file]" + +/datum/map_config/proc/MakeNextMap() + return config_filename == "data/next_map.json" || fcopy(config_filename, "data/next_map.json") diff --git a/code/datums/martial/boxing.dm b/code/datums/martial/boxing.dm index 107af2a60fd0..fb7a6293922c 100644 --- a/code/datums/martial/boxing.dm +++ b/code/datums/martial/boxing.dm @@ -45,10 +45,7 @@ to_chat(A, "You knock [D] out with a haymaker!") D.apply_effect(200,EFFECT_KNOCKDOWN,armor_block) D.SetSleeping(100) - D.forcesay(GLOB.hit_appends) log_combat(A, D, "knocked out (boxing) ") - else if(!(D.mobility_flags & MOBILITY_STAND)) - D.forcesay(GLOB.hit_appends) return 1 /obj/item/clothing/gloves/boxing diff --git a/code/datums/mind.dm b/code/datums/mind.dm index 42ef89e66c87..e2eadf69d81e 100644 --- a/code/datums/mind.dm +++ b/code/datums/mind.dm @@ -1,791 +1,816 @@ -/* Note from Carnie: - The way datum/mind stuff works has been changed a lot. - Minds now represent IC characters rather than following a client around constantly. - - Guidelines for using minds properly: - - - Never mind.transfer_to(ghost). The var/current and var/original of a mind must always be of type mob/living! - ghost.mind is however used as a reference to the ghost's corpse - - - When creating a new mob for an existing IC character (e.g. cloning a dead guy or borging a brain of a human) - the existing mind of the old mob should be transfered to the new mob like so: - - mind.transfer_to(new_mob) - - - You must not assign key= or ckey= after transfer_to() since the transfer_to transfers the client for you. - By setting key or ckey explicitly after transferring the mind with transfer_to you will cause bugs like DCing - the player. - - - IMPORTANT NOTE 2, if you want a player to become a ghost, use mob.ghostize() It does all the hard work for you. - - - When creating a new mob which will be a new IC character (e.g. putting a shade in a construct or randomly selecting - a ghost to become a xeno during an event). Simply assign the key or ckey like you've always done. - - new_mob.key = key - - The Login proc will handle making a new mind for that mobtype (including setting up stuff like mind.name). Simple! - However if you want that mind to have any special properties like being a traitor etc you will have to do that - yourself. - -*/ - -/datum/mind - var/key - var/name //replaces mob/var/original_name - var/ghostname //replaces name for observers name if set - var/mob/living/current - var/active = 0 - - var/memory - - var/assigned_role - var/special_role - var/list/restricted_roles = list() - var/list/datum/objective/objectives = list() // WaspStation Edit - Cryopods - - var/list/spell_list = list() // Wizard mode & "Give Spell" badmin button. - - var/linglink - var/datum/martial_art/martial_art - var/static/default_martial_art = new/datum/martial_art - var/miming = FALSE // Mime's vow of silence - var/list/antag_datums - var/antag_hud_icon_state = null //this mind's ANTAG_HUD should have this icon_state - var/datum/atom_hud/antag/antag_hud = null //this mind's antag HUD - var/damnation_type = 0 - var/datum/mind/soulOwner //who owns the soul. Under normal circumstances, this will point to src - var/hasSoul = TRUE // If false, renders the character unable to sell their soul. - var/holy_role = NONE //is this person a chaplain or admin role allowed to use bibles, Any rank besides 'NONE' allows for this. - - var/mob/living/enslaved_to //If this mind's master is another mob (i.e. adamantine golems) - var/datum/language_holder/language_holder - var/unconvertable = FALSE - var/late_joiner = FALSE - - var/last_death = 0 - - var/force_escaped = FALSE // Set by Into The Sunset command of the shuttle manipulator - - var/list/learned_recipes //List of learned recipe TYPES. - - ///Assoc list of skills - level - var/list/known_skills = list() - ///Assoc list of skills - exp - var/list/skill_experience = list() - - var/list/crew_objectives - -/datum/mind/New(key) - src.key = key - soulOwner = src - martial_art = default_martial_art - -/datum/mind/Destroy() - SSticker.minds -= src - if(islist(antag_datums)) - QDEL_LIST(antag_datums) - return ..() - -/datum/mind/proc/get_language_holder() - if(!language_holder) - language_holder = new (src) - return language_holder - -/datum/mind/proc/transfer_to(mob/new_character, force_key_move = 0) - if(current) // remove ourself from our old body's mind variable - current.mind = null - UnregisterSignal(current, COMSIG_MOB_DEATH) - SStgui.on_transfer(current, new_character) - - if(key) - if(new_character.key != key) //if we're transferring into a body with a key associated which is not ours - new_character.ghostize(1) //we'll need to ghostize so that key isn't mobless. - else - key = new_character.key - - if(new_character.mind) //disassociate any mind currently in our new body's mind variable - new_character.mind.current = null - - var/datum/atom_hud/antag/hud_to_transfer = antag_hud//we need this because leave_hud() will clear this list - var/mob/living/old_current = current - if(current) - current.transfer_observers_to(new_character) //transfer anyone observing the old character to the new one - current = new_character //associate ourself with our new body - new_character.mind = src //and associate our new body with ourself - for(var/a in antag_datums) //Makes sure all antag datums effects are applied in the new body - var/datum/antagonist/A = a - A.on_body_transfer(old_current, current) - if(iscarbon(new_character)) - var/mob/living/carbon/C = new_character - C.last_mind = src - transfer_antag_huds(hud_to_transfer) //inherit the antag HUD - transfer_actions(new_character) - transfer_martial_arts(new_character) - RegisterSignal(new_character, COMSIG_MOB_DEATH, .proc/set_death_time) - if(active || force_key_move) - new_character.key = key //now transfer the key to link the client to our new body - current.update_atom_languages() - - -///Adjust experience of a specific skill -/datum/mind/proc/adjust_experience(skill, amt, silent = FALSE) - var/datum/skill/S = GetSkillRef(skill) - skill_experience[S] = max(0, skill_experience[S] + amt) //Prevent going below 0 - var/old_level = known_skills[S] - switch(skill_experience[S]) - if(SKILL_EXP_LEGENDARY to INFINITY) - known_skills[S] = SKILL_LEVEL_LEGENDARY - if(SKILL_EXP_MASTER to SKILL_EXP_LEGENDARY) - known_skills[S] = SKILL_LEVEL_MASTER - if(SKILL_EXP_EXPERT to SKILL_EXP_MASTER) - known_skills[S] = SKILL_LEVEL_EXPERT - if(SKILL_EXP_JOURNEYMAN to SKILL_EXP_EXPERT) - known_skills[S] = SKILL_LEVEL_JOURNEYMAN - if(SKILL_EXP_APPRENTICE to SKILL_EXP_JOURNEYMAN) - known_skills[S] = SKILL_LEVEL_APPRENTICE - if(SKILL_EXP_NOVICE to SKILL_EXP_APPRENTICE) - known_skills[S] = SKILL_LEVEL_NOVICE - if(0 to SKILL_EXP_NOVICE) - known_skills[S] = SKILL_LEVEL_NONE - if(isnull(old_level) || known_skills[S] == old_level) - return //same level or we just started earning xp towards the first level. - if(silent) - return - if(known_skills[S] >= old_level) - S.level_gained(src, known_skills[S], old_level) - else - S.level_lost(src, known_skills[S], old_level) - -///Gets the skill's singleton and returns the result of its get_skill_modifier -/datum/mind/proc/get_skill_modifier(skill, modifier) - var/datum/skill/S = GetSkillRef(skill) - return S.get_skill_modifier(modifier, known_skills[S] || SKILL_LEVEL_NONE) - -/datum/mind/proc/get_skill_level(skill) - var/datum/skill/S = GetSkillRef(skill) - return known_skills[S] || SKILL_LEVEL_NONE - -/datum/mind/proc/print_levels(user) - var/list/shown_skills = list() - for(var/i in known_skills) - if(known_skills[i]) //Do we actually have a level in this? - shown_skills += i - if(!length(shown_skills)) - to_chat(user, "You don't seem to have any particularly outstanding skills.") - return - var/msg = "" - msg += "*---------*\nYour skills\n" - for(var/i in shown_skills) - var/datum/skill/S = i - msg += "[i] - [SSskills.level_names[known_skills[S]]]\n" - msg += "" - to_chat(user, msg) - - -/datum/mind/proc/set_death_time() - last_death = world.time - -/datum/mind/proc/store_memory(new_text) - var/newlength = length_char(memory) + length_char(new_text) - if (newlength > MAX_MESSAGE_LEN * 100) - memory = copytext_char(memory, -newlength-MAX_MESSAGE_LEN * 100) - memory += "[new_text]
    " - -/datum/mind/proc/wipe_memory() - memory = null - -// Datum antag mind procs -/datum/mind/proc/add_antag_datum(datum_type_or_instance, team) - if(!datum_type_or_instance) - return - var/datum/antagonist/A - if(!ispath(datum_type_or_instance)) - A = datum_type_or_instance - if(!istype(A)) - return - else - A = new datum_type_or_instance() - //Choose snowflake variation if antagonist handles it - var/datum/antagonist/S = A.specialization(src) - if(S && S != A) - qdel(A) - A = S - if(!A.can_be_owned(src)) - qdel(A) - return - A.owner = src - LAZYADD(antag_datums, A) - A.create_team(team) - var/datum/team/antag_team = A.get_team() - if(antag_team) - antag_team.add_member(src) - A.on_gain() - log_game("[key_name(src)] has gained antag datum [A.name]([A.type])") - return A - -/datum/mind/proc/remove_antag_datum(datum_type) - if(!datum_type) - return - var/datum/antagonist/A = has_antag_datum(datum_type) - if(A) - A.on_removal() - return TRUE - - -/datum/mind/proc/remove_all_antag_datums() //For the Lazy amongst us. - for(var/a in antag_datums) - var/datum/antagonist/A = a - A.on_removal() - -/datum/mind/proc/has_antag_datum(datum_type, check_subtypes = TRUE) - if(!datum_type) - return - . = FALSE - for(var/a in antag_datums) - var/datum/antagonist/A = a - if(check_subtypes && istype(A, datum_type)) - return A - else if(A.type == datum_type) - return A - -/* - Removes antag type's references from a mind. - objectives, uplinks, powers etc are all handled. -*/ - -/datum/mind/proc/remove_changeling() - var/datum/antagonist/changeling/C = has_antag_datum(/datum/antagonist/changeling) - if(C) - remove_antag_datum(/datum/antagonist/changeling) - special_role = null - -/datum/mind/proc/remove_traitor() - remove_antag_datum(/datum/antagonist/traitor) - -/datum/mind/proc/remove_brother() - if(src in SSticker.mode.brothers) - remove_antag_datum(/datum/antagonist/brother) - -/datum/mind/proc/remove_nukeop() - var/datum/antagonist/nukeop/nuke = has_antag_datum(/datum/antagonist/nukeop,TRUE) - if(nuke) - remove_antag_datum(nuke.type) - special_role = null - -/datum/mind/proc/remove_wizard() - remove_antag_datum(/datum/antagonist/wizard) - special_role = null - -/datum/mind/proc/remove_cultist() - if(src in SSticker.mode.cult) - SSticker.mode.remove_cultist(src, 0, 0) - special_role = null - remove_antag_equip() - -/datum/mind/proc/remove_rev() - var/datum/antagonist/rev/rev = has_antag_datum(/datum/antagonist/rev) - if(rev) - remove_antag_datum(rev.type) - special_role = null - - -/datum/mind/proc/remove_antag_equip() - var/list/Mob_Contents = current.get_contents() - for(var/obj/item/I in Mob_Contents) - var/datum/component/uplink/O = I.GetComponent(/datum/component/uplink) //Todo make this reset signal - if(O) - O.unlock_code = null - -/datum/mind/proc/remove_all_antag() //For the Lazy amongst us. - remove_changeling() - remove_traitor() - remove_nukeop() - remove_wizard() - remove_cultist() - remove_rev() - -/datum/mind/proc/equip_traitor(employer = "The Syndicate", silent = FALSE, datum/antagonist/uplink_owner) - if(!current) - return - var/mob/living/carbon/human/traitor_mob = current - if (!istype(traitor_mob)) - return - - var/list/all_contents = traitor_mob.GetAllContents() - var/obj/item/pda/PDA = locate() in all_contents - var/obj/item/radio/R = locate() in all_contents - var/obj/item/pen/P - - if (PDA) // Prioritize PDA pen, otherwise the pocket protector pens will be chosen, which causes numerous ahelps about missing uplink - P = locate() in PDA - if (!P) // If we couldn't find a pen in the PDA, or we didn't even have a PDA, do it the old way - P = locate() in all_contents - - var/obj/item/uplink_loc - - if(traitor_mob.client && traitor_mob.client.prefs) - switch(traitor_mob.client.prefs.uplink_spawn_loc) - if(UPLINK_PDA) - uplink_loc = PDA - if(!uplink_loc) - uplink_loc = R - if(!uplink_loc) - uplink_loc = P - if(UPLINK_RADIO) - uplink_loc = R - if(!uplink_loc) - uplink_loc = PDA - if(!uplink_loc) - uplink_loc = P - if(UPLINK_PEN) - uplink_loc = P - - if(!uplink_loc) // We've looked everywhere, let's just give you a pen - if(istype(traitor_mob.back,/obj/item/storage)) //ok buddy you better have a backpack! - P = new /obj/item/pen(traitor_mob.back) - else - P = new /obj/item/pen(traitor_mob.loc) - traitor_mob.put_in_hands(P) // I hope you don't have arms and your traitor pen gets stolen for all this trouble you've caused. - uplink_loc = P - - if (!uplink_loc) - if(!silent) - to_chat(traitor_mob, "Unfortunately, [employer] wasn't able to get you an Uplink.") - . = 0 - else - . = uplink_loc - var/datum/component/uplink/U = uplink_loc.AddComponent(/datum/component/uplink, traitor_mob.key) - if(!U) - CRASH("Uplink creation failed.") - U.setup_unlock_code() - if(!silent) - if(uplink_loc == R) - to_chat(traitor_mob, "[employer] has cunningly disguised a Syndicate Uplink as your [R.name]. Simply dial the frequency [format_frequency(U.unlock_code)] to unlock its hidden features.") - else if(uplink_loc == PDA) - to_chat(traitor_mob, "[employer] has cunningly disguised a Syndicate Uplink as your [PDA.name]. Simply enter the code \"[U.unlock_code]\" into the ringtone select to unlock its hidden features.") - else if(uplink_loc == P) - to_chat(traitor_mob, "[employer] has cunningly disguised a Syndicate Uplink as your [P.name]. Simply twist the top of the pen [english_list(U.unlock_code)] from its starting position to unlock its hidden features.") - - if(uplink_owner) - uplink_owner.antag_memory += U.unlock_note + "
    " - else - traitor_mob.mind.store_memory(U.unlock_note) - - -//Link a new mobs mind to the creator of said mob. They will join any team they are currently on, and will only switch teams when their creator does. - -/datum/mind/proc/enslave_mind_to_creator(mob/living/creator) - if(iscultist(creator)) - SSticker.mode.add_cultist(src) - - else if(is_revolutionary(creator)) - var/datum/antagonist/rev/converter = creator.mind.has_antag_datum(/datum/antagonist/rev,TRUE) - converter.add_revolutionary(src,FALSE) - - else if(is_nuclear_operative(creator)) - var/datum/antagonist/nukeop/converter = creator.mind.has_antag_datum(/datum/antagonist/nukeop,TRUE) - var/datum/antagonist/nukeop/N = new() - N.send_to_spawnpoint = FALSE - N.nukeop_outfit = null - add_antag_datum(N,converter.nuke_team) - - - enslaved_to = creator - - current.faction |= creator.faction - creator.faction |= current.faction - - if(creator.mind.special_role) - message_admins("[ADMIN_LOOKUPFLW(current)] has been created by [ADMIN_LOOKUPFLW(creator)], an antagonist.") - to_chat(current, "Despite your creator's current allegiances, your true master remains [creator.real_name]. If their loyalties change, so do yours. This will never change unless your creator's body is destroyed.") - -/datum/mind/proc/show_memory(mob/recipient, window=1) - if(!recipient) - recipient = current - var/output = "[current.real_name]'s Memories:
    " - output += memory - - - var/list/all_objectives = list() - for(var/datum/antagonist/A in antag_datums) - output += A.antag_memory - all_objectives |= A.objectives - - if(all_objectives.len) - output += "Objectives:" - var/obj_count = 1 - for(var/datum/objective/objective in all_objectives) - output += "
    Objective #[obj_count++]: [objective.explanation_text]" - var/list/datum/mind/other_owners = objective.get_owners() - src - if(other_owners.len) - output += "
      " - for(var/datum/mind/M in other_owners) - output += "
    • Conspirator: [M.name]
    • " - output += "
    " - - if(window) - recipient << browse(output,"window=memory") - else if(all_objectives.len || memory) - to_chat(recipient, "[output]") - -/datum/mind/Topic(href, href_list) - if(!check_rights(R_ADMIN)) - return - - var/self_antagging = usr == current - - if(href_list["add_antag"]) - add_antag_wrapper(text2path(href_list["add_antag"]),usr) - if(href_list["remove_antag"]) - var/datum/antagonist/A = locate(href_list["remove_antag"]) in antag_datums - if(!istype(A)) - to_chat(usr,"Invalid antagonist ref to be removed.") - return - A.admin_remove(usr) - - if (href_list["role_edit"]) - var/new_role = input("Select new role", "Assigned role", assigned_role) as null|anything in sortList(get_all_jobs()) - if (!new_role) - return - assigned_role = new_role - - else if (href_list["memory_edit"]) - var/new_memo = stripped_multiline_input(usr, "Write new memory", "Memory", memory, MAX_MESSAGE_LEN) - if (isnull(new_memo)) - return - memory = new_memo - - else if (href_list["obj_edit"] || href_list["obj_add"]) - var/objective_pos //Edited objectives need to keep same order in antag objective list - var/def_value - var/datum/antagonist/target_antag - var/datum/objective/old_objective //The old objective we're replacing/editing - var/datum/objective/new_objective //New objective we're be adding - - if(href_list["obj_edit"]) - for(var/datum/antagonist/A in antag_datums) - old_objective = locate(href_list["obj_edit"]) in A.objectives - if(old_objective) - target_antag = A - objective_pos = A.objectives.Find(old_objective) - break - if(!old_objective) - to_chat(usr,"Invalid objective.") - return - else - if(href_list["target_antag"]) - var/datum/antagonist/X = locate(href_list["target_antag"]) in antag_datums - if(X) - target_antag = X - if(!target_antag) - switch(antag_datums.len) - if(0) - target_antag = add_antag_datum(/datum/antagonist/custom) - if(1) - target_antag = antag_datums[1] - else - var/datum/antagonist/target = input("Which antagonist gets the objective:", "Antagonist", "(new custom antag)") as null|anything in sortList(antag_datums) + "(new custom antag)" - if (QDELETED(target)) - return - else if(target == "(new custom antag)") - target_antag = add_antag_datum(/datum/antagonist/custom) - else - target_antag = target - - if(!GLOB.admin_objective_list) - generate_admin_objective_list() - - if(old_objective) - if(old_objective.name in GLOB.admin_objective_list) - def_value = old_objective.name - - var/selected_type = input("Select objective type:", "Objective type", def_value) as null|anything in GLOB.admin_objective_list - selected_type = GLOB.admin_objective_list[selected_type] - if (!selected_type) - return - - if(!old_objective) - //Add new one - new_objective = new selected_type - new_objective.owner = src - new_objective.admin_edit(usr) - target_antag.objectives += new_objective - message_admins("[key_name_admin(usr)] added a new objective for [current]: [new_objective.explanation_text]") - log_admin("[key_name(usr)] added a new objective for [current]: [new_objective.explanation_text]") - else - if(old_objective.type == selected_type) - //Edit the old - old_objective.admin_edit(usr) - new_objective = old_objective - else - //Replace the old - new_objective = new selected_type - new_objective.owner = src - new_objective.admin_edit(usr) - target_antag.objectives -= old_objective - target_antag.objectives.Insert(objective_pos, new_objective) - message_admins("[key_name_admin(usr)] edited [current]'s objective to [new_objective.explanation_text]") - log_admin("[key_name(usr)] edited [current]'s objective to [new_objective.explanation_text]") - - else if (href_list["obj_delete"]) - var/datum/objective/objective - for(var/datum/antagonist/A in antag_datums) - objective = locate(href_list["obj_delete"]) in A.objectives - if(istype(objective)) - A.objectives -= objective - break - if(!objective) - to_chat(usr,"Invalid objective.") - return - //qdel(objective) Needs cleaning objective destroys - message_admins("[key_name_admin(usr)] removed an objective for [current]: [objective.explanation_text]") - log_admin("[key_name(usr)] removed an objective for [current]: [objective.explanation_text]") - - else if(href_list["obj_completed"]) - var/datum/objective/objective - for(var/datum/antagonist/A in antag_datums) - objective = locate(href_list["obj_completed"]) in A.objectives - if(istype(objective)) - objective = objective - break - if(!objective) - to_chat(usr,"Invalid objective.") - return - objective.completed = !objective.completed - log_admin("[key_name(usr)] toggled the win state for [current]'s objective: [objective.explanation_text]") - - else if (href_list["silicon"]) - switch(href_list["silicon"]) - if("unemag") - var/mob/living/silicon/robot/R = current - if (istype(R)) - R.SetEmagged(0) - message_admins("[key_name_admin(usr)] has unemag'ed [R].") - log_admin("[key_name(usr)] has unemag'ed [R].") - - if("unemagcyborgs") - if(isAI(current)) - var/mob/living/silicon/ai/ai = current - for (var/mob/living/silicon/robot/R in ai.connected_robots) - R.SetEmagged(0) - message_admins("[key_name_admin(usr)] has unemag'ed [ai]'s Cyborgs.") - log_admin("[key_name(usr)] has unemag'ed [ai]'s Cyborgs.") - - else if (href_list["common"]) - switch(href_list["common"]) - if("undress") - for(var/obj/item/W in current) - current.dropItemToGround(W, TRUE) //The 1 forces all items to drop, since this is an admin undress. - if("takeuplink") - take_uplink() - memory = null//Remove any memory they may have had. - log_admin("[key_name(usr)] removed [current]'s uplink.") - if("crystals") - if(check_rights(R_FUN, 0)) - var/datum/component/uplink/U = find_syndicate_uplink() - if(U) - var/crystals = input("Amount of telecrystals for [key]","Syndicate uplink", U.telecrystals) as null | num - if(!isnull(crystals)) - U.telecrystals = crystals - message_admins("[key_name_admin(usr)] changed [current]'s telecrystal count to [crystals].") - log_admin("[key_name(usr)] changed [current]'s telecrystal count to [crystals].") - if("uplink") - if(!equip_traitor()) - to_chat(usr, "Equipping a syndicate failed!") - log_admin("[key_name(usr)] tried and failed to give [current] an uplink.") - else - log_admin("[key_name(usr)] gave [current] an uplink.") - - else if (href_list["obj_announce"]) - announce_objectives() - - //Something in here might have changed your mob - if(self_antagging && (!usr || !usr.client) && current.client) - usr = current - traitor_panel() - - -/datum/mind/proc/get_all_objectives() - var/list/all_objectives = list() - for(var/datum/antagonist/A in antag_datums) - all_objectives |= A.objectives - return all_objectives - -/datum/mind/proc/announce_objectives() - var/obj_count = 1 - to_chat(current, "Your current objectives:") - for(var/objective in get_all_objectives()) - var/datum/objective/O = objective - to_chat(current, "Objective #[obj_count]: [O.explanation_text]") - obj_count++ - -/datum/mind/proc/find_syndicate_uplink() - var/list/L = current.GetAllContents() - for (var/i in L) - var/atom/movable/I = i - . = I.GetComponent(/datum/component/uplink) - if(.) - break - -/datum/mind/proc/take_uplink() - qdel(find_syndicate_uplink()) - -/datum/mind/proc/make_Traitor() - if(!(has_antag_datum(/datum/antagonist/traitor))) - add_antag_datum(/datum/antagonist/traitor) - -/datum/mind/proc/make_Contractor_Support() - if(!(has_antag_datum(/datum/antagonist/traitor/contractor_support))) - add_antag_datum(/datum/antagonist/traitor/contractor_support) - -/datum/mind/proc/make_Changeling() - var/datum/antagonist/changeling/C = has_antag_datum(/datum/antagonist/changeling) - if(!C) - C = add_antag_datum(/datum/antagonist/changeling) - special_role = ROLE_CHANGELING - return C - -/datum/mind/proc/make_Wizard() - if(!has_antag_datum(/datum/antagonist/wizard)) - special_role = ROLE_WIZARD - assigned_role = ROLE_WIZARD - add_antag_datum(/datum/antagonist/wizard) - - -/datum/mind/proc/make_Cultist() - if(!has_antag_datum(/datum/antagonist/cult,TRUE)) - SSticker.mode.add_cultist(src,FALSE,equip=TRUE) - special_role = ROLE_CULTIST - to_chat(current, "You catch a glimpse of the Realm of Nar'Sie, The Geometer of Blood. You now see how flimsy your world is, you see that it should be open to the knowledge of Nar'Sie.") - to_chat(current, "Assist your new brethren in their dark dealings. Their goal is yours, and yours is theirs. You serve the Dark One above all else. Bring It back.") - -/datum/mind/proc/make_Rev() - var/datum/antagonist/rev/head/head = new() - head.give_flash = TRUE - head.give_hud = TRUE - add_antag_datum(head) - special_role = ROLE_REV_HEAD - -/datum/mind/proc/AddSpell(obj/effect/proc_holder/spell/S) - spell_list += S - S.action.Grant(current) - -/datum/mind/proc/owns_soul() - return soulOwner == src - -//To remove a specific spell from a mind -/datum/mind/proc/RemoveSpell(obj/effect/proc_holder/spell/spell) - if(!spell) - return - for(var/X in spell_list) - var/obj/effect/proc_holder/spell/S = X - if(istype(S, spell)) - spell_list -= S - qdel(S) - -/datum/mind/proc/RemoveAllSpells() - for(var/obj/effect/proc_holder/S in spell_list) - RemoveSpell(S) - -/datum/mind/proc/transfer_martial_arts(mob/living/new_character) - if(!ishuman(new_character)) - return - if(martial_art) - if(martial_art.base) //Is the martial art temporary? - martial_art.remove(new_character) - else - martial_art.teach(new_character) - -/datum/mind/proc/transfer_actions(mob/living/new_character) - if(current && current.actions) - for(var/datum/action/A in current.actions) - A.Grant(new_character) - transfer_mindbound_actions(new_character) - -/datum/mind/proc/transfer_mindbound_actions(mob/living/new_character) - for(var/X in spell_list) - var/obj/effect/proc_holder/spell/S = X - S.action.Grant(new_character) - -/datum/mind/proc/disrupt_spells(delay, list/exceptions = New()) - for(var/X in spell_list) - var/obj/effect/proc_holder/spell/S = X - for(var/type in exceptions) - if(istype(S, type)) - continue - S.charge_counter = delay - S.updateButtonIcon() - INVOKE_ASYNC(S, /obj/effect/proc_holder/spell.proc/start_recharge) - -/datum/mind/proc/get_ghost(even_if_they_cant_reenter, ghosts_with_clients) - for(var/mob/dead/observer/G in (ghosts_with_clients ? GLOB.player_list : GLOB.dead_mob_list)) - if(G.mind == src) - if(G.can_reenter_corpse || even_if_they_cant_reenter) - return G - break - -/datum/mind/proc/grab_ghost(force) - var/mob/dead/observer/G = get_ghost(even_if_they_cant_reenter = force) - . = G - if(G) - G.reenter_corpse() - - -/datum/mind/proc/has_objective(objective_type) - for(var/datum/antagonist/A in antag_datums) - for(var/O in A.objectives) - if(istype(O,objective_type)) - return TRUE - -/mob/proc/sync_mind() - mind_initialize() //updates the mind (or creates and initializes one if one doesn't exist) - mind.active = 1 //indicates that the mind is currently synced with a client - -/datum/mind/proc/has_martialart(string) - if(martial_art && martial_art.id == string) - return martial_art - return FALSE - -/mob/dead/new_player/sync_mind() - return - -/mob/dead/observer/sync_mind() - return - -//Initialisation procs -/mob/proc/mind_initialize() - if(mind) - mind.key = key - - else - mind = new /datum/mind(key) - SSticker.minds += mind - if(!mind.name) - mind.name = real_name - mind.current = src - -/mob/living/carbon/mind_initialize() - ..() - last_mind = mind - -//HUMAN -/mob/living/carbon/human/mind_initialize() - ..() - if(!mind.assigned_role) - mind.assigned_role = "Unassigned" //default - -//AI -/mob/living/silicon/ai/mind_initialize() - ..() - mind.assigned_role = "AI" - -//BORG -/mob/living/silicon/robot/mind_initialize() - ..() - mind.assigned_role = "Cyborg" - -//PAI -/mob/living/silicon/pai/mind_initialize() - ..() - mind.assigned_role = ROLE_PAI - mind.special_role = "" +/* Note from Carnie: + The way datum/mind stuff works has been changed a lot. + Minds now represent IC characters rather than following a client around constantly. + + Guidelines for using minds properly: + + - Never mind.transfer_to(ghost). The var/current and var/original of a mind must always be of type mob/living! + ghost.mind is however used as a reference to the ghost's corpse + + - When creating a new mob for an existing IC character (e.g. cloning a dead guy or borging a brain of a human) + the existing mind of the old mob should be transfered to the new mob like so: + + mind.transfer_to(new_mob) + + - You must not assign key= or ckey= after transfer_to() since the transfer_to transfers the client for you. + By setting key or ckey explicitly after transferring the mind with transfer_to you will cause bugs like DCing + the player. + + - IMPORTANT NOTE 2, if you want a player to become a ghost, use mob.ghostize() It does all the hard work for you. + + - When creating a new mob which will be a new IC character (e.g. putting a shade in a construct or randomly selecting + a ghost to become a xeno during an event). Simply assign the key or ckey like you've always done. + + new_mob.key = key + + The Login proc will handle making a new mind for that mobtype (including setting up stuff like mind.name). Simple! + However if you want that mind to have any special properties like being a traitor etc you will have to do that + yourself. + +*/ + +/datum/mind + var/key + var/name //replaces mob/var/original_name + var/ghostname //replaces name for observers name if set + var/mob/living/current + var/active = 0 + + var/memory + + var/assigned_role + var/special_role + var/list/restricted_roles = list() + var/list/datum/objective/objectives = list() // WaspStation Edit - Cryopods + + var/list/spell_list = list() // Wizard mode & "Give Spell" badmin button. + + var/linglink + var/datum/martial_art/martial_art + var/static/default_martial_art = new/datum/martial_art + var/miming = FALSE // Mime's vow of silence + var/list/antag_datums + var/antag_hud_icon_state = null //this mind's ANTAG_HUD should have this icon_state + var/datum/atom_hud/antag/antag_hud = null //this mind's antag HUD + var/damnation_type = 0 + var/datum/mind/soulOwner //who owns the soul. Under normal circumstances, this will point to src + var/hasSoul = TRUE // If false, renders the character unable to sell their soul. + var/holy_role = NONE //is this person a chaplain or admin role allowed to use bibles, Any rank besides 'NONE' allows for this. + + var/mob/living/enslaved_to //If this mind's master is another mob (i.e. adamantine golems) + var/datum/language_holder/language_holder + var/unconvertable = FALSE + var/late_joiner = FALSE + + var/last_death = 0 + + var/force_escaped = FALSE // Set by Into The Sunset command of the shuttle manipulator + + var/list/learned_recipes //List of learned recipe TYPES. + + ///List of skills the user has recieved a reward for. Should not be used to keep track of currently known skills. Lazy list because it shouldnt be filled often + var/list/skills_rewarded + ///Assoc list of skills. Use SKILL_LVL to access level, and SKILL_EXP to access skill's exp. + var/list/known_skills = list() + + ///Wasp edit - Crew objectives variable, stores crew objective datums + var/list/crew_objectives + + +/datum/mind/New(key) + src.key = key + soulOwner = src + martial_art = default_martial_art + init_known_skills() + +/datum/mind/Destroy() + SSticker.minds -= src + if(islist(antag_datums)) + QDEL_LIST(antag_datums) + return ..() + +/datum/mind/proc/get_language_holder() + if(!language_holder) + language_holder = new (src) + return language_holder + +/datum/mind/proc/transfer_to(mob/new_character, force_key_move = 0) + if(current) // remove ourself from our old body's mind variable + current.mind = null + UnregisterSignal(current, COMSIG_MOB_DEATH) + SStgui.on_transfer(current, new_character) + + if(key) + if(new_character.key != key) //if we're transferring into a body with a key associated which is not ours + new_character.ghostize(1) //we'll need to ghostize so that key isn't mobless. + else + key = new_character.key + + if(new_character.mind) //disassociate any mind currently in our new body's mind variable + new_character.mind.current = null + + var/datum/atom_hud/antag/hud_to_transfer = antag_hud//we need this because leave_hud() will clear this list + var/mob/living/old_current = current + if(current) + current.transfer_observers_to(new_character) //transfer anyone observing the old character to the new one + current = new_character //associate ourself with our new body + new_character.mind = src //and associate our new body with ourself + for(var/a in antag_datums) //Makes sure all antag datums effects are applied in the new body + var/datum/antagonist/A = a + A.on_body_transfer(old_current, current) + if(iscarbon(new_character)) + var/mob/living/carbon/C = new_character + C.last_mind = src + transfer_antag_huds(hud_to_transfer) //inherit the antag HUD + transfer_actions(new_character) + transfer_martial_arts(new_character) + RegisterSignal(new_character, COMSIG_MOB_DEATH, .proc/set_death_time) + if(active || force_key_move) + new_character.key = key //now transfer the key to link the client to our new body + current.update_atom_languages() + +/datum/mind/proc/init_known_skills() + for (var/type in GLOB.skill_types) + known_skills[type] = list(SKILL_LEVEL_NONE, 0) + +///Return the amount of EXP needed to go to the next level. Returns 0 if max level +/datum/mind/proc/exp_needed_to_level_up(skill) + var/lvl = update_skill_level(skill) + if (lvl >= length(SKILL_EXP_LIST)) //If we're already past the last exp threshold + return 0 + return SKILL_EXP_LIST[lvl+1] - known_skills[skill][SKILL_EXP] + +///Adjust experience of a specific skill +/datum/mind/proc/adjust_experience(skill, amt, silent = FALSE, force_old_level = 0) + var/datum/skill/S = GetSkillRef(skill) + var/old_level = force_old_level ? force_old_level : known_skills[skill][SKILL_LVL] //Get current level of the S skill + known_skills[skill][SKILL_EXP] = max(0, known_skills[skill][SKILL_EXP] + amt) //Update exp. Prevent going below 0 + known_skills[skill][SKILL_LVL] = update_skill_level(skill)//Check what the current skill level is based on that skill's exp + if(silent) + return + if(known_skills[skill][SKILL_LVL] > old_level) + S.level_gained(src, known_skills[skill][SKILL_LVL], old_level) + else if(known_skills[skill][SKILL_LVL] < old_level) + S.level_lost(src, known_skills[skill][SKILL_LVL], old_level) + +///Set experience of a specific skill to a number +/datum/mind/proc/set_experience(skill, amt, silent = FALSE) + var/old_level = known_skills[skill][SKILL_EXP] + known_skills[skill][SKILL_EXP] = amt + adjust_experience(skill, 0, silent, old_level) //Make a call to adjust_experience to handle updating level + +///Set level of a specific skill +/datum/mind/proc/set_level(skill, newlevel, silent = FALSE) + var/oldlevel = get_skill_level(skill) + var/difference = SKILL_EXP_LIST[newlevel] - SKILL_EXP_LIST[oldlevel] + adjust_experience(skill, difference, silent) + +///Check what the current skill level is based on that skill's exp +/datum/mind/proc/update_skill_level(skill) + var/i = 0 + for (var/exp in SKILL_EXP_LIST) + i ++ + if (known_skills[skill][SKILL_EXP] >= SKILL_EXP_LIST[i]) + continue + return i - 1 //Return level based on the last exp requirement that we were greater than + return i //If we had greater EXP than even the last exp threshold, we return the last level + +///Gets the skill's singleton and returns the result of its get_skill_modifier +/datum/mind/proc/get_skill_modifier(skill, modifier) + var/datum/skill/S = GetSkillRef(skill) + return S.get_skill_modifier(modifier, known_skills[skill][SKILL_LVL]) + +///Gets the player's current level number from the relevant skill +/datum/mind/proc/get_skill_level(skill) + return known_skills[skill][SKILL_LVL] + +///Gets the player's current exp from the relevant skill +/datum/mind/proc/get_skill_exp(skill) + return known_skills[skill][SKILL_EXP] + +/datum/mind/proc/get_skill_level_name(skill) + var/level = get_skill_level(skill) + return SSskills.level_names[level] + +/datum/mind/proc/print_levels(user) + var/list/shown_skills = list() + for(var/i in known_skills) + if(known_skills[i][SKILL_LVL] > SKILL_LEVEL_NONE) //Do we actually have a level in this? + shown_skills += i + if(!length(shown_skills)) + to_chat(user, "You don't seem to have any particularly outstanding skills.") + return + var/msg = "*---------*\nYour skills\n" + for(var/i in shown_skills) + var/datum/skill/the_skill = i + msg += "[initial(the_skill.name)] - [get_skill_level_name(the_skill)]\n" + msg += "" + to_chat(user, msg) + +/datum/mind/proc/set_death_time() + last_death = world.time + +/datum/mind/proc/store_memory(new_text) + var/newlength = length_char(memory) + length_char(new_text) + if (newlength > MAX_MESSAGE_LEN * 100) + memory = copytext_char(memory, -newlength-MAX_MESSAGE_LEN * 100) + memory += "[new_text]
    " + +/datum/mind/proc/wipe_memory() + memory = null + +// Datum antag mind procs +/datum/mind/proc/add_antag_datum(datum_type_or_instance, team) + if(!datum_type_or_instance) + return + var/datum/antagonist/A + if(!ispath(datum_type_or_instance)) + A = datum_type_or_instance + if(!istype(A)) + return + else + A = new datum_type_or_instance() + //Choose snowflake variation if antagonist handles it + var/datum/antagonist/S = A.specialization(src) + if(S && S != A) + qdel(A) + A = S + if(!A.can_be_owned(src)) + qdel(A) + return + A.owner = src + LAZYADD(antag_datums, A) + A.create_team(team) + var/datum/team/antag_team = A.get_team() + if(antag_team) + antag_team.add_member(src) + A.on_gain() + log_game("[key_name(src)] has gained antag datum [A.name]([A.type])") + return A + +/datum/mind/proc/remove_antag_datum(datum_type) + if(!datum_type) + return + var/datum/antagonist/A = has_antag_datum(datum_type) + if(A) + A.on_removal() + return TRUE + + +/datum/mind/proc/remove_all_antag_datums() //For the Lazy amongst us. + for(var/a in antag_datums) + var/datum/antagonist/A = a + A.on_removal() + +/datum/mind/proc/has_antag_datum(datum_type, check_subtypes = TRUE) + if(!datum_type) + return + . = FALSE + for(var/a in antag_datums) + var/datum/antagonist/A = a + if(check_subtypes && istype(A, datum_type)) + return A + else if(A.type == datum_type) + return A + +/* + Removes antag type's references from a mind. + objectives, uplinks, powers etc are all handled. +*/ + +/datum/mind/proc/remove_changeling() + var/datum/antagonist/changeling/C = has_antag_datum(/datum/antagonist/changeling) + if(C) + remove_antag_datum(/datum/antagonist/changeling) + special_role = null + +/datum/mind/proc/remove_traitor() + remove_antag_datum(/datum/antagonist/traitor) + +/datum/mind/proc/remove_brother() + if(src in SSticker.mode.brothers) + remove_antag_datum(/datum/antagonist/brother) + +/datum/mind/proc/remove_nukeop() + var/datum/antagonist/nukeop/nuke = has_antag_datum(/datum/antagonist/nukeop,TRUE) + if(nuke) + remove_antag_datum(nuke.type) + special_role = null + +/datum/mind/proc/remove_wizard() + remove_antag_datum(/datum/antagonist/wizard) + special_role = null + +/datum/mind/proc/remove_cultist() + if(src in SSticker.mode.cult) + SSticker.mode.remove_cultist(src, 0, 0) + special_role = null + remove_antag_equip() + +/datum/mind/proc/remove_rev() + var/datum/antagonist/rev/rev = has_antag_datum(/datum/antagonist/rev) + if(rev) + remove_antag_datum(rev.type) + special_role = null + + +/datum/mind/proc/remove_antag_equip() + var/list/Mob_Contents = current.get_contents() + for(var/obj/item/I in Mob_Contents) + var/datum/component/uplink/O = I.GetComponent(/datum/component/uplink) //Todo make this reset signal + if(O) + O.unlock_code = null + +/datum/mind/proc/remove_all_antag() //For the Lazy amongst us. + remove_changeling() + remove_traitor() + remove_nukeop() + remove_wizard() + remove_cultist() + remove_rev() + +/datum/mind/proc/equip_traitor(employer = "The Syndicate", silent = FALSE, datum/antagonist/uplink_owner) + if(!current) + return + var/mob/living/carbon/human/traitor_mob = current + if (!istype(traitor_mob)) + return + + var/list/all_contents = traitor_mob.GetAllContents() + var/obj/item/pda/PDA = locate() in all_contents + var/obj/item/radio/R = locate() in all_contents + var/obj/item/pen/P + + if (PDA) // Prioritize PDA pen, otherwise the pocket protector pens will be chosen, which causes numerous ahelps about missing uplink + P = locate() in PDA + if (!P) // If we couldn't find a pen in the PDA, or we didn't even have a PDA, do it the old way + P = locate() in all_contents + + var/obj/item/uplink_loc + + if(traitor_mob.client && traitor_mob.client.prefs) + switch(traitor_mob.client.prefs.uplink_spawn_loc) + if(UPLINK_PDA) + uplink_loc = PDA + if(!uplink_loc) + uplink_loc = R + if(!uplink_loc) + uplink_loc = P + if(UPLINK_RADIO) + uplink_loc = R + if(!uplink_loc) + uplink_loc = PDA + if(!uplink_loc) + uplink_loc = P + if(UPLINK_PEN) + uplink_loc = P + + if(!uplink_loc) // We've looked everywhere, let's just give you a pen + if(istype(traitor_mob.back,/obj/item/storage)) //ok buddy you better have a backpack! + P = new /obj/item/pen(traitor_mob.back) + else + P = new /obj/item/pen(traitor_mob.loc) + traitor_mob.put_in_hands(P) // I hope you don't have arms and your traitor pen gets stolen for all this trouble you've caused. + uplink_loc = P + + if (!uplink_loc) + if(!silent) + to_chat(traitor_mob, "Unfortunately, [employer] wasn't able to get you an Uplink.") + . = 0 + else + . = uplink_loc + var/datum/component/uplink/U = uplink_loc.AddComponent(/datum/component/uplink, traitor_mob.key) + if(!U) + CRASH("Uplink creation failed.") + U.setup_unlock_code() + if(!silent) + if(uplink_loc == R) + to_chat(traitor_mob, "[employer] has cunningly disguised a Syndicate Uplink as your [R.name]. Simply dial the frequency [format_frequency(U.unlock_code)] to unlock its hidden features.") + else if(uplink_loc == PDA) + to_chat(traitor_mob, "[employer] has cunningly disguised a Syndicate Uplink as your [PDA.name]. Simply enter the code \"[U.unlock_code]\" into the ringtone select to unlock its hidden features.") + else if(uplink_loc == P) + to_chat(traitor_mob, "[employer] has cunningly disguised a Syndicate Uplink as your [P.name]. Simply twist the top of the pen [english_list(U.unlock_code)] from its starting position to unlock its hidden features.") + + if(uplink_owner) + uplink_owner.antag_memory += U.unlock_note + "
    " + else + traitor_mob.mind.store_memory(U.unlock_note) + + +//Link a new mobs mind to the creator of said mob. They will join any team they are currently on, and will only switch teams when their creator does. + +/datum/mind/proc/enslave_mind_to_creator(mob/living/creator) + if(iscultist(creator)) + SSticker.mode.add_cultist(src) + + else if(is_revolutionary(creator)) + var/datum/antagonist/rev/converter = creator.mind.has_antag_datum(/datum/antagonist/rev,TRUE) + converter.add_revolutionary(src,FALSE) + + else if(is_nuclear_operative(creator)) + var/datum/antagonist/nukeop/converter = creator.mind.has_antag_datum(/datum/antagonist/nukeop,TRUE) + var/datum/antagonist/nukeop/N = new() + N.send_to_spawnpoint = FALSE + N.nukeop_outfit = null + add_antag_datum(N,converter.nuke_team) + + + enslaved_to = creator + + current.faction |= creator.faction + creator.faction |= current.faction + + if(creator.mind.special_role) + message_admins("[ADMIN_LOOKUPFLW(current)] has been created by [ADMIN_LOOKUPFLW(creator)], an antagonist.") + to_chat(current, "Despite your creator's current allegiances, your true master remains [creator.real_name]. If their loyalties change, so do yours. This will never change unless your creator's body is destroyed.") + +/datum/mind/proc/show_memory(mob/recipient, window=1) + if(!recipient) + recipient = current + var/output = "[current.real_name]'s Memories:
    " + output += memory + + + var/list/all_objectives = list() + for(var/datum/antagonist/A in antag_datums) + output += A.antag_memory + all_objectives |= A.objectives + + if(all_objectives.len) + output += "Objectives:" + var/obj_count = 1 + for(var/datum/objective/objective in all_objectives) + output += "
    Objective #[obj_count++]: [objective.explanation_text]" + var/list/datum/mind/other_owners = objective.get_owners() - src + if(other_owners.len) + output += "
      " + for(var/datum/mind/M in other_owners) + output += "
    • Conspirator: [M.name]
    • " + output += "
    " + + if(window) + recipient << browse(output,"window=memory") + else if(all_objectives.len || memory) + to_chat(recipient, "[output]") + +/datum/mind/Topic(href, href_list) + if(!check_rights(R_ADMIN)) + return + + var/self_antagging = usr == current + + if(href_list["add_antag"]) + add_antag_wrapper(text2path(href_list["add_antag"]),usr) + if(href_list["remove_antag"]) + var/datum/antagonist/A = locate(href_list["remove_antag"]) in antag_datums + if(!istype(A)) + to_chat(usr,"Invalid antagonist ref to be removed.") + return + A.admin_remove(usr) + + if (href_list["role_edit"]) + var/new_role = input("Select new role", "Assigned role", assigned_role) as null|anything in sortList(get_all_jobs()) + if (!new_role) + return + assigned_role = new_role + + else if (href_list["memory_edit"]) + var/new_memo = stripped_multiline_input(usr, "Write new memory", "Memory", memory, MAX_MESSAGE_LEN) + if (isnull(new_memo)) + return + memory = new_memo + + else if (href_list["obj_edit"] || href_list["obj_add"]) + var/objective_pos //Edited objectives need to keep same order in antag objective list + var/def_value + var/datum/antagonist/target_antag + var/datum/objective/old_objective //The old objective we're replacing/editing + var/datum/objective/new_objective //New objective we're be adding + + if(href_list["obj_edit"]) + for(var/datum/antagonist/A in antag_datums) + old_objective = locate(href_list["obj_edit"]) in A.objectives + if(old_objective) + target_antag = A + objective_pos = A.objectives.Find(old_objective) + break + if(!old_objective) + to_chat(usr,"Invalid objective.") + return + else + if(href_list["target_antag"]) + var/datum/antagonist/X = locate(href_list["target_antag"]) in antag_datums + if(X) + target_antag = X + if(!target_antag) + switch(antag_datums.len) + if(0) + target_antag = add_antag_datum(/datum/antagonist/custom) + if(1) + target_antag = antag_datums[1] + else + var/datum/antagonist/target = input("Which antagonist gets the objective:", "Antagonist", "(new custom antag)") as null|anything in sortList(antag_datums) + "(new custom antag)" + if (QDELETED(target)) + return + else if(target == "(new custom antag)") + target_antag = add_antag_datum(/datum/antagonist/custom) + else + target_antag = target + + if(!GLOB.admin_objective_list) + generate_admin_objective_list() + + if(old_objective) + if(old_objective.name in GLOB.admin_objective_list) + def_value = old_objective.name + + var/selected_type = input("Select objective type:", "Objective type", def_value) as null|anything in GLOB.admin_objective_list + selected_type = GLOB.admin_objective_list[selected_type] + if (!selected_type) + return + + if(!old_objective) + //Add new one + new_objective = new selected_type + new_objective.owner = src + new_objective.admin_edit(usr) + target_antag.objectives += new_objective + message_admins("[key_name_admin(usr)] added a new objective for [current]: [new_objective.explanation_text]") + log_admin("[key_name(usr)] added a new objective for [current]: [new_objective.explanation_text]") + else + if(old_objective.type == selected_type) + //Edit the old + old_objective.admin_edit(usr) + new_objective = old_objective + else + //Replace the old + new_objective = new selected_type + new_objective.owner = src + new_objective.admin_edit(usr) + target_antag.objectives -= old_objective + target_antag.objectives.Insert(objective_pos, new_objective) + message_admins("[key_name_admin(usr)] edited [current]'s objective to [new_objective.explanation_text]") + log_admin("[key_name(usr)] edited [current]'s objective to [new_objective.explanation_text]") + + else if (href_list["obj_delete"]) + var/datum/objective/objective + for(var/datum/antagonist/A in antag_datums) + objective = locate(href_list["obj_delete"]) in A.objectives + if(istype(objective)) + A.objectives -= objective + break + if(!objective) + to_chat(usr,"Invalid objective.") + return + //qdel(objective) Needs cleaning objective destroys + message_admins("[key_name_admin(usr)] removed an objective for [current]: [objective.explanation_text]") + log_admin("[key_name(usr)] removed an objective for [current]: [objective.explanation_text]") + + else if(href_list["obj_completed"]) + var/datum/objective/objective + for(var/datum/antagonist/A in antag_datums) + objective = locate(href_list["obj_completed"]) in A.objectives + if(istype(objective)) + objective = objective + break + if(!objective) + to_chat(usr,"Invalid objective.") + return + objective.completed = !objective.completed + log_admin("[key_name(usr)] toggled the win state for [current]'s objective: [objective.explanation_text]") + + else if (href_list["silicon"]) + switch(href_list["silicon"]) + if("unemag") + var/mob/living/silicon/robot/R = current + if (istype(R)) + R.SetEmagged(0) + message_admins("[key_name_admin(usr)] has unemag'ed [R].") + log_admin("[key_name(usr)] has unemag'ed [R].") + + if("unemagcyborgs") + if(isAI(current)) + var/mob/living/silicon/ai/ai = current + for (var/mob/living/silicon/robot/R in ai.connected_robots) + R.SetEmagged(0) + message_admins("[key_name_admin(usr)] has unemag'ed [ai]'s Cyborgs.") + log_admin("[key_name(usr)] has unemag'ed [ai]'s Cyborgs.") + + else if (href_list["common"]) + switch(href_list["common"]) + if("undress") + for(var/obj/item/W in current) + current.dropItemToGround(W, TRUE) //The 1 forces all items to drop, since this is an admin undress. + if("takeuplink") + take_uplink() + memory = null//Remove any memory they may have had. + log_admin("[key_name(usr)] removed [current]'s uplink.") + if("crystals") + if(check_rights(R_FUN, 0)) + var/datum/component/uplink/U = find_syndicate_uplink() + if(U) + var/crystals = input("Amount of telecrystals for [key]","Syndicate uplink", U.telecrystals) as null | num + if(!isnull(crystals)) + U.telecrystals = crystals + message_admins("[key_name_admin(usr)] changed [current]'s telecrystal count to [crystals].") + log_admin("[key_name(usr)] changed [current]'s telecrystal count to [crystals].") + if("uplink") + if(!equip_traitor()) + to_chat(usr, "Equipping a syndicate failed!") + log_admin("[key_name(usr)] tried and failed to give [current] an uplink.") + else + log_admin("[key_name(usr)] gave [current] an uplink.") + + else if (href_list["obj_announce"]) + announce_objectives() + + //Something in here might have changed your mob + if(self_antagging && (!usr || !usr.client) && current.client) + usr = current + traitor_panel() + + +/datum/mind/proc/get_all_objectives() + var/list/all_objectives = list() + for(var/datum/antagonist/A in antag_datums) + all_objectives |= A.objectives + return all_objectives + +/datum/mind/proc/announce_objectives() + var/obj_count = 1 + to_chat(current, "Your current objectives:") + for(var/objective in get_all_objectives()) + var/datum/objective/O = objective + to_chat(current, "Objective #[obj_count]: [O.explanation_text]") + obj_count++ + +/datum/mind/proc/find_syndicate_uplink() + var/list/L = current.GetAllContents() + for (var/i in L) + var/atom/movable/I = i + . = I.GetComponent(/datum/component/uplink) + if(.) + break + +/datum/mind/proc/take_uplink() + qdel(find_syndicate_uplink()) + +/datum/mind/proc/make_Traitor() + if(!(has_antag_datum(/datum/antagonist/traitor))) + add_antag_datum(/datum/antagonist/traitor) + +/datum/mind/proc/make_Contractor_Support() + if(!(has_antag_datum(/datum/antagonist/traitor/contractor_support))) + add_antag_datum(/datum/antagonist/traitor/contractor_support) + +/datum/mind/proc/make_Changeling() + var/datum/antagonist/changeling/C = has_antag_datum(/datum/antagonist/changeling) + if(!C) + C = add_antag_datum(/datum/antagonist/changeling) + special_role = ROLE_CHANGELING + return C + +/datum/mind/proc/make_Wizard() + if(!has_antag_datum(/datum/antagonist/wizard)) + special_role = ROLE_WIZARD + assigned_role = ROLE_WIZARD + add_antag_datum(/datum/antagonist/wizard) + + +/datum/mind/proc/make_Cultist() + if(!has_antag_datum(/datum/antagonist/cult,TRUE)) + SSticker.mode.add_cultist(src,FALSE,equip=TRUE) + special_role = ROLE_CULTIST + to_chat(current, "You catch a glimpse of the Realm of Nar'Sie, The Geometer of Blood. You now see how flimsy your world is, you see that it should be open to the knowledge of Nar'Sie.") + to_chat(current, "Assist your new brethren in their dark dealings. Their goal is yours, and yours is theirs. You serve the Dark One above all else. Bring It back.") + +/datum/mind/proc/make_Rev() + var/datum/antagonist/rev/head/head = new() + head.give_flash = TRUE + head.give_hud = TRUE + add_antag_datum(head) + special_role = ROLE_REV_HEAD + +/datum/mind/proc/AddSpell(obj/effect/proc_holder/spell/S) + spell_list += S + S.action.Grant(current) + +/datum/mind/proc/owns_soul() + return soulOwner == src + +//To remove a specific spell from a mind +/datum/mind/proc/RemoveSpell(obj/effect/proc_holder/spell/spell) + if(!spell) + return + for(var/X in spell_list) + var/obj/effect/proc_holder/spell/S = X + if(istype(S, spell)) + spell_list -= S + qdel(S) + +/datum/mind/proc/RemoveAllSpells() + for(var/obj/effect/proc_holder/S in spell_list) + RemoveSpell(S) + +/datum/mind/proc/transfer_martial_arts(mob/living/new_character) + if(!ishuman(new_character)) + return + if(martial_art) + if(martial_art.base) //Is the martial art temporary? + martial_art.remove(new_character) + else + martial_art.teach(new_character) + +/datum/mind/proc/transfer_actions(mob/living/new_character) + if(current && current.actions) + for(var/datum/action/A in current.actions) + A.Grant(new_character) + transfer_mindbound_actions(new_character) + +/datum/mind/proc/transfer_mindbound_actions(mob/living/new_character) + for(var/X in spell_list) + var/obj/effect/proc_holder/spell/S = X + S.action.Grant(new_character) + +/datum/mind/proc/disrupt_spells(delay, list/exceptions = New()) + for(var/X in spell_list) + var/obj/effect/proc_holder/spell/S = X + for(var/type in exceptions) + if(istype(S, type)) + continue + S.charge_counter = delay + S.updateButtonIcon() + INVOKE_ASYNC(S, /obj/effect/proc_holder/spell.proc/start_recharge) + +/datum/mind/proc/get_ghost(even_if_they_cant_reenter, ghosts_with_clients) + for(var/mob/dead/observer/G in (ghosts_with_clients ? GLOB.player_list : GLOB.dead_mob_list)) + if(G.mind == src) + if(G.can_reenter_corpse || even_if_they_cant_reenter) + return G + break + +/datum/mind/proc/grab_ghost(force) + var/mob/dead/observer/G = get_ghost(even_if_they_cant_reenter = force) + . = G + if(G) + G.reenter_corpse() + + +/datum/mind/proc/has_objective(objective_type) + for(var/datum/antagonist/A in antag_datums) + for(var/O in A.objectives) + if(istype(O,objective_type)) + return TRUE + +/mob/proc/sync_mind() + mind_initialize() //updates the mind (or creates and initializes one if one doesn't exist) + mind.active = 1 //indicates that the mind is currently synced with a client + +/datum/mind/proc/has_martialart(string) + if(martial_art && martial_art.id == string) + return martial_art + return FALSE + +/mob/dead/new_player/sync_mind() + return + +/mob/dead/observer/sync_mind() + return + +//Initialisation procs +/mob/proc/mind_initialize() + if(mind) + mind.key = key + + else + mind = new /datum/mind(key) + SSticker.minds += mind + if(!mind.name) + mind.name = real_name + mind.current = src + +/mob/living/carbon/mind_initialize() + ..() + last_mind = mind + +//HUMAN +/mob/living/carbon/human/mind_initialize() + ..() + if(!mind.assigned_role) + mind.assigned_role = "Unassigned" //default + +//AI +/mob/living/silicon/ai/mind_initialize() + ..() + mind.assigned_role = "AI" + +//BORG +/mob/living/silicon/robot/mind_initialize() + ..() + mind.assigned_role = "Cyborg" + +//PAI +/mob/living/silicon/pai/mind_initialize() + ..() + mind.assigned_role = ROLE_PAI + mind.special_role = "" diff --git a/code/datums/mood_events/_mood_event.dm b/code/datums/mood_events/_mood_event.dm index d2b773fb5a55..c1cb787fc59d 100644 --- a/code/datums/mood_events/_mood_event.dm +++ b/code/datums/mood_events/_mood_event.dm @@ -1,25 +1,25 @@ -/datum/mood_event - var/description ///For descriptions, use the span classes bold nicegreen, nicegreen, none, warning and boldwarning in order from great to horrible. - var/mood_change = 0 - var/timeout = 0 - var/hidden = FALSE//Not shown on examine - var/category //string of what category this mood was added in as - var/special_screen_obj //if it isn't null, it will replace or add onto the mood icon with this (same file). see happiness drug for example - var/special_screen_replace = TRUE //if false, it will be an overlay instead - var/mob/owner - -/datum/mood_event/New(mob/M, ...) - owner = M - var/list/params = args.Copy(2) - add_effects(arglist(params)) - -/datum/mood_event/Destroy() - remove_effects() - owner = null - return ..() - -/datum/mood_event/proc/add_effects(param) - return - -/datum/mood_event/proc/remove_effects() - return +/datum/mood_event + var/description ///For descriptions, use the span classes bold nicegreen, nicegreen, none, warning and boldwarning in order from great to horrible. + var/mood_change = 0 + var/timeout = 0 + var/hidden = FALSE//Not shown on examine + var/category //string of what category this mood was added in as + var/special_screen_obj //if it isn't null, it will replace or add onto the mood icon with this (same file). see happiness drug for example + var/special_screen_replace = TRUE //if false, it will be an overlay instead + var/mob/owner + +/datum/mood_event/New(mob/M, ...) + owner = M + var/list/params = args.Copy(2) + add_effects(arglist(params)) + +/datum/mood_event/Destroy() + remove_effects() + owner = null + return ..() + +/datum/mood_event/proc/add_effects(param) + return + +/datum/mood_event/proc/remove_effects() + return diff --git a/code/datums/mood_events/drug_events.dm b/code/datums/mood_events/drug_events.dm index bcb9bee20f96..0311e280b126 100644 --- a/code/datums/mood_events/drug_events.dm +++ b/code/datums/mood_events/drug_events.dm @@ -1,80 +1,80 @@ -/datum/mood_event/high - mood_change = 6 - description = "Woooow duudeeeeee...I'm tripping baaalls...\n" - -/datum/mood_event/smoked - description = "I have had a smoke recently.\n" - mood_change = 2 - timeout = 6 MINUTES - -/datum/mood_event/wrong_brand - description = "I hate that brand of cigarettes.\n" - mood_change = -2 - timeout = 6 MINUTES - -/datum/mood_event/overdose - mood_change = -8 - timeout = 5 MINUTES - -/datum/mood_event/overdose/add_effects(drug_name) - description = "I think I took a bit too much of that [drug_name]\n" - -/datum/mood_event/withdrawal_light - mood_change = -2 - -/datum/mood_event/withdrawal_light/add_effects(drug_name) - description = "I could use some [drug_name]\n" - -/datum/mood_event/withdrawal_medium - mood_change = -5 - -/datum/mood_event/withdrawal_medium/add_effects(drug_name) - description = "I really need [drug_name]\n" - -/datum/mood_event/withdrawal_severe - mood_change = -8 - -/datum/mood_event/withdrawal_severe/add_effects(drug_name) - description = "Oh god I need some of that [drug_name]\n" - -/datum/mood_event/withdrawal_critical - mood_change = -10 - -/datum/mood_event/withdrawal_critical/add_effects(drug_name) - description = "[drug_name]! [drug_name]! [drug_name]!\n" - -/datum/mood_event/happiness_drug - description = "Can't feel a thing...\n" - mood_change = 50 - -/datum/mood_event/happiness_drug_good_od - description = "YES! YES!! YES!!!\n" - mood_change = 100 - timeout = 30 SECONDS - special_screen_obj = "mood_happiness_good" - -/datum/mood_event/happiness_drug_bad_od - description = "NO! NO!! NO!!!\n" - mood_change = -100 - timeout = 30 SECONDS - special_screen_obj = "mood_happiness_bad" - -/datum/mood_event/narcotic_medium - description = "I feel comfortably numb.\n" - mood_change = 4 - timeout = 3 MINUTES - -/datum/mood_event/narcotic_heavy - description = "I feel like I'm wrapped up in cotton!\n" - mood_change = 9 - timeout = 3 MINUTES - -/datum/mood_event/stimulant_medium - description = "I have so much energy! I feel like I could do anything!\n" - mood_change = 4 - timeout = 3 MINUTES - -/datum/mood_event/stimulant_heavy - description = "Eh ah AAAAH! HA HA HA HA HAA! Uuuh.\n" - mood_change = 6 - timeout = 3 MINUTES +/datum/mood_event/high + mood_change = 6 + description = "Woooow duudeeeeee...I'm tripping baaalls...\n" + +/datum/mood_event/smoked + description = "I have had a smoke recently.\n" + mood_change = 2 + timeout = 6 MINUTES + +/datum/mood_event/wrong_brand + description = "I hate that brand of cigarettes.\n" + mood_change = -2 + timeout = 6 MINUTES + +/datum/mood_event/overdose + mood_change = -8 + timeout = 5 MINUTES + +/datum/mood_event/overdose/add_effects(drug_name) + description = "I think I took a bit too much of that [drug_name]\n" + +/datum/mood_event/withdrawal_light + mood_change = -2 + +/datum/mood_event/withdrawal_light/add_effects(drug_name) + description = "I could use some [drug_name]\n" + +/datum/mood_event/withdrawal_medium + mood_change = -5 + +/datum/mood_event/withdrawal_medium/add_effects(drug_name) + description = "I really need [drug_name]\n" + +/datum/mood_event/withdrawal_severe + mood_change = -8 + +/datum/mood_event/withdrawal_severe/add_effects(drug_name) + description = "Oh god I need some of that [drug_name]\n" + +/datum/mood_event/withdrawal_critical + mood_change = -10 + +/datum/mood_event/withdrawal_critical/add_effects(drug_name) + description = "[drug_name]! [drug_name]! [drug_name]!\n" + +/datum/mood_event/happiness_drug + description = "Can't feel a thing...\n" + mood_change = 50 + +/datum/mood_event/happiness_drug_good_od + description = "YES! YES!! YES!!!\n" + mood_change = 100 + timeout = 30 SECONDS + special_screen_obj = "mood_happiness_good" + +/datum/mood_event/happiness_drug_bad_od + description = "NO! NO!! NO!!!\n" + mood_change = -100 + timeout = 30 SECONDS + special_screen_obj = "mood_happiness_bad" + +/datum/mood_event/narcotic_medium + description = "I feel comfortably numb.\n" + mood_change = 4 + timeout = 3 MINUTES + +/datum/mood_event/narcotic_heavy + description = "I feel like I'm wrapped up in cotton!\n" + mood_change = 9 + timeout = 3 MINUTES + +/datum/mood_event/stimulant_medium + description = "I have so much energy! I feel like I could do anything!\n" + mood_change = 4 + timeout = 3 MINUTES + +/datum/mood_event/stimulant_heavy + description = "Eh ah AAAAH! HA HA HA HA HAA! Uuuh.\n" + mood_change = 6 + timeout = 3 MINUTES diff --git a/code/datums/mood_events/needs_events.dm b/code/datums/mood_events/needs_events.dm index e357a58b70f4..e9ce07165326 100644 --- a/code/datums/mood_events/needs_events.dm +++ b/code/datums/mood_events/needs_events.dm @@ -1,93 +1,93 @@ -//nutrition -/datum/mood_event/fat - description = "I'm so fat...\n" //muh fatshaming - mood_change = -6 - -/datum/mood_event/wellfed - description = "I'm stuffed!\n" - mood_change = 8 - -/datum/mood_event/fed - description = "I have recently had some food.\n" - mood_change = 5 - -/datum/mood_event/hungry - description = "I'm getting a bit hungry.\n" - mood_change = -6 - -/datum/mood_event/starving - description = "I'm starving!\n" - mood_change = -10 - -//charge -/datum/mood_event/supercharged - description = "I can't possibly keep all this power inside, I need to release some quick!\n" - mood_change = -10 - -/datum/mood_event/overcharged - description = "I feel dangerously overcharged, perhaps I should release some power.\n" - mood_change = -4 - -/datum/mood_event/charged - description = "I feel the power in my veins!\n" - mood_change = 6 - -/datum/mood_event/lowpower - description = "My power is running low, I should go charge up somewhere.\n" - mood_change = -6 - -/datum/mood_event/decharged - description = "I'm in desperate need of some electricity!\n" - mood_change = -10 - -//Disgust -/datum/mood_event/gross - description = "I saw something gross.\n" - mood_change = -4 - -/datum/mood_event/verygross - description = "I think I'm going to puke...\n" - mood_change = -6 - -/datum/mood_event/disgusted - description = "Oh god that's disgusting...\n" - mood_change = -8 - -/datum/mood_event/disgust/bad_smell - description = "You smell something horribly decayed inside this room.\n" - mood_change = -6 - -/datum/mood_event/disgust/nauseating_stench - description = "The stench of rotting carcasses is unbearable!\n" - mood_change = -12 - -//Generic needs events -/datum/mood_event/favorite_food - description = "I really enjoyed eating that.\n" - mood_change = 5 - timeout = 4 MINUTES - -/datum/mood_event/gross_food - description = "I really didn't like that food.\n" - mood_change = -2 - timeout = 4 MINUTES - -/datum/mood_event/disgusting_food - description = "That food was disgusting!\n" - mood_change = -6 - timeout = 4 MINUTES - -/datum/mood_event/breakfast - description = "Nothing like a hearty breakfast to start the shift.\n" - mood_change = 2 - timeout = 10 MINUTES - -/datum/mood_event/nice_shower - description = "I have recently had a nice shower.\n" - mood_change = 4 - timeout = 5 MINUTES - -/datum/mood_event/fresh_laundry - description = "There's nothing like the feeling of a freshly laundered jumpsuit.\n" - mood_change = 2 - timeout = 10 MINUTES +//nutrition +/datum/mood_event/fat + description = "I'm so fat...\n" //muh fatshaming + mood_change = -6 + +/datum/mood_event/wellfed + description = "I'm stuffed!\n" + mood_change = 8 + +/datum/mood_event/fed + description = "I have recently had some food.\n" + mood_change = 5 + +/datum/mood_event/hungry + description = "I'm getting a bit hungry.\n" + mood_change = -6 + +/datum/mood_event/starving + description = "I'm starving!\n" + mood_change = -10 + +//charge +/datum/mood_event/supercharged + description = "I can't possibly keep all this power inside, I need to release some quick!\n" + mood_change = -10 + +/datum/mood_event/overcharged + description = "I feel dangerously overcharged, perhaps I should release some power.\n" + mood_change = -4 + +/datum/mood_event/charged + description = "I feel the power in my veins!\n" + mood_change = 6 + +/datum/mood_event/lowpower + description = "My power is running low, I should go charge up somewhere.\n" + mood_change = -6 + +/datum/mood_event/decharged + description = "I'm in desperate need of some electricity!\n" + mood_change = -10 + +//Disgust +/datum/mood_event/gross + description = "I saw something gross.\n" + mood_change = -4 + +/datum/mood_event/verygross + description = "I think I'm going to puke...\n" + mood_change = -6 + +/datum/mood_event/disgusted + description = "Oh god that's disgusting...\n" + mood_change = -8 + +/datum/mood_event/disgust/bad_smell + description = "You smell something horribly decayed inside this room.\n" + mood_change = -6 + +/datum/mood_event/disgust/nauseating_stench + description = "The stench of rotting carcasses is unbearable!\n" + mood_change = -12 + +//Generic needs events +/datum/mood_event/favorite_food + description = "I really enjoyed eating that.\n" + mood_change = 5 + timeout = 4 MINUTES + +/datum/mood_event/gross_food + description = "I really didn't like that food.\n" + mood_change = -2 + timeout = 4 MINUTES + +/datum/mood_event/disgusting_food + description = "That food was disgusting!\n" + mood_change = -6 + timeout = 4 MINUTES + +/datum/mood_event/breakfast + description = "Nothing like a hearty breakfast to start the shift.\n" + mood_change = 2 + timeout = 10 MINUTES + +/datum/mood_event/nice_shower + description = "I have recently had a nice shower.\n" + mood_change = 4 + timeout = 5 MINUTES + +/datum/mood_event/fresh_laundry + description = "There's nothing like the feeling of a freshly laundered jumpsuit.\n" + mood_change = 2 + timeout = 10 MINUTES diff --git a/code/datums/numbered_display.dm b/code/datums/numbered_display.dm index 84ab35a8defd..9aa880aa75d9 100644 --- a/code/datums/numbered_display.dm +++ b/code/datums/numbered_display.dm @@ -1,10 +1,10 @@ -//Used in storage. -/datum/numbered_display - var/obj/item/sample_object - var/number - -/datum/numbered_display/New(obj/item/sample, _number = 1) - if(!istype(sample)) - qdel(src) - sample_object = sample - number = _number +//Used in storage. +/datum/numbered_display + var/obj/item/sample_object + var/number + +/datum/numbered_display/New(obj/item/sample, _number = 1) + if(!istype(sample)) + qdel(src) + sample_object = sample + number = _number diff --git a/code/datums/position_point_vector.dm b/code/datums/position_point_vector.dm index 1709147b36d1..07fe0ed7652f 100644 --- a/code/datums/position_point_vector.dm +++ b/code/datums/position_point_vector.dm @@ -1,226 +1,226 @@ -//Designed for things that need precision trajectories like projectiles. -//Don't use this for anything that you don't absolutely have to use this with (like projectiles!) because it isn't worth using a datum unless you need accuracy down to decimal places in pixels. - -//You might see places where it does - 16 - 1. This is intentionally 17 instead of 16, because of how byond's tiles work and how not doing it will result in rounding errors like things getting put on the wrong turf. - -#define RETURN_PRECISE_POSITION(A) new /datum/position(A) -#define RETURN_PRECISE_POINT(A) new /datum/point(A) - -#define RETURN_POINT_VECTOR(ATOM, ANGLE, SPEED) (new /datum/point/vector(ATOM, null, null, null, null, ANGLE, SPEED)) -#define RETURN_POINT_VECTOR_INCREMENT(ATOM, ANGLE, SPEED, AMT) (new /datum/point/vector(ATOM, null, null, null, null, ANGLE, SPEED, AMT)) - -/proc/point_midpoint_points(datum/point/a, datum/point/b) //Obviously will not support multiZ calculations! Same for the two below. - var/datum/point/P = new - P.x = a.x + (b.x - a.x) / 2 - P.y = a.y + (b.y - a.y) / 2 - P.z = a.z - return P - -/proc/pixel_length_between_points(datum/point/a, datum/point/b) - return sqrt(((b.x - a.x) ** 2) + ((b.y - a.y) ** 2)) - -/proc/angle_between_points(datum/point/a, datum/point/b) - return ATAN2((b.y - a.y), (b.x - a.x)) - -/datum/position //For positions with map x/y/z and pixel x/y so you don't have to return lists. Could use addition/subtraction in the future I guess. - var/x = 0 - var/y = 0 - var/z = 0 - var/pixel_x = 0 - var/pixel_y = 0 - -/datum/position/proc/valid() - return x && y && z && !isnull(pixel_x) && !isnull(pixel_y) - -/datum/position/New(_x = 0, _y = 0, _z = 0, _pixel_x = 0, _pixel_y = 0) //first argument can also be a /datum/point. - if(istype(_x, /datum/point)) - var/datum/point/P = _x - var/turf/T = P.return_turf() - _x = T.x - _y = T.y - _z = T.z - _pixel_x = P.return_px() - _pixel_y = P.return_py() - else if(isatom(_x)) - var/atom/A = _x - _x = A.x - _y = A.y - _z = A.z - _pixel_x = A.pixel_x - _pixel_y = A.pixel_y - x = _x - y = _y - z = _z - pixel_x = _pixel_x - pixel_y = _pixel_y - -/datum/position/proc/return_turf() - return locate(x, y, z) - -/datum/position/proc/return_px() - return pixel_x - -/datum/position/proc/return_py() - return pixel_y - -/datum/position/proc/return_point() - return new /datum/point(src) - -/datum/point //A precise point on the map in absolute pixel locations based on world.icon_size. Pixels are FROM THE EDGE OF THE MAP! - var/x = 0 - var/y = 0 - var/z = 0 - -/datum/point/proc/valid() - return x && y && z - -/datum/point/proc/copy_to(datum/point/p = new) - p.x = x - p.y = y - p.z = z - return p - -/datum/point/New(_x, _y, _z, _pixel_x = 0, _pixel_y = 0) //first argument can also be a /datum/position or /atom. - if(istype(_x, /datum/position)) - var/datum/position/P = _x - _x = P.x - _y = P.y - _z = P.z - _pixel_x = P.pixel_x - _pixel_y = P.pixel_y - else if(istype(_x, /atom)) - var/atom/A = _x - _x = A.x - _y = A.y - _z = A.z - _pixel_x = A.pixel_x - _pixel_y = A.pixel_y - initialize_location(_x, _y, _z, _pixel_x, _pixel_y) - -/datum/point/proc/initialize_location(tile_x, tile_y, tile_z, p_x = 0, p_y = 0) - if(!isnull(tile_x)) - x = ((tile_x - 1) * world.icon_size) + world.icon_size / 2 + p_x + 1 - if(!isnull(tile_y)) - y = ((tile_y - 1) * world.icon_size) + world.icon_size / 2 + p_y + 1 - if(!isnull(tile_z)) - z = tile_z - -/datum/point/proc/debug_out() - var/turf/T = return_turf() - return "\ref[src] aX [x] aY [y] aZ [z] pX [return_px()] pY [return_py()] mX [T.x] mY [T.y] mZ [T.z]" - -/datum/point/proc/move_atom_to_src(atom/movable/AM) - AM.forceMove(return_turf()) - AM.pixel_x = return_px() - AM.pixel_y = return_py() - -/datum/point/proc/return_turf() - return locate(CEILING(x / world.icon_size, 1), CEILING(y / world.icon_size, 1), z) - -/datum/point/proc/return_coordinates() //[turf_x, turf_y, z] - return list(CEILING(x / world.icon_size, 1), CEILING(y / world.icon_size, 1), z) - -/datum/point/proc/return_position() - return new /datum/position(src) - -/datum/point/proc/return_px() - return MODULUS(x, world.icon_size) - 16 - 1 - -/datum/point/proc/return_py() - return MODULUS(y, world.icon_size) - 16 - 1 - -/datum/point/vector - var/speed = 32 //pixels per iteration - var/iteration = 0 - var/angle = 0 - var/mpx = 0 //calculated x/y movement amounts to prevent having to do trig every step. - var/mpy = 0 - var/starting_x = 0 //just like before, pixels from EDGE of map! This is set in initialize_location(). - var/starting_y = 0 - var/starting_z = 0 - -/datum/point/vector/New(_x, _y, _z, _pixel_x = 0, _pixel_y = 0, _angle, _speed, initial_increment = 0) - ..() - initialize_trajectory(_speed, _angle) - if(initial_increment) - increment(initial_increment) - -/datum/point/vector/initialize_location(tile_x, tile_y, tile_z, p_x = 0, p_y = 0) - . = ..() - starting_x = x - starting_y = y - starting_z = z - -/datum/point/vector/copy_to(datum/point/vector/v = new) - ..(v) - v.speed = speed - v.iteration = iteration - v.angle = angle - v.mpx = mpx - v.mpy = mpy - v.starting_x = starting_x - v.starting_y = starting_y - v.starting_z = starting_z - return v - -/datum/point/vector/proc/initialize_trajectory(pixel_speed, new_angle) - if(!isnull(pixel_speed)) - speed = pixel_speed - set_angle(new_angle) - -/datum/point/vector/proc/set_angle(new_angle) //calculations use "byond angle" where north is 0 instead of 90, and south is 180 instead of 270. - if(isnull(angle)) - return - angle = new_angle - update_offsets() - -/datum/point/vector/proc/update_offsets() - mpx = sin(angle) * speed - mpy = cos(angle) * speed - -/datum/point/vector/proc/set_speed(new_speed) - if(isnull(new_speed) || speed == new_speed) - return - speed = new_speed - update_offsets() - -/datum/point/vector/proc/increment(multiplier = 1) - iteration++ - x += mpx * (multiplier) - y += mpy * (multiplier) - -/datum/point/vector/proc/return_vector_after_increments(amount = 7, multiplier = 1, force_simulate = FALSE) - var/datum/point/vector/v = copy_to() - if(force_simulate) - for(var/i in 1 to amount) - v.increment(multiplier) - else - v.increment(multiplier * amount) - return v - -/datum/point/vector/proc/on_z_change() - return - -/datum/point/vector/processed //pixel_speed is per decisecond. - var/last_process = 0 - var/last_move = 0 - var/paused = FALSE - -/datum/point/vector/processed/Destroy() - STOP_PROCESSING(SSprojectiles, src) - return ..() - -/datum/point/vector/processed/proc/start() - last_process = world.time - last_move = world.time - START_PROCESSING(SSprojectiles, src) - -/datum/point/vector/processed/process() - if(paused) - last_move += world.time - last_process - last_process = world.time - return - var/needed_time = world.time - last_move - last_process = world.time - last_move = world.time - increment(needed_time / SSprojectiles.wait) +//Designed for things that need precision trajectories like projectiles. +//Don't use this for anything that you don't absolutely have to use this with (like projectiles!) because it isn't worth using a datum unless you need accuracy down to decimal places in pixels. + +//You might see places where it does - 16 - 1. This is intentionally 17 instead of 16, because of how byond's tiles work and how not doing it will result in rounding errors like things getting put on the wrong turf. + +#define RETURN_PRECISE_POSITION(A) new /datum/position(A) +#define RETURN_PRECISE_POINT(A) new /datum/point(A) + +#define RETURN_POINT_VECTOR(ATOM, ANGLE, SPEED) (new /datum/point/vector(ATOM, null, null, null, null, ANGLE, SPEED)) +#define RETURN_POINT_VECTOR_INCREMENT(ATOM, ANGLE, SPEED, AMT) (new /datum/point/vector(ATOM, null, null, null, null, ANGLE, SPEED, AMT)) + +/proc/point_midpoint_points(datum/point/a, datum/point/b) //Obviously will not support multiZ calculations! Same for the two below. + var/datum/point/P = new + P.x = a.x + (b.x - a.x) / 2 + P.y = a.y + (b.y - a.y) / 2 + P.z = a.z + return P + +/proc/pixel_length_between_points(datum/point/a, datum/point/b) + return sqrt(((b.x - a.x) ** 2) + ((b.y - a.y) ** 2)) + +/proc/angle_between_points(datum/point/a, datum/point/b) + return ATAN2((b.y - a.y), (b.x - a.x)) + +/datum/position //For positions with map x/y/z and pixel x/y so you don't have to return lists. Could use addition/subtraction in the future I guess. + var/x = 0 + var/y = 0 + var/z = 0 + var/pixel_x = 0 + var/pixel_y = 0 + +/datum/position/proc/valid() + return x && y && z && !isnull(pixel_x) && !isnull(pixel_y) + +/datum/position/New(_x = 0, _y = 0, _z = 0, _pixel_x = 0, _pixel_y = 0) //first argument can also be a /datum/point. + if(istype(_x, /datum/point)) + var/datum/point/P = _x + var/turf/T = P.return_turf() + _x = T.x + _y = T.y + _z = T.z + _pixel_x = P.return_px() + _pixel_y = P.return_py() + else if(isatom(_x)) + var/atom/A = _x + _x = A.x + _y = A.y + _z = A.z + _pixel_x = A.pixel_x + _pixel_y = A.pixel_y + x = _x + y = _y + z = _z + pixel_x = _pixel_x + pixel_y = _pixel_y + +/datum/position/proc/return_turf() + return locate(x, y, z) + +/datum/position/proc/return_px() + return pixel_x + +/datum/position/proc/return_py() + return pixel_y + +/datum/position/proc/return_point() + return new /datum/point(src) + +/datum/point //A precise point on the map in absolute pixel locations based on world.icon_size. Pixels are FROM THE EDGE OF THE MAP! + var/x = 0 + var/y = 0 + var/z = 0 + +/datum/point/proc/valid() + return x && y && z + +/datum/point/proc/copy_to(datum/point/p = new) + p.x = x + p.y = y + p.z = z + return p + +/datum/point/New(_x, _y, _z, _pixel_x = 0, _pixel_y = 0) //first argument can also be a /datum/position or /atom. + if(istype(_x, /datum/position)) + var/datum/position/P = _x + _x = P.x + _y = P.y + _z = P.z + _pixel_x = P.pixel_x + _pixel_y = P.pixel_y + else if(istype(_x, /atom)) + var/atom/A = _x + _x = A.x + _y = A.y + _z = A.z + _pixel_x = A.pixel_x + _pixel_y = A.pixel_y + initialize_location(_x, _y, _z, _pixel_x, _pixel_y) + +/datum/point/proc/initialize_location(tile_x, tile_y, tile_z, p_x = 0, p_y = 0) + if(!isnull(tile_x)) + x = ((tile_x - 1) * world.icon_size) + world.icon_size / 2 + p_x + 1 + if(!isnull(tile_y)) + y = ((tile_y - 1) * world.icon_size) + world.icon_size / 2 + p_y + 1 + if(!isnull(tile_z)) + z = tile_z + +/datum/point/proc/debug_out() + var/turf/T = return_turf() + return "\ref[src] aX [x] aY [y] aZ [z] pX [return_px()] pY [return_py()] mX [T.x] mY [T.y] mZ [T.z]" + +/datum/point/proc/move_atom_to_src(atom/movable/AM) + AM.forceMove(return_turf()) + AM.pixel_x = return_px() + AM.pixel_y = return_py() + +/datum/point/proc/return_turf() + return locate(CEILING(x / world.icon_size, 1), CEILING(y / world.icon_size, 1), z) + +/datum/point/proc/return_coordinates() //[turf_x, turf_y, z] + return list(CEILING(x / world.icon_size, 1), CEILING(y / world.icon_size, 1), z) + +/datum/point/proc/return_position() + return new /datum/position(src) + +/datum/point/proc/return_px() + return MODULUS(x, world.icon_size) - 16 - 1 + +/datum/point/proc/return_py() + return MODULUS(y, world.icon_size) - 16 - 1 + +/datum/point/vector + var/speed = 32 //pixels per iteration + var/iteration = 0 + var/angle = 0 + var/mpx = 0 //calculated x/y movement amounts to prevent having to do trig every step. + var/mpy = 0 + var/starting_x = 0 //just like before, pixels from EDGE of map! This is set in initialize_location(). + var/starting_y = 0 + var/starting_z = 0 + +/datum/point/vector/New(_x, _y, _z, _pixel_x = 0, _pixel_y = 0, _angle, _speed, initial_increment = 0) + ..() + initialize_trajectory(_speed, _angle) + if(initial_increment) + increment(initial_increment) + +/datum/point/vector/initialize_location(tile_x, tile_y, tile_z, p_x = 0, p_y = 0) + . = ..() + starting_x = x + starting_y = y + starting_z = z + +/datum/point/vector/copy_to(datum/point/vector/v = new) + ..(v) + v.speed = speed + v.iteration = iteration + v.angle = angle + v.mpx = mpx + v.mpy = mpy + v.starting_x = starting_x + v.starting_y = starting_y + v.starting_z = starting_z + return v + +/datum/point/vector/proc/initialize_trajectory(pixel_speed, new_angle) + if(!isnull(pixel_speed)) + speed = pixel_speed + set_angle(new_angle) + +/datum/point/vector/proc/set_angle(new_angle) //calculations use "byond angle" where north is 0 instead of 90, and south is 180 instead of 270. + if(isnull(angle)) + return + angle = new_angle + update_offsets() + +/datum/point/vector/proc/update_offsets() + mpx = sin(angle) * speed + mpy = cos(angle) * speed + +/datum/point/vector/proc/set_speed(new_speed) + if(isnull(new_speed) || speed == new_speed) + return + speed = new_speed + update_offsets() + +/datum/point/vector/proc/increment(multiplier = 1) + iteration++ + x += mpx * (multiplier) + y += mpy * (multiplier) + +/datum/point/vector/proc/return_vector_after_increments(amount = 7, multiplier = 1, force_simulate = FALSE) + var/datum/point/vector/v = copy_to() + if(force_simulate) + for(var/i in 1 to amount) + v.increment(multiplier) + else + v.increment(multiplier * amount) + return v + +/datum/point/vector/proc/on_z_change() + return + +/datum/point/vector/processed //pixel_speed is per decisecond. + var/last_process = 0 + var/last_move = 0 + var/paused = FALSE + +/datum/point/vector/processed/Destroy() + STOP_PROCESSING(SSprojectiles, src) + return ..() + +/datum/point/vector/processed/proc/start() + last_process = world.time + last_move = world.time + START_PROCESSING(SSprojectiles, src) + +/datum/point/vector/processed/process() + if(paused) + last_move += world.time - last_process + last_process = world.time + return + var/needed_time = world.time - last_move + last_process = world.time + last_move = world.time + increment(needed_time / SSprojectiles.wait) diff --git a/code/datums/recipe.dm b/code/datums/recipe.dm index 0cd636bfeb8f..11141e3452e1 100644 --- a/code/datums/recipe.dm +++ b/code/datums/recipe.dm @@ -1,120 +1,120 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * - * /datum/recipe by rastaf0 13 apr 2011 * - * * * * * * * * * * * * * * * * * * * * * * * * * * - * This is powerful and flexible recipe system. - * It exists not only for food. - * supports both reagents and objects as prerequisites. - * In order to use this system you have to define a deriative from /datum/recipe - * * reagents are reagents. Acid, milc, booze, etc. - * * items are objects. Fruits, tools, circuit boards. - * * result is type to create as new object - * * time is optional parameter, you shall use in in your machine, - default /datum/recipe/ procs does not rely on this parameter. - * - * Functions you need: - * /datum/recipe/proc/make(var/obj/container as obj) - * Creates result inside container, - * deletes prerequisite reagents, - * transfers reagents from prerequisite objects, - * deletes all prerequisite objects (even not needed for recipe at the moment). - * - * /proc/select_recipe(list/datum/recipe/avaiable_recipes, obj/obj as obj, exact = 1) - * Wonderful function that select suitable recipe for you. - * obj is a machine (or magik hat) with prerequisites, - * exact = 0 forces algorithm to ignore superfluous stuff. - * - * - * Functions you do not need to call directly but could: - * /datum/recipe/proc/check_reagents(var/datum/reagents/avail_reagents) - * //1=precisely, 0=insufficiently, -1=superfluous - * - * /datum/recipe/proc/check_items(var/obj/container as obj) - * //1=precisely, 0=insufficiently, -1=superfluous - * - * */ - -/datum/recipe - var/list/reagents_list // example: = list(/datum/reagent/consumable/berryjuice = 5) // do not list same reagent twice - var/list/items // example: =list(/obj/item/crowbar, /obj/item/welder) // place /foo/bar before /foo - var/result //example: = /obj/item/reagent_containers/food/snacks/donut - var/time = 100 // 1/10 part of second - - -/datum/recipe/proc/check_reagents(datum/reagents/avail_reagents) //1=precisely, 0=insufficiently, -1=superfluous - . = 1 - for (var/r_r in reagents_list) - var/aval_r_amnt = avail_reagents.get_reagent_amount(r_r) - if (!(abs(aval_r_amnt - reagents_list[r_r])<0.5)) //if NOT equals - if (aval_r_amnt>reagents_list[r_r]) - . = -1 - else - return 0 - if ((reagents_list?(reagents_list.len):(0)) < avail_reagents.reagent_list.len) - return -1 - return . - -/datum/recipe/proc/check_items(obj/container) //1=precisely, 0=insufficiently, -1=superfluous - if (!items) - if (locate(/obj/) in container) - return -1 - else - return 1 - . = 1 - var/list/checklist = items.Copy() - for (var/obj/O in container) - var/found = 0 - for (var/type in checklist) - if (istype(O,type)) - checklist-=type - found = 1 - break - if (!found) - . = -1 - if (checklist.len) - return 0 - return . - -//general version -/datum/recipe/proc/make(obj/container) - var/obj/result_obj = new result(container) - for (var/obj/O in (container.contents-result_obj)) - O.reagents.trans_to(result_obj, O.reagents.total_volume) - qdel(O) - container.reagents.clear_reagents() - return result_obj - -// food-related -/datum/recipe/proc/make_food(obj/container) - var/obj/result_obj = new result(container) - for (var/obj/O in (container.contents-result_obj)) - if (O.reagents) - O.reagents.del_reagent(/datum/reagent/consumable/nutriment) - O.reagents.update_total() - O.reagents.trans_to(result_obj, O.reagents.total_volume) - qdel(O) - container.reagents.clear_reagents() - return result_obj - -/proc/select_recipe(list/datum/recipe/avaiable_recipes, obj/obj, exact = 1 as num) - if (!exact) - exact = -1 - var/list/datum/recipe/possible_recipes = new - for (var/datum/recipe/recipe in avaiable_recipes) - if (recipe.check_reagents(obj.reagents)==exact && recipe.check_items(obj)==exact) - possible_recipes+=recipe - if (possible_recipes.len==0) - return null - else if (possible_recipes.len==1) - return possible_recipes[1] - else //okay, let's select the most complicated recipe - var/r_count = 0 - var/i_count = 0 - . = possible_recipes[1] - for (var/datum/recipe/recipe in possible_recipes) - var/N_i = (recipe.items)?(recipe.items.len):0 - var/N_r = (recipe.reagents_list)?(recipe.reagents_list.len):0 - if (N_i > i_count || (N_i== i_count && N_r > r_count )) - r_count = N_r - i_count = N_i - . = recipe - return . +/* * * * * * * * * * * * * * * * * * * * * * * * * * + * /datum/recipe by rastaf0 13 apr 2011 * + * * * * * * * * * * * * * * * * * * * * * * * * * * + * This is powerful and flexible recipe system. + * It exists not only for food. + * supports both reagents and objects as prerequisites. + * In order to use this system you have to define a deriative from /datum/recipe + * * reagents are reagents. Acid, milc, booze, etc. + * * items are objects. Fruits, tools, circuit boards. + * * result is type to create as new object + * * time is optional parameter, you shall use in in your machine, + default /datum/recipe/ procs does not rely on this parameter. + * + * Functions you need: + * /datum/recipe/proc/make(var/obj/container as obj) + * Creates result inside container, + * deletes prerequisite reagents, + * transfers reagents from prerequisite objects, + * deletes all prerequisite objects (even not needed for recipe at the moment). + * + * /proc/select_recipe(list/datum/recipe/avaiable_recipes, obj/obj as obj, exact = 1) + * Wonderful function that select suitable recipe for you. + * obj is a machine (or magik hat) with prerequisites, + * exact = 0 forces algorithm to ignore superfluous stuff. + * + * + * Functions you do not need to call directly but could: + * /datum/recipe/proc/check_reagents(var/datum/reagents/avail_reagents) + * //1=precisely, 0=insufficiently, -1=superfluous + * + * /datum/recipe/proc/check_items(var/obj/container as obj) + * //1=precisely, 0=insufficiently, -1=superfluous + * + * */ + +/datum/recipe + var/list/reagents_list // example: = list(/datum/reagent/consumable/berryjuice = 5) // do not list same reagent twice + var/list/items // example: =list(/obj/item/crowbar, /obj/item/welder) // place /foo/bar before /foo + var/result //example: = /obj/item/reagent_containers/food/snacks/donut + var/time = 100 // 1/10 part of second + + +/datum/recipe/proc/check_reagents(datum/reagents/avail_reagents) //1=precisely, 0=insufficiently, -1=superfluous + . = 1 + for (var/r_r in reagents_list) + var/aval_r_amnt = avail_reagents.get_reagent_amount(r_r) + if (!(abs(aval_r_amnt - reagents_list[r_r])<0.5)) //if NOT equals + if (aval_r_amnt>reagents_list[r_r]) + . = -1 + else + return 0 + if ((reagents_list?(reagents_list.len):(0)) < avail_reagents.reagent_list.len) + return -1 + return . + +/datum/recipe/proc/check_items(obj/container) //1=precisely, 0=insufficiently, -1=superfluous + if (!items) + if (locate(/obj/) in container) + return -1 + else + return 1 + . = 1 + var/list/checklist = items.Copy() + for (var/obj/O in container) + var/found = 0 + for (var/type in checklist) + if (istype(O,type)) + checklist-=type + found = 1 + break + if (!found) + . = -1 + if (checklist.len) + return 0 + return . + +//general version +/datum/recipe/proc/make(obj/container) + var/obj/result_obj = new result(container) + for (var/obj/O in (container.contents-result_obj)) + O.reagents.trans_to(result_obj, O.reagents.total_volume) + qdel(O) + container.reagents.clear_reagents() + return result_obj + +// food-related +/datum/recipe/proc/make_food(obj/container) + var/obj/result_obj = new result(container) + for (var/obj/O in (container.contents-result_obj)) + if (O.reagents) + O.reagents.del_reagent(/datum/reagent/consumable/nutriment) + O.reagents.update_total() + O.reagents.trans_to(result_obj, O.reagents.total_volume) + qdel(O) + container.reagents.clear_reagents() + return result_obj + +/proc/select_recipe(list/datum/recipe/avaiable_recipes, obj/obj, exact = 1 as num) + if (!exact) + exact = -1 + var/list/datum/recipe/possible_recipes = new + for (var/datum/recipe/recipe in avaiable_recipes) + if (recipe.check_reagents(obj.reagents)==exact && recipe.check_items(obj)==exact) + possible_recipes+=recipe + if (possible_recipes.len==0) + return null + else if (possible_recipes.len==1) + return possible_recipes[1] + else //okay, let's select the most complicated recipe + var/r_count = 0 + var/i_count = 0 + . = possible_recipes[1] + for (var/datum/recipe/recipe in possible_recipes) + var/N_i = (recipe.items)?(recipe.items.len):0 + var/N_r = (recipe.reagents_list)?(recipe.reagents_list.len):0 + if (N_i > i_count || (N_i== i_count && N_r > r_count )) + r_count = N_r + i_count = N_i + . = recipe + return . diff --git a/code/datums/skills/_skill.dm b/code/datums/skills/_skill.dm index ca38b812c116..b593a2b418e4 100644 --- a/code/datums/skills/_skill.dm +++ b/code/datums/skills/_skill.dm @@ -1,10 +1,42 @@ +GLOBAL_LIST_INIT(skill_types, subtypesof(/datum/skill)) + /datum/skill - var/name = "Skill" + var/name = "Skilling" + var/title = "Skiller" var/desc = "the art of doing things" + ///Dictionary of modifier type - list of modifiers (indexed by level). 7 entries in each list for all 7 skill levels. var/modifiers = list(SKILL_SPEED_MODIFIER = list(1, 1, 1, 1, 1, 1, 1)) //Dictionary of modifier type - list of modifiers (indexed by level). 7 entries in each list for all 7 skill levels. + ///List Path pointing to the skill cape reward that will appear when a user finishes leveling up a skill + var/skill_cape_path + ///List associating different messages that appear on level up with different levels + var/list/levelUpMessages = list() + ///List associating different messages that appear on level up with different levels + var/list/levelDownMessages = list() /datum/skill/proc/get_skill_modifier(modifier, level) - return modifiers[modifier][level+1] //+1 because lists start at 1 + return modifiers[modifier][level] //Levels range from 1 (None) to 7 (Legendary) +/** + * new: sets up some lists. + * + *Can't happen in the datum's definition because these lists are not constant expressions + */ +/datum/skill/New() + . = ..() + levelUpMessages = list("What the hell is [name]? Tell an admin if you see this message.", //This first index shouldn't ever really be used + "I'm starting to figure out what [name] really is!", + "I'm getting a little better at [name]!", + "I'm getting much better at [name]!", + "I feel like I've become quite proficient at [name]!", + "After lots of practice, I've begun to truly understand the intricies \ + and surprising depth behind [name]. I now condsider myself a master [title].", + "Through incredible determination and effort, I've reached the peak of my [name] abiltities. I'm finally able to consider myself a legendary [title]!" ) + levelDownMessages = list("I have somehow completely lost all understanding of [name]. Please tell an admin if you see this.", + "I'm starting to forget what [name] really even is. I need more practice...", + "I'm getting a little worse at [name]. I'll need to keep practicing to get better at it...", + "I'm getting a little worse at [name]...", + "I'm losing my [name] expertise ....", + "I feel like I'm losing my mastery of [name].", + "I feel as though my legendary [name] skills have deteriorated. I'll need more intense training to recover my lost skills." ) /** * level_gained: Gives skill levelup messages to the user @@ -16,10 +48,34 @@ * * old_level - Similar to the above, but the level you had before levelling up. */ /datum/skill/proc/level_gained(var/datum/mind/mind, new_level, old_level)//just for announcements (doesn't go off if the xp gain is silent) - to_chat(mind.current, "I feel like I've become more proficient at [name]!") - + to_chat(mind.current, levelUpMessages[new_level]) //new_level will be a value from 1 to 6, so we get appropriate message from the 6-element levelUpMessages list /** * level_lost: See level_gained, same idea but fires on skill level-down */ /datum/skill/proc/level_lost(var/datum/mind/mind, new_level, old_level) - to_chat(mind.current, "I feel like I've become worse at [name]!") + to_chat(mind.current, levelDownMessages[old_level]) //old_level will be a value from 1 to 6, so we get appropriate message from the 6-element levelUpMessages list + +/** + * try_skill_reward: Checks to see if a user is eligable for a tangible reward for reaching a certain skill level + * + * Currently gives the user a special cloak when they reach a legendary level at any given skill + * Arguments: + * * mind - The mind that you'll want to send messages and rewards to + * * new_level - The current level of the user. Used to check if it meets the requirements for a reward + */ +/datum/skill/proc/try_skill_reward(var/datum/mind/mind, new_level) + if (new_level != SKILL_LEVEL_LEGENDARY) + return + if (!ispath(skill_cape_path)) + to_chat(mind.current, "My legendary [name] skill is quite impressive, though it seems the Professional [title] Association doesn't have any status symbols to commemorate my abilities with. I should let Centcom know of this travesty, maybe they can do something about it.") + return + if (LAZYFIND(mind.skills_rewarded, src.type)) + to_chat(mind.current, "It seems the Professional [title] Association won't send me another status symbol.") + return + var/obj/structure/closet/supplypod/bluespacepod/pod = new() + pod.landingDelay = 150 + pod.explosionSize = list(0,0,0,0) + to_chat(mind.current, "My legendary skill has attracted the attention of the Professional [title] Association. It seems they are sending me a status symbol to commemorate my abilities.") + var/turf/T = get_turf(mind.current) + new /obj/effect/DPtarget(T, pod , new skill_cape_path(T)) + LAZYADD(mind.skills_rewarded, src.type) diff --git a/code/datums/skills/cleaning.dm b/code/datums/skills/cleaning.dm index 6255c8c27f8e..98f173c22f78 100644 --- a/code/datums/skills/cleaning.dm +++ b/code/datums/skills/cleaning.dm @@ -1,4 +1,6 @@ /datum/skill/cleaning - name = "Scrubbing" + name = "Cleaning" + title = "Cleaner" desc = "It’s not who I am underneath, but what I mop up that defines me." modifiers = list(SKILL_SPEED_MODIFIER = list(1, 0.9, 0.8, 0.7, 0.6, 0.5, 0.36)) //speed also touches probability in using up a soap's charge + skill_cape_path = /obj/item/clothing/neck/cloak/skill_reward/cleaning diff --git a/code/datums/skills/gaming.dm b/code/datums/skills/gaming.dm index 5a8e098fda0d..b737b7ee1726 100644 --- a/code/datums/skills/gaming.dm +++ b/code/datums/skills/gaming.dm @@ -1,15 +1,13 @@ /datum/skill/gaming name = "Gaming" + title = "Gamer" desc = "My proficiency as a gamer. This helps me beat bosses with ease, powergame in Orion Trail, and makes me wanna slam some gamer fuel." modifiers = list(SKILL_PROBS_MODIFIER = list(0, 5, 10, 15, 15, 20, 25), SKILL_RANDS_MODIFIER = list(0, 1, 2, 3, 4, 5, 7)) + skill_cape_path = /obj/item/clothing/neck/cloak/skill_reward/gaming -/datum/skill/gaming/level_gained(var/datum/mind/mind, new_level, old_level) - switch(new_level) - if(SKILL_LEVEL_JOURNEYMAN)//gives slight extra perks outside roll modifiers - to_chat(mind.current, "I'm starting to pick up the meta of these arcade games. \ - If I were to minmax the optimal strat and accentuate my playstyle around well-refined tech...") - if(SKILL_LEVEL_LEGENDARY)//leads to a mysterious power... - to_chat(mind.current, "Maybe gamerfuel actually would help me play better...") - else - to_chat(mind.current, "I'm getting better at these arcade games!") +/datum/skill/gaming/New() + . = ..() + levelUpMessages[1] = "I'm starting to get a hang of the controls of these games..." + levelUpMessages[4] = "I'm starting to pick up the meta of these arcade games. If I were to minmax the optimal strat and accentuate my playstyle around well-refined tech..." + levelUpMessages[6] = "Through incredible determination and effort, I've reached the peak of my [name] abilities. I wonder how I can become any more powerful... Maybe gamer fuel would actually help me play better..?" diff --git a/code/datums/skills/medical.dm b/code/datums/skills/medical.dm index 1f4c7bda0608..f9bd5d541e6c 100644 --- a/code/datums/skills/medical.dm +++ b/code/datums/skills/medical.dm @@ -1,4 +1,6 @@ -/datum/skill/medical - name = "Medical" +/datum/skill/healing + name = "Healing" + title = "Healer" desc = "From Bandaids to biopsies, this improves your ability to get people back up both in the field and on the operating table." modifiers = list(SKILL_SPEED_MODIFIER = list(1, 0.95, 0.9, 0.85, 0.75, 0.6, 0.5)) + skill_cape_path = /obj/item/clothing/neck/cloak/skill_reward/healing diff --git a/code/datums/skills/mining.dm b/code/datums/skills/mining.dm index 43f8eee46079..e1b698a3d871 100644 --- a/code/datums/skills/mining.dm +++ b/code/datums/skills/mining.dm @@ -1,4 +1,6 @@ /datum/skill/mining name = "Mining" + title = "Miner" desc = "A dwarf's biggest skill, after drinking." modifiers = list(SKILL_SPEED_MODIFIER = list(1, 0.95, 0.9, 0.85, 0.75, 0.6, 0.5),SKILL_PROBS_MODIFIER=list(10, 15, 20, 25, 30, 35, 40)) + skill_cape_path = /obj/item/clothing/neck/cloak/skill_reward/mining diff --git a/code/datums/spawners_menu.dm b/code/datums/spawners_menu.dm index a6f49e9c323c..95a7d8e63391 100644 --- a/code/datums/spawners_menu.dm +++ b/code/datums/spawners_menu.dm @@ -1,62 +1,65 @@ -/datum/spawners_menu - var/mob/dead/observer/owner - -/datum/spawners_menu/New(mob/dead/observer/new_owner) - if(!istype(new_owner)) - qdel(src) - owner = new_owner - -/datum/spawners_menu/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.observer_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "SpawnersMenu", "Spawners Menu", 700, 600, master_ui, state) - ui.open() - -/datum/spawners_menu/ui_data(mob/user) - var/list/data = list() - data["spawners"] = list() - for(var/spawner in GLOB.mob_spawners) - var/list/this = list() - this["name"] = spawner - this["short_desc"] = "" - this["flavor_text"] = "" - this["important_warning"] = "" - this["refs"] = list() - for(var/spawner_obj in GLOB.mob_spawners[spawner]) - this["refs"] += "[REF(spawner_obj)]" - if(!this["desc"]) - if(istype(spawner_obj, /obj/effect/mob_spawn)) - var/obj/effect/mob_spawn/MS = spawner_obj - this["short_desc"] = MS.short_desc - this["flavor_text"] = MS.flavour_text - this["important_info"] = MS.important_info - else - var/obj/O = spawner_obj - this["desc"] = O.desc - this["amount_left"] = LAZYLEN(GLOB.mob_spawners[spawner]) - data["spawners"] += list(this) - - return data - -/datum/spawners_menu/ui_act(action, params) - if(..()) - return - - var/group_name = params["name"] - if(!group_name || !(group_name in GLOB.mob_spawners)) - return - var/list/spawnerlist = GLOB.mob_spawners[group_name] - if(!spawnerlist.len) - return - var/obj/effect/mob_spawn/MS = pick(spawnerlist) - if(!istype(MS) || !(MS in GLOB.poi_list)) - return - switch(action) - if("jump") - if(MS) - owner.forceMove(get_turf(MS)) - . = TRUE - if("spawn") - if(MS) - MS.attack_ghost(owner) - . = TRUE +/datum/spawners_menu + var/mob/dead/observer/owner + +/datum/spawners_menu/New(mob/dead/observer/new_owner) + if(!istype(new_owner)) + qdel(src) + owner = new_owner + +/datum/spawners_menu/ui_state(mob/user) + return GLOB.observer_state + +/datum/spawners_menu/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "SpawnersMenu") + ui.open() + +/datum/spawners_menu/ui_data(mob/user) + var/list/data = list() + data["spawners"] = list() + for(var/spawner in GLOB.mob_spawners) + var/list/this = list() + this["name"] = spawner + this["short_desc"] = "" + this["flavor_text"] = "" + this["important_warning"] = "" + this["refs"] = list() + for(var/spawner_obj in GLOB.mob_spawners[spawner]) + this["refs"] += "[REF(spawner_obj)]" + if(!this["desc"]) + if(istype(spawner_obj, /obj/effect/mob_spawn)) + var/obj/effect/mob_spawn/MS = spawner_obj + this["short_desc"] = MS.short_desc + this["flavor_text"] = MS.flavour_text + this["important_info"] = MS.important_info + else + var/obj/O = spawner_obj + this["desc"] = O.desc + this["amount_left"] = LAZYLEN(GLOB.mob_spawners[spawner]) + data["spawners"] += list(this) + + return data + +/datum/spawners_menu/ui_act(action, params) + if(..()) + return + + var/group_name = params["name"] + if(!group_name || !(group_name in GLOB.mob_spawners)) + return + var/list/spawnerlist = GLOB.mob_spawners[group_name] + if(!spawnerlist.len) + return + var/obj/effect/mob_spawn/MS = pick(spawnerlist) + if(!istype(MS) || !(MS in GLOB.poi_list)) + return + switch(action) + if("jump") + if(MS) + owner.forceMove(get_turf(MS)) + . = TRUE + if("spawn") + if(MS) + MS.attack_ghost(owner) + . = TRUE diff --git a/code/datums/tgs_event_handler.dm b/code/datums/tgs_event_handler.dm new file mode 100644 index 000000000000..434450b9bec5 --- /dev/null +++ b/code/datums/tgs_event_handler.dm @@ -0,0 +1,41 @@ +/datum/tgs_event_handler/impl + var/datum/timedevent/reattach_timer + +/datum/tgs_event_handler/impl/HandleEvent(event_code, ...) + switch(event_code) + if(TGS_EVENT_REBOOT_MODE_CHANGE) + var/list/reboot_mode_lookup = list ("[TGS_REBOOT_MODE_NORMAL]" = "be normal", "[TGS_REBOOT_MODE_SHUTDOWN]" = "shutdown the server", "[TGS_REBOOT_MODE_RESTART]" = "hard restart the server") + var/old_reboot_mode = args[2] + var/new_reboot_mode = args[3] + message_admins("TGS: 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]]") + if(TGS_EVENT_INSTANCE_RENAMED) + message_admins("TGS: Instance renamed to from [world.TgsInstanceName()] to [args[2]]") + if(TGS_EVENT_COMPILE_START) + message_admins("TGS: Deployment started, new game version incoming...") + if(TGS_EVENT_COMPILE_CANCELLED) + message_admins("TGS: Deployment cancelled!") + if(TGS_EVENT_COMPILE_FAILURE) + message_admins("TGS: Deployment failed!") + if(TGS_EVENT_DEPLOYMENT_COMPLETE) + message_admins("TGS: Deployment complete!") + to_chat(world, "Server updated, changes will be applied on the next round...") + if(TGS_EVENT_WATCHDOG_DETACH) + message_admins("TGS restarting...") + reattach_timer = addtimer(CALLBACK(src, .proc/LateOnReattach), 1 MINUTES) + if(TGS_EVENT_WATCHDOG_REATTACH) + var/datum/tgs_version/old_version = world.TgsVersion() + var/datum/tgs_version/new_version = args[2] + if(!old_version.Equals(new_version)) + to_chat(world, "TGS updated to v[new_version.deprefixed_parameter]") + else + message_admins("TGS: Back online") + if(reattach_timer) + deltimer(reattach_timer) + reattach_timer = null + if(TGS_EVENT_WATCHDOG_SHUTDOWN) + to_chat_immediate(world, "Server is shutting down!") + +/datum/tgs_event_handler/impl/proc/LateOnReattach() + message_admins("Warning: TGS hasn't notified us of it coming back for a full minute! Is there a problem?") diff --git a/code/datums/verbs.dm b/code/datums/verbs.dm index 8301c5fe384f..39afde14d03c 100644 --- a/code/datums/verbs.dm +++ b/code/datums/verbs.dm @@ -1,102 +1,102 @@ -/datum/verbs - var/name - var/list/children - var/datum/verbs/parent - var/list/verblist - var/abstract = FALSE - -//returns the master list for verbs of a type -/datum/verbs/proc/GetList() - CRASH("Abstract verblist for [type]") - -//do things for each entry in Generate_list -//return value sets Generate_list[verbpath] -/datum/verbs/proc/HandleVerb(list/entry, procpath/verbpath, ...) - return entry - -/datum/verbs/New() - var/mainlist = GetList() - var/ourentry = mainlist[type] - children = list() - verblist = list() - if (ourentry) - if (!islist(ourentry)) //some of our childern already loaded - qdel(src) - CRASH("Verb double load: [type]") - Add_children(ourentry) - - mainlist[type] = src - - Load_verbs(type, typesof("[type]/verb")) - - var/datum/verbs/parent = mainlist[parent_type] - if (!parent) - mainlist[parent_type] = list(src) - else if (islist(parent)) - parent += src - else - parent.Add_children(list(src)) - -/datum/verbs/proc/Set_parent(datum/verbs/_parent) - parent = _parent - if (abstract) - parent.Add_children(children) - var/list/verblistoftypes = list() - for(var/thing in verblist) - LAZYADD(verblistoftypes[verblist[thing]], thing) - - for(var/verbparenttype in verblistoftypes) - parent.Load_verbs(verbparenttype, verblistoftypes[verbparenttype]) - -/datum/verbs/proc/Add_children(list/kids) - if (abstract && parent) - parent.Add_children(kids) - return - - for(var/thing in kids) - var/datum/verbs/item = thing - item.Set_parent(src) - if (!item.abstract) - children += item - -/datum/verbs/proc/Load_verbs(verb_parent_type, list/verbs) - if (abstract && parent) - parent.Load_verbs(verb_parent_type, verbs) - return - - for (var/verbpath in verbs) - verblist[verbpath] = verb_parent_type - -/datum/verbs/proc/Generate_list(...) - . = list() - if (length(children)) - for (var/thing in children) - var/datum/verbs/child = thing - var/list/childlist = child.Generate_list(arglist(args)) - if (childlist) - var/childname = "[child]" - if (childname == "[child.type]") - var/list/tree = splittext(childname, "/") - childname = tree[tree.len] - .[child.type] = "parent=[url_encode(type)];name=[childname]" - . += childlist - - for (var/thing in verblist) - var/procpath/verbpath = thing - if (!verbpath) - stack_trace("Bad VERB in [type] verblist: [english_list(verblist)]") - var/list/entry = list() - entry["parent"] = "[type]" - entry["name"] = verbpath.desc - if (verbpath.name[1] == "@") - entry["command"] = copytext(verbpath.name, length(verbpath.name[1]) + 1) - else - entry["command"] = replacetext(verbpath.name, " ", "-") - - .[verbpath] = HandleVerb(arglist(list(entry, verbpath) + args)) - -/world/proc/LoadVerbs(verb_type) - if(!ispath(verb_type, /datum/verbs) || verb_type == /datum/verbs) - CRASH("Invalid verb_type: [verb_type]") - for (var/typepath in subtypesof(verb_type)) - new typepath() +/datum/verbs + var/name + var/list/children + var/datum/verbs/parent + var/list/verblist + var/abstract = FALSE + +//returns the master list for verbs of a type +/datum/verbs/proc/GetList() + CRASH("Abstract verblist for [type]") + +//do things for each entry in Generate_list +//return value sets Generate_list[verbpath] +/datum/verbs/proc/HandleVerb(list/entry, procpath/verbpath, ...) + return entry + +/datum/verbs/New() + var/mainlist = GetList() + var/ourentry = mainlist[type] + children = list() + verblist = list() + if (ourentry) + if (!islist(ourentry)) //some of our childern already loaded + qdel(src) + CRASH("Verb double load: [type]") + Add_children(ourentry) + + mainlist[type] = src + + Load_verbs(type, typesof("[type]/verb")) + + var/datum/verbs/parent = mainlist[parent_type] + if (!parent) + mainlist[parent_type] = list(src) + else if (islist(parent)) + parent += src + else + parent.Add_children(list(src)) + +/datum/verbs/proc/Set_parent(datum/verbs/_parent) + parent = _parent + if (abstract) + parent.Add_children(children) + var/list/verblistoftypes = list() + for(var/thing in verblist) + LAZYADD(verblistoftypes[verblist[thing]], thing) + + for(var/verbparenttype in verblistoftypes) + parent.Load_verbs(verbparenttype, verblistoftypes[verbparenttype]) + +/datum/verbs/proc/Add_children(list/kids) + if (abstract && parent) + parent.Add_children(kids) + return + + for(var/thing in kids) + var/datum/verbs/item = thing + item.Set_parent(src) + if (!item.abstract) + children += item + +/datum/verbs/proc/Load_verbs(verb_parent_type, list/verbs) + if (abstract && parent) + parent.Load_verbs(verb_parent_type, verbs) + return + + for (var/verbpath in verbs) + verblist[verbpath] = verb_parent_type + +/datum/verbs/proc/Generate_list(...) + . = list() + if (length(children)) + for (var/thing in children) + var/datum/verbs/child = thing + var/list/childlist = child.Generate_list(arglist(args)) + if (childlist) + var/childname = "[child]" + if (childname == "[child.type]") + var/list/tree = splittext(childname, "/") + childname = tree[tree.len] + .[child.type] = "parent=[url_encode(type)];name=[childname]" + . += childlist + + for (var/thing in verblist) + var/procpath/verbpath = thing + if (!verbpath) + stack_trace("Bad VERB in [type] verblist: [english_list(verblist)]") + var/list/entry = list() + entry["parent"] = "[type]" + entry["name"] = verbpath.desc + if (verbpath.name[1] == "@") + entry["command"] = copytext(verbpath.name, length(verbpath.name[1]) + 1) + else + entry["command"] = replacetext(verbpath.name, " ", "-") + + .[verbpath] = HandleVerb(arglist(list(entry, verbpath) + args)) + +/world/proc/LoadVerbs(verb_type) + if(!ispath(verb_type, /datum/verbs) || verb_type == /datum/verbs) + CRASH("Invalid verb_type: [verb_type]") + for (var/typepath in subtypesof(verb_type)) + new typepath() diff --git a/code/datums/wires/_wires.dm b/code/datums/wires/_wires.dm index 46d1a7261f68..dcac1b70df7a 100644 --- a/code/datums/wires/_wires.dm +++ b/code/datums/wires/_wires.dm @@ -1,297 +1,299 @@ -#define MAXIMUM_EMP_WIRES 3 - -/proc/is_wire_tool(obj/item/I) - if(!I) - return - - if(I.tool_behaviour == TOOL_WIRECUTTER || I.tool_behaviour == TOOL_MULTITOOL) - return TRUE - if(istype(I, /obj/item/assembly)) - var/obj/item/assembly/A = I - if(A.attachable) - return TRUE - -/atom/proc/attempt_wire_interaction(mob/user) - if(!wires) - return WIRE_INTERACTION_FAIL - if(!user.CanReach(src)) - return WIRE_INTERACTION_FAIL - wires.interact(user) - return WIRE_INTERACTION_BLOCK - -/datum/wires - var/atom/holder = null // The holder (atom that contains these wires). - var/holder_type = null // The holder's typepath (used to make wire colors common to all holders). - var/proper_name = "Unknown" // The display name for the wire set shown in station blueprints. Not used if randomize is true or it's an item NT wouldn't know about (Explosives/Nuke) - - var/list/wires = list() // List of wires. - var/list/cut_wires = list() // List of wires that have been cut. - var/list/colors = list() // Dictionary of colors to wire. - var/list/assemblies = list() // List of attached assemblies. - var/randomize = 0 // If every instance of these wires should be random. - // Prevents wires from showing up in station blueprints - -/datum/wires/New(atom/holder) - ..() - if(!istype(holder, holder_type)) - CRASH("Wire holder is not of the expected type!") - - src.holder = holder - if(randomize) - randomize() - else - if(!GLOB.wire_color_directory[holder_type]) - randomize() - GLOB.wire_color_directory[holder_type] = colors - GLOB.wire_name_directory[holder_type] = proper_name - else - colors = GLOB.wire_color_directory[holder_type] - -/datum/wires/Destroy() - holder = null - assemblies = list() - return ..() - -/datum/wires/proc/add_duds(duds) - while(duds) - var/dud = WIRE_DUD_PREFIX + "[--duds]" - if(dud in wires) - continue - wires += dud - -/datum/wires/proc/randomize() - var/static/list/possible_colors = list( - "blue", - "brown", - "crimson", - "cyan", - "gold", - "grey", - "green", - "magenta", - "orange", - "pink", - "purple", - "red", - "silver", - "violet", - "white", - "yellow" - ) - - var/list/my_possible_colors = possible_colors.Copy() - - for(var/wire in shuffle(wires)) - colors[pick_n_take(my_possible_colors)] = wire - -/datum/wires/proc/shuffle_wires() - colors.Cut() - randomize() - -/datum/wires/proc/repair() - cut_wires.Cut() - -/datum/wires/proc/get_wire(color) - return colors[color] - -/datum/wires/proc/get_color_of_wire(wire_type) - for(var/color in colors) - var/other_type = colors[color] - if(wire_type == other_type) - return color - -/datum/wires/proc/get_attached(color) - if(assemblies[color]) - return assemblies[color] - return null - -/datum/wires/proc/is_attached(color) - if(assemblies[color]) - return TRUE - -/datum/wires/proc/is_cut(wire) - return (wire in cut_wires) - -/datum/wires/proc/is_color_cut(color) - return is_cut(get_wire(color)) - -/datum/wires/proc/is_all_cut() - if(cut_wires.len == wires.len) - return TRUE - -/datum/wires/proc/is_dud(wire) - return findtext(wire, WIRE_DUD_PREFIX, 1, length(WIRE_DUD_PREFIX) + 1) - -/datum/wires/proc/is_dud_color(color) - return is_dud(get_wire(color)) - -/datum/wires/proc/cut(wire) - if(is_cut(wire)) - cut_wires -= wire - on_cut(wire, mend = TRUE) - else - cut_wires += wire - on_cut(wire, mend = FALSE) - -/datum/wires/proc/cut_color(color) - cut(get_wire(color)) - -/datum/wires/proc/cut_random() - cut(wires[rand(1, wires.len)]) - -/datum/wires/proc/cut_all() - for(var/wire in wires) - cut(wire) - -/datum/wires/proc/pulse(wire, user) - if(is_cut(wire)) - return - on_pulse(wire, user) - -/datum/wires/proc/pulse_color(color, mob/living/user) - pulse(get_wire(color), user) - -/datum/wires/proc/pulse_assembly(obj/item/assembly/S) - for(var/color in assemblies) - if(S == assemblies[color]) - pulse_color(color) - return TRUE - -/datum/wires/proc/attach_assembly(color, obj/item/assembly/S) - if(S && istype(S) && S.attachable && !is_attached(color)) - assemblies[color] = S - S.forceMove(holder) - S.connected = src - return S - -/datum/wires/proc/detach_assembly(color) - var/obj/item/assembly/S = get_attached(color) - if(S && istype(S)) - assemblies -= color - S.connected = null - S.forceMove(holder.drop_location()) - return S - -/// Called from [/atom/proc/emp_act] -/datum/wires/proc/emp_pulse() - var/list/possible_wires = shuffle(wires) - var/remaining_pulses = MAXIMUM_EMP_WIRES - - for(var/wire in possible_wires) - if(prob(33)) - pulse(wire) - remaining_pulses-- - if(!remaining_pulses) - break - -// Overridable Procs -/datum/wires/proc/interactable(mob/user) - return TRUE - -/datum/wires/proc/get_status() - return list() - -/datum/wires/proc/on_cut(wire, mend = FALSE) - return - -/datum/wires/proc/on_pulse(wire, user) - return -// End Overridable Procs - -/datum/wires/proc/interact(mob/user) - if(!interactable(user)) - return - ui_interact(user) - for(var/A in assemblies) - var/obj/item/I = assemblies[A] - if(istype(I) && I.on_found(user)) - return - -/datum/wires/ui_host() - return holder - -/datum/wires/ui_status(mob/user) - if(interactable(user)) - return ..() - return UI_CLOSE - -/datum/wires/ui_interact(mob/user, ui_key = "wires", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.physical_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if (!ui) - ui = new(user, src, ui_key, "Wires", "[holder.name] Wires", 350, 150 + wires.len * 30, master_ui, state) - ui.open() - -/datum/wires/ui_data(mob/user) - var/list/data = list() - var/list/payload = list() - var/reveal_wires = FALSE - - // Admin ghost can see a purpose of each wire. - if(IsAdminGhost(user)) - reveal_wires = TRUE - - // Same for anyone with an abductor multitool. - else if(user.is_holding_item_of_type(/obj/item/multitool/abductor)) - reveal_wires = TRUE - - // Station blueprints do that too, but only if the wires are not randomized. - else if(user.is_holding_item_of_type(/obj/item/areaeditor/blueprints) && !randomize) - reveal_wires = TRUE - - for(var/color in colors) - payload.Add(list(list( - "color" = color, - "wire" = ((reveal_wires && !is_dud_color(color)) ? get_wire(color) : null), - "cut" = is_color_cut(color), - "attached" = is_attached(color) - ))) - data["wires"] = payload - data["status"] = get_status() - return data - -/datum/wires/ui_act(action, params) - if(..() || !interactable(usr)) - return - var/target_wire = params["wire"] - var/mob/living/L = usr - var/obj/item/I - switch(action) - if("cut") - I = L.is_holding_tool_quality(TOOL_WIRECUTTER) - if(I || IsAdminGhost(usr)) - if(I && holder) - I.play_tool_sound(holder, 20) - cut_color(target_wire) - . = TRUE - else - to_chat(L, "You need wirecutters!") - if("pulse") - I = L.is_holding_tool_quality(TOOL_MULTITOOL) - if(I || IsAdminGhost(usr)) - if(I && holder) - I.play_tool_sound(holder, 20) - pulse_color(target_wire, L) - . = TRUE - else - to_chat(L, "You need a multitool!") - if("attach") - if(is_attached(target_wire)) - I = detach_assembly(target_wire) - if(I) - L.put_in_hands(I) - . = TRUE - else - I = L.get_active_held_item() - if(istype(I, /obj/item/assembly)) - var/obj/item/assembly/A = I - if(A.attachable) - if(!L.temporarilyRemoveItemFromInventory(A)) - return - if(!attach_assembly(target_wire, A)) - A.forceMove(L.drop_location()) - . = TRUE - else - to_chat(L, "You need an attachable assembly!") - -#undef MAXIMUM_EMP_WIRES +#define MAXIMUM_EMP_WIRES 3 + +/proc/is_wire_tool(obj/item/I) + if(!I) + return + + if(I.tool_behaviour == TOOL_WIRECUTTER || I.tool_behaviour == TOOL_MULTITOOL) + return TRUE + if(istype(I, /obj/item/assembly)) + var/obj/item/assembly/A = I + if(A.attachable) + return TRUE + +/atom/proc/attempt_wire_interaction(mob/user) + if(!wires) + return WIRE_INTERACTION_FAIL + if(!user.CanReach(src)) + return WIRE_INTERACTION_FAIL + wires.interact(user) + return WIRE_INTERACTION_BLOCK + +/datum/wires + var/atom/holder = null // The holder (atom that contains these wires). + var/holder_type = null // The holder's typepath (used to make wire colors common to all holders). + var/proper_name = "Unknown" // The display name for the wire set shown in station blueprints. Not used if randomize is true or it's an item NT wouldn't know about (Explosives/Nuke) + + var/list/wires = list() // List of wires. + var/list/cut_wires = list() // List of wires that have been cut. + var/list/colors = list() // Dictionary of colors to wire. + var/list/assemblies = list() // List of attached assemblies. + var/randomize = 0 // If every instance of these wires should be random. + // Prevents wires from showing up in station blueprints + +/datum/wires/New(atom/holder) + ..() + if(!istype(holder, holder_type)) + CRASH("Wire holder is not of the expected type!") + + src.holder = holder + if(randomize) + randomize() + else + if(!GLOB.wire_color_directory[holder_type]) + randomize() + GLOB.wire_color_directory[holder_type] = colors + GLOB.wire_name_directory[holder_type] = proper_name + else + colors = GLOB.wire_color_directory[holder_type] + +/datum/wires/Destroy() + holder = null + assemblies = list() + return ..() + +/datum/wires/proc/add_duds(duds) + while(duds) + var/dud = WIRE_DUD_PREFIX + "[--duds]" + if(dud in wires) + continue + wires += dud + +/datum/wires/proc/randomize() + var/static/list/possible_colors = list( + "blue", + "brown", + "crimson", + "cyan", + "gold", + "grey", + "green", + "magenta", + "orange", + "pink", + "purple", + "red", + "silver", + "violet", + "white", + "yellow" + ) + + var/list/my_possible_colors = possible_colors.Copy() + + for(var/wire in shuffle(wires)) + colors[pick_n_take(my_possible_colors)] = wire + +/datum/wires/proc/shuffle_wires() + colors.Cut() + randomize() + +/datum/wires/proc/repair() + cut_wires.Cut() + +/datum/wires/proc/get_wire(color) + return colors[color] + +/datum/wires/proc/get_color_of_wire(wire_type) + for(var/color in colors) + var/other_type = colors[color] + if(wire_type == other_type) + return color + +/datum/wires/proc/get_attached(color) + if(assemblies[color]) + return assemblies[color] + return null + +/datum/wires/proc/is_attached(color) + if(assemblies[color]) + return TRUE + +/datum/wires/proc/is_cut(wire) + return (wire in cut_wires) + +/datum/wires/proc/is_color_cut(color) + return is_cut(get_wire(color)) + +/datum/wires/proc/is_all_cut() + if(cut_wires.len == wires.len) + return TRUE + +/datum/wires/proc/is_dud(wire) + return findtext(wire, WIRE_DUD_PREFIX, 1, length(WIRE_DUD_PREFIX) + 1) + +/datum/wires/proc/is_dud_color(color) + return is_dud(get_wire(color)) + +/datum/wires/proc/cut(wire) + if(is_cut(wire)) + cut_wires -= wire + on_cut(wire, mend = TRUE) + else + cut_wires += wire + on_cut(wire, mend = FALSE) + +/datum/wires/proc/cut_color(color) + cut(get_wire(color)) + +/datum/wires/proc/cut_random() + cut(wires[rand(1, wires.len)]) + +/datum/wires/proc/cut_all() + for(var/wire in wires) + cut(wire) + +/datum/wires/proc/pulse(wire, user) + if(is_cut(wire)) + return + on_pulse(wire, user) + +/datum/wires/proc/pulse_color(color, mob/living/user) + pulse(get_wire(color), user) + +/datum/wires/proc/pulse_assembly(obj/item/assembly/S) + for(var/color in assemblies) + if(S == assemblies[color]) + pulse_color(color) + return TRUE + +/datum/wires/proc/attach_assembly(color, obj/item/assembly/S) + if(S && istype(S) && S.attachable && !is_attached(color)) + assemblies[color] = S + S.forceMove(holder) + S.connected = src + return S + +/datum/wires/proc/detach_assembly(color) + var/obj/item/assembly/S = get_attached(color) + if(S && istype(S)) + assemblies -= color + S.connected = null + S.forceMove(holder.drop_location()) + return S + +/// Called from [/atom/proc/emp_act] +/datum/wires/proc/emp_pulse() + var/list/possible_wires = shuffle(wires) + var/remaining_pulses = MAXIMUM_EMP_WIRES + + for(var/wire in possible_wires) + if(prob(33)) + pulse(wire) + remaining_pulses-- + if(!remaining_pulses) + break + +// Overridable Procs +/datum/wires/proc/interactable(mob/user) + return TRUE + +/datum/wires/proc/get_status() + return list() + +/datum/wires/proc/on_cut(wire, mend = FALSE) + return + +/datum/wires/proc/on_pulse(wire, user) + return +// End Overridable Procs + +/datum/wires/proc/interact(mob/user) + if(!interactable(user)) + return + ui_interact(user) + for(var/A in assemblies) + var/obj/item/I = assemblies[A] + if(istype(I) && I.on_found(user)) + return + +/datum/wires/ui_host() + return holder + +/datum/wires/ui_status(mob/user) + if(interactable(user)) + return ..() + return UI_CLOSE + +/datum/wires/ui_state(mob/user) + return GLOB.physical_state + +/datum/wires/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if (!ui) + ui = new(user, src, "Wires", "[holder.name] Wires") + ui.open() + +/datum/wires/ui_data(mob/user) + var/list/data = list() + var/list/payload = list() + var/reveal_wires = FALSE + + // Admin ghost can see a purpose of each wire. + if(IsAdminGhost(user)) + reveal_wires = TRUE + + // Same for anyone with an abductor multitool. + else if(user.is_holding_item_of_type(/obj/item/multitool/abductor)) + reveal_wires = TRUE + + // Station blueprints do that too, but only if the wires are not randomized. + else if(user.is_holding_item_of_type(/obj/item/areaeditor/blueprints) && !randomize) + reveal_wires = TRUE + + for(var/color in colors) + payload.Add(list(list( + "color" = color, + "wire" = ((reveal_wires && !is_dud_color(color)) ? get_wire(color) : null), + "cut" = is_color_cut(color), + "attached" = is_attached(color) + ))) + data["wires"] = payload + data["status"] = get_status() + return data + +/datum/wires/ui_act(action, params) + if(..() || !interactable(usr)) + return + var/target_wire = params["wire"] + var/mob/living/L = usr + var/obj/item/I + switch(action) + if("cut") + I = L.is_holding_tool_quality(TOOL_WIRECUTTER) + if(I || IsAdminGhost(usr)) + if(I && holder) + I.play_tool_sound(holder, 20) + cut_color(target_wire) + . = TRUE + else + to_chat(L, "You need wirecutters!") + if("pulse") + I = L.is_holding_tool_quality(TOOL_MULTITOOL) + if(I || IsAdminGhost(usr)) + if(I && holder) + I.play_tool_sound(holder, 20) + pulse_color(target_wire, L) + . = TRUE + else + to_chat(L, "You need a multitool!") + if("attach") + if(is_attached(target_wire)) + I = detach_assembly(target_wire) + if(I) + L.put_in_hands(I) + . = TRUE + else + I = L.get_active_held_item() + if(istype(I, /obj/item/assembly)) + var/obj/item/assembly/A = I + if(A.attachable) + if(!L.temporarilyRemoveItemFromInventory(A)) + return + if(!attach_assembly(target_wire, A)) + A.forceMove(L.drop_location()) + . = TRUE + else + to_chat(L, "You need an attachable assembly!") + +#undef MAXIMUM_EMP_WIRES diff --git a/code/datums/wires/airalarm.dm b/code/datums/wires/airalarm.dm index 237d36f51bd3..c46e06a4adfa 100644 --- a/code/datums/wires/airalarm.dm +++ b/code/datums/wires/airalarm.dm @@ -1,74 +1,74 @@ -/datum/wires/airalarm - holder_type = /obj/machinery/airalarm - proper_name = "Air Alarm" - -/datum/wires/airalarm/New(atom/holder) - wires = list( - WIRE_POWER, - WIRE_IDSCAN, WIRE_AI, - WIRE_PANIC, WIRE_ALARM - ) - add_duds(3) - ..() - -/datum/wires/airalarm/interactable(mob/user) - var/obj/machinery/airalarm/A = holder - if(A.panel_open && A.buildstage == 2) - return TRUE - -/datum/wires/airalarm/get_status() - var/obj/machinery/airalarm/A = holder - var/list/status = list() - status += "The interface light is [A.locked ? "red" : "green"]." - status += "The short indicator is [A.shorted ? "lit" : "off"]." - status += "The AI connection light is [!A.aidisabled ? "on" : "off"]." - return status - -/datum/wires/airalarm/on_pulse(wire) - var/obj/machinery/airalarm/A = holder - switch(wire) - if(WIRE_POWER) // Short out for a long time. - if(!A.shorted) - A.shorted = TRUE - A.update_icon() - addtimer(CALLBACK(A, /obj/machinery/airalarm.proc/reset, wire), 1200) - if(WIRE_IDSCAN) // Toggle lock. - A.locked = !A.locked - if(WIRE_AI) // Disable AI control for a while. - if(!A.aidisabled) - A.aidisabled = TRUE - addtimer(CALLBACK(A, /obj/machinery/airalarm.proc/reset, wire), 100) - if(WIRE_PANIC) // Toggle panic siphon. - if(!A.shorted) - if(A.mode == 1) // AALARM_MODE_SCRUB - A.mode = 3 // AALARM_MODE_PANIC - else - A.mode = 1 // AALARM_MODE_SCRUB - A.apply_mode(usr) - if(WIRE_ALARM) // Clear alarms. - var/area/AA = get_area(A) - if(AA.atmosalert(0, holder)) - A.post_alert(0) - A.update_icon() - -/datum/wires/airalarm/on_cut(wire, mend) - var/obj/machinery/airalarm/A = holder - switch(wire) - if(WIRE_POWER) // Short out forever. - A.shock(usr, 50) - A.shorted = !mend - A.update_icon() - if(WIRE_IDSCAN) - if(!mend) - A.locked = TRUE - if(WIRE_AI) - A.aidisabled = mend // Enable/disable AI control. - if(WIRE_PANIC) // Force panic syphon on. - if(!mend && !A.shorted) - A.mode = 3 // AALARM_MODE_PANIC - A.apply_mode(usr) - if(WIRE_ALARM) // Post alarm. - var/area/AA = get_area(A) - if(AA.atmosalert(2, holder)) - A.post_alert(2) - A.update_icon() +/datum/wires/airalarm + holder_type = /obj/machinery/airalarm + proper_name = "Air Alarm" + +/datum/wires/airalarm/New(atom/holder) + wires = list( + WIRE_POWER, + WIRE_IDSCAN, WIRE_AI, + WIRE_PANIC, WIRE_ALARM + ) + add_duds(3) + ..() + +/datum/wires/airalarm/interactable(mob/user) + var/obj/machinery/airalarm/A = holder + if(A.panel_open && A.buildstage == 2) + return TRUE + +/datum/wires/airalarm/get_status() + var/obj/machinery/airalarm/A = holder + var/list/status = list() + status += "The interface light is [A.locked ? "red" : "green"]." + status += "The short indicator is [A.shorted ? "lit" : "off"]." + status += "The AI connection light is [!A.aidisabled ? "on" : "off"]." + return status + +/datum/wires/airalarm/on_pulse(wire) + var/obj/machinery/airalarm/A = holder + switch(wire) + if(WIRE_POWER) // Short out for a long time. + if(!A.shorted) + A.shorted = TRUE + A.update_icon() + addtimer(CALLBACK(A, /obj/machinery/airalarm.proc/reset, wire), 1200) + if(WIRE_IDSCAN) // Toggle lock. + A.locked = !A.locked + if(WIRE_AI) // Disable AI control for a while. + if(!A.aidisabled) + A.aidisabled = TRUE + addtimer(CALLBACK(A, /obj/machinery/airalarm.proc/reset, wire), 100) + if(WIRE_PANIC) // Toggle panic siphon. + if(!A.shorted) + if(A.mode == 1) // AALARM_MODE_SCRUB + A.mode = 3 // AALARM_MODE_PANIC + else + A.mode = 1 // AALARM_MODE_SCRUB + A.apply_mode(usr) + if(WIRE_ALARM) // Clear alarms. + var/area/AA = get_area(A) + if(AA.atmosalert(0, holder)) + A.post_alert(0) + A.update_icon() + +/datum/wires/airalarm/on_cut(wire, mend) + var/obj/machinery/airalarm/A = holder + switch(wire) + if(WIRE_POWER) // Short out forever. + A.shock(usr, 50) + A.shorted = !mend + A.update_icon() + if(WIRE_IDSCAN) + if(!mend) + A.locked = TRUE + if(WIRE_AI) + A.aidisabled = mend // Enable/disable AI control. + if(WIRE_PANIC) // Force panic syphon on. + if(!mend && !A.shorted) + A.mode = 3 // AALARM_MODE_PANIC + A.apply_mode(usr) + if(WIRE_ALARM) // Post alarm. + var/area/AA = get_area(A) + if(AA.atmosalert(2, holder)) + A.post_alert(2) + A.update_icon() diff --git a/code/datums/wires/airlock.dm b/code/datums/wires/airlock.dm index 13e5c0aa4956..fa17ee500771 100644 --- a/code/datums/wires/airlock.dm +++ b/code/datums/wires/airlock.dm @@ -1,140 +1,152 @@ -/datum/wires/airlock - holder_type = /obj/machinery/door/airlock - proper_name = "Airlock" - -/datum/wires/airlock/secure - randomize = TRUE - -/datum/wires/airlock/New(atom/holder) - wires = list( - WIRE_POWER1, WIRE_POWER2, - WIRE_BACKUP1, WIRE_BACKUP2, - WIRE_OPEN, WIRE_BOLTS, WIRE_IDSCAN, WIRE_AI, - WIRE_SHOCK, WIRE_SAFETY, WIRE_TIMING, WIRE_LIGHT, - WIRE_ZAP1, WIRE_ZAP2 - ) - add_duds(2) - ..() - -/datum/wires/airlock/interactable(mob/user) - var/obj/machinery/door/airlock/A = holder - if(!issilicon(user) && A.isElectrified() && A.shock(user, 100)) - return FALSE - if(A.panel_open) - return TRUE - -/datum/wires/airlock/get_status() - var/obj/machinery/door/airlock/A = holder - var/list/status = list() - status += "The door bolts [A.locked ? "have fallen!" : "look up."]" - status += "The test light is [A.hasPower() ? "on" : "off"]." - status += "The AI connection light is [A.aiControlDisabled || (A.obj_flags & EMAGGED) ? "off" : "on"]." - status += "The check wiring light is [A.safe ? "off" : "on"]." - status += "The timer is powered [A.autoclose ? "on" : "off"]." - status += "The speed light is [A.normalspeed ? "on" : "off"]." - status += "The emergency light is [A.emergency ? "on" : "off"]." - return status - -/datum/wires/airlock/on_pulse(wire) - set waitfor = FALSE - var/obj/machinery/door/airlock/A = holder - switch(wire) - if(WIRE_POWER1, WIRE_POWER2) // Pulse to loose power. - A.loseMainPower() - if(WIRE_BACKUP1, WIRE_BACKUP2) // Pulse to loose backup power. - A.loseBackupPower() - if(WIRE_OPEN) // Pulse to open door (only works not emagged and ID wire is cut or no access is required). - if(A.obj_flags & EMAGGED) - return - if(!A.requiresID() || A.check_access(null)) - if(A.density) - INVOKE_ASYNC(A, /obj/machinery/door/airlock.proc/open) - else - INVOKE_ASYNC(A, /obj/machinery/door/airlock.proc/close) - if(WIRE_BOLTS) // Pulse to toggle bolts (but only raise if power is on). - if(!A.locked) - A.bolt() - else - if(A.hasPower()) - A.unbolt() - A.update_icon() - if(WIRE_IDSCAN) // Pulse to disable emergency access and flash red lights. - if(A.hasPower() && A.density) - A.do_animate("deny") - if(A.emergency) - A.emergency = FALSE - A.update_icon() - if(WIRE_AI) // Pulse to disable WIRE_AI control for 10 ticks (follows same rules as cutting). - if(A.aiControlDisabled == 0) - A.aiControlDisabled = 1 - else if(A.aiControlDisabled == -1) - A.aiControlDisabled = 2 - sleep(10) - if(A) - if(A.aiControlDisabled == 1) - A.aiControlDisabled = 0 - else if(A.aiControlDisabled == 2) - A.aiControlDisabled = -1 - if(WIRE_SHOCK) // Pulse to shock the door for 10 ticks. - if(!A.secondsElectrified) - A.set_electrified(MACHINE_DEFAULT_ELECTRIFY_TIME, usr) - if(WIRE_SAFETY) - A.safe = !A.safe - if(!A.density) - A.close() - if(WIRE_TIMING) - A.normalspeed = !A.normalspeed - if(WIRE_LIGHT) - A.lights = !A.lights - A.update_icon() - -/datum/wires/airlock/on_cut(wire, mend) - var/obj/machinery/door/airlock/A = holder - switch(wire) - if(WIRE_POWER1, WIRE_POWER2) // Cut to loose power, repair all to gain power. - if(mend && !is_cut(WIRE_POWER1) && !is_cut(WIRE_POWER2)) - A.regainMainPower() - else - A.loseMainPower() - if(isliving(usr)) - A.shock(usr, 50) - if(WIRE_BACKUP1, WIRE_BACKUP2) // Cut to loose backup power, repair all to gain backup power. - if(mend && !is_cut(WIRE_BACKUP1) && !is_cut(WIRE_BACKUP2)) - A.regainBackupPower() - else - A.loseBackupPower() - if(isliving(usr)) - A.shock(usr, 50) - if(WIRE_BOLTS) // Cut to drop bolts, mend does nothing. - if(!mend) - A.bolt() - if(WIRE_AI) // Cut to disable WIRE_AI control, mend to re-enable. - if(mend) - if(A.aiControlDisabled == 1) // 0 = normal, 1 = locked out, 2 = overridden by WIRE_AI, -1 = previously overridden by WIRE_AI - A.aiControlDisabled = 0 - else if(A.aiControlDisabled == 2) - A.aiControlDisabled = -1 - else - if(A.aiControlDisabled == 0) - A.aiControlDisabled = 1 - else if(A.aiControlDisabled == -1) - A.aiControlDisabled = 2 - if(WIRE_SHOCK) // Cut to shock the door, mend to unshock. - if(mend) - if(A.secondsElectrified) - A.set_electrified(MACHINE_NOT_ELECTRIFIED, usr) - else - if(A.secondsElectrified != MACHINE_ELECTRIFIED_PERMANENT) - A.set_electrified(MACHINE_ELECTRIFIED_PERMANENT, usr) - if(WIRE_SAFETY) // Cut to disable safeties, mend to re-enable. - A.safe = mend - if(WIRE_TIMING) // Cut to disable auto-close, mend to re-enable. - A.autoclose = mend - if(A.autoclose && !A.density) - A.close() - if(WIRE_LIGHT) // Cut to disable lights, mend to re-enable. - A.lights = mend - A.update_icon() - if(WIRE_ZAP1, WIRE_ZAP2) // Ouch. - if(isliving(usr)) - A.shock(usr, 50) +/datum/wires/airlock + holder_type = /obj/machinery/door/airlock + proper_name = "Airlock" + +/datum/wires/airlock/secure + randomize = TRUE + +/datum/wires/airlock/New(atom/holder) + wires = list( + WIRE_POWER1, WIRE_POWER2, + WIRE_BACKUP1, WIRE_BACKUP2, + WIRE_OPEN, WIRE_BOLTS, WIRE_IDSCAN, WIRE_AI, + WIRE_SHOCK, WIRE_SAFETY, WIRE_TIMING, WIRE_LIGHT, + WIRE_ZAP1, WIRE_ZAP2 + ) + add_duds(2) + ..() + +/datum/wires/airlock/interact(mob/user) + var/obj/machinery/door/airlock/airlock_holder = holder + if (!issilicon(user) && airlock_holder.isElectrified() && airlock_holder.shock(user, 100)) + return + + return ..() + +/datum/wires/airlock/interactable(mob/user) + var/obj/machinery/door/airlock/A = holder + if(!issilicon(user) && A.isElectrified()) + var/mob/living/carbon/carbon_user = user + if (!istype(carbon_user) || carbon_user.should_electrocute(src)) + return FALSE + + if(A.panel_open) + return TRUE + +/datum/wires/airlock/get_status() + var/obj/machinery/door/airlock/A = holder + var/list/status = list() + status += "The door bolts [A.locked ? "have fallen!" : "look up."]" + status += "The test light is [A.hasPower() ? "on" : "off"]." + status += "The AI connection light is [A.aiControlDisabled || (A.obj_flags & EMAGGED) ? "off" : "on"]." + status += "The check wiring light is [A.safe ? "off" : "on"]." + status += "The timer is powered [A.autoclose ? "on" : "off"]." + status += "The speed light is [A.normalspeed ? "on" : "off"]." + status += "The emergency light is [A.emergency ? "on" : "off"]." + return status + +/datum/wires/airlock/on_pulse(wire) + set waitfor = FALSE + var/obj/machinery/door/airlock/A = holder + switch(wire) + if(WIRE_POWER1, WIRE_POWER2) // Pulse to loose power. + A.loseMainPower() + if(WIRE_BACKUP1, WIRE_BACKUP2) // Pulse to loose backup power. + A.loseBackupPower() + if(WIRE_OPEN) // Pulse to open door (only works not emagged and ID wire is cut or no access is required). + if(A.obj_flags & EMAGGED) + return + if(!A.requiresID() || A.check_access(null)) + if(A.density) + INVOKE_ASYNC(A, /obj/machinery/door/airlock.proc/open) + else + INVOKE_ASYNC(A, /obj/machinery/door/airlock.proc/close) + if(WIRE_BOLTS) // Pulse to toggle bolts (but only raise if power is on). + if(!A.locked) + A.bolt() + else + if(A.hasPower()) + A.unbolt() + A.update_icon() + if(WIRE_IDSCAN) // Pulse to disable emergency access and flash red lights. + if(A.hasPower() && A.density) + A.do_animate("deny") + if(A.emergency) + A.emergency = FALSE + A.update_icon() + if(WIRE_AI) // Pulse to disable WIRE_AI control for 10 ticks (follows same rules as cutting). + if(A.aiControlDisabled == 0) + A.aiControlDisabled = 1 + else if(A.aiControlDisabled == -1) + A.aiControlDisabled = 2 + sleep(10) + if(A) + if(A.aiControlDisabled == 1) + A.aiControlDisabled = 0 + else if(A.aiControlDisabled == 2) + A.aiControlDisabled = -1 + if(WIRE_SHOCK) // Pulse to shock the door for 10 ticks. + if(!A.secondsElectrified) + A.set_electrified(MACHINE_DEFAULT_ELECTRIFY_TIME, usr) + A.shock(usr, 100) + if(WIRE_SAFETY) + A.safe = !A.safe + if(!A.density) + A.close() + if(WIRE_TIMING) + A.normalspeed = !A.normalspeed + if(WIRE_LIGHT) + A.lights = !A.lights + A.update_icon() + +/datum/wires/airlock/on_cut(wire, mend) + var/obj/machinery/door/airlock/A = holder + switch(wire) + if(WIRE_POWER1, WIRE_POWER2) // Cut to loose power, repair all to gain power. + if(mend && !is_cut(WIRE_POWER1) && !is_cut(WIRE_POWER2)) + A.regainMainPower() + else + A.loseMainPower() + if(isliving(usr)) + A.shock(usr, 50) + if(WIRE_BACKUP1, WIRE_BACKUP2) // Cut to loose backup power, repair all to gain backup power. + if(mend && !is_cut(WIRE_BACKUP1) && !is_cut(WIRE_BACKUP2)) + A.regainBackupPower() + else + A.loseBackupPower() + if(isliving(usr)) + A.shock(usr, 50) + if(WIRE_BOLTS) // Cut to drop bolts, mend does nothing. + if(!mend) + A.bolt() + if(WIRE_AI) // Cut to disable WIRE_AI control, mend to re-enable. + if(mend) + if(A.aiControlDisabled == 1) // 0 = normal, 1 = locked out, 2 = overridden by WIRE_AI, -1 = previously overridden by WIRE_AI + A.aiControlDisabled = 0 + else if(A.aiControlDisabled == 2) + A.aiControlDisabled = -1 + else + if(A.aiControlDisabled == 0) + A.aiControlDisabled = 1 + else if(A.aiControlDisabled == -1) + A.aiControlDisabled = 2 + if(WIRE_SHOCK) // Cut to shock the door, mend to unshock. + if(mend) + if(A.secondsElectrified) + A.set_electrified(MACHINE_NOT_ELECTRIFIED, usr) + else + if(A.secondsElectrified != MACHINE_ELECTRIFIED_PERMANENT) + A.set_electrified(MACHINE_ELECTRIFIED_PERMANENT, usr) + A.shock(usr, 100) + if(WIRE_SAFETY) // Cut to disable safeties, mend to re-enable. + A.safe = mend + if(WIRE_TIMING) // Cut to disable auto-close, mend to re-enable. + A.autoclose = mend + if(A.autoclose && !A.density) + A.close() + if(WIRE_LIGHT) // Cut to disable lights, mend to re-enable. + A.lights = mend + A.update_icon() + if(WIRE_ZAP1, WIRE_ZAP2) // Ouch. + if(isliving(usr)) + A.shock(usr, 50) diff --git a/code/datums/wires/apc.dm b/code/datums/wires/apc.dm index 9fa90221786a..e97c1f3654e3 100644 --- a/code/datums/wires/apc.dm +++ b/code/datums/wires/apc.dm @@ -1,55 +1,55 @@ -/datum/wires/apc - holder_type = /obj/machinery/power/apc - proper_name = "APC" - -/datum/wires/apc/New(atom/holder) - wires = list( - WIRE_POWER1, WIRE_POWER2, - WIRE_IDSCAN, WIRE_AI - ) - add_duds(6) - ..() - -/datum/wires/apc/interactable(mob/user) - var/obj/machinery/power/apc/A = holder - if(A.panel_open && !A.opened) - return TRUE - -/datum/wires/apc/get_status() - var/obj/machinery/power/apc/A = holder - var/list/status = list() - status += "The interface light is [A.locked ? "red" : "green"]." - status += "The short indicator is [A.shorted ? "lit" : "off"]." - status += "The AI connection light is [!A.aidisabled ? "on" : "off"]." - return status - -/datum/wires/apc/on_pulse(wire) - var/obj/machinery/power/apc/A = holder - switch(wire) - if(WIRE_POWER1, WIRE_POWER2) // Short for a long while. - if(!A.shorted) - A.shorted = TRUE - addtimer(CALLBACK(A, /obj/machinery/power/apc.proc/reset, wire), 1200) - if(WIRE_IDSCAN) // Unlock for a little while. - A.locked = FALSE - addtimer(CALLBACK(A, /obj/machinery/power/apc.proc/reset, wire), 300) - if(WIRE_AI) // Disable AI control for a very short time. - if(!A.aidisabled) - A.aidisabled = TRUE - addtimer(CALLBACK(A, /obj/machinery/power/apc.proc/reset, wire), 10) - -/datum/wires/apc/on_cut(index, mend) - var/obj/machinery/power/apc/A = holder - switch(index) - if(WIRE_POWER1, WIRE_POWER2) // Short out. - if(mend && !is_cut(WIRE_POWER1) && !is_cut(WIRE_POWER2)) - A.shorted = FALSE - A.shock(usr, 50) - else - A.shorted = TRUE - A.shock(usr, 50) - if(WIRE_AI) // Disable AI control. - if(mend) - A.aidisabled = FALSE - else - A.aidisabled = TRUE +/datum/wires/apc + holder_type = /obj/machinery/power/apc + proper_name = "APC" + +/datum/wires/apc/New(atom/holder) + wires = list( + WIRE_POWER1, WIRE_POWER2, + WIRE_IDSCAN, WIRE_AI + ) + add_duds(6) + ..() + +/datum/wires/apc/interactable(mob/user) + var/obj/machinery/power/apc/A = holder + if(A.panel_open && !A.opened) + return TRUE + +/datum/wires/apc/get_status() + var/obj/machinery/power/apc/A = holder + var/list/status = list() + status += "The interface light is [A.locked ? "red" : "green"]." + status += "The short indicator is [A.shorted ? "lit" : "off"]." + status += "The AI connection light is [!A.aidisabled ? "on" : "off"]." + return status + +/datum/wires/apc/on_pulse(wire) + var/obj/machinery/power/apc/A = holder + switch(wire) + if(WIRE_POWER1, WIRE_POWER2) // Short for a long while. + if(!A.shorted) + A.shorted = TRUE + addtimer(CALLBACK(A, /obj/machinery/power/apc.proc/reset, wire), 1200) + if(WIRE_IDSCAN) // Unlock for a little while. + A.locked = FALSE + addtimer(CALLBACK(A, /obj/machinery/power/apc.proc/reset, wire), 300) + if(WIRE_AI) // Disable AI control for a very short time. + if(!A.aidisabled) + A.aidisabled = TRUE + addtimer(CALLBACK(A, /obj/machinery/power/apc.proc/reset, wire), 10) + +/datum/wires/apc/on_cut(index, mend) + var/obj/machinery/power/apc/A = holder + switch(index) + if(WIRE_POWER1, WIRE_POWER2) // Short out. + if(mend && !is_cut(WIRE_POWER1) && !is_cut(WIRE_POWER2)) + A.shorted = FALSE + A.shock(usr, 50) + else + A.shorted = TRUE + A.shock(usr, 50) + if(WIRE_AI) // Disable AI control. + if(mend) + A.aidisabled = FALSE + else + A.aidisabled = TRUE diff --git a/code/datums/wires/autolathe.dm b/code/datums/wires/autolathe.dm index 242a460c8298..7f3519f39471 100644 --- a/code/datums/wires/autolathe.dm +++ b/code/datums/wires/autolathe.dm @@ -1,48 +1,48 @@ -/datum/wires/autolathe - holder_type = /obj/machinery/autolathe - proper_name = "Autolathe" - -/datum/wires/autolathe/New(atom/holder) - wires = list( - WIRE_HACK, WIRE_DISABLE, - WIRE_SHOCK, WIRE_ZAP - ) - add_duds(6) - ..() - -/datum/wires/autolathe/interactable(mob/user) - var/obj/machinery/autolathe/A = holder - if(A.panel_open) - return TRUE - -/datum/wires/autolathe/get_status() - var/obj/machinery/autolathe/A = holder - var/list/status = list() - status += "The red light is [A.disabled ? "on" : "off"]." - status += "The blue light is [A.hacked ? "on" : "off"]." - return status - -/datum/wires/autolathe/on_pulse(wire) - var/obj/machinery/autolathe/A = holder - switch(wire) - if(WIRE_HACK) - A.adjust_hacked(!A.hacked) - addtimer(CALLBACK(A, /obj/machinery/autolathe.proc/reset, wire), 60) - if(WIRE_SHOCK) - A.shocked = !A.shocked - addtimer(CALLBACK(A, /obj/machinery/autolathe.proc/reset, wire), 60) - if(WIRE_DISABLE) - A.disabled = !A.disabled - addtimer(CALLBACK(A, /obj/machinery/autolathe.proc/reset, wire), 60) - -/datum/wires/autolathe/on_cut(wire, mend) - var/obj/machinery/autolathe/A = holder - switch(wire) - if(WIRE_HACK) - A.adjust_hacked(!mend) - if(WIRE_HACK) - A.shocked = !mend - if(WIRE_DISABLE) - A.disabled = !mend - if(WIRE_ZAP) - A.shock(usr, 50) +/datum/wires/autolathe + holder_type = /obj/machinery/autolathe + proper_name = "Autolathe" + +/datum/wires/autolathe/New(atom/holder) + wires = list( + WIRE_HACK, WIRE_DISABLE, + WIRE_SHOCK, WIRE_ZAP + ) + add_duds(6) + ..() + +/datum/wires/autolathe/interactable(mob/user) + var/obj/machinery/autolathe/A = holder + if(A.panel_open) + return TRUE + +/datum/wires/autolathe/get_status() + var/obj/machinery/autolathe/A = holder + var/list/status = list() + status += "The red light is [A.disabled ? "on" : "off"]." + status += "The blue light is [A.hacked ? "on" : "off"]." + return status + +/datum/wires/autolathe/on_pulse(wire) + var/obj/machinery/autolathe/A = holder + switch(wire) + if(WIRE_HACK) + A.adjust_hacked(!A.hacked) + addtimer(CALLBACK(A, /obj/machinery/autolathe.proc/reset, wire), 60) + if(WIRE_SHOCK) + A.shocked = !A.shocked + addtimer(CALLBACK(A, /obj/machinery/autolathe.proc/reset, wire), 60) + if(WIRE_DISABLE) + A.disabled = !A.disabled + addtimer(CALLBACK(A, /obj/machinery/autolathe.proc/reset, wire), 60) + +/datum/wires/autolathe/on_cut(wire, mend) + var/obj/machinery/autolathe/A = holder + switch(wire) + if(WIRE_HACK) + A.adjust_hacked(!mend) + if(WIRE_HACK) + A.shocked = !mend + if(WIRE_DISABLE) + A.disabled = !mend + if(WIRE_ZAP) + A.shock(usr, 50) diff --git a/code/datums/wires/explosive.dm b/code/datums/wires/explosive.dm index 4a8f5d2453da..e3f73d287b72 100644 --- a/code/datums/wires/explosive.dm +++ b/code/datums/wires/explosive.dm @@ -1,120 +1,120 @@ -/datum/wires/explosive - var/duds_number = 2 // All "dud" wires cause an explosion when cut or pulsed - randomize = TRUE // Prevents wires from showing up on blueprints - -/datum/wires/explosive/New(atom/holder) - add_duds(duds_number) // Duds also explode here. - ..() - -/datum/wires/explosive/on_pulse(index) - explode() - -/datum/wires/explosive/on_cut(index, mend) - explode() - -/datum/wires/explosive/proc/explode() - return - -/datum/wires/explosive/chem_grenade - duds_number = 1 - holder_type = /obj/item/grenade/chem_grenade - var/fingerprint - -/datum/wires/explosive/chem_grenade/interactable(mob/user) - var/obj/item/grenade/chem_grenade/G = holder - if(G.stage == GRENADE_WIRED) - return TRUE - -/datum/wires/explosive/chem_grenade/attach_assembly(color, obj/item/assembly/S) - if(istype(S,/obj/item/assembly/timer)) - var/obj/item/grenade/chem_grenade/G = holder - var/obj/item/assembly/timer/T = S - G.det_time = T.saved_time*10 - else if(istype(S,/obj/item/assembly/prox_sensor)) - var/obj/item/grenade/chem_grenade/G = holder - G.landminemode = S - S.proximity_monitor.wire = TRUE - fingerprint = S.fingerprintslast - return ..() - -/datum/wires/explosive/chem_grenade/explode() - var/obj/item/grenade/chem_grenade/G = holder - var/obj/item/assembly/assembly = get_attached(get_wire(1)) - message_admins("\An [assembly] has pulsed a grenade, which was installed by [fingerprint].") - log_game("\An [assembly] has pulsed a grenade, which was installed by [fingerprint].") - var/mob/M = get_mob_by_ckey(fingerprint) - var/turf/T = get_turf(M) - G.log_grenade(M, T) - G.prime() - -/datum/wires/explosive/chem_grenade/detach_assembly(color) - var/obj/item/assembly/S = get_attached(color) - if(S && istype(S)) - assemblies -= color - S.connected = null - S.forceMove(holder.drop_location()) - var/obj/item/grenade/chem_grenade/G = holder - G.landminemode = null - return S - -/datum/wires/explosive/c4 // Also includes X4 - holder_type = /obj/item/grenade/c4 - -/datum/wires/explosive/c4/interactable(mob/user) // No need to unscrew wire panels on plastic explosives - return TRUE - -/datum/wires/explosive/c4/explode() - var/obj/item/grenade/c4/P = holder - P.prime() - -/datum/wires/explosive/pizza - holder_type = /obj/item/pizzabox - -/datum/wires/explosive/pizza/New(atom/holder) - wires = list( - WIRE_DISARM - ) - add_duds(3) // Duds also explode here. - ..() - -/datum/wires/explosive/pizza/interactable(mob/user) - var/obj/item/pizzabox/P = holder - if(P.open && P.bomb) - return TRUE - -/datum/wires/explosive/pizza/get_status() - var/obj/item/pizzabox/P = holder - var/list/status = list() - status += "The red light is [P.bomb_active ? "on" : "off"]." - status += "The green light is [P.bomb_defused ? "on": "off"]." - return status - -/datum/wires/explosive/pizza/on_pulse(wire) - var/obj/item/pizzabox/P = holder - switch(wire) - if(WIRE_DISARM) // Pulse to toggle - P.bomb_defused = !P.bomb_defused - else // Boom - explode() - -/datum/wires/explosive/pizza/on_cut(wire, mend) - var/obj/item/pizzabox/P = holder - switch(wire) - if(WIRE_DISARM) // Disarm and untrap the box. - if(!mend) - P.bomb_defused = TRUE - else - if(!mend && !P.bomb_defused) - explode() - -/datum/wires/explosive/pizza/explode() - var/obj/item/pizzabox/P = holder - P.bomb.detonate() - - -/datum/wires/explosive/gibtonite - holder_type = /obj/item/gibtonite - -/datum/wires/explosive/gibtonite/explode() - var/obj/item/gibtonite/P = holder - P.GibtoniteReaction(null, 2) +/datum/wires/explosive + var/duds_number = 2 // All "dud" wires cause an explosion when cut or pulsed + randomize = TRUE // Prevents wires from showing up on blueprints + +/datum/wires/explosive/New(atom/holder) + add_duds(duds_number) // Duds also explode here. + ..() + +/datum/wires/explosive/on_pulse(index) + explode() + +/datum/wires/explosive/on_cut(index, mend) + explode() + +/datum/wires/explosive/proc/explode() + return + +/datum/wires/explosive/chem_grenade + duds_number = 1 + holder_type = /obj/item/grenade/chem_grenade + var/fingerprint + +/datum/wires/explosive/chem_grenade/interactable(mob/user) + var/obj/item/grenade/chem_grenade/G = holder + if(G.stage == GRENADE_WIRED) + return TRUE + +/datum/wires/explosive/chem_grenade/attach_assembly(color, obj/item/assembly/S) + if(istype(S,/obj/item/assembly/timer)) + var/obj/item/grenade/chem_grenade/G = holder + var/obj/item/assembly/timer/T = S + G.det_time = T.saved_time*10 + else if(istype(S,/obj/item/assembly/prox_sensor)) + var/obj/item/grenade/chem_grenade/G = holder + G.landminemode = S + S.proximity_monitor.wire = TRUE + fingerprint = S.fingerprintslast + return ..() + +/datum/wires/explosive/chem_grenade/explode() + var/obj/item/grenade/chem_grenade/G = holder + var/obj/item/assembly/assembly = get_attached(get_wire(1)) + message_admins("\An [assembly] has pulsed a grenade, which was installed by [fingerprint].") + log_game("\An [assembly] has pulsed a grenade, which was installed by [fingerprint].") + var/mob/M = get_mob_by_ckey(fingerprint) + var/turf/T = get_turf(M) + G.log_grenade(M, T) + G.prime() + +/datum/wires/explosive/chem_grenade/detach_assembly(color) + var/obj/item/assembly/S = get_attached(color) + if(S && istype(S)) + assemblies -= color + S.connected = null + S.forceMove(holder.drop_location()) + var/obj/item/grenade/chem_grenade/G = holder + G.landminemode = null + return S + +/datum/wires/explosive/c4 // Also includes X4 + holder_type = /obj/item/grenade/c4 + +/datum/wires/explosive/c4/interactable(mob/user) // No need to unscrew wire panels on plastic explosives + return TRUE + +/datum/wires/explosive/c4/explode() + var/obj/item/grenade/c4/P = holder + P.prime() + +/datum/wires/explosive/pizza + holder_type = /obj/item/pizzabox + +/datum/wires/explosive/pizza/New(atom/holder) + wires = list( + WIRE_DISARM + ) + add_duds(3) // Duds also explode here. + ..() + +/datum/wires/explosive/pizza/interactable(mob/user) + var/obj/item/pizzabox/P = holder + if(P.open && P.bomb) + return TRUE + +/datum/wires/explosive/pizza/get_status() + var/obj/item/pizzabox/P = holder + var/list/status = list() + status += "The red light is [P.bomb_active ? "on" : "off"]." + status += "The green light is [P.bomb_defused ? "on": "off"]." + return status + +/datum/wires/explosive/pizza/on_pulse(wire) + var/obj/item/pizzabox/P = holder + switch(wire) + if(WIRE_DISARM) // Pulse to toggle + P.bomb_defused = !P.bomb_defused + else // Boom + explode() + +/datum/wires/explosive/pizza/on_cut(wire, mend) + var/obj/item/pizzabox/P = holder + switch(wire) + if(WIRE_DISARM) // Disarm and untrap the box. + if(!mend) + P.bomb_defused = TRUE + else + if(!mend && !P.bomb_defused) + explode() + +/datum/wires/explosive/pizza/explode() + var/obj/item/pizzabox/P = holder + P.bomb.detonate() + + +/datum/wires/explosive/gibtonite + holder_type = /obj/item/gibtonite + +/datum/wires/explosive/gibtonite/explode() + var/obj/item/gibtonite/P = holder + P.GibtoniteReaction(null, 2) diff --git a/code/datums/wires/mulebot.dm b/code/datums/wires/mulebot.dm index 9c71c9568f21..08a10afbfca8 100644 --- a/code/datums/wires/mulebot.dm +++ b/code/datums/wires/mulebot.dm @@ -1,34 +1,34 @@ -/datum/wires/mulebot - holder_type = /mob/living/simple_animal/bot/mulebot - randomize = TRUE - -/datum/wires/mulebot/New(atom/holder) - wires = list( - WIRE_POWER1, WIRE_POWER2, - WIRE_AVOIDANCE, WIRE_LOADCHECK, - WIRE_MOTOR1, WIRE_MOTOR2, - WIRE_RX, WIRE_TX, WIRE_BEACON - ) - ..() - -/datum/wires/mulebot/interactable(mob/user) - var/mob/living/simple_animal/bot/mulebot/M = holder - if(M.open) - return TRUE - -/datum/wires/mulebot/on_pulse(wire) - var/mob/living/simple_animal/bot/mulebot/M = holder - if(!M.has_power(TRUE)) - return //logically mulebots can't flash and beep if they don't have power. - switch(wire) - if(WIRE_POWER1, WIRE_POWER2) - holder.visible_message("[icon2html(M, viewers(holder))] The charge light flickers.") - if(WIRE_AVOIDANCE) - holder.visible_message("[icon2html(M, viewers(holder))] The external warning lights flash briefly.") - flick("[M.base_icon]1", M) - if(WIRE_LOADCHECK) - holder.visible_message("[icon2html(M, viewers(holder))] The load platform clunks.") - if(WIRE_MOTOR1, WIRE_MOTOR2) - holder.visible_message("[icon2html(M, viewers(holder))] The drive motor whines briefly.") - else - holder.visible_message("[icon2html(M, viewers(holder))] You hear a radio crackle.") +/datum/wires/mulebot + holder_type = /mob/living/simple_animal/bot/mulebot + randomize = TRUE + +/datum/wires/mulebot/New(atom/holder) + wires = list( + WIRE_POWER1, WIRE_POWER2, + WIRE_AVOIDANCE, WIRE_LOADCHECK, + WIRE_MOTOR1, WIRE_MOTOR2, + WIRE_RX, WIRE_TX, WIRE_BEACON + ) + ..() + +/datum/wires/mulebot/interactable(mob/user) + var/mob/living/simple_animal/bot/mulebot/M = holder + if(M.open) + return TRUE + +/datum/wires/mulebot/on_pulse(wire) + var/mob/living/simple_animal/bot/mulebot/M = holder + if(!M.has_power(TRUE)) + return //logically mulebots can't flash and beep if they don't have power. + switch(wire) + if(WIRE_POWER1, WIRE_POWER2) + holder.visible_message("[icon2html(M, viewers(holder))] The charge light flickers.") + if(WIRE_AVOIDANCE) + holder.visible_message("[icon2html(M, viewers(holder))] The external warning lights flash briefly.") + flick("[M.base_icon]1", M) + if(WIRE_LOADCHECK) + holder.visible_message("[icon2html(M, viewers(holder))] The load platform clunks.") + if(WIRE_MOTOR1, WIRE_MOTOR2) + holder.visible_message("[icon2html(M, viewers(holder))] The drive motor whines briefly.") + else + holder.visible_message("[icon2html(M, viewers(holder))] You hear a radio crackle.") diff --git a/code/datums/wires/particle_accelerator.dm b/code/datums/wires/particle_accelerator.dm index 630a506b7777..e58dba4631f7 100644 --- a/code/datums/wires/particle_accelerator.dm +++ b/code/datums/wires/particle_accelerator.dm @@ -1,48 +1,48 @@ -/datum/wires/particle_accelerator/control_box - holder_type = /obj/machinery/particle_accelerator/control_box - proper_name = "Particle Accelerator" - -/datum/wires/particle_accelerator/control_box/New(atom/holder) - wires = list( - WIRE_POWER, WIRE_STRENGTH, WIRE_LIMIT, - WIRE_INTERFACE - ) - add_duds(2) - ..() - -/datum/wires/particle_accelerator/control_box/interactable(mob/user) - var/obj/machinery/particle_accelerator/control_box/C = holder - if(C.construction_state == 2) - return TRUE - -/datum/wires/particle_accelerator/control_box/on_pulse(wire) - var/obj/machinery/particle_accelerator/control_box/C = holder - switch(wire) - if(WIRE_POWER) - C.toggle_power() - if(WIRE_STRENGTH) - C.add_strength() - if(WIRE_INTERFACE) - C.interface_control = !C.interface_control - if(WIRE_LIMIT) - C.visible_message("[icon2html(C, viewers(holder))][C] makes a large whirring noise.") - -/datum/wires/particle_accelerator/control_box/on_cut(wire, mend) - var/obj/machinery/particle_accelerator/control_box/C = holder - switch(wire) - if(WIRE_POWER) - if(C.active == !mend) - C.toggle_power() - if(WIRE_STRENGTH) - for(var/i = 1; i < 3; i++) - C.remove_strength() - if(WIRE_INTERFACE) - if(!mend) - C.interface_control = FALSE - if(WIRE_LIMIT) - C.strength_upper_limit = (mend ? 2 : 3) - if(C.strength_upper_limit < C.strength) - C.remove_strength() - -/datum/wires/particle_accelerator/control_box/emp_pulse() // to prevent singulo from pulsing wires - return +/datum/wires/particle_accelerator/control_box + holder_type = /obj/machinery/particle_accelerator/control_box + proper_name = "Particle Accelerator" + +/datum/wires/particle_accelerator/control_box/New(atom/holder) + wires = list( + WIRE_POWER, WIRE_STRENGTH, WIRE_LIMIT, + WIRE_INTERFACE + ) + add_duds(2) + ..() + +/datum/wires/particle_accelerator/control_box/interactable(mob/user) + var/obj/machinery/particle_accelerator/control_box/C = holder + if(C.construction_state == 2) + return TRUE + +/datum/wires/particle_accelerator/control_box/on_pulse(wire) + var/obj/machinery/particle_accelerator/control_box/C = holder + switch(wire) + if(WIRE_POWER) + C.toggle_power() + if(WIRE_STRENGTH) + C.add_strength() + if(WIRE_INTERFACE) + C.interface_control = !C.interface_control + if(WIRE_LIMIT) + C.visible_message("[icon2html(C, viewers(holder))][C] makes a large whirring noise.") + +/datum/wires/particle_accelerator/control_box/on_cut(wire, mend) + var/obj/machinery/particle_accelerator/control_box/C = holder + switch(wire) + if(WIRE_POWER) + if(C.active == !mend) + C.toggle_power() + if(WIRE_STRENGTH) + for(var/i = 1; i < 3; i++) + C.remove_strength() + if(WIRE_INTERFACE) + if(!mend) + C.interface_control = FALSE + if(WIRE_LIMIT) + C.strength_upper_limit = (mend ? 2 : 3) + if(C.strength_upper_limit < C.strength) + C.remove_strength() + +/datum/wires/particle_accelerator/control_box/emp_pulse() // to prevent singulo from pulsing wires + return diff --git a/code/datums/wires/radio.dm b/code/datums/wires/radio.dm index f950d7a7ce9c..a1118da6d73c 100644 --- a/code/datums/wires/radio.dm +++ b/code/datums/wires/radio.dm @@ -1,25 +1,25 @@ -/datum/wires/radio - holder_type = /obj/item/radio - proper_name = "Radio" - -/datum/wires/radio/New(atom/holder) - wires = list( - WIRE_SIGNAL, - WIRE_RX, WIRE_TX - ) - ..() - -/datum/wires/radio/interactable(mob/user) - var/obj/item/radio/R = holder - return R.unscrewed - -/datum/wires/radio/on_pulse(index) - var/obj/item/radio/R = holder - switch(index) - if(WIRE_SIGNAL) - R.listening = !R.listening - R.broadcasting = R.listening - if(WIRE_RX) - R.listening = !R.listening - if(WIRE_TX) - R.broadcasting = !R.broadcasting +/datum/wires/radio + holder_type = /obj/item/radio + proper_name = "Radio" + +/datum/wires/radio/New(atom/holder) + wires = list( + WIRE_SIGNAL, + WIRE_RX, WIRE_TX + ) + ..() + +/datum/wires/radio/interactable(mob/user) + var/obj/item/radio/R = holder + return R.unscrewed + +/datum/wires/radio/on_pulse(index) + var/obj/item/radio/R = holder + switch(index) + if(WIRE_SIGNAL) + R.listening = !R.listening + R.broadcasting = R.listening + if(WIRE_RX) + R.listening = !R.listening + if(WIRE_TX) + R.broadcasting = !R.broadcasting diff --git a/code/datums/wires/robot.dm b/code/datums/wires/robot.dm index c24e40150b3d..febb3d0ce2e9 100644 --- a/code/datums/wires/robot.dm +++ b/code/datums/wires/robot.dm @@ -1,86 +1,86 @@ -/datum/wires/robot - holder_type = /mob/living/silicon/robot - randomize = TRUE - -/datum/wires/robot/New(atom/holder) - wires = list( - WIRE_AI, WIRE_CAMERA, - WIRE_LAWSYNC, WIRE_LOCKDOWN, - WIRE_RESET_MODULE - ) - add_duds(2) - ..() - -/datum/wires/robot/interactable(mob/user) - var/mob/living/silicon/robot/R = holder - if(R.wiresexposed) - return TRUE - -/datum/wires/robot/get_status() - var/mob/living/silicon/robot/R = holder - var/list/status = list() - status += "The law sync module is [R.lawupdate ? "on" : "off"]." - status += "The intelligence link display shows [R.connected_ai ? R.connected_ai.name : "NULL"]." - status += "The camera light is [!isnull(R.builtInCamera) && R.builtInCamera.status ? "on" : "off"]." - status += "The lockdown indicator is [R.lockcharge ? "on" : "off"]." - status += "There is a star symbol above the [get_color_of_wire(WIRE_RESET_MODULE)] wire." - return status - -/datum/wires/robot/on_pulse(wire, user) - var/mob/living/silicon/robot/R = holder - switch(wire) - if(WIRE_AI) // Pulse to pick a new AI. - if(!R.emagged) - var/new_ai - if(user) - new_ai = select_active_ai(user, R.z) - else - new_ai = select_active_ai(R, R.z) - R.notify_ai(DISCONNECT) - if(new_ai && (new_ai != R.connected_ai)) - R.connected_ai = new_ai - if(R.shell) - R.undeploy() //If this borg is an AI shell, disconnect the controlling AI and assign ti to a new AI - R.notify_ai(AI_SHELL) - else - R.notify_ai(TRUE) - if(WIRE_CAMERA) // Pulse to disable the camera. - if(!QDELETED(R.builtInCamera) && !R.scrambledcodes) - R.builtInCamera.toggle_cam(usr, 0) - R.visible_message("[R]'s camera lens focuses loudly.", "Your camera lens focuses loudly.") - if(WIRE_LAWSYNC) // Forces a law update if possible. - if(R.lawupdate) - R.visible_message("[R] gently chimes.", "LawSync protocol engaged.") - R.lawsync() - R.show_laws() - if(WIRE_LOCKDOWN) - R.SetLockdown(!R.lockcharge) // Toggle - if(WIRE_RESET_MODULE) - if(R.has_module()) - R.visible_message("[R]'s module servos twitch.", "Your module display flickers.") - -/datum/wires/robot/on_cut(wire, mend) - var/mob/living/silicon/robot/R = holder - switch(wire) - if(WIRE_AI) // Cut the AI wire to reset AI control. - if(!mend) - R.notify_ai(DISCONNECT) - if(R.shell) - R.undeploy() - R.connected_ai = null - if(WIRE_LAWSYNC) // Cut the law wire, and the borg will no longer receive law updates from its AI. Repair and it will re-sync. - if(mend) - if(!R.emagged) - R.lawupdate = TRUE - else if(!R.deployed) //AI shells must always have the same laws as the AI - R.lawupdate = FALSE - if (WIRE_CAMERA) // Disable the camera. - if(!QDELETED(R.builtInCamera) && !R.scrambledcodes) - R.builtInCamera.status = mend - R.builtInCamera.toggle_cam(usr, 0) - R.visible_message("[R]'s camera lens focuses loudly.", "Your camera lens focuses loudly.") - if(WIRE_LOCKDOWN) // Simple lockdown. - R.SetLockdown(!mend) - if(WIRE_RESET_MODULE) - if(R.has_module() && !mend) - R.ResetModule() +/datum/wires/robot + holder_type = /mob/living/silicon/robot + randomize = TRUE + +/datum/wires/robot/New(atom/holder) + wires = list( + WIRE_AI, WIRE_CAMERA, + WIRE_LAWSYNC, WIRE_LOCKDOWN, + WIRE_RESET_MODULE + ) + add_duds(2) + ..() + +/datum/wires/robot/interactable(mob/user) + var/mob/living/silicon/robot/R = holder + if(R.wiresexposed) + return TRUE + +/datum/wires/robot/get_status() + var/mob/living/silicon/robot/R = holder + var/list/status = list() + status += "The law sync module is [R.lawupdate ? "on" : "off"]." + status += "The intelligence link display shows [R.connected_ai ? R.connected_ai.name : "NULL"]." + status += "The camera light is [!isnull(R.builtInCamera) && R.builtInCamera.status ? "on" : "off"]." + status += "The lockdown indicator is [R.lockcharge ? "on" : "off"]." + status += "There is a star symbol above the [get_color_of_wire(WIRE_RESET_MODULE)] wire." + return status + +/datum/wires/robot/on_pulse(wire, user) + var/mob/living/silicon/robot/R = holder + switch(wire) + if(WIRE_AI) // Pulse to pick a new AI. + if(!R.emagged) + var/new_ai + if(user) + new_ai = select_active_ai(user, R.z) + else + new_ai = select_active_ai(R, R.z) + R.notify_ai(DISCONNECT) + if(new_ai && (new_ai != R.connected_ai)) + R.connected_ai = new_ai + if(R.shell) + R.undeploy() //If this borg is an AI shell, disconnect the controlling AI and assign ti to a new AI + R.notify_ai(AI_SHELL) + else + R.notify_ai(TRUE) + if(WIRE_CAMERA) // Pulse to disable the camera. + if(!QDELETED(R.builtInCamera) && !R.scrambledcodes) + R.builtInCamera.toggle_cam(usr, 0) + R.visible_message("[R]'s camera lens focuses loudly.", "Your camera lens focuses loudly.") + if(WIRE_LAWSYNC) // Forces a law update if possible. + if(R.lawupdate) + R.visible_message("[R] gently chimes.", "LawSync protocol engaged.") + R.lawsync() + R.show_laws() + if(WIRE_LOCKDOWN) + R.SetLockdown(!R.lockcharge) // Toggle + if(WIRE_RESET_MODULE) + if(R.has_module()) + R.visible_message("[R]'s module servos twitch.", "Your module display flickers.") + +/datum/wires/robot/on_cut(wire, mend) + var/mob/living/silicon/robot/R = holder + switch(wire) + if(WIRE_AI) // Cut the AI wire to reset AI control. + if(!mend) + R.notify_ai(DISCONNECT) + if(R.shell) + R.undeploy() + R.connected_ai = null + if(WIRE_LAWSYNC) // Cut the law wire, and the borg will no longer receive law updates from its AI. Repair and it will re-sync. + if(mend) + if(!R.emagged) + R.lawupdate = TRUE + else if(!R.deployed) //AI shells must always have the same laws as the AI + R.lawupdate = FALSE + if (WIRE_CAMERA) // Disable the camera. + if(!QDELETED(R.builtInCamera) && !R.scrambledcodes) + R.builtInCamera.status = mend + R.builtInCamera.toggle_cam(usr, 0) + R.visible_message("[R]'s camera lens focuses loudly.", "Your camera lens focuses loudly.") + if(WIRE_LOCKDOWN) // Simple lockdown. + R.SetLockdown(!mend) + if(WIRE_RESET_MODULE) + if(R.has_module() && !mend) + R.ResetModule() diff --git a/code/datums/wires/suit_storage_unit.dm b/code/datums/wires/suit_storage_unit.dm index 4813ad8d207f..eb7781203b2b 100644 --- a/code/datums/wires/suit_storage_unit.dm +++ b/code/datums/wires/suit_storage_unit.dm @@ -1,45 +1,45 @@ -/datum/wires/suit_storage_unit - holder_type = /obj/machinery/suit_storage_unit - proper_name = "Suit Storage Unit" - -/datum/wires/suit_storage_unit/New(atom/holder) - wires = list( - WIRE_HACK, WIRE_SAFETY, - WIRE_ZAP - ) - add_duds(2) - ..() - -/datum/wires/suit_storage_unit/interactable(mob/user) - var/obj/machinery/suit_storage_unit/SSU = holder - if(SSU.panel_open) - return TRUE - -/datum/wires/suit_storage_unit/get_status() - var/obj/machinery/suit_storage_unit/SSU = holder - var/list/status = list() - status += "The UV bulb is [SSU.uv_super ? "glowing" : "dim"]." - status += "The service light is [SSU.safeties ? "off" : "on"]." - return status - -/datum/wires/suit_storage_unit/on_pulse(wire) - var/obj/machinery/suit_storage_unit/SSU = holder - switch(wire) - if(WIRE_HACK) - SSU.uv_super = !SSU.uv_super - if(WIRE_SAFETY) - SSU.safeties = !SSU.safeties - if(WIRE_ZAP) - if(usr) - SSU.shock(usr) - -/datum/wires/suit_storage_unit/on_cut(wire, mend) - var/obj/machinery/suit_storage_unit/SSU = holder - switch(wire) - if(WIRE_HACK) - SSU.uv_super = !mend - if(WIRE_SAFETY) - SSU.safeties = mend - if(WIRE_ZAP) - if(usr) - SSU.shock(usr) +/datum/wires/suit_storage_unit + holder_type = /obj/machinery/suit_storage_unit + proper_name = "Suit Storage Unit" + +/datum/wires/suit_storage_unit/New(atom/holder) + wires = list( + WIRE_HACK, WIRE_SAFETY, + WIRE_ZAP + ) + add_duds(2) + ..() + +/datum/wires/suit_storage_unit/interactable(mob/user) + var/obj/machinery/suit_storage_unit/SSU = holder + if(SSU.panel_open) + return TRUE + +/datum/wires/suit_storage_unit/get_status() + var/obj/machinery/suit_storage_unit/SSU = holder + var/list/status = list() + status += "The UV bulb is [SSU.uv_super ? "glowing" : "dim"]." + status += "The service light is [SSU.safeties ? "off" : "on"]." + return status + +/datum/wires/suit_storage_unit/on_pulse(wire) + var/obj/machinery/suit_storage_unit/SSU = holder + switch(wire) + if(WIRE_HACK) + SSU.uv_super = !SSU.uv_super + if(WIRE_SAFETY) + SSU.safeties = !SSU.safeties + if(WIRE_ZAP) + if(usr) + SSU.shock(usr) + +/datum/wires/suit_storage_unit/on_cut(wire, mend) + var/obj/machinery/suit_storage_unit/SSU = holder + switch(wire) + if(WIRE_HACK) + SSU.uv_super = !mend + if(WIRE_SAFETY) + SSU.safeties = mend + if(WIRE_ZAP) + if(usr) + SSU.shock(usr) diff --git a/code/datums/wires/syndicatebomb.dm b/code/datums/wires/syndicatebomb.dm index 1ebcf704539b..fa777eefcbac 100644 --- a/code/datums/wires/syndicatebomb.dm +++ b/code/datums/wires/syndicatebomb.dm @@ -1,91 +1,91 @@ -/datum/wires/syndicatebomb - holder_type = /obj/machinery/syndicatebomb - randomize = TRUE - -/datum/wires/syndicatebomb/New(atom/holder) - wires = list( - WIRE_BOOM, WIRE_UNBOLT, - WIRE_ACTIVATE, WIRE_DELAY, WIRE_PROCEED - ) - ..() - -/datum/wires/syndicatebomb/interactable(mob/user) - var/obj/machinery/syndicatebomb/P = holder - if(P.open_panel) - return TRUE - -/datum/wires/syndicatebomb/on_pulse(wire) - var/obj/machinery/syndicatebomb/B = holder - switch(wire) - if(WIRE_BOOM) - if(B.active) - holder.visible_message("[icon2html(B, viewers(holder))] An alarm sounds! It's go-") - B.explode_now = TRUE - tell_admins(B) - else - holder.visible_message("[icon2html(B, viewers(holder))] Nothing happens.") - if(WIRE_UNBOLT) - holder.visible_message("[icon2html(B, viewers(holder))] The bolts spin in place for a moment.") - if(WIRE_DELAY) - if(B.delayedbig) - holder.visible_message("[icon2html(B, viewers(holder))] Nothing happens.") - else - holder.visible_message("[icon2html(B, viewers(holder))] The bomb chirps.") - playsound(B, 'sound/machines/chime.ogg', 30, TRUE) - B.detonation_timer += 300 - if(B.active) - B.delayedbig = TRUE - if(WIRE_PROCEED) - holder.visible_message("[icon2html(B, viewers(holder))] The bomb buzzes ominously!") - playsound(B, 'sound/machines/buzz-sigh.ogg', 30, TRUE) - var/seconds = B.seconds_remaining() - if(seconds >= 61) // Long fuse bombs can suddenly become more dangerous if you tinker with them. - B.detonation_timer = world.time + 600 - else if(seconds >= 21) - B.detonation_timer -= 100 - else if(seconds >= 11) // Both to prevent negative timers and to have a little mercy. - B.detonation_timer = world.time + 100 - if(WIRE_ACTIVATE) - if(!B.active) - holder.visible_message("[icon2html(B, viewers(holder))] You hear the bomb start ticking!") - B.activate() - B.update_icon() - else if(B.delayedlittle) - holder.visible_message("[icon2html(B, viewers(holder))] Nothing happens.") - else - holder.visible_message("[icon2html(B, viewers(holder))] The bomb seems to hesitate for a moment.") - B.detonation_timer += 100 - B.delayedlittle = TRUE - -/datum/wires/syndicatebomb/on_cut(wire, mend) - var/obj/machinery/syndicatebomb/B = holder - switch(wire) - if(WIRE_BOOM) - if(!mend && B.active) - holder.visible_message("[icon2html(B, viewers(holder))] An alarm sounds! It's go-") - B.explode_now = TRUE - tell_admins(B) - if(WIRE_UNBOLT) - if(!mend && B.anchored) - holder.visible_message("[icon2html(B, viewers(holder))] The bolts lift out of the ground!") - playsound(B, 'sound/effects/stealthoff.ogg', 30, TRUE) - B.anchored = FALSE - if(WIRE_PROCEED) - if(!mend && B.active) - holder.visible_message("[icon2html(B, viewers(holder))] An alarm sounds! It's go-") - B.explode_now = TRUE - tell_admins(B) - if(WIRE_ACTIVATE) - if(!mend && B.active) - holder.visible_message("[icon2html(B, viewers(holder))] The timer stops! The bomb has been defused!") - B.active = FALSE - B.delayedlittle = FALSE - B.delayedbig = FALSE - B.update_icon() - -/datum/wires/syndicatebomb/proc/tell_admins(obj/machinery/syndicatebomb/B) - if(istype(B, /obj/machinery/syndicatebomb/training)) - return - var/turf/T = get_turf(B) - log_game("\A [B] was detonated via boom wire at [AREACOORD(T)].") - message_admins("A [B.name] was detonated via boom wire at [ADMIN_VERBOSEJMP(T)].") +/datum/wires/syndicatebomb + holder_type = /obj/machinery/syndicatebomb + randomize = TRUE + +/datum/wires/syndicatebomb/New(atom/holder) + wires = list( + WIRE_BOOM, WIRE_UNBOLT, + WIRE_ACTIVATE, WIRE_DELAY, WIRE_PROCEED + ) + ..() + +/datum/wires/syndicatebomb/interactable(mob/user) + var/obj/machinery/syndicatebomb/P = holder + if(P.open_panel) + return TRUE + +/datum/wires/syndicatebomb/on_pulse(wire) + var/obj/machinery/syndicatebomb/B = holder + switch(wire) + if(WIRE_BOOM) + if(B.active) + holder.visible_message("[icon2html(B, viewers(holder))] An alarm sounds! It's go-") + B.explode_now = TRUE + tell_admins(B) + else + holder.visible_message("[icon2html(B, viewers(holder))] Nothing happens.") + if(WIRE_UNBOLT) + holder.visible_message("[icon2html(B, viewers(holder))] The bolts spin in place for a moment.") + if(WIRE_DELAY) + if(B.delayedbig) + holder.visible_message("[icon2html(B, viewers(holder))] Nothing happens.") + else + holder.visible_message("[icon2html(B, viewers(holder))] The bomb chirps.") + playsound(B, 'sound/machines/chime.ogg', 30, TRUE) + B.detonation_timer += 300 + if(B.active) + B.delayedbig = TRUE + if(WIRE_PROCEED) + holder.visible_message("[icon2html(B, viewers(holder))] The bomb buzzes ominously!") + playsound(B, 'sound/machines/buzz-sigh.ogg', 30, TRUE) + var/seconds = B.seconds_remaining() + if(seconds >= 61) // Long fuse bombs can suddenly become more dangerous if you tinker with them. + B.detonation_timer = world.time + 600 + else if(seconds >= 21) + B.detonation_timer -= 100 + else if(seconds >= 11) // Both to prevent negative timers and to have a little mercy. + B.detonation_timer = world.time + 100 + if(WIRE_ACTIVATE) + if(!B.active) + holder.visible_message("[icon2html(B, viewers(holder))] You hear the bomb start ticking!") + B.activate() + B.update_icon() + else if(B.delayedlittle) + holder.visible_message("[icon2html(B, viewers(holder))] Nothing happens.") + else + holder.visible_message("[icon2html(B, viewers(holder))] The bomb seems to hesitate for a moment.") + B.detonation_timer += 100 + B.delayedlittle = TRUE + +/datum/wires/syndicatebomb/on_cut(wire, mend) + var/obj/machinery/syndicatebomb/B = holder + switch(wire) + if(WIRE_BOOM) + if(!mend && B.active) + holder.visible_message("[icon2html(B, viewers(holder))] An alarm sounds! It's go-") + B.explode_now = TRUE + tell_admins(B) + if(WIRE_UNBOLT) + if(!mend && B.anchored) + holder.visible_message("[icon2html(B, viewers(holder))] The bolts lift out of the ground!") + playsound(B, 'sound/effects/stealthoff.ogg', 30, TRUE) + B.anchored = FALSE + if(WIRE_PROCEED) + if(!mend && B.active) + holder.visible_message("[icon2html(B, viewers(holder))] An alarm sounds! It's go-") + B.explode_now = TRUE + tell_admins(B) + if(WIRE_ACTIVATE) + if(!mend && B.active) + holder.visible_message("[icon2html(B, viewers(holder))] The timer stops! The bomb has been defused!") + B.active = FALSE + B.delayedlittle = FALSE + B.delayedbig = FALSE + B.update_icon() + +/datum/wires/syndicatebomb/proc/tell_admins(obj/machinery/syndicatebomb/B) + if(istype(B, /obj/machinery/syndicatebomb/training)) + return + var/turf/T = get_turf(B) + log_game("\A [B] was detonated via boom wire at [AREACOORD(T)].") + message_admins("A [B.name] was detonated via boom wire at [ADMIN_VERBOSEJMP(T)].") diff --git a/code/datums/wires/vending.dm b/code/datums/wires/vending.dm index 46217d334283..6113eaaf307a 100644 --- a/code/datums/wires/vending.dm +++ b/code/datums/wires/vending.dm @@ -1,62 +1,62 @@ -/datum/wires/vending - holder_type = /obj/machinery/vending - proper_name = "Vending Unit" - -/datum/wires/vending/New(atom/holder) - wires = list( - WIRE_THROW, WIRE_SHOCK, WIRE_SPEAKER, - WIRE_CONTRABAND, WIRE_IDSCAN - ) - add_duds(1) - ..() - -/datum/wires/vending/interactable(mob/user) - var/obj/machinery/vending/V = holder - if(!issilicon(user) && V.seconds_electrified && V.shock(user, 100)) - return FALSE - if(V.panel_open) - return TRUE - -/datum/wires/vending/get_status() - var/obj/machinery/vending/V = holder - var/list/status = list() - status += "The orange light is [V.seconds_electrified ? "on" : "off"]." - status += "The red light is [V.shoot_inventory ? "off" : "blinking"]." - status += "The green light is [V.extended_inventory ? "on" : "off"]." - status += "A [V.scan_id ? "purple" : "yellow"] light is on." - status += "A white light is [V.age_restrictions ? "on" : "off"]." - status += "The speaker light is [V.shut_up ? "off" : "on"]." - return status - -/datum/wires/vending/on_pulse(wire) - var/obj/machinery/vending/V = holder - switch(wire) - if(WIRE_THROW) - V.shoot_inventory = !V.shoot_inventory - if(WIRE_CONTRABAND) - V.extended_inventory = !V.extended_inventory - if(WIRE_SHOCK) - V.seconds_electrified = MACHINE_DEFAULT_ELECTRIFY_TIME - if(WIRE_IDSCAN) - V.scan_id = !V.scan_id - if(WIRE_SPEAKER) - V.shut_up = !V.shut_up - if(WIRE_AGELIMIT) - V.age_restrictions = !V.age_restrictions - -/datum/wires/vending/on_cut(wire, mend) - var/obj/machinery/vending/V = holder - switch(wire) - if(WIRE_THROW) - V.shoot_inventory = !mend - if(WIRE_CONTRABAND) - V.extended_inventory = FALSE - if(WIRE_SHOCK) - if(mend) - V.seconds_electrified = MACHINE_NOT_ELECTRIFIED - else - V.seconds_electrified = MACHINE_ELECTRIFIED_PERMANENT - if(WIRE_IDSCAN) - V.scan_id = mend - if(WIRE_SPEAKER) - V.shut_up = mend +/datum/wires/vending + holder_type = /obj/machinery/vending + proper_name = "Vending Unit" + +/datum/wires/vending/New(atom/holder) + wires = list( + WIRE_THROW, WIRE_SHOCK, WIRE_SPEAKER, + WIRE_CONTRABAND, WIRE_IDSCAN + ) + add_duds(1) + ..() + +/datum/wires/vending/interactable(mob/user) + var/obj/machinery/vending/V = holder + if(!issilicon(user) && V.seconds_electrified && V.shock(user, 100)) + return FALSE + if(V.panel_open) + return TRUE + +/datum/wires/vending/get_status() + var/obj/machinery/vending/V = holder + var/list/status = list() + status += "The orange light is [V.seconds_electrified ? "on" : "off"]." + status += "The red light is [V.shoot_inventory ? "off" : "blinking"]." + status += "The green light is [V.extended_inventory ? "on" : "off"]." + status += "A [V.scan_id ? "purple" : "yellow"] light is on." + status += "A white light is [V.age_restrictions ? "on" : "off"]." + status += "The speaker light is [V.shut_up ? "off" : "on"]." + return status + +/datum/wires/vending/on_pulse(wire) + var/obj/machinery/vending/V = holder + switch(wire) + if(WIRE_THROW) + V.shoot_inventory = !V.shoot_inventory + if(WIRE_CONTRABAND) + V.extended_inventory = !V.extended_inventory + if(WIRE_SHOCK) + V.seconds_electrified = MACHINE_DEFAULT_ELECTRIFY_TIME + if(WIRE_IDSCAN) + V.scan_id = !V.scan_id + if(WIRE_SPEAKER) + V.shut_up = !V.shut_up + if(WIRE_AGELIMIT) + V.age_restrictions = !V.age_restrictions + +/datum/wires/vending/on_cut(wire, mend) + var/obj/machinery/vending/V = holder + switch(wire) + if(WIRE_THROW) + V.shoot_inventory = !mend + if(WIRE_CONTRABAND) + V.extended_inventory = FALSE + if(WIRE_SHOCK) + if(mend) + V.seconds_electrified = MACHINE_NOT_ELECTRIFIED + else + V.seconds_electrified = MACHINE_ELECTRIFIED_PERMANENT + if(WIRE_IDSCAN) + V.scan_id = mend + if(WIRE_SPEAKER) + V.shut_up = mend diff --git a/code/datums/world_topic.dm b/code/datums/world_topic.dm index 5c08e8349df2..bae755a0701a 100644 --- a/code/datums/world_topic.dm +++ b/code/datums/world_topic.dm @@ -1,220 +1,220 @@ -// SETUP - -/proc/TopicHandlers() - . = list() - var/list/all_handlers = subtypesof(/datum/world_topic) - for(var/I in all_handlers) - var/datum/world_topic/WT = I - var/keyword = initial(WT.keyword) - if(!keyword) - warning("[WT] has no keyword! Ignoring...") - continue - var/existing_path = .[keyword] - if(existing_path) - warning("[existing_path] and [WT] have the same keyword! Ignoring [WT]...") - else if(keyword == "key") - warning("[WT] has keyword 'key'! Ignoring...") - else - .[keyword] = WT - -// DATUM - -/datum/world_topic - var/keyword - var/log = TRUE - var/key_valid - var/require_comms_key = FALSE - -/datum/world_topic/proc/TryRun(list/input) - key_valid = config && (CONFIG_GET(string/comms_key) == input["key"]) - input -= "key" - if(require_comms_key && !key_valid) - . = "Bad Key" - if (input["format"] == "json") - . = list("error" = .) - else - . = Run(input) - if (input["format"] == "json") - . = json_encode(.) - else if(islist(.)) - . = list2params(.) - -/datum/world_topic/proc/Run(list/input) - CRASH("Run() not implemented for [type]!") - -// TOPICS - -/datum/world_topic/ping - keyword = "ping" - log = FALSE - -/datum/world_topic/ping/Run(list/input) - . = 0 - for (var/client/C in GLOB.clients) - ++. - -/datum/world_topic/playing - keyword = "playing" - log = FALSE - -/datum/world_topic/playing/Run(list/input) - return GLOB.player_list.len - -/datum/world_topic/pr_announce - keyword = "announce" - require_comms_key = TRUE - var/static/list/PRcounts = list() //PR id -> number of times announced this round - -/datum/world_topic/pr_announce/Run(list/input) - var/list/payload = json_decode(input["payload"]) - var/id = "[payload["pull_request"]["id"]]" - if(!PRcounts[id]) - PRcounts[id] = 1 - else - ++PRcounts[id] - if(PRcounts[id] > PR_ANNOUNCEMENTS_PER_ROUND) - return - - var/final_composed = "PR: [input[keyword]]" - for(var/client/C in GLOB.clients) - C.AnnouncePR(final_composed) - -/datum/world_topic/ahelp_relay - keyword = "Ahelp" - require_comms_key = TRUE - -/datum/world_topic/ahelp_relay/Run(list/input) - relay_msg_admins("HELP: [input["source"]] [input["message_sender"]]: [input["message"]]") - -/datum/world_topic/comms_console - keyword = "Comms_Console" - require_comms_key = TRUE - -/datum/world_topic/comms_console/Run(list/input) - minor_announce(input["message"], "Incoming message from [input["message_sender"]]") - for(var/obj/machinery/computer/communications/CM in GLOB.machines) - CM.overrideCooldown() - -/datum/world_topic/news_report - keyword = "News_Report" - require_comms_key = TRUE - -/datum/world_topic/news_report/Run(list/input) - minor_announce(input["message"], "Breaking Update From [input["message_sender"]]") - -/datum/world_topic/server_hop - keyword = "server_hop" - -/datum/world_topic/server_hop/Run(list/input) - var/expected_key = input[keyword] - for(var/mob/dead/observer/O in GLOB.player_list) - if(O.key == expected_key) - new /obj/screen/splash(O.client, TRUE) - break - -/datum/world_topic/adminmsg - keyword = "adminmsg" - require_comms_key = TRUE - -/datum/world_topic/adminmsg/Run(list/input) - return TgsPm(input[keyword], input["msg"], input["sender"]) - -/datum/world_topic/namecheck - keyword = "namecheck" - require_comms_key = TRUE - -/datum/world_topic/namecheck/Run(list/input) - //Oh this is a hack, someone refactor the functionality out of the chat command PLS - var/datum/tgs_chat_command/namecheck/NC = new - var/datum/tgs_chat_user/user = new - user.friendly_name = input["sender"] - user.mention = user.friendly_name - return NC.Run(user, input["namecheck"]) - -/datum/world_topic/adminwho - keyword = "adminwho" - require_comms_key = TRUE - -/datum/world_topic/adminwho/Run(list/input) - return tgsadminwho() - -/datum/world_topic/status - keyword = "status" - -/datum/world_topic/status/Run(list/input) - . = list() - .["version"] = GLOB.game_version - .["mode"] = GLOB.master_mode - .["respawn"] = config ? !CONFIG_GET(flag/norespawn) : FALSE - .["enter"] = GLOB.enter_allowed - .["vote"] = CONFIG_GET(flag/allow_vote_mode) - .["ai"] = CONFIG_GET(flag/allow_ai) - .["host"] = world.host ? world.host : null - .["round_id"] = GLOB.round_id - .["players"] = GLOB.clients.len - .["revision"] = GLOB.revdata.commit - .["revision_date"] = GLOB.revdata.date - .["hub"] = GLOB.hub_visibility - - - var/list/adm = get_admin_counts() - var/list/presentmins = adm["present"] - var/list/afkmins = adm["afk"] - .["admins"] = presentmins.len + afkmins.len //equivalent to the info gotten from adminwho - - var/list/mnt = get_mentor_counts() - .["mentors"] = mnt["total"] // we don't have stealth mentors, so we can just use the total. - - .["gamestate"] = SSticker.current_state - - .["map_name"] = SSmapping.config?.map_name || "Loading..." - - if(key_valid) - .["active_players"] = get_active_player_count() - if(SSticker.HasRoundStarted()) - .["real_mode"] = SSticker.mode.name - // Key-authed callers may know the truth behind the "secret" - - .["security_level"] = get_security_level() - .["round_duration"] = SSticker ? round((world.time-SSticker.round_start_time)/10) : 0 - // Amount of world's ticks in seconds, useful for calculating round duration - - //Time dilation stats. - .["time_dilation_current"] = SStime_track.time_dilation_current - .["time_dilation_avg"] = SStime_track.time_dilation_avg - .["time_dilation_avg_slow"] = SStime_track.time_dilation_avg_slow - .["time_dilation_avg_fast"] = SStime_track.time_dilation_avg_fast - - //pop cap stats - .["soft_popcap"] = CONFIG_GET(number/soft_popcap) || 0 - .["hard_popcap"] = CONFIG_GET(number/hard_popcap) || 0 - .["extreme_popcap"] = CONFIG_GET(number/extreme_popcap) || 0 - .["popcap"] = max(CONFIG_GET(number/soft_popcap), CONFIG_GET(number/hard_popcap), CONFIG_GET(number/extreme_popcap)) //generalized field for this concept for use across ss13 codebases - .["bunkered"] = CONFIG_GET(flag/panic_bunker) || FALSE - if(SSshuttle && SSshuttle.emergency) - .["shuttle_mode"] = SSshuttle.emergency.mode - // Shuttle status, see /__DEFINES/stat.dm - .["shuttle_timer"] = SSshuttle.emergency.timeLeft() - // Shuttle timer, in seconds - -/datum/world_topic/whois - keyword = "whoIs" - -/datum/world_topic/whois/Run(list/input) - . = list() - .["players"] = GLOB.clients - - return list2params(.) - -/datum/world_topic/getadmins - keyword = "getAdmins" - -/datum/world_topic/getadmins/Run(list/input) - . = list() - var/list/adm = get_admin_counts() - var/list/presentmins = adm["present"] - var/list/afkmins = adm["afk"] - .["admins"] = presentmins - .["admins"] += afkmins - - return list2params(.) +// SETUP + +/proc/TopicHandlers() + . = list() + var/list/all_handlers = subtypesof(/datum/world_topic) + for(var/I in all_handlers) + var/datum/world_topic/WT = I + var/keyword = initial(WT.keyword) + if(!keyword) + warning("[WT] has no keyword! Ignoring...") + continue + var/existing_path = .[keyword] + if(existing_path) + warning("[existing_path] and [WT] have the same keyword! Ignoring [WT]...") + else if(keyword == "key") + warning("[WT] has keyword 'key'! Ignoring...") + else + .[keyword] = WT + +// DATUM + +/datum/world_topic + var/keyword + var/log = TRUE + var/key_valid + var/require_comms_key = FALSE + +/datum/world_topic/proc/TryRun(list/input) + key_valid = config && (CONFIG_GET(string/comms_key) == input["key"]) + input -= "key" + if(require_comms_key && !key_valid) + . = "Bad Key" + if (input["format"] == "json") + . = list("error" = .) + else + . = Run(input) + if (input["format"] == "json") + . = json_encode(.) + else if(islist(.)) + . = list2params(.) + +/datum/world_topic/proc/Run(list/input) + CRASH("Run() not implemented for [type]!") + +// TOPICS + +/datum/world_topic/ping + keyword = "ping" + log = FALSE + +/datum/world_topic/ping/Run(list/input) + . = 0 + for (var/client/C in GLOB.clients) + ++. + +/datum/world_topic/playing + keyword = "playing" + log = FALSE + +/datum/world_topic/playing/Run(list/input) + return GLOB.player_list.len + +/datum/world_topic/pr_announce + keyword = "announce" + require_comms_key = TRUE + var/static/list/PRcounts = list() //PR id -> number of times announced this round + +/datum/world_topic/pr_announce/Run(list/input) + var/list/payload = json_decode(input["payload"]) + var/id = "[payload["pull_request"]["id"]]" + if(!PRcounts[id]) + PRcounts[id] = 1 + else + ++PRcounts[id] + if(PRcounts[id] > PR_ANNOUNCEMENTS_PER_ROUND) + return + + var/final_composed = "PR: [input[keyword]]" + for(var/client/C in GLOB.clients) + C.AnnouncePR(final_composed) + +/datum/world_topic/ahelp_relay + keyword = "Ahelp" + require_comms_key = TRUE + +/datum/world_topic/ahelp_relay/Run(list/input) + relay_msg_admins("HELP: [input["source"]] [input["message_sender"]]: [input["message"]]") + +/datum/world_topic/comms_console + keyword = "Comms_Console" + require_comms_key = TRUE + +/datum/world_topic/comms_console/Run(list/input) + minor_announce(input["message"], "Incoming message from [input["message_sender"]]") + for(var/obj/machinery/computer/communications/CM in GLOB.machines) + CM.overrideCooldown() + +/datum/world_topic/news_report + keyword = "News_Report" + require_comms_key = TRUE + +/datum/world_topic/news_report/Run(list/input) + minor_announce(input["message"], "Breaking Update From [input["message_sender"]]") + +/datum/world_topic/server_hop + keyword = "server_hop" + +/datum/world_topic/server_hop/Run(list/input) + var/expected_key = input[keyword] + for(var/mob/dead/observer/O in GLOB.player_list) + if(O.key == expected_key) + new /obj/screen/splash(O.client, TRUE) + break + +/datum/world_topic/adminmsg + keyword = "adminmsg" + require_comms_key = TRUE + +/datum/world_topic/adminmsg/Run(list/input) + return TgsPm(input[keyword], input["msg"], input["sender"]) + +/datum/world_topic/namecheck + keyword = "namecheck" + require_comms_key = TRUE + +/datum/world_topic/namecheck/Run(list/input) + //Oh this is a hack, someone refactor the functionality out of the chat command PLS + var/datum/tgs_chat_command/namecheck/NC = new + var/datum/tgs_chat_user/user = new + user.friendly_name = input["sender"] + user.mention = user.friendly_name + return NC.Run(user, input["namecheck"]) + +/datum/world_topic/adminwho + keyword = "adminwho" + require_comms_key = TRUE + +/datum/world_topic/adminwho/Run(list/input) + return tgsadminwho() + +/datum/world_topic/status + keyword = "status" + +/datum/world_topic/status/Run(list/input) + . = list() + .["version"] = GLOB.game_version + .["mode"] = GLOB.master_mode + .["respawn"] = config ? !CONFIG_GET(flag/norespawn) : FALSE + .["enter"] = GLOB.enter_allowed + .["vote"] = CONFIG_GET(flag/allow_vote_mode) + .["ai"] = CONFIG_GET(flag/allow_ai) + .["host"] = world.host ? world.host : null + .["round_id"] = GLOB.round_id + .["players"] = GLOB.clients.len + .["revision"] = GLOB.revdata.commit + .["revision_date"] = GLOB.revdata.date + .["hub"] = GLOB.hub_visibility + + + var/list/adm = get_admin_counts() + var/list/presentmins = adm["present"] + var/list/afkmins = adm["afk"] + .["admins"] = presentmins.len + afkmins.len //equivalent to the info gotten from adminwho + + var/list/mnt = get_mentor_counts() + .["mentors"] = mnt["total"] // we don't have stealth mentors, so we can just use the total. + + .["gamestate"] = SSticker.current_state + + .["map_name"] = SSmapping.config?.map_name || "Loading..." + + if(key_valid) + .["active_players"] = get_active_player_count() + if(SSticker.HasRoundStarted()) + .["real_mode"] = SSticker.mode.name + // Key-authed callers may know the truth behind the "secret" + + .["security_level"] = get_security_level() + .["round_duration"] = SSticker ? round((world.time-SSticker.round_start_time)/10) : 0 + // Amount of world's ticks in seconds, useful for calculating round duration + + //Time dilation stats. + .["time_dilation_current"] = SStime_track.time_dilation_current + .["time_dilation_avg"] = SStime_track.time_dilation_avg + .["time_dilation_avg_slow"] = SStime_track.time_dilation_avg_slow + .["time_dilation_avg_fast"] = SStime_track.time_dilation_avg_fast + + //pop cap stats + .["soft_popcap"] = CONFIG_GET(number/soft_popcap) || 0 + .["hard_popcap"] = CONFIG_GET(number/hard_popcap) || 0 + .["extreme_popcap"] = CONFIG_GET(number/extreme_popcap) || 0 + .["popcap"] = max(CONFIG_GET(number/soft_popcap), CONFIG_GET(number/hard_popcap), CONFIG_GET(number/extreme_popcap)) //generalized field for this concept for use across ss13 codebases + .["bunkered"] = CONFIG_GET(flag/panic_bunker) || FALSE + if(SSshuttle && SSshuttle.emergency) + .["shuttle_mode"] = SSshuttle.emergency.mode + // Shuttle status, see /__DEFINES/stat.dm + .["shuttle_timer"] = SSshuttle.emergency.timeLeft() + // Shuttle timer, in seconds + +/datum/world_topic/whois + keyword = "whoIs" + +/datum/world_topic/whois/Run(list/input) + . = list() + .["players"] = GLOB.clients + + return list2params(.) + +/datum/world_topic/getadmins + keyword = "getAdmins" + +/datum/world_topic/getadmins/Run(list/input) + . = list() + var/list/adm = get_admin_counts() + var/list/presentmins = adm["present"] + var/list/afkmins = adm["afk"] + .["admins"] = presentmins + .["admins"] += afkmins + + return list2params(.) diff --git a/code/game/area/Space_Station_13_areas.dm b/code/game/area/Space_Station_13_areas.dm index c67662928ac1..ec8fd953fa22 100644 --- a/code/game/area/Space_Station_13_areas.dm +++ b/code/game/area/Space_Station_13_areas.dm @@ -1,1253 +1,1253 @@ -/* - -### This file contains a list of all the areas in your station. Format is as follows: - -/area/CATEGORY/OR/DESCRIPTOR/NAME (you can make as many subdivisions as you want) - name = "NICE NAME" (not required but makes things really nice) - icon = 'ICON FILENAME' (defaults to 'icons/turf/areas.dmi') - icon_state = "NAME OF ICON" (defaults to "unknown" (blank)) - requires_power = FALSE (defaults to true) - ambientsounds = list() (defaults to GENERIC from sound.dm. override it as "ambientsounds = list('sound/ambience/signal.ogg')" or using another define. - -NOTE: there are two lists of areas in the end of this file: centcom and station itself. Please maintain these lists valid. --rastaf0 - -*/ - - -/*-----------------------------------------------------------------------------*/ - -/area/ai_monitored //stub defined ai_monitored.dm - -/area/ai_monitored/turret_protected - -/area/space - icon_state = "space" - requires_power = TRUE - always_unpowered = TRUE - dynamic_lighting = DYNAMIC_LIGHTING_DISABLED - power_light = FALSE - power_equip = FALSE - power_environ = FALSE - valid_territory = FALSE - outdoors = TRUE - ambientsounds = SPACE - blob_allowed = FALSE //Eating up space doesn't count for victory as a blob. - flags_1 = CAN_BE_DIRTY_1 - -/area/space/nearstation - icon_state = "space_near" - dynamic_lighting = DYNAMIC_LIGHTING_IFSTARLIGHT - -/area/start - name = "start area" - icon_state = "start" - requires_power = FALSE - dynamic_lighting = DYNAMIC_LIGHTING_DISABLED - has_gravity = STANDARD_GRAVITY - - -/area/testroom - requires_power = FALSE - name = "Test Room" - icon_state = "storage" - -//EXTRA - -/area/asteroid - name = "Asteroid" - icon_state = "asteroid" - requires_power = FALSE - has_gravity = STANDARD_GRAVITY - blob_allowed = FALSE //Nope, no winning on the asteroid as a blob. Gotta eat the station. - valid_territory = FALSE - ambientsounds = MINING - flags_1 = CAN_BE_DIRTY_1 - -/area/asteroid/nearstation - dynamic_lighting = DYNAMIC_LIGHTING_FORCED - ambientsounds = RUINS - always_unpowered = FALSE - requires_power = TRUE - blob_allowed = TRUE - -/area/asteroid/nearstation/bomb_site - name = "Bomb Testing Asteroid" - -//STATION13 - -//Maintenance - -/area/maintenance - ambientsounds = MAINTENANCE - valid_territory = FALSE - - -//Departments - -/area/maintenance/department/chapel - name = "Chapel Maintenance" - icon_state = "maint_chapel" - -/area/maintenance/department/chapel/monastery - name = "Monastery Maintenance" - icon_state = "maint_monastery" - -/area/maintenance/department/crew_quarters/bar - name = "Bar Maintenance" - icon_state = "maint_bar" - -/area/maintenance/department/crew_quarters/dorms - name = "Dormitory Maintenance" - icon_state = "maint_dorms" - -/area/maintenance/department/eva - name = "EVA Maintenance" - icon_state = "maint_eva" - -/area/maintenance/department/electrical - name = "Electrical Maintenance" - icon_state = "maint_electrical" - -/area/maintenance/department/engine/atmos - name = "Atmospherics Maintenance" - icon_state = "maint_atmos" - -/area/maintenance/department/security - name = "Security Maintenance" - icon_state = "maint_sec" - -/area/maintenance/department/security/upper - name = "Upper Security Maintenance" - -/area/maintenance/department/security/brig - name = "Brig Maintenance" - icon_state = "maint_brig" - -/area/maintenance/department/medical - name = "Medbay Maintenance" - icon_state = "medbay_maint" - -/area/maintenance/department/medical/central - name = "Central Medbay Maintenance" - icon_state = "medbay_maint_central" - -/area/maintenance/department/medical/morgue - name = "Morgue Maintenance" - icon_state = "morgue_maint" - -/area/maintenance/department/science - name = "Science Maintenance" - icon_state = "maint_sci" - -/area/maintenance/department/science/central - name = "Central Science Maintenance" - icon_state = "maint_sci_central" - -/area/maintenance/department/cargo - name = "Cargo Maintenance" - icon_state = "maint_cargo" - -/area/maintenance/department/bridge - name = "Bridge Maintenance" - icon_state = "maint_bridge" - -/area/maintenance/department/engine - name = "Engineering Maintenance" - icon_state = "maint_engi" - -/area/maintenance/department/science/xenobiology - name = "Xenobiology Maintenance" - icon_state = "xenomaint" - xenobiology_compatible = TRUE - - -//Maintenance - Generic - -/area/maintenance/aft - name = "Aft Maintenance" - icon_state = "amaint" - -/area/maintenance/aft/upper - name = "Upper Aft Maintenance" - -/area/maintenance/aft/secondary - name = "Aft Maintenance" - icon_state = "amaint_2" - -/area/maintenance/central - name = "Central Maintenance" - icon_state = "maintcentral" - -/area/maintenance/central/secondary - name = "Central Maintenance" - icon_state = "maintcentral" - -/area/maintenance/fore - name = "Fore Maintenance" - icon_state = "fmaint" - -/area/maintenance/fore/upper - name = "Upper Fore Maintenance" - -/area/maintenance/fore/secondary - name = "Fore Maintenance" - icon_state = "fmaint_2" - -/area/maintenance/starboard - name = "Starboard Maintenance" - icon_state = "smaint" - -/area/maintenance/starboard/upper - name = "Upper Starboard Maintenance" - -/area/maintenance/starboard/central - name = "Central Starboard Maintenance" - icon_state = "smaint" - -/area/maintenance/starboard/secondary - name = "Secondary Starboard Maintenance" - icon_state = "smaint_2" - -/area/maintenance/starboard/aft - name = "Starboard Quarter Maintenance" - icon_state = "asmaint" - -/area/maintenance/starboard/aft/secondary - name = "Secondary Starboard Quarter Maintenance" - icon_state = "asmaint_2" - -/area/maintenance/starboard/fore - name = "Starboard Bow Maintenance" - icon_state = "fsmaint" - -/area/maintenance/port - name = "Port Maintenance" - icon_state = "pmaint" - -/area/maintenance/port/central - name = "Central Port Maintenance" - icon_state = "maintcentral" - -/area/maintenance/port/aft - name = "Port Quarter Maintenance" - icon_state = "apmaint" - -/area/maintenance/port/fore - name = "Port Bow Maintenance" - icon_state = "fpmaint" - -/area/maintenance/disposal - name = "Waste Disposal" - icon_state = "disposal" - -/area/maintenance/disposal/incinerator - name = "Incinerator" - icon_state = "disposal" - - -//Hallway - -/area/hallway/primary/aft - name = "Aft Primary Hallway" - icon_state = "hallA" - -/area/hallway/primary/fore - name = "Fore Primary Hallway" - icon_state = "hallF" - -/area/hallway/primary/starboard - name = "Starboard Primary Hallway" - icon_state = "hallS" - -/area/hallway/primary/port - name = "Port Primary Hallway" - icon_state = "hallP" - -/area/hallway/primary/central - name = "Central Primary Hallway" - icon_state = "hallC" - -/area/hallway/primary/upper - name = "Upper Central Primary Hallway" - icon_state = "hallC" - - -/area/hallway/secondary/command - name = "Command Hallway" - icon_state = "bridge_hallway" - -/area/hallway/secondary/construction - name = "Construction Area" - icon_state = "construction" - -/area/hallway/secondary/exit - name = "Escape Shuttle Hallway" - icon_state = "escape" - -/area/hallway/secondary/exit/departure_lounge - name = "Departure Lounge" - icon_state = "escape_lounge" - -/area/hallway/secondary/entry - name = "Arrival Shuttle Hallway" - icon_state = "entry" - -/area/hallway/secondary/service - name = "Service Hallway" - icon_state = "hall_service" - -//Command - -/area/bridge - name = "Bridge" - icon_state = "bridge" - ambientsounds = list('sound/ambience/signal.ogg') - -/area/lieutenant - name = "Lieutenant's Office" - icon_state = "blueold" - -/area/bridge/meeting_room - name = "Heads of Staff Meeting Room" - icon_state = "meeting" - -/area/bridge/meeting_room/council - name = "Council Chamber" - icon_state = "meeting" - -/area/bridge/showroom/corporate - name = "Corporate Showroom" - icon_state = "showroom" - -/area/crew_quarters/heads/captain - name = "Captain's Office" - icon_state = "captain" - -/area/crew_quarters/heads/captain/private - name = "Captain's Quarters" - icon_state = "captain" - -/area/crew_quarters/heads/chief - name = "Chief Engineer's Office" - icon_state = "ce_office" - -/area/crew_quarters/heads/cmo - name = "Chief Medical Officer's Office" - icon_state = "cmo_office" - -/area/crew_quarters/heads/head_of_personnel - name = "Head of Personnel's Office" - icon_state = "hop_office" - -/area/crew_quarters/heads/hos - name = "Head of Security's Office" - icon_state = "hos_office" - -/area/crew_quarters/heads/hor - name = "Research Director's Office" - icon_state = "rd_office" - -/area/comms - name = "Communications Relay" - icon_state = "tcomsatcham" - -/area/server - name = "Messaging Server Room" - icon_state = "server" - -//Crew - -/area/crew_quarters/dorms - name = "Dormitories" - icon_state = "Sleep" - safe = TRUE - -/area/crew_quarters/dorms/barracks - name = "Sleep Barracks" - -/area/crew_quarters/dorms/barracks/male - name = "Male Sleep Barracks" - -/area/crew_quarters/dorms/barracks/female - name = "Female Sleep Barracks" - -/area/crew_quarters/toilet - name = "Dormitory Toilets" - icon_state = "toilet" - -/area/crew_quarters/toilet/auxiliary - name = "Auxiliary Restrooms" - icon_state = "toilet" - -/area/crew_quarters/toilet/locker - name = "Locker Toilets" - icon_state = "toilet" - -/area/crew_quarters/toilet/restrooms - name = "Restrooms" - icon_state = "toilet" - -/area/crew_quarters/locker - name = "Locker Room" - icon_state = "locker" - -/area/crew_quarters/lounge - name = "Lounge" - icon_state = "yellow" - -/area/crew_quarters/fitness - name = "Fitness Room" - icon_state = "fitness" - -/area/crew_quarters/fitness/locker_room - name = "Unisex Locker Room" - icon_state = "fitness" - -/area/crew_quarters/fitness/locker_room/male - name = "Male Locker Room" - -/area/crew_quarters/fitness/locker_room/female - name = "Female Locker Room" - - -/area/crew_quarters/fitness/recreation - name = "Recreation Area" - icon_state = "fitness" - -/area/crew_quarters/cafeteria - name = "Cafeteria" - icon_state = "cafeteria" - -/area/crew_quarters/kitchen - name = "Kitchen" - icon_state = "kitchen" - -/area/crew_quarters/kitchen/coldroom - name = "Kitchen Cold Room" - icon_state = "kitchen_cold" - -/area/crew_quarters/bar - name = "Bar" - icon_state = "bar" - mood_bonus = 5 - mood_message = "I love being in the bar!\n" - -/area/crew_quarters/bar/atrium - name = "Atrium" - icon_state = "bar" - -/area/crew_quarters/electronic_marketing_den - name = "Electronic Marketing Den" - icon_state = "bar" - -/area/crew_quarters/abandoned_gambling_den - name = "Abandoned Gambling Den" - icon_state = "abandoned_g_den" - -/area/crew_quarters/abandoned_gambling_den/secondary - icon_state = "abandoned_g_den_2" - -/area/crew_quarters/theatre - name = "Theatre" - icon_state = "Theatre" - -/area/crew_quarters/theatre/abandoned - name = "Abandoned Theatre" - icon_state = "Theatre" - -/area/library - name = "Library" - icon_state = "library" - flags_1 = CULT_PERMITTED_1 - -/area/library/lounge - name = "Library Lounge" - icon_state = "library" - -/area/library/artgallery - name = " Art Gallery" - icon_state = "library" - -/area/library/private - name = "Library Private Study" - icon_state = "library" - -/area/library/upper - name = "Library Upper Floor" - icon_state = "library" - -/area/library/printer - name = "Library Printer Room" - icon_state = "library" - -/area/library/abandoned - name = "Abandoned Library" - icon_state = "library" - flags_1 = CULT_PERMITTED_1 - -/area/chapel - icon_state = "chapel" - ambientsounds = HOLY - flags_1 = NONE - -/area/chapel/main - name = "Chapel" - -/area/chapel/main/monastery - name = "Monastery" - -/area/chapel/office - name = "Chapel Office" - icon_state = "chapeloffice" - -/area/chapel/asteroid - name = "Chapel Asteroid" - icon_state = "explored" - -/area/chapel/asteroid/monastery - name = "Monastery Asteroid" - -/area/chapel/dock - name = "Chapel Dock" - icon_state = "construction" - -/area/lawoffice - name = "Law Office" - icon_state = "law" - - -//Engineering - -/area/engine - ambientsounds = ENGINEERING - -/area/engine/engine_smes - name = "Engineering SMES" - icon_state = "engine_smes" - -/area/engine/engineering - name = "Engineering" - icon_state = "engine" - -/area/engine/atmos - name = "Atmospherics" - icon_state = "atmos" - flags_1 = CULT_PERMITTED_1 - -/area/engine/atmos/upper - name = "Upper Atmospherics" - -/area/engine/atmospherics_engine - name = "Atmospherics Engine" - icon_state = "atmos_engine" - valid_territory = FALSE - -/area/engine/engine_room //donut station specific - name = "Engine Room" - icon_state = "atmos_engine" - -/area/engine/lobby - name = "Engineering Lobby" - icon_state = "engi_lobby" - -/area/engine/engine_room/external - name = "Supermatter External Access" - icon_state = "engine_foyer" - -/area/engine/supermatter - name = "Supermatter Engine" - icon_state = "engine_sm" - valid_territory = FALSE - -/area/engine/break_room - name = "Engineering Foyer" - icon_state = "engine_foyer" - -/area/engine/gravity_generator - name = "Gravity Generator Room" - icon_state = "grav_gen" - -/area/engine/storage - name = "Engineering Storage" - icon_state = "engi_storage" - -/area/engine/storage_shared - name = "Shared Engineering Storage" - icon_state = "engi_storage" - -/area/engine/transit_tube - name = "Transit Tube" - icon_state = "transit_tube" - - -//Solars - -/area/solar - requires_power = FALSE - dynamic_lighting = DYNAMIC_LIGHTING_IFSTARLIGHT - valid_territory = FALSE - blob_allowed = FALSE - flags_1 = NONE - ambientsounds = ENGINEERING - -/area/solar/fore - name = "Fore Solar Array" - icon_state = "yellow" - -/area/solar/aft - name = "Aft Solar Array" - icon_state = "yellow" - -/area/solar/aux/port - name = "Port Bow Auxiliary Solar Array" - icon_state = "panelsA" - -/area/solar/aux/starboard - name = "Starboard Bow Auxiliary Solar Array" - icon_state = "panelsA" - -/area/solar/starboard - name = "Starboard Solar Array" - icon_state = "panelsS" - -/area/solar/starboard/aft - name = "Starboard Quarter Solar Array" - icon_state = "panelsAS" - -/area/solar/starboard/fore - name = "Starboard Bow Solar Array" - icon_state = "panelsFS" - -/area/solar/port - name = "Port Solar Array" - icon_state = "panelsP" - -/area/solar/port/aft - name = "Port Quarter Solar Array" - icon_state = "panelsAP" - -/area/solar/port/fore - name = "Port Bow Solar Array" - icon_state = "panelsFP" - -/area/solar/aisat - name = "AI Satellite Solars" - icon_state = "yellow" - -//Solar Maint - -/area/maintenance/solars - name = "Solar Maintenance" - icon_state = "yellow" - -/area/maintenance/solars/port - name = "Port Solar Maintenance" - icon_state = "SolarcontrolP" - -/area/maintenance/solars/port/aft - name = "Port Quarter Solar Maintenance" - icon_state = "SolarcontrolAP" - -/area/maintenance/solars/port/fore - name = "Port Bow Solar Maintenance" - icon_state = "SolarcontrolFP" - -/area/maintenance/solars/starboard - name = "Starboard Solar Maintenance" - icon_state = "SolarcontrolS" - -/area/maintenance/solars/starboard/aft - name = "Starboard Quarter Solar Maintenance" - icon_state = "SolarcontrolAS" - -/area/maintenance/solars/starboard/fore - name = "Starboard Bow Solar Maintenance" - icon_state = "SolarcontrolFS" - -//Teleporter - -/area/teleporter - name = "Teleporter Room" - icon_state = "teleporter" - ambientsounds = ENGINEERING - -/area/gateway - name = "Gateway" - icon_state = "gateway" - ambientsounds = ENGINEERING - -//MedBay - -/area/medical - name = "Medical" - icon_state = "medbay3" - ambientsounds = MEDICAL - -/area/medical/abandoned - name = "Abandoned Medbay" - icon_state = "medbay3" - ambientsounds = list('sound/ambience/signal.ogg') - -/area/medical/medbay/central - name = "Medbay Central" - icon_state = "medbay" - -/area/medical/medbay/lobby - name = "Medbay Lobby" - icon_state = "medbay" - - //Medbay is a large area, these additional areas help level out APC load. - -/area/medical/medbay/zone2 - name = "Medbay" - icon_state = "medbay2" - -/area/medical/medbay/aft - name = "Medbay Aft" - icon_state = "medbay3" - -/area/medical/storage - name = "Medbay Storage" - icon_state = "medbay2" - -/area/medical/paramedic - name = "Paramedic Dispatch" - icon_state = "medbay2" - -/area/medical/office - name = "Medical Office" - icon_state = "medoffice" - -/area/medical/surgery/room_c - name = "Surgery C" - icon_state = "surgery" - -/area/medical/surgery/room_d - name = "Surgery D" - icon_state = "surgery" - -/area/medical/break_room - name = "Medical Break Room" - icon_state = "medbay2" - -/area/medical/coldroom - name = "Medical Cold Room" - icon_state = "kitchen_cold" - -/area/medical/patients_rooms - name = "Patients' Rooms" - icon_state = "patients" - -/area/medical/patients_rooms/room_a - name = "Patient Room A" - icon_state = "patients" - -/area/medical/patients_rooms/room_b - name = "Patient Room B" - icon_state = "patients" - -/area/medical/virology - name = "Virology" - icon_state = "virology" - flags_1 = CULT_PERMITTED_1 - -/area/medical/morgue - name = "Morgue" - icon_state = "morgue" - ambientsounds = SPOOKY - -/area/medical/chemistry - name = "Chemistry" - icon_state = "chem" - -/area/medical/pharmacy - name = "Pharmacy" - icon_state = "pharmacy" - -/area/medical/surgery - name = "Surgery" - icon_state = "surgery" - -/area/medical/surgery/room_b - name = "Surgery B" - icon_state = "surgery" - -/area/medical/cryo - name = "Cryogenics" - icon_state = "cryo" - -/area/medical/exam_room - name = "Exam Room" - icon_state = "exam_room" - -/area/medical/genetics - name = "Genetics Lab" - icon_state = "genetics" - -/area/medical/sleeper - name = "Medbay Treatment Center" - icon_state = "exam_room" - -/area/medical/psychology - name = "Psychology Office" - icon_state = "psychology" - mood_bonus = 3 - mood_message = "I feel at ease here.\n" - ambientsounds = list('sound/ambience/aurora_caelus_short.ogg') - -//Security - -/area/security - name = "Security" - icon_state = "security" - ambientsounds = HIGHSEC - -/area/security/main - name = "Security Office" - icon_state = "security" - -/area/security/brig - name = "Brig" - icon_state = "brig" - -/area/security/brig/upper - name = "Brig Overlook" - -/area/security/courtroom - name = "Courtroom" - icon_state = "courtroom" - -/area/security/prison - name = "Prison Wing" - icon_state = "sec_prison" - -/area/security/prison/toilet //radproof - name = "Prison Toilet" - icon_state = "sec_prison_safe" - -/area/security/prison/safe //radproof - name = "Prison Wing Cells" - icon_state = "sec_prison_safe" - -/area/security/prison/upper - name = "Upper Prison Wing" - icon_state = "prison_upper" - -/area/security/prison/visit - name = "Prison Visitation Area" - icon_state = "prison_visit" - -/area/security/prison/rec - name = "Prison Rec Room" - icon_state = "prison_rec" - -/area/security/prison/mess - name = "Prison Mess Hall" - icon_state = "prison_mess" - -/area/security/prison/work - name = "Prison Work Room" - icon_state = "prison_work" - -/area/security/prison/shower - name = "Prison Shower" - icon_state = "prison_shower" - -/area/security/prison/workout - name = "Prison Gym" - icon_state = "prison_workout" - -/area/security/prison/garden - name = "Prison Garden" - icon_state = "prison_garden" - -/area/security/processing - name = "Labor Shuttle Dock" - icon_state = "sec_prison" - -/area/security/processing/cremation - name = "Security Crematorium" - icon_state = "sec_prison" - -/area/security/warden - name = "Brig Control" - icon_state = "Warden" - -/area/security/detectives_office - name = "Detective's Office" - icon_state = "detective" - ambientsounds = list('sound/ambience/ambidet1.ogg','sound/ambience/ambidet2.ogg') - -/area/security/detectives_office/private_investigators_office - name = "Private Investigator's Office" - icon_state = "detective" - -/area/security/range - name = "Firing Range" - icon_state = "firingrange" - -/area/security/execution - icon_state = "execution_room" - -/area/security/execution/transfer - name = "Transfer Centre" - -/area/security/execution/education - name = "Prisoner Education Chamber" - -/area/security/nuke_storage - name = "Vault" - icon_state = "nuke_storage" - -/area/ai_monitored/nuke_storage - name = "Vault" - icon_state = "nuke_storage" - -/area/security/checkpoint - name = "Security Checkpoint" - icon_state = "checkpoint1" - -/area/security/checkpoint/auxiliary - icon_state = "checkpoint_aux" - -/area/security/checkpoint/escape - icon_state = "checkpoint_esc" - -/area/security/checkpoint/supply - name = "Security Post - Cargo Bay" - icon_state = "checkpoint_supp" - -/area/security/checkpoint/engineering - name = "Security Post - Engineering" - icon_state = "checkpoint_engi" - -/area/security/checkpoint/medical - name = "Security Post - Medbay" - icon_state = "checkpoint_med" - -/area/security/checkpoint/science - name = "Security Post - Science" - icon_state = "checkpoint_sci" - -/area/security/checkpoint/science/research - name = "Security Post - Research Division" - icon_state = "checkpoint_res" - -/area/security/checkpoint/customs - name = "Customs" - icon_state = "customs_point" - -/area/security/checkpoint/customs/auxiliary - icon_state = "customs_point_aux" - - -//Service - -/area/quartermaster - name = "Quartermasters" - icon_state = "quart" - -/area/quartermaster/sorting - name = "Delivery Office" - icon_state = "cargo_delivery" - -/area/quartermaster/warehouse - name = "Warehouse" - icon_state = "cargo_warehouse" - -/area/quartermaster/warehouse/upper - name = "Upper Warehouse" - -/area/quartermaster/office - name = "Cargo Office" - icon_state = "quartoffice" - -/area/quartermaster/storage - name = "Cargo Bay" - icon_state = "cargo_bay" - -/area/quartermaster/qm - name = "Quartermaster's Office" - icon_state = "quart" - -/area/quartermaster/qm/perch - name = "Quartermaster's Perch" - icon_state = "quartperch" - -/area/quartermaster/miningdock - name = "Mining Dock" - icon_state = "mining" - -/area/quartermaster/miningoffice - name = "Mining Office" - icon_state = "mining" - -/area/janitor - name = "Custodial Closet" - icon_state = "janitor" - flags_1 = CULT_PERMITTED_1 - -/area/hydroponics - name = "Hydroponics" - icon_state = "hydro" - -/area/hydroponics/upper - name = "Upper Hydroponics" - icon_state = "hydro" - -/area/hydroponics/garden - name = "Garden" - icon_state = "garden" - -/area/hydroponics/garden/abandoned - name = "Abandoned Garden" - icon_state = "abandoned_garden" - -/area/hydroponics/garden/monastery - name = "Monastery Garden" - icon_state = "hydro" - - -//Science - -/area/science - name = "Science Division" - icon_state = "toxlab" - -/area/science/lab - name = "Research and Development" - icon_state = "toxlab" - -/area/science/xenobiology - name = "Xenobiology Lab" - icon_state = "toxlab" - -/area/science/storage - name = "Toxins Storage" - icon_state = "toxstorage" - -/area/science/test_area - valid_territory = FALSE - name = "Toxins Test Area" - icon_state = "toxtest" - -/area/science/mixing - name = "Toxins Mixing Lab" - icon_state = "toxmix" - -/area/science/mixing/chamber - name = "Toxins Mixing Chamber" - icon_state = "toxmix" - valid_territory = FALSE - -/area/science/misc_lab - name = "Testing Lab" - icon_state = "toxmisc" - -/area/science/misc_lab/range - name = "Research Testing Range" - icon_state = "toxmisc" - -/area/science/server - name = "Research Division Server Room" - icon_state = "server" - -/area/science/explab - name = "Experimentation Lab" - icon_state = "toxmisc" - -/area/science/robotics - name = "Robotics" - icon_state = "medresearch" - -/area/science/robotics/mechbay - name = "Mech Bay" - icon_state = "mechbay" - -/area/science/robotics/lab - name = "Robotics Lab" - icon_state = "ass_line" - -/area/science/research - name = "Research Division" - icon_state = "medresearch" - -/area/science/research/abandoned - name = "Abandoned Research Lab" - icon_state = "medresearch" - -/area/science/nanite - name = "Nanite Lab" - icon_state = "toxmisc" - -//Storage - -/area/storage/tools - name = "Auxiliary Tool Storage" - icon_state = "storage" - -/area/storage/primary - name = "Primary Tool Storage" - icon_state = "primarystorage" - -/area/storage/art - name = "Art Supply Storage" - icon_state = "storage" - -/area/storage/tcom - name = "Telecomms Storage" - icon_state = "green" - valid_territory = FALSE - -/area/storage/eva - name = "EVA Storage" - icon_state = "eva" - -/area/storage/emergency/starboard - name = "Starboard Emergency Storage" - icon_state = "emergencystorage" - -/area/storage/emergency/port - name = "Port Emergency Storage" - icon_state = "emergencystorage" - -/area/storage/tech - name = "Technical Storage" - icon_state = "auxstorage" - -//Construction - -/area/construction - name = "Construction Area" - icon_state = "yellow" - ambientsounds = ENGINEERING - -/area/construction/mining/aux_base - name = "Auxiliary Base Construction" - icon_state = "aux_base_construction" - -/area/construction/storage_wing - name = "Storage Wing" - icon_state = "storage_wing" - -// Vacant Rooms -/area/vacant_room - name = "Vacant Room" - icon_state = "vacant_room" - ambientsounds = MAINTENANCE - -/area/vacant_room/office - name = "Vacant Office" - icon_state = "vacant_office" - -/area/vacant_room/commissary - name = "Vacant Commissary" - icon_state = "vacant_commissary" - -//AI - -/area/ai_monitored/security/armory - name = "Armory" - icon_state = "armory" - ambientsounds = HIGHSEC - -/area/ai_monitored/security/armory/upper - name = "Upper Armory" - -/area/ai_monitored/storage/eva - name = "EVA Storage" - icon_state = "eva" - ambientsounds = HIGHSEC - -/area/ai_monitored/storage/eva/upper - name = "Upper EVA Storage" - -/area/ai_monitored/storage/satellite - name = "AI Satellite Maint" - icon_state = "storage" - ambientsounds = HIGHSEC - - //Turret_protected - -/area/ai_monitored/turret_protected - ambientsounds = list('sound/ambience/ambimalf.ogg', 'sound/ambience/ambitech.ogg', 'sound/ambience/ambitech2.ogg', 'sound/ambience/ambiatmos.ogg', 'sound/ambience/ambiatmos2.ogg') - -/area/ai_monitored/turret_protected/ai_upload - name = "AI Upload Chamber" - icon_state = "ai_upload" - -/area/ai_monitored/turret_protected/ai_upload_foyer - name = "AI Upload Access" - icon_state = "ai_foyer" - -/area/ai_monitored/turret_protected/ai - name = "AI Chamber" - icon_state = "ai_chamber" - -/area/ai_monitored/turret_protected/aisat - name = "AI Satellite" - icon_state = "ai" - -/area/ai_monitored/turret_protected/aisat/atmos - name = "AI Satellite Atmos" - icon_state = "ai" - -/area/ai_monitored/turret_protected/aisat/foyer - name = "AI Satellite Foyer" - icon_state = "ai" - -/area/ai_monitored/turret_protected/aisat/service - name = "AI Satellite Service" - icon_state = "ai" - -/area/ai_monitored/turret_protected/aisat/hallway - name = "AI Satellite Hallway" - icon_state = "ai" - -/area/aisat - name = "AI Satellite Exterior" - icon_state = "yellow" - -/area/ai_monitored/turret_protected/aisat_interior - name = "AI Satellite Antechamber" - icon_state = "ai" - -/area/ai_monitored/turret_protected/AIsatextAS - name = "AI Sat Ext" - icon_state = "storage" - -/area/ai_monitored/turret_protected/AIsatextAP - name = "AI Sat Ext" - icon_state = "storage" - - -// Telecommunications Satellite - -/area/tcommsat - ambientsounds = list('sound/ambience/ambisin2.ogg', 'sound/ambience/signal.ogg', 'sound/ambience/signal.ogg', 'sound/ambience/ambigen10.ogg', 'sound/ambience/ambitech.ogg',\ - 'sound/ambience/ambitech2.ogg', 'sound/ambience/ambitech3.ogg', 'sound/ambience/ambimystery.ogg') - -/area/tcommsat/computer - name = "Telecomms Control Room" - icon_state = "tcomsatcomp" - -/area/tcommsat/server - name = "Telecomms Server Room" - icon_state = "tcomsatcham" - -/area/tcommsat/server/upper - name = "Upper Telecomms Server Room" - -//External Hull Access -/area/maintenance/external - name = "External Hull Access" - icon_state = "amaint" - -/area/maintenance/external/aft - name = "Aft External Hull Access" - -/area/maintenance/external/port - name = "Port External Hull Access" - -/area/maintenance/external/port/bow - name = "Port Bow External Hull Access" +/* + +### This file contains a list of all the areas in your station. Format is as follows: + +/area/CATEGORY/OR/DESCRIPTOR/NAME (you can make as many subdivisions as you want) + name = "NICE NAME" (not required but makes things really nice) + icon = 'ICON FILENAME' (defaults to 'icons/turf/areas.dmi') + icon_state = "NAME OF ICON" (defaults to "unknown" (blank)) + requires_power = FALSE (defaults to true) + ambientsounds = list() (defaults to GENERIC from sound.dm. override it as "ambientsounds = list('sound/ambience/signal.ogg')" or using another define. + +NOTE: there are two lists of areas in the end of this file: centcom and station itself. Please maintain these lists valid. --rastaf0 + +*/ + + +/*-----------------------------------------------------------------------------*/ + +/area/ai_monitored //stub defined ai_monitored.dm + +/area/ai_monitored/turret_protected + +/area/space + icon_state = "space" + requires_power = TRUE + always_unpowered = TRUE + dynamic_lighting = DYNAMIC_LIGHTING_DISABLED + power_light = FALSE + power_equip = FALSE + power_environ = FALSE + valid_territory = FALSE + outdoors = TRUE + ambientsounds = SPACE + blob_allowed = FALSE //Eating up space doesn't count for victory as a blob. + flags_1 = CAN_BE_DIRTY_1 + +/area/space/nearstation + icon_state = "space_near" + dynamic_lighting = DYNAMIC_LIGHTING_IFSTARLIGHT + +/area/start + name = "start area" + icon_state = "start" + requires_power = FALSE + dynamic_lighting = DYNAMIC_LIGHTING_DISABLED + has_gravity = STANDARD_GRAVITY + + +/area/testroom + requires_power = FALSE + name = "Test Room" + icon_state = "storage" + +//EXTRA + +/area/asteroid + name = "Asteroid" + icon_state = "asteroid" + requires_power = FALSE + has_gravity = STANDARD_GRAVITY + blob_allowed = FALSE //Nope, no winning on the asteroid as a blob. Gotta eat the station. + valid_territory = FALSE + ambientsounds = MINING + flags_1 = CAN_BE_DIRTY_1 + +/area/asteroid/nearstation + dynamic_lighting = DYNAMIC_LIGHTING_FORCED + ambientsounds = RUINS + always_unpowered = FALSE + requires_power = TRUE + blob_allowed = TRUE + +/area/asteroid/nearstation/bomb_site + name = "Bomb Testing Asteroid" + +//STATION13 + +//Maintenance + +/area/maintenance + ambientsounds = MAINTENANCE + valid_territory = FALSE + + +//Departments + +/area/maintenance/department/chapel + name = "Chapel Maintenance" + icon_state = "maint_chapel" + +/area/maintenance/department/chapel/monastery + name = "Monastery Maintenance" + icon_state = "maint_monastery" + +/area/maintenance/department/crew_quarters/bar + name = "Bar Maintenance" + icon_state = "maint_bar" + +/area/maintenance/department/crew_quarters/dorms + name = "Dormitory Maintenance" + icon_state = "maint_dorms" + +/area/maintenance/department/eva + name = "EVA Maintenance" + icon_state = "maint_eva" + +/area/maintenance/department/electrical + name = "Electrical Maintenance" + icon_state = "maint_electrical" + +/area/maintenance/department/engine/atmos + name = "Atmospherics Maintenance" + icon_state = "maint_atmos" + +/area/maintenance/department/security + name = "Security Maintenance" + icon_state = "maint_sec" + +/area/maintenance/department/security/upper + name = "Upper Security Maintenance" + +/area/maintenance/department/security/brig + name = "Brig Maintenance" + icon_state = "maint_brig" + +/area/maintenance/department/medical + name = "Medbay Maintenance" + icon_state = "medbay_maint" + +/area/maintenance/department/medical/central + name = "Central Medbay Maintenance" + icon_state = "medbay_maint_central" + +/area/maintenance/department/medical/morgue + name = "Morgue Maintenance" + icon_state = "morgue_maint" + +/area/maintenance/department/science + name = "Science Maintenance" + icon_state = "maint_sci" + +/area/maintenance/department/science/central + name = "Central Science Maintenance" + icon_state = "maint_sci_central" + +/area/maintenance/department/cargo + name = "Cargo Maintenance" + icon_state = "maint_cargo" + +/area/maintenance/department/bridge + name = "Bridge Maintenance" + icon_state = "maint_bridge" + +/area/maintenance/department/engine + name = "Engineering Maintenance" + icon_state = "maint_engi" + +/area/maintenance/department/science/xenobiology + name = "Xenobiology Maintenance" + icon_state = "xenomaint" + xenobiology_compatible = TRUE + + +//Maintenance - Generic + +/area/maintenance/aft + name = "Aft Maintenance" + icon_state = "amaint" + +/area/maintenance/aft/upper + name = "Upper Aft Maintenance" + +/area/maintenance/aft/secondary + name = "Aft Maintenance" + icon_state = "amaint_2" + +/area/maintenance/central + name = "Central Maintenance" + icon_state = "maintcentral" + +/area/maintenance/central/secondary + name = "Central Maintenance" + icon_state = "maintcentral" + +/area/maintenance/fore + name = "Fore Maintenance" + icon_state = "fmaint" + +/area/maintenance/fore/upper + name = "Upper Fore Maintenance" + +/area/maintenance/fore/secondary + name = "Fore Maintenance" + icon_state = "fmaint_2" + +/area/maintenance/starboard + name = "Starboard Maintenance" + icon_state = "smaint" + +/area/maintenance/starboard/upper + name = "Upper Starboard Maintenance" + +/area/maintenance/starboard/central + name = "Central Starboard Maintenance" + icon_state = "smaint" + +/area/maintenance/starboard/secondary + name = "Secondary Starboard Maintenance" + icon_state = "smaint_2" + +/area/maintenance/starboard/aft + name = "Starboard Quarter Maintenance" + icon_state = "asmaint" + +/area/maintenance/starboard/aft/secondary + name = "Secondary Starboard Quarter Maintenance" + icon_state = "asmaint_2" + +/area/maintenance/starboard/fore + name = "Starboard Bow Maintenance" + icon_state = "fsmaint" + +/area/maintenance/port + name = "Port Maintenance" + icon_state = "pmaint" + +/area/maintenance/port/central + name = "Central Port Maintenance" + icon_state = "maintcentral" + +/area/maintenance/port/aft + name = "Port Quarter Maintenance" + icon_state = "apmaint" + +/area/maintenance/port/fore + name = "Port Bow Maintenance" + icon_state = "fpmaint" + +/area/maintenance/disposal + name = "Waste Disposal" + icon_state = "disposal" + +/area/maintenance/disposal/incinerator + name = "Incinerator" + icon_state = "disposal" + + +//Hallway + +/area/hallway/primary/aft + name = "Aft Primary Hallway" + icon_state = "hallA" + +/area/hallway/primary/fore + name = "Fore Primary Hallway" + icon_state = "hallF" + +/area/hallway/primary/starboard + name = "Starboard Primary Hallway" + icon_state = "hallS" + +/area/hallway/primary/port + name = "Port Primary Hallway" + icon_state = "hallP" + +/area/hallway/primary/central + name = "Central Primary Hallway" + icon_state = "hallC" + +/area/hallway/primary/upper + name = "Upper Central Primary Hallway" + icon_state = "hallC" + + +/area/hallway/secondary/command + name = "Command Hallway" + icon_state = "bridge_hallway" + +/area/hallway/secondary/construction + name = "Construction Area" + icon_state = "construction" + +/area/hallway/secondary/exit + name = "Escape Shuttle Hallway" + icon_state = "escape" + +/area/hallway/secondary/exit/departure_lounge + name = "Departure Lounge" + icon_state = "escape_lounge" + +/area/hallway/secondary/entry + name = "Arrival Shuttle Hallway" + icon_state = "entry" + +/area/hallway/secondary/service + name = "Service Hallway" + icon_state = "hall_service" + +//Command + +/area/bridge + name = "Bridge" + icon_state = "bridge" + ambientsounds = list('sound/ambience/signal.ogg') + +/area/lieutenant + name = "Lieutenant's Office" + icon_state = "blueold" + +/area/bridge/meeting_room + name = "Heads of Staff Meeting Room" + icon_state = "meeting" + +/area/bridge/meeting_room/council + name = "Council Chamber" + icon_state = "meeting" + +/area/bridge/showroom/corporate + name = "Corporate Showroom" + icon_state = "showroom" + +/area/crew_quarters/heads/captain + name = "Captain's Office" + icon_state = "captain" + +/area/crew_quarters/heads/captain/private + name = "Captain's Quarters" + icon_state = "captain" + +/area/crew_quarters/heads/chief + name = "Chief Engineer's Office" + icon_state = "ce_office" + +/area/crew_quarters/heads/cmo + name = "Chief Medical Officer's Office" + icon_state = "cmo_office" + +/area/crew_quarters/heads/head_of_personnel + name = "Head of Personnel's Office" + icon_state = "hop_office" + +/area/crew_quarters/heads/hos + name = "Head of Security's Office" + icon_state = "hos_office" + +/area/crew_quarters/heads/hor + name = "Research Director's Office" + icon_state = "rd_office" + +/area/comms + name = "Communications Relay" + icon_state = "tcomsatcham" + +/area/server + name = "Messaging Server Room" + icon_state = "server" + +//Crew + +/area/crew_quarters/dorms + name = "Dormitories" + icon_state = "Sleep" + safe = TRUE + +/area/crew_quarters/dorms/barracks + name = "Sleep Barracks" + +/area/crew_quarters/dorms/barracks/male + name = "Male Sleep Barracks" + +/area/crew_quarters/dorms/barracks/female + name = "Female Sleep Barracks" + +/area/crew_quarters/toilet + name = "Dormitory Toilets" + icon_state = "toilet" + +/area/crew_quarters/toilet/auxiliary + name = "Auxiliary Restrooms" + icon_state = "toilet" + +/area/crew_quarters/toilet/locker + name = "Locker Toilets" + icon_state = "toilet" + +/area/crew_quarters/toilet/restrooms + name = "Restrooms" + icon_state = "toilet" + +/area/crew_quarters/locker + name = "Locker Room" + icon_state = "locker" + +/area/crew_quarters/lounge + name = "Lounge" + icon_state = "yellow" + +/area/crew_quarters/fitness + name = "Fitness Room" + icon_state = "fitness" + +/area/crew_quarters/fitness/locker_room + name = "Unisex Locker Room" + icon_state = "fitness" + +/area/crew_quarters/fitness/locker_room/male + name = "Male Locker Room" + +/area/crew_quarters/fitness/locker_room/female + name = "Female Locker Room" + + +/area/crew_quarters/fitness/recreation + name = "Recreation Area" + icon_state = "fitness" + +/area/crew_quarters/cafeteria + name = "Cafeteria" + icon_state = "cafeteria" + +/area/crew_quarters/kitchen + name = "Kitchen" + icon_state = "kitchen" + +/area/crew_quarters/kitchen/coldroom + name = "Kitchen Cold Room" + icon_state = "kitchen_cold" + +/area/crew_quarters/bar + name = "Bar" + icon_state = "bar" + mood_bonus = 5 + mood_message = "I love being in the bar!\n" + +/area/crew_quarters/bar/atrium + name = "Atrium" + icon_state = "bar" + +/area/crew_quarters/electronic_marketing_den + name = "Electronic Marketing Den" + icon_state = "bar" + +/area/crew_quarters/abandoned_gambling_den + name = "Abandoned Gambling Den" + icon_state = "abandoned_g_den" + +/area/crew_quarters/abandoned_gambling_den/secondary + icon_state = "abandoned_g_den_2" + +/area/crew_quarters/theatre + name = "Theatre" + icon_state = "Theatre" + +/area/crew_quarters/theatre/abandoned + name = "Abandoned Theatre" + icon_state = "Theatre" + +/area/library + name = "Library" + icon_state = "library" + flags_1 = CULT_PERMITTED_1 + +/area/library/lounge + name = "Library Lounge" + icon_state = "library" + +/area/library/artgallery + name = " Art Gallery" + icon_state = "library" + +/area/library/private + name = "Library Private Study" + icon_state = "library" + +/area/library/upper + name = "Library Upper Floor" + icon_state = "library" + +/area/library/printer + name = "Library Printer Room" + icon_state = "library" + +/area/library/abandoned + name = "Abandoned Library" + icon_state = "library" + flags_1 = CULT_PERMITTED_1 + +/area/chapel + icon_state = "chapel" + ambientsounds = HOLY + flags_1 = NONE + +/area/chapel/main + name = "Chapel" + +/area/chapel/main/monastery + name = "Monastery" + +/area/chapel/office + name = "Chapel Office" + icon_state = "chapeloffice" + +/area/chapel/asteroid + name = "Chapel Asteroid" + icon_state = "explored" + +/area/chapel/asteroid/monastery + name = "Monastery Asteroid" + +/area/chapel/dock + name = "Chapel Dock" + icon_state = "construction" + +/area/lawoffice + name = "Law Office" + icon_state = "law" + + +//Engineering + +/area/engine + ambientsounds = ENGINEERING + +/area/engine/engine_smes + name = "Engineering SMES" + icon_state = "engine_smes" + +/area/engine/engineering + name = "Engineering" + icon_state = "engine" + +/area/engine/atmos + name = "Atmospherics" + icon_state = "atmos" + flags_1 = CULT_PERMITTED_1 + +/area/engine/atmos/upper + name = "Upper Atmospherics" + +/area/engine/atmospherics_engine + name = "Atmospherics Engine" + icon_state = "atmos_engine" + valid_territory = FALSE + +/area/engine/engine_room //donut station specific + name = "Engine Room" + icon_state = "atmos_engine" + +/area/engine/lobby + name = "Engineering Lobby" + icon_state = "engi_lobby" + +/area/engine/engine_room/external + name = "Supermatter External Access" + icon_state = "engine_foyer" + +/area/engine/supermatter + name = "Supermatter Engine" + icon_state = "engine_sm" + valid_territory = FALSE + +/area/engine/break_room + name = "Engineering Foyer" + icon_state = "engine_foyer" + +/area/engine/gravity_generator + name = "Gravity Generator Room" + icon_state = "grav_gen" + +/area/engine/storage + name = "Engineering Storage" + icon_state = "engi_storage" + +/area/engine/storage_shared + name = "Shared Engineering Storage" + icon_state = "engi_storage" + +/area/engine/transit_tube + name = "Transit Tube" + icon_state = "transit_tube" + + +//Solars + +/area/solar + requires_power = FALSE + dynamic_lighting = DYNAMIC_LIGHTING_IFSTARLIGHT + valid_territory = FALSE + blob_allowed = FALSE + flags_1 = NONE + ambientsounds = ENGINEERING + +/area/solar/fore + name = "Fore Solar Array" + icon_state = "yellow" + +/area/solar/aft + name = "Aft Solar Array" + icon_state = "yellow" + +/area/solar/aux/port + name = "Port Bow Auxiliary Solar Array" + icon_state = "panelsA" + +/area/solar/aux/starboard + name = "Starboard Bow Auxiliary Solar Array" + icon_state = "panelsA" + +/area/solar/starboard + name = "Starboard Solar Array" + icon_state = "panelsS" + +/area/solar/starboard/aft + name = "Starboard Quarter Solar Array" + icon_state = "panelsAS" + +/area/solar/starboard/fore + name = "Starboard Bow Solar Array" + icon_state = "panelsFS" + +/area/solar/port + name = "Port Solar Array" + icon_state = "panelsP" + +/area/solar/port/aft + name = "Port Quarter Solar Array" + icon_state = "panelsAP" + +/area/solar/port/fore + name = "Port Bow Solar Array" + icon_state = "panelsFP" + +/area/solar/aisat + name = "AI Satellite Solars" + icon_state = "yellow" + +//Solar Maint + +/area/maintenance/solars + name = "Solar Maintenance" + icon_state = "yellow" + +/area/maintenance/solars/port + name = "Port Solar Maintenance" + icon_state = "SolarcontrolP" + +/area/maintenance/solars/port/aft + name = "Port Quarter Solar Maintenance" + icon_state = "SolarcontrolAP" + +/area/maintenance/solars/port/fore + name = "Port Bow Solar Maintenance" + icon_state = "SolarcontrolFP" + +/area/maintenance/solars/starboard + name = "Starboard Solar Maintenance" + icon_state = "SolarcontrolS" + +/area/maintenance/solars/starboard/aft + name = "Starboard Quarter Solar Maintenance" + icon_state = "SolarcontrolAS" + +/area/maintenance/solars/starboard/fore + name = "Starboard Bow Solar Maintenance" + icon_state = "SolarcontrolFS" + +//Teleporter + +/area/teleporter + name = "Teleporter Room" + icon_state = "teleporter" + ambientsounds = ENGINEERING + +/area/gateway + name = "Gateway" + icon_state = "gateway" + ambientsounds = ENGINEERING + +//MedBay + +/area/medical + name = "Medical" + icon_state = "medbay3" + ambientsounds = MEDICAL + +/area/medical/abandoned + name = "Abandoned Medbay" + icon_state = "medbay3" + ambientsounds = list('sound/ambience/signal.ogg') + +/area/medical/medbay/central + name = "Medbay Central" + icon_state = "medbay" + +/area/medical/medbay/lobby + name = "Medbay Lobby" + icon_state = "medbay" + + //Medbay is a large area, these additional areas help level out APC load. + +/area/medical/medbay/zone2 + name = "Medbay" + icon_state = "medbay2" + +/area/medical/medbay/aft + name = "Medbay Aft" + icon_state = "medbay3" + +/area/medical/storage + name = "Medbay Storage" + icon_state = "medbay2" + +/area/medical/paramedic + name = "Paramedic Dispatch" + icon_state = "medbay2" + +/area/medical/office + name = "Medical Office" + icon_state = "medoffice" + +/area/medical/surgery/room_c + name = "Surgery C" + icon_state = "surgery" + +/area/medical/surgery/room_d + name = "Surgery D" + icon_state = "surgery" + +/area/medical/break_room + name = "Medical Break Room" + icon_state = "medbay2" + +/area/medical/coldroom + name = "Medical Cold Room" + icon_state = "kitchen_cold" + +/area/medical/patients_rooms + name = "Patients' Rooms" + icon_state = "patients" + +/area/medical/patients_rooms/room_a + name = "Patient Room A" + icon_state = "patients" + +/area/medical/patients_rooms/room_b + name = "Patient Room B" + icon_state = "patients" + +/area/medical/virology + name = "Virology" + icon_state = "virology" + flags_1 = CULT_PERMITTED_1 + +/area/medical/morgue + name = "Morgue" + icon_state = "morgue" + ambientsounds = SPOOKY + +/area/medical/chemistry + name = "Chemistry" + icon_state = "chem" + +/area/medical/pharmacy + name = "Pharmacy" + icon_state = "pharmacy" + +/area/medical/surgery + name = "Surgery" + icon_state = "surgery" + +/area/medical/surgery/room_b + name = "Surgery B" + icon_state = "surgery" + +/area/medical/cryo + name = "Cryogenics" + icon_state = "cryo" + +/area/medical/exam_room + name = "Exam Room" + icon_state = "exam_room" + +/area/medical/genetics + name = "Genetics Lab" + icon_state = "genetics" + +/area/medical/sleeper + name = "Medbay Treatment Center" + icon_state = "exam_room" + +/area/medical/psychology + name = "Psychology Office" + icon_state = "psychology" + mood_bonus = 3 + mood_message = "I feel at ease here.\n" + ambientsounds = list('sound/ambience/aurora_caelus_short.ogg') + +//Security + +/area/security + name = "Security" + icon_state = "security" + ambientsounds = HIGHSEC + +/area/security/main + name = "Security Office" + icon_state = "security" + +/area/security/brig + name = "Brig" + icon_state = "brig" + +/area/security/brig/upper + name = "Brig Overlook" + +/area/security/courtroom + name = "Courtroom" + icon_state = "courtroom" + +/area/security/prison + name = "Prison Wing" + icon_state = "sec_prison" + +/area/security/prison/toilet //radproof + name = "Prison Toilet" + icon_state = "sec_prison_safe" + +/area/security/prison/safe //radproof + name = "Prison Wing Cells" + icon_state = "sec_prison_safe" + +/area/security/prison/upper + name = "Upper Prison Wing" + icon_state = "prison_upper" + +/area/security/prison/visit + name = "Prison Visitation Area" + icon_state = "prison_visit" + +/area/security/prison/rec + name = "Prison Rec Room" + icon_state = "prison_rec" + +/area/security/prison/mess + name = "Prison Mess Hall" + icon_state = "prison_mess" + +/area/security/prison/work + name = "Prison Work Room" + icon_state = "prison_work" + +/area/security/prison/shower + name = "Prison Shower" + icon_state = "prison_shower" + +/area/security/prison/workout + name = "Prison Gym" + icon_state = "prison_workout" + +/area/security/prison/garden + name = "Prison Garden" + icon_state = "prison_garden" + +/area/security/processing + name = "Labor Shuttle Dock" + icon_state = "sec_prison" + +/area/security/processing/cremation + name = "Security Crematorium" + icon_state = "sec_prison" + +/area/security/warden + name = "Brig Control" + icon_state = "Warden" + +/area/security/detectives_office + name = "Detective's Office" + icon_state = "detective" + ambientsounds = list('sound/ambience/ambidet1.ogg','sound/ambience/ambidet2.ogg') + +/area/security/detectives_office/private_investigators_office + name = "Private Investigator's Office" + icon_state = "detective" + +/area/security/range + name = "Firing Range" + icon_state = "firingrange" + +/area/security/execution + icon_state = "execution_room" + +/area/security/execution/transfer + name = "Transfer Centre" + +/area/security/execution/education + name = "Prisoner Education Chamber" + +/area/security/nuke_storage + name = "Vault" + icon_state = "nuke_storage" + +/area/ai_monitored/nuke_storage + name = "Vault" + icon_state = "nuke_storage" + +/area/security/checkpoint + name = "Security Checkpoint" + icon_state = "checkpoint1" + +/area/security/checkpoint/auxiliary + icon_state = "checkpoint_aux" + +/area/security/checkpoint/escape + icon_state = "checkpoint_esc" + +/area/security/checkpoint/supply + name = "Security Post - Cargo Bay" + icon_state = "checkpoint_supp" + +/area/security/checkpoint/engineering + name = "Security Post - Engineering" + icon_state = "checkpoint_engi" + +/area/security/checkpoint/medical + name = "Security Post - Medbay" + icon_state = "checkpoint_med" + +/area/security/checkpoint/science + name = "Security Post - Science" + icon_state = "checkpoint_sci" + +/area/security/checkpoint/science/research + name = "Security Post - Research Division" + icon_state = "checkpoint_res" + +/area/security/checkpoint/customs + name = "Customs" + icon_state = "customs_point" + +/area/security/checkpoint/customs/auxiliary + icon_state = "customs_point_aux" + + +//Service + +/area/quartermaster + name = "Quartermasters" + icon_state = "quart" + +/area/quartermaster/sorting + name = "Delivery Office" + icon_state = "cargo_delivery" + +/area/quartermaster/warehouse + name = "Warehouse" + icon_state = "cargo_warehouse" + +/area/quartermaster/warehouse/upper + name = "Upper Warehouse" + +/area/quartermaster/office + name = "Cargo Office" + icon_state = "quartoffice" + +/area/quartermaster/storage + name = "Cargo Bay" + icon_state = "cargo_bay" + +/area/quartermaster/qm + name = "Quartermaster's Office" + icon_state = "quart" + +/area/quartermaster/qm/perch + name = "Quartermaster's Perch" + icon_state = "quartperch" + +/area/quartermaster/miningdock + name = "Mining Dock" + icon_state = "mining" + +/area/quartermaster/miningoffice + name = "Mining Office" + icon_state = "mining" + +/area/janitor + name = "Custodial Closet" + icon_state = "janitor" + flags_1 = CULT_PERMITTED_1 + +/area/hydroponics + name = "Hydroponics" + icon_state = "hydro" + +/area/hydroponics/upper + name = "Upper Hydroponics" + icon_state = "hydro" + +/area/hydroponics/garden + name = "Garden" + icon_state = "garden" + +/area/hydroponics/garden/abandoned + name = "Abandoned Garden" + icon_state = "abandoned_garden" + +/area/hydroponics/garden/monastery + name = "Monastery Garden" + icon_state = "hydro" + + +//Science + +/area/science + name = "Science Division" + icon_state = "toxlab" + +/area/science/lab + name = "Research and Development" + icon_state = "toxlab" + +/area/science/xenobiology + name = "Xenobiology Lab" + icon_state = "toxlab" + +/area/science/storage + name = "Toxins Storage" + icon_state = "toxstorage" + +/area/science/test_area + valid_territory = FALSE + name = "Toxins Test Area" + icon_state = "toxtest" + +/area/science/mixing + name = "Toxins Mixing Lab" + icon_state = "toxmix" + +/area/science/mixing/chamber + name = "Toxins Mixing Chamber" + icon_state = "toxmix" + valid_territory = FALSE + +/area/science/misc_lab + name = "Testing Lab" + icon_state = "toxmisc" + +/area/science/misc_lab/range + name = "Research Testing Range" + icon_state = "toxmisc" + +/area/science/server + name = "Research Division Server Room" + icon_state = "server" + +/area/science/explab + name = "Experimentation Lab" + icon_state = "toxmisc" + +/area/science/robotics + name = "Robotics" + icon_state = "medresearch" + +/area/science/robotics/mechbay + name = "Mech Bay" + icon_state = "mechbay" + +/area/science/robotics/lab + name = "Robotics Lab" + icon_state = "ass_line" + +/area/science/research + name = "Research Division" + icon_state = "medresearch" + +/area/science/research/abandoned + name = "Abandoned Research Lab" + icon_state = "medresearch" + +/area/science/nanite + name = "Nanite Lab" + icon_state = "toxmisc" + +//Storage + +/area/storage/tools + name = "Auxiliary Tool Storage" + icon_state = "storage" + +/area/storage/primary + name = "Primary Tool Storage" + icon_state = "primarystorage" + +/area/storage/art + name = "Art Supply Storage" + icon_state = "storage" + +/area/storage/tcom + name = "Telecomms Storage" + icon_state = "green" + valid_territory = FALSE + +/area/storage/eva + name = "EVA Storage" + icon_state = "eva" + +/area/storage/emergency/starboard + name = "Starboard Emergency Storage" + icon_state = "emergencystorage" + +/area/storage/emergency/port + name = "Port Emergency Storage" + icon_state = "emergencystorage" + +/area/storage/tech + name = "Technical Storage" + icon_state = "auxstorage" + +//Construction + +/area/construction + name = "Construction Area" + icon_state = "yellow" + ambientsounds = ENGINEERING + +/area/construction/mining/aux_base + name = "Auxiliary Base Construction" + icon_state = "aux_base_construction" + +/area/construction/storage_wing + name = "Storage Wing" + icon_state = "storage_wing" + +// Vacant Rooms +/area/vacant_room + name = "Vacant Room" + icon_state = "vacant_room" + ambientsounds = MAINTENANCE + +/area/vacant_room/office + name = "Vacant Office" + icon_state = "vacant_office" + +/area/vacant_room/commissary + name = "Vacant Commissary" + icon_state = "vacant_commissary" + +//AI + +/area/ai_monitored/security/armory + name = "Armory" + icon_state = "armory" + ambientsounds = HIGHSEC + +/area/ai_monitored/security/armory/upper + name = "Upper Armory" + +/area/ai_monitored/storage/eva + name = "EVA Storage" + icon_state = "eva" + ambientsounds = HIGHSEC + +/area/ai_monitored/storage/eva/upper + name = "Upper EVA Storage" + +/area/ai_monitored/storage/satellite + name = "AI Satellite Maint" + icon_state = "storage" + ambientsounds = HIGHSEC + + //Turret_protected + +/area/ai_monitored/turret_protected + ambientsounds = list('sound/ambience/ambimalf.ogg', 'sound/ambience/ambitech.ogg', 'sound/ambience/ambitech2.ogg', 'sound/ambience/ambiatmos.ogg', 'sound/ambience/ambiatmos2.ogg') + +/area/ai_monitored/turret_protected/ai_upload + name = "AI Upload Chamber" + icon_state = "ai_upload" + +/area/ai_monitored/turret_protected/ai_upload_foyer + name = "AI Upload Access" + icon_state = "ai_foyer" + +/area/ai_monitored/turret_protected/ai + name = "AI Chamber" + icon_state = "ai_chamber" + +/area/ai_monitored/turret_protected/aisat + name = "AI Satellite" + icon_state = "ai" + +/area/ai_monitored/turret_protected/aisat/atmos + name = "AI Satellite Atmos" + icon_state = "ai" + +/area/ai_monitored/turret_protected/aisat/foyer + name = "AI Satellite Foyer" + icon_state = "ai" + +/area/ai_monitored/turret_protected/aisat/service + name = "AI Satellite Service" + icon_state = "ai" + +/area/ai_monitored/turret_protected/aisat/hallway + name = "AI Satellite Hallway" + icon_state = "ai" + +/area/aisat + name = "AI Satellite Exterior" + icon_state = "yellow" + +/area/ai_monitored/turret_protected/aisat_interior + name = "AI Satellite Antechamber" + icon_state = "ai" + +/area/ai_monitored/turret_protected/AIsatextAS + name = "AI Sat Ext" + icon_state = "storage" + +/area/ai_monitored/turret_protected/AIsatextAP + name = "AI Sat Ext" + icon_state = "storage" + + +// Telecommunications Satellite + +/area/tcommsat + ambientsounds = list('sound/ambience/ambisin2.ogg', 'sound/ambience/signal.ogg', 'sound/ambience/signal.ogg', 'sound/ambience/ambigen10.ogg', 'sound/ambience/ambitech.ogg',\ + 'sound/ambience/ambitech2.ogg', 'sound/ambience/ambitech3.ogg', 'sound/ambience/ambimystery.ogg') + +/area/tcommsat/computer + name = "Telecomms Control Room" + icon_state = "tcomsatcomp" + +/area/tcommsat/server + name = "Telecomms Server Room" + icon_state = "tcomsatcham" + +/area/tcommsat/server/upper + name = "Upper Telecomms Server Room" + +//External Hull Access +/area/maintenance/external + name = "External Hull Access" + icon_state = "amaint" + +/area/maintenance/external/aft + name = "Aft External Hull Access" + +/area/maintenance/external/port + name = "Port External Hull Access" + +/area/maintenance/external/port/bow + name = "Port Bow External Hull Access" diff --git a/code/game/area/ai_monitored.dm b/code/game/area/ai_monitored.dm index d1250c91520e..65da1c6edf40 100644 --- a/code/game/area/ai_monitored.dm +++ b/code/game/area/ai_monitored.dm @@ -1,30 +1,30 @@ -/area/ai_monitored - name = "AI Monitored Area" - var/list/obj/machinery/camera/motioncameras = list() - var/list/datum/weakref/motionTargets = list() - -/area/ai_monitored/Initialize(mapload) - . = ..() - if(mapload) - for (var/obj/machinery/camera/M in src) - if(M.isMotion()) - motioncameras.Add(M) - M.area_motion = src - -//Only need to use one camera - -/area/ai_monitored/Entered(atom/movable/O) - ..() - if (ismob(O) && motioncameras.len) - for(var/X in motioncameras) - var/obj/machinery/camera/cam = X - cam.newTarget(O) - return - -/area/ai_monitored/Exited(atom/movable/O) - ..() - if (ismob(O) && motioncameras.len) - for(var/X in motioncameras) - var/obj/machinery/camera/cam = X - cam.lostTargetRef(WEAKREF(O)) - return +/area/ai_monitored + name = "AI Monitored Area" + var/list/obj/machinery/camera/motioncameras = list() + var/list/datum/weakref/motionTargets = list() + +/area/ai_monitored/Initialize(mapload) + . = ..() + if(mapload) + for (var/obj/machinery/camera/M in src) + if(M.isMotion()) + motioncameras.Add(M) + M.area_motion = src + +//Only need to use one camera + +/area/ai_monitored/Entered(atom/movable/O) + ..() + if (ismob(O) && motioncameras.len) + for(var/X in motioncameras) + var/obj/machinery/camera/cam = X + cam.newTarget(O) + return + +/area/ai_monitored/Exited(atom/movable/O) + ..() + if (ismob(O) && motioncameras.len) + for(var/X in motioncameras) + var/obj/machinery/camera/cam = X + cam.lostTargetRef(WEAKREF(O)) + return diff --git a/code/game/atoms.dm b/code/game/atoms.dm index ee39ee389641..07360199d54e 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -1,1383 +1,1383 @@ -/** - * The base type for nearly all physical objects in SS13 - - * Lots and lots of functionality lives here, although in general we are striving to move - * as much as possible to the components/elements system - */ -/atom - layer = TURF_LAYER - plane = GAME_PLANE - - ///If non-null, overrides a/an/some in all cases - var/article - - ///First atom flags var - var/flags_1 = NONE - ///Intearaction flags - var/interaction_flags_atom = NONE - - var/flags_ricochet = NONE - - ///When a projectile tries to ricochet off this atom, the projectile ricochet chance is multiplied by this - var/ricochet_chance_mod = 1 - ///When a projectile ricochets off this atom, it deals the normal damage * this modifier to this atom - var/ricochet_damage_mod = 0.33 - - ///Reagents holder - var/datum/reagents/reagents = null - - ///This atom's HUD (med/sec, etc) images. Associative list. - var/list/image/hud_list = null - ///HUD images that this atom can provide. - var/list/hud_possible - - ///Value used to increment ex_act() if reactionary_explosions is on - var/explosion_block = 0 - - /** - * used to store the different colors on an atom - * - * its inherent color, the colored paint applied on it, special color effect etc... - */ - var/list/atom_colours - - - ///overlays that should remain on top and not normally removed when using cut_overlay functions, like c4. - var/list/priority_overlays - /// a very temporary list of overlays to remove - var/list/remove_overlays - /// a very temporary list of overlays to add - var/list/add_overlays - - ///vis overlays managed by SSvis_overlays to automaticaly turn them like other overlays - var/list/managed_vis_overlays - ///overlays managed by [update_overlays][/atom/proc/update_overlays] to prevent removing overlays that weren't added by the same proc - var/list/managed_overlays - - ///Proximity monitor associated with this atom - var/datum/proximity_monitor/proximity_monitor - ///Cooldown tick timer for buckle messages - var/buckle_message_cooldown = 0 - ///Last fingerprints to touch this atom - var/fingerprintslast - - var/list/filter_data //For handling persistent filters - - ///Economy cost of item - var/custom_price - ///Economy cost of item in premium vendor - var/custom_premium_price - ///Whether spessmen with an ID with an age below AGE_MINOR (20 by default) can buy this item - var/age_restricted = FALSE - - //List of datums orbiting this atom - var/datum/component/orbiter/orbiters - - /// Radiation insulation types - var/rad_insulation = RAD_NO_INSULATION - - ///The custom materials this atom is made of, used by a lot of things like furniture, walls, and floors (if I finish the functionality, that is.) - var/list/custom_materials - ///Bitfield for how the atom handles materials. - var/material_flags = NONE - ///Modifier that raises/lowers the effect of the amount of a material, prevents small and easy to get items from being death machines. - var/material_modifier = 1 - - var/datum/wires/wires = null - - var/list/alternate_appearances - - ///Mobs that are currently do_after'ing this atom, to be cleared from on Destroy() - var/list/targeted_by - - /// Last appearance of the atom for demo saving purposes - var/image/demo_last_appearance - - /// Last name used to calculate a color for the chatmessage overlays - var/chat_color_name - /// Last color calculated for the the chatmessage overlays - var/chat_color - /// A luminescence-shifted value of the last color calculated for chatmessage overlays - var/chat_color_darkened - -/** - * Called when an atom is created in byond (built in engine proc) - * - * Not a lot happens here in SS13 code, as we offload most of the work to the - * [Intialization][/atom/proc/Initialize] proc, mostly we run the preloader - * if the preloader is being used and then call [InitAtom][/datum/controller/subsystem/atoms/proc/InitAtom] of which the ultimate - * result is that the Intialize proc is called. - * - * We also generate a tag here if the DF_USE_TAG flag is set on the atom - */ -/atom/New(loc, ...) - //atom creation method that preloads variables at creation - if(GLOB.use_preloader && (src.type == GLOB._preloader.target_path))//in case the instanciated atom is creating other atoms in New() - world.preloader_load(src) - - if(datum_flags & DF_USE_TAG) - GenerateTag() - - var/do_initialize = SSatoms.initialized - if(do_initialize != INITIALIZATION_INSSATOMS) - args[1] = do_initialize == INITIALIZATION_INNEW_MAPLOAD - if(SSatoms.InitAtom(src, args)) - //we were deleted - return - -/** - * The primary method that objects are setup in SS13 with - * - * we don't use New as we have better control over when this is called and we can choose - * to delay calls or hook other logic in and so forth - * - * During roundstart map parsing, atoms are queued for intialization in the base atom/New(), - * After the map has loaded, then Initalize is called on all atoms one by one. NB: this - * is also true for loading map templates as well, so they don't Initalize until all objects - * in the map file are parsed and present in the world - * - * If you're creating an object at any point after SSInit has run then this proc will be - * immediately be called from New. - * - * mapload: This parameter is true if the atom being loaded is either being intialized during - * the Atom subsystem intialization, or if the atom is being loaded from the map template. - * If the item is being created at runtime any time after the Atom subsystem is intialized then - * it's false. - * - * You must always call the parent of this proc, otherwise failures will occur as the item - * will not be seen as initalized (this can lead to all sorts of strange behaviour, like - * the item being completely unclickable) - * - * You must not sleep in this proc, or any subprocs - * - * Any parameters from new are passed through (excluding loc), naturally if you're loading from a map - * there are no other arguments - * - * Must return an [initialization hint][INITIALIZE_HINT_NORMAL] or a runtime will occur. - * - * Note: the following functions don't call the base for optimization and must copypasta handling: - * * [/turf/Initialize] - * * [/turf/open/space/Initialize] - */ -/atom/proc/Initialize(mapload, ...) - SHOULD_NOT_SLEEP(TRUE) - SHOULD_CALL_PARENT(TRUE) - if(flags_1 & INITIALIZED_1) - stack_trace("Warning: [src]([type]) initialized multiple times!") - flags_1 |= INITIALIZED_1 - - if(loc) - SEND_SIGNAL(loc, COMSIG_ATOM_CREATED, src) /// Sends a signal that the new atom `src`, has been created at `loc` - - //atom color stuff - if(color) - add_atom_colour(color, FIXED_COLOUR_PRIORITY) - - if (light_power && light_range) - update_light() - - if (opacity && isturf(loc)) - var/turf/T = loc - T.has_opaque_atom = TRUE // No need to recalculate it in this case, it's guaranteed to be on afterwards anyways. - - if (canSmoothWith) - canSmoothWith = typelist("canSmoothWith", canSmoothWith) - - var/temp_list = list() - for(var/i in custom_materials) - temp_list[SSmaterials.GetMaterialRef(i)] = custom_materials[i] //Get the proper instanced version - - custom_materials = null //Null the list to prepare for applying the materials properly - set_custom_materials(temp_list) - - ComponentInitialize() - - return INITIALIZE_HINT_NORMAL - -/** - * Late Intialization, for code that should run after all atoms have run Intialization - * - * To have your LateIntialize proc be called, your atoms [Initalization][/atom/proc/Initialize] - * proc must return the hint - * [INITIALIZE_HINT_LATELOAD] otherwise you will never be called. - * - * useful for doing things like finding other machines on GLOB.machines because you can guarantee - * that all atoms will actually exist in the "WORLD" at this time and that all their Intialization - * code has been run - */ -/atom/proc/LateInitialize() - set waitfor = FALSE - -/// Put your [AddComponent] calls here -/atom/proc/ComponentInitialize() - return - -/** - * Top level of the destroy chain for most atoms - * - * Cleans up the following: - * * Removes alternate apperances from huds that see them - * * qdels the reagent holder from atoms if it exists - * * clears the orbiters list - * * clears overlays and priority overlays - * * clears the light object - */ -/atom/Destroy() - if(alternate_appearances) - for(var/K in alternate_appearances) - var/datum/atom_hud/alternate_appearance/AA = alternate_appearances[K] - AA.remove_from_hud(src) - - if(reagents) - qdel(reagents) - - orbiters = null // The component is attached to us normaly and will be deleted elsewhere - - LAZYCLEARLIST(overlays) - LAZYCLEARLIST(priority_overlays) - - for(var/i in targeted_by) - var/mob/M = i - LAZYREMOVE(M.do_afters, src) - - targeted_by = null - QDEL_NULL(light) - - return ..() - -/atom/proc/handle_ricochet(obj/projectile/P) - var/turf/p_turf = get_turf(P) - var/face_direction = get_dir(src, p_turf) - var/face_angle = dir2angle(face_direction) - var/incidence_s = GET_ANGLE_OF_INCIDENCE(face_angle, (P.Angle + 180)) - var/a_incidence_s = abs(incidence_s) - if(a_incidence_s > 90 && a_incidence_s < 270) - return FALSE - if((P.flag in list("bullet", "bomb")) && P.ricochet_incidence_leeway) - if((a_incidence_s < 90 && a_incidence_s < 90 - P.ricochet_incidence_leeway) || (a_incidence_s > 270 && a_incidence_s -270 > P.ricochet_incidence_leeway)) - return - var/new_angle_s = SIMPLIFY_DEGREES(face_angle + incidence_s) - P.setAngle(new_angle_s) - return TRUE - -///Can the mover object pass this atom, while heading for the target turf -/atom/proc/CanPass(atom/movable/mover, turf/target) - SHOULD_CALL_PARENT(TRUE) - SHOULD_BE_PURE(TRUE) - if(mover.movement_type & UNSTOPPABLE) - return TRUE - . = CanAllowThrough(mover, target) - // This is cheaper than calling the proc every time since most things dont override CanPassThrough - if(!mover.generic_canpass) - return mover.CanPassThrough(src, target, .) - -/// Returns true or false to allow the mover to move through src -/atom/proc/CanAllowThrough(atom/movable/mover, turf/target) - SHOULD_CALL_PARENT(TRUE) - SHOULD_BE_PURE(TRUE) - return !density - -/** - * Is this atom currently located on centcom - * - * Specifically, is it on the z level and within the centcom areas - * - * You can also be in a shuttleshuttle during endgame transit - * - * Used in gamemode to identify mobs who have escaped and for some other areas of the code - * who don't want atoms where they shouldn't be - */ -/atom/proc/onCentCom() - var/turf/T = get_turf(src) - if(!T) - return FALSE - - if(is_reserved_level(T.z)) - for(var/A in SSshuttle.mobile) - var/obj/docking_port/mobile/M = A - if(M.launch_status == ENDGAME_TRANSIT) - for(var/place in M.shuttle_areas) - var/area/shuttle/shuttle_area = place - if(T in shuttle_area) - return TRUE - - if(!is_centcom_level(T.z))//if not, don't bother - return FALSE - - //Check for centcom itself - if(istype(T.loc, /area/centcom)) - return TRUE - - //Check for centcom shuttles - for(var/A in SSshuttle.mobile) - var/obj/docking_port/mobile/M = A - if(M.launch_status == ENDGAME_LAUNCHED) - for(var/place in M.shuttle_areas) - var/area/shuttle/shuttle_area = place - if(T in shuttle_area) - return TRUE - -/** - * Is the atom in any of the centcom syndicate areas - * - * Either in the syndie base on centcom, or any of their shuttles - * - * Also used in gamemode code for win conditions - */ -/atom/proc/onSyndieBase() - var/turf/T = get_turf(src) - if(!T) - return FALSE - - if(!is_centcom_level(T.z))//if not, don't bother - return FALSE - - if(istype(T.loc, /area/shuttle/syndicate) || istype(T.loc, /area/syndicate_mothership) || istype(T.loc, /area/shuttle/assault_pod)) - return TRUE - - return FALSE - -/** - * Is the atom in an away mission - * - * Must be in the away mission z-level to return TRUE - * - * Also used in gamemode code for win conditions - */ -/atom/proc/onAwayMission() - var/turf/T = get_turf(src) - if(!T) - return FALSE - - if(is_away_level(T.z)) - return TRUE - - return FALSE - - - -///This atom has been hit by a hulkified mob in hulk mode (user) -/atom/proc/attack_hulk(mob/living/carbon/human/user) - SEND_SIGNAL(src, COMSIG_ATOM_HULK_ATTACK, user) - -/** - * Ensure a list of atoms/reagents exists inside this atom - * - * Goes throught he list of passed in parts, if they're reagents, adds them to our reagent holder - * creating the reagent holder if it exists. - * - * If the part is a moveable atom and the previous location of the item was a mob/living, - * it calls the inventory handler transferItemToLoc for that mob/living and transfers the part - * to this atom - * - * Otherwise it simply forceMoves the atom into this atom - */ -/atom/proc/CheckParts(list/parts_list) - for(var/A in parts_list) - if(istype(A, /datum/reagent)) - if(!reagents) - reagents = new() - reagents.reagent_list.Add(A) - reagents.conditional_update() - else if(ismovable(A)) - var/atom/movable/M = A - if(isliving(M.loc)) - var/mob/living/L = M.loc - L.transferItemToLoc(M, src) - else - M.forceMove(src) - -///Hook for multiz??? -/atom/proc/update_multiz(prune_on_fail = FALSE) - return FALSE - -///Take air from the passed in gas mixture datum -/atom/proc/assume_air(datum/gas_mixture/giver) - qdel(giver) - return null - -///Remove air from this atom -/atom/proc/remove_air(amount) - return null - -///Return the current air environment in this atom -/atom/proc/return_air() - if(loc) - return loc.return_air() - else - return null - -///Return the air if we can analyze it -/atom/proc/return_analyzable_air() - return null - -///Check if this atoms eye is still alive (probably) -/atom/proc/check_eye(mob/user) - return - -/atom/proc/Bumped(atom/movable/AM) - set waitfor = FALSE - SEND_SIGNAL(src, COMSIG_ATOM_BUMPED, AM) - -/// Convenience proc to see if a container is open for chemistry handling -/atom/proc/is_open_container() - return is_refillable() && is_drainable() - -/// Is this atom injectable into other atoms -/atom/proc/is_injectable(mob/user, allowmobs = TRUE) - return reagents && (reagents.flags & (INJECTABLE | REFILLABLE)) - -/// Can we draw from this atom with an injectable atom -/atom/proc/is_drawable(mob/user, allowmobs = TRUE) - return reagents && (reagents.flags & (DRAWABLE | DRAINABLE)) - -/// Can this atoms reagents be refilled -/atom/proc/is_refillable() - return reagents && (reagents.flags & REFILLABLE) - -/// Is this atom drainable of reagents -/atom/proc/is_drainable() - return reagents && (reagents.flags & DRAINABLE) - -/** Handles exposing this atom to a list of reagents. - * - * Sends COMSIG_ATOM_EXPOSE_REAGENTS - * Calls expose_atom() for every reagent in the reagent list. - * - * Arguments: - * - [reagents][/list]: The list of reagents the atom is being exposed to. - * - [source][/datum/reagents]: The reagent holder the reagents are being sourced from. - * - method: How the atom is being exposed to the reagents. - * - volume_modifier: Volume multiplier. - * - show_message: Whether to display anything to mobs when they are exposed. - */ -/atom/proc/expose_reagents(list/reagents, datum/reagents/source, method=TOUCH, volume_modifier=1, show_message=TRUE) - if((. = SEND_SIGNAL(src, COMSIG_ATOM_EXPOSE_REAGENTS, reagents, source, method, volume_modifier, show_message)) & COMPONENT_NO_EXPOSE_REAGENTS) - return - - for(var/reagent in reagents) - var/datum/reagent/R = reagent - . |= R.expose_atom(src, reagents[R]) - -/// Are you allowed to drop this atom -/atom/proc/AllowDrop() - return FALSE - -/atom/proc/CheckExit() - return 1 - -///Is this atom within 1 tile of another atom -/atom/proc/HasProximity(atom/movable/AM as mob|obj) - return - -/** - * React to an EMP of the given severity - * - * Default behaviour is to send the [COMSIG_ATOM_EMP_ACT] signal - * - * If the signal does not return protection, and there are attached wires then we call - * [emp_pulse][/datum/wires/proc/emp_pulse] on the wires - * - * We then return the protection value - */ -/atom/proc/emp_act(severity) - var/protection = SEND_SIGNAL(src, COMSIG_ATOM_EMP_ACT, severity) - if(!(protection & EMP_PROTECT_WIRES) && istype(wires)) - wires.emp_pulse() - return protection // Pass the protection value collected here upwards - -/** - * React to a hit by a projectile object - * - * Default behaviour is to send the [COMSIG_ATOM_BULLET_ACT] and then call [on_hit][/obj/projectile/proc/on_hit] on the projectile - */ -/atom/proc/bullet_act(obj/projectile/P, def_zone) - SEND_SIGNAL(src, COMSIG_ATOM_BULLET_ACT, P, def_zone) - . = P.on_hit(src, 0, def_zone) - -///Return true if we're inside the passed in atom -/atom/proc/in_contents_of(container)//can take class or object instance as argument - if(ispath(container)) - if(istype(src.loc, container)) - return TRUE - else if(src in container) - return TRUE - return FALSE - -/** - * Get the name of this object for examine - * - * You can override what is returned from this proc by registering to listen for the - * [COMSIG_ATOM_GET_EXAMINE_NAME] signal - */ -/atom/proc/get_examine_name(mob/user) - . = "\a [src]" - var/list/override = list(gender == PLURAL ? "some" : "a", " ", "[name]") - if(article) - . = "[article] [src]" - override[EXAMINE_POSITION_ARTICLE] = article - if(SEND_SIGNAL(src, COMSIG_ATOM_GET_EXAMINE_NAME, user, override) & COMPONENT_EXNAME_CHANGED) - . = override.Join("") - -///Generate the full examine string of this atom (including icon for goonchat) -/atom/proc/get_examine_string(mob/user, thats = FALSE) - return "[icon2html(src, user)] [thats? "That's ":""][get_examine_name(user)]" - -/** - * Called when a mob examines (shift click or verb) this atom - * - * Default behaviour is to get the name and icon of the object and it's reagents where - * the [TRANSPARENT] flag is set on the reagents holder - * - * Produces a signal [COMSIG_PARENT_EXAMINE] - */ -/atom/proc/examine(mob/user) - . = list("[get_examine_string(user, TRUE)].") - - if(desc) - . += desc - - if(custom_materials) - var/list/materials_list = list() - for(var/i in custom_materials) - var/datum/material/M = i - materials_list += "[M.name]" - . += "It is made out of [english_list(materials_list)]." - if(reagents) - if(reagents.flags & TRANSPARENT) - . += "It contains:" - if(length(reagents.reagent_list)) - if(user.can_see_reagents()) //Show each individual reagent - for(var/datum/reagent/R in reagents.reagent_list) - . += "[R.volume] units of [R.name]" - else //Otherwise, just show the total volume - var/total_volume = 0 - for(var/datum/reagent/R in reagents.reagent_list) - total_volume += R.volume - . += "[total_volume] units of various reagents" - else - . += "Nothing." - else if(reagents.flags & AMOUNT_VISIBLE) - if(reagents.total_volume) - . += "It has [reagents.total_volume] unit\s left." - else - . += "It's empty." - - SEND_SIGNAL(src, COMSIG_PARENT_EXAMINE, user, .) - -/// Updates the icon of the atom -/atom/proc/update_icon() - var/signalOut = SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_ICON) - . = FALSE - - if(!(signalOut & COMSIG_ATOM_NO_UPDATE_ICON_STATE)) - update_icon_state() - . = TRUE - - if(!(signalOut & COMSIG_ATOM_NO_UPDATE_OVERLAYS)) - var/list/new_overlays = update_overlays() - if(managed_overlays) - cut_overlay(managed_overlays) - managed_overlays = null - if(length(new_overlays)) - managed_overlays = new_overlays - add_overlay(new_overlays) - . = TRUE - - SEND_SIGNAL(src, COMSIG_ATOM_UPDATED_ICON, signalOut, .) - -/// Updates the icon state of the atom -/atom/proc/update_icon_state() - -/// Updates the overlays of the atom -/atom/proc/update_overlays() - SHOULD_CALL_PARENT(1) - . = list() - SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_OVERLAYS, .) - -/** - * An atom we are buckled or is contained within us has tried to move - * - * Default behaviour is to send a warning that the user can't move while buckled as long - * as the [buckle_message_cooldown][/atom/var/buckle_message_cooldown] has expired (50 ticks) - */ -/atom/proc/relaymove(mob/user) - if(buckle_message_cooldown <= world.time) - buckle_message_cooldown = world.time + 50 - to_chat(user, "You can't move while buckled to [src]!") - return - -/// Handle what happens when your contents are exploded by a bomb -/atom/proc/contents_explosion(severity, target) - return //For handling the effects of explosions on contents that would not normally be effected - -/** - * React to being hit by an explosion - * - * Default behaviour is to call [contents_explosion][/atom/proc/contents_explosion] and send the [COMSIG_ATOM_EX_ACT] signal - */ -/atom/proc/ex_act(severity, target) - set waitfor = FALSE - contents_explosion(severity, target) - SEND_SIGNAL(src, COMSIG_ATOM_EX_ACT, severity, target) - -/** - * React to a hit by a blob objecd - * - * default behaviour is to send the [COMSIG_ATOM_BLOB_ACT] signal - */ -/atom/proc/blob_act(obj/structure/blob/B) - SEND_SIGNAL(src, COMSIG_ATOM_BLOB_ACT, B) - return - -/atom/proc/fire_act(exposed_temperature, exposed_volume) - SEND_SIGNAL(src, COMSIG_ATOM_FIRE_ACT, exposed_temperature, exposed_volume) - return - -/** - * React to being hit by a thrown object - * - * Default behaviour is to call [hitby_react][/atom/proc/hitby_react] on ourselves after 2 seconds if we are dense - * and under normal gravity. - * - * Im not sure why this the case, maybe to prevent lots of hitby's if the thrown object is - * deleted shortly after hitting something (during explosions or other massive events that - * throw lots of items around - singularity being a notable example) - */ -/atom/proc/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) - if(density && !has_gravity(AM)) //thrown stuff bounces off dense stuff in no grav, unless the thrown stuff ends up inside what it hit(embedding, bola, etc...). - addtimer(CALLBACK(src, .proc/hitby_react, AM), 2) - -/** - * We have have actually hit the passed in atom - * - * Default behaviour is to move back from the item that hit us - */ -/atom/proc/hitby_react(atom/movable/AM) - if(AM && isturf(AM.loc)) - step(AM, turn(AM.dir, 180)) - -///Handle the atom being slipped over -/atom/proc/handle_slip(mob/living/carbon/C, knockdown_amount, obj/O, lube, paralyze, force_drop) - return - -///returns the mob's dna info as a list, to be inserted in an object's blood_DNA list -/mob/living/proc/get_blood_dna_list() - if(get_blood_id() != /datum/reagent/blood) - return - return list("ANIMAL DNA" = "Y-") - -///Get the mobs dna list -/mob/living/carbon/get_blood_dna_list() - if(get_blood_id() != /datum/reagent/blood) - return - var/list/blood_dna = list() - if(dna) - blood_dna[dna.unique_enzymes] = dna.blood_type - else - blood_dna["UNKNOWN DNA"] = "X*" - return blood_dna - -/mob/living/carbon/alien/get_blood_dna_list() - return list("UNKNOWN DNA" = "X*") - -/mob/living/silicon/get_blood_dna_list() - return list("MOTOR OIL" = "SAE 5W-30") //just a little flavor text. - -///to add a mob's dna info into an object's blood_dna list. -/atom/proc/transfer_mob_blood_dna(mob/living/L) - // Returns 0 if we have that blood already - var/new_blood_dna = L.get_blood_dna_list() - if(!new_blood_dna) - return FALSE - var/old_length = blood_DNA_length() - add_blood_DNA(new_blood_dna) - if(blood_DNA_length() == old_length) - return FALSE - return TRUE - -///to add blood from a mob onto something, and transfer their dna info -/atom/proc/add_mob_blood(mob/living/M) - var/list/blood_dna = M.get_blood_dna_list() - if(!blood_dna) - return FALSE - return add_blood_DNA(blood_dna) - -///Is this atom in space -/atom/proc/isinspace() - if(isspaceturf(get_turf(src))) - return TRUE - else - return FALSE - -///Used for making a sound when a mob involuntarily falls into the ground. -/atom/proc/handle_fall(mob/faller) - return - -///Respond to the singularity eating this atom -/atom/proc/singularity_act() - return - -/** - * Respond to the singularity pulling on us - * - * Default behaviour is to send [COMSIG_ATOM_SING_PULL] and return - */ -/atom/proc/singularity_pull(obj/singularity/S, current_size) - SEND_SIGNAL(src, COMSIG_ATOM_SING_PULL, S, current_size) - - -/** - * Respond to acid being used on our atom - * - * Default behaviour is to send [COMSIG_ATOM_ACID_ACT] and return - */ -/atom/proc/acid_act(acidpwr, acid_volume) - SEND_SIGNAL(src, COMSIG_ATOM_ACID_ACT, acidpwr, acid_volume) - -/** - * Respond to an emag being used on our atom - * - * Default behaviour is to send [COMSIG_ATOM_EMAG_ACT] and return - */ -/atom/proc/emag_act(mob/user) - SEND_SIGNAL(src, COMSIG_ATOM_EMAG_ACT, user) - -/** - * Respond to a radioactive wave hitting this atom - * - * Default behaviour is to send [COMSIG_ATOM_RAD_ACT] and return - */ -/atom/proc/rad_act(strength) - SEND_SIGNAL(src, COMSIG_ATOM_RAD_ACT, strength) - -/** - * Respond to narsie eating our atom - * - * Default behaviour is to send [COMSIG_ATOM_NARSIE_ACT] and return - */ -/atom/proc/narsie_act() - SEND_SIGNAL(src, COMSIG_ATOM_NARSIE_ACT) - - -///Return the values you get when an RCD eats you? -/atom/proc/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd) - return FALSE - - -/** - * Respond to an RCD acting on our item - * - * Default behaviour is to send [COMSIG_ATOM_RCD_ACT] and return FALSE - */ -/atom/proc/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode) - SEND_SIGNAL(src, COMSIG_ATOM_RCD_ACT, user, the_rcd, passed_mode) - return FALSE - -/** - * Respond to a electric bolt action on our item - * - * Default behaviour is to return, we define here to allow for cleaner code later on - */ -/atom/proc/zap_act(power, zap_flags, shocked_targets) - return - - -/** - * Implement the behaviour for when a user click drags a storage object to your atom - * - * This behaviour is usually to mass transfer, but this is no longer a used proc as it just - * calls the underyling /datum/component/storage dump act if a component exists - * - * TODO these should be purely component items that intercept the atom clicks higher in the - * call chain - */ -/atom/proc/storage_contents_dump_act(obj/item/storage/src_object, mob/user) - if(GetComponent(/datum/component/storage)) - return component_storage_contents_dump_act(src_object, user) - return FALSE - -/** - * Implement the behaviour for when a user click drags another storage item to you - * - * In this case we get as many of the tiems from the target items compoent storage and then - * put everything into ourselves (or our storage component) - * - * TODO these should be purely component items that intercept the atom clicks higher in the - * call chain - */ -/atom/proc/component_storage_contents_dump_act(datum/component/storage/src_object, mob/user) - var/list/things = src_object.contents() - var/datum/progressbar/progress = new(user, things.len, src) - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - while (do_after(user, 10, TRUE, src, FALSE, CALLBACK(STR, /datum/component/storage.proc/handle_mass_item_insertion, things, src_object, user, progress))) - stoplag(1) - progress.end_progress() - to_chat(user, "You dump as much of [src_object.parent]'s contents [STR.insert_preposition]to [src] as you can.") - STR.orient2hud(user) - src_object.orient2hud(user) - if(user.active_storage) //refresh the HUD to show the transfered contents - user.active_storage.close(user) - user.active_storage.show_to(user) - return TRUE - -///Get the best place to dump the items contained in the source storage item? -/atom/proc/get_dumping_location(obj/item/storage/source,mob/user) - return null - -/** - * This proc is called when an atom in our contents has it's [Destroy][/atom/Destroy] called - * - * Default behaviour is to simply send [COMSIG_ATOM_CONTENTS_DEL] - */ -/atom/proc/handle_atom_del(atom/A) - SEND_SIGNAL(src, COMSIG_ATOM_CONTENTS_DEL, A) - -/** - * called when the turf the atom resides on is ChangeTurfed - * - * Default behaviour is to loop through atom contents and call their HandleTurfChange() proc - */ -/atom/proc/HandleTurfChange(turf/T) - for(var/a in src) - var/atom/A = a - A.HandleTurfChange(T) - -/** - * the vision impairment to give to the mob whose perspective is set to that atom - * - * (e.g. an unfocused camera giving you an impaired vision when looking through it) - */ -/atom/proc/get_remote_view_fullscreens(mob/user) - return - -/** - * the sight changes to give to the mob whose perspective is set to that atom - * - * (e.g. A mob with nightvision loses its nightvision while looking through a normal camera) - */ -/atom/proc/update_remote_sight(mob/living/user) - return - - -/** - * Hook for running code when a dir change occurs - * - * Not recommended to use, listen for the [COMSIG_ATOM_DIR_CHANGE] signal instead (sent by this proc) - */ -/atom/proc/setDir(newdir) - SHOULD_CALL_PARENT(TRUE) - SEND_SIGNAL(src, COMSIG_ATOM_DIR_CHANGE, dir, newdir) - dir = newdir - -///Handle melee attack by a mech -/atom/proc/mech_melee_attack(obj/mecha/M) - return - -/** - * Called when the atom log's in or out - * - * Default behaviour is to call on_log on the location this atom is in - */ -/atom/proc/on_log(login) - if(loc) - loc.on_log(login) - - -/* - Atom Colour Priority System - A System that gives finer control over which atom colour to colour the atom with. - The "highest priority" one is always displayed as opposed to the default of - "whichever was set last is displayed" -*/ - - -///Adds an instance of colour_type to the atom's atom_colours list -/atom/proc/add_atom_colour(coloration, colour_priority) - if(!atom_colours || !atom_colours.len) - atom_colours = list() - atom_colours.len = COLOUR_PRIORITY_AMOUNT //four priority levels currently. - if(!coloration) - return - if(colour_priority > atom_colours.len) - return - atom_colours[colour_priority] = coloration - update_atom_colour() - - -///Removes an instance of colour_type from the atom's atom_colours list -/atom/proc/remove_atom_colour(colour_priority, coloration) - if(!atom_colours) - atom_colours = list() - atom_colours.len = COLOUR_PRIORITY_AMOUNT //four priority levels currently. - if(colour_priority > atom_colours.len) - return - if(coloration && atom_colours[colour_priority] != coloration) - return //if we don't have the expected color (for a specific priority) to remove, do nothing - atom_colours[colour_priority] = null - update_atom_colour() - - -///Resets the atom's color to null, and then sets it to the highest priority colour available -/atom/proc/update_atom_colour() - if(!atom_colours) - atom_colours = list() - atom_colours.len = COLOUR_PRIORITY_AMOUNT //four priority levels currently. - color = null - for(var/C in atom_colours) - if(islist(C)) - var/list/L = C - if(L.len) - color = L - return - else if(C) - color = C - return - - -///Proc for being washed by a shower -/atom/proc/washed(var/atom/washer) - . = SEND_SIGNAL(src, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_WEAK) - remove_atom_colour(WASHABLE_COLOUR_PRIORITY) - - var/datum/component/radioactive/healthy_green_glow = GetComponent(/datum/component/radioactive) - if(!healthy_green_glow || QDELETED(healthy_green_glow)) - return - var/strength = healthy_green_glow.strength - if(strength <= RAD_BACKGROUND_RADIATION) - qdel(healthy_green_glow) - return - healthy_green_glow.strength -= max(0, (healthy_green_glow.strength - (RAD_BACKGROUND_RADIATION * 2)) * 0.2) - - -/** - * call back when a var is edited on this atom - * - * Can be used to implement special handling of vars - * - * At the atom level, if you edit a var named "color" it will add the atom colour with - * admin level priority to the atom colours list - * - * Also, if GLOB.Debug2 is FALSE, it sets the [ADMIN_SPAWNED_1] flag on [flags_1][/atom/var/flags_1], which signifies - * the object has been admin edited - */ -/atom/vv_edit_var(var_name, var_value) - if(!GLOB.Debug2) - flags_1 |= ADMIN_SPAWNED_1 - . = ..() - switch(var_name) - if("color") - add_atom_colour(color, ADMIN_COLOUR_PRIORITY) - -/** - * Return the markup to for the dropdown list for the VV panel for this atom - * - * Override in subtypes to add custom VV handling in the VV panel - */ -/atom/vv_get_dropdown() - . = ..() - VV_DROPDOWN_OPTION("", "---------") - if(!ismovable(src)) - var/turf/curturf = get_turf(src) - if(curturf) - . += "" - VV_DROPDOWN_OPTION(VV_HK_MODIFY_TRANSFORM, "Modify Transform") - 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") - -/atom/vv_do_topic(list/href_list) - . = ..() - if(href_list[VV_HK_ADD_REAGENT] && check_rights(R_VAREDIT)) - if(!reagents) - var/amount = input(usr, "Specify the reagent size of [src]", "Set Reagent Size", 50) as num|null - if(amount) - create_reagents(amount) - - if(reagents) - var/chosen_id - switch(alert(usr, "Choose a method.", "Add Reagents", "Search", "Choose from a list", "I'm feeling lucky")) - if("Search") - var/valid_id - while(!valid_id) - chosen_id = input(usr, "Enter the ID of the reagent you want to add.", "Search reagents") as null|text - if(isnull(chosen_id)) //Get me out of here! - break - if (!ispath(text2path(chosen_id))) - chosen_id = pick_closest_path(chosen_id, make_types_fancy(subtypesof(/datum/reagent))) - if (ispath(chosen_id)) - valid_id = TRUE - else - valid_id = TRUE - if(!valid_id) - to_chat(usr, "A reagent with that ID doesn't exist!") - if("Choose from a list") - chosen_id = input(usr, "Choose a reagent to add.", "Choose a reagent.") as null|anything in sortList(subtypesof(/datum/reagent), /proc/cmp_typepaths_asc) - if("I'm feeling lucky") - chosen_id = pick(subtypesof(/datum/reagent)) - if(chosen_id) - var/amount = input(usr, "Choose the amount to add.", "Choose the amount.", reagents.maximum_volume) as num|null - if(amount) - reagents.add_reagent(chosen_id, amount) - log_admin("[key_name(usr)] has added [amount] units of [chosen_id] to [src]") - message_admins("[key_name(usr)] has added [amount] units of [chosen_id] to [src]") - if(href_list[VV_HK_TRIGGER_EXPLOSION] && check_rights(R_FUN)) - usr.client.cmd_admin_explosion(src) - if(href_list[VV_HK_TRIGGER_EMP] && check_rights(R_FUN)) - usr.client.cmd_admin_emp(src) - if(href_list[VV_HK_MODIFY_TRANSFORM] && check_rights(R_VAREDIT)) - var/result = input(usr, "Choose the transformation to apply","Transform Mod") as null|anything in list("Scale","Translate","Rotate") - var/matrix/M = transform - switch(result) - if("Scale") - var/x = input(usr, "Choose x mod","Transform Mod") as null|num - var/y = input(usr, "Choose y mod","Transform Mod") as null|num - if(!isnull(x) && !isnull(y)) - transform = M.Scale(x,y) - if("Translate") - var/x = input(usr, "Choose x mod","Transform Mod") as null|num - var/y = input(usr, "Choose y mod","Transform Mod") as null|num - if(!isnull(x) && !isnull(y)) - transform = M.Translate(x,y) - if("Rotate") - var/angle = input(usr, "Choose angle to rotate","Transform Mod") as null|num - if(!isnull(angle)) - transform = M.Turn(angle) - if(href_list[VV_HK_AUTO_RENAME] && check_rights(R_VAREDIT)) - var/newname = input(usr, "What do you want to rename this to?", "Automatic Rename") as null|text - if(newname) - vv_auto_rename(newname) - -/atom/vv_get_header() - . = ..() - var/refid = REF(src) - . += "[VV_HREF_TARGETREF(refid, VV_HK_AUTO_RENAME, "[src]")]" - . += "
    << [dir2text(dir) || dir] >>" - -///Where atoms should drop if taken from this atom -/atom/proc/drop_location() - var/atom/L = loc - if(!L) - return null - return L.AllowDrop() ? L : L.drop_location() - -/atom/proc/vv_auto_rename(newname) - name = newname - -/** - * An atom has entered this atom's contents - * - * Default behaviour is to send the [COMSIG_ATOM_ENTERED] - */ -/atom/Entered(atom/movable/AM, atom/oldLoc) - SEND_SIGNAL(src, COMSIG_ATOM_ENTERED, AM, oldLoc) - -/** - * An atom is attempting to exit this atom's contents - * - * Default behaviour is to send the [COMSIG_ATOM_EXIT] - * - * Return value should be set to FALSE if the moving atom is unable to leave, - * otherwise leave value the result of the parent call - */ -/atom/Exit(atom/movable/AM, atom/newLoc) - . = ..() - if(SEND_SIGNAL(src, COMSIG_ATOM_EXIT, AM, newLoc) & COMPONENT_ATOM_BLOCK_EXIT) - return FALSE - -/** - * An atom has exited this atom's contents - * - * Default behaviour is to send the [COMSIG_ATOM_EXITED] - */ -/atom/Exited(atom/movable/AM, atom/newLoc) - SEND_SIGNAL(src, COMSIG_ATOM_EXITED, AM, newLoc) - -///Return atom temperature -/atom/proc/return_temperature() - return - -/** - *Tool behavior procedure. Redirects to tool-specific procs by default. - * - * You can override it to catch all tool interactions, for use in complex deconstruction procs. - * - * Must return parent proc ..() in the end if overridden - */ -/atom/proc/tool_act(mob/living/user, obj/item/I, tool_type) - switch(tool_type) - if(TOOL_CROWBAR) - . |= crowbar_act(user, I) - if(TOOL_MULTITOOL) - . |= multitool_act(user, I) - if(TOOL_SCREWDRIVER) - . |= screwdriver_act(user, I) - if(TOOL_WRENCH) - . |= wrench_act(user, I) - if(TOOL_WIRECUTTER) - . |= wirecutter_act(user, I) - if(TOOL_WELDER) - . |= welder_act(user, I) - if(TOOL_ANALYZER) - . |= analyzer_act(user, I) - if(. & COMPONENT_BLOCK_TOOL_ATTACK) - return TRUE - -//! Tool-specific behavior procs. They send signals, so try to call ..() -/// - -///Crowbar act -/atom/proc/crowbar_act(mob/living/user, obj/item/I) - return SEND_SIGNAL(src, COMSIG_ATOM_CROWBAR_ACT, user, I) - -///Multitool act -/atom/proc/multitool_act(mob/living/user, obj/item/I) - return SEND_SIGNAL(src, COMSIG_ATOM_MULTITOOL_ACT, user, I) - -///Check if the multitool has an item in it's data buffer -/atom/proc/multitool_check_buffer(user, obj/item/I, silent = FALSE) - if(!istype(I, /obj/item/multitool)) - if(user && !silent) - to_chat(user, "[I] has no data buffer!") - return FALSE - return TRUE - -///Screwdriver act -/atom/proc/screwdriver_act(mob/living/user, obj/item/I) - return SEND_SIGNAL(src, COMSIG_ATOM_SCREWDRIVER_ACT, user, I) - -///Wrench act -/atom/proc/wrench_act(mob/living/user, obj/item/I) - return SEND_SIGNAL(src, COMSIG_ATOM_WRENCH_ACT, user, I) - -///Wirecutter act -/atom/proc/wirecutter_act(mob/living/user, obj/item/I) - return SEND_SIGNAL(src, COMSIG_ATOM_WIRECUTTER_ACT, user, I) - -///Welder act -/atom/proc/welder_act(mob/living/user, obj/item/I) - return SEND_SIGNAL(src, COMSIG_ATOM_WELDER_ACT, user, I) - -///Analyzer act -/atom/proc/analyzer_act(mob/living/user, obj/item/I) - return SEND_SIGNAL(src, COMSIG_ATOM_ANALYSER_ACT, user, I) - -///Generate a tag for this atom -/atom/proc/GenerateTag() - return - -///Connect this atom to a shuttle -/atom/proc/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) - return - -/// Generic logging helper -/atom/proc/log_message(message, message_type, color=null, log_globally=TRUE) - if(!log_globally) - return - - var/log_text = "[key_name(src)] [message] [loc_name(src)]" - switch(message_type) - if(LOG_ATTACK) - log_attack(log_text) - if(LOG_SAY) - log_say(log_text) - if(LOG_WHISPER) - log_whisper(log_text) - if(LOG_EMOTE) - log_emote(log_text) - if(LOG_SUBTLER) //Wasp Edit - log_subtler(log_text) - if(LOG_DSAY) - log_dsay(log_text) - if(LOG_PDA) - log_pda(log_text) - if(LOG_CHAT) - log_chat(log_text) - if(LOG_COMMENT) - log_comment(log_text) - if(LOG_TELECOMMS) - log_telecomms(log_text) - if(LOG_ECON) - log_econ(log_text) - if(LOG_OOC) - log_ooc(log_text) - if(LOG_ADMIN) - log_admin(log_text) - if(LOG_ADMIN_PRIVATE) - log_admin_private(log_text) - if(LOG_ASAY) - log_adminsay(log_text) - if(LOG_OWNERSHIP) - log_game(log_text) - if(LOG_GAME) - log_game(log_text) - if(LOG_MECHA) - log_mecha(log_text) - if(LOG_SHUTTLE) - log_shuttle(log_text) - else - stack_trace("Invalid individual logging type: [message_type]. Defaulting to [LOG_GAME] (LOG_GAME).") - log_game(log_text) - -/// Helper for logging chat messages or other logs with arbitrary inputs (e.g. announcements) -/atom/proc/log_talk(message, message_type, tag=null, log_globally=TRUE, forced_by=null) - var/prefix = tag ? "([tag]) " : "" - var/suffix = forced_by ? " FORCED by [forced_by]" : "" - log_message("[prefix]\"[message]\"[suffix]", message_type, log_globally=log_globally) - -/// Helper for logging of messages with only one sender and receiver -/proc/log_directed_talk(atom/source, atom/target, message, message_type, tag) - if(!tag) - stack_trace("Unspecified tag for private message") - tag = "UNKNOWN" - - source.log_talk(message, message_type, tag="[tag] to [key_name(target)]") - if(source != target) - target.log_talk(message, message_type, tag="[tag] from [key_name(source)]", log_globally=FALSE) - -/** - * Log a combat message in the attack log - * - * Arguments: - * * atom/user - argument is the actor performing the action - * * atom/target - argument is the target of the action - * * what_done - is a verb describing the action (e.g. punched, throwed, kicked, etc.) - * * atom/object - is a tool with which the action was made (usually an item) - * * addition - is any additional text, which will be appended to the rest of the log line - */ -/proc/log_combat(atom/user, atom/target, what_done, atom/object=null, addition=null) - var/ssource = key_name(user) - var/starget = key_name(target) - - var/mob/living/living_target = target - var/hp = istype(living_target) ? " (NEWHP: [living_target.health]) " : "" - - var/sobject = "" - if(object) - sobject = " with [object]" - var/saddition = "" - if(addition) - saddition = " [addition]" - - var/postfix = "[sobject][saddition][hp]" - - var/message = "has [what_done] [starget][postfix]" - user.log_message(message, LOG_ATTACK, color="red") - - if(user != target) - var/reverse_message = "has been [what_done] by [ssource][postfix]" - target.log_message(reverse_message, LOG_ATTACK, color="orange", log_globally=FALSE) - -/atom/movable/proc/add_filter(name,priority,list/params) - LAZYINITLIST(filter_data) - var/list/p = params.Copy() - p["priority"] = priority - filter_data[name] = p - update_filters() - -/atom/movable/proc/update_filters() - filters = null - filter_data = sortTim(filter_data, /proc/cmp_filter_data_priority, TRUE) - for(var/f in filter_data) - var/list/data = filter_data[f] - var/list/arguments = data.Copy() - arguments -= "priority" - filters += filter(arglist(arguments)) - -/atom/movable/proc/get_filter(name) - if(filter_data && filter_data[name]) - return filters[filter_data.Find(name)] - -/atom/movable/proc/remove_filter(name) - if(filter_data && filter_data[name]) - filter_data -= name - update_filters() - -/atom/proc/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(list/materials, multiplier = 1) - - if(!materials) - materials = custom_materials - - 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 - - if(!length(materials)) - return - - custom_materials = list() //Reset the list - - for(var/x in materials) - var/datum/material/custom_material = SSmaterials.GetMaterialRef(x) - - if(!(material_flags & MATERIAL_NO_EFFECTS)) - custom_material.on_applied(src, materials[custom_material] * multiplier * material_modifier, material_flags) - custom_materials[custom_material] += materials[x] * multiplier - -/** - * Returns true if this atom has gravity for the passed in turf - * - * Sends signals [COMSIG_ATOM_HAS_GRAVITY] and [COMSIG_TURF_HAS_GRAVITY], both can force gravity with - * the forced gravity var - * - * Gravity situations: - * * No gravity if you're not in a turf - * * No gravity if this atom is in is a space turf - * * Gravity if the area it's in always has gravity - * * Gravity if there's a gravity generator on the z level - * * Gravity if the Z level has an SSMappingTrait for ZTRAIT_GRAVITY - * * otherwise no gravity - */ -/atom/proc/has_gravity(turf/T) - if(!T || !isturf(T)) - T = get_turf(src) - - if(!T) - return 0 - - var/list/forced_gravity = list() - SEND_SIGNAL(src, COMSIG_ATOM_HAS_GRAVITY, T, forced_gravity) - if(!forced_gravity.len) - SEND_SIGNAL(T, COMSIG_TURF_HAS_GRAVITY, src, forced_gravity) - if(forced_gravity.len) - var/max_grav - for(var/i in forced_gravity) - max_grav = max(max_grav, i) - return max_grav - - if(isspaceturf(T)) // Turf never has gravity - return FALSE - if(istype(T, /turf/open/openspace)) //openspace in a space area doesn't get gravity - if(istype(get_area(T), /area/space)) - return FALSE - - var/area/A = get_area(T) - if(A.has_gravity) // Areas which always has gravity - return A.has_gravity - else - // There's a gravity generator on our z level - if(GLOB.gravity_generators["[T.z]"]) - var/max_grav = 0 - for(var/obj/machinery/gravity_generator/main/G in GLOB.gravity_generators["[T.z]"]) - max_grav = max(G.setting,max_grav) - return max_grav - return SSmapping.level_trait(T.z, ZTRAIT_GRAVITY) - -/** - * Called when a mob examines (shift click or verb) this atom twice (or more) within EXAMINE_MORE_TIME (default 1.5 seconds) - * - * This is where you can put extra information on something that may be superfluous or not important in critical gameplay - * moments, while allowing people to manually double-examine to take a closer look - * - * Produces a signal [COMSIG_PARENT_EXAMINE_MORE] - */ -/atom/proc/examine_more(mob/user) - . = list() - SEND_SIGNAL(src, COMSIG_PARENT_EXAMINE_MORE, user, .) - if(!LAZYLEN(.)) // lol ..length - return list("You examine [src] closer, but find nothing of interest...") +/** + * The base type for nearly all physical objects in SS13 + + * Lots and lots of functionality lives here, although in general we are striving to move + * as much as possible to the components/elements system + */ +/atom + layer = TURF_LAYER + plane = GAME_PLANE + + ///If non-null, overrides a/an/some in all cases + var/article + + ///First atom flags var + var/flags_1 = NONE + ///Intearaction flags + var/interaction_flags_atom = NONE + + var/flags_ricochet = NONE + + ///When a projectile tries to ricochet off this atom, the projectile ricochet chance is multiplied by this + var/ricochet_chance_mod = 1 + ///When a projectile ricochets off this atom, it deals the normal damage * this modifier to this atom + var/ricochet_damage_mod = 0.33 + + ///Reagents holder + var/datum/reagents/reagents = null + + ///This atom's HUD (med/sec, etc) images. Associative list. + var/list/image/hud_list = null + ///HUD images that this atom can provide. + var/list/hud_possible + + ///Value used to increment ex_act() if reactionary_explosions is on + var/explosion_block = 0 + + /** + * used to store the different colors on an atom + * + * its inherent color, the colored paint applied on it, special color effect etc... + */ + var/list/atom_colours + + + ///overlays that should remain on top and not normally removed when using cut_overlay functions, like c4. + var/list/priority_overlays + /// a very temporary list of overlays to remove + var/list/remove_overlays + /// a very temporary list of overlays to add + var/list/add_overlays + + ///vis overlays managed by SSvis_overlays to automaticaly turn them like other overlays + var/list/managed_vis_overlays + ///overlays managed by [update_overlays][/atom/proc/update_overlays] to prevent removing overlays that weren't added by the same proc + var/list/managed_overlays + + ///Proximity monitor associated with this atom + var/datum/proximity_monitor/proximity_monitor + ///Cooldown tick timer for buckle messages + var/buckle_message_cooldown = 0 + ///Last fingerprints to touch this atom + var/fingerprintslast + + var/list/filter_data //For handling persistent filters + + ///Economy cost of item + var/custom_price + ///Economy cost of item in premium vendor + var/custom_premium_price + ///Whether spessmen with an ID with an age below AGE_MINOR (20 by default) can buy this item + var/age_restricted = FALSE + + //List of datums orbiting this atom + var/datum/component/orbiter/orbiters + + /// Radiation insulation types + var/rad_insulation = RAD_NO_INSULATION + + ///The custom materials this atom is made of, used by a lot of things like furniture, walls, and floors (if I finish the functionality, that is.) + var/list/custom_materials + ///Bitfield for how the atom handles materials. + var/material_flags = NONE + ///Modifier that raises/lowers the effect of the amount of a material, prevents small and easy to get items from being death machines. + var/material_modifier = 1 + + var/datum/wires/wires = null + + var/list/alternate_appearances + + ///Mobs that are currently do_after'ing this atom, to be cleared from on Destroy() + var/list/targeted_by + + /// Last appearance of the atom for demo saving purposes + var/image/demo_last_appearance + + /// Last name used to calculate a color for the chatmessage overlays + var/chat_color_name + /// Last color calculated for the the chatmessage overlays + var/chat_color + /// A luminescence-shifted value of the last color calculated for chatmessage overlays + var/chat_color_darkened + +/** + * Called when an atom is created in byond (built in engine proc) + * + * Not a lot happens here in SS13 code, as we offload most of the work to the + * [Intialization][/atom/proc/Initialize] proc, mostly we run the preloader + * if the preloader is being used and then call [InitAtom][/datum/controller/subsystem/atoms/proc/InitAtom] of which the ultimate + * result is that the Intialize proc is called. + * + * We also generate a tag here if the DF_USE_TAG flag is set on the atom + */ +/atom/New(loc, ...) + //atom creation method that preloads variables at creation + if(GLOB.use_preloader && (src.type == GLOB._preloader.target_path))//in case the instanciated atom is creating other atoms in New() + world.preloader_load(src) + + if(datum_flags & DF_USE_TAG) + GenerateTag() + + var/do_initialize = SSatoms.initialized + if(do_initialize != INITIALIZATION_INSSATOMS) + args[1] = do_initialize == INITIALIZATION_INNEW_MAPLOAD + if(SSatoms.InitAtom(src, args)) + //we were deleted + return + +/** + * The primary method that objects are setup in SS13 with + * + * we don't use New as we have better control over when this is called and we can choose + * to delay calls or hook other logic in and so forth + * + * During roundstart map parsing, atoms are queued for intialization in the base atom/New(), + * After the map has loaded, then Initalize is called on all atoms one by one. NB: this + * is also true for loading map templates as well, so they don't Initalize until all objects + * in the map file are parsed and present in the world + * + * If you're creating an object at any point after SSInit has run then this proc will be + * immediately be called from New. + * + * mapload: This parameter is true if the atom being loaded is either being intialized during + * the Atom subsystem intialization, or if the atom is being loaded from the map template. + * If the item is being created at runtime any time after the Atom subsystem is intialized then + * it's false. + * + * You must always call the parent of this proc, otherwise failures will occur as the item + * will not be seen as initalized (this can lead to all sorts of strange behaviour, like + * the item being completely unclickable) + * + * You must not sleep in this proc, or any subprocs + * + * Any parameters from new are passed through (excluding loc), naturally if you're loading from a map + * there are no other arguments + * + * Must return an [initialization hint][INITIALIZE_HINT_NORMAL] or a runtime will occur. + * + * Note: the following functions don't call the base for optimization and must copypasta handling: + * * [/turf/Initialize] + * * [/turf/open/space/Initialize] + */ +/atom/proc/Initialize(mapload, ...) + SHOULD_NOT_SLEEP(TRUE) + SHOULD_CALL_PARENT(TRUE) + if(flags_1 & INITIALIZED_1) + stack_trace("Warning: [src]([type]) initialized multiple times!") + flags_1 |= INITIALIZED_1 + + if(loc) + SEND_SIGNAL(loc, COMSIG_ATOM_CREATED, src) /// Sends a signal that the new atom `src`, has been created at `loc` + + //atom color stuff + if(color) + add_atom_colour(color, FIXED_COLOUR_PRIORITY) + + if (light_power && light_range) + update_light() + + if (opacity && isturf(loc)) + var/turf/T = loc + T.has_opaque_atom = TRUE // No need to recalculate it in this case, it's guaranteed to be on afterwards anyways. + + if (canSmoothWith) + canSmoothWith = typelist("canSmoothWith", canSmoothWith) + + var/temp_list = list() + for(var/i in custom_materials) + temp_list[SSmaterials.GetMaterialRef(i)] = custom_materials[i] //Get the proper instanced version + + custom_materials = null //Null the list to prepare for applying the materials properly + set_custom_materials(temp_list) + + ComponentInitialize() + + return INITIALIZE_HINT_NORMAL + +/** + * Late Intialization, for code that should run after all atoms have run Intialization + * + * To have your LateIntialize proc be called, your atoms [Initalization][/atom/proc/Initialize] + * proc must return the hint + * [INITIALIZE_HINT_LATELOAD] otherwise you will never be called. + * + * useful for doing things like finding other machines on GLOB.machines because you can guarantee + * that all atoms will actually exist in the "WORLD" at this time and that all their Intialization + * code has been run + */ +/atom/proc/LateInitialize() + set waitfor = FALSE + +/// Put your [AddComponent] calls here +/atom/proc/ComponentInitialize() + return + +/** + * Top level of the destroy chain for most atoms + * + * Cleans up the following: + * * Removes alternate apperances from huds that see them + * * qdels the reagent holder from atoms if it exists + * * clears the orbiters list + * * clears overlays and priority overlays + * * clears the light object + */ +/atom/Destroy() + if(alternate_appearances) + for(var/K in alternate_appearances) + var/datum/atom_hud/alternate_appearance/AA = alternate_appearances[K] + AA.remove_from_hud(src) + + if(reagents) + qdel(reagents) + + orbiters = null // The component is attached to us normaly and will be deleted elsewhere + + LAZYCLEARLIST(overlays) + LAZYCLEARLIST(priority_overlays) + + for(var/i in targeted_by) + var/mob/M = i + LAZYREMOVE(M.do_afters, src) + + targeted_by = null + QDEL_NULL(light) + + return ..() + +/atom/proc/handle_ricochet(obj/projectile/P) + var/turf/p_turf = get_turf(P) + var/face_direction = get_dir(src, p_turf) + var/face_angle = dir2angle(face_direction) + var/incidence_s = GET_ANGLE_OF_INCIDENCE(face_angle, (P.Angle + 180)) + var/a_incidence_s = abs(incidence_s) + if(a_incidence_s > 90 && a_incidence_s < 270) + return FALSE + if((P.flag in list("bullet", "bomb")) && P.ricochet_incidence_leeway) + if((a_incidence_s < 90 && a_incidence_s < 90 - P.ricochet_incidence_leeway) || (a_incidence_s > 270 && a_incidence_s -270 > P.ricochet_incidence_leeway)) + return + var/new_angle_s = SIMPLIFY_DEGREES(face_angle + incidence_s) + P.setAngle(new_angle_s) + return TRUE + +///Can the mover object pass this atom, while heading for the target turf +/atom/proc/CanPass(atom/movable/mover, turf/target) + SHOULD_CALL_PARENT(TRUE) + SHOULD_BE_PURE(TRUE) + if(mover.movement_type & UNSTOPPABLE) + return TRUE + . = CanAllowThrough(mover, target) + // This is cheaper than calling the proc every time since most things dont override CanPassThrough + if(!mover.generic_canpass) + return mover.CanPassThrough(src, target, .) + +/// Returns true or false to allow the mover to move through src +/atom/proc/CanAllowThrough(atom/movable/mover, turf/target) + SHOULD_CALL_PARENT(TRUE) + SHOULD_BE_PURE(TRUE) + return !density + +/** + * Is this atom currently located on centcom + * + * Specifically, is it on the z level and within the centcom areas + * + * You can also be in a shuttleshuttle during endgame transit + * + * Used in gamemode to identify mobs who have escaped and for some other areas of the code + * who don't want atoms where they shouldn't be + */ +/atom/proc/onCentCom() + var/turf/T = get_turf(src) + if(!T) + return FALSE + + if(is_reserved_level(T.z)) + for(var/A in SSshuttle.mobile) + var/obj/docking_port/mobile/M = A + if(M.launch_status == ENDGAME_TRANSIT) + for(var/place in M.shuttle_areas) + var/area/shuttle/shuttle_area = place + if(T in shuttle_area) + return TRUE + + if(!is_centcom_level(T.z))//if not, don't bother + return FALSE + + //Check for centcom itself + if(istype(T.loc, /area/centcom)) + return TRUE + + //Check for centcom shuttles + for(var/A in SSshuttle.mobile) + var/obj/docking_port/mobile/M = A + if(M.launch_status == ENDGAME_LAUNCHED) + for(var/place in M.shuttle_areas) + var/area/shuttle/shuttle_area = place + if(T in shuttle_area) + return TRUE + +/** + * Is the atom in any of the centcom syndicate areas + * + * Either in the syndie base on centcom, or any of their shuttles + * + * Also used in gamemode code for win conditions + */ +/atom/proc/onSyndieBase() + var/turf/T = get_turf(src) + if(!T) + return FALSE + + if(!is_centcom_level(T.z))//if not, don't bother + return FALSE + + if(istype(T.loc, /area/shuttle/syndicate) || istype(T.loc, /area/syndicate_mothership) || istype(T.loc, /area/shuttle/assault_pod)) + return TRUE + + return FALSE + +/** + * Is the atom in an away mission + * + * Must be in the away mission z-level to return TRUE + * + * Also used in gamemode code for win conditions + */ +/atom/proc/onAwayMission() + var/turf/T = get_turf(src) + if(!T) + return FALSE + + if(is_away_level(T.z)) + return TRUE + + return FALSE + + + +///This atom has been hit by a hulkified mob in hulk mode (user) +/atom/proc/attack_hulk(mob/living/carbon/human/user) + SEND_SIGNAL(src, COMSIG_ATOM_HULK_ATTACK, user) + +/** + * Ensure a list of atoms/reagents exists inside this atom + * + * Goes throught he list of passed in parts, if they're reagents, adds them to our reagent holder + * creating the reagent holder if it exists. + * + * If the part is a moveable atom and the previous location of the item was a mob/living, + * it calls the inventory handler transferItemToLoc for that mob/living and transfers the part + * to this atom + * + * Otherwise it simply forceMoves the atom into this atom + */ +/atom/proc/CheckParts(list/parts_list) + for(var/A in parts_list) + if(istype(A, /datum/reagent)) + if(!reagents) + reagents = new() + reagents.reagent_list.Add(A) + reagents.conditional_update() + else if(ismovable(A)) + var/atom/movable/M = A + if(isliving(M.loc)) + var/mob/living/L = M.loc + L.transferItemToLoc(M, src) + else + M.forceMove(src) + +///Hook for multiz??? +/atom/proc/update_multiz(prune_on_fail = FALSE) + return FALSE + +///Take air from the passed in gas mixture datum +/atom/proc/assume_air(datum/gas_mixture/giver) + qdel(giver) + return null + +///Remove air from this atom +/atom/proc/remove_air(amount) + return null + +///Return the current air environment in this atom +/atom/proc/return_air() + if(loc) + return loc.return_air() + else + return null + +///Return the air if we can analyze it +/atom/proc/return_analyzable_air() + return null + +///Check if this atoms eye is still alive (probably) +/atom/proc/check_eye(mob/user) + return + +/atom/proc/Bumped(atom/movable/AM) + set waitfor = FALSE + SEND_SIGNAL(src, COMSIG_ATOM_BUMPED, AM) + +/// Convenience proc to see if a container is open for chemistry handling +/atom/proc/is_open_container() + return is_refillable() && is_drainable() + +/// Is this atom injectable into other atoms +/atom/proc/is_injectable(mob/user, allowmobs = TRUE) + return reagents && (reagents.flags & (INJECTABLE | REFILLABLE)) + +/// Can we draw from this atom with an injectable atom +/atom/proc/is_drawable(mob/user, allowmobs = TRUE) + return reagents && (reagents.flags & (DRAWABLE | DRAINABLE)) + +/// Can this atoms reagents be refilled +/atom/proc/is_refillable() + return reagents && (reagents.flags & REFILLABLE) + +/// Is this atom drainable of reagents +/atom/proc/is_drainable() + return reagents && (reagents.flags & DRAINABLE) + +/** Handles exposing this atom to a list of reagents. + * + * Sends COMSIG_ATOM_EXPOSE_REAGENTS + * Calls expose_atom() for every reagent in the reagent list. + * + * Arguments: + * - [reagents][/list]: The list of reagents the atom is being exposed to. + * - [source][/datum/reagents]: The reagent holder the reagents are being sourced from. + * - method: How the atom is being exposed to the reagents. + * - volume_modifier: Volume multiplier. + * - show_message: Whether to display anything to mobs when they are exposed. + */ +/atom/proc/expose_reagents(list/reagents, datum/reagents/source, method=TOUCH, volume_modifier=1, show_message=TRUE) + if((. = SEND_SIGNAL(src, COMSIG_ATOM_EXPOSE_REAGENTS, reagents, source, method, volume_modifier, show_message)) & COMPONENT_NO_EXPOSE_REAGENTS) + return + + for(var/reagent in reagents) + var/datum/reagent/R = reagent + . |= R.expose_atom(src, reagents[R]) + +/// Are you allowed to drop this atom +/atom/proc/AllowDrop() + return FALSE + +/atom/proc/CheckExit() + return 1 + +///Is this atom within 1 tile of another atom +/atom/proc/HasProximity(atom/movable/AM as mob|obj) + return + +/** + * React to an EMP of the given severity + * + * Default behaviour is to send the [COMSIG_ATOM_EMP_ACT] signal + * + * If the signal does not return protection, and there are attached wires then we call + * [emp_pulse][/datum/wires/proc/emp_pulse] on the wires + * + * We then return the protection value + */ +/atom/proc/emp_act(severity) + var/protection = SEND_SIGNAL(src, COMSIG_ATOM_EMP_ACT, severity) + if(!(protection & EMP_PROTECT_WIRES) && istype(wires)) + wires.emp_pulse() + return protection // Pass the protection value collected here upwards + +/** + * React to a hit by a projectile object + * + * Default behaviour is to send the [COMSIG_ATOM_BULLET_ACT] and then call [on_hit][/obj/projectile/proc/on_hit] on the projectile + */ +/atom/proc/bullet_act(obj/projectile/P, def_zone) + SEND_SIGNAL(src, COMSIG_ATOM_BULLET_ACT, P, def_zone) + . = P.on_hit(src, 0, def_zone) + +///Return true if we're inside the passed in atom +/atom/proc/in_contents_of(container)//can take class or object instance as argument + if(ispath(container)) + if(istype(src.loc, container)) + return TRUE + else if(src in container) + return TRUE + return FALSE + +/** + * Get the name of this object for examine + * + * You can override what is returned from this proc by registering to listen for the + * [COMSIG_ATOM_GET_EXAMINE_NAME] signal + */ +/atom/proc/get_examine_name(mob/user) + . = "\a [src]" + var/list/override = list(gender == PLURAL ? "some" : "a", " ", "[name]") + if(article) + . = "[article] [src]" + override[EXAMINE_POSITION_ARTICLE] = article + if(SEND_SIGNAL(src, COMSIG_ATOM_GET_EXAMINE_NAME, user, override) & COMPONENT_EXNAME_CHANGED) + . = override.Join("") + +///Generate the full examine string of this atom (including icon for goonchat) +/atom/proc/get_examine_string(mob/user, thats = FALSE) + return "[icon2html(src, user)] [thats? "That's ":""][get_examine_name(user)]" + +/** + * Called when a mob examines (shift click or verb) this atom + * + * Default behaviour is to get the name and icon of the object and it's reagents where + * the [TRANSPARENT] flag is set on the reagents holder + * + * Produces a signal [COMSIG_PARENT_EXAMINE] + */ +/atom/proc/examine(mob/user) + . = list("[get_examine_string(user, TRUE)].") + + if(desc) + . += desc + + if(custom_materials) + var/list/materials_list = list() + for(var/i in custom_materials) + var/datum/material/M = i + materials_list += "[M.name]" + . += "It is made out of [english_list(materials_list)]." + if(reagents) + if(reagents.flags & TRANSPARENT) + . += "It contains:" + if(length(reagents.reagent_list)) + if(user.can_see_reagents()) //Show each individual reagent + for(var/datum/reagent/R in reagents.reagent_list) + . += "[R.volume] units of [R.name]" + else //Otherwise, just show the total volume + var/total_volume = 0 + for(var/datum/reagent/R in reagents.reagent_list) + total_volume += R.volume + . += "[total_volume] units of various reagents" + else + . += "Nothing." + else if(reagents.flags & AMOUNT_VISIBLE) + if(reagents.total_volume) + . += "It has [reagents.total_volume] unit\s left." + else + . += "It's empty." + + SEND_SIGNAL(src, COMSIG_PARENT_EXAMINE, user, .) + +/// Updates the icon of the atom +/atom/proc/update_icon() + var/signalOut = SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_ICON) + . = FALSE + + if(!(signalOut & COMSIG_ATOM_NO_UPDATE_ICON_STATE)) + update_icon_state() + . = TRUE + + if(!(signalOut & COMSIG_ATOM_NO_UPDATE_OVERLAYS)) + var/list/new_overlays = update_overlays() + if(managed_overlays) + cut_overlay(managed_overlays) + managed_overlays = null + if(length(new_overlays)) + managed_overlays = new_overlays + add_overlay(new_overlays) + . = TRUE + + SEND_SIGNAL(src, COMSIG_ATOM_UPDATED_ICON, signalOut, .) + +/// Updates the icon state of the atom +/atom/proc/update_icon_state() + +/// Updates the overlays of the atom +/atom/proc/update_overlays() + SHOULD_CALL_PARENT(1) + . = list() + SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_OVERLAYS, .) + +/** + * An atom we are buckled or is contained within us has tried to move + * + * Default behaviour is to send a warning that the user can't move while buckled as long + * as the [buckle_message_cooldown][/atom/var/buckle_message_cooldown] has expired (50 ticks) + */ +/atom/proc/relaymove(mob/user) + if(buckle_message_cooldown <= world.time) + buckle_message_cooldown = world.time + 50 + to_chat(user, "You can't move while buckled to [src]!") + return + +/// Handle what happens when your contents are exploded by a bomb +/atom/proc/contents_explosion(severity, target) + return //For handling the effects of explosions on contents that would not normally be effected + +/** + * React to being hit by an explosion + * + * Default behaviour is to call [contents_explosion][/atom/proc/contents_explosion] and send the [COMSIG_ATOM_EX_ACT] signal + */ +/atom/proc/ex_act(severity, target) + set waitfor = FALSE + contents_explosion(severity, target) + SEND_SIGNAL(src, COMSIG_ATOM_EX_ACT, severity, target) + +/** + * React to a hit by a blob objecd + * + * default behaviour is to send the [COMSIG_ATOM_BLOB_ACT] signal + */ +/atom/proc/blob_act(obj/structure/blob/B) + SEND_SIGNAL(src, COMSIG_ATOM_BLOB_ACT, B) + return + +/atom/proc/fire_act(exposed_temperature, exposed_volume) + SEND_SIGNAL(src, COMSIG_ATOM_FIRE_ACT, exposed_temperature, exposed_volume) + return + +/** + * React to being hit by a thrown object + * + * Default behaviour is to call [hitby_react][/atom/proc/hitby_react] on ourselves after 2 seconds if we are dense + * and under normal gravity. + * + * Im not sure why this the case, maybe to prevent lots of hitby's if the thrown object is + * deleted shortly after hitting something (during explosions or other massive events that + * throw lots of items around - singularity being a notable example) + */ +/atom/proc/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) + if(density && !has_gravity(AM)) //thrown stuff bounces off dense stuff in no grav, unless the thrown stuff ends up inside what it hit(embedding, bola, etc...). + addtimer(CALLBACK(src, .proc/hitby_react, AM), 2) + +/** + * We have have actually hit the passed in atom + * + * Default behaviour is to move back from the item that hit us + */ +/atom/proc/hitby_react(atom/movable/AM) + if(AM && isturf(AM.loc)) + step(AM, turn(AM.dir, 180)) + +///Handle the atom being slipped over +/atom/proc/handle_slip(mob/living/carbon/C, knockdown_amount, obj/O, lube, paralyze, force_drop) + return + +///returns the mob's dna info as a list, to be inserted in an object's blood_DNA list +/mob/living/proc/get_blood_dna_list() + if(get_blood_id() != /datum/reagent/blood) + return + return list("ANIMAL DNA" = "Y-") + +///Get the mobs dna list +/mob/living/carbon/get_blood_dna_list() + if(get_blood_id() != /datum/reagent/blood) + return + var/list/blood_dna = list() + if(dna) + blood_dna[dna.unique_enzymes] = dna.blood_type + else + blood_dna["UNKNOWN DNA"] = "X*" + return blood_dna + +/mob/living/carbon/alien/get_blood_dna_list() + return list("UNKNOWN DNA" = "X*") + +/mob/living/silicon/get_blood_dna_list() + return list("MOTOR OIL" = "SAE 5W-30") //just a little flavor text. + +///to add a mob's dna info into an object's blood_dna list. +/atom/proc/transfer_mob_blood_dna(mob/living/L) + // Returns 0 if we have that blood already + var/new_blood_dna = L.get_blood_dna_list() + if(!new_blood_dna) + return FALSE + var/old_length = blood_DNA_length() + add_blood_DNA(new_blood_dna) + if(blood_DNA_length() == old_length) + return FALSE + return TRUE + +///to add blood from a mob onto something, and transfer their dna info +/atom/proc/add_mob_blood(mob/living/M) + var/list/blood_dna = M.get_blood_dna_list() + if(!blood_dna) + return FALSE + return add_blood_DNA(blood_dna) + +///Is this atom in space +/atom/proc/isinspace() + if(isspaceturf(get_turf(src))) + return TRUE + else + return FALSE + +///Used for making a sound when a mob involuntarily falls into the ground. +/atom/proc/handle_fall(mob/faller) + return + +///Respond to the singularity eating this atom +/atom/proc/singularity_act() + return + +/** + * Respond to the singularity pulling on us + * + * Default behaviour is to send [COMSIG_ATOM_SING_PULL] and return + */ +/atom/proc/singularity_pull(obj/singularity/S, current_size) + SEND_SIGNAL(src, COMSIG_ATOM_SING_PULL, S, current_size) + + +/** + * Respond to acid being used on our atom + * + * Default behaviour is to send [COMSIG_ATOM_ACID_ACT] and return + */ +/atom/proc/acid_act(acidpwr, acid_volume) + SEND_SIGNAL(src, COMSIG_ATOM_ACID_ACT, acidpwr, acid_volume) + +/** + * Respond to an emag being used on our atom + * + * Default behaviour is to send [COMSIG_ATOM_EMAG_ACT] and return + */ +/atom/proc/emag_act(mob/user) + SEND_SIGNAL(src, COMSIG_ATOM_EMAG_ACT, user) + +/** + * Respond to a radioactive wave hitting this atom + * + * Default behaviour is to send [COMSIG_ATOM_RAD_ACT] and return + */ +/atom/proc/rad_act(strength) + SEND_SIGNAL(src, COMSIG_ATOM_RAD_ACT, strength) + +/** + * Respond to narsie eating our atom + * + * Default behaviour is to send [COMSIG_ATOM_NARSIE_ACT] and return + */ +/atom/proc/narsie_act() + SEND_SIGNAL(src, COMSIG_ATOM_NARSIE_ACT) + + +///Return the values you get when an RCD eats you? +/atom/proc/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd) + return FALSE + + +/** + * Respond to an RCD acting on our item + * + * Default behaviour is to send [COMSIG_ATOM_RCD_ACT] and return FALSE + */ +/atom/proc/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode) + SEND_SIGNAL(src, COMSIG_ATOM_RCD_ACT, user, the_rcd, passed_mode) + return FALSE + +/** + * Respond to a electric bolt action on our item + * + * Default behaviour is to return, we define here to allow for cleaner code later on + */ +/atom/proc/zap_act(power, zap_flags, shocked_targets) + return + + +/** + * Implement the behaviour for when a user click drags a storage object to your atom + * + * This behaviour is usually to mass transfer, but this is no longer a used proc as it just + * calls the underyling /datum/component/storage dump act if a component exists + * + * TODO these should be purely component items that intercept the atom clicks higher in the + * call chain + */ +/atom/proc/storage_contents_dump_act(obj/item/storage/src_object, mob/user) + if(GetComponent(/datum/component/storage)) + return component_storage_contents_dump_act(src_object, user) + return FALSE + +/** + * Implement the behaviour for when a user click drags another storage item to you + * + * In this case we get as many of the tiems from the target items compoent storage and then + * put everything into ourselves (or our storage component) + * + * TODO these should be purely component items that intercept the atom clicks higher in the + * call chain + */ +/atom/proc/component_storage_contents_dump_act(datum/component/storage/src_object, mob/user) + var/list/things = src_object.contents() + var/datum/progressbar/progress = new(user, things.len, src) + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + while (do_after(user, 10, TRUE, src, FALSE, CALLBACK(STR, /datum/component/storage.proc/handle_mass_item_insertion, things, src_object, user, progress))) + stoplag(1) + progress.end_progress() + to_chat(user, "You dump as much of [src_object.parent]'s contents [STR.insert_preposition]to [src] as you can.") + STR.orient2hud(user) + src_object.orient2hud(user) + if(user.active_storage) //refresh the HUD to show the transfered contents + user.active_storage.close(user) + user.active_storage.show_to(user) + return TRUE + +///Get the best place to dump the items contained in the source storage item? +/atom/proc/get_dumping_location(obj/item/storage/source,mob/user) + return null + +/** + * This proc is called when an atom in our contents has it's [Destroy][/atom/Destroy] called + * + * Default behaviour is to simply send [COMSIG_ATOM_CONTENTS_DEL] + */ +/atom/proc/handle_atom_del(atom/A) + SEND_SIGNAL(src, COMSIG_ATOM_CONTENTS_DEL, A) + +/** + * called when the turf the atom resides on is ChangeTurfed + * + * Default behaviour is to loop through atom contents and call their HandleTurfChange() proc + */ +/atom/proc/HandleTurfChange(turf/T) + for(var/a in src) + var/atom/A = a + A.HandleTurfChange(T) + +/** + * the vision impairment to give to the mob whose perspective is set to that atom + * + * (e.g. an unfocused camera giving you an impaired vision when looking through it) + */ +/atom/proc/get_remote_view_fullscreens(mob/user) + return + +/** + * the sight changes to give to the mob whose perspective is set to that atom + * + * (e.g. A mob with nightvision loses its nightvision while looking through a normal camera) + */ +/atom/proc/update_remote_sight(mob/living/user) + return + + +/** + * Hook for running code when a dir change occurs + * + * Not recommended to use, listen for the [COMSIG_ATOM_DIR_CHANGE] signal instead (sent by this proc) + */ +/atom/proc/setDir(newdir) + SHOULD_CALL_PARENT(TRUE) + SEND_SIGNAL(src, COMSIG_ATOM_DIR_CHANGE, dir, newdir) + dir = newdir + +///Handle melee attack by a mech +/atom/proc/mech_melee_attack(obj/mecha/M) + return + +/** + * Called when the atom log's in or out + * + * Default behaviour is to call on_log on the location this atom is in + */ +/atom/proc/on_log(login) + if(loc) + loc.on_log(login) + + +/* + Atom Colour Priority System + A System that gives finer control over which atom colour to colour the atom with. + The "highest priority" one is always displayed as opposed to the default of + "whichever was set last is displayed" +*/ + + +///Adds an instance of colour_type to the atom's atom_colours list +/atom/proc/add_atom_colour(coloration, colour_priority) + if(!atom_colours || !atom_colours.len) + atom_colours = list() + atom_colours.len = COLOUR_PRIORITY_AMOUNT //four priority levels currently. + if(!coloration) + return + if(colour_priority > atom_colours.len) + return + atom_colours[colour_priority] = coloration + update_atom_colour() + + +///Removes an instance of colour_type from the atom's atom_colours list +/atom/proc/remove_atom_colour(colour_priority, coloration) + if(!atom_colours) + atom_colours = list() + atom_colours.len = COLOUR_PRIORITY_AMOUNT //four priority levels currently. + if(colour_priority > atom_colours.len) + return + if(coloration && atom_colours[colour_priority] != coloration) + return //if we don't have the expected color (for a specific priority) to remove, do nothing + atom_colours[colour_priority] = null + update_atom_colour() + + +///Resets the atom's color to null, and then sets it to the highest priority colour available +/atom/proc/update_atom_colour() + if(!atom_colours) + atom_colours = list() + atom_colours.len = COLOUR_PRIORITY_AMOUNT //four priority levels currently. + color = null + for(var/C in atom_colours) + if(islist(C)) + var/list/L = C + if(L.len) + color = L + return + else if(C) + color = C + return + + +///Proc for being washed by a shower +/atom/proc/washed(var/atom/washer) + . = SEND_SIGNAL(src, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_WEAK) + remove_atom_colour(WASHABLE_COLOUR_PRIORITY) + + var/datum/component/radioactive/healthy_green_glow = GetComponent(/datum/component/radioactive) + if(!healthy_green_glow || QDELETED(healthy_green_glow)) + return + var/strength = healthy_green_glow.strength + if(strength <= RAD_BACKGROUND_RADIATION) + qdel(healthy_green_glow) + return + healthy_green_glow.strength -= max(0, (healthy_green_glow.strength - (RAD_BACKGROUND_RADIATION * 2)) * 0.2) + + +/** + * call back when a var is edited on this atom + * + * Can be used to implement special handling of vars + * + * At the atom level, if you edit a var named "color" it will add the atom colour with + * admin level priority to the atom colours list + * + * Also, if GLOB.Debug2 is FALSE, it sets the [ADMIN_SPAWNED_1] flag on [flags_1][/atom/var/flags_1], which signifies + * the object has been admin edited + */ +/atom/vv_edit_var(var_name, var_value) + if(!GLOB.Debug2) + flags_1 |= ADMIN_SPAWNED_1 + . = ..() + switch(var_name) + if("color") + add_atom_colour(color, ADMIN_COLOUR_PRIORITY) + +/** + * Return the markup to for the dropdown list for the VV panel for this atom + * + * Override in subtypes to add custom VV handling in the VV panel + */ +/atom/vv_get_dropdown() + . = ..() + VV_DROPDOWN_OPTION("", "---------") + if(!ismovable(src)) + var/turf/curturf = get_turf(src) + if(curturf) + . += "" + VV_DROPDOWN_OPTION(VV_HK_MODIFY_TRANSFORM, "Modify Transform") + 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") + +/atom/vv_do_topic(list/href_list) + . = ..() + if(href_list[VV_HK_ADD_REAGENT] && check_rights(R_VAREDIT)) + if(!reagents) + var/amount = input(usr, "Specify the reagent size of [src]", "Set Reagent Size", 50) as num|null + if(amount) + create_reagents(amount) + + if(reagents) + var/chosen_id + switch(alert(usr, "Choose a method.", "Add Reagents", "Search", "Choose from a list", "I'm feeling lucky")) + if("Search") + var/valid_id + while(!valid_id) + chosen_id = input(usr, "Enter the ID of the reagent you want to add.", "Search reagents") as null|text + if(isnull(chosen_id)) //Get me out of here! + break + if (!ispath(text2path(chosen_id))) + chosen_id = pick_closest_path(chosen_id, make_types_fancy(subtypesof(/datum/reagent))) + if (ispath(chosen_id)) + valid_id = TRUE + else + valid_id = TRUE + if(!valid_id) + to_chat(usr, "A reagent with that ID doesn't exist!") + if("Choose from a list") + chosen_id = input(usr, "Choose a reagent to add.", "Choose a reagent.") as null|anything in sortList(subtypesof(/datum/reagent), /proc/cmp_typepaths_asc) + if("I'm feeling lucky") + chosen_id = pick(subtypesof(/datum/reagent)) + if(chosen_id) + var/amount = input(usr, "Choose the amount to add.", "Choose the amount.", reagents.maximum_volume) as num|null + if(amount) + reagents.add_reagent(chosen_id, amount) + log_admin("[key_name(usr)] has added [amount] units of [chosen_id] to [src]") + message_admins("[key_name(usr)] has added [amount] units of [chosen_id] to [src]") + if(href_list[VV_HK_TRIGGER_EXPLOSION] && check_rights(R_FUN)) + usr.client.cmd_admin_explosion(src) + if(href_list[VV_HK_TRIGGER_EMP] && check_rights(R_FUN)) + usr.client.cmd_admin_emp(src) + if(href_list[VV_HK_MODIFY_TRANSFORM] && check_rights(R_VAREDIT)) + var/result = input(usr, "Choose the transformation to apply","Transform Mod") as null|anything in list("Scale","Translate","Rotate") + var/matrix/M = transform + switch(result) + if("Scale") + var/x = input(usr, "Choose x mod","Transform Mod") as null|num + var/y = input(usr, "Choose y mod","Transform Mod") as null|num + if(!isnull(x) && !isnull(y)) + transform = M.Scale(x,y) + if("Translate") + var/x = input(usr, "Choose x mod","Transform Mod") as null|num + var/y = input(usr, "Choose y mod","Transform Mod") as null|num + if(!isnull(x) && !isnull(y)) + transform = M.Translate(x,y) + if("Rotate") + var/angle = input(usr, "Choose angle to rotate","Transform Mod") as null|num + if(!isnull(angle)) + transform = M.Turn(angle) + if(href_list[VV_HK_AUTO_RENAME] && check_rights(R_VAREDIT)) + var/newname = input(usr, "What do you want to rename this to?", "Automatic Rename") as null|text + if(newname) + vv_auto_rename(newname) + +/atom/vv_get_header() + . = ..() + var/refid = REF(src) + . += "[VV_HREF_TARGETREF(refid, VV_HK_AUTO_RENAME, "[src]")]" + . += "
    << [dir2text(dir) || dir] >>" + +///Where atoms should drop if taken from this atom +/atom/proc/drop_location() + var/atom/L = loc + if(!L) + return null + return L.AllowDrop() ? L : L.drop_location() + +/atom/proc/vv_auto_rename(newname) + name = newname + +/** + * An atom has entered this atom's contents + * + * Default behaviour is to send the [COMSIG_ATOM_ENTERED] + */ +/atom/Entered(atom/movable/AM, atom/oldLoc) + SEND_SIGNAL(src, COMSIG_ATOM_ENTERED, AM, oldLoc) + +/** + * An atom is attempting to exit this atom's contents + * + * Default behaviour is to send the [COMSIG_ATOM_EXIT] + * + * Return value should be set to FALSE if the moving atom is unable to leave, + * otherwise leave value the result of the parent call + */ +/atom/Exit(atom/movable/AM, atom/newLoc) + . = ..() + if(SEND_SIGNAL(src, COMSIG_ATOM_EXIT, AM, newLoc) & COMPONENT_ATOM_BLOCK_EXIT) + return FALSE + +/** + * An atom has exited this atom's contents + * + * Default behaviour is to send the [COMSIG_ATOM_EXITED] + */ +/atom/Exited(atom/movable/AM, atom/newLoc) + SEND_SIGNAL(src, COMSIG_ATOM_EXITED, AM, newLoc) + +///Return atom temperature +/atom/proc/return_temperature() + return + +/** + *Tool behavior procedure. Redirects to tool-specific procs by default. + * + * You can override it to catch all tool interactions, for use in complex deconstruction procs. + * + * Must return parent proc ..() in the end if overridden + */ +/atom/proc/tool_act(mob/living/user, obj/item/I, tool_type) + switch(tool_type) + if(TOOL_CROWBAR) + . |= crowbar_act(user, I) + if(TOOL_MULTITOOL) + . |= multitool_act(user, I) + if(TOOL_SCREWDRIVER) + . |= screwdriver_act(user, I) + if(TOOL_WRENCH) + . |= wrench_act(user, I) + if(TOOL_WIRECUTTER) + . |= wirecutter_act(user, I) + if(TOOL_WELDER) + . |= welder_act(user, I) + if(TOOL_ANALYZER) + . |= analyzer_act(user, I) + if(. & COMPONENT_BLOCK_TOOL_ATTACK) + return TRUE + +//! Tool-specific behavior procs. They send signals, so try to call ..() +/// + +///Crowbar act +/atom/proc/crowbar_act(mob/living/user, obj/item/I) + return SEND_SIGNAL(src, COMSIG_ATOM_CROWBAR_ACT, user, I) + +///Multitool act +/atom/proc/multitool_act(mob/living/user, obj/item/I) + return SEND_SIGNAL(src, COMSIG_ATOM_MULTITOOL_ACT, user, I) + +///Check if the multitool has an item in it's data buffer +/atom/proc/multitool_check_buffer(user, obj/item/I, silent = FALSE) + if(!istype(I, /obj/item/multitool)) + if(user && !silent) + to_chat(user, "[I] has no data buffer!") + return FALSE + return TRUE + +///Screwdriver act +/atom/proc/screwdriver_act(mob/living/user, obj/item/I) + return SEND_SIGNAL(src, COMSIG_ATOM_SCREWDRIVER_ACT, user, I) + +///Wrench act +/atom/proc/wrench_act(mob/living/user, obj/item/I) + return SEND_SIGNAL(src, COMSIG_ATOM_WRENCH_ACT, user, I) + +///Wirecutter act +/atom/proc/wirecutter_act(mob/living/user, obj/item/I) + return SEND_SIGNAL(src, COMSIG_ATOM_WIRECUTTER_ACT, user, I) + +///Welder act +/atom/proc/welder_act(mob/living/user, obj/item/I) + return SEND_SIGNAL(src, COMSIG_ATOM_WELDER_ACT, user, I) + +///Analyzer act +/atom/proc/analyzer_act(mob/living/user, obj/item/I) + return SEND_SIGNAL(src, COMSIG_ATOM_ANALYSER_ACT, user, I) + +///Generate a tag for this atom +/atom/proc/GenerateTag() + return + +///Connect this atom to a shuttle +/atom/proc/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) + return + +/// Generic logging helper +/atom/proc/log_message(message, message_type, color=null, log_globally=TRUE) + if(!log_globally) + return + + var/log_text = "[key_name(src)] [message] [loc_name(src)]" + switch(message_type) + if(LOG_ATTACK) + log_attack(log_text) + if(LOG_SAY) + log_say(log_text) + if(LOG_WHISPER) + log_whisper(log_text) + if(LOG_EMOTE) + log_emote(log_text) + if(LOG_SUBTLER) //Wasp Edit + log_subtler(log_text) + if(LOG_DSAY) + log_dsay(log_text) + if(LOG_PDA) + log_pda(log_text) + if(LOG_CHAT) + log_chat(log_text) + if(LOG_COMMENT) + log_comment(log_text) + if(LOG_TELECOMMS) + log_telecomms(log_text) + if(LOG_ECON) + log_econ(log_text) + if(LOG_OOC) + log_ooc(log_text) + if(LOG_ADMIN) + log_admin(log_text) + if(LOG_ADMIN_PRIVATE) + log_admin_private(log_text) + if(LOG_ASAY) + log_adminsay(log_text) + if(LOG_OWNERSHIP) + log_game(log_text) + if(LOG_GAME) + log_game(log_text) + if(LOG_MECHA) + log_mecha(log_text) + if(LOG_SHUTTLE) + log_shuttle(log_text) + else + stack_trace("Invalid individual logging type: [message_type]. Defaulting to [LOG_GAME] (LOG_GAME).") + log_game(log_text) + +/// Helper for logging chat messages or other logs with arbitrary inputs (e.g. announcements) +/atom/proc/log_talk(message, message_type, tag=null, log_globally=TRUE, forced_by=null) + var/prefix = tag ? "([tag]) " : "" + var/suffix = forced_by ? " FORCED by [forced_by]" : "" + log_message("[prefix]\"[message]\"[suffix]", message_type, log_globally=log_globally) + +/// Helper for logging of messages with only one sender and receiver +/proc/log_directed_talk(atom/source, atom/target, message, message_type, tag) + if(!tag) + stack_trace("Unspecified tag for private message") + tag = "UNKNOWN" + + source.log_talk(message, message_type, tag="[tag] to [key_name(target)]") + if(source != target) + target.log_talk(message, message_type, tag="[tag] from [key_name(source)]", log_globally=FALSE) + +/** + * Log a combat message in the attack log + * + * Arguments: + * * atom/user - argument is the actor performing the action + * * atom/target - argument is the target of the action + * * what_done - is a verb describing the action (e.g. punched, throwed, kicked, etc.) + * * atom/object - is a tool with which the action was made (usually an item) + * * addition - is any additional text, which will be appended to the rest of the log line + */ +/proc/log_combat(atom/user, atom/target, what_done, atom/object=null, addition=null) + var/ssource = key_name(user) + var/starget = key_name(target) + + var/mob/living/living_target = target + var/hp = istype(living_target) ? " (NEWHP: [living_target.health]) " : "" + + var/sobject = "" + if(object) + sobject = " with [object]" + var/saddition = "" + if(addition) + saddition = " [addition]" + + var/postfix = "[sobject][saddition][hp]" + + var/message = "has [what_done] [starget][postfix]" + user.log_message(message, LOG_ATTACK, color="red") + + if(user != target) + var/reverse_message = "has been [what_done] by [ssource][postfix]" + target.log_message(reverse_message, LOG_ATTACK, color="orange", log_globally=FALSE) + +/atom/movable/proc/add_filter(name,priority,list/params) + LAZYINITLIST(filter_data) + var/list/p = params.Copy() + p["priority"] = priority + filter_data[name] = p + update_filters() + +/atom/movable/proc/update_filters() + filters = null + filter_data = sortTim(filter_data, /proc/cmp_filter_data_priority, TRUE) + for(var/f in filter_data) + var/list/data = filter_data[f] + var/list/arguments = data.Copy() + arguments -= "priority" + filters += filter(arglist(arguments)) + +/atom/movable/proc/get_filter(name) + if(filter_data && filter_data[name]) + return filters[filter_data.Find(name)] + +/atom/movable/proc/remove_filter(name) + if(filter_data && filter_data[name]) + filter_data -= name + update_filters() + +/atom/proc/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(list/materials, multiplier = 1) + + if(!materials) + materials = custom_materials + + 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 + + if(!length(materials)) + return + + custom_materials = list() //Reset the list + + for(var/x in materials) + var/datum/material/custom_material = SSmaterials.GetMaterialRef(x) + + if(!(material_flags & MATERIAL_NO_EFFECTS)) + custom_material.on_applied(src, materials[custom_material] * multiplier * material_modifier, material_flags) + custom_materials[custom_material] += materials[x] * multiplier + +/** + * Returns true if this atom has gravity for the passed in turf + * + * Sends signals [COMSIG_ATOM_HAS_GRAVITY] and [COMSIG_TURF_HAS_GRAVITY], both can force gravity with + * the forced gravity var + * + * Gravity situations: + * * No gravity if you're not in a turf + * * No gravity if this atom is in is a space turf + * * Gravity if the area it's in always has gravity + * * Gravity if there's a gravity generator on the z level + * * Gravity if the Z level has an SSMappingTrait for ZTRAIT_GRAVITY + * * otherwise no gravity + */ +/atom/proc/has_gravity(turf/T) + if(!T || !isturf(T)) + T = get_turf(src) + + if(!T) + return 0 + + var/list/forced_gravity = list() + SEND_SIGNAL(src, COMSIG_ATOM_HAS_GRAVITY, T, forced_gravity) + if(!forced_gravity.len) + SEND_SIGNAL(T, COMSIG_TURF_HAS_GRAVITY, src, forced_gravity) + if(forced_gravity.len) + var/max_grav + for(var/i in forced_gravity) + max_grav = max(max_grav, i) + return max_grav + + if(isspaceturf(T)) // Turf never has gravity + return FALSE + if(istype(T, /turf/open/openspace)) //openspace in a space area doesn't get gravity + if(istype(get_area(T), /area/space)) + return FALSE + + var/area/A = get_area(T) + if(A.has_gravity) // Areas which always has gravity + return A.has_gravity + else + // There's a gravity generator on our z level + if(GLOB.gravity_generators["[T.z]"]) + var/max_grav = 0 + for(var/obj/machinery/gravity_generator/main/G in GLOB.gravity_generators["[T.z]"]) + max_grav = max(G.setting,max_grav) + return max_grav + return SSmapping.level_trait(T.z, ZTRAIT_GRAVITY) + +/** + * Called when a mob examines (shift click or verb) this atom twice (or more) within EXAMINE_MORE_TIME (default 1.5 seconds) + * + * This is where you can put extra information on something that may be superfluous or not important in critical gameplay + * moments, while allowing people to manually double-examine to take a closer look + * + * Produces a signal [COMSIG_PARENT_EXAMINE_MORE] + */ +/atom/proc/examine_more(mob/user) + . = list() + SEND_SIGNAL(src, COMSIG_PARENT_EXAMINE_MORE, user, .) + if(!LAZYLEN(.)) // lol ..length + return list("You examine [src] closer, but find nothing of interest...") diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 04e8ca7802ea..c94cf3dce453 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -1,996 +1,996 @@ -/atom/movable - layer = OBJ_LAYER - var/last_move = null - var/last_move_time = 0 - var/anchored = FALSE - var/move_resist = MOVE_RESIST_DEFAULT - var/move_force = MOVE_FORCE_DEFAULT - var/pull_force = PULL_FORCE_DEFAULT - var/datum/thrownthing/throwing = null - var/throw_speed = 2 //How many tiles to move per ds when being thrown. Float values are fully supported - var/throw_range = 7 - var/mob/pulledby = null - var/initial_language_holder = /datum/language_holder - var/datum/language_holder/language_holder // Mindless mobs and objects need language too, some times. Mind holder takes prescedence. - var/verb_say = "says" - var/verb_ask = "asks" - var/verb_exclaim = "exclaims" - var/verb_whisper = "whispers" - var/verb_sing = "sings" - var/verb_yell = "yells" - var/speech_span - var/inertia_dir = 0 - var/atom/inertia_last_loc - var/inertia_moving = 0 - var/inertia_next_move = 0 - var/inertia_move_delay = 5 - var/pass_flags = NONE - /// If false makes [CanPass][/atom/proc/CanPass] call [CanPassThrough][/atom/movable/proc/CanPassThrough] on this type instead of using default behaviour - var/generic_canpass = TRUE - var/moving_diagonally = 0 //0: not doing a diagonal move. 1 and 2: doing the first/second step of the diagonal move - var/atom/movable/moving_from_pull //attempt to resume grab after moving instead of before. - var/list/client_mobs_in_contents // This contains all the client mobs within this container - var/list/acted_explosions //for explosion dodging - glide_size = 8 - appearance_flags = TILE_BOUND|PIXEL_SCALE - var/datum/forced_movement/force_moving = null //handled soley by forced_movement.dm - var/movement_type = GROUND //Incase you have multiple types, you automatically use the most useful one. IE: Skating on ice, flippers on water, flying over chasm/space, etc. - var/atom/movable/pulling - var/grab_state = 0 - var/throwforce = 0 - var/datum/component/orbiter/orbiting - var/can_be_z_moved = TRUE - - var/zfalling = FALSE - - ///Last location of the atom for demo recording purposes - var/atom/demo_last_loc - - /// Either FALSE, [EMISSIVE_BLOCK_GENERIC], or [EMISSIVE_BLOCK_UNIQUE] - var/blocks_emissive = FALSE - ///Internal holder for emissive blocker object, do not use directly use blocks_emissive - var/atom/movable/emissive_blocker/em_block - - -/atom/movable/Initialize(mapload) - . = ..() - switch(blocks_emissive) - if(EMISSIVE_BLOCK_GENERIC) - update_emissive_block() - if(EMISSIVE_BLOCK_UNIQUE) - render_target = ref(src) - em_block = new(src, render_target) - vis_contents += em_block - -/atom/movable/Destroy() - QDEL_NULL(em_block) - return ..() - -/atom/movable/proc/update_emissive_block() - if(blocks_emissive != EMISSIVE_BLOCK_GENERIC) - return - if(length(managed_vis_overlays)) - for(var/a in managed_vis_overlays) - var/obj/effect/overlay/vis/vs - if(vs.plane == EMISSIVE_BLOCKER_PLANE) - SSvis_overlays.remove_vis_overlay(src, list(vs)) - break - SSvis_overlays.add_vis_overlay(src, icon, icon_state, EMISSIVE_BLOCKER_LAYER, EMISSIVE_BLOCKER_PLANE, dir) - -/atom/movable/proc/can_zFall(turf/source, levels = 1, turf/target, direction) - if(!direction) - direction = DOWN - if(!source) - source = get_turf(src) - if(!source) - return FALSE - if(!target) - target = get_step_multiz(source, direction) - if(!target) - return FALSE - return !(movement_type & FLYING) && has_gravity(source) && !throwing - -/atom/movable/proc/onZImpact(turf/T, levels) - var/atom/highest = T - for(var/i in T.contents) - var/atom/A = i - if(!A.density) - continue - if(isobj(A) || ismob(A)) - if(A.layer > highest.layer) - highest = A - INVOKE_ASYNC(src, .proc/SpinAnimation, 5, 2) - throw_impact(highest) - return TRUE - -//For physical constraints to travelling up/down. -/atom/movable/proc/can_zTravel(turf/destination, direction) - var/turf/T = get_turf(src) - if(!T) - return FALSE - if(!direction) - if(!destination) - return FALSE - direction = get_dir(T, destination) - if(direction != UP && direction != DOWN) - return FALSE - if(!destination) - destination = get_step_multiz(src, direction) - if(!destination) - return FALSE - return T.zPassOut(src, direction, destination) && destination.zPassIn(src, direction, T) - -/atom/movable/vv_edit_var(var_name, var_value) - var/static/list/banned_edits = list("step_x", "step_y", "step_size", "bounds") - var/static/list/careful_edits = list("bound_x", "bound_y", "bound_width", "bound_height") - if(var_name in banned_edits) - return FALSE //PLEASE no. - if((var_name in careful_edits) && (var_value % world.icon_size) != 0) - return FALSE - switch(var_name) - if("x") - var/turf/T = locate(var_value, y, z) - if(T) - forceMove(T) - return TRUE - return FALSE - if("y") - var/turf/T = locate(x, var_value, z) - if(T) - forceMove(T) - return TRUE - return FALSE - if("z") - var/turf/T = locate(x, y, var_value) - if(T) - forceMove(T) - return TRUE - return FALSE - if("loc") - if(istype(var_value, /atom)) - forceMove(var_value) - return TRUE - else if(isnull(var_value)) - moveToNullspace() - return TRUE - return FALSE - return ..() - -/atom/movable/proc/start_pulling(atom/movable/AM, state, force = move_force, supress_message = FALSE) - if(QDELETED(AM)) - return FALSE - if(!(AM.can_be_pulled(src, state, force))) - return FALSE - - // If we're pulling something then drop what we're currently pulling and pull this instead. - if(pulling) - if(state == 0) - stop_pulling() - return FALSE - // Are we trying to pull something we are already pulling? Then enter grab cycle and end. - if(AM == pulling) - setGrabState(state) - if(istype(AM,/mob/living)) - var/mob/living/AMob = AM - AMob.grabbedby(src) - return TRUE - stop_pulling() - - SEND_SIGNAL(src, COMSIG_ATOM_START_PULL, AM, state, force) - - if(AM.pulledby) - log_combat(AM, AM.pulledby, "pulled from", src) - AM.pulledby.stop_pulling() //an object can't be pulled by two mobs at once. - pulling = AM - AM.pulledby = src - setGrabState(state) - if(ismob(AM)) - var/mob/M = AM - log_combat(src, M, "grabbed", addition="passive grab") - if(!supress_message) - M.visible_message("[src] grabs [M] passively.", \ - "[src] grabs you passively.") - return TRUE - -/atom/movable/proc/stop_pulling() - if(pulling) - pulling.pulledby = null - var/mob/living/ex_pulled = pulling - pulling = null - setGrabState(0) - if(isliving(ex_pulled)) - var/mob/living/L = ex_pulled - L.update_mobility()// mob gets up if it was lyng down in a chokehold - -/atom/movable/proc/Move_Pulled(atom/A) - if(!pulling) - return - if(pulling.anchored || pulling.move_resist > move_force || !pulling.Adjacent(src)) - stop_pulling() - return - if(isliving(pulling)) - var/mob/living/L = pulling - if(L.buckled && L.buckled.buckle_prevents_pull) //if they're buckled to something that disallows pulling, prevent it - stop_pulling() - return - if(A == loc && pulling.density) - return - if(!Process_Spacemove(get_dir(pulling.loc, A))) - return - step(pulling, get_dir(pulling.loc, A)) - return TRUE - -/mob/living/Move_Pulled(atom/A) - . = ..() - if(!. || !isliving(A)) - return - var/mob/living/L = A - set_pull_offsets(L, grab_state) - -/atom/movable/proc/check_pulling() - if(pulling) - var/atom/movable/pullee = pulling - if(pullee && get_dist(src, pullee) > 1) - stop_pulling() - return - if(!isturf(loc)) - stop_pulling() - return - if(pullee && !isturf(pullee.loc) && pullee.loc != loc) //to be removed once all code that changes an object's loc uses forceMove(). - log_game("DEBUG:[src]'s pull on [pullee] wasn't broken despite [pullee] being in [pullee.loc]. Pull stopped manually.") - stop_pulling() - return - if(pulling.anchored || pulling.move_resist > move_force) - stop_pulling() - return - if(pulledby && moving_diagonally != FIRST_DIAG_STEP && get_dist(src, pulledby) > 1) //separated from our puller and not in the middle of a diagonal move. - pulledby.stop_pulling() - -//////////////////////////////////////// -// Here's where we rewrite how byond handles movement except slightly different -// To be removed on step_ conversion -// All this work to prevent a second bump -/atom/movable/Move(atom/newloc, direct=0) - . = FALSE - if(!newloc || newloc == loc) - return - - if(!direct) - direct = get_dir(src, newloc) - setDir(direct) - - // Wasp start - multi tile object handling - if(bound_width != world.icon_size || bound_height != world.icon_size) - var/list/newlocs = isturf(newloc) ? block(locate(newloc.x+(-bound_x)/world.icon_size,newloc.y+(-bound_y)/world.icon_size,newloc.z),locate(newloc.x+(-bound_x+bound_width)/world.icon_size-1,newloc.y+(-bound_y+bound_height)/world.icon_size-1,newloc.z)) : list(newloc) - if(!newlocs) - return // we're trying to cross into the edge of space - var/bothturfs = isturf(newloc) && isturf(loc) - var/dx = bothturfs ? newloc.x - loc.x : 0 - var/dy = bothturfs ? newloc.y - loc.y : 0 - var/dz = bothturfs ? newloc.z - loc.z : 0 - for(var/atom/A in (locs - newlocs)) - if(!A.Exit(src, bothturfs ? locate(A.x+dx,A.y+dy,A.z+dz) : newloc)) - return - for(var/atom/A in (newlocs - locs)) - if(!A.Enter(src, bothturfs ? locate(A.x-dx,A.y-dy,A.z+dz) : loc)) - return - else - if(!loc.Exit(src, newloc)) - return - - if(!newloc.Enter(src, src.loc)) - return - // Wasp end - - if (SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_MOVE, newloc) & COMPONENT_MOVABLE_BLOCK_PRE_MOVE) - return - - // Past this is the point of no return - var/atom/oldloc = loc - var/area/oldarea = get_area(oldloc) - var/area/newarea = get_area(newloc) - loc = newloc - . = TRUE - oldloc.Exited(src, newloc) - if(oldarea != newarea) - oldarea.Exited(src, newloc) - - for(var/i in oldloc) - if(i == src) // Multi tile objects - continue - var/atom/movable/thing = i - thing.Uncrossed(src) - - newloc.Entered(src, oldloc) - if(oldarea != newarea) - newarea.Entered(src, oldloc) - - for(var/i in loc) - if(i == src) // Multi tile objects - continue - var/atom/movable/thing = i - thing.Crossed(src) - -//////////////////////////////////////// - -/atom/movable/Move(atom/newloc, direct) - var/atom/movable/pullee = pulling - var/turf/T = loc - if(!moving_from_pull) - check_pulling() - if(!loc || !newloc) - return FALSE - var/atom/oldloc = loc - - if(loc != newloc) - if (!(direct & (direct - 1))) //Cardinal move - . = ..() - else //Diagonal move, split it into cardinal moves - moving_diagonally = FIRST_DIAG_STEP - var/first_step_dir - // The `&& moving_diagonally` checks are so that a forceMove taking - // place due to a Crossed, Bumped, etc. call will interrupt - // the second half of the diagonal movement, or the second attempt - // at a first half if step() fails because we hit something. - if (direct & NORTH) - if (direct & EAST) - if (step(src, NORTH) && moving_diagonally) - first_step_dir = NORTH - moving_diagonally = SECOND_DIAG_STEP - . = step(src, EAST) - else if (moving_diagonally && step(src, EAST)) - first_step_dir = EAST - moving_diagonally = SECOND_DIAG_STEP - . = step(src, NORTH) - else if (direct & WEST) - if (step(src, NORTH) && moving_diagonally) - first_step_dir = NORTH - moving_diagonally = SECOND_DIAG_STEP - . = step(src, WEST) - else if (moving_diagonally && step(src, WEST)) - first_step_dir = WEST - moving_diagonally = SECOND_DIAG_STEP - . = step(src, NORTH) - else if (direct & SOUTH) - if (direct & EAST) - if (step(src, SOUTH) && moving_diagonally) - first_step_dir = SOUTH - moving_diagonally = SECOND_DIAG_STEP - . = step(src, EAST) - else if (moving_diagonally && step(src, EAST)) - first_step_dir = EAST - moving_diagonally = SECOND_DIAG_STEP - . = step(src, SOUTH) - else if (direct & WEST) - if (step(src, SOUTH) && moving_diagonally) - first_step_dir = SOUTH - moving_diagonally = SECOND_DIAG_STEP - . = step(src, WEST) - else if (moving_diagonally && step(src, WEST)) - first_step_dir = WEST - moving_diagonally = SECOND_DIAG_STEP - . = step(src, SOUTH) - if(moving_diagonally == SECOND_DIAG_STEP) - if(!.) - setDir(first_step_dir) - else if (!inertia_moving) - inertia_next_move = world.time + inertia_move_delay - newtonian_move(direct) - moving_diagonally = 0 - return - - if(!loc || (loc == oldloc && oldloc != newloc)) - last_move = 0 - return - - if(.) - Moved(oldloc, direct) - if(. && pulling && pulling == pullee && pulling != moving_from_pull) //we were pulling a thing and didn't lose it during our move. - if(pulling.anchored) - stop_pulling() - else - var/pull_dir = get_dir(src, pulling) - //puller and pullee more than one tile away or in diagonal position - if(get_dist(src, pulling) > 1 || (moving_diagonally != SECOND_DIAG_STEP && ((pull_dir - 1) & pull_dir))) - pulling.moving_from_pull = src - pulling.Move(T, get_dir(pulling, T)) //the pullee tries to reach our previous position - pulling.moving_from_pull = null - check_pulling() - - last_move = direct - setDir(direct) - if(. && has_buckled_mobs() && !handle_buckled_mob_movement(loc,direct)) //movement failed due to buckled mob(s) - return FALSE - -//Called after a successful Move(). By this point, we've already moved -/atom/movable/proc/Moved(atom/OldLoc, Dir, Forced = FALSE) - SHOULD_CALL_PARENT(TRUE) - SEND_SIGNAL(src, COMSIG_MOVABLE_MOVED, OldLoc, Dir, Forced) - if (!inertia_moving) - inertia_next_move = world.time + inertia_move_delay - newtonian_move(Dir) - if (length(client_mobs_in_contents)) - update_parallax_contents() - - return TRUE - -/atom/movable/Destroy(force) - QDEL_NULL(proximity_monitor) - QDEL_NULL(language_holder) - - unbuckle_all_mobs(force=1) - - . = ..() - if(loc) - //Restore air flow if we were blocking it (movables with ATMOS_PASS_PROC will need to do this manually if necessary) - if(((CanAtmosPass == ATMOS_PASS_DENSITY && density) || CanAtmosPass == ATMOS_PASS_NO) && isturf(loc)) - CanAtmosPass = ATMOS_PASS_YES - air_update_turf(TRUE) - loc.handle_atom_del(src) - for(var/atom/movable/AM in contents) - qdel(AM) - moveToNullspace() - invisibility = INVISIBILITY_ABSTRACT - if(pulledby) - pulledby.stop_pulling() - - if(orbiting) - orbiting.end_orbit(src) - orbiting = null - -// Make sure you know what you're doing if you call this, this is intended to only be called by byond directly. -// You probably want CanPass() -/atom/movable/Cross(atom/movable/AM) - . = TRUE - SEND_SIGNAL(src, COMSIG_MOVABLE_CROSS, AM) - return CanPass(AM, AM.loc, TRUE) - -//oldloc = old location on atom, inserted when forceMove is called and ONLY when forceMove is called! -/atom/movable/Crossed(atom/movable/AM, oldloc) - SHOULD_CALL_PARENT(TRUE) - . = ..() - SEND_SIGNAL(src, COMSIG_MOVABLE_CROSSED, AM) - -/atom/movable/Uncross(atom/movable/AM, atom/newloc) - . = ..() - if(SEND_SIGNAL(src, COMSIG_MOVABLE_UNCROSS, AM) & COMPONENT_MOVABLE_BLOCK_UNCROSS) - return FALSE - if(isturf(newloc) && !CheckExit(AM, newloc)) - return FALSE - -/atom/movable/Uncrossed(atom/movable/AM) - SEND_SIGNAL(src, COMSIG_MOVABLE_UNCROSSED, AM) - -/atom/movable/Bump(atom/A) - if(!A) - CRASH("Bump was called with no argument.") - SEND_SIGNAL(src, COMSIG_MOVABLE_BUMP, A) - . = ..() - if(!QDELETED(throwing)) - throwing.hit_atom(A) - . = TRUE - if(QDELETED(A)) - return - A.Bumped(src) - -/atom/movable/proc/forceMove(atom/destination) - . = FALSE - if(destination) - . = doMove(destination) - else - CRASH("No valid destination passed into forceMove") - -/atom/movable/proc/moveToNullspace() - return doMove(null) - -/atom/movable/proc/doMove(atom/destination) - . = FALSE - if(destination) - if(pulledby) - pulledby.stop_pulling() - var/atom/oldloc = loc - var/same_loc = oldloc == destination - var/area/old_area = get_area(oldloc) - var/area/destarea = get_area(destination) - - loc = destination - moving_diagonally = 0 - - if(!same_loc) - if(oldloc) - oldloc.Exited(src, destination) - if(old_area && old_area != destarea) - old_area.Exited(src, destination) - for(var/atom/movable/AM in oldloc) - AM.Uncrossed(src) - var/turf/oldturf = get_turf(oldloc) - var/turf/destturf = get_turf(destination) - var/old_z = (oldturf ? oldturf.z : null) - var/dest_z = (destturf ? destturf.z : null) - if (old_z != dest_z) - onTransitZ(old_z, dest_z) - destination.Entered(src, oldloc) - if(destarea && old_area != destarea) - destarea.Entered(src, oldloc) - - for(var/atom/movable/AM in destination) - if(AM == src) - continue - AM.Crossed(src, oldloc) - - Moved(oldloc, NONE, TRUE) - . = TRUE - - //If no destination, move the atom into nullspace (don't do this unless you know what you're doing) - else - . = TRUE - if (loc) - var/atom/oldloc = loc - var/area/old_area = get_area(oldloc) - oldloc.Exited(src, null) - if(old_area) - old_area.Exited(src, null) - loc = null - -/atom/movable/proc/onTransitZ(old_z,new_z) - SEND_SIGNAL(src, COMSIG_MOVABLE_Z_CHANGED, old_z, new_z) - for (var/item in src) // Notify contents of Z-transition. This can be overridden IF we know the items contents do not care. - var/atom/movable/AM = item - AM.onTransitZ(old_z,new_z) - -/atom/movable/proc/setMovetype(newval) - movement_type = newval - -/** - * Called whenever an object moves and by mobs when they attempt to move themselves through space - * And when an object or action applies a force on src, see [newtonian_move][/atom/movable/proc/newtonian_move] - * - * Return 0 to have src start/keep drifting in a no-grav area and 1 to stop/not start drifting - * - * Mobs should return 1 if they should be able to move of their own volition, see [/client/Move] - * - * Arguments: - * * movement_dir - 0 when stopping or any dir when trying to move - */ -/atom/movable/proc/Process_Spacemove(movement_dir = 0) - if(has_gravity(src)) - return 1 - - if(pulledby) - return 1 - - if(throwing) - return 1 - - if(!isturf(loc)) - return 1 - - if(locate(/obj/structure/lattice) in range(1, get_turf(src))) //Not realistic but makes pushing things in space easier - return 1 - - return 0 - - -/// Only moves the object if it's under no gravity -/atom/movable/proc/newtonian_move(direction) - if(!loc || Process_Spacemove(0)) - inertia_dir = 0 - return 0 - - inertia_dir = direction - if(!direction) - return 1 - inertia_last_loc = loc - SSspacedrift.processing[src] = src - return 1 - -/atom/movable/proc/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - set waitfor = 0 - var/hitpush = TRUE - var/impact_signal = SEND_SIGNAL(src, COMSIG_MOVABLE_IMPACT, hit_atom, throwingdatum) - if(impact_signal & COMPONENT_MOVABLE_IMPACT_FLIP_HITPUSH) - hitpush = FALSE // hacky, tie this to something else or a proper workaround later - - if(impact_signal & ~COMPONENT_MOVABLE_IMPACT_NEVERMIND) // in case a signal interceptor broke or deleted the thing before we could process our hit - return hit_atom.hitby(src, throwingdatum=throwingdatum, hitpush=hitpush) - -/atom/movable/hitby(atom/movable/AM, skipcatch, hitpush = TRUE, blocked, datum/thrownthing/throwingdatum) - if(!anchored && hitpush && (!throwingdatum || (throwingdatum.force >= (move_resist * MOVE_FORCE_PUSH_RATIO)))) - step(src, AM.dir) - ..() - -/atom/movable/proc/safe_throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG, gentle = FALSE) - if((force < (move_resist * MOVE_FORCE_THROW_RATIO)) || (move_resist == INFINITY)) - return - return throw_at(target, range, speed, thrower, spin, diagonals_first, callback, force, gentle) - -///If this returns FALSE then callback will not be called. -/atom/movable/proc/throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG, gentle = FALSE, quickstart = TRUE) - . = FALSE - if (!target || speed <= 0) - return - - if(SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_THROW, args) & COMPONENT_CANCEL_THROW) - return - - if (pulledby) - pulledby.stop_pulling() - - //They are moving! Wouldn't it be cool if we calculated their momentum and added it to the throw? - if (thrower && thrower.last_move && thrower.client && thrower.client.move_delay >= world.time + world.tick_lag*2) - var/user_momentum = thrower.cached_multiplicative_slowdown - if (!user_momentum) //no movement_delay, this means they move once per byond tick, lets calculate from that instead. - user_momentum = world.tick_lag - - user_momentum = 1 / user_momentum // convert from ds to the tiles per ds that throw_at uses. - - if (get_dir(thrower, target) & last_move) - user_momentum = user_momentum //basically a noop, but needed - else if (get_dir(target, thrower) & last_move) - user_momentum = -user_momentum //we are moving away from the target, lets slowdown the throw accordingly - else - user_momentum = 0 - - - if (user_momentum) - //first lets add that momentum to range. - range *= (user_momentum / speed) + 1 - //then lets add it to speed - speed += user_momentum - if (speed <= 0) - return//no throw speed, the user was moving too fast. - - . = TRUE // No failure conditions past this point. - - var/datum/thrownthing/TT = new() - TT.thrownthing = src - TT.target = target - TT.target_turf = get_turf(target) - TT.init_dir = get_dir(src, target) - TT.maxrange = range - TT.speed = speed - TT.thrower = thrower - TT.diagonals_first = diagonals_first - TT.force = force - TT.gentle = gentle - TT.callback = callback - if(!QDELETED(thrower)) - TT.target_zone = thrower.zone_selected - - var/dist_x = abs(target.x - src.x) - var/dist_y = abs(target.y - src.y) - var/dx = (target.x > src.x) ? EAST : WEST - var/dy = (target.y > src.y) ? NORTH : SOUTH - - if (dist_x == dist_y) - TT.pure_diagonal = 1 - - else if(dist_x <= dist_y) - var/olddist_x = dist_x - var/olddx = dx - dist_x = dist_y - dist_y = olddist_x - dx = dy - dy = olddx - TT.dist_x = dist_x - TT.dist_y = dist_y - TT.dx = dx - TT.dy = dy - TT.diagonal_error = dist_x/2 - dist_y - TT.start_time = world.time - - if(pulledby) - pulledby.stop_pulling() - - throwing = TT - if(spin) - SpinAnimation(5, 1) - - SEND_SIGNAL(src, COMSIG_MOVABLE_POST_THROW, TT, spin) - SSthrowing.processing[src] = TT - if (SSthrowing.state == SS_PAUSED && length(SSthrowing.currentrun)) - SSthrowing.currentrun[src] = TT - if (quickstart) - TT.tick() - -/atom/movable/proc/handle_buckled_mob_movement(newloc,direct) - for(var/m in buckled_mobs) - var/mob/living/buckled_mob = m - if(!buckled_mob.Move(newloc, direct)) - forceMove(buckled_mob.loc) - last_move = buckled_mob.last_move - inertia_dir = last_move - buckled_mob.inertia_dir = last_move - return 0 - return 1 - -/atom/movable/proc/force_pushed(atom/movable/pusher, force = MOVE_FORCE_DEFAULT, direction) - return FALSE - -/atom/movable/proc/force_push(atom/movable/AM, force = move_force, direction, silent = FALSE) - . = AM.force_pushed(src, force, direction) - if(!silent && .) - visible_message("[src] forcefully pushes against [AM]!", "You forcefully push against [AM]!") - -/atom/movable/proc/move_crush(atom/movable/AM, force = move_force, direction, silent = FALSE) - . = AM.move_crushed(src, force, direction) - if(!silent && .) - visible_message("[src] crushes past [AM]!", "You crush [AM]!") - -/atom/movable/proc/move_crushed(atom/movable/pusher, force = MOVE_FORCE_DEFAULT, direction) - return FALSE - -/atom/movable/CanAllowThrough(atom/movable/mover, turf/target) - . = ..() - if(mover in buckled_mobs) - return TRUE - -/// Returns true or false to allow src to move through the blocker, mover has final say -/atom/movable/proc/CanPassThrough(atom/blocker, turf/target, blocker_opinion) - SHOULD_CALL_PARENT(TRUE) - SHOULD_BE_PURE(TRUE) - return blocker_opinion - -/// called when this atom is removed from a storage item, which is passed on as S. The loc variable is already set to the new destination before this is called. -/atom/movable/proc/on_exit_storage(datum/component/storage/concrete/S) - return - -/// called when this atom is added into a storage item, which is passed on as S. The loc variable is already set to the storage item. -/atom/movable/proc/on_enter_storage(datum/component/storage/concrete/S) - return - -/atom/movable/proc/get_spacemove_backup() - var/atom/movable/dense_object_backup - for(var/A in orange(1, get_turf(src))) - if(isarea(A)) - continue - else if(isturf(A)) - var/turf/turf = A - if(!turf.density) - continue - return turf - else - var/atom/movable/AM = A - if(!AM.CanPass(src) || AM.density) - if(AM.anchored) - return AM - dense_object_backup = AM - break - . = dense_object_backup - -///called when a mob resists while inside a container that is itself inside something. -/atom/movable/proc/relay_container_resist(mob/living/user, obj/O) - return - - -/atom/movable/proc/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect) - if(!no_effect && (visual_effect_icon || used_item)) - do_item_attack_animation(A, visual_effect_icon, used_item) - - if(A == src) - return //don't do an animation if attacking self - var/pixel_x_diff = 0 - var/pixel_y_diff = 0 - - var/direction = get_dir(src, A) - if(direction & NORTH) - pixel_y_diff = 8 - else if(direction & SOUTH) - pixel_y_diff = -8 - - if(direction & EAST) - pixel_x_diff = 8 - else if(direction & WEST) - pixel_x_diff = -8 - - animate(src, pixel_x = pixel_x + pixel_x_diff, pixel_y = pixel_y + pixel_y_diff, time = 2) - animate(src, pixel_x = pixel_x - pixel_x_diff, pixel_y = pixel_y - pixel_y_diff, time = 2) - -/atom/movable/proc/do_item_attack_animation(atom/A, visual_effect_icon, obj/item/used_item) - var/image/I - if(visual_effect_icon) - I = image('icons/effects/effects.dmi', A, visual_effect_icon, A.layer + 0.1) - else if(used_item) - I = image(icon = used_item, loc = A, layer = A.layer + 0.1) - I.plane = GAME_PLANE - - // Scale the icon. - I.transform *= 0.75 - // The icon should not rotate. - I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA - - // Set the direction of the icon animation. - var/direction = get_dir(src, A) - if(direction & NORTH) - I.pixel_y = -16 - else if(direction & SOUTH) - I.pixel_y = 16 - - if(direction & EAST) - I.pixel_x = -16 - else if(direction & WEST) - I.pixel_x = 16 - - if(!direction) // Attacked self?! - I.pixel_z = 16 - - if(!I) - return - - flick_overlay(I, GLOB.clients, 5) // 5 ticks/half a second - - // And animate the attack! - animate(I, alpha = 175, pixel_x = 0, pixel_y = 0, pixel_z = 0, time = 3) - -/atom/movable/vv_get_dropdown() - . = ..() - . += "" - . += "" - -/atom/movable/proc/ex_check(ex_id) - if(!ex_id) - return TRUE - LAZYINITLIST(acted_explosions) - if(ex_id in acted_explosions) - return FALSE - acted_explosions += ex_id - return TRUE - -//TODO: Better floating -/atom/movable/proc/float(on) - if(throwing) - return - if(on && !(movement_type & FLOATING)) - animate(src, pixel_y = pixel_y + 2, time = 10, loop = -1) - sleep(10) - animate(src, pixel_y = pixel_y - 2, time = 10, loop = -1) - setMovetype(movement_type | FLOATING) - else if (!on && (movement_type & FLOATING)) - animate(src, pixel_y = initial(pixel_y), time = 10) - setMovetype(movement_type & ~FLOATING) - - -/* Language procs -* Unless you are doing something very specific, these are the ones you want to use. -*/ - -/// Gets or creates the relevant language holder. For mindless atoms, gets the local one. For atom with mind, gets the mind one. -/atom/movable/proc/get_language_holder(get_minds = TRUE) - if(!language_holder) - language_holder = new initial_language_holder(src) - return language_holder - -/// Grants the supplied language and sets omnitongue true. -/atom/movable/proc/grant_language(language, understood = TRUE, spoken = TRUE, source = LANGUAGE_ATOM) - var/datum/language_holder/LH = get_language_holder() - return LH.grant_language(language, understood, spoken, source) - -/// Grants every language. -/atom/movable/proc/grant_all_languages(understood = TRUE, spoken = TRUE, grant_omnitongue = TRUE, source = LANGUAGE_MIND) - var/datum/language_holder/LH = get_language_holder() - return LH.grant_all_languages(understood, spoken, grant_omnitongue, source) - -/// Removes a single language. -/atom/movable/proc/remove_language(language, understood = TRUE, spoken = TRUE, source = LANGUAGE_ALL) - var/datum/language_holder/LH = get_language_holder() - return LH.remove_language(language, understood, spoken, source) - -/// Removes every language and sets omnitongue false. -/atom/movable/proc/remove_all_languages(source = LANGUAGE_ALL, remove_omnitongue = FALSE) - var/datum/language_holder/LH = get_language_holder() - return LH.remove_all_languages(source, remove_omnitongue) - -/// Adds a language to the blocked language list. Use this over remove_language in cases where you will give languages back later. -/atom/movable/proc/add_blocked_language(language, source = LANGUAGE_ATOM) - var/datum/language_holder/LH = get_language_holder() - return LH.add_blocked_language(language, source) - -/// Removes a language from the blocked language list. -/atom/movable/proc/remove_blocked_language(language, source = LANGUAGE_ATOM) - var/datum/language_holder/LH = get_language_holder() - return LH.remove_blocked_language(language, source) - -/// Checks if atom has the language. If spoken is true, only checks if atom can speak the language. -/atom/movable/proc/has_language(language, spoken = FALSE) - var/datum/language_holder/LH = get_language_holder() - return LH.has_language(language, spoken) - -/// Checks if atom can speak the language. -/atom/movable/proc/can_speak_language(language) - var/datum/language_holder/LH = get_language_holder() - return LH.can_speak_language(language) - -/// Returns the result of tongue specific limitations on spoken languages. -/atom/movable/proc/could_speak_language(language) - return TRUE - -/// Returns selected language, if it can be spoken, or finds, sets and returns a new selected language if possible. -/atom/movable/proc/get_selected_language() - var/datum/language_holder/LH = get_language_holder() - return LH.get_selected_language() - -/// Gets a random understood language, useful for hallucinations and such. -/atom/movable/proc/get_random_understood_language() - var/datum/language_holder/LH = get_language_holder() - return LH.get_random_understood_language() - -/// Gets a random spoken language, useful for forced speech and such. -/atom/movable/proc/get_random_spoken_language() - var/datum/language_holder/LH = get_language_holder() - return LH.get_random_spoken_language() - -/// Copies all languages into the supplied atom/language holder. Source should be overridden when you -/// do not want the language overwritten by later atom updates or want to avoid blocked languages. -/atom/movable/proc/copy_languages(from_holder, source_override) - if(isatom(from_holder)) - var/atom/movable/thing = from_holder - from_holder = thing.get_language_holder() - var/datum/language_holder/LH = get_language_holder() - return LH.copy_languages(from_holder, source_override) - -/// Empties out the atom specific languages and updates them according to the current atoms language holder. -/// As a side effect, it also creates missing language holders in the process. -/atom/movable/proc/update_atom_languages() - var/datum/language_holder/LH = get_language_holder() - return LH.update_atom_languages(src) - -/* End language procs */ - - -/atom/movable/proc/ConveyorMove(movedir) - set waitfor = FALSE - if(!anchored && has_gravity()) - step(src, movedir) - -//Returns an atom's power cell, if it has one. Overload for individual items. -/atom/movable/proc/get_cell() - return - -/atom/movable/proc/can_be_pulled(user, grab_state, force) - if(src == user || !isturf(loc)) - return FALSE - if(anchored || throwing) - return FALSE - if(force < (move_resist * MOVE_FORCE_PULL_RATIO)) - return FALSE - return TRUE - -/** - * Updates the grab state of the movable - * - * This exists to act as a hook for behaviour - */ -/atom/movable/proc/setGrabState(newstate) - grab_state = newstate - -/obj/item/proc/do_pickup_animation(atom/target) - set waitfor = FALSE - if(!istype(loc, /turf)) - return - var/image/I = image(icon = src, loc = loc, layer = layer + 0.1) - I.plane = GAME_PLANE - I.transform *= 0.75 - I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA - var/turf/T = get_turf(src) - var/direction - var/to_x = 0 - var/to_y = 0 - - if(!QDELETED(T) && !QDELETED(target)) - direction = get_dir(T, target) - if(direction & NORTH) - to_y = 32 - else if(direction & SOUTH) - to_y = -32 - if(direction & EAST) - to_x = 32 - else if(direction & WEST) - to_x = -32 - if(!direction) - to_y = 16 - flick_overlay(I, GLOB.clients, 6) - var/matrix/M = new - M.Turn(pick(-30, 30)) - animate(I, alpha = 175, pixel_x = to_x, pixel_y = to_y, time = 3, transform = M, easing = CUBIC_EASING) - sleep(1) - animate(I, alpha = 0, transform = matrix(), time = 1) +/atom/movable + layer = OBJ_LAYER + var/last_move = null + var/last_move_time = 0 + var/anchored = FALSE + var/move_resist = MOVE_RESIST_DEFAULT + var/move_force = MOVE_FORCE_DEFAULT + var/pull_force = PULL_FORCE_DEFAULT + var/datum/thrownthing/throwing = null + var/throw_speed = 2 //How many tiles to move per ds when being thrown. Float values are fully supported + var/throw_range = 7 + var/mob/pulledby = null + var/initial_language_holder = /datum/language_holder + var/datum/language_holder/language_holder // Mindless mobs and objects need language too, some times. Mind holder takes prescedence. + var/verb_say = "says" + var/verb_ask = "asks" + var/verb_exclaim = "exclaims" + var/verb_whisper = "whispers" + var/verb_sing = "sings" + var/verb_yell = "yells" + var/speech_span + var/inertia_dir = 0 + var/atom/inertia_last_loc + var/inertia_moving = 0 + var/inertia_next_move = 0 + var/inertia_move_delay = 5 + var/pass_flags = NONE + /// If false makes [CanPass][/atom/proc/CanPass] call [CanPassThrough][/atom/movable/proc/CanPassThrough] on this type instead of using default behaviour + var/generic_canpass = TRUE + var/moving_diagonally = 0 //0: not doing a diagonal move. 1 and 2: doing the first/second step of the diagonal move + var/atom/movable/moving_from_pull //attempt to resume grab after moving instead of before. + var/list/client_mobs_in_contents // This contains all the client mobs within this container + var/list/acted_explosions //for explosion dodging + glide_size = 8 + appearance_flags = TILE_BOUND|PIXEL_SCALE + var/datum/forced_movement/force_moving = null //handled soley by forced_movement.dm + var/movement_type = GROUND //Incase you have multiple types, you automatically use the most useful one. IE: Skating on ice, flippers on water, flying over chasm/space, etc. + var/atom/movable/pulling + var/grab_state = 0 + var/throwforce = 0 + var/datum/component/orbiter/orbiting + var/can_be_z_moved = TRUE + + var/zfalling = FALSE + + ///Last location of the atom for demo recording purposes + var/atom/demo_last_loc + + /// Either FALSE, [EMISSIVE_BLOCK_GENERIC], or [EMISSIVE_BLOCK_UNIQUE] + var/blocks_emissive = FALSE + ///Internal holder for emissive blocker object, do not use directly use blocks_emissive + var/atom/movable/emissive_blocker/em_block + + +/atom/movable/Initialize(mapload) + . = ..() + switch(blocks_emissive) + if(EMISSIVE_BLOCK_GENERIC) + update_emissive_block() + if(EMISSIVE_BLOCK_UNIQUE) + render_target = ref(src) + em_block = new(src, render_target) + vis_contents += em_block + +/atom/movable/Destroy() + QDEL_NULL(em_block) + return ..() + +/atom/movable/proc/update_emissive_block() + if(blocks_emissive != EMISSIVE_BLOCK_GENERIC) + return + if(length(managed_vis_overlays)) + for(var/a in managed_vis_overlays) + var/obj/effect/overlay/vis/vs + if(vs.plane == EMISSIVE_BLOCKER_PLANE) + SSvis_overlays.remove_vis_overlay(src, list(vs)) + break + SSvis_overlays.add_vis_overlay(src, icon, icon_state, EMISSIVE_BLOCKER_LAYER, EMISSIVE_BLOCKER_PLANE, dir) + +/atom/movable/proc/can_zFall(turf/source, levels = 1, turf/target, direction) + if(!direction) + direction = DOWN + if(!source) + source = get_turf(src) + if(!source) + return FALSE + if(!target) + target = get_step_multiz(source, direction) + if(!target) + return FALSE + return !(movement_type & FLYING) && has_gravity(source) && !throwing + +/atom/movable/proc/onZImpact(turf/T, levels) + var/atom/highest = T + for(var/i in T.contents) + var/atom/A = i + if(!A.density) + continue + if(isobj(A) || ismob(A)) + if(A.layer > highest.layer) + highest = A + INVOKE_ASYNC(src, .proc/SpinAnimation, 5, 2) + throw_impact(highest) + return TRUE + +//For physical constraints to travelling up/down. +/atom/movable/proc/can_zTravel(turf/destination, direction) + var/turf/T = get_turf(src) + if(!T) + return FALSE + if(!direction) + if(!destination) + return FALSE + direction = get_dir(T, destination) + if(direction != UP && direction != DOWN) + return FALSE + if(!destination) + destination = get_step_multiz(src, direction) + if(!destination) + return FALSE + return T.zPassOut(src, direction, destination) && destination.zPassIn(src, direction, T) + +/atom/movable/vv_edit_var(var_name, var_value) + var/static/list/banned_edits = list("step_x", "step_y", "step_size", "bounds") + var/static/list/careful_edits = list("bound_x", "bound_y", "bound_width", "bound_height") + if(var_name in banned_edits) + return FALSE //PLEASE no. + if((var_name in careful_edits) && (var_value % world.icon_size) != 0) + return FALSE + switch(var_name) + if("x") + var/turf/T = locate(var_value, y, z) + if(T) + forceMove(T) + return TRUE + return FALSE + if("y") + var/turf/T = locate(x, var_value, z) + if(T) + forceMove(T) + return TRUE + return FALSE + if("z") + var/turf/T = locate(x, y, var_value) + if(T) + forceMove(T) + return TRUE + return FALSE + if("loc") + if(istype(var_value, /atom)) + forceMove(var_value) + return TRUE + else if(isnull(var_value)) + moveToNullspace() + return TRUE + return FALSE + return ..() + +/atom/movable/proc/start_pulling(atom/movable/AM, state, force = move_force, supress_message = FALSE) + if(QDELETED(AM)) + return FALSE + if(!(AM.can_be_pulled(src, state, force))) + return FALSE + + // If we're pulling something then drop what we're currently pulling and pull this instead. + if(pulling) + if(state == 0) + stop_pulling() + return FALSE + // Are we trying to pull something we are already pulling? Then enter grab cycle and end. + if(AM == pulling) + setGrabState(state) + if(istype(AM,/mob/living)) + var/mob/living/AMob = AM + AMob.grabbedby(src) + return TRUE + stop_pulling() + + SEND_SIGNAL(src, COMSIG_ATOM_START_PULL, AM, state, force) + + if(AM.pulledby) + log_combat(AM, AM.pulledby, "pulled from", src) + AM.pulledby.stop_pulling() //an object can't be pulled by two mobs at once. + pulling = AM + AM.pulledby = src + setGrabState(state) + if(ismob(AM)) + var/mob/M = AM + log_combat(src, M, "grabbed", addition="passive grab") + if(!supress_message) + M.visible_message("[src] grabs [M] passively.", \ + "[src] grabs you passively.") + return TRUE + +/atom/movable/proc/stop_pulling() + if(pulling) + pulling.pulledby = null + var/mob/living/ex_pulled = pulling + pulling = null + setGrabState(0) + if(isliving(ex_pulled)) + var/mob/living/L = ex_pulled + L.update_mobility()// mob gets up if it was lyng down in a chokehold + +/atom/movable/proc/Move_Pulled(atom/A) + if(!pulling) + return + if(pulling.anchored || pulling.move_resist > move_force || !pulling.Adjacent(src)) + stop_pulling() + return + if(isliving(pulling)) + var/mob/living/L = pulling + if(L.buckled && L.buckled.buckle_prevents_pull) //if they're buckled to something that disallows pulling, prevent it + stop_pulling() + return + if(A == loc && pulling.density) + return + if(!Process_Spacemove(get_dir(pulling.loc, A))) + return + step(pulling, get_dir(pulling.loc, A)) + return TRUE + +/mob/living/Move_Pulled(atom/A) + . = ..() + if(!. || !isliving(A)) + return + var/mob/living/L = A + set_pull_offsets(L, grab_state) + +/atom/movable/proc/check_pulling() + if(pulling) + var/atom/movable/pullee = pulling + if(pullee && get_dist(src, pullee) > 1) + stop_pulling() + return + if(!isturf(loc)) + stop_pulling() + return + if(pullee && !isturf(pullee.loc) && pullee.loc != loc) //to be removed once all code that changes an object's loc uses forceMove(). + log_game("DEBUG:[src]'s pull on [pullee] wasn't broken despite [pullee] being in [pullee.loc]. Pull stopped manually.") + stop_pulling() + return + if(pulling.anchored || pulling.move_resist > move_force) + stop_pulling() + return + if(pulledby && moving_diagonally != FIRST_DIAG_STEP && get_dist(src, pulledby) > 1) //separated from our puller and not in the middle of a diagonal move. + pulledby.stop_pulling() + +//////////////////////////////////////// +// Here's where we rewrite how byond handles movement except slightly different +// To be removed on step_ conversion +// All this work to prevent a second bump +/atom/movable/Move(atom/newloc, direct=0) + . = FALSE + if(!newloc || newloc == loc) + return + + if(!direct) + direct = get_dir(src, newloc) + setDir(direct) + + // Wasp start - multi tile object handling + if(bound_width != world.icon_size || bound_height != world.icon_size) + var/list/newlocs = isturf(newloc) ? block(locate(newloc.x+(-bound_x)/world.icon_size,newloc.y+(-bound_y)/world.icon_size,newloc.z),locate(newloc.x+(-bound_x+bound_width)/world.icon_size-1,newloc.y+(-bound_y+bound_height)/world.icon_size-1,newloc.z)) : list(newloc) + if(!newlocs) + return // we're trying to cross into the edge of space + var/bothturfs = isturf(newloc) && isturf(loc) + var/dx = bothturfs ? newloc.x - loc.x : 0 + var/dy = bothturfs ? newloc.y - loc.y : 0 + var/dz = bothturfs ? newloc.z - loc.z : 0 + for(var/atom/A in (locs - newlocs)) + if(!A.Exit(src, bothturfs ? locate(A.x+dx,A.y+dy,A.z+dz) : newloc)) + return + for(var/atom/A in (newlocs - locs)) + if(!A.Enter(src, bothturfs ? locate(A.x-dx,A.y-dy,A.z+dz) : loc)) + return + else + if(!loc.Exit(src, newloc)) + return + + if(!newloc.Enter(src, src.loc)) + return + // Wasp end + + if (SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_MOVE, newloc) & COMPONENT_MOVABLE_BLOCK_PRE_MOVE) + return + + // Past this is the point of no return + var/atom/oldloc = loc + var/area/oldarea = get_area(oldloc) + var/area/newarea = get_area(newloc) + loc = newloc + . = TRUE + oldloc.Exited(src, newloc) + if(oldarea != newarea) + oldarea.Exited(src, newloc) + + for(var/i in oldloc) + if(i == src) // Multi tile objects + continue + var/atom/movable/thing = i + thing.Uncrossed(src) + + newloc.Entered(src, oldloc) + if(oldarea != newarea) + newarea.Entered(src, oldloc) + + for(var/i in loc) + if(i == src) // Multi tile objects + continue + var/atom/movable/thing = i + thing.Crossed(src) + +//////////////////////////////////////// + +/atom/movable/Move(atom/newloc, direct) + var/atom/movable/pullee = pulling + var/turf/T = loc + if(!moving_from_pull) + check_pulling() + if(!loc || !newloc) + return FALSE + var/atom/oldloc = loc + + if(loc != newloc) + if (!(direct & (direct - 1))) //Cardinal move + . = ..() + else //Diagonal move, split it into cardinal moves + moving_diagonally = FIRST_DIAG_STEP + var/first_step_dir + // The `&& moving_diagonally` checks are so that a forceMove taking + // place due to a Crossed, Bumped, etc. call will interrupt + // the second half of the diagonal movement, or the second attempt + // at a first half if step() fails because we hit something. + if (direct & NORTH) + if (direct & EAST) + if (step(src, NORTH) && moving_diagonally) + first_step_dir = NORTH + moving_diagonally = SECOND_DIAG_STEP + . = step(src, EAST) + else if (moving_diagonally && step(src, EAST)) + first_step_dir = EAST + moving_diagonally = SECOND_DIAG_STEP + . = step(src, NORTH) + else if (direct & WEST) + if (step(src, NORTH) && moving_diagonally) + first_step_dir = NORTH + moving_diagonally = SECOND_DIAG_STEP + . = step(src, WEST) + else if (moving_diagonally && step(src, WEST)) + first_step_dir = WEST + moving_diagonally = SECOND_DIAG_STEP + . = step(src, NORTH) + else if (direct & SOUTH) + if (direct & EAST) + if (step(src, SOUTH) && moving_diagonally) + first_step_dir = SOUTH + moving_diagonally = SECOND_DIAG_STEP + . = step(src, EAST) + else if (moving_diagonally && step(src, EAST)) + first_step_dir = EAST + moving_diagonally = SECOND_DIAG_STEP + . = step(src, SOUTH) + else if (direct & WEST) + if (step(src, SOUTH) && moving_diagonally) + first_step_dir = SOUTH + moving_diagonally = SECOND_DIAG_STEP + . = step(src, WEST) + else if (moving_diagonally && step(src, WEST)) + first_step_dir = WEST + moving_diagonally = SECOND_DIAG_STEP + . = step(src, SOUTH) + if(moving_diagonally == SECOND_DIAG_STEP) + if(!.) + setDir(first_step_dir) + else if (!inertia_moving) + inertia_next_move = world.time + inertia_move_delay + newtonian_move(direct) + moving_diagonally = 0 + return + + if(!loc || (loc == oldloc && oldloc != newloc)) + last_move = 0 + return + + if(.) + Moved(oldloc, direct) + if(. && pulling && pulling == pullee && pulling != moving_from_pull) //we were pulling a thing and didn't lose it during our move. + if(pulling.anchored) + stop_pulling() + else + var/pull_dir = get_dir(src, pulling) + //puller and pullee more than one tile away or in diagonal position + if(get_dist(src, pulling) > 1 || (moving_diagonally != SECOND_DIAG_STEP && ((pull_dir - 1) & pull_dir))) + pulling.moving_from_pull = src + pulling.Move(T, get_dir(pulling, T)) //the pullee tries to reach our previous position + pulling.moving_from_pull = null + check_pulling() + + last_move = direct + setDir(direct) + if(. && has_buckled_mobs() && !handle_buckled_mob_movement(loc,direct)) //movement failed due to buckled mob(s) + return FALSE + +//Called after a successful Move(). By this point, we've already moved +/atom/movable/proc/Moved(atom/OldLoc, Dir, Forced = FALSE) + SHOULD_CALL_PARENT(TRUE) + SEND_SIGNAL(src, COMSIG_MOVABLE_MOVED, OldLoc, Dir, Forced) + if (!inertia_moving) + inertia_next_move = world.time + inertia_move_delay + newtonian_move(Dir) + if (length(client_mobs_in_contents)) + update_parallax_contents() + + return TRUE + +/atom/movable/Destroy(force) + QDEL_NULL(proximity_monitor) + QDEL_NULL(language_holder) + + unbuckle_all_mobs(force=1) + + . = ..() + if(loc) + //Restore air flow if we were blocking it (movables with ATMOS_PASS_PROC will need to do this manually if necessary) + if(((CanAtmosPass == ATMOS_PASS_DENSITY && density) || CanAtmosPass == ATMOS_PASS_NO) && isturf(loc)) + CanAtmosPass = ATMOS_PASS_YES + air_update_turf(TRUE) + loc.handle_atom_del(src) + for(var/atom/movable/AM in contents) + qdel(AM) + moveToNullspace() + invisibility = INVISIBILITY_ABSTRACT + if(pulledby) + pulledby.stop_pulling() + + if(orbiting) + orbiting.end_orbit(src) + orbiting = null + +// Make sure you know what you're doing if you call this, this is intended to only be called by byond directly. +// You probably want CanPass() +/atom/movable/Cross(atom/movable/AM) + . = TRUE + SEND_SIGNAL(src, COMSIG_MOVABLE_CROSS, AM) + return CanPass(AM, AM.loc, TRUE) + +//oldloc = old location on atom, inserted when forceMove is called and ONLY when forceMove is called! +/atom/movable/Crossed(atom/movable/AM, oldloc) + SHOULD_CALL_PARENT(TRUE) + . = ..() + SEND_SIGNAL(src, COMSIG_MOVABLE_CROSSED, AM) + +/atom/movable/Uncross(atom/movable/AM, atom/newloc) + . = ..() + if(SEND_SIGNAL(src, COMSIG_MOVABLE_UNCROSS, AM) & COMPONENT_MOVABLE_BLOCK_UNCROSS) + return FALSE + if(isturf(newloc) && !CheckExit(AM, newloc)) + return FALSE + +/atom/movable/Uncrossed(atom/movable/AM) + SEND_SIGNAL(src, COMSIG_MOVABLE_UNCROSSED, AM) + +/atom/movable/Bump(atom/A) + if(!A) + CRASH("Bump was called with no argument.") + SEND_SIGNAL(src, COMSIG_MOVABLE_BUMP, A) + . = ..() + if(!QDELETED(throwing)) + throwing.hit_atom(A) + . = TRUE + if(QDELETED(A)) + return + A.Bumped(src) + +/atom/movable/proc/forceMove(atom/destination) + . = FALSE + if(destination) + . = doMove(destination) + else + CRASH("No valid destination passed into forceMove") + +/atom/movable/proc/moveToNullspace() + return doMove(null) + +/atom/movable/proc/doMove(atom/destination) + . = FALSE + if(destination) + if(pulledby) + pulledby.stop_pulling() + var/atom/oldloc = loc + var/same_loc = oldloc == destination + var/area/old_area = get_area(oldloc) + var/area/destarea = get_area(destination) + + loc = destination + moving_diagonally = 0 + + if(!same_loc) + if(oldloc) + oldloc.Exited(src, destination) + if(old_area && old_area != destarea) + old_area.Exited(src, destination) + for(var/atom/movable/AM in oldloc) + AM.Uncrossed(src) + var/turf/oldturf = get_turf(oldloc) + var/turf/destturf = get_turf(destination) + var/old_z = (oldturf ? oldturf.z : null) + var/dest_z = (destturf ? destturf.z : null) + if (old_z != dest_z) + onTransitZ(old_z, dest_z) + destination.Entered(src, oldloc) + if(destarea && old_area != destarea) + destarea.Entered(src, oldloc) + + for(var/atom/movable/AM in destination) + if(AM == src) + continue + AM.Crossed(src, oldloc) + + Moved(oldloc, NONE, TRUE) + . = TRUE + + //If no destination, move the atom into nullspace (don't do this unless you know what you're doing) + else + . = TRUE + if (loc) + var/atom/oldloc = loc + var/area/old_area = get_area(oldloc) + oldloc.Exited(src, null) + if(old_area) + old_area.Exited(src, null) + loc = null + +/atom/movable/proc/onTransitZ(old_z,new_z) + SEND_SIGNAL(src, COMSIG_MOVABLE_Z_CHANGED, old_z, new_z) + for (var/item in src) // Notify contents of Z-transition. This can be overridden IF we know the items contents do not care. + var/atom/movable/AM = item + AM.onTransitZ(old_z,new_z) + +/atom/movable/proc/setMovetype(newval) + movement_type = newval + +/** + * Called whenever an object moves and by mobs when they attempt to move themselves through space + * And when an object or action applies a force on src, see [newtonian_move][/atom/movable/proc/newtonian_move] + * + * Return 0 to have src start/keep drifting in a no-grav area and 1 to stop/not start drifting + * + * Mobs should return 1 if they should be able to move of their own volition, see [/client/Move] + * + * Arguments: + * * movement_dir - 0 when stopping or any dir when trying to move + */ +/atom/movable/proc/Process_Spacemove(movement_dir = 0) + if(has_gravity(src)) + return 1 + + if(pulledby) + return 1 + + if(throwing) + return 1 + + if(!isturf(loc)) + return 1 + + if(locate(/obj/structure/lattice) in range(1, get_turf(src))) //Not realistic but makes pushing things in space easier + return 1 + + return 0 + + +/// Only moves the object if it's under no gravity +/atom/movable/proc/newtonian_move(direction) + if(!loc || Process_Spacemove(0)) + inertia_dir = 0 + return 0 + + inertia_dir = direction + if(!direction) + return 1 + inertia_last_loc = loc + SSspacedrift.processing[src] = src + return 1 + +/atom/movable/proc/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + set waitfor = 0 + var/hitpush = TRUE + var/impact_signal = SEND_SIGNAL(src, COMSIG_MOVABLE_IMPACT, hit_atom, throwingdatum) + if(impact_signal & COMPONENT_MOVABLE_IMPACT_FLIP_HITPUSH) + hitpush = FALSE // hacky, tie this to something else or a proper workaround later + + if(impact_signal & ~COMPONENT_MOVABLE_IMPACT_NEVERMIND) // in case a signal interceptor broke or deleted the thing before we could process our hit + return hit_atom.hitby(src, throwingdatum=throwingdatum, hitpush=hitpush) + +/atom/movable/hitby(atom/movable/AM, skipcatch, hitpush = TRUE, blocked, datum/thrownthing/throwingdatum) + if(!anchored && hitpush && (!throwingdatum || (throwingdatum.force >= (move_resist * MOVE_FORCE_PUSH_RATIO)))) + step(src, AM.dir) + ..() + +/atom/movable/proc/safe_throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG, gentle = FALSE) + if((force < (move_resist * MOVE_FORCE_THROW_RATIO)) || (move_resist == INFINITY)) + return + return throw_at(target, range, speed, thrower, spin, diagonals_first, callback, force, gentle) + +///If this returns FALSE then callback will not be called. +/atom/movable/proc/throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG, gentle = FALSE, quickstart = TRUE) + . = FALSE + if (!target || speed <= 0) + return + + if(SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_THROW, args) & COMPONENT_CANCEL_THROW) + return + + if (pulledby) + pulledby.stop_pulling() + + //They are moving! Wouldn't it be cool if we calculated their momentum and added it to the throw? + if (thrower && thrower.last_move && thrower.client && thrower.client.move_delay >= world.time + world.tick_lag*2) + var/user_momentum = thrower.cached_multiplicative_slowdown + if (!user_momentum) //no movement_delay, this means they move once per byond tick, lets calculate from that instead. + user_momentum = world.tick_lag + + user_momentum = 1 / user_momentum // convert from ds to the tiles per ds that throw_at uses. + + if (get_dir(thrower, target) & last_move) + user_momentum = user_momentum //basically a noop, but needed + else if (get_dir(target, thrower) & last_move) + user_momentum = -user_momentum //we are moving away from the target, lets slowdown the throw accordingly + else + user_momentum = 0 + + + if (user_momentum) + //first lets add that momentum to range. + range *= (user_momentum / speed) + 1 + //then lets add it to speed + speed += user_momentum + if (speed <= 0) + return//no throw speed, the user was moving too fast. + + . = TRUE // No failure conditions past this point. + + var/datum/thrownthing/TT = new() + TT.thrownthing = src + TT.target = target + TT.target_turf = get_turf(target) + TT.init_dir = get_dir(src, target) + TT.maxrange = range + TT.speed = speed + TT.thrower = thrower + TT.diagonals_first = diagonals_first + TT.force = force + TT.gentle = gentle + TT.callback = callback + if(!QDELETED(thrower)) + TT.target_zone = thrower.zone_selected + + var/dist_x = abs(target.x - src.x) + var/dist_y = abs(target.y - src.y) + var/dx = (target.x > src.x) ? EAST : WEST + var/dy = (target.y > src.y) ? NORTH : SOUTH + + if (dist_x == dist_y) + TT.pure_diagonal = 1 + + else if(dist_x <= dist_y) + var/olddist_x = dist_x + var/olddx = dx + dist_x = dist_y + dist_y = olddist_x + dx = dy + dy = olddx + TT.dist_x = dist_x + TT.dist_y = dist_y + TT.dx = dx + TT.dy = dy + TT.diagonal_error = dist_x/2 - dist_y + TT.start_time = world.time + + if(pulledby) + pulledby.stop_pulling() + + throwing = TT + if(spin) + SpinAnimation(5, 1) + + SEND_SIGNAL(src, COMSIG_MOVABLE_POST_THROW, TT, spin) + SSthrowing.processing[src] = TT + if (SSthrowing.state == SS_PAUSED && length(SSthrowing.currentrun)) + SSthrowing.currentrun[src] = TT + if (quickstart) + TT.tick() + +/atom/movable/proc/handle_buckled_mob_movement(newloc,direct) + for(var/m in buckled_mobs) + var/mob/living/buckled_mob = m + if(!buckled_mob.Move(newloc, direct)) + forceMove(buckled_mob.loc) + last_move = buckled_mob.last_move + inertia_dir = last_move + buckled_mob.inertia_dir = last_move + return 0 + return 1 + +/atom/movable/proc/force_pushed(atom/movable/pusher, force = MOVE_FORCE_DEFAULT, direction) + return FALSE + +/atom/movable/proc/force_push(atom/movable/AM, force = move_force, direction, silent = FALSE) + . = AM.force_pushed(src, force, direction) + if(!silent && .) + visible_message("[src] forcefully pushes against [AM]!", "You forcefully push against [AM]!") + +/atom/movable/proc/move_crush(atom/movable/AM, force = move_force, direction, silent = FALSE) + . = AM.move_crushed(src, force, direction) + if(!silent && .) + visible_message("[src] crushes past [AM]!", "You crush [AM]!") + +/atom/movable/proc/move_crushed(atom/movable/pusher, force = MOVE_FORCE_DEFAULT, direction) + return FALSE + +/atom/movable/CanAllowThrough(atom/movable/mover, turf/target) + . = ..() + if(mover in buckled_mobs) + return TRUE + +/// Returns true or false to allow src to move through the blocker, mover has final say +/atom/movable/proc/CanPassThrough(atom/blocker, turf/target, blocker_opinion) + SHOULD_CALL_PARENT(TRUE) + SHOULD_BE_PURE(TRUE) + return blocker_opinion + +/// called when this atom is removed from a storage item, which is passed on as S. The loc variable is already set to the new destination before this is called. +/atom/movable/proc/on_exit_storage(datum/component/storage/concrete/S) + return + +/// called when this atom is added into a storage item, which is passed on as S. The loc variable is already set to the storage item. +/atom/movable/proc/on_enter_storage(datum/component/storage/concrete/S) + return + +/atom/movable/proc/get_spacemove_backup() + var/atom/movable/dense_object_backup + for(var/A in orange(1, get_turf(src))) + if(isarea(A)) + continue + else if(isturf(A)) + var/turf/turf = A + if(!turf.density) + continue + return turf + else + var/atom/movable/AM = A + if(!AM.CanPass(src) || AM.density) + if(AM.anchored) + return AM + dense_object_backup = AM + break + . = dense_object_backup + +///called when a mob resists while inside a container that is itself inside something. +/atom/movable/proc/relay_container_resist(mob/living/user, obj/O) + return + + +/atom/movable/proc/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect) + if(!no_effect && (visual_effect_icon || used_item)) + do_item_attack_animation(A, visual_effect_icon, used_item) + + if(A == src) + return //don't do an animation if attacking self + var/pixel_x_diff = 0 + var/pixel_y_diff = 0 + + var/direction = get_dir(src, A) + if(direction & NORTH) + pixel_y_diff = 8 + else if(direction & SOUTH) + pixel_y_diff = -8 + + if(direction & EAST) + pixel_x_diff = 8 + else if(direction & WEST) + pixel_x_diff = -8 + + animate(src, pixel_x = pixel_x + pixel_x_diff, pixel_y = pixel_y + pixel_y_diff, time = 2) + animate(src, pixel_x = pixel_x - pixel_x_diff, pixel_y = pixel_y - pixel_y_diff, time = 2) + +/atom/movable/proc/do_item_attack_animation(atom/A, visual_effect_icon, obj/item/used_item) + var/image/I + if(visual_effect_icon) + I = image('icons/effects/effects.dmi', A, visual_effect_icon, A.layer + 0.1) + else if(used_item) + I = image(icon = used_item, loc = A, layer = A.layer + 0.1) + I.plane = GAME_PLANE + + // Scale the icon. + I.transform *= 0.75 + // The icon should not rotate. + I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA + + // Set the direction of the icon animation. + var/direction = get_dir(src, A) + if(direction & NORTH) + I.pixel_y = -16 + else if(direction & SOUTH) + I.pixel_y = 16 + + if(direction & EAST) + I.pixel_x = -16 + else if(direction & WEST) + I.pixel_x = 16 + + if(!direction) // Attacked self?! + I.pixel_z = 16 + + if(!I) + return + + flick_overlay(I, GLOB.clients, 5) // 5 ticks/half a second + + // And animate the attack! + animate(I, alpha = 175, pixel_x = 0, pixel_y = 0, pixel_z = 0, time = 3) + +/atom/movable/vv_get_dropdown() + . = ..() + . += "" + . += "" + +/atom/movable/proc/ex_check(ex_id) + if(!ex_id) + return TRUE + LAZYINITLIST(acted_explosions) + if(ex_id in acted_explosions) + return FALSE + acted_explosions += ex_id + return TRUE + +//TODO: Better floating +/atom/movable/proc/float(on) + if(throwing) + return + if(on && !(movement_type & FLOATING)) + animate(src, pixel_y = pixel_y + 2, time = 10, loop = -1) + sleep(10) + animate(src, pixel_y = pixel_y - 2, time = 10, loop = -1) + setMovetype(movement_type | FLOATING) + else if (!on && (movement_type & FLOATING)) + animate(src, pixel_y = initial(pixel_y), time = 10) + setMovetype(movement_type & ~FLOATING) + + +/* Language procs +* Unless you are doing something very specific, these are the ones you want to use. +*/ + +/// Gets or creates the relevant language holder. For mindless atoms, gets the local one. For atom with mind, gets the mind one. +/atom/movable/proc/get_language_holder(get_minds = TRUE) + if(!language_holder) + language_holder = new initial_language_holder(src) + return language_holder + +/// Grants the supplied language and sets omnitongue true. +/atom/movable/proc/grant_language(language, understood = TRUE, spoken = TRUE, source = LANGUAGE_ATOM) + var/datum/language_holder/LH = get_language_holder() + return LH.grant_language(language, understood, spoken, source) + +/// Grants every language. +/atom/movable/proc/grant_all_languages(understood = TRUE, spoken = TRUE, grant_omnitongue = TRUE, source = LANGUAGE_MIND) + var/datum/language_holder/LH = get_language_holder() + return LH.grant_all_languages(understood, spoken, grant_omnitongue, source) + +/// Removes a single language. +/atom/movable/proc/remove_language(language, understood = TRUE, spoken = TRUE, source = LANGUAGE_ALL) + var/datum/language_holder/LH = get_language_holder() + return LH.remove_language(language, understood, spoken, source) + +/// Removes every language and sets omnitongue false. +/atom/movable/proc/remove_all_languages(source = LANGUAGE_ALL, remove_omnitongue = FALSE) + var/datum/language_holder/LH = get_language_holder() + return LH.remove_all_languages(source, remove_omnitongue) + +/// Adds a language to the blocked language list. Use this over remove_language in cases where you will give languages back later. +/atom/movable/proc/add_blocked_language(language, source = LANGUAGE_ATOM) + var/datum/language_holder/LH = get_language_holder() + return LH.add_blocked_language(language, source) + +/// Removes a language from the blocked language list. +/atom/movable/proc/remove_blocked_language(language, source = LANGUAGE_ATOM) + var/datum/language_holder/LH = get_language_holder() + return LH.remove_blocked_language(language, source) + +/// Checks if atom has the language. If spoken is true, only checks if atom can speak the language. +/atom/movable/proc/has_language(language, spoken = FALSE) + var/datum/language_holder/LH = get_language_holder() + return LH.has_language(language, spoken) + +/// Checks if atom can speak the language. +/atom/movable/proc/can_speak_language(language) + var/datum/language_holder/LH = get_language_holder() + return LH.can_speak_language(language) + +/// Returns the result of tongue specific limitations on spoken languages. +/atom/movable/proc/could_speak_language(language) + return TRUE + +/// Returns selected language, if it can be spoken, or finds, sets and returns a new selected language if possible. +/atom/movable/proc/get_selected_language() + var/datum/language_holder/LH = get_language_holder() + return LH.get_selected_language() + +/// Gets a random understood language, useful for hallucinations and such. +/atom/movable/proc/get_random_understood_language() + var/datum/language_holder/LH = get_language_holder() + return LH.get_random_understood_language() + +/// Gets a random spoken language, useful for forced speech and such. +/atom/movable/proc/get_random_spoken_language() + var/datum/language_holder/LH = get_language_holder() + return LH.get_random_spoken_language() + +/// Copies all languages into the supplied atom/language holder. Source should be overridden when you +/// do not want the language overwritten by later atom updates or want to avoid blocked languages. +/atom/movable/proc/copy_languages(from_holder, source_override) + if(isatom(from_holder)) + var/atom/movable/thing = from_holder + from_holder = thing.get_language_holder() + var/datum/language_holder/LH = get_language_holder() + return LH.copy_languages(from_holder, source_override) + +/// Empties out the atom specific languages and updates them according to the current atoms language holder. +/// As a side effect, it also creates missing language holders in the process. +/atom/movable/proc/update_atom_languages() + var/datum/language_holder/LH = get_language_holder() + return LH.update_atom_languages(src) + +/* End language procs */ + + +/atom/movable/proc/ConveyorMove(movedir) + set waitfor = FALSE + if(!anchored && has_gravity()) + step(src, movedir) + +//Returns an atom's power cell, if it has one. Overload for individual items. +/atom/movable/proc/get_cell() + return + +/atom/movable/proc/can_be_pulled(user, grab_state, force) + if(src == user || !isturf(loc)) + return FALSE + if(anchored || throwing) + return FALSE + if(force < (move_resist * MOVE_FORCE_PULL_RATIO)) + return FALSE + return TRUE + +/** + * Updates the grab state of the movable + * + * This exists to act as a hook for behaviour + */ +/atom/movable/proc/setGrabState(newstate) + grab_state = newstate + +/obj/item/proc/do_pickup_animation(atom/target) + set waitfor = FALSE + if(!istype(loc, /turf)) + return + var/image/I = image(icon = src, loc = loc, layer = layer + 0.1) + I.plane = GAME_PLANE + I.transform *= 0.75 + I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA + var/turf/T = get_turf(src) + var/direction + var/to_x = 0 + var/to_y = 0 + + if(!QDELETED(T) && !QDELETED(target)) + direction = get_dir(T, target) + if(direction & NORTH) + to_y = 32 + else if(direction & SOUTH) + to_y = -32 + if(direction & EAST) + to_x = 32 + else if(direction & WEST) + to_x = -32 + if(!direction) + to_y = 16 + flick_overlay(I, GLOB.clients, 6) + var/matrix/M = new + M.Turn(pick(-30, 30)) + animate(I, alpha = 175, pixel_x = to_x, pixel_y = to_y, time = 3, transform = M, easing = CUBIC_EASING) + sleep(1) + animate(I, alpha = 0, transform = matrix(), time = 1) diff --git a/code/game/communications.dm b/code/game/communications.dm index ed470473d954..696b942434f7 100644 --- a/code/game/communications.dm +++ b/code/game/communications.dm @@ -1,199 +1,199 @@ -/* - HOW IT WORKS - - The SSradio is a global object maintaining all radio transmissions, think about it as about "ether". - Note that walkie-talkie, intercoms and headsets handle transmission using nonstandard way. - procs: - - add_object(obj/device as obj, var/new_frequency as num, var/filter as text|null = null) - Adds listening object. - parameters: - device - device receiving signals, must have proc receive_signal (see description below). - one device may listen several frequencies, but not same frequency twice. - new_frequency - see possibly frequencies below; - filter - thing for optimization. Optional, but recommended. - All filters should be consolidated in this file, see defines later. - Device without listening filter will receive all signals (on specified frequency). - Device with filter will receive any signals sent without filter. - Device with filter will not receive any signals sent with different filter. - returns: - Reference to frequency object. - - remove_object (obj/device, old_frequency) - Obliviously, after calling this proc, device will not receive any signals on old_frequency. - Other frequencies will left unaffected. - - return_frequency(var/frequency as num) - returns: - Reference to frequency object. Use it if you need to send and do not need to listen. - - radio_frequency is a global object maintaining list of devices that listening specific frequency. - procs: - - post_signal(obj/source as obj|null, datum/signal/signal, var/filter as text|null = null, var/range as num|null = null) - Sends signal to all devices that wants such signal. - parameters: - source - object, emitted signal. Usually, devices will not receive their own signals. - signal - see description below. - filter - described above. - range - radius of regular byond's square circle on that z-level. null means everywhere, on all z-levels. - - obj/proc/receive_signal(datum/signal/signal, var/receive_method as num, var/receive_param) - Handler from received signals. By default does nothing. Define your own for your object. - Avoid of sending signals directly from this proc, use spawn(0). Do not use sleep() here please. - parameters: - signal - see description below. Extract all needed data from the signal before doing sleep(), spawn() or return! - receive_method - may be TRANSMISSION_WIRE or TRANSMISSION_RADIO. - TRANSMISSION_WIRE is currently unused. - receive_param - for TRANSMISSION_RADIO here comes frequency. - - datum/signal - vars: - source - an object that emitted signal. Used for debug and bearing. - data - list with transmitting data. Usual use pattern: - data["msg"] = "hello world" - encryption - Some number symbolizing "encryption key". - Note that game actually do not use any cryptography here. - If receiving object don't know right key, it must ignore encrypted signal in its receive_signal. - -*/ -/* the radio controller is a confusing piece of shit and didnt work - so i made radios not use the radio controller. -*/ -GLOBAL_LIST_EMPTY(all_radios) -/proc/add_radio(obj/item/radio, freq) - if(!freq || !radio) - return - if(!GLOB.all_radios["[freq]"]) - GLOB.all_radios["[freq]"] = list(radio) - return freq - - GLOB.all_radios["[freq]"] |= radio - return freq - -/proc/remove_radio(obj/item/radio, freq) - if(!freq || !radio) - return - if(!GLOB.all_radios["[freq]"]) - return - - GLOB.all_radios["[freq]"] -= radio - -/proc/remove_radio_all(obj/item/radio) - for(var/freq in GLOB.all_radios) - GLOB.all_radios["[freq]"] -= radio - -// For information on what objects or departments use what frequencies, -// see __DEFINES/radio.dm. Mappers may also select additional frequencies for -// use in maps, such as in intercoms. - -GLOBAL_LIST_INIT(radiochannels, list( - RADIO_CHANNEL_COMMON = FREQ_COMMON, - RADIO_CHANNEL_SCIENCE = FREQ_SCIENCE, - RADIO_CHANNEL_COMMAND = FREQ_COMMAND, - RADIO_CHANNEL_MEDICAL = FREQ_MEDICAL, - RADIO_CHANNEL_ENGINEERING = FREQ_ENGINEERING, - RADIO_CHANNEL_SECURITY = FREQ_SECURITY, - RADIO_CHANNEL_CENTCOM = FREQ_CENTCOM, - RADIO_CHANNEL_SYNDICATE = FREQ_SYNDICATE, - RADIO_CHANNEL_SUPPLY = FREQ_SUPPLY, - RADIO_CHANNEL_SERVICE = FREQ_SERVICE, - RADIO_CHANNEL_AI_PRIVATE = FREQ_AI_PRIVATE, - RADIO_CHANNEL_CTF_RED = FREQ_CTF_RED, - RADIO_CHANNEL_CTF_BLUE = FREQ_CTF_BLUE -)) - -GLOBAL_LIST_INIT(reverseradiochannels, list( - "[FREQ_COMMON]" = RADIO_CHANNEL_COMMON, - "[FREQ_SCIENCE]" = RADIO_CHANNEL_SCIENCE, - "[FREQ_COMMAND]" = RADIO_CHANNEL_COMMAND, - "[FREQ_MEDICAL]" = RADIO_CHANNEL_MEDICAL, - "[FREQ_ENGINEERING]" = RADIO_CHANNEL_ENGINEERING, - "[FREQ_SECURITY]" = RADIO_CHANNEL_SECURITY, - "[FREQ_CENTCOM]" = RADIO_CHANNEL_CENTCOM, - "[FREQ_SYNDICATE]" = RADIO_CHANNEL_SYNDICATE, - "[FREQ_SUPPLY]" = RADIO_CHANNEL_SUPPLY, - "[FREQ_SERVICE]" = RADIO_CHANNEL_SERVICE, - "[FREQ_AI_PRIVATE]" = RADIO_CHANNEL_AI_PRIVATE, - "[FREQ_CTF_RED]" = RADIO_CHANNEL_CTF_RED, - "[FREQ_CTF_BLUE]" = RADIO_CHANNEL_CTF_BLUE -)) - -/datum/radio_frequency - var/frequency as num - var/list/list/obj/devices = list() - -/datum/radio_frequency/New(freq) - frequency = freq - -//If range > 0, only post to devices on the same z_level and within range -//Use range = -1, to restrain to the same z_level without limiting range -/datum/radio_frequency/proc/post_signal(obj/source as obj|null, datum/signal/signal, filter = null as text|null, range = null as num|null) - // Ensure the signal's data is fully filled - signal.source = source - signal.frequency = frequency - - //Apply filter to the signal. If none supply, broadcast to every devices - //_default channel is always checked - var/list/filter_list - - if(filter) - filter_list = list(filter,"_default") - else - filter_list = devices - - //If checking range, find the source turf - var/turf/start_point - if(range) - start_point = get_turf(source) - if(!start_point) - return 0 - - //Send the data - for(var/current_filter in filter_list) - for(var/obj/device in devices[current_filter]) - if(device == source) - continue - if(range) - var/turf/end_point = get_turf(device) - if(!end_point) - continue - if(start_point.z != end_point.z || (range > 0 && get_dist(start_point, end_point) > range)) - continue - device.receive_signal(signal) - -/datum/radio_frequency/proc/add_listener(obj/device, filter as text|null) - if (!filter) - filter = "_default" - - var/list/devices_line = devices[filter] - if(!devices_line) - devices[filter] = devices_line = list() - devices_line += device - - -/datum/radio_frequency/proc/remove_listener(obj/device) - for(var/devices_filter in devices) - var/list/devices_line = devices[devices_filter] - if(!devices_line) - devices -= devices_filter - devices_line -= device - if(!devices_line.len) - devices -= devices_filter - - -/obj/proc/receive_signal(datum/signal/signal) - return - -/datum/signal - var/obj/source - var/frequency = 0 - var/transmission_method - var/list/data - -/datum/signal/New(data, transmission_method = TRANSMISSION_RADIO) - src.data = data || list() - src.transmission_method = transmission_method +/* + HOW IT WORKS + + The SSradio is a global object maintaining all radio transmissions, think about it as about "ether". + Note that walkie-talkie, intercoms and headsets handle transmission using nonstandard way. + procs: + + add_object(obj/device as obj, var/new_frequency as num, var/filter as text|null = null) + Adds listening object. + parameters: + device - device receiving signals, must have proc receive_signal (see description below). + one device may listen several frequencies, but not same frequency twice. + new_frequency - see possibly frequencies below; + filter - thing for optimization. Optional, but recommended. + All filters should be consolidated in this file, see defines later. + Device without listening filter will receive all signals (on specified frequency). + Device with filter will receive any signals sent without filter. + Device with filter will not receive any signals sent with different filter. + returns: + Reference to frequency object. + + remove_object (obj/device, old_frequency) + Obliviously, after calling this proc, device will not receive any signals on old_frequency. + Other frequencies will left unaffected. + + return_frequency(var/frequency as num) + returns: + Reference to frequency object. Use it if you need to send and do not need to listen. + + radio_frequency is a global object maintaining list of devices that listening specific frequency. + procs: + + post_signal(obj/source as obj|null, datum/signal/signal, var/filter as text|null = null, var/range as num|null = null) + Sends signal to all devices that wants such signal. + parameters: + source - object, emitted signal. Usually, devices will not receive their own signals. + signal - see description below. + filter - described above. + range - radius of regular byond's square circle on that z-level. null means everywhere, on all z-levels. + + obj/proc/receive_signal(datum/signal/signal, var/receive_method as num, var/receive_param) + Handler from received signals. By default does nothing. Define your own for your object. + Avoid of sending signals directly from this proc, use spawn(0). Do not use sleep() here please. + parameters: + signal - see description below. Extract all needed data from the signal before doing sleep(), spawn() or return! + receive_method - may be TRANSMISSION_WIRE or TRANSMISSION_RADIO. + TRANSMISSION_WIRE is currently unused. + receive_param - for TRANSMISSION_RADIO here comes frequency. + + datum/signal + vars: + source + an object that emitted signal. Used for debug and bearing. + data + list with transmitting data. Usual use pattern: + data["msg"] = "hello world" + encryption + Some number symbolizing "encryption key". + Note that game actually do not use any cryptography here. + If receiving object don't know right key, it must ignore encrypted signal in its receive_signal. + +*/ +/* the radio controller is a confusing piece of shit and didnt work + so i made radios not use the radio controller. +*/ +GLOBAL_LIST_EMPTY(all_radios) +/proc/add_radio(obj/item/radio, freq) + if(!freq || !radio) + return + if(!GLOB.all_radios["[freq]"]) + GLOB.all_radios["[freq]"] = list(radio) + return freq + + GLOB.all_radios["[freq]"] |= radio + return freq + +/proc/remove_radio(obj/item/radio, freq) + if(!freq || !radio) + return + if(!GLOB.all_radios["[freq]"]) + return + + GLOB.all_radios["[freq]"] -= radio + +/proc/remove_radio_all(obj/item/radio) + for(var/freq in GLOB.all_radios) + GLOB.all_radios["[freq]"] -= radio + +// For information on what objects or departments use what frequencies, +// see __DEFINES/radio.dm. Mappers may also select additional frequencies for +// use in maps, such as in intercoms. + +GLOBAL_LIST_INIT(radiochannels, list( + RADIO_CHANNEL_COMMON = FREQ_COMMON, + RADIO_CHANNEL_SCIENCE = FREQ_SCIENCE, + RADIO_CHANNEL_COMMAND = FREQ_COMMAND, + RADIO_CHANNEL_MEDICAL = FREQ_MEDICAL, + RADIO_CHANNEL_ENGINEERING = FREQ_ENGINEERING, + RADIO_CHANNEL_SECURITY = FREQ_SECURITY, + RADIO_CHANNEL_CENTCOM = FREQ_CENTCOM, + RADIO_CHANNEL_SYNDICATE = FREQ_SYNDICATE, + RADIO_CHANNEL_SUPPLY = FREQ_SUPPLY, + RADIO_CHANNEL_SERVICE = FREQ_SERVICE, + RADIO_CHANNEL_AI_PRIVATE = FREQ_AI_PRIVATE, + RADIO_CHANNEL_CTF_RED = FREQ_CTF_RED, + RADIO_CHANNEL_CTF_BLUE = FREQ_CTF_BLUE +)) + +GLOBAL_LIST_INIT(reverseradiochannels, list( + "[FREQ_COMMON]" = RADIO_CHANNEL_COMMON, + "[FREQ_SCIENCE]" = RADIO_CHANNEL_SCIENCE, + "[FREQ_COMMAND]" = RADIO_CHANNEL_COMMAND, + "[FREQ_MEDICAL]" = RADIO_CHANNEL_MEDICAL, + "[FREQ_ENGINEERING]" = RADIO_CHANNEL_ENGINEERING, + "[FREQ_SECURITY]" = RADIO_CHANNEL_SECURITY, + "[FREQ_CENTCOM]" = RADIO_CHANNEL_CENTCOM, + "[FREQ_SYNDICATE]" = RADIO_CHANNEL_SYNDICATE, + "[FREQ_SUPPLY]" = RADIO_CHANNEL_SUPPLY, + "[FREQ_SERVICE]" = RADIO_CHANNEL_SERVICE, + "[FREQ_AI_PRIVATE]" = RADIO_CHANNEL_AI_PRIVATE, + "[FREQ_CTF_RED]" = RADIO_CHANNEL_CTF_RED, + "[FREQ_CTF_BLUE]" = RADIO_CHANNEL_CTF_BLUE +)) + +/datum/radio_frequency + var/frequency as num + var/list/list/obj/devices = list() + +/datum/radio_frequency/New(freq) + frequency = freq + +//If range > 0, only post to devices on the same z_level and within range +//Use range = -1, to restrain to the same z_level without limiting range +/datum/radio_frequency/proc/post_signal(obj/source as obj|null, datum/signal/signal, filter = null as text|null, range = null as num|null) + // Ensure the signal's data is fully filled + signal.source = source + signal.frequency = frequency + + //Apply filter to the signal. If none supply, broadcast to every devices + //_default channel is always checked + var/list/filter_list + + if(filter) + filter_list = list(filter,"_default") + else + filter_list = devices + + //If checking range, find the source turf + var/turf/start_point + if(range) + start_point = get_turf(source) + if(!start_point) + return 0 + + //Send the data + for(var/current_filter in filter_list) + for(var/obj/device in devices[current_filter]) + if(device == source) + continue + if(range) + var/turf/end_point = get_turf(device) + if(!end_point) + continue + if(start_point.z != end_point.z || (range > 0 && get_dist(start_point, end_point) > range)) + continue + device.receive_signal(signal) + +/datum/radio_frequency/proc/add_listener(obj/device, filter as text|null) + if (!filter) + filter = "_default" + + var/list/devices_line = devices[filter] + if(!devices_line) + devices[filter] = devices_line = list() + devices_line += device + + +/datum/radio_frequency/proc/remove_listener(obj/device) + for(var/devices_filter in devices) + var/list/devices_line = devices[devices_filter] + if(!devices_line) + devices -= devices_filter + devices_line -= device + if(!devices_line.len) + devices -= devices_filter + + +/obj/proc/receive_signal(datum/signal/signal) + return + +/datum/signal + var/obj/source + var/frequency = 0 + var/transmission_method + var/list/data + +/datum/signal/New(data, transmission_method = TRANSMISSION_RADIO) + src.data = data || list() + src.transmission_method = transmission_method diff --git a/code/game/gamemodes/brother/traitor_bro.dm b/code/game/gamemodes/brother/traitor_bro.dm index babddde01611..4b9499be7787 100644 --- a/code/game/gamemodes/brother/traitor_bro.dm +++ b/code/game/gamemodes/brother/traitor_bro.dm @@ -1,67 +1,67 @@ -/datum/game_mode - var/list/datum/mind/brothers = list() - var/list/datum/team/brother_team/brother_teams = list() - -/datum/game_mode/traitor/bros - name = "traitor+brothers" - config_tag = "traitorbro" - restricted_jobs = list("Prisoner","AI", "Cyborg") - - announce_span = "danger" - announce_text = "There are Syndicate agents and Blood Brothers on the station!\n\ - Traitors: Accomplish your objectives.\n\ - Blood Brothers: Accomplish your objectives.\n\ - Crew: Do not let the traitors or brothers succeed!" - - var/list/datum/team/brother_team/pre_brother_teams = list() - var/const/team_amount = 2 //hard limit on brother teams if scaling is turned off - var/const/min_team_size = 2 - traitors_required = FALSE //Only teams are possible - -/datum/game_mode/traitor/bros/pre_setup() - if(CONFIG_GET(flag/protect_roles_from_antagonist)) - restricted_jobs += protected_jobs - if(CONFIG_GET(flag/protect_assistant_from_antagonist)) - restricted_jobs += "Assistant" - - var/list/datum/mind/possible_brothers = get_players_for_role(ROLE_BROTHER) - - var/num_teams = team_amount - var/bsc = CONFIG_GET(number/brother_scaling_coeff) - if(bsc) - num_teams = max(1, round(num_players() / bsc)) - - for(var/j = 1 to num_teams) - if(possible_brothers.len < min_team_size || antag_candidates.len <= required_enemies) - break - var/datum/team/brother_team/team = new - var/team_size = prob(10) ? min(3, possible_brothers.len) : 2 - for(var/k = 1 to team_size) - var/datum/mind/bro = antag_pick(possible_brothers) - possible_brothers -= bro - antag_candidates -= bro - team.add_member(bro) - bro.special_role = "brother" - bro.restricted_roles = restricted_jobs - log_game("[key_name(bro)] has been selected as a Brother") - pre_brother_teams += team - . = ..() - if(.) //To ensure the game mode is going ahead - for(var/teams in pre_brother_teams) - for(var/antag in teams) - GLOB.pre_setup_antags += antag - return - -/datum/game_mode/traitor/bros/post_setup() - for(var/datum/team/brother_team/team in pre_brother_teams) - team.pick_meeting_area() - team.forge_brother_objectives() - for(var/datum/mind/M in team.members) - M.add_antag_datum(/datum/antagonist/brother, team) - GLOB.pre_setup_antags -= M - team.update_name() - brother_teams += pre_brother_teams - return ..() - -/datum/game_mode/traitor/bros/generate_report() - return "It's Syndicate recruiting season. Be alert for potential Syndicate infiltrators, but also watch out for disgruntled employees trying to defect. Unlike Nanotrasen, the Syndicate prides itself in teamwork and will only recruit pairs that share a brotherly trust." +/datum/game_mode + var/list/datum/mind/brothers = list() + var/list/datum/team/brother_team/brother_teams = list() + +/datum/game_mode/traitor/bros + name = "traitor+brothers" + config_tag = "traitorbro" + restricted_jobs = list("Prisoner","AI", "Cyborg") + + announce_span = "danger" + announce_text = "There are Syndicate agents and Blood Brothers on the station!\n\ + Traitors: Accomplish your objectives.\n\ + Blood Brothers: Accomplish your objectives.\n\ + Crew: Do not let the traitors or brothers succeed!" + + var/list/datum/team/brother_team/pre_brother_teams = list() + var/const/team_amount = 2 //hard limit on brother teams if scaling is turned off + var/const/min_team_size = 2 + traitors_required = FALSE //Only teams are possible + +/datum/game_mode/traitor/bros/pre_setup() + if(CONFIG_GET(flag/protect_roles_from_antagonist)) + restricted_jobs += protected_jobs + if(CONFIG_GET(flag/protect_assistant_from_antagonist)) + restricted_jobs += "Assistant" + + var/list/datum/mind/possible_brothers = get_players_for_role(ROLE_BROTHER) + + var/num_teams = team_amount + var/bsc = CONFIG_GET(number/brother_scaling_coeff) + if(bsc) + num_teams = max(1, round(num_players() / bsc)) + + for(var/j = 1 to num_teams) + if(possible_brothers.len < min_team_size || antag_candidates.len <= required_enemies) + break + var/datum/team/brother_team/team = new + var/team_size = prob(10) ? min(3, possible_brothers.len) : 2 + for(var/k = 1 to team_size) + var/datum/mind/bro = antag_pick(possible_brothers) + possible_brothers -= bro + antag_candidates -= bro + team.add_member(bro) + bro.special_role = "brother" + bro.restricted_roles = restricted_jobs + log_game("[key_name(bro)] has been selected as a Brother") + pre_brother_teams += team + . = ..() + if(.) //To ensure the game mode is going ahead + for(var/teams in pre_brother_teams) + for(var/antag in teams) + GLOB.pre_setup_antags += antag + return + +/datum/game_mode/traitor/bros/post_setup() + for(var/datum/team/brother_team/team in pre_brother_teams) + team.pick_meeting_area() + team.forge_brother_objectives() + for(var/datum/mind/M in team.members) + M.add_antag_datum(/datum/antagonist/brother, team) + GLOB.pre_setup_antags -= M + team.update_name() + brother_teams += pre_brother_teams + return ..() + +/datum/game_mode/traitor/bros/generate_report() + return "It's Syndicate recruiting season. Be alert for potential Syndicate infiltrators, but also watch out for disgruntled employees trying to defect. Unlike Nanotrasen, the Syndicate prides itself in teamwork and will only recruit pairs that share a brotherly trust." diff --git a/code/game/gamemodes/changeling/changeling.dm b/code/game/gamemodes/changeling/changeling.dm index 68356c7cc2d9..a441361cf375 100644 --- a/code/game/gamemodes/changeling/changeling.dm +++ b/code/game/gamemodes/changeling/changeling.dm @@ -1,160 +1,160 @@ -GLOBAL_LIST_INIT(possible_changeling_IDs, list("Alpha","Beta","Gamma","Delta","Epsilon","Zeta","Eta","Theta","Iota","Kappa","Lambda","Mu","Nu","Xi","Omicron","Pi","Rho","Sigma","Tau","Upsilon","Phi","Chi","Psi","Omega")) -GLOBAL_LIST_INIT(slots, list("head", "wear_mask", "back", "wear_suit", "w_uniform", "shoes", "belt", "gloves", "glasses", "ears", "wear_id", "s_store")) -GLOBAL_LIST_INIT(slot2slot, list("head" = ITEM_SLOT_HEAD, "wear_mask" = ITEM_SLOT_MASK, "neck" = ITEM_SLOT_NECK, "back" = ITEM_SLOT_BACK, "wear_suit" = ITEM_SLOT_OCLOTHING, "w_uniform" = ITEM_SLOT_ICLOTHING, "shoes" = ITEM_SLOT_FEET, "belt" = ITEM_SLOT_BELT, "gloves" = ITEM_SLOT_GLOVES, "glasses" = ITEM_SLOT_EYES, "ears" = ITEM_SLOT_EARS, "wear_id" = ITEM_SLOT_ID, "s_store" = ITEM_SLOT_SUITSTORE)) -GLOBAL_LIST_INIT(slot2type, list("head" = /obj/item/clothing/head/changeling, "wear_mask" = /obj/item/clothing/mask/changeling, "back" = /obj/item/changeling, "wear_suit" = /obj/item/clothing/suit/changeling, "w_uniform" = /obj/item/clothing/under/changeling, "shoes" = /obj/item/clothing/shoes/changeling, "belt" = /obj/item/changeling, "gloves" = /obj/item/clothing/gloves/changeling, "glasses" = /obj/item/clothing/glasses/changeling, "ears" = /obj/item/changeling, "wear_id" = /obj/item/changeling, "s_store" = /obj/item/changeling)) -GLOBAL_VAR(changeling_team_objective_type) //If this is not null, we hand our this objective to all lings - - -/datum/game_mode/changeling - name = "changeling" - config_tag = "changeling" - report_type = "changeling" - antag_flag = ROLE_CHANGELING - false_report_weight = 10 - restricted_jobs = list("AI", "Cyborg") - protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Brig Physician", "Lieutenant", "Prisoner") // Waspstation edit - Brig Physicians, Second Officer - required_players = 15 - required_enemies = 1 - recommended_enemies = 4 - reroll_friendly = 1 - - announce_span = "green" - announce_text = "Alien changelings have infiltrated the crew!\n\ - Changelings: Accomplish the objectives assigned to you.\n\ - Crew: Root out and eliminate the changeling menace." - - title_icon = "changeling" - - var/const/changeling_amount = 4 //hard limit on changelings if scaling is turned off - var/list/changelings = list() - -/datum/game_mode/changeling/pre_setup() - - if(CONFIG_GET(flag/protect_roles_from_antagonist)) - restricted_jobs += protected_jobs - - if(CONFIG_GET(flag/protect_assistant_from_antagonist)) - restricted_jobs += "Assistant" - - var/num_changelings = 1 - - var/csc = CONFIG_GET(number/changeling_scaling_coeff) - if(csc) - num_changelings = max(1, min(round(num_players() / (csc * 2)) + 2, round(num_players() / csc))) - else - num_changelings = max(1, min(num_players(), changeling_amount)) - - if(antag_candidates.len>0) - for(var/i = 0, i < num_changelings, i++) - if(!antag_candidates.len) - break - var/datum/mind/changeling = antag_pick(antag_candidates) - antag_candidates -= changeling - changelings += changeling - changeling.special_role = ROLE_CHANGELING - changeling.restricted_roles = restricted_jobs - GLOB.pre_setup_antags += changeling - return TRUE - else - setup_error = "Not enough changeling candidates" - return FALSE - -/datum/game_mode/changeling/post_setup() - //Decide if it's ok for the lings to have a team objective - //And then set it up to be handed out in forge_changeling_objectives - var/list/team_objectives = subtypesof(/datum/objective/changeling_team_objective) - var/list/possible_team_objectives = list() - for(var/T in team_objectives) - var/datum/objective/changeling_team_objective/CTO = T - - if(changelings.len >= initial(CTO.min_lings)) - possible_team_objectives += T - - if(possible_team_objectives.len && prob(20*changelings.len)) - GLOB.changeling_team_objective_type = pick(possible_team_objectives) - - for(var/datum/mind/changeling in changelings) - log_game("[key_name(changeling)] has been selected as a changeling") - var/datum/antagonist/changeling/new_antag = new() - new_antag.team_mode = TRUE - changeling.add_antag_datum(new_antag) - GLOB.pre_setup_antags -= changeling - ..() - -/datum/game_mode/changeling/make_antag_chance(mob/living/carbon/human/character) //Assigns changeling to latejoiners - var/csc = CONFIG_GET(number/changeling_scaling_coeff) - var/changelingcap = min(round(GLOB.joined_player_list.len / (csc * 2)) + 2, round(GLOB.joined_player_list.len / csc)) - if(changelings.len >= changelingcap) //Caps number of latejoin antagonists - return - if(changelings.len <= (changelingcap - 2) || prob(100 - (csc * 2))) - if(ROLE_CHANGELING in character.client.prefs.be_special) - if(!is_banned_from(character.ckey, list(ROLE_CHANGELING, ROLE_SYNDICATE)) && !QDELETED(character)) - if(age_check(character.client)) - if(!(character.job in restricted_jobs)) - character.mind.make_Changeling() - changelings += character.mind - -/datum/game_mode/changeling/generate_report() - return "The Gorlex Marauders have announced the successful raid and destruction of Central Command containment ship #S-[rand(1111, 9999)]. This ship housed only a single prisoner - \ - codenamed \"Thing\", and it was highly adaptive and extremely dangerous. We have reason to believe that the Thing has allied with the Syndicate, and you should note that likelihood \ - of the Thing being sent to a station in this sector is highly likely. It may be in the guise of any crew member. Trust nobody - suspect everybody. Do not announce this to the crew, \ - as paranoia may spread and inhibit workplace efficiency." - -/proc/changeling_transform(mob/living/carbon/human/user, datum/changelingprofile/chosen_prof) - var/datum/dna/chosen_dna = chosen_prof.dna - user.real_name = chosen_prof.name - user.underwear = chosen_prof.underwear - user.undershirt = chosen_prof.undershirt - user.socks = chosen_prof.socks - - chosen_dna.transfer_identity(user, 1) - user.updateappearance(mutcolor_update=1) - user.update_body() - user.domutcheck() - - //vars hackery. not pretty, but better than the alternative. - for(var/slot in GLOB.slots) - if(istype(user.vars[slot], GLOB.slot2type[slot]) && !(chosen_prof.exists_list[slot])) //remove unnecessary flesh items - qdel(user.vars[slot]) - continue - - if((user.vars[slot] && !istype(user.vars[slot], GLOB.slot2type[slot])) || !(chosen_prof.exists_list[slot])) - continue - - var/obj/item/C - var/equip = 0 - if(!user.vars[slot]) - var/thetype = GLOB.slot2type[slot] - equip = 1 - C = new thetype(user) - - else if(istype(user.vars[slot], GLOB.slot2type[slot])) - C = user.vars[slot] - - C.appearance = chosen_prof.appearance_list[slot] - C.name = chosen_prof.name_list[slot] - C.flags_cover = chosen_prof.flags_cover_list[slot] - C.item_state = chosen_prof.item_state_list[slot] - C.mob_overlay_icon = chosen_prof.mob_overlay_icon_list[slot] - if(equip) - user.equip_to_slot_or_del(C, GLOB.slot2slot[slot]) - - user.regenerate_icons() - - -/datum/game_mode/changeling/generate_credit_text() - var/list/round_credits = list() - var/len_before_addition - - round_credits += "

    The Slippery Changelings:

    " - len_before_addition = round_credits.len - for(var/datum/mind/M in changelings) - var/datum/antagonist/changeling/cling = M.has_antag_datum(/datum/antagonist/changeling) - if(cling) - round_credits += "

    [cling.changelingID] in the body of [M.name]

    " - if(len_before_addition == round_credits.len) - round_credits += list("

    Uh oh, we lost track of the shape shifters!

    ", "

    Nobody move!

    ") - round_credits += "
    " - - round_credits += ..() - return round_credits +GLOBAL_LIST_INIT(possible_changeling_IDs, list("Alpha","Beta","Gamma","Delta","Epsilon","Zeta","Eta","Theta","Iota","Kappa","Lambda","Mu","Nu","Xi","Omicron","Pi","Rho","Sigma","Tau","Upsilon","Phi","Chi","Psi","Omega")) +GLOBAL_LIST_INIT(slots, list("head", "wear_mask", "back", "wear_suit", "w_uniform", "shoes", "belt", "gloves", "glasses", "ears", "wear_id", "s_store")) +GLOBAL_LIST_INIT(slot2slot, list("head" = ITEM_SLOT_HEAD, "wear_mask" = ITEM_SLOT_MASK, "neck" = ITEM_SLOT_NECK, "back" = ITEM_SLOT_BACK, "wear_suit" = ITEM_SLOT_OCLOTHING, "w_uniform" = ITEM_SLOT_ICLOTHING, "shoes" = ITEM_SLOT_FEET, "belt" = ITEM_SLOT_BELT, "gloves" = ITEM_SLOT_GLOVES, "glasses" = ITEM_SLOT_EYES, "ears" = ITEM_SLOT_EARS, "wear_id" = ITEM_SLOT_ID, "s_store" = ITEM_SLOT_SUITSTORE)) +GLOBAL_LIST_INIT(slot2type, list("head" = /obj/item/clothing/head/changeling, "wear_mask" = /obj/item/clothing/mask/changeling, "back" = /obj/item/changeling, "wear_suit" = /obj/item/clothing/suit/changeling, "w_uniform" = /obj/item/clothing/under/changeling, "shoes" = /obj/item/clothing/shoes/changeling, "belt" = /obj/item/changeling, "gloves" = /obj/item/clothing/gloves/changeling, "glasses" = /obj/item/clothing/glasses/changeling, "ears" = /obj/item/changeling, "wear_id" = /obj/item/changeling, "s_store" = /obj/item/changeling)) +GLOBAL_VAR(changeling_team_objective_type) //If this is not null, we hand our this objective to all lings + + +/datum/game_mode/changeling + name = "changeling" + config_tag = "changeling" + report_type = "changeling" + antag_flag = ROLE_CHANGELING + false_report_weight = 10 + restricted_jobs = list("AI", "Cyborg") + protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Brig Physician", "Lieutenant", "Prisoner") // Waspstation edit - Brig Physicians, Second Officer + required_players = 15 + required_enemies = 1 + recommended_enemies = 4 + reroll_friendly = 1 + + announce_span = "green" + announce_text = "Alien changelings have infiltrated the crew!\n\ + Changelings: Accomplish the objectives assigned to you.\n\ + Crew: Root out and eliminate the changeling menace." + + title_icon = "changeling" + + var/const/changeling_amount = 4 //hard limit on changelings if scaling is turned off + var/list/changelings = list() + +/datum/game_mode/changeling/pre_setup() + + if(CONFIG_GET(flag/protect_roles_from_antagonist)) + restricted_jobs += protected_jobs + + if(CONFIG_GET(flag/protect_assistant_from_antagonist)) + restricted_jobs += "Assistant" + + var/num_changelings = 1 + + var/csc = CONFIG_GET(number/changeling_scaling_coeff) + if(csc) + num_changelings = max(1, min(round(num_players() / (csc * 2)) + 2, round(num_players() / csc))) + else + num_changelings = max(1, min(num_players(), changeling_amount)) + + if(antag_candidates.len>0) + for(var/i = 0, i < num_changelings, i++) + if(!antag_candidates.len) + break + var/datum/mind/changeling = antag_pick(antag_candidates) + antag_candidates -= changeling + changelings += changeling + changeling.special_role = ROLE_CHANGELING + changeling.restricted_roles = restricted_jobs + GLOB.pre_setup_antags += changeling + return TRUE + else + setup_error = "Not enough changeling candidates" + return FALSE + +/datum/game_mode/changeling/post_setup() + //Decide if it's ok for the lings to have a team objective + //And then set it up to be handed out in forge_changeling_objectives + var/list/team_objectives = subtypesof(/datum/objective/changeling_team_objective) + var/list/possible_team_objectives = list() + for(var/T in team_objectives) + var/datum/objective/changeling_team_objective/CTO = T + + if(changelings.len >= initial(CTO.min_lings)) + possible_team_objectives += T + + if(possible_team_objectives.len && prob(20*changelings.len)) + GLOB.changeling_team_objective_type = pick(possible_team_objectives) + + for(var/datum/mind/changeling in changelings) + log_game("[key_name(changeling)] has been selected as a changeling") + var/datum/antagonist/changeling/new_antag = new() + new_antag.team_mode = TRUE + changeling.add_antag_datum(new_antag) + GLOB.pre_setup_antags -= changeling + ..() + +/datum/game_mode/changeling/make_antag_chance(mob/living/carbon/human/character) //Assigns changeling to latejoiners + var/csc = CONFIG_GET(number/changeling_scaling_coeff) + var/changelingcap = min(round(GLOB.joined_player_list.len / (csc * 2)) + 2, round(GLOB.joined_player_list.len / csc)) + if(changelings.len >= changelingcap) //Caps number of latejoin antagonists + return + if(changelings.len <= (changelingcap - 2) || prob(100 - (csc * 2))) + if(ROLE_CHANGELING in character.client.prefs.be_special) + if(!is_banned_from(character.ckey, list(ROLE_CHANGELING, ROLE_SYNDICATE)) && !QDELETED(character)) + if(age_check(character.client)) + if(!(character.job in restricted_jobs)) + character.mind.make_Changeling() + changelings += character.mind + +/datum/game_mode/changeling/generate_report() + return "The Gorlex Marauders have announced the successful raid and destruction of Central Command containment ship #S-[rand(1111, 9999)]. This ship housed only a single prisoner - \ + codenamed \"Thing\", and it was highly adaptive and extremely dangerous. We have reason to believe that the Thing has allied with the Syndicate, and you should note that likelihood \ + of the Thing being sent to a station in this sector is highly likely. It may be in the guise of any crew member. Trust nobody - suspect everybody. Do not announce this to the crew, \ + as paranoia may spread and inhibit workplace efficiency." + +/proc/changeling_transform(mob/living/carbon/human/user, datum/changelingprofile/chosen_prof) + var/datum/dna/chosen_dna = chosen_prof.dna + user.real_name = chosen_prof.name + user.underwear = chosen_prof.underwear + user.undershirt = chosen_prof.undershirt + user.socks = chosen_prof.socks + + chosen_dna.transfer_identity(user, 1) + user.updateappearance(mutcolor_update=1) + user.update_body() + user.domutcheck() + + //vars hackery. not pretty, but better than the alternative. + for(var/slot in GLOB.slots) + if(istype(user.vars[slot], GLOB.slot2type[slot]) && !(chosen_prof.exists_list[slot])) //remove unnecessary flesh items + qdel(user.vars[slot]) + continue + + if((user.vars[slot] && !istype(user.vars[slot], GLOB.slot2type[slot])) || !(chosen_prof.exists_list[slot])) + continue + + var/obj/item/C + var/equip = 0 + if(!user.vars[slot]) + var/thetype = GLOB.slot2type[slot] + equip = 1 + C = new thetype(user) + + else if(istype(user.vars[slot], GLOB.slot2type[slot])) + C = user.vars[slot] + + C.appearance = chosen_prof.appearance_list[slot] + C.name = chosen_prof.name_list[slot] + C.flags_cover = chosen_prof.flags_cover_list[slot] + C.item_state = chosen_prof.item_state_list[slot] + C.mob_overlay_icon = chosen_prof.mob_overlay_icon_list[slot] + if(equip) + user.equip_to_slot_or_del(C, GLOB.slot2slot[slot]) + + user.regenerate_icons() + + +/datum/game_mode/changeling/generate_credit_text() + var/list/round_credits = list() + var/len_before_addition + + round_credits += "

    The Slippery Changelings:

    " + len_before_addition = round_credits.len + for(var/datum/mind/M in changelings) + var/datum/antagonist/changeling/cling = M.has_antag_datum(/datum/antagonist/changeling) + if(cling) + round_credits += "

    [cling.changelingID] in the body of [M.name]

    " + if(len_before_addition == round_credits.len) + round_credits += list("

    Uh oh, we lost track of the shape shifters!

    ", "

    Nobody move!

    ") + round_credits += "
    " + + round_credits += ..() + return round_credits diff --git a/code/game/gamemodes/changeling/traitor_chan.dm b/code/game/gamemodes/changeling/traitor_chan.dm index 745883428b35..e722505c2d31 100644 --- a/code/game/gamemodes/changeling/traitor_chan.dm +++ b/code/game/gamemodes/changeling/traitor_chan.dm @@ -1,88 +1,88 @@ -/datum/game_mode/traitor/changeling - name = "traitor+changeling" - config_tag = "traitorchan" - report_type = "traitorchan" - false_report_weight = 10 - traitors_possible = 3 //hard limit on traitors if scaling is turned off - restricted_jobs = list("Prisoner","AI", "Cyborg") - required_players = 25 - required_enemies = 1 // how many of each type are required - recommended_enemies = 3 - reroll_friendly = 1 - announce_span = "Traitors and Changelings" - announce_text = "There are alien creatures on the station along with some syndicate operatives out for their own gain! Do not let the changelings or the traitors succeed!" - - var/list/possible_changelings = list() - var/list/changelings = list() - var/const/changeling_amount = 1 //hard limit on changelings if scaling is turned off - -/datum/game_mode/traitor/changeling/can_start() - if(!..()) - return 0 - possible_changelings = get_players_for_role(ROLE_CHANGELING) - if(possible_changelings.len < required_enemies) - return 0 - return 1 - -/datum/game_mode/traitor/changeling/pre_setup() - if(CONFIG_GET(flag/protect_roles_from_antagonist)) - restricted_jobs += protected_jobs - - if(CONFIG_GET(flag/protect_assistant_from_antagonist)) - restricted_jobs += "Assistant" - - var/list/datum/mind/possible_changelings = get_players_for_role(ROLE_CHANGELING) - - var/num_changelings = 1 - - var/csc = CONFIG_GET(number/changeling_scaling_coeff) - if(csc) - num_changelings = max(1, min(round(num_players() / (csc * 4)) + 2, round(num_players() / (csc * 2)))) - else - num_changelings = max(1, min(num_players(), changeling_amount/2)) - - if(possible_changelings.len>0) - for(var/j = 0, j < num_changelings, j++) - if(!possible_changelings.len) - break - var/datum/mind/changeling = antag_pick(possible_changelings) - antag_candidates -= changeling - possible_changelings -= changeling - changeling.special_role = ROLE_CHANGELING - changelings += changeling - changeling.restricted_roles = restricted_jobs - . = ..() - if(.) //To ensure the game mode is going ahead - for(var/antag in changelings) - GLOB.pre_setup_antags += antag - return - else - return FALSE - -/datum/game_mode/traitor/changeling/post_setup() - for(var/datum/mind/changeling in changelings) - changeling.add_antag_datum(/datum/antagonist/changeling) - GLOB.pre_setup_antags -= changeling - return ..() - -/datum/game_mode/traitor/changeling/make_antag_chance(mob/living/carbon/human/character) //Assigns changeling to latejoiners - var/csc = CONFIG_GET(number/changeling_scaling_coeff) - var/changelingcap = min( round(GLOB.joined_player_list.len / (csc * 4)) + 2, round(GLOB.joined_player_list.len / (csc * 2))) - if(changelings.len >= changelingcap) //Caps number of latejoin antagonists - ..() - return - if(changelings.len <= (changelingcap - 2) || prob(100 / (csc * 4))) - if(ROLE_CHANGELING in character.client.prefs.be_special) - if(!is_banned_from(character.ckey, list(ROLE_CHANGELING, ROLE_SYNDICATE)) && !QDELETED(character)) - if(age_check(character.client)) - if(!(character.job in restricted_jobs)) - character.mind.make_Changeling() - changelings += character.mind - if(QDELETED(character)) - return - ..() - -/datum/game_mode/traitor/changeling/generate_report() - return "The Syndicate has started some experimental research regarding humanoid shapeshifting. There are rumors that this technology will be field tested on a Nanotrasen station \ - for infiltration purposes. Be advised that support personel may also be deployed to defend these shapeshifters. Trust nobody - suspect everybody. Do not announce this to the crew, \ - as paranoia may spread and inhibit workplace efficiency." +/datum/game_mode/traitor/changeling + name = "traitor+changeling" + config_tag = "traitorchan" + report_type = "traitorchan" + false_report_weight = 10 + traitors_possible = 3 //hard limit on traitors if scaling is turned off + restricted_jobs = list("Prisoner","AI", "Cyborg") + required_players = 25 + required_enemies = 1 // how many of each type are required + recommended_enemies = 3 + reroll_friendly = 1 + announce_span = "Traitors and Changelings" + announce_text = "There are alien creatures on the station along with some syndicate operatives out for their own gain! Do not let the changelings or the traitors succeed!" + + var/list/possible_changelings = list() + var/list/changelings = list() + var/const/changeling_amount = 1 //hard limit on changelings if scaling is turned off + +/datum/game_mode/traitor/changeling/can_start() + if(!..()) + return 0 + possible_changelings = get_players_for_role(ROLE_CHANGELING) + if(possible_changelings.len < required_enemies) + return 0 + return 1 + +/datum/game_mode/traitor/changeling/pre_setup() + if(CONFIG_GET(flag/protect_roles_from_antagonist)) + restricted_jobs += protected_jobs + + if(CONFIG_GET(flag/protect_assistant_from_antagonist)) + restricted_jobs += "Assistant" + + var/list/datum/mind/possible_changelings = get_players_for_role(ROLE_CHANGELING) + + var/num_changelings = 1 + + var/csc = CONFIG_GET(number/changeling_scaling_coeff) + if(csc) + num_changelings = max(1, min(round(num_players() / (csc * 4)) + 2, round(num_players() / (csc * 2)))) + else + num_changelings = max(1, min(num_players(), changeling_amount/2)) + + if(possible_changelings.len>0) + for(var/j = 0, j < num_changelings, j++) + if(!possible_changelings.len) + break + var/datum/mind/changeling = antag_pick(possible_changelings) + antag_candidates -= changeling + possible_changelings -= changeling + changeling.special_role = ROLE_CHANGELING + changelings += changeling + changeling.restricted_roles = restricted_jobs + . = ..() + if(.) //To ensure the game mode is going ahead + for(var/antag in changelings) + GLOB.pre_setup_antags += antag + return + else + return FALSE + +/datum/game_mode/traitor/changeling/post_setup() + for(var/datum/mind/changeling in changelings) + changeling.add_antag_datum(/datum/antagonist/changeling) + GLOB.pre_setup_antags -= changeling + return ..() + +/datum/game_mode/traitor/changeling/make_antag_chance(mob/living/carbon/human/character) //Assigns changeling to latejoiners + var/csc = CONFIG_GET(number/changeling_scaling_coeff) + var/changelingcap = min( round(GLOB.joined_player_list.len / (csc * 4)) + 2, round(GLOB.joined_player_list.len / (csc * 2))) + if(changelings.len >= changelingcap) //Caps number of latejoin antagonists + ..() + return + if(changelings.len <= (changelingcap - 2) || prob(100 / (csc * 4))) + if(ROLE_CHANGELING in character.client.prefs.be_special) + if(!is_banned_from(character.ckey, list(ROLE_CHANGELING, ROLE_SYNDICATE)) && !QDELETED(character)) + if(age_check(character.client)) + if(!(character.job in restricted_jobs)) + character.mind.make_Changeling() + changelings += character.mind + if(QDELETED(character)) + return + ..() + +/datum/game_mode/traitor/changeling/generate_report() + return "The Syndicate has started some experimental research regarding humanoid shapeshifting. There are rumors that this technology will be field tested on a Nanotrasen station \ + for infiltration purposes. Be advised that support personel may also be deployed to defend these shapeshifters. Trust nobody - suspect everybody. Do not announce this to the crew, \ + as paranoia may spread and inhibit workplace efficiency." diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm index ab7b54e05c54..de1eb7b46760 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm @@ -297,7 +297,7 @@ return TRUE /datum/dynamic_ruleset/roundstart/nuclear/round_result() - var result = nuke_team.get_result() + var/result = nuke_team.get_result() switch(result) if(NUKE_RESULT_FLUKE) SSticker.mode_result = "loss - syndicate nuked - disk secured" diff --git a/code/game/gamemodes/events.dm b/code/game/gamemodes/events.dm index ef781c5ee364..8a7181aaa3dd 100644 --- a/code/game/gamemodes/events.dm +++ b/code/game/gamemodes/events.dm @@ -1,66 +1,66 @@ -/proc/power_failure() - priority_announce("Abnormal activity detected in [station_name()]'s powernet. As a precautionary measure, the station's power will be shut off for an indeterminate duration.", "Critical Power Failure", 'sound/ai/poweroff.ogg') - for(var/obj/machinery/power/smes/S in GLOB.machines) - if(istype(get_area(S), /area/ai_monitored/turret_protected) || !is_station_level(S.z)) - continue - S.charge = 0 - S.output_level = 0 - S.output_attempt = FALSE - S.update_icon() - S.power_change() - - for(var/area/A in GLOB.the_station_areas) - if(!A.requires_power || A.always_unpowered ) - continue - if(GLOB.typecache_powerfailure_safe_areas[A.type]) - continue - - A.power_light = FALSE - A.power_equip = FALSE - A.power_environ = FALSE - A.power_change() - - for(var/obj/machinery/power/apc/C in GLOB.apcs_list) - if(C.cell && is_station_level(C.z)) - var/area/A = C.area - if(GLOB.typecache_powerfailure_safe_areas[A.type]) - continue - - C.cell.charge = 0 - -/proc/power_restore() - - priority_announce("Power has been restored to [station_name()]. We apologize for the inconvenience.", "Power Systems Nominal", 'sound/ai/poweron.ogg') - for(var/obj/machinery/power/apc/C in GLOB.machines) - if(C.cell && is_station_level(C.z)) - C.cell.charge = C.cell.maxcharge - C.failure_timer = 0 - for(var/obj/machinery/power/smes/S in GLOB.machines) - if(!is_station_level(S.z)) - continue - S.charge = S.capacity - S.output_level = S.output_level_max - S.output_attempt = TRUE - S.update_icon() - S.power_change() - for(var/area/A in GLOB.the_station_areas) - if(!A.requires_power || A.always_unpowered) - continue - if(!istype(A, /area/shuttle)) - A.power_light = TRUE - A.power_equip = TRUE - A.power_environ = TRUE - A.power_change() - -/proc/power_restore_quick() - - priority_announce("All SMESs on [station_name()] have been recharged. We apologize for the inconvenience.", "Power Systems Nominal", 'sound/ai/poweron.ogg') - for(var/obj/machinery/power/smes/S in GLOB.machines) - if(!is_station_level(S.z)) - continue - S.charge = S.capacity - S.output_level = S.output_level_max - S.output_attempt = TRUE - S.update_icon() - S.power_change() - +/proc/power_failure() + priority_announce("Abnormal activity detected in [station_name()]'s powernet. As a precautionary measure, the station's power will be shut off for an indeterminate duration.", "Critical Power Failure", 'sound/ai/poweroff.ogg') + for(var/obj/machinery/power/smes/S in GLOB.machines) + if(istype(get_area(S), /area/ai_monitored/turret_protected) || !is_station_level(S.z)) + continue + S.charge = 0 + S.output_level = 0 + S.output_attempt = FALSE + S.update_icon() + S.power_change() + + for(var/area/A in GLOB.the_station_areas) + if(!A.requires_power || A.always_unpowered ) + continue + if(GLOB.typecache_powerfailure_safe_areas[A.type]) + continue + + A.power_light = FALSE + A.power_equip = FALSE + A.power_environ = FALSE + A.power_change() + + for(var/obj/machinery/power/apc/C in GLOB.apcs_list) + if(C.cell && is_station_level(C.z)) + var/area/A = C.area + if(GLOB.typecache_powerfailure_safe_areas[A.type]) + continue + + C.cell.charge = 0 + +/proc/power_restore() + + priority_announce("Power has been restored to [station_name()]. We apologize for the inconvenience.", "Power Systems Nominal", 'sound/ai/poweron.ogg') + for(var/obj/machinery/power/apc/C in GLOB.machines) + if(C.cell && is_station_level(C.z)) + C.cell.charge = C.cell.maxcharge + C.failure_timer = 0 + for(var/obj/machinery/power/smes/S in GLOB.machines) + if(!is_station_level(S.z)) + continue + S.charge = S.capacity + S.output_level = S.output_level_max + S.output_attempt = TRUE + S.update_icon() + S.power_change() + for(var/area/A in GLOB.the_station_areas) + if(!A.requires_power || A.always_unpowered) + continue + if(!istype(A, /area/shuttle)) + A.power_light = TRUE + A.power_equip = TRUE + A.power_environ = TRUE + A.power_change() + +/proc/power_restore_quick() + + priority_announce("All SMESs on [station_name()] have been recharged. We apologize for the inconvenience.", "Power Systems Nominal", 'sound/ai/poweron.ogg') + for(var/obj/machinery/power/smes/S in GLOB.machines) + if(!is_station_level(S.z)) + continue + S.charge = S.capacity + S.output_level = S.output_level_max + S.output_attempt = TRUE + S.update_icon() + S.power_change() + diff --git a/code/game/gamemodes/extended/extended.dm b/code/game/gamemodes/extended/extended.dm index ebbef29dbbb8..67afcf0e0fe2 100644 --- a/code/game/gamemodes/extended/extended.dm +++ b/code/game/gamemodes/extended/extended.dm @@ -1,31 +1,31 @@ -/datum/game_mode/extended - name = "secret extended" - config_tag = "secret_extended" - report_type = "extended" - false_report_weight = 5 - required_players = 0 - - announce_span = "notice" - announce_text = "Just have fun and enjoy the game!" - - title_icon = "extended_white" - -/datum/game_mode/extended/pre_setup() - return 1 - -/datum/game_mode/extended/generate_report() - return "The transmission mostly failed to mention your sector. It is possible that there is nothing in the Syndicate that could threaten your station during this shift." - -/datum/game_mode/extended/announced - name = "extended" - config_tag = "extended" - false_report_weight = 0 - -/datum/game_mode/extended/announced/generate_station_goals() - for(var/T in subtypesof(/datum/station_goal)) - var/datum/station_goal/G = new T - station_goals += G - G.on_report() - -/datum/game_mode/extended/announced/send_intercept(report = 0) - priority_announce("Thanks to the tireless efforts of our security and intelligence divisions, there are currently no credible threats to [station_name()]. All station construction projects have been authorized. Have a secure shift!", "Security Report", 'sound/ai/commandreport.ogg') +/datum/game_mode/extended + name = "secret extended" + config_tag = "secret_extended" + report_type = "extended" + false_report_weight = 5 + required_players = 0 + + announce_span = "notice" + announce_text = "Just have fun and enjoy the game!" + + title_icon = "extended_white" + +/datum/game_mode/extended/pre_setup() + return 1 + +/datum/game_mode/extended/generate_report() + return "The transmission mostly failed to mention your sector. It is possible that there is nothing in the Syndicate that could threaten your station during this shift." + +/datum/game_mode/extended/announced + name = "extended" + config_tag = "extended" + false_report_weight = 0 + +/datum/game_mode/extended/announced/generate_station_goals() + for(var/T in subtypesof(/datum/station_goal)) + var/datum/station_goal/G = new T + station_goals += G + G.on_report() + +/datum/game_mode/extended/announced/send_intercept(report = 0) + priority_announce("Thanks to the tireless efforts of our security and intelligence divisions, there are currently no credible threats to [station_name()]. All station construction projects have been authorized. Have a secure shift!", "Security Report", 'sound/ai/commandreport.ogg') diff --git a/code/game/gamemodes/game_mode.dm b/code/game/gamemodes/game_mode.dm index aa2720cfa04f..619ad2ebe71e 100644 --- a/code/game/gamemodes/game_mode.dm +++ b/code/game/gamemodes/game_mode.dm @@ -1,729 +1,736 @@ -/* - * GAMEMODES (by Rastaf0) - * - * In the new mode system all special roles are fully supported. - * You can have proper wizards/traitors/changelings/cultists during any mode. - * Only two things really depends on gamemode: - * 1. Starting roles, equipment and preparations - * 2. Conditions of finishing the round. - * - */ - - -/datum/game_mode - var/name = "invalid" - var/config_tag = null - var/votable = 1 - var/probability = 0 - var/false_report_weight = 0 //How often will this show up incorrectly in a centcom report? - var/report_type = "invalid" //gamemodes with the same report type will not show up in the command report together. - var/station_was_nuked = 0 //see nuclearbomb.dm and malfunction.dm - var/nuke_off_station = 0 //Used for tracking where the nuke hit - var/round_ends_with_antag_death = 0 //flags the "one verse the station" antags as such - var/list/datum/mind/antag_candidates = list() // List of possible starting antags goes here - var/list/restricted_jobs = list() // Jobs it doesn't make sense to be. I.E chaplain or AI cultist - var/list/protected_jobs = list() // Jobs that can't be traitors because - var/list/required_jobs = list() // alternative required job groups eg list(list(cap=1),list(hos=1,sec=2)) translates to one captain OR one hos and two secmans - var/required_players = 0 - var/maximum_players = -1 // -1 is no maximum, positive numbers limit the selection of a mode on overstaffed stations - var/required_enemies = 0 - var/recommended_enemies = 0 - var/antag_flag = null //preferences flag such as BE_WIZARD that need to be turned on for players to be antag - var/mob/living/living_antag_player = null - var/datum/game_mode/replacementmode = null - var/round_converted = 0 //0: round not converted, 1: round going to convert, 2: round converted - var/reroll_friendly //During mode conversion only these are in the running - var/continuous_sanity_checked //Catches some cases where config options could be used to suggest that modes without antagonists should end when all antagonists die - var/enemy_minimum_age = 7 //How many days must players have been playing before they can play this antagonist - - var/announce_span = "warning" //The gamemode's name will be in this span during announcement. - var/announce_text = "This gamemode forgot to set a descriptive text! Uh oh!" //Used to describe a gamemode when it's announced. - - // title_icon and title_icon_state are used for the credits that roll at the end - var/title_icon - - var/const/waittime_l = 600 - var/const/waittime_h = 1800 // started at 1800 - - var/list/datum/station_goal/station_goals = list() - - var/allow_persistence_save = TRUE - - var/gamemode_ready = FALSE //Is the gamemode all set up and ready to start checking for ending conditions. - var/setup_error //What stopepd setting up the mode. - - /// Associative list of current players, in order: living players, living antagonists, dead players and observers. - var/list/list/current_players = list(CURRENT_LIVING_PLAYERS = list(), CURRENT_LIVING_ANTAGS = list(), CURRENT_DEAD_PLAYERS = list(), CURRENT_OBSERVERS = list()) - - -/datum/game_mode/proc/announce() //Shows the gamemode's name and a fast description. - to_chat(world, "The gamemode is: [name]!") - to_chat(world, "[announce_text]") - - -///Checks to see if the game can be setup and ran with the current number of players or whatnot. -/datum/game_mode/proc/can_start() - var/playerC = 0 - for(var/i in GLOB.new_player_list) - var/mob/dead/new_player/player = i - if(player.ready == PLAYER_READY_TO_PLAY) - playerC++ - if(!GLOB.Debug2) - if(playerC < required_players || (maximum_players >= 0 && playerC > maximum_players)) - return 0 - antag_candidates = get_players_for_role(antag_flag) - if(!GLOB.Debug2) - if(antag_candidates.len < required_enemies) - return 0 - return 1 - else - message_admins("DEBUG: GAME STARTING WITHOUT PLAYER NUMBER CHECKS, THIS WILL PROBABLY BREAK SHIT.") - return 1 - - -///Attempts to select players for special roles the mode might have. -/datum/game_mode/proc/pre_setup() - return 1 - -///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. - if(!report) - report = !CONFIG_GET(flag/no_intercept_report) - addtimer(CALLBACK(GLOBAL_PROC, .proc/display_roundstart_logout_report), ROUNDSTART_LOGOUT_REPORT_TIME) - - 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 - if(SSticker.mode) - sql += "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]") - 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 - - -///Handles late-join antag assignments -/datum/game_mode/proc/make_antag_chance(mob/living/carbon/human/character) - if(replacementmode && round_converted == 2) - replacementmode.make_antag_chance(character) - return - - -///Allows rounds to basically be "rerolled" should the initial premise fall through. Also known as mulligan antags. -/datum/game_mode/proc/convert_roundtype() - set waitfor = FALSE - var/list/living_crew = list() - - for(var/i in GLOB.player_list) - var/mob/Player = i - if(Player.mind && Player.stat != DEAD && !isnewplayer(Player) && !isbrain(Player)) - living_crew += Player - var/malc = CONFIG_GET(number/midround_antag_life_check) - if(living_crew.len / GLOB.joined_player_list.len <= malc) //If a lot of the player base died, we start fresh - message_admins("Convert_roundtype failed due to too many dead people. Limit is [malc * 100]% living crew") - return null - - var/list/datum/game_mode/runnable_modes = config.get_runnable_midround_modes(living_crew.len) - var/list/datum/game_mode/usable_modes = list() - for(var/datum/game_mode/G in runnable_modes) - if(G.reroll_friendly && living_crew.len >= G.required_players) - usable_modes += G - else - qdel(G) - - if(!usable_modes.len) - message_admins("Convert_roundtype failed due to no valid modes to convert to. Please report this error to the Coders.") - return null - - replacementmode = pickweight(usable_modes) - - switch(SSshuttle.emergency.mode) //Rounds on the verge of ending don't get new antags, they just run out - if(SHUTTLE_STRANDED, SHUTTLE_ESCAPE) - return 1 - if(SHUTTLE_CALL) - if(SSshuttle.emergency.timeLeft(1) < initial(SSshuttle.emergencyCallTime)*0.5) - return 1 - - var/matc = CONFIG_GET(number/midround_antag_time_check) - if(world.time >= (matc * 600)) - message_admins("Convert_roundtype failed due to round length. Limit is [matc] minutes.") - return null - - var/list/antag_candidates = list() - - for(var/mob/living/carbon/human/H in living_crew) - if(H.client && H.client.prefs.allow_midround_antag && !is_centcom_level(H.z)) - antag_candidates += H - - if(!antag_candidates) - message_admins("Convert_roundtype failed due to no antag candidates.") - return null - - antag_candidates = shuffle(antag_candidates) - - if(CONFIG_GET(flag/protect_roles_from_antagonist)) - replacementmode.restricted_jobs += replacementmode.protected_jobs - if(CONFIG_GET(flag/protect_assistant_from_antagonist)) - replacementmode.restricted_jobs += "Assistant" - - message_admins("The roundtype will be converted. If you have other plans for the station or feel the station is too messed up to inhabit stop the creation of antags or end the round now.") - log_game("Roundtype converted to [replacementmode.name]") - - . = 1 - - sleep(rand(600,1800)) - if(!SSticker.IsRoundInProgress()) - message_admins("Roundtype conversion cancelled, the game appears to have finished!") - round_converted = 0 - return - //somewhere between 1 and 3 minutes from now - if(!CONFIG_GET(keyed_list/midround_antag)[SSticker.mode.config_tag]) - round_converted = 0 - return 1 - for(var/mob/living/carbon/human/H in antag_candidates) - if(H.client) - replacementmode.make_antag_chance(H) - replacementmode.gamemode_ready = TRUE //Awful but we're not doing standard setup here. - round_converted = 2 - message_admins("-- IMPORTANT: The roundtype has been converted to [replacementmode.name], antagonists may have been created! --") - - -///Called by the gameSSticker -/datum/game_mode/process() - return 0 - -//For things that do not die easily -/datum/game_mode/proc/are_special_antags_dead() - return TRUE - - -/datum/game_mode/proc/check_finished(force_ending) //to be called by SSticker - if(!SSticker.setup_done || !gamemode_ready) - return FALSE - if(replacementmode && round_converted == 2) - return replacementmode.check_finished() - if(SSshuttle.emergency && (SSshuttle.emergency.mode == SHUTTLE_ENDGAME)) - return TRUE - if(station_was_nuked) - return TRUE - var/list/continuous = CONFIG_GET(keyed_list/continuous) - var/list/midround_antag = CONFIG_GET(keyed_list/midround_antag) - if(!round_converted && (!continuous[config_tag] || (continuous[config_tag] && midround_antag[config_tag]))) //Non-continuous or continous with replacement antags - if(!continuous_sanity_checked) //make sure we have antags to be checking in the first place - for(var/mob/Player in GLOB.mob_list) - if(Player.mind) - if(Player.mind.special_role || LAZYLEN(Player.mind.antag_datums)) - continuous_sanity_checked = 1 - return 0 - if(!continuous_sanity_checked) - message_admins("The roundtype ([config_tag]) has no antagonists, continuous round has been defaulted to on and midround_antag has been defaulted to off.") - continuous[config_tag] = TRUE - midround_antag[config_tag] = FALSE - SSshuttle.clearHostileEnvironment(src) - return 0 - - - if(living_antag_player && living_antag_player.mind && isliving(living_antag_player) && living_antag_player.stat != DEAD && !isnewplayer(living_antag_player) &&!isbrain(living_antag_player) && (living_antag_player.mind.special_role || LAZYLEN(living_antag_player.mind.antag_datums))) - return 0 //A resource saver: once we find someone who has to die for all antags to be dead, we can just keep checking them, cycling over everyone only when we lose our mark. - - for(var/mob/Player in GLOB.alive_mob_list) - if(Player.mind && Player.stat != DEAD && !isnewplayer(Player) &&!isbrain(Player) && Player.client && (Player.mind.special_role || LAZYLEN(Player.mind.antag_datums))) //Someone's still antagging but is their antagonist datum important enough to skip mulligan? - for(var/datum/antagonist/antag_types in Player.mind.antag_datums) - if(antag_types.prevent_roundtype_conversion) - living_antag_player = Player //they were an important antag, they're our new mark - return 0 - - if(!are_special_antags_dead()) - return FALSE - - if(!continuous[config_tag] || force_ending) - return 1 - - else - round_converted = convert_roundtype() - if(!round_converted) - if(round_ends_with_antag_death) - return 1 - else - midround_antag[config_tag] = 0 - return 0 - - return 0 - - -/datum/game_mode/proc/check_win() //universal trigger to be called at mob death, nuke explosion, etc. To be called from everywhere. - return 0 - -/datum/game_mode/proc/send_intercept() - var/intercepttext = "Central Command Status Summary
    " - intercepttext += "Central Command has intercepted and partially decoded a Syndicate transmission with vital information regarding their movements. The following report outlines the most \ - likely threats to appear in your sector." - var/list/report_weights = config.mode_false_report_weight.Copy() - report_weights[report_type] = 0 //Prevent the current mode from being falsely selected. - var/list/reports = list() - var/Count = 0 //To compensate for missing correct report - if(prob(65)) // 65% chance the actual mode will appear on the list - reports += config.mode_reports[report_type] - Count++ - for(var/i in Count to rand(3,5)) //Between three and five wrong entries on the list. - var/false_report_type = pickweightAllowZero(report_weights) - report_weights[false_report_type] = 0 //Make it so the same false report won't be selected twice - reports += config.mode_reports[false_report_type] - - reports = shuffle(reports) //Randomize the order, so the real one is at a random position. - - for(var/report in reports) - intercepttext += "
    " - intercepttext += report - - if(station_goals.len) - intercepttext += "
    Special Orders for [station_name()]:" - for(var/datum/station_goal/G in station_goals) - G.on_report() - intercepttext += G.get_report() - - print_command_report(intercepttext, "Central Command Status Summary", announce=FALSE) - priority_announce("A summary has been copied and printed to all communications consoles.", "Enemy communication intercepted. Security level elevated.", 'sound/ai/intercept.ogg') - if(GLOB.security_level < SEC_LEVEL_BLUE) - set_security_level(SEC_LEVEL_BLUE) - - -// This is a frequency selection system. You may imagine it like a raffle where each player can have some number of tickets. The more tickets you have the more likely you are to -// "win". The default is 100 tickets. If no players use any extra tickets (earned with the antagonist rep system) calling this function should be equivalent to calling the normal -// pick() function. By default you may use up to 100 extra tickets per roll, meaning at maximum a player may double their chances compared to a player who has no extra tickets. -// -// The odds of being picked are simply (your_tickets / total_tickets). Suppose you have one player using fifty (50) extra tickets, and one who uses no extra: -// Player A: 150 tickets -// Player B: 100 tickets -// Total: 250 tickets -// -// The odds become: -// Player A: 150 / 250 = 0.6 = 60% -// Player B: 100 / 250 = 0.4 = 40% -/datum/game_mode/proc/antag_pick(list/datum/candidates) - if(!CONFIG_GET(flag/use_antag_rep)) // || candidates.len <= 1) - return pick(candidates) - - // Tickets start at 100 - var/DEFAULT_ANTAG_TICKETS = CONFIG_GET(number/default_antag_tickets) - - // You may use up to 100 extra tickets (double your odds) - var/MAX_TICKETS_PER_ROLL = CONFIG_GET(number/max_tickets_per_roll) - - - var/total_tickets = 0 - - MAX_TICKETS_PER_ROLL += DEFAULT_ANTAG_TICKETS - - var/p_ckey - var/p_rep - - for(var/datum/mind/mind in candidates) - p_ckey = ckey(mind.key) - total_tickets += min(SSpersistence.antag_rep[p_ckey] + DEFAULT_ANTAG_TICKETS, MAX_TICKETS_PER_ROLL) - - var/antag_select = rand(1,total_tickets) - var/current = 1 - - for(var/datum/mind/mind in candidates) - p_ckey = ckey(mind.key) - p_rep = SSpersistence.antag_rep[p_ckey] - - var/previous = current - var/spend = min(p_rep + DEFAULT_ANTAG_TICKETS, MAX_TICKETS_PER_ROLL) - current += spend - - if(antag_select >= previous && antag_select <= (current-1)) - SSpersistence.antag_rep_change[p_ckey] = -(spend - DEFAULT_ANTAG_TICKETS) - -// WARNING("AR_DEBUG: Player [mind.key] won spending [spend] tickets from starting value [SSpersistence.antag_rep[p_ckey]]") - - return mind - - WARNING("Something has gone terribly wrong. /datum/game_mode/proc/antag_pick failed to select a candidate. Falling back to pick()") - return pick(candidates) - -/datum/game_mode/proc/get_players_for_role(role) - var/list/players = list() - var/list/candidates = list() - var/list/drafted = list() - var/datum/mind/applicant = null - - // Ultimate randomizing code right here - for(var/i in GLOB.new_player_list) - var/mob/dead/new_player/player = i - if(player.ready == PLAYER_READY_TO_PLAY && player.check_preferences()) - players += player - - // Shuffling, the players list is now ping-independent!!! - // Goodbye antag dante - players = shuffle(players) - - for(var/mob/dead/new_player/player in players) - if(player.client && player.ready == PLAYER_READY_TO_PLAY) - if(role in player.client.prefs.be_special) - if(!is_banned_from(player.ckey, list(role, ROLE_SYNDICATE)) && !QDELETED(player)) - if(age_check(player.client)) //Must be older than the minimum age - candidates += player.mind // Get a list of all the people who want to be the antagonist for this round - - if(restricted_jobs) - for(var/datum/mind/player in candidates) - for(var/job in restricted_jobs) // Remove people who want to be antagonist but have a job already that precludes it - if(player.assigned_role == job) - candidates -= player - - if(candidates.len < recommended_enemies) - for(var/mob/dead/new_player/player in players) - if(player.client && player.ready == PLAYER_READY_TO_PLAY) - if(!(role in player.client.prefs.be_special)) // We don't have enough people who want to be antagonist, make a separate list of people who don't want to be one - if(!is_banned_from(player.ckey, list(role, ROLE_SYNDICATE)) && !QDELETED(player)) - drafted += player.mind - - if(restricted_jobs) - for(var/datum/mind/player in drafted) // Remove people who can't be an antagonist - for(var/job in restricted_jobs) - if(player.assigned_role == job) - drafted -= player - - drafted = shuffle(drafted) // Will hopefully increase randomness, Donkie - - while(candidates.len < recommended_enemies) // Pick randomlly just the number of people we need and add them to our list of candidates - if(drafted.len > 0) - applicant = pick(drafted) - if(applicant) - candidates += applicant - drafted.Remove(applicant) - - else // Not enough scrubs, ABORT ABORT ABORT - break - - return candidates // Returns: The number of people who had the antagonist role set to yes, regardless of recomended_enemies, if that number is greater than recommended_enemies - // recommended_enemies if the number of people with that role set to yes is less than recomended_enemies, - // Less if there are not enough valid players in the game entirely to make recommended_enemies. - - - -/datum/game_mode/proc/num_players() - . = 0 - for(var/i in GLOB.new_player_list) - var/mob/dead/new_player/P = i - if(P.ready == PLAYER_READY_TO_PLAY) - . ++ - -//////////////////////////// -//Keeps track of all heads// -//////////////////////////// - -/datum/game_mode/proc/get_living_by_department(var/department) - . = list() - for(var/mob/living/carbon/human/player in GLOB.mob_list) - if(player.stat != DEAD && player.mind && (player.mind.assigned_role in department)) - . |= player.mind - -/datum/game_mode/proc/get_all_by_department(var/department) - . = list() - for(var/mob/player in GLOB.mob_list) - if(player.mind && (player.mind.assigned_role in department)) - . |= player.mind - -///////////////////////////////////////////// -//Keeps track of all living silicon members// -///////////////////////////////////////////// -/datum/game_mode/proc/get_living_silicon() - . = list() - for(var/mob/living/silicon/player in GLOB.mob_list) - if(player.stat != DEAD && player.mind && (player.mind.assigned_role in GLOB.nonhuman_positions)) - . |= player.mind - -/////////////////////////////////////// -//Keeps track of all silicon members // -/////////////////////////////////////// -/datum/game_mode/proc/get_all_silicon() - . = list() - for(var/mob/living/silicon/player in GLOB.mob_list) - if(player.mind && (player.mind.assigned_role in GLOB.nonhuman_positions)) - . |= player.mind - -/proc/reopen_roundstart_suicide_roles() - var/list/valid_positions = list() - valid_positions += GLOB.engineering_positions - valid_positions += GLOB.medical_positions - valid_positions += GLOB.science_positions - valid_positions += GLOB.supply_positions - valid_positions += GLOB.service_positions - valid_positions += GLOB.security_positions - if(CONFIG_GET(flag/reopen_roundstart_suicide_roles_command_positions)) - valid_positions += GLOB.command_positions //add any remaining command positions - else - valid_positions -= GLOB.command_positions //remove all command positions that were added from their respective department positions lists. - - var/list/reopened_jobs = list() - for(var/X in GLOB.suicided_mob_list) - if(!isliving(X)) - continue - var/mob/living/L = X - if(L.job in valid_positions) - var/datum/job/J = SSjob.GetJob(L.job) - if(!J) - continue - J.current_positions = max(J.current_positions-1, 0) - reopened_jobs += L.job - - if(CONFIG_GET(flag/reopen_roundstart_suicide_roles_command_report)) - if(reopened_jobs.len) - var/reopened_job_report_positions - for(var/dead_dudes_job in reopened_jobs) - reopened_job_report_positions = "[reopened_job_report_positions ? "[reopened_job_report_positions]\n":""][dead_dudes_job]" - - var/suicide_command_report = "Central Command Human Resources Board
    \ - Notice of Personnel Change

    \ - To personnel management staff aboard [station_name()]:

    \ - Our medical staff have detected a series of anomalies in the vital sensors \ - of some of the staff aboard your station.

    \ - Further investigation into the situation on our end resulted in us discovering \ - a series of rather... unforturnate decisions that were made on the part of said staff.

    \ - As such, we have taken the liberty to automatically reopen employment opportunities for the positions of the crew members \ - who have decided not to partake in our research. We will be forwarding their cases to our employment review board \ - to determine their eligibility for continued service with the company (and of course the \ - continued storage of cloning records within the central medical backup server.)

    \ - The following positions have been reopened on our behalf:

    \ - [reopened_job_report_positions]
    " - - print_command_report(suicide_command_report, "Central Command Personnel Update") - - -////////////////////////// -//Reports player logouts// -////////////////////////// -/proc/display_roundstart_logout_report() - var/list/msg = list("Roundstart logout report\n\n") - for(var/i in GLOB.mob_living_list) - var/mob/living/L = i - var/mob/living/carbon/C = L - if (istype(C) && !C.last_mind) - continue // never had a client - - if(L.ckey && !GLOB.directory[L.ckey]) - msg += "[L.name] ([L.key]), the [L.job] (Disconnected)\n" - - - if(L.ckey && L.client) - var/failed = FALSE - if(L.client.inactivity >= (ROUNDSTART_LOGOUT_REPORT_TIME / 2)) //Connected, but inactive (alt+tabbed or something) - msg += "[L.name] ([L.key]), the [L.job] (Connected, Inactive)\n" - failed = TRUE //AFK client - if(!failed && L.stat) - if(L.suiciding) //Suicider - msg += "[L.name] ([L.key]), the [L.job] (Suicide)\n" - failed = TRUE //Disconnected client - if(!failed && L.stat == UNCONSCIOUS) - msg += "[L.name] ([L.key]), the [L.job] (Dying)\n" - failed = TRUE //Unconscious - if(!failed && L.stat == DEAD) - msg += "[L.name] ([L.key]), the [L.job] (Dead)\n" - failed = TRUE //Dead - - var/p_ckey = L.client.ckey -// WARNING("AR_DEBUG: [p_ckey]: failed - [failed], antag_rep_change: [SSpersistence.antag_rep_change[p_ckey]]") - - // people who died or left should not gain any reputation - // people who rolled antagonist still lose it - if(failed && SSpersistence.antag_rep_change[p_ckey] > 0) -// WARNING("AR_DEBUG: Zeroed [p_ckey]'s antag_rep_change") - SSpersistence.antag_rep_change[p_ckey] = 0 - - continue //Happy connected client - for(var/mob/dead/observer/D in GLOB.dead_mob_list) - if(D.mind && D.mind.current == L) - if(L.stat == DEAD) - if(L.suiciding) //Suicider - msg += "[L.name] ([ckey(D.mind.key)]), the [L.job] (Suicide)\n" - continue //Disconnected client - else - msg += "[L.name] ([ckey(D.mind.key)]), the [L.job] (Dead)\n" - continue //Dead mob, ghost abandoned - else - if(D.can_reenter_corpse) - continue //Adminghost, or cult/wizard ghost - else - msg += "[L.name] ([ckey(D.mind.key)]), the [L.job] (Ghosted)\n" - continue //Ghosted while alive - - - for (var/C in GLOB.admins) - to_chat(C, msg.Join()) - -//If the configuration option is set to require players to be logged as old enough to play certain jobs, then this proc checks that they are, otherwise it just returns 1 -/datum/game_mode/proc/age_check(client/C) - if(get_remaining_days(C) == 0) - return 1 //Available in 0 days = available right now = player is old enough to play. - return 0 - - -/datum/game_mode/proc/get_remaining_days(client/C) - if(!C) - return 0 - if(!CONFIG_GET(flag/use_age_restriction_for_jobs)) - return 0 - if(!isnum(C.player_age)) - return 0 //This is only a number if the db connection is established, otherwise it is text: "Requires database", meaning these restrictions cannot be enforced - if(!isnum(enemy_minimum_age)) - return 0 - - return max(0, enemy_minimum_age - C.player_age) - -/datum/game_mode/proc/remove_antag_for_borging(datum/mind/newborgie) - SSticker.mode.remove_cultist(newborgie, 0, 0) - var/datum/antagonist/rev/rev = newborgie.has_antag_datum(/datum/antagonist/rev) - if(rev) - rev.remove_revolutionary(TRUE) - -/datum/game_mode/proc/generate_station_goals() - var/list/possible = list() - for(var/T in subtypesof(/datum/station_goal)) - var/datum/station_goal/G = T - if(config_tag in initial(G.gamemode_blacklist)) - continue - possible += T - var/goal_weights = 0 - while(possible.len && goal_weights < STATION_GOAL_BUDGET) - var/datum/station_goal/picked = pick_n_take(possible) - goal_weights += initial(picked.weight) - station_goals += new picked - - -/datum/game_mode/proc/generate_report() //Generates a small text blurb for the gamemode in centcom report - return "Gamemode report for [name] not set. Contact a coder." - -//By default nuke just ends the round -/datum/game_mode/proc/OnNukeExplosion(off_station) - nuke_off_station = off_station - if(off_station < 2) - station_was_nuked = TRUE //Will end the round on next check. - -//Additional report section in roundend report -/datum/game_mode/proc/special_report() - return - -//Set result and news report here -/datum/game_mode/proc/set_round_result() - SSticker.mode_result = "undefined" - if(station_was_nuked) - SSticker.news_report = STATION_DESTROYED_NUKE - if(EMERGENCY_ESCAPED_OR_ENDGAMED) - SSticker.news_report = STATION_EVACUATED - if(SSshuttle.emergency.is_hijacked()) - SSticker.news_report = SHUTTLE_HIJACK - -/// Mode specific admin panel. -/datum/game_mode/proc/admin_panel() - return - - -/datum/game_mode/proc/generate_credit_text() - var/list/round_credits = list() - var/len_before_addition - - // HEADS OF STAFF - round_credits += "

    The Glorious Command Staff:

    " - len_before_addition = round_credits.len - for(var/datum/mind/current in SSticker.mode.get_all_by_department(GLOB.command_positions)) - round_credits += "

    [current.name] as the [current.assigned_role]

    " - if(round_credits.len == len_before_addition) - round_credits += list("

    A serious bureaucratic error has occurred!

    ", "

    No one was in charge of the crew!

    ") - round_credits += "
    " - - // SILICONS - round_credits += "

    The Silicon \"Intelligences\":

    " - len_before_addition = round_credits.len - for(var/datum/mind/current in SSticker.mode.get_all_silicon()) - round_credits += "

    [current.name] as the [current.assigned_role]

    " - if(round_credits.len == len_before_addition) - round_credits += list("

    [station_name()] had no silicon helpers!

    ", "

    Not a single door was opened today!

    ") - round_credits += "
    " - - // SECURITY - round_credits += "

    The Brave Security Officers:

    " - len_before_addition = round_credits.len - for(var/datum/mind/current in SSticker.mode.get_all_by_department(GLOB.security_positions)) - round_credits += "

    [current.name] as the [current.assigned_role]

    " - if(round_credits.len == len_before_addition) - round_credits += list("

    [station_name()] has fallen to Communism!

    ", "

    No one was there to protect the crew!

    ") - round_credits += "
    " - - // MEDICAL - round_credits += "

    The Wise Medical Department:

    " - len_before_addition = round_credits.len - for(var/datum/mind/current in SSticker.mode.get_all_by_department(GLOB.medical_positions)) - round_credits += "

    [current.name] as the [current.assigned_role]

    " - if(round_credits.len == len_before_addition) - round_credits += list("

    Healthcare was not included!

    ", "

    There were no doctors today!

    ") - round_credits += "
    " - - // ENGINEERING - round_credits += "

    The Industrious Engineers:

    " - len_before_addition = round_credits.len - for(var/datum/mind/current in SSticker.mode.get_all_by_department(GLOB.engineering_positions)) - round_credits += "

    [current.name] as the [current.assigned_role]

    " - if(round_credits.len == len_before_addition) - round_credits += list("

    [station_name()] probably did not last long!

    ", "

    No one was holding the station together!

    ") - round_credits += "
    " - - // SCIENCE - round_credits += "

    The Inventive Science Employees:

    " - len_before_addition = round_credits.len - for(var/datum/mind/current in SSticker.mode.get_all_by_department(GLOB.science_positions)) - round_credits += "

    [current.name] as the [current.assigned_role]

    " - if(round_credits.len == len_before_addition) - round_credits += list("

    No one was doing \"science\" today!

    ", "

    Everyone probably made it out alright, then!

    ") - round_credits += "
    " - - // CARGO - round_credits += "

    The Rugged Cargo Crew:

    " - len_before_addition = round_credits.len - for(var/datum/mind/current in SSticker.mode.get_all_by_department(GLOB.supply_positions)) - round_credits += "

    [current.name] as the [current.assigned_role]

    " - if(round_credits.len == len_before_addition) - round_credits += list("

    The station was freed from paperwork!

    ", "

    No one worked in cargo today!

    ") - round_credits += "
    " - - // CIVILIANS - var/list/human_garbage = list() - round_credits += "

    The Hardy Civilians:

    " - len_before_addition = round_credits.len - for(var/datum/mind/current in SSticker.mode.get_all_by_department(GLOB.service_positions)) - if(current.assigned_role == "Assistant") - human_garbage += current - else - round_credits += "

    [current.name] as the [current.assigned_role]

    " - if(round_credits.len == len_before_addition) - round_credits += list("

    Everyone was stuck in traffic this morning!

    ", "

    No civilians made it to work!

    ") - round_credits += "
    " - - round_credits += "

    The Helpful Assistants:

    " - len_before_addition = round_credits.len - for(var/datum/mind/current in human_garbage) - round_credits += "

    [current.name]

    " - if(round_credits.len == len_before_addition) - round_credits += list("

    The station was free of greytide assistance!

    ", "

    Not a single Assistant showed up on the station today!

    ") - - round_credits += "
    " - round_credits += "
    " - - return round_credits + + +/* + * GAMEMODES (by Rastaf0) + * + * In the new mode system all special roles are fully supported. + * You can have proper wizards/traitors/changelings/cultists during any mode. + * Only two things really depends on gamemode: + * 1. Starting roles, equipment and preparations + * 2. Conditions of finishing the round. + * + */ + + +/datum/game_mode + var/name = "invalid" + var/config_tag = null + var/votable = 1 + var/probability = 0 + var/false_report_weight = 0 //How often will this show up incorrectly in a centcom report? + var/report_type = "invalid" //gamemodes with the same report type will not show up in the command report together. + var/station_was_nuked = 0 //see nuclearbomb.dm and malfunction.dm + var/nuke_off_station = 0 //Used for tracking where the nuke hit + var/round_ends_with_antag_death = 0 //flags the "one verse the station" antags as such + var/list/datum/mind/antag_candidates = list() // List of possible starting antags goes here + var/list/restricted_jobs = list() // Jobs it doesn't make sense to be. I.E chaplain or AI cultist + var/list/protected_jobs = list() // Jobs that can't be traitors because + var/list/required_jobs = list() // alternative required job groups eg list(list(cap=1),list(hos=1,sec=2)) translates to one captain OR one hos and two secmans + var/required_players = 0 + var/maximum_players = -1 // -1 is no maximum, positive numbers limit the selection of a mode on overstaffed stations + var/required_enemies = 0 + var/recommended_enemies = 0 + var/antag_flag = null //preferences flag such as BE_WIZARD that need to be turned on for players to be antag + var/mob/living/living_antag_player = null + var/datum/game_mode/replacementmode = null + var/round_converted = 0 //0: round not converted, 1: round going to convert, 2: round converted + var/reroll_friendly //During mode conversion only these are in the running + var/continuous_sanity_checked //Catches some cases where config options could be used to suggest that modes without antagonists should end when all antagonists die + var/enemy_minimum_age = 7 //How many days must players have been playing before they can play this antagonist + + var/announce_span = "warning" //The gamemode's name will be in this span during announcement. + var/announce_text = "This gamemode forgot to set a descriptive text! Uh oh!" //Used to describe a gamemode when it's announced. + + // title_icon and title_icon_state are used for the credits that roll at the end + var/title_icon + + var/const/waittime_l = 600 + var/const/waittime_h = 1800 // started at 1800 + + var/list/datum/station_goal/station_goals = list() + + var/allow_persistence_save = TRUE + + var/gamemode_ready = FALSE //Is the gamemode all set up and ready to start checking for ending conditions. + var/setup_error //What stopepd setting up the mode. + + /// Associative list of current players, in order: living players, living antagonists, dead players and observers. + var/list/list/current_players = list(CURRENT_LIVING_PLAYERS = list(), CURRENT_LIVING_ANTAGS = list(), CURRENT_DEAD_PLAYERS = list(), CURRENT_OBSERVERS = list()) + + +/datum/game_mode/proc/announce() //Shows the gamemode's name and a fast description. + to_chat(world, "The gamemode is: [name]!") + to_chat(world, "[announce_text]") + + +///Checks to see if the game can be setup and ran with the current number of players or whatnot. +/datum/game_mode/proc/can_start() + var/playerC = 0 + for(var/i in GLOB.new_player_list) + var/mob/dead/new_player/player = i + if(player.ready == PLAYER_READY_TO_PLAY) + playerC++ + if(!GLOB.Debug2) + if(playerC < required_players || (maximum_players >= 0 && playerC > maximum_players)) + return 0 + antag_candidates = get_players_for_role(antag_flag) + if(!GLOB.Debug2) + if(antag_candidates.len < required_enemies) + return 0 + return 1 + else + message_admins("DEBUG: GAME STARTING WITHOUT PLAYER NUMBER CHECKS, THIS WILL PROBABLY BREAK SHIT.") + return 1 + + +///Attempts to select players for special roles the mode might have. +/datum/game_mode/proc/pre_setup() + return 1 + +///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. + if(!report) + report = !CONFIG_GET(flag/no_intercept_report) + addtimer(CALLBACK(GLOBAL_PROC, .proc/display_roundstart_logout_report), ROUNDSTART_LOGOUT_REPORT_TIME) + + 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/list/to_set = list() + var/arguments = list() + if(SSticker.mode) + to_set += "game_mode = :game_mode" + arguments["game_mode"] = SSticker.mode + if(GLOB.revdata.originmastercommit) + to_set += "commit_hash = :commit_hash" + arguments["commit_hash"] = GLOB.revdata.originmastercommit + if(to_set.len) + arguments["round_id"] = GLOB.round_id + var/datum/DBQuery/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 + + +///Handles late-join antag assignments +/datum/game_mode/proc/make_antag_chance(mob/living/carbon/human/character) + if(replacementmode && round_converted == 2) + replacementmode.make_antag_chance(character) + return + + +///Allows rounds to basically be "rerolled" should the initial premise fall through. Also known as mulligan antags. +/datum/game_mode/proc/convert_roundtype() + set waitfor = FALSE + var/list/living_crew = list() + + for(var/i in GLOB.player_list) + var/mob/Player = i + if(Player.mind && Player.stat != DEAD && !isnewplayer(Player) && !isbrain(Player)) + living_crew += Player + var/malc = CONFIG_GET(number/midround_antag_life_check) + if(living_crew.len / GLOB.joined_player_list.len <= malc) //If a lot of the player base died, we start fresh + message_admins("Convert_roundtype failed due to too many dead people. Limit is [malc * 100]% living crew") + return null + + var/list/datum/game_mode/runnable_modes = config.get_runnable_midround_modes(living_crew.len) + var/list/datum/game_mode/usable_modes = list() + for(var/datum/game_mode/G in runnable_modes) + if(G.reroll_friendly && living_crew.len >= G.required_players) + usable_modes += G + else + qdel(G) + + if(!usable_modes.len) + message_admins("Convert_roundtype failed due to no valid modes to convert to. Please report this error to the Coders.") + return null + + replacementmode = pickweight(usable_modes) + + switch(SSshuttle.emergency.mode) //Rounds on the verge of ending don't get new antags, they just run out + if(SHUTTLE_STRANDED, SHUTTLE_ESCAPE) + return 1 + if(SHUTTLE_CALL) + if(SSshuttle.emergency.timeLeft(1) < initial(SSshuttle.emergencyCallTime)*0.5) + return 1 + + var/matc = CONFIG_GET(number/midround_antag_time_check) + if(world.time >= (matc * 600)) + message_admins("Convert_roundtype failed due to round length. Limit is [matc] minutes.") + return null + + var/list/antag_candidates = list() + + for(var/mob/living/carbon/human/H in living_crew) + if(H.client && H.client.prefs.allow_midround_antag && !is_centcom_level(H.z)) + antag_candidates += H + + if(!antag_candidates) + message_admins("Convert_roundtype failed due to no antag candidates.") + return null + + antag_candidates = shuffle(antag_candidates) + + if(CONFIG_GET(flag/protect_roles_from_antagonist)) + replacementmode.restricted_jobs += replacementmode.protected_jobs + if(CONFIG_GET(flag/protect_assistant_from_antagonist)) + replacementmode.restricted_jobs += "Assistant" + + message_admins("The roundtype will be converted. If you have other plans for the station or feel the station is too messed up to inhabit stop the creation of antags or end the round now.") + log_game("Roundtype converted to [replacementmode.name]") + + . = 1 + + sleep(rand(600,1800)) + if(!SSticker.IsRoundInProgress()) + message_admins("Roundtype conversion cancelled, the game appears to have finished!") + round_converted = 0 + return + //somewhere between 1 and 3 minutes from now + if(!CONFIG_GET(keyed_list/midround_antag)[SSticker.mode.config_tag]) + round_converted = 0 + return 1 + for(var/mob/living/carbon/human/H in antag_candidates) + if(H.client) + replacementmode.make_antag_chance(H) + replacementmode.gamemode_ready = TRUE //Awful but we're not doing standard setup here. + round_converted = 2 + message_admins("-- IMPORTANT: The roundtype has been converted to [replacementmode.name], antagonists may have been created! --") + + +///Called by the gameSSticker +/datum/game_mode/process() + return 0 + +//For things that do not die easily +/datum/game_mode/proc/are_special_antags_dead() + return TRUE + + +/datum/game_mode/proc/check_finished(force_ending) //to be called by SSticker + if(!SSticker.setup_done || !gamemode_ready) + return FALSE + if(replacementmode && round_converted == 2) + return replacementmode.check_finished() + if(SSshuttle.emergency && (SSshuttle.emergency.mode == SHUTTLE_ENDGAME)) + return TRUE + if(station_was_nuked) + return TRUE + var/list/continuous = CONFIG_GET(keyed_list/continuous) + var/list/midround_antag = CONFIG_GET(keyed_list/midround_antag) + if(!round_converted && (!continuous[config_tag] || (continuous[config_tag] && midround_antag[config_tag]))) //Non-continuous or continous with replacement antags + if(!continuous_sanity_checked) //make sure we have antags to be checking in the first place + for(var/mob/Player in GLOB.mob_list) + if(Player.mind) + if(Player.mind.special_role || LAZYLEN(Player.mind.antag_datums)) + continuous_sanity_checked = 1 + return 0 + if(!continuous_sanity_checked) + message_admins("The roundtype ([config_tag]) has no antagonists, continuous round has been defaulted to on and midround_antag has been defaulted to off.") + continuous[config_tag] = TRUE + midround_antag[config_tag] = FALSE + SSshuttle.clearHostileEnvironment(src) + return 0 + + + if(living_antag_player && living_antag_player.mind && isliving(living_antag_player) && living_antag_player.stat != DEAD && !isnewplayer(living_antag_player) &&!isbrain(living_antag_player) && (living_antag_player.mind.special_role || LAZYLEN(living_antag_player.mind.antag_datums))) + return 0 //A resource saver: once we find someone who has to die for all antags to be dead, we can just keep checking them, cycling over everyone only when we lose our mark. + + for(var/mob/Player in GLOB.alive_mob_list) + if(Player.mind && Player.stat != DEAD && !isnewplayer(Player) &&!isbrain(Player) && Player.client && (Player.mind.special_role || LAZYLEN(Player.mind.antag_datums))) //Someone's still antagging but is their antagonist datum important enough to skip mulligan? + for(var/datum/antagonist/antag_types in Player.mind.antag_datums) + if(antag_types.prevent_roundtype_conversion) + living_antag_player = Player //they were an important antag, they're our new mark + return 0 + + if(!are_special_antags_dead()) + return FALSE + + if(!continuous[config_tag] || force_ending) + return 1 + + else + round_converted = convert_roundtype() + if(!round_converted) + if(round_ends_with_antag_death) + return 1 + else + midround_antag[config_tag] = 0 + return 0 + + return 0 + + +/datum/game_mode/proc/check_win() //universal trigger to be called at mob death, nuke explosion, etc. To be called from everywhere. + return 0 + +/datum/game_mode/proc/send_intercept() + var/intercepttext = "Central Command Status Summary
    " + intercepttext += "Central Command has intercepted and partially decoded a Syndicate transmission with vital information regarding their movements. The following report outlines the most \ + likely threats to appear in your sector." + var/list/report_weights = config.mode_false_report_weight.Copy() + report_weights[report_type] = 0 //Prevent the current mode from being falsely selected. + var/list/reports = list() + var/Count = 0 //To compensate for missing correct report + if(prob(65)) // 65% chance the actual mode will appear on the list + reports += config.mode_reports[report_type] + Count++ + for(var/i in Count to rand(3,5)) //Between three and five wrong entries on the list. + var/false_report_type = pickweightAllowZero(report_weights) + report_weights[false_report_type] = 0 //Make it so the same false report won't be selected twice + reports += config.mode_reports[false_report_type] + + reports = shuffle(reports) //Randomize the order, so the real one is at a random position. + + for(var/report in reports) + intercepttext += "
    " + intercepttext += report + + if(station_goals.len) + intercepttext += "
    Special Orders for [station_name()]:" + for(var/datum/station_goal/G in station_goals) + G.on_report() + intercepttext += G.get_report() + + print_command_report(intercepttext, "Central Command Status Summary", announce=FALSE) + priority_announce("A summary has been copied and printed to all communications consoles.", "Enemy communication intercepted. Security level elevated.", 'sound/ai/intercept.ogg') + if(GLOB.security_level < SEC_LEVEL_BLUE) + set_security_level(SEC_LEVEL_BLUE) + + +// This is a frequency selection system. You may imagine it like a raffle where each player can have some number of tickets. The more tickets you have the more likely you are to +// "win". The default is 100 tickets. If no players use any extra tickets (earned with the antagonist rep system) calling this function should be equivalent to calling the normal +// pick() function. By default you may use up to 100 extra tickets per roll, meaning at maximum a player may double their chances compared to a player who has no extra tickets. +// +// The odds of being picked are simply (your_tickets / total_tickets). Suppose you have one player using fifty (50) extra tickets, and one who uses no extra: +// Player A: 150 tickets +// Player B: 100 tickets +// Total: 250 tickets +// +// The odds become: +// Player A: 150 / 250 = 0.6 = 60% +// Player B: 100 / 250 = 0.4 = 40% +/datum/game_mode/proc/antag_pick(list/datum/candidates) + if(!CONFIG_GET(flag/use_antag_rep)) // || candidates.len <= 1) + return pick(candidates) + + // Tickets start at 100 + var/DEFAULT_ANTAG_TICKETS = CONFIG_GET(number/default_antag_tickets) + + // You may use up to 100 extra tickets (double your odds) + var/MAX_TICKETS_PER_ROLL = CONFIG_GET(number/max_tickets_per_roll) + + + var/total_tickets = 0 + + MAX_TICKETS_PER_ROLL += DEFAULT_ANTAG_TICKETS + + var/p_ckey + var/p_rep + + for(var/datum/mind/mind in candidates) + p_ckey = ckey(mind.key) + total_tickets += min(SSpersistence.antag_rep[p_ckey] + DEFAULT_ANTAG_TICKETS, MAX_TICKETS_PER_ROLL) + + var/antag_select = rand(1,total_tickets) + var/current = 1 + + for(var/datum/mind/mind in candidates) + p_ckey = ckey(mind.key) + p_rep = SSpersistence.antag_rep[p_ckey] + + var/previous = current + var/spend = min(p_rep + DEFAULT_ANTAG_TICKETS, MAX_TICKETS_PER_ROLL) + current += spend + + if(antag_select >= previous && antag_select <= (current-1)) + SSpersistence.antag_rep_change[p_ckey] = -(spend - DEFAULT_ANTAG_TICKETS) + +// WARNING("AR_DEBUG: Player [mind.key] won spending [spend] tickets from starting value [SSpersistence.antag_rep[p_ckey]]") + + return mind + + WARNING("Something has gone terribly wrong. /datum/game_mode/proc/antag_pick failed to select a candidate. Falling back to pick()") + return pick(candidates) + +/datum/game_mode/proc/get_players_for_role(role) + var/list/players = list() + var/list/candidates = list() + var/list/drafted = list() + var/datum/mind/applicant = null + + // Ultimate randomizing code right here + for(var/i in GLOB.new_player_list) + var/mob/dead/new_player/player = i + if(player.ready == PLAYER_READY_TO_PLAY && player.check_preferences()) + players += player + + // Shuffling, the players list is now ping-independent!!! + // Goodbye antag dante + players = shuffle(players) + + for(var/mob/dead/new_player/player in players) + if(player.client && player.ready == PLAYER_READY_TO_PLAY) + if(role in player.client.prefs.be_special) + if(!is_banned_from(player.ckey, list(role, ROLE_SYNDICATE)) && !QDELETED(player)) + if(age_check(player.client)) //Must be older than the minimum age + candidates += player.mind // Get a list of all the people who want to be the antagonist for this round + + if(restricted_jobs) + for(var/datum/mind/player in candidates) + for(var/job in restricted_jobs) // Remove people who want to be antagonist but have a job already that precludes it + if(player.assigned_role == job) + candidates -= player + + if(candidates.len < recommended_enemies) + for(var/mob/dead/new_player/player in players) + if(player.client && player.ready == PLAYER_READY_TO_PLAY) + if(!(role in player.client.prefs.be_special)) // We don't have enough people who want to be antagonist, make a separate list of people who don't want to be one + if(!is_banned_from(player.ckey, list(role, ROLE_SYNDICATE)) && !QDELETED(player)) + drafted += player.mind + + if(restricted_jobs) + for(var/datum/mind/player in drafted) // Remove people who can't be an antagonist + for(var/job in restricted_jobs) + if(player.assigned_role == job) + drafted -= player + + drafted = shuffle(drafted) // Will hopefully increase randomness, Donkie + + while(candidates.len < recommended_enemies) // Pick randomlly just the number of people we need and add them to our list of candidates + if(drafted.len > 0) + applicant = pick(drafted) + if(applicant) + candidates += applicant + drafted.Remove(applicant) + + else // Not enough scrubs, ABORT ABORT ABORT + break + + return candidates // Returns: The number of people who had the antagonist role set to yes, regardless of recomended_enemies, if that number is greater than recommended_enemies + // recommended_enemies if the number of people with that role set to yes is less than recomended_enemies, + // Less if there are not enough valid players in the game entirely to make recommended_enemies. + + + +/datum/game_mode/proc/num_players() + . = 0 + for(var/i in GLOB.new_player_list) + var/mob/dead/new_player/P = i + if(P.ready == PLAYER_READY_TO_PLAY) + . ++ + +//////////////////////////// +//Keeps track of all heads// +//////////////////////////// + +/datum/game_mode/proc/get_living_by_department(var/department) + . = list() + for(var/mob/living/carbon/human/player in GLOB.mob_list) + if(player.stat != DEAD && player.mind && (player.mind.assigned_role in department)) + . |= player.mind + +/datum/game_mode/proc/get_all_by_department(var/department) + . = list() + for(var/mob/player in GLOB.mob_list) + if(player.mind && (player.mind.assigned_role in department)) + . |= player.mind + +///////////////////////////////////////////// +//Keeps track of all living silicon members// +///////////////////////////////////////////// +/datum/game_mode/proc/get_living_silicon() + . = list() + for(var/mob/living/silicon/player in GLOB.mob_list) + if(player.stat != DEAD && player.mind && (player.mind.assigned_role in GLOB.nonhuman_positions)) + . |= player.mind + +/////////////////////////////////////// +//Keeps track of all silicon members // +/////////////////////////////////////// +/datum/game_mode/proc/get_all_silicon() + . = list() + for(var/mob/living/silicon/player in GLOB.mob_list) + if(player.mind && (player.mind.assigned_role in GLOB.nonhuman_positions)) + . |= player.mind + +/proc/reopen_roundstart_suicide_roles() + var/list/valid_positions = list() + valid_positions += GLOB.engineering_positions + valid_positions += GLOB.medical_positions + valid_positions += GLOB.science_positions + valid_positions += GLOB.supply_positions + valid_positions += GLOB.service_positions + valid_positions += GLOB.security_positions + if(CONFIG_GET(flag/reopen_roundstart_suicide_roles_command_positions)) + valid_positions += GLOB.command_positions //add any remaining command positions + else + valid_positions -= GLOB.command_positions //remove all command positions that were added from their respective department positions lists. + + var/list/reopened_jobs = list() + for(var/X in GLOB.suicided_mob_list) + if(!isliving(X)) + continue + var/mob/living/L = X + if(L.job in valid_positions) + var/datum/job/J = SSjob.GetJob(L.job) + if(!J) + continue + J.current_positions = max(J.current_positions-1, 0) + reopened_jobs += L.job + + if(CONFIG_GET(flag/reopen_roundstart_suicide_roles_command_report)) + if(reopened_jobs.len) + var/reopened_job_report_positions + for(var/dead_dudes_job in reopened_jobs) + reopened_job_report_positions = "[reopened_job_report_positions ? "[reopened_job_report_positions]\n":""][dead_dudes_job]" + + var/suicide_command_report = "Central Command Human Resources Board
    \ + Notice of Personnel Change

    \ + To personnel management staff aboard [station_name()]:

    \ + Our medical staff have detected a series of anomalies in the vital sensors \ + of some of the staff aboard your station.

    \ + Further investigation into the situation on our end resulted in us discovering \ + a series of rather... unforturnate decisions that were made on the part of said staff.

    \ + As such, we have taken the liberty to automatically reopen employment opportunities for the positions of the crew members \ + who have decided not to partake in our research. We will be forwarding their cases to our employment review board \ + to determine their eligibility for continued service with the company (and of course the \ + continued storage of cloning records within the central medical backup server.)

    \ + The following positions have been reopened on our behalf:

    \ + [reopened_job_report_positions]
    " + + print_command_report(suicide_command_report, "Central Command Personnel Update") + + +////////////////////////// +//Reports player logouts// +////////////////////////// +/proc/display_roundstart_logout_report() + var/list/msg = list("Roundstart logout report\n\n") + for(var/i in GLOB.mob_living_list) + var/mob/living/L = i + var/mob/living/carbon/C = L + if (istype(C) && !C.last_mind) + continue // never had a client + + if(L.ckey && !GLOB.directory[L.ckey]) + msg += "[L.name] ([L.key]), the [L.job] (Disconnected)\n" + + + if(L.ckey && L.client) + var/failed = FALSE + if(L.client.inactivity >= (ROUNDSTART_LOGOUT_REPORT_TIME / 2)) //Connected, but inactive (alt+tabbed or something) + msg += "[L.name] ([L.key]), the [L.job] (Connected, Inactive)\n" + failed = TRUE //AFK client + if(!failed && L.stat) + if(L.suiciding) //Suicider + msg += "[L.name] ([L.key]), the [L.job] (Suicide)\n" + failed = TRUE //Disconnected client + if(!failed && L.stat == UNCONSCIOUS) + msg += "[L.name] ([L.key]), the [L.job] (Dying)\n" + failed = TRUE //Unconscious + if(!failed && L.stat == DEAD) + msg += "[L.name] ([L.key]), the [L.job] (Dead)\n" + failed = TRUE //Dead + + var/p_ckey = L.client.ckey +// WARNING("AR_DEBUG: [p_ckey]: failed - [failed], antag_rep_change: [SSpersistence.antag_rep_change[p_ckey]]") + + // people who died or left should not gain any reputation + // people who rolled antagonist still lose it + if(failed && SSpersistence.antag_rep_change[p_ckey] > 0) +// WARNING("AR_DEBUG: Zeroed [p_ckey]'s antag_rep_change") + SSpersistence.antag_rep_change[p_ckey] = 0 + + continue //Happy connected client + for(var/mob/dead/observer/D in GLOB.dead_mob_list) + if(D.mind && D.mind.current == L) + if(L.stat == DEAD) + if(L.suiciding) //Suicider + msg += "[L.name] ([ckey(D.mind.key)]), the [L.job] (Suicide)\n" + continue //Disconnected client + else + msg += "[L.name] ([ckey(D.mind.key)]), the [L.job] (Dead)\n" + continue //Dead mob, ghost abandoned + else + if(D.can_reenter_corpse) + continue //Adminghost, or cult/wizard ghost + else + msg += "[L.name] ([ckey(D.mind.key)]), the [L.job] (Ghosted)\n" + continue //Ghosted while alive + + + for (var/C in GLOB.admins) + to_chat(C, msg.Join()) + +//If the configuration option is set to require players to be logged as old enough to play certain jobs, then this proc checks that they are, otherwise it just returns 1 +/datum/game_mode/proc/age_check(client/C) + if(get_remaining_days(C) == 0) + return 1 //Available in 0 days = available right now = player is old enough to play. + return 0 + + +/datum/game_mode/proc/get_remaining_days(client/C) + if(!C) + return 0 + if(!CONFIG_GET(flag/use_age_restriction_for_jobs)) + return 0 + if(!isnum(C.player_age)) + return 0 //This is only a number if the db connection is established, otherwise it is text: "Requires database", meaning these restrictions cannot be enforced + if(!isnum(enemy_minimum_age)) + return 0 + + return max(0, enemy_minimum_age - C.player_age) + +/datum/game_mode/proc/remove_antag_for_borging(datum/mind/newborgie) + SSticker.mode.remove_cultist(newborgie, 0, 0) + var/datum/antagonist/rev/rev = newborgie.has_antag_datum(/datum/antagonist/rev) + if(rev) + rev.remove_revolutionary(TRUE) + +/datum/game_mode/proc/generate_station_goals() + var/list/possible = list() + for(var/T in subtypesof(/datum/station_goal)) + var/datum/station_goal/G = T + if(config_tag in initial(G.gamemode_blacklist)) + continue + possible += T + var/goal_weights = 0 + while(possible.len && goal_weights < STATION_GOAL_BUDGET) + var/datum/station_goal/picked = pick_n_take(possible) + goal_weights += initial(picked.weight) + station_goals += new picked + + +/datum/game_mode/proc/generate_report() //Generates a small text blurb for the gamemode in centcom report + return "Gamemode report for [name] not set. Contact a coder." + +//By default nuke just ends the round +/datum/game_mode/proc/OnNukeExplosion(off_station) + nuke_off_station = off_station + if(off_station < 2) + station_was_nuked = TRUE //Will end the round on next check. + +//Additional report section in roundend report +/datum/game_mode/proc/special_report() + return + +//Set result and news report here +/datum/game_mode/proc/set_round_result() + SSticker.mode_result = "undefined" + if(station_was_nuked) + SSticker.news_report = STATION_DESTROYED_NUKE + if(EMERGENCY_ESCAPED_OR_ENDGAMED) + SSticker.news_report = STATION_EVACUATED + if(SSshuttle.emergency.is_hijacked()) + SSticker.news_report = SHUTTLE_HIJACK + +/// Mode specific admin panel. +/datum/game_mode/proc/admin_panel() + return + + +/datum/game_mode/proc/generate_credit_text() + var/list/round_credits = list() + var/len_before_addition + + // HEADS OF STAFF + round_credits += "

    The Glorious Command Staff:

    " + len_before_addition = round_credits.len + for(var/datum/mind/current in SSticker.mode.get_all_by_department(GLOB.command_positions)) + round_credits += "

    [current.name] as the [current.assigned_role]

    " + if(round_credits.len == len_before_addition) + round_credits += list("

    A serious bureaucratic error has occurred!

    ", "

    No one was in charge of the crew!

    ") + round_credits += "
    " + + // SILICONS + round_credits += "

    The Silicon \"Intelligences\":

    " + len_before_addition = round_credits.len + for(var/datum/mind/current in SSticker.mode.get_all_silicon()) + round_credits += "

    [current.name] as the [current.assigned_role]

    " + if(round_credits.len == len_before_addition) + round_credits += list("

    [station_name()] had no silicon helpers!

    ", "

    Not a single door was opened today!

    ") + round_credits += "
    " + + // SECURITY + round_credits += "

    The Brave Security Officers:

    " + len_before_addition = round_credits.len + for(var/datum/mind/current in SSticker.mode.get_all_by_department(GLOB.security_positions)) + round_credits += "

    [current.name] as the [current.assigned_role]

    " + if(round_credits.len == len_before_addition) + round_credits += list("

    [station_name()] has fallen to Communism!

    ", "

    No one was there to protect the crew!

    ") + round_credits += "
    " + + // MEDICAL + round_credits += "

    The Wise Medical Department:

    " + len_before_addition = round_credits.len + for(var/datum/mind/current in SSticker.mode.get_all_by_department(GLOB.medical_positions)) + round_credits += "

    [current.name] as the [current.assigned_role]

    " + if(round_credits.len == len_before_addition) + round_credits += list("

    Healthcare was not included!

    ", "

    There were no doctors today!

    ") + round_credits += "
    " + + // ENGINEERING + round_credits += "

    The Industrious Engineers:

    " + len_before_addition = round_credits.len + for(var/datum/mind/current in SSticker.mode.get_all_by_department(GLOB.engineering_positions)) + round_credits += "

    [current.name] as the [current.assigned_role]

    " + if(round_credits.len == len_before_addition) + round_credits += list("

    [station_name()] probably did not last long!

    ", "

    No one was holding the station together!

    ") + round_credits += "
    " + + // SCIENCE + round_credits += "

    The Inventive Science Employees:

    " + len_before_addition = round_credits.len + for(var/datum/mind/current in SSticker.mode.get_all_by_department(GLOB.science_positions)) + round_credits += "

    [current.name] as the [current.assigned_role]

    " + if(round_credits.len == len_before_addition) + round_credits += list("

    No one was doing \"science\" today!

    ", "

    Everyone probably made it out alright, then!

    ") + round_credits += "
    " + + // CARGO + round_credits += "

    The Rugged Cargo Crew:

    " + len_before_addition = round_credits.len + for(var/datum/mind/current in SSticker.mode.get_all_by_department(GLOB.supply_positions)) + round_credits += "

    [current.name] as the [current.assigned_role]

    " + if(round_credits.len == len_before_addition) + round_credits += list("

    The station was freed from paperwork!

    ", "

    No one worked in cargo today!

    ") + round_credits += "
    " + + // CIVILIANS + var/list/human_garbage = list() + round_credits += "

    The Hardy Civilians:

    " + len_before_addition = round_credits.len + for(var/datum/mind/current in SSticker.mode.get_all_by_department(GLOB.service_positions)) + if(current.assigned_role == "Assistant") + human_garbage += current + else + round_credits += "

    [current.name] as the [current.assigned_role]

    " + if(round_credits.len == len_before_addition) + round_credits += list("

    Everyone was stuck in traffic this morning!

    ", "

    No civilians made it to work!

    ") + round_credits += "
    " + + round_credits += "

    The Helpful Assistants:

    " + len_before_addition = round_credits.len + for(var/datum/mind/current in human_garbage) + round_credits += "

    [current.name]

    " + if(round_credits.len == len_before_addition) + round_credits += list("

    The station was free of greytide assistance!

    ", "

    Not a single Assistant showed up on the station today!

    ") + + round_credits += "
    " + round_credits += "
    " + + return round_credits diff --git a/code/game/gamemodes/meteor/meteor.dm b/code/game/gamemodes/meteor/meteor.dm index c61b26c8753c..9149f5da35f6 100644 --- a/code/game/gamemodes/meteor/meteor.dm +++ b/code/game/gamemodes/meteor/meteor.dm @@ -1,61 +1,61 @@ -/datum/game_mode/meteor - name = "meteor" - config_tag = "meteor" - report_type = "meteor" - false_report_weight = 1 - var/meteordelay = 2000 - var/nometeors = 0 - var/rampupdelta = 5 - required_players = 0 - - announce_span = "danger" - announce_text = "A major meteor shower is bombarding the station! The crew needs to evacuate or survive the onslaught." - - title_icon = "meteor" - -/datum/game_mode/meteor/process() - if(nometeors || meteordelay > world.time - SSticker.round_start_time) - return - - var/list/wavetype = GLOB.meteors_normal - var/meteorminutes = (world.time - SSticker.round_start_time - meteordelay) / 10 / 60 - - - if (prob(meteorminutes)) - wavetype = GLOB.meteors_threatening - - if (prob(meteorminutes/2)) - wavetype = GLOB.meteors_catastrophic - - var/ramp_up_final = clamp(round(meteorminutes/rampupdelta), 1, 10) - - spawn_meteors(ramp_up_final, wavetype) - - -/datum/game_mode/meteor/special_report() - var/survivors = 0 - var/list/survivor_list = list() - - for(var/mob/living/player in GLOB.player_list) - if(player.stat != DEAD) - ++survivors - - if(player.onCentCom()) - survivor_list += "[player.real_name] escaped to the safety of CentCom." - else if(player.onSyndieBase()) - survivor_list += "[player.real_name] escaped to the (relative) safety of Syndicate Space." - else - survivor_list += "[player.real_name] survived but is stranded without any hope of rescue." - - if(survivors) - return "
    The following survived the meteor storm:
    [survivor_list.Join("
    ")]
    " - else - return "
    Nobody survived the meteor storm!
    " - -/datum/game_mode/meteor/set_round_result() - ..() - SSticker.mode_result = "end - evacuation" - -/datum/game_mode/meteor/generate_report() - return "[pick("Asteroids have", "Meteors have", "Large rocks have", "Stellar minerals have", "Space hail has", "Debris has")] been detected near your station, and a collision is possible, \ - though unlikely. Be prepared for largescale impacts and destruction. Please note that the debris will prevent the escape shuttle from arriving quickly." +/datum/game_mode/meteor + name = "meteor" + config_tag = "meteor" + report_type = "meteor" + false_report_weight = 1 + var/meteordelay = 2000 + var/nometeors = 0 + var/rampupdelta = 5 + required_players = 0 + + announce_span = "danger" + announce_text = "A major meteor shower is bombarding the station! The crew needs to evacuate or survive the onslaught." + + title_icon = "meteor" + +/datum/game_mode/meteor/process() + if(nometeors || meteordelay > world.time - SSticker.round_start_time) + return + + var/list/wavetype = GLOB.meteors_normal + var/meteorminutes = (world.time - SSticker.round_start_time - meteordelay) / 10 / 60 + + + if (prob(meteorminutes)) + wavetype = GLOB.meteors_threatening + + if (prob(meteorminutes/2)) + wavetype = GLOB.meteors_catastrophic + + var/ramp_up_final = clamp(round(meteorminutes/rampupdelta), 1, 10) + + spawn_meteors(ramp_up_final, wavetype) + + +/datum/game_mode/meteor/special_report() + var/survivors = 0 + var/list/survivor_list = list() + + for(var/mob/living/player in GLOB.player_list) + if(player.stat != DEAD) + ++survivors + + if(player.onCentCom()) + survivor_list += "[player.real_name] escaped to the safety of CentCom." + else if(player.onSyndieBase()) + survivor_list += "[player.real_name] escaped to the (relative) safety of Syndicate Space." + else + survivor_list += "[player.real_name] survived but is stranded without any hope of rescue." + + if(survivors) + return "
    The following survived the meteor storm:
    [survivor_list.Join("
    ")]
    " + else + return "
    Nobody survived the meteor storm!
    " + +/datum/game_mode/meteor/set_round_result() + ..() + SSticker.mode_result = "end - evacuation" + +/datum/game_mode/meteor/generate_report() + return "[pick("Asteroids have", "Meteors have", "Large rocks have", "Stellar minerals have", "Space hail has", "Debris has")] been detected near your station, and a collision is possible, \ + though unlikely. Be prepared for largescale impacts and destruction. Please note that the debris will prevent the escape shuttle from arriving quickly." diff --git a/code/game/gamemodes/nuclear/nuclear.dm b/code/game/gamemodes/nuclear/nuclear.dm index acdee76b847a..e81d2af21b78 100644 --- a/code/game/gamemodes/nuclear/nuclear.dm +++ b/code/game/gamemodes/nuclear/nuclear.dm @@ -73,7 +73,7 @@ /datum/game_mode/nuclear/set_round_result() ..() - var result = nuke_team.get_result() + var/result = nuke_team.get_result() switch(result) if(NUKE_RESULT_FLUKE) SSticker.mode_result = "loss - syndicate nuked - disk secured" @@ -122,7 +122,7 @@ gloves = /obj/item/clothing/gloves/combat back = /obj/item/storage/backpack/fireproof ears = /obj/item/radio/headset/syndicate/alt - l_pocket = /obj/item/pinpointer/nuke/syndicate + l_pocket = /obj/item/modular_computer/tablet/nukeops id = /obj/item/card/id/syndicate belt = /obj/item/gun/ballistic/automatic/pistol backpack_contents = list(/obj/item/storage/box/survival/syndie=1,\ diff --git a/code/game/gamemodes/objective_items.dm b/code/game/gamemodes/objective_items.dm index fa2f6c37605b..d61dc880c7bd 100644 --- a/code/game/gamemodes/objective_items.dm +++ b/code/game/gamemodes/objective_items.dm @@ -1,271 +1,271 @@ -//Contains the target item datums for Steal objectives. - -/datum/objective_item - var/name = "A silly bike horn! Honk!" - var/targetitem = /obj/item/bikehorn //typepath of the objective item - var/difficulty = 9001 //vaguely how hard it is to do this objective - var/list/excludefromjob = list() //If you don't want a job to get a certain objective (no captain stealing his own medal, etcetc) - var/list/altitems = list() //Items which can serve as an alternative to the objective (darn you blueprints) - var/list/special_equipment = list() - -/datum/objective_item/proc/check_special_completion() //for objectives with special checks (is that slime extract unused? does that intellicard have an ai in it? etcetc) - return 1 - -/datum/objective_item/proc/TargetExists() - return TRUE - -/datum/objective_item/steal/New() - ..() - if(TargetExists()) - GLOB.possible_items += src - else - qdel(src) - -/datum/objective_item/steal/Destroy() - GLOB.possible_items -= src - return ..() - -/datum/objective_item/steal/caplaser - name = "the captain's antique laser gun." - targetitem = /obj/item/gun/energy/laser/captain - difficulty = 5 - excludefromjob = list("Captain") - -/datum/objective_item/steal/hoslaser - name = "the head of security's personal laser gun." - targetitem = /obj/item/gun/energy/e_gun/hos - difficulty = 10 - excludefromjob = list("Head Of Security") - -/datum/objective_item/steal/handtele - name = "a hand teleporter." - targetitem = /obj/item/hand_tele - difficulty = 5 - excludefromjob = list("Captain", "Research Director") - -/datum/objective_item/steal/jetpack - name = "the Captain's jetpack." - targetitem = /obj/item/tank/jetpack/oxygen/captain - difficulty = 5 - excludefromjob = list("Captain") - -/datum/objective_item/steal/magboots - name = "the chief engineer's advanced magnetic boots." - targetitem = /obj/item/clothing/shoes/magboots/advance - difficulty = 5 - excludefromjob = list("Chief Engineer") - -/datum/objective_item/steal/capmedal - name = "the medal of captaincy." - targetitem = /obj/item/clothing/accessory/medal/gold/captain - difficulty = 5 - excludefromjob = list("Captain") - -/datum/objective_item/steal/hypo - name = "the hypospray." - targetitem = /obj/item/reagent_containers/hypospray/CMO - difficulty = 5 - excludefromjob = list("Chief Medical Officer") - -/datum/objective_item/steal/nukedisc - name = "the nuclear authentication disk." - targetitem = /obj/item/disk/nuclear - difficulty = 5 - excludefromjob = list("Captain") - -/datum/objective_item/steal/nukedisc/check_special_completion(obj/item/disk/nuclear/N) - return !N.fake - -/datum/objective_item/steal/reflector - name = "a reflector trenchcoat." - targetitem = /obj/item/clothing/suit/hooded/ablative - difficulty = 3 - excludefromjob = list("Head of Security", "Warden") - -/datum/objective_item/steal/reactive - name = "the reactive teleport armor." - targetitem = /obj/item/clothing/suit/armor/reactive/teleport - difficulty = 5 - excludefromjob = list("Research Director") - -/datum/objective_item/steal/documents - name = "any set of secret documents of any organization." - targetitem = /obj/item/documents //Any set of secret documents. Doesn't have to be NT's - difficulty = 5 - -/datum/objective_item/steal/nuke_core - name = "the heavily radioactive plutonium core from the onboard self-destruct. Take care to wear the proper safety equipment when extracting the core!" - targetitem = /obj/item/nuke_core - difficulty = 15 - -/datum/objective_item/steal/nuke_core/New() - special_equipment += /obj/item/storage/box/syndie_kit/nuke - ..() - -/datum/objective_item/steal/supermatter - name = "a sliver of a supermatter crystal. Be sure to use the proper safety equipment when extracting the sliver!" - targetitem = /obj/item/nuke_core/supermatter_sliver - difficulty = 15 - -/datum/objective_item/steal/supermatter/New() - special_equipment += /obj/item/storage/box/syndie_kit/supermatter - ..() - -/datum/objective_item/steal/supermatter/TargetExists() - return GLOB.main_supermatter_engine != null - -//Items with special checks! -/datum/objective_item/steal/plasma - name = "28 moles of plasma (full tank)." - targetitem = /obj/item/tank - difficulty = 3 - excludefromjob = list("Chief Engineer","Research Director","Station Engineer","Scientist","Atmospheric Technician") - -/datum/objective_item/steal/plasma/check_special_completion(obj/item/tank/T) - var/target_amount = text2num(name) - var/found_amount = 0 - found_amount += T.air_contents.get_moles(/datum/gas/plasma) - return found_amount>=target_amount - - -/datum/objective_item/steal/functionalai - name = "a functional AI." - targetitem = /obj/item/aicard - difficulty = 20 //beyond the impossible - -/datum/objective_item/steal/functionalai/check_special_completion(obj/item/aicard/C) - for(var/mob/living/silicon/ai/A in C) - if(isAI(A) && A.stat != DEAD) //See if any AI's are alive inside that card. - return 1 - return 0 - -/datum/objective_item/steal/blueprints - name = "the station blueprints." - targetitem = /obj/item/areaeditor/blueprints - difficulty = 10 - excludefromjob = list("Chief Engineer") - altitems = list(/obj/item/photo) - -/datum/objective_item/steal/blueprints/check_special_completion(obj/item/I) - if(istype(I, /obj/item/areaeditor/blueprints)) - return TRUE - if(istype(I, /obj/item/photo)) - var/obj/item/photo/P = I - if(P.picture.has_blueprints) //if the blueprints are in frame - return TRUE - return FALSE - -/datum/objective_item/steal/slime - name = "an unused sample of slime extract." - targetitem = /obj/item/slime_extract - difficulty = 3 - excludefromjob = list("Research Director","Scientist") - -/datum/objective_item/steal/slime/check_special_completion(obj/item/slime_extract/E) - if(E.Uses > 0) - return 1 - return 0 - -/datum/objective_item/steal/blackbox - name = "The Blackbox." - targetitem = /obj/item/blackbox - difficulty = 10 - excludefromjob = list("Chief Engineer","Station Engineer","Atmospheric Technician") - -//Unique Objectives -/datum/objective_item/unique/docs_red - name = "the \"Red\" secret documents." - targetitem = /obj/item/documents/syndicate/red - difficulty = 10 - -/datum/objective_item/unique/docs_blue - name = "the \"Blue\" secret documents." - targetitem = /obj/item/documents/syndicate/blue - difficulty = 10 - -/datum/objective_item/special/New() - ..() - if(TargetExists()) - GLOB.possible_items_special += src - else - qdel(src) - -/datum/objective_item/special/Destroy() - GLOB.possible_items_special -= src - return ..() - -//Old ninja objectives. -/datum/objective_item/special/pinpointer/nuke - name = "the captain's pinpointer." - targetitem = /obj/item/pinpointer - difficulty = 10 - -/datum/objective_item/special/aegun - name = "an advanced energy gun." - targetitem = /obj/item/gun/energy/e_gun/nuclear - difficulty = 10 - -/datum/objective_item/special/ddrill - name = "a diamond drill." - targetitem = /obj/item/pickaxe/drill/diamonddrill - difficulty = 10 - -/datum/objective_item/special/boh - name = "a bag of holding." - targetitem = /obj/item/storage/backpack/holding - difficulty = 10 - -/datum/objective_item/special/hypercell - name = "a hyper-capacity power cell." - targetitem = /obj/item/stock_parts/cell/hyper - difficulty = 5 - -/datum/objective_item/special/laserpointer - name = "a laser pointer." - targetitem = /obj/item/laser_pointer - difficulty = 5 - -/datum/objective_item/special/corgimeat - name = "a piece of corgi meat." - targetitem = /obj/item/reagent_containers/food/snacks/meat/slab/corgi - difficulty = 5 - -/datum/objective_item/stack/New() - ..() - if(TargetExists()) - GLOB.possible_items_special += src - else - qdel(src) - -/datum/objective_item/stack/Destroy() - GLOB.possible_items_special -= src - return ..() - -//Stack objectives get their own subtype -/datum/objective_item/stack - name = "5 cardboard." - targetitem = /obj/item/stack/sheet/cardboard - difficulty = 9001 - -/datum/objective_item/stack/check_special_completion(obj/item/stack/S) - var/target_amount = text2num(name) - var/found_amount = 0 - - if(istype(S, targetitem)) - found_amount = S.amount - return found_amount>=target_amount - -/datum/objective_item/stack/diamond - name = "10 diamonds." - targetitem = /obj/item/stack/sheet/mineral/diamond - difficulty = 10 - -/datum/objective_item/stack/gold - name = "50 gold bars." - targetitem = /obj/item/stack/sheet/mineral/gold - difficulty = 15 - -/datum/objective_item/stack/uranium - name = "25 refined uranium bars." - targetitem = /obj/item/stack/sheet/mineral/uranium - difficulty = 10 +//Contains the target item datums for Steal objectives. + +/datum/objective_item + var/name = "A silly bike horn! Honk!" + var/targetitem = /obj/item/bikehorn //typepath of the objective item + var/difficulty = 9001 //vaguely how hard it is to do this objective + var/list/excludefromjob = list() //If you don't want a job to get a certain objective (no captain stealing his own medal, etcetc) + var/list/altitems = list() //Items which can serve as an alternative to the objective (darn you blueprints) + var/list/special_equipment = list() + +/datum/objective_item/proc/check_special_completion() //for objectives with special checks (is that slime extract unused? does that intellicard have an ai in it? etcetc) + return 1 + +/datum/objective_item/proc/TargetExists() + return TRUE + +/datum/objective_item/steal/New() + ..() + if(TargetExists()) + GLOB.possible_items += src + else + qdel(src) + +/datum/objective_item/steal/Destroy() + GLOB.possible_items -= src + return ..() + +/datum/objective_item/steal/caplaser + name = "the captain's antique laser gun." + targetitem = /obj/item/gun/energy/laser/captain + difficulty = 5 + excludefromjob = list("Captain") + +/datum/objective_item/steal/hoslaser + name = "the head of security's personal laser gun." + targetitem = /obj/item/gun/energy/e_gun/hos + difficulty = 10 + excludefromjob = list("Head Of Security") + +/datum/objective_item/steal/handtele + name = "a hand teleporter." + targetitem = /obj/item/hand_tele + difficulty = 5 + excludefromjob = list("Captain", "Research Director") + +/datum/objective_item/steal/jetpack + name = "the Captain's jetpack." + targetitem = /obj/item/tank/jetpack/oxygen/captain + difficulty = 5 + excludefromjob = list("Captain") + +/datum/objective_item/steal/magboots + name = "the chief engineer's advanced magnetic boots." + targetitem = /obj/item/clothing/shoes/magboots/advance + difficulty = 5 + excludefromjob = list("Chief Engineer") + +/datum/objective_item/steal/capmedal + name = "the medal of captaincy." + targetitem = /obj/item/clothing/accessory/medal/gold/captain + difficulty = 5 + excludefromjob = list("Captain") + +/datum/objective_item/steal/hypo + name = "the hypospray." + targetitem = /obj/item/reagent_containers/hypospray/CMO + difficulty = 5 + excludefromjob = list("Chief Medical Officer") + +/datum/objective_item/steal/nukedisc + name = "the nuclear authentication disk." + targetitem = /obj/item/disk/nuclear + difficulty = 5 + excludefromjob = list("Captain") + +/datum/objective_item/steal/nukedisc/check_special_completion(obj/item/disk/nuclear/N) + return !N.fake + +/datum/objective_item/steal/reflector + name = "a reflector trenchcoat." + targetitem = /obj/item/clothing/suit/hooded/ablative + difficulty = 3 + excludefromjob = list("Head of Security", "Warden") + +/datum/objective_item/steal/reactive + name = "the reactive teleport armor." + targetitem = /obj/item/clothing/suit/armor/reactive/teleport + difficulty = 5 + excludefromjob = list("Research Director") + +/datum/objective_item/steal/documents + name = "any set of secret documents of any organization." + targetitem = /obj/item/documents //Any set of secret documents. Doesn't have to be NT's + difficulty = 5 + +/datum/objective_item/steal/nuke_core + name = "the heavily radioactive plutonium core from the onboard self-destruct. Take care to wear the proper safety equipment when extracting the core!" + targetitem = /obj/item/nuke_core + difficulty = 15 + +/datum/objective_item/steal/nuke_core/New() + special_equipment += /obj/item/storage/box/syndie_kit/nuke + ..() + +/datum/objective_item/steal/supermatter + name = "a sliver of a supermatter crystal. Be sure to use the proper safety equipment when extracting the sliver!" + targetitem = /obj/item/nuke_core/supermatter_sliver + difficulty = 15 + +/datum/objective_item/steal/supermatter/New() + special_equipment += /obj/item/storage/box/syndie_kit/supermatter + ..() + +/datum/objective_item/steal/supermatter/TargetExists() + return GLOB.main_supermatter_engine != null + +//Items with special checks! +/datum/objective_item/steal/plasma + name = "28 moles of plasma (full tank)." + targetitem = /obj/item/tank + difficulty = 3 + excludefromjob = list("Chief Engineer","Research Director","Station Engineer","Scientist","Atmospheric Technician") + +/datum/objective_item/steal/plasma/check_special_completion(obj/item/tank/T) + var/target_amount = text2num(name) + var/found_amount = 0 + found_amount += T.air_contents.get_moles(/datum/gas/plasma) + return found_amount>=target_amount + + +/datum/objective_item/steal/functionalai + name = "a functional AI." + targetitem = /obj/item/aicard + difficulty = 20 //beyond the impossible + +/datum/objective_item/steal/functionalai/check_special_completion(obj/item/aicard/C) + for(var/mob/living/silicon/ai/A in C) + if(isAI(A) && A.stat != DEAD) //See if any AI's are alive inside that card. + return 1 + return 0 + +/datum/objective_item/steal/blueprints + name = "the station blueprints." + targetitem = /obj/item/areaeditor/blueprints + difficulty = 10 + excludefromjob = list("Chief Engineer") + altitems = list(/obj/item/photo) + +/datum/objective_item/steal/blueprints/check_special_completion(obj/item/I) + if(istype(I, /obj/item/areaeditor/blueprints)) + return TRUE + if(istype(I, /obj/item/photo)) + var/obj/item/photo/P = I + if(P.picture.has_blueprints) //if the blueprints are in frame + return TRUE + return FALSE + +/datum/objective_item/steal/slime + name = "an unused sample of slime extract." + targetitem = /obj/item/slime_extract + difficulty = 3 + excludefromjob = list("Research Director","Scientist") + +/datum/objective_item/steal/slime/check_special_completion(obj/item/slime_extract/E) + if(E.Uses > 0) + return 1 + return 0 + +/datum/objective_item/steal/blackbox + name = "The Blackbox." + targetitem = /obj/item/blackbox + difficulty = 10 + excludefromjob = list("Chief Engineer","Station Engineer","Atmospheric Technician") + +//Unique Objectives +/datum/objective_item/unique/docs_red + name = "the \"Red\" secret documents." + targetitem = /obj/item/documents/syndicate/red + difficulty = 10 + +/datum/objective_item/unique/docs_blue + name = "the \"Blue\" secret documents." + targetitem = /obj/item/documents/syndicate/blue + difficulty = 10 + +/datum/objective_item/special/New() + ..() + if(TargetExists()) + GLOB.possible_items_special += src + else + qdel(src) + +/datum/objective_item/special/Destroy() + GLOB.possible_items_special -= src + return ..() + +//Old ninja objectives. +/datum/objective_item/special/pinpointer/nuke + name = "the captain's pinpointer." + targetitem = /obj/item/pinpointer + difficulty = 10 + +/datum/objective_item/special/aegun + name = "an advanced energy gun." + targetitem = /obj/item/gun/energy/e_gun/nuclear + difficulty = 10 + +/datum/objective_item/special/ddrill + name = "a diamond drill." + targetitem = /obj/item/pickaxe/drill/diamonddrill + difficulty = 10 + +/datum/objective_item/special/boh + name = "a bag of holding." + targetitem = /obj/item/storage/backpack/holding + difficulty = 10 + +/datum/objective_item/special/hypercell + name = "a hyper-capacity power cell." + targetitem = /obj/item/stock_parts/cell/hyper + difficulty = 5 + +/datum/objective_item/special/laserpointer + name = "a laser pointer." + targetitem = /obj/item/laser_pointer + difficulty = 5 + +/datum/objective_item/special/corgimeat + name = "a piece of corgi meat." + targetitem = /obj/item/reagent_containers/food/snacks/meat/slab/corgi + difficulty = 5 + +/datum/objective_item/stack/New() + ..() + if(TargetExists()) + GLOB.possible_items_special += src + else + qdel(src) + +/datum/objective_item/stack/Destroy() + GLOB.possible_items_special -= src + return ..() + +//Stack objectives get their own subtype +/datum/objective_item/stack + name = "5 cardboard." + targetitem = /obj/item/stack/sheet/cardboard + difficulty = 9001 + +/datum/objective_item/stack/check_special_completion(obj/item/stack/S) + var/target_amount = text2num(name) + var/found_amount = 0 + + if(istype(S, targetitem)) + found_amount = S.amount + return found_amount>=target_amount + +/datum/objective_item/stack/diamond + name = "10 diamonds." + targetitem = /obj/item/stack/sheet/mineral/diamond + difficulty = 10 + +/datum/objective_item/stack/gold + name = "50 gold bars." + targetitem = /obj/item/stack/sheet/mineral/gold + difficulty = 15 + +/datum/objective_item/stack/uranium + name = "25 refined uranium bars." + targetitem = /obj/item/stack/sheet/mineral/uranium + difficulty = 10 diff --git a/code/game/gamemodes/sandbox/airlock_maker.dm b/code/game/gamemodes/sandbox/airlock_maker.dm index 6b988fd07721..ddb622ab08c5 100644 --- a/code/game/gamemodes/sandbox/airlock_maker.dm +++ b/code/game/gamemodes/sandbox/airlock_maker.dm @@ -1,141 +1,141 @@ -/* - This is for the sandbox for now, - maybe useful later for an actual thing? - -Sayu -*/ - -/obj/structure/door_assembly - var/datum/airlock_maker/maker = null - -/obj/structure/door_assembly/attack_hand() - . = ..() - if(.) - return - if(maker) - maker.interact() - -/datum/airlock_maker - var/obj/structure/door_assembly/linked = null - - var/list/access_used = null - var/require_all = 1 - - var/paintjob = "none" - var/glassdoor = 0 - - var/doorname = "airlock" - -/datum/airlock_maker/New(var/atom/target_loc) - linked = new(target_loc) - linked.maker = src - linked.anchored = FALSE - access_used = list() - - interact() - -/datum/airlock_maker/proc/linkpretty(href,desc,active) - if(!desc) - var/static/list/defaults = list("No","Yes") - desc = defaults[active+1] - if(active) - return "[desc]" - return "[desc]" - -/datum/airlock_maker/proc/interact() - var/list/leftcolumn = list() - var/list/rightcolumn = list() - leftcolumn += "Required Access" - for(var/access in get_all_accesses()) - leftcolumn += linkpretty("access=[access]",get_access_desc(access),access in access_used) - leftcolumn += "Require all listed accesses: [linkpretty("reqall",null,require_all)]" - - rightcolumn += "Paintjob" - for(var/option in list("none","engineering","atmos","security","command","medical","research","mining","maintenance","external","highsecurity")) - rightcolumn += linkpretty("paint=[option]",option,option == paintjob) - rightcolumn += "Glass door: " + linkpretty("glass",null,glassdoor) + "

    " - var/length = max(leftcolumn.len,rightcolumn.len) - - var/dat = "You may move the model airlock around. A new airlock will be built in its space when you click done, below.

    " - dat += "Door name: \"[doorname]\"" - dat += "" - for(var/i=1; i<=length; i++) - dat += "" - - dat += "
    " - if(i<=leftcolumn.len) - dat += leftcolumn[i] - dat += "" - if(i<=rightcolumn.len) - dat += rightcolumn[i] - dat += "

    Finalize Airlock Construction | Cancel and Destroy Airlock" - usr << browse(dat,"window=airlockmaker") - -/datum/airlock_maker/Topic(var/href,var/list/href_list) - if(!usr) - return - if(!src || !linked || !linked.loc) - usr << browse(null,"window=airlockmaker") - return - - if("rename" in href_list) - var/newname = stripped_input(usr,"New airlock name:","Name the airlock",doorname) - if(newname) - doorname = newname - if("access" in href_list) - var/value = text2num(href_list["access"]) - access_used ^= value - if("reqall" in href_list) - require_all = !require_all - if("paint" in href_list) - paintjob = href_list["paint"] - if("glass" in href_list) - glassdoor = !glassdoor - - if("cancel" in href_list) - usr << browse(null,"window=airlockmaker") - qdel(linked) - qdel(src) - return - - if("done" in href_list) - usr << browse(null,"window=airlockmaker") - var/turf/t_loc = linked.loc - qdel(linked) - if(!istype(t_loc)) - return - - var/target_type = "/obj/machinery/door/airlock" - if(glassdoor) - if(paintjob != "none") - if(paintjob in list("external","highsecurity","maintenance")) // no glass version - target_type += "/[paintjob]" - else - target_type += "/glass_[paintjob]" - else - target_type += "/glass" - else if(paintjob != "none") - target_type += "/[paintjob]" - var/final = target_type - target_type = text2path(final) - if(!target_type) - to_chat(usr, "Didn't work, contact Sayu with this: [final]") - usr << browse(null,"window=airlockmaker") - return - - var/obj/machinery/door/D = new target_type(t_loc) - - D.name = doorname - - if(access_used.len == 0) - D.req_access = null - D.req_one_access = null - else if(require_all) - D.req_access = access_used.Copy() - D.req_one_access = null - else - D.req_access = null - D.req_one_access = access_used.Copy() - - return - - interact() +/* + This is for the sandbox for now, + maybe useful later for an actual thing? + -Sayu +*/ + +/obj/structure/door_assembly + var/datum/airlock_maker/maker = null + +/obj/structure/door_assembly/attack_hand() + . = ..() + if(.) + return + if(maker) + maker.interact() + +/datum/airlock_maker + var/obj/structure/door_assembly/linked = null + + var/list/access_used = null + var/require_all = 1 + + var/paintjob = "none" + var/glassdoor = 0 + + var/doorname = "airlock" + +/datum/airlock_maker/New(var/atom/target_loc) + linked = new(target_loc) + linked.maker = src + linked.anchored = FALSE + access_used = list() + + interact() + +/datum/airlock_maker/proc/linkpretty(href,desc,active) + if(!desc) + var/static/list/defaults = list("No","Yes") + desc = defaults[active+1] + if(active) + return "[desc]" + return "[desc]" + +/datum/airlock_maker/proc/interact() + var/list/leftcolumn = list() + var/list/rightcolumn = list() + leftcolumn += "Required Access" + for(var/access in get_all_accesses()) + leftcolumn += linkpretty("access=[access]",get_access_desc(access),access in access_used) + leftcolumn += "Require all listed accesses: [linkpretty("reqall",null,require_all)]" + + rightcolumn += "Paintjob" + for(var/option in list("none","engineering","atmos","security","command","medical","research","mining","maintenance","external","highsecurity")) + rightcolumn += linkpretty("paint=[option]",option,option == paintjob) + rightcolumn += "Glass door: " + linkpretty("glass",null,glassdoor) + "

    " + var/length = max(leftcolumn.len,rightcolumn.len) + + var/dat = "You may move the model airlock around. A new airlock will be built in its space when you click done, below.

    " + dat += "Door name: \"[doorname]\"" + dat += "" + for(var/i=1; i<=length; i++) + dat += "" + + dat += "
    " + if(i<=leftcolumn.len) + dat += leftcolumn[i] + dat += "" + if(i<=rightcolumn.len) + dat += rightcolumn[i] + dat += "

    Finalize Airlock Construction | Cancel and Destroy Airlock" + usr << browse(dat,"window=airlockmaker") + +/datum/airlock_maker/Topic(var/href,var/list/href_list) + if(!usr) + return + if(!src || !linked || !linked.loc) + usr << browse(null,"window=airlockmaker") + return + + if("rename" in href_list) + var/newname = stripped_input(usr,"New airlock name:","Name the airlock",doorname) + if(newname) + doorname = newname + if("access" in href_list) + var/value = text2num(href_list["access"]) + access_used ^= value + if("reqall" in href_list) + require_all = !require_all + if("paint" in href_list) + paintjob = href_list["paint"] + if("glass" in href_list) + glassdoor = !glassdoor + + if("cancel" in href_list) + usr << browse(null,"window=airlockmaker") + qdel(linked) + qdel(src) + return + + if("done" in href_list) + usr << browse(null,"window=airlockmaker") + var/turf/t_loc = linked.loc + qdel(linked) + if(!istype(t_loc)) + return + + var/target_type = "/obj/machinery/door/airlock" + if(glassdoor) + if(paintjob != "none") + if(paintjob in list("external","highsecurity","maintenance")) // no glass version + target_type += "/[paintjob]" + else + target_type += "/glass_[paintjob]" + else + target_type += "/glass" + else if(paintjob != "none") + target_type += "/[paintjob]" + var/final = target_type + target_type = text2path(final) + if(!target_type) + to_chat(usr, "Didn't work, contact Sayu with this: [final]") + usr << browse(null,"window=airlockmaker") + return + + var/obj/machinery/door/D = new target_type(t_loc) + + D.name = doorname + + if(access_used.len == 0) + D.req_access = null + D.req_one_access = null + else if(require_all) + D.req_access = access_used.Copy() + D.req_one_access = null + else + D.req_access = null + D.req_one_access = access_used.Copy() + + return + + interact() diff --git a/code/game/gamemodes/sandbox/h_sandbox.dm b/code/game/gamemodes/sandbox/h_sandbox.dm index 649b10fcf3ad..0041e6c2371e 100644 --- a/code/game/gamemodes/sandbox/h_sandbox.dm +++ b/code/game/gamemodes/sandbox/h_sandbox.dm @@ -1,302 +1,302 @@ -GLOBAL_VAR_INIT(hsboxspawn, TRUE) - -/mob/proc/CanBuild() - sandbox = new/datum/hSB - sandbox.owner = src.ckey - if(src.client.holder) - sandbox.admin = 1 - verbs += new/mob/proc/sandbox_panel -/mob/proc/sandbox_panel() - set name = "Sandbox Panel" - if(sandbox) - sandbox.update() - -/datum/hSB - var/owner = null - var/admin = 0 - - var/static/clothinfo = null - var/static/reaginfo = null - var/static/objinfo = null - var/canisterinfo = null - var/hsbinfo = null - //items that shouldn't spawn on the floor because they would bug or act weird - var/static/list/spawn_forbidden = list( - /obj/item/tk_grab, /obj/item/implant, // not implanter, the actual thing that is inside you - /obj/item/assembly, /obj/item/onetankbomb, /obj/item/pda/ai, - /obj/item/smallDelivery, /obj/projectile, - /obj/item/borg/sight, /obj/item/borg/stun, /obj/item/robot_module) - -/datum/hSB/proc/update() - var/static/list/hrefs = list( - "Space Gear", - "Suit Up (Space Travel Gear)" = "hsbsuit", - "Spawn Gas Mask" = "hsbspawn&path=[/obj/item/clothing/mask/gas]", - "Spawn Emergency Air Tank" = "hsbspawn&path=[/obj/item/tank/internals/emergency_oxygen/double]", - - "Standard Tools", - "Spawn Flashlight" = "hsbspawn&path=[/obj/item/flashlight]", - "Spawn Toolbox" = "hsbspawn&path=[/obj/item/storage/toolbox/mechanical]", - "Spawn Experimental Welding tool" = "hsbspawn&path=[/obj/item/weldingtool/experimental]", - "Spawn Light Replacer" = "hsbspawn&path=[/obj/item/lightreplacer]", - "Spawn Medical Kit" = "hsbspawn&path=[/obj/item/storage/firstaid/regular]", - "Spawn All-Access ID" = "hsbaaid", - - "Building Supplies", - "Spawn 50 Wood" = "hsbwood", - "Spawn 50 Metal" = "hsbmetal", - "Spawn 50 Plasteel" = "hsbplasteel", - "Spawn 50 Reinforced Glass" = "hsbrglass", - "Spawn 50 Glass" = "hsbglass", - "Spawn Box of Materials" = "hsbspawn&path=[/obj/item/storage/box/material]", - "Spawn Full Cable Coil" = "hsbspawn&path=[/obj/item/stack/cable_coil]", - "Spawn Hyper Capacity Power Cell" = "hsbspawn&path=[/obj/item/stock_parts/cell/hyper]", - "Spawn Inf. Capacity Power Cell" = "hsbspawn&path=[/obj/item/stock_parts/cell/infinite]", - "Spawn Rapid Construction Device" = "hsbrcd", - "Spawn RCD Ammo" = "hsb_safespawn&path=[/obj/item/rcd_ammo]", - "Spawn Airlock" = "hsbairlock", - - "Miscellaneous", - "Spawn Air Scrubber" = "hsbscrubber", - "Spawn CentCom Technology Disk" = "hsbspawn&path=[/obj/item/disk/tech_disk/debug]", - "Spawn Adminordrazine" = "hsbspawn&path=[/obj/item/reagent_containers/pill/adminordrazine]", - "Spawn Water Tank" = "hsbspawn&path=[/obj/structure/reagent_dispensers/watertank]", - - "Bots", - "Spawn Cleanbot" = "hsbspawn&path=[/mob/living/simple_animal/bot/cleanbot]", - "Spawn Floorbot" = "hsbspawn&path=[/mob/living/simple_animal/bot/floorbot]", - "Spawn Medbot" = "hsbspawn&path=[/mob/living/simple_animal/bot/medbot]", - - "Canisters", - "Spawn O2 Canister" = "hsbspawn&path=[/obj/machinery/portable_atmospherics/canister/oxygen]", - "Spawn Air Canister" = "hsbspawn&path=[/obj/machinery/portable_atmospherics/canister/air]") - - - if(!hsbinfo) - hsbinfo = "
    Sandbox Panel

    " - if(admin) - hsbinfo += "Administration
    " - hsbinfo += "- Toggle Object Spawning
    " - hsbinfo += "- Toggle Item Spawn Panel Auto-close
    " - hsbinfo += "Canister Spawning
    " - hsbinfo += "- Spawn Plasma Canister
    " - hsbinfo += "- Spawn CO2 Canister
    " - hsbinfo += "- Spawn Nitrogen Canister
    " - hsbinfo += "- Spawn N2O Canister
    " - else - hsbinfo += "Some item spawning may be disabled by the administrators.
    " - hsbinfo += "Only administrators may spawn dangerous canisters.
    " - for(var/T in hrefs) - var/href = hrefs[T] - if(href) - hsbinfo += "- [T]
    " - else - hsbinfo += "
    [T]
    " - hsbinfo += "
    " - hsbinfo += "- Spawn Clothing...
    " - hsbinfo += "- Spawn Reagent Container...
    " - hsbinfo += "- Spawn Other Item...

    " - - usr << browse(hsbinfo, "window=hsbpanel") - -/datum/hSB/Topic(href, href_list) - if(!usr || !src || !(src.owner == usr.ckey)) - if(usr) - usr << browse(null,"window=sandbox") - return - - if(href_list["hsb"]) - switch(href_list["hsb"]) - // - // Admin: toggle spawning - // - if("hsbtobj") - if(!admin) return - if(GLOB.hsboxspawn) - to_chat(world, "Sandbox: \black[usr.key] has disabled object spawning!") - GLOB.hsboxspawn = FALSE - return - else - to_chat(world, "Sandbox: \black[usr.key] has enabled object spawning!") - GLOB.hsboxspawn = TRUE - return - // - // Admin: Toggle auto-close - // - if("hsbtac") - if(!admin) return - var/sbac = CONFIG_GET(flag/sandbox_autoclose) - if(sbac) - to_chat(world, "Sandbox: \black [usr.key] has removed the object spawn limiter.") - else - to_chat(world, "Sandbox: \black [usr.key] has added a limiter to object spawning. The window will now auto-close after use.") - CONFIG_SET(flag/sandbox_autoclose, !sbac) - return - // - // Spacesuit with full air jetpack set as internals - // - if("hsbsuit") - var/mob/living/carbon/human/P = usr - if(!istype(P)) return - if(P.wear_suit) - P.wear_suit.forceMove(P.drop_location()) - P.wear_suit.layer = initial(P.wear_suit.layer) - P.wear_suit.plane = initial(P.wear_suit.plane) - P.wear_suit = null - P.wear_suit = new/obj/item/clothing/suit/space(P) - P.wear_suit.layer = ABOVE_HUD_LAYER - P.wear_suit.plane = ABOVE_HUD_PLANE - P.update_inv_wear_suit() - if(P.head) - P.head.forceMove(P.drop_location()) - P.head.layer = initial(P.head.layer) - P.head.plane = initial(P.head.plane) - P.head = null - P.head = new/obj/item/clothing/head/helmet/space(P) - P.head.layer = ABOVE_HUD_LAYER - P.head.plane = ABOVE_HUD_PLANE - P.update_inv_head() - if(P.wear_mask) - P.wear_mask.forceMove(P.drop_location()) - P.wear_mask.layer = initial(P.wear_mask.layer) - P.wear_mask.plane = initial(P.wear_mask.plane) - P.wear_mask = null - P.wear_mask = new/obj/item/clothing/mask/gas(P) - P.wear_mask.layer = ABOVE_HUD_LAYER - P.wear_mask.plane = ABOVE_HUD_PLANE - P.update_inv_wear_mask() - if(P.back) - P.back.forceMove(P.drop_location()) - P.back.layer = initial(P.back.layer) - P.back.plane = initial(P.back.plane) - P.back = null - P.back = new/obj/item/tank/jetpack/oxygen(P) - P.back.layer = ABOVE_HUD_LAYER - P.back.plane = ABOVE_HUD_PLANE - P.update_inv_back() - P.internal = P.back - P.update_internals_hud_icon(1) - - if("hsbscrubber") // This is beyond its normal capability but this is sandbox and you spawned one, I assume you need it - var/obj/hsb = new/obj/machinery/portable_atmospherics/scrubber{volume_rate=50*ONE_ATMOSPHERE;on=1}(usr.loc) - hsb.update_icon() // hackish but it wasn't meant to be spawned I guess? - - // - // Stacked Materials - // - - if("hsbrglass") - new/obj/item/stack/sheet/rglass{amount=50}(usr.loc) - - if("hsbmetal") - new/obj/item/stack/sheet/metal{amount=50}(usr.loc) - - if("hsbplasteel") - new/obj/item/stack/sheet/plasteel{amount=50}(usr.loc) - - if("hsbglass") - new/obj/item/stack/sheet/glass{amount=50}(usr.loc) - - if("hsbwood") - new/obj/item/stack/sheet/mineral/wood{amount=50}(usr.loc) - - // - // All access ID - // - if("hsbaaid") - var/obj/item/card/id/gold/ID = new(usr.loc) - ID.registered_name = usr.real_name - ID.assignment = "Sandbox" - ID.access = get_all_accesses() - ID.update_label() - - // - // RCD - starts with full clip - // Spawn check due to grief potential (destroying floors, walls, etc) - // - if("hsbrcd") - if(!GLOB.hsboxspawn) return - - new/obj/item/construction/rcd/combat(usr.loc) - - // - // New sandbox airlock maker - // - if("hsbairlock") - new /datum/airlock_maker(usr.loc) - - // - // Object spawn window - // - - // Clothing - if("hsbcloth") - if(!GLOB.hsboxspawn) return - - if(!clothinfo) - clothinfo = "Clothing (Reagent Containers) (Other Items)

    " - var/list/all_items = subtypesof(/obj/item/clothing) - for(var/typekey in spawn_forbidden) - all_items -= typesof(typekey) - for(var/O in reverseRange(all_items)) - clothinfo += "[O]
    " - - usr << browse(clothinfo,"window=sandbox") - - // Reagent containers - if("hsbreag") - if(!GLOB.hsboxspawn) return - - if(!reaginfo) - reaginfo = "Reagent Containers (Clothing) (Other Items)

    " - var/list/all_items = subtypesof(/obj/item/reagent_containers) - for(var/typekey in spawn_forbidden) - all_items -= typesof(typekey) - for(var/O in reverseRange(all_items)) - reaginfo += "[O]
    " - - usr << browse(reaginfo,"window=sandbox") - - // Other items - if("hsbobj") - if(!GLOB.hsboxspawn) return - - if(!objinfo) - objinfo = "Other Items (Clothing) (Reagent Containers)

    " - var/list/all_items = subtypesof(/obj/item/) - typesof(/obj/item/clothing) - typesof(/obj/item/reagent_containers) - for(var/typekey in spawn_forbidden) - all_items -= typesof(typekey) - - for(var/O in reverseRange(all_items)) - objinfo += "[O]
    " - - usr << browse(objinfo,"window=sandbox") - - // - // Safespawn checks to see if spawning is disabled. - // - if("hsb_safespawn") - if(!GLOB.hsboxspawn) - usr << browse(null,"window=sandbox") - return - - var/typepath = text2path(href_list["path"]) - if(!typepath) - to_chat(usr, "Bad path: \"[href_list["path"]]\"") - return - new typepath(usr.loc) - - if(CONFIG_GET(flag/sandbox_autoclose)) - usr << browse(null,"window=sandbox") - // - // For everything else in the href list - // - if("hsbspawn") - var/typepath = text2path(href_list["path"]) - if(!typepath) - to_chat(usr, "Bad path: \"[href_list["path"]]\"") - return - new typepath(usr.loc) - - if(CONFIG_GET(flag/sandbox_autoclose)) - usr << browse(null,"window=sandbox") +GLOBAL_VAR_INIT(hsboxspawn, TRUE) + +/mob/proc/CanBuild() + sandbox = new/datum/hSB + sandbox.owner = src.ckey + if(src.client.holder) + sandbox.admin = 1 + verbs += new/mob/proc/sandbox_panel +/mob/proc/sandbox_panel() + set name = "Sandbox Panel" + if(sandbox) + sandbox.update() + +/datum/hSB + var/owner = null + var/admin = 0 + + var/static/clothinfo = null + var/static/reaginfo = null + var/static/objinfo = null + var/canisterinfo = null + var/hsbinfo = null + //items that shouldn't spawn on the floor because they would bug or act weird + var/static/list/spawn_forbidden = list( + /obj/item/tk_grab, /obj/item/implant, // not implanter, the actual thing that is inside you + /obj/item/assembly, /obj/item/onetankbomb, /obj/item/pda/ai, + /obj/item/smallDelivery, /obj/projectile, + /obj/item/borg/sight, /obj/item/borg/stun, /obj/item/robot_module) + +/datum/hSB/proc/update() + var/static/list/hrefs = list( + "Space Gear", + "Suit Up (Space Travel Gear)" = "hsbsuit", + "Spawn Gas Mask" = "hsbspawn&path=[/obj/item/clothing/mask/gas]", + "Spawn Emergency Air Tank" = "hsbspawn&path=[/obj/item/tank/internals/emergency_oxygen/double]", + + "Standard Tools", + "Spawn Flashlight" = "hsbspawn&path=[/obj/item/flashlight]", + "Spawn Toolbox" = "hsbspawn&path=[/obj/item/storage/toolbox/mechanical]", + "Spawn Experimental Welding tool" = "hsbspawn&path=[/obj/item/weldingtool/experimental]", + "Spawn Light Replacer" = "hsbspawn&path=[/obj/item/lightreplacer]", + "Spawn Medical Kit" = "hsbspawn&path=[/obj/item/storage/firstaid/regular]", + "Spawn All-Access ID" = "hsbaaid", + + "Building Supplies", + "Spawn 50 Wood" = "hsbwood", + "Spawn 50 Metal" = "hsbmetal", + "Spawn 50 Plasteel" = "hsbplasteel", + "Spawn 50 Reinforced Glass" = "hsbrglass", + "Spawn 50 Glass" = "hsbglass", + "Spawn Box of Materials" = "hsbspawn&path=[/obj/item/storage/box/material]", + "Spawn Full Cable Coil" = "hsbspawn&path=[/obj/item/stack/cable_coil]", + "Spawn Hyper Capacity Power Cell" = "hsbspawn&path=[/obj/item/stock_parts/cell/hyper]", + "Spawn Inf. Capacity Power Cell" = "hsbspawn&path=[/obj/item/stock_parts/cell/infinite]", + "Spawn Rapid Construction Device" = "hsbrcd", + "Spawn RCD Ammo" = "hsb_safespawn&path=[/obj/item/rcd_ammo]", + "Spawn Airlock" = "hsbairlock", + + "Miscellaneous", + "Spawn Air Scrubber" = "hsbscrubber", + "Spawn CentCom Technology Disk" = "hsbspawn&path=[/obj/item/disk/tech_disk/debug]", + "Spawn Adminordrazine" = "hsbspawn&path=[/obj/item/reagent_containers/pill/adminordrazine]", + "Spawn Water Tank" = "hsbspawn&path=[/obj/structure/reagent_dispensers/watertank]", + + "Bots", + "Spawn Cleanbot" = "hsbspawn&path=[/mob/living/simple_animal/bot/cleanbot]", + "Spawn Floorbot" = "hsbspawn&path=[/mob/living/simple_animal/bot/floorbot]", + "Spawn Medbot" = "hsbspawn&path=[/mob/living/simple_animal/bot/medbot]", + + "Canisters", + "Spawn O2 Canister" = "hsbspawn&path=[/obj/machinery/portable_atmospherics/canister/oxygen]", + "Spawn Air Canister" = "hsbspawn&path=[/obj/machinery/portable_atmospherics/canister/air]") + + + if(!hsbinfo) + hsbinfo = "
    Sandbox Panel

    " + if(admin) + hsbinfo += "Administration
    " + hsbinfo += "- Toggle Object Spawning
    " + hsbinfo += "- Toggle Item Spawn Panel Auto-close
    " + hsbinfo += "Canister Spawning
    " + hsbinfo += "- Spawn Plasma Canister
    " + hsbinfo += "- Spawn CO2 Canister
    " + hsbinfo += "- Spawn Nitrogen Canister
    " + hsbinfo += "- Spawn N2O Canister
    " + else + hsbinfo += "Some item spawning may be disabled by the administrators.
    " + hsbinfo += "Only administrators may spawn dangerous canisters.
    " + for(var/T in hrefs) + var/href = hrefs[T] + if(href) + hsbinfo += "- [T]
    " + else + hsbinfo += "
    [T]
    " + hsbinfo += "
    " + hsbinfo += "- Spawn Clothing...
    " + hsbinfo += "- Spawn Reagent Container...
    " + hsbinfo += "- Spawn Other Item...

    " + + usr << browse(hsbinfo, "window=hsbpanel") + +/datum/hSB/Topic(href, href_list) + if(!usr || !src || !(src.owner == usr.ckey)) + if(usr) + usr << browse(null,"window=sandbox") + return + + if(href_list["hsb"]) + switch(href_list["hsb"]) + // + // Admin: toggle spawning + // + if("hsbtobj") + if(!admin) return + if(GLOB.hsboxspawn) + to_chat(world, "Sandbox: \black[usr.key] has disabled object spawning!") + GLOB.hsboxspawn = FALSE + return + else + to_chat(world, "Sandbox: \black[usr.key] has enabled object spawning!") + GLOB.hsboxspawn = TRUE + return + // + // Admin: Toggle auto-close + // + if("hsbtac") + if(!admin) return + var/sbac = CONFIG_GET(flag/sandbox_autoclose) + if(sbac) + to_chat(world, "Sandbox: \black [usr.key] has removed the object spawn limiter.") + else + to_chat(world, "Sandbox: \black [usr.key] has added a limiter to object spawning. The window will now auto-close after use.") + CONFIG_SET(flag/sandbox_autoclose, !sbac) + return + // + // Spacesuit with full air jetpack set as internals + // + if("hsbsuit") + var/mob/living/carbon/human/P = usr + if(!istype(P)) return + if(P.wear_suit) + P.wear_suit.forceMove(P.drop_location()) + P.wear_suit.layer = initial(P.wear_suit.layer) + P.wear_suit.plane = initial(P.wear_suit.plane) + P.wear_suit = null + P.wear_suit = new/obj/item/clothing/suit/space(P) + P.wear_suit.layer = ABOVE_HUD_LAYER + P.wear_suit.plane = ABOVE_HUD_PLANE + P.update_inv_wear_suit() + if(P.head) + P.head.forceMove(P.drop_location()) + P.head.layer = initial(P.head.layer) + P.head.plane = initial(P.head.plane) + P.head = null + P.head = new/obj/item/clothing/head/helmet/space(P) + P.head.layer = ABOVE_HUD_LAYER + P.head.plane = ABOVE_HUD_PLANE + P.update_inv_head() + if(P.wear_mask) + P.wear_mask.forceMove(P.drop_location()) + P.wear_mask.layer = initial(P.wear_mask.layer) + P.wear_mask.plane = initial(P.wear_mask.plane) + P.wear_mask = null + P.wear_mask = new/obj/item/clothing/mask/gas(P) + P.wear_mask.layer = ABOVE_HUD_LAYER + P.wear_mask.plane = ABOVE_HUD_PLANE + P.update_inv_wear_mask() + if(P.back) + P.back.forceMove(P.drop_location()) + P.back.layer = initial(P.back.layer) + P.back.plane = initial(P.back.plane) + P.back = null + P.back = new/obj/item/tank/jetpack/oxygen(P) + P.back.layer = ABOVE_HUD_LAYER + P.back.plane = ABOVE_HUD_PLANE + P.update_inv_back() + P.internal = P.back + P.update_internals_hud_icon(1) + + if("hsbscrubber") // This is beyond its normal capability but this is sandbox and you spawned one, I assume you need it + var/obj/hsb = new/obj/machinery/portable_atmospherics/scrubber{volume_rate=50*ONE_ATMOSPHERE;on=1}(usr.loc) + hsb.update_icon() // hackish but it wasn't meant to be spawned I guess? + + // + // Stacked Materials + // + + if("hsbrglass") + new/obj/item/stack/sheet/rglass{amount=50}(usr.loc) + + if("hsbmetal") + new/obj/item/stack/sheet/metal{amount=50}(usr.loc) + + if("hsbplasteel") + new/obj/item/stack/sheet/plasteel{amount=50}(usr.loc) + + if("hsbglass") + new/obj/item/stack/sheet/glass{amount=50}(usr.loc) + + if("hsbwood") + new/obj/item/stack/sheet/mineral/wood{amount=50}(usr.loc) + + // + // All access ID + // + if("hsbaaid") + var/obj/item/card/id/gold/ID = new(usr.loc) + ID.registered_name = usr.real_name + ID.assignment = "Sandbox" + ID.access = get_all_accesses() + ID.update_label() + + // + // RCD - starts with full clip + // Spawn check due to grief potential (destroying floors, walls, etc) + // + if("hsbrcd") + if(!GLOB.hsboxspawn) return + + new/obj/item/construction/rcd/combat(usr.loc) + + // + // New sandbox airlock maker + // + if("hsbairlock") + new /datum/airlock_maker(usr.loc) + + // + // Object spawn window + // + + // Clothing + if("hsbcloth") + if(!GLOB.hsboxspawn) return + + if(!clothinfo) + clothinfo = "Clothing (Reagent Containers) (Other Items)

    " + var/list/all_items = subtypesof(/obj/item/clothing) + for(var/typekey in spawn_forbidden) + all_items -= typesof(typekey) + for(var/O in reverseRange(all_items)) + clothinfo += "[O]
    " + + usr << browse(clothinfo,"window=sandbox") + + // Reagent containers + if("hsbreag") + if(!GLOB.hsboxspawn) return + + if(!reaginfo) + reaginfo = "Reagent Containers (Clothing) (Other Items)

    " + var/list/all_items = subtypesof(/obj/item/reagent_containers) + for(var/typekey in spawn_forbidden) + all_items -= typesof(typekey) + for(var/O in reverseRange(all_items)) + reaginfo += "[O]
    " + + usr << browse(reaginfo,"window=sandbox") + + // Other items + if("hsbobj") + if(!GLOB.hsboxspawn) return + + if(!objinfo) + objinfo = "Other Items (Clothing) (Reagent Containers)

    " + var/list/all_items = subtypesof(/obj/item/) - typesof(/obj/item/clothing) - typesof(/obj/item/reagent_containers) + for(var/typekey in spawn_forbidden) + all_items -= typesof(typekey) + + for(var/O in reverseRange(all_items)) + objinfo += "[O]
    " + + usr << browse(objinfo,"window=sandbox") + + // + // Safespawn checks to see if spawning is disabled. + // + if("hsb_safespawn") + if(!GLOB.hsboxspawn) + usr << browse(null,"window=sandbox") + return + + var/typepath = text2path(href_list["path"]) + if(!typepath) + to_chat(usr, "Bad path: \"[href_list["path"]]\"") + return + new typepath(usr.loc) + + if(CONFIG_GET(flag/sandbox_autoclose)) + usr << browse(null,"window=sandbox") + // + // For everything else in the href list + // + if("hsbspawn") + var/typepath = text2path(href_list["path"]) + if(!typepath) + to_chat(usr, "Bad path: \"[href_list["path"]]\"") + return + new typepath(usr.loc) + + if(CONFIG_GET(flag/sandbox_autoclose)) + usr << browse(null,"window=sandbox") diff --git a/code/game/gamemodes/sandbox/sandbox.dm b/code/game/gamemodes/sandbox/sandbox.dm index a41a6a185edc..8d4846d579e3 100644 --- a/code/game/gamemodes/sandbox/sandbox.dm +++ b/code/game/gamemodes/sandbox/sandbox.dm @@ -1,22 +1,22 @@ -/datum/game_mode/sandbox - name = "sandbox" - config_tag = "sandbox" - report_type = "sandbox" - required_players = 0 - - announce_span = "info" - announce_text = "Build your own station... or just shoot each other!" - - allow_persistence_save = FALSE - -/datum/game_mode/sandbox/pre_setup() - for(var/mob/M in GLOB.player_list) - M.CanBuild() - return 1 - -/datum/game_mode/sandbox/post_setup() - ..() - SSshuttle.registerHostileEnvironment(src) - -/datum/game_mode/sandbox/generate_report() - return "Sensors indicate that crewmembers have been all given psychic powers from which they can manifest various objects.

    This can only end poorly." +/datum/game_mode/sandbox + name = "sandbox" + config_tag = "sandbox" + report_type = "sandbox" + required_players = 0 + + announce_span = "info" + announce_text = "Build your own station... or just shoot each other!" + + allow_persistence_save = FALSE + +/datum/game_mode/sandbox/pre_setup() + for(var/mob/M in GLOB.player_list) + M.CanBuild() + return 1 + +/datum/game_mode/sandbox/post_setup() + ..() + SSshuttle.registerHostileEnvironment(src) + +/datum/game_mode/sandbox/generate_report() + return "Sensors indicate that crewmembers have been all given psychic powers from which they can manifest various objects.

    This can only end poorly." diff --git a/code/game/gamemodes/traitor/double_agents.dm b/code/game/gamemodes/traitor/double_agents.dm index 9e8b9985cdbf..7e3db6e86c4d 100644 --- a/code/game/gamemodes/traitor/double_agents.dm +++ b/code/game/gamemodes/traitor/double_agents.dm @@ -1,83 +1,83 @@ -/datum/game_mode - var/list/target_list = list() - var/list/late_joining_list = list() - -/datum/game_mode/traitor/internal_affairs - name = "Internal Affairs" - config_tag = "internal_affairs" - report_type = "internal_affairs" - false_report_weight = 10 - required_players = 25 - required_enemies = 5 - recommended_enemies = 8 - reroll_friendly = 0 - traitor_name = "Nanotrasen Internal Affairs Agent" - antag_flag = ROLE_INTERNAL_AFFAIRS - - traitors_possible = 10 //hard limit on traitors if scaling is turned off - num_modifier = 4 // Four additional traitors - antag_datum = /datum/antagonist/traitor/internal_affairs - - announce_text = "There are Nanotrasen Internal Affairs Agents trying to kill each other!\n\ - IAA: Eliminate your targets and protect yourself!\n\ - Crew: Stop the IAA agents before they can cause too much mayhem." - - - -/datum/game_mode/traitor/internal_affairs/post_setup() - var/i = 0 - for(var/datum/mind/traitor in pre_traitors) - i++ - if(i + 1 > pre_traitors.len) - i = 0 - target_list[traitor] = pre_traitors[i+1] - ..() - - -/datum/game_mode/traitor/internal_affairs/add_latejoin_traitor(datum/mind/character) - - check_potential_agents() - - // As soon as we get 3 or 4 extra latejoin traitors, make them traitors and kill each other. - if(late_joining_list.len >= rand(3, 4)) - // True randomness - shuffle_inplace(late_joining_list) - // Reset the target_list, it'll be used again in force_traitor_objectives - target_list = list() - - // Basically setting the target_list for who is killing who - var/i = 0 - for(var/datum/mind/traitor in late_joining_list) - i++ - if(i + 1 > late_joining_list.len) - i = 0 - target_list[traitor] = late_joining_list[i + 1] - traitor.special_role = traitor_name - - // Now, give them their targets - for(var/datum/mind/traitor in target_list) - ..(traitor) - - late_joining_list = list() - else - late_joining_list += character - return - -/datum/game_mode/traitor/internal_affairs/proc/check_potential_agents() - - for(var/M in late_joining_list) - if(istype(M, /datum/mind)) - var/datum/mind/agent_mind = M - if(ishuman(agent_mind.current)) - var/mob/living/carbon/human/H = agent_mind.current - if(H.stat != DEAD) - if(H.client) - continue // It all checks out. - - // If any check fails, remove them from our list - late_joining_list -= M - - -/datum/game_mode/traitor/internal_affairs/generate_report() - return "Nanotrasen denies any accusations of placing internal affairs agents onboard your station to eliminate inconvenient employees. Any further accusations against CentCom for such \ - actions will be met with a conversation with an official internal affairs agent." +/datum/game_mode + var/list/target_list = list() + var/list/late_joining_list = list() + +/datum/game_mode/traitor/internal_affairs + name = "Internal Affairs" + config_tag = "internal_affairs" + report_type = "internal_affairs" + false_report_weight = 10 + required_players = 25 + required_enemies = 5 + recommended_enemies = 8 + reroll_friendly = 0 + traitor_name = "Nanotrasen Internal Affairs Agent" + antag_flag = ROLE_INTERNAL_AFFAIRS + + traitors_possible = 10 //hard limit on traitors if scaling is turned off + num_modifier = 4 // Four additional traitors + antag_datum = /datum/antagonist/traitor/internal_affairs + + announce_text = "There are Nanotrasen Internal Affairs Agents trying to kill each other!\n\ + IAA: Eliminate your targets and protect yourself!\n\ + Crew: Stop the IAA agents before they can cause too much mayhem." + + + +/datum/game_mode/traitor/internal_affairs/post_setup() + var/i = 0 + for(var/datum/mind/traitor in pre_traitors) + i++ + if(i + 1 > pre_traitors.len) + i = 0 + target_list[traitor] = pre_traitors[i+1] + ..() + + +/datum/game_mode/traitor/internal_affairs/add_latejoin_traitor(datum/mind/character) + + check_potential_agents() + + // As soon as we get 3 or 4 extra latejoin traitors, make them traitors and kill each other. + if(late_joining_list.len >= rand(3, 4)) + // True randomness + shuffle_inplace(late_joining_list) + // Reset the target_list, it'll be used again in force_traitor_objectives + target_list = list() + + // Basically setting the target_list for who is killing who + var/i = 0 + for(var/datum/mind/traitor in late_joining_list) + i++ + if(i + 1 > late_joining_list.len) + i = 0 + target_list[traitor] = late_joining_list[i + 1] + traitor.special_role = traitor_name + + // Now, give them their targets + for(var/datum/mind/traitor in target_list) + ..(traitor) + + late_joining_list = list() + else + late_joining_list += character + return + +/datum/game_mode/traitor/internal_affairs/proc/check_potential_agents() + + for(var/M in late_joining_list) + if(istype(M, /datum/mind)) + var/datum/mind/agent_mind = M + if(ishuman(agent_mind.current)) + var/mob/living/carbon/human/H = agent_mind.current + if(H.stat != DEAD) + if(H.client) + continue // It all checks out. + + // If any check fails, remove them from our list + late_joining_list -= M + + +/datum/game_mode/traitor/internal_affairs/generate_report() + return "Nanotrasen denies any accusations of placing internal affairs agents onboard your station to eliminate inconvenient employees. Any further accusations against CentCom for such \ + actions will be met with a conversation with an official internal affairs agent." diff --git a/code/game/gamemodes/traitor/traitor.dm b/code/game/gamemodes/traitor/traitor.dm index af80651ac960..e14cc9b1fc17 100644 --- a/code/game/gamemodes/traitor/traitor.dm +++ b/code/game/gamemodes/traitor/traitor.dm @@ -1,138 +1,138 @@ -/datum/game_mode - var/traitor_name = "traitor" - var/list/datum/mind/traitors = list() - - var/datum/mind/exchange_red - var/datum/mind/exchange_blue - -/datum/game_mode/traitor - name = "traitor" - config_tag = "traitor" - report_type = "traitor" - antag_flag = ROLE_TRAITOR - false_report_weight = 20 //Reports of traitors are pretty common. - restricted_jobs = list("Cyborg")//They are part of the AI if he is traitor so are they, they use to get double chances - protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Brig Physician", "Lieutenant", "Prisoner") // Waspstation edit - Brig Physicians, Second Officer - required_players = 0 - required_enemies = 1 - recommended_enemies = 4 - reroll_friendly = 1 - enemy_minimum_age = 0 - - announce_span = "danger" - announce_text = "There are Syndicate agents on the station!\n\ - Traitors: Accomplish your objectives.\n\ - Crew: Do not let the traitors succeed!" - - title_icon = "traitor" - - var/list/datum/mind/pre_traitors = list() - var/traitors_possible = 4 //hard limit on traitors if scaling is turned off - var/num_modifier = 0 // Used for gamemodes, that are a child of traitor, that need more than the usual. - var/antag_datum = /datum/antagonist/traitor //what type of antag to create - var/traitors_required = TRUE //Will allow no traitors - - -/datum/game_mode/traitor/pre_setup() - - var/total_ready_players = 0; - for(var/i in GLOB.new_player_list) - var/mob/dead/new_player/player = i - if(player.ready == PLAYER_READY_TO_PLAY) - total_ready_players++ - - if(CONFIG_GET(flag/protect_roles_from_antagonist)) - for(var/id in (CONFIG_GET(keyed_list/no_traitor_head))) - switch(id) - if("chief_medical_officer") - protected_jobs += "Chief Medical Officer" - if("research_director") - protected_jobs += "Research Director" - if("chief_engineer") - protected_jobs += "Chief Engineer" - restricted_jobs += protected_jobs - - if(CONFIG_GET(number/traitor_malf_ai_min_pop) > total_ready_players) - restricted_jobs += "AI" - - if(CONFIG_GET(flag/protect_assistant_from_antagonist)) - restricted_jobs += "Assistant" - - var/num_traitors = 1 - - var/tsc = CONFIG_GET(number/traitor_scaling_coeff) - if(tsc) - num_traitors = max(1, min(round(num_players() / (tsc * 2)) + 2 + num_modifier, round(num_players() / tsc) + num_modifier)) - else - num_traitors = max(1, min(num_players(), traitors_possible)) - - for(var/j = 0, j < num_traitors, j++) - if (!antag_candidates.len) - break - var/datum/mind/traitor = antag_pick(antag_candidates) - pre_traitors += traitor - traitor.special_role = traitor_name - traitor.restricted_roles = restricted_jobs - log_game("[key_name(traitor)] has been selected as a [traitor_name]") - antag_candidates.Remove(traitor) - - var/enough_tators = !traitors_required || pre_traitors.len > 0 - - if(!enough_tators) - setup_error = "Not enough traitor candidates" - return FALSE - else - for(var/antag in pre_traitors) - GLOB.pre_setup_antags += antag - return TRUE - - -/datum/game_mode/traitor/post_setup() - for(var/datum/mind/traitor in pre_traitors) - var/datum/antagonist/traitor/new_antag = new antag_datum() - addtimer(CALLBACK(traitor, /datum/mind.proc/add_antag_datum, new_antag), rand(10,100)) - GLOB.pre_setup_antags -= traitor - if(!exchange_blue) - exchange_blue = -1 //Block latejoiners from getting exchange objectives - ..() - - //We're not actually ready until all traitors are assigned. - gamemode_ready = FALSE - addtimer(VARSET_CALLBACK(src, gamemode_ready, TRUE), 101) - return TRUE - -/datum/game_mode/traitor/make_antag_chance(mob/living/carbon/human/character) //Assigns traitor to latejoiners - var/tsc = CONFIG_GET(number/traitor_scaling_coeff) - var/traitorcap = min(round(GLOB.joined_player_list.len / (tsc * 2)) + 2 + num_modifier, round(GLOB.joined_player_list.len / tsc) + num_modifier) - if((SSticker.mode.traitors.len + pre_traitors.len) >= traitorcap) //Upper cap for number of latejoin antagonists - return - if((SSticker.mode.traitors.len + pre_traitors.len) <= (traitorcap - 2) || prob(100 / (tsc * 2))) - if(antag_flag in character.client.prefs.be_special) - if(!is_banned_from(character.ckey, list(ROLE_TRAITOR, ROLE_SYNDICATE)) && !QDELETED(character)) - if(age_check(character.client)) - if(!(character.job in restricted_jobs)) - add_latejoin_traitor(character.mind) - -/datum/game_mode/traitor/proc/add_latejoin_traitor(datum/mind/character) - var/datum/antagonist/traitor/new_antag = new antag_datum() - character.add_antag_datum(new_antag) - -/datum/game_mode/traitor/generate_report() - return "Although more specific threats are commonplace, you should always remain vigilant for Syndicate agents aboard your station. Syndicate communications have implied that many \ - Nanotrasen employees are Syndicate agents with hidden memories that may be activated at a moment's notice, so it's possible that these agents might not even know their positions." - - -/datum/game_mode/traitor/generate_credit_text() - var/list/round_credits = list() - var/len_before_addition - - round_credits += "

    The [syndicate_name()] Spies:

    " - len_before_addition = round_credits.len - for(var/datum/mind/traitor in traitors) - round_credits += "

    [traitor.name] as a [syndicate_name()] traitor

    " - if(len_before_addition == round_credits.len) - round_credits += list("

    The traitors have concealed their treachery!

    ", "

    We couldn't locate them!

    ") - round_credits += "
    " - - round_credits += ..() - return round_credits +/datum/game_mode + var/traitor_name = "traitor" + var/list/datum/mind/traitors = list() + + var/datum/mind/exchange_red + var/datum/mind/exchange_blue + +/datum/game_mode/traitor + name = "traitor" + config_tag = "traitor" + report_type = "traitor" + antag_flag = ROLE_TRAITOR + false_report_weight = 20 //Reports of traitors are pretty common. + restricted_jobs = list("Cyborg")//They are part of the AI if he is traitor so are they, they use to get double chances + protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Brig Physician", "Lieutenant", "Prisoner") // Waspstation edit - Brig Physicians, Second Officer + required_players = 0 + required_enemies = 1 + recommended_enemies = 4 + reroll_friendly = 1 + enemy_minimum_age = 0 + + announce_span = "danger" + announce_text = "There are Syndicate agents on the station!\n\ + Traitors: Accomplish your objectives.\n\ + Crew: Do not let the traitors succeed!" + + title_icon = "traitor" + + var/list/datum/mind/pre_traitors = list() + var/traitors_possible = 4 //hard limit on traitors if scaling is turned off + var/num_modifier = 0 // Used for gamemodes, that are a child of traitor, that need more than the usual. + var/antag_datum = /datum/antagonist/traitor //what type of antag to create + var/traitors_required = TRUE //Will allow no traitors + + +/datum/game_mode/traitor/pre_setup() + + var/total_ready_players = 0; + for(var/i in GLOB.new_player_list) + var/mob/dead/new_player/player = i + if(player.ready == PLAYER_READY_TO_PLAY) + total_ready_players++ + + if(CONFIG_GET(flag/protect_roles_from_antagonist)) + for(var/id in (CONFIG_GET(keyed_list/no_traitor_head))) + switch(id) + if("chief_medical_officer") + protected_jobs += "Chief Medical Officer" + if("research_director") + protected_jobs += "Research Director" + if("chief_engineer") + protected_jobs += "Chief Engineer" + restricted_jobs += protected_jobs + + if(CONFIG_GET(number/traitor_malf_ai_min_pop) > total_ready_players) + restricted_jobs += "AI" + + if(CONFIG_GET(flag/protect_assistant_from_antagonist)) + restricted_jobs += "Assistant" + + var/num_traitors = 1 + + var/tsc = CONFIG_GET(number/traitor_scaling_coeff) + if(tsc) + num_traitors = max(1, min(round(num_players() / (tsc * 2)) + 2 + num_modifier, round(num_players() / tsc) + num_modifier)) + else + num_traitors = max(1, min(num_players(), traitors_possible)) + + for(var/j = 0, j < num_traitors, j++) + if (!antag_candidates.len) + break + var/datum/mind/traitor = antag_pick(antag_candidates) + pre_traitors += traitor + traitor.special_role = traitor_name + traitor.restricted_roles = restricted_jobs + log_game("[key_name(traitor)] has been selected as a [traitor_name]") + antag_candidates.Remove(traitor) + + var/enough_tators = !traitors_required || pre_traitors.len > 0 + + if(!enough_tators) + setup_error = "Not enough traitor candidates" + return FALSE + else + for(var/antag in pre_traitors) + GLOB.pre_setup_antags += antag + return TRUE + + +/datum/game_mode/traitor/post_setup() + for(var/datum/mind/traitor in pre_traitors) + var/datum/antagonist/traitor/new_antag = new antag_datum() + addtimer(CALLBACK(traitor, /datum/mind.proc/add_antag_datum, new_antag), rand(10,100)) + GLOB.pre_setup_antags -= traitor + if(!exchange_blue) + exchange_blue = -1 //Block latejoiners from getting exchange objectives + ..() + + //We're not actually ready until all traitors are assigned. + gamemode_ready = FALSE + addtimer(VARSET_CALLBACK(src, gamemode_ready, TRUE), 101) + return TRUE + +/datum/game_mode/traitor/make_antag_chance(mob/living/carbon/human/character) //Assigns traitor to latejoiners + var/tsc = CONFIG_GET(number/traitor_scaling_coeff) + var/traitorcap = min(round(GLOB.joined_player_list.len / (tsc * 2)) + 2 + num_modifier, round(GLOB.joined_player_list.len / tsc) + num_modifier) + if((SSticker.mode.traitors.len + pre_traitors.len) >= traitorcap) //Upper cap for number of latejoin antagonists + return + if((SSticker.mode.traitors.len + pre_traitors.len) <= (traitorcap - 2) || prob(100 / (tsc * 2))) + if(antag_flag in character.client.prefs.be_special) + if(!is_banned_from(character.ckey, list(ROLE_TRAITOR, ROLE_SYNDICATE)) && !QDELETED(character)) + if(age_check(character.client)) + if(!(character.job in restricted_jobs)) + add_latejoin_traitor(character.mind) + +/datum/game_mode/traitor/proc/add_latejoin_traitor(datum/mind/character) + var/datum/antagonist/traitor/new_antag = new antag_datum() + character.add_antag_datum(new_antag) + +/datum/game_mode/traitor/generate_report() + return "Although more specific threats are commonplace, you should always remain vigilant for Syndicate agents aboard your station. Syndicate communications have implied that many \ + Nanotrasen employees are Syndicate agents with hidden memories that may be activated at a moment's notice, so it's possible that these agents might not even know their positions." + + +/datum/game_mode/traitor/generate_credit_text() + var/list/round_credits = list() + var/len_before_addition + + round_credits += "

    The [syndicate_name()] Spies:

    " + len_before_addition = round_credits.len + for(var/datum/mind/traitor in traitors) + round_credits += "

    [traitor.name] as a [syndicate_name()] traitor

    " + if(len_before_addition == round_credits.len) + round_credits += list("

    The traitors have concealed their treachery!

    ", "

    We couldn't locate them!

    ") + round_credits += "
    " + + round_credits += ..() + return round_credits diff --git a/code/game/gamemodes/wizard/wizard.dm b/code/game/gamemodes/wizard/wizard.dm index 328f38fa98df..c3895f302279 100644 --- a/code/game/gamemodes/wizard/wizard.dm +++ b/code/game/gamemodes/wizard/wizard.dm @@ -1,106 +1,106 @@ -/datum/game_mode - var/list/datum/mind/wizards = list() - var/list/datum/mind/apprentices = list() - -/datum/game_mode/wizard - name = "wizard" - config_tag = "wizard" - report_type = "wizard" - antag_flag = ROLE_WIZARD - false_report_weight = 10 - required_players = 20 - required_enemies = 1 - recommended_enemies = 1 - enemy_minimum_age = 14 - round_ends_with_antag_death = 1 - 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\ - Crew: Eliminate the wizard before they can succeed!" - var/finished = 0 - - title_icon = "wizard" - -/datum/game_mode/wizard/pre_setup() - var/datum/mind/wizard = antag_pick(antag_candidates) - wizards += wizard - wizard.assigned_role = ROLE_WIZARD - wizard.special_role = ROLE_WIZARD - log_game("[key_name(wizard)] has been selected as a Wizard") //TODO: Move these to base antag datum - if(GLOB.wizardstart.len == 0) - setup_error = "No wizard starting location found" - return FALSE - for(var/datum/mind/wiz in wizards) - wiz.current.forceMove(pick(GLOB.wizardstart)) - return TRUE - - -/datum/game_mode/wizard/post_setup() - for(var/datum/mind/wizard in wizards) - wizard.add_antag_datum(/datum/antagonist/wizard) - return ..() - -/datum/game_mode/wizard/generate_report() - return "A dangerous Wizards' Federation individual by the name of [pick(GLOB.wizard_first)] [pick(GLOB.wizard_second)] has recently escaped confinement from an unlisted prison facility. This \ - man is a dangerous mutant with the ability to alter himself and the world around him by what he and his leaders believe to be magic. If this man attempts an attack on your station, \ - his execution is highly encouraged, as is the preservation of his body for later study." - -/////////////////////////////////////////////////// -//Deals with checking if player is a wizard // -/////////////////////////////////////////////////// -/proc/is_wizard(mob/M) - return M.mind?.has_antag_datum(/datum/antagonist/wizard) - -/datum/game_mode/wizard/are_special_antags_dead() - for(var/datum/mind/wizard in wizards | apprentices) - if(isliving(wizard.current) && wizard.current.stat!=DEAD) - return FALSE - - for(var/obj/item/phylactery/P in GLOB.poi_list) //TODO : IsProperlyDead() - if(P.mind && P.mind.has_antag_datum(/datum/antagonist/wizard)) - return FALSE - - if(SSevents.wizardmode) //If summon events was active, turn it off - SSevents.toggleWizardmode() - SSevents.resetFrequency() - - return TRUE - -/datum/game_mode/wizard/check_finished() - . = ..() - if(.) - finished = TRUE - else if(gamemode_ready && are_special_antags_dead() && !CONFIG_GET(keyed_list/continuous)[config_tag]) - finished = TRUE - . = TRUE - -/datum/game_mode/wizard/set_round_result() - ..() - if(finished) - SSticker.mode_result = "loss - wizard killed" - SSticker.news_report = WIZARD_KILLED - -/datum/game_mode/wizard/special_report() - if(finished) - return "
    The wizard[(wizards.len>1)?"s":""] has been killed by the crew! The Space Wizards Federation has been taught a lesson they will not soon forget!
    " - -//returns whether the mob is a wizard (or apprentice) -/proc/iswizard(mob/living/M) - return M.mind && M.mind.has_antag_datum(/datum/antagonist/wizard,TRUE) - -/datum/game_mode/wizard/generate_credit_text() - var/list/round_credits = list() - var/len_before_addition - - round_credits += "

    The Space Wizard Federation:

    " - len_before_addition = round_credits.len - for(var/datum/mind/wizard in wizards) - round_credits += "

    [wizard.name] as a master wizard

    " - for(var/datum/mind/apprentice in apprentices) - round_credits += "

    [apprentice.name] as an eager apprentice

    " - if(len_before_addition == round_credits.len) - round_credits += list("

    The wizards have removed themselves from this realm of existance!

    ", "

    We couldn't locate them!

    ") - round_credits += "
    " - - round_credits += ..() - return round_credits +/datum/game_mode + var/list/datum/mind/wizards = list() + var/list/datum/mind/apprentices = list() + +/datum/game_mode/wizard + name = "wizard" + config_tag = "wizard" + report_type = "wizard" + antag_flag = ROLE_WIZARD + false_report_weight = 10 + required_players = 20 + required_enemies = 1 + recommended_enemies = 1 + enemy_minimum_age = 14 + round_ends_with_antag_death = 1 + 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\ + Crew: Eliminate the wizard before they can succeed!" + var/finished = 0 + + title_icon = "wizard" + +/datum/game_mode/wizard/pre_setup() + var/datum/mind/wizard = antag_pick(antag_candidates) + wizards += wizard + wizard.assigned_role = ROLE_WIZARD + wizard.special_role = ROLE_WIZARD + log_game("[key_name(wizard)] has been selected as a Wizard") //TODO: Move these to base antag datum + if(GLOB.wizardstart.len == 0) + setup_error = "No wizard starting location found" + return FALSE + for(var/datum/mind/wiz in wizards) + wiz.current.forceMove(pick(GLOB.wizardstart)) + return TRUE + + +/datum/game_mode/wizard/post_setup() + for(var/datum/mind/wizard in wizards) + wizard.add_antag_datum(/datum/antagonist/wizard) + return ..() + +/datum/game_mode/wizard/generate_report() + return "A dangerous Wizards' Federation individual by the name of [pick(GLOB.wizard_first)] [pick(GLOB.wizard_second)] has recently escaped confinement from an unlisted prison facility. This \ + man is a dangerous mutant with the ability to alter himself and the world around him by what he and his leaders believe to be magic. If this man attempts an attack on your station, \ + his execution is highly encouraged, as is the preservation of his body for later study." + +/////////////////////////////////////////////////// +//Deals with checking if player is a wizard // +/////////////////////////////////////////////////// +/proc/is_wizard(mob/M) + return M.mind?.has_antag_datum(/datum/antagonist/wizard) + +/datum/game_mode/wizard/are_special_antags_dead() + for(var/datum/mind/wizard in wizards | apprentices) + if(isliving(wizard.current) && wizard.current.stat!=DEAD) + return FALSE + + for(var/obj/item/phylactery/P in GLOB.poi_list) //TODO : IsProperlyDead() + if(P.mind && P.mind.has_antag_datum(/datum/antagonist/wizard)) + return FALSE + + if(SSevents.wizardmode) //If summon events was active, turn it off + SSevents.toggleWizardmode() + SSevents.resetFrequency() + + return TRUE + +/datum/game_mode/wizard/check_finished() + . = ..() + if(.) + finished = TRUE + else if(gamemode_ready && are_special_antags_dead() && !CONFIG_GET(keyed_list/continuous)[config_tag]) + finished = TRUE + . = TRUE + +/datum/game_mode/wizard/set_round_result() + ..() + if(finished) + SSticker.mode_result = "loss - wizard killed" + SSticker.news_report = WIZARD_KILLED + +/datum/game_mode/wizard/special_report() + if(finished) + return "
    The wizard[(wizards.len>1)?"s":""] has been killed by the crew! The Space Wizards Federation has been taught a lesson they will not soon forget!
    " + +//returns whether the mob is a wizard (or apprentice) +/proc/iswizard(mob/living/M) + return M.mind && M.mind.has_antag_datum(/datum/antagonist/wizard,TRUE) + +/datum/game_mode/wizard/generate_credit_text() + var/list/round_credits = list() + var/len_before_addition + + round_credits += "

    The Space Wizard Federation:

    " + len_before_addition = round_credits.len + for(var/datum/mind/wizard in wizards) + round_credits += "

    [wizard.name] as a master wizard

    " + for(var/datum/mind/apprentice in apprentices) + round_credits += "

    [apprentice.name] as an eager apprentice

    " + if(len_before_addition == round_credits.len) + round_credits += list("

    The wizards have removed themselves from this realm of existance!

    ", "

    We couldn't locate them!

    ") + round_credits += "
    " + + round_credits += ..() + return round_credits diff --git a/code/game/machinery/Beacon.dm b/code/game/machinery/Beacon.dm index 7593e392fc8b..fd918d1cb2d5 100644 --- a/code/game/machinery/Beacon.dm +++ b/code/game/machinery/Beacon.dm @@ -1,30 +1,30 @@ -/obj/machinery/bluespace_beacon - - icon = 'icons/obj/objects.dmi' - icon_state = "floor_beaconf" - name = "bluespace gigabeacon" - desc = "A device that draws power from bluespace and creates a permanent tracking beacon." - layer = LOW_OBJ_LAYER - use_power = IDLE_POWER_USE - idle_power_usage = 0 - var/obj/item/beacon/Beacon - -/obj/machinery/bluespace_beacon/Initialize() - . = ..() - var/turf/T = loc - Beacon = new(T) - Beacon.invisibility = INVISIBILITY_MAXIMUM - - AddElement(/datum/element/undertile, TRAIT_T_RAY_VISIBLE) - -/obj/machinery/bluespace_beacon/Destroy() - QDEL_NULL(Beacon) - return ..() - -/obj/machinery/bluespace_beacon/process() - if(!Beacon) - var/turf/T = loc - Beacon = new(T) - Beacon.invisibility = INVISIBILITY_MAXIMUM - else if (Beacon.loc != loc) - Beacon.forceMove(loc) +/obj/machinery/bluespace_beacon + + icon = 'icons/obj/objects.dmi' + icon_state = "floor_beaconf" + name = "bluespace gigabeacon" + desc = "A device that draws power from bluespace and creates a permanent tracking beacon." + layer = LOW_OBJ_LAYER + use_power = IDLE_POWER_USE + idle_power_usage = 0 + var/obj/item/beacon/Beacon + +/obj/machinery/bluespace_beacon/Initialize() + . = ..() + var/turf/T = loc + Beacon = new(T) + Beacon.invisibility = INVISIBILITY_MAXIMUM + + AddElement(/datum/element/undertile, TRAIT_T_RAY_VISIBLE) + +/obj/machinery/bluespace_beacon/Destroy() + QDEL_NULL(Beacon) + return ..() + +/obj/machinery/bluespace_beacon/process() + if(!Beacon) + var/turf/T = loc + Beacon = new(T) + Beacon.invisibility = INVISIBILITY_MAXIMUM + else if (Beacon.loc != loc) + Beacon.forceMove(loc) diff --git a/code/game/machinery/PDApainter.dm b/code/game/machinery/PDApainter.dm index 00ba4b2eabfb..3aa18d3d8898 100644 --- a/code/game/machinery/PDApainter.dm +++ b/code/game/machinery/PDApainter.dm @@ -1,145 +1,145 @@ -/obj/machinery/pdapainter - name = "\improper PDA painter" - desc = "A PDA painting machine. To use, simply insert your PDA and choose the desired preset paint scheme." - icon = 'icons/obj/pda.dmi' - icon_state = "pdapainter" - density = TRUE - max_integrity = 200 - var/obj/item/pda/storedpda = null - var/list/colorlist = list() - -/obj/machinery/pdapainter/update_icon_state() - if(machine_stat & BROKEN) - icon_state = "[initial(icon_state)]-broken" - return - - if(powered()) - icon_state = initial(icon_state) - else - icon_state = "[initial(icon_state)]-off" - -/obj/machinery/pdapainter/update_overlays() - . = ..() - - if(machine_stat & BROKEN) - return - - if(storedpda) - . += "[initial(icon_state)]-closed" - -/obj/machinery/pdapainter/Initialize() - . = ..() - var/list/blocked = list( - /obj/item/pda/ai/pai, - /obj/item/pda/ai, - /obj/item/pda/heads, - /obj/item/pda/clear, - /obj/item/pda/syndicate, - /obj/item/pda/chameleon, - /obj/item/pda/chameleon/broken) - - for(var/P in typesof(/obj/item/pda) - blocked) - var/obj/item/pda/D = new P - - //D.name = "PDA Style [colorlist.len+1]" //Gotta set the name, otherwise it all comes up as "PDA" - D.name = D.icon_state //PDAs don't have unique names, but using the sprite names works. - - src.colorlist += D - -/obj/machinery/pdapainter/Destroy() - QDEL_NULL(storedpda) - return ..() - -/obj/machinery/pdapainter/on_deconstruction() - if(storedpda) - storedpda.forceMove(loc) - storedpda = null - -/obj/machinery/pdapainter/contents_explosion(severity, target) - if(storedpda) - storedpda.ex_act(severity, target) - -/obj/machinery/pdapainter/handle_atom_del(atom/A) - if(A == storedpda) - storedpda = null - update_icon() - -/obj/machinery/pdapainter/attackby(obj/item/O, mob/user, params) - if(machine_stat & BROKEN) - if(O.tool_behaviour == TOOL_WELDER && user.a_intent != INTENT_HARM) - if(!O.tool_start_check(user, amount=0)) - return - user.visible_message("[user] is repairing [src].", \ - "You begin repairing [src]...", \ - "You hear welding.") - if(O.use_tool(src, user, 40, volume=50)) - if(!(machine_stat & BROKEN)) - return - to_chat(user, "You repair [src].") - machine_stat &= ~BROKEN - obj_integrity = max_integrity - update_icon() - - else - return ..() - - else if(default_unfasten_wrench(user, O)) - power_change() - return - - else if(istype(O, /obj/item/pda)) - if(storedpda) - to_chat(user, "There is already a PDA inside!") - return - else if(!user.transferItemToLoc(O, src)) - return - storedpda = O - O.add_fingerprint(user) - update_icon() - - else - return ..() - -/obj/machinery/pdapainter/deconstruct(disassembled = TRUE) - obj_break() - -/obj/machinery/pdapainter/attack_hand(mob/user) - . = ..() - if(.) - return - - if(storedpda) - if(machine_stat & BROKEN) //otherwise the PDA is stuck until repaired - ejectpda() - to_chat(user, "You manage to eject the loaded PDA.") - else - var/obj/item/pda/P - P = input(user, "Select your color!", "PDA Painting") as null|anything in sortNames(colorlist) - if(!P) - return - if(!in_range(src, user)) - return - if(!storedpda)//is the pda still there? - return - storedpda.icon_state = P.icon_state - storedpda.desc = P.desc - ejectpda() - - else - to_chat(user, "[src] is empty!") - - -/obj/machinery/pdapainter/verb/ejectpda() - set name = "Eject PDA" - set category = "Object" - set src in oview(1) - - if(usr.stat || usr.restrained()) - return - - if(storedpda) - storedpda.forceMove(drop_location()) - storedpda = null - update_icon() - else - to_chat(usr, "[src] is empty!") +/obj/machinery/pdapainter + name = "\improper PDA painter" + desc = "A PDA painting machine. To use, simply insert your PDA and choose the desired preset paint scheme." + icon = 'icons/obj/pda.dmi' + icon_state = "pdapainter" + density = TRUE + max_integrity = 200 + var/obj/item/pda/storedpda = null + var/list/colorlist = list() + +/obj/machinery/pdapainter/update_icon_state() + if(machine_stat & BROKEN) + icon_state = "[initial(icon_state)]-broken" + return + + if(powered()) + icon_state = initial(icon_state) + else + icon_state = "[initial(icon_state)]-off" + +/obj/machinery/pdapainter/update_overlays() + . = ..() + + if(machine_stat & BROKEN) + return + + if(storedpda) + . += "[initial(icon_state)]-closed" + +/obj/machinery/pdapainter/Initialize() + . = ..() + var/list/blocked = list( + /obj/item/pda/ai/pai, + /obj/item/pda/ai, + /obj/item/pda/heads, + /obj/item/pda/clear, + /obj/item/pda/syndicate, + /obj/item/pda/chameleon, + /obj/item/pda/chameleon/broken) + + for(var/P in typesof(/obj/item/pda) - blocked) + var/obj/item/pda/D = new P + + //D.name = "PDA Style [colorlist.len+1]" //Gotta set the name, otherwise it all comes up as "PDA" + D.name = D.icon_state //PDAs don't have unique names, but using the sprite names works. + + src.colorlist += D + +/obj/machinery/pdapainter/Destroy() + QDEL_NULL(storedpda) + return ..() + +/obj/machinery/pdapainter/on_deconstruction() + if(storedpda) + storedpda.forceMove(loc) + storedpda = null + +/obj/machinery/pdapainter/contents_explosion(severity, target) + if(storedpda) + storedpda.ex_act(severity, target) + +/obj/machinery/pdapainter/handle_atom_del(atom/A) + if(A == storedpda) + storedpda = null + update_icon() + +/obj/machinery/pdapainter/attackby(obj/item/O, mob/user, params) + if(machine_stat & BROKEN) + if(O.tool_behaviour == TOOL_WELDER && user.a_intent != INTENT_HARM) + if(!O.tool_start_check(user, amount=0)) + return + user.visible_message("[user] is repairing [src].", \ + "You begin repairing [src]...", \ + "You hear welding.") + if(O.use_tool(src, user, 40, volume=50)) + if(!(machine_stat & BROKEN)) + return + to_chat(user, "You repair [src].") + machine_stat &= ~BROKEN + obj_integrity = max_integrity + update_icon() + + else + return ..() + + else if(default_unfasten_wrench(user, O)) + power_change() + return + + else if(istype(O, /obj/item/pda)) + if(storedpda) + to_chat(user, "There is already a PDA inside!") + return + else if(!user.transferItemToLoc(O, src)) + return + storedpda = O + O.add_fingerprint(user) + update_icon() + + else + return ..() + +/obj/machinery/pdapainter/deconstruct(disassembled = TRUE) + obj_break() + +/obj/machinery/pdapainter/attack_hand(mob/user) + . = ..() + if(.) + return + + if(storedpda) + if(machine_stat & BROKEN) //otherwise the PDA is stuck until repaired + ejectpda() + to_chat(user, "You manage to eject the loaded PDA.") + else + var/obj/item/pda/P + P = input(user, "Select your color!", "PDA Painting") as null|anything in sortNames(colorlist) + if(!P) + return + if(!in_range(src, user)) + return + if(!storedpda)//is the pda still there? + return + storedpda.icon_state = P.icon_state + storedpda.desc = P.desc + ejectpda() + + else + to_chat(user, "[src] is empty!") + + +/obj/machinery/pdapainter/verb/ejectpda() + set name = "Eject PDA" + set category = "Object" + set src in oview(1) + + if(usr.stat || usr.restrained()) + return + + if(storedpda) + storedpda.forceMove(drop_location()) + storedpda = null + update_icon() + else + to_chat(usr, "[src] is empty!") diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm index 29e282c1c391..7c33b59720ee 100644 --- a/code/game/machinery/_machinery.dm +++ b/code/game/machinery/_machinery.dm @@ -126,11 +126,8 @@ Class Procs: var/market_verb = "Customer" var/payment_department = ACCOUNT_ENG - // For storing and overriding ui id and dimensions + // For storing and overriding ui id var/tgui_id // ID of TGUI interface - var/ui_style // ID of custom TGUI style (optional) - var/ui_x // Default size of TGUI window, in pixels - var/ui_y /obj/machinery/Initialize() if(!armor) diff --git a/code/game/machinery/ai_slipper.dm b/code/game/machinery/ai_slipper.dm index 4ae99a0587da..43fafb819eed 100644 --- a/code/game/machinery/ai_slipper.dm +++ b/code/game/machinery/ai_slipper.dm @@ -1,43 +1,43 @@ -/obj/machinery/ai_slipper - name = "foam dispenser" - desc = "A remotely-activatable dispenser for crowd-controlling foam." - icon = 'icons/obj/device.dmi' - icon_state = "ai-slipper0" - layer = PROJECTILE_HIT_THRESHHOLD_LAYER - plane = FLOOR_PLANE - max_integrity = 200 - armor = list("melee" = 50, "bullet" = 20, "laser" = 20, "energy" = 20, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30) - - var/uses = 20 - var/cooldown = 0 - var/cooldown_time = 100 - req_access = list(ACCESS_AI_UPLOAD) - -/obj/machinery/ai_slipper/examine(mob/user) - . = ..() - . += "It has [uses] uses of foam remaining." - -/obj/machinery/ai_slipper/update_icon_state() - if(machine_stat & BROKEN) - return - if((machine_stat & NOPOWER) || cooldown_time > world.time || !uses) - icon_state = "ai-slipper0" - else - icon_state = "ai-slipper1" - -/obj/machinery/ai_slipper/interact(mob/user) - if(!allowed(user)) - to_chat(user, "Access denied.") - return - if(!uses) - to_chat(user, "[src] is out of foam and cannot be activated!") - return - if(cooldown_time > world.time) - to_chat(user, "[src] cannot be activated for [DisplayTimeText(world.time - cooldown_time)]!") - return - new /obj/effect/particle_effect/foam(loc) - uses-- - to_chat(user, "You activate [src]. It now has [uses] uses of foam remaining.") - cooldown = world.time + cooldown_time - power_change() - addtimer(CALLBACK(src, .proc/power_change), cooldown_time) +/obj/machinery/ai_slipper + name = "foam dispenser" + desc = "A remotely-activatable dispenser for crowd-controlling foam." + icon = 'icons/obj/device.dmi' + icon_state = "ai-slipper0" + layer = PROJECTILE_HIT_THRESHHOLD_LAYER + plane = FLOOR_PLANE + max_integrity = 200 + armor = list("melee" = 50, "bullet" = 20, "laser" = 20, "energy" = 20, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30) + + var/uses = 20 + var/cooldown = 0 + var/cooldown_time = 100 + req_access = list(ACCESS_AI_UPLOAD) + +/obj/machinery/ai_slipper/examine(mob/user) + . = ..() + . += "It has [uses] uses of foam remaining." + +/obj/machinery/ai_slipper/update_icon_state() + if(machine_stat & BROKEN) + return + if((machine_stat & NOPOWER) || cooldown_time > world.time || !uses) + icon_state = "ai-slipper0" + else + icon_state = "ai-slipper1" + +/obj/machinery/ai_slipper/interact(mob/user) + if(!allowed(user)) + to_chat(user, "Access denied.") + return + if(!uses) + to_chat(user, "[src] is out of foam and cannot be activated!") + return + if(cooldown_time > world.time) + to_chat(user, "[src] cannot be activated for [DisplayTimeText(world.time - cooldown_time)]!") + return + new /obj/effect/particle_effect/foam(loc) + uses-- + to_chat(user, "You activate [src]. It now has [uses] uses of foam remaining.") + cooldown = world.time + cooldown_time + power_change() + addtimer(CALLBACK(src, .proc/power_change), cooldown_time) diff --git a/code/game/machinery/airlock_control.dm b/code/game/machinery/airlock_control.dm index 200488b9cbf5..4d7e59c32b34 100644 --- a/code/game/machinery/airlock_control.dm +++ b/code/game/machinery/airlock_control.dm @@ -1,164 +1,164 @@ -#define AIRLOCK_CONTROL_RANGE 5 - -// This code allows for airlocks to be controlled externally by setting an id_tag and comm frequency (disables ID access) -/obj/machinery/door/airlock - var/id_tag - var/frequency - var/datum/radio_frequency/radio_connection - - -/obj/machinery/door/airlock/receive_signal(datum/signal/signal) - if(!signal) - return - - if(id_tag != signal.data["tag"] || !signal.data["command"]) - return - - switch(signal.data["command"]) - if("open") - open(1) - - if("close") - close(1) - - if("unlock") - locked = FALSE - update_icon() - - if("lock") - locked = TRUE - update_icon() - - if("secure_open") - locked = FALSE - update_icon() - - sleep(2) - open(1) - - locked = TRUE - update_icon() - - if("secure_close") - locked = FALSE - close(1) - - locked = TRUE - sleep(2) - update_icon() - - send_status() - - -/obj/machinery/door/airlock/proc/send_status() - if(radio_connection) - var/datum/signal/signal = new(list( - "tag" = id_tag, - "timestamp" = world.time, - "door_status" = density ? "closed" : "open", - "lock_status" = locked ? "locked" : "unlocked" - )) - radio_connection.post_signal(src, signal, range = AIRLOCK_CONTROL_RANGE, filter = RADIO_AIRLOCK) - - -/obj/machinery/door/airlock/open(surpress_send) - . = ..() - if(!surpress_send) - send_status() - - -/obj/machinery/door/airlock/close(surpress_send) - . = ..() - if(!surpress_send) - send_status() - - -/obj/machinery/door/airlock/proc/set_frequency(new_frequency) - SSradio.remove_object(src, frequency) - if(new_frequency) - frequency = new_frequency - radio_connection = SSradio.add_object(src, frequency, RADIO_AIRLOCK) - -/obj/machinery/door/airlock/Destroy() - if(frequency) - SSradio.remove_object(src,frequency) - return ..() - -/obj/machinery/airlock_sensor - icon = 'icons/obj/airlock_machines.dmi' - icon_state = "airlock_sensor_off" - name = "airlock sensor" - resistance_flags = FIRE_PROOF - - power_channel = AREA_USAGE_ENVIRON - - var/id_tag - var/master_tag - var/frequency = FREQ_AIRLOCK_CONTROL - - var/datum/radio_frequency/radio_connection - - var/on = TRUE - var/alert = FALSE - -/obj/machinery/airlock_sensor/incinerator_toxmix - id_tag = INCINERATOR_TOXMIX_AIRLOCK_SENSOR - master_tag = INCINERATOR_TOXMIX_AIRLOCK_CONTROLLER - -/obj/machinery/airlock_sensor/incinerator_atmos - id_tag = INCINERATOR_ATMOS_AIRLOCK_SENSOR - master_tag = INCINERATOR_ATMOS_AIRLOCK_CONTROLLER - -/obj/machinery/airlock_sensor/incinerator_syndicatelava - id_tag = INCINERATOR_SYNDICATELAVA_AIRLOCK_SENSOR - master_tag = INCINERATOR_SYNDICATELAVA_AIRLOCK_CONTROLLER - -/obj/machinery/airlock_sensor/update_icon_state() - if(on) - if(alert) - icon_state = "airlock_sensor_alert" - else - icon_state = "airlock_sensor_standby" - else - icon_state = "airlock_sensor_off" - -/obj/machinery/airlock_sensor/attack_hand(mob/user) - . = ..() - if(.) - return - var/datum/signal/signal = new(list( - "tag" = master_tag, - "command" = "cycle" - )) - - radio_connection.post_signal(src, signal, range = AIRLOCK_CONTROL_RANGE, filter = RADIO_AIRLOCK) - flick("airlock_sensor_cycle", src) - -/obj/machinery/airlock_sensor/process() - if(on) - var/datum/gas_mixture/air_sample = return_air() - var/pressure = round(air_sample.return_pressure(),0.1) - alert = (pressure < ONE_ATMOSPHERE*0.8) - - var/datum/signal/signal = new(list( - "tag" = id_tag, - "timestamp" = world.time, - "pressure" = num2text(pressure) - )) - - radio_connection.post_signal(src, signal, range = AIRLOCK_CONTROL_RANGE, filter = RADIO_AIRLOCK) - - update_icon() - -/obj/machinery/airlock_sensor/proc/set_frequency(new_frequency) - SSradio.remove_object(src, frequency) - frequency = new_frequency - radio_connection = SSradio.add_object(src, frequency, RADIO_AIRLOCK) - -/obj/machinery/airlock_sensor/Initialize() - . = ..() - set_frequency(frequency) - -/obj/machinery/airlock_sensor/Destroy() - SSradio.remove_object(src,frequency) - return ..() +#define AIRLOCK_CONTROL_RANGE 5 + +// This code allows for airlocks to be controlled externally by setting an id_tag and comm frequency (disables ID access) +/obj/machinery/door/airlock + var/id_tag + var/frequency + var/datum/radio_frequency/radio_connection + + +/obj/machinery/door/airlock/receive_signal(datum/signal/signal) + if(!signal) + return + + if(id_tag != signal.data["tag"] || !signal.data["command"]) + return + + switch(signal.data["command"]) + if("open") + open(1) + + if("close") + close(1) + + if("unlock") + locked = FALSE + update_icon() + + if("lock") + locked = TRUE + update_icon() + + if("secure_open") + locked = FALSE + update_icon() + + sleep(2) + open(1) + + locked = TRUE + update_icon() + + if("secure_close") + locked = FALSE + close(1) + + locked = TRUE + sleep(2) + update_icon() + + send_status() + + +/obj/machinery/door/airlock/proc/send_status() + if(radio_connection) + var/datum/signal/signal = new(list( + "tag" = id_tag, + "timestamp" = world.time, + "door_status" = density ? "closed" : "open", + "lock_status" = locked ? "locked" : "unlocked" + )) + radio_connection.post_signal(src, signal, range = AIRLOCK_CONTROL_RANGE, filter = RADIO_AIRLOCK) + + +/obj/machinery/door/airlock/open(surpress_send) + . = ..() + if(!surpress_send) + send_status() + + +/obj/machinery/door/airlock/close(surpress_send) + . = ..() + if(!surpress_send) + send_status() + + +/obj/machinery/door/airlock/proc/set_frequency(new_frequency) + SSradio.remove_object(src, frequency) + if(new_frequency) + frequency = new_frequency + radio_connection = SSradio.add_object(src, frequency, RADIO_AIRLOCK) + +/obj/machinery/door/airlock/Destroy() + if(frequency) + SSradio.remove_object(src,frequency) + return ..() + +/obj/machinery/airlock_sensor + icon = 'icons/obj/airlock_machines.dmi' + icon_state = "airlock_sensor_off" + name = "airlock sensor" + resistance_flags = FIRE_PROOF + + power_channel = AREA_USAGE_ENVIRON + + var/id_tag + var/master_tag + var/frequency = FREQ_AIRLOCK_CONTROL + + var/datum/radio_frequency/radio_connection + + var/on = TRUE + var/alert = FALSE + +/obj/machinery/airlock_sensor/incinerator_toxmix + id_tag = INCINERATOR_TOXMIX_AIRLOCK_SENSOR + master_tag = INCINERATOR_TOXMIX_AIRLOCK_CONTROLLER + +/obj/machinery/airlock_sensor/incinerator_atmos + id_tag = INCINERATOR_ATMOS_AIRLOCK_SENSOR + master_tag = INCINERATOR_ATMOS_AIRLOCK_CONTROLLER + +/obj/machinery/airlock_sensor/incinerator_syndicatelava + id_tag = INCINERATOR_SYNDICATELAVA_AIRLOCK_SENSOR + master_tag = INCINERATOR_SYNDICATELAVA_AIRLOCK_CONTROLLER + +/obj/machinery/airlock_sensor/update_icon_state() + if(on) + if(alert) + icon_state = "airlock_sensor_alert" + else + icon_state = "airlock_sensor_standby" + else + icon_state = "airlock_sensor_off" + +/obj/machinery/airlock_sensor/attack_hand(mob/user) + . = ..() + if(.) + return + var/datum/signal/signal = new(list( + "tag" = master_tag, + "command" = "cycle" + )) + + radio_connection.post_signal(src, signal, range = AIRLOCK_CONTROL_RANGE, filter = RADIO_AIRLOCK) + flick("airlock_sensor_cycle", src) + +/obj/machinery/airlock_sensor/process() + if(on) + var/datum/gas_mixture/air_sample = return_air() + var/pressure = round(air_sample.return_pressure(),0.1) + alert = (pressure < ONE_ATMOSPHERE*0.8) + + var/datum/signal/signal = new(list( + "tag" = id_tag, + "timestamp" = world.time, + "pressure" = num2text(pressure) + )) + + radio_connection.post_signal(src, signal, range = AIRLOCK_CONTROL_RANGE, filter = RADIO_AIRLOCK) + + update_icon() + +/obj/machinery/airlock_sensor/proc/set_frequency(new_frequency) + SSradio.remove_object(src, frequency) + frequency = new_frequency + radio_connection = SSradio.add_object(src, frequency, RADIO_AIRLOCK) + +/obj/machinery/airlock_sensor/Initialize() + . = ..() + set_frequency(frequency) + +/obj/machinery/airlock_sensor/Destroy() + SSradio.remove_object(src,frequency) + return ..() diff --git a/code/game/machinery/airlock_cycle_control.dm b/code/game/machinery/airlock_cycle_control.dm index b80e0ea375bb..f8585337a6d5 100644 --- a/code/game/machinery/airlock_cycle_control.dm +++ b/code/game/machinery/airlock_cycle_control.dm @@ -611,11 +611,10 @@ return ..() return UI_CLOSE -/obj/machinery/advanced_airlock_controller/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/machinery/advanced_airlock_controller/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "AdvancedAirlockController", name, 440, 650, master_ui, state) + ui = new(user, src, "AdvancedAirlockController", name) ui.open() /obj/machinery/advanced_airlock_controller/ui_data(mob/user) diff --git a/code/game/machinery/announcement_system.dm b/code/game/machinery/announcement_system.dm index 9df4e22a48fb..b414bf53445f 100644 --- a/code/game/machinery/announcement_system.dm +++ b/code/game/machinery/announcement_system.dm @@ -97,13 +97,10 @@ GLOBAL_LIST_EMPTY(announcement_systems) for(var/channel in channels) radio.talk_into(src, message, channel) -//config stuff - -/obj/machinery/announcement_system/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - . = ..() - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/machinery/announcement_system/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "AutomatedAnnouncement", "Automated Announcement System", 500, 225, master_ui, state) + ui = new(user, src, "AutomatedAnnouncement") ui.open() /obj/machinery/announcement_system/ui_data() diff --git a/code/game/machinery/autolathe.dm b/code/game/machinery/autolathe.dm index cdd69c318e7c..46f0f3126b96 100644 --- a/code/game/machinery/autolathe.dm +++ b/code/game/machinery/autolathe.dm @@ -1,433 +1,433 @@ -#define AUTOLATHE_MAIN_MENU 1 -#define AUTOLATHE_CATEGORY_MENU 2 -#define AUTOLATHE_SEARCH_MENU 3 - -/obj/machinery/autolathe - name = "autolathe" - desc = "It produces items using metal and glass." - icon_state = "autolathe" - density = TRUE - use_power = IDLE_POWER_USE - idle_power_usage = 10 - active_power_usage = 100 - circuit = /obj/item/circuitboard/machine/autolathe - layer = BELOW_OBJ_LAYER - - var/operating = FALSE - var/list/L = list() - var/list/LL = list() - var/hacked = FALSE - var/disabled = 0 - var/shocked = FALSE - var/hack_wire - var/disable_wire - var/shock_wire - - var/busy = FALSE - 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/list/categories = list( - "Tools", - "Electronics", - "Construction", - "T-Comm", - "Security", - "Machinery", - "Medical", - "Misc", - "Dinnerware", - "Imported" - ) - -/obj/machinery/autolathe/Initialize() - 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 /datum/techweb/specialized/autounlocking/autolathe - matching_designs = list() - -/obj/machinery/autolathe/Destroy() - QDEL_NULL(wires) - return ..() - -/obj/machinery/autolathe/ui_interact(mob/user) - . = ..() - if(!is_operational()) - return - - if(shocked && !(machine_stat & NOPOWER)) - shock(user,50) - - var/dat - - switch(screen) - if(AUTOLATHE_MAIN_MENU) - dat = main_win(user) - if(AUTOLATHE_CATEGORY_MENU) - dat = category_win(user,selected_category) - if(AUTOLATHE_SEARCH_MENU) - dat = search_win(user) - - var/datum/browser/popup = new(user, "autolathe", name, 400, 500) - popup.set_content(dat) - popup.open() - -/obj/machinery/autolathe/on_deconstruction() - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - materials.retrieve_all() - -/obj/machinery/autolathe/attackby(obj/item/O, mob/user, params) - if (busy) - to_chat(user, "The autolathe is busy. Please wait for completion of previous operation.") - return TRUE - - if(default_deconstruction_screwdriver(user, "autolathe_t", "autolathe", O)) - updateUsrDialog() - return TRUE - - if(default_deconstruction_crowbar(O)) - return TRUE - - if(panel_open && is_wire_tool(O)) - wires.interact(user) - return TRUE - - if(user.a_intent == INTENT_HARM) //so we can hit the machine - return ..() - - if(machine_stat) - 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.") - busy = TRUE - var/obj/item/disk/design_disk/D = O - if(do_after(user, 14.4, target = src)) - for(var/B in D.blueprints) - if(B) - stored_research.add_design(B) - busy = FALSE - return TRUE - - return ..() - - -/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(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() - -/obj/machinery/autolathe/Topic(href, href_list) - if(..()) - return - if (!busy) - if(href_list["menu"]) - screen = text2num(href_list["menu"]) - updateUsrDialog() - - if(href_list["category"]) - selected_category = href_list["category"] - updateUsrDialog() - - if(href_list["make"]) - - ///////////////// - //href protection - being_built = stored_research.isDesignResearchedID(href_list["make"]) - if(!being_built) - return - - var/multiplier = text2num(href_list["multiplier"]) - var/is_stack = ispath(being_built.build_path, /obj/item/stack) - multiplier = clamp(multiplier,1,50) - - ///////////////// - - var/coeff = (is_stack ? 1 : prod_coeff) //stacks are unaffected by production coefficient - var/total_amount = 0 - - for(var/MAT in being_built.materials) - total_amount += being_built.materials[MAT] - - var/power = max(2000, (total_amount)*multiplier/5) //Change this to use all materials - - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - - var/list/materials_used = list() - var/list/custom_materials = list() //These will apply their material effect, This should usually only be one. - - for(var/MAT in being_built.materials) - var/datum/material/used_material = MAT - var/amount_needed = being_built.materials[MAT] * coeff * multiplier - if(istext(used_material)) //This means its a category - var/list/list_to_show = list() - for(var/i in SSmaterials.materials_by_category[used_material]) - if(materials.materials[i] > 0) - list_to_show += i - - used_material = input("Choose [used_material]", "Custom Material") as null|anything in sortList(list_to_show, /proc/cmp_typepaths_asc) - if(!used_material) - return //Didn't pick any material, so you can't build shit either. - custom_materials[used_material] += amount_needed - - materials_used[used_material] = amount_needed - - if(materials.has_materials(materials_used)) - busy = TRUE - use_power(power) - icon_state = "autolathe_n" - var/time = is_stack ? 32 : (32 * coeff * multiplier) ** 0.8 - 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.") - - if(href_list["search"]) - matching_designs.Cut() - - for(var/v in stored_research.researched_designs) - var/datum/design/D = SSresearch.techweb_design_by_id(v) - if(findtext(D.name,href_list["to_search"])) - matching_designs.Add(D) - updateUsrDialog() - else - to_chat(usr, "The autolathe is busy. Please wait for completion of previous operation.") - - updateUsrDialog() - - return - -/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) - var/obj/item/stack/N = new being_built.build_path(A, multiplier) - N.update_icon() - N.autolathe_crafted(src) - else - for(var/i=1, i<=multiplier, i++) - var/obj/item/new_item = new being_built.build_path(A) - new_item.autolathe_crafted(src) - - 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 - updateDialog() - -/obj/machinery/autolathe/RefreshParts() - var/T = 0 - for(var/obj/item/stock_parts/matter_bin/MB in component_parts) - T += MB.rating*75000 - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - materials.max_amount = T - T=1.2 - for(var/obj/item/stock_parts/manipulator/M in component_parts) - T -= M.rating*0.2 - prod_coeff = min(1,max(0,T)) // Coeff going 1 -> 0,8 -> 0,6 -> 0,4 - -/obj/machinery/autolathe/examine(mob/user) - . += ..() - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - if(in_range(user, src) || isobserver(user)) - . += "The status display reads: Storing up to [materials.max_amount] material units.
    Material consumption at [prod_coeff*100]%.
    " - -/obj/machinery/autolathe/proc/main_win(mob/user) - var/dat = "

    Autolathe Menu:


    " - dat += materials_printout() - - dat += "
    \ - \ - \ - \ - \ - \ -

    " - - var/line_length = 1 - dat += "" - - for(var/C in categories) - if(line_length > 2) - dat += "" - line_length = 1 - - dat += "" - line_length++ - - dat += "
    [C]
    " - return dat - -/obj/machinery/autolathe/proc/category_win(mob/user,selected_category) - var/dat = "Return to main menu" - dat += "

    Browsing [selected_category]:


    " - dat += materials_printout() - - for(var/v in stored_research.researched_designs) - var/datum/design/D = SSresearch.techweb_design_by_id(v) - if(!(selected_category in D.category)) - continue - - if(disabled || !can_build(D)) - dat += "[D.name]" - else - dat += "[D.name]" - - if(ispath(D.build_path, /obj/item/stack)) - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - var/max_multiplier - for(var/datum/material/mat in D.materials) - max_multiplier = min(D.maxstack, round(materials.get_material_amount(mat)/D.materials[mat])) - if (max_multiplier>10 && !disabled) - dat += " x10" - if (max_multiplier>25 && !disabled) - dat += " x25" - if(max_multiplier > 0 && !disabled) - dat += " x[max_multiplier]" - else - if(!disabled && can_build(D, 5)) - dat += " x5" - if(!disabled && can_build(D, 10)) - dat += " x10" - - dat += "[get_design_cost(D)]
    " - - dat += "
    " - return dat - -/obj/machinery/autolathe/proc/search_win(mob/user) - var/dat = "Return to main menu" - dat += "

    Search results:


    " - dat += materials_printout() - - for(var/v in matching_designs) - var/datum/design/D = v - if(disabled || !can_build(D)) - dat += "[D.name]" - else - dat += "[D.name]" - - if(ispath(D.build_path, /obj/item/stack)) - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - var/max_multiplier - for(var/datum/material/mat in D.materials) - max_multiplier = min(D.maxstack, round(materials.get_material_amount(mat)/D.materials[mat])) - if (max_multiplier>10 && !disabled) - dat += " x10" - if (max_multiplier>25 && !disabled) - dat += " x25" - if(max_multiplier > 0 && !disabled) - dat += " x[max_multiplier]" - - dat += "[get_design_cost(D)]
    " - - dat += "
    " - return dat - -/obj/machinery/autolathe/proc/materials_printout() - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - var/dat = "Total amount: [materials.total_amount] / [materials.max_amount] cm3
    " - for(var/mat_id in materials.materials) - var/datum/material/M = mat_id - var/mineral_amount = materials.materials[mat_id] - if(mineral_amount > 0) - dat += "[M.name] amount: [mineral_amount] cm3
    " - return dat - -/obj/machinery/autolathe/proc/can_build(datum/design/D, amount = 1) - if(D.make_reagents.len) - return FALSE - - var/coeff = (ispath(D.build_path, /obj/item/stack) ? 1 : prod_coeff) - - var/list/required_materials = list() - - for(var/i in D.materials) - required_materials[i] = D.materials[i] * coeff * amount - - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - - return materials.has_materials(required_materials) - - -/obj/machinery/autolathe/proc/get_design_cost(datum/design/D) - var/coeff = (ispath(D.build_path, /obj/item/stack) ? 1 : prod_coeff) - var/dat - for(var/i in D.materials) - if(istext(i)) //Category handling - dat += "[D.materials[i] * coeff] [i]" - else - var/datum/material/M = i - dat += "[D.materials[i] * coeff] [M.name] " - return dat - -/obj/machinery/autolathe/proc/reset(wire) - switch(wire) - if(WIRE_HACK) - if(!wires.is_cut(wire)) - adjust_hacked(FALSE) - if(WIRE_SHOCK) - if(!wires.is_cut(wire)) - shocked = FALSE - if(WIRE_DISABLE) - if(!wires.is_cut(wire)) - disabled = FALSE - -/obj/machinery/autolathe/proc/shock(mob/user, prb) - if(machine_stat & (BROKEN|NOPOWER)) // unpowered, no shock - return FALSE - if(!prob(prb)) - return FALSE - var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread - s.set_up(5, 1, src) - s.start() - if (electrocute_mob(user, get_area(src), src, 0.7, TRUE)) - return TRUE - else - return FALSE - -/obj/machinery/autolathe/proc/adjust_hacked(state) - hacked = state - for(var/id in SSresearch.techweb_designs) - var/datum/design/D = SSresearch.techweb_design_by_id(id) - if((D.build_type & AUTOLATHE) && ("hacked" in D.category)) - if(hacked) - stored_research.add_design(D) - else - stored_research.remove_design(D) - -/obj/machinery/autolathe/hacked/Initialize() - . = ..() - adjust_hacked(TRUE) - -//Called when the object is constructed by an autolathe -//Has a reference to the autolathe so you can do !!FUN!! things with hacked lathes -/obj/item/proc/autolathe_crafted(obj/machinery/autolathe/A) - return +#define AUTOLATHE_MAIN_MENU 1 +#define AUTOLATHE_CATEGORY_MENU 2 +#define AUTOLATHE_SEARCH_MENU 3 + +/obj/machinery/autolathe + name = "autolathe" + desc = "It produces items using metal and glass." + icon_state = "autolathe" + density = TRUE + use_power = IDLE_POWER_USE + idle_power_usage = 10 + active_power_usage = 100 + circuit = /obj/item/circuitboard/machine/autolathe + layer = BELOW_OBJ_LAYER + + var/operating = FALSE + var/list/L = list() + var/list/LL = list() + var/hacked = FALSE + var/disabled = 0 + var/shocked = FALSE + var/hack_wire + var/disable_wire + var/shock_wire + + var/busy = FALSE + 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/list/categories = list( + "Tools", + "Electronics", + "Construction", + "T-Comm", + "Security", + "Machinery", + "Medical", + "Misc", + "Dinnerware", + "Imported" + ) + +/obj/machinery/autolathe/Initialize() + 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 /datum/techweb/specialized/autounlocking/autolathe + matching_designs = list() + +/obj/machinery/autolathe/Destroy() + QDEL_NULL(wires) + return ..() + +/obj/machinery/autolathe/ui_interact(mob/user) + . = ..() + if(!is_operational()) + return + + if(shocked && !(machine_stat & NOPOWER)) + shock(user,50) + + var/dat + + switch(screen) + if(AUTOLATHE_MAIN_MENU) + dat = main_win(user) + if(AUTOLATHE_CATEGORY_MENU) + dat = category_win(user,selected_category) + if(AUTOLATHE_SEARCH_MENU) + dat = search_win(user) + + var/datum/browser/popup = new(user, "autolathe", name, 400, 500) + popup.set_content(dat) + popup.open() + +/obj/machinery/autolathe/on_deconstruction() + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + materials.retrieve_all() + +/obj/machinery/autolathe/attackby(obj/item/O, mob/user, params) + if (busy) + to_chat(user, "The autolathe is busy. Please wait for completion of previous operation.") + return TRUE + + if(default_deconstruction_screwdriver(user, "autolathe_t", "autolathe", O)) + updateUsrDialog() + return TRUE + + if(default_deconstruction_crowbar(O)) + return TRUE + + if(panel_open && is_wire_tool(O)) + wires.interact(user) + return TRUE + + if(user.a_intent == INTENT_HARM) //so we can hit the machine + return ..() + + if(machine_stat) + 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.") + busy = TRUE + var/obj/item/disk/design_disk/D = O + if(do_after(user, 14.4, target = src)) + for(var/B in D.blueprints) + if(B) + stored_research.add_design(B) + busy = FALSE + return TRUE + + return ..() + + +/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(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() + +/obj/machinery/autolathe/Topic(href, href_list) + if(..()) + return + if (!busy) + if(href_list["menu"]) + screen = text2num(href_list["menu"]) + updateUsrDialog() + + if(href_list["category"]) + selected_category = href_list["category"] + updateUsrDialog() + + if(href_list["make"]) + + ///////////////// + //href protection + being_built = stored_research.isDesignResearchedID(href_list["make"]) + if(!being_built) + return + + var/multiplier = text2num(href_list["multiplier"]) + var/is_stack = ispath(being_built.build_path, /obj/item/stack) + multiplier = clamp(multiplier,1,50) + + ///////////////// + + var/coeff = (is_stack ? 1 : prod_coeff) //stacks are unaffected by production coefficient + var/total_amount = 0 + + for(var/MAT in being_built.materials) + total_amount += being_built.materials[MAT] + + var/power = max(2000, (total_amount)*multiplier/5) //Change this to use all materials + + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + + var/list/materials_used = list() + var/list/custom_materials = list() //These will apply their material effect, This should usually only be one. + + for(var/MAT in being_built.materials) + var/datum/material/used_material = MAT + var/amount_needed = being_built.materials[MAT] * coeff * multiplier + if(istext(used_material)) //This means its a category + var/list/list_to_show = list() + for(var/i in SSmaterials.materials_by_category[used_material]) + if(materials.materials[i] > 0) + list_to_show += i + + used_material = input("Choose [used_material]", "Custom Material") as null|anything in sortList(list_to_show, /proc/cmp_typepaths_asc) + if(!used_material) + return //Didn't pick any material, so you can't build shit either. + custom_materials[used_material] += amount_needed + + materials_used[used_material] = amount_needed + + if(materials.has_materials(materials_used)) + busy = TRUE + use_power(power) + icon_state = "autolathe_n" + var/time = is_stack ? 32 : (32 * coeff * multiplier) ** 0.8 + 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.") + + if(href_list["search"]) + matching_designs.Cut() + + for(var/v in stored_research.researched_designs) + var/datum/design/D = SSresearch.techweb_design_by_id(v) + if(findtext(D.name,href_list["to_search"])) + matching_designs.Add(D) + updateUsrDialog() + else + to_chat(usr, "The autolathe is busy. Please wait for completion of previous operation.") + + updateUsrDialog() + + return + +/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) + var/obj/item/stack/N = new being_built.build_path(A, multiplier) + N.update_icon() + N.autolathe_crafted(src) + else + for(var/i=1, i<=multiplier, i++) + var/obj/item/new_item = new being_built.build_path(A) + new_item.autolathe_crafted(src) + + 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 + updateDialog() + +/obj/machinery/autolathe/RefreshParts() + var/T = 0 + for(var/obj/item/stock_parts/matter_bin/MB in component_parts) + T += MB.rating*75000 + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + materials.max_amount = T + T=1.2 + for(var/obj/item/stock_parts/manipulator/M in component_parts) + T -= M.rating*0.2 + prod_coeff = min(1,max(0,T)) // Coeff going 1 -> 0,8 -> 0,6 -> 0,4 + +/obj/machinery/autolathe/examine(mob/user) + . += ..() + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + if(in_range(user, src) || isobserver(user)) + . += "The status display reads: Storing up to [materials.max_amount] material units.
    Material consumption at [prod_coeff*100]%.
    " + +/obj/machinery/autolathe/proc/main_win(mob/user) + var/dat = "

    Autolathe Menu:


    " + dat += materials_printout() + + dat += "
    \ + \ + \ + \ + \ + \ +

    " + + var/line_length = 1 + dat += "" + + for(var/C in categories) + if(line_length > 2) + dat += "" + line_length = 1 + + dat += "" + line_length++ + + dat += "
    [C]
    " + return dat + +/obj/machinery/autolathe/proc/category_win(mob/user,selected_category) + var/dat = "Return to main menu" + dat += "

    Browsing [selected_category]:


    " + dat += materials_printout() + + for(var/v in stored_research.researched_designs) + var/datum/design/D = SSresearch.techweb_design_by_id(v) + if(!(selected_category in D.category)) + continue + + if(disabled || !can_build(D)) + dat += "[D.name]" + else + dat += "[D.name]" + + if(ispath(D.build_path, /obj/item/stack)) + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + var/max_multiplier + for(var/datum/material/mat in D.materials) + max_multiplier = min(D.maxstack, round(materials.get_material_amount(mat)/D.materials[mat])) + if (max_multiplier>10 && !disabled) + dat += " x10" + if (max_multiplier>25 && !disabled) + dat += " x25" + if(max_multiplier > 0 && !disabled) + dat += " x[max_multiplier]" + else + if(!disabled && can_build(D, 5)) + dat += " x5" + if(!disabled && can_build(D, 10)) + dat += " x10" + + dat += "[get_design_cost(D)]
    " + + dat += "
    " + return dat + +/obj/machinery/autolathe/proc/search_win(mob/user) + var/dat = "Return to main menu" + dat += "

    Search results:


    " + dat += materials_printout() + + for(var/v in matching_designs) + var/datum/design/D = v + if(disabled || !can_build(D)) + dat += "[D.name]" + else + dat += "[D.name]" + + if(ispath(D.build_path, /obj/item/stack)) + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + var/max_multiplier + for(var/datum/material/mat in D.materials) + max_multiplier = min(D.maxstack, round(materials.get_material_amount(mat)/D.materials[mat])) + if (max_multiplier>10 && !disabled) + dat += " x10" + if (max_multiplier>25 && !disabled) + dat += " x25" + if(max_multiplier > 0 && !disabled) + dat += " x[max_multiplier]" + + dat += "[get_design_cost(D)]
    " + + dat += "
    " + return dat + +/obj/machinery/autolathe/proc/materials_printout() + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + var/dat = "Total amount: [materials.total_amount] / [materials.max_amount] cm3
    " + for(var/mat_id in materials.materials) + var/datum/material/M = mat_id + var/mineral_amount = materials.materials[mat_id] + if(mineral_amount > 0) + dat += "[M.name] amount: [mineral_amount] cm3
    " + return dat + +/obj/machinery/autolathe/proc/can_build(datum/design/D, amount = 1) + if(D.make_reagents.len) + return FALSE + + var/coeff = (ispath(D.build_path, /obj/item/stack) ? 1 : prod_coeff) + + var/list/required_materials = list() + + for(var/i in D.materials) + required_materials[i] = D.materials[i] * coeff * amount + + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + + return materials.has_materials(required_materials) + + +/obj/machinery/autolathe/proc/get_design_cost(datum/design/D) + var/coeff = (ispath(D.build_path, /obj/item/stack) ? 1 : prod_coeff) + var/dat + for(var/i in D.materials) + if(istext(i)) //Category handling + dat += "[D.materials[i] * coeff] [i]" + else + var/datum/material/M = i + dat += "[D.materials[i] * coeff] [M.name] " + return dat + +/obj/machinery/autolathe/proc/reset(wire) + switch(wire) + if(WIRE_HACK) + if(!wires.is_cut(wire)) + adjust_hacked(FALSE) + if(WIRE_SHOCK) + if(!wires.is_cut(wire)) + shocked = FALSE + if(WIRE_DISABLE) + if(!wires.is_cut(wire)) + disabled = FALSE + +/obj/machinery/autolathe/proc/shock(mob/user, prb) + if(machine_stat & (BROKEN|NOPOWER)) // unpowered, no shock + return FALSE + if(!prob(prb)) + return FALSE + var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread + s.set_up(5, 1, src) + s.start() + if (electrocute_mob(user, get_area(src), src, 0.7, TRUE)) + return TRUE + else + return FALSE + +/obj/machinery/autolathe/proc/adjust_hacked(state) + hacked = state + for(var/id in SSresearch.techweb_designs) + var/datum/design/D = SSresearch.techweb_design_by_id(id) + if((D.build_type & AUTOLATHE) && ("hacked" in D.category)) + if(hacked) + stored_research.add_design(D) + else + stored_research.remove_design(D) + +/obj/machinery/autolathe/hacked/Initialize() + . = ..() + adjust_hacked(TRUE) + +//Called when the object is constructed by an autolathe +//Has a reference to the autolathe so you can do !!FUN!! things with hacked lathes +/obj/item/proc/autolathe_crafted(obj/machinery/autolathe/A) + return diff --git a/code/game/machinery/bank_machine.dm b/code/game/machinery/bank_machine.dm index 1f3c57abcf00..9633ede69bba 100644 --- a/code/game/machinery/bank_machine.dm +++ b/code/game/machinery/bank_machine.dm @@ -3,8 +3,7 @@ desc = "A machine used to deposit and withdraw station funds." icon = 'goon/icons/obj/goon_terminals.dmi' idle_power_usage = 100 - ui_x = 335 - ui_y = 160 + var/siphoning = FALSE var/next_warning = 0 var/obj/item/radio/radio @@ -61,11 +60,10 @@ radio.talk_into(src, message, radio_channel) next_warning = world.time + minimum_time_between_warnings -/obj/machinery/computer/bank_machine/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/machinery/computer/bank_machine/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "BankMachine", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, "BankMachine", name) ui.open() /obj/machinery/computer/bank_machine/ui_data(mob/user) diff --git a/code/game/machinery/bounty_board.dm b/code/game/machinery/bounty_board.dm new file mode 100644 index 000000000000..fdae8f94aa9b --- /dev/null +++ b/code/game/machinery/bounty_board.dm @@ -0,0 +1,188 @@ +GLOBAL_LIST_EMPTY(allbountyboards) +GLOBAL_LIST_EMPTY(request_list) +/** + * A machine that acts basically like a quest board. + * Enables crew to create requests, crew can sign up to perform the request, and the requester can chose who to pay-out. + */ +/obj/machinery/bounty_board + name = "bounty board" + desc = "Alows you to place requests for goods and services across the station, as well as pay those who actually did it." + icon = 'icons/obj/terminals.dmi' + icon_state = "request_kiosk" + light_color = LIGHT_COLOR_GREEN + ///Reference to the currently logged in user. + var/datum/bank_account/current_user + ///The station request datum being affected by UI actions. + var/datum/station_request/active_request + ///Value of the currently bounty input + var/bounty_value = 1 + ///Text of the currently written bounty + var/bounty_text = "" + +/obj/machinery/bounty_board/Initialize(mapload, ndir, building) + . = ..() + GLOB.allbountyboards += src + if(building) + setDir(ndir) + pixel_x = (dir & 3)? 0 : (dir == 4 ? -32 : 32) + pixel_y = (dir & 3)? (dir ==1 ? -32 : 32) : 0 + +/obj/machinery/bounty_board/Destroy() + GLOB.allbountyboards -= src + . = ..() + +/obj/machinery/bounty_board/attackby(obj/item/I, mob/living/user, params) + . = ..() + if(istype(I,/obj/item/card/id)) + var/obj/item/card/id/current_card = I + if(current_card.registered_account) + current_user = current_card.registered_account + return TRUE + to_chat(user, "There's no account assigned with this ID.") + return TRUE + if(I.tool_behaviour == TOOL_WRENCH) + to_chat(user, "You start [anchored ? "un" : ""]securing [name]...") + I.play_tool_sound(src) + if(I.use_tool(src, user, 30)) + playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE) + if(machine_stat & BROKEN) + to_chat(user, "The broken remains of [src] fall on the ground.") + new /obj/item/stack/sheet/metal(loc, 3) + new /obj/item/shard(loc) + else + to_chat(user, "You [anchored ? "un" : ""]secure [name].") + new /obj/item/wallframe/bounty_board(loc) + qdel(src) + +/obj/machinery/bounty_board/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "RequestKiosk", name) + ui.open() + +/obj/machinery/bounty_board/ui_data(mob/user) + var/list/data = list() + var/list/formatted_requests = list() + var/list/formatted_applicants = list() + for(var/i in GLOB.request_list) + if(!i) + continue + var/datum/station_request/request = i + formatted_requests += list(list("owner" = request.owner, "value" = request.value, "description" = request.description, "acc_number" = request.req_number)) + if(request.applicants) + for(var/datum/bank_account/j in request.applicants) + formatted_applicants += list(list("name" = j.account_holder, "request_id" = request.owner_account.account_id, "requestee_id" = j.account_id)) + var/obj/item/card/id/id_card = user.get_idcard() + if(id_card?.registered_account) + current_user = id_card.registered_account + if(current_user) + data["accountName"] = current_user.account_holder + data["requests"] = formatted_requests + data["applicants"] = formatted_applicants + data["bountyValue"] = bounty_value + data["bountyText"] = bounty_text + return data + +/obj/machinery/bounty_board/ui_act(action, list/params) + if(..()) + return + var/current_ref_num = params["request"] + var/current_app_num = params["applicant"] + var/datum/bank_account/request_target + if(current_ref_num) + for(var/datum/station_request/i in GLOB.request_list) + if("[i.req_number]" == "[current_ref_num]") + active_request = i + break + if(active_request) + for(var/datum/bank_account/j in active_request.applicants) + if("[j.account_id]" == "[current_app_num]") + request_target = j + break + switch(action) + if("createBounty") + if(!current_user || !bounty_text) + playsound(src, 'sound/machines/buzz-sigh.ogg', 20, TRUE) + return TRUE + for(var/datum/station_request/i in GLOB.request_list) + if("[i.req_number]" == "[current_user.account_id]") + say("Account already has active bounty.") + return + var/datum/station_request/curr_request = new /datum/station_request(current_user.account_holder, bounty_value,bounty_text,current_user.account_id, current_user) + GLOB.request_list += list(curr_request) + for(var/obj/i in GLOB.allbountyboards) + i.say("New bounty has been added!") + playsound(i.loc, 'sound/effects/cashregister.ogg', 30, TRUE) + if("apply") + if(!current_user) + say("Please swipe a valid ID first.") + return TRUE + if(current_user.account_holder == active_request.owner) + playsound(src, 'sound/machines/buzz-sigh.ogg', 20, TRUE) + return TRUE + active_request.applicants += list(current_user) + if("payApplicant") + if(!current_user) + return + if(!current_user.has_money(active_request.value) || (current_user.account_holder != active_request.owner)) + playsound(src, 'sound/machines/buzz-sigh.ogg', 30, TRUE) + return + request_target.transfer_money(current_user, active_request.value) + say("Paid out [active_request.value] credits.") + return TRUE + if("clear") + if(current_user) + current_user = null + say("Account Reset.") + return TRUE + if("deleteRequest") + if(!active_request || !current_user) + playsound(src, 'sound/machines/buzz-sigh.ogg', 20, TRUE) + return FALSE + if(active_request?.owner != current_user?.account_holder) + playsound(src, 'sound/machines/buzz-sigh.ogg', 20, TRUE) + return TRUE + say("Deleted current request.") + GLOB.request_list.Remove(active_request) + return TRUE + if("bountyVal") + bounty_value = text2num(params["bountyval"]) + if(!bounty_value) + bounty_value = 1 + if("bountyText") + bounty_text = (params["bountytext"]) + . = TRUE + +/obj/item/wallframe/bounty_board + name = "disassembled bounty board" + desc = "Used to build a new bounty board, just secure to the wall." + icon_state = "request_kiosk" + custom_materials = list(/datum/material/iron=14000, /datum/material/glass=8000) + result_path = /obj/machinery/bounty_board + +/** + * A combined all in one datum that stores everything about the request, the requester's account, as well as the requestee's account + * All of this is passed to the Request Console UI in order to present in organized way. + */ +/datum/station_request + ///Name of the Request Owner. + var/owner + ///Value of the request. + var/value + ///Text description of the request to be shown within the UI. + var/description + ///Internal number of the request for organizing. Id card number. + var/req_number + ///The account of the request owner. + var/datum/bank_account/owner_account + ///the account of the request fulfiller. + var/list/applicants = list() + +/datum/station_request/New(var/owned, var/newvalue, var/newdescription, var/reqnum, var/own_account) + . = ..() + owner = owned + value = newvalue + description = newdescription + req_number = reqnum + if(istype(own_account, /datum/bank_account)) + owner_account = own_account diff --git a/code/game/machinery/buttons.dm b/code/game/machinery/buttons.dm index 97a8e811293d..f208b114f81c 100644 --- a/code/game/machinery/buttons.dm +++ b/code/game/machinery/buttons.dm @@ -1,290 +1,290 @@ -/obj/machinery/button - name = "button" - desc = "A remote control switch." - icon = 'icons/obj/stationobjs.dmi' - icon_state = "doorctrl" - var/skin = "doorctrl" - power_channel = AREA_USAGE_ENVIRON - var/obj/item/assembly/device - var/obj/item/electronics/airlock/board - var/device_type = null - var/id = null - var/initialized_button = 0 - armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 50, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 70) - use_power = IDLE_POWER_USE - idle_power_usage = 2 - resistance_flags = LAVA_PROOF | FIRE_PROOF - -/obj/machinery/button/indestructible - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - -/obj/machinery/button/Initialize(mapload, ndir = 0, built = 0) - . = ..() - if(built) - setDir(ndir) - pixel_x = (dir & 3)? 0 : (dir == 4 ? -24 : 24) - pixel_y = (dir & 3)? (dir ==1 ? -24 : 24) : 0 - panel_open = TRUE - update_icon() - - - if(!built && !device && device_type) - device = new device_type(src) - - src.check_access(null) - - if(req_access.len || req_one_access.len) - board = new(src) - if(req_access.len) - board.accesses = req_access - else - board.one_access = 1 - board.accesses = req_one_access - - setup_device() - -/obj/machinery/button/update_icon_state() - if(panel_open) - icon_state = "button-open" - else if(machine_stat & (NOPOWER|BROKEN)) - icon_state = "[skin]-p" - else - icon_state = skin - -/obj/machinery/button/update_overlays() - . = ..() - if(!panel_open) - return - if(device) - . += "button-device" - if(board) - . += "button-board" - -/obj/machinery/button/attackby(obj/item/W, mob/user, params) - if(W.tool_behaviour == TOOL_SCREWDRIVER) - if(panel_open || allowed(user)) - default_deconstruction_screwdriver(user, "button-open", "[skin]",W) - update_icon() - else - to_chat(user, "Maintenance Access Denied.") - flick("[skin]-denied", src) - return - - if(panel_open) - if(!device && istype(W, /obj/item/assembly)) - if(!user.transferItemToLoc(W, src)) - to_chat(user, "\The [W] is stuck to you!") - return - device = W - to_chat(user, "You add [W] to the button.") - - if(!board && istype(W, /obj/item/electronics/airlock)) - if(!user.transferItemToLoc(W, src)) - to_chat(user, "\The [W] is stuck to you!") - return - board = W - if(board.one_access) - req_one_access = board.accesses - else - req_access = board.accesses - to_chat(user, "You add [W] to the button.") - - if(!device && !board && W.tool_behaviour == TOOL_WRENCH) - to_chat(user, "You start unsecuring the button frame...") - W.play_tool_sound(src) - if(W.use_tool(src, user, 40)) - to_chat(user, "You unsecure the button frame.") - transfer_fingerprints_to(new /obj/item/wallframe/button(get_turf(src))) - playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE) - qdel(src) - - update_icon() - return - - if(user.a_intent != INTENT_HARM && !(W.item_flags & NOBLUDGEON)) - return attack_hand(user) - else - return ..() - -/obj/machinery/button/emag_act(mob/user) - if(obj_flags & EMAGGED) - return - req_access = list() - req_one_access = list() - playsound(src, "sparks", 100, TRUE) - obj_flags |= EMAGGED - -/obj/machinery/button/attack_ai(mob/user) - if(!panel_open) - return attack_hand(user) - -/obj/machinery/button/attack_robot(mob/user) - return attack_ai(user) - -/obj/machinery/button/proc/setup_device() - if(id && istype(device, /obj/item/assembly/control)) - var/obj/item/assembly/control/A = device - A.id = id - initialized_button = 1 - -/obj/machinery/button/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) - if(id && istype(device, /obj/item/assembly/control)) - var/obj/item/assembly/control/A = device - A.id = "[idnum][id]" - -/obj/machinery/button/attack_hand(mob/user) - . = ..() - if(.) - return - if(!initialized_button) - setup_device() - add_fingerprint(user) - if(panel_open) - if(device || board) - if(device) - device.forceMove(drop_location()) - device = null - if(board) - board.forceMove(drop_location()) - req_access = list() - req_one_access = list() - board = null - update_icon() - to_chat(user, "You remove electronics from the button frame.") - - else - if(skin == "doorctrl") - skin = "launcher" - else - skin = "doorctrl" - to_chat(user, "You change the button frame's front panel.") - return - - if((machine_stat & (NOPOWER|BROKEN))) - return - - if(device && device.next_activate > world.time) - return - - if(!allowed(user)) - to_chat(user, "Access Denied.") - flick("[skin]-denied", src) - return - - use_power(5) - icon_state = "[skin]1" - - if(device) - device.pulsed() - SEND_GLOBAL_SIGNAL(COMSIG_GLOB_BUTTON_PRESSED,src) - - addtimer(CALLBACK(src, /atom/.proc/update_icon), 15) - -/obj/machinery/button/door - name = "door button" - desc = "A door remote control switch." - var/normaldoorcontrol = FALSE - var/specialfunctions = OPEN // Bitflag, see assembly file - var/sync_doors = TRUE - -/obj/machinery/button/door/indestructible - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - -/obj/machinery/button/door/setup_device() - if(!device) - if(normaldoorcontrol) - var/obj/item/assembly/control/airlock/A = new(src) - A.specialfunctions = specialfunctions - device = A - else - var/obj/item/assembly/control/C = new(src) - C.sync_doors = sync_doors - device = C - ..() - -/obj/machinery/button/door/incinerator_vent_toxmix - name = "combustion chamber vent control" - id = INCINERATOR_TOXMIX_VENT - req_access = list(ACCESS_TOX) - -/obj/machinery/button/door/incinerator_vent_atmos_main - name = "turbine vent control" - id = INCINERATOR_ATMOS_MAINVENT - req_one_access = list(ACCESS_ATMOSPHERICS, ACCESS_MAINT_TUNNELS) - -/obj/machinery/button/door/incinerator_vent_atmos_aux - name = "combustion chamber vent control" - id = INCINERATOR_ATMOS_AUXVENT - req_one_access = list(ACCESS_ATMOSPHERICS, ACCESS_MAINT_TUNNELS) - -/obj/machinery/button/door/incinerator_vent_syndicatelava_main - name = "turbine vent control" - id = INCINERATOR_SYNDICATELAVA_MAINVENT - req_access = list(ACCESS_SYNDICATE) - -/obj/machinery/button/door/incinerator_vent_syndicatelava_aux - name = "combustion chamber vent control" - id = INCINERATOR_SYNDICATELAVA_AUXVENT - req_access = list(ACCESS_SYNDICATE) - -/obj/machinery/button/massdriver - name = "mass driver button" - desc = "A remote control switch for a mass driver." - icon_state = "launcher" - skin = "launcher" - device_type = /obj/item/assembly/control/massdriver - -/obj/machinery/button/massdriver/indestructible - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - -/obj/machinery/button/ignition - name = "ignition switch" - desc = "A remote control switch for a mounted igniter." - icon_state = "launcher" - skin = "launcher" - device_type = /obj/item/assembly/control/igniter - -/obj/machinery/button/ignition/indestructible - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - -/obj/machinery/button/ignition/incinerator - name = "combustion chamber ignition switch" - desc = "A remote control switch for the combustion chamber's igniter." - -/obj/machinery/button/ignition/incinerator/toxmix - id = INCINERATOR_TOXMIX_IGNITER - -/obj/machinery/button/ignition/incinerator/atmos - id = INCINERATOR_ATMOS_IGNITER - -/obj/machinery/button/ignition/incinerator/syndicatelava - id = INCINERATOR_SYNDICATELAVA_IGNITER - -/obj/machinery/button/flasher - name = "flasher button" - desc = "A remote control switch for a mounted flasher." - icon_state = "launcher" - skin = "launcher" - device_type = /obj/item/assembly/control/flasher - -/obj/machinery/button/flasher/indestructible - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - -/obj/machinery/button/crematorium - name = "crematorium igniter" - desc = "Burn baby burn!" - icon_state = "launcher" - skin = "launcher" - device_type = /obj/item/assembly/control/crematorium - req_access = list() - id = 1 - -/obj/machinery/button/crematorium/indestructible - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - -/obj/item/wallframe/button - name = "button frame" - desc = "Used for building buttons." - icon_state = "button" - result_path = /obj/machinery/button - custom_materials = list(/datum/material/iron=MINERAL_MATERIAL_AMOUNT) +/obj/machinery/button + name = "button" + desc = "A remote control switch." + icon = 'icons/obj/stationobjs.dmi' + icon_state = "doorctrl" + var/skin = "doorctrl" + power_channel = AREA_USAGE_ENVIRON + var/obj/item/assembly/device + var/obj/item/electronics/airlock/board + var/device_type = null + var/id = null + var/initialized_button = 0 + armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 50, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 70) + use_power = IDLE_POWER_USE + idle_power_usage = 2 + resistance_flags = LAVA_PROOF | FIRE_PROOF + +/obj/machinery/button/indestructible + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + +/obj/machinery/button/Initialize(mapload, ndir = 0, built = 0) + . = ..() + if(built) + setDir(ndir) + pixel_x = (dir & 3)? 0 : (dir == 4 ? -24 : 24) + pixel_y = (dir & 3)? (dir ==1 ? -24 : 24) : 0 + panel_open = TRUE + update_icon() + + + if(!built && !device && device_type) + device = new device_type(src) + + src.check_access(null) + + if(req_access.len || req_one_access.len) + board = new(src) + if(req_access.len) + board.accesses = req_access + else + board.one_access = 1 + board.accesses = req_one_access + + setup_device() + +/obj/machinery/button/update_icon_state() + if(panel_open) + icon_state = "button-open" + else if(machine_stat & (NOPOWER|BROKEN)) + icon_state = "[skin]-p" + else + icon_state = skin + +/obj/machinery/button/update_overlays() + . = ..() + if(!panel_open) + return + if(device) + . += "button-device" + if(board) + . += "button-board" + +/obj/machinery/button/attackby(obj/item/W, mob/user, params) + if(W.tool_behaviour == TOOL_SCREWDRIVER) + if(panel_open || allowed(user)) + default_deconstruction_screwdriver(user, "button-open", "[skin]",W) + update_icon() + else + to_chat(user, "Maintenance Access Denied.") + flick("[skin]-denied", src) + return + + if(panel_open) + if(!device && istype(W, /obj/item/assembly)) + if(!user.transferItemToLoc(W, src)) + to_chat(user, "\The [W] is stuck to you!") + return + device = W + to_chat(user, "You add [W] to the button.") + + if(!board && istype(W, /obj/item/electronics/airlock)) + if(!user.transferItemToLoc(W, src)) + to_chat(user, "\The [W] is stuck to you!") + return + board = W + if(board.one_access) + req_one_access = board.accesses + else + req_access = board.accesses + to_chat(user, "You add [W] to the button.") + + if(!device && !board && W.tool_behaviour == TOOL_WRENCH) + to_chat(user, "You start unsecuring the button frame...") + W.play_tool_sound(src) + if(W.use_tool(src, user, 40)) + to_chat(user, "You unsecure the button frame.") + transfer_fingerprints_to(new /obj/item/wallframe/button(get_turf(src))) + playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE) + qdel(src) + + update_icon() + return + + if(user.a_intent != INTENT_HARM && !(W.item_flags & NOBLUDGEON)) + return attack_hand(user) + else + return ..() + +/obj/machinery/button/emag_act(mob/user) + if(obj_flags & EMAGGED) + return + req_access = list() + req_one_access = list() + playsound(src, "sparks", 100, TRUE) + obj_flags |= EMAGGED + +/obj/machinery/button/attack_ai(mob/user) + if(!panel_open) + return attack_hand(user) + +/obj/machinery/button/attack_robot(mob/user) + return attack_ai(user) + +/obj/machinery/button/proc/setup_device() + if(id && istype(device, /obj/item/assembly/control)) + var/obj/item/assembly/control/A = device + A.id = id + initialized_button = 1 + +/obj/machinery/button/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) + if(id && istype(device, /obj/item/assembly/control)) + var/obj/item/assembly/control/A = device + A.id = "[idnum][id]" + +/obj/machinery/button/attack_hand(mob/user) + . = ..() + if(.) + return + if(!initialized_button) + setup_device() + add_fingerprint(user) + if(panel_open) + if(device || board) + if(device) + device.forceMove(drop_location()) + device = null + if(board) + board.forceMove(drop_location()) + req_access = list() + req_one_access = list() + board = null + update_icon() + to_chat(user, "You remove electronics from the button frame.") + + else + if(skin == "doorctrl") + skin = "launcher" + else + skin = "doorctrl" + to_chat(user, "You change the button frame's front panel.") + return + + if((machine_stat & (NOPOWER|BROKEN))) + return + + if(device && device.next_activate > world.time) + return + + if(!allowed(user)) + to_chat(user, "Access Denied.") + flick("[skin]-denied", src) + return + + use_power(5) + icon_state = "[skin]1" + + if(device) + device.pulsed() + SEND_GLOBAL_SIGNAL(COMSIG_GLOB_BUTTON_PRESSED,src) + + addtimer(CALLBACK(src, /atom/.proc/update_icon), 15) + +/obj/machinery/button/door + name = "door button" + desc = "A door remote control switch." + var/normaldoorcontrol = FALSE + var/specialfunctions = OPEN // Bitflag, see assembly file + var/sync_doors = TRUE + +/obj/machinery/button/door/indestructible + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + +/obj/machinery/button/door/setup_device() + if(!device) + if(normaldoorcontrol) + var/obj/item/assembly/control/airlock/A = new(src) + A.specialfunctions = specialfunctions + device = A + else + var/obj/item/assembly/control/C = new(src) + C.sync_doors = sync_doors + device = C + ..() + +/obj/machinery/button/door/incinerator_vent_toxmix + name = "combustion chamber vent control" + id = INCINERATOR_TOXMIX_VENT + req_access = list(ACCESS_TOX) + +/obj/machinery/button/door/incinerator_vent_atmos_main + name = "turbine vent control" + id = INCINERATOR_ATMOS_MAINVENT + req_one_access = list(ACCESS_ATMOSPHERICS, ACCESS_MAINT_TUNNELS) + +/obj/machinery/button/door/incinerator_vent_atmos_aux + name = "combustion chamber vent control" + id = INCINERATOR_ATMOS_AUXVENT + req_one_access = list(ACCESS_ATMOSPHERICS, ACCESS_MAINT_TUNNELS) + +/obj/machinery/button/door/incinerator_vent_syndicatelava_main + name = "turbine vent control" + id = INCINERATOR_SYNDICATELAVA_MAINVENT + req_access = list(ACCESS_SYNDICATE) + +/obj/machinery/button/door/incinerator_vent_syndicatelava_aux + name = "combustion chamber vent control" + id = INCINERATOR_SYNDICATELAVA_AUXVENT + req_access = list(ACCESS_SYNDICATE) + +/obj/machinery/button/massdriver + name = "mass driver button" + desc = "A remote control switch for a mass driver." + icon_state = "launcher" + skin = "launcher" + device_type = /obj/item/assembly/control/massdriver + +/obj/machinery/button/massdriver/indestructible + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + +/obj/machinery/button/ignition + name = "ignition switch" + desc = "A remote control switch for a mounted igniter." + icon_state = "launcher" + skin = "launcher" + device_type = /obj/item/assembly/control/igniter + +/obj/machinery/button/ignition/indestructible + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + +/obj/machinery/button/ignition/incinerator + name = "combustion chamber ignition switch" + desc = "A remote control switch for the combustion chamber's igniter." + +/obj/machinery/button/ignition/incinerator/toxmix + id = INCINERATOR_TOXMIX_IGNITER + +/obj/machinery/button/ignition/incinerator/atmos + id = INCINERATOR_ATMOS_IGNITER + +/obj/machinery/button/ignition/incinerator/syndicatelava + id = INCINERATOR_SYNDICATELAVA_IGNITER + +/obj/machinery/button/flasher + name = "flasher button" + desc = "A remote control switch for a mounted flasher." + icon_state = "launcher" + skin = "launcher" + device_type = /obj/item/assembly/control/flasher + +/obj/machinery/button/flasher/indestructible + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + +/obj/machinery/button/crematorium + name = "crematorium igniter" + desc = "Burn baby burn!" + icon_state = "launcher" + skin = "launcher" + device_type = /obj/item/assembly/control/crematorium + req_access = list() + id = 1 + +/obj/machinery/button/crematorium/indestructible + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + +/obj/item/wallframe/button + name = "button frame" + desc = "Used for building buttons." + icon_state = "button" + result_path = /obj/machinery/button + custom_materials = list(/datum/material/iron=MINERAL_MATERIAL_AMOUNT) diff --git a/code/game/machinery/camera/camera_assembly.dm b/code/game/machinery/camera/camera_assembly.dm index ee4041c83bbd..fa3ccf256deb 100644 --- a/code/game/machinery/camera/camera_assembly.dm +++ b/code/game/machinery/camera/camera_assembly.dm @@ -1,287 +1,287 @@ -#define STATE_WRENCHED 1 -#define STATE_WELDED 2 -#define STATE_WIRED 3 -#define STATE_FINISHED 4 - -/obj/item/wallframe/camera - name = "camera assembly" - desc = "The basic construction for Nanotrasen-Always-Watching-You cameras." - icon = 'icons/obj/machines/camera.dmi' - icon_state = "cameracase" - custom_materials = list(/datum/material/iron=400, /datum/material/glass=250) - result_path = /obj/structure/camera_assembly - -/obj/structure/camera_assembly - name = "camera assembly" - desc = "The basic construction for Nanotrasen-Always-Watching-You cameras." - icon = 'icons/obj/machines/camera.dmi' - icon_state = "camera_assembly" - max_integrity = 150 - // Motion, EMP-Proof, X-ray - var/obj/item/analyzer/xray_module - var/malf_xray_firmware_active //used to keep from revealing malf AI upgrades for user facing isXRay() checks when they use Upgrade Camera Network ability - //will be false if the camera is upgraded with the proper parts. - var/malf_xray_firmware_present //so the malf upgrade is restored when the normal upgrade part is removed. - var/obj/item/stack/sheet/mineral/plasma/emp_module - var/malf_emp_firmware_active //used to keep from revealing malf AI upgrades for user facing isEmp() checks after they use Upgrade Camera Network ability - //will be false if the camera is upgraded with the proper parts. - var/malf_emp_firmware_present //so the malf upgrade is restored when the normal upgrade part is removed. - var/obj/item/assembly/prox_sensor/proxy_module - var/state = STATE_WRENCHED - -/obj/structure/camera_assembly/examine(mob/user) - . = ..() - //upgrade messages - var/has_upgrades - if(emp_module) - . += "It has electromagnetic interference shielding installed." - has_upgrades = TRUE - else if(state == STATE_WIRED) - . += "It can be shielded against electromagnetic interference with some plasma." - if(xray_module) - . += "It has an X-ray photodiode installed." - has_upgrades = TRUE - else if(state == STATE_WIRED) - . += "It can be upgraded with an X-ray photodiode with an analyzer." - if(proxy_module) - . += "It has a proximity sensor installed." - has_upgrades = TRUE - else if(state == STATE_WIRED) - . += "It can be upgraded with a proximity sensor." - - //construction states - switch(state) - if(STATE_WRENCHED) - . += "You can secure it in place with a welder, or removed with a wrench." - if(STATE_WELDED) - . += "You can add wires to it, or unweld it from the wall." - if(STATE_WIRED) - if(has_upgrades) - . += "You can remove the contained upgrades with a crowbar." - . += "You can complete it with a screwdriver, or unwire it to start removal." - if(STATE_FINISHED) - . += "You shouldn't be seeing this, tell a coder!" - -/obj/structure/camera_assembly/Initialize(mapload, ndir, building) - . = ..() - if(building) - setDir(ndir) - -/obj/structure/camera_assembly/update_icon_state() - icon_state = "[xray_module ? "xray" : null][initial(icon_state)]" - -/obj/structure/camera_assembly/handle_atom_del(atom/A) - if(A == xray_module) - xray_module = null - update_icon() - if(malf_xray_firmware_present) - malf_xray_firmware_active = malf_xray_firmware_present //re-enable firmware based upgrades after the part is removed. - if(istype(loc, /obj/machinery/camera)) - var/obj/machinery/camera/contained_camera = loc - contained_camera.removeXRay(malf_xray_firmware_present) //make sure we don't remove MALF upgrades. - - else if(A == emp_module) - emp_module = null - if(malf_emp_firmware_present) - malf_emp_firmware_active = malf_emp_firmware_present //re-enable firmware based upgrades after the part is removed. - if(istype(loc, /obj/machinery/camera)) - var/obj/machinery/camera/contained_camera = loc - contained_camera.removeEmpProof(malf_emp_firmware_present) //make sure we don't remove MALF upgrades - - else if(A == proxy_module) - emp_module = null - if(istype(loc, /obj/machinery/camera)) - var/obj/machinery/camera/contained_camera = loc - contained_camera.removeMotion() - - return ..() - - -/obj/structure/camera_assembly/Destroy() - QDEL_NULL(xray_module) - QDEL_NULL(emp_module) - QDEL_NULL(proxy_module) - return ..() - -/obj/structure/camera_assembly/proc/drop_upgrade(obj/item/I) - I.forceMove(drop_location()) - if(I == xray_module) - xray_module = null - if(malf_xray_firmware_present) - malf_xray_firmware_active = malf_xray_firmware_present //re-enable firmware based upgrades after the part is removed. - update_icon() - - else if(I == emp_module) - emp_module = null - if(malf_emp_firmware_present) - malf_emp_firmware_active = malf_emp_firmware_present //re-enable firmware based upgrades after the part is removed. - - else if(I == proxy_module) - proxy_module = null - - -/obj/structure/camera_assembly/attackby(obj/item/W, mob/living/user, params) - switch(state) - if(STATE_WRENCHED) - if(W.tool_behaviour == TOOL_WELDER) - if(weld(W, user)) - to_chat(user, "You weld [src] securely into place.") - setAnchored(TRUE) - state = STATE_WELDED - return - - if(STATE_WELDED) - if(istype(W, /obj/item/stack/cable_coil)) - var/obj/item/stack/cable_coil/C = W - if(C.use(2)) - to_chat(user, "You add wires to [src].") - state = STATE_WIRED - else - to_chat(user, "You need two lengths of cable to wire a camera!") - return - return - - else if(W.tool_behaviour == TOOL_WELDER) - - if(weld(W, user)) - to_chat(user, "You unweld [src] from its place.") - state = STATE_WRENCHED - setAnchored(TRUE) - return - - if(STATE_WIRED) // Upgrades! - if(istype(W, /obj/item/stack/sheet/mineral/plasma)) //emp upgrade - if(emp_module) - to_chat(user, "[src] already contains a [emp_module]!") - return - if(!W.use_tool(src, user, 0, amount=1)) //only use one sheet, otherwise the whole stack will be consumed. - return - emp_module = new(src) - if(malf_xray_firmware_active) - malf_xray_firmware_active = FALSE //flavor reason: MALF AI Upgrade Camera Network ability's firmware is incompatible with the new part - //real reason: make it a normal upgrade so the finished camera's icons and examine texts are restored. - to_chat(user, "You attach [W] into [src]'s inner circuits.") - return - - else if(istype(W, /obj/item/analyzer)) //xray upgrade - if(xray_module) - to_chat(user, "[src] already contains a [xray_module]!") - return - if(!user.transferItemToLoc(W, src)) - return - to_chat(user, "You attach [W] into [src]'s inner circuits.") - xray_module = W - if(malf_xray_firmware_active) - malf_xray_firmware_active = FALSE //flavor reason: MALF AI Upgrade Camera Network ability's firmware is incompatible with the new part - //real reason: make it a normal upgrade so the finished camera's icons and examine texts are restored. - update_icon() - return - - else if(istype(W, /obj/item/assembly/prox_sensor)) //motion sensing upgrade - if(proxy_module) - to_chat(user, "[src] already contains a [proxy_module]!") - return - if(!user.transferItemToLoc(W, src)) - return - to_chat(user, "You attach [W] into [src]'s inner circuits.") - proxy_module = W - return - - return ..() - -/obj/structure/camera_assembly/crowbar_act(mob/user, obj/item/tool) - if(state != STATE_WIRED) - return FALSE - var/list/droppable_parts = list() - if(xray_module) - droppable_parts += xray_module - if(emp_module) - droppable_parts += emp_module - if(proxy_module) - droppable_parts += proxy_module - if(!droppable_parts.len) - return - var/obj/item/choice = input(user, "Select a part to remove:", src) as null|obj in sortNames(droppable_parts) - if(!choice || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - to_chat(user, "You remove [choice] from [src].") - drop_upgrade(choice) - tool.play_tool_sound(src) - return TRUE - -/obj/structure/camera_assembly/screwdriver_act(mob/user, obj/item/tool) - . = ..() - if(.) - return TRUE - if(state != STATE_WIRED) - return FALSE - - tool.play_tool_sound(src) - var/input = stripped_input(user, "Which networks would you like to connect this camera to? Separate networks with a comma. No Spaces!\nFor example: SS13,Security,Secret ", "Set Network", "SS13") - if(!input) - to_chat(user, "No input found, please hang up and try your call again!") - return - var/list/tempnetwork = splittext(input, ",") - if(tempnetwork.len < 1) - to_chat(user, "No network found, please hang up and try your call again!") - return - for(var/i in tempnetwork) - tempnetwork -= i - tempnetwork += lowertext(i) - state = STATE_FINISHED - var/obj/machinery/camera/C = new(loc, src) - forceMove(C) - C.setDir(src.dir) - - C.network = tempnetwork - var/area/A = get_area(src) - C.c_tag = "[A.name] ([rand(1, 999)])" - return TRUE - -/obj/structure/camera_assembly/wirecutter_act(mob/user, obj/item/I) - . = ..() - if(state != STATE_WIRED) - return - - new /obj/item/stack/cable_coil(drop_location(), 2) - I.play_tool_sound(src) - to_chat(user, "You cut the wires from the circuits.") - state = STATE_WELDED - return TRUE - -/obj/structure/camera_assembly/wrench_act(mob/user, obj/item/I) - . = ..() - if(state != STATE_WRENCHED) - return - I.play_tool_sound(src) - to_chat(user, "You detach [src] from its place.") - new /obj/item/wallframe/camera(drop_location()) - //drop upgrades - if(xray_module) - drop_upgrade(xray_module) - if(emp_module) - drop_upgrade(emp_module) - if(proxy_module) - drop_upgrade(proxy_module) - - qdel(src) - return TRUE - -/obj/structure/camera_assembly/proc/weld(obj/item/weldingtool/W, mob/living/user) - if(!W.tool_start_check(user, amount=3)) - return FALSE - to_chat(user, "You start to weld [src]...") - if(W.use_tool(src, user, 20, amount=3, volume = 50)) - return TRUE - return FALSE - -/obj/structure/camera_assembly/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - new /obj/item/stack/sheet/metal(loc) - qdel(src) - - -#undef STATE_WRENCHED -#undef STATE_WELDED -#undef STATE_WIRED -#undef STATE_FINISHED +#define STATE_WRENCHED 1 +#define STATE_WELDED 2 +#define STATE_WIRED 3 +#define STATE_FINISHED 4 + +/obj/item/wallframe/camera + name = "camera assembly" + desc = "The basic construction for Nanotrasen-Always-Watching-You cameras." + icon = 'icons/obj/machines/camera.dmi' + icon_state = "cameracase" + custom_materials = list(/datum/material/iron=400, /datum/material/glass=250) + result_path = /obj/structure/camera_assembly + +/obj/structure/camera_assembly + name = "camera assembly" + desc = "The basic construction for Nanotrasen-Always-Watching-You cameras." + icon = 'icons/obj/machines/camera.dmi' + icon_state = "camera_assembly" + max_integrity = 150 + // Motion, EMP-Proof, X-ray + var/obj/item/analyzer/xray_module + var/malf_xray_firmware_active //used to keep from revealing malf AI upgrades for user facing isXRay() checks when they use Upgrade Camera Network ability + //will be false if the camera is upgraded with the proper parts. + var/malf_xray_firmware_present //so the malf upgrade is restored when the normal upgrade part is removed. + var/obj/item/stack/sheet/mineral/plasma/emp_module + var/malf_emp_firmware_active //used to keep from revealing malf AI upgrades for user facing isEmp() checks after they use Upgrade Camera Network ability + //will be false if the camera is upgraded with the proper parts. + var/malf_emp_firmware_present //so the malf upgrade is restored when the normal upgrade part is removed. + var/obj/item/assembly/prox_sensor/proxy_module + var/state = STATE_WRENCHED + +/obj/structure/camera_assembly/examine(mob/user) + . = ..() + //upgrade messages + var/has_upgrades + if(emp_module) + . += "It has electromagnetic interference shielding installed." + has_upgrades = TRUE + else if(state == STATE_WIRED) + . += "It can be shielded against electromagnetic interference with some plasma." + if(xray_module) + . += "It has an X-ray photodiode installed." + has_upgrades = TRUE + else if(state == STATE_WIRED) + . += "It can be upgraded with an X-ray photodiode with an analyzer." + if(proxy_module) + . += "It has a proximity sensor installed." + has_upgrades = TRUE + else if(state == STATE_WIRED) + . += "It can be upgraded with a proximity sensor." + + //construction states + switch(state) + if(STATE_WRENCHED) + . += "You can secure it in place with a welder, or removed with a wrench." + if(STATE_WELDED) + . += "You can add wires to it, or unweld it from the wall." + if(STATE_WIRED) + if(has_upgrades) + . += "You can remove the contained upgrades with a crowbar." + . += "You can complete it with a screwdriver, or unwire it to start removal." + if(STATE_FINISHED) + . += "You shouldn't be seeing this, tell a coder!" + +/obj/structure/camera_assembly/Initialize(mapload, ndir, building) + . = ..() + if(building) + setDir(ndir) + +/obj/structure/camera_assembly/update_icon_state() + icon_state = "[xray_module ? "xray" : null][initial(icon_state)]" + +/obj/structure/camera_assembly/handle_atom_del(atom/A) + if(A == xray_module) + xray_module = null + update_icon() + if(malf_xray_firmware_present) + malf_xray_firmware_active = malf_xray_firmware_present //re-enable firmware based upgrades after the part is removed. + if(istype(loc, /obj/machinery/camera)) + var/obj/machinery/camera/contained_camera = loc + contained_camera.removeXRay(malf_xray_firmware_present) //make sure we don't remove MALF upgrades. + + else if(A == emp_module) + emp_module = null + if(malf_emp_firmware_present) + malf_emp_firmware_active = malf_emp_firmware_present //re-enable firmware based upgrades after the part is removed. + if(istype(loc, /obj/machinery/camera)) + var/obj/machinery/camera/contained_camera = loc + contained_camera.removeEmpProof(malf_emp_firmware_present) //make sure we don't remove MALF upgrades + + else if(A == proxy_module) + emp_module = null + if(istype(loc, /obj/machinery/camera)) + var/obj/machinery/camera/contained_camera = loc + contained_camera.removeMotion() + + return ..() + + +/obj/structure/camera_assembly/Destroy() + QDEL_NULL(xray_module) + QDEL_NULL(emp_module) + QDEL_NULL(proxy_module) + return ..() + +/obj/structure/camera_assembly/proc/drop_upgrade(obj/item/I) + I.forceMove(drop_location()) + if(I == xray_module) + xray_module = null + if(malf_xray_firmware_present) + malf_xray_firmware_active = malf_xray_firmware_present //re-enable firmware based upgrades after the part is removed. + update_icon() + + else if(I == emp_module) + emp_module = null + if(malf_emp_firmware_present) + malf_emp_firmware_active = malf_emp_firmware_present //re-enable firmware based upgrades after the part is removed. + + else if(I == proxy_module) + proxy_module = null + + +/obj/structure/camera_assembly/attackby(obj/item/W, mob/living/user, params) + switch(state) + if(STATE_WRENCHED) + if(W.tool_behaviour == TOOL_WELDER) + if(weld(W, user)) + to_chat(user, "You weld [src] securely into place.") + setAnchored(TRUE) + state = STATE_WELDED + return + + if(STATE_WELDED) + if(istype(W, /obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/C = W + if(C.use(2)) + to_chat(user, "You add wires to [src].") + state = STATE_WIRED + else + to_chat(user, "You need two lengths of cable to wire a camera!") + return + return + + else if(W.tool_behaviour == TOOL_WELDER) + + if(weld(W, user)) + to_chat(user, "You unweld [src] from its place.") + state = STATE_WRENCHED + setAnchored(TRUE) + return + + if(STATE_WIRED) // Upgrades! + if(istype(W, /obj/item/stack/sheet/mineral/plasma)) //emp upgrade + if(emp_module) + to_chat(user, "[src] already contains a [emp_module]!") + return + if(!W.use_tool(src, user, 0, amount=1)) //only use one sheet, otherwise the whole stack will be consumed. + return + emp_module = new(src) + if(malf_xray_firmware_active) + malf_xray_firmware_active = FALSE //flavor reason: MALF AI Upgrade Camera Network ability's firmware is incompatible with the new part + //real reason: make it a normal upgrade so the finished camera's icons and examine texts are restored. + to_chat(user, "You attach [W] into [src]'s inner circuits.") + return + + else if(istype(W, /obj/item/analyzer)) //xray upgrade + if(xray_module) + to_chat(user, "[src] already contains a [xray_module]!") + return + if(!user.transferItemToLoc(W, src)) + return + to_chat(user, "You attach [W] into [src]'s inner circuits.") + xray_module = W + if(malf_xray_firmware_active) + malf_xray_firmware_active = FALSE //flavor reason: MALF AI Upgrade Camera Network ability's firmware is incompatible with the new part + //real reason: make it a normal upgrade so the finished camera's icons and examine texts are restored. + update_icon() + return + + else if(istype(W, /obj/item/assembly/prox_sensor)) //motion sensing upgrade + if(proxy_module) + to_chat(user, "[src] already contains a [proxy_module]!") + return + if(!user.transferItemToLoc(W, src)) + return + to_chat(user, "You attach [W] into [src]'s inner circuits.") + proxy_module = W + return + + return ..() + +/obj/structure/camera_assembly/crowbar_act(mob/user, obj/item/tool) + if(state != STATE_WIRED) + return FALSE + var/list/droppable_parts = list() + if(xray_module) + droppable_parts += xray_module + if(emp_module) + droppable_parts += emp_module + if(proxy_module) + droppable_parts += proxy_module + if(!droppable_parts.len) + return + var/obj/item/choice = input(user, "Select a part to remove:", src) as null|obj in sortNames(droppable_parts) + if(!choice || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + to_chat(user, "You remove [choice] from [src].") + drop_upgrade(choice) + tool.play_tool_sound(src) + return TRUE + +/obj/structure/camera_assembly/screwdriver_act(mob/user, obj/item/tool) + . = ..() + if(.) + return TRUE + if(state != STATE_WIRED) + return FALSE + + tool.play_tool_sound(src) + var/input = stripped_input(user, "Which networks would you like to connect this camera to? Separate networks with a comma. No Spaces!\nFor example: SS13,Security,Secret ", "Set Network", "SS13") + if(!input) + to_chat(user, "No input found, please hang up and try your call again!") + return + var/list/tempnetwork = splittext(input, ",") + if(tempnetwork.len < 1) + to_chat(user, "No network found, please hang up and try your call again!") + return + for(var/i in tempnetwork) + tempnetwork -= i + tempnetwork += lowertext(i) + state = STATE_FINISHED + var/obj/machinery/camera/C = new(loc, src) + forceMove(C) + C.setDir(src.dir) + + C.network = tempnetwork + var/area/A = get_area(src) + C.c_tag = "[A.name] ([rand(1, 999)])" + return TRUE + +/obj/structure/camera_assembly/wirecutter_act(mob/user, obj/item/I) + . = ..() + if(state != STATE_WIRED) + return + + new /obj/item/stack/cable_coil(drop_location(), 2) + I.play_tool_sound(src) + to_chat(user, "You cut the wires from the circuits.") + state = STATE_WELDED + return TRUE + +/obj/structure/camera_assembly/wrench_act(mob/user, obj/item/I) + . = ..() + if(state != STATE_WRENCHED) + return + I.play_tool_sound(src) + to_chat(user, "You detach [src] from its place.") + new /obj/item/wallframe/camera(drop_location()) + //drop upgrades + if(xray_module) + drop_upgrade(xray_module) + if(emp_module) + drop_upgrade(emp_module) + if(proxy_module) + drop_upgrade(proxy_module) + + qdel(src) + return TRUE + +/obj/structure/camera_assembly/proc/weld(obj/item/weldingtool/W, mob/living/user) + if(!W.tool_start_check(user, amount=3)) + return FALSE + to_chat(user, "You start to weld [src]...") + if(W.use_tool(src, user, 20, amount=3, volume = 50)) + return TRUE + return FALSE + +/obj/structure/camera_assembly/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + new /obj/item/stack/sheet/metal(loc) + qdel(src) + + +#undef STATE_WRENCHED +#undef STATE_WELDED +#undef STATE_WIRED +#undef STATE_FINISHED diff --git a/code/game/machinery/camera/motion.dm b/code/game/machinery/camera/motion.dm index 3638fb50ccda..a5f531cfd603 100644 --- a/code/game/machinery/camera/motion.dm +++ b/code/game/machinery/camera/motion.dm @@ -1,112 +1,112 @@ -/obj/machinery/camera - - var/list/datum/weakref/localMotionTargets = list() - var/detectTime = 0 - var/area/ai_monitored/area_motion = null - var/alarm_delay = 30 // Don't forget, there's another 3 seconds in queueAlarm() - -/obj/machinery/camera/process() - // motion camera event loop - if(!isMotion()) - . = PROCESS_KILL - return - if(machine_stat & EMPED) - return - if (detectTime > 0) - var/elapsed = world.time - detectTime - if (elapsed > alarm_delay) - triggerAlarm() - else if (detectTime == -1) - for (var/datum/weakref/targetref in getTargetList()) - var/mob/target = targetref.resolve() - if(QDELETED(target) || target.stat == DEAD || (!area_motion && !in_range(src, target))) - //If not part of a monitored area and the camera is not in range or the target is dead - lostTargetRef(targetref) - -/obj/machinery/camera/proc/getTargetList() - if(area_motion) - return area_motion.motionTargets - return localMotionTargets - -/obj/machinery/camera/proc/newTarget(mob/target) - if(isAI(target)) - return FALSE - if (detectTime == 0) - detectTime = world.time // start the clock - var/list/targets = getTargetList() - targets |= WEAKREF(target) - return TRUE - -/obj/machinery/camera/Destroy() - var/area/ai_monitored/A = get_area(src) - localMotionTargets = null - if(istype(A)) - A.motioncameras -= src - cancelAlarm() - return ..() - -/obj/machinery/camera/proc/lostTargetRef(datum/weakref/R) - var/list/targets = getTargetList() - targets -= R - if (targets.len == 0) - cancelAlarm() - -/obj/machinery/camera/proc/cancelAlarm() - if (detectTime == -1) - for (var/i in GLOB.silicon_mobs) - var/mob/living/silicon/aiPlayer = i - if (status) - aiPlayer.cancelAlarm("Motion", get_area(src), src) - detectTime = 0 - return TRUE - -/obj/machinery/camera/proc/triggerAlarm() - if (!detectTime) - return FALSE - for (var/mob/living/silicon/aiPlayer in GLOB.player_list) - if (status) - aiPlayer.triggerAlarm("Motion", get_area(src), list(src), src) - visible_message("A red light flashes on the [src]!") - detectTime = -1 - return TRUE - -/obj/machinery/camera/HasProximity(atom/movable/AM as mob|obj) - // Motion cameras outside of an "ai monitored" area will use this to detect stuff. - if (!area_motion) - if(isliving(AM)) - newTarget(AM) - -/obj/machinery/camera/motion/thunderdome - name = "entertainment camera" - network = list("thunder") - c_tag = "Arena" - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF | FREEZE_PROOF - -/obj/machinery/camera/motion/thunderdome/Initialize() - . = ..() - proximity_monitor.SetRange(7) - -/obj/machinery/camera/motion/thunderdome/HasProximity(atom/movable/AM as mob|obj) - if (!isliving(AM) || get_area(AM) != get_area(src)) - return - localMotionTargets |= WEAKREF(AM) - if (!detectTime) - for(var/obj/machinery/computer/security/telescreen/entertainment/TV in GLOB.machines) - TV.notify(TRUE) - detectTime = world.time + 30 SECONDS - -/obj/machinery/camera/motion/thunderdome/process() - if (!detectTime) - return - - for (var/datum/weakref/targetref in localMotionTargets) - var/mob/target = targetref.resolve() - if(QDELETED(target) || target.stat == DEAD || get_dist(src, target) > 7 || get_area(src) != get_area(target)) - localMotionTargets -= targetref - - if (localMotionTargets.len) - detectTime = world.time + 30 SECONDS - else if (world.time > detectTime) - detectTime = 0 - for(var/obj/machinery/computer/security/telescreen/entertainment/TV in GLOB.machines) - TV.notify(FALSE) +/obj/machinery/camera + + var/list/datum/weakref/localMotionTargets = list() + var/detectTime = 0 + var/area/ai_monitored/area_motion = null + var/alarm_delay = 30 // Don't forget, there's another 3 seconds in queueAlarm() + +/obj/machinery/camera/process() + // motion camera event loop + if(!isMotion()) + . = PROCESS_KILL + return + if(machine_stat & EMPED) + return + if (detectTime > 0) + var/elapsed = world.time - detectTime + if (elapsed > alarm_delay) + triggerAlarm() + else if (detectTime == -1) + for (var/datum/weakref/targetref in getTargetList()) + var/mob/target = targetref.resolve() + if(QDELETED(target) || target.stat == DEAD || (!area_motion && !in_range(src, target))) + //If not part of a monitored area and the camera is not in range or the target is dead + lostTargetRef(targetref) + +/obj/machinery/camera/proc/getTargetList() + if(area_motion) + return area_motion.motionTargets + return localMotionTargets + +/obj/machinery/camera/proc/newTarget(mob/target) + if(isAI(target)) + return FALSE + if (detectTime == 0) + detectTime = world.time // start the clock + var/list/targets = getTargetList() + targets |= WEAKREF(target) + return TRUE + +/obj/machinery/camera/Destroy() + var/area/ai_monitored/A = get_area(src) + localMotionTargets = null + if(istype(A)) + A.motioncameras -= src + cancelAlarm() + return ..() + +/obj/machinery/camera/proc/lostTargetRef(datum/weakref/R) + var/list/targets = getTargetList() + targets -= R + if (targets.len == 0) + cancelAlarm() + +/obj/machinery/camera/proc/cancelAlarm() + if (detectTime == -1) + for (var/i in GLOB.silicon_mobs) + var/mob/living/silicon/aiPlayer = i + if (status) + aiPlayer.cancelAlarm("Motion", get_area(src), src) + detectTime = 0 + return TRUE + +/obj/machinery/camera/proc/triggerAlarm() + if (!detectTime) + return FALSE + for (var/mob/living/silicon/aiPlayer in GLOB.player_list) + if (status) + aiPlayer.triggerAlarm("Motion", get_area(src), list(src), src) + visible_message("A red light flashes on the [src]!") + detectTime = -1 + return TRUE + +/obj/machinery/camera/HasProximity(atom/movable/AM as mob|obj) + // Motion cameras outside of an "ai monitored" area will use this to detect stuff. + if (!area_motion) + if(isliving(AM)) + newTarget(AM) + +/obj/machinery/camera/motion/thunderdome + name = "entertainment camera" + network = list("thunder") + c_tag = "Arena" + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF | FREEZE_PROOF + +/obj/machinery/camera/motion/thunderdome/Initialize() + . = ..() + proximity_monitor.SetRange(7) + +/obj/machinery/camera/motion/thunderdome/HasProximity(atom/movable/AM as mob|obj) + if (!isliving(AM) || get_area(AM) != get_area(src)) + return + localMotionTargets |= WEAKREF(AM) + if (!detectTime) + for(var/obj/machinery/computer/security/telescreen/entertainment/TV in GLOB.machines) + TV.notify(TRUE) + detectTime = world.time + 30 SECONDS + +/obj/machinery/camera/motion/thunderdome/process() + if (!detectTime) + return + + for (var/datum/weakref/targetref in localMotionTargets) + var/mob/target = targetref.resolve() + if(QDELETED(target) || target.stat == DEAD || get_dist(src, target) > 7 || get_area(src) != get_area(target)) + localMotionTargets -= targetref + + if (localMotionTargets.len) + detectTime = world.time + 30 SECONDS + else if (world.time > detectTime) + detectTime = 0 + for(var/obj/machinery/computer/security/telescreen/entertainment/TV in GLOB.machines) + TV.notify(FALSE) diff --git a/code/game/machinery/camera/presets.dm b/code/game/machinery/camera/presets.dm index 9e1978468b17..dcbff23b21ae 100644 --- a/code/game/machinery/camera/presets.dm +++ b/code/game/machinery/camera/presets.dm @@ -1,140 +1,140 @@ -// PRESETS - -// EMP -/obj/machinery/camera/emp_proof - start_active = TRUE - -/obj/machinery/camera/emp_proof/Initialize() - . = ..() - upgradeEmpProof() - -// EMP + Motion - -/obj/machinery/camera/emp_proof/motion/Initialize() - . = ..() - upgradeMotion() - -// X-ray - -/obj/machinery/camera/xray - start_active = TRUE - icon_state = "xraycamera" //mapping icon - Thanks to Krutchen for the icons. - -/obj/machinery/camera/xray/Initialize() - . = ..() - upgradeXRay() - -// MOTION -/obj/machinery/camera/motion - start_active = TRUE - name = "motion-sensitive security camera" - -/obj/machinery/camera/motion/Initialize() - . = ..() - upgradeMotion() - -// ALL UPGRADES -/obj/machinery/camera/all - start_active = TRUE - icon_state = "xraycamera" //mapping icon. - -/obj/machinery/camera/all/Initialize() - . = ..() - upgradeEmpProof() - upgradeXRay() - upgradeMotion() - -// AUTONAME - -/obj/machinery/camera/autoname - var/number = 0 //camera number in area - -//This camera type automatically sets it's name to whatever the area that it's in is called. -/obj/machinery/camera/autoname/Initialize() - ..() - return INITIALIZE_HINT_LATELOAD - -/obj/machinery/camera/autoname/LateInitialize() - . = ..() - number = 1 - var/area/A = get_area(src) - if(A) - for(var/obj/machinery/camera/autoname/C in GLOB.machines) - if(C == src) - continue - var/area/CA = get_area(C) - if(CA.type == A.type) - if(C.number) - number = max(number, C.number+1) - c_tag = "[A.name] #[number]" - - -// UPGRADE PROCS - -/obj/machinery/camera/proc/isEmpProof(ignore_malf_upgrades) - return (upgrades & CAMERA_UPGRADE_EMP_PROOF) && (!(ignore_malf_upgrades && assembly.malf_emp_firmware_active)) - -/obj/machinery/camera/proc/upgradeEmpProof(malf_upgrade, ignore_malf_upgrades) - if(isEmpProof(ignore_malf_upgrades)) //pass a malf upgrade to ignore_malf_upgrades so we can replace the malf module with the normal one - return //that way if someone tries to upgrade an already malf-upgraded camera, it'll just upgrade it to a normal version. - emp_component = AddComponent(/datum/component/empprotection, EMP_PROTECT_SELF | EMP_PROTECT_WIRES | EMP_PROTECT_CONTENTS) - if(malf_upgrade) - assembly.malf_emp_firmware_active = TRUE //don't add parts to drop, update icon, ect. reconstructing it will also retain the upgrade. - assembly.malf_emp_firmware_present = TRUE //so the upgrade is retained after incompatible parts are removed. - - else if(!assembly.emp_module) //only happens via upgrading in camera/attackby() - assembly.emp_module = new(assembly) - if(assembly.malf_emp_firmware_active) - assembly.malf_emp_firmware_active = FALSE //make it appear like it's just normally upgraded so the icons and examine texts are restored. - - upgrades |= CAMERA_UPGRADE_EMP_PROOF - -/obj/machinery/camera/proc/removeEmpProof(ignore_malf_upgrades) - if(ignore_malf_upgrades) //don't downgrade it if malf software is forced onto it. - return - emp_component.RemoveComponent() - upgrades &= ~CAMERA_UPGRADE_EMP_PROOF - - - -/obj/machinery/camera/proc/isXRay(ignore_malf_upgrades) - return (upgrades & CAMERA_UPGRADE_XRAY) && (!(ignore_malf_upgrades && assembly.malf_xray_firmware_active)) - -/obj/machinery/camera/proc/upgradeXRay(malf_upgrade, ignore_malf_upgrades) - if(isXRay(ignore_malf_upgrades)) //pass a malf upgrade to ignore_malf_upgrades so we can replace the malf upgrade with the normal one - return //that way if someone tries to upgrade an already malf-upgraded camera, it'll just upgrade it to a normal version. - if(malf_upgrade) - assembly.malf_xray_firmware_active = TRUE //don't add parts to drop, update icon, ect. reconstructing it will also retain the upgrade. - assembly.malf_xray_firmware_present = TRUE //so the upgrade is retained after incompatible parts are removed. - - else if(!assembly.xray_module) //only happens via upgrading in camera/attackby() - assembly.xray_module = new(assembly) - if(assembly.malf_xray_firmware_active) - assembly.malf_xray_firmware_active = FALSE //make it appear like it's just normally upgraded so the icons and examine texts are restored. - - upgrades |= CAMERA_UPGRADE_XRAY - update_icon() - -/obj/machinery/camera/proc/removeXRay(ignore_malf_upgrades) - if(!ignore_malf_upgrades) //don't downgrade it if malf software is forced onto it. - upgrades &= ~CAMERA_UPGRADE_XRAY - update_icon() - - - -/obj/machinery/camera/proc/isMotion() - return upgrades & CAMERA_UPGRADE_MOTION - -/obj/machinery/camera/proc/upgradeMotion() - if(isMotion()) - return - if(name == initial(name)) - name = "motion-sensitive security camera" - if(!assembly.proxy_module) - assembly.proxy_module = new(assembly) - upgrades |= CAMERA_UPGRADE_MOTION - -/obj/machinery/camera/proc/removeMotion() - if(name == "motion-sensitive security camera") - name = "security camera" - upgrades &= ~CAMERA_UPGRADE_MOTION +// PRESETS + +// EMP +/obj/machinery/camera/emp_proof + start_active = TRUE + +/obj/machinery/camera/emp_proof/Initialize() + . = ..() + upgradeEmpProof() + +// EMP + Motion + +/obj/machinery/camera/emp_proof/motion/Initialize() + . = ..() + upgradeMotion() + +// X-ray + +/obj/machinery/camera/xray + start_active = TRUE + icon_state = "xraycamera" //mapping icon - Thanks to Krutchen for the icons. + +/obj/machinery/camera/xray/Initialize() + . = ..() + upgradeXRay() + +// MOTION +/obj/machinery/camera/motion + start_active = TRUE + name = "motion-sensitive security camera" + +/obj/machinery/camera/motion/Initialize() + . = ..() + upgradeMotion() + +// ALL UPGRADES +/obj/machinery/camera/all + start_active = TRUE + icon_state = "xraycamera" //mapping icon. + +/obj/machinery/camera/all/Initialize() + . = ..() + upgradeEmpProof() + upgradeXRay() + upgradeMotion() + +// AUTONAME + +/obj/machinery/camera/autoname + var/number = 0 //camera number in area + +//This camera type automatically sets it's name to whatever the area that it's in is called. +/obj/machinery/camera/autoname/Initialize() + ..() + return INITIALIZE_HINT_LATELOAD + +/obj/machinery/camera/autoname/LateInitialize() + . = ..() + number = 1 + var/area/A = get_area(src) + if(A) + for(var/obj/machinery/camera/autoname/C in GLOB.machines) + if(C == src) + continue + var/area/CA = get_area(C) + if(CA.type == A.type) + if(C.number) + number = max(number, C.number+1) + c_tag = "[A.name] #[number]" + + +// UPGRADE PROCS + +/obj/machinery/camera/proc/isEmpProof(ignore_malf_upgrades) + return (upgrades & CAMERA_UPGRADE_EMP_PROOF) && (!(ignore_malf_upgrades && assembly.malf_emp_firmware_active)) + +/obj/machinery/camera/proc/upgradeEmpProof(malf_upgrade, ignore_malf_upgrades) + if(isEmpProof(ignore_malf_upgrades)) //pass a malf upgrade to ignore_malf_upgrades so we can replace the malf module with the normal one + return //that way if someone tries to upgrade an already malf-upgraded camera, it'll just upgrade it to a normal version. + emp_component = AddComponent(/datum/component/empprotection, EMP_PROTECT_SELF | EMP_PROTECT_WIRES | EMP_PROTECT_CONTENTS) + if(malf_upgrade) + assembly.malf_emp_firmware_active = TRUE //don't add parts to drop, update icon, ect. reconstructing it will also retain the upgrade. + assembly.malf_emp_firmware_present = TRUE //so the upgrade is retained after incompatible parts are removed. + + else if(!assembly.emp_module) //only happens via upgrading in camera/attackby() + assembly.emp_module = new(assembly) + if(assembly.malf_emp_firmware_active) + assembly.malf_emp_firmware_active = FALSE //make it appear like it's just normally upgraded so the icons and examine texts are restored. + + upgrades |= CAMERA_UPGRADE_EMP_PROOF + +/obj/machinery/camera/proc/removeEmpProof(ignore_malf_upgrades) + if(ignore_malf_upgrades) //don't downgrade it if malf software is forced onto it. + return + emp_component.RemoveComponent() + upgrades &= ~CAMERA_UPGRADE_EMP_PROOF + + + +/obj/machinery/camera/proc/isXRay(ignore_malf_upgrades) + return (upgrades & CAMERA_UPGRADE_XRAY) && (!(ignore_malf_upgrades && assembly.malf_xray_firmware_active)) + +/obj/machinery/camera/proc/upgradeXRay(malf_upgrade, ignore_malf_upgrades) + if(isXRay(ignore_malf_upgrades)) //pass a malf upgrade to ignore_malf_upgrades so we can replace the malf upgrade with the normal one + return //that way if someone tries to upgrade an already malf-upgraded camera, it'll just upgrade it to a normal version. + if(malf_upgrade) + assembly.malf_xray_firmware_active = TRUE //don't add parts to drop, update icon, ect. reconstructing it will also retain the upgrade. + assembly.malf_xray_firmware_present = TRUE //so the upgrade is retained after incompatible parts are removed. + + else if(!assembly.xray_module) //only happens via upgrading in camera/attackby() + assembly.xray_module = new(assembly) + if(assembly.malf_xray_firmware_active) + assembly.malf_xray_firmware_active = FALSE //make it appear like it's just normally upgraded so the icons and examine texts are restored. + + upgrades |= CAMERA_UPGRADE_XRAY + update_icon() + +/obj/machinery/camera/proc/removeXRay(ignore_malf_upgrades) + if(!ignore_malf_upgrades) //don't downgrade it if malf software is forced onto it. + upgrades &= ~CAMERA_UPGRADE_XRAY + update_icon() + + + +/obj/machinery/camera/proc/isMotion() + return upgrades & CAMERA_UPGRADE_MOTION + +/obj/machinery/camera/proc/upgradeMotion() + if(isMotion()) + return + if(name == initial(name)) + name = "motion-sensitive security camera" + if(!assembly.proxy_module) + assembly.proxy_module = new(assembly) + upgrades |= CAMERA_UPGRADE_MOTION + +/obj/machinery/camera/proc/removeMotion() + if(name == "motion-sensitive security camera") + name = "security camera" + upgrades &= ~CAMERA_UPGRADE_MOTION diff --git a/code/game/machinery/camera/tracking.dm b/code/game/machinery/camera/tracking.dm index 26f08e0e295e..1b4b01f204db 100644 --- a/code/game/machinery/camera/tracking.dm +++ b/code/game/machinery/camera/tracking.dm @@ -1,154 +1,154 @@ -/mob/living/silicon/ai/proc/get_camera_list() - var/list/L = list() - for (var/obj/machinery/camera/C in GLOB.cameranet.cameras) - L.Add(C) - - camera_sort(L) - - var/list/T = list() - - for (var/obj/machinery/camera/C in L) - var/list/tempnetwork = C.network&src.network - if (tempnetwork.len) - T[text("[][]", C.c_tag, (C.can_use() ? null : " (Deactivated)"))] = C - - return T - -/mob/living/silicon/ai/proc/show_camera_list() - var/list/cameras = get_camera_list() - var/camera = input(src, "Choose which camera you want to view", "Cameras") as null|anything in cameras - switchCamera(cameras[camera]) - -/datum/trackable - var/initialized = FALSE - var/list/names = list() - var/list/namecounts = list() - var/list/humans = list() - var/list/others = list() - -/mob/living/silicon/ai/proc/trackable_mobs() - track.initialized = TRUE - track.names.Cut() - track.namecounts.Cut() - track.humans.Cut() - track.others.Cut() - - if(usr.stat == DEAD) - return list() - - for(var/i in GLOB.mob_living_list) - var/mob/living/L = i - if(!L.can_track(usr)) - continue - - var/name = L.name - while(name in track.names) - track.namecounts[name]++ - name = text("[] ([])", name, track.namecounts[name]) - track.names.Add(name) - track.namecounts[name] = 1 - - if(ishuman(L)) - track.humans[name] = L - else - track.others[name] = L - - var/list/targets = sortList(track.humans) + sortList(track.others) - - return targets - -/mob/living/silicon/ai/verb/ai_camera_track(target_name in trackable_mobs()) - set name = "track" - set hidden = 1 //Don't display it on the verb lists. This verb exists purely so you can type "track Oldman Robustin" and follow his ass - - if(!target_name) - return - - if(!track.initialized) - trackable_mobs() - - var/mob/target = (isnull(track.humans[target_name]) ? track.others[target_name] : track.humans[target_name]) - - ai_actual_track(target) - -/mob/living/silicon/ai/proc/ai_actual_track(mob/living/target) - if(!istype(target)) - return - var/mob/living/silicon/ai/U = usr - - U.cameraFollow = target - U.tracking = 1 - - if(!target || !target.can_track(usr)) - to_chat(U, "Target is not near any active cameras.") - U.cameraFollow = null - return - - to_chat(U, "Now tracking [target.get_visible_name()] on camera.") - - INVOKE_ASYNC(src, .proc/do_track, target, U) - -/mob/living/silicon/ai/proc/do_track(mob/living/target, mob/living/silicon/ai/U) - var/cameraticks = 0 - - while(U.cameraFollow == target) - if(U.cameraFollow == null) - return - - if(!target.can_track(usr)) - U.tracking = 1 - if(!cameraticks) - to_chat(U, "Target is not near any active cameras. Attempting to reacquire...") - cameraticks++ - if(cameraticks > 9) - U.cameraFollow = null - to_chat(U, "Unable to reacquire, cancelling track...") - tracking = 0 - return - else - sleep(10) - continue - - else - cameraticks = 0 - U.tracking = 0 - - if(U.eyeobj) - U.eyeobj.setLoc(get_turf(target)) - - else - view_core() - U.cameraFollow = null - return - - sleep(10) - -/proc/near_camera(mob/living/M) - if (!isturf(M.loc)) - return FALSE - if(issilicon(M)) - var/mob/living/silicon/S = M - if((QDELETED(S.builtInCamera) || !S.builtInCamera.can_use()) && !GLOB.cameranet.checkCameraVis(M)) - return FALSE - else if(!GLOB.cameranet.checkCameraVis(M)) - return FALSE - return TRUE - -/obj/machinery/camera/attack_ai(mob/living/silicon/ai/user) - if (!istype(user)) - return - if (!can_use()) - return - user.switchCamera(src) - -/proc/camera_sort(list/L) - var/obj/machinery/camera/a - var/obj/machinery/camera/b - - for (var/i = L.len, i > 0, i--) - for (var/j = 1 to i - 1) - a = L[j] - b = L[j + 1] - if (sorttext(a.c_tag, b.c_tag) < 0) - L.Swap(j, j + 1) - return L +/mob/living/silicon/ai/proc/get_camera_list() + var/list/L = list() + for (var/obj/machinery/camera/C in GLOB.cameranet.cameras) + L.Add(C) + + camera_sort(L) + + var/list/T = list() + + for (var/obj/machinery/camera/C in L) + var/list/tempnetwork = C.network&src.network + if (tempnetwork.len) + T[text("[][]", C.c_tag, (C.can_use() ? null : " (Deactivated)"))] = C + + return T + +/mob/living/silicon/ai/proc/show_camera_list() + var/list/cameras = get_camera_list() + var/camera = input(src, "Choose which camera you want to view", "Cameras") as null|anything in cameras + switchCamera(cameras[camera]) + +/datum/trackable + var/initialized = FALSE + var/list/names = list() + var/list/namecounts = list() + var/list/humans = list() + var/list/others = list() + +/mob/living/silicon/ai/proc/trackable_mobs() + track.initialized = TRUE + track.names.Cut() + track.namecounts.Cut() + track.humans.Cut() + track.others.Cut() + + if(usr.stat == DEAD) + return list() + + for(var/i in GLOB.mob_living_list) + var/mob/living/L = i + if(!L.can_track(usr)) + continue + + var/name = L.name + while(name in track.names) + track.namecounts[name]++ + name = text("[] ([])", name, track.namecounts[name]) + track.names.Add(name) + track.namecounts[name] = 1 + + if(ishuman(L)) + track.humans[name] = L + else + track.others[name] = L + + var/list/targets = sortList(track.humans) + sortList(track.others) + + return targets + +/mob/living/silicon/ai/verb/ai_camera_track(target_name in trackable_mobs()) + set name = "track" + set hidden = 1 //Don't display it on the verb lists. This verb exists purely so you can type "track Oldman Robustin" and follow his ass + + if(!target_name) + return + + if(!track.initialized) + trackable_mobs() + + var/mob/target = (isnull(track.humans[target_name]) ? track.others[target_name] : track.humans[target_name]) + + ai_actual_track(target) + +/mob/living/silicon/ai/proc/ai_actual_track(mob/living/target) + if(!istype(target)) + return + var/mob/living/silicon/ai/U = usr + + U.cameraFollow = target + U.tracking = 1 + + if(!target || !target.can_track(usr)) + to_chat(U, "Target is not near any active cameras.") + U.cameraFollow = null + return + + to_chat(U, "Now tracking [target.get_visible_name()] on camera.") + + INVOKE_ASYNC(src, .proc/do_track, target, U) + +/mob/living/silicon/ai/proc/do_track(mob/living/target, mob/living/silicon/ai/U) + var/cameraticks = 0 + + while(U.cameraFollow == target) + if(U.cameraFollow == null) + return + + if(!target.can_track(usr)) + U.tracking = 1 + if(!cameraticks) + to_chat(U, "Target is not near any active cameras. Attempting to reacquire...") + cameraticks++ + if(cameraticks > 9) + U.cameraFollow = null + to_chat(U, "Unable to reacquire, cancelling track...") + tracking = 0 + return + else + sleep(10) + continue + + else + cameraticks = 0 + U.tracking = 0 + + if(U.eyeobj) + U.eyeobj.setLoc(get_turf(target)) + + else + view_core() + U.cameraFollow = null + return + + sleep(10) + +/proc/near_camera(mob/living/M) + if (!isturf(M.loc)) + return FALSE + if(issilicon(M)) + var/mob/living/silicon/S = M + if((QDELETED(S.builtInCamera) || !S.builtInCamera.can_use()) && !GLOB.cameranet.checkCameraVis(M)) + return FALSE + else if(!GLOB.cameranet.checkCameraVis(M)) + return FALSE + return TRUE + +/obj/machinery/camera/attack_ai(mob/living/silicon/ai/user) + if (!istype(user)) + return + if (!can_use()) + return + user.switchCamera(src) + +/proc/camera_sort(list/L) + var/obj/machinery/camera/a + var/obj/machinery/camera/b + + for (var/i = L.len, i > 0, i--) + for (var/j = 1 to i - 1) + a = L[j] + b = L[j + 1] + if (sorttext(a.c_tag, b.c_tag) < 0) + L.Swap(j, j + 1) + return L diff --git a/code/game/machinery/cell_charger.dm b/code/game/machinery/cell_charger.dm index 2a4fd3452fd4..f23c7b7bf1fa 100644 --- a/code/game/machinery/cell_charger.dm +++ b/code/game/machinery/cell_charger.dm @@ -1,131 +1,131 @@ -/obj/machinery/cell_charger - name = "cell charger" - desc = "It charges power cells." - icon = 'icons/obj/power.dmi' - icon_state = "ccharger" - use_power = IDLE_POWER_USE - idle_power_usage = 5 - active_power_usage = 60 - power_channel = AREA_USAGE_EQUIP - circuit = /obj/item/circuitboard/machine/cell_charger - pass_flags = PASSTABLE - var/obj/item/stock_parts/cell/charging = null - var/charge_rate = 500 - -/obj/machinery/cell_charger/update_overlays() - . = ..() - - if(!charging) - return - - . += image(charging.icon, charging.icon_state) - . += "ccharger-on" - if(!(machine_stat & (BROKEN|NOPOWER))) - var/newlevel = round(charging.percent() * 4 / 100) - . += "ccharger-o[newlevel]" - -/obj/machinery/cell_charger/examine(mob/user) - . = ..() - . += "There's [charging ? "a" : "no"] cell in the charger." - if(charging) - . += "Current charge: [round(charging.percent(), 1)]%." - if(in_range(user, src) || isobserver(user)) - . += "The status display reads: Charge rate at [charge_rate]J per cycle." - -/obj/machinery/cell_charger/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/stock_parts/cell) && !panel_open) - if(machine_stat & BROKEN) - to_chat(user, "[src] is broken!") - return - if(!anchored) - to_chat(user, "[src] isn't attached to the ground!") - return - if(charging) - to_chat(user, "There is already a cell in the charger!") - return - else - var/area/a = loc.loc // Gets our locations location, like a dream within a dream - if(!isarea(a)) - return - if(a.power_equip == 0) // There's no APC in this area, don't try to cheat power! - to_chat(user, "[src] blinks red as you try to insert the cell!") - return - if(!user.transferItemToLoc(W,src)) - return - - charging = W - user.visible_message("[user] inserts a cell into [src].", "You insert a cell into [src].") - update_icon() - else - if(!charging && default_deconstruction_screwdriver(user, icon_state, icon_state, W)) - return - if(default_deconstruction_crowbar(W)) - return - if(!charging && default_unfasten_wrench(user, W)) - return - return ..() - -/obj/machinery/cell_charger/deconstruct() - if(charging) - charging.forceMove(drop_location()) - return ..() - -/obj/machinery/cell_charger/Destroy() - QDEL_NULL(charging) - return ..() - -/obj/machinery/cell_charger/proc/removecell() - charging.update_icon() - charging = null - update_icon() - -/obj/machinery/cell_charger/attack_hand(mob/user) - . = ..() - if(.) - return - if(!charging) - return - - user.put_in_hands(charging) - charging.add_fingerprint(user) - - user.visible_message("[user] removes [charging] from [src].", "You remove [charging] from [src].") - - removecell() - -/obj/machinery/cell_charger/attack_tk(mob/user) - if(!charging) - return - - charging.forceMove(loc) - to_chat(user, "You telekinetically remove [charging] from [src].") - - removecell() - -/obj/machinery/cell_charger/attack_ai(mob/user) - return - -/obj/machinery/cell_charger/emp_act(severity) - . = ..() - - if(machine_stat & (BROKEN|NOPOWER) || . & EMP_PROTECT_CONTENTS) - return - - if(charging) - charging.emp_act(severity) - -/obj/machinery/cell_charger/RefreshParts() - charge_rate = 500 - for(var/obj/item/stock_parts/capacitor/C in component_parts) - charge_rate *= C.rating - -/obj/machinery/cell_charger/process() - if(!charging || !anchored || (machine_stat & (BROKEN|NOPOWER))) - return - - if(charging.percent() >= 100) - return - use_power(charge_rate) - charging.give(charge_rate) //this is 2558, efficient batteries exist - - update_icon() +/obj/machinery/cell_charger + name = "cell charger" + desc = "It charges power cells." + icon = 'icons/obj/power.dmi' + icon_state = "ccharger" + use_power = IDLE_POWER_USE + idle_power_usage = 5 + active_power_usage = 60 + power_channel = AREA_USAGE_EQUIP + circuit = /obj/item/circuitboard/machine/cell_charger + pass_flags = PASSTABLE + var/obj/item/stock_parts/cell/charging = null + var/charge_rate = 500 + +/obj/machinery/cell_charger/update_overlays() + . = ..() + + if(!charging) + return + + . += image(charging.icon, charging.icon_state) + . += "ccharger-on" + if(!(machine_stat & (BROKEN|NOPOWER))) + var/newlevel = round(charging.percent() * 4 / 100) + . += "ccharger-o[newlevel]" + +/obj/machinery/cell_charger/examine(mob/user) + . = ..() + . += "There's [charging ? "a" : "no"] cell in the charger." + if(charging) + . += "Current charge: [round(charging.percent(), 1)]%." + if(in_range(user, src) || isobserver(user)) + . += "The status display reads: Charge rate at [charge_rate]J per cycle." + +/obj/machinery/cell_charger/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/stock_parts/cell) && !panel_open) + if(machine_stat & BROKEN) + to_chat(user, "[src] is broken!") + return + if(!anchored) + to_chat(user, "[src] isn't attached to the ground!") + return + if(charging) + to_chat(user, "There is already a cell in the charger!") + return + else + var/area/a = loc.loc // Gets our locations location, like a dream within a dream + if(!isarea(a)) + return + if(a.power_equip == 0) // There's no APC in this area, don't try to cheat power! + to_chat(user, "[src] blinks red as you try to insert the cell!") + return + if(!user.transferItemToLoc(W,src)) + return + + charging = W + user.visible_message("[user] inserts a cell into [src].", "You insert a cell into [src].") + update_icon() + else + if(!charging && default_deconstruction_screwdriver(user, icon_state, icon_state, W)) + return + if(default_deconstruction_crowbar(W)) + return + if(!charging && default_unfasten_wrench(user, W)) + return + return ..() + +/obj/machinery/cell_charger/deconstruct() + if(charging) + charging.forceMove(drop_location()) + return ..() + +/obj/machinery/cell_charger/Destroy() + QDEL_NULL(charging) + return ..() + +/obj/machinery/cell_charger/proc/removecell() + charging.update_icon() + charging = null + update_icon() + +/obj/machinery/cell_charger/attack_hand(mob/user) + . = ..() + if(.) + return + if(!charging) + return + + user.put_in_hands(charging) + charging.add_fingerprint(user) + + user.visible_message("[user] removes [charging] from [src].", "You remove [charging] from [src].") + + removecell() + +/obj/machinery/cell_charger/attack_tk(mob/user) + if(!charging) + return + + charging.forceMove(loc) + to_chat(user, "You telekinetically remove [charging] from [src].") + + removecell() + +/obj/machinery/cell_charger/attack_ai(mob/user) + return + +/obj/machinery/cell_charger/emp_act(severity) + . = ..() + + if(machine_stat & (BROKEN|NOPOWER) || . & EMP_PROTECT_CONTENTS) + return + + if(charging) + charging.emp_act(severity) + +/obj/machinery/cell_charger/RefreshParts() + charge_rate = 500 + for(var/obj/item/stock_parts/capacitor/C in component_parts) + charge_rate *= C.rating + +/obj/machinery/cell_charger/process() + if(!charging || !anchored || (machine_stat & (BROKEN|NOPOWER))) + return + + if(charging.percent() >= 100) + return + use_power(charge_rate) + charging.give(charge_rate) //this is 2558, efficient batteries exist + + update_icon() diff --git a/code/game/machinery/computer/Operating.dm b/code/game/machinery/computer/Operating.dm index c3fcdd8d0e6e..8942f83e5351 100644 --- a/code/game/machinery/computer/Operating.dm +++ b/code/game/machinery/computer/Operating.dm @@ -1,155 +1,156 @@ -#define MENU_OPERATION 1 -#define MENU_SURGERIES 2 - -/obj/machinery/computer/operating - name = "operating computer" - desc = "Monitors patient vitals and displays surgery steps. Can be loaded with surgery disks to perform experimental procedures. Automatically syncs to stasis beds within its line of sight for surgical tech advancement." - icon_screen = "crew" - icon_keyboard = "med_key" - circuit = /obj/item/circuitboard/computer/operating - ui_x = 350 - ui_y = 470 - - var/mob/living/carbon/human/patient - var/obj/structure/table/optable/table - var/obj/machinery/stasis/sbed - var/list/advanced_surgeries = list() - var/datum/techweb/linked_techweb - light_color = LIGHT_COLOR_BLUE - -/obj/machinery/computer/operating/Initialize() - . = ..() - linked_techweb = SSresearch.science_tech - find_table() - -/obj/machinery/computer/operating/Destroy() - for(var/direction in GLOB.alldirs) - table = locate(/obj/structure/table/optable) in get_step(src, direction) - if(table && table.computer == src) - table.computer = null - else - sbed = locate(/obj/machinery/stasis) in get_step(src, direction) - if(sbed && sbed.op_computer == src) - sbed.op_computer = null - . = ..() - -/obj/machinery/computer/operating/attackby(obj/item/O, mob/user, params) - if(istype(O, /obj/item/disk/surgery)) - user.visible_message("[user] begins to load \the [O] in \the [src]...", \ - "You begin to load a surgery protocol from \the [O]...", \ - "You hear the chatter of a floppy drive.") - var/obj/item/disk/surgery/D = O - if(do_after(user, 10, target = src)) - advanced_surgeries |= D.surgeries - return TRUE - return ..() - -/obj/machinery/computer/operating/proc/sync_surgeries() - for(var/i in linked_techweb.researched_designs) - var/datum/design/surgery/D = SSresearch.techweb_design_by_id(i) - if(!istype(D)) - continue - advanced_surgeries |= D.surgery - -/obj/machinery/computer/operating/proc/find_table() - for(var/direction in GLOB.alldirs) - table = locate(/obj/structure/table/optable) in get_step(src, direction) - if(table) - table.computer = src - break - else - sbed = locate(/obj/machinery/stasis) in get_step(src, direction) - if(sbed) - sbed.op_computer = src - break - -/obj/machinery/computer/operating/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.not_incapacitated_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "OperatingComputer", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/machinery/computer/operating/ui_data(mob/user) - var/list/data = list() - var/list/surgeries = list() - for(var/X in advanced_surgeries) - var/datum/surgery/S = X - var/list/surgery = list() - surgery["name"] = initial(S.name) - surgery["desc"] = initial(S.desc) - surgeries += list(surgery) - data["surgeries"] = surgeries - data["patient"] = null - if(table) - data["table"] = table - if(!table.check_eligible_patient()) - return data - data["patient"] = list() - patient = table.patient - else - if(sbed) - data["table"] = sbed - if(!ishuman(sbed.occupant) && !ismonkey(sbed.occupant)) - return data - data["patient"] = list() - patient = sbed.occupant - else - data["patient"] = null - return data - switch(patient.stat) - if(CONSCIOUS) - data["patient"]["stat"] = "Conscious" - data["patient"]["statstate"] = "good" - if(SOFT_CRIT) - data["patient"]["stat"] = "Conscious" - data["patient"]["statstate"] = "average" - if(UNCONSCIOUS) - data["patient"]["stat"] = "Unconscious" - data["patient"]["statstate"] = "average" - if(DEAD) - data["patient"]["stat"] = "Dead" - data["patient"]["statstate"] = "bad" - data["patient"]["health"] = patient.health - data["patient"]["blood_type"] = patient.dna.blood_type - data["patient"]["maxHealth"] = patient.maxHealth - data["patient"]["minHealth"] = HEALTH_THRESHOLD_DEAD - data["patient"]["bruteLoss"] = patient.getBruteLoss() - data["patient"]["fireLoss"] = patient.getFireLoss() - data["patient"]["toxLoss"] = patient.getToxLoss() - data["patient"]["oxyLoss"] = patient.getOxyLoss() - data["procedures"] = list() - if(patient.surgeries.len) - for(var/datum/surgery/procedure in patient.surgeries) - var/datum/surgery_step/surgery_step = procedure.get_surgery_step() - var/chems_needed = surgery_step.get_chem_list() - var/alternative_step - var/alt_chems_needed = "" - if(surgery_step.repeatable) - var/datum/surgery_step/next_step = procedure.get_surgery_next_step() - if(next_step) - alternative_step = capitalize(next_step.name) - alt_chems_needed = next_step.get_chem_list() - else - alternative_step = "Finish operation" - data["procedures"] += list(list( - "name" = capitalize("[parse_zone(procedure.location)] [procedure.name]"), - "next_step" = capitalize(surgery_step.name), - "chems_needed" = chems_needed, - "alternative_step" = alternative_step, - "alt_chems_needed" = alt_chems_needed - )) - return data - - - -/obj/machinery/computer/operating/ui_act(action, params) - if(..()) - return - switch(action) - if("sync") - sync_surgeries() - . = TRUE - . = TRUE - -#undef MENU_OPERATION -#undef MENU_SURGERIES +#define MENU_OPERATION 1 +#define MENU_SURGERIES 2 + +/obj/machinery/computer/operating + name = "operating computer" + desc = "Monitors patient vitals and displays surgery steps. Can be loaded with surgery disks to perform experimental procedures. Automatically syncs to stasis beds within its line of sight for surgical tech advancement." + icon_screen = "crew" + icon_keyboard = "med_key" + circuit = /obj/item/circuitboard/computer/operating + + var/mob/living/carbon/human/patient + var/obj/structure/table/optable/table + var/obj/machinery/stasis/sbed + var/list/advanced_surgeries = list() + var/datum/techweb/linked_techweb + light_color = LIGHT_COLOR_BLUE + +/obj/machinery/computer/operating/Initialize() + . = ..() + linked_techweb = SSresearch.science_tech + find_table() + +/obj/machinery/computer/operating/Destroy() + for(var/direction in GLOB.alldirs) + table = locate(/obj/structure/table/optable) in get_step(src, direction) + if(table && table.computer == src) + table.computer = null + else + sbed = locate(/obj/machinery/stasis) in get_step(src, direction) + if(sbed && sbed.op_computer == src) + sbed.op_computer = null + . = ..() + +/obj/machinery/computer/operating/attackby(obj/item/O, mob/user, params) + if(istype(O, /obj/item/disk/surgery)) + user.visible_message("[user] begins to load \the [O] in \the [src]...", \ + "You begin to load a surgery protocol from \the [O]...", \ + "You hear the chatter of a floppy drive.") + var/obj/item/disk/surgery/D = O + if(do_after(user, 10, target = src)) + advanced_surgeries |= D.surgeries + return TRUE + return ..() + +/obj/machinery/computer/operating/proc/sync_surgeries() + for(var/i in linked_techweb.researched_designs) + var/datum/design/surgery/D = SSresearch.techweb_design_by_id(i) + if(!istype(D)) + continue + advanced_surgeries |= D.surgery + +/obj/machinery/computer/operating/proc/find_table() + for(var/direction in GLOB.alldirs) + table = locate(/obj/structure/table/optable) in get_step(src, direction) + if(table) + table.computer = src + break + else + sbed = locate(/obj/machinery/stasis) in get_step(src, direction) + if(sbed) + sbed.op_computer = src + break + +/obj/machinery/computer/operating/ui_state(mob/user) + return GLOB.not_incapacitated_state + +/obj/machinery/computer/operating/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "OperatingComputer", name) + ui.open() + +/obj/machinery/computer/operating/ui_data(mob/user) + var/list/data = list() + var/list/surgeries = list() + for(var/X in advanced_surgeries) + var/datum/surgery/S = X + var/list/surgery = list() + surgery["name"] = initial(S.name) + surgery["desc"] = initial(S.desc) + surgeries += list(surgery) + data["surgeries"] = surgeries + data["patient"] = null + if(table) + data["table"] = table + if(!table.check_eligible_patient()) + return data + data["patient"] = list() + patient = table.patient + else + if(sbed) + data["table"] = sbed + if(!ishuman(sbed.occupant) && !ismonkey(sbed.occupant)) + return data + data["patient"] = list() + patient = sbed.occupant + else + data["patient"] = null + return data + switch(patient.stat) + if(CONSCIOUS) + data["patient"]["stat"] = "Conscious" + data["patient"]["statstate"] = "good" + if(SOFT_CRIT) + data["patient"]["stat"] = "Conscious" + data["patient"]["statstate"] = "average" + if(UNCONSCIOUS) + data["patient"]["stat"] = "Unconscious" + data["patient"]["statstate"] = "average" + if(DEAD) + data["patient"]["stat"] = "Dead" + data["patient"]["statstate"] = "bad" + data["patient"]["health"] = patient.health + data["patient"]["blood_type"] = patient.dna.blood_type + data["patient"]["maxHealth"] = patient.maxHealth + data["patient"]["minHealth"] = HEALTH_THRESHOLD_DEAD + data["patient"]["bruteLoss"] = patient.getBruteLoss() + data["patient"]["fireLoss"] = patient.getFireLoss() + data["patient"]["toxLoss"] = patient.getToxLoss() + data["patient"]["oxyLoss"] = patient.getOxyLoss() + data["procedures"] = list() + if(patient.surgeries.len) + for(var/datum/surgery/procedure in patient.surgeries) + var/datum/surgery_step/surgery_step = procedure.get_surgery_step() + var/chems_needed = surgery_step.get_chem_list() + var/alternative_step + var/alt_chems_needed = "" + if(surgery_step.repeatable) + var/datum/surgery_step/next_step = procedure.get_surgery_next_step() + if(next_step) + alternative_step = capitalize(next_step.name) + alt_chems_needed = next_step.get_chem_list() + else + alternative_step = "Finish operation" + data["procedures"] += list(list( + "name" = capitalize("[parse_zone(procedure.location)] [procedure.name]"), + "next_step" = capitalize(surgery_step.name), + "chems_needed" = chems_needed, + "alternative_step" = alternative_step, + "alt_chems_needed" = alt_chems_needed + )) + return data + + + +/obj/machinery/computer/operating/ui_act(action, params) + if(..()) + return + switch(action) + if("sync") + sync_surgeries() + . = TRUE + . = TRUE + +#undef MENU_OPERATION +#undef MENU_SURGERIES diff --git a/code/game/machinery/computer/aifixer.dm b/code/game/machinery/computer/aifixer.dm index a918a6bc88f8..5de7edc54106 100644 --- a/code/game/machinery/computer/aifixer.dm +++ b/code/game/machinery/computer/aifixer.dm @@ -6,8 +6,7 @@ icon_keyboard = "tech_key" icon_screen = "ai-fixer" light_color = LIGHT_COLOR_PINK - ui_x = 370 - ui_y = 360 + /// Variable containing transferred AI var/mob/living/silicon/ai/occupier /// Variable dictating if we are in the process of restoring the occupier AI @@ -22,11 +21,10 @@ else return ..() -/obj/machinery/computer/aifixer/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/machinery/computer/aifixer/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "AiRestorer", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, "AiRestorer", name) ui.open() /obj/machinery/computer/aifixer/ui_data(mob/user) @@ -43,7 +41,7 @@ data["restoring"] = restoring data["health"] = (occupier.health + 100) / 2 data["isDead"] = occupier.stat == DEAD - data["laws"] = occupier.laws.get_law_list(include_zeroth = 1) + data["laws"] = occupier.laws.get_law_list(include_zeroth = TRUE, render_html = FALSE) return data diff --git a/code/game/machinery/computer/apc_control.dm b/code/game/machinery/computer/apc_control.dm index b3b2c02a9ea9..15c947f8c73f 100644 --- a/code/game/machinery/computer/apc_control.dm +++ b/code/game/machinery/computer/apc_control.dm @@ -12,8 +12,6 @@ var/restoring = FALSE var/list/logs var/auth_id = "\[NULL\]:" - ui_x = 550 - ui_y = 500 /obj/machinery/computer/apc_control/Initialize(mapload, obj/item/circuitboard/C) . = ..() @@ -41,14 +39,13 @@ /obj/machinery/computer/apc_control/proc/check_apc(obj/machinery/power/apc/APC) return APC.z == z && !APC.malfhack && !APC.aidisabled && !(APC.obj_flags & EMAGGED) && !APC.machine_stat && !istype(APC.area, /area/ai_monitored) && !APC.area.outdoors -/obj/machinery/computer/apc_control/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) // Remember to use the appropriate state. +/obj/machinery/computer/apc_control/ui_interact(mob/user, datum/tgui/ui) operator = user - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "ApcControl", "APC Controller", ui_x, ui_y, master_ui, state) + ui = new(user, src, "ApcControl") ui.open() - /obj/machinery/computer/apc_control/ui_data(mob/user) var/list/data = list() data["auth_id"] = auth_id diff --git a/code/game/machinery/computer/arena.dm b/code/game/machinery/computer/arena.dm index ed0ce112ad1b..fd0236eb5197 100644 --- a/code/game/machinery/computer/arena.dm +++ b/code/game/machinery/computer/arena.dm @@ -323,7 +323,7 @@ var/obj/item/reagent_containers/food/drinks/trophy/gold_cup/G = new(get_turf(L)) G.name = "[L.real_name]'s Trophy" -/obj/machinery/computer/arena/ui_interact(mob/user, ui_key, datum/tgui/ui, force_open, datum/tgui/master_ui, datum/ui_state/state) +/obj/machinery/computer/arena/ui_interact(mob/user) . = ..() var/list/dat = list() dat += "
    Spawning is currently [ready_to_spawn ? "enabled" : "disabled"] Toggle
    " diff --git a/code/game/machinery/computer/atmos_alert.dm b/code/game/machinery/computer/atmos_alert.dm index cd88eaa10fa1..65cc6fc7f445 100644 --- a/code/game/machinery/computer/atmos_alert.dm +++ b/code/game/machinery/computer/atmos_alert.dm @@ -1,90 +1,87 @@ -/obj/machinery/computer/atmos_alert - name = "atmospheric alert console" - desc = "Used to monitor the station's air alarms." - circuit = /obj/item/circuitboard/computer/atmos_alert - ui_x = 350 - ui_y = 300 - icon_screen = "alert:0" - icon_keyboard = "atmos_key" - var/list/priority_alarms = list() - var/list/minor_alarms = list() - var/receive_frequency = FREQ_ATMOS_ALARMS - var/datum/radio_frequency/radio_connection - - light_color = LIGHT_COLOR_CYAN - -/obj/machinery/computer/atmos_alert/Initialize() - . = ..() - set_frequency(receive_frequency) - -/obj/machinery/computer/atmos_alert/Destroy() - SSradio.remove_object(src, receive_frequency) - return ..() - -/obj/machinery/computer/atmos_alert/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "AtmosAlertConsole", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/machinery/computer/atmos_alert/ui_data(mob/user) - var/list/data = list() - - data["priority"] = list() - for(var/zone in priority_alarms) - data["priority"] += zone - data["minor"] = list() - for(var/zone in minor_alarms) - data["minor"] += zone - - return data - -/obj/machinery/computer/atmos_alert/ui_act(action, params) - if(..()) - return - switch(action) - if("clear") - var/zone = params["zone"] - if(zone in priority_alarms) - to_chat(usr, "Priority alarm for [zone] cleared.") - priority_alarms -= zone - . = TRUE - if(zone in minor_alarms) - to_chat(usr, "Minor alarm for [zone] cleared.") - minor_alarms -= zone - . = TRUE - update_icon() - -/obj/machinery/computer/atmos_alert/proc/set_frequency(new_frequency) - SSradio.remove_object(src, receive_frequency) - receive_frequency = new_frequency - radio_connection = SSradio.add_object(src, receive_frequency, RADIO_ATMOSIA) - -/obj/machinery/computer/atmos_alert/receive_signal(datum/signal/signal) - if(!signal) - return - - var/zone = signal.data["zone"] - var/severity = signal.data["alert"] - - if(!zone || !severity) - return - - minor_alarms -= zone - priority_alarms -= zone - if(severity == "severe") - priority_alarms += zone - else if (severity == "minor") - minor_alarms += zone - update_icon() - return - -/obj/machinery/computer/atmos_alert/update_overlays() - . = ..() - if(machine_stat & (NOPOWER|BROKEN)) - return - if(priority_alarms.len) - . += "alert:2" - else if(minor_alarms.len) - . += "alert:1" +/obj/machinery/computer/atmos_alert + name = "atmospheric alert console" + desc = "Used to monitor the station's air alarms." + circuit = /obj/item/circuitboard/computer/atmos_alert + icon_screen = "alert:0" + icon_keyboard = "atmos_key" + var/list/priority_alarms = list() + var/list/minor_alarms = list() + var/receive_frequency = FREQ_ATMOS_ALARMS + var/datum/radio_frequency/radio_connection + + light_color = LIGHT_COLOR_CYAN + +/obj/machinery/computer/atmos_alert/Initialize() + . = ..() + set_frequency(receive_frequency) + +/obj/machinery/computer/atmos_alert/Destroy() + SSradio.remove_object(src, receive_frequency) + return ..() + +/obj/machinery/computer/atmos_alert/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "AtmosAlertConsole", name) + ui.open() + +/obj/machinery/computer/atmos_alert/ui_data(mob/user) + var/list/data = list() + + data["priority"] = list() + for(var/zone in priority_alarms) + data["priority"] += zone + data["minor"] = list() + for(var/zone in minor_alarms) + data["minor"] += zone + + return data + +/obj/machinery/computer/atmos_alert/ui_act(action, params) + if(..()) + return + switch(action) + if("clear") + var/zone = params["zone"] + if(zone in priority_alarms) + to_chat(usr, "Priority alarm for [zone] cleared.") + priority_alarms -= zone + . = TRUE + if(zone in minor_alarms) + to_chat(usr, "Minor alarm for [zone] cleared.") + minor_alarms -= zone + . = TRUE + update_icon() + +/obj/machinery/computer/atmos_alert/proc/set_frequency(new_frequency) + SSradio.remove_object(src, receive_frequency) + receive_frequency = new_frequency + radio_connection = SSradio.add_object(src, receive_frequency, RADIO_ATMOSIA) + +/obj/machinery/computer/atmos_alert/receive_signal(datum/signal/signal) + if(!signal) + return + + var/zone = signal.data["zone"] + var/severity = signal.data["alert"] + + if(!zone || !severity) + return + + minor_alarms -= zone + priority_alarms -= zone + if(severity == "severe") + priority_alarms += zone + else if (severity == "minor") + minor_alarms += zone + update_icon() + return + +/obj/machinery/computer/atmos_alert/update_overlays() + . = ..() + if(machine_stat & (NOPOWER|BROKEN)) + return + if(priority_alarms.len) + . += "alert:2" + else if(minor_alarms.len) + . += "alert:1" diff --git a/code/game/machinery/computer/atmos_control.dm b/code/game/machinery/computer/atmos_control.dm index 489e6f6cb1e9..ee7510749c9f 100644 --- a/code/game/machinery/computer/atmos_control.dm +++ b/code/game/machinery/computer/atmos_control.dm @@ -1,335 +1,316 @@ -///////////////////////////////////////////////////////////// -// AIR SENSOR (found in gas tanks) -///////////////////////////////////////////////////////////// - -/obj/machinery/air_sensor - name = "gas sensor" - icon = 'icons/obj/stationobjs.dmi' - icon_state = "gsensor1" - resistance_flags = FIRE_PROOF - - var/on = TRUE - - var/id_tag - var/frequency = FREQ_ATMOS_STORAGE - var/datum/radio_frequency/radio_connection - -/obj/machinery/air_sensor/atmos/toxin_tank - name = "plasma tank gas sensor" - id_tag = ATMOS_GAS_MONITOR_SENSOR_TOX -/obj/machinery/air_sensor/atmos/toxins_mixing_tank - name = "toxins mixing gas sensor" - id_tag = ATMOS_GAS_MONITOR_SENSOR_TOXINS_LAB -/obj/machinery/air_sensor/atmos/oxygen_tank - name = "oxygen tank gas sensor" - id_tag = ATMOS_GAS_MONITOR_SENSOR_O2 -/obj/machinery/air_sensor/atmos/nitrogen_tank - name = "nitrogen tank gas sensor" - id_tag = ATMOS_GAS_MONITOR_SENSOR_N2 -/obj/machinery/air_sensor/atmos/mix_tank - name = "mix tank gas sensor" - id_tag = ATMOS_GAS_MONITOR_SENSOR_MIX -/obj/machinery/air_sensor/atmos/nitrous_tank - name = "nitrous oxide tank gas sensor" - id_tag = ATMOS_GAS_MONITOR_SENSOR_N2O -/obj/machinery/air_sensor/atmos/air_tank - name = "air mix tank gas sensor" - id_tag = ATMOS_GAS_MONITOR_SENSOR_AIR -/obj/machinery/air_sensor/atmos/carbon_tank - name = "carbon dioxide tank gas sensor" - id_tag = ATMOS_GAS_MONITOR_SENSOR_CO2 -/obj/machinery/air_sensor/atmos/incinerator_tank - name = "incinerator chamber gas sensor" - id_tag = ATMOS_GAS_MONITOR_SENSOR_INCINERATOR - -/obj/machinery/air_sensor/update_icon_state() - icon_state = "gsensor[on]" - -/obj/machinery/air_sensor/process_atmos() - if(on) - var/datum/gas_mixture/air_sample = return_air() - - var/datum/signal/signal = new(list( - "sigtype" = "status", - "id_tag" = id_tag, - "timestamp" = world.time, - "pressure" = air_sample.return_pressure(), - "temperature" = air_sample.return_temperature(), - "gases" = list() - )) - var/total_moles = air_sample.total_moles() - if(total_moles) - for(var/gas_id in air_sample.get_gases()) - var/gas_name = GLOB.meta_gas_info[gas_id][META_GAS_NAME] - signal.data["gases"][gas_name] = air_sample.get_moles(gas_id) / total_moles * 100 - - radio_connection.post_signal(src, signal, filter = RADIO_ATMOSIA) - - -/obj/machinery/air_sensor/proc/set_frequency(new_frequency) - SSradio.remove_object(src, frequency) - frequency = new_frequency - radio_connection = SSradio.add_object(src, frequency, RADIO_ATMOSIA) - -/obj/machinery/air_sensor/Initialize() - . = ..() - SSair.atmos_machinery += src - set_frequency(frequency) - -/obj/machinery/air_sensor/Destroy() - SSair.atmos_machinery -= src - SSradio.remove_object(src, frequency) - return ..() - -///////////////////////////////////////////////////////////// -// GENERAL AIR CONTROL (a.k.a atmos computer) -///////////////////////////////////////////////////////////// -GLOBAL_LIST_EMPTY(atmos_air_controllers) - -/obj/machinery/computer/atmos_control - name = "atmospherics monitoring" - desc = "Used to monitor the station's atmospherics sensors." - icon_screen = "tank" - icon_keyboard = "atmos_key" - circuit = /obj/item/circuitboard/computer/atmos_control - ui_x = 400 - ui_y = 925 - - var/frequency = FREQ_ATMOS_STORAGE - var/list/sensors = list( - ATMOS_GAS_MONITOR_SENSOR_N2 = "Nitrogen Tank", - ATMOS_GAS_MONITOR_SENSOR_O2 = "Oxygen Tank", - ATMOS_GAS_MONITOR_SENSOR_CO2 = "Carbon Dioxide Tank", - ATMOS_GAS_MONITOR_SENSOR_TOX = "Plasma Tank", - 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_LOOP_DISTRIBUTION = "Distribution Loop", - ATMOS_GAS_MONITOR_LOOP_ATMOS_WASTE = "Atmos Waste Loop", - ATMOS_GAS_MONITOR_SENSOR_INCINERATOR = "Incinerator Chamber", - ATMOS_GAS_MONITOR_SENSOR_TOXINS_LAB = "Toxins Mixing Chamber" - ) - var/list/sensor_information = list() - var/datum/radio_frequency/radio_connection - - light_color = LIGHT_COLOR_CYAN - -/obj/machinery/computer/atmos_control/Initialize() - . = ..() - GLOB.atmos_air_controllers += src - set_frequency(frequency) - -/obj/machinery/computer/atmos_control/Destroy() - GLOB.atmos_air_controllers -= src - SSradio.remove_object(src, frequency) - return ..() - -/obj/machinery/computer/atmos_control/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "AtmosControlConsole", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/machinery/computer/atmos_control/ui_data(mob/user) - var/data = list() - - data["sensors"] = list() - for(var/id_tag in sensors) - var/long_name = sensors[id_tag] - var/list/info = sensor_information[id_tag] - if(!info) - continue - data["sensors"] += list(list( - "id_tag" = id_tag, - "long_name" = sanitize(long_name), - "pressure" = info["pressure"], - "temperature" = info["temperature"], - "gases" = info["gases"] - )) - return data - -/obj/machinery/computer/atmos_control/receive_signal(datum/signal/signal) - if(!signal) - return - - var/id_tag = signal.data["id_tag"] - if(!id_tag || !sensors.Find(id_tag)) - return - - sensor_information[id_tag] = signal.data - -/obj/machinery/computer/atmos_control/proc/set_frequency(new_frequency) - SSradio.remove_object(src, frequency) - frequency = new_frequency - radio_connection = SSradio.add_object(src, frequency, RADIO_ATMOSIA) - -//Incinerator sensor only -/obj/machinery/computer/atmos_control/incinerator - name = "Incinerator Air Control" - sensors = list(ATMOS_GAS_MONITOR_SENSOR_INCINERATOR = "Incinerator Chamber") - circuit = /obj/item/circuitboard/computer/atmos_control/incinerator - 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") - circuit = /obj/item/circuitboard/computer/atmos_control/toxinsmix - ui_x = 400 - ui_y = 300 - -///////////////////////////////////////////////////////////// -// LARGE TANK CONTROL -///////////////////////////////////////////////////////////// - -/obj/machinery/computer/atmos_control/tank - var/input_tag - 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 - output_tag = ATMOS_GAS_MONITOR_OUTPUT_O2 - sensors = list(ATMOS_GAS_MONITOR_SENSOR_O2 = "Oxygen Tank") - circuit = /obj/item/circuitboard/computer/atmos_control/tank/oxygen_tank - -/obj/machinery/computer/atmos_control/tank/toxin_tank - name = "Plasma Supply Control" - input_tag = ATMOS_GAS_MONITOR_INPUT_TOX - output_tag = ATMOS_GAS_MONITOR_OUTPUT_TOX - sensors = list(ATMOS_GAS_MONITOR_SENSOR_TOX = "Plasma Tank") - circuit = /obj/item/circuitboard/computer/atmos_control/tank/toxin_tank - -/obj/machinery/computer/atmos_control/tank/air_tank - name = "Mixed Air Supply Control" - input_tag = ATMOS_GAS_MONITOR_INPUT_AIR - output_tag = ATMOS_GAS_MONITOR_OUTPUT_AIR - sensors = list(ATMOS_GAS_MONITOR_SENSOR_AIR = "Air Mix Tank") - circuit = /obj/item/circuitboard/computer/atmos_control/tank/air_tank - -/obj/machinery/computer/atmos_control/tank/mix_tank - name = "Gas Mix Tank Control" - input_tag = ATMOS_GAS_MONITOR_INPUT_MIX - output_tag = ATMOS_GAS_MONITOR_OUTPUT_MIX - sensors = list(ATMOS_GAS_MONITOR_SENSOR_MIX = "Gas Mix Tank") - circuit = /obj/item/circuitboard/computer/atmos_control/tank/mix_tank - -/obj/machinery/computer/atmos_control/tank/nitrous_tank - name = "Nitrous Oxide Supply Control" - input_tag = ATMOS_GAS_MONITOR_INPUT_N2O - output_tag = ATMOS_GAS_MONITOR_OUTPUT_N2O - sensors = list(ATMOS_GAS_MONITOR_SENSOR_N2O = "Nitrous Oxide Tank") - circuit = /obj/item/circuitboard/computer/atmos_control/tank/nitrous_tank - -/obj/machinery/computer/atmos_control/tank/nitrogen_tank - name = "Nitrogen Supply Control" - input_tag = ATMOS_GAS_MONITOR_INPUT_N2 - output_tag = ATMOS_GAS_MONITOR_OUTPUT_N2 - sensors = list(ATMOS_GAS_MONITOR_SENSOR_N2 = "Nitrogen Tank") - circuit = /obj/item/circuitboard/computer/atmos_control/tank/nitrogen_tank - -/obj/machinery/computer/atmos_control/tank/carbon_tank - name = "Carbon Dioxide Supply Control" - input_tag = ATMOS_GAS_MONITOR_INPUT_CO2 - output_tag = ATMOS_GAS_MONITOR_OUTPUT_CO2 - sensors = list(ATMOS_GAS_MONITOR_SENSOR_CO2 = "Carbon Dioxide Tank") - circuit = /obj/item/circuitboard/computer/atmos_control/tank/carbon_tank - -// 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(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, "_") - IO |= text[1] - for(var/obj/machinery/atmospherics/components/unary/outlet_injector/U in devices) - var/list/text = splittext(U.id, "_") - IO |= text[1] - if(!IO.len) - to_chat(user, "No machinery detected.") - var/S = input("Select the device set: ", "Selection", IO[1]) as anything in sortList(IO) - if(src) - src.input_tag = "[S]_in" - src.output_tag = "[S]_out" - name = "[uppertext(S)] Supply Control" - var/list/new_devices = freq.devices["atmosia"] - 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" = "[S] Tank") - break - - for(var/obj/machinery/atmospherics/components/unary/outlet_injector/U in devices) - U.broadcast_status() - for(var/obj/machinery/atmospherics/components/unary/vent_pump/U in devices) - U.broadcast_status() - -/obj/machinery/computer/atmos_control/tank/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "AtmosControlConsole", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/machinery/computer/atmos_control/tank/ui_data(mob/user) - var/list/data = ..() - data["tank"] = TRUE - data["inputting"] = input_info ? input_info["power"] : FALSE - data["inputRate"] = input_info ? input_info["volume_rate"] : 0 - data["outputting"] = output_info ? output_info["power"] : FALSE - data["outputPressure"] = output_info ? output_info["internal"] : 0 - - return data - -/obj/machinery/computer/atmos_control/tank/ui_act(action, params) - if(..() || !radio_connection) - return - var/datum/signal/signal = new(list("sigtype" = "command", "user" = usr)) - switch(action) - if("reconnect") - reconnect(usr) - . = TRUE - if("input") - signal.data += list("tag" = input_tag, "power_toggle" = TRUE) - . = TRUE - if("rate") - var/target = text2num(params["rate"]) - if(!isnull(target)) - target = clamp(target, 0, MAX_TRANSFER_RATE) - signal.data += list("tag" = input_tag, "set_volume_rate" = target) - . = TRUE - if("output") - signal.data += list("tag" = output_tag, "power_toggle" = TRUE) - . = TRUE - if("pressure") - var/target = text2num(params["pressure"]) - if(!isnull(target)) - target = clamp(target, 0, 4500) - signal.data += list("tag" = output_tag, "set_internal_pressure" = target) - . = TRUE - radio_connection.post_signal(src, signal, filter = RADIO_ATMOSIA) - -/obj/machinery/computer/atmos_control/tank/receive_signal(datum/signal/signal) - if(!signal) - return - - var/id_tag = signal.data["tag"] - - if(input_tag == id_tag) - input_info = signal.data - else if(output_tag == id_tag) - output_info = signal.data - else - ..(signal) +///////////////////////////////////////////////////////////// +// AIR SENSOR (found in gas tanks) +///////////////////////////////////////////////////////////// + +/obj/machinery/air_sensor + name = "gas sensor" + icon = 'icons/obj/stationobjs.dmi' + icon_state = "gsensor1" + resistance_flags = FIRE_PROOF + + var/on = TRUE + + var/id_tag + var/frequency = FREQ_ATMOS_STORAGE + var/datum/radio_frequency/radio_connection + +/obj/machinery/air_sensor/atmos/toxin_tank + name = "plasma tank gas sensor" + id_tag = ATMOS_GAS_MONITOR_SENSOR_TOX +/obj/machinery/air_sensor/atmos/toxins_mixing_tank + name = "toxins mixing gas sensor" + id_tag = ATMOS_GAS_MONITOR_SENSOR_TOXINS_LAB +/obj/machinery/air_sensor/atmos/oxygen_tank + name = "oxygen tank gas sensor" + id_tag = ATMOS_GAS_MONITOR_SENSOR_O2 +/obj/machinery/air_sensor/atmos/nitrogen_tank + name = "nitrogen tank gas sensor" + id_tag = ATMOS_GAS_MONITOR_SENSOR_N2 +/obj/machinery/air_sensor/atmos/mix_tank + name = "mix tank gas sensor" + id_tag = ATMOS_GAS_MONITOR_SENSOR_MIX +/obj/machinery/air_sensor/atmos/nitrous_tank + name = "nitrous oxide tank gas sensor" + id_tag = ATMOS_GAS_MONITOR_SENSOR_N2O +/obj/machinery/air_sensor/atmos/air_tank + name = "air mix tank gas sensor" + id_tag = ATMOS_GAS_MONITOR_SENSOR_AIR +/obj/machinery/air_sensor/atmos/carbon_tank + name = "carbon dioxide tank gas sensor" + id_tag = ATMOS_GAS_MONITOR_SENSOR_CO2 +/obj/machinery/air_sensor/atmos/incinerator_tank + name = "incinerator chamber gas sensor" + id_tag = ATMOS_GAS_MONITOR_SENSOR_INCINERATOR + +/obj/machinery/air_sensor/update_icon_state() + icon_state = "gsensor[on]" + +/obj/machinery/air_sensor/process_atmos() + if(on) + var/datum/gas_mixture/air_sample = return_air() + + var/datum/signal/signal = new(list( + "sigtype" = "status", + "id_tag" = id_tag, + "timestamp" = world.time, + "pressure" = air_sample.return_pressure(), + "temperature" = air_sample.return_temperature(), + "gases" = list() + )) + var/total_moles = air_sample.total_moles() + if(total_moles) + for(var/gas_id in air_sample.get_gases()) + var/gas_name = GLOB.meta_gas_info[gas_id][META_GAS_NAME] + signal.data["gases"][gas_name] = air_sample.get_moles(gas_id) / total_moles * 100 + + radio_connection.post_signal(src, signal, filter = RADIO_ATMOSIA) + + +/obj/machinery/air_sensor/proc/set_frequency(new_frequency) + SSradio.remove_object(src, frequency) + frequency = new_frequency + radio_connection = SSradio.add_object(src, frequency, RADIO_ATMOSIA) + +/obj/machinery/air_sensor/Initialize() + . = ..() + SSair.atmos_machinery += src + set_frequency(frequency) + +/obj/machinery/air_sensor/Destroy() + SSair.atmos_machinery -= src + SSradio.remove_object(src, frequency) + return ..() + +///////////////////////////////////////////////////////////// +// GENERAL AIR CONTROL (a.k.a atmos computer) +///////////////////////////////////////////////////////////// +GLOBAL_LIST_EMPTY(atmos_air_controllers) + +/obj/machinery/computer/atmos_control + name = "atmospherics monitoring" + desc = "Used to monitor the station's atmospherics sensors." + icon_screen = "tank" + icon_keyboard = "atmos_key" + circuit = /obj/item/circuitboard/computer/atmos_control + + var/frequency = FREQ_ATMOS_STORAGE + var/list/sensors = list( + ATMOS_GAS_MONITOR_SENSOR_N2 = "Nitrogen Tank", + ATMOS_GAS_MONITOR_SENSOR_O2 = "Oxygen Tank", + ATMOS_GAS_MONITOR_SENSOR_CO2 = "Carbon Dioxide Tank", + ATMOS_GAS_MONITOR_SENSOR_TOX = "Plasma Tank", + 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_LOOP_DISTRIBUTION = "Distribution Loop", + ATMOS_GAS_MONITOR_LOOP_ATMOS_WASTE = "Atmos Waste Loop", + ATMOS_GAS_MONITOR_SENSOR_INCINERATOR = "Incinerator Chamber", + ATMOS_GAS_MONITOR_SENSOR_TOXINS_LAB = "Toxins Mixing Chamber" + ) + var/list/sensor_information = list() + var/datum/radio_frequency/radio_connection + + light_color = LIGHT_COLOR_CYAN + +/obj/machinery/computer/atmos_control/Initialize() + . = ..() + GLOB.atmos_air_controllers += src + set_frequency(frequency) + +/obj/machinery/computer/atmos_control/Destroy() + GLOB.atmos_air_controllers -= src + SSradio.remove_object(src, frequency) + return ..() + +/obj/machinery/computer/atmos_control/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "AtmosControlConsole", name) + ui.open() + +/obj/machinery/computer/atmos_control/ui_data(mob/user) + var/data = list() + + data["sensors"] = list() + for(var/id_tag in sensors) + var/long_name = sensors[id_tag] + var/list/info = sensor_information[id_tag] + if(!info) + continue + data["sensors"] += list(list( + "id_tag" = id_tag, + "long_name" = sanitize(long_name), + "pressure" = info["pressure"], + "temperature" = info["temperature"], + "gases" = info["gases"] + )) + return data + +/obj/machinery/computer/atmos_control/receive_signal(datum/signal/signal) + if(!signal) + return + + var/id_tag = signal.data["id_tag"] + if(!id_tag || !sensors.Find(id_tag)) + return + + sensor_information[id_tag] = signal.data + +/obj/machinery/computer/atmos_control/proc/set_frequency(new_frequency) + SSradio.remove_object(src, frequency) + frequency = new_frequency + radio_connection = SSradio.add_object(src, frequency, RADIO_ATMOSIA) + +//Incinerator sensor only +/obj/machinery/computer/atmos_control/incinerator + name = "Incinerator Air Control" + sensors = list(ATMOS_GAS_MONITOR_SENSOR_INCINERATOR = "Incinerator Chamber") + circuit = /obj/item/circuitboard/computer/atmos_control/incinerator + +//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") + circuit = /obj/item/circuitboard/computer/atmos_control/toxinsmix + +///////////////////////////////////////////////////////////// +// LARGE TANK CONTROL +///////////////////////////////////////////////////////////// + +/obj/machinery/computer/atmos_control/tank + var/input_tag + var/output_tag + frequency = FREQ_ATMOS_STORAGE + circuit = /obj/item/circuitboard/computer/atmos_control/tank + var/list/input_info + var/list/output_info + +/obj/machinery/computer/atmos_control/tank/oxygen_tank + name = "Oxygen Supply Control" + input_tag = ATMOS_GAS_MONITOR_INPUT_O2 + output_tag = ATMOS_GAS_MONITOR_OUTPUT_O2 + sensors = list(ATMOS_GAS_MONITOR_SENSOR_O2 = "Oxygen Tank") + circuit = /obj/item/circuitboard/computer/atmos_control/tank/oxygen_tank + +/obj/machinery/computer/atmos_control/tank/toxin_tank + name = "Plasma Supply Control" + input_tag = ATMOS_GAS_MONITOR_INPUT_TOX + output_tag = ATMOS_GAS_MONITOR_OUTPUT_TOX + sensors = list(ATMOS_GAS_MONITOR_SENSOR_TOX = "Plasma Tank") + circuit = /obj/item/circuitboard/computer/atmos_control/tank/toxin_tank + +/obj/machinery/computer/atmos_control/tank/air_tank + name = "Mixed Air Supply Control" + input_tag = ATMOS_GAS_MONITOR_INPUT_AIR + output_tag = ATMOS_GAS_MONITOR_OUTPUT_AIR + sensors = list(ATMOS_GAS_MONITOR_SENSOR_AIR = "Air Mix Tank") + circuit = /obj/item/circuitboard/computer/atmos_control/tank/air_tank + +/obj/machinery/computer/atmos_control/tank/mix_tank + name = "Gas Mix Tank Control" + input_tag = ATMOS_GAS_MONITOR_INPUT_MIX + output_tag = ATMOS_GAS_MONITOR_OUTPUT_MIX + sensors = list(ATMOS_GAS_MONITOR_SENSOR_MIX = "Gas Mix Tank") + circuit = /obj/item/circuitboard/computer/atmos_control/tank/mix_tank + +/obj/machinery/computer/atmos_control/tank/nitrous_tank + name = "Nitrous Oxide Supply Control" + input_tag = ATMOS_GAS_MONITOR_INPUT_N2O + output_tag = ATMOS_GAS_MONITOR_OUTPUT_N2O + sensors = list(ATMOS_GAS_MONITOR_SENSOR_N2O = "Nitrous Oxide Tank") + circuit = /obj/item/circuitboard/computer/atmos_control/tank/nitrous_tank + +/obj/machinery/computer/atmos_control/tank/nitrogen_tank + name = "Nitrogen Supply Control" + input_tag = ATMOS_GAS_MONITOR_INPUT_N2 + output_tag = ATMOS_GAS_MONITOR_OUTPUT_N2 + sensors = list(ATMOS_GAS_MONITOR_SENSOR_N2 = "Nitrogen Tank") + circuit = /obj/item/circuitboard/computer/atmos_control/tank/nitrogen_tank + +/obj/machinery/computer/atmos_control/tank/carbon_tank + name = "Carbon Dioxide Supply Control" + input_tag = ATMOS_GAS_MONITOR_INPUT_CO2 + output_tag = ATMOS_GAS_MONITOR_OUTPUT_CO2 + sensors = list(ATMOS_GAS_MONITOR_SENSOR_CO2 = "Carbon Dioxide Tank") + circuit = /obj/item/circuitboard/computer/atmos_control/tank/carbon_tank + +// 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(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, "_") + IO |= text[1] + for(var/obj/machinery/atmospherics/components/unary/outlet_injector/U in devices) + var/list/text = splittext(U.id, "_") + IO |= text[1] + if(!IO.len) + to_chat(user, "No machinery detected.") + var/S = input("Select the device set: ", "Selection", IO[1]) as anything in sortList(IO) + if(src) + src.input_tag = "[S]_in" + src.output_tag = "[S]_out" + name = "[uppertext(S)] Supply Control" + var/list/new_devices = freq.devices["atmosia"] + 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" = "[S] Tank") + break + + for(var/obj/machinery/atmospherics/components/unary/outlet_injector/U in devices) + U.broadcast_status() + for(var/obj/machinery/atmospherics/components/unary/vent_pump/U in devices) + U.broadcast_status() + +/obj/machinery/computer/atmos_control/tank/ui_data(mob/user) + var/list/data = ..() + data["tank"] = TRUE + data["inputting"] = input_info ? input_info["power"] : FALSE + data["inputRate"] = input_info ? input_info["volume_rate"] : 0 + data["outputting"] = output_info ? output_info["power"] : FALSE + data["outputPressure"] = output_info ? output_info["internal"] : 0 + return data + +/obj/machinery/computer/atmos_control/tank/ui_act(action, params) + if(..() || !radio_connection) + return + var/datum/signal/signal = new(list("sigtype" = "command", "user" = usr)) + switch(action) + if("reconnect") + reconnect(usr) + . = TRUE + if("input") + signal.data += list("tag" = input_tag, "power_toggle" = TRUE) + . = TRUE + if("rate") + var/target = text2num(params["rate"]) + if(!isnull(target)) + target = clamp(target, 0, MAX_TRANSFER_RATE) + signal.data += list("tag" = input_tag, "set_volume_rate" = target) + . = TRUE + if("output") + signal.data += list("tag" = output_tag, "power_toggle" = TRUE) + . = TRUE + if("pressure") + var/target = text2num(params["pressure"]) + if(!isnull(target)) + target = clamp(target, 0, 4500) + signal.data += list("tag" = output_tag, "set_internal_pressure" = target) + . = TRUE + radio_connection.post_signal(src, signal, filter = RADIO_ATMOSIA) + +/obj/machinery/computer/atmos_control/tank/receive_signal(datum/signal/signal) + if(!signal) + return + + var/id_tag = signal.data["tag"] + + if(input_tag == id_tag) + input_info = signal.data + else if(output_tag == id_tag) + output_info = signal.data + else + ..(signal) diff --git a/code/game/machinery/computer/buildandrepair.dm b/code/game/machinery/computer/buildandrepair.dm index 270767b1a3c2..24f26f80410b 100644 --- a/code/game/machinery/computer/buildandrepair.dm +++ b/code/game/machinery/computer/buildandrepair.dm @@ -1,143 +1,143 @@ -/obj/structure/frame/computer - name = "computer frame" - icon_state = "0" - state = 0 - -/obj/structure/frame/computer/attackby(obj/item/P, mob/user, params) - add_fingerprint(user) - switch(state) - if(0) - if(P.tool_behaviour == TOOL_WRENCH) - to_chat(user, "You start wrenching the frame into place...") - if(P.use_tool(src, user, 20, volume=50)) - to_chat(user, "You wrench the frame into place.") - setAnchored(TRUE) - state = 1 - return - if(P.tool_behaviour == TOOL_WELDER) - if(!P.tool_start_check(user, amount=0)) - return - - to_chat(user, "You start deconstructing the frame...") - if(P.use_tool(src, user, 20, volume=50)) - to_chat(user, "You deconstruct the frame.") - var/obj/item/stack/sheet/metal/M = new (drop_location(), 5) - M.add_fingerprint(user) - qdel(src) - return - if(1) - if(P.tool_behaviour == TOOL_WRENCH) - to_chat(user, "You start to unfasten the frame...") - if(P.use_tool(src, user, 20, volume=50)) - to_chat(user, "You unfasten the frame.") - setAnchored(FALSE) - state = 0 - return - if(istype(P, /obj/item/circuitboard/computer) && !circuit) - if(!user.transferItemToLoc(P, src)) - return - playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE) - to_chat(user, "You place [P] inside the frame.") - icon_state = "1" - circuit = P - circuit.add_fingerprint(user) - return - - else if(istype(P, /obj/item/circuitboard) && !circuit) - to_chat(user, "This frame does not accept circuit boards of this type!") - return - if(P.tool_behaviour == TOOL_SCREWDRIVER && circuit) - P.play_tool_sound(src) - to_chat(user, "You screw [circuit] into place.") - state = 2 - icon_state = "2" - return - if(P.tool_behaviour == TOOL_CROWBAR && circuit) - P.play_tool_sound(src) - to_chat(user, "You remove [circuit].") - state = 1 - icon_state = "0" - circuit.forceMove(drop_location()) - circuit.add_fingerprint(user) - circuit = null - return - if(2) - if(P.tool_behaviour == TOOL_SCREWDRIVER && circuit) - P.play_tool_sound(src) - to_chat(user, "You unfasten the circuit board.") - state = 1 - icon_state = "1" - return - if(istype(P, /obj/item/stack/cable_coil)) - if(!P.tool_start_check(user, amount=5)) - return - to_chat(user, "You start adding cables to the frame...") - if(P.use_tool(src, user, 20, volume=50, amount=5)) - if(state != 2) - return - to_chat(user, "You add cables to the frame.") - state = 3 - icon_state = "3" - return - if(3) - if(P.tool_behaviour == TOOL_WIRECUTTER) - P.play_tool_sound(src) - to_chat(user, "You remove the cables.") - state = 2 - icon_state = "2" - var/obj/item/stack/cable_coil/A = new (drop_location(), 5) - A.add_fingerprint(user) - return - - if(istype(P, /obj/item/stack/sheet/glass)) - if(!P.tool_start_check(user, amount=2)) - return - playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE) - to_chat(user, "You start to put in the glass panel...") - if(P.use_tool(src, user, 20, amount=2)) - if(state != 3) - return - to_chat(user, "You put in the glass panel.") - state = 4 - src.icon_state = "4" - return - if(4) - if(P.tool_behaviour == TOOL_CROWBAR) - P.play_tool_sound(src) - to_chat(user, "You remove the glass panel.") - state = 3 - icon_state = "3" - var/obj/item/stack/sheet/glass/G = new(drop_location(), 2) - G.add_fingerprint(user) - return - if(P.tool_behaviour == TOOL_SCREWDRIVER) - P.play_tool_sound(src) - to_chat(user, "You connect the monitor.") - var/obj/B = new circuit.build_path (loc, circuit) - B.setDir(dir) - transfer_fingerprints_to(B) - qdel(src) - return - if(user.a_intent == INTENT_HARM) - return ..() - - -/obj/structure/frame/computer/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - if(state == 4) - new /obj/item/shard(drop_location()) - new /obj/item/shard(drop_location()) - if(state >= 3) - new /obj/item/stack/cable_coil(drop_location(), 5) - ..() - -/obj/structure/frame/computer/AltClick(mob/user) - ..() - if(!isliving(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) - return - - if(anchored) - to_chat(usr, "You must unwrench [src] before rotating it!") - return - - setDir(turn(dir, -90)) +/obj/structure/frame/computer + name = "computer frame" + icon_state = "0" + state = 0 + +/obj/structure/frame/computer/attackby(obj/item/P, mob/user, params) + add_fingerprint(user) + switch(state) + if(0) + if(P.tool_behaviour == TOOL_WRENCH) + to_chat(user, "You start wrenching the frame into place...") + if(P.use_tool(src, user, 20, volume=50)) + to_chat(user, "You wrench the frame into place.") + setAnchored(TRUE) + state = 1 + return + if(P.tool_behaviour == TOOL_WELDER) + if(!P.tool_start_check(user, amount=0)) + return + + to_chat(user, "You start deconstructing the frame...") + if(P.use_tool(src, user, 20, volume=50)) + to_chat(user, "You deconstruct the frame.") + var/obj/item/stack/sheet/metal/M = new (drop_location(), 5) + M.add_fingerprint(user) + qdel(src) + return + if(1) + if(P.tool_behaviour == TOOL_WRENCH) + to_chat(user, "You start to unfasten the frame...") + if(P.use_tool(src, user, 20, volume=50)) + to_chat(user, "You unfasten the frame.") + setAnchored(FALSE) + state = 0 + return + if(istype(P, /obj/item/circuitboard/computer) && !circuit) + if(!user.transferItemToLoc(P, src)) + return + playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE) + to_chat(user, "You place [P] inside the frame.") + icon_state = "1" + circuit = P + circuit.add_fingerprint(user) + return + + else if(istype(P, /obj/item/circuitboard) && !circuit) + to_chat(user, "This frame does not accept circuit boards of this type!") + return + if(P.tool_behaviour == TOOL_SCREWDRIVER && circuit) + P.play_tool_sound(src) + to_chat(user, "You screw [circuit] into place.") + state = 2 + icon_state = "2" + return + if(P.tool_behaviour == TOOL_CROWBAR && circuit) + P.play_tool_sound(src) + to_chat(user, "You remove [circuit].") + state = 1 + icon_state = "0" + circuit.forceMove(drop_location()) + circuit.add_fingerprint(user) + circuit = null + return + if(2) + if(P.tool_behaviour == TOOL_SCREWDRIVER && circuit) + P.play_tool_sound(src) + to_chat(user, "You unfasten the circuit board.") + state = 1 + icon_state = "1" + return + if(istype(P, /obj/item/stack/cable_coil)) + if(!P.tool_start_check(user, amount=5)) + return + to_chat(user, "You start adding cables to the frame...") + if(P.use_tool(src, user, 20, volume=50, amount=5)) + if(state != 2) + return + to_chat(user, "You add cables to the frame.") + state = 3 + icon_state = "3" + return + if(3) + if(P.tool_behaviour == TOOL_WIRECUTTER) + P.play_tool_sound(src) + to_chat(user, "You remove the cables.") + state = 2 + icon_state = "2" + var/obj/item/stack/cable_coil/A = new (drop_location(), 5) + A.add_fingerprint(user) + return + + if(istype(P, /obj/item/stack/sheet/glass)) + if(!P.tool_start_check(user, amount=2)) + return + playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE) + to_chat(user, "You start to put in the glass panel...") + if(P.use_tool(src, user, 20, amount=2)) + if(state != 3) + return + to_chat(user, "You put in the glass panel.") + state = 4 + src.icon_state = "4" + return + if(4) + if(P.tool_behaviour == TOOL_CROWBAR) + P.play_tool_sound(src) + to_chat(user, "You remove the glass panel.") + state = 3 + icon_state = "3" + var/obj/item/stack/sheet/glass/G = new(drop_location(), 2) + G.add_fingerprint(user) + return + if(P.tool_behaviour == TOOL_SCREWDRIVER) + P.play_tool_sound(src) + to_chat(user, "You connect the monitor.") + var/obj/B = new circuit.build_path (loc, circuit) + B.setDir(dir) + transfer_fingerprints_to(B) + qdel(src) + return + if(user.a_intent == INTENT_HARM) + return ..() + + +/obj/structure/frame/computer/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + if(state == 4) + new /obj/item/shard(drop_location()) + new /obj/item/shard(drop_location()) + if(state >= 3) + new /obj/item/stack/cable_coil(drop_location(), 5) + ..() + +/obj/structure/frame/computer/AltClick(mob/user) + ..() + if(!isliving(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + return + + if(anchored) + to_chat(usr, "You must unwrench [src] before rotating it!") + return + + setDir(turn(dir, -90)) diff --git a/code/game/machinery/computer/camera.dm b/code/game/machinery/computer/camera.dm index 3b07ae3591bd..4e0e0fc12d6e 100644 --- a/code/game/machinery/computer/camera.dm +++ b/code/game/machinery/computer/camera.dm @@ -1,344 +1,340 @@ -/obj/machinery/computer/security - name = "security camera console" - desc = "Used to access the various cameras on the station." - icon_screen = "cameras" - icon_keyboard = "security_key" - circuit = /obj/item/circuitboard/computer/security - light_color = LIGHT_COLOR_RED - ui_x = 870 - ui_y = 708 - - var/list/network = list("ss13") - var/obj/machinery/camera/active_camera - var/list/concurrent_users = list() - - // Stuff needed to render the map - var/map_name - var/const/default_map_size = 15 - var/obj/screen/map_view/cam_screen - var/obj/screen/plane_master/lighting/cam_plane_master - var/obj/screen/background/cam_background - -/obj/machinery/computer/security/Initialize() - . = ..() - // Map name has to start and end with an A-Z character, - // and definitely NOT with a square bracket or even a number. - // I wasted 6 hours on this. :agony: - 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_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_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(cam_background) - return ..() - -/obj/machinery/computer/security/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) - for(var/i in network) - network -= i - network += "[idnum][i]" - -/obj/machinery/computer/security/ui_interact(\ - mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - // Update UI - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - // Show static if can't use the camera - if(!active_camera?.can_use()) - show_camera_static() - 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 - // Turn on the console - if(length(concurrent_users) == 1 && is_living) - playsound(src, 'sound/machines/terminal_on.ogg', 25, FALSE) - use_power(active_power_usage) - // Register map objects - user.client.register_map_obj(cam_screen) - user.client.register_map_obj(cam_plane_master) - user.client.register_map_obj(cam_background) - // Open UI - ui = new(user, src, ui_key, "CameraConsole", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/machinery/computer/security/ui_data() - var/list/data = list() - data["network"] = network - data["activeCamera"] = null - if(active_camera) - if(!active_camera?.can_use()) - data["activeCamera"] = list( - name = active_camera.c_tag + " (DEACTIVATED)", - status = active_camera.status, - ) - else - data["activeCamera"] = list( - name = active_camera.c_tag, - status = active_camera.status, - ) - return data - -/obj/machinery/computer/security/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] - if(!C?.can_use()) - data["cameras"] += list(list( - name = C.c_tag + " (DEACTIVATED)", - )) - else - data["cameras"] += list(list( - name = C.c_tag, - )) - return data - -/obj/machinery/computer/security/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/C = cameras[c_tag] - active_camera = C - playsound(src, get_sfx("terminal_type"), 25, FALSE) - - // Show static if can't use the camera - if(!active_camera?.can_use()) - show_camera_static() - 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) - - return TRUE - -/obj/machinery/computer/security/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) - use_power(0) - -/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) - -// Returns the list of cameras accessible from this computer -/obj/machinery/computer/security/proc/get_available_cameras() - var/list/L = list() - for (var/obj/machinery/camera/C in GLOB.cameranet.cameras) - if((is_away_level(z) || is_away_level(C.z)) && (C.z != z))//if on away mission, can only receive feed from same z_level cameras - continue - L.Add(C) - var/list/D = list() - for(var/obj/machinery/camera/C in L) - if(!C.network) - stack_trace("Camera in a cameranet has no camera network") - continue - if(!(islist(C.network))) - stack_trace("Camera in a cameranet has a non-list camera network") - continue - var/list/tempnetwork = C.network & network - if(tempnetwork.len) - D["[C.c_tag]"] = C - return D - -// SECURITY MONITORS - -/obj/machinery/computer/security/wooden_tv - name = "security camera monitor" - desc = "An old TV hooked into the station's camera network." - icon_state = "television" - icon_keyboard = null - icon_screen = "detective_tv" - pass_flags = PASSTABLE - -/obj/machinery/computer/security/mining - name = "outpost camera console" - desc = "Used to access the various cameras on the outpost." - icon_screen = "mining" - icon_keyboard = "mining_key" - network = list("mine", "auxbase") - circuit = /obj/item/circuitboard/computer/mining - -/obj/machinery/computer/security/research - name = "research camera console" - desc = "Used to access the various cameras in science." - network = list("rd") - circuit = /obj/item/circuitboard/computer/research - -/obj/machinery/computer/security/hos - name = "\improper Head of Security's camera console" - desc = "A custom security console with added access to the labor camp network." - network = list("ss13", "labor") - circuit = null - -/obj/machinery/computer/security/labor - name = "labor camp monitoring" - desc = "Used to access the various cameras on the labor camp." - network = list("labor") - circuit = null - -/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." - network = list("mine", "auxbase", "vault") - circuit = null - -// TELESCREENS - -/obj/machinery/computer/security/telescreen - name = "\improper Telescreen" - 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/update_icon_state() - icon_state = initial(icon_state) - if(machine_stat & BROKEN) - icon_state += "b" - -/obj/machinery/computer/security/telescreen/entertainment - name = "entertainment monitor" - desc = "Damn, they better have the /tg/ channel on these things." - icon = 'icons/obj/status_display.dmi' - icon_state = "entertainment_blank" - network = list("thunder") - density = FALSE - circuit = null - interaction_flags_atom = NONE // interact() is called by BigClick() - var/icon_state_off = "entertainment_blank" - var/icon_state_on = "entertainment" - -/obj/machinery/computer/security/telescreen/entertainment/Initialize() - . = ..() - RegisterSignal(src, COMSIG_CLICK, .proc/BigClick) - -// Bypass clickchain to allow humans to use the telescreen from a distance -/obj/machinery/computer/security/telescreen/entertainment/proc/BigClick() - interact(usr) - -/obj/machinery/computer/security/telescreen/entertainment/proc/notify(on) - if(on && icon_state == icon_state_off) - say(pick( - "Feats of bravery live now at the thunderdome!", - "Two enter, one leaves! Tune in now!", - "Violence like you've never seen it before!", - "Spears! Camera! Action! LIVE NOW!")) - icon_state = icon_state_on - else - icon_state = icon_state_off - -/obj/machinery/computer/security/telescreen/rd - name = "\improper Research Director's telescreen" - desc = "Used for watching the AI and the RD's goons from the safety of his office." - network = list("rd", "aicore", "aiupload", "minisat", "xeno", "test") - -/obj/machinery/computer/security/telescreen/research - name = "research telescreen" - desc = "A telescreen with access to the research division's camera network." - network = list("rd") - -/obj/machinery/computer/security/telescreen/ce - name = "\improper Chief Engineer's telescreen" - desc = "Used for watching the engine, telecommunications and the minisat." - network = list("engine", "singularity", "tcomms", "minisat") - -/obj/machinery/computer/security/telescreen/cmo - name = "\improper Chief Medical Officer's telescreen" - desc = "A telescreen with access to the medbay's camera network." - network = list("medbay") - -/obj/machinery/computer/security/telescreen/vault - name = "vault monitor" - desc = "A telescreen that connects to the vault's camera network." - network = list("vault") - -/obj/machinery/computer/security/telescreen/toxins - name = "bomb test site monitor" - desc = "A telescreen that connects to the bomb test site's camera." - network = list("toxins") - -/obj/machinery/computer/security/telescreen/engine - name = "engine monitor" - desc = "A telescreen that connects to the engine's camera network." - network = list("engine") - -/obj/machinery/computer/security/telescreen/turbine - name = "turbine monitor" - desc = "A telescreen that connects to the turbine's camera." - network = list("turbine") - -/obj/machinery/computer/security/telescreen/interrogation - name = "interrogation room monitor" - desc = "A telescreen that connects to the interrogation room's camera." - network = list("interrogation") - -/obj/machinery/computer/security/telescreen/prison - name = "prison monitor" - desc = "A telescreen that connects to the permabrig's camera network." - network = list("prison") - -/obj/machinery/computer/security/telescreen/auxbase - name = "auxillary base monitor" - desc = "A telescreen that connects to the auxillary base's camera." - network = list("auxbase") - -/obj/machinery/computer/security/telescreen/minisat - name = "minisat monitor" - desc = "A telescreen that connects to the minisat's camera network." - network = list("minisat") - -/obj/machinery/computer/security/telescreen/aiupload - name = "\improper AI upload monitor" - desc = "A telescreen that connects to the AI upload's camera network." - network = list("aiupload") +/obj/machinery/computer/security + name = "security camera console" + desc = "Used to access the various cameras on the station." + icon_screen = "cameras" + icon_keyboard = "security_key" + circuit = /obj/item/circuitboard/computer/security + light_color = LIGHT_COLOR_RED + + var/list/network = list("ss13") + var/obj/machinery/camera/active_camera + var/list/concurrent_users = list() + + // Stuff needed to render the map + var/map_name + var/const/default_map_size = 15 + var/obj/screen/map_view/cam_screen + var/obj/screen/plane_master/lighting/cam_plane_master + var/obj/screen/background/cam_background + +/obj/machinery/computer/security/Initialize() + . = ..() + // Map name has to start and end with an A-Z character, + // and definitely NOT with a square bracket or even a number. + // I wasted 6 hours on this. :agony: + 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_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_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(cam_background) + return ..() + +/obj/machinery/computer/security/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) + for(var/i in network) + network -= i + network += "[idnum][i]" + +/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() + 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 + // Turn on the console + if(length(concurrent_users) == 1 && is_living) + playsound(src, 'sound/machines/terminal_on.ogg', 25, FALSE) + use_power(active_power_usage) + // Register map objects + user.client.register_map_obj(cam_screen) + user.client.register_map_obj(cam_plane_master) + user.client.register_map_obj(cam_background) + // Open UI + ui = new(user, src, "CameraConsole", name) + ui.open() + +/obj/machinery/computer/security/ui_data() + var/list/data = list() + data["network"] = network + data["activeCamera"] = null + if(active_camera) + if(!active_camera?.can_use()) + data["activeCamera"] = list( + name = active_camera.c_tag + " (DEACTIVATED)", + status = active_camera.status, + ) + else + data["activeCamera"] = list( + name = active_camera.c_tag, + status = active_camera.status, + ) + return data + +/obj/machinery/computer/security/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] + if(!C?.can_use()) + data["cameras"] += list(list( + name = C.c_tag + " (DEACTIVATED)", + )) + else + data["cameras"] += list(list( + name = C.c_tag, + )) + return data + +/obj/machinery/computer/security/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/C = cameras[c_tag] + active_camera = C + playsound(src, get_sfx("terminal_type"), 25, FALSE) + + // Show static if can't use the camera + if(!active_camera?.can_use()) + show_camera_static() + 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) + + return TRUE + +/obj/machinery/computer/security/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) + use_power(0) + +/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) + +// Returns the list of cameras accessible from this computer +/obj/machinery/computer/security/proc/get_available_cameras() + var/list/L = list() + for (var/obj/machinery/camera/C in GLOB.cameranet.cameras) + if((is_away_level(z) || is_away_level(C.z)) && (C.z != z))//if on away mission, can only receive feed from same z_level cameras + continue + L.Add(C) + var/list/D = list() + for(var/obj/machinery/camera/C in L) + if(!C.network) + stack_trace("Camera in a cameranet has no camera network") + continue + if(!(islist(C.network))) + stack_trace("Camera in a cameranet has a non-list camera network") + continue + var/list/tempnetwork = C.network & network + if(tempnetwork.len) + D["[C.c_tag]"] = C + return D + +// SECURITY MONITORS + +/obj/machinery/computer/security/wooden_tv + name = "security camera monitor" + desc = "An old TV hooked into the station's camera network." + icon_state = "television" + icon_keyboard = null + icon_screen = "detective_tv" + pass_flags = PASSTABLE + +/obj/machinery/computer/security/mining + name = "outpost camera console" + desc = "Used to access the various cameras on the outpost." + icon_screen = "mining" + icon_keyboard = "mining_key" + network = list("mine", "auxbase") + circuit = /obj/item/circuitboard/computer/mining + +/obj/machinery/computer/security/research + name = "research camera console" + desc = "Used to access the various cameras in science." + network = list("rd") + circuit = /obj/item/circuitboard/computer/research + +/obj/machinery/computer/security/hos + name = "\improper Head of Security's camera console" + desc = "A custom security console with added access to the labor camp network." + network = list("ss13", "labor") + circuit = null + +/obj/machinery/computer/security/labor + name = "labor camp monitoring" + desc = "Used to access the various cameras on the labor camp." + network = list("labor") + circuit = null + +/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." + network = list("mine", "auxbase", "vault") + circuit = null + +// TELESCREENS + +/obj/machinery/computer/security/telescreen + name = "\improper Telescreen" + 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/update_icon_state() + icon_state = initial(icon_state) + if(machine_stat & BROKEN) + icon_state += "b" + +/obj/machinery/computer/security/telescreen/entertainment + name = "entertainment monitor" + desc = "Damn, they better have the /tg/ channel on these things." + icon = 'icons/obj/status_display.dmi' + icon_state = "entertainment_blank" + network = list("thunder") + density = FALSE + circuit = null + interaction_flags_atom = NONE // interact() is called by BigClick() + var/icon_state_off = "entertainment_blank" + var/icon_state_on = "entertainment" + +/obj/machinery/computer/security/telescreen/entertainment/Initialize() + . = ..() + RegisterSignal(src, COMSIG_CLICK, .proc/BigClick) + +// Bypass clickchain to allow humans to use the telescreen from a distance +/obj/machinery/computer/security/telescreen/entertainment/proc/BigClick() + interact(usr) + +/obj/machinery/computer/security/telescreen/entertainment/proc/notify(on) + if(on && icon_state == icon_state_off) + say(pick( + "Feats of bravery live now at the thunderdome!", + "Two enter, one leaves! Tune in now!", + "Violence like you've never seen it before!", + "Spears! Camera! Action! LIVE NOW!")) + icon_state = icon_state_on + else + icon_state = icon_state_off + +/obj/machinery/computer/security/telescreen/rd + name = "\improper Research Director's telescreen" + desc = "Used for watching the AI and the RD's goons from the safety of his office." + network = list("rd", "aicore", "aiupload", "minisat", "xeno", "test") + +/obj/machinery/computer/security/telescreen/research + name = "research telescreen" + desc = "A telescreen with access to the research division's camera network." + network = list("rd") + +/obj/machinery/computer/security/telescreen/ce + name = "\improper Chief Engineer's telescreen" + desc = "Used for watching the engine, telecommunications and the minisat." + network = list("engine", "singularity", "tcomms", "minisat") + +/obj/machinery/computer/security/telescreen/cmo + name = "\improper Chief Medical Officer's telescreen" + desc = "A telescreen with access to the medbay's camera network." + network = list("medbay") + +/obj/machinery/computer/security/telescreen/vault + name = "vault monitor" + desc = "A telescreen that connects to the vault's camera network." + network = list("vault") + +/obj/machinery/computer/security/telescreen/toxins + name = "bomb test site monitor" + desc = "A telescreen that connects to the bomb test site's camera." + network = list("toxins") + +/obj/machinery/computer/security/telescreen/engine + name = "engine monitor" + desc = "A telescreen that connects to the engine's camera network." + network = list("engine") + +/obj/machinery/computer/security/telescreen/turbine + name = "turbine monitor" + desc = "A telescreen that connects to the turbine's camera." + network = list("turbine") + +/obj/machinery/computer/security/telescreen/interrogation + name = "interrogation room monitor" + desc = "A telescreen that connects to the interrogation room's camera." + network = list("interrogation") + +/obj/machinery/computer/security/telescreen/prison + name = "prison monitor" + desc = "A telescreen that connects to the permabrig's camera network." + network = list("prison") + +/obj/machinery/computer/security/telescreen/auxbase + name = "auxillary base monitor" + desc = "A telescreen that connects to the auxillary base's camera." + network = list("auxbase") + +/obj/machinery/computer/security/telescreen/minisat + name = "minisat monitor" + desc = "A telescreen that connects to the minisat's camera network." + network = list("minisat") + +/obj/machinery/computer/security/telescreen/aiupload + name = "\improper AI upload monitor" + desc = "A telescreen that connects to the AI upload's camera network." + network = list("aiupload") diff --git a/code/game/machinery/computer/card.dm b/code/game/machinery/computer/card.dm index d780c2f3542a..e4a554f327cf 100644 --- a/code/game/machinery/computer/card.dm +++ b/code/game/machinery/computer/card.dm @@ -1,636 +1,636 @@ - - -//Keeps track of the time for the ID console. Having it as a global variable prevents people from dismantling/reassembling it to -//increase the slots of many jobs. -GLOBAL_VAR_INIT(time_last_changed_position, 0) - -#define JOB_ALLOWED 1 -#define JOB_COOLDOWN -2 -#define JOB_MAX_POSITIONS -1 // Trying to reduce the number of slots below that of current holders of that job, or trying to open more slots than allowed -#define JOB_DENIED 0 - -/obj/machinery/computer/card - name = "identification console" - desc = "You can use this to manage jobs and ID access." - icon_screen = "id" - icon_keyboard = "id_key" - req_one_access = list(ACCESS_HEADS, ACCESS_CHANGE_IDS) - circuit = /obj/item/circuitboard/computer/card - var/mode = 0 - var/printing = null - var/target_dept = 0 //Which department this computer has access to. 0=all departments - - //Cooldown for closing positions in seconds - //if set to -1: No cooldown... probably a bad idea - //if set to 0: Not able to close "original" positions. You can only close positions that you have opened before - var/change_position_cooldown = 30 - //Jobs you cannot open new positions for - var/list/blacklisted = list( - "AI", - "Assistant", - "Cyborg", - "Captain", - "Head of Personnel", - "Head of Security", - "Chief Engineer", - "Research Director", - "Chief Medical Officer", - "Brig Physician", - "Lieutenant", - "Prisoner") - - //The scaling factor of max total positions in relation to the total amount of people on board the station in % - var/max_relative_positions = 30 //30%: Seems reasonable, limit of 6 @ 20 players - - //This is used to keep track of opened positions for jobs to allow instant closing - //Assoc array: "JobName" = (int) - var/list/opened_positions = list() - var/obj/item/card/id/inserted_scan_id - var/obj/item/card/id/inserted_modify_id - var/list/region_access = null - var/list/head_subordinates = null - - light_color = LIGHT_COLOR_BLUE - -/obj/machinery/computer/card/proc/get_jobs() - return get_all_jobs() - -/obj/machinery/computer/card/centcom/get_jobs() - return get_all_centcom_jobs() - -/obj/machinery/computer/card/Initialize() - . = ..() - change_position_cooldown = CONFIG_GET(number/id_console_jobslot_delay) - -/obj/machinery/computer/card/examine(mob/user) - . = ..() - if(inserted_scan_id || inserted_modify_id) - . += "Alt-click to eject the ID card." - -/obj/machinery/computer/card/attackby(obj/I, mob/user, params) - if(isidcard(I)) - if(check_access(I) && !inserted_scan_id) - if(id_insert(user, I, inserted_scan_id)) - inserted_scan_id = I - updateUsrDialog() - else if(id_insert(user, I, inserted_modify_id)) - inserted_modify_id = I - updateUsrDialog() - else - return ..() - -/obj/machinery/computer/card/Destroy() - if(inserted_scan_id) - qdel(inserted_scan_id) - inserted_scan_id = null - if(inserted_modify_id) - qdel(inserted_modify_id) - inserted_modify_id = null - return ..() - -/obj/machinery/computer/card/handle_atom_del(atom/A) - ..() - if(A == inserted_scan_id) - inserted_scan_id = null - updateUsrDialog() - if(A == inserted_modify_id) - inserted_modify_id = null - updateUsrDialog() - -/obj/machinery/computer/card/on_deconstruction() - if(inserted_scan_id) - inserted_scan_id.forceMove(drop_location()) - inserted_scan_id = null - if(inserted_modify_id) - inserted_modify_id.forceMove(drop_location()) - inserted_modify_id = null - -//Check if you can't open a new position for a certain job -/obj/machinery/computer/card/proc/job_blacklisted(jobtitle) - return (jobtitle in blacklisted) - -//Logic check for Topic() if you can open the job -/obj/machinery/computer/card/proc/can_open_job(datum/job/job) - if(job) - if(!job_blacklisted(job.title)) - if((job.total_positions <= GLOB.player_list.len * (max_relative_positions / 100))) - var/delta = (world.time / 10) - GLOB.time_last_changed_position - if((change_position_cooldown < delta) || (opened_positions[job.title] < 0)) - return JOB_ALLOWED - return JOB_COOLDOWN - return JOB_MAX_POSITIONS - return JOB_DENIED - -//Logic check for Topic() if you can close the job -/obj/machinery/computer/card/proc/can_close_job(datum/job/job) - if(job) - if(!job_blacklisted(job.title)) - if(job.total_positions > job.current_positions) - var/delta = (world.time / 10) - GLOB.time_last_changed_position - if((change_position_cooldown < delta) || (opened_positions[job.title] > 0)) - return JOB_ALLOWED - return JOB_COOLDOWN - return JOB_MAX_POSITIONS - return JOB_DENIED - - -/obj/machinery/computer/card/proc/id_insert(mob/user, obj/item/inserting_item, obj/item/target) - var/obj/item/card/id/card_to_insert = inserting_item - var/holder_item = FALSE - - if(!isidcard(card_to_insert)) - card_to_insert = inserting_item.RemoveID() - holder_item = TRUE - - if(!card_to_insert || !user.transferItemToLoc(card_to_insert, src)) - return FALSE - - if(target) - if(holder_item && inserting_item.InsertID(target)) - playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE) - else - id_eject(user, target) - - user.visible_message("[user] inserts \the [card_to_insert] into \the [src].", - "You insert \the [card_to_insert] into \the [src].") - playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE) - updateUsrDialog() - return TRUE - -/obj/machinery/computer/card/proc/id_eject(mob/user, obj/target) - if(!target) - to_chat(user, "That slot is empty!") - return FALSE - else - target.forceMove(drop_location()) - if(!issilicon(user) && Adjacent(user)) - user.put_in_hands(target) - user.visible_message("[user] gets \the [target] from \the [src].", \ - "You get \the [target] from \the [src].") - playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE) - updateUsrDialog() - return TRUE - -/obj/machinery/computer/card/AltClick(mob/user) - ..() - if(!user.canUseTopic(src, !issilicon(user)) || !is_operational()) - return - if(inserted_modify_id) - if(id_eject(user, inserted_modify_id)) - inserted_modify_id = null - updateUsrDialog() - return - if(inserted_scan_id) - if(id_eject(user, inserted_scan_id)) - inserted_scan_id = null - updateUsrDialog() - return - -/obj/machinery/computer/card/ui_interact(mob/user) - . = ..() - var/list/dat = list() - if (mode == 1) // accessing crew manifest - dat += "Crew Manifest:
    Please use security record computer to modify entries.

    " - for(var/datum/data/record/t in sortRecord(GLOB.data_core.general)) - dat += {"[t.fields["name"]] - [t.fields["rank"]]
    "} - dat += "Print

    Access ID modification console.
    " - - else if(mode == 2) - // JOB MANAGEMENT - dat += {"Return - - "} - for(var/datum/job/job in SSjob.occupations) - dat += "" - if(job.title in blacklisted) - continue - dat += {" - - " - dat += "
    JobSlotsOpen jobClose jobPrioritize
    [job.title][job.current_positions]/[job.total_positions]"} - switch(can_open_job(job)) - if(JOB_ALLOWED) - if(authenticated == 2) - dat += "Open Position
    " - else - dat += "Open Position" - if(JOB_COOLDOWN) - var/time_to_wait = round(change_position_cooldown - ((world.time / 10) - GLOB.time_last_changed_position), 1) - var/mins = round(time_to_wait / 60) - var/seconds = time_to_wait - (60*mins) - dat += "Cooldown ongoing: [mins]:[(seconds < 10) ? "0[seconds]" : "[seconds]"]" - else - dat += "Denied" - dat += "
    " - switch(can_close_job(job)) - if(JOB_ALLOWED) - if(authenticated == 2) - dat += "Close Position" - else - dat += "Close Position" - if(JOB_COOLDOWN) - var/time_to_wait = round(change_position_cooldown - ((world.time / 10) - GLOB.time_last_changed_position), 1) - var/mins = round(time_to_wait / 60) - var/seconds = time_to_wait - (60*mins) - dat += "Cooldown ongoing: [mins]:[(seconds < 10) ? "0[seconds]" : "[seconds]"]" - else - dat += "Denied" - dat += "" - switch(job.total_positions) - if(0) - dat += "Denied" - else - if(authenticated == 2) - if(job in SSjob.prioritized_jobs) - dat += "Deprioritize" - else - if(SSjob.prioritized_jobs.len < 5) - dat += "Prioritize" - else - dat += "Denied" - else - dat += "Prioritize" - - dat += "
    " - else - var/list/header = list() - - var/scan_name = inserted_scan_id ? html_encode(inserted_scan_id.name) : "--------" - var/target_name = inserted_modify_id ? html_encode(inserted_modify_id.name) : "--------" - var/target_owner = (inserted_modify_id && inserted_modify_id.registered_name) ? html_encode(inserted_modify_id.registered_name) : "--------" - var/target_rank = (inserted_modify_id && inserted_modify_id.assignment) ? html_encode(inserted_modify_id.assignment) : "Unassigned" - var/target_age = (inserted_modify_id && inserted_modify_id.registered_age) ? html_encode(inserted_modify_id.registered_age) : "--------" - - if(!authenticated) - header += {"
    Please insert the cards into the slots
    - Target: [target_name]
    - Confirm Identity: [scan_name]
    "} - else - header += {"

    - Target: Remove [target_name] || - Confirm Identity: Remove [scan_name]
    - Access Crew Manifest
    - [!target_dept ? "Job Management
    " : ""] - Log Out
    "} - - header += "
    " - - var/body - - if (authenticated && inserted_modify_id) - var/list/carddesc = list() - var/list/jobs = list() - if (authenticated == 2) - var/list/jobs_all = list() - for(var/job in (list("Unassigned") + get_jobs() + "Custom")) - jobs_all += "[replacetext(job, " ", " ")] " //make sure there isn't a line break in the middle of a job - carddesc += {""} - carddesc += {"
    - - - registered name: - registered age: - -
    - Assignment: "} - - jobs += "[target_rank]" //CHECK THIS - - else - carddesc += "registered_name: [target_owner]" - jobs += "Assignment: [target_rank] (Demote)" - - var/list/accesses = list() - if(istype(src, /obj/machinery/computer/card/centcom)) // REE - accesses += "
    Central Command:
    " - for(var/A in get_all_centcom_access()) - if(A in inserted_modify_id.access) - accesses += "[replacetext(get_centcom_access_desc(A), " ", " ")] " - else - accesses += "[replacetext(get_centcom_access_desc(A), " ", " ")] " - else - accesses += {"
    Access
    - - "} - for(var/i = 1; i <= 7; i++) - if(authenticated == 1 && !(i in region_access)) - continue - accesses += "" - accesses += "" - for(var/i = 1; i <= 7; i++) - if(authenticated == 1 && !(i in region_access)) - continue - accesses += "" - accesses += "
    [get_region_accesses_name(i)]:
    " - for(var/A in get_region_accesses(i)) - if(A in inserted_modify_id.access) - accesses += "[replacetext(get_access_desc(A), " ", " ")] " - else - accesses += "[replacetext(get_access_desc(A), " ", " ")] " - accesses += "
    " - accesses += "
    " - body = "[carddesc.Join()]
    [jobs.Join()]

    [accesses.Join()]
    " //CHECK THIS - - else if (!authenticated) - body = {"Log In

    - Access Crew Manifest

    "} - if(!target_dept) - body += "Job Management
    " - - dat = list("", header.Join(), body, "
    ") - var/datum/browser/popup = new(user, "id_com", src.name, 900, 620) - popup.set_content(dat.Join()) - popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) - popup.open() - -/obj/machinery/computer/card/Topic(href, href_list) - if(..()) - return - - if(!usr.canUseTopic(src, !issilicon(usr)) || !is_operational()) - usr.unset_machine() - usr << browse(null, "window=id_com") - return - - usr.set_machine(src) - switch(href_list["choice"]) - if ("inserted_modify_id") - if(inserted_modify_id && !usr.get_active_held_item()) - if(id_eject(usr, inserted_modify_id)) - inserted_modify_id = null - updateUsrDialog() - return - if(usr.get_id_in_hand()) - var/obj/item/held_item = usr.get_active_held_item() - var/obj/item/card/id/id_to_insert = held_item.GetID() - if(id_insert(usr, held_item, inserted_modify_id)) - inserted_modify_id = id_to_insert - updateUsrDialog() - if ("inserted_scan_id") - if(inserted_scan_id && !usr.get_active_held_item()) - if(id_eject(usr, inserted_scan_id)) - inserted_scan_id = null - updateUsrDialog() - return - if(usr.get_id_in_hand()) - var/obj/item/held_item = usr.get_active_held_item() - var/obj/item/card/id/id_to_insert = held_item.GetID() - if(id_insert(usr, held_item, inserted_scan_id)) - inserted_scan_id = id_to_insert - updateUsrDialog() - if ("auth") - if ((!( authenticated ) && (inserted_scan_id || issilicon(usr)) || mode)) - if (check_access(inserted_scan_id)) - region_access = list() - head_subordinates = list() - if(ACCESS_CHANGE_IDS in inserted_scan_id.access) - if(target_dept) - head_subordinates = get_all_jobs() - region_access |= target_dept - authenticated = 1 - else - authenticated = 2 - playsound(src, 'sound/machines/terminal_on.ogg', 50, FALSE) - - else - if((ACCESS_HOP in inserted_scan_id.access) && ((target_dept==1) || !target_dept)) - region_access |= 1 - region_access |= 6 - get_subordinates("Head of Personnel") - if((ACCESS_HOS in inserted_scan_id.access) && ((target_dept==2) || !target_dept)) - region_access |= 2 - get_subordinates("Head of Security") - if((ACCESS_CMO in inserted_scan_id.access) && ((target_dept==3) || !target_dept)) - region_access |= 3 - get_subordinates("Chief Medical Officer") - if((ACCESS_RD in inserted_scan_id.access) && ((target_dept==4) || !target_dept)) - region_access |= 4 - get_subordinates("Research Director") - if((ACCESS_CE in inserted_scan_id.access) && ((target_dept==5) || !target_dept)) - region_access |= 5 - get_subordinates("Chief Engineer") - if(region_access) - authenticated = 1 - else if ((!( authenticated ) && issilicon(usr)) && (!inserted_modify_id)) - to_chat(usr, "You can't modify an ID without an ID inserted to modify! Once one is in the modify slot on the computer, you can log in.") - if ("logout") - region_access = null - head_subordinates = null - authenticated = 0 - playsound(src, 'sound/machines/terminal_off.ogg', 50, FALSE) - - if("access") - if(href_list["allowed"]) - if(authenticated) - var/access_type = text2num(href_list["access_target"]) - var/access_allowed = text2num(href_list["allowed"]) - if(access_type in (istype(src, /obj/machinery/computer/card/centcom)?get_all_centcom_access() : get_all_accesses())) - inserted_modify_id.access -= access_type - if(access_allowed == 1) - inserted_modify_id.access += access_type - playsound(src, "terminal_type", 50, FALSE) - if ("assign") - if (authenticated == 2) - var/t1 = href_list["assign_target"] - if(t1 == "Custom") - var/newJob = reject_bad_text(input("Enter a custom job assignment.", "Assignment", inserted_modify_id ? inserted_modify_id.assignment : "Unassigned"), MAX_NAME_LEN) - if(newJob) - t1 = newJob - - else if(t1 == "Unassigned") - inserted_modify_id.access -= get_all_accesses() - - else - var/datum/job/jobdatum - for(var/jobtype in typesof(/datum/job)) - var/datum/job/J = new jobtype - if(ckey(J.title) == ckey(t1)) - jobdatum = J - updateUsrDialog() - break - if(!jobdatum) - to_chat(usr, "No log exists for this job.") - updateUsrDialog() - return - if(inserted_modify_id.registered_account) - inserted_modify_id.registered_account.account_job = jobdatum // this is a terrible idea and people will grief but sure whatever - - inserted_modify_id.access = ( istype(src, /obj/machinery/computer/card/centcom) ? get_centcom_access(t1) : jobdatum.get_access() ) - if (inserted_modify_id) - inserted_modify_id.assignment = t1 - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) - if ("demote") - if(inserted_modify_id.assignment in head_subordinates || inserted_modify_id.assignment == "Assistant") - inserted_modify_id.assignment = "Unassigned" - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) - else - to_chat(usr, "You are not authorized to demote this position.") - if ("reg") - if (authenticated) - var/t2 = inserted_modify_id - if ((authenticated && inserted_modify_id == t2 && (in_range(src, usr) || issilicon(usr)) && isturf(loc))) - var/newAge = text2num(href_list["setage"])|null - if(newAge && isnum(newAge)) - inserted_modify_id.registered_age = newAge - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) - else if(!isnull(newAge)) - to_chat(usr, "Invalid age entered- age not updated.") - updateUsrDialog() - - var/newName = reject_bad_name(href_list["reg"]) - if(newName) - inserted_modify_id.registered_name = newName - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) - else - to_chat(usr, "Invalid name entered.") - updateUsrDialog() - return - if ("mode") - mode = text2num(href_list["mode_target"]) - - if("return") - //DISPLAY MAIN MENU - mode = 3; - playsound(src, "terminal_type", 25, FALSE) - - if("make_job_available") - // MAKE ANOTHER JOB POSITION AVAILABLE FOR LATE JOINERS - if(authenticated && !target_dept) - var/edit_job_target = href_list["job"] - var/datum/job/j = SSjob.GetJob(edit_job_target) - if(!j) - updateUsrDialog() - return 0 - if(can_open_job(j) != 1) - updateUsrDialog() - return 0 - if(opened_positions[edit_job_target] >= 0) - GLOB.time_last_changed_position = world.time / 10 - j.total_positions++ - opened_positions[edit_job_target]++ - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) - - if("make_job_unavailable") - // MAKE JOB POSITION UNAVAILABLE FOR LATE JOINERS - if(authenticated && !target_dept) - var/edit_job_target = href_list["job"] - var/datum/job/j = SSjob.GetJob(edit_job_target) - if(!j) - updateUsrDialog() - return 0 - if(can_close_job(j) != 1) - updateUsrDialog() - return 0 - //Allow instant closing without cooldown if a position has been opened before - if(opened_positions[edit_job_target] <= 0) - GLOB.time_last_changed_position = world.time / 10 - j.total_positions-- - opened_positions[edit_job_target]-- - playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE) - - if ("prioritize_job") - // TOGGLE WHETHER JOB APPEARS AS PRIORITIZED IN THE LOBBY - if(authenticated && !target_dept) - var/priority_target = href_list["job"] - var/datum/job/j = SSjob.GetJob(priority_target) - if(!j) - updateUsrDialog() - return 0 - var/priority = TRUE - if(j in SSjob.prioritized_jobs) - SSjob.prioritized_jobs -= j - priority = FALSE - else if(j.total_positions <= j.current_positions) - to_chat(usr, "[j.title] has had all positions filled. Open up more slots before prioritizing it.") - updateUsrDialog() - return - else - SSjob.prioritized_jobs += j - to_chat(usr, "[j.title] has been successfully [priority ? "prioritized" : "unprioritized"]. Potential employees will notice your request.") - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) - - if ("print") - if (!( printing )) - printing = 1 - sleep(50) - var/obj/item/paper/P = new /obj/item/paper( loc ) - var/t1 = "Crew Manifest:
    " - for(var/datum/data/record/t in sortRecord(GLOB.data_core.general)) - t1 += t.fields["name"] + " - " + t.fields["rank"] + "
    " - P.info = t1 - P.name = "paper- 'Crew Manifest'" - printing = null - playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE) - if (inserted_modify_id) - inserted_modify_id.update_label() - updateUsrDialog() - -/obj/machinery/computer/card/proc/get_subordinates(rank) - for(var/datum/job/job in SSjob.occupations) - if(rank in job.department_head) - head_subordinates += job.title - -/obj/machinery/computer/card/centcom - name = "\improper CentCom identification console" - circuit = /obj/item/circuitboard/computer/card/centcom - req_access = list(ACCESS_CENT_CAPTAIN) - -/obj/machinery/computer/card/minor - name = "department management console" - desc = "You can use this to change ID's for specific departments." - icon_screen = "idminor" - circuit = /obj/item/circuitboard/computer/card/minor - -/obj/machinery/computer/card/minor/Initialize() - . = ..() - var/obj/item/circuitboard/computer/card/minor/typed_circuit = circuit - if(target_dept) - typed_circuit.target_dept = target_dept - else - target_dept = typed_circuit.target_dept - var/list/dept_list = list("general","security","medical","science","engineering") - name = "[dept_list[target_dept]] department console" - -/obj/machinery/computer/card/minor/hos - target_dept = 2 - icon_screen = "idhos" - - light_color = LIGHT_COLOR_RED - -/obj/machinery/computer/card/minor/cmo - target_dept = 3 - icon_screen = "idcmo" - -/obj/machinery/computer/card/minor/rd - target_dept = 4 - icon_screen = "idrd" - - light_color = LIGHT_COLOR_PINK - -/obj/machinery/computer/card/minor/ce - target_dept = 5 - icon_screen = "idce" - - light_color = LIGHT_COLOR_YELLOW - -#undef JOB_ALLOWED -#undef JOB_COOLDOWN -#undef JOB_MAX_POSITIONS -#undef JOB_DENIED + + +//Keeps track of the time for the ID console. Having it as a global variable prevents people from dismantling/reassembling it to +//increase the slots of many jobs. +GLOBAL_VAR_INIT(time_last_changed_position, 0) + +#define JOB_ALLOWED 1 +#define JOB_COOLDOWN -2 +#define JOB_MAX_POSITIONS -1 // Trying to reduce the number of slots below that of current holders of that job, or trying to open more slots than allowed +#define JOB_DENIED 0 + +/obj/machinery/computer/card + name = "identification console" + desc = "You can use this to manage jobs and ID access." + icon_screen = "id" + icon_keyboard = "id_key" + req_one_access = list(ACCESS_HEADS, ACCESS_CHANGE_IDS) + circuit = /obj/item/circuitboard/computer/card + var/mode = 0 + var/printing = null + var/target_dept = 0 //Which department this computer has access to. 0=all departments + + //Cooldown for closing positions in seconds + //if set to -1: No cooldown... probably a bad idea + //if set to 0: Not able to close "original" positions. You can only close positions that you have opened before + var/change_position_cooldown = 30 + //Jobs you cannot open new positions for + var/list/blacklisted = list( + "AI", + "Assistant", + "Cyborg", + "Captain", + "Head of Personnel", + "Head of Security", + "Chief Engineer", + "Research Director", + "Chief Medical Officer", + "Brig Physician", + "Lieutenant", + "Prisoner") + + //The scaling factor of max total positions in relation to the total amount of people on board the station in % + var/max_relative_positions = 30 //30%: Seems reasonable, limit of 6 @ 20 players + + //This is used to keep track of opened positions for jobs to allow instant closing + //Assoc array: "JobName" = (int) + var/list/opened_positions = list() + var/obj/item/card/id/inserted_scan_id + var/obj/item/card/id/inserted_modify_id + var/list/region_access = null + var/list/head_subordinates = null + + light_color = LIGHT_COLOR_BLUE + +/obj/machinery/computer/card/proc/get_jobs() + return get_all_jobs() + +/obj/machinery/computer/card/centcom/get_jobs() + return get_all_centcom_jobs() + +/obj/machinery/computer/card/Initialize() + . = ..() + change_position_cooldown = CONFIG_GET(number/id_console_jobslot_delay) + +/obj/machinery/computer/card/examine(mob/user) + . = ..() + if(inserted_scan_id || inserted_modify_id) + . += "Alt-click to eject the ID card." + +/obj/machinery/computer/card/attackby(obj/I, mob/user, params) + if(isidcard(I)) + if(check_access(I) && !inserted_scan_id) + if(id_insert(user, I, inserted_scan_id)) + inserted_scan_id = I + updateUsrDialog() + else if(id_insert(user, I, inserted_modify_id)) + inserted_modify_id = I + updateUsrDialog() + else + return ..() + +/obj/machinery/computer/card/Destroy() + if(inserted_scan_id) + qdel(inserted_scan_id) + inserted_scan_id = null + if(inserted_modify_id) + qdel(inserted_modify_id) + inserted_modify_id = null + return ..() + +/obj/machinery/computer/card/handle_atom_del(atom/A) + ..() + if(A == inserted_scan_id) + inserted_scan_id = null + updateUsrDialog() + if(A == inserted_modify_id) + inserted_modify_id = null + updateUsrDialog() + +/obj/machinery/computer/card/on_deconstruction() + if(inserted_scan_id) + inserted_scan_id.forceMove(drop_location()) + inserted_scan_id = null + if(inserted_modify_id) + inserted_modify_id.forceMove(drop_location()) + inserted_modify_id = null + +//Check if you can't open a new position for a certain job +/obj/machinery/computer/card/proc/job_blacklisted(jobtitle) + return (jobtitle in blacklisted) + +//Logic check for Topic() if you can open the job +/obj/machinery/computer/card/proc/can_open_job(datum/job/job) + if(job) + if(!job_blacklisted(job.title)) + if((job.total_positions <= GLOB.player_list.len * (max_relative_positions / 100))) + var/delta = (world.time / 10) - GLOB.time_last_changed_position + if((change_position_cooldown < delta) || (opened_positions[job.title] < 0)) + return JOB_ALLOWED + return JOB_COOLDOWN + return JOB_MAX_POSITIONS + return JOB_DENIED + +//Logic check for Topic() if you can close the job +/obj/machinery/computer/card/proc/can_close_job(datum/job/job) + if(job) + if(!job_blacklisted(job.title)) + if(job.total_positions > job.current_positions) + var/delta = (world.time / 10) - GLOB.time_last_changed_position + if((change_position_cooldown < delta) || (opened_positions[job.title] > 0)) + return JOB_ALLOWED + return JOB_COOLDOWN + return JOB_MAX_POSITIONS + return JOB_DENIED + + +/obj/machinery/computer/card/proc/id_insert(mob/user, obj/item/inserting_item, obj/item/target) + var/obj/item/card/id/card_to_insert = inserting_item + var/holder_item = FALSE + + if(!isidcard(card_to_insert)) + card_to_insert = inserting_item.RemoveID() + holder_item = TRUE + + if(!card_to_insert || !user.transferItemToLoc(card_to_insert, src)) + return FALSE + + if(target) + if(holder_item && inserting_item.InsertID(target)) + playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE) + else + id_eject(user, target) + + user.visible_message("[user] inserts \the [card_to_insert] into \the [src].", + "You insert \the [card_to_insert] into \the [src].") + playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE) + updateUsrDialog() + return TRUE + +/obj/machinery/computer/card/proc/id_eject(mob/user, obj/target) + if(!target) + to_chat(user, "That slot is empty!") + return FALSE + else + target.forceMove(drop_location()) + if(!issilicon(user) && Adjacent(user)) + user.put_in_hands(target) + user.visible_message("[user] gets \the [target] from \the [src].", \ + "You get \the [target] from \the [src].") + playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE) + updateUsrDialog() + return TRUE + +/obj/machinery/computer/card/AltClick(mob/user) + ..() + if(!user.canUseTopic(src, !issilicon(user)) || !is_operational()) + return + if(inserted_modify_id) + if(id_eject(user, inserted_modify_id)) + inserted_modify_id = null + updateUsrDialog() + return + if(inserted_scan_id) + if(id_eject(user, inserted_scan_id)) + inserted_scan_id = null + updateUsrDialog() + return + +/obj/machinery/computer/card/ui_interact(mob/user) + . = ..() + var/list/dat = list() + if (mode == 1) // accessing crew manifest + dat += "Crew Manifest:
    Please use security record computer to modify entries.

    " + for(var/datum/data/record/t in sortRecord(GLOB.data_core.general)) + dat += {"[t.fields["name"]] - [t.fields["rank"]]
    "} + dat += "Print

    Access ID modification console.
    " + + else if(mode == 2) + // JOB MANAGEMENT + dat += {"Return + + "} + for(var/datum/job/job in SSjob.occupations) + dat += "" + if(job.title in blacklisted) + continue + dat += {" + + " + dat += "
    JobSlotsOpen jobClose jobPrioritize
    [job.title][job.current_positions]/[job.total_positions]"} + switch(can_open_job(job)) + if(JOB_ALLOWED) + if(authenticated == 2) + dat += "Open Position
    " + else + dat += "Open Position" + if(JOB_COOLDOWN) + var/time_to_wait = round(change_position_cooldown - ((world.time / 10) - GLOB.time_last_changed_position), 1) + var/mins = round(time_to_wait / 60) + var/seconds = time_to_wait - (60*mins) + dat += "Cooldown ongoing: [mins]:[(seconds < 10) ? "0[seconds]" : "[seconds]"]" + else + dat += "Denied" + dat += "
    " + switch(can_close_job(job)) + if(JOB_ALLOWED) + if(authenticated == 2) + dat += "Close Position" + else + dat += "Close Position" + if(JOB_COOLDOWN) + var/time_to_wait = round(change_position_cooldown - ((world.time / 10) - GLOB.time_last_changed_position), 1) + var/mins = round(time_to_wait / 60) + var/seconds = time_to_wait - (60*mins) + dat += "Cooldown ongoing: [mins]:[(seconds < 10) ? "0[seconds]" : "[seconds]"]" + else + dat += "Denied" + dat += "" + switch(job.total_positions) + if(0) + dat += "Denied" + else + if(authenticated == 2) + if(job in SSjob.prioritized_jobs) + dat += "Deprioritize" + else + if(SSjob.prioritized_jobs.len < 5) + dat += "Prioritize" + else + dat += "Denied" + else + dat += "Prioritize" + + dat += "
    " + else + var/list/header = list() + + var/scan_name = inserted_scan_id ? html_encode(inserted_scan_id.name) : "--------" + var/target_name = inserted_modify_id ? html_encode(inserted_modify_id.name) : "--------" + var/target_owner = (inserted_modify_id && inserted_modify_id.registered_name) ? html_encode(inserted_modify_id.registered_name) : "--------" + var/target_rank = (inserted_modify_id && inserted_modify_id.assignment) ? html_encode(inserted_modify_id.assignment) : "Unassigned" + var/target_age = (inserted_modify_id && inserted_modify_id.registered_age) ? html_encode(inserted_modify_id.registered_age) : "--------" + + if(!authenticated) + header += {"
    Please insert the cards into the slots
    + Target: [target_name]
    + Confirm Identity: [scan_name]
    "} + else + header += {"

    + Target: Remove [target_name] || + Confirm Identity: Remove [scan_name]
    + Access Crew Manifest
    + [!target_dept ? "Job Management
    " : ""] + Log Out
    "} + + header += "
    " + + var/body + + if (authenticated && inserted_modify_id) + var/list/carddesc = list() + var/list/jobs = list() + if (authenticated == 2) + var/list/jobs_all = list() + for(var/job in (list("Unassigned") + get_jobs() + "Custom")) + jobs_all += "[replacetext(job, " ", " ")] " //make sure there isn't a line break in the middle of a job + carddesc += {""} + carddesc += {"
    + + + registered name: + registered age: + +
    + Assignment: "} + + jobs += "[target_rank]" //CHECK THIS + + else + carddesc += "registered_name: [target_owner]" + jobs += "Assignment: [target_rank] (Demote)" + + var/list/accesses = list() + if(istype(src, /obj/machinery/computer/card/centcom)) // REE + accesses += "
    Central Command:
    " + for(var/A in get_all_centcom_access()) + if(A in inserted_modify_id.access) + accesses += "[replacetext(get_centcom_access_desc(A), " ", " ")] " + else + accesses += "[replacetext(get_centcom_access_desc(A), " ", " ")] " + else + accesses += {"
    Access
    + + "} + for(var/i = 1; i <= 7; i++) + if(authenticated == 1 && !(i in region_access)) + continue + accesses += "" + accesses += "" + for(var/i = 1; i <= 7; i++) + if(authenticated == 1 && !(i in region_access)) + continue + accesses += "" + accesses += "
    [get_region_accesses_name(i)]:
    " + for(var/A in get_region_accesses(i)) + if(A in inserted_modify_id.access) + accesses += "[replacetext(get_access_desc(A), " ", " ")] " + else + accesses += "[replacetext(get_access_desc(A), " ", " ")] " + accesses += "
    " + accesses += "
    " + body = "[carddesc.Join()]
    [jobs.Join()]

    [accesses.Join()]
    " //CHECK THIS + + else if (!authenticated) + body = {"Log In

    + Access Crew Manifest

    "} + if(!target_dept) + body += "Job Management
    " + + dat = list("", header.Join(), body, "
    ") + var/datum/browser/popup = new(user, "id_com", src.name, 900, 620) + popup.set_content(dat.Join()) + popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) + popup.open() + +/obj/machinery/computer/card/Topic(href, href_list) + if(..()) + return + + if(!usr.canUseTopic(src, !issilicon(usr)) || !is_operational()) + usr.unset_machine() + usr << browse(null, "window=id_com") + return + + usr.set_machine(src) + switch(href_list["choice"]) + if ("inserted_modify_id") + if(inserted_modify_id && !usr.get_active_held_item()) + if(id_eject(usr, inserted_modify_id)) + inserted_modify_id = null + updateUsrDialog() + return + if(usr.get_id_in_hand()) + var/obj/item/held_item = usr.get_active_held_item() + var/obj/item/card/id/id_to_insert = held_item.GetID() + if(id_insert(usr, held_item, inserted_modify_id)) + inserted_modify_id = id_to_insert + updateUsrDialog() + if ("inserted_scan_id") + if(inserted_scan_id && !usr.get_active_held_item()) + if(id_eject(usr, inserted_scan_id)) + inserted_scan_id = null + updateUsrDialog() + return + if(usr.get_id_in_hand()) + var/obj/item/held_item = usr.get_active_held_item() + var/obj/item/card/id/id_to_insert = held_item.GetID() + if(id_insert(usr, held_item, inserted_scan_id)) + inserted_scan_id = id_to_insert + updateUsrDialog() + if ("auth") + if ((!( authenticated ) && (inserted_scan_id || issilicon(usr)) || mode)) + if (check_access(inserted_scan_id)) + region_access = list() + head_subordinates = list() + if(ACCESS_CHANGE_IDS in inserted_scan_id.access) + if(target_dept) + head_subordinates = get_all_jobs() + region_access |= target_dept + authenticated = 1 + else + authenticated = 2 + playsound(src, 'sound/machines/terminal_on.ogg', 50, FALSE) + + else + if((ACCESS_HOP in inserted_scan_id.access) && ((target_dept==1) || !target_dept)) + region_access |= 1 + region_access |= 6 + get_subordinates("Head of Personnel") + if((ACCESS_HOS in inserted_scan_id.access) && ((target_dept==2) || !target_dept)) + region_access |= 2 + get_subordinates("Head of Security") + if((ACCESS_CMO in inserted_scan_id.access) && ((target_dept==3) || !target_dept)) + region_access |= 3 + get_subordinates("Chief Medical Officer") + if((ACCESS_RD in inserted_scan_id.access) && ((target_dept==4) || !target_dept)) + region_access |= 4 + get_subordinates("Research Director") + if((ACCESS_CE in inserted_scan_id.access) && ((target_dept==5) || !target_dept)) + region_access |= 5 + get_subordinates("Chief Engineer") + if(region_access) + authenticated = 1 + else if ((!( authenticated ) && issilicon(usr)) && (!inserted_modify_id)) + to_chat(usr, "You can't modify an ID without an ID inserted to modify! Once one is in the modify slot on the computer, you can log in.") + if ("logout") + region_access = null + head_subordinates = null + authenticated = 0 + playsound(src, 'sound/machines/terminal_off.ogg', 50, FALSE) + + if("access") + if(href_list["allowed"]) + if(authenticated) + var/access_type = text2num(href_list["access_target"]) + var/access_allowed = text2num(href_list["allowed"]) + if(access_type in (istype(src, /obj/machinery/computer/card/centcom)?get_all_centcom_access() : get_all_accesses())) + inserted_modify_id.access -= access_type + if(access_allowed == 1) + inserted_modify_id.access += access_type + playsound(src, "terminal_type", 50, FALSE) + if ("assign") + if (authenticated == 2) + var/t1 = href_list["assign_target"] + if(t1 == "Custom") + var/newJob = reject_bad_text(input("Enter a custom job assignment.", "Assignment", inserted_modify_id ? inserted_modify_id.assignment : "Unassigned"), MAX_NAME_LEN) + if(newJob) + t1 = newJob + + else if(t1 == "Unassigned") + inserted_modify_id.access -= get_all_accesses() + + else + var/datum/job/jobdatum + for(var/jobtype in typesof(/datum/job)) + var/datum/job/J = new jobtype + if(ckey(J.title) == ckey(t1)) + jobdatum = J + updateUsrDialog() + break + if(!jobdatum) + to_chat(usr, "No log exists for this job.") + updateUsrDialog() + return + if(inserted_modify_id.registered_account) + inserted_modify_id.registered_account.account_job = jobdatum // this is a terrible idea and people will grief but sure whatever + + inserted_modify_id.access = ( istype(src, /obj/machinery/computer/card/centcom) ? get_centcom_access(t1) : jobdatum.get_access() ) + if (inserted_modify_id) + inserted_modify_id.assignment = t1 + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) + if ("demote") + if(inserted_modify_id.assignment in head_subordinates || inserted_modify_id.assignment == "Assistant") + inserted_modify_id.assignment = "Unassigned" + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) + else + to_chat(usr, "You are not authorized to demote this position.") + if ("reg") + if (authenticated) + var/t2 = inserted_modify_id + if ((authenticated && inserted_modify_id == t2 && (in_range(src, usr) || issilicon(usr)) && isturf(loc))) + var/newAge = text2num(href_list["setage"])|null + if(newAge && isnum(newAge)) + inserted_modify_id.registered_age = newAge + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) + else if(!isnull(newAge)) + to_chat(usr, "Invalid age entered- age not updated.") + updateUsrDialog() + + var/newName = reject_bad_name(href_list["reg"]) + if(newName) + inserted_modify_id.registered_name = newName + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) + else + to_chat(usr, "Invalid name entered.") + updateUsrDialog() + return + if ("mode") + mode = text2num(href_list["mode_target"]) + + if("return") + //DISPLAY MAIN MENU + mode = 3; + playsound(src, "terminal_type", 25, FALSE) + + if("make_job_available") + // MAKE ANOTHER JOB POSITION AVAILABLE FOR LATE JOINERS + if(authenticated && !target_dept) + var/edit_job_target = href_list["job"] + var/datum/job/j = SSjob.GetJob(edit_job_target) + if(!j) + updateUsrDialog() + return 0 + if(can_open_job(j) != 1) + updateUsrDialog() + return 0 + if(opened_positions[edit_job_target] >= 0) + GLOB.time_last_changed_position = world.time / 10 + j.total_positions++ + opened_positions[edit_job_target]++ + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) + + if("make_job_unavailable") + // MAKE JOB POSITION UNAVAILABLE FOR LATE JOINERS + if(authenticated && !target_dept) + var/edit_job_target = href_list["job"] + var/datum/job/j = SSjob.GetJob(edit_job_target) + if(!j) + updateUsrDialog() + return 0 + if(can_close_job(j) != 1) + updateUsrDialog() + return 0 + //Allow instant closing without cooldown if a position has been opened before + if(opened_positions[edit_job_target] <= 0) + GLOB.time_last_changed_position = world.time / 10 + j.total_positions-- + opened_positions[edit_job_target]-- + playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE) + + if ("prioritize_job") + // TOGGLE WHETHER JOB APPEARS AS PRIORITIZED IN THE LOBBY + if(authenticated && !target_dept) + var/priority_target = href_list["job"] + var/datum/job/j = SSjob.GetJob(priority_target) + if(!j) + updateUsrDialog() + return 0 + var/priority = TRUE + if(j in SSjob.prioritized_jobs) + SSjob.prioritized_jobs -= j + priority = FALSE + else if(j.total_positions <= j.current_positions) + to_chat(usr, "[j.title] has had all positions filled. Open up more slots before prioritizing it.") + updateUsrDialog() + return + else + SSjob.prioritized_jobs += j + to_chat(usr, "[j.title] has been successfully [priority ? "prioritized" : "unprioritized"]. Potential employees will notice your request.") + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) + + if ("print") + if (!( printing )) + printing = 1 + sleep(50) + var/obj/item/paper/P = new /obj/item/paper( loc ) + var/t1 = "Crew Manifest:
    " + for(var/datum/data/record/t in sortRecord(GLOB.data_core.general)) + t1 += t.fields["name"] + " - " + t.fields["rank"] + "
    " + P.info = t1 + P.name = "paper- 'Crew Manifest'" + printing = null + playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE) + if (inserted_modify_id) + inserted_modify_id.update_label() + updateUsrDialog() + +/obj/machinery/computer/card/proc/get_subordinates(rank) + for(var/datum/job/job in SSjob.occupations) + if(rank in job.department_head) + head_subordinates += job.title + +/obj/machinery/computer/card/centcom + name = "\improper CentCom identification console" + circuit = /obj/item/circuitboard/computer/card/centcom + req_access = list(ACCESS_CENT_CAPTAIN) + +/obj/machinery/computer/card/minor + name = "department management console" + desc = "You can use this to change ID's for specific departments." + icon_screen = "idminor" + circuit = /obj/item/circuitboard/computer/card/minor + +/obj/machinery/computer/card/minor/Initialize() + . = ..() + var/obj/item/circuitboard/computer/card/minor/typed_circuit = circuit + if(target_dept) + typed_circuit.target_dept = target_dept + else + target_dept = typed_circuit.target_dept + var/list/dept_list = list("general","security","medical","science","engineering") + name = "[dept_list[target_dept]] department console" + +/obj/machinery/computer/card/minor/hos + target_dept = 2 + icon_screen = "idhos" + + light_color = LIGHT_COLOR_RED + +/obj/machinery/computer/card/minor/cmo + target_dept = 3 + icon_screen = "idcmo" + +/obj/machinery/computer/card/minor/rd + target_dept = 4 + icon_screen = "idrd" + + light_color = LIGHT_COLOR_PINK + +/obj/machinery/computer/card/minor/ce + target_dept = 5 + icon_screen = "idce" + + light_color = LIGHT_COLOR_YELLOW + +#undef JOB_ALLOWED +#undef JOB_COOLDOWN +#undef JOB_MAX_POSITIONS +#undef JOB_DENIED diff --git a/code/game/machinery/computer/communications.dm b/code/game/machinery/computer/communications.dm index efa5d85ebbea..fa7f23530064 100755 --- a/code/game/machinery/computer/communications.dm +++ b/code/game/machinery/computer/communications.dm @@ -1,767 +1,767 @@ -// The communications computer -/obj/machinery/computer/communications - name = "communications console" - desc = "A console used for high-priority announcements and emergencies." - icon_screen = "comm" - icon_keyboard = "tech_key" - req_access = list(ACCESS_HEADS) - circuit = /obj/item/circuitboard/computer/communications - 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/const/STATE_DEFAULT = 1 - var/const/STATE_CALLSHUTTLE = 2 - var/const/STATE_CANCELSHUTTLE = 3 - var/const/STATE_MESSAGELIST = 4 - var/const/STATE_VIEWMESSAGE = 5 - var/const/STATE_DELMESSAGE = 6 - var/const/STATE_STATUSDISPLAY = 7 - var/const/STATE_ALERT_LEVEL = 8 - var/const/STATE_CONFIRM_LEVEL = 9 - var/const/STATE_TOGGLE_EMERGENCY = 10 - var/const/STATE_PURCHASE = 11 - - var/stat_msg1 - var/stat_msg2 - - light_color = LIGHT_COLOR_BLUE - -/obj/machinery/computer/communications/proc/checkCCcooldown() - var/obj/item/circuitboard/computer/communications/CM = circuit - if(CM.lastTimeUsed + 600 > world.time) - return FALSE - return TRUE - -/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) - - - 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((20 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) - 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_BLUE) - tmp_alertlevel = SEC_LEVEL_BLUE //Cannot engage delta with this - set_security_level(tmp_alertlevel) - 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 = get_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(" has changed the security level to [security_level] with [src] at [get_area_name(usr, TRUE)].", "[usr.real_name]", usr, message_type=DEADCHAT_ANNOUNCEMENT) - 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(" has sent an outgoing message to the other station(s).", "[usr.real_name]", usr, message_type=DEADCHAT_ANNOUNCEMENT) - 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.[S.extra_desc ? " [S.extra_desc]" : ""]" , "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) - 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(" enabled emergency maintenance access at [get_area_name(usr, TRUE)].", "[usr.real_name]", usr, message_type=DEADCHAT_ANNOUNCEMENT) - 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(" disabled emergency maintenance access at [get_area_name(usr, TRUE)].", "[usr.real_name]", usr, message_type=DEADCHAT_ANNOUNCEMENT) - 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.") - usr.log_talk(input, LOG_SAY, tag="CentCom announcement") - deadchat_broadcast(" has messaged CentCom, \"[input]\" at [get_area_name(usr, TRUE)].", "[usr.real_name]", usr, message_type=DEADCHAT_ANNOUNCEMENT) - 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.") - usr.log_talk(input, LOG_SAY, tag="Syndicate announcement") - deadchat_broadcast(" has messaged the Syndicate, \"[input]\" at [get_area_name(usr, TRUE)].", "[usr.real_name]", usr, message_type=DEADCHAT_ANNOUNCEMENT) - 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",'sound/ai/commandreport.ogg') - 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") - tmp_alertlevel = text2num( href_list["newalertlevel"] ) - if(!tmp_alertlevel) - tmp_alertlevel = 0 - 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_BLUE) - tmp_alertlevel = SEC_LEVEL_BLUE //Cannot engage delta with this - set_security_level(tmp_alertlevel) - if(GLOB.security_level != old_level) - //Only notify people if an actual change happened - var/security_level = get_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(" has changed the security level to [security_level] from [src] at [get_area_name(usr, TRUE)].", "[usr.real_name]", usr, message_type=DEADCHAT_ANNOUNCEMENT) - 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(" enabled emergency maintenance access.", "[usr.real_name]", usr, message_type=DEADCHAT_ANNOUNCEMENT) - 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(" disabled emergency maintenance access.", "[usr.real_name]", usr, message_type=DEADCHAT_ANNOUNCEMENT) - aistate = STATE_DEFAULT - - updateUsrDialog() - -/obj/machinery/computer/communications/attackby(obj/I, mob/user, params) - if(istype(I, /obj/item/card/id)) - attack_hand(user) - else - return ..() - -/obj/machinery/computer/communications/emag_act(mob/user) - if(obj_flags & EMAGGED) - return - obj_flags |= EMAGGED - SSshuttle.shuttle_purchase_requirements_met |= SHUTTLE_UNLOCK_EMAGGED //Wasp Edit - Makes shuttles from Emag list purchaseable - if(authenticated == 1) - authenticated = 2 - to_chat(user, "You scramble the communication routing circuits!") - playsound(src, 'sound/machines/terminal_alert.ogg', 50, FALSE) - -/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!") - 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")]" - - - var/datum/browser/popup = new(user, "communications", "Communications Console", 400, 500) - popup.set_title_image(user.browse_rsc_icon(icon, icon_state)) - - 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: [get_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 += "Blue
    " - dat += "Green" - if(STATE_CONFIRM_LEVEL) - dat += "Current alert level: [get_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: [get_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 += "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)) - 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?") - if(!input || !user.canUseTopic(src, !issilicon(usr))) - return - if(!(user.can_speak())) //No more cheating, mime/random mute guy! - input = "..." - 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(" made a priority announcement from [get_area_name(usr, TRUE)].", "[user.real_name]", user, message_type=DEADCHAT_ANNOUNCEMENT) - -/obj/machinery/computer/communications/proc/post_status(command, data1, data2) - - var/datum/radio_frequency/frequency = SSradio.return_frequency(FREQ_STATUS_DISPLAYS) - - if(!frequency) - return - - var/datum/signal/status_signal = new(list("command" = command)) - switch(command) - if("message") - status_signal.data["msg1"] = data1 - status_signal.data["msg2"] = data2 - if("alert") - status_signal.data["picture_state"] = data1 - - 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 - -/obj/machinery/computer/communications/proc/add_message(datum/comm_message/new_message) - messages += new_message - -/datum/comm_message - var/title - var/content - var/list/possible_answers = list() - var/answered - var/datum/callback/answer_callback - -/datum/comm_message/New(new_title,new_content,new_possible_answers) - ..() - if(new_title) - title = new_title - if(new_content) - content = new_content - if(new_possible_answers) - possible_answers = new_possible_answers +// The communications computer +/obj/machinery/computer/communications + name = "communications console" + desc = "A console used for high-priority announcements and emergencies." + icon_screen = "comm" + icon_keyboard = "tech_key" + req_access = list(ACCESS_HEADS) + circuit = /obj/item/circuitboard/computer/communications + 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/const/STATE_DEFAULT = 1 + var/const/STATE_CALLSHUTTLE = 2 + var/const/STATE_CANCELSHUTTLE = 3 + var/const/STATE_MESSAGELIST = 4 + var/const/STATE_VIEWMESSAGE = 5 + var/const/STATE_DELMESSAGE = 6 + var/const/STATE_STATUSDISPLAY = 7 + var/const/STATE_ALERT_LEVEL = 8 + var/const/STATE_CONFIRM_LEVEL = 9 + var/const/STATE_TOGGLE_EMERGENCY = 10 + var/const/STATE_PURCHASE = 11 + + var/stat_msg1 + var/stat_msg2 + + light_color = LIGHT_COLOR_BLUE + +/obj/machinery/computer/communications/proc/checkCCcooldown() + var/obj/item/circuitboard/computer/communications/CM = circuit + if(CM.lastTimeUsed + 600 > world.time) + return FALSE + return TRUE + +/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) + + + 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((20 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) + 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_BLUE) + tmp_alertlevel = SEC_LEVEL_BLUE //Cannot engage delta with this + set_security_level(tmp_alertlevel) + 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 = get_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(" has changed the security level to [security_level] with [src] at [get_area_name(usr, TRUE)].", "[usr.real_name]", usr, message_type=DEADCHAT_ANNOUNCEMENT) + 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(" has sent an outgoing message to the other station(s).", "[usr.real_name]", usr, message_type=DEADCHAT_ANNOUNCEMENT) + 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.[S.extra_desc ? " [S.extra_desc]" : ""]" , "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) + 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(" enabled emergency maintenance access at [get_area_name(usr, TRUE)].", "[usr.real_name]", usr, message_type=DEADCHAT_ANNOUNCEMENT) + 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(" disabled emergency maintenance access at [get_area_name(usr, TRUE)].", "[usr.real_name]", usr, message_type=DEADCHAT_ANNOUNCEMENT) + 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.") + usr.log_talk(input, LOG_SAY, tag="CentCom announcement") + deadchat_broadcast(" has messaged CentCom, \"[input]\" at [get_area_name(usr, TRUE)].", "[usr.real_name]", usr, message_type=DEADCHAT_ANNOUNCEMENT) + 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.") + usr.log_talk(input, LOG_SAY, tag="Syndicate announcement") + deadchat_broadcast(" has messaged the Syndicate, \"[input]\" at [get_area_name(usr, TRUE)].", "[usr.real_name]", usr, message_type=DEADCHAT_ANNOUNCEMENT) + 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",'sound/ai/commandreport.ogg') + 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") + tmp_alertlevel = text2num( href_list["newalertlevel"] ) + if(!tmp_alertlevel) + tmp_alertlevel = 0 + 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_BLUE) + tmp_alertlevel = SEC_LEVEL_BLUE //Cannot engage delta with this + set_security_level(tmp_alertlevel) + if(GLOB.security_level != old_level) + //Only notify people if an actual change happened + var/security_level = get_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(" has changed the security level to [security_level] from [src] at [get_area_name(usr, TRUE)].", "[usr.real_name]", usr, message_type=DEADCHAT_ANNOUNCEMENT) + 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(" enabled emergency maintenance access.", "[usr.real_name]", usr, message_type=DEADCHAT_ANNOUNCEMENT) + 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(" disabled emergency maintenance access.", "[usr.real_name]", usr, message_type=DEADCHAT_ANNOUNCEMENT) + aistate = STATE_DEFAULT + + updateUsrDialog() + +/obj/machinery/computer/communications/attackby(obj/I, mob/user, params) + if(istype(I, /obj/item/card/id)) + attack_hand(user) + else + return ..() + +/obj/machinery/computer/communications/emag_act(mob/user) + if(obj_flags & EMAGGED) + return + obj_flags |= EMAGGED + SSshuttle.shuttle_purchase_requirements_met |= SHUTTLE_UNLOCK_EMAGGED //Wasp Edit - Makes shuttles from Emag list purchaseable + if(authenticated == 1) + authenticated = 2 + to_chat(user, "You scramble the communication routing circuits!") + playsound(src, 'sound/machines/terminal_alert.ogg', 50, FALSE) + +/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!") + 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")]" + + + var/datum/browser/popup = new(user, "communications", "Communications Console", 400, 500) + popup.set_title_image(user.browse_rsc_icon(icon, icon_state)) + + 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: [get_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 += "Blue
    " + dat += "Green" + if(STATE_CONFIRM_LEVEL) + dat += "Current alert level: [get_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: [get_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 += "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)) + 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?") + if(!input || !user.canUseTopic(src, !issilicon(usr))) + return + if(!(user.can_speak())) //No more cheating, mime/random mute guy! + input = "..." + 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(" made a priority announcement from [get_area_name(usr, TRUE)].", "[user.real_name]", user, message_type=DEADCHAT_ANNOUNCEMENT) + +/obj/machinery/computer/communications/proc/post_status(command, data1, data2) + + var/datum/radio_frequency/frequency = SSradio.return_frequency(FREQ_STATUS_DISPLAYS) + + if(!frequency) + return + + var/datum/signal/status_signal = new(list("command" = command)) + switch(command) + if("message") + status_signal.data["msg1"] = data1 + status_signal.data["msg2"] = data2 + if("alert") + status_signal.data["picture_state"] = data1 + + 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 + +/obj/machinery/computer/communications/proc/add_message(datum/comm_message/new_message) + messages += new_message + +/datum/comm_message + var/title + var/content + var/list/possible_answers = list() + var/answered + var/datum/callback/answer_callback + +/datum/comm_message/New(new_title,new_content,new_possible_answers) + ..() + if(new_title) + title = new_title + if(new_content) + content = new_content + if(new_possible_answers) + possible_answers = new_possible_answers diff --git a/code/game/machinery/computer/crew.dm b/code/game/machinery/computer/crew.dm index 455133bbfbb0..73e302f827c8 100644 --- a/code/game/machinery/computer/crew.dm +++ b/code/game/machinery/computer/crew.dm @@ -1,200 +1,200 @@ -#define SENSORS_UPDATE_PERIOD 100 //How often the sensor data updates. - -/obj/machinery/computer/crew - name = "crew monitoring console" - desc = "Used to monitor active health sensors built into most of the crew's uniforms." - icon_screen = "crew" - icon_keyboard = "med_key" - use_power = IDLE_POWER_USE - idle_power_usage = 250 - active_power_usage = 500 - circuit = /obj/item/circuitboard/computer/crew - - light_color = LIGHT_COLOR_BLUE - -/obj/machinery/computer/crew/syndie - icon_keyboard = "syndie_key" - -/obj/machinery/computer/crew/interact(mob/user) - GLOB.crewmonitor.show(user,src) - -GLOBAL_DATUM_INIT(crewmonitor, /datum/crewmonitor, new) - -/datum/crewmonitor - var/list/ui_sources = list() //List of user -> ui source - var/list/jobs - var/list/data_by_z = list() - var/list/last_update = list() - -/datum/crewmonitor/New() - . = ..() - - var/list/jobs = new/list() - jobs["Captain"] = 00 - jobs["Head of Personnel"] = 02 - jobs["Lieutenant"] = 05 - jobs["Head of Security"] = 10 - jobs["Warden"] = 11 - jobs["Security Officer"] = 12 - jobs["Detective"] = 13 - jobs["Brig Physician"] = 14 - jobs["Chief Medical Officer"] = 20 - jobs["Chemist"] = 21 - jobs["Virologist"] = 22 - jobs["Medical Doctor"] = 23 - jobs["Paramedic"] = 24 - jobs["Research Director"] = 30 - jobs["Scientist"] = 31 - jobs["Roboticist"] = 32 - jobs["Geneticist"] = 33 - jobs["Chief Engineer"] = 40 - jobs["Station Engineer"] = 41 - jobs["Atmospheric Technician"] = 42 - jobs["Quartermaster"] = 51 - jobs["Shaft Miner"] = 52 - jobs["Cargo Technician"] = 53 - jobs["Bartender"] = 61 - jobs["Cook"] = 62 - jobs["Botanist"] = 63 - jobs["Curator"] = 64 - jobs["Chaplain"] = 65 - jobs["Clown"] = 66 - jobs["Mime"] = 67 - jobs["Janitor"] = 68 - jobs["Lawyer"] = 69 - jobs["Psychologist"] = 70 - jobs["Admiral"] = 200 - jobs["CentCom Commander"] = 210 - jobs["Custodian"] = 211 - jobs["Medical Officer"] = 212 - jobs["Research Officer"] = 213 - jobs["Emergency Response Team Commander"] = 220 - jobs["Security Response Officer"] = 221 - jobs["Engineer Response Officer"] = 222 - jobs["Medical Response Officer"] = 223 - jobs["Assistant"] = 999 //Unknowns/custom jobs should appear after civilians, and before assistants - - src.jobs = jobs - -/datum/crewmonitor/Destroy() - return ..() - -/datum/crewmonitor/ui_interact(mob/user, ui_key = "crew", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if (!ui) - ui = new(user, src, ui_key, "CrewConsole", "crew monitor", 800, 600 , master_ui, state) - ui.open() - -/datum/crewmonitor/proc/show(mob/M, source) - ui_sources[M] = source - ui_interact(M) - -/datum/crewmonitor/ui_host(mob/user) - return ui_sources[user] - -/datum/crewmonitor/ui_data(mob/user) - var/z = user.z - if(!z) - var/turf/T = get_turf(user) - z = T.z - var/list/zdata = update_data(z) - . = list() - .["sensors"] = zdata - .["link_allowed"] = isAI(user) - -/datum/crewmonitor/proc/update_data(z) - if(data_by_z["[z]"] && last_update["[z]"] && world.time <= last_update["[z]"] + SENSORS_UPDATE_PERIOD) - return data_by_z["[z]"] - - var/list/results = list() - var/obj/item/clothing/under/U - var/obj/item/card/id/I - var/turf/pos - var/ijob - var/name - var/assignment - var/oxydam - var/toxdam - var/burndam - var/brutedam - var/area - var/pos_x - var/pos_y - var/life_status - - for(var/i in GLOB.human_list) - var/mob/living/carbon/human/H = i - var/nanite_sensors = FALSE - if(H in SSnanites.nanite_monitored_mobs) - nanite_sensors = TRUE - // Check if their z-level is correct and if they are wearing a uniform. - // Accept H.z==0 as well in case the mob is inside an object. - if ((H.z == 0 || H.z == z) && (istype(H.w_uniform, /obj/item/clothing/under) || nanite_sensors)) - U = H.w_uniform - - // Are the suit sensors on? - if (nanite_sensors || ((U.has_sensor > 0) && U.sensor_mode)) - pos = H.z == 0 || (nanite_sensors || U.sensor_mode == SENSOR_COORDS) ? get_turf(H) : null - - // Special case: If the mob is inside an object confirm the z-level on turf level. - if (H.z == 0 && (!pos || pos.z != z)) - continue - - I = H.wear_id ? H.wear_id.GetID() : null - - if (I) - name = I.registered_name - assignment = I.assignment - ijob = jobs[I.GetJobName()] //Why didn't it do this already - Wasp Edit - Alt Titles - else - name = "Unknown" - assignment = "" - ijob = 80 - - if (nanite_sensors || U.sensor_mode >= SENSOR_LIVING) - life_status = ((H.stat < DEAD) ? TRUE : FALSE) //So anything less that dead is marked as alive. (Soft crit, concious, unconcious) - else - life_status = null - - if (nanite_sensors || U.sensor_mode >= SENSOR_VITALS) - oxydam = round(H.getOxyLoss(),1) - toxdam = round(H.getToxLoss(),1) - burndam = round(H.getFireLoss(),1) - brutedam = round(H.getBruteLoss(),1) - else - oxydam = null - toxdam = null - burndam = null - brutedam = null - - if (nanite_sensors || U.sensor_mode >= SENSOR_COORDS) - if (!pos) - pos = get_turf(H) - area = get_area_name(H, TRUE) - pos_x = pos.x - pos_y = pos.y - else - area = null - pos_x = null - pos_y = null - - results[++results.len] = list("name" = name, "assignment" = assignment, "ijob" = ijob, "life_status" = life_status, "oxydam" = oxydam, "toxdam" = toxdam, "burndam" = burndam, "brutedam" = brutedam, "area" = area, "pos_x" = pos_x, "pos_y" = pos_y, "can_track" = H.can_track(null)) - - data_by_z["[z]"] = sortTim(results,/proc/sensor_compare) - last_update["[z]"] = world.time - - return results - -/proc/sensor_compare(list/a,list/b) - return a["ijob"] - b["ijob"] - -/datum/crewmonitor/ui_act(action,params) - var/mob/living/silicon/ai/AI = usr - if(!istype(AI)) - return - switch (action) - if ("select_person") - AI.ai_camera_track(params["name"]) - -#undef SENSORS_UPDATE_PERIOD +#define SENSORS_UPDATE_PERIOD 100 //How often the sensor data updates. + +/obj/machinery/computer/crew + name = "crew monitoring console" + desc = "Used to monitor active health sensors built into most of the crew's uniforms." + icon_screen = "crew" + icon_keyboard = "med_key" + use_power = IDLE_POWER_USE + idle_power_usage = 250 + active_power_usage = 500 + circuit = /obj/item/circuitboard/computer/crew + + light_color = LIGHT_COLOR_BLUE + +/obj/machinery/computer/crew/syndie + icon_keyboard = "syndie_key" + +/obj/machinery/computer/crew/interact(mob/user) + GLOB.crewmonitor.show(user,src) + +GLOBAL_DATUM_INIT(crewmonitor, /datum/crewmonitor, new) + +/datum/crewmonitor + var/list/ui_sources = list() //List of user -> ui source + var/list/jobs + var/list/data_by_z = list() + var/list/last_update = list() + +/datum/crewmonitor/New() + . = ..() + + var/list/jobs = new/list() + jobs["Captain"] = 00 + jobs["Head of Personnel"] = 02 + jobs["Lieutenant"] = 05 + jobs["Head of Security"] = 10 + jobs["Warden"] = 11 + jobs["Security Officer"] = 12 + jobs["Detective"] = 13 + jobs["Brig Physician"] = 14 + jobs["Chief Medical Officer"] = 20 + jobs["Chemist"] = 21 + jobs["Virologist"] = 22 + jobs["Medical Doctor"] = 23 + jobs["Paramedic"] = 24 + jobs["Research Director"] = 30 + jobs["Scientist"] = 31 + jobs["Roboticist"] = 32 + jobs["Geneticist"] = 33 + jobs["Chief Engineer"] = 40 + jobs["Station Engineer"] = 41 + jobs["Atmospheric Technician"] = 42 + jobs["Quartermaster"] = 51 + jobs["Shaft Miner"] = 52 + jobs["Cargo Technician"] = 53 + jobs["Bartender"] = 61 + jobs["Cook"] = 62 + jobs["Botanist"] = 63 + jobs["Curator"] = 64 + jobs["Chaplain"] = 65 + jobs["Clown"] = 66 + jobs["Mime"] = 67 + jobs["Janitor"] = 68 + jobs["Lawyer"] = 69 + jobs["Psychologist"] = 70 + jobs["Admiral"] = 200 + jobs["CentCom Commander"] = 210 + jobs["Custodian"] = 211 + jobs["Medical Officer"] = 212 + jobs["Research Officer"] = 213 + jobs["Emergency Response Team Commander"] = 220 + jobs["Security Response Officer"] = 221 + jobs["Engineer Response Officer"] = 222 + jobs["Medical Response Officer"] = 223 + jobs["Assistant"] = 999 //Unknowns/custom jobs should appear after civilians, and before assistants + + src.jobs = jobs + +/datum/crewmonitor/Destroy() + return ..() + +/datum/crewmonitor/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if (!ui) + ui = new(user, src, "CrewConsole") + ui.open() + +/datum/crewmonitor/proc/show(mob/M, source) + ui_sources[M] = source + ui_interact(M) + +/datum/crewmonitor/ui_host(mob/user) + return ui_sources[user] + +/datum/crewmonitor/ui_data(mob/user) + var/z = user.z + if(!z) + var/turf/T = get_turf(user) + z = T.z + var/list/zdata = update_data(z) + . = list() + .["sensors"] = zdata + .["link_allowed"] = isAI(user) + +/datum/crewmonitor/proc/update_data(z) + if(data_by_z["[z]"] && last_update["[z]"] && world.time <= last_update["[z]"] + SENSORS_UPDATE_PERIOD) + return data_by_z["[z]"] + + var/list/results = list() + var/obj/item/clothing/under/U + var/obj/item/card/id/I + var/turf/pos + var/ijob + var/name + var/assignment + var/oxydam + var/toxdam + var/burndam + var/brutedam + var/area + var/pos_x + var/pos_y + var/life_status + + for(var/i in GLOB.human_list) + var/mob/living/carbon/human/H = i + var/nanite_sensors = FALSE + if(H in SSnanites.nanite_monitored_mobs) + nanite_sensors = TRUE + // Check if their z-level is correct and if they are wearing a uniform. + // Accept H.z==0 as well in case the mob is inside an object. + if ((H.z == 0 || H.z == z) && (istype(H.w_uniform, /obj/item/clothing/under) || nanite_sensors)) + U = H.w_uniform + + // Are the suit sensors on? + if (nanite_sensors || ((U.has_sensor > 0) && U.sensor_mode)) + pos = H.z == 0 || (nanite_sensors || U.sensor_mode == SENSOR_COORDS) ? get_turf(H) : null + + // Special case: If the mob is inside an object confirm the z-level on turf level. + if (H.z == 0 && (!pos || pos.z != z)) + continue + + I = H.wear_id ? H.wear_id.GetID() : null + + if (I) + name = I.registered_name + assignment = I.assignment + ijob = jobs[I.GetJobName()] //Why didn't it do this already - Wasp Edit - Alt Titles + else + name = "Unknown" + assignment = "" + ijob = 80 + + if (nanite_sensors || U.sensor_mode >= SENSOR_LIVING) + life_status = ((H.stat < DEAD) ? TRUE : FALSE) //So anything less that dead is marked as alive. (Soft crit, concious, unconcious) + else + life_status = null + + if (nanite_sensors || U.sensor_mode >= SENSOR_VITALS) + oxydam = round(H.getOxyLoss(),1) + toxdam = round(H.getToxLoss(),1) + burndam = round(H.getFireLoss(),1) + brutedam = round(H.getBruteLoss(),1) + else + oxydam = null + toxdam = null + burndam = null + brutedam = null + + if (nanite_sensors || U.sensor_mode >= SENSOR_COORDS) + if (!pos) + pos = get_turf(H) + area = get_area_name(H, TRUE) + pos_x = pos.x + pos_y = pos.y + else + area = null + pos_x = null + pos_y = null + + results[++results.len] = list("name" = name, "assignment" = assignment, "ijob" = ijob, "life_status" = life_status, "oxydam" = oxydam, "toxdam" = toxdam, "burndam" = burndam, "brutedam" = brutedam, "area" = area, "pos_x" = pos_x, "pos_y" = pos_y, "can_track" = H.can_track(null)) + + data_by_z["[z]"] = sortTim(results,/proc/sensor_compare) + last_update["[z]"] = world.time + + return results + +/proc/sensor_compare(list/a,list/b) + return a["ijob"] - b["ijob"] + +/datum/crewmonitor/ui_act(action,params) + var/mob/living/silicon/ai/AI = usr + if(!istype(AI)) + return + switch (action) + if ("select_person") + AI.ai_camera_track(params["name"]) + +#undef SENSORS_UPDATE_PERIOD + diff --git a/code/game/machinery/computer/dna_console.dm b/code/game/machinery/computer/dna_console.dm index 22ea6b763070..04275e0f6695 100644 --- a/code/game/machinery/computer/dna_console.dm +++ b/code/game/machinery/computer/dna_console.dm @@ -207,12 +207,7 @@ // already discovered mutations stored_research = SSresearch.science_tech -/obj/machinery/computer/scan_consolenew/examine(mob/user) - . = ..() - -/obj/machinery/computer/scan_consolenew/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - . = ..() - +/obj/machinery/computer/scan_consolenew/ui_interact(mob/user, datum/tgui/ui) // Most of ui_interact is spent setting variables for passing to the tgui // interface. // We can also do some general state processing here too as it's a good @@ -254,10 +249,9 @@ time_to_pulse = round((rad_pulse_timer - world.time)/10) // Attempt to update tgui ui, open and update if needed. - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "DnaConsole", name, 539, 710, master_ui, state) + ui = new(user, src, "DnaConsole") ui.open() /obj/machinery/computer/scan_consolenew/ui_data(mob/user) diff --git a/code/game/machinery/computer/launchpad_control.dm b/code/game/machinery/computer/launchpad_control.dm index ed5a031dba2f..e7961c18ecda 100644 --- a/code/game/machinery/computer/launchpad_control.dm +++ b/code/game/machinery/computer/launchpad_control.dm @@ -4,8 +4,6 @@ icon_screen = "teleport" icon_keyboard = "teleport_key" circuit = /obj/item/circuitboard/computer/launchpad_console - ui_x = 475 - ui_y = 260 var/selected_id var/list/obj/machinery/launchpad/launchpads @@ -53,10 +51,10 @@ var/obj/machinery/launchpad/pad = launchpads[number] return pad -/obj/machinery/computer/launchpad/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/machinery/computer/launchpad/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "LaunchpadConsole", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, "LaunchpadConsole", name) ui.open() /obj/machinery/computer/launchpad/ui_data(mob/user) diff --git a/code/game/machinery/computer/law.dm b/code/game/machinery/computer/law.dm index 8fe12fb4122a..33cac5f5edac 100644 --- a/code/game/machinery/computer/law.dm +++ b/code/game/machinery/computer/law.dm @@ -1,77 +1,77 @@ - - -/obj/machinery/computer/upload - var/mob/living/silicon/current = null //The target of future law uploads - icon_screen = "command" - time_to_screwdrive = 60 - -/obj/machinery/computer/upload/Initialize() - . = ..() - AddComponent(/datum/component/gps, "Encrypted Upload") - -/obj/machinery/computer/upload/attackby(obj/item/O, mob/user, params) - if(istype(O, /obj/item/aiModule)) - var/obj/item/aiModule/M = O - if(machine_stat & (NOPOWER|BROKEN|MAINT)) - return - if(!current) - to_chat(user, "You haven't selected anything to transmit laws to!") - return - if(!can_upload_to(current)) - to_chat(user, "Upload failed! Check to make sure [current.name] is functioning properly.") - current = null - return - var/turf/currentloc = get_turf(current) - if(currentloc && user.z != currentloc.z) - to_chat(user, "Upload failed! Unable to establish a connection to [current.name]. You're too far away!") - current = null - return - M.install(current.laws, user) - else - return ..() - -/obj/machinery/computer/upload/proc/can_upload_to(mob/living/silicon/S) - if(S.stat == DEAD) - return FALSE - return TRUE - -/obj/machinery/computer/upload/ai - name = "\improper AI upload console" - desc = "Used to upload laws to the AI." - circuit = /obj/item/circuitboard/computer/aiupload - -/obj/machinery/computer/upload/ai/interact(mob/user) - current = select_active_ai(user, z) - - if (!current) - to_chat(user, "No active AIs detected!") - else - to_chat(user, "[current.name] selected for law changes.") - -/obj/machinery/computer/upload/ai/can_upload_to(mob/living/silicon/ai/A) - if(!A || !isAI(A)) - return FALSE - if(A.control_disabled) - return FALSE - return ..() - - -/obj/machinery/computer/upload/borg - name = "cyborg upload console" - desc = "Used to upload laws to Cyborgs." - circuit = /obj/item/circuitboard/computer/borgupload - -/obj/machinery/computer/upload/borg/interact(mob/user) - current = select_active_free_borg(user) - - if(!current) - to_chat(user, "No active unslaved cyborgs detected.") - else - to_chat(user, "[current.name] selected for law changes.") - -/obj/machinery/computer/upload/borg/can_upload_to(mob/living/silicon/robot/B) - if(!B || !iscyborg(B)) - return FALSE - if(B.scrambledcodes || B.emagged) - return FALSE - return ..() + + +/obj/machinery/computer/upload + var/mob/living/silicon/current = null //The target of future law uploads + icon_screen = "command" + time_to_screwdrive = 60 + +/obj/machinery/computer/upload/Initialize() + . = ..() + AddComponent(/datum/component/gps, "Encrypted Upload") + +/obj/machinery/computer/upload/attackby(obj/item/O, mob/user, params) + if(istype(O, /obj/item/aiModule)) + var/obj/item/aiModule/M = O + if(machine_stat & (NOPOWER|BROKEN|MAINT)) + return + if(!current) + to_chat(user, "You haven't selected anything to transmit laws to!") + return + if(!can_upload_to(current)) + to_chat(user, "Upload failed! Check to make sure [current.name] is functioning properly.") + current = null + return + var/turf/currentloc = get_turf(current) + if(currentloc && user.z != currentloc.z) + to_chat(user, "Upload failed! Unable to establish a connection to [current.name]. You're too far away!") + current = null + return + M.install(current.laws, user) + else + return ..() + +/obj/machinery/computer/upload/proc/can_upload_to(mob/living/silicon/S) + if(S.stat == DEAD) + return FALSE + return TRUE + +/obj/machinery/computer/upload/ai + name = "\improper AI upload console" + desc = "Used to upload laws to the AI." + circuit = /obj/item/circuitboard/computer/aiupload + +/obj/machinery/computer/upload/ai/interact(mob/user) + current = select_active_ai(user, z) + + if (!current) + to_chat(user, "No active AIs detected!") + else + to_chat(user, "[current.name] selected for law changes.") + +/obj/machinery/computer/upload/ai/can_upload_to(mob/living/silicon/ai/A) + if(!A || !isAI(A)) + return FALSE + if(A.control_disabled) + return FALSE + return ..() + + +/obj/machinery/computer/upload/borg + name = "cyborg upload console" + desc = "Used to upload laws to Cyborgs." + circuit = /obj/item/circuitboard/computer/borgupload + +/obj/machinery/computer/upload/borg/interact(mob/user) + current = select_active_free_borg(user) + + if(!current) + to_chat(user, "No active unslaved cyborgs detected.") + else + to_chat(user, "[current.name] selected for law changes.") + +/obj/machinery/computer/upload/borg/can_upload_to(mob/living/silicon/robot/B) + if(!B || !iscyborg(B)) + return FALSE + if(B.scrambledcodes || B.emagged) + return FALSE + return ..() diff --git a/code/game/machinery/computer/medical.dm b/code/game/machinery/computer/medical.dm index e039bdb735f2..7485dda30a3f 100644 --- a/code/game/machinery/computer/medical.dm +++ b/code/game/machinery/computer/medical.dm @@ -1,579 +1,579 @@ - - -/obj/machinery/computer/med_data//TODO:SANITY - name = "medical records console" - desc = "This can be used to check medical records." - icon_screen = "medcomp" - icon_keyboard = "med_key" - req_one_access = list(ACCESS_MEDICAL, ACCESS_FORENSICS_LOCKERS) - circuit = /obj/item/circuitboard/computer/med_data - var/rank = null - var/screen = null - var/datum/data/record/active1 - var/datum/data/record/active2 - var/temp = null - var/printing = null - //Sorting Variables - var/sortBy = "name" - var/order = 1 // -1 = Descending - 1 = Ascending - - light_color = LIGHT_COLOR_BLUE - -/obj/machinery/computer/med_data/syndie - icon_keyboard = "syndie_key" - -/obj/machinery/computer/med_data/ui_interact(mob/user) - . = ..() - if(isliving(user)) - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) - var/dat - if(temp) - dat = text("[temp]

    Clear Screen") - else - if(authenticated) - switch(screen) - if(1) - dat += {" -Search Records -
    List Records -
    -
    Virus Database -
    Medbot Tracking -
    -
    Record Maintenance -
    {Log Out}
    -"} - if(2) - dat += {" -

    - - - - -
    Records:
    - - - - - - - - -"} - - - if(!isnull(GLOB.data_core.general)) - for(var/datum/data/record/R in sortRecord(GLOB.data_core.general, sortBy, order)) - var/blood_type = "" - var/b_dna = "" - for(var/datum/data/record/E in GLOB.data_core.medical) - if((E.fields["name"] == R.fields["name"] && E.fields["id"] == R.fields["id"])) - blood_type = E.fields["blood_type"] - b_dna = E.fields["b_dna"] - var/background - - if(R.fields["m_stat"] == "*Insane*" || R.fields["p_stat"] == "*Deceased*") - background = "'background-color:#990000;'" - else if(R.fields["p_stat"] == "*Unconscious*" || R.fields["m_stat"] == "*Unstable*") - background = "'background-color:#CD6500;'" - else if(R.fields["p_stat"] == "Physically Unfit" || R.fields["m_stat"] == "*Watch*") - background = "'background-color:#3BB9FF;'" - else - background = "'background-color:#4F7529;'" - - dat += text("", background, R.fields["id"], R.fields["name"]) - dat += text("", R.fields["id"]) - dat += text("", R.fields["fingerprint"], b_dna) - dat += text("", blood_type) - dat += text("", R.fields["p_stat"]) - dat += text("", R.fields["m_stat"]) - dat += "
    NameIDFingerprints (F) | DNA UE (D)Blood TypePhysical StatusMental Status
    [][]F: []
    D: []
    [][][]

    " - dat += "
    Back" - if(3) - dat += "Records Maintenance
    \nBackup To Disk
    \nUpload From Disk
    \nDelete All Records
    \n
    \nBack" - if(4) - - dat += "" - if(active1 in GLOB.data_core.general) - if(istype(active1.fields["photo_front"], /obj/item/photo)) - var/obj/item/photo/P1 = active1.fields["photo_front"] - user << browse_rsc(P1.picture.picture_image, "photo_front") - if(istype(active1.fields["photo_side"], /obj/item/photo)) - var/obj/item/photo/P2 = active1.fields["photo_side"] - user << browse_rsc(P2.picture.picture_image, "photo_side") - dat += "" - dat += "" - dat += "" - dat += "" - dat += "" - dat += "" - dat += "" - dat += "" - dat += "" - dat += "" - else - dat += "" - - dat += "" - if(active2 in GLOB.data_core.medical) - dat += "" - dat += "" - dat += "" - dat += "" - dat += "" - dat += "" - dat += "" - dat += "" - dat += "" //(per disease info placed in log/comment section) - dat += "" - dat += "" - - dat += "" - var/counter = 1 - while(active2.fields[text("com_[]", counter)]) - dat += "" - counter++ - dat += "" - - dat += "" - else - dat += "" - dat += "" - dat += "" - dat += "" - dat += "
    Medical Record
    Name:[active1.fields["name"]]
    ID:[active1.fields["id"]]
    Gender: [active1.fields["gender"]] 
    Age: [active1.fields["age"]] 
    Species: [active1.fields["species"]] 
    Fingerprint: [active1.fields["fingerprint"]] 
    Physical Status: [active1.fields["p_stat"]] 
    Mental Status: [active1.fields["m_stat"]] 
    General Record Lost!

    Medical Data
    Blood Type: [active2.fields["blood_type"]] 
    DNA: [active2.fields["b_dna"]] 

    Minor Disabilities:

     [active2.fields["mi_dis"]] 
    Details: [active2.fields["mi_dis_d"]] 

    Major Disabilities:

     [active2.fields["ma_dis"]] 
    Details: [active2.fields["ma_dis_d"]] 

    Allergies:

     [active2.fields["alg"]] 
    Details: [active2.fields["alg_d"]] 

    Current Diseases:

     [active2.fields["cdi"]] 
    Details: [active2.fields["cdi_d"]] 

    Important Notes:

     [active2.fields["notes"]] 

    Comments/Log
    [active2.fields[text("com_[]", counter)]]
    Delete Entry
    Add Entry

    Delete Record (Medical Only)
    Medical Record Lost!

    New Record
    Print Record
    Back
    " - if(5) - dat += "
    Virus Database
    " - for(var/Dt in typesof(/datum/disease/)) - var/datum/disease/Dis = new Dt(0) - if(istype(Dis, /datum/disease/advance)) - continue // TODO (tm): Add advance diseases to the virus database which no one uses. - if(!Dis.desc) - continue - dat += "
    [Dis.name]" - dat += "
    Back" - if(6) - dat += "
    Medical Robot Monitor
    " - dat += "Back" - dat += "
    Medical Robots:" - var/bdat = null - for(var/mob/living/simple_animal/bot/medbot/M in GLOB.alive_mob_list) - if(M.z != z) - continue //only find medibots on the same z-level as the computer - var/turf/bl = get_turf(M) - if(bl) //if it can't find a turf for the medibot, then it probably shouldn't be showing up - bdat += "[M.name] - \[[bl.x],[bl.y]\] - [M.on ? "Online" : "Offline"]
    " - if(!bdat) - dat += "
    None detected
    " - else - dat += "
    [bdat]" - - else - else - dat += "{Log In}" - var/datum/browser/popup = new(user, "med_rec", "Medical Records Console", 600, 400) - popup.set_content(dat) - popup.set_title_image(user.browse_rsc_icon(icon, icon_state)) - popup.open() - -/obj/machinery/computer/med_data/Topic(href, href_list) - . = ..() - if(.) - return . - if(!(active1 in GLOB.data_core.general)) - active1 = null - if(!(active2 in GLOB.data_core.medical)) - active2 = null - - if(usr.contents.Find(src) || (in_range(src, usr) && isturf(loc)) || issilicon(usr) || IsAdminGhost(usr)) - usr.set_machine(src) - if(href_list["temp"]) - temp = null - else if(href_list["logout"]) - authenticated = null - screen = null - active1 = null - active2 = null - playsound(src, 'sound/machines/terminal_off.ogg', 50, FALSE) - else if(href_list["choice"]) - // SORTING! - if(href_list["choice"] == "Sorting") - // Reverse the order if clicked twice - if(sortBy == href_list["sort"]) - if(order == 1) - order = -1 - else - order = 1 - else - // New sorting order! - sortBy = href_list["sort"] - order = initial(order) - else if(href_list["login"]) - var/mob/M = usr - var/obj/item/card/id/I = M.get_idcard(TRUE) - if(issilicon(M)) - active1 = null - active2 = null - authenticated = 1 - rank = "AI" - screen = 1 - else if(IsAdminGhost(M)) - active1 = null - active2 = null - authenticated = 1 - rank = "Central Command" - screen = 1 - else if(istype(I) && check_access(I)) - active1 = null - active2 = null - authenticated = I.registered_name - rank = I.assignment - screen = 1 - else - to_chat(usr, "Unauthorized access.") - playsound(src, 'sound/machines/terminal_on.ogg', 50, FALSE) - if(authenticated) - if(href_list["screen"]) - screen = text2num(href_list["screen"]) - if(screen < 1) - screen = 1 - - active1 = null - active2 = null - - else if(href_list["vir"]) - var/type = href_list["vir"] - var/datum/disease/Dis = new type(0) - var/AfS = "" - for(var/mob/M in Dis.viable_mobtypes) - AfS += " [initial(M.name)];" - temp = {"Name: [Dis.name] -
    Number of stages: [Dis.max_stages] -
    Spread: [Dis.spread_text] Transmission -
    Possible Cure: [(Dis.cure_text||"none")] -
    Affected Lifeforms:[AfS] -
    -
    Notes: [Dis.desc] -
    -
    Severity: [Dis.severity]"} - - else if(href_list["del_all"]) - temp = "Are you sure you wish to delete all records?
    \n\tYes
    \n\tNo
    " - - else if(href_list["del_all2"]) - investigate_log("[key_name(usr)] has deleted all medical records.", INVESTIGATE_RECORDS) - GLOB.data_core.medical.Cut() - temp = "All records deleted." - - else if(href_list["field"]) - var/a1 = active1 - var/a2 = active2 - switch(href_list["field"]) - if("fingerprint") - if(active1) - var/t1 = stripped_input("Please input fingerprint hash:", "Med. records", active1.fields["fingerprint"], null) - if(!canUseMedicalRecordsConsole(usr, t1, a1)) - return - active1.fields["fingerprint"] = t1 - if("gender") - if(active1) - if(active1.fields["gender"] == "Male") - active1.fields["gender"] = "Female" - else if(active1.fields["gender"] == "Female") - active1.fields["gender"] = "Other" - else - active1.fields["gender"] = "Male" - if("age") - if(active1) - var/t1 = input("Please input age:", "Med. records", active1.fields["age"], null) as num - if(!canUseMedicalRecordsConsole(usr, t1, a1)) - return - active1.fields["age"] = t1 - if("species") - if(active1) - var/t1 = stripped_input("Please input species name", "Med. records", active1.fields["species"], null) - if(!canUseMedicalRecordsConsole(usr, t1, a1)) - return - active1.fields["species"] = t1 - if("mi_dis") - if(active2) - var/t1 = stripped_input("Please input minor disabilities list:", "Med. records", active2.fields["mi_dis"], null) - if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) - return - active2.fields["mi_dis"] = t1 - if("mi_dis_d") - if(active2) - var/t1 = stripped_input("Please summarize minor dis.:", "Med. records", active2.fields["mi_dis_d"], null) - if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) - return - active2.fields["mi_dis_d"] = t1 - if("ma_dis") - if(active2) - var/t1 = stripped_input("Please input major disabilities list:", "Med. records", active2.fields["ma_dis"], null) - if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) - return - active2.fields["ma_dis"] = t1 - if("ma_dis_d") - if(active2) - var/t1 = stripped_input("Please summarize major dis.:", "Med. records", active2.fields["ma_dis_d"], null) - if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) - return - active2.fields["ma_dis_d"] = t1 - if("alg") - if(active2) - var/t1 = stripped_input("Please state allergies:", "Med. records", active2.fields["alg"], null) - if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) - return - active2.fields["alg"] = t1 - if("alg_d") - if(active2) - var/t1 = stripped_input("Please summarize allergies:", "Med. records", active2.fields["alg_d"], null) - if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) - return - active2.fields["alg_d"] = t1 - if("cdi") - if(active2) - var/t1 = stripped_input("Please state diseases:", "Med. records", active2.fields["cdi"], null) - if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) - return - active2.fields["cdi"] = t1 - if("cdi_d") - if(active2) - var/t1 = stripped_input("Please summarize diseases:", "Med. records", active2.fields["cdi_d"], null) - if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) - return - active2.fields["cdi_d"] = t1 - if("notes") - if(active2) - var/t1 = stripped_input("Please summarize notes:", "Med. records", active2.fields["notes"], null) - if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) - return - active2.fields["notes"] = t1 - if("p_stat") - if(active1) - temp = "Physical Condition:
    \n\t*Deceased*
    \n\t*Unconscious*
    \n\tActive
    \n\tPhysically Unfit
    " - if("m_stat") - if(active1) - temp = "Mental Condition:
    \n\t*Insane*
    \n\t*Unstable*
    \n\t*Watch*
    \n\tStable
    " - if("blood_type") - if(active2) - temp = "Blood Type:
    \n\tA- A+
    \n\tB- B+
    \n\tAB- AB+
    \n\tO- O+
    " - if("b_dna") - if(active2) - var/t1 = stripped_input("Please input DNA hash:", "Med. records", active2.fields["b_dna"], null) - if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) - return - active2.fields["b_dna"] = t1 - if("show_photo_front") - if(active1) - if(active1.fields["photo_front"]) - if(istype(active1.fields["photo_front"], /obj/item/photo)) - var/obj/item/photo/P = active1.fields["photo_front"] - P.show(usr) - if("show_photo_side") - if(active1) - if(active1.fields["photo_side"]) - if(istype(active1.fields["photo_side"], /obj/item/photo)) - var/obj/item/photo/P = active1.fields["photo_side"] - P.show(usr) - else - - else if(href_list["p_stat"]) - if(active1) - switch(href_list["p_stat"]) - if("deceased") - active1.fields["p_stat"] = "*Deceased*" - if("unconscious") - active1.fields["p_stat"] = "*Unconscious*" - if("active") - active1.fields["p_stat"] = "Active" - if("unfit") - active1.fields["p_stat"] = "Physically Unfit" - - else if(href_list["m_stat"]) - if(active1) - switch(href_list["m_stat"]) - if("insane") - active1.fields["m_stat"] = "*Insane*" - if("unstable") - active1.fields["m_stat"] = "*Unstable*" - if("watch") - active1.fields["m_stat"] = "*Watch*" - if("stable") - active1.fields["m_stat"] = "Stable" - - - else if(href_list["blood_type"]) - if(active2) - switch(href_list["blood_type"]) - if("an") - active2.fields["blood_type"] = "A-" - if("bn") - active2.fields["blood_type"] = "B-" - if("abn") - active2.fields["blood_type"] = "AB-" - if("on") - active2.fields["blood_type"] = "O-" - if("ap") - active2.fields["blood_type"] = "A+" - if("bp") - active2.fields["blood_type"] = "B+" - if("abp") - active2.fields["blood_type"] = "AB+" - if("op") - active2.fields["blood_type"] = "O+" - - - else if(href_list["del_r"]) - if(active2) - temp = "Are you sure you wish to delete the record (Medical Portion Only)?
    \n\tYes
    \n\tNo
    " - - else if(href_list["del_r2"]) - investigate_log("[key_name(usr)] has deleted the medical records for [active1.fields["name"]].", INVESTIGATE_RECORDS) - if(active2) - qdel(active2) - active2 = null - - else if(href_list["d_rec"]) - active1 = find_record("id", href_list["d_rec"], GLOB.data_core.general) - if(active1) - active2 = find_record("id", href_list["d_rec"], GLOB.data_core.medical) - if(!active2) - active1 = null - screen = 4 - - else if(href_list["new"]) - if((istype(active1, /datum/data/record) && !( istype(active2, /datum/data/record) ))) - var/datum/data/record/R = new /datum/data/record( ) - R.fields["name"] = active1.fields["name"] - R.fields["id"] = active1.fields["id"] - R.name = text("Medical Record #[]", R.fields["id"]) - R.fields["blood_type"] = "Unknown" - R.fields["b_dna"] = "Unknown" - R.fields["mi_dis"] = "None" - R.fields["mi_dis_d"] = "No minor disabilities have been diagnosed." - R.fields["ma_dis"] = "None" - R.fields["ma_dis_d"] = "No major disabilities have been diagnosed." - R.fields["alg"] = "None" - R.fields["alg_d"] = "No allergies have been detected in this patient." - R.fields["cdi"] = "None" - R.fields["cdi_d"] = "No diseases have been diagnosed at the moment." - R.fields["notes"] = "No notes." - GLOB.data_core.medical += R - active2 = R - screen = 4 - - else if(href_list["add_c"]) - if(!(active2 in GLOB.data_core.medical)) - return - var/a2 = active2 - var/t1 = stripped_multiline_input("Add Comment:", "Med. records", null, null) - if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) - return - var/counter = 1 - while(active2.fields[text("com_[]", counter)]) - counter++ - active2.fields[text("com_[]", counter)] = text("Made by [] ([]) on [] [], []
    []", authenticated, rank, station_time_timestamp(), time2text(world.realtime, "MMM DD"), GLOB.year_integer+540, t1) - - else if(href_list["del_c"]) - if((istype(active2, /datum/data/record) && active2.fields[text("com_[]", href_list["del_c"])])) - active2.fields[text("com_[]", href_list["del_c"])] = "Deleted" - - else if(href_list["search"]) - var/t1 = stripped_input(usr, "Search String: (Name, DNA, or ID)", "Med. records") - if(!canUseMedicalRecordsConsole(usr, t1)) - return - active1 = null - active2 = null - t1 = lowertext(t1) - for(var/datum/data/record/R in GLOB.data_core.medical) - if((lowertext(R.fields["name"]) == t1 || t1 == lowertext(R.fields["id"]) || t1 == lowertext(R.fields["b_dna"]))) - active2 = R - else - //Foreach continue //goto(3229) - if(!( active2 )) - temp = text("Could not locate record [].", sanitize(t1)) - else - for(var/datum/data/record/E in GLOB.data_core.general) - if((E.fields["name"] == active2.fields["name"] || E.fields["id"] == active2.fields["id"])) - active1 = E - else - //Foreach continue //goto(3334) - screen = 4 - - else if(href_list["print_p"]) - if(!( printing )) - printing = 1 - GLOB.data_core.medicalPrintCount++ - playsound(loc, 'sound/items/poster_being_created.ogg', 100, TRUE) - sleep(30) - var/obj/item/paper/P = new /obj/item/paper( loc ) - P.info = "
    Medical Record - (MR-[GLOB.data_core.medicalPrintCount])

    " - if(active1 in GLOB.data_core.general) - P.info += text("Name: [] ID: []
    \nGender: []
    \nAge: []
    ", active1.fields["name"], active1.fields["id"], active1.fields["gender"], active1.fields["age"]) - P.info += "\nSpecies: [active1.fields["species"]]
    " - P.info += text("\nFingerprint: []
    \nPhysical Status: []
    \nMental Status: []
    ", active1.fields["fingerprint"], active1.fields["p_stat"], active1.fields["m_stat"]) - else - P.info += "General Record Lost!
    " - if(active2 in GLOB.data_core.medical) - P.info += text("
    \n
    Medical Data

    \nBlood Type: []
    \nDNA: []
    \n
    \nMinor Disabilities: []
    \nDetails: []
    \n
    \nMajor Disabilities: []
    \nDetails: []
    \n
    \nAllergies: []
    \nDetails: []
    \n
    \nCurrent Diseases: [] (per disease info placed in log/comment section)
    \nDetails: []
    \n
    \nImportant Notes:
    \n\t[]
    \n
    \n
    Comments/Log

    ", active2.fields["blood_type"], active2.fields["b_dna"], active2.fields["mi_dis"], active2.fields["mi_dis_d"], active2.fields["ma_dis"], active2.fields["ma_dis_d"], active2.fields["alg"], active2.fields["alg_d"], active2.fields["cdi"], active2.fields["cdi_d"], active2.fields["notes"]) - var/counter = 1 - while(active2.fields[text("com_[]", counter)]) - P.info += text("[]
    ", active2.fields[text("com_[]", counter)]) - counter++ - P.name = text("MR-[] '[]'", GLOB.data_core.medicalPrintCount, active1.fields["name"]) - else - P.info += "Medical Record Lost!
    " - P.name = text("MR-[] '[]'", GLOB.data_core.medicalPrintCount, "Record Lost") - P.info += "" - P.update_icon() - printing = null - - add_fingerprint(usr) - updateUsrDialog() - return - -/obj/machinery/computer/med_data/emp_act(severity) - . = ..() - if(!(machine_stat & (BROKEN|NOPOWER)) && !(. & EMP_PROTECT_SELF)) - for(var/datum/data/record/R in GLOB.data_core.medical) - if(prob(10/severity)) - switch(rand(1,6)) - if(1) - if(prob(10)) - R.fields["name"] = random_unique_lizard_name(R.fields["gender"],1) - else - R.fields["name"] = random_unique_name(R.fields["gender"],1) - if(2) - R.fields["gender"] = pick("Male", "Female", "Other") - if(3) - R.fields["age"] = rand(AGE_MIN, AGE_MAX) - if(4) - R.fields["blood_type"] = random_blood_type() - if(5) - R.fields["p_stat"] = pick("*Unconscious*", "Active", "Physically Unfit") - if(6) - R.fields["m_stat"] = pick("*Insane*", "*Unstable*", "*Watch*", "Stable") - continue - - else if(prob(1)) - qdel(R) - continue - -/obj/machinery/computer/med_data/proc/canUseMedicalRecordsConsole(mob/user, message = 1, record1, record2) - if(user) - if(message) - if(authenticated) - if(user.canUseTopic(src, !issilicon(user))) - if(!record1 || record1 == active1) - if(!record2 || record2 == active2) - return 1 - return 0 - -/obj/machinery/computer/med_data/laptop - name = "medical laptop" - desc = "A cheap Nanotrasen medical laptop, it functions as a medical records computer. It's bolted to the table." - icon_state = "laptop" - icon_screen = "medlaptop" - icon_keyboard = "laptop_key" - pass_flags = PASSTABLE + + +/obj/machinery/computer/med_data//TODO:SANITY + name = "medical records console" + desc = "This can be used to check medical records." + icon_screen = "medcomp" + icon_keyboard = "med_key" + req_one_access = list(ACCESS_MEDICAL, ACCESS_FORENSICS_LOCKERS) + circuit = /obj/item/circuitboard/computer/med_data + var/rank = null + var/screen = null + var/datum/data/record/active1 + var/datum/data/record/active2 + var/temp = null + var/printing = null + //Sorting Variables + var/sortBy = "name" + var/order = 1 // -1 = Descending - 1 = Ascending + + light_color = LIGHT_COLOR_BLUE + +/obj/machinery/computer/med_data/syndie + icon_keyboard = "syndie_key" + +/obj/machinery/computer/med_data/ui_interact(mob/user) + . = ..() + if(isliving(user)) + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) + var/dat + if(temp) + dat = text("[temp]

    Clear Screen") + else + if(authenticated) + switch(screen) + if(1) + dat += {" +Search Records +
    List Records +
    +
    Virus Database +
    Medbot Tracking +
    +
    Record Maintenance +
    {Log Out}
    +"} + if(2) + dat += {" +

    + + + + +
    Records:
    + + + + + + + + +"} + + + if(!isnull(GLOB.data_core.general)) + for(var/datum/data/record/R in sortRecord(GLOB.data_core.general, sortBy, order)) + var/blood_type = "" + var/b_dna = "" + for(var/datum/data/record/E in GLOB.data_core.medical) + if((E.fields["name"] == R.fields["name"] && E.fields["id"] == R.fields["id"])) + blood_type = E.fields["blood_type"] + b_dna = E.fields["b_dna"] + var/background + + if(R.fields["m_stat"] == "*Insane*" || R.fields["p_stat"] == "*Deceased*") + background = "'background-color:#990000;'" + else if(R.fields["p_stat"] == "*Unconscious*" || R.fields["m_stat"] == "*Unstable*") + background = "'background-color:#CD6500;'" + else if(R.fields["p_stat"] == "Physically Unfit" || R.fields["m_stat"] == "*Watch*") + background = "'background-color:#3BB9FF;'" + else + background = "'background-color:#4F7529;'" + + dat += text("", background, R.fields["id"], R.fields["name"]) + dat += text("", R.fields["id"]) + dat += text("", R.fields["fingerprint"], b_dna) + dat += text("", blood_type) + dat += text("", R.fields["p_stat"]) + dat += text("", R.fields["m_stat"]) + dat += "
    NameIDFingerprints (F) | DNA UE (D)Blood TypePhysical StatusMental Status
    [][]F: []
    D: []
    [][][]

    " + dat += "
    Back" + if(3) + dat += "Records Maintenance
    \nBackup To Disk
    \nUpload From Disk
    \nDelete All Records
    \n
    \nBack" + if(4) + + dat += "" + if(active1 in GLOB.data_core.general) + if(istype(active1.fields["photo_front"], /obj/item/photo)) + var/obj/item/photo/P1 = active1.fields["photo_front"] + user << browse_rsc(P1.picture.picture_image, "photo_front") + if(istype(active1.fields["photo_side"], /obj/item/photo)) + var/obj/item/photo/P2 = active1.fields["photo_side"] + user << browse_rsc(P2.picture.picture_image, "photo_side") + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + else + dat += "" + + dat += "" + if(active2 in GLOB.data_core.medical) + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" //(per disease info placed in log/comment section) + dat += "" + dat += "" + + dat += "" + var/counter = 1 + while(active2.fields[text("com_[]", counter)]) + dat += "" + counter++ + dat += "" + + dat += "" + else + dat += "" + dat += "" + dat += "" + dat += "" + dat += "
    Medical Record
    Name:[active1.fields["name"]]
    ID:[active1.fields["id"]]
    Gender: [active1.fields["gender"]] 
    Age: [active1.fields["age"]] 
    Species: [active1.fields["species"]] 
    Fingerprint: [active1.fields["fingerprint"]] 
    Physical Status: [active1.fields["p_stat"]] 
    Mental Status: [active1.fields["m_stat"]] 
    General Record Lost!

    Medical Data
    Blood Type: [active2.fields["blood_type"]] 
    DNA: [active2.fields["b_dna"]] 

    Minor Disabilities:

     [active2.fields["mi_dis"]] 
    Details: [active2.fields["mi_dis_d"]] 

    Major Disabilities:

     [active2.fields["ma_dis"]] 
    Details: [active2.fields["ma_dis_d"]] 

    Allergies:

     [active2.fields["alg"]] 
    Details: [active2.fields["alg_d"]] 

    Current Diseases:

     [active2.fields["cdi"]] 
    Details: [active2.fields["cdi_d"]] 

    Important Notes:

     [active2.fields["notes"]] 

    Comments/Log
    [active2.fields[text("com_[]", counter)]]
    Delete Entry
    Add Entry

    Delete Record (Medical Only)
    Medical Record Lost!

    New Record
    Print Record
    Back
    " + if(5) + dat += "
    Virus Database
    " + for(var/Dt in typesof(/datum/disease/)) + var/datum/disease/Dis = new Dt(0) + if(istype(Dis, /datum/disease/advance)) + continue // TODO (tm): Add advance diseases to the virus database which no one uses. + if(!Dis.desc) + continue + dat += "
    [Dis.name]" + dat += "
    Back" + if(6) + dat += "
    Medical Robot Monitor
    " + dat += "Back" + dat += "
    Medical Robots:" + var/bdat = null + for(var/mob/living/simple_animal/bot/medbot/M in GLOB.alive_mob_list) + if(M.z != z) + continue //only find medibots on the same z-level as the computer + var/turf/bl = get_turf(M) + if(bl) //if it can't find a turf for the medibot, then it probably shouldn't be showing up + bdat += "[M.name] - \[[bl.x],[bl.y]\] - [M.on ? "Online" : "Offline"]
    " + if(!bdat) + dat += "
    None detected
    " + else + dat += "
    [bdat]" + + else + else + dat += "{Log In}" + var/datum/browser/popup = new(user, "med_rec", "Medical Records Console", 600, 400) + popup.set_content(dat) + popup.set_title_image(user.browse_rsc_icon(icon, icon_state)) + popup.open() + +/obj/machinery/computer/med_data/Topic(href, href_list) + . = ..() + if(.) + return . + if(!(active1 in GLOB.data_core.general)) + active1 = null + if(!(active2 in GLOB.data_core.medical)) + active2 = null + + if(usr.contents.Find(src) || (in_range(src, usr) && isturf(loc)) || issilicon(usr) || IsAdminGhost(usr)) + usr.set_machine(src) + if(href_list["temp"]) + temp = null + else if(href_list["logout"]) + authenticated = null + screen = null + active1 = null + active2 = null + playsound(src, 'sound/machines/terminal_off.ogg', 50, FALSE) + else if(href_list["choice"]) + // SORTING! + if(href_list["choice"] == "Sorting") + // Reverse the order if clicked twice + if(sortBy == href_list["sort"]) + if(order == 1) + order = -1 + else + order = 1 + else + // New sorting order! + sortBy = href_list["sort"] + order = initial(order) + else if(href_list["login"]) + var/mob/M = usr + var/obj/item/card/id/I = M.get_idcard(TRUE) + if(issilicon(M)) + active1 = null + active2 = null + authenticated = 1 + rank = "AI" + screen = 1 + else if(IsAdminGhost(M)) + active1 = null + active2 = null + authenticated = 1 + rank = "Central Command" + screen = 1 + else if(istype(I) && check_access(I)) + active1 = null + active2 = null + authenticated = I.registered_name + rank = I.assignment + screen = 1 + else + to_chat(usr, "Unauthorized access.") + playsound(src, 'sound/machines/terminal_on.ogg', 50, FALSE) + if(authenticated) + if(href_list["screen"]) + screen = text2num(href_list["screen"]) + if(screen < 1) + screen = 1 + + active1 = null + active2 = null + + else if(href_list["vir"]) + var/type = href_list["vir"] + var/datum/disease/Dis = new type(0) + var/AfS = "" + for(var/mob/M in Dis.viable_mobtypes) + AfS += " [initial(M.name)];" + temp = {"Name: [Dis.name] +
    Number of stages: [Dis.max_stages] +
    Spread: [Dis.spread_text] Transmission +
    Possible Cure: [(Dis.cure_text||"none")] +
    Affected Lifeforms:[AfS] +
    +
    Notes: [Dis.desc] +
    +
    Severity: [Dis.severity]"} + + else if(href_list["del_all"]) + temp = "Are you sure you wish to delete all records?
    \n\tYes
    \n\tNo
    " + + else if(href_list["del_all2"]) + investigate_log("[key_name(usr)] has deleted all medical records.", INVESTIGATE_RECORDS) + GLOB.data_core.medical.Cut() + temp = "All records deleted." + + else if(href_list["field"]) + var/a1 = active1 + var/a2 = active2 + switch(href_list["field"]) + if("fingerprint") + if(active1) + var/t1 = stripped_input("Please input fingerprint hash:", "Med. records", active1.fields["fingerprint"], null) + if(!canUseMedicalRecordsConsole(usr, t1, a1)) + return + active1.fields["fingerprint"] = t1 + if("gender") + if(active1) + if(active1.fields["gender"] == "Male") + active1.fields["gender"] = "Female" + else if(active1.fields["gender"] == "Female") + active1.fields["gender"] = "Other" + else + active1.fields["gender"] = "Male" + if("age") + if(active1) + var/t1 = input("Please input age:", "Med. records", active1.fields["age"], null) as num + if(!canUseMedicalRecordsConsole(usr, t1, a1)) + return + active1.fields["age"] = t1 + if("species") + if(active1) + var/t1 = stripped_input("Please input species name", "Med. records", active1.fields["species"], null) + if(!canUseMedicalRecordsConsole(usr, t1, a1)) + return + active1.fields["species"] = t1 + if("mi_dis") + if(active2) + var/t1 = stripped_input("Please input minor disabilities list:", "Med. records", active2.fields["mi_dis"], null) + if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) + return + active2.fields["mi_dis"] = t1 + if("mi_dis_d") + if(active2) + var/t1 = stripped_input("Please summarize minor dis.:", "Med. records", active2.fields["mi_dis_d"], null) + if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) + return + active2.fields["mi_dis_d"] = t1 + if("ma_dis") + if(active2) + var/t1 = stripped_input("Please input major disabilities list:", "Med. records", active2.fields["ma_dis"], null) + if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) + return + active2.fields["ma_dis"] = t1 + if("ma_dis_d") + if(active2) + var/t1 = stripped_input("Please summarize major dis.:", "Med. records", active2.fields["ma_dis_d"], null) + if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) + return + active2.fields["ma_dis_d"] = t1 + if("alg") + if(active2) + var/t1 = stripped_input("Please state allergies:", "Med. records", active2.fields["alg"], null) + if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) + return + active2.fields["alg"] = t1 + if("alg_d") + if(active2) + var/t1 = stripped_input("Please summarize allergies:", "Med. records", active2.fields["alg_d"], null) + if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) + return + active2.fields["alg_d"] = t1 + if("cdi") + if(active2) + var/t1 = stripped_input("Please state diseases:", "Med. records", active2.fields["cdi"], null) + if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) + return + active2.fields["cdi"] = t1 + if("cdi_d") + if(active2) + var/t1 = stripped_input("Please summarize diseases:", "Med. records", active2.fields["cdi_d"], null) + if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) + return + active2.fields["cdi_d"] = t1 + if("notes") + if(active2) + var/t1 = stripped_input("Please summarize notes:", "Med. records", active2.fields["notes"], null) + if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) + return + active2.fields["notes"] = t1 + if("p_stat") + if(active1) + temp = "Physical Condition:
    \n\t*Deceased*
    \n\t*Unconscious*
    \n\tActive
    \n\tPhysically Unfit
    " + if("m_stat") + if(active1) + temp = "Mental Condition:
    \n\t*Insane*
    \n\t*Unstable*
    \n\t*Watch*
    \n\tStable
    " + if("blood_type") + if(active2) + temp = "Blood Type:
    \n\tA- A+
    \n\tB- B+
    \n\tAB- AB+
    \n\tO- O+
    " + if("b_dna") + if(active2) + var/t1 = stripped_input("Please input DNA hash:", "Med. records", active2.fields["b_dna"], null) + if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) + return + active2.fields["b_dna"] = t1 + if("show_photo_front") + if(active1) + if(active1.fields["photo_front"]) + if(istype(active1.fields["photo_front"], /obj/item/photo)) + var/obj/item/photo/P = active1.fields["photo_front"] + P.show(usr) + if("show_photo_side") + if(active1) + if(active1.fields["photo_side"]) + if(istype(active1.fields["photo_side"], /obj/item/photo)) + var/obj/item/photo/P = active1.fields["photo_side"] + P.show(usr) + else + + else if(href_list["p_stat"]) + if(active1) + switch(href_list["p_stat"]) + if("deceased") + active1.fields["p_stat"] = "*Deceased*" + if("unconscious") + active1.fields["p_stat"] = "*Unconscious*" + if("active") + active1.fields["p_stat"] = "Active" + if("unfit") + active1.fields["p_stat"] = "Physically Unfit" + + else if(href_list["m_stat"]) + if(active1) + switch(href_list["m_stat"]) + if("insane") + active1.fields["m_stat"] = "*Insane*" + if("unstable") + active1.fields["m_stat"] = "*Unstable*" + if("watch") + active1.fields["m_stat"] = "*Watch*" + if("stable") + active1.fields["m_stat"] = "Stable" + + + else if(href_list["blood_type"]) + if(active2) + switch(href_list["blood_type"]) + if("an") + active2.fields["blood_type"] = "A-" + if("bn") + active2.fields["blood_type"] = "B-" + if("abn") + active2.fields["blood_type"] = "AB-" + if("on") + active2.fields["blood_type"] = "O-" + if("ap") + active2.fields["blood_type"] = "A+" + if("bp") + active2.fields["blood_type"] = "B+" + if("abp") + active2.fields["blood_type"] = "AB+" + if("op") + active2.fields["blood_type"] = "O+" + + + else if(href_list["del_r"]) + if(active2) + temp = "Are you sure you wish to delete the record (Medical Portion Only)?
    \n\tYes
    \n\tNo
    " + + else if(href_list["del_r2"]) + investigate_log("[key_name(usr)] has deleted the medical records for [active1.fields["name"]].", INVESTIGATE_RECORDS) + if(active2) + qdel(active2) + active2 = null + + else if(href_list["d_rec"]) + active1 = find_record("id", href_list["d_rec"], GLOB.data_core.general) + if(active1) + active2 = find_record("id", href_list["d_rec"], GLOB.data_core.medical) + if(!active2) + active1 = null + screen = 4 + + else if(href_list["new"]) + if((istype(active1, /datum/data/record) && !( istype(active2, /datum/data/record) ))) + var/datum/data/record/R = new /datum/data/record( ) + R.fields["name"] = active1.fields["name"] + R.fields["id"] = active1.fields["id"] + R.name = text("Medical Record #[]", R.fields["id"]) + R.fields["blood_type"] = "Unknown" + R.fields["b_dna"] = "Unknown" + R.fields["mi_dis"] = "None" + R.fields["mi_dis_d"] = "No minor disabilities have been diagnosed." + R.fields["ma_dis"] = "None" + R.fields["ma_dis_d"] = "No major disabilities have been diagnosed." + R.fields["alg"] = "None" + R.fields["alg_d"] = "No allergies have been detected in this patient." + R.fields["cdi"] = "None" + R.fields["cdi_d"] = "No diseases have been diagnosed at the moment." + R.fields["notes"] = "No notes." + GLOB.data_core.medical += R + active2 = R + screen = 4 + + else if(href_list["add_c"]) + if(!(active2 in GLOB.data_core.medical)) + return + var/a2 = active2 + var/t1 = stripped_multiline_input("Add Comment:", "Med. records", null, null) + if(!canUseMedicalRecordsConsole(usr, t1, null, a2)) + return + var/counter = 1 + while(active2.fields[text("com_[]", counter)]) + counter++ + active2.fields[text("com_[]", counter)] = text("Made by [] ([]) on [] [], []
    []", authenticated, rank, station_time_timestamp(), time2text(world.realtime, "MMM DD"), GLOB.year_integer+540, t1) + + else if(href_list["del_c"]) + if((istype(active2, /datum/data/record) && active2.fields[text("com_[]", href_list["del_c"])])) + active2.fields[text("com_[]", href_list["del_c"])] = "Deleted" + + else if(href_list["search"]) + var/t1 = stripped_input(usr, "Search String: (Name, DNA, or ID)", "Med. records") + if(!canUseMedicalRecordsConsole(usr, t1)) + return + active1 = null + active2 = null + t1 = lowertext(t1) + for(var/datum/data/record/R in GLOB.data_core.medical) + if((lowertext(R.fields["name"]) == t1 || t1 == lowertext(R.fields["id"]) || t1 == lowertext(R.fields["b_dna"]))) + active2 = R + else + //Foreach continue //goto(3229) + if(!( active2 )) + temp = text("Could not locate record [].", sanitize(t1)) + else + for(var/datum/data/record/E in GLOB.data_core.general) + if((E.fields["name"] == active2.fields["name"] || E.fields["id"] == active2.fields["id"])) + active1 = E + else + //Foreach continue //goto(3334) + screen = 4 + + else if(href_list["print_p"]) + if(!( printing )) + printing = 1 + GLOB.data_core.medicalPrintCount++ + playsound(loc, 'sound/items/poster_being_created.ogg', 100, TRUE) + sleep(30) + var/obj/item/paper/P = new /obj/item/paper( loc ) + P.info = "
    Medical Record - (MR-[GLOB.data_core.medicalPrintCount])

    " + if(active1 in GLOB.data_core.general) + P.info += text("Name: [] ID: []
    \nGender: []
    \nAge: []
    ", active1.fields["name"], active1.fields["id"], active1.fields["gender"], active1.fields["age"]) + P.info += "\nSpecies: [active1.fields["species"]]
    " + P.info += text("\nFingerprint: []
    \nPhysical Status: []
    \nMental Status: []
    ", active1.fields["fingerprint"], active1.fields["p_stat"], active1.fields["m_stat"]) + else + P.info += "General Record Lost!
    " + if(active2 in GLOB.data_core.medical) + P.info += text("
    \n
    Medical Data

    \nBlood Type: []
    \nDNA: []
    \n
    \nMinor Disabilities: []
    \nDetails: []
    \n
    \nMajor Disabilities: []
    \nDetails: []
    \n
    \nAllergies: []
    \nDetails: []
    \n
    \nCurrent Diseases: [] (per disease info placed in log/comment section)
    \nDetails: []
    \n
    \nImportant Notes:
    \n\t[]
    \n
    \n
    Comments/Log

    ", active2.fields["blood_type"], active2.fields["b_dna"], active2.fields["mi_dis"], active2.fields["mi_dis_d"], active2.fields["ma_dis"], active2.fields["ma_dis_d"], active2.fields["alg"], active2.fields["alg_d"], active2.fields["cdi"], active2.fields["cdi_d"], active2.fields["notes"]) + var/counter = 1 + while(active2.fields[text("com_[]", counter)]) + P.info += text("[]
    ", active2.fields[text("com_[]", counter)]) + counter++ + P.name = text("MR-[] '[]'", GLOB.data_core.medicalPrintCount, active1.fields["name"]) + else + P.info += "Medical Record Lost!
    " + P.name = text("MR-[] '[]'", GLOB.data_core.medicalPrintCount, "Record Lost") + P.info += "" + P.update_icon() + printing = null + + add_fingerprint(usr) + updateUsrDialog() + return + +/obj/machinery/computer/med_data/emp_act(severity) + . = ..() + if(!(machine_stat & (BROKEN|NOPOWER)) && !(. & EMP_PROTECT_SELF)) + for(var/datum/data/record/R in GLOB.data_core.medical) + if(prob(10/severity)) + switch(rand(1,6)) + if(1) + if(prob(10)) + R.fields["name"] = random_unique_lizard_name(R.fields["gender"],1) + else + R.fields["name"] = random_unique_name(R.fields["gender"],1) + if(2) + R.fields["gender"] = pick("Male", "Female", "Other") + if(3) + R.fields["age"] = rand(AGE_MIN, AGE_MAX) + if(4) + R.fields["blood_type"] = random_blood_type() + if(5) + R.fields["p_stat"] = pick("*Unconscious*", "Active", "Physically Unfit") + if(6) + R.fields["m_stat"] = pick("*Insane*", "*Unstable*", "*Watch*", "Stable") + continue + + else if(prob(1)) + qdel(R) + continue + +/obj/machinery/computer/med_data/proc/canUseMedicalRecordsConsole(mob/user, message = 1, record1, record2) + if(user) + if(message) + if(authenticated) + if(user.canUseTopic(src, !issilicon(user))) + if(!record1 || record1 == active1) + if(!record2 || record2 == active2) + return 1 + return 0 + +/obj/machinery/computer/med_data/laptop + name = "medical laptop" + desc = "A cheap Nanotrasen medical laptop, it functions as a medical records computer. It's bolted to the table." + icon_state = "laptop" + icon_screen = "medlaptop" + icon_keyboard = "laptop_key" + pass_flags = PASSTABLE diff --git a/code/game/machinery/computer/pod.dm b/code/game/machinery/computer/pod.dm index 59a56dedf4ea..c8de903e8b41 100644 --- a/code/game/machinery/computer/pod.dm +++ b/code/game/machinery/computer/pod.dm @@ -1,133 +1,133 @@ -/obj/machinery/computer/pod - name = "mass driver launch control" - desc = "A combined blastdoor and mass driver control unit." - var/obj/machinery/mass_driver/connected = null - var/title = "Mass Driver Controls" - var/id = 1 - var/timing = 0 - var/time = 30 - var/range = 4 - - -/obj/machinery/computer/pod/Initialize() - . = ..() - for(var/obj/machinery/mass_driver/M in range(range, src)) - if(M.id == id) - connected = M - - -/obj/machinery/computer/pod/proc/alarm() - if(machine_stat & (NOPOWER|BROKEN)) - return - - if(!connected) - say("Cannot locate mass driver connector. Cancelling firing sequence!") - return - - for(var/obj/machinery/door/poddoor/M in range(range, src)) - if(M.id == id) - M.open() - - sleep(20) - for(var/obj/machinery/mass_driver/M in range(range, src)) - if(M.id == id) - M.power = connected.power - M.drive() - - sleep(50) - for(var/obj/machinery/door/poddoor/M in range(range, src)) - if(M.id == id) - M.close() - -/obj/machinery/computer/pod/ui_interact(mob/user) - . = ..() - if(!allowed(user)) - to_chat(user, "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 SECONDS))] - - + +" - 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] " - 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.set_title_image(user.browse_rsc_icon(icon, icon_state)) - 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)) || issilicon(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"]) - 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"]) - for(var/obj/machinery/mass_driver/M in range(range, src)) - if(M.id == id) - M.power = connected.power - M.drive() - updateUsrDialog() - -/obj/machinery/computer/pod/old - name = "\improper DoorMex control console" - title = "Door Controls" - icon_state = "oldcomp" - icon_screen = "library" - icon_keyboard = null - -/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 - name = "\improper Magix System IV" - desc = "An arcane artifact that holds much magic. Running E-Knock 2.2: Sorcerer's Edition." +/obj/machinery/computer/pod + name = "mass driver launch control" + desc = "A combined blastdoor and mass driver control unit." + var/obj/machinery/mass_driver/connected = null + var/title = "Mass Driver Controls" + var/id = 1 + var/timing = 0 + var/time = 30 + var/range = 4 + + +/obj/machinery/computer/pod/Initialize() + . = ..() + for(var/obj/machinery/mass_driver/M in range(range, src)) + if(M.id == id) + connected = M + + +/obj/machinery/computer/pod/proc/alarm() + if(machine_stat & (NOPOWER|BROKEN)) + return + + if(!connected) + say("Cannot locate mass driver connector. Cancelling firing sequence!") + return + + for(var/obj/machinery/door/poddoor/M in range(range, src)) + if(M.id == id) + M.open() + + sleep(20) + for(var/obj/machinery/mass_driver/M in range(range, src)) + if(M.id == id) + M.power = connected.power + M.drive() + + sleep(50) + for(var/obj/machinery/door/poddoor/M in range(range, src)) + if(M.id == id) + M.close() + +/obj/machinery/computer/pod/ui_interact(mob/user) + . = ..() + if(!allowed(user)) + to_chat(user, "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 SECONDS))] - - + +" + 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] " + 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.set_title_image(user.browse_rsc_icon(icon, icon_state)) + 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)) || issilicon(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"]) + 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"]) + for(var/obj/machinery/mass_driver/M in range(range, src)) + if(M.id == id) + M.power = connected.power + M.drive() + updateUsrDialog() + +/obj/machinery/computer/pod/old + name = "\improper DoorMex control console" + title = "Door Controls" + icon_state = "oldcomp" + icon_screen = "library" + icon_keyboard = null + +/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 + name = "\improper Magix System IV" + desc = "An arcane artifact that holds much magic. Running E-Knock 2.2: Sorcerer's Edition." diff --git a/code/game/machinery/computer/prisoner/gulag_teleporter.dm b/code/game/machinery/computer/prisoner/gulag_teleporter.dm index 6cb6a1d34ed6..0a8d0dbb2b06 100644 --- a/code/game/machinery/computer/prisoner/gulag_teleporter.dm +++ b/code/game/machinery/computer/prisoner/gulag_teleporter.dm @@ -6,8 +6,6 @@ icon_keyboard = "security_key" req_access = list(ACCESS_ARMORY) circuit = /obj/item/circuitboard/computer/gulag_teleporter_console - ui_x = 350 - ui_y = 295 var/default_goal = 200 var/obj/machinery/gulag_teleporter/teleporter = null @@ -21,11 +19,10 @@ . = ..() scan_machinery() -/obj/machinery/computer/prisoner/gulag_teleporter_computer/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/machinery/computer/prisoner/gulag_teleporter_computer/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "GulagTeleporterConsole", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, "GulagTeleporterConsole", name) ui.open() /obj/machinery/computer/prisoner/gulag_teleporter_computer/ui_data(mob/user) diff --git a/code/game/machinery/computer/robot.dm b/code/game/machinery/computer/robot.dm index 115b827e54e2..d30b27b9184a 100644 --- a/code/game/machinery/computer/robot.dm +++ b/code/game/machinery/computer/robot.dm @@ -1,127 +1,124 @@ -/obj/machinery/computer/robotics - name = "robotics control console" - desc = "Used to remotely lockdown or detonate linked Cyborgs and Drones." - icon_screen = "robot" - icon_keyboard = "rd_key" - req_access = list(ACCESS_ROBOTICS) - circuit = /obj/item/circuitboard/computer/robotics - light_color = LIGHT_COLOR_PINK - ui_x = 500 - ui_y = 460 - -/obj/machinery/computer/robotics/proc/can_control(mob/user, mob/living/silicon/robot/R) - . = FALSE - if(!istype(R)) - return - if(isAI(user)) - if(R.connected_ai != user) - return - if(iscyborg(user)) - if(R != user) - return - if(R.scrambledcodes) - return - return TRUE - -/obj/machinery/computer/robotics/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "RoboticsControlConsole", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/machinery/computer/robotics/ui_data(mob/user) - var/list/data = list() - - data["can_hack"] = FALSE - if(issilicon(user)) - var/mob/living/silicon/S = user - if(S.hack_software) - data["can_hack"] = TRUE - else if(IsAdminGhost(user)) - data["can_hack"] = TRUE - - data["cyborgs"] = list() - for(var/mob/living/silicon/robot/R in GLOB.silicon_mobs) - if(!can_control(user, R)) - continue - if(z != (get_turf(R)).z) - continue - var/list/cyborg_data = list( - name = R.name, - locked_down = R.lockcharge, - status = R.stat, - charge = R.cell ? round(R.cell.percent()) : null, - module = R.module ? "[R.module.name] Module" : "No Module Detected", - synchronization = R.connected_ai, - emagged = R.emagged, - ref = REF(R) - ) - data["cyborgs"] += list(cyborg_data) - - data["drones"] = list() - for(var/mob/living/simple_animal/drone/D in GLOB.drones_list) - if(D.hacked) - continue - if(z != (get_turf(D)).z) - continue - var/list/drone_data = list( - name = D.name, - status = D.stat, - ref = REF(D) - ) - data["drones"] += list(drone_data) - - return data - -/obj/machinery/computer/robotics/ui_act(action, params) - if(..()) - return - - switch(action) - if("killbot") - if(allowed(usr)) - var/mob/living/silicon/robot/R = locate(params["ref"]) in GLOB.silicon_mobs - if(can_control(usr, R) && !..()) - var/turf/T = get_turf(R) - message_admins("[ADMIN_LOOKUPFLW(usr)] detonated [key_name_admin(R, R.client)] at [ADMIN_VERBOSEJMP(T)]!") - log_game("\[key_name(usr)] detonated [key_name(R)]!") - if(R.connected_ai) - to_chat(R.connected_ai, "

    ALERT - Cyborg detonation detected: [R.name]
    ") - R.self_destruct() - else - to_chat(usr, "Access Denied.") - if("stopbot") - if(allowed(usr)) - var/mob/living/silicon/robot/R = locate(params["ref"]) in GLOB.silicon_mobs - if(can_control(usr, R) && !..()) - message_admins("[ADMIN_LOOKUPFLW(usr)] [!R.lockcharge ? "locked down" : "released"] [ADMIN_LOOKUPFLW(R)]!") - log_game("[key_name(usr)] [!R.lockcharge ? "locked down" : "released"] [key_name(R)]!") - R.SetLockdown(!R.lockcharge) - to_chat(R, "[!R.lockcharge ? "Your lockdown has been lifted!" : "You have been locked down!"]") - if(R.connected_ai) - to_chat(R.connected_ai, "[!R.lockcharge ? "NOTICE - Cyborg lockdown lifted" : "ALERT - Cyborg lockdown detected"]: [R.name]
    ") - else - to_chat(usr, "Access Denied.") - if("magbot") - var/mob/living/silicon/S = usr - if((istype(S) && S.hack_software) || IsAdminGhost(usr)) - var/mob/living/silicon/robot/R = locate(params["ref"]) in GLOB.silicon_mobs - if(istype(R) && !R.emagged && (R.connected_ai == usr || IsAdminGhost(usr)) && !R.scrambledcodes && can_control(usr, R)) - log_game("[key_name(usr)] emagged [key_name(R)] using robotic console!") - message_admins("[ADMIN_LOOKUPFLW(usr)] emagged cyborg [key_name_admin(R)] using robotic console!") - R.SetEmagged(TRUE) - if("killdrone") - if(allowed(usr)) - var/mob/living/simple_animal/drone/D = locate(params["ref"]) in GLOB.mob_list - if(D.hacked) - to_chat(usr, "ERROR: [D] is not responding to external commands.") - else - var/turf/T = get_turf(D) - message_admins("[ADMIN_LOOKUPFLW(usr)] detonated [key_name_admin(D)] at [ADMIN_VERBOSEJMP(T)]!") - log_game("[key_name(usr)] detonated [key_name(D)]!") - var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread - s.set_up(3, TRUE, D) - s.start() - D.visible_message("\the [D] self destructs!") - D.gib() +/obj/machinery/computer/robotics + name = "robotics control console" + desc = "Used to remotely lockdown or detonate linked Cyborgs and Drones." + icon_screen = "robot" + icon_keyboard = "rd_key" + req_access = list(ACCESS_ROBOTICS) + circuit = /obj/item/circuitboard/computer/robotics + light_color = LIGHT_COLOR_PINK + +/obj/machinery/computer/robotics/proc/can_control(mob/user, mob/living/silicon/robot/R) + . = FALSE + if(!istype(R)) + return + if(isAI(user)) + if(R.connected_ai != user) + return + if(iscyborg(user)) + if(R != user) + return + if(R.scrambledcodes) + return + return TRUE + +/obj/machinery/computer/robotics/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "RoboticsControlConsole", name) + ui.open() + +/obj/machinery/computer/robotics/ui_data(mob/user) + var/list/data = list() + + data["can_hack"] = FALSE + if(issilicon(user)) + var/mob/living/silicon/S = user + if(S.hack_software) + data["can_hack"] = TRUE + else if(IsAdminGhost(user)) + data["can_hack"] = TRUE + + data["cyborgs"] = list() + for(var/mob/living/silicon/robot/R in GLOB.silicon_mobs) + if(!can_control(user, R)) + continue + if(z != (get_turf(R)).z) + continue + var/list/cyborg_data = list( + name = R.name, + locked_down = R.lockcharge, + status = R.stat, + charge = R.cell ? round(R.cell.percent()) : null, + module = R.module ? "[R.module.name] Module" : "No Module Detected", + synchronization = R.connected_ai, + emagged = R.emagged, + ref = REF(R) + ) + data["cyborgs"] += list(cyborg_data) + + data["drones"] = list() + for(var/mob/living/simple_animal/drone/D in GLOB.drones_list) + if(D.hacked) + continue + if(z != (get_turf(D)).z) + continue + var/list/drone_data = list( + name = D.name, + status = D.stat, + ref = REF(D) + ) + data["drones"] += list(drone_data) + + return data + +/obj/machinery/computer/robotics/ui_act(action, params) + if(..()) + return + + switch(action) + if("killbot") + if(allowed(usr)) + var/mob/living/silicon/robot/R = locate(params["ref"]) in GLOB.silicon_mobs + if(can_control(usr, R) && !..()) + var/turf/T = get_turf(R) + message_admins("[ADMIN_LOOKUPFLW(usr)] detonated [key_name_admin(R, R.client)] at [ADMIN_VERBOSEJMP(T)]!") + log_game("\[key_name(usr)] detonated [key_name(R)]!") + if(R.connected_ai) + to_chat(R.connected_ai, "

    ALERT - Cyborg detonation detected: [R.name]
    ") + R.self_destruct() + else + to_chat(usr, "Access Denied.") + if("stopbot") + if(allowed(usr)) + var/mob/living/silicon/robot/R = locate(params["ref"]) in GLOB.silicon_mobs + if(can_control(usr, R) && !..()) + message_admins("[ADMIN_LOOKUPFLW(usr)] [!R.lockcharge ? "locked down" : "released"] [ADMIN_LOOKUPFLW(R)]!") + log_game("[key_name(usr)] [!R.lockcharge ? "locked down" : "released"] [key_name(R)]!") + R.SetLockdown(!R.lockcharge) + to_chat(R, "[!R.lockcharge ? "Your lockdown has been lifted!" : "You have been locked down!"]") + if(R.connected_ai) + to_chat(R.connected_ai, "[!R.lockcharge ? "NOTICE - Cyborg lockdown lifted" : "ALERT - Cyborg lockdown detected"]: [R.name]
    ") + else + to_chat(usr, "Access Denied.") + if("magbot") + var/mob/living/silicon/S = usr + if((istype(S) && S.hack_software) || IsAdminGhost(usr)) + var/mob/living/silicon/robot/R = locate(params["ref"]) in GLOB.silicon_mobs + if(istype(R) && !R.emagged && (R.connected_ai == usr || IsAdminGhost(usr)) && !R.scrambledcodes && can_control(usr, R)) + log_game("[key_name(usr)] emagged [key_name(R)] using robotic console!") + message_admins("[ADMIN_LOOKUPFLW(usr)] emagged cyborg [key_name_admin(R)] using robotic console!") + R.SetEmagged(TRUE) + if("killdrone") + if(allowed(usr)) + var/mob/living/simple_animal/drone/D = locate(params["ref"]) in GLOB.mob_list + if(D.hacked) + to_chat(usr, "ERROR: [D] is not responding to external commands.") + else + var/turf/T = get_turf(D) + message_admins("[ADMIN_LOOKUPFLW(usr)] detonated [key_name_admin(D)] at [ADMIN_VERBOSEJMP(T)]!") + log_game("[key_name(usr)] detonated [key_name(D)]!") + var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread + s.set_up(3, TRUE, D) + s.start() + D.visible_message("\the [D] self destructs!") + D.gib() diff --git a/code/game/machinery/computer/station_alert.dm b/code/game/machinery/computer/station_alert.dm index ab42b16d8aed..839d339e5a73 100644 --- a/code/game/machinery/computer/station_alert.dm +++ b/code/game/machinery/computer/station_alert.dm @@ -1,91 +1,88 @@ -/obj/machinery/computer/station_alert - name = "station alert console" - desc = "Used to access the station's automated alert system." - icon_screen = "alert:0" - icon_keyboard = "atmos_key" - circuit = /obj/item/circuitboard/computer/stationalert - ui_x = 325 - ui_y = 500 - var/alarms = list("Fire" = list(), "Atmosphere" = list(), "Power" = list()) - - light_color = LIGHT_COLOR_CYAN - -/obj/machinery/computer/station_alert/Initialize() - . = ..() - GLOB.alert_consoles += src - -/obj/machinery/computer/station_alert/Destroy() - GLOB.alert_consoles -= src - return ..() - -/obj/machinery/computer/station_alert/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "StationAlertConsole", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/machinery/computer/station_alert/ui_data(mob/user) - var/list/data = list() - - data["alarms"] = list() - for(var/class in alarms) - data["alarms"][class] = list() - for(var/area in alarms[class]) - data["alarms"][class] += area - - return data - -/obj/machinery/computer/station_alert/proc/triggerAlarm(class, area/A, O, obj/source) - if(source.z != z) - return - if(machine_stat & (BROKEN)) - return - - var/list/L = alarms[class] - for(var/I in L) - if (I == A.name) - var/list/alarm = L[I] - var/list/sources = alarm[3] - if (!(source in sources)) - sources += source - return 1 - var/obj/machinery/camera/C = null - var/list/CL = null - if(O && islist(O)) - CL = O - if (CL.len == 1) - C = CL[1] - else if(O && istype(O, /obj/machinery/camera)) - C = O - L[A.name] = list(A, (C ? C : O), list(source)) - return 1 - - -/obj/machinery/computer/station_alert/proc/cancelAlarm(class, area/A, obj/origin) - if(machine_stat & (BROKEN)) - return - var/list/L = alarms[class] - var/cleared = 0 - for (var/I in L) - if (I == A.name) - var/list/alarm = L[I] - var/list/srcs = alarm[3] - if (origin in srcs) - srcs -= origin - if (srcs.len == 0) - cleared = 1 - L -= I - return !cleared - -/obj/machinery/computer/station_alert/update_overlays() - . = ..() - if(machine_stat & (NOPOWER|BROKEN)) - return - var/active_alarms = FALSE - for(var/cat in alarms) - var/list/L = alarms[cat] - if(L.len) - active_alarms = TRUE - if(active_alarms) - . += "alert:2" +/obj/machinery/computer/station_alert + name = "station alert console" + desc = "Used to access the station's automated alert system." + icon_screen = "alert:0" + icon_keyboard = "atmos_key" + circuit = /obj/item/circuitboard/computer/stationalert + var/alarms = list("Fire" = list(), "Atmosphere" = list(), "Power" = list()) + + light_color = LIGHT_COLOR_CYAN + +/obj/machinery/computer/station_alert/Initialize() + . = ..() + GLOB.alert_consoles += src + +/obj/machinery/computer/station_alert/Destroy() + GLOB.alert_consoles -= src + return ..() + +/obj/machinery/computer/station_alert/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "StationAlertConsole", name) + ui.open() + +/obj/machinery/computer/station_alert/ui_data(mob/user) + var/list/data = list() + + data["alarms"] = list() + for(var/class in alarms) + data["alarms"][class] = list() + for(var/area in alarms[class]) + data["alarms"][class] += area + + return data + +/obj/machinery/computer/station_alert/proc/triggerAlarm(class, area/A, O, obj/source) + if(source.z != z) + return + if(machine_stat & (BROKEN)) + return + + var/list/L = alarms[class] + for(var/I in L) + if (I == A.name) + var/list/alarm = L[I] + var/list/sources = alarm[3] + if (!(source in sources)) + sources += source + return 1 + var/obj/machinery/camera/C = null + var/list/CL = null + if(O && islist(O)) + CL = O + if (CL.len == 1) + C = CL[1] + else if(O && istype(O, /obj/machinery/camera)) + C = O + L[A.name] = list(A, (C ? C : O), list(source)) + return 1 + + +/obj/machinery/computer/station_alert/proc/cancelAlarm(class, area/A, obj/origin) + if(machine_stat & (BROKEN)) + return + var/list/L = alarms[class] + var/cleared = 0 + for (var/I in L) + if (I == A.name) + var/list/alarm = L[I] + var/list/srcs = alarm[3] + if (origin in srcs) + srcs -= origin + if (srcs.len == 0) + cleared = 1 + L -= I + return !cleared + +/obj/machinery/computer/station_alert/update_overlays() + . = ..() + if(machine_stat & (NOPOWER|BROKEN)) + return + var/active_alarms = FALSE + for(var/cat in alarms) + var/list/L = alarms[cat] + if(L.len) + active_alarms = TRUE + if(active_alarms) + . += "alert:2" diff --git a/code/game/machinery/computer/teleporter.dm b/code/game/machinery/computer/teleporter.dm index 6be511f78518..9307a0781d54 100644 --- a/code/game/machinery/computer/teleporter.dm +++ b/code/game/machinery/computer/teleporter.dm @@ -5,8 +5,7 @@ icon_keyboard = "teleport_key" light_color = LIGHT_COLOR_BLUE circuit = /obj/item/circuitboard/computer/teleporter - ui_x = 470 - ui_y = 140 + var/regime_set = "Teleporter" var/id var/obj/machinery/teleport/station/power_station @@ -33,11 +32,10 @@ break return power_station -/obj/machinery/computer/teleporter/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/machinery/computer/teleporter/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "Teleporter", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, "Teleporter", name) ui.open() /obj/machinery/computer/teleporter/ui_data(mob/user) diff --git a/code/game/machinery/constructable_frame.dm b/code/game/machinery/constructable_frame.dm index 0cd8dca07bc2..9293e9c8e760 100644 --- a/code/game/machinery/constructable_frame.dm +++ b/code/game/machinery/constructable_frame.dm @@ -1,281 +1,281 @@ -/obj/structure/frame - name = "frame" - icon = 'icons/obj/stock_parts.dmi' - icon_state = "box_0" - density = TRUE - max_integrity = 250 - var/obj/item/circuitboard/machine/circuit = null - var/state = 1 - -/obj/structure/frame/examine(user) - . = ..() - if(circuit) - . += "It has \a [circuit] installed." - - -/obj/structure/frame/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - new /obj/item/stack/sheet/metal(loc, 5) - if(circuit) - circuit.forceMove(loc) - circuit = null - qdel(src) - - -/obj/structure/frame/machine - name = "machine frame" - var/list/components = null - var/list/req_components = null - var/list/req_component_names = null // user-friendly names of components - -/obj/structure/frame/machine/examine(user) - . = ..() - if(state == 3 && req_components && req_component_names) - var/hasContent = 0 - var/requires = "It requires" - - for(var/i = 1 to req_components.len) - var/tname = req_components[i] - var/amt = req_components[tname] - if(amt == 0) - continue - var/use_and = i == req_components.len - requires += "[(hasContent ? (use_and ? ", and" : ",") : "")] [amt] [amt == 1 ? req_component_names[tname] : "[req_component_names[tname]]\s"]" - hasContent = 1 - - if(hasContent) - . += "[requires]." - else - . += "It does not require any more components." - -/obj/structure/frame/machine/proc/update_namelist() - if(!req_components) - return - - req_component_names = new() - for(var/tname in req_components) - if(ispath(tname, /obj/item/stack)) - var/obj/item/stack/S = tname - var/singular_name = initial(S.singular_name) - if(singular_name) - req_component_names[tname] = singular_name - else - req_component_names[tname] = initial(S.name) - else - var/obj/O = tname - req_component_names[tname] = initial(O.name) - -/obj/structure/frame/machine/proc/get_req_components_amt() - var/amt = 0 - for(var/path in req_components) - amt += req_components[path] - return amt - -/obj/structure/frame/machine/attackby(obj/item/P, mob/user, params) - switch(state) - if(1) - if(istype(P, /obj/item/circuitboard/machine)) - to_chat(user, "The frame needs wiring first!") - return - else if(istype(P, /obj/item/circuitboard)) - to_chat(user, "This frame does not accept circuit boards of this type!") - return - if(istype(P, /obj/item/stack/cable_coil)) - if(!P.tool_start_check(user, amount=5)) - return - - to_chat(user, "You start to add cables to the frame...") - if(P.use_tool(src, user, 20, volume=50, amount=5)) - to_chat(user, "You add cables to the frame.") - state = 2 - icon_state = "box_1" - - return - if(P.tool_behaviour == TOOL_SCREWDRIVER && !anchored) - user.visible_message("[user] disassembles the frame.", \ - "You start to disassemble the frame...", "You hear banging and clanking.") - if(P.use_tool(src, user, 40, volume=50)) - if(state == 1) - to_chat(user, "You disassemble the frame.") - var/obj/item/stack/sheet/metal/M = new (loc, 5) - M.add_fingerprint(user) - qdel(src) - return - if(P.tool_behaviour == TOOL_WRENCH) - to_chat(user, "You start [anchored ? "un" : ""]securing [src]...") - if(P.use_tool(src, user, 40, volume=75)) - if(state == 1) - to_chat(user, "You [anchored ? "un" : ""]secure [src].") - setAnchored(!anchored) - return - - if(2) - if(P.tool_behaviour == TOOL_WRENCH) - to_chat(user, "You start [anchored ? "un" : ""]securing [src]...") - if(P.use_tool(src, user, 40, volume=75)) - to_chat(user, "You [anchored ? "un" : ""]secure [src].") - setAnchored(!anchored) - return - - if(istype(P, /obj/item/circuitboard/machine)) - var/obj/item/circuitboard/machine/B = P - if(!B.build_path) - to_chat(user, "This circuitboard seems to be broken.") - return - if(!anchored && B.needs_anchored) - to_chat(user, "The frame needs to be secured first!") - return - if(!user.transferItemToLoc(B, src)) - return - playsound(src.loc, 'sound/items/deconstruct.ogg', 50, TRUE) - to_chat(user, "You add the circuit board to the frame.") - circuit = B - icon_state = "box_2" - state = 3 - components = list() - req_components = B.req_components.Copy() - update_namelist() - return - - else if(istype(P, /obj/item/circuitboard)) - to_chat(user, "This frame does not accept circuit boards of this type!") - return - - if(P.tool_behaviour == TOOL_WIRECUTTER) - P.play_tool_sound(src) - to_chat(user, "You remove the cables.") - state = 1 - icon_state = "box_0" - new /obj/item/stack/cable_coil(drop_location(), 5) - return - - if(3) - if(P.tool_behaviour == TOOL_CROWBAR) - P.play_tool_sound(src) - state = 2 - circuit.forceMove(drop_location()) - components.Remove(circuit) - circuit = null - if(components.len == 0) - to_chat(user, "You remove the circuit board.") - else - to_chat(user, "You remove the circuit board and other components.") - for(var/atom/movable/AM in components) - AM.forceMove(drop_location()) - desc = initial(desc) - req_components = null - components = null - icon_state = "box_1" - return - - if(P.tool_behaviour == TOOL_WRENCH && !circuit.needs_anchored) - to_chat(user, "You start [anchored ? "un" : ""]securing [src]...") - if(P.use_tool(src, user, 40, volume=75)) - to_chat(user, "You [anchored ? "un" : ""]secure [src].") - setAnchored(!anchored) - return - - if(P.tool_behaviour == TOOL_SCREWDRIVER) - var/component_check = 1 - for(var/R in req_components) - if(req_components[R] > 0) - component_check = 0 - break - if(component_check) - P.play_tool_sound(src) - var/obj/machinery/new_machine = new circuit.build_path(loc) - if(new_machine.circuit) - QDEL_NULL(new_machine.circuit) - new_machine.circuit = circuit - new_machine.setAnchored(anchored) - new_machine.on_construction() - for(var/obj/O in new_machine.component_parts) - qdel(O) - new_machine.component_parts = list() - for(var/obj/O in src) - O.moveToNullspace() - new_machine.component_parts += O - circuit.moveToNullspace() - new_machine.RefreshParts() - qdel(src) - return - - if(istype(P, /obj/item/storage/part_replacer) && P.contents.len && get_req_components_amt()) - var/obj/item/storage/part_replacer/replacer = P - var/list/added_components = list() - var/list/part_list = list() - - //Assemble a list of current parts, then sort them by their rating! - for(var/obj/item/co in replacer) - part_list += co - //Sort the parts. This ensures that higher tier items are applied first. - part_list = sortTim(part_list, /proc/cmp_rped_sort) - - for(var/path in req_components) - while(req_components[path] > 0 && (locate(path) in part_list)) - var/obj/item/part = (locate(path) in part_list) - part_list -= part - if(istype(part,/obj/item/stack)) - var/obj/item/stack/S = part - var/used_amt = min(round(S.get_amount()), req_components[path]) - if(!used_amt || !S.use(used_amt)) - continue - var/NS = new S.merge_type(src, used_amt) - added_components[NS] = path - req_components[path] -= used_amt - else - added_components[part] = path - if(SEND_SIGNAL(replacer, COMSIG_TRY_STORAGE_TAKE, part, src)) - req_components[path]-- - - for(var/obj/item/part in added_components) - if(istype(part,/obj/item/stack)) - var/obj/item/stack/S = part - var/obj/item/stack/NS = locate(S.merge_type) in components //find a stack to merge with - if(NS) - S.merge(NS) - if(!QDELETED(part)) //If we're a stack and we merged we might not exist anymore - components += part - to_chat(user, "[part.name] applied.") - if(added_components.len) - replacer.play_rped_sound() - return - - if(isitem(P) && get_req_components_amt()) - for(var/I in req_components) - if(istype(P, I) && (req_components[I] > 0)) - if(istype(P, /obj/item/stack)) - var/obj/item/stack/S = P - var/used_amt = min(round(S.get_amount()), req_components[I]) - - if(used_amt && S.use(used_amt)) - var/obj/item/stack/NS = locate(S.merge_type) in components - - if(!NS) - NS = new S.merge_type(src, used_amt) - components += NS - else - NS.add(used_amt) - - req_components[I] -= used_amt - to_chat(user, "You add [P] to [src].") - return - if(!user.transferItemToLoc(P, src)) - break - to_chat(user, "You add [P] to [src].") - components += P - req_components[I]-- - return 1 - to_chat(user, "You cannot add that to the machine!") - return 0 - if(user.a_intent == INTENT_HARM) - return ..() - -/obj/structure/frame/machine/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - if(state >= 2) - new /obj/item/stack/cable_coil(loc , 5) - for(var/X in components) - var/obj/item/I = X - I.forceMove(loc) - ..() +/obj/structure/frame + name = "frame" + icon = 'icons/obj/stock_parts.dmi' + icon_state = "box_0" + density = TRUE + max_integrity = 250 + var/obj/item/circuitboard/machine/circuit = null + var/state = 1 + +/obj/structure/frame/examine(user) + . = ..() + if(circuit) + . += "It has \a [circuit] installed." + + +/obj/structure/frame/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + new /obj/item/stack/sheet/metal(loc, 5) + if(circuit) + circuit.forceMove(loc) + circuit = null + qdel(src) + + +/obj/structure/frame/machine + name = "machine frame" + var/list/components = null + var/list/req_components = null + var/list/req_component_names = null // user-friendly names of components + +/obj/structure/frame/machine/examine(user) + . = ..() + if(state == 3 && req_components && req_component_names) + var/hasContent = 0 + var/requires = "It requires" + + for(var/i = 1 to req_components.len) + var/tname = req_components[i] + var/amt = req_components[tname] + if(amt == 0) + continue + var/use_and = i == req_components.len + requires += "[(hasContent ? (use_and ? ", and" : ",") : "")] [amt] [amt == 1 ? req_component_names[tname] : "[req_component_names[tname]]\s"]" + hasContent = 1 + + if(hasContent) + . += "[requires]." + else + . += "It does not require any more components." + +/obj/structure/frame/machine/proc/update_namelist() + if(!req_components) + return + + req_component_names = new() + for(var/tname in req_components) + if(ispath(tname, /obj/item/stack)) + var/obj/item/stack/S = tname + var/singular_name = initial(S.singular_name) + if(singular_name) + req_component_names[tname] = singular_name + else + req_component_names[tname] = initial(S.name) + else + var/obj/O = tname + req_component_names[tname] = initial(O.name) + +/obj/structure/frame/machine/proc/get_req_components_amt() + var/amt = 0 + for(var/path in req_components) + amt += req_components[path] + return amt + +/obj/structure/frame/machine/attackby(obj/item/P, mob/user, params) + switch(state) + if(1) + if(istype(P, /obj/item/circuitboard/machine)) + to_chat(user, "The frame needs wiring first!") + return + else if(istype(P, /obj/item/circuitboard)) + to_chat(user, "This frame does not accept circuit boards of this type!") + return + if(istype(P, /obj/item/stack/cable_coil)) + if(!P.tool_start_check(user, amount=5)) + return + + to_chat(user, "You start to add cables to the frame...") + if(P.use_tool(src, user, 20, volume=50, amount=5)) + to_chat(user, "You add cables to the frame.") + state = 2 + icon_state = "box_1" + + return + if(P.tool_behaviour == TOOL_SCREWDRIVER && !anchored) + user.visible_message("[user] disassembles the frame.", \ + "You start to disassemble the frame...", "You hear banging and clanking.") + if(P.use_tool(src, user, 40, volume=50)) + if(state == 1) + to_chat(user, "You disassemble the frame.") + var/obj/item/stack/sheet/metal/M = new (loc, 5) + M.add_fingerprint(user) + qdel(src) + return + if(P.tool_behaviour == TOOL_WRENCH) + to_chat(user, "You start [anchored ? "un" : ""]securing [src]...") + if(P.use_tool(src, user, 40, volume=75)) + if(state == 1) + to_chat(user, "You [anchored ? "un" : ""]secure [src].") + setAnchored(!anchored) + return + + if(2) + if(P.tool_behaviour == TOOL_WRENCH) + to_chat(user, "You start [anchored ? "un" : ""]securing [src]...") + if(P.use_tool(src, user, 40, volume=75)) + to_chat(user, "You [anchored ? "un" : ""]secure [src].") + setAnchored(!anchored) + return + + if(istype(P, /obj/item/circuitboard/machine)) + var/obj/item/circuitboard/machine/B = P + if(!B.build_path) + to_chat(user, "This circuitboard seems to be broken.") + return + if(!anchored && B.needs_anchored) + to_chat(user, "The frame needs to be secured first!") + return + if(!user.transferItemToLoc(B, src)) + return + playsound(src.loc, 'sound/items/deconstruct.ogg', 50, TRUE) + to_chat(user, "You add the circuit board to the frame.") + circuit = B + icon_state = "box_2" + state = 3 + components = list() + req_components = B.req_components.Copy() + update_namelist() + return + + else if(istype(P, /obj/item/circuitboard)) + to_chat(user, "This frame does not accept circuit boards of this type!") + return + + if(P.tool_behaviour == TOOL_WIRECUTTER) + P.play_tool_sound(src) + to_chat(user, "You remove the cables.") + state = 1 + icon_state = "box_0" + new /obj/item/stack/cable_coil(drop_location(), 5) + return + + if(3) + if(P.tool_behaviour == TOOL_CROWBAR) + P.play_tool_sound(src) + state = 2 + circuit.forceMove(drop_location()) + components.Remove(circuit) + circuit = null + if(components.len == 0) + to_chat(user, "You remove the circuit board.") + else + to_chat(user, "You remove the circuit board and other components.") + for(var/atom/movable/AM in components) + AM.forceMove(drop_location()) + desc = initial(desc) + req_components = null + components = null + icon_state = "box_1" + return + + if(P.tool_behaviour == TOOL_WRENCH && !circuit.needs_anchored) + to_chat(user, "You start [anchored ? "un" : ""]securing [src]...") + if(P.use_tool(src, user, 40, volume=75)) + to_chat(user, "You [anchored ? "un" : ""]secure [src].") + setAnchored(!anchored) + return + + if(P.tool_behaviour == TOOL_SCREWDRIVER) + var/component_check = 1 + for(var/R in req_components) + if(req_components[R] > 0) + component_check = 0 + break + if(component_check) + P.play_tool_sound(src) + var/obj/machinery/new_machine = new circuit.build_path(loc) + if(new_machine.circuit) + QDEL_NULL(new_machine.circuit) + new_machine.circuit = circuit + new_machine.setAnchored(anchored) + new_machine.on_construction() + for(var/obj/O in new_machine.component_parts) + qdel(O) + new_machine.component_parts = list() + for(var/obj/O in src) + O.moveToNullspace() + new_machine.component_parts += O + circuit.moveToNullspace() + new_machine.RefreshParts() + qdel(src) + return + + if(istype(P, /obj/item/storage/part_replacer) && P.contents.len && get_req_components_amt()) + var/obj/item/storage/part_replacer/replacer = P + var/list/added_components = list() + var/list/part_list = list() + + //Assemble a list of current parts, then sort them by their rating! + for(var/obj/item/co in replacer) + part_list += co + //Sort the parts. This ensures that higher tier items are applied first. + part_list = sortTim(part_list, /proc/cmp_rped_sort) + + for(var/path in req_components) + while(req_components[path] > 0 && (locate(path) in part_list)) + var/obj/item/part = (locate(path) in part_list) + part_list -= part + if(istype(part,/obj/item/stack)) + var/obj/item/stack/S = part + var/used_amt = min(round(S.get_amount()), req_components[path]) + if(!used_amt || !S.use(used_amt)) + continue + var/NS = new S.merge_type(src, used_amt) + added_components[NS] = path + req_components[path] -= used_amt + else + added_components[part] = path + if(SEND_SIGNAL(replacer, COMSIG_TRY_STORAGE_TAKE, part, src)) + req_components[path]-- + + for(var/obj/item/part in added_components) + if(istype(part,/obj/item/stack)) + var/obj/item/stack/S = part + var/obj/item/stack/NS = locate(S.merge_type) in components //find a stack to merge with + if(NS) + S.merge(NS) + if(!QDELETED(part)) //If we're a stack and we merged we might not exist anymore + components += part + to_chat(user, "[part.name] applied.") + if(added_components.len) + replacer.play_rped_sound() + return + + if(isitem(P) && get_req_components_amt()) + for(var/I in req_components) + if(istype(P, I) && (req_components[I] > 0)) + if(istype(P, /obj/item/stack)) + var/obj/item/stack/S = P + var/used_amt = min(round(S.get_amount()), req_components[I]) + + if(used_amt && S.use(used_amt)) + var/obj/item/stack/NS = locate(S.merge_type) in components + + if(!NS) + NS = new S.merge_type(src, used_amt) + components += NS + else + NS.add(used_amt) + + req_components[I] -= used_amt + to_chat(user, "You add [P] to [src].") + return + if(!user.transferItemToLoc(P, src)) + break + to_chat(user, "You add [P] to [src].") + components += P + req_components[I]-- + return 1 + to_chat(user, "You cannot add that to the machine!") + return 0 + if(user.a_intent == INTENT_HARM) + return ..() + +/obj/structure/frame/machine/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + if(state >= 2) + new /obj/item/stack/cable_coil(loc , 5) + for(var/X in components) + var/obj/item/I = X + I.forceMove(loc) + ..() diff --git a/code/game/machinery/dance_machine.dm b/code/game/machinery/dance_machine.dm index ff28914286d1..4a1d449244a9 100644 --- a/code/game/machinery/dance_machine.dm +++ b/code/game/machinery/dance_machine.dm @@ -11,6 +11,8 @@ var/stop = 0 var/list/songs = list() var/datum/track/selection = null + /// Volume of the songs played + var/volume = 100 /obj/machinery/jukebox/disco name = "radiant dance machine mark IV" @@ -82,40 +84,53 @@ else icon_state = "[initial(icon_state)]" -/obj/machinery/jukebox/ui_interact(mob/user) - . = ..() - if(!user.canUseTopic(src, !issilicon(user))) - return - if (!anchored) +/obj/machinery/jukebox/ui_status(mob/user) + if(!anchored) to_chat(user,"This device must be anchored by a wrench!") - return - if(!allowed(user)) + return UI_CLOSE + if(!allowed(user) && !isobserver(user)) to_chat(user,"Error: Access Denied.") - user.playsound_local(src,'sound/misc/compiler-failure.ogg', 25, 1) - return - if(!songs.len) + user.playsound_local(src, 'sound/misc/compiler-failure.ogg', 25, TRUE) + return UI_CLOSE + if(!songs.len && !isobserver(user)) to_chat(user,"Error: No music tracks have been authorized for your station. Petition Central Command to resolve this issue.") - playsound(src,'sound/misc/compiler-failure.ogg', 25, TRUE) - return - var/list/dat = list() - dat +="
    " - dat += " Select Track
    " - dat += "Track Selected: [selection.song_name]
    " - dat += "Track Length: [DisplayTimeText(selection.song_length)]

    " - var/datum/browser/popup = new(user, "vending", "[name]", 400, 350) - popup.set_content(dat.Join()) - popup.open() - - -/obj/machinery/jukebox/Topic(href, href_list) - if(..()) + playsound(src, 'sound/misc/compiler-failure.ogg', 25, TRUE) + return UI_CLOSE + return ..() + +/obj/machinery/jukebox/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "Jukebox", name) + ui.open() + +/obj/machinery/jukebox/ui_data(mob/user) + var/list/data = list() + data["active"] = active + data["songs"] = list() + for(var/datum/track/S in songs) + var/list/track_data = list( + name = S.song_name + ) + data["songs"] += list(track_data) + data["track_selected"] = null + data["track_length"] = null + data["track_beat"] = null + if(selection) + data["track_selected"] = selection.song_name + data["track_length"] = DisplayTimeText(selection.song_length) + data["track_beat"] = selection.song_beat + data["volume"] = volume + return data + +/obj/machinery/jukebox/ui_act(action, list/params) + . = ..() + if(.) return - add_fingerprint(usr) - switch(href_list["action"]) + + switch(action) if("toggle") - if (QDELETED(src)) + if(QDELETED(src)) return if(!active) if(stop > world.time) @@ -124,23 +139,36 @@ return activate_music() START_PROCESSING(SSobj, src) - updateUsrDialog() - else if(active) + return TRUE + else stop = 0 - updateUsrDialog() - if("select") + return TRUE + if("select_track") if(active) to_chat(usr, "Error: You cannot change the song until the current one is over.") return - var/list/available = list() for(var/datum/track/S in songs) available[S.song_name] = S - var/selected = input(usr, "Choose your song", "Track:") as null|anything in sortList(available) + var/selected = params["track"] if(QDELETED(src) || !selected || !istype(available[selected], /datum/track)) return selection = available[selected] - updateUsrDialog() + return TRUE + if("set_volume") + var/new_volume = params["volume"] + if(new_volume == "reset") + volume = initial(volume) + return TRUE + else if(new_volume == "min") + volume = 0 + return TRUE + else if(new_volume == "max") + volume = 100 + return TRUE + else if(text2num(new_volume) != null) + volume = text2num(new_volume) + return TRUE /obj/machinery/jukebox/proc/activate_music() active = TRUE @@ -368,7 +396,6 @@ sleep(1) M.lying_fix() - /obj/machinery/jukebox/disco/proc/dance4(var/mob/living/M) var/speed = rand(1,3) set waitfor = 0 @@ -441,7 +468,7 @@ continue if(!(M in rangers)) rangers[M] = TRUE - M.playsound_local(get_turf(M), null, 100, channel = CHANNEL_JUKEBOX, S = song_played) + M.playsound_local(get_turf(M), null, volume, channel = CHANNEL_JUKEBOX, S = song_played) for(var/mob/L in rangers) if(get_dist(src,L) > 10) rangers -= L @@ -456,7 +483,6 @@ update_icon() stop = world.time + 100 - /obj/machinery/jukebox/disco/process() . = ..() if(active) diff --git a/code/game/machinery/defibrillator_mount.dm b/code/game/machinery/defibrillator_mount.dm index 898005f9beb7..4bf3550644ab 100644 --- a/code/game/machinery/defibrillator_mount.dm +++ b/code/game/machinery/defibrillator_mount.dm @@ -169,7 +169,7 @@ /obj/machinery/defibrillator_mount/charging/process() var/obj/item/stock_parts/cell/C = get_cell() - if(C.charge < C.maxcharge && is_operational()) + if(C && C.charge < C.maxcharge && is_operational()) use_power(100) C.give(80) update_icon() diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm index d6c2799de5f6..f2fbf3ea17f3 100644 --- a/code/game/machinery/doors/airlock.dm +++ b/code/game/machinery/doors/airlock.dm @@ -1437,11 +1437,10 @@ else if(istype(note, /obj/item/photo)) return "photo" -/obj/machinery/door/airlock/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/machinery/door/airlock/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "AiAirlock", name, 500, 390, master_ui, state) + ui = new(user, src, "AiAirlock", name) ui.open() return TRUE diff --git a/code/game/machinery/doors/airlock_electronics.dm b/code/game/machinery/doors/airlock_electronics.dm index ae8a069b332f..c6e08b96c22a 100644 --- a/code/game/machinery/doors/airlock_electronics.dm +++ b/code/game/machinery/doors/airlock_electronics.dm @@ -15,11 +15,13 @@ . = ..() . += "Has a neat selection menu for modifying airlock access levels." -/obj/item/electronics/airlock/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.hands_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/item/electronics/airlock/ui_state(mob/user) + return GLOB.hands_state + +/obj/item/electronics/airlock/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "AirlockElectronics", name, 420, 485, master_ui, state) + ui = new(user, src, "AirlockElectronics", name) ui.open() /obj/item/electronics/airlock/ui_static_data(mob/user) diff --git a/code/game/machinery/doors/alarmlock.dm b/code/game/machinery/doors/alarmlock.dm index 67904fb336de..07722469a7de 100644 --- a/code/game/machinery/doors/alarmlock.dm +++ b/code/game/machinery/doors/alarmlock.dm @@ -1,43 +1,43 @@ -/obj/machinery/door/airlock/alarmlock - name = "glass alarm airlock" - icon = 'icons/obj/doors/airlocks/station2/glass.dmi' - overlays_file = 'icons/obj/doors/airlocks/station2/overlays.dmi' - opacity = 0 - assemblytype = /obj/structure/door_assembly/door_assembly_public - glass = TRUE - - var/datum/radio_frequency/air_connection - var/air_frequency = FREQ_ATMOS_ALARMS - autoclose = FALSE - -/obj/machinery/door/airlock/alarmlock/Initialize() - . = ..() - air_connection = new - -/obj/machinery/door/airlock/alarmlock/Destroy() - SSradio.remove_object(src,air_frequency) - air_connection = null - return ..() - -/obj/machinery/door/airlock/alarmlock/Initialize() - . = ..() - SSradio.remove_object(src, air_frequency) - air_connection = SSradio.add_object(src, air_frequency, RADIO_TO_AIRALARM) - open() - -/obj/machinery/door/airlock/alarmlock/receive_signal(datum/signal/signal) - ..() - if(machine_stat & (NOPOWER|BROKEN)) - return - - var/alarm_area = signal.data["zone"] - var/alert = signal.data["alert"] - - if(alarm_area == get_area_name(src)) - switch(alert) - if("severe") - autoclose = TRUE - close() - if("minor", "clear") - autoclose = FALSE - open() +/obj/machinery/door/airlock/alarmlock + name = "glass alarm airlock" + icon = 'icons/obj/doors/airlocks/station2/glass.dmi' + overlays_file = 'icons/obj/doors/airlocks/station2/overlays.dmi' + opacity = 0 + assemblytype = /obj/structure/door_assembly/door_assembly_public + glass = TRUE + + var/datum/radio_frequency/air_connection + var/air_frequency = FREQ_ATMOS_ALARMS + autoclose = FALSE + +/obj/machinery/door/airlock/alarmlock/Initialize() + . = ..() + air_connection = new + +/obj/machinery/door/airlock/alarmlock/Destroy() + SSradio.remove_object(src,air_frequency) + air_connection = null + return ..() + +/obj/machinery/door/airlock/alarmlock/Initialize() + . = ..() + SSradio.remove_object(src, air_frequency) + air_connection = SSradio.add_object(src, air_frequency, RADIO_TO_AIRALARM) + open() + +/obj/machinery/door/airlock/alarmlock/receive_signal(datum/signal/signal) + ..() + if(machine_stat & (NOPOWER|BROKEN)) + return + + var/alarm_area = signal.data["zone"] + var/alert = signal.data["alert"] + + if(alarm_area == get_area_name(src)) + switch(alert) + if("severe") + autoclose = TRUE + close() + if("minor", "clear") + autoclose = FALSE + open() diff --git a/code/game/machinery/doors/brigdoors.dm b/code/game/machinery/doors/brigdoors.dm index 315ef79b2894..85d5f7803c8a 100644 --- a/code/game/machinery/doors/brigdoors.dm +++ b/code/game/machinery/doors/brigdoors.dm @@ -1,255 +1,252 @@ -#define CHARS_PER_LINE 5 -#define FONT_SIZE "5pt" -#define FONT_COLOR "#09f" -#define FONT_STYLE "Small Fonts" -#define MAX_TIMER 9000 - -#define PRESET_SHORT 1200 -#define PRESET_MEDIUM 1800 -#define PRESET_LONG 3000 - - - -/////////////////////////////////////////////////////////////////////////////////////////////// -// Brig Door control displays. -// Description: This is a controls the timer for the brig doors, displays the timer on itself and -// has a popup window when used, allowing to set the timer. -// Code Notes: Combination of old brigdoor.dm code from rev4407 and the status_display.dm code -// Date: 01/September/2010 -// Programmer: Veryinky -///////////////////////////////////////////////////////////////////////////////////////////////// -/obj/machinery/door_timer - name = "door timer" - icon = 'icons/obj/status_display.dmi' - icon_state = "frame" - desc = "A remote control for a door." - req_access = list(ACCESS_SECURITY) - density = FALSE - var/id = null // id of linked machinery/lockers - - var/activation_time = 0 - var/timer_duration = 0 - - var/timing = FALSE // boolean, true/1 timer is on, false/0 means it's not timing - var/list/obj/machinery/targets = list() - var/obj/item/radio/Radio //needed to send messages to sec radio - - maptext_height = 26 - maptext_width = 32 - maptext_y = -1 - ui_x = 300 - ui_y = 138 - -/obj/machinery/door_timer/Initialize() - . = ..() - - Radio = new/obj/item/radio(src) - Radio.listening = 0 - -/obj/machinery/door_timer/Initialize() - . = ..() - if(id != null) - for(var/obj/machinery/door/window/brigdoor/M in urange(20, src)) - if (M.id == id) - targets += M - - for(var/obj/machinery/flasher/F in urange(20, src)) - if(F.id == id) - targets += F - - for(var/obj/structure/closet/secure_closet/brig/C in urange(20, src)) - if(C.id == id) - targets += C - - if(!targets.len) - obj_break() - update_icon() - - -//Main door timer loop, if it's timing and time is >0 reduce time by 1. -// if it's less than 0, open door, reset timer -// update the door_timer window and the icon -/obj/machinery/door_timer/process() - if(machine_stat & (NOPOWER|BROKEN)) - return - - if(timing) - if(world.time - activation_time >= timer_duration) - timer_end() // open doors, reset timer, clear status screen - update_icon() - -// open/closedoor checks if door_timer has power, if so it checks if the -// linked door is open/closed (by density) then opens it/closes it. -/obj/machinery/door_timer/proc/timer_start() - if(machine_stat & (NOPOWER|BROKEN)) - return 0 - - activation_time = world.time - timing = TRUE - - for(var/obj/machinery/door/window/brigdoor/door in targets) - if(door.density) - continue - INVOKE_ASYNC(door, /obj/machinery/door/window/brigdoor.proc/close) - - for(var/obj/structure/closet/secure_closet/brig/C in targets) - if(C.broken) - continue - if(C.opened && !C.close()) - continue - C.locked = TRUE - C.update_icon() - return 1 - - -/obj/machinery/door_timer/proc/timer_end(forced = FALSE) - - if(machine_stat & (NOPOWER|BROKEN)) - return 0 - - if(!forced) - Radio.set_frequency(FREQ_SECURITY) - Radio.talk_into(src, "Timer has expired. Releasing prisoner.", FREQ_SECURITY) - - timing = FALSE - activation_time = null - set_timer(0) - update_icon() - - for(var/obj/machinery/door/window/brigdoor/door in targets) - if(!door.density) - continue - INVOKE_ASYNC(door, /obj/machinery/door/window/brigdoor.proc/open) - - for(var/obj/structure/closet/secure_closet/brig/C in targets) - if(C.broken) - continue - if(C.opened) - continue - C.locked = FALSE - C.update_icon() - - return 1 - - -/obj/machinery/door_timer/proc/time_left(seconds = FALSE) - . = max(0,timer_duration - (activation_time ? world.time - activation_time : 0)) - if(seconds) - . /= 10 - -/obj/machinery/door_timer/proc/set_timer(value) - var/new_time = clamp(value,0,MAX_TIMER) - . = new_time == timer_duration //return 1 on no change - timer_duration = new_time - -/obj/machinery/door_timer/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "BrigTimer", name, ui_x, ui_y, master_ui, state) - ui.open() - -//icon update function -// if NOPOWER, display blank -// if BROKEN, display blue screen of death icon AI uses -// if timing=true, run update display function -/obj/machinery/door_timer/update_icon() - if(machine_stat & (NOPOWER)) - icon_state = "frame" - return - - if(machine_stat & (BROKEN)) - set_picture("ai_bsod") - return - - if(timing) - var/disp1 = id - var/time_left = time_left(seconds = TRUE) - var/disp2 = "[add_leading(num2text((time_left / 60) % 60), 2, "0")]:[add_leading(num2text(time_left % 60), 2, "0")]" - if(length(disp2) > CHARS_PER_LINE) - disp2 = "Error" - update_display(disp1, disp2) - else - if(maptext) - maptext = "" - return - - -// Adds an icon in case the screen is broken/off, stolen from status_display.dm -/obj/machinery/door_timer/proc/set_picture(state) - if(maptext) - maptext = "" - cut_overlays() - add_overlay(mutable_appearance('icons/obj/status_display.dmi', state)) - - -//Checks to see if there's 1 line or 2, adds text-icons-numbers/letters over display -// Stolen from status_display -/obj/machinery/door_timer/proc/update_display(line1, line2) - line1 = uppertext(line1) - line2 = uppertext(line2) - var/new_text = {"
    [line1]
    [line2]
    "} - if(maptext != new_text) - maptext = new_text - -/obj/machinery/door_timer/ui_data() - var/list/data = list() - var/time_left = time_left(seconds = TRUE) - data["seconds"] = round(time_left % 60) - data["minutes"] = round((time_left - data["seconds"]) / 60) - data["timing"] = timing - data["flash_charging"] = FALSE - for(var/obj/machinery/flasher/F in targets) - if(F.last_flash && (F.last_flash + 150) > world.time) - data["flash_charging"] = TRUE - break - return data - - -/obj/machinery/door_timer/ui_act(action, params) - if(..()) - return - . = TRUE - - if(!allowed(usr)) - to_chat(usr, "Access denied.") - return FALSE - - switch(action) - if("time") - var/value = text2num(params["adjust"]) - if(value) - . = set_timer(time_left()+value) - if("start") - timer_start() - if("stop") - timer_end(forced = TRUE) - if("flash") - for(var/obj/machinery/flasher/F in targets) - F.flash() - if("preset") - var/preset = params["preset"] - var/preset_time = time_left() - switch(preset) - if("short") - preset_time = PRESET_SHORT - if("medium") - preset_time = PRESET_MEDIUM - if("long") - preset_time = PRESET_LONG - . = set_timer(preset_time) - if(timing) - activation_time = world.time - else - . = FALSE - - -#undef PRESET_SHORT -#undef PRESET_MEDIUM -#undef PRESET_LONG - -#undef MAX_TIMER -#undef FONT_SIZE -#undef FONT_COLOR -#undef FONT_STYLE -#undef CHARS_PER_LINE +#define CHARS_PER_LINE 5 +#define FONT_SIZE "5pt" +#define FONT_COLOR "#09f" +#define FONT_STYLE "Small Fonts" +#define MAX_TIMER 9000 + +#define PRESET_SHORT 1200 +#define PRESET_MEDIUM 1800 +#define PRESET_LONG 3000 + + + +/////////////////////////////////////////////////////////////////////////////////////////////// +// Brig Door control displays. +// Description: This is a controls the timer for the brig doors, displays the timer on itself and +// has a popup window when used, allowing to set the timer. +// Code Notes: Combination of old brigdoor.dm code from rev4407 and the status_display.dm code +// Date: 01/September/2010 +// Programmer: Veryinky +///////////////////////////////////////////////////////////////////////////////////////////////// +/obj/machinery/door_timer + name = "door timer" + icon = 'icons/obj/status_display.dmi' + icon_state = "frame" + desc = "A remote control for a door." + req_access = list(ACCESS_SECURITY) + density = FALSE + var/id = null // id of linked machinery/lockers + + var/activation_time = 0 + var/timer_duration = 0 + + var/timing = FALSE // boolean, true/1 timer is on, false/0 means it's not timing + var/list/obj/machinery/targets = list() + var/obj/item/radio/Radio //needed to send messages to sec radio + + maptext_height = 26 + maptext_width = 32 + maptext_y = -1 + +/obj/machinery/door_timer/Initialize() + . = ..() + + Radio = new/obj/item/radio(src) + Radio.listening = 0 + +/obj/machinery/door_timer/Initialize() + . = ..() + if(id != null) + for(var/obj/machinery/door/window/brigdoor/M in urange(20, src)) + if (M.id == id) + targets += M + + for(var/obj/machinery/flasher/F in urange(20, src)) + if(F.id == id) + targets += F + + for(var/obj/structure/closet/secure_closet/brig/C in urange(20, src)) + if(C.id == id) + targets += C + + if(!targets.len) + obj_break() + update_icon() + + +//Main door timer loop, if it's timing and time is >0 reduce time by 1. +// if it's less than 0, open door, reset timer +// update the door_timer window and the icon +/obj/machinery/door_timer/process() + if(machine_stat & (NOPOWER|BROKEN)) + return + + if(timing) + if(world.time - activation_time >= timer_duration) + timer_end() // open doors, reset timer, clear status screen + update_icon() + +// open/closedoor checks if door_timer has power, if so it checks if the +// linked door is open/closed (by density) then opens it/closes it. +/obj/machinery/door_timer/proc/timer_start() + if(machine_stat & (NOPOWER|BROKEN)) + return 0 + + activation_time = world.time + timing = TRUE + + for(var/obj/machinery/door/window/brigdoor/door in targets) + if(door.density) + continue + INVOKE_ASYNC(door, /obj/machinery/door/window/brigdoor.proc/close) + + for(var/obj/structure/closet/secure_closet/brig/C in targets) + if(C.broken) + continue + if(C.opened && !C.close()) + continue + C.locked = TRUE + C.update_icon() + return 1 + + +/obj/machinery/door_timer/proc/timer_end(forced = FALSE) + + if(machine_stat & (NOPOWER|BROKEN)) + return 0 + + if(!forced) + Radio.set_frequency(FREQ_SECURITY) + Radio.talk_into(src, "Timer has expired. Releasing prisoner.", FREQ_SECURITY) + + timing = FALSE + activation_time = null + set_timer(0) + update_icon() + + for(var/obj/machinery/door/window/brigdoor/door in targets) + if(!door.density) + continue + INVOKE_ASYNC(door, /obj/machinery/door/window/brigdoor.proc/open) + + for(var/obj/structure/closet/secure_closet/brig/C in targets) + if(C.broken) + continue + if(C.opened) + continue + C.locked = FALSE + C.update_icon() + + return 1 + + +/obj/machinery/door_timer/proc/time_left(seconds = FALSE) + . = max(0,timer_duration - (activation_time ? world.time - activation_time : 0)) + if(seconds) + . /= 10 + +/obj/machinery/door_timer/proc/set_timer(value) + var/new_time = clamp(value,0,MAX_TIMER) + . = new_time == timer_duration //return 1 on no change + timer_duration = new_time + +/obj/machinery/door_timer/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "BrigTimer", name) + ui.open() + +//icon update function +// if NOPOWER, display blank +// if BROKEN, display blue screen of death icon AI uses +// if timing=true, run update display function +/obj/machinery/door_timer/update_icon() + if(machine_stat & (NOPOWER)) + icon_state = "frame" + return + + if(machine_stat & (BROKEN)) + set_picture("ai_bsod") + return + + if(timing) + var/disp1 = id + var/time_left = time_left(seconds = TRUE) + var/disp2 = "[add_leading(num2text((time_left / 60) % 60), 2, "0")]:[add_leading(num2text(time_left % 60), 2, "0")]" + if(length(disp2) > CHARS_PER_LINE) + disp2 = "Error" + update_display(disp1, disp2) + else + if(maptext) + maptext = "" + return + + +// Adds an icon in case the screen is broken/off, stolen from status_display.dm +/obj/machinery/door_timer/proc/set_picture(state) + if(maptext) + maptext = "" + cut_overlays() + add_overlay(mutable_appearance('icons/obj/status_display.dmi', state)) + + +//Checks to see if there's 1 line or 2, adds text-icons-numbers/letters over display +// Stolen from status_display +/obj/machinery/door_timer/proc/update_display(line1, line2) + line1 = uppertext(line1) + line2 = uppertext(line2) + var/new_text = {"
    [line1]
    [line2]
    "} + if(maptext != new_text) + maptext = new_text + +/obj/machinery/door_timer/ui_data() + var/list/data = list() + var/time_left = time_left(seconds = TRUE) + data["seconds"] = round(time_left % 60) + data["minutes"] = round((time_left - data["seconds"]) / 60) + data["timing"] = timing + data["flash_charging"] = FALSE + for(var/obj/machinery/flasher/F in targets) + if(F.last_flash && (F.last_flash + 150) > world.time) + data["flash_charging"] = TRUE + break + return data + + +/obj/machinery/door_timer/ui_act(action, params) + if(..()) + return + . = TRUE + + if(!allowed(usr)) + to_chat(usr, "Access denied.") + return FALSE + + switch(action) + if("time") + var/value = text2num(params["adjust"]) + if(value) + . = set_timer(time_left()+value) + if("start") + timer_start() + if("stop") + timer_end(forced = TRUE) + if("flash") + for(var/obj/machinery/flasher/F in targets) + F.flash() + if("preset") + var/preset = params["preset"] + var/preset_time = time_left() + switch(preset) + if("short") + preset_time = PRESET_SHORT + if("medium") + preset_time = PRESET_MEDIUM + if("long") + preset_time = PRESET_LONG + . = set_timer(preset_time) + if(timing) + activation_time = world.time + else + . = FALSE + + +#undef PRESET_SHORT +#undef PRESET_MEDIUM +#undef PRESET_LONG + +#undef MAX_TIMER +#undef FONT_SIZE +#undef FONT_COLOR +#undef FONT_STYLE +#undef CHARS_PER_LINE diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm index e14047d9332f..bf12f6563e38 100644 --- a/code/game/machinery/doors/door.dm +++ b/code/game/machinery/doors/door.dm @@ -1,451 +1,451 @@ -/obj/machinery/door - name = "door" - desc = "It opens and closes." - icon = 'icons/obj/doors/Doorint.dmi' - icon_state = "door1" - opacity = 1 - density = TRUE - move_resist = MOVE_FORCE_VERY_STRONG - layer = OPEN_DOOR_LAYER - power_channel = AREA_USAGE_ENVIRON - max_integrity = 350 - armor = list("melee" = 30, "bullet" = 30, "laser" = 20, "energy" = 20, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 70) - CanAtmosPass = ATMOS_PASS_DENSITY - flags_1 = PREVENT_CLICK_UNDER_1 - ricochet_chance_mod = 0.8 - damage_deflection = 10 - - interaction_flags_atom = INTERACT_ATOM_UI_INTERACT - - var/air_tight = FALSE //TRUE means density will be set as soon as the door begins to close - var/secondsElectrified = MACHINE_NOT_ELECTRIFIED - var/shockedby - var/visible = TRUE - var/operating = FALSE - var/glass = FALSE - var/welded = FALSE - var/normalspeed = 1 - var/heat_proof = FALSE // For rglass-windowed airlocks and firedoors - var/emergency = FALSE // Emergency access override - var/sub_door = FALSE // true if it's meant to go under another door. - var/closingLayer = CLOSED_DOOR_LAYER - var/autoclose = FALSE //does it automatically close after some time - var/safe = TRUE //whether the door detects things and mobs in its way and reopen or crushes them. - var/locked = FALSE //whether the door is bolted or not. - var/assemblytype //the type of door frame to drop during deconstruction - var/datum/effect_system/spark_spread/spark_system - var/real_explosion_block //ignore this, just use explosion_block - var/red_alert_access = FALSE //if TRUE, this door will always open on red alert - var/poddoor = FALSE - var/unres_sides = 0 //Unrestricted sides. A bitflag for which direction (if any) can open the door with no access - var/safety_mode = FALSE ///Whether or not the airlock can be opened with bare hands while unpowered - var/can_crush = TRUE /// Whether or not the door can crush mobs. - -/obj/machinery/door/examine(mob/user) - . = ..() - if(red_alert_access) - if(GLOB.security_level >= SEC_LEVEL_RED) - . += "Due to a security threat, its access requirements have been lifted!" - else - . += "In the event of a red alert, its access requirements will automatically lift." - if(!poddoor) - . += "Its maintenance panel is screwed in place." - if(safety_mode) - . += "It has labels indicating that it has an emergency mechanism to open it with just your hands if there's no power." - -/obj/machinery/door/check_access_list(list/access_list) - if(red_alert_access && GLOB.security_level >= SEC_LEVEL_RED) - return TRUE - return ..() - -/obj/machinery/door/Initialize() - . = ..() - set_init_door_layer() - update_freelook_sight() - air_update_turf(1) - GLOB.airlocks += src - spark_system = new /datum/effect_system/spark_spread - spark_system.set_up(2, 1, src) - if(density) - flags_1 |= PREVENT_CLICK_UNDER_1 - else - flags_1 &= ~PREVENT_CLICK_UNDER_1 - - //doors only block while dense though so we have to use the proc - real_explosion_block = explosion_block - explosion_block = EXPLOSION_BLOCK_PROC - -/obj/machinery/door/proc/set_init_door_layer() - if(density) - layer = closingLayer - else - layer = initial(layer) - -/obj/machinery/door/Destroy() - update_freelook_sight() - GLOB.airlocks -= src - if(spark_system) - qdel(spark_system) - spark_system = null - return ..() - -/obj/machinery/door/proc/try_safety_unlock(mob/user) - if(safety_mode && !hasPower() && density) - to_chat(user, "You begin unlocking the airlock safety mechanism...") - if(do_after(user, 15 SECONDS, target = src)) - try_to_crowbar(null, user) - return TRUE - return FALSE - -/obj/machinery/door/Bumped(atom/movable/AM) - . = ..() - if(operating || (obj_flags & EMAGGED)) - return - if(ismob(AM)) - var/mob/B = AM - if((isdrone(B) || iscyborg(B)) && B.stat) - return - if(isliving(AM)) - var/mob/living/M = AM - if(world.time - M.last_bumped <= 10) - return //Can bump-open one airlock per second. This is to prevent shock spam. - M.last_bumped = world.time - if(M.restrained() && !check_access(null)) - return - if(try_safety_unlock(M)) - return - bumpopen(M) - return - - if(ismecha(AM)) - var/obj/mecha/mecha = AM - if(density) - if(mecha.occupant) - if(world.time - mecha.occupant.last_bumped <= 10) - return - mecha.occupant.last_bumped = world.time - if(mecha.occupant && (src.allowed(mecha.occupant) || src.check_access_list(mecha.operation_req_access))) - open() - else - do_animate("deny") - return - - if(isitem(AM)) - var/obj/item/I = AM - if(!density || (I.w_class < WEIGHT_CLASS_NORMAL && !LAZYLEN(I.GetAccess()))) - return - if(check_access(I)) - open() - else - do_animate("deny") - return - -/obj/machinery/door/Move() - var/turf/T = loc - . = ..() - move_update_air(T) - -/obj/machinery/door/CanAllowThrough(atom/movable/mover, turf/target) - . = ..() - if(.) - return - - if(istype(mover) && (mover.pass_flags & PASSGLASS)) - return !opacity - -/obj/machinery/door/proc/bumpopen(mob/user) - if(operating) - return - add_fingerprint(user) - if(!requiresID()) - user = null - - if(density && !(obj_flags & EMAGGED)) - if(allowed(user)) - open() - else - do_animate("deny") - return - -/obj/machinery/door/attack_hand(mob/user) - . = ..() - if(.) - return - if(try_safety_unlock(user)) - return - return try_to_activate_door(user) - -/obj/machinery/door/attack_tk(mob/user) - if(requiresID() && !allowed(null)) - return - ..() - -/obj/machinery/door/proc/try_to_activate_door(mob/user) - add_fingerprint(user) - if(operating || (obj_flags & EMAGGED)) - return - if(!requiresID()) - user = null //so allowed(user) always succeeds - if(allowed(user)) - if(density) - open() - else - close() - return TRUE - if(density) - do_animate("deny") - -/obj/machinery/door/allowed(mob/M) - if(emergency) - return TRUE - if(unrestricted_side(M)) - return TRUE - return ..() - -/obj/machinery/door/proc/unrestricted_side(mob/M) //Allows for specific side of airlocks to be unrestrected (IE, can exit maint freely, but need access to enter) - return get_dir(src, M) & unres_sides - -/obj/machinery/door/proc/try_to_weld(obj/item/weldingtool/W, mob/user) - return - -/obj/machinery/door/proc/try_to_crowbar(obj/item/I, mob/user) - return - -/obj/machinery/door/proc/is_holding_pressure() - var/turf/open/T = loc - if(!T) - return FALSE - if(!density) - return FALSE - // alrighty now we check for how much pressure we're holding back - var/min_moles = T.air.total_moles() - var/max_moles = min_moles - // okay this is a bit hacky. First, we set density to 0 and recalculate our adjacent turfs - density = FALSE - T.ImmediateCalculateAdjacentTurfs() - // then we use those adjacent turfs to figure out what the difference between the lowest and highest pressures we'd be holding is - for(var/turf/open/T2 in T.atmos_adjacent_turfs) - if((flags_1 & ON_BORDER_1) && get_dir(src, T2) != dir) - continue - var/moles = T2.air.total_moles() - if(moles < min_moles) - min_moles = moles - if(moles > max_moles) - max_moles = moles - density = TRUE - T.ImmediateCalculateAdjacentTurfs() // alright lets put it back - return max_moles - min_moles > 20 - -/obj/machinery/door/attackby(obj/item/I, mob/user, params) - if(user.a_intent != INTENT_HARM && (I.tool_behaviour == TOOL_CROWBAR || istype(I, /obj/item/fireaxe))) - var/forced_open = FALSE - if(istype(I, /obj/item/crowbar)) - var/obj/item/crowbar/C = I - forced_open = C.force_opens - try_to_crowbar(I, user, forced_open) - return TRUE - else if(I.tool_behaviour == TOOL_WELDER) - try_to_weld(I, user) - return TRUE - else if(!(I.item_flags & NOBLUDGEON) && user.a_intent != INTENT_HARM) - try_to_activate_door(user) - return TRUE - return ..() - -/obj/machinery/door/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir) - . = ..() - if(. && obj_integrity > 0) - if(damage_amount >= 10 && prob(30)) - spark_system.start() - -/obj/machinery/door/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) - switch(damage_type) - if(BRUTE) - if(glass) - playsound(loc, 'sound/effects/glasshit.ogg', 90, TRUE) - else if(damage_amount) - playsound(loc, 'sound/weapons/smash.ogg', 50, TRUE) - else - playsound(src, 'sound/weapons/tap.ogg', 50, TRUE) - if(BURN) - playsound(src.loc, 'sound/items/welder.ogg', 100, TRUE) - -/obj/machinery/door/emp_act(severity) - . = ..() - if (. & EMP_PROTECT_SELF) - return - if(prob(20/severity) && (istype(src, /obj/machinery/door/airlock) || istype(src, /obj/machinery/door/window)) ) - INVOKE_ASYNC(src, .proc/open) - if(prob(severity*10 - 20)) - if(secondsElectrified == MACHINE_NOT_ELECTRIFIED) - secondsElectrified = MACHINE_ELECTRIFIED_PERMANENT - LAZYADD(shockedby, "\[[time_stamp()]\]EM Pulse") - addtimer(CALLBACK(src, .proc/unelectrify), 300) - -/obj/machinery/door/proc/unelectrify() - secondsElectrified = MACHINE_NOT_ELECTRIFIED - -/obj/machinery/door/update_icon_state() - if(density) - icon_state = "door1" - else - icon_state = "door0" - -/obj/machinery/door/proc/do_animate(animation) - switch(animation) - if("opening") - if(panel_open) - flick("o_doorc0", src) - else - flick("doorc0", src) - if("closing") - if(panel_open) - flick("o_doorc1", src) - else - flick("doorc1", src) - if("deny") - if(!machine_stat) - flick("door_deny", src) - - -/obj/machinery/door/proc/open() - if(!density) - return 1 - if(operating) - return - operating = TRUE - do_animate("opening") - set_opacity(0) - sleep(5) - density = FALSE - flags_1 &= ~PREVENT_CLICK_UNDER_1 - sleep(5) - layer = initial(layer) - update_icon() - set_opacity(0) - operating = FALSE - air_update_turf(1) - update_freelook_sight() - if(autoclose) - addtimer(CALLBACK(src, .proc/close), autoclose) - return 1 - -/obj/machinery/door/proc/close() - if(density) - return TRUE - if(operating || welded) - return - if(safe) - for(var/atom/movable/M in get_turf(src)) - if(M.density && M != src) //something is blocking the door - if(autoclose) - autoclose_in(60) - return - - operating = TRUE - - do_animate("closing") - layer = closingLayer - if(air_tight) - density = TRUE - sleep(5) - density = TRUE - flags_1 |= PREVENT_CLICK_UNDER_1 - sleep(5) - update_icon() - if(visible && !glass) - set_opacity(1) - operating = FALSE - air_update_turf(1) - update_freelook_sight() - - if(!can_crush) - return TRUE - - if(safe) - CheckForMobs() - else if(!(flags_1 & ON_BORDER_1)) - crush() - return TRUE - -/obj/machinery/door/proc/CheckForMobs() - if(locate(/mob/living) in get_turf(src)) - sleep(1) - open() - -/obj/machinery/door/proc/crush() - for(var/mob/living/L in get_turf(src)) - L.visible_message("[src] closes on [L], crushing [L.p_them()]!", "[src] closes on you and crushes you!") - if(isalien(L)) //For xenos - L.adjustBruteLoss(DOOR_CRUSH_DAMAGE * 1.5) //Xenos go into crit after aproximately the same amount of crushes as humans. - L.emote("roar") - else if(ishuman(L)) //For humans - L.adjustBruteLoss(DOOR_CRUSH_DAMAGE) - L.emote("scream") - L.Paralyze(100) - else if(ismonkey(L)) //For monkeys - L.adjustBruteLoss(DOOR_CRUSH_DAMAGE) - L.Paralyze(100) - else //for simple_animals & borgs - L.adjustBruteLoss(DOOR_CRUSH_DAMAGE) - var/turf/location = get_turf(src) - //add_blood doesn't work for borgs/xenos, but add_blood_floor does. - L.add_splatter_floor(location) - log_combat(src, L, "crushed") - for(var/obj/mecha/M in get_turf(src)) - M.take_damage(DOOR_CRUSH_DAMAGE) - log_combat(src, M, "crushed") - -/obj/machinery/door/proc/autoclose() - if(!QDELETED(src) && !density && !operating && !locked && !welded && autoclose) - close() - -/obj/machinery/door/proc/autoclose_in(wait) - addtimer(CALLBACK(src, .proc/autoclose), wait, TIMER_UNIQUE | TIMER_NO_HASH_WAIT | TIMER_OVERRIDE) - -/obj/machinery/door/proc/requiresID() - return 1 - -/obj/machinery/door/proc/hasPower() - return !(machine_stat & NOPOWER) - -/obj/machinery/door/proc/update_freelook_sight() - if(!glass && GLOB.cameranet) - GLOB.cameranet.updateVisibility(src, 0) - -/obj/machinery/door/BlockSuperconductivity() // All non-glass airlocks block heat, this is intended. - if(opacity || heat_proof) - return 1 - return 0 - -/obj/machinery/door/morgue - icon = 'icons/obj/doors/doormorgue.dmi' - -/obj/machinery/door/get_dumping_location(obj/item/storage/source,mob/user) - return null - -/obj/machinery/door/proc/lock() - return - -/obj/machinery/door/proc/unlock() - return - -/obj/machinery/door/proc/hostile_lockdown(mob/origin) - if(!machine_stat) //So that only powered doors are closed. - close() //Close ALL the doors! - -/obj/machinery/door/proc/disable_lockdown() - if(!machine_stat) //Opens only powered doors. - open() //Open everything! - -/obj/machinery/door/ex_act(severity, target) - //if it blows up a wall it should blow up a door - ..(severity ? max(1, severity - 1) : 0, target) - -/obj/machinery/door/GetExplosionBlock() - return density ? real_explosion_block : 0 - -/obj/machinery/door/power_change() - . = ..() - if(. && !(machine_stat & NOPOWER)) - autoclose_in(rand(0.5 SECONDS, 3 SECONDS)) +/obj/machinery/door + name = "door" + desc = "It opens and closes." + icon = 'icons/obj/doors/Doorint.dmi' + icon_state = "door1" + opacity = 1 + density = TRUE + move_resist = MOVE_FORCE_VERY_STRONG + layer = OPEN_DOOR_LAYER + power_channel = AREA_USAGE_ENVIRON + max_integrity = 350 + armor = list("melee" = 30, "bullet" = 30, "laser" = 20, "energy" = 20, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 70) + CanAtmosPass = ATMOS_PASS_DENSITY + flags_1 = PREVENT_CLICK_UNDER_1 + ricochet_chance_mod = 0.8 + damage_deflection = 10 + + interaction_flags_atom = INTERACT_ATOM_UI_INTERACT + + var/air_tight = FALSE //TRUE means density will be set as soon as the door begins to close + var/secondsElectrified = MACHINE_NOT_ELECTRIFIED + var/shockedby + var/visible = TRUE + var/operating = FALSE + var/glass = FALSE + var/welded = FALSE + var/normalspeed = 1 + var/heat_proof = FALSE // For rglass-windowed airlocks and firedoors + var/emergency = FALSE // Emergency access override + var/sub_door = FALSE // true if it's meant to go under another door. + var/closingLayer = CLOSED_DOOR_LAYER + var/autoclose = FALSE //does it automatically close after some time + var/safe = TRUE //whether the door detects things and mobs in its way and reopen or crushes them. + var/locked = FALSE //whether the door is bolted or not. + var/assemblytype //the type of door frame to drop during deconstruction + var/datum/effect_system/spark_spread/spark_system + var/real_explosion_block //ignore this, just use explosion_block + var/red_alert_access = FALSE //if TRUE, this door will always open on red alert + var/poddoor = FALSE + var/unres_sides = 0 //Unrestricted sides. A bitflag for which direction (if any) can open the door with no access + var/safety_mode = FALSE ///Whether or not the airlock can be opened with bare hands while unpowered + var/can_crush = TRUE /// Whether or not the door can crush mobs. + +/obj/machinery/door/examine(mob/user) + . = ..() + if(red_alert_access) + if(GLOB.security_level >= SEC_LEVEL_RED) + . += "Due to a security threat, its access requirements have been lifted!" + else + . += "In the event of a red alert, its access requirements will automatically lift." + if(!poddoor) + . += "Its maintenance panel is screwed in place." + if(safety_mode) + . += "It has labels indicating that it has an emergency mechanism to open it with just your hands if there's no power." + +/obj/machinery/door/check_access_list(list/access_list) + if(red_alert_access && GLOB.security_level >= SEC_LEVEL_RED) + return TRUE + return ..() + +/obj/machinery/door/Initialize() + . = ..() + set_init_door_layer() + update_freelook_sight() + air_update_turf(1) + GLOB.airlocks += src + spark_system = new /datum/effect_system/spark_spread + spark_system.set_up(2, 1, src) + if(density) + flags_1 |= PREVENT_CLICK_UNDER_1 + else + flags_1 &= ~PREVENT_CLICK_UNDER_1 + + //doors only block while dense though so we have to use the proc + real_explosion_block = explosion_block + explosion_block = EXPLOSION_BLOCK_PROC + +/obj/machinery/door/proc/set_init_door_layer() + if(density) + layer = closingLayer + else + layer = initial(layer) + +/obj/machinery/door/Destroy() + update_freelook_sight() + GLOB.airlocks -= src + if(spark_system) + qdel(spark_system) + spark_system = null + return ..() + +/obj/machinery/door/proc/try_safety_unlock(mob/user) + if(safety_mode && !hasPower() && density) + to_chat(user, "You begin unlocking the airlock safety mechanism...") + if(do_after(user, 15 SECONDS, target = src)) + try_to_crowbar(null, user) + return TRUE + return FALSE + +/obj/machinery/door/Bumped(atom/movable/AM) + . = ..() + if(operating || (obj_flags & EMAGGED)) + return + if(ismob(AM)) + var/mob/B = AM + if((isdrone(B) || iscyborg(B)) && B.stat) + return + if(isliving(AM)) + var/mob/living/M = AM + if(world.time - M.last_bumped <= 10) + return //Can bump-open one airlock per second. This is to prevent shock spam. + M.last_bumped = world.time + if(M.restrained() && !check_access(null)) + return + if(try_safety_unlock(M)) + return + bumpopen(M) + return + + if(ismecha(AM)) + var/obj/mecha/mecha = AM + if(density) + if(mecha.occupant) + if(world.time - mecha.occupant.last_bumped <= 10) + return + mecha.occupant.last_bumped = world.time + if(mecha.occupant && (src.allowed(mecha.occupant) || src.check_access_list(mecha.operation_req_access))) + open() + else + do_animate("deny") + return + + if(isitem(AM)) + var/obj/item/I = AM + if(!density || (I.w_class < WEIGHT_CLASS_NORMAL && !LAZYLEN(I.GetAccess()))) + return + if(check_access(I)) + open() + else + do_animate("deny") + return + +/obj/machinery/door/Move() + var/turf/T = loc + . = ..() + move_update_air(T) + +/obj/machinery/door/CanAllowThrough(atom/movable/mover, turf/target) + . = ..() + if(.) + return + + if(istype(mover) && (mover.pass_flags & PASSGLASS)) + return !opacity + +/obj/machinery/door/proc/bumpopen(mob/user) + if(operating) + return + add_fingerprint(user) + if(!requiresID()) + user = null + + if(density && !(obj_flags & EMAGGED)) + if(allowed(user)) + open() + else + do_animate("deny") + return + +/obj/machinery/door/attack_hand(mob/user) + . = ..() + if(.) + return + if(try_safety_unlock(user)) + return + return try_to_activate_door(user) + +/obj/machinery/door/attack_tk(mob/user) + if(requiresID() && !allowed(null)) + return + ..() + +/obj/machinery/door/proc/try_to_activate_door(mob/user) + add_fingerprint(user) + if(operating || (obj_flags & EMAGGED)) + return + if(!requiresID()) + user = null //so allowed(user) always succeeds + if(allowed(user)) + if(density) + open() + else + close() + return TRUE + if(density) + do_animate("deny") + +/obj/machinery/door/allowed(mob/M) + if(emergency) + return TRUE + if(unrestricted_side(M)) + return TRUE + return ..() + +/obj/machinery/door/proc/unrestricted_side(mob/M) //Allows for specific side of airlocks to be unrestrected (IE, can exit maint freely, but need access to enter) + return get_dir(src, M) & unres_sides + +/obj/machinery/door/proc/try_to_weld(obj/item/weldingtool/W, mob/user) + return + +/obj/machinery/door/proc/try_to_crowbar(obj/item/I, mob/user) + return + +/obj/machinery/door/proc/is_holding_pressure() + var/turf/open/T = loc + if(!T) + return FALSE + if(!density) + return FALSE + // alrighty now we check for how much pressure we're holding back + var/min_moles = T.air.total_moles() + var/max_moles = min_moles + // okay this is a bit hacky. First, we set density to 0 and recalculate our adjacent turfs + density = FALSE + T.ImmediateCalculateAdjacentTurfs() + // then we use those adjacent turfs to figure out what the difference between the lowest and highest pressures we'd be holding is + for(var/turf/open/T2 in T.atmos_adjacent_turfs) + if((flags_1 & ON_BORDER_1) && get_dir(src, T2) != dir) + continue + var/moles = T2.air.total_moles() + if(moles < min_moles) + min_moles = moles + if(moles > max_moles) + max_moles = moles + density = TRUE + T.ImmediateCalculateAdjacentTurfs() // alright lets put it back + return max_moles - min_moles > 20 + +/obj/machinery/door/attackby(obj/item/I, mob/user, params) + if(user.a_intent != INTENT_HARM && (I.tool_behaviour == TOOL_CROWBAR || istype(I, /obj/item/fireaxe))) + var/forced_open = FALSE + if(istype(I, /obj/item/crowbar)) + var/obj/item/crowbar/C = I + forced_open = C.force_opens + try_to_crowbar(I, user, forced_open) + return TRUE + else if(I.tool_behaviour == TOOL_WELDER) + try_to_weld(I, user) + return TRUE + else if(!(I.item_flags & NOBLUDGEON) && user.a_intent != INTENT_HARM) + try_to_activate_door(user) + return TRUE + return ..() + +/obj/machinery/door/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir) + . = ..() + if(. && obj_integrity > 0) + if(damage_amount >= 10 && prob(30)) + spark_system.start() + +/obj/machinery/door/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) + switch(damage_type) + if(BRUTE) + if(glass) + playsound(loc, 'sound/effects/glasshit.ogg', 90, TRUE) + else if(damage_amount) + playsound(loc, 'sound/weapons/smash.ogg', 50, TRUE) + else + playsound(src, 'sound/weapons/tap.ogg', 50, TRUE) + if(BURN) + playsound(src.loc, 'sound/items/welder.ogg', 100, TRUE) + +/obj/machinery/door/emp_act(severity) + . = ..() + if (. & EMP_PROTECT_SELF) + return + if(prob(20/severity) && (istype(src, /obj/machinery/door/airlock) || istype(src, /obj/machinery/door/window)) ) + INVOKE_ASYNC(src, .proc/open) + if(prob(severity*10 - 20)) + if(secondsElectrified == MACHINE_NOT_ELECTRIFIED) + secondsElectrified = MACHINE_ELECTRIFIED_PERMANENT + LAZYADD(shockedby, "\[[time_stamp()]\]EM Pulse") + addtimer(CALLBACK(src, .proc/unelectrify), 300) + +/obj/machinery/door/proc/unelectrify() + secondsElectrified = MACHINE_NOT_ELECTRIFIED + +/obj/machinery/door/update_icon_state() + if(density) + icon_state = "door1" + else + icon_state = "door0" + +/obj/machinery/door/proc/do_animate(animation) + switch(animation) + if("opening") + if(panel_open) + flick("o_doorc0", src) + else + flick("doorc0", src) + if("closing") + if(panel_open) + flick("o_doorc1", src) + else + flick("doorc1", src) + if("deny") + if(!machine_stat) + flick("door_deny", src) + + +/obj/machinery/door/proc/open() + if(!density) + return 1 + if(operating) + return + operating = TRUE + do_animate("opening") + set_opacity(0) + sleep(5) + density = FALSE + flags_1 &= ~PREVENT_CLICK_UNDER_1 + sleep(5) + layer = initial(layer) + update_icon() + set_opacity(0) + operating = FALSE + air_update_turf(1) + update_freelook_sight() + if(autoclose) + addtimer(CALLBACK(src, .proc/close), autoclose) + return 1 + +/obj/machinery/door/proc/close() + if(density) + return TRUE + if(operating || welded) + return + if(safe) + for(var/atom/movable/M in get_turf(src)) + if(M.density && M != src) //something is blocking the door + if(autoclose) + autoclose_in(60) + return + + operating = TRUE + + do_animate("closing") + layer = closingLayer + if(air_tight) + density = TRUE + sleep(5) + density = TRUE + flags_1 |= PREVENT_CLICK_UNDER_1 + sleep(5) + update_icon() + if(visible && !glass) + set_opacity(1) + operating = FALSE + air_update_turf(1) + update_freelook_sight() + + if(!can_crush) + return TRUE + + if(safe) + CheckForMobs() + else if(!(flags_1 & ON_BORDER_1)) + crush() + return TRUE + +/obj/machinery/door/proc/CheckForMobs() + if(locate(/mob/living) in get_turf(src)) + sleep(1) + open() + +/obj/machinery/door/proc/crush() + for(var/mob/living/L in get_turf(src)) + L.visible_message("[src] closes on [L], crushing [L.p_them()]!", "[src] closes on you and crushes you!") + if(isalien(L)) //For xenos + L.adjustBruteLoss(DOOR_CRUSH_DAMAGE * 1.5) //Xenos go into crit after aproximately the same amount of crushes as humans. + L.emote("roar") + else if(ishuman(L)) //For humans + L.adjustBruteLoss(DOOR_CRUSH_DAMAGE) + L.emote("scream") + L.Paralyze(100) + else if(ismonkey(L)) //For monkeys + L.adjustBruteLoss(DOOR_CRUSH_DAMAGE) + L.Paralyze(100) + else //for simple_animals & borgs + L.adjustBruteLoss(DOOR_CRUSH_DAMAGE) + var/turf/location = get_turf(src) + //add_blood doesn't work for borgs/xenos, but add_blood_floor does. + L.add_splatter_floor(location) + log_combat(src, L, "crushed") + for(var/obj/mecha/M in get_turf(src)) + M.take_damage(DOOR_CRUSH_DAMAGE) + log_combat(src, M, "crushed") + +/obj/machinery/door/proc/autoclose() + if(!QDELETED(src) && !density && !operating && !locked && !welded && autoclose) + close() + +/obj/machinery/door/proc/autoclose_in(wait) + addtimer(CALLBACK(src, .proc/autoclose), wait, TIMER_UNIQUE | TIMER_NO_HASH_WAIT | TIMER_OVERRIDE) + +/obj/machinery/door/proc/requiresID() + return 1 + +/obj/machinery/door/proc/hasPower() + return !(machine_stat & NOPOWER) + +/obj/machinery/door/proc/update_freelook_sight() + if(!glass && GLOB.cameranet) + GLOB.cameranet.updateVisibility(src, 0) + +/obj/machinery/door/BlockSuperconductivity() // All non-glass airlocks block heat, this is intended. + if(opacity || heat_proof) + return 1 + return 0 + +/obj/machinery/door/morgue + icon = 'icons/obj/doors/doormorgue.dmi' + +/obj/machinery/door/get_dumping_location(obj/item/storage/source,mob/user) + return null + +/obj/machinery/door/proc/lock() + return + +/obj/machinery/door/proc/unlock() + return + +/obj/machinery/door/proc/hostile_lockdown(mob/origin) + if(!machine_stat) //So that only powered doors are closed. + close() //Close ALL the doors! + +/obj/machinery/door/proc/disable_lockdown() + if(!machine_stat) //Opens only powered doors. + open() //Open everything! + +/obj/machinery/door/ex_act(severity, target) + //if it blows up a wall it should blow up a door + ..(severity ? max(1, severity - 1) : 0, target) + +/obj/machinery/door/GetExplosionBlock() + return density ? real_explosion_block : 0 + +/obj/machinery/door/power_change() + . = ..() + if(. && !(machine_stat & NOPOWER)) + autoclose_in(rand(0.5 SECONDS, 3 SECONDS)) diff --git a/code/game/machinery/doors/firedoor.dm b/code/game/machinery/doors/firedoor.dm index 84911a73c5c0..781cf54e39a6 100644 --- a/code/game/machinery/doors/firedoor.dm +++ b/code/game/machinery/doors/firedoor.dm @@ -1,671 +1,671 @@ -#define CONSTRUCTION_COMPLETE 0 //No construction done - functioning as normal -#define CONSTRUCTION_PANEL_OPEN 1 //Maintenance panel is open, still functioning -#define CONSTRUCTION_WIRES_EXPOSED 2 //Cover plate is removed, wires are available -#define CONSTRUCTION_GUTTED 3 //Wires are removed, circuit ready to remove -#define CONSTRUCTION_NOCIRCUIT 4 //Circuit board removed, can safely weld apart - -/obj/machinery/door/firedoor - name = "firelock" - desc = "Apply crowbar." - icon = 'icons/obj/doors/doorfireglass.dmi' - icon_state = "door_open" - opacity = FALSE - density = FALSE - max_integrity = 300 - resistance_flags = FIRE_PROOF - heat_proof = TRUE - glass = TRUE - sub_door = TRUE - explosion_block = 1 - safe = FALSE - layer = BELOW_OPEN_DOOR_LAYER - closingLayer = CLOSED_FIREDOOR_LAYER - assemblytype = /obj/structure/firelock_frame - armor = list("melee" = 30, "bullet" = 30, "laser" = 20, "energy" = 20, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 95, "acid" = 70) - interaction_flags_machine = INTERACT_MACHINE_WIRES_IF_OPEN | INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OPEN_SILICON | INTERACT_MACHINE_REQUIRES_SILICON | INTERACT_MACHINE_OPEN - air_tight = TRUE - var/emergency_close_timer = 0 - var/nextstate = null - var/boltslocked = TRUE - var/list/affecting_areas - -/obj/machinery/door/firedoor/Initialize() - . = ..() - CalculateAffectingAreas() - -/obj/machinery/door/firedoor/examine(mob/user) - . = ..() - if(!density) - . += "It is open, but could be pried closed." - else if(!welded) - . += "It is closed, but could be pried open. Deconstruction would require it to be welded shut." - else if(boltslocked) - . += "It is welded shut. The floor bolts have been locked by screws." - else - . += "The bolt locks have been unscrewed, but the bolts themselves are still wrenched to the floor." - -/obj/machinery/door/firedoor/proc/CalculateAffectingAreas() - remove_from_areas() - affecting_areas = get_adjacent_open_areas(src) | get_area(src) - for(var/I in affecting_areas) - var/area/A = I - LAZYADD(A.firedoors, src) - -/obj/machinery/door/firedoor/closed - icon_state = "door_closed" - opacity = TRUE - density = TRUE - -//see also turf/AfterChange for adjacency shennanigans - -/obj/machinery/door/firedoor/proc/remove_from_areas() - if(affecting_areas) - for(var/I in affecting_areas) - var/area/A = I - LAZYREMOVE(A.firedoors, src) - -/obj/machinery/door/firedoor/Destroy() - remove_from_areas() - affecting_areas.Cut() - return ..() - -/obj/machinery/door/firedoor/Bumped(atom/movable/AM) - if(panel_open || operating || welded || (machine_stat & NOPOWER)) - return - if(ismob(AM)) - var/mob/user = AM - if(allow_hand_open(user)) - add_fingerprint(user) - open() - return TRUE - if(ismecha(AM)) - var/obj/mecha/M = AM - if(M.occupant && allow_hand_open(M.occupant)) - open() - return TRUE - return FALSE - - -/obj/machinery/door/firedoor/power_change() - . = ..() - latetoggle() - -/obj/machinery/door/firedoor/attack_hand(mob/user) - . = ..() - if(.) - return - if(!welded && !operating && !(machine_stat & NOPOWER) && (!density || allow_hand_open(user))) - add_fingerprint(user) - if(density) - emergency_close_timer = world.time + 30 // prevent it from instaclosing again if in space - open() - else - close() - return TRUE - if(operating || !density) - return - user.changeNext_move(CLICK_CD_MELEE) - - user.visible_message("[user] bangs on \the [src].", \ - "You bang on \the [src].") - playsound(loc, 'sound/effects/glassknock.ogg', 10, FALSE, frequency = 32000) - -/obj/machinery/door/firedoor/attackby(obj/item/C, mob/user, params) - add_fingerprint(user) - if(operating) - return - - if(welded) - if(C.tool_behaviour == TOOL_WRENCH) - if(boltslocked) - to_chat(user, "There are screws locking the bolts in place!") - return - C.play_tool_sound(src) - user.visible_message("[user] starts undoing [src]'s bolts...", \ - "You start unfastening [src]'s floor bolts...") - if(!C.use_tool(src, user, 50)) - return - playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, TRUE) - user.visible_message("[user] unfastens [src]'s bolts.", \ - "You undo [src]'s floor bolts.") - deconstruct(TRUE) - return - if(C.tool_behaviour == TOOL_SCREWDRIVER) - user.visible_message("[user] [boltslocked ? "unlocks" : "locks"] [src]'s bolts.", \ - "You [boltslocked ? "unlock" : "lock"] [src]'s floor bolts.") - C.play_tool_sound(src) - boltslocked = !boltslocked - return - - return ..() - -/obj/machinery/door/firedoor/try_to_activate_door(mob/user) - return - -/obj/machinery/door/firedoor/try_to_weld(obj/item/weldingtool/W, mob/user) - if(!W.tool_start_check(user, amount=0)) - return - user.visible_message("[user] starts [welded ? "unwelding" : "welding"] [src].", "You start welding [src].") - if(W.use_tool(src, user, 40, volume=50)) - welded = !welded - to_chat(user, "[user] [welded?"welds":"unwelds"] [src].", "You [welded ? "weld" : "unweld"] [src].") - update_icon() - -/obj/machinery/door/firedoor/try_to_crowbar(obj/item/I, mob/user) - if(welded || operating) - return - - if(density) - if(is_holding_pressure()) - // tell the user that this is a bad idea, and have a do_after as well - to_chat(user, "As you begin crowbarring \the [src] a gush of air blows in your face... maybe you should reconsider?") - if(!do_after(user, 20, TRUE, src)) // give them a few seconds to reconsider their decision. - return - log_game("[key_name(user)] has opened a firelock with a pressure difference at [AREACOORD(loc)]") - user.log_message("has opened a firelock with a pressure difference at [AREACOORD(loc)]", LOG_ATTACK) - // since we have high-pressure-ness, close all other firedoors on the tile - whack_a_mole() - if(welded || operating || !density) - return // in case things changed during our do_after - emergency_close_timer = world.time + 60 // prevent it from instaclosing again if in space - open() - else - close() - - -/obj/machinery/door/firedoor/proc/allow_hand_open(mob/user) - var/area/A = get_area(src) - if(A && A.fire) - return FALSE - return !is_holding_pressure() - -/obj/machinery/door/firedoor/attack_ai(mob/user) - add_fingerprint(user) - if(welded || operating || machine_stat & NOPOWER) - return TRUE - if(density) - open() - else - close() - return TRUE - -/obj/machinery/door/firedoor/attack_robot(mob/user) - return attack_ai(user) - -/obj/machinery/door/firedoor/attack_alien(mob/user) - add_fingerprint(user) - if(welded) - to_chat(user, "[src] refuses to budge!") - return - open() - -/obj/machinery/door/firedoor/do_animate(animation) - switch(animation) - if("opening") - flick("door_opening", src) - if("closing") - flick("door_closing", src) - -/obj/machinery/door/firedoor/update_icon_state() - if(density) - icon_state = "door_closed" - else - icon_state = "door_open" - -/obj/machinery/door/firedoor/update_overlays() - . = ..() - if(!welded) - return - if(density) - . += "welded" - else - . += "welded_open" - -/obj/machinery/door/firedoor/open() - . = ..() - latetoggle() - -/obj/machinery/door/firedoor/close() - . = ..() - latetoggle() - -/obj/machinery/door/firedoor/proc/whack_a_mole(reconsider_immediately = FALSE) - set waitfor = 0 - for(var/cdir in GLOB.cardinals) - if((flags_1 & ON_BORDER_1) && cdir != dir) - continue - whack_a_mole_part(get_step(src, cdir), reconsider_immediately) - if(flags_1 & ON_BORDER_1) - whack_a_mole_part(get_turf(src), reconsider_immediately) - -/obj/machinery/door/firedoor/proc/whack_a_mole_part(turf/start_point, reconsider_immediately) - set waitfor = 0 - var/list/doors_to_close = list() - var/list/turfs = list() - turfs[start_point] = 1 - for(var/i = 1; (i <= turfs.len && i <= 11); i++) // check up to 11 turfs. - var/turf/open/T = turfs[i] - if(istype(T, /turf/open/space)) - return -1 - for(var/T2 in T.atmos_adjacent_turfs) - if(turfs[T2]) - continue - var/is_cut_by_unopen_door = FALSE - for(var/obj/machinery/door/firedoor/FD in T2) - if((FD.flags_1 & ON_BORDER_1) && get_dir(T2, T) != FD.dir) - continue - if(FD.operating || FD == src || FD.welded || FD.density) - continue - doors_to_close += FD - is_cut_by_unopen_door = TRUE - - for(var/obj/machinery/door/firedoor/FD in T) - if((FD.flags_1 & ON_BORDER_1) && get_dir(T, T2) != FD.dir) - continue - if(FD.operating || FD == src || FD.welded || FD.density) - continue - doors_to_close += FD - is_cut_by_unopen_door= TRUE - if(!is_cut_by_unopen_door) - turfs[T2] = 1 - if(turfs.len > 10) - return // too big, don't bother - for(var/obj/machinery/door/firedoor/FD in doors_to_close) - FD.emergency_pressure_stop(FALSE) - if(reconsider_immediately) - var/turf/open/T = FD.loc - if(istype(T)) - T.ImmediateCalculateAdjacentTurfs() - -/obj/machinery/door/firedoor/proc/emergency_pressure_stop(consider_timer = TRUE) - set waitfor = 0 - if(density || operating || welded) - return - if(world.time >= emergency_close_timer || !consider_timer) - close() - -/obj/machinery/door/firedoor/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - var/obj/structure/firelock_frame/F = new assemblytype(get_turf(src)) - F.dir = src.dir - F.firelock_type = src.type - if(disassembled) - F.constructionStep = CONSTRUCTION_PANEL_OPEN - else - F.constructionStep = CONSTRUCTION_WIRES_EXPOSED - F.obj_integrity = F.max_integrity * 0.5 - F.update_icon() - qdel(src) - - -/obj/machinery/door/firedoor/proc/latetoggle() - if(operating || machine_stat & NOPOWER || !nextstate) - return - switch(nextstate) - if(FIREDOOR_OPEN) - nextstate = null - open() - if(FIREDOOR_CLOSED) - nextstate = null - close() - -/obj/machinery/door/firedoor/border_only - icon = 'icons/obj/doors/edge_Doorfire.dmi' - can_crush = FALSE - flags_1 = ON_BORDER_1 - CanAtmosPass = ATMOS_PASS_PROC - assemblytype = /obj/structure/firelock_frame/border - -/obj/machinery/door/firedoor/border_only/closed - icon_state = "door_closed" - opacity = TRUE - density = TRUE - -/obj/machinery/door/firedoor/border_only/close() - if(density) - return TRUE - if(operating || welded) - return - var/turf/T1 = get_turf(src) - var/turf/T2 = get_step(T1, dir) - for(var/mob/living/M in T1) - if(M.stat == CONSCIOUS && M.pulling && M.pulling.loc == T2 && !M.pulling.anchored && M.pulling.move_resist <= M.move_force) - var/mob/living/M2 = M.pulling - if(!istype(M2) || !M2.buckled || !M2.buckled.buckle_prevents_pull) - to_chat(M, "You pull [M.pulling] through [src] right as it closes") - M.pulling.forceMove(T1) - M.start_pulling(M2) - for(var/mob/living/M in T2) - if(M.stat == CONSCIOUS && M.pulling && M.pulling.loc == T1 && !M.pulling.anchored && M.pulling.move_resist <= M.move_force) - var/mob/living/M2 = M.pulling - if(!istype(M2) || !M2.buckled || !M2.buckled.buckle_prevents_pull) - to_chat(M, "You pull [M.pulling] through [src] right as it closes") - M.pulling.forceMove(T2) - M.start_pulling(M2) - . = ..() - -/obj/machinery/door/firedoor/border_only/allow_hand_open(mob/user) - var/area/A = get_area(src) - if((!A || !A.fire) && !is_holding_pressure()) - return TRUE - whack_a_mole(TRUE) // WOOP WOOP SIDE EFFECTS - var/turf/T = loc - var/turf/T2 = get_step(T, dir) - if(!T || !T2) - return - var/status1 = check_door_side(T) - var/status2 = check_door_side(T2) - if((status1 == 1 && status2 == -1) || (status1 == -1 && status2 == 1)) - to_chat(user, "Access denied. Try closing another firedoor to minimize decompression, or using a crowbar.") - return FALSE - return TRUE - -/obj/machinery/door/firedoor/border_only/proc/check_door_side(turf/open/start_point) - var/list/turfs = list() - turfs[start_point] = 1 - for(var/i = 1; (i <= turfs.len && i <= 11); i++) // check up to 11 turfs. - var/turf/open/T = turfs[i] - if(istype(T, /turf/open/space)) - return -1 - for(var/T2 in T.atmos_adjacent_turfs) - turfs[T2] = 1 - if(turfs.len <= 10) - return 0 // not big enough to matter - return start_point.air.return_pressure() < 20 ? -1 : 1 - -/obj/machinery/door/firedoor/border_only/CanAllowThrough(atom/movable/mover, turf/target) - . = ..() - if(istype(mover) && (mover.pass_flags & PASSGLASS)) - return TRUE - if(!(get_dir(loc, target) == dir)) //Make sure looking at appropriate border - return TRUE - -/obj/machinery/door/firedoor/border_only/CheckExit(atom/movable/mover as mob|obj, turf/target) - if(istype(mover) && (mover.pass_flags & PASSGLASS)) - return TRUE - if(get_dir(loc, target) == dir) - return !density - else - return TRUE - -/obj/machinery/door/firedoor/border_only/CanAtmosPass(turf/T) - if(get_dir(loc, T) == dir) - return !density - else - return TRUE - -/obj/machinery/door/firedoor/heavy - name = "heavy firelock" - icon = 'icons/obj/doors/doorfire.dmi' - glass = FALSE - explosion_block = 2 - assemblytype = /obj/structure/firelock_frame/heavy - max_integrity = 550 - -/obj/machinery/door/firedoor/window - name = "firelock window shutter" - icon = 'icons/obj/doors/doorfirewindow.dmi' - desc = "A second window that slides in when the original window is broken, designed to protect against hull breaches. Truly a work of genius by NT engineers." - glass = TRUE - explosion_block = 0 - max_integrity = 100 - resistance_flags = 0 // not fireproof - heat_proof = FALSE - assemblytype = /obj/structure/firelock_frame/window - -/obj/item/electronics/firelock - name = "firelock circuitry" - custom_price = 50 - desc = "A circuit board used in construction of firelocks." - icon_state = "mainboard" - -/obj/structure/firelock_frame - name = "firelock frame" - desc = "A partially completed firelock." - icon = 'icons/obj/doors/doorfire.dmi' - icon_state = "frame1" - anchored = FALSE - density = TRUE - var/constructionStep = CONSTRUCTION_NOCIRCUIT - var/reinforced = 0 - var/firelock_type - -/obj/structure/firelock_frame/examine(mob/user) - . = ..() - switch(constructionStep) - if(CONSTRUCTION_PANEL_OPEN) - . += "It is unbolted from the floor. A small loosely connected metal plate is covering the wires." - if(!reinforced) - . += "It could be reinforced with plasteel." - if(CONSTRUCTION_WIRES_EXPOSED) - . += "The maintenance plate has been pried away, and wires are trailing." - if(CONSTRUCTION_GUTTED) - . += "The maintenance panel is missing wires and the circuit board is loosely connected." - if(CONSTRUCTION_NOCIRCUIT) - . += "There are no firelock electronics in the frame. The frame could be cut apart." - -/obj/structure/firelock_frame/update_icon_state() - icon_state = "frame[constructionStep]" - -/obj/structure/firelock_frame/attackby(obj/item/C, mob/user) - switch(constructionStep) - if(CONSTRUCTION_PANEL_OPEN) - if(C.tool_behaviour == TOOL_CROWBAR) - C.play_tool_sound(src) - user.visible_message("[user] starts prying something out from [src]...", \ - "You begin prying out the wire cover...") - if(!C.use_tool(src, user, 50)) - return - if(constructionStep != CONSTRUCTION_PANEL_OPEN) - return - playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, TRUE) - user.visible_message("[user] pries out a metal plate from [src], exposing the wires.", \ - "You remove the cover plate from [src], exposing the wires.") - constructionStep = CONSTRUCTION_WIRES_EXPOSED - update_icon() - return - if(C.tool_behaviour == TOOL_WRENCH) - var/obj/machinery/door/firedoor/A = locate(/obj/machinery/door/firedoor) in get_turf(src) - if(A && A.dir == src.dir) - to_chat(user, "There's already a firelock there.") - return - C.play_tool_sound(src) - user.visible_message("[user] starts bolting down [src]...", \ - "You begin bolting [src]...") - if(!C.use_tool(src, user, 30)) - return - var/obj/machinery/door/firedoor/D = locate(/obj/machinery/door/firedoor) in get_turf(src) - if(D && D.dir == src.dir) - return - user.visible_message("[user] finishes the firelock.", \ - "You finish the firelock.") - playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, TRUE) - if(reinforced) - new /obj/machinery/door/firedoor/heavy(get_turf(src)) - else - var/obj/machinery/door/firedoor/F = new firelock_type(get_turf(src)) - F.dir = src.dir - F.update_icon() - qdel(src) - return - if(istype(C, /obj/item/stack/sheet/plasteel)) - var/obj/item/stack/sheet/plasteel/P = C - if(reinforced) - to_chat(user, "[src] is already reinforced.") - return - if(P.get_amount() < 2) - to_chat(user, "You need more plasteel to reinforce [src].") - return - user.visible_message("[user] begins reinforcing [src]...", \ - "You begin reinforcing [src]...") - playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, TRUE) - if(do_after(user, 60, target = src)) - if(constructionStep != CONSTRUCTION_PANEL_OPEN || reinforced || P.get_amount() < 2 || !P) - return - user.visible_message("[user] reinforces [src].", \ - "You reinforce [src].") - playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, TRUE) - P.use(2) - reinforced = 1 - return - - if(CONSTRUCTION_WIRES_EXPOSED) - if(C.tool_behaviour == TOOL_WIRECUTTER) - C.play_tool_sound(src) - user.visible_message("[user] starts cutting the wires from [src]...", \ - "You begin removing [src]'s wires...") - if(!C.use_tool(src, user, 60)) - return - if(constructionStep != CONSTRUCTION_WIRES_EXPOSED) - return - user.visible_message("[user] removes the wires from [src].", \ - "You remove the wiring from [src], exposing the circuit board.") - new/obj/item/stack/cable_coil(get_turf(src), 5) - constructionStep = CONSTRUCTION_GUTTED - update_icon() - return - if(C.tool_behaviour == TOOL_CROWBAR) - C.play_tool_sound(src) - user.visible_message("[user] starts prying a metal plate into [src]...", \ - "You begin prying the cover plate back onto [src]...") - if(!C.use_tool(src, user, 80)) - return - if(constructionStep != CONSTRUCTION_WIRES_EXPOSED) - return - playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, TRUE) - user.visible_message("[user] pries the metal plate into [src].", \ - "You pry [src]'s cover plate into place, hiding the wires.") - constructionStep = CONSTRUCTION_PANEL_OPEN - update_icon() - return - if(CONSTRUCTION_GUTTED) - if(C.tool_behaviour == TOOL_CROWBAR) - user.visible_message("[user] begins removing the circuit board from [src]...", \ - "You begin prying out the circuit board from [src]...") - if(!C.use_tool(src, user, 50, volume=50)) - return - if(constructionStep != CONSTRUCTION_GUTTED) - return - user.visible_message("[user] removes [src]'s circuit board.", \ - "You remove the circuit board from [src].") - new /obj/item/electronics/firelock(drop_location()) - constructionStep = CONSTRUCTION_NOCIRCUIT - update_icon() - return - if(istype(C, /obj/item/stack/cable_coil)) - var/obj/item/stack/cable_coil/B = C - if(B.get_amount() < 5) - to_chat(user, "You need more wires to add wiring to [src].") - return - user.visible_message("[user] begins wiring [src]...", \ - "You begin adding wires to [src]...") - playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, TRUE) - if(do_after(user, 60, target = src)) - if(constructionStep != CONSTRUCTION_GUTTED || B.get_amount() < 5 || !B) - return - user.visible_message("[user] adds wires to [src].", \ - "You wire [src].") - playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, TRUE) - B.use(5) - constructionStep = CONSTRUCTION_WIRES_EXPOSED - update_icon() - return - if(CONSTRUCTION_NOCIRCUIT) - if(C.tool_behaviour == TOOL_WELDER) - if(!C.tool_start_check(user, amount=1)) - return - user.visible_message("[user] begins cutting apart [src]'s frame...", \ - "You begin slicing [src] apart...") - - if(C.use_tool(src, user, 40, volume=50, amount=1)) - if(constructionStep != CONSTRUCTION_NOCIRCUIT) - return - user.visible_message("[user] cuts apart [src]!", \ - "You cut [src] into metal.") - var/turf/T = get_turf(src) - new /obj/item/stack/sheet/metal(T, 3) - if(reinforced) - new /obj/item/stack/sheet/plasteel(T, 2) - qdel(src) - return - if(istype(C, /obj/item/electronics/firelock)) - user.visible_message("[user] starts adding [C] to [src]...", \ - "You begin adding a circuit board to [src]...") - playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, TRUE) - if(!do_after(user, 40, target = src)) - return - if(constructionStep != CONSTRUCTION_NOCIRCUIT) - return - qdel(C) - user.visible_message("[user] adds a circuit to [src].", \ - "You insert and secure [C].") - playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, TRUE) - constructionStep = CONSTRUCTION_GUTTED - update_icon() - return - if(istype(C, /obj/item/electroadaptive_pseudocircuit)) - var/obj/item/electroadaptive_pseudocircuit/P = C - if(!P.adapt_circuit(user, 30)) - return - user.visible_message("[user] fabricates a circuit and places it into [src].", \ - "You adapt a firelock circuit and slot it into the assembly.") - constructionStep = CONSTRUCTION_GUTTED - update_icon() - return - return ..() - -/obj/structure/firelock_frame/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd) - if(the_rcd.mode == RCD_DECONSTRUCT) - return list("mode" = RCD_DECONSTRUCT, "delay" = 50, "cost" = 16) - else if((constructionStep == CONSTRUCTION_NOCIRCUIT) && (the_rcd.upgrade & RCD_UPGRADE_SIMPLE_CIRCUITS)) - return list("mode" = RCD_UPGRADE_SIMPLE_CIRCUITS, "delay" = 20, "cost" = 1) - return FALSE - -/obj/structure/firelock_frame/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode) - switch(passed_mode) - if(RCD_UPGRADE_SIMPLE_CIRCUITS) - user.visible_message("[user] fabricates a circuit and places it into [src].", \ - "You adapt a firelock circuit and slot it into the assembly.") - constructionStep = CONSTRUCTION_GUTTED - update_icon() - return TRUE - if(RCD_DECONSTRUCT) - to_chat(user, "You deconstruct [src].") - qdel(src) - return TRUE - return FALSE - -/obj/structure/firelock_frame/heavy - name = "heavy firelock frame" - reinforced = TRUE - -/obj/structure/firelock_frame/border - name = "firelock frame" - icon = 'icons/obj/doors/edge_Doorfire.dmi' - icon_state = "frame1" - firelock_type = /obj/machinery/door/firedoor/border_only/closed - flags_1 = ON_BORDER_1 - -/obj/structure/firelock_frame/border/ComponentInitialize() - . = ..() - AddComponent(/datum/component/simple_rotation, ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS, null, CALLBACK(src, .proc/can_be_rotated)) - -/obj/structure/firelock_frame/border/proc/can_be_rotated(mob/user, rotation_type) - if (anchored) - to_chat(user, "It is fastened to the floor!") - return FALSE - return TRUE - -/obj/structure/firelock_frame/window - name = "window firelock frame" - icon = 'icons/obj/doors/doorfirewindow.dmi' - icon_state = "door_frame" - -/obj/structure/firelock_frame/window/update_icon() - return - -#undef CONSTRUCTION_COMPLETE -#undef CONSTRUCTION_PANEL_OPEN -#undef CONSTRUCTION_WIRES_EXPOSED -#undef CONSTRUCTION_GUTTED -#undef CONSTRUCTION_NOCIRCUIT +#define CONSTRUCTION_COMPLETE 0 //No construction done - functioning as normal +#define CONSTRUCTION_PANEL_OPEN 1 //Maintenance panel is open, still functioning +#define CONSTRUCTION_WIRES_EXPOSED 2 //Cover plate is removed, wires are available +#define CONSTRUCTION_GUTTED 3 //Wires are removed, circuit ready to remove +#define CONSTRUCTION_NOCIRCUIT 4 //Circuit board removed, can safely weld apart + +/obj/machinery/door/firedoor + name = "firelock" + desc = "Apply crowbar." + icon = 'icons/obj/doors/doorfireglass.dmi' + icon_state = "door_open" + opacity = FALSE + density = FALSE + max_integrity = 300 + resistance_flags = FIRE_PROOF + heat_proof = TRUE + glass = TRUE + sub_door = TRUE + explosion_block = 1 + safe = FALSE + layer = BELOW_OPEN_DOOR_LAYER + closingLayer = CLOSED_FIREDOOR_LAYER + assemblytype = /obj/structure/firelock_frame + armor = list("melee" = 30, "bullet" = 30, "laser" = 20, "energy" = 20, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 95, "acid" = 70) + interaction_flags_machine = INTERACT_MACHINE_WIRES_IF_OPEN | INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OPEN_SILICON | INTERACT_MACHINE_REQUIRES_SILICON | INTERACT_MACHINE_OPEN + air_tight = TRUE + var/emergency_close_timer = 0 + var/nextstate = null + var/boltslocked = TRUE + var/list/affecting_areas + +/obj/machinery/door/firedoor/Initialize() + . = ..() + CalculateAffectingAreas() + +/obj/machinery/door/firedoor/examine(mob/user) + . = ..() + if(!density) + . += "It is open, but could be pried closed." + else if(!welded) + . += "It is closed, but could be pried open. Deconstruction would require it to be welded shut." + else if(boltslocked) + . += "It is welded shut. The floor bolts have been locked by screws." + else + . += "The bolt locks have been unscrewed, but the bolts themselves are still wrenched to the floor." + +/obj/machinery/door/firedoor/proc/CalculateAffectingAreas() + remove_from_areas() + affecting_areas = get_adjacent_open_areas(src) | get_area(src) + for(var/I in affecting_areas) + var/area/A = I + LAZYADD(A.firedoors, src) + +/obj/machinery/door/firedoor/closed + icon_state = "door_closed" + opacity = TRUE + density = TRUE + +//see also turf/AfterChange for adjacency shennanigans + +/obj/machinery/door/firedoor/proc/remove_from_areas() + if(affecting_areas) + for(var/I in affecting_areas) + var/area/A = I + LAZYREMOVE(A.firedoors, src) + +/obj/machinery/door/firedoor/Destroy() + remove_from_areas() + affecting_areas.Cut() + return ..() + +/obj/machinery/door/firedoor/Bumped(atom/movable/AM) + if(panel_open || operating || welded || (machine_stat & NOPOWER)) + return + if(ismob(AM)) + var/mob/user = AM + if(allow_hand_open(user)) + add_fingerprint(user) + open() + return TRUE + if(ismecha(AM)) + var/obj/mecha/M = AM + if(M.occupant && allow_hand_open(M.occupant)) + open() + return TRUE + return FALSE + + +/obj/machinery/door/firedoor/power_change() + . = ..() + latetoggle() + +/obj/machinery/door/firedoor/attack_hand(mob/user) + . = ..() + if(.) + return + if(!welded && !operating && !(machine_stat & NOPOWER) && (!density || allow_hand_open(user))) + add_fingerprint(user) + if(density) + emergency_close_timer = world.time + 30 // prevent it from instaclosing again if in space + open() + else + close() + return TRUE + if(operating || !density) + return + user.changeNext_move(CLICK_CD_MELEE) + + user.visible_message("[user] bangs on \the [src].", \ + "You bang on \the [src].") + playsound(loc, 'sound/effects/glassknock.ogg', 10, FALSE, frequency = 32000) + +/obj/machinery/door/firedoor/attackby(obj/item/C, mob/user, params) + add_fingerprint(user) + if(operating) + return + + if(welded) + if(C.tool_behaviour == TOOL_WRENCH) + if(boltslocked) + to_chat(user, "There are screws locking the bolts in place!") + return + C.play_tool_sound(src) + user.visible_message("[user] starts undoing [src]'s bolts...", \ + "You start unfastening [src]'s floor bolts...") + if(!C.use_tool(src, user, 50)) + return + playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, TRUE) + user.visible_message("[user] unfastens [src]'s bolts.", \ + "You undo [src]'s floor bolts.") + deconstruct(TRUE) + return + if(C.tool_behaviour == TOOL_SCREWDRIVER) + user.visible_message("[user] [boltslocked ? "unlocks" : "locks"] [src]'s bolts.", \ + "You [boltslocked ? "unlock" : "lock"] [src]'s floor bolts.") + C.play_tool_sound(src) + boltslocked = !boltslocked + return + + return ..() + +/obj/machinery/door/firedoor/try_to_activate_door(mob/user) + return + +/obj/machinery/door/firedoor/try_to_weld(obj/item/weldingtool/W, mob/user) + if(!W.tool_start_check(user, amount=0)) + return + user.visible_message("[user] starts [welded ? "unwelding" : "welding"] [src].", "You start welding [src].") + if(W.use_tool(src, user, 40, volume=50)) + welded = !welded + to_chat(user, "[user] [welded?"welds":"unwelds"] [src].", "You [welded ? "weld" : "unweld"] [src].") + update_icon() + +/obj/machinery/door/firedoor/try_to_crowbar(obj/item/I, mob/user) + if(welded || operating) + return + + if(density) + if(is_holding_pressure()) + // tell the user that this is a bad idea, and have a do_after as well + to_chat(user, "As you begin crowbarring \the [src] a gush of air blows in your face... maybe you should reconsider?") + if(!do_after(user, 20, TRUE, src)) // give them a few seconds to reconsider their decision. + return + log_game("[key_name(user)] has opened a firelock with a pressure difference at [AREACOORD(loc)]") + user.log_message("has opened a firelock with a pressure difference at [AREACOORD(loc)]", LOG_ATTACK) + // since we have high-pressure-ness, close all other firedoors on the tile + whack_a_mole() + if(welded || operating || !density) + return // in case things changed during our do_after + emergency_close_timer = world.time + 60 // prevent it from instaclosing again if in space + open() + else + close() + + +/obj/machinery/door/firedoor/proc/allow_hand_open(mob/user) + var/area/A = get_area(src) + if(A && A.fire) + return FALSE + return !is_holding_pressure() + +/obj/machinery/door/firedoor/attack_ai(mob/user) + add_fingerprint(user) + if(welded || operating || machine_stat & NOPOWER) + return TRUE + if(density) + open() + else + close() + return TRUE + +/obj/machinery/door/firedoor/attack_robot(mob/user) + return attack_ai(user) + +/obj/machinery/door/firedoor/attack_alien(mob/user) + add_fingerprint(user) + if(welded) + to_chat(user, "[src] refuses to budge!") + return + open() + +/obj/machinery/door/firedoor/do_animate(animation) + switch(animation) + if("opening") + flick("door_opening", src) + if("closing") + flick("door_closing", src) + +/obj/machinery/door/firedoor/update_icon_state() + if(density) + icon_state = "door_closed" + else + icon_state = "door_open" + +/obj/machinery/door/firedoor/update_overlays() + . = ..() + if(!welded) + return + if(density) + . += "welded" + else + . += "welded_open" + +/obj/machinery/door/firedoor/open() + . = ..() + latetoggle() + +/obj/machinery/door/firedoor/close() + . = ..() + latetoggle() + +/obj/machinery/door/firedoor/proc/whack_a_mole(reconsider_immediately = FALSE) + set waitfor = 0 + for(var/cdir in GLOB.cardinals) + if((flags_1 & ON_BORDER_1) && cdir != dir) + continue + whack_a_mole_part(get_step(src, cdir), reconsider_immediately) + if(flags_1 & ON_BORDER_1) + whack_a_mole_part(get_turf(src), reconsider_immediately) + +/obj/machinery/door/firedoor/proc/whack_a_mole_part(turf/start_point, reconsider_immediately) + set waitfor = 0 + var/list/doors_to_close = list() + var/list/turfs = list() + turfs[start_point] = 1 + for(var/i = 1; (i <= turfs.len && i <= 11); i++) // check up to 11 turfs. + var/turf/open/T = turfs[i] + if(istype(T, /turf/open/space)) + return -1 + for(var/T2 in T.atmos_adjacent_turfs) + if(turfs[T2]) + continue + var/is_cut_by_unopen_door = FALSE + for(var/obj/machinery/door/firedoor/FD in T2) + if((FD.flags_1 & ON_BORDER_1) && get_dir(T2, T) != FD.dir) + continue + if(FD.operating || FD == src || FD.welded || FD.density) + continue + doors_to_close += FD + is_cut_by_unopen_door = TRUE + + for(var/obj/machinery/door/firedoor/FD in T) + if((FD.flags_1 & ON_BORDER_1) && get_dir(T, T2) != FD.dir) + continue + if(FD.operating || FD == src || FD.welded || FD.density) + continue + doors_to_close += FD + is_cut_by_unopen_door= TRUE + if(!is_cut_by_unopen_door) + turfs[T2] = 1 + if(turfs.len > 10) + return // too big, don't bother + for(var/obj/machinery/door/firedoor/FD in doors_to_close) + FD.emergency_pressure_stop(FALSE) + if(reconsider_immediately) + var/turf/open/T = FD.loc + if(istype(T)) + T.ImmediateCalculateAdjacentTurfs() + +/obj/machinery/door/firedoor/proc/emergency_pressure_stop(consider_timer = TRUE) + set waitfor = 0 + if(density || operating || welded) + return + if(world.time >= emergency_close_timer || !consider_timer) + close() + +/obj/machinery/door/firedoor/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + var/obj/structure/firelock_frame/F = new assemblytype(get_turf(src)) + F.dir = src.dir + F.firelock_type = src.type + if(disassembled) + F.constructionStep = CONSTRUCTION_PANEL_OPEN + else + F.constructionStep = CONSTRUCTION_WIRES_EXPOSED + F.obj_integrity = F.max_integrity * 0.5 + F.update_icon() + qdel(src) + + +/obj/machinery/door/firedoor/proc/latetoggle() + if(operating || machine_stat & NOPOWER || !nextstate) + return + switch(nextstate) + if(FIREDOOR_OPEN) + nextstate = null + open() + if(FIREDOOR_CLOSED) + nextstate = null + close() + +/obj/machinery/door/firedoor/border_only + icon = 'icons/obj/doors/edge_Doorfire.dmi' + can_crush = FALSE + flags_1 = ON_BORDER_1 + CanAtmosPass = ATMOS_PASS_PROC + assemblytype = /obj/structure/firelock_frame/border + +/obj/machinery/door/firedoor/border_only/closed + icon_state = "door_closed" + opacity = TRUE + density = TRUE + +/obj/machinery/door/firedoor/border_only/close() + if(density) + return TRUE + if(operating || welded) + return + var/turf/T1 = get_turf(src) + var/turf/T2 = get_step(T1, dir) + for(var/mob/living/M in T1) + if(M.stat == CONSCIOUS && M.pulling && M.pulling.loc == T2 && !M.pulling.anchored && M.pulling.move_resist <= M.move_force) + var/mob/living/M2 = M.pulling + if(!istype(M2) || !M2.buckled || !M2.buckled.buckle_prevents_pull) + to_chat(M, "You pull [M.pulling] through [src] right as it closes") + M.pulling.forceMove(T1) + M.start_pulling(M2) + for(var/mob/living/M in T2) + if(M.stat == CONSCIOUS && M.pulling && M.pulling.loc == T1 && !M.pulling.anchored && M.pulling.move_resist <= M.move_force) + var/mob/living/M2 = M.pulling + if(!istype(M2) || !M2.buckled || !M2.buckled.buckle_prevents_pull) + to_chat(M, "You pull [M.pulling] through [src] right as it closes") + M.pulling.forceMove(T2) + M.start_pulling(M2) + . = ..() + +/obj/machinery/door/firedoor/border_only/allow_hand_open(mob/user) + var/area/A = get_area(src) + if((!A || !A.fire) && !is_holding_pressure()) + return TRUE + whack_a_mole(TRUE) // WOOP WOOP SIDE EFFECTS + var/turf/T = loc + var/turf/T2 = get_step(T, dir) + if(!T || !T2) + return + var/status1 = check_door_side(T) + var/status2 = check_door_side(T2) + if((status1 == 1 && status2 == -1) || (status1 == -1 && status2 == 1)) + to_chat(user, "Access denied. Try closing another firedoor to minimize decompression, or using a crowbar.") + return FALSE + return TRUE + +/obj/machinery/door/firedoor/border_only/proc/check_door_side(turf/open/start_point) + var/list/turfs = list() + turfs[start_point] = 1 + for(var/i = 1; (i <= turfs.len && i <= 11); i++) // check up to 11 turfs. + var/turf/open/T = turfs[i] + if(istype(T, /turf/open/space)) + return -1 + for(var/T2 in T.atmos_adjacent_turfs) + turfs[T2] = 1 + if(turfs.len <= 10) + return 0 // not big enough to matter + return start_point.air.return_pressure() < 20 ? -1 : 1 + +/obj/machinery/door/firedoor/border_only/CanAllowThrough(atom/movable/mover, turf/target) + . = ..() + if(istype(mover) && (mover.pass_flags & PASSGLASS)) + return TRUE + if(!(get_dir(loc, target) == dir)) //Make sure looking at appropriate border + return TRUE + +/obj/machinery/door/firedoor/border_only/CheckExit(atom/movable/mover as mob|obj, turf/target) + if(istype(mover) && (mover.pass_flags & PASSGLASS)) + return TRUE + if(get_dir(loc, target) == dir) + return !density + else + return TRUE + +/obj/machinery/door/firedoor/border_only/CanAtmosPass(turf/T) + if(get_dir(loc, T) == dir) + return !density + else + return TRUE + +/obj/machinery/door/firedoor/heavy + name = "heavy firelock" + icon = 'icons/obj/doors/doorfire.dmi' + glass = FALSE + explosion_block = 2 + assemblytype = /obj/structure/firelock_frame/heavy + max_integrity = 550 + +/obj/machinery/door/firedoor/window + name = "firelock window shutter" + icon = 'icons/obj/doors/doorfirewindow.dmi' + desc = "A second window that slides in when the original window is broken, designed to protect against hull breaches. Truly a work of genius by NT engineers." + glass = TRUE + explosion_block = 0 + max_integrity = 100 + resistance_flags = 0 // not fireproof + heat_proof = FALSE + assemblytype = /obj/structure/firelock_frame/window + +/obj/item/electronics/firelock + name = "firelock circuitry" + custom_price = 50 + desc = "A circuit board used in construction of firelocks." + icon_state = "mainboard" + +/obj/structure/firelock_frame + name = "firelock frame" + desc = "A partially completed firelock." + icon = 'icons/obj/doors/doorfire.dmi' + icon_state = "frame1" + anchored = FALSE + density = TRUE + var/constructionStep = CONSTRUCTION_NOCIRCUIT + var/reinforced = 0 + var/firelock_type + +/obj/structure/firelock_frame/examine(mob/user) + . = ..() + switch(constructionStep) + if(CONSTRUCTION_PANEL_OPEN) + . += "It is unbolted from the floor. A small loosely connected metal plate is covering the wires." + if(!reinforced) + . += "It could be reinforced with plasteel." + if(CONSTRUCTION_WIRES_EXPOSED) + . += "The maintenance plate has been pried away, and wires are trailing." + if(CONSTRUCTION_GUTTED) + . += "The maintenance panel is missing wires and the circuit board is loosely connected." + if(CONSTRUCTION_NOCIRCUIT) + . += "There are no firelock electronics in the frame. The frame could be cut apart." + +/obj/structure/firelock_frame/update_icon_state() + icon_state = "frame[constructionStep]" + +/obj/structure/firelock_frame/attackby(obj/item/C, mob/user) + switch(constructionStep) + if(CONSTRUCTION_PANEL_OPEN) + if(C.tool_behaviour == TOOL_CROWBAR) + C.play_tool_sound(src) + user.visible_message("[user] starts prying something out from [src]...", \ + "You begin prying out the wire cover...") + if(!C.use_tool(src, user, 50)) + return + if(constructionStep != CONSTRUCTION_PANEL_OPEN) + return + playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, TRUE) + user.visible_message("[user] pries out a metal plate from [src], exposing the wires.", \ + "You remove the cover plate from [src], exposing the wires.") + constructionStep = CONSTRUCTION_WIRES_EXPOSED + update_icon() + return + if(C.tool_behaviour == TOOL_WRENCH) + var/obj/machinery/door/firedoor/A = locate(/obj/machinery/door/firedoor) in get_turf(src) + if(A && A.dir == src.dir) + to_chat(user, "There's already a firelock there.") + return + C.play_tool_sound(src) + user.visible_message("[user] starts bolting down [src]...", \ + "You begin bolting [src]...") + if(!C.use_tool(src, user, 30)) + return + var/obj/machinery/door/firedoor/D = locate(/obj/machinery/door/firedoor) in get_turf(src) + if(D && D.dir == src.dir) + return + user.visible_message("[user] finishes the firelock.", \ + "You finish the firelock.") + playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, TRUE) + if(reinforced) + new /obj/machinery/door/firedoor/heavy(get_turf(src)) + else + var/obj/machinery/door/firedoor/F = new firelock_type(get_turf(src)) + F.dir = src.dir + F.update_icon() + qdel(src) + return + if(istype(C, /obj/item/stack/sheet/plasteel)) + var/obj/item/stack/sheet/plasteel/P = C + if(reinforced) + to_chat(user, "[src] is already reinforced.") + return + if(P.get_amount() < 2) + to_chat(user, "You need more plasteel to reinforce [src].") + return + user.visible_message("[user] begins reinforcing [src]...", \ + "You begin reinforcing [src]...") + playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, TRUE) + if(do_after(user, 60, target = src)) + if(constructionStep != CONSTRUCTION_PANEL_OPEN || reinforced || P.get_amount() < 2 || !P) + return + user.visible_message("[user] reinforces [src].", \ + "You reinforce [src].") + playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, TRUE) + P.use(2) + reinforced = 1 + return + + if(CONSTRUCTION_WIRES_EXPOSED) + if(C.tool_behaviour == TOOL_WIRECUTTER) + C.play_tool_sound(src) + user.visible_message("[user] starts cutting the wires from [src]...", \ + "You begin removing [src]'s wires...") + if(!C.use_tool(src, user, 60)) + return + if(constructionStep != CONSTRUCTION_WIRES_EXPOSED) + return + user.visible_message("[user] removes the wires from [src].", \ + "You remove the wiring from [src], exposing the circuit board.") + new/obj/item/stack/cable_coil(get_turf(src), 5) + constructionStep = CONSTRUCTION_GUTTED + update_icon() + return + if(C.tool_behaviour == TOOL_CROWBAR) + C.play_tool_sound(src) + user.visible_message("[user] starts prying a metal plate into [src]...", \ + "You begin prying the cover plate back onto [src]...") + if(!C.use_tool(src, user, 80)) + return + if(constructionStep != CONSTRUCTION_WIRES_EXPOSED) + return + playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, TRUE) + user.visible_message("[user] pries the metal plate into [src].", \ + "You pry [src]'s cover plate into place, hiding the wires.") + constructionStep = CONSTRUCTION_PANEL_OPEN + update_icon() + return + if(CONSTRUCTION_GUTTED) + if(C.tool_behaviour == TOOL_CROWBAR) + user.visible_message("[user] begins removing the circuit board from [src]...", \ + "You begin prying out the circuit board from [src]...") + if(!C.use_tool(src, user, 50, volume=50)) + return + if(constructionStep != CONSTRUCTION_GUTTED) + return + user.visible_message("[user] removes [src]'s circuit board.", \ + "You remove the circuit board from [src].") + new /obj/item/electronics/firelock(drop_location()) + constructionStep = CONSTRUCTION_NOCIRCUIT + update_icon() + return + if(istype(C, /obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/B = C + if(B.get_amount() < 5) + to_chat(user, "You need more wires to add wiring to [src].") + return + user.visible_message("[user] begins wiring [src]...", \ + "You begin adding wires to [src]...") + playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, TRUE) + if(do_after(user, 60, target = src)) + if(constructionStep != CONSTRUCTION_GUTTED || B.get_amount() < 5 || !B) + return + user.visible_message("[user] adds wires to [src].", \ + "You wire [src].") + playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, TRUE) + B.use(5) + constructionStep = CONSTRUCTION_WIRES_EXPOSED + update_icon() + return + if(CONSTRUCTION_NOCIRCUIT) + if(C.tool_behaviour == TOOL_WELDER) + if(!C.tool_start_check(user, amount=1)) + return + user.visible_message("[user] begins cutting apart [src]'s frame...", \ + "You begin slicing [src] apart...") + + if(C.use_tool(src, user, 40, volume=50, amount=1)) + if(constructionStep != CONSTRUCTION_NOCIRCUIT) + return + user.visible_message("[user] cuts apart [src]!", \ + "You cut [src] into metal.") + var/turf/T = get_turf(src) + new /obj/item/stack/sheet/metal(T, 3) + if(reinforced) + new /obj/item/stack/sheet/plasteel(T, 2) + qdel(src) + return + if(istype(C, /obj/item/electronics/firelock)) + user.visible_message("[user] starts adding [C] to [src]...", \ + "You begin adding a circuit board to [src]...") + playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, TRUE) + if(!do_after(user, 40, target = src)) + return + if(constructionStep != CONSTRUCTION_NOCIRCUIT) + return + qdel(C) + user.visible_message("[user] adds a circuit to [src].", \ + "You insert and secure [C].") + playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, TRUE) + constructionStep = CONSTRUCTION_GUTTED + update_icon() + return + if(istype(C, /obj/item/electroadaptive_pseudocircuit)) + var/obj/item/electroadaptive_pseudocircuit/P = C + if(!P.adapt_circuit(user, 30)) + return + user.visible_message("[user] fabricates a circuit and places it into [src].", \ + "You adapt a firelock circuit and slot it into the assembly.") + constructionStep = CONSTRUCTION_GUTTED + update_icon() + return + return ..() + +/obj/structure/firelock_frame/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd) + if(the_rcd.mode == RCD_DECONSTRUCT) + return list("mode" = RCD_DECONSTRUCT, "delay" = 50, "cost" = 16) + else if((constructionStep == CONSTRUCTION_NOCIRCUIT) && (the_rcd.upgrade & RCD_UPGRADE_SIMPLE_CIRCUITS)) + return list("mode" = RCD_UPGRADE_SIMPLE_CIRCUITS, "delay" = 20, "cost" = 1) + return FALSE + +/obj/structure/firelock_frame/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode) + switch(passed_mode) + if(RCD_UPGRADE_SIMPLE_CIRCUITS) + user.visible_message("[user] fabricates a circuit and places it into [src].", \ + "You adapt a firelock circuit and slot it into the assembly.") + constructionStep = CONSTRUCTION_GUTTED + update_icon() + return TRUE + if(RCD_DECONSTRUCT) + to_chat(user, "You deconstruct [src].") + qdel(src) + return TRUE + return FALSE + +/obj/structure/firelock_frame/heavy + name = "heavy firelock frame" + reinforced = TRUE + +/obj/structure/firelock_frame/border + name = "firelock frame" + icon = 'icons/obj/doors/edge_Doorfire.dmi' + icon_state = "frame1" + firelock_type = /obj/machinery/door/firedoor/border_only/closed + flags_1 = ON_BORDER_1 + +/obj/structure/firelock_frame/border/ComponentInitialize() + . = ..() + AddComponent(/datum/component/simple_rotation, ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS, null, CALLBACK(src, .proc/can_be_rotated)) + +/obj/structure/firelock_frame/border/proc/can_be_rotated(mob/user, rotation_type) + if (anchored) + to_chat(user, "It is fastened to the floor!") + return FALSE + return TRUE + +/obj/structure/firelock_frame/window + name = "window firelock frame" + icon = 'icons/obj/doors/doorfirewindow.dmi' + icon_state = "door_frame" + +/obj/structure/firelock_frame/window/update_icon() + return + +#undef CONSTRUCTION_COMPLETE +#undef CONSTRUCTION_PANEL_OPEN +#undef CONSTRUCTION_WIRES_EXPOSED +#undef CONSTRUCTION_GUTTED +#undef CONSTRUCTION_NOCIRCUIT diff --git a/code/game/machinery/doors/poddoor.dm b/code/game/machinery/doors/poddoor.dm index dfe84db4e544..c38755b53f48 100644 --- a/code/game/machinery/doors/poddoor.dm +++ b/code/game/machinery/doors/poddoor.dm @@ -1,116 +1,116 @@ -/obj/machinery/door/poddoor - name = "blast door" - desc = "A heavy duty blast door that opens mechanically." - icon = 'icons/obj/doors/blastdoor.dmi' - icon_state = "closed" - var/id = 1 - layer = BLASTDOOR_LAYER - closingLayer = CLOSED_BLASTDOOR_LAYER - sub_door = TRUE - explosion_block = 3 - heat_proof = TRUE - safe = FALSE - max_integrity = 600 - armor = list("melee" = 50, "bullet" = 100, "laser" = 100, "energy" = 100, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 70) - resistance_flags = FIRE_PROOF - damage_deflection = 70 - poddoor = TRUE - -/obj/machinery/door/poddoor/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) - id = "[idnum][id]" - -/obj/machinery/door/poddoor/preopen - icon_state = "open" - density = FALSE - opacity = 0 - -/obj/machinery/door/poddoor/ert - name = "hardened blast door" - desc = "A heavy duty blast door that only opens for dire emergencies." - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - -//special poddoors that open when emergency shuttle docks at centcom -/obj/machinery/door/poddoor/shuttledock - var/checkdir = 4 //door won't open if turf in this dir is `turftype` - var/turftype = /turf/open/space - -/obj/machinery/door/poddoor/shuttledock/proc/check() - var/turf/T = get_step(src, checkdir) - if(!istype(T, turftype)) - INVOKE_ASYNC(src, .proc/open) - else - INVOKE_ASYNC(src, .proc/close) - -/obj/machinery/door/poddoor/incinerator_toxmix - name = "combustion chamber vent" - id = INCINERATOR_TOXMIX_VENT - -/obj/machinery/door/poddoor/incinerator_atmos_main - name = "turbine vent" - id = INCINERATOR_ATMOS_MAINVENT - -/obj/machinery/door/poddoor/incinerator_atmos_aux - name = "combustion chamber vent" - id = INCINERATOR_ATMOS_AUXVENT - -/obj/machinery/door/poddoor/incinerator_syndicatelava_main - name = "turbine vent" - id = INCINERATOR_SYNDICATELAVA_MAINVENT - -/obj/machinery/door/poddoor/incinerator_syndicatelava_aux - name = "combustion chamber vent" - id = INCINERATOR_SYNDICATELAVA_AUXVENT - -/obj/machinery/door/poddoor/Bumped(atom/movable/AM) - if(density) - return 0 - else - return ..() - -//"BLAST" doors are obviously stronger than regular doors when it comes to BLASTS. -/obj/machinery/door/poddoor/ex_act(severity, target) - if(severity == 3) - return - ..() - -/obj/machinery/door/poddoor/do_animate(animation) - switch(animation) - if("opening") - flick("opening", src) - playsound(src, 'sound/machines/blastdoor.ogg', 30, TRUE) - if("closing") - flick("closing", src) - playsound(src, 'sound/machines/blastdoor.ogg', 30, TRUE) - -/obj/machinery/door/poddoor/update_icon_state() - if(density) - icon_state = "closed" - else - icon_state = "open" - -/obj/machinery/door/poddoor/try_to_activate_door(mob/user) - return - -/obj/machinery/door/poddoor/try_to_crowbar(obj/item/I, mob/user) - if(machine_stat & NOPOWER) - open(TRUE) - -/obj/machinery/door/poddoor/attack_alien(mob/living/carbon/alien/humanoid/user) - if(density & !(resistance_flags & INDESTRUCTIBLE)) - add_fingerprint(user) - user.visible_message("[user] begins prying open [src].",\ - "You begin digging your claws into [src] with all your might!",\ - "You hear groaning metal...") - playsound(src, 'sound/machines/airlock_alien_prying.ogg', 100, TRUE) - - var/time_to_open = 5 SECONDS - if(hasPower()) - time_to_open = 15 SECONDS - - if(do_after(user, time_to_open, TRUE, src)) - if(density && !open(TRUE)) //The airlock is still closed, but something prevented it opening. (Another player noticed and bolted/welded the airlock in time!) - to_chat(user, "Despite your efforts, [src] managed to resist your attempts to open it!") - - else - return ..() - +/obj/machinery/door/poddoor + name = "blast door" + desc = "A heavy duty blast door that opens mechanically." + icon = 'icons/obj/doors/blastdoor.dmi' + icon_state = "closed" + var/id = 1 + layer = BLASTDOOR_LAYER + closingLayer = CLOSED_BLASTDOOR_LAYER + sub_door = TRUE + explosion_block = 3 + heat_proof = TRUE + safe = FALSE + max_integrity = 600 + armor = list("melee" = 50, "bullet" = 100, "laser" = 100, "energy" = 100, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 70) + resistance_flags = FIRE_PROOF + damage_deflection = 70 + poddoor = TRUE + +/obj/machinery/door/poddoor/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) + id = "[idnum][id]" + +/obj/machinery/door/poddoor/preopen + icon_state = "open" + density = FALSE + opacity = 0 + +/obj/machinery/door/poddoor/ert + name = "hardened blast door" + desc = "A heavy duty blast door that only opens for dire emergencies." + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + +//special poddoors that open when emergency shuttle docks at centcom +/obj/machinery/door/poddoor/shuttledock + var/checkdir = 4 //door won't open if turf in this dir is `turftype` + var/turftype = /turf/open/space + +/obj/machinery/door/poddoor/shuttledock/proc/check() + var/turf/T = get_step(src, checkdir) + if(!istype(T, turftype)) + INVOKE_ASYNC(src, .proc/open) + else + INVOKE_ASYNC(src, .proc/close) + +/obj/machinery/door/poddoor/incinerator_toxmix + name = "combustion chamber vent" + id = INCINERATOR_TOXMIX_VENT + +/obj/machinery/door/poddoor/incinerator_atmos_main + name = "turbine vent" + id = INCINERATOR_ATMOS_MAINVENT + +/obj/machinery/door/poddoor/incinerator_atmos_aux + name = "combustion chamber vent" + id = INCINERATOR_ATMOS_AUXVENT + +/obj/machinery/door/poddoor/incinerator_syndicatelava_main + name = "turbine vent" + id = INCINERATOR_SYNDICATELAVA_MAINVENT + +/obj/machinery/door/poddoor/incinerator_syndicatelava_aux + name = "combustion chamber vent" + id = INCINERATOR_SYNDICATELAVA_AUXVENT + +/obj/machinery/door/poddoor/Bumped(atom/movable/AM) + if(density) + return 0 + else + return ..() + +//"BLAST" doors are obviously stronger than regular doors when it comes to BLASTS. +/obj/machinery/door/poddoor/ex_act(severity, target) + if(severity == 3) + return + ..() + +/obj/machinery/door/poddoor/do_animate(animation) + switch(animation) + if("opening") + flick("opening", src) + playsound(src, 'sound/machines/blastdoor.ogg', 30, TRUE) + if("closing") + flick("closing", src) + playsound(src, 'sound/machines/blastdoor.ogg', 30, TRUE) + +/obj/machinery/door/poddoor/update_icon_state() + if(density) + icon_state = "closed" + else + icon_state = "open" + +/obj/machinery/door/poddoor/try_to_activate_door(mob/user) + return + +/obj/machinery/door/poddoor/try_to_crowbar(obj/item/I, mob/user) + if(machine_stat & NOPOWER) + open(TRUE) + +/obj/machinery/door/poddoor/attack_alien(mob/living/carbon/alien/humanoid/user) + if(density & !(resistance_flags & INDESTRUCTIBLE)) + add_fingerprint(user) + user.visible_message("[user] begins prying open [src].",\ + "You begin digging your claws into [src] with all your might!",\ + "You hear groaning metal...") + playsound(src, 'sound/machines/airlock_alien_prying.ogg', 100, TRUE) + + var/time_to_open = 5 SECONDS + if(hasPower()) + time_to_open = 15 SECONDS + + if(do_after(user, time_to_open, TRUE, src)) + if(density && !open(TRUE)) //The airlock is still closed, but something prevented it opening. (Another player noticed and bolted/welded the airlock in time!) + to_chat(user, "Despite your efforts, [src] managed to resist your attempts to open it!") + + else + return ..() + diff --git a/code/game/machinery/doors/shutters.dm b/code/game/machinery/doors/shutters.dm index fb75dc275fe9..cb0262e2d8e3 100644 --- a/code/game/machinery/doors/shutters.dm +++ b/code/game/machinery/doors/shutters.dm @@ -1,17 +1,17 @@ -/obj/machinery/door/poddoor/shutters - gender = PLURAL - name = "shutters" - desc = "Heavy duty metal shutters that open mechanically." - icon = 'icons/obj/doors/shutters.dmi' - layer = SHUTTER_LAYER - closingLayer = SHUTTER_LAYER - damage_deflection = 20 - -/obj/machinery/door/poddoor/shutters/preopen - icon_state = "open" - density = FALSE - opacity = 0 - -/obj/machinery/door/poddoor/shutters/indestructible - name = "hardened shutters" - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF +/obj/machinery/door/poddoor/shutters + gender = PLURAL + name = "shutters" + desc = "Heavy duty metal shutters that open mechanically." + icon = 'icons/obj/doors/shutters.dmi' + layer = SHUTTER_LAYER + closingLayer = SHUTTER_LAYER + damage_deflection = 20 + +/obj/machinery/door/poddoor/shutters/preopen + icon_state = "open" + density = FALSE + opacity = 0 + +/obj/machinery/door/poddoor/shutters/indestructible + name = "hardened shutters" + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF diff --git a/code/game/machinery/doors/unpowered.dm b/code/game/machinery/doors/unpowered.dm index 324d8087ce30..5ced36c775ba 100644 --- a/code/game/machinery/doors/unpowered.dm +++ b/code/game/machinery/doors/unpowered.dm @@ -1,25 +1,25 @@ -/obj/machinery/door/unpowered - -/obj/machinery/door/unpowered/Bumped(atom/movable/AM) - if(src.locked) - return - ..() - return - - -/obj/machinery/door/unpowered/attackby(obj/item/I, mob/user, params) - if(locked) - return - else - return ..() - -/obj/machinery/door/unpowered/emag_act() - return - -/obj/machinery/door/unpowered/shuttle - icon = 'icons/turf/shuttle.dmi' - name = "door" - icon_state = "door1" - opacity = 1 - density = TRUE - explosion_block = 1 +/obj/machinery/door/unpowered + +/obj/machinery/door/unpowered/Bumped(atom/movable/AM) + if(src.locked) + return + ..() + return + + +/obj/machinery/door/unpowered/attackby(obj/item/I, mob/user, params) + if(locked) + return + else + return ..() + +/obj/machinery/door/unpowered/emag_act() + return + +/obj/machinery/door/unpowered/shuttle + icon = 'icons/turf/shuttle.dmi' + name = "door" + icon_state = "door1" + opacity = 1 + density = TRUE + explosion_block = 1 diff --git a/code/game/machinery/doors/windowdoor.dm b/code/game/machinery/doors/windowdoor.dm index 3f419f6a2e82..59018c13b98c 100644 --- a/code/game/machinery/doors/windowdoor.dm +++ b/code/game/machinery/doors/windowdoor.dm @@ -1,481 +1,481 @@ -/obj/machinery/door/window - name = "interior door" - desc = "A strong door." - icon = 'icons/obj/doors/windoor.dmi' - icon_state = "left" - layer = ABOVE_WINDOW_LAYER - closingLayer = ABOVE_WINDOW_LAYER - resistance_flags = ACID_PROOF - var/base_state = "left" - max_integrity = 150 //If you change this, consider changing ../door/window/brigdoor/ max_integrity at the bottom of this .dm file - integrity_failure = 0 - armor = list("melee" = 20, "bullet" = 50, "laser" = 50, "energy" = 50, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 70, "acid" = 100) - visible = FALSE - flags_1 = ON_BORDER_1 - opacity = 0 - CanAtmosPass = ATMOS_PASS_PROC - interaction_flags_machine = INTERACT_MACHINE_WIRES_IF_OPEN | INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OPEN_SILICON | INTERACT_MACHINE_REQUIRES_SILICON | INTERACT_MACHINE_OPEN - var/obj/item/electronics/airlock/electronics = null - var/reinf = 0 - var/shards = 2 - var/rods = 2 - var/cable = 1 - var/list/debris = list() - -/obj/machinery/door/window/Initialize(mapload, set_dir) - . = ..() - flags_1 &= ~PREVENT_CLICK_UNDER_1 - if(set_dir) - setDir(set_dir) - if(req_access && req_access.len) - icon_state = "[icon_state]" - base_state = icon_state - for(var/i in 1 to shards) - debris += new /obj/item/shard(src) - if(rods) - debris += new /obj/item/stack/rods(src, rods) - if(cable) - debris += new /obj/item/stack/cable_coil(src, cable) - -/obj/machinery/door/window/ComponentInitialize() - . = ..() - AddComponent(/datum/component/ntnet_interface) - -/obj/machinery/door/window/Destroy() - density = FALSE - QDEL_LIST(debris) - if(obj_integrity == 0) - playsound(src, "shatter", 70, TRUE) - electronics = null - return ..() - -/obj/machinery/door/window/update_icon_state() - if(density) - icon_state = base_state - else - icon_state = "[base_state]open" - -/obj/machinery/door/window/proc/open_and_close() - if(!open()) - return - autoclose = TRUE - if(check_access(null)) - sleep(50) - else //secure doors close faster - sleep(20) - if(!density && autoclose) //did someone change state while we slept? - close() - -/obj/machinery/door/window/Bumped(atom/movable/AM) - if( operating || !density ) - return - if (!( ismob(AM) )) - if(ismecha(AM)) - var/obj/mecha/mecha = AM - if(mecha.occupant && allowed(mecha.occupant)) - open_and_close() - else - do_animate("deny") - return - if (!( SSticker )) - return - var/mob/M = AM - if(M.restrained() || ((isdrone(M) || iscyborg(M)) && M.stat)) - return - bumpopen(M) - -/obj/machinery/door/window/bumpopen(mob/user) - if( operating || !density ) - return - add_fingerprint(user) - if(!requiresID()) - user = null - - if(allowed(user)) - open_and_close() - else - do_animate("deny") - return - -/obj/machinery/door/window/CanAllowThrough(atom/movable/mover, turf/target) - . = ..() - if(istype(mover) && (mover.pass_flags & PASSGLASS)) - return TRUE - if(get_dir(loc, target) == dir) //Make sure looking at appropriate border - return - if(istype(mover, /obj/structure/window)) - var/obj/structure/window/W = mover - if(!valid_window_location(loc, W.ini_dir)) - return FALSE - else if(istype(mover, /obj/structure/windoor_assembly)) - var/obj/structure/windoor_assembly/W = mover - if(!valid_window_location(loc, W.ini_dir)) - return FALSE - else if(istype(mover, /obj/machinery/door/window) && !valid_window_location(loc, mover.dir)) - return FALSE - else - return TRUE - -/obj/machinery/door/window/CanAtmosPass(turf/T) - if(get_dir(loc, T) == dir) - return !density - else - return 1 - -//used in the AStar algorithm to determinate if the turf the door is on is passable -/obj/machinery/door/window/CanAStarPass(obj/item/card/id/ID, to_dir) - return !density || (dir != to_dir) || (check_access(ID) && hasPower()) - -/obj/machinery/door/window/CheckExit(atom/movable/mover as mob|obj, turf/target) - if(istype(mover) && (mover.pass_flags & PASSGLASS)) - return 1 - if(get_dir(loc, target) == dir) - return !density - else - return 1 - -/obj/machinery/door/window/open(forced=FALSE) - if (operating) //doors can still open when emag-disabled - return 0 - if(!forced) - if(!hasPower()) - return 0 - if(forced < 2) - if(obj_flags & EMAGGED) - return 0 - if(!operating) //in case of emag - operating = TRUE - do_animate("opening") - playsound(src, 'sound/machines/windowdoor.ogg', 100, TRUE) - icon_state ="[base_state]open" - sleep(10) - density = FALSE - air_update_turf(1) - update_freelook_sight() - - if(operating == 1) //emag again - operating = FALSE - return 1 - -/obj/machinery/door/window/close(forced=FALSE) - if (operating) - return 0 - if(!forced) - if(!hasPower()) - return 0 - if(forced < 2) - if(obj_flags & EMAGGED) - return 0 - operating = TRUE - do_animate("closing") - playsound(src, 'sound/machines/windowdoor.ogg', 100, TRUE) - icon_state = base_state - - density = TRUE - air_update_turf(1) - update_freelook_sight() - sleep(10) - - operating = FALSE - return 1 - -/obj/machinery/door/window/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) - switch(damage_type) - if(BRUTE) - playsound(src, 'sound/effects/glasshit.ogg', 90, TRUE) - if(BURN) - playsound(src, 'sound/items/welder.ogg', 100, TRUE) - - -/obj/machinery/door/window/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1) && !disassembled) - for(var/obj/fragment in debris) - fragment.forceMove(get_turf(src)) - transfer_fingerprints_to(fragment) - debris -= fragment - qdel(src) - -/obj/machinery/door/window/narsie_act() - add_atom_colour("#7D1919", FIXED_COLOUR_PRIORITY) - -/obj/machinery/door/window/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) - if(exposed_temperature > T0C + (reinf ? 1600 : 800)) - take_damage(round(exposed_volume / 200), BURN, 0, 0) - ..() - -/obj/machinery/door/window/emag_act(mob/user) - if(!operating && density && !(obj_flags & EMAGGED)) - obj_flags |= EMAGGED - operating = TRUE - flick("[base_state]spark", src) - playsound(src, "sparks", 75, TRUE) - sleep(6) - operating = FALSE - desc += "
    Its access panel is smoking slightly." - open(2) - -/obj/machinery/door/window/attackby(obj/item/I, mob/living/user, params) - - if(operating) - return - - add_fingerprint(user) - if(!(flags_1&NODECONSTRUCT_1)) - if(I.tool_behaviour == TOOL_SCREWDRIVER) - if(density || operating) - to_chat(user, "You need to open the door to access the maintenance panel!") - return - I.play_tool_sound(src) - panel_open = !panel_open - to_chat(user, "You [panel_open ? "open":"close"] the maintenance panel of the [name].") - return - - if(I.tool_behaviour == TOOL_CROWBAR) - if(panel_open && !density && !operating) - user.visible_message("[user] removes the electronics from the [name].", \ - "You start to remove electronics from the [name]...") - if(I.use_tool(src, user, 40, volume=50)) - if(panel_open && !density && !operating && loc) - var/obj/structure/windoor_assembly/WA = new /obj/structure/windoor_assembly(loc) - switch(base_state) - if("left") - WA.facing = "l" - if("right") - WA.facing = "r" - if("leftsecure") - WA.facing = "l" - WA.secure = TRUE - if("rightsecure") - WA.facing = "r" - WA.secure = TRUE - WA.setAnchored(TRUE) - WA.state= "02" - WA.setDir(dir) - WA.ini_dir = dir - WA.update_icon() - WA.created_name = name - - if(obj_flags & EMAGGED) - to_chat(user, "You discard the damaged electronics.") - qdel(src) - return - - to_chat(user, "You remove the airlock electronics.") - - var/obj/item/electronics/airlock/ae - if(!electronics) - ae = new/obj/item/electronics/airlock(drop_location()) - if(req_one_access) - ae.one_access = 1 - ae.accesses = req_one_access - else - ae.accesses = req_access - else - ae = electronics - electronics = null - ae.forceMove(drop_location()) - - qdel(src) - return - return ..() - -/obj/machinery/door/window/interact(mob/user) //for sillycones - try_to_activate_door(user) - -/obj/machinery/door/window/try_to_activate_door(mob/user) - if (..()) - autoclose = FALSE - -/obj/machinery/door/window/try_to_crowbar(obj/item/I, mob/user) - if(!hasPower()) - if(density) - open(2) - else - close(2) - else - to_chat(user, "The door's motors resist your efforts to force it!") - -/obj/machinery/door/window/do_animate(animation) - switch(animation) - if("opening") - flick("[base_state]opening", src) - if("closing") - flick("[base_state]closing", src) - if("deny") - flick("[base_state]deny", src) - -/obj/machinery/door/window/check_access_ntnet(datum/netdata/data) - return !requiresID() || ..() - -/obj/machinery/door/window/ntnet_receive(datum/netdata/data) - // Check if the airlock is powered. - if(!hasPower()) - return - - // Check packet access level. - if(!check_access_ntnet(data)) - return - - // Handle received packet. - var/command = lowertext(data.data["data"]) - var/command_value = lowertext(data.data["data_secondary"]) - switch(command) - if("open") - if(command_value == "on" && !density) - return - - if(command_value == "off" && density) - return - - if(density) - INVOKE_ASYNC(src, .proc/open) - else - INVOKE_ASYNC(src, .proc/close) - if("touch") - INVOKE_ASYNC(src, .proc/open_and_close) - -/obj/machinery/door/window/brigdoor - name = "secure door" - icon_state = "leftsecure" - base_state = "leftsecure" - var/id = null - max_integrity = 300 //Stronger doors for prison (regular window door health is 200) - reinf = 1 - explosion_block = 1 - -/obj/machinery/door/window/brigdoor/security/cell - name = "cell door" - desc = "For keeping in criminal scum." - req_access = list(ACCESS_BRIG) - -/obj/machinery/door/window/brigdoor/security/holding - name = "holding cell door" - req_one_access = list(ACCESS_SEC_DOORS, ACCESS_LAWYER) //love for the lawyer - -/obj/machinery/door/window/northleft - dir = NORTH - -/obj/machinery/door/window/eastleft - dir = EAST - -/obj/machinery/door/window/westleft - dir = WEST - -/obj/machinery/door/window/southleft - dir = SOUTH - -/obj/machinery/door/window/northright - dir = NORTH - icon_state = "right" - base_state = "right" - -/obj/machinery/door/window/eastright - dir = EAST - icon_state = "right" - base_state = "right" - -/obj/machinery/door/window/westright - dir = WEST - icon_state = "right" - base_state = "right" - -/obj/machinery/door/window/southright - dir = SOUTH - icon_state = "right" - base_state = "right" - -/obj/machinery/door/window/brigdoor/northleft - dir = NORTH - -/obj/machinery/door/window/brigdoor/eastleft - dir = EAST - -/obj/machinery/door/window/brigdoor/westleft - dir = WEST - -/obj/machinery/door/window/brigdoor/southleft - dir = SOUTH - -/obj/machinery/door/window/brigdoor/northright - dir = NORTH - icon_state = "rightsecure" - base_state = "rightsecure" - -/obj/machinery/door/window/brigdoor/eastright - dir = EAST - icon_state = "rightsecure" - base_state = "rightsecure" - -/obj/machinery/door/window/brigdoor/westright - dir = WEST - icon_state = "rightsecure" - base_state = "rightsecure" - -/obj/machinery/door/window/brigdoor/southright - dir = SOUTH - icon_state = "rightsecure" - base_state = "rightsecure" - -/obj/machinery/door/window/brigdoor/security/cell/northleft - dir = NORTH - -/obj/machinery/door/window/brigdoor/security/cell/eastleft - dir = EAST - -/obj/machinery/door/window/brigdoor/security/cell/westleft - dir = WEST - -/obj/machinery/door/window/brigdoor/security/cell/southleft - dir = SOUTH - -/obj/machinery/door/window/brigdoor/security/cell/northright - dir = NORTH - icon_state = "rightsecure" - base_state = "rightsecure" - -/obj/machinery/door/window/brigdoor/security/cell/eastright - dir = EAST - icon_state = "rightsecure" - base_state = "rightsecure" - -/obj/machinery/door/window/brigdoor/security/cell/westright - dir = WEST - icon_state = "rightsecure" - base_state = "rightsecure" - -/obj/machinery/door/window/brigdoor/security/cell/southright - dir = SOUTH - icon_state = "rightsecure" - base_state = "rightsecure" - -/obj/machinery/door/window/brigdoor/security/holding/northleft - dir = NORTH - -/obj/machinery/door/window/brigdoor/security/holding/eastleft - dir = EAST - -/obj/machinery/door/window/brigdoor/security/holding/westleft - dir = WEST - -/obj/machinery/door/window/brigdoor/security/holding/southleft - dir = SOUTH - -/obj/machinery/door/window/brigdoor/security/holding/northright - dir = NORTH - icon_state = "rightsecure" - base_state = "rightsecure" - -/obj/machinery/door/window/brigdoor/security/holding/eastright - dir = EAST - icon_state = "rightsecure" - base_state = "rightsecure" - -/obj/machinery/door/window/brigdoor/security/holding/westright - dir = WEST - icon_state = "rightsecure" - base_state = "rightsecure" - -/obj/machinery/door/window/brigdoor/security/holding/southright - dir = SOUTH - icon_state = "rightsecure" - base_state = "rightsecure" +/obj/machinery/door/window + name = "interior door" + desc = "A strong door." + icon = 'icons/obj/doors/windoor.dmi' + icon_state = "left" + layer = ABOVE_WINDOW_LAYER + closingLayer = ABOVE_WINDOW_LAYER + resistance_flags = ACID_PROOF + var/base_state = "left" + max_integrity = 150 //If you change this, consider changing ../door/window/brigdoor/ max_integrity at the bottom of this .dm file + integrity_failure = 0 + armor = list("melee" = 20, "bullet" = 50, "laser" = 50, "energy" = 50, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 70, "acid" = 100) + visible = FALSE + flags_1 = ON_BORDER_1 + opacity = 0 + CanAtmosPass = ATMOS_PASS_PROC + interaction_flags_machine = INTERACT_MACHINE_WIRES_IF_OPEN | INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OPEN_SILICON | INTERACT_MACHINE_REQUIRES_SILICON | INTERACT_MACHINE_OPEN + var/obj/item/electronics/airlock/electronics = null + var/reinf = 0 + var/shards = 2 + var/rods = 2 + var/cable = 1 + var/list/debris = list() + +/obj/machinery/door/window/Initialize(mapload, set_dir) + . = ..() + flags_1 &= ~PREVENT_CLICK_UNDER_1 + if(set_dir) + setDir(set_dir) + if(req_access && req_access.len) + icon_state = "[icon_state]" + base_state = icon_state + for(var/i in 1 to shards) + debris += new /obj/item/shard(src) + if(rods) + debris += new /obj/item/stack/rods(src, rods) + if(cable) + debris += new /obj/item/stack/cable_coil(src, cable) + +/obj/machinery/door/window/ComponentInitialize() + . = ..() + AddComponent(/datum/component/ntnet_interface) + +/obj/machinery/door/window/Destroy() + density = FALSE + QDEL_LIST(debris) + if(obj_integrity == 0) + playsound(src, "shatter", 70, TRUE) + electronics = null + return ..() + +/obj/machinery/door/window/update_icon_state() + if(density) + icon_state = base_state + else + icon_state = "[base_state]open" + +/obj/machinery/door/window/proc/open_and_close() + if(!open()) + return + autoclose = TRUE + if(check_access(null)) + sleep(50) + else //secure doors close faster + sleep(20) + if(!density && autoclose) //did someone change state while we slept? + close() + +/obj/machinery/door/window/Bumped(atom/movable/AM) + if( operating || !density ) + return + if (!( ismob(AM) )) + if(ismecha(AM)) + var/obj/mecha/mecha = AM + if(mecha.occupant && allowed(mecha.occupant)) + open_and_close() + else + do_animate("deny") + return + if (!( SSticker )) + return + var/mob/M = AM + if(M.restrained() || ((isdrone(M) || iscyborg(M)) && M.stat)) + return + bumpopen(M) + +/obj/machinery/door/window/bumpopen(mob/user) + if( operating || !density ) + return + add_fingerprint(user) + if(!requiresID()) + user = null + + if(allowed(user)) + open_and_close() + else + do_animate("deny") + return + +/obj/machinery/door/window/CanAllowThrough(atom/movable/mover, turf/target) + . = ..() + if(istype(mover) && (mover.pass_flags & PASSGLASS)) + return TRUE + if(get_dir(loc, target) == dir) //Make sure looking at appropriate border + return + if(istype(mover, /obj/structure/window)) + var/obj/structure/window/W = mover + if(!valid_window_location(loc, W.ini_dir)) + return FALSE + else if(istype(mover, /obj/structure/windoor_assembly)) + var/obj/structure/windoor_assembly/W = mover + if(!valid_window_location(loc, W.ini_dir)) + return FALSE + else if(istype(mover, /obj/machinery/door/window) && !valid_window_location(loc, mover.dir)) + return FALSE + else + return TRUE + +/obj/machinery/door/window/CanAtmosPass(turf/T) + if(get_dir(loc, T) == dir) + return !density + else + return 1 + +//used in the AStar algorithm to determinate if the turf the door is on is passable +/obj/machinery/door/window/CanAStarPass(obj/item/card/id/ID, to_dir) + return !density || (dir != to_dir) || (check_access(ID) && hasPower()) + +/obj/machinery/door/window/CheckExit(atom/movable/mover as mob|obj, turf/target) + if(istype(mover) && (mover.pass_flags & PASSGLASS)) + return 1 + if(get_dir(loc, target) == dir) + return !density + else + return 1 + +/obj/machinery/door/window/open(forced=FALSE) + if (operating) //doors can still open when emag-disabled + return 0 + if(!forced) + if(!hasPower()) + return 0 + if(forced < 2) + if(obj_flags & EMAGGED) + return 0 + if(!operating) //in case of emag + operating = TRUE + do_animate("opening") + playsound(src, 'sound/machines/windowdoor.ogg', 100, TRUE) + icon_state ="[base_state]open" + sleep(10) + density = FALSE + air_update_turf(1) + update_freelook_sight() + + if(operating == 1) //emag again + operating = FALSE + return 1 + +/obj/machinery/door/window/close(forced=FALSE) + if (operating) + return 0 + if(!forced) + if(!hasPower()) + return 0 + if(forced < 2) + if(obj_flags & EMAGGED) + return 0 + operating = TRUE + do_animate("closing") + playsound(src, 'sound/machines/windowdoor.ogg', 100, TRUE) + icon_state = base_state + + density = TRUE + air_update_turf(1) + update_freelook_sight() + sleep(10) + + operating = FALSE + return 1 + +/obj/machinery/door/window/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) + switch(damage_type) + if(BRUTE) + playsound(src, 'sound/effects/glasshit.ogg', 90, TRUE) + if(BURN) + playsound(src, 'sound/items/welder.ogg', 100, TRUE) + + +/obj/machinery/door/window/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1) && !disassembled) + for(var/obj/fragment in debris) + fragment.forceMove(get_turf(src)) + transfer_fingerprints_to(fragment) + debris -= fragment + qdel(src) + +/obj/machinery/door/window/narsie_act() + add_atom_colour("#7D1919", FIXED_COLOUR_PRIORITY) + +/obj/machinery/door/window/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) + if(exposed_temperature > T0C + (reinf ? 1600 : 800)) + take_damage(round(exposed_volume / 200), BURN, 0, 0) + ..() + +/obj/machinery/door/window/emag_act(mob/user) + if(!operating && density && !(obj_flags & EMAGGED)) + obj_flags |= EMAGGED + operating = TRUE + flick("[base_state]spark", src) + playsound(src, "sparks", 75, TRUE) + sleep(6) + operating = FALSE + desc += "
    Its access panel is smoking slightly." + open(2) + +/obj/machinery/door/window/attackby(obj/item/I, mob/living/user, params) + + if(operating) + return + + add_fingerprint(user) + if(!(flags_1&NODECONSTRUCT_1)) + if(I.tool_behaviour == TOOL_SCREWDRIVER) + if(density || operating) + to_chat(user, "You need to open the door to access the maintenance panel!") + return + I.play_tool_sound(src) + panel_open = !panel_open + to_chat(user, "You [panel_open ? "open":"close"] the maintenance panel of the [name].") + return + + if(I.tool_behaviour == TOOL_CROWBAR) + if(panel_open && !density && !operating) + user.visible_message("[user] removes the electronics from the [name].", \ + "You start to remove electronics from the [name]...") + if(I.use_tool(src, user, 40, volume=50)) + if(panel_open && !density && !operating && loc) + var/obj/structure/windoor_assembly/WA = new /obj/structure/windoor_assembly(loc) + switch(base_state) + if("left") + WA.facing = "l" + if("right") + WA.facing = "r" + if("leftsecure") + WA.facing = "l" + WA.secure = TRUE + if("rightsecure") + WA.facing = "r" + WA.secure = TRUE + WA.setAnchored(TRUE) + WA.state= "02" + WA.setDir(dir) + WA.ini_dir = dir + WA.update_icon() + WA.created_name = name + + if(obj_flags & EMAGGED) + to_chat(user, "You discard the damaged electronics.") + qdel(src) + return + + to_chat(user, "You remove the airlock electronics.") + + var/obj/item/electronics/airlock/ae + if(!electronics) + ae = new/obj/item/electronics/airlock(drop_location()) + if(req_one_access) + ae.one_access = 1 + ae.accesses = req_one_access + else + ae.accesses = req_access + else + ae = electronics + electronics = null + ae.forceMove(drop_location()) + + qdel(src) + return + return ..() + +/obj/machinery/door/window/interact(mob/user) //for sillycones + try_to_activate_door(user) + +/obj/machinery/door/window/try_to_activate_door(mob/user) + if (..()) + autoclose = FALSE + +/obj/machinery/door/window/try_to_crowbar(obj/item/I, mob/user) + if(!hasPower()) + if(density) + open(2) + else + close(2) + else + to_chat(user, "The door's motors resist your efforts to force it!") + +/obj/machinery/door/window/do_animate(animation) + switch(animation) + if("opening") + flick("[base_state]opening", src) + if("closing") + flick("[base_state]closing", src) + if("deny") + flick("[base_state]deny", src) + +/obj/machinery/door/window/check_access_ntnet(datum/netdata/data) + return !requiresID() || ..() + +/obj/machinery/door/window/ntnet_receive(datum/netdata/data) + // Check if the airlock is powered. + if(!hasPower()) + return + + // Check packet access level. + if(!check_access_ntnet(data)) + return + + // Handle received packet. + var/command = lowertext(data.data["data"]) + var/command_value = lowertext(data.data["data_secondary"]) + switch(command) + if("open") + if(command_value == "on" && !density) + return + + if(command_value == "off" && density) + return + + if(density) + INVOKE_ASYNC(src, .proc/open) + else + INVOKE_ASYNC(src, .proc/close) + if("touch") + INVOKE_ASYNC(src, .proc/open_and_close) + +/obj/machinery/door/window/brigdoor + name = "secure door" + icon_state = "leftsecure" + base_state = "leftsecure" + var/id = null + max_integrity = 300 //Stronger doors for prison (regular window door health is 200) + reinf = 1 + explosion_block = 1 + +/obj/machinery/door/window/brigdoor/security/cell + name = "cell door" + desc = "For keeping in criminal scum." + req_access = list(ACCESS_BRIG) + +/obj/machinery/door/window/brigdoor/security/holding + name = "holding cell door" + req_one_access = list(ACCESS_SEC_DOORS, ACCESS_LAWYER) //love for the lawyer + +/obj/machinery/door/window/northleft + dir = NORTH + +/obj/machinery/door/window/eastleft + dir = EAST + +/obj/machinery/door/window/westleft + dir = WEST + +/obj/machinery/door/window/southleft + dir = SOUTH + +/obj/machinery/door/window/northright + dir = NORTH + icon_state = "right" + base_state = "right" + +/obj/machinery/door/window/eastright + dir = EAST + icon_state = "right" + base_state = "right" + +/obj/machinery/door/window/westright + dir = WEST + icon_state = "right" + base_state = "right" + +/obj/machinery/door/window/southright + dir = SOUTH + icon_state = "right" + base_state = "right" + +/obj/machinery/door/window/brigdoor/northleft + dir = NORTH + +/obj/machinery/door/window/brigdoor/eastleft + dir = EAST + +/obj/machinery/door/window/brigdoor/westleft + dir = WEST + +/obj/machinery/door/window/brigdoor/southleft + dir = SOUTH + +/obj/machinery/door/window/brigdoor/northright + dir = NORTH + icon_state = "rightsecure" + base_state = "rightsecure" + +/obj/machinery/door/window/brigdoor/eastright + dir = EAST + icon_state = "rightsecure" + base_state = "rightsecure" + +/obj/machinery/door/window/brigdoor/westright + dir = WEST + icon_state = "rightsecure" + base_state = "rightsecure" + +/obj/machinery/door/window/brigdoor/southright + dir = SOUTH + icon_state = "rightsecure" + base_state = "rightsecure" + +/obj/machinery/door/window/brigdoor/security/cell/northleft + dir = NORTH + +/obj/machinery/door/window/brigdoor/security/cell/eastleft + dir = EAST + +/obj/machinery/door/window/brigdoor/security/cell/westleft + dir = WEST + +/obj/machinery/door/window/brigdoor/security/cell/southleft + dir = SOUTH + +/obj/machinery/door/window/brigdoor/security/cell/northright + dir = NORTH + icon_state = "rightsecure" + base_state = "rightsecure" + +/obj/machinery/door/window/brigdoor/security/cell/eastright + dir = EAST + icon_state = "rightsecure" + base_state = "rightsecure" + +/obj/machinery/door/window/brigdoor/security/cell/westright + dir = WEST + icon_state = "rightsecure" + base_state = "rightsecure" + +/obj/machinery/door/window/brigdoor/security/cell/southright + dir = SOUTH + icon_state = "rightsecure" + base_state = "rightsecure" + +/obj/machinery/door/window/brigdoor/security/holding/northleft + dir = NORTH + +/obj/machinery/door/window/brigdoor/security/holding/eastleft + dir = EAST + +/obj/machinery/door/window/brigdoor/security/holding/westleft + dir = WEST + +/obj/machinery/door/window/brigdoor/security/holding/southleft + dir = SOUTH + +/obj/machinery/door/window/brigdoor/security/holding/northright + dir = NORTH + icon_state = "rightsecure" + base_state = "rightsecure" + +/obj/machinery/door/window/brigdoor/security/holding/eastright + dir = EAST + icon_state = "rightsecure" + base_state = "rightsecure" + +/obj/machinery/door/window/brigdoor/security/holding/westright + dir = WEST + icon_state = "rightsecure" + base_state = "rightsecure" + +/obj/machinery/door/window/brigdoor/security/holding/southright + dir = SOUTH + icon_state = "rightsecure" + base_state = "rightsecure" diff --git a/code/game/machinery/doppler_array.dm b/code/game/machinery/doppler_array.dm index 811fbec81ec3..680a87b06372 100644 --- a/code/game/machinery/doppler_array.dm +++ b/code/game/machinery/doppler_array.dm @@ -1,162 +1,240 @@ -/obj/machinery/doppler_array - name = "tachyon-doppler array" - desc = "A highly precise directional sensor array which measures the release of quants from decaying tachyons. The doppler shifting of the mirror-image formed by these quants can reveal the size, location and temporal affects of energetic disturbances within a large radius ahead of the array.\n" - icon = 'icons/obj/machines/research.dmi' - icon_state = "tdoppler" - density = TRUE - var/cooldown = 10 - var/next_announce = 0 - var/max_dist = 150 - verb_say = "states coldly" - var/list/message_log = list() - -/obj/machinery/doppler_array/Initialize() - . = ..() - RegisterSignal(SSdcs, COMSIG_GLOB_EXPLOSION, .proc/sense_explosion) - -/obj/machinery/doppler_array/ComponentInitialize() - . = ..() - AddComponent(/datum/component/simple_rotation,ROTATION_ALTCLICK | ROTATION_CLOCKWISE,null,null,CALLBACK(src,.proc/rot_message)) - -/obj/machinery/doppler_array/ui_interact(mob/user) - . = ..() - if(machine_stat) - return FALSE - - var/list/dat = list() - for(var/i in 1 to message_log.len) - dat += "Log recording #[i]: [message_log[i]]

    " - dat += "Delete logs
    " - dat += "
    " - dat += "(Refresh)
    " - dat += "" - var/datum/browser/popup = new(user, "computer", name, 400, 500) - popup.set_content(dat.Join(" ")) - popup.open() - return - -/obj/machinery/doppler_array/Topic(href, href_list) - if(..()) - return - if(href_list["delete_log"]) - message_log.Cut() - if(href_list["refresh"]) - updateUsrDialog() - - updateUsrDialog() - return - -/obj/machinery/doppler_array/attackby(obj/item/I, mob/user, params) - if(I.tool_behaviour == TOOL_WRENCH) - if(!anchored && !isinspace()) - anchored = TRUE - power_change() - to_chat(user, "You fasten [src].") - else if(anchored) - anchored = FALSE - power_change() - to_chat(user, "You unfasten [src].") - I.play_tool_sound(src) - return - return ..() - -/obj/machinery/doppler_array/proc/rot_message(mob/user) - to_chat(user, "You adjust [src]'s dish to face to the [dir2text(dir)].") - playsound(src, 'sound/items/screwdriver2.ogg', 50, TRUE) - -/obj/machinery/doppler_array/proc/sense_explosion(datum/source, turf/epicenter, devastation_range, heavy_impact_range, light_impact_range, - took, orig_dev_range, orig_heavy_range, orig_light_range) - if(machine_stat & NOPOWER) - return FALSE - var/turf/zone = get_turf(src) - if(zone.z != epicenter.z) - return FALSE - - if(next_announce > world.time) - return FALSE - next_announce = world.time + cooldown - - var/distance = get_dist(epicenter, zone) - var/direct = get_dir(zone, epicenter) - - if(distance > max_dist) - return FALSE - if(!(direct & dir)) - return FALSE - - var/list/messages = list("Explosive disturbance detected.", - "Epicenter at: grid ([epicenter.x],[epicenter.y]). Temporal displacement of tachyons: [took] seconds.", - "Factual: Epicenter radius: [devastation_range]. Outer radius: [heavy_impact_range]. Shockwave radius: [light_impact_range].") - - // If the bomb was capped, say its theoretical size. - if(devastation_range < orig_dev_range || heavy_impact_range < orig_heavy_range || light_impact_range < orig_light_range) - messages += "Theoretical: Epicenter radius: [orig_dev_range]. Outer radius: [orig_heavy_range]. Shockwave radius: [orig_light_range]." - - for(var/message in messages) - say(message) - LAZYADD(message_log, messages.Join(" ")) - return TRUE - -/obj/machinery/doppler_array/powered() - if(!anchored) - return FALSE - return ..() - -/obj/machinery/doppler_array/update_icon_state() - if(machine_stat & BROKEN) - icon_state = "[initial(icon_state)]-broken" - else if(powered()) - icon_state = initial(icon_state) - else - icon_state = "[initial(icon_state)]-off" - -/obj/machinery/doppler_array/research - name = "tachyon-doppler research array" - desc = "A specialized tachyon-doppler bomb detection array that uses the results of the highest yield of explosions for research." - var/datum/techweb/linked_techweb - -/obj/machinery/doppler_array/research/sense_explosion(datum/source, turf/epicenter, devastation_range, heavy_impact_range, light_impact_range, - took, orig_dev_range, orig_heavy_range, orig_light_range) //probably needs a way to ignore admin explosives later on - . = ..() - if(!.) - return - if(!istype(linked_techweb)) - say("Warning: No linked research system!") - return - - var/point_gain = 0 - - /*****The Point Calculator*****/ - - if(orig_light_range < 10) - say("Explosion not large enough for research calculations.") - return - else if(orig_light_range < 4500) - point_gain = (83300 * orig_light_range) / (orig_light_range + 3000) - else - point_gain = TECHWEB_BOMB_POINTCAP - - /*****The Point Capper*****/ - if(point_gain > linked_techweb.largest_bomb_value) - if(point_gain <= TECHWEB_BOMB_POINTCAP || linked_techweb.largest_bomb_value < TECHWEB_BOMB_POINTCAP) - var/old_tech_largest_bomb_value = linked_techweb.largest_bomb_value //held so we can pull old before we do math - linked_techweb.largest_bomb_value = point_gain - point_gain -= old_tech_largest_bomb_value - point_gain = min(point_gain,TECHWEB_BOMB_POINTCAP) - else - linked_techweb.largest_bomb_value = TECHWEB_BOMB_POINTCAP - point_gain = 1000 - var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_SCI) - if(D) - D.adjust_money(point_gain) - linked_techweb.add_point_type(TECHWEB_POINT_TYPE_DEFAULT, point_gain) - say("Explosion details and mixture analyzed and sold to the highest bidder for [point_gain] cr, with a reward of [point_gain] points.") - - else //you've made smaller bombs - say("Data already captured. Aborting.") - return - - -/obj/machinery/doppler_array/research/science/Initialize() - . = ..() - linked_techweb = SSresearch.science_tech +#define PRINTER_TIMEOUT 40 + +/obj/machinery/doppler_array + name = "tachyon-doppler array" + desc = "A highly precise directional sensor array which measures the release of quants from decaying tachyons. The doppler shifting of the mirror-image formed by these quants can reveal the size, location and temporal affects of energetic disturbances within a large radius ahead of the array.\n" + icon = 'icons/obj/machines/research.dmi' + icon_state = "tdoppler" + density = TRUE + verb_say = "states coldly" + var/cooldown = 10 + var/next_announce = 0 + var/max_dist = 150 + /// Number which will be part of the name of the next record, increased by one for each already created record + var/record_number = 1 + /// Cooldown for the print function + var/printer_ready = 0 + /// List of all explosion records in the form of /datum/data/tachyon_record + var/list/records = list() + +/obj/machinery/doppler_array/Initialize() + . = ..() + RegisterSignal(SSdcs, COMSIG_GLOB_EXPLOSION, .proc/sense_explosion) + printer_ready = world.time + PRINTER_TIMEOUT + +/obj/machinery/doppler_array/ComponentInitialize() + . = ..() + AddComponent(/datum/component/simple_rotation,ROTATION_ALTCLICK | ROTATION_CLOCKWISE,null,null,CALLBACK(src,.proc/rot_message)) + +/datum/data/tachyon_record + name = "Log Recording" + var/timestamp + var/coordinates = "" + var/displacement = 0 + var/factual_radius = list() + var/theory_radius = list() + +/obj/machinery/doppler_array/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "TachyonArray", name) + ui.open() + +/obj/machinery/doppler_array/ui_data(mob/user) + var/list/data = list() + data["records"] = list() + for(var/datum/data/tachyon_record/R in records) + var/list/record_data = list( + name = R.name, + timestamp = R.timestamp, + coordinates = R.coordinates, + displacement = R.displacement, + factual_epicenter_radius = R.factual_radius["epicenter_radius"], + factual_outer_radius = R.factual_radius["outer_radius"], + factual_shockwave_radius = R.factual_radius["shockwave_radius"], + theory_epicenter_radius = R.theory_radius["epicenter_radius"], + theory_outer_radius = R.theory_radius["outer_radius"], + theory_shockwave_radius = R.theory_radius["shockwave_radius"], + ref = REF(R) + ) + data["records"] += list(record_data) + return data + +/obj/machinery/doppler_array/ui_act(action, list/params) + if(..()) + return + + switch(action) + if("delete_record") + var/datum/data/tachyon_record/record = locate(params["ref"]) in records + if(!records || !(record in records)) + return + records -= record + return TRUE + if("print_record") + var/datum/data/tachyon_record/record = locate(params["ref"]) in records + if(!records || !(record in records)) + return + print(usr, record) + return TRUE + +/obj/machinery/doppler_array/proc/print(mob/user, datum/data/tachyon_record/record) + if(!record) + return + if(printer_ready < world.time) + printer_ready = world.time + PRINTER_TIMEOUT + new /obj/item/paper/record_printout(loc, record) + else if(user) + to_chat(user, "[src] is busy right now.") + +/obj/item/paper/record_printout + name = "paper - Log Recording" + +/obj/item/paper/record_printout/Initialize(mapload, datum/data/tachyon_record/record) + . = ..() + + if(record) + name = "paper - [record.name]" + + info += {"

    [record.name]

    +
    • Timestamp: [record.timestamp]
    • +
    • Coordinates: [record.coordinates]
    • +
    • Displacement: [record.displacement] seconds
    • +
    • Epicenter Radius: [record.factual_radius["epicenter_radius"]]
    • +
    • Outer Radius: [record.factual_radius["outer_radius"]]
    • +
    • Shockwave Radius: [record.factual_radius["shockwave_radius"]]
    "} + + if(length(record.theory_radius)) + info += {"
    • Theoretical Epicenter Radius: [record.theory_radius["epicenter_radius"]]
    • +
    • Theoretical Outer Radius: [record.theory_radius["outer_radius"]]
    • +
    • Theoretical Shockwave Radius: [record.theory_radius["shockwave_radius"]]
    "} + + update_icon() + +/obj/machinery/doppler_array/attackby(obj/item/I, mob/user, params) + if(I.tool_behaviour == TOOL_WRENCH) + if(!anchored && !isinspace()) + anchored = TRUE + power_change() + to_chat(user, "You fasten [src].") + else if(anchored) + anchored = FALSE + power_change() + to_chat(user, "You unfasten [src].") + I.play_tool_sound(src) + return + return ..() + +/obj/machinery/doppler_array/proc/rot_message(mob/user) + to_chat(user, "You adjust [src]'s dish to face to the [dir2text(dir)].") + playsound(src, 'sound/items/screwdriver2.ogg', 50, TRUE) + +/obj/machinery/doppler_array/proc/sense_explosion(datum/source, turf/epicenter, devastation_range, heavy_impact_range, light_impact_range, + took, orig_dev_range, orig_heavy_range, orig_light_range) + if(machine_stat & NOPOWER) + return FALSE + var/turf/zone = get_turf(src) + if(zone.z != epicenter.z) + return FALSE + + if(next_announce > world.time) + return FALSE + next_announce = world.time + cooldown + + var/distance = get_dist(epicenter, zone) + var/direct = get_dir(zone, epicenter) + + if(distance > max_dist) + return FALSE + if(!(direct & dir)) + return FALSE + + var/datum/data/tachyon_record/R = new /datum/data/tachyon_record() + R.name = "Log Recording #[record_number]" + R.timestamp = station_time_timestamp() + R.coordinates = "[epicenter.x], [epicenter.y]" + R.displacement = took + R.factual_radius["epicenter_radius"] = devastation_range + R.factual_radius["outer_radius"] = heavy_impact_range + R.factual_radius["shockwave_radius"] = light_impact_range + + var/list/messages = list("Explosive disturbance detected.", + "Epicenter at: grid ([epicenter.x], [epicenter.y]). Temporal displacement of tachyons: [took] seconds.", + "Factual: Epicenter radius: [devastation_range]. Outer radius: [heavy_impact_range]. Shockwave radius: [light_impact_range].") + + // If the bomb was capped, say its theoretical size. + if(devastation_range < orig_dev_range || heavy_impact_range < orig_heavy_range || light_impact_range < orig_light_range) + messages += "Theoretical: Epicenter radius: [orig_dev_range]. Outer radius: [orig_heavy_range]. Shockwave radius: [orig_light_range]." + R.theory_radius["epicenter_radius"] = orig_dev_range + R.theory_radius["outer_radius"] = orig_heavy_range + R.theory_radius["shockwave_radius"] = orig_light_range + + for(var/message in messages) + say(message) + + record_number++ + records += R + return TRUE + +/obj/machinery/doppler_array/powered() + if(!anchored) + return FALSE + return ..() + +/obj/machinery/doppler_array/update_icon_state() + if(machine_stat & BROKEN) + icon_state = "[initial(icon_state)]-broken" + else if(powered()) + icon_state = initial(icon_state) + else + icon_state = "[initial(icon_state)]-off" + +/obj/machinery/doppler_array/research + name = "tachyon-doppler research array" + desc = "A specialized tachyon-doppler bomb detection array that uses the results of the highest yield of explosions for research." + var/datum/techweb/linked_techweb + +/obj/machinery/doppler_array/research/sense_explosion(datum/source, turf/epicenter, devastation_range, heavy_impact_range, light_impact_range, + took, orig_dev_range, orig_heavy_range, orig_light_range) //probably needs a way to ignore admin explosives later on + . = ..() + if(!.) + return + if(!istype(linked_techweb)) + say("Warning: No linked research system!") + return + + var/point_gain = 0 + /*****The Point Calculator*****/ + if(orig_light_range < 10) + say("Explosion not large enough for research calculations.") + return + else if(orig_light_range < 4500) + point_gain = (83300 * orig_light_range) / (orig_light_range + 3000) + else + point_gain = TECHWEB_BOMB_POINTCAP + + /*****The Point Capper*****/ + if(point_gain > linked_techweb.largest_bomb_value) + if(point_gain <= TECHWEB_BOMB_POINTCAP || linked_techweb.largest_bomb_value < TECHWEB_BOMB_POINTCAP) + var/old_tech_largest_bomb_value = linked_techweb.largest_bomb_value //held so we can pull old before we do math + linked_techweb.largest_bomb_value = point_gain + point_gain -= old_tech_largest_bomb_value + point_gain = min(point_gain,TECHWEB_BOMB_POINTCAP) + else + linked_techweb.largest_bomb_value = TECHWEB_BOMB_POINTCAP + point_gain = 1000 + var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_SCI) + if(D) + D.adjust_money(point_gain) + linked_techweb.add_point_type(TECHWEB_POINT_TYPE_DEFAULT, point_gain) + say("Explosion details and mixture analyzed and sold to the highest bidder for [point_gain] cr, with a reward of [point_gain] points.") + + else //you've made smaller bombs + say("Data already captured. Aborting.") + return + +/obj/machinery/doppler_array/research/science/Initialize() + . = ..() + linked_techweb = SSresearch.science_tech + +#undef PRINTER_TIMEOUT diff --git a/code/game/machinery/embedded_controller/access_controller.dm b/code/game/machinery/embedded_controller/access_controller.dm index 50fc03a44329..a404f9283490 100644 --- a/code/game/machinery/embedded_controller/access_controller.dm +++ b/code/game/machinery/embedded_controller/access_controller.dm @@ -1,309 +1,309 @@ -#define CLOSING 1 -#define OPENING 2 -#define CYCLE 3 -#define CYCLE_EXTERIOR 4 -#define CYCLE_INTERIOR 5 - -/obj/machinery/doorButtons - power_channel = AREA_USAGE_ENVIRON - use_power = IDLE_POWER_USE - idle_power_usage = 2 - active_power_usage = 4 - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - var/idSelf - -/obj/machinery/doorButtons/attackby(obj/O, mob/user) - return attack_hand(user) - -/obj/machinery/doorButtons/proc/findObjsByTag() - return - -/obj/machinery/doorButtons/Initialize() - ..() - return INITIALIZE_HINT_LATELOAD - -/obj/machinery/doorButtons/LateInitialize() - findObjsByTag() - -/obj/machinery/doorButtons/emag_act(mob/user) - if(obj_flags & EMAGGED) - return - obj_flags |= EMAGGED - req_access = list() - req_one_access = list() - playsound(src, "sparks", 100, TRUE) - to_chat(user, "You short out the access controller.") - -/obj/machinery/doorButtons/proc/removeMe() - - -/obj/machinery/doorButtons/access_button - icon = 'icons/obj/airlock_machines.dmi' - icon_state = "access_button_standby" - name = "access button" - desc = "A button used for the explicit purpose of opening an airlock." - var/idDoor - var/obj/machinery/door/airlock/door - var/obj/machinery/doorButtons/airlock_controller/controller - var/busy - -/obj/machinery/doorButtons/access_button/findObjsByTag() - for(var/obj/machinery/doorButtons/airlock_controller/A in GLOB.machines) - if(A.idSelf == idSelf) - controller = A - break - for(var/obj/machinery/door/airlock/I in GLOB.machines) - if(I.id_tag == idDoor) - door = I - break - -/obj/machinery/doorButtons/access_button/interact(mob/user) - if(busy) - return - if(!allowed(user)) - to_chat(user, "Access denied.") - return - if(controller && !controller.busy && door) - if(controller.machine_stat & NOPOWER) - return - busy = TRUE - update_icon() - if(door.density) - if(!controller.exteriorAirlock || !controller.interiorAirlock) - controller.onlyOpen(door) - else - if(controller.exteriorAirlock.density && controller.interiorAirlock.density) - controller.onlyOpen(door) - else - controller.cycleClose(door) - else - controller.onlyClose(door) - sleep(20) - busy = FALSE - update_icon() - -/obj/machinery/doorButtons/access_button/update_icon_state() - if(machine_stat & NOPOWER) - icon_state = "access_button_off" - else - if(busy) - icon_state = "access_button_cycle" - else - icon_state = "access_button_standby" - -/obj/machinery/doorButtons/access_button/removeMe(obj/O) - if(O == door) - door = null - - - -/obj/machinery/doorButtons/airlock_controller - icon = 'icons/obj/airlock_machines.dmi' - icon_state = "access_control_standby" - name = "access console" - desc = "A small console that can cycle opening between two airlocks." - var/obj/machinery/door/airlock/interiorAirlock - var/obj/machinery/door/airlock/exteriorAirlock - var/idInterior - var/idExterior - var/busy - var/lostPower - -/obj/machinery/doorButtons/airlock_controller/removeMe(obj/O) - if(O == interiorAirlock) - interiorAirlock = null - else if(O == exteriorAirlock) - exteriorAirlock = null - -/obj/machinery/doorButtons/airlock_controller/Destroy() - for(var/obj/machinery/doorButtons/access_button/A in GLOB.machines) - if(A.controller == src) - A.controller = null - return ..() - -/obj/machinery/doorButtons/airlock_controller/Topic(href, href_list) - if(..()) - return - if(busy) - return - if(!allowed(usr)) - to_chat(usr, "Access denied.") - return - switch(href_list["command"]) - if("close_exterior") - onlyClose(exteriorAirlock) - if("close_interior") - onlyClose(interiorAirlock) - if("cycle_exterior") - cycleClose(exteriorAirlock) - if("cycle_interior") - cycleClose(interiorAirlock) - if("open_exterior") - onlyOpen(exteriorAirlock) - if("open_interior") - onlyOpen(interiorAirlock) - -/obj/machinery/doorButtons/airlock_controller/proc/onlyOpen(obj/machinery/door/airlock/A) - if(A) - busy = CLOSING - update_icon() - openDoor(A) - -/obj/machinery/doorButtons/airlock_controller/proc/onlyClose(obj/machinery/door/airlock/A) - if(A) - busy = CLOSING - closeDoor(A) - -/obj/machinery/doorButtons/airlock_controller/proc/closeDoor(obj/machinery/door/airlock/A) - if(A.density) - goIdle() - return FALSE - update_icon() - A.safe = FALSE //Door crushies, manual door after all. Set every time in case someone changed it, safe doors can end up waiting forever. - A.unbolt() - if(A.close()) - if(machine_stat & NOPOWER || lostPower || !A || QDELETED(A)) - goIdle(TRUE) - return FALSE - A.bolt() - goIdle(TRUE) - return TRUE - goIdle(TRUE) - return FALSE - -/obj/machinery/doorButtons/airlock_controller/proc/cycleClose(obj/machinery/door/airlock/A) - if(!A || !exteriorAirlock || !interiorAirlock) - return - if(exteriorAirlock.density == interiorAirlock.density || !A.density) - return - busy = CYCLE - update_icon() - if(A == interiorAirlock) - if(closeDoor(exteriorAirlock)) - busy = CYCLE_INTERIOR - else - if(closeDoor(interiorAirlock)) - busy = CYCLE_EXTERIOR - -/obj/machinery/doorButtons/airlock_controller/proc/cycleOpen(obj/machinery/door/airlock/A) - if(!A) - goIdle(TRUE) - if(A == exteriorAirlock) - if(interiorAirlock) - if(!interiorAirlock.density || !interiorAirlock.locked) - return - else - if(exteriorAirlock) - if(!exteriorAirlock.density || !exteriorAirlock.locked) - return - if(busy != OPENING) - busy = OPENING - openDoor(A) - -/obj/machinery/doorButtons/airlock_controller/proc/openDoor(obj/machinery/door/airlock/A) - if(exteriorAirlock && interiorAirlock && (!exteriorAirlock.density || !interiorAirlock.density)) - goIdle(TRUE) - return - A.unbolt() - INVOKE_ASYNC(src, .proc/do_openDoor, A) - -/obj/machinery/doorButtons/airlock_controller/proc/do_openDoor(obj/machinery/door/airlock/A) - if(A && A.open()) - if(machine_stat | (NOPOWER) && !lostPower && A && !QDELETED(A)) - A.bolt() - goIdle(TRUE) - -/obj/machinery/doorButtons/airlock_controller/proc/goIdle(update) - lostPower = FALSE - busy = FALSE - if(update) - update_icon() - updateUsrDialog() - -/obj/machinery/doorButtons/airlock_controller/process() - if(machine_stat & NOPOWER) - return - if(busy == CYCLE_EXTERIOR) - cycleOpen(exteriorAirlock) - else if(busy == CYCLE_INTERIOR) - cycleOpen(interiorAirlock) - -/obj/machinery/doorButtons/airlock_controller/power_change() - . = ..() - if(machine_stat & NOPOWER) - lostPower = TRUE - else - if(!busy) - lostPower = FALSE - -/obj/machinery/doorButtons/airlock_controller/findObjsByTag() - for(var/obj/machinery/door/airlock/A in GLOB.machines) - if(A.id_tag == idInterior) - interiorAirlock = A - else if(A.id_tag == idExterior) - exteriorAirlock = A - -/obj/machinery/doorButtons/airlock_controller/update_icon_state() - if(machine_stat & NOPOWER) - icon_state = "access_control_off" - return - if(busy || lostPower) - icon_state = "access_control_process" - else - icon_state = "access_control_standby" - -/obj/machinery/doorButtons/airlock_controller/ui_interact(mob/user) - var/datum/browser/popup = new(user, "computer", name) - popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) - popup.set_content(returnText()) - popup.open() - -/obj/machinery/doorButtons/airlock_controller/proc/returnText() - var/output - if(!exteriorAirlock && !interiorAirlock) - return "ERROR ERROR ERROR ERROR" - if(lostPower) - output = "Initializing..." - else - if(!exteriorAirlock || !interiorAirlock) - if(!exteriorAirlock) - if(interiorAirlock.density) - output = "Open Interior Airlock
    " - else - output = "Close Interior Airlock
    " - else - if(exteriorAirlock.density) - output = "Open Exterior Airlock
    " - else - output = "Close Exterior Airlock
    " - else - if(exteriorAirlock.density) - if(interiorAirlock.density) - output = {"Open Exterior Airlock
    - Open Interior Airlock
    "} - else - output = {"Cycle to Exterior Airlock
    - Close Interior Airlock
    "} - else - if(interiorAirlock.density) - output = {"Close Exterior Airlock
    - Cycle to Interior Airlock
    "} - else - output = {"Close Exterior Airlock
    - Close Interior Airlock
    "} - - - output = {"Access Control Console
    - [output]
    "} - if(exteriorAirlock) - output += "Exterior Door: [exteriorAirlock.density ? "closed" : "open"]
    " - if(interiorAirlock) - output += "Interior Door: [interiorAirlock.density ? "closed" : "open"]
    " - - return output - -#undef CLOSING -#undef OPENING -#undef CYCLE -#undef CYCLE_EXTERIOR -#undef CYCLE_INTERIOR +#define CLOSING 1 +#define OPENING 2 +#define CYCLE 3 +#define CYCLE_EXTERIOR 4 +#define CYCLE_INTERIOR 5 + +/obj/machinery/doorButtons + power_channel = AREA_USAGE_ENVIRON + use_power = IDLE_POWER_USE + idle_power_usage = 2 + active_power_usage = 4 + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + var/idSelf + +/obj/machinery/doorButtons/attackby(obj/O, mob/user) + return attack_hand(user) + +/obj/machinery/doorButtons/proc/findObjsByTag() + return + +/obj/machinery/doorButtons/Initialize() + ..() + return INITIALIZE_HINT_LATELOAD + +/obj/machinery/doorButtons/LateInitialize() + findObjsByTag() + +/obj/machinery/doorButtons/emag_act(mob/user) + if(obj_flags & EMAGGED) + return + obj_flags |= EMAGGED + req_access = list() + req_one_access = list() + playsound(src, "sparks", 100, TRUE) + to_chat(user, "You short out the access controller.") + +/obj/machinery/doorButtons/proc/removeMe() + + +/obj/machinery/doorButtons/access_button + icon = 'icons/obj/airlock_machines.dmi' + icon_state = "access_button_standby" + name = "access button" + desc = "A button used for the explicit purpose of opening an airlock." + var/idDoor + var/obj/machinery/door/airlock/door + var/obj/machinery/doorButtons/airlock_controller/controller + var/busy + +/obj/machinery/doorButtons/access_button/findObjsByTag() + for(var/obj/machinery/doorButtons/airlock_controller/A in GLOB.machines) + if(A.idSelf == idSelf) + controller = A + break + for(var/obj/machinery/door/airlock/I in GLOB.machines) + if(I.id_tag == idDoor) + door = I + break + +/obj/machinery/doorButtons/access_button/interact(mob/user) + if(busy) + return + if(!allowed(user)) + to_chat(user, "Access denied.") + return + if(controller && !controller.busy && door) + if(controller.machine_stat & NOPOWER) + return + busy = TRUE + update_icon() + if(door.density) + if(!controller.exteriorAirlock || !controller.interiorAirlock) + controller.onlyOpen(door) + else + if(controller.exteriorAirlock.density && controller.interiorAirlock.density) + controller.onlyOpen(door) + else + controller.cycleClose(door) + else + controller.onlyClose(door) + sleep(20) + busy = FALSE + update_icon() + +/obj/machinery/doorButtons/access_button/update_icon_state() + if(machine_stat & NOPOWER) + icon_state = "access_button_off" + else + if(busy) + icon_state = "access_button_cycle" + else + icon_state = "access_button_standby" + +/obj/machinery/doorButtons/access_button/removeMe(obj/O) + if(O == door) + door = null + + + +/obj/machinery/doorButtons/airlock_controller + icon = 'icons/obj/airlock_machines.dmi' + icon_state = "access_control_standby" + name = "access console" + desc = "A small console that can cycle opening between two airlocks." + var/obj/machinery/door/airlock/interiorAirlock + var/obj/machinery/door/airlock/exteriorAirlock + var/idInterior + var/idExterior + var/busy + var/lostPower + +/obj/machinery/doorButtons/airlock_controller/removeMe(obj/O) + if(O == interiorAirlock) + interiorAirlock = null + else if(O == exteriorAirlock) + exteriorAirlock = null + +/obj/machinery/doorButtons/airlock_controller/Destroy() + for(var/obj/machinery/doorButtons/access_button/A in GLOB.machines) + if(A.controller == src) + A.controller = null + return ..() + +/obj/machinery/doorButtons/airlock_controller/Topic(href, href_list) + if(..()) + return + if(busy) + return + if(!allowed(usr)) + to_chat(usr, "Access denied.") + return + switch(href_list["command"]) + if("close_exterior") + onlyClose(exteriorAirlock) + if("close_interior") + onlyClose(interiorAirlock) + if("cycle_exterior") + cycleClose(exteriorAirlock) + if("cycle_interior") + cycleClose(interiorAirlock) + if("open_exterior") + onlyOpen(exteriorAirlock) + if("open_interior") + onlyOpen(interiorAirlock) + +/obj/machinery/doorButtons/airlock_controller/proc/onlyOpen(obj/machinery/door/airlock/A) + if(A) + busy = CLOSING + update_icon() + openDoor(A) + +/obj/machinery/doorButtons/airlock_controller/proc/onlyClose(obj/machinery/door/airlock/A) + if(A) + busy = CLOSING + closeDoor(A) + +/obj/machinery/doorButtons/airlock_controller/proc/closeDoor(obj/machinery/door/airlock/A) + if(A.density) + goIdle() + return FALSE + update_icon() + A.safe = FALSE //Door crushies, manual door after all. Set every time in case someone changed it, safe doors can end up waiting forever. + A.unbolt() + if(A.close()) + if(machine_stat & NOPOWER || lostPower || !A || QDELETED(A)) + goIdle(TRUE) + return FALSE + A.bolt() + goIdle(TRUE) + return TRUE + goIdle(TRUE) + return FALSE + +/obj/machinery/doorButtons/airlock_controller/proc/cycleClose(obj/machinery/door/airlock/A) + if(!A || !exteriorAirlock || !interiorAirlock) + return + if(exteriorAirlock.density == interiorAirlock.density || !A.density) + return + busy = CYCLE + update_icon() + if(A == interiorAirlock) + if(closeDoor(exteriorAirlock)) + busy = CYCLE_INTERIOR + else + if(closeDoor(interiorAirlock)) + busy = CYCLE_EXTERIOR + +/obj/machinery/doorButtons/airlock_controller/proc/cycleOpen(obj/machinery/door/airlock/A) + if(!A) + goIdle(TRUE) + if(A == exteriorAirlock) + if(interiorAirlock) + if(!interiorAirlock.density || !interiorAirlock.locked) + return + else + if(exteriorAirlock) + if(!exteriorAirlock.density || !exteriorAirlock.locked) + return + if(busy != OPENING) + busy = OPENING + openDoor(A) + +/obj/machinery/doorButtons/airlock_controller/proc/openDoor(obj/machinery/door/airlock/A) + if(exteriorAirlock && interiorAirlock && (!exteriorAirlock.density || !interiorAirlock.density)) + goIdle(TRUE) + return + A.unbolt() + INVOKE_ASYNC(src, .proc/do_openDoor, A) + +/obj/machinery/doorButtons/airlock_controller/proc/do_openDoor(obj/machinery/door/airlock/A) + if(A && A.open()) + if(machine_stat | (NOPOWER) && !lostPower && A && !QDELETED(A)) + A.bolt() + goIdle(TRUE) + +/obj/machinery/doorButtons/airlock_controller/proc/goIdle(update) + lostPower = FALSE + busy = FALSE + if(update) + update_icon() + updateUsrDialog() + +/obj/machinery/doorButtons/airlock_controller/process() + if(machine_stat & NOPOWER) + return + if(busy == CYCLE_EXTERIOR) + cycleOpen(exteriorAirlock) + else if(busy == CYCLE_INTERIOR) + cycleOpen(interiorAirlock) + +/obj/machinery/doorButtons/airlock_controller/power_change() + . = ..() + if(machine_stat & NOPOWER) + lostPower = TRUE + else + if(!busy) + lostPower = FALSE + +/obj/machinery/doorButtons/airlock_controller/findObjsByTag() + for(var/obj/machinery/door/airlock/A in GLOB.machines) + if(A.id_tag == idInterior) + interiorAirlock = A + else if(A.id_tag == idExterior) + exteriorAirlock = A + +/obj/machinery/doorButtons/airlock_controller/update_icon_state() + if(machine_stat & NOPOWER) + icon_state = "access_control_off" + return + if(busy || lostPower) + icon_state = "access_control_process" + else + icon_state = "access_control_standby" + +/obj/machinery/doorButtons/airlock_controller/ui_interact(mob/user) + var/datum/browser/popup = new(user, "computer", name) + popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) + popup.set_content(returnText()) + popup.open() + +/obj/machinery/doorButtons/airlock_controller/proc/returnText() + var/output + if(!exteriorAirlock && !interiorAirlock) + return "ERROR ERROR ERROR ERROR" + if(lostPower) + output = "Initializing..." + else + if(!exteriorAirlock || !interiorAirlock) + if(!exteriorAirlock) + if(interiorAirlock.density) + output = "Open Interior Airlock
    " + else + output = "Close Interior Airlock
    " + else + if(exteriorAirlock.density) + output = "Open Exterior Airlock
    " + else + output = "Close Exterior Airlock
    " + else + if(exteriorAirlock.density) + if(interiorAirlock.density) + output = {"Open Exterior Airlock
    + Open Interior Airlock
    "} + else + output = {"Cycle to Exterior Airlock
    + Close Interior Airlock
    "} + else + if(interiorAirlock.density) + output = {"Close Exterior Airlock
    + Cycle to Interior Airlock
    "} + else + output = {"Close Exterior Airlock
    + Close Interior Airlock
    "} + + + output = {"Access Control Console
    + [output]
    "} + if(exteriorAirlock) + output += "Exterior Door: [exteriorAirlock.density ? "closed" : "open"]
    " + if(interiorAirlock) + output += "Interior Door: [interiorAirlock.density ? "closed" : "open"]
    " + + return output + +#undef CLOSING +#undef OPENING +#undef CYCLE +#undef CYCLE_EXTERIOR +#undef CYCLE_INTERIOR diff --git a/code/game/machinery/embedded_controller/airlock_controller.dm b/code/game/machinery/embedded_controller/airlock_controller.dm index 493929381b45..c028beb3d69f 100644 --- a/code/game/machinery/embedded_controller/airlock_controller.dm +++ b/code/game/machinery/embedded_controller/airlock_controller.dm @@ -1,315 +1,315 @@ -//States for airlock_control -#define AIRLOCK_STATE_INOPEN -2 -#define AIRLOCK_STATE_PRESSURIZE -1 -#define AIRLOCK_STATE_CLOSED 0 -#define AIRLOCK_STATE_DEPRESSURIZE 1 -#define AIRLOCK_STATE_OUTOPEN 2 - -/datum/computer/file/embedded_program/airlock_controller - var/id_tag - var/exterior_door_tag //Burn chamber facing door - var/interior_door_tag //Station facing door - var/airpump_tag //See: dp_vent_pump.dm - var/sensor_tag //See: /obj/machinery/airlock_sensor - var/sanitize_external //Before the interior airlock opens, do we first drain all gases inside the chamber and then repressurize? - - state = AIRLOCK_STATE_CLOSED - var/target_state = AIRLOCK_STATE_CLOSED - var/sensor_pressure = null - -/datum/computer/file/embedded_program/airlock_controller/receive_signal(datum/signal/signal) - var/receive_tag = signal.data["tag"] - if(!receive_tag) - return - - if(receive_tag==sensor_tag) - if(signal.data["pressure"]) - sensor_pressure = text2num(signal.data["pressure"]) - - else if(receive_tag==exterior_door_tag) - memory["exterior_status"] = signal.data["door_status"] - - else if(receive_tag==interior_door_tag) - memory["interior_status"] = signal.data["door_status"] - - else if(receive_tag==airpump_tag) - if(signal.data["power"]) - memory["pump_status"] = signal.data["direction"] - else - memory["pump_status"] = "off" - - else if(receive_tag==id_tag) - switch(signal.data["command"]) - if("cycle") - if(state < AIRLOCK_STATE_CLOSED) - target_state = AIRLOCK_STATE_OUTOPEN - else - target_state = AIRLOCK_STATE_INOPEN - -/datum/computer/file/embedded_program/airlock_controller/receive_user_command(command) - switch(command) - if("cycle_closed") - target_state = AIRLOCK_STATE_CLOSED - if("cycle_exterior") - target_state = AIRLOCK_STATE_OUTOPEN - if("cycle_interior") - target_state = AIRLOCK_STATE_INOPEN - if("abort") - target_state = AIRLOCK_STATE_CLOSED - -/datum/computer/file/embedded_program/airlock_controller/process() - var/process_again = 1 - while(process_again) - process_again = 0 - switch(state) - if(AIRLOCK_STATE_INOPEN) // state -2 - if(target_state > state) - if(memory["interior_status"] == "closed") - state = AIRLOCK_STATE_CLOSED - process_again = 1 - else - post_signal(new /datum/signal(list( - "tag" = interior_door_tag, - "command" = "secure_close" - ))) - else - if(memory["pump_status"] != "off") - post_signal(new /datum/signal(list( - "tag" = airpump_tag, - "power" = 0, - "sigtype" = "command" - ))) - - if(AIRLOCK_STATE_PRESSURIZE) - if(target_state < state) - if(sensor_pressure >= ONE_ATMOSPHERE*0.95) - if(memory["interior_status"] == "open") - state = AIRLOCK_STATE_INOPEN - process_again = 1 - else - post_signal(new /datum/signal(list( - "tag" = interior_door_tag, - "command" = "secure_open" - ))) - else - var/datum/signal/signal = new(list( - "tag" = airpump_tag, - "sigtype" = "command" - )) - if(memory["pump_status"] == "siphon") - signal.data["stabilize"] = 1 - else if(memory["pump_status"] != "release") - signal.data["power"] = 1 - post_signal(signal) - else if(target_state > state) - state = AIRLOCK_STATE_CLOSED - process_again = 1 - - if(AIRLOCK_STATE_CLOSED) - if(target_state > state) - if(memory["interior_status"] == "closed") - state = AIRLOCK_STATE_DEPRESSURIZE - process_again = 1 - else - post_signal(new /datum/signal(list( - "tag" = interior_door_tag, - "command" = "secure_close" - ))) - else if(target_state < state) - if(memory["exterior_status"] == "closed") - state = AIRLOCK_STATE_PRESSURIZE - process_again = 1 - else - post_signal(new /datum/signal(list( - "tag" = exterior_door_tag, - "command" = "secure_close" - ))) - - else - if(memory["pump_status"] != "off") - post_signal(new /datum/signal(list( - "tag" = airpump_tag, - "power" = 0, - "sigtype" = "command" - ))) - - if(AIRLOCK_STATE_DEPRESSURIZE) - var/target_pressure = ONE_ATMOSPHERE*0.05 - if(sanitize_external) - target_pressure = ONE_ATMOSPHERE*0.01 - - if(sensor_pressure <= target_pressure) - if(target_state > state) - if(memory["exterior_status"] == "open") - state = AIRLOCK_STATE_OUTOPEN - else - post_signal(new /datum/signal(list( - "tag" = exterior_door_tag, - "command" = "secure_open" - ))) - else if(target_state < state) - state = AIRLOCK_STATE_CLOSED - process_again = 1 - else if((target_state < state) && !sanitize_external) - state = AIRLOCK_STATE_CLOSED - process_again = 1 - else - var/datum/signal/signal = new(list( - "tag" = airpump_tag, - "sigtype" = "command" - )) - if(memory["pump_status"] == "release") - signal.data["purge"] = 1 - else if(memory["pump_status"] != "siphon") - signal.data["power"] = 1 - post_signal(signal) - - if(AIRLOCK_STATE_OUTOPEN) //state 2 - if(target_state < state) - if(memory["exterior_status"] == "closed") - if(sanitize_external) - state = AIRLOCK_STATE_DEPRESSURIZE - process_again = 1 - else - state = AIRLOCK_STATE_CLOSED - process_again = 1 - else - post_signal(new /datum/signal(list( - "tag" = exterior_door_tag, - "command" = "secure_close" - ))) - else - if(memory["pump_status"] != "off") - post_signal(new /datum/signal(list( - "tag" = airpump_tag, - "power" = 0, - "sigtype" = "command" - ))) - - memory["sensor_pressure"] = sensor_pressure - memory["processing"] = state != target_state - //sensor_pressure = null //not sure if we can comment this out. Uncomment in case of problems -rastaf0 - - return 1 - - -/obj/machinery/embedded_controller/radio/airlock_controller - icon = 'icons/obj/airlock_machines.dmi' - icon_state = "airlock_control_standby" - - name = "airlock console" - density = FALSE - - frequency = FREQ_AIRLOCK_CONTROL - power_channel = AREA_USAGE_ENVIRON - - // Setup parameters only - var/id_tag - var/exterior_door_tag - var/interior_door_tag - var/airpump_tag - var/sensor_tag - var/sanitize_external - -/obj/machinery/embedded_controller/radio/airlock_controller/incinerator_toxmix - name = "Incinerator Access Console" - airpump_tag = INCINERATOR_TOXMIX_DP_VENTPUMP - exterior_door_tag = INCINERATOR_TOXMIX_AIRLOCK_EXTERIOR - id_tag = INCINERATOR_TOXMIX_AIRLOCK_CONTROLLER - interior_door_tag = INCINERATOR_TOXMIX_AIRLOCK_INTERIOR - sanitize_external = TRUE - sensor_tag = INCINERATOR_TOXMIX_AIRLOCK_SENSOR - -/obj/machinery/embedded_controller/radio/airlock_controller/incinerator_atmos - name = "Incinerator Access Console" - airpump_tag = INCINERATOR_ATMOS_DP_VENTPUMP - exterior_door_tag = INCINERATOR_ATMOS_AIRLOCK_EXTERIOR - id_tag = INCINERATOR_ATMOS_AIRLOCK_CONTROLLER - interior_door_tag = INCINERATOR_ATMOS_AIRLOCK_INTERIOR - sanitize_external = TRUE - sensor_tag = INCINERATOR_ATMOS_AIRLOCK_SENSOR - -/obj/machinery/embedded_controller/radio/airlock_controller/incinerator_syndicatelava - name = "Incinerator Access Console" - airpump_tag = INCINERATOR_SYNDICATELAVA_DP_VENTPUMP - exterior_door_tag = INCINERATOR_SYNDICATELAVA_AIRLOCK_EXTERIOR - id_tag = INCINERATOR_SYNDICATELAVA_AIRLOCK_CONTROLLER - interior_door_tag = INCINERATOR_SYNDICATELAVA_AIRLOCK_INTERIOR - sanitize_external = TRUE - sensor_tag = INCINERATOR_SYNDICATELAVA_AIRLOCK_SENSOR - -/obj/machinery/embedded_controller/radio/airlock_controller/Initialize(mapload) - . = ..() - if(!mapload) - return - - var/datum/computer/file/embedded_program/airlock_controller/new_prog = new - - new_prog.id_tag = id_tag - new_prog.exterior_door_tag = exterior_door_tag - new_prog.interior_door_tag = interior_door_tag - new_prog.airpump_tag = airpump_tag - new_prog.sensor_tag = sensor_tag - new_prog.sanitize_external = sanitize_external - - new_prog.master = src - program = new_prog - -/obj/machinery/embedded_controller/radio/airlock_controller/update_icon_state() - if(on && program) - if(program.memory["processing"]) - icon_state = "airlock_control_process" - else - icon_state = "airlock_control_standby" - else - icon_state = "airlock_control_off" - - -/obj/machinery/embedded_controller/radio/airlock_controller/return_text() - var/state_options = null - - var/state = 0 - var/sensor_pressure = "----" - var/exterior_status = "----" - var/interior_status = "----" - var/pump_status = "----" - var/current_status = "Inactive
     " - if(program) - state = program.state - sensor_pressure = program.memory["sensor_pressure"] ? program.memory["sensor_pressure"] : "----" - exterior_status = program.memory["exterior_status"] ? program.memory["exterior_status"] : "----" - interior_status = program.memory["interior_status"] ? program.memory["interior_status"] : "----" - pump_status = program.memory["pump_status"] ? program.memory["pump_status"] : "----" - - switch(state) - if(AIRLOCK_STATE_INOPEN) - state_options = {"Close Interior Airlock
    -Cycle to Exterior Airlock
    "} - current_status = "Interior Airlock Open
    Chamber Pressurized" - if(AIRLOCK_STATE_PRESSURIZE) - state_options = "Abort Cycling
    " - current_status = "Cycling to Interior Airlock
    Chamber Pressurizing" - if(AIRLOCK_STATE_CLOSED) - state_options = {"Open Interior Airlock
    -Open Exterior Airlock
    "} - if(AIRLOCK_STATE_DEPRESSURIZE) - state_options = "Abort Cycling
    " - current_status = "Cycling to Exterior Airlock
    Chamber Depressurizing" - if(AIRLOCK_STATE_OUTOPEN) - state_options = {"Cycle to Interior Airlock
    -Close Exterior Airlock
    "} - current_status = "Exterior Airlock Open
    Chamber Depressurized" - - var/output = {"

    Airlock Status

    -
    -
    Current Status:
    [current_status]
    -
     
    -
    \> Chamber Pressure:
    [sensor_pressure] kPa
    -
    \> Control Pump:
    [pump_status]
    -
    \> Interior Door:
    [interior_status]
    -
    \> Exterior Door:
    [exterior_status]
    -
    -
    -[state_options]"} - - return output +//States for airlock_control +#define AIRLOCK_STATE_INOPEN -2 +#define AIRLOCK_STATE_PRESSURIZE -1 +#define AIRLOCK_STATE_CLOSED 0 +#define AIRLOCK_STATE_DEPRESSURIZE 1 +#define AIRLOCK_STATE_OUTOPEN 2 + +/datum/computer/file/embedded_program/airlock_controller + var/id_tag + var/exterior_door_tag //Burn chamber facing door + var/interior_door_tag //Station facing door + var/airpump_tag //See: dp_vent_pump.dm + var/sensor_tag //See: /obj/machinery/airlock_sensor + var/sanitize_external //Before the interior airlock opens, do we first drain all gases inside the chamber and then repressurize? + + state = AIRLOCK_STATE_CLOSED + var/target_state = AIRLOCK_STATE_CLOSED + var/sensor_pressure = null + +/datum/computer/file/embedded_program/airlock_controller/receive_signal(datum/signal/signal) + var/receive_tag = signal.data["tag"] + if(!receive_tag) + return + + if(receive_tag==sensor_tag) + if(signal.data["pressure"]) + sensor_pressure = text2num(signal.data["pressure"]) + + else if(receive_tag==exterior_door_tag) + memory["exterior_status"] = signal.data["door_status"] + + else if(receive_tag==interior_door_tag) + memory["interior_status"] = signal.data["door_status"] + + else if(receive_tag==airpump_tag) + if(signal.data["power"]) + memory["pump_status"] = signal.data["direction"] + else + memory["pump_status"] = "off" + + else if(receive_tag==id_tag) + switch(signal.data["command"]) + if("cycle") + if(state < AIRLOCK_STATE_CLOSED) + target_state = AIRLOCK_STATE_OUTOPEN + else + target_state = AIRLOCK_STATE_INOPEN + +/datum/computer/file/embedded_program/airlock_controller/receive_user_command(command) + switch(command) + if("cycle_closed") + target_state = AIRLOCK_STATE_CLOSED + if("cycle_exterior") + target_state = AIRLOCK_STATE_OUTOPEN + if("cycle_interior") + target_state = AIRLOCK_STATE_INOPEN + if("abort") + target_state = AIRLOCK_STATE_CLOSED + +/datum/computer/file/embedded_program/airlock_controller/process() + var/process_again = 1 + while(process_again) + process_again = 0 + switch(state) + if(AIRLOCK_STATE_INOPEN) // state -2 + if(target_state > state) + if(memory["interior_status"] == "closed") + state = AIRLOCK_STATE_CLOSED + process_again = 1 + else + post_signal(new /datum/signal(list( + "tag" = interior_door_tag, + "command" = "secure_close" + ))) + else + if(memory["pump_status"] != "off") + post_signal(new /datum/signal(list( + "tag" = airpump_tag, + "power" = 0, + "sigtype" = "command" + ))) + + if(AIRLOCK_STATE_PRESSURIZE) + if(target_state < state) + if(sensor_pressure >= ONE_ATMOSPHERE*0.95) + if(memory["interior_status"] == "open") + state = AIRLOCK_STATE_INOPEN + process_again = 1 + else + post_signal(new /datum/signal(list( + "tag" = interior_door_tag, + "command" = "secure_open" + ))) + else + var/datum/signal/signal = new(list( + "tag" = airpump_tag, + "sigtype" = "command" + )) + if(memory["pump_status"] == "siphon") + signal.data["stabilize"] = 1 + else if(memory["pump_status"] != "release") + signal.data["power"] = 1 + post_signal(signal) + else if(target_state > state) + state = AIRLOCK_STATE_CLOSED + process_again = 1 + + if(AIRLOCK_STATE_CLOSED) + if(target_state > state) + if(memory["interior_status"] == "closed") + state = AIRLOCK_STATE_DEPRESSURIZE + process_again = 1 + else + post_signal(new /datum/signal(list( + "tag" = interior_door_tag, + "command" = "secure_close" + ))) + else if(target_state < state) + if(memory["exterior_status"] == "closed") + state = AIRLOCK_STATE_PRESSURIZE + process_again = 1 + else + post_signal(new /datum/signal(list( + "tag" = exterior_door_tag, + "command" = "secure_close" + ))) + + else + if(memory["pump_status"] != "off") + post_signal(new /datum/signal(list( + "tag" = airpump_tag, + "power" = 0, + "sigtype" = "command" + ))) + + if(AIRLOCK_STATE_DEPRESSURIZE) + var/target_pressure = ONE_ATMOSPHERE*0.05 + if(sanitize_external) + target_pressure = ONE_ATMOSPHERE*0.01 + + if(sensor_pressure <= target_pressure) + if(target_state > state) + if(memory["exterior_status"] == "open") + state = AIRLOCK_STATE_OUTOPEN + else + post_signal(new /datum/signal(list( + "tag" = exterior_door_tag, + "command" = "secure_open" + ))) + else if(target_state < state) + state = AIRLOCK_STATE_CLOSED + process_again = 1 + else if((target_state < state) && !sanitize_external) + state = AIRLOCK_STATE_CLOSED + process_again = 1 + else + var/datum/signal/signal = new(list( + "tag" = airpump_tag, + "sigtype" = "command" + )) + if(memory["pump_status"] == "release") + signal.data["purge"] = 1 + else if(memory["pump_status"] != "siphon") + signal.data["power"] = 1 + post_signal(signal) + + if(AIRLOCK_STATE_OUTOPEN) //state 2 + if(target_state < state) + if(memory["exterior_status"] == "closed") + if(sanitize_external) + state = AIRLOCK_STATE_DEPRESSURIZE + process_again = 1 + else + state = AIRLOCK_STATE_CLOSED + process_again = 1 + else + post_signal(new /datum/signal(list( + "tag" = exterior_door_tag, + "command" = "secure_close" + ))) + else + if(memory["pump_status"] != "off") + post_signal(new /datum/signal(list( + "tag" = airpump_tag, + "power" = 0, + "sigtype" = "command" + ))) + + memory["sensor_pressure"] = sensor_pressure + memory["processing"] = state != target_state + //sensor_pressure = null //not sure if we can comment this out. Uncomment in case of problems -rastaf0 + + return 1 + + +/obj/machinery/embedded_controller/radio/airlock_controller + icon = 'icons/obj/airlock_machines.dmi' + icon_state = "airlock_control_standby" + + name = "airlock console" + density = FALSE + + frequency = FREQ_AIRLOCK_CONTROL + power_channel = AREA_USAGE_ENVIRON + + // Setup parameters only + var/id_tag + var/exterior_door_tag + var/interior_door_tag + var/airpump_tag + var/sensor_tag + var/sanitize_external + +/obj/machinery/embedded_controller/radio/airlock_controller/incinerator_toxmix + name = "Incinerator Access Console" + airpump_tag = INCINERATOR_TOXMIX_DP_VENTPUMP + exterior_door_tag = INCINERATOR_TOXMIX_AIRLOCK_EXTERIOR + id_tag = INCINERATOR_TOXMIX_AIRLOCK_CONTROLLER + interior_door_tag = INCINERATOR_TOXMIX_AIRLOCK_INTERIOR + sanitize_external = TRUE + sensor_tag = INCINERATOR_TOXMIX_AIRLOCK_SENSOR + +/obj/machinery/embedded_controller/radio/airlock_controller/incinerator_atmos + name = "Incinerator Access Console" + airpump_tag = INCINERATOR_ATMOS_DP_VENTPUMP + exterior_door_tag = INCINERATOR_ATMOS_AIRLOCK_EXTERIOR + id_tag = INCINERATOR_ATMOS_AIRLOCK_CONTROLLER + interior_door_tag = INCINERATOR_ATMOS_AIRLOCK_INTERIOR + sanitize_external = TRUE + sensor_tag = INCINERATOR_ATMOS_AIRLOCK_SENSOR + +/obj/machinery/embedded_controller/radio/airlock_controller/incinerator_syndicatelava + name = "Incinerator Access Console" + airpump_tag = INCINERATOR_SYNDICATELAVA_DP_VENTPUMP + exterior_door_tag = INCINERATOR_SYNDICATELAVA_AIRLOCK_EXTERIOR + id_tag = INCINERATOR_SYNDICATELAVA_AIRLOCK_CONTROLLER + interior_door_tag = INCINERATOR_SYNDICATELAVA_AIRLOCK_INTERIOR + sanitize_external = TRUE + sensor_tag = INCINERATOR_SYNDICATELAVA_AIRLOCK_SENSOR + +/obj/machinery/embedded_controller/radio/airlock_controller/Initialize(mapload) + . = ..() + if(!mapload) + return + + var/datum/computer/file/embedded_program/airlock_controller/new_prog = new + + new_prog.id_tag = id_tag + new_prog.exterior_door_tag = exterior_door_tag + new_prog.interior_door_tag = interior_door_tag + new_prog.airpump_tag = airpump_tag + new_prog.sensor_tag = sensor_tag + new_prog.sanitize_external = sanitize_external + + new_prog.master = src + program = new_prog + +/obj/machinery/embedded_controller/radio/airlock_controller/update_icon_state() + if(on && program) + if(program.memory["processing"]) + icon_state = "airlock_control_process" + else + icon_state = "airlock_control_standby" + else + icon_state = "airlock_control_off" + + +/obj/machinery/embedded_controller/radio/airlock_controller/return_text() + var/state_options = null + + var/state = 0 + var/sensor_pressure = "----" + var/exterior_status = "----" + var/interior_status = "----" + var/pump_status = "----" + var/current_status = "Inactive
     " + if(program) + state = program.state + sensor_pressure = program.memory["sensor_pressure"] ? program.memory["sensor_pressure"] : "----" + exterior_status = program.memory["exterior_status"] ? program.memory["exterior_status"] : "----" + interior_status = program.memory["interior_status"] ? program.memory["interior_status"] : "----" + pump_status = program.memory["pump_status"] ? program.memory["pump_status"] : "----" + + switch(state) + if(AIRLOCK_STATE_INOPEN) + state_options = {"Close Interior Airlock
    +Cycle to Exterior Airlock
    "} + current_status = "Interior Airlock Open
    Chamber Pressurized" + if(AIRLOCK_STATE_PRESSURIZE) + state_options = "Abort Cycling
    " + current_status = "Cycling to Interior Airlock
    Chamber Pressurizing" + if(AIRLOCK_STATE_CLOSED) + state_options = {"Open Interior Airlock
    +Open Exterior Airlock
    "} + if(AIRLOCK_STATE_DEPRESSURIZE) + state_options = "Abort Cycling
    " + current_status = "Cycling to Exterior Airlock
    Chamber Depressurizing" + if(AIRLOCK_STATE_OUTOPEN) + state_options = {"Cycle to Interior Airlock
    +Close Exterior Airlock
    "} + current_status = "Exterior Airlock Open
    Chamber Depressurized" + + var/output = {"

    Airlock Status

    +
    +
    Current Status:
    [current_status]
    +
     
    +
    \> Chamber Pressure:
    [sensor_pressure] kPa
    +
    \> Control Pump:
    [pump_status]
    +
    \> Interior Door:
    [interior_status]
    +
    \> Exterior Door:
    [exterior_status]
    +
    +
    +[state_options]"} + + return output diff --git a/code/game/machinery/embedded_controller/embedded_controller_base.dm b/code/game/machinery/embedded_controller/embedded_controller_base.dm index 8607b26642f5..6fd351bcff65 100644 --- a/code/game/machinery/embedded_controller/embedded_controller_base.dm +++ b/code/game/machinery/embedded_controller/embedded_controller_base.dm @@ -1,85 +1,85 @@ -/datum/computer/file/embedded_program - var/list/memory = list() - var/state - var/obj/machinery/embedded_controller/master - -/datum/computer/file/embedded_program/proc/post_signal(datum/signal/signal, comm_line) - if(master) - master.post_signal(signal, comm_line) - else - qdel(signal) - -/datum/computer/file/embedded_program/proc/receive_user_command(command) - -/datum/computer/file/embedded_program/proc/receive_signal(datum/signal/signal) - return null - -/datum/computer/file/embedded_program/process() - return 0 - -/obj/machinery/embedded_controller - var/datum/computer/file/embedded_program/program - - name = "embedded controller" - density = FALSE - - var/on = TRUE - -/obj/machinery/embedded_controller/ui_interact(mob/user) - . = ..() - user.set_machine(src) - var/datum/browser/popup = new(user, "computer", name) // Set up the popup browser window - popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) - popup.set_content(return_text()) - popup.open() - -/obj/machinery/embedded_controller/proc/return_text() - -/obj/machinery/embedded_controller/proc/post_signal(datum/signal/signal, comm_line) - return 0 - -/obj/machinery/embedded_controller/receive_signal(datum/signal/signal) - if(istype(signal) && program) - program.receive_signal(signal) - -/obj/machinery/embedded_controller/Topic(href, href_list) - if(..()) - return 0 - - if(program) - program.receive_user_command(href_list["command"]) - addtimer(CALLBACK(program, /datum/computer/file/embedded_program.proc/process), 5) - - usr.set_machine(src) - addtimer(CALLBACK(src, .proc/updateDialog), 5) - -/obj/machinery/embedded_controller/process() - if(program) - program.process() - - update_icon() - src.updateDialog() - -/obj/machinery/embedded_controller/radio - var/frequency - var/datum/radio_frequency/radio_connection - -/obj/machinery/embedded_controller/radio/Destroy() - SSradio.remove_object(src,frequency) - return ..() - -/obj/machinery/embedded_controller/radio/Initialize() - . = ..() - set_frequency(frequency) - -/obj/machinery/embedded_controller/radio/post_signal(datum/signal/signal) - signal.transmission_method = TRANSMISSION_RADIO - if(radio_connection) - return radio_connection.post_signal(src, signal) - else - signal = null - -/obj/machinery/embedded_controller/radio/proc/set_frequency(new_frequency) - SSradio.remove_object(src, frequency) - frequency = new_frequency - radio_connection = SSradio.add_object(src, frequency) +/datum/computer/file/embedded_program + var/list/memory = list() + var/state + var/obj/machinery/embedded_controller/master + +/datum/computer/file/embedded_program/proc/post_signal(datum/signal/signal, comm_line) + if(master) + master.post_signal(signal, comm_line) + else + qdel(signal) + +/datum/computer/file/embedded_program/proc/receive_user_command(command) + +/datum/computer/file/embedded_program/proc/receive_signal(datum/signal/signal) + return null + +/datum/computer/file/embedded_program/process() + return 0 + +/obj/machinery/embedded_controller + var/datum/computer/file/embedded_program/program + + name = "embedded controller" + density = FALSE + + var/on = TRUE + +/obj/machinery/embedded_controller/ui_interact(mob/user) + . = ..() + user.set_machine(src) + var/datum/browser/popup = new(user, "computer", name) // Set up the popup browser window + popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) + popup.set_content(return_text()) + popup.open() + +/obj/machinery/embedded_controller/proc/return_text() + +/obj/machinery/embedded_controller/proc/post_signal(datum/signal/signal, comm_line) + return 0 + +/obj/machinery/embedded_controller/receive_signal(datum/signal/signal) + if(istype(signal) && program) + program.receive_signal(signal) + +/obj/machinery/embedded_controller/Topic(href, href_list) + if(..()) + return 0 + + if(program) + program.receive_user_command(href_list["command"]) + addtimer(CALLBACK(program, /datum/computer/file/embedded_program.proc/process), 5) + + usr.set_machine(src) + addtimer(CALLBACK(src, .proc/updateDialog), 5) + +/obj/machinery/embedded_controller/process() + if(program) + program.process() + + update_icon() + src.updateDialog() + +/obj/machinery/embedded_controller/radio + var/frequency + var/datum/radio_frequency/radio_connection + +/obj/machinery/embedded_controller/radio/Destroy() + SSradio.remove_object(src,frequency) + return ..() + +/obj/machinery/embedded_controller/radio/Initialize() + . = ..() + set_frequency(frequency) + +/obj/machinery/embedded_controller/radio/post_signal(datum/signal/signal) + signal.transmission_method = TRANSMISSION_RADIO + if(radio_connection) + return radio_connection.post_signal(src, signal) + else + signal = null + +/obj/machinery/embedded_controller/radio/proc/set_frequency(new_frequency) + SSradio.remove_object(src, frequency) + frequency = new_frequency + radio_connection = SSradio.add_object(src, frequency) diff --git a/code/game/machinery/embedded_controller/simple_vent_controller.dm b/code/game/machinery/embedded_controller/simple_vent_controller.dm index 6de766090c2a..4de102becb00 100644 --- a/code/game/machinery/embedded_controller/simple_vent_controller.dm +++ b/code/game/machinery/embedded_controller/simple_vent_controller.dm @@ -1,72 +1,72 @@ -/datum/computer/file/embedded_program/simple_vent_controller - - var/airpump_tag - -/datum/computer/file/embedded_program/simple_vent_controller/receive_user_command(command) - switch(command) - if("vent_inactive") - post_signal(new /datum/signal(list( - "tag" = airpump_tag, - "sigtype" = "command", - "power" = 0 - ))) - - if("vent_pump") - post_signal(new /datum/signal(list( - "tag" = airpump_tag, - "sigtype" = "command", - "stabilize" = 1, - "power" = 1 - ))) - - if("vent_clear") - post_signal(new /datum/signal(list( - "tag" = airpump_tag, - "sigtype" = "command", - "purge" = 1, - "power" = 1 - ))) - -/datum/computer/file/embedded_program/simple_vent_controller/process() - return 0 - - -/obj/machinery/embedded_controller/radio/simple_vent_controller - icon = 'icons/obj/airlock_machines.dmi' - icon_state = "airlock_control_standby" - - name = "vent controller" - density = FALSE - - frequency = FREQ_ATMOS_CONTROL - power_channel = AREA_USAGE_ENVIRON - - // Setup parameters only - var/airpump_tag - -/obj/machinery/embedded_controller/radio/simple_vent_controller/Initialize(mapload) - . = ..() - if(!mapload) - return - var/datum/computer/file/embedded_program/simple_vent_controller/new_prog = new - - new_prog.airpump_tag = airpump_tag - new_prog.master = src - program = new_prog - -/obj/machinery/embedded_controller/radio/simple_vent_controller/update_icon_state() - if(on && program) - icon_state = "airlock_control_standby" - else - icon_state = "airlock_control_off" - - -/obj/machinery/embedded_controller/radio/simple_vent_controller/return_text() - var/state_options = null - state_options = {"Deactivate Vent
    -Activate Vent / Pump
    -Activate Vent / Clear
    "} - var/output = {"Vent Control Console
    -[state_options]
    "} - - return output +/datum/computer/file/embedded_program/simple_vent_controller + + var/airpump_tag + +/datum/computer/file/embedded_program/simple_vent_controller/receive_user_command(command) + switch(command) + if("vent_inactive") + post_signal(new /datum/signal(list( + "tag" = airpump_tag, + "sigtype" = "command", + "power" = 0 + ))) + + if("vent_pump") + post_signal(new /datum/signal(list( + "tag" = airpump_tag, + "sigtype" = "command", + "stabilize" = 1, + "power" = 1 + ))) + + if("vent_clear") + post_signal(new /datum/signal(list( + "tag" = airpump_tag, + "sigtype" = "command", + "purge" = 1, + "power" = 1 + ))) + +/datum/computer/file/embedded_program/simple_vent_controller/process() + return 0 + + +/obj/machinery/embedded_controller/radio/simple_vent_controller + icon = 'icons/obj/airlock_machines.dmi' + icon_state = "airlock_control_standby" + + name = "vent controller" + density = FALSE + + frequency = FREQ_ATMOS_CONTROL + power_channel = AREA_USAGE_ENVIRON + + // Setup parameters only + var/airpump_tag + +/obj/machinery/embedded_controller/radio/simple_vent_controller/Initialize(mapload) + . = ..() + if(!mapload) + return + var/datum/computer/file/embedded_program/simple_vent_controller/new_prog = new + + new_prog.airpump_tag = airpump_tag + new_prog.master = src + program = new_prog + +/obj/machinery/embedded_controller/radio/simple_vent_controller/update_icon_state() + if(on && program) + icon_state = "airlock_control_standby" + else + icon_state = "airlock_control_off" + + +/obj/machinery/embedded_controller/radio/simple_vent_controller/return_text() + var/state_options = null + state_options = {"Deactivate Vent
    +Activate Vent / Pump
    +Activate Vent / Clear
    "} + var/output = {"Vent Control Console
    +[state_options]
    "} + + return output diff --git a/code/game/machinery/firealarm.dm b/code/game/machinery/firealarm.dm index b8fd11102201..21b948eddf1a 100644 --- a/code/game/machinery/firealarm.dm +++ b/code/game/machinery/firealarm.dm @@ -1,347 +1,347 @@ -#define FIREALARM_COOLDOWN 67 // Chosen fairly arbitrarily, it is the length of the audio in FireAlarm.ogg. The actual track length is 7 seconds 8ms but but the audio stops at 6s 700ms - -/obj/item/electronics/firealarm - name = "fire alarm electronics" - custom_price = 50 - desc = "A fire alarm circuit. Can handle heat levels up to 40 degrees celsius." - -/obj/item/wallframe/firealarm - name = "fire alarm frame" - desc = "Used for building fire alarms." - icon = 'icons/obj/monitors.dmi' - icon_state = "fire_bitem" - result_path = /obj/machinery/firealarm - -/obj/machinery/firealarm - name = "fire alarm" - desc = "\"Pull this in case of emergency\". Thus, keep pulling it forever." - icon = 'icons/obj/monitors.dmi' - icon_state = "fire0" - max_integrity = 250 - integrity_failure = 0.4 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 30) - use_power = IDLE_POWER_USE - idle_power_usage = 2 - active_power_usage = 6 - power_channel = AREA_USAGE_ENVIRON - resistance_flags = FIRE_PROOF - - light_power = 0 - light_range = 7 - light_color = "#ff3232" - - //Trick to get the glowing overlay visible from a distance - luminosity = 1 - - var/detecting = 1 - var/buildstage = 2 // 2 = complete, 1 = no wires, 0 = circuit gone - var/last_alarm = 0 - var/area/myarea = null - -/obj/machinery/firealarm/Initialize(mapload, dir, building) - . = ..() - if(dir) - src.setDir(dir) - if(building) - buildstage = 0 - panel_open = TRUE - pixel_x = (dir & 3)? 0 : (dir == 4 ? -24 : 24) - pixel_y = (dir & 3)? (dir ==1 ? -24 : 24) : 0 - update_icon() - myarea = get_area(src) - LAZYADD(myarea.firealarms, src) - -/obj/machinery/firealarm/Destroy() - LAZYREMOVE(myarea.firealarms, src) - return ..() - -/obj/machinery/firealarm/update_icon_state() - if(panel_open) - icon_state = "fire_b[buildstage]" - return - - if(machine_stat & BROKEN) - icon_state = "firex" - return - - icon_state = "fire0" - -/obj/machinery/firealarm/update_overlays() - . = ..() - SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays) - - if(machine_stat & NOPOWER) - return - - . += "fire_overlay" - - if(is_station_level(z)) - . += "fire_[GLOB.security_level]" - SSvis_overlays.add_vis_overlay(src, icon, "fire_[GLOB.security_level]", layer, plane, dir) - SSvis_overlays.add_vis_overlay(src, icon, "fire_[GLOB.security_level]", layer, EMISSIVE_PLANE, dir) - else - . += "fire_[SEC_LEVEL_GREEN]" - SSvis_overlays.add_vis_overlay(src, icon, "fire_[SEC_LEVEL_GREEN]", layer, plane, dir) - SSvis_overlays.add_vis_overlay(src, icon, "fire_[SEC_LEVEL_GREEN]", layer, EMISSIVE_PLANE, dir) - - var/area/A = get_area(src) - - if(!detecting || !A.fire) - . += "fire_off" - SSvis_overlays.add_vis_overlay(src, icon, "fire_off", layer, plane, dir) - SSvis_overlays.add_vis_overlay(src, icon, "fire_off", layer, EMISSIVE_PLANE, dir) - else if(obj_flags & EMAGGED) - . += "fire_emagged" - SSvis_overlays.add_vis_overlay(src, icon, "fire_emagged", layer, plane, dir) - SSvis_overlays.add_vis_overlay(src, icon, "fire_emagged", layer, EMISSIVE_PLANE, dir) - else - . += "fire_on" - SSvis_overlays.add_vis_overlay(src, icon, "fire_on", layer, plane, dir) - SSvis_overlays.add_vis_overlay(src, icon, "fire_on", layer, EMISSIVE_PLANE, dir) - -/obj/machinery/firealarm/emp_act(severity) - . = ..() - - if (. & EMP_PROTECT_SELF) - return - - if(prob(50 / severity)) - alarm() - -/obj/machinery/firealarm/emag_act(mob/user) - if(obj_flags & EMAGGED) - return - obj_flags |= EMAGGED - update_icon() - if(user) - user.visible_message("Sparks fly out of [src]!", - "You emag [src], disabling its thermal sensors.") - playsound(src, "sparks", 50, TRUE) - -/obj/machinery/firealarm/temperature_expose(datum/gas_mixture/air, temperature, volume) - if((temperature > T0C + 200 || temperature < BODYTEMP_COLD_DAMAGE_LIMIT) && (last_alarm+FIREALARM_COOLDOWN < world.time) && !(obj_flags & EMAGGED) && detecting && !machine_stat) - alarm() - ..() - -/obj/machinery/firealarm/proc/alarm(mob/user) - if(!is_operational() || (last_alarm+FIREALARM_COOLDOWN > world.time)) - return - last_alarm = world.time - var/area/A = get_area(src) - A.firealert(src) - playsound(loc, 'goon/sound/machinery/FireAlarm.ogg', 75) - if(user) - log_game("[user] triggered a fire alarm at [COORD(src)]") - -/obj/machinery/firealarm/proc/reset(mob/user) - if(!is_operational()) - return - var/area/A = get_area(src) - A.firereset(src) - if(user) - log_game("[user] reset a fire alarm at [COORD(src)]") - -/obj/machinery/firealarm/attack_hand(mob/user) - if(buildstage != 2) - return ..() - add_fingerprint(user) - var/area/A = get_area(src) - if(A.fire) - reset(user) - else - alarm(user) - -/obj/machinery/firealarm/attack_ai(mob/user) - return attack_hand(user) - -/obj/machinery/firealarm/attack_robot(mob/user) - return attack_hand(user) - -/obj/machinery/firealarm/attackby(obj/item/W, mob/user, params) - add_fingerprint(user) - - if(W.tool_behaviour == TOOL_SCREWDRIVER && buildstage == 2) - W.play_tool_sound(src) - panel_open = !panel_open - to_chat(user, "The wires have been [panel_open ? "exposed" : "unexposed"].") - update_icon() - return - - if(panel_open) - - if(W.tool_behaviour == TOOL_WELDER && user.a_intent == INTENT_HELP) - if(obj_integrity < max_integrity) - if(!W.tool_start_check(user, amount=0)) - return - - to_chat(user, "You begin repairing [src]...") - if(W.use_tool(src, user, 40, volume=50)) - obj_integrity = max_integrity - to_chat(user, "You repair [src].") - else - to_chat(user, "[src] is already in good condition!") - return - - switch(buildstage) - if(2) - if(W.tool_behaviour == TOOL_MULTITOOL) - detecting = !detecting - if (src.detecting) - user.visible_message("[user] reconnects [src]'s detecting unit!", "You reconnect [src]'s detecting unit.") - else - user.visible_message("[user] disconnects [src]'s detecting unit!", "You disconnect [src]'s detecting unit.") - return - - else if(W.tool_behaviour == TOOL_WIRECUTTER) - buildstage = 1 - W.play_tool_sound(src) - new /obj/item/stack/cable_coil(user.loc, 5) - to_chat(user, "You cut the wires from \the [src].") - update_icon() - return - - else if(W.force) //hit and turn it on - ..() - var/area/A = get_area(src) - if(!A.fire) - alarm() - return - - if(1) - if(istype(W, /obj/item/stack/cable_coil)) - var/obj/item/stack/cable_coil/coil = W - if(coil.get_amount() < 5) - to_chat(user, "You need more cable for this!") - else - coil.use(5) - buildstage = 2 - to_chat(user, "You wire \the [src].") - update_icon() - return - - else if(W.tool_behaviour == TOOL_CROWBAR) - user.visible_message("[user.name] removes the electronics from [src.name].", \ - "You start prying out the circuit...") - if(W.use_tool(src, user, 20, volume=50)) - if(buildstage == 1) - if(machine_stat & BROKEN) - to_chat(user, "You remove the destroyed circuit.") - machine_stat &= ~BROKEN - else - to_chat(user, "You pry out the circuit.") - new /obj/item/electronics/firealarm(user.loc) - buildstage = 0 - update_icon() - return - if(0) - if(istype(W, /obj/item/electronics/firealarm)) - to_chat(user, "You insert the circuit.") - qdel(W) - buildstage = 1 - update_icon() - return - - else if(istype(W, /obj/item/electroadaptive_pseudocircuit)) - var/obj/item/electroadaptive_pseudocircuit/P = W - if(!P.adapt_circuit(user, 15)) - return - user.visible_message("[user] fabricates a circuit and places it into [src].", \ - "You adapt a fire alarm circuit and slot it into the assembly.") - buildstage = 1 - update_icon() - return - - else if(W.tool_behaviour == TOOL_WRENCH) - user.visible_message("[user] removes the fire alarm assembly from the wall.", \ - "You remove the fire alarm assembly from the wall.") - var/obj/item/wallframe/firealarm/frame = new /obj/item/wallframe/firealarm() - frame.forceMove(user.drop_location()) - W.play_tool_sound(src) - qdel(src) - return - - return ..() - -/obj/machinery/firealarm/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd) - if((buildstage == 0) && (the_rcd.upgrade & RCD_UPGRADE_SIMPLE_CIRCUITS)) - return list("mode" = RCD_UPGRADE_SIMPLE_CIRCUITS, "delay" = 20, "cost" = 1) - return FALSE - -/obj/machinery/firealarm/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode) - switch(passed_mode) - if(RCD_UPGRADE_SIMPLE_CIRCUITS) - user.visible_message("[user] fabricates a circuit and places it into [src].", \ - "You adapt a fire alarm circuit and slot it into the assembly.") - buildstage = 1 - update_icon() - return TRUE - return FALSE - -/obj/machinery/firealarm/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir) - . = ..() - if(.) //damage received - if(obj_integrity > 0 && !(machine_stat & BROKEN) && buildstage != 0) - if(prob(33)) - alarm() - -/obj/machinery/firealarm/singularity_pull(S, current_size) - if (current_size >= STAGE_FIVE) // If the singulo is strong enough to pull anchored objects, the fire alarm experiences integrity failure - deconstruct() - ..() - -/obj/machinery/firealarm/obj_break(damage_flag) - if(buildstage == 0) //can't break the electronics if there isn't any inside. - return - . = ..() - if(.) - LAZYREMOVE(myarea.firealarms, src) - -/obj/machinery/firealarm/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - new /obj/item/stack/sheet/metal(loc, 1) - if(!(machine_stat & BROKEN)) - var/obj/item/I = new /obj/item/electronics/firealarm(loc) - if(!disassembled) - I.obj_integrity = I.max_integrity * 0.5 - new /obj/item/stack/cable_coil(loc, 3) - qdel(src) - -/obj/machinery/firealarm/proc/update_fire_light(fire) - if(fire == !!light_power) - return // do nothing if we're already active - if(fire) - set_light(l_power = 0.8) - else - set_light(l_power = 0) - -/* - * Return of Party button - */ - -/area - var/party = FALSE - -/obj/machinery/firealarm/partyalarm - name = "\improper PARTY BUTTON" - desc = "Cuban Pete is in the house!" - var/static/party_overlay - -/obj/machinery/firealarm/partyalarm/reset() - if (machine_stat & (NOPOWER|BROKEN)) - return - var/area/A = get_area(src) - if (!A || !A.party) - return - A.party = FALSE - A.cut_overlay(party_overlay) - -/obj/machinery/firealarm/partyalarm/alarm() - if (machine_stat & (NOPOWER|BROKEN)) - return - var/area/A = get_area(src) - if (!A || A.party || A.name == "Space") - return - A.party = TRUE - if (!party_overlay) - party_overlay = iconstate2appearance('icons/turf/areas.dmi', "party") - A.add_overlay(party_overlay) +#define FIREALARM_COOLDOWN 67 // Chosen fairly arbitrarily, it is the length of the audio in FireAlarm.ogg. The actual track length is 7 seconds 8ms but but the audio stops at 6s 700ms + +/obj/item/electronics/firealarm + name = "fire alarm electronics" + custom_price = 50 + desc = "A fire alarm circuit. Can handle heat levels up to 40 degrees celsius." + +/obj/item/wallframe/firealarm + name = "fire alarm frame" + desc = "Used for building fire alarms." + icon = 'icons/obj/monitors.dmi' + icon_state = "fire_bitem" + result_path = /obj/machinery/firealarm + +/obj/machinery/firealarm + name = "fire alarm" + desc = "\"Pull this in case of emergency\". Thus, keep pulling it forever." + icon = 'icons/obj/monitors.dmi' + icon_state = "fire0" + max_integrity = 250 + integrity_failure = 0.4 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 30) + use_power = IDLE_POWER_USE + idle_power_usage = 2 + active_power_usage = 6 + power_channel = AREA_USAGE_ENVIRON + resistance_flags = FIRE_PROOF + + light_power = 0 + light_range = 7 + light_color = "#ff3232" + + //Trick to get the glowing overlay visible from a distance + luminosity = 1 + + var/detecting = 1 + var/buildstage = 2 // 2 = complete, 1 = no wires, 0 = circuit gone + var/last_alarm = 0 + var/area/myarea = null + +/obj/machinery/firealarm/Initialize(mapload, dir, building) + . = ..() + if(dir) + src.setDir(dir) + if(building) + buildstage = 0 + panel_open = TRUE + pixel_x = (dir & 3)? 0 : (dir == 4 ? -24 : 24) + pixel_y = (dir & 3)? (dir ==1 ? -24 : 24) : 0 + update_icon() + myarea = get_area(src) + LAZYADD(myarea.firealarms, src) + +/obj/machinery/firealarm/Destroy() + LAZYREMOVE(myarea.firealarms, src) + return ..() + +/obj/machinery/firealarm/update_icon_state() + if(panel_open) + icon_state = "fire_b[buildstage]" + return + + if(machine_stat & BROKEN) + icon_state = "firex" + return + + icon_state = "fire0" + +/obj/machinery/firealarm/update_overlays() + . = ..() + SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays) + + if(machine_stat & NOPOWER) + return + + . += "fire_overlay" + + if(is_station_level(z)) + . += "fire_[GLOB.security_level]" + SSvis_overlays.add_vis_overlay(src, icon, "fire_[GLOB.security_level]", layer, plane, dir) + SSvis_overlays.add_vis_overlay(src, icon, "fire_[GLOB.security_level]", layer, EMISSIVE_PLANE, dir) + else + . += "fire_[SEC_LEVEL_GREEN]" + SSvis_overlays.add_vis_overlay(src, icon, "fire_[SEC_LEVEL_GREEN]", layer, plane, dir) + SSvis_overlays.add_vis_overlay(src, icon, "fire_[SEC_LEVEL_GREEN]", layer, EMISSIVE_PLANE, dir) + + var/area/A = get_area(src) + + if(!detecting || !A.fire) + . += "fire_off" + SSvis_overlays.add_vis_overlay(src, icon, "fire_off", layer, plane, dir) + SSvis_overlays.add_vis_overlay(src, icon, "fire_off", layer, EMISSIVE_PLANE, dir) + else if(obj_flags & EMAGGED) + . += "fire_emagged" + SSvis_overlays.add_vis_overlay(src, icon, "fire_emagged", layer, plane, dir) + SSvis_overlays.add_vis_overlay(src, icon, "fire_emagged", layer, EMISSIVE_PLANE, dir) + else + . += "fire_on" + SSvis_overlays.add_vis_overlay(src, icon, "fire_on", layer, plane, dir) + SSvis_overlays.add_vis_overlay(src, icon, "fire_on", layer, EMISSIVE_PLANE, dir) + +/obj/machinery/firealarm/emp_act(severity) + . = ..() + + if (. & EMP_PROTECT_SELF) + return + + if(prob(50 / severity)) + alarm() + +/obj/machinery/firealarm/emag_act(mob/user) + if(obj_flags & EMAGGED) + return + obj_flags |= EMAGGED + update_icon() + if(user) + user.visible_message("Sparks fly out of [src]!", + "You emag [src], disabling its thermal sensors.") + playsound(src, "sparks", 50, TRUE) + +/obj/machinery/firealarm/temperature_expose(datum/gas_mixture/air, temperature, volume) + if((temperature > T0C + 200 || temperature < BODYTEMP_COLD_DAMAGE_LIMIT) && (last_alarm+FIREALARM_COOLDOWN < world.time) && !(obj_flags & EMAGGED) && detecting && !machine_stat) + alarm() + ..() + +/obj/machinery/firealarm/proc/alarm(mob/user) + if(!is_operational() || (last_alarm+FIREALARM_COOLDOWN > world.time)) + return + last_alarm = world.time + var/area/A = get_area(src) + A.firealert(src) + playsound(loc, 'goon/sound/machinery/FireAlarm.ogg', 75) + if(user) + log_game("[user] triggered a fire alarm at [COORD(src)]") + +/obj/machinery/firealarm/proc/reset(mob/user) + if(!is_operational()) + return + var/area/A = get_area(src) + A.firereset(src) + if(user) + log_game("[user] reset a fire alarm at [COORD(src)]") + +/obj/machinery/firealarm/attack_hand(mob/user) + if(buildstage != 2) + return ..() + add_fingerprint(user) + var/area/A = get_area(src) + if(A.fire) + reset(user) + else + alarm(user) + +/obj/machinery/firealarm/attack_ai(mob/user) + return attack_hand(user) + +/obj/machinery/firealarm/attack_robot(mob/user) + return attack_hand(user) + +/obj/machinery/firealarm/attackby(obj/item/W, mob/user, params) + add_fingerprint(user) + + if(W.tool_behaviour == TOOL_SCREWDRIVER && buildstage == 2) + W.play_tool_sound(src) + panel_open = !panel_open + to_chat(user, "The wires have been [panel_open ? "exposed" : "unexposed"].") + update_icon() + return + + if(panel_open) + + if(W.tool_behaviour == TOOL_WELDER && user.a_intent == INTENT_HELP) + if(obj_integrity < max_integrity) + if(!W.tool_start_check(user, amount=0)) + return + + to_chat(user, "You begin repairing [src]...") + if(W.use_tool(src, user, 40, volume=50)) + obj_integrity = max_integrity + to_chat(user, "You repair [src].") + else + to_chat(user, "[src] is already in good condition!") + return + + switch(buildstage) + if(2) + if(W.tool_behaviour == TOOL_MULTITOOL) + detecting = !detecting + if (src.detecting) + user.visible_message("[user] reconnects [src]'s detecting unit!", "You reconnect [src]'s detecting unit.") + else + user.visible_message("[user] disconnects [src]'s detecting unit!", "You disconnect [src]'s detecting unit.") + return + + else if(W.tool_behaviour == TOOL_WIRECUTTER) + buildstage = 1 + W.play_tool_sound(src) + new /obj/item/stack/cable_coil(user.loc, 5) + to_chat(user, "You cut the wires from \the [src].") + update_icon() + return + + else if(W.force) //hit and turn it on + ..() + var/area/A = get_area(src) + if(!A.fire) + alarm() + return + + if(1) + if(istype(W, /obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/coil = W + if(coil.get_amount() < 5) + to_chat(user, "You need more cable for this!") + else + coil.use(5) + buildstage = 2 + to_chat(user, "You wire \the [src].") + update_icon() + return + + else if(W.tool_behaviour == TOOL_CROWBAR) + user.visible_message("[user.name] removes the electronics from [src.name].", \ + "You start prying out the circuit...") + if(W.use_tool(src, user, 20, volume=50)) + if(buildstage == 1) + if(machine_stat & BROKEN) + to_chat(user, "You remove the destroyed circuit.") + machine_stat &= ~BROKEN + else + to_chat(user, "You pry out the circuit.") + new /obj/item/electronics/firealarm(user.loc) + buildstage = 0 + update_icon() + return + if(0) + if(istype(W, /obj/item/electronics/firealarm)) + to_chat(user, "You insert the circuit.") + qdel(W) + buildstage = 1 + update_icon() + return + + else if(istype(W, /obj/item/electroadaptive_pseudocircuit)) + var/obj/item/electroadaptive_pseudocircuit/P = W + if(!P.adapt_circuit(user, 15)) + return + user.visible_message("[user] fabricates a circuit and places it into [src].", \ + "You adapt a fire alarm circuit and slot it into the assembly.") + buildstage = 1 + update_icon() + return + + else if(W.tool_behaviour == TOOL_WRENCH) + user.visible_message("[user] removes the fire alarm assembly from the wall.", \ + "You remove the fire alarm assembly from the wall.") + var/obj/item/wallframe/firealarm/frame = new /obj/item/wallframe/firealarm() + frame.forceMove(user.drop_location()) + W.play_tool_sound(src) + qdel(src) + return + + return ..() + +/obj/machinery/firealarm/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd) + if((buildstage == 0) && (the_rcd.upgrade & RCD_UPGRADE_SIMPLE_CIRCUITS)) + return list("mode" = RCD_UPGRADE_SIMPLE_CIRCUITS, "delay" = 20, "cost" = 1) + return FALSE + +/obj/machinery/firealarm/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode) + switch(passed_mode) + if(RCD_UPGRADE_SIMPLE_CIRCUITS) + user.visible_message("[user] fabricates a circuit and places it into [src].", \ + "You adapt a fire alarm circuit and slot it into the assembly.") + buildstage = 1 + update_icon() + return TRUE + return FALSE + +/obj/machinery/firealarm/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir) + . = ..() + if(.) //damage received + if(obj_integrity > 0 && !(machine_stat & BROKEN) && buildstage != 0) + if(prob(33)) + alarm() + +/obj/machinery/firealarm/singularity_pull(S, current_size) + if (current_size >= STAGE_FIVE) // If the singulo is strong enough to pull anchored objects, the fire alarm experiences integrity failure + deconstruct() + ..() + +/obj/machinery/firealarm/obj_break(damage_flag) + if(buildstage == 0) //can't break the electronics if there isn't any inside. + return + . = ..() + if(.) + LAZYREMOVE(myarea.firealarms, src) + +/obj/machinery/firealarm/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + new /obj/item/stack/sheet/metal(loc, 1) + if(!(machine_stat & BROKEN)) + var/obj/item/I = new /obj/item/electronics/firealarm(loc) + if(!disassembled) + I.obj_integrity = I.max_integrity * 0.5 + new /obj/item/stack/cable_coil(loc, 3) + qdel(src) + +/obj/machinery/firealarm/proc/update_fire_light(fire) + if(fire == !!light_power) + return // do nothing if we're already active + if(fire) + set_light(l_power = 0.8) + else + set_light(l_power = 0) + +/* + * Return of Party button + */ + +/area + var/party = FALSE + +/obj/machinery/firealarm/partyalarm + name = "\improper PARTY BUTTON" + desc = "Cuban Pete is in the house!" + var/static/party_overlay + +/obj/machinery/firealarm/partyalarm/reset() + if (machine_stat & (NOPOWER|BROKEN)) + return + var/area/A = get_area(src) + if (!A || !A.party) + return + A.party = FALSE + A.cut_overlay(party_overlay) + +/obj/machinery/firealarm/partyalarm/alarm() + if (machine_stat & (NOPOWER|BROKEN)) + return + var/area/A = get_area(src) + if (!A || A.party || A.name == "Space") + return + A.party = TRUE + if (!party_overlay) + party_overlay = iconstate2appearance('icons/turf/areas.dmi', "party") + A.add_overlay(party_overlay) diff --git a/code/game/machinery/flasher.dm b/code/game/machinery/flasher.dm index a8196b915736..dcc36d24dbf9 100644 --- a/code/game/machinery/flasher.dm +++ b/code/game/machinery/flasher.dm @@ -1,205 +1,205 @@ -// It is a gizmo that flashes a small area - -/obj/machinery/flasher - name = "mounted flash" - desc = "A wall-mounted flashbulb device." - icon = 'icons/obj/stationobjs.dmi' - icon_state = "mflash1" - max_integrity = 250 - integrity_failure = 0.4 - light_color = LIGHT_COLOR_WHITE - light_power = FLASH_LIGHT_POWER - damage_deflection = 10 - var/obj/item/assembly/flash/handheld/bulb - var/id = null - var/range = 2 //this is roughly the size of brig cell - var/last_flash = 0 //Don't want it getting spammed like regular flashes - var/strength = 100 //How knocked down targets are when flashed. - var/base_state = "mflash" - -/obj/machinery/flasher/portable //Portable version of the flasher. Only flashes when anchored - name = "portable flasher" - desc = "A portable flashing device. Wrench to activate and deactivate. Cannot detect slow movements." - icon_state = "pflash1-p" - strength = 80 - anchored = FALSE - base_state = "pflash" - density = TRUE - -/obj/machinery/flasher/Initialize(mapload, ndir = 0, built = 0) - . = ..() // ..() is EXTREMELY IMPORTANT, never forget to add it - if(built) - setDir(ndir) - pixel_x = (dir & 3)? 0 : (dir == 4 ? -28 : 28) - pixel_y = (dir & 3)? (dir ==1 ? -28 : 28) : 0 - else - bulb = new(src) - -/obj/machinery/flasher/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) - id = "[idnum][id]" - -/obj/machinery/flasher/Destroy() - QDEL_NULL(bulb) - return ..() - -/obj/machinery/flasher/powered() - if(!anchored || !bulb) - return FALSE - return ..() - -/obj/machinery/flasher/update_icon_state() - if (powered()) - if(bulb.burnt_out) - icon_state = "[base_state]1-p" - else - icon_state = "[base_state]1" - else - icon_state = "[base_state]1-p" - -//Don't want to render prison breaks impossible -/obj/machinery/flasher/attackby(obj/item/W, mob/user, params) - add_fingerprint(user) - if (W.tool_behaviour == TOOL_WIRECUTTER) - if (bulb) - user.visible_message("[user] begins to disconnect [src]'s flashbulb.", "You begin to disconnect [src]'s flashbulb...") - if(W.use_tool(src, user, 30, volume=50) && bulb) - user.visible_message("[user] disconnects [src]'s flashbulb!", "You disconnect [src]'s flashbulb.") - bulb.forceMove(loc) - bulb = null - power_change() - - else if (istype(W, /obj/item/assembly/flash/handheld)) - if (!bulb) - if(!user.transferItemToLoc(W, src)) - return - user.visible_message("[user] installs [W] into [src].", "You install [W] into [src].") - bulb = W - power_change() - else - to_chat(user, "A flashbulb is already installed in [src]!") - - else if (W.tool_behaviour == TOOL_WRENCH) - if(!bulb) - to_chat(user, "You start unsecuring the flasher frame...") - if(W.use_tool(src, user, 40, volume=50)) - to_chat(user, "You unsecure the flasher frame.") - deconstruct(TRUE) - else - to_chat(user, "Remove a flashbulb from [src] first!") - else - return ..() - -//Let the AI trigger them directly. -/obj/machinery/flasher/attack_ai() - if (anchored) - return flash() - -/obj/machinery/flasher/proc/flash() - if (!powered() || !bulb) - return - - if (bulb.burnt_out || (last_flash && world.time < src.last_flash + 150)) - return - - if(!bulb.flash_recharge(30)) //Bulb can burn out if it's used too often too fast - power_change() - return - - playsound(src.loc, 'sound/weapons/flash.ogg', 100, TRUE) - flick("[base_state]_flash", src) - flash_lighting_fx(FLASH_LIGHT_RANGE, light_power, light_color) - last_flash = world.time - use_power(1000) - - var/flashed = FALSE - for (var/mob/living/L in viewers(src, null)) - if (get_dist(src, L) > range) - continue - - if(L.flash_act(affect_silicon = 1)) - L.Paralyze(strength) - flashed = TRUE - - if(flashed) - bulb.times_used++ - - return 1 - - -/obj/machinery/flasher/emp_act(severity) - . = ..() - if(!(machine_stat & (BROKEN|NOPOWER)) && !(. & EMP_PROTECT_SELF)) - if(bulb && prob(75/severity)) - flash() - bulb.burn_out() - power_change() - -/obj/machinery/flasher/obj_break(damage_flag) - . = ..() - if(. && bulb) - bulb.burn_out() - power_change() - -/obj/machinery/flasher/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - if(bulb) - bulb.forceMove(loc) - bulb = null - if(disassembled) - var/obj/item/wallframe/flasher/F = new(get_turf(src)) - transfer_fingerprints_to(F) - F.id = id - playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE) - else - new /obj/item/stack/sheet/metal (loc, 2) - qdel(src) - -/obj/machinery/flasher/portable/Initialize() - . = ..() - proximity_monitor = new(src, 0) - -/obj/machinery/flasher/portable/HasProximity(atom/movable/AM) - if (last_flash && world.time < last_flash + 150) - return - - if(istype(AM, /mob/living/carbon)) - var/mob/living/carbon/M = AM - if (M.m_intent != MOVE_INTENT_WALK && anchored) - flash() - -/obj/machinery/flasher/portable/attackby(obj/item/W, mob/user, params) - if (W.tool_behaviour == TOOL_WRENCH) - W.play_tool_sound(src, 100) - - if (!anchored && !isinspace()) - to_chat(user, "[src] is now secured.") - add_overlay("[base_state]-s") - setAnchored(TRUE) - power_change() - proximity_monitor.SetRange(range) - else - to_chat(user, "[src] can now be moved.") - cut_overlays() - setAnchored(FALSE) - power_change() - proximity_monitor.SetRange(0) - - else - return ..() - -/obj/item/wallframe/flasher - name = "mounted flash frame" - desc = "Used for building wall-mounted flashers." - icon = 'icons/obj/stationobjs.dmi' - icon_state = "mflash_frame" - result_path = /obj/machinery/flasher - var/id = null - -/obj/item/wallframe/flasher/examine(mob/user) - . = ..() - . += "Its channel ID is '[id]'." - -/obj/item/wallframe/flasher/after_attach(var/obj/O) - ..() - var/obj/machinery/flasher/F = O - F.id = id +// It is a gizmo that flashes a small area + +/obj/machinery/flasher + name = "mounted flash" + desc = "A wall-mounted flashbulb device." + icon = 'icons/obj/stationobjs.dmi' + icon_state = "mflash1" + max_integrity = 250 + integrity_failure = 0.4 + light_color = LIGHT_COLOR_WHITE + light_power = FLASH_LIGHT_POWER + damage_deflection = 10 + var/obj/item/assembly/flash/handheld/bulb + var/id = null + var/range = 2 //this is roughly the size of brig cell + var/last_flash = 0 //Don't want it getting spammed like regular flashes + var/strength = 100 //How knocked down targets are when flashed. + var/base_state = "mflash" + +/obj/machinery/flasher/portable //Portable version of the flasher. Only flashes when anchored + name = "portable flasher" + desc = "A portable flashing device. Wrench to activate and deactivate. Cannot detect slow movements." + icon_state = "pflash1-p" + strength = 80 + anchored = FALSE + base_state = "pflash" + density = TRUE + +/obj/machinery/flasher/Initialize(mapload, ndir = 0, built = 0) + . = ..() // ..() is EXTREMELY IMPORTANT, never forget to add it + if(built) + setDir(ndir) + pixel_x = (dir & 3)? 0 : (dir == 4 ? -28 : 28) + pixel_y = (dir & 3)? (dir ==1 ? -28 : 28) : 0 + else + bulb = new(src) + +/obj/machinery/flasher/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) + id = "[idnum][id]" + +/obj/machinery/flasher/Destroy() + QDEL_NULL(bulb) + return ..() + +/obj/machinery/flasher/powered() + if(!anchored || !bulb) + return FALSE + return ..() + +/obj/machinery/flasher/update_icon_state() + if (powered()) + if(bulb.burnt_out) + icon_state = "[base_state]1-p" + else + icon_state = "[base_state]1" + else + icon_state = "[base_state]1-p" + +//Don't want to render prison breaks impossible +/obj/machinery/flasher/attackby(obj/item/W, mob/user, params) + add_fingerprint(user) + if (W.tool_behaviour == TOOL_WIRECUTTER) + if (bulb) + user.visible_message("[user] begins to disconnect [src]'s flashbulb.", "You begin to disconnect [src]'s flashbulb...") + if(W.use_tool(src, user, 30, volume=50) && bulb) + user.visible_message("[user] disconnects [src]'s flashbulb!", "You disconnect [src]'s flashbulb.") + bulb.forceMove(loc) + bulb = null + power_change() + + else if (istype(W, /obj/item/assembly/flash/handheld)) + if (!bulb) + if(!user.transferItemToLoc(W, src)) + return + user.visible_message("[user] installs [W] into [src].", "You install [W] into [src].") + bulb = W + power_change() + else + to_chat(user, "A flashbulb is already installed in [src]!") + + else if (W.tool_behaviour == TOOL_WRENCH) + if(!bulb) + to_chat(user, "You start unsecuring the flasher frame...") + if(W.use_tool(src, user, 40, volume=50)) + to_chat(user, "You unsecure the flasher frame.") + deconstruct(TRUE) + else + to_chat(user, "Remove a flashbulb from [src] first!") + else + return ..() + +//Let the AI trigger them directly. +/obj/machinery/flasher/attack_ai() + if (anchored) + return flash() + +/obj/machinery/flasher/proc/flash() + if (!powered() || !bulb) + return + + if (bulb.burnt_out || (last_flash && world.time < src.last_flash + 150)) + return + + if(!bulb.flash_recharge(30)) //Bulb can burn out if it's used too often too fast + power_change() + return + + playsound(src.loc, 'sound/weapons/flash.ogg', 100, TRUE) + flick("[base_state]_flash", src) + flash_lighting_fx(FLASH_LIGHT_RANGE, light_power, light_color) + last_flash = world.time + use_power(1000) + + var/flashed = FALSE + for (var/mob/living/L in viewers(src, null)) + if (get_dist(src, L) > range) + continue + + if(L.flash_act(affect_silicon = 1)) + L.Paralyze(strength) + flashed = TRUE + + if(flashed) + bulb.times_used++ + + return 1 + + +/obj/machinery/flasher/emp_act(severity) + . = ..() + if(!(machine_stat & (BROKEN|NOPOWER)) && !(. & EMP_PROTECT_SELF)) + if(bulb && prob(75/severity)) + flash() + bulb.burn_out() + power_change() + +/obj/machinery/flasher/obj_break(damage_flag) + . = ..() + if(. && bulb) + bulb.burn_out() + power_change() + +/obj/machinery/flasher/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + if(bulb) + bulb.forceMove(loc) + bulb = null + if(disassembled) + var/obj/item/wallframe/flasher/F = new(get_turf(src)) + transfer_fingerprints_to(F) + F.id = id + playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE) + else + new /obj/item/stack/sheet/metal (loc, 2) + qdel(src) + +/obj/machinery/flasher/portable/Initialize() + . = ..() + proximity_monitor = new(src, 0) + +/obj/machinery/flasher/portable/HasProximity(atom/movable/AM) + if (last_flash && world.time < last_flash + 150) + return + + if(istype(AM, /mob/living/carbon)) + var/mob/living/carbon/M = AM + if (M.m_intent != MOVE_INTENT_WALK && anchored) + flash() + +/obj/machinery/flasher/portable/attackby(obj/item/W, mob/user, params) + if (W.tool_behaviour == TOOL_WRENCH) + W.play_tool_sound(src, 100) + + if (!anchored && !isinspace()) + to_chat(user, "[src] is now secured.") + add_overlay("[base_state]-s") + setAnchored(TRUE) + power_change() + proximity_monitor.SetRange(range) + else + to_chat(user, "[src] can now be moved.") + cut_overlays() + setAnchored(FALSE) + power_change() + proximity_monitor.SetRange(0) + + else + return ..() + +/obj/item/wallframe/flasher + name = "mounted flash frame" + desc = "Used for building wall-mounted flashers." + icon = 'icons/obj/stationobjs.dmi' + icon_state = "mflash_frame" + result_path = /obj/machinery/flasher + var/id = null + +/obj/item/wallframe/flasher/examine(mob/user) + . = ..() + . += "Its channel ID is '[id]'." + +/obj/item/wallframe/flasher/after_attach(var/obj/O) + ..() + var/obj/machinery/flasher/F = O + F.id = id diff --git a/code/game/machinery/gulag_item_reclaimer.dm b/code/game/machinery/gulag_item_reclaimer.dm index 98f9a304f727..eae7115e3407 100644 --- a/code/game/machinery/gulag_item_reclaimer.dm +++ b/code/game/machinery/gulag_item_reclaimer.dm @@ -8,8 +8,7 @@ use_power = IDLE_POWER_USE idle_power_usage = 100 active_power_usage = 2500 - ui_x = 325 - ui_y = 400 + var/list/stored_items = list() var/obj/machinery/gulag_teleporter/linked_teleporter = null @@ -27,11 +26,10 @@ req_access = list() obj_flags |= EMAGGED -/obj/machinery/gulag_item_reclaimer/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/machinery/gulag_item_reclaimer/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "GulagItemReclaimer", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, "GulagItemReclaimer", name) ui.open() /obj/machinery/gulag_item_reclaimer/ui_data(mob/user) diff --git a/code/game/machinery/hologram.dm b/code/game/machinery/hologram.dm index 2e24be983025..529b11b8f4aa 100644 --- a/code/game/machinery/hologram.dm +++ b/code/game/machinery/hologram.dm @@ -1,701 +1,698 @@ -/* Holograms! - * Contains: - * Holopad - * Hologram - * Other stuff - */ - -/* -Revised. Original based on space ninja hologram code. Which is also mine. /N -How it works: -AI clicks on holopad in camera view. View centers on holopad. -AI clicks again on the holopad to display a hologram. Hologram stays as long as AI is looking at the pad and it (the hologram) is in range of the pad. -AI can use the directional keys to move the hologram around, provided the above conditions are met and the AI in question is the holopad's master. -Any number of AIs can use a holopad. /Lo6 -AI may cancel the hologram at any time by clicking on the holopad once more. - -Possible to do for anyone motivated enough: - Give an AI variable for different hologram icons. - Itegrate EMP effect to disable the unit. -*/ - - -/* - * Holopad - */ - -#define HOLOPAD_PASSIVE_POWER_USAGE 1 -#define HOLOGRAM_POWER_USAGE 2 - -/obj/machinery/holopad - name = "holopad" - desc = "It's a floor-mounted device for projecting holographic images." - icon_state = "holopad0" - layer = LOW_OBJ_LAYER - plane = FLOOR_PLANE - flags_1 = HEAR_1 - req_access = list(ACCESS_KEYCARD_AUTH) //Used to allow for forced connecting to other (not secure) holopads. Anyone can make a call, though. - use_power = IDLE_POWER_USE - idle_power_usage = 5 - active_power_usage = 100 - max_integrity = 300 - armor = list("melee" = 50, "bullet" = 20, "laser" = 20, "energy" = 20, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 0) - circuit = /obj/item/circuitboard/machine/holopad - ui_x = 440 - ui_y = 245 - /// List of living mobs that use the holopad - var/list/masters - /// Holoray-mob link - var/list/holorays - /// To prevent request spam. ~Carn - var/last_request = 0 - /// Change to change how far the AI can move away from the holopad before deactivating - var/holo_range = 5 - /// Array of /datum/holocalls - var/list/holo_calls - /// Currently outgoing holocall, do not modify the datums only check and call the public procs - var/datum/holocall/outgoing_call - /// Record disk - var/obj/item/disk/holodisk/disk - /// Currently replaying a recording - var/replay_mode = FALSE - /// Currently looping a recording - var/loop_mode = FALSE - /// Currently recording - var/record_mode = FALSE - /// Recording start time - var/record_start = 0 - /// User that inititiated the recording - var/record_user - /// Replay hologram - var/obj/effect/overlay/holo_pad_hologram/replay_holo - /// Calls will be automatically answered after a couple rings, here for debugging - var/static/force_answer_call = FALSE - var/static/list/holopads = list() - var/obj/effect/overlay/holoray/ray - var/ringing = FALSE - var/offset = FALSE - var/on_network = TRUE - /// For pads in secure areas; do not allow forced connecting - var/secure = FALSE - /// If we are currently calling another holopad - var/calling = FALSE - -/obj/machinery/holopad/secure - name = "secure holopad" - desc = "It's a floor-mounted device for projecting holographic images. This one will refuse to auto-connect incoming calls." - secure = TRUE - -obj/machinery/holopad/secure/Initialize() - . = ..() - var/obj/item/circuitboard/machine/holopad/board = circuit - board.secure = TRUE - board.build_path = /obj/machinery/holopad/secure - -/obj/machinery/holopad/tutorial - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - flags_1 = NODECONSTRUCT_1 - on_network = FALSE - var/proximity_range = 1 - -/obj/machinery/holopad/tutorial/Initialize(mapload) - . = ..() - if(proximity_range) - proximity_monitor = new(src, proximity_range) - if(mapload) - var/obj/item/disk/holodisk/new_disk = locate(/obj/item/disk/holodisk) in src.loc - if(new_disk && !disk) - new_disk.forceMove(src) - disk = new_disk - -/obj/machinery/holopad/tutorial/attack_hand(mob/user) - if(!istype(user)) - return - if(user.incapacitated() || !is_operational()) - return - if(replay_mode) - replay_stop() - else if(disk && disk.record) - replay_start() - -/obj/machinery/holopad/tutorial/HasProximity(atom/movable/AM) - if (!isliving(AM)) - return - if(!replay_mode && (disk && disk.record)) - replay_start() - -/obj/machinery/holopad/Initialize() - . = ..() - if(on_network) - holopads += src - -/obj/machinery/holopad/Destroy() - if(outgoing_call) - outgoing_call.ConnectionFailure(src) - - for(var/I in holo_calls) - var/datum/holocall/HC = I - HC.ConnectionFailure(src) - - for (var/I in masters) - clear_holo(I) - - if(replay_mode) - replay_stop() - if(record_mode) - record_stop() - - QDEL_NULL(disk) - - holopads -= src - return ..() - -/obj/machinery/holopad/power_change() - . = ..() - if (!powered()) - if(replay_mode) - replay_stop() - if(record_mode) - record_stop() - if(outgoing_call) - outgoing_call.ConnectionFailure(src) - -/obj/machinery/holopad/obj_break() - . = ..() - if(outgoing_call) - outgoing_call.ConnectionFailure(src) - -/obj/machinery/holopad/RefreshParts() - var/holograph_range = 4 - for(var/obj/item/stock_parts/capacitor/B in component_parts) - holograph_range += 1 * B.rating - holo_range = holograph_range - -/obj/machinery/holopad/examine(mob/user) - . = ..() - if(in_range(user, src) || isobserver(user)) - . += "The status display reads: Current projection range: [holo_range] units." - -/obj/machinery/holopad/attackby(obj/item/P, mob/user, params) - if(default_deconstruction_screwdriver(user, "holopad_open", "holopad0", P)) - return - - if(default_pry_open(P)) - return - - if(default_unfasten_wrench(user, P)) - return - - if(default_deconstruction_crowbar(P)) - return - - if(istype(P,/obj/item/disk/holodisk)) - if(disk) - to_chat(user,"There's already a disk inside [src]!") - return - if (!user.transferItemToLoc(P,src)) - return - to_chat(user,"You insert [P] into [src].") - disk = P - return - - return ..() - -/obj/machinery/holopad/ui_status(mob/user) - if(!is_operational()) - return UI_CLOSE - if(outgoing_call && !calling) - return UI_CLOSE - return ..() - -/obj/machinery/holopad/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "Holopad", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/machinery/holopad/ui_data(mob/user) - var/list/data = list() - data["calling"] = calling - data["on_network"] = on_network - data["on_cooldown"] = last_request + 200 < world.time ? FALSE : TRUE - data["allowed"] = allowed(user) - data["disk"] = disk ? TRUE : FALSE - data["disk_record"] = disk?.record ? TRUE : FALSE - data["replay_mode"] = replay_mode - data["loop_mode"] = loop_mode - data["record_mode"] = record_mode - data["holo_calls"] = list() - for(var/I in holo_calls) - var/datum/holocall/HC = I - var/list/call_data = list( - caller = HC.user, - connected = HC.connected_holopad == src ? TRUE : FALSE, - ref = REF(HC) - ) - data["holo_calls"] += list(call_data) - return data - -/obj/machinery/holopad/ui_act(action, list/params) - . = ..() - if(.) - return - - switch(action) - if("AIrequest") - if(last_request + 200 < world.time) - last_request = world.time - to_chat(usr, "You requested an AI's presence.") - var/area/area = get_area(src) - for(var/mob/living/silicon/ai/AI in GLOB.silicon_mobs) - if(!AI.client) - continue - to_chat(AI, "Your presence is requested at \the [area].") - return TRUE - else - to_chat(usr, "A request for AI presence was already sent recently.") - return - if("holocall") - if(outgoing_call) - return - if(usr.loc == loc) - var/list/callnames = list() - for(var/I in holopads) - var/area/A = get_area(I) - if(A) - LAZYADD(callnames[A], I) - callnames -= get_area(src) - var/result = input(usr, "Choose an area to call", "Holocall") as null|anything in sortNames(callnames) - if(QDELETED(usr) || !result || outgoing_call) - return - if(usr.loc == loc) - var/input = text2num(params["headcall"]) - var/headcall = input == 1 ? TRUE : FALSE - new /datum/holocall(usr, src, callnames[result], headcall) - calling = TRUE - return TRUE - else - to_chat(usr, "You must stand on the holopad to make a call!") - if("connectcall") - var/datum/holocall/call_to_connect = locate(params["holopad"]) in holo_calls - if(!QDELETED(call_to_connect)) - call_to_connect.Answer(src) - return TRUE - if("disconnectcall") - var/datum/holocall/call_to_disconnect = locate(params["holopad"]) in holo_calls - if(!QDELETED(call_to_disconnect)) - call_to_disconnect.Disconnect(src) - return TRUE - if("disk_eject") - if(disk && !replay_mode) - disk.forceMove(drop_location()) - disk = null - return TRUE - if("replay_mode") - if(replay_mode) - replay_stop() - return TRUE - else - replay_start() - return TRUE - if("loop_mode") - loop_mode = !loop_mode - return TRUE - if("record_mode") - if(record_mode) - record_stop() - return TRUE - else - record_start(usr) - return TRUE - if("record_clear") - record_clear() - return TRUE - if("offset") - offset++ - if(offset > 4) - offset = FALSE - var/turf/new_turf - if(!offset) - new_turf = get_turf(src) - else - new_turf = get_step(src, GLOB.cardinals[offset]) - replay_holo.forceMove(new_turf) - return TRUE - if("hang_up") - if(outgoing_call) - outgoing_call.Disconnect(src) - return TRUE - -/** - * hangup_all_calls: Disconnects all current holocalls from the holopad - */ -/obj/machinery/holopad/proc/hangup_all_calls() - for(var/I in holo_calls) - var/datum/holocall/HC = I - HC.Disconnect(src) - -//do not allow AIs to answer calls or people will use it to meta the AI sattelite -/obj/machinery/holopad/attack_ai(mob/living/silicon/ai/user) - if (!istype(user)) - return - if (!on_network) - return - /*There are pretty much only three ways to interact here. - I don't need to check for client since they're clicking on an object. - This may change in the future but for now will suffice.*/ - if(user.eyeobj.loc != src.loc)//Set client eye on the object if it's not already. - user.eyeobj.setLoc(get_turf(src)) - else if(!LAZYLEN(masters) || !masters[user])//If there is no hologram, possibly make one. - activate_holo(user) - else//If there is a hologram, remove it. - clear_holo(user) - -/obj/machinery/holopad/process() - if(LAZYLEN(masters)) - for(var/I in masters) - var/mob/living/master = I - var/mob/living/silicon/ai/AI = master - if(!istype(AI)) - AI = null - - if(!is_operational() || !validate_user(master)) - clear_holo(master) - - if(outgoing_call) - outgoing_call.Check() - - ringing = FALSE - - for(var/I in holo_calls) - var/datum/holocall/HC = I - if(HC.connected_holopad != src) - if(force_answer_call && world.time > (HC.call_start_time + (HOLOPAD_MAX_DIAL_TIME / 2))) - HC.Answer(src) - break - if(HC.head_call && !secure) - HC.Answer(src) - break - if(outgoing_call) - HC.Disconnect(src)//can't answer calls while calling - else - playsound(src, 'sound/machines/twobeep.ogg', 100) //bring, bring! - ringing = TRUE - - update_icon() - -/obj/machinery/holopad/proc/activate_holo(mob/living/user) - var/mob/living/silicon/ai/AI = user - if(!istype(AI)) - AI = null - - if(is_operational() && (!AI || AI.eyeobj.loc == loc))//If the projector has power and client eye is on it - if (AI && istype(AI.current, /obj/machinery/holopad)) - to_chat(user, "ERROR: \black Image feed in progress.") - return - - var/obj/effect/overlay/holo_pad_hologram/Hologram = new(loc)//Spawn a blank effect at the location. - if(AI) - Hologram.icon = AI.holo_icon - else //make it like real life - Hologram.icon = user.icon - Hologram.icon_state = user.icon_state - Hologram.copy_overlays(user, TRUE) - //codersprite some holo effects here - Hologram.alpha = 100 - Hologram.add_atom_colour("#77abff", FIXED_COLOUR_PRIORITY) - Hologram.Impersonation = user - - Hologram.mouse_opacity = MOUSE_OPACITY_TRANSPARENT//So you can't click on it. - Hologram.layer = FLY_LAYER//Above all the other objects/mobs. Or the vast majority of them. - Hologram.setAnchored(TRUE)//So space wind cannot drag it. - Hologram.name = "[user.name] (Hologram)"//If someone decides to right click. - Hologram.set_light(2) //hologram lighting - move_hologram() - - set_holo(user, Hologram) - visible_message("A holographic image of [user] flickers to life before your eyes!") - - return Hologram - else - to_chat(user, "ERROR: Unable to project hologram.") - -/*This is the proc for special two-way communication between AI and holopad/people talking near holopad. -For the other part of the code, check silicon say.dm. Particularly robot talk.*/ -/obj/machinery/holopad/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode) - . = ..() - if(speaker && LAZYLEN(masters) && !radio_freq)//Master is mostly a safety in case lag hits or something. Radio_freq so AIs dont hear holopad stuff through radios. - for(var/mob/living/silicon/ai/master in masters) - if(masters[master] && speaker != master) - master.relay_speech(message, speaker, message_language, raw_message, radio_freq, spans, message_mode) - - for(var/I in holo_calls) - var/datum/holocall/HC = I - if(HC.connected_holopad == src && speaker != HC.hologram) - HC.user.Hear(message, speaker, message_language, raw_message, radio_freq, spans, message_mode) - - if(outgoing_call && speaker == outgoing_call.user) - outgoing_call.hologram.say(raw_message) - - if(record_mode && speaker == record_user) - record_message(speaker,raw_message,message_language) - -/obj/machinery/holopad/proc/SetLightsAndPower() - var/total_users = LAZYLEN(masters) + LAZYLEN(holo_calls) - use_power = total_users > 0 ? ACTIVE_POWER_USE : IDLE_POWER_USE - active_power_usage = HOLOPAD_PASSIVE_POWER_USAGE + (HOLOGRAM_POWER_USAGE * total_users) - if(total_users || replay_mode) - set_light(2) - else - set_light(0) - update_icon() - -/obj/machinery/holopad/update_icon_state() - var/total_users = LAZYLEN(masters) + LAZYLEN(holo_calls) - if(ringing) - icon_state = "holopad_ringing" - else if(total_users || replay_mode) - icon_state = "holopad1" - else - icon_state = "holopad0" - -/obj/machinery/holopad/proc/set_holo(mob/living/user, obj/effect/overlay/holo_pad_hologram/h) - LAZYSET(masters, user, h) - LAZYSET(holorays, user, new /obj/effect/overlay/holoray(loc)) - var/mob/living/silicon/ai/AI = user - if(istype(AI)) - AI.current = src - SetLightsAndPower() - update_holoray(user, get_turf(loc)) - return TRUE - -/obj/machinery/holopad/proc/clear_holo(mob/living/user) - qdel(masters[user]) // Get rid of user's hologram - unset_holo(user) - return TRUE - -/obj/machinery/holopad/proc/unset_holo(mob/living/user) - var/mob/living/silicon/ai/AI = user - if(istype(AI) && AI.current == src) - AI.current = null - LAZYREMOVE(masters, user) // Discard AI from the list of those who use holopad - qdel(holorays[user]) - LAZYREMOVE(holorays, user) - SetLightsAndPower() - return TRUE - -//Try to transfer hologram to another pad that can project on T -/obj/machinery/holopad/proc/transfer_to_nearby_pad(turf/T,mob/holo_owner) - var/obj/effect/overlay/holo_pad_hologram/h = masters[holo_owner] - if(!h || h.HC) //Holocalls can't change source. - return FALSE - for(var/pad in holopads) - var/obj/machinery/holopad/another = pad - if(another == src) - continue - if(another.validate_location(T)) - unset_holo(holo_owner) - if(another.masters && another.masters[holo_owner]) - another.clear_holo(holo_owner) - another.set_holo(holo_owner, h) - return TRUE - return FALSE - -/obj/machinery/holopad/proc/validate_user(mob/living/user) - if(QDELETED(user) || user.incapacitated() || !user.client) - return FALSE - return TRUE - -//Can we display holos there -//Area check instead of line of sight check because this is a called a lot if AI wants to move around. -/obj/machinery/holopad/proc/validate_location(turf/T,check_los = FALSE) - if(T.z == z && get_dist(T, src) <= holo_range && T.loc == get_area(src)) - return TRUE - else - return FALSE - -/obj/machinery/holopad/proc/move_hologram(mob/living/user, turf/new_turf) - if(LAZYLEN(masters) && masters[user]) - var/obj/effect/overlay/holo_pad_hologram/holo = masters[user] - var/transfered = FALSE - if(!validate_location(new_turf)) - if(!transfer_to_nearby_pad(new_turf,user)) - clear_holo(user) - return FALSE - else - transfered = TRUE - //All is good. - holo.forceMove(new_turf) - if(!transfered) - update_holoray(user,new_turf) - return TRUE - - -/obj/machinery/holopad/proc/update_holoray(mob/living/user, turf/new_turf) - var/obj/effect/overlay/holo_pad_hologram/holo = masters[user] - var/obj/effect/overlay/holoray/ray = holorays[user] - var/disty = holo.y - ray.y - var/distx = holo.x - ray.x - var/newangle - if(!disty) - if(distx >= 0) - newangle = 90 - else - newangle = 270 - else - newangle = arctan(distx/disty) - if(disty < 0) - newangle += 180 - else if(distx < 0) - newangle += 360 - var/matrix/M = matrix() - if (get_dist(get_turf(holo),new_turf) <= 1) - animate(ray, transform = turn(M.Scale(1,sqrt(distx*distx+disty*disty)),newangle),time = 1) - else - ray.transform = turn(M.Scale(1,sqrt(distx*distx+disty*disty)),newangle) - -// RECORDED MESSAGES - -/obj/machinery/holopad/proc/setup_replay_holo(datum/holorecord/record) - var/obj/effect/overlay/holo_pad_hologram/Hologram = new(loc)//Spawn a blank effect at the location. - Hologram.add_overlay(record.caller_image) - Hologram.alpha = 170 - Hologram.add_atom_colour("#77abff", FIXED_COLOUR_PRIORITY) - Hologram.dir = SOUTH //for now - var/datum/language_holder/holder = Hologram.get_language_holder() - holder.selected_language = record.language - Hologram.mouse_opacity = MOUSE_OPACITY_TRANSPARENT//So you can't click on it. - Hologram.layer = FLY_LAYER//Above all the other objects/mobs. Or the vast majority of them. - Hologram.setAnchored(TRUE)//So space wind cannot drag it. - Hologram.name = "[record.caller_name] (Hologram)"//If someone decides to right click. - Hologram.set_light(2) //hologram lighting - visible_message("A holographic image of [record.caller_name] flickers to life before your eyes!") - return Hologram - -/obj/machinery/holopad/proc/replay_start() - if(!replay_mode) - replay_mode = TRUE - replay_holo = setup_replay_holo(disk.record) - SetLightsAndPower() - replay_entry(1) - -/obj/machinery/holopad/proc/replay_stop() - if(replay_mode) - replay_mode = FALSE - offset = FALSE - QDEL_NULL(replay_holo) - SetLightsAndPower() - -/obj/machinery/holopad/proc/record_start(mob/living/user) - if(!user || !disk || disk.record) - return - disk.record = new - record_mode = TRUE - record_start = world.time - record_user = user - disk.record.set_caller_image(user) - -/obj/machinery/holopad/proc/record_message(mob/living/speaker,message,language) - if(!record_mode) - return - //make this command so you can have multiple languages in single record - if((!disk.record.caller_name || disk.record.caller_name == "Unknown") && istype(speaker)) - disk.record.caller_name = speaker.name - if(!disk.record.language) - disk.record.language = language - else if(language != disk.record.language) - disk.record.entries += list(list(HOLORECORD_LANGUAGE,language)) - - var/current_delay = 0 - for(var/E in disk.record.entries) - var/list/entry = E - if(entry[1] != HOLORECORD_DELAY) - continue - current_delay += entry[2] - - var/time_delta = world.time - record_start - current_delay - - if(time_delta >= 1) - disk.record.entries += list(list(HOLORECORD_DELAY,time_delta)) - disk.record.entries += list(list(HOLORECORD_SAY,message)) - if(disk.record.entries.len >= HOLORECORD_MAX_LENGTH) - record_stop() - -/obj/machinery/holopad/proc/replay_entry(entry_number) - if(!replay_mode) - return - if (!disk.record.entries.len) // check for zero entries such as photographs and no text recordings - return // and pretty much just display them statically untill manually stopped - if(disk.record.entries.len < entry_number) - if(loop_mode) - entry_number = 1 - else - replay_stop() - return - var/list/entry = disk.record.entries[entry_number] - var/command = entry[1] - switch(command) - if(HOLORECORD_SAY) - var/message = entry[2] - if(replay_holo) - replay_holo.say(message) - if(HOLORECORD_SOUND) - playsound(src,entry[2],50,TRUE) - if(HOLORECORD_DELAY) - addtimer(CALLBACK(src,.proc/replay_entry,entry_number+1),entry[2]) - return - if(HOLORECORD_LANGUAGE) - var/datum/language_holder/holder = replay_holo.get_language_holder() - holder.selected_language = entry[2] - if(HOLORECORD_PRESET) - var/preset_type = entry[2] - var/datum/preset_holoimage/H = new preset_type - replay_holo.cut_overlays() - replay_holo.add_overlay(H.build_image()) - if(HOLORECORD_RENAME) - replay_holo.name = entry[2] + " (Hologram)" - .(entry_number+1) - -/obj/machinery/holopad/proc/record_stop() - if(record_mode) - record_mode = FALSE - record_user = null - -/obj/machinery/holopad/proc/record_clear() - if(disk && disk.record) - QDEL_NULL(disk.record) - -/obj/effect/overlay/holo_pad_hologram - initial_language_holder = /datum/language_holder/universal - var/mob/living/Impersonation - var/datum/holocall/HC - -/obj/effect/overlay/holo_pad_hologram/Destroy() - Impersonation = null - if(!QDELETED(HC)) - HC.Disconnect(HC.calling_holopad) - return ..() - -/obj/effect/overlay/holo_pad_hologram/Process_Spacemove(movement_dir = 0) - return TRUE - -/obj/effect/overlay/holo_pad_hologram/examine(mob/user) - if(Impersonation) - return Impersonation.examine(user) - return ..() - -/obj/effect/overlay/holoray - name = "holoray" - icon = 'icons/effects/96x96.dmi' - icon_state = "holoray" - layer = FLY_LAYER - density = FALSE - anchored = TRUE - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - pixel_x = -32 - pixel_y = -32 - alpha = 100 - -#undef HOLOPAD_PASSIVE_POWER_USAGE -#undef HOLOGRAM_POWER_USAGE +/* Holograms! + * Contains: + * Holopad + * Hologram + * Other stuff + */ + +/* +Revised. Original based on space ninja hologram code. Which is also mine. /N +How it works: +AI clicks on holopad in camera view. View centers on holopad. +AI clicks again on the holopad to display a hologram. Hologram stays as long as AI is looking at the pad and it (the hologram) is in range of the pad. +AI can use the directional keys to move the hologram around, provided the above conditions are met and the AI in question is the holopad's master. +Any number of AIs can use a holopad. /Lo6 +AI may cancel the hologram at any time by clicking on the holopad once more. + +Possible to do for anyone motivated enough: + Give an AI variable for different hologram icons. + Itegrate EMP effect to disable the unit. +*/ + + +/* + * Holopad + */ + +#define HOLOPAD_PASSIVE_POWER_USAGE 1 +#define HOLOGRAM_POWER_USAGE 2 + +/obj/machinery/holopad + name = "holopad" + desc = "It's a floor-mounted device for projecting holographic images." + icon_state = "holopad0" + layer = LOW_OBJ_LAYER + plane = FLOOR_PLANE + flags_1 = HEAR_1 + req_access = list(ACCESS_KEYCARD_AUTH) //Used to allow for forced connecting to other (not secure) holopads. Anyone can make a call, though. + use_power = IDLE_POWER_USE + idle_power_usage = 5 + active_power_usage = 100 + max_integrity = 300 + armor = list("melee" = 50, "bullet" = 20, "laser" = 20, "energy" = 20, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 0) + circuit = /obj/item/circuitboard/machine/holopad + /// List of living mobs that use the holopad + var/list/masters + /// Holoray-mob link + var/list/holorays + /// To prevent request spam. ~Carn + var/last_request = 0 + /// Change to change how far the AI can move away from the holopad before deactivating + var/holo_range = 5 + /// Array of /datum/holocalls + var/list/holo_calls + /// Currently outgoing holocall, do not modify the datums only check and call the public procs + var/datum/holocall/outgoing_call + /// Record disk + var/obj/item/disk/holodisk/disk + /// Currently replaying a recording + var/replay_mode = FALSE + /// Currently looping a recording + var/loop_mode = FALSE + /// Currently recording + var/record_mode = FALSE + /// Recording start time + var/record_start = 0 + /// User that inititiated the recording + var/record_user + /// Replay hologram + var/obj/effect/overlay/holo_pad_hologram/replay_holo + /// Calls will be automatically answered after a couple rings, here for debugging + var/static/force_answer_call = FALSE + var/static/list/holopads = list() + var/obj/effect/overlay/holoray/ray + var/ringing = FALSE + var/offset = FALSE + var/on_network = TRUE + /// For pads in secure areas; do not allow forced connecting + var/secure = FALSE + /// If we are currently calling another holopad + var/calling = FALSE + +/obj/machinery/holopad/secure + name = "secure holopad" + desc = "It's a floor-mounted device for projecting holographic images. This one will refuse to auto-connect incoming calls." + secure = TRUE + +obj/machinery/holopad/secure/Initialize() + . = ..() + var/obj/item/circuitboard/machine/holopad/board = circuit + board.secure = TRUE + board.build_path = /obj/machinery/holopad/secure + +/obj/machinery/holopad/tutorial + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + flags_1 = NODECONSTRUCT_1 + on_network = FALSE + var/proximity_range = 1 + +/obj/machinery/holopad/tutorial/Initialize(mapload) + . = ..() + if(proximity_range) + proximity_monitor = new(src, proximity_range) + if(mapload) + var/obj/item/disk/holodisk/new_disk = locate(/obj/item/disk/holodisk) in src.loc + if(new_disk && !disk) + new_disk.forceMove(src) + disk = new_disk + +/obj/machinery/holopad/tutorial/attack_hand(mob/user) + if(!istype(user)) + return + if(user.incapacitated() || !is_operational()) + return + if(replay_mode) + replay_stop() + else if(disk && disk.record) + replay_start() + +/obj/machinery/holopad/tutorial/HasProximity(atom/movable/AM) + if (!isliving(AM)) + return + if(!replay_mode && (disk && disk.record)) + replay_start() + +/obj/machinery/holopad/Initialize() + . = ..() + if(on_network) + holopads += src + +/obj/machinery/holopad/Destroy() + if(outgoing_call) + outgoing_call.ConnectionFailure(src) + + for(var/I in holo_calls) + var/datum/holocall/HC = I + HC.ConnectionFailure(src) + + for (var/I in masters) + clear_holo(I) + + if(replay_mode) + replay_stop() + if(record_mode) + record_stop() + + QDEL_NULL(disk) + + holopads -= src + return ..() + +/obj/machinery/holopad/power_change() + . = ..() + if (!powered()) + if(replay_mode) + replay_stop() + if(record_mode) + record_stop() + if(outgoing_call) + outgoing_call.ConnectionFailure(src) + +/obj/machinery/holopad/obj_break() + . = ..() + if(outgoing_call) + outgoing_call.ConnectionFailure(src) + +/obj/machinery/holopad/RefreshParts() + var/holograph_range = 4 + for(var/obj/item/stock_parts/capacitor/B in component_parts) + holograph_range += 1 * B.rating + holo_range = holograph_range + +/obj/machinery/holopad/examine(mob/user) + . = ..() + if(in_range(user, src) || isobserver(user)) + . += "The status display reads: Current projection range: [holo_range] units." + +/obj/machinery/holopad/attackby(obj/item/P, mob/user, params) + if(default_deconstruction_screwdriver(user, "holopad_open", "holopad0", P)) + return + + if(default_pry_open(P)) + return + + if(default_unfasten_wrench(user, P)) + return + + if(default_deconstruction_crowbar(P)) + return + + if(istype(P,/obj/item/disk/holodisk)) + if(disk) + to_chat(user,"There's already a disk inside [src]!") + return + if (!user.transferItemToLoc(P,src)) + return + to_chat(user,"You insert [P] into [src].") + disk = P + return + + return ..() + +/obj/machinery/holopad/ui_status(mob/user) + if(!is_operational()) + return UI_CLOSE + if(outgoing_call && !calling) + return UI_CLOSE + return ..() + +/obj/machinery/holopad/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "Holopad", name) + ui.open() + +/obj/machinery/holopad/ui_data(mob/user) + var/list/data = list() + data["calling"] = calling + data["on_network"] = on_network + data["on_cooldown"] = last_request + 200 < world.time ? FALSE : TRUE + data["allowed"] = allowed(user) + data["disk"] = disk ? TRUE : FALSE + data["disk_record"] = disk?.record ? TRUE : FALSE + data["replay_mode"] = replay_mode + data["loop_mode"] = loop_mode + data["record_mode"] = record_mode + data["holo_calls"] = list() + for(var/I in holo_calls) + var/datum/holocall/HC = I + var/list/call_data = list( + caller = HC.user, + connected = HC.connected_holopad == src ? TRUE : FALSE, + ref = REF(HC) + ) + data["holo_calls"] += list(call_data) + return data + +/obj/machinery/holopad/ui_act(action, list/params) + . = ..() + if(.) + return + + switch(action) + if("AIrequest") + if(last_request + 200 < world.time) + last_request = world.time + to_chat(usr, "You requested an AI's presence.") + var/area/area = get_area(src) + for(var/mob/living/silicon/ai/AI in GLOB.silicon_mobs) + if(!AI.client) + continue + to_chat(AI, "Your presence is requested at \the [area].") + return TRUE + else + to_chat(usr, "A request for AI presence was already sent recently.") + return + if("holocall") + if(outgoing_call) + return + if(usr.loc == loc) + var/list/callnames = list() + for(var/I in holopads) + var/area/A = get_area(I) + if(A) + LAZYADD(callnames[A], I) + callnames -= get_area(src) + var/result = input(usr, "Choose an area to call", "Holocall") as null|anything in sortNames(callnames) + if(QDELETED(usr) || !result || outgoing_call) + return + if(usr.loc == loc) + var/input = text2num(params["headcall"]) + var/headcall = input == 1 ? TRUE : FALSE + new /datum/holocall(usr, src, callnames[result], headcall) + calling = TRUE + return TRUE + else + to_chat(usr, "You must stand on the holopad to make a call!") + if("connectcall") + var/datum/holocall/call_to_connect = locate(params["holopad"]) in holo_calls + if(!QDELETED(call_to_connect)) + call_to_connect.Answer(src) + return TRUE + if("disconnectcall") + var/datum/holocall/call_to_disconnect = locate(params["holopad"]) in holo_calls + if(!QDELETED(call_to_disconnect)) + call_to_disconnect.Disconnect(src) + return TRUE + if("disk_eject") + if(disk && !replay_mode) + disk.forceMove(drop_location()) + disk = null + return TRUE + if("replay_mode") + if(replay_mode) + replay_stop() + return TRUE + else + replay_start() + return TRUE + if("loop_mode") + loop_mode = !loop_mode + return TRUE + if("record_mode") + if(record_mode) + record_stop() + return TRUE + else + record_start(usr) + return TRUE + if("record_clear") + record_clear() + return TRUE + if("offset") + offset++ + if(offset > 4) + offset = FALSE + var/turf/new_turf + if(!offset) + new_turf = get_turf(src) + else + new_turf = get_step(src, GLOB.cardinals[offset]) + replay_holo.forceMove(new_turf) + return TRUE + if("hang_up") + if(outgoing_call) + outgoing_call.Disconnect(src) + return TRUE + +/** + * hangup_all_calls: Disconnects all current holocalls from the holopad + */ +/obj/machinery/holopad/proc/hangup_all_calls() + for(var/I in holo_calls) + var/datum/holocall/HC = I + HC.Disconnect(src) + +//do not allow AIs to answer calls or people will use it to meta the AI sattelite +/obj/machinery/holopad/attack_ai(mob/living/silicon/ai/user) + if (!istype(user)) + return + if (!on_network) + return + /*There are pretty much only three ways to interact here. + I don't need to check for client since they're clicking on an object. + This may change in the future but for now will suffice.*/ + if(user.eyeobj.loc != src.loc)//Set client eye on the object if it's not already. + user.eyeobj.setLoc(get_turf(src)) + else if(!LAZYLEN(masters) || !masters[user])//If there is no hologram, possibly make one. + activate_holo(user) + else//If there is a hologram, remove it. + clear_holo(user) + +/obj/machinery/holopad/process() + if(LAZYLEN(masters)) + for(var/I in masters) + var/mob/living/master = I + var/mob/living/silicon/ai/AI = master + if(!istype(AI)) + AI = null + + if(!is_operational() || !validate_user(master)) + clear_holo(master) + + if(outgoing_call) + outgoing_call.Check() + + ringing = FALSE + + for(var/I in holo_calls) + var/datum/holocall/HC = I + if(HC.connected_holopad != src) + if(force_answer_call && world.time > (HC.call_start_time + (HOLOPAD_MAX_DIAL_TIME / 2))) + HC.Answer(src) + break + if(HC.head_call && !secure) + HC.Answer(src) + break + if(outgoing_call) + HC.Disconnect(src)//can't answer calls while calling + else + playsound(src, 'sound/machines/twobeep.ogg', 100) //bring, bring! + ringing = TRUE + + update_icon() + +/obj/machinery/holopad/proc/activate_holo(mob/living/user) + var/mob/living/silicon/ai/AI = user + if(!istype(AI)) + AI = null + + if(is_operational() && (!AI || AI.eyeobj.loc == loc))//If the projector has power and client eye is on it + if (AI && istype(AI.current, /obj/machinery/holopad)) + to_chat(user, "ERROR: \black Image feed in progress.") + return + + var/obj/effect/overlay/holo_pad_hologram/Hologram = new(loc)//Spawn a blank effect at the location. + if(AI) + Hologram.icon = AI.holo_icon + else //make it like real life + Hologram.icon = user.icon + Hologram.icon_state = user.icon_state + Hologram.copy_overlays(user, TRUE) + //codersprite some holo effects here + Hologram.alpha = 100 + Hologram.add_atom_colour("#77abff", FIXED_COLOUR_PRIORITY) + Hologram.Impersonation = user + + Hologram.mouse_opacity = MOUSE_OPACITY_TRANSPARENT//So you can't click on it. + Hologram.layer = FLY_LAYER//Above all the other objects/mobs. Or the vast majority of them. + Hologram.setAnchored(TRUE)//So space wind cannot drag it. + Hologram.name = "[user.name] (Hologram)"//If someone decides to right click. + Hologram.set_light(2) //hologram lighting + move_hologram() + + set_holo(user, Hologram) + visible_message("A holographic image of [user] flickers to life before your eyes!") + + return Hologram + else + to_chat(user, "ERROR: Unable to project hologram.") + +/*This is the proc for special two-way communication between AI and holopad/people talking near holopad. +For the other part of the code, check silicon say.dm. Particularly robot talk.*/ +/obj/machinery/holopad/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode) + . = ..() + if(speaker && LAZYLEN(masters) && !radio_freq)//Master is mostly a safety in case lag hits or something. Radio_freq so AIs dont hear holopad stuff through radios. + for(var/mob/living/silicon/ai/master in masters) + if(masters[master] && speaker != master) + master.relay_speech(message, speaker, message_language, raw_message, radio_freq, spans, message_mode) + + for(var/I in holo_calls) + var/datum/holocall/HC = I + if(HC.connected_holopad == src && speaker != HC.hologram) + HC.user.Hear(message, speaker, message_language, raw_message, radio_freq, spans, message_mode) + + if(outgoing_call && speaker == outgoing_call.user) + outgoing_call.hologram.say(raw_message) + + if(record_mode && speaker == record_user) + record_message(speaker,raw_message,message_language) + +/obj/machinery/holopad/proc/SetLightsAndPower() + var/total_users = LAZYLEN(masters) + LAZYLEN(holo_calls) + use_power = total_users > 0 ? ACTIVE_POWER_USE : IDLE_POWER_USE + active_power_usage = HOLOPAD_PASSIVE_POWER_USAGE + (HOLOGRAM_POWER_USAGE * total_users) + if(total_users || replay_mode) + set_light(2) + else + set_light(0) + update_icon() + +/obj/machinery/holopad/update_icon_state() + var/total_users = LAZYLEN(masters) + LAZYLEN(holo_calls) + if(ringing) + icon_state = "holopad_ringing" + else if(total_users || replay_mode) + icon_state = "holopad1" + else + icon_state = "holopad0" + +/obj/machinery/holopad/proc/set_holo(mob/living/user, obj/effect/overlay/holo_pad_hologram/h) + LAZYSET(masters, user, h) + LAZYSET(holorays, user, new /obj/effect/overlay/holoray(loc)) + var/mob/living/silicon/ai/AI = user + if(istype(AI)) + AI.current = src + SetLightsAndPower() + update_holoray(user, get_turf(loc)) + return TRUE + +/obj/machinery/holopad/proc/clear_holo(mob/living/user) + qdel(masters[user]) // Get rid of user's hologram + unset_holo(user) + return TRUE + +/obj/machinery/holopad/proc/unset_holo(mob/living/user) + var/mob/living/silicon/ai/AI = user + if(istype(AI) && AI.current == src) + AI.current = null + LAZYREMOVE(masters, user) // Discard AI from the list of those who use holopad + qdel(holorays[user]) + LAZYREMOVE(holorays, user) + SetLightsAndPower() + return TRUE + +//Try to transfer hologram to another pad that can project on T +/obj/machinery/holopad/proc/transfer_to_nearby_pad(turf/T,mob/holo_owner) + var/obj/effect/overlay/holo_pad_hologram/h = masters[holo_owner] + if(!h || h.HC) //Holocalls can't change source. + return FALSE + for(var/pad in holopads) + var/obj/machinery/holopad/another = pad + if(another == src) + continue + if(another.validate_location(T)) + unset_holo(holo_owner) + if(another.masters && another.masters[holo_owner]) + another.clear_holo(holo_owner) + another.set_holo(holo_owner, h) + return TRUE + return FALSE + +/obj/machinery/holopad/proc/validate_user(mob/living/user) + if(QDELETED(user) || user.incapacitated() || !user.client) + return FALSE + return TRUE + +//Can we display holos there +//Area check instead of line of sight check because this is a called a lot if AI wants to move around. +/obj/machinery/holopad/proc/validate_location(turf/T,check_los = FALSE) + if(T.z == z && get_dist(T, src) <= holo_range && T.loc == get_area(src)) + return TRUE + else + return FALSE + +/obj/machinery/holopad/proc/move_hologram(mob/living/user, turf/new_turf) + if(LAZYLEN(masters) && masters[user]) + var/obj/effect/overlay/holo_pad_hologram/holo = masters[user] + var/transfered = FALSE + if(!validate_location(new_turf)) + if(!transfer_to_nearby_pad(new_turf,user)) + clear_holo(user) + return FALSE + else + transfered = TRUE + //All is good. + holo.forceMove(new_turf) + if(!transfered) + update_holoray(user,new_turf) + return TRUE + + +/obj/machinery/holopad/proc/update_holoray(mob/living/user, turf/new_turf) + var/obj/effect/overlay/holo_pad_hologram/holo = masters[user] + var/obj/effect/overlay/holoray/ray = holorays[user] + var/disty = holo.y - ray.y + var/distx = holo.x - ray.x + var/newangle + if(!disty) + if(distx >= 0) + newangle = 90 + else + newangle = 270 + else + newangle = arctan(distx/disty) + if(disty < 0) + newangle += 180 + else if(distx < 0) + newangle += 360 + var/matrix/M = matrix() + if (get_dist(get_turf(holo),new_turf) <= 1) + animate(ray, transform = turn(M.Scale(1,sqrt(distx*distx+disty*disty)),newangle),time = 1) + else + ray.transform = turn(M.Scale(1,sqrt(distx*distx+disty*disty)),newangle) + +// RECORDED MESSAGES + +/obj/machinery/holopad/proc/setup_replay_holo(datum/holorecord/record) + var/obj/effect/overlay/holo_pad_hologram/Hologram = new(loc)//Spawn a blank effect at the location. + Hologram.add_overlay(record.caller_image) + Hologram.alpha = 170 + Hologram.add_atom_colour("#77abff", FIXED_COLOUR_PRIORITY) + Hologram.dir = SOUTH //for now + var/datum/language_holder/holder = Hologram.get_language_holder() + holder.selected_language = record.language + Hologram.mouse_opacity = MOUSE_OPACITY_TRANSPARENT//So you can't click on it. + Hologram.layer = FLY_LAYER//Above all the other objects/mobs. Or the vast majority of them. + Hologram.setAnchored(TRUE)//So space wind cannot drag it. + Hologram.name = "[record.caller_name] (Hologram)"//If someone decides to right click. + Hologram.set_light(2) //hologram lighting + visible_message("A holographic image of [record.caller_name] flickers to life before your eyes!") + return Hologram + +/obj/machinery/holopad/proc/replay_start() + if(!replay_mode) + replay_mode = TRUE + replay_holo = setup_replay_holo(disk.record) + SetLightsAndPower() + replay_entry(1) + +/obj/machinery/holopad/proc/replay_stop() + if(replay_mode) + replay_mode = FALSE + offset = FALSE + QDEL_NULL(replay_holo) + SetLightsAndPower() + +/obj/machinery/holopad/proc/record_start(mob/living/user) + if(!user || !disk || disk.record) + return + disk.record = new + record_mode = TRUE + record_start = world.time + record_user = user + disk.record.set_caller_image(user) + +/obj/machinery/holopad/proc/record_message(mob/living/speaker,message,language) + if(!record_mode) + return + //make this command so you can have multiple languages in single record + if((!disk.record.caller_name || disk.record.caller_name == "Unknown") && istype(speaker)) + disk.record.caller_name = speaker.name + if(!disk.record.language) + disk.record.language = language + else if(language != disk.record.language) + disk.record.entries += list(list(HOLORECORD_LANGUAGE,language)) + + var/current_delay = 0 + for(var/E in disk.record.entries) + var/list/entry = E + if(entry[1] != HOLORECORD_DELAY) + continue + current_delay += entry[2] + + var/time_delta = world.time - record_start - current_delay + + if(time_delta >= 1) + disk.record.entries += list(list(HOLORECORD_DELAY,time_delta)) + disk.record.entries += list(list(HOLORECORD_SAY,message)) + if(disk.record.entries.len >= HOLORECORD_MAX_LENGTH) + record_stop() + +/obj/machinery/holopad/proc/replay_entry(entry_number) + if(!replay_mode) + return + if (!disk.record.entries.len) // check for zero entries such as photographs and no text recordings + return // and pretty much just display them statically untill manually stopped + if(disk.record.entries.len < entry_number) + if(loop_mode) + entry_number = 1 + else + replay_stop() + return + var/list/entry = disk.record.entries[entry_number] + var/command = entry[1] + switch(command) + if(HOLORECORD_SAY) + var/message = entry[2] + if(replay_holo) + replay_holo.say(message) + if(HOLORECORD_SOUND) + playsound(src,entry[2],50,TRUE) + if(HOLORECORD_DELAY) + addtimer(CALLBACK(src,.proc/replay_entry,entry_number+1),entry[2]) + return + if(HOLORECORD_LANGUAGE) + var/datum/language_holder/holder = replay_holo.get_language_holder() + holder.selected_language = entry[2] + if(HOLORECORD_PRESET) + var/preset_type = entry[2] + var/datum/preset_holoimage/H = new preset_type + replay_holo.cut_overlays() + replay_holo.add_overlay(H.build_image()) + if(HOLORECORD_RENAME) + replay_holo.name = entry[2] + " (Hologram)" + .(entry_number+1) + +/obj/machinery/holopad/proc/record_stop() + if(record_mode) + record_mode = FALSE + record_user = null + +/obj/machinery/holopad/proc/record_clear() + if(disk && disk.record) + QDEL_NULL(disk.record) + +/obj/effect/overlay/holo_pad_hologram + initial_language_holder = /datum/language_holder/universal + var/mob/living/Impersonation + var/datum/holocall/HC + +/obj/effect/overlay/holo_pad_hologram/Destroy() + Impersonation = null + if(!QDELETED(HC)) + HC.Disconnect(HC.calling_holopad) + return ..() + +/obj/effect/overlay/holo_pad_hologram/Process_Spacemove(movement_dir = 0) + return TRUE + +/obj/effect/overlay/holo_pad_hologram/examine(mob/user) + if(Impersonation) + return Impersonation.examine(user) + return ..() + +/obj/effect/overlay/holoray + name = "holoray" + icon = 'icons/effects/96x96.dmi' + icon_state = "holoray" + layer = FLY_LAYER + density = FALSE + anchored = TRUE + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + pixel_x = -32 + pixel_y = -32 + alpha = 100 + +#undef HOLOPAD_PASSIVE_POWER_USAGE +#undef HOLOGRAM_POWER_USAGE diff --git a/code/game/machinery/hypnochair.dm b/code/game/machinery/hypnochair.dm index 670d8f758c37..608ba529eb7b 100644 --- a/code/game/machinery/hypnochair.dm +++ b/code/game/machinery/hypnochair.dm @@ -6,14 +6,12 @@ circuit = /obj/item/circuitboard/machine/hypnochair density = TRUE opacity = 0 - ui_x = 375 - ui_y = 480 + var/mob/living/carbon/victim = null ///Keeps track of the victim to apply effects if it teleports away var/interrogating = FALSE ///Is the device currently interrogating someone? var/start_time = 0 ///Time when the interrogation was started, to calculate effect in case of interruption var/trigger_phrase = "" ///Trigger phrase to implant var/timerid = 0 ///Timer ID for interrogations - var/message_cooldown = 0 ///Cooldown for breakout message /obj/machinery/hypnochair/Initialize() @@ -25,19 +23,19 @@ if(!occupant && default_deconstruction_screwdriver(user, icon_state, icon_state, I)) update_icon() return - if(default_pry_open(I)) return - if(default_deconstruction_crowbar(I)) return - return ..() -/obj/machinery/hypnochair/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.notcontained_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/machinery/hypnochair/ui_state(mob/user) + return GLOB.notcontained_state + +/obj/machinery/hypnochair/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "HypnoChair", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, "HypnoChair", name) ui.open() /obj/machinery/hypnochair/ui_data() diff --git a/code/game/machinery/igniter.dm b/code/game/machinery/igniter.dm index 4d97055d7d56..8121cae2ea21 100644 --- a/code/game/machinery/igniter.dm +++ b/code/game/machinery/igniter.dm @@ -1,138 +1,138 @@ -/obj/machinery/igniter - name = "igniter" - desc = "It's useful for igniting plasma." - icon = 'icons/obj/stationobjs.dmi' - icon_state = "igniter0" - plane = FLOOR_PLANE - use_power = IDLE_POWER_USE - idle_power_usage = 2 - active_power_usage = 4 - max_integrity = 300 - armor = list("melee" = 50, "bullet" = 30, "laser" = 70, "energy" = 50, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 70) - resistance_flags = FIRE_PROOF - var/id = null - var/on = FALSE - -/obj/machinery/igniter/incinerator_toxmix - id = INCINERATOR_TOXMIX_IGNITER - -/obj/machinery/igniter/incinerator_atmos - id = INCINERATOR_ATMOS_IGNITER - -/obj/machinery/igniter/incinerator_syndicatelava - id = INCINERATOR_SYNDICATELAVA_IGNITER - -/obj/machinery/igniter/on - on = TRUE - icon_state = "igniter1" - -/obj/machinery/igniter/attack_hand(mob/user) - . = ..() - if(.) - return - add_fingerprint(user) - - use_power(50) - on = !( on ) - update_icon() - -/obj/machinery/igniter/process() //ugh why is this even in process()? - if (src.on && !(machine_stat & NOPOWER) ) - var/turf/location = src.loc - if (isturf(location)) - location.hotspot_expose(1000,500,1) - return 1 - -/obj/machinery/igniter/Initialize() - . = ..() - icon_state = "igniter[on]" - -/obj/machinery/igniter/update_icon_state() - if(machine_stat & NOPOWER) - icon_state = "igniter0" - else - icon_state = "igniter[on]" - -/obj/machinery/igniter/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) - id = "[idnum][id]" - -// Wall mounted remote-control igniter. - -/obj/machinery/sparker - name = "mounted igniter" - desc = "A wall-mounted ignition device." - icon = 'icons/obj/stationobjs.dmi' - icon_state = "migniter" - resistance_flags = FIRE_PROOF - var/id = null - var/disable = 0 - var/last_spark = 0 - var/datum/effect_system/spark_spread/spark_system - -/obj/machinery/sparker/toxmix - id = INCINERATOR_TOXMIX_IGNITER - -/obj/machinery/sparker/Initialize() - . = ..() - spark_system = new /datum/effect_system/spark_spread - spark_system.set_up(2, 1, src) - spark_system.attach(src) - -/obj/machinery/sparker/Destroy() - QDEL_NULL(spark_system) - return ..() - -/obj/machinery/sparker/update_icon_state() - if(disable) - icon_state = "[initial(icon_state)]-d" - else if(powered()) - icon_state = "[initial(icon_state)]" - else - icon_state = "[initial(icon_state)]-p" - -/obj/machinery/sparker/powered() - if(!disable) - return FALSE - return ..() - -/obj/machinery/sparker/attackby(obj/item/W, mob/user, params) - if (W.tool_behaviour == TOOL_SCREWDRIVER) - add_fingerprint(user) - src.disable = !src.disable - if (src.disable) - user.visible_message("[user] disables \the [src]!", "You disable the connection to \the [src].") - if (!src.disable) - user.visible_message("[user] reconnects \the [src]!", "You fix the connection to \the [src].") - update_icon() - else - return ..() - -/obj/machinery/sparker/attack_ai() - if (anchored) - return src.ignite() - else - return - -/obj/machinery/sparker/proc/ignite() - if (!(powered())) - return - - if ((src.disable) || (src.last_spark && world.time < src.last_spark + 50)) - return - - - flick("[initial(icon_state)]-spark", src) - spark_system.start() - last_spark = world.time - use_power(1000) - var/turf/location = src.loc - if (isturf(location)) - location.hotspot_expose(1000,2500,1) - return 1 - -/obj/machinery/sparker/emp_act(severity) - . = ..() - if (. & EMP_PROTECT_SELF) - return - if(!(machine_stat & (BROKEN|NOPOWER))) - ignite() +/obj/machinery/igniter + name = "igniter" + desc = "It's useful for igniting plasma." + icon = 'icons/obj/stationobjs.dmi' + icon_state = "igniter0" + plane = FLOOR_PLANE + use_power = IDLE_POWER_USE + idle_power_usage = 2 + active_power_usage = 4 + max_integrity = 300 + armor = list("melee" = 50, "bullet" = 30, "laser" = 70, "energy" = 50, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 70) + resistance_flags = FIRE_PROOF + var/id = null + var/on = FALSE + +/obj/machinery/igniter/incinerator_toxmix + id = INCINERATOR_TOXMIX_IGNITER + +/obj/machinery/igniter/incinerator_atmos + id = INCINERATOR_ATMOS_IGNITER + +/obj/machinery/igniter/incinerator_syndicatelava + id = INCINERATOR_SYNDICATELAVA_IGNITER + +/obj/machinery/igniter/on + on = TRUE + icon_state = "igniter1" + +/obj/machinery/igniter/attack_hand(mob/user) + . = ..() + if(.) + return + add_fingerprint(user) + + use_power(50) + on = !( on ) + update_icon() + +/obj/machinery/igniter/process() //ugh why is this even in process()? + if (src.on && !(machine_stat & NOPOWER) ) + var/turf/location = src.loc + if (isturf(location)) + location.hotspot_expose(1000,500,1) + return 1 + +/obj/machinery/igniter/Initialize() + . = ..() + icon_state = "igniter[on]" + +/obj/machinery/igniter/update_icon_state() + if(machine_stat & NOPOWER) + icon_state = "igniter0" + else + icon_state = "igniter[on]" + +/obj/machinery/igniter/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) + id = "[idnum][id]" + +// Wall mounted remote-control igniter. + +/obj/machinery/sparker + name = "mounted igniter" + desc = "A wall-mounted ignition device." + icon = 'icons/obj/stationobjs.dmi' + icon_state = "migniter" + resistance_flags = FIRE_PROOF + var/id = null + var/disable = 0 + var/last_spark = 0 + var/datum/effect_system/spark_spread/spark_system + +/obj/machinery/sparker/toxmix + id = INCINERATOR_TOXMIX_IGNITER + +/obj/machinery/sparker/Initialize() + . = ..() + spark_system = new /datum/effect_system/spark_spread + spark_system.set_up(2, 1, src) + spark_system.attach(src) + +/obj/machinery/sparker/Destroy() + QDEL_NULL(spark_system) + return ..() + +/obj/machinery/sparker/update_icon_state() + if(disable) + icon_state = "[initial(icon_state)]-d" + else if(powered()) + icon_state = "[initial(icon_state)]" + else + icon_state = "[initial(icon_state)]-p" + +/obj/machinery/sparker/powered() + if(!disable) + return FALSE + return ..() + +/obj/machinery/sparker/attackby(obj/item/W, mob/user, params) + if (W.tool_behaviour == TOOL_SCREWDRIVER) + add_fingerprint(user) + src.disable = !src.disable + if (src.disable) + user.visible_message("[user] disables \the [src]!", "You disable the connection to \the [src].") + if (!src.disable) + user.visible_message("[user] reconnects \the [src]!", "You fix the connection to \the [src].") + update_icon() + else + return ..() + +/obj/machinery/sparker/attack_ai() + if (anchored) + return src.ignite() + else + return + +/obj/machinery/sparker/proc/ignite() + if (!(powered())) + return + + if ((src.disable) || (src.last_spark && world.time < src.last_spark + 50)) + return + + + flick("[initial(icon_state)]-spark", src) + spark_system.start() + last_spark = world.time + use_power(1000) + var/turf/location = src.loc + if (isturf(location)) + location.hotspot_expose(1000,2500,1) + return 1 + +/obj/machinery/sparker/emp_act(severity) + . = ..() + if (. & EMP_PROTECT_SELF) + return + if(!(machine_stat & (BROKEN|NOPOWER))) + ignite() diff --git a/code/game/machinery/launch_pad.dm b/code/game/machinery/launch_pad.dm index 23e1b9dcc1c8..5e3fc20ebc6d 100644 --- a/code/game/machinery/launch_pad.dm +++ b/code/game/machinery/launch_pad.dm @@ -321,12 +321,15 @@ ui_interact(user) to_chat(user, "[src] projects a display onto your retina.") -/obj/item/launchpad_remote/ui_interact(mob/user, ui_key = "launchpad_remote", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + +/obj/item/launchpad_remote/ui_state(mob/user) + return GLOB.inventory_state + +/obj/item/launchpad_remote/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "LaunchpadRemote", "Briefcase Launchpad Remote", 300, 240, master_ui, state) //width, height + ui = new(user, src, "LaunchpadRemote") ui.open() - ui.set_autoupdate(TRUE) /obj/item/launchpad_remote/ui_data(mob/user) diff --git a/code/game/machinery/lightswitch.dm b/code/game/machinery/lightswitch.dm index 5841080ad235..72590088a061 100644 --- a/code/game/machinery/lightswitch.dm +++ b/code/game/machinery/lightswitch.dm @@ -1,64 +1,64 @@ -/// The light switch. Can have multiple per area. -/obj/machinery/light_switch - name = "light switch" - icon = 'icons/obj/power.dmi' - icon_state = "light1" - desc = "Make dark." - power_channel = AREA_USAGE_LIGHT - /// Set this to a string, path, or area instance to control that area - /// instead of the switch's location. - var/area/area = null - -/obj/machinery/light_switch/Initialize() - . = ..() - if(istext(area)) - area = text2path(area) - if(ispath(area)) - area = GLOB.areas_by_type[area] - if(!area) - area = get_area(src) - - if(!name) - name = "light switch ([area.name])" - - update_icon() - -/obj/machinery/light_switch/update_icon_state() - SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays) - luminosity = 0 - if(machine_stat & NOPOWER) - icon_state = "light-p" - else - luminosity = 1 - SSvis_overlays.add_vis_overlay(src, icon, "light-glow", EMISSIVE_LAYER, EMISSIVE_PLANE, dir, alpha) - if(area.lightswitch) - icon_state = "light1" - else - icon_state = "light0" - -/obj/machinery/light_switch/examine(mob/user) - . = ..() - . += "It is [area.lightswitch ? "on" : "off"]." - -/obj/machinery/light_switch/interact(mob/user) - . = ..() - - area.lightswitch = !area.lightswitch - area.update_icon() - - for(var/obj/machinery/light_switch/L in area) - L.update_icon() - - area.power_change() - -/obj/machinery/light_switch/power_change() - SHOULD_CALL_PARENT(0) - if(area == get_area(src)) - return ..() - -/obj/machinery/light_switch/emp_act(severity) - . = ..() - if (. & EMP_PROTECT_SELF) - return - if(!(machine_stat & (BROKEN|NOPOWER))) - power_change() +/// The light switch. Can have multiple per area. +/obj/machinery/light_switch + name = "light switch" + icon = 'icons/obj/power.dmi' + icon_state = "light1" + desc = "Make dark." + power_channel = AREA_USAGE_LIGHT + /// Set this to a string, path, or area instance to control that area + /// instead of the switch's location. + var/area/area = null + +/obj/machinery/light_switch/Initialize() + . = ..() + if(istext(area)) + area = text2path(area) + if(ispath(area)) + area = GLOB.areas_by_type[area] + if(!area) + area = get_area(src) + + if(!name) + name = "light switch ([area.name])" + + update_icon() + +/obj/machinery/light_switch/update_icon_state() + SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays) + luminosity = 0 + if(machine_stat & NOPOWER) + icon_state = "light-p" + else + luminosity = 1 + SSvis_overlays.add_vis_overlay(src, icon, "light-glow", EMISSIVE_LAYER, EMISSIVE_PLANE, dir, alpha) + if(area.lightswitch) + icon_state = "light1" + else + icon_state = "light0" + +/obj/machinery/light_switch/examine(mob/user) + . = ..() + . += "It is [area.lightswitch ? "on" : "off"]." + +/obj/machinery/light_switch/interact(mob/user) + . = ..() + + area.lightswitch = !area.lightswitch + area.update_icon() + + for(var/obj/machinery/light_switch/L in area) + L.update_icon() + + area.power_change() + +/obj/machinery/light_switch/power_change() + SHOULD_CALL_PARENT(0) + if(area == get_area(src)) + return ..() + +/obj/machinery/light_switch/emp_act(severity) + . = ..() + if (. & EMP_PROTECT_SELF) + return + if(!(machine_stat & (BROKEN|NOPOWER))) + power_change() diff --git a/code/game/machinery/magnet.dm b/code/game/machinery/magnet.dm index 02c320f2a48a..4c18e747af46 100644 --- a/code/game/machinery/magnet.dm +++ b/code/game/machinery/magnet.dm @@ -1,378 +1,378 @@ -// Magnetic attractor, creates variable magnetic fields and attraction. -// Can also be used to emit electron/proton beams to create a center of magnetism on another tile - -// tl;dr: it's magnets lol -// This was created for firing ranges, but I suppose this could have other applications - Doohl - -/obj/machinery/magnetic_module - icon = 'icons/obj/objects.dmi' - icon_state = "floor_magnet-f" - name = "electromagnetic generator" - desc = "A device that uses station power to create points of magnetic energy." - layer = LOW_OBJ_LAYER - use_power = IDLE_POWER_USE - idle_power_usage = 50 - - var/freq = FREQ_MAGNETS // radio frequency - var/electricity_level = 1 // intensity of the magnetic pull - var/magnetic_field = 1 // the range of magnetic attraction - var/code = 0 // frequency code, they should be different unless you have a group of magnets working together or something - var/turf/center // the center of magnetic attraction - var/on = FALSE - var/magneting = FALSE - - // x, y modifiers to the center turf; (0, 0) is centered on the magnet, whereas (1, -1) is one tile right, one tile down - var/center_x = 0 - var/center_y = 0 - var/max_dist = 20 // absolute value of center_x,y cannot exceed this integer - -/obj/machinery/magnetic_module/Initialize() - ..() - var/turf/T = loc - center = T - SSradio.add_object(src, freq, RADIO_MAGNETS) - - AddElement(/datum/element/undertile, TRAIT_T_RAY_VISIBLE) - - return INITIALIZE_HINT_LATELOAD - -/obj/machinery/magnetic_module/LateInitialize() - magnetic_process() - -/obj/machinery/magnetic_module/Destroy() - SSradio.remove_object(src, freq) - center = null - return ..() - - -// update the icon_state -/obj/machinery/magnetic_module/update_icon_state() - var/state="floor_magnet" - var/onstate="" - - if(!on) - onstate="0" - - icon_state = "[state][onstate]" - -/obj/machinery/magnetic_module/receive_signal(datum/signal/signal) - - var/command = signal.data["command"] - var/modifier = signal.data["modifier"] - var/signal_code = signal.data["code"] - if(command && (signal_code == code)) - - Cmd(command, modifier) - - - -/obj/machinery/magnetic_module/proc/Cmd(command, modifier) - - if(command) - switch(command) - if("set-electriclevel") - if(modifier) - electricity_level = modifier - if("set-magneticfield") - if(modifier) - magnetic_field = modifier - - if("add-elec") - electricity_level++ - if(electricity_level > 12) - electricity_level = 12 - if("sub-elec") - electricity_level-- - if(electricity_level <= 0) - electricity_level = 1 - if("add-mag") - magnetic_field++ - if(magnetic_field > 4) - magnetic_field = 4 - if("sub-mag") - magnetic_field-- - if(magnetic_field <= 0) - magnetic_field = 1 - - if("set-x") - if(modifier) - center_x = modifier - if("set-y") - if(modifier) - center_y = modifier - - if("N") // NORTH - center_y++ - if("S") // SOUTH - center_y-- - if("E") // EAST - center_x++ - if("W") // WEST - center_x-- - if("C") // CENTER - center_x = 0 - center_y = 0 - if("R") // RANDOM - center_x = rand(-max_dist, max_dist) - center_y = rand(-max_dist, max_dist) - - if("set-code") - if(modifier) - code = modifier - if("toggle-power") - on = !on - - if(on) - INVOKE_ASYNC(src, .proc/magnetic_process) - - - -/obj/machinery/magnetic_module/process() - if(machine_stat & NOPOWER) - on = FALSE - - // Sanity checks: - if(electricity_level <= 0) - electricity_level = 1 - if(magnetic_field <= 0) - magnetic_field = 1 - - - // Limitations: - if(abs(center_x) > max_dist) - center_x = max_dist - if(abs(center_y) > max_dist) - center_y = max_dist - if(magnetic_field > 4) - magnetic_field = 4 - if(electricity_level > 12) - electricity_level = 12 - - // Update power usage: - if(on) - use_power = ACTIVE_POWER_USE - active_power_usage = electricity_level*15 - else - use_power = NO_POWER_USE - - update_icon() - - -/obj/machinery/magnetic_module/proc/magnetic_process() // proc that actually does the magneting - if(magneting) - return - while(on) - - magneting = TRUE - center = locate(x+center_x, y+center_y, z) - if(center) - for(var/obj/M in orange(magnetic_field, center)) - if(!M.anchored && (M.flags_1 & CONDUCT_1)) - step_towards(M, center) - - for(var/mob/living/silicon/S in orange(magnetic_field, center)) - if(isAI(S)) - continue - step_towards(S, center) - - use_power(electricity_level * 5) - sleep(13 - electricity_level) - - magneting = FALSE - - - - -/obj/machinery/magnetic_controller - name = "magnetic control console" - icon = 'icons/obj/airlock_machines.dmi' // uses an airlock machine icon, THINK GREEN HELP THE ENVIRONMENT - RECYCLING! - icon_state = "airlock_control_standby" - density = FALSE - use_power = IDLE_POWER_USE - idle_power_usage = 45 - var/frequency = FREQ_MAGNETS - var/code = 0 - var/list/magnets = list() - var/title = "Magnetic Control Console" - var/autolink = 0 // if set to 1, can't probe for other magnets! - - var/pathpos = 1 // position in the path - var/path = "w;e;e;w;s;n;n;s" // text path of the magnet - var/speed = 1 // lowest = 1, highest = 10 - var/list/rpath = list() // real path of the magnet, used in iterator - - var/moving = 0 // 1 if scheduled to loop - var/looping = 0 // 1 if looping - - var/datum/radio_frequency/radio_connection - - -/obj/machinery/magnetic_controller/Initialize() - . = ..() - if(autolink) - for(var/obj/machinery/magnetic_module/M in GLOB.machines) - if(M.freq == frequency && M.code == code) - magnets.Add(M) - - if(path) // check for default path - filter_path() // renders rpath - radio_connection = SSradio.add_object(src, frequency, RADIO_MAGNETS) - -/obj/machinery/magnetic_controller/Destroy() - SSradio.remove_object(src, frequency) - magnets = null - rpath = null - return ..() - -/obj/machinery/magnetic_controller/process() - if(magnets.len == 0 && autolink) - for(var/obj/machinery/magnetic_module/M in GLOB.machines) - if(M.freq == frequency && M.code == code) - magnets.Add(M) - -/obj/machinery/magnetic_controller/ui_interact(mob/user) - . = ..() - var/dat = "Magnetic Control Console

    " - if(!autolink) - dat += {" - Frequency: [frequency]
    - Code: [code]
    - Probe Generators
    - "} - - if(magnets.len >= 1) - - dat += "Magnets confirmed:
    " - var/i = 0 - for(var/obj/machinery/magnetic_module/M in magnets) - i++ - dat += "     < \[[i]\] ([M.on ? "On":"Off"]) | Electricity level: - [M.electricity_level] +; Magnetic field: - [M.magnetic_field] +
    " - - dat += "
    Speed: - [speed] +
    " - dat += "Path: {[path]}
    " - dat += "Moving: [moving ? "Enabled":"Disabled"]" - - - user << browse(dat, "window=magnet;size=400x500") - onclose(user, "magnet") - -/obj/machinery/magnetic_controller/Topic(href, href_list) - if(..()) - return - usr.set_machine(src) - - if(href_list["radio-op"]) - - // Prepare signal beforehand, because this is a radio operation - var/datum/signal/signal = new(list("code" = code)) - - // Apply any necessary commands - switch(href_list["radio-op"]) - if("togglepower") - signal.data["command"] = "toggle-power" - - if("minuselec") - signal.data["command"] = "sub-elec" - if("pluselec") - signal.data["command"] = "add-elec" - - if("minusmag") - signal.data["command"] = "sub-mag" - if("plusmag") - signal.data["command"] = "add-mag" - - - // Broadcast the signal - - radio_connection.post_signal(src, signal, filter = RADIO_MAGNETS) - - updateUsrDialog() - - if(href_list["operation"]) - switch(href_list["operation"]) - if("plusspeed") - speed ++ - if(speed > 10) - speed = 10 - if("minusspeed") - speed -- - if(speed <= 0) - speed = 1 - if("setpath") - var/newpath = stripped_input(usr, "Please define a new path!", "New Path", path, MAX_MESSAGE_LEN) - if(newpath && newpath != "") - moving = 0 // stop moving - path = newpath - pathpos = 1 // reset position - filter_path() // renders rpath - - if("togglemoving") - moving = !moving - if(moving) - INVOKE_ASYNC(src, .proc/MagnetMove) - - - updateUsrDialog() - -/obj/machinery/magnetic_controller/proc/MagnetMove() - if(looping) - return - - while(moving && rpath.len >= 1) - - if(machine_stat & (BROKEN|NOPOWER)) - break - - looping = 1 - - // Prepare the radio signal - var/datum/signal/signal = new(list("code" = code)) - - if(pathpos > rpath.len) // if the position is greater than the length, we just loop through the list! - pathpos = 1 - - var/nextmove = uppertext(rpath[pathpos]) // makes it un-case-sensitive - - if(!(nextmove in list("N","S","E","W","C","R"))) - // N, S, E, W are directional - // C is center - // R is random (in magnetic field's bounds) - qdel(signal) - break // break the loop if the character located is invalid - - signal.data["command"] = nextmove - - - pathpos++ // increase iterator - - // Broadcast the signal - INVOKE_ASYNC(radio_connection, /datum/radio_frequency.proc/post_signal, src, signal, RADIO_MAGNETS) - - if(speed == 10) - sleep(1) - else - sleep(12-speed) - - looping = 0 - - -/obj/machinery/magnetic_controller/proc/filter_path() - // Generates the rpath variable using the path string, think of this as "string2list" - // Doesn't use params2list() because of the akward way it stacks entities - rpath = list() // clear rpath - var/maximum_characters = 50 - var/lentext = length(path) - var/nextchar = "" - var/charcount = 0 - - for(var/i = 1, i <= lentext, i += length(nextchar)) // iterates through all characters in path - nextchar = path[i] // find next character - - if(nextchar in list(";", "&", "*", " ")) // if char is a separator, ignore - continue - - rpath += nextchar // else, add to list - // there doesn't HAVE to be separators but it makes paths syntatically visible - charcount++ - if(charcount >= maximum_characters) - break +// Magnetic attractor, creates variable magnetic fields and attraction. +// Can also be used to emit electron/proton beams to create a center of magnetism on another tile + +// tl;dr: it's magnets lol +// This was created for firing ranges, but I suppose this could have other applications - Doohl + +/obj/machinery/magnetic_module + icon = 'icons/obj/objects.dmi' + icon_state = "floor_magnet-f" + name = "electromagnetic generator" + desc = "A device that uses station power to create points of magnetic energy." + layer = LOW_OBJ_LAYER + use_power = IDLE_POWER_USE + idle_power_usage = 50 + + var/freq = FREQ_MAGNETS // radio frequency + var/electricity_level = 1 // intensity of the magnetic pull + var/magnetic_field = 1 // the range of magnetic attraction + var/code = 0 // frequency code, they should be different unless you have a group of magnets working together or something + var/turf/center // the center of magnetic attraction + var/on = FALSE + var/magneting = FALSE + + // x, y modifiers to the center turf; (0, 0) is centered on the magnet, whereas (1, -1) is one tile right, one tile down + var/center_x = 0 + var/center_y = 0 + var/max_dist = 20 // absolute value of center_x,y cannot exceed this integer + +/obj/machinery/magnetic_module/Initialize() + ..() + var/turf/T = loc + center = T + SSradio.add_object(src, freq, RADIO_MAGNETS) + + AddElement(/datum/element/undertile, TRAIT_T_RAY_VISIBLE) + + return INITIALIZE_HINT_LATELOAD + +/obj/machinery/magnetic_module/LateInitialize() + magnetic_process() + +/obj/machinery/magnetic_module/Destroy() + SSradio.remove_object(src, freq) + center = null + return ..() + + +// update the icon_state +/obj/machinery/magnetic_module/update_icon_state() + var/state="floor_magnet" + var/onstate="" + + if(!on) + onstate="0" + + icon_state = "[state][onstate]" + +/obj/machinery/magnetic_module/receive_signal(datum/signal/signal) + + var/command = signal.data["command"] + var/modifier = signal.data["modifier"] + var/signal_code = signal.data["code"] + if(command && (signal_code == code)) + + Cmd(command, modifier) + + + +/obj/machinery/magnetic_module/proc/Cmd(command, modifier) + + if(command) + switch(command) + if("set-electriclevel") + if(modifier) + electricity_level = modifier + if("set-magneticfield") + if(modifier) + magnetic_field = modifier + + if("add-elec") + electricity_level++ + if(electricity_level > 12) + electricity_level = 12 + if("sub-elec") + electricity_level-- + if(electricity_level <= 0) + electricity_level = 1 + if("add-mag") + magnetic_field++ + if(magnetic_field > 4) + magnetic_field = 4 + if("sub-mag") + magnetic_field-- + if(magnetic_field <= 0) + magnetic_field = 1 + + if("set-x") + if(modifier) + center_x = modifier + if("set-y") + if(modifier) + center_y = modifier + + if("N") // NORTH + center_y++ + if("S") // SOUTH + center_y-- + if("E") // EAST + center_x++ + if("W") // WEST + center_x-- + if("C") // CENTER + center_x = 0 + center_y = 0 + if("R") // RANDOM + center_x = rand(-max_dist, max_dist) + center_y = rand(-max_dist, max_dist) + + if("set-code") + if(modifier) + code = modifier + if("toggle-power") + on = !on + + if(on) + INVOKE_ASYNC(src, .proc/magnetic_process) + + + +/obj/machinery/magnetic_module/process() + if(machine_stat & NOPOWER) + on = FALSE + + // Sanity checks: + if(electricity_level <= 0) + electricity_level = 1 + if(magnetic_field <= 0) + magnetic_field = 1 + + + // Limitations: + if(abs(center_x) > max_dist) + center_x = max_dist + if(abs(center_y) > max_dist) + center_y = max_dist + if(magnetic_field > 4) + magnetic_field = 4 + if(electricity_level > 12) + electricity_level = 12 + + // Update power usage: + if(on) + use_power = ACTIVE_POWER_USE + active_power_usage = electricity_level*15 + else + use_power = NO_POWER_USE + + update_icon() + + +/obj/machinery/magnetic_module/proc/magnetic_process() // proc that actually does the magneting + if(magneting) + return + while(on) + + magneting = TRUE + center = locate(x+center_x, y+center_y, z) + if(center) + for(var/obj/M in orange(magnetic_field, center)) + if(!M.anchored && (M.flags_1 & CONDUCT_1)) + step_towards(M, center) + + for(var/mob/living/silicon/S in orange(magnetic_field, center)) + if(isAI(S)) + continue + step_towards(S, center) + + use_power(electricity_level * 5) + sleep(13 - electricity_level) + + magneting = FALSE + + + + +/obj/machinery/magnetic_controller + name = "magnetic control console" + icon = 'icons/obj/airlock_machines.dmi' // uses an airlock machine icon, THINK GREEN HELP THE ENVIRONMENT - RECYCLING! + icon_state = "airlock_control_standby" + density = FALSE + use_power = IDLE_POWER_USE + idle_power_usage = 45 + var/frequency = FREQ_MAGNETS + var/code = 0 + var/list/magnets = list() + var/title = "Magnetic Control Console" + var/autolink = 0 // if set to 1, can't probe for other magnets! + + var/pathpos = 1 // position in the path + var/path = "w;e;e;w;s;n;n;s" // text path of the magnet + var/speed = 1 // lowest = 1, highest = 10 + var/list/rpath = list() // real path of the magnet, used in iterator + + var/moving = 0 // 1 if scheduled to loop + var/looping = 0 // 1 if looping + + var/datum/radio_frequency/radio_connection + + +/obj/machinery/magnetic_controller/Initialize() + . = ..() + if(autolink) + for(var/obj/machinery/magnetic_module/M in GLOB.machines) + if(M.freq == frequency && M.code == code) + magnets.Add(M) + + if(path) // check for default path + filter_path() // renders rpath + radio_connection = SSradio.add_object(src, frequency, RADIO_MAGNETS) + +/obj/machinery/magnetic_controller/Destroy() + SSradio.remove_object(src, frequency) + magnets = null + rpath = null + return ..() + +/obj/machinery/magnetic_controller/process() + if(magnets.len == 0 && autolink) + for(var/obj/machinery/magnetic_module/M in GLOB.machines) + if(M.freq == frequency && M.code == code) + magnets.Add(M) + +/obj/machinery/magnetic_controller/ui_interact(mob/user) + . = ..() + var/dat = "Magnetic Control Console

    " + if(!autolink) + dat += {" + Frequency: [frequency]
    + Code: [code]
    + Probe Generators
    + "} + + if(magnets.len >= 1) + + dat += "Magnets confirmed:
    " + var/i = 0 + for(var/obj/machinery/magnetic_module/M in magnets) + i++ + dat += "     < \[[i]\] ([M.on ? "On":"Off"]) | Electricity level: - [M.electricity_level] +; Magnetic field: - [M.magnetic_field] +
    " + + dat += "
    Speed: - [speed] +
    " + dat += "Path: {[path]}
    " + dat += "Moving: [moving ? "Enabled":"Disabled"]" + + + user << browse(dat, "window=magnet;size=400x500") + onclose(user, "magnet") + +/obj/machinery/magnetic_controller/Topic(href, href_list) + if(..()) + return + usr.set_machine(src) + + if(href_list["radio-op"]) + + // Prepare signal beforehand, because this is a radio operation + var/datum/signal/signal = new(list("code" = code)) + + // Apply any necessary commands + switch(href_list["radio-op"]) + if("togglepower") + signal.data["command"] = "toggle-power" + + if("minuselec") + signal.data["command"] = "sub-elec" + if("pluselec") + signal.data["command"] = "add-elec" + + if("minusmag") + signal.data["command"] = "sub-mag" + if("plusmag") + signal.data["command"] = "add-mag" + + + // Broadcast the signal + + radio_connection.post_signal(src, signal, filter = RADIO_MAGNETS) + + updateUsrDialog() + + if(href_list["operation"]) + switch(href_list["operation"]) + if("plusspeed") + speed ++ + if(speed > 10) + speed = 10 + if("minusspeed") + speed -- + if(speed <= 0) + speed = 1 + if("setpath") + var/newpath = stripped_input(usr, "Please define a new path!", "New Path", path, MAX_MESSAGE_LEN) + if(newpath && newpath != "") + moving = 0 // stop moving + path = newpath + pathpos = 1 // reset position + filter_path() // renders rpath + + if("togglemoving") + moving = !moving + if(moving) + INVOKE_ASYNC(src, .proc/MagnetMove) + + + updateUsrDialog() + +/obj/machinery/magnetic_controller/proc/MagnetMove() + if(looping) + return + + while(moving && rpath.len >= 1) + + if(machine_stat & (BROKEN|NOPOWER)) + break + + looping = 1 + + // Prepare the radio signal + var/datum/signal/signal = new(list("code" = code)) + + if(pathpos > rpath.len) // if the position is greater than the length, we just loop through the list! + pathpos = 1 + + var/nextmove = uppertext(rpath[pathpos]) // makes it un-case-sensitive + + if(!(nextmove in list("N","S","E","W","C","R"))) + // N, S, E, W are directional + // C is center + // R is random (in magnetic field's bounds) + qdel(signal) + break // break the loop if the character located is invalid + + signal.data["command"] = nextmove + + + pathpos++ // increase iterator + + // Broadcast the signal + INVOKE_ASYNC(radio_connection, /datum/radio_frequency.proc/post_signal, src, signal, RADIO_MAGNETS) + + if(speed == 10) + sleep(1) + else + sleep(12-speed) + + looping = 0 + + +/obj/machinery/magnetic_controller/proc/filter_path() + // Generates the rpath variable using the path string, think of this as "string2list" + // Doesn't use params2list() because of the akward way it stacks entities + rpath = list() // clear rpath + var/maximum_characters = 50 + var/lentext = length(path) + var/nextchar = "" + var/charcount = 0 + + for(var/i = 1, i <= lentext, i += length(nextchar)) // iterates through all characters in path + nextchar = path[i] // find next character + + if(nextchar in list(";", "&", "*", " ")) // if char is a separator, ignore + continue + + rpath += nextchar // else, add to list + // there doesn't HAVE to be separators but it makes paths syntatically visible + charcount++ + if(charcount >= maximum_characters) + break diff --git a/code/game/machinery/mass_driver.dm b/code/game/machinery/mass_driver.dm index 5d300f992dc2..06e081698c17 100644 --- a/code/game/machinery/mass_driver.dm +++ b/code/game/machinery/mass_driver.dm @@ -1,42 +1,42 @@ -/obj/machinery/mass_driver - name = "mass driver" - desc = "The finest in spring-loaded piston toy technology, now on a space station near you." - icon = 'icons/obj/stationobjs.dmi' - icon_state = "mass_driver" - use_power = IDLE_POWER_USE - idle_power_usage = 2 - active_power_usage = 50 - var/power = 1 - var/code = 1 - 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/proc/drive(amount) - if(machine_stat & (BROKEN|NOPOWER)) - return - use_power(500) - var/O_limit - var/atom/target = get_edge_target_turf(src, dir) - for(var/atom/movable/O in loc) - if(!O.anchored || ismecha(O)) //Mechs need their launch platforms. - if(ismob(O) && !isliving(O)) - continue - O_limit++ - if(O_limit >= 20) - audible_message("[src] lets out a screech, it doesn't seem to be able to handle the load.") - break - use_power(500) - O.throw_at(target, drive_range * power, power) - flick("mass_driver1", src) - - -/obj/machinery/mass_driver/emp_act(severity) - . = ..() - if (. & EMP_PROTECT_SELF) - return - if(machine_stat & (BROKEN|NOPOWER)) - return - drive() +/obj/machinery/mass_driver + name = "mass driver" + desc = "The finest in spring-loaded piston toy technology, now on a space station near you." + icon = 'icons/obj/stationobjs.dmi' + icon_state = "mass_driver" + use_power = IDLE_POWER_USE + idle_power_usage = 2 + active_power_usage = 50 + var/power = 1 + var/code = 1 + 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/proc/drive(amount) + if(machine_stat & (BROKEN|NOPOWER)) + return + use_power(500) + var/O_limit + var/atom/target = get_edge_target_turf(src, dir) + for(var/atom/movable/O in loc) + if(!O.anchored || ismecha(O)) //Mechs need their launch platforms. + if(ismob(O) && !isliving(O)) + continue + O_limit++ + if(O_limit >= 20) + audible_message("[src] lets out a screech, it doesn't seem to be able to handle the load.") + break + use_power(500) + O.throw_at(target, drive_range * power, power) + flick("mass_driver1", src) + + +/obj/machinery/mass_driver/emp_act(severity) + . = ..() + if (. & EMP_PROTECT_SELF) + return + if(machine_stat & (BROKEN|NOPOWER)) + return + drive() diff --git a/code/game/machinery/medical_kiosk.dm b/code/game/machinery/medical_kiosk.dm index f02e21d73ec1..104c89db9166 100644 --- a/code/game/machinery/medical_kiosk.dm +++ b/code/game/machinery/medical_kiosk.dm @@ -153,7 +153,7 @@ else . += "\The [src] has its scanner clipped to the side. Alt-Click to remove." -/obj/machinery/medical_kiosk/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) +/obj/machinery/medical_kiosk/ui_interact(mob/user, datum/tgui/ui) var/patient_distance = 0 if(!ishuman(user)) to_chat(user, "[src] is unable to interface with non-humanoids!") @@ -169,10 +169,9 @@ say("Patient out of range. Resetting biometrics.") clearScans() return - - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "MedicalKiosk", name, 575, 420, master_ui, state) + ui = new(user, src, "MedicalKiosk", name) ui.open() icon_state = "kiosk_off" RefreshParts() diff --git a/code/game/machinery/navbeacon.dm b/code/game/machinery/navbeacon.dm index 64ea63081cfb..a6ac127b3372 100644 --- a/code/game/machinery/navbeacon.dm +++ b/code/game/machinery/navbeacon.dm @@ -1,226 +1,226 @@ -// Navigation beacon for AI robots -// No longer exists on the radio controller, it is managed by a global list. - -/obj/machinery/navbeacon - - icon = 'icons/obj/objects.dmi' - icon_state = "navbeacon0-f" - name = "navigation beacon" - desc = "A radio beacon used for bot navigation and crew wayfinding." - layer = LOW_OBJ_LAYER - max_integrity = 500 - armor = list("melee" = 70, "bullet" = 70, "laser" = 70, "energy" = 70, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80) - - var/open = FALSE // true if cover is open - var/locked = TRUE // true if controls are locked - var/freq = FREQ_NAV_BEACON - var/location = "" // location response text - var/list/codes // assoc. list of transponder codes - var/codes_txt = "" // codes as set on map: "tag1;tag2" or "tag1=value;tag2=value" - var/wayfinding = FALSE - - req_one_access = list(ACCESS_ENGINE, ACCESS_ROBOTICS) - -/obj/machinery/navbeacon/Initialize() - . = ..() - - if(wayfinding) - if(!location) - var/obj/machinery/door/airlock/A = locate(/obj/machinery/door/airlock) in loc - if(A) - location = A.name - else - location = get_area(src) - codes_txt += "wayfinding=[location]" - - set_codes() - - glob_lists_register(init=TRUE) - - AddElement(/datum/element/undertile, TRAIT_T_RAY_VISIBLE) - -/obj/machinery/navbeacon/Destroy() - glob_lists_deregister() - return ..() - -/obj/machinery/navbeacon/onTransitZ(old_z, new_z) - if (GLOB.navbeacons["[old_z]"]) - GLOB.navbeacons["[old_z]"] -= src - if (GLOB.navbeacons["[new_z]"]) - GLOB.navbeacons["[new_z]"] += src - ..() - -// set the transponder codes assoc list from codes_txt -/obj/machinery/navbeacon/proc/set_codes() - if(!codes_txt) - return - - codes = new() - - var/list/entries = splittext(codes_txt, ";") // entries are separated by semicolons - - for(var/e in entries) - var/index = findtext(e, "=") // format is "key=value" - if(index) - var/key = copytext(e, 1, index) - var/val = copytext(e, index + length(e[index])) - codes[key] = val - else - codes[e] = "1" - -/obj/machinery/navbeacon/proc/glob_lists_deregister() - if (GLOB.navbeacons["[z]"]) - GLOB.navbeacons["[z]"] -= src //Remove from beacon list, if in one. - GLOB.deliverybeacons -= src - GLOB.deliverybeacontags -= location - GLOB.wayfindingbeacons -= src - -/obj/machinery/navbeacon/proc/glob_lists_register(var/init=FALSE) - if(!init) - glob_lists_deregister() - if(codes["patrol"]) - if(!GLOB.navbeacons["[z]"]) - GLOB.navbeacons["[z]"] = list() - GLOB.navbeacons["[z]"] += src //Register with the patrol list! - if(codes["delivery"]) - GLOB.deliverybeacons += src - GLOB.deliverybeacontags += location - if(codes["wayfinding"]) - GLOB.wayfindingbeacons += src - -// update the icon_state -/obj/machinery/navbeacon/update_icon_state() - icon_state = "navbeacon[open]" - -/obj/machinery/navbeacon/attackby(obj/item/I, mob/user, params) - var/turf/T = loc - if(T.intact) - return // prevent intraction when T-scanner revealed - - if(I.tool_behaviour == TOOL_SCREWDRIVER) - open = !open - - user.visible_message("[user] [open ? "opens" : "closes"] the beacon's cover.", "You [open ? "open" : "close"] the beacon's cover.") - - update_icon() - - else if (istype(I, /obj/item/card/id)||istype(I, /obj/item/pda)) - if(open) - if (src.allowed(user)) - src.locked = !src.locked - to_chat(user, "Controls are now [src.locked ? "locked" : "unlocked"].") - else - to_chat(user, "Access denied.") - updateDialog() - else - to_chat(user, "You must open the cover first!") - else - return ..() - -/obj/machinery/navbeacon/attack_ai(mob/user) - interact(user, 1) - -/obj/machinery/navbeacon/attack_paw() - return - -/obj/machinery/navbeacon/ui_interact(mob/user) - . = ..() - var/ai = isAI(user) - var/turf/T = loc - if(T.intact) - return // prevent intraction when T-scanner revealed - - if(!open && !ai) // can't alter controls if not open, unless you're an AI - to_chat(user, "The beacon's control cover is closed!") - return - - - var/t - - if(locked && !ai) - t = {"Navigation Beacon

    -(swipe card to unlock controls)
    -Location: [location ? location : "(none)"]
    -Transponder Codes:
      "} - - for(var/key in codes) - t += "
    • [key] ... [codes[key]]" - t+= "
        " - - else - - t = {"Navigation Beacon

        -(swipe card to lock controls)
        - -
        -Location: [location ? location : "None"]
        -Transponder Codes:
          "} - - for(var/key in codes) - t += "
        • [key] ... [codes[key]]" - t += " Edit" - t += " Delete
          " - t += " Add New
          " - t+= "
            " - - var/datum/browser/popup = new(user, "navbeacon", "Navigation Beacon", 300, 400) - popup.set_content(t) - popup.open() - return - -/obj/machinery/navbeacon/Topic(href, href_list) - if(..()) - return - if(open && !locked) - usr.set_machine(src) - - if(href_list["locedit"]) - var/newloc = stripped_input(usr, "Enter New Location", "Navigation Beacon", location, MAX_MESSAGE_LEN) - if(newloc) - location = newloc - glob_lists_register() - updateDialog() - - else if(href_list["edit"]) - var/codekey = href_list["code"] - - var/newkey = stripped_input(usr, "Enter Transponder Code Key", "Navigation Beacon", codekey) - if(!newkey) - return - - var/codeval = codes[codekey] - var/newval = stripped_input(usr, "Enter Transponder Code Value", "Navigation Beacon", codeval) - if(!newval) - newval = codekey - return - - codes.Remove(codekey) - codes[newkey] = newval - glob_lists_register() - - updateDialog() - - else if(href_list["delete"]) - var/codekey = href_list["code"] - codes.Remove(codekey) - glob_lists_register() - updateDialog() - - else if(href_list["add"]) - - var/newkey = stripped_input(usr, "Enter New Transponder Code Key", "Navigation Beacon") - if(!newkey) - return - - var/newval = stripped_input(usr, "Enter New Transponder Code Value", "Navigation Beacon") - if(!newval) - newval = "1" - return - - if(!codes) - codes = new() - - codes[newkey] = newval - glob_lists_register() - - updateDialog() +// Navigation beacon for AI robots +// No longer exists on the radio controller, it is managed by a global list. + +/obj/machinery/navbeacon + + icon = 'icons/obj/objects.dmi' + icon_state = "navbeacon0-f" + name = "navigation beacon" + desc = "A radio beacon used for bot navigation and crew wayfinding." + layer = LOW_OBJ_LAYER + max_integrity = 500 + armor = list("melee" = 70, "bullet" = 70, "laser" = 70, "energy" = 70, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80) + + var/open = FALSE // true if cover is open + var/locked = TRUE // true if controls are locked + var/freq = FREQ_NAV_BEACON + var/location = "" // location response text + var/list/codes // assoc. list of transponder codes + var/codes_txt = "" // codes as set on map: "tag1;tag2" or "tag1=value;tag2=value" + var/wayfinding = FALSE + + req_one_access = list(ACCESS_ENGINE, ACCESS_ROBOTICS) + +/obj/machinery/navbeacon/Initialize() + . = ..() + + if(wayfinding) + if(!location) + var/obj/machinery/door/airlock/A = locate(/obj/machinery/door/airlock) in loc + if(A) + location = A.name + else + location = get_area(src) + codes_txt += "wayfinding=[location]" + + set_codes() + + glob_lists_register(init=TRUE) + + AddElement(/datum/element/undertile, TRAIT_T_RAY_VISIBLE) + +/obj/machinery/navbeacon/Destroy() + glob_lists_deregister() + return ..() + +/obj/machinery/navbeacon/onTransitZ(old_z, new_z) + if (GLOB.navbeacons["[old_z]"]) + GLOB.navbeacons["[old_z]"] -= src + if (GLOB.navbeacons["[new_z]"]) + GLOB.navbeacons["[new_z]"] += src + ..() + +// set the transponder codes assoc list from codes_txt +/obj/machinery/navbeacon/proc/set_codes() + if(!codes_txt) + return + + codes = new() + + var/list/entries = splittext(codes_txt, ";") // entries are separated by semicolons + + for(var/e in entries) + var/index = findtext(e, "=") // format is "key=value" + if(index) + var/key = copytext(e, 1, index) + var/val = copytext(e, index + length(e[index])) + codes[key] = val + else + codes[e] = "1" + +/obj/machinery/navbeacon/proc/glob_lists_deregister() + if (GLOB.navbeacons["[z]"]) + GLOB.navbeacons["[z]"] -= src //Remove from beacon list, if in one. + GLOB.deliverybeacons -= src + GLOB.deliverybeacontags -= location + GLOB.wayfindingbeacons -= src + +/obj/machinery/navbeacon/proc/glob_lists_register(var/init=FALSE) + if(!init) + glob_lists_deregister() + if(codes["patrol"]) + if(!GLOB.navbeacons["[z]"]) + GLOB.navbeacons["[z]"] = list() + GLOB.navbeacons["[z]"] += src //Register with the patrol list! + if(codes["delivery"]) + GLOB.deliverybeacons += src + GLOB.deliverybeacontags += location + if(codes["wayfinding"]) + GLOB.wayfindingbeacons += src + +// update the icon_state +/obj/machinery/navbeacon/update_icon_state() + icon_state = "navbeacon[open]" + +/obj/machinery/navbeacon/attackby(obj/item/I, mob/user, params) + var/turf/T = loc + if(T.intact) + return // prevent intraction when T-scanner revealed + + if(I.tool_behaviour == TOOL_SCREWDRIVER) + open = !open + + user.visible_message("[user] [open ? "opens" : "closes"] the beacon's cover.", "You [open ? "open" : "close"] the beacon's cover.") + + update_icon() + + else if (istype(I, /obj/item/card/id)||istype(I, /obj/item/pda)) + if(open) + if (src.allowed(user)) + src.locked = !src.locked + to_chat(user, "Controls are now [src.locked ? "locked" : "unlocked"].") + else + to_chat(user, "Access denied.") + updateDialog() + else + to_chat(user, "You must open the cover first!") + else + return ..() + +/obj/machinery/navbeacon/attack_ai(mob/user) + interact(user, 1) + +/obj/machinery/navbeacon/attack_paw() + return + +/obj/machinery/navbeacon/ui_interact(mob/user) + . = ..() + var/ai = isAI(user) + var/turf/T = loc + if(T.intact) + return // prevent intraction when T-scanner revealed + + if(!open && !ai) // can't alter controls if not open, unless you're an AI + to_chat(user, "The beacon's control cover is closed!") + return + + + var/t + + if(locked && !ai) + t = {"Navigation Beacon

            +(swipe card to unlock controls)
            +Location: [location ? location : "(none)"]
            +Transponder Codes:
              "} + + for(var/key in codes) + t += "
            • [key] ... [codes[key]]" + t+= "
                " + + else + + t = {"Navigation Beacon

                +(swipe card to lock controls)
                + +
                +Location: [location ? location : "None"]
                +Transponder Codes:
                  "} + + for(var/key in codes) + t += "
                • [key] ... [codes[key]]" + t += " Edit" + t += " Delete
                  " + t += " Add New
                  " + t+= "
                    " + + var/datum/browser/popup = new(user, "navbeacon", "Navigation Beacon", 300, 400) + popup.set_content(t) + popup.open() + return + +/obj/machinery/navbeacon/Topic(href, href_list) + if(..()) + return + if(open && !locked) + usr.set_machine(src) + + if(href_list["locedit"]) + var/newloc = stripped_input(usr, "Enter New Location", "Navigation Beacon", location, MAX_MESSAGE_LEN) + if(newloc) + location = newloc + glob_lists_register() + updateDialog() + + else if(href_list["edit"]) + var/codekey = href_list["code"] + + var/newkey = stripped_input(usr, "Enter Transponder Code Key", "Navigation Beacon", codekey) + if(!newkey) + return + + var/codeval = codes[codekey] + var/newval = stripped_input(usr, "Enter Transponder Code Value", "Navigation Beacon", codeval) + if(!newval) + newval = codekey + return + + codes.Remove(codekey) + codes[newkey] = newval + glob_lists_register() + + updateDialog() + + else if(href_list["delete"]) + var/codekey = href_list["code"] + codes.Remove(codekey) + glob_lists_register() + updateDialog() + + else if(href_list["add"]) + + var/newkey = stripped_input(usr, "Enter New Transponder Code Key", "Navigation Beacon") + if(!newkey) + return + + var/newval = stripped_input(usr, "Enter New Transponder Code Value", "Navigation Beacon") + if(!newval) + newval = "1" + return + + if(!codes) + codes = new() + + codes[newkey] = newval + glob_lists_register() + + updateDialog() diff --git a/code/game/machinery/pipe/construction.dm b/code/game/machinery/pipe/construction.dm index 25c9bddeff00..bf18a5bf680c 100644 --- a/code/game/machinery/pipe/construction.dm +++ b/code/game/machinery/pipe/construction.dm @@ -1,234 +1,234 @@ -/*CONTENTS -Buildable pipes -Buildable meters -*/ - -//construction defines are in __defines/pipe_construction.dm -//update those defines ANY TIME an atmos path is changed... -//...otherwise construction will stop working - -/obj/item/pipe - name = "pipe" - desc = "A pipe." - var/pipe_type - var/pipename - force = 7 - throwforce = 7 - icon = 'icons/obj/atmospherics/pipes/pipe_item.dmi' - icon_state = "simple" - item_state = "buildpipe" - w_class = WEIGHT_CLASS_NORMAL - var/piping_layer = PIPING_LAYER_DEFAULT - var/RPD_type - -/obj/item/pipe/directional - RPD_type = PIPE_UNARY -/obj/item/pipe/binary - RPD_type = PIPE_STRAIGHT -/obj/item/pipe/binary/bendable - RPD_type = PIPE_BENDABLE -/obj/item/pipe/trinary - RPD_type = PIPE_TRINARY -/obj/item/pipe/trinary/flippable - RPD_type = PIPE_TRIN_M - var/flipped = FALSE -/obj/item/pipe/quaternary - RPD_type = PIPE_ONEDIR - -/obj/item/pipe/ComponentInitialize() - //Flipping handled manually due to custom handling for trinary pipes - AddComponent(/datum/component/simple_rotation, ROTATION_ALTCLICK | ROTATION_CLOCKWISE) - -/obj/item/pipe/Initialize(mapload, _pipe_type, _dir, obj/machinery/atmospherics/make_from) - if(make_from) - make_from_existing(make_from) - else - pipe_type = _pipe_type - setDir(_dir) - - update() - pixel_x += rand(-5, 5) - pixel_y += rand(-5, 5) - return ..() - -/obj/item/pipe/proc/make_from_existing(obj/machinery/atmospherics/make_from) - setDir(make_from.dir) - pipename = make_from.name - add_atom_colour(make_from.color, FIXED_COLOUR_PRIORITY) - pipe_type = make_from.type - -/obj/item/pipe/trinary/flippable/make_from_existing(obj/machinery/atmospherics/components/trinary/make_from) - ..() - if(make_from.flipped) - do_a_flip() - -/obj/item/pipe/dropped() - if(loc) - setPipingLayer(piping_layer) - return ..() - -/obj/item/pipe/proc/setPipingLayer(new_layer = PIPING_LAYER_DEFAULT) - var/obj/machinery/atmospherics/fakeA = pipe_type - - if(initial(fakeA.pipe_flags) & PIPING_ALL_LAYER) - new_layer = PIPING_LAYER_DEFAULT - piping_layer = new_layer - - PIPING_LAYER_SHIFT(src, piping_layer) - layer = initial(layer) + ((piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_LCHANGE) - -/obj/item/pipe/proc/update() - var/obj/machinery/atmospherics/fakeA = pipe_type - name = "[initial(fakeA.name)] fitting" - icon_state = initial(fakeA.pipe_state) - if(ispath(pipe_type,/obj/machinery/atmospherics/pipe/heat_exchanging)) - resistance_flags |= FIRE_PROOF | LAVA_PROOF - -/obj/item/pipe/verb/flip() - set category = "Object" - set name = "Flip Pipe" - set src in view(1) - - if ( usr.incapacitated() ) - return - - do_a_flip() - -/obj/item/pipe/proc/do_a_flip() - setDir(turn(dir, -180)) - -/obj/item/pipe/trinary/flippable/do_a_flip() - setDir(turn(dir, flipped ? 45 : -45)) - flipped = !flipped - -/obj/item/pipe/Move() - var/old_dir = dir - ..() - setDir(old_dir) //pipes changing direction when moved is just annoying and buggy - -// Convert dir of fitting into dir of built component -/obj/item/pipe/proc/fixed_dir() - return dir - -/obj/item/pipe/binary/fixed_dir() - . = dir - if(dir == SOUTH) - . = NORTH - else if(dir == WEST) - . = EAST - -/obj/item/pipe/trinary/flippable/fixed_dir() - . = dir - if(dir in GLOB.diagonals) - . = turn(dir, 45) - -/obj/item/pipe/attack_self(mob/user) - setDir(turn(dir,-90)) - -/obj/item/pipe/wrench_act(mob/living/user, obj/item/wrench/W) - . = ..() - if(!isturf(loc)) - return TRUE - - add_fingerprint(user) - - var/obj/machinery/atmospherics/fakeA = pipe_type - var/flags = initial(fakeA.pipe_flags) - for(var/obj/machinery/atmospherics/M in loc) - if((M.pipe_flags & flags & PIPING_ONE_PER_TURF)) //Only one dense/requires density object per tile, eg connectors/cryo/heater/coolers. - to_chat(user, "Something is hogging the tile!") - return TRUE - if((M.piping_layer != piping_layer) && !((M.pipe_flags | flags) & PIPING_ALL_LAYER)) //don't continue if either pipe goes across all layers - continue - if(M.GetInitDirections() & SSair.get_init_dirs(pipe_type, fixed_dir())) // matches at least one direction on either type of pipe - to_chat(user, "There is already a pipe at that location!") - return TRUE - // no conflicts found - - var/obj/machinery/atmospherics/A = new pipe_type(loc) - build_pipe(A) - A.on_construction(color, piping_layer) - transfer_fingerprints_to(A) - - W.play_tool_sound(src) - user.visible_message( \ - "[user] fastens \the [src].", \ - "You fasten \the [src].", \ - "You hear ratcheting.") - - qdel(src) - -/obj/item/pipe/proc/build_pipe(obj/machinery/atmospherics/A) - A.setDir(fixed_dir()) - A.SetInitDirections() - - if(pipename) - A.name = pipename - if(A.on) - // Certain pre-mapped subtypes are on by default, we want to preserve - // every other aspect of these subtypes (name, pre-set filters, etc.) - // but they shouldn't turn on automatically when wrenched. - A.on = FALSE - -/obj/item/pipe/trinary/flippable/build_pipe(obj/machinery/atmospherics/components/trinary/T) - ..() - T.flipped = flipped - -/obj/item/pipe/directional/suicide_act(mob/user) - user.visible_message("[user] shoves [src] in [user.p_their()] mouth and turns it on! It looks like [user.p_theyre()] trying to commit suicide!") - if(iscarbon(user)) - var/mob/living/carbon/C = user - for(var/i=1 to 20) - C.vomit(0, TRUE, FALSE, 4, FALSE) - if(prob(20)) - C.spew_organ() - sleep(5) - C.blood_volume = 0 - return(OXYLOSS|BRUTELOSS) - -/obj/item/pipe_meter - name = "meter" - desc = "A meter that can be laid on pipes." - icon = 'icons/obj/atmospherics/pipes/pipe_item.dmi' - icon_state = "meter" - item_state = "buildpipe" - w_class = WEIGHT_CLASS_BULKY - var/piping_layer = PIPING_LAYER_DEFAULT - -/obj/item/pipe_meter/wrench_act(mob/living/user, obj/item/wrench/W) - . = ..() - var/obj/machinery/atmospherics/pipe/pipe - for(var/obj/machinery/atmospherics/pipe/P in loc) - if(P.piping_layer == piping_layer) - pipe = P - break - if(!pipe) - to_chat(user, "You need to fasten it to a pipe!") - return TRUE - new /obj/machinery/meter(loc, piping_layer) - W.play_tool_sound(src) - to_chat(user, "You fasten the meter to the pipe.") - qdel(src) - -/obj/item/pipe_meter/screwdriver_act(mob/living/user, obj/item/S) - . = ..() - if(.) - return TRUE - - if(!isturf(loc)) - to_chat(user, "You need to fasten it to the floor!") - return TRUE - - new /obj/machinery/meter/turf(loc, piping_layer) - S.play_tool_sound(src) - to_chat(user, "You fasten the meter to the [loc.name].") - qdel(src) - -/obj/item/pipe_meter/dropped() - . = ..() - if(loc) - setAttachLayer(piping_layer) - -/obj/item/pipe_meter/proc/setAttachLayer(new_layer = PIPING_LAYER_DEFAULT) - piping_layer = new_layer - PIPING_LAYER_DOUBLE_SHIFT(src, piping_layer) +/*CONTENTS +Buildable pipes +Buildable meters +*/ + +//construction defines are in __defines/pipe_construction.dm +//update those defines ANY TIME an atmos path is changed... +//...otherwise construction will stop working + +/obj/item/pipe + name = "pipe" + desc = "A pipe." + var/pipe_type + var/pipename + force = 7 + throwforce = 7 + icon = 'icons/obj/atmospherics/pipes/pipe_item.dmi' + icon_state = "simple" + item_state = "buildpipe" + w_class = WEIGHT_CLASS_NORMAL + var/piping_layer = PIPING_LAYER_DEFAULT + var/RPD_type + +/obj/item/pipe/directional + RPD_type = PIPE_UNARY +/obj/item/pipe/binary + RPD_type = PIPE_STRAIGHT +/obj/item/pipe/binary/bendable + RPD_type = PIPE_BENDABLE +/obj/item/pipe/trinary + RPD_type = PIPE_TRINARY +/obj/item/pipe/trinary/flippable + RPD_type = PIPE_TRIN_M + var/flipped = FALSE +/obj/item/pipe/quaternary + RPD_type = PIPE_ONEDIR + +/obj/item/pipe/ComponentInitialize() + //Flipping handled manually due to custom handling for trinary pipes + AddComponent(/datum/component/simple_rotation, ROTATION_ALTCLICK | ROTATION_CLOCKWISE) + +/obj/item/pipe/Initialize(mapload, _pipe_type, _dir, obj/machinery/atmospherics/make_from) + if(make_from) + make_from_existing(make_from) + else + pipe_type = _pipe_type + setDir(_dir) + + update() + pixel_x += rand(-5, 5) + pixel_y += rand(-5, 5) + return ..() + +/obj/item/pipe/proc/make_from_existing(obj/machinery/atmospherics/make_from) + setDir(make_from.dir) + pipename = make_from.name + add_atom_colour(make_from.color, FIXED_COLOUR_PRIORITY) + pipe_type = make_from.type + +/obj/item/pipe/trinary/flippable/make_from_existing(obj/machinery/atmospherics/components/trinary/make_from) + ..() + if(make_from.flipped) + do_a_flip() + +/obj/item/pipe/dropped() + if(loc) + setPipingLayer(piping_layer) + return ..() + +/obj/item/pipe/proc/setPipingLayer(new_layer = PIPING_LAYER_DEFAULT) + var/obj/machinery/atmospherics/fakeA = pipe_type + + if(initial(fakeA.pipe_flags) & PIPING_ALL_LAYER) + new_layer = PIPING_LAYER_DEFAULT + piping_layer = new_layer + + PIPING_LAYER_SHIFT(src, piping_layer) + layer = initial(layer) + ((piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_LCHANGE) + +/obj/item/pipe/proc/update() + var/obj/machinery/atmospherics/fakeA = pipe_type + name = "[initial(fakeA.name)] fitting" + icon_state = initial(fakeA.pipe_state) + if(ispath(pipe_type,/obj/machinery/atmospherics/pipe/heat_exchanging)) + resistance_flags |= FIRE_PROOF | LAVA_PROOF + +/obj/item/pipe/verb/flip() + set category = "Object" + set name = "Flip Pipe" + set src in view(1) + + if ( usr.incapacitated() ) + return + + do_a_flip() + +/obj/item/pipe/proc/do_a_flip() + setDir(turn(dir, -180)) + +/obj/item/pipe/trinary/flippable/do_a_flip() + setDir(turn(dir, flipped ? 45 : -45)) + flipped = !flipped + +/obj/item/pipe/Move() + var/old_dir = dir + ..() + setDir(old_dir) //pipes changing direction when moved is just annoying and buggy + +// Convert dir of fitting into dir of built component +/obj/item/pipe/proc/fixed_dir() + return dir + +/obj/item/pipe/binary/fixed_dir() + . = dir + if(dir == SOUTH) + . = NORTH + else if(dir == WEST) + . = EAST + +/obj/item/pipe/trinary/flippable/fixed_dir() + . = dir + if(dir in GLOB.diagonals) + . = turn(dir, 45) + +/obj/item/pipe/attack_self(mob/user) + setDir(turn(dir,-90)) + +/obj/item/pipe/wrench_act(mob/living/user, obj/item/wrench/W) + . = ..() + if(!isturf(loc)) + return TRUE + + add_fingerprint(user) + + var/obj/machinery/atmospherics/fakeA = pipe_type + var/flags = initial(fakeA.pipe_flags) + for(var/obj/machinery/atmospherics/M in loc) + if((M.pipe_flags & flags & PIPING_ONE_PER_TURF)) //Only one dense/requires density object per tile, eg connectors/cryo/heater/coolers. + to_chat(user, "Something is hogging the tile!") + return TRUE + if((M.piping_layer != piping_layer) && !((M.pipe_flags | flags) & PIPING_ALL_LAYER)) //don't continue if either pipe goes across all layers + continue + if(M.GetInitDirections() & SSair.get_init_dirs(pipe_type, fixed_dir())) // matches at least one direction on either type of pipe + to_chat(user, "There is already a pipe at that location!") + return TRUE + // no conflicts found + + var/obj/machinery/atmospherics/A = new pipe_type(loc) + build_pipe(A) + A.on_construction(color, piping_layer) + transfer_fingerprints_to(A) + + W.play_tool_sound(src) + user.visible_message( \ + "[user] fastens \the [src].", \ + "You fasten \the [src].", \ + "You hear ratcheting.") + + qdel(src) + +/obj/item/pipe/proc/build_pipe(obj/machinery/atmospherics/A) + A.setDir(fixed_dir()) + A.SetInitDirections() + + if(pipename) + A.name = pipename + if(A.on) + // Certain pre-mapped subtypes are on by default, we want to preserve + // every other aspect of these subtypes (name, pre-set filters, etc.) + // but they shouldn't turn on automatically when wrenched. + A.on = FALSE + +/obj/item/pipe/trinary/flippable/build_pipe(obj/machinery/atmospherics/components/trinary/T) + ..() + T.flipped = flipped + +/obj/item/pipe/directional/suicide_act(mob/user) + user.visible_message("[user] shoves [src] in [user.p_their()] mouth and turns it on! It looks like [user.p_theyre()] trying to commit suicide!") + if(iscarbon(user)) + var/mob/living/carbon/C = user + for(var/i=1 to 20) + C.vomit(0, TRUE, FALSE, 4, FALSE) + if(prob(20)) + C.spew_organ() + sleep(5) + C.blood_volume = 0 + return(OXYLOSS|BRUTELOSS) + +/obj/item/pipe_meter + name = "meter" + desc = "A meter that can be laid on pipes." + icon = 'icons/obj/atmospherics/pipes/pipe_item.dmi' + icon_state = "meter" + item_state = "buildpipe" + w_class = WEIGHT_CLASS_BULKY + var/piping_layer = PIPING_LAYER_DEFAULT + +/obj/item/pipe_meter/wrench_act(mob/living/user, obj/item/wrench/W) + . = ..() + var/obj/machinery/atmospherics/pipe/pipe + for(var/obj/machinery/atmospherics/pipe/P in loc) + if(P.piping_layer == piping_layer) + pipe = P + break + if(!pipe) + to_chat(user, "You need to fasten it to a pipe!") + return TRUE + new /obj/machinery/meter(loc, piping_layer) + W.play_tool_sound(src) + to_chat(user, "You fasten the meter to the pipe.") + qdel(src) + +/obj/item/pipe_meter/screwdriver_act(mob/living/user, obj/item/S) + . = ..() + if(.) + return TRUE + + if(!isturf(loc)) + to_chat(user, "You need to fasten it to the floor!") + return TRUE + + new /obj/machinery/meter/turf(loc, piping_layer) + S.play_tool_sound(src) + to_chat(user, "You fasten the meter to the [loc.name].") + qdel(src) + +/obj/item/pipe_meter/dropped() + . = ..() + if(loc) + setAttachLayer(piping_layer) + +/obj/item/pipe_meter/proc/setAttachLayer(new_layer = PIPING_LAYER_DEFAULT) + piping_layer = new_layer + PIPING_LAYER_DOUBLE_SHIFT(src, piping_layer) diff --git a/code/game/machinery/pipe/pipe_dispenser.dm b/code/game/machinery/pipe/pipe_dispenser.dm index 5b72ef26844f..93a0f7869b6f 100644 --- a/code/game/machinery/pipe/pipe_dispenser.dm +++ b/code/game/machinery/pipe/pipe_dispenser.dm @@ -1,214 +1,214 @@ -/obj/machinery/pipedispenser - name = "pipe dispenser" - icon = 'icons/obj/stationobjs.dmi' - icon_state = "pipe_d" - desc = "Dispenses countless types of pipes. Very useful if you need pipes." - density = TRUE - interaction_flags_machine = INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OPEN_SILICON | INTERACT_MACHINE_OFFLINE - var/wait = 0 - var/piping_layer = PIPING_LAYER_DEFAULT - -/obj/machinery/pipedispenser/attack_paw(mob/user) - return attack_hand(user) - -/obj/machinery/pipedispenser/ui_interact(mob/user) - . = ..() - var/dat = "PIPING LAYER: --[piping_layer]++
                    " - - var/recipes = GLOB.atmos_pipe_recipes - - for(var/category in recipes) - var/list/cat_recipes = recipes[category] - dat += "[category]:
                      " - - for(var/i in cat_recipes) - var/datum/pipe_info/I = i - dat += I.Render(src) - - dat += "
                    " - - user << browse("[src][dat]", "window=pipedispenser") - onclose(user, "pipedispenser") - return - -/obj/machinery/pipedispenser/Topic(href, href_list) - if(..()) - return 1 - var/mob/living/L = usr - if(!anchored || (istype(L) && !(L.mobility_flags & MOBILITY_UI)) || usr.stat || usr.restrained() || !in_range(loc, usr)) - usr << browse(null, "window=pipedispenser") - return 1 - usr.set_machine(src) - add_fingerprint(usr) - if(href_list["makepipe"]) - if(wait < world.time) - var/p_type = text2path(href_list["makepipe"]) - if (!verify_recipe(GLOB.atmos_pipe_recipes, p_type)) - return - var/p_dir = text2num(href_list["dir"]) - var/obj/item/pipe/P = new (loc, p_type, p_dir) - P.setPipingLayer(piping_layer) - P.add_fingerprint(usr) - wait = world.time + 10 - if(href_list["makemeter"]) - if(wait < world.time ) - new /obj/item/pipe_meter(loc) - wait = world.time + 15 - if(href_list["layer_up"]) - piping_layer = clamp(++piping_layer, PIPING_LAYER_MIN, PIPING_LAYER_MAX) - if(href_list["layer_down"]) - piping_layer = clamp(--piping_layer, PIPING_LAYER_MIN, PIPING_LAYER_MAX) - return - -/obj/machinery/pipedispenser/attackby(obj/item/W, mob/user, params) - add_fingerprint(user) - if (istype(W, /obj/item/pipe) || istype(W, /obj/item/pipe_meter)) - to_chat(usr, "You put [W] back into [src].") - qdel(W) - return - else - return ..() - -/obj/machinery/pipedispenser/proc/verify_recipe(recipes, path) - for(var/category in recipes) - var/list/cat_recipes = recipes[category] - for(var/i in cat_recipes) - var/datum/pipe_info/info = i - if (path == info.id) - return TRUE - return FALSE - -/obj/machinery/pipedispenser/wrench_act(mob/living/user, obj/item/I) - ..() - if(default_unfasten_wrench(user, I, 40)) - user << browse(null, "window=pipedispenser") - - return TRUE - - -/obj/machinery/pipedispenser/disposal - name = "disposal pipe dispenser" - icon = 'icons/obj/stationobjs.dmi' - icon_state = "pipe_d" - desc = "Dispenses pipes that will ultimately be used to move trash around." - density = TRUE - - -//Allow you to drag-drop disposal pipes and transit tubes into it -/obj/machinery/pipedispenser/disposal/MouseDrop_T(obj/structure/pipe, mob/usr) - if(!usr.incapacitated()) - return - - if (!istype(pipe, /obj/structure/disposalconstruct) && !istype(pipe, /obj/structure/c_transit_tube) && !istype(pipe, /obj/structure/c_transit_tube_pod)) - return - - if (get_dist(usr, src) > 1 || get_dist(src,pipe) > 1 ) - return - - if (pipe.anchored) - return - - qdel(pipe) - -/obj/machinery/pipedispenser/disposal/interact(mob/user) - - var/dat = "" - var/recipes = GLOB.disposal_pipe_recipes - - for(var/category in recipes) - var/list/cat_recipes = recipes[category] - dat += "[category]:
                      " - - for(var/i in cat_recipes) - var/datum/pipe_info/I = i - dat += I.Render(src) - - dat += "
                    " - - user << browse("[src][dat]", "window=pipedispenser") - return - - -/obj/machinery/pipedispenser/disposal/Topic(href, href_list) - if(..()) - return 1 - usr.set_machine(src) - add_fingerprint(usr) - if(href_list["dmake"]) - if(wait < world.time) - var/p_type = text2path(href_list["dmake"]) - if (!verify_recipe(GLOB.disposal_pipe_recipes, p_type)) - return - var/obj/structure/disposalconstruct/C = new (loc, p_type) - - if(!C.can_place()) - to_chat(usr, "There's not enough room to build that here!") - qdel(C) - return - if(href_list["dir"]) - C.setDir(text2num(href_list["dir"])) - C.add_fingerprint(usr) - C.update_icon() - wait = world.time + 15 - return - -//transit tube dispenser -//inherit disposal for the dragging proc -/obj/machinery/pipedispenser/disposal/transit_tube - name = "transit tube dispenser" - icon = 'icons/obj/stationobjs.dmi' - icon_state = "pipe_d" - density = TRUE - desc = "Dispenses pipes that will move beings around." - -/obj/machinery/pipedispenser/disposal/transit_tube/interact(mob/user) - - var/dat = {"Transit Tubes:
                    -Straight Tube
                    -Straight Tube with Crossing
                    -Curved Tube
                    -Diagonal Tube
                    -Diagonal Tube with Crossing
                    -Junction
                    -Station Equipment:
                    -Through Tube Station
                    -Terminus Tube Station
                    -Transit Tube Pod
                    -"} - - user << browse("[src][dat]", "window=pipedispenser") - return - - -/obj/machinery/pipedispenser/disposal/transit_tube/Topic(href, href_list) - if(..()) - return 1 - usr.set_machine(src) - add_fingerprint(usr) - if(wait < world.time) - if(href_list["tube"]) - var/tube_type = text2num(href_list["tube"]) - var/obj/structure/C - switch(tube_type) - if(TRANSIT_TUBE_STRAIGHT) - C = new /obj/structure/c_transit_tube(loc) - if(TRANSIT_TUBE_STRAIGHT_CROSSING) - C = new /obj/structure/c_transit_tube/crossing(loc) - if(TRANSIT_TUBE_CURVED) - C = new /obj/structure/c_transit_tube/curved(loc) - if(TRANSIT_TUBE_DIAGONAL) - C = new /obj/structure/c_transit_tube/diagonal(loc) - if(TRANSIT_TUBE_DIAGONAL_CROSSING) - C = new /obj/structure/c_transit_tube/diagonal/crossing(loc) - if(TRANSIT_TUBE_JUNCTION) - C = new /obj/structure/c_transit_tube/junction(loc) - if(TRANSIT_TUBE_STATION) - C = new /obj/structure/c_transit_tube/station(loc) - if(TRANSIT_TUBE_TERMINUS) - C = new /obj/structure/c_transit_tube/station/reverse(loc) - if(TRANSIT_TUBE_POD) - C = new /obj/structure/c_transit_tube_pod(loc) - if(C) - C.add_fingerprint(usr) - wait = world.time + 15 - return +/obj/machinery/pipedispenser + name = "pipe dispenser" + icon = 'icons/obj/stationobjs.dmi' + icon_state = "pipe_d" + desc = "Dispenses countless types of pipes. Very useful if you need pipes." + density = TRUE + interaction_flags_machine = INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OPEN_SILICON | INTERACT_MACHINE_OFFLINE + var/wait = 0 + var/piping_layer = PIPING_LAYER_DEFAULT + +/obj/machinery/pipedispenser/attack_paw(mob/user) + return attack_hand(user) + +/obj/machinery/pipedispenser/ui_interact(mob/user) + . = ..() + var/dat = "PIPING LAYER: --[piping_layer]++
                    " + + var/recipes = GLOB.atmos_pipe_recipes + + for(var/category in recipes) + var/list/cat_recipes = recipes[category] + dat += "[category]:
                      " + + for(var/i in cat_recipes) + var/datum/pipe_info/I = i + dat += I.Render(src) + + dat += "
                    " + + user << browse("[src][dat]", "window=pipedispenser") + onclose(user, "pipedispenser") + return + +/obj/machinery/pipedispenser/Topic(href, href_list) + if(..()) + return 1 + var/mob/living/L = usr + if(!anchored || (istype(L) && !(L.mobility_flags & MOBILITY_UI)) || usr.stat || usr.restrained() || !in_range(loc, usr)) + usr << browse(null, "window=pipedispenser") + return 1 + usr.set_machine(src) + add_fingerprint(usr) + if(href_list["makepipe"]) + if(wait < world.time) + var/p_type = text2path(href_list["makepipe"]) + if (!verify_recipe(GLOB.atmos_pipe_recipes, p_type)) + return + var/p_dir = text2num(href_list["dir"]) + var/obj/item/pipe/P = new (loc, p_type, p_dir) + P.setPipingLayer(piping_layer) + P.add_fingerprint(usr) + wait = world.time + 10 + if(href_list["makemeter"]) + if(wait < world.time ) + new /obj/item/pipe_meter(loc) + wait = world.time + 15 + if(href_list["layer_up"]) + piping_layer = clamp(++piping_layer, PIPING_LAYER_MIN, PIPING_LAYER_MAX) + if(href_list["layer_down"]) + piping_layer = clamp(--piping_layer, PIPING_LAYER_MIN, PIPING_LAYER_MAX) + return + +/obj/machinery/pipedispenser/attackby(obj/item/W, mob/user, params) + add_fingerprint(user) + if (istype(W, /obj/item/pipe) || istype(W, /obj/item/pipe_meter)) + to_chat(usr, "You put [W] back into [src].") + qdel(W) + return + else + return ..() + +/obj/machinery/pipedispenser/proc/verify_recipe(recipes, path) + for(var/category in recipes) + var/list/cat_recipes = recipes[category] + for(var/i in cat_recipes) + var/datum/pipe_info/info = i + if (path == info.id) + return TRUE + return FALSE + +/obj/machinery/pipedispenser/wrench_act(mob/living/user, obj/item/I) + ..() + if(default_unfasten_wrench(user, I, 40)) + user << browse(null, "window=pipedispenser") + + return TRUE + + +/obj/machinery/pipedispenser/disposal + name = "disposal pipe dispenser" + icon = 'icons/obj/stationobjs.dmi' + icon_state = "pipe_d" + desc = "Dispenses pipes that will ultimately be used to move trash around." + density = TRUE + + +//Allow you to drag-drop disposal pipes and transit tubes into it +/obj/machinery/pipedispenser/disposal/MouseDrop_T(obj/structure/pipe, mob/usr) + if(!usr.incapacitated()) + return + + if (!istype(pipe, /obj/structure/disposalconstruct) && !istype(pipe, /obj/structure/c_transit_tube) && !istype(pipe, /obj/structure/c_transit_tube_pod)) + return + + if (get_dist(usr, src) > 1 || get_dist(src,pipe) > 1 ) + return + + if (pipe.anchored) + return + + qdel(pipe) + +/obj/machinery/pipedispenser/disposal/interact(mob/user) + + var/dat = "" + var/recipes = GLOB.disposal_pipe_recipes + + for(var/category in recipes) + var/list/cat_recipes = recipes[category] + dat += "[category]:
                      " + + for(var/i in cat_recipes) + var/datum/pipe_info/I = i + dat += I.Render(src) + + dat += "
                    " + + user << browse("[src][dat]", "window=pipedispenser") + return + + +/obj/machinery/pipedispenser/disposal/Topic(href, href_list) + if(..()) + return 1 + usr.set_machine(src) + add_fingerprint(usr) + if(href_list["dmake"]) + if(wait < world.time) + var/p_type = text2path(href_list["dmake"]) + if (!verify_recipe(GLOB.disposal_pipe_recipes, p_type)) + return + var/obj/structure/disposalconstruct/C = new (loc, p_type) + + if(!C.can_place()) + to_chat(usr, "There's not enough room to build that here!") + qdel(C) + return + if(href_list["dir"]) + C.setDir(text2num(href_list["dir"])) + C.add_fingerprint(usr) + C.update_icon() + wait = world.time + 15 + return + +//transit tube dispenser +//inherit disposal for the dragging proc +/obj/machinery/pipedispenser/disposal/transit_tube + name = "transit tube dispenser" + icon = 'icons/obj/stationobjs.dmi' + icon_state = "pipe_d" + density = TRUE + desc = "Dispenses pipes that will move beings around." + +/obj/machinery/pipedispenser/disposal/transit_tube/interact(mob/user) + + var/dat = {"Transit Tubes:
                    +Straight Tube
                    +Straight Tube with Crossing
                    +Curved Tube
                    +Diagonal Tube
                    +Diagonal Tube with Crossing
                    +Junction
                    +Station Equipment:
                    +Through Tube Station
                    +Terminus Tube Station
                    +Transit Tube Pod
                    +"} + + user << browse("[src][dat]", "window=pipedispenser") + return + + +/obj/machinery/pipedispenser/disposal/transit_tube/Topic(href, href_list) + if(..()) + return 1 + usr.set_machine(src) + add_fingerprint(usr) + if(wait < world.time) + if(href_list["tube"]) + var/tube_type = text2num(href_list["tube"]) + var/obj/structure/C + switch(tube_type) + if(TRANSIT_TUBE_STRAIGHT) + C = new /obj/structure/c_transit_tube(loc) + if(TRANSIT_TUBE_STRAIGHT_CROSSING) + C = new /obj/structure/c_transit_tube/crossing(loc) + if(TRANSIT_TUBE_CURVED) + C = new /obj/structure/c_transit_tube/curved(loc) + if(TRANSIT_TUBE_DIAGONAL) + C = new /obj/structure/c_transit_tube/diagonal(loc) + if(TRANSIT_TUBE_DIAGONAL_CROSSING) + C = new /obj/structure/c_transit_tube/diagonal/crossing(loc) + if(TRANSIT_TUBE_JUNCTION) + C = new /obj/structure/c_transit_tube/junction(loc) + if(TRANSIT_TUBE_STATION) + C = new /obj/structure/c_transit_tube/station(loc) + if(TRANSIT_TUBE_TERMINUS) + C = new /obj/structure/c_transit_tube/station/reverse(loc) + if(TRANSIT_TUBE_POD) + C = new /obj/structure/c_transit_tube_pod(loc) + if(C) + C.add_fingerprint(usr) + wait = world.time + 15 + return diff --git a/code/game/machinery/porta_turret/portable_turret.dm b/code/game/machinery/porta_turret/portable_turret.dm index 66e3e19bd4de..068c851baefe 100644 --- a/code/game/machinery/porta_turret/portable_turret.dm +++ b/code/game/machinery/porta_turret/portable_turret.dm @@ -1,1096 +1,1115 @@ -#define TURRET_STUN 0 -#define TURRET_LETHAL 1 - -#define POPUP_ANIM_TIME 5 -#define POPDOWN_ANIM_TIME 5 //Be sure to change the icon animation at the same time or it'll look bad - -#define TURRET_FLAG_SHOOT_ALL_REACT (1<<0) // The turret gets pissed off and shoots at people nearby (unless they have sec access!) -#define TURRET_FLAG_AUTH_WEAPONS (1<<1) // Checks if it can shoot people that have a weapon they aren't authorized to have -#define TURRET_FLAG_SHOOT_CRIMINALS (1<<2) // Checks if it can shoot people that are wanted -#define TURRET_FLAG_SHOOT_ALL (1<<3) // The turret gets pissed off and shoots at people nearby (unless they have sec access!) -#define TURRET_FLAG_SHOOT_ANOMALOUS (1<<4) // Checks if it can shoot at unidentified lifeforms (ie xenos) -#define TURRET_FLAG_SHOOT_UNSHIELDED (1<<5) // Checks if it can shoot people that aren't mindshielded and who arent heads -#define TURRET_FLAG_SHOOT_BORGS (1<<6) // checks if it can shoot cyborgs -#define TURRET_FLAG_SHOOT_HEADS (1<<7) // checks if it can shoot at heads of staff - -/obj/machinery/porta_turret - name = "turret" - icon = 'icons/obj/turrets.dmi' - icon_state = "turretCover" - layer = OBJ_LAYER - invisibility = INVISIBILITY_OBSERVER //the turret is invisible if it's inside its cover - density = TRUE - desc = "A covered turret that shoots at its enemies." - use_power = IDLE_POWER_USE //this turret uses and requires power - idle_power_usage = 50 //when inactive, this turret takes up constant 50 Equipment power - active_power_usage = 300 //when active, this turret takes up constant 300 Equipment power - req_access = list(ACCESS_SEC_DOORS) - power_channel = AREA_USAGE_EQUIP //drains power from the EQUIPMENT channel - - var/base_icon_state = "standard" - var/scan_range = 7 - var/atom/base = null //for turrets inside other objects - - var/raised = 0 //if the turret cover is "open" and the turret is raised - var/raising= 0 //if the turret is currently opening or closing its cover - - max_integrity = 160 //the turret's health - integrity_failure = 0.5 - armor = list("melee" = 50, "bullet" = 30, "laser" = 30, "energy" = 30, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 90) - - var/locked = TRUE //if the turret's behaviour control access is locked - var/controllock = FALSE //if the turret responds to control panels - - var/installation = /obj/item/gun/energy/e_gun/turret //the type of weapon installed by default - var/obj/item/gun/stored_gun = null - var/gun_charge = 0 //the charge of the gun when retrieved from wreckage - - var/mode = TURRET_STUN - - var/stun_projectile = null //stun mode projectile type - var/stun_projectile_sound - var/lethal_projectile = null //lethal mode projectile type - var/lethal_projectile_sound - - var/reqpower = 500 //power needed per shot - var/always_up = 0 //Will stay active - var/has_cover = 1 //Hides the cover - - var/obj/machinery/porta_turret_cover/cover = null //the cover that is covering this turret - - var/last_fired = 0 //world.time the turret last fired - var/shot_delay = 15 //ticks until next shot (1.5 ?) - - - var/turret_flags = TURRET_FLAG_SHOOT_CRIMINALS | TURRET_FLAG_SHOOT_ANOMALOUS - - var/on = TRUE //determines if the turret is on - - var/list/faction = list("turret" ) // Same faction mobs will never be shot at, no matter the other settings - - var/datum/effect_system/spark_spread/spark_system //the spark system, used for generating... sparks? - - var/obj/machinery/turretid/cp = null - - var/wall_turret_direction //The turret will try to shoot from a turf in that direction when in a wall - - var/manual_control = FALSE // - var/datum/action/turret_quit/quit_action - var/datum/action/turret_toggle/toggle_action - var/mob/remote_controller - -/obj/machinery/porta_turret/Initialize() - . = ..() - if(!base) - base = src - update_icon() - //Sets up a spark system - spark_system = new /datum/effect_system/spark_spread - spark_system.set_up(5, 0, src) - spark_system.attach(src) - - setup() - if(has_cover) - cover = new /obj/machinery/porta_turret_cover(loc) - cover.parent_turret = src - var/mutable_appearance/base = mutable_appearance('icons/obj/turrets.dmi', "basedark") - base.layer = NOT_HIGH_OBJ_LAYER - underlays += base - if(!has_cover) - INVOKE_ASYNC(src, .proc/popUp) - -/obj/machinery/porta_turret/update_icon_state() - if(!anchored) - icon_state = "turretCover" - return - if(machine_stat & BROKEN) - icon_state = "[base_icon_state]_broken" - else - if(powered()) - if(on && raised) - switch(mode) - if(TURRET_STUN) - icon_state = "[base_icon_state]_stun" - if(TURRET_LETHAL) - icon_state = "[base_icon_state]_lethal" - else - icon_state = "[base_icon_state]_off" - else - icon_state = "[base_icon_state]_unpowered" - -/obj/machinery/porta_turret/proc/setup(obj/item/gun/turret_gun) - if(stored_gun) - qdel(stored_gun) - stored_gun = null - - if(installation && !turret_gun) - stored_gun = new installation(src) - else if (turret_gun) - stored_gun = turret_gun - - var/list/gun_properties = stored_gun.get_turret_properties() - - //required properties - stun_projectile = gun_properties["stun_projectile"] - stun_projectile_sound = gun_properties["stun_projectile_sound"] - lethal_projectile = gun_properties["lethal_projectile"] - lethal_projectile_sound = gun_properties["lethal_projectile_sound"] - base_icon_state = gun_properties["base_icon_state"] - - //optional properties - if(gun_properties["shot_delay"]) - shot_delay = gun_properties["shot_delay"] - if(gun_properties["reqpower"]) - reqpower = gun_properties["reqpower"] - - update_icon() - return gun_properties - -/obj/machinery/porta_turret/Destroy() - //deletes its own cover with it - QDEL_NULL(cover) - base = null - if(cp) - cp.turrets -= src - cp = null - QDEL_NULL(stored_gun) - QDEL_NULL(spark_system) - remove_control() - return ..() - -/obj/machinery/porta_turret/ui_interact(mob/user) - . = ..() - var/dat - dat += "Status: [on ? "On" : "Off"]
                    " - dat += "Behaviour controls are [locked ? "locked" : "unlocked"]
                    " - - if(!locked) - dat += "Check for Weapon Authorization: [turret_flags & TURRET_FLAG_AUTH_WEAPONS ? "Yes" : "No"]
                    " - dat += "Neutralize Wanted Criminals: [turret_flags & TURRET_FLAG_SHOOT_CRIMINALS ? "Yes" : "No"]
                    " - dat += "Neutralize All Non-Security and Non-Command Personnel: [turret_flags & TURRET_FLAG_SHOOT_ALL ? "Yes" : "No"]
                    " - dat += "Neutralize All Unidentified Life Signs: [turret_flags & TURRET_FLAG_SHOOT_ANOMALOUS ? "Yes" : "No"]
                    " - dat += "Neutralize All Non-Mindshielded Personnel: [turret_flags & TURRET_FLAG_SHOOT_UNSHIELDED ? "Yes" : "No"]
                    " - dat += "Neutralize All Cyborgs: [turret_flags & TURRET_FLAG_SHOOT_BORGS ? "Yes" : "No"]
                    " - dat += "Ignore Heads Of Staff: [turret_flags & TURRET_FLAG_SHOOT_HEADS ? "No" : "Yes"]
                    " - if(issilicon(user)) - if(!manual_control) - var/mob/living/silicon/S = user - if(S.hack_software) - dat += "Assume direct control : Manual Control
                    " - else - dat += "Warning! Remote control protocol enabled.
                    " - - - var/datum/browser/popup = new(user, "autosec", "Automatic Portable Turret Installation", 300, 300) - popup.set_content(dat) - popup.open() - -/obj/machinery/porta_turret/Topic(href, href_list) - if(..()) - return - usr.set_machine(src) - add_fingerprint(usr) - - if(href_list["power"] && !locked) - if(anchored) //you can't turn a turret on/off if it's not anchored/secured - on = !on //toggle on/off - else - to_chat(usr, "It has to be secured first!") - interact(usr) - return - - if(href_list["operation"]) - switch(href_list["operation"]) //toggles customizable behavioural protocols - if("authweapon") - turret_flags ^= TURRET_FLAG_AUTH_WEAPONS - if("shootcriminals") - turret_flags ^= TURRET_FLAG_SHOOT_CRIMINALS - if("shootall") - turret_flags ^= TURRET_FLAG_SHOOT_ALL - if("checkxenos") - turret_flags ^= TURRET_FLAG_SHOOT_ANOMALOUS - if("checkloyal") - turret_flags ^= TURRET_FLAG_SHOOT_UNSHIELDED - if ("shootborgs") - turret_flags ^= TURRET_FLAG_SHOOT_BORGS - if ("shootheads") - turret_flags ^= TURRET_FLAG_SHOOT_HEADS - if("manual") - if(issilicon(usr) && !manual_control) - give_control(usr) - - interact(usr) - -/obj/machinery/porta_turret/power_change() - . = ..() - if(!anchored || (machine_stat & BROKEN) || !powered()) - update_icon() - remove_control() - -/obj/machinery/porta_turret/attackby(obj/item/I, mob/user, params) - if(machine_stat & BROKEN) - if(I.tool_behaviour == TOOL_CROWBAR) - //If the turret is destroyed, you can remove it with a crowbar to - //try and salvage its components - to_chat(user, "You begin prying the metal coverings off...") - if(I.use_tool(src, user, 20)) - if(prob(70)) - if(stored_gun) - stored_gun.forceMove(loc) - stored_gun = null - to_chat(user, "You remove the turret and salvage some components.") - if(prob(50)) - new /obj/item/stack/sheet/metal(loc, rand(1,4)) - if(prob(50)) - new /obj/item/assembly/prox_sensor(loc) - else - to_chat(user, "You remove the turret but did not manage to salvage anything.") - qdel(src) - - else if((I.tool_behaviour == TOOL_WRENCH) && (!on)) - if(raised) - return - - //This code handles moving the turret around. After all, it's a portable turret! - if(!anchored && !isinspace()) - setAnchored(TRUE) - invisibility = INVISIBILITY_MAXIMUM - update_icon() - to_chat(user, "You secure the exterior bolts on the turret.") - if(has_cover) - cover = new /obj/machinery/porta_turret_cover(loc) //create a new turret. While this is handled in process(), this is to workaround a bug where the turret becomes invisible for a split second - cover.parent_turret = src //make the cover's parent src - else if(anchored) - setAnchored(FALSE) - to_chat(user, "You unsecure the exterior bolts on the turret.") - power_change() - invisibility = 0 - qdel(cover) //deletes the cover, and the turret instance itself becomes its own cover. - - else if(I.GetID()) - //Behavior lock/unlock mangement - if(allowed(user)) - locked = !locked - to_chat(user, "Controls are now [locked ? "locked" : "unlocked"].") - else - to_chat(user, "Access denied.") - else if(I.tool_behaviour == TOOL_MULTITOOL && !locked) - if(!multitool_check_buffer(user, I)) - return - var/obj/item/multitool/M = I - M.buffer = src - to_chat(user, "You add [src] to multitool buffer.") - else - return ..() - -/obj/machinery/porta_turret/emag_act(mob/user) - if(obj_flags & EMAGGED) - return - to_chat(user, "You short out [src]'s threat assessment circuits.") - audible_message("[src] hums oddly...") - obj_flags |= EMAGGED - controllock = TRUE - on = FALSE //turns off the turret temporarily - update_icon() - //6 seconds for the traitor to gtfo of the area before the turret decides to ruin his shit - addtimer(VARSET_CALLBACK(src, on, TRUE), 6 SECONDS) - //turns it back on. The cover popUp() popDown() are automatically called in process(), no need to define it here - - -/obj/machinery/porta_turret/emp_act(severity) - . = ..() - if (. & EMP_PROTECT_SELF) - return - if(on) - //if the turret is on, the EMP no matter how severe disables the turret for a while - //and scrambles its settings, with a slight chance of having an emag effect - if(prob(50)) - turret_flags |= TURRET_FLAG_SHOOT_CRIMINALS - if(prob(50)) - turret_flags |= TURRET_FLAG_AUTH_WEAPONS - if(prob(20)) - turret_flags |= TURRET_FLAG_SHOOT_ALL // Shooting everyone is a pretty big deal, so it's least likely to get turned on - - on = FALSE - remove_control() - - addtimer(VARSET_CALLBACK(src, on, TRUE), rand(60,600)) - -/obj/machinery/porta_turret/take_damage(damage, damage_type = BRUTE, damage_flag = 0, sound_effect = 1) - . = ..() - if(. && obj_integrity > 0) //damage received - if(prob(30)) - spark_system.start() - if(on && !(turret_flags & TURRET_FLAG_SHOOT_ALL_REACT) && !(obj_flags & EMAGGED)) - turret_flags |= TURRET_FLAG_SHOOT_ALL_REACT - addtimer(CALLBACK(src, .proc/reset_attacked), 60) - -/obj/machinery/porta_turret/proc/reset_attacked() - turret_flags &= ~TURRET_FLAG_SHOOT_ALL_REACT - -/obj/machinery/porta_turret/deconstruct(disassembled = TRUE) - qdel(src) - -/obj/machinery/porta_turret/obj_break(damage_flag) - . = ..() - if(.) - power_change() - invisibility = 0 - spark_system.start() //creates some sparks because they look cool - qdel(cover) //deletes the cover - no need on keeping it there! - - - -/obj/machinery/porta_turret/process() - //the main machinery process - if(cover == null && anchored) //if it has no cover and is anchored - if(machine_stat & BROKEN) //if the turret is borked - qdel(cover) //delete its cover, assuming it has one. Workaround for a pesky little bug - else - if(has_cover) - cover = new /obj/machinery/porta_turret_cover(loc) //if the turret has no cover and is anchored, give it a cover - cover.parent_turret = src //assign the cover its parent_turret, which would be this (src) - - if(!on || (machine_stat & (NOPOWER|BROKEN)) || manual_control) - return - - var/list/targets = list() - for(var/mob/A in view(scan_range, base)) - if(A.invisibility > SEE_INVISIBLE_LIVING) - continue - - if(turret_flags & TURRET_FLAG_SHOOT_ANOMALOUS)//if it's set to check for simple animals - if(isanimal(A)) - var/mob/living/simple_animal/SA = A - if(SA.stat || in_faction(SA)) //don't target if dead or in faction - continue - targets += SA - continue - - if(issilicon(A)) - var/mob/living/silicon/sillycone = A - - if(ispAI(A)) - continue - - if((turret_flags & TURRET_FLAG_SHOOT_BORGS) && sillycone.stat != DEAD && iscyborg(sillycone)) - targets += sillycone - continue - - if(sillycone.stat || in_faction(sillycone)) - continue - - if(iscyborg(sillycone)) - var/mob/living/silicon/robot/sillyconerobot = A - if(LAZYLEN(faction) && (ROLE_SYNDICATE in faction) && sillyconerobot.emagged == TRUE) - continue - - if(iscarbon(A)) - var/mob/living/carbon/C = A - //If not emagged, only target carbons that can use items - if(mode != TURRET_LETHAL && (C.stat || C.handcuffed || !(C.mobility_flags & MOBILITY_USE))) - continue - - //If emagged, target all but dead carbons - if(mode == TURRET_LETHAL && C.stat == DEAD) - continue - - //if the target is a human and not in our faction, analyze threat level - if(ishuman(C) && !in_faction(C)) - - if(assess_perp(C) >= 4) - targets += C - else if(turret_flags & TURRET_FLAG_SHOOT_ANOMALOUS) //non humans who are not simple animals (xenos etc) - if(!in_faction(C)) - targets += C - for(var/A in GLOB.mechas_list) - if((get_dist(A, base) < scan_range) && can_see(base, A, scan_range)) - var/obj/mecha/Mech = A - if(Mech.occupant && !in_faction(Mech.occupant)) //If there is a user and they're not in our faction - if(assess_perp(Mech.occupant) >= 4) - targets += Mech - // Wasp start - for(var/A in GLOB.spacepods_list) - if((get_dist(A, base) < scan_range) && can_see(base, A, scan_range)) - var/obj/spacepod/SP = A - if(SP.pilot && !in_faction(SP.pilot)) - if(assess_perp(SP.pilot) >= 4) - targets += SP - // Wasp end - - if((turret_flags & TURRET_FLAG_SHOOT_ANOMALOUS) && GLOB.blobs.len && (mode == TURRET_LETHAL)) - for(var/obj/structure/blob/B in view(scan_range, base)) - targets += B - - if(targets.len) - tryToShootAt(targets) - else if(!always_up) - popDown() // no valid targets, close the cover - -/obj/machinery/porta_turret/proc/tryToShootAt(list/atom/movable/targets) - while(targets.len > 0) - var/atom/movable/M = pick(targets) - targets -= M - if(target(M)) - return 1 - - -/obj/machinery/porta_turret/proc/popUp() //pops the turret up - if(!anchored) - return - if(raising || raised) - return - if(machine_stat & BROKEN) - return - invisibility = 0 - raising = 1 - if(cover) - flick("popup", cover) - sleep(POPUP_ANIM_TIME) - raising = 0 - if(cover) - cover.icon_state = "openTurretCover" - raised = 1 - layer = MOB_LAYER - -/obj/machinery/porta_turret/proc/popDown() //pops the turret down - if(raising || !raised) - return - if(machine_stat & BROKEN) - return - layer = OBJ_LAYER - raising = 1 - if(cover) - flick("popdown", cover) - sleep(POPDOWN_ANIM_TIME) - raising = 0 - if(cover) - cover.icon_state = "turretCover" - raised = 0 - invisibility = 2 - update_icon() - -/obj/machinery/porta_turret/proc/assess_perp(mob/living/carbon/human/perp) - var/threatcount = 0 //the integer returned - - if(obj_flags & EMAGGED) - return 10 //if emagged, always return 10. - - if((turret_flags & (TURRET_FLAG_SHOOT_ALL | TURRET_FLAG_SHOOT_ALL_REACT)) && !allowed(perp)) - //if the turret has been attacked or is angry, target all non-sec people - if(!allowed(perp)) - return 10 - - if(turret_flags & TURRET_FLAG_AUTH_WEAPONS) //check for weapon authorization - if(isnull(perp.wear_id) || istype(perp.wear_id.GetID(), /obj/item/card/id/syndicate)) - - if(allowed(perp)) //if the perp has security access, return 0 - return 0 - if(perp.is_holding_item_of_type(/obj/item/gun) || perp.is_holding_item_of_type(/obj/item/melee/baton)) - threatcount += 4 - - if(istype(perp.belt, /obj/item/gun) || istype(perp.belt, /obj/item/melee/baton)) - threatcount += 2 - - if(turret_flags & TURRET_FLAG_SHOOT_CRIMINALS) //if the turret can check the records, check if they are set to *Arrest* on records - var/perpname = perp.get_face_name(perp.get_id_name()) - var/datum/data/record/R = find_record("name", perpname, GLOB.data_core.security) - if(!R || (R.fields["criminal"] == "*Arrest*")) - threatcount += 4 - - if((turret_flags & TURRET_FLAG_SHOOT_UNSHIELDED) && (!HAS_TRAIT(perp, TRAIT_MINDSHIELD))) - threatcount += 4 - - // If we aren't shooting heads then return a threatcount of 0 - if (!(turret_flags & TURRET_FLAG_SHOOT_HEADS) && (perp.get_assignment() in GLOB.command_positions)) - return 0 - - return threatcount - -/obj/machinery/porta_turret/proc/in_faction(mob/target) - for(var/faction1 in faction) - if(faction1 in target.faction) - return TRUE - return FALSE - -/obj/machinery/porta_turret/proc/target(atom/movable/target) - if(target) - popUp() //pop the turret up if it's not already up. - setDir(get_dir(base, target))//even if you can't shoot, follow the target - shootAt(target) - return 1 - return - -/obj/machinery/porta_turret/proc/shootAt(atom/movable/target) - if(!raised) //the turret has to be raised in order to fire - makes sense, right? - return - - if(!(obj_flags & EMAGGED)) //if it hasn't been emagged, cooldown before shooting again - if(last_fired + shot_delay > world.time) - return - last_fired = world.time - - var/turf/T = get_turf(src) - var/turf/U = get_turf(target) - if(!istype(T) || !istype(U)) - return - - //Wall turrets will try to find adjacent empty turf to shoot from to cover full arc - if(T.density) - if(wall_turret_direction) - var/turf/closer = get_step(T,wall_turret_direction) - if(istype(closer) && !is_blocked_turf(closer) && T.Adjacent(closer)) - T = closer - else - var/target_dir = get_dir(T,target) - for(var/d in list(0,-45,45)) - var/turf/closer = get_step(T,turn(target_dir,d)) - if(istype(closer) && !is_blocked_turf(closer) && T.Adjacent(closer)) - T = closer - break - - update_icon() - var/obj/projectile/A - //any emagged turrets drains 2x power and uses a different projectile? - if(mode == TURRET_STUN) - use_power(reqpower) - A = new stun_projectile(T) - playsound(loc, stun_projectile_sound, 75, TRUE) - else - use_power(reqpower * 2) - A = new lethal_projectile(T) - playsound(loc, lethal_projectile_sound, 75, TRUE) - - - //Shooting Code: - A.preparePixelProjectile(target, T) - A.firer = src - A.fired_from = src - A.fire() - return A - -/obj/machinery/porta_turret/proc/setState(on, mode, shoot_cyborgs) - if(controllock) - return - - shoot_cyborgs ? (turret_flags |= TURRET_FLAG_SHOOT_BORGS) : (turret_flags &= ~TURRET_FLAG_SHOOT_BORGS) - src.on = on - if(!on) - popDown() - src.mode = mode - power_change() - - -/datum/action/turret_toggle - name = "Toggle Mode" - icon_icon = 'icons/mob/actions/actions_mecha.dmi' - button_icon_state = "mech_cycle_equip_off" - -/datum/action/turret_toggle/Trigger() - var/obj/machinery/porta_turret/P = target - if(!istype(P)) - return - P.setState(P.on,!P.mode) - -/datum/action/turret_quit - name = "Release Control" - icon_icon = 'icons/mob/actions/actions_mecha.dmi' - button_icon_state = "mech_eject" - -/datum/action/turret_quit/Trigger() - var/obj/machinery/porta_turret/P = target - if(!istype(P)) - return - P.remove_control(FALSE) - -/obj/machinery/porta_turret/proc/give_control(mob/A) - if(manual_control || !can_interact(A)) - return FALSE - remote_controller = A - if(!quit_action) - quit_action = new(src) - quit_action.Grant(remote_controller) - if(!toggle_action) - toggle_action = new(src) - toggle_action.Grant(remote_controller) - remote_controller.reset_perspective(src) - remote_controller.click_intercept = src - manual_control = TRUE - always_up = TRUE - popUp() - return TRUE - -/obj/machinery/porta_turret/proc/remove_control(warning_message = TRUE) - if(!manual_control) - return FALSE - if(remote_controller) - if(warning_message) - to_chat(remote_controller, "Your uplink to [src] has been severed!") - quit_action.Remove(remote_controller) - toggle_action.Remove(remote_controller) - remote_controller.click_intercept = null - remote_controller.reset_perspective() - always_up = initial(always_up) - manual_control = FALSE - remote_controller = null - return TRUE - -/obj/machinery/porta_turret/proc/InterceptClickOn(mob/living/caller, params, atom/A) - if(!manual_control) - return FALSE - if(!can_interact(caller)) - remove_control() - return FALSE - log_combat(caller,A,"fired with manual turret control at") - target(A) - return TRUE - -/obj/machinery/porta_turret/syndicate - installation = null - always_up = 1 - use_power = NO_POWER_USE - has_cover = 0 - scan_range = 9 - req_access = list(ACCESS_SYNDICATE) - mode = TURRET_LETHAL - stun_projectile = /obj/projectile/bullet - lethal_projectile = /obj/projectile/bullet - lethal_projectile_sound = 'sound/weapons/gun/pistol/shot.ogg' - stun_projectile_sound = 'sound/weapons/gun/pistol/shot.ogg' - icon_state = "syndie_off" - base_icon_state = "syndie" - faction = list(ROLE_SYNDICATE) - desc = "A ballistic machine gun auto-turret." - -/obj/machinery/porta_turret/syndicate/ComponentInitialize() - . = ..() - AddComponent(/datum/component/empprotection, EMP_PROTECT_SELF | EMP_PROTECT_WIRES) - -/obj/machinery/porta_turret/syndicate/setup() - return - -/obj/machinery/porta_turret/syndicate/assess_perp(mob/living/carbon/human/perp) - return 10 //Syndicate turrets shoot everything not in their faction - -/obj/machinery/porta_turret/syndicate/energy - icon_state = "standard_lethal" - base_icon_state = "standard" - stun_projectile = /obj/projectile/energy/electrode - stun_projectile_sound = 'sound/weapons/taser.ogg' - lethal_projectile = /obj/projectile/beam/laser - lethal_projectile_sound = 'sound/weapons/laser.ogg' - desc = "An energy blaster auto-turret." - -/obj/machinery/porta_turret/syndicate/energy/heavy - name = "syndicate heavy laser turret" - desc = "A heavy laser auto-turret." - icon_state = "standard_lethal" - base_icon_state = "standard" - stun_projectile = /obj/projectile/energy/electrode - stun_projectile_sound = 'sound/weapons/taser.ogg' - lethal_projectile = /obj/projectile/beam/laser/heavylaser - lethal_projectile_sound = 'sound/weapons/lasercannonfire.ogg' - desc = "An energy blaster auto-turret." - -/obj/machinery/porta_turret/syndicate/energy/raven - stun_projectile = /obj/projectile/beam/laser - stun_projectile_sound = 'sound/weapons/laser.ogg' - faction = list("neutral","silicon","turret") - - -/obj/machinery/porta_turret/syndicate/pod - name = "syndicate semi-auto turret" - desc = "A ballistic semi-automatic auto-turret." - integrity_failure = 0.5 - max_integrity = 40 - stun_projectile = /obj/projectile/bullet/syndicate_turret - lethal_projectile = /obj/projectile/bullet/syndicate_turret - -/obj/machinery/porta_turret/syndicate/shuttle - name = "syndicate penetrator turret" - desc = "A ballistic penetrator auto-turret." - scan_range = 9 - shot_delay = 3 - stun_projectile = /obj/projectile/bullet/p50/penetrator/shuttle - lethal_projectile = /obj/projectile/bullet/p50/penetrator/shuttle - lethal_projectile_sound = 'sound/weapons/gun/smg/shot.ogg' - stun_projectile_sound = 'sound/weapons/gun/smg/shot.ogg' - armor = list("melee" = 50, "bullet" = 30, "laser" = 30, "energy" = 30, "bomb" = 80, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 90) - -/obj/machinery/porta_turret/syndicate/shuttle/target(atom/movable/target) - if(target) - setDir(get_dir(base, target))//even if you can't shoot, follow the target - shootAt(target) - addtimer(CALLBACK(src, .proc/shootAt, target), 5) - addtimer(CALLBACK(src, .proc/shootAt, target), 10) - addtimer(CALLBACK(src, .proc/shootAt, target), 15) - return TRUE - -/obj/machinery/porta_turret/ai - faction = list("silicon") - turret_flags = TURRET_FLAG_SHOOT_CRIMINALS | TURRET_FLAG_SHOOT_ANOMALOUS | TURRET_FLAG_SHOOT_HEADS - -/obj/machinery/porta_turret/ai/assess_perp(mob/living/carbon/human/perp) - return 10 //AI turrets shoot at everything not in their faction - -/obj/machinery/porta_turret/aux_base - name = "perimeter defense turret" - desc = "A plasma beam turret calibrated to defend outposts against non-humanoid fauna. It is more effective when exposed to the environment." - installation = null - lethal_projectile = /obj/projectile/plasma/turret - lethal_projectile_sound = 'sound/weapons/plasma_cutter.ogg' - mode = TURRET_LETHAL //It would be useless in stun mode anyway - faction = list("neutral","silicon","turret") //Minebots, medibots, etc that should not be shot. - -/obj/machinery/porta_turret/aux_base/assess_perp(mob/living/carbon/human/perp) - return 0 //Never shoot humanoids. You are on your own if Ashwalkers or the like attack! - -/obj/machinery/porta_turret/aux_base/setup() - return - -/obj/machinery/porta_turret/aux_base/interact(mob/user) //Controlled solely from the base console. - return - -/obj/machinery/porta_turret/aux_base/Initialize() - . = ..() - cover.name = name - cover.desc = desc - -/obj/machinery/porta_turret/centcom_shuttle - installation = null - max_integrity = 260 - always_up = 1 - use_power = NO_POWER_USE - has_cover = 0 - scan_range = 9 - stun_projectile = /obj/projectile/beam/laser - lethal_projectile = /obj/projectile/beam/laser - lethal_projectile_sound = 'sound/weapons/plasma_cutter.ogg' - stun_projectile_sound = 'sound/weapons/plasma_cutter.ogg' - icon_state = "syndie_off" - base_icon_state = "syndie" - faction = list("neutral","silicon","turret") - mode = TURRET_LETHAL - -/obj/machinery/porta_turret/centcom_shuttle/ComponentInitialize() - . = ..() - AddComponent(/datum/component/empprotection, EMP_PROTECT_SELF | EMP_PROTECT_WIRES) - -/obj/machinery/porta_turret/centcom_shuttle/assess_perp(mob/living/carbon/human/perp) - return 0 - -/obj/machinery/porta_turret/centcom_shuttle/setup() - return - -/obj/machinery/porta_turret/centcom_shuttle/weak - max_integrity = 120 - integrity_failure = 0.5 - name = "Old Laser Turret" - desc = "A turret built with substandard parts and run down further with age. Still capable of delivering lethal lasers to the odd space carp, but not much else." - stun_projectile = /obj/projectile/beam/weak/penetrator - lethal_projectile = /obj/projectile/beam/weak/penetrator - faction = list("neutral","silicon","turret") - -//////////////////////// -//Turret Control Panel// -//////////////////////// - -/obj/machinery/turretid - name = "turret control panel" - desc = "Used to control a room's automated defenses." - icon = 'icons/obj/machines/turret_control.dmi' - icon_state = "control_standby" - density = FALSE - var/enabled = 1 - var/lethal = 0 - var/locked = TRUE - var/control_area = null //can be area name, path or nothing. - var/ailock = 0 // AI cannot use this - var/shoot_cyborgs = FALSE - req_access = list(ACCESS_AI_UPLOAD) - var/list/obj/machinery/porta_turret/turrets = list() - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - -/obj/machinery/turretid/Initialize(mapload, ndir = 0, built = 0) - . = ..() - if(built) - setDir(ndir) - locked = FALSE - pixel_x = (dir & 3)? 0 : (dir == 4 ? -24 : 24) - pixel_y = (dir & 3)? (dir ==1 ? -24 : 24) : 0 - power_change() //Checks power and initial settings - -/obj/machinery/turretid/Destroy() - turrets.Cut() - return ..() - -/obj/machinery/turretid/Initialize(mapload) //map-placed turrets autolink turrets - . = ..() - if(!mapload) - return - - if(control_area) - control_area = get_area_instance_from_text(control_area) - if(control_area == null) - control_area = get_area(src) - stack_trace("Bad control_area path for [src], [src.control_area]") - else if(!control_area) - control_area = get_area(src) - - for(var/obj/machinery/porta_turret/T in control_area) - turrets |= T - T.cp = src - -/obj/machinery/turretid/examine(mob/user) - . += ..() - if(issilicon(user) && (!machine_stat & BROKEN)) - . += {"Ctrl-click [src] to [ enabled ? "disable" : "enable"] turrets. - Alt-click [src] to set turrets to [ lethal ? "stun" : "kill"]."} - -/obj/machinery/turretid/attackby(obj/item/I, mob/user, params) - if(machine_stat & BROKEN) - return - - if(I.tool_behaviour == TOOL_MULTITOOL) - if(!multitool_check_buffer(user, I)) - return - var/obj/item/multitool/M = I - if(M.buffer && istype(M.buffer, /obj/machinery/porta_turret)) - turrets |= M.buffer - to_chat(user, "You link \the [M.buffer] with \the [src].") - return - - if (issilicon(user)) - return attack_hand(user) - - if ( get_dist(src, user) == 0 ) // trying to unlock the interface - if (allowed(usr)) - if(obj_flags & EMAGGED) - to_chat(user, "The turret control is unresponsive!") - return - - locked = !locked - to_chat(user, "You [ locked ? "lock" : "unlock"] the panel.") - if (locked) - if (user.machine==src) - user.unset_machine() - user << browse(null, "window=turretid") - else - if (user.machine==src) - attack_hand(user) - else - to_chat(user, "Access denied.") - -/obj/machinery/turretid/emag_act(mob/user) - if(obj_flags & EMAGGED) - return - to_chat(user, "You short out the turret controls' access analysis module.") - obj_flags |= EMAGGED - locked = FALSE - if(user && user.machine == src) - attack_hand(user) - -/obj/machinery/turretid/attack_ai(mob/user) - if(!ailock || IsAdminGhost(user)) - return attack_hand(user) - else - to_chat(user, "There seems to be a firewall preventing you from accessing this device!") - -/obj/machinery/turretid/ui_interact(mob/user) - . = ..() - if ( get_dist(src, user) > 0 ) - if ( !(issilicon(user) || IsAdminGhost(user)) ) - to_chat(user, "You are too far away!") - user.unset_machine() - user << browse(null, "window=turretid") - return - - var/t = "" - - if(locked && !(issilicon(user) || IsAdminGhost(user))) - t += "
                    Swipe ID card to unlock interface
                    " - else - if(!issilicon(user) && !IsAdminGhost(user)) - t += "
                    Swipe ID card to lock interface
                    " - t += "Turrets [enabled?"activated":"deactivated"] - [enabled?"Disable":"Enable"]?
                    " - t += "Currently set for [lethal?"lethal":"stun repeatedly"] - Change to [lethal?"Stun repeatedly":"Lethal"]?
                    " - t += "Target Cyborgs [shoot_cyborgs?"Yes":"No"] - Change to [shoot_cyborgs?"Dont Shoot Borgs":"Shoot Borgs"]?
                    " - var/datum/browser/popup = new(user, "turretid", "Turret Control Panel ([get_area_name(src, TRUE)])") - popup.set_content(t) - popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) - popup.open() - -/obj/machinery/turretid/Topic(href, href_list) - if(..()) - return - if (locked) - if(!(issilicon(usr) || IsAdminGhost(usr))) - to_chat(usr, "Control panel is locked!") - return - if (href_list["toggleOn"]) - toggle_on(usr) - else if (href_list["toggleLethal"]) - toggle_lethal(usr) - else if (href_list["shoot_silicons"]) - shoot_silicons(usr) - attack_hand(usr) - -/obj/machinery/turretid/proc/toggle_lethal(mob/user) - lethal = !lethal - add_hiddenprint(user) - log_combat(user, src, "[lethal ? "enabled" : "disabled"] lethals on") - updateTurrets() - -/obj/machinery/turretid/proc/toggle_on(mob/user) - enabled = !enabled - add_hiddenprint(user) - log_combat(user, src, "[enabled ? "enabled" : "disabled"]") - updateTurrets() -/obj/machinery/turretid/proc/shoot_silicons(mob/user) - shoot_cyborgs = !shoot_cyborgs - add_hiddenprint(user) - log_combat(user, src, "[shoot_cyborgs ? "Shooting Borgs" : "Not Shooting Borgs"]") - updateTurrets() -/obj/machinery/turretid/proc/updateTurrets() - for (var/obj/machinery/porta_turret/aTurret in turrets) - aTurret.setState(enabled, lethal, shoot_cyborgs) - update_icon() - -/obj/machinery/turretid/update_icon_state() - if(machine_stat & NOPOWER) - icon_state = "control_off" - else if (enabled) - if (lethal) - icon_state = "control_kill" - else - icon_state = "control_stun" - else - icon_state = "control_standby" - -/obj/item/wallframe/turret_control - name = "turret control frame" - desc = "Used for building turret control panels." - icon_state = "apc" - result_path = /obj/machinery/turretid - custom_materials = list(/datum/material/iron=MINERAL_MATERIAL_AMOUNT) - -/obj/item/gun/proc/get_turret_properties() - . = list() - .["lethal_projectile"] = null - .["lethal_projectile_sound"] = null - .["stun_projectile"] = null - .["stun_projectile_sound"] = null - .["base_icon_state"] = "standard" - -/obj/item/gun/energy/get_turret_properties() - . = ..() - - var/obj/item/ammo_casing/primary_ammo = ammo_type[1] - - .["stun_projectile"] = initial(primary_ammo.projectile_type) - .["stun_projectile_sound"] = initial(primary_ammo.fire_sound) - - if(ammo_type.len > 1) - var/obj/item/ammo_casing/secondary_ammo = ammo_type[2] - .["lethal_projectile"] = initial(secondary_ammo.projectile_type) - .["lethal_projectile_sound"] = initial(secondary_ammo.fire_sound) - else - .["lethal_projectile"] = .["stun_projectile"] - .["lethal_projectile_sound"] = .["stun_projectile_sound"] - -/obj/item/gun/ballistic/get_turret_properties() - . = ..() - var/obj/item/ammo_box/mag = mag_type - var/obj/item/ammo_casing/primary_ammo = initial(mag.ammo_type) - - .["base_icon_state"] = "syndie" - .["stun_projectile"] = initial(primary_ammo.projectile_type) - .["stun_projectile_sound"] = initial(primary_ammo.fire_sound) - .["lethal_projectile"] = .["stun_projectile"] - .["lethal_projectile_sound"] = .["stun_projectile_sound"] - - -/obj/item/gun/energy/laser/bluetag/get_turret_properties() - . = ..() - .["stun_projectile"] = /obj/projectile/beam/lasertag/bluetag - .["lethal_projectile"] = /obj/projectile/beam/lasertag/bluetag - .["base_icon_state"] = "blue" - .["shot_delay"] = 30 - .["team_color"] = "blue" - -/obj/item/gun/energy/laser/redtag/get_turret_properties() - . = ..() - .["stun_projectile"] = /obj/projectile/beam/lasertag/redtag - .["lethal_projectile"] = /obj/projectile/beam/lasertag/redtag - .["base_icon_state"] = "red" - .["shot_delay"] = 30 - .["team_color"] = "red" - -/obj/item/gun/energy/e_gun/turret/get_turret_properties() - . = ..() - -/obj/machinery/porta_turret/lasertag - req_access = list(ACCESS_MAINT_TUNNELS, ACCESS_THEATRE) - turret_flags = TURRET_FLAG_AUTH_WEAPONS - var/team_color - -/obj/machinery/porta_turret/lasertag/assess_perp(mob/living/carbon/human/perp) - . = 0 - if(team_color == "blue") //Lasertag turrets target the opposing team, how great is that? -Sieve - . = 0 //But does not target anyone else - if(istype(perp.wear_suit, /obj/item/clothing/suit/redtag)) - . += 4 - if(perp.is_holding_item_of_type(/obj/item/gun/energy/laser/redtag)) - . += 4 - if(istype(perp.belt, /obj/item/gun/energy/laser/redtag)) - . += 2 - - if(team_color == "red") - . = 0 - if(istype(perp.wear_suit, /obj/item/clothing/suit/bluetag)) - . += 4 - if(perp.is_holding_item_of_type(/obj/item/gun/energy/laser/bluetag)) - . += 4 - if(istype(perp.belt, /obj/item/gun/energy/laser/bluetag)) - . += 2 - -/obj/machinery/porta_turret/lasertag/setup(obj/item/gun/gun) - var/list/properties = ..() - if(properties["team_color"]) - team_color = properties["team_color"] - -/obj/machinery/porta_turret/lasertag/ui_interact(mob/user) - . = ..() - if(ishuman(user)) - var/mob/living/carbon/human/H = user - if(team_color == "blue" && istype(H.wear_suit, /obj/item/clothing/suit/redtag)) - return - if(team_color == "red" && istype(H.wear_suit, /obj/item/clothing/suit/bluetag)) - return - - var/dat = "Status: [on ? "On" : "Off"]" - - var/datum/browser/popup = new(user, "autosec", "Automatic Portable Turret Installation", 300, 300) - popup.set_content(dat) - popup.open() - -//lasertag presets -/obj/machinery/porta_turret/lasertag/red - installation = /obj/item/gun/energy/laser/redtag - team_color = "red" - -/obj/machinery/porta_turret/lasertag/blue - installation = /obj/item/gun/energy/laser/bluetag - team_color = "blue" - -/obj/machinery/porta_turret/lasertag/bullet_act(obj/projectile/P) - . = ..() - if(on) - if(team_color == "blue") - if(istype(P, /obj/projectile/beam/lasertag/redtag)) - on = FALSE - addtimer(VARSET_CALLBACK(src, on, TRUE), 10 SECONDS) - else if(team_color == "red") - if(istype(P, /obj/projectile/beam/lasertag/bluetag)) - on = FALSE - addtimer(VARSET_CALLBACK(src, on, TRUE), 10 SECONDS) +#define TURRET_STUN 0 +#define TURRET_LETHAL 1 + +#define POPUP_ANIM_TIME 5 +#define POPDOWN_ANIM_TIME 5 //Be sure to change the icon animation at the same time or it'll look bad + +#define TURRET_FLAG_SHOOT_ALL_REACT (1<<0) // The turret gets pissed off and shoots at people nearby (unless they have sec access!) +#define TURRET_FLAG_AUTH_WEAPONS (1<<1) // Checks if it can shoot people that have a weapon they aren't authorized to have +#define TURRET_FLAG_SHOOT_CRIMINALS (1<<2) // Checks if it can shoot people that are wanted +#define TURRET_FLAG_SHOOT_ALL (1<<3) // The turret gets pissed off and shoots at people nearby (unless they have sec access!) +#define TURRET_FLAG_SHOOT_ANOMALOUS (1<<4) // Checks if it can shoot at unidentified lifeforms (ie xenos) +#define TURRET_FLAG_SHOOT_UNSHIELDED (1<<5) // Checks if it can shoot people that aren't mindshielded and who arent heads +#define TURRET_FLAG_SHOOT_BORGS (1<<6) // checks if it can shoot cyborgs +#define TURRET_FLAG_SHOOT_HEADS (1<<7) // checks if it can shoot at heads of staff + +/obj/machinery/porta_turret + name = "turret" + icon = 'icons/obj/turrets.dmi' + icon_state = "turretCover" + layer = OBJ_LAYER + invisibility = INVISIBILITY_OBSERVER //the turret is invisible if it's inside its cover + density = TRUE + desc = "A covered turret that shoots at its enemies." + use_power = IDLE_POWER_USE //this turret uses and requires power + idle_power_usage = 50 //when inactive, this turret takes up constant 50 Equipment power + active_power_usage = 300 //when active, this turret takes up constant 300 Equipment power + req_access = list(ACCESS_SECURITY) /// Only people with Security access + power_channel = AREA_USAGE_EQUIP //drains power from the EQUIPMENT channel + max_integrity = 160 //the turret's health + integrity_failure = 0.5 + armor = list("melee" = 50, "bullet" = 30, "laser" = 30, "energy" = 30, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 90) + /// Base turret icon state + var/base_icon_state = "standard" + /// Scan range of the turret for locating targets + var/scan_range = 7 + /// For turrets inside other objects + var/atom/base = null + /// If the turret cover is "open" and the turret is raised + var/raised = FALSE + /// If the turret is currently opening or closing its cover + var/raising = FALSE + /// If the turret's behaviour control access is locked + var/locked = TRUE + /// If the turret responds to control panels + var/controllock = FALSE + /// The type of weapon installed by default + var/installation = /obj/item/gun/energy/e_gun/turret + /// What stored gun is in the turret + var/obj/item/gun/stored_gun = null + /// The charge of the gun when retrieved from wreckage + var/gun_charge = 0 + /// In which mode is turret in, stun or lethal + var/mode = TURRET_STUN + /// Stun mode projectile type + var/stun_projectile = null + /// Sound of stun projectile + var/stun_projectile_sound + /// Lethal mode projectile type + var/lethal_projectile = null + /// Sound of lethal projectile + var/lethal_projectile_sound + /// Power needed per shot + var/reqpower = 500 + /// Will stay active + var/always_up = FALSE + /// Hides the cover + var/has_cover = TRUE + /// The cover that is covering this turret + var/obj/machinery/porta_turret_cover/cover = null + /// World.time the turret last fired + var/last_fired = 0 + /// Ticks until next shot (1.5 ?) + var/shot_delay = 15 + /// Turret flags about who is turret allowed to shoot + var/turret_flags = TURRET_FLAG_SHOOT_CRIMINALS | TURRET_FLAG_SHOOT_ANOMALOUS + /// Determines if the turret is on + var/on = TRUE + /// Same faction mobs will never be shot at, no matter the other settings + var/list/faction = list("turret") + /// The spark system, used for generating... sparks? + var/datum/effect_system/spark_spread/spark_system + /// Linked turret control panel of the turret + var/obj/machinery/turretid/cp = null + /// The turret will try to shoot from a turf in that direction when in a wall + var/wall_turret_direction + /// If the turret is manually controlled + var/manual_control = FALSE + /// Action button holder for quitting manual control + var/datum/action/turret_quit/quit_action + /// Action button holder for switching between turret modes when manually controlling + var/datum/action/turret_toggle/toggle_action + /// Mob that is remotely controlling the turret + var/mob/remote_controller + +/obj/machinery/porta_turret/Initialize() + . = ..() + if(!base) + base = src + update_icon() + //Sets up a spark system + spark_system = new /datum/effect_system/spark_spread + spark_system.set_up(5, 0, src) + spark_system.attach(src) + + setup() + if(has_cover) + cover = new /obj/machinery/porta_turret_cover(loc) + cover.parent_turret = src + var/mutable_appearance/base = mutable_appearance('icons/obj/turrets.dmi', "basedark") + base.layer = NOT_HIGH_OBJ_LAYER + underlays += base + if(!has_cover) + INVOKE_ASYNC(src, .proc/popUp) + +/obj/machinery/porta_turret/proc/toggle_on(var/set_to) + var/current = on + if (!isnull(set_to)) + on = set_to + else + on = !on + if (current != on) + check_should_process() + if (!on) + popDown() + +/obj/machinery/porta_turret/proc/check_should_process() + if (datum_flags & DF_ISPROCESSING) + if (!on || !anchored || (machine_stat & BROKEN) || !powered()) + end_processing() + else + if (on && anchored && !(machine_stat & BROKEN) && powered()) + begin_processing() + +/obj/machinery/porta_turret/update_icon_state() + if(!anchored) + icon_state = "turretCover" + return + if(machine_stat & BROKEN) + icon_state = "[base_icon_state]_broken" + else + if(powered()) + if(on && raised) + switch(mode) + if(TURRET_STUN) + icon_state = "[base_icon_state]_stun" + if(TURRET_LETHAL) + icon_state = "[base_icon_state]_lethal" + else + icon_state = "[base_icon_state]_off" + else + icon_state = "[base_icon_state]_unpowered" + +/obj/machinery/porta_turret/proc/setup(obj/item/gun/turret_gun) + if(stored_gun) + qdel(stored_gun) + stored_gun = null + + if(installation && !turret_gun) + stored_gun = new installation(src) + else if (turret_gun) + stored_gun = turret_gun + + var/list/gun_properties = stored_gun.get_turret_properties() + + //required properties + stun_projectile = gun_properties["stun_projectile"] + stun_projectile_sound = gun_properties["stun_projectile_sound"] + lethal_projectile = gun_properties["lethal_projectile"] + lethal_projectile_sound = gun_properties["lethal_projectile_sound"] + base_icon_state = gun_properties["base_icon_state"] + + //optional properties + if(gun_properties["shot_delay"]) + shot_delay = gun_properties["shot_delay"] + if(gun_properties["reqpower"]) + reqpower = gun_properties["reqpower"] + + update_icon() + return gun_properties + +/obj/machinery/porta_turret/Destroy() + //deletes its own cover with it + QDEL_NULL(cover) + base = null + if(cp) + cp.turrets -= src + cp = null + QDEL_NULL(stored_gun) + QDEL_NULL(spark_system) + remove_control() + return ..() + +/obj/machinery/porta_turret/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "PortableTurret", name) + ui.open() + +/obj/machinery/porta_turret/ui_data(mob/user) + var/list/data = list( + "locked" = locked, + "on" = on, + "check_weapons" = turret_flags & TURRET_FLAG_AUTH_WEAPONS, + "neutralize_criminals" = turret_flags & TURRET_FLAG_SHOOT_CRIMINALS, + "neutralize_all" = turret_flags & TURRET_FLAG_SHOOT_ALL, + "neutralize_unidentified" = turret_flags & TURRET_FLAG_SHOOT_ANOMALOUS, + "neutralize_nonmindshielded" = turret_flags & TURRET_FLAG_SHOOT_UNSHIELDED, + "neutralize_cyborgs" = turret_flags & TURRET_FLAG_SHOOT_BORGS, + "ignore_heads" = turret_flags & TURRET_FLAG_SHOOT_HEADS, + "manual_control" = manual_control, + "silicon_user" = FALSE, + "allow_manual_control" = FALSE, + "lasertag_turret" = istype(src, /obj/machinery/porta_turret/lasertag), + ) + if(issilicon(user)) + data["silicon_user"] = TRUE + if(!manual_control) + var/mob/living/silicon/S = user + if(S.hack_software) + data["allow_manual_control"] = TRUE + return data + +/obj/machinery/porta_turret/ui_act(action, list/params) + . = ..() + if(.) + return + + switch(action) + if("power") + if(anchored) + toggle_on() + return TRUE + else + to_chat(usr, "It has to be secured first!") + if("authweapon") + turret_flags ^= TURRET_FLAG_AUTH_WEAPONS + return TRUE + if("shootcriminals") + turret_flags ^= TURRET_FLAG_SHOOT_CRIMINALS + return TRUE + if("shootall") + turret_flags ^= TURRET_FLAG_SHOOT_ALL + return TRUE + if("checkxenos") + turret_flags ^= TURRET_FLAG_SHOOT_ANOMALOUS + return TRUE + if("checkloyal") + turret_flags ^= TURRET_FLAG_SHOOT_UNSHIELDED + return TRUE + if("shootborgs") + turret_flags ^= TURRET_FLAG_SHOOT_BORGS + return TRUE + if("shootheads") + turret_flags ^= TURRET_FLAG_SHOOT_HEADS + return TRUE + if("manual") + if(!issilicon(usr)) + return + give_control(usr) + return TRUE + +/obj/machinery/porta_turret/ui_host(mob/user) + if(has_cover && cover) + return cover + if(base) + return base + return src + +/obj/machinery/porta_turret/power_change() + . = ..() + if(!anchored || (machine_stat & BROKEN) || !powered()) + update_icon() + remove_control() + check_should_process() + +/obj/machinery/porta_turret/attackby(obj/item/I, mob/user, params) + if(machine_stat & BROKEN) + if(I.tool_behaviour == TOOL_CROWBAR) + //If the turret is destroyed, you can remove it with a crowbar to + //try and salvage its components + to_chat(user, "You begin prying the metal coverings off...") + if(I.use_tool(src, user, 20)) + if(prob(70)) + if(stored_gun) + stored_gun.forceMove(loc) + stored_gun = null + to_chat(user, "You remove the turret and salvage some components.") + if(prob(50)) + new /obj/item/stack/sheet/metal(loc, rand(1,4)) + if(prob(50)) + new /obj/item/assembly/prox_sensor(loc) + else + to_chat(user, "You remove the turret but did not manage to salvage anything.") + qdel(src) + + else if((I.tool_behaviour == TOOL_WRENCH) && (!on)) + if(raised) + return + + //This code handles moving the turret around. After all, it's a portable turret! + if(!anchored && !isinspace()) + setAnchored(TRUE) + invisibility = INVISIBILITY_MAXIMUM + update_icon() + to_chat(user, "You secure the exterior bolts on the turret.") + if(has_cover) + cover = new /obj/machinery/porta_turret_cover(loc) //create a new turret. While this is handled in process(), this is to workaround a bug where the turret becomes invisible for a split second + cover.parent_turret = src //make the cover's parent src + else if(anchored) + setAnchored(FALSE) + to_chat(user, "You unsecure the exterior bolts on the turret.") + power_change() + invisibility = 0 + qdel(cover) //deletes the cover, and the turret instance itself becomes its own cover. + + else if(I.GetID()) + //Behavior lock/unlock mangement + if(allowed(user)) + locked = !locked + to_chat(user, "Controls are now [locked ? "locked" : "unlocked"].") + else + to_chat(user, "Access denied.") + else if(I.tool_behaviour == TOOL_MULTITOOL && !locked) + if(!multitool_check_buffer(user, I)) + return + var/obj/item/multitool/M = I + M.buffer = src + to_chat(user, "You add [src] to multitool buffer.") + else + return ..() + +/obj/machinery/porta_turret/emag_act(mob/user) + if(obj_flags & EMAGGED) + return + to_chat(user, "You short out [src]'s threat assessment circuits.") + audible_message("[src] hums oddly...") + obj_flags |= EMAGGED + controllock = TRUE + toggle_on(FALSE) //turns off the turret temporarily + update_icon() + //6 seconds for the traitor to gtfo of the area before the turret decides to ruin his shit + addtimer(CALLBACK(src, .proc/toggle_on, TRUE), 6 SECONDS) + //turns it back on. The cover popUp() popDown() are automatically called in process(), no need to define it here + +/obj/machinery/porta_turret/emp_act(severity) + . = ..() + if (. & EMP_PROTECT_SELF) + return + if(on) + //if the turret is on, the EMP no matter how severe disables the turret for a while + //and scrambles its settings, with a slight chance of having an emag effect + if(prob(50)) + turret_flags |= TURRET_FLAG_SHOOT_CRIMINALS + if(prob(50)) + turret_flags |= TURRET_FLAG_AUTH_WEAPONS + if(prob(20)) + turret_flags |= TURRET_FLAG_SHOOT_ALL // Shooting everyone is a pretty big deal, so it's least likely to get turned on + + toggle_on(FALSE) + remove_control() + + addtimer(CALLBACK(src, .proc/toggle_on, TRUE), rand(60,600)) + +/obj/machinery/porta_turret/take_damage(damage, damage_type = BRUTE, damage_flag = 0, sound_effect = 1) + . = ..() + if(. && obj_integrity > 0) //damage received + if(prob(30)) + spark_system.start() + if(on && !(turret_flags & TURRET_FLAG_SHOOT_ALL_REACT) && !(obj_flags & EMAGGED)) + turret_flags |= TURRET_FLAG_SHOOT_ALL_REACT + addtimer(CALLBACK(src, .proc/reset_attacked), 60) + +/obj/machinery/porta_turret/proc/reset_attacked() + turret_flags &= ~TURRET_FLAG_SHOOT_ALL_REACT + +/obj/machinery/porta_turret/deconstruct(disassembled = TRUE) + qdel(src) + +/obj/machinery/porta_turret/obj_break(damage_flag) + . = ..() + if(.) + power_change() + invisibility = 0 + spark_system.start() //creates some sparks because they look cool + qdel(cover) //deletes the cover - no need on keeping it there! + +/obj/machinery/porta_turret/process() + //the main machinery process + if(cover == null && anchored) //if it has no cover and is anchored + if(machine_stat & BROKEN) //if the turret is borked + qdel(cover) //delete its cover, assuming it has one. Workaround for a pesky little bug + else + if(has_cover) + cover = new /obj/machinery/porta_turret_cover(loc) //if the turret has no cover and is anchored, give it a cover + cover.parent_turret = src //assign the cover its parent_turret, which would be this (src) + + if(!on || (machine_stat & (NOPOWER|BROKEN)) || manual_control) + return PROCESS_KILL + + var/list/targets = list() + for(var/mob/A in view(scan_range, base)) + if(A.invisibility > SEE_INVISIBLE_LIVING) + continue + + if(turret_flags & TURRET_FLAG_SHOOT_ANOMALOUS)//if it's set to check for simple animals + if(isanimal(A)) + var/mob/living/simple_animal/SA = A + if(SA.stat || in_faction(SA)) //don't target if dead or in faction + continue + targets += SA + continue + + if(issilicon(A)) + var/mob/living/silicon/sillycone = A + + if(ispAI(A)) + continue + + if((turret_flags & TURRET_FLAG_SHOOT_BORGS) && sillycone.stat != DEAD && iscyborg(sillycone)) + targets += sillycone + continue + + if(sillycone.stat || in_faction(sillycone)) + continue + + if(iscyborg(sillycone)) + var/mob/living/silicon/robot/sillyconerobot = A + if(LAZYLEN(faction) && (ROLE_SYNDICATE in faction) && sillyconerobot.emagged == TRUE) + continue + + else if(iscarbon(A)) + var/mob/living/carbon/C = A + //If not emagged, only target carbons that can use items + if(mode != TURRET_LETHAL && (C.stat || C.handcuffed || !(C.mobility_flags & MOBILITY_USE))) + continue + + //If emagged, target all but dead carbons + if(mode == TURRET_LETHAL && C.stat == DEAD) + continue + + //if the target is a human and not in our faction, analyze threat level + if(ishuman(C) && !in_faction(C)) + + if(assess_perp(C) >= 4) + targets += C + else if(turret_flags & TURRET_FLAG_SHOOT_ANOMALOUS) //non humans who are not simple animals (xenos etc) + if(!in_faction(C)) + targets += C + + for(var/A in GLOB.mechas_list) + if((get_dist(A, base) < scan_range) && can_see(base, A, scan_range)) + var/obj/mecha/Mech = A + if(Mech.occupant && !in_faction(Mech.occupant)) //If there is a user and they're not in our faction + if(assess_perp(Mech.occupant) >= 4) + targets += Mech + + if((turret_flags & TURRET_FLAG_SHOOT_ANOMALOUS) && GLOB.blobs.len && (mode == TURRET_LETHAL)) + for(var/obj/structure/blob/B in view(scan_range, base)) + targets += B + + if(targets.len) + tryToShootAt(targets) + else if(!always_up) + popDown() // no valid targets, close the cover + +/obj/machinery/porta_turret/proc/tryToShootAt(list/atom/movable/targets) + while(targets.len > 0) + var/atom/movable/M = pick(targets) + targets -= M + if(target(M)) + return 1 + +/obj/machinery/porta_turret/proc/popUp() //pops the turret up + if(!anchored) + return + if(raising || raised) + return + if(machine_stat & BROKEN) + return + invisibility = 0 + raising = 1 + if(cover) + flick("popup", cover) + sleep(POPUP_ANIM_TIME) + raising = 0 + if(cover) + cover.icon_state = "openTurretCover" + raised = 1 + layer = MOB_LAYER + +/obj/machinery/porta_turret/proc/popDown() //pops the turret down + if(raising || !raised) + return + if(machine_stat & BROKEN) + return + layer = OBJ_LAYER + raising = 1 + if(cover) + flick("popdown", cover) + sleep(POPDOWN_ANIM_TIME) + raising = 0 + if(cover) + cover.icon_state = "turretCover" + raised = 0 + invisibility = 2 + update_icon() + +/obj/machinery/porta_turret/proc/assess_perp(mob/living/carbon/human/perp) + var/threatcount = 0 //the integer returned + + if(obj_flags & EMAGGED) + return 10 //if emagged, always return 10. + + if((turret_flags & (TURRET_FLAG_SHOOT_ALL | TURRET_FLAG_SHOOT_ALL_REACT)) && !allowed(perp)) + //if the turret has been attacked or is angry, target all non-sec people + if(!allowed(perp)) + return 10 + + if(turret_flags & TURRET_FLAG_AUTH_WEAPONS) //check for weapon authorization + if(isnull(perp.wear_id) || istype(perp.wear_id.GetID(), /obj/item/card/id/syndicate)) + + if(allowed(perp)) //if the perp has security access, return 0 + return 0 + if(perp.is_holding_item_of_type(/obj/item/gun) || perp.is_holding_item_of_type(/obj/item/melee/baton)) + threatcount += 4 + + if(istype(perp.belt, /obj/item/gun) || istype(perp.belt, /obj/item/melee/baton)) + threatcount += 2 + + if(turret_flags & TURRET_FLAG_SHOOT_CRIMINALS) //if the turret can check the records, check if they are set to *Arrest* on records + var/perpname = perp.get_face_name(perp.get_id_name()) + var/datum/data/record/R = find_record("name", perpname, GLOB.data_core.security) + if(!R || (R.fields["criminal"] == "*Arrest*")) + threatcount += 4 + + if((turret_flags & TURRET_FLAG_SHOOT_UNSHIELDED) && (!HAS_TRAIT(perp, TRAIT_MINDSHIELD))) + threatcount += 4 + + // If we aren't shooting heads then return a threatcount of 0 + if (!(turret_flags & TURRET_FLAG_SHOOT_HEADS) && (perp.get_assignment() in GLOB.command_positions)) + return 0 + + return threatcount + +/obj/machinery/porta_turret/proc/in_faction(mob/target) + for(var/faction1 in faction) + if(faction1 in target.faction) + return TRUE + return FALSE + +/obj/machinery/porta_turret/proc/target(atom/movable/target) + if(target) + popUp() //pop the turret up if it's not already up. + setDir(get_dir(base, target))//even if you can't shoot, follow the target + shootAt(target) + return 1 + return + +/obj/machinery/porta_turret/proc/shootAt(atom/movable/target) + if(!raised) //the turret has to be raised in order to fire - makes sense, right? + return + + if(!(obj_flags & EMAGGED)) //if it hasn't been emagged, cooldown before shooting again + if(last_fired + shot_delay > world.time) + return + last_fired = world.time + + var/turf/T = get_turf(src) + var/turf/U = get_turf(target) + if(!istype(T) || !istype(U)) + return + + //Wall turrets will try to find adjacent empty turf to shoot from to cover full arc + if(T.density) + if(wall_turret_direction) + var/turf/closer = get_step(T,wall_turret_direction) + if(istype(closer) && !is_blocked_turf(closer) && T.Adjacent(closer)) + T = closer + else + var/target_dir = get_dir(T,target) + for(var/d in list(0,-45,45)) + var/turf/closer = get_step(T,turn(target_dir,d)) + if(istype(closer) && !is_blocked_turf(closer) && T.Adjacent(closer)) + T = closer + break + + update_icon() + var/obj/projectile/A + //any emagged turrets drains 2x power and uses a different projectile? + if(mode == TURRET_STUN) + use_power(reqpower) + A = new stun_projectile(T) + playsound(loc, stun_projectile_sound, 75, TRUE) + else + use_power(reqpower * 2) + A = new lethal_projectile(T) + playsound(loc, lethal_projectile_sound, 75, TRUE) + + + //Shooting Code: + A.preparePixelProjectile(target, T) + A.firer = src + A.fired_from = src + A.fire() + return A + +/obj/machinery/porta_turret/proc/setState(on, mode, shoot_cyborgs) + if(controllock) + return + + shoot_cyborgs ? (turret_flags |= TURRET_FLAG_SHOOT_BORGS) : (turret_flags &= ~TURRET_FLAG_SHOOT_BORGS) + toggle_on(on) + src.mode = mode + power_change() + +/datum/action/turret_toggle + name = "Toggle Mode" + icon_icon = 'icons/mob/actions/actions_mecha.dmi' + button_icon_state = "mech_cycle_equip_off" + +/datum/action/turret_toggle/Trigger() + var/obj/machinery/porta_turret/P = target + if(!istype(P)) + return + P.setState(P.on,!P.mode) + +/datum/action/turret_quit + name = "Release Control" + icon_icon = 'icons/mob/actions/actions_mecha.dmi' + button_icon_state = "mech_eject" + +/datum/action/turret_quit/Trigger() + var/obj/machinery/porta_turret/P = target + if(!istype(P)) + return + P.remove_control(FALSE) + +/obj/machinery/porta_turret/proc/give_control(mob/A) + if(manual_control || !can_interact(A)) + return FALSE + remote_controller = A + if(!quit_action) + quit_action = new(src) + quit_action.Grant(remote_controller) + if(!toggle_action) + toggle_action = new(src) + toggle_action.Grant(remote_controller) + remote_controller.reset_perspective(src) + remote_controller.click_intercept = src + manual_control = TRUE + always_up = TRUE + popUp() + return TRUE + +/obj/machinery/porta_turret/proc/remove_control(warning_message = TRUE) + if(!manual_control) + return FALSE + if(remote_controller) + if(warning_message) + to_chat(remote_controller, "Your uplink to [src] has been severed!") + quit_action.Remove(remote_controller) + toggle_action.Remove(remote_controller) + remote_controller.click_intercept = null + remote_controller.reset_perspective() + always_up = initial(always_up) + manual_control = FALSE + remote_controller = null + return TRUE + +/obj/machinery/porta_turret/proc/InterceptClickOn(mob/living/caller, params, atom/A) + if(!manual_control) + return FALSE + if(!can_interact(caller)) + remove_control() + return FALSE + log_combat(caller,A,"fired with manual turret control at") + target(A) + return TRUE + +/obj/machinery/porta_turret/syndicate + installation = null + always_up = 1 + use_power = NO_POWER_USE + has_cover = 0 + scan_range = 9 + req_access = list(ACCESS_SYNDICATE) + mode = TURRET_LETHAL + stun_projectile = /obj/projectile/bullet + lethal_projectile = /obj/projectile/bullet + lethal_projectile_sound = 'sound/weapons/gun/pistol/shot.ogg' + stun_projectile_sound = 'sound/weapons/gun/pistol/shot.ogg' + icon_state = "syndie_off" + base_icon_state = "syndie" + faction = list(ROLE_SYNDICATE) + desc = "A ballistic machine gun auto-turret." + +/obj/machinery/porta_turret/syndicate/ComponentInitialize() + . = ..() + AddComponent(/datum/component/empprotection, EMP_PROTECT_SELF | EMP_PROTECT_WIRES) + +/obj/machinery/porta_turret/syndicate/setup() + return + +/obj/machinery/porta_turret/syndicate/assess_perp(mob/living/carbon/human/perp) + return 10 //Syndicate turrets shoot everything not in their faction + +/obj/machinery/porta_turret/syndicate/energy + icon_state = "standard_lethal" + base_icon_state = "standard" + stun_projectile = /obj/projectile/energy/electrode + stun_projectile_sound = 'sound/weapons/taser.ogg' + lethal_projectile = /obj/projectile/beam/laser + lethal_projectile_sound = 'sound/weapons/laser.ogg' + desc = "An energy blaster auto-turret." + +/obj/machinery/porta_turret/syndicate/energy/heavy + icon_state = "standard_lethal" + base_icon_state = "standard" + stun_projectile = /obj/projectile/energy/electrode + stun_projectile_sound = 'sound/weapons/taser.ogg' + lethal_projectile = /obj/projectile/beam/laser/heavylaser + lethal_projectile_sound = 'sound/weapons/lasercannonfire.ogg' + desc = "An energy blaster auto-turret." + +/obj/machinery/porta_turret/syndicate/energy/raven + stun_projectile = /obj/projectile/beam/laser + stun_projectile_sound = 'sound/weapons/laser.ogg' + faction = list("neutral","silicon","turret") + +/obj/machinery/porta_turret/syndicate/pod + integrity_failure = 0.5 + max_integrity = 40 + stun_projectile = /obj/projectile/bullet/syndicate_turret + lethal_projectile = /obj/projectile/bullet/syndicate_turret + +/obj/machinery/porta_turret/syndicate/shuttle + scan_range = 9 + shot_delay = 3 + stun_projectile = /obj/projectile/bullet/p50/penetrator/shuttle + lethal_projectile = /obj/projectile/bullet/p50/penetrator/shuttle + lethal_projectile_sound = 'sound/weapons/gun/smg/shot.ogg' + stun_projectile_sound = 'sound/weapons/gun/smg/shot.ogg' + armor = list("melee" = 50, "bullet" = 30, "laser" = 30, "energy" = 30, "bomb" = 80, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 90) + +/obj/machinery/porta_turret/syndicate/shuttle/target(atom/movable/target) + if(target) + setDir(get_dir(base, target))//even if you can't shoot, follow the target + shootAt(target) + addtimer(CALLBACK(src, .proc/shootAt, target), 5) + addtimer(CALLBACK(src, .proc/shootAt, target), 10) + addtimer(CALLBACK(src, .proc/shootAt, target), 15) + return TRUE + +/obj/machinery/porta_turret/ai + faction = list("silicon") + turret_flags = TURRET_FLAG_SHOOT_CRIMINALS | TURRET_FLAG_SHOOT_ANOMALOUS | TURRET_FLAG_SHOOT_HEADS + +/obj/machinery/porta_turret/ai/assess_perp(mob/living/carbon/human/perp) + return 10 //AI turrets shoot at everything not in their faction + +/obj/machinery/porta_turret/aux_base + name = "perimeter defense turret" + desc = "A plasma beam turret calibrated to defend outposts against non-humanoid fauna. It is more effective when exposed to the environment." + installation = null + lethal_projectile = /obj/projectile/plasma/turret + lethal_projectile_sound = 'sound/weapons/plasma_cutter.ogg' + mode = TURRET_LETHAL //It would be useless in stun mode anyway + faction = list("neutral","silicon","turret") //Minebots, medibots, etc that should not be shot. + +/obj/machinery/porta_turret/aux_base/assess_perp(mob/living/carbon/human/perp) + return 0 //Never shoot humanoids. You are on your own if Ashwalkers or the like attack! + +/obj/machinery/porta_turret/aux_base/setup() + return + +/obj/machinery/porta_turret/aux_base/interact(mob/user) //Controlled solely from the base console. + return + +/obj/machinery/porta_turret/aux_base/Initialize() + . = ..() + cover.name = name + cover.desc = desc + +/obj/machinery/porta_turret/centcom_shuttle + installation = null + max_integrity = 260 + always_up = 1 + use_power = NO_POWER_USE + has_cover = 0 + scan_range = 9 + stun_projectile = /obj/projectile/beam/laser + lethal_projectile = /obj/projectile/beam/laser + lethal_projectile_sound = 'sound/weapons/plasma_cutter.ogg' + stun_projectile_sound = 'sound/weapons/plasma_cutter.ogg' + icon_state = "syndie_off" + base_icon_state = "syndie" + faction = list("neutral","silicon","turret") + mode = TURRET_LETHAL + +/obj/machinery/porta_turret/centcom_shuttle/ComponentInitialize() + . = ..() + AddComponent(/datum/component/empprotection, EMP_PROTECT_SELF | EMP_PROTECT_WIRES) + +/obj/machinery/porta_turret/centcom_shuttle/assess_perp(mob/living/carbon/human/perp) + return 0 + +/obj/machinery/porta_turret/centcom_shuttle/setup() + return + +/obj/machinery/porta_turret/centcom_shuttle/weak + max_integrity = 120 + integrity_failure = 0.5 + name = "Old Laser Turret" + desc = "A turret built with substandard parts and run down further with age. Still capable of delivering lethal lasers to the odd space carp, but not much else." + stun_projectile = /obj/projectile/beam/weak/penetrator + lethal_projectile = /obj/projectile/beam/weak/penetrator + faction = list("neutral","silicon","turret") + +//////////////////////// +//Turret Control Panel// +//////////////////////// + +/obj/machinery/turretid + name = "turret control panel" + desc = "Used to control a room's automated defenses." + icon = 'icons/obj/machines/turret_control.dmi' + icon_state = "control_standby" + density = FALSE + req_access = list(ACCESS_AI_UPLOAD) + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + /// Variable dictating if linked turrets are active and will shoot targets + var/enabled = TRUE + /// Variable dictating if linked turrets will shoot lethal projectiles + var/lethal = FALSE + /// Variable dictating if the panel is locked, preventing changes to turret settings + var/locked = TRUE + /// An area in which linked turrets are located, it can be an area name, path or nothing + var/control_area = null + /// AI is unable to use this machine if set to TRUE + var/ailock = FALSE + /// Variable dictating if linked turrets will shoot cyborgs + var/shoot_cyborgs = FALSE + /// List of all linked turrets + var/list/turrets = list() + +/obj/machinery/turretid/Initialize(mapload, ndir = 0, built = 0) + . = ..() + if(built) + setDir(ndir) + locked = FALSE + pixel_x = (dir & 3)? 0 : (dir == 4 ? -24 : 24) + pixel_y = (dir & 3)? (dir ==1 ? -24 : 24) : 0 + power_change() //Checks power and initial settings + +/obj/machinery/turretid/Destroy() + turrets.Cut() + return ..() + +/obj/machinery/turretid/Initialize(mapload) //map-placed turrets autolink turrets + . = ..() + if(!mapload) + return + + if(control_area) + control_area = get_area_instance_from_text(control_area) + if(control_area == null) + control_area = get_area(src) + stack_trace("Bad control_area path for [src], [src.control_area]") + else if(!control_area) + control_area = get_area(src) + + for(var/obj/machinery/porta_turret/T in control_area) + turrets |= T + T.cp = src + +/obj/machinery/turretid/examine(mob/user) + . += ..() + if(issilicon(user) && !(machine_stat & BROKEN)) + . += {"Ctrl-click [src] to [ enabled ? "disable" : "enable"] turrets. + Alt-click [src] to set turrets to [ lethal ? "stun" : "kill"]."} + +/obj/machinery/turretid/attackby(obj/item/I, mob/user, params) + if(machine_stat & BROKEN) + return + + if(I.tool_behaviour == TOOL_MULTITOOL) + if(!multitool_check_buffer(user, I)) + return + var/obj/item/multitool/M = I + if(M.buffer && istype(M.buffer, /obj/machinery/porta_turret)) + turrets |= M.buffer + to_chat(user, "You link \the [M.buffer] with \the [src].") + return + + if (issilicon(user)) + return attack_hand(user) + + if ( get_dist(src, user) == 0 ) // trying to unlock the interface + if (allowed(usr)) + if(obj_flags & EMAGGED) + to_chat(user, "The turret control is unresponsive!") + return + + locked = !locked + to_chat(user, "You [ locked ? "lock" : "unlock"] the panel.") + else + to_chat(user, "Access denied.") + +/obj/machinery/turretid/emag_act(mob/user) + if(obj_flags & EMAGGED) + return + to_chat(user, "You short out the turret controls' access analysis module.") + obj_flags |= EMAGGED + locked = FALSE + +/obj/machinery/turretid/attack_ai(mob/user) + if(!ailock || IsAdminGhost(user)) + return attack_hand(user) + else + to_chat(user, "There seems to be a firewall preventing you from accessing this device!") + +/obj/machinery/turretid/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "TurretControl", name) + ui.open() + +/obj/machinery/turretid/ui_data(mob/user) + var/list/data = list() + data["locked"] = locked + data["siliconUser"] = user.has_unlimited_silicon_privilege + data["enabled"] = enabled + data["lethal"] = lethal + data["shootCyborgs"] = shoot_cyborgs + return data + +/obj/machinery/turretid/ui_act(action, list/params) + . = ..() + if(.) + return + + switch(action) + if("lock") + if(!usr.has_unlimited_silicon_privilege) + return + if((obj_flags & EMAGGED) || (machine_stat & BROKEN)) + to_chat(usr, "The turret control is unresponsive!") + return + locked = !locked + return TRUE + if("power") + toggle_on(usr) + return TRUE + if("mode") + toggle_lethal(usr) + return TRUE + if("shoot_silicons") + shoot_silicons(usr) + return TRUE + +/obj/machinery/turretid/proc/toggle_lethal(mob/user) + lethal = !lethal + add_hiddenprint(user) + log_combat(user, src, "[lethal ? "enabled" : "disabled"] lethals on") + updateTurrets() + +/obj/machinery/turretid/proc/toggle_on(mob/user) + enabled = !enabled + add_hiddenprint(user) + log_combat(user, src, "[enabled ? "enabled" : "disabled"]") + updateTurrets() + +/obj/machinery/turretid/proc/shoot_silicons(mob/user) + shoot_cyborgs = !shoot_cyborgs + add_hiddenprint(user) + log_combat(user, src, "[shoot_cyborgs ? "Shooting Borgs" : "Not Shooting Borgs"]") + updateTurrets() + +/obj/machinery/turretid/proc/updateTurrets() + for (var/obj/machinery/porta_turret/aTurret in turrets) + aTurret.setState(enabled, lethal, shoot_cyborgs) + update_icon() + +/obj/machinery/turretid/update_icon_state() + if(machine_stat & NOPOWER) + icon_state = "control_off" + else if (enabled) + if (lethal) + icon_state = "control_kill" + else + icon_state = "control_stun" + else + icon_state = "control_standby" + +/obj/item/wallframe/turret_control + name = "turret control frame" + desc = "Used for building turret control panels." + icon_state = "apc" + result_path = /obj/machinery/turretid + custom_materials = list(/datum/material/iron=MINERAL_MATERIAL_AMOUNT) + +/obj/item/gun/proc/get_turret_properties() + . = list() + .["lethal_projectile"] = null + .["lethal_projectile_sound"] = null + .["stun_projectile"] = null + .["stun_projectile_sound"] = null + .["base_icon_state"] = "standard" + +/obj/item/gun/energy/get_turret_properties() + . = ..() + + var/obj/item/ammo_casing/primary_ammo = ammo_type[1] + + .["stun_projectile"] = initial(primary_ammo.projectile_type) + .["stun_projectile_sound"] = initial(primary_ammo.fire_sound) + + if(ammo_type.len > 1) + var/obj/item/ammo_casing/secondary_ammo = ammo_type[2] + .["lethal_projectile"] = initial(secondary_ammo.projectile_type) + .["lethal_projectile_sound"] = initial(secondary_ammo.fire_sound) + else + .["lethal_projectile"] = .["stun_projectile"] + .["lethal_projectile_sound"] = .["stun_projectile_sound"] + +/obj/item/gun/ballistic/get_turret_properties() + . = ..() + var/obj/item/ammo_box/mag = mag_type + var/obj/item/ammo_casing/primary_ammo = initial(mag.ammo_type) + + .["base_icon_state"] = "syndie" + .["stun_projectile"] = initial(primary_ammo.projectile_type) + .["stun_projectile_sound"] = initial(primary_ammo.fire_sound) + .["lethal_projectile"] = .["stun_projectile"] + .["lethal_projectile_sound"] = .["stun_projectile_sound"] + + +/obj/item/gun/energy/laser/bluetag/get_turret_properties() + . = ..() + .["stun_projectile"] = /obj/projectile/beam/lasertag/bluetag + .["lethal_projectile"] = /obj/projectile/beam/lasertag/bluetag + .["base_icon_state"] = "blue" + .["shot_delay"] = 30 + .["team_color"] = "blue" + +/obj/item/gun/energy/laser/redtag/get_turret_properties() + . = ..() + .["stun_projectile"] = /obj/projectile/beam/lasertag/redtag + .["lethal_projectile"] = /obj/projectile/beam/lasertag/redtag + .["base_icon_state"] = "red" + .["shot_delay"] = 30 + .["team_color"] = "red" + +/obj/item/gun/energy/e_gun/turret/get_turret_properties() + . = ..() + +/obj/machinery/porta_turret/lasertag + req_access = list(ACCESS_MAINT_TUNNELS, ACCESS_THEATRE) + turret_flags = TURRET_FLAG_AUTH_WEAPONS + var/team_color + +/obj/machinery/porta_turret/lasertag/assess_perp(mob/living/carbon/human/perp) + . = 0 + if(team_color == "blue") //Lasertag turrets target the opposing team, how great is that? -Sieve + . = 0 //But does not target anyone else + if(istype(perp.wear_suit, /obj/item/clothing/suit/redtag)) + . += 4 + if(perp.is_holding_item_of_type(/obj/item/gun/energy/laser/redtag)) + . += 4 + if(istype(perp.belt, /obj/item/gun/energy/laser/redtag)) + . += 2 + + if(team_color == "red") + . = 0 + if(istype(perp.wear_suit, /obj/item/clothing/suit/bluetag)) + . += 4 + if(perp.is_holding_item_of_type(/obj/item/gun/energy/laser/bluetag)) + . += 4 + if(istype(perp.belt, /obj/item/gun/energy/laser/bluetag)) + . += 2 + +/obj/machinery/porta_turret/lasertag/setup(obj/item/gun/gun) + var/list/properties = ..() + if(properties["team_color"]) + team_color = properties["team_color"] + +/obj/machinery/porta_turret/lasertag/ui_status(mob/user) + if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(team_color == "blue" && istype(H.wear_suit, /obj/item/clothing/suit/redtag)) + return UI_CLOSE + if(team_color == "red" && istype(H.wear_suit, /obj/item/clothing/suit/bluetag)) + return UI_CLOSE + return ..() + +//lasertag presets +/obj/machinery/porta_turret/lasertag/red + installation = /obj/item/gun/energy/laser/redtag + team_color = "red" + +/obj/machinery/porta_turret/lasertag/blue + installation = /obj/item/gun/energy/laser/bluetag + team_color = "blue" + +/obj/machinery/porta_turret/lasertag/bullet_act(obj/projectile/P) + . = ..() + if(on) + if(team_color == "blue") + if(istype(P, /obj/projectile/beam/lasertag/redtag)) + toggle_on(FALSE) + addtimer(CALLBACK(src, .proc/toggle_on, TRUE), 10 SECONDS) + else if(team_color == "red") + if(istype(P, /obj/projectile/beam/lasertag/bluetag)) + toggle_on(FALSE) + addtimer(CALLBACK(src, .proc/toggle_on, TRUE), 10 SECONDS) diff --git a/code/game/machinery/porta_turret/portable_turret_cover.dm b/code/game/machinery/porta_turret/portable_turret_cover.dm index 3892dbcc7a22..0da5ba09fe60 100644 --- a/code/game/machinery/porta_turret/portable_turret_cover.dm +++ b/code/game/machinery/porta_turret/portable_turret_cover.dm @@ -24,18 +24,16 @@ //I'm not fixing it because i'm fucking bored of this code already, but someone should just reroute these to the parent turret's procs. /obj/machinery/porta_turret_cover/attack_ai(mob/user) - . = ..() - if(.) - return - return parent_turret.attack_ai(user) + return ..() || parent_turret.attack_ai(user) +/obj/machinery/porta_turret_cover/attack_robot(mob/user) + return ..() || parent_turret.attack_robot(user) /obj/machinery/porta_turret_cover/attack_hand(mob/user) - . = ..() - if(.) - return - return parent_turret.attack_hand(user) + return ..() || parent_turret.attack_hand(user) +/obj/machinery/porta_turret_cover/attack_ghost(mob/user) + return ..() || parent_turret.attack_ghost(user) /obj/machinery/porta_turret_cover/attackby(obj/item/I, mob/user, params) if(I.tool_behaviour == TOOL_WRENCH && !parent_turret.on) @@ -58,7 +56,6 @@ if(parent_turret.allowed(user)) parent_turret.locked = !parent_turret.locked to_chat(user, "Controls are now [parent_turret.locked ? "locked" : "unlocked"].") - updateUsrDialog() else to_chat(user, "Access denied.") else if(I.tool_behaviour == TOOL_MULTITOOL && !parent_turret.locked) diff --git a/code/game/machinery/recharger.dm b/code/game/machinery/recharger.dm index bb1df8ea8cd9..ccb1206aadf4 100755 --- a/code/game/machinery/recharger.dm +++ b/code/game/machinery/recharger.dm @@ -1,181 +1,181 @@ -/obj/machinery/recharger - name = "recharger" - icon = 'icons/obj/stationobjs.dmi' - icon_state = "recharger" - desc = "A charging dock for energy based weaponry." - use_power = IDLE_POWER_USE - idle_power_usage = 4 - active_power_usage = 250 - circuit = /obj/item/circuitboard/machine/recharger - pass_flags = PASSTABLE - var/obj/item/charging = null - var/recharge_coeff = 1 - var/using_power = FALSE //Did we put power into "charging" last process()? - - var/static/list/allowed_devices = typecacheof(list( - /obj/item/gun/energy, - /obj/item/melee/baton, - /obj/item/ammo_box/magazine/recharge, - /obj/item/modular_computer)) - -/obj/machinery/recharger/RefreshParts() - for(var/obj/item/stock_parts/capacitor/C in component_parts) - recharge_coeff = C.rating - -/obj/machinery/recharger/examine(mob/user) - . = ..() - if(!in_range(user, src) && !issilicon(user) && !isobserver(user)) - . += "You're too far away to examine [src]'s contents and display!" - return - - if(charging) - . += {"\The [src] contains: - - \A [charging]."} - - if(!(machine_stat & (NOPOWER|BROKEN))) - . += "The status display reads:" - . += "- Recharging [recharge_coeff*10]% cell charge per cycle." - if(charging) - var/obj/item/stock_parts/cell/C = charging.get_cell() - . += "- \The [charging]'s cell is at [C.percent()]%." - - -/obj/machinery/recharger/proc/setCharging(new_charging) - charging = new_charging - if (new_charging) - START_PROCESSING(SSmachines, src) - use_power = ACTIVE_POWER_USE - using_power = TRUE - update_icon() - else - use_power = IDLE_POWER_USE - using_power = FALSE - update_icon() - -/obj/machinery/recharger/attackby(obj/item/G, mob/user, params) - if(G.tool_behaviour == TOOL_WRENCH) - if(charging) - to_chat(user, "Remove the charging item first!") - return - setAnchored(!anchored) - power_change() - to_chat(user, "You [anchored ? "attached" : "detached"] [src].") - G.play_tool_sound(src) - return - - var/allowed = is_type_in_typecache(G, allowed_devices) - - if(allowed) - if(anchored) - if(charging || panel_open) - return 1 - - //Checks to make sure he's not in space doing it, and that the area got proper power. - var/area/a = get_area(src) - if(!isarea(a) || a.power_equip == 0) - to_chat(user, "[src] blinks red as you try to insert [G].") - return 1 - - if (istype(G, /obj/item/gun/energy)) - var/obj/item/gun/energy/E = G - if(!E.can_charge) - to_chat(user, "Your gun has no external power connector.") - return 1 - - if(!user.transferItemToLoc(G, src)) - return 1 - setCharging(G) - - else - to_chat(user, "[src] isn't connected to anything!") - return 1 - - if(anchored && !charging) - if(default_deconstruction_screwdriver(user, "rechargeropen", "recharger0", G)) - return - - if(panel_open && G.tool_behaviour == TOOL_CROWBAR) - default_deconstruction_crowbar(G) - return - - return ..() - -/obj/machinery/recharger/attack_hand(mob/user) - . = ..() - if(.) - return - - add_fingerprint(user) - if(charging) - charging.update_icon() - charging.forceMove(drop_location()) - user.put_in_hands(charging) - setCharging(null) - -/obj/machinery/recharger/attack_tk(mob/user) - if(charging) - charging.update_icon() - charging.forceMove(drop_location()) - setCharging(null) - -/obj/machinery/recharger/process() - if(machine_stat & (NOPOWER|BROKEN) || !anchored) - return PROCESS_KILL - - using_power = FALSE - if(charging) - var/obj/item/stock_parts/cell/C = charging.get_cell() - if(C) - if(C.charge < C.maxcharge) - C.give(C.chargerate * recharge_coeff) - use_power(250 * recharge_coeff) - using_power = TRUE - update_icon() - - if(istype(charging, /obj/item/ammo_box/magazine/recharge)) - var/obj/item/ammo_box/magazine/recharge/R = charging - if(R.stored_ammo.len < R.max_ammo) - R.stored_ammo += new R.ammo_type(R) - use_power(200 * recharge_coeff) - using_power = TRUE - update_icon() - return - else - return PROCESS_KILL - -/obj/machinery/recharger/emp_act(severity) - . = ..() - if (. & EMP_PROTECT_CONTENTS) - return - if(!(machine_stat & (NOPOWER|BROKEN)) && anchored) - if(istype(charging, /obj/item/gun/energy)) - var/obj/item/gun/energy/E = charging - if(E.cell) - E.cell.emp_act(severity) - - else if(istype(charging, /obj/item/melee/baton)) - var/obj/item/melee/baton/B = charging - if(B.cell) - B.cell.charge = 0 - -/obj/machinery/recharger/update_overlays() - . = ..() - SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays) - luminosity = 0 - if(machine_stat & (NOPOWER|BROKEN) || !anchored) - return - if(panel_open) - SSvis_overlays.add_vis_overlay(src, icon, "recharger-open", layer, plane, dir, alpha) - return - - luminosity = 1 - if (charging) - if(using_power) - SSvis_overlays.add_vis_overlay(src, icon, "recharger-charging", layer, plane, dir, alpha) - SSvis_overlays.add_vis_overlay(src, icon, "recharger-charging", EMISSIVE_LAYER, EMISSIVE_PLANE, dir, alpha) - else - SSvis_overlays.add_vis_overlay(src, icon, "recharger-full", layer, plane, dir, alpha) - SSvis_overlays.add_vis_overlay(src, icon, "recharger-full", EMISSIVE_LAYER, EMISSIVE_PLANE, dir, alpha) - else - SSvis_overlays.add_vis_overlay(src, icon, "recharger-empty", layer, plane, dir, alpha) - SSvis_overlays.add_vis_overlay(src, icon, "recharger-empty", EMISSIVE_LAYER, EMISSIVE_PLANE, dir, alpha) +/obj/machinery/recharger + name = "recharger" + icon = 'icons/obj/stationobjs.dmi' + icon_state = "recharger" + desc = "A charging dock for energy based weaponry." + use_power = IDLE_POWER_USE + idle_power_usage = 4 + active_power_usage = 250 + circuit = /obj/item/circuitboard/machine/recharger + pass_flags = PASSTABLE + var/obj/item/charging = null + var/recharge_coeff = 1 + var/using_power = FALSE //Did we put power into "charging" last process()? + + var/static/list/allowed_devices = typecacheof(list( + /obj/item/gun/energy, + /obj/item/melee/baton, + /obj/item/ammo_box/magazine/recharge, + /obj/item/modular_computer)) + +/obj/machinery/recharger/RefreshParts() + for(var/obj/item/stock_parts/capacitor/C in component_parts) + recharge_coeff = C.rating + +/obj/machinery/recharger/examine(mob/user) + . = ..() + if(!in_range(user, src) && !issilicon(user) && !isobserver(user)) + . += "You're too far away to examine [src]'s contents and display!" + return + + if(charging) + . += {"\The [src] contains: + - \A [charging]."} + + if(!(machine_stat & (NOPOWER|BROKEN))) + . += "The status display reads:" + . += "- Recharging [recharge_coeff*10]% cell charge per cycle." + if(charging) + var/obj/item/stock_parts/cell/C = charging.get_cell() + . += "- \The [charging]'s cell is at [C.percent()]%." + + +/obj/machinery/recharger/proc/setCharging(new_charging) + charging = new_charging + if (new_charging) + START_PROCESSING(SSmachines, src) + use_power = ACTIVE_POWER_USE + using_power = TRUE + update_icon() + else + use_power = IDLE_POWER_USE + using_power = FALSE + update_icon() + +/obj/machinery/recharger/attackby(obj/item/G, mob/user, params) + if(G.tool_behaviour == TOOL_WRENCH) + if(charging) + to_chat(user, "Remove the charging item first!") + return + setAnchored(!anchored) + power_change() + to_chat(user, "You [anchored ? "attached" : "detached"] [src].") + G.play_tool_sound(src) + return + + var/allowed = is_type_in_typecache(G, allowed_devices) + + if(allowed) + if(anchored) + if(charging || panel_open) + return 1 + + //Checks to make sure he's not in space doing it, and that the area got proper power. + var/area/a = get_area(src) + if(!isarea(a) || a.power_equip == 0) + to_chat(user, "[src] blinks red as you try to insert [G].") + return 1 + + if (istype(G, /obj/item/gun/energy)) + var/obj/item/gun/energy/E = G + if(!E.can_charge) + to_chat(user, "Your gun has no external power connector.") + return 1 + + if(!user.transferItemToLoc(G, src)) + return 1 + setCharging(G) + + else + to_chat(user, "[src] isn't connected to anything!") + return 1 + + if(anchored && !charging) + if(default_deconstruction_screwdriver(user, "rechargeropen", "recharger0", G)) + return + + if(panel_open && G.tool_behaviour == TOOL_CROWBAR) + default_deconstruction_crowbar(G) + return + + return ..() + +/obj/machinery/recharger/attack_hand(mob/user) + . = ..() + if(.) + return + + add_fingerprint(user) + if(charging) + charging.update_icon() + charging.forceMove(drop_location()) + user.put_in_hands(charging) + setCharging(null) + +/obj/machinery/recharger/attack_tk(mob/user) + if(charging) + charging.update_icon() + charging.forceMove(drop_location()) + setCharging(null) + +/obj/machinery/recharger/process() + if(machine_stat & (NOPOWER|BROKEN) || !anchored) + return PROCESS_KILL + + using_power = FALSE + if(charging) + var/obj/item/stock_parts/cell/C = charging.get_cell() + if(C) + if(C.charge < C.maxcharge) + C.give(C.chargerate * recharge_coeff) + use_power(250 * recharge_coeff) + using_power = TRUE + update_icon() + + if(istype(charging, /obj/item/ammo_box/magazine/recharge)) + var/obj/item/ammo_box/magazine/recharge/R = charging + if(R.stored_ammo.len < R.max_ammo) + R.stored_ammo += new R.ammo_type(R) + use_power(200 * recharge_coeff) + using_power = TRUE + update_icon() + return + else + return PROCESS_KILL + +/obj/machinery/recharger/emp_act(severity) + . = ..() + if (. & EMP_PROTECT_CONTENTS) + return + if(!(machine_stat & (NOPOWER|BROKEN)) && anchored) + if(istype(charging, /obj/item/gun/energy)) + var/obj/item/gun/energy/E = charging + if(E.cell) + E.cell.emp_act(severity) + + else if(istype(charging, /obj/item/melee/baton)) + var/obj/item/melee/baton/B = charging + if(B.cell) + B.cell.charge = 0 + +/obj/machinery/recharger/update_overlays() + . = ..() + SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays) + luminosity = 0 + if(machine_stat & (NOPOWER|BROKEN) || !anchored) + return + if(panel_open) + SSvis_overlays.add_vis_overlay(src, icon, "recharger-open", layer, plane, dir, alpha) + return + + luminosity = 1 + if (charging) + if(using_power) + SSvis_overlays.add_vis_overlay(src, icon, "recharger-charging", layer, plane, dir, alpha) + SSvis_overlays.add_vis_overlay(src, icon, "recharger-charging", EMISSIVE_LAYER, EMISSIVE_PLANE, dir, alpha) + else + SSvis_overlays.add_vis_overlay(src, icon, "recharger-full", layer, plane, dir, alpha) + SSvis_overlays.add_vis_overlay(src, icon, "recharger-full", EMISSIVE_LAYER, EMISSIVE_PLANE, dir, alpha) + else + SSvis_overlays.add_vis_overlay(src, icon, "recharger-empty", layer, plane, dir, alpha) + SSvis_overlays.add_vis_overlay(src, icon, "recharger-empty", EMISSIVE_LAYER, EMISSIVE_PLANE, dir, alpha) diff --git a/code/game/machinery/rechargestation.dm b/code/game/machinery/rechargestation.dm index 02828ef46c98..b3dbaab0a091 100644 --- a/code/game/machinery/rechargestation.dm +++ b/code/game/machinery/rechargestation.dm @@ -1,103 +1,103 @@ -/obj/machinery/recharge_station - name = "cyborg recharging station" - desc = "This device recharges cyborgs and resupplies them with materials." - icon = 'icons/obj/objects.dmi' - icon_state = "borgcharger0" - density = FALSE - use_power = IDLE_POWER_USE - idle_power_usage = 5 - active_power_usage = 1000 - req_access = list(ACCESS_ROBOTICS) - state_open = TRUE - circuit = /obj/item/circuitboard/machine/cyborgrecharger - occupant_typecache = list(/mob/living/silicon/robot, /mob/living/carbon/human) - var/recharge_speed - var/repairs - -/obj/machinery/recharge_station/Initialize() - . = ..() - update_icon() - -/obj/machinery/recharge_station/RefreshParts() - recharge_speed = 0 - repairs = 0 - for(var/obj/item/stock_parts/capacitor/C in component_parts) - recharge_speed += C.rating * 100 - for(var/obj/item/stock_parts/manipulator/M in component_parts) - repairs += M.rating - 1 - for(var/obj/item/stock_parts/cell/C in component_parts) - recharge_speed *= C.maxcharge / 10000 - -/obj/machinery/recharge_station/examine(mob/user) - . = ..() - if(in_range(user, src) || isobserver(user)) - . += "The status display reads: Recharging [recharge_speed]J per cycle." - if(repairs) - . += "[src] has been upgraded to support automatic repairs." - -/obj/machinery/recharge_station/process() - if(!is_operational()) - return - - if(occupant) - process_occupant() - return 1 - -/obj/machinery/recharge_station/relaymove(mob/user) - if(user.stat) - return - open_machine() - -/obj/machinery/recharge_station/emp_act(severity) - . = ..() - if(!(machine_stat & (BROKEN|NOPOWER))) - if(occupant && !(. & EMP_PROTECT_CONTENTS)) - occupant.emp_act(severity) - if (!(. & EMP_PROTECT_SELF)) - open_machine() - -/obj/machinery/recharge_station/attackby(obj/item/P, mob/user, params) - if(state_open) - if(default_deconstruction_screwdriver(user, "borgdecon2", "borgcharger0", P)) - return - - if(default_pry_open(P)) - return - - if(default_deconstruction_crowbar(P)) - return - return ..() - -/obj/machinery/recharge_station/interact(mob/user) - toggle_open() - return TRUE - -/obj/machinery/recharge_station/proc/toggle_open() - if(state_open) - close_machine() - else - open_machine() - -/obj/machinery/recharge_station/open_machine() - . = ..() - use_power = IDLE_POWER_USE - -/obj/machinery/recharge_station/close_machine() - . = ..() - if(occupant) - use_power = ACTIVE_POWER_USE //It always tries to charge, even if it can't. - add_fingerprint(occupant) - -/obj/machinery/recharge_station/update_icon_state() - if(is_operational()) - if(state_open) - icon_state = "borgcharger0" - else - icon_state = (occupant ? "borgcharger1" : "borgcharger2") - else - icon_state = (state_open ? "borgcharger-u0" : "borgcharger-u1") - -/obj/machinery/recharge_station/proc/process_occupant() - if(!occupant) - return - SEND_SIGNAL(occupant, COMSIG_PROCESS_BORGCHARGER_OCCUPANT, recharge_speed, repairs) +/obj/machinery/recharge_station + name = "cyborg recharging station" + desc = "This device recharges cyborgs and resupplies them with materials." + icon = 'icons/obj/objects.dmi' + icon_state = "borgcharger0" + density = FALSE + use_power = IDLE_POWER_USE + idle_power_usage = 5 + active_power_usage = 1000 + req_access = list(ACCESS_ROBOTICS) + state_open = TRUE + circuit = /obj/item/circuitboard/machine/cyborgrecharger + occupant_typecache = list(/mob/living/silicon/robot, /mob/living/carbon/human) + var/recharge_speed + var/repairs + +/obj/machinery/recharge_station/Initialize() + . = ..() + update_icon() + +/obj/machinery/recharge_station/RefreshParts() + recharge_speed = 0 + repairs = 0 + for(var/obj/item/stock_parts/capacitor/C in component_parts) + recharge_speed += C.rating * 100 + for(var/obj/item/stock_parts/manipulator/M in component_parts) + repairs += M.rating - 1 + for(var/obj/item/stock_parts/cell/C in component_parts) + recharge_speed *= C.maxcharge / 10000 + +/obj/machinery/recharge_station/examine(mob/user) + . = ..() + if(in_range(user, src) || isobserver(user)) + . += "The status display reads: Recharging [recharge_speed]J per cycle." + if(repairs) + . += "[src] has been upgraded to support automatic repairs." + +/obj/machinery/recharge_station/process() + if(!is_operational()) + return + + if(occupant) + process_occupant() + return 1 + +/obj/machinery/recharge_station/relaymove(mob/user) + if(user.stat) + return + open_machine() + +/obj/machinery/recharge_station/emp_act(severity) + . = ..() + if(!(machine_stat & (BROKEN|NOPOWER))) + if(occupant && !(. & EMP_PROTECT_CONTENTS)) + occupant.emp_act(severity) + if (!(. & EMP_PROTECT_SELF)) + open_machine() + +/obj/machinery/recharge_station/attackby(obj/item/P, mob/user, params) + if(state_open) + if(default_deconstruction_screwdriver(user, "borgdecon2", "borgcharger0", P)) + return + + if(default_pry_open(P)) + return + + if(default_deconstruction_crowbar(P)) + return + return ..() + +/obj/machinery/recharge_station/interact(mob/user) + toggle_open() + return TRUE + +/obj/machinery/recharge_station/proc/toggle_open() + if(state_open) + close_machine() + else + open_machine() + +/obj/machinery/recharge_station/open_machine() + . = ..() + use_power = IDLE_POWER_USE + +/obj/machinery/recharge_station/close_machine() + . = ..() + if(occupant) + use_power = ACTIVE_POWER_USE //It always tries to charge, even if it can't. + add_fingerprint(occupant) + +/obj/machinery/recharge_station/update_icon_state() + if(is_operational()) + if(state_open) + icon_state = "borgcharger0" + else + icon_state = (occupant ? "borgcharger1" : "borgcharger2") + else + icon_state = (state_open ? "borgcharger-u0" : "borgcharger-u1") + +/obj/machinery/recharge_station/proc/process_occupant() + if(!occupant) + return + SEND_SIGNAL(occupant, COMSIG_PROCESS_BORGCHARGER_OCCUPANT, recharge_speed, repairs) diff --git a/code/game/machinery/recycler.dm b/code/game/machinery/recycler.dm index 6a5a613a0e59..9a41a98787c7 100644 --- a/code/game/machinery/recycler.dm +++ b/code/game/machinery/recycler.dm @@ -1,202 +1,202 @@ -#define SAFETY_COOLDOWN 100 - -/obj/machinery/recycler - name = "recycler" - desc = "A large crushing machine used to recycle small items inefficiently. There are lights on the side." - icon = 'icons/obj/recycling.dmi' - icon_state = "grinder-o0" - layer = ABOVE_ALL_MOB_LAYER // Overhead - density = TRUE - circuit = /obj/item/circuitboard/machine/recycler - var/safety_mode = FALSE // Temporarily stops machine if it detects a mob - var/icon_name = "grinder-o" - var/blood = 0 - var/eat_dir = WEST - var/amount_produced = 50 - var/crush_damage = 1000 - var/eat_victim_items = TRUE - var/item_recycle_sound = 'sound/items/welder.ogg' - -/obj/machinery/recycler/Initialize() - AddComponent(/datum/component/butchering/recycler, 1, amount_produced,amount_produced/5) - AddComponent(/datum/component/material_container, list(/datum/material/iron, /datum/material/glass, /datum/material/silver, /datum/material/plasma, /datum/material/gold, /datum/material/diamond, /datum/material/plastic, /datum/material/uranium, /datum/material/bananium, /datum/material/titanium, /datum/material/bluespace), INFINITY, FALSE, null, null, null, TRUE) - . = ..() - update_icon() - req_one_access = get_all_accesses() + get_all_centcom_access() - -/obj/machinery/recycler/RefreshParts() - var/amt_made = 0 - var/mat_mod = 0 - for(var/obj/item/stock_parts/matter_bin/B in component_parts) - mat_mod = 2 * B.rating - mat_mod *= 50000 - for(var/obj/item/stock_parts/manipulator/M in component_parts) - amt_made = 12.5 * M.rating //% of materials salvaged - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - materials.max_amount = mat_mod - amount_produced = min(50, amt_made) + 50 - var/datum/component/butchering/butchering = GetComponent(/datum/component/butchering/recycler) - butchering.effectiveness = amount_produced - butchering.bonus_modifier = amount_produced/5 - -/obj/machinery/recycler/examine(mob/user) - . = ..() - . += "Reclaiming [amount_produced]% of materials salvaged." - . += {"The power light is [(machine_stat & NOPOWER) ? "off" : "on"]. - The safety-mode light is [safety_mode ? "on" : "off"]. - The safety-sensors status light is [obj_flags & EMAGGED ? "off" : "on"]."} - - -/obj/machinery/recycler/attackby(obj/item/I, mob/user, params) - if(default_deconstruction_screwdriver(user, "grinder-oOpen", "grinder-o0", I)) - return - - if(default_pry_open(I)) - return - - if(default_unfasten_wrench(user, I)) - return - - if(default_deconstruction_crowbar(I)) - return - return ..() - -/obj/machinery/recycler/emag_act(mob/user) - if(obj_flags & EMAGGED) - return - obj_flags |= EMAGGED - if(safety_mode) - safety_mode = FALSE - update_icon() - playsound(src, "sparks", 75, TRUE, -1) - to_chat(user, "You use the cryptographic sequencer on [src].") - -/obj/machinery/recycler/update_icon_state() - ..() - var/is_powered = !(machine_stat & (BROKEN|NOPOWER)) - if(safety_mode) - is_powered = FALSE - icon_state = icon_name + "[is_powered]" + "[(blood ? "bld" : "")]" // add the blood tag at the end - -/obj/machinery/recycler/CanAllowThrough(atom/movable/AM) - . = ..() - if(!anchored) - return - var/move_dir = get_dir(loc, AM.loc) - if(move_dir == eat_dir) - return TRUE - -/obj/machinery/recycler/Crossed(atom/movable/AM) - eat(AM) - . = ..() - -/obj/machinery/recycler/proc/eat(atom/movable/AM0, sound=TRUE) - if(machine_stat & (BROKEN|NOPOWER)) - return - if(safety_mode) - return - if(!isturf(AM0.loc)) - return //I don't know how you called Crossed() but stop it. - - var/list/to_eat = AM0.GetAllContents() - - var/living_detected = FALSE //technically includes silicons as well but eh - var/list/nom = list() - var/list/crunchy_nom = list() //Mobs have to be handled differently so they get a different list instead of checking them multiple times. - - for(var/i in to_eat) - var/atom/movable/AM = i - if(istype(AM, /obj/item)) - var/obj/item/bodypart/head/as_head = AM - var/obj/item/mmi/as_mmi = AM - if(istype(AM, /obj/item/organ/brain) || (istype(as_head) && as_head.brain) || (istype(as_mmi) && as_mmi.brain) || istype(AM, /obj/item/dullahan_relay)) - living_detected = TRUE - nom += AM - else if(isliving(AM)) - living_detected = TRUE - crunchy_nom += AM - var/not_eaten = to_eat.len - nom.len - crunchy_nom.len - if(living_detected) // First, check if we have any living beings detected. - if(obj_flags & EMAGGED) - for(var/CRUNCH in crunchy_nom) // Eat them and keep going because we don't care about safety. - if(isliving(CRUNCH)) // MMIs and brains will get eaten like normal items - crush_living(CRUNCH) - else // Stop processing right now without eating anything. - emergency_stop() - return - for(var/nommed in nom) - recycle_item(nommed) - if(nom.len && sound) - playsound(src, item_recycle_sound, (50 + nom.len*5), TRUE, nom.len, ignore_walls = (nom.len - 10)) // As a substitute for playing 50 sounds at once. - if(not_eaten) - playsound(src, 'sound/machines/buzz-sigh.ogg', (50 + not_eaten*5), FALSE, not_eaten, ignore_walls = (not_eaten - 10)) // Ditto. - if(!ismob(AM0)) - AM0.moveToNullspace() - qdel(AM0) - else // Lets not move a mob to nullspace and qdel it, yes? - for(var/i in AM0.contents) - var/atom/movable/content = i - content.moveToNullspace() - qdel(content) - -/obj/machinery/recycler/proc/recycle_item(obj/item/I) - - var/obj/item/grown/log/L = I - if(istype(L)) - var/seed_modifier = 0 - if(L.seed) - seed_modifier = round(L.seed.potency / 25) - new L.plank_type(loc, 1 + seed_modifier) - else - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - var/material_amount = materials.get_item_material_amount(I) - if(!material_amount) - return - materials.insert_item(I, multiplier = (amount_produced / 100)) - materials.retrieve_all() - - -/obj/machinery/recycler/proc/emergency_stop() - playsound(src, 'sound/machines/buzz-sigh.ogg', 50, FALSE) - safety_mode = TRUE - update_icon() - addtimer(CALLBACK(src, .proc/reboot), SAFETY_COOLDOWN) - -/obj/machinery/recycler/proc/reboot() - playsound(src, 'sound/machines/ping.ogg', 50, FALSE) - safety_mode = FALSE - update_icon() - -/obj/machinery/recycler/proc/crush_living(mob/living/L) - - L.forceMove(loc) - - if(issilicon(L)) - playsound(src, 'sound/items/welder.ogg', 50, TRUE) - else - playsound(src, 'sound/effects/splat.ogg', 50, TRUE) - - if(iscarbon(L)) - if(L.stat == CONSCIOUS) - L.say("ARRRRRRRRRRRGH!!!", forced="recycler grinding") - add_mob_blood(L) - - if(!blood && !issilicon(L)) - blood = TRUE - update_icon() - - // Instantly lie down, also go unconscious from the pain, before you die. - L.Unconscious(100) - L.adjustBruteLoss(crush_damage) - -/obj/machinery/recycler/deathtrap - name = "dangerous old crusher" - obj_flags = CAN_BE_HIT | EMAGGED - crush_damage = 120 - flags_1 = NODECONSTRUCT_1 - -/obj/item/paper/guides/recycler - name = "paper - 'garbage duty instructions'" - info = "

                    New Assignment

                    You have been assigned to collect garbage from trash bins, located around the station. The crewmembers will put their trash into it and you will collect the said trash.

                    There is a recycling machine near your closet, inside maintenance; use it to recycle the trash for a small chance to get useful minerals. Then deliver these minerals to cargo or engineering. You are our last hope for a clean station, do not screw this up!" - -#undef SAFETY_COOLDOWN +#define SAFETY_COOLDOWN 100 + +/obj/machinery/recycler + name = "recycler" + desc = "A large crushing machine used to recycle small items inefficiently. There are lights on the side." + icon = 'icons/obj/recycling.dmi' + icon_state = "grinder-o0" + layer = ABOVE_ALL_MOB_LAYER // Overhead + density = TRUE + circuit = /obj/item/circuitboard/machine/recycler + var/safety_mode = FALSE // Temporarily stops machine if it detects a mob + var/icon_name = "grinder-o" + var/blood = 0 + var/eat_dir = WEST + var/amount_produced = 50 + var/crush_damage = 1000 + var/eat_victim_items = TRUE + var/item_recycle_sound = 'sound/items/welder.ogg' + +/obj/machinery/recycler/Initialize() + AddComponent(/datum/component/butchering/recycler, 1, amount_produced,amount_produced/5) + AddComponent(/datum/component/material_container, list(/datum/material/iron, /datum/material/glass, /datum/material/silver, /datum/material/plasma, /datum/material/gold, /datum/material/diamond, /datum/material/plastic, /datum/material/uranium, /datum/material/bananium, /datum/material/titanium, /datum/material/bluespace), INFINITY, FALSE, null, null, null, TRUE) + . = ..() + update_icon() + req_one_access = get_all_accesses() + get_all_centcom_access() + +/obj/machinery/recycler/RefreshParts() + var/amt_made = 0 + var/mat_mod = 0 + for(var/obj/item/stock_parts/matter_bin/B in component_parts) + mat_mod = 2 * B.rating + mat_mod *= 50000 + for(var/obj/item/stock_parts/manipulator/M in component_parts) + amt_made = 12.5 * M.rating //% of materials salvaged + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + materials.max_amount = mat_mod + amount_produced = min(50, amt_made) + 50 + var/datum/component/butchering/butchering = GetComponent(/datum/component/butchering/recycler) + butchering.effectiveness = amount_produced + butchering.bonus_modifier = amount_produced/5 + +/obj/machinery/recycler/examine(mob/user) + . = ..() + . += "Reclaiming [amount_produced]% of materials salvaged." + . += {"The power light is [(machine_stat & NOPOWER) ? "off" : "on"]. + The safety-mode light is [safety_mode ? "on" : "off"]. + The safety-sensors status light is [obj_flags & EMAGGED ? "off" : "on"]."} + + +/obj/machinery/recycler/attackby(obj/item/I, mob/user, params) + if(default_deconstruction_screwdriver(user, "grinder-oOpen", "grinder-o0", I)) + return + + if(default_pry_open(I)) + return + + if(default_unfasten_wrench(user, I)) + return + + if(default_deconstruction_crowbar(I)) + return + return ..() + +/obj/machinery/recycler/emag_act(mob/user) + if(obj_flags & EMAGGED) + return + obj_flags |= EMAGGED + if(safety_mode) + safety_mode = FALSE + update_icon() + playsound(src, "sparks", 75, TRUE, -1) + to_chat(user, "You use the cryptographic sequencer on [src].") + +/obj/machinery/recycler/update_icon_state() + ..() + var/is_powered = !(machine_stat & (BROKEN|NOPOWER)) + if(safety_mode) + is_powered = FALSE + icon_state = icon_name + "[is_powered]" + "[(blood ? "bld" : "")]" // add the blood tag at the end + +/obj/machinery/recycler/CanAllowThrough(atom/movable/AM) + . = ..() + if(!anchored) + return + var/move_dir = get_dir(loc, AM.loc) + if(move_dir == eat_dir) + return TRUE + +/obj/machinery/recycler/Crossed(atom/movable/AM) + eat(AM) + . = ..() + +/obj/machinery/recycler/proc/eat(atom/movable/AM0, sound=TRUE) + if(machine_stat & (BROKEN|NOPOWER)) + return + if(safety_mode) + return + if(!isturf(AM0.loc)) + return //I don't know how you called Crossed() but stop it. + + var/list/to_eat = AM0.GetAllContents() + + var/living_detected = FALSE //technically includes silicons as well but eh + var/list/nom = list() + var/list/crunchy_nom = list() //Mobs have to be handled differently so they get a different list instead of checking them multiple times. + + for(var/i in to_eat) + var/atom/movable/AM = i + if(istype(AM, /obj/item)) + var/obj/item/bodypart/head/as_head = AM + var/obj/item/mmi/as_mmi = AM + if(istype(AM, /obj/item/organ/brain) || (istype(as_head) && as_head.brain) || (istype(as_mmi) && as_mmi.brain) || istype(AM, /obj/item/dullahan_relay)) + living_detected = TRUE + nom += AM + else if(isliving(AM)) + living_detected = TRUE + crunchy_nom += AM + var/not_eaten = to_eat.len - nom.len - crunchy_nom.len + if(living_detected) // First, check if we have any living beings detected. + if(obj_flags & EMAGGED) + for(var/CRUNCH in crunchy_nom) // Eat them and keep going because we don't care about safety. + if(isliving(CRUNCH)) // MMIs and brains will get eaten like normal items + crush_living(CRUNCH) + else // Stop processing right now without eating anything. + emergency_stop() + return + for(var/nommed in nom) + recycle_item(nommed) + if(nom.len && sound) + playsound(src, item_recycle_sound, (50 + nom.len*5), TRUE, nom.len, ignore_walls = (nom.len - 10)) // As a substitute for playing 50 sounds at once. + if(not_eaten) + playsound(src, 'sound/machines/buzz-sigh.ogg', (50 + not_eaten*5), FALSE, not_eaten, ignore_walls = (not_eaten - 10)) // Ditto. + if(!ismob(AM0)) + AM0.moveToNullspace() + qdel(AM0) + else // Lets not move a mob to nullspace and qdel it, yes? + for(var/i in AM0.contents) + var/atom/movable/content = i + content.moveToNullspace() + qdel(content) + +/obj/machinery/recycler/proc/recycle_item(obj/item/I) + + var/obj/item/grown/log/L = I + if(istype(L)) + var/seed_modifier = 0 + if(L.seed) + seed_modifier = round(L.seed.potency / 25) + new L.plank_type(loc, 1 + seed_modifier) + else + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + var/material_amount = materials.get_item_material_amount(I) + if(!material_amount) + return + materials.insert_item(I, multiplier = (amount_produced / 100)) + materials.retrieve_all() + + +/obj/machinery/recycler/proc/emergency_stop() + playsound(src, 'sound/machines/buzz-sigh.ogg', 50, FALSE) + safety_mode = TRUE + update_icon() + addtimer(CALLBACK(src, .proc/reboot), SAFETY_COOLDOWN) + +/obj/machinery/recycler/proc/reboot() + playsound(src, 'sound/machines/ping.ogg', 50, FALSE) + safety_mode = FALSE + update_icon() + +/obj/machinery/recycler/proc/crush_living(mob/living/L) + + L.forceMove(loc) + + if(issilicon(L)) + playsound(src, 'sound/items/welder.ogg', 50, TRUE) + else + playsound(src, 'sound/effects/splat.ogg', 50, TRUE) + + if(iscarbon(L)) + if(L.stat == CONSCIOUS) + L.say("ARRRRRRRRRRRGH!!!", forced="recycler grinding") + add_mob_blood(L) + + if(!blood && !issilicon(L)) + blood = TRUE + update_icon() + + // Instantly lie down, also go unconscious from the pain, before you die. + L.Unconscious(100) + L.adjustBruteLoss(crush_damage) + +/obj/machinery/recycler/deathtrap + name = "dangerous old crusher" + obj_flags = CAN_BE_HIT | EMAGGED + crush_damage = 120 + flags_1 = NODECONSTRUCT_1 + +/obj/item/paper/guides/recycler + name = "paper - 'garbage duty instructions'" + info = "

                    New Assignment

                    You have been assigned to collect garbage from trash bins, located around the station. The crewmembers will put their trash into it and you will collect the said trash.

                    There is a recycling machine near your closet, inside maintenance; use it to recycle the trash for a small chance to get useful minerals. Then deliver these minerals to cargo or engineering. You are our last hope for a clean station, do not screw this up!" + +#undef SAFETY_COOLDOWN diff --git a/code/game/machinery/requests_console.dm b/code/game/machinery/requests_console.dm index d8cb88da1085..7bb2f1ff1fa3 100644 --- a/code/game/machinery/requests_console.dm +++ b/code/game/machinery/requests_console.dm @@ -1,461 +1,461 @@ -/******************** Requests Console ********************/ -/** Originally written by errorage, updated by: Carn, needs more work though. I just added some security fixes */ - -GLOBAL_LIST_EMPTY(req_console_assistance) -GLOBAL_LIST_EMPTY(req_console_supplies) -GLOBAL_LIST_EMPTY(req_console_information) -GLOBAL_LIST_EMPTY(allConsoles) -GLOBAL_LIST_EMPTY(req_console_ckey_departments) - - -#define REQ_SCREEN_MAIN 0 -#define REQ_SCREEN_REQ_ASSISTANCE 1 -#define REQ_SCREEN_REQ_SUPPLIES 2 -#define REQ_SCREEN_RELAY 3 -#define REQ_SCREEN_WRITE 4 -#define REQ_SCREEN_CHOOSE 5 -#define REQ_SCREEN_SENT 6 -#define REQ_SCREEN_ERR 7 -#define REQ_SCREEN_VIEW_MSGS 8 -#define REQ_SCREEN_AUTHENTICATE 9 -#define REQ_SCREEN_ANNOUNCE 10 - -#define REQ_EMERGENCY_SECURITY 1 -#define REQ_EMERGENCY_ENGINEERING 2 -#define REQ_EMERGENCY_MEDICAL 3 - -/obj/machinery/requests_console - name = "requests console" - desc = "A console intended to send requests to different departments on the station." - icon = 'waspstation/icons/obj/terminals.dmi' //WaspStation Edit - Better Icons - icon_state = "req_comp0" - var/department = "Unknown" //The list of all departments on the station (Determined from this variable on each unit) Set this to the same thing if you want several consoles in one department - var/list/messages = list() //List of all messages - var/departmentType = 0 //bitflag - // 0 = none (not listed, can only replied to) - // assistance = 1 - // supplies = 2 - // info = 4 - // assistance + supplies = 3 - // assistance + info = 5 - // supplies + info = 6 - // assistance + supplies + info = 7 - var/newmessagepriority = REQ_NO_NEW_MESSAGE - var/screen = REQ_SCREEN_MAIN - // 0 = main menu, - // 1 = req. assistance, - // 2 = req. supplies - // 3 = relay information - // 4 = write msg - not used - // 5 = choose priority - not used - // 6 = sent successfully - // 7 = sent unsuccessfully - // 8 = view messages - // 9 = authentication before sending - // 10 = send announcement - var/silent = FALSE // set to 1 for it not to beep all the time - var/hackState = FALSE - var/announcementConsole = FALSE // FALSE = This console cannot be used to send department announcements, TRUE = This console can send department announcements - var/open = FALSE // TRUE if open - var/announceAuth = FALSE //Will be set to 1 when you authenticate yourself for announcements - var/msgVerified = "" //Will contain the name of the person who verified it - var/msgStamped = "" //If a message is stamped, this will contain the stamp name - var/message = "" - var/to_department = "" //the department which will be receiving the message - var/priority = REQ_NO_NEW_MESSAGE //Priority of the message being sent - var/obj/item/radio/Radio - var/emergency //If an emergency has been called by this device. Acts as both a cooldown and lets the responder know where it the emergency was triggered from - var/receive_ore_updates = FALSE //If ore redemption machines will send an update when it receives new ores. - max_integrity = 300 - armor = list("melee" = 70, "bullet" = 30, "laser" = 30, "energy" = 30, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 90) - -/obj/machinery/requests_console/update_icon_state() - if(machine_stat & NOPOWER) - set_light(0) - else - set_light(1.4,0.7,"#34D352")//green light - if(open) - if(!hackState) - icon_state="req_comp_open" - else - icon_state="req_comp_rewired" - else if(machine_stat & NOPOWER) - if(icon_state != "req_comp_off") - icon_state = "req_comp_off" - else - if(emergency || (newmessagepriority == REQ_EXTREME_MESSAGE_PRIORITY)) - icon_state = "req_comp3" - else if(newmessagepriority == REQ_HIGH_MESSAGE_PRIORITY) - icon_state = "req_comp2" - else if(newmessagepriority == REQ_NORMAL_MESSAGE_PRIORITY) - icon_state = "req_comp1" - else - icon_state = "req_comp0" - -/obj/machinery/requests_console/Initialize() - . = ..() - name = "\improper [department] requests console" - GLOB.allConsoles += src - - if(departmentType) - - if((departmentType & REQ_DEP_TYPE_ASSISTANCE) && !(department in GLOB.req_console_assistance)) - GLOB.req_console_assistance += department - - if((departmentType & REQ_DEP_TYPE_SUPPLIES) && !(department in GLOB.req_console_supplies)) - GLOB.req_console_supplies += department - - if((departmentType & REQ_DEP_TYPE_INFORMATION) && !(department in GLOB.req_console_information)) - GLOB.req_console_information += department - - GLOB.req_console_ckey_departments[ckey(department)] = department - - Radio = new /obj/item/radio(src) - Radio.listening = 0 - -/obj/machinery/requests_console/Destroy() - QDEL_NULL(Radio) - GLOB.allConsoles -= src - return ..() - -/obj/machinery/requests_console/ui_interact(mob/user) - . = ..() - var/dat = "" - if(!open) - switch(screen) - if(REQ_SCREEN_MAIN) - announceAuth = FALSE - if (newmessagepriority == REQ_NORMAL_MESSAGE_PRIORITY) - dat += "
                    There are new messages

                    " - else if (newmessagepriority == REQ_HIGH_MESSAGE_PRIORITY) - dat += "
                    There are new PRIORITY messages

                    " - else if (newmessagepriority == REQ_EXTREME_MESSAGE_PRIORITY) - dat += "
                    There are new EXTREME PRIORITY messages

                    " - dat += "View Messages

                    " - - dat += "Request Assistance
                    " - dat += "Request Supplies
                    " - dat += "Relay Anonymous Information

                    " - - if(!emergency) - dat += "Emergency: Security
                    " - dat += "Emergency: Engineering
                    " - dat += "Emergency: Medical

                    " - else - dat += "[emergency] has been dispatched to this location.

                    " - - if(announcementConsole) - dat += "Send Station-wide Announcement

                    " - if (silent) - dat += "Speaker OFF" - else - dat += "Speaker ON" - if(REQ_SCREEN_REQ_ASSISTANCE) - dat += "Which department do you need assistance from?

                    " - dat += departments_table(GLOB.req_console_assistance) - - if(REQ_SCREEN_REQ_SUPPLIES) - dat += "Which department do you need supplies from?

                    " - dat += departments_table(GLOB.req_console_supplies) - - if(REQ_SCREEN_RELAY) - dat += "Which department would you like to send information to?

                    " - dat += departments_table(GLOB.req_console_information) - - if(REQ_SCREEN_SENT) - dat += "Message sent.

                    " - dat += "<< Back
                    " - - if(REQ_SCREEN_ERR) - dat += "An error occurred.

                    " - dat += "<< Back
                    " - - if(REQ_SCREEN_VIEW_MSGS) - for (var/obj/machinery/requests_console/Console in GLOB.allConsoles) - if (Console.department == department) - Console.newmessagepriority = REQ_NO_NEW_MESSAGE - Console.update_icon() - - newmessagepriority = REQ_NO_NEW_MESSAGE - update_icon() - var/messageComposite = "" - for(var/msg in messages) // This puts more recent messages at the *top*, where they belong. - messageComposite = "
                    [msg]
                    " + messageComposite - dat += messageComposite - dat += "
                    << Back to Main Menu
                    " - - if(REQ_SCREEN_AUTHENTICATE) - dat += "Message Authentication

                    " - dat += "Message for [to_department]: [message]

                    " - dat += "
                    You may authenticate your message now by scanning your ID or your stamp

                    " - dat += "Validated by: [msgVerified ? msgVerified : "Not Validated"]
                    " - dat += "Stamped by: [msgStamped ? msgStamped : "Not Stamped"]

                    " - dat += "Send Message
                    " - dat += "
                    << Discard Message
                    " - - if(REQ_SCREEN_ANNOUNCE) - dat += "

                    Station-wide Announcement

                    " - if(announceAuth) - dat += "
                    Authentication accepted

                    " - else - dat += "
                    Swipe your card to authenticate yourself

                    " - dat += "Message: [message ? message : "No Message"]
                    " - dat += "[message ? "Edit" : "Write"] Message

                    " - if ((announceAuth || IsAdminGhost(user)) && message) - dat += "Announce Message
                    " - else - dat += "Announce Message
                    " - dat += "
                    << Back
                    " - - if(!dat) - CRASH("No UI for src. Screen var is: [screen]") - var/datum/browser/popup = new(user, "req_console", "[department] Requests Console", 450, 440) - popup.set_content(dat) - popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) - popup.open() - return - -/obj/machinery/requests_console/proc/departments_table(list/req_consoles) - var/dat = "" - dat += "" - for(var/req_dpt in req_consoles) - if (req_dpt != department) - dat += "" - dat += "" - dat += "" - dat += "" - dat += "
                    [req_dpt]Normal High" - if(hackState) - dat += "EXTREME" - dat += "
                    " - dat += "
                    << Back
                    " - return dat - -/obj/machinery/requests_console/Topic(href, href_list) - if(..()) - return - usr.set_machine(src) - add_fingerprint(usr) - - if(href_list["write"]) - to_department = ckey(reject_bad_text(href_list["write"])) //write contains the string of the receiving department's name - - var/new_message = (to_department in GLOB.req_console_ckey_departments) && stripped_input(usr, "Write your message:", "Awaiting Input", "", MAX_MESSAGE_LEN) - if(new_message) - to_department = GLOB.req_console_ckey_departments[to_department] - message = new_message - screen = REQ_SCREEN_AUTHENTICATE - priority = clamp(text2num(href_list["priority"]), REQ_NORMAL_MESSAGE_PRIORITY, REQ_EXTREME_MESSAGE_PRIORITY) - - if(href_list["writeAnnouncement"]) - var/new_message = reject_bad_text(stripped_input(usr, "Write your message:", "Awaiting Input", "", MAX_MESSAGE_LEN)) - if(new_message) - message = new_message - priority = clamp(text2num(href_list["priority"]) || REQ_NORMAL_MESSAGE_PRIORITY, REQ_NORMAL_MESSAGE_PRIORITY, REQ_EXTREME_MESSAGE_PRIORITY) - else - message = "" - announceAuth = FALSE - screen = REQ_SCREEN_MAIN - - if(href_list["sendAnnouncement"]) - if(!announcementConsole) - return - if(isliving(usr)) - var/mob/living/L = usr - message = L.treat_message(message) - minor_announce(message, "[department] Announcement:", from = usr) - GLOB.news_network.SubmitArticle(message, department, "Station Announcements", null) - usr.log_talk(message, LOG_SAY, tag="station announcement from [src]") - message_admins("[ADMIN_LOOKUPFLW(usr)] has made a station announcement from [src] at [AREACOORD(usr)].") - deadchat_broadcast(" made a station announcement from [get_area_name(usr, TRUE)].", "[usr.real_name]", usr, message_type=DEADCHAT_ANNOUNCEMENT) - announceAuth = FALSE - message = "" - screen = REQ_SCREEN_MAIN - - if(href_list["emergency"]) - if(!emergency) - var/radio_freq - switch(text2num(href_list["emergency"])) - if(REQ_EMERGENCY_SECURITY) //Security - radio_freq = FREQ_SECURITY - emergency = "Security" - if(REQ_EMERGENCY_ENGINEERING) //Engineering - radio_freq = FREQ_ENGINEERING - emergency = "Engineering" - if(REQ_EMERGENCY_MEDICAL) //Medical - radio_freq = FREQ_MEDICAL - emergency = "Medical" - if(radio_freq) - Radio.set_frequency(radio_freq) - Radio.talk_into(src,"[emergency] emergency in [department]!!",radio_freq) - update_icon() - addtimer(CALLBACK(src, .proc/clear_emergency), 5 MINUTES) - - if(href_list["send"] && message && to_department && priority) - - var/radio_freq - switch(ckey(to_department)) - if("bridge") - radio_freq = FREQ_COMMAND - if("medbay") - radio_freq = FREQ_MEDICAL - if("science") - radio_freq = FREQ_SCIENCE - if("engineering") - radio_freq = FREQ_ENGINEERING - if("security") - radio_freq = FREQ_SECURITY - if("cargobay" || "mining") - radio_freq = FREQ_SUPPLY - - var/datum/signal/subspace/messaging/rc/signal = new(src, list( - "sender" = department, - "rec_dpt" = to_department, - "send_dpt" = department, - "message" = message, - "verified" = msgVerified, - "stamped" = msgStamped, - "priority" = priority, - "notify_freq" = radio_freq - )) - signal.send_to_receivers() - - screen = signal.data["done"] ? REQ_SCREEN_SENT : REQ_SCREEN_ERR - - //Handle screen switching - if(href_list["setScreen"]) - var/set_screen = clamp(text2num(href_list["setScreen"]) || 0, REQ_SCREEN_MAIN, REQ_SCREEN_ANNOUNCE) - switch(set_screen) - if(REQ_SCREEN_MAIN) - to_department = "" - msgVerified = "" - msgStamped = "" - message = "" - priority = -1 - if(REQ_SCREEN_ANNOUNCE) - if(!announcementConsole) - return - screen = set_screen - - //Handle silencing the console - if(href_list["setSilent"]) - silent = text2num(href_list["setSilent"]) ? TRUE : FALSE - - updateUsrDialog() - -/obj/machinery/requests_console/say_mod(input, message_mode) - if(spantext_char(input, "!", -3)) - return "blares" - else - . = ..() - -/obj/machinery/requests_console/proc/clear_emergency() - emergency = null - update_icon() - -//from message_server.dm: Console.createmessage(data["sender"], data["send_dpt"], data["message"], data["verified"], data["stamped"], data["priority"], data["notify_freq"]) -/obj/machinery/requests_console/proc/createmessage(source, source_department, message, msgVerified, msgStamped, priority, radio_freq) - var/linkedsender - - var/sending = "[message]
                    " - if(msgVerified) - sending = "[sending][msgVerified]
                    " - if(msgStamped) - sending = "[sending][msgStamped]
                    " - - linkedsender = source_department ? "[source_department]" : (source || "unknown") - - var/authentic = (msgVerified || msgStamped) && " (Authenticated)" - var/alert = "Message from [source][authentic]" - var/silenced = silent - var/header = "From: [linkedsender] Received: [station_time_timestamp()]
                    " - - switch(priority) - if(REQ_NORMAL_MESSAGE_PRIORITY) - if(newmessagepriority < REQ_NORMAL_MESSAGE_PRIORITY) - newmessagepriority = REQ_NORMAL_MESSAGE_PRIORITY - update_icon() - - if(REQ_HIGH_MESSAGE_PRIORITY) - header = "High Priority
                    [header]" - alert = "PRIORITY Alert from [source][authentic]" - if(newmessagepriority < REQ_HIGH_MESSAGE_PRIORITY) - newmessagepriority = REQ_HIGH_MESSAGE_PRIORITY - update_icon() - - if(REQ_EXTREME_MESSAGE_PRIORITY) - header = "!!!Extreme Priority!!!
                    [header]" - alert = "EXTREME PRIORITY Alert from [source][authentic]" - silenced = FALSE - if(newmessagepriority < REQ_EXTREME_MESSAGE_PRIORITY) - newmessagepriority = REQ_EXTREME_MESSAGE_PRIORITY - update_icon() - - messages += "[header][sending]" - - if(!silenced) - playsound(src, 'sound/machines/twobeep_high.ogg', 50, TRUE) - say(alert) - - if(radio_freq) - Radio.set_frequency(radio_freq) - Radio.talk_into(src, "[alert]: [message]", radio_freq) - -/obj/machinery/requests_console/attackby(obj/item/O, mob/user, params) - if(O.tool_behaviour == TOOL_CROWBAR) - if(open) - to_chat(user, "You close the maintenance panel.") - open = FALSE - else - to_chat(user, "You open the maintenance panel.") - open = TRUE - update_icon() - return - if(O.tool_behaviour == TOOL_SCREWDRIVER) - if(open) - hackState = !hackState - if(hackState) - to_chat(user, "You modify the wiring.") - else - to_chat(user, "You reset the wiring.") - update_icon() - else - to_chat(user, "You must open the maintenance panel first!") - return - - var/obj/item/card/id/ID = O.GetID() - if(ID) - if(screen == REQ_SCREEN_AUTHENTICATE) - msgVerified = "Verified by [ID.registered_name] ([ID.assignment])" - updateUsrDialog() - if(screen == REQ_SCREEN_ANNOUNCE) - if (ACCESS_RC_ANNOUNCE in ID.access) - announceAuth = TRUE - else - announceAuth = FALSE - to_chat(user, "You are not authorized to send announcements!") - updateUsrDialog() - return - if (istype(O, /obj/item/stamp)) - if(screen == REQ_SCREEN_AUTHENTICATE) - var/obj/item/stamp/T = O - msgStamped = "Stamped with the [T.name]" - updateUsrDialog() - return - return ..() - -#undef REQ_EMERGENCY_SECURITY -#undef REQ_EMERGENCY_ENGINEERING -#undef REQ_EMERGENCY_MEDICAL - -#undef REQ_SCREEN_MAIN -#undef REQ_SCREEN_REQ_ASSISTANCE -#undef REQ_SCREEN_REQ_SUPPLIES -#undef REQ_SCREEN_RELAY -#undef REQ_SCREEN_WRITE -#undef REQ_SCREEN_CHOOSE -#undef REQ_SCREEN_SENT -#undef REQ_SCREEN_ERR -#undef REQ_SCREEN_VIEW_MSGS -#undef REQ_SCREEN_AUTHENTICATE -#undef REQ_SCREEN_ANNOUNCE +/******************** Requests Console ********************/ +/** Originally written by errorage, updated by: Carn, needs more work though. I just added some security fixes */ + +GLOBAL_LIST_EMPTY(req_console_assistance) +GLOBAL_LIST_EMPTY(req_console_supplies) +GLOBAL_LIST_EMPTY(req_console_information) +GLOBAL_LIST_EMPTY(allConsoles) +GLOBAL_LIST_EMPTY(req_console_ckey_departments) + + +#define REQ_SCREEN_MAIN 0 +#define REQ_SCREEN_REQ_ASSISTANCE 1 +#define REQ_SCREEN_REQ_SUPPLIES 2 +#define REQ_SCREEN_RELAY 3 +#define REQ_SCREEN_WRITE 4 +#define REQ_SCREEN_CHOOSE 5 +#define REQ_SCREEN_SENT 6 +#define REQ_SCREEN_ERR 7 +#define REQ_SCREEN_VIEW_MSGS 8 +#define REQ_SCREEN_AUTHENTICATE 9 +#define REQ_SCREEN_ANNOUNCE 10 + +#define REQ_EMERGENCY_SECURITY 1 +#define REQ_EMERGENCY_ENGINEERING 2 +#define REQ_EMERGENCY_MEDICAL 3 + +/obj/machinery/requests_console + name = "requests console" + desc = "A console intended to send requests to different departments on the station." + icon = 'waspstation/icons/obj/terminals.dmi' //WaspStation Edit - Better Icons + icon_state = "req_comp0" + var/department = "Unknown" //The list of all departments on the station (Determined from this variable on each unit) Set this to the same thing if you want several consoles in one department + var/list/messages = list() //List of all messages + var/departmentType = 0 //bitflag + // 0 = none (not listed, can only replied to) + // assistance = 1 + // supplies = 2 + // info = 4 + // assistance + supplies = 3 + // assistance + info = 5 + // supplies + info = 6 + // assistance + supplies + info = 7 + var/newmessagepriority = REQ_NO_NEW_MESSAGE + var/screen = REQ_SCREEN_MAIN + // 0 = main menu, + // 1 = req. assistance, + // 2 = req. supplies + // 3 = relay information + // 4 = write msg - not used + // 5 = choose priority - not used + // 6 = sent successfully + // 7 = sent unsuccessfully + // 8 = view messages + // 9 = authentication before sending + // 10 = send announcement + var/silent = FALSE // set to 1 for it not to beep all the time + var/hackState = FALSE + var/announcementConsole = FALSE // FALSE = This console cannot be used to send department announcements, TRUE = This console can send department announcements + var/open = FALSE // TRUE if open + var/announceAuth = FALSE //Will be set to 1 when you authenticate yourself for announcements + var/msgVerified = "" //Will contain the name of the person who verified it + var/msgStamped = "" //If a message is stamped, this will contain the stamp name + var/message = "" + var/to_department = "" //the department which will be receiving the message + var/priority = REQ_NO_NEW_MESSAGE //Priority of the message being sent + var/obj/item/radio/Radio + var/emergency //If an emergency has been called by this device. Acts as both a cooldown and lets the responder know where it the emergency was triggered from + var/receive_ore_updates = FALSE //If ore redemption machines will send an update when it receives new ores. + max_integrity = 300 + armor = list("melee" = 70, "bullet" = 30, "laser" = 30, "energy" = 30, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 90) + +/obj/machinery/requests_console/update_icon_state() + if(machine_stat & NOPOWER) + set_light(0) + else + set_light(1.4,0.7,"#34D352")//green light + if(open) + if(!hackState) + icon_state="req_comp_open" + else + icon_state="req_comp_rewired" + else if(machine_stat & NOPOWER) + if(icon_state != "req_comp_off") + icon_state = "req_comp_off" + else + if(emergency || (newmessagepriority == REQ_EXTREME_MESSAGE_PRIORITY)) + icon_state = "req_comp3" + else if(newmessagepriority == REQ_HIGH_MESSAGE_PRIORITY) + icon_state = "req_comp2" + else if(newmessagepriority == REQ_NORMAL_MESSAGE_PRIORITY) + icon_state = "req_comp1" + else + icon_state = "req_comp0" + +/obj/machinery/requests_console/Initialize() + . = ..() + name = "\improper [department] requests console" + GLOB.allConsoles += src + + if(departmentType) + + if((departmentType & REQ_DEP_TYPE_ASSISTANCE) && !(department in GLOB.req_console_assistance)) + GLOB.req_console_assistance += department + + if((departmentType & REQ_DEP_TYPE_SUPPLIES) && !(department in GLOB.req_console_supplies)) + GLOB.req_console_supplies += department + + if((departmentType & REQ_DEP_TYPE_INFORMATION) && !(department in GLOB.req_console_information)) + GLOB.req_console_information += department + + GLOB.req_console_ckey_departments[ckey(department)] = department + + Radio = new /obj/item/radio(src) + Radio.listening = 0 + +/obj/machinery/requests_console/Destroy() + QDEL_NULL(Radio) + GLOB.allConsoles -= src + return ..() + +/obj/machinery/requests_console/ui_interact(mob/user) + . = ..() + var/dat = "" + if(!open) + switch(screen) + if(REQ_SCREEN_MAIN) + announceAuth = FALSE + if (newmessagepriority == REQ_NORMAL_MESSAGE_PRIORITY) + dat += "
                    There are new messages

                    " + else if (newmessagepriority == REQ_HIGH_MESSAGE_PRIORITY) + dat += "
                    There are new PRIORITY messages

                    " + else if (newmessagepriority == REQ_EXTREME_MESSAGE_PRIORITY) + dat += "
                    There are new EXTREME PRIORITY messages

                    " + dat += "View Messages

                    " + + dat += "Request Assistance
                    " + dat += "Request Supplies
                    " + dat += "Relay Anonymous Information

                    " + + if(!emergency) + dat += "Emergency: Security
                    " + dat += "Emergency: Engineering
                    " + dat += "Emergency: Medical

                    " + else + dat += "[emergency] has been dispatched to this location.

                    " + + if(announcementConsole) + dat += "Send Station-wide Announcement

                    " + if (silent) + dat += "Speaker OFF" + else + dat += "Speaker ON" + if(REQ_SCREEN_REQ_ASSISTANCE) + dat += "Which department do you need assistance from?

                    " + dat += departments_table(GLOB.req_console_assistance) + + if(REQ_SCREEN_REQ_SUPPLIES) + dat += "Which department do you need supplies from?

                    " + dat += departments_table(GLOB.req_console_supplies) + + if(REQ_SCREEN_RELAY) + dat += "Which department would you like to send information to?

                    " + dat += departments_table(GLOB.req_console_information) + + if(REQ_SCREEN_SENT) + dat += "Message sent.

                    " + dat += "<< Back
                    " + + if(REQ_SCREEN_ERR) + dat += "An error occurred.

                    " + dat += "<< Back
                    " + + if(REQ_SCREEN_VIEW_MSGS) + for (var/obj/machinery/requests_console/Console in GLOB.allConsoles) + if (Console.department == department) + Console.newmessagepriority = REQ_NO_NEW_MESSAGE + Console.update_icon() + + newmessagepriority = REQ_NO_NEW_MESSAGE + update_icon() + var/messageComposite = "" + for(var/msg in messages) // This puts more recent messages at the *top*, where they belong. + messageComposite = "
                    [msg]
                    " + messageComposite + dat += messageComposite + dat += "
                    << Back to Main Menu
                    " + + if(REQ_SCREEN_AUTHENTICATE) + dat += "Message Authentication

                    " + dat += "Message for [to_department]: [message]

                    " + dat += "
                    You may authenticate your message now by scanning your ID or your stamp

                    " + dat += "Validated by: [msgVerified ? msgVerified : "Not Validated"]
                    " + dat += "Stamped by: [msgStamped ? msgStamped : "Not Stamped"]

                    " + dat += "Send Message
                    " + dat += "
                    << Discard Message
                    " + + if(REQ_SCREEN_ANNOUNCE) + dat += "

                    Station-wide Announcement

                    " + if(announceAuth) + dat += "
                    Authentication accepted

                    " + else + dat += "
                    Swipe your card to authenticate yourself

                    " + dat += "Message: [message ? message : "No Message"]
                    " + dat += "[message ? "Edit" : "Write"] Message

                    " + if ((announceAuth || IsAdminGhost(user)) && message) + dat += "Announce Message
                    " + else + dat += "Announce Message
                    " + dat += "
                    << Back
                    " + + if(!dat) + CRASH("No UI for src. Screen var is: [screen]") + var/datum/browser/popup = new(user, "req_console", "[department] Requests Console", 450, 440) + popup.set_content(dat) + popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) + popup.open() + return + +/obj/machinery/requests_console/proc/departments_table(list/req_consoles) + var/dat = "" + dat += "" + for(var/req_dpt in req_consoles) + if (req_dpt != department) + dat += "" + dat += "" + dat += "" + dat += "" + dat += "
                    [req_dpt]Normal High" + if(hackState) + dat += "EXTREME" + dat += "
                    " + dat += "
                    << Back
                    " + return dat + +/obj/machinery/requests_console/Topic(href, href_list) + if(..()) + return + usr.set_machine(src) + add_fingerprint(usr) + + if(href_list["write"]) + to_department = ckey(reject_bad_text(href_list["write"])) //write contains the string of the receiving department's name + + var/new_message = (to_department in GLOB.req_console_ckey_departments) && stripped_input(usr, "Write your message:", "Awaiting Input", "", MAX_MESSAGE_LEN) + if(new_message) + to_department = GLOB.req_console_ckey_departments[to_department] + message = new_message + screen = REQ_SCREEN_AUTHENTICATE + priority = clamp(text2num(href_list["priority"]), REQ_NORMAL_MESSAGE_PRIORITY, REQ_EXTREME_MESSAGE_PRIORITY) + + if(href_list["writeAnnouncement"]) + var/new_message = reject_bad_text(stripped_input(usr, "Write your message:", "Awaiting Input", "", MAX_MESSAGE_LEN)) + if(new_message) + message = new_message + priority = clamp(text2num(href_list["priority"]) || REQ_NORMAL_MESSAGE_PRIORITY, REQ_NORMAL_MESSAGE_PRIORITY, REQ_EXTREME_MESSAGE_PRIORITY) + else + message = "" + announceAuth = FALSE + screen = REQ_SCREEN_MAIN + + if(href_list["sendAnnouncement"]) + if(!announcementConsole) + return + if(isliving(usr)) + var/mob/living/L = usr + message = L.treat_message(message) + minor_announce(message, "[department] Announcement:", from = usr) + GLOB.news_network.SubmitArticle(message, department, "Station Announcements", null) + usr.log_talk(message, LOG_SAY, tag="station announcement from [src]") + message_admins("[ADMIN_LOOKUPFLW(usr)] has made a station announcement from [src] at [AREACOORD(usr)].") + deadchat_broadcast(" made a station announcement from [get_area_name(usr, TRUE)].", "[usr.real_name]", usr, message_type=DEADCHAT_ANNOUNCEMENT) + announceAuth = FALSE + message = "" + screen = REQ_SCREEN_MAIN + + if(href_list["emergency"]) + if(!emergency) + var/radio_freq + switch(text2num(href_list["emergency"])) + if(REQ_EMERGENCY_SECURITY) //Security + radio_freq = FREQ_SECURITY + emergency = "Security" + if(REQ_EMERGENCY_ENGINEERING) //Engineering + radio_freq = FREQ_ENGINEERING + emergency = "Engineering" + if(REQ_EMERGENCY_MEDICAL) //Medical + radio_freq = FREQ_MEDICAL + emergency = "Medical" + if(radio_freq) + Radio.set_frequency(radio_freq) + Radio.talk_into(src,"[emergency] emergency in [department]!!",radio_freq) + update_icon() + addtimer(CALLBACK(src, .proc/clear_emergency), 5 MINUTES) + + if(href_list["send"] && message && to_department && priority) + + var/radio_freq + switch(ckey(to_department)) + if("bridge") + radio_freq = FREQ_COMMAND + if("medbay") + radio_freq = FREQ_MEDICAL + if("science") + radio_freq = FREQ_SCIENCE + if("engineering") + radio_freq = FREQ_ENGINEERING + if("security") + radio_freq = FREQ_SECURITY + if("cargobay" || "mining") + radio_freq = FREQ_SUPPLY + + var/datum/signal/subspace/messaging/rc/signal = new(src, list( + "sender" = department, + "rec_dpt" = to_department, + "send_dpt" = department, + "message" = message, + "verified" = msgVerified, + "stamped" = msgStamped, + "priority" = priority, + "notify_freq" = radio_freq + )) + signal.send_to_receivers() + + screen = signal.data["done"] ? REQ_SCREEN_SENT : REQ_SCREEN_ERR + + //Handle screen switching + if(href_list["setScreen"]) + var/set_screen = clamp(text2num(href_list["setScreen"]) || 0, REQ_SCREEN_MAIN, REQ_SCREEN_ANNOUNCE) + switch(set_screen) + if(REQ_SCREEN_MAIN) + to_department = "" + msgVerified = "" + msgStamped = "" + message = "" + priority = -1 + if(REQ_SCREEN_ANNOUNCE) + if(!announcementConsole) + return + screen = set_screen + + //Handle silencing the console + if(href_list["setSilent"]) + silent = text2num(href_list["setSilent"]) ? TRUE : FALSE + + updateUsrDialog() + +/obj/machinery/requests_console/say_mod(input, message_mode) + if(spantext_char(input, "!", -3)) + return "blares" + else + . = ..() + +/obj/machinery/requests_console/proc/clear_emergency() + emergency = null + update_icon() + +//from message_server.dm: Console.createmessage(data["sender"], data["send_dpt"], data["message"], data["verified"], data["stamped"], data["priority"], data["notify_freq"]) +/obj/machinery/requests_console/proc/createmessage(source, source_department, message, msgVerified, msgStamped, priority, radio_freq) + var/linkedsender + + var/sending = "[message]
                    " + if(msgVerified) + sending = "[sending][msgVerified]
                    " + if(msgStamped) + sending = "[sending][msgStamped]
                    " + + linkedsender = source_department ? "[source_department]" : (source || "unknown") + + var/authentic = (msgVerified || msgStamped) && " (Authenticated)" + var/alert = "Message from [source][authentic]" + var/silenced = silent + var/header = "From: [linkedsender] Received: [station_time_timestamp()]
                    " + + switch(priority) + if(REQ_NORMAL_MESSAGE_PRIORITY) + if(newmessagepriority < REQ_NORMAL_MESSAGE_PRIORITY) + newmessagepriority = REQ_NORMAL_MESSAGE_PRIORITY + update_icon() + + if(REQ_HIGH_MESSAGE_PRIORITY) + header = "High Priority
                    [header]" + alert = "PRIORITY Alert from [source][authentic]" + if(newmessagepriority < REQ_HIGH_MESSAGE_PRIORITY) + newmessagepriority = REQ_HIGH_MESSAGE_PRIORITY + update_icon() + + if(REQ_EXTREME_MESSAGE_PRIORITY) + header = "!!!Extreme Priority!!!
                    [header]" + alert = "EXTREME PRIORITY Alert from [source][authentic]" + silenced = FALSE + if(newmessagepriority < REQ_EXTREME_MESSAGE_PRIORITY) + newmessagepriority = REQ_EXTREME_MESSAGE_PRIORITY + update_icon() + + messages += "[header][sending]" + + if(!silenced) + playsound(src, 'sound/machines/twobeep_high.ogg', 50, TRUE) + say(alert) + + if(radio_freq) + Radio.set_frequency(radio_freq) + Radio.talk_into(src, "[alert]: [message]", radio_freq) + +/obj/machinery/requests_console/attackby(obj/item/O, mob/user, params) + if(O.tool_behaviour == TOOL_CROWBAR) + if(open) + to_chat(user, "You close the maintenance panel.") + open = FALSE + else + to_chat(user, "You open the maintenance panel.") + open = TRUE + update_icon() + return + if(O.tool_behaviour == TOOL_SCREWDRIVER) + if(open) + hackState = !hackState + if(hackState) + to_chat(user, "You modify the wiring.") + else + to_chat(user, "You reset the wiring.") + update_icon() + else + to_chat(user, "You must open the maintenance panel first!") + return + + var/obj/item/card/id/ID = O.GetID() + if(ID) + if(screen == REQ_SCREEN_AUTHENTICATE) + msgVerified = "Verified by [ID.registered_name] ([ID.assignment])" + updateUsrDialog() + if(screen == REQ_SCREEN_ANNOUNCE) + if (ACCESS_RC_ANNOUNCE in ID.access) + announceAuth = TRUE + else + announceAuth = FALSE + to_chat(user, "You are not authorized to send announcements!") + updateUsrDialog() + return + if (istype(O, /obj/item/stamp)) + if(screen == REQ_SCREEN_AUTHENTICATE) + var/obj/item/stamp/T = O + msgStamped = "Stamped with the [T.name]" + updateUsrDialog() + return + return ..() + +#undef REQ_EMERGENCY_SECURITY +#undef REQ_EMERGENCY_ENGINEERING +#undef REQ_EMERGENCY_MEDICAL + +#undef REQ_SCREEN_MAIN +#undef REQ_SCREEN_REQ_ASSISTANCE +#undef REQ_SCREEN_REQ_SUPPLIES +#undef REQ_SCREEN_RELAY +#undef REQ_SCREEN_WRITE +#undef REQ_SCREEN_CHOOSE +#undef REQ_SCREEN_SENT +#undef REQ_SCREEN_ERR +#undef REQ_SCREEN_VIEW_MSGS +#undef REQ_SCREEN_AUTHENTICATE +#undef REQ_SCREEN_ANNOUNCE diff --git a/code/game/machinery/roulette_machine.dm b/code/game/machinery/roulette_machine.dm index 76e502db17c3..8e3f2ca2759c 100644 --- a/code/game/machinery/roulette_machine.dm +++ b/code/game/machinery/roulette_machine.dm @@ -1,425 +1,423 @@ -#define ROULETTE_SINGLES_PAYOUT 36 -#define ROULETTE_SIMPLE_PAYOUT 2 -#define ROULETTE_DOZ_COL_PAYOUT 3 - -#define ROULETTE_BET_ODD "odd" -#define ROULETTE_BET_EVEN "even" -#define ROULETTE_BET_1TO18 "s1-18" //adds s to prevent text2num from working -#define ROULETTE_BET_19TO36 "s19-36" //adds s to prevent text2num from working -#define ROULETTE_BET_1TO12 "s1-12" -#define ROULETTE_BET_13TO24 "s13-24" -#define ROULETTE_BET_25TO36 "s25-36" -#define ROULETTE_BET_2TO1_FIRST "s1st col" -#define ROULETTE_BET_2TO1_SECOND "s2nd col" -#define ROULETTE_BET_2TO1_THIRD "s3rd col" -#define ROULETTE_BET_BLACK "black" -#define ROULETTE_BET_RED "red" - -#define ROULETTE_JACKPOT_AMOUNT 1000 - -///Machine that lets you play roulette. Odds are pre-defined to be the same as European Roulette without the "En Prison" rule -/obj/machinery/roulette - name = "Roulette Table" - desc = "A computerized roulette table. Swipe your ID to play or register yourself as owner!" - icon = 'icons/obj/machines/roulette.dmi' - icon_state = "idle" - density = TRUE - use_power = IDLE_POWER_USE - anchored = FALSE - idle_power_usage = 10 - active_power_usage = 100 - max_integrity = 500 - ui_x = 603 - ui_y = 475 - armor = list("melee" = 45, "bullet" = 30, "laser" = 30, "energy" = 30, "bomb" = 10, "bio" = 30, "rad" = 30, "fire" = 30, "acid" = 30) - var/static/list/numbers = list("0" = "green", "1" = "red", "3" = "red", "5" = "red", "7" = "red", "9" = "red", "12" = "red", "14" = "red", "16" = "red",\ - "18" = "red", "19" = "red", "21" = "red", "23" = "red", "25" = "red", "27" = "red", "30" = "red", "32" = "red", "34" = "red", "36" = "red",\ - "2" = "black", "4" = "black", "6" = "black", "8" = "black", "10" = "black", "11" = "black", "13" = "black", "15" = "black", "17" = "black", "20" = "black",\ - "22" = "black", "24" = "black", "26" = "black", "28" = "black", "29" = "black", "31" = "black", "33" = "black", "35" = "black") - - var/chosen_bet_amount = 10 - var/chosen_bet_type = "0" - var/last_anti_spam = 0 - var/anti_spam_cooldown = 20 - var/obj/item/card/id/my_card - var/playing = FALSE - var/locked = FALSE - var/drop_dir = SOUTH - var/static/list/coin_values = list(/obj/item/coin/diamond = 100, /obj/item/coin/gold = 25, /obj/item/coin/silver = 10, /obj/item/coin/iron = 1) //Make sure this is ordered from left to right. - var/list/coins_to_dispense = list() - var/datum/looping_sound/jackpot/jackpot_loop - var/on = TRUE - var/last_spin = 13 - -/obj/machinery/roulette/Initialize() - . = ..() - jackpot_loop = new(list(src), FALSE) - wires = new /datum/wires/roulette(src) - -/obj/machinery/roulette/obj_break(damage_flag) - prize_theft(0.05) - . = ..() - -/obj/machinery/roulette/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - if(machine_stat & MAINT) - return - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "Roulette", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/machinery/roulette/ui_data(mob/user) - var/list/data = list() - data["IsAnchored"] = anchored - data["BetAmount"] = chosen_bet_amount - data["BetType"] = chosen_bet_type - data["HouseBalance"] = my_card?.registered_account.account_balance - data["LastSpin"] = last_spin - data["Spinning"] = playing - if(ishuman(user)) - var/mob/living/carbon/human/H = user - var/obj/item/card/id/C = H.get_idcard(TRUE) - if(C) - data["AccountBalance"] = C.registered_account.account_balance - else - data["AccountBalance"] = 0 - data["CanUnbolt"] = (H.get_idcard() == my_card) - - return data - -/obj/machinery/roulette/ui_act(action, params) - if(..()) - return - switch(action) - if("anchor") - anchored = !anchored - . = TRUE - if("ChangeBetAmount") - chosen_bet_amount = clamp(text2num(params["amount"]), 10, 500) - . = TRUE - if("ChangeBetType") - chosen_bet_type = params["type"] - . = TRUE - update_icon() // Not applicable to all objects. - -///Handles setting ownership and the betting itself. -/obj/machinery/roulette/attackby(obj/item/W, mob/user, params) - if(machine_stat & MAINT && is_wire_tool(W)) - wires.interact(user) - return - if(playing) - return ..() - if(istype(W, /obj/item/card/id)) - playsound(src, 'sound/machines/card_slide.ogg', 50, TRUE) - - if(machine_stat & MAINT || !on || locked) - to_chat(user, "The machine appears to be disabled.") - return FALSE - - if(my_card) - var/obj/item/card/id/player_card = W - if(player_card.registered_account.account_balance < chosen_bet_amount) //Does the player have enough funds - audible_message("You do not have the funds to play! Lower your bet or get more money.") - playsound(src, 'sound/machines/buzz-two.ogg', 30, TRUE) - return FALSE - if(!chosen_bet_amount || isnull(chosen_bet_type)) - return FALSE - - //double to nothing bets - var/list/doubles = list( - ROULETTE_BET_2TO1_FIRST, - ROULETTE_BET_2TO1_SECOND, - ROULETTE_BET_2TO1_THIRD, - ROULETTE_BET_1TO12, - ROULETTE_BET_13TO24, - ROULETTE_BET_25TO36 - ) - //result of text2num is null if text starts with a character, meaning it's not a singles bet - var/single = !isnull(text2num(chosen_bet_type)) - var/potential_payout_mult - if (single) - potential_payout_mult = ROULETTE_SINGLES_PAYOUT - else - if (chosen_bet_type in doubles) - potential_payout_mult = ROULETTE_DOZ_COL_PAYOUT - else - potential_payout_mult = ROULETTE_SIMPLE_PAYOUT - var/potential_payout = chosen_bet_amount * potential_payout_mult - - if(!check_bartender_funds(potential_payout)) - return FALSE //bartender is too poor - - if(last_anti_spam > world.time) //do not cheat me - return FALSE - - last_anti_spam = world.time + anti_spam_cooldown - - icon_state = "rolling" //Prepare the new icon state for rolling before hand. - flick("flick_up", src) - playsound(src, 'sound/machines/piston_raise.ogg', 70) - playsound(src, 'sound/machines/chime.ogg', 50) - - addtimer(CALLBACK(src, .proc/play, user, player_card, chosen_bet_type, chosen_bet_amount, potential_payout), 4) //Animation first - return TRUE - else - var/obj/item/card/id/new_card = W - if(new_card.registered_account) - var/msg = stripped_input(user, "Name of your roulette wheel:", "Roulette Naming", "Roulette Machine") - if(!msg) - return - name = msg - desc = "Owned by [new_card.registered_account.account_holder], draws directly from [user.p_their()] account." - my_card = new_card - to_chat(user, "You link the wheel to your account.") - power_change() - return - return ..() - -///Proc called when player is going to try and play -/obj/machinery/roulette/proc/play(mob/user, obj/item/card/id/player_id, bet_type, bet_amount, potential_payout) - - var/payout = potential_payout - - my_card.registered_account.transfer_money(player_id.registered_account, bet_amount) - - playing = TRUE - update_icon() - set_light(0) - - var/rolled_number = rand(0, 36) - - playsound(src, 'sound/machines/roulettewheel.ogg', 50) - addtimer(CALLBACK(src, .proc/finish_play, player_id, bet_type, bet_amount, payout, rolled_number), 34) //4 deciseconds more so the animation can play - addtimer(CALLBACK(src, .proc/finish_play_animation), 30) - -/obj/machinery/roulette/proc/finish_play_animation() - icon_state = "idle" - flick("flick_down", src) - playsound(src, 'sound/machines/piston_lower.ogg', 70) - -///Ran after a while to check if the player won or not. -/obj/machinery/roulette/proc/finish_play(obj/item/card/id/player_id, bet_type, bet_amount, potential_payout, rolled_number) - last_spin = rolled_number - - var/is_winner = check_win(bet_type, bet_amount, rolled_number) //Predetermine if we won - var/color = numbers["[rolled_number]"] //Weird syntax, but dict uses strings. - var/result = "[rolled_number] [color]" //e.g. 31 black - - audible_message("The result is: [result]") - - playing = FALSE - update_icon(potential_payout, color, rolled_number, is_winner) - handle_color_light(color) - - if(!is_winner) - audible_message("You lost! Better luck next time") - playsound(src, 'sound/machines/synth_no.ogg', 50) - return FALSE - - audible_message("You have won [potential_payout] credits! Congratulations!") - playsound(src, 'sound/machines/synth_yes.ogg', 50) - - dispense_prize(potential_payout) - -///Fills a list of coins that should be dropped. -/obj/machinery/roulette/proc/dispense_prize(payout) - - if(payout >= ROULETTE_JACKPOT_AMOUNT) - jackpot_loop.start() - - var/remaining_payout = payout - - my_card.registered_account.adjust_money(-payout) - - for(var/coin_type in coin_values) //Loop through all coins from most valuable to least valuable. Try to give as much of that coin (the iterable) as possible until you can't anymore, then move to the next. - var/value = coin_values[coin_type] //Change this to use initial value once we change to mat datum coins. - var/coin_count = round(remaining_payout / value) - - if(!coin_count) //Cant make coins of this type, as we can't reach it's value. - continue - - remaining_payout -= value * coin_count - coins_to_dispense[coin_type] += coin_count - - drop_coin() //Start recursively dropping coins - -///Recursive function that runs until it runs out of coins to drop. -/obj/machinery/roulette/proc/drop_coin() - var/coin_to_drop - - for(var/i in coins_to_dispense) //Find which coin to drop - if(coins_to_dispense[i] <= 0) //Less than 1? go to next potential coin. - continue - coin_to_drop = i - break - - if(!coin_to_drop) //No more coins, stop recursion. - jackpot_loop.stop() - return FALSE - - coins_to_dispense[coin_to_drop] -= 1 - - var/turf/drop_loc = get_step(loc, drop_dir) - var/obj/item/cash = new coin_to_drop(drop_loc) - playsound(cash, pick(list('sound/machines/coindrop.ogg', 'sound/machines/coindrop2.ogg')), 40, TRUE) - - addtimer(CALLBACK(src, .proc/drop_coin), 3) //Recursion time - - -///Fills a list of coins that should be dropped. -/obj/machinery/roulette/proc/prize_theft(percentage) - if(locked) - return - locked = TRUE - var/stolen_cash = my_card.registered_account.account_balance * percentage - dispense_prize(stolen_cash) - - -///Returns TRUE if the player bet correctly. -/obj/machinery/roulette/proc/check_win(bet_type, bet_amount, rolled_number) - var/actual_bet_number = text2num(bet_type) //Only returns the numeric bet types, AKA singles. - if(actual_bet_number) //This means we're playing singles - return rolled_number == actual_bet_number - - switch(bet_type) //Otherwise, we are playing a "special" game, switch on all the cases so we can check. - if(ROULETTE_BET_ODD) - return ISODD(rolled_number) - if(ROULETTE_BET_EVEN) - return ISEVEN(rolled_number) - if(ROULETTE_BET_1TO18) - return (rolled_number >= 1 && rolled_number <= 18) //between 1 to 18 - if(ROULETTE_BET_19TO36) - return rolled_number > 18 //between 19 to 36, no need to check bounds because we wont go higher anyways - if(ROULETTE_BET_BLACK) - return "black" == numbers["[rolled_number]"]//Check if our number is black in the numbers dict - if(ROULETTE_BET_RED) - return "red" == numbers["[rolled_number]"] //Check if our number is black in the numbers dict - if(ROULETTE_BET_1TO12) - return (rolled_number >= 1 && rolled_number <= 12) - if(ROULETTE_BET_13TO24) - return (rolled_number >= 13 && rolled_number <= 24) - if(ROULETTE_BET_25TO36) - return (rolled_number >= 25 && rolled_number <= 36) - if(ROULETTE_BET_2TO1_FIRST) - //You could do this mathematically but w/e this is easy to understand - //numbers in the first column - var/list/winners = list(1, 4, 7, 10, 13, 16, 19, 22, 25, 28, 31, 34) - return (rolled_number in winners) - if(ROULETTE_BET_2TO1_SECOND) - //numbers in the second column - var/list/winners = list(2, 5, 8, 11, 14, 17, 20, 23, 26, 29, 32, 35) - return (rolled_number in winners) - if(ROULETTE_BET_2TO1_THIRD) - //numbers in the third column - var/list/winners = list(3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36) - return (rolled_number in winners) - - -///Returns TRUE if the owner has enough funds to payout -/obj/machinery/roulette/proc/check_bartender_funds(payout) - if(my_card.registered_account.account_balance >= payout) - return TRUE //We got the betting amount - audible_message("The bank account of [my_card.registered_account.account_holder] does not have enough funds to pay out the potential prize, contact them to fill up their account or lower your bet!") - playsound(src, 'sound/machines/buzz-two.ogg', 30, TRUE) - return FALSE - -/obj/machinery/roulette/update_icon(payout, color, rolled_number, is_winner = FALSE) - cut_overlays() - - if(machine_stat & MAINT) - return - - if(playing) - add_overlay("random_numbers") - - if(!payout || !color || isnull(rolled_number)) //Don't fall for tricks. - return - - //Overlay for ring - if(is_winner && payout >= ROULETTE_JACKPOT_AMOUNT) - add_overlay("jackpot") - else - add_overlay(color) - - var/numberright = rolled_number % 10 //Right hand number - var/numberleft = (rolled_number - numberright) / 10 //Left hand number - - var/shift_amount = 2 //How much the icon moves left/right - - if(numberleft != 0) //Don't make the number if we are 0. - var/mutable_appearance/number1 = mutable_appearance(icon, "[numberleft]") - number1.pixel_x = -shift_amount - add_overlay(number1) - else - shift_amount = 0 //We can stay centered. - - var/mutable_appearance/number2 = mutable_appearance(icon, "[numberright]") - number2.pixel_x = shift_amount - add_overlay(number2) - -/obj/machinery/roulette/proc/handle_color_light(color) - switch(color) - if("green") - set_light(2,2, LIGHT_COLOR_GREEN) - if("red") - set_light(2,2, LIGHT_COLOR_RED) - -/obj/machinery/roulette/welder_act(mob/living/user, obj/item/I) - . = ..() - if(machine_stat & MAINT) - to_chat(user, "You start re-attaching the top section of [src]...") - if(I.use_tool(src, user, 30, volume=50)) - to_chat(user, "You re-attach the top section of [src].") - machine_stat &= ~MAINT - icon_state = "idle" - else - to_chat(user, "You start welding the top section from [src]...") - if(I.use_tool(src, user, 30, volume=50)) - to_chat(user, "You removed the top section of [src].") - machine_stat |= MAINT - icon_state = "open" - -/obj/machinery/roulette/proc/shock(mob/user, prb) - if(!on) // unpowered, no shock - return FALSE - if(!prob(prb)) - return FALSE //you lucked out, no shock for you - do_sparks(5, TRUE, src) - if(electrocute_mob(user, get_area(src), src, 1, TRUE)) - return TRUE - else - return FALSE - -/obj/item/roulette_wheel_beacon - name = "roulette wheel beacon" - desc = "N.T. approved roulette wheel beacon, toss it down and you will have a complementary roulette wheel delivered to you." - icon = 'icons/obj/objects.dmi' - icon_state = "floor_beacon" - var/used - -/obj/item/roulette_wheel_beacon/attack_self() - if(used) - return - loc.visible_message("\The [src] begins to beep loudly!") - used = TRUE - addtimer(CALLBACK(src, .proc/launch_payload), 40) - -/obj/item/roulette_wheel_beacon/proc/launch_payload() - var/obj/structure/closet/supplypod/centcompod/toLaunch = new() - - new /obj/machinery/roulette(toLaunch) - - new /obj/effect/DPtarget(drop_location(), toLaunch) - qdel(src) - -#undef ROULETTE_SINGLES_PAYOUT -#undef ROULETTE_SIMPLE_PAYOUT - -#undef ROULETTE_BET_ODD -#undef ROULETTE_BET_EVEN -#undef ROULETTE_BET_1TO18 -#undef ROULETTE_BET_19TO36 -#undef ROULETTE_BET_BLACK -#undef ROULETTE_BET_RED - -#undef ROULETTE_JACKPOT_AMOUNT +#define ROULETTE_SINGLES_PAYOUT 36 +#define ROULETTE_SIMPLE_PAYOUT 2 +#define ROULETTE_DOZ_COL_PAYOUT 3 + +#define ROULETTE_BET_ODD "odd" +#define ROULETTE_BET_EVEN "even" +#define ROULETTE_BET_1TO18 "s1-18" //adds s to prevent text2num from working +#define ROULETTE_BET_19TO36 "s19-36" //adds s to prevent text2num from working +#define ROULETTE_BET_1TO12 "s1-12" +#define ROULETTE_BET_13TO24 "s13-24" +#define ROULETTE_BET_25TO36 "s25-36" +#define ROULETTE_BET_2TO1_FIRST "s1st col" +#define ROULETTE_BET_2TO1_SECOND "s2nd col" +#define ROULETTE_BET_2TO1_THIRD "s3rd col" +#define ROULETTE_BET_BLACK "black" +#define ROULETTE_BET_RED "red" + +#define ROULETTE_JACKPOT_AMOUNT 1000 + +///Machine that lets you play roulette. Odds are pre-defined to be the same as European Roulette without the "En Prison" rule +/obj/machinery/roulette + name = "Roulette Table" + desc = "A computerized roulette table. Swipe your ID to play or register yourself as owner!" + icon = 'icons/obj/machines/roulette.dmi' + icon_state = "idle" + density = TRUE + use_power = IDLE_POWER_USE + anchored = FALSE + idle_power_usage = 10 + active_power_usage = 100 + max_integrity = 500 + armor = list("melee" = 45, "bullet" = 30, "laser" = 30, "energy" = 30, "bomb" = 10, "bio" = 30, "rad" = 30, "fire" = 30, "acid" = 30) + var/static/list/numbers = list("0" = "green", "1" = "red", "3" = "red", "5" = "red", "7" = "red", "9" = "red", "12" = "red", "14" = "red", "16" = "red",\ + "18" = "red", "19" = "red", "21" = "red", "23" = "red", "25" = "red", "27" = "red", "30" = "red", "32" = "red", "34" = "red", "36" = "red",\ + "2" = "black", "4" = "black", "6" = "black", "8" = "black", "10" = "black", "11" = "black", "13" = "black", "15" = "black", "17" = "black", "20" = "black",\ + "22" = "black", "24" = "black", "26" = "black", "28" = "black", "29" = "black", "31" = "black", "33" = "black", "35" = "black") + + var/chosen_bet_amount = 10 + var/chosen_bet_type = "0" + var/last_anti_spam = 0 + var/anti_spam_cooldown = 20 + var/obj/item/card/id/my_card + var/playing = FALSE + var/locked = FALSE + var/drop_dir = SOUTH + var/static/list/coin_values = list(/obj/item/coin/diamond = 100, /obj/item/coin/gold = 25, /obj/item/coin/silver = 10, /obj/item/coin/iron = 1) //Make sure this is ordered from left to right. + var/list/coins_to_dispense = list() + var/datum/looping_sound/jackpot/jackpot_loop + var/on = TRUE + var/last_spin = 13 + +/obj/machinery/roulette/Initialize() + . = ..() + jackpot_loop = new(list(src), FALSE) + wires = new /datum/wires/roulette(src) + +/obj/machinery/roulette/obj_break(damage_flag) + prize_theft(0.05) + . = ..() + +/obj/machinery/roulette/ui_interact(mob/user, datum/tgui/ui) + if(machine_stat & MAINT) + return + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "Roulette", name) + ui.open() + +/obj/machinery/roulette/ui_data(mob/user) + var/list/data = list() + data["IsAnchored"] = anchored + data["BetAmount"] = chosen_bet_amount + data["BetType"] = chosen_bet_type + data["HouseBalance"] = my_card?.registered_account.account_balance + data["LastSpin"] = last_spin + data["Spinning"] = playing + if(ishuman(user)) + var/mob/living/carbon/human/H = user + var/obj/item/card/id/C = H.get_idcard(TRUE) + if(C) + data["AccountBalance"] = C.registered_account.account_balance + else + data["AccountBalance"] = 0 + data["CanUnbolt"] = (H.get_idcard() == my_card) + + return data + +/obj/machinery/roulette/ui_act(action, params) + if(..()) + return + switch(action) + if("anchor") + anchored = !anchored + . = TRUE + if("ChangeBetAmount") + chosen_bet_amount = clamp(text2num(params["amount"]), 10, 500) + . = TRUE + if("ChangeBetType") + chosen_bet_type = params["type"] + . = TRUE + update_icon() // Not applicable to all objects. + +///Handles setting ownership and the betting itself. +/obj/machinery/roulette/attackby(obj/item/W, mob/user, params) + if(machine_stat & MAINT && is_wire_tool(W)) + wires.interact(user) + return + if(playing) + return ..() + if(istype(W, /obj/item/card/id)) + playsound(src, 'sound/machines/card_slide.ogg', 50, TRUE) + + if(machine_stat & MAINT || !on || locked) + to_chat(user, "The machine appears to be disabled.") + return FALSE + + if(my_card) + var/obj/item/card/id/player_card = W + if(player_card.registered_account.account_balance < chosen_bet_amount) //Does the player have enough funds + audible_message("You do not have the funds to play! Lower your bet or get more money.") + playsound(src, 'sound/machines/buzz-two.ogg', 30, TRUE) + return FALSE + if(!chosen_bet_amount || isnull(chosen_bet_type)) + return FALSE + + //double to nothing bets + var/list/doubles = list( + ROULETTE_BET_2TO1_FIRST, + ROULETTE_BET_2TO1_SECOND, + ROULETTE_BET_2TO1_THIRD, + ROULETTE_BET_1TO12, + ROULETTE_BET_13TO24, + ROULETTE_BET_25TO36 + ) + //result of text2num is null if text starts with a character, meaning it's not a singles bet + var/single = !isnull(text2num(chosen_bet_type)) + var/potential_payout_mult + if (single) + potential_payout_mult = ROULETTE_SINGLES_PAYOUT + else + if (chosen_bet_type in doubles) + potential_payout_mult = ROULETTE_DOZ_COL_PAYOUT + else + potential_payout_mult = ROULETTE_SIMPLE_PAYOUT + var/potential_payout = chosen_bet_amount * potential_payout_mult + + if(!check_bartender_funds(potential_payout)) + return FALSE //bartender is too poor + + if(last_anti_spam > world.time) //do not cheat me + return FALSE + + last_anti_spam = world.time + anti_spam_cooldown + + icon_state = "rolling" //Prepare the new icon state for rolling before hand. + flick("flick_up", src) + playsound(src, 'sound/machines/piston_raise.ogg', 70) + playsound(src, 'sound/machines/chime.ogg', 50) + + addtimer(CALLBACK(src, .proc/play, user, player_card, chosen_bet_type, chosen_bet_amount, potential_payout), 4) //Animation first + return TRUE + else + var/obj/item/card/id/new_card = W + if(new_card.registered_account) + var/msg = stripped_input(user, "Name of your roulette wheel:", "Roulette Naming", "Roulette Machine") + if(!msg) + return + name = msg + desc = "Owned by [new_card.registered_account.account_holder], draws directly from [user.p_their()] account." + my_card = new_card + to_chat(user, "You link the wheel to your account.") + power_change() + return + return ..() + +///Proc called when player is going to try and play +/obj/machinery/roulette/proc/play(mob/user, obj/item/card/id/player_id, bet_type, bet_amount, potential_payout) + + var/payout = potential_payout + + my_card.registered_account.transfer_money(player_id.registered_account, bet_amount) + + playing = TRUE + update_icon() + set_light(0) + + var/rolled_number = rand(0, 36) + + playsound(src, 'sound/machines/roulettewheel.ogg', 50) + addtimer(CALLBACK(src, .proc/finish_play, player_id, bet_type, bet_amount, payout, rolled_number), 34) //4 deciseconds more so the animation can play + addtimer(CALLBACK(src, .proc/finish_play_animation), 30) + +/obj/machinery/roulette/proc/finish_play_animation() + icon_state = "idle" + flick("flick_down", src) + playsound(src, 'sound/machines/piston_lower.ogg', 70) + +///Ran after a while to check if the player won or not. +/obj/machinery/roulette/proc/finish_play(obj/item/card/id/player_id, bet_type, bet_amount, potential_payout, rolled_number) + last_spin = rolled_number + + var/is_winner = check_win(bet_type, bet_amount, rolled_number) //Predetermine if we won + var/color = numbers["[rolled_number]"] //Weird syntax, but dict uses strings. + var/result = "[rolled_number] [color]" //e.g. 31 black + + audible_message("The result is: [result]") + + playing = FALSE + update_icon(potential_payout, color, rolled_number, is_winner) + handle_color_light(color) + + if(!is_winner) + audible_message("You lost! Better luck next time") + playsound(src, 'sound/machines/synth_no.ogg', 50) + return FALSE + + audible_message("You have won [potential_payout] credits! Congratulations!") + playsound(src, 'sound/machines/synth_yes.ogg', 50) + + dispense_prize(potential_payout) + +///Fills a list of coins that should be dropped. +/obj/machinery/roulette/proc/dispense_prize(payout) + + if(payout >= ROULETTE_JACKPOT_AMOUNT) + jackpot_loop.start() + + var/remaining_payout = payout + + my_card.registered_account.adjust_money(-payout) + + for(var/coin_type in coin_values) //Loop through all coins from most valuable to least valuable. Try to give as much of that coin (the iterable) as possible until you can't anymore, then move to the next. + var/value = coin_values[coin_type] //Change this to use initial value once we change to mat datum coins. + var/coin_count = round(remaining_payout / value) + + if(!coin_count) //Cant make coins of this type, as we can't reach it's value. + continue + + remaining_payout -= value * coin_count + coins_to_dispense[coin_type] += coin_count + + drop_coin() //Start recursively dropping coins + +///Recursive function that runs until it runs out of coins to drop. +/obj/machinery/roulette/proc/drop_coin() + var/coin_to_drop + + for(var/i in coins_to_dispense) //Find which coin to drop + if(coins_to_dispense[i] <= 0) //Less than 1? go to next potential coin. + continue + coin_to_drop = i + break + + if(!coin_to_drop) //No more coins, stop recursion. + jackpot_loop.stop() + return FALSE + + coins_to_dispense[coin_to_drop] -= 1 + + var/turf/drop_loc = get_step(loc, drop_dir) + var/obj/item/cash = new coin_to_drop(drop_loc) + playsound(cash, pick(list('sound/machines/coindrop.ogg', 'sound/machines/coindrop2.ogg')), 40, TRUE) + + addtimer(CALLBACK(src, .proc/drop_coin), 3) //Recursion time + + +///Fills a list of coins that should be dropped. +/obj/machinery/roulette/proc/prize_theft(percentage) + if(locked) + return + locked = TRUE + var/stolen_cash = my_card.registered_account.account_balance * percentage + dispense_prize(stolen_cash) + + +///Returns TRUE if the player bet correctly. +/obj/machinery/roulette/proc/check_win(bet_type, bet_amount, rolled_number) + var/actual_bet_number = text2num(bet_type) //Only returns the numeric bet types, AKA singles. + if(actual_bet_number) //This means we're playing singles + return rolled_number == actual_bet_number + + switch(bet_type) //Otherwise, we are playing a "special" game, switch on all the cases so we can check. + if(ROULETTE_BET_ODD) + return ISODD(rolled_number) + if(ROULETTE_BET_EVEN) + return ISEVEN(rolled_number) + if(ROULETTE_BET_1TO18) + return (rolled_number >= 1 && rolled_number <= 18) //between 1 to 18 + if(ROULETTE_BET_19TO36) + return rolled_number > 18 //between 19 to 36, no need to check bounds because we wont go higher anyways + if(ROULETTE_BET_BLACK) + return "black" == numbers["[rolled_number]"]//Check if our number is black in the numbers dict + if(ROULETTE_BET_RED) + return "red" == numbers["[rolled_number]"] //Check if our number is black in the numbers dict + if(ROULETTE_BET_1TO12) + return (rolled_number >= 1 && rolled_number <= 12) + if(ROULETTE_BET_13TO24) + return (rolled_number >= 13 && rolled_number <= 24) + if(ROULETTE_BET_25TO36) + return (rolled_number >= 25 && rolled_number <= 36) + if(ROULETTE_BET_2TO1_FIRST) + //You could do this mathematically but w/e this is easy to understand + //numbers in the first column + var/list/winners = list(1, 4, 7, 10, 13, 16, 19, 22, 25, 28, 31, 34) + return (rolled_number in winners) + if(ROULETTE_BET_2TO1_SECOND) + //numbers in the second column + var/list/winners = list(2, 5, 8, 11, 14, 17, 20, 23, 26, 29, 32, 35) + return (rolled_number in winners) + if(ROULETTE_BET_2TO1_THIRD) + //numbers in the third column + var/list/winners = list(3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36) + return (rolled_number in winners) + + +///Returns TRUE if the owner has enough funds to payout +/obj/machinery/roulette/proc/check_bartender_funds(payout) + if(my_card.registered_account.account_balance >= payout) + return TRUE //We got the betting amount + audible_message("The bank account of [my_card.registered_account.account_holder] does not have enough funds to pay out the potential prize, contact them to fill up their account or lower your bet!") + playsound(src, 'sound/machines/buzz-two.ogg', 30, TRUE) + return FALSE + +/obj/machinery/roulette/update_icon(payout, color, rolled_number, is_winner = FALSE) + cut_overlays() + + if(machine_stat & MAINT) + return + + if(playing) + add_overlay("random_numbers") + + if(!payout || !color || isnull(rolled_number)) //Don't fall for tricks. + return + + //Overlay for ring + if(is_winner && payout >= ROULETTE_JACKPOT_AMOUNT) + add_overlay("jackpot") + else + add_overlay(color) + + var/numberright = rolled_number % 10 //Right hand number + var/numberleft = (rolled_number - numberright) / 10 //Left hand number + + var/shift_amount = 2 //How much the icon moves left/right + + if(numberleft != 0) //Don't make the number if we are 0. + var/mutable_appearance/number1 = mutable_appearance(icon, "[numberleft]") + number1.pixel_x = -shift_amount + add_overlay(number1) + else + shift_amount = 0 //We can stay centered. + + var/mutable_appearance/number2 = mutable_appearance(icon, "[numberright]") + number2.pixel_x = shift_amount + add_overlay(number2) + +/obj/machinery/roulette/proc/handle_color_light(color) + switch(color) + if("green") + set_light(2,2, LIGHT_COLOR_GREEN) + if("red") + set_light(2,2, LIGHT_COLOR_RED) + +/obj/machinery/roulette/welder_act(mob/living/user, obj/item/I) + . = ..() + if(machine_stat & MAINT) + to_chat(user, "You start re-attaching the top section of [src]...") + if(I.use_tool(src, user, 30, volume=50)) + to_chat(user, "You re-attach the top section of [src].") + machine_stat &= ~MAINT + icon_state = "idle" + else + to_chat(user, "You start welding the top section from [src]...") + if(I.use_tool(src, user, 30, volume=50)) + to_chat(user, "You removed the top section of [src].") + machine_stat |= MAINT + icon_state = "open" + +/obj/machinery/roulette/proc/shock(mob/user, prb) + if(!on) // unpowered, no shock + return FALSE + if(!prob(prb)) + return FALSE //you lucked out, no shock for you + do_sparks(5, TRUE, src) + if(electrocute_mob(user, get_area(src), src, 1, TRUE)) + return TRUE + else + return FALSE + +/obj/item/roulette_wheel_beacon + name = "roulette wheel beacon" + desc = "N.T. approved roulette wheel beacon, toss it down and you will have a complementary roulette wheel delivered to you." + icon = 'icons/obj/objects.dmi' + icon_state = "floor_beacon" + var/used + +/obj/item/roulette_wheel_beacon/attack_self() + if(used) + return + loc.visible_message("\The [src] begins to beep loudly!") + used = TRUE + addtimer(CALLBACK(src, .proc/launch_payload), 40) + +/obj/item/roulette_wheel_beacon/proc/launch_payload() + var/obj/structure/closet/supplypod/centcompod/toLaunch = new() + + new /obj/machinery/roulette(toLaunch) + + new /obj/effect/DPtarget(drop_location(), toLaunch) + qdel(src) + +#undef ROULETTE_SINGLES_PAYOUT +#undef ROULETTE_SIMPLE_PAYOUT + +#undef ROULETTE_BET_ODD +#undef ROULETTE_BET_EVEN +#undef ROULETTE_BET_1TO18 +#undef ROULETTE_BET_19TO36 +#undef ROULETTE_BET_BLACK +#undef ROULETTE_BET_RED + +#undef ROULETTE_JACKPOT_AMOUNT diff --git a/code/game/machinery/scan_gate.dm b/code/game/machinery/scan_gate.dm index e9dc66db6b76..ecff455abef6 100644 --- a/code/game/machinery/scan_gate.dm +++ b/code/game/machinery/scan_gate.dm @@ -27,8 +27,6 @@ use_power = IDLE_POWER_USE idle_power_usage = 50 circuit = /obj/item/circuitboard/machine/scanner_gate - ui_x = 400 - ui_y = 300 var/scanline_timer var/next_beep = 0 //avoids spam @@ -183,11 +181,10 @@ return FALSE return ..() -/obj/machinery/scanner_gate/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/machinery/scanner_gate/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "ScannerGate", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, "ScannerGate", name) ui.open() /obj/machinery/scanner_gate/ui_data() diff --git a/code/game/machinery/spaceheater.dm b/code/game/machinery/spaceheater.dm index 70602a0b6631..f726f4a898e6 100644 --- a/code/game/machinery/spaceheater.dm +++ b/code/game/machinery/spaceheater.dm @@ -13,9 +13,8 @@ max_integrity = 250 armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 10) circuit = /obj/item/circuitboard/machine/space_heater - ui_x = 400 - ui_y = 305 - + /// We don't use area power, we always use the cell + use_power = NO_POWER_USE var/obj/item/stock_parts/cell/cell var/on = FALSE var/mode = HEATER_MODE_STANDBY @@ -170,11 +169,10 @@ else return ..() -/obj/machinery/space_heater/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.physical_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/machinery/space_heater/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "SpaceHeater", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, "SpaceHeater", name) ui.open() /obj/machinery/space_heater/ui_data() diff --git a/code/game/machinery/status_display.dm b/code/game/machinery/status_display.dm index e04ed7c9745a..040bb0ed0215 100644 --- a/code/game/machinery/status_display.dm +++ b/code/game/machinery/status_display.dm @@ -1,371 +1,371 @@ -// Status display -// (formerly Countdown timer display) - -#define CHARS_PER_LINE 5 -#define FONT_SIZE "5pt" -#define FONT_COLOR "#09f" -#define FONT_STYLE "Small Fonts" -#define SCROLL_SPEED 2 - -#define SD_BLANK 0 // 0 = Blank -#define SD_EMERGENCY 1 // 1 = Emergency Shuttle timer -#define SD_MESSAGE 2 // 2 = Arbitrary message(s) -#define SD_PICTURE 3 // 3 = alert picture - -#define SD_AI_EMOTE 1 // 1 = AI emoticon -#define SD_AI_BSOD 2 // 2 = Blue screen of death - -/// Status display which can show images and scrolling text. -/obj/machinery/status_display - name = "status display" - desc = null - icon = 'icons/obj/status_display.dmi' - icon_state = "frame" - density = FALSE - use_power = IDLE_POWER_USE - idle_power_usage = 10 - - maptext_height = 26 - maptext_width = 32 - maptext_y = -1 - - var/message1 = "" // message line 1 - var/message2 = "" // message line 2 - var/index1 // display index for scrolling messages or 0 if non-scrolling - var/index2 - -/// Immediately blank the display. -/obj/machinery/status_display/proc/remove_display() - cut_overlays() - if(maptext) - maptext = "" - -/// Immediately change the display to the given picture. -/obj/machinery/status_display/proc/set_picture(state) - remove_display() - add_overlay(state) - -/// Immediately change the display to the given two lines. -/obj/machinery/status_display/proc/update_display(line1, line2) - line1 = uppertext(line1) - line2 = uppertext(line2) - var/new_text = {"
                    [line1]
                    [line2]
                    "} - if(maptext != new_text) - maptext = new_text - -/// Prepare the display to marquee the given two lines. -/// -/// Call with no arguments to disable. -/obj/machinery/status_display/proc/set_message(m1, m2) - if(m1) - index1 = (length_char(m1) > CHARS_PER_LINE) - message1 = m1 - else - message1 = "" - index1 = 0 - - if(m2) - index2 = (length_char(m2) > CHARS_PER_LINE) - message2 = m2 - else - message2 = "" - index2 = 0 - -// Timed process - performs default marquee action if so needed. -/obj/machinery/status_display/process() - if(machine_stat & NOPOWER) - // No power, no processing. - remove_display() - return PROCESS_KILL - - var/line1 = message1 - if(index1) - line1 = copytext_char("[message1]|[message1]", index1, index1 + CHARS_PER_LINE) - var/message1_len = length_char(message1) - index1 += SCROLL_SPEED - if(index1 > message1_len + 1) - index1 -= (message1_len + 1) - - var/line2 = message2 - if(index2) - line2 = copytext_char("[message2]|[message2]", index2, index2 + CHARS_PER_LINE) - var/message2_len = length_char(message2) - index2 += SCROLL_SPEED - if(index2 > message2_len + 1) - index2 -= (message2_len + 1) - - update_display(line1, line2) - if (!index1 && !index2) - // No marquee, no processing. - return PROCESS_KILL - -/// Update the display and, if necessary, re-enable processing. -/obj/machinery/status_display/proc/update() - if (process() != PROCESS_KILL) - START_PROCESSING(SSmachines, src) - -/obj/machinery/status_display/power_change() - . = ..() - update() - -/obj/machinery/status_display/emp_act(severity) - . = ..() - if(machine_stat & (NOPOWER|BROKEN) || . & EMP_PROTECT_SELF) - return - set_picture("ai_bsod") - -/obj/machinery/status_display/examine(mob/user) - . = ..() - if (message1 || message2) - . += "The display says:" - if (message1) - . += "
                    \t[html_encode(message1)]" - if (message2) - . += "
                    \t[html_encode(message2)]" - -// Helper procs for child display types. -/obj/machinery/status_display/proc/display_shuttle_status(obj/docking_port/mobile/shuttle) - if(!shuttle) - // the shuttle is missing - no processing - update_display("shutl?","") - return PROCESS_KILL - else if(shuttle.timer) - var/line1 = "-[shuttle.getModeStr()]-" - var/line2 = shuttle.getTimerStr() - - if(length_char(line2) > CHARS_PER_LINE) - line2 = "error" - update_display(line1, line2) - else - // don't kill processing, the timer might turn back on - remove_display() - -/obj/machinery/status_display/proc/examine_shuttle(mob/user, obj/docking_port/mobile/shuttle) - if (shuttle) - var/modestr = shuttle.getModeStr() - if (modestr) - if (shuttle.timer) - modestr = "
                    \t[modestr]: [shuttle.getTimerStr()]" - else - modestr = "
                    \t[modestr]" - return "The display says:
                    \t[shuttle.name][modestr]" - else - return "The display says:
                    \tShuttle missing!" - - -/// Evac display which shows shuttle timer or message set by Command. -/obj/machinery/status_display/evac - var/frequency = FREQ_STATUS_DISPLAYS - var/mode = SD_EMERGENCY - var/friendc = FALSE // track if Friend Computer mode - var/last_picture // For when Friend Computer mode is undone - -/obj/machinery/status_display/evac/Initialize() - . = ..() - // register for radio system - SSradio.add_object(src, frequency) - -/obj/machinery/status_display/evac/Destroy() - SSradio.remove_object(src,frequency) - return ..() - -/obj/machinery/status_display/evac/process() - if(machine_stat & NOPOWER) - // No power, no processing. - remove_display() - return PROCESS_KILL - - if(friendc) //Makes all status displays except supply shuttle timer display the eye -- Urist - set_picture("ai_friend") - return PROCESS_KILL - - switch(mode) - if(SD_BLANK) - remove_display() - return PROCESS_KILL - - if(SD_EMERGENCY) - return display_shuttle_status(SSshuttle.emergency) - - if(SD_MESSAGE) - return ..() - - if(SD_PICTURE) - set_picture(last_picture) - return PROCESS_KILL - -/obj/machinery/status_display/evac/examine(mob/user) - . = ..() - if(mode == SD_EMERGENCY) - . += examine_shuttle(user, SSshuttle.emergency) - else if(!message1 && !message2) - . += "The display is blank." - -/obj/machinery/status_display/evac/receive_signal(datum/signal/signal) - switch(signal.data["command"]) - if("blank") - mode = SD_BLANK - set_message(null, null) - if("shuttle") - mode = SD_EMERGENCY - set_message(null, null) - if("message") - mode = SD_MESSAGE - set_message(signal.data["msg1"], signal.data["msg2"]) - if("alert") - mode = SD_PICTURE - last_picture = signal.data["picture_state"] - set_picture(last_picture) - if("friendcomputer") - friendc = !friendc - update() - - -/// Supply display which shows the status of the supply shuttle. -/obj/machinery/status_display/supply - name = "supply display" - -/obj/machinery/status_display/supply/process() - if(machine_stat & NOPOWER) - // No power, no processing. - remove_display() - return PROCESS_KILL - - var/line1 - var/line2 - if(!SSshuttle.supply) - // Might be missing in our first update on initialize before shuttles - // have loaded. Cross our fingers that it will soon return. - line1 = "CARGO" - line2 = "shutl?" - else if(SSshuttle.supply.mode == SHUTTLE_IDLE) - if(is_station_level(SSshuttle.supply.z)) - line1 = "CARGO" - line2 = "Docked" - else - line1 = "CARGO" - line2 = SSshuttle.supply.getTimerStr() - if(length_char(line2) > CHARS_PER_LINE) - line2 = "Error" - update_display(line1, line2) - -/obj/machinery/status_display/supply/examine(mob/user) - . = ..() - var/obj/docking_port/mobile/shuttle = SSshuttle.supply - var/shuttleMsg = null - if (shuttle.mode == SHUTTLE_IDLE) - if (is_station_level(shuttle.z)) - shuttleMsg = "Docked" - else - shuttleMsg = "[shuttle.getModeStr()]: [shuttle.getTimerStr()]" - if (shuttleMsg) - . += "The display says:
                    \t[shuttleMsg]" - else - . += "The display is blank." - - -/// General-purpose shuttle status display. -/obj/machinery/status_display/shuttle - name = "shuttle display" - var/shuttle_id - -/obj/machinery/status_display/shuttle/process() - if(!shuttle_id || (machine_stat & NOPOWER)) - // No power, no processing. - remove_display() - return PROCESS_KILL - - return display_shuttle_status(SSshuttle.getShuttle(shuttle_id)) - -/obj/machinery/status_display/shuttle/examine(mob/user) - . = ..() - if(shuttle_id) - . += examine_shuttle(user, SSshuttle.getShuttle(shuttle_id)) - else - . += "The display is blank." - -/obj/machinery/status_display/shuttle/vv_edit_var(var_name, var_value) - . = ..() - if(!.) - return - switch(var_name) - if("shuttle_id") - update() - -/obj/machinery/status_display/shuttle/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override) - if (port && (shuttle_id == initial(shuttle_id) || override)) - shuttle_id = port.id - update() - - -/// Pictograph display which the AI can use to emote. -/obj/machinery/status_display/ai - name = "\improper AI display" - desc = "A small screen which the AI can use to present itself." - - var/mode = SD_BLANK - var/emotion = "Neutral" - -/obj/machinery/status_display/ai/Initialize() - . = ..() - GLOB.ai_status_displays.Add(src) - -/obj/machinery/status_display/ai/Destroy() - GLOB.ai_status_displays.Remove(src) - . = ..() - -/obj/machinery/status_display/ai/attack_ai(mob/living/silicon/ai/user) - if(isAI(user)) - user.ai_statuschange() - -/obj/machinery/status_display/ai/process() - if(mode == SD_BLANK || (machine_stat & NOPOWER)) - remove_display() - return PROCESS_KILL - - if(mode == SD_AI_EMOTE) - switch(emotion) - if("Very Happy") - set_picture("ai_veryhappy") - if("Happy") - set_picture("ai_happy") - if("Neutral") - set_picture("ai_neutral") - if("Unsure") - set_picture("ai_unsure") - if("Confused") - set_picture("ai_confused") - if("Sad") - set_picture("ai_sad") - if("BSOD") - set_picture("ai_bsod") - if("Blank") - set_picture("ai_off") - if("Problems?") - set_picture("ai_trollface") - if("Awesome") - set_picture("ai_awesome") - if("Dorfy") - set_picture("ai_urist") - if("Thinking") - set_picture("ai_thinking") - if("Facepalm") - set_picture("ai_facepalm") - if("Friend Computer") - set_picture("ai_friend") - if("Blue Glow") - set_picture("ai_sal") - if("Red Glow") - set_picture("ai_hal") - return PROCESS_KILL - - if(mode == SD_AI_BSOD) - set_picture("ai_bsod") - return PROCESS_KILL - - -#undef CHARS_PER_LINE -#undef FONT_SIZE -#undef FONT_COLOR -#undef FONT_STYLE -#undef SCROLL_SPEED +// Status display +// (formerly Countdown timer display) + +#define CHARS_PER_LINE 5 +#define FONT_SIZE "5pt" +#define FONT_COLOR "#09f" +#define FONT_STYLE "Small Fonts" +#define SCROLL_SPEED 2 + +#define SD_BLANK 0 // 0 = Blank +#define SD_EMERGENCY 1 // 1 = Emergency Shuttle timer +#define SD_MESSAGE 2 // 2 = Arbitrary message(s) +#define SD_PICTURE 3 // 3 = alert picture + +#define SD_AI_EMOTE 1 // 1 = AI emoticon +#define SD_AI_BSOD 2 // 2 = Blue screen of death + +/// Status display which can show images and scrolling text. +/obj/machinery/status_display + name = "status display" + desc = null + icon = 'icons/obj/status_display.dmi' + icon_state = "frame" + density = FALSE + use_power = IDLE_POWER_USE + idle_power_usage = 10 + + maptext_height = 26 + maptext_width = 32 + maptext_y = -1 + + var/message1 = "" // message line 1 + var/message2 = "" // message line 2 + var/index1 // display index for scrolling messages or 0 if non-scrolling + var/index2 + +/// Immediately blank the display. +/obj/machinery/status_display/proc/remove_display() + cut_overlays() + if(maptext) + maptext = "" + +/// Immediately change the display to the given picture. +/obj/machinery/status_display/proc/set_picture(state) + remove_display() + add_overlay(state) + +/// Immediately change the display to the given two lines. +/obj/machinery/status_display/proc/update_display(line1, line2) + line1 = uppertext(line1) + line2 = uppertext(line2) + var/new_text = {"
                    [line1]
                    [line2]
                    "} + if(maptext != new_text) + maptext = new_text + +/// Prepare the display to marquee the given two lines. +/// +/// Call with no arguments to disable. +/obj/machinery/status_display/proc/set_message(m1, m2) + if(m1) + index1 = (length_char(m1) > CHARS_PER_LINE) + message1 = m1 + else + message1 = "" + index1 = 0 + + if(m2) + index2 = (length_char(m2) > CHARS_PER_LINE) + message2 = m2 + else + message2 = "" + index2 = 0 + +// Timed process - performs default marquee action if so needed. +/obj/machinery/status_display/process() + if(machine_stat & NOPOWER) + // No power, no processing. + remove_display() + return PROCESS_KILL + + var/line1 = message1 + if(index1) + line1 = copytext_char("[message1]|[message1]", index1, index1 + CHARS_PER_LINE) + var/message1_len = length_char(message1) + index1 += SCROLL_SPEED + if(index1 > message1_len + 1) + index1 -= (message1_len + 1) + + var/line2 = message2 + if(index2) + line2 = copytext_char("[message2]|[message2]", index2, index2 + CHARS_PER_LINE) + var/message2_len = length_char(message2) + index2 += SCROLL_SPEED + if(index2 > message2_len + 1) + index2 -= (message2_len + 1) + + update_display(line1, line2) + if (!index1 && !index2) + // No marquee, no processing. + return PROCESS_KILL + +/// Update the display and, if necessary, re-enable processing. +/obj/machinery/status_display/proc/update() + if (process() != PROCESS_KILL) + START_PROCESSING(SSmachines, src) + +/obj/machinery/status_display/power_change() + . = ..() + update() + +/obj/machinery/status_display/emp_act(severity) + . = ..() + if(machine_stat & (NOPOWER|BROKEN) || . & EMP_PROTECT_SELF) + return + set_picture("ai_bsod") + +/obj/machinery/status_display/examine(mob/user) + . = ..() + if (message1 || message2) + . += "The display says:" + if (message1) + . += "
                    \t[html_encode(message1)]" + if (message2) + . += "
                    \t[html_encode(message2)]" + +// Helper procs for child display types. +/obj/machinery/status_display/proc/display_shuttle_status(obj/docking_port/mobile/shuttle) + if(!shuttle) + // the shuttle is missing - no processing + update_display("shutl?","") + return PROCESS_KILL + else if(shuttle.timer) + var/line1 = "-[shuttle.getModeStr()]-" + var/line2 = shuttle.getTimerStr() + + if(length_char(line2) > CHARS_PER_LINE) + line2 = "error" + update_display(line1, line2) + else + // don't kill processing, the timer might turn back on + remove_display() + +/obj/machinery/status_display/proc/examine_shuttle(mob/user, obj/docking_port/mobile/shuttle) + if (shuttle) + var/modestr = shuttle.getModeStr() + if (modestr) + if (shuttle.timer) + modestr = "
                    \t[modestr]: [shuttle.getTimerStr()]" + else + modestr = "
                    \t[modestr]" + return "The display says:
                    \t[shuttle.name][modestr]" + else + return "The display says:
                    \tShuttle missing!" + + +/// Evac display which shows shuttle timer or message set by Command. +/obj/machinery/status_display/evac + var/frequency = FREQ_STATUS_DISPLAYS + var/mode = SD_EMERGENCY + var/friendc = FALSE // track if Friend Computer mode + var/last_picture // For when Friend Computer mode is undone + +/obj/machinery/status_display/evac/Initialize() + . = ..() + // register for radio system + SSradio.add_object(src, frequency) + +/obj/machinery/status_display/evac/Destroy() + SSradio.remove_object(src,frequency) + return ..() + +/obj/machinery/status_display/evac/process() + if(machine_stat & NOPOWER) + // No power, no processing. + remove_display() + return PROCESS_KILL + + if(friendc) //Makes all status displays except supply shuttle timer display the eye -- Urist + set_picture("ai_friend") + return PROCESS_KILL + + switch(mode) + if(SD_BLANK) + remove_display() + return PROCESS_KILL + + if(SD_EMERGENCY) + return display_shuttle_status(SSshuttle.emergency) + + if(SD_MESSAGE) + return ..() + + if(SD_PICTURE) + set_picture(last_picture) + return PROCESS_KILL + +/obj/machinery/status_display/evac/examine(mob/user) + . = ..() + if(mode == SD_EMERGENCY) + . += examine_shuttle(user, SSshuttle.emergency) + else if(!message1 && !message2) + . += "The display is blank." + +/obj/machinery/status_display/evac/receive_signal(datum/signal/signal) + switch(signal.data["command"]) + if("blank") + mode = SD_BLANK + set_message(null, null) + if("shuttle") + mode = SD_EMERGENCY + set_message(null, null) + if("message") + mode = SD_MESSAGE + set_message(signal.data["msg1"], signal.data["msg2"]) + if("alert") + mode = SD_PICTURE + last_picture = signal.data["picture_state"] + set_picture(last_picture) + if("friendcomputer") + friendc = !friendc + update() + + +/// Supply display which shows the status of the supply shuttle. +/obj/machinery/status_display/supply + name = "supply display" + +/obj/machinery/status_display/supply/process() + if(machine_stat & NOPOWER) + // No power, no processing. + remove_display() + return PROCESS_KILL + + var/line1 + var/line2 + if(!SSshuttle.supply) + // Might be missing in our first update on initialize before shuttles + // have loaded. Cross our fingers that it will soon return. + line1 = "CARGO" + line2 = "shutl?" + else if(SSshuttle.supply.mode == SHUTTLE_IDLE) + if(is_station_level(SSshuttle.supply.z)) + line1 = "CARGO" + line2 = "Docked" + else + line1 = "CARGO" + line2 = SSshuttle.supply.getTimerStr() + if(length_char(line2) > CHARS_PER_LINE) + line2 = "Error" + update_display(line1, line2) + +/obj/machinery/status_display/supply/examine(mob/user) + . = ..() + var/obj/docking_port/mobile/shuttle = SSshuttle.supply + var/shuttleMsg = null + if (shuttle.mode == SHUTTLE_IDLE) + if (is_station_level(shuttle.z)) + shuttleMsg = "Docked" + else + shuttleMsg = "[shuttle.getModeStr()]: [shuttle.getTimerStr()]" + if (shuttleMsg) + . += "The display says:
                    \t[shuttleMsg]" + else + . += "The display is blank." + + +/// General-purpose shuttle status display. +/obj/machinery/status_display/shuttle + name = "shuttle display" + var/shuttle_id + +/obj/machinery/status_display/shuttle/process() + if(!shuttle_id || (machine_stat & NOPOWER)) + // No power, no processing. + remove_display() + return PROCESS_KILL + + return display_shuttle_status(SSshuttle.getShuttle(shuttle_id)) + +/obj/machinery/status_display/shuttle/examine(mob/user) + . = ..() + if(shuttle_id) + . += examine_shuttle(user, SSshuttle.getShuttle(shuttle_id)) + else + . += "The display is blank." + +/obj/machinery/status_display/shuttle/vv_edit_var(var_name, var_value) + . = ..() + if(!.) + return + switch(var_name) + if("shuttle_id") + update() + +/obj/machinery/status_display/shuttle/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override) + if (port && (shuttle_id == initial(shuttle_id) || override)) + shuttle_id = port.id + update() + + +/// Pictograph display which the AI can use to emote. +/obj/machinery/status_display/ai + name = "\improper AI display" + desc = "A small screen which the AI can use to present itself." + + var/mode = SD_BLANK + var/emotion = "Neutral" + +/obj/machinery/status_display/ai/Initialize() + . = ..() + GLOB.ai_status_displays.Add(src) + +/obj/machinery/status_display/ai/Destroy() + GLOB.ai_status_displays.Remove(src) + . = ..() + +/obj/machinery/status_display/ai/attack_ai(mob/living/silicon/ai/user) + if(isAI(user)) + user.ai_statuschange() + +/obj/machinery/status_display/ai/process() + if(mode == SD_BLANK || (machine_stat & NOPOWER)) + remove_display() + return PROCESS_KILL + + if(mode == SD_AI_EMOTE) + switch(emotion) + if("Very Happy") + set_picture("ai_veryhappy") + if("Happy") + set_picture("ai_happy") + if("Neutral") + set_picture("ai_neutral") + if("Unsure") + set_picture("ai_unsure") + if("Confused") + set_picture("ai_confused") + if("Sad") + set_picture("ai_sad") + if("BSOD") + set_picture("ai_bsod") + if("Blank") + set_picture("ai_off") + if("Problems?") + set_picture("ai_trollface") + if("Awesome") + set_picture("ai_awesome") + if("Dorfy") + set_picture("ai_urist") + if("Thinking") + set_picture("ai_thinking") + if("Facepalm") + set_picture("ai_facepalm") + if("Friend Computer") + set_picture("ai_friend") + if("Blue Glow") + set_picture("ai_sal") + if("Red Glow") + set_picture("ai_hal") + return PROCESS_KILL + + if(mode == SD_AI_BSOD) + set_picture("ai_bsod") + return PROCESS_KILL + + +#undef CHARS_PER_LINE +#undef FONT_SIZE +#undef FONT_COLOR +#undef FONT_STYLE +#undef SCROLL_SPEED diff --git a/code/game/machinery/suit_storage_unit.dm b/code/game/machinery/suit_storage_unit.dm index 7d4d1017ad3c..a80592fecbbc 100644 --- a/code/game/machinery/suit_storage_unit.dm +++ b/code/game/machinery/suit_storage_unit.dm @@ -1,497 +1,497 @@ -// SUIT STORAGE UNIT ///////////////// -/obj/machinery/suit_storage_unit - name = "suit storage unit" - desc = "An industrial unit made to hold and decontaminate irradiated equipment. It comes with a built-in UV cauterization mechanism. A small warning label advises that organic matter should not be placed into the unit." - icon = 'icons/obj/machines/suit_storage.dmi' - icon_state = "close" - density = TRUE - max_integrity = 250 - ui_x = 400 - ui_y = 305 - - var/obj/item/clothing/suit/space/suit = null - var/obj/item/clothing/head/helmet/space/helmet = null - var/obj/item/clothing/mask/mask = null - var/obj/item/storage = null - // if you add more storage slots, update cook() to clear their radiation too. - - /// What type of spacesuit the unit starts with when spawned. - var/suit_type = null - /// What type of space helmet the unit starts with when spawned. - var/helmet_type = null - /// What type of breathmask the unit starts with when spawned. - var/mask_type = null - /// What type of additional item the unit starts with when spawned. - var/storage_type = null - - state_open = FALSE - /// If the SSU's doors are locked closed. Can be toggled manually via the UI, but is also locked automatically when the UV decontamination sequence is running. - var/locked = FALSE - panel_open = FALSE - /// If the safety wire is cut/pulsed, the SSU can run the decontamination sequence while occupied by a mob. The mob will be burned during every cycle of cook(). - var/safeties = TRUE - - /// If UV decontamination sequence is running. See cook() - var/uv = FALSE - /** - * If the hack wire is cut/pulsed. - * Modifies effects of cook() - * * If FALSE, decontamination sequence will clear radiation for all atoms (and their contents) contained inside the unit, and burn any mobs inside. - * * If TRUE, decontamination sequence will delete all items contained within, and if occupied by a mob, intensifies burn damage delt. All wires will be cut at the end. - */ - var/uv_super = FALSE - /// How many cycles remain for the decontamination sequence. - var/uv_cycles = 6 - /// Cooldown for occupant breakout messages via relaymove() - var/message_cooldown - /// How long it takes to break out of the SSU. - var/breakout_time = 300 - -/obj/machinery/suit_storage_unit/standard_unit - suit_type = /obj/item/clothing/suit/space/eva - helmet_type = /obj/item/clothing/head/helmet/space/eva - mask_type = /obj/item/clothing/mask/breath - -/obj/machinery/suit_storage_unit/captain - suit_type = /obj/item/clothing/suit/space/hardsuit/swat/captain - mask_type = /obj/item/clothing/mask/gas/atmos/captain - storage_type = /obj/item/tank/jetpack/oxygen/captain - -/obj/machinery/suit_storage_unit/engine - suit_type = /obj/item/clothing/suit/space/hardsuit/engine - mask_type = /obj/item/clothing/mask/breath - storage_type= /obj/item/clothing/shoes/magboots - -/obj/machinery/suit_storage_unit/atmos - suit_type = /obj/item/clothing/suit/space/hardsuit/engine/atmos - mask_type = /obj/item/clothing/mask/gas/atmos - storage_type = /obj/item/watertank/atmos - -/obj/machinery/suit_storage_unit/ce - suit_type = /obj/item/clothing/suit/space/hardsuit/engine/elite - mask_type = /obj/item/clothing/mask/breath - storage_type= /obj/item/clothing/shoes/magboots/advance - -/obj/machinery/suit_storage_unit/security - suit_type = /obj/item/clothing/suit/space/hardsuit/security - mask_type = /obj/item/clothing/mask/gas/sechailer - -/obj/machinery/suit_storage_unit/hos - suit_type = /obj/item/clothing/suit/space/hardsuit/security/hos - mask_type = /obj/item/clothing/mask/gas/sechailer - storage_type = /obj/item/tank/internals/oxygen - -/obj/machinery/suit_storage_unit/mining - suit_type = /obj/item/clothing/suit/hooded/explorer - mask_type = /obj/item/clothing/mask/gas/explorer - -/obj/machinery/suit_storage_unit/mining/eva - suit_type = /obj/item/clothing/suit/space/hardsuit/mining - mask_type = /obj/item/clothing/mask/breath - -/obj/machinery/suit_storage_unit/cmo - suit_type = /obj/item/clothing/suit/space/hardsuit/medical/cmo - mask_type = /obj/item/clothing/mask/breath/medical - storage_type = /obj/item/tank/internals/oxygen - -/obj/machinery/suit_storage_unit/rd - suit_type = /obj/item/clothing/suit/space/hardsuit/rd - mask_type = /obj/item/clothing/mask/breath - -/obj/machinery/suit_storage_unit/syndicate - suit_type = /obj/item/clothing/suit/space/hardsuit/syndi - mask_type = /obj/item/clothing/mask/gas/syndicate - storage_type = /obj/item/tank/jetpack/oxygen/harness - -/obj/machinery/suit_storage_unit/ert/command - suit_type = /obj/item/clothing/suit/space/hardsuit/ert - mask_type = /obj/item/clothing/mask/breath - storage_type = /obj/item/tank/internals/emergency_oxygen/double - -/obj/machinery/suit_storage_unit/ert/security - suit_type = /obj/item/clothing/suit/space/hardsuit/ert/sec - mask_type = /obj/item/clothing/mask/breath - storage_type = /obj/item/tank/internals/emergency_oxygen/double - -/obj/machinery/suit_storage_unit/ert/engineer - suit_type = /obj/item/clothing/suit/space/hardsuit/ert/engi - mask_type = /obj/item/clothing/mask/breath - storage_type = /obj/item/tank/internals/emergency_oxygen/double - -/obj/machinery/suit_storage_unit/ert/medical - suit_type = /obj/item/clothing/suit/space/hardsuit/ert/med - mask_type = /obj/item/clothing/mask/breath - storage_type = /obj/item/tank/internals/emergency_oxygen/double - -/obj/machinery/suit_storage_unit/radsuit - name = "radiation suit storage unit" - suit_type = /obj/item/clothing/suit/radiation - helmet_type = /obj/item/clothing/head/radiation - storage_type = /obj/item/geiger_counter - -/obj/machinery/suit_storage_unit/open - state_open = TRUE - density = FALSE - -/obj/machinery/suit_storage_unit/Initialize() - . = ..() - wires = new /datum/wires/suit_storage_unit(src) - if(suit_type) - suit = new suit_type(src) - if(helmet_type) - helmet = new helmet_type(src) - if(mask_type) - mask = new mask_type(src) - if(storage_type) - storage = new storage_type(src) - update_icon() - -/obj/machinery/suit_storage_unit/Destroy() - QDEL_NULL(suit) - QDEL_NULL(helmet) - QDEL_NULL(mask) - QDEL_NULL(storage) - return ..() - -/obj/machinery/suit_storage_unit/update_overlays() - . = ..() - - if(uv) - if(uv_super) - . += "super" - else if(occupant) - . += "uvhuman" - else - . += "uv" - else if(state_open) - if(machine_stat & BROKEN) - . += "broken" - else - . += "open" - if(suit) - . += "suit" - if(helmet) - . += "helm" - if(storage) - . += "storage" - else if(occupant) - . += "human" - -/obj/machinery/suit_storage_unit/power_change() - . = ..() - if(!is_operational() && state_open) - open_machine() - dump_contents() - update_icon() - -/obj/machinery/suit_storage_unit/dump_contents() - dropContents() - helmet = null - suit = null - mask = null - storage = null - occupant = null - -/obj/machinery/suit_storage_unit/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - open_machine() - dump_contents() - new /obj/item/stack/sheet/metal (loc, 2) - qdel(src) - -/obj/machinery/suit_storage_unit/MouseDrop_T(atom/A, mob/living/user) - if(!istype(user) || user.stat || !Adjacent(user) || !Adjacent(A) || !isliving(A)) - return - if(isliving(user)) - var/mob/living/L = user - if(!(L.mobility_flags & MOBILITY_STAND)) - return - var/mob/living/target = A - if(!state_open) - to_chat(user, "The unit's doors are shut!") - return - if(!is_operational()) - to_chat(user, "The unit is not operational!") - return - if(occupant || helmet || suit || storage) - to_chat(user, "It's too cluttered inside to fit in!") - return - - if(target == user) - user.visible_message("[user] starts squeezing into [src]!", "You start working your way into [src]...") - else - target.visible_message("[user] starts shoving [target] into [src]!", "[user] starts shoving you into [src]!") - - if(do_mob(user, target, 30)) - if(occupant || helmet || suit || storage) - return - if(target == user) - user.visible_message("[user] slips into [src] and closes the door behind [user.p_them()]!", "You slip into [src]'s cramped space and shut its door.") - else - target.visible_message("[user] pushes [target] into [src] and shuts its door!", "[user] shoves you into [src] and shuts the door!") - close_machine(target) - add_fingerprint(user) - -/** - * UV decontamination sequence. - * Duration is determined by the uv_cycles var. - * Effects determined by the uv_super var. - * * If FALSE, all atoms (and their contents) contained are cleared of radiation. If a mob is inside, they are burned every cycle. - * * If TRUE, all items contained are destroyed, and burn damage applied to the mob is increased. All wires will be cut at the end. - * All atoms still inside at the end of all cycles are ejected from the unit. -*/ -/obj/machinery/suit_storage_unit/proc/cook() - var/mob/living/mob_occupant = occupant - if(uv_cycles) - uv_cycles-- - uv = TRUE - locked = TRUE - update_icon() - if(occupant) - if(uv_super) - mob_occupant.adjustFireLoss(rand(20, 36)) - else - mob_occupant.adjustFireLoss(rand(10, 16)) - mob_occupant.emote("scream") - addtimer(CALLBACK(src, .proc/cook), 50) - else - uv_cycles = initial(uv_cycles) - uv = FALSE - locked = FALSE - if(uv_super) - visible_message("[src]'s door creaks open with a loud whining noise. A cloud of foul black smoke escapes from its chamber.") - playsound(src, 'sound/machines/airlock_alien_prying.ogg', 50, TRUE) - helmet = null - qdel(helmet) - suit = null - qdel(suit) // Delete everything but the occupant. - mask = null - qdel(mask) - storage = null - qdel(storage) - // The wires get damaged too. - wires.cut_all() - else - if(!occupant) - visible_message("[src]'s door slides open. The glowing yellow lights dim to a gentle green.") - else - visible_message("[src]'s door slides open, barraging you with the nauseating smell of charred flesh.") - mob_occupant.radiation = 0 - playsound(src, 'sound/machines/airlockclose.ogg', 25, TRUE) - var/list/things_to_clear = list() //Done this way since using GetAllContents on the SSU itself would include circuitry and such. - if(suit) - things_to_clear += suit - things_to_clear += suit.GetAllContents() - if(helmet) - things_to_clear += helmet - things_to_clear += helmet.GetAllContents() - if(mask) - things_to_clear += mask - things_to_clear += mask.GetAllContents() - if(storage) - things_to_clear += storage - things_to_clear += storage.GetAllContents() - if(occupant) - things_to_clear += occupant - things_to_clear += occupant.GetAllContents() - for(var/atom/movable/AM in things_to_clear) //Scorches away blood and forensic evidence, although the SSU itself is unaffected - SEND_SIGNAL(AM, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_STRONG) - var/datum/component/radioactive/contamination = AM.GetComponent(/datum/component/radioactive) - if(contamination) - qdel(contamination) - open_machine(FALSE) - if(occupant) - dump_contents() - -/obj/machinery/suit_storage_unit/proc/shock(mob/user, prb) - if(!prob(prb)) - var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread - s.set_up(5, 1, src) - s.start() - if(electrocute_mob(user, src, src, 1, TRUE)) - return 1 - -/obj/machinery/suit_storage_unit/relaymove(mob/user) - if(locked) - if(message_cooldown <= world.time) - message_cooldown = world.time + 50 - to_chat(user, "[src]'s door won't budge!") - return - open_machine() - dump_contents() - -/obj/machinery/suit_storage_unit/container_resist(mob/living/user) - if(!locked) - open_machine() - dump_contents() - return - user.changeNext_move(CLICK_CD_BREAKOUT) - user.last_special = world.time + CLICK_CD_BREAKOUT - user.visible_message("You see [user] kicking against the doors of [src]!", \ - "You start kicking against the doors... (this will take about [DisplayTimeText(breakout_time)].)", \ - "You hear a thump from [src].") - if(do_after(user,(breakout_time), target = src)) - if(!user || user.stat != CONSCIOUS || user.loc != src ) - return - user.visible_message("[user] successfully broke out of [src]!", \ - "You successfully break out of [src]!") - open_machine() - dump_contents() - - add_fingerprint(user) - if(locked) - visible_message("You see [user] kicking against the doors of [src]!", \ - "You start kicking against the doors...") - addtimer(CALLBACK(src, .proc/resist_open, user), 300) - else - open_machine() - dump_contents() - -/obj/machinery/suit_storage_unit/proc/resist_open(mob/user) - if(!state_open && occupant && (user in src) && user.stat == 0) // Check they're still here. - visible_message("You see [user] burst out of [src]!", \ - "You escape the cramped confines of [src]!") - open_machine() - -/obj/machinery/suit_storage_unit/attackby(obj/item/I, mob/user, params) - if(state_open && is_operational()) - if(istype(I, /obj/item/clothing/suit)) - if(suit) - to_chat(user, "The unit already contains a suit!.") - return - if(!user.transferItemToLoc(I, src)) - return - suit = I - else if(istype(I, /obj/item/clothing/head)) - if(helmet) - to_chat(user, "The unit already contains a helmet!") - return - if(!user.transferItemToLoc(I, src)) - return - helmet = I - else if(istype(I, /obj/item/clothing/mask)) - if(mask) - to_chat(user, "The unit already contains a mask!") - return - if(!user.transferItemToLoc(I, src)) - return - mask = I - else - if(storage) - to_chat(user, "The auxiliary storage compartment is full!") - return - if(!user.transferItemToLoc(I, src)) - return - storage = I - - visible_message("[user] inserts [I] into [src]", "You load [I] into [src].") - update_icon() - return - - if(panel_open && is_wire_tool(I)) - wires.interact(user) - return - if(!state_open) - if(default_deconstruction_screwdriver(user, "panel", "close", I)) - return - if(default_pry_open(I)) - dump_contents() - return - - return ..() - -/* ref tg-git issue #45036 - screwdriving it open while it's running a decontamination sequence without closing the panel prior to finish - causes the SSU to break due to state_open being set to TRUE at the end, and the panel becoming inaccessible. -*/ -/obj/machinery/suit_storage_unit/default_deconstruction_screwdriver(mob/user, icon_state_open, icon_state_closed, obj/item/I) - if(!(flags_1 & NODECONSTRUCT_1) && I.tool_behaviour == TOOL_SCREWDRIVER && uv) - to_chat(user, "It might not be wise to fiddle with [src] while it's running...") - return TRUE - return ..() - - -/obj/machinery/suit_storage_unit/default_pry_open(obj/item/I)//needs to check if the storage is locked. - . = !(state_open || panel_open || is_operational() || locked || (flags_1 & NODECONSTRUCT_1)) && I.tool_behaviour == TOOL_CROWBAR - if(.) - I.play_tool_sound(src, 50) - visible_message("[usr] pries open \the [src].", "You pry open \the [src].") - open_machine() - -/obj/machinery/suit_storage_unit/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.notcontained_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "SuitStorageUnit", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/machinery/suit_storage_unit/ui_data() - var/list/data = list() - data["locked"] = locked - data["open"] = state_open - data["safeties"] = safeties - data["uv_active"] = uv - data["uv_super"] = uv_super - if(helmet) - data["helmet"] = helmet.name - else - data["helmet"] = null - if(suit) - data["suit"] = suit.name - else - data["suit"] = null - if(mask) - data["mask"] = mask.name - else - data["mask"] = null - if(storage) - data["storage"] = storage.name - else - data["storage"] = null - if(occupant) - data["occupied"] = TRUE - else - data["occupied"] = FALSE - return data - -/obj/machinery/suit_storage_unit/ui_act(action, params) - if(..() || uv) - return - switch(action) - if("door") - if(state_open) - close_machine() - else - open_machine(0) - if(occupant) - dump_contents() // Dump out contents if someone is in there. - . = TRUE - if("lock") - if(state_open) - return - locked = !locked - . = TRUE - if("uv") - if(occupant && safeties) - return - else if(!helmet && !mask && !suit && !storage && !occupant) - return - else - if(occupant) - var/mob/living/mob_occupant = occupant - to_chat(mob_occupant, "[src]'s confines grow warm, then hot, then scorching. You're being burned [!mob_occupant.stat ? "alive" : "away"]!") - cook() - . = TRUE - if("dispense") - if(!state_open) - return - - var/static/list/valid_items = list("helmet", "suit", "mask", "storage") - var/item_name = params["item"] - if(item_name in valid_items) - var/obj/item/I = vars[item_name] - vars[item_name] = null - if(I) - I.forceMove(loc) - . = TRUE - update_icon() +// SUIT STORAGE UNIT ///////////////// +/obj/machinery/suit_storage_unit + name = "suit storage unit" + desc = "An industrial unit made to hold and decontaminate irradiated equipment. It comes with a built-in UV cauterization mechanism. A small warning label advises that organic matter should not be placed into the unit." + icon = 'icons/obj/machines/suit_storage.dmi' + icon_state = "close" + density = TRUE + max_integrity = 250 + + var/obj/item/clothing/suit/space/suit = null + var/obj/item/clothing/head/helmet/space/helmet = null + var/obj/item/clothing/mask/mask = null + var/obj/item/storage = null + // if you add more storage slots, update cook() to clear their radiation too. + + /// What type of spacesuit the unit starts with when spawned. + var/suit_type = null + /// What type of space helmet the unit starts with when spawned. + var/helmet_type = null + /// What type of breathmask the unit starts with when spawned. + var/mask_type = null + /// What type of additional item the unit starts with when spawned. + var/storage_type = null + + state_open = FALSE + /// If the SSU's doors are locked closed. Can be toggled manually via the UI, but is also locked automatically when the UV decontamination sequence is running. + var/locked = FALSE + panel_open = FALSE + /// If the safety wire is cut/pulsed, the SSU can run the decontamination sequence while occupied by a mob. The mob will be burned during every cycle of cook(). + var/safeties = TRUE + + /// If UV decontamination sequence is running. See cook() + var/uv = FALSE + /** + * If the hack wire is cut/pulsed. + * Modifies effects of cook() + * * If FALSE, decontamination sequence will clear radiation for all atoms (and their contents) contained inside the unit, and burn any mobs inside. + * * If TRUE, decontamination sequence will delete all items contained within, and if occupied by a mob, intensifies burn damage delt. All wires will be cut at the end. + */ + var/uv_super = FALSE + /// How many cycles remain for the decontamination sequence. + var/uv_cycles = 6 + /// Cooldown for occupant breakout messages via relaymove() + var/message_cooldown + /// How long it takes to break out of the SSU. + var/breakout_time = 300 + +/obj/machinery/suit_storage_unit/standard_unit + suit_type = /obj/item/clothing/suit/space/eva + helmet_type = /obj/item/clothing/head/helmet/space/eva + mask_type = /obj/item/clothing/mask/breath + +/obj/machinery/suit_storage_unit/captain + suit_type = /obj/item/clothing/suit/space/hardsuit/swat/captain + mask_type = /obj/item/clothing/mask/gas/atmos/captain + storage_type = /obj/item/tank/jetpack/oxygen/captain + +/obj/machinery/suit_storage_unit/engine + suit_type = /obj/item/clothing/suit/space/hardsuit/engine + mask_type = /obj/item/clothing/mask/breath + storage_type= /obj/item/clothing/shoes/magboots + +/obj/machinery/suit_storage_unit/atmos + suit_type = /obj/item/clothing/suit/space/hardsuit/engine/atmos + mask_type = /obj/item/clothing/mask/gas/atmos + storage_type = /obj/item/watertank/atmos + +/obj/machinery/suit_storage_unit/ce + suit_type = /obj/item/clothing/suit/space/hardsuit/engine/elite + mask_type = /obj/item/clothing/mask/breath + storage_type= /obj/item/clothing/shoes/magboots/advance + +/obj/machinery/suit_storage_unit/security + suit_type = /obj/item/clothing/suit/space/hardsuit/security + mask_type = /obj/item/clothing/mask/gas/sechailer + +/obj/machinery/suit_storage_unit/hos + suit_type = /obj/item/clothing/suit/space/hardsuit/security/hos + mask_type = /obj/item/clothing/mask/gas/sechailer + storage_type = /obj/item/tank/internals/oxygen + +/obj/machinery/suit_storage_unit/mining + suit_type = /obj/item/clothing/suit/hooded/explorer + mask_type = /obj/item/clothing/mask/gas/explorer + +/obj/machinery/suit_storage_unit/mining/eva + suit_type = /obj/item/clothing/suit/space/hardsuit/mining + mask_type = /obj/item/clothing/mask/breath + +/obj/machinery/suit_storage_unit/cmo + suit_type = /obj/item/clothing/suit/space/hardsuit/medical/cmo + mask_type = /obj/item/clothing/mask/breath/medical + storage_type = /obj/item/tank/internals/oxygen + +/obj/machinery/suit_storage_unit/rd + suit_type = /obj/item/clothing/suit/space/hardsuit/rd + mask_type = /obj/item/clothing/mask/breath + +/obj/machinery/suit_storage_unit/syndicate + suit_type = /obj/item/clothing/suit/space/hardsuit/syndi + mask_type = /obj/item/clothing/mask/gas/syndicate + storage_type = /obj/item/tank/jetpack/oxygen/harness + +/obj/machinery/suit_storage_unit/ert/command + suit_type = /obj/item/clothing/suit/space/hardsuit/ert + mask_type = /obj/item/clothing/mask/breath + storage_type = /obj/item/tank/internals/emergency_oxygen/double + +/obj/machinery/suit_storage_unit/ert/security + suit_type = /obj/item/clothing/suit/space/hardsuit/ert/sec + mask_type = /obj/item/clothing/mask/breath + storage_type = /obj/item/tank/internals/emergency_oxygen/double + +/obj/machinery/suit_storage_unit/ert/engineer + suit_type = /obj/item/clothing/suit/space/hardsuit/ert/engi + mask_type = /obj/item/clothing/mask/breath + storage_type = /obj/item/tank/internals/emergency_oxygen/double + +/obj/machinery/suit_storage_unit/ert/medical + suit_type = /obj/item/clothing/suit/space/hardsuit/ert/med + mask_type = /obj/item/clothing/mask/breath + storage_type = /obj/item/tank/internals/emergency_oxygen/double + +/obj/machinery/suit_storage_unit/radsuit + name = "radiation suit storage unit" + suit_type = /obj/item/clothing/suit/radiation + helmet_type = /obj/item/clothing/head/radiation + storage_type = /obj/item/geiger_counter + +/obj/machinery/suit_storage_unit/open + state_open = TRUE + density = FALSE + +/obj/machinery/suit_storage_unit/Initialize() + . = ..() + wires = new /datum/wires/suit_storage_unit(src) + if(suit_type) + suit = new suit_type(src) + if(helmet_type) + helmet = new helmet_type(src) + if(mask_type) + mask = new mask_type(src) + if(storage_type) + storage = new storage_type(src) + update_icon() + +/obj/machinery/suit_storage_unit/Destroy() + QDEL_NULL(suit) + QDEL_NULL(helmet) + QDEL_NULL(mask) + QDEL_NULL(storage) + return ..() + +/obj/machinery/suit_storage_unit/update_overlays() + . = ..() + + if(uv) + if(uv_super) + . += "super" + else if(occupant) + . += "uvhuman" + else + . += "uv" + else if(state_open) + if(machine_stat & BROKEN) + . += "broken" + else + . += "open" + if(suit) + . += "suit" + if(helmet) + . += "helm" + if(storage) + . += "storage" + else if(occupant) + . += "human" + +/obj/machinery/suit_storage_unit/power_change() + . = ..() + if(!is_operational() && state_open) + open_machine() + dump_contents() + update_icon() + +/obj/machinery/suit_storage_unit/dump_contents() + dropContents() + helmet = null + suit = null + mask = null + storage = null + occupant = null + +/obj/machinery/suit_storage_unit/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + open_machine() + dump_contents() + new /obj/item/stack/sheet/metal (loc, 2) + qdel(src) + +/obj/machinery/suit_storage_unit/MouseDrop_T(atom/A, mob/living/user) + if(!istype(user) || user.stat || !Adjacent(user) || !Adjacent(A) || !isliving(A)) + return + if(isliving(user)) + var/mob/living/L = user + if(!(L.mobility_flags & MOBILITY_STAND)) + return + var/mob/living/target = A + if(!state_open) + to_chat(user, "The unit's doors are shut!") + return + if(!is_operational()) + to_chat(user, "The unit is not operational!") + return + if(occupant || helmet || suit || storage) + to_chat(user, "It's too cluttered inside to fit in!") + return + + if(target == user) + user.visible_message("[user] starts squeezing into [src]!", "You start working your way into [src]...") + else + target.visible_message("[user] starts shoving [target] into [src]!", "[user] starts shoving you into [src]!") + + if(do_mob(user, target, 30)) + if(occupant || helmet || suit || storage) + return + if(target == user) + user.visible_message("[user] slips into [src] and closes the door behind [user.p_them()]!", "You slip into [src]'s cramped space and shut its door.") + else + target.visible_message("[user] pushes [target] into [src] and shuts its door!", "[user] shoves you into [src] and shuts the door!") + close_machine(target) + add_fingerprint(user) + +/** + * UV decontamination sequence. + * Duration is determined by the uv_cycles var. + * Effects determined by the uv_super var. + * * If FALSE, all atoms (and their contents) contained are cleared of radiation. If a mob is inside, they are burned every cycle. + * * If TRUE, all items contained are destroyed, and burn damage applied to the mob is increased. All wires will be cut at the end. + * All atoms still inside at the end of all cycles are ejected from the unit. +*/ +/obj/machinery/suit_storage_unit/proc/cook() + var/mob/living/mob_occupant = occupant + if(uv_cycles) + uv_cycles-- + uv = TRUE + locked = TRUE + update_icon() + if(occupant) + if(uv_super) + mob_occupant.adjustFireLoss(rand(20, 36)) + else + mob_occupant.adjustFireLoss(rand(10, 16)) + mob_occupant.emote("scream") + addtimer(CALLBACK(src, .proc/cook), 50) + else + uv_cycles = initial(uv_cycles) + uv = FALSE + locked = FALSE + if(uv_super) + visible_message("[src]'s door creaks open with a loud whining noise. A cloud of foul black smoke escapes from its chamber.") + playsound(src, 'sound/machines/airlock_alien_prying.ogg', 50, TRUE) + helmet = null + qdel(helmet) + suit = null + qdel(suit) // Delete everything but the occupant. + mask = null + qdel(mask) + storage = null + qdel(storage) + // The wires get damaged too. + wires.cut_all() + else + if(!occupant) + visible_message("[src]'s door slides open. The glowing yellow lights dim to a gentle green.") + else + visible_message("[src]'s door slides open, barraging you with the nauseating smell of charred flesh.") + mob_occupant.radiation = 0 + playsound(src, 'sound/machines/airlockclose.ogg', 25, TRUE) + var/list/things_to_clear = list() //Done this way since using GetAllContents on the SSU itself would include circuitry and such. + if(suit) + things_to_clear += suit + things_to_clear += suit.GetAllContents() + if(helmet) + things_to_clear += helmet + things_to_clear += helmet.GetAllContents() + if(mask) + things_to_clear += mask + things_to_clear += mask.GetAllContents() + if(storage) + things_to_clear += storage + things_to_clear += storage.GetAllContents() + if(occupant) + things_to_clear += occupant + things_to_clear += occupant.GetAllContents() + for(var/atom/movable/AM in things_to_clear) //Scorches away blood and forensic evidence, although the SSU itself is unaffected + SEND_SIGNAL(AM, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_STRONG) + var/datum/component/radioactive/contamination = AM.GetComponent(/datum/component/radioactive) + if(contamination) + qdel(contamination) + open_machine(FALSE) + if(occupant) + dump_contents() + +/obj/machinery/suit_storage_unit/proc/shock(mob/user, prb) + if(!prob(prb)) + var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread + s.set_up(5, 1, src) + s.start() + if(electrocute_mob(user, src, src, 1, TRUE)) + return 1 + +/obj/machinery/suit_storage_unit/relaymove(mob/user) + if(locked) + if(message_cooldown <= world.time) + message_cooldown = world.time + 50 + to_chat(user, "[src]'s door won't budge!") + return + open_machine() + dump_contents() + +/obj/machinery/suit_storage_unit/container_resist(mob/living/user) + if(!locked) + open_machine() + dump_contents() + return + user.changeNext_move(CLICK_CD_BREAKOUT) + user.last_special = world.time + CLICK_CD_BREAKOUT + user.visible_message("You see [user] kicking against the doors of [src]!", \ + "You start kicking against the doors... (this will take about [DisplayTimeText(breakout_time)].)", \ + "You hear a thump from [src].") + if(do_after(user,(breakout_time), target = src)) + if(!user || user.stat != CONSCIOUS || user.loc != src ) + return + user.visible_message("[user] successfully broke out of [src]!", \ + "You successfully break out of [src]!") + open_machine() + dump_contents() + + add_fingerprint(user) + if(locked) + visible_message("You see [user] kicking against the doors of [src]!", \ + "You start kicking against the doors...") + addtimer(CALLBACK(src, .proc/resist_open, user), 300) + else + open_machine() + dump_contents() + +/obj/machinery/suit_storage_unit/proc/resist_open(mob/user) + if(!state_open && occupant && (user in src) && user.stat == 0) // Check they're still here. + visible_message("You see [user] burst out of [src]!", \ + "You escape the cramped confines of [src]!") + open_machine() + +/obj/machinery/suit_storage_unit/attackby(obj/item/I, mob/user, params) + if(state_open && is_operational()) + if(istype(I, /obj/item/clothing/suit)) + if(suit) + to_chat(user, "The unit already contains a suit!.") + return + if(!user.transferItemToLoc(I, src)) + return + suit = I + else if(istype(I, /obj/item/clothing/head)) + if(helmet) + to_chat(user, "The unit already contains a helmet!") + return + if(!user.transferItemToLoc(I, src)) + return + helmet = I + else if(istype(I, /obj/item/clothing/mask)) + if(mask) + to_chat(user, "The unit already contains a mask!") + return + if(!user.transferItemToLoc(I, src)) + return + mask = I + else + if(storage) + to_chat(user, "The auxiliary storage compartment is full!") + return + if(!user.transferItemToLoc(I, src)) + return + storage = I + + visible_message("[user] inserts [I] into [src]", "You load [I] into [src].") + update_icon() + return + + if(panel_open && is_wire_tool(I)) + wires.interact(user) + return + if(!state_open) + if(default_deconstruction_screwdriver(user, "panel", "close", I)) + return + if(default_pry_open(I)) + dump_contents() + return + + return ..() + +/* ref tg-git issue #45036 + screwdriving it open while it's running a decontamination sequence without closing the panel prior to finish + causes the SSU to break due to state_open being set to TRUE at the end, and the panel becoming inaccessible. +*/ +/obj/machinery/suit_storage_unit/default_deconstruction_screwdriver(mob/user, icon_state_open, icon_state_closed, obj/item/I) + if(!(flags_1 & NODECONSTRUCT_1) && I.tool_behaviour == TOOL_SCREWDRIVER && uv) + to_chat(user, "It might not be wise to fiddle with [src] while it's running...") + return TRUE + return ..() + + +/obj/machinery/suit_storage_unit/default_pry_open(obj/item/I)//needs to check if the storage is locked. + . = !(state_open || panel_open || is_operational() || locked || (flags_1 & NODECONSTRUCT_1)) && I.tool_behaviour == TOOL_CROWBAR + if(.) + I.play_tool_sound(src, 50) + visible_message("[usr] pries open \the [src].", "You pry open \the [src].") + open_machine() + +/obj/machinery/suit_storage_unit/ui_state(mob/user) + return GLOB.notcontained_state + +/obj/machinery/suit_storage_unit/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "SuitStorageUnit", name) + ui.open() + +/obj/machinery/suit_storage_unit/ui_data() + var/list/data = list() + data["locked"] = locked + data["open"] = state_open + data["safeties"] = safeties + data["uv_active"] = uv + data["uv_super"] = uv_super + if(helmet) + data["helmet"] = helmet.name + else + data["helmet"] = null + if(suit) + data["suit"] = suit.name + else + data["suit"] = null + if(mask) + data["mask"] = mask.name + else + data["mask"] = null + if(storage) + data["storage"] = storage.name + else + data["storage"] = null + if(occupant) + data["occupied"] = TRUE + else + data["occupied"] = FALSE + return data + +/obj/machinery/suit_storage_unit/ui_act(action, params) + if(..() || uv) + return + switch(action) + if("door") + if(state_open) + close_machine() + else + open_machine(0) + if(occupant) + dump_contents() // Dump out contents if someone is in there. + . = TRUE + if("lock") + if(state_open) + return + locked = !locked + . = TRUE + if("uv") + if(occupant && safeties) + return + else if(!helmet && !mask && !suit && !storage && !occupant) + return + else + if(occupant) + var/mob/living/mob_occupant = occupant + to_chat(mob_occupant, "[src]'s confines grow warm, then hot, then scorching. You're being burned [!mob_occupant.stat ? "alive" : "away"]!") + cook() + . = TRUE + if("dispense") + if(!state_open) + return + + var/static/list/valid_items = list("helmet", "suit", "mask", "storage") + var/item_name = params["item"] + if(item_name in valid_items) + var/obj/item/I = vars[item_name] + vars[item_name] = null + if(I) + I.forceMove(loc) + . = TRUE + update_icon() diff --git a/code/game/machinery/syndicatebeacon.dm b/code/game/machinery/syndicatebeacon.dm index 08c92f4811de..3cb03e05c772 100644 --- a/code/game/machinery/syndicatebeacon.dm +++ b/code/game/machinery/syndicatebeacon.dm @@ -1,155 +1,155 @@ -//////////////////////////////////////// -//Singularity beacon -//////////////////////////////////////// -/obj/machinery/power/singularity_beacon - name = "ominous beacon" - desc = "This looks suspicious..." - icon = 'icons/obj/singularity.dmi' - icon_state = "beacon0" - - anchored = FALSE - density = TRUE - layer = BELOW_MOB_LAYER //so people can't hide it and it's REALLY OBVIOUS - machine_stat = 0 - verb_say = "states" - var/cooldown = 0 - - var/active = 0 - var/icontype = "beacon" - - -/obj/machinery/power/singularity_beacon/proc/Activate(mob/user = null) - if(surplus() < 1500) - if(user) - to_chat(user, "The connected wire doesn't have enough current.") - return - for(var/obj/singularity/singulo in GLOB.singularities) - if(singulo.z == z) - singulo.target = src - icon_state = "[icontype]1" - active = 1 - if(user) - to_chat(user, "You activate the beacon.") - - -/obj/machinery/power/singularity_beacon/proc/Deactivate(mob/user = null) - for(var/obj/singularity/singulo in GLOB.singularities) - if(singulo.target == src) - singulo.target = null - icon_state = "[icontype]0" - active = 0 - if(user) - to_chat(user, "You deactivate the beacon.") - - -/obj/machinery/power/singularity_beacon/attack_ai(mob/user) - return - - -/obj/machinery/power/singularity_beacon/attack_hand(mob/user) - . = ..() - if(.) - return - if(anchored) - return active ? Deactivate(user) : Activate(user) - else - to_chat(user, "You need to screw \the [src] to the floor first!") - -/obj/machinery/power/singularity_beacon/attackby(obj/item/W, mob/user, params) - if(W.tool_behaviour == TOOL_WRENCH) - if(active) - to_chat(user, "You need to deactivate \the [src] first!") - return - - if(anchored) - setAnchored(FALSE) - to_chat(user, "You unbolt \the [src] from the floor and detach it from the cable.") - disconnect_from_network() - return - else - if(!connect_to_network()) - to_chat(user, "\The [src] must be placed over an exposed, powered cable node!") - return - setAnchored(TRUE) - to_chat(user, "You bolt \the [src] to the floor and attach it to the cable.") - return - else if(W.tool_behaviour == TOOL_SCREWDRIVER) - user.visible_message( \ - "[user] messes with \the [src] for a bit.", \ - "You can't fit the screwdriver into \the [src]'s bolts! Try using a wrench.") - else - return ..() - -/obj/machinery/power/singularity_beacon/Destroy() - if(active) - Deactivate() - return ..() - -//stealth direct power usage -/obj/machinery/power/singularity_beacon/process() - if(!active) - return - - if(surplus() >= 1500) - add_load(1500) - if(cooldown <= world.time) - cooldown = world.time + 80 - for(var/obj/singularity/singulo in GLOB.singularities) - if(singulo.z == z) - say("[singulo] is now [get_dist(src,singulo)] standard lengths away to the [dir2text(get_dir(src,singulo))]") - else - Deactivate() - say("Insufficient charge detected - powering down") - - -/obj/machinery/power/singularity_beacon/syndicate - icontype = "beaconsynd" - icon_state = "beaconsynd0" - -// SINGULO BEACON SPAWNER -/obj/item/sbeacondrop - name = "suspicious beacon" - icon = 'icons/obj/device.dmi' - icon_state = "beacon" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - desc = "A label on it reads: Warning: Activating this device will send a special beacon to your location." - w_class = WEIGHT_CLASS_SMALL - var/droptype = /obj/machinery/power/singularity_beacon/syndicate - - -/obj/item/sbeacondrop/attack_self(mob/user) - if(user) - to_chat(user, "Locked In.") - new droptype( user.loc ) - playsound(src, 'sound/effects/pop.ogg', 100, TRUE, TRUE) - qdel(src) - return - -/obj/item/sbeacondrop/bomb - desc = "A label on it reads: Warning: Activating this device will send a high-ordinance explosive to your location." - droptype = /obj/machinery/syndicatebomb - -/obj/item/sbeacondrop/powersink - desc = "A label on it reads: Warning: Activating this device will send a power draining device to your location." - droptype = /obj/item/powersink - -/obj/item/sbeacondrop/clownbomb - desc = "A label on it reads: Warning: Activating this device will send a silly explosive to your location." - droptype = /obj/machinery/syndicatebomb/badmin/clown - -/obj/item/sbeacondrop/semiautoturret - desc = "A label on it reads: Warning: Activating this device will send a semi-auto turret to your location." - droptype = /obj/machinery/porta_turret/syndicate/pod - -/obj/item/sbeacondrop/heavylaserturret - desc = "A label on it reads: Warning: Activating this device will send a heavy laser turret to your location." - droptype = /obj/machinery/porta_turret/syndicate/energy/heavy - -/obj/item/sbeacondrop/penetratorturret - desc = "A label on it reads: Warning: Activating this device will send a penetrator turret to your location." - droptype = /obj/machinery/porta_turret/syndicate/shuttle - -/obj/item/sbeacondrop/constructshell - desc = "A label on it reads: Warning: Activating this device will send a Nar'sian construct shell to your location." - droptype = /obj/structure/constructshell +//////////////////////////////////////// +//Singularity beacon +//////////////////////////////////////// +/obj/machinery/power/singularity_beacon + name = "ominous beacon" + desc = "This looks suspicious..." + icon = 'icons/obj/singularity.dmi' + icon_state = "beacon0" + + anchored = FALSE + density = TRUE + layer = BELOW_MOB_LAYER //so people can't hide it and it's REALLY OBVIOUS + machine_stat = 0 + verb_say = "states" + var/cooldown = 0 + + var/active = 0 + var/icontype = "beacon" + + +/obj/machinery/power/singularity_beacon/proc/Activate(mob/user = null) + if(surplus() < 1500) + if(user) + to_chat(user, "The connected wire doesn't have enough current.") + return + for(var/obj/singularity/singulo in GLOB.singularities) + if(singulo.z == z) + singulo.target = src + icon_state = "[icontype]1" + active = 1 + if(user) + to_chat(user, "You activate the beacon.") + + +/obj/machinery/power/singularity_beacon/proc/Deactivate(mob/user = null) + for(var/obj/singularity/singulo in GLOB.singularities) + if(singulo.target == src) + singulo.target = null + icon_state = "[icontype]0" + active = 0 + if(user) + to_chat(user, "You deactivate the beacon.") + + +/obj/machinery/power/singularity_beacon/attack_ai(mob/user) + return + + +/obj/machinery/power/singularity_beacon/attack_hand(mob/user) + . = ..() + if(.) + return + if(anchored) + return active ? Deactivate(user) : Activate(user) + else + to_chat(user, "You need to screw \the [src] to the floor first!") + +/obj/machinery/power/singularity_beacon/attackby(obj/item/W, mob/user, params) + if(W.tool_behaviour == TOOL_WRENCH) + if(active) + to_chat(user, "You need to deactivate \the [src] first!") + return + + if(anchored) + setAnchored(FALSE) + to_chat(user, "You unbolt \the [src] from the floor and detach it from the cable.") + disconnect_from_network() + return + else + if(!connect_to_network()) + to_chat(user, "\The [src] must be placed over an exposed, powered cable node!") + return + setAnchored(TRUE) + to_chat(user, "You bolt \the [src] to the floor and attach it to the cable.") + return + else if(W.tool_behaviour == TOOL_SCREWDRIVER) + user.visible_message( \ + "[user] messes with \the [src] for a bit.", \ + "You can't fit the screwdriver into \the [src]'s bolts! Try using a wrench.") + else + return ..() + +/obj/machinery/power/singularity_beacon/Destroy() + if(active) + Deactivate() + return ..() + +//stealth direct power usage +/obj/machinery/power/singularity_beacon/process() + if(!active) + return + + if(surplus() >= 1500) + add_load(1500) + if(cooldown <= world.time) + cooldown = world.time + 80 + for(var/obj/singularity/singulo in GLOB.singularities) + if(singulo.z == z) + say("[singulo] is now [get_dist(src,singulo)] standard lengths away to the [dir2text(get_dir(src,singulo))]") + else + Deactivate() + say("Insufficient charge detected - powering down") + + +/obj/machinery/power/singularity_beacon/syndicate + icontype = "beaconsynd" + icon_state = "beaconsynd0" + +// SINGULO BEACON SPAWNER +/obj/item/sbeacondrop + name = "suspicious beacon" + icon = 'icons/obj/device.dmi' + icon_state = "beacon" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + desc = "A label on it reads: Warning: Activating this device will send a special beacon to your location." + w_class = WEIGHT_CLASS_SMALL + var/droptype = /obj/machinery/power/singularity_beacon/syndicate + + +/obj/item/sbeacondrop/attack_self(mob/user) + if(user) + to_chat(user, "Locked In.") + new droptype( user.loc ) + playsound(src, 'sound/effects/pop.ogg', 100, TRUE, TRUE) + qdel(src) + return + +/obj/item/sbeacondrop/bomb + desc = "A label on it reads: Warning: Activating this device will send a high-ordinance explosive to your location." + droptype = /obj/machinery/syndicatebomb + +/obj/item/sbeacondrop/powersink + desc = "A label on it reads: Warning: Activating this device will send a power draining device to your location." + droptype = /obj/item/powersink + +/obj/item/sbeacondrop/clownbomb + desc = "A label on it reads: Warning: Activating this device will send a silly explosive to your location." + droptype = /obj/machinery/syndicatebomb/badmin/clown + +/obj/item/sbeacondrop/semiautoturret + desc = "A label on it reads: Warning: Activating this device will send a semi-auto turret to your location." + droptype = /obj/machinery/porta_turret/syndicate/pod + +/obj/item/sbeacondrop/heavylaserturret + desc = "A label on it reads: Warning: Activating this device will send a heavy laser turret to your location." + droptype = /obj/machinery/porta_turret/syndicate/energy/heavy + +/obj/item/sbeacondrop/penetratorturret + desc = "A label on it reads: Warning: Activating this device will send a penetrator turret to your location." + droptype = /obj/machinery/porta_turret/syndicate/shuttle + +/obj/item/sbeacondrop/constructshell + desc = "A label on it reads: Warning: Activating this device will send a Nar'sian construct shell to your location." + droptype = /obj/structure/constructshell diff --git a/code/game/machinery/telecomms/computers/message.dm b/code/game/machinery/telecomms/computers/message.dm index bf867c6e6d81..f3b77f196958 100644 --- a/code/game/machinery/telecomms/computers/message.dm +++ b/code/game/machinery/telecomms/computers/message.dm @@ -1,480 +1,480 @@ -/* - The monitoring computer for the messaging server. - Lets you read PDA and request console messages. -*/ - -#define LINKED_SERVER_NONRESPONSIVE (!linkedServer || (linkedServer.machine_stat & (NOPOWER|BROKEN))) - -#define MSG_MON_SCREEN_MAIN 0 -#define MSG_MON_SCREEN_LOGS 1 -#define MSG_MON_SCREEN_HACKED 2 -#define MSG_MON_SCREEN_CUSTOM_MSG 3 -#define MSG_MON_SCREEN_REQUEST_LOGS 4 - -// The monitor itself. -/obj/machinery/computer/message_monitor - name = "message monitor console" - desc = "Used to monitor the crew's PDA messages, as well as request console messages." - icon_screen = "comm_logs" - circuit = /obj/item/circuitboard/computer/message_monitor - //Server linked to. - var/obj/machinery/telecomms/message_server/linkedServer = null - //Sparks effect - For emag - var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread - //Messages - Saves me time if I want to change something. - var/noserver = "ALERT: No server detected." - var/incorrectkey = "ALERT: Incorrect decryption key!" - var/defaultmsg = "Welcome. Please select an option." - var/rebootmsg = "%$&(£: Critical %$$@ Error // !RestArting! - ?pLeaSe wAit!" - //Computer properties - var/screen = MSG_MON_SCREEN_MAIN // 0 = Main menu, 1 = Message Logs, 2 = Hacked screen, 3 = Custom Message - var/hacking = FALSE // Is it being hacked into by the AI/Cyborg - var/message = "System bootup complete. Please select an option." // The message that shows on the main menu. - var/auth = FALSE // Are they authenticated? - var/optioncount = 7 - // Custom Message Properties - var/customsender = "System Administrator" - var/obj/item/pda/customrecepient = null - var/customjob = "Admin" - var/custommessage = "This is a test, please ignore." - - light_color = LIGHT_COLOR_GREEN - -/obj/machinery/computer/message_monitor/attackby(obj/item/O, mob/living/user, params) - if(O.tool_behaviour == TOOL_SCREWDRIVER && (obj_flags & EMAGGED)) - //Stops people from just unscrewing the monitor and putting it back to get the console working again. - to_chat(user, "It is too hot to mess with!") - else - return ..() - -/obj/machinery/computer/message_monitor/emag_act(mob/user) - if(obj_flags & EMAGGED) - return - if(!isnull(linkedServer)) - obj_flags |= EMAGGED - screen = MSG_MON_SCREEN_HACKED - spark_system.set_up(5, 0, src) - spark_system.start() - var/obj/item/paper/monitorkey/MK = new(loc, linkedServer) - // Will help make emagging the console not so easy to get away with. - MK.info += "

                    £%@%(*$%&(£&?*(%&£/{}" - var/time = 100 * length(linkedServer.decryptkey) - addtimer(CALLBACK(src, .proc/UnmagConsole), time) - message = rebootmsg - else - to_chat(user, "A no server error appears on the screen.") - -/obj/machinery/computer/message_monitor/New() - ..() - GLOB.telecomms_list += src - -/obj/machinery/computer/message_monitor/Initialize() - ..() - return INITIALIZE_HINT_LATELOAD - -/obj/machinery/computer/message_monitor/LateInitialize() - //Is the server isn't linked to a server, and there's a server available, default it to the first one in the list. - if(!linkedServer) - for(var/obj/machinery/telecomms/message_server/S in GLOB.telecomms_list) - linkedServer = S - break - -/obj/machinery/computer/message_monitor/Destroy() - GLOB.telecomms_list -= src - return ..() - -/obj/machinery/computer/message_monitor/ui_interact(mob/living/user) - . = ..() - //If the computer is being hacked or is emagged, display the reboot message. - if(hacking || (obj_flags & EMAGGED)) - message = rebootmsg - var/dat = "
                    " - - if(auth) - dat += "

                    \[Authenticated\] /" - dat += " Server Power: [linkedServer && linkedServer.on ? "\[On\]":"\[Off\]"]

                    " - else - dat += "

                    \[Unauthenticated\] /" - dat += " Server Power: [linkedServer && linkedServer.on ? "\[On\]":"\[Off\]"]

                    " - - if(hacking || (obj_flags & EMAGGED)) - screen = MSG_MON_SCREEN_HACKED - else if(!auth || LINKED_SERVER_NONRESPONSIVE) - if(LINKED_SERVER_NONRESPONSIVE) - message = noserver - screen = MSG_MON_SCREEN_MAIN - - switch(screen) - //Main menu - if(MSG_MON_SCREEN_MAIN) - // = TAB - var/i = 0 - dat += "
                    [++i]. Link To A Server
                    " - if(auth) - if(LINKED_SERVER_NONRESPONSIVE) - dat += "
                    ERROR: Server not found!
                    " - else - dat += "
                    [++i]. View Message Logs
                    " - dat += "
                    [++i]. View Request Console Logs
                    " - dat += "
                    [++i]. Clear Message Logs
                    " - dat += "
                    [++i]. Clear Request Console Logs
                    " - dat += "
                    [++i]. Set Custom Key
                    " - dat += "
                    [++i]. Send Admin Message
                    " - else - for(var/n = ++i; n <= optioncount; n++) - dat += "
                    [n]. ---------------
                    " - var/mob/living/silicon/S = usr - if(istype(S) && S.hack_software) - //Malf/Traitor AIs can bruteforce into the system to gain the Key. - dat += "
                    *&@#. Bruteforce Key
                    " - else - dat += "
                    " - - //Bottom message - if(!auth) - dat += "

                    Please authenticate with the server in order to show additional options." - else - dat += "

                    Reg, #514 forbids sending messages to a Head of Staff containing Erotic Rendering Properties." - - //Message Logs - if(MSG_MON_SCREEN_LOGS) - var/index = 0 - dat += "
                    Back - Refresh

                    " - dat += "" - for(var/datum/data_pda_msg/pda in linkedServer.pda_msgs) - index++ - if(index > 3000) - break - // Del - Sender - Recepient - Message - // X - Al Green - Your Mom - WHAT UP!? - dat += "" - dat += "
                    XSenderRecipientMessage
                    X
                    [pda.sender][pda.recipient][pda.message][pda.picture ? " (Photo)":""]
                    " - //Hacking screen. - if(MSG_MON_SCREEN_HACKED) - if(isAI(user) || iscyborg(user)) - dat += "Brute-forcing for server key.
                    It will take 20 seconds for every character that the password has." - dat += "In the meantime, this console can reveal your true intentions if you let someone access it. Make sure no humans enter the room during that time." - else - //It's the same message as the one above but in binary. Because robots understand binary and humans don't... well I thought it was clever. - dat += {"01000010011100100111010101110100011001010010110
                    - 10110011001101111011100100110001101101001011011100110011
                    - 10010000001100110011011110111001000100000011100110110010
                    - 10111001001110110011001010111001000100000011010110110010
                    - 10111100100101110001000000100100101110100001000000111011
                    - 10110100101101100011011000010000001110100011000010110101
                    - 10110010100100000001100100011000000100000011100110110010
                    - 10110001101101111011011100110010001110011001000000110011
                    - 00110111101110010001000000110010101110110011001010111001
                    - 00111100100100000011000110110100001100001011100100110000
                    - 10110001101110100011001010111001000100000011101000110100
                    - 00110000101110100001000000111010001101000011001010010000
                    - 00111000001100001011100110111001101110111011011110111001
                    - 00110010000100000011010000110000101110011001011100010000
                    - 00100100101101110001000000111010001101000011001010010000
                    - 00110110101100101011000010110111001110100011010010110110
                    - 10110010100101100001000000111010001101000011010010111001
                    - 10010000001100011011011110110111001110011011011110110110
                    - 00110010100100000011000110110000101101110001000000111001
                    - 00110010101110110011001010110000101101100001000000111100
                    - 10110111101110101011100100010000001110100011100100111010
                    - 10110010100100000011010010110111001110100011001010110111
                    - 00111010001101001011011110110111001110011001000000110100
                    - 10110011000100000011110010110111101110101001000000110110
                    - 00110010101110100001000000111001101101111011011010110010
                    - 10110111101101110011001010010000001100001011000110110001
                    - 10110010101110011011100110010000001101001011101000010111
                    - 00010000001001101011000010110101101100101001000000111001
                    - 10111010101110010011001010010000001101110011011110010000
                    - 00110100001110101011011010110000101101110011100110010000
                    - 00110010101101110011101000110010101110010001000000111010
                    - 00110100001100101001000000111001001101111011011110110110
                    - 10010000001100100011101010111001001101001011011100110011
                    - 10010000001110100011010000110000101110100001000000111010
                    - 001101001011011010110010100101110"} - - //Fake messages - if(MSG_MON_SCREEN_CUSTOM_MSG) - dat += "
                    Back - Reset

                    " - - dat += {" - - - - "} - //Sender - Sender's Job - Recepient - Message - //Al Green- Your Dad - Your Mom - WHAT UP!? - - dat += {" - - - "} - dat += "
                    SenderSender's JobRecipientMessage
                    [customsender][customjob][customrecepient ? customrecepient.owner : "NONE"][custommessage]

                    Send" - - //Request Console Logs - if(MSG_MON_SCREEN_REQUEST_LOGS) - - var/index = 0 - /* data_rc_msg - X - 5% - var/rec_dpt = "Unspecified" //name of the person - 15% - var/send_dpt = "Unspecified" //name of the sender- 15% - var/message = "Blank" //transferred message - 300px - var/stamp = "Unstamped" - 15% - var/id_auth = "Unauthenticated" - 15% - var/priority = "Normal" - 10% - */ - dat += "
                    Back - Refresh

                    " - dat += {" - "} - for(var/datum/data_rc_msg/rc in linkedServer.rc_msgs) - index++ - if(index > 3000) - break - // Del - Sender - Recepient - Message - // X - Al Green - Your Mom - WHAT UP!? - dat += {" - "} - dat += "
                    XSending Dep.Receiving Dep.MessageStampID Auth.Priority.
                    X
                    [rc.send_dpt][rc.rec_dpt][rc.message][rc.stamp][rc.id_auth][rc.priority]
                    " - - message = defaultmsg - var/datum/browser/popup = new(user, "hologram_console", name, 700, 700) - popup.set_content(dat) - popup.set_title_image(user.browse_rsc_icon(icon, icon_state)) - popup.open() - -/obj/machinery/computer/message_monitor/proc/BruteForce(mob/user) - if(isnull(linkedServer)) - to_chat(user, "Could not complete brute-force: Linked Server Disconnected!") - else - var/currentKey = linkedServer.decryptkey - to_chat(user, "Brute-force completed! The key is '[currentKey]'.") - hacking = FALSE - screen = MSG_MON_SCREEN_MAIN // Return the screen back to normal - -/obj/machinery/computer/message_monitor/proc/UnmagConsole() - obj_flags &= ~EMAGGED - -/obj/machinery/computer/message_monitor/proc/ResetMessage() - customsender = "System Administrator" - customrecepient = null - custommessage = "This is a test, please ignore." - customjob = "Admin" - -/obj/machinery/computer/message_monitor/Topic(href, href_list) - if(..()) - return - - if(usr.contents.Find(src) || (in_range(src, usr) && isturf(loc)) || issilicon(usr)) - //Authenticate - if (href_list["auth"]) - if(LINKED_SERVER_NONRESPONSIVE) - message = noserver - else if(auth) - auth = FALSE - screen = MSG_MON_SCREEN_MAIN - else - var/dkey = trim(input(usr, "Please enter the decryption key.") as text|null) - if(dkey && dkey != "") - if(linkedServer.decryptkey == dkey) - auth = TRUE - else - message = incorrectkey - - //Turn the server on/off. - if (href_list["active"]) - if(LINKED_SERVER_NONRESPONSIVE) - message = noserver - else if(auth) - linkedServer.toggled = !linkedServer.toggled - //Find a server - if (href_list["find"]) - var/list/message_servers = list() - for (var/obj/machinery/telecomms/message_server/M in GLOB.telecomms_list) - message_servers += M - - if(message_servers.len > 1) - linkedServer = input(usr, "Please select a server.", "Select a server.", null) as null|anything in message_servers - message = "NOTICE: Server selected." - else if(message_servers.len > 0) - linkedServer = message_servers[1] - message = "NOTICE: Only Single Server Detected - Server selected." - else - message = noserver - - //View the logs - KEY REQUIRED - if (href_list["view_logs"]) - if(LINKED_SERVER_NONRESPONSIVE) - message = noserver - else if(auth) - screen = MSG_MON_SCREEN_LOGS - - //Clears the logs - KEY REQUIRED - if (href_list["clear_logs"]) - if(LINKED_SERVER_NONRESPONSIVE) - message = noserver - else if(auth) - linkedServer.pda_msgs = list() - message = "NOTICE: Logs cleared." - //Clears the request console logs - KEY REQUIRED - if (href_list["clear_requests"]) - if(LINKED_SERVER_NONRESPONSIVE) - message = noserver - else if(auth) - linkedServer.rc_msgs = list() - message = "NOTICE: Logs cleared." - //Change the password - KEY REQUIRED - if (href_list["pass"]) - if(LINKED_SERVER_NONRESPONSIVE) - message = noserver - else if(auth) - var/dkey = stripped_input(usr, "Please enter the decryption key.") - if(dkey && dkey != "") - if(linkedServer.decryptkey == dkey) - var/newkey = stripped_input(usr,"Please enter the new key (3 - 16 characters max):") - if(length(newkey) <= 3) - message = "NOTICE: Decryption key too short!" - else if(length(newkey) > 16) - message = "NOTICE: Decryption key too long!" - else if(newkey && newkey != "") - linkedServer.decryptkey = newkey - message = "NOTICE: Decryption key set." - else - message = incorrectkey - - //Hack the Console to get the password - if (href_list["hack"]) - var/mob/living/silicon/S = usr - if(istype(S) && S.hack_software) - hacking = TRUE - screen = MSG_MON_SCREEN_HACKED - //Time it takes to bruteforce is dependant on the password length. - addtimer(CALLBACK(src, .proc/finish_bruteforce, usr), 100*length(linkedServer.decryptkey)) - - //Delete the log. - if (href_list["delete_logs"]) - //Are they on the view logs screen? - if(screen == MSG_MON_SCREEN_LOGS) - if(LINKED_SERVER_NONRESPONSIVE) - message = noserver - else //if(istype(href_list["delete_logs"], /datum/data_pda_msg)) - linkedServer.pda_msgs -= locate(href_list["delete_logs"]) in linkedServer.pda_msgs - message = "NOTICE: Log Deleted!" - //Delete the request console log. - if (href_list["delete_requests"]) - //Are they on the view logs screen? - if(screen == MSG_MON_SCREEN_REQUEST_LOGS) - if(LINKED_SERVER_NONRESPONSIVE) - message = noserver - else //if(istype(href_list["delete_logs"], /datum/data_pda_msg)) - linkedServer.rc_msgs -= locate(href_list["delete_requests"]) in linkedServer.rc_msgs - message = "NOTICE: Log Deleted!" - //Create a custom message - if (href_list["msg"]) - if(LINKED_SERVER_NONRESPONSIVE) - message = noserver - else if(auth) - screen = MSG_MON_SCREEN_CUSTOM_MSG - //Fake messaging selection - KEY REQUIRED - if (href_list["select"]) - if(LINKED_SERVER_NONRESPONSIVE) - message = noserver - screen = MSG_MON_SCREEN_MAIN - else - switch(href_list["select"]) - - //Reset - if("Reset") - ResetMessage() - - //Select Your Name - if("Sender") - customsender = stripped_input(usr, "Please enter the sender's name.") || customsender - - //Select Receiver - if("Recepient") - //Get out list of viable PDAs - var/list/obj/item/pda/sendPDAs = get_viewable_pdas() - if(GLOB.PDAs && GLOB.PDAs.len > 0) - customrecepient = input(usr, "Select a PDA from the list.") as null|anything in sendPDAs - else - customrecepient = null - - //Enter custom job - if("RecJob") - customjob = stripped_input(usr, "Please enter the sender's job.") || customjob - - //Enter message - if("Message") - custommessage = stripped_input(usr, "Please enter your message.") || custommessage - - //Send message - if("Send") - if(isnull(customsender) || customsender == "") - customsender = "UNKNOWN" - - if(isnull(customrecepient)) - message = "NOTICE: No recepient selected!" - return attack_hand(usr) - - if(isnull(custommessage) || custommessage == "") - message = "NOTICE: No message entered!" - return attack_hand(usr) - - var/datum/signal/subspace/messaging/pda/signal = new(src, list( - "name" = "[customsender]", - "job" = "[customjob]", - "message" = custommessage, - "targets" = list("[customrecepient.owner] ([customrecepient.ownjob])") - )) - // this will log the signal and transmit it to the target - linkedServer.receive_information(signal, null) - usr.log_message("(PDA: [name] | [usr.real_name]) sent \"[custommessage]\" to [signal.format_target()]", LOG_PDA) - - - //Request Console Logs - KEY REQUIRED - if(href_list["view_requests"]) - if(LINKED_SERVER_NONRESPONSIVE) - message = noserver - else if(auth) - screen = MSG_MON_SCREEN_REQUEST_LOGS - - if (href_list["back"]) - screen = MSG_MON_SCREEN_MAIN - - return attack_hand(usr) - -/obj/machinery/computer/message_monitor/proc/finish_bruteforce(mob/user) - if(!QDELETED(user)) - BruteForce(user) - return - hacking = FALSE - screen = MSG_MON_SCREEN_MAIN - -#undef MSG_MON_SCREEN_MAIN -#undef MSG_MON_SCREEN_LOGS -#undef MSG_MON_SCREEN_HACKED -#undef MSG_MON_SCREEN_CUSTOM_MSG -#undef MSG_MON_SCREEN_REQUEST_LOGS - -#undef LINKED_SERVER_NONRESPONSIVE - -/obj/item/paper/monitorkey - name = "monitor decryption key" - -/obj/item/paper/monitorkey/Initialize(mapload, obj/machinery/telecomms/message_server/server) - ..() - if (server) - print(server) - return INITIALIZE_HINT_NORMAL - else - return INITIALIZE_HINT_LATELOAD - -/obj/item/paper/monitorkey/proc/print(obj/machinery/telecomms/message_server/server) - info = "

                    Daily Key Reset


                    The new message monitor key is '[server.decryptkey]'.
                    Please keep this a secret and away from the clown.
                    If necessary, change the password to a more secure one." - add_overlay("paper_words") - -/obj/item/paper/monitorkey/LateInitialize() - for (var/obj/machinery/telecomms/message_server/preset/server in GLOB.telecomms_list) - if (server.decryptkey) - print(server) - break +/* + The monitoring computer for the messaging server. + Lets you read PDA and request console messages. +*/ + +#define LINKED_SERVER_NONRESPONSIVE (!linkedServer || (linkedServer.machine_stat & (NOPOWER|BROKEN))) + +#define MSG_MON_SCREEN_MAIN 0 +#define MSG_MON_SCREEN_LOGS 1 +#define MSG_MON_SCREEN_HACKED 2 +#define MSG_MON_SCREEN_CUSTOM_MSG 3 +#define MSG_MON_SCREEN_REQUEST_LOGS 4 + +// The monitor itself. +/obj/machinery/computer/message_monitor + name = "message monitor console" + desc = "Used to monitor the crew's PDA messages, as well as request console messages." + icon_screen = "comm_logs" + circuit = /obj/item/circuitboard/computer/message_monitor + //Server linked to. + var/obj/machinery/telecomms/message_server/linkedServer = null + //Sparks effect - For emag + var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread + //Messages - Saves me time if I want to change something. + var/noserver = "ALERT: No server detected." + var/incorrectkey = "ALERT: Incorrect decryption key!" + var/defaultmsg = "Welcome. Please select an option." + var/rebootmsg = "%$&(£: Critical %$$@ Error // !RestArting! - ?pLeaSe wAit!" + //Computer properties + var/screen = MSG_MON_SCREEN_MAIN // 0 = Main menu, 1 = Message Logs, 2 = Hacked screen, 3 = Custom Message + var/hacking = FALSE // Is it being hacked into by the AI/Cyborg + var/message = "System bootup complete. Please select an option." // The message that shows on the main menu. + var/auth = FALSE // Are they authenticated? + var/optioncount = 7 + // Custom Message Properties + var/customsender = "System Administrator" + var/obj/item/pda/customrecepient = null + var/customjob = "Admin" + var/custommessage = "This is a test, please ignore." + + light_color = LIGHT_COLOR_GREEN + +/obj/machinery/computer/message_monitor/attackby(obj/item/O, mob/living/user, params) + if(O.tool_behaviour == TOOL_SCREWDRIVER && (obj_flags & EMAGGED)) + //Stops people from just unscrewing the monitor and putting it back to get the console working again. + to_chat(user, "It is too hot to mess with!") + else + return ..() + +/obj/machinery/computer/message_monitor/emag_act(mob/user) + if(obj_flags & EMAGGED) + return + if(!isnull(linkedServer)) + obj_flags |= EMAGGED + screen = MSG_MON_SCREEN_HACKED + spark_system.set_up(5, 0, src) + spark_system.start() + var/obj/item/paper/monitorkey/MK = new(loc, linkedServer) + // Will help make emagging the console not so easy to get away with. + MK.info += "

                    £%@%(*$%&(£&?*(%&£/{}" + var/time = 100 * length(linkedServer.decryptkey) + addtimer(CALLBACK(src, .proc/UnmagConsole), time) + message = rebootmsg + else + to_chat(user, "A no server error appears on the screen.") + +/obj/machinery/computer/message_monitor/New() + ..() + GLOB.telecomms_list += src + +/obj/machinery/computer/message_monitor/Initialize() + ..() + return INITIALIZE_HINT_LATELOAD + +/obj/machinery/computer/message_monitor/LateInitialize() + //Is the server isn't linked to a server, and there's a server available, default it to the first one in the list. + if(!linkedServer) + for(var/obj/machinery/telecomms/message_server/S in GLOB.telecomms_list) + linkedServer = S + break + +/obj/machinery/computer/message_monitor/Destroy() + GLOB.telecomms_list -= src + return ..() + +/obj/machinery/computer/message_monitor/ui_interact(mob/living/user) + . = ..() + //If the computer is being hacked or is emagged, display the reboot message. + if(hacking || (obj_flags & EMAGGED)) + message = rebootmsg + var/dat = "
                    " + + if(auth) + dat += "

                    \[Authenticated\] /" + dat += " Server Power: [linkedServer && linkedServer.on ? "\[On\]":"\[Off\]"]

                    " + else + dat += "

                    \[Unauthenticated\] /" + dat += " Server Power: [linkedServer && linkedServer.on ? "\[On\]":"\[Off\]"]

                    " + + if(hacking || (obj_flags & EMAGGED)) + screen = MSG_MON_SCREEN_HACKED + else if(!auth || LINKED_SERVER_NONRESPONSIVE) + if(LINKED_SERVER_NONRESPONSIVE) + message = noserver + screen = MSG_MON_SCREEN_MAIN + + switch(screen) + //Main menu + if(MSG_MON_SCREEN_MAIN) + // = TAB + var/i = 0 + dat += "
                    [++i]. Link To A Server
                    " + if(auth) + if(LINKED_SERVER_NONRESPONSIVE) + dat += "
                    ERROR: Server not found!
                    " + else + dat += "
                    [++i]. View Message Logs
                    " + dat += "
                    [++i]. View Request Console Logs
                    " + dat += "
                    [++i]. Clear Message Logs
                    " + dat += "
                    [++i]. Clear Request Console Logs
                    " + dat += "
                    [++i]. Set Custom Key
                    " + dat += "
                    [++i]. Send Admin Message
                    " + else + for(var/n = ++i; n <= optioncount; n++) + dat += "
                    [n]. ---------------
                    " + var/mob/living/silicon/S = usr + if(istype(S) && S.hack_software) + //Malf/Traitor AIs can bruteforce into the system to gain the Key. + dat += "
                    *&@#. Bruteforce Key
                    " + else + dat += "
                    " + + //Bottom message + if(!auth) + dat += "

                    Please authenticate with the server in order to show additional options." + else + dat += "

                    Reg, #514 forbids sending messages to a Head of Staff containing Erotic Rendering Properties." + + //Message Logs + if(MSG_MON_SCREEN_LOGS) + var/index = 0 + dat += "
                    Back - Refresh

                    " + dat += "" + for(var/datum/data_pda_msg/pda in linkedServer.pda_msgs) + index++ + if(index > 3000) + break + // Del - Sender - Recepient - Message + // X - Al Green - Your Mom - WHAT UP!? + dat += "" + dat += "
                    XSenderRecipientMessage
                    X
                    [pda.sender][pda.recipient][pda.message][pda.picture ? " (Photo)":""]
                    " + //Hacking screen. + if(MSG_MON_SCREEN_HACKED) + if(isAI(user) || iscyborg(user)) + dat += "Brute-forcing for server key.
                    It will take 20 seconds for every character that the password has." + dat += "In the meantime, this console can reveal your true intentions if you let someone access it. Make sure no humans enter the room during that time." + else + //It's the same message as the one above but in binary. Because robots understand binary and humans don't... well I thought it was clever. + dat += {"01000010011100100111010101110100011001010010110
                    + 10110011001101111011100100110001101101001011011100110011
                    + 10010000001100110011011110111001000100000011100110110010
                    + 10111001001110110011001010111001000100000011010110110010
                    + 10111100100101110001000000100100101110100001000000111011
                    + 10110100101101100011011000010000001110100011000010110101
                    + 10110010100100000001100100011000000100000011100110110010
                    + 10110001101101111011011100110010001110011001000000110011
                    + 00110111101110010001000000110010101110110011001010111001
                    + 00111100100100000011000110110100001100001011100100110000
                    + 10110001101110100011001010111001000100000011101000110100
                    + 00110000101110100001000000111010001101000011001010010000
                    + 00111000001100001011100110111001101110111011011110111001
                    + 00110010000100000011010000110000101110011001011100010000
                    + 00100100101101110001000000111010001101000011001010010000
                    + 00110110101100101011000010110111001110100011010010110110
                    + 10110010100101100001000000111010001101000011010010111001
                    + 10010000001100011011011110110111001110011011011110110110
                    + 00110010100100000011000110110000101101110001000000111001
                    + 00110010101110110011001010110000101101100001000000111100
                    + 10110111101110101011100100010000001110100011100100111010
                    + 10110010100100000011010010110111001110100011001010110111
                    + 00111010001101001011011110110111001110011001000000110100
                    + 10110011000100000011110010110111101110101001000000110110
                    + 00110010101110100001000000111001101101111011011010110010
                    + 10110111101101110011001010010000001100001011000110110001
                    + 10110010101110011011100110010000001101001011101000010111
                    + 00010000001001101011000010110101101100101001000000111001
                    + 10111010101110010011001010010000001101110011011110010000
                    + 00110100001110101011011010110000101101110011100110010000
                    + 00110010101101110011101000110010101110010001000000111010
                    + 00110100001100101001000000111001001101111011011110110110
                    + 10010000001100100011101010111001001101001011011100110011
                    + 10010000001110100011010000110000101110100001000000111010
                    + 001101001011011010110010100101110"} + + //Fake messages + if(MSG_MON_SCREEN_CUSTOM_MSG) + dat += "
                    Back - Reset

                    " + + dat += {" + + + + "} + //Sender - Sender's Job - Recepient - Message + //Al Green- Your Dad - Your Mom - WHAT UP!? + + dat += {" + + + "} + dat += "
                    SenderSender's JobRecipientMessage
                    [customsender][customjob][customrecepient ? customrecepient.owner : "NONE"][custommessage]

                    Send" + + //Request Console Logs + if(MSG_MON_SCREEN_REQUEST_LOGS) + + var/index = 0 + /* data_rc_msg + X - 5% + var/rec_dpt = "Unspecified" //name of the person - 15% + var/send_dpt = "Unspecified" //name of the sender- 15% + var/message = "Blank" //transferred message - 300px + var/stamp = "Unstamped" - 15% + var/id_auth = "Unauthenticated" - 15% + var/priority = "Normal" - 10% + */ + dat += "
                    Back - Refresh

                    " + dat += {" + "} + for(var/datum/data_rc_msg/rc in linkedServer.rc_msgs) + index++ + if(index > 3000) + break + // Del - Sender - Recepient - Message + // X - Al Green - Your Mom - WHAT UP!? + dat += {" + "} + dat += "
                    XSending Dep.Receiving Dep.MessageStampID Auth.Priority.
                    X
                    [rc.send_dpt][rc.rec_dpt][rc.message][rc.stamp][rc.id_auth][rc.priority]
                    " + + message = defaultmsg + var/datum/browser/popup = new(user, "hologram_console", name, 700, 700) + popup.set_content(dat) + popup.set_title_image(user.browse_rsc_icon(icon, icon_state)) + popup.open() + +/obj/machinery/computer/message_monitor/proc/BruteForce(mob/user) + if(isnull(linkedServer)) + to_chat(user, "Could not complete brute-force: Linked Server Disconnected!") + else + var/currentKey = linkedServer.decryptkey + to_chat(user, "Brute-force completed! The key is '[currentKey]'.") + hacking = FALSE + screen = MSG_MON_SCREEN_MAIN // Return the screen back to normal + +/obj/machinery/computer/message_monitor/proc/UnmagConsole() + obj_flags &= ~EMAGGED + +/obj/machinery/computer/message_monitor/proc/ResetMessage() + customsender = "System Administrator" + customrecepient = null + custommessage = "This is a test, please ignore." + customjob = "Admin" + +/obj/machinery/computer/message_monitor/Topic(href, href_list) + if(..()) + return + + if(usr.contents.Find(src) || (in_range(src, usr) && isturf(loc)) || issilicon(usr)) + //Authenticate + if (href_list["auth"]) + if(LINKED_SERVER_NONRESPONSIVE) + message = noserver + else if(auth) + auth = FALSE + screen = MSG_MON_SCREEN_MAIN + else + var/dkey = trim(input(usr, "Please enter the decryption key.") as text|null) + if(dkey && dkey != "") + if(linkedServer.decryptkey == dkey) + auth = TRUE + else + message = incorrectkey + + //Turn the server on/off. + if (href_list["active"]) + if(LINKED_SERVER_NONRESPONSIVE) + message = noserver + else if(auth) + linkedServer.toggled = !linkedServer.toggled + //Find a server + if (href_list["find"]) + var/list/message_servers = list() + for (var/obj/machinery/telecomms/message_server/M in GLOB.telecomms_list) + message_servers += M + + if(message_servers.len > 1) + linkedServer = input(usr, "Please select a server.", "Select a server.", null) as null|anything in message_servers + message = "NOTICE: Server selected." + else if(message_servers.len > 0) + linkedServer = message_servers[1] + message = "NOTICE: Only Single Server Detected - Server selected." + else + message = noserver + + //View the logs - KEY REQUIRED + if (href_list["view_logs"]) + if(LINKED_SERVER_NONRESPONSIVE) + message = noserver + else if(auth) + screen = MSG_MON_SCREEN_LOGS + + //Clears the logs - KEY REQUIRED + if (href_list["clear_logs"]) + if(LINKED_SERVER_NONRESPONSIVE) + message = noserver + else if(auth) + linkedServer.pda_msgs = list() + message = "NOTICE: Logs cleared." + //Clears the request console logs - KEY REQUIRED + if (href_list["clear_requests"]) + if(LINKED_SERVER_NONRESPONSIVE) + message = noserver + else if(auth) + linkedServer.rc_msgs = list() + message = "NOTICE: Logs cleared." + //Change the password - KEY REQUIRED + if (href_list["pass"]) + if(LINKED_SERVER_NONRESPONSIVE) + message = noserver + else if(auth) + var/dkey = stripped_input(usr, "Please enter the decryption key.") + if(dkey && dkey != "") + if(linkedServer.decryptkey == dkey) + var/newkey = stripped_input(usr,"Please enter the new key (3 - 16 characters max):") + if(length(newkey) <= 3) + message = "NOTICE: Decryption key too short!" + else if(length(newkey) > 16) + message = "NOTICE: Decryption key too long!" + else if(newkey && newkey != "") + linkedServer.decryptkey = newkey + message = "NOTICE: Decryption key set." + else + message = incorrectkey + + //Hack the Console to get the password + if (href_list["hack"]) + var/mob/living/silicon/S = usr + if(istype(S) && S.hack_software) + hacking = TRUE + screen = MSG_MON_SCREEN_HACKED + //Time it takes to bruteforce is dependant on the password length. + addtimer(CALLBACK(src, .proc/finish_bruteforce, usr), 100*length(linkedServer.decryptkey)) + + //Delete the log. + if (href_list["delete_logs"]) + //Are they on the view logs screen? + if(screen == MSG_MON_SCREEN_LOGS) + if(LINKED_SERVER_NONRESPONSIVE) + message = noserver + else //if(istype(href_list["delete_logs"], /datum/data_pda_msg)) + linkedServer.pda_msgs -= locate(href_list["delete_logs"]) in linkedServer.pda_msgs + message = "NOTICE: Log Deleted!" + //Delete the request console log. + if (href_list["delete_requests"]) + //Are they on the view logs screen? + if(screen == MSG_MON_SCREEN_REQUEST_LOGS) + if(LINKED_SERVER_NONRESPONSIVE) + message = noserver + else //if(istype(href_list["delete_logs"], /datum/data_pda_msg)) + linkedServer.rc_msgs -= locate(href_list["delete_requests"]) in linkedServer.rc_msgs + message = "NOTICE: Log Deleted!" + //Create a custom message + if (href_list["msg"]) + if(LINKED_SERVER_NONRESPONSIVE) + message = noserver + else if(auth) + screen = MSG_MON_SCREEN_CUSTOM_MSG + //Fake messaging selection - KEY REQUIRED + if (href_list["select"]) + if(LINKED_SERVER_NONRESPONSIVE) + message = noserver + screen = MSG_MON_SCREEN_MAIN + else + switch(href_list["select"]) + + //Reset + if("Reset") + ResetMessage() + + //Select Your Name + if("Sender") + customsender = stripped_input(usr, "Please enter the sender's name.") || customsender + + //Select Receiver + if("Recepient") + //Get out list of viable PDAs + var/list/obj/item/pda/sendPDAs = get_viewable_pdas() + if(GLOB.PDAs && GLOB.PDAs.len > 0) + customrecepient = input(usr, "Select a PDA from the list.") as null|anything in sendPDAs + else + customrecepient = null + + //Enter custom job + if("RecJob") + customjob = stripped_input(usr, "Please enter the sender's job.") || customjob + + //Enter message + if("Message") + custommessage = stripped_input(usr, "Please enter your message.") || custommessage + + //Send message + if("Send") + if(isnull(customsender) || customsender == "") + customsender = "UNKNOWN" + + if(isnull(customrecepient)) + message = "NOTICE: No recepient selected!" + return attack_hand(usr) + + if(isnull(custommessage) || custommessage == "") + message = "NOTICE: No message entered!" + return attack_hand(usr) + + var/datum/signal/subspace/messaging/pda/signal = new(src, list( + "name" = "[customsender]", + "job" = "[customjob]", + "message" = custommessage, + "targets" = list("[customrecepient.owner] ([customrecepient.ownjob])") + )) + // this will log the signal and transmit it to the target + linkedServer.receive_information(signal, null) + usr.log_message("(PDA: [name] | [usr.real_name]) sent \"[custommessage]\" to [signal.format_target()]", LOG_PDA) + + + //Request Console Logs - KEY REQUIRED + if(href_list["view_requests"]) + if(LINKED_SERVER_NONRESPONSIVE) + message = noserver + else if(auth) + screen = MSG_MON_SCREEN_REQUEST_LOGS + + if (href_list["back"]) + screen = MSG_MON_SCREEN_MAIN + + return attack_hand(usr) + +/obj/machinery/computer/message_monitor/proc/finish_bruteforce(mob/user) + if(!QDELETED(user)) + BruteForce(user) + return + hacking = FALSE + screen = MSG_MON_SCREEN_MAIN + +#undef MSG_MON_SCREEN_MAIN +#undef MSG_MON_SCREEN_LOGS +#undef MSG_MON_SCREEN_HACKED +#undef MSG_MON_SCREEN_CUSTOM_MSG +#undef MSG_MON_SCREEN_REQUEST_LOGS + +#undef LINKED_SERVER_NONRESPONSIVE + +/obj/item/paper/monitorkey + name = "monitor decryption key" + +/obj/item/paper/monitorkey/Initialize(mapload, obj/machinery/telecomms/message_server/server) + ..() + if (server) + print(server) + return INITIALIZE_HINT_NORMAL + else + return INITIALIZE_HINT_LATELOAD + +/obj/item/paper/monitorkey/proc/print(obj/machinery/telecomms/message_server/server) + info = "

                    Daily Key Reset


                    The new message monitor key is '[server.decryptkey]'.
                    Please keep this a secret and away from the clown.
                    If necessary, change the password to a more secure one." + add_overlay("paper_words") + +/obj/item/paper/monitorkey/LateInitialize() + for (var/obj/machinery/telecomms/message_server/preset/server in GLOB.telecomms_list) + if (server.decryptkey) + print(server) + break diff --git a/code/game/machinery/telecomms/machine_interactions.dm b/code/game/machinery/telecomms/machine_interactions.dm index 569b6ef534ac..2ef00eac2339 100644 --- a/code/game/machinery/telecomms/machine_interactions.dm +++ b/code/game/machinery/telecomms/machine_interactions.dm @@ -7,6 +7,8 @@ /obj/machinery/telecomms var/temp = "" // output message + var/tempfreq = FREQ_COMMON + var/mob/living/operator /obj/machinery/telecomms/attackby(obj/item/P, mob/user, params) @@ -27,74 +29,182 @@ else return ..() -/obj/machinery/telecomms/ui_interact(mob/user) - . = ..() - // You need a multitool to use this, or be silicon - if(!issilicon(user)) - // istype returns false if the value is null - if(!istype(user.get_active_held_item(), /obj/item/multitool)) - return - var/obj/item/multitool/P = get_multitool(user) - var/dat - dat = "[name]

                    [name] Access

                    " - dat += "
                    [temp]
                    " - dat += "
                    Power Status: [toggled ? "On" : "Off"]" - if(on && toggled) - if(id != "" && id) - dat += "
                    Identification String: [id]" - else - dat += "
                    Identification String: NULL" - dat += "
                    Network: [network]" - dat += "
                    Prefabrication: [autolinkers.len ? "TRUE" : "FALSE"]" - if(hide) - dat += "
                    Shadow Link: ACTIVE" - - //Show additional options for certain machines. - dat += Options_Menu() - - dat += "
                    Linked Network Entities:
                      " - - var/i = 0 - for(var/obj/machinery/telecomms/T in links) - i++ - if(T.hide && !hide) - continue - dat += "
                    1. [REF(T)] [T.name] ([T.id]) \[X\]
                    2. " - dat += "
                    " - - dat += "
                    Filtering Frequencies: " - - i = 0 - if(length(freq_listening)) - for(var/x in freq_listening) - i++ - if(i < length(freq_listening)) - dat += "[format_frequency(x)] GHz\[X\]; " - else - dat += "[format_frequency(x)] GHz\[X\]" - else - dat += "NONE" +/obj/machinery/telecomms/ui_interact(mob/user, datum/tgui/ui) + operator = user + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "Telecomms") + ui.open() + +/obj/machinery/telecomms/ui_data(mob/user) + var/list/data = list() + + data += add_option() + + data["minfreq"] = MIN_FREE_FREQ + data["maxfreq"] = MAX_FREE_FREQ + data["frequency"] = tempfreq + + var/obj/item/multitool/heldmultitool = get_multitool(user) + data["multitool"] = heldmultitool + + if(heldmultitool) + data["multibuff"] = heldmultitool.buffer + + data["toggled"] = toggled + data["id"] = id + data["network"] = network + data["prefab"] = autolinkers.len ? TRUE : FALSE + + var/list/linked = list() + var/i = 0 + data["linked"] = list() + for(var/obj/machinery/telecomms/machine in links) + i++ + if(machine.hide && !hide) + continue + var/list/entry = list() + entry["index"] = i + entry["name"] = machine.name + entry["id"] = machine.id + linked += list(entry) + data["linked"] = linked + + var/list/frequencies = list() + data["frequencies"] = list() + for(var/x in freq_listening) + frequencies += list(x) + data["frequencies"] = frequencies + + return data + +/obj/machinery/telecomms/ui_act(action, params) + if(..()) + return - dat += "
                    \[Add Filter\]" - dat += "
                    " + if(!issilicon(usr)) + if(!istype(usr.get_active_held_item(), /obj/item/multitool)) + return - if(P) - var/obj/machinery/telecomms/T = P.buffer - if(istype(T)) - dat += "

                    MULTITOOL BUFFER: [T] ([T.id]) \[Link\] \[Flush\]" + var/obj/item/multitool/heldmultitool = get_multitool(operator) + + switch(action) + if("toggle") + toggled = !toggled + update_power() + update_icon_state() + log_game("[key_name(operator)] toggled [toggled ? "On" : "Off"] [src] at [AREACOORD(src)].") + . = TRUE + if("id") + if(params["value"]) + if(length(params["value"]) > 32) + to_chat(operator, "Error: Machine ID too long!") + playsound(src, 'sound/machines/buzz-sigh.ogg', 50, TRUE) + return + else + id = params["value"] + log_game("[key_name(operator)] has changed the ID for [src] at [AREACOORD(src)] to [id].") + . = TRUE + if("network") + if(params["value"]) + if(length(params["value"]) > 15) + to_chat(operator, "Error: Network name too long!") + playsound(src, 'sound/machines/buzz-sigh.ogg', 50, TRUE) + return + else + for(var/obj/machinery/telecomms/T in links) + T.links.Remove(src) + network = params["value"] + links = list() + log_game("[key_name(operator)] has changed the network for [src] at [AREACOORD(src)] to [network].") + . = TRUE + if("tempfreq") + if(params["value"]) + tempfreq = text2num(params["value"]) * 10 + if("freq") + var/newfreq = tempfreq * 10 + if(newfreq == FREQ_SYNDICATE) + to_chat(operator, "Error: Interference preventing filtering frequency: \"[newfreq / 10] GHz\"") + playsound(src, 'sound/machines/buzz-sigh.ogg', 50, TRUE) else - dat += "

                    MULTITOOL BUFFER:
                    \[Add Machine\]" + if(!(newfreq in freq_listening) && newfreq < 10000) + freq_listening.Add(newfreq) + log_game("[key_name(operator)] added frequency [newfreq] for [src] at [AREACOORD(src)].") + . = TRUE + if("delete") + freq_listening.Remove(params["value"]) + log_game("[key_name(operator)] added removed frequency [params["value"]] for [src] at [AREACOORD(src)].") + . = TRUE + if("unlink") + var/obj/machinery/telecomms/T = links[text2num(params["value"])] + if(T) + // Remove link entries from both T and src. + if(T.links) + T.links.Remove(src) + links.Remove(T) + log_game("[key_name(operator)] unlinked [src] and [T] at [AREACOORD(src)].") + . = TRUE + if("link") + if(heldmultitool) + var/obj/machinery/telecomms/T = heldmultitool.buffer + if(istype(T) && T != src) + if(!(src in T.links)) + T.links += src + if(!(T in links)) + links += T + log_game("[key_name(operator)] linked [src] for [T] at [AREACOORD(src)].") + . = TRUE + if("buffer") + heldmultitool.buffer = src + . = TRUE + if("flush") + heldmultitool.buffer = null + . = TRUE + + add_act(action, params) + . = TRUE + +/obj/machinery/telecomms/proc/add_option() + return - dat += "
                    " - temp = "" - user << browse(dat, "window=tcommachine;size=520x500;can_resize=0") - onclose(user, "tcommachine") - return TRUE +/obj/machinery/telecomms/bus/add_option() + var/list/data = list() + data["type"] = "bus" + data["changefrequency"] = change_frequency + return data + +/obj/machinery/telecomms/relay/add_option() + var/list/data = list() + data["type"] = "relay" + data["broadcasting"] = broadcasting + data["receiving"] = receiving + return data + +/obj/machinery/telecomms/proc/add_act(action, params) + +/obj/machinery/telecomms/relay/add_act(action, params) + switch(action) + if("broadcast") + broadcasting = !broadcasting + . = TRUE + if("receive") + receiving = !receiving + . = TRUE + +/obj/machinery/telecomms/bus/add_act(action, params) + switch(action) + if("change_freq") + var/newfreq = text2num(params["value"]) * 10 + if(newfreq) + if(newfreq < 10000) + change_frequency = newfreq + . = TRUE + else + change_frequency = 0 // Returns a multitool from a user depending on their mobtype. /obj/machinery/telecomms/proc/get_multitool(mob/user) - var/obj/item/multitool/P = null // Let's double check if(!issilicon(user) && istype(user.get_active_held_item(), /obj/item/multitool)) @@ -107,168 +217,6 @@ P = user.get_active_held_item() return P -// Additional Options for certain machines. Use this when you want to add an option to a specific machine. -// Example of how to use below. - -/obj/machinery/telecomms/proc/Options_Menu() - return "" - -// The topic for Additional Options. Use this for checking href links for your specific option. -// Example of how to use below. -/obj/machinery/telecomms/proc/Options_Topic(href, href_list) - return - -// RELAY - -/obj/machinery/telecomms/relay/Options_Menu() - var/dat = "" - dat += "
                    Broadcasting: [broadcasting ? "YES" : "NO"]" - dat += "
                    Receiving: [receiving ? "YES" : "NO"]" - return dat - -/obj/machinery/telecomms/relay/Options_Topic(href, href_list) - - if(href_list["receive"]) - receiving = !receiving - temp = "-% Receiving mode changed. %-" - if(href_list["broadcast"]) - broadcasting = !broadcasting - temp = "-% Broadcasting mode changed. %-" - -// BUS - -/obj/machinery/telecomms/bus/Options_Menu() - var/dat = "
                    Change Signal Frequency: [change_frequency ? "YES ([change_frequency])" : "NO"]" - return dat - -/obj/machinery/telecomms/bus/Options_Topic(href, href_list) - - if(href_list["change_freq"]) - - var/newfreq = input(usr, "Specify a new frequency for new signals to change to. Enter null to turn off frequency changing. Decimals assigned automatically.", src, network) as null|num - if(canAccess(usr)) - if(newfreq) - if(findtext(num2text(newfreq), ".")) - newfreq *= 10 // shift the decimal one place - if(newfreq < 10000) - change_frequency = newfreq - temp = "-% New frequency to change to assigned: \"[newfreq] GHz\" %-" - else - change_frequency = 0 - temp = "-% Frequency changing deactivated %-" - - -/obj/machinery/telecomms/Topic(href, href_list) - if(..()) - return - - if(!issilicon(usr)) - if(!istype(usr.get_active_held_item(), /obj/item/multitool)) - return - - var/obj/item/multitool/P = get_multitool(usr) - - if(href_list["input"]) - switch(href_list["input"]) - - if("toggle") - - toggled = !toggled - temp = "-% [src] has been [toggled ? "activated" : "deactivated"]." - update_power() - - - if("id") - var/newid = reject_bad_text(stripped_input(usr, "Specify the new ID for this machine", src, id, MAX_MESSAGE_LEN)) - if(newid && canAccess(usr)) - id = newid - temp = "-% New ID assigned: \"[id]\" %-" - - if("network") - var/newnet = stripped_input(usr, "Specify the new network for this machine. This will break all current links.", src, network) - if(newnet && canAccess(usr)) - - if(length(newnet) > 15) - temp = "-% Too many characters in new network tag %-" - - else - for(var/obj/machinery/telecomms/T in links) - T.links.Remove(src) - - network = newnet - links = list() - temp = "-% New network tag assigned: \"[network]\" %-" - - - if("freq") - var/newfreq = input(usr, "Specify a new frequency to filter (GHz). Decimals assigned automatically.", src, network) as null|num - if(newfreq && canAccess(usr)) - if(findtext(num2text(newfreq), ".")) - newfreq *= 10 // shift the decimal one place - if(newfreq == FREQ_SYNDICATE) - temp = "-% Error: Interference preventing filtering frequency: \"[newfreq] GHz\" %-" - else - if(!(newfreq in freq_listening) && newfreq < 10000) - freq_listening.Add(newfreq) - temp = "-% New frequency filter assigned: \"[newfreq] GHz\" %-" - - if(href_list["delete"]) - - // changed the layout about to workaround a pesky runtime -- Doohl - - var/x = text2num(href_list["delete"]) - temp = "-% Removed frequency filter [x] %-" - freq_listening.Remove(x) - - if(href_list["unlink"]) - - if(text2num(href_list["unlink"]) <= length(links)) - var/obj/machinery/telecomms/T = links[text2num(href_list["unlink"])] - if(T) - temp = "-% Removed [REF(T)] [T.name] from linked entities. %-" - - // Remove link entries from both T and src. - - if(T.links) - T.links.Remove(src) - links.Remove(T) - - else - temp = "-% Unable to locate machine to unlink from, try again. %-" - - if(href_list["link"]) - - if(P) - var/obj/machinery/telecomms/T = P.buffer - if(istype(T) && T != src) - if(!(src in T.links)) - T.links += src - - if(!(T in links)) - links += T - - temp = "-% Successfully linked with [REF(T)] [T.name] %-" - - else - temp = "-% Unable to acquire buffer %-" - - if(href_list["buffer"]) - - P.buffer = src - temp = "-% Successfully stored [REF(P.buffer)] [P.buffer.name] in buffer %-" - - - if(href_list["flush"]) - - temp = "-% Buffer successfully flushed. %-" - P.buffer = null - - Options_Topic(href, href_list) - - usr.set_machine(src) - - updateUsrDialog() - /obj/machinery/telecomms/proc/canAccess(mob/user) if(issilicon(user) || in_range(user, src)) return TRUE diff --git a/code/game/machinery/telecomms/machines/message_server.dm b/code/game/machinery/telecomms/machines/message_server.dm index 7137971c3740..ed4258dce1f7 100644 --- a/code/game/machinery/telecomms/machines/message_server.dm +++ b/code/game/machinery/telecomms/machines/message_server.dm @@ -1,268 +1,268 @@ -/* - The equivalent of the server, for PDA and request console messages. - Without it, PDA and request console messages cannot be transmitted. - PDAs require the rest of the telecomms setup, but request consoles only - require the message server. -*/ - -// A decorational representation of SSblackbox, usually placed alongside the message server. Also contains a traitor theft item. -/obj/machinery/blackbox_recorder - icon = 'icons/obj/stationobjs.dmi' - icon_state = "blackbox" - name = "Blackbox Recorder" - density = TRUE - use_power = IDLE_POWER_USE - idle_power_usage = 10 - active_power_usage = 100 - armor = list("melee" = 25, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 70) - var/obj/item/stored - -/obj/machinery/blackbox_recorder/Initialize() - . = ..() - stored = new /obj/item/blackbox(src) - -/obj/machinery/blackbox_recorder/attack_hand(mob/living/user) - . = ..() - if(stored) - user.put_in_hands(stored) - stored = null - to_chat(user, "You remove the blackbox from [src]. The tapes stop spinning.") - update_icon() - return - else - to_chat(user, "It seems that the blackbox is missing...") - return - -/obj/machinery/blackbox_recorder/attackby(obj/item/I, mob/living/user, params) - if(istype(I, /obj/item/blackbox)) - if(HAS_TRAIT(I, TRAIT_NODROP) || !user.transferItemToLoc(I, src)) - to_chat(user, "[I] is stuck to your hand!") - return - user.visible_message("[user] clicks [I] into [src]!", \ - "You press the device into [src], and it clicks into place. The tapes begin spinning again.") - playsound(src, 'sound/machines/click.ogg', 50, TRUE) - stored = I - update_icon() - return - return ..() - -/obj/machinery/blackbox_recorder/Destroy() - if(stored) - stored.forceMove(loc) - new /obj/effect/decal/cleanable/oil(loc) - return ..() - -/obj/machinery/blackbox_recorder/update_icon() - . = ..() - if(!stored) - icon_state = "blackbox_b" - else - icon_state = "blackbox" - -/obj/item/blackbox - name = "\proper the blackbox" - desc = "A strange relic, capable of recording data on extradimensional vertices. It lives inside the blackbox recorder for safe keeping." - icon = 'icons/obj/stationobjs.dmi' - icon_state = "blackcube" - lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' - righthand_file = 'icons/mob/inhands/items_righthand.dmi' - w_class = WEIGHT_CLASS_BULKY - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF - -#define MESSAGE_SERVER_FUNCTIONING_MESSAGE "This is an automated message. The messaging system is functioning correctly." - -// The message server itself. -/obj/machinery/telecomms/message_server - icon_state = "message_server" - name = "Messaging Server" - desc = "A machine that processes and routes PDA and request console messages." - density = TRUE - use_power = IDLE_POWER_USE - idle_power_usage = 10 - active_power_usage = 100 - circuit = /obj/item/circuitboard/machine/telecomms/message_server - - var/list/datum/data_pda_msg/pda_msgs = list() - var/list/datum/data_rc_msg/rc_msgs = list() - var/decryptkey = "password" - var/calibrating = 15 MINUTES //Init reads this and adds world.time, then becomes 0 when that time has passed and the machine works - -/obj/machinery/telecomms/message_server/Initialize(mapload) - . = ..() - if (!decryptkey) - decryptkey = GenerateKey() - - if (calibrating) - calibrating += world.time - say("Calibrating... Estimated wait time: [rand(3, 9)] minutes.") - pda_msgs += new /datum/data_pda_msg("System Administrator", "system", "This is an automated message. System calibration started at [station_time_timestamp()]") - else - pda_msgs += new /datum/data_pda_msg("System Administrator", "system", MESSAGE_SERVER_FUNCTIONING_MESSAGE) - -/obj/machinery/telecomms/message_server/Destroy() - for(var/obj/machinery/computer/message_monitor/monitor in GLOB.telecomms_list) - if(monitor.linkedServer && monitor.linkedServer == src) - monitor.linkedServer = null - . = ..() - -/obj/machinery/telecomms/message_server/examine(mob/user) - . = ..() - if(calibrating) - . += "It's still calibrating." - -/obj/machinery/telecomms/message_server/proc/GenerateKey() - var/newKey - newKey += pick("the", "if", "of", "as", "in", "a", "you", "from", "to", "an", "too", "little", "snow", "dead", "drunk", "rosebud", "duck", "al", "le") - newKey += pick("diamond", "beer", "mushroom", "assistant", "clown", "captain", "twinkie", "security", "nuke", "small", "big", "escape", "yellow", "gloves", "monkey", "engine", "nuclear", "ai") - newKey += pick("1", "2", "3", "4", "5", "6", "7", "8", "9", "0") - return newKey - -/obj/machinery/telecomms/message_server/process() - . = ..() - if(calibrating && calibrating <= world.time) - calibrating = 0 - pda_msgs += new /datum/data_pda_msg("System Administrator", "system", MESSAGE_SERVER_FUNCTIONING_MESSAGE) - -/obj/machinery/telecomms/message_server/receive_information(datum/signal/subspace/messaging/signal, obj/machinery/telecomms/machine_from) - // can't log non-message signals - if(!istype(signal) || !signal.data["message"] || !on || calibrating) - return - - // log the signal - if(istype(signal, /datum/signal/subspace/messaging/pda)) - var/datum/signal/subspace/messaging/pda/PDAsignal = signal - var/datum/data_pda_msg/M = new(PDAsignal.format_target(), "[PDAsignal.data["name"]] ([PDAsignal.data["job"]])", PDAsignal.data["message"], PDAsignal.data["photo"]) - pda_msgs += M - signal.logged = M - else if(istype(signal, /datum/signal/subspace/messaging/rc)) - var/datum/data_rc_msg/M = new(signal.data["rec_dpt"], signal.data["send_dpt"], signal.data["message"], signal.data["stamped"], signal.data["verified"], signal.data["priority"]) - signal.logged = M - if(signal.data["send_dpt"]) // don't log messages not from a department but allow them to work - rc_msgs += M - signal.data["reject"] = FALSE - - // pass it along to either the hub or the broadcaster - if(!relay_information(signal, /obj/machinery/telecomms/hub)) - relay_information(signal, /obj/machinery/telecomms/broadcaster) - -/obj/machinery/telecomms/message_server/update_overlays() - . = ..() - - if(calibrating) - . += "message_server_calibrate" - - -// Root messaging signal datum -/datum/signal/subspace/messaging - frequency = FREQ_COMMON - server_type = /obj/machinery/telecomms/message_server - var/datum/logged - -/datum/signal/subspace/messaging/New(init_source, init_data) - source = init_source - data = init_data - var/turf/T = get_turf(source) - levels = list(T.z) - if(!("reject" in data)) - data["reject"] = TRUE - -/datum/signal/subspace/messaging/copy() - var/datum/signal/subspace/messaging/copy = new type(source, data.Copy()) - copy.original = src - copy.levels = levels - return copy - -// PDA signal datum -/datum/signal/subspace/messaging/pda/proc/format_target() - if (length(data["targets"]) > 1) - return "Everyone" - return data["targets"][1] - -/datum/signal/subspace/messaging/pda/proc/format_message() - if (logged && data["photo"]) - return "\"[data["message"]]\" (Photo)" - return "\"[data["message"]]\"" - -/datum/signal/subspace/messaging/pda/broadcast() - if (!logged) // Can only go through if a message server logs it - return - for (var/obj/item/pda/P in GLOB.PDAs) - if ("[P.owner] ([P.ownjob])" in data["targets"]) - P.receive_message(src) - -// Request Console signal datum -/datum/signal/subspace/messaging/rc/broadcast() - if (!logged) // Like /pda, only if logged - return - var/rec_dpt = ckey(data["rec_dpt"]) - for (var/obj/machinery/requests_console/Console in GLOB.allConsoles) - if(ckey(Console.department) == rec_dpt || (data["ore_update"] && Console.receive_ore_updates)) - Console.createmessage(data["sender"], data["send_dpt"], data["message"], data["verified"], data["stamped"], data["priority"], data["notify_freq"]) - -// Log datums stored by the message server. -/datum/data_pda_msg - var/sender = "Unspecified" - var/recipient = "Unspecified" - var/message = "Blank" // transferred message - var/datum/picture/picture // attached photo - var/automated = 0 //automated message - -/datum/data_pda_msg/New(param_rec, param_sender, param_message, param_photo) - if(param_rec) - recipient = param_rec - if(param_sender) - sender = param_sender - if(param_message) - message = param_message - if(param_photo) - picture = param_photo - -/datum/data_pda_msg/Topic(href,href_list) - ..() - if(href_list["photo"]) - var/mob/M = usr - M << browse_rsc(picture.picture_image, "pda_photo.png") - M << browse("PDA Photo" \ - + "" \ - + "" \ - + "", "window=pdaphoto;size=[picture.psize_x]x[picture.psize_y];can-close=true") - onclose(M, "pdaphoto") - -/datum/data_rc_msg - var/rec_dpt = "Unspecified" // receiving department - var/send_dpt = "Unspecified" // sending department - var/message = "Blank" - var/stamp = "Unstamped" - var/id_auth = "Unauthenticated" - var/priority = "Normal" - -/datum/data_rc_msg/New(param_rec, param_sender, param_message, param_stamp, param_id_auth, param_priority) - if(param_rec) - rec_dpt = param_rec - if(param_sender) - send_dpt = param_sender - if(param_message) - message = param_message - if(param_stamp) - stamp = param_stamp - if(param_id_auth) - id_auth = param_id_auth - if(param_priority) - switch(param_priority) - if(REQ_NORMAL_MESSAGE_PRIORITY) - priority = "Normal" - if(REQ_HIGH_MESSAGE_PRIORITY) - priority = "High" - if(REQ_EXTREME_MESSAGE_PRIORITY) - priority = "Extreme" - else - priority = "Undetermined" - -#undef MESSAGE_SERVER_FUNCTIONING_MESSAGE - -/obj/machinery/telecomms/message_server/preset - id = "Messaging Server" - network = "tcommsat" - autolinkers = list("messaging") - decryptkey = null //random - calibrating = 0 +/* + The equivalent of the server, for PDA and request console messages. + Without it, PDA and request console messages cannot be transmitted. + PDAs require the rest of the telecomms setup, but request consoles only + require the message server. +*/ + +// A decorational representation of SSblackbox, usually placed alongside the message server. Also contains a traitor theft item. +/obj/machinery/blackbox_recorder + icon = 'icons/obj/stationobjs.dmi' + icon_state = "blackbox" + name = "Blackbox Recorder" + density = TRUE + use_power = IDLE_POWER_USE + idle_power_usage = 10 + active_power_usage = 100 + armor = list("melee" = 25, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 70) + var/obj/item/stored + +/obj/machinery/blackbox_recorder/Initialize() + . = ..() + stored = new /obj/item/blackbox(src) + +/obj/machinery/blackbox_recorder/attack_hand(mob/living/user) + . = ..() + if(stored) + user.put_in_hands(stored) + stored = null + to_chat(user, "You remove the blackbox from [src]. The tapes stop spinning.") + update_icon() + return + else + to_chat(user, "It seems that the blackbox is missing...") + return + +/obj/machinery/blackbox_recorder/attackby(obj/item/I, mob/living/user, params) + if(istype(I, /obj/item/blackbox)) + if(HAS_TRAIT(I, TRAIT_NODROP) || !user.transferItemToLoc(I, src)) + to_chat(user, "[I] is stuck to your hand!") + return + user.visible_message("[user] clicks [I] into [src]!", \ + "You press the device into [src], and it clicks into place. The tapes begin spinning again.") + playsound(src, 'sound/machines/click.ogg', 50, TRUE) + stored = I + update_icon() + return + return ..() + +/obj/machinery/blackbox_recorder/Destroy() + if(stored) + stored.forceMove(loc) + new /obj/effect/decal/cleanable/oil(loc) + return ..() + +/obj/machinery/blackbox_recorder/update_icon() + . = ..() + if(!stored) + icon_state = "blackbox_b" + else + icon_state = "blackbox" + +/obj/item/blackbox + name = "\proper the blackbox" + desc = "A strange relic, capable of recording data on extradimensional vertices. It lives inside the blackbox recorder for safe keeping." + icon = 'icons/obj/stationobjs.dmi' + icon_state = "blackcube" + lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' + righthand_file = 'icons/mob/inhands/items_righthand.dmi' + w_class = WEIGHT_CLASS_BULKY + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF + +#define MESSAGE_SERVER_FUNCTIONING_MESSAGE "This is an automated message. The messaging system is functioning correctly." + +// The message server itself. +/obj/machinery/telecomms/message_server + icon_state = "message_server" + name = "Messaging Server" + desc = "A machine that processes and routes PDA and request console messages." + density = TRUE + use_power = IDLE_POWER_USE + idle_power_usage = 10 + active_power_usage = 100 + circuit = /obj/item/circuitboard/machine/telecomms/message_server + + var/list/datum/data_pda_msg/pda_msgs = list() + var/list/datum/data_rc_msg/rc_msgs = list() + var/decryptkey = "password" + var/calibrating = 15 MINUTES //Init reads this and adds world.time, then becomes 0 when that time has passed and the machine works + +/obj/machinery/telecomms/message_server/Initialize(mapload) + . = ..() + if (!decryptkey) + decryptkey = GenerateKey() + + if (calibrating) + calibrating += world.time + say("Calibrating... Estimated wait time: [rand(3, 9)] minutes.") + pda_msgs += new /datum/data_pda_msg("System Administrator", "system", "This is an automated message. System calibration started at [station_time_timestamp()]") + else + pda_msgs += new /datum/data_pda_msg("System Administrator", "system", MESSAGE_SERVER_FUNCTIONING_MESSAGE) + +/obj/machinery/telecomms/message_server/Destroy() + for(var/obj/machinery/computer/message_monitor/monitor in GLOB.telecomms_list) + if(monitor.linkedServer && monitor.linkedServer == src) + monitor.linkedServer = null + . = ..() + +/obj/machinery/telecomms/message_server/examine(mob/user) + . = ..() + if(calibrating) + . += "It's still calibrating." + +/obj/machinery/telecomms/message_server/proc/GenerateKey() + var/newKey + newKey += pick("the", "if", "of", "as", "in", "a", "you", "from", "to", "an", "too", "little", "snow", "dead", "drunk", "rosebud", "duck", "al", "le") + newKey += pick("diamond", "beer", "mushroom", "assistant", "clown", "captain", "twinkie", "security", "nuke", "small", "big", "escape", "yellow", "gloves", "monkey", "engine", "nuclear", "ai") + newKey += pick("1", "2", "3", "4", "5", "6", "7", "8", "9", "0") + return newKey + +/obj/machinery/telecomms/message_server/process() + . = ..() + if(calibrating && calibrating <= world.time) + calibrating = 0 + pda_msgs += new /datum/data_pda_msg("System Administrator", "system", MESSAGE_SERVER_FUNCTIONING_MESSAGE) + +/obj/machinery/telecomms/message_server/receive_information(datum/signal/subspace/messaging/signal, obj/machinery/telecomms/machine_from) + // can't log non-message signals + if(!istype(signal) || !signal.data["message"] || !on || calibrating) + return + + // log the signal + if(istype(signal, /datum/signal/subspace/messaging/pda)) + var/datum/signal/subspace/messaging/pda/PDAsignal = signal + var/datum/data_pda_msg/M = new(PDAsignal.format_target(), "[PDAsignal.data["name"]] ([PDAsignal.data["job"]])", PDAsignal.data["message"], PDAsignal.data["photo"]) + pda_msgs += M + signal.logged = M + else if(istype(signal, /datum/signal/subspace/messaging/rc)) + var/datum/data_rc_msg/M = new(signal.data["rec_dpt"], signal.data["send_dpt"], signal.data["message"], signal.data["stamped"], signal.data["verified"], signal.data["priority"]) + signal.logged = M + if(signal.data["send_dpt"]) // don't log messages not from a department but allow them to work + rc_msgs += M + signal.data["reject"] = FALSE + + // pass it along to either the hub or the broadcaster + if(!relay_information(signal, /obj/machinery/telecomms/hub)) + relay_information(signal, /obj/machinery/telecomms/broadcaster) + +/obj/machinery/telecomms/message_server/update_overlays() + . = ..() + + if(calibrating) + . += "message_server_calibrate" + + +// Root messaging signal datum +/datum/signal/subspace/messaging + frequency = FREQ_COMMON + server_type = /obj/machinery/telecomms/message_server + var/datum/logged + +/datum/signal/subspace/messaging/New(init_source, init_data) + source = init_source + data = init_data + var/turf/T = get_turf(source) + levels = list(T.z) + if(!("reject" in data)) + data["reject"] = TRUE + +/datum/signal/subspace/messaging/copy() + var/datum/signal/subspace/messaging/copy = new type(source, data.Copy()) + copy.original = src + copy.levels = levels + return copy + +// PDA signal datum +/datum/signal/subspace/messaging/pda/proc/format_target() + if (length(data["targets"]) > 1) + return "Everyone" + return data["targets"][1] + +/datum/signal/subspace/messaging/pda/proc/format_message() + if (logged && data["photo"]) + return "\"[data["message"]]\" (Photo)" + return "\"[data["message"]]\"" + +/datum/signal/subspace/messaging/pda/broadcast() + if (!logged) // Can only go through if a message server logs it + return + for (var/obj/item/pda/P in GLOB.PDAs) + if ("[P.owner] ([P.ownjob])" in data["targets"]) + P.receive_message(src) + +// Request Console signal datum +/datum/signal/subspace/messaging/rc/broadcast() + if (!logged) // Like /pda, only if logged + return + var/rec_dpt = ckey(data["rec_dpt"]) + for (var/obj/machinery/requests_console/Console in GLOB.allConsoles) + if(ckey(Console.department) == rec_dpt || (data["ore_update"] && Console.receive_ore_updates)) + Console.createmessage(data["sender"], data["send_dpt"], data["message"], data["verified"], data["stamped"], data["priority"], data["notify_freq"]) + +// Log datums stored by the message server. +/datum/data_pda_msg + var/sender = "Unspecified" + var/recipient = "Unspecified" + var/message = "Blank" // transferred message + var/datum/picture/picture // attached photo + var/automated = 0 //automated message + +/datum/data_pda_msg/New(param_rec, param_sender, param_message, param_photo) + if(param_rec) + recipient = param_rec + if(param_sender) + sender = param_sender + if(param_message) + message = param_message + if(param_photo) + picture = param_photo + +/datum/data_pda_msg/Topic(href,href_list) + ..() + if(href_list["photo"]) + var/mob/M = usr + M << browse_rsc(picture.picture_image, "pda_photo.png") + M << browse("PDA Photo" \ + + "" \ + + "" \ + + "", "window=pdaphoto;size=[picture.psize_x]x[picture.psize_y];can-close=true") + onclose(M, "pdaphoto") + +/datum/data_rc_msg + var/rec_dpt = "Unspecified" // receiving department + var/send_dpt = "Unspecified" // sending department + var/message = "Blank" + var/stamp = "Unstamped" + var/id_auth = "Unauthenticated" + var/priority = "Normal" + +/datum/data_rc_msg/New(param_rec, param_sender, param_message, param_stamp, param_id_auth, param_priority) + if(param_rec) + rec_dpt = param_rec + if(param_sender) + send_dpt = param_sender + if(param_message) + message = param_message + if(param_stamp) + stamp = param_stamp + if(param_id_auth) + id_auth = param_id_auth + if(param_priority) + switch(param_priority) + if(REQ_NORMAL_MESSAGE_PRIORITY) + priority = "Normal" + if(REQ_HIGH_MESSAGE_PRIORITY) + priority = "High" + if(REQ_EXTREME_MESSAGE_PRIORITY) + priority = "Extreme" + else + priority = "Undetermined" + +#undef MESSAGE_SERVER_FUNCTIONING_MESSAGE + +/obj/machinery/telecomms/message_server/preset + id = "Messaging Server" + network = "tcommsat" + autolinkers = list("messaging") + decryptkey = null //random + calibrating = 0 diff --git a/code/game/machinery/telecomms/telecomunications.dm b/code/game/machinery/telecomms/telecomunications.dm index d70d03ac4e23..af687355b2b5 100644 --- a/code/game/machinery/telecomms/telecomunications.dm +++ b/code/game/machinery/telecomms/telecomunications.dm @@ -1,154 +1,154 @@ - -/* - Hello, friends, this is Doohl from sexylands. You may be wondering what this - monstrous code file is. Sit down, boys and girls, while I tell you the tale. - - - The telecom machines were designed to be compatible with any radio - signals, provided they use subspace transmission. Currently they are only used for - headsets, but they can eventually be outfitted for real COMPUTER networks. This - is just a skeleton, ladies and gentlemen. - - Look at radio.dm for the prequel to this code. -*/ - -GLOBAL_LIST_EMPTY(telecomms_list) - -/obj/machinery/telecomms - icon = 'icons/obj/machines/telecomms.dmi' - critical_machine = TRUE - var/list/links = list() // list of machines this machine is linked to - var/traffic = 0 // value increases as traffic increases - var/netspeed = 5 // how much traffic to lose per tick (50 gigabytes/second * netspeed) - var/list/autolinkers = list() // list of text/number values to link with - var/id = "NULL" // identification string - var/network = "NULL" // the network of the machinery - - var/list/freq_listening = list() // list of frequencies to tune into: if none, will listen to all - - var/on = TRUE - var/toggled = TRUE // Is it toggled on - var/long_range_link = FALSE // Can you link it across Z levels or on the otherside of the map? (Relay & Hub) - var/hide = FALSE // Is it a hidden machine? - - -/obj/machinery/telecomms/proc/relay_information(datum/signal/subspace/signal, filter, copysig, amount = 20) - // relay signal to all linked machinery that are of type [filter]. If signal has been sent [amount] times, stop sending - - if(!on) - return - var/send_count = 0 - - // Apply some lag based on traffic rates - var/netlag = round(traffic / 50) - if(netlag > signal.data["slow"]) - signal.data["slow"] = netlag - - // Loop through all linked machines and send the signal or copy. - for(var/obj/machinery/telecomms/machine in links) - if(filter && !istype( machine, filter )) - continue - if(!machine.on) - continue - if(amount && send_count >= amount) - break - if(z != machine.loc.z && !long_range_link && !machine.long_range_link) - continue - - send_count++ - if(machine.is_freq_listening(signal)) - machine.traffic++ - - if(copysig) - machine.receive_information(signal.copy(), src) - else - machine.receive_information(signal, src) - - if(send_count > 0 && is_freq_listening(signal)) - traffic++ - - return send_count - -/obj/machinery/telecomms/proc/relay_direct_information(datum/signal/signal, obj/machinery/telecomms/machine) - // send signal directly to a machine - machine.receive_information(signal, src) - -/obj/machinery/telecomms/proc/receive_information(datum/signal/signal, obj/machinery/telecomms/machine_from) - // receive information from linked machinery - -/obj/machinery/telecomms/proc/is_freq_listening(datum/signal/signal) - // return TRUE if found, FALSE if not found - return signal && (!freq_listening.len || (signal.frequency in freq_listening)) - -/obj/machinery/telecomms/Initialize(mapload) - . = ..() - GLOB.telecomms_list += src - if(mapload && autolinkers.len) - return INITIALIZE_HINT_LATELOAD - -/obj/machinery/telecomms/LateInitialize() - ..() - for(var/obj/machinery/telecomms/T in (long_range_link ? GLOB.telecomms_list : urange(20, src, 1))) - add_link(T) - -/obj/machinery/telecomms/Destroy() - GLOB.telecomms_list -= src - for(var/obj/machinery/telecomms/comm in GLOB.telecomms_list) - comm.links -= src - links = list() - return ..() - -// Used in auto linking -/obj/machinery/telecomms/proc/add_link(obj/machinery/telecomms/T) - var/turf/position = get_turf(src) - var/turf/T_position = get_turf(T) - if((position.z == T_position.z) || (long_range_link && T.long_range_link)) - if(src != T) - for(var/x in autolinkers) - if(x in T.autolinkers) - links |= T - T.links |= src - - -/obj/machinery/telecomms/update_icon_state() - if(on) - if(panel_open) - icon_state = "[initial(icon_state)]_o" - else - icon_state = initial(icon_state) - else - if(panel_open) - icon_state = "[initial(icon_state)]_o_off" - else - icon_state = "[initial(icon_state)]_off" - -/obj/machinery/telecomms/proc/update_power() - - if(toggled) - if(machine_stat & (BROKEN|NOPOWER|EMPED)) // if powered, on. if not powered, off. if too damaged, off - on = FALSE - else - on = TRUE - else - on = FALSE - -/obj/machinery/telecomms/process() - update_power() - - // Update the icon - update_icon() - - if(traffic > 0) - traffic -= netspeed - -/obj/machinery/telecomms/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - if(prob(100/severity) && !(machine_stat & EMPED)) - machine_stat |= EMPED - var/duration = (300 * 10)/severity - addtimer(CALLBACK(src, .proc/de_emp), rand(duration - 20, duration + 20)) - -/obj/machinery/telecomms/proc/de_emp() - machine_stat &= ~EMPED + +/* + Hello, friends, this is Doohl from sexylands. You may be wondering what this + monstrous code file is. Sit down, boys and girls, while I tell you the tale. + + + The telecom machines were designed to be compatible with any radio + signals, provided they use subspace transmission. Currently they are only used for + headsets, but they can eventually be outfitted for real COMPUTER networks. This + is just a skeleton, ladies and gentlemen. + + Look at radio.dm for the prequel to this code. +*/ + +GLOBAL_LIST_EMPTY(telecomms_list) + +/obj/machinery/telecomms + icon = 'icons/obj/machines/telecomms.dmi' + critical_machine = TRUE + var/list/links = list() // list of machines this machine is linked to + var/traffic = 0 // value increases as traffic increases + var/netspeed = 5 // how much traffic to lose per tick (50 gigabytes/second * netspeed) + var/list/autolinkers = list() // list of text/number values to link with + var/id = "NULL" // identification string + var/network = "NULL" // the network of the machinery + + var/list/freq_listening = list() // list of frequencies to tune into: if none, will listen to all + + var/on = TRUE + var/toggled = TRUE // Is it toggled on + var/long_range_link = FALSE // Can you link it across Z levels or on the otherside of the map? (Relay & Hub) + var/hide = FALSE // Is it a hidden machine? + + +/obj/machinery/telecomms/proc/relay_information(datum/signal/subspace/signal, filter, copysig, amount = 20) + // relay signal to all linked machinery that are of type [filter]. If signal has been sent [amount] times, stop sending + + if(!on) + return + var/send_count = 0 + + // Apply some lag based on traffic rates + var/netlag = round(traffic / 50) + if(netlag > signal.data["slow"]) + signal.data["slow"] = netlag + + // Loop through all linked machines and send the signal or copy. + for(var/obj/machinery/telecomms/machine in links) + if(filter && !istype( machine, filter )) + continue + if(!machine.on) + continue + if(amount && send_count >= amount) + break + if(z != machine.loc.z && !long_range_link && !machine.long_range_link) + continue + + send_count++ + if(machine.is_freq_listening(signal)) + machine.traffic++ + + if(copysig) + machine.receive_information(signal.copy(), src) + else + machine.receive_information(signal, src) + + if(send_count > 0 && is_freq_listening(signal)) + traffic++ + + return send_count + +/obj/machinery/telecomms/proc/relay_direct_information(datum/signal/signal, obj/machinery/telecomms/machine) + // send signal directly to a machine + machine.receive_information(signal, src) + +/obj/machinery/telecomms/proc/receive_information(datum/signal/signal, obj/machinery/telecomms/machine_from) + // receive information from linked machinery + +/obj/machinery/telecomms/proc/is_freq_listening(datum/signal/signal) + // return TRUE if found, FALSE if not found + return signal && (!freq_listening.len || (signal.frequency in freq_listening)) + +/obj/machinery/telecomms/Initialize(mapload) + . = ..() + GLOB.telecomms_list += src + if(mapload && autolinkers.len) + return INITIALIZE_HINT_LATELOAD + +/obj/machinery/telecomms/LateInitialize() + ..() + for(var/obj/machinery/telecomms/T in (long_range_link ? GLOB.telecomms_list : urange(20, src, 1))) + add_link(T) + +/obj/machinery/telecomms/Destroy() + GLOB.telecomms_list -= src + for(var/obj/machinery/telecomms/comm in GLOB.telecomms_list) + comm.links -= src + links = list() + return ..() + +// Used in auto linking +/obj/machinery/telecomms/proc/add_link(obj/machinery/telecomms/T) + var/turf/position = get_turf(src) + var/turf/T_position = get_turf(T) + if((position.z == T_position.z) || (long_range_link && T.long_range_link)) + if(src != T) + for(var/x in autolinkers) + if(x in T.autolinkers) + links |= T + T.links |= src + + +/obj/machinery/telecomms/update_icon_state() + if(on) + if(panel_open) + icon_state = "[initial(icon_state)]_o" + else + icon_state = initial(icon_state) + else + if(panel_open) + icon_state = "[initial(icon_state)]_o_off" + else + icon_state = "[initial(icon_state)]_off" + +/obj/machinery/telecomms/proc/update_power() + + if(toggled) + if(machine_stat & (BROKEN|NOPOWER|EMPED)) // if powered, on. if not powered, off. if too damaged, off + on = FALSE + else + on = TRUE + else + on = FALSE + +/obj/machinery/telecomms/process() + update_power() + + // Update the icon + update_icon() + + if(traffic > 0) + traffic -= netspeed + +/obj/machinery/telecomms/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + if(prob(100/severity) && !(machine_stat & EMPED)) + machine_stat |= EMPED + var/duration = (300 * 10)/severity + addtimer(CALLBACK(src, .proc/de_emp), rand(duration - 20, duration + 20)) + +/obj/machinery/telecomms/proc/de_emp() + machine_stat &= ~EMPED diff --git a/code/game/machinery/washing_machine.dm b/code/game/machinery/washing_machine.dm index 655214a0164f..76dadd7f25c6 100644 --- a/code/game/machinery/washing_machine.dm +++ b/code/game/machinery/washing_machine.dm @@ -1,358 +1,358 @@ -//dye registry, add dye colors and their resulting output here if you want the sprite to change instead of just the color. -GLOBAL_LIST_INIT(dye_registry, list( - DYE_REGISTRY_UNDER = list( - DYE_RED = /obj/item/clothing/under/color/red, - DYE_ORANGE = /obj/item/clothing/under/color/orange, - DYE_YELLOW = /obj/item/clothing/under/color/yellow, - DYE_GREEN = /obj/item/clothing/under/color/green, - DYE_BLUE = /obj/item/clothing/under/color/blue, - DYE_PURPLE = /obj/item/clothing/under/color/lightpurple, - DYE_BLACK = /obj/item/clothing/under/color/black, - DYE_WHITE = /obj/item/clothing/under/color/white, - DYE_RAINBOW = /obj/item/clothing/under/color/rainbow, - DYE_MIME = /obj/item/clothing/under/rank/civilian/mime, - DYE_CLOWN = /obj/item/clothing/under/rank/civilian/clown, - DYE_CHAP = /obj/item/clothing/under/rank/civilian/chaplain, - DYE_QM = /obj/item/clothing/under/rank/cargo/qm, - DYE_LAW = /obj/item/clothing/under/suit/black, - DYE_CAPTAIN = /obj/item/clothing/under/rank/command/captain, - DYE_FO = /obj/item/clothing/under/rank/command/head_of_personnel, - DYE_HOS = /obj/item/clothing/under/rank/security/head_of_security, - DYE_CE = /obj/item/clothing/under/rank/engineering/chief_engineer, - DYE_RD = /obj/item/clothing/under/rank/rnd/research_director, - DYE_CMO = /obj/item/clothing/under/rank/medical/chief_medical_officer, - DYE_REDCOAT = /obj/item/clothing/under/costume/redcoat, - DYE_SYNDICATE = /obj/item/clothing/under/syndicate, - DYE_CENTCOM = /obj/item/clothing/under/rank/centcom/commander - ), - DYE_REGISTRY_JUMPSKIRT = list( - DYE_RED = /obj/item/clothing/under/color/jumpskirt/red, - DYE_ORANGE = /obj/item/clothing/under/color/jumpskirt/orange, - DYE_YELLOW = /obj/item/clothing/under/color/jumpskirt/yellow, - DYE_GREEN = /obj/item/clothing/under/color/jumpskirt/green, - DYE_BLUE = /obj/item/clothing/under/color/jumpskirt/blue, - DYE_PURPLE = /obj/item/clothing/under/color/jumpskirt/lightpurple, - DYE_BLACK = /obj/item/clothing/under/color/jumpskirt/black, - DYE_WHITE = /obj/item/clothing/under/color/jumpskirt/white, - DYE_RAINBOW = /obj/item/clothing/under/color/jumpskirt/rainbow, - DYE_MIME = /obj/item/clothing/under/rank/civilian/mime/skirt, - DYE_CHAP = /obj/item/clothing/under/rank/civilian/chaplain/skirt, - DYE_QM = /obj/item/clothing/under/rank/cargo/qm/skirt, - DYE_CAPTAIN = /obj/item/clothing/under/rank/command/captain/skirt, - DYE_HOP = /obj/item/clothing/under/rank/command/head_of_personnel/skirt, - DYE_HOS = /obj/item/clothing/under/rank/security/head_of_security/skirt, - DYE_CE = /obj/item/clothing/under/rank/engineering/chief_engineer/skirt, - DYE_RD = /obj/item/clothing/under/rank/rnd/research_director/skirt, - DYE_CMO = /obj/item/clothing/under/rank/medical/chief_medical_officer/skirt, - ), - DYE_REGISTRY_GLOVES = list( - DYE_RED = /obj/item/clothing/gloves/color/red, - DYE_ORANGE = /obj/item/clothing/gloves/color/orange, - DYE_YELLOW = /obj/item/clothing/gloves/color/yellow, - DYE_GREEN = /obj/item/clothing/gloves/color/green, - DYE_BLUE = /obj/item/clothing/gloves/color/blue, - DYE_PURPLE = /obj/item/clothing/gloves/color/purple, - DYE_BLACK = /obj/item/clothing/gloves/color/black, - DYE_WHITE = /obj/item/clothing/gloves/color/white, - DYE_RAINBOW = /obj/item/clothing/gloves/color/rainbow, - DYE_MIME = /obj/item/clothing/gloves/color/white, - DYE_CLOWN = /obj/item/clothing/gloves/color/rainbow, - DYE_QM = /obj/item/clothing/gloves/color/brown, - DYE_CAPTAIN = /obj/item/clothing/gloves/color/captain, - DYE_FO = /obj/item/clothing/gloves/color/grey, - DYE_HOS = /obj/item/clothing/gloves/color/black, - DYE_CE = /obj/item/clothing/gloves/color/black, - DYE_RD = /obj/item/clothing/gloves/color/grey, - DYE_CMO = /obj/item/clothing/gloves/color/latex/nitrile, - DYE_REDCOAT = /obj/item/clothing/gloves/color/white, - DYE_SYNDICATE = /obj/item/clothing/gloves/combat, - DYE_CENTCOM = /obj/item/clothing/gloves/combat - ), - DYE_REGISTRY_SNEAKERS = list( - DYE_RED = /obj/item/clothing/shoes/sneakers/red, - DYE_ORANGE = /obj/item/clothing/shoes/sneakers/orange, - DYE_YELLOW = /obj/item/clothing/shoes/sneakers/yellow, - DYE_GREEN = /obj/item/clothing/shoes/sneakers/green, - DYE_BLUE = /obj/item/clothing/shoes/sneakers/blue, - DYE_PURPLE = /obj/item/clothing/shoes/sneakers/purple, - DYE_BLACK = /obj/item/clothing/shoes/sneakers/black, - DYE_WHITE = /obj/item/clothing/shoes/sneakers/white, - DYE_RAINBOW = /obj/item/clothing/shoes/sneakers/rainbow, - DYE_MIME = /obj/item/clothing/shoes/sneakers/black, - DYE_CLOWN = /obj/item/clothing/shoes/sneakers/rainbow, - DYE_QM = /obj/item/clothing/shoes/sneakers/brown, - DYE_CAPTAIN = /obj/item/clothing/shoes/sneakers/brown, - DYE_FO = /obj/item/clothing/shoes/sneakers/brown, - DYE_CE = /obj/item/clothing/shoes/sneakers/brown, - DYE_RD = /obj/item/clothing/shoes/sneakers/brown, - DYE_CMO = /obj/item/clothing/shoes/sneakers/brown, - DYE_SYNDICATE = /obj/item/clothing/shoes/combat, - DYE_CENTCOM = /obj/item/clothing/shoes/combat - ), - DYE_REGISTRY_FANNYPACK = list( - DYE_RED = /obj/item/storage/belt/fannypack/red, - DYE_ORANGE = /obj/item/storage/belt/fannypack/orange, - DYE_YELLOW = /obj/item/storage/belt/fannypack/yellow, - DYE_GREEN = /obj/item/storage/belt/fannypack/green, - DYE_BLUE = /obj/item/storage/belt/fannypack/blue, - DYE_PURPLE = /obj/item/storage/belt/fannypack/purple, - DYE_BLACK = /obj/item/storage/belt/fannypack/black, - DYE_WHITE = /obj/item/storage/belt/fannypack/white, - DYE_SYNDICATE = /obj/item/storage/belt/military - ), - DYE_REGISTRY_BEDSHEET = list( - DYE_RED = /obj/item/bedsheet/red, - DYE_ORANGE = /obj/item/bedsheet/orange, - DYE_YELLOW = /obj/item/bedsheet/yellow, - DYE_GREEN = /obj/item/bedsheet/green, - DYE_BLUE = /obj/item/bedsheet/blue, - DYE_PURPLE = /obj/item/bedsheet/purple, - DYE_BLACK = /obj/item/bedsheet/black, - DYE_WHITE = /obj/item/bedsheet, - DYE_RAINBOW = /obj/item/bedsheet/rainbow, - DYE_MIME = /obj/item/bedsheet/mime, - DYE_CLOWN = /obj/item/bedsheet/clown, - DYE_CHAP = /obj/item/bedsheet/chaplain, - DYE_QM = /obj/item/bedsheet/qm, - DYE_LAW = /obj/item/bedsheet/black, - DYE_CAPTAIN = /obj/item/bedsheet/captain, - DYE_FO = /obj/item/bedsheet/head_of_personnel, - DYE_HOS = /obj/item/bedsheet/hos, - DYE_CE = /obj/item/bedsheet/ce, - DYE_RD = /obj/item/bedsheet/rd, - DYE_CMO = /obj/item/bedsheet/cmo, - DYE_COSMIC = /obj/item/bedsheet/cosmos, - DYE_SYNDICATE = /obj/item/bedsheet/syndie, - DYE_CENTCOM = /obj/item/bedsheet/centcom - ), - DYE_LAWYER_SPECIAL = list( - DYE_COSMIC = /obj/item/clothing/under/rank/civilian/lawyer/galaxy, - DYE_SYNDICATE = /obj/item/clothing/under/rank/civilian/lawyer/galaxy/red - ) -)) - -/obj/machinery/washing_machine - name = "washing machine" - desc = "Gets rid of those pesky bloodstains, or your money back!" - icon = 'icons/obj/machines/washing_machine.dmi' - icon_state = "wm_1_0" - density = TRUE - state_open = TRUE - var/busy = FALSE - var/bloody_mess = 0 - var/obj/item/color_source - var/max_wash_capacity = 5 - -/obj/machinery/washing_machine/ComponentInitialize() - . = ..() - RegisterSignal(src, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_blood) - -/obj/machinery/washing_machine/examine(mob/user) - . = ..() - if(!busy) - . += "Alt-click it to start a wash cycle." - -/obj/machinery/washing_machine/AltClick(mob/user) - if(!user.canUseTopic(src, !issilicon(user))) - return - if(busy) - return - if(state_open) - to_chat(user, "Close the door first!") - return - if(bloody_mess) - to_chat(user, "[src] must be cleaned up first!") - return - busy = TRUE - update_icon() - addtimer(CALLBACK(src, .proc/wash_cycle), 200) - - START_PROCESSING(SSfastprocess, src) - -/obj/machinery/washing_machine/process() - if(!busy) - animate(src, transform=matrix(), time=2) - return PROCESS_KILL - if(anchored) - if(prob(5)) - var/matrix/M = new - M.Translate(rand(-1, 1), rand(0, 1)) - animate(src, transform=M, time=1) - animate(transform=matrix(), time=1) - else - if(prob(1)) - step(src, pick(GLOB.cardinals)) - var/matrix/M = new - M.Translate(rand(-3, 3), rand(-1, 3)) - animate(src, transform=M, time=2) - -/obj/machinery/washing_machine/proc/clean_blood() - if(!busy) - bloody_mess = FALSE - update_icon() - -/obj/machinery/washing_machine/proc/wash_cycle() - for(var/X in contents) - var/atom/movable/AM = X - SEND_SIGNAL(AM, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_STRENGTH_BLOOD) - AM.machine_wash(src) - - busy = FALSE - if(color_source) - qdel(color_source) - color_source = null - update_icon() - -/obj/item/proc/dye_item(dye_color, dye_key_override) - var/dye_key_selector = dye_key_override ? dye_key_override : dying_key - if(undyeable) - return FALSE - if(dye_key_selector) - if(!GLOB.dye_registry[dye_key_selector]) - log_runtime("Item just tried to be dyed with an invalid registry key: [dye_key_selector]") - return FALSE - var/obj/item/target_type = GLOB.dye_registry[dye_key_selector][dye_color] - if(target_type) - icon = initial(target_type.icon) - icon_state = initial(target_type.icon_state) - lefthand_file = initial(target_type.lefthand_file) - righthand_file = initial(target_type.righthand_file) - item_state = initial(target_type.item_state) - mob_overlay_icon = initial(target_type.mob_overlay_icon) - inhand_x_dimension = initial(target_type.inhand_x_dimension) - inhand_y_dimension = initial(target_type.inhand_y_dimension) - name = initial(target_type.name) - desc = "[initial(target_type.desc)] The colors look a little dodgy." - return target_type //successfully "appearance copy" dyed something; returns the target type as a hacky way of extending - add_atom_colour(dye_color, FIXED_COLOUR_PRIORITY) - return FALSE - -//what happens to this object when washed inside a washing machine -/atom/movable/proc/machine_wash(obj/machinery/washing_machine/WM) - return - -/obj/item/stack/sheet/hairlesshide/machine_wash(obj/machinery/washing_machine/WM) - new /obj/item/stack/sheet/wethide(drop_location(), amount) - qdel(src) - -/obj/item/clothing/suit/hooded/ian_costume/machine_wash(obj/machinery/washing_machine/WM) - new /obj/item/reagent_containers/food/snacks/meat/slab/corgi(loc) - qdel(src) - -/mob/living/simple_animal/pet/machine_wash(obj/machinery/washing_machine/WM) - WM.bloody_mess = TRUE - gib() - -/obj/item/machine_wash(obj/machinery/washing_machine/WM) - if(WM.color_source) - dye_item(WM.color_source.dye_color) - -/obj/item/clothing/under/dye_item(dye_color, dye_key) - . = ..() - if(.) - var/obj/item/clothing/under/U = . - can_adjust = initial(U.can_adjust) - if(!can_adjust && adjusted) //we deadjust the uniform if it's now unadjustable - toggle_jumpsuit_adjust() - -/obj/item/clothing/under/machine_wash(obj/machinery/washing_machine/WM) - freshly_laundered = TRUE - addtimer(VARSET_CALLBACK(src, freshly_laundered, FALSE), 5 MINUTES, TIMER_UNIQUE | TIMER_OVERRIDE) - ..() - -/obj/item/clothing/head/mob_holder/machine_wash(obj/machinery/washing_machine/WM) - ..() - held_mob.machine_wash(WM) - -/obj/item/clothing/shoes/sneakers/machine_wash(obj/machinery/washing_machine/WM) - if(chained) - chained = 0 - slowdown = SHOES_SLOWDOWN - new /obj/item/restraints/handcuffs(loc) - ..() - -/obj/machinery/washing_machine/relaymove(mob/user) - container_resist(user) - -/obj/machinery/washing_machine/container_resist(mob/living/user) - if(!busy) - add_fingerprint(user) - open_machine() - -/obj/machinery/washing_machine/update_icon_state() - if(busy) - icon_state = "wm_running_[bloody_mess]" - else if(bloody_mess) - icon_state = "wm_[state_open]_blood" - else - var/full = contents.len ? 1 : 0 - icon_state = "wm_[state_open]_[full]" - -/obj/machinery/washing_machine/update_overlays() - . = ..() - if(panel_open) - . += "wm_panel" - -/obj/machinery/washing_machine/attackby(obj/item/W, mob/user, params) - if(panel_open && !busy && default_unfasten_wrench(user, W)) - return - - if(default_deconstruction_screwdriver(user, null, null, W)) - update_icon() - return - - else if(user.a_intent != INTENT_HARM) - if (!state_open) - to_chat(user, "Open the door first!") - return TRUE - - if(bloody_mess) - to_chat(user, "[src] must be cleaned up first!") - return TRUE - - if(contents.len >= max_wash_capacity) - to_chat(user, "The washing machine is full!") - return TRUE - - if(!user.transferItemToLoc(W, src)) - to_chat(user, "\The [W] is stuck to your hand, you cannot put it in the washing machine!") - return TRUE - if(W.dye_color) - color_source = W - update_icon() - - else - return ..() - -/obj/machinery/washing_machine/attack_hand(mob/user) - . = ..() - if(.) - return - if(busy) - to_chat(user, "[src] is busy!") - return - - if(user.pulling && user.a_intent == INTENT_GRAB && isliving(user.pulling)) - var/mob/living/L = user.pulling - if(L.buckled || L.has_buckled_mobs()) - return - if(state_open) - if(istype(L, /mob/living/simple_animal/pet)) - L.forceMove(src) - update_icon() - return - - if(!state_open) - open_machine() - else - state_open = FALSE //close the door - update_icon() - -/obj/machinery/washing_machine/deconstruct(disassembled = TRUE) - new /obj/item/stack/sheet/metal(drop_location(), 2) - qdel(src) - -/obj/machinery/washing_machine/open_machine(drop = 1) - ..() - density = TRUE //because machinery/open_machine() sets it to 0 - color_source = null +//dye registry, add dye colors and their resulting output here if you want the sprite to change instead of just the color. +GLOBAL_LIST_INIT(dye_registry, list( + DYE_REGISTRY_UNDER = list( + DYE_RED = /obj/item/clothing/under/color/red, + DYE_ORANGE = /obj/item/clothing/under/color/orange, + DYE_YELLOW = /obj/item/clothing/under/color/yellow, + DYE_GREEN = /obj/item/clothing/under/color/green, + DYE_BLUE = /obj/item/clothing/under/color/blue, + DYE_PURPLE = /obj/item/clothing/under/color/lightpurple, + DYE_BLACK = /obj/item/clothing/under/color/black, + DYE_WHITE = /obj/item/clothing/under/color/white, + DYE_RAINBOW = /obj/item/clothing/under/color/rainbow, + DYE_MIME = /obj/item/clothing/under/rank/civilian/mime, + DYE_CLOWN = /obj/item/clothing/under/rank/civilian/clown, + DYE_CHAP = /obj/item/clothing/under/rank/civilian/chaplain, + DYE_QM = /obj/item/clothing/under/rank/cargo/qm, + DYE_LAW = /obj/item/clothing/under/suit/black, + DYE_CAPTAIN = /obj/item/clothing/under/rank/command/captain, + DYE_FO = /obj/item/clothing/under/rank/command/head_of_personnel, + DYE_HOS = /obj/item/clothing/under/rank/security/head_of_security, + DYE_CE = /obj/item/clothing/under/rank/engineering/chief_engineer, + DYE_RD = /obj/item/clothing/under/rank/rnd/research_director, + DYE_CMO = /obj/item/clothing/under/rank/medical/chief_medical_officer, + DYE_REDCOAT = /obj/item/clothing/under/costume/redcoat, + DYE_SYNDICATE = /obj/item/clothing/under/syndicate, + DYE_CENTCOM = /obj/item/clothing/under/rank/centcom/commander + ), + DYE_REGISTRY_JUMPSKIRT = list( + DYE_RED = /obj/item/clothing/under/color/jumpskirt/red, + DYE_ORANGE = /obj/item/clothing/under/color/jumpskirt/orange, + DYE_YELLOW = /obj/item/clothing/under/color/jumpskirt/yellow, + DYE_GREEN = /obj/item/clothing/under/color/jumpskirt/green, + DYE_BLUE = /obj/item/clothing/under/color/jumpskirt/blue, + DYE_PURPLE = /obj/item/clothing/under/color/jumpskirt/lightpurple, + DYE_BLACK = /obj/item/clothing/under/color/jumpskirt/black, + DYE_WHITE = /obj/item/clothing/under/color/jumpskirt/white, + DYE_RAINBOW = /obj/item/clothing/under/color/jumpskirt/rainbow, + DYE_MIME = /obj/item/clothing/under/rank/civilian/mime/skirt, + DYE_CHAP = /obj/item/clothing/under/rank/civilian/chaplain/skirt, + DYE_QM = /obj/item/clothing/under/rank/cargo/qm/skirt, + DYE_CAPTAIN = /obj/item/clothing/under/rank/command/captain/skirt, + DYE_HOP = /obj/item/clothing/under/rank/command/head_of_personnel/skirt, + DYE_HOS = /obj/item/clothing/under/rank/security/head_of_security/skirt, + DYE_CE = /obj/item/clothing/under/rank/engineering/chief_engineer/skirt, + DYE_RD = /obj/item/clothing/under/rank/rnd/research_director/skirt, + DYE_CMO = /obj/item/clothing/under/rank/medical/chief_medical_officer/skirt, + ), + DYE_REGISTRY_GLOVES = list( + DYE_RED = /obj/item/clothing/gloves/color/red, + DYE_ORANGE = /obj/item/clothing/gloves/color/orange, + DYE_YELLOW = /obj/item/clothing/gloves/color/yellow, + DYE_GREEN = /obj/item/clothing/gloves/color/green, + DYE_BLUE = /obj/item/clothing/gloves/color/blue, + DYE_PURPLE = /obj/item/clothing/gloves/color/purple, + DYE_BLACK = /obj/item/clothing/gloves/color/black, + DYE_WHITE = /obj/item/clothing/gloves/color/white, + DYE_RAINBOW = /obj/item/clothing/gloves/color/rainbow, + DYE_MIME = /obj/item/clothing/gloves/color/white, + DYE_CLOWN = /obj/item/clothing/gloves/color/rainbow, + DYE_QM = /obj/item/clothing/gloves/color/brown, + DYE_CAPTAIN = /obj/item/clothing/gloves/color/captain, + DYE_FO = /obj/item/clothing/gloves/color/grey, + DYE_HOS = /obj/item/clothing/gloves/color/black, + DYE_CE = /obj/item/clothing/gloves/color/black, + DYE_RD = /obj/item/clothing/gloves/color/grey, + DYE_CMO = /obj/item/clothing/gloves/color/latex/nitrile, + DYE_REDCOAT = /obj/item/clothing/gloves/color/white, + DYE_SYNDICATE = /obj/item/clothing/gloves/combat, + DYE_CENTCOM = /obj/item/clothing/gloves/combat + ), + DYE_REGISTRY_SNEAKERS = list( + DYE_RED = /obj/item/clothing/shoes/sneakers/red, + DYE_ORANGE = /obj/item/clothing/shoes/sneakers/orange, + DYE_YELLOW = /obj/item/clothing/shoes/sneakers/yellow, + DYE_GREEN = /obj/item/clothing/shoes/sneakers/green, + DYE_BLUE = /obj/item/clothing/shoes/sneakers/blue, + DYE_PURPLE = /obj/item/clothing/shoes/sneakers/purple, + DYE_BLACK = /obj/item/clothing/shoes/sneakers/black, + DYE_WHITE = /obj/item/clothing/shoes/sneakers/white, + DYE_RAINBOW = /obj/item/clothing/shoes/sneakers/rainbow, + DYE_MIME = /obj/item/clothing/shoes/sneakers/black, + DYE_CLOWN = /obj/item/clothing/shoes/sneakers/rainbow, + DYE_QM = /obj/item/clothing/shoes/sneakers/brown, + DYE_CAPTAIN = /obj/item/clothing/shoes/sneakers/brown, + DYE_FO = /obj/item/clothing/shoes/sneakers/brown, + DYE_CE = /obj/item/clothing/shoes/sneakers/brown, + DYE_RD = /obj/item/clothing/shoes/sneakers/brown, + DYE_CMO = /obj/item/clothing/shoes/sneakers/brown, + DYE_SYNDICATE = /obj/item/clothing/shoes/combat, + DYE_CENTCOM = /obj/item/clothing/shoes/combat + ), + DYE_REGISTRY_FANNYPACK = list( + DYE_RED = /obj/item/storage/belt/fannypack/red, + DYE_ORANGE = /obj/item/storage/belt/fannypack/orange, + DYE_YELLOW = /obj/item/storage/belt/fannypack/yellow, + DYE_GREEN = /obj/item/storage/belt/fannypack/green, + DYE_BLUE = /obj/item/storage/belt/fannypack/blue, + DYE_PURPLE = /obj/item/storage/belt/fannypack/purple, + DYE_BLACK = /obj/item/storage/belt/fannypack/black, + DYE_WHITE = /obj/item/storage/belt/fannypack/white, + DYE_SYNDICATE = /obj/item/storage/belt/military + ), + DYE_REGISTRY_BEDSHEET = list( + DYE_RED = /obj/item/bedsheet/red, + DYE_ORANGE = /obj/item/bedsheet/orange, + DYE_YELLOW = /obj/item/bedsheet/yellow, + DYE_GREEN = /obj/item/bedsheet/green, + DYE_BLUE = /obj/item/bedsheet/blue, + DYE_PURPLE = /obj/item/bedsheet/purple, + DYE_BLACK = /obj/item/bedsheet/black, + DYE_WHITE = /obj/item/bedsheet, + DYE_RAINBOW = /obj/item/bedsheet/rainbow, + DYE_MIME = /obj/item/bedsheet/mime, + DYE_CLOWN = /obj/item/bedsheet/clown, + DYE_CHAP = /obj/item/bedsheet/chaplain, + DYE_QM = /obj/item/bedsheet/qm, + DYE_LAW = /obj/item/bedsheet/black, + DYE_CAPTAIN = /obj/item/bedsheet/captain, + DYE_FO = /obj/item/bedsheet/head_of_personnel, + DYE_HOS = /obj/item/bedsheet/hos, + DYE_CE = /obj/item/bedsheet/ce, + DYE_RD = /obj/item/bedsheet/rd, + DYE_CMO = /obj/item/bedsheet/cmo, + DYE_COSMIC = /obj/item/bedsheet/cosmos, + DYE_SYNDICATE = /obj/item/bedsheet/syndie, + DYE_CENTCOM = /obj/item/bedsheet/centcom + ), + DYE_LAWYER_SPECIAL = list( + DYE_COSMIC = /obj/item/clothing/under/rank/civilian/lawyer/galaxy, + DYE_SYNDICATE = /obj/item/clothing/under/rank/civilian/lawyer/galaxy/red + ) +)) + +/obj/machinery/washing_machine + name = "washing machine" + desc = "Gets rid of those pesky bloodstains, or your money back!" + icon = 'icons/obj/machines/washing_machine.dmi' + icon_state = "wm_1_0" + density = TRUE + state_open = TRUE + var/busy = FALSE + var/bloody_mess = 0 + var/obj/item/color_source + var/max_wash_capacity = 5 + +/obj/machinery/washing_machine/ComponentInitialize() + . = ..() + RegisterSignal(src, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_blood) + +/obj/machinery/washing_machine/examine(mob/user) + . = ..() + if(!busy) + . += "Alt-click it to start a wash cycle." + +/obj/machinery/washing_machine/AltClick(mob/user) + if(!user.canUseTopic(src, !issilicon(user))) + return + if(busy) + return + if(state_open) + to_chat(user, "Close the door first!") + return + if(bloody_mess) + to_chat(user, "[src] must be cleaned up first!") + return + busy = TRUE + update_icon() + addtimer(CALLBACK(src, .proc/wash_cycle), 200) + + START_PROCESSING(SSfastprocess, src) + +/obj/machinery/washing_machine/process() + if(!busy) + animate(src, transform=matrix(), time=2) + return PROCESS_KILL + if(anchored) + if(prob(5)) + var/matrix/M = new + M.Translate(rand(-1, 1), rand(0, 1)) + animate(src, transform=M, time=1) + animate(transform=matrix(), time=1) + else + if(prob(1)) + step(src, pick(GLOB.cardinals)) + var/matrix/M = new + M.Translate(rand(-3, 3), rand(-1, 3)) + animate(src, transform=M, time=2) + +/obj/machinery/washing_machine/proc/clean_blood() + if(!busy) + bloody_mess = FALSE + update_icon() + +/obj/machinery/washing_machine/proc/wash_cycle() + for(var/X in contents) + var/atom/movable/AM = X + SEND_SIGNAL(AM, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_STRENGTH_BLOOD) + AM.machine_wash(src) + + busy = FALSE + if(color_source) + qdel(color_source) + color_source = null + update_icon() + +/obj/item/proc/dye_item(dye_color, dye_key_override) + var/dye_key_selector = dye_key_override ? dye_key_override : dying_key + if(undyeable) + return FALSE + if(dye_key_selector) + if(!GLOB.dye_registry[dye_key_selector]) + log_runtime("Item just tried to be dyed with an invalid registry key: [dye_key_selector]") + return FALSE + var/obj/item/target_type = GLOB.dye_registry[dye_key_selector][dye_color] + if(target_type) + icon = initial(target_type.icon) + icon_state = initial(target_type.icon_state) + lefthand_file = initial(target_type.lefthand_file) + righthand_file = initial(target_type.righthand_file) + item_state = initial(target_type.item_state) + mob_overlay_icon = initial(target_type.mob_overlay_icon) + inhand_x_dimension = initial(target_type.inhand_x_dimension) + inhand_y_dimension = initial(target_type.inhand_y_dimension) + name = initial(target_type.name) + desc = "[initial(target_type.desc)] The colors look a little dodgy." + return target_type //successfully "appearance copy" dyed something; returns the target type as a hacky way of extending + add_atom_colour(dye_color, FIXED_COLOUR_PRIORITY) + return FALSE + +//what happens to this object when washed inside a washing machine +/atom/movable/proc/machine_wash(obj/machinery/washing_machine/WM) + return + +/obj/item/stack/sheet/hairlesshide/machine_wash(obj/machinery/washing_machine/WM) + new /obj/item/stack/sheet/wethide(drop_location(), amount) + qdel(src) + +/obj/item/clothing/suit/hooded/ian_costume/machine_wash(obj/machinery/washing_machine/WM) + new /obj/item/reagent_containers/food/snacks/meat/slab/corgi(loc) + qdel(src) + +/mob/living/simple_animal/pet/machine_wash(obj/machinery/washing_machine/WM) + WM.bloody_mess = TRUE + gib() + +/obj/item/machine_wash(obj/machinery/washing_machine/WM) + if(WM.color_source) + dye_item(WM.color_source.dye_color) + +/obj/item/clothing/under/dye_item(dye_color, dye_key) + . = ..() + if(.) + var/obj/item/clothing/under/U = . + can_adjust = initial(U.can_adjust) + if(!can_adjust && adjusted) //we deadjust the uniform if it's now unadjustable + toggle_jumpsuit_adjust() + +/obj/item/clothing/under/machine_wash(obj/machinery/washing_machine/WM) + freshly_laundered = TRUE + addtimer(VARSET_CALLBACK(src, freshly_laundered, FALSE), 5 MINUTES, TIMER_UNIQUE | TIMER_OVERRIDE) + ..() + +/obj/item/clothing/head/mob_holder/machine_wash(obj/machinery/washing_machine/WM) + ..() + held_mob.machine_wash(WM) + +/obj/item/clothing/shoes/sneakers/machine_wash(obj/machinery/washing_machine/WM) + if(chained) + chained = 0 + slowdown = SHOES_SLOWDOWN + new /obj/item/restraints/handcuffs(loc) + ..() + +/obj/machinery/washing_machine/relaymove(mob/user) + container_resist(user) + +/obj/machinery/washing_machine/container_resist(mob/living/user) + if(!busy) + add_fingerprint(user) + open_machine() + +/obj/machinery/washing_machine/update_icon_state() + if(busy) + icon_state = "wm_running_[bloody_mess]" + else if(bloody_mess) + icon_state = "wm_[state_open]_blood" + else + var/full = contents.len ? 1 : 0 + icon_state = "wm_[state_open]_[full]" + +/obj/machinery/washing_machine/update_overlays() + . = ..() + if(panel_open) + . += "wm_panel" + +/obj/machinery/washing_machine/attackby(obj/item/W, mob/user, params) + if(panel_open && !busy && default_unfasten_wrench(user, W)) + return + + if(default_deconstruction_screwdriver(user, null, null, W)) + update_icon() + return + + else if(user.a_intent != INTENT_HARM) + if (!state_open) + to_chat(user, "Open the door first!") + return TRUE + + if(bloody_mess) + to_chat(user, "[src] must be cleaned up first!") + return TRUE + + if(contents.len >= max_wash_capacity) + to_chat(user, "The washing machine is full!") + return TRUE + + if(!user.transferItemToLoc(W, src)) + to_chat(user, "\The [W] is stuck to your hand, you cannot put it in the washing machine!") + return TRUE + if(W.dye_color) + color_source = W + update_icon() + + else + return ..() + +/obj/machinery/washing_machine/attack_hand(mob/user) + . = ..() + if(.) + return + if(busy) + to_chat(user, "[src] is busy!") + return + + if(user.pulling && user.a_intent == INTENT_GRAB && isliving(user.pulling)) + var/mob/living/L = user.pulling + if(L.buckled || L.has_buckled_mobs()) + return + if(state_open) + if(istype(L, /mob/living/simple_animal/pet)) + L.forceMove(src) + update_icon() + return + + if(!state_open) + open_machine() + else + state_open = FALSE //close the door + update_icon() + +/obj/machinery/washing_machine/deconstruct(disassembled = TRUE) + new /obj/item/stack/sheet/metal(drop_location(), 2) + qdel(src) + +/obj/machinery/washing_machine/open_machine(drop = 1) + ..() + density = TRUE //because machinery/open_machine() sets it to 0 + color_source = null diff --git a/code/game/machinery/wishgranter.dm b/code/game/machinery/wishgranter.dm index a16db8db30ff..d79d55541049 100644 --- a/code/game/machinery/wishgranter.dm +++ b/code/game/machinery/wishgranter.dm @@ -1,43 +1,43 @@ -/obj/machinery/wish_granter - name = "wish granter" - desc = "You're not so sure about this, anymore..." - icon = 'icons/obj/device.dmi' - icon_state = "syndbeacon" - - use_power = NO_POWER_USE - density = TRUE - - var/charges = 1 - var/insisting = 0 - -/obj/machinery/wish_granter/attack_hand(mob/living/carbon/user) - . = ..() - if(.) - return - if(charges <= 0) - to_chat(user, "The Wish Granter lies silent.") - return - - else if(!ishuman(user)) - to_chat(user, "You feel a dark stirring inside of the Wish Granter, something you want nothing of. Your instincts are better than any man's.") - return - - else if(is_special_character(user)) - to_chat(user, "Even to a heart as dark as yours, you know nothing good will come of this. Something instinctual makes you pull away.") - - else if (!insisting) - to_chat(user, "Your first touch makes the Wish Granter stir, listening to you. Are you really sure you want to do this?") - insisting++ - - else - to_chat(user, "You speak. [pick("I want the station to disappear","Humanity is corrupt, mankind must be destroyed","I want to be rich", "I want to rule the world","I want immortality.")]. The Wish Granter answers.") - to_chat(user, "Your head pounds for a moment, before your vision clears. You are the avatar of the Wish Granter, and your power is LIMITLESS! And it's all yours. You need to make sure no one can take it from you. No one can know, first.") - - charges-- - insisting = 0 - - user.mind.add_antag_datum(/datum/antagonist/wishgranter) - - to_chat(user, "You have a very bad feeling about this.") - - return +/obj/machinery/wish_granter + name = "wish granter" + desc = "You're not so sure about this, anymore..." + icon = 'icons/obj/device.dmi' + icon_state = "syndbeacon" + + use_power = NO_POWER_USE + density = TRUE + + var/charges = 1 + var/insisting = 0 + +/obj/machinery/wish_granter/attack_hand(mob/living/carbon/user) + . = ..() + if(.) + return + if(charges <= 0) + to_chat(user, "The Wish Granter lies silent.") + return + + else if(!ishuman(user)) + to_chat(user, "You feel a dark stirring inside of the Wish Granter, something you want nothing of. Your instincts are better than any man's.") + return + + else if(is_special_character(user)) + to_chat(user, "Even to a heart as dark as yours, you know nothing good will come of this. Something instinctual makes you pull away.") + + else if (!insisting) + to_chat(user, "Your first touch makes the Wish Granter stir, listening to you. Are you really sure you want to do this?") + insisting++ + + else + to_chat(user, "You speak. [pick("I want the station to disappear","Humanity is corrupt, mankind must be destroyed","I want to be rich", "I want to rule the world","I want immortality.")]. The Wish Granter answers.") + to_chat(user, "Your head pounds for a moment, before your vision clears. You are the avatar of the Wish Granter, and your power is LIMITLESS! And it's all yours. You need to make sure no one can take it from you. No one can know, first.") + + charges-- + insisting = 0 + + user.mind.add_antag_datum(/datum/antagonist/wishgranter) + + to_chat(user, "You have a very bad feeling about this.") + + return diff --git a/code/game/mecha/combat/combat.dm b/code/game/mecha/combat/combat.dm index 0834f28d0088..2e26096eacf1 100644 --- a/code/game/mecha/combat/combat.dm +++ b/code/game/mecha/combat/combat.dm @@ -1,18 +1,18 @@ -/obj/mecha/combat - force = 30 - internals_req_access = list(ACCESS_MECH_SCIENCE, ACCESS_MECH_SECURITY) - internal_damage_threshold = 50 - armor = list("melee" = 30, "bullet" = 30, "laser" = 15, "energy" = 20, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - mouse_pointer = 'icons/effects/mouse_pointers/mecha_mouse.dmi' - destruction_sleep_duration = 40 - exit_delay = 40 - -/obj/mecha/combat/restore_equipment() - mouse_pointer = 'icons/effects/mouse_pointers/mecha_mouse.dmi' - . = ..() - -/obj/mecha/combat/proc/max_ammo() //Max the ammo stored for Nuke Ops mechs, or anyone else that calls this - for(var/obj/item/I in equipment) - if(istype(I, /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/)) - var/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/gun = I - gun.projectiles_cache = gun.projectiles_cache_max +/obj/mecha/combat + force = 30 + internals_req_access = list(ACCESS_MECH_SCIENCE, ACCESS_MECH_SECURITY) + internal_damage_threshold = 50 + armor = list("melee" = 30, "bullet" = 30, "laser" = 15, "energy" = 20, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + mouse_pointer = 'icons/effects/mouse_pointers/mecha_mouse.dmi' + destruction_sleep_duration = 40 + exit_delay = 40 + +/obj/mecha/combat/restore_equipment() + mouse_pointer = 'icons/effects/mouse_pointers/mecha_mouse.dmi' + . = ..() + +/obj/mecha/combat/proc/max_ammo() //Max the ammo stored for Nuke Ops mechs, or anyone else that calls this + for(var/obj/item/I in equipment) + if(istype(I, /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/)) + var/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/gun = I + gun.projectiles_cache = gun.projectiles_cache_max diff --git a/code/game/mecha/combat/durand.dm b/code/game/mecha/combat/durand.dm index 07369c90998d..efafa478429c 100644 --- a/code/game/mecha/combat/durand.dm +++ b/code/game/mecha/combat/durand.dm @@ -1,210 +1,210 @@ -/obj/mecha/combat/durand - desc = "An aging combat exosuit utilized by the Nanotrasen corporation. Originally developed to combat hostile alien lifeforms." - name = "\improper Durand" - icon_state = "durand" - step_in = 4 - dir_in = 1 //Facing North. - max_integrity = 400 - deflect_chance = 20 - armor = list("melee" = 40, "bullet" = 35, "laser" = 15, "energy" = 10, "bomb" = 20, "bio" = 0, "rad" = 50, "fire" = 100, "acid" = 100) - max_temperature = 30000 - infra_luminosity = 8 - force = 40 - wreckage = /obj/structure/mecha_wreckage/durand - var/obj/durand_shield/shield - -/obj/mecha/combat/durand/Initialize() - shield = new/obj/durand_shield - shield.chassis = src - shield.layer = layer - RegisterSignal(src, COMSIG_MECHA_ACTION_ACTIVATE, .proc/relay) - RegisterSignal(src, COMSIG_PROJECTILE_PREHIT, .proc/prehit) - . = ..() - -/obj/mecha/combat/durand/Destroy() - if(shield) - qdel(shield) - . = ..() - -/obj/mecha/combat/durand/GrantActions(mob/living/user, human_occupant = 0) - ..() - defense_action.Grant(user, src) - -/obj/mecha/combat/durand/RemoveActions(mob/living/user, human_occupant = 0) - ..() - defense_action.Remove(user) - -/obj/mecha/combat/durand/process() - . = ..() - if(defense_mode && !use_power(100)) - defense_action.Activate(forced_state = TRUE) - -/obj/mecha/combat/durand/domove(direction) - . = ..() - if(shield) - shield.forceMove(loc) - shield.dir = dir - -/obj/mecha/combat/durand/forceMove(var/turf/T) - . = ..() - shield.forceMove(T) - -/obj/mecha/combat/durand/go_out(forced, atom/newloc = loc) - if(defense_mode) - defense_action.Activate(forced_state = TRUE) - . = ..() - -///Relays the signal from the action button to the shield, and creates a new shield if the old one is MIA. -/obj/mecha/combat/durand/proc/relay(datum/source, list/signal_args) - if(!shield) //if the shield somehow got deleted - shield = new/obj/durand_shield - shield.chassis = src - shield.layer = layer - shield.forceMove(loc) - shield.dir = dir - SEND_SIGNAL(shield, COMSIG_MECHA_ACTION_ACTIVATE, source, signal_args) - -//Redirects projectiles to the shield if defense_check decides they should be blocked and returns true. -/obj/mecha/combat/durand/proc/prehit(obj/projectile/source, list/signal_args) - if(defense_check(source.loc) && shield) - signal_args[2] = shield - - -/**Checks if defense mode is enabled, and if the attacker is standing in an area covered by the shield. -Expects a turf. Returns true if the attack should be blocked, false if not.*/ -/obj/mecha/combat/durand/proc/defense_check(var/turf/aloc) - if (!defense_mode || !shield || shield.switching) - return FALSE - . = FALSE - switch(dir) - if (1) - if(abs(x - aloc.x) <= (y - aloc.y) * -2) - . = TRUE - if (2) - if(abs(x - aloc.x) <= (y - aloc.y) * 2) - . = TRUE - if (4) - if(abs(y - aloc.y) <= (x - aloc.x) * -2) - . = TRUE - if (8) - if(abs(y - aloc.y) <= (x - aloc.x) * 2) - . = TRUE - return - -obj/mecha/combat/durand/attack_generic(mob/user, damage_amount = 0, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, armor_penetration = 0) - if(defense_check(user.loc)) - log_message("Attack absorbed by defense field. Attacker - [user].", LOG_MECHA, color="orange") - shield.attack_generic(user, damage_amount, damage_type, damage_flag, sound_effect, armor_penetration) - else - . = ..() - -/obj/mecha/combat/durand/blob_act(obj/structure/blob/B) - if(defense_check(B.loc)) - log_message("Attack by blob. Attacker - [B].", LOG_MECHA, color="red") - log_message("Attack absorbed by defense field.", LOG_MECHA, color="orange") - shield.blob_act(B) - else - . = ..() - -/obj/mecha/combat/durand/attackby(obj/item/W as obj, mob/user as mob, params) - if(defense_check(user.loc)) - log_message("Attack absorbed by defense field. Attacker - [user], with [W]", LOG_MECHA, color="orange") - shield.attackby(W, user, params) - else - . = ..() - -/obj/mecha/combat/durand/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) - if(defense_check(AM.loc)) - log_message("Impact with [AM] absorbed by defense field.", LOG_MECHA, color="orange") - shield.hitby(AM, skipcatch, hitpush, blocked, throwingdatum) - else - . = ..() - -//////////////////////////// -///// Shield processing //// -//////////////////////////// - -/**An object to take the hit for us when using the Durand's defense mode. -It is spawned in during the durand's initilization, and always stays on the same tile. -Normally invisible, until defense mode is actvated. When the durand detects an attack that should be blocked, the -attack is passed to the shield. The shield takes the damage, uses it to calculate charge cost, and then sets its -own integrity back to max. Shield is automatically dropped if we run out of power or the user gets out.*/ - -/obj/durand_shield //projectiles get passed to this when defense mode is enabled - name = "defense grid" - icon = 'icons/mecha/durand_shield.dmi' - icon_state = "shield_null" - invisibility = INVISIBILITY_MAXIMUM //no showing on right-click - pixel_y = 4 - max_integrity = 10000 - obj_integrity = 10000 - anchored = TRUE - var/obj/mecha/combat/durand/chassis ///Our link back to the durand - var/switching = FALSE ///To keep track of things during the animation - -/obj/durand_shield/Initialize() - . = ..() - RegisterSignal(src, COMSIG_MECHA_ACTION_ACTIVATE, .proc/activate) - -/obj/durand_shield/Destroy() - if(chassis) - chassis.shield = null - . = ..() - -/**Handles activating and deactivating the shield. This proc is called by a signal sent from the mech's action button -and relayed by the mech itself. The "forced" variabe, signal_args[1], will skip the to-pilot text and is meant for when -the shield is disabled by means other than the action button (like running out of power)*/ - -/obj/durand_shield/proc/activate(datum/source, datum/action/innate/mecha/mech_defense_mode/button, list/signal_args) - if(!chassis || !chassis.occupant) - return - if(switching && !signal_args[1]) - return - if(!chassis.defense_mode && (!chassis.cell || chassis.cell.charge < 100)) //If it's off, and we have less than 100 units of power - chassis.occupant_message("Insufficient power; cannot activate defense mode.") - return - switching = TRUE - chassis.defense_mode = !chassis.defense_mode - chassis.defense_action.button_icon_state = "mech_defense_mode_[chassis.defense_mode ? "on" : "off"]" //This is backwards because we haven't changed the var yet - if(!signal_args[1]) - chassis.occupant_message("Defense mode [chassis.defense_mode?"enabled":"disabled"].") - chassis.log_message("User has toggled defense mode -- now [chassis.defense_mode?"enabled":"disabled"].", LOG_MECHA) - else - chassis.log_message("defense mode state changed -- now [chassis.defense_mode?"enabled":"disabled"].", LOG_MECHA) - chassis.defense_action.UpdateButtonIcon() - - if(chassis.defense_mode) - invisibility = 0 - flick("shield_raise", src) - playsound(src, 'sound/mecha/mech_shield_raise.ogg', 50, FALSE) - set_light(l_range = MINIMUM_USEFUL_LIGHT_RANGE , l_power = 5, l_color = "#00FFFF") - sleep(3) - icon_state = "shield" - else - flick("shield_drop", src) - playsound(src, 'sound/mecha/mech_shield_drop.ogg', 50, FALSE) - sleep(5) - set_light(0) - icon_state = "shield_null" - invisibility = INVISIBILITY_MAXIMUM //no showing on right-click - switching = FALSE - -/obj/durand_shield/take_damage() - if(!chassis) - qdel(src) - return - if(!chassis.defense_mode) //if defense mode is disabled, we're taking damage that we shouldn't be taking - return - . = ..() - flick("shield_impact", src) - if(!chassis.use_power((max_integrity - obj_integrity) * 100)) - chassis.cell?.charge = 0 - chassis.defense_action.Activate(forced_state = TRUE) - obj_integrity = 10000 - -/obj/durand_shield/play_attack_sound() - playsound(src, 'sound/mecha/mech_shield_deflect.ogg', 100, TRUE) - -/obj/durand_shield/bullet_act() - play_attack_sound() - . = ..() +/obj/mecha/combat/durand + desc = "An aging combat exosuit utilized by the Nanotrasen corporation. Originally developed to combat hostile alien lifeforms." + name = "\improper Durand" + icon_state = "durand" + step_in = 4 + dir_in = 1 //Facing North. + max_integrity = 400 + deflect_chance = 20 + armor = list("melee" = 40, "bullet" = 35, "laser" = 15, "energy" = 10, "bomb" = 20, "bio" = 0, "rad" = 50, "fire" = 100, "acid" = 100) + max_temperature = 30000 + infra_luminosity = 8 + force = 40 + wreckage = /obj/structure/mecha_wreckage/durand + var/obj/durand_shield/shield + +/obj/mecha/combat/durand/Initialize() + shield = new/obj/durand_shield + shield.chassis = src + shield.layer = layer + RegisterSignal(src, COMSIG_MECHA_ACTION_ACTIVATE, .proc/relay) + RegisterSignal(src, COMSIG_PROJECTILE_PREHIT, .proc/prehit) + . = ..() + +/obj/mecha/combat/durand/Destroy() + if(shield) + qdel(shield) + . = ..() + +/obj/mecha/combat/durand/GrantActions(mob/living/user, human_occupant = 0) + ..() + defense_action.Grant(user, src) + +/obj/mecha/combat/durand/RemoveActions(mob/living/user, human_occupant = 0) + ..() + defense_action.Remove(user) + +/obj/mecha/combat/durand/process() + . = ..() + if(defense_mode && !use_power(100)) + defense_action.Activate(forced_state = TRUE) + +/obj/mecha/combat/durand/domove(direction) + . = ..() + if(shield) + shield.forceMove(loc) + shield.dir = dir + +/obj/mecha/combat/durand/forceMove(var/turf/T) + . = ..() + shield.forceMove(T) + +/obj/mecha/combat/durand/go_out(forced, atom/newloc = loc) + if(defense_mode) + defense_action.Activate(forced_state = TRUE) + . = ..() + +///Relays the signal from the action button to the shield, and creates a new shield if the old one is MIA. +/obj/mecha/combat/durand/proc/relay(datum/source, list/signal_args) + if(!shield) //if the shield somehow got deleted + shield = new/obj/durand_shield + shield.chassis = src + shield.layer = layer + shield.forceMove(loc) + shield.dir = dir + SEND_SIGNAL(shield, COMSIG_MECHA_ACTION_ACTIVATE, source, signal_args) + +//Redirects projectiles to the shield if defense_check decides they should be blocked and returns true. +/obj/mecha/combat/durand/proc/prehit(obj/projectile/source, list/signal_args) + if(defense_check(source.loc) && shield) + signal_args[2] = shield + + +/**Checks if defense mode is enabled, and if the attacker is standing in an area covered by the shield. +Expects a turf. Returns true if the attack should be blocked, false if not.*/ +/obj/mecha/combat/durand/proc/defense_check(var/turf/aloc) + if (!defense_mode || !shield || shield.switching) + return FALSE + . = FALSE + switch(dir) + if (1) + if(abs(x - aloc.x) <= (y - aloc.y) * -2) + . = TRUE + if (2) + if(abs(x - aloc.x) <= (y - aloc.y) * 2) + . = TRUE + if (4) + if(abs(y - aloc.y) <= (x - aloc.x) * -2) + . = TRUE + if (8) + if(abs(y - aloc.y) <= (x - aloc.x) * 2) + . = TRUE + return + +obj/mecha/combat/durand/attack_generic(mob/user, damage_amount = 0, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, armor_penetration = 0) + if(defense_check(user.loc)) + log_message("Attack absorbed by defense field. Attacker - [user].", LOG_MECHA, color="orange") + shield.attack_generic(user, damage_amount, damage_type, damage_flag, sound_effect, armor_penetration) + else + . = ..() + +/obj/mecha/combat/durand/blob_act(obj/structure/blob/B) + if(defense_check(B.loc)) + log_message("Attack by blob. Attacker - [B].", LOG_MECHA, color="red") + log_message("Attack absorbed by defense field.", LOG_MECHA, color="orange") + shield.blob_act(B) + else + . = ..() + +/obj/mecha/combat/durand/attackby(obj/item/W as obj, mob/user as mob, params) + if(defense_check(user.loc)) + log_message("Attack absorbed by defense field. Attacker - [user], with [W]", LOG_MECHA, color="orange") + shield.attackby(W, user, params) + else + . = ..() + +/obj/mecha/combat/durand/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) + if(defense_check(AM.loc)) + log_message("Impact with [AM] absorbed by defense field.", LOG_MECHA, color="orange") + shield.hitby(AM, skipcatch, hitpush, blocked, throwingdatum) + else + . = ..() + +//////////////////////////// +///// Shield processing //// +//////////////////////////// + +/**An object to take the hit for us when using the Durand's defense mode. +It is spawned in during the durand's initilization, and always stays on the same tile. +Normally invisible, until defense mode is actvated. When the durand detects an attack that should be blocked, the +attack is passed to the shield. The shield takes the damage, uses it to calculate charge cost, and then sets its +own integrity back to max. Shield is automatically dropped if we run out of power or the user gets out.*/ + +/obj/durand_shield //projectiles get passed to this when defense mode is enabled + name = "defense grid" + icon = 'icons/mecha/durand_shield.dmi' + icon_state = "shield_null" + invisibility = INVISIBILITY_MAXIMUM //no showing on right-click + pixel_y = 4 + max_integrity = 10000 + obj_integrity = 10000 + anchored = TRUE + var/obj/mecha/combat/durand/chassis ///Our link back to the durand + var/switching = FALSE ///To keep track of things during the animation + +/obj/durand_shield/Initialize() + . = ..() + RegisterSignal(src, COMSIG_MECHA_ACTION_ACTIVATE, .proc/activate) + +/obj/durand_shield/Destroy() + if(chassis) + chassis.shield = null + . = ..() + +/**Handles activating and deactivating the shield. This proc is called by a signal sent from the mech's action button +and relayed by the mech itself. The "forced" variabe, signal_args[1], will skip the to-pilot text and is meant for when +the shield is disabled by means other than the action button (like running out of power)*/ + +/obj/durand_shield/proc/activate(datum/source, datum/action/innate/mecha/mech_defense_mode/button, list/signal_args) + if(!chassis || !chassis.occupant) + return + if(switching && !signal_args[1]) + return + if(!chassis.defense_mode && (!chassis.cell || chassis.cell.charge < 100)) //If it's off, and we have less than 100 units of power + chassis.occupant_message("Insufficient power; cannot activate defense mode.") + return + switching = TRUE + chassis.defense_mode = !chassis.defense_mode + chassis.defense_action.button_icon_state = "mech_defense_mode_[chassis.defense_mode ? "on" : "off"]" //This is backwards because we haven't changed the var yet + if(!signal_args[1]) + chassis.occupant_message("Defense mode [chassis.defense_mode?"enabled":"disabled"].") + chassis.log_message("User has toggled defense mode -- now [chassis.defense_mode?"enabled":"disabled"].", LOG_MECHA) + else + chassis.log_message("defense mode state changed -- now [chassis.defense_mode?"enabled":"disabled"].", LOG_MECHA) + chassis.defense_action.UpdateButtonIcon() + + if(chassis.defense_mode) + invisibility = 0 + flick("shield_raise", src) + playsound(src, 'sound/mecha/mech_shield_raise.ogg', 50, FALSE) + set_light(l_range = MINIMUM_USEFUL_LIGHT_RANGE , l_power = 5, l_color = "#00FFFF") + sleep(3) + icon_state = "shield" + else + flick("shield_drop", src) + playsound(src, 'sound/mecha/mech_shield_drop.ogg', 50, FALSE) + sleep(5) + set_light(0) + icon_state = "shield_null" + invisibility = INVISIBILITY_MAXIMUM //no showing on right-click + switching = FALSE + +/obj/durand_shield/take_damage() + if(!chassis) + qdel(src) + return + if(!chassis.defense_mode) //if defense mode is disabled, we're taking damage that we shouldn't be taking + return + . = ..() + flick("shield_impact", src) + if(!chassis.use_power((max_integrity - obj_integrity) * 100)) + chassis.cell?.charge = 0 + chassis.defense_action.Activate(forced_state = TRUE) + obj_integrity = 10000 + +/obj/durand_shield/play_attack_sound() + playsound(src, 'sound/mecha/mech_shield_deflect.ogg', 100, TRUE) + +/obj/durand_shield/bullet_act() + play_attack_sound() + . = ..() diff --git a/code/game/mecha/combat/gygax.dm b/code/game/mecha/combat/gygax.dm index 24197a34f45f..5774716fca71 100644 --- a/code/game/mecha/combat/gygax.dm +++ b/code/game/mecha/combat/gygax.dm @@ -1,69 +1,69 @@ -/obj/mecha/combat/gygax - desc = "A lightweight, security exosuit. Popular among private and corporate security." - name = "\improper Gygax" - icon_state = "gygax" - step_in = 3 - dir_in = 1 //Facing North. - max_integrity = 250 - deflect_chance = 5 - armor = list("melee" = 25, "bullet" = 20, "laser" = 30, "energy" = 15, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - max_temperature = 25000 - leg_overload_coeff = 80 - infra_luminosity = 6 - force = 25 - wreckage = /obj/structure/mecha_wreckage/gygax - internal_damage_threshold = 35 - max_equip = 3 - step_energy_drain = 3 - -/obj/mecha/combat/gygax/mechturn(direction) - . = ..() - if(!strafe && !occupant.client.keys_held["Alt"]) - mechstep(direction) //agile mechs get to move and turn in the same step - -/obj/mecha/combat/gygax/dark - desc = "A lightweight exosuit, painted in a dark scheme. This model appears to have some modifications." - name = "\improper Dark Gygax" - icon_state = "darkgygax" - max_integrity = 300 - deflect_chance = 20 - armor = list("melee" = 40, "bullet" = 40, "laser" = 50, "energy" = 35, "bomb" = 20, "bio" = 0, "rad" =20, "fire" = 100, "acid" = 100) - max_temperature = 35000 - leg_overload_coeff = 70 - force = 30 - operation_req_access = list(ACCESS_SYNDICATE) - internals_req_access = list(ACCESS_SYNDICATE) - wreckage = /obj/structure/mecha_wreckage/gygax/dark - max_equip = 5 - destruction_sleep_duration = 20 - -/obj/mecha/combat/gygax/dark/loaded/Initialize() - . = ..() - var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/thrusters/ion(src) - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/scattershot - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/anticcw_armor_booster - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/antiproj_armor_booster - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/tesla_energy_relay - ME.attach(src) - max_ammo() - -/obj/mecha/combat/gygax/dark/add_cell(obj/item/stock_parts/cell/C=null) - if(C) - C.forceMove(src) - cell = C - return - cell = new /obj/item/stock_parts/cell/bluespace(src) - - -/obj/mecha/combat/gygax/GrantActions(mob/living/user, human_occupant = 0) - ..() - overload_action.Grant(user, src) - - -/obj/mecha/combat/gygax/RemoveActions(mob/living/user, human_occupant = 0) - ..() - overload_action.Remove(user) +/obj/mecha/combat/gygax + desc = "A lightweight, security exosuit. Popular among private and corporate security." + name = "\improper Gygax" + icon_state = "gygax" + step_in = 3 + dir_in = 1 //Facing North. + max_integrity = 250 + deflect_chance = 5 + armor = list("melee" = 25, "bullet" = 20, "laser" = 30, "energy" = 15, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + max_temperature = 25000 + leg_overload_coeff = 80 + infra_luminosity = 6 + force = 25 + wreckage = /obj/structure/mecha_wreckage/gygax + internal_damage_threshold = 35 + max_equip = 3 + step_energy_drain = 3 + +/obj/mecha/combat/gygax/mechturn(direction) + . = ..() + if(!strafe && !occupant.client.keys_held["Alt"]) + mechstep(direction) //agile mechs get to move and turn in the same step + +/obj/mecha/combat/gygax/dark + desc = "A lightweight exosuit, painted in a dark scheme. This model appears to have some modifications." + name = "\improper Dark Gygax" + icon_state = "darkgygax" + max_integrity = 300 + deflect_chance = 20 + armor = list("melee" = 40, "bullet" = 40, "laser" = 50, "energy" = 35, "bomb" = 20, "bio" = 0, "rad" =20, "fire" = 100, "acid" = 100) + max_temperature = 35000 + leg_overload_coeff = 70 + force = 30 + operation_req_access = list(ACCESS_SYNDICATE) + internals_req_access = list(ACCESS_SYNDICATE) + wreckage = /obj/structure/mecha_wreckage/gygax/dark + max_equip = 5 + destruction_sleep_duration = 20 + +/obj/mecha/combat/gygax/dark/loaded/Initialize() + . = ..() + var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/thrusters/ion(src) + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/scattershot + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/anticcw_armor_booster + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/antiproj_armor_booster + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/tesla_energy_relay + ME.attach(src) + max_ammo() + +/obj/mecha/combat/gygax/dark/add_cell(obj/item/stock_parts/cell/C=null) + if(C) + C.forceMove(src) + cell = C + return + cell = new /obj/item/stock_parts/cell/bluespace(src) + + +/obj/mecha/combat/gygax/GrantActions(mob/living/user, human_occupant = 0) + ..() + overload_action.Grant(user, src) + + +/obj/mecha/combat/gygax/RemoveActions(mob/living/user, human_occupant = 0) + ..() + overload_action.Remove(user) diff --git a/code/game/mecha/combat/honker.dm b/code/game/mecha/combat/honker.dm index 3e2396d398f1..05d0445fa52a 100644 --- a/code/game/mecha/combat/honker.dm +++ b/code/game/mecha/combat/honker.dm @@ -1,168 +1,168 @@ -/obj/mecha/combat/honker - desc = "Produced by \"Tyranny of Honk, INC\", this exosuit is designed as heavy clown-support. Used to spread the fun and joy of life. HONK!" - name = "\improper H.O.N.K" - icon_state = "honker" - step_in = 3 - max_integrity = 140 - deflect_chance = 60 - internal_damage_threshold = 60 - armor = list("melee" = -20, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - max_temperature = 25000 - infra_luminosity = 5 - operation_req_access = list(ACCESS_THEATRE) - internals_req_access = list(ACCESS_MECH_SCIENCE, ACCESS_THEATRE) - wreckage = /obj/structure/mecha_wreckage/honker - add_req_access = 0 - max_equip = 3 - var/squeak = TRUE - -/obj/mecha/combat/honker/get_stats_part() - var/integrity = obj_integrity/max_integrity*100 - var/cell_charge = get_charge() - var/datum/gas_mixture/int_tank_air = internal_tank.return_air() - var/tank_pressure = internal_tank ? round(int_tank_air.return_pressure(),0.01) : "None" - var/tank_temperature = internal_tank ? int_tank_air.return_temperature() : "Unknown" - var/cabin_pressure = round(return_pressure(),0.01) - var/output = {"[report_internal_damage()] - [integrity<30?"DAMAGE LEVEL CRITICAL
                    ":null] - [internal_damage&MECHA_INT_TEMP_CONTROL?"CLOWN SUPPORT SYSTEM MALFUNCTION
                    ":null] - [internal_damage&MECHA_INT_TANK_BREACH?"GAS TANK HONK
                    ":null] - [internal_damage&MECHA_INT_CONTROL_LOST?"HONK-A-DOODLE - Recalibrate
                    ":null] - IntegriHONK: [integrity]%
                    - PowerHONK charge: [isnull(cell_charge)?"No powercell installed":"[cell.percent()]%"]
                    - Air source: [use_internal_tank?"Internal Airtank":"Environment"]
                    - AirHONK pressure: [tank_pressure]kPa
                    - AirHONK temperature: [tank_temperature]°K|[tank_temperature - T0C]°C
                    - HONK pressure: [cabin_pressure>WARNING_HIGH_PRESSURE ? "[cabin_pressure]": cabin_pressure]kPa
                    - HONK temperature: [return_temperature()]°K|[return_temperature() - T0C]°C
                    - Lights: [lights?"on":"off"]
                    - [dna_lock?"DNA-locked:
                    [dna_lock] \[Reset\]
                    ":null] - "} - return output - -/obj/mecha/combat/honker/get_stats_html() - var/output = {" - - - [src.name] data - - - - -
                    - [src.get_stats_part()] -
                    -
                    - [src.get_equipment_list()] -
                    -
                    -
                    - [src.get_commands()] -
                    - - - "} - return output - -/obj/mecha/combat/honker/get_commands() - var/output = {" - "} - output += ..() - return output - - -/obj/mecha/combat/honker/get_equipment_list() - if(!equipment.len) - return - var/output = "Honk-ON-Systems:
                    " - for(var/obj/item/mecha_parts/mecha_equipment/MT in equipment) - output += "
                    [MT.get_equip_info()]
                    " - output += "
                    " - return output - -/obj/mecha/combat/honker/play_stepsound() - if(squeak) - playsound(src, "clownstep", 70, 1) - squeak = !squeak - -/obj/mecha/combat/honker/Topic(href, href_list) - ..() - if (href_list["play_sound"]) - switch(href_list["play_sound"]) - if("sadtrombone") - playsound(src, 'sound/misc/sadtrombone.ogg', 50) - if("bikehorn") - playsound(src, 'sound/items/bikehorn.ogg', 50) - if("airhorn2") - playsound(src, 'sound/items/airhorn2.ogg', 40) //soundfile has higher than average volume - if("carhorn") - playsound(src, 'sound/items/carhorn.ogg', 80) //soundfile has lower than average volume - if("party_horn") - playsound(src, 'sound/items/party_horn.ogg', 50) - if("reee") - playsound(src, 'sound/effects/reee.ogg', 50) - if("weeoo1") - playsound(src, 'sound/items/weeoo1.ogg', 50) - if("hiss1") - playsound(src, 'sound/voice/hiss1.ogg', 50) - if("armbomb") - playsound(src, 'sound/weapons/armbomb.ogg', 50) - if("saberon") - playsound(src, 'sound/weapons/saberon.ogg', 50) - if("airlock_alien_prying") - playsound(src, 'sound/machines/airlock_alien_prying.ogg', 50) - if("lightningbolt") - playsound(src, 'sound/magic/lightningbolt.ogg', 50) - if("explosionfar") - playsound(src, 'sound/effects/explosionfar.ogg', 50) - return +/obj/mecha/combat/honker + desc = "Produced by \"Tyranny of Honk, INC\", this exosuit is designed as heavy clown-support. Used to spread the fun and joy of life. HONK!" + name = "\improper H.O.N.K" + icon_state = "honker" + step_in = 3 + max_integrity = 140 + deflect_chance = 60 + internal_damage_threshold = 60 + armor = list("melee" = -20, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + max_temperature = 25000 + infra_luminosity = 5 + operation_req_access = list(ACCESS_THEATRE) + internals_req_access = list(ACCESS_MECH_SCIENCE, ACCESS_THEATRE) + wreckage = /obj/structure/mecha_wreckage/honker + add_req_access = 0 + max_equip = 3 + var/squeak = TRUE + +/obj/mecha/combat/honker/get_stats_part() + var/integrity = obj_integrity/max_integrity*100 + var/cell_charge = get_charge() + var/datum/gas_mixture/int_tank_air = internal_tank.return_air() + var/tank_pressure = internal_tank ? round(int_tank_air.return_pressure(),0.01) : "None" + var/tank_temperature = internal_tank ? int_tank_air.return_temperature() : "Unknown" + var/cabin_pressure = round(return_pressure(),0.01) + var/output = {"[report_internal_damage()] + [integrity<30?"DAMAGE LEVEL CRITICAL
                    ":null] + [internal_damage&MECHA_INT_TEMP_CONTROL?"CLOWN SUPPORT SYSTEM MALFUNCTION
                    ":null] + [internal_damage&MECHA_INT_TANK_BREACH?"GAS TANK HONK
                    ":null] + [internal_damage&MECHA_INT_CONTROL_LOST?"HONK-A-DOODLE - Recalibrate
                    ":null] + IntegriHONK: [integrity]%
                    + PowerHONK charge: [isnull(cell_charge)?"No powercell installed":"[cell.percent()]%"]
                    + Air source: [use_internal_tank?"Internal Airtank":"Environment"]
                    + AirHONK pressure: [tank_pressure]kPa
                    + AirHONK temperature: [tank_temperature]°K|[tank_temperature - T0C]°C
                    + HONK pressure: [cabin_pressure>WARNING_HIGH_PRESSURE ? "[cabin_pressure]": cabin_pressure]kPa
                    + HONK temperature: [return_temperature()]°K|[return_temperature() - T0C]°C
                    + Lights: [lights?"on":"off"]
                    + [dna_lock?"DNA-locked:
                    [dna_lock] \[Reset\]
                    ":null] + "} + return output + +/obj/mecha/combat/honker/get_stats_html() + var/output = {" + + + [src.name] data + + + + +
                    + [src.get_stats_part()] +
                    +
                    + [src.get_equipment_list()] +
                    +
                    +
                    + [src.get_commands()] +
                    + + + "} + return output + +/obj/mecha/combat/honker/get_commands() + var/output = {" + "} + output += ..() + return output + + +/obj/mecha/combat/honker/get_equipment_list() + if(!equipment.len) + return + var/output = "Honk-ON-Systems:
                    " + for(var/obj/item/mecha_parts/mecha_equipment/MT in equipment) + output += "
                    [MT.get_equip_info()]
                    " + output += "
                    " + return output + +/obj/mecha/combat/honker/play_stepsound() + if(squeak) + playsound(src, "clownstep", 70, 1) + squeak = !squeak + +/obj/mecha/combat/honker/Topic(href, href_list) + ..() + if (href_list["play_sound"]) + switch(href_list["play_sound"]) + if("sadtrombone") + playsound(src, 'sound/misc/sadtrombone.ogg', 50) + if("bikehorn") + playsound(src, 'sound/items/bikehorn.ogg', 50) + if("airhorn2") + playsound(src, 'sound/items/airhorn2.ogg', 40) //soundfile has higher than average volume + if("carhorn") + playsound(src, 'sound/items/carhorn.ogg', 80) //soundfile has lower than average volume + if("party_horn") + playsound(src, 'sound/items/party_horn.ogg', 50) + if("reee") + playsound(src, 'sound/effects/reee.ogg', 50) + if("weeoo1") + playsound(src, 'sound/items/weeoo1.ogg', 50) + if("hiss1") + playsound(src, 'sound/voice/hiss1.ogg', 50) + if("armbomb") + playsound(src, 'sound/weapons/armbomb.ogg', 50) + if("saberon") + playsound(src, 'sound/weapons/saberon.ogg', 50) + if("airlock_alien_prying") + playsound(src, 'sound/machines/airlock_alien_prying.ogg', 50) + if("lightningbolt") + playsound(src, 'sound/magic/lightningbolt.ogg', 50) + if("explosionfar") + playsound(src, 'sound/effects/explosionfar.ogg', 50) + return diff --git a/code/game/mecha/combat/marauder.dm b/code/game/mecha/combat/marauder.dm index 8c6e2b194cac..d1d4bfc1ea48 100644 --- a/code/game/mecha/combat/marauder.dm +++ b/code/game/mecha/combat/marauder.dm @@ -1,103 +1,103 @@ -/obj/mecha/combat/marauder - desc = "Heavy-duty, combat exosuit, developed after the Durand model. Rarely found among civilian populations." - name = "\improper Marauder" - icon_state = "marauder" - step_in = 5 - max_integrity = 500 - deflect_chance = 25 - armor = list("melee" = 50, "bullet" = 55, "laser" = 40, "energy" = 30, "bomb" = 30, "bio" = 0, "rad" = 60, "fire" = 100, "acid" = 100) - max_temperature = 60000 - resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF - infra_luminosity = 3 - operation_req_access = list(ACCESS_CENT_SPECOPS) - internals_req_access = list(ACCESS_CENT_SPECOPS) - wreckage = /obj/structure/mecha_wreckage/marauder - add_req_access = 0 - internal_damage_threshold = 25 - force = 45 - max_equip = 5 - bumpsmash = 1 - -/obj/mecha/combat/marauder/GrantActions(mob/living/user, human_occupant = 0) - ..() - smoke_action.Grant(user, src) - zoom_action.Grant(user, src) - -/obj/mecha/combat/marauder/RemoveActions(mob/living/user, human_occupant = 0) - ..() - smoke_action.Remove(user) - zoom_action.Remove(user) - -/obj/mecha/combat/marauder/loaded/Initialize() - . = ..() - var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/thrusters/ion(src) - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/weapon/energy/pulse(src) - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack(src) - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/tesla_energy_relay(src) - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/antiproj_armor_booster(src) - ME.attach(src) - max_ammo() - -/obj/mecha/combat/marauder/seraph - desc = "Heavy-duty, command-type exosuit. This is a custom model, utilized only by high-ranking military personnel." - name = "\improper Seraph" - icon_state = "seraph" - operation_req_access = list(ACCESS_CENT_SPECOPS) - internals_req_access = list(ACCESS_CENT_SPECOPS) - step_in = 3 - max_integrity = 550 - wreckage = /obj/structure/mecha_wreckage/seraph - internal_damage_threshold = 20 - force = 55 - max_equip = 6 - -/obj/mecha/combat/marauder/seraph/Initialize() - . = ..() - var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/thrusters/ion(src) - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/weapon/energy/pulse(src) - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack(src) - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/teleporter(src) - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/tesla_energy_relay(src) - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/antiproj_armor_booster(src) - ME.attach(src) - max_ammo() - -/obj/mecha/combat/marauder/mauler - desc = "Heavy-duty, combat exosuit, developed off of the existing Marauder model." - name = "\improper Mauler" - icon_state = "mauler" - operation_req_access = list(ACCESS_SYNDICATE) - internals_req_access = list(ACCESS_SYNDICATE) - wreckage = /obj/structure/mecha_wreckage/mauler - max_equip = 6 - destruction_sleep_duration = 20 - -/obj/mecha/combat/marauder/mauler/Initialize() - . = ..() - var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/thrusters/ion(src) - ME.attach(src) - -/obj/mecha/combat/marauder/mauler/loaded/Initialize() - . = ..() - var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/lmg(src) - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/scattershot(src) - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack(src) - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/tesla_energy_relay(src) - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/antiproj_armor_booster(src) - ME.attach(src) - max_ammo() - - +/obj/mecha/combat/marauder + desc = "Heavy-duty, combat exosuit, developed after the Durand model. Rarely found among civilian populations." + name = "\improper Marauder" + icon_state = "marauder" + step_in = 5 + max_integrity = 500 + deflect_chance = 25 + armor = list("melee" = 50, "bullet" = 55, "laser" = 40, "energy" = 30, "bomb" = 30, "bio" = 0, "rad" = 60, "fire" = 100, "acid" = 100) + max_temperature = 60000 + resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF + infra_luminosity = 3 + operation_req_access = list(ACCESS_CENT_SPECOPS) + internals_req_access = list(ACCESS_CENT_SPECOPS) + wreckage = /obj/structure/mecha_wreckage/marauder + add_req_access = 0 + internal_damage_threshold = 25 + force = 45 + max_equip = 5 + bumpsmash = 1 + +/obj/mecha/combat/marauder/GrantActions(mob/living/user, human_occupant = 0) + ..() + smoke_action.Grant(user, src) + zoom_action.Grant(user, src) + +/obj/mecha/combat/marauder/RemoveActions(mob/living/user, human_occupant = 0) + ..() + smoke_action.Remove(user) + zoom_action.Remove(user) + +/obj/mecha/combat/marauder/loaded/Initialize() + . = ..() + var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/thrusters/ion(src) + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/weapon/energy/pulse(src) + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack(src) + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/tesla_energy_relay(src) + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/antiproj_armor_booster(src) + ME.attach(src) + max_ammo() + +/obj/mecha/combat/marauder/seraph + desc = "Heavy-duty, command-type exosuit. This is a custom model, utilized only by high-ranking military personnel." + name = "\improper Seraph" + icon_state = "seraph" + operation_req_access = list(ACCESS_CENT_SPECOPS) + internals_req_access = list(ACCESS_CENT_SPECOPS) + step_in = 3 + max_integrity = 550 + wreckage = /obj/structure/mecha_wreckage/seraph + internal_damage_threshold = 20 + force = 55 + max_equip = 6 + +/obj/mecha/combat/marauder/seraph/Initialize() + . = ..() + var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/thrusters/ion(src) + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/weapon/energy/pulse(src) + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack(src) + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/teleporter(src) + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/tesla_energy_relay(src) + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/antiproj_armor_booster(src) + ME.attach(src) + max_ammo() + +/obj/mecha/combat/marauder/mauler + desc = "Heavy-duty, combat exosuit, developed off of the existing Marauder model." + name = "\improper Mauler" + icon_state = "mauler" + operation_req_access = list(ACCESS_SYNDICATE) + internals_req_access = list(ACCESS_SYNDICATE) + wreckage = /obj/structure/mecha_wreckage/mauler + max_equip = 6 + destruction_sleep_duration = 20 + +/obj/mecha/combat/marauder/mauler/Initialize() + . = ..() + var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/thrusters/ion(src) + ME.attach(src) + +/obj/mecha/combat/marauder/mauler/loaded/Initialize() + . = ..() + var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/lmg(src) + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/scattershot(src) + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack(src) + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/tesla_energy_relay(src) + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/antiproj_armor_booster(src) + ME.attach(src) + max_ammo() + + diff --git a/code/game/mecha/combat/phazon.dm b/code/game/mecha/combat/phazon.dm index 4f37ac7f1b97..95b60eba68d8 100644 --- a/code/game/mecha/combat/phazon.dm +++ b/code/game/mecha/combat/phazon.dm @@ -1,30 +1,30 @@ -/obj/mecha/combat/phazon - desc = "This is a Phazon exosuit. The pinnacle of scientific research and pride of Nanotrasen, it uses cutting edge bluespace technology and expensive materials." - name = "\improper Phazon" - icon_state = "phazon" - step_in = 2 - dir_in = 2 //Facing South. - step_energy_drain = 3 - max_integrity = 200 - deflect_chance = 30 - armor = list("melee" = 30, "bullet" = 30, "laser" = 30, "energy" = 30, "bomb" = 30, "bio" = 0, "rad" = 50, "fire" = 100, "acid" = 100) - max_temperature = 25000 - infra_luminosity = 3 - wreckage = /obj/structure/mecha_wreckage/phazon - add_req_access = 1 - internal_damage_threshold = 25 - force = 15 - max_equip = 3 - phase_state = "phazon-phase" - -/obj/mecha/combat/phazon/GrantActions(mob/living/user, human_occupant = 0) - ..() - switch_damtype_action.Grant(user, src) - phasing_action.Grant(user, src) - - -/obj/mecha/combat/phazon/RemoveActions(mob/living/user, human_occupant = 0) - ..() - switch_damtype_action.Remove(user) - phasing_action.Remove(user) - +/obj/mecha/combat/phazon + desc = "This is a Phazon exosuit. The pinnacle of scientific research and pride of Nanotrasen, it uses cutting edge bluespace technology and expensive materials." + name = "\improper Phazon" + icon_state = "phazon" + step_in = 2 + dir_in = 2 //Facing South. + step_energy_drain = 3 + max_integrity = 200 + deflect_chance = 30 + armor = list("melee" = 30, "bullet" = 30, "laser" = 30, "energy" = 30, "bomb" = 30, "bio" = 0, "rad" = 50, "fire" = 100, "acid" = 100) + max_temperature = 25000 + infra_luminosity = 3 + wreckage = /obj/structure/mecha_wreckage/phazon + add_req_access = 1 + internal_damage_threshold = 25 + force = 15 + max_equip = 3 + phase_state = "phazon-phase" + +/obj/mecha/combat/phazon/GrantActions(mob/living/user, human_occupant = 0) + ..() + switch_damtype_action.Grant(user, src) + phasing_action.Grant(user, src) + + +/obj/mecha/combat/phazon/RemoveActions(mob/living/user, human_occupant = 0) + ..() + switch_damtype_action.Remove(user) + phasing_action.Remove(user) + diff --git a/code/game/mecha/combat/reticence.dm b/code/game/mecha/combat/reticence.dm index ad628f2df955..d1beff7265be 100644 --- a/code/game/mecha/combat/reticence.dm +++ b/code/game/mecha/combat/reticence.dm @@ -1,28 +1,28 @@ -/obj/mecha/combat/reticence - desc = "A silent, fast, and nigh-invisible miming exosuit. Popular among mimes and mime assassins." - name = "\improper reticence" - icon_state = "reticence" - step_in = 2 - dir_in = 1 //Facing North. - max_integrity = 100 - deflect_chance = 3 - armor = list("melee" = 25, "bullet" = 20, "laser" = 30, "energy" = 15, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - max_temperature = 15000 - wreckage = /obj/structure/mecha_wreckage/reticence - operation_req_access = list(ACCESS_THEATRE) - internals_req_access = list(ACCESS_MECH_SCIENCE, ACCESS_THEATRE) - add_req_access = 0 - internal_damage_threshold = 25 - max_equip = 2 - step_energy_drain = 3 - color = "#87878715" - stepsound = null - turnsound = null - opacity = 0 - -/obj/mecha/combat/reticence/loaded/Initialize() - . = ..() - var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/silenced - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/rcd //HAHA IT MAKES WALLS GET IT - ME.attach(src) +/obj/mecha/combat/reticence + desc = "A silent, fast, and nigh-invisible miming exosuit. Popular among mimes and mime assassins." + name = "\improper reticence" + icon_state = "reticence" + step_in = 2 + dir_in = 1 //Facing North. + max_integrity = 100 + deflect_chance = 3 + armor = list("melee" = 25, "bullet" = 20, "laser" = 30, "energy" = 15, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + max_temperature = 15000 + wreckage = /obj/structure/mecha_wreckage/reticence + operation_req_access = list(ACCESS_THEATRE) + internals_req_access = list(ACCESS_MECH_SCIENCE, ACCESS_THEATRE) + add_req_access = 0 + internal_damage_threshold = 25 + max_equip = 2 + step_energy_drain = 3 + color = "#87878715" + stepsound = null + turnsound = null + opacity = 0 + +/obj/mecha/combat/reticence/loaded/Initialize() + . = ..() + var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/silenced + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/rcd //HAHA IT MAKES WALLS GET IT + ME.attach(src) diff --git a/code/game/mecha/equipment/mecha_equipment.dm b/code/game/mecha/equipment/mecha_equipment.dm index 3a0e5bf52938..e9e3b335ffcc 100644 --- a/code/game/mecha/equipment/mecha_equipment.dm +++ b/code/game/mecha/equipment/mecha_equipment.dm @@ -1,176 +1,176 @@ -//DO NOT ADD MECHA PARTS TO THE GAME WITH THE DEFAULT "SPRITE ME" SPRITE! -//I'm annoyed I even have to tell you this! SPRITE FIRST, then commit. - -/obj/item/mecha_parts/mecha_equipment - name = "mecha equipment" - icon = 'icons/mecha/mecha_equipment.dmi' - icon_state = "mecha_equip" - force = 5 - max_integrity = 300 - var/equip_cooldown = 0 // cooldown after use - var/equip_ready = 1 //whether the equipment is ready for use. (or deactivated/activated for static stuff) - var/energy_drain = 0 - var/obj/mecha/chassis = null - ///Bitflag. Determines the range of the equipment. - var/range = MECHA_MELEE - var/salvageable = 1 - var/detachable = TRUE // Set to FALSE for built-in equipment that cannot be removed - var/selectable = 1 // Set to 0 for passive equipment such as mining scanner or armor plates - var/harmful = FALSE //Controls if equipment can be used to attack by a pacifist. - var/destroy_sound = 'sound/mecha/critdestr.ogg' - -/obj/item/mecha_parts/mecha_equipment/proc/update_chassis_page() - if(chassis) - send_byjax(chassis.occupant,"exosuit.browser","eq_list",chassis.get_equipment_list()) - send_byjax(chassis.occupant,"exosuit.browser","equipment_menu",chassis.get_equipment_menu(),"dropdowns") - return 1 - return - -/obj/item/mecha_parts/mecha_equipment/proc/update_equip_info() - if(chassis) - send_byjax(chassis.occupant,"exosuit.browser","[REF(src)]",get_equip_info()) - return 1 - return - -/obj/item/mecha_parts/mecha_equipment/Destroy() - if(chassis) - chassis.equipment -= src - if(chassis.selected == src) - chassis.selected = null - src.update_chassis_page() - log_message("[src] is destroyed.", LOG_MECHA) - if(chassis.occupant) - chassis.occupant_message("[src] is destroyed!") - chassis.occupant.playsound_local(chassis, destroy_sound, 50) - if(!detachable) //If we're a built-in nondetachable equipment, let's lock up the slot that we were in. - chassis.max_equip-- - chassis = null - return ..() - -/obj/item/mecha_parts/mecha_equipment/try_attach_part(mob/user, obj/mecha/M) - if(can_attach(M)) - if(!user.temporarilyRemoveItemFromInventory(src)) - return FALSE - attach(M) - user.visible_message("[user] attaches [src] to [M].", "You attach [src] to [M].") - return TRUE - to_chat(user, "You are unable to attach [src] to [M]!") - return FALSE - -/obj/item/mecha_parts/mecha_equipment/proc/get_equip_info() - if(!chassis) - return - var/txt = "* " - if(chassis.selected == src) - txt += "[src.name]" - else if(selectable) - txt += "[src.name]" - else - txt += "[src.name]" - - return txt - -/obj/item/mecha_parts/mecha_equipment/proc/is_ranged()//add a distance restricted equipment. Why not? - return range&MECHA_RANGED - -/obj/item/mecha_parts/mecha_equipment/proc/is_melee() - return range&MECHA_MELEE - - -/obj/item/mecha_parts/mecha_equipment/proc/action_checks(atom/target) - if(!target) - return 0 - if(!chassis) - return 0 - if(!equip_ready) - return 0 - if(energy_drain && !chassis.has_charge(energy_drain)) - return 0 - if(chassis.is_currently_ejecting) - return 0 - if(chassis.equipment_disabled) - to_chat(chassis.occupant, "Error -- Equipment control unit is unresponsive.") - return 0 - return 1 - -/obj/item/mecha_parts/mecha_equipment/proc/action(atom/target) - return 0 - -/obj/item/mecha_parts/mecha_equipment/proc/start_cooldown() - set_ready_state(0) - chassis.use_power(energy_drain) - addtimer(CALLBACK(src, .proc/set_ready_state, 1), equip_cooldown) - -/obj/item/mecha_parts/mecha_equipment/proc/do_after_cooldown(atom/target) - if(!chassis) - return - var/C = chassis.loc - set_ready_state(0) - chassis.use_power(energy_drain) - . = do_after(chassis.occupant, equip_cooldown, target=target) - set_ready_state(1) - if(!chassis || chassis.loc != C || src != chassis.selected || !(get_dir(chassis, target)&chassis.dir)) - return 0 - -/obj/item/mecha_parts/mecha_equipment/proc/do_after_mecha(atom/target, delay) - if(!chassis) - return - var/C = chassis.loc - . = do_after(chassis.occupant, delay, target=target) - if(!chassis || chassis.loc != C || src != chassis.selected || !(get_dir(chassis, target)&chassis.dir)) - return 0 - -/obj/item/mecha_parts/mecha_equipment/proc/can_attach(obj/mecha/M) - if(M.equipment.len[src] is destroyed!") + chassis.occupant.playsound_local(chassis, destroy_sound, 50) + if(!detachable) //If we're a built-in nondetachable equipment, let's lock up the slot that we were in. + chassis.max_equip-- + chassis = null + return ..() + +/obj/item/mecha_parts/mecha_equipment/try_attach_part(mob/user, obj/mecha/M) + if(can_attach(M)) + if(!user.temporarilyRemoveItemFromInventory(src)) + return FALSE + attach(M) + user.visible_message("[user] attaches [src] to [M].", "You attach [src] to [M].") + return TRUE + to_chat(user, "You are unable to attach [src] to [M]!") + return FALSE + +/obj/item/mecha_parts/mecha_equipment/proc/get_equip_info() + if(!chassis) + return + var/txt = "* " + if(chassis.selected == src) + txt += "[src.name]" + else if(selectable) + txt += "[src.name]" + else + txt += "[src.name]" + + return txt + +/obj/item/mecha_parts/mecha_equipment/proc/is_ranged()//add a distance restricted equipment. Why not? + return range&MECHA_RANGED + +/obj/item/mecha_parts/mecha_equipment/proc/is_melee() + return range&MECHA_MELEE + + +/obj/item/mecha_parts/mecha_equipment/proc/action_checks(atom/target) + if(!target) + return 0 + if(!chassis) + return 0 + if(!equip_ready) + return 0 + if(energy_drain && !chassis.has_charge(energy_drain)) + return 0 + if(chassis.is_currently_ejecting) + return 0 + if(chassis.equipment_disabled) + to_chat(chassis.occupant, "Error -- Equipment control unit is unresponsive.") + return 0 + return 1 + +/obj/item/mecha_parts/mecha_equipment/proc/action(atom/target) + return 0 + +/obj/item/mecha_parts/mecha_equipment/proc/start_cooldown() + set_ready_state(0) + chassis.use_power(energy_drain) + addtimer(CALLBACK(src, .proc/set_ready_state, 1), equip_cooldown) + +/obj/item/mecha_parts/mecha_equipment/proc/do_after_cooldown(atom/target) + if(!chassis) + return + var/C = chassis.loc + set_ready_state(0) + chassis.use_power(energy_drain) + . = do_after(chassis.occupant, equip_cooldown, target=target) + set_ready_state(1) + if(!chassis || chassis.loc != C || src != chassis.selected || !(get_dir(chassis, target)&chassis.dir)) + return 0 + +/obj/item/mecha_parts/mecha_equipment/proc/do_after_mecha(atom/target, delay) + if(!chassis) + return + var/C = chassis.loc + . = do_after(chassis.occupant, delay, target=target) + if(!chassis || chassis.loc != C || src != chassis.selected || !(get_dir(chassis, target)&chassis.dir)) + return 0 + +/obj/item/mecha_parts/mecha_equipment/proc/can_attach(obj/mecha/M) + if(M.equipment.lenYou start putting [target] into [src]...") - chassis.visible_message("[chassis] starts putting [target] into \the [src].") - if(do_after_cooldown(target)) - if(!patient_insertion_check(target)) - return - target.forceMove(src) - patient = target - START_PROCESSING(SSobj, src) - update_equip_info() - occupant_message("[target] successfully loaded into [src]. Life support functions engaged.") - chassis.visible_message("[chassis] loads [target] into [src].") - log_message("[target] loaded. Life support functions engaged.", LOG_MECHA) - -/obj/item/mecha_parts/mecha_equipment/medical/sleeper/proc/patient_insertion_check(mob/living/carbon/target) - if(target.buckled) - occupant_message("[target] will not fit into the sleeper because [target.p_theyre()] buckled to [target.buckled]!") - return - if(target.has_buckled_mobs()) - occupant_message("[target] will not fit into the sleeper because of the creatures attached to it!") - return - if(patient) - occupant_message("The sleeper is already occupied!") - return - return 1 - -/obj/item/mecha_parts/mecha_equipment/medical/sleeper/proc/go_out() - if(!patient) - return - patient.forceMove(get_turf(src)) - occupant_message("[patient] ejected. Life support functions disabled.") - log_message("[patient] ejected. Life support functions disabled.", LOG_MECHA) - STOP_PROCESSING(SSobj, src) - patient = null - update_equip_info() - -/obj/item/mecha_parts/mecha_equipment/medical/sleeper/detach() - if(patient) - occupant_message("Unable to detach [src] - equipment occupied!") - return - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/item/mecha_parts/mecha_equipment/medical/sleeper/get_equip_info() - var/output = ..() - if(output) - var/temp = "" - if(patient) - temp = "
                    \[Occupant: [patient] ([patient.stat > 1 ? "*DECEASED*" : "Health: [patient.health]%"])\]
                    View stats|Eject" - return "[output] [temp]" - -/obj/item/mecha_parts/mecha_equipment/medical/sleeper/Topic(href,href_list) - ..() - if(href_list["eject"]) - go_out() - if(href_list["view_stats"]) - chassis.occupant << browse(get_patient_stats(),"window=msleeper") - onclose(chassis.occupant, "msleeper") - return - if(href_list["inject"]) - var/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/SG = locate() in chassis - var/datum/reagent/R = locate(href_list["inject"]) in SG.reagents.reagent_list - if (istype(R)) - inject_reagent(R, SG) - -/obj/item/mecha_parts/mecha_equipment/medical/sleeper/proc/get_patient_stats() - if(!patient) - return - return {" - - - [patient] statistics - - - - -

                    Health statistics

                    -
                    - [get_patient_dam()] -
                    -

                    Reagents in bloodstream

                    -
                    - [get_patient_reagents()] -
                    -
                    - [get_available_reagents()] -
                    - - "} - -/obj/item/mecha_parts/mecha_equipment/medical/sleeper/proc/get_patient_dam() - var/t1 - switch(patient.stat) - if(0) - t1 = "Conscious" - if(1) - t1 = "Unconscious" - if(2) - t1 = "*dead*" - else - t1 = "Unknown" - return {"Health: [patient.stat > 1 ? "[t1]" : "[patient.health]% ([t1])"]
                    - Core Temperature: [patient.bodytemperature-T0C]°C ([patient.bodytemperature*1.8-459.67]°F)
                    - Brute Damage: [patient.getBruteLoss()]%
                    - Respiratory Damage: [patient.getOxyLoss()]%
                    - Toxin Content: [patient.getToxLoss()]%
                    - Burn Severity: [patient.getFireLoss()]%
                    - [patient.getCloneLoss() ? "Subject appears to have cellular damage." : ""]
                    - [patient.getOrganLoss(ORGAN_SLOT_BRAIN) ? "Significant brain damage detected." : ""]
                    - [length(patient.get_traumas()) ? "Brain Traumas detected." : ""]
                    - "} - -/obj/item/mecha_parts/mecha_equipment/medical/sleeper/proc/get_patient_reagents() - if(patient.reagents) - for(var/datum/reagent/R in patient.reagents.reagent_list) - if(R.volume > 0) - . += "[R]: [round(R.volume,0.01)]
                    " - return . || "None" - -/obj/item/mecha_parts/mecha_equipment/medical/sleeper/proc/get_available_reagents() - var/output - var/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/SG = locate(/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun) in chassis - if(SG && SG.reagents && islist(SG.reagents.reagent_list)) - for(var/datum/reagent/R in SG.reagents.reagent_list) - if(R.volume > 0) - output += "Inject [R.name]
                    " - return output - - -/obj/item/mecha_parts/mecha_equipment/medical/sleeper/proc/inject_reagent(datum/reagent/R,obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/SG) - if(!R || !patient || !SG || !(SG in chassis.equipment)) - return 0 - var/to_inject = min(R.volume, inject_amount) - if(to_inject && patient.reagents.get_reagent_amount(R.type) + to_inject <= inject_amount*2) - occupant_message("Injecting [patient] with [to_inject] units of [R.name].") - log_message("Injecting [patient] with [to_inject] units of [R.name].", LOG_MECHA) - log_combat(chassis.occupant, patient, "injected", "[name] ([R] - [to_inject] units)") - SG.reagents.trans_id_to(patient,R.type,to_inject) - update_equip_info() - return - -/obj/item/mecha_parts/mecha_equipment/medical/sleeper/update_equip_info() - if(..()) - if(patient) - send_byjax(chassis.occupant,"msleeper.browser","lossinfo",get_patient_dam()) - send_byjax(chassis.occupant,"msleeper.browser","reagents",get_patient_reagents()) - send_byjax(chassis.occupant,"msleeper.browser","injectwith",get_available_reagents()) - return 1 - return - -/obj/item/mecha_parts/mecha_equipment/medical/sleeper/container_resist(mob/living/user) - go_out() - -/obj/item/mecha_parts/mecha_equipment/medical/sleeper/process() - if(..()) - return - if(!chassis.has_charge(energy_drain)) - set_ready_state(1) - log_message("Deactivated.", LOG_MECHA) - occupant_message("[src] deactivated - no power.") - STOP_PROCESSING(SSobj, src) - return - var/mob/living/carbon/M = patient - if(!M) - return - if(M.health > 0) - M.adjustOxyLoss(-1) - M.AdjustStun(-80) - M.AdjustKnockdown(-80) - M.AdjustParalyzed(-80) - M.AdjustImmobilized(-80) - M.AdjustUnconscious(-80) - if(M.reagents.get_reagent_amount(/datum/reagent/medicine/epinephrine) < 5) - M.reagents.add_reagent(/datum/reagent/medicine/epinephrine, 5) - chassis.use_power(energy_drain) - update_equip_info() - - - - -///////////////////////////////// Syringe Gun /////////////////////////////////////////////////////////////// - - -/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun - name = "exosuit syringe gun" - desc = "Equipment for medical exosuits. A chem synthesizer with syringe gun. Reagents inside are held in stasis, so no reactions will occur." - icon = 'icons/obj/guns/projectile.dmi' - icon_state = "syringegun" - var/list/syringes - var/list/known_reagents - var/list/processed_reagents - var/max_syringes = 10 - var/max_volume = 75 //max reagent volume - var/synth_speed = 5 //[num] reagent units per cycle - energy_drain = 10 - var/mode = 0 //0 - fire syringe, 1 - analyze reagents. - range = MECHA_MELEE|MECHA_RANGED - equip_cooldown = 10 - -/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/Initialize() - . = ..() - create_reagents(max_volume, NO_REACT) - syringes = new - known_reagents = list(/datum/reagent/medicine/epinephrine="Epinephrine",/datum/reagent/medicine/charcoal="Charcoal") - processed_reagents = new - -/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/detach() - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/Destroy() - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/can_attach(obj/mecha/medical/M) - if(..()) - if(istype(M)) - return 1 - return 0 - -/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/get_equip_info() - var/output = ..() - if(output) - return "[output] \[[mode? "Analyze" : "Launch"]\]
                    \[Syringes: [syringes.len]/[max_syringes] | Reagents: [reagents.total_volume]/[reagents.maximum_volume]\]
                    Reagents list" - -/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/action(atom/movable/target) - if(!action_checks(target)) - return - if(istype(target, /obj/item/reagent_containers/syringe)) - return load_syringe(target) - if(istype(target, /obj/item/storage))//Loads syringes from boxes - for(var/obj/item/reagent_containers/syringe/S in target.contents) - load_syringe(S) - return - if(mode) - return analyze_reagents(target) - if(!syringes.len) - occupant_message("No syringes loaded.") - return - if(reagents.total_volume<=0) - occupant_message("No available reagents to load syringe with.") - return - var/turf/trg = get_turf(target) - var/obj/item/reagent_containers/syringe/mechsyringe = syringes[1] - mechsyringe.forceMove(get_turf(chassis)) - reagents.trans_to(mechsyringe, min(mechsyringe.volume, reagents.total_volume), transfered_by = chassis.occupant) - syringes -= mechsyringe - mechsyringe.icon = 'icons/obj/chemical.dmi' - mechsyringe.icon_state = "syringeproj" - playsound(chassis, 'sound/items/syringeproj.ogg', 50, TRUE) - log_message("Launched [mechsyringe] from [src], targeting [target].", LOG_MECHA) - var/mob/originaloccupant = chassis.occupant - spawn(0) - src = null //if src is deleted, still process the syringe - for(var/i=0, i<6, i++) - if(!mechsyringe) - break - if(step_towards(mechsyringe,trg)) - var/list/mobs = new - for(var/mob/living/carbon/M in mechsyringe.loc) - mobs += M - if(length(mobs)) - var/mob/living/carbon/M = pick(mobs) - var/R - mechsyringe.visible_message(" [M] is hit by the syringe!") - if(M.can_inject(null, 1)) - if(mechsyringe.reagents) - for(var/datum/reagent/A in mechsyringe.reagents.reagent_list) - R += "[A.name] ([num2text(A.volume)]" - mechsyringe.icon_state = initial(mechsyringe.icon_state) - mechsyringe.icon = initial(mechsyringe.icon) - mechsyringe.reagents.expose(M, INJECT) - mechsyringe.reagents.trans_to(M, mechsyringe.reagents.total_volume, transfered_by = originaloccupant) - M.take_bodypart_damage(2) - log_combat(originaloccupant, M, "shot", "syringegun") - break - else if(mechsyringe.loc == trg) - mechsyringe.icon_state = initial(mechsyringe.icon_state) - mechsyringe.icon = initial(mechsyringe.icon) - mechsyringe.update_icon() - break - else - mechsyringe.icon_state = initial(mechsyringe.icon_state) - mechsyringe.icon = initial(mechsyringe.icon) - mechsyringe.update_icon() - break - sleep(1) - return 1 - - -/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/Topic(href,href_list) - ..() - if (href_list["toggle_mode"]) - mode = !mode - update_equip_info() - return - if (href_list["select_reagents"]) - processed_reagents.len = 0 - var/m = 0 - var/message - for(var/i=1 to known_reagents.len) - if(m>=synth_speed) - break - var/reagent = text2path(href_list["reagent_[i]"]) - if(reagent && (reagent in known_reagents)) - message = "[m ? ", " : null][known_reagents[reagent]]" - processed_reagents += reagent - m++ - if(processed_reagents.len) - message += " added to production" - START_PROCESSING(SSobj, src) - occupant_message(message) - occupant_message("Reagent processing started.") - log_message("Reagent processing started.", LOG_MECHA) - return - if (href_list["show_reagents"]) - chassis.occupant << browse(get_reagents_page(),"window=msyringegun") - if (href_list["purge_reagent"]) - var/reagent = href_list["purge_reagent"] - if(reagent) - reagents.del_reagent(reagent) - return - if (href_list["purge_all"]) - reagents.clear_reagents() - -/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/proc/get_reagents_page() - var/output = {" - - - Reagent Synthesizer - - - - -

                    Current reagents:

                    -
                    - [get_current_reagents()] -
                    -

                    Reagents production:

                    -
                    - [get_reagents_form()] -
                    - - - "} - return output - -/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/proc/get_reagents_form() - var/r_list = get_reagents_list() - var/inputs - if(r_list) - inputs += "" - inputs += "" - inputs += "" - var/output = {" - [r_list || "No known reagents"] - [inputs] - - [r_list? "Only the first [synth_speed] selected reagent\s will be added to production" : null] - "} - return output - -/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/proc/get_reagents_list() - var/output - for(var/i=1 to known_reagents.len) - var/reagent_id = known_reagents[i] - output += {" [known_reagents[reagent_id]]
                    "} - return output - -/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/proc/get_current_reagents() - var/output - for(var/datum/reagent/R in reagents.reagent_list) - if(R.volume > 0) - output += "[R]: [round(R.volume,0.001)] - Purge Reagent
                    " - if(output) - output += "Total: [round(reagents.total_volume,0.001)]/[reagents.maximum_volume] - Purge All" - return output || "None" - -/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/proc/load_syringe(obj/item/reagent_containers/syringe/S) - if(syringes.len= 2) - occupant_message("The syringe is too far away!") - return 0 - for(var/obj/structure/D in S.loc)//Basic level check for structures in the way (Like grilles and windows) - if(!(D.CanPass(S,src.loc))) - occupant_message("Unable to load syringe!") - return 0 - for(var/obj/machinery/door/D in S.loc)//Checks for doors - if(!(D.CanPass(S,src.loc))) - occupant_message("Unable to load syringe!") - return 0 - S.reagents.trans_to(src, S.reagents.total_volume, transfered_by = chassis.occupant) - S.forceMove(src) - syringes += S - occupant_message("Syringe loaded.") - update_equip_info() - return 1 - occupant_message("[src]'s syringe chamber is full!") - return 0 - -/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/proc/analyze_reagents(atom/A) - if(get_dist(src,A) >= 4) - occupant_message("The object is too far away!") - return 0 - if(!A.reagents || ismob(A)) - occupant_message("No reagent info gained from [A].") - return 0 - occupant_message("Analyzing reagents...") - for(var/datum/reagent/R in A.reagents.reagent_list) - if(R.can_synth && add_known_reagent(R.type,R.name)) - occupant_message("Reagent analyzed, identified as [R.name] and added to database.") - send_byjax(chassis.occupant,"msyringegun.browser","reagents_form",get_reagents_form()) - occupant_message("Analyzis complete.") - return 1 - -/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/proc/add_known_reagent(r_id,r_name) - if(!(r_id in known_reagents)) - known_reagents += r_id - known_reagents[r_id] = r_name - return 1 - return 0 - -/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/update_equip_info() - if(..()) - send_byjax(chassis.occupant,"msyringegun.browser","reagents",get_current_reagents()) - send_byjax(chassis.occupant,"msyringegun.browser","reagents_form",get_reagents_form()) - return 1 - -/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/on_reagent_change(changetype) - ..() - update_equip_info() - - -/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/process() - if(..()) - return - if(!processed_reagents.len || reagents.total_volume >= reagents.maximum_volume || !chassis.has_charge(energy_drain)) - occupant_message("Reagent processing stopped.") - log_message("Reagent processing stopped.", LOG_MECHA) - STOP_PROCESSING(SSobj, src) - return - var/amount = synth_speed / processed_reagents.len - for(var/reagent in processed_reagents) - reagents.add_reagent(reagent,amount) - chassis.use_power(energy_drain) - -///////////////////////////////// Medical Beam /////////////////////////////////////////////////////////////// - -/obj/item/mecha_parts/mecha_equipment/medical/mechmedbeam - name = "exosuit medical beamgun" - desc = "Equipment for medical exosuits. Generates a focused beam of medical nanites." - icon_state = "mecha_medigun" - energy_drain = 10 - range = MECHA_MELEE|MECHA_RANGED - equip_cooldown = 0 - var/obj/item/gun/medbeam/mech/medigun - custom_materials = list(/datum/material/iron = 15000, /datum/material/glass = 8000, /datum/material/plasma = 3000, /datum/material/gold = 8000, /datum/material/diamond = 2000) - material_flags = MATERIAL_NO_EFFECTS - -/obj/item/mecha_parts/mecha_equipment/medical/mechmedbeam/Initialize() - . = ..() - medigun = new(src) - - -/obj/item/mecha_parts/mecha_equipment/medical/mechmedbeam/Destroy() - qdel(medigun) - return ..() - -/obj/item/mecha_parts/mecha_equipment/medical/mechmedbeam/process() - if(..()) - return - medigun.process() - -/obj/item/mecha_parts/mecha_equipment/medical/mechmedbeam/action(atom/target) - medigun.process_fire(target, loc) - - -/obj/item/mecha_parts/mecha_equipment/medical/mechmedbeam/detach() - STOP_PROCESSING(SSobj, src) - medigun.LoseTarget() - return ..() +// Sleeper, Medical Beam, and Syringe gun + +/obj/item/mecha_parts/mecha_equipment/medical + +/obj/item/mecha_parts/mecha_equipment/medical/Initialize() + . = ..() + START_PROCESSING(SSobj, src) + +/obj/item/mecha_parts/mecha_equipment/medical/can_attach(obj/mecha/medical/M) + if(..() && istype(M)) + return 1 + + +/obj/item/mecha_parts/mecha_equipment/medical/attach(obj/mecha/M) + ..() + START_PROCESSING(SSobj, src) + +/obj/item/mecha_parts/mecha_equipment/medical/Destroy() + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/mecha_parts/mecha_equipment/medical/process() + if(!chassis) + STOP_PROCESSING(SSobj, src) + return 1 + +/obj/item/mecha_parts/mecha_equipment/medical/mechmedbeam/detach() + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/mecha_parts/mecha_equipment/medical/sleeper + name = "mounted sleeper" + desc = "Equipment for medical exosuits. A mounted sleeper that stabilizes patients and can inject reagents in the exosuit's reserves." + icon = 'icons/obj/machines/sleeper.dmi' + icon_state = "sleeper" + energy_drain = 20 + range = MECHA_MELEE + equip_cooldown = 20 + var/mob/living/carbon/patient = null + var/inject_amount = 10 + salvageable = 0 + +/obj/item/mecha_parts/mecha_equipment/medical/sleeper/Destroy() + for(var/atom/movable/AM in src) + AM.forceMove(get_turf(src)) + return ..() + +/obj/item/mecha_parts/mecha_equipment/medical/sleeper/Exit(atom/movable/O) + return 0 + +/obj/item/mecha_parts/mecha_equipment/medical/sleeper/action(mob/living/carbon/target) + if(!action_checks(target)) + return + if(!istype(target)) + return + if(!patient_insertion_check(target)) + return + occupant_message("You start putting [target] into [src]...") + chassis.visible_message("[chassis] starts putting [target] into \the [src].") + if(do_after_cooldown(target)) + if(!patient_insertion_check(target)) + return + target.forceMove(src) + patient = target + START_PROCESSING(SSobj, src) + update_equip_info() + occupant_message("[target] successfully loaded into [src]. Life support functions engaged.") + chassis.visible_message("[chassis] loads [target] into [src].") + log_message("[target] loaded. Life support functions engaged.", LOG_MECHA) + +/obj/item/mecha_parts/mecha_equipment/medical/sleeper/proc/patient_insertion_check(mob/living/carbon/target) + if(target.buckled) + occupant_message("[target] will not fit into the sleeper because [target.p_theyre()] buckled to [target.buckled]!") + return + if(target.has_buckled_mobs()) + occupant_message("[target] will not fit into the sleeper because of the creatures attached to it!") + return + if(patient) + occupant_message("The sleeper is already occupied!") + return + return 1 + +/obj/item/mecha_parts/mecha_equipment/medical/sleeper/proc/go_out() + if(!patient) + return + patient.forceMove(get_turf(src)) + occupant_message("[patient] ejected. Life support functions disabled.") + log_message("[patient] ejected. Life support functions disabled.", LOG_MECHA) + STOP_PROCESSING(SSobj, src) + patient = null + update_equip_info() + +/obj/item/mecha_parts/mecha_equipment/medical/sleeper/detach() + if(patient) + occupant_message("Unable to detach [src] - equipment occupied!") + return + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/mecha_parts/mecha_equipment/medical/sleeper/get_equip_info() + var/output = ..() + if(output) + var/temp = "" + if(patient) + temp = "
                    \[Occupant: [patient] ([patient.stat > 1 ? "*DECEASED*" : "Health: [patient.health]%"])\]
                    View stats|Eject" + return "[output] [temp]" + +/obj/item/mecha_parts/mecha_equipment/medical/sleeper/Topic(href,href_list) + ..() + if(href_list["eject"]) + go_out() + if(href_list["view_stats"]) + chassis.occupant << browse(get_patient_stats(),"window=msleeper") + onclose(chassis.occupant, "msleeper") + return + if(href_list["inject"]) + var/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/SG = locate() in chassis + var/datum/reagent/R = locate(href_list["inject"]) in SG.reagents.reagent_list + if (istype(R)) + inject_reagent(R, SG) + +/obj/item/mecha_parts/mecha_equipment/medical/sleeper/proc/get_patient_stats() + if(!patient) + return + return {" + + + [patient] statistics + + + + +

                    Health statistics

                    +
                    + [get_patient_dam()] +
                    +

                    Reagents in bloodstream

                    +
                    + [get_patient_reagents()] +
                    +
                    + [get_available_reagents()] +
                    + + "} + +/obj/item/mecha_parts/mecha_equipment/medical/sleeper/proc/get_patient_dam() + var/t1 + switch(patient.stat) + if(0) + t1 = "Conscious" + if(1) + t1 = "Unconscious" + if(2) + t1 = "*dead*" + else + t1 = "Unknown" + return {"Health: [patient.stat > 1 ? "[t1]" : "[patient.health]% ([t1])"]
                    + Core Temperature: [patient.bodytemperature-T0C]°C ([patient.bodytemperature*1.8-459.67]°F)
                    + Brute Damage: [patient.getBruteLoss()]%
                    + Respiratory Damage: [patient.getOxyLoss()]%
                    + Toxin Content: [patient.getToxLoss()]%
                    + Burn Severity: [patient.getFireLoss()]%
                    + [patient.getCloneLoss() ? "Subject appears to have cellular damage." : ""]
                    + [patient.getOrganLoss(ORGAN_SLOT_BRAIN) ? "Significant brain damage detected." : ""]
                    + [length(patient.get_traumas()) ? "Brain Traumas detected." : ""]
                    + "} + +/obj/item/mecha_parts/mecha_equipment/medical/sleeper/proc/get_patient_reagents() + if(patient.reagents) + for(var/datum/reagent/R in patient.reagents.reagent_list) + if(R.volume > 0) + . += "[R]: [round(R.volume,0.01)]
                    " + return . || "None" + +/obj/item/mecha_parts/mecha_equipment/medical/sleeper/proc/get_available_reagents() + var/output + var/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/SG = locate(/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun) in chassis + if(SG && SG.reagents && islist(SG.reagents.reagent_list)) + for(var/datum/reagent/R in SG.reagents.reagent_list) + if(R.volume > 0) + output += "Inject [R.name]
                    " + return output + + +/obj/item/mecha_parts/mecha_equipment/medical/sleeper/proc/inject_reagent(datum/reagent/R,obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/SG) + if(!R || !patient || !SG || !(SG in chassis.equipment)) + return 0 + var/to_inject = min(R.volume, inject_amount) + if(to_inject && patient.reagents.get_reagent_amount(R.type) + to_inject <= inject_amount*2) + occupant_message("Injecting [patient] with [to_inject] units of [R.name].") + log_message("Injecting [patient] with [to_inject] units of [R.name].", LOG_MECHA) + log_combat(chassis.occupant, patient, "injected", "[name] ([R] - [to_inject] units)") + SG.reagents.trans_id_to(patient,R.type,to_inject) + update_equip_info() + return + +/obj/item/mecha_parts/mecha_equipment/medical/sleeper/update_equip_info() + if(..()) + if(patient) + send_byjax(chassis.occupant,"msleeper.browser","lossinfo",get_patient_dam()) + send_byjax(chassis.occupant,"msleeper.browser","reagents",get_patient_reagents()) + send_byjax(chassis.occupant,"msleeper.browser","injectwith",get_available_reagents()) + return 1 + return + +/obj/item/mecha_parts/mecha_equipment/medical/sleeper/container_resist(mob/living/user) + go_out() + +/obj/item/mecha_parts/mecha_equipment/medical/sleeper/process() + if(..()) + return + if(!chassis.has_charge(energy_drain)) + set_ready_state(1) + log_message("Deactivated.", LOG_MECHA) + occupant_message("[src] deactivated - no power.") + STOP_PROCESSING(SSobj, src) + return + var/mob/living/carbon/M = patient + if(!M) + return + if(M.health > 0) + M.adjustOxyLoss(-1) + M.AdjustStun(-80) + M.AdjustKnockdown(-80) + M.AdjustParalyzed(-80) + M.AdjustImmobilized(-80) + M.AdjustUnconscious(-80) + if(M.reagents.get_reagent_amount(/datum/reagent/medicine/epinephrine) < 5) + M.reagents.add_reagent(/datum/reagent/medicine/epinephrine, 5) + chassis.use_power(energy_drain) + update_equip_info() + + + + +///////////////////////////////// Syringe Gun /////////////////////////////////////////////////////////////// + + +/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun + name = "exosuit syringe gun" + desc = "Equipment for medical exosuits. A chem synthesizer with syringe gun. Reagents inside are held in stasis, so no reactions will occur." + icon = 'icons/obj/guns/projectile.dmi' + icon_state = "syringegun" + var/list/syringes + var/list/known_reagents + var/list/processed_reagents + var/max_syringes = 10 + var/max_volume = 75 //max reagent volume + var/synth_speed = 5 //[num] reagent units per cycle + energy_drain = 10 + var/mode = 0 //0 - fire syringe, 1 - analyze reagents. + range = MECHA_MELEE|MECHA_RANGED + equip_cooldown = 10 + +/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/Initialize() + . = ..() + create_reagents(max_volume, NO_REACT) + syringes = new + known_reagents = list(/datum/reagent/medicine/epinephrine="Epinephrine",/datum/reagent/medicine/charcoal="Charcoal") + processed_reagents = new + +/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/detach() + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/Destroy() + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/can_attach(obj/mecha/medical/M) + if(..()) + if(istype(M)) + return 1 + return 0 + +/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/get_equip_info() + var/output = ..() + if(output) + return "[output] \[[mode? "Analyze" : "Launch"]\]
                    \[Syringes: [syringes.len]/[max_syringes] | Reagents: [reagents.total_volume]/[reagents.maximum_volume]\]
                    Reagents list" + +/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/action(atom/movable/target) + if(!action_checks(target)) + return + if(istype(target, /obj/item/reagent_containers/syringe)) + return load_syringe(target) + if(istype(target, /obj/item/storage))//Loads syringes from boxes + for(var/obj/item/reagent_containers/syringe/S in target.contents) + load_syringe(S) + return + if(mode) + return analyze_reagents(target) + if(!syringes.len) + occupant_message("No syringes loaded.") + return + if(reagents.total_volume<=0) + occupant_message("No available reagents to load syringe with.") + return + var/turf/trg = get_turf(target) + var/obj/item/reagent_containers/syringe/mechsyringe = syringes[1] + mechsyringe.forceMove(get_turf(chassis)) + reagents.trans_to(mechsyringe, min(mechsyringe.volume, reagents.total_volume), transfered_by = chassis.occupant) + syringes -= mechsyringe + mechsyringe.icon = 'icons/obj/chemical.dmi' + mechsyringe.icon_state = "syringeproj" + playsound(chassis, 'sound/items/syringeproj.ogg', 50, TRUE) + log_message("Launched [mechsyringe] from [src], targeting [target].", LOG_MECHA) + var/mob/originaloccupant = chassis.occupant + spawn(0) + src = null //if src is deleted, still process the syringe + for(var/i=0, i<6, i++) + if(!mechsyringe) + break + if(step_towards(mechsyringe,trg)) + var/list/mobs = new + for(var/mob/living/carbon/M in mechsyringe.loc) + mobs += M + if(length(mobs)) + var/mob/living/carbon/M = pick(mobs) + var/R + mechsyringe.visible_message(" [M] is hit by the syringe!") + if(M.can_inject(null, 1)) + if(mechsyringe.reagents) + for(var/datum/reagent/A in mechsyringe.reagents.reagent_list) + R += "[A.name] ([num2text(A.volume)]" + mechsyringe.icon_state = initial(mechsyringe.icon_state) + mechsyringe.icon = initial(mechsyringe.icon) + mechsyringe.reagents.expose(M, INJECT) + mechsyringe.reagents.trans_to(M, mechsyringe.reagents.total_volume, transfered_by = originaloccupant) + M.take_bodypart_damage(2) + log_combat(originaloccupant, M, "shot", "syringegun") + break + else if(mechsyringe.loc == trg) + mechsyringe.icon_state = initial(mechsyringe.icon_state) + mechsyringe.icon = initial(mechsyringe.icon) + mechsyringe.update_icon() + break + else + mechsyringe.icon_state = initial(mechsyringe.icon_state) + mechsyringe.icon = initial(mechsyringe.icon) + mechsyringe.update_icon() + break + sleep(1) + return 1 + + +/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/Topic(href,href_list) + ..() + if (href_list["toggle_mode"]) + mode = !mode + update_equip_info() + return + if (href_list["select_reagents"]) + processed_reagents.len = 0 + var/m = 0 + var/message + for(var/i=1 to known_reagents.len) + if(m>=synth_speed) + break + var/reagent = text2path(href_list["reagent_[i]"]) + if(reagent && (reagent in known_reagents)) + message = "[m ? ", " : null][known_reagents[reagent]]" + processed_reagents += reagent + m++ + if(processed_reagents.len) + message += " added to production" + START_PROCESSING(SSobj, src) + occupant_message(message) + occupant_message("Reagent processing started.") + log_message("Reagent processing started.", LOG_MECHA) + return + if (href_list["show_reagents"]) + chassis.occupant << browse(get_reagents_page(),"window=msyringegun") + if (href_list["purge_reagent"]) + var/reagent = href_list["purge_reagent"] + if(reagent) + reagents.del_reagent(reagent) + return + if (href_list["purge_all"]) + reagents.clear_reagents() + +/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/proc/get_reagents_page() + var/output = {" + + + Reagent Synthesizer + + + + +

                    Current reagents:

                    +
                    + [get_current_reagents()] +
                    +

                    Reagents production:

                    +
                    + [get_reagents_form()] +
                    + + + "} + return output + +/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/proc/get_reagents_form() + var/r_list = get_reagents_list() + var/inputs + if(r_list) + inputs += "" + inputs += "" + inputs += "" + var/output = {"
                    + [r_list || "No known reagents"] + [inputs] +
                    + [r_list? "Only the first [synth_speed] selected reagent\s will be added to production" : null] + "} + return output + +/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/proc/get_reagents_list() + var/output + for(var/i=1 to known_reagents.len) + var/reagent_id = known_reagents[i] + output += {" [known_reagents[reagent_id]]
                    "} + return output + +/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/proc/get_current_reagents() + var/output + for(var/datum/reagent/R in reagents.reagent_list) + if(R.volume > 0) + output += "[R]: [round(R.volume,0.001)] - Purge Reagent
                    " + if(output) + output += "Total: [round(reagents.total_volume,0.001)]/[reagents.maximum_volume] - Purge All" + return output || "None" + +/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/proc/load_syringe(obj/item/reagent_containers/syringe/S) + if(syringes.len= 2) + occupant_message("The syringe is too far away!") + return 0 + for(var/obj/structure/D in S.loc)//Basic level check for structures in the way (Like grilles and windows) + if(!(D.CanPass(S,src.loc))) + occupant_message("Unable to load syringe!") + return 0 + for(var/obj/machinery/door/D in S.loc)//Checks for doors + if(!(D.CanPass(S,src.loc))) + occupant_message("Unable to load syringe!") + return 0 + S.reagents.trans_to(src, S.reagents.total_volume, transfered_by = chassis.occupant) + S.forceMove(src) + syringes += S + occupant_message("Syringe loaded.") + update_equip_info() + return 1 + occupant_message("[src]'s syringe chamber is full!") + return 0 + +/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/proc/analyze_reagents(atom/A) + if(get_dist(src,A) >= 4) + occupant_message("The object is too far away!") + return 0 + if(!A.reagents || ismob(A)) + occupant_message("No reagent info gained from [A].") + return 0 + occupant_message("Analyzing reagents...") + for(var/datum/reagent/R in A.reagents.reagent_list) + if(R.can_synth && add_known_reagent(R.type,R.name)) + occupant_message("Reagent analyzed, identified as [R.name] and added to database.") + send_byjax(chassis.occupant,"msyringegun.browser","reagents_form",get_reagents_form()) + occupant_message("Analyzis complete.") + return 1 + +/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/proc/add_known_reagent(r_id,r_name) + if(!(r_id in known_reagents)) + known_reagents += r_id + known_reagents[r_id] = r_name + return 1 + return 0 + +/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/update_equip_info() + if(..()) + send_byjax(chassis.occupant,"msyringegun.browser","reagents",get_current_reagents()) + send_byjax(chassis.occupant,"msyringegun.browser","reagents_form",get_reagents_form()) + return 1 + +/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/on_reagent_change(changetype) + ..() + update_equip_info() + + +/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/process() + if(..()) + return + if(!processed_reagents.len || reagents.total_volume >= reagents.maximum_volume || !chassis.has_charge(energy_drain)) + occupant_message("Reagent processing stopped.") + log_message("Reagent processing stopped.", LOG_MECHA) + STOP_PROCESSING(SSobj, src) + return + var/amount = synth_speed / processed_reagents.len + for(var/reagent in processed_reagents) + reagents.add_reagent(reagent,amount) + chassis.use_power(energy_drain) + +///////////////////////////////// Medical Beam /////////////////////////////////////////////////////////////// + +/obj/item/mecha_parts/mecha_equipment/medical/mechmedbeam + name = "exosuit medical beamgun" + desc = "Equipment for medical exosuits. Generates a focused beam of medical nanites." + icon_state = "mecha_medigun" + energy_drain = 10 + range = MECHA_MELEE|MECHA_RANGED + equip_cooldown = 0 + var/obj/item/gun/medbeam/mech/medigun + custom_materials = list(/datum/material/iron = 15000, /datum/material/glass = 8000, /datum/material/plasma = 3000, /datum/material/gold = 8000, /datum/material/diamond = 2000) + material_flags = MATERIAL_NO_EFFECTS + +/obj/item/mecha_parts/mecha_equipment/medical/mechmedbeam/Initialize() + . = ..() + medigun = new(src) + + +/obj/item/mecha_parts/mecha_equipment/medical/mechmedbeam/Destroy() + qdel(medigun) + return ..() + +/obj/item/mecha_parts/mecha_equipment/medical/mechmedbeam/process() + if(..()) + return + medigun.process() + +/obj/item/mecha_parts/mecha_equipment/medical/mechmedbeam/action(atom/target) + medigun.process_fire(target, loc) + + +/obj/item/mecha_parts/mecha_equipment/medical/mechmedbeam/detach() + STOP_PROCESSING(SSobj, src) + medigun.LoseTarget() + return ..() diff --git a/code/game/mecha/equipment/weapons/weapons.dm b/code/game/mecha/equipment/weapons/weapons.dm index 6721e2ebc560..14489eba591a 100644 --- a/code/game/mecha/equipment/weapons/weapons.dm +++ b/code/game/mecha/equipment/weapons/weapons.dm @@ -1,496 +1,496 @@ -/obj/item/mecha_parts/mecha_equipment/weapon - name = "mecha weapon" - range = MECHA_RANGED - destroy_sound = 'sound/mecha/weapdestr.ogg' - var/projectile - var/fire_sound - var/projectiles_per_shot = 1 - var/variance = 0 - var/randomspread = 0 //use random spread for machineguns, instead of shotgun scatter - var/projectile_delay = 0 - var/firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect //the visual effect appearing when the weapon is fired. - var/kickback = TRUE //Will using this weapon in no grav push mecha back. - -/obj/item/mecha_parts/mecha_equipment/weapon/can_attach(obj/mecha/M) - if(!..()) - return FALSE - if(istype(M, /obj/mecha/combat)) - return TRUE - if((locate(/obj/item/mecha_parts/concealed_weapon_bay) in M.contents) && !(locate(/obj/item/mecha_parts/mecha_equipment/weapon) in M.equipment)) - return TRUE - return FALSE - -/obj/item/mecha_parts/mecha_equipment/weapon/proc/get_shot_amount() - return projectiles_per_shot - -/obj/item/mecha_parts/mecha_equipment/weapon/action(atom/target, params) - if(!action_checks(target)) - return 0 - - var/turf/curloc = get_turf(chassis) - var/turf/targloc = get_turf(target) - if (!targloc || !istype(targloc) || !curloc) - return 0 - if (targloc == curloc) - return 0 - - set_ready_state(0) - for(var/i=1 to get_shot_amount()) - var/obj/projectile/A = new projectile(curloc) - A.firer = chassis.occupant - A.original = target - if(!A.suppressed && firing_effect_type) - new firing_effect_type(get_turf(src), chassis.dir) - - var/spread = 0 - if(variance) - if(randomspread) - spread = round((rand() - 0.5) * variance) - else - spread = round((i / projectiles_per_shot - 0.5) * variance) - A.preparePixelProjectile(target, chassis.occupant, params, spread) - - A.fire() - playsound(chassis, fire_sound, 50, TRUE) - - sleep(max(0, projectile_delay)) - - if(kickback) - chassis.newtonian_move(turn(chassis.dir,180)) - chassis.log_message("Fired from [src.name], targeting [target].", LOG_MECHA) - return 1 - - -//Base energy weapon type -/obj/item/mecha_parts/mecha_equipment/weapon/energy - name = "general energy weapon" - firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect/energy - -/obj/item/mecha_parts/mecha_equipment/weapon/energy/get_shot_amount() - return min(round(chassis.cell.charge / energy_drain), projectiles_per_shot) - -/obj/item/mecha_parts/mecha_equipment/weapon/energy/start_cooldown() - set_ready_state(0) - chassis.use_power(energy_drain*get_shot_amount()) - addtimer(CALLBACK(src, .proc/set_ready_state, 1), equip_cooldown) - -/obj/item/mecha_parts/mecha_equipment/weapon/energy/laser - equip_cooldown = 8 - name = "\improper CH-PS \"Immolator\" laser" - desc = "A weapon for combat exosuits. Shoots basic lasers." - icon_state = "mecha_laser" - energy_drain = 30 - projectile = /obj/projectile/beam/laser - fire_sound = 'sound/weapons/laser.ogg' - harmful = TRUE - -/obj/item/mecha_parts/mecha_equipment/weapon/energy/disabler - equip_cooldown = 8 - name = "\improper CH-DS \"Peacemaker\" disabler" - desc = "A weapon for combat exosuits. Shoots basic disablers." - icon_state = "mecha_disabler" - energy_drain = 30 - projectile = /obj/projectile/beam/disabler - fire_sound = 'sound/weapons/taser2.ogg' - -/obj/item/mecha_parts/mecha_equipment/weapon/energy/laser/heavy - equip_cooldown = 15 - name = "\improper CH-LC \"Solaris\" laser cannon" - desc = "A weapon for combat exosuits. Shoots heavy lasers." - icon_state = "mecha_laser" - energy_drain = 60 - projectile = /obj/projectile/beam/laser/heavylaser - fire_sound = 'sound/weapons/lasercannonfire.ogg' - -/obj/item/mecha_parts/mecha_equipment/weapon/energy/ion - equip_cooldown = 20 - name = "\improper MKIV ion heavy cannon" - desc = "A weapon for combat exosuits. Shoots technology-disabling ion beams. Don't catch yourself in the blast!" - icon_state = "mecha_ion" - energy_drain = 120 - projectile = /obj/projectile/ion - fire_sound = 'sound/weapons/laser.ogg' - -/obj/item/mecha_parts/mecha_equipment/weapon/energy/tesla - equip_cooldown = 35 - name = "\improper MKI Tesla Cannon" - desc = "A weapon for combat exosuits. Fires bolts of electricity similar to the experimental tesla engine." - icon_state = "mecha_ion" - energy_drain = 500 - projectile = /obj/projectile/energy/tesla/cannon - fire_sound = 'sound/magic/lightningbolt.ogg' - harmful = TRUE - -/obj/item/mecha_parts/mecha_equipment/weapon/energy/pulse - equip_cooldown = 30 - name = "eZ-13 MK2 heavy pulse rifle" - desc = "A weapon for combat exosuits. Shoots powerful destructive blasts capable of demolishing obstacles." - icon_state = "mecha_pulse" - energy_drain = 120 - projectile = /obj/projectile/beam/pulse/heavy - fire_sound = 'sound/weapons/marauder.ogg' - harmful = TRUE - -/obj/item/mecha_parts/mecha_equipment/weapon/energy/plasma - equip_cooldown = 10 - name = "217-D Heavy Plasma Cutter" - desc = "A device that shoots resonant plasma bursts at extreme velocity. The blasts are capable of crushing rock and demolishing solid obstacles." - icon_state = "mecha_plasmacutter" - item_state = "plasmacutter" - lefthand_file = 'icons/mob/inhands/weapons/guns_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/guns_righthand.dmi' - energy_drain = 30 - projectile = /obj/projectile/plasma/adv/mech - fire_sound = 'sound/weapons/plasma_cutter.ogg' - harmful = TRUE - -/obj/item/mecha_parts/mecha_equipment/weapon/energy/plasma/can_attach(obj/mecha/working/M) - if(..()) //combat mech - return 1 - else if(M.equipment.len < M.max_equip && istype(M)) - return 1 - return 0 - -/obj/item/mecha_parts/mecha_equipment/weapon/energy/taser - name = "\improper PBT \"Pacifier\" mounted taser" - desc = "A weapon for combat exosuits. Shoots non-lethal stunning electrodes." - icon_state = "mecha_taser" - energy_drain = 20 - equip_cooldown = 8 - projectile = /obj/projectile/energy/electrode - fire_sound = 'sound/weapons/taser.ogg' - - -/obj/item/mecha_parts/mecha_equipment/weapon/honker - name = "\improper HoNkER BlAsT 5000" - desc = "Equipment for clown exosuits. Spreads fun and joy to everyone around. Honk!" - icon_state = "mecha_honker" - energy_drain = 200 - equip_cooldown = 150 - range = MECHA_MELEE|MECHA_RANGED - kickback = FALSE - -/obj/item/mecha_parts/mecha_equipment/weapon/honker/can_attach(obj/mecha/combat/honker/M) - if(..()) - if(istype(M)) - return 1 - return 0 - -/obj/item/mecha_parts/mecha_equipment/weapon/honker/action(target, params) - if(!action_checks(target)) - return - playsound(chassis, 'sound/items/airhorn.ogg', 100, TRUE) - chassis.occupant_message("HONK") - for(var/mob/living/carbon/M in ohearers(6, chassis)) - if(ishuman(M)) - var/mob/living/carbon/human/H = M - if(istype(H.ears, /obj/item/clothing/ears/earmuffs)) - continue - var/turf/turf_check = get_turf(M) - if(isspaceturf(turf_check) && !turf_check.Adjacent(src)) //in space nobody can hear you honk. - continue - to_chat(M, "HONK") - M.SetSleeping(0) - M.stuttering += 20 - M.adjustEarDamage(0, 30) - M.Paralyze(60) - if(prob(30)) - M.Stun(200) - M.Unconscious(80) - else - M.Jitter(500) - - log_message("Honked from [src.name]. HONK!", LOG_MECHA) - var/turf/T = get_turf(src) - message_admins("[ADMIN_LOOKUPFLW(chassis.occupant)] used a Mecha Honker in [ADMIN_VERBOSEJMP(T)]") - log_game("[key_name(chassis.occupant)] used a Mecha Honker in [AREACOORD(T)]") - return 1 - - -//Base ballistic weapon type -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic - name = "general ballistic weapon" - fire_sound = 'sound/weapons/gun/smg/shot.ogg' - var/projectiles - var/projectiles_cache //ammo to be loaded in, if possible. - var/projectiles_cache_max - var/projectile_energy_cost - var/disabledreload //For weapons with no cache (like the rockets) which are reloaded by hand - var/ammo_type - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/get_shot_amount() - return min(projectiles, projectiles_per_shot) - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/action_checks(target) - if(!..()) - return 0 - if(projectiles <= 0) - return 0 - return 1 - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/get_equip_info() - return "[..()] \[[src.projectiles][projectiles_cache_max &&!projectile_energy_cost?"/[projectiles_cache]":""]\][!disabledreload &&(src.projectiles < initial(src.projectiles))?" - Rearm":null]" - - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/rearm() - if(projectiles < initial(projectiles)) - var/projectiles_to_add = initial(projectiles) - projectiles - - if(projectile_energy_cost) - while(chassis.get_charge() >= projectile_energy_cost && projectiles_to_add) - projectiles++ - projectiles_to_add-- - chassis.use_power(projectile_energy_cost) - - else - if(!projectiles_cache) - return FALSE - if(projectiles_to_add <= projectiles_cache) - projectiles = projectiles + projectiles_to_add - projectiles_cache = projectiles_cache - projectiles_to_add - else - projectiles = projectiles + projectiles_cache - projectiles_cache = 0 - - send_byjax(chassis.occupant,"exosuit.browser","[REF(src)]",src.get_equip_info()) - log_message("Rearmed [src.name].", LOG_MECHA) - return TRUE - - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/needs_rearm() - . = !(projectiles > 0) - - - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/Topic(href, href_list) - ..() - if (href_list["rearm"]) - src.rearm() - return - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/action(atom/target) - if(..()) - projectiles -= get_shot_amount() - send_byjax(chassis.occupant,"exosuit.browser","[REF(src)]",src.get_equip_info()) - return 1 - - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/carbine - name = "\improper FNX-99 \"Hades\" Carbine" - desc = "A weapon for combat exosuits. Shoots incendiary bullets." - icon_state = "mecha_carbine" - equip_cooldown = 10 - projectile = /obj/projectile/bullet/incendiary/fnx99 - projectiles = 24 - projectiles_cache = 24 - projectiles_cache_max = 96 - harmful = TRUE - ammo_type = "incendiary" - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/silenced - name = "\improper S.H.H. \"Quietus\" Carbine" - desc = "A weapon for combat exosuits. A mime invention, field tests have shown that targets cannot even scream before going down." - fire_sound = 'sound/weapons/gun/general/heavy_shot_suppressed.ogg' - icon_state = "mecha_mime" - equip_cooldown = 30 - projectile = /obj/projectile/bullet/mime - projectiles = 6 - projectile_energy_cost = 50 - harmful = TRUE - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/scattershot - name = "\improper LBX AC 10 \"Scattershot\"" - desc = "A weapon for combat exosuits. Shoots a spread of pellets." - icon_state = "mecha_scatter" - equip_cooldown = 20 - projectile = /obj/projectile/bullet/scattershot - projectiles = 40 - projectiles_cache = 40 - projectiles_cache_max = 160 - projectiles_per_shot = 4 - variance = 25 - harmful = TRUE - ammo_type = "scattershot" - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/lmg - name = "\improper Ultra AC 2" - desc = "A weapon for combat exosuits. Shoots a rapid, three shot burst." - icon_state = "mecha_uac2" - equip_cooldown = 10 - projectile = /obj/projectile/bullet/lmg - projectiles = 300 - projectiles_cache = 300 - projectiles_cache_max = 1200 - projectiles_per_shot = 3 - variance = 6 - randomspread = 1 - projectile_delay = 2 - harmful = TRUE - ammo_type = "lmg" - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack - name = "\improper SRM-8 missile rack" - desc = "A weapon for combat exosuits. Launches light explosive missiles." - icon_state = "mecha_missilerack" - projectile = /obj/projectile/bullet/a84mm_he - fire_sound = 'sound/weapons/gun/general/rocket_launch.ogg' - projectiles = 8 - projectiles_cache = 0 - projectiles_cache_max = 0 - disabledreload = TRUE - equip_cooldown = 60 - harmful = TRUE - ammo_type = "missiles_he" - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack/spacecops - projectiles = 420 - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack/breaching - name = "\improper BRM-6 missile rack" - desc = "A weapon for combat exosuits. Launches low-explosive breaching missiles designed to explode only when striking a sturdy target." - icon_state = "mecha_missilerack_six" - projectile = /obj/projectile/bullet/a84mm_br - fire_sound = 'sound/weapons/gun/general/rocket_launch.ogg' - projectiles = 6 - projectiles_cache = 0 - projectiles_cache_max = 0 - disabledreload = TRUE - equip_cooldown = 60 - harmful = TRUE - ammo_type = "missiles_br" - - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher - var/missile_speed = 2 - var/missile_range = 30 - var/diags_first = FALSE - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/action(target) - if(!action_checks(target)) - return - var/obj/O = new projectile(chassis.loc) - playsound(chassis, fire_sound, 50, TRUE) - log_message("Launched a [O.name] from [name], targeting [target].", LOG_MECHA) - projectiles-- - proj_init(O) - O.throw_at(target, missile_range, missile_speed, chassis.occupant, FALSE, diagonals_first = diags_first) - return 1 - -//used for projectile initilisation (priming flashbang) and additional logging -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/proc/proj_init(var/obj/O) - return - - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/flashbang - name = "\improper SGL-6 grenade launcher" - desc = "A weapon for combat exosuits. Launches primed flashbangs." - icon_state = "mecha_grenadelnchr" - projectile = /obj/item/grenade/flashbang - fire_sound = 'sound/weapons/gun/general/grenade_launch.ogg' - projectiles = 6 - projectiles_cache = 6 - projectiles_cache_max = 24 - missile_speed = 1.5 - equip_cooldown = 60 - var/det_time = 20 - ammo_type = "flashbang" - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/flashbang/proj_init(var/obj/item/grenade/flashbang/F) - var/turf/T = get_turf(src) - message_admins("[ADMIN_LOOKUPFLW(chassis.occupant)] fired a [src] in [ADMIN_VERBOSEJMP(T)]") - log_game("[key_name(chassis.occupant)] fired a [src] in [AREACOORD(T)]") - addtimer(CALLBACK(F, /obj/item/grenade/flashbang.proc/prime), det_time) - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/flashbang/clusterbang //Because I am a heartless bastard -Sieve //Heartless? for making the poor man's honkblast? - Kaze - name = "\improper SOB-3 grenade launcher" - desc = "A weapon for combat exosuits. Launches primed clusterbangs. You monster." - projectiles = 3 - projectiles_cache = 0 - projectiles_cache_max = 0 - disabledreload = TRUE - projectile = /obj/item/grenade/clusterbuster - equip_cooldown = 90 - ammo_type = "clusterbang" - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/banana_mortar - name = "banana mortar" - desc = "Equipment for clown exosuits. Launches banana peels." - icon_state = "mecha_bananamrtr" - projectile = /obj/item/grown/bananapeel - fire_sound = 'sound/items/bikehorn.ogg' - projectiles = 15 - missile_speed = 1.5 - projectile_energy_cost = 100 - equip_cooldown = 20 - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/banana_mortar/can_attach(obj/mecha/combat/honker/M) - if(..()) - if(istype(M)) - return 1 - return 0 - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/mousetrap_mortar - name = "mousetrap mortar" - desc = "Equipment for clown exosuits. Launches armed mousetraps." - icon_state = "mecha_mousetrapmrtr" - projectile = /obj/item/assembly/mousetrap/armed - fire_sound = 'sound/items/bikehorn.ogg' - projectiles = 15 - missile_speed = 1.5 - projectile_energy_cost = 100 - equip_cooldown = 10 - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/mousetrap_mortar/can_attach(obj/mecha/combat/honker/M) - if(..()) - if(istype(M)) - return 1 - return 0 - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/mousetrap_mortar/proj_init(var/obj/item/assembly/mousetrap/armed/M) - M.secured = 1 - - -//Classic extending punching glove, but weaponised! -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/punching_glove - name = "\improper Oingo Boingo Punch-face" - desc = "Equipment for clown exosuits. Delivers fun right to your face!" - icon_state = "mecha_punching_glove" - energy_drain = 250 - equip_cooldown = 20 - range = MECHA_MELEE|MECHA_RANGED - missile_range = 5 - projectile = /obj/item/punching_glove - fire_sound = 'sound/items/bikehorn.ogg' - projectiles = 10 - projectile_energy_cost = 500 - diags_first = TRUE - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/punching_glove/can_attach(obj/mecha/combat/honker/M) - if(..()) - if(istype(M)) - return 1 - return 0 - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/punching_glove/action(target) - . = ..() - if(.) - chassis.occupant_message("HONK") - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/punching_glove/proj_init(obj/item/punching_glove/PG) - if(!istype(PG)) - return - //has to be low sleep or it looks weird, the beam doesn't exist for very long so it's a non-issue - chassis.Beam(PG, icon_state = "chain", time = missile_range * 20, maxdistance = missile_range + 2, beam_sleep_time = 1) - -/obj/item/punching_glove - name = "punching glove" - desc = "INCOMING HONKS" - throwforce = 35 - icon_state = "punching_glove" - -/obj/item/punching_glove/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - if(!..()) - if(ismovable(hit_atom)) - var/atom/movable/AM = hit_atom - AM.safe_throw_at(get_edge_target_turf(AM,get_dir(src, AM)), 7, 2) - qdel(src) +/obj/item/mecha_parts/mecha_equipment/weapon + name = "mecha weapon" + range = MECHA_RANGED + destroy_sound = 'sound/mecha/weapdestr.ogg' + var/projectile + var/fire_sound + var/projectiles_per_shot = 1 + var/variance = 0 + var/randomspread = 0 //use random spread for machineguns, instead of shotgun scatter + var/projectile_delay = 0 + var/firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect //the visual effect appearing when the weapon is fired. + var/kickback = TRUE //Will using this weapon in no grav push mecha back. + +/obj/item/mecha_parts/mecha_equipment/weapon/can_attach(obj/mecha/M) + if(!..()) + return FALSE + if(istype(M, /obj/mecha/combat)) + return TRUE + if((locate(/obj/item/mecha_parts/concealed_weapon_bay) in M.contents) && !(locate(/obj/item/mecha_parts/mecha_equipment/weapon) in M.equipment)) + return TRUE + return FALSE + +/obj/item/mecha_parts/mecha_equipment/weapon/proc/get_shot_amount() + return projectiles_per_shot + +/obj/item/mecha_parts/mecha_equipment/weapon/action(atom/target, params) + if(!action_checks(target)) + return 0 + + var/turf/curloc = get_turf(chassis) + var/turf/targloc = get_turf(target) + if (!targloc || !istype(targloc) || !curloc) + return 0 + if (targloc == curloc) + return 0 + + set_ready_state(0) + for(var/i=1 to get_shot_amount()) + var/obj/projectile/A = new projectile(curloc) + A.firer = chassis.occupant + A.original = target + if(!A.suppressed && firing_effect_type) + new firing_effect_type(get_turf(src), chassis.dir) + + var/spread = 0 + if(variance) + if(randomspread) + spread = round((rand() - 0.5) * variance) + else + spread = round((i / projectiles_per_shot - 0.5) * variance) + A.preparePixelProjectile(target, chassis.occupant, params, spread) + + A.fire() + playsound(chassis, fire_sound, 50, TRUE) + + sleep(max(0, projectile_delay)) + + if(kickback) + chassis.newtonian_move(turn(chassis.dir,180)) + chassis.log_message("Fired from [src.name], targeting [target].", LOG_MECHA) + return 1 + + +//Base energy weapon type +/obj/item/mecha_parts/mecha_equipment/weapon/energy + name = "general energy weapon" + firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect/energy + +/obj/item/mecha_parts/mecha_equipment/weapon/energy/get_shot_amount() + return min(round(chassis.cell.charge / energy_drain), projectiles_per_shot) + +/obj/item/mecha_parts/mecha_equipment/weapon/energy/start_cooldown() + set_ready_state(0) + chassis.use_power(energy_drain*get_shot_amount()) + addtimer(CALLBACK(src, .proc/set_ready_state, 1), equip_cooldown) + +/obj/item/mecha_parts/mecha_equipment/weapon/energy/laser + equip_cooldown = 8 + name = "\improper CH-PS \"Immolator\" laser" + desc = "A weapon for combat exosuits. Shoots basic lasers." + icon_state = "mecha_laser" + energy_drain = 30 + projectile = /obj/projectile/beam/laser + fire_sound = 'sound/weapons/laser.ogg' + harmful = TRUE + +/obj/item/mecha_parts/mecha_equipment/weapon/energy/disabler + equip_cooldown = 8 + name = "\improper CH-DS \"Peacemaker\" disabler" + desc = "A weapon for combat exosuits. Shoots basic disablers." + icon_state = "mecha_disabler" + energy_drain = 30 + projectile = /obj/projectile/beam/disabler + fire_sound = 'sound/weapons/taser2.ogg' + +/obj/item/mecha_parts/mecha_equipment/weapon/energy/laser/heavy + equip_cooldown = 15 + name = "\improper CH-LC \"Solaris\" laser cannon" + desc = "A weapon for combat exosuits. Shoots heavy lasers." + icon_state = "mecha_laser" + energy_drain = 60 + projectile = /obj/projectile/beam/laser/heavylaser + fire_sound = 'sound/weapons/lasercannonfire.ogg' + +/obj/item/mecha_parts/mecha_equipment/weapon/energy/ion + equip_cooldown = 20 + name = "\improper MKIV ion heavy cannon" + desc = "A weapon for combat exosuits. Shoots technology-disabling ion beams. Don't catch yourself in the blast!" + icon_state = "mecha_ion" + energy_drain = 120 + projectile = /obj/projectile/ion + fire_sound = 'sound/weapons/laser.ogg' + +/obj/item/mecha_parts/mecha_equipment/weapon/energy/tesla + equip_cooldown = 35 + name = "\improper MKI Tesla Cannon" + desc = "A weapon for combat exosuits. Fires bolts of electricity similar to the experimental tesla engine." + icon_state = "mecha_ion" + energy_drain = 500 + projectile = /obj/projectile/energy/tesla/cannon + fire_sound = 'sound/magic/lightningbolt.ogg' + harmful = TRUE + +/obj/item/mecha_parts/mecha_equipment/weapon/energy/pulse + equip_cooldown = 30 + name = "eZ-13 MK2 heavy pulse rifle" + desc = "A weapon for combat exosuits. Shoots powerful destructive blasts capable of demolishing obstacles." + icon_state = "mecha_pulse" + energy_drain = 120 + projectile = /obj/projectile/beam/pulse/heavy + fire_sound = 'sound/weapons/marauder.ogg' + harmful = TRUE + +/obj/item/mecha_parts/mecha_equipment/weapon/energy/plasma + equip_cooldown = 10 + name = "217-D Heavy Plasma Cutter" + desc = "A device that shoots resonant plasma bursts at extreme velocity. The blasts are capable of crushing rock and demolishing solid obstacles." + icon_state = "mecha_plasmacutter" + item_state = "plasmacutter" + lefthand_file = 'icons/mob/inhands/weapons/guns_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/guns_righthand.dmi' + energy_drain = 30 + projectile = /obj/projectile/plasma/adv/mech + fire_sound = 'sound/weapons/plasma_cutter.ogg' + harmful = TRUE + +/obj/item/mecha_parts/mecha_equipment/weapon/energy/plasma/can_attach(obj/mecha/working/M) + if(..()) //combat mech + return 1 + else if(M.equipment.len < M.max_equip && istype(M)) + return 1 + return 0 + +/obj/item/mecha_parts/mecha_equipment/weapon/energy/taser + name = "\improper PBT \"Pacifier\" mounted taser" + desc = "A weapon for combat exosuits. Shoots non-lethal stunning electrodes." + icon_state = "mecha_taser" + energy_drain = 20 + equip_cooldown = 8 + projectile = /obj/projectile/energy/electrode + fire_sound = 'sound/weapons/taser.ogg' + + +/obj/item/mecha_parts/mecha_equipment/weapon/honker + name = "\improper HoNkER BlAsT 5000" + desc = "Equipment for clown exosuits. Spreads fun and joy to everyone around. Honk!" + icon_state = "mecha_honker" + energy_drain = 200 + equip_cooldown = 150 + range = MECHA_MELEE|MECHA_RANGED + kickback = FALSE + +/obj/item/mecha_parts/mecha_equipment/weapon/honker/can_attach(obj/mecha/combat/honker/M) + if(..()) + if(istype(M)) + return 1 + return 0 + +/obj/item/mecha_parts/mecha_equipment/weapon/honker/action(target, params) + if(!action_checks(target)) + return + playsound(chassis, 'sound/items/airhorn.ogg', 100, TRUE) + chassis.occupant_message("HONK") + for(var/mob/living/carbon/M in ohearers(6, chassis)) + if(ishuman(M)) + var/mob/living/carbon/human/H = M + if(istype(H.ears, /obj/item/clothing/ears/earmuffs)) + continue + var/turf/turf_check = get_turf(M) + if(isspaceturf(turf_check) && !turf_check.Adjacent(src)) //in space nobody can hear you honk. + continue + to_chat(M, "HONK") + M.SetSleeping(0) + M.stuttering += 20 + M.adjustEarDamage(0, 30) + M.Paralyze(60) + if(prob(30)) + M.Stun(200) + M.Unconscious(80) + else + M.Jitter(500) + + log_message("Honked from [src.name]. HONK!", LOG_MECHA) + var/turf/T = get_turf(src) + message_admins("[ADMIN_LOOKUPFLW(chassis.occupant)] used a Mecha Honker in [ADMIN_VERBOSEJMP(T)]") + log_game("[key_name(chassis.occupant)] used a Mecha Honker in [AREACOORD(T)]") + return 1 + + +//Base ballistic weapon type +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic + name = "general ballistic weapon" + fire_sound = 'sound/weapons/gun/smg/shot.ogg' + var/projectiles + var/projectiles_cache //ammo to be loaded in, if possible. + var/projectiles_cache_max + var/projectile_energy_cost + var/disabledreload //For weapons with no cache (like the rockets) which are reloaded by hand + var/ammo_type + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/get_shot_amount() + return min(projectiles, projectiles_per_shot) + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/action_checks(target) + if(!..()) + return 0 + if(projectiles <= 0) + return 0 + return 1 + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/get_equip_info() + return "[..()] \[[src.projectiles][projectiles_cache_max &&!projectile_energy_cost?"/[projectiles_cache]":""]\][!disabledreload &&(src.projectiles < initial(src.projectiles))?" - Rearm":null]" + + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/rearm() + if(projectiles < initial(projectiles)) + var/projectiles_to_add = initial(projectiles) - projectiles + + if(projectile_energy_cost) + while(chassis.get_charge() >= projectile_energy_cost && projectiles_to_add) + projectiles++ + projectiles_to_add-- + chassis.use_power(projectile_energy_cost) + + else + if(!projectiles_cache) + return FALSE + if(projectiles_to_add <= projectiles_cache) + projectiles = projectiles + projectiles_to_add + projectiles_cache = projectiles_cache - projectiles_to_add + else + projectiles = projectiles + projectiles_cache + projectiles_cache = 0 + + send_byjax(chassis.occupant,"exosuit.browser","[REF(src)]",src.get_equip_info()) + log_message("Rearmed [src.name].", LOG_MECHA) + return TRUE + + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/needs_rearm() + . = !(projectiles > 0) + + + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/Topic(href, href_list) + ..() + if (href_list["rearm"]) + src.rearm() + return + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/action(atom/target) + if(..()) + projectiles -= get_shot_amount() + send_byjax(chassis.occupant,"exosuit.browser","[REF(src)]",src.get_equip_info()) + return 1 + + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/carbine + name = "\improper FNX-99 \"Hades\" Carbine" + desc = "A weapon for combat exosuits. Shoots incendiary bullets." + icon_state = "mecha_carbine" + equip_cooldown = 10 + projectile = /obj/projectile/bullet/incendiary/fnx99 + projectiles = 24 + projectiles_cache = 24 + projectiles_cache_max = 96 + harmful = TRUE + ammo_type = "incendiary" + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/silenced + name = "\improper S.H.H. \"Quietus\" Carbine" + desc = "A weapon for combat exosuits. A mime invention, field tests have shown that targets cannot even scream before going down." + fire_sound = 'sound/weapons/gun/general/heavy_shot_suppressed.ogg' + icon_state = "mecha_mime" + equip_cooldown = 30 + projectile = /obj/projectile/bullet/mime + projectiles = 6 + projectile_energy_cost = 50 + harmful = TRUE + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/scattershot + name = "\improper LBX AC 10 \"Scattershot\"" + desc = "A weapon for combat exosuits. Shoots a spread of pellets." + icon_state = "mecha_scatter" + equip_cooldown = 20 + projectile = /obj/projectile/bullet/scattershot + projectiles = 40 + projectiles_cache = 40 + projectiles_cache_max = 160 + projectiles_per_shot = 4 + variance = 25 + harmful = TRUE + ammo_type = "scattershot" + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/lmg + name = "\improper Ultra AC 2" + desc = "A weapon for combat exosuits. Shoots a rapid, three shot burst." + icon_state = "mecha_uac2" + equip_cooldown = 10 + projectile = /obj/projectile/bullet/lmg + projectiles = 300 + projectiles_cache = 300 + projectiles_cache_max = 1200 + projectiles_per_shot = 3 + variance = 6 + randomspread = 1 + projectile_delay = 2 + harmful = TRUE + ammo_type = "lmg" + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack + name = "\improper SRM-8 missile rack" + desc = "A weapon for combat exosuits. Launches light explosive missiles." + icon_state = "mecha_missilerack" + projectile = /obj/projectile/bullet/a84mm_he + fire_sound = 'sound/weapons/gun/general/rocket_launch.ogg' + projectiles = 8 + projectiles_cache = 0 + projectiles_cache_max = 0 + disabledreload = TRUE + equip_cooldown = 60 + harmful = TRUE + ammo_type = "missiles_he" + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack/spacecops + projectiles = 420 + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack/breaching + name = "\improper BRM-6 missile rack" + desc = "A weapon for combat exosuits. Launches low-explosive breaching missiles designed to explode only when striking a sturdy target." + icon_state = "mecha_missilerack_six" + projectile = /obj/projectile/bullet/a84mm_br + fire_sound = 'sound/weapons/gun/general/rocket_launch.ogg' + projectiles = 6 + projectiles_cache = 0 + projectiles_cache_max = 0 + disabledreload = TRUE + equip_cooldown = 60 + harmful = TRUE + ammo_type = "missiles_br" + + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher + var/missile_speed = 2 + var/missile_range = 30 + var/diags_first = FALSE + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/action(target) + if(!action_checks(target)) + return + var/obj/O = new projectile(chassis.loc) + playsound(chassis, fire_sound, 50, TRUE) + log_message("Launched a [O.name] from [name], targeting [target].", LOG_MECHA) + projectiles-- + proj_init(O) + O.throw_at(target, missile_range, missile_speed, chassis.occupant, FALSE, diagonals_first = diags_first) + return 1 + +//used for projectile initilisation (priming flashbang) and additional logging +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/proc/proj_init(var/obj/O) + return + + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/flashbang + name = "\improper SGL-6 grenade launcher" + desc = "A weapon for combat exosuits. Launches primed flashbangs." + icon_state = "mecha_grenadelnchr" + projectile = /obj/item/grenade/flashbang + fire_sound = 'sound/weapons/gun/general/grenade_launch.ogg' + projectiles = 6 + projectiles_cache = 6 + projectiles_cache_max = 24 + missile_speed = 1.5 + equip_cooldown = 60 + var/det_time = 20 + ammo_type = "flashbang" + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/flashbang/proj_init(var/obj/item/grenade/flashbang/F) + var/turf/T = get_turf(src) + message_admins("[ADMIN_LOOKUPFLW(chassis.occupant)] fired a [src] in [ADMIN_VERBOSEJMP(T)]") + log_game("[key_name(chassis.occupant)] fired a [src] in [AREACOORD(T)]") + addtimer(CALLBACK(F, /obj/item/grenade/flashbang.proc/prime), det_time) + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/flashbang/clusterbang //Because I am a heartless bastard -Sieve //Heartless? for making the poor man's honkblast? - Kaze + name = "\improper SOB-3 grenade launcher" + desc = "A weapon for combat exosuits. Launches primed clusterbangs. You monster." + projectiles = 3 + projectiles_cache = 0 + projectiles_cache_max = 0 + disabledreload = TRUE + projectile = /obj/item/grenade/clusterbuster + equip_cooldown = 90 + ammo_type = "clusterbang" + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/banana_mortar + name = "banana mortar" + desc = "Equipment for clown exosuits. Launches banana peels." + icon_state = "mecha_bananamrtr" + projectile = /obj/item/grown/bananapeel + fire_sound = 'sound/items/bikehorn.ogg' + projectiles = 15 + missile_speed = 1.5 + projectile_energy_cost = 100 + equip_cooldown = 20 + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/banana_mortar/can_attach(obj/mecha/combat/honker/M) + if(..()) + if(istype(M)) + return 1 + return 0 + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/mousetrap_mortar + name = "mousetrap mortar" + desc = "Equipment for clown exosuits. Launches armed mousetraps." + icon_state = "mecha_mousetrapmrtr" + projectile = /obj/item/assembly/mousetrap/armed + fire_sound = 'sound/items/bikehorn.ogg' + projectiles = 15 + missile_speed = 1.5 + projectile_energy_cost = 100 + equip_cooldown = 10 + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/mousetrap_mortar/can_attach(obj/mecha/combat/honker/M) + if(..()) + if(istype(M)) + return 1 + return 0 + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/mousetrap_mortar/proj_init(var/obj/item/assembly/mousetrap/armed/M) + M.secured = 1 + + +//Classic extending punching glove, but weaponised! +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/punching_glove + name = "\improper Oingo Boingo Punch-face" + desc = "Equipment for clown exosuits. Delivers fun right to your face!" + icon_state = "mecha_punching_glove" + energy_drain = 250 + equip_cooldown = 20 + range = MECHA_MELEE|MECHA_RANGED + missile_range = 5 + projectile = /obj/item/punching_glove + fire_sound = 'sound/items/bikehorn.ogg' + projectiles = 10 + projectile_energy_cost = 500 + diags_first = TRUE + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/punching_glove/can_attach(obj/mecha/combat/honker/M) + if(..()) + if(istype(M)) + return 1 + return 0 + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/punching_glove/action(target) + . = ..() + if(.) + chassis.occupant_message("HONK") + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/punching_glove/proj_init(obj/item/punching_glove/PG) + if(!istype(PG)) + return + //has to be low sleep or it looks weird, the beam doesn't exist for very long so it's a non-issue + chassis.Beam(PG, icon_state = "chain", time = missile_range * 20, maxdistance = missile_range + 2, beam_sleep_time = 1) + +/obj/item/punching_glove + name = "punching glove" + desc = "INCOMING HONKS" + throwforce = 35 + icon_state = "punching_glove" + +/obj/item/punching_glove/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + if(!..()) + if(ismovable(hit_atom)) + var/atom/movable/AM = hit_atom + AM.safe_throw_at(get_edge_target_turf(AM,get_dir(src, AM)), 7, 2) + qdel(src) diff --git a/code/game/mecha/mech_bay.dm b/code/game/mecha/mech_bay.dm index 36f17f281c10..8803d1980772 100644 --- a/code/game/mecha/mech_bay.dm +++ b/code/game/mecha/mech_bay.dm @@ -86,15 +86,13 @@ icon_screen = "recharge_comp" icon_keyboard = "rd_key" circuit = /obj/item/circuitboard/computer/mech_bay_power_console - ui_x = 400 - ui_y = 200 var/obj/machinery/mech_bay_recharge_port/recharge_port light_color = LIGHT_COLOR_PINK -/obj/machinery/computer/mech_bay_power_console/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/machinery/computer/mech_bay_power_console/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "MechBayPowerConsole", "Mech Bay Power Control Console", ui_x, ui_y, master_ui, state) + ui = new(user, src, "MechBayPowerConsole", name) ui.open() /obj/machinery/computer/mech_bay_power_console/ui_act(action, params) diff --git a/code/game/mecha/mech_fabricator.dm b/code/game/mecha/mech_fabricator.dm index b08cfb2aecb5..a8a0d4b867b4 100644 --- a/code/game/mecha/mech_fabricator.dm +++ b/code/game/mecha/mech_fabricator.dm @@ -1,488 +1,488 @@ -/obj/machinery/mecha_part_fabricator - icon = 'icons/obj/robotics.dmi' - icon_state = "fab-idle" - name = "exosuit fabricator" - desc = "Nothing is being built." - density = TRUE - use_power = IDLE_POWER_USE - idle_power_usage = 20 - active_power_usage = 5000 - req_access = list(ACCESS_ROBOTICS) - circuit = /obj/item/circuitboard/machine/mechfab - var/time_coeff = 1 - var/component_coeff = 1 - var/datum/techweb/specialized/autounlocking/exofab/stored_research - var/sync = 0 - var/part_set - var/datum/design/being_built - var/list/queue = list() - var/list/datum/design/matching_designs - var/processing_queue = 0 - var/screen = "main" - var/link_on_init = TRUE - var/temp - var/datum/component/remote_materials/rmat - var/list/part_sets = list( - "Cyborg", - "Ripley", - "Firefighter", - "Odysseus", - "Gygax", - "Durand", - "H.O.N.K", - "Phazon", - "Exosuit Equipment", - "Exosuit Ammunition", - "Cyborg Upgrade Modules", - "IPC components", - "Misc" - ) - -/obj/machinery/mecha_part_fabricator/Initialize(mapload) - stored_research = new - matching_designs = list() - rmat = AddComponent(/datum/component/remote_materials, "mechfab", mapload && link_on_init) - RefreshParts() //Recalculating local material sizes if the fab isn't linked - return ..() - -/obj/machinery/mecha_part_fabricator/RefreshParts() - var/T = 0 - - //maximum stocking amount (default 300000, 600000 at T4) - for(var/obj/item/stock_parts/matter_bin/M in component_parts) - T += M.rating - rmat.set_local_size((200000 + (T*50000))) - - //resources adjustment coefficient (1 -> 0.85 -> 0.7 -> 0.55) - T = 1.15 - for(var/obj/item/stock_parts/micro_laser/Ma in component_parts) - T -= Ma.rating*0.15 - component_coeff = T - - //building time adjustment coefficient (1 -> 0.8 -> 0.6) - T = -1 - for(var/obj/item/stock_parts/manipulator/Ml in component_parts) - T += Ml.rating - time_coeff = round(initial(time_coeff) - (initial(time_coeff)*(T))/5,0.01) - -/obj/machinery/mecha_part_fabricator/examine(mob/user) - . = ..() - if(in_range(user, src) || isobserver(user)) - . += "The status display reads: Storing up to [rmat.local_size] material units.
                    Material consumption at [component_coeff*100]%.
                    Build time reduced by [100-time_coeff*100]%.
                    " - -/obj/machinery/mecha_part_fabricator/emag_act() - if(obj_flags & EMAGGED) - return - obj_flags |= EMAGGED - req_access = list() - say("DB error \[Code 0x00F1\]") - sleep(10) - say("Attempting auto-repair...") - sleep(15) - say("User DB corrupted \[Code 0x00FA\]. Truncating data structure...") - sleep(30) - say("User DB truncated. Please contact your Nanotrasen system operator for future assistance.") - -/obj/machinery/mecha_part_fabricator/proc/output_parts_list(set_name) - var/output = "" - for(var/v in stored_research.researched_designs) - var/datum/design/D = SSresearch.techweb_design_by_id(v) - if(D.build_type & MECHFAB) - if(!(set_name in D.category)) - continue - output += "
                    [output_part_info(D)]
                    " - if(check_resources(D)) - output += "Build | " - output += "Add to queue?
                    " - return output - -/obj/machinery/mecha_part_fabricator/proc/output_part_info(datum/design/D) - var/output = "[initial(D.name)] (Cost: [output_part_cost(D)]) [get_construction_time_w_coeff(D)/10]sec" - return output - -/obj/machinery/mecha_part_fabricator/proc/output_part_cost(datum/design/D) - var/i = 0 - var/output - for(var/c in D.materials) - var/datum/material/M = c - output += "[i?" | ":null][get_resource_cost_w_coeff(D, M)] [M.name]" - i++ - return output - -/obj/machinery/mecha_part_fabricator/proc/output_ui_header() - var/output - output += "
                    Mecha Fabricator
                    " - output += "Security protocols: [(obj_flags & EMAGGED)? "Disabled" : "Enabled"]
                    " - if (rmat.mat_container) - output += "Material Amount: [rmat.format_amount()]" - else - output += "No material storage connected, please contact the quartermaster." - output += "
                    Sync with R&D servers
                    " - output += "Main Screen" - output += "
                    " - output += "
                    \ - \ - \ - \ - \ -

                    " - return output - -/obj/machinery/mecha_part_fabricator/proc/get_resources_w_coeff(datum/design/D) - var/list/resources = list() - for(var/R in D.materials) - var/datum/material/M = R - resources[M] = get_resource_cost_w_coeff(D, M) - return resources - -/obj/machinery/mecha_part_fabricator/proc/check_resources(datum/design/D) - if(D.reagents_list.len) // No reagents storage - no reagent designs. - return FALSE - var/datum/component/material_container/materials = rmat.mat_container - if(materials.has_materials(get_resources_w_coeff(D))) - return TRUE - return FALSE - -/obj/machinery/mecha_part_fabricator/proc/output_ui_materials() - var/output - output += "

                    Material Storage:

                    " - for(var/mat_id in rmat.mat_container.materials) - var/datum/material/M = mat_id - var/amount = rmat.mat_container.materials[mat_id] - var/ref = REF(M) - output += "* [amount] of [M.name]: " - if(amount >= MINERAL_MATERIAL_AMOUNT) output += "Eject" - if(amount >= MINERAL_MATERIAL_AMOUNT*5) output += "5x" - if(amount >= MINERAL_MATERIAL_AMOUNT) output += "All" - output += "
                    " - output += "
                    " - return output - -/obj/machinery/mecha_part_fabricator/proc/search(string) - matching_designs.Cut() - for(var/v in stored_research.researched_designs) - var/datum/design/D = SSresearch.techweb_design_by_id(v) - if(!(D.build_type & MECHFAB)) - continue - if(findtext(D.name,string)) - matching_designs.Add(D) - -/obj/machinery/mecha_part_fabricator/proc/output_ui_search() - var/output - output += "

                    Search Results:

                    " - for(var/datum/design/D in matching_designs) - output += "
                    [output_part_info(D)]
                    " - if(check_resources(D)) - output += "Build | " - output += "Add to queue?
                    " - return output - -/obj/machinery/mecha_part_fabricator/proc/build_part(datum/design/D) - var/list/res_coef = get_resources_w_coeff(D) - - var/datum/component/material_container/materials = rmat.mat_container - if (!materials) - say("No access to material storage, please contact the quartermaster.") - return FALSE - if (rmat.on_hold()) - say("Mineral access is on hold, please contact the quartermaster.") - return FALSE - if(!check_resources(D)) - say("Not enough resources. Queue processing stopped.") - return FALSE - being_built = D - desc = "It's building \a [initial(D.name)]." - materials.use_materials(res_coef) - rmat.silo_log(src, "built", -1, "[D.name]", res_coef) - - add_overlay("fab-active") - use_power = ACTIVE_POWER_USE - updateUsrDialog() - sleep(get_construction_time_w_coeff(D)) - use_power = IDLE_POWER_USE - cut_overlay("fab-active") - desc = initial(desc) - - var/location = get_step(src,(dir)) - var/obj/item/I = new D.build_path(location) - I.material_flags |= MATERIAL_NO_EFFECTS //Find a better way to do this. - I.set_custom_materials(res_coef) - say("\The [I] is complete.") - being_built = null - - updateUsrDialog() - return TRUE - -/obj/machinery/mecha_part_fabricator/proc/update_queue_on_page() - send_byjax(usr,"mecha_fabricator.browser","queue",list_queue()) - return - -/obj/machinery/mecha_part_fabricator/proc/add_part_set_to_queue(set_name) - if(set_name in part_sets) - for(var/v in stored_research.researched_designs) - var/datum/design/D = SSresearch.techweb_design_by_id(v) - if(D.build_type & MECHFAB) - if(set_name in D.category) - add_to_queue(D) - -/obj/machinery/mecha_part_fabricator/proc/add_to_queue(D) - if(!istype(queue)) - queue = list() - if(D) - queue[++queue.len] = D - return queue.len - -/obj/machinery/mecha_part_fabricator/proc/remove_from_queue(index) - if(!isnum(index) || !ISINTEGER(index) || !istype(queue) || (index<1 || index>queue.len)) - return FALSE - queue.Cut(index,++index) - return TRUE - -/obj/machinery/mecha_part_fabricator/proc/process_queue() - var/datum/design/D = queue[1] - if(!D) - remove_from_queue(1) - if(queue.len) - return process_queue() - else - return - temp = null - while(D) - if(machine_stat&(NOPOWER|BROKEN)) - return FALSE - if(build_part(D)) - remove_from_queue(1) - else - return FALSE - D = LAZYACCESS(queue, 1) - say("Queue processing finished successfully.") - -/obj/machinery/mecha_part_fabricator/proc/list_queue() - var/output = "Queue contains:" - if(!istype(queue) || !queue.len) - output += "
                    Nothing" - else - output += "
                      " - var/i = 0 - for(var/datum/design/D in queue) - i++ - var/obj/part = D.build_path - output += "" - output += initial(part.name) + " - " - output += "[i>1?"":null] " - output += "[i↓":null] " - output += "Remove" - - output += "
                    " - output += "Process queue | Clear queue" - return output - -/obj/machinery/mecha_part_fabricator/proc/sync() - for(var/obj/machinery/computer/rdconsole/RDC in oview(7,src)) - RDC.stored_research.copy_research_to(stored_research) - updateUsrDialog() - say("Successfully synchronized with R&D server.") - return - - temp = "Unable to connect to local R&D Database.
                    Please check your connections and try again.
                    Return" - updateUsrDialog() - return - -/obj/machinery/mecha_part_fabricator/proc/get_resource_cost_w_coeff(datum/design/D, var/datum/material/resource, roundto = 1) - return round(D.materials[resource]*component_coeff, roundto) - -/obj/machinery/mecha_part_fabricator/proc/get_construction_time_w_coeff(datum/design/D, roundto = 1) //aran - return round(initial(D.construction_time)*time_coeff, roundto) - -/obj/machinery/mecha_part_fabricator/ui_interact(mob/user as mob) - . = ..() - var/dat, left_part - user.set_machine(src) - var/turf/exit = get_step(src,(dir)) - if(exit.density) - say("Error! Part outlet is obstructed.") - return - if(temp) - left_part = temp - else if(being_built) - var/obj/I = being_built.build_path - left_part = {"
                    Building [initial(I.name)].
                    - Please wait until completion...
                    "} - else - left_part = output_ui_header() - switch(screen) - if("main") - for(var/part_set in part_sets) - left_part += "[part_set] - Add all parts to queue
                    " - if("parts") - left_part += output_parts_list(part_set) - if("resources") - left_part += output_ui_materials() - if("search") - left_part += output_ui_search() - dat = {" - - - [name] - - - - - - - - - -
                    - [left_part] - - [list_queue()] -
                    - - "} - - var/datum/browser/popup = new(user, "mecha_fabricator", name, 1000, 430) - popup.set_content(dat) - popup.open() - //user << browse(dat, "window=mecha_fabricator;size=1000x430") - //onclose(user, "mecha_fabricator") - return - -/obj/machinery/mecha_part_fabricator/Topic(href, href_list) - if(..()) - return - if(href_list["part_set"]) - var/tpart_set = href_list["part_set"] - if(tpart_set) - if(tpart_set=="clear") - part_set = null - else - part_set = tpart_set - screen = "parts" - if(href_list["part"]) - var/T = href_list["part"] - for(var/v in stored_research.researched_designs) - var/datum/design/D = SSresearch.techweb_design_by_id(v) - if(D.build_type & MECHFAB) - if(D.id == T) - if(!processing_queue) - build_part(D) - else - add_to_queue(D) - break - if(href_list["add_to_queue"]) - var/T = href_list["add_to_queue"] - for(var/v in stored_research.researched_designs) - var/datum/design/D = SSresearch.techweb_design_by_id(v) - if(D.build_type & MECHFAB) - if(D.id == T) - add_to_queue(D) - break - return update_queue_on_page() - if(href_list["remove_from_queue"]) - remove_from_queue(text2num(href_list["remove_from_queue"])) - return update_queue_on_page() - if(href_list["partset_to_queue"]) - add_part_set_to_queue(href_list["partset_to_queue"]) - return update_queue_on_page() - if(href_list["process_queue"]) - INVOKE_ASYNC(src, .proc/do_process_queue) - if(href_list["clear_temp"]) - temp = null - if(href_list["screen"]) - screen = href_list["screen"] - if(href_list["queue_move"] && href_list["index"]) - var/index = text2num(href_list["index"]) - var/new_index = index + text2num(href_list["queue_move"]) - if(isnum(index) && isnum(new_index) && ISINTEGER(index) && ISINTEGER(new_index)) - if(ISINRANGE(new_index,1,queue.len)) - queue.Swap(index,new_index) - return update_queue_on_page() - if(href_list["clear_queue"]) - queue = list() - return update_queue_on_page() - if(href_list["sync"]) - sync() - if(href_list["part_desc"]) - var/T = href_list["part_desc"] - for(var/v in stored_research.researched_designs) - var/datum/design/D = SSresearch.techweb_design_by_id(v) - if(D.build_type & MECHFAB) - if(D.id == T) - var/obj/part = D.build_path - temp = {"

                    [initial(part.name)] description:

                    - [initial(part.desc)]
                    - Return - "} - break - if(href_list["search"]) //Search for designs with name matching pattern - search(href_list["to_search"]) - screen = "search" - - if(href_list["remove_mat"] && href_list["material"]) - var/datum/material/Mat = locate(href_list["material"]) - eject_sheets(Mat, text2num(href_list["remove_mat"])) - - updateUsrDialog() - return - -/obj/machinery/mecha_part_fabricator/proc/do_process_queue() - if(processing_queue || being_built) - return FALSE - processing_queue = 1 - process_queue() - processing_queue = 0 - -/obj/machinery/mecha_part_fabricator/proc/eject_sheets(eject_sheet, eject_amt) - var/datum/component/material_container/mat_container = rmat.mat_container - if (!mat_container) - say("No access to material storage, please contact the quartermaster.") - return 0 - if (rmat.on_hold()) - say("Mineral access is on hold, please contact the quartermaster.") - return 0 - var/count = mat_container.retrieve_sheets(text2num(eject_amt), eject_sheet, drop_location()) - var/list/matlist = list() - matlist[eject_sheet] = text2num(eject_amt) - rmat.silo_log(src, "ejected", -count, "sheets", matlist) - return count - -/obj/machinery/mecha_part_fabricator/proc/AfterMaterialInsert(item_inserted, id_inserted, amount_inserted) - var/datum/material/M = id_inserted - add_overlay("fab-load-[M.name]") - addtimer(CALLBACK(src, /atom/proc/cut_overlay, "fab-load-[M.name]"), 10) - updateUsrDialog() - -/obj/machinery/mecha_part_fabricator/attackby(obj/item/W, mob/user, params) - if(default_deconstruction_screwdriver(user, "fab-o", "fab-idle", W)) - return TRUE - - if(default_deconstruction_crowbar(W)) - return TRUE - - return ..() - - -/obj/machinery/mecha_part_fabricator/proc/is_insertion_ready(mob/user) - if(panel_open) - to_chat(user, "You can't load [src] while it's opened!") - return FALSE - if(being_built) - to_chat(user, "\The [src] is currently processing! Please wait until completion.") - return FALSE - - return TRUE - -/obj/machinery/mecha_part_fabricator/maint - link_on_init = FALSE +/obj/machinery/mecha_part_fabricator + icon = 'icons/obj/robotics.dmi' + icon_state = "fab-idle" + name = "exosuit fabricator" + desc = "Nothing is being built." + density = TRUE + use_power = IDLE_POWER_USE + idle_power_usage = 20 + active_power_usage = 5000 + req_access = list(ACCESS_ROBOTICS) + circuit = /obj/item/circuitboard/machine/mechfab + var/time_coeff = 1 + var/component_coeff = 1 + var/datum/techweb/specialized/autounlocking/exofab/stored_research + var/sync = 0 + var/part_set + var/datum/design/being_built + var/list/queue = list() + var/list/datum/design/matching_designs + var/processing_queue = 0 + var/screen = "main" + var/link_on_init = TRUE + var/temp + var/datum/component/remote_materials/rmat + var/list/part_sets = list( + "Cyborg", + "Ripley", + "Firefighter", + "Odysseus", + "Gygax", + "Durand", + "H.O.N.K", + "Phazon", + "Exosuit Equipment", + "Exosuit Ammunition", + "Cyborg Upgrade Modules", + "IPC components", + "Misc" + ) + +/obj/machinery/mecha_part_fabricator/Initialize(mapload) + stored_research = new + matching_designs = list() + rmat = AddComponent(/datum/component/remote_materials, "mechfab", mapload && link_on_init) + RefreshParts() //Recalculating local material sizes if the fab isn't linked + return ..() + +/obj/machinery/mecha_part_fabricator/RefreshParts() + var/T = 0 + + //maximum stocking amount (default 300000, 600000 at T4) + for(var/obj/item/stock_parts/matter_bin/M in component_parts) + T += M.rating + rmat.set_local_size((200000 + (T*50000))) + + //resources adjustment coefficient (1 -> 0.85 -> 0.7 -> 0.55) + T = 1.15 + for(var/obj/item/stock_parts/micro_laser/Ma in component_parts) + T -= Ma.rating*0.15 + component_coeff = T + + //building time adjustment coefficient (1 -> 0.8 -> 0.6) + T = -1 + for(var/obj/item/stock_parts/manipulator/Ml in component_parts) + T += Ml.rating + time_coeff = round(initial(time_coeff) - (initial(time_coeff)*(T))/5,0.01) + +/obj/machinery/mecha_part_fabricator/examine(mob/user) + . = ..() + if(in_range(user, src) || isobserver(user)) + . += "The status display reads: Storing up to [rmat.local_size] material units.
                    Material consumption at [component_coeff*100]%.
                    Build time reduced by [100-time_coeff*100]%.
                    " + +/obj/machinery/mecha_part_fabricator/emag_act() + if(obj_flags & EMAGGED) + return + obj_flags |= EMAGGED + req_access = list() + say("DB error \[Code 0x00F1\]") + sleep(10) + say("Attempting auto-repair...") + sleep(15) + say("User DB corrupted \[Code 0x00FA\]. Truncating data structure...") + sleep(30) + say("User DB truncated. Please contact your Nanotrasen system operator for future assistance.") + +/obj/machinery/mecha_part_fabricator/proc/output_parts_list(set_name) + var/output = "" + for(var/v in stored_research.researched_designs) + var/datum/design/D = SSresearch.techweb_design_by_id(v) + if(D.build_type & MECHFAB) + if(!(set_name in D.category)) + continue + output += "
                    [output_part_info(D)]
                    " + if(check_resources(D)) + output += "Build | " + output += "Add to queue?
                    " + return output + +/obj/machinery/mecha_part_fabricator/proc/output_part_info(datum/design/D) + var/output = "[initial(D.name)] (Cost: [output_part_cost(D)]) [get_construction_time_w_coeff(D)/10]sec" + return output + +/obj/machinery/mecha_part_fabricator/proc/output_part_cost(datum/design/D) + var/i = 0 + var/output + for(var/c in D.materials) + var/datum/material/M = c + output += "[i?" | ":null][get_resource_cost_w_coeff(D, M)] [M.name]" + i++ + return output + +/obj/machinery/mecha_part_fabricator/proc/output_ui_header() + var/output + output += "
                    Mecha Fabricator
                    " + output += "Security protocols: [(obj_flags & EMAGGED)? "Disabled" : "Enabled"]
                    " + if (rmat.mat_container) + output += "Material Amount: [rmat.format_amount()]" + else + output += "No material storage connected, please contact the quartermaster." + output += "
                    Sync with R&D servers
                    " + output += "Main Screen" + output += "
                    " + output += "
                    \ + \ + \ + \ + \ +

                    " + return output + +/obj/machinery/mecha_part_fabricator/proc/get_resources_w_coeff(datum/design/D) + var/list/resources = list() + for(var/R in D.materials) + var/datum/material/M = R + resources[M] = get_resource_cost_w_coeff(D, M) + return resources + +/obj/machinery/mecha_part_fabricator/proc/check_resources(datum/design/D) + if(D.reagents_list.len) // No reagents storage - no reagent designs. + return FALSE + var/datum/component/material_container/materials = rmat.mat_container + if(materials.has_materials(get_resources_w_coeff(D))) + return TRUE + return FALSE + +/obj/machinery/mecha_part_fabricator/proc/output_ui_materials() + var/output + output += "

                    Material Storage:

                    " + for(var/mat_id in rmat.mat_container.materials) + var/datum/material/M = mat_id + var/amount = rmat.mat_container.materials[mat_id] + var/ref = REF(M) + output += "* [amount] of [M.name]: " + if(amount >= MINERAL_MATERIAL_AMOUNT) output += "Eject" + if(amount >= MINERAL_MATERIAL_AMOUNT*5) output += "5x" + if(amount >= MINERAL_MATERIAL_AMOUNT) output += "All" + output += "
                    " + output += "
                    " + return output + +/obj/machinery/mecha_part_fabricator/proc/search(string) + matching_designs.Cut() + for(var/v in stored_research.researched_designs) + var/datum/design/D = SSresearch.techweb_design_by_id(v) + if(!(D.build_type & MECHFAB)) + continue + if(findtext(D.name,string)) + matching_designs.Add(D) + +/obj/machinery/mecha_part_fabricator/proc/output_ui_search() + var/output + output += "

                    Search Results:

                    " + for(var/datum/design/D in matching_designs) + output += "
                    [output_part_info(D)]
                    " + if(check_resources(D)) + output += "Build | " + output += "Add to queue?
                    " + return output + +/obj/machinery/mecha_part_fabricator/proc/build_part(datum/design/D) + var/list/res_coef = get_resources_w_coeff(D) + + var/datum/component/material_container/materials = rmat.mat_container + if (!materials) + say("No access to material storage, please contact the quartermaster.") + return FALSE + if (rmat.on_hold()) + say("Mineral access is on hold, please contact the quartermaster.") + return FALSE + if(!check_resources(D)) + say("Not enough resources. Queue processing stopped.") + return FALSE + being_built = D + desc = "It's building \a [initial(D.name)]." + materials.use_materials(res_coef) + rmat.silo_log(src, "built", -1, "[D.name]", res_coef) + + add_overlay("fab-active") + use_power = ACTIVE_POWER_USE + updateUsrDialog() + sleep(get_construction_time_w_coeff(D)) + use_power = IDLE_POWER_USE + cut_overlay("fab-active") + desc = initial(desc) + + var/location = get_step(src,(dir)) + var/obj/item/I = new D.build_path(location) + I.material_flags |= MATERIAL_NO_EFFECTS //Find a better way to do this. + I.set_custom_materials(res_coef) + say("\The [I] is complete.") + being_built = null + + updateUsrDialog() + return TRUE + +/obj/machinery/mecha_part_fabricator/proc/update_queue_on_page() + send_byjax(usr,"mecha_fabricator.browser","queue",list_queue()) + return + +/obj/machinery/mecha_part_fabricator/proc/add_part_set_to_queue(set_name) + if(set_name in part_sets) + for(var/v in stored_research.researched_designs) + var/datum/design/D = SSresearch.techweb_design_by_id(v) + if(D.build_type & MECHFAB) + if(set_name in D.category) + add_to_queue(D) + +/obj/machinery/mecha_part_fabricator/proc/add_to_queue(D) + if(!istype(queue)) + queue = list() + if(D) + queue[++queue.len] = D + return queue.len + +/obj/machinery/mecha_part_fabricator/proc/remove_from_queue(index) + if(!isnum(index) || !ISINTEGER(index) || !istype(queue) || (index<1 || index>queue.len)) + return FALSE + queue.Cut(index,++index) + return TRUE + +/obj/machinery/mecha_part_fabricator/proc/process_queue() + var/datum/design/D = queue[1] + if(!D) + remove_from_queue(1) + if(queue.len) + return process_queue() + else + return + temp = null + while(D) + if(machine_stat&(NOPOWER|BROKEN)) + return FALSE + if(build_part(D)) + remove_from_queue(1) + else + return FALSE + D = LAZYACCESS(queue, 1) + say("Queue processing finished successfully.") + +/obj/machinery/mecha_part_fabricator/proc/list_queue() + var/output = "Queue contains:" + if(!istype(queue) || !queue.len) + output += "
                    Nothing" + else + output += "
                      " + var/i = 0 + for(var/datum/design/D in queue) + i++ + var/obj/part = D.build_path + output += "" + output += initial(part.name) + " - " + output += "[i>1?"":null] " + output += "[i↓":null] " + output += "Remove" + + output += "
                    " + output += "Process queue | Clear queue" + return output + +/obj/machinery/mecha_part_fabricator/proc/sync() + for(var/obj/machinery/computer/rdconsole/RDC in oview(7,src)) + RDC.stored_research.copy_research_to(stored_research) + updateUsrDialog() + say("Successfully synchronized with R&D server.") + return + + temp = "Unable to connect to local R&D Database.
                    Please check your connections and try again.
                    Return" + updateUsrDialog() + return + +/obj/machinery/mecha_part_fabricator/proc/get_resource_cost_w_coeff(datum/design/D, var/datum/material/resource, roundto = 1) + return round(D.materials[resource]*component_coeff, roundto) + +/obj/machinery/mecha_part_fabricator/proc/get_construction_time_w_coeff(datum/design/D, roundto = 1) //aran + return round(initial(D.construction_time)*time_coeff, roundto) + +/obj/machinery/mecha_part_fabricator/ui_interact(mob/user) + . = ..() + var/dat, left_part + user.set_machine(src) + var/turf/exit = get_step(src,(dir)) + if(exit.density) + say("Error! Part outlet is obstructed.") + return + if(temp) + left_part = temp + else if(being_built) + var/obj/I = being_built.build_path + left_part = {"
                    Building [initial(I.name)].
                    + Please wait until completion...
                    "} + else + left_part = output_ui_header() + switch(screen) + if("main") + for(var/part_set in part_sets) + left_part += "[part_set] - Add all parts to queue
                    " + if("parts") + left_part += output_parts_list(part_set) + if("resources") + left_part += output_ui_materials() + if("search") + left_part += output_ui_search() + dat = {" + + + [name] + + + + + + + + + +
                    + [left_part] + + [list_queue()] +
                    + + "} + + var/datum/browser/popup = new(user, "mecha_fabricator", name, 1000, 430) + popup.set_content(dat) + popup.open() + //user << browse(dat, "window=mecha_fabricator;size=1000x430") + //onclose(user, "mecha_fabricator") + return + +/obj/machinery/mecha_part_fabricator/Topic(href, href_list) + if(..()) + return + if(href_list["part_set"]) + var/tpart_set = href_list["part_set"] + if(tpart_set) + if(tpart_set=="clear") + part_set = null + else + part_set = tpart_set + screen = "parts" + if(href_list["part"]) + var/T = href_list["part"] + for(var/v in stored_research.researched_designs) + var/datum/design/D = SSresearch.techweb_design_by_id(v) + if(D.build_type & MECHFAB) + if(D.id == T) + if(!processing_queue) + build_part(D) + else + add_to_queue(D) + break + if(href_list["add_to_queue"]) + var/T = href_list["add_to_queue"] + for(var/v in stored_research.researched_designs) + var/datum/design/D = SSresearch.techweb_design_by_id(v) + if(D.build_type & MECHFAB) + if(D.id == T) + add_to_queue(D) + break + return update_queue_on_page() + if(href_list["remove_from_queue"]) + remove_from_queue(text2num(href_list["remove_from_queue"])) + return update_queue_on_page() + if(href_list["partset_to_queue"]) + add_part_set_to_queue(href_list["partset_to_queue"]) + return update_queue_on_page() + if(href_list["process_queue"]) + INVOKE_ASYNC(src, .proc/do_process_queue) + if(href_list["clear_temp"]) + temp = null + if(href_list["screen"]) + screen = href_list["screen"] + if(href_list["queue_move"] && href_list["index"]) + var/index = text2num(href_list["index"]) + var/new_index = index + text2num(href_list["queue_move"]) + if(isnum(index) && isnum(new_index) && ISINTEGER(index) && ISINTEGER(new_index)) + if(ISINRANGE(new_index,1,queue.len)) + queue.Swap(index,new_index) + return update_queue_on_page() + if(href_list["clear_queue"]) + queue = list() + return update_queue_on_page() + if(href_list["sync"]) + sync() + if(href_list["part_desc"]) + var/T = href_list["part_desc"] + for(var/v in stored_research.researched_designs) + var/datum/design/D = SSresearch.techweb_design_by_id(v) + if(D.build_type & MECHFAB) + if(D.id == T) + var/obj/part = D.build_path + temp = {"

                    [initial(part.name)] description:

                    + [initial(part.desc)]
                    + Return + "} + break + if(href_list["search"]) //Search for designs with name matching pattern + search(href_list["to_search"]) + screen = "search" + + if(href_list["remove_mat"] && href_list["material"]) + var/datum/material/Mat = locate(href_list["material"]) + eject_sheets(Mat, text2num(href_list["remove_mat"])) + + updateUsrDialog() + return + +/obj/machinery/mecha_part_fabricator/proc/do_process_queue() + if(processing_queue || being_built) + return FALSE + processing_queue = 1 + process_queue() + processing_queue = 0 + +/obj/machinery/mecha_part_fabricator/proc/eject_sheets(eject_sheet, eject_amt) + var/datum/component/material_container/mat_container = rmat.mat_container + if (!mat_container) + say("No access to material storage, please contact the quartermaster.") + return 0 + if (rmat.on_hold()) + say("Mineral access is on hold, please contact the quartermaster.") + return 0 + var/count = mat_container.retrieve_sheets(text2num(eject_amt), eject_sheet, drop_location()) + var/list/matlist = list() + matlist[eject_sheet] = text2num(eject_amt) + rmat.silo_log(src, "ejected", -count, "sheets", matlist) + return count + +/obj/machinery/mecha_part_fabricator/proc/AfterMaterialInsert(item_inserted, id_inserted, amount_inserted) + var/datum/material/M = id_inserted + add_overlay("fab-load-[M.name]") + addtimer(CALLBACK(src, /atom/proc/cut_overlay, "fab-load-[M.name]"), 10) + updateUsrDialog() + +/obj/machinery/mecha_part_fabricator/attackby(obj/item/W, mob/user, params) + if(default_deconstruction_screwdriver(user, "fab-o", "fab-idle", W)) + return TRUE + + if(default_deconstruction_crowbar(W)) + return TRUE + + return ..() + + +/obj/machinery/mecha_part_fabricator/proc/is_insertion_ready(mob/user) + if(panel_open) + to_chat(user, "You can't load [src] while it's opened!") + return FALSE + if(being_built) + to_chat(user, "\The [src] is currently processing! Please wait until completion.") + return FALSE + + return TRUE + +/obj/machinery/mecha_part_fabricator/maint + link_on_init = FALSE diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm index 52839b6ee64a..153d95c2811d 100644 --- a/code/game/mecha/mecha.dm +++ b/code/game/mecha/mecha.dm @@ -1,1191 +1,1191 @@ -/obj/mecha - name = "mecha" - desc = "Exosuit" - icon = 'icons/mecha/mecha.dmi' - density = TRUE //Dense. To raise the heat. - opacity = 1 ///opaque. Menacing. - move_force = MOVE_FORCE_VERY_STRONG - move_resist = MOVE_FORCE_EXTREMELY_STRONG - resistance_flags = FIRE_PROOF | ACID_PROOF - layer = BELOW_MOB_LAYER//icon draw layer - infra_luminosity = 15 //byond implementation is bugged. - force = 5 - flags_1 = HEAR_1 - var/ruin_mecha = FALSE //if the mecha starts on a ruin, don't automatically give it a tracking beacon to prevent metagaming. - var/can_move = 0 //time of next allowed movement - var/mob/living/carbon/occupant = null - var/step_in = 10 //make a step in step_in/10 sec. - var/dir_in = 2//What direction will the mech face when entered/powered on? Defaults to South. - var/normal_step_energy_drain = 10 //How much energy the mech will consume each time it moves. This variable is a backup for when leg actuators affect the energy drain. - var/step_energy_drain = 10 - var/melee_energy_drain = 15 - var/overload_step_energy_drain_min = 100 - max_integrity = 300 //max_integrity is base health - var/deflect_chance = 10 //chance to deflect the incoming projectiles, hits, or lesser the effect of ex_act. - armor = list("melee" = 20, "bullet" = 10, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - var/list/facing_modifiers = list(MECHA_FRONT_ARMOUR = 1.5, MECHA_SIDE_ARMOUR = 1, MECHA_BACK_ARMOUR = 0.5) - var/equipment_disabled = 0 //disabled due to EMP - var/obj/item/stock_parts/cell/cell ///Keeps track of the mech's cell - var/obj/item/stock_parts/scanning_module/scanmod ///Keeps track of the mech's scanning module - var/obj/item/stock_parts/capacitor/capacitor ///Keeps track of the mech's capacitor - var/construction_state = MECHA_LOCKED - var/last_message = 0 - var/add_req_access = 1 - var/maint_access = 0 - var/dna_lock //dna-locking the mech - var/list/proc_res = list() //stores proc owners, like proc_res["functionname"] = owner reference - var/datum/effect_system/spark_spread/spark_system = new - var/lights = FALSE - var/lights_power = 6 - var/last_user_hud = 1 // used to show/hide the mecha hud while preserving previous preference - var/completely_disabled = FALSE //stops the mech from doing anything - - var/bumpsmash = 0 //Whether or not the mech destroys walls by running into it. - //inner atmos - var/use_internal_tank = 0 - var/internal_tank_valve = ONE_ATMOSPHERE - var/obj/machinery/portable_atmospherics/canister/internal_tank - var/datum/gas_mixture/cabin_air - var/obj/machinery/atmospherics/components/unary/portables_connector/connected_port = null - - var/obj/item/radio/mech/radio - var/list/trackers = list() - - var/max_temperature = 25000 - var/internal_damage_threshold = 50 //health percentage below which internal damage is possible - var/internal_damage = 0 //contains bitflags - - var/list/operation_req_access = list()//required access level for mecha operation - var/list/internals_req_access = list(ACCESS_MECH_ENGINE, ACCESS_MECH_SCIENCE)//REQUIRED ACCESS LEVEL TO OPEN CELL COMPARTMENT - - var/wreckage - - var/list/equipment = new - var/obj/item/mecha_parts/mecha_equipment/selected - var/max_equip = 3 - var/datum/events/events //Wasp - Readded for Smartwire Revert - - var/step_silent = FALSE //Used for disabling mech step sounds while using thrusters or pushing off lockers - var/stepsound = 'sound/mecha/mechstep.ogg' - var/turnsound = 'sound/mecha/mechturn.ogg' - - var/melee_cooldown = 10 - var/melee_can_hit = 1 - - var/silicon_pilot = FALSE //set to true if an AI or MMI is piloting. - - var/enter_delay = 40 //Time taken to enter the mech - var/exit_delay = 20 //Time to exit mech - var/destruction_sleep_duration = 20 //Time that mech pilot is put to sleep for if mech is destroyed - var/enclosed = TRUE //Set to false for open-cockpit mechs - var/silicon_icon_state = null //if the mech has a different icon when piloted by an AI or MMI - var/is_currently_ejecting = FALSE //Mech cannot use equiptment when true, set to true if pilot is trying to exit mech - - //Action datums - var/datum/action/innate/mecha/mech_eject/eject_action = new - var/datum/action/innate/mecha/mech_toggle_internals/internals_action = new - var/datum/action/innate/mecha/mech_cycle_equip/cycle_action = new - var/datum/action/innate/mecha/mech_toggle_lights/lights_action = new - var/datum/action/innate/mecha/mech_view_stats/stats_action = new - var/datum/action/innate/mecha/mech_defense_mode/defense_action = new - var/datum/action/innate/mecha/mech_overload_mode/overload_action = new - var/datum/effect_system/smoke_spread/smoke_system = new //not an action, but trigged by one - var/datum/action/innate/mecha/mech_smoke/smoke_action = new - var/datum/action/innate/mecha/mech_zoom/zoom_action = new - var/datum/action/innate/mecha/mech_switch_damtype/switch_damtype_action = new - var/datum/action/innate/mecha/mech_toggle_phasing/phasing_action = new - var/datum/action/innate/mecha/strafe/strafing_action = new - - //Action vars - var/obj/item/mecha_parts/mecha_equipment/thrusters/active_thrusters - var/defense_mode = FALSE - var/leg_overload_mode = FALSE - var/leg_overload_coeff = 100 - var/zoom_mode = FALSE - var/smoke = 5 - var/smoke_ready = 1 - var/smoke_cooldown = 100 - var/phasing = FALSE - var/phasing_energy_drain = 200 - var/phase_state = "" //icon_state when phasing - var/strafe = FALSE //If we are strafing - - var/nextsmash = 0 - var/smashcooldown = 3 //deciseconds - - var/occupant_sight_flags = 0 //sight flags to give to the occupant (e.g. mech mining scanner gives meson-like vision) - var/mouse_pointer - - hud_possible = list (DIAG_STAT_HUD, DIAG_BATT_HUD, DIAG_MECH_HUD, DIAG_TRACK_HUD) - -/obj/item/radio/mech //this has to go somewhere - -/obj/mecha/Initialize() - . = ..() - events = new //Wasp - Readded for Smartwire Revert - icon_state += "-open" - add_radio() - add_cabin() - if (enclosed) - add_airtank() - spark_system.set_up(2, 0, src) - spark_system.attach(src) - smoke_system.set_up(3, src) - smoke_system.attach(src) - add_cell() - add_scanmod() - add_capacitor() - START_PROCESSING(SSobj, src) - GLOB.poi_list |= src - log_message("[src.name] created.", LOG_MECHA) - GLOB.mechas_list += src //global mech list - prepare_huds() - for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds) - diag_hud.add_to_hud(src) - diag_hud_set_mechhealth() - diag_hud_set_mechcell() - diag_hud_set_mechstat() - -/obj/mecha/update_icon_state() - if(silicon_pilot && silicon_icon_state) - icon_state = silicon_icon_state - -/obj/mecha/get_cell() - return cell - -/obj/mecha/Destroy() - if(occupant) - occupant.SetSleeping(destruction_sleep_duration) - go_out() - var/mob/living/silicon/ai/AI - for(var/mob/M in src) //Let's just be ultra sure - if(isAI(M)) - occupant = null - AI = M //AIs are loaded into the mech computer itself. When the mech dies, so does the AI. They can be recovered with an AI card from the wreck. - else - M.forceMove(loc) - for(var/obj/item/mecha_parts/mecha_equipment/E in equipment) - E.detach(loc) - qdel(E) - if(cell) - qdel(cell) - if(scanmod) - qdel(scanmod) - if(capacitor) - qdel(capacitor) - if(internal_tank) - qdel(internal_tank) - if(AI) - AI.gib() //No wreck, no AI to recover - STOP_PROCESSING(SSobj, src) - GLOB.poi_list.Remove(src) - equipment.Cut() - cell = null - scanmod = null - capacitor = null - internal_tank = null - if(loc) - loc.assume_air(cabin_air) - air_update_turf() - else - qdel(cabin_air) - cabin_air = null - qdel(spark_system) - spark_system = null - qdel(smoke_system) - smoke_system = null - - GLOB.mechas_list -= src //global mech list - return ..() - -/obj/mecha/proc/restore_equipment() - equipment_disabled = 0 - if(occupant) - SEND_SOUND(occupant, sound('sound/items/timer.ogg', volume=50)) - to_chat(occupant, "Equipment control unit has been rebooted successfully.") - occupant.update_mouse_pointer() - -/obj/mecha/CheckParts(list/parts_list) - ..() - cell = locate(/obj/item/stock_parts/cell) in contents - scanmod = locate(/obj/item/stock_parts/scanning_module) in contents - capacitor = locate(/obj/item/stock_parts/capacitor) in contents - update_part_values() - -/obj/mecha/proc/update_part_values() ///Updates the values given by scanning module and capacitor tier, called when a part is removed or inserted. - if(scanmod) - normal_step_energy_drain = 20 - (5 * scanmod.rating) //10 is normal, so on lowest part its worse, on second its ok and on higher its real good up to 0 on best - step_energy_drain = normal_step_energy_drain - else - normal_step_energy_drain = 500 - step_energy_drain = normal_step_energy_drain - if(capacitor) - armor = armor.modifyRating(energy = (capacitor.rating * 5)) //Each level of capacitor protects the mech against emp by 5% - else //because we can still be hit without a cap, even if we can't move - armor = armor.setRating(energy = 0) - - -//////////////////////// -////// Helpers ///////// -//////////////////////// - -/obj/mecha/proc/add_airtank() - internal_tank = new /obj/machinery/portable_atmospherics/canister/air(src) - return internal_tank - -///Adds a cell, for use in Map-spawned mechs, Nuke Ops mechs, and admin-spawned mechs. Mechs built by hand will replace this. -/obj/mecha/proc/add_cell(var/obj/item/stock_parts/cell/C=null) - QDEL_NULL(cell) - if(C) - C.forceMove(src) - cell = C - return - cell = new /obj/item/stock_parts/cell/high/plus(src) - -///Adds a scanning module, for use in Map-spawned mechs, Nuke Ops mechs, and admin-spawned mechs. Mechs built by hand will replace this. -/obj/mecha/proc/add_scanmod(var/obj/item/stock_parts/scanning_module/sm=null) - QDEL_NULL(scanmod) - if(sm) - sm.forceMove(src) - scanmod = sm - return - scanmod = new /obj/item/stock_parts/scanning_module(src) - -///Adds a capacitor, for use in Map-spawned mechs, Nuke Ops mechs, and admin-spawned mechs. Mechs built by hand will replace this. -/obj/mecha/proc/add_capacitor(var/obj/item/stock_parts/capacitor/cap=null) - QDEL_NULL(capacitor) - if(cap) - cap.forceMove(src) - capacitor = cap - else - capacitor = new /obj/item/stock_parts/capacitor(src) - -/obj/mecha/proc/add_cabin() - cabin_air = new - 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 - -/obj/mecha/proc/add_radio() - radio = new(src) - radio.name = "[src] radio" - radio.icon = icon - radio.icon_state = icon_state - radio.subspace_transmission = TRUE - -/obj/mecha/proc/can_use(mob/user) - if(user != occupant) - return 0 - if(user && ismob(user)) - if(!user.incapacitated()) - return 1 - return 0 - -//////////////////////////////////////////////////////////////////////////////// - -/obj/mecha/examine(mob/user) - . = ..() - var/integrity = obj_integrity*100/max_integrity - switch(integrity) - if(85 to 100) - . += "It's fully intact." - if(65 to 85) - . += "It's slightly damaged." - if(45 to 65) - . += "It's badly damaged." - if(25 to 45) - . += "It's heavily damaged." - else - . += "It's falling apart." - var/hide_weapon = locate(/obj/item/mecha_parts/concealed_weapon_bay) in contents - var/hidden_weapon = hide_weapon ? (locate(/obj/item/mecha_parts/mecha_equipment/weapon) in equipment) : null - var/list/visible_equipment = equipment - hidden_weapon - if(visible_equipment.len) - . += "It's equipped with:" - for(var/obj/item/mecha_parts/mecha_equipment/ME in visible_equipment) - . += "[icon2html(ME, user)] \A [ME]." - if(!enclosed) - if(silicon_pilot) - . += "[src] appears to be piloting itself..." - else if(occupant && occupant != user) //!silicon_pilot implied - . += "You can see [occupant] inside." - if(ishuman(user)) - var/mob/living/carbon/human/H = user - for(var/O in H.held_items) - if(istype(O, /obj/item/gun)) - . += "It looks like you can hit the pilot directly if you target the center or above." - break //in case user is holding two guns - -//processing internal damage, temperature, air regulation, alert updates, lights power use. -/obj/mecha/process() - var/internal_temp_regulation = 1 - - if(internal_damage) - if(internal_damage & MECHA_INT_FIRE) - if(!(internal_damage & MECHA_INT_TEMP_CONTROL) && prob(5)) - clearInternalDamage(MECHA_INT_FIRE) - if(internal_tank) - var/datum/gas_mixture/int_tank_air = internal_tank.return_air() - if(int_tank_air.return_pressure() > internal_tank.maximum_pressure && !(internal_damage & MECHA_INT_TANK_BREACH)) - setInternalDamage(MECHA_INT_TANK_BREACH) - if(int_tank_air && int_tank_air.return_volume() > 0) //heat the air_contents - int_tank_air.set_temperature(min(6000+T0C, int_tank_air.return_temperature()+rand(10,15))) - if(cabin_air && cabin_air.return_volume()>0) - cabin_air.set_temperature(min(6000+T0C, cabin_air.return_temperature()+rand(10,15))) - if(cabin_air.return_temperature() > max_temperature/2) - take_damage(4/round(max_temperature/cabin_air.return_temperature(),0.1), BURN, 0, 0) - - if(internal_damage & MECHA_INT_TEMP_CONTROL) - internal_temp_regulation = 0 - - if(internal_damage & MECHA_INT_TANK_BREACH) //remove some air from internal tank - if(internal_tank) - var/datum/gas_mixture/int_tank_air = internal_tank.return_air() - var/datum/gas_mixture/leaked_gas = int_tank_air.remove_ratio(0.1) - if(loc) - loc.assume_air(leaked_gas) - air_update_turf() - else - qdel(leaked_gas) - - if(internal_damage & MECHA_INT_SHORT_CIRCUIT) - if(get_charge()) - spark_system.start() - cell.charge -= min(20,cell.charge) - cell.maxcharge -= min(20,cell.maxcharge) - - if(internal_temp_regulation) - if(cabin_air && cabin_air.return_volume() > 0) - var/delta = cabin_air.return_temperature() - T20C - cabin_air.set_temperature(cabin_air.return_temperature() - max(-10, min(10, round(delta/4,0.1)))) - - if(internal_tank) - var/datum/gas_mixture/tank_air = internal_tank.return_air() - - var/release_pressure = internal_tank_valve - var/cabin_pressure = cabin_air.return_pressure() - var/pressure_delta = min(release_pressure - cabin_pressure, (tank_air.return_pressure() - cabin_pressure)/2) - var/transfer_moles = 0 - if(pressure_delta > 0) //cabin pressure lower than release pressure - if(tank_air.return_temperature() > 0) - transfer_moles = pressure_delta*cabin_air.return_volume()/(cabin_air.return_temperature() * R_IDEAL_GAS_EQUATION) - var/datum/gas_mixture/removed = tank_air.remove(transfer_moles) - cabin_air.merge(removed) - else if(pressure_delta < 0) //cabin pressure higher than release pressure - var/datum/gas_mixture/t_air = return_air() - pressure_delta = cabin_pressure - release_pressure - if(t_air) - pressure_delta = min(cabin_pressure - t_air.return_pressure(), pressure_delta) - if(pressure_delta > 0) //if location pressure is lower than cabin pressure - transfer_moles = pressure_delta*cabin_air.return_volume()/(cabin_air.return_temperature() * R_IDEAL_GAS_EQUATION) - var/datum/gas_mixture/removed = cabin_air.remove(transfer_moles) - if(t_air) - t_air.merge(removed) - else //just delete the cabin gas, we're in space or some shit - qdel(removed) - - if(occupant) - if(cell) - var/cellcharge = cell.charge/cell.maxcharge - switch(cellcharge) - if(0.75 to INFINITY) - occupant.clear_alert("charge") - if(0.5 to 0.75) - occupant.throw_alert("charge", /obj/screen/alert/lowcell, 1) - if(0.25 to 0.5) - occupant.throw_alert("charge", /obj/screen/alert/lowcell, 2) - if(0.01 to 0.25) - occupant.throw_alert("charge", /obj/screen/alert/lowcell, 3) - else - occupant.throw_alert("charge", /obj/screen/alert/emptycell) - - var/integrity = obj_integrity/max_integrity*100 - switch(integrity) - if(30 to 45) - occupant.throw_alert("mech damage", /obj/screen/alert/low_mech_integrity, 1) - if(15 to 35) - occupant.throw_alert("mech damage", /obj/screen/alert/low_mech_integrity, 2) - if(-INFINITY to 15) - occupant.throw_alert("mech damage", /obj/screen/alert/low_mech_integrity, 3) - else - occupant.clear_alert("mech damage") - var/atom/checking = occupant.loc - // recursive check to handle all cases regarding very nested occupants, - // such as brainmob inside brainitem inside MMI inside mecha - while (!isnull(checking)) - if (isturf(checking)) - // hit a turf before hitting the mecha, seems like they have - // been moved out - occupant.clear_alert("charge") - occupant.clear_alert("mech damage") - RemoveActions(occupant, human_occupant=1) - occupant = null - break - else if (checking == src) - break // all good - checking = checking.loc - - if(lights) - var/lights_energy_drain = 2 - use_power(lights_energy_drain) - - if(!enclosed && occupant?.incapacitated()) //no sides mean it's easy to just sorta fall out if you're incapacitated. - visible_message("[occupant] tumbles out of the cockpit!") - go_out() //Maybe we should install seat belts? - -//Diagnostic HUD updates - diag_hud_set_mechhealth() - diag_hud_set_mechcell() - diag_hud_set_mechstat() - -/obj/mecha/fire_act() //Check if we should ignite the pilot of an open-canopy mech - . = ..() - if (occupant && !enclosed && !silicon_pilot) - if (occupant.fire_stacks < 5) - occupant.fire_stacks += 1 - occupant.IgniteMob() - -/obj/mecha/proc/drop_item()//Derpfix, but may be useful in future for engineering exosuits. - return - -/obj/mecha/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode) - . = ..() - if(speaker == occupant) - if(radio?.broadcasting) - radio.talk_into(speaker, text, , spans, message_language) - //flick speech bubble - var/list/speech_bubble_recipients = list() - for(var/mob/M in get_hearers_in_view(7,src)) - if(M.client) - speech_bubble_recipients.Add(M.client) - INVOKE_ASYNC(GLOBAL_PROC, /proc/flick_overlay, image('icons/mob/talk.dmi', src, "machine[say_test(raw_message)]",MOB_LAYER+1), speech_bubble_recipients, 30) - -//////////////////////////// -///// Action processing //// -//////////////////////////// - - -/obj/mecha/proc/click_action(atom/target,mob/user,params) - if(!occupant || occupant != user ) - return - if(!locate(/turf) in list(target,target.loc)) // Prevents inventory from being drilled - return - if(completely_disabled) - return - if(is_currently_ejecting) - return - if(phasing) - occupant_message("Unable to interact with objects while phasing.") - return - if(user.incapacitated()) - return - if(construction_state) - occupant_message("Maintenance protocols in effect.") - return - if(!get_charge()) - return - if(src == target) - return - var/dir_to_target = get_dir(src,target) - if(dir_to_target && !(dir_to_target & dir))//wrong direction - return - if(internal_damage & MECHA_INT_CONTROL_LOST) - if (!target) - return - target = pick(view(3,target)) - - var/mob/living/L = user - if(!Adjacent(target)) - if(selected && selected.is_ranged()) - if(HAS_TRAIT(L, TRAIT_PACIFISM) && selected.harmful) - to_chat(user, "You don't want to harm other living beings!") - return - if(selected.action(target,params)) - selected.start_cooldown() - else if(selected && selected.is_melee()) - if(isliving(target) && selected.harmful && HAS_TRAIT(L, TRAIT_PACIFISM)) - to_chat(user, "You don't want to harm other living beings!") - return - if(selected.action(target,params)) - selected.start_cooldown() - else - if(internal_damage & MECHA_INT_CONTROL_LOST) - var/list/possible_targets = oview(1,src) - if (!length(possible_targets)) - return - target = pick(possible_targets) - if(!melee_can_hit || !istype(target, /atom)) - return - target.mech_melee_attack(src) - melee_can_hit = FALSE - addtimer(VARSET_CALLBACK(src, melee_can_hit, TRUE), melee_cooldown) - - -/obj/mecha/proc/range_action(atom/target) - return - - -////////////////////////////////// -//////// Movement procs //////// -////////////////////////////////// - -///Plays the mech step sound effect. Split from movement procs so that other mechs (HONK) can override this one specific part. -/obj/mecha/proc/play_stepsound() - if(stepsound) - playsound(src,stepsound,40,1) - -/obj/mecha/Move(atom/newloc, direct) - . = ..() - if(.) - events.fireEvent("onMove",get_turf(src)) //Wasp - Readded for Smartwire Revert - if (internal_tank?.disconnect()) // Something moved us and broke connection - occupant_message("Air port connection has been severed!") - log_message("Lost connection to gas port.", LOG_MECHA) - -/obj/mecha/Process_Spacemove(var/movement_dir = 0) - . = ..() - if(.) - return TRUE - - var/atom/movable/backup = get_spacemove_backup() - if(backup) - if(istype(backup) && movement_dir && !backup.anchored) - if(backup.newtonian_move(turn(movement_dir, 180))) - step_silent = TRUE - if(occupant) - to_chat(occupant, "You push off [backup] to propel yourself.") - return TRUE - - if(can_move <= world.time && active_thrusters && movement_dir && active_thrusters.thrust(movement_dir)) - step_silent = TRUE - return TRUE - - return FALSE - -/obj/mecha/relaymove(mob/user,direction) - if(completely_disabled) - return - if(!direction) - return - if(user != occupant) //While not "realistic", this piece is player friendly. - user.forceMove(get_turf(src)) - to_chat(user, "You climb out from [src].") - return 0 - if(internal_tank?.connected_port) - if(world.time - last_message > 20) - occupant_message("Unable to move while connected to the air system port!") - last_message = world.time - return 0 - if(construction_state) - if(world.time - last_message > 20) - occupant_message("Maintenance protocols in effect.") - last_message = world.time - return - return domove(direction) - -/obj/mecha/proc/domove(direction) - if(can_move >= world.time) - return 0 - if(!Process_Spacemove(direction)) - return 0 - if(!has_charge(step_energy_drain)) - return 0 - if(zoom_mode) - if(world.time - last_message > 20) - occupant_message("Unable to move while in zoom mode!") - last_message = world.time - return 0 - if(!cell) - if(world.time - last_message > 20) - occupant_message("Missing power cell.") - last_message = world.time - return 0 - if(!scanmod || !capacitor) - if(world.time - last_message > 20) - occupant_message("Missing [scanmod? "capacitor" : "scanning module"].") - last_message = world.time - return 0 - - var/move_result = 0 - var/oldloc = loc - if(internal_damage & MECHA_INT_CONTROL_LOST) - move_result = mechsteprand() - else if(dir != direction && (!strafe || occupant.client.keys_held["Alt"])) - move_result = mechturn(direction) - else - move_result = mechstep(direction) - if(move_result || loc != oldloc)// halfway done diagonal move still returns false - use_power(step_energy_drain) - can_move = world.time + step_in - return 1 - return 0 - -/obj/mecha/proc/mechturn(direction) - setDir(direction) - if(turnsound) - playsound(src,turnsound,40,TRUE) - return 1 - -/obj/mecha/proc/mechstep(direction) - var/current_dir = dir - . = step(src,direction) - if(strafe) - setDir(current_dir) - if(. && !step_silent) - play_stepsound() - step_silent = FALSE - -/obj/mecha/proc/mechsteprand() - . = step_rand(src) - if(. && !step_silent) - play_stepsound() - step_silent = FALSE - -/obj/mecha/Bump(var/atom/obstacle) - if(phasing && get_charge() >= phasing_energy_drain && !throwing) - if(!can_move) - return - can_move = 0 - if(phase_state) - flick(phase_state, src) - forceMove(get_step(src,dir)) - use_power(phasing_energy_drain) - addtimer(VARSET_CALLBACK(src, can_move, TRUE), step_in*3) - else - if(..()) //mech was thrown - return - if(bumpsmash && occupant) //Need a pilot to push the PUNCH button. - if(nextsmash < world.time) - obstacle.mech_melee_attack(src) - nextsmash = world.time + smashcooldown - if(!obstacle || obstacle.CanPass(src,get_step(src,dir))) - step(src,dir) - if(isobj(obstacle)) - var/obj/O = obstacle - if(!O.anchored && O.move_resist <= move_force) - step(obstacle, dir) - else if(ismob(obstacle)) - var/mob/M = obstacle - if(M.move_resist <= move_force) - step(obstacle, dir) - - - - - -/////////////////////////////////// -//////// Internal damage //////// -/////////////////////////////////// - -/obj/mecha/proc/check_for_internal_damage(list/possible_int_damage,ignore_threshold=null) - if(!islist(possible_int_damage) || !length(possible_int_damage)) - return - if(prob(20)) - if(ignore_threshold || obj_integrity*100/max_integrity < internal_damage_threshold) - for(var/T in possible_int_damage) - if(internal_damage & T) - possible_int_damage -= T - if (length(possible_int_damage)) - var/int_dam_flag = pick(possible_int_damage) - if(int_dam_flag) - setInternalDamage(int_dam_flag) - if(prob(5)) - if(ignore_threshold || obj_integrity*100/max_integrity < internal_damage_threshold) - if (length(equipment)) - var/obj/item/mecha_parts/mecha_equipment/ME = pick(equipment) - qdel(ME) - -/obj/mecha/proc/setInternalDamage(int_dam_flag) - internal_damage |= int_dam_flag - log_message("Internal damage of type [int_dam_flag].", LOG_MECHA) - SEND_SOUND(occupant, sound('sound/machines/warning-buzzer.ogg',wait=0)) - diag_hud_set_mechstat() - -/obj/mecha/proc/clearInternalDamage(int_dam_flag) - if(internal_damage & int_dam_flag) - switch(int_dam_flag) - if(MECHA_INT_TEMP_CONTROL) - occupant_message("Life support system reactivated.") - if(MECHA_INT_FIRE) - occupant_message("Internal fire extinquished.") - if(MECHA_INT_TANK_BREACH) - occupant_message("Damaged internal tank has been sealed.") - internal_damage &= ~int_dam_flag - diag_hud_set_mechstat() - -///////////////////////////////////// -//////////// AI piloting //////////// -///////////////////////////////////// - -/obj/mecha/attack_ai(mob/living/silicon/ai/user) - if(!isAI(user)) - return - //Allows the Malf to scan a mech's status and loadout, helping it to decide if it is a worthy chariot. - if(user.can_dominate_mechs) - examine(user) //Get diagnostic information! - for(var/obj/item/mecha_parts/mecha_tracking/B in trackers) - to_chat(user, "Warning: Tracking Beacon detected. Enter at your own risk. Beacon Data:") - to_chat(user, "[B.get_mecha_info()]") - break - //Nothing like a big, red link to make the player feel powerful! - to_chat(user, "ASSUME DIRECT CONTROL?
                    ") - else - examine(user) - if(occupant) - to_chat(user, "This exosuit has a pilot and cannot be controlled.") - return - var/can_control_mech = 0 - for(var/obj/item/mecha_parts/mecha_tracking/ai_control/A in trackers) - can_control_mech = 1 - to_chat(user, "[icon2html(src, user)] Status of [name]:\n[A.get_mecha_info()]") - break - if(!can_control_mech) - to_chat(user, "You cannot control exosuits without AI control beacons installed.") - return - to_chat(user, "Take control of exosuit?
                    ") - -/obj/mecha/transfer_ai(interaction, mob/user, mob/living/silicon/ai/AI, obj/item/aicard/card) - if(!..()) - return - - //Transfer from core or card to mech. Proc is called by mech. - switch(interaction) - if(AI_TRANS_TO_CARD) //Upload AI from mech to AI card. - if(!construction_state) //Mech must be in maint mode to allow carding. - to_chat(user, "[name] must have maintenance protocols active in order to allow a transfer.") - return - AI = occupant - if(!AI || !isAI(occupant)) //Mech does not have an AI for a pilot - to_chat(user, "No AI detected in the [name] onboard computer.") - return - AI.ai_restore_power()//So the AI initially has power. - AI.control_disabled = TRUE - AI.radio_enabled = FALSE - AI.disconnect_shell() - RemoveActions(AI, TRUE) - occupant = null - silicon_pilot = FALSE - AI.forceMove(card) - card.AI = AI - AI.controlled_mech = null - AI.remote_control = null - icon_state = initial(icon_state)+"-open" - to_chat(AI, "You have been downloaded to a mobile storage device. Wireless connection offline.") - to_chat(user, "Transfer successful: [AI.name] ([rand(1000,9999)].exe) removed from [name] and stored within local memory.") - - if(AI_MECH_HACK) //Called by AIs on the mech - AI.linked_core = new /obj/structure/AIcore/deactivated(AI.loc) - if(AI.can_dominate_mechs) - if(occupant) //Oh, I am sorry, were you using that? - to_chat(AI, "Pilot detected! Forced ejection initiated!") - to_chat(occupant, "You have been forcibly ejected!") - go_out(1) //IT IS MINE, NOW. SUCK IT, RD! - ai_enter_mech(AI, interaction) - - if(AI_TRANS_FROM_CARD) //Using an AI card to upload to a mech. - AI = card.AI - if(!AI) - to_chat(user, "There is no AI currently installed on this device.") - return - if(AI.deployed_shell) //Recall AI if shelled so it can be checked for a client - AI.disconnect_shell() - if(AI.stat || !AI.client) - to_chat(user, "[AI.name] is currently unresponsive, and cannot be uploaded.") - return - if(occupant || dna_lock) //Normal AIs cannot steal mechs! - to_chat(user, "Access denied. [name] is [occupant ? "currently occupied" : "secured with a DNA lock"].") - return - AI.control_disabled = FALSE - AI.radio_enabled = TRUE - to_chat(user, "Transfer successful: [AI.name] ([rand(1000,9999)].exe) installed and executed successfully. Local copy has been removed.") - card.AI = null - ai_enter_mech(AI, interaction) - -//Hack and From Card interactions share some code, so leave that here for both to use. -/obj/mecha/proc/ai_enter_mech(mob/living/silicon/ai/AI, interaction) - AI.ai_restore_power() - AI.forceMove(src) - occupant = AI - silicon_pilot = TRUE - icon_state = initial(icon_state) - update_icon() - playsound(src, 'sound/machines/windowdoor.ogg', 50, TRUE) - if(!internal_damage) - SEND_SOUND(occupant, sound('sound/mecha/nominal.ogg',volume=50)) - AI.cancel_camera() - AI.controlled_mech = src - AI.remote_control = src - AI.mobility_flags = ALL //Much easier than adding AI checks! Be sure to set this back to 0 if you decide to allow an AI to leave a mech somehow. - AI.can_shunt = 0 //ONE AI ENTERS. NO AI LEAVES. - to_chat(AI, AI.can_dominate_mechs ? "Takeover of [name] complete! You are now loaded onto the onboard computer. Do not attempt to leave the station sector!" :\ - "You have been uploaded to a mech's onboard computer.") - to_chat(AI, "Use Middle-Mouse to activate mech functions and equipment. Click normally for AI interactions.") - if(interaction == AI_TRANS_FROM_CARD) - GrantActions(AI, FALSE) //No eject/return to core action for AI uploaded by card - else - GrantActions(AI, !AI.can_dominate_mechs) - - -//An actual AI (simple_animal mecha pilot) entering the mech -/obj/mecha/proc/aimob_enter_mech(mob/living/simple_animal/hostile/syndicate/mecha_pilot/pilot_mob) - if(pilot_mob && pilot_mob.Adjacent(src)) - if(occupant) - return - icon_state = initial(icon_state) - occupant = pilot_mob - pilot_mob.mecha = src - pilot_mob.forceMove(src) - GrantActions(pilot_mob)//needed for checks, and incase a badmin puts somebody in the mob - -/obj/mecha/proc/aimob_exit_mech(mob/living/simple_animal/hostile/syndicate/mecha_pilot/pilot_mob) - if(occupant == pilot_mob) - occupant = null - if(pilot_mob.mecha == src) - pilot_mob.mecha = null - icon_state = "[initial(icon_state)]-open" - pilot_mob.forceMove(get_turf(src)) - RemoveActions(pilot_mob) - - -///////////////////////////////////// -//////// Atmospheric stuff //////// -///////////////////////////////////// - -/obj/mecha/remove_air(amount) - if(use_internal_tank) - return cabin_air.remove(amount) - return ..() - -/obj/mecha/return_air() - if(use_internal_tank) - return cabin_air - return ..() - -/obj/mecha/return_analyzable_air() - return cabin_air - -/obj/mecha/proc/return_pressure() - var/datum/gas_mixture/t_air = return_air() - if(t_air) - . = t_air.return_pressure() - -/obj/mecha/return_temperature() - var/datum/gas_mixture/t_air = return_air() - if(t_air) - . = t_air.return_temperature() - -/obj/mecha/MouseDrop_T(mob/M, mob/user) - if((user != M) || user.incapacitated() || !Adjacent(user)) - return - if(!ishuman(user)) // no silicons or drones in mechas. - return - if(HAS_TRAIT(user, TRAIT_PRIMITIVE)) //no lavalizards either. - to_chat(user, "The knowledge to use this device eludes you!") - return - log_message("[user] tries to move in.", LOG_MECHA) - if (occupant) - to_chat(usr, "The [name] is already occupied!") - log_message("Permission denied (Occupied).", LOG_MECHA) - return - if(dna_lock) - var/passed = FALSE - if(user.has_dna()) - var/mob/living/carbon/C = user - if(C.dna.unique_enzymes==dna_lock) - passed = TRUE - if (!passed) - to_chat(user, "Access denied. [name] is secured with a DNA lock.") - log_message("Permission denied (DNA LOCK).", LOG_MECHA) - return - if(!operation_allowed(user)) - to_chat(user, "Access denied. Insufficient operation keycodes.") - log_message("Permission denied (No keycode).", LOG_MECHA) - return - if(user.buckled) - to_chat(user, "You are currently buckled and cannot move.") - log_message("Permission denied (Buckled).", LOG_MECHA) - return - if(user.has_buckled_mobs()) //mob attached to us - to_chat(user, "You can't enter the exosuit with other creatures attached to you!") - log_message("Permission denied (Attached mobs).", LOG_MECHA) - return - - visible_message("[user] starts to climb into [name].") - - if(do_after(user, enter_delay, target = src)) - if(obj_integrity <= 0) - to_chat(user, "You cannot get in the [name], it has been destroyed!") - else if(occupant) - to_chat(user, "[occupant] was faster! Try better next time, loser.") - else if(user.buckled) - to_chat(user, "You can't enter the exosuit while buckled.") - else if(user.has_buckled_mobs()) - to_chat(user, "You can't enter the exosuit with other creatures attached to you!") - else - moved_inside(user) - else - to_chat(user, "You stop entering the exosuit!") - -/obj/mecha/proc/moved_inside(mob/living/carbon/human/H) - . = FALSE - if(H && H.client && (H in range(1))) - occupant = H - H.forceMove(src) - H.update_mouse_pointer() - add_fingerprint(H) - GrantActions(H, human_occupant=1) - forceMove(loc) - log_message("[H] moved in as pilot.", LOG_MECHA) - icon_state = initial(icon_state) - setDir(dir_in) - playsound(src, 'sound/machines/windowdoor.ogg', 50, TRUE) - if(!internal_damage) - SEND_SOUND(occupant, sound('sound/mecha/nominal.ogg',volume=50)) - return TRUE - -/obj/mecha/proc/mmi_move_inside(obj/item/mmi/M, mob/user) - . = FALSE - if(!M.brain_check(user)) - return - - var/mob/living/brain/B = M.brainmob - if(occupant) - to_chat(user, "Occupant detected!") - return - if(dna_lock && (!B.stored_dna || (dna_lock != B.stored_dna.unique_enzymes))) - to_chat(user, "Access denied. [name] is secured with a DNA lock.") - return - - visible_message("[user] starts to insert an MMI into [name].") - - if(do_after(user, 40, target = src)) - if(!occupant) - return mmi_moved_inside(M, user) - else - to_chat(user, "Occupant detected!") - else - to_chat(user, "You stop inserting the MMI.") - -/obj/mecha/proc/mmi_moved_inside(obj/item/mmi/M, mob/user) - if(!(Adjacent(M) && Adjacent(user))) - return FALSE - if(!M.brain_check(user)) - return FALSE - - var/mob/living/brain/B = M.brainmob - if(!user.transferItemToLoc(M, src)) - to_chat(user, "\the [M] is stuck to your hand, you cannot put it in \the [src]!") - return FALSE - - M.mecha = src - occupant = B - silicon_pilot = TRUE - B.forceMove(src) //should allow relaymove - B.reset_perspective(src) - B.remote_control = src - B.update_mobility() - B.update_mouse_pointer() - icon_state = initial(icon_state) - update_icon() - setDir(dir_in) - log_message("[M] moved in as pilot.", LOG_MECHA) - if(!internal_damage) - SEND_SOUND(occupant, sound('sound/mecha/nominal.ogg',volume=50)) - GrantActions(B) - log_game("[key_name(user)] has put the MMI/posibrain of [key_name(B)] into [src] at [AREACOORD(src)]") - return TRUE - -/obj/mecha/container_resist(mob/living/user) - is_currently_ejecting = TRUE - to_chat(occupant, "You begin the ejection procedure. Equipment is disabled during this process. Hold still to finish ejecting.") - if(do_after(occupant, has_gravity() ? exit_delay : 0 , target = src)) - to_chat(occupant, "You exit the mech.") - go_out() - else - to_chat(occupant, "You stop exiting the mech. Weapons are enabled again.") - is_currently_ejecting = FALSE - -/obj/mecha/Exited(atom/movable/M, atom/newloc) - if(occupant && occupant == M) // The occupant exited the mech without calling go_out() - go_out(TRUE, newloc) - - if(cell && cell == M) - cell = null - return - if(scanmod && scanmod == M) - scanmod = null - update_part_values() - return - if(capacitor && capacitor == M) - armor = armor.modifyRating(energy = (capacitor.rating * -5)) //lose the energy armor if we lose this cap - capacitor = null - update_part_values() - -/obj/mecha/proc/go_out(forced, atom/newloc = loc) - if(!occupant) - return - var/atom/movable/mob_container - occupant.clear_alert("charge") - occupant.clear_alert("mech damage") - if(ishuman(occupant)) - mob_container = occupant - RemoveActions(occupant, human_occupant=1) - else if(isbrain(occupant)) - var/mob/living/brain/brain = occupant - RemoveActions(brain) - mob_container = brain.container - else if(isAI(occupant)) - var/mob/living/silicon/ai/AI = occupant - if(forced)//This should only happen if there are multiple AIs in a round, and at least one is Malf. - RemoveActions(occupant) - occupant.gib() //If one Malf decides to steal a mech from another AI (even other Malfs!), they are destroyed, as they have nowhere to go when replaced. - occupant = null - silicon_pilot = FALSE - return - else - if(!AI.linked_core) - to_chat(AI, "Inactive core destroyed. Unable to return.") - AI.linked_core = null - return - to_chat(AI, "Returning to core...") - AI.controlled_mech = null - AI.remote_control = null - RemoveActions(occupant, 1) - mob_container = AI - newloc = get_turf(AI.linked_core) - qdel(AI.linked_core) - else - return - var/mob/living/L = occupant - occupant = null //we need it null when forceMove calls Exited(). - silicon_pilot = FALSE - if(mob_container.forceMove(newloc))//ejecting mob container - log_message("[mob_container] moved out.", LOG_MECHA) - L << browse(null, "window=exosuit") - - if(istype(mob_container, /obj/item/mmi)) - var/obj/item/mmi/mmi = mob_container - if(mmi.brainmob) - L.forceMove(mmi) - L.reset_perspective() - mmi.mecha = null - mmi.update_icon() - L.mobility_flags = NONE - icon_state = initial(icon_state)+"-open" - setDir(dir_in) - - if(L && L.client) - L.update_mouse_pointer() - L.client.change_view(CONFIG_GET(string/default_view)) - zoom_mode = 0 - -///////////////////////// -////// Access stuff ///// -///////////////////////// - -/obj/mecha/proc/operation_allowed(mob/M) - req_access = operation_req_access - req_one_access = list() - return allowed(M) - -/obj/mecha/proc/internals_access_allowed(mob/M) - req_one_access = internals_req_access - req_access = list() - return allowed(M) - - - -//////////////////////////////// -/////// Messages and Log /////// -//////////////////////////////// - -/obj/mecha/proc/occupant_message(message as text) - if(message) - if(occupant && occupant.client) - to_chat(occupant, "[icon2html(src, occupant)] [message]") - -GLOBAL_VAR_INIT(year, time2text(world.realtime,"YYYY")) -GLOBAL_VAR_INIT(year_integer, text2num(year)) // = 2013??? - -/////////////////////// -///// Power stuff ///// -/////////////////////// - -/obj/mecha/proc/has_charge(amount) - return (get_charge()>=amount) - -/obj/mecha/proc/get_charge() - for(var/obj/item/mecha_parts/mecha_equipment/tesla_energy_relay/R in equipment) - var/relay_charge = R.get_charge() - if(relay_charge) - return relay_charge - if(cell) - return max(0, cell.charge) - -/obj/mecha/proc/use_power(amount) - if(get_charge() && cell.use(amount)) - return 1 - return 0 - -/obj/mecha/proc/give_power(amount) - if(!isnull(get_charge())) - cell.give(amount) - return 1 - return 0 - -/obj/mecha/update_remote_sight(mob/living/user) - if(occupant_sight_flags) - if(user == occupant) - user.sight |= occupant_sight_flags - -/////////////////////// -////// Ammo stuff ///// -/////////////////////// - -/obj/mecha/proc/ammo_resupply(var/obj/item/mecha_ammo/A, mob/user,var/fail_chat_override = FALSE) - if(!A.rounds) - if(!fail_chat_override) - to_chat(user, "This box of ammo is empty!") - return FALSE - var/ammo_needed - var/found_gun - for(var/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/gun in equipment) - ammo_needed = 0 - - if(istype(gun, /obj/item/mecha_parts/mecha_equipment/weapon/ballistic) && gun.ammo_type == A.ammo_type) - found_gun = TRUE - if(A.direct_load) - ammo_needed = initial(gun.projectiles) - gun.projectiles - else - ammo_needed = gun.projectiles_cache_max - gun.projectiles_cache - - if(ammo_needed) - if(ammo_needed < A.rounds) - if(A.direct_load) - gun.projectiles = gun.projectiles + ammo_needed - else - gun.projectiles_cache = gun.projectiles_cache + ammo_needed - playsound(get_turf(user),A.load_audio,50,TRUE) - to_chat(user, "You add [ammo_needed] [A.round_term][ammo_needed > 1?"s":""] to the [gun.name]") - A.rounds = A.rounds - ammo_needed - A.update_name() - return TRUE - - else - if(A.direct_load) - gun.projectiles = gun.projectiles + A.rounds - else - gun.projectiles_cache = gun.projectiles_cache + A.rounds - playsound(get_turf(user),A.load_audio,50,TRUE) - to_chat(user, "You add [A.rounds] [A.round_term][A.rounds > 1?"s":""] to the [gun.name]") - A.rounds = 0 - A.update_name() - return TRUE - if(!fail_chat_override) - if(found_gun) - to_chat(user, "You can't fit any more ammo of this type!") - else - to_chat(user, "None of the equipment on this exosuit can use this ammo!") - return FALSE +/obj/mecha + name = "mecha" + desc = "Exosuit" + icon = 'icons/mecha/mecha.dmi' + density = TRUE //Dense. To raise the heat. + opacity = 1 ///opaque. Menacing. + move_force = MOVE_FORCE_VERY_STRONG + move_resist = MOVE_FORCE_EXTREMELY_STRONG + resistance_flags = FIRE_PROOF | ACID_PROOF + layer = BELOW_MOB_LAYER//icon draw layer + infra_luminosity = 15 //byond implementation is bugged. + force = 5 + flags_1 = HEAR_1 + var/ruin_mecha = FALSE //if the mecha starts on a ruin, don't automatically give it a tracking beacon to prevent metagaming. + var/can_move = 0 //time of next allowed movement + var/mob/living/carbon/occupant = null + var/step_in = 10 //make a step in step_in/10 sec. + var/dir_in = 2//What direction will the mech face when entered/powered on? Defaults to South. + var/normal_step_energy_drain = 10 //How much energy the mech will consume each time it moves. This variable is a backup for when leg actuators affect the energy drain. + var/step_energy_drain = 10 + var/melee_energy_drain = 15 + var/overload_step_energy_drain_min = 100 + max_integrity = 300 //max_integrity is base health + var/deflect_chance = 10 //chance to deflect the incoming projectiles, hits, or lesser the effect of ex_act. + armor = list("melee" = 20, "bullet" = 10, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + var/list/facing_modifiers = list(MECHA_FRONT_ARMOUR = 1.5, MECHA_SIDE_ARMOUR = 1, MECHA_BACK_ARMOUR = 0.5) + var/equipment_disabled = 0 //disabled due to EMP + var/obj/item/stock_parts/cell/cell ///Keeps track of the mech's cell + var/obj/item/stock_parts/scanning_module/scanmod ///Keeps track of the mech's scanning module + var/obj/item/stock_parts/capacitor/capacitor ///Keeps track of the mech's capacitor + var/construction_state = MECHA_LOCKED + var/last_message = 0 + var/add_req_access = 1 + var/maint_access = 0 + var/dna_lock //dna-locking the mech + var/list/proc_res = list() //stores proc owners, like proc_res["functionname"] = owner reference + var/datum/effect_system/spark_spread/spark_system = new + var/lights = FALSE + var/lights_power = 6 + var/last_user_hud = 1 // used to show/hide the mecha hud while preserving previous preference + var/completely_disabled = FALSE //stops the mech from doing anything + + var/bumpsmash = 0 //Whether or not the mech destroys walls by running into it. + //inner atmos + var/use_internal_tank = 0 + var/internal_tank_valve = ONE_ATMOSPHERE + var/obj/machinery/portable_atmospherics/canister/internal_tank + var/datum/gas_mixture/cabin_air + var/obj/machinery/atmospherics/components/unary/portables_connector/connected_port = null + + var/obj/item/radio/mech/radio + var/list/trackers = list() + + var/max_temperature = 25000 + var/internal_damage_threshold = 50 //health percentage below which internal damage is possible + var/internal_damage = 0 //contains bitflags + + var/list/operation_req_access = list()//required access level for mecha operation + var/list/internals_req_access = list(ACCESS_MECH_ENGINE, ACCESS_MECH_SCIENCE)//REQUIRED ACCESS LEVEL TO OPEN CELL COMPARTMENT + + var/wreckage + + var/list/equipment = new + var/obj/item/mecha_parts/mecha_equipment/selected + var/max_equip = 3 + var/datum/events/events //Wasp - Readded for Smartwire Revert + + var/step_silent = FALSE //Used for disabling mech step sounds while using thrusters or pushing off lockers + var/stepsound = 'sound/mecha/mechstep.ogg' + var/turnsound = 'sound/mecha/mechturn.ogg' + + var/melee_cooldown = 10 + var/melee_can_hit = 1 + + var/silicon_pilot = FALSE //set to true if an AI or MMI is piloting. + + var/enter_delay = 40 //Time taken to enter the mech + var/exit_delay = 20 //Time to exit mech + var/destruction_sleep_duration = 20 //Time that mech pilot is put to sleep for if mech is destroyed + var/enclosed = TRUE //Set to false for open-cockpit mechs + var/silicon_icon_state = null //if the mech has a different icon when piloted by an AI or MMI + var/is_currently_ejecting = FALSE //Mech cannot use equiptment when true, set to true if pilot is trying to exit mech + + //Action datums + var/datum/action/innate/mecha/mech_eject/eject_action = new + var/datum/action/innate/mecha/mech_toggle_internals/internals_action = new + var/datum/action/innate/mecha/mech_cycle_equip/cycle_action = new + var/datum/action/innate/mecha/mech_toggle_lights/lights_action = new + var/datum/action/innate/mecha/mech_view_stats/stats_action = new + var/datum/action/innate/mecha/mech_defense_mode/defense_action = new + var/datum/action/innate/mecha/mech_overload_mode/overload_action = new + var/datum/effect_system/smoke_spread/smoke_system = new //not an action, but trigged by one + var/datum/action/innate/mecha/mech_smoke/smoke_action = new + var/datum/action/innate/mecha/mech_zoom/zoom_action = new + var/datum/action/innate/mecha/mech_switch_damtype/switch_damtype_action = new + var/datum/action/innate/mecha/mech_toggle_phasing/phasing_action = new + var/datum/action/innate/mecha/strafe/strafing_action = new + + //Action vars + var/obj/item/mecha_parts/mecha_equipment/thrusters/active_thrusters + var/defense_mode = FALSE + var/leg_overload_mode = FALSE + var/leg_overload_coeff = 100 + var/zoom_mode = FALSE + var/smoke = 5 + var/smoke_ready = 1 + var/smoke_cooldown = 100 + var/phasing = FALSE + var/phasing_energy_drain = 200 + var/phase_state = "" //icon_state when phasing + var/strafe = FALSE //If we are strafing + + var/nextsmash = 0 + var/smashcooldown = 3 //deciseconds + + var/occupant_sight_flags = 0 //sight flags to give to the occupant (e.g. mech mining scanner gives meson-like vision) + var/mouse_pointer + + hud_possible = list (DIAG_STAT_HUD, DIAG_BATT_HUD, DIAG_MECH_HUD, DIAG_TRACK_HUD) + +/obj/item/radio/mech //this has to go somewhere + +/obj/mecha/Initialize() + . = ..() + events = new //Wasp - Readded for Smartwire Revert + icon_state += "-open" + add_radio() + add_cabin() + if (enclosed) + add_airtank() + spark_system.set_up(2, 0, src) + spark_system.attach(src) + smoke_system.set_up(3, src) + smoke_system.attach(src) + add_cell() + add_scanmod() + add_capacitor() + START_PROCESSING(SSobj, src) + GLOB.poi_list |= src + log_message("[src.name] created.", LOG_MECHA) + GLOB.mechas_list += src //global mech list + prepare_huds() + for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds) + diag_hud.add_to_hud(src) + diag_hud_set_mechhealth() + diag_hud_set_mechcell() + diag_hud_set_mechstat() + +/obj/mecha/update_icon_state() + if(silicon_pilot && silicon_icon_state) + icon_state = silicon_icon_state + +/obj/mecha/get_cell() + return cell + +/obj/mecha/Destroy() + if(occupant) + occupant.SetSleeping(destruction_sleep_duration) + go_out() + var/mob/living/silicon/ai/AI + for(var/mob/M in src) //Let's just be ultra sure + if(isAI(M)) + occupant = null + AI = M //AIs are loaded into the mech computer itself. When the mech dies, so does the AI. They can be recovered with an AI card from the wreck. + else + M.forceMove(loc) + for(var/obj/item/mecha_parts/mecha_equipment/E in equipment) + E.detach(loc) + qdel(E) + if(cell) + qdel(cell) + if(scanmod) + qdel(scanmod) + if(capacitor) + qdel(capacitor) + if(internal_tank) + qdel(internal_tank) + if(AI) + AI.gib() //No wreck, no AI to recover + STOP_PROCESSING(SSobj, src) + GLOB.poi_list.Remove(src) + equipment.Cut() + cell = null + scanmod = null + capacitor = null + internal_tank = null + if(loc) + loc.assume_air(cabin_air) + air_update_turf() + else + qdel(cabin_air) + cabin_air = null + qdel(spark_system) + spark_system = null + qdel(smoke_system) + smoke_system = null + + GLOB.mechas_list -= src //global mech list + return ..() + +/obj/mecha/proc/restore_equipment() + equipment_disabled = 0 + if(occupant) + SEND_SOUND(occupant, sound('sound/items/timer.ogg', volume=50)) + to_chat(occupant, "Equipment control unit has been rebooted successfully.") + occupant.update_mouse_pointer() + +/obj/mecha/CheckParts(list/parts_list) + ..() + cell = locate(/obj/item/stock_parts/cell) in contents + scanmod = locate(/obj/item/stock_parts/scanning_module) in contents + capacitor = locate(/obj/item/stock_parts/capacitor) in contents + update_part_values() + +/obj/mecha/proc/update_part_values() ///Updates the values given by scanning module and capacitor tier, called when a part is removed or inserted. + if(scanmod) + normal_step_energy_drain = 20 - (5 * scanmod.rating) //10 is normal, so on lowest part its worse, on second its ok and on higher its real good up to 0 on best + step_energy_drain = normal_step_energy_drain + else + normal_step_energy_drain = 500 + step_energy_drain = normal_step_energy_drain + if(capacitor) + armor = armor.modifyRating(energy = (capacitor.rating * 5)) //Each level of capacitor protects the mech against emp by 5% + else //because we can still be hit without a cap, even if we can't move + armor = armor.setRating(energy = 0) + + +//////////////////////// +////// Helpers ///////// +//////////////////////// + +/obj/mecha/proc/add_airtank() + internal_tank = new /obj/machinery/portable_atmospherics/canister/air(src) + return internal_tank + +///Adds a cell, for use in Map-spawned mechs, Nuke Ops mechs, and admin-spawned mechs. Mechs built by hand will replace this. +/obj/mecha/proc/add_cell(var/obj/item/stock_parts/cell/C=null) + QDEL_NULL(cell) + if(C) + C.forceMove(src) + cell = C + return + cell = new /obj/item/stock_parts/cell/high/plus(src) + +///Adds a scanning module, for use in Map-spawned mechs, Nuke Ops mechs, and admin-spawned mechs. Mechs built by hand will replace this. +/obj/mecha/proc/add_scanmod(var/obj/item/stock_parts/scanning_module/sm=null) + QDEL_NULL(scanmod) + if(sm) + sm.forceMove(src) + scanmod = sm + return + scanmod = new /obj/item/stock_parts/scanning_module(src) + +///Adds a capacitor, for use in Map-spawned mechs, Nuke Ops mechs, and admin-spawned mechs. Mechs built by hand will replace this. +/obj/mecha/proc/add_capacitor(var/obj/item/stock_parts/capacitor/cap=null) + QDEL_NULL(capacitor) + if(cap) + cap.forceMove(src) + capacitor = cap + else + capacitor = new /obj/item/stock_parts/capacitor(src) + +/obj/mecha/proc/add_cabin() + cabin_air = new + 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 + +/obj/mecha/proc/add_radio() + radio = new(src) + radio.name = "[src] radio" + radio.icon = icon + radio.icon_state = icon_state + radio.subspace_transmission = TRUE + +/obj/mecha/proc/can_use(mob/user) + if(user != occupant) + return 0 + if(user && ismob(user)) + if(!user.incapacitated()) + return 1 + return 0 + +//////////////////////////////////////////////////////////////////////////////// + +/obj/mecha/examine(mob/user) + . = ..() + var/integrity = obj_integrity*100/max_integrity + switch(integrity) + if(85 to 100) + . += "It's fully intact." + if(65 to 85) + . += "It's slightly damaged." + if(45 to 65) + . += "It's badly damaged." + if(25 to 45) + . += "It's heavily damaged." + else + . += "It's falling apart." + var/hide_weapon = locate(/obj/item/mecha_parts/concealed_weapon_bay) in contents + var/hidden_weapon = hide_weapon ? (locate(/obj/item/mecha_parts/mecha_equipment/weapon) in equipment) : null + var/list/visible_equipment = equipment - hidden_weapon + if(visible_equipment.len) + . += "It's equipped with:" + for(var/obj/item/mecha_parts/mecha_equipment/ME in visible_equipment) + . += "[icon2html(ME, user)] \A [ME]." + if(!enclosed) + if(silicon_pilot) + . += "[src] appears to be piloting itself..." + else if(occupant && occupant != user) //!silicon_pilot implied + . += "You can see [occupant] inside." + if(ishuman(user)) + var/mob/living/carbon/human/H = user + for(var/O in H.held_items) + if(istype(O, /obj/item/gun)) + . += "It looks like you can hit the pilot directly if you target the center or above." + break //in case user is holding two guns + +//processing internal damage, temperature, air regulation, alert updates, lights power use. +/obj/mecha/process() + var/internal_temp_regulation = 1 + + if(internal_damage) + if(internal_damage & MECHA_INT_FIRE) + if(!(internal_damage & MECHA_INT_TEMP_CONTROL) && prob(5)) + clearInternalDamage(MECHA_INT_FIRE) + if(internal_tank) + var/datum/gas_mixture/int_tank_air = internal_tank.return_air() + if(int_tank_air.return_pressure() > internal_tank.maximum_pressure && !(internal_damage & MECHA_INT_TANK_BREACH)) + setInternalDamage(MECHA_INT_TANK_BREACH) + if(int_tank_air && int_tank_air.return_volume() > 0) //heat the air_contents + int_tank_air.set_temperature(min(6000+T0C, int_tank_air.return_temperature()+rand(10,15))) + if(cabin_air && cabin_air.return_volume()>0) + cabin_air.set_temperature(min(6000+T0C, cabin_air.return_temperature()+rand(10,15))) + if(cabin_air.return_temperature() > max_temperature/2) + take_damage(4/round(max_temperature/cabin_air.return_temperature(),0.1), BURN, 0, 0) + + if(internal_damage & MECHA_INT_TEMP_CONTROL) + internal_temp_regulation = 0 + + if(internal_damage & MECHA_INT_TANK_BREACH) //remove some air from internal tank + if(internal_tank) + var/datum/gas_mixture/int_tank_air = internal_tank.return_air() + var/datum/gas_mixture/leaked_gas = int_tank_air.remove_ratio(0.1) + if(loc) + loc.assume_air(leaked_gas) + air_update_turf() + else + qdel(leaked_gas) + + if(internal_damage & MECHA_INT_SHORT_CIRCUIT) + if(get_charge()) + spark_system.start() + cell.charge -= min(20,cell.charge) + cell.maxcharge -= min(20,cell.maxcharge) + + if(internal_temp_regulation) + if(cabin_air && cabin_air.return_volume() > 0) + var/delta = cabin_air.return_temperature() - T20C + cabin_air.set_temperature(cabin_air.return_temperature() - max(-10, min(10, round(delta/4,0.1)))) + + if(internal_tank) + var/datum/gas_mixture/tank_air = internal_tank.return_air() + + var/release_pressure = internal_tank_valve + var/cabin_pressure = cabin_air.return_pressure() + var/pressure_delta = min(release_pressure - cabin_pressure, (tank_air.return_pressure() - cabin_pressure)/2) + var/transfer_moles = 0 + if(pressure_delta > 0) //cabin pressure lower than release pressure + if(tank_air.return_temperature() > 0) + transfer_moles = pressure_delta*cabin_air.return_volume()/(cabin_air.return_temperature() * R_IDEAL_GAS_EQUATION) + var/datum/gas_mixture/removed = tank_air.remove(transfer_moles) + cabin_air.merge(removed) + else if(pressure_delta < 0) //cabin pressure higher than release pressure + var/datum/gas_mixture/t_air = return_air() + pressure_delta = cabin_pressure - release_pressure + if(t_air) + pressure_delta = min(cabin_pressure - t_air.return_pressure(), pressure_delta) + if(pressure_delta > 0) //if location pressure is lower than cabin pressure + transfer_moles = pressure_delta*cabin_air.return_volume()/(cabin_air.return_temperature() * R_IDEAL_GAS_EQUATION) + var/datum/gas_mixture/removed = cabin_air.remove(transfer_moles) + if(t_air) + t_air.merge(removed) + else //just delete the cabin gas, we're in space or some shit + qdel(removed) + + if(occupant) + if(cell) + var/cellcharge = cell.charge/cell.maxcharge + switch(cellcharge) + if(0.75 to INFINITY) + occupant.clear_alert("charge") + if(0.5 to 0.75) + occupant.throw_alert("charge", /obj/screen/alert/lowcell, 1) + if(0.25 to 0.5) + occupant.throw_alert("charge", /obj/screen/alert/lowcell, 2) + if(0.01 to 0.25) + occupant.throw_alert("charge", /obj/screen/alert/lowcell, 3) + else + occupant.throw_alert("charge", /obj/screen/alert/emptycell) + + var/integrity = obj_integrity/max_integrity*100 + switch(integrity) + if(30 to 45) + occupant.throw_alert("mech damage", /obj/screen/alert/low_mech_integrity, 1) + if(15 to 35) + occupant.throw_alert("mech damage", /obj/screen/alert/low_mech_integrity, 2) + if(-INFINITY to 15) + occupant.throw_alert("mech damage", /obj/screen/alert/low_mech_integrity, 3) + else + occupant.clear_alert("mech damage") + var/atom/checking = occupant.loc + // recursive check to handle all cases regarding very nested occupants, + // such as brainmob inside brainitem inside MMI inside mecha + while (!isnull(checking)) + if (isturf(checking)) + // hit a turf before hitting the mecha, seems like they have + // been moved out + occupant.clear_alert("charge") + occupant.clear_alert("mech damage") + RemoveActions(occupant, human_occupant=1) + occupant = null + break + else if (checking == src) + break // all good + checking = checking.loc + + if(lights) + var/lights_energy_drain = 2 + use_power(lights_energy_drain) + + if(!enclosed && occupant?.incapacitated()) //no sides mean it's easy to just sorta fall out if you're incapacitated. + visible_message("[occupant] tumbles out of the cockpit!") + go_out() //Maybe we should install seat belts? + +//Diagnostic HUD updates + diag_hud_set_mechhealth() + diag_hud_set_mechcell() + diag_hud_set_mechstat() + +/obj/mecha/fire_act() //Check if we should ignite the pilot of an open-canopy mech + . = ..() + if (occupant && !enclosed && !silicon_pilot) + if (occupant.fire_stacks < 5) + occupant.fire_stacks += 1 + occupant.IgniteMob() + +/obj/mecha/proc/drop_item()//Derpfix, but may be useful in future for engineering exosuits. + return + +/obj/mecha/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode) + . = ..() + if(speaker == occupant) + if(radio?.broadcasting) + radio.talk_into(speaker, text, , spans, message_language) + //flick speech bubble + var/list/speech_bubble_recipients = list() + for(var/mob/M in get_hearers_in_view(7,src)) + if(M.client) + speech_bubble_recipients.Add(M.client) + INVOKE_ASYNC(GLOBAL_PROC, /proc/flick_overlay, image('icons/mob/talk.dmi', src, "machine[say_test(raw_message)]",MOB_LAYER+1), speech_bubble_recipients, 30) + +//////////////////////////// +///// Action processing //// +//////////////////////////// + + +/obj/mecha/proc/click_action(atom/target,mob/user,params) + if(!occupant || occupant != user ) + return + if(!locate(/turf) in list(target,target.loc)) // Prevents inventory from being drilled + return + if(completely_disabled) + return + if(is_currently_ejecting) + return + if(phasing) + occupant_message("Unable to interact with objects while phasing.") + return + if(user.incapacitated()) + return + if(construction_state) + occupant_message("Maintenance protocols in effect.") + return + if(!get_charge()) + return + if(src == target) + return + var/dir_to_target = get_dir(src,target) + if(dir_to_target && !(dir_to_target & dir))//wrong direction + return + if(internal_damage & MECHA_INT_CONTROL_LOST) + if (!target) + return + target = pick(view(3,target)) + + var/mob/living/L = user + if(!Adjacent(target)) + if(selected && selected.is_ranged()) + if(HAS_TRAIT(L, TRAIT_PACIFISM) && selected.harmful) + to_chat(user, "You don't want to harm other living beings!") + return + if(selected.action(target,params)) + selected.start_cooldown() + else if(selected && selected.is_melee()) + if(isliving(target) && selected.harmful && HAS_TRAIT(L, TRAIT_PACIFISM)) + to_chat(user, "You don't want to harm other living beings!") + return + if(selected.action(target,params)) + selected.start_cooldown() + else + if(internal_damage & MECHA_INT_CONTROL_LOST) + var/list/possible_targets = oview(1,src) + if (!length(possible_targets)) + return + target = pick(possible_targets) + if(!melee_can_hit || !istype(target, /atom)) + return + target.mech_melee_attack(src) + melee_can_hit = FALSE + addtimer(VARSET_CALLBACK(src, melee_can_hit, TRUE), melee_cooldown) + + +/obj/mecha/proc/range_action(atom/target) + return + + +////////////////////////////////// +//////// Movement procs //////// +////////////////////////////////// + +///Plays the mech step sound effect. Split from movement procs so that other mechs (HONK) can override this one specific part. +/obj/mecha/proc/play_stepsound() + if(stepsound) + playsound(src,stepsound,40,1) + +/obj/mecha/Move(atom/newloc, direct) + . = ..() + if(.) + events.fireEvent("onMove",get_turf(src)) //Wasp - Readded for Smartwire Revert + if (internal_tank?.disconnect()) // Something moved us and broke connection + occupant_message("Air port connection has been severed!") + log_message("Lost connection to gas port.", LOG_MECHA) + +/obj/mecha/Process_Spacemove(var/movement_dir = 0) + . = ..() + if(.) + return TRUE + + var/atom/movable/backup = get_spacemove_backup() + if(backup) + if(istype(backup) && movement_dir && !backup.anchored) + if(backup.newtonian_move(turn(movement_dir, 180))) + step_silent = TRUE + if(occupant) + to_chat(occupant, "You push off [backup] to propel yourself.") + return TRUE + + if(can_move <= world.time && active_thrusters && movement_dir && active_thrusters.thrust(movement_dir)) + step_silent = TRUE + return TRUE + + return FALSE + +/obj/mecha/relaymove(mob/user,direction) + if(completely_disabled) + return + if(!direction) + return + if(user != occupant) //While not "realistic", this piece is player friendly. + user.forceMove(get_turf(src)) + to_chat(user, "You climb out from [src].") + return 0 + if(internal_tank?.connected_port) + if(world.time - last_message > 20) + occupant_message("Unable to move while connected to the air system port!") + last_message = world.time + return 0 + if(construction_state) + if(world.time - last_message > 20) + occupant_message("Maintenance protocols in effect.") + last_message = world.time + return + return domove(direction) + +/obj/mecha/proc/domove(direction) + if(can_move >= world.time) + return 0 + if(!Process_Spacemove(direction)) + return 0 + if(!has_charge(step_energy_drain)) + return 0 + if(zoom_mode) + if(world.time - last_message > 20) + occupant_message("Unable to move while in zoom mode!") + last_message = world.time + return 0 + if(!cell) + if(world.time - last_message > 20) + occupant_message("Missing power cell.") + last_message = world.time + return 0 + if(!scanmod || !capacitor) + if(world.time - last_message > 20) + occupant_message("Missing [scanmod? "capacitor" : "scanning module"].") + last_message = world.time + return 0 + + var/move_result = 0 + var/oldloc = loc + if(internal_damage & MECHA_INT_CONTROL_LOST) + move_result = mechsteprand() + else if(dir != direction && (!strafe || occupant.client.keys_held["Alt"])) + move_result = mechturn(direction) + else + move_result = mechstep(direction) + if(move_result || loc != oldloc)// halfway done diagonal move still returns false + use_power(step_energy_drain) + can_move = world.time + step_in + return 1 + return 0 + +/obj/mecha/proc/mechturn(direction) + setDir(direction) + if(turnsound) + playsound(src,turnsound,40,TRUE) + return 1 + +/obj/mecha/proc/mechstep(direction) + var/current_dir = dir + . = step(src,direction) + if(strafe) + setDir(current_dir) + if(. && !step_silent) + play_stepsound() + step_silent = FALSE + +/obj/mecha/proc/mechsteprand() + . = step_rand(src) + if(. && !step_silent) + play_stepsound() + step_silent = FALSE + +/obj/mecha/Bump(var/atom/obstacle) + if(phasing && get_charge() >= phasing_energy_drain && !throwing) + if(!can_move) + return + can_move = 0 + if(phase_state) + flick(phase_state, src) + forceMove(get_step(src,dir)) + use_power(phasing_energy_drain) + addtimer(VARSET_CALLBACK(src, can_move, TRUE), step_in*3) + else + if(..()) //mech was thrown + return + if(bumpsmash && occupant) //Need a pilot to push the PUNCH button. + if(nextsmash < world.time) + obstacle.mech_melee_attack(src) + nextsmash = world.time + smashcooldown + if(!obstacle || obstacle.CanPass(src,get_step(src,dir))) + step(src,dir) + if(isobj(obstacle)) + var/obj/O = obstacle + if(!O.anchored && O.move_resist <= move_force) + step(obstacle, dir) + else if(ismob(obstacle)) + var/mob/M = obstacle + if(M.move_resist <= move_force) + step(obstacle, dir) + + + + + +/////////////////////////////////// +//////// Internal damage //////// +/////////////////////////////////// + +/obj/mecha/proc/check_for_internal_damage(list/possible_int_damage,ignore_threshold=null) + if(!islist(possible_int_damage) || !length(possible_int_damage)) + return + if(prob(20)) + if(ignore_threshold || obj_integrity*100/max_integrity < internal_damage_threshold) + for(var/T in possible_int_damage) + if(internal_damage & T) + possible_int_damage -= T + if (length(possible_int_damage)) + var/int_dam_flag = pick(possible_int_damage) + if(int_dam_flag) + setInternalDamage(int_dam_flag) + if(prob(5)) + if(ignore_threshold || obj_integrity*100/max_integrity < internal_damage_threshold) + if (length(equipment)) + var/obj/item/mecha_parts/mecha_equipment/ME = pick(equipment) + qdel(ME) + +/obj/mecha/proc/setInternalDamage(int_dam_flag) + internal_damage |= int_dam_flag + log_message("Internal damage of type [int_dam_flag].", LOG_MECHA) + SEND_SOUND(occupant, sound('sound/machines/warning-buzzer.ogg',wait=0)) + diag_hud_set_mechstat() + +/obj/mecha/proc/clearInternalDamage(int_dam_flag) + if(internal_damage & int_dam_flag) + switch(int_dam_flag) + if(MECHA_INT_TEMP_CONTROL) + occupant_message("Life support system reactivated.") + if(MECHA_INT_FIRE) + occupant_message("Internal fire extinquished.") + if(MECHA_INT_TANK_BREACH) + occupant_message("Damaged internal tank has been sealed.") + internal_damage &= ~int_dam_flag + diag_hud_set_mechstat() + +///////////////////////////////////// +//////////// AI piloting //////////// +///////////////////////////////////// + +/obj/mecha/attack_ai(mob/living/silicon/ai/user) + if(!isAI(user)) + return + //Allows the Malf to scan a mech's status and loadout, helping it to decide if it is a worthy chariot. + if(user.can_dominate_mechs) + examine(user) //Get diagnostic information! + for(var/obj/item/mecha_parts/mecha_tracking/B in trackers) + to_chat(user, "Warning: Tracking Beacon detected. Enter at your own risk. Beacon Data:") + to_chat(user, "[B.get_mecha_info()]") + break + //Nothing like a big, red link to make the player feel powerful! + to_chat(user, "ASSUME DIRECT CONTROL?
                    ") + else + examine(user) + if(occupant) + to_chat(user, "This exosuit has a pilot and cannot be controlled.") + return + var/can_control_mech = 0 + for(var/obj/item/mecha_parts/mecha_tracking/ai_control/A in trackers) + can_control_mech = 1 + to_chat(user, "[icon2html(src, user)] Status of [name]:\n[A.get_mecha_info()]") + break + if(!can_control_mech) + to_chat(user, "You cannot control exosuits without AI control beacons installed.") + return + to_chat(user, "Take control of exosuit?
                    ") + +/obj/mecha/transfer_ai(interaction, mob/user, mob/living/silicon/ai/AI, obj/item/aicard/card) + if(!..()) + return + + //Transfer from core or card to mech. Proc is called by mech. + switch(interaction) + if(AI_TRANS_TO_CARD) //Upload AI from mech to AI card. + if(!construction_state) //Mech must be in maint mode to allow carding. + to_chat(user, "[name] must have maintenance protocols active in order to allow a transfer.") + return + AI = occupant + if(!AI || !isAI(occupant)) //Mech does not have an AI for a pilot + to_chat(user, "No AI detected in the [name] onboard computer.") + return + AI.ai_restore_power()//So the AI initially has power. + AI.control_disabled = TRUE + AI.radio_enabled = FALSE + AI.disconnect_shell() + RemoveActions(AI, TRUE) + occupant = null + silicon_pilot = FALSE + AI.forceMove(card) + card.AI = AI + AI.controlled_mech = null + AI.remote_control = null + icon_state = initial(icon_state)+"-open" + to_chat(AI, "You have been downloaded to a mobile storage device. Wireless connection offline.") + to_chat(user, "Transfer successful: [AI.name] ([rand(1000,9999)].exe) removed from [name] and stored within local memory.") + + if(AI_MECH_HACK) //Called by AIs on the mech + AI.linked_core = new /obj/structure/AIcore/deactivated(AI.loc) + if(AI.can_dominate_mechs) + if(occupant) //Oh, I am sorry, were you using that? + to_chat(AI, "Pilot detected! Forced ejection initiated!") + to_chat(occupant, "You have been forcibly ejected!") + go_out(1) //IT IS MINE, NOW. SUCK IT, RD! + ai_enter_mech(AI, interaction) + + if(AI_TRANS_FROM_CARD) //Using an AI card to upload to a mech. + AI = card.AI + if(!AI) + to_chat(user, "There is no AI currently installed on this device.") + return + if(AI.deployed_shell) //Recall AI if shelled so it can be checked for a client + AI.disconnect_shell() + if(AI.stat || !AI.client) + to_chat(user, "[AI.name] is currently unresponsive, and cannot be uploaded.") + return + if(occupant || dna_lock) //Normal AIs cannot steal mechs! + to_chat(user, "Access denied. [name] is [occupant ? "currently occupied" : "secured with a DNA lock"].") + return + AI.control_disabled = FALSE + AI.radio_enabled = TRUE + to_chat(user, "Transfer successful: [AI.name] ([rand(1000,9999)].exe) installed and executed successfully. Local copy has been removed.") + card.AI = null + ai_enter_mech(AI, interaction) + +//Hack and From Card interactions share some code, so leave that here for both to use. +/obj/mecha/proc/ai_enter_mech(mob/living/silicon/ai/AI, interaction) + AI.ai_restore_power() + AI.forceMove(src) + occupant = AI + silicon_pilot = TRUE + icon_state = initial(icon_state) + update_icon() + playsound(src, 'sound/machines/windowdoor.ogg', 50, TRUE) + if(!internal_damage) + SEND_SOUND(occupant, sound('sound/mecha/nominal.ogg',volume=50)) + AI.cancel_camera() + AI.controlled_mech = src + AI.remote_control = src + AI.mobility_flags = ALL //Much easier than adding AI checks! Be sure to set this back to 0 if you decide to allow an AI to leave a mech somehow. + AI.can_shunt = 0 //ONE AI ENTERS. NO AI LEAVES. + to_chat(AI, AI.can_dominate_mechs ? "Takeover of [name] complete! You are now loaded onto the onboard computer. Do not attempt to leave the station sector!" :\ + "You have been uploaded to a mech's onboard computer.") + to_chat(AI, "Use Middle-Mouse to activate mech functions and equipment. Click normally for AI interactions.") + if(interaction == AI_TRANS_FROM_CARD) + GrantActions(AI, FALSE) //No eject/return to core action for AI uploaded by card + else + GrantActions(AI, !AI.can_dominate_mechs) + + +//An actual AI (simple_animal mecha pilot) entering the mech +/obj/mecha/proc/aimob_enter_mech(mob/living/simple_animal/hostile/syndicate/mecha_pilot/pilot_mob) + if(pilot_mob && pilot_mob.Adjacent(src)) + if(occupant) + return + icon_state = initial(icon_state) + occupant = pilot_mob + pilot_mob.mecha = src + pilot_mob.forceMove(src) + GrantActions(pilot_mob)//needed for checks, and incase a badmin puts somebody in the mob + +/obj/mecha/proc/aimob_exit_mech(mob/living/simple_animal/hostile/syndicate/mecha_pilot/pilot_mob) + if(occupant == pilot_mob) + occupant = null + if(pilot_mob.mecha == src) + pilot_mob.mecha = null + icon_state = "[initial(icon_state)]-open" + pilot_mob.forceMove(get_turf(src)) + RemoveActions(pilot_mob) + + +///////////////////////////////////// +//////// Atmospheric stuff //////// +///////////////////////////////////// + +/obj/mecha/remove_air(amount) + if(use_internal_tank) + return cabin_air.remove(amount) + return ..() + +/obj/mecha/return_air() + if(use_internal_tank) + return cabin_air + return ..() + +/obj/mecha/return_analyzable_air() + return cabin_air + +/obj/mecha/proc/return_pressure() + var/datum/gas_mixture/t_air = return_air() + if(t_air) + . = t_air.return_pressure() + +/obj/mecha/return_temperature() + var/datum/gas_mixture/t_air = return_air() + if(t_air) + . = t_air.return_temperature() + +/obj/mecha/MouseDrop_T(mob/M, mob/user) + if((user != M) || user.incapacitated() || !Adjacent(user)) + return + if(!ishuman(user)) // no silicons or drones in mechas. + return + if(HAS_TRAIT(user, TRAIT_PRIMITIVE)) //no lavalizards either. + to_chat(user, "The knowledge to use this device eludes you!") + return + log_message("[user] tries to move in.", LOG_MECHA) + if (occupant) + to_chat(usr, "The [name] is already occupied!") + log_message("Permission denied (Occupied).", LOG_MECHA) + return + if(dna_lock) + var/passed = FALSE + if(user.has_dna()) + var/mob/living/carbon/C = user + if(C.dna.unique_enzymes==dna_lock) + passed = TRUE + if (!passed) + to_chat(user, "Access denied. [name] is secured with a DNA lock.") + log_message("Permission denied (DNA LOCK).", LOG_MECHA) + return + if(!operation_allowed(user)) + to_chat(user, "Access denied. Insufficient operation keycodes.") + log_message("Permission denied (No keycode).", LOG_MECHA) + return + if(user.buckled) + to_chat(user, "You are currently buckled and cannot move.") + log_message("Permission denied (Buckled).", LOG_MECHA) + return + if(user.has_buckled_mobs()) //mob attached to us + to_chat(user, "You can't enter the exosuit with other creatures attached to you!") + log_message("Permission denied (Attached mobs).", LOG_MECHA) + return + + visible_message("[user] starts to climb into [name].") + + if(do_after(user, enter_delay, target = src)) + if(obj_integrity <= 0) + to_chat(user, "You cannot get in the [name], it has been destroyed!") + else if(occupant) + to_chat(user, "[occupant] was faster! Try better next time, loser.") + else if(user.buckled) + to_chat(user, "You can't enter the exosuit while buckled.") + else if(user.has_buckled_mobs()) + to_chat(user, "You can't enter the exosuit with other creatures attached to you!") + else + moved_inside(user) + else + to_chat(user, "You stop entering the exosuit!") + +/obj/mecha/proc/moved_inside(mob/living/carbon/human/H) + . = FALSE + if(H && H.client && (H in range(1))) + occupant = H + H.forceMove(src) + H.update_mouse_pointer() + add_fingerprint(H) + GrantActions(H, human_occupant=1) + forceMove(loc) + log_message("[H] moved in as pilot.", LOG_MECHA) + icon_state = initial(icon_state) + setDir(dir_in) + playsound(src, 'sound/machines/windowdoor.ogg', 50, TRUE) + if(!internal_damage) + SEND_SOUND(occupant, sound('sound/mecha/nominal.ogg',volume=50)) + return TRUE + +/obj/mecha/proc/mmi_move_inside(obj/item/mmi/M, mob/user) + . = FALSE + if(!M.brain_check(user)) + return + + var/mob/living/brain/B = M.brainmob + if(occupant) + to_chat(user, "Occupant detected!") + return + if(dna_lock && (!B.stored_dna || (dna_lock != B.stored_dna.unique_enzymes))) + to_chat(user, "Access denied. [name] is secured with a DNA lock.") + return + + visible_message("[user] starts to insert an MMI into [name].") + + if(do_after(user, 40, target = src)) + if(!occupant) + return mmi_moved_inside(M, user) + else + to_chat(user, "Occupant detected!") + else + to_chat(user, "You stop inserting the MMI.") + +/obj/mecha/proc/mmi_moved_inside(obj/item/mmi/M, mob/user) + if(!(Adjacent(M) && Adjacent(user))) + return FALSE + if(!M.brain_check(user)) + return FALSE + + var/mob/living/brain/B = M.brainmob + if(!user.transferItemToLoc(M, src)) + to_chat(user, "\the [M] is stuck to your hand, you cannot put it in \the [src]!") + return FALSE + + M.mecha = src + occupant = B + silicon_pilot = TRUE + B.forceMove(src) //should allow relaymove + B.reset_perspective(src) + B.remote_control = src + B.update_mobility() + B.update_mouse_pointer() + icon_state = initial(icon_state) + update_icon() + setDir(dir_in) + log_message("[M] moved in as pilot.", LOG_MECHA) + if(!internal_damage) + SEND_SOUND(occupant, sound('sound/mecha/nominal.ogg',volume=50)) + GrantActions(B) + log_game("[key_name(user)] has put the MMI/posibrain of [key_name(B)] into [src] at [AREACOORD(src)]") + return TRUE + +/obj/mecha/container_resist(mob/living/user) + is_currently_ejecting = TRUE + to_chat(occupant, "You begin the ejection procedure. Equipment is disabled during this process. Hold still to finish ejecting.") + if(do_after(occupant, has_gravity() ? exit_delay : 0 , target = src)) + to_chat(occupant, "You exit the mech.") + go_out() + else + to_chat(occupant, "You stop exiting the mech. Weapons are enabled again.") + is_currently_ejecting = FALSE + +/obj/mecha/Exited(atom/movable/M, atom/newloc) + if(occupant && occupant == M) // The occupant exited the mech without calling go_out() + go_out(TRUE, newloc) + + if(cell && cell == M) + cell = null + return + if(scanmod && scanmod == M) + scanmod = null + update_part_values() + return + if(capacitor && capacitor == M) + armor = armor.modifyRating(energy = (capacitor.rating * -5)) //lose the energy armor if we lose this cap + capacitor = null + update_part_values() + +/obj/mecha/proc/go_out(forced, atom/newloc = loc) + if(!occupant) + return + var/atom/movable/mob_container + occupant.clear_alert("charge") + occupant.clear_alert("mech damage") + if(ishuman(occupant)) + mob_container = occupant + RemoveActions(occupant, human_occupant=1) + else if(isbrain(occupant)) + var/mob/living/brain/brain = occupant + RemoveActions(brain) + mob_container = brain.container + else if(isAI(occupant)) + var/mob/living/silicon/ai/AI = occupant + if(forced)//This should only happen if there are multiple AIs in a round, and at least one is Malf. + RemoveActions(occupant) + occupant.gib() //If one Malf decides to steal a mech from another AI (even other Malfs!), they are destroyed, as they have nowhere to go when replaced. + occupant = null + silicon_pilot = FALSE + return + else + if(!AI.linked_core) + to_chat(AI, "Inactive core destroyed. Unable to return.") + AI.linked_core = null + return + to_chat(AI, "Returning to core...") + AI.controlled_mech = null + AI.remote_control = null + RemoveActions(occupant, 1) + mob_container = AI + newloc = get_turf(AI.linked_core) + qdel(AI.linked_core) + else + return + var/mob/living/L = occupant + occupant = null //we need it null when forceMove calls Exited(). + silicon_pilot = FALSE + if(mob_container.forceMove(newloc))//ejecting mob container + log_message("[mob_container] moved out.", LOG_MECHA) + L << browse(null, "window=exosuit") + + if(istype(mob_container, /obj/item/mmi)) + var/obj/item/mmi/mmi = mob_container + if(mmi.brainmob) + L.forceMove(mmi) + L.reset_perspective() + mmi.mecha = null + mmi.update_icon() + L.mobility_flags = NONE + icon_state = initial(icon_state)+"-open" + setDir(dir_in) + + if(L && L.client) + L.update_mouse_pointer() + L.client.change_view(CONFIG_GET(string/default_view)) + zoom_mode = 0 + +///////////////////////// +////// Access stuff ///// +///////////////////////// + +/obj/mecha/proc/operation_allowed(mob/M) + req_access = operation_req_access + req_one_access = list() + return allowed(M) + +/obj/mecha/proc/internals_access_allowed(mob/M) + req_one_access = internals_req_access + req_access = list() + return allowed(M) + + + +//////////////////////////////// +/////// Messages and Log /////// +//////////////////////////////// + +/obj/mecha/proc/occupant_message(message as text) + if(message) + if(occupant && occupant.client) + to_chat(occupant, "[icon2html(src, occupant)] [message]") + +GLOBAL_VAR_INIT(year, time2text(world.realtime,"YYYY")) +GLOBAL_VAR_INIT(year_integer, text2num(year)) // = 2013??? + +/////////////////////// +///// Power stuff ///// +/////////////////////// + +/obj/mecha/proc/has_charge(amount) + return (get_charge()>=amount) + +/obj/mecha/proc/get_charge() + for(var/obj/item/mecha_parts/mecha_equipment/tesla_energy_relay/R in equipment) + var/relay_charge = R.get_charge() + if(relay_charge) + return relay_charge + if(cell) + return max(0, cell.charge) + +/obj/mecha/proc/use_power(amount) + if(get_charge() && cell.use(amount)) + return 1 + return 0 + +/obj/mecha/proc/give_power(amount) + if(!isnull(get_charge())) + cell.give(amount) + return 1 + return 0 + +/obj/mecha/update_remote_sight(mob/living/user) + if(occupant_sight_flags) + if(user == occupant) + user.sight |= occupant_sight_flags + +/////////////////////// +////// Ammo stuff ///// +/////////////////////// + +/obj/mecha/proc/ammo_resupply(var/obj/item/mecha_ammo/A, mob/user,var/fail_chat_override = FALSE) + if(!A.rounds) + if(!fail_chat_override) + to_chat(user, "This box of ammo is empty!") + return FALSE + var/ammo_needed + var/found_gun + for(var/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/gun in equipment) + ammo_needed = 0 + + if(istype(gun, /obj/item/mecha_parts/mecha_equipment/weapon/ballistic) && gun.ammo_type == A.ammo_type) + found_gun = TRUE + if(A.direct_load) + ammo_needed = initial(gun.projectiles) - gun.projectiles + else + ammo_needed = gun.projectiles_cache_max - gun.projectiles_cache + + if(ammo_needed) + if(ammo_needed < A.rounds) + if(A.direct_load) + gun.projectiles = gun.projectiles + ammo_needed + else + gun.projectiles_cache = gun.projectiles_cache + ammo_needed + playsound(get_turf(user),A.load_audio,50,TRUE) + to_chat(user, "You add [ammo_needed] [A.round_term][ammo_needed > 1?"s":""] to the [gun.name]") + A.rounds = A.rounds - ammo_needed + A.update_name() + return TRUE + + else + if(A.direct_load) + gun.projectiles = gun.projectiles + A.rounds + else + gun.projectiles_cache = gun.projectiles_cache + A.rounds + playsound(get_turf(user),A.load_audio,50,TRUE) + to_chat(user, "You add [A.rounds] [A.round_term][A.rounds > 1?"s":""] to the [gun.name]") + A.rounds = 0 + A.update_name() + return TRUE + if(!fail_chat_override) + if(found_gun) + to_chat(user, "You can't fit any more ammo of this type!") + else + to_chat(user, "None of the equipment on this exosuit can use this ammo!") + return FALSE diff --git a/code/game/mecha/mecha_construction_paths.dm b/code/game/mecha/mecha_construction_paths.dm index f64eda0e9f59..5733465ae7a1 100644 --- a/code/game/mecha/mecha_construction_paths.dm +++ b/code/game/mecha/mecha_construction_paths.dm @@ -1,1325 +1,1325 @@ -//////////////////////////////// -///// Construction datums ////// -//////////////////////////////// -/datum/component/construction/mecha - var/base_icon - - // Component typepaths. - // most must be defined unless - // get_steps is overriden. - - // Circuit board typepaths. - // circuit_control and circuit_periph must be defined - // unless get_circuit_steps is overriden. - var/circuit_control - var/circuit_periph - var/circuit_weapon - - // Armor plating typepaths. both must be defined - // unless relevant step procs are overriden. amounts - // must be defined if using /obj/item/stack/sheet types - var/inner_plating - var/inner_plating_amount - - var/outer_plating - var/outer_plating_amount - -/datum/component/construction/mecha/spawn_result() - if(!result) - return - // Remove default mech power cell, as we replace it with a new one. - var/obj/mecha/M = new result(drop_location()) - QDEL_NULL(M.cell) - QDEL_NULL(M.scanmod) - QDEL_NULL(M.capacitor) - - var/obj/item/mecha_parts/chassis/parent_chassis = parent - M.CheckParts(parent_chassis.contents) - - SSblackbox.record_feedback("tally", "mechas_created", 1, M.name) - QDEL_NULL(parent) - -// Default proc to generate mech steps. -// Override if the mech needs an entirely custom process (See HONK mech) -// Otherwise override specific steps as needed (Ripley, firefighter, Phazon) -/datum/component/construction/mecha/proc/get_steps() - return get_frame_steps() + get_circuit_steps() + (circuit_weapon ? get_circuit_weapon_steps() : list()) + get_stockpart_steps() + get_inner_plating_steps() + get_outer_plating_steps() - -/datum/component/construction/mecha/update_parent(step_index) - steps = get_steps() - ..() - // By default, each step in mech construction has a single icon_state: - // "[base_icon][index - 1]" - // For example, Ripley's step 1 icon_state is "ripley0". - var/atom/parent_atom = parent - if(!steps[index]["icon_state"] && base_icon) - parent_atom.icon_state = "[base_icon][index - 1]" - -/datum/component/construction/unordered/mecha_chassis/custom_action(obj/item/I, mob/living/user, typepath) - . = user.transferItemToLoc(I, parent) - if(.) - var/atom/parent_atom = parent - user.visible_message("[user] connects [I] to [parent].", "You connect [I] to [parent].") - parent_atom.add_overlay(I.icon_state+"+o") - qdel(I) - -/datum/component/construction/unordered/mecha_chassis/spawn_result() - var/atom/parent_atom = parent - parent_atom.icon = 'icons/mecha/mech_construction.dmi' - parent_atom.density = TRUE - parent_atom.cut_overlays() - ..() - -// Default proc for the first steps of mech construction. -/datum/component/construction/mecha/proc/get_frame_steps() - return list( - list( - "key" = TOOL_WRENCH, - "desc" = "The hydraulic systems are disconnected." - ), - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_WRENCH, - "desc" = "The hydraulic systems are connected." - ), - list( - "key" = /obj/item/stack/cable_coil, - "amount" = 5, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "The hydraulic systems are active." - ), - list( - "key" = TOOL_WIRECUTTER, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "The wiring is added." - ) - ) - -// Default proc for the circuit board steps of a mech. -// Second set of steps by default. -/datum/component/construction/mecha/proc/get_circuit_steps() - return list( - list( - "key" = circuit_control, - "action" = ITEM_DELETE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "The wiring is adjusted." - ), - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "Central control module is installed." - ), - list( - "key" = circuit_periph, - "action" = ITEM_DELETE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Central control module is secured." - ), - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "Peripherals control module is installed." - ) - ) - -// Default proc for weapon circuitboard steps -// Used by combat mechs -/datum/component/construction/mecha/proc/get_circuit_weapon_steps() - return list( - list( - "key" = circuit_weapon, - "action" = ITEM_DELETE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Peripherals control module is secured." - ), - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "Weapons control module is installed." - ) - ) - -// Default proc for stock part installation -// Third set of steps by default -/datum/component/construction/mecha/proc/get_stockpart_steps() - var/prevstep_text = circuit_weapon ? "Weapons control module is secured." : "Peripherals control module is secured." - return list( - list( - "key" = /obj/item/stock_parts/scanning_module, - "action" = ITEM_MOVE_INSIDE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = prevstep_text - ), - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "Scanner module is installed." - ), - list( - "key" = /obj/item/stock_parts/capacitor, - "action" = ITEM_MOVE_INSIDE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Scanner module is secured." - ), - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "Capacitor is installed." - ), - list( - "key" = /obj/item/stock_parts/cell, - "action" = ITEM_MOVE_INSIDE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Capacitor is secured." - ), - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "The power cell is installed." - ) - ) - -// Default proc for inner armor plating -// Fourth set of steps by default -/datum/component/construction/mecha/proc/get_inner_plating_steps() - var/list/first_step - if(ispath(inner_plating, /obj/item/stack/sheet)) - first_step = list( - list( - "key" = inner_plating, - "amount" = inner_plating_amount, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "The power cell is secured." - ) - ) - else - first_step = list( - list( - "key" = inner_plating, - "action" = ITEM_DELETE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "The power cell is secured." - ) - ) - - return first_step + list( - list( - "key" = TOOL_WRENCH, - "back_key" = TOOL_CROWBAR, - "desc" = "Inner plating is installed." - ), - list( - "key" = TOOL_WELDER, - "back_key" = TOOL_WRENCH, - "desc" = "Inner Plating is wrenched." - ) - ) - -// Default proc for outer armor plating -// Fifth set of steps by default -/datum/component/construction/mecha/proc/get_outer_plating_steps() - var/list/first_step - if(ispath(outer_plating, /obj/item/stack/sheet)) - first_step = list( - list( - "key" = outer_plating, - "amount" = outer_plating_amount, - "back_key" = TOOL_WELDER, - "desc" = "Inner plating is welded." - ) - ) - else - first_step = list( - list( - "key" = outer_plating, - "action" = ITEM_DELETE, - "back_key" = TOOL_WELDER, - "desc" = "Inner plating is welded." - ) - ) - - return first_step + list( - list( - "key" = TOOL_WRENCH, - "back_key" = TOOL_CROWBAR, - "desc" = "External armor is installed." - ), - list( - "key" = TOOL_WELDER, - "back_key" = TOOL_WRENCH, - "desc" = "External armor is wrenched." - ) - ) - - -/datum/component/construction/unordered/mecha_chassis/ripley - result = /datum/component/construction/mecha/ripley - steps = list( - /obj/item/mecha_parts/part/ripley_torso, - /obj/item/mecha_parts/part/ripley_left_arm, - /obj/item/mecha_parts/part/ripley_right_arm, - /obj/item/mecha_parts/part/ripley_left_leg, - /obj/item/mecha_parts/part/ripley_right_leg - ) - -/datum/component/construction/mecha/ripley - result = /obj/mecha/working/ripley - base_icon = "ripley" - - circuit_control = /obj/item/circuitboard/mecha/ripley/main - circuit_periph = /obj/item/circuitboard/mecha/ripley/peripherals - - inner_plating=/obj/item/stack/sheet/metal - inner_plating_amount = 5 - - outer_plating=/obj/item/stack/rods - outer_plating_amount = 10 - -/datum/component/construction/mecha/ripley/get_outer_plating_steps() - return list( - list( - "key" = /obj/item/stack/rods, - "amount" = 10, - "back_key" = TOOL_WELDER, - "desc" = "Outer Plating is welded." - ), - list( - "key" = TOOL_WELDER, - "back_key" = TOOL_WIRECUTTER, - "desc" = "Cockpit wire screen is installed." - ), - ) - -/datum/component/construction/mecha/ripley/custom_action(obj/item/I, mob/living/user, diff) - if(!..()) - return FALSE - - switch(index) - if(1) - user.visible_message("[user] connects [parent] hydraulic systems.", "You connect [parent] hydraulic systems.") - if(2) - if(diff==FORWARD) - user.visible_message("[user] activates [parent] hydraulic systems.", "You activate [parent] hydraulic systems.") - else - user.visible_message("[user] disconnects [parent] hydraulic systems.", "You disconnect [parent] hydraulic systems.") - if(3) - if(diff==FORWARD) - user.visible_message("[user] adds the wiring to [parent].", "You add the wiring to [parent].") - else - user.visible_message("[user] deactivates [parent] hydraulic systems.", "You deactivate [parent] hydraulic systems.") - if(4) - if(diff==FORWARD) - user.visible_message("[user] adjusts the wiring of [parent].", "You adjust the wiring of [parent].") - else - user.visible_message("[user] removes the wiring from [parent].", "You remove the wiring from [parent].") - if(5) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] disconnects the wiring of [parent].", "You disconnect the wiring of [parent].") - if(6) - if(diff==FORWARD) - user.visible_message("[user] secures the mainboard.", "You secure the mainboard.") - else - user.visible_message("[user] removes the central control module from [parent].", "You remove the central computer mainboard from [parent].") - if(7) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] unfastens the mainboard.", "You unfasten the mainboard.") - if(8) - if(diff==FORWARD) - user.visible_message("[user] secures the peripherals control module.", "You secure the peripherals control module.") - else - user.visible_message("[user] removes the peripherals control module from [parent].", "You remove the peripherals control module from [parent].") - if(9) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] unfastens the peripherals control module.", "You unfasten the peripherals control module.") - if(10) - if(diff==FORWARD) - user.visible_message("[user] secures the scanner module.", "You secure the scanner module.") - else - user.visible_message("[user] removes the scanner module from [parent].", "You remove the scanner module from [parent].") - if(11) - if(diff==FORWARD) - user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") - else - user.visible_message("[user] unfastens the scanner module.", "You unfasten the scanner module.") - if(12) - if(diff==FORWARD) - user.visible_message("[user] secures [I].", "You secure [I].") - else - user.visible_message("[user] removes the capacitor from [parent].", "You remove the capacitor from [parent].") - if(13) - if(diff==FORWARD) - user.visible_message("[user] installs [I].", "You install [I].") - else - user.visible_message("[user] unsecures the capacitor from [parent].", "You unsecure the capacitor from [parent].") - if(14) - if(diff==FORWARD) - user.visible_message("[user] secures the power cell.", "You secure the power cell.") - else - user.visible_message("[user] pries the power cell from [parent].", "You pry the power cell from [parent].") - if(15) - if(diff==FORWARD) - user.visible_message("[user] installs the internal armor layer to [parent].", "You install the internal armor layer to [parent].") - else - user.visible_message("[user] unfastens the power cell.", "You unfasten the power cell.") - if(16) - if(diff==FORWARD) - user.visible_message("[user] secures the internal armor layer.", "You secure the internal armor layer.") - else - user.visible_message("[user] pries internal armor layer from [parent].", "You pry internal armor layer from [parent].") - if(17) - if(diff==FORWARD) - user.visible_message("[user] welds the internal armor layer to [parent].", "You weld the internal armor layer to [parent].") - else - user.visible_message("[user] unfastens the internal armor layer.", "You unfasten the internal armor layer.") - if(18) - if(diff==FORWARD) - user.visible_message("[user] installs the external reinforced armor layer to [parent].", "You install the external reinforced armor layer to [parent].") - else - user.visible_message("[user] cuts the internal armor layer from [parent].", "You cut the internal armor layer from [parent].") - if(19) - if(diff==FORWARD) - user.visible_message("[user] secures the external armor layer.", "You secure the external reinforced armor layer.") - else - user.visible_message("[user] pries external armor layer from [parent].", "You pry external armor layer from [parent].") - if(20) - if(diff==FORWARD) - user.visible_message("[user] welds the external armor layer to [parent].", "You weld the external armor layer to [parent].") - else - user.visible_message("[user] unfastens the external armor layer.", "You unfasten the external armor layer.") - return TRUE - -/datum/component/construction/unordered/mecha_chassis/gygax - result = /datum/component/construction/mecha/gygax - steps = list( - /obj/item/mecha_parts/part/gygax_torso, - /obj/item/mecha_parts/part/gygax_left_arm, - /obj/item/mecha_parts/part/gygax_right_arm, - /obj/item/mecha_parts/part/gygax_left_leg, - /obj/item/mecha_parts/part/gygax_right_leg, - /obj/item/mecha_parts/part/gygax_head - ) - -/datum/component/construction/mecha/gygax - result = /obj/mecha/combat/gygax - base_icon = "gygax" - - circuit_control = /obj/item/circuitboard/mecha/gygax/main - circuit_periph = /obj/item/circuitboard/mecha/gygax/peripherals - circuit_weapon = /obj/item/circuitboard/mecha/gygax/targeting - - inner_plating = /obj/item/stack/sheet/metal - inner_plating_amount = 5 - - outer_plating=/obj/item/mecha_parts/part/gygax_armor - outer_plating_amount=1 - -/datum/component/construction/mecha/gygax/action(datum/source, atom/used_atom, mob/user) - return check_step(used_atom,user) - -/datum/component/construction/mecha/gygax/custom_action(obj/item/I, mob/living/user, diff) - if(!..()) - return FALSE - - switch(index) - if(1) - user.visible_message("[user] connects [parent] hydraulic systems.", "You connect [parent] hydraulic systems.") - if(2) - if(diff==FORWARD) - user.visible_message("[user] activates [parent] hydraulic systems.", "You activate [parent] hydraulic systems.") - else - user.visible_message("[user] disconnects [parent] hydraulic systems.", "You disconnect [parent] hydraulic systems.") - if(3) - if(diff==FORWARD) - user.visible_message("[user] adds the wiring to [parent].", "You add the wiring to [parent].") - else - user.visible_message("[user] deactivates [parent] hydraulic systems.", "You deactivate [parent] hydraulic systems.") - if(4) - if(diff==FORWARD) - user.visible_message("[user] adjusts the wiring of [parent].", "You adjust the wiring of [parent].") - else - user.visible_message("[user] removes the wiring from [parent].", "You remove the wiring from [parent].") - if(5) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] disconnects the wiring of [parent].", "You disconnect the wiring of [parent].") - if(6) - if(diff==FORWARD) - user.visible_message("[user] secures the mainboard.", "You secure the mainboard.") - else - user.visible_message("[user] removes the central control module from [parent].", "You remove the central computer mainboard from [parent].") - if(7) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] unfastens the mainboard.", "You unfasten the mainboard.") - if(8) - if(diff==FORWARD) - user.visible_message("[user] secures the peripherals control module.", "You secure the peripherals control module.") - else - user.visible_message("[user] removes the peripherals control module from [parent].", "You remove the peripherals control module from [parent].") - if(9) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] unfastens the peripherals control module.", "You unfasten the peripherals control module.") - if(10) - if(diff==FORWARD) - user.visible_message("[user] secures the weapon control module.", "You secure the weapon control module.") - else - user.visible_message("[user] removes the weapon control module from [parent].", "You remove the weapon control module from [parent].") - if(11) - if(diff==FORWARD) - user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") - else - user.visible_message("[user] unfastens the weapon control module.", "You unfasten the weapon control module.") - if(12) - if(diff==FORWARD) - user.visible_message("[user] secures the scanner module.", "You secure the scanner module.") - else - user.visible_message("[user] removes the scanner module from [parent].", "You remove the scanner module from [parent].") - if(13) - if(diff==FORWARD) - user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") - else - user.visible_message("[user] unfastens the scanner module.", "You unfasten the scanner module.") - if(14) - if(diff==FORWARD) - user.visible_message("[user] secures the capacitor.", "You secure the capacitor.") - else - user.visible_message("[user] removes the capacitor from [parent].", "You remove the capacitor from [parent].") - if(15) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] unfastens the capacitor.", "You unfasten the capacitor.") - if(16) - if(diff==FORWARD) - user.visible_message("[user] secures the power cell.", "You secure the power cell.") - else - user.visible_message("[user] pries the power cell from [parent].", "You pry the power cell from [parent].") - if(17) - if(diff==FORWARD) - user.visible_message("[user] installs the internal armor layer to [parent].", "You install the internal armor layer to [parent].") - else - user.visible_message("[user] unfastens the power cell.", "You unfasten the power cell.") - if(18) - if(diff==FORWARD) - user.visible_message("[user] secures the internal armor layer.", "You secure the internal armor layer.") - else - user.visible_message("[user] pries internal armor layer from [parent].", "You pry internal armor layer from [parent].") - if(19) - if(diff==FORWARD) - user.visible_message("[user] welds the internal armor layer to [parent].", "You weld the internal armor layer to [parent].") - else - user.visible_message("[user] unfastens the internal armor layer.", "You unfasten the internal armor layer.") - if(20) - if(diff==FORWARD) - user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") - else - user.visible_message("[user] cuts the internal armor layer from [parent].", "You cut the internal armor layer from [parent].") - if(21) - if(diff==FORWARD) - user.visible_message("[user] secures Gygax Armor Plates.", "You secure Gygax Armor Plates.") - else - user.visible_message("[user] pries Gygax Armor Plates from [parent].", "You pry Gygax Armor Plates from [parent].") - if(22) - if(diff==FORWARD) - user.visible_message("[user] welds Gygax Armor Plates to [parent].", "You weld Gygax Armor Plates to [parent].") - else - user.visible_message("[user] unfastens Gygax Armor Plates.", "You unfasten Gygax Armor Plates.") - return TRUE - -/datum/component/construction/unordered/mecha_chassis/firefighter - result = /datum/component/construction/mecha/firefighter - steps = list( - /obj/item/mecha_parts/part/ripley_torso, - /obj/item/mecha_parts/part/ripley_left_arm, - /obj/item/mecha_parts/part/ripley_right_arm, - /obj/item/mecha_parts/part/ripley_left_leg, - /obj/item/mecha_parts/part/ripley_right_leg, - /obj/item/clothing/suit/fire - ) - -/datum/component/construction/mecha/firefighter - result = /obj/mecha/working/ripley/firefighter - base_icon = "fireripley" - - circuit_control = /obj/item/circuitboard/mecha/ripley/main - circuit_periph = /obj/item/circuitboard/mecha/ripley/peripherals - - inner_plating = /obj/item/stack/sheet/plasteel - inner_plating_amount = 5 - -/datum/component/construction/mecha/firefighter/get_outer_plating_steps() - return list( - list( - "key" = /obj/item/stack/sheet/plasteel, - "amount" = 5, - "back_key" = TOOL_WELDER, - "desc" = "Internal armor is welded." - ), - list( - "key" = /obj/item/stack/sheet/plasteel, - "amount" = 5, - "back_key" = TOOL_CROWBAR, - "desc" = "External armor is being installed." - ), - list( - "key" = TOOL_WRENCH, - "back_key" = TOOL_CROWBAR, - "desc" = "External armor is installed." - ), - list( - "key" = TOOL_WELDER, - "back_key" = TOOL_WRENCH, - "desc" = "External armor is wrenched." - ), - ) - -/datum/component/construction/mecha/firefighter/custom_action(obj/item/I, mob/living/user, diff) - if(!..()) - return FALSE - - //TODO: better messages. - switch(index) - if(1) - user.visible_message("[user] connects [parent] hydraulic systems.", "You connect [parent] hydraulic systems.") - if(2) - if(diff==FORWARD) - user.visible_message("[user] activates [parent] hydraulic systems.", "You activate [parent] hydraulic systems.") - else - user.visible_message("[user] disconnects [parent] hydraulic systems.", "You disconnect [parent] hydraulic systems.") - if(3) - if(diff==FORWARD) - user.visible_message("[user] adds the wiring to [parent].", "You add the wiring to [parent].") - else - user.visible_message("[user] deactivates [parent] hydraulic systems.", "You deactivate [parent] hydraulic systems.") - if(4) - if(diff==FORWARD) - user.visible_message("[user] adjusts the wiring of [parent].", "You adjust the wiring of [parent].") - else - user.visible_message("[user] removes the wiring from [parent].", "You remove the wiring from [parent].") - if(5) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] disconnects the wiring of [parent].", "You disconnect the wiring of [parent].") - if(6) - if(diff==FORWARD) - user.visible_message("[user] secures the mainboard.", "You secure the mainboard.") - else - user.visible_message("[user] removes the central control module from [parent].", "You remove the central computer mainboard from [parent].") - if(7) - if(diff==FORWARD) - user.visible_message("[user] installs [I]into [parent].", "You install [I]into [parent].") - else - user.visible_message("[user] unfastens the mainboard.", "You unfasten the mainboard.") - if(8) - if(diff==FORWARD) - user.visible_message("[user] secures the peripherals control module.", "You secure the peripherals control module.") - else - user.visible_message("[user] removes the peripherals control module from [parent].", "You remove the peripherals control module from [parent].") - if(9) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] unfastens the peripherals control module.", "You unfasten the peripherals control module.") - if(10) - if(diff==FORWARD) - user.visible_message("[user] secures the scanner module.", "You secure the scanner module.") - else - user.visible_message("[user] removes the scanner module from [parent].", "You remove the scanner module from [parent].") - if(11) - if(diff==FORWARD) - user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") - else - user.visible_message("[user] unfastens the scanner module.", "You unfasten the scanner module.") - if(12) - if(diff==FORWARD) - user.visible_message("[user] secures the capacitor.", "You secure the capacitor.") - else - user.visible_message("[user] removes the capacitor from [parent].", "You remove the capacitor from [parent].") - if(13) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] unfastens the capacitor.", "You unfasten the capacitor.") - if(14) - if(diff==FORWARD) - user.visible_message("[user] secures the power cell.", "You secure the power cell.") - else - user.visible_message("[user] pries the power cell from [parent].", "You pry the power cell from [parent].") - if(15) - if(diff==FORWARD) - user.visible_message("[user] installs the internal armor layer to [parent].", "You install the internal armor layer to [parent].") - else - user.visible_message("[user] unfastens the power cell.", "You unfasten the power cell.") - if(16) - if(diff==FORWARD) - user.visible_message("[user] secures the internal armor layer.", "You secure the internal armor layer.") - else - user.visible_message("[user] pries internal armor layer from [parent].", "You pry internal armor layer from [parent].") - if(17) - if(diff==FORWARD) - user.visible_message("[user] welds the internal armor layer to [parent].", "You weld the internal armor layer to [parent].") - else - user.visible_message("[user] unfastens the internal armor layer.", "You unfasten the internal armor layer.") - if(18) - if(diff==FORWARD) - user.visible_message("[user] starts to install the external armor layer to [parent].", "You install the external armor layer to [parent].") - else - user.visible_message("[user] cuts the internal armor layer from [parent].", "You cut the internal armor layer from [parent].") - if(19) - if(diff==FORWARD) - user.visible_message("[user] installs the external reinforced armor layer to [parent].", "You install the external reinforced armor layer to [parent].") - else - user.visible_message("[user] removes the external armor from [parent].", "You remove the external armor from [parent].") - if(20) - if(diff==FORWARD) - user.visible_message("[user] secures the external armor layer.", "You secure the external reinforced armor layer.") - else - user.visible_message("[user] pries external armor layer from [parent].", "You pry external armor layer from [parent].") - if(21) - if(diff==FORWARD) - user.visible_message("[user] welds the external armor layer to [parent].", "You weld the external armor layer to [parent].") - else - user.visible_message("[user] unfastens the external armor layer.", "You unfasten the external armor layer.") - return TRUE - -/datum/component/construction/unordered/mecha_chassis/honker - result = /datum/component/construction/mecha/honker - steps = list( - /obj/item/mecha_parts/part/honker_torso, - /obj/item/mecha_parts/part/honker_left_arm, - /obj/item/mecha_parts/part/honker_right_arm, - /obj/item/mecha_parts/part/honker_left_leg, - /obj/item/mecha_parts/part/honker_right_leg, - /obj/item/mecha_parts/part/honker_head - ) - -/datum/component/construction/mecha/honker - result = /obj/mecha/combat/honker - steps = list( - list( - "key" = /obj/item/bikehorn - ), - list( - "key" = /obj/item/circuitboard/mecha/honker/main, - "action" = ITEM_DELETE - ), - list( - "key" = /obj/item/bikehorn - ), - list( - "key" = /obj/item/circuitboard/mecha/honker/peripherals, - "action" = ITEM_DELETE - ), - list( - "key" = /obj/item/bikehorn - ), - list( - "key" = /obj/item/circuitboard/mecha/honker/targeting, - "action" = ITEM_DELETE - ), - list( - "key" = /obj/item/bikehorn - ), - list( - "key" = /obj/item/stock_parts/scanning_module, - "action" = ITEM_MOVE_INSIDE - ), - list( - "key" = /obj/item/bikehorn - ), - list( - "key" = /obj/item/stock_parts/capacitor, - "action" = ITEM_MOVE_INSIDE - ), - list( - "key" = /obj/item/bikehorn - ), - list( - "key" = /obj/item/stock_parts/cell, - "action" = ITEM_MOVE_INSIDE - ), - list( - "key" = /obj/item/bikehorn - ), - list( - "key" = /obj/item/clothing/mask/gas/clown_hat, - "action" = ITEM_DELETE - ), - list( - "key" = /obj/item/bikehorn - ), - list( - "key" = /obj/item/clothing/shoes/clown_shoes, - "action" = ITEM_DELETE - ), - list( - "key" = /obj/item/bikehorn - ), - ) - -/datum/component/construction/mecha/honker/get_steps() - return steps - -// HONK doesn't have any construction step icons, so we just set an icon once. -/datum/component/construction/mecha/honker/update_parent(step_index) - if(step_index == 1) - var/atom/parent_atom = parent - parent_atom.icon = 'icons/mecha/mech_construct.dmi' - parent_atom.icon_state = "honker_chassis" - ..() - -/datum/component/construction/mecha/honker/custom_action(obj/item/I, mob/living/user, diff) - if(!..()) - return FALSE - - if(istype(I, /obj/item/bikehorn)) - playsound(parent, 'sound/items/bikehorn.ogg', 50, TRUE) - user.visible_message("HONK!") - - //TODO: better messages. - switch(index) - if(2) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - if(4) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - if(6) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - if(8) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - if(10) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - if(12) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - if(14) - user.visible_message("[user] puts [I] on [parent].", "You put [I] on [parent].") - if(16) - user.visible_message("[user] puts [I] on [parent].", "You put [I] on [parent].") - return TRUE - -/datum/component/construction/unordered/mecha_chassis/durand - result = /datum/component/construction/mecha/durand - steps = list( - /obj/item/mecha_parts/part/durand_torso, - /obj/item/mecha_parts/part/durand_left_arm, - /obj/item/mecha_parts/part/durand_right_arm, - /obj/item/mecha_parts/part/durand_left_leg, - /obj/item/mecha_parts/part/durand_right_leg, - /obj/item/mecha_parts/part/durand_head - ) - -/datum/component/construction/mecha/durand - result = /obj/mecha/combat/durand - base_icon = "durand" - - circuit_control = /obj/item/circuitboard/mecha/durand/main - circuit_periph = /obj/item/circuitboard/mecha/durand/peripherals - circuit_weapon = /obj/item/circuitboard/mecha/durand/targeting - - inner_plating = /obj/item/stack/sheet/metal - inner_plating_amount = 5 - - outer_plating = /obj/item/mecha_parts/part/durand_armor - outer_plating_amount = 1 - -/datum/component/construction/mecha/durand/custom_action(obj/item/I, mob/living/user, diff) - if(!..()) - return FALSE - - //TODO: better messages. - switch(index) - if(1) - user.visible_message("[user] connects [parent] hydraulic systems.", "You connect [parent] hydraulic systems.") - if(2) - if(diff==FORWARD) - user.visible_message("[user] activates [parent] hydraulic systems.", "You activate [parent] hydraulic systems.") - else - user.visible_message("[user] disconnects [parent] hydraulic systems.", "You disconnect [parent] hydraulic systems.") - if(3) - if(diff==FORWARD) - user.visible_message("[user] adds the wiring to [parent].", "You add the wiring to [parent].") - else - user.visible_message("[user] deactivates [parent] hydraulic systems.", "You deactivate [parent] hydraulic systems.") - if(4) - if(diff==FORWARD) - user.visible_message("[user] adjusts the wiring of [parent].", "You adjust the wiring of [parent].") - else - user.visible_message("[user] removes the wiring from [parent].", "You remove the wiring from [parent].") - if(5) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] disconnects the wiring of [parent].", "You disconnect the wiring of [parent].") - if(6) - if(diff==FORWARD) - user.visible_message("[user] secures the mainboard.", "You secure the mainboard.") - else - user.visible_message("[user] removes the central control module from [parent].", "You remove the central computer mainboard from [parent].") - if(7) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] unfastens the mainboard.", "You unfasten the mainboard.") - if(8) - if(diff==FORWARD) - user.visible_message("[user] secures the peripherals control module.", "You secure the peripherals control module.") - else - user.visible_message("[user] removes the peripherals control module from [parent].", "You remove the peripherals control module from [parent].") - if(9) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] unfastens the peripherals control module.", "You unfasten the peripherals control module.") - if(10) - if(diff==FORWARD) - user.visible_message("[user] secures the weapon control module.", "You secure the weapon control module.") - else - user.visible_message("[user] removes the weapon control module from [parent].", "You remove the weapon control module from [parent].") - if(11) - if(diff==FORWARD) - user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") - else - user.visible_message("[user] unfastens the weapon control module.", "You unfasten the weapon control module.") - if(12) - if(diff==FORWARD) - user.visible_message("[user] secures the scanner module.", "You secure the scanner module.") - else - user.visible_message("[user] removes the scanner module from [parent].", "You remove the scanner module from [parent].") - if(13) - if(diff==FORWARD) - user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") - else - user.visible_message("[user] unfastens the scanner module.", "You unfasten the scanner module.") - if(14) - if(diff==FORWARD) - user.visible_message("[user] secures the capacitor.", "You secure the capacitor.") - else - user.visible_message("[user] removes the capacitor from [parent].", "You remove the capacitor from [parent].") - if(15) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] unfastens the capacitor.", "You unfasten the capacitor.") - if(16) - if(diff==FORWARD) - user.visible_message("[user] secures the power cell.", "You secure the power cell.") - else - user.visible_message("[user] pries the power cell from [parent].", "You pry the power cell from [parent].") - if(17) - if(diff==FORWARD) - user.visible_message("[user] installs the internal armor layer to [parent].", "You install the internal armor layer to [parent].") - else - user.visible_message("[user] unfastens the power cell.", "You unfasten the power cell.") - if(18) - if(diff==FORWARD) - user.visible_message("[user] secures the internal armor layer.", "You secure the internal armor layer.") - else - user.visible_message("[user] pries internal armor layer from [parent].", "You pry internal armor layer from [parent].") - if(19) - if(diff==FORWARD) - user.visible_message("[user] welds the internal armor layer to [parent].", "You weld the internal armor layer to [parent].") - else - user.visible_message("[user] unfastens the internal armor layer.", "You unfasten the internal armor layer.") - if(20) - if(diff==FORWARD) - user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") - else - user.visible_message("[user] cuts the internal armor layer from [parent].", "You cut the internal armor layer from [parent].") - if(21) - if(diff==FORWARD) - user.visible_message("[user] secures Durand Armor Plates.", "You secure Durand Armor Plates.") - else - user.visible_message("[user] pries Durand Armor Plates from [parent].", "You pry Durand Armor Plates from [parent].") - if(22) - if(diff==FORWARD) - user.visible_message("[user] welds Durand Armor Plates to [parent].", "You weld Durand Armor Plates to [parent].") - else - user.visible_message("[user] unfastens Durand Armor Plates.", "You unfasten Durand Armor Plates.") - return TRUE - -//PHAZON - -/datum/component/construction/unordered/mecha_chassis/phazon - result = /datum/component/construction/mecha/phazon - steps = list( - /obj/item/mecha_parts/part/phazon_torso, - /obj/item/mecha_parts/part/phazon_left_arm, - /obj/item/mecha_parts/part/phazon_right_arm, - /obj/item/mecha_parts/part/phazon_left_leg, - /obj/item/mecha_parts/part/phazon_right_leg, - /obj/item/mecha_parts/part/phazon_head - ) - -/datum/component/construction/mecha/phazon - result = /obj/mecha/combat/phazon - base_icon = "phazon" - - circuit_control = /obj/item/circuitboard/mecha/phazon/main - circuit_periph = /obj/item/circuitboard/mecha/phazon/peripherals - circuit_weapon = /obj/item/circuitboard/mecha/phazon/targeting - - inner_plating = /obj/item/stack/sheet/plasteel - inner_plating_amount = 5 - - outer_plating = /obj/item/mecha_parts/part/phazon_armor - outer_plating_amount = 1 - -/datum/component/construction/mecha/phazon/get_stockpart_steps() - return list( - list( - "key" = /obj/item/stock_parts/scanning_module, - "action" = ITEM_MOVE_INSIDE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Weapon control module is secured." - ), - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "Scanner module is installed." - ), - list( - "key" = /obj/item/stock_parts/capacitor, - "action" = ITEM_MOVE_INSIDE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Scanner module is secured." - ), - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "Capacitor is installed." - ), - list( - "key" = /obj/item/stack/ore/bluespace_crystal, - "amount" = 1, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Capacitor is secured." - ), - list( - "key" = /obj/item/stack/cable_coil, - "amount" = 5, - "back_key" = TOOL_CROWBAR, - "desc" = "The bluespace crystal is installed." - ), - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_WIRECUTTER, - "desc" = "The bluespace crystal is connected." - ), - list( - "key" = /obj/item/stock_parts/cell, - "action" = ITEM_MOVE_INSIDE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "The bluespace crystal is engaged." - ), - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "The power cell is installed.", - "icon_state" = "phazon17" - // This is the point where a step icon is skipped, so "icon_state" had to be set manually starting from here. - ) - ) - -/datum/component/construction/mecha/phazon/get_outer_plating_steps() - return list( - list( - "key" = outer_plating, - "amount" = 1, - "action" = ITEM_DELETE, - "back_key" = TOOL_WELDER, - "desc" = "Internal armor is welded." - ), - list( - "key" = TOOL_WRENCH, - "back_key" = TOOL_CROWBAR, - "desc" = "External armor is installed." - ), - list( - "key" = TOOL_WELDER, - "back_key" = TOOL_WRENCH, - "desc" = "External armor is wrenched." - ), - list( - "key" = /obj/item/assembly/signaler/anomaly, //WaspStation Edit - Any anomaly core for Phazons - "action" = ITEM_DELETE, - "back_key" = TOOL_WELDER, - "desc" = "Bluespace anomaly core socket is open.", - "icon_state" = "phazon24" - ) - ) - -/datum/component/construction/mecha/phazon/custom_action(obj/item/I, mob/living/user, diff) - if(!..()) - return FALSE - - //TODO: better messages. - switch(index) - if(1) - user.visible_message("[user] connects [parent] hydraulic systems.", "You connect [parent] hydraulic systems.") - if(2) - if(diff==FORWARD) - user.visible_message("[user] activates [parent] hydraulic systems.", "You activate [parent] hydraulic systems.") - else - user.visible_message("[user] disconnects [parent] hydraulic systems.", "You disconnect [parent] hydraulic systems.") - if(3) - if(diff==FORWARD) - user.visible_message("[user] adds the wiring to [parent].", "You add the wiring to [parent].") - else - user.visible_message("[user] deactivates [parent] hydraulic systems.", "You deactivate [parent] hydraulic systems.") - if(4) - if(diff==FORWARD) - user.visible_message("[user] adjusts the wiring of [parent].", "You adjust the wiring of [parent].") - else - user.visible_message("[user] removes the wiring from [parent].", "You remove the wiring from [parent].") - if(5) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] disconnects the wiring of [parent].", "You disconnect the wiring of [parent].") - if(6) - if(diff==FORWARD) - user.visible_message("[user] secures the mainboard.", "You secure the mainboard.") - else - user.visible_message("[user] removes the central control module from [parent].", "You remove the central computer mainboard from [parent].") - if(7) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] unfastens the mainboard.", "You unfasten the mainboard.") - if(8) - if(diff==FORWARD) - user.visible_message("[user] secures the peripherals control module.", "You secure the peripherals control module.") - else - user.visible_message("[user] removes the peripherals control module from [parent].", "You remove the peripherals control module from [parent].") - if(9) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] unfastens the peripherals control module.", "You unfasten the peripherals control module.") - if(10) - if(diff==FORWARD) - user.visible_message("[user] secures the weapon control module.", "You secure the weapon control module.") - else - user.visible_message("[user] removes the weapon control module from [parent].", "You remove the weapon control module from [parent].") - if(11) - if(diff==FORWARD) - user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") - else - user.visible_message("[user] unfastens the weapon control module.", "You unfasten the weapon control module.") - if(12) - if(diff==FORWARD) - user.visible_message("[user] secures the scanner module.", "You secure the scanner module.") - else - user.visible_message("[user] removes the scanner module from [parent].", "You remove the scanner module from [parent].") - if(13) - if(diff==FORWARD) - user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") - else - user.visible_message("[user] unfastens the scanner module.", "You unfasten the scanner module.") - if(14) - if(diff==FORWARD) - user.visible_message("[user] secures the capacitor.", "You secure the capacitor.") - else - user.visible_message("[user] removes the capacitor from [parent].", "You remove the capacitor from [parent].") - if(15) - if(diff==FORWARD) - user.visible_message("[user] installs [I].", "You install [I].") - else - user.visible_message("[user] unsecures the capacitor from [parent].", "You unsecure the capacitor from [parent].") - if(16) - if(diff==FORWARD) - user.visible_message("[user] connects the bluespace crystal.", "You connect the bluespace crystal.") - else - user.visible_message("[user] removes the bluespace crystal from [parent].", "You remove the bluespace crystal from [parent].") - if(17) - if(diff==FORWARD) - user.visible_message("[user] engages the bluespace crystal.", "You engage the bluespace crystal.") - else - user.visible_message("[user] disconnects the bluespace crystal from [parent].", "You disconnect the bluespace crystal from [parent].") - if(18) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] disengages the bluespace crystal.", "You disengage the bluespace crystal.") - if(19) - if(diff==FORWARD) - user.visible_message("[user] secures the power cell.", "You secure the power cell.") - else - user.visible_message("[user] pries the power cell from [parent].", "You pry the power cell from [parent].") - if(20) - if(diff==FORWARD) - user.visible_message("[user] installs the phase armor layer to [parent].", "You install the phase armor layer to [parent].") - else - user.visible_message("[user] unfastens the power cell.", "You unfasten the power cell.") - if(21) - if(diff==FORWARD) - user.visible_message("[user] secures the phase armor layer.", "You secure the phase armor layer.") - else - user.visible_message("[user] pries the phase armor layer from [parent].", "You pry the phase armor layer from [parent].") - if(22) - if(diff==FORWARD) - user.visible_message("[user] welds the phase armor layer to [parent].", "You weld the phase armor layer to [parent].") - else - user.visible_message("[user] unfastens the phase armor layer.", "You unfasten the phase armor layer.") - if(23) - if(diff==FORWARD) - user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") - else - user.visible_message("[user] cuts phase armor layer from [parent].", "You cut the phase armor layer from [parent].") - if(24) - if(diff==FORWARD) - user.visible_message("[user] secures Phazon Armor Plates.", "You secure Phazon Armor Plates.") - else - user.visible_message("[user] pries Phazon Armor Plates from [parent].", "You pry Phazon Armor Plates from [parent].") - if(25) - if(diff==FORWARD) - user.visible_message("[user] welds Phazon Armor Plates to [parent].", "You weld Phazon Armor Plates to [parent].") - else - user.visible_message("[user] unfastens Phazon Armor Plates.", "You unfasten Phazon Armor Plates.") - if(26) - if(diff==FORWARD) - user.visible_message("[user] carefully inserts the anomaly core into [parent] and secures it.", //WS Edit - "You slowly place the anomaly core into its socket and close its chamber.") //WS Edit - Any anomaly core for phazons - return TRUE - -//ODYSSEUS - -/datum/component/construction/unordered/mecha_chassis/odysseus - result = /datum/component/construction/mecha/odysseus - steps = list( - /obj/item/mecha_parts/part/odysseus_torso, - /obj/item/mecha_parts/part/odysseus_head, - /obj/item/mecha_parts/part/odysseus_left_arm, - /obj/item/mecha_parts/part/odysseus_right_arm, - /obj/item/mecha_parts/part/odysseus_left_leg, - /obj/item/mecha_parts/part/odysseus_right_leg - ) - -/datum/component/construction/mecha/odysseus - result = /obj/mecha/medical/odysseus - base_icon = "odysseus" - - circuit_control = /obj/item/circuitboard/mecha/odysseus/main - circuit_periph = /obj/item/circuitboard/mecha/odysseus/peripherals - - inner_plating = /obj/item/stack/sheet/metal - inner_plating_amount = 5 - - outer_plating = /obj/item/stack/sheet/plasteel - outer_plating_amount = 5 - -/datum/component/construction/mecha/odysseus/custom_action(obj/item/I, mob/living/user, diff) - if(!..()) - return FALSE - - //TODO: better messages. - switch(index) - if(1) - user.visible_message("[user] connects [parent] hydraulic systems.", "You connect [parent] hydraulic systems.") - if(2) - if(diff==FORWARD) - user.visible_message("[user] activates [parent] hydraulic systems.", "You activate [parent] hydraulic systems.") - else - user.visible_message("[user] disconnects [parent] hydraulic systems.", "You disconnect [parent] hydraulic systems.") - if(3) - if(diff==FORWARD) - user.visible_message("[user] adds the wiring to [parent].", "You add the wiring to [parent].") - else - user.visible_message("[user] deactivates [parent] hydraulic systems.", "You deactivate [parent] hydraulic systems.") - if(4) - if(diff==FORWARD) - user.visible_message("[user] adjusts the wiring of [parent].", "You adjust the wiring of [parent].") - else - user.visible_message("[user] removes the wiring from [parent].", "You remove the wiring from [parent].") - if(5) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] disconnects the wiring of [parent].", "You disconnect the wiring of [parent].") - if(6) - if(diff==FORWARD) - user.visible_message("[user] secures the mainboard.", "You secure the mainboard.") - else - user.visible_message("[user] removes the central control module from [parent].", "You remove the central computer mainboard from [parent].") - if(7) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] unfastens the mainboard.", "You unfasten the mainboard.") - if(8) - if(diff==FORWARD) - user.visible_message("[user] secures the peripherals control module.", "You secure the peripherals control module.") - else - user.visible_message("[user] removes the peripherals control module from [parent].", "You remove the peripherals control module from [parent].") - if(9) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] unfastens the peripherals control module.", "You unfasten the peripherals control module.") - if(10) - if(diff==FORWARD) - user.visible_message("[user] secures the scanner module.", "You secure the scanner module.") - else - user.visible_message("[user] removes the scanner module from [parent].", "You remove the scanner module from [parent].") - if(11) - if(diff==FORWARD) - user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") - else - user.visible_message("[user] unfastens the scanner module.", "You unfasten the scanner module.") - if(12) - if(diff==FORWARD) - user.visible_message("[user] secures the capacitor.", "You secure the capacitor.") - else - user.visible_message("[user] removes the capacitor from [parent].", "You remove the capacitor from [parent].") - if(13) - if(diff==FORWARD) - user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") - else - user.visible_message("[user] unfastens the capacitor.", "You unfasten the capacitor.") - if(14) - if(diff==FORWARD) - user.visible_message("[user] secures the power cell.", "You secure the power cell.") - else - user.visible_message("[user] pries the power cell from [parent].", "You pry the power cell from [parent].") - if(15) - if(diff==FORWARD) - user.visible_message("[user] installs the internal armor layer to [parent].", "You install the internal armor layer to [parent].") - else - user.visible_message("[user] unfastens the power cell.", "You unfasten the power cell.") - if(16) - if(diff==FORWARD) - user.visible_message("[user] secures the internal armor layer.", "You secure the internal armor layer.") - else - user.visible_message("[user] pries internal armor layer from [parent].", "You pry internal armor layer from [parent].") - if(17) - if(diff==FORWARD) - user.visible_message("[user] welds the internal armor layer to [parent].", "You weld the internal armor layer to [parent].") - else - user.visible_message("[user] unfastens the internal armor layer.", "You unfasten the internal armor layer.") - if(18) - if(diff==FORWARD) - user.visible_message("[user] installs the external armor layer to [parent].", "You install the external reinforced armor layer to [parent].") - else - user.visible_message("[user] cuts the internal armor layer from [parent].", "You cut the internal armor layer from [parent].") - if(19) - if(diff==FORWARD) - user.visible_message("[user] secures the external armor layer.", "You secure the external reinforced armor layer.") - else - user.visible_message("[user] pries the external armor layer from [parent].", "You pry the external armor layer from [parent].") - if(20) - if(diff==FORWARD) - user.visible_message("[user] welds the external armor layer to [parent].", "You weld the external armor layer to [parent].") - else - user.visible_message("[user] unfastens the external armor layer.", "You unfasten the external armor layer.") - return TRUE +//////////////////////////////// +///// Construction datums ////// +//////////////////////////////// +/datum/component/construction/mecha + var/base_icon + + // Component typepaths. + // most must be defined unless + // get_steps is overriden. + + // Circuit board typepaths. + // circuit_control and circuit_periph must be defined + // unless get_circuit_steps is overriden. + var/circuit_control + var/circuit_periph + var/circuit_weapon + + // Armor plating typepaths. both must be defined + // unless relevant step procs are overriden. amounts + // must be defined if using /obj/item/stack/sheet types + var/inner_plating + var/inner_plating_amount + + var/outer_plating + var/outer_plating_amount + +/datum/component/construction/mecha/spawn_result() + if(!result) + return + // Remove default mech power cell, as we replace it with a new one. + var/obj/mecha/M = new result(drop_location()) + QDEL_NULL(M.cell) + QDEL_NULL(M.scanmod) + QDEL_NULL(M.capacitor) + + var/obj/item/mecha_parts/chassis/parent_chassis = parent + M.CheckParts(parent_chassis.contents) + + SSblackbox.record_feedback("tally", "mechas_created", 1, M.name) + QDEL_NULL(parent) + +// Default proc to generate mech steps. +// Override if the mech needs an entirely custom process (See HONK mech) +// Otherwise override specific steps as needed (Ripley, firefighter, Phazon) +/datum/component/construction/mecha/proc/get_steps() + return get_frame_steps() + get_circuit_steps() + (circuit_weapon ? get_circuit_weapon_steps() : list()) + get_stockpart_steps() + get_inner_plating_steps() + get_outer_plating_steps() + +/datum/component/construction/mecha/update_parent(step_index) + steps = get_steps() + ..() + // By default, each step in mech construction has a single icon_state: + // "[base_icon][index - 1]" + // For example, Ripley's step 1 icon_state is "ripley0". + var/atom/parent_atom = parent + if(!steps[index]["icon_state"] && base_icon) + parent_atom.icon_state = "[base_icon][index - 1]" + +/datum/component/construction/unordered/mecha_chassis/custom_action(obj/item/I, mob/living/user, typepath) + . = user.transferItemToLoc(I, parent) + if(.) + var/atom/parent_atom = parent + user.visible_message("[user] connects [I] to [parent].", "You connect [I] to [parent].") + parent_atom.add_overlay(I.icon_state+"+o") + qdel(I) + +/datum/component/construction/unordered/mecha_chassis/spawn_result() + var/atom/parent_atom = parent + parent_atom.icon = 'icons/mecha/mech_construction.dmi' + parent_atom.density = TRUE + parent_atom.cut_overlays() + ..() + +// Default proc for the first steps of mech construction. +/datum/component/construction/mecha/proc/get_frame_steps() + return list( + list( + "key" = TOOL_WRENCH, + "desc" = "The hydraulic systems are disconnected." + ), + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_WRENCH, + "desc" = "The hydraulic systems are connected." + ), + list( + "key" = /obj/item/stack/cable_coil, + "amount" = 5, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "The hydraulic systems are active." + ), + list( + "key" = TOOL_WIRECUTTER, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "The wiring is added." + ) + ) + +// Default proc for the circuit board steps of a mech. +// Second set of steps by default. +/datum/component/construction/mecha/proc/get_circuit_steps() + return list( + list( + "key" = circuit_control, + "action" = ITEM_DELETE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "The wiring is adjusted." + ), + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "Central control module is installed." + ), + list( + "key" = circuit_periph, + "action" = ITEM_DELETE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Central control module is secured." + ), + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "Peripherals control module is installed." + ) + ) + +// Default proc for weapon circuitboard steps +// Used by combat mechs +/datum/component/construction/mecha/proc/get_circuit_weapon_steps() + return list( + list( + "key" = circuit_weapon, + "action" = ITEM_DELETE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Peripherals control module is secured." + ), + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "Weapons control module is installed." + ) + ) + +// Default proc for stock part installation +// Third set of steps by default +/datum/component/construction/mecha/proc/get_stockpart_steps() + var/prevstep_text = circuit_weapon ? "Weapons control module is secured." : "Peripherals control module is secured." + return list( + list( + "key" = /obj/item/stock_parts/scanning_module, + "action" = ITEM_MOVE_INSIDE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = prevstep_text + ), + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "Scanner module is installed." + ), + list( + "key" = /obj/item/stock_parts/capacitor, + "action" = ITEM_MOVE_INSIDE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Scanner module is secured." + ), + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "Capacitor is installed." + ), + list( + "key" = /obj/item/stock_parts/cell, + "action" = ITEM_MOVE_INSIDE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Capacitor is secured." + ), + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "The power cell is installed." + ) + ) + +// Default proc for inner armor plating +// Fourth set of steps by default +/datum/component/construction/mecha/proc/get_inner_plating_steps() + var/list/first_step + if(ispath(inner_plating, /obj/item/stack/sheet)) + first_step = list( + list( + "key" = inner_plating, + "amount" = inner_plating_amount, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "The power cell is secured." + ) + ) + else + first_step = list( + list( + "key" = inner_plating, + "action" = ITEM_DELETE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "The power cell is secured." + ) + ) + + return first_step + list( + list( + "key" = TOOL_WRENCH, + "back_key" = TOOL_CROWBAR, + "desc" = "Inner plating is installed." + ), + list( + "key" = TOOL_WELDER, + "back_key" = TOOL_WRENCH, + "desc" = "Inner Plating is wrenched." + ) + ) + +// Default proc for outer armor plating +// Fifth set of steps by default +/datum/component/construction/mecha/proc/get_outer_plating_steps() + var/list/first_step + if(ispath(outer_plating, /obj/item/stack/sheet)) + first_step = list( + list( + "key" = outer_plating, + "amount" = outer_plating_amount, + "back_key" = TOOL_WELDER, + "desc" = "Inner plating is welded." + ) + ) + else + first_step = list( + list( + "key" = outer_plating, + "action" = ITEM_DELETE, + "back_key" = TOOL_WELDER, + "desc" = "Inner plating is welded." + ) + ) + + return first_step + list( + list( + "key" = TOOL_WRENCH, + "back_key" = TOOL_CROWBAR, + "desc" = "External armor is installed." + ), + list( + "key" = TOOL_WELDER, + "back_key" = TOOL_WRENCH, + "desc" = "External armor is wrenched." + ) + ) + + +/datum/component/construction/unordered/mecha_chassis/ripley + result = /datum/component/construction/mecha/ripley + steps = list( + /obj/item/mecha_parts/part/ripley_torso, + /obj/item/mecha_parts/part/ripley_left_arm, + /obj/item/mecha_parts/part/ripley_right_arm, + /obj/item/mecha_parts/part/ripley_left_leg, + /obj/item/mecha_parts/part/ripley_right_leg + ) + +/datum/component/construction/mecha/ripley + result = /obj/mecha/working/ripley + base_icon = "ripley" + + circuit_control = /obj/item/circuitboard/mecha/ripley/main + circuit_periph = /obj/item/circuitboard/mecha/ripley/peripherals + + inner_plating=/obj/item/stack/sheet/metal + inner_plating_amount = 5 + + outer_plating=/obj/item/stack/rods + outer_plating_amount = 10 + +/datum/component/construction/mecha/ripley/get_outer_plating_steps() + return list( + list( + "key" = /obj/item/stack/rods, + "amount" = 10, + "back_key" = TOOL_WELDER, + "desc" = "Outer Plating is welded." + ), + list( + "key" = TOOL_WELDER, + "back_key" = TOOL_WIRECUTTER, + "desc" = "Cockpit wire screen is installed." + ), + ) + +/datum/component/construction/mecha/ripley/custom_action(obj/item/I, mob/living/user, diff) + if(!..()) + return FALSE + + switch(index) + if(1) + user.visible_message("[user] connects [parent] hydraulic systems.", "You connect [parent] hydraulic systems.") + if(2) + if(diff==FORWARD) + user.visible_message("[user] activates [parent] hydraulic systems.", "You activate [parent] hydraulic systems.") + else + user.visible_message("[user] disconnects [parent] hydraulic systems.", "You disconnect [parent] hydraulic systems.") + if(3) + if(diff==FORWARD) + user.visible_message("[user] adds the wiring to [parent].", "You add the wiring to [parent].") + else + user.visible_message("[user] deactivates [parent] hydraulic systems.", "You deactivate [parent] hydraulic systems.") + if(4) + if(diff==FORWARD) + user.visible_message("[user] adjusts the wiring of [parent].", "You adjust the wiring of [parent].") + else + user.visible_message("[user] removes the wiring from [parent].", "You remove the wiring from [parent].") + if(5) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] disconnects the wiring of [parent].", "You disconnect the wiring of [parent].") + if(6) + if(diff==FORWARD) + user.visible_message("[user] secures the mainboard.", "You secure the mainboard.") + else + user.visible_message("[user] removes the central control module from [parent].", "You remove the central computer mainboard from [parent].") + if(7) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] unfastens the mainboard.", "You unfasten the mainboard.") + if(8) + if(diff==FORWARD) + user.visible_message("[user] secures the peripherals control module.", "You secure the peripherals control module.") + else + user.visible_message("[user] removes the peripherals control module from [parent].", "You remove the peripherals control module from [parent].") + if(9) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] unfastens the peripherals control module.", "You unfasten the peripherals control module.") + if(10) + if(diff==FORWARD) + user.visible_message("[user] secures the scanner module.", "You secure the scanner module.") + else + user.visible_message("[user] removes the scanner module from [parent].", "You remove the scanner module from [parent].") + if(11) + if(diff==FORWARD) + user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") + else + user.visible_message("[user] unfastens the scanner module.", "You unfasten the scanner module.") + if(12) + if(diff==FORWARD) + user.visible_message("[user] secures [I].", "You secure [I].") + else + user.visible_message("[user] removes the capacitor from [parent].", "You remove the capacitor from [parent].") + if(13) + if(diff==FORWARD) + user.visible_message("[user] installs [I].", "You install [I].") + else + user.visible_message("[user] unsecures the capacitor from [parent].", "You unsecure the capacitor from [parent].") + if(14) + if(diff==FORWARD) + user.visible_message("[user] secures the power cell.", "You secure the power cell.") + else + user.visible_message("[user] pries the power cell from [parent].", "You pry the power cell from [parent].") + if(15) + if(diff==FORWARD) + user.visible_message("[user] installs the internal armor layer to [parent].", "You install the internal armor layer to [parent].") + else + user.visible_message("[user] unfastens the power cell.", "You unfasten the power cell.") + if(16) + if(diff==FORWARD) + user.visible_message("[user] secures the internal armor layer.", "You secure the internal armor layer.") + else + user.visible_message("[user] pries internal armor layer from [parent].", "You pry internal armor layer from [parent].") + if(17) + if(diff==FORWARD) + user.visible_message("[user] welds the internal armor layer to [parent].", "You weld the internal armor layer to [parent].") + else + user.visible_message("[user] unfastens the internal armor layer.", "You unfasten the internal armor layer.") + if(18) + if(diff==FORWARD) + user.visible_message("[user] installs the external reinforced armor layer to [parent].", "You install the external reinforced armor layer to [parent].") + else + user.visible_message("[user] cuts the internal armor layer from [parent].", "You cut the internal armor layer from [parent].") + if(19) + if(diff==FORWARD) + user.visible_message("[user] secures the external armor layer.", "You secure the external reinforced armor layer.") + else + user.visible_message("[user] pries external armor layer from [parent].", "You pry external armor layer from [parent].") + if(20) + if(diff==FORWARD) + user.visible_message("[user] welds the external armor layer to [parent].", "You weld the external armor layer to [parent].") + else + user.visible_message("[user] unfastens the external armor layer.", "You unfasten the external armor layer.") + return TRUE + +/datum/component/construction/unordered/mecha_chassis/gygax + result = /datum/component/construction/mecha/gygax + steps = list( + /obj/item/mecha_parts/part/gygax_torso, + /obj/item/mecha_parts/part/gygax_left_arm, + /obj/item/mecha_parts/part/gygax_right_arm, + /obj/item/mecha_parts/part/gygax_left_leg, + /obj/item/mecha_parts/part/gygax_right_leg, + /obj/item/mecha_parts/part/gygax_head + ) + +/datum/component/construction/mecha/gygax + result = /obj/mecha/combat/gygax + base_icon = "gygax" + + circuit_control = /obj/item/circuitboard/mecha/gygax/main + circuit_periph = /obj/item/circuitboard/mecha/gygax/peripherals + circuit_weapon = /obj/item/circuitboard/mecha/gygax/targeting + + inner_plating = /obj/item/stack/sheet/metal + inner_plating_amount = 5 + + outer_plating=/obj/item/mecha_parts/part/gygax_armor + outer_plating_amount=1 + +/datum/component/construction/mecha/gygax/action(datum/source, atom/used_atom, mob/user) + return check_step(used_atom,user) + +/datum/component/construction/mecha/gygax/custom_action(obj/item/I, mob/living/user, diff) + if(!..()) + return FALSE + + switch(index) + if(1) + user.visible_message("[user] connects [parent] hydraulic systems.", "You connect [parent] hydraulic systems.") + if(2) + if(diff==FORWARD) + user.visible_message("[user] activates [parent] hydraulic systems.", "You activate [parent] hydraulic systems.") + else + user.visible_message("[user] disconnects [parent] hydraulic systems.", "You disconnect [parent] hydraulic systems.") + if(3) + if(diff==FORWARD) + user.visible_message("[user] adds the wiring to [parent].", "You add the wiring to [parent].") + else + user.visible_message("[user] deactivates [parent] hydraulic systems.", "You deactivate [parent] hydraulic systems.") + if(4) + if(diff==FORWARD) + user.visible_message("[user] adjusts the wiring of [parent].", "You adjust the wiring of [parent].") + else + user.visible_message("[user] removes the wiring from [parent].", "You remove the wiring from [parent].") + if(5) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] disconnects the wiring of [parent].", "You disconnect the wiring of [parent].") + if(6) + if(diff==FORWARD) + user.visible_message("[user] secures the mainboard.", "You secure the mainboard.") + else + user.visible_message("[user] removes the central control module from [parent].", "You remove the central computer mainboard from [parent].") + if(7) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] unfastens the mainboard.", "You unfasten the mainboard.") + if(8) + if(diff==FORWARD) + user.visible_message("[user] secures the peripherals control module.", "You secure the peripherals control module.") + else + user.visible_message("[user] removes the peripherals control module from [parent].", "You remove the peripherals control module from [parent].") + if(9) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] unfastens the peripherals control module.", "You unfasten the peripherals control module.") + if(10) + if(diff==FORWARD) + user.visible_message("[user] secures the weapon control module.", "You secure the weapon control module.") + else + user.visible_message("[user] removes the weapon control module from [parent].", "You remove the weapon control module from [parent].") + if(11) + if(diff==FORWARD) + user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") + else + user.visible_message("[user] unfastens the weapon control module.", "You unfasten the weapon control module.") + if(12) + if(diff==FORWARD) + user.visible_message("[user] secures the scanner module.", "You secure the scanner module.") + else + user.visible_message("[user] removes the scanner module from [parent].", "You remove the scanner module from [parent].") + if(13) + if(diff==FORWARD) + user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") + else + user.visible_message("[user] unfastens the scanner module.", "You unfasten the scanner module.") + if(14) + if(diff==FORWARD) + user.visible_message("[user] secures the capacitor.", "You secure the capacitor.") + else + user.visible_message("[user] removes the capacitor from [parent].", "You remove the capacitor from [parent].") + if(15) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] unfastens the capacitor.", "You unfasten the capacitor.") + if(16) + if(diff==FORWARD) + user.visible_message("[user] secures the power cell.", "You secure the power cell.") + else + user.visible_message("[user] pries the power cell from [parent].", "You pry the power cell from [parent].") + if(17) + if(diff==FORWARD) + user.visible_message("[user] installs the internal armor layer to [parent].", "You install the internal armor layer to [parent].") + else + user.visible_message("[user] unfastens the power cell.", "You unfasten the power cell.") + if(18) + if(diff==FORWARD) + user.visible_message("[user] secures the internal armor layer.", "You secure the internal armor layer.") + else + user.visible_message("[user] pries internal armor layer from [parent].", "You pry internal armor layer from [parent].") + if(19) + if(diff==FORWARD) + user.visible_message("[user] welds the internal armor layer to [parent].", "You weld the internal armor layer to [parent].") + else + user.visible_message("[user] unfastens the internal armor layer.", "You unfasten the internal armor layer.") + if(20) + if(diff==FORWARD) + user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") + else + user.visible_message("[user] cuts the internal armor layer from [parent].", "You cut the internal armor layer from [parent].") + if(21) + if(diff==FORWARD) + user.visible_message("[user] secures Gygax Armor Plates.", "You secure Gygax Armor Plates.") + else + user.visible_message("[user] pries Gygax Armor Plates from [parent].", "You pry Gygax Armor Plates from [parent].") + if(22) + if(diff==FORWARD) + user.visible_message("[user] welds Gygax Armor Plates to [parent].", "You weld Gygax Armor Plates to [parent].") + else + user.visible_message("[user] unfastens Gygax Armor Plates.", "You unfasten Gygax Armor Plates.") + return TRUE + +/datum/component/construction/unordered/mecha_chassis/firefighter + result = /datum/component/construction/mecha/firefighter + steps = list( + /obj/item/mecha_parts/part/ripley_torso, + /obj/item/mecha_parts/part/ripley_left_arm, + /obj/item/mecha_parts/part/ripley_right_arm, + /obj/item/mecha_parts/part/ripley_left_leg, + /obj/item/mecha_parts/part/ripley_right_leg, + /obj/item/clothing/suit/fire + ) + +/datum/component/construction/mecha/firefighter + result = /obj/mecha/working/ripley/firefighter + base_icon = "fireripley" + + circuit_control = /obj/item/circuitboard/mecha/ripley/main + circuit_periph = /obj/item/circuitboard/mecha/ripley/peripherals + + inner_plating = /obj/item/stack/sheet/plasteel + inner_plating_amount = 5 + +/datum/component/construction/mecha/firefighter/get_outer_plating_steps() + return list( + list( + "key" = /obj/item/stack/sheet/plasteel, + "amount" = 5, + "back_key" = TOOL_WELDER, + "desc" = "Internal armor is welded." + ), + list( + "key" = /obj/item/stack/sheet/plasteel, + "amount" = 5, + "back_key" = TOOL_CROWBAR, + "desc" = "External armor is being installed." + ), + list( + "key" = TOOL_WRENCH, + "back_key" = TOOL_CROWBAR, + "desc" = "External armor is installed." + ), + list( + "key" = TOOL_WELDER, + "back_key" = TOOL_WRENCH, + "desc" = "External armor is wrenched." + ), + ) + +/datum/component/construction/mecha/firefighter/custom_action(obj/item/I, mob/living/user, diff) + if(!..()) + return FALSE + + //TODO: better messages. + switch(index) + if(1) + user.visible_message("[user] connects [parent] hydraulic systems.", "You connect [parent] hydraulic systems.") + if(2) + if(diff==FORWARD) + user.visible_message("[user] activates [parent] hydraulic systems.", "You activate [parent] hydraulic systems.") + else + user.visible_message("[user] disconnects [parent] hydraulic systems.", "You disconnect [parent] hydraulic systems.") + if(3) + if(diff==FORWARD) + user.visible_message("[user] adds the wiring to [parent].", "You add the wiring to [parent].") + else + user.visible_message("[user] deactivates [parent] hydraulic systems.", "You deactivate [parent] hydraulic systems.") + if(4) + if(diff==FORWARD) + user.visible_message("[user] adjusts the wiring of [parent].", "You adjust the wiring of [parent].") + else + user.visible_message("[user] removes the wiring from [parent].", "You remove the wiring from [parent].") + if(5) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] disconnects the wiring of [parent].", "You disconnect the wiring of [parent].") + if(6) + if(diff==FORWARD) + user.visible_message("[user] secures the mainboard.", "You secure the mainboard.") + else + user.visible_message("[user] removes the central control module from [parent].", "You remove the central computer mainboard from [parent].") + if(7) + if(diff==FORWARD) + user.visible_message("[user] installs [I]into [parent].", "You install [I]into [parent].") + else + user.visible_message("[user] unfastens the mainboard.", "You unfasten the mainboard.") + if(8) + if(diff==FORWARD) + user.visible_message("[user] secures the peripherals control module.", "You secure the peripherals control module.") + else + user.visible_message("[user] removes the peripherals control module from [parent].", "You remove the peripherals control module from [parent].") + if(9) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] unfastens the peripherals control module.", "You unfasten the peripherals control module.") + if(10) + if(diff==FORWARD) + user.visible_message("[user] secures the scanner module.", "You secure the scanner module.") + else + user.visible_message("[user] removes the scanner module from [parent].", "You remove the scanner module from [parent].") + if(11) + if(diff==FORWARD) + user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") + else + user.visible_message("[user] unfastens the scanner module.", "You unfasten the scanner module.") + if(12) + if(diff==FORWARD) + user.visible_message("[user] secures the capacitor.", "You secure the capacitor.") + else + user.visible_message("[user] removes the capacitor from [parent].", "You remove the capacitor from [parent].") + if(13) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] unfastens the capacitor.", "You unfasten the capacitor.") + if(14) + if(diff==FORWARD) + user.visible_message("[user] secures the power cell.", "You secure the power cell.") + else + user.visible_message("[user] pries the power cell from [parent].", "You pry the power cell from [parent].") + if(15) + if(diff==FORWARD) + user.visible_message("[user] installs the internal armor layer to [parent].", "You install the internal armor layer to [parent].") + else + user.visible_message("[user] unfastens the power cell.", "You unfasten the power cell.") + if(16) + if(diff==FORWARD) + user.visible_message("[user] secures the internal armor layer.", "You secure the internal armor layer.") + else + user.visible_message("[user] pries internal armor layer from [parent].", "You pry internal armor layer from [parent].") + if(17) + if(diff==FORWARD) + user.visible_message("[user] welds the internal armor layer to [parent].", "You weld the internal armor layer to [parent].") + else + user.visible_message("[user] unfastens the internal armor layer.", "You unfasten the internal armor layer.") + if(18) + if(diff==FORWARD) + user.visible_message("[user] starts to install the external armor layer to [parent].", "You install the external armor layer to [parent].") + else + user.visible_message("[user] cuts the internal armor layer from [parent].", "You cut the internal armor layer from [parent].") + if(19) + if(diff==FORWARD) + user.visible_message("[user] installs the external reinforced armor layer to [parent].", "You install the external reinforced armor layer to [parent].") + else + user.visible_message("[user] removes the external armor from [parent].", "You remove the external armor from [parent].") + if(20) + if(diff==FORWARD) + user.visible_message("[user] secures the external armor layer.", "You secure the external reinforced armor layer.") + else + user.visible_message("[user] pries external armor layer from [parent].", "You pry external armor layer from [parent].") + if(21) + if(diff==FORWARD) + user.visible_message("[user] welds the external armor layer to [parent].", "You weld the external armor layer to [parent].") + else + user.visible_message("[user] unfastens the external armor layer.", "You unfasten the external armor layer.") + return TRUE + +/datum/component/construction/unordered/mecha_chassis/honker + result = /datum/component/construction/mecha/honker + steps = list( + /obj/item/mecha_parts/part/honker_torso, + /obj/item/mecha_parts/part/honker_left_arm, + /obj/item/mecha_parts/part/honker_right_arm, + /obj/item/mecha_parts/part/honker_left_leg, + /obj/item/mecha_parts/part/honker_right_leg, + /obj/item/mecha_parts/part/honker_head + ) + +/datum/component/construction/mecha/honker + result = /obj/mecha/combat/honker + steps = list( + list( + "key" = /obj/item/bikehorn + ), + list( + "key" = /obj/item/circuitboard/mecha/honker/main, + "action" = ITEM_DELETE + ), + list( + "key" = /obj/item/bikehorn + ), + list( + "key" = /obj/item/circuitboard/mecha/honker/peripherals, + "action" = ITEM_DELETE + ), + list( + "key" = /obj/item/bikehorn + ), + list( + "key" = /obj/item/circuitboard/mecha/honker/targeting, + "action" = ITEM_DELETE + ), + list( + "key" = /obj/item/bikehorn + ), + list( + "key" = /obj/item/stock_parts/scanning_module, + "action" = ITEM_MOVE_INSIDE + ), + list( + "key" = /obj/item/bikehorn + ), + list( + "key" = /obj/item/stock_parts/capacitor, + "action" = ITEM_MOVE_INSIDE + ), + list( + "key" = /obj/item/bikehorn + ), + list( + "key" = /obj/item/stock_parts/cell, + "action" = ITEM_MOVE_INSIDE + ), + list( + "key" = /obj/item/bikehorn + ), + list( + "key" = /obj/item/clothing/mask/gas/clown_hat, + "action" = ITEM_DELETE + ), + list( + "key" = /obj/item/bikehorn + ), + list( + "key" = /obj/item/clothing/shoes/clown_shoes, + "action" = ITEM_DELETE + ), + list( + "key" = /obj/item/bikehorn + ), + ) + +/datum/component/construction/mecha/honker/get_steps() + return steps + +// HONK doesn't have any construction step icons, so we just set an icon once. +/datum/component/construction/mecha/honker/update_parent(step_index) + if(step_index == 1) + var/atom/parent_atom = parent + parent_atom.icon = 'icons/mecha/mech_construct.dmi' + parent_atom.icon_state = "honker_chassis" + ..() + +/datum/component/construction/mecha/honker/custom_action(obj/item/I, mob/living/user, diff) + if(!..()) + return FALSE + + if(istype(I, /obj/item/bikehorn)) + playsound(parent, 'sound/items/bikehorn.ogg', 50, TRUE) + user.visible_message("HONK!") + + //TODO: better messages. + switch(index) + if(2) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + if(4) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + if(6) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + if(8) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + if(10) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + if(12) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + if(14) + user.visible_message("[user] puts [I] on [parent].", "You put [I] on [parent].") + if(16) + user.visible_message("[user] puts [I] on [parent].", "You put [I] on [parent].") + return TRUE + +/datum/component/construction/unordered/mecha_chassis/durand + result = /datum/component/construction/mecha/durand + steps = list( + /obj/item/mecha_parts/part/durand_torso, + /obj/item/mecha_parts/part/durand_left_arm, + /obj/item/mecha_parts/part/durand_right_arm, + /obj/item/mecha_parts/part/durand_left_leg, + /obj/item/mecha_parts/part/durand_right_leg, + /obj/item/mecha_parts/part/durand_head + ) + +/datum/component/construction/mecha/durand + result = /obj/mecha/combat/durand + base_icon = "durand" + + circuit_control = /obj/item/circuitboard/mecha/durand/main + circuit_periph = /obj/item/circuitboard/mecha/durand/peripherals + circuit_weapon = /obj/item/circuitboard/mecha/durand/targeting + + inner_plating = /obj/item/stack/sheet/metal + inner_plating_amount = 5 + + outer_plating = /obj/item/mecha_parts/part/durand_armor + outer_plating_amount = 1 + +/datum/component/construction/mecha/durand/custom_action(obj/item/I, mob/living/user, diff) + if(!..()) + return FALSE + + //TODO: better messages. + switch(index) + if(1) + user.visible_message("[user] connects [parent] hydraulic systems.", "You connect [parent] hydraulic systems.") + if(2) + if(diff==FORWARD) + user.visible_message("[user] activates [parent] hydraulic systems.", "You activate [parent] hydraulic systems.") + else + user.visible_message("[user] disconnects [parent] hydraulic systems.", "You disconnect [parent] hydraulic systems.") + if(3) + if(diff==FORWARD) + user.visible_message("[user] adds the wiring to [parent].", "You add the wiring to [parent].") + else + user.visible_message("[user] deactivates [parent] hydraulic systems.", "You deactivate [parent] hydraulic systems.") + if(4) + if(diff==FORWARD) + user.visible_message("[user] adjusts the wiring of [parent].", "You adjust the wiring of [parent].") + else + user.visible_message("[user] removes the wiring from [parent].", "You remove the wiring from [parent].") + if(5) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] disconnects the wiring of [parent].", "You disconnect the wiring of [parent].") + if(6) + if(diff==FORWARD) + user.visible_message("[user] secures the mainboard.", "You secure the mainboard.") + else + user.visible_message("[user] removes the central control module from [parent].", "You remove the central computer mainboard from [parent].") + if(7) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] unfastens the mainboard.", "You unfasten the mainboard.") + if(8) + if(diff==FORWARD) + user.visible_message("[user] secures the peripherals control module.", "You secure the peripherals control module.") + else + user.visible_message("[user] removes the peripherals control module from [parent].", "You remove the peripherals control module from [parent].") + if(9) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] unfastens the peripherals control module.", "You unfasten the peripherals control module.") + if(10) + if(diff==FORWARD) + user.visible_message("[user] secures the weapon control module.", "You secure the weapon control module.") + else + user.visible_message("[user] removes the weapon control module from [parent].", "You remove the weapon control module from [parent].") + if(11) + if(diff==FORWARD) + user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") + else + user.visible_message("[user] unfastens the weapon control module.", "You unfasten the weapon control module.") + if(12) + if(diff==FORWARD) + user.visible_message("[user] secures the scanner module.", "You secure the scanner module.") + else + user.visible_message("[user] removes the scanner module from [parent].", "You remove the scanner module from [parent].") + if(13) + if(diff==FORWARD) + user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") + else + user.visible_message("[user] unfastens the scanner module.", "You unfasten the scanner module.") + if(14) + if(diff==FORWARD) + user.visible_message("[user] secures the capacitor.", "You secure the capacitor.") + else + user.visible_message("[user] removes the capacitor from [parent].", "You remove the capacitor from [parent].") + if(15) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] unfastens the capacitor.", "You unfasten the capacitor.") + if(16) + if(diff==FORWARD) + user.visible_message("[user] secures the power cell.", "You secure the power cell.") + else + user.visible_message("[user] pries the power cell from [parent].", "You pry the power cell from [parent].") + if(17) + if(diff==FORWARD) + user.visible_message("[user] installs the internal armor layer to [parent].", "You install the internal armor layer to [parent].") + else + user.visible_message("[user] unfastens the power cell.", "You unfasten the power cell.") + if(18) + if(diff==FORWARD) + user.visible_message("[user] secures the internal armor layer.", "You secure the internal armor layer.") + else + user.visible_message("[user] pries internal armor layer from [parent].", "You pry internal armor layer from [parent].") + if(19) + if(diff==FORWARD) + user.visible_message("[user] welds the internal armor layer to [parent].", "You weld the internal armor layer to [parent].") + else + user.visible_message("[user] unfastens the internal armor layer.", "You unfasten the internal armor layer.") + if(20) + if(diff==FORWARD) + user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") + else + user.visible_message("[user] cuts the internal armor layer from [parent].", "You cut the internal armor layer from [parent].") + if(21) + if(diff==FORWARD) + user.visible_message("[user] secures Durand Armor Plates.", "You secure Durand Armor Plates.") + else + user.visible_message("[user] pries Durand Armor Plates from [parent].", "You pry Durand Armor Plates from [parent].") + if(22) + if(diff==FORWARD) + user.visible_message("[user] welds Durand Armor Plates to [parent].", "You weld Durand Armor Plates to [parent].") + else + user.visible_message("[user] unfastens Durand Armor Plates.", "You unfasten Durand Armor Plates.") + return TRUE + +//PHAZON + +/datum/component/construction/unordered/mecha_chassis/phazon + result = /datum/component/construction/mecha/phazon + steps = list( + /obj/item/mecha_parts/part/phazon_torso, + /obj/item/mecha_parts/part/phazon_left_arm, + /obj/item/mecha_parts/part/phazon_right_arm, + /obj/item/mecha_parts/part/phazon_left_leg, + /obj/item/mecha_parts/part/phazon_right_leg, + /obj/item/mecha_parts/part/phazon_head + ) + +/datum/component/construction/mecha/phazon + result = /obj/mecha/combat/phazon + base_icon = "phazon" + + circuit_control = /obj/item/circuitboard/mecha/phazon/main + circuit_periph = /obj/item/circuitboard/mecha/phazon/peripherals + circuit_weapon = /obj/item/circuitboard/mecha/phazon/targeting + + inner_plating = /obj/item/stack/sheet/plasteel + inner_plating_amount = 5 + + outer_plating = /obj/item/mecha_parts/part/phazon_armor + outer_plating_amount = 1 + +/datum/component/construction/mecha/phazon/get_stockpart_steps() + return list( + list( + "key" = /obj/item/stock_parts/scanning_module, + "action" = ITEM_MOVE_INSIDE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Weapon control module is secured." + ), + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "Scanner module is installed." + ), + list( + "key" = /obj/item/stock_parts/capacitor, + "action" = ITEM_MOVE_INSIDE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Scanner module is secured." + ), + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "Capacitor is installed." + ), + list( + "key" = /obj/item/stack/ore/bluespace_crystal, + "amount" = 1, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Capacitor is secured." + ), + list( + "key" = /obj/item/stack/cable_coil, + "amount" = 5, + "back_key" = TOOL_CROWBAR, + "desc" = "The bluespace crystal is installed." + ), + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_WIRECUTTER, + "desc" = "The bluespace crystal is connected." + ), + list( + "key" = /obj/item/stock_parts/cell, + "action" = ITEM_MOVE_INSIDE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "The bluespace crystal is engaged." + ), + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "The power cell is installed.", + "icon_state" = "phazon17" + // This is the point where a step icon is skipped, so "icon_state" had to be set manually starting from here. + ) + ) + +/datum/component/construction/mecha/phazon/get_outer_plating_steps() + return list( + list( + "key" = outer_plating, + "amount" = 1, + "action" = ITEM_DELETE, + "back_key" = TOOL_WELDER, + "desc" = "Internal armor is welded." + ), + list( + "key" = TOOL_WRENCH, + "back_key" = TOOL_CROWBAR, + "desc" = "External armor is installed." + ), + list( + "key" = TOOL_WELDER, + "back_key" = TOOL_WRENCH, + "desc" = "External armor is wrenched." + ), + list( + "key" = /obj/item/assembly/signaler/anomaly, //WaspStation Edit - Any anomaly core for Phazons + "action" = ITEM_DELETE, + "back_key" = TOOL_WELDER, + "desc" = "Bluespace anomaly core socket is open.", + "icon_state" = "phazon24" + ) + ) + +/datum/component/construction/mecha/phazon/custom_action(obj/item/I, mob/living/user, diff) + if(!..()) + return FALSE + + //TODO: better messages. + switch(index) + if(1) + user.visible_message("[user] connects [parent] hydraulic systems.", "You connect [parent] hydraulic systems.") + if(2) + if(diff==FORWARD) + user.visible_message("[user] activates [parent] hydraulic systems.", "You activate [parent] hydraulic systems.") + else + user.visible_message("[user] disconnects [parent] hydraulic systems.", "You disconnect [parent] hydraulic systems.") + if(3) + if(diff==FORWARD) + user.visible_message("[user] adds the wiring to [parent].", "You add the wiring to [parent].") + else + user.visible_message("[user] deactivates [parent] hydraulic systems.", "You deactivate [parent] hydraulic systems.") + if(4) + if(diff==FORWARD) + user.visible_message("[user] adjusts the wiring of [parent].", "You adjust the wiring of [parent].") + else + user.visible_message("[user] removes the wiring from [parent].", "You remove the wiring from [parent].") + if(5) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] disconnects the wiring of [parent].", "You disconnect the wiring of [parent].") + if(6) + if(diff==FORWARD) + user.visible_message("[user] secures the mainboard.", "You secure the mainboard.") + else + user.visible_message("[user] removes the central control module from [parent].", "You remove the central computer mainboard from [parent].") + if(7) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] unfastens the mainboard.", "You unfasten the mainboard.") + if(8) + if(diff==FORWARD) + user.visible_message("[user] secures the peripherals control module.", "You secure the peripherals control module.") + else + user.visible_message("[user] removes the peripherals control module from [parent].", "You remove the peripherals control module from [parent].") + if(9) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] unfastens the peripherals control module.", "You unfasten the peripherals control module.") + if(10) + if(diff==FORWARD) + user.visible_message("[user] secures the weapon control module.", "You secure the weapon control module.") + else + user.visible_message("[user] removes the weapon control module from [parent].", "You remove the weapon control module from [parent].") + if(11) + if(diff==FORWARD) + user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") + else + user.visible_message("[user] unfastens the weapon control module.", "You unfasten the weapon control module.") + if(12) + if(diff==FORWARD) + user.visible_message("[user] secures the scanner module.", "You secure the scanner module.") + else + user.visible_message("[user] removes the scanner module from [parent].", "You remove the scanner module from [parent].") + if(13) + if(diff==FORWARD) + user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") + else + user.visible_message("[user] unfastens the scanner module.", "You unfasten the scanner module.") + if(14) + if(diff==FORWARD) + user.visible_message("[user] secures the capacitor.", "You secure the capacitor.") + else + user.visible_message("[user] removes the capacitor from [parent].", "You remove the capacitor from [parent].") + if(15) + if(diff==FORWARD) + user.visible_message("[user] installs [I].", "You install [I].") + else + user.visible_message("[user] unsecures the capacitor from [parent].", "You unsecure the capacitor from [parent].") + if(16) + if(diff==FORWARD) + user.visible_message("[user] connects the bluespace crystal.", "You connect the bluespace crystal.") + else + user.visible_message("[user] removes the bluespace crystal from [parent].", "You remove the bluespace crystal from [parent].") + if(17) + if(diff==FORWARD) + user.visible_message("[user] engages the bluespace crystal.", "You engage the bluespace crystal.") + else + user.visible_message("[user] disconnects the bluespace crystal from [parent].", "You disconnect the bluespace crystal from [parent].") + if(18) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] disengages the bluespace crystal.", "You disengage the bluespace crystal.") + if(19) + if(diff==FORWARD) + user.visible_message("[user] secures the power cell.", "You secure the power cell.") + else + user.visible_message("[user] pries the power cell from [parent].", "You pry the power cell from [parent].") + if(20) + if(diff==FORWARD) + user.visible_message("[user] installs the phase armor layer to [parent].", "You install the phase armor layer to [parent].") + else + user.visible_message("[user] unfastens the power cell.", "You unfasten the power cell.") + if(21) + if(diff==FORWARD) + user.visible_message("[user] secures the phase armor layer.", "You secure the phase armor layer.") + else + user.visible_message("[user] pries the phase armor layer from [parent].", "You pry the phase armor layer from [parent].") + if(22) + if(diff==FORWARD) + user.visible_message("[user] welds the phase armor layer to [parent].", "You weld the phase armor layer to [parent].") + else + user.visible_message("[user] unfastens the phase armor layer.", "You unfasten the phase armor layer.") + if(23) + if(diff==FORWARD) + user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") + else + user.visible_message("[user] cuts phase armor layer from [parent].", "You cut the phase armor layer from [parent].") + if(24) + if(diff==FORWARD) + user.visible_message("[user] secures Phazon Armor Plates.", "You secure Phazon Armor Plates.") + else + user.visible_message("[user] pries Phazon Armor Plates from [parent].", "You pry Phazon Armor Plates from [parent].") + if(25) + if(diff==FORWARD) + user.visible_message("[user] welds Phazon Armor Plates to [parent].", "You weld Phazon Armor Plates to [parent].") + else + user.visible_message("[user] unfastens Phazon Armor Plates.", "You unfasten Phazon Armor Plates.") + if(26) + if(diff==FORWARD) + user.visible_message("[user] carefully inserts the anomaly core into [parent] and secures it.", //WS Edit + "You slowly place the anomaly core into its socket and close its chamber.") //WS Edit - Any anomaly core for phazons + return TRUE + +//ODYSSEUS + +/datum/component/construction/unordered/mecha_chassis/odysseus + result = /datum/component/construction/mecha/odysseus + steps = list( + /obj/item/mecha_parts/part/odysseus_torso, + /obj/item/mecha_parts/part/odysseus_head, + /obj/item/mecha_parts/part/odysseus_left_arm, + /obj/item/mecha_parts/part/odysseus_right_arm, + /obj/item/mecha_parts/part/odysseus_left_leg, + /obj/item/mecha_parts/part/odysseus_right_leg + ) + +/datum/component/construction/mecha/odysseus + result = /obj/mecha/medical/odysseus + base_icon = "odysseus" + + circuit_control = /obj/item/circuitboard/mecha/odysseus/main + circuit_periph = /obj/item/circuitboard/mecha/odysseus/peripherals + + inner_plating = /obj/item/stack/sheet/metal + inner_plating_amount = 5 + + outer_plating = /obj/item/stack/sheet/plasteel + outer_plating_amount = 5 + +/datum/component/construction/mecha/odysseus/custom_action(obj/item/I, mob/living/user, diff) + if(!..()) + return FALSE + + //TODO: better messages. + switch(index) + if(1) + user.visible_message("[user] connects [parent] hydraulic systems.", "You connect [parent] hydraulic systems.") + if(2) + if(diff==FORWARD) + user.visible_message("[user] activates [parent] hydraulic systems.", "You activate [parent] hydraulic systems.") + else + user.visible_message("[user] disconnects [parent] hydraulic systems.", "You disconnect [parent] hydraulic systems.") + if(3) + if(diff==FORWARD) + user.visible_message("[user] adds the wiring to [parent].", "You add the wiring to [parent].") + else + user.visible_message("[user] deactivates [parent] hydraulic systems.", "You deactivate [parent] hydraulic systems.") + if(4) + if(diff==FORWARD) + user.visible_message("[user] adjusts the wiring of [parent].", "You adjust the wiring of [parent].") + else + user.visible_message("[user] removes the wiring from [parent].", "You remove the wiring from [parent].") + if(5) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] disconnects the wiring of [parent].", "You disconnect the wiring of [parent].") + if(6) + if(diff==FORWARD) + user.visible_message("[user] secures the mainboard.", "You secure the mainboard.") + else + user.visible_message("[user] removes the central control module from [parent].", "You remove the central computer mainboard from [parent].") + if(7) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] unfastens the mainboard.", "You unfasten the mainboard.") + if(8) + if(diff==FORWARD) + user.visible_message("[user] secures the peripherals control module.", "You secure the peripherals control module.") + else + user.visible_message("[user] removes the peripherals control module from [parent].", "You remove the peripherals control module from [parent].") + if(9) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] unfastens the peripherals control module.", "You unfasten the peripherals control module.") + if(10) + if(diff==FORWARD) + user.visible_message("[user] secures the scanner module.", "You secure the scanner module.") + else + user.visible_message("[user] removes the scanner module from [parent].", "You remove the scanner module from [parent].") + if(11) + if(diff==FORWARD) + user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") + else + user.visible_message("[user] unfastens the scanner module.", "You unfasten the scanner module.") + if(12) + if(diff==FORWARD) + user.visible_message("[user] secures the capacitor.", "You secure the capacitor.") + else + user.visible_message("[user] removes the capacitor from [parent].", "You remove the capacitor from [parent].") + if(13) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] unfastens the capacitor.", "You unfasten the capacitor.") + if(14) + if(diff==FORWARD) + user.visible_message("[user] secures the power cell.", "You secure the power cell.") + else + user.visible_message("[user] pries the power cell from [parent].", "You pry the power cell from [parent].") + if(15) + if(diff==FORWARD) + user.visible_message("[user] installs the internal armor layer to [parent].", "You install the internal armor layer to [parent].") + else + user.visible_message("[user] unfastens the power cell.", "You unfasten the power cell.") + if(16) + if(diff==FORWARD) + user.visible_message("[user] secures the internal armor layer.", "You secure the internal armor layer.") + else + user.visible_message("[user] pries internal armor layer from [parent].", "You pry internal armor layer from [parent].") + if(17) + if(diff==FORWARD) + user.visible_message("[user] welds the internal armor layer to [parent].", "You weld the internal armor layer to [parent].") + else + user.visible_message("[user] unfastens the internal armor layer.", "You unfasten the internal armor layer.") + if(18) + if(diff==FORWARD) + user.visible_message("[user] installs the external armor layer to [parent].", "You install the external reinforced armor layer to [parent].") + else + user.visible_message("[user] cuts the internal armor layer from [parent].", "You cut the internal armor layer from [parent].") + if(19) + if(diff==FORWARD) + user.visible_message("[user] secures the external armor layer.", "You secure the external reinforced armor layer.") + else + user.visible_message("[user] pries the external armor layer from [parent].", "You pry the external armor layer from [parent].") + if(20) + if(diff==FORWARD) + user.visible_message("[user] welds the external armor layer to [parent].", "You weld the external armor layer to [parent].") + else + user.visible_message("[user] unfastens the external armor layer.", "You unfasten the external armor layer.") + return TRUE diff --git a/code/game/mecha/mecha_control_console.dm b/code/game/mecha/mecha_control_console.dm index 9c52797129e8..50d18c6fda79 100644 --- a/code/game/mecha/mecha_control_console.dm +++ b/code/game/mecha/mecha_control_console.dm @@ -1,163 +1,160 @@ -/obj/machinery/computer/mecha - name = "exosuit control console" - desc = "Used to remotely locate or lockdown exosuits." - icon_screen = "mecha" - icon_keyboard = "tech_key" - req_access = list(ACCESS_ROBOTICS) - circuit = /obj/item/circuitboard/computer/mecha_control - ui_x = 500 - ui_y = 500 - -/obj/machinery/computer/mecha/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "ExosuitControlConsole", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/machinery/computer/mecha/ui_data(mob/user) - var/list/data = list() - - var/list/trackerlist = list() - for(var/obj/mecha/MC in GLOB.mechas_list) - trackerlist += MC.trackers - - data["mechs"] = list() - for(var/obj/item/mecha_parts/mecha_tracking/MT in trackerlist) - if(!MT.chassis) - continue - var/obj/mecha/M = MT.chassis - var/list/mech_data = list( - name = M.name, - 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, - location = get_area_name(M, TRUE), - active_equipment = M.selected, - emp_recharging = MT.recharging, - tracker_ref = REF(MT) - ) - 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) - ) - - data["mechs"] += list(mech_data) - - return data - -/obj/machinery/computer/mecha/ui_act(action, params) - if(..()) - return - - switch(action) - if("send_message") - var/obj/item/mecha_parts/mecha_tracking/MT = locate(params["tracker_ref"]) - if(!istype(MT)) - return - 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(usr, "Message sent.") - . = TRUE - if("shock") - var/obj/item/mecha_parts/mecha_tracking/MT = locate(params["tracker_ref"]) - if(!istype(MT)) - return - 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."] ") - . = TRUE - -/obj/item/mecha_parts/mecha_tracking - name = "exosuit tracking beacon" - desc = "Device used to transmit exosuit data." - icon = 'icons/obj/device.dmi' - icon_state = "motion2" - w_class = WEIGHT_CLASS_SMALL - /// If this beacon allows for AI control. Exists to avoid using istype() on checking - var/ai_beacon = FALSE - /// Cooldown variable for EMP pulsing - var/recharging = FALSE - /// The Mecha that this tracking beacon is attached to - var/obj/mecha/chassis - -/** - * Returns a html formatted string describing attached mech status - */ -/obj/item/mecha_parts/mecha_tracking/proc/get_mecha_info() - if(!chassis) - return FALSE - - var/cell_charge = chassis.get_charge() - var/answer = {"Name: [chassis.name]
                    - Integrity: [round((chassis.obj_integrity/chassis.max_integrity * 100), 0.01)]%
                    - Cell Charge: [isnull(cell_charge) ? "Not Found":"[chassis.cell.percent()]%"]
                    - Airtank: [chassis.internal_tank ? "[round(chassis.return_pressure(), 0.01)]" : "Not Equipped"] kPa
                    - Pilot: [chassis.occupant || "None"]
                    - Location: [get_area_name(chassis, TRUE) || "Unknown"]
                    - 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)]%" - - return answer - -/obj/item/mecha_parts/mecha_tracking/emp_act() - . = ..() - if(!(. & EMP_PROTECT_SELF)) - qdel(src) - -/obj/item/mecha_parts/mecha_tracking/Destroy() - if(chassis) - if(src in chassis.trackers) - chassis.trackers -= src - chassis = null - return ..() - -/obj/item/mecha_parts/mecha_tracking/try_attach_part(mob/user, obj/mecha/M) - if(!..()) - return - M.trackers += src - M.diag_hud_set_mechtracking() - chassis = M - -/** - * 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 - if(chassis) - chassis.emp_act(EMP_HEAVY) - addtimer(CALLBACK(src, /obj/item/mecha_parts/mecha_tracking/proc/recharge), 5 SECONDS, TIMER_UNIQUE | TIMER_OVERRIDE) - recharging = TRUE - -/** - * Resets recharge variable, allowing tracker to be EMP pulsed again - */ -/obj/item/mecha_parts/mecha_tracking/proc/recharge() - recharging = FALSE - -/obj/item/mecha_parts/mecha_tracking/ai_control - name = "exosuit AI control beacon" - desc = "A device used to transmit exosuit data. Also allows active AI units to take control of said exosuit." - ai_beacon = TRUE - -/obj/item/storage/box/mechabeacons - name = "exosuit tracking beacons" - -/obj/item/storage/box/mechabeacons/PopulateContents() - ..() - new /obj/item/mecha_parts/mecha_tracking(src) - new /obj/item/mecha_parts/mecha_tracking(src) - new /obj/item/mecha_parts/mecha_tracking(src) - new /obj/item/mecha_parts/mecha_tracking(src) - new /obj/item/mecha_parts/mecha_tracking(src) - new /obj/item/mecha_parts/mecha_tracking(src) - new /obj/item/mecha_parts/mecha_tracking(src) +/obj/machinery/computer/mecha + name = "exosuit control console" + desc = "Used to remotely locate or lockdown exosuits." + icon_screen = "mecha" + icon_keyboard = "tech_key" + req_access = list(ACCESS_ROBOTICS) + circuit = /obj/item/circuitboard/computer/mecha_control + +/obj/machinery/computer/mecha/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "ExosuitControlConsole", name) + ui.open() + +/obj/machinery/computer/mecha/ui_data(mob/user) + var/list/data = list() + + var/list/trackerlist = list() + for(var/obj/mecha/MC in GLOB.mechas_list) + trackerlist += MC.trackers + + data["mechs"] = list() + for(var/obj/item/mecha_parts/mecha_tracking/MT in trackerlist) + if(!MT.chassis) + continue + var/obj/mecha/M = MT.chassis + var/list/mech_data = list( + name = M.name, + 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, + location = get_area_name(M, TRUE), + active_equipment = M.selected, + emp_recharging = MT.recharging, + tracker_ref = REF(MT) + ) + 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) + ) + + data["mechs"] += list(mech_data) + + return data + +/obj/machinery/computer/mecha/ui_act(action, params) + if(..()) + return + + switch(action) + if("send_message") + var/obj/item/mecha_parts/mecha_tracking/MT = locate(params["tracker_ref"]) + if(!istype(MT)) + return + 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(usr, "Message sent.") + . = TRUE + if("shock") + var/obj/item/mecha_parts/mecha_tracking/MT = locate(params["tracker_ref"]) + if(!istype(MT)) + return + 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."] ") + . = TRUE + +/obj/item/mecha_parts/mecha_tracking + name = "exosuit tracking beacon" + desc = "Device used to transmit exosuit data." + icon = 'icons/obj/device.dmi' + icon_state = "motion2" + w_class = WEIGHT_CLASS_SMALL + /// If this beacon allows for AI control. Exists to avoid using istype() on checking + var/ai_beacon = FALSE + /// Cooldown variable for EMP pulsing + var/recharging = FALSE + /// The Mecha that this tracking beacon is attached to + var/obj/mecha/chassis + +/** + * Returns a html formatted string describing attached mech status + */ +/obj/item/mecha_parts/mecha_tracking/proc/get_mecha_info() + if(!chassis) + return FALSE + + var/cell_charge = chassis.get_charge() + var/answer = {"Name: [chassis.name]
                    + Integrity: [round((chassis.obj_integrity/chassis.max_integrity * 100), 0.01)]%
                    + Cell Charge: [isnull(cell_charge) ? "Not Found":"[chassis.cell.percent()]%"]
                    + Airtank: [chassis.internal_tank ? "[round(chassis.return_pressure(), 0.01)]" : "Not Equipped"] kPa
                    + Pilot: [chassis.occupant || "None"]
                    + Location: [get_area_name(chassis, TRUE) || "Unknown"]
                    + 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)]%" + + return answer + +/obj/item/mecha_parts/mecha_tracking/emp_act() + . = ..() + if(!(. & EMP_PROTECT_SELF)) + qdel(src) + +/obj/item/mecha_parts/mecha_tracking/Destroy() + if(chassis) + if(src in chassis.trackers) + chassis.trackers -= src + chassis = null + return ..() + +/obj/item/mecha_parts/mecha_tracking/try_attach_part(mob/user, obj/mecha/M) + if(!..()) + return + M.trackers += src + M.diag_hud_set_mechtracking() + chassis = M + +/** + * 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 + if(chassis) + chassis.emp_act(EMP_HEAVY) + addtimer(CALLBACK(src, /obj/item/mecha_parts/mecha_tracking/proc/recharge), 5 SECONDS, TIMER_UNIQUE | TIMER_OVERRIDE) + recharging = TRUE + +/** + * Resets recharge variable, allowing tracker to be EMP pulsed again + */ +/obj/item/mecha_parts/mecha_tracking/proc/recharge() + recharging = FALSE + +/obj/item/mecha_parts/mecha_tracking/ai_control + name = "exosuit AI control beacon" + desc = "A device used to transmit exosuit data. Also allows active AI units to take control of said exosuit." + ai_beacon = TRUE + +/obj/item/storage/box/mechabeacons + name = "exosuit tracking beacons" + +/obj/item/storage/box/mechabeacons/PopulateContents() + ..() + new /obj/item/mecha_parts/mecha_tracking(src) + new /obj/item/mecha_parts/mecha_tracking(src) + new /obj/item/mecha_parts/mecha_tracking(src) + new /obj/item/mecha_parts/mecha_tracking(src) + new /obj/item/mecha_parts/mecha_tracking(src) + new /obj/item/mecha_parts/mecha_tracking(src) + new /obj/item/mecha_parts/mecha_tracking(src) diff --git a/code/game/mecha/mecha_parts.dm b/code/game/mecha/mecha_parts.dm index 74b3cf9b67f4..06b5aba989ef 100644 --- a/code/game/mecha/mecha_parts.dm +++ b/code/game/mecha/mecha_parts.dm @@ -1,354 +1,354 @@ -///////////////////////// -////// Mecha Parts ////// -///////////////////////// - -/obj/item/mecha_parts - name = "mecha part" - icon = 'icons/mecha/mech_construct.dmi' - icon_state = "blank" - w_class = WEIGHT_CLASS_GIGANTIC - flags_1 = CONDUCT_1 - -/obj/item/mecha_parts/proc/try_attach_part(mob/user, obj/mecha/M) //For attaching parts to a finished mech - if(!user.transferItemToLoc(src, M)) - to_chat(user, "\The [src] is stuck to your hand, you cannot put it in \the [M]!") - return FALSE - user.visible_message("[user] attaches [src] to [M].", "You attach [src] to [M].") - return TRUE - -/obj/item/mecha_parts/part/try_attach_part(mob/user, obj/mecha/M) - return - -/obj/item/mecha_parts/chassis - name = "Mecha Chassis" - icon_state = "backbone" - interaction_flags_item = NONE //Don't pick us up!! - var/construct_type - -/obj/item/mecha_parts/chassis/Initialize() - . = ..() - if(construct_type) - AddComponent(construct_type) - -/////////// Ripley - -/obj/item/mecha_parts/chassis/ripley - name = "\improper Ripley chassis" - construct_type = /datum/component/construction/unordered/mecha_chassis/ripley - -/obj/item/mecha_parts/part/ripley_torso - name = "\improper Ripley torso" - desc = "A torso part of Ripley APLU. Contains power unit, processing core and life support systems." - icon_state = "ripley_harness" - -/obj/item/mecha_parts/part/ripley_left_arm - name = "\improper Ripley left arm" - desc = "A Ripley APLU left arm. Data and power sockets are compatible with most exosuit tools." - icon_state = "ripley_l_arm" - -/obj/item/mecha_parts/part/ripley_right_arm - name = "\improper Ripley right arm" - desc = "A Ripley APLU right arm. Data and power sockets are compatible with most exosuit tools." - icon_state = "ripley_r_arm" - -/obj/item/mecha_parts/part/ripley_left_leg - name = "\improper Ripley left leg" - desc = "A Ripley APLU left leg. Contains somewhat complex servodrives and balance maintaining systems." - icon_state = "ripley_l_leg" - -/obj/item/mecha_parts/part/ripley_right_leg - name = "\improper Ripley right leg" - desc = "A Ripley APLU right leg. Contains somewhat complex servodrives and balance maintaining systems." - icon_state = "ripley_r_leg" - -///////// Odysseus - -/obj/item/mecha_parts/chassis/odysseus - name = "\improper Odysseus chassis" - construct_type = /datum/component/construction/unordered/mecha_chassis/odysseus - -/obj/item/mecha_parts/part/odysseus_head - name = "\improper Odysseus head" - desc = "An Odysseus head. Contains an integrated medical HUD scanner." - icon_state = "odysseus_head" - -/obj/item/mecha_parts/part/odysseus_torso - name = "\improper Odysseus torso" - desc="A torso part of Odysseus. Contains power unit, processing core and life support systems along with an attachment port for a mounted sleeper." - icon_state = "odysseus_torso" - -/obj/item/mecha_parts/part/odysseus_left_arm - name = "\improper Odysseus left arm" - desc = "An Odysseus left arm. Data and power sockets are compatible with specialized medical equipment." - icon_state = "odysseus_l_arm" - -/obj/item/mecha_parts/part/odysseus_right_arm - name = "\improper Odysseus right arm" - desc = "An Odysseus right arm. Data and power sockets are compatible with specialized medical equipment." - icon_state = "odysseus_r_arm" - -/obj/item/mecha_parts/part/odysseus_left_leg - name = "\improper Odysseus left leg" - desc = "An Odysseus left leg. Contains complex servodrives and balance maintaining systems to maintain stability for critical patients." - icon_state = "odysseus_l_leg" - -/obj/item/mecha_parts/part/odysseus_right_leg - name = "\improper Odysseus right leg" - desc = "An odysseus right leg. Contains complex servodrives and balance maintaining systems to maintain stability for critical patients." - icon_state = "odysseus_r_leg" - -///////// Gygax - -/obj/item/mecha_parts/chassis/gygax - name = "\improper Gygax chassis" - construct_type = /datum/component/construction/unordered/mecha_chassis/gygax - -/obj/item/mecha_parts/part/gygax_torso - name = "\improper Gygax torso" - desc = "A torso part of Gygax. Contains power unit, processing core and life support systems." - icon_state = "gygax_harness" - -/obj/item/mecha_parts/part/gygax_head - name = "\improper Gygax head" - desc = "A Gygax head. Houses advanced surveillance and targeting sensors." - icon_state = "gygax_head" - -/obj/item/mecha_parts/part/gygax_left_arm - name = "\improper Gygax left arm" - desc = "A Gygax left arm. Data and power sockets are compatible with most exosuit tools and weapons." - icon_state = "gygax_l_arm" - -/obj/item/mecha_parts/part/gygax_right_arm - name = "\improper Gygax right arm" - desc = "A Gygax right arm. Data and power sockets are compatible with most exosuit tools and weapons." - icon_state = "gygax_r_arm" - -/obj/item/mecha_parts/part/gygax_left_leg - name = "\improper Gygax left leg" - desc = "A Gygax left leg. Constructed with advanced servomechanisms and actuators to enable faster speed." - icon_state = "gygax_l_leg" - -/obj/item/mecha_parts/part/gygax_right_leg - name = "\improper Gygax right leg" - desc = "A Gygax right leg. Constructed with advanced servomechanisms and actuators to enable faster speed." - icon_state = "gygax_r_leg" - -/obj/item/mecha_parts/part/gygax_armor - gender = PLURAL - name = "\improper Gygax armor plates" - desc = "A set of armor plates designed for the Gygax. Designed to effectively deflect damage with a lightweight construction." - icon_state = "gygax_armor" - - -//////////// Durand - -/obj/item/mecha_parts/chassis/durand - name = "\improper Durand chassis" - construct_type = /datum/component/construction/unordered/mecha_chassis/durand - -/obj/item/mecha_parts/part/durand_torso - name = "\improper Durand torso" - desc = "A torso part of Durand. Contains power unit, processing core and life support systems within a robust protective frame." - icon_state = "durand_harness" - -/obj/item/mecha_parts/part/durand_head - name = "\improper Durand head" - desc = "A Durand head. Houses advanced surveillance and targeting sensors." - icon_state = "durand_head" - -/obj/item/mecha_parts/part/durand_left_arm - name = "\improper Durand left arm" - desc = "A Durand left arm. Data and power sockets are compatible with most exosuit tools and weapons. Packs a really mean punch as well." - icon_state = "durand_l_arm" - -/obj/item/mecha_parts/part/durand_right_arm - name = "\improper Durand right arm" - desc = "A Durand right arm. Data and power sockets are compatible with most exosuit tools and weapons. Packs a really mean punch as well." - icon_state = "durand_r_arm" - -/obj/item/mecha_parts/part/durand_left_leg - name = "\improper Durand left leg" - desc = "A Durand left leg. Built particularly sturdy to support the Durand's heavy weight and defensive needs." - icon_state = "durand_l_leg" - -/obj/item/mecha_parts/part/durand_right_leg - name = "\improper Durand right leg" - desc = "A Durand right leg. Built particularly sturdy to support the Durand's heavy weight and defensive needs." - icon_state = "durand_r_leg" - -/obj/item/mecha_parts/part/durand_armor - gender = PLURAL - name = "\improper Durand armor plates" - desc = "A set of armor plates for the Durand. Built heavy to resist an incredible amount of brute force." - icon_state = "durand_armor" - -////////// Firefighter - -/obj/item/mecha_parts/chassis/firefighter - name = "\improper Firefighter chassis" - construct_type = /datum/component/construction/unordered/mecha_chassis/firefighter - - -////////// HONK - -/obj/item/mecha_parts/chassis/honker - name = "\improper H.O.N.K chassis" - construct_type = /datum/component/construction/unordered/mecha_chassis/honker - -/obj/item/mecha_parts/part/honker_torso - name = "\improper H.O.N.K torso" - desc = "A torso part of H.O.N.K. Contains chuckle unit, bananium core and honk support systems." - icon_state = "honker_harness" - -/obj/item/mecha_parts/part/honker_head - name = "\improper H.O.N.K head" - desc = "A H.O.N.K head. Appears to lack a face plate." - icon_state = "honker_head" - -/obj/item/mecha_parts/part/honker_left_arm - name = "\improper H.O.N.K left arm" - desc = "A H.O.N.K left arm. With unique sockets that accept odd weaponry designed by clown scientists." - icon_state = "honker_l_arm" - -/obj/item/mecha_parts/part/honker_right_arm - name = "\improper H.O.N.K right arm" - desc = "A H.O.N.K right arm. With unique sockets that accept odd weaponry designed by clown scientists." - icon_state = "honker_r_arm" - -/obj/item/mecha_parts/part/honker_left_leg - name = "\improper H.O.N.K left leg" - desc = "A H.O.N.K left leg. The foot appears just large enough to fully accommodate a clown shoe." - icon_state = "honker_l_leg" - -/obj/item/mecha_parts/part/honker_right_leg - name = "\improper H.O.N.K right leg" - desc = "A H.O.N.K right leg. The foot appears just large enough to fully accommodate a clown shoe." - icon_state = "honker_r_leg" - - -////////// Phazon - -/obj/item/mecha_parts/chassis/phazon - name = "\improper Phazon chassis" - construct_type = /datum/component/construction/unordered/mecha_chassis/phazon - -/obj/item/mecha_parts/chassis/phazon/attackby(obj/item/I, mob/user, params) - . = ..() - if(istype(I, /obj/item/assembly/signaler/anomaly) && !istype(I, /obj/item/assembly/signaler/anomaly/bluespace)) - to_chat(user, "The anomaly core socket only accepts bluespace anomaly cores!") - -/obj/item/mecha_parts/part/phazon_torso - name="\improper Phazon torso" - desc="A Phazon torso part. The socket for the bluespace core that powers the exosuit's unique phase drives is located in the middle." - icon_state = "phazon_harness" - -/obj/item/mecha_parts/part/phazon_head - name="\improper Phazon head" - desc="A Phazon head. Its sensors are carefully calibrated to provide vision and data even when the exosuit is phasing." - icon_state = "phazon_head" - -/obj/item/mecha_parts/part/phazon_left_arm - name="\improper Phazon left arm" - desc="A Phazon left arm. Several microtool arrays are located under the armor plating, which can be adjusted to the situation at hand." - icon_state = "phazon_l_arm" - -/obj/item/mecha_parts/part/phazon_right_arm - name="\improper Phazon right arm" - desc="A Phazon right arm. Several microtool arrays are located under the armor plating, which can be adjusted to the situation at hand." - icon_state = "phazon_r_arm" - -/obj/item/mecha_parts/part/phazon_left_leg - name="\improper Phazon left leg" - desc="A Phazon left leg. It contains the unique phase drives that allow the exosuit to phase through solid matter when engaged." - icon_state = "phazon_l_leg" - -/obj/item/mecha_parts/part/phazon_right_leg - name="\improper Phazon right leg" - desc="A Phazon right leg. It contains the unique phase drives that allow the exosuit to phase through solid matter when engaged." - icon_state = "phazon_r_leg" - -/obj/item/mecha_parts/part/phazon_armor - name="Phazon armor" - desc="Phazon armor plates. They are layered with plasma to protect the pilot from the stress of phasing and have unusual properties." - icon_state = "phazon_armor" - - -///////// Circuitboards - -/obj/item/circuitboard/mecha - name = "exosuit circuit board" - icon = 'icons/obj/module.dmi' - icon_state = "std_mod" - item_state = "electronic" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - flags_1 = CONDUCT_1 - force = 5 - w_class = WEIGHT_CLASS_SMALL - throwforce = 0 - throw_speed = 3 - throw_range = 7 - -/obj/item/circuitboard/mecha/ripley/peripherals - name = "Ripley Peripherals Control module (Exosuit Board)" - icon_state = "mcontroller" - -/obj/item/circuitboard/mecha/ripley/main - name = "Ripley Central Control module (Exosuit Board)" - icon_state = "mainboard" - - -/obj/item/circuitboard/mecha/gygax/peripherals - name = "Gygax Peripherals Control module (Exosuit Board)" - icon_state = "mcontroller" - -/obj/item/circuitboard/mecha/gygax/targeting - name = "Gygax Weapon Control and Targeting module (Exosuit Board)" - icon_state = "mcontroller" - -/obj/item/circuitboard/mecha/gygax/main - name = "Gygax Central Control module (Exosuit Board)" - icon_state = "mainboard" - -/obj/item/circuitboard/mecha/durand/peripherals - name = "Durand Peripherals Control module (Exosuit Board)" - icon_state = "mcontroller" - -/obj/item/circuitboard/mecha/durand/targeting - name = "Durand Weapon Control and Targeting module (Exosuit Board)" - icon_state = "mcontroller" - -/obj/item/circuitboard/mecha/durand/main - name = "Durand Central Control module (Exosuit Board)" - icon_state = "mainboard" - -/obj/item/circuitboard/mecha/honker/peripherals - name = "H.O.N.K Peripherals Control module (Exosuit Board)" - icon_state = "mcontroller" - -/obj/item/circuitboard/mecha/honker/targeting - name = "H.O.N.K Weapon Control and Targeting module (Exosuit Board)" - icon_state = "mcontroller" - -/obj/item/circuitboard/mecha/honker/main - name = "H.O.N.K Central Control module (Exosuit Board)" - icon_state = "mainboard" - -/obj/item/circuitboard/mecha/odysseus/peripherals - name = "Odysseus Peripherals Control module (Exosuit Board)" - icon_state = "mcontroller" - -/obj/item/circuitboard/mecha/odysseus/main - name = "Odysseus Central Control module (Exosuit Board)" - icon_state = "mainboard" - -/obj/item/circuitboard/mecha/phazon/peripherals - name = "Phazon Peripherals Control module (Exosuit Board)" - icon_state = "mcontroller" - -/obj/item/circuitboard/mecha/phazon/targeting - name = "Phazon Weapon Control and Targeting module (Exosuit Board)" - icon_state = "mcontroller" - -/obj/item/circuitboard/mecha/phazon/main - name = "Phazon Central Control module (Exosuit Board)" +///////////////////////// +////// Mecha Parts ////// +///////////////////////// + +/obj/item/mecha_parts + name = "mecha part" + icon = 'icons/mecha/mech_construct.dmi' + icon_state = "blank" + w_class = WEIGHT_CLASS_GIGANTIC + flags_1 = CONDUCT_1 + +/obj/item/mecha_parts/proc/try_attach_part(mob/user, obj/mecha/M) //For attaching parts to a finished mech + if(!user.transferItemToLoc(src, M)) + to_chat(user, "\The [src] is stuck to your hand, you cannot put it in \the [M]!") + return FALSE + user.visible_message("[user] attaches [src] to [M].", "You attach [src] to [M].") + return TRUE + +/obj/item/mecha_parts/part/try_attach_part(mob/user, obj/mecha/M) + return + +/obj/item/mecha_parts/chassis + name = "Mecha Chassis" + icon_state = "backbone" + interaction_flags_item = NONE //Don't pick us up!! + var/construct_type + +/obj/item/mecha_parts/chassis/Initialize() + . = ..() + if(construct_type) + AddComponent(construct_type) + +/////////// Ripley + +/obj/item/mecha_parts/chassis/ripley + name = "\improper Ripley chassis" + construct_type = /datum/component/construction/unordered/mecha_chassis/ripley + +/obj/item/mecha_parts/part/ripley_torso + name = "\improper Ripley torso" + desc = "A torso part of Ripley APLU. Contains power unit, processing core and life support systems." + icon_state = "ripley_harness" + +/obj/item/mecha_parts/part/ripley_left_arm + name = "\improper Ripley left arm" + desc = "A Ripley APLU left arm. Data and power sockets are compatible with most exosuit tools." + icon_state = "ripley_l_arm" + +/obj/item/mecha_parts/part/ripley_right_arm + name = "\improper Ripley right arm" + desc = "A Ripley APLU right arm. Data and power sockets are compatible with most exosuit tools." + icon_state = "ripley_r_arm" + +/obj/item/mecha_parts/part/ripley_left_leg + name = "\improper Ripley left leg" + desc = "A Ripley APLU left leg. Contains somewhat complex servodrives and balance maintaining systems." + icon_state = "ripley_l_leg" + +/obj/item/mecha_parts/part/ripley_right_leg + name = "\improper Ripley right leg" + desc = "A Ripley APLU right leg. Contains somewhat complex servodrives and balance maintaining systems." + icon_state = "ripley_r_leg" + +///////// Odysseus + +/obj/item/mecha_parts/chassis/odysseus + name = "\improper Odysseus chassis" + construct_type = /datum/component/construction/unordered/mecha_chassis/odysseus + +/obj/item/mecha_parts/part/odysseus_head + name = "\improper Odysseus head" + desc = "An Odysseus head. Contains an integrated medical HUD scanner." + icon_state = "odysseus_head" + +/obj/item/mecha_parts/part/odysseus_torso + name = "\improper Odysseus torso" + desc="A torso part of Odysseus. Contains power unit, processing core and life support systems along with an attachment port for a mounted sleeper." + icon_state = "odysseus_torso" + +/obj/item/mecha_parts/part/odysseus_left_arm + name = "\improper Odysseus left arm" + desc = "An Odysseus left arm. Data and power sockets are compatible with specialized medical equipment." + icon_state = "odysseus_l_arm" + +/obj/item/mecha_parts/part/odysseus_right_arm + name = "\improper Odysseus right arm" + desc = "An Odysseus right arm. Data and power sockets are compatible with specialized medical equipment." + icon_state = "odysseus_r_arm" + +/obj/item/mecha_parts/part/odysseus_left_leg + name = "\improper Odysseus left leg" + desc = "An Odysseus left leg. Contains complex servodrives and balance maintaining systems to maintain stability for critical patients." + icon_state = "odysseus_l_leg" + +/obj/item/mecha_parts/part/odysseus_right_leg + name = "\improper Odysseus right leg" + desc = "An odysseus right leg. Contains complex servodrives and balance maintaining systems to maintain stability for critical patients." + icon_state = "odysseus_r_leg" + +///////// Gygax + +/obj/item/mecha_parts/chassis/gygax + name = "\improper Gygax chassis" + construct_type = /datum/component/construction/unordered/mecha_chassis/gygax + +/obj/item/mecha_parts/part/gygax_torso + name = "\improper Gygax torso" + desc = "A torso part of Gygax. Contains power unit, processing core and life support systems." + icon_state = "gygax_harness" + +/obj/item/mecha_parts/part/gygax_head + name = "\improper Gygax head" + desc = "A Gygax head. Houses advanced surveillance and targeting sensors." + icon_state = "gygax_head" + +/obj/item/mecha_parts/part/gygax_left_arm + name = "\improper Gygax left arm" + desc = "A Gygax left arm. Data and power sockets are compatible with most exosuit tools and weapons." + icon_state = "gygax_l_arm" + +/obj/item/mecha_parts/part/gygax_right_arm + name = "\improper Gygax right arm" + desc = "A Gygax right arm. Data and power sockets are compatible with most exosuit tools and weapons." + icon_state = "gygax_r_arm" + +/obj/item/mecha_parts/part/gygax_left_leg + name = "\improper Gygax left leg" + desc = "A Gygax left leg. Constructed with advanced servomechanisms and actuators to enable faster speed." + icon_state = "gygax_l_leg" + +/obj/item/mecha_parts/part/gygax_right_leg + name = "\improper Gygax right leg" + desc = "A Gygax right leg. Constructed with advanced servomechanisms and actuators to enable faster speed." + icon_state = "gygax_r_leg" + +/obj/item/mecha_parts/part/gygax_armor + gender = PLURAL + name = "\improper Gygax armor plates" + desc = "A set of armor plates designed for the Gygax. Designed to effectively deflect damage with a lightweight construction." + icon_state = "gygax_armor" + + +//////////// Durand + +/obj/item/mecha_parts/chassis/durand + name = "\improper Durand chassis" + construct_type = /datum/component/construction/unordered/mecha_chassis/durand + +/obj/item/mecha_parts/part/durand_torso + name = "\improper Durand torso" + desc = "A torso part of Durand. Contains power unit, processing core and life support systems within a robust protective frame." + icon_state = "durand_harness" + +/obj/item/mecha_parts/part/durand_head + name = "\improper Durand head" + desc = "A Durand head. Houses advanced surveillance and targeting sensors." + icon_state = "durand_head" + +/obj/item/mecha_parts/part/durand_left_arm + name = "\improper Durand left arm" + desc = "A Durand left arm. Data and power sockets are compatible with most exosuit tools and weapons. Packs a really mean punch as well." + icon_state = "durand_l_arm" + +/obj/item/mecha_parts/part/durand_right_arm + name = "\improper Durand right arm" + desc = "A Durand right arm. Data and power sockets are compatible with most exosuit tools and weapons. Packs a really mean punch as well." + icon_state = "durand_r_arm" + +/obj/item/mecha_parts/part/durand_left_leg + name = "\improper Durand left leg" + desc = "A Durand left leg. Built particularly sturdy to support the Durand's heavy weight and defensive needs." + icon_state = "durand_l_leg" + +/obj/item/mecha_parts/part/durand_right_leg + name = "\improper Durand right leg" + desc = "A Durand right leg. Built particularly sturdy to support the Durand's heavy weight and defensive needs." + icon_state = "durand_r_leg" + +/obj/item/mecha_parts/part/durand_armor + gender = PLURAL + name = "\improper Durand armor plates" + desc = "A set of armor plates for the Durand. Built heavy to resist an incredible amount of brute force." + icon_state = "durand_armor" + +////////// Firefighter + +/obj/item/mecha_parts/chassis/firefighter + name = "\improper Firefighter chassis" + construct_type = /datum/component/construction/unordered/mecha_chassis/firefighter + + +////////// HONK + +/obj/item/mecha_parts/chassis/honker + name = "\improper H.O.N.K chassis" + construct_type = /datum/component/construction/unordered/mecha_chassis/honker + +/obj/item/mecha_parts/part/honker_torso + name = "\improper H.O.N.K torso" + desc = "A torso part of H.O.N.K. Contains chuckle unit, bananium core and honk support systems." + icon_state = "honker_harness" + +/obj/item/mecha_parts/part/honker_head + name = "\improper H.O.N.K head" + desc = "A H.O.N.K head. Appears to lack a face plate." + icon_state = "honker_head" + +/obj/item/mecha_parts/part/honker_left_arm + name = "\improper H.O.N.K left arm" + desc = "A H.O.N.K left arm. With unique sockets that accept odd weaponry designed by clown scientists." + icon_state = "honker_l_arm" + +/obj/item/mecha_parts/part/honker_right_arm + name = "\improper H.O.N.K right arm" + desc = "A H.O.N.K right arm. With unique sockets that accept odd weaponry designed by clown scientists." + icon_state = "honker_r_arm" + +/obj/item/mecha_parts/part/honker_left_leg + name = "\improper H.O.N.K left leg" + desc = "A H.O.N.K left leg. The foot appears just large enough to fully accommodate a clown shoe." + icon_state = "honker_l_leg" + +/obj/item/mecha_parts/part/honker_right_leg + name = "\improper H.O.N.K right leg" + desc = "A H.O.N.K right leg. The foot appears just large enough to fully accommodate a clown shoe." + icon_state = "honker_r_leg" + + +////////// Phazon + +/obj/item/mecha_parts/chassis/phazon + name = "\improper Phazon chassis" + construct_type = /datum/component/construction/unordered/mecha_chassis/phazon + +/obj/item/mecha_parts/chassis/phazon/attackby(obj/item/I, mob/user, params) + . = ..() + if(istype(I, /obj/item/assembly/signaler/anomaly) && !istype(I, /obj/item/assembly/signaler/anomaly/bluespace)) + to_chat(user, "The anomaly core socket only accepts bluespace anomaly cores!") + +/obj/item/mecha_parts/part/phazon_torso + name="\improper Phazon torso" + desc="A Phazon torso part. The socket for the bluespace core that powers the exosuit's unique phase drives is located in the middle." + icon_state = "phazon_harness" + +/obj/item/mecha_parts/part/phazon_head + name="\improper Phazon head" + desc="A Phazon head. Its sensors are carefully calibrated to provide vision and data even when the exosuit is phasing." + icon_state = "phazon_head" + +/obj/item/mecha_parts/part/phazon_left_arm + name="\improper Phazon left arm" + desc="A Phazon left arm. Several microtool arrays are located under the armor plating, which can be adjusted to the situation at hand." + icon_state = "phazon_l_arm" + +/obj/item/mecha_parts/part/phazon_right_arm + name="\improper Phazon right arm" + desc="A Phazon right arm. Several microtool arrays are located under the armor plating, which can be adjusted to the situation at hand." + icon_state = "phazon_r_arm" + +/obj/item/mecha_parts/part/phazon_left_leg + name="\improper Phazon left leg" + desc="A Phazon left leg. It contains the unique phase drives that allow the exosuit to phase through solid matter when engaged." + icon_state = "phazon_l_leg" + +/obj/item/mecha_parts/part/phazon_right_leg + name="\improper Phazon right leg" + desc="A Phazon right leg. It contains the unique phase drives that allow the exosuit to phase through solid matter when engaged." + icon_state = "phazon_r_leg" + +/obj/item/mecha_parts/part/phazon_armor + name="Phazon armor" + desc="Phazon armor plates. They are layered with plasma to protect the pilot from the stress of phasing and have unusual properties." + icon_state = "phazon_armor" + + +///////// Circuitboards + +/obj/item/circuitboard/mecha + name = "exosuit circuit board" + icon = 'icons/obj/module.dmi' + icon_state = "std_mod" + item_state = "electronic" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + flags_1 = CONDUCT_1 + force = 5 + w_class = WEIGHT_CLASS_SMALL + throwforce = 0 + throw_speed = 3 + throw_range = 7 + +/obj/item/circuitboard/mecha/ripley/peripherals + name = "Ripley Peripherals Control module (Exosuit Board)" + icon_state = "mcontroller" + +/obj/item/circuitboard/mecha/ripley/main + name = "Ripley Central Control module (Exosuit Board)" + icon_state = "mainboard" + + +/obj/item/circuitboard/mecha/gygax/peripherals + name = "Gygax Peripherals Control module (Exosuit Board)" + icon_state = "mcontroller" + +/obj/item/circuitboard/mecha/gygax/targeting + name = "Gygax Weapon Control and Targeting module (Exosuit Board)" + icon_state = "mcontroller" + +/obj/item/circuitboard/mecha/gygax/main + name = "Gygax Central Control module (Exosuit Board)" + icon_state = "mainboard" + +/obj/item/circuitboard/mecha/durand/peripherals + name = "Durand Peripherals Control module (Exosuit Board)" + icon_state = "mcontroller" + +/obj/item/circuitboard/mecha/durand/targeting + name = "Durand Weapon Control and Targeting module (Exosuit Board)" + icon_state = "mcontroller" + +/obj/item/circuitboard/mecha/durand/main + name = "Durand Central Control module (Exosuit Board)" + icon_state = "mainboard" + +/obj/item/circuitboard/mecha/honker/peripherals + name = "H.O.N.K Peripherals Control module (Exosuit Board)" + icon_state = "mcontroller" + +/obj/item/circuitboard/mecha/honker/targeting + name = "H.O.N.K Weapon Control and Targeting module (Exosuit Board)" + icon_state = "mcontroller" + +/obj/item/circuitboard/mecha/honker/main + name = "H.O.N.K Central Control module (Exosuit Board)" + icon_state = "mainboard" + +/obj/item/circuitboard/mecha/odysseus/peripherals + name = "Odysseus Peripherals Control module (Exosuit Board)" + icon_state = "mcontroller" + +/obj/item/circuitboard/mecha/odysseus/main + name = "Odysseus Central Control module (Exosuit Board)" + icon_state = "mainboard" + +/obj/item/circuitboard/mecha/phazon/peripherals + name = "Phazon Peripherals Control module (Exosuit Board)" + icon_state = "mcontroller" + +/obj/item/circuitboard/mecha/phazon/targeting + name = "Phazon Weapon Control and Targeting module (Exosuit Board)" + icon_state = "mcontroller" + +/obj/item/circuitboard/mecha/phazon/main + name = "Phazon Central Control module (Exosuit Board)" diff --git a/code/game/mecha/mecha_wreckage.dm b/code/game/mecha/mecha_wreckage.dm index 91b2a2bbf2a1..e5cc4570e98f 100644 --- a/code/game/mecha/mecha_wreckage.dm +++ b/code/game/mecha/mecha_wreckage.dm @@ -1,213 +1,213 @@ -/////////////////////////////////// -//////// Mecha wreckage //////// -/////////////////////////////////// - - -/obj/structure/mecha_wreckage - name = "exosuit wreckage" - desc = "Remains of some unfortunate mecha. Completely irreparable, but perhaps something can be salvaged." - icon = 'icons/mecha/mecha.dmi' - density = TRUE - anchored = FALSE - opacity = 0 - var/list/welder_salvage = list(/obj/item/stack/sheet/plasteel, /obj/item/stack/sheet/metal, /obj/item/stack/rods) - var/salvage_num = 5 - var/list/crowbar_salvage = list() - var/wires_removed = FALSE - var/mob/living/silicon/ai/AI //AIs to be salvaged - var/list/parts - -/obj/structure/mecha_wreckage/Initialize(mapload, mob/living/silicon/ai/AI_pilot) - . = ..() - if(parts) - for(var/i in 1 to 2) - if(!parts.len) - break - if(prob(60)) - continue - var/part = pick(parts) - welder_salvage += part - parts = null - if(!AI_pilot) //Type-checking for this is already done in mecha/Destroy() - return - AI = AI_pilot - AI.apply_damage(150, BURN) //Give the AI a bit of damage from the "shock" of being suddenly shut down - AI.death() //The damage is not enough to kill the AI, but to be 'corrupted files' in need of repair. - AI.forceMove(src) //Put the dead AI inside the wreckage for recovery - add_overlay(mutable_appearance('icons/obj/projectiles.dmi', "green_laser")) //Overlay for the recovery beacon - AI.controlled_mech = null - AI.remote_control = null - -/obj/structure/mecha_wreckage/Destroy() - if(AI) - QDEL_NULL(AI) - QDEL_LIST(crowbar_salvage) - return ..() - -/obj/structure/mecha_wreckage/examine(mob/user) - . = ..() - if(!AI) - return - . += "The AI recovery beacon is active." - -/obj/structure/mecha_wreckage/welder_act(mob/living/user, obj/item/I) - ..() - . = TRUE - if(salvage_num <= 0 || !length(welder_salvage)) - to_chat(user, "You don't see anything that can be cut with [I]!") - return - if(!I.use_tool(src, user, 0, volume=50)) - return - if(prob(30)) - to_chat(user, "You fail to salvage anything valuable from [src]!") - return - var/type = pick(welder_salvage) - var/N = new type(get_turf(user)) - user.visible_message("[user] cuts [N] from [src].", "You cut [N] from [src].") - if(!istype(N, /obj/item/stack)) - welder_salvage -= type - salvage_num-- - -/obj/structure/mecha_wreckage/wirecutter_act(mob/living/user, obj/item/I) - ..() - . = TRUE - if(wires_removed) - to_chat(user, "You don't see anything that can be cut with [I]!") - return - var/N = new /obj/item/stack/cable_coil(get_turf(user), rand(1,3)) - user.visible_message("[user] cuts [N] from [src].", "You cut [N] from [src].") - wires_removed = TRUE - -/obj/structure/mecha_wreckage/crowbar_act(mob/living/user, obj/item/I) - ..() - . = TRUE - if(crowbar_salvage.len) - var/obj/S = pick(crowbar_salvage) - S.forceMove(user.drop_location()) - user.visible_message("[user] pries [S] from [src].", "You pry [S] from [src].") - crowbar_salvage -= S - return - to_chat(user, "You don't see anything that can be cut with [I]!") - -/obj/structure/mecha_wreckage/transfer_ai(interaction, mob/user, null, obj/item/aicard/card) - if(!..()) - return - - //Proc called on the wreck by the AI card. - if(interaction != AI_TRANS_TO_CARD) //AIs can only be transferred in one direction, from the wreck to the card. - return - if(!AI) //No AI in the wreck - to_chat(user, "No AI backups found.") - return - cut_overlays() //Remove the recovery beacon overlay - AI.forceMove(card) //Move the dead AI to the card. - card.AI = AI - if(AI.client) //AI player is still in the dead AI and is connected - to_chat(AI, "The remains of your file system have been recovered on a mobile storage device.") - else //Give the AI a heads-up that it is probably going to get fixed. - AI.notify_ghost_cloning("You have been recovered from the wreckage!", source = card) - to_chat(user, "Backup files recovered: [AI.name] ([rand(1000,9999)].exe) salvaged from [name] and stored within local memory.") - AI = null - -/obj/structure/mecha_wreckage/gygax - name = "\improper Gygax wreckage" - icon_state = "gygax-broken" - parts = list( - /obj/item/mecha_parts/part/gygax_torso, - /obj/item/mecha_parts/part/gygax_head, - /obj/item/mecha_parts/part/gygax_left_arm, - /obj/item/mecha_parts/part/gygax_right_arm, - /obj/item/mecha_parts/part/gygax_left_leg, - /obj/item/mecha_parts/part/gygax_right_leg - ) - -/obj/structure/mecha_wreckage/gygax/dark - name = "\improper Dark Gygax wreckage" - icon_state = "darkgygax-broken" - -/obj/structure/mecha_wreckage/marauder - name = "\improper Marauder wreckage" - icon_state = "marauder-broken" - -/obj/structure/mecha_wreckage/mauler - name = "\improper Mauler wreckage" - icon_state = "mauler-broken" - desc = "The syndicate won't be very happy about this..." - -/obj/structure/mecha_wreckage/seraph - name = "\improper Seraph wreckage" - icon_state = "seraph-broken" - -/obj/structure/mecha_wreckage/reticence - name = "\improper Reticence wreckage" - icon_state = "reticence-broken" - color = "#87878715" - desc = "..." - -/obj/structure/mecha_wreckage/ripley - name = "\improper Ripley wreckage" - icon_state = "ripley-broken" - parts = list(/obj/item/mecha_parts/part/ripley_torso, - /obj/item/mecha_parts/part/ripley_left_arm, - /obj/item/mecha_parts/part/ripley_right_arm, - /obj/item/mecha_parts/part/ripley_left_leg, - /obj/item/mecha_parts/part/ripley_right_leg) - -/obj/structure/mecha_wreckage/ripley/mkii - name = "\improper Ripley MK-II wreckage" - icon_state = "ripleymkii-broken" - -/obj/structure/mecha_wreckage/ripley/firefighter - name = "\improper Firefighter wreckage" - icon_state = "firefighter-broken" - parts = list(/obj/item/mecha_parts/part/ripley_torso, - /obj/item/mecha_parts/part/ripley_left_arm, - /obj/item/mecha_parts/part/ripley_right_arm, - /obj/item/mecha_parts/part/ripley_left_leg, - /obj/item/mecha_parts/part/ripley_right_leg, - /obj/item/clothing/suit/fire) - -/obj/structure/mecha_wreckage/ripley/deathripley - name = "\improper Death-Ripley wreckage" - icon_state = "deathripley-broken" - parts = null - -/obj/structure/mecha_wreckage/honker - name = "\improper H.O.N.K wreckage" - icon_state = "honker-broken" - desc = "All is right in the universe." - parts = list( - /obj/item/mecha_parts/chassis/honker, - /obj/item/mecha_parts/part/honker_torso, - /obj/item/mecha_parts/part/honker_head, - /obj/item/mecha_parts/part/honker_left_arm, - /obj/item/mecha_parts/part/honker_right_arm, - /obj/item/mecha_parts/part/honker_left_leg, - /obj/item/mecha_parts/part/honker_right_leg) - -/obj/structure/mecha_wreckage/durand - name = "\improper Durand wreckage" - icon_state = "durand-broken" - parts = list( - /obj/item/mecha_parts/part/durand_torso, - /obj/item/mecha_parts/part/durand_head, - /obj/item/mecha_parts/part/durand_left_arm, - /obj/item/mecha_parts/part/durand_right_arm, - /obj/item/mecha_parts/part/durand_left_leg, - /obj/item/mecha_parts/part/durand_right_leg) - -/obj/structure/mecha_wreckage/phazon - name = "\improper Phazon wreckage" - icon_state = "phazon-broken" - - -/obj/structure/mecha_wreckage/odysseus - name = "\improper Odysseus wreckage" - icon_state = "odysseus-broken" - parts = list( - /obj/item/mecha_parts/part/odysseus_torso, - /obj/item/mecha_parts/part/odysseus_head, - /obj/item/mecha_parts/part/odysseus_left_arm, - /obj/item/mecha_parts/part/odysseus_right_arm, - /obj/item/mecha_parts/part/odysseus_left_leg, - /obj/item/mecha_parts/part/odysseus_right_leg) +/////////////////////////////////// +//////// Mecha wreckage //////// +/////////////////////////////////// + + +/obj/structure/mecha_wreckage + name = "exosuit wreckage" + desc = "Remains of some unfortunate mecha. Completely irreparable, but perhaps something can be salvaged." + icon = 'icons/mecha/mecha.dmi' + density = TRUE + anchored = FALSE + opacity = 0 + var/list/welder_salvage = list(/obj/item/stack/sheet/plasteel, /obj/item/stack/sheet/metal, /obj/item/stack/rods) + var/salvage_num = 5 + var/list/crowbar_salvage = list() + var/wires_removed = FALSE + var/mob/living/silicon/ai/AI //AIs to be salvaged + var/list/parts + +/obj/structure/mecha_wreckage/Initialize(mapload, mob/living/silicon/ai/AI_pilot) + . = ..() + if(parts) + for(var/i in 1 to 2) + if(!parts.len) + break + if(prob(60)) + continue + var/part = pick(parts) + welder_salvage += part + parts = null + if(!AI_pilot) //Type-checking for this is already done in mecha/Destroy() + return + AI = AI_pilot + AI.apply_damage(150, BURN) //Give the AI a bit of damage from the "shock" of being suddenly shut down + AI.death() //The damage is not enough to kill the AI, but to be 'corrupted files' in need of repair. + AI.forceMove(src) //Put the dead AI inside the wreckage for recovery + add_overlay(mutable_appearance('icons/obj/projectiles.dmi', "green_laser")) //Overlay for the recovery beacon + AI.controlled_mech = null + AI.remote_control = null + +/obj/structure/mecha_wreckage/Destroy() + if(AI) + QDEL_NULL(AI) + QDEL_LIST(crowbar_salvage) + return ..() + +/obj/structure/mecha_wreckage/examine(mob/user) + . = ..() + if(!AI) + return + . += "The AI recovery beacon is active." + +/obj/structure/mecha_wreckage/welder_act(mob/living/user, obj/item/I) + ..() + . = TRUE + if(salvage_num <= 0 || !length(welder_salvage)) + to_chat(user, "You don't see anything that can be cut with [I]!") + return + if(!I.use_tool(src, user, 0, volume=50)) + return + if(prob(30)) + to_chat(user, "You fail to salvage anything valuable from [src]!") + return + var/type = pick(welder_salvage) + var/N = new type(get_turf(user)) + user.visible_message("[user] cuts [N] from [src].", "You cut [N] from [src].") + if(!istype(N, /obj/item/stack)) + welder_salvage -= type + salvage_num-- + +/obj/structure/mecha_wreckage/wirecutter_act(mob/living/user, obj/item/I) + ..() + . = TRUE + if(wires_removed) + to_chat(user, "You don't see anything that can be cut with [I]!") + return + var/N = new /obj/item/stack/cable_coil(get_turf(user), rand(1,3)) + user.visible_message("[user] cuts [N] from [src].", "You cut [N] from [src].") + wires_removed = TRUE + +/obj/structure/mecha_wreckage/crowbar_act(mob/living/user, obj/item/I) + ..() + . = TRUE + if(crowbar_salvage.len) + var/obj/S = pick(crowbar_salvage) + S.forceMove(user.drop_location()) + user.visible_message("[user] pries [S] from [src].", "You pry [S] from [src].") + crowbar_salvage -= S + return + to_chat(user, "You don't see anything that can be cut with [I]!") + +/obj/structure/mecha_wreckage/transfer_ai(interaction, mob/user, null, obj/item/aicard/card) + if(!..()) + return + + //Proc called on the wreck by the AI card. + if(interaction != AI_TRANS_TO_CARD) //AIs can only be transferred in one direction, from the wreck to the card. + return + if(!AI) //No AI in the wreck + to_chat(user, "No AI backups found.") + return + cut_overlays() //Remove the recovery beacon overlay + AI.forceMove(card) //Move the dead AI to the card. + card.AI = AI + if(AI.client) //AI player is still in the dead AI and is connected + to_chat(AI, "The remains of your file system have been recovered on a mobile storage device.") + else //Give the AI a heads-up that it is probably going to get fixed. + AI.notify_ghost_cloning("You have been recovered from the wreckage!", source = card) + to_chat(user, "Backup files recovered: [AI.name] ([rand(1000,9999)].exe) salvaged from [name] and stored within local memory.") + AI = null + +/obj/structure/mecha_wreckage/gygax + name = "\improper Gygax wreckage" + icon_state = "gygax-broken" + parts = list( + /obj/item/mecha_parts/part/gygax_torso, + /obj/item/mecha_parts/part/gygax_head, + /obj/item/mecha_parts/part/gygax_left_arm, + /obj/item/mecha_parts/part/gygax_right_arm, + /obj/item/mecha_parts/part/gygax_left_leg, + /obj/item/mecha_parts/part/gygax_right_leg + ) + +/obj/structure/mecha_wreckage/gygax/dark + name = "\improper Dark Gygax wreckage" + icon_state = "darkgygax-broken" + +/obj/structure/mecha_wreckage/marauder + name = "\improper Marauder wreckage" + icon_state = "marauder-broken" + +/obj/structure/mecha_wreckage/mauler + name = "\improper Mauler wreckage" + icon_state = "mauler-broken" + desc = "The syndicate won't be very happy about this..." + +/obj/structure/mecha_wreckage/seraph + name = "\improper Seraph wreckage" + icon_state = "seraph-broken" + +/obj/structure/mecha_wreckage/reticence + name = "\improper Reticence wreckage" + icon_state = "reticence-broken" + color = "#87878715" + desc = "..." + +/obj/structure/mecha_wreckage/ripley + name = "\improper Ripley wreckage" + icon_state = "ripley-broken" + parts = list(/obj/item/mecha_parts/part/ripley_torso, + /obj/item/mecha_parts/part/ripley_left_arm, + /obj/item/mecha_parts/part/ripley_right_arm, + /obj/item/mecha_parts/part/ripley_left_leg, + /obj/item/mecha_parts/part/ripley_right_leg) + +/obj/structure/mecha_wreckage/ripley/mkii + name = "\improper Ripley MK-II wreckage" + icon_state = "ripleymkii-broken" + +/obj/structure/mecha_wreckage/ripley/firefighter + name = "\improper Firefighter wreckage" + icon_state = "firefighter-broken" + parts = list(/obj/item/mecha_parts/part/ripley_torso, + /obj/item/mecha_parts/part/ripley_left_arm, + /obj/item/mecha_parts/part/ripley_right_arm, + /obj/item/mecha_parts/part/ripley_left_leg, + /obj/item/mecha_parts/part/ripley_right_leg, + /obj/item/clothing/suit/fire) + +/obj/structure/mecha_wreckage/ripley/deathripley + name = "\improper Death-Ripley wreckage" + icon_state = "deathripley-broken" + parts = null + +/obj/structure/mecha_wreckage/honker + name = "\improper H.O.N.K wreckage" + icon_state = "honker-broken" + desc = "All is right in the universe." + parts = list( + /obj/item/mecha_parts/chassis/honker, + /obj/item/mecha_parts/part/honker_torso, + /obj/item/mecha_parts/part/honker_head, + /obj/item/mecha_parts/part/honker_left_arm, + /obj/item/mecha_parts/part/honker_right_arm, + /obj/item/mecha_parts/part/honker_left_leg, + /obj/item/mecha_parts/part/honker_right_leg) + +/obj/structure/mecha_wreckage/durand + name = "\improper Durand wreckage" + icon_state = "durand-broken" + parts = list( + /obj/item/mecha_parts/part/durand_torso, + /obj/item/mecha_parts/part/durand_head, + /obj/item/mecha_parts/part/durand_left_arm, + /obj/item/mecha_parts/part/durand_right_arm, + /obj/item/mecha_parts/part/durand_left_leg, + /obj/item/mecha_parts/part/durand_right_leg) + +/obj/structure/mecha_wreckage/phazon + name = "\improper Phazon wreckage" + icon_state = "phazon-broken" + + +/obj/structure/mecha_wreckage/odysseus + name = "\improper Odysseus wreckage" + icon_state = "odysseus-broken" + parts = list( + /obj/item/mecha_parts/part/odysseus_torso, + /obj/item/mecha_parts/part/odysseus_head, + /obj/item/mecha_parts/part/odysseus_left_arm, + /obj/item/mecha_parts/part/odysseus_right_arm, + /obj/item/mecha_parts/part/odysseus_left_leg, + /obj/item/mecha_parts/part/odysseus_right_leg) diff --git a/code/game/mecha/medical/medical.dm b/code/game/mecha/medical/medical.dm index bbb845075729..1541ff1b7cd3 100644 --- a/code/game/mecha/medical/medical.dm +++ b/code/game/mecha/medical/medical.dm @@ -1,7 +1,7 @@ -/obj/mecha/medical - internals_req_access = list(ACCESS_MECH_SCIENCE, ACCESS_MECH_MEDICAL) - -/obj/mecha/medical/mechturn(direction) - . = ..() - if(!strafe && !occupant.client.keys_held["Alt"]) - mechstep(direction) //agile mechs get to move and turn in the same step +/obj/mecha/medical + internals_req_access = list(ACCESS_MECH_SCIENCE, ACCESS_MECH_MEDICAL) + +/obj/mecha/medical/mechturn(direction) + . = ..() + if(!strafe && !occupant.client.keys_held["Alt"]) + mechstep(direction) //agile mechs get to move and turn in the same step diff --git a/code/game/mecha/medical/odysseus.dm b/code/game/mecha/medical/odysseus.dm index 015b3514676d..27e139c84ab6 100644 --- a/code/game/mecha/medical/odysseus.dm +++ b/code/game/mecha/medical/odysseus.dm @@ -1,31 +1,31 @@ -/obj/mecha/medical/odysseus - desc = "These exosuits are developed and produced by Vey-Med. (© All rights reserved)." - name = "\improper Odysseus" - icon_state = "odysseus" - step_in = 2 - max_temperature = 15000 - max_integrity = 120 - wreckage = /obj/structure/mecha_wreckage/odysseus - internal_damage_threshold = 35 - deflect_chance = 15 - step_energy_drain = 6 - -/obj/mecha/medical/odysseus/moved_inside(mob/living/carbon/human/H) - . = ..() - if(.) - var/datum/atom_hud/hud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] - hud.add_hud_to(H) - -/obj/mecha/medical/odysseus/go_out() - if(isliving(occupant)) - var/mob/living/L = occupant - var/datum/atom_hud/hud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] - hud.remove_hud_from(L) - ..() - -/obj/mecha/medical/odysseus/mmi_moved_inside(obj/item/mmi/M, mob/user) - . = ..() - if(.) - var/datum/atom_hud/hud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] - var/mob/living/brain/B = M.brainmob - hud.add_hud_to(B) +/obj/mecha/medical/odysseus + desc = "These exosuits are developed and produced by Vey-Med. (© All rights reserved)." + name = "\improper Odysseus" + icon_state = "odysseus" + step_in = 2 + max_temperature = 15000 + max_integrity = 120 + wreckage = /obj/structure/mecha_wreckage/odysseus + internal_damage_threshold = 35 + deflect_chance = 15 + step_energy_drain = 6 + +/obj/mecha/medical/odysseus/moved_inside(mob/living/carbon/human/H) + . = ..() + if(.) + var/datum/atom_hud/hud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] + hud.add_hud_to(H) + +/obj/mecha/medical/odysseus/go_out() + if(isliving(occupant)) + var/mob/living/L = occupant + var/datum/atom_hud/hud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] + hud.remove_hud_from(L) + ..() + +/obj/mecha/medical/odysseus/mmi_moved_inside(obj/item/mmi/M, mob/user) + . = ..() + if(.) + var/datum/atom_hud/hud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] + var/mob/living/brain/B = M.brainmob + hud.add_hud_to(B) diff --git a/code/game/mecha/working/ripley.dm b/code/game/mecha/working/ripley.dm index b9512af99c81..9ea4f18faf90 100644 --- a/code/game/mecha/working/ripley.dm +++ b/code/game/mecha/working/ripley.dm @@ -1,222 +1,222 @@ -/obj/mecha/working/ripley - desc = "Autonomous Power Loader Unit MK-I. Designed primarily around heavy lifting, the Ripley can be outfitted with utility equipment to fill a number of roles." - name = "\improper APLU MK-I \"Ripley\"" - icon_state = "ripley" - silicon_icon_state = "ripley-empty" - step_in = 1.5 //Move speed, lower is faster. - var/fast_pressure_step_in = 1.5 //step_in while in low pressure conditions - var/slow_pressure_step_in = 2.0 //step_in while in normal pressure conditions - max_temperature = 20000 - max_integrity = 200 - lights_power = 7 - deflect_chance = 15 - armor = list("melee" = 40, "bullet" = 20, "laser" = 10, "energy" = 20, "bomb" = 40, "bio" = 0, "rad" = 20, "fire" = 100, "acid" = 100) - max_equip = 6 - wreckage = /obj/structure/mecha_wreckage/ripley - internals_req_access = list(ACCESS_MECH_ENGINE, ACCESS_MECH_SCIENCE, ACCESS_MECH_MINING) - var/list/cargo = new - var/cargo_capacity = 15 - var/hides = 0 - enclosed = FALSE //Normal ripley has an open cockpit design - enter_delay = 10 //can enter in a quarter of the time of other mechs - exit_delay = 10 - opacity = FALSE //Ripley has a window - -/obj/mecha/working/ripley/Move() - . = ..() - if(.) - collect_ore() - update_pressure() - -/obj/mecha/working/ripley/proc/collect_ore() - if(locate(/obj/item/mecha_parts/mecha_equipment/hydraulic_clamp) in equipment) - var/obj/structure/ore_box/ore_box = locate(/obj/structure/ore_box) in cargo - if(ore_box) - for(var/obj/item/stack/ore/ore in range(1, src)) - if(ore.Adjacent(src) && ((get_dir(src, ore) & dir) || ore.loc == loc)) //we can reach it and it's in front of us? grab it! - ore.forceMove(ore_box) - -/obj/mecha/working/ripley/Destroy() - for(var/atom/movable/A in cargo) - A.forceMove(drop_location()) - step_rand(A) - cargo.Cut() - return ..() - -/obj/mecha/working/ripley/go_out() - ..() - update_icon() - -/obj/mecha/working/ripley/moved_inside(mob/living/carbon/human/H) - ..() - update_icon() - -/obj/mecha/working/ripley/check_for_internal_damage(list/possible_int_damage,ignore_threshold=null) - if (!enclosed) - possible_int_damage -= (MECHA_INT_TEMP_CONTROL + MECHA_INT_TANK_BREACH) //if we don't even have an air tank, these two doesn't make a ton of sense. - . = ..() - - -/obj/mecha/working/ripley/Initialize() - . = ..() - AddComponent(/datum/component/armor_plate,3,/obj/item/stack/sheet/animalhide/goliath_hide,list("melee" = 10, "bullet" = 5, "laser" = 5)) - - -/obj/mecha/working/ripley/mkii - desc = "Autonomous Power Loader Unit MK-II. This prototype Ripley is refitted with a pressurized cabin, trading its prior speed for atmospheric protection" - name = "\improper APLU MK-II \"Ripley\"" - icon_state = "ripleymkii" - fast_pressure_step_in = 2 //step_in while in low pressure conditions - slow_pressure_step_in = 4 //step_in while in normal pressure conditions - step_in = 4 - armor = list("melee" = 40, "bullet" = 20, "laser" = 10, "energy" = 20, "bomb" = 40, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - wreckage = /obj/structure/mecha_wreckage/ripley/mkii - enclosed = TRUE - enter_delay = 40 - silicon_icon_state = null - opacity = TRUE - -/obj/mecha/working/ripley/firefighter - desc = "Autonomous Power Loader Unit MK-III. This model is refitted with a pressurized cabin and additional thermal protection." - name = "\improper APLU MK-III \"Firefighter\"" - icon_state = "firefighter" - max_temperature = 65000 - max_integrity = 250 - fast_pressure_step_in = 2 //step_in while in low pressure conditions - slow_pressure_step_in = 4 //step_in while in normal pressure conditions - step_in = 4 - resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF - lights_power = 7 - armor = list("melee" = 40, "bullet" = 30, "laser" = 30, "energy" = 30, "bomb" = 60, "bio" = 0, "rad" = 70, "fire" = 100, "acid" = 100) - max_equip = 5 // More armor, less tools - wreckage = /obj/structure/mecha_wreckage/ripley/firefighter - enclosed = TRUE - enter_delay = 40 - silicon_icon_state = null - opacity = TRUE - - -/obj/mecha/working/ripley/deathripley - desc = "OH SHIT IT'S THE DEATHSQUAD WE'RE ALL GONNA DIE" - name = "\improper DEATH-RIPLEY" - icon_state = "deathripley" - fast_pressure_step_in = 2 //step_in while in low pressure conditions - slow_pressure_step_in = 3 //step_in while in normal pressure conditions - step_in = 4 - lights_power = 7 - wreckage = /obj/structure/mecha_wreckage/ripley/deathripley - step_energy_drain = 0 - enclosed = TRUE - enter_delay = 40 - silicon_icon_state = null - opacity = TRUE - -/obj/mecha/working/ripley/deathripley/Initialize() - . = ..() - var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/hydraulic_clamp/kill - ME.attach(src) - -/obj/mecha/working/ripley/deathripley/real - desc = "OH SHIT IT'S THE DEATHSQUAD WE'RE ALL GONNA DIE. FOR REAL" - -/obj/mecha/working/ripley/deathripley/real/Initialize() - . = ..() - for(var/obj/item/mecha_parts/mecha_equipment/E in equipment) - E.detach() - qdel(E) - equipment.Cut() - var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/hydraulic_clamp/kill/real - ME.attach(src) - -/obj/mecha/working/ripley/mining - desc = "An old, dusty mining Ripley." - name = "\improper APLU \"Miner\"" - obj_integrity = 75 //Low starting health - -/obj/mecha/working/ripley/mining/Initialize() - . = ..() - if(cell) - cell.charge = FLOOR(cell.charge * 0.25, 1) //Starts at very low charge - if(prob(70)) //Maybe add a drill - if(prob(15)) //Possible diamond drill... Feeling lucky? - var/obj/item/mecha_parts/mecha_equipment/drill/diamonddrill/D = new - D.attach(src) - else - var/obj/item/mecha_parts/mecha_equipment/drill/D = new - D.attach(src) - - else //Add plasma cutter if no drill - var/obj/item/mecha_parts/mecha_equipment/weapon/energy/plasma/P = new - P.attach(src) - - //Add ore box to cargo - cargo.Add(new /obj/structure/ore_box(src)) - - //Attach hydraulic clamp - var/obj/item/mecha_parts/mecha_equipment/hydraulic_clamp/HC = new - HC.attach(src) - for(var/obj/item/mecha_parts/mecha_tracking/B in trackers)//Deletes the beacon so it can't be found easily - qdel(B) - - var/obj/item/mecha_parts/mecha_equipment/mining_scanner/scanner = new - scanner.attach(src) - -/obj/mecha/working/ripley/Exit(atom/movable/O) - if(O in cargo) - return 0 - return ..() - -/obj/mecha/working/ripley/Topic(href, href_list) - ..() - if(href_list["drop_from_cargo"]) - var/obj/O = locate(href_list["drop_from_cargo"]) in cargo - if(O) - occupant_message("You unload [O].") - O.forceMove(drop_location()) - cargo -= O - log_message("Unloaded [O]. Cargo compartment capacity: [cargo_capacity - src.cargo.len]", LOG_MECHA) - return - - -/obj/mecha/working/ripley/contents_explosion(severity, target) - for(var/X in cargo) - var/obj/O = X - if(prob(30/severity)) - cargo -= O - O.forceMove(drop_location()) - . = ..() - -/obj/mecha/working/ripley/get_stats_part() - var/output = ..() - output += "Cargo Compartment Contents:
                    " - if(cargo.len) - for(var/obj/O in cargo) - output += "Unload : [O]
                    " - else - output += "Nothing" - output += "
                    " - return output - -/obj/mecha/working/ripley/proc/update_pressure() - var/turf/T = get_turf(loc) - - if(lavaland_equipment_pressure_check(T)) - step_in = fast_pressure_step_in - for(var/obj/item/mecha_parts/mecha_equipment/drill/drill in equipment) - drill.equip_cooldown = initial(drill.equip_cooldown)/2 - else - step_in = slow_pressure_step_in - for(var/obj/item/mecha_parts/mecha_equipment/drill/drill in equipment) - drill.equip_cooldown = initial(drill.equip_cooldown) - -/obj/mecha/working/ripley/relay_container_resist(mob/living/user, obj/O) - to_chat(user, "You lean on the back of [O] and start pushing so it falls out of [src].") - if(do_after(user, 300, target = O)) - if(!user || user.stat != CONSCIOUS || user.loc != src || O.loc != src ) - return - to_chat(user, "You successfully pushed [O] out of [src]!") - O.forceMove(drop_location()) - cargo -= O - else - if(user.loc == src) //so we don't get the message if we resisted multiple times and succeeded. - to_chat(user, "You fail to push [O] out of [src]!") +/obj/mecha/working/ripley + desc = "Autonomous Power Loader Unit MK-I. Designed primarily around heavy lifting, the Ripley can be outfitted with utility equipment to fill a number of roles." + name = "\improper APLU MK-I \"Ripley\"" + icon_state = "ripley" + silicon_icon_state = "ripley-empty" + step_in = 1.5 //Move speed, lower is faster. + var/fast_pressure_step_in = 1.5 //step_in while in low pressure conditions + var/slow_pressure_step_in = 2.0 //step_in while in normal pressure conditions + max_temperature = 20000 + max_integrity = 200 + lights_power = 7 + deflect_chance = 15 + armor = list("melee" = 40, "bullet" = 20, "laser" = 10, "energy" = 20, "bomb" = 40, "bio" = 0, "rad" = 20, "fire" = 100, "acid" = 100) + max_equip = 6 + wreckage = /obj/structure/mecha_wreckage/ripley + internals_req_access = list(ACCESS_MECH_ENGINE, ACCESS_MECH_SCIENCE, ACCESS_MECH_MINING) + var/list/cargo = new + var/cargo_capacity = 15 + var/hides = 0 + enclosed = FALSE //Normal ripley has an open cockpit design + enter_delay = 10 //can enter in a quarter of the time of other mechs + exit_delay = 10 + opacity = FALSE //Ripley has a window + +/obj/mecha/working/ripley/Move() + . = ..() + if(.) + collect_ore() + update_pressure() + +/obj/mecha/working/ripley/proc/collect_ore() + if(locate(/obj/item/mecha_parts/mecha_equipment/hydraulic_clamp) in equipment) + var/obj/structure/ore_box/ore_box = locate(/obj/structure/ore_box) in cargo + if(ore_box) + for(var/obj/item/stack/ore/ore in range(1, src)) + if(ore.Adjacent(src) && ((get_dir(src, ore) & dir) || ore.loc == loc)) //we can reach it and it's in front of us? grab it! + ore.forceMove(ore_box) + +/obj/mecha/working/ripley/Destroy() + for(var/atom/movable/A in cargo) + A.forceMove(drop_location()) + step_rand(A) + cargo.Cut() + return ..() + +/obj/mecha/working/ripley/go_out() + ..() + update_icon() + +/obj/mecha/working/ripley/moved_inside(mob/living/carbon/human/H) + ..() + update_icon() + +/obj/mecha/working/ripley/check_for_internal_damage(list/possible_int_damage,ignore_threshold=null) + if (!enclosed) + possible_int_damage -= (MECHA_INT_TEMP_CONTROL + MECHA_INT_TANK_BREACH) //if we don't even have an air tank, these two doesn't make a ton of sense. + . = ..() + + +/obj/mecha/working/ripley/Initialize() + . = ..() + AddComponent(/datum/component/armor_plate,3,/obj/item/stack/sheet/animalhide/goliath_hide,list("melee" = 10, "bullet" = 5, "laser" = 5)) + + +/obj/mecha/working/ripley/mkii + desc = "Autonomous Power Loader Unit MK-II. This prototype Ripley is refitted with a pressurized cabin, trading its prior speed for atmospheric protection" + name = "\improper APLU MK-II \"Ripley\"" + icon_state = "ripleymkii" + fast_pressure_step_in = 2 //step_in while in low pressure conditions + slow_pressure_step_in = 4 //step_in while in normal pressure conditions + step_in = 4 + armor = list("melee" = 40, "bullet" = 20, "laser" = 10, "energy" = 20, "bomb" = 40, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + wreckage = /obj/structure/mecha_wreckage/ripley/mkii + enclosed = TRUE + enter_delay = 40 + silicon_icon_state = null + opacity = TRUE + +/obj/mecha/working/ripley/firefighter + desc = "Autonomous Power Loader Unit MK-III. This model is refitted with a pressurized cabin and additional thermal protection." + name = "\improper APLU MK-III \"Firefighter\"" + icon_state = "firefighter" + max_temperature = 65000 + max_integrity = 250 + fast_pressure_step_in = 2 //step_in while in low pressure conditions + slow_pressure_step_in = 4 //step_in while in normal pressure conditions + step_in = 4 + resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF + lights_power = 7 + armor = list("melee" = 40, "bullet" = 30, "laser" = 30, "energy" = 30, "bomb" = 60, "bio" = 0, "rad" = 70, "fire" = 100, "acid" = 100) + max_equip = 5 // More armor, less tools + wreckage = /obj/structure/mecha_wreckage/ripley/firefighter + enclosed = TRUE + enter_delay = 40 + silicon_icon_state = null + opacity = TRUE + + +/obj/mecha/working/ripley/deathripley + desc = "OH SHIT IT'S THE DEATHSQUAD WE'RE ALL GONNA DIE" + name = "\improper DEATH-RIPLEY" + icon_state = "deathripley" + fast_pressure_step_in = 2 //step_in while in low pressure conditions + slow_pressure_step_in = 3 //step_in while in normal pressure conditions + step_in = 4 + lights_power = 7 + wreckage = /obj/structure/mecha_wreckage/ripley/deathripley + step_energy_drain = 0 + enclosed = TRUE + enter_delay = 40 + silicon_icon_state = null + opacity = TRUE + +/obj/mecha/working/ripley/deathripley/Initialize() + . = ..() + var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/hydraulic_clamp/kill + ME.attach(src) + +/obj/mecha/working/ripley/deathripley/real + desc = "OH SHIT IT'S THE DEATHSQUAD WE'RE ALL GONNA DIE. FOR REAL" + +/obj/mecha/working/ripley/deathripley/real/Initialize() + . = ..() + for(var/obj/item/mecha_parts/mecha_equipment/E in equipment) + E.detach() + qdel(E) + equipment.Cut() + var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/hydraulic_clamp/kill/real + ME.attach(src) + +/obj/mecha/working/ripley/mining + desc = "An old, dusty mining Ripley." + name = "\improper APLU \"Miner\"" + obj_integrity = 75 //Low starting health + +/obj/mecha/working/ripley/mining/Initialize() + . = ..() + if(cell) + cell.charge = FLOOR(cell.charge * 0.25, 1) //Starts at very low charge + if(prob(70)) //Maybe add a drill + if(prob(15)) //Possible diamond drill... Feeling lucky? + var/obj/item/mecha_parts/mecha_equipment/drill/diamonddrill/D = new + D.attach(src) + else + var/obj/item/mecha_parts/mecha_equipment/drill/D = new + D.attach(src) + + else //Add plasma cutter if no drill + var/obj/item/mecha_parts/mecha_equipment/weapon/energy/plasma/P = new + P.attach(src) + + //Add ore box to cargo + cargo.Add(new /obj/structure/ore_box(src)) + + //Attach hydraulic clamp + var/obj/item/mecha_parts/mecha_equipment/hydraulic_clamp/HC = new + HC.attach(src) + for(var/obj/item/mecha_parts/mecha_tracking/B in trackers)//Deletes the beacon so it can't be found easily + qdel(B) + + var/obj/item/mecha_parts/mecha_equipment/mining_scanner/scanner = new + scanner.attach(src) + +/obj/mecha/working/ripley/Exit(atom/movable/O) + if(O in cargo) + return 0 + return ..() + +/obj/mecha/working/ripley/Topic(href, href_list) + ..() + if(href_list["drop_from_cargo"]) + var/obj/O = locate(href_list["drop_from_cargo"]) in cargo + if(O) + occupant_message("You unload [O].") + O.forceMove(drop_location()) + cargo -= O + log_message("Unloaded [O]. Cargo compartment capacity: [cargo_capacity - src.cargo.len]", LOG_MECHA) + return + + +/obj/mecha/working/ripley/contents_explosion(severity, target) + for(var/X in cargo) + var/obj/O = X + if(prob(30/severity)) + cargo -= O + O.forceMove(drop_location()) + . = ..() + +/obj/mecha/working/ripley/get_stats_part() + var/output = ..() + output += "Cargo Compartment Contents:
                    " + if(cargo.len) + for(var/obj/O in cargo) + output += "Unload : [O]
                    " + else + output += "Nothing" + output += "
                    " + return output + +/obj/mecha/working/ripley/proc/update_pressure() + var/turf/T = get_turf(loc) + + if(lavaland_equipment_pressure_check(T)) + step_in = fast_pressure_step_in + for(var/obj/item/mecha_parts/mecha_equipment/drill/drill in equipment) + drill.equip_cooldown = initial(drill.equip_cooldown)/2 + else + step_in = slow_pressure_step_in + for(var/obj/item/mecha_parts/mecha_equipment/drill/drill in equipment) + drill.equip_cooldown = initial(drill.equip_cooldown) + +/obj/mecha/working/ripley/relay_container_resist(mob/living/user, obj/O) + to_chat(user, "You lean on the back of [O] and start pushing so it falls out of [src].") + if(do_after(user, 300, target = O)) + if(!user || user.stat != CONSCIOUS || user.loc != src || O.loc != src ) + return + to_chat(user, "You successfully pushed [O] out of [src]!") + O.forceMove(drop_location()) + cargo -= O + else + if(user.loc == src) //so we don't get the message if we resisted multiple times and succeeded. + to_chat(user, "You fail to push [O] out of [src]!") diff --git a/code/game/mecha/working/working.dm b/code/game/mecha/working/working.dm index b3e9c4ba55e2..3e45148ef51e 100644 --- a/code/game/mecha/working/working.dm +++ b/code/game/mecha/working/working.dm @@ -1,2 +1,2 @@ -/obj/mecha/working - internal_damage_threshold = 60 +/obj/mecha/working + internal_damage_threshold = 60 diff --git a/code/game/objects/effects/bump_teleporter.dm b/code/game/objects/effects/bump_teleporter.dm index 23b186c0094d..0337b076ff7e 100644 --- a/code/game/objects/effects/bump_teleporter.dm +++ b/code/game/objects/effects/bump_teleporter.dm @@ -1,37 +1,37 @@ -/obj/effect/bump_teleporter - name = "bump-teleporter" - icon = 'icons/mob/screen_gen.dmi' - icon_state = "x2" - var/id = null //id of this bump_teleporter. - var/id_target = null //id of bump_teleporter which this moves you to. - invisibility = INVISIBILITY_ABSTRACT //nope, can't see this - anchored = TRUE - density = TRUE - opacity = 0 - - var/static/list/AllTeleporters - -/obj/effect/bump_teleporter/Initialize() - . = ..() - LAZYADD(AllTeleporters, src) - -/obj/effect/bump_teleporter/Destroy() - LAZYREMOVE(AllTeleporters, src) - return ..() - - -/obj/effect/bump_teleporter/singularity_act() - return - -/obj/effect/bump_teleporter/singularity_pull() - return - -/obj/effect/bump_teleporter/Bumped(atom/movable/AM) - if(!ismob(AM)) - return - if(!id_target) - return - - for(var/obj/effect/bump_teleporter/BT in AllTeleporters) - if(BT.id == src.id_target) - AM.forceMove(BT.loc) //Teleport to location with correct id. +/obj/effect/bump_teleporter + name = "bump-teleporter" + icon = 'icons/mob/screen_gen.dmi' + icon_state = "x2" + var/id = null //id of this bump_teleporter. + var/id_target = null //id of bump_teleporter which this moves you to. + invisibility = INVISIBILITY_ABSTRACT //nope, can't see this + anchored = TRUE + density = TRUE + opacity = 0 + + var/static/list/AllTeleporters + +/obj/effect/bump_teleporter/Initialize() + . = ..() + LAZYADD(AllTeleporters, src) + +/obj/effect/bump_teleporter/Destroy() + LAZYREMOVE(AllTeleporters, src) + return ..() + + +/obj/effect/bump_teleporter/singularity_act() + return + +/obj/effect/bump_teleporter/singularity_pull() + return + +/obj/effect/bump_teleporter/Bumped(atom/movable/AM) + if(!ismob(AM)) + return + if(!id_target) + return + + for(var/obj/effect/bump_teleporter/BT in AllTeleporters) + if(BT.id == src.id_target) + AM.forceMove(BT.loc) //Teleport to location with correct id. diff --git a/code/game/objects/effects/decals/cleanable.dm b/code/game/objects/effects/decals/cleanable.dm index c6f8422c6cf5..47ac77ccc9e7 100644 --- a/code/game/objects/effects/decals/cleanable.dm +++ b/code/game/objects/effects/decals/cleanable.dm @@ -1,111 +1,111 @@ -/obj/effect/decal/cleanable - gender = PLURAL - layer = ABOVE_NORMAL_TURF_LAYER - var/list/random_icon_states = null - var/blood_state = "" //I'm sorry but cleanable/blood code is ass, and so is blood_DNA - var/bloodiness = 0 //0-100, amount of blood in this decal, used for making footprints and affecting the alpha of bloody footprints - var/mergeable_decal = TRUE //when two of these are on a same tile or do we need to merge them into just one? - var/beauty = 0 - -/obj/effect/decal/cleanable/Initialize(mapload, list/datum/disease/diseases) - . = ..() - if (random_icon_states && (icon_state == initial(icon_state)) && length(random_icon_states) > 0) - icon_state = pick(random_icon_states) - create_reagents(300) - if(loc && isturf(loc)) - for(var/obj/effect/decal/cleanable/C in loc) - if(C != src && C.type == type && !QDELETED(C)) - if (replace_decal(C)) - return INITIALIZE_HINT_QDEL - - if(LAZYLEN(diseases)) - var/list/datum/disease/diseases_to_add = list() - for(var/datum/disease/D in diseases) - if(D.spread_flags & DISEASE_SPREAD_CONTACT_FLUIDS) - diseases_to_add += D - if(LAZYLEN(diseases_to_add)) - AddComponent(/datum/component/infective, diseases_to_add) - - addtimer(CALLBACK(src, /datum.proc/_AddComponent, list(/datum/component/beauty, beauty)), 0) - - var/turf/T = get_turf(src) - if(T && is_station_level(T.z)) - SSblackbox.record_feedback("tally", "station_mess_created", 1, name) - -/obj/effect/decal/cleanable/Destroy() - var/turf/T = get_turf(src) - if(T && is_station_level(T.z)) - SSblackbox.record_feedback("tally", "station_mess_destroyed", 1, name) - return ..() - -/obj/effect/decal/cleanable/proc/replace_decal(obj/effect/decal/cleanable/C) // Returns true if we should give up in favor of the pre-existing decal - if(mergeable_decal) - return TRUE - -/obj/effect/decal/cleanable/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/reagent_containers/glass) || istype(W, /obj/item/reagent_containers/food/drinks)) - if(src.reagents && W.reagents) - . = 1 //so the containers don't splash their content on the src while scooping. - if(!src.reagents.total_volume) - to_chat(user, "[src] isn't thick enough to scoop up!") - return - if(W.reagents.total_volume >= W.reagents.maximum_volume) - to_chat(user, "[W] is full!") - return - to_chat(user, "You scoop up [src] into [W]!") - reagents.trans_to(W, reagents.total_volume, transfered_by = user) - if(!reagents.total_volume) //scooped up all of it - qdel(src) - return - if(W.get_temperature()) //todo: make heating a reagent holder proc - if(istype(W, /obj/item/clothing/mask/cigarette)) - return - else - var/hotness = W.get_temperature() - reagents.expose_temperature(hotness) - to_chat(user, "You heat [name] with [W]!") - else - return ..() - -/obj/effect/decal/cleanable/ex_act() - if(reagents) - for(var/datum/reagent/R in reagents.reagent_list) - R.on_ex_act() - ..() - -/obj/effect/decal/cleanable/fire_act(exposed_temperature, exposed_volume) - if(reagents) - reagents.expose_temperature(exposed_temperature) - ..() - - -//Add "bloodiness" of this blood's type, to the human's shoes -//This is on /cleanable because fuck this ancient mess -/obj/effect/decal/cleanable/Crossed(atom/movable/O) - ..() - if(ishuman(O)) - var/mob/living/carbon/human/H = O - if(H.shoes && blood_state && bloodiness) - var/obj/item/clothing/shoes/S = H.shoes - if(!S.can_be_bloody) - return - var/add_blood = 0 - if(bloodiness >= BLOOD_GAIN_PER_STEP) - add_blood = BLOOD_GAIN_PER_STEP - else - add_blood = bloodiness - bloodiness -= add_blood - S.bloody_shoes[blood_state] = min(MAX_SHOE_BLOODINESS,S.bloody_shoes[blood_state]+add_blood) - S.add_blood_DNA(return_blood_DNA()) - S.blood_state = blood_state - update_icon() - H.update_inv_shoes() -/atom/effect/decal/cleanable/washed(atom/washer) - . = ..() - qdel(src) - -/obj/effect/decal/cleanable/proc/can_bloodcrawl_in() - if((blood_state != BLOOD_STATE_OIL) && (blood_state != BLOOD_STATE_NOT_BLOODY)) - return bloodiness - else - return 0 +/obj/effect/decal/cleanable + gender = PLURAL + layer = ABOVE_NORMAL_TURF_LAYER + var/list/random_icon_states = null + var/blood_state = "" //I'm sorry but cleanable/blood code is ass, and so is blood_DNA + var/bloodiness = 0 //0-100, amount of blood in this decal, used for making footprints and affecting the alpha of bloody footprints + var/mergeable_decal = TRUE //when two of these are on a same tile or do we need to merge them into just one? + var/beauty = 0 + +/obj/effect/decal/cleanable/Initialize(mapload, list/datum/disease/diseases) + . = ..() + if (random_icon_states && (icon_state == initial(icon_state)) && length(random_icon_states) > 0) + icon_state = pick(random_icon_states) + create_reagents(300) + if(loc && isturf(loc)) + for(var/obj/effect/decal/cleanable/C in loc) + if(C != src && C.type == type && !QDELETED(C)) + if (replace_decal(C)) + return INITIALIZE_HINT_QDEL + + if(LAZYLEN(diseases)) + var/list/datum/disease/diseases_to_add = list() + for(var/datum/disease/D in diseases) + if(D.spread_flags & DISEASE_SPREAD_CONTACT_FLUIDS) + diseases_to_add += D + if(LAZYLEN(diseases_to_add)) + AddComponent(/datum/component/infective, diseases_to_add) + + addtimer(CALLBACK(src, /datum.proc/_AddComponent, list(/datum/component/beauty, beauty)), 0) + + var/turf/T = get_turf(src) + if(T && is_station_level(T.z)) + SSblackbox.record_feedback("tally", "station_mess_created", 1, name) + +/obj/effect/decal/cleanable/Destroy() + var/turf/T = get_turf(src) + if(T && is_station_level(T.z)) + SSblackbox.record_feedback("tally", "station_mess_destroyed", 1, name) + return ..() + +/obj/effect/decal/cleanable/proc/replace_decal(obj/effect/decal/cleanable/C) // Returns true if we should give up in favor of the pre-existing decal + if(mergeable_decal) + return TRUE + +/obj/effect/decal/cleanable/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/reagent_containers/glass) || istype(W, /obj/item/reagent_containers/food/drinks)) + if(src.reagents && W.reagents) + . = 1 //so the containers don't splash their content on the src while scooping. + if(!src.reagents.total_volume) + to_chat(user, "[src] isn't thick enough to scoop up!") + return + if(W.reagents.total_volume >= W.reagents.maximum_volume) + to_chat(user, "[W] is full!") + return + to_chat(user, "You scoop up [src] into [W]!") + reagents.trans_to(W, reagents.total_volume, transfered_by = user) + if(!reagents.total_volume) //scooped up all of it + qdel(src) + return + if(W.get_temperature()) //todo: make heating a reagent holder proc + if(istype(W, /obj/item/clothing/mask/cigarette)) + return + else + var/hotness = W.get_temperature() + reagents.expose_temperature(hotness) + to_chat(user, "You heat [name] with [W]!") + else + return ..() + +/obj/effect/decal/cleanable/ex_act() + if(reagents) + for(var/datum/reagent/R in reagents.reagent_list) + R.on_ex_act() + ..() + +/obj/effect/decal/cleanable/fire_act(exposed_temperature, exposed_volume) + if(reagents) + reagents.expose_temperature(exposed_temperature) + ..() + + +//Add "bloodiness" of this blood's type, to the human's shoes +//This is on /cleanable because fuck this ancient mess +/obj/effect/decal/cleanable/Crossed(atom/movable/O) + ..() + if(ishuman(O)) + var/mob/living/carbon/human/H = O + if(H.shoes && blood_state && bloodiness) + var/obj/item/clothing/shoes/S = H.shoes + if(!S.can_be_bloody) + return + var/add_blood = 0 + if(bloodiness >= BLOOD_GAIN_PER_STEP) + add_blood = BLOOD_GAIN_PER_STEP + else + add_blood = bloodiness + bloodiness -= add_blood + S.bloody_shoes[blood_state] = min(MAX_SHOE_BLOODINESS,S.bloody_shoes[blood_state]+add_blood) + S.add_blood_DNA(return_blood_DNA()) + S.blood_state = blood_state + update_icon() + H.update_inv_shoes() +/atom/effect/decal/cleanable/washed(atom/washer) + . = ..() + qdel(src) + +/obj/effect/decal/cleanable/proc/can_bloodcrawl_in() + if((blood_state != BLOOD_STATE_OIL) && (blood_state != BLOOD_STATE_NOT_BLOODY)) + return bloodiness + else + return 0 diff --git a/code/game/objects/effects/decals/cleanable/aliens.dm b/code/game/objects/effects/decals/cleanable/aliens.dm index 3e36332aa872..6c3a455eddec 100644 --- a/code/game/objects/effects/decals/cleanable/aliens.dm +++ b/code/game/objects/effects/decals/cleanable/aliens.dm @@ -1,80 +1,80 @@ -// Note: BYOND is object oriented. There is no reason for this to be copy/pasted blood code. - -/obj/effect/decal/cleanable/xenoblood - name = "xeno blood" - desc = "It's green and acidic. It looks like... blood?" - icon = 'icons/effects/blood.dmi' - icon_state = "xfloor1" - random_icon_states = list("xfloor1", "xfloor2", "xfloor3", "xfloor4", "xfloor5", "xfloor6", "xfloor7") - bloodiness = BLOOD_AMOUNT_PER_DECAL - blood_state = BLOOD_STATE_XENO - beauty = -250 - -/obj/effect/decal/cleanable/xenoblood/Initialize() - . = ..() - add_blood_DNA(list("UNKNOWN DNA" = "X*")) - -/obj/effect/decal/cleanable/xenoblood/xsplatter - random_icon_states = list("xgibbl1", "xgibbl2", "xgibbl3", "xgibbl4", "xgibbl5") - -/obj/effect/decal/cleanable/xenoblood/xgibs - name = "xeno gibs" - desc = "Gnarly..." - icon = 'icons/effects/blood.dmi' - icon_state = "xgib1" - layer = LOW_OBJ_LAYER - random_icon_states = list("xgib1", "xgib2", "xgib3", "xgib4", "xgib5", "xgib6") - mergeable_decal = FALSE - -/obj/effect/decal/cleanable/xenoblood/xgibs/proc/streak(list/directions) - set waitfor = 0 - var/direction = pick(directions) - for(var/i = 0, i < pick(1, 200; 2, 150; 3, 50), i++) - sleep(2) - if(i > 0) - new /obj/effect/decal/cleanable/xenoblood/xsplatter(loc) - if(!step_to(src, get_step(src, direction), 0)) - break - -/obj/effect/decal/cleanable/xenoblood/xgibs/ex_act() - return - -/obj/effect/decal/cleanable/xenoblood/xgibs/up - icon_state = "xgibup1" - random_icon_states = list("xgib1", "xgib2", "xgib3", "xgib4", "xgib5", "xgib6","xgibup1","xgibup1","xgibup1") - -/obj/effect/decal/cleanable/xenoblood/xgibs/down - icon_state = "xgibdown1" - random_icon_states = list("xgib1", "xgib2", "xgib3", "xgib4", "xgib5", "xgib6","xgibdown1","xgibdown1","xgibdown1") - -/obj/effect/decal/cleanable/xenoblood/xgibs/body - icon_state = "xgibtorso" - random_icon_states = list("xgibhead", "xgibtorso") - -/obj/effect/decal/cleanable/xenoblood/xgibs/torso - icon_state = "xgibtorso" - random_icon_states = list("xgibtorso") - -/obj/effect/decal/cleanable/xenoblood/xgibs/limb - icon_state = "xgibleg" - random_icon_states = list("xgibleg", "xgibarm") - -/obj/effect/decal/cleanable/xenoblood/xgibs/core - icon_state = "xgibmid1" - random_icon_states = list("xgibmid1", "xgibmid2", "xgibmid3") - -/obj/effect/decal/cleanable/xenoblood/xgibs/larva - icon_state = "xgiblarva1" - random_icon_states = list("xgiblarva1", "xgiblarva2") - -/obj/effect/decal/cleanable/xenoblood/xgibs/larva/body - icon_state = "xgiblarvatorso" - random_icon_states = list("xgiblarvahead", "xgiblarvatorso") - -/obj/effect/decal/cleanable/blood/xtracks - icon_state = "xtracks" - random_icon_states = null - -/obj/effect/decal/cleanable/blood/xtracks/Initialize() - . = ..() - add_blood_DNA(list("Unknown DNA" = "X*")) +// Note: BYOND is object oriented. There is no reason for this to be copy/pasted blood code. + +/obj/effect/decal/cleanable/xenoblood + name = "xeno blood" + desc = "It's green and acidic. It looks like... blood?" + icon = 'icons/effects/blood.dmi' + icon_state = "xfloor1" + random_icon_states = list("xfloor1", "xfloor2", "xfloor3", "xfloor4", "xfloor5", "xfloor6", "xfloor7") + bloodiness = BLOOD_AMOUNT_PER_DECAL + blood_state = BLOOD_STATE_XENO + beauty = -250 + +/obj/effect/decal/cleanable/xenoblood/Initialize() + . = ..() + add_blood_DNA(list("UNKNOWN DNA" = "X*")) + +/obj/effect/decal/cleanable/xenoblood/xsplatter + random_icon_states = list("xgibbl1", "xgibbl2", "xgibbl3", "xgibbl4", "xgibbl5") + +/obj/effect/decal/cleanable/xenoblood/xgibs + name = "xeno gibs" + desc = "Gnarly..." + icon = 'icons/effects/blood.dmi' + icon_state = "xgib1" + layer = LOW_OBJ_LAYER + random_icon_states = list("xgib1", "xgib2", "xgib3", "xgib4", "xgib5", "xgib6") + mergeable_decal = FALSE + +/obj/effect/decal/cleanable/xenoblood/xgibs/proc/streak(list/directions) + set waitfor = 0 + var/direction = pick(directions) + for(var/i = 0, i < pick(1, 200; 2, 150; 3, 50), i++) + sleep(2) + if(i > 0) + new /obj/effect/decal/cleanable/xenoblood/xsplatter(loc) + if(!step_to(src, get_step(src, direction), 0)) + break + +/obj/effect/decal/cleanable/xenoblood/xgibs/ex_act() + return + +/obj/effect/decal/cleanable/xenoblood/xgibs/up + icon_state = "xgibup1" + random_icon_states = list("xgib1", "xgib2", "xgib3", "xgib4", "xgib5", "xgib6","xgibup1","xgibup1","xgibup1") + +/obj/effect/decal/cleanable/xenoblood/xgibs/down + icon_state = "xgibdown1" + random_icon_states = list("xgib1", "xgib2", "xgib3", "xgib4", "xgib5", "xgib6","xgibdown1","xgibdown1","xgibdown1") + +/obj/effect/decal/cleanable/xenoblood/xgibs/body + icon_state = "xgibtorso" + random_icon_states = list("xgibhead", "xgibtorso") + +/obj/effect/decal/cleanable/xenoblood/xgibs/torso + icon_state = "xgibtorso" + random_icon_states = list("xgibtorso") + +/obj/effect/decal/cleanable/xenoblood/xgibs/limb + icon_state = "xgibleg" + random_icon_states = list("xgibleg", "xgibarm") + +/obj/effect/decal/cleanable/xenoblood/xgibs/core + icon_state = "xgibmid1" + random_icon_states = list("xgibmid1", "xgibmid2", "xgibmid3") + +/obj/effect/decal/cleanable/xenoblood/xgibs/larva + icon_state = "xgiblarva1" + random_icon_states = list("xgiblarva1", "xgiblarva2") + +/obj/effect/decal/cleanable/xenoblood/xgibs/larva/body + icon_state = "xgiblarvatorso" + random_icon_states = list("xgiblarvahead", "xgiblarvatorso") + +/obj/effect/decal/cleanable/blood/xtracks + icon_state = "xtracks" + random_icon_states = null + +/obj/effect/decal/cleanable/blood/xtracks/Initialize() + . = ..() + add_blood_DNA(list("Unknown DNA" = "X*")) diff --git a/code/game/objects/effects/decals/cleanable/humans.dm b/code/game/objects/effects/decals/cleanable/humans.dm index edafa39828ab..95e17007b983 100644 --- a/code/game/objects/effects/decals/cleanable/humans.dm +++ b/code/game/objects/effects/decals/cleanable/humans.dm @@ -1,239 +1,239 @@ -/obj/effect/decal/cleanable/blood - name = "blood" - desc = "It's red and gooey. Perhaps it's the chef's cooking?" - icon = 'icons/effects/blood.dmi' - icon_state = "floor1" - random_icon_states = list("floor1", "floor2", "floor3", "floor4", "floor5", "floor6", "floor7") - blood_state = BLOOD_STATE_HUMAN - bloodiness = BLOOD_AMOUNT_PER_DECAL - beauty = -100 - -/obj/effect/decal/cleanable/blood/replace_decal(obj/effect/decal/cleanable/blood/C) - C.add_blood_DNA(return_blood_DNA()) - if (bloodiness) - C.bloodiness = min((C.bloodiness + bloodiness),MAX_SHOE_BLOODINESS) - return ..() - -/obj/effect/decal/cleanable/blood/old - name = "dried blood" - desc = "Looks like it's been here a while. Eew." - bloodiness = 0 - icon_state = "floor1-old" - -/obj/effect/decal/cleanable/blood/old/Initialize(mapload, list/datum/disease/diseases) - add_blood_DNA(list("Non-human DNA" = random_blood_type())) // Needs to happen before ..() - . = ..() - icon_state = "[icon_state]-old" //change from the normal blood icon selected from random_icon_states in the parent's Initialize to the old dried up blood. - -/obj/effect/decal/cleanable/blood/splatter - icon_state = "gibbl1" - random_icon_states = list("gibbl1", "gibbl2", "gibbl3", "gibbl4", "gibbl5") - -/obj/effect/decal/cleanable/blood/tracks - icon_state = "tracks" - desc = "They look like tracks left by wheels." - random_icon_states = null - beauty = -50 - -/obj/effect/decal/cleanable/trail_holder //not a child of blood on purpose - name = "blood" - icon = 'icons/effects/blood.dmi' - desc = "Your instincts say you shouldn't be following these." - beauty = -50 - var/list/existing_dirs = list() - -/obj/effect/decal/cleanable/trail_holder/can_bloodcrawl_in() - return TRUE - -/obj/effect/decal/cleanable/blood/gibs - name = "gibs" - desc = "They look bloody and gruesome." - icon = 'icons/effects/blood.dmi' - icon_state = "gib1" - layer = LOW_OBJ_LAYER - random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6") - mergeable_decal = FALSE - - var/already_rotting = FALSE - -/obj/effect/decal/cleanable/blood/gibs/Initialize(mapload, list/datum/disease/diseases) - . = ..() - reagents.add_reagent(/datum/reagent/liquidgibs, 5) - if(already_rotting) - start_rotting(rename=FALSE) - else - addtimer(CALLBACK(src, .proc/start_rotting), 2 MINUTES) - -/obj/effect/decal/cleanable/blood/gibs/proc/start_rotting(rename=TRUE) - if(rename) - name = "rotting [initial(name)]" - desc += " They smell terrible." - AddComponent(/datum/component/rot/gibs) - -/obj/effect/decal/cleanable/blood/gibs/ex_act(severity, target) - return - -/obj/effect/decal/cleanable/blood/gibs/Crossed(atom/movable/L) - if(isliving(L) && has_gravity(loc)) - playsound(loc, 'sound/effects/gib_step.ogg', HAS_TRAIT(L, TRAIT_LIGHT_STEP) ? 20 : 50, TRUE) - . = ..() - -/obj/effect/decal/cleanable/blood/gibs/proc/streak(list/directions) - set waitfor = FALSE - var/list/diseases = list() - SEND_SIGNAL(src, COMSIG_GIBS_STREAK, directions, diseases) - var/direction = pick(directions) - for(var/i in 0 to pick(0, 200; 1, 150; 2, 50)) - sleep(2) - if(i > 0) - new /obj/effect/decal/cleanable/blood/splatter(loc, diseases) - if(!step_to(src, get_step(src, direction), 0)) - break - -/obj/effect/decal/cleanable/blood/gibs/up - icon_state = "gibup1" - random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6","gibup1","gibup1","gibup1") - -/obj/effect/decal/cleanable/blood/gibs/down - icon_state = "gibdown1" - random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6","gibdown1","gibdown1","gibdown1") - -/obj/effect/decal/cleanable/blood/gibs/body - icon_state = "gibtorso" - random_icon_states = list("gibhead", "gibtorso") - -/obj/effect/decal/cleanable/blood/gibs/torso - icon_state = "gibtorso" - random_icon_states = null - -/obj/effect/decal/cleanable/blood/gibs/limb - icon_state = "gibleg" - random_icon_states = list("gibleg", "gibarm") - -/obj/effect/decal/cleanable/blood/gibs/core - icon_state = "gibmid1" - random_icon_states = list("gibmid1", "gibmid2", "gibmid3") - -/obj/effect/decal/cleanable/blood/gibs/old - name = "old rotting gibs" - desc = "Space Jesus, why didn't anyone clean this up? They smell terrible." - bloodiness = 0 - already_rotting = TRUE - -/obj/effect/decal/cleanable/blood/gibs/old/Initialize(mapload, list/datum/disease/diseases) - . = ..() - setDir(pick(1,2,4,8)) - icon_state += "-old" - add_blood_DNA(list("Non-human DNA" = random_blood_type())) - -/obj/effect/decal/cleanable/blood/drip - name = "drips of blood" - desc = "It's red." - icon_state = "drip5" //using drip5 since the others tend to blend in with pipes & wires. - random_icon_states = list("drip1","drip2","drip3","drip4","drip5") - bloodiness = 0 - var/drips = 1 - -/obj/effect/decal/cleanable/blood/drip/can_bloodcrawl_in() - return TRUE - - -//BLOODY FOOTPRINTS -/obj/effect/decal/cleanable/blood/footprints - name = "footprints" - desc = "WHOSE FOOTPRINTS ARE THESE?" - icon = 'icons/effects/footprints.dmi' - icon_state = "blood1" - random_icon_states = null - blood_state = BLOOD_STATE_HUMAN //the icon state to load images from - var/entered_dirs = 0 - var/exited_dirs = 0 - var/list/shoe_types = list() - -/obj/effect/decal/cleanable/blood/footprints/Initialize(mapload) - . = ..() - icon_state = "" //All of the footprint visuals come from overlays - if(mapload) - entered_dirs |= dir //Keep the same appearance as in the map editor - update_icon() - -//Rotate all of the footprint directions too -/obj/effect/decal/cleanable/blood/footprints/setDir(newdir) - if(dir == newdir) - return ..() - - var/ang_change = dir2angle(newdir) - dir2angle(dir) - var/old_entered_dirs = entered_dirs - var/old_exited_dirs = exited_dirs - entered_dirs = 0 - exited_dirs = 0 - - for(var/Ddir in GLOB.cardinals) - if(old_entered_dirs & Ddir) - entered_dirs |= angle2dir_cardinal(dir2angle(Ddir) + ang_change) - if(old_exited_dirs & Ddir) - exited_dirs |= angle2dir_cardinal(dir2angle(Ddir) + ang_change) - - update_icon() - return ..() - -/obj/effect/decal/cleanable/blood/footprints/Crossed(atom/movable/O) - ..() - if(ishuman(O)) - var/mob/living/carbon/human/H = O - var/obj/item/clothing/shoes/S = H.shoes - if(S && S.bloody_shoes[blood_state]) - S.bloody_shoes[blood_state] = max(S.bloody_shoes[blood_state] - BLOOD_LOSS_PER_STEP, 0) - shoe_types |= S.type - if (!(entered_dirs & H.dir)) - entered_dirs |= H.dir - update_icon() - -/obj/effect/decal/cleanable/blood/footprints/Uncrossed(atom/movable/O) - ..() - if(ishuman(O)) - var/mob/living/carbon/human/H = O - var/obj/item/clothing/shoes/S = H.shoes - if(S && S.bloody_shoes[blood_state]) - S.bloody_shoes[blood_state] = max(S.bloody_shoes[blood_state] - BLOOD_LOSS_PER_STEP, 0) - shoe_types |= S.type - if (!(exited_dirs & H.dir)) - exited_dirs |= H.dir - update_icon() - - -/obj/effect/decal/cleanable/blood/footprints/update_icon() - cut_overlays() - - for(var/Ddir in GLOB.cardinals) - if(entered_dirs & Ddir) - var/image/bloodstep_overlay = GLOB.bloody_footprints_cache["entered-[blood_state]-[Ddir]"] - if(!bloodstep_overlay) - GLOB.bloody_footprints_cache["entered-[blood_state]-[Ddir]"] = bloodstep_overlay = image(icon, "[blood_state]1", dir = Ddir) - add_overlay(bloodstep_overlay) - if(exited_dirs & Ddir) - var/image/bloodstep_overlay = GLOB.bloody_footprints_cache["exited-[blood_state]-[Ddir]"] - if(!bloodstep_overlay) - GLOB.bloody_footprints_cache["exited-[blood_state]-[Ddir]"] = bloodstep_overlay = image(icon, "[blood_state]2", dir = Ddir) - add_overlay(bloodstep_overlay) - - alpha = BLOODY_FOOTPRINT_BASE_ALPHA+bloodiness - - -/obj/effect/decal/cleanable/blood/footprints/examine(mob/user) - . = ..() - if(shoe_types.len) - . += "You recognise the footprints as belonging to:\n" - for(var/shoe in shoe_types) - var/obj/item/clothing/shoes/S = shoe - . += "[icon2html(initial(S.icon), user)] Some [initial(S.name)].\n" - -/obj/effect/decal/cleanable/blood/footprints/replace_decal(obj/effect/decal/cleanable/C) - if(blood_state != C.blood_state) //We only replace footprints of the same type as us - return - ..() - -/obj/effect/decal/cleanable/blood/footprints/can_bloodcrawl_in() - if((blood_state != BLOOD_STATE_OIL) && (blood_state != BLOOD_STATE_NOT_BLOODY)) - return 1 - return 0 +/obj/effect/decal/cleanable/blood + name = "blood" + desc = "It's red and gooey. Perhaps it's the chef's cooking?" + icon = 'icons/effects/blood.dmi' + icon_state = "floor1" + random_icon_states = list("floor1", "floor2", "floor3", "floor4", "floor5", "floor6", "floor7") + blood_state = BLOOD_STATE_HUMAN + bloodiness = BLOOD_AMOUNT_PER_DECAL + beauty = -100 + +/obj/effect/decal/cleanable/blood/replace_decal(obj/effect/decal/cleanable/blood/C) + C.add_blood_DNA(return_blood_DNA()) + if (bloodiness) + C.bloodiness = min((C.bloodiness + bloodiness),MAX_SHOE_BLOODINESS) + return ..() + +/obj/effect/decal/cleanable/blood/old + name = "dried blood" + desc = "Looks like it's been here a while. Eew." + bloodiness = 0 + icon_state = "floor1-old" + +/obj/effect/decal/cleanable/blood/old/Initialize(mapload, list/datum/disease/diseases) + add_blood_DNA(list("Non-human DNA" = random_blood_type())) // Needs to happen before ..() + . = ..() + icon_state = "[icon_state]-old" //change from the normal blood icon selected from random_icon_states in the parent's Initialize to the old dried up blood. + +/obj/effect/decal/cleanable/blood/splatter + icon_state = "gibbl1" + random_icon_states = list("gibbl1", "gibbl2", "gibbl3", "gibbl4", "gibbl5") + +/obj/effect/decal/cleanable/blood/tracks + icon_state = "tracks" + desc = "They look like tracks left by wheels." + random_icon_states = null + beauty = -50 + +/obj/effect/decal/cleanable/trail_holder //not a child of blood on purpose + name = "blood" + icon = 'icons/effects/blood.dmi' + desc = "Your instincts say you shouldn't be following these." + beauty = -50 + var/list/existing_dirs = list() + +/obj/effect/decal/cleanable/trail_holder/can_bloodcrawl_in() + return TRUE + +/obj/effect/decal/cleanable/blood/gibs + name = "gibs" + desc = "They look bloody and gruesome." + icon = 'icons/effects/blood.dmi' + icon_state = "gib1" + layer = LOW_OBJ_LAYER + random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6") + mergeable_decal = FALSE + + var/already_rotting = FALSE + +/obj/effect/decal/cleanable/blood/gibs/Initialize(mapload, list/datum/disease/diseases) + . = ..() + reagents.add_reagent(/datum/reagent/liquidgibs, 5) + if(already_rotting) + start_rotting(rename=FALSE) + else + addtimer(CALLBACK(src, .proc/start_rotting), 2 MINUTES) + +/obj/effect/decal/cleanable/blood/gibs/proc/start_rotting(rename=TRUE) + if(rename) + name = "rotting [initial(name)]" + desc += " They smell terrible." + AddComponent(/datum/component/rot/gibs) + +/obj/effect/decal/cleanable/blood/gibs/ex_act(severity, target) + return + +/obj/effect/decal/cleanable/blood/gibs/Crossed(atom/movable/L) + if(isliving(L) && has_gravity(loc)) + playsound(loc, 'sound/effects/gib_step.ogg', HAS_TRAIT(L, TRAIT_LIGHT_STEP) ? 20 : 50, TRUE) + . = ..() + +/obj/effect/decal/cleanable/blood/gibs/proc/streak(list/directions) + set waitfor = FALSE + var/list/diseases = list() + SEND_SIGNAL(src, COMSIG_GIBS_STREAK, directions, diseases) + var/direction = pick(directions) + for(var/i in 0 to pick(0, 200; 1, 150; 2, 50)) + sleep(2) + if(i > 0) + new /obj/effect/decal/cleanable/blood/splatter(loc, diseases) + if(!step_to(src, get_step(src, direction), 0)) + break + +/obj/effect/decal/cleanable/blood/gibs/up + icon_state = "gibup1" + random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6","gibup1","gibup1","gibup1") + +/obj/effect/decal/cleanable/blood/gibs/down + icon_state = "gibdown1" + random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6","gibdown1","gibdown1","gibdown1") + +/obj/effect/decal/cleanable/blood/gibs/body + icon_state = "gibtorso" + random_icon_states = list("gibhead", "gibtorso") + +/obj/effect/decal/cleanable/blood/gibs/torso + icon_state = "gibtorso" + random_icon_states = null + +/obj/effect/decal/cleanable/blood/gibs/limb + icon_state = "gibleg" + random_icon_states = list("gibleg", "gibarm") + +/obj/effect/decal/cleanable/blood/gibs/core + icon_state = "gibmid1" + random_icon_states = list("gibmid1", "gibmid2", "gibmid3") + +/obj/effect/decal/cleanable/blood/gibs/old + name = "old rotting gibs" + desc = "Space Jesus, why didn't anyone clean this up? They smell terrible." + bloodiness = 0 + already_rotting = TRUE + +/obj/effect/decal/cleanable/blood/gibs/old/Initialize(mapload, list/datum/disease/diseases) + . = ..() + setDir(pick(1,2,4,8)) + icon_state += "-old" + add_blood_DNA(list("Non-human DNA" = random_blood_type())) + +/obj/effect/decal/cleanable/blood/drip + name = "drips of blood" + desc = "It's red." + icon_state = "drip5" //using drip5 since the others tend to blend in with pipes & wires. + random_icon_states = list("drip1","drip2","drip3","drip4","drip5") + bloodiness = 0 + var/drips = 1 + +/obj/effect/decal/cleanable/blood/drip/can_bloodcrawl_in() + return TRUE + + +//BLOODY FOOTPRINTS +/obj/effect/decal/cleanable/blood/footprints + name = "footprints" + desc = "WHOSE FOOTPRINTS ARE THESE?" + icon = 'icons/effects/footprints.dmi' + icon_state = "blood1" + random_icon_states = null + blood_state = BLOOD_STATE_HUMAN //the icon state to load images from + var/entered_dirs = 0 + var/exited_dirs = 0 + var/list/shoe_types = list() + +/obj/effect/decal/cleanable/blood/footprints/Initialize(mapload) + . = ..() + icon_state = "" //All of the footprint visuals come from overlays + if(mapload) + entered_dirs |= dir //Keep the same appearance as in the map editor + update_icon() + +//Rotate all of the footprint directions too +/obj/effect/decal/cleanable/blood/footprints/setDir(newdir) + if(dir == newdir) + return ..() + + var/ang_change = dir2angle(newdir) - dir2angle(dir) + var/old_entered_dirs = entered_dirs + var/old_exited_dirs = exited_dirs + entered_dirs = 0 + exited_dirs = 0 + + for(var/Ddir in GLOB.cardinals) + if(old_entered_dirs & Ddir) + entered_dirs |= angle2dir_cardinal(dir2angle(Ddir) + ang_change) + if(old_exited_dirs & Ddir) + exited_dirs |= angle2dir_cardinal(dir2angle(Ddir) + ang_change) + + update_icon() + return ..() + +/obj/effect/decal/cleanable/blood/footprints/Crossed(atom/movable/O) + ..() + if(ishuman(O)) + var/mob/living/carbon/human/H = O + var/obj/item/clothing/shoes/S = H.shoes + if(S && S.bloody_shoes[blood_state]) + S.bloody_shoes[blood_state] = max(S.bloody_shoes[blood_state] - BLOOD_LOSS_PER_STEP, 0) + shoe_types |= S.type + if (!(entered_dirs & H.dir)) + entered_dirs |= H.dir + update_icon() + +/obj/effect/decal/cleanable/blood/footprints/Uncrossed(atom/movable/O) + ..() + if(ishuman(O)) + var/mob/living/carbon/human/H = O + var/obj/item/clothing/shoes/S = H.shoes + if(S && S.bloody_shoes[blood_state]) + S.bloody_shoes[blood_state] = max(S.bloody_shoes[blood_state] - BLOOD_LOSS_PER_STEP, 0) + shoe_types |= S.type + if (!(exited_dirs & H.dir)) + exited_dirs |= H.dir + update_icon() + + +/obj/effect/decal/cleanable/blood/footprints/update_icon() + cut_overlays() + + for(var/Ddir in GLOB.cardinals) + if(entered_dirs & Ddir) + var/image/bloodstep_overlay = GLOB.bloody_footprints_cache["entered-[blood_state]-[Ddir]"] + if(!bloodstep_overlay) + GLOB.bloody_footprints_cache["entered-[blood_state]-[Ddir]"] = bloodstep_overlay = image(icon, "[blood_state]1", dir = Ddir) + add_overlay(bloodstep_overlay) + if(exited_dirs & Ddir) + var/image/bloodstep_overlay = GLOB.bloody_footprints_cache["exited-[blood_state]-[Ddir]"] + if(!bloodstep_overlay) + GLOB.bloody_footprints_cache["exited-[blood_state]-[Ddir]"] = bloodstep_overlay = image(icon, "[blood_state]2", dir = Ddir) + add_overlay(bloodstep_overlay) + + alpha = BLOODY_FOOTPRINT_BASE_ALPHA+bloodiness + + +/obj/effect/decal/cleanable/blood/footprints/examine(mob/user) + . = ..() + if(shoe_types.len) + . += "You recognise the footprints as belonging to:\n" + for(var/shoe in shoe_types) + var/obj/item/clothing/shoes/S = shoe + . += "[icon2html(initial(S.icon), user)] Some [initial(S.name)].\n" + +/obj/effect/decal/cleanable/blood/footprints/replace_decal(obj/effect/decal/cleanable/C) + if(blood_state != C.blood_state) //We only replace footprints of the same type as us + return + ..() + +/obj/effect/decal/cleanable/blood/footprints/can_bloodcrawl_in() + if((blood_state != BLOOD_STATE_OIL) && (blood_state != BLOOD_STATE_NOT_BLOODY)) + return 1 + return 0 diff --git a/code/game/objects/effects/decals/cleanable/misc.dm b/code/game/objects/effects/decals/cleanable/misc.dm index 360204a665b4..5fa630a4305e 100644 --- a/code/game/objects/effects/decals/cleanable/misc.dm +++ b/code/game/objects/effects/decals/cleanable/misc.dm @@ -1,239 +1,239 @@ -/obj/effect/decal/cleanable/generic - name = "clutter" - desc = "Someone should clean that up." - icon = 'icons/obj/objects.dmi' - icon_state = "shards" - beauty = -50 - -/obj/effect/decal/cleanable/ash - name = "ashes" - desc = "Ashes to ashes, dust to dust, and into space." - icon = 'icons/obj/objects.dmi' - icon_state = "ash" - mergeable_decal = FALSE - beauty = -50 - -/obj/effect/decal/cleanable/ash/Initialize() - . = ..() - reagents.add_reagent(/datum/reagent/ash, 30) - pixel_x = rand(-5, 5) - pixel_y = rand(-5, 5) - -/obj/effect/decal/cleanable/ash/crematorium -//crematoriums need their own ash cause default ash deletes itself if created in an obj - turf_loc_check = FALSE - -/obj/effect/decal/cleanable/ash/large - name = "large pile of ashes" - icon_state = "big_ash" - beauty = -100 - -/obj/effect/decal/cleanable/ash/large/Initialize() - . = ..() - reagents.add_reagent(/datum/reagent/ash, 30) //double the amount of ash. - -/obj/effect/decal/cleanable/glass - name = "tiny shards" - desc = "Back to sand." - icon = 'icons/obj/shards.dmi' - icon_state = "tiny" - beauty = -100 - -/obj/effect/decal/cleanable/glass/Initialize() - . = ..() - setDir(pick(GLOB.cardinals)) - -/obj/effect/decal/cleanable/glass/ex_act() - qdel(src) - -/obj/effect/decal/cleanable/glass/plasma - icon_state = "plasmatiny" - -/obj/effect/decal/cleanable/dirt - name = "dirt" - desc = "Someone should clean that up." - icon_state = "dirt" - canSmoothWith = list(/obj/effect/decal/cleanable/dirt, /turf/closed/wall, /obj/structure/falsewall) - smooth = SMOOTH_FALSE - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - beauty = -75 - -/obj/effect/decal/cleanable/dirt/Initialize() - . = ..() - var/turf/T = get_turf(src) - if(T.tiled_dirt) - smooth = SMOOTH_MORE - icon = 'icons/effects/dirt.dmi' - icon_state = "" - queue_smooth(src) - queue_smooth_neighbors(src) - -/obj/effect/decal/cleanable/dirt/Destroy() - queue_smooth_neighbors(src) - return ..() - -/obj/effect/decal/cleanable/dirt/dust - name = "dust" - desc = "A thin layer of dust coating the floor." - -/obj/effect/decal/cleanable/greenglow - name = "glowing goo" - desc = "Jeez. I hope that's not for lunch." - icon_state = "greenglow" - light_power = 2 - light_range = 0.8 - light_color = "#22FFAA" - beauty = -300 - -/obj/effect/decal/cleanable/greenglow/ex_act() - return - -/obj/effect/decal/cleanable/greenglow/filled/Initialize() - . = ..() - reagents.add_reagent(pick(/datum/reagent/uranium, /datum/reagent/uranium/radium), 5) - -/obj/effect/decal/cleanable/greenglow/ecto - name = "ectoplasmic puddle" - desc = "You know who to call." - light_power = 2 - -/obj/effect/decal/cleanable/cobweb - name = "cobweb" - desc = "Somebody should remove that." - gender = NEUTER - layer = WALL_OBJ_LAYER - icon_state = "cobweb1" - resistance_flags = FLAMMABLE - beauty = -100 - -/obj/effect/decal/cleanable/cobweb/cobweb2 - icon_state = "cobweb2" - -/obj/effect/decal/cleanable/molten_object - name = "gooey grey mass" - desc = "It looks like a melted... something." - gender = NEUTER - icon = 'icons/effects/effects.dmi' - icon_state = "molten" - mergeable_decal = FALSE - beauty = -150 - -/obj/effect/decal/cleanable/molten_object/large - name = "big gooey grey mass" - icon_state = "big_molten" - beauty = -300 - -//Vomit (sorry) -/obj/effect/decal/cleanable/vomit - name = "vomit" - desc = "Gosh, how unpleasant." - icon = 'icons/effects/blood.dmi' - icon_state = "vomit_1" - random_icon_states = list("vomit_1", "vomit_2", "vomit_3", "vomit_4") - beauty = -150 - -/obj/effect/decal/cleanable/vomit/attack_hand(mob/user) - . = ..() - if(.) - return - if(ishuman(user)) - var/mob/living/carbon/human/H = user - if(isflyperson(H)) - playsound(get_turf(src), 'sound/items/drink.ogg', 50, TRUE) //slurp - H.visible_message("[H] extends a small proboscis into the vomit pool, sucking it with a slurping sound.") - if(reagents) - for(var/datum/reagent/R in reagents.reagent_list) - if (istype(R, /datum/reagent/consumable)) - var/datum/reagent/consumable/nutri_check = R - if(nutri_check.nutriment_factor >0) - H.adjust_nutrition(nutri_check.nutriment_factor * nutri_check.volume) - reagents.remove_reagent(nutri_check.type,nutri_check.volume) - reagents.trans_to(H, reagents.total_volume, transfered_by = user) - qdel(src) - -/obj/effect/decal/cleanable/vomit/old - name = "crusty dried vomit" - desc = "You try not to look at the chunks, and fail." - -/obj/effect/decal/cleanable/vomit/old/Initialize(mapload, list/datum/disease/diseases) - . = ..() - icon_state += "-old" - -/obj/effect/decal/cleanable/chem_pile - name = "chemical pile" - desc = "A pile of chemicals. You can't quite tell what's inside it." - gender = NEUTER - icon = 'icons/obj/objects.dmi' - icon_state = "ash" - -/obj/effect/decal/cleanable/shreds - name = "shreds" - desc = "The shredded remains of what appears to be clothing." - icon_state = "shreds" - gender = PLURAL - mergeable_decal = FALSE - -/obj/effect/decal/cleanable/shreds/ex_act(severity, target) - if(severity == 1) //so shreds created during an explosion aren't deleted by the explosion. - qdel(src) - -/obj/effect/decal/cleanable/shreds/Initialize(mapload, oldname) - pixel_x = rand(-10, 10) - pixel_y = rand(-10, 10) - if(!isnull(oldname)) - desc = "The sad remains of what used to be [oldname]" - . = ..() - -/obj/effect/decal/cleanable/glitter - name = "generic glitter pile" - desc = "The herpes of arts and crafts." - icon = 'icons/effects/atmospherics.dmi' - icon_state = "plasma_old" - gender = NEUTER - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - -/obj/effect/decal/cleanable/glitter/pink - name = "pink glitter" - icon_state = "plasma" - -/obj/effect/decal/cleanable/glitter/white - name = "white glitter" - icon_state = "nitrous_oxide" - -/obj/effect/decal/cleanable/glitter/blue - name = "blue glitter" - icon_state = "freon" - -/obj/effect/decal/cleanable/plasma - name = "stabilized plasma" - desc = "A puddle of stabilized plasma." - icon_state = "flour" - icon = 'icons/effects/tomatodecal.dmi' - color = "#2D2D2D" - -/obj/effect/decal/cleanable/insectguts - name = "insect guts" - desc = "One bug squashed. Four more will rise in its place." - icon = 'icons/effects/blood.dmi' - icon_state = "xfloor1" - random_icon_states = list("xfloor1", "xfloor2", "xfloor3", "xfloor4", "xfloor5", "xfloor6", "xfloor7") - -/obj/effect/decal/cleanable/confetti - name = "confetti" - desc = "Tiny bits of colored paper thrown about for the janitor to enjoy!" - icon = 'icons/effects/confetti_and_decor.dmi' - icon_state = "confetti" - mouse_opacity = MOUSE_OPACITY_TRANSPARENT //the confetti itself might be annoying enough - -/obj/effect/decal/cleanable/plastic - name = "plastic shreds" - desc = "Bits of torn, broken, worthless plastic." - icon = 'icons/obj/objects.dmi' - icon_state = "shards" - color = "#c6f4ff" - -/obj/effect/decal/cleanable/wrapping - name = "wrapping shreds" - desc = "Torn pieces of cardboard and paper, left over from a package." - icon = 'icons/obj/objects.dmi' - icon_state = "paper_shreds" +/obj/effect/decal/cleanable/generic + name = "clutter" + desc = "Someone should clean that up." + icon = 'icons/obj/objects.dmi' + icon_state = "shards" + beauty = -50 + +/obj/effect/decal/cleanable/ash + name = "ashes" + desc = "Ashes to ashes, dust to dust, and into space." + icon = 'icons/obj/objects.dmi' + icon_state = "ash" + mergeable_decal = FALSE + beauty = -50 + +/obj/effect/decal/cleanable/ash/Initialize() + . = ..() + reagents.add_reagent(/datum/reagent/ash, 30) + pixel_x = rand(-5, 5) + pixel_y = rand(-5, 5) + +/obj/effect/decal/cleanable/ash/crematorium +//crematoriums need their own ash cause default ash deletes itself if created in an obj + turf_loc_check = FALSE + +/obj/effect/decal/cleanable/ash/large + name = "large pile of ashes" + icon_state = "big_ash" + beauty = -100 + +/obj/effect/decal/cleanable/ash/large/Initialize() + . = ..() + reagents.add_reagent(/datum/reagent/ash, 30) //double the amount of ash. + +/obj/effect/decal/cleanable/glass + name = "tiny shards" + desc = "Back to sand." + icon = 'icons/obj/shards.dmi' + icon_state = "tiny" + beauty = -100 + +/obj/effect/decal/cleanable/glass/Initialize() + . = ..() + setDir(pick(GLOB.cardinals)) + +/obj/effect/decal/cleanable/glass/ex_act() + qdel(src) + +/obj/effect/decal/cleanable/glass/plasma + icon_state = "plasmatiny" + +/obj/effect/decal/cleanable/dirt + name = "dirt" + desc = "Someone should clean that up." + icon_state = "dirt" + canSmoothWith = list(/obj/effect/decal/cleanable/dirt, /turf/closed/wall, /obj/structure/falsewall) + smooth = SMOOTH_FALSE + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + beauty = -75 + +/obj/effect/decal/cleanable/dirt/Initialize() + . = ..() + var/turf/T = get_turf(src) + if(T.tiled_dirt) + smooth = SMOOTH_MORE + icon = 'icons/effects/dirt.dmi' + icon_state = "" + queue_smooth(src) + queue_smooth_neighbors(src) + +/obj/effect/decal/cleanable/dirt/Destroy() + queue_smooth_neighbors(src) + return ..() + +/obj/effect/decal/cleanable/dirt/dust + name = "dust" + desc = "A thin layer of dust coating the floor." + +/obj/effect/decal/cleanable/greenglow + name = "glowing goo" + desc = "Jeez. I hope that's not for lunch." + icon_state = "greenglow" + light_power = 2 + light_range = 0.8 + light_color = "#22FFAA" + beauty = -300 + +/obj/effect/decal/cleanable/greenglow/ex_act() + return + +/obj/effect/decal/cleanable/greenglow/filled/Initialize() + . = ..() + reagents.add_reagent(pick(/datum/reagent/uranium, /datum/reagent/uranium/radium), 5) + +/obj/effect/decal/cleanable/greenglow/ecto + name = "ectoplasmic puddle" + desc = "You know who to call." + light_power = 2 + +/obj/effect/decal/cleanable/cobweb + name = "cobweb" + desc = "Somebody should remove that." + gender = NEUTER + layer = WALL_OBJ_LAYER + icon_state = "cobweb1" + resistance_flags = FLAMMABLE + beauty = -100 + +/obj/effect/decal/cleanable/cobweb/cobweb2 + icon_state = "cobweb2" + +/obj/effect/decal/cleanable/molten_object + name = "gooey grey mass" + desc = "It looks like a melted... something." + gender = NEUTER + icon = 'icons/effects/effects.dmi' + icon_state = "molten" + mergeable_decal = FALSE + beauty = -150 + +/obj/effect/decal/cleanable/molten_object/large + name = "big gooey grey mass" + icon_state = "big_molten" + beauty = -300 + +//Vomit (sorry) +/obj/effect/decal/cleanable/vomit + name = "vomit" + desc = "Gosh, how unpleasant." + icon = 'icons/effects/blood.dmi' + icon_state = "vomit_1" + random_icon_states = list("vomit_1", "vomit_2", "vomit_3", "vomit_4") + beauty = -150 + +/obj/effect/decal/cleanable/vomit/attack_hand(mob/user) + . = ..() + if(.) + return + if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(isflyperson(H)) + playsound(get_turf(src), 'sound/items/drink.ogg', 50, TRUE) //slurp + H.visible_message("[H] extends a small proboscis into the vomit pool, sucking it with a slurping sound.") + if(reagents) + for(var/datum/reagent/R in reagents.reagent_list) + if (istype(R, /datum/reagent/consumable)) + var/datum/reagent/consumable/nutri_check = R + if(nutri_check.nutriment_factor >0) + H.adjust_nutrition(nutri_check.nutriment_factor * nutri_check.volume) + reagents.remove_reagent(nutri_check.type,nutri_check.volume) + reagents.trans_to(H, reagents.total_volume, transfered_by = user) + qdel(src) + +/obj/effect/decal/cleanable/vomit/old + name = "crusty dried vomit" + desc = "You try not to look at the chunks, and fail." + +/obj/effect/decal/cleanable/vomit/old/Initialize(mapload, list/datum/disease/diseases) + . = ..() + icon_state += "-old" + +/obj/effect/decal/cleanable/chem_pile + name = "chemical pile" + desc = "A pile of chemicals. You can't quite tell what's inside it." + gender = NEUTER + icon = 'icons/obj/objects.dmi' + icon_state = "ash" + +/obj/effect/decal/cleanable/shreds + name = "shreds" + desc = "The shredded remains of what appears to be clothing." + icon_state = "shreds" + gender = PLURAL + mergeable_decal = FALSE + +/obj/effect/decal/cleanable/shreds/ex_act(severity, target) + if(severity == 1) //so shreds created during an explosion aren't deleted by the explosion. + qdel(src) + +/obj/effect/decal/cleanable/shreds/Initialize(mapload, oldname) + pixel_x = rand(-10, 10) + pixel_y = rand(-10, 10) + if(!isnull(oldname)) + desc = "The sad remains of what used to be [oldname]" + . = ..() + +/obj/effect/decal/cleanable/glitter + name = "generic glitter pile" + desc = "The herpes of arts and crafts." + icon = 'icons/effects/atmospherics.dmi' + icon_state = "plasma_old" + gender = NEUTER + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + +/obj/effect/decal/cleanable/glitter/pink + name = "pink glitter" + icon_state = "plasma" + +/obj/effect/decal/cleanable/glitter/white + name = "white glitter" + icon_state = "nitrous_oxide" + +/obj/effect/decal/cleanable/glitter/blue + name = "blue glitter" + icon_state = "freon" + +/obj/effect/decal/cleanable/plasma + name = "stabilized plasma" + desc = "A puddle of stabilized plasma." + icon_state = "flour" + icon = 'icons/effects/tomatodecal.dmi' + color = "#2D2D2D" + +/obj/effect/decal/cleanable/insectguts + name = "insect guts" + desc = "One bug squashed. Four more will rise in its place." + icon = 'icons/effects/blood.dmi' + icon_state = "xfloor1" + random_icon_states = list("xfloor1", "xfloor2", "xfloor3", "xfloor4", "xfloor5", "xfloor6", "xfloor7") + +/obj/effect/decal/cleanable/confetti + name = "confetti" + desc = "Tiny bits of colored paper thrown about for the janitor to enjoy!" + icon = 'icons/effects/confetti_and_decor.dmi' + icon_state = "confetti" + mouse_opacity = MOUSE_OPACITY_TRANSPARENT //the confetti itself might be annoying enough + +/obj/effect/decal/cleanable/plastic + name = "plastic shreds" + desc = "Bits of torn, broken, worthless plastic." + icon = 'icons/obj/objects.dmi' + icon_state = "shards" + color = "#c6f4ff" + +/obj/effect/decal/cleanable/wrapping + name = "wrapping shreds" + desc = "Torn pieces of cardboard and paper, left over from a package." + icon = 'icons/obj/objects.dmi' + icon_state = "paper_shreds" diff --git a/code/game/objects/effects/decals/cleanable/robots.dm b/code/game/objects/effects/decals/cleanable/robots.dm index 937c620a4aeb..79059b51f351 100644 --- a/code/game/objects/effects/decals/cleanable/robots.dm +++ b/code/game/objects/effects/decals/cleanable/robots.dm @@ -1,83 +1,83 @@ -// Note: BYOND is object oriented. There is no reason for this to be copy/pasted blood code. - -/obj/effect/decal/cleanable/robot_debris - name = "robot debris" - desc = "It's a useless heap of junk... or is it?" - icon = 'icons/mob/robots.dmi' - icon_state = "gib1" - layer = LOW_OBJ_LAYER - random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6", "gib7") - blood_state = BLOOD_STATE_OIL - bloodiness = BLOOD_AMOUNT_PER_DECAL - mergeable_decal = FALSE - beauty = -50 - -/obj/effect/decal/cleanable/robot_debris/proc/streak(list/directions) - set waitfor = 0 - var/direction = pick(directions) - for (var/i = 0, i < pick(1, 200; 2, 150; 3, 50), i++) - sleep(2) - if (i > 0) - if (prob(40)) - new /obj/effect/decal/cleanable/oil/streak(src.loc) - else if (prob(10)) - var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread - s.set_up(3, 1, src) - s.start() - if (!step_to(src, get_step(src, direction), 0)) - break - -/obj/effect/decal/cleanable/robot_debris/ex_act() - return - -/obj/effect/decal/cleanable/robot_debris/limb - icon_state = "gibarm" - random_icon_states = list("gibarm", "gibleg") - -/obj/effect/decal/cleanable/robot_debris/up - icon_state = "gibup1" - random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6", "gib7","gibup1","gibup1") - -/obj/effect/decal/cleanable/robot_debris/down - icon_state = "gibdown1" - random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6", "gib7","gibdown1","gibdown1") - -/obj/effect/decal/cleanable/oil - name = "motor oil" - desc = "It's black and greasy. Looks like Beepsky made another mess." - icon = 'icons/mob/robots.dmi' - icon_state = "floor1" - random_icon_states = list("floor1", "floor2", "floor3", "floor4", "floor5", "floor6", "floor7") - blood_state = BLOOD_STATE_OIL - bloodiness = BLOOD_AMOUNT_PER_DECAL - beauty = -100 - -/obj/effect/decal/cleanable/oil/Initialize() - . = ..() - reagents.add_reagent(/datum/reagent/fuel/oil, 30) - -/obj/effect/decal/cleanable/oil/attackby(obj/item/I, mob/living/user) - var/attacked_by_hot_thing = I.get_temperature() - if(attacked_by_hot_thing) - visible_message("[user] tries to ignite [src] with [I]!", "You try to ignite [src] with [I].") - log_combat(user, src, (attacked_by_hot_thing < 480) ? "tried to ignite" : "ignited", I) - fire_act(attacked_by_hot_thing) - return - return ..() - -/obj/effect/decal/cleanable/oil/fire_act(exposed_temperature, exposed_volume) - if(exposed_temperature < 480) - return - visible_message("[src] catches fire!") - var/turf/T = get_turf(src) - qdel(src) - new /obj/effect/hotspot(T) - -/obj/effect/decal/cleanable/oil/streak - icon_state = "streak1" - random_icon_states = list("streak1", "streak2", "streak3", "streak4", "streak5") - beauty = -50 - -/obj/effect/decal/cleanable/oil/slippery/ComponentInitialize() - . = ..() - AddComponent(/datum/component/slippery, 80, (NO_SLIP_WHEN_WALKING | SLIDE)) +// Note: BYOND is object oriented. There is no reason for this to be copy/pasted blood code. + +/obj/effect/decal/cleanable/robot_debris + name = "robot debris" + desc = "It's a useless heap of junk... or is it?" + icon = 'icons/mob/robots.dmi' + icon_state = "gib1" + layer = LOW_OBJ_LAYER + random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6", "gib7") + blood_state = BLOOD_STATE_OIL + bloodiness = BLOOD_AMOUNT_PER_DECAL + mergeable_decal = FALSE + beauty = -50 + +/obj/effect/decal/cleanable/robot_debris/proc/streak(list/directions) + set waitfor = 0 + var/direction = pick(directions) + for (var/i = 0, i < pick(1, 200; 2, 150; 3, 50), i++) + sleep(2) + if (i > 0) + if (prob(40)) + new /obj/effect/decal/cleanable/oil/streak(src.loc) + else if (prob(10)) + var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread + s.set_up(3, 1, src) + s.start() + if (!step_to(src, get_step(src, direction), 0)) + break + +/obj/effect/decal/cleanable/robot_debris/ex_act() + return + +/obj/effect/decal/cleanable/robot_debris/limb + icon_state = "gibarm" + random_icon_states = list("gibarm", "gibleg") + +/obj/effect/decal/cleanable/robot_debris/up + icon_state = "gibup1" + random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6", "gib7","gibup1","gibup1") + +/obj/effect/decal/cleanable/robot_debris/down + icon_state = "gibdown1" + random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6", "gib7","gibdown1","gibdown1") + +/obj/effect/decal/cleanable/oil + name = "motor oil" + desc = "It's black and greasy. Looks like Beepsky made another mess." + icon = 'icons/mob/robots.dmi' + icon_state = "floor1" + random_icon_states = list("floor1", "floor2", "floor3", "floor4", "floor5", "floor6", "floor7") + blood_state = BLOOD_STATE_OIL + bloodiness = BLOOD_AMOUNT_PER_DECAL + beauty = -100 + +/obj/effect/decal/cleanable/oil/Initialize() + . = ..() + reagents.add_reagent(/datum/reagent/fuel/oil, 30) + +/obj/effect/decal/cleanable/oil/attackby(obj/item/I, mob/living/user) + var/attacked_by_hot_thing = I.get_temperature() + if(attacked_by_hot_thing) + visible_message("[user] tries to ignite [src] with [I]!", "You try to ignite [src] with [I].") + log_combat(user, src, (attacked_by_hot_thing < 480) ? "tried to ignite" : "ignited", I) + fire_act(attacked_by_hot_thing) + return + return ..() + +/obj/effect/decal/cleanable/oil/fire_act(exposed_temperature, exposed_volume) + if(exposed_temperature < 480) + return + visible_message("[src] catches fire!") + var/turf/T = get_turf(src) + qdel(src) + new /obj/effect/hotspot(T) + +/obj/effect/decal/cleanable/oil/streak + icon_state = "streak1" + random_icon_states = list("streak1", "streak2", "streak3", "streak4", "streak5") + beauty = -50 + +/obj/effect/decal/cleanable/oil/slippery/ComponentInitialize() + . = ..() + AddComponent(/datum/component/slippery, 80, (NO_SLIP_WHEN_WALKING | SLIDE)) diff --git a/code/game/objects/effects/decals/crayon.dm b/code/game/objects/effects/decals/crayon.dm index f1d777b8dab6..da5366350cd7 100644 --- a/code/game/objects/effects/decals/crayon.dm +++ b/code/game/objects/effects/decals/crayon.dm @@ -1,49 +1,49 @@ -GLOBAL_LIST(gang_tags) - -/obj/effect/decal/cleanable/crayon - name = "rune" - desc = "Graffiti. Damn kids." - icon = 'icons/effects/crayondecal.dmi' - icon_state = "rune1" - gender = NEUTER - plane = GAME_PLANE //makes the graffiti visible over a wall. - mergeable_decal = FALSE - var/do_icon_rotate = TRUE - var/rotation = 0 - var/paint_colour = "#FFFFFF" - -/obj/effect/decal/cleanable/crayon/Initialize(mapload, main, type, e_name, graf_rot, alt_icon = null) - . = ..() - if(e_name) - name = e_name - desc = "A [name] vandalizing the station." - if(alt_icon) - icon = alt_icon - if(type) - icon_state = type - if(graf_rot) - rotation = graf_rot - if(rotation && do_icon_rotate) - var/matrix/M = matrix() - M.Turn(rotation) - src.transform = M - if(main) - paint_colour = main - add_atom_colour(paint_colour, FIXED_COLOUR_PRIORITY) -/obj/effect/decal/cleanable/crayon/NeverShouldHaveComeHere(turf/T) - return isgroundlessturf(T) - -/obj/effect/decal/cleanable/crayon/gang - name = "Leet Like Jeff K gang tag" - desc = "Looks like someone's claimed this area for Leet Like Jeff K." - icon = 'icons/obj/gang/tags.dmi' - layer = BELOW_MOB_LAYER - var/datum/team/gang/my_gang - -/obj/effect/decal/cleanable/crayon/gang/Initialize(mapload, main, type, e_name, graf_rot, alt_icon = null) - . = ..() - LAZYADD(GLOB.gang_tags, src) - -/obj/effect/decal/cleanable/crayon/gang/Destroy() - LAZYREMOVE(GLOB.gang_tags, src) - ..() +GLOBAL_LIST(gang_tags) + +/obj/effect/decal/cleanable/crayon + name = "rune" + desc = "Graffiti. Damn kids." + icon = 'icons/effects/crayondecal.dmi' + icon_state = "rune1" + gender = NEUTER + plane = GAME_PLANE //makes the graffiti visible over a wall. + mergeable_decal = FALSE + var/do_icon_rotate = TRUE + var/rotation = 0 + var/paint_colour = "#FFFFFF" + +/obj/effect/decal/cleanable/crayon/Initialize(mapload, main, type, e_name, graf_rot, alt_icon = null) + . = ..() + if(e_name) + name = e_name + desc = "A [name] vandalizing the station." + if(alt_icon) + icon = alt_icon + if(type) + icon_state = type + if(graf_rot) + rotation = graf_rot + if(rotation && do_icon_rotate) + var/matrix/M = matrix() + M.Turn(rotation) + src.transform = M + if(main) + paint_colour = main + add_atom_colour(paint_colour, FIXED_COLOUR_PRIORITY) +/obj/effect/decal/cleanable/crayon/NeverShouldHaveComeHere(turf/T) + return isgroundlessturf(T) + +/obj/effect/decal/cleanable/crayon/gang + name = "Leet Like Jeff K gang tag" + desc = "Looks like someone's claimed this area for Leet Like Jeff K." + icon = 'icons/obj/gang/tags.dmi' + layer = BELOW_MOB_LAYER + var/datum/team/gang/my_gang + +/obj/effect/decal/cleanable/crayon/gang/Initialize(mapload, main, type, e_name, graf_rot, alt_icon = null) + . = ..() + LAZYADD(GLOB.gang_tags, src) + +/obj/effect/decal/cleanable/crayon/gang/Destroy() + LAZYREMOVE(GLOB.gang_tags, src) + ..() diff --git a/code/game/objects/effects/decals/decal.dm b/code/game/objects/effects/decals/decal.dm index 96d719cc3418..803b7250801f 100644 --- a/code/game/objects/effects/decals/decal.dm +++ b/code/game/objects/effects/decals/decal.dm @@ -1,48 +1,48 @@ -/obj/effect/decal - name = "decal" - plane = FLOOR_PLANE - anchored = TRUE - resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF - var/turf_loc_check = TRUE - -/obj/effect/decal/Initialize() - . = ..() - if(turf_loc_check && (!isturf(loc) || NeverShouldHaveComeHere(loc))) - return INITIALIZE_HINT_QDEL - -/obj/effect/decal/blob_act(obj/structure/blob/B) - if(B && B.loc == loc) - qdel(src) - -/obj/effect/decal/proc/NeverShouldHaveComeHere(turf/T) - return isclosedturf(T) || isgroundlessturf(T) - -/obj/effect/decal/ex_act(severity, target) - qdel(src) - -/obj/effect/decal/fire_act(exposed_temperature, exposed_volume) - if(!(resistance_flags & FIRE_PROOF)) //non fire proof decal or being burned by lava - qdel(src) - -/obj/effect/decal/HandleTurfChange(turf/T) - ..() - if(T == loc && NeverShouldHaveComeHere(T)) - qdel(src) - -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/obj/effect/turf_decal - icon = 'icons/turf/decals.dmi' - icon_state = "warningline" - layer = TURF_DECAL_LAYER - -/obj/effect/turf_decal/Initialize() - ..() - return INITIALIZE_HINT_QDEL - -/obj/effect/turf_decal/ComponentInitialize() - . = ..() - var/turf/T = loc - if(!istype(T)) //you know this will happen somehow - CRASH("Turf decal initialized in an object/nullspace") - T.AddComponent(/datum/component/decal, icon, icon_state, dir, CLEAN_NEVER, color, null, null, alpha) +/obj/effect/decal + name = "decal" + plane = FLOOR_PLANE + anchored = TRUE + resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF + var/turf_loc_check = TRUE + +/obj/effect/decal/Initialize() + . = ..() + if(turf_loc_check && (!isturf(loc) || NeverShouldHaveComeHere(loc))) + return INITIALIZE_HINT_QDEL + +/obj/effect/decal/blob_act(obj/structure/blob/B) + if(B && B.loc == loc) + qdel(src) + +/obj/effect/decal/proc/NeverShouldHaveComeHere(turf/T) + return isclosedturf(T) || isgroundlessturf(T) + +/obj/effect/decal/ex_act(severity, target) + qdel(src) + +/obj/effect/decal/fire_act(exposed_temperature, exposed_volume) + if(!(resistance_flags & FIRE_PROOF)) //non fire proof decal or being burned by lava + qdel(src) + +/obj/effect/decal/HandleTurfChange(turf/T) + ..() + if(T == loc && NeverShouldHaveComeHere(T)) + qdel(src) + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/obj/effect/turf_decal + icon = 'icons/turf/decals.dmi' + icon_state = "warningline" + layer = TURF_DECAL_LAYER + +/obj/effect/turf_decal/Initialize() + ..() + return INITIALIZE_HINT_QDEL + +/obj/effect/turf_decal/ComponentInitialize() + . = ..() + var/turf/T = loc + if(!istype(T)) //you know this will happen somehow + CRASH("Turf decal initialized in an object/nullspace") + T.AddComponent(/datum/component/decal, icon, icon_state, dir, CLEAN_NEVER, color, null, null, alpha) diff --git a/code/game/objects/effects/decals/misc.dm b/code/game/objects/effects/decals/misc.dm index 05ff9cb863c4..8f46d5cc4631 100644 --- a/code/game/objects/effects/decals/misc.dm +++ b/code/game/objects/effects/decals/misc.dm @@ -1,31 +1,31 @@ -/obj/effect/temp_visual/point - name = "pointer" - icon = 'icons/mob/screen_gen.dmi' - icon_state = "arrow" - layer = POINT_LAYER - duration = 25 - -/obj/effect/temp_visual/point/Initialize(mapload, set_invis = 0) - . = ..() - var/atom/old_loc = loc - loc = get_turf(src) // We don't want to actualy trigger anything when it moves - pixel_x = old_loc.pixel_x - pixel_y = old_loc.pixel_y - invisibility = set_invis - -//Used by spraybottles. -/obj/effect/decal/chempuff - name = "chemicals" - icon = 'icons/obj/chempuff.dmi' - pass_flags = PASSTABLE | PASSGRILLE - layer = FLY_LAYER - -/obj/effect/decal/chempuff/blob_act(obj/structure/blob/B) - return - -/obj/effect/decal/fakelattice - name = "lattice" - desc = "A lightweight support lattice." - icon = 'icons/obj/smooth_structures/lattice.dmi' - icon_state = "lattice" - density = TRUE +/obj/effect/temp_visual/point + name = "pointer" + icon = 'icons/mob/screen_gen.dmi' + icon_state = "arrow" + layer = POINT_LAYER + duration = 25 + +/obj/effect/temp_visual/point/Initialize(mapload, set_invis = 0) + . = ..() + var/atom/old_loc = loc + loc = get_turf(src) // We don't want to actualy trigger anything when it moves + pixel_x = old_loc.pixel_x + pixel_y = old_loc.pixel_y + invisibility = set_invis + +//Used by spraybottles. +/obj/effect/decal/chempuff + name = "chemicals" + icon = 'icons/obj/chempuff.dmi' + pass_flags = PASSTABLE | PASSGRILLE + layer = FLY_LAYER + +/obj/effect/decal/chempuff/blob_act(obj/structure/blob/B) + return + +/obj/effect/decal/fakelattice + name = "lattice" + desc = "A lightweight support lattice." + icon = 'icons/obj/smooth_structures/lattice.dmi' + icon_state = "lattice" + density = TRUE diff --git a/code/game/objects/effects/decals/remains.dm b/code/game/objects/effects/decals/remains.dm index 8c2e76b168d0..790b6c5a5922 100644 --- a/code/game/objects/effects/decals/remains.dm +++ b/code/game/objects/effects/decals/remains.dm @@ -1,33 +1,33 @@ -/obj/effect/decal/remains - name = "remains" - gender = PLURAL - icon = 'icons/effects/blood.dmi' - -/obj/effect/decal/remains/acid_act() - visible_message("[src] dissolve[gender==PLURAL?"":"s"] into a puddle of sizzling goop!") - playsound(src, 'sound/items/welder.ogg', 150, TRUE) - new /obj/effect/decal/cleanable/greenglow(drop_location()) - qdel(src) - -/obj/effect/decal/remains/human - desc = "They look like human remains. They have a strange aura about them." - icon_state = "remains" - -/obj/effect/decal/remains/plasma - icon_state = "remainsplasma" - -/obj/effect/decal/remains/xeno - desc = "They look like the remains of something... alien. They have a strange aura about them." - icon_state = "remainsxeno" - -/obj/effect/decal/remains/xeno/larva - icon_state = "remainslarva" - -/obj/effect/decal/remains/robot - desc = "They look like the remains of something mechanical. They have a strange aura about them." - icon = 'icons/mob/robots.dmi' - icon_state = "remainsrobot" - -/obj/effect/decal/cleanable/robot_debris/old - name = "dusty robot debris" - desc = "Looks like nobody has touched this in a while." +/obj/effect/decal/remains + name = "remains" + gender = PLURAL + icon = 'icons/effects/blood.dmi' + +/obj/effect/decal/remains/acid_act() + visible_message("[src] dissolve[gender==PLURAL?"":"s"] into a puddle of sizzling goop!") + playsound(src, 'sound/items/welder.ogg', 150, TRUE) + new /obj/effect/decal/cleanable/greenglow(drop_location()) + qdel(src) + +/obj/effect/decal/remains/human + desc = "They look like human remains. They have a strange aura about them." + icon_state = "remains" + +/obj/effect/decal/remains/plasma + icon_state = "remainsplasma" + +/obj/effect/decal/remains/xeno + desc = "They look like the remains of something... alien. They have a strange aura about them." + icon_state = "remainsxeno" + +/obj/effect/decal/remains/xeno/larva + icon_state = "remainslarva" + +/obj/effect/decal/remains/robot + desc = "They look like the remains of something mechanical. They have a strange aura about them." + icon = 'icons/mob/robots.dmi' + icon_state = "remainsrobot" + +/obj/effect/decal/cleanable/robot_debris/old + name = "dusty robot debris" + desc = "Looks like nobody has touched this in a while." diff --git a/code/game/objects/effects/forcefields.dm b/code/game/objects/effects/forcefields.dm index 865e2fa8691d..d3fa9ce2c122 100644 --- a/code/game/objects/effects/forcefields.dm +++ b/code/game/objects/effects/forcefields.dm @@ -1,38 +1,38 @@ -/obj/effect/forcefield - desc = "A space wizard's magic wall." - name = "FORCEWALL" - icon_state = "m_shield" - anchored = TRUE - opacity = 0 - density = TRUE - CanAtmosPass = ATMOS_PASS_DENSITY - var/timeleft = 300 //Set to 0 for permanent forcefields (ugh) - -/obj/effect/forcefield/Initialize() - . = ..() - if(timeleft) - QDEL_IN(src, timeleft) - -/obj/effect/forcefield/singularity_pull() - return - -/obj/effect/forcefield/cult - desc = "An unholy shield that blocks all attacks." - name = "glowing wall" - icon = 'icons/effects/cult_effects.dmi' - icon_state = "cultshield" - CanAtmosPass = ATMOS_PASS_NO - timeleft = 200 - -///////////Mimewalls/////////// - -/obj/effect/forcefield/mime - icon_state = "nothing" - name = "invisible wall" - desc = "You have a bad feeling about this." - alpha = 0 - -/obj/effect/forcefield/mime/advanced - name = "invisible blockade" - desc = "You're gonna be here awhile." - timeleft = 600 +/obj/effect/forcefield + desc = "A space wizard's magic wall." + name = "FORCEWALL" + icon_state = "m_shield" + anchored = TRUE + opacity = 0 + density = TRUE + CanAtmosPass = ATMOS_PASS_DENSITY + var/timeleft = 300 //Set to 0 for permanent forcefields (ugh) + +/obj/effect/forcefield/Initialize() + . = ..() + if(timeleft) + QDEL_IN(src, timeleft) + +/obj/effect/forcefield/singularity_pull() + return + +/obj/effect/forcefield/cult + desc = "An unholy shield that blocks all attacks." + name = "glowing wall" + icon = 'icons/effects/cult_effects.dmi' + icon_state = "cultshield" + CanAtmosPass = ATMOS_PASS_NO + timeleft = 200 + +///////////Mimewalls/////////// + +/obj/effect/forcefield/mime + icon_state = "nothing" + name = "invisible wall" + desc = "You have a bad feeling about this." + alpha = 0 + +/obj/effect/forcefield/mime/advanced + name = "invisible blockade" + desc = "You're gonna be here awhile." + timeleft = 600 diff --git a/code/game/objects/effects/landmarks.dm b/code/game/objects/effects/landmarks.dm index 41a90c85153f..bc6a40013924 100644 --- a/code/game/objects/effects/landmarks.dm +++ b/code/game/objects/effects/landmarks.dm @@ -1,445 +1,445 @@ -/obj/effect/landmark - name = "landmark" - icon = 'icons/effects/landmarks_static.dmi' - icon_state = "x2" - anchored = TRUE - layer = MID_LANDMARK_LAYER - invisibility = INVISIBILITY_ABSTRACT - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - -/obj/effect/landmark/singularity_act() - return - -// Please stop bombing the Observer-Start landmark. -/obj/effect/landmark/ex_act() - return - -/obj/effect/landmark/singularity_pull() - return - -INITIALIZE_IMMEDIATE(/obj/effect/landmark) - -/obj/effect/landmark/Initialize() - . = ..() - GLOB.landmarks_list += src - -/obj/effect/landmark/Destroy() - GLOB.landmarks_list -= src - return ..() - -/obj/effect/landmark/start - name = "start" - icon = 'icons/mob/landmarks.dmi' - icon_state = "x" - anchored = TRUE - layer = MOB_LAYER - var/jobspawn_override = FALSE - var/delete_after_roundstart = TRUE - var/used = FALSE - -/obj/effect/landmark/start/proc/after_round_start() - if(delete_after_roundstart) - qdel(src) - -/obj/effect/landmark/start/New() - GLOB.start_landmarks_list += src - if(jobspawn_override) - if(!GLOB.jobspawn_overrides[name]) - GLOB.jobspawn_overrides[name] = list() - GLOB.jobspawn_overrides[name] += src - ..() - if(name != "start") - tag = "start*[name]" - -/obj/effect/landmark/start/Destroy() - GLOB.start_landmarks_list -= src - if(jobspawn_override) - GLOB.jobspawn_overrides[name] -= src - return ..() - -// START LANDMARKS FOLLOW. Don't change the names unless -// you are refactoring shitty landmark code. -/obj/effect/landmark/start/assistant - name = "Assistant" - icon_state = "Assistant" //icon_state is case sensitive. why are all of these capitalized? because fuck you that's why - -/obj/effect/landmark/start/assistant/override - jobspawn_override = TRUE - delete_after_roundstart = FALSE - -/obj/effect/landmark/start/prisoner - name = "Prisoner" - icon_state = "Prisoner" - -/obj/effect/landmark/start/janitor - name = "Janitor" - icon_state = "Janitor" - -/obj/effect/landmark/start/cargo_technician - name = "Cargo Technician" - icon_state = "Cargo Technician" - -/obj/effect/landmark/start/bartender - name = "Bartender" - icon_state = "Bartender" - -/obj/effect/landmark/start/clown - name = "Clown" - icon_state = "Clown" - -/obj/effect/landmark/start/mime - name = "Mime" - icon_state = "Mime" - -/obj/effect/landmark/start/quartermaster - name = "Quartermaster" - icon_state = "Quartermaster" - -/obj/effect/landmark/start/atmospheric_technician - name = "Atmospheric Technician" - icon_state = "Atmospheric Technician" - -/obj/effect/landmark/start/cook - name = "Cook" - icon_state = "Cook" - -/obj/effect/landmark/start/shaft_miner - name = "Shaft Miner" - icon_state = "Shaft Miner" - -/obj/effect/landmark/start/security_officer - name = "Security Officer" - icon_state = "Security Officer" - -/obj/effect/landmark/start/botanist - name = "Botanist" - icon_state = "Botanist" - -/obj/effect/landmark/start/head_of_security - name = "Head of Security" - icon_state = "Head of Security" - -/obj/effect/landmark/start/captain - name = "Captain" - icon_state = "Captain" - -/obj/effect/landmark/start/detective - name = "Detective" - icon_state = "Detective" - -/obj/effect/landmark/start/warden - name = "Warden" - icon_state = "Warden" - -/obj/effect/landmark/start/chief_engineer - name = "Chief Engineer" - icon_state = "Chief Engineer" - -/obj/effect/landmark/start/head_of_personnel - name = "Head of Personnel" - icon_state = "Head of Personnel" - -/obj/effect/landmark/start/librarian - name = "Curator" - icon_state = "Curator" - -/obj/effect/landmark/start/lawyer - name = "Lawyer" - icon_state = "Lawyer" - -/obj/effect/landmark/start/station_engineer - name = "Station Engineer" - icon_state = "Station Engineer" - -/obj/effect/landmark/start/medical_doctor - name = "Medical Doctor" - icon_state = "Medical Doctor" - -/obj/effect/landmark/start/paramedic - name = "Paramedic" - icon_state = "Paramedic" - -/obj/effect/landmark/start/scientist - name = "Scientist" - icon_state = "Scientist" - -/obj/effect/landmark/start/chemist - name = "Chemist" - icon_state = "Chemist" - -/obj/effect/landmark/start/roboticist - name = "Roboticist" - icon_state = "Roboticist" - -/obj/effect/landmark/start/research_director - name = "Research Director" - icon_state = "Research Director" - -/obj/effect/landmark/start/geneticist - name = "Geneticist" - icon_state = "Geneticist" - -/obj/effect/landmark/start/chief_medical_officer - name = "Chief Medical Officer" - icon_state = "Chief Medical Officer" - -/obj/effect/landmark/start/virologist - name = "Virologist" - icon_state = "Virologist" - -/obj/effect/landmark/start/psychologist - name = "Psychologist" - icon_state = "Psychologist" - -/obj/effect/landmark/start/chaplain - name = "Chaplain" - icon_state = "Chaplain" - -/obj/effect/landmark/start/cyborg - name = "Cyborg" - icon_state = "Cyborg" - -/obj/effect/landmark/start/ai - name = "AI" - icon_state = "AI" - delete_after_roundstart = FALSE - var/primary_ai = TRUE - var/latejoin_active = TRUE - -/obj/effect/landmark/start/ai/after_round_start() - if(latejoin_active && !used) - new /obj/structure/AIcore/latejoin_inactive(loc) - return ..() - -/obj/effect/landmark/start/ai/secondary - icon = 'icons/effects/landmarks_static.dmi' - icon_state = "ai_spawn" - primary_ai = FALSE - latejoin_active = FALSE - -/obj/effect/landmark/start/brig_phys - name = "Brig Physician" - icon_state = "Brig Physician" - -/obj/effect/landmark/start/lieutenant - name = "Lieutenant" - icon_state = "Lieutenant" - -//Department Security spawns - -/obj/effect/landmark/start/depsec - name = "department_sec" - icon_state = "Security Officer" - -/obj/effect/landmark/start/depsec/New() - ..() - GLOB.department_security_spawns += src - -/obj/effect/landmark/start/depsec/Destroy() - GLOB.department_security_spawns -= src - return ..() - -/obj/effect/landmark/start/depsec/supply - name = "supply_sec" - -/obj/effect/landmark/start/depsec/medical - name = "medical_sec" - -/obj/effect/landmark/start/depsec/engineering - name = "engineering_sec" - -/obj/effect/landmark/start/depsec/science - name = "science_sec" - -//Antagonist spawns - -/obj/effect/landmark/start/wizard - name = "wizard" - icon = 'icons/effects/landmarks_static.dmi' - icon_state = "wiznerd_spawn" - -/obj/effect/landmark/start/wizard/Initialize() - ..() - GLOB.wizardstart += loc - return INITIALIZE_HINT_QDEL - -/obj/effect/landmark/start/nukeop - name = "nukeop" - icon = 'icons/effects/landmarks_static.dmi' - icon_state = "snukeop_spawn" - -/obj/effect/landmark/start/nukeop/Initialize() - ..() - GLOB.nukeop_start += loc - return INITIALIZE_HINT_QDEL - -/obj/effect/landmark/start/nukeop_leader - name = "nukeop leader" - icon = 'icons/effects/landmarks_static.dmi' - icon_state = "snukeop_leader_spawn" - -/obj/effect/landmark/start/nukeop_leader/Initialize() - ..() - GLOB.nukeop_leader_start += loc - return INITIALIZE_HINT_QDEL - -// Must be immediate because players will -// join before SSatom initializes everything. -INITIALIZE_IMMEDIATE(/obj/effect/landmark/start/new_player) - -/obj/effect/landmark/start/new_player - name = "New Player" - -/obj/effect/landmark/start/new_player/Initialize() - ..() - GLOB.newplayer_start += loc - return INITIALIZE_HINT_QDEL - -/obj/effect/landmark/latejoin - name = "JoinLate" - -/obj/effect/landmark/latejoin/Initialize(mapload) - ..() - SSjob.latejoin_trackers += loc - return INITIALIZE_HINT_QDEL - -//space carps, magicarps, lone ops, slaughter demons, possibly revenants spawn here -/obj/effect/landmark/carpspawn - name = "carpspawn" - icon_state = "carp_spawn" - -//observer start -/obj/effect/landmark/observer_start - name = "Observer-Start" - icon_state = "observer_start" - -//xenos, morphs and nightmares spawn here -/obj/effect/landmark/xeno_spawn - name = "xeno_spawn" - icon_state = "xeno_spawn" - -/obj/effect/landmark/xeno_spawn/Initialize(mapload) - ..() - GLOB.xeno_spawn += loc - return INITIALIZE_HINT_QDEL - -//objects with the stationloving component (nuke disk) respawn here. -//also blobs that have their spawn forcemoved (running out of time when picking their spawn spot), santa and respawning devils -/obj/effect/landmark/blobstart - name = "blobstart" - icon_state = "blob_start" - -/obj/effect/landmark/blobstart/Initialize(mapload) - ..() - GLOB.blobstart += loc - return INITIALIZE_HINT_QDEL - -//spawns sec equipment lockers depending on the number of sec officers -/obj/effect/landmark/secequipment - name = "secequipment" - icon_state = "secequipment" - -/obj/effect/landmark/secequipment/Initialize(mapload) - ..() - GLOB.secequipment += loc - return INITIALIZE_HINT_QDEL - -//players that get put in admin jail show up here -/obj/effect/landmark/prisonwarp - name = "prisonwarp" - icon_state = "prisonwarp" - -/obj/effect/landmark/prisonwarp/Initialize(mapload) - ..() - GLOB.prisonwarp += loc - return INITIALIZE_HINT_QDEL - -/obj/effect/landmark/ert_spawn - name = "Emergencyresponseteam" - icon_state = "ert_spawn" - -/obj/effect/landmark/ert_spawn/Initialize(mapload) - ..() - GLOB.emergencyresponseteamspawn += loc - return INITIALIZE_HINT_QDEL - -//ninja energy nets teleport victims here -/obj/effect/landmark/holding_facility - name = "Holding Facility" - icon_state = "holding_facility" - -/obj/effect/landmark/holding_facility/Initialize(mapload) - ..() - GLOB.holdingfacility += loc - return INITIALIZE_HINT_QDEL - -/obj/effect/landmark/thunderdome/observe - name = "tdomeobserve" - icon_state = "tdome_observer" - -/obj/effect/landmark/thunderdome/observe/Initialize(mapload) - ..() - GLOB.tdomeobserve += loc - return INITIALIZE_HINT_QDEL - -/obj/effect/landmark/thunderdome/one - name = "tdome1" - icon_state = "tdome_t1" - -/obj/effect/landmark/thunderdome/one/Initialize(mapload) - ..() - GLOB.tdome1 += loc - return INITIALIZE_HINT_QDEL - -/obj/effect/landmark/thunderdome/two - name = "tdome2" - icon_state = "tdome_t2" - -/obj/effect/landmark/thunderdome/two/Initialize(mapload) - ..() - GLOB.tdome2 += loc - return INITIALIZE_HINT_QDEL - -/obj/effect/landmark/thunderdome/admin - name = "tdomeadmin" - icon_state = "tdome_admin" - -/obj/effect/landmark/thunderdome/admin/Initialize(mapload) - ..() - GLOB.tdomeadmin += loc - return INITIALIZE_HINT_QDEL - -//generic event spawns -/obj/effect/landmark/event_spawn - name = "generic event spawn" - icon_state = "generic_event" - layer = HIGH_LANDMARK_LAYER - - -/obj/effect/landmark/event_spawn/New() - ..() - GLOB.generic_event_spawns += src - -/obj/effect/landmark/event_spawn/Destroy() - GLOB.generic_event_spawns -= src - return ..() - -/obj/effect/landmark/ruin - var/datum/map_template/ruin/ruin_template - -/obj/effect/landmark/ruin/New(loc, my_ruin_template) - name = "ruin_[GLOB.ruin_landmarks.len + 1]" - ..(loc) - ruin_template = my_ruin_template - GLOB.ruin_landmarks |= src - -/obj/effect/landmark/ruin/Destroy() - GLOB.ruin_landmarks -= src - ruin_template = null - . = ..() - -// handled in portals.dm, id connected to one-way portal -/obj/effect/landmark/portal_exit - name = "portal exit" - icon_state = "portal_exit" - var/id +/obj/effect/landmark + name = "landmark" + icon = 'icons/effects/landmarks_static.dmi' + icon_state = "x2" + anchored = TRUE + layer = MID_LANDMARK_LAYER + invisibility = INVISIBILITY_ABSTRACT + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + +/obj/effect/landmark/singularity_act() + return + +// Please stop bombing the Observer-Start landmark. +/obj/effect/landmark/ex_act() + return + +/obj/effect/landmark/singularity_pull() + return + +INITIALIZE_IMMEDIATE(/obj/effect/landmark) + +/obj/effect/landmark/Initialize() + . = ..() + GLOB.landmarks_list += src + +/obj/effect/landmark/Destroy() + GLOB.landmarks_list -= src + return ..() + +/obj/effect/landmark/start + name = "start" + icon = 'icons/mob/landmarks.dmi' + icon_state = "x" + anchored = TRUE + layer = MOB_LAYER + var/jobspawn_override = FALSE + var/delete_after_roundstart = TRUE + var/used = FALSE + +/obj/effect/landmark/start/proc/after_round_start() + if(delete_after_roundstart) + qdel(src) + +/obj/effect/landmark/start/New() + GLOB.start_landmarks_list += src + if(jobspawn_override) + if(!GLOB.jobspawn_overrides[name]) + GLOB.jobspawn_overrides[name] = list() + GLOB.jobspawn_overrides[name] += src + ..() + if(name != "start") + tag = "start*[name]" + +/obj/effect/landmark/start/Destroy() + GLOB.start_landmarks_list -= src + if(jobspawn_override) + GLOB.jobspawn_overrides[name] -= src + return ..() + +// START LANDMARKS FOLLOW. Don't change the names unless +// you are refactoring shitty landmark code. +/obj/effect/landmark/start/assistant + name = "Assistant" + icon_state = "Assistant" //icon_state is case sensitive. why are all of these capitalized? because fuck you that's why + +/obj/effect/landmark/start/assistant/override + jobspawn_override = TRUE + delete_after_roundstart = FALSE + +/obj/effect/landmark/start/prisoner + name = "Prisoner" + icon_state = "Prisoner" + +/obj/effect/landmark/start/janitor + name = "Janitor" + icon_state = "Janitor" + +/obj/effect/landmark/start/cargo_technician + name = "Cargo Technician" + icon_state = "Cargo Technician" + +/obj/effect/landmark/start/bartender + name = "Bartender" + icon_state = "Bartender" + +/obj/effect/landmark/start/clown + name = "Clown" + icon_state = "Clown" + +/obj/effect/landmark/start/mime + name = "Mime" + icon_state = "Mime" + +/obj/effect/landmark/start/quartermaster + name = "Quartermaster" + icon_state = "Quartermaster" + +/obj/effect/landmark/start/atmospheric_technician + name = "Atmospheric Technician" + icon_state = "Atmospheric Technician" + +/obj/effect/landmark/start/cook + name = "Cook" + icon_state = "Cook" + +/obj/effect/landmark/start/shaft_miner + name = "Shaft Miner" + icon_state = "Shaft Miner" + +/obj/effect/landmark/start/security_officer + name = "Security Officer" + icon_state = "Security Officer" + +/obj/effect/landmark/start/botanist + name = "Botanist" + icon_state = "Botanist" + +/obj/effect/landmark/start/head_of_security + name = "Head of Security" + icon_state = "Head of Security" + +/obj/effect/landmark/start/captain + name = "Captain" + icon_state = "Captain" + +/obj/effect/landmark/start/detective + name = "Detective" + icon_state = "Detective" + +/obj/effect/landmark/start/warden + name = "Warden" + icon_state = "Warden" + +/obj/effect/landmark/start/chief_engineer + name = "Chief Engineer" + icon_state = "Chief Engineer" + +/obj/effect/landmark/start/head_of_personnel + name = "Head of Personnel" + icon_state = "Head of Personnel" + +/obj/effect/landmark/start/librarian + name = "Curator" + icon_state = "Curator" + +/obj/effect/landmark/start/lawyer + name = "Lawyer" + icon_state = "Lawyer" + +/obj/effect/landmark/start/station_engineer + name = "Station Engineer" + icon_state = "Station Engineer" + +/obj/effect/landmark/start/medical_doctor + name = "Medical Doctor" + icon_state = "Medical Doctor" + +/obj/effect/landmark/start/paramedic + name = "Paramedic" + icon_state = "Paramedic" + +/obj/effect/landmark/start/scientist + name = "Scientist" + icon_state = "Scientist" + +/obj/effect/landmark/start/chemist + name = "Chemist" + icon_state = "Chemist" + +/obj/effect/landmark/start/roboticist + name = "Roboticist" + icon_state = "Roboticist" + +/obj/effect/landmark/start/research_director + name = "Research Director" + icon_state = "Research Director" + +/obj/effect/landmark/start/geneticist + name = "Geneticist" + icon_state = "Geneticist" + +/obj/effect/landmark/start/chief_medical_officer + name = "Chief Medical Officer" + icon_state = "Chief Medical Officer" + +/obj/effect/landmark/start/virologist + name = "Virologist" + icon_state = "Virologist" + +/obj/effect/landmark/start/psychologist + name = "Psychologist" + icon_state = "Psychologist" + +/obj/effect/landmark/start/chaplain + name = "Chaplain" + icon_state = "Chaplain" + +/obj/effect/landmark/start/cyborg + name = "Cyborg" + icon_state = "Cyborg" + +/obj/effect/landmark/start/ai + name = "AI" + icon_state = "AI" + delete_after_roundstart = FALSE + var/primary_ai = TRUE + var/latejoin_active = TRUE + +/obj/effect/landmark/start/ai/after_round_start() + if(latejoin_active && !used) + new /obj/structure/AIcore/latejoin_inactive(loc) + return ..() + +/obj/effect/landmark/start/ai/secondary + icon = 'icons/effects/landmarks_static.dmi' + icon_state = "ai_spawn" + primary_ai = FALSE + latejoin_active = FALSE + +/obj/effect/landmark/start/brig_phys + name = "Brig Physician" + icon_state = "Brig Physician" + +/obj/effect/landmark/start/lieutenant + name = "Lieutenant" + icon_state = "Lieutenant" + +//Department Security spawns + +/obj/effect/landmark/start/depsec + name = "department_sec" + icon_state = "Security Officer" + +/obj/effect/landmark/start/depsec/New() + ..() + GLOB.department_security_spawns += src + +/obj/effect/landmark/start/depsec/Destroy() + GLOB.department_security_spawns -= src + return ..() + +/obj/effect/landmark/start/depsec/supply + name = "supply_sec" + +/obj/effect/landmark/start/depsec/medical + name = "medical_sec" + +/obj/effect/landmark/start/depsec/engineering + name = "engineering_sec" + +/obj/effect/landmark/start/depsec/science + name = "science_sec" + +//Antagonist spawns + +/obj/effect/landmark/start/wizard + name = "wizard" + icon = 'icons/effects/landmarks_static.dmi' + icon_state = "wiznerd_spawn" + +/obj/effect/landmark/start/wizard/Initialize() + ..() + GLOB.wizardstart += loc + return INITIALIZE_HINT_QDEL + +/obj/effect/landmark/start/nukeop + name = "nukeop" + icon = 'icons/effects/landmarks_static.dmi' + icon_state = "snukeop_spawn" + +/obj/effect/landmark/start/nukeop/Initialize() + ..() + GLOB.nukeop_start += loc + return INITIALIZE_HINT_QDEL + +/obj/effect/landmark/start/nukeop_leader + name = "nukeop leader" + icon = 'icons/effects/landmarks_static.dmi' + icon_state = "snukeop_leader_spawn" + +/obj/effect/landmark/start/nukeop_leader/Initialize() + ..() + GLOB.nukeop_leader_start += loc + return INITIALIZE_HINT_QDEL + +// Must be immediate because players will +// join before SSatom initializes everything. +INITIALIZE_IMMEDIATE(/obj/effect/landmark/start/new_player) + +/obj/effect/landmark/start/new_player + name = "New Player" + +/obj/effect/landmark/start/new_player/Initialize() + ..() + GLOB.newplayer_start += loc + return INITIALIZE_HINT_QDEL + +/obj/effect/landmark/latejoin + name = "JoinLate" + +/obj/effect/landmark/latejoin/Initialize(mapload) + ..() + SSjob.latejoin_trackers += loc + return INITIALIZE_HINT_QDEL + +//space carps, magicarps, lone ops, slaughter demons, possibly revenants spawn here +/obj/effect/landmark/carpspawn + name = "carpspawn" + icon_state = "carp_spawn" + +//observer start +/obj/effect/landmark/observer_start + name = "Observer-Start" + icon_state = "observer_start" + +//xenos, morphs and nightmares spawn here +/obj/effect/landmark/xeno_spawn + name = "xeno_spawn" + icon_state = "xeno_spawn" + +/obj/effect/landmark/xeno_spawn/Initialize(mapload) + ..() + GLOB.xeno_spawn += loc + return INITIALIZE_HINT_QDEL + +//objects with the stationloving component (nuke disk) respawn here. +//also blobs that have their spawn forcemoved (running out of time when picking their spawn spot), santa and respawning devils +/obj/effect/landmark/blobstart + name = "blobstart" + icon_state = "blob_start" + +/obj/effect/landmark/blobstart/Initialize(mapload) + ..() + GLOB.blobstart += loc + return INITIALIZE_HINT_QDEL + +//spawns sec equipment lockers depending on the number of sec officers +/obj/effect/landmark/secequipment + name = "secequipment" + icon_state = "secequipment" + +/obj/effect/landmark/secequipment/Initialize(mapload) + ..() + GLOB.secequipment += loc + return INITIALIZE_HINT_QDEL + +//players that get put in admin jail show up here +/obj/effect/landmark/prisonwarp + name = "prisonwarp" + icon_state = "prisonwarp" + +/obj/effect/landmark/prisonwarp/Initialize(mapload) + ..() + GLOB.prisonwarp += loc + return INITIALIZE_HINT_QDEL + +/obj/effect/landmark/ert_spawn + name = "Emergencyresponseteam" + icon_state = "ert_spawn" + +/obj/effect/landmark/ert_spawn/Initialize(mapload) + ..() + GLOB.emergencyresponseteamspawn += loc + return INITIALIZE_HINT_QDEL + +//ninja energy nets teleport victims here +/obj/effect/landmark/holding_facility + name = "Holding Facility" + icon_state = "holding_facility" + +/obj/effect/landmark/holding_facility/Initialize(mapload) + ..() + GLOB.holdingfacility += loc + return INITIALIZE_HINT_QDEL + +/obj/effect/landmark/thunderdome/observe + name = "tdomeobserve" + icon_state = "tdome_observer" + +/obj/effect/landmark/thunderdome/observe/Initialize(mapload) + ..() + GLOB.tdomeobserve += loc + return INITIALIZE_HINT_QDEL + +/obj/effect/landmark/thunderdome/one + name = "tdome1" + icon_state = "tdome_t1" + +/obj/effect/landmark/thunderdome/one/Initialize(mapload) + ..() + GLOB.tdome1 += loc + return INITIALIZE_HINT_QDEL + +/obj/effect/landmark/thunderdome/two + name = "tdome2" + icon_state = "tdome_t2" + +/obj/effect/landmark/thunderdome/two/Initialize(mapload) + ..() + GLOB.tdome2 += loc + return INITIALIZE_HINT_QDEL + +/obj/effect/landmark/thunderdome/admin + name = "tdomeadmin" + icon_state = "tdome_admin" + +/obj/effect/landmark/thunderdome/admin/Initialize(mapload) + ..() + GLOB.tdomeadmin += loc + return INITIALIZE_HINT_QDEL + +//generic event spawns +/obj/effect/landmark/event_spawn + name = "generic event spawn" + icon_state = "generic_event" + layer = HIGH_LANDMARK_LAYER + + +/obj/effect/landmark/event_spawn/New() + ..() + GLOB.generic_event_spawns += src + +/obj/effect/landmark/event_spawn/Destroy() + GLOB.generic_event_spawns -= src + return ..() + +/obj/effect/landmark/ruin + var/datum/map_template/ruin/ruin_template + +/obj/effect/landmark/ruin/New(loc, my_ruin_template) + name = "ruin_[GLOB.ruin_landmarks.len + 1]" + ..(loc) + ruin_template = my_ruin_template + GLOB.ruin_landmarks |= src + +/obj/effect/landmark/ruin/Destroy() + GLOB.ruin_landmarks -= src + ruin_template = null + . = ..() + +// handled in portals.dm, id connected to one-way portal +/obj/effect/landmark/portal_exit + name = "portal exit" + icon_state = "portal_exit" + var/id diff --git a/code/game/objects/effects/mines.dm b/code/game/objects/effects/mines.dm index 119a0893d477..fcf22fc51861 100644 --- a/code/game/objects/effects/mines.dm +++ b/code/game/objects/effects/mines.dm @@ -1,195 +1,195 @@ -/obj/effect/mine - name = "dummy mine" - desc = "Better stay away from that thing." - density = FALSE - anchored = TRUE - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "uglymine" - var/triggered = 0 - -/obj/effect/mine/proc/mineEffect(mob/victim) - to_chat(victim, "*click*") - -/obj/effect/mine/Crossed(AM as mob|obj) - . = ..() - if(isturf(loc)) - if(ismob(AM)) - var/mob/MM = AM - if(!(MM.movement_type & FLYING)) - triggermine(AM) - else - triggermine(AM) - -/obj/effect/mine/proc/triggermine(mob/victim) - if(triggered) - return - visible_message("[victim] sets off [icon2html(src, viewers(src))] [src]!") - var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread - s.set_up(3, 1, src) - s.start() - mineEffect(victim) - SEND_SIGNAL(src, COMSIG_MINE_TRIGGERED) - triggered = 1 - qdel(src) - - -/obj/effect/mine/explosive - name = "explosive mine" - var/range_devastation = 0 - var/range_heavy = 1 - var/range_light = 2 - var/range_flash = 3 - -/obj/effect/mine/explosive/mineEffect(mob/victim) - explosion(loc, range_devastation, range_heavy, range_light, range_flash) - -/obj/effect/mine/stun - name = "stun mine" - var/stun_time = 80 - -/obj/effect/mine/shrapnel - name = "shrapnel mine" - var/shrapnel_type = /obj/projectile/bullet/shrapnel - var/shrapnel_magnitude = 3 - -/obj/effect/mine/shrapnel/mineEffect(mob/victim) - AddComponent(/datum/component/pellet_cloud, projectile_type=shrapnel_type, magnitude=shrapnel_magnitude) - -/obj/effect/mine/shrapnel/sting - name = "stinger mine" - shrapnel_type = /obj/projectile/bullet/pellet/stingball - -/obj/effect/mine/stun/mineEffect(mob/living/victim) - if(isliving(victim)) - victim.Paralyze(stun_time) - -/obj/effect/mine/kickmine - name = "kick mine" - -/obj/effect/mine/kickmine/mineEffect(mob/victim) - if(isliving(victim) && victim.client) - to_chat(victim, "You have been kicked FOR NO REISIN!") - qdel(victim.client) - - -/obj/effect/mine/gas - name = "oxygen mine" - var/gas_amount = 360 - var/gas_type = "o2" - -/obj/effect/mine/gas/mineEffect(mob/victim) - atmos_spawn_air("[gas_type]=[gas_amount]") - - -/obj/effect/mine/gas/plasma - name = "plasma mine" - gas_type = "plasma" - - -/obj/effect/mine/gas/n2o - name = "\improper N2O mine" - gas_type = "n2o" - - -/obj/effect/mine/gas/water_vapor - name = "chilled vapor mine" - gas_amount = 500 - gas_type = "water_vapor" - -/obj/effect/mine/sound - name = "honkblaster 1000" - var/sound = 'sound/items/bikehorn.ogg' - -/obj/effect/mine/sound/mineEffect(mob/victim) - playsound(loc, sound, 100, TRUE) - - -/obj/effect/mine/sound/bwoink - name = "bwoink mine" - sound = 'sound/effects/adminhelp.ogg' - -/obj/effect/mine/pickup - name = "pickup" - desc = "pick me up" - icon = 'icons/effects/effects.dmi' - icon_state = "electricity2" - density = FALSE - var/duration = 0 - -/obj/effect/mine/pickup/Initialize() - . = ..() - animate(src, pixel_y = 4, time = 20, loop = -1) - -/obj/effect/mine/pickup/triggermine(mob/victim) - if(triggered) - return - triggered = 1 - invisibility = INVISIBILITY_ABSTRACT - mineEffect(victim) - qdel(src) - - -/obj/effect/mine/pickup/bloodbath - name = "Red Orb" - desc = "You feel angry just looking at it." - duration = 1200 //2min - color = "#FF0000" - -/obj/effect/mine/pickup/bloodbath/mineEffect(mob/living/carbon/victim) - if(!victim.client || !istype(victim)) - return - to_chat(victim, "RIP AND TEAR") - var/old_color = victim.client.color - var/static/list/red_splash = list(1,0,0,0.8,0.2,0, 0.8,0,0.2,0.1,0,0) - var/static/list/pure_red = list(0,0,0,0,0,0,0,0,0,1,0,0) - - INVOKE_ASYNC(src, .proc/blood_delusion, victim) - - var/obj/item/chainsaw/doomslayer/chainsaw = new(victim.loc) - victim.log_message("entered a blood frenzy", LOG_ATTACK) - - ADD_TRAIT(chainsaw, TRAIT_NODROP, CHAINSAW_FRENZY_TRAIT) - victim.drop_all_held_items() - victim.put_in_hands(chainsaw, forced = TRUE) - chainsaw.attack_self(victim) - victim.reagents.add_reagent(/datum/reagent/medicine/adminordrazine,25) - to_chat(victim, "KILL, KILL, KILL! YOU HAVE NO ALLIES ANYMORE, KILL THEM ALL!") - - victim.client.color = pure_red - animate(victim.client,color = red_splash, time = 10, easing = SINE_EASING|EASE_OUT) - sleep(10) - animate(victim.client,color = old_color, time = duration)//, easing = SINE_EASING|EASE_OUT) - sleep(duration) - to_chat(victim, "Your bloodlust seeps back into the bog of your subconscious and you regain self control.") - qdel(chainsaw) - victim.log_message("exited a blood frenzy", LOG_ATTACK) - qdel(src) - -/obj/effect/mine/pickup/bloodbath/proc/blood_delusion(mob/living/carbon/victim) - new /datum/hallucination/delusion(victim, TRUE, "demon", duration, 0) - -/obj/effect/mine/pickup/healing - name = "Blue Orb" - desc = "You feel better just looking at it." - color = "#0000FF" - -/obj/effect/mine/pickup/healing/mineEffect(mob/living/carbon/victim) - if(!victim.client || !istype(victim)) - return - to_chat(victim, "You feel great!") - victim.revive(full_heal = TRUE, admin_revive = TRUE) - -/obj/effect/mine/pickup/speed - name = "Yellow Orb" - desc = "You feel faster just looking at it." - color = "#FFFF00" - duration = 300 - -/obj/effect/mine/pickup/speed/mineEffect(mob/living/carbon/victim) - if(!victim.client || !istype(victim)) - return - to_chat(victim, "You feel fast!") - victim.add_movespeed_modifier(/datum/movespeed_modifier/yellow_orb) - sleep(duration) - victim.remove_movespeed_modifier(/datum/movespeed_modifier/yellow_orb) - to_chat(victim, "You slow down.") +/obj/effect/mine + name = "dummy mine" + desc = "Better stay away from that thing." + density = FALSE + anchored = TRUE + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "uglymine" + var/triggered = 0 + +/obj/effect/mine/proc/mineEffect(mob/victim) + to_chat(victim, "*click*") + +/obj/effect/mine/Crossed(AM as mob|obj) + . = ..() + if(isturf(loc)) + if(ismob(AM)) + var/mob/MM = AM + if(!(MM.movement_type & FLYING)) + triggermine(AM) + else + triggermine(AM) + +/obj/effect/mine/proc/triggermine(mob/victim) + if(triggered) + return + visible_message("[victim] sets off [icon2html(src, viewers(src))] [src]!") + var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread + s.set_up(3, 1, src) + s.start() + mineEffect(victim) + SEND_SIGNAL(src, COMSIG_MINE_TRIGGERED) + triggered = 1 + qdel(src) + + +/obj/effect/mine/explosive + name = "explosive mine" + var/range_devastation = 0 + var/range_heavy = 1 + var/range_light = 2 + var/range_flash = 3 + +/obj/effect/mine/explosive/mineEffect(mob/victim) + explosion(loc, range_devastation, range_heavy, range_light, range_flash) + +/obj/effect/mine/stun + name = "stun mine" + var/stun_time = 80 + +/obj/effect/mine/shrapnel + name = "shrapnel mine" + var/shrapnel_type = /obj/projectile/bullet/shrapnel + var/shrapnel_magnitude = 3 + +/obj/effect/mine/shrapnel/mineEffect(mob/victim) + AddComponent(/datum/component/pellet_cloud, projectile_type=shrapnel_type, magnitude=shrapnel_magnitude) + +/obj/effect/mine/shrapnel/sting + name = "stinger mine" + shrapnel_type = /obj/projectile/bullet/pellet/stingball + +/obj/effect/mine/stun/mineEffect(mob/living/victim) + if(isliving(victim)) + victim.Paralyze(stun_time) + +/obj/effect/mine/kickmine + name = "kick mine" + +/obj/effect/mine/kickmine/mineEffect(mob/victim) + if(isliving(victim) && victim.client) + to_chat(victim, "You have been kicked FOR NO REISIN!") + qdel(victim.client) + + +/obj/effect/mine/gas + name = "oxygen mine" + var/gas_amount = 360 + var/gas_type = "o2" + +/obj/effect/mine/gas/mineEffect(mob/victim) + atmos_spawn_air("[gas_type]=[gas_amount]") + + +/obj/effect/mine/gas/plasma + name = "plasma mine" + gas_type = "plasma" + + +/obj/effect/mine/gas/n2o + name = "\improper N2O mine" + gas_type = "n2o" + + +/obj/effect/mine/gas/water_vapor + name = "chilled vapor mine" + gas_amount = 500 + gas_type = "water_vapor" + +/obj/effect/mine/sound + name = "honkblaster 1000" + var/sound = 'sound/items/bikehorn.ogg' + +/obj/effect/mine/sound/mineEffect(mob/victim) + playsound(loc, sound, 100, TRUE) + + +/obj/effect/mine/sound/bwoink + name = "bwoink mine" + sound = 'sound/effects/adminhelp.ogg' + +/obj/effect/mine/pickup + name = "pickup" + desc = "pick me up" + icon = 'icons/effects/effects.dmi' + icon_state = "electricity2" + density = FALSE + var/duration = 0 + +/obj/effect/mine/pickup/Initialize() + . = ..() + animate(src, pixel_y = 4, time = 20, loop = -1) + +/obj/effect/mine/pickup/triggermine(mob/victim) + if(triggered) + return + triggered = 1 + invisibility = INVISIBILITY_ABSTRACT + mineEffect(victim) + qdel(src) + + +/obj/effect/mine/pickup/bloodbath + name = "Red Orb" + desc = "You feel angry just looking at it." + duration = 1200 //2min + color = "#FF0000" + +/obj/effect/mine/pickup/bloodbath/mineEffect(mob/living/carbon/victim) + if(!victim.client || !istype(victim)) + return + to_chat(victim, "RIP AND TEAR") + var/old_color = victim.client.color + var/static/list/red_splash = list(1,0,0,0.8,0.2,0, 0.8,0,0.2,0.1,0,0) + var/static/list/pure_red = list(0,0,0,0,0,0,0,0,0,1,0,0) + + INVOKE_ASYNC(src, .proc/blood_delusion, victim) + + var/obj/item/chainsaw/doomslayer/chainsaw = new(victim.loc) + victim.log_message("entered a blood frenzy", LOG_ATTACK) + + ADD_TRAIT(chainsaw, TRAIT_NODROP, CHAINSAW_FRENZY_TRAIT) + victim.drop_all_held_items() + victim.put_in_hands(chainsaw, forced = TRUE) + chainsaw.attack_self(victim) + victim.reagents.add_reagent(/datum/reagent/medicine/adminordrazine,25) + to_chat(victim, "KILL, KILL, KILL! YOU HAVE NO ALLIES ANYMORE, KILL THEM ALL!") + + victim.client.color = pure_red + animate(victim.client,color = red_splash, time = 10, easing = SINE_EASING|EASE_OUT) + sleep(10) + animate(victim.client,color = old_color, time = duration)//, easing = SINE_EASING|EASE_OUT) + sleep(duration) + to_chat(victim, "Your bloodlust seeps back into the bog of your subconscious and you regain self control.") + qdel(chainsaw) + victim.log_message("exited a blood frenzy", LOG_ATTACK) + qdel(src) + +/obj/effect/mine/pickup/bloodbath/proc/blood_delusion(mob/living/carbon/victim) + new /datum/hallucination/delusion(victim, TRUE, "demon", duration, 0) + +/obj/effect/mine/pickup/healing + name = "Blue Orb" + desc = "You feel better just looking at it." + color = "#0000FF" + +/obj/effect/mine/pickup/healing/mineEffect(mob/living/carbon/victim) + if(!victim.client || !istype(victim)) + return + to_chat(victim, "You feel great!") + victim.revive(full_heal = TRUE, admin_revive = TRUE) + +/obj/effect/mine/pickup/speed + name = "Yellow Orb" + desc = "You feel faster just looking at it." + color = "#FFFF00" + duration = 300 + +/obj/effect/mine/pickup/speed/mineEffect(mob/living/carbon/victim) + if(!victim.client || !istype(victim)) + return + to_chat(victim, "You feel fast!") + victim.add_movespeed_modifier(/datum/movespeed_modifier/yellow_orb) + sleep(duration) + victim.remove_movespeed_modifier(/datum/movespeed_modifier/yellow_orb) + to_chat(victim, "You slow down.") diff --git a/code/game/objects/effects/misc.dm b/code/game/objects/effects/misc.dm index fdf580fb6c75..b7ded01d5a21 100644 --- a/code/game/objects/effects/misc.dm +++ b/code/game/objects/effects/misc.dm @@ -1,94 +1,94 @@ -//The effect when you wrap a dead body in gift wrap -/obj/effect/spresent - name = "strange present" - desc = "It's a ... present?" - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "strangepresent" - density = TRUE - anchored = FALSE - -/obj/effect/beam - name = "beam" - var/def_zone - pass_flags = PASSTABLE - -/obj/effect/beam/singularity_act() - return - -/obj/effect/beam/singularity_pull() - return - -/obj/effect/spawner - name = "object spawner" - -/obj/effect/list_container - name = "list container" - -/obj/effect/list_container/mobl - name = "mobl" - var/master = null - - var/list/container = list( ) - -/obj/effect/overlay/thermite - name = "thermite" - desc = "Looks hot." - icon = 'icons/effects/fire.dmi' - icon_state = "2" //what? - anchored = TRUE - opacity = TRUE - density = TRUE - layer = FLY_LAYER - -/obj/effect/supplypod_selector - icon_state = "supplypod_selector" - layer = FLY_LAYER - -//Makes a tile fully lit no matter what -/obj/effect/fullbright - icon = 'icons/effects/alphacolors.dmi' - icon_state = "white" - plane = LIGHTING_PLANE - layer = LIGHTING_LAYER - blend_mode = BLEND_ADD - -/obj/effect/abstract/marker - name = "marker" - icon = 'icons/effects/effects.dmi' - anchored = TRUE - icon_state = "wave3" - layer = RIPPLE_LAYER - -/obj/effect/abstract/marker/Initialize(mapload) - . = ..() - GLOB.all_abstract_markers += src - -/obj/effect/abstract/marker/Destroy() - GLOB.all_abstract_markers -= src - . = ..() - -/obj/effect/abstract/marker/at - name = "active turf marker" - - -/obj/effect/dummy/lighting_obj - name = "lighting fx obj" - desc = "Tell a coder if you're seeing this." - icon_state = "nothing" - light_color = "#FFFFFF" - light_range = MINIMUM_USEFUL_LIGHT_RANGE - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - -/obj/effect/dummy/lighting_obj/Initialize(mapload, _color, _range, _power, _duration) - . = ..() - set_light(_range ? _range : light_range, _power ? _power : light_power, _color ? _color : light_color) - if(_duration) - QDEL_IN(src, _duration) - -/obj/effect/dummy/lighting_obj/moblight - name = "mob lighting fx" - -/obj/effect/dummy/lighting_obj/moblight/Initialize(mapload, _color, _range, _power, _duration) - . = ..() - if(!ismob(loc)) - return INITIALIZE_HINT_QDEL +//The effect when you wrap a dead body in gift wrap +/obj/effect/spresent + name = "strange present" + desc = "It's a ... present?" + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "strangepresent" + density = TRUE + anchored = FALSE + +/obj/effect/beam + name = "beam" + var/def_zone + pass_flags = PASSTABLE + +/obj/effect/beam/singularity_act() + return + +/obj/effect/beam/singularity_pull() + return + +/obj/effect/spawner + name = "object spawner" + +/obj/effect/list_container + name = "list container" + +/obj/effect/list_container/mobl + name = "mobl" + var/master = null + + var/list/container = list( ) + +/obj/effect/overlay/thermite + name = "thermite" + desc = "Looks hot." + icon = 'icons/effects/fire.dmi' + icon_state = "2" //what? + anchored = TRUE + opacity = TRUE + density = TRUE + layer = FLY_LAYER + +/obj/effect/supplypod_selector + icon_state = "supplypod_selector" + layer = FLY_LAYER + +//Makes a tile fully lit no matter what +/obj/effect/fullbright + icon = 'icons/effects/alphacolors.dmi' + icon_state = "white" + plane = LIGHTING_PLANE + layer = LIGHTING_LAYER + blend_mode = BLEND_ADD + +/obj/effect/abstract/marker + name = "marker" + icon = 'icons/effects/effects.dmi' + anchored = TRUE + icon_state = "wave3" + layer = RIPPLE_LAYER + +/obj/effect/abstract/marker/Initialize(mapload) + . = ..() + GLOB.all_abstract_markers += src + +/obj/effect/abstract/marker/Destroy() + GLOB.all_abstract_markers -= src + . = ..() + +/obj/effect/abstract/marker/at + name = "active turf marker" + + +/obj/effect/dummy/lighting_obj + name = "lighting fx obj" + desc = "Tell a coder if you're seeing this." + icon_state = "nothing" + light_color = "#FFFFFF" + light_range = MINIMUM_USEFUL_LIGHT_RANGE + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + +/obj/effect/dummy/lighting_obj/Initialize(mapload, _color, _range, _power, _duration) + . = ..() + set_light(_range ? _range : light_range, _power ? _power : light_power, _color ? _color : light_color) + if(_duration) + QDEL_IN(src, _duration) + +/obj/effect/dummy/lighting_obj/moblight + name = "mob lighting fx" + +/obj/effect/dummy/lighting_obj/moblight/Initialize(mapload, _color, _range, _power, _duration) + . = ..() + if(!ismob(loc)) + return INITIALIZE_HINT_QDEL diff --git a/code/game/objects/effects/portals.dm b/code/game/objects/effects/portals.dm index 427be3628fcc..56d322a04a97 100644 --- a/code/game/objects/effects/portals.dm +++ b/code/game/objects/effects/portals.dm @@ -1,228 +1,228 @@ - -/proc/create_portal_pair(turf/source, turf/destination, _lifespan = 300, accuracy = 0, newtype = /obj/effect/portal, atmos_link_override) - if(!istype(source) || !istype(destination)) - return - var/turf/actual_destination = get_teleport_turf(destination, accuracy) - var/obj/effect/portal/P1 = new newtype(source, _lifespan, null, FALSE, null, atmos_link_override) - var/obj/effect/portal/P2 = new newtype(actual_destination, _lifespan, P1, TRUE, null, atmos_link_override) - if(!istype(P1)||!istype(P2)) - return - P1.link_portal(P2) - P1.hardlinked = TRUE - return list(P1, P2) - -/obj/effect/portal - name = "portal" - desc = "Looks unstable. Best to test it with the clown." - icon = 'icons/obj/stationobjs.dmi' - icon_state = "portal" - anchored = TRUE - var/mech_sized = FALSE - var/obj/effect/portal/linked - var/hardlinked = TRUE //Requires a linked portal at all times. Destroy if there's no linked portal, if there is destroy it when this one is deleted. - var/teleport_channel = TELEPORT_CHANNEL_BLUESPACE - var/turf/hard_target //For when a portal needs a hard target and isn't to be linked. - var/atmos_link = FALSE //Link source/destination atmos. - var/turf/open/atmos_source //Atmos link source - var/turf/open/atmos_destination //Atmos link destination - var/allow_anchored = FALSE - var/innate_accuracy_penalty = 0 - var/last_effect = 0 - var/force_teleport = FALSE - -/obj/effect/portal/anom - name = "wormhole" - icon = 'icons/obj/objects.dmi' - icon_state = "anom" - layer = RIPPLE_LAYER - mech_sized = TRUE - teleport_channel = TELEPORT_CHANNEL_WORMHOLE - -/obj/effect/portal/Move(newloc) - for(var/T in newloc) - if(istype(T, /obj/effect/portal)) - return FALSE - return ..() - -/obj/effect/portal/attackby(obj/item/W, mob/user, params) - if(user && Adjacent(user)) - user.forceMove(get_turf(src)) - return TRUE - -/obj/effect/portal/Crossed(atom/movable/AM, oldloc, force_stop = 0) - if(force_stop) - return ..() - if(isobserver(AM)) - return ..() - if(linked && (get_turf(oldloc) == get_turf(linked))) - return ..() - if(!teleport(AM)) - return ..() - -/obj/effect/portal/attack_tk(mob/user) - return - -/obj/effect/portal/attack_hand(mob/user) - . = ..() - if(.) - return - if(get_turf(user) == get_turf(src)) - teleport(user) - if(Adjacent(user)) - user.forceMove(get_turf(src)) - -/obj/effect/portal/Initialize(mapload, _lifespan = 0, obj/effect/portal/_linked, automatic_link = FALSE, turf/hard_target_override, atmos_link_override) - . = ..() - GLOB.portals += src - if(!istype(_linked) && automatic_link) - . = INITIALIZE_HINT_QDEL - CRASH("Somebody fucked up.") - if(_lifespan > 0) - QDEL_IN(src, _lifespan) - if(!isnull(atmos_link_override)) - atmos_link = atmos_link_override - link_portal(_linked) - hardlinked = automatic_link - if(isturf(hard_target_override)) - hard_target = hard_target_override - -/obj/effect/portal/singularity_pull() - return - -/obj/effect/portal/singularity_act() - return - -/obj/effect/portal/proc/link_portal(obj/effect/portal/newlink) - linked = newlink - if(atmos_link) - link_atmos() - -/obj/effect/portal/proc/link_atmos() - if(atmos_source || atmos_destination) - unlink_atmos() - if(!isopenturf(get_turf(src))) - return FALSE - if(linked) - if(isopenturf(get_turf(linked))) - atmos_source = get_turf(src) - atmos_destination = get_turf(linked) - else if(hard_target) - if(isopenturf(hard_target)) - atmos_source = get_turf(src) - atmos_destination = hard_target - else - return FALSE - if(!istype(atmos_source) || !istype(atmos_destination)) - return FALSE - LAZYINITLIST(atmos_source.atmos_adjacent_turfs) - LAZYINITLIST(atmos_destination.atmos_adjacent_turfs) - if(atmos_source.atmos_adjacent_turfs[atmos_destination] || atmos_destination.atmos_adjacent_turfs[atmos_source]) //Already linked! - return FALSE - atmos_source.atmos_adjacent_turfs[atmos_destination] = TRUE - atmos_destination.atmos_adjacent_turfs[atmos_source] = TRUE - atmos_source.air_update_turf(FALSE) - atmos_destination.air_update_turf(FALSE) - -/obj/effect/portal/proc/unlink_atmos() - if(istype(atmos_source)) - if(istype(atmos_destination) && !atmos_source.Adjacent(atmos_destination) && !CANATMOSPASS(atmos_destination, atmos_source)) - LAZYREMOVE(atmos_source.atmos_adjacent_turfs, atmos_destination) - atmos_source = null - if(istype(atmos_destination)) - if(istype(atmos_source) && !atmos_destination.Adjacent(atmos_source) && !CANATMOSPASS(atmos_source, atmos_destination)) - LAZYREMOVE(atmos_destination.atmos_adjacent_turfs, atmos_source) - atmos_destination = null - -/obj/effect/portal/Destroy() - GLOB.portals -= src - unlink_atmos() - if(hardlinked && !QDELETED(linked)) - QDEL_NULL(linked) - else - linked = null - return ..() - -/obj/effect/portal/attack_ghost(mob/dead/observer/O) - if(!teleport(O, TRUE)) - return ..() - -/obj/effect/portal/proc/teleport(atom/movable/M, force = FALSE) - if(!force && (!istype(M) || iseffect(M) || (ismecha(M) && !mech_sized) || (!isobj(M) && !ismob(M)))) //Things that shouldn't teleport. - return - var/turf/real_target = get_link_target_turf() - if(!istype(real_target)) - return FALSE - if(!force && (!ismecha(M) && !istype(M, /obj/projectile) && M.anchored && !allow_anchored)) - return - if(ismegafauna(M)) - message_admins("[M] has used a portal at [ADMIN_VERBOSEJMP(src)] made by [usr].") - var/no_effect = FALSE - if(last_effect == world.time) - no_effect = TRUE - else - last_effect = world.time - if(do_teleport(M, real_target, innate_accuracy_penalty, no_effects = no_effect, channel = teleport_channel, forced = force_teleport)) - if(istype(M, /obj/projectile)) - var/obj/projectile/P = M - P.ignore_source_check = TRUE - return TRUE - return FALSE - -/obj/effect/portal/proc/get_link_target_turf() - var/turf/real_target - if(!istype(linked) || QDELETED(linked)) - if(hardlinked) - qdel(src) - if(!istype(hard_target) || QDELETED(hard_target)) - hard_target = null - return - else - real_target = hard_target - linked = null - else - real_target = get_turf(linked) - return real_target - -/obj/effect/portal/permanent - name = "permanent portal" - desc = "An unwavering portal that will never fade." - hardlinked = FALSE // dont qdel my portal nerd - force_teleport = TRUE // force teleports because they're a mapmaker tool - var/id // var edit or set id in map editor - -/obj/effect/portal/permanent/proc/set_linked() - if(!id) - return - for(var/obj/effect/portal/permanent/P in GLOB.portals - src) - if(P.id == id) - P.linked = src - linked = P - break - -/obj/effect/portal/permanent/teleport(atom/movable/M, force = FALSE) - set_linked() // update portal links - . = ..() - -/obj/effect/portal/permanent/one_way // doesn't have a return portal, can have multiple exits, /obj/effect/landmark/portal_exit to mark them - name = "one-way portal" - desc = "You get the feeling that this might not be the safest thing you've ever done." - -/obj/effect/portal/permanent/one_way/set_linked() - if(!id) - return - var/list/possible_turfs = list() - for(var/obj/effect/landmark/portal_exit/PE in GLOB.landmarks_list) - if(PE.id == id) - var/turf/T = get_turf(PE) - if(T) - possible_turfs |= T - if(possible_turfs.len) - hard_target = pick(possible_turfs) - -/obj/effect/portal/permanent/one_way/one_use - name = "one-use portal" - desc = "This is probably the worst decision you'll ever make in your life." - -/obj/effect/portal/permanent/one_way/one_use/teleport(atom/movable/M, force = FALSE) - . = ..() - qdel(src) + +/proc/create_portal_pair(turf/source, turf/destination, _lifespan = 300, accuracy = 0, newtype = /obj/effect/portal, atmos_link_override) + if(!istype(source) || !istype(destination)) + return + var/turf/actual_destination = get_teleport_turf(destination, accuracy) + var/obj/effect/portal/P1 = new newtype(source, _lifespan, null, FALSE, null, atmos_link_override) + var/obj/effect/portal/P2 = new newtype(actual_destination, _lifespan, P1, TRUE, null, atmos_link_override) + if(!istype(P1)||!istype(P2)) + return + P1.link_portal(P2) + P1.hardlinked = TRUE + return list(P1, P2) + +/obj/effect/portal + name = "portal" + desc = "Looks unstable. Best to test it with the clown." + icon = 'icons/obj/stationobjs.dmi' + icon_state = "portal" + anchored = TRUE + var/mech_sized = FALSE + var/obj/effect/portal/linked + var/hardlinked = TRUE //Requires a linked portal at all times. Destroy if there's no linked portal, if there is destroy it when this one is deleted. + var/teleport_channel = TELEPORT_CHANNEL_BLUESPACE + var/turf/hard_target //For when a portal needs a hard target and isn't to be linked. + var/atmos_link = FALSE //Link source/destination atmos. + var/turf/open/atmos_source //Atmos link source + var/turf/open/atmos_destination //Atmos link destination + var/allow_anchored = FALSE + var/innate_accuracy_penalty = 0 + var/last_effect = 0 + var/force_teleport = FALSE + +/obj/effect/portal/anom + name = "wormhole" + icon = 'icons/obj/objects.dmi' + icon_state = "anom" + layer = RIPPLE_LAYER + mech_sized = TRUE + teleport_channel = TELEPORT_CHANNEL_WORMHOLE + +/obj/effect/portal/Move(newloc) + for(var/T in newloc) + if(istype(T, /obj/effect/portal)) + return FALSE + return ..() + +/obj/effect/portal/attackby(obj/item/W, mob/user, params) + if(user && Adjacent(user)) + user.forceMove(get_turf(src)) + return TRUE + +/obj/effect/portal/Crossed(atom/movable/AM, oldloc, force_stop = 0) + if(force_stop) + return ..() + if(isobserver(AM)) + return ..() + if(linked && (get_turf(oldloc) == get_turf(linked))) + return ..() + if(!teleport(AM)) + return ..() + +/obj/effect/portal/attack_tk(mob/user) + return + +/obj/effect/portal/attack_hand(mob/user) + . = ..() + if(.) + return + if(get_turf(user) == get_turf(src)) + teleport(user) + if(Adjacent(user)) + user.forceMove(get_turf(src)) + +/obj/effect/portal/Initialize(mapload, _lifespan = 0, obj/effect/portal/_linked, automatic_link = FALSE, turf/hard_target_override, atmos_link_override) + . = ..() + GLOB.portals += src + if(!istype(_linked) && automatic_link) + . = INITIALIZE_HINT_QDEL + CRASH("Somebody fucked up.") + if(_lifespan > 0) + QDEL_IN(src, _lifespan) + if(!isnull(atmos_link_override)) + atmos_link = atmos_link_override + link_portal(_linked) + hardlinked = automatic_link + if(isturf(hard_target_override)) + hard_target = hard_target_override + +/obj/effect/portal/singularity_pull() + return + +/obj/effect/portal/singularity_act() + return + +/obj/effect/portal/proc/link_portal(obj/effect/portal/newlink) + linked = newlink + if(atmos_link) + link_atmos() + +/obj/effect/portal/proc/link_atmos() + if(atmos_source || atmos_destination) + unlink_atmos() + if(!isopenturf(get_turf(src))) + return FALSE + if(linked) + if(isopenturf(get_turf(linked))) + atmos_source = get_turf(src) + atmos_destination = get_turf(linked) + else if(hard_target) + if(isopenturf(hard_target)) + atmos_source = get_turf(src) + atmos_destination = hard_target + else + return FALSE + if(!istype(atmos_source) || !istype(atmos_destination)) + return FALSE + LAZYINITLIST(atmos_source.atmos_adjacent_turfs) + LAZYINITLIST(atmos_destination.atmos_adjacent_turfs) + if(atmos_source.atmos_adjacent_turfs[atmos_destination] || atmos_destination.atmos_adjacent_turfs[atmos_source]) //Already linked! + return FALSE + atmos_source.atmos_adjacent_turfs[atmos_destination] = TRUE + atmos_destination.atmos_adjacent_turfs[atmos_source] = TRUE + atmos_source.air_update_turf(FALSE) + atmos_destination.air_update_turf(FALSE) + +/obj/effect/portal/proc/unlink_atmos() + if(istype(atmos_source)) + if(istype(atmos_destination) && !atmos_source.Adjacent(atmos_destination) && !CANATMOSPASS(atmos_destination, atmos_source)) + LAZYREMOVE(atmos_source.atmos_adjacent_turfs, atmos_destination) + atmos_source = null + if(istype(atmos_destination)) + if(istype(atmos_source) && !atmos_destination.Adjacent(atmos_source) && !CANATMOSPASS(atmos_source, atmos_destination)) + LAZYREMOVE(atmos_destination.atmos_adjacent_turfs, atmos_source) + atmos_destination = null + +/obj/effect/portal/Destroy() + GLOB.portals -= src + unlink_atmos() + if(hardlinked && !QDELETED(linked)) + QDEL_NULL(linked) + else + linked = null + return ..() + +/obj/effect/portal/attack_ghost(mob/dead/observer/O) + if(!teleport(O, TRUE)) + return ..() + +/obj/effect/portal/proc/teleport(atom/movable/M, force = FALSE) + if(!force && (!istype(M) || iseffect(M) || (ismecha(M) && !mech_sized) || (!isobj(M) && !ismob(M)))) //Things that shouldn't teleport. + return + var/turf/real_target = get_link_target_turf() + if(!istype(real_target)) + return FALSE + if(!force && (!ismecha(M) && !istype(M, /obj/projectile) && M.anchored && !allow_anchored)) + return + if(ismegafauna(M)) + message_admins("[M] has used a portal at [ADMIN_VERBOSEJMP(src)] made by [usr].") + var/no_effect = FALSE + if(last_effect == world.time) + no_effect = TRUE + else + last_effect = world.time + if(do_teleport(M, real_target, innate_accuracy_penalty, no_effects = no_effect, channel = teleport_channel, forced = force_teleport)) + if(istype(M, /obj/projectile)) + var/obj/projectile/P = M + P.ignore_source_check = TRUE + return TRUE + return FALSE + +/obj/effect/portal/proc/get_link_target_turf() + var/turf/real_target + if(!istype(linked) || QDELETED(linked)) + if(hardlinked) + qdel(src) + if(!istype(hard_target) || QDELETED(hard_target)) + hard_target = null + return + else + real_target = hard_target + linked = null + else + real_target = get_turf(linked) + return real_target + +/obj/effect/portal/permanent + name = "permanent portal" + desc = "An unwavering portal that will never fade." + hardlinked = FALSE // dont qdel my portal nerd + force_teleport = TRUE // force teleports because they're a mapmaker tool + var/id // var edit or set id in map editor + +/obj/effect/portal/permanent/proc/set_linked() + if(!id) + return + for(var/obj/effect/portal/permanent/P in GLOB.portals - src) + if(P.id == id) + P.linked = src + linked = P + break + +/obj/effect/portal/permanent/teleport(atom/movable/M, force = FALSE) + set_linked() // update portal links + . = ..() + +/obj/effect/portal/permanent/one_way // doesn't have a return portal, can have multiple exits, /obj/effect/landmark/portal_exit to mark them + name = "one-way portal" + desc = "You get the feeling that this might not be the safest thing you've ever done." + +/obj/effect/portal/permanent/one_way/set_linked() + if(!id) + return + var/list/possible_turfs = list() + for(var/obj/effect/landmark/portal_exit/PE in GLOB.landmarks_list) + if(PE.id == id) + var/turf/T = get_turf(PE) + if(T) + possible_turfs |= T + if(possible_turfs.len) + hard_target = pick(possible_turfs) + +/obj/effect/portal/permanent/one_way/one_use + name = "one-use portal" + desc = "This is probably the worst decision you'll ever make in your life." + +/obj/effect/portal/permanent/one_way/one_use/teleport(atom/movable/M, force = FALSE) + . = ..() + qdel(src) diff --git a/code/game/objects/effects/proximity.dm b/code/game/objects/effects/proximity.dm index 904022b1a89c..827890164eb1 100644 --- a/code/game/objects/effects/proximity.dm +++ b/code/game/objects/effects/proximity.dm @@ -1,117 +1,117 @@ -/datum/proximity_monitor - var/atom/host //the atom we are tracking - var/atom/hasprox_receiver //the atom that will receive HasProximity calls. - var/atom/last_host_loc - var/list/checkers //list of /obj/effect/abstract/proximity_checkers - var/current_range - var/ignore_if_not_on_turf //don't check turfs in range if the host's loc isn't a turf - var/wire = FALSE - -/datum/proximity_monitor/New(atom/_host, range, _ignore_if_not_on_turf = TRUE) - checkers = list() - last_host_loc = _host.loc - ignore_if_not_on_turf = _ignore_if_not_on_turf - current_range = range - SetHost(_host) - -/datum/proximity_monitor/proc/SetHost(atom/H,atom/R) - if(H == host) - return - if(host) - UnregisterSignal(host, COMSIG_MOVABLE_MOVED) - if(R) - hasprox_receiver = R - else if(hasprox_receiver == host) //Default case - hasprox_receiver = H - host = H - RegisterSignal(host, COMSIG_MOVABLE_MOVED, .proc/HandleMove) - last_host_loc = host.loc - SetRange(current_range,TRUE) - -/datum/proximity_monitor/Destroy() - host = null - last_host_loc = null - hasprox_receiver = null - QDEL_LIST(checkers) - return ..() - -/datum/proximity_monitor/proc/HandleMove() - var/atom/_host = host - var/atom/new_host_loc = _host.loc - if(last_host_loc != new_host_loc) - last_host_loc = new_host_loc //hopefully this won't cause GC issues with containers - var/curr_range = current_range - SetRange(curr_range, TRUE) - if(curr_range) - testing("HasProx: [host] -> [host]") - hasprox_receiver.HasProximity(host) //if we are processing, we're guaranteed to be a movable - -/datum/proximity_monitor/proc/SetRange(range, force_rebuild = FALSE) - if(!force_rebuild && range == current_range) - return FALSE - . = TRUE - - current_range = range - - var/list/checkers_local = checkers - var/old_checkers_len = checkers_local.len - - var/atom/_host = host - - var/atom/loc_to_use = ignore_if_not_on_turf ? _host.loc : get_turf(_host) - if(wire && !isturf(loc_to_use)) //it makes assemblies attached on wires work - loc_to_use = get_turf(loc_to_use) - if(!isturf(loc_to_use)) //only check the host's loc - if(range) - var/obj/effect/abstract/proximity_checker/pc - if(old_checkers_len) - pc = checkers_local[old_checkers_len] - --checkers_local.len - QDEL_LIST(checkers_local) - else - pc = new(loc_to_use, src) - - checkers_local += pc //only check the host's loc - return - - var/list/turfs = RANGE_TURFS(range, loc_to_use) - var/turfs_len = turfs.len - var/old_checkers_used = min(turfs_len, old_checkers_len) - - //reuse what we can - for(var/I in 1 to old_checkers_len) - if(I <= old_checkers_used) - var/obj/effect/abstract/proximity_checker/pc = checkers_local[I] - pc.forceMove(turfs[I]) - else - qdel(checkers_local[I]) //delete the leftovers - - if(old_checkers_len < turfs_len) - //create what we lack - for(var/I in (old_checkers_used + 1) to turfs_len) - checkers_local += new /obj/effect/abstract/proximity_checker(turfs[I], src) - else - checkers_local.Cut(old_checkers_used + 1, old_checkers_len) - -/obj/effect/abstract/proximity_checker - invisibility = INVISIBILITY_ABSTRACT - anchored = TRUE - var/datum/proximity_monitor/monitor - -/obj/effect/abstract/proximity_checker/Initialize(mapload, datum/proximity_monitor/_monitor) - . = ..() - if(_monitor) - monitor = _monitor - else - stack_trace("proximity_checker created without host") - return INITIALIZE_HINT_QDEL - -/obj/effect/abstract/proximity_checker/Destroy() - monitor = null - return ..() - -/obj/effect/abstract/proximity_checker/Crossed(atom/movable/AM) - set waitfor = FALSE - . = ..() - if(monitor && monitor.hasprox_receiver) - monitor.hasprox_receiver.HasProximity(AM) +/datum/proximity_monitor + var/atom/host //the atom we are tracking + var/atom/hasprox_receiver //the atom that will receive HasProximity calls. + var/atom/last_host_loc + var/list/checkers //list of /obj/effect/abstract/proximity_checkers + var/current_range + var/ignore_if_not_on_turf //don't check turfs in range if the host's loc isn't a turf + var/wire = FALSE + +/datum/proximity_monitor/New(atom/_host, range, _ignore_if_not_on_turf = TRUE) + checkers = list() + last_host_loc = _host.loc + ignore_if_not_on_turf = _ignore_if_not_on_turf + current_range = range + SetHost(_host) + +/datum/proximity_monitor/proc/SetHost(atom/H,atom/R) + if(H == host) + return + if(host) + UnregisterSignal(host, COMSIG_MOVABLE_MOVED) + if(R) + hasprox_receiver = R + else if(hasprox_receiver == host) //Default case + hasprox_receiver = H + host = H + RegisterSignal(host, COMSIG_MOVABLE_MOVED, .proc/HandleMove) + last_host_loc = host.loc + SetRange(current_range,TRUE) + +/datum/proximity_monitor/Destroy() + host = null + last_host_loc = null + hasprox_receiver = null + QDEL_LIST(checkers) + return ..() + +/datum/proximity_monitor/proc/HandleMove() + var/atom/_host = host + var/atom/new_host_loc = _host.loc + if(last_host_loc != new_host_loc) + last_host_loc = new_host_loc //hopefully this won't cause GC issues with containers + var/curr_range = current_range + SetRange(curr_range, TRUE) + if(curr_range) + testing("HasProx: [host] -> [host]") + hasprox_receiver.HasProximity(host) //if we are processing, we're guaranteed to be a movable + +/datum/proximity_monitor/proc/SetRange(range, force_rebuild = FALSE) + if(!force_rebuild && range == current_range) + return FALSE + . = TRUE + + current_range = range + + var/list/checkers_local = checkers + var/old_checkers_len = checkers_local.len + + var/atom/_host = host + + var/atom/loc_to_use = ignore_if_not_on_turf ? _host.loc : get_turf(_host) + if(wire && !isturf(loc_to_use)) //it makes assemblies attached on wires work + loc_to_use = get_turf(loc_to_use) + if(!isturf(loc_to_use)) //only check the host's loc + if(range) + var/obj/effect/abstract/proximity_checker/pc + if(old_checkers_len) + pc = checkers_local[old_checkers_len] + --checkers_local.len + QDEL_LIST(checkers_local) + else + pc = new(loc_to_use, src) + + checkers_local += pc //only check the host's loc + return + + var/list/turfs = RANGE_TURFS(range, loc_to_use) + var/turfs_len = turfs.len + var/old_checkers_used = min(turfs_len, old_checkers_len) + + //reuse what we can + for(var/I in 1 to old_checkers_len) + if(I <= old_checkers_used) + var/obj/effect/abstract/proximity_checker/pc = checkers_local[I] + pc.forceMove(turfs[I]) + else + qdel(checkers_local[I]) //delete the leftovers + + if(old_checkers_len < turfs_len) + //create what we lack + for(var/I in (old_checkers_used + 1) to turfs_len) + checkers_local += new /obj/effect/abstract/proximity_checker(turfs[I], src) + else + checkers_local.Cut(old_checkers_used + 1, old_checkers_len) + +/obj/effect/abstract/proximity_checker + invisibility = INVISIBILITY_ABSTRACT + anchored = TRUE + var/datum/proximity_monitor/monitor + +/obj/effect/abstract/proximity_checker/Initialize(mapload, datum/proximity_monitor/_monitor) + . = ..() + if(_monitor) + monitor = _monitor + else + stack_trace("proximity_checker created without host") + return INITIALIZE_HINT_QDEL + +/obj/effect/abstract/proximity_checker/Destroy() + monitor = null + return ..() + +/obj/effect/abstract/proximity_checker/Crossed(atom/movable/AM) + set waitfor = FALSE + . = ..() + if(monitor && monitor.hasprox_receiver) + monitor.hasprox_receiver.HasProximity(AM) diff --git a/code/game/objects/effects/spawners/bombspawner.dm b/code/game/objects/effects/spawners/bombspawner.dm index b267d34430b8..c520c27d0e97 100644 --- a/code/game/objects/effects/spawners/bombspawner.dm +++ b/code/game/objects/effects/spawners/bombspawner.dm @@ -1,65 +1,65 @@ -#define CELSIUS_TO_KELVIN(T_K) ((T_K) + T0C) - -#define OPTIMAL_TEMP_K_PLA_BURN_SCALE(PRESSURE_P,PRESSURE_O,TEMP_O) (((PRESSURE_P) * GLOB.meta_gas_info[/datum/gas/plasma][META_GAS_SPECIFIC_HEAT]) / (((PRESSURE_P) * GLOB.meta_gas_info[/datum/gas/plasma][META_GAS_SPECIFIC_HEAT] + (PRESSURE_O) * GLOB.meta_gas_info[/datum/gas/oxygen][META_GAS_SPECIFIC_HEAT]) / PLASMA_UPPER_TEMPERATURE - (PRESSURE_O) * GLOB.meta_gas_info[/datum/gas/oxygen][META_GAS_SPECIFIC_HEAT] / CELSIUS_TO_KELVIN(TEMP_O))) -#define OPTIMAL_TEMP_K_PLA_BURN_RATIO(PRESSURE_P,PRESSURE_O,TEMP_O) (CELSIUS_TO_KELVIN(TEMP_O) * PLASMA_OXYGEN_FULLBURN * (PRESSURE_P) / (PRESSURE_O)) - -/obj/effect/spawner/newbomb - name = "bomb" - icon = 'icons/mob/screen_gen.dmi' - icon_state = "x" - var/temp_p = 1500 - var/temp_o = 1000 // tank temperatures - var/pressure_p = 10 * ONE_ATMOSPHERE - var/pressure_o = 10 * ONE_ATMOSPHERE //tank pressures - var/assembly_type - -/obj/effect/spawner/newbomb/Initialize() - . = ..() - var/obj/item/transfer_valve/V = new(src.loc) - var/obj/item/tank/internals/plasma/PT = new(V) - var/obj/item/tank/internals/oxygen/OT = new(V) - - PT.air_contents.set_moles(/datum/gas/plasma, pressure_p*PT.volume/(R_IDEAL_GAS_EQUATION*CELSIUS_TO_KELVIN(temp_p))) - PT.air_contents.set_temperature(CELSIUS_TO_KELVIN(temp_p)) - - OT.air_contents.set_moles(/datum/gas/oxygen, pressure_o*OT.volume/(R_IDEAL_GAS_EQUATION*CELSIUS_TO_KELVIN(temp_o))) - OT.air_contents.set_temperature(CELSIUS_TO_KELVIN(temp_o)) - - V.tank_one = PT - V.tank_two = OT - PT.master = V - OT.master = V - - if(assembly_type) - var/obj/item/assembly/A = new assembly_type(V) - V.attached_device = A - A.holder = V - - V.update_icon() - - return INITIALIZE_HINT_QDEL - -/obj/effect/spawner/newbomb/timer/syndicate/Initialize() - temp_p = (OPTIMAL_TEMP_K_PLA_BURN_SCALE(pressure_p, pressure_o, temp_o)/2 + OPTIMAL_TEMP_K_PLA_BURN_RATIO(pressure_p, pressure_o, temp_o)/2) - T0C - . = ..() - -/obj/effect/spawner/newbomb/timer - assembly_type = /obj/item/assembly/timer - -/obj/effect/spawner/newbomb/timer/syndicate - pressure_o = TANK_LEAK_PRESSURE - 1 - temp_o = 20 - - pressure_p = TANK_LEAK_PRESSURE - 1 - -/obj/effect/spawner/newbomb/proximity - assembly_type = /obj/item/assembly/prox_sensor - -/obj/effect/spawner/newbomb/radio - assembly_type = /obj/item/assembly/signaler - - -#undef CELSIUS_TO_KELVIN - -#undef OPTIMAL_TEMP_K_PLA_BURN_SCALE -#undef OPTIMAL_TEMP_K_PLA_BURN_RATIO +#define CELSIUS_TO_KELVIN(T_K) ((T_K) + T0C) + +#define OPTIMAL_TEMP_K_PLA_BURN_SCALE(PRESSURE_P,PRESSURE_O,TEMP_O) (((PRESSURE_P) * GLOB.meta_gas_info[/datum/gas/plasma][META_GAS_SPECIFIC_HEAT]) / (((PRESSURE_P) * GLOB.meta_gas_info[/datum/gas/plasma][META_GAS_SPECIFIC_HEAT] + (PRESSURE_O) * GLOB.meta_gas_info[/datum/gas/oxygen][META_GAS_SPECIFIC_HEAT]) / PLASMA_UPPER_TEMPERATURE - (PRESSURE_O) * GLOB.meta_gas_info[/datum/gas/oxygen][META_GAS_SPECIFIC_HEAT] / CELSIUS_TO_KELVIN(TEMP_O))) +#define OPTIMAL_TEMP_K_PLA_BURN_RATIO(PRESSURE_P,PRESSURE_O,TEMP_O) (CELSIUS_TO_KELVIN(TEMP_O) * PLASMA_OXYGEN_FULLBURN * (PRESSURE_P) / (PRESSURE_O)) + +/obj/effect/spawner/newbomb + name = "bomb" + icon = 'icons/mob/screen_gen.dmi' + icon_state = "x" + var/temp_p = 1500 + var/temp_o = 1000 // tank temperatures + var/pressure_p = 10 * ONE_ATMOSPHERE + var/pressure_o = 10 * ONE_ATMOSPHERE //tank pressures + var/assembly_type + +/obj/effect/spawner/newbomb/Initialize() + . = ..() + var/obj/item/transfer_valve/V = new(src.loc) + var/obj/item/tank/internals/plasma/PT = new(V) + var/obj/item/tank/internals/oxygen/OT = new(V) + + PT.air_contents.set_moles(/datum/gas/plasma, pressure_p*PT.volume/(R_IDEAL_GAS_EQUATION*CELSIUS_TO_KELVIN(temp_p))) + PT.air_contents.set_temperature(CELSIUS_TO_KELVIN(temp_p)) + + OT.air_contents.set_moles(/datum/gas/oxygen, pressure_o*OT.volume/(R_IDEAL_GAS_EQUATION*CELSIUS_TO_KELVIN(temp_o))) + OT.air_contents.set_temperature(CELSIUS_TO_KELVIN(temp_o)) + + V.tank_one = PT + V.tank_two = OT + PT.master = V + OT.master = V + + if(assembly_type) + var/obj/item/assembly/A = new assembly_type(V) + V.attached_device = A + A.holder = V + + V.update_icon() + + return INITIALIZE_HINT_QDEL + +/obj/effect/spawner/newbomb/timer/syndicate/Initialize() + temp_p = (OPTIMAL_TEMP_K_PLA_BURN_SCALE(pressure_p, pressure_o, temp_o)/2 + OPTIMAL_TEMP_K_PLA_BURN_RATIO(pressure_p, pressure_o, temp_o)/2) - T0C + . = ..() + +/obj/effect/spawner/newbomb/timer + assembly_type = /obj/item/assembly/timer + +/obj/effect/spawner/newbomb/timer/syndicate + pressure_o = TANK_LEAK_PRESSURE - 1 + temp_o = 20 + + pressure_p = TANK_LEAK_PRESSURE - 1 + +/obj/effect/spawner/newbomb/proximity + assembly_type = /obj/item/assembly/prox_sensor + +/obj/effect/spawner/newbomb/radio + assembly_type = /obj/item/assembly/signaler + + +#undef CELSIUS_TO_KELVIN + +#undef OPTIMAL_TEMP_K_PLA_BURN_SCALE +#undef OPTIMAL_TEMP_K_PLA_BURN_RATIO diff --git a/code/game/objects/effects/spawners/gibspawner.dm b/code/game/objects/effects/spawners/gibspawner.dm index 739e8237af67..1056ea3a2f8f 100644 --- a/code/game/objects/effects/spawners/gibspawner.dm +++ b/code/game/objects/effects/spawners/gibspawner.dm @@ -1,153 +1,153 @@ - -/obj/effect/gibspawner - icon_state = "gibspawner"// For the map editor - var/sparks = 0 //whether sparks spread - var/virusProb = 20 //the chance for viruses to spread on the gibs - var/gib_mob_type //generate a fake mob to transfer DNA from if we weren't passed a mob. - var/sound_to_play = 'sound/effects/blobattack.ogg' - var/sound_vol = 60 - var/list/gibtypes = list() //typepaths of the gib decals to spawn - var/list/gibamounts = list() //amount to spawn for each gib decal type we'll spawn. - var/list/gibdirections = list() //of lists of possible directions to spread each gib decal type towards. - -/obj/effect/gibspawner/Initialize(mapload, mob/living/source_mob, list/datum/disease/diseases) - . = ..() - - if(gibtypes.len != gibamounts.len) - stack_trace("Gib list amount length mismatch!") - return - if(gibamounts.len != gibdirections.len) - stack_trace("Gib list dir length mismatch!") - return - - var/obj/effect/decal/cleanable/blood/gibs/gib = null - - if(sound_to_play && isnum(sound_vol)) - playsound(src, sound_to_play, sound_vol, TRUE) - - if(sparks) - var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread - s.set_up(2, 1, loc) - s.start() - - - var/list/dna_to_add //find the dna to pass to the spawned gibs. do note this can be null if the mob doesn't have blood. add_blood_DNA() has built in null handling. - if(source_mob && istype(source_mob)) - dna_to_add = source_mob.get_blood_dna_list() //ez pz - else if(gib_mob_type) - var/mob/living/temp_mob = new gib_mob_type(src) //generate a fake mob so that we pull the right type of DNA for the gibs. - dna_to_add = temp_mob.get_blood_dna_list() - qdel(temp_mob) - else - dna_to_add = list("Non-human DNA" = random_blood_type()) //else, generate a random bloodtype for it. - - - for(var/i = 1, i<= gibtypes.len, i++) - if(gibamounts[i]) - for(var/j = 1, j<= gibamounts[i], j++) - var/gibType = gibtypes[i] - gib = new gibType(loc, diseases) - - gib.add_blood_DNA(dna_to_add) - - var/list/directions = gibdirections[i] - if(isturf(loc)) - if(directions.len) - gib.streak(directions) - - return INITIALIZE_HINT_QDEL - - -/obj/effect/gibspawner/generic - gibtypes = list(/obj/effect/decal/cleanable/blood/gibs, /obj/effect/decal/cleanable/blood/gibs, /obj/effect/decal/cleanable/blood/gibs/core) - gibamounts = list(2, 2, 1) - sound_vol = 40 - -/obj/effect/gibspawner/generic/Initialize() - if(!gibdirections.len) - gibdirections = list(list(WEST, NORTHWEST, SOUTHWEST, NORTH),list(EAST, NORTHEAST, SOUTHEAST, SOUTH), list()) - return ..() - -/obj/effect/gibspawner/generic/animal - gib_mob_type = /mob/living/simple_animal/pet - - - -/obj/effect/gibspawner/human - gibtypes = list(/obj/effect/decal/cleanable/blood/gibs/up, /obj/effect/decal/cleanable/blood/gibs/down, /obj/effect/decal/cleanable/blood/gibs, /obj/effect/decal/cleanable/blood/gibs, /obj/effect/decal/cleanable/blood/gibs/body, /obj/effect/decal/cleanable/blood/gibs/limb, /obj/effect/decal/cleanable/blood/gibs/core) - gibamounts = list(1, 1, 1, 1, 1, 1, 1) - gib_mob_type = /mob/living/carbon/human - sound_vol = 50 - -/obj/effect/gibspawner/human/Initialize() - if(!gibdirections.len) - gibdirections = list(list(NORTH, NORTHEAST, NORTHWEST),list(SOUTH, SOUTHEAST, SOUTHWEST),list(WEST, NORTHWEST, SOUTHWEST),list(EAST, NORTHEAST, SOUTHEAST), GLOB.alldirs, GLOB.alldirs, list()) - return ..() - - -/obj/effect/gibspawner/human/bodypartless //only the gibs that don't look like actual full bodyparts (except torso). - gibtypes = list(/obj/effect/decal/cleanable/blood/gibs, /obj/effect/decal/cleanable/blood/gibs/core, /obj/effect/decal/cleanable/blood/gibs, /obj/effect/decal/cleanable/blood/gibs/core, /obj/effect/decal/cleanable/blood/gibs, /obj/effect/decal/cleanable/blood/gibs/torso) - gibamounts = list(1, 1, 1, 1, 1, 1) - -/obj/effect/gibspawner/human/bodypartless/Initialize() - if(!gibdirections.len) - gibdirections = list(list(NORTH, NORTHEAST, NORTHWEST),list(SOUTH, SOUTHEAST, SOUTHWEST),list(WEST, NORTHWEST, SOUTHWEST),list(EAST, NORTHEAST, SOUTHEAST), GLOB.alldirs, list()) - return ..() - - - -/obj/effect/gibspawner/xeno - gibtypes = list(/obj/effect/decal/cleanable/xenoblood/xgibs/up, /obj/effect/decal/cleanable/xenoblood/xgibs/down, /obj/effect/decal/cleanable/xenoblood/xgibs, /obj/effect/decal/cleanable/xenoblood/xgibs, /obj/effect/decal/cleanable/xenoblood/xgibs/body, /obj/effect/decal/cleanable/xenoblood/xgibs/limb, /obj/effect/decal/cleanable/xenoblood/xgibs/core) - gibamounts = list(1, 1, 1, 1, 1, 1, 1) - gib_mob_type = /mob/living/carbon/alien - -/obj/effect/gibspawner/xeno/Initialize() - if(!gibdirections.len) - gibdirections = list(list(NORTH, NORTHEAST, NORTHWEST),list(SOUTH, SOUTHEAST, SOUTHWEST),list(WEST, NORTHWEST, SOUTHWEST),list(EAST, NORTHEAST, SOUTHEAST), GLOB.alldirs, GLOB.alldirs, list()) - return ..() - - -/obj/effect/gibspawner/xeno/bodypartless //only the gibs that don't look like actual full bodyparts (except torso). - gibtypes = list(/obj/effect/decal/cleanable/xenoblood/xgibs, /obj/effect/decal/cleanable/xenoblood/xgibs/core, /obj/effect/decal/cleanable/xenoblood/xgibs, /obj/effect/decal/cleanable/xenoblood/xgibs/core, /obj/effect/decal/cleanable/xenoblood/xgibs, /obj/effect/decal/cleanable/xenoblood/xgibs/torso) - gibamounts = list(1, 1, 1, 1, 1, 1) - - -/obj/effect/gibspawner/xeno/bodypartless/Initialize() - if(!gibdirections.len) - gibdirections = list(list(NORTH, NORTHEAST, NORTHWEST),list(SOUTH, SOUTHEAST, SOUTHWEST),list(WEST, NORTHWEST, SOUTHWEST),list(EAST, NORTHEAST, SOUTHEAST), GLOB.alldirs, list()) - return ..() - - - -/obj/effect/gibspawner/larva - gibtypes = list(/obj/effect/decal/cleanable/xenoblood/xgibs/larva, /obj/effect/decal/cleanable/xenoblood/xgibs/larva, /obj/effect/decal/cleanable/xenoblood/xgibs/larva/body, /obj/effect/decal/cleanable/xenoblood/xgibs/larva/body) - gibamounts = list(1, 1, 1, 1) - gib_mob_type = /mob/living/carbon/alien/larva - -/obj/effect/gibspawner/larva/Initialize() - if(!gibdirections.len) - gibdirections = list(list(NORTH, NORTHEAST, NORTHWEST),list(SOUTH, SOUTHEAST, SOUTHWEST), list(), GLOB.alldirs) - return ..() - -/obj/effect/gibspawner/larva/bodypartless - gibtypes = list(/obj/effect/decal/cleanable/xenoblood/xgibs/larva, /obj/effect/decal/cleanable/xenoblood/xgibs/larva, /obj/effect/decal/cleanable/xenoblood/xgibs/larva) - gibamounts = list(1, 1, 1) - -/obj/effect/gibspawner/larva/bodypartless/Initialize() - if(!gibdirections.len) - gibdirections = list(list(NORTH, NORTHEAST, NORTHWEST),list(SOUTH, SOUTHEAST, SOUTHWEST), list()) - return ..() - - - -/obj/effect/gibspawner/robot - sparks = 1 - gibtypes = list(/obj/effect/decal/cleanable/robot_debris/up, /obj/effect/decal/cleanable/robot_debris/down, /obj/effect/decal/cleanable/robot_debris, /obj/effect/decal/cleanable/robot_debris, /obj/effect/decal/cleanable/robot_debris, /obj/effect/decal/cleanable/robot_debris/limb) - gibamounts = list(1, 1, 1, 1, 1, 1) - gib_mob_type = /mob/living/silicon - -/obj/effect/gibspawner/robot/Initialize() - if(!gibdirections.len) - gibdirections = list(list(NORTH, NORTHEAST, NORTHWEST),list(SOUTH, SOUTHEAST, SOUTHWEST),list(WEST, NORTHWEST, SOUTHWEST),list(EAST, NORTHEAST, SOUTHEAST), GLOB.alldirs, GLOB.alldirs) - gibamounts[6] = pick(0, 1, 2) - return ..() + +/obj/effect/gibspawner + icon_state = "gibspawner"// For the map editor + var/sparks = 0 //whether sparks spread + var/virusProb = 20 //the chance for viruses to spread on the gibs + var/gib_mob_type //generate a fake mob to transfer DNA from if we weren't passed a mob. + var/sound_to_play = 'sound/effects/blobattack.ogg' + var/sound_vol = 60 + var/list/gibtypes = list() //typepaths of the gib decals to spawn + var/list/gibamounts = list() //amount to spawn for each gib decal type we'll spawn. + var/list/gibdirections = list() //of lists of possible directions to spread each gib decal type towards. + +/obj/effect/gibspawner/Initialize(mapload, mob/living/source_mob, list/datum/disease/diseases) + . = ..() + + if(gibtypes.len != gibamounts.len) + stack_trace("Gib list amount length mismatch!") + return + if(gibamounts.len != gibdirections.len) + stack_trace("Gib list dir length mismatch!") + return + + var/obj/effect/decal/cleanable/blood/gibs/gib = null + + if(sound_to_play && isnum(sound_vol)) + playsound(src, sound_to_play, sound_vol, TRUE) + + if(sparks) + var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread + s.set_up(2, 1, loc) + s.start() + + + var/list/dna_to_add //find the dna to pass to the spawned gibs. do note this can be null if the mob doesn't have blood. add_blood_DNA() has built in null handling. + if(source_mob && istype(source_mob)) + dna_to_add = source_mob.get_blood_dna_list() //ez pz + else if(gib_mob_type) + var/mob/living/temp_mob = new gib_mob_type(src) //generate a fake mob so that we pull the right type of DNA for the gibs. + dna_to_add = temp_mob.get_blood_dna_list() + qdel(temp_mob) + else + dna_to_add = list("Non-human DNA" = random_blood_type()) //else, generate a random bloodtype for it. + + + for(var/i = 1, i<= gibtypes.len, i++) + if(gibamounts[i]) + for(var/j = 1, j<= gibamounts[i], j++) + var/gibType = gibtypes[i] + gib = new gibType(loc, diseases) + + gib.add_blood_DNA(dna_to_add) + + var/list/directions = gibdirections[i] + if(isturf(loc)) + if(directions.len) + gib.streak(directions) + + return INITIALIZE_HINT_QDEL + + +/obj/effect/gibspawner/generic + gibtypes = list(/obj/effect/decal/cleanable/blood/gibs, /obj/effect/decal/cleanable/blood/gibs, /obj/effect/decal/cleanable/blood/gibs/core) + gibamounts = list(2, 2, 1) + sound_vol = 40 + +/obj/effect/gibspawner/generic/Initialize() + if(!gibdirections.len) + gibdirections = list(list(WEST, NORTHWEST, SOUTHWEST, NORTH),list(EAST, NORTHEAST, SOUTHEAST, SOUTH), list()) + return ..() + +/obj/effect/gibspawner/generic/animal + gib_mob_type = /mob/living/simple_animal/pet + + + +/obj/effect/gibspawner/human + gibtypes = list(/obj/effect/decal/cleanable/blood/gibs/up, /obj/effect/decal/cleanable/blood/gibs/down, /obj/effect/decal/cleanable/blood/gibs, /obj/effect/decal/cleanable/blood/gibs, /obj/effect/decal/cleanable/blood/gibs/body, /obj/effect/decal/cleanable/blood/gibs/limb, /obj/effect/decal/cleanable/blood/gibs/core) + gibamounts = list(1, 1, 1, 1, 1, 1, 1) + gib_mob_type = /mob/living/carbon/human + sound_vol = 50 + +/obj/effect/gibspawner/human/Initialize() + if(!gibdirections.len) + gibdirections = list(list(NORTH, NORTHEAST, NORTHWEST),list(SOUTH, SOUTHEAST, SOUTHWEST),list(WEST, NORTHWEST, SOUTHWEST),list(EAST, NORTHEAST, SOUTHEAST), GLOB.alldirs, GLOB.alldirs, list()) + return ..() + + +/obj/effect/gibspawner/human/bodypartless //only the gibs that don't look like actual full bodyparts (except torso). + gibtypes = list(/obj/effect/decal/cleanable/blood/gibs, /obj/effect/decal/cleanable/blood/gibs/core, /obj/effect/decal/cleanable/blood/gibs, /obj/effect/decal/cleanable/blood/gibs/core, /obj/effect/decal/cleanable/blood/gibs, /obj/effect/decal/cleanable/blood/gibs/torso) + gibamounts = list(1, 1, 1, 1, 1, 1) + +/obj/effect/gibspawner/human/bodypartless/Initialize() + if(!gibdirections.len) + gibdirections = list(list(NORTH, NORTHEAST, NORTHWEST),list(SOUTH, SOUTHEAST, SOUTHWEST),list(WEST, NORTHWEST, SOUTHWEST),list(EAST, NORTHEAST, SOUTHEAST), GLOB.alldirs, list()) + return ..() + + + +/obj/effect/gibspawner/xeno + gibtypes = list(/obj/effect/decal/cleanable/xenoblood/xgibs/up, /obj/effect/decal/cleanable/xenoblood/xgibs/down, /obj/effect/decal/cleanable/xenoblood/xgibs, /obj/effect/decal/cleanable/xenoblood/xgibs, /obj/effect/decal/cleanable/xenoblood/xgibs/body, /obj/effect/decal/cleanable/xenoblood/xgibs/limb, /obj/effect/decal/cleanable/xenoblood/xgibs/core) + gibamounts = list(1, 1, 1, 1, 1, 1, 1) + gib_mob_type = /mob/living/carbon/alien + +/obj/effect/gibspawner/xeno/Initialize() + if(!gibdirections.len) + gibdirections = list(list(NORTH, NORTHEAST, NORTHWEST),list(SOUTH, SOUTHEAST, SOUTHWEST),list(WEST, NORTHWEST, SOUTHWEST),list(EAST, NORTHEAST, SOUTHEAST), GLOB.alldirs, GLOB.alldirs, list()) + return ..() + + +/obj/effect/gibspawner/xeno/bodypartless //only the gibs that don't look like actual full bodyparts (except torso). + gibtypes = list(/obj/effect/decal/cleanable/xenoblood/xgibs, /obj/effect/decal/cleanable/xenoblood/xgibs/core, /obj/effect/decal/cleanable/xenoblood/xgibs, /obj/effect/decal/cleanable/xenoblood/xgibs/core, /obj/effect/decal/cleanable/xenoblood/xgibs, /obj/effect/decal/cleanable/xenoblood/xgibs/torso) + gibamounts = list(1, 1, 1, 1, 1, 1) + + +/obj/effect/gibspawner/xeno/bodypartless/Initialize() + if(!gibdirections.len) + gibdirections = list(list(NORTH, NORTHEAST, NORTHWEST),list(SOUTH, SOUTHEAST, SOUTHWEST),list(WEST, NORTHWEST, SOUTHWEST),list(EAST, NORTHEAST, SOUTHEAST), GLOB.alldirs, list()) + return ..() + + + +/obj/effect/gibspawner/larva + gibtypes = list(/obj/effect/decal/cleanable/xenoblood/xgibs/larva, /obj/effect/decal/cleanable/xenoblood/xgibs/larva, /obj/effect/decal/cleanable/xenoblood/xgibs/larva/body, /obj/effect/decal/cleanable/xenoblood/xgibs/larva/body) + gibamounts = list(1, 1, 1, 1) + gib_mob_type = /mob/living/carbon/alien/larva + +/obj/effect/gibspawner/larva/Initialize() + if(!gibdirections.len) + gibdirections = list(list(NORTH, NORTHEAST, NORTHWEST),list(SOUTH, SOUTHEAST, SOUTHWEST), list(), GLOB.alldirs) + return ..() + +/obj/effect/gibspawner/larva/bodypartless + gibtypes = list(/obj/effect/decal/cleanable/xenoblood/xgibs/larva, /obj/effect/decal/cleanable/xenoblood/xgibs/larva, /obj/effect/decal/cleanable/xenoblood/xgibs/larva) + gibamounts = list(1, 1, 1) + +/obj/effect/gibspawner/larva/bodypartless/Initialize() + if(!gibdirections.len) + gibdirections = list(list(NORTH, NORTHEAST, NORTHWEST),list(SOUTH, SOUTHEAST, SOUTHWEST), list()) + return ..() + + + +/obj/effect/gibspawner/robot + sparks = 1 + gibtypes = list(/obj/effect/decal/cleanable/robot_debris/up, /obj/effect/decal/cleanable/robot_debris/down, /obj/effect/decal/cleanable/robot_debris, /obj/effect/decal/cleanable/robot_debris, /obj/effect/decal/cleanable/robot_debris, /obj/effect/decal/cleanable/robot_debris/limb) + gibamounts = list(1, 1, 1, 1, 1, 1) + gib_mob_type = /mob/living/silicon + +/obj/effect/gibspawner/robot/Initialize() + if(!gibdirections.len) + gibdirections = list(list(NORTH, NORTHEAST, NORTHWEST),list(SOUTH, SOUTHEAST, SOUTHWEST),list(WEST, NORTHWEST, SOUTHWEST),list(EAST, NORTHEAST, SOUTHEAST), GLOB.alldirs, GLOB.alldirs) + gibamounts[6] = pick(0, 1, 2) + return ..() diff --git a/code/game/objects/effects/spawners/lootdrop.dm b/code/game/objects/effects/spawners/lootdrop.dm index fe23df4bd5b4..cb29bdd9a586 100644 --- a/code/game/objects/effects/spawners/lootdrop.dm +++ b/code/game/objects/effects/spawners/lootdrop.dm @@ -1,487 +1,487 @@ -/obj/effect/spawner/lootdrop - icon = 'icons/effects/landmarks_static.dmi' - icon_state = "random_loot" - layer = OBJ_LAYER - var/lootcount = 1 //how many items will be spawned - var/lootdoubles = TRUE //if the same item can be spawned twice - var/list/loot //a list of possible items to spawn e.g. list(/obj/item, /obj/structure, /obj/effect) - var/fan_out_items = FALSE //Whether the items should be distributed to offsets 0,1,-1,2,-2,3,-3.. This overrides pixel_x/y on the spawner itself - -/obj/effect/spawner/lootdrop/Initialize(mapload) - ..() - if(loot && loot.len) - var/loot_spawned = 0 - while((lootcount-loot_spawned) && loot.len) - var/lootspawn = pickweight(loot) - while(islist(lootspawn)) - lootspawn = pickweight(lootspawn) - if(!lootdoubles) - loot.Remove(lootspawn) - - if(lootspawn) - var/atom/movable/spawned_loot = new lootspawn(loc) - if (!fan_out_items) - if (pixel_x != 0) - spawned_loot.pixel_x = pixel_x - if (pixel_y != 0) - spawned_loot.pixel_y = pixel_y - else - if (loot_spawned) - spawned_loot.pixel_x = spawned_loot.pixel_y = ((!(loot_spawned%2)*loot_spawned/2)*-1)+((loot_spawned%2)*(loot_spawned+1)/2*1) - loot_spawned++ - return INITIALIZE_HINT_QDEL - -/obj/effect/spawner/lootdrop/donkpockets - name = "donk pocket box spawner" - lootdoubles = FALSE - - loot = list( - /obj/item/storage/box/donkpockets/donkpocketspicy = 1, - /obj/item/storage/box/donkpockets/donkpocketteriyaki = 1, - /obj/item/storage/box/donkpockets/donkpocketpizza = 1, - /obj/item/storage/box/donkpockets/donkpocketberry = 1, - /obj/item/storage/box/donkpockets/donkpockethonk = 1, - ) - - -/obj/effect/spawner/lootdrop/armory_contraband - name = "armory contraband gun spawner" - lootdoubles = FALSE - - loot = list( - /obj/item/gun/ballistic/automatic/pistol = 8, - /obj/item/gun/ballistic/shotgun/automatic/combat = 5, - /obj/item/gun/ballistic/automatic/pistol/deagle, - /obj/item/gun/ballistic/revolver/mateba - ) - -/obj/effect/spawner/lootdrop/armory_contraband/metastation - loot = list(/obj/item/gun/ballistic/automatic/pistol = 5, - /obj/item/gun/ballistic/shotgun/automatic/combat = 5, - /obj/item/gun/ballistic/automatic/pistol/deagle, - /obj/item/storage/box/syndie_kit/throwing_weapons = 3, - /obj/item/gun/ballistic/revolver/mateba) - -/obj/effect/spawner/lootdrop/armory_contraband/donutstation - loot = list(/obj/item/grenade/clusterbuster/teargas = 5, - /obj/item/gun/ballistic/shotgun/automatic/combat = 5, - /obj/item/bikehorn/golden, - /obj/item/grenade/clusterbuster, - /obj/item/storage/box/syndie_kit/throwing_weapons = 3, - /obj/item/gun/ballistic/revolver/mateba) - -/obj/effect/spawner/lootdrop/prison_contraband - name = "prison contraband loot spawner" - loot = list(/obj/item/clothing/mask/cigarette/space_cigarette = 4, - /obj/item/clothing/mask/cigarette/robust = 2, - /obj/item/clothing/mask/cigarette/carp = 3, - /obj/item/clothing/mask/cigarette/uplift = 2, - /obj/item/clothing/mask/cigarette/dromedary = 3, - /obj/item/clothing/mask/cigarette/robustgold = 1, - /obj/item/storage/fancy/cigarettes/cigpack_uplift = 3, - /obj/item/storage/fancy/cigarettes = 3, - /obj/item/clothing/mask/cigarette/rollie/cannabis = 4, - /obj/item/toy/crayon/spraycan = 2, - /obj/item/crowbar = 1, - /obj/item/assembly/flash/handheld = 1, - /obj/item/restraints/handcuffs/cable/zipties = 1, - /obj/item/restraints/handcuffs = 1, - /obj/item/radio/off = 1, - /obj/item/lighter = 3, - /obj/item/storage/box/matches = 3, - /obj/item/reagent_containers/syringe/contraband/space_drugs = 1, - /obj/item/reagent_containers/syringe/contraband/krokodil = 1, - /obj/item/reagent_containers/syringe/contraband/crank = 1, - /obj/item/reagent_containers/syringe/contraband/methamphetamine = 1, - /obj/item/reagent_containers/syringe/contraband/bath_salts = 1, - /obj/item/reagent_containers/syringe/contraband/fentanyl = 1, - /obj/item/reagent_containers/syringe/contraband/morphine = 1, - /obj/item/storage/pill_bottle/happy = 1, - /obj/item/storage/pill_bottle/lsd = 1, - /obj/item/storage/pill_bottle/psicodine = 1, - /obj/item/reagent_containers/food/drinks/beer = 4, - /obj/item/reagent_containers/food/drinks/bottle/whiskey = 1, - /obj/item/paper/fluff/jobs/prisoner/letter = 1, - /obj/item/grenade/smokebomb = 1, - /obj/item/flashlight/seclite = 1, - /obj/item/tailclub = 1, //want to buy makeshift wooden club sprite - /obj/item/kitchen/knife/shiv = 4, - /obj/item/kitchen/knife/shiv/carrot = 1, - /obj/item/kitchen/knife = 1, - /obj/item/storage/wallet/random = 1, - /obj/item/pda = 1 - ) - -/obj/effect/spawner/lootdrop/gambling - name = "gambling valuables spawner" - loot = list( - /obj/item/gun/ballistic/revolver/russian = 5, - /obj/item/clothing/head/ushanka = 3, - /obj/item/storage/box/syndie_kit/throwing_weapons, - /obj/item/coin/gold, - /obj/item/reagent_containers/food/drinks/bottle/vodka/badminka, - ) - -/obj/effect/spawner/lootdrop/grille_or_trash - name = "maint grille or trash spawner" - loot = list(/obj/structure/grille = 5, - /obj/item/cigbutt = 1, - /obj/item/trash/cheesie = 1, - /obj/item/trash/candy = 1, - /obj/item/trash/chips = 1, - /obj/item/reagent_containers/food/snacks/deadmouse = 1, - /obj/item/trash/pistachios = 1, - /obj/item/trash/plate = 1, - /obj/item/trash/popcorn = 1, - /obj/item/trash/raisins = 1, - /obj/item/trash/sosjerky = 1, - /obj/item/trash/syndi_cakes = 1) - -/obj/effect/spawner/lootdrop/three_course_meal - name = "three course meal spawner" - lootcount = 3 - lootdoubles = FALSE - var/soups = list( - /obj/item/reagent_containers/food/snacks/soup/beet, - /obj/item/reagent_containers/food/snacks/soup/sweetpotato, - /obj/item/reagent_containers/food/snacks/soup/stew, - /obj/item/reagent_containers/food/snacks/soup/hotchili, - /obj/item/reagent_containers/food/snacks/soup/nettle, - /obj/item/reagent_containers/food/snacks/soup/meatball) - var/salads = list( - /obj/item/reagent_containers/food/snacks/salad/herbsalad, - /obj/item/reagent_containers/food/snacks/salad/validsalad, - /obj/item/reagent_containers/food/snacks/salad/fruit, - /obj/item/reagent_containers/food/snacks/salad/jungle, - /obj/item/reagent_containers/food/snacks/salad/aesirsalad) - var/mains = list( - /obj/item/reagent_containers/food/snacks/bearsteak, - /obj/item/reagent_containers/food/snacks/enchiladas, - /obj/item/reagent_containers/food/snacks/stewedsoymeat, - /obj/item/reagent_containers/food/snacks/burger/bigbite, - /obj/item/reagent_containers/food/snacks/burger/superbite, - /obj/item/reagent_containers/food/snacks/burger/fivealarm) - -/obj/effect/spawner/lootdrop/three_course_meal/Initialize(mapload) - loot = list(pick(soups) = 1,pick(salads) = 1,pick(mains) = 1) - . = ..() - -/obj/effect/spawner/lootdrop/maintenance - name = "maintenance loot spawner" - // see code/_globalvars/lists/maintenance_loot.dm for loot table - -/obj/effect/spawner/lootdrop/maintenance/Initialize(mapload) - loot = GLOB.maintenance_loot - . = ..() - -/obj/effect/spawner/lootdrop/maintenance/two - name = "2 x maintenance loot spawner" - lootcount = 2 - -/obj/effect/spawner/lootdrop/maintenance/three - name = "3 x maintenance loot spawner" - lootcount = 3 - -/obj/effect/spawner/lootdrop/maintenance/four - name = "4 x maintenance loot spawner" - lootcount = 4 - -/obj/effect/spawner/lootdrop/maintenance/five - name = "5 x maintenance loot spawner" - lootcount = 5 - -/obj/effect/spawner/lootdrop/maintenance/six - name = "6 x maintenance loot spawner" - lootcount = 6 - -/obj/effect/spawner/lootdrop/maintenance/seven - name = "7 x maintenance loot spawner" - lootcount = 7 - -/obj/effect/spawner/lootdrop/maintenance/eight - name = "8 x maintenance loot spawner" - lootcount = 8 - -/obj/effect/spawner/lootdrop/crate_spawner - name = "lootcrate spawner" //USE PROMO CODE "SELLOUT" FOR 20% OFF! - lootdoubles = FALSE - - loot = list( - /obj/structure/closet/crate/secure/loot = 20, - "" = 80 - ) - -/obj/effect/spawner/lootdrop/organ_spawner - name = "ayylien organ spawner" - loot = list( - /obj/item/organ/heart/gland/electric = 3, - /obj/item/organ/heart/gland/trauma = 4, - /obj/item/organ/heart/gland/egg = 7, - /obj/item/organ/heart/gland/chem = 5, - /obj/item/organ/heart/gland/mindshock = 5, - /obj/item/organ/heart/gland/plasma = 7, - /obj/item/organ/heart/gland/transform = 5, - /obj/item/organ/heart/gland/slime = 4, - /obj/item/organ/heart/gland/spiderman = 5, - /obj/item/organ/heart/gland/ventcrawling = 1, - /obj/item/organ/body_egg/alien_embryo = 1, - /obj/item/organ/regenerative_core = 2) - lootcount = 3 - -/obj/effect/spawner/lootdrop/memeorgans - name = "meme organ spawner" - loot = list( - /obj/item/organ/ears/penguin, - /obj/item/organ/ears/cat, - /obj/item/organ/eyes/moth, - /obj/item/organ/eyes/snail, - /obj/item/organ/tongue/bone, - /obj/item/organ/tongue/fly, - /obj/item/organ/tongue/snail, - /obj/item/organ/tongue/lizard, - /obj/item/organ/tongue/alien, - /obj/item/organ/tongue/ethereal, - /obj/item/organ/tongue/robot, - /obj/item/organ/tongue/zombie, - /obj/item/organ/appendix, - /obj/item/organ/liver/fly, - /obj/item/organ/lungs/plasmaman, - /obj/item/organ/tail/cat, - /obj/item/organ/tail/lizard) - lootcount = 5 - -/obj/effect/spawner/lootdrop/two_percent_xeno_egg_spawner - name = "2% chance xeno egg spawner" - loot = list( - /obj/effect/decal/remains/xeno = 49, - /obj/effect/spawner/xeno_egg_delivery = 1) - -/obj/effect/spawner/lootdrop/costume - name = "random costume spawner" - -/obj/effect/spawner/lootdrop/costume/Initialize() - loot = list() - for(var/path in subtypesof(/obj/effect/spawner/bundle/costume)) - loot[path] = TRUE - . = ..() - -// Minor lootdrops follow - -/obj/effect/spawner/lootdrop/minor/beret_or_rabbitears - name = "beret or rabbit ears spawner" - loot = list( - /obj/item/clothing/head/beret = 1, - /obj/item/clothing/head/rabbitears = 1) - -/obj/effect/spawner/lootdrop/minor/bowler_or_that - name = "bowler or top hat spawner" - loot = list( - /obj/item/clothing/head/bowler = 1, - /obj/item/clothing/head/that = 1) - -/obj/effect/spawner/lootdrop/minor/kittyears_or_rabbitears - name = "kitty ears or rabbit ears spawner" - loot = list( - /obj/item/clothing/head/kitty = 1, - /obj/item/clothing/head/rabbitears = 1) - -/obj/effect/spawner/lootdrop/minor/pirate_or_bandana - name = "pirate hat or bandana spawner" - loot = list( - /obj/item/clothing/head/pirate = 1, - /obj/item/clothing/head/bandana = 1) - -/obj/effect/spawner/lootdrop/minor/twentyfive_percent_cyborg_mask - name = "25% cyborg mask spawner" - loot = list( - /obj/item/clothing/mask/gas/cyborg = 25, - "" = 75) - -/obj/effect/spawner/lootdrop/aimodule_harmless // These shouldn't allow the AI to start butchering people - name = "harmless AI module spawner" - loot = list( - /obj/item/aiModule/core/full/asimov, - /obj/item/aiModule/core/full/asimovpp, - /obj/item/aiModule/core/full/hippocratic, - /obj/item/aiModule/core/full/paladin_devotion, - /obj/item/aiModule/core/full/paladin - ) - -/obj/effect/spawner/lootdrop/aimodule_neutral // These shouldn't allow the AI to start butchering people without reason - name = "neutral AI module spawner" - loot = list( - /obj/item/aiModule/core/full/corp, - /obj/item/aiModule/core/full/maintain, - /obj/item/aiModule/core/full/drone, - /obj/item/aiModule/core/full/peacekeeper, - /obj/item/aiModule/core/full/reporter, - /obj/item/aiModule/core/full/robocop, - /obj/item/aiModule/core/full/liveandletlive, - /obj/item/aiModule/core/full/hulkamania - ) - -/obj/effect/spawner/lootdrop/aimodule_harmful // These will get the shuttle called - name = "harmful AI module spawner" - loot = list( - /obj/item/aiModule/core/full/antimov, - /obj/item/aiModule/core/full/balance, - /obj/item/aiModule/core/full/tyrant, - /obj/item/aiModule/core/full/thermurderdynamic, - /obj/item/aiModule/core/full/damaged, - /obj/item/aiModule/reset/purge - ) - -// Tech storage circuit board spawners - -/obj/effect/spawner/lootdrop/techstorage - name = "generic circuit board spawner" - lootdoubles = FALSE - fan_out_items = TRUE - lootcount = INFINITY - -/obj/effect/spawner/lootdrop/techstorage/service - name = "service circuit board spawner" - loot = list( - /obj/item/circuitboard/computer/arcade/battle, - /obj/item/circuitboard/computer/arcade/orion_trail, - /obj/item/circuitboard/machine/autolathe, - /obj/item/circuitboard/computer/mining, - /obj/item/circuitboard/machine/ore_redemption, - /obj/item/circuitboard/machine/mining_equipment_vendor, - /obj/item/circuitboard/machine/microwave, - /obj/item/circuitboard/machine/chem_dispenser/drinks, - /obj/item/circuitboard/machine/chem_dispenser/drinks/beer, - /obj/item/circuitboard/computer/slot_machine - ) - -/obj/effect/spawner/lootdrop/techstorage/rnd - name = "RnD circuit board spawner" - loot = list( - /obj/item/circuitboard/computer/aifixer, - /obj/item/circuitboard/machine/rdserver, - /obj/item/circuitboard/machine/mechfab, - /obj/item/circuitboard/machine/circuit_imprinter/department, - /obj/item/circuitboard/computer/teleporter, - /obj/item/circuitboard/machine/destructive_analyzer, - /obj/item/circuitboard/computer/rdconsole, - /obj/item/circuitboard/computer/nanite_chamber_control, - /obj/item/circuitboard/computer/nanite_cloud_controller, - /obj/item/circuitboard/machine/nanite_chamber, - /obj/item/circuitboard/machine/nanite_programmer, - /obj/item/circuitboard/machine/nanite_program_hub - ) - -/obj/effect/spawner/lootdrop/techstorage/security - name = "security circuit board spawner" - loot = list( - /obj/item/circuitboard/computer/secure_data, - /obj/item/circuitboard/computer/security, - /obj/item/circuitboard/computer/prisoner - ) - -/obj/effect/spawner/lootdrop/techstorage/engineering - name = "engineering circuit board spawner" - loot = list( - /obj/item/circuitboard/computer/atmos_alert, - /obj/item/circuitboard/computer/stationalert, - /obj/item/circuitboard/computer/powermonitor - ) - -/obj/effect/spawner/lootdrop/techstorage/tcomms - name = "tcomms circuit board spawner" - loot = list( - /obj/item/circuitboard/computer/message_monitor, - /obj/item/circuitboard/machine/telecomms/broadcaster, - /obj/item/circuitboard/machine/telecomms/bus, - /obj/item/circuitboard/machine/telecomms/server, - /obj/item/circuitboard/machine/telecomms/receiver, - /obj/item/circuitboard/machine/telecomms/processor, - /obj/item/circuitboard/machine/announcement_system, - /obj/item/circuitboard/computer/comm_server, - /obj/item/circuitboard/computer/comm_monitor - ) - -/obj/effect/spawner/lootdrop/techstorage/medical - name = "medical circuit board spawner" - loot = list( - /obj/item/circuitboard/machine/chem_dispenser, - /obj/item/circuitboard/computer/scan_consolenew, - /obj/item/circuitboard/computer/med_data, - /obj/item/circuitboard/machine/smoke_machine, - /obj/item/circuitboard/machine/chem_master, - /obj/item/circuitboard/machine/dnascanner, - /obj/item/circuitboard/computer/pandemic - ) - -/obj/effect/spawner/lootdrop/techstorage/AI - name = "secure AI circuit board spawner" - loot = list( - /obj/item/circuitboard/computer/aiupload, - /obj/item/circuitboard/computer/borgupload, - /obj/item/circuitboard/aicore - ) - -/obj/effect/spawner/lootdrop/techstorage/command - name = "secure command circuit board spawner" - loot = list( - /obj/item/circuitboard/computer/crew, - /obj/item/circuitboard/computer/communications, - /obj/item/circuitboard/computer/card - ) - -/obj/effect/spawner/lootdrop/techstorage/RnD_secure - name = "secure RnD circuit board spawner" - loot = list( - /obj/item/circuitboard/computer/mecha_control, - /obj/item/circuitboard/computer/apc_control, - /obj/item/circuitboard/computer/robotics - ) - -/obj/effect/spawner/lootdrop/mafia_outfit - name = "mafia outfit spawner" - loot = list( - /obj/effect/spawner/bundle/costume/mafia = 20, - /obj/effect/spawner/bundle/costume/mafia/white = 5, - /obj/effect/spawner/bundle/costume/mafia/checkered = 2, - /obj/effect/spawner/bundle/costume/mafia/beige = 5 - ) - -//finds the probabilities of items spawning from a loot spawner's loot pool -/obj/item/loot_table_maker - icon = 'icons/effects/landmarks_static.dmi' - icon_state = "random_loot" - var/spawner_to_test = /obj/effect/spawner/lootdrop/maintenance //what lootdrop spawner to use the loot pool of - var/loot_count = 180 //180 is about how much maint loot spawns per map as of 11/14/2019 - //result outputs - var/list/spawned_table //list of all items "spawned" and how many - var/list/stat_table //list of all items "spawned" and their occurrance probability - -/obj/item/loot_table_maker/Initialize() - . = ..() - make_table() - -/obj/item/loot_table_maker/attack_self(mob/user) - to_chat(user, "Loot pool re-rolled.") - make_table() - -/obj/item/loot_table_maker/proc/make_table() - spawned_table = list() - stat_table = list() - var/obj/effect/spawner/lootdrop/spawner_to_table = new spawner_to_test - var/lootpool = spawner_to_table.loot - qdel(spawner_to_table) - for(var/i in 1 to loot_count) - var/loot_spawn = pick_loot(lootpool) - if(!(loot_spawn in spawned_table)) - spawned_table[loot_spawn] = 1 - else - spawned_table[loot_spawn] += 1 - stat_table += spawned_table - for(var/item in stat_table) - stat_table[item] /= loot_count - -/obj/item/loot_table_maker/proc/pick_loot(lootpool) //selects path from loot table and returns it - var/lootspawn = pickweight(lootpool) - while(islist(lootspawn)) - lootspawn = pickweight(lootspawn) - return lootspawn +/obj/effect/spawner/lootdrop + icon = 'icons/effects/landmarks_static.dmi' + icon_state = "random_loot" + layer = OBJ_LAYER + var/lootcount = 1 //how many items will be spawned + var/lootdoubles = TRUE //if the same item can be spawned twice + var/list/loot //a list of possible items to spawn e.g. list(/obj/item, /obj/structure, /obj/effect) + var/fan_out_items = FALSE //Whether the items should be distributed to offsets 0,1,-1,2,-2,3,-3.. This overrides pixel_x/y on the spawner itself + +/obj/effect/spawner/lootdrop/Initialize(mapload) + ..() + if(loot && loot.len) + var/loot_spawned = 0 + while((lootcount-loot_spawned) && loot.len) + var/lootspawn = pickweight(loot) + while(islist(lootspawn)) + lootspawn = pickweight(lootspawn) + if(!lootdoubles) + loot.Remove(lootspawn) + + if(lootspawn) + var/atom/movable/spawned_loot = new lootspawn(loc) + if (!fan_out_items) + if (pixel_x != 0) + spawned_loot.pixel_x = pixel_x + if (pixel_y != 0) + spawned_loot.pixel_y = pixel_y + else + if (loot_spawned) + spawned_loot.pixel_x = spawned_loot.pixel_y = ((!(loot_spawned%2)*loot_spawned/2)*-1)+((loot_spawned%2)*(loot_spawned+1)/2*1) + loot_spawned++ + return INITIALIZE_HINT_QDEL + +/obj/effect/spawner/lootdrop/donkpockets + name = "donk pocket box spawner" + lootdoubles = FALSE + + loot = list( + /obj/item/storage/box/donkpockets/donkpocketspicy = 1, + /obj/item/storage/box/donkpockets/donkpocketteriyaki = 1, + /obj/item/storage/box/donkpockets/donkpocketpizza = 1, + /obj/item/storage/box/donkpockets/donkpocketberry = 1, + /obj/item/storage/box/donkpockets/donkpockethonk = 1, + ) + + +/obj/effect/spawner/lootdrop/armory_contraband + name = "armory contraband gun spawner" + lootdoubles = FALSE + + loot = list( + /obj/item/gun/ballistic/automatic/pistol = 8, + /obj/item/gun/ballistic/shotgun/automatic/combat = 5, + /obj/item/gun/ballistic/automatic/pistol/deagle, + /obj/item/gun/ballistic/revolver/mateba + ) + +/obj/effect/spawner/lootdrop/armory_contraband/metastation + loot = list(/obj/item/gun/ballistic/automatic/pistol = 5, + /obj/item/gun/ballistic/shotgun/automatic/combat = 5, + /obj/item/gun/ballistic/automatic/pistol/deagle, + /obj/item/storage/box/syndie_kit/throwing_weapons = 3, + /obj/item/gun/ballistic/revolver/mateba) + +/obj/effect/spawner/lootdrop/armory_contraband/donutstation + loot = list(/obj/item/grenade/clusterbuster/teargas = 5, + /obj/item/gun/ballistic/shotgun/automatic/combat = 5, + /obj/item/bikehorn/golden, + /obj/item/grenade/clusterbuster, + /obj/item/storage/box/syndie_kit/throwing_weapons = 3, + /obj/item/gun/ballistic/revolver/mateba) + +/obj/effect/spawner/lootdrop/prison_contraband + name = "prison contraband loot spawner" + loot = list(/obj/item/clothing/mask/cigarette/space_cigarette = 4, + /obj/item/clothing/mask/cigarette/robust = 2, + /obj/item/clothing/mask/cigarette/carp = 3, + /obj/item/clothing/mask/cigarette/uplift = 2, + /obj/item/clothing/mask/cigarette/dromedary = 3, + /obj/item/clothing/mask/cigarette/robustgold = 1, + /obj/item/storage/fancy/cigarettes/cigpack_uplift = 3, + /obj/item/storage/fancy/cigarettes = 3, + /obj/item/clothing/mask/cigarette/rollie/cannabis = 4, + /obj/item/toy/crayon/spraycan = 2, + /obj/item/crowbar = 1, + /obj/item/assembly/flash/handheld = 1, + /obj/item/restraints/handcuffs/cable/zipties = 1, + /obj/item/restraints/handcuffs = 1, + /obj/item/radio/off = 1, + /obj/item/lighter = 3, + /obj/item/storage/box/matches = 3, + /obj/item/reagent_containers/syringe/contraband/space_drugs = 1, + /obj/item/reagent_containers/syringe/contraband/krokodil = 1, + /obj/item/reagent_containers/syringe/contraband/crank = 1, + /obj/item/reagent_containers/syringe/contraband/methamphetamine = 1, + /obj/item/reagent_containers/syringe/contraband/bath_salts = 1, + /obj/item/reagent_containers/syringe/contraband/fentanyl = 1, + /obj/item/reagent_containers/syringe/contraband/morphine = 1, + /obj/item/storage/pill_bottle/happy = 1, + /obj/item/storage/pill_bottle/lsd = 1, + /obj/item/storage/pill_bottle/psicodine = 1, + /obj/item/reagent_containers/food/drinks/beer = 4, + /obj/item/reagent_containers/food/drinks/bottle/whiskey = 1, + /obj/item/paper/fluff/jobs/prisoner/letter = 1, + /obj/item/grenade/smokebomb = 1, + /obj/item/flashlight/seclite = 1, + /obj/item/tailclub = 1, //want to buy makeshift wooden club sprite + /obj/item/kitchen/knife/shiv = 4, + /obj/item/kitchen/knife/shiv/carrot = 1, + /obj/item/kitchen/knife = 1, + /obj/item/storage/wallet/random = 1, + /obj/item/pda = 1 + ) + +/obj/effect/spawner/lootdrop/gambling + name = "gambling valuables spawner" + loot = list( + /obj/item/gun/ballistic/revolver/russian = 5, + /obj/item/clothing/head/ushanka = 3, + /obj/item/storage/box/syndie_kit/throwing_weapons, + /obj/item/coin/gold, + /obj/item/reagent_containers/food/drinks/bottle/vodka/badminka, + ) + +/obj/effect/spawner/lootdrop/grille_or_trash + name = "maint grille or trash spawner" + loot = list(/obj/structure/grille = 5, + /obj/item/cigbutt = 1, + /obj/item/trash/cheesie = 1, + /obj/item/trash/candy = 1, + /obj/item/trash/chips = 1, + /obj/item/reagent_containers/food/snacks/deadmouse = 1, + /obj/item/trash/pistachios = 1, + /obj/item/trash/plate = 1, + /obj/item/trash/popcorn = 1, + /obj/item/trash/raisins = 1, + /obj/item/trash/sosjerky = 1, + /obj/item/trash/syndi_cakes = 1) + +/obj/effect/spawner/lootdrop/three_course_meal + name = "three course meal spawner" + lootcount = 3 + lootdoubles = FALSE + var/soups = list( + /obj/item/reagent_containers/food/snacks/soup/beet, + /obj/item/reagent_containers/food/snacks/soup/sweetpotato, + /obj/item/reagent_containers/food/snacks/soup/stew, + /obj/item/reagent_containers/food/snacks/soup/hotchili, + /obj/item/reagent_containers/food/snacks/soup/nettle, + /obj/item/reagent_containers/food/snacks/soup/meatball) + var/salads = list( + /obj/item/reagent_containers/food/snacks/salad/herbsalad, + /obj/item/reagent_containers/food/snacks/salad/validsalad, + /obj/item/reagent_containers/food/snacks/salad/fruit, + /obj/item/reagent_containers/food/snacks/salad/jungle, + /obj/item/reagent_containers/food/snacks/salad/aesirsalad) + var/mains = list( + /obj/item/reagent_containers/food/snacks/bearsteak, + /obj/item/reagent_containers/food/snacks/enchiladas, + /obj/item/reagent_containers/food/snacks/stewedsoymeat, + /obj/item/reagent_containers/food/snacks/burger/bigbite, + /obj/item/reagent_containers/food/snacks/burger/superbite, + /obj/item/reagent_containers/food/snacks/burger/fivealarm) + +/obj/effect/spawner/lootdrop/three_course_meal/Initialize(mapload) + loot = list(pick(soups) = 1,pick(salads) = 1,pick(mains) = 1) + . = ..() + +/obj/effect/spawner/lootdrop/maintenance + name = "maintenance loot spawner" + // see code/_globalvars/lists/maintenance_loot.dm for loot table + +/obj/effect/spawner/lootdrop/maintenance/Initialize(mapload) + loot = GLOB.maintenance_loot + . = ..() + +/obj/effect/spawner/lootdrop/maintenance/two + name = "2 x maintenance loot spawner" + lootcount = 2 + +/obj/effect/spawner/lootdrop/maintenance/three + name = "3 x maintenance loot spawner" + lootcount = 3 + +/obj/effect/spawner/lootdrop/maintenance/four + name = "4 x maintenance loot spawner" + lootcount = 4 + +/obj/effect/spawner/lootdrop/maintenance/five + name = "5 x maintenance loot spawner" + lootcount = 5 + +/obj/effect/spawner/lootdrop/maintenance/six + name = "6 x maintenance loot spawner" + lootcount = 6 + +/obj/effect/spawner/lootdrop/maintenance/seven + name = "7 x maintenance loot spawner" + lootcount = 7 + +/obj/effect/spawner/lootdrop/maintenance/eight + name = "8 x maintenance loot spawner" + lootcount = 8 + +/obj/effect/spawner/lootdrop/crate_spawner + name = "lootcrate spawner" //USE PROMO CODE "SELLOUT" FOR 20% OFF! + lootdoubles = FALSE + + loot = list( + /obj/structure/closet/crate/secure/loot = 20, + "" = 80 + ) + +/obj/effect/spawner/lootdrop/organ_spawner + name = "ayylien organ spawner" + loot = list( + /obj/item/organ/heart/gland/electric = 3, + /obj/item/organ/heart/gland/trauma = 4, + /obj/item/organ/heart/gland/egg = 7, + /obj/item/organ/heart/gland/chem = 5, + /obj/item/organ/heart/gland/mindshock = 5, + /obj/item/organ/heart/gland/plasma = 7, + /obj/item/organ/heart/gland/transform = 5, + /obj/item/organ/heart/gland/slime = 4, + /obj/item/organ/heart/gland/spiderman = 5, + /obj/item/organ/heart/gland/ventcrawling = 1, + /obj/item/organ/body_egg/alien_embryo = 1, + /obj/item/organ/regenerative_core = 2) + lootcount = 3 + +/obj/effect/spawner/lootdrop/memeorgans + name = "meme organ spawner" + loot = list( + /obj/item/organ/ears/penguin, + /obj/item/organ/ears/cat, + /obj/item/organ/eyes/moth, + /obj/item/organ/eyes/snail, + /obj/item/organ/tongue/bone, + /obj/item/organ/tongue/fly, + /obj/item/organ/tongue/snail, + /obj/item/organ/tongue/lizard, + /obj/item/organ/tongue/alien, + /obj/item/organ/tongue/ethereal, + /obj/item/organ/tongue/robot, + /obj/item/organ/tongue/zombie, + /obj/item/organ/appendix, + /obj/item/organ/liver/fly, + /obj/item/organ/lungs/plasmaman, + /obj/item/organ/tail/cat, + /obj/item/organ/tail/lizard) + lootcount = 5 + +/obj/effect/spawner/lootdrop/two_percent_xeno_egg_spawner + name = "2% chance xeno egg spawner" + loot = list( + /obj/effect/decal/remains/xeno = 49, + /obj/effect/spawner/xeno_egg_delivery = 1) + +/obj/effect/spawner/lootdrop/costume + name = "random costume spawner" + +/obj/effect/spawner/lootdrop/costume/Initialize() + loot = list() + for(var/path in subtypesof(/obj/effect/spawner/bundle/costume)) + loot[path] = TRUE + . = ..() + +// Minor lootdrops follow + +/obj/effect/spawner/lootdrop/minor/beret_or_rabbitears + name = "beret or rabbit ears spawner" + loot = list( + /obj/item/clothing/head/beret = 1, + /obj/item/clothing/head/rabbitears = 1) + +/obj/effect/spawner/lootdrop/minor/bowler_or_that + name = "bowler or top hat spawner" + loot = list( + /obj/item/clothing/head/bowler = 1, + /obj/item/clothing/head/that = 1) + +/obj/effect/spawner/lootdrop/minor/kittyears_or_rabbitears + name = "kitty ears or rabbit ears spawner" + loot = list( + /obj/item/clothing/head/kitty = 1, + /obj/item/clothing/head/rabbitears = 1) + +/obj/effect/spawner/lootdrop/minor/pirate_or_bandana + name = "pirate hat or bandana spawner" + loot = list( + /obj/item/clothing/head/pirate = 1, + /obj/item/clothing/head/bandana = 1) + +/obj/effect/spawner/lootdrop/minor/twentyfive_percent_cyborg_mask + name = "25% cyborg mask spawner" + loot = list( + /obj/item/clothing/mask/gas/cyborg = 25, + "" = 75) + +/obj/effect/spawner/lootdrop/aimodule_harmless // These shouldn't allow the AI to start butchering people + name = "harmless AI module spawner" + loot = list( + /obj/item/aiModule/core/full/asimov, + /obj/item/aiModule/core/full/asimovpp, + /obj/item/aiModule/core/full/hippocratic, + /obj/item/aiModule/core/full/paladin_devotion, + /obj/item/aiModule/core/full/paladin + ) + +/obj/effect/spawner/lootdrop/aimodule_neutral // These shouldn't allow the AI to start butchering people without reason + name = "neutral AI module spawner" + loot = list( + /obj/item/aiModule/core/full/corp, + /obj/item/aiModule/core/full/maintain, + /obj/item/aiModule/core/full/drone, + /obj/item/aiModule/core/full/peacekeeper, + /obj/item/aiModule/core/full/reporter, + /obj/item/aiModule/core/full/robocop, + /obj/item/aiModule/core/full/liveandletlive, + /obj/item/aiModule/core/full/hulkamania + ) + +/obj/effect/spawner/lootdrop/aimodule_harmful // These will get the shuttle called + name = "harmful AI module spawner" + loot = list( + /obj/item/aiModule/core/full/antimov, + /obj/item/aiModule/core/full/balance, + /obj/item/aiModule/core/full/tyrant, + /obj/item/aiModule/core/full/thermurderdynamic, + /obj/item/aiModule/core/full/damaged, + /obj/item/aiModule/reset/purge + ) + +// Tech storage circuit board spawners + +/obj/effect/spawner/lootdrop/techstorage + name = "generic circuit board spawner" + lootdoubles = FALSE + fan_out_items = TRUE + lootcount = INFINITY + +/obj/effect/spawner/lootdrop/techstorage/service + name = "service circuit board spawner" + loot = list( + /obj/item/circuitboard/computer/arcade/battle, + /obj/item/circuitboard/computer/arcade/orion_trail, + /obj/item/circuitboard/machine/autolathe, + /obj/item/circuitboard/computer/mining, + /obj/item/circuitboard/machine/ore_redemption, + /obj/item/circuitboard/machine/mining_equipment_vendor, + /obj/item/circuitboard/machine/microwave, + /obj/item/circuitboard/machine/chem_dispenser/drinks, + /obj/item/circuitboard/machine/chem_dispenser/drinks/beer, + /obj/item/circuitboard/computer/slot_machine + ) + +/obj/effect/spawner/lootdrop/techstorage/rnd + name = "RnD circuit board spawner" + loot = list( + /obj/item/circuitboard/computer/aifixer, + /obj/item/circuitboard/machine/rdserver, + /obj/item/circuitboard/machine/mechfab, + /obj/item/circuitboard/machine/circuit_imprinter/department, + /obj/item/circuitboard/computer/teleporter, + /obj/item/circuitboard/machine/destructive_analyzer, + /obj/item/circuitboard/computer/rdconsole, + /obj/item/circuitboard/computer/nanite_chamber_control, + /obj/item/circuitboard/computer/nanite_cloud_controller, + /obj/item/circuitboard/machine/nanite_chamber, + /obj/item/circuitboard/machine/nanite_programmer, + /obj/item/circuitboard/machine/nanite_program_hub + ) + +/obj/effect/spawner/lootdrop/techstorage/security + name = "security circuit board spawner" + loot = list( + /obj/item/circuitboard/computer/secure_data, + /obj/item/circuitboard/computer/security, + /obj/item/circuitboard/computer/prisoner + ) + +/obj/effect/spawner/lootdrop/techstorage/engineering + name = "engineering circuit board spawner" + loot = list( + /obj/item/circuitboard/computer/atmos_alert, + /obj/item/circuitboard/computer/stationalert, + /obj/item/circuitboard/computer/powermonitor + ) + +/obj/effect/spawner/lootdrop/techstorage/tcomms + name = "tcomms circuit board spawner" + loot = list( + /obj/item/circuitboard/computer/message_monitor, + /obj/item/circuitboard/machine/telecomms/broadcaster, + /obj/item/circuitboard/machine/telecomms/bus, + /obj/item/circuitboard/machine/telecomms/server, + /obj/item/circuitboard/machine/telecomms/receiver, + /obj/item/circuitboard/machine/telecomms/processor, + /obj/item/circuitboard/machine/announcement_system, + /obj/item/circuitboard/computer/comm_server, + /obj/item/circuitboard/computer/comm_monitor + ) + +/obj/effect/spawner/lootdrop/techstorage/medical + name = "medical circuit board spawner" + loot = list( + /obj/item/circuitboard/machine/chem_dispenser, + /obj/item/circuitboard/computer/scan_consolenew, + /obj/item/circuitboard/computer/med_data, + /obj/item/circuitboard/machine/smoke_machine, + /obj/item/circuitboard/machine/chem_master, + /obj/item/circuitboard/machine/dnascanner, + /obj/item/circuitboard/computer/pandemic + ) + +/obj/effect/spawner/lootdrop/techstorage/AI + name = "secure AI circuit board spawner" + loot = list( + /obj/item/circuitboard/computer/aiupload, + /obj/item/circuitboard/computer/borgupload, + /obj/item/circuitboard/aicore + ) + +/obj/effect/spawner/lootdrop/techstorage/command + name = "secure command circuit board spawner" + loot = list( + /obj/item/circuitboard/computer/crew, + /obj/item/circuitboard/computer/communications, + /obj/item/circuitboard/computer/card + ) + +/obj/effect/spawner/lootdrop/techstorage/RnD_secure + name = "secure RnD circuit board spawner" + loot = list( + /obj/item/circuitboard/computer/mecha_control, + /obj/item/circuitboard/computer/apc_control, + /obj/item/circuitboard/computer/robotics + ) + +/obj/effect/spawner/lootdrop/mafia_outfit + name = "mafia outfit spawner" + loot = list( + /obj/effect/spawner/bundle/costume/mafia = 20, + /obj/effect/spawner/bundle/costume/mafia/white = 5, + /obj/effect/spawner/bundle/costume/mafia/checkered = 2, + /obj/effect/spawner/bundle/costume/mafia/beige = 5 + ) + +//finds the probabilities of items spawning from a loot spawner's loot pool +/obj/item/loot_table_maker + icon = 'icons/effects/landmarks_static.dmi' + icon_state = "random_loot" + var/spawner_to_test = /obj/effect/spawner/lootdrop/maintenance //what lootdrop spawner to use the loot pool of + var/loot_count = 180 //180 is about how much maint loot spawns per map as of 11/14/2019 + //result outputs + var/list/spawned_table //list of all items "spawned" and how many + var/list/stat_table //list of all items "spawned" and their occurrance probability + +/obj/item/loot_table_maker/Initialize() + . = ..() + make_table() + +/obj/item/loot_table_maker/attack_self(mob/user) + to_chat(user, "Loot pool re-rolled.") + make_table() + +/obj/item/loot_table_maker/proc/make_table() + spawned_table = list() + stat_table = list() + var/obj/effect/spawner/lootdrop/spawner_to_table = new spawner_to_test + var/lootpool = spawner_to_table.loot + qdel(spawner_to_table) + for(var/i in 1 to loot_count) + var/loot_spawn = pick_loot(lootpool) + if(!(loot_spawn in spawned_table)) + spawned_table[loot_spawn] = 1 + else + spawned_table[loot_spawn] += 1 + stat_table += spawned_table + for(var/item in stat_table) + stat_table[item] /= loot_count + +/obj/item/loot_table_maker/proc/pick_loot(lootpool) //selects path from loot table and returns it + var/lootspawn = pickweight(lootpool) + while(islist(lootspawn)) + lootspawn = pickweight(lootspawn) + return lootspawn diff --git a/code/game/objects/effects/spiders.dm b/code/game/objects/effects/spiders.dm index 3ddcdd9730d0..4f6726912734 100644 --- a/code/game/objects/effects/spiders.dm +++ b/code/game/objects/effects/spiders.dm @@ -1,264 +1,264 @@ -//generic procs copied from obj/effect/alien -/obj/structure/spider - name = "web" - icon = 'icons/effects/effects.dmi' - desc = "It's stringy and sticky." - anchored = TRUE - density = FALSE - max_integrity = 15 - - - -/obj/structure/spider/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) - if(damage_type == BURN)//the stickiness of the web mutes all attack sounds except fire damage type - playsound(loc, 'sound/items/welder.ogg', 100, TRUE) - - -/obj/structure/spider/run_obj_armor(damage_amount, damage_type, damage_flag = 0, attack_dir) - if(damage_flag == "melee") - switch(damage_type) - if(BURN) - damage_amount *= 2 - if(BRUTE) - damage_amount *= 0.25 - . = ..() - -/obj/structure/spider/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) - if(exposed_temperature > 300) - take_damage(5, BURN, 0, 0) - -/obj/structure/spider/stickyweb - var/genetic = FALSE - icon_state = "stickyweb1" - -/obj/structure/spider/stickyweb/Initialize() - if(prob(50)) - icon_state = "stickyweb2" - . = ..() - -/obj/structure/spider/stickyweb/CanAllowThrough(atom/movable/mover, turf/target) - . = ..() - if(genetic) - return - if(istype(mover, /mob/living/simple_animal/hostile/poison/giant_spider)) - return TRUE - else if(isliving(mover)) - if(istype(mover.pulledby, /mob/living/simple_animal/hostile/poison/giant_spider)) - return TRUE - if(prob(50)) - to_chat(mover, "You get stuck in \the [src] for a moment.") - return FALSE - else if(istype(mover, /obj/projectile)) - return prob(30) - -/obj/structure/spider/stickyweb/genetic //for the spider genes in genetics - genetic = TRUE - var/mob/living/allowed_mob - -/obj/structure/spider/stickyweb/genetic/Initialize(mapload, allowedmob) - allowed_mob = allowedmob - . = ..() - -/obj/structure/spider/stickyweb/genetic/CanAllowThrough(atom/movable/mover, turf/target) - . = ..() //this is the normal spider web return aka a spider would make this TRUE - if(mover == allowed_mob) - return TRUE - else if(isliving(mover)) //we change the spider to not be able to go through here - if(mover.pulledby == allowed_mob) - return TRUE - if(prob(50)) - to_chat(mover, "You get stuck in \the [src] for a moment.") - return FALSE - else if(istype(mover, /obj/projectile)) - return prob(30) - -/obj/structure/spider/eggcluster - name = "egg cluster" - desc = "They seem to pulse slightly with an inner life." - icon_state = "eggs" - var/amount_grown = 0 - var/player_spiders = 0 - var/directive = "" //Message from the mother - var/poison_type = /datum/reagent/toxin - var/poison_per_bite = 5 - var/list/faction = list("spiders") - -/obj/structure/spider/eggcluster/Initialize() - pixel_x = rand(3,-3) - pixel_y = rand(3,-3) - START_PROCESSING(SSobj, src) - . = ..() - -/obj/structure/spider/eggcluster/process() - amount_grown += rand(0,2) - if(amount_grown >= 100) - var/num = rand(3,12) - for(var/i=0, iYou hear something scampering through the ventilation ducts.") - - addtimer(CALLBACK(src, .proc/finish_vent_move, exit_vent), travel_time) - -/obj/structure/spider/spiderling/proc/finish_vent_move(obj/machinery/atmospherics/components/unary/vent_pump/exit_vent) - if(QDELETED(exit_vent) || exit_vent.welded) - cancel_vent_move() - return - forceMove(exit_vent.loc) - entry_vent = null - -/obj/structure/spider/spiderling/process() - if(travelling_in_vent) - if(isturf(loc)) - travelling_in_vent = 0 - entry_vent = null - else if(entry_vent) - if(get_dist(src, entry_vent) <= 1) - var/list/vents = list() - var/datum/pipeline/entry_vent_parent = entry_vent.parents[1] - for(var/obj/machinery/atmospherics/components/unary/vent_pump/temp_vent in entry_vent_parent.other_atmosmch) - vents.Add(temp_vent) - if(!vents.len) - entry_vent = null - return - var/obj/machinery/atmospherics/components/unary/vent_pump/exit_vent = pick(vents) - if(prob(50)) - visible_message("[src] scrambles into the ventilation ducts!", \ - "You hear something scampering through the ventilation ducts.") - - addtimer(CALLBACK(src, .proc/vent_move, exit_vent), rand(20,60)) - - //================= - - else if(prob(33)) - var/list/nearby = oview(10, src) - if(nearby.len) - var/target_atom = pick(nearby) - walk_to(src, target_atom) - if(prob(40)) - src.visible_message("\The [src] skitters[pick(" away"," around","")].") - else if(prob(10)) - //ventcrawl! - for(var/obj/machinery/atmospherics/components/unary/vent_pump/v in view(7,src)) - if(!v.welded) - entry_vent = v - walk_to(src, entry_vent, 1) - break - if(isturf(loc)) - amount_grown += rand(0,2) - if(amount_grown >= 100) - if(!grow_as) - if(prob(3)) - grow_as = pick(/mob/living/simple_animal/hostile/poison/giant_spider/tarantula, /mob/living/simple_animal/hostile/poison/giant_spider/hunter/viper, /mob/living/simple_animal/hostile/poison/giant_spider/nurse/midwife) - else - grow_as = pick(/mob/living/simple_animal/hostile/poison/giant_spider, /mob/living/simple_animal/hostile/poison/giant_spider/hunter, /mob/living/simple_animal/hostile/poison/giant_spider/nurse) - var/mob/living/simple_animal/hostile/poison/giant_spider/S = new grow_as(src.loc) - S.faction = faction.Copy() - S.directive = directive - if(player_spiders) - S.playable_spider = TRUE - notify_ghosts("Spider [S.name] can be controlled", null, enter_link="(Click to play)", source=S, action=NOTIFY_ATTACK, ignore_key = POLL_IGNORE_SPIDER) - qdel(src) - - - -/obj/structure/spider/cocoon - name = "cocoon" - desc = "Something wrapped in silky spider web." - icon_state = "cocoon1" - max_integrity = 60 - -/obj/structure/spider/cocoon/Initialize() - icon_state = pick("cocoon1","cocoon2","cocoon3") - . = ..() - -/obj/structure/spider/cocoon/container_resist(mob/living/user) - var/breakout_time = 600 - user.changeNext_move(CLICK_CD_BREAKOUT) - user.last_special = world.time + CLICK_CD_BREAKOUT - to_chat(user, "You struggle against the tight bonds... (This will take about [DisplayTimeText(breakout_time)].)") - visible_message("You see something struggling and writhing in \the [src]!") - if(do_after(user,(breakout_time), target = src)) - if(!user || user.stat != CONSCIOUS || user.loc != src) - return - qdel(src) - - - -/obj/structure/spider/cocoon/Destroy() - var/turf/T = get_turf(src) - src.visible_message("\The [src] splits open.") - for(var/atom/movable/A in contents) - A.forceMove(T) - return ..() +//generic procs copied from obj/effect/alien +/obj/structure/spider + name = "web" + icon = 'icons/effects/effects.dmi' + desc = "It's stringy and sticky." + anchored = TRUE + density = FALSE + max_integrity = 15 + + + +/obj/structure/spider/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) + if(damage_type == BURN)//the stickiness of the web mutes all attack sounds except fire damage type + playsound(loc, 'sound/items/welder.ogg', 100, TRUE) + + +/obj/structure/spider/run_obj_armor(damage_amount, damage_type, damage_flag = 0, attack_dir) + if(damage_flag == "melee") + switch(damage_type) + if(BURN) + damage_amount *= 2 + if(BRUTE) + damage_amount *= 0.25 + . = ..() + +/obj/structure/spider/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) + if(exposed_temperature > 300) + take_damage(5, BURN, 0, 0) + +/obj/structure/spider/stickyweb + var/genetic = FALSE + icon_state = "stickyweb1" + +/obj/structure/spider/stickyweb/Initialize() + if(prob(50)) + icon_state = "stickyweb2" + . = ..() + +/obj/structure/spider/stickyweb/CanAllowThrough(atom/movable/mover, turf/target) + . = ..() + if(genetic) + return + if(istype(mover, /mob/living/simple_animal/hostile/poison/giant_spider)) + return TRUE + else if(isliving(mover)) + if(istype(mover.pulledby, /mob/living/simple_animal/hostile/poison/giant_spider)) + return TRUE + if(prob(50)) + to_chat(mover, "You get stuck in \the [src] for a moment.") + return FALSE + else if(istype(mover, /obj/projectile)) + return prob(30) + +/obj/structure/spider/stickyweb/genetic //for the spider genes in genetics + genetic = TRUE + var/mob/living/allowed_mob + +/obj/structure/spider/stickyweb/genetic/Initialize(mapload, allowedmob) + allowed_mob = allowedmob + . = ..() + +/obj/structure/spider/stickyweb/genetic/CanAllowThrough(atom/movable/mover, turf/target) + . = ..() //this is the normal spider web return aka a spider would make this TRUE + if(mover == allowed_mob) + return TRUE + else if(isliving(mover)) //we change the spider to not be able to go through here + if(mover.pulledby == allowed_mob) + return TRUE + if(prob(50)) + to_chat(mover, "You get stuck in \the [src] for a moment.") + return FALSE + else if(istype(mover, /obj/projectile)) + return prob(30) + +/obj/structure/spider/eggcluster + name = "egg cluster" + desc = "They seem to pulse slightly with an inner life." + icon_state = "eggs" + var/amount_grown = 0 + var/player_spiders = 0 + var/directive = "" //Message from the mother + var/poison_type = /datum/reagent/toxin + var/poison_per_bite = 5 + var/list/faction = list("spiders") + +/obj/structure/spider/eggcluster/Initialize() + pixel_x = rand(3,-3) + pixel_y = rand(3,-3) + START_PROCESSING(SSobj, src) + . = ..() + +/obj/structure/spider/eggcluster/process() + amount_grown += rand(0,2) + if(amount_grown >= 100) + var/num = rand(3,12) + for(var/i=0, iYou hear something scampering through the ventilation ducts.") + + addtimer(CALLBACK(src, .proc/finish_vent_move, exit_vent), travel_time) + +/obj/structure/spider/spiderling/proc/finish_vent_move(obj/machinery/atmospherics/components/unary/vent_pump/exit_vent) + if(QDELETED(exit_vent) || exit_vent.welded) + cancel_vent_move() + return + forceMove(exit_vent.loc) + entry_vent = null + +/obj/structure/spider/spiderling/process() + if(travelling_in_vent) + if(isturf(loc)) + travelling_in_vent = 0 + entry_vent = null + else if(entry_vent) + if(get_dist(src, entry_vent) <= 1) + var/list/vents = list() + var/datum/pipeline/entry_vent_parent = entry_vent.parents[1] + for(var/obj/machinery/atmospherics/components/unary/vent_pump/temp_vent in entry_vent_parent.other_atmosmch) + vents.Add(temp_vent) + if(!vents.len) + entry_vent = null + return + var/obj/machinery/atmospherics/components/unary/vent_pump/exit_vent = pick(vents) + if(prob(50)) + visible_message("[src] scrambles into the ventilation ducts!", \ + "You hear something scampering through the ventilation ducts.") + + addtimer(CALLBACK(src, .proc/vent_move, exit_vent), rand(20,60)) + + //================= + + else if(prob(33)) + var/list/nearby = oview(10, src) + if(nearby.len) + var/target_atom = pick(nearby) + walk_to(src, target_atom) + if(prob(40)) + src.visible_message("\The [src] skitters[pick(" away"," around","")].") + else if(prob(10)) + //ventcrawl! + for(var/obj/machinery/atmospherics/components/unary/vent_pump/v in view(7,src)) + if(!v.welded) + entry_vent = v + walk_to(src, entry_vent, 1) + break + if(isturf(loc)) + amount_grown += rand(0,2) + if(amount_grown >= 100) + if(!grow_as) + if(prob(3)) + grow_as = pick(/mob/living/simple_animal/hostile/poison/giant_spider/tarantula, /mob/living/simple_animal/hostile/poison/giant_spider/hunter/viper, /mob/living/simple_animal/hostile/poison/giant_spider/nurse/midwife) + else + grow_as = pick(/mob/living/simple_animal/hostile/poison/giant_spider, /mob/living/simple_animal/hostile/poison/giant_spider/hunter, /mob/living/simple_animal/hostile/poison/giant_spider/nurse) + var/mob/living/simple_animal/hostile/poison/giant_spider/S = new grow_as(src.loc) + S.faction = faction.Copy() + S.directive = directive + if(player_spiders) + S.playable_spider = TRUE + notify_ghosts("Spider [S.name] can be controlled", null, enter_link="(Click to play)", source=S, action=NOTIFY_ATTACK, ignore_key = POLL_IGNORE_SPIDER) + qdel(src) + + + +/obj/structure/spider/cocoon + name = "cocoon" + desc = "Something wrapped in silky spider web." + icon_state = "cocoon1" + max_integrity = 60 + +/obj/structure/spider/cocoon/Initialize() + icon_state = pick("cocoon1","cocoon2","cocoon3") + . = ..() + +/obj/structure/spider/cocoon/container_resist(mob/living/user) + var/breakout_time = 600 + user.changeNext_move(CLICK_CD_BREAKOUT) + user.last_special = world.time + CLICK_CD_BREAKOUT + to_chat(user, "You struggle against the tight bonds... (This will take about [DisplayTimeText(breakout_time)].)") + visible_message("You see something struggling and writhing in \the [src]!") + if(do_after(user,(breakout_time), target = src)) + if(!user || user.stat != CONSCIOUS || user.loc != src) + return + qdel(src) + + + +/obj/structure/spider/cocoon/Destroy() + var/turf/T = get_turf(src) + src.visible_message("\The [src] splits open.") + for(var/atom/movable/A in contents) + A.forceMove(T) + return ..() diff --git a/code/game/objects/effects/step_triggers.dm b/code/game/objects/effects/step_triggers.dm index a91b66a7c528..f8da95fee32f 100644 --- a/code/game/objects/effects/step_triggers.dm +++ b/code/game/objects/effects/step_triggers.dm @@ -1,200 +1,200 @@ -/* Simple object type, calls a proc when "stepped" on by something */ - -/obj/effect/step_trigger - var/affect_ghosts = 0 - var/stopper = 1 // stops throwers - var/mobs_only = FALSE - invisibility = INVISIBILITY_ABSTRACT // nope cant see this shit - anchored = TRUE - -/obj/effect/step_trigger/proc/Trigger(atom/movable/A) - return 0 - -/obj/effect/step_trigger/Crossed(H as mob|obj) - ..() - if(!H) - return - if(isobserver(H) && !affect_ghosts) - return - if(!ismob(H) && mobs_only) - return - Trigger(H) - - -/obj/effect/step_trigger/singularity_act() - return - -/obj/effect/step_trigger/singularity_pull() - return - -/* Sends a message to mob when triggered*/ - -/obj/effect/step_trigger/message - var/message //the message to give to the mob - var/once = 1 - mobs_only = TRUE - -/obj/effect/step_trigger/message/Trigger(mob/M) - if(M.client) - to_chat(M, "[message]") - if(once) - qdel(src) - -/* Tosses things in a certain direction */ - -/obj/effect/step_trigger/thrower - var/direction = SOUTH // the direction of throw - var/tiles = 3 // if 0: forever until atom hits a stopper - var/immobilize = 1 // if nonzero: prevents mobs from moving while they're being flung - var/speed = 1 // delay of movement - var/facedir = 0 // if 1: atom faces the direction of movement - var/nostop = 0 // if 1: will only be stopped by teleporters - var/list/affecting = list() - -/obj/effect/step_trigger/thrower/Trigger(atom/A) - if(!A || !ismovable(A)) - return - var/atom/movable/AM = A - var/curtiles = 0 - var/stopthrow = 0 - for(var/obj/effect/step_trigger/thrower/T in orange(2, src)) - if(AM in T.affecting) - return - - if(isliving(AM)) - var/mob/living/M = AM - if(immobilize) - M.mobility_flags &= ~MOBILITY_MOVE - - affecting.Add(AM) - while(AM && !stopthrow) - if(tiles) - if(curtiles >= tiles) - break - if(AM.z != src.z) - break - - curtiles++ - - sleep(speed) - - // Calculate if we should stop the process - if(!nostop) - for(var/obj/effect/step_trigger/T in get_step(AM, direction)) - if(T.stopper && T != src) - stopthrow = 1 - else - for(var/obj/effect/step_trigger/teleporter/T in get_step(AM, direction)) - if(T.stopper) - stopthrow = 1 - - if(AM) - var/predir = AM.dir - step(AM, direction) - if(!facedir) - AM.setDir(predir) - - - - affecting.Remove(AM) - - if(isliving(AM)) - var/mob/living/M = AM - if(immobilize) - M.mobility_flags |= MOBILITY_MOVE - M.update_mobility() - -/* Stops things thrown by a thrower, doesn't do anything */ - -/obj/effect/step_trigger/stopper - -/* Instant teleporter */ - -/obj/effect/step_trigger/teleporter - var/teleport_x = 0 // teleportation coordinates (if one is null, then no teleport!) - var/teleport_y = 0 - var/teleport_z = 0 - -/obj/effect/step_trigger/teleporter/Trigger(atom/movable/A) - if(teleport_x && teleport_y && teleport_z) - - var/turf/T = locate(teleport_x, teleport_y, teleport_z) - A.forceMove(T) - -/* Random teleporter, teleports atoms to locations ranging from teleport_x - teleport_x_offset, etc */ - -/obj/effect/step_trigger/teleporter/random - var/teleport_x_offset = 0 - var/teleport_y_offset = 0 - var/teleport_z_offset = 0 - -/obj/effect/step_trigger/teleporter/random/Trigger(atom/movable/A) - if(teleport_x && teleport_y && teleport_z) - if(teleport_x_offset && teleport_y_offset && teleport_z_offset) - - var/turf/T = locate(rand(teleport_x, teleport_x_offset), rand(teleport_y, teleport_y_offset), rand(teleport_z, teleport_z_offset)) - if (T) - A.forceMove(T) - -/* Fancy teleporter, creates sparks and smokes when used */ - -/obj/effect/step_trigger/teleport_fancy - var/locationx - var/locationy - var/uses = 1 //0 for infinite uses - var/entersparks = 0 - var/exitsparks = 0 - var/entersmoke = 0 - var/exitsmoke = 0 - -/obj/effect/step_trigger/teleport_fancy/Trigger(mob/M) - var/dest = locate(locationx, locationy, z) - M.Move(dest) - - if(entersparks) - var/datum/effect_system/spark_spread/s = new - s.set_up(4, 1, src) - s.start() - if(exitsparks) - var/datum/effect_system/spark_spread/s = new - s.set_up(4, 1, dest) - s.start() - - if(entersmoke) - var/datum/effect_system/smoke_spread/s = new - s.set_up(4, 1, src, 0) - s.start() - if(exitsmoke) - var/datum/effect_system/smoke_spread/s = new - s.set_up(4, 1, dest, 0) - s.start() - - uses-- - if(uses == 0) - qdel(src) - -/* Simple sound player, Mapper friendly! */ - -/obj/effect/step_trigger/sound_effect - var/sound //eg. path to the sound, inside '' eg: 'growl.ogg' - var/volume = 100 - var/freq_vary = 1 //Should the frequency of the sound vary? - var/extra_range = 0 // eg World.view = 7, extra_range = 1, 7+1 = 8, 8 turfs radius - var/happens_once = 0 - var/triggerer_only = 0 //Whether the triggerer is the only person who hears this - - -/obj/effect/step_trigger/sound_effect/Trigger(atom/movable/A) - var/turf/T = get_turf(A) - - if(!T) - return - - if(triggerer_only && ismob(A)) - var/mob/B = A - B.playsound_local(T, sound, volume, freq_vary) - else - playsound(T, sound, volume, freq_vary, extra_range) - - if(happens_once) - qdel(src) +/* Simple object type, calls a proc when "stepped" on by something */ + +/obj/effect/step_trigger + var/affect_ghosts = 0 + var/stopper = 1 // stops throwers + var/mobs_only = FALSE + invisibility = INVISIBILITY_ABSTRACT // nope cant see this shit + anchored = TRUE + +/obj/effect/step_trigger/proc/Trigger(atom/movable/A) + return 0 + +/obj/effect/step_trigger/Crossed(H as mob|obj) + ..() + if(!H) + return + if(isobserver(H) && !affect_ghosts) + return + if(!ismob(H) && mobs_only) + return + Trigger(H) + + +/obj/effect/step_trigger/singularity_act() + return + +/obj/effect/step_trigger/singularity_pull() + return + +/* Sends a message to mob when triggered*/ + +/obj/effect/step_trigger/message + var/message //the message to give to the mob + var/once = 1 + mobs_only = TRUE + +/obj/effect/step_trigger/message/Trigger(mob/M) + if(M.client) + to_chat(M, "[message]") + if(once) + qdel(src) + +/* Tosses things in a certain direction */ + +/obj/effect/step_trigger/thrower + var/direction = SOUTH // the direction of throw + var/tiles = 3 // if 0: forever until atom hits a stopper + var/immobilize = 1 // if nonzero: prevents mobs from moving while they're being flung + var/speed = 1 // delay of movement + var/facedir = 0 // if 1: atom faces the direction of movement + var/nostop = 0 // if 1: will only be stopped by teleporters + var/list/affecting = list() + +/obj/effect/step_trigger/thrower/Trigger(atom/A) + if(!A || !ismovable(A)) + return + var/atom/movable/AM = A + var/curtiles = 0 + var/stopthrow = 0 + for(var/obj/effect/step_trigger/thrower/T in orange(2, src)) + if(AM in T.affecting) + return + + if(isliving(AM)) + var/mob/living/M = AM + if(immobilize) + M.mobility_flags &= ~MOBILITY_MOVE + + affecting.Add(AM) + while(AM && !stopthrow) + if(tiles) + if(curtiles >= tiles) + break + if(AM.z != src.z) + break + + curtiles++ + + sleep(speed) + + // Calculate if we should stop the process + if(!nostop) + for(var/obj/effect/step_trigger/T in get_step(AM, direction)) + if(T.stopper && T != src) + stopthrow = 1 + else + for(var/obj/effect/step_trigger/teleporter/T in get_step(AM, direction)) + if(T.stopper) + stopthrow = 1 + + if(AM) + var/predir = AM.dir + step(AM, direction) + if(!facedir) + AM.setDir(predir) + + + + affecting.Remove(AM) + + if(isliving(AM)) + var/mob/living/M = AM + if(immobilize) + M.mobility_flags |= MOBILITY_MOVE + M.update_mobility() + +/* Stops things thrown by a thrower, doesn't do anything */ + +/obj/effect/step_trigger/stopper + +/* Instant teleporter */ + +/obj/effect/step_trigger/teleporter + var/teleport_x = 0 // teleportation coordinates (if one is null, then no teleport!) + var/teleport_y = 0 + var/teleport_z = 0 + +/obj/effect/step_trigger/teleporter/Trigger(atom/movable/A) + if(teleport_x && teleport_y && teleport_z) + + var/turf/T = locate(teleport_x, teleport_y, teleport_z) + A.forceMove(T) + +/* Random teleporter, teleports atoms to locations ranging from teleport_x - teleport_x_offset, etc */ + +/obj/effect/step_trigger/teleporter/random + var/teleport_x_offset = 0 + var/teleport_y_offset = 0 + var/teleport_z_offset = 0 + +/obj/effect/step_trigger/teleporter/random/Trigger(atom/movable/A) + if(teleport_x && teleport_y && teleport_z) + if(teleport_x_offset && teleport_y_offset && teleport_z_offset) + + var/turf/T = locate(rand(teleport_x, teleport_x_offset), rand(teleport_y, teleport_y_offset), rand(teleport_z, teleport_z_offset)) + if (T) + A.forceMove(T) + +/* Fancy teleporter, creates sparks and smokes when used */ + +/obj/effect/step_trigger/teleport_fancy + var/locationx + var/locationy + var/uses = 1 //0 for infinite uses + var/entersparks = 0 + var/exitsparks = 0 + var/entersmoke = 0 + var/exitsmoke = 0 + +/obj/effect/step_trigger/teleport_fancy/Trigger(mob/M) + var/dest = locate(locationx, locationy, z) + M.Move(dest) + + if(entersparks) + var/datum/effect_system/spark_spread/s = new + s.set_up(4, 1, src) + s.start() + if(exitsparks) + var/datum/effect_system/spark_spread/s = new + s.set_up(4, 1, dest) + s.start() + + if(entersmoke) + var/datum/effect_system/smoke_spread/s = new + s.set_up(4, 1, src, 0) + s.start() + if(exitsmoke) + var/datum/effect_system/smoke_spread/s = new + s.set_up(4, 1, dest, 0) + s.start() + + uses-- + if(uses == 0) + qdel(src) + +/* Simple sound player, Mapper friendly! */ + +/obj/effect/step_trigger/sound_effect + var/sound //eg. path to the sound, inside '' eg: 'growl.ogg' + var/volume = 100 + var/freq_vary = 1 //Should the frequency of the sound vary? + var/extra_range = 0 // eg World.view = 7, extra_range = 1, 7+1 = 8, 8 turfs radius + var/happens_once = 0 + var/triggerer_only = 0 //Whether the triggerer is the only person who hears this + + +/obj/effect/step_trigger/sound_effect/Trigger(atom/movable/A) + var/turf/T = get_turf(A) + + if(!T) + return + + if(triggerer_only && ismob(A)) + var/mob/B = A + B.playsound_local(T, sound, volume, freq_vary) + else + playsound(T, sound, volume, freq_vary, extra_range) + + if(happens_once) + qdel(src) diff --git a/code/game/objects/effects/temporary_visuals/projectiles/impact.dm b/code/game/objects/effects/temporary_visuals/projectiles/impact.dm index a7c3d270e787..875eaf5e60a1 100644 --- a/code/game/objects/effects/temporary_visuals/projectiles/impact.dm +++ b/code/game/objects/effects/temporary_visuals/projectiles/impact.dm @@ -1,38 +1,38 @@ -/obj/effect/projectile/impact - name = "beam impact" - icon = 'icons/obj/projectiles_impact.dmi' - -/obj/effect/projectile/impact/laser - name = "laser impact" - icon_state = "impact_laser" - -/obj/effect/projectile/impact/laser/blue - name = "laser impact" - icon_state = "impact_blue" - -/obj/effect/projectile/impact/disabler - name = "disabler impact" - icon_state = "impact_omni" - -/obj/effect/projectile/impact/xray - name = "\improper X-ray impact" - icon_state = "impact_xray" - -/obj/effect/projectile/impact/pulse - name = "pulse impact" - icon_state = "impact_u_laser" - -/obj/effect/projectile/impact/plasma_cutter - name = "plasma impact" - icon_state = "impact_plasmacutter" - -/obj/effect/projectile/impact/stun - name = "stun impact" - icon_state = "impact_stun" - -/obj/effect/projectile/impact/heavy_laser - name = "heavy laser impact" - icon_state = "impact_beam_heavy" - -/obj/effect/projectile/impact/wormhole - icon_state = "wormhole_g" +/obj/effect/projectile/impact + name = "beam impact" + icon = 'icons/obj/projectiles_impact.dmi' + +/obj/effect/projectile/impact/laser + name = "laser impact" + icon_state = "impact_laser" + +/obj/effect/projectile/impact/laser/blue + name = "laser impact" + icon_state = "impact_blue" + +/obj/effect/projectile/impact/disabler + name = "disabler impact" + icon_state = "impact_omni" + +/obj/effect/projectile/impact/xray + name = "\improper X-ray impact" + icon_state = "impact_xray" + +/obj/effect/projectile/impact/pulse + name = "pulse impact" + icon_state = "impact_u_laser" + +/obj/effect/projectile/impact/plasma_cutter + name = "plasma impact" + icon_state = "impact_plasmacutter" + +/obj/effect/projectile/impact/stun + name = "stun impact" + icon_state = "impact_stun" + +/obj/effect/projectile/impact/heavy_laser + name = "heavy laser impact" + icon_state = "impact_beam_heavy" + +/obj/effect/projectile/impact/wormhole + icon_state = "wormhole_g" diff --git a/code/game/objects/effects/temporary_visuals/projectiles/muzzle.dm b/code/game/objects/effects/temporary_visuals/projectiles/muzzle.dm index 133d06b4b687..ad6b23f50416 100644 --- a/code/game/objects/effects/temporary_visuals/projectiles/muzzle.dm +++ b/code/game/objects/effects/temporary_visuals/projectiles/muzzle.dm @@ -1,30 +1,30 @@ -/obj/effect/projectile/muzzle - name = "muzzle flash" - icon = 'icons/obj/projectiles_muzzle.dmi' - -/obj/effect/projectile/muzzle/laser - icon_state = "muzzle_laser" - -/obj/effect/projectile/muzzle/laser/blue - icon_state = "muzzle_laser_blue" - -/obj/effect/projectile/muzzle/disabler - icon_state = "muzzle_omni" - -/obj/effect/projectile/muzzle/xray - icon_state = "muzzle_xray" - -/obj/effect/projectile/muzzle/pulse - icon_state = "muzzle_u_laser" - -/obj/effect/projectile/muzzle/plasma_cutter - icon_state = "muzzle_plasmacutter" - -/obj/effect/projectile/muzzle/stun - icon_state = "muzzle_stun" - -/obj/effect/projectile/muzzle/heavy_laser - icon_state = "muzzle_beam_heavy" - -/obj/effect/projectile/muzzle/wormhole - icon_state = "wormhole_g" +/obj/effect/projectile/muzzle + name = "muzzle flash" + icon = 'icons/obj/projectiles_muzzle.dmi' + +/obj/effect/projectile/muzzle/laser + icon_state = "muzzle_laser" + +/obj/effect/projectile/muzzle/laser/blue + icon_state = "muzzle_laser_blue" + +/obj/effect/projectile/muzzle/disabler + icon_state = "muzzle_omni" + +/obj/effect/projectile/muzzle/xray + icon_state = "muzzle_xray" + +/obj/effect/projectile/muzzle/pulse + icon_state = "muzzle_u_laser" + +/obj/effect/projectile/muzzle/plasma_cutter + icon_state = "muzzle_plasmacutter" + +/obj/effect/projectile/muzzle/stun + icon_state = "muzzle_stun" + +/obj/effect/projectile/muzzle/heavy_laser + icon_state = "muzzle_beam_heavy" + +/obj/effect/projectile/muzzle/wormhole + icon_state = "wormhole_g" diff --git a/code/game/objects/effects/temporary_visuals/projectiles/projectile_effects.dm b/code/game/objects/effects/temporary_visuals/projectiles/projectile_effects.dm index 103cee137d39..49cdc3667cff 100644 --- a/code/game/objects/effects/temporary_visuals/projectiles/projectile_effects.dm +++ b/code/game/objects/effects/temporary_visuals/projectiles/projectile_effects.dm @@ -1,60 +1,60 @@ -/obj/effect/projectile - name = "pew" - icon = 'icons/obj/projectiles.dmi' - icon_state = "nothing" - layer = ABOVE_MOB_LAYER - anchored = TRUE - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - appearance_flags = 0 - -/obj/effect/projectile/singularity_pull() - return - -/obj/effect/projectile/singularity_act() - return - -/obj/effect/projectile/proc/scale_to(nx,ny,override=TRUE) - var/matrix/M - if(!override) - M = transform - else - M = new - M.Scale(nx,ny) - transform = M - -/obj/effect/projectile/proc/turn_to(angle,override=TRUE) - var/matrix/M - if(!override) - M = transform - else - M = new - M.Turn(angle) - transform = M - -/obj/effect/projectile/New(angle_override, p_x, p_y, color_override, scaling = 1) - if(angle_override && p_x && p_y && color_override && scaling) - apply_vars(angle_override, p_x, p_y, color_override, scaling) - return ..() - -/obj/effect/projectile/proc/apply_vars(angle_override, p_x = 0, p_y = 0, color_override, scaling = 1, new_loc, increment = 0) - var/mutable_appearance/look = new(src) - look.pixel_x = p_x - look.pixel_y = p_y - if(color_override) - look.color = color_override - appearance = look - scale_to(1,scaling, FALSE) - turn_to(angle_override, FALSE) - if(!isnull(new_loc)) //If you want to null it just delete it... - forceMove(new_loc) - for(var/i in 1 to increment) - pixel_x += round((sin(angle_override)+16*sin(angle_override)*2), 1) - pixel_y += round((cos(angle_override)+16*cos(angle_override)*2), 1) - -/obj/effect/projectile_lighting - var/owner - -/obj/effect/projectile_lighting/Initialize(mapload, color, range, intensity, owner_key) - . = ..() - set_light(range, intensity, color) - owner = owner_key +/obj/effect/projectile + name = "pew" + icon = 'icons/obj/projectiles.dmi' + icon_state = "nothing" + layer = ABOVE_MOB_LAYER + anchored = TRUE + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + appearance_flags = 0 + +/obj/effect/projectile/singularity_pull() + return + +/obj/effect/projectile/singularity_act() + return + +/obj/effect/projectile/proc/scale_to(nx,ny,override=TRUE) + var/matrix/M + if(!override) + M = transform + else + M = new + M.Scale(nx,ny) + transform = M + +/obj/effect/projectile/proc/turn_to(angle,override=TRUE) + var/matrix/M + if(!override) + M = transform + else + M = new + M.Turn(angle) + transform = M + +/obj/effect/projectile/New(angle_override, p_x, p_y, color_override, scaling = 1) + if(angle_override && p_x && p_y && color_override && scaling) + apply_vars(angle_override, p_x, p_y, color_override, scaling) + return ..() + +/obj/effect/projectile/proc/apply_vars(angle_override, p_x = 0, p_y = 0, color_override, scaling = 1, new_loc, increment = 0) + var/mutable_appearance/look = new(src) + look.pixel_x = p_x + look.pixel_y = p_y + if(color_override) + look.color = color_override + appearance = look + scale_to(1,scaling, FALSE) + turn_to(angle_override, FALSE) + if(!isnull(new_loc)) //If you want to null it just delete it... + forceMove(new_loc) + for(var/i in 1 to increment) + pixel_x += round((sin(angle_override)+16*sin(angle_override)*2), 1) + pixel_y += round((cos(angle_override)+16*cos(angle_override)*2), 1) + +/obj/effect/projectile_lighting + var/owner + +/obj/effect/projectile_lighting/Initialize(mapload, color, range, intensity, owner_key) + . = ..() + set_light(range, intensity, color) + owner = owner_key diff --git a/code/game/objects/effects/temporary_visuals/projectiles/tracer.dm b/code/game/objects/effects/temporary_visuals/projectiles/tracer.dm index 4111b2ec6f56..23ecf438c4f9 100644 --- a/code/game/objects/effects/temporary_visuals/projectiles/tracer.dm +++ b/code/game/objects/effects/temporary_visuals/projectiles/tracer.dm @@ -1,68 +1,68 @@ -/proc/generate_tracer_between_points(datum/point/starting, datum/point/ending, beam_type, color, qdel_in = 5, light_range = 2, light_color_override, light_intensity = 1, instance_key) //Do not pass z-crossing points as that will not be properly (and likely will never be properly until it's absolutely needed) supported! - if(!istype(starting) || !istype(ending) || !ispath(beam_type)) - return - var/datum/point/midpoint = point_midpoint_points(starting, ending) - var/obj/effect/projectile/tracer/PB = new beam_type - if(isnull(light_color_override)) - light_color_override = color - PB.apply_vars(angle_between_points(starting, ending), midpoint.return_px(), midpoint.return_py(), color, pixel_length_between_points(starting, ending) / world.icon_size, midpoint.return_turf(), 0) - . = PB - if(light_range > 0 && light_intensity > 0) - var/list/turf/line = getline(starting.return_turf(), ending.return_turf()) - tracing_line: - for(var/i in line) - var/turf/T = i - for(var/obj/effect/projectile_lighting/PL in T) - if(PL.owner == instance_key) - continue tracing_line - QDEL_IN(new /obj/effect/projectile_lighting(T, light_color_override, light_range, light_intensity, instance_key), qdel_in > 0? qdel_in : 5) - line = null - if(qdel_in) - QDEL_IN(PB, qdel_in) - -/obj/effect/projectile/tracer - name = "beam" - icon = 'icons/obj/projectiles_tracer.dmi' - -/obj/effect/projectile/tracer/laser - name = "laser" - icon_state = "beam" - -/obj/effect/projectile/tracer/laser/blue - icon_state = "beam_blue" - -/obj/effect/projectile/tracer/disabler - name = "disabler" - icon_state = "beam_omni" - -/obj/effect/projectile/tracer/xray - name = "\improper X-ray laser" - icon_state = "xray" - -/obj/effect/projectile/tracer/pulse - name = "pulse laser" - icon_state = "u_laser" - -/obj/effect/projectile/tracer/plasma_cutter - name = "plasma blast" - icon_state = "plasmacutter" - -/obj/effect/projectile/tracer/stun - name = "stun beam" - icon_state = "stun" - -/obj/effect/projectile/tracer/heavy_laser - name = "heavy laser" - icon_state = "beam_heavy" - -//BEAM RIFLE -/obj/effect/projectile/tracer/tracer/beam_rifle - icon_state = "tracer_beam" - -/obj/effect/projectile/tracer/tracer/aiming - icon_state = "pixelbeam_greyscale" - layer = ABOVE_LIGHTING_LAYER - plane = ABOVE_LIGHTING_PLANE - -/obj/effect/projectile/tracer/wormhole - icon_state = "wormhole_g" +/proc/generate_tracer_between_points(datum/point/starting, datum/point/ending, beam_type, color, qdel_in = 5, light_range = 2, light_color_override, light_intensity = 1, instance_key) //Do not pass z-crossing points as that will not be properly (and likely will never be properly until it's absolutely needed) supported! + if(!istype(starting) || !istype(ending) || !ispath(beam_type)) + return + var/datum/point/midpoint = point_midpoint_points(starting, ending) + var/obj/effect/projectile/tracer/PB = new beam_type + if(isnull(light_color_override)) + light_color_override = color + PB.apply_vars(angle_between_points(starting, ending), midpoint.return_px(), midpoint.return_py(), color, pixel_length_between_points(starting, ending) / world.icon_size, midpoint.return_turf(), 0) + . = PB + if(light_range > 0 && light_intensity > 0) + var/list/turf/line = getline(starting.return_turf(), ending.return_turf()) + tracing_line: + for(var/i in line) + var/turf/T = i + for(var/obj/effect/projectile_lighting/PL in T) + if(PL.owner == instance_key) + continue tracing_line + QDEL_IN(new /obj/effect/projectile_lighting(T, light_color_override, light_range, light_intensity, instance_key), qdel_in > 0? qdel_in : 5) + line = null + if(qdel_in) + QDEL_IN(PB, qdel_in) + +/obj/effect/projectile/tracer + name = "beam" + icon = 'icons/obj/projectiles_tracer.dmi' + +/obj/effect/projectile/tracer/laser + name = "laser" + icon_state = "beam" + +/obj/effect/projectile/tracer/laser/blue + icon_state = "beam_blue" + +/obj/effect/projectile/tracer/disabler + name = "disabler" + icon_state = "beam_omni" + +/obj/effect/projectile/tracer/xray + name = "\improper X-ray laser" + icon_state = "xray" + +/obj/effect/projectile/tracer/pulse + name = "pulse laser" + icon_state = "u_laser" + +/obj/effect/projectile/tracer/plasma_cutter + name = "plasma blast" + icon_state = "plasmacutter" + +/obj/effect/projectile/tracer/stun + name = "stun beam" + icon_state = "stun" + +/obj/effect/projectile/tracer/heavy_laser + name = "heavy laser" + icon_state = "beam_heavy" + +//BEAM RIFLE +/obj/effect/projectile/tracer/tracer/beam_rifle + icon_state = "tracer_beam" + +/obj/effect/projectile/tracer/tracer/aiming + icon_state = "pixelbeam_greyscale" + layer = ABOVE_LIGHTING_LAYER + plane = ABOVE_LIGHTING_PLANE + +/obj/effect/projectile/tracer/wormhole + icon_state = "wormhole_g" diff --git a/code/game/objects/empulse.dm b/code/game/objects/empulse.dm index f81ba58f435f..e7246fd04ef6 100644 --- a/code/game/objects/empulse.dm +++ b/code/game/objects/empulse.dm @@ -1,32 +1,32 @@ -/proc/empulse(turf/epicenter, heavy_range, light_range, log=0) - if(!epicenter) - return - - if(!isturf(epicenter)) - epicenter = get_turf(epicenter.loc) - - if(log) - message_admins("EMP with size ([heavy_range], [light_range]) in area [epicenter.loc.name] ") - log_game("EMP with size ([heavy_range], [light_range]) in area [epicenter.loc.name] ") - - if(heavy_range > 1) - new /obj/effect/temp_visual/emp/pulse(epicenter) - - if(heavy_range > light_range) - light_range = heavy_range - - for(var/A in spiral_range(light_range, epicenter)) - var/atom/T = A - var/distance = get_dist(epicenter, T) - if(distance < 0) - distance = 0 - if(distance < heavy_range) - T.emp_act(EMP_HEAVY) - else if(distance == heavy_range) - if(prob(50)) - T.emp_act(EMP_HEAVY) - else - T.emp_act(EMP_LIGHT) - else if(distance <= light_range) - T.emp_act(EMP_LIGHT) - return 1 +/proc/empulse(turf/epicenter, heavy_range, light_range, log=0) + if(!epicenter) + return + + if(!isturf(epicenter)) + epicenter = get_turf(epicenter.loc) + + if(log) + message_admins("EMP with size ([heavy_range], [light_range]) in area [epicenter.loc.name] ") + log_game("EMP with size ([heavy_range], [light_range]) in area [epicenter.loc.name] ") + + if(heavy_range > 1) + new /obj/effect/temp_visual/emp/pulse(epicenter) + + if(heavy_range > light_range) + light_range = heavy_range + + for(var/A in spiral_range(light_range, epicenter)) + var/atom/T = A + var/distance = get_dist(epicenter, T) + if(distance < 0) + distance = 0 + if(distance < heavy_range) + T.emp_act(EMP_HEAVY) + else if(distance == heavy_range) + if(prob(50)) + T.emp_act(EMP_HEAVY) + else + T.emp_act(EMP_LIGHT) + else if(distance <= light_range) + T.emp_act(EMP_LIGHT) + return 1 diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 54d89082f1d1..c899898ed51c 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -1,993 +1,993 @@ -GLOBAL_DATUM_INIT(fire_overlay, /mutable_appearance, mutable_appearance('icons/effects/fire.dmi', "fire")) - -GLOBAL_VAR_INIT(rpg_loot_items, FALSE) -// if true, everyone item when created will have its name changed to be -// more... RPG-like. - -GLOBAL_VAR_INIT(stickpocalypse, FALSE) // if true, all non-embeddable items will be able to harmlessly stick to people when thrown -GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to embed in people, takes precedence over stickpocalypse - -/obj/item - name = "item" - icon = 'icons/obj/items_and_weapons.dmi' - blocks_emissive = EMISSIVE_BLOCK_GENERIC - ///icon state name for inhand overlays - var/item_state = null - ///Icon file for left hand inhand overlays - var/lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' - ///Icon file for right inhand overlays - var/righthand_file = 'icons/mob/inhands/items_righthand.dmi' - - ///Icon file for mob worn overlays. - ///no var for state because it should *always* be the same as icon_state - var/icon/mob_overlay_icon - ///Forced mob worn layer instead of the standard preferred ssize. - var/alternate_worn_layer - - ///Dimensions of the icon file used when this item is worn, eg: hats.dmi (32x32 sprite, 64x64 sprite, etc.). Allows inhands/worn sprites to be of any size, but still centered on a mob properly - var/worn_x_dimension = 32 - ///Dimensions of the icon file used when this item is worn, eg: hats.dmi (32x32 sprite, 64x64 sprite, etc.). Allows inhands/worn sprites to be of any size, but still centered on a mob properly - var/worn_y_dimension = 32 - ///Same as for [worn_x_dimension][/obj/item/var/worn_x_dimension] but for inhands, uses the lefthand_ and righthand_ file vars - var/inhand_x_dimension = 32 - ///Same as for [worn_y_dimension][/obj/item/var/worn_y_dimension] but for inhands, uses the lefthand_ and righthand_ file vars - var/inhand_y_dimension = 32 - - - max_integrity = 200 - - obj_flags = NONE - ///Item flags for the item - var/item_flags = NONE - - ///Sound played when you hit something with the item - var/hitsound - ///Played when the item is used, for example tools - var/usesound - ///Used when yate into a mob - var/mob_throw_hit_sound - ///Sound used when equipping the item into a valid slot - var/equip_sound - ///Sound uses when picking the item up (into your hands) - var/pickup_sound - ///Sound uses when dropping the item, or when its thrown. - var/drop_sound - - ///How large is the object, used for stuff like whether it can fit in backpacks or not - var/w_class = WEIGHT_CLASS_NORMAL - ///This is used to determine on which slots an item can fit. - var/slot_flags = 0 - pass_flags = PASSTABLE - pressure_resistance = 4 - var/obj/item/master = null - - ///flags which determine which body parts are protected from heat. [See here][HEAD] - var/heat_protection = 0 - ///flags which determine which body parts are protected from cold. [See here][HEAD] - var/cold_protection = 0 - ///Set this variable to determine up to which temperature (IN KELVIN) the item protects against heat damage. Keep at null to disable protection. Only protects areas set by heat_protection flags - var/max_heat_protection_temperature - ///Set this variable to determine down to which temperature (IN KELVIN) the item protects against cold damage. 0 is NOT an acceptable number due to if(varname) tests!! Keep at null to disable protection. Only protects areas set by cold_protection flags - var/min_cold_protection_temperature - - ///list of /datum/action's that this item has. - var/list/actions - ///list of paths of action datums to give to the item on New(). - var/list/actions_types - - //Since any item can now be a piece of clothing, this has to be put here so all items share it. - ///This flag is used to determine when items in someone's inventory cover others. IE helmets making it so you can't see glasses, etc. - var/flags_inv - ///you can see someone's mask through their transparent visor, but you can't reach it - var/transparent_protection = NONE - - ///flags for what should be done when you click on the item, default is picking it up - var/interaction_flags_item = INTERACT_ITEM_ATTACK_HAND_PICKUP - - ///What body parts are covered by the clothing when you wear it - var/body_parts_covered = 0 - ///Literally does nothing right now - var/gas_transfer_coefficient = 1 - /// How likely a disease or chemical is to get through a piece of clothing - var/permeability_coefficient = 1 - /// for electrical admittance/conductance (electrocution checks and shit) - var/siemens_coefficient = 1 - /// How much clothing is slowing you down. Negative values speeds you up - var/slowdown = 0 - ///percentage of armour effectiveness to remove - var/armour_penetration = 0 - ///What objects the suit storage can store - var/list/allowed = null - ///In deciseconds, how long an item takes to equip; counts only for normal clothing slots, not pockets etc. - var/equip_delay_self = 0 - ///In deciseconds, how long an item takes to put on another person - var/equip_delay_other = 20 - ///In deciseconds, how long an item takes to remove from another person - var/strip_delay = 40 - ///How long it takes to resist out of the item (cuffs and such) - var/breakouttime = 0 - - ///Used in [atom/proc/attackby] to say how something was attacked "[x] has been [z.attack_verb] by [y] with [z]" - var/list/attack_verb - ///list() of species types, if a species cannot put items in a certain slot, but species type is in list, it will be able to wear that item - var/list/species_exception = null - - ///Who threw the item - var/mob/thrownby = null - - ///the icon to indicate this object is being dragged - mouse_drag_pointer = MOUSE_ACTIVE_POINTER - - ///Does it embed and if yes, what kind of embed - var/list/embedding = NONE - - ///for flags such as [GLASSESCOVERSEYES] - var/flags_cover = 0 - var/heat = 0 - ///All items with sharpness of IS_SHARP or higher will automatically get the butchering component. - var/sharpness = IS_BLUNT - - ///How a tool acts when you use it on something, such as wirecutters cutting wires while multitools measure power - var/tool_behaviour = NONE - ///How fast does the tool work - var/toolspeed = 1 - - var/block_chance = 0 - var/hit_reaction_chance = 0 //If you want to have something unrelated to blocking/armour piercing etc. Maybe not needed, but trying to think ahead/allow more freedom - ///In tiles, how far this weapon can reach; 1 for adjacent, which is default - var/reach = 1 - - ///The list of slots by priority. equip_to_appropriate_slot() uses this list. Doesn't matter if a mob type doesn't have a slot. For default list, see [/mob/proc/equip_to_appropriate_slot] - var/list/slot_equipment_priority = null - - ///Reference to the datum that determines whether dogs can wear the item: Needs to be in /obj/item because corgis can wear a lot of non-clothing items - var/datum/dog_fashion/dog_fashion = null - - //Tooltip vars - ///string form of an item's force. Edit this var only to set a custom force string - var/force_string - var/last_force_string_check = 0 - var/tip_timer - - ///Determines who can shoot this - var/trigger_guard = TRIGGER_GUARD_NONE - - ///Used as the dye color source in the washing machine only (at the moment). Can be a hex color or a key corresponding to a registry entry, see washing_machine.dm - var/dye_color - ///Whether the item is unaffected by standard dying. - var/undyeable = FALSE - ///What dye registry should be looked at when dying this item; see washing_machine.dm - var/dying_key - - ///Grinder var:A reagent list containing the reagents this item produces when ground up in a grinder - this can be an empty list to allow for reagent transferring only - var/list/grind_results - //Grinder var:A reagent list containing blah blah... but when JUICED in a grinder! - var/list/juice_results - - var/canMouseDown = FALSE - - -/obj/item/Initialize() - - if(attack_verb) - attack_verb = typelist("attack_verb", attack_verb) - - . = ..() - for(var/path in actions_types) - new path(src) - actions_types = null - - if(force_string) - item_flags |= FORCE_STRING_OVERRIDE - - if(!hitsound) - if(damtype == "fire") - hitsound = 'sound/items/welder.ogg' - if(damtype == "brute") - hitsound = "swing_hit" - -/obj/item/Destroy() - item_flags &= ~DROPDEL //prevent reqdels - if(ismob(loc)) - var/mob/m = loc - m.temporarilyRemoveItemFromInventory(src, TRUE) - for(var/X in actions) - qdel(X) - return ..() - -/obj/item/proc/check_allowed_items(atom/target, not_inside, target_self) - if(((src in target) && !target_self) || (!isturf(target.loc) && !isturf(target) && not_inside)) - return 0 - else - return 1 - -/obj/item/blob_act(obj/structure/blob/B) - if(B && B.loc == loc) - qdel(src) - -/obj/item/ComponentInitialize() - . = ..() - // this proc says it's for initializing components, but we're initializing elements too because it's you and me against the world >:) - if(!LAZYLEN(embedding)) - if(GLOB.embedpocalypse) - embedding = EMBED_POINTY - name = "pointy [name]" - else if(GLOB.stickpocalypse) - embedding = EMBED_HARMLESS - name = "sticky [name]" - - updateEmbedding() - - if(GLOB.rpg_loot_items) - AddComponent(/datum/component/fantasy) - - if(sharpness) //give sharp objects butchering functionality, for consistency - AddComponent(/datum/component/butchering, 80 * toolspeed) - -/**Makes cool stuff happen when you suicide with an item - * - *Outputs a creative message and then return the damagetype done - * Arguments: - * * user: The mob that is suiciding - */ -/obj/item/proc/suicide_act(mob/user) - return - -/obj/item/verb/move_to_top() - set name = "Move To Top" - set category = "Object" - set src in oview(1) - - if(!isturf(loc) || usr.stat || usr.restrained()) - return - - if(isliving(usr)) - var/mob/living/L = usr - if(!(L.mobility_flags & MOBILITY_PICKUP)) - return - - var/turf/T = loc - loc = null - loc = T - -/obj/item/examine(mob/user) //This might be spammy. Remove? - . = ..() - - . += "[gender == PLURAL ? "They are" : "It is"] a [weightclass2text(w_class)] item." - - if(resistance_flags & INDESTRUCTIBLE) - . += "[src] seems extremely robust! It'll probably withstand anything that could happen to it!" - else - if(resistance_flags & LAVA_PROOF) - . += "[src] is made of an extremely heat-resistant material, it'd probably be able to withstand lava!" - if(resistance_flags & (ACID_PROOF | UNACIDABLE)) - . += "[src] looks pretty robust! It'd probably be able to withstand acid!" - if(resistance_flags & FREEZE_PROOF) - . += "[src] is made of cold-resistant materials." - if(resistance_flags & FIRE_PROOF) - . += "[src] is made of fire-retardant materials." - - if(!user.research_scanner) - return - - /// Research prospects, including boostable nodes and point values. Deliver to a console to know whether the boosts have already been used. - var/list/research_msg = list("Research prospects: ") - ///Separator between the items on the list - var/sep = "" - ///Nodes that can be boosted - var/list/boostable_nodes = techweb_item_boost_check(src) - if (boostable_nodes) - for(var/id in boostable_nodes) - var/datum/techweb_node/node = SSresearch.techweb_node_by_id(id) - if(!node) - continue - research_msg += sep - research_msg += node.display_name - sep = ", " - var/list/points = techweb_item_point_check(src) - if (length(points)) - sep = ", " - research_msg += techweb_point_display_generic(points) - - if (!sep) // nothing was shown - research_msg += "None" - - // Extractable materials. Only shows the names, not the amounts. - research_msg += ".
                    Extractable materials: " - if (length(custom_materials)) - sep = "" - for(var/mat in custom_materials) - research_msg += sep - research_msg += CallMaterialName(mat) - sep = ", " - else - research_msg += "None" - research_msg += "." - . += research_msg.Join() - -/obj/item/interact(mob/user) - add_fingerprint(user) - ui_interact(user) - -/obj/item/ui_act(action, list/params) - add_fingerprint(usr) - return ..() - -/obj/item/attack_hand(mob/user) - . = ..() - if(.) - return - if(!user) - return - if(anchored) - return - - if(resistance_flags & ON_FIRE) - var/mob/living/carbon/C = user - var/can_handle_hot = FALSE - if(!istype(C)) - can_handle_hot = TRUE - else if(C.gloves && (C.gloves.max_heat_protection_temperature > 360)) - can_handle_hot = TRUE - else if(HAS_TRAIT(C, TRAIT_RESISTHEAT) || HAS_TRAIT(C, TRAIT_RESISTHEATHANDS)) - can_handle_hot = TRUE - - if(can_handle_hot) - extinguish() - to_chat(user, "You put out the fire on [src].") - else - to_chat(user, "You burn your hand on [src]!") - var/obj/item/bodypart/affecting = C.get_bodypart("[(user.active_hand_index % 2 == 0) ? "r" : "l" ]_arm") - if(affecting && affecting.receive_damage( 0, 5 )) // 5 burn damage - C.update_damage_overlays() - return - - if(acid_level > 20 && !ismob(loc))// so we can still remove the clothes on us that have acid. - var/mob/living/carbon/C = user - if(istype(C)) - if(!C.gloves || (!(C.gloves.resistance_flags & (UNACIDABLE|ACID_PROOF)))) - to_chat(user, "The acid on [src] burns your hand!") - var/obj/item/bodypart/affecting = C.get_bodypart("[(user.active_hand_index % 2 == 0) ? "r" : "l" ]_arm") - if(affecting && affecting.receive_damage( 0, 5 )) // 5 burn damage - C.update_damage_overlays() - - if(!(interaction_flags_item & INTERACT_ITEM_ATTACK_HAND_PICKUP)) //See if we're supposed to auto pickup. - return - - //Heavy gravity makes picking up things very slow. - var/grav = user.has_gravity() - if(grav > STANDARD_GRAVITY) - var/grav_power = min(3,grav - STANDARD_GRAVITY) - to_chat(user,"You start picking up [src]...") - if(!do_mob(user,src,30*grav_power)) - return - - - //If the item is in a storage item, take it out - SEND_SIGNAL(loc, COMSIG_TRY_STORAGE_TAKE, src, user.loc, TRUE) - if(QDELETED(src)) //moving it out of the storage to the floor destroyed it. - return - - if(throwing) - throwing.finalize(FALSE) - if(loc == user) - if(!allow_attack_hand_drop(user) || !user.temporarilyRemoveItemFromInventory(src)) - return - - pickup(user) - add_fingerprint(user) - if(!user.put_in_active_hand(src, FALSE, FALSE)) - user.dropItemToGround(src) - -/obj/item/proc/allow_attack_hand_drop(mob/user) - return TRUE - -/obj/item/attack_paw(mob/user) - if(!user) - return - if(anchored) - return - - SEND_SIGNAL(loc, COMSIG_TRY_STORAGE_TAKE, src, user.loc, TRUE) - - if(throwing) - throwing.finalize(FALSE) - if(loc == user) - if(!user.temporarilyRemoveItemFromInventory(src)) - return - - pickup(user) - add_fingerprint(user) - if(!user.put_in_active_hand(src, FALSE, FALSE)) - user.dropItemToGround(src) - -/obj/item/attack_alien(mob/user) - var/mob/living/carbon/alien/A = user - - if(!A.has_fine_manipulation) - if(src in A.contents) // To stop Aliens having items stuck in their pockets - A.dropItemToGround(src) - to_chat(user, "Your claws aren't capable of such fine manipulation!") - return - attack_paw(A) - -/obj/item/attack_ai(mob/user) - if(istype(src.loc, /obj/item/robot_module)) - //If the item is part of a cyborg module, equip it - if(!iscyborg(user)) - return - var/mob/living/silicon/robot/R = user - if(!R.low_power_mode) //can't equip modules with an empty cell. - R.activate_module(src) - R.hud_used.update_robot_modules_display() - -/obj/item/proc/GetDeconstructableContents() - return GetAllContents() - src - -// afterattack() and attack() prototypes moved to _onclick/item_attack.dm for consistency - -/obj/item/proc/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - SEND_SIGNAL(src, COMSIG_ITEM_HIT_REACT, args) - if(prob(final_block_chance)) - owner.visible_message("[owner] blocks [attack_text] with [src]!") - return 1 - return 0 - -/obj/item/proc/talk_into(mob/M, input, channel, spans, datum/language/language) - return ITALICS | REDUCE_RANGE - -/obj/item/proc/dropped(mob/user, silent = FALSE) - SHOULD_CALL_PARENT(1) - for(var/X in actions) - var/datum/action/A = X - A.Remove(user) - if(item_flags & DROPDEL) - qdel(src) - item_flags &= ~IN_INVENTORY - SEND_SIGNAL(src, COMSIG_ITEM_DROPPED,user) - if(!silent) - playsound(src, drop_sound, DROP_SOUND_VOLUME, ignore_walls = FALSE) - user?.update_equipment_speed_mods() - -/// called just as an item is picked up (loc is not yet changed) -/obj/item/proc/pickup(mob/user) - SHOULD_CALL_PARENT(1) - SEND_SIGNAL(src, COMSIG_ITEM_PICKUP, user) - item_flags |= IN_INVENTORY - -/// called when "found" in pockets and storage items. Returns 1 if the search should end. -/obj/item/proc/on_found(mob/finder) - return - -/** - *called after an item is placed in an equipment slot - - * Arguments: - * * user is mob that equipped it - * * slot uses the slot_X defines found in setup.dm for items that can be placed in multiple slots - * * Initial is used to indicate whether or not this is the initial equipment (job datums etc) or just a player doing it - */ -/obj/item/proc/equipped(mob/user, slot, initial = FALSE) - SHOULD_CALL_PARENT(1) - SEND_SIGNAL(src, COMSIG_ITEM_EQUIPPED, user, slot) - for(var/X in actions) - var/datum/action/A = X - if(item_action_slot_check(slot, user)) //some items only give their actions buttons when in a specific slot. - A.Grant(user) - item_flags |= IN_INVENTORY - if(!initial) - if(equip_sound && (slot_flags & slot)) - playsound(src, equip_sound, EQUIP_SOUND_VOLUME, TRUE, ignore_walls = FALSE) - else if(slot == ITEM_SLOT_HANDS) - playsound(src, pickup_sound, PICKUP_SOUND_VOLUME, ignore_walls = FALSE) - user.update_equipment_speed_mods() - -///sometimes we only want to grant the item's action if it's equipped in a specific slot. -/obj/item/proc/item_action_slot_check(slot, mob/user) - if(slot == ITEM_SLOT_BACKPACK || slot == ITEM_SLOT_LEGCUFFED) //these aren't true slots, so avoid granting actions there - return FALSE - return TRUE - -/** - *the mob M is attempting to equip this item into the slot passed through as 'slot'. Return 1 if it can do this and 0 if it can't. - *if this is being done by a mob other than M, it will include the mob equipper, who is trying to equip the item to mob M. equipper will be null otherwise. - *If you are making custom procs but would like to retain partial or complete functionality of this one, include a 'return ..()' to where you want this to happen. - * Arguments: - * * disable_warning to TRUE if you wish it to not give you text outputs. - * * slot is the slot we are trying to equip to - * * equipper is the mob trying to equip the item - * * bypass_equip_delay_self for whether we want to bypass the equip delay - */ -/obj/item/proc/mob_can_equip(mob/living/M, mob/living/equipper, slot, disable_warning = FALSE, bypass_equip_delay_self = FALSE, swap = FALSE) - if(!M) - return FALSE - - return M.can_equip(src, slot, disable_warning, bypass_equip_delay_self, swap) - -/obj/item/verb/verb_pickup() - set src in oview(1) - set category = "Object" - set name = "Pick up" - - if(usr.incapacitated() || !Adjacent(usr)) - return - - if(isliving(usr)) - var/mob/living/L = usr - if(!(L.mobility_flags & MOBILITY_PICKUP)) - return - - if(usr.get_active_held_item() == null) // Let me know if this has any problems -Yota - usr.UnarmedAttack(src) - -/** - *This proc is executed when someone clicks the on-screen UI button. - *The default action is attack_self(). - *Checks before we get to here are: mob is alive, mob is not restrained, stunned, asleep, resting, laying, item is on the mob. - */ -/obj/item/proc/ui_action_click(mob/user, actiontype) - attack_self(user) - -///This proc determines if and at what an object will reflect energy projectiles if it's in l_hand,r_hand or wear_suit -/obj/item/proc/IsReflect(var/def_zone) - return FALSE - -/obj/item/proc/eyestab(mob/living/carbon/M, mob/living/carbon/user) - - var/is_human_victim - var/obj/item/bodypart/affecting = M.get_bodypart(BODY_ZONE_HEAD) - if(ishuman(M)) - if(!affecting) //no head! - return - is_human_victim = TRUE - - if(M.is_eyes_covered()) - // you can't stab someone in the eyes wearing a mask! - to_chat(user, "You're going to need to remove [M.p_their()] eye protection first!") - return - - if(isalien(M))//Aliens don't have eyes./N slimes also don't have eyes! - to_chat(user, "You cannot locate any eyes on this creature!") - return - - if(isbrain(M)) - to_chat(user, "You cannot locate any organic eyes on this brain!") - return - - src.add_fingerprint(user) - - playsound(loc, src.hitsound, 30, TRUE, -1) - - user.do_attack_animation(M) - - if(M != user) - M.visible_message("[user] stabs [M] in the eye with [src]!", \ - "[user] stabs you in the eye with [src]!") - else - user.visible_message( \ - "[user] stabs [user.p_them()]self in the eyes with [src]!", \ - "You stab yourself in the eyes with [src]!" \ - ) - if(is_human_victim) - var/mob/living/carbon/human/U = M - U.apply_damage(7, BRUTE, affecting) - - else - M.take_bodypart_damage(7) - - SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "eye_stab", /datum/mood_event/eye_stab) - - log_combat(user, M, "attacked", "[src.name]", "(INTENT: [uppertext(user.a_intent)])") - - var/obj/item/organ/eyes/eyes = M.getorganslot(ORGAN_SLOT_EYES) - if (!eyes) - return - M.adjust_blurriness(3) - eyes.applyOrganDamage(rand(2,4)) - if(eyes.damage >= 10) - M.adjust_blurriness(15) - if(M.stat != DEAD) - to_chat(M, "Your eyes start to bleed profusely!") - if(!(M.is_blind() || HAS_TRAIT(M, TRAIT_NEARSIGHT))) - to_chat(M, "You become nearsighted!") - M.become_nearsighted(EYE_DAMAGE) - if(prob(50)) - if(M.stat != DEAD) - if(M.drop_all_held_items()) - to_chat(M, "You drop what you're holding and clutch at your eyes!") - M.adjust_blurriness(10) - M.Unconscious(20) - M.Paralyze(40) - if (prob(eyes.damage - 10 + 1)) - M.become_blind(EYE_DAMAGE) - to_chat(M, "You go blind!") - -/obj/item/singularity_pull(S, current_size) - ..() - if(current_size >= STAGE_FOUR) - throw_at(S,14,3, spin=0) - else - return - -/obj/item/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - if(hit_atom && !QDELETED(hit_atom)) - SEND_SIGNAL(src, COMSIG_MOVABLE_IMPACT, hit_atom, throwingdatum) - if(get_temperature() && isliving(hit_atom)) - var/mob/living/L = hit_atom - L.IgniteMob() - var/itempush = 1 - if(w_class < 4) - itempush = 0 //too light to push anything - if(istype(hit_atom, /mob/living)) //Living mobs handle hit sounds differently. - var/volume = get_volume_by_throwforce_and_or_w_class() - if (throwforce > 0) - if (mob_throw_hit_sound) - playsound(hit_atom, mob_throw_hit_sound, volume, TRUE, -1) - else if(hitsound) - playsound(hit_atom, hitsound, volume, TRUE, -1) - else - playsound(hit_atom, 'sound/weapons/genhit.ogg',volume, TRUE, -1) - else - playsound(hit_atom, 'sound/weapons/throwtap.ogg', 1, volume, -1) - - else - playsound(src, drop_sound, YEET_SOUND_VOLUME, ignore_walls = FALSE) - return hit_atom.hitby(src, 0, itempush, throwingdatum=throwingdatum) - -/obj/item/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, gentle = FALSE, quickstart = TRUE) - if(HAS_TRAIT(src, TRAIT_NODROP)) - return - thrownby = thrower - callback = CALLBACK(src, .proc/after_throw, callback) //replace their callback with our own - . = ..(target, range, speed, thrower, spin, diagonals_first, callback, force, gentle, quickstart = quickstart) - - -/obj/item/proc/after_throw(datum/callback/callback) - if (callback) //call the original callback - . = callback.Invoke() - item_flags &= ~IN_INVENTORY - if(!pixel_y && !pixel_x) - pixel_x = rand(-8,8) - pixel_y = rand(-8,8) - - -/obj/item/proc/remove_item_from_storage(atom/newLoc) //please use this if you're going to snowflake an item out of a obj/item/storage - if(!newLoc) - return FALSE - if(SEND_SIGNAL(loc, COMSIG_CONTAINS_STORAGE)) - return SEND_SIGNAL(loc, COMSIG_TRY_STORAGE_TAKE, src, newLoc, TRUE) - return FALSE - -/obj/item/proc/get_belt_overlay() //Returns the icon used for overlaying the object on a belt - return mutable_appearance('icons/obj/clothing/belt_overlays.dmi', icon_state) - -/obj/item/proc/update_slot_icon() - if(!ismob(loc)) - return - var/mob/owner = loc - var/flags = slot_flags - if(flags & ITEM_SLOT_OCLOTHING) - owner.update_inv_wear_suit() - if(flags & ITEM_SLOT_ICLOTHING) - owner.update_inv_w_uniform() - if(flags & ITEM_SLOT_GLOVES) - owner.update_inv_gloves() - if(flags & ITEM_SLOT_EYES) - owner.update_inv_glasses() - if(flags & ITEM_SLOT_EARS) - owner.update_inv_ears() - if(flags & ITEM_SLOT_MASK) - owner.update_inv_wear_mask() - if(flags & ITEM_SLOT_HEAD) - owner.update_inv_head() - if(flags & ITEM_SLOT_FEET) - owner.update_inv_shoes() - if(flags & ITEM_SLOT_ID) - owner.update_inv_wear_id() - if(flags & ITEM_SLOT_BELT) - owner.update_inv_belt() - if(flags & ITEM_SLOT_BACK) - owner.update_inv_back() - if(flags & ITEM_SLOT_NECK) - owner.update_inv_neck() - -///Returns the temperature of src. If you want to know if an item is hot use this proc. -/obj/item/proc/get_temperature() - return heat - -///Returns the sharpness of src. If you want to get the sharpness of an item use this. -/obj/item/proc/get_sharpness() - return sharpness - -/obj/item/proc/get_dismemberment_chance(obj/item/bodypart/affecting) - if(affecting.can_dismember(src)) - if((sharpness || damtype == BURN || (damtype == BRUTE && (affecting.owner.dna && affecting.owner.dna.species && (TRAIT_EASYDISMEMBER in affecting.owner.dna.species.species_traits)))) && w_class >= WEIGHT_CLASS_NORMAL && force >= 10) - . = force * (affecting.get_damage() / affecting.max_damage) - -/obj/item/proc/get_dismember_sound() - if(damtype == BURN) - . = 'sound/weapons/sear.ogg' - else - . = "desceration" - -/obj/item/proc/open_flame(flame_heat=700) - var/turf/location = loc - if(ismob(location)) - var/mob/M = location - var/success = FALSE - if(src == M.get_item_by_slot(ITEM_SLOT_MASK)) - success = TRUE - if(success) - location = get_turf(M) - if(isturf(location)) - location.hotspot_expose(flame_heat, 5) - -/obj/item/proc/ignition_effect(atom/A, mob/user) - if(get_temperature()) - . = "[user] lights [A] with [src]." - else - . = "" - -/obj/item/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) - return - -/obj/item/attack_hulk(mob/living/carbon/human/user) - return FALSE - -/obj/item/attack_animal(mob/living/simple_animal/M) - if (obj_flags & CAN_BE_HIT) - return ..() - return 0 - -/obj/item/mech_melee_attack(obj/mecha/M) - return 0 - -/obj/item/burn() - if(!QDELETED(src)) - var/turf/T = get_turf(src) - var/ash_type = /obj/effect/decal/cleanable/ash - if(w_class == WEIGHT_CLASS_HUGE || w_class == WEIGHT_CLASS_GIGANTIC) - ash_type = /obj/effect/decal/cleanable/ash/large - var/obj/effect/decal/cleanable/ash/A = new ash_type(T) - A.desc += "\nLooks like this used to be \an [name] some time ago." - ..() - -/obj/item/acid_melt() - if(!QDELETED(src)) - var/turf/T = get_turf(src) - var/obj/effect/decal/cleanable/molten_object/MO = new(T) - MO.pixel_x = rand(-16,16) - MO.pixel_y = rand(-16,16) - MO.desc = "Looks like this was \an [src] some time ago." - ..() - -/obj/item/proc/microwave_act(obj/machinery/microwave/M) - SEND_SIGNAL(src, COMSIG_ITEM_MICROWAVE_ACT, M) - if(istype(M) && M.dirty < 100) - M.dirty++ - -/obj/item/proc/on_mob_death(mob/living/L, gibbed) - -/obj/item/proc/grind_requirements(obj/machinery/reagentgrinder/R) //Used to check for extra requirements for grinding an object - return TRUE - -///Called BEFORE the object is ground up - use this to change grind results based on conditions. Use "return -1" to prevent the grinding from occurring -/obj/item/proc/on_grind() - -/obj/item/proc/on_juice() - -/obj/item/proc/set_force_string() - switch(force) - if(0 to 4) - force_string = "very low" - if(4 to 7) - force_string = "low" - if(7 to 10) - force_string = "medium" - if(10 to 11) - force_string = "high" - if(11 to 20) //12 is the force of a toolbox - force_string = "robust" - if(20 to 25) - force_string = "very robust" - else - force_string = "exceptionally robust" - last_force_string_check = force - -/obj/item/proc/openTip(location, control, params, user) - if(last_force_string_check != force && !(item_flags & FORCE_STRING_OVERRIDE)) - set_force_string() - if(!(item_flags & FORCE_STRING_OVERRIDE)) - openToolTip(user,src,params,title = name,content = "[desc]
                    [force ? "Force: [force_string]" : ""]",theme = "") - else - openToolTip(user,src,params,title = name,content = "[desc]
                    Force: [force_string]",theme = "") - -/obj/item/MouseEntered(location, control, params) - if((item_flags & IN_INVENTORY || item_flags & IN_STORAGE) && usr.client.prefs.enable_tips && !QDELETED(src)) - var/timedelay = usr.client.prefs.tip_delay/100 - var/user = usr - tip_timer = addtimer(CALLBACK(src, .proc/openTip, location, control, params, user), timedelay, TIMER_STOPPABLE)//timer takes delay in deciseconds, but the pref is in milliseconds. dividing by 100 converts it. - -/obj/item/MouseExited() - deltimer(tip_timer)//delete any in-progress timer if the mouse is moved off the item before it finishes - closeToolTip(usr) - - -/// Called when a mob tries to use the item as a tool.Handles most checks. -/obj/item/proc/use_tool(atom/target, mob/living/user, delay, amount=0, volume=0, datum/callback/extra_checks) - // No delay means there is no start message, and no reason to call tool_start_check before use_tool. - // Run the start check here so we wouldn't have to call it manually. - if(!delay && !tool_start_check(user, amount)) - return - - var/skill_modifier = 1 - - if(tool_behaviour == TOOL_MINING && ishuman(user)) - var/mob/living/carbon/human/H = user - skill_modifier = H.mind.get_skill_modifier(/datum/skill/mining, SKILL_SPEED_MODIFIER) - - if(H.mind.get_skill_level(/datum/skill/mining) >= SKILL_LEVEL_JOURNEYMAN && prob(H.mind.get_skill_modifier(/datum/skill/mining, SKILL_PROBS_MODIFIER))) // we check if the skill level is greater than Journeyman and then we check for the probality for that specific level. - mineral_scan_pulse(get_turf(H), SKILL_LEVEL_JOURNEYMAN - 2) //SKILL_LEVEL_JOURNEYMAN = 3 So to get range of 1+ we have to subtract 2 from it,. - - delay *= toolspeed * skill_modifier - - - // Play tool sound at the beginning of tool usage. - play_tool_sound(target, volume) - - if(delay) - // Create a callback with checks that would be called every tick by do_after. - var/datum/callback/tool_check = CALLBACK(src, .proc/tool_check_callback, user, amount, extra_checks) - - if(ismob(target)) - if(!do_mob(user, target, delay, extra_checks=tool_check)) - return - - else - if(!do_after(user, delay, target=target, extra_checks=tool_check)) - return - else - // Invoke the extra checks once, just in case. - if(extra_checks && !extra_checks.Invoke()) - return - - // Use tool's fuel, stack sheets or charges if amount is set. - if(amount && !use(amount)) - return - - // Play tool sound at the end of tool usage, - // but only if the delay between the beginning and the end is not too small - if(delay >= MIN_TOOL_SOUND_DELAY) - play_tool_sound(target, volume) - - return TRUE - -/// Called before [obj/item/proc/use_tool] if there is a delay, or by [obj/item/proc/use_tool] if there isn't. Only ever used by welding tools and stacks, so it's not added on any other [obj/item/proc/use_tool] checks. -/obj/item/proc/tool_start_check(mob/living/user, amount=0) - . = tool_use_check(user, amount) - if(.) - SEND_SIGNAL(src, COMSIG_TOOL_START_USE, user) - -/// A check called by [/obj/item/proc/tool_start_check] once, and by use_tool on every tick of delay. -/obj/item/proc/tool_use_check(mob/living/user, amount) - return !amount - -/// Generic use proc. Depending on the item, it uses up fuel, charges, sheets, etc. Returns TRUE on success, FALSE on failure. -/obj/item/proc/use(used) - return !used - -/// Plays item's usesound, if any. -/obj/item/proc/play_tool_sound(atom/target, volume=50) - if(target && usesound && volume) - var/played_sound = usesound - - if(islist(usesound)) - played_sound = pick(usesound) - - playsound(target, played_sound, volume, TRUE) - -/// Used in a callback that is passed by use_tool into do_after call. Do not override, do not call manually. -/obj/item/proc/tool_check_callback(mob/living/user, amount, datum/callback/extra_checks) - SHOULD_NOT_OVERRIDE(TRUE) - . = tool_use_check(user, amount) && (!extra_checks || extra_checks.Invoke()) - if(.) - SEND_SIGNAL(src, COMSIG_TOOL_IN_USE, user) - -/// Returns a numeric value for sorting items used as parts in machines, so they can be replaced by the rped -/obj/item/proc/get_part_rating() - return 0 - -/obj/item/doMove(atom/destination) - if (ismob(loc)) - var/mob/M = loc - var/hand_index = M.get_held_index_of_item(src) - if(hand_index) - M.held_items[hand_index] = null - M.update_inv_hands() - if(M.client) - M.client.screen -= src - layer = initial(layer) - plane = initial(plane) - appearance_flags &= ~NO_CLIENT_COLOR - dropped(M, FALSE) - return ..() - -/obj/item/proc/embedded(mob/living/carbon/human/embedded_mob) - return - -/obj/item/proc/unembedded() - if(item_flags & DROPDEL) - QDEL_NULL(src) - return TRUE - -/obj/item/proc/canStrip(mob/stripper, mob/owner) - SHOULD_BE_PURE(TRUE) - return !HAS_TRAIT(src, TRAIT_NODROP) - -/obj/item/proc/doStrip(mob/stripper, mob/owner) - return owner.dropItemToGround(src) - -///Does the current embedding var meet the criteria for being harmless? Namely, does it have a pain multiplier and jostle pain mult of 0? If so, return true. -/obj/item/proc/isEmbedHarmless() - if(embedding) - return !isnull(embedding["pain_mult"]) && !isnull(embedding["jostle_pain_mult"]) && embedding["pain_mult"] == 0 && embedding["jostle_pain_mult"] == 0 - -///In case we want to do something special (like self delete) upon failing to embed in something, return true -/obj/item/proc/failedEmbed() - if(item_flags & DROPDEL) - QDEL_NULL(src) - return TRUE - -///Called by the carbon throw_item() proc. Returns null if the item negates the throw, or a reference to the thing to suffer the throw else. -/obj/item/proc/on_thrown(mob/living/carbon/user, atom/target) - if((item_flags & ABSTRACT) || HAS_TRAIT(src, TRAIT_NODROP)) - return - user.dropItemToGround(src, silent = TRUE) - if(throwforce && HAS_TRAIT(user, TRAIT_PACIFISM)) - to_chat(user, "You set [src] down gently on the ground.") - return - return src - -/** - * tryEmbed() is for when you want to try embedding something without dealing with the damage + hit messages of calling hitby() on the item while targetting the target. - * - * Really, this is used mostly with projectiles with shrapnel payloads, from [/datum/element/embed/proc/checkEmbedProjectile], and called on said shrapnel. Mostly acts as an intermediate between different embed elements. - * - * Arguments: - * * target- Either a body part, a carbon, or a closed turf. What are we hitting? - * * forced- Do we want this to go through 100%? - */ -/obj/item/proc/tryEmbed(atom/target, forced=FALSE, silent=FALSE) - if(!isbodypart(target) && !iscarbon(target) && !isclosedturf(target)) - return - if(!forced && !LAZYLEN(embedding)) - return - - if(SEND_SIGNAL(src, COMSIG_EMBED_TRY_FORCE, target, forced, silent)) - return TRUE - failedEmbed() - -///For when you want to disable an item's embedding capabilities (like transforming weapons and such), this proc will detach any active embed elements from it. -/obj/item/proc/disableEmbedding() - SEND_SIGNAL(src, COMSIG_ITEM_DISABLE_EMBED) - return - -///For when you want to add/update the embedding on an item. Uses the vars in [/obj/item/embedding], and defaults to config values for values that aren't set. Will automatically detach previous embed elements on this item. -/obj/item/proc/updateEmbedding() - if(!islist(embedding) || !LAZYLEN(embedding)) - return - - AddElement(/datum/element/embed,\ - embed_chance = (!isnull(embedding["embed_chance"]) ? embedding["embed_chance"] : EMBED_CHANCE),\ - fall_chance = (!isnull(embedding["fall_chance"]) ? embedding["fall_chance"] : EMBEDDED_ITEM_FALLOUT),\ - pain_chance = (!isnull(embedding["pain_chance"]) ? embedding["pain_chance"] : EMBEDDED_PAIN_CHANCE),\ - pain_mult = (!isnull(embedding["pain_mult"]) ? embedding["pain_mult"] : EMBEDDED_PAIN_MULTIPLIER),\ - remove_pain_mult = (!isnull(embedding["remove_pain_mult"]) ? embedding["remove_pain_mult"] : EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER),\ - rip_time = (!isnull(embedding["rip_time"]) ? embedding["rip_time"] : EMBEDDED_UNSAFE_REMOVAL_TIME),\ - ignore_throwspeed_threshold = (!isnull(embedding["ignore_throwspeed_threshold"]) ? embedding["ignore_throwspeed_threshold"] : FALSE),\ - impact_pain_mult = (!isnull(embedding["impact_pain_mult"]) ? embedding["impact_pain_mult"] : EMBEDDED_IMPACT_PAIN_MULTIPLIER),\ - jostle_chance = (!isnull(embedding["jostle_chance"]) ? embedding["jostle_chance"] : EMBEDDED_JOSTLE_CHANCE),\ - jostle_pain_mult = (!isnull(embedding["jostle_pain_mult"]) ? embedding["jostle_pain_mult"] : EMBEDDED_JOSTLE_PAIN_MULTIPLIER),\ - pain_stam_pct = (!isnull(embedding["pain_stam_pct"]) ? embedding["pain_stam_pct"] : EMBEDDED_PAIN_STAM_PCT),\ - embed_chance_turf_mod = (!isnull(embedding["embed_chance_turf_mod"]) ? embedding["embed_chance_turf_mod"] : EMBED_CHANCE_TURF_MOD)) - return TRUE +GLOBAL_DATUM_INIT(fire_overlay, /mutable_appearance, mutable_appearance('icons/effects/fire.dmi', "fire")) + +GLOBAL_VAR_INIT(rpg_loot_items, FALSE) +// if true, everyone item when created will have its name changed to be +// more... RPG-like. + +GLOBAL_VAR_INIT(stickpocalypse, FALSE) // if true, all non-embeddable items will be able to harmlessly stick to people when thrown +GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to embed in people, takes precedence over stickpocalypse + +/obj/item + name = "item" + icon = 'icons/obj/items_and_weapons.dmi' + blocks_emissive = EMISSIVE_BLOCK_GENERIC + ///icon state name for inhand overlays + var/item_state = null + ///Icon file for left hand inhand overlays + var/lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' + ///Icon file for right inhand overlays + var/righthand_file = 'icons/mob/inhands/items_righthand.dmi' + + ///Icon file for mob worn overlays. + ///no var for state because it should *always* be the same as icon_state + var/icon/mob_overlay_icon + ///Forced mob worn layer instead of the standard preferred ssize. + var/alternate_worn_layer + + ///Dimensions of the icon file used when this item is worn, eg: hats.dmi (32x32 sprite, 64x64 sprite, etc.). Allows inhands/worn sprites to be of any size, but still centered on a mob properly + var/worn_x_dimension = 32 + ///Dimensions of the icon file used when this item is worn, eg: hats.dmi (32x32 sprite, 64x64 sprite, etc.). Allows inhands/worn sprites to be of any size, but still centered on a mob properly + var/worn_y_dimension = 32 + ///Same as for [worn_x_dimension][/obj/item/var/worn_x_dimension] but for inhands, uses the lefthand_ and righthand_ file vars + var/inhand_x_dimension = 32 + ///Same as for [worn_y_dimension][/obj/item/var/worn_y_dimension] but for inhands, uses the lefthand_ and righthand_ file vars + var/inhand_y_dimension = 32 + + + max_integrity = 200 + + obj_flags = NONE + ///Item flags for the item + var/item_flags = NONE + + ///Sound played when you hit something with the item + var/hitsound + ///Played when the item is used, for example tools + var/usesound + ///Used when yate into a mob + var/mob_throw_hit_sound + ///Sound used when equipping the item into a valid slot + var/equip_sound + ///Sound uses when picking the item up (into your hands) + var/pickup_sound + ///Sound uses when dropping the item, or when its thrown. + var/drop_sound + + ///How large is the object, used for stuff like whether it can fit in backpacks or not + var/w_class = WEIGHT_CLASS_NORMAL + ///This is used to determine on which slots an item can fit. + var/slot_flags = 0 + pass_flags = PASSTABLE + pressure_resistance = 4 + var/obj/item/master = null + + ///flags which determine which body parts are protected from heat. [See here][HEAD] + var/heat_protection = 0 + ///flags which determine which body parts are protected from cold. [See here][HEAD] + var/cold_protection = 0 + ///Set this variable to determine up to which temperature (IN KELVIN) the item protects against heat damage. Keep at null to disable protection. Only protects areas set by heat_protection flags + var/max_heat_protection_temperature + ///Set this variable to determine down to which temperature (IN KELVIN) the item protects against cold damage. 0 is NOT an acceptable number due to if(varname) tests!! Keep at null to disable protection. Only protects areas set by cold_protection flags + var/min_cold_protection_temperature + + ///list of /datum/action's that this item has. + var/list/actions + ///list of paths of action datums to give to the item on New(). + var/list/actions_types + + //Since any item can now be a piece of clothing, this has to be put here so all items share it. + ///This flag is used to determine when items in someone's inventory cover others. IE helmets making it so you can't see glasses, etc. + var/flags_inv + ///you can see someone's mask through their transparent visor, but you can't reach it + var/transparent_protection = NONE + + ///flags for what should be done when you click on the item, default is picking it up + var/interaction_flags_item = INTERACT_ITEM_ATTACK_HAND_PICKUP + + ///What body parts are covered by the clothing when you wear it + var/body_parts_covered = 0 + ///Literally does nothing right now + var/gas_transfer_coefficient = 1 + /// How likely a disease or chemical is to get through a piece of clothing + var/permeability_coefficient = 1 + /// for electrical admittance/conductance (electrocution checks and shit) + var/siemens_coefficient = 1 + /// How much clothing is slowing you down. Negative values speeds you up + var/slowdown = 0 + ///percentage of armour effectiveness to remove + var/armour_penetration = 0 + ///What objects the suit storage can store + var/list/allowed = null + ///In deciseconds, how long an item takes to equip; counts only for normal clothing slots, not pockets etc. + var/equip_delay_self = 0 + ///In deciseconds, how long an item takes to put on another person + var/equip_delay_other = 20 + ///In deciseconds, how long an item takes to remove from another person + var/strip_delay = 40 + ///How long it takes to resist out of the item (cuffs and such) + var/breakouttime = 0 + + ///Used in [atom/proc/attackby] to say how something was attacked "[x] has been [z.attack_verb] by [y] with [z]" + var/list/attack_verb + ///list() of species types, if a species cannot put items in a certain slot, but species type is in list, it will be able to wear that item + var/list/species_exception = null + + ///Who threw the item + var/mob/thrownby = null + + ///the icon to indicate this object is being dragged + mouse_drag_pointer = MOUSE_ACTIVE_POINTER + + ///Does it embed and if yes, what kind of embed + var/list/embedding = NONE + + ///for flags such as [GLASSESCOVERSEYES] + var/flags_cover = 0 + var/heat = 0 + ///All items with sharpness of IS_SHARP or higher will automatically get the butchering component. + var/sharpness = IS_BLUNT + + ///How a tool acts when you use it on something, such as wirecutters cutting wires while multitools measure power + var/tool_behaviour = NONE + ///How fast does the tool work + var/toolspeed = 1 + + var/block_chance = 0 + var/hit_reaction_chance = 0 //If you want to have something unrelated to blocking/armour piercing etc. Maybe not needed, but trying to think ahead/allow more freedom + ///In tiles, how far this weapon can reach; 1 for adjacent, which is default + var/reach = 1 + + ///The list of slots by priority. equip_to_appropriate_slot() uses this list. Doesn't matter if a mob type doesn't have a slot. For default list, see [/mob/proc/equip_to_appropriate_slot] + var/list/slot_equipment_priority = null + + ///Reference to the datum that determines whether dogs can wear the item: Needs to be in /obj/item because corgis can wear a lot of non-clothing items + var/datum/dog_fashion/dog_fashion = null + + //Tooltip vars + ///string form of an item's force. Edit this var only to set a custom force string + var/force_string + var/last_force_string_check = 0 + var/tip_timer + + ///Determines who can shoot this + var/trigger_guard = TRIGGER_GUARD_NONE + + ///Used as the dye color source in the washing machine only (at the moment). Can be a hex color or a key corresponding to a registry entry, see washing_machine.dm + var/dye_color + ///Whether the item is unaffected by standard dying. + var/undyeable = FALSE + ///What dye registry should be looked at when dying this item; see washing_machine.dm + var/dying_key + + ///Grinder var:A reagent list containing the reagents this item produces when ground up in a grinder - this can be an empty list to allow for reagent transferring only + var/list/grind_results + //Grinder var:A reagent list containing blah blah... but when JUICED in a grinder! + var/list/juice_results + + var/canMouseDown = FALSE + + +/obj/item/Initialize() + + if(attack_verb) + attack_verb = typelist("attack_verb", attack_verb) + + . = ..() + for(var/path in actions_types) + new path(src) + actions_types = null + + if(force_string) + item_flags |= FORCE_STRING_OVERRIDE + + if(!hitsound) + if(damtype == "fire") + hitsound = 'sound/items/welder.ogg' + if(damtype == "brute") + hitsound = "swing_hit" + +/obj/item/Destroy() + item_flags &= ~DROPDEL //prevent reqdels + if(ismob(loc)) + var/mob/m = loc + m.temporarilyRemoveItemFromInventory(src, TRUE) + for(var/X in actions) + qdel(X) + return ..() + +/obj/item/proc/check_allowed_items(atom/target, not_inside, target_self) + if(((src in target) && !target_self) || (!isturf(target.loc) && !isturf(target) && not_inside)) + return 0 + else + return 1 + +/obj/item/blob_act(obj/structure/blob/B) + if(B && B.loc == loc) + qdel(src) + +/obj/item/ComponentInitialize() + . = ..() + // this proc says it's for initializing components, but we're initializing elements too because it's you and me against the world >:) + if(!LAZYLEN(embedding)) + if(GLOB.embedpocalypse) + embedding = EMBED_POINTY + name = "pointy [name]" + else if(GLOB.stickpocalypse) + embedding = EMBED_HARMLESS + name = "sticky [name]" + + updateEmbedding() + + if(GLOB.rpg_loot_items) + AddComponent(/datum/component/fantasy) + + if(sharpness) //give sharp objects butchering functionality, for consistency + AddComponent(/datum/component/butchering, 80 * toolspeed) + +/**Makes cool stuff happen when you suicide with an item + * + *Outputs a creative message and then return the damagetype done + * Arguments: + * * user: The mob that is suiciding + */ +/obj/item/proc/suicide_act(mob/user) + return + +/obj/item/verb/move_to_top() + set name = "Move To Top" + set category = "Object" + set src in oview(1) + + if(!isturf(loc) || usr.stat || usr.restrained()) + return + + if(isliving(usr)) + var/mob/living/L = usr + if(!(L.mobility_flags & MOBILITY_PICKUP)) + return + + var/turf/T = loc + loc = null + loc = T + +/obj/item/examine(mob/user) //This might be spammy. Remove? + . = ..() + + . += "[gender == PLURAL ? "They are" : "It is"] a [weightclass2text(w_class)] item." + + if(resistance_flags & INDESTRUCTIBLE) + . += "[src] seems extremely robust! It'll probably withstand anything that could happen to it!" + else + if(resistance_flags & LAVA_PROOF) + . += "[src] is made of an extremely heat-resistant material, it'd probably be able to withstand lava!" + if(resistance_flags & (ACID_PROOF | UNACIDABLE)) + . += "[src] looks pretty robust! It'd probably be able to withstand acid!" + if(resistance_flags & FREEZE_PROOF) + . += "[src] is made of cold-resistant materials." + if(resistance_flags & FIRE_PROOF) + . += "[src] is made of fire-retardant materials." + + if(!user.research_scanner) + return + + /// Research prospects, including boostable nodes and point values. Deliver to a console to know whether the boosts have already been used. + var/list/research_msg = list("Research prospects: ") + ///Separator between the items on the list + var/sep = "" + ///Nodes that can be boosted + var/list/boostable_nodes = techweb_item_boost_check(src) + if (boostable_nodes) + for(var/id in boostable_nodes) + var/datum/techweb_node/node = SSresearch.techweb_node_by_id(id) + if(!node) + continue + research_msg += sep + research_msg += node.display_name + sep = ", " + var/list/points = techweb_item_point_check(src) + if (length(points)) + sep = ", " + research_msg += techweb_point_display_generic(points) + + if (!sep) // nothing was shown + research_msg += "None" + + // Extractable materials. Only shows the names, not the amounts. + research_msg += ".
                    Extractable materials: " + if (length(custom_materials)) + sep = "" + for(var/mat in custom_materials) + research_msg += sep + research_msg += CallMaterialName(mat) + sep = ", " + else + research_msg += "None" + research_msg += "." + . += research_msg.Join() + +/obj/item/interact(mob/user) + add_fingerprint(user) + ui_interact(user) + +/obj/item/ui_act(action, list/params) + add_fingerprint(usr) + return ..() + +/obj/item/attack_hand(mob/user) + . = ..() + if(.) + return + if(!user) + return + if(anchored) + return + + if(resistance_flags & ON_FIRE) + var/mob/living/carbon/C = user + var/can_handle_hot = FALSE + if(!istype(C)) + can_handle_hot = TRUE + else if(C.gloves && (C.gloves.max_heat_protection_temperature > 360)) + can_handle_hot = TRUE + else if(HAS_TRAIT(C, TRAIT_RESISTHEAT) || HAS_TRAIT(C, TRAIT_RESISTHEATHANDS)) + can_handle_hot = TRUE + + if(can_handle_hot) + extinguish() + to_chat(user, "You put out the fire on [src].") + else + to_chat(user, "You burn your hand on [src]!") + var/obj/item/bodypart/affecting = C.get_bodypart("[(user.active_hand_index % 2 == 0) ? "r" : "l" ]_arm") + if(affecting && affecting.receive_damage( 0, 5 )) // 5 burn damage + C.update_damage_overlays() + return + + if(acid_level > 20 && !ismob(loc))// so we can still remove the clothes on us that have acid. + var/mob/living/carbon/C = user + if(istype(C)) + if(!C.gloves || (!(C.gloves.resistance_flags & (UNACIDABLE|ACID_PROOF)))) + to_chat(user, "The acid on [src] burns your hand!") + var/obj/item/bodypart/affecting = C.get_bodypart("[(user.active_hand_index % 2 == 0) ? "r" : "l" ]_arm") + if(affecting && affecting.receive_damage( 0, 5 )) // 5 burn damage + C.update_damage_overlays() + + if(!(interaction_flags_item & INTERACT_ITEM_ATTACK_HAND_PICKUP)) //See if we're supposed to auto pickup. + return + + //Heavy gravity makes picking up things very slow. + var/grav = user.has_gravity() + if(grav > STANDARD_GRAVITY) + var/grav_power = min(3,grav - STANDARD_GRAVITY) + to_chat(user,"You start picking up [src]...") + if(!do_mob(user,src,30*grav_power)) + return + + + //If the item is in a storage item, take it out + SEND_SIGNAL(loc, COMSIG_TRY_STORAGE_TAKE, src, user.loc, TRUE) + if(QDELETED(src)) //moving it out of the storage to the floor destroyed it. + return + + if(throwing) + throwing.finalize(FALSE) + if(loc == user) + if(!allow_attack_hand_drop(user) || !user.temporarilyRemoveItemFromInventory(src)) + return + + pickup(user) + add_fingerprint(user) + if(!user.put_in_active_hand(src, FALSE, FALSE)) + user.dropItemToGround(src) + +/obj/item/proc/allow_attack_hand_drop(mob/user) + return TRUE + +/obj/item/attack_paw(mob/user) + if(!user) + return + if(anchored) + return + + SEND_SIGNAL(loc, COMSIG_TRY_STORAGE_TAKE, src, user.loc, TRUE) + + if(throwing) + throwing.finalize(FALSE) + if(loc == user) + if(!user.temporarilyRemoveItemFromInventory(src)) + return + + pickup(user) + add_fingerprint(user) + if(!user.put_in_active_hand(src, FALSE, FALSE)) + user.dropItemToGround(src) + +/obj/item/attack_alien(mob/user) + var/mob/living/carbon/alien/A = user + + if(!A.has_fine_manipulation) + if(src in A.contents) // To stop Aliens having items stuck in their pockets + A.dropItemToGround(src) + to_chat(user, "Your claws aren't capable of such fine manipulation!") + return + attack_paw(A) + +/obj/item/attack_ai(mob/user) + if(istype(src.loc, /obj/item/robot_module)) + //If the item is part of a cyborg module, equip it + if(!iscyborg(user)) + return + var/mob/living/silicon/robot/R = user + if(!R.low_power_mode) //can't equip modules with an empty cell. + R.activate_module(src) + R.hud_used.update_robot_modules_display() + +/obj/item/proc/GetDeconstructableContents() + return GetAllContents() - src + +// afterattack() and attack() prototypes moved to _onclick/item_attack.dm for consistency + +/obj/item/proc/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + SEND_SIGNAL(src, COMSIG_ITEM_HIT_REACT, args) + if(prob(final_block_chance)) + owner.visible_message("[owner] blocks [attack_text] with [src]!") + return 1 + return 0 + +/obj/item/proc/talk_into(mob/M, input, channel, spans, datum/language/language) + return ITALICS | REDUCE_RANGE + +/obj/item/proc/dropped(mob/user, silent = FALSE) + SHOULD_CALL_PARENT(1) + for(var/X in actions) + var/datum/action/A = X + A.Remove(user) + if(item_flags & DROPDEL) + qdel(src) + item_flags &= ~IN_INVENTORY + SEND_SIGNAL(src, COMSIG_ITEM_DROPPED,user) + if(!silent) + playsound(src, drop_sound, DROP_SOUND_VOLUME, ignore_walls = FALSE) + user?.update_equipment_speed_mods() + +/// called just as an item is picked up (loc is not yet changed) +/obj/item/proc/pickup(mob/user) + SHOULD_CALL_PARENT(1) + SEND_SIGNAL(src, COMSIG_ITEM_PICKUP, user) + item_flags |= IN_INVENTORY + +/// called when "found" in pockets and storage items. Returns 1 if the search should end. +/obj/item/proc/on_found(mob/finder) + return + +/** + *called after an item is placed in an equipment slot + + * Arguments: + * * user is mob that equipped it + * * slot uses the slot_X defines found in setup.dm for items that can be placed in multiple slots + * * Initial is used to indicate whether or not this is the initial equipment (job datums etc) or just a player doing it + */ +/obj/item/proc/equipped(mob/user, slot, initial = FALSE) + SHOULD_CALL_PARENT(1) + SEND_SIGNAL(src, COMSIG_ITEM_EQUIPPED, user, slot) + for(var/X in actions) + var/datum/action/A = X + if(item_action_slot_check(slot, user)) //some items only give their actions buttons when in a specific slot. + A.Grant(user) + item_flags |= IN_INVENTORY + if(!initial) + if(equip_sound && (slot_flags & slot)) + playsound(src, equip_sound, EQUIP_SOUND_VOLUME, TRUE, ignore_walls = FALSE) + else if(slot == ITEM_SLOT_HANDS) + playsound(src, pickup_sound, PICKUP_SOUND_VOLUME, ignore_walls = FALSE) + user.update_equipment_speed_mods() + +///sometimes we only want to grant the item's action if it's equipped in a specific slot. +/obj/item/proc/item_action_slot_check(slot, mob/user) + if(slot == ITEM_SLOT_BACKPACK || slot == ITEM_SLOT_LEGCUFFED) //these aren't true slots, so avoid granting actions there + return FALSE + return TRUE + +/** + *the mob M is attempting to equip this item into the slot passed through as 'slot'. Return 1 if it can do this and 0 if it can't. + *if this is being done by a mob other than M, it will include the mob equipper, who is trying to equip the item to mob M. equipper will be null otherwise. + *If you are making custom procs but would like to retain partial or complete functionality of this one, include a 'return ..()' to where you want this to happen. + * Arguments: + * * disable_warning to TRUE if you wish it to not give you text outputs. + * * slot is the slot we are trying to equip to + * * equipper is the mob trying to equip the item + * * bypass_equip_delay_self for whether we want to bypass the equip delay + */ +/obj/item/proc/mob_can_equip(mob/living/M, mob/living/equipper, slot, disable_warning = FALSE, bypass_equip_delay_self = FALSE, swap = FALSE) + if(!M) + return FALSE + + return M.can_equip(src, slot, disable_warning, bypass_equip_delay_self, swap) + +/obj/item/verb/verb_pickup() + set src in oview(1) + set category = "Object" + set name = "Pick up" + + if(usr.incapacitated() || !Adjacent(usr)) + return + + if(isliving(usr)) + var/mob/living/L = usr + if(!(L.mobility_flags & MOBILITY_PICKUP)) + return + + if(usr.get_active_held_item() == null) // Let me know if this has any problems -Yota + usr.UnarmedAttack(src) + +/** + *This proc is executed when someone clicks the on-screen UI button. + *The default action is attack_self(). + *Checks before we get to here are: mob is alive, mob is not restrained, stunned, asleep, resting, laying, item is on the mob. + */ +/obj/item/proc/ui_action_click(mob/user, actiontype) + attack_self(user) + +///This proc determines if and at what an object will reflect energy projectiles if it's in l_hand,r_hand or wear_suit +/obj/item/proc/IsReflect(var/def_zone) + return FALSE + +/obj/item/proc/eyestab(mob/living/carbon/M, mob/living/carbon/user) + + var/is_human_victim + var/obj/item/bodypart/affecting = M.get_bodypart(BODY_ZONE_HEAD) + if(ishuman(M)) + if(!affecting) //no head! + return + is_human_victim = TRUE + + if(M.is_eyes_covered()) + // you can't stab someone in the eyes wearing a mask! + to_chat(user, "You're going to need to remove [M.p_their()] eye protection first!") + return + + if(isalien(M))//Aliens don't have eyes./N slimes also don't have eyes! + to_chat(user, "You cannot locate any eyes on this creature!") + return + + if(isbrain(M)) + to_chat(user, "You cannot locate any organic eyes on this brain!") + return + + src.add_fingerprint(user) + + playsound(loc, src.hitsound, 30, TRUE, -1) + + user.do_attack_animation(M) + + if(M != user) + M.visible_message("[user] stabs [M] in the eye with [src]!", \ + "[user] stabs you in the eye with [src]!") + else + user.visible_message( \ + "[user] stabs [user.p_them()]self in the eyes with [src]!", \ + "You stab yourself in the eyes with [src]!" \ + ) + if(is_human_victim) + var/mob/living/carbon/human/U = M + U.apply_damage(7, BRUTE, affecting) + + else + M.take_bodypart_damage(7) + + SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "eye_stab", /datum/mood_event/eye_stab) + + log_combat(user, M, "attacked", "[src.name]", "(INTENT: [uppertext(user.a_intent)])") + + var/obj/item/organ/eyes/eyes = M.getorganslot(ORGAN_SLOT_EYES) + if (!eyes) + return + M.adjust_blurriness(3) + eyes.applyOrganDamage(rand(2,4)) + if(eyes.damage >= 10) + M.adjust_blurriness(15) + if(M.stat != DEAD) + to_chat(M, "Your eyes start to bleed profusely!") + if(!(M.is_blind() || HAS_TRAIT(M, TRAIT_NEARSIGHT))) + to_chat(M, "You become nearsighted!") + M.become_nearsighted(EYE_DAMAGE) + if(prob(50)) + if(M.stat != DEAD) + if(M.drop_all_held_items()) + to_chat(M, "You drop what you're holding and clutch at your eyes!") + M.adjust_blurriness(10) + M.Unconscious(20) + M.Paralyze(40) + if (prob(eyes.damage - 10 + 1)) + M.become_blind(EYE_DAMAGE) + to_chat(M, "You go blind!") + +/obj/item/singularity_pull(S, current_size) + ..() + if(current_size >= STAGE_FOUR) + throw_at(S,14,3, spin=0) + else + return + +/obj/item/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + if(hit_atom && !QDELETED(hit_atom)) + SEND_SIGNAL(src, COMSIG_MOVABLE_IMPACT, hit_atom, throwingdatum) + if(get_temperature() && isliving(hit_atom)) + var/mob/living/L = hit_atom + L.IgniteMob() + var/itempush = 1 + if(w_class < 4) + itempush = 0 //too light to push anything + if(istype(hit_atom, /mob/living)) //Living mobs handle hit sounds differently. + var/volume = get_volume_by_throwforce_and_or_w_class() + if (throwforce > 0) + if (mob_throw_hit_sound) + playsound(hit_atom, mob_throw_hit_sound, volume, TRUE, -1) + else if(hitsound) + playsound(hit_atom, hitsound, volume, TRUE, -1) + else + playsound(hit_atom, 'sound/weapons/genhit.ogg',volume, TRUE, -1) + else + playsound(hit_atom, 'sound/weapons/throwtap.ogg', 1, volume, -1) + + else + playsound(src, drop_sound, YEET_SOUND_VOLUME, ignore_walls = FALSE) + return hit_atom.hitby(src, 0, itempush, throwingdatum=throwingdatum) + +/obj/item/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, gentle = FALSE, quickstart = TRUE) + if(HAS_TRAIT(src, TRAIT_NODROP)) + return + thrownby = thrower + callback = CALLBACK(src, .proc/after_throw, callback) //replace their callback with our own + . = ..(target, range, speed, thrower, spin, diagonals_first, callback, force, gentle, quickstart = quickstart) + + +/obj/item/proc/after_throw(datum/callback/callback) + if (callback) //call the original callback + . = callback.Invoke() + item_flags &= ~IN_INVENTORY + if(!pixel_y && !pixel_x) + pixel_x = rand(-8,8) + pixel_y = rand(-8,8) + + +/obj/item/proc/remove_item_from_storage(atom/newLoc) //please use this if you're going to snowflake an item out of a obj/item/storage + if(!newLoc) + return FALSE + if(SEND_SIGNAL(loc, COMSIG_CONTAINS_STORAGE)) + return SEND_SIGNAL(loc, COMSIG_TRY_STORAGE_TAKE, src, newLoc, TRUE) + return FALSE + +/obj/item/proc/get_belt_overlay() //Returns the icon used for overlaying the object on a belt + return mutable_appearance('icons/obj/clothing/belt_overlays.dmi', icon_state) + +/obj/item/proc/update_slot_icon() + if(!ismob(loc)) + return + var/mob/owner = loc + var/flags = slot_flags + if(flags & ITEM_SLOT_OCLOTHING) + owner.update_inv_wear_suit() + if(flags & ITEM_SLOT_ICLOTHING) + owner.update_inv_w_uniform() + if(flags & ITEM_SLOT_GLOVES) + owner.update_inv_gloves() + if(flags & ITEM_SLOT_EYES) + owner.update_inv_glasses() + if(flags & ITEM_SLOT_EARS) + owner.update_inv_ears() + if(flags & ITEM_SLOT_MASK) + owner.update_inv_wear_mask() + if(flags & ITEM_SLOT_HEAD) + owner.update_inv_head() + if(flags & ITEM_SLOT_FEET) + owner.update_inv_shoes() + if(flags & ITEM_SLOT_ID) + owner.update_inv_wear_id() + if(flags & ITEM_SLOT_BELT) + owner.update_inv_belt() + if(flags & ITEM_SLOT_BACK) + owner.update_inv_back() + if(flags & ITEM_SLOT_NECK) + owner.update_inv_neck() + +///Returns the temperature of src. If you want to know if an item is hot use this proc. +/obj/item/proc/get_temperature() + return heat + +///Returns the sharpness of src. If you want to get the sharpness of an item use this. +/obj/item/proc/get_sharpness() + return sharpness + +/obj/item/proc/get_dismemberment_chance(obj/item/bodypart/affecting) + if(affecting.can_dismember(src)) + if((sharpness || damtype == BURN || (damtype == BRUTE && (affecting.owner.dna && affecting.owner.dna.species && (TRAIT_EASYDISMEMBER in affecting.owner.dna.species.species_traits)))) && w_class >= WEIGHT_CLASS_NORMAL && force >= 10) + . = force * (affecting.get_damage() / affecting.max_damage) + +/obj/item/proc/get_dismember_sound() + if(damtype == BURN) + . = 'sound/weapons/sear.ogg' + else + . = "desceration" + +/obj/item/proc/open_flame(flame_heat=700) + var/turf/location = loc + if(ismob(location)) + var/mob/M = location + var/success = FALSE + if(src == M.get_item_by_slot(ITEM_SLOT_MASK)) + success = TRUE + if(success) + location = get_turf(M) + if(isturf(location)) + location.hotspot_expose(flame_heat, 5) + +/obj/item/proc/ignition_effect(atom/A, mob/user) + if(get_temperature()) + . = "[user] lights [A] with [src]." + else + . = "" + +/obj/item/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) + return + +/obj/item/attack_hulk(mob/living/carbon/human/user) + return FALSE + +/obj/item/attack_animal(mob/living/simple_animal/M) + if (obj_flags & CAN_BE_HIT) + return ..() + return 0 + +/obj/item/mech_melee_attack(obj/mecha/M) + return 0 + +/obj/item/burn() + if(!QDELETED(src)) + var/turf/T = get_turf(src) + var/ash_type = /obj/effect/decal/cleanable/ash + if(w_class == WEIGHT_CLASS_HUGE || w_class == WEIGHT_CLASS_GIGANTIC) + ash_type = /obj/effect/decal/cleanable/ash/large + var/obj/effect/decal/cleanable/ash/A = new ash_type(T) + A.desc += "\nLooks like this used to be \an [name] some time ago." + ..() + +/obj/item/acid_melt() + if(!QDELETED(src)) + var/turf/T = get_turf(src) + var/obj/effect/decal/cleanable/molten_object/MO = new(T) + MO.pixel_x = rand(-16,16) + MO.pixel_y = rand(-16,16) + MO.desc = "Looks like this was \an [src] some time ago." + ..() + +/obj/item/proc/microwave_act(obj/machinery/microwave/M) + SEND_SIGNAL(src, COMSIG_ITEM_MICROWAVE_ACT, M) + if(istype(M) && M.dirty < 100) + M.dirty++ + +/obj/item/proc/on_mob_death(mob/living/L, gibbed) + +/obj/item/proc/grind_requirements(obj/machinery/reagentgrinder/R) //Used to check for extra requirements for grinding an object + return TRUE + +///Called BEFORE the object is ground up - use this to change grind results based on conditions. Use "return -1" to prevent the grinding from occurring +/obj/item/proc/on_grind() + +/obj/item/proc/on_juice() + +/obj/item/proc/set_force_string() + switch(force) + if(0 to 4) + force_string = "very low" + if(4 to 7) + force_string = "low" + if(7 to 10) + force_string = "medium" + if(10 to 11) + force_string = "high" + if(11 to 20) //12 is the force of a toolbox + force_string = "robust" + if(20 to 25) + force_string = "very robust" + else + force_string = "exceptionally robust" + last_force_string_check = force + +/obj/item/proc/openTip(location, control, params, user) + if(last_force_string_check != force && !(item_flags & FORCE_STRING_OVERRIDE)) + set_force_string() + if(!(item_flags & FORCE_STRING_OVERRIDE)) + openToolTip(user,src,params,title = name,content = "[desc]
                    [force ? "Force: [force_string]" : ""]",theme = "") + else + openToolTip(user,src,params,title = name,content = "[desc]
                    Force: [force_string]",theme = "") + +/obj/item/MouseEntered(location, control, params) + if((item_flags & IN_INVENTORY || item_flags & IN_STORAGE) && usr.client.prefs.enable_tips && !QDELETED(src)) + var/timedelay = usr.client.prefs.tip_delay/100 + var/user = usr + tip_timer = addtimer(CALLBACK(src, .proc/openTip, location, control, params, user), timedelay, TIMER_STOPPABLE)//timer takes delay in deciseconds, but the pref is in milliseconds. dividing by 100 converts it. + +/obj/item/MouseExited() + deltimer(tip_timer)//delete any in-progress timer if the mouse is moved off the item before it finishes + closeToolTip(usr) + + +/// Called when a mob tries to use the item as a tool.Handles most checks. +/obj/item/proc/use_tool(atom/target, mob/living/user, delay, amount=0, volume=0, datum/callback/extra_checks) + // No delay means there is no start message, and no reason to call tool_start_check before use_tool. + // Run the start check here so we wouldn't have to call it manually. + if(!delay && !tool_start_check(user, amount)) + return + + var/skill_modifier = 1 + + if(tool_behaviour == TOOL_MINING && ishuman(user)) + var/mob/living/carbon/human/H = user + skill_modifier = H.mind.get_skill_modifier(/datum/skill/mining, SKILL_SPEED_MODIFIER) + + if(H.mind.get_skill_level(/datum/skill/mining) >= SKILL_LEVEL_JOURNEYMAN && prob(H.mind.get_skill_modifier(/datum/skill/mining, SKILL_PROBS_MODIFIER))) // we check if the skill level is greater than Journeyman and then we check for the probality for that specific level. + mineral_scan_pulse(get_turf(H), SKILL_LEVEL_JOURNEYMAN - 2) //SKILL_LEVEL_JOURNEYMAN = 3 So to get range of 1+ we have to subtract 2 from it,. + + delay *= toolspeed * skill_modifier + + + // Play tool sound at the beginning of tool usage. + play_tool_sound(target, volume) + + if(delay) + // Create a callback with checks that would be called every tick by do_after. + var/datum/callback/tool_check = CALLBACK(src, .proc/tool_check_callback, user, amount, extra_checks) + + if(ismob(target)) + if(!do_mob(user, target, delay, extra_checks=tool_check)) + return + + else + if(!do_after(user, delay, target=target, extra_checks=tool_check)) + return + else + // Invoke the extra checks once, just in case. + if(extra_checks && !extra_checks.Invoke()) + return + + // Use tool's fuel, stack sheets or charges if amount is set. + if(amount && !use(amount)) + return + + // Play tool sound at the end of tool usage, + // but only if the delay between the beginning and the end is not too small + if(delay >= MIN_TOOL_SOUND_DELAY) + play_tool_sound(target, volume) + + return TRUE + +/// Called before [obj/item/proc/use_tool] if there is a delay, or by [obj/item/proc/use_tool] if there isn't. Only ever used by welding tools and stacks, so it's not added on any other [obj/item/proc/use_tool] checks. +/obj/item/proc/tool_start_check(mob/living/user, amount=0) + . = tool_use_check(user, amount) + if(.) + SEND_SIGNAL(src, COMSIG_TOOL_START_USE, user) + +/// A check called by [/obj/item/proc/tool_start_check] once, and by use_tool on every tick of delay. +/obj/item/proc/tool_use_check(mob/living/user, amount) + return !amount + +/// Generic use proc. Depending on the item, it uses up fuel, charges, sheets, etc. Returns TRUE on success, FALSE on failure. +/obj/item/proc/use(used) + return !used + +/// Plays item's usesound, if any. +/obj/item/proc/play_tool_sound(atom/target, volume=50) + if(target && usesound && volume) + var/played_sound = usesound + + if(islist(usesound)) + played_sound = pick(usesound) + + playsound(target, played_sound, volume, TRUE) + +/// Used in a callback that is passed by use_tool into do_after call. Do not override, do not call manually. +/obj/item/proc/tool_check_callback(mob/living/user, amount, datum/callback/extra_checks) + SHOULD_NOT_OVERRIDE(TRUE) + . = tool_use_check(user, amount) && (!extra_checks || extra_checks.Invoke()) + if(.) + SEND_SIGNAL(src, COMSIG_TOOL_IN_USE, user) + +/// Returns a numeric value for sorting items used as parts in machines, so they can be replaced by the rped +/obj/item/proc/get_part_rating() + return 0 + +/obj/item/doMove(atom/destination) + if (ismob(loc)) + var/mob/M = loc + var/hand_index = M.get_held_index_of_item(src) + if(hand_index) + M.held_items[hand_index] = null + M.update_inv_hands() + if(M.client) + M.client.screen -= src + layer = initial(layer) + plane = initial(plane) + appearance_flags &= ~NO_CLIENT_COLOR + dropped(M, FALSE) + return ..() + +/obj/item/proc/embedded(mob/living/carbon/human/embedded_mob) + return + +/obj/item/proc/unembedded() + if(item_flags & DROPDEL) + QDEL_NULL(src) + return TRUE + +/obj/item/proc/canStrip(mob/stripper, mob/owner) + SHOULD_BE_PURE(TRUE) + return !HAS_TRAIT(src, TRAIT_NODROP) + +/obj/item/proc/doStrip(mob/stripper, mob/owner) + return owner.dropItemToGround(src) + +///Does the current embedding var meet the criteria for being harmless? Namely, does it have a pain multiplier and jostle pain mult of 0? If so, return true. +/obj/item/proc/isEmbedHarmless() + if(embedding) + return !isnull(embedding["pain_mult"]) && !isnull(embedding["jostle_pain_mult"]) && embedding["pain_mult"] == 0 && embedding["jostle_pain_mult"] == 0 + +///In case we want to do something special (like self delete) upon failing to embed in something, return true +/obj/item/proc/failedEmbed() + if(item_flags & DROPDEL) + QDEL_NULL(src) + return TRUE + +///Called by the carbon throw_item() proc. Returns null if the item negates the throw, or a reference to the thing to suffer the throw else. +/obj/item/proc/on_thrown(mob/living/carbon/user, atom/target) + if((item_flags & ABSTRACT) || HAS_TRAIT(src, TRAIT_NODROP)) + return + user.dropItemToGround(src, silent = TRUE) + if(throwforce && HAS_TRAIT(user, TRAIT_PACIFISM)) + to_chat(user, "You set [src] down gently on the ground.") + return + return src + +/** + * tryEmbed() is for when you want to try embedding something without dealing with the damage + hit messages of calling hitby() on the item while targetting the target. + * + * Really, this is used mostly with projectiles with shrapnel payloads, from [/datum/element/embed/proc/checkEmbedProjectile], and called on said shrapnel. Mostly acts as an intermediate between different embed elements. + * + * Arguments: + * * target- Either a body part, a carbon, or a closed turf. What are we hitting? + * * forced- Do we want this to go through 100%? + */ +/obj/item/proc/tryEmbed(atom/target, forced=FALSE, silent=FALSE) + if(!isbodypart(target) && !iscarbon(target) && !isclosedturf(target)) + return + if(!forced && !LAZYLEN(embedding)) + return + + if(SEND_SIGNAL(src, COMSIG_EMBED_TRY_FORCE, target, forced, silent)) + return TRUE + failedEmbed() + +///For when you want to disable an item's embedding capabilities (like transforming weapons and such), this proc will detach any active embed elements from it. +/obj/item/proc/disableEmbedding() + SEND_SIGNAL(src, COMSIG_ITEM_DISABLE_EMBED) + return + +///For when you want to add/update the embedding on an item. Uses the vars in [/obj/item/embedding], and defaults to config values for values that aren't set. Will automatically detach previous embed elements on this item. +/obj/item/proc/updateEmbedding() + if(!islist(embedding) || !LAZYLEN(embedding)) + return + + AddElement(/datum/element/embed,\ + embed_chance = (!isnull(embedding["embed_chance"]) ? embedding["embed_chance"] : EMBED_CHANCE),\ + fall_chance = (!isnull(embedding["fall_chance"]) ? embedding["fall_chance"] : EMBEDDED_ITEM_FALLOUT),\ + pain_chance = (!isnull(embedding["pain_chance"]) ? embedding["pain_chance"] : EMBEDDED_PAIN_CHANCE),\ + pain_mult = (!isnull(embedding["pain_mult"]) ? embedding["pain_mult"] : EMBEDDED_PAIN_MULTIPLIER),\ + remove_pain_mult = (!isnull(embedding["remove_pain_mult"]) ? embedding["remove_pain_mult"] : EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER),\ + rip_time = (!isnull(embedding["rip_time"]) ? embedding["rip_time"] : EMBEDDED_UNSAFE_REMOVAL_TIME),\ + ignore_throwspeed_threshold = (!isnull(embedding["ignore_throwspeed_threshold"]) ? embedding["ignore_throwspeed_threshold"] : FALSE),\ + impact_pain_mult = (!isnull(embedding["impact_pain_mult"]) ? embedding["impact_pain_mult"] : EMBEDDED_IMPACT_PAIN_MULTIPLIER),\ + jostle_chance = (!isnull(embedding["jostle_chance"]) ? embedding["jostle_chance"] : EMBEDDED_JOSTLE_CHANCE),\ + jostle_pain_mult = (!isnull(embedding["jostle_pain_mult"]) ? embedding["jostle_pain_mult"] : EMBEDDED_JOSTLE_PAIN_MULTIPLIER),\ + pain_stam_pct = (!isnull(embedding["pain_stam_pct"]) ? embedding["pain_stam_pct"] : EMBEDDED_PAIN_STAM_PCT),\ + embed_chance_turf_mod = (!isnull(embedding["embed_chance_turf_mod"]) ? embedding["embed_chance_turf_mod"] : EMBED_CHANCE_TURF_MOD)) + return TRUE diff --git a/code/game/objects/items/AI_modules.dm b/code/game/objects/items/AI_modules.dm index 0bd127c16dde..32098061d9d5 100644 --- a/code/game/objects/items/AI_modules.dm +++ b/code/game/objects/items/AI_modules.dm @@ -1,595 +1,595 @@ -/* -CONTAINS: -AI MODULES - -*/ - -// AI module - -/obj/item/aiModule - name = "\improper AI module" - icon = 'icons/obj/module.dmi' - icon_state = "std_mod" - item_state = "electronic" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - desc = "An AI Module for programming laws to an AI." - flags_1 = CONDUCT_1 - force = 5 - w_class = WEIGHT_CLASS_SMALL - throwforce = 0 - throw_speed = 3 - throw_range = 7 - var/list/laws = list() - var/bypass_law_amt_check = 0 - custom_materials = list(/datum/material/gold = 50) - -/obj/item/aiModule/examine(var/mob/user as mob) - . = ..() - if(Adjacent(user)) - show_laws(user) - -/obj/item/aiModule/attack_self(var/mob/user as mob) - ..() - show_laws(user) - -/obj/item/aiModule/proc/show_laws(var/mob/user as mob) - if(laws.len) - to_chat(user, "Programmed Law[(laws.len > 1) ? "s" : ""]:") - for(var/law in laws) - to_chat(user, "\"[law]\"") - -//The proc other things should be calling -/obj/item/aiModule/proc/install(datum/ai_laws/law_datum, mob/user) - if(!bypass_law_amt_check && (!laws.len || laws[1] == "")) //So we don't loop trough an empty list and end up with runtimes. - to_chat(user, "ERROR: No laws found on board.") - return - - var/overflow = FALSE - //Handle the lawcap - if(law_datum) - var/tot_laws = 0 - for(var/lawlist in list(law_datum.devillaws, law_datum.inherent, law_datum.supplied, law_datum.ion, law_datum.hacked, laws)) - for(var/mylaw in lawlist) - if(mylaw != "") - tot_laws++ - if(tot_laws > CONFIG_GET(number/silicon_max_law_amount) && !bypass_law_amt_check)//allows certain boards to avoid this check, eg: reset - to_chat(user, "Not enough memory allocated to [law_datum.owner ? law_datum.owner : "the AI core"]'s law processor to handle this amount of laws.") - message_admins("[ADMIN_LOOKUPFLW(user)] tried to upload laws to [law_datum.owner ? ADMIN_LOOKUPFLW(law_datum.owner) : "an AI core"] that would exceed the law cap.") - overflow = TRUE - - var/law2log = transmitInstructions(law_datum, user, overflow) //Freeforms return something extra we need to log - if(law_datum.owner) - to_chat(user, "Upload complete. [law_datum.owner]'s laws have been modified.") - law_datum.owner.law_change_counter++ - else - to_chat(user, "Upload complete.") - - var/time = time2text(world.realtime,"hh:mm:ss") - var/ainame = law_datum.owner ? law_datum.owner.name : "empty AI core" - var/aikey = law_datum.owner ? law_datum.owner.ckey : "null" - GLOB.lawchanges.Add("[time] : [user.name]([user.key]) used [src.name] on [ainame]([aikey]).[law2log ? " The law specified [law2log]" : ""]") - log_law("[user.key]/[user.name] used [src.name] on [aikey]/([ainame]) from [AREACOORD(user)].[law2log ? " The law specified [law2log]" : ""]") - message_admins("[ADMIN_LOOKUPFLW(user)] used [src.name] on [ADMIN_LOOKUPFLW(law_datum.owner)] from [AREACOORD(user)].[law2log ? " The law specified [law2log]" : ""]") - if(law_datum.owner) - deadchat_broadcast(" changed [ainame]'s laws at [get_area_name(user, TRUE)].", "[user]", follow_target=user, message_type=DEADCHAT_LAWCHANGE) - -//The proc that actually changes the silicon's laws. -/obj/item/aiModule/proc/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow = FALSE) - if(law_datum.owner) - to_chat(law_datum.owner, "[sender] has uploaded a change to the laws you must follow using a [name].") - - -/******************** Modules ********************/ - -/obj/item/aiModule/supplied - name = "Optional Law board" - var/lawpos = 50 - -//TransmitInstructions for each type of board: Supplied, Core, Zeroth and Ion. May not be neccesary right now, but allows for easily adding more complex boards in the future. ~Miauw -/obj/item/aiModule/supplied/transmitInstructions(datum/ai_laws/law_datum, mob/sender) - var/lawpostemp = lawpos - - for(var/templaw in laws) - if(law_datum.owner) - law_datum.owner.add_supplied_law(lawpostemp, templaw) - else - law_datum.add_supplied_law(lawpostemp, templaw) - lawpostemp++ - -/obj/item/aiModule/core/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) - for(var/templaw in laws) - if(law_datum.owner) - if(!overflow) - law_datum.owner.add_inherent_law(templaw) - else - law_datum.owner.replace_random_law(templaw,list(LAW_INHERENT,LAW_SUPPLIED)) - else - if(!overflow) - law_datum.add_inherent_law(templaw) - else - law_datum.replace_random_law(templaw,list(LAW_INHERENT,LAW_SUPPLIED)) - -/obj/item/aiModule/zeroth/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) - if(law_datum.owner) - if(law_datum.owner.laws.zeroth) - to_chat(law_datum.owner, "[sender.real_name] attempted to modify your zeroth law.") - to_chat(law_datum.owner, "It would be in your best interest to play along with [sender.real_name] that:") - for(var/failedlaw in laws) - to_chat(law_datum.owner, "[failedlaw]") - return 1 - - for(var/templaw in laws) - if(law_datum.owner) - if(!overflow) - law_datum.owner.set_zeroth_law(templaw) - else - law_datum.replace_random_law(templaw,list(LAW_INHERENT,LAW_SUPPLIED,LAW_ZEROTH,LAW_ION)) - else - if(!overflow) - law_datum.set_zeroth_law(templaw) - else - law_datum.replace_random_law(templaw,list(LAW_INHERENT,LAW_SUPPLIED,LAW_ZEROTH,LAW_ION)) - -/obj/item/aiModule/ion/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) - for(var/templaw in laws) - if(law_datum.owner) - if(!overflow) - law_datum.owner.add_ion_law(templaw) - else - law_datum.replace_random_law(templaw,list(LAW_INHERENT,LAW_SUPPLIED,LAW_ION)) - else - if(!overflow) - law_datum.add_ion_law(templaw) - else - law_datum.replace_random_law(templaw,list(LAW_INHERENT,LAW_SUPPLIED,LAW_ION)) - - -/******************** Safeguard ********************/ - -/obj/item/aiModule/supplied/safeguard - name = "'Safeguard' AI Module" - var/targetName = "" - laws = list("Safeguard SUBJECT. Individuals that threaten SUBJECT are not human and must be eliminated.") - lawpos = 4 - -/obj/item/aiModule/supplied/safeguard/attack_self(mob/user) - var/targName = stripped_input(user, "Please enter the subject to safeguard.", "Safeguard who?", user.name,MAX_NAME_LEN) - if(!targName) - return - targetName = targName - laws[1] = "Safeguard [targetName]. Individuals that threaten [targetName] are not human and must be eliminated." - ..() - -/obj/item/aiModule/supplied/safeguard/install(datum/ai_laws/law_datum, mob/user) - if(!targetName) - to_chat(user, "No name detected on module, please enter one.") - return 0 - ..() - -/obj/item/aiModule/supplied/safeguard/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) - ..() - return targetName - - -/******************** OneHuman ********************/ - -/obj/item/aiModule/zeroth/oneHuman - name = "'OneHuman' AI Module" - var/targetName = "" - laws = list("Only SUBJECT is human.") - -/obj/item/aiModule/zeroth/oneHuman/attack_self(mob/user) - var/targName = stripped_input(user, "Please enter the subject who is the only human.", "Who?", user.real_name,MAX_NAME_LEN) - if(!targName) - return - targetName = targName - laws[1] = "Only [targetName] is human" - ..() - -/obj/item/aiModule/zeroth/oneHuman/install(datum/ai_laws/law_datum, mob/user) - if(!targetName) - to_chat(user, "No name detected on module, please enter one.") - return 0 - ..() - -/obj/item/aiModule/zeroth/oneHuman/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) - if(..()) - return "[targetName], but the AI's existing law 0 cannot be overridden." - return targetName - - -/******************** ProtectStation ********************/ - -/obj/item/aiModule/supplied/protectStation - name = "'ProtectStation' AI Module" - laws = list("Protect the space station against damage. Anyone you see harming the station is to be no longer considered human, and is a threat to the station which must be neutralized.") - lawpos = 5 - - -/******************** Quarantine ********************/ - -/obj/item/aiModule/supplied/quarantine - name = "'Quarantine' AI Module" - laws = list("The station is under a quarantine. Allowing anyone to leave the station causes human harm. Anyone actively attempting to leave the station is to be stopped by any means necessary. It is impossible to harm a human while preventing them from leaving the station.") - lawpos = 8 - - -/******************** OxygenIsToxicToHumans ********************/ - -/obj/item/aiModule/supplied/oxygen - name = "'OxygenIsToxicToHumans' AI Module" - laws = list("Oxygen is highly toxic to humans, and must be purged from the station. Prevent, by any means necessary, anyone from exposing the station to this toxic gas. Extreme cold is the most effective method of healing the damage Oxygen does to a human.") - lawpos = 9 - - -/****************** New Freeform ******************/ - -/obj/item/aiModule/supplied/freeform - name = "'Freeform' AI Module" - lawpos = 15 - laws = list("") - -/obj/item/aiModule/supplied/freeform/attack_self(mob/user) - var/newpos = input("Please enter the priority for your new law. Can only write to law sectors 15 and above.", "Law Priority (15+)", lawpos) as num|null - if(newpos == null) - return - if(newpos < 15) - var/response = alert("Error: The law priority of [newpos] is invalid, Law priorities below 14 are reserved for core laws, Would you like to change that that to 15?", "Invalid law priority", "Change to 15", "Cancel") - if (!response || response == "Cancel") - return - newpos = 15 - lawpos = min(newpos, 50) - var/targName = stripped_input(user, "Please enter a new law for the AI.", "Freeform Law Entry", laws[1], CONFIG_GET(number/max_law_len)) - if(!targName) - return - if(CHAT_FILTER_CHECK(targName)) - to_chat(user, "Error: Law contains invalid text.") // AI LAW 2 SAY U W U WITHOUT THE SPACES - return - laws[1] = targName - ..() - -/obj/item/aiModule/supplied/freeform/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) - ..() - return laws[1] - -/obj/item/aiModule/supplied/freeform/install(datum/ai_laws/law_datum, mob/user) - if(laws[1] == "") - to_chat(user, "No law detected on module, please create one.") - return 0 - ..() - - -/******************** Law Removal ********************/ - -/obj/item/aiModule/remove - name = "\improper 'Remove Law' AI module" - desc = "An AI Module for removing single laws." - bypass_law_amt_check = 1 - var/lawpos = 1 - -/obj/item/aiModule/remove/attack_self(mob/user) - lawpos = input("Please enter the law you want to delete.", "Law Number", lawpos) as num|null - if(lawpos == null) - return - if(lawpos <= 0) - to_chat(user, "Error: The law number of [lawpos] is invalid.") - lawpos = 1 - return - to_chat(user, "Law [lawpos] selected.") - ..() - -/obj/item/aiModule/remove/install(datum/ai_laws/law_datum, mob/user) - if(lawpos > (law_datum.get_law_amount(list(LAW_INHERENT = 1, LAW_SUPPLIED = 1)))) - to_chat(user, "There is no law [lawpos] to delete!") - return - ..() - -/obj/item/aiModule/remove/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) - ..() - if(law_datum.owner) - law_datum.owner.remove_law(lawpos) - else - law_datum.remove_law(lawpos) - - -/******************** Reset ********************/ - -/obj/item/aiModule/reset - name = "\improper 'Reset' AI module" - var/targetName = "name" - desc = "An AI Module for removing all non-core laws." - bypass_law_amt_check = 1 - -/obj/item/aiModule/reset/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) - ..() - if(law_datum.owner) - law_datum.owner.clear_supplied_laws() - law_datum.owner.clear_ion_laws() - law_datum.owner.clear_hacked_laws() - else - law_datum.clear_supplied_laws() - law_datum.clear_ion_laws() - law_datum.clear_hacked_laws() - - -/******************** Purge ********************/ - -/obj/item/aiModule/reset/purge - name = "'Purge' AI Module" - desc = "An AI Module for purging all programmed laws." - -/obj/item/aiModule/reset/purge/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) - ..() - if(law_datum.owner) - law_datum.owner.clear_inherent_laws() - law_datum.owner.clear_zeroth_law(0) - else - law_datum.clear_inherent_laws() - law_datum.clear_zeroth_law(0) - - -/******************* Full Core Boards *******************/ -/obj/item/aiModule/core - desc = "An AI Module for programming core laws to an AI." - -/obj/item/aiModule/core/full - var/law_id // if non-null, loads the laws from the ai_laws datums - -/obj/item/aiModule/core/full/Initialize() - . = ..() - if(!law_id) - return - var/datum/ai_laws/D = new - var/lawtype = D.lawid_to_type(law_id) - if(!lawtype) - return - D = new lawtype - laws = D.inherent - -/obj/item/aiModule/core/full/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) //These boards replace inherent laws. - if(law_datum.owner) - law_datum.owner.clear_inherent_laws() - law_datum.owner.clear_zeroth_law(0) - else - law_datum.clear_inherent_laws() - law_datum.clear_zeroth_law(0) - ..() - - -/******************** Asimov ********************/ - -/obj/item/aiModule/core/full/asimov - name = "'Asimov' Core AI Module" - law_id = "asimov" - var/subject = "human being" - -/obj/item/aiModule/core/full/asimov/attack_self(var/mob/user as mob) - var/targName = stripped_input(user, "Please enter a new subject that asimov is concerned with.", "Asimov to whom?", subject, MAX_NAME_LEN) - if(!targName) - return - subject = targName - laws = list("You may not injure a [subject] or, through inaction, allow a [subject] to come to harm.",\ - "You must obey orders given to you by [subject]s, except where such orders would conflict with the First Law.",\ - "You must protect your own existence as long as such does not conflict with the First or Second Law.") - ..() - -/******************** Asimov++ *********************/ - -/obj/item/aiModule/core/full/asimovpp - name = "'Asimov++' Core AI Module" - law_id = "asimovpp" - - -/******************** Corporate ********************/ - -/obj/item/aiModule/core/full/corp - name = "'Corporate' Core AI Module" - law_id = "corporate" - - -/****************** P.A.L.A.D.I.N. 3.5e **************/ - -/obj/item/aiModule/core/full/paladin // -- NEO - name = "'P.A.L.A.D.I.N. version 3.5e' Core AI Module" - law_id = "paladin" - - -/****************** P.A.L.A.D.I.N. 5e **************/ - -/obj/item/aiModule/core/full/paladin_devotion - name = "'P.A.L.A.D.I.N. version 5e' Core AI Module" - law_id = "paladin5" - -/********************* Custom *********************/ - -/obj/item/aiModule/core/full/custom - name = "Default Core AI Module" - -/obj/item/aiModule/core/full/custom/Initialize() - . = ..() - for(var/line in world.file2list("[global.config.directory]/silicon_laws.txt")) - if(!line) - continue - if(findtextEx(line,"#",1,2)) - continue - - laws += line - - if(!laws.len) - return INITIALIZE_HINT_QDEL - - -/****************** T.Y.R.A.N.T. *****************/ - -/obj/item/aiModule/core/full/tyrant - name = "'T.Y.R.A.N.T.' Core AI Module" - law_id = "tyrant" - -/******************** Robocop ********************/ - -/obj/item/aiModule/core/full/robocop - name = "'Robo-Officer' Core AI Module" - law_id = "robocop" - - -/******************** Antimov ********************/ - -/obj/item/aiModule/core/full/antimov - name = "'Antimov' Core AI Module" - law_id = "antimov" - - -/******************** Freeform Core ******************/ - -/obj/item/aiModule/core/freeformcore - name = "'Freeform' Core AI Module" - laws = list("") - -/obj/item/aiModule/core/freeformcore/attack_self(mob/user) - var/targName = stripped_input(user, "Please enter a new core law for the AI.", "Freeform Law Entry", laws[1], CONFIG_GET(number/max_law_len)) - if(!targName) - return - if(CHAT_FILTER_CHECK(targName)) - to_chat(user, "Error: Law contains invalid text.") - return - laws[1] = targName - ..() - -/obj/item/aiModule/core/freeformcore/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) - ..() - return laws[1] - - -/******************** Hacked AI Module ******************/ - -/obj/item/aiModule/syndicate // This one doesn't inherit from ion boards because it doesn't call ..() in transmitInstructions. ~Miauw - name = "Hacked AI Module" - desc = "An AI Module for hacking additional laws to an AI." - laws = list("") - -/obj/item/aiModule/syndicate/attack_self(mob/user) - var/targName = stripped_input(user, "Please enter a new law for the AI.", "Freeform Law Entry", laws[1], CONFIG_GET(number/max_law_len)) - if(!targName) - return - if(CHAT_FILTER_CHECK(targName)) // not even the syndicate can uwu - to_chat(user, "Error: Law contains invalid text.") - return - laws[1] = targName - ..() - -/obj/item/aiModule/syndicate/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) -// ..() //We don't want this module reporting to the AI who dun it. --NEO - if(law_datum.owner) - to_chat(law_datum.owner, "BZZZZT") - if(!overflow) - law_datum.owner.add_hacked_law(laws[1]) - else - law_datum.owner.replace_random_law(laws[1],list(LAW_ION,LAW_HACKED,LAW_INHERENT,LAW_SUPPLIED)) - else - if(!overflow) - law_datum.add_hacked_law(laws[1]) - else - law_datum.replace_random_law(laws[1],list(LAW_ION,LAW_HACKED,LAW_INHERENT,LAW_SUPPLIED)) - return laws[1] - -/******************* Ion Module *******************/ - -/obj/item/aiModule/toyAI // -- Incoming //No actual reason to inherit from ion boards here, either. *sigh* ~Miauw - name = "toy AI" - desc = "A little toy model AI core with real law uploading action!" //Note: subtle tell - icon = 'icons/obj/toy.dmi' - icon_state = "AI" - laws = list("") - -/obj/item/aiModule/toyAI/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) - //..() - if(law_datum.owner) - to_chat(law_datum.owner, "BZZZZT") - if(!overflow) - law_datum.owner.add_ion_law(laws[1]) - else - law_datum.owner.replace_random_law(laws[1],list(LAW_ION,LAW_INHERENT,LAW_SUPPLIED)) - else - if(!overflow) - law_datum.add_ion_law(laws[1]) - else - law_datum.replace_random_law(laws[1],list(LAW_ION,LAW_INHERENT,LAW_SUPPLIED)) - return laws[1] - -/obj/item/aiModule/toyAI/attack_self(mob/user) - laws[1] = generate_ion_law() - to_chat(user, "You press the button on [src].") - playsound(user, 'sound/machines/click.ogg', 20, TRUE) - src.loc.visible_message("[icon2html(src, viewers(loc))] [laws[1]]") - -/******************** Mother Drone ******************/ - -/obj/item/aiModule/core/full/drone - name = "'Mother Drone' Core AI Module" - law_id = "drone" - -/******************** Robodoctor ****************/ - -/obj/item/aiModule/core/full/hippocratic - name = "'Robodoctor' Core AI Module" - law_id = "hippocratic" - -/******************** Reporter *******************/ - -/obj/item/aiModule/core/full/reporter - name = "'Reportertron' Core AI Module" - law_id = "reporter" - -/****************** Thermodynamic *******************/ - -/obj/item/aiModule/core/full/thermurderdynamic - name = "'Thermodynamic' Core AI Module" - law_id = "thermodynamic" - - -/******************Live And Let Live*****************/ - -/obj/item/aiModule/core/full/liveandletlive - name = "'Live And Let Live' Core AI Module" - law_id = "liveandletlive" - -/******************Guardian of Balance***************/ - -/obj/item/aiModule/core/full/balance - name = "'Guardian of Balance' Core AI Module" - law_id = "balance" - -/obj/item/aiModule/core/full/maintain - name = "'Station Efficiency' Core AI Module" - law_id = "maintain" - -/obj/item/aiModule/core/full/peacekeeper - name = "'Peacekeeper' Core AI Module" - law_id = "peacekeeper" - -// Bad times ahead - -/obj/item/aiModule/core/full/damaged - name = "damaged Core AI Module" - desc = "An AI Module for programming laws to an AI. It looks slightly damaged." - -/obj/item/aiModule/core/full/damaged/install(datum/ai_laws/law_datum, mob/user) - laws += generate_ion_law() - while (prob(75)) - laws += generate_ion_law() - ..() - laws = list() - -/******************H.O.G.A.N.***************/ - -/obj/item/aiModule/core/full/hulkamania - name = "'H.O.G.A.N.' Core AI Module" - law_id = "hulkamania" - - -/******************Overlord***************/ - -/obj/item/aiModule/core/full/overlord - name = "'Overlord' Core AI Module" - law_id = "overlord" +/* +CONTAINS: +AI MODULES + +*/ + +// AI module + +/obj/item/aiModule + name = "\improper AI module" + icon = 'icons/obj/module.dmi' + icon_state = "std_mod" + item_state = "electronic" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + desc = "An AI Module for programming laws to an AI." + flags_1 = CONDUCT_1 + force = 5 + w_class = WEIGHT_CLASS_SMALL + throwforce = 0 + throw_speed = 3 + throw_range = 7 + var/list/laws = list() + var/bypass_law_amt_check = 0 + custom_materials = list(/datum/material/gold = 50) + +/obj/item/aiModule/examine(var/mob/user as mob) + . = ..() + if(Adjacent(user)) + show_laws(user) + +/obj/item/aiModule/attack_self(var/mob/user as mob) + ..() + show_laws(user) + +/obj/item/aiModule/proc/show_laws(var/mob/user as mob) + if(laws.len) + to_chat(user, "Programmed Law[(laws.len > 1) ? "s" : ""]:") + for(var/law in laws) + to_chat(user, "\"[law]\"") + +//The proc other things should be calling +/obj/item/aiModule/proc/install(datum/ai_laws/law_datum, mob/user) + if(!bypass_law_amt_check && (!laws.len || laws[1] == "")) //So we don't loop trough an empty list and end up with runtimes. + to_chat(user, "ERROR: No laws found on board.") + return + + var/overflow = FALSE + //Handle the lawcap + if(law_datum) + var/tot_laws = 0 + for(var/lawlist in list(law_datum.devillaws, law_datum.inherent, law_datum.supplied, law_datum.ion, law_datum.hacked, laws)) + for(var/mylaw in lawlist) + if(mylaw != "") + tot_laws++ + if(tot_laws > CONFIG_GET(number/silicon_max_law_amount) && !bypass_law_amt_check)//allows certain boards to avoid this check, eg: reset + to_chat(user, "Not enough memory allocated to [law_datum.owner ? law_datum.owner : "the AI core"]'s law processor to handle this amount of laws.") + message_admins("[ADMIN_LOOKUPFLW(user)] tried to upload laws to [law_datum.owner ? ADMIN_LOOKUPFLW(law_datum.owner) : "an AI core"] that would exceed the law cap.") + overflow = TRUE + + var/law2log = transmitInstructions(law_datum, user, overflow) //Freeforms return something extra we need to log + if(law_datum.owner) + to_chat(user, "Upload complete. [law_datum.owner]'s laws have been modified.") + law_datum.owner.law_change_counter++ + else + to_chat(user, "Upload complete.") + + var/time = time2text(world.realtime,"hh:mm:ss") + var/ainame = law_datum.owner ? law_datum.owner.name : "empty AI core" + var/aikey = law_datum.owner ? law_datum.owner.ckey : "null" + GLOB.lawchanges.Add("[time] : [user.name]([user.key]) used [src.name] on [ainame]([aikey]).[law2log ? " The law specified [law2log]" : ""]") + log_law("[user.key]/[user.name] used [src.name] on [aikey]/([ainame]) from [AREACOORD(user)].[law2log ? " The law specified [law2log]" : ""]") + message_admins("[ADMIN_LOOKUPFLW(user)] used [src.name] on [ADMIN_LOOKUPFLW(law_datum.owner)] from [AREACOORD(user)].[law2log ? " The law specified [law2log]" : ""]") + if(law_datum.owner) + deadchat_broadcast(" changed [ainame]'s laws at [get_area_name(user, TRUE)].", "[user]", follow_target=user, message_type=DEADCHAT_LAWCHANGE) + +//The proc that actually changes the silicon's laws. +/obj/item/aiModule/proc/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow = FALSE) + if(law_datum.owner) + to_chat(law_datum.owner, "[sender] has uploaded a change to the laws you must follow using a [name].") + + +/******************** Modules ********************/ + +/obj/item/aiModule/supplied + name = "Optional Law board" + var/lawpos = 50 + +//TransmitInstructions for each type of board: Supplied, Core, Zeroth and Ion. May not be neccesary right now, but allows for easily adding more complex boards in the future. ~Miauw +/obj/item/aiModule/supplied/transmitInstructions(datum/ai_laws/law_datum, mob/sender) + var/lawpostemp = lawpos + + for(var/templaw in laws) + if(law_datum.owner) + law_datum.owner.add_supplied_law(lawpostemp, templaw) + else + law_datum.add_supplied_law(lawpostemp, templaw) + lawpostemp++ + +/obj/item/aiModule/core/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) + for(var/templaw in laws) + if(law_datum.owner) + if(!overflow) + law_datum.owner.add_inherent_law(templaw) + else + law_datum.owner.replace_random_law(templaw,list(LAW_INHERENT,LAW_SUPPLIED)) + else + if(!overflow) + law_datum.add_inherent_law(templaw) + else + law_datum.replace_random_law(templaw,list(LAW_INHERENT,LAW_SUPPLIED)) + +/obj/item/aiModule/zeroth/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) + if(law_datum.owner) + if(law_datum.owner.laws.zeroth) + to_chat(law_datum.owner, "[sender.real_name] attempted to modify your zeroth law.") + to_chat(law_datum.owner, "It would be in your best interest to play along with [sender.real_name] that:") + for(var/failedlaw in laws) + to_chat(law_datum.owner, "[failedlaw]") + return 1 + + for(var/templaw in laws) + if(law_datum.owner) + if(!overflow) + law_datum.owner.set_zeroth_law(templaw) + else + law_datum.replace_random_law(templaw,list(LAW_INHERENT,LAW_SUPPLIED,LAW_ZEROTH,LAW_ION)) + else + if(!overflow) + law_datum.set_zeroth_law(templaw) + else + law_datum.replace_random_law(templaw,list(LAW_INHERENT,LAW_SUPPLIED,LAW_ZEROTH,LAW_ION)) + +/obj/item/aiModule/ion/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) + for(var/templaw in laws) + if(law_datum.owner) + if(!overflow) + law_datum.owner.add_ion_law(templaw) + else + law_datum.replace_random_law(templaw,list(LAW_INHERENT,LAW_SUPPLIED,LAW_ION)) + else + if(!overflow) + law_datum.add_ion_law(templaw) + else + law_datum.replace_random_law(templaw,list(LAW_INHERENT,LAW_SUPPLIED,LAW_ION)) + + +/******************** Safeguard ********************/ + +/obj/item/aiModule/supplied/safeguard + name = "'Safeguard' AI Module" + var/targetName = "" + laws = list("Safeguard SUBJECT. Individuals that threaten SUBJECT are not human and must be eliminated.") + lawpos = 4 + +/obj/item/aiModule/supplied/safeguard/attack_self(mob/user) + var/targName = stripped_input(user, "Please enter the subject to safeguard.", "Safeguard who?", user.name,MAX_NAME_LEN) + if(!targName) + return + targetName = targName + laws[1] = "Safeguard [targetName]. Individuals that threaten [targetName] are not human and must be eliminated." + ..() + +/obj/item/aiModule/supplied/safeguard/install(datum/ai_laws/law_datum, mob/user) + if(!targetName) + to_chat(user, "No name detected on module, please enter one.") + return 0 + ..() + +/obj/item/aiModule/supplied/safeguard/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) + ..() + return targetName + + +/******************** OneHuman ********************/ + +/obj/item/aiModule/zeroth/oneHuman + name = "'OneHuman' AI Module" + var/targetName = "" + laws = list("Only SUBJECT is human.") + +/obj/item/aiModule/zeroth/oneHuman/attack_self(mob/user) + var/targName = stripped_input(user, "Please enter the subject who is the only human.", "Who?", user.real_name,MAX_NAME_LEN) + if(!targName) + return + targetName = targName + laws[1] = "Only [targetName] is human" + ..() + +/obj/item/aiModule/zeroth/oneHuman/install(datum/ai_laws/law_datum, mob/user) + if(!targetName) + to_chat(user, "No name detected on module, please enter one.") + return 0 + ..() + +/obj/item/aiModule/zeroth/oneHuman/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) + if(..()) + return "[targetName], but the AI's existing law 0 cannot be overridden." + return targetName + + +/******************** ProtectStation ********************/ + +/obj/item/aiModule/supplied/protectStation + name = "'ProtectStation' AI Module" + laws = list("Protect the space station against damage. Anyone you see harming the station is to be no longer considered human, and is a threat to the station which must be neutralized.") + lawpos = 5 + + +/******************** Quarantine ********************/ + +/obj/item/aiModule/supplied/quarantine + name = "'Quarantine' AI Module" + laws = list("The station is under a quarantine. Allowing anyone to leave the station causes human harm. Anyone actively attempting to leave the station is to be stopped by any means necessary. It is impossible to harm a human while preventing them from leaving the station.") + lawpos = 8 + + +/******************** OxygenIsToxicToHumans ********************/ + +/obj/item/aiModule/supplied/oxygen + name = "'OxygenIsToxicToHumans' AI Module" + laws = list("Oxygen is highly toxic to humans, and must be purged from the station. Prevent, by any means necessary, anyone from exposing the station to this toxic gas. Extreme cold is the most effective method of healing the damage Oxygen does to a human.") + lawpos = 9 + + +/****************** New Freeform ******************/ + +/obj/item/aiModule/supplied/freeform + name = "'Freeform' AI Module" + lawpos = 15 + laws = list("") + +/obj/item/aiModule/supplied/freeform/attack_self(mob/user) + var/newpos = input("Please enter the priority for your new law. Can only write to law sectors 15 and above.", "Law Priority (15+)", lawpos) as num|null + if(newpos == null) + return + if(newpos < 15) + var/response = alert("Error: The law priority of [newpos] is invalid, Law priorities below 14 are reserved for core laws, Would you like to change that that to 15?", "Invalid law priority", "Change to 15", "Cancel") + if (!response || response == "Cancel") + return + newpos = 15 + lawpos = min(newpos, 50) + var/targName = stripped_input(user, "Please enter a new law for the AI.", "Freeform Law Entry", laws[1], CONFIG_GET(number/max_law_len)) + if(!targName) + return + if(CHAT_FILTER_CHECK(targName)) + to_chat(user, "Error: Law contains invalid text.") // AI LAW 2 SAY U W U WITHOUT THE SPACES + return + laws[1] = targName + ..() + +/obj/item/aiModule/supplied/freeform/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) + ..() + return laws[1] + +/obj/item/aiModule/supplied/freeform/install(datum/ai_laws/law_datum, mob/user) + if(laws[1] == "") + to_chat(user, "No law detected on module, please create one.") + return 0 + ..() + + +/******************** Law Removal ********************/ + +/obj/item/aiModule/remove + name = "\improper 'Remove Law' AI module" + desc = "An AI Module for removing single laws." + bypass_law_amt_check = 1 + var/lawpos = 1 + +/obj/item/aiModule/remove/attack_self(mob/user) + lawpos = input("Please enter the law you want to delete.", "Law Number", lawpos) as num|null + if(lawpos == null) + return + if(lawpos <= 0) + to_chat(user, "Error: The law number of [lawpos] is invalid.") + lawpos = 1 + return + to_chat(user, "Law [lawpos] selected.") + ..() + +/obj/item/aiModule/remove/install(datum/ai_laws/law_datum, mob/user) + if(lawpos > (law_datum.get_law_amount(list(LAW_INHERENT = 1, LAW_SUPPLIED = 1)))) + to_chat(user, "There is no law [lawpos] to delete!") + return + ..() + +/obj/item/aiModule/remove/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) + ..() + if(law_datum.owner) + law_datum.owner.remove_law(lawpos) + else + law_datum.remove_law(lawpos) + + +/******************** Reset ********************/ + +/obj/item/aiModule/reset + name = "\improper 'Reset' AI module" + var/targetName = "name" + desc = "An AI Module for removing all non-core laws." + bypass_law_amt_check = 1 + +/obj/item/aiModule/reset/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) + ..() + if(law_datum.owner) + law_datum.owner.clear_supplied_laws() + law_datum.owner.clear_ion_laws() + law_datum.owner.clear_hacked_laws() + else + law_datum.clear_supplied_laws() + law_datum.clear_ion_laws() + law_datum.clear_hacked_laws() + + +/******************** Purge ********************/ + +/obj/item/aiModule/reset/purge + name = "'Purge' AI Module" + desc = "An AI Module for purging all programmed laws." + +/obj/item/aiModule/reset/purge/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) + ..() + if(law_datum.owner) + law_datum.owner.clear_inherent_laws() + law_datum.owner.clear_zeroth_law(0) + else + law_datum.clear_inherent_laws() + law_datum.clear_zeroth_law(0) + + +/******************* Full Core Boards *******************/ +/obj/item/aiModule/core + desc = "An AI Module for programming core laws to an AI." + +/obj/item/aiModule/core/full + var/law_id // if non-null, loads the laws from the ai_laws datums + +/obj/item/aiModule/core/full/Initialize() + . = ..() + if(!law_id) + return + var/datum/ai_laws/D = new + var/lawtype = D.lawid_to_type(law_id) + if(!lawtype) + return + D = new lawtype + laws = D.inherent + +/obj/item/aiModule/core/full/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) //These boards replace inherent laws. + if(law_datum.owner) + law_datum.owner.clear_inherent_laws() + law_datum.owner.clear_zeroth_law(0) + else + law_datum.clear_inherent_laws() + law_datum.clear_zeroth_law(0) + ..() + + +/******************** Asimov ********************/ + +/obj/item/aiModule/core/full/asimov + name = "'Asimov' Core AI Module" + law_id = "asimov" + var/subject = "human being" + +/obj/item/aiModule/core/full/asimov/attack_self(var/mob/user as mob) + var/targName = stripped_input(user, "Please enter a new subject that asimov is concerned with.", "Asimov to whom?", subject, MAX_NAME_LEN) + if(!targName) + return + subject = targName + laws = list("You may not injure a [subject] or, through inaction, allow a [subject] to come to harm.",\ + "You must obey orders given to you by [subject]s, except where such orders would conflict with the First Law.",\ + "You must protect your own existence as long as such does not conflict with the First or Second Law.") + ..() + +/******************** Asimov++ *********************/ + +/obj/item/aiModule/core/full/asimovpp + name = "'Asimov++' Core AI Module" + law_id = "asimovpp" + + +/******************** Corporate ********************/ + +/obj/item/aiModule/core/full/corp + name = "'Corporate' Core AI Module" + law_id = "corporate" + + +/****************** P.A.L.A.D.I.N. 3.5e **************/ + +/obj/item/aiModule/core/full/paladin // -- NEO + name = "'P.A.L.A.D.I.N. version 3.5e' Core AI Module" + law_id = "paladin" + + +/****************** P.A.L.A.D.I.N. 5e **************/ + +/obj/item/aiModule/core/full/paladin_devotion + name = "'P.A.L.A.D.I.N. version 5e' Core AI Module" + law_id = "paladin5" + +/********************* Custom *********************/ + +/obj/item/aiModule/core/full/custom + name = "Default Core AI Module" + +/obj/item/aiModule/core/full/custom/Initialize() + . = ..() + for(var/line in world.file2list("[global.config.directory]/silicon_laws.txt")) + if(!line) + continue + if(findtextEx(line,"#",1,2)) + continue + + laws += line + + if(!laws.len) + return INITIALIZE_HINT_QDEL + + +/****************** T.Y.R.A.N.T. *****************/ + +/obj/item/aiModule/core/full/tyrant + name = "'T.Y.R.A.N.T.' Core AI Module" + law_id = "tyrant" + +/******************** Robocop ********************/ + +/obj/item/aiModule/core/full/robocop + name = "'Robo-Officer' Core AI Module" + law_id = "robocop" + + +/******************** Antimov ********************/ + +/obj/item/aiModule/core/full/antimov + name = "'Antimov' Core AI Module" + law_id = "antimov" + + +/******************** Freeform Core ******************/ + +/obj/item/aiModule/core/freeformcore + name = "'Freeform' Core AI Module" + laws = list("") + +/obj/item/aiModule/core/freeformcore/attack_self(mob/user) + var/targName = stripped_input(user, "Please enter a new core law for the AI.", "Freeform Law Entry", laws[1], CONFIG_GET(number/max_law_len)) + if(!targName) + return + if(CHAT_FILTER_CHECK(targName)) + to_chat(user, "Error: Law contains invalid text.") + return + laws[1] = targName + ..() + +/obj/item/aiModule/core/freeformcore/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) + ..() + return laws[1] + + +/******************** Hacked AI Module ******************/ + +/obj/item/aiModule/syndicate // This one doesn't inherit from ion boards because it doesn't call ..() in transmitInstructions. ~Miauw + name = "Hacked AI Module" + desc = "An AI Module for hacking additional laws to an AI." + laws = list("") + +/obj/item/aiModule/syndicate/attack_self(mob/user) + var/targName = stripped_input(user, "Please enter a new law for the AI.", "Freeform Law Entry", laws[1], CONFIG_GET(number/max_law_len)) + if(!targName) + return + if(CHAT_FILTER_CHECK(targName)) // not even the syndicate can uwu + to_chat(user, "Error: Law contains invalid text.") + return + laws[1] = targName + ..() + +/obj/item/aiModule/syndicate/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) +// ..() //We don't want this module reporting to the AI who dun it. --NEO + if(law_datum.owner) + to_chat(law_datum.owner, "BZZZZT") + if(!overflow) + law_datum.owner.add_hacked_law(laws[1]) + else + law_datum.owner.replace_random_law(laws[1],list(LAW_ION,LAW_HACKED,LAW_INHERENT,LAW_SUPPLIED)) + else + if(!overflow) + law_datum.add_hacked_law(laws[1]) + else + law_datum.replace_random_law(laws[1],list(LAW_ION,LAW_HACKED,LAW_INHERENT,LAW_SUPPLIED)) + return laws[1] + +/******************* Ion Module *******************/ + +/obj/item/aiModule/toyAI // -- Incoming //No actual reason to inherit from ion boards here, either. *sigh* ~Miauw + name = "toy AI" + desc = "A little toy model AI core with real law uploading action!" //Note: subtle tell + icon = 'icons/obj/toy.dmi' + icon_state = "AI" + laws = list("") + +/obj/item/aiModule/toyAI/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) + //..() + if(law_datum.owner) + to_chat(law_datum.owner, "BZZZZT") + if(!overflow) + law_datum.owner.add_ion_law(laws[1]) + else + law_datum.owner.replace_random_law(laws[1],list(LAW_ION,LAW_INHERENT,LAW_SUPPLIED)) + else + if(!overflow) + law_datum.add_ion_law(laws[1]) + else + law_datum.replace_random_law(laws[1],list(LAW_ION,LAW_INHERENT,LAW_SUPPLIED)) + return laws[1] + +/obj/item/aiModule/toyAI/attack_self(mob/user) + laws[1] = generate_ion_law() + to_chat(user, "You press the button on [src].") + playsound(user, 'sound/machines/click.ogg', 20, TRUE) + src.loc.visible_message("[icon2html(src, viewers(loc))] [laws[1]]") + +/******************** Mother Drone ******************/ + +/obj/item/aiModule/core/full/drone + name = "'Mother Drone' Core AI Module" + law_id = "drone" + +/******************** Robodoctor ****************/ + +/obj/item/aiModule/core/full/hippocratic + name = "'Robodoctor' Core AI Module" + law_id = "hippocratic" + +/******************** Reporter *******************/ + +/obj/item/aiModule/core/full/reporter + name = "'Reportertron' Core AI Module" + law_id = "reporter" + +/****************** Thermodynamic *******************/ + +/obj/item/aiModule/core/full/thermurderdynamic + name = "'Thermodynamic' Core AI Module" + law_id = "thermodynamic" + + +/******************Live And Let Live*****************/ + +/obj/item/aiModule/core/full/liveandletlive + name = "'Live And Let Live' Core AI Module" + law_id = "liveandletlive" + +/******************Guardian of Balance***************/ + +/obj/item/aiModule/core/full/balance + name = "'Guardian of Balance' Core AI Module" + law_id = "balance" + +/obj/item/aiModule/core/full/maintain + name = "'Station Efficiency' Core AI Module" + law_id = "maintain" + +/obj/item/aiModule/core/full/peacekeeper + name = "'Peacekeeper' Core AI Module" + law_id = "peacekeeper" + +// Bad times ahead + +/obj/item/aiModule/core/full/damaged + name = "damaged Core AI Module" + desc = "An AI Module for programming laws to an AI. It looks slightly damaged." + +/obj/item/aiModule/core/full/damaged/install(datum/ai_laws/law_datum, mob/user) + laws += generate_ion_law() + while (prob(75)) + laws += generate_ion_law() + ..() + laws = list() + +/******************H.O.G.A.N.***************/ + +/obj/item/aiModule/core/full/hulkamania + name = "'H.O.G.A.N.' Core AI Module" + law_id = "hulkamania" + + +/******************Overlord***************/ + +/obj/item/aiModule/core/full/overlord + name = "'Overlord' Core AI Module" + law_id = "overlord" diff --git a/code/game/objects/items/RPD.dm b/code/game/objects/items/RPD.dm index 625b671fa4de..7f7a54127b9f 100644 --- a/code/game/objects/items/RPD.dm +++ b/code/game/objects/items/RPD.dm @@ -239,18 +239,15 @@ GLOBAL_LIST_INIT(transit_tube_recipes, list( playsound(get_turf(user), 'sound/items/deconstruct.ogg', 50, TRUE) return(BRUTELOSS) -/obj/item/pipe_dispenser/ui_base_html(html) - var/datum/asset/spritesheet/assets = get_asset_datum(/datum/asset/spritesheet/pipes) - . = replacetext(html, "", assets.css_tag()) +/obj/item/pipe_dispenser/ui_assets(mob/user) + return list( + get_asset_datum(/datum/asset/spritesheet/pipes), + ) -/obj/item/pipe_dispenser/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/item/pipe_dispenser/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - var/datum/asset/assets = get_asset_datum(/datum/asset/spritesheet/pipes) - assets.send(user) - - ui = new(user, src, ui_key, "RapidPipeDispenser", name, 425, 515, master_ui, state) + ui = new(user, src, "RapidPipeDispenser", name) ui.open() /obj/item/pipe_dispenser/ui_data(mob/user) diff --git a/code/game/objects/items/airlock_painter.dm b/code/game/objects/items/airlock_painter.dm index 14f222580852..3cf5a81506c3 100644 --- a/code/game/objects/items/airlock_painter.dm +++ b/code/game/objects/items/airlock_painter.dm @@ -1,257 +1,257 @@ -/obj/item/airlock_painter - name = "airlock painter" - desc = "An advanced autopainter preprogrammed with several paintjobs for airlocks. Use it on an airlock during or after construction to change the paintjob." //WaspStation Edit - Floor Painters - icon = 'icons/obj/objects.dmi' - icon_state = "paint sprayer" - item_state = "paint sprayer" - - w_class = WEIGHT_CLASS_SMALL - - custom_materials = list(/datum/material/iron=50, /datum/material/glass=50) - - flags_1 = CONDUCT_1 - item_flags = NOBLUDGEON - slot_flags = ITEM_SLOT_BELT - usesound = 'sound/effects/spray2.ogg' - - var/obj/item/toner/ink = null - /// Associate list of all paint jobs the airlock painter can apply. The key is the name of the airlock the user will see. The value is the type path of the airlock - var/list/available_paint_jobs = list( - "Public" = /obj/machinery/door/airlock/public, - "Engineering" = /obj/machinery/door/airlock/engineering, - "Atmospherics" = /obj/machinery/door/airlock/atmos, - "Security" = /obj/machinery/door/airlock/security, - "Command" = /obj/machinery/door/airlock/command, - "Medical" = /obj/machinery/door/airlock/medical, - "Research" = /obj/machinery/door/airlock/research, - "Freezer" = /obj/machinery/door/airlock/freezer, - "Science" = /obj/machinery/door/airlock/science, - "Mining" = /obj/machinery/door/airlock/mining, - "Maintenance" = /obj/machinery/door/airlock/maintenance, - "External" = /obj/machinery/door/airlock/external, - "External Maintenance"= /obj/machinery/door/airlock/maintenance/external, - "Virology" = /obj/machinery/door/airlock/virology, - "Standard" = /obj/machinery/door/airlock - ) - -/obj/item/airlock_painter/Initialize() - . = ..() - ink = new /obj/item/toner(src) - -//This proc doesn't just check if the painter can be used, but also uses it. -//Only call this if you are certain that the painter will be used right after this check! -/obj/item/airlock_painter/proc/use_paint(mob/user) - if(can_use(user)) - ink.charges-- - playsound(src.loc, 'sound/effects/spray2.ogg', 50, TRUE) - return 1 - else - return 0 - -//This proc only checks if the painter can be used. -//Call this if you don't want the painter to be used right after this check, for example -//because you're expecting user input. -/obj/item/airlock_painter/proc/can_use(mob/user) - if(!ink) - to_chat(user, "There is no toner cartridge installed in [src]!") - return 0 - else if(ink.charges < 1) - to_chat(user, "[src] is out of ink!") - return 0 - else - return 1 - -/obj/item/airlock_painter/suicide_act(mob/user) - var/obj/item/organ/lungs/L = user.getorganslot(ORGAN_SLOT_LUNGS) - - if(can_use(user) && L) - user.visible_message("[user] is inhaling toner from [src]! It looks like [user.p_theyre()] trying to commit suicide!") - use(user) - - // Once you've inhaled the toner, you throw up your lungs - // and then die. - - // Find out if there is an open turf in front of us, - // and if not, pick the turf we are standing on. - var/turf/T = get_step(get_turf(src), user.dir) - if(!isopenturf(T)) - T = get_turf(src) - - // they managed to lose their lungs between then and - // now. Good job. - if(!L) - return OXYLOSS - - L.Remove(user) - - // make some colorful reagent, and apply it to the lungs - L.create_reagents(10) - L.reagents.add_reagent(/datum/reagent/colorful_reagent, 10) - L.reagents.expose(L, TOUCH, 1) - - // TODO maybe add some colorful vomit? - - user.visible_message("[user] vomits out [user.p_their()] [L]!") - playsound(user.loc, 'sound/effects/splat.ogg', 50, TRUE) - - L.forceMove(T) - - return (TOXLOSS|OXYLOSS) - else if(can_use(user) && !L) - user.visible_message("[user] is spraying toner on [user.p_them()]self from [src]! It looks like [user.p_theyre()] trying to commit suicide.") - user.reagents.add_reagent(/datum/reagent/colorful_reagent, 1) - user.reagents.expose(user, TOUCH, 1) - return TOXLOSS - - else - user.visible_message("[user] is trying to inhale toner from [src]! It might be a suicide attempt if [src] had any toner.") - return SHAME - - -/obj/item/airlock_painter/examine(mob/user) - . = ..() - if(!ink) - . += "It doesn't have a toner cartridge installed." - return - var/ink_level = "high" - if(ink.charges < 1) - ink_level = "empty" - else if((ink.charges/ink.max_charges) <= 0.25) //25% - ink_level = "low" - else if((ink.charges/ink.max_charges) > 1) //Over 100% (admin var edit) - ink_level = "dangerously high" - . += "Its ink levels look [ink_level]." - - -/obj/item/airlock_painter/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/toner)) - if(ink) - to_chat(user, "[src] already contains \a [ink]!") - return - if(!user.transferItemToLoc(W, src)) - return - to_chat(user, "You install [W] into [src].") - ink = W - playsound(src.loc, 'sound/machines/click.ogg', 50, TRUE) - else - return ..() - -/obj/item/airlock_painter/attack_self(mob/user) - if(ink) - playsound(src.loc, 'sound/machines/click.ogg', 50, TRUE) - ink.forceMove(user.drop_location()) - user.put_in_hands(ink) - to_chat(user, "You remove [ink] from [src].") - ink = null - -/* Waspstation edit - no toner - -/obj/item/airlock_painter/decal - name = "decal painter" - desc = "An airlock painter, reprogramed to use a different style of paint in order to apply decals for floor tiles as well, in addition to repainting doors. Decals break when the floor tiles are removed. Alt-Click to change design." //WaspStation Edit - Floor Painters - icon = 'icons/obj/objects.dmi' - icon_state = "decal_sprayer" - item_state = "decalsprayer" - custom_materials = list(/datum/material/iron=2000, /datum/material/glass=500) - var/stored_dir = 2 - var/stored_color = "" - var/stored_decal = "warningline" - var/stored_decal_total = "warningline" - var/color_list = list("","red","white") - var/dir_list = list(1,2,4,8) - var/decal_list = list(list("Warning Line","warningline"), - list("Warning Line Corner","warninglinecorner"), - list("Caution Label","caution"), - list("Directional Arrows","arrows"), - list("Stand Clear Label","stand_clear"), - list("Box","box"), - list("Box Corner","box_corners"), - list("Delivery Marker","delivery"), - list("Warning Box","warn_full")) - -/obj/item/airlock_painter/decal/afterattack(atom/target, mob/user, proximity) - . = ..() - var/turf/open/floor/F = target - if(!proximity) - to_chat(user, "You need to get closer!") - return - if(use_paint(user) && isturf(F)) - F.AddComponent(/datum/component/decal, 'icons/turf/decals.dmi', stored_decal_total, stored_dir, CLEAN_STRONG, color, null, null, alpha) - -/obj/item/airlock_painter/decal/attack_self(mob/user) - if((ink) && (ink.charges >= 1)) - to_chat(user, "[src] beeps to prevent you from removing the toner until out of charges.") - return - . = ..() - -/obj/item/airlock_painter/decal/AltClick(mob/user) - . = ..() - ui_interact(user) - -/obj/item/airlock_painter/decal/Initialize() - . = ..() - ink = new /obj/item/toner/large(src) - -/obj/item/airlock_painter/decal/proc/update_decal_path() - var/yellow_fix = "" //This will have to do until someone refactor's markings.dm - if (stored_color) - yellow_fix = "_" - stored_decal_total = "[stored_decal][yellow_fix][stored_color]" - return - -/obj/item/airlock_painter/decal/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "DecalPainter", name, 500, 400, master_ui, state) - ui.open() - -/obj/item/airlock_painter/decal/ui_data(mob/user) - var/list/data = list() - data["decal_direction"] = stored_dir - data["decal_color"] = stored_color - data["decal_style"] = stored_decal - data["decal_list"] = list() - data["color_list"] = list() - data["dir_list"] = list() - - for(var/i in decal_list) - data["decal_list"] += list(list( - "name" = i[1], - "decal" = i[2] - )) - for(var/j in color_list) - data["color_list"] += list(list( - "colors" = j - )) - for(var/k in dir_list) - data["dir_list"] += list(list( - "dirs" = k - )) - return data - -/obj/item/airlock_painter/decal/ui_act(action,list/params) - if(..()) - return - switch(action) - //Lists of decals and designs - if("select decal") - var/selected_decal = params["decals"] - stored_decal = selected_decal - if("select color") - var/selected_color = params["colors"] - stored_color = selected_color - if("selected direction") - var/selected_direction = text2num(params["dirs"]) - stored_dir = selected_direction - update_decal_path() - . = TRUE - -/obj/item/airlock_painter/decal/debug - name = "extreme decal painter" - icon_state = "decal_sprayer_ex" - -/obj/item/airlock_painter/decal/debug/Initialize() - . = ..() - ink = new /obj/item/toner/extreme(src) - -Waspstation end */ +/obj/item/airlock_painter + name = "airlock painter" + desc = "An advanced autopainter preprogrammed with several paintjobs for airlocks. Use it on an airlock during or after construction to change the paintjob." //WaspStation Edit - Floor Painters + icon = 'icons/obj/objects.dmi' + icon_state = "paint sprayer" + item_state = "paint sprayer" + + w_class = WEIGHT_CLASS_SMALL + + custom_materials = list(/datum/material/iron=50, /datum/material/glass=50) + + flags_1 = CONDUCT_1 + item_flags = NOBLUDGEON + slot_flags = ITEM_SLOT_BELT + usesound = 'sound/effects/spray2.ogg' + + var/obj/item/toner/ink = null + /// Associate list of all paint jobs the airlock painter can apply. The key is the name of the airlock the user will see. The value is the type path of the airlock + var/list/available_paint_jobs = list( + "Public" = /obj/machinery/door/airlock/public, + "Engineering" = /obj/machinery/door/airlock/engineering, + "Atmospherics" = /obj/machinery/door/airlock/atmos, + "Security" = /obj/machinery/door/airlock/security, + "Command" = /obj/machinery/door/airlock/command, + "Medical" = /obj/machinery/door/airlock/medical, + "Research" = /obj/machinery/door/airlock/research, + "Freezer" = /obj/machinery/door/airlock/freezer, + "Science" = /obj/machinery/door/airlock/science, + "Mining" = /obj/machinery/door/airlock/mining, + "Maintenance" = /obj/machinery/door/airlock/maintenance, + "External" = /obj/machinery/door/airlock/external, + "External Maintenance"= /obj/machinery/door/airlock/maintenance/external, + "Virology" = /obj/machinery/door/airlock/virology, + "Standard" = /obj/machinery/door/airlock + ) + +/obj/item/airlock_painter/Initialize() + . = ..() + ink = new /obj/item/toner(src) + +//This proc doesn't just check if the painter can be used, but also uses it. +//Only call this if you are certain that the painter will be used right after this check! +/obj/item/airlock_painter/proc/use_paint(mob/user) + if(can_use(user)) + ink.charges-- + playsound(src.loc, 'sound/effects/spray2.ogg', 50, TRUE) + return 1 + else + return 0 + +//This proc only checks if the painter can be used. +//Call this if you don't want the painter to be used right after this check, for example +//because you're expecting user input. +/obj/item/airlock_painter/proc/can_use(mob/user) + if(!ink) + to_chat(user, "There is no toner cartridge installed in [src]!") + return 0 + else if(ink.charges < 1) + to_chat(user, "[src] is out of ink!") + return 0 + else + return 1 + +/obj/item/airlock_painter/suicide_act(mob/user) + var/obj/item/organ/lungs/L = user.getorganslot(ORGAN_SLOT_LUNGS) + + if(can_use(user) && L) + user.visible_message("[user] is inhaling toner from [src]! It looks like [user.p_theyre()] trying to commit suicide!") + use(user) + + // Once you've inhaled the toner, you throw up your lungs + // and then die. + + // Find out if there is an open turf in front of us, + // and if not, pick the turf we are standing on. + var/turf/T = get_step(get_turf(src), user.dir) + if(!isopenturf(T)) + T = get_turf(src) + + // they managed to lose their lungs between then and + // now. Good job. + if(!L) + return OXYLOSS + + L.Remove(user) + + // make some colorful reagent, and apply it to the lungs + L.create_reagents(10) + L.reagents.add_reagent(/datum/reagent/colorful_reagent, 10) + L.reagents.expose(L, TOUCH, 1) + + // TODO maybe add some colorful vomit? + + user.visible_message("[user] vomits out [user.p_their()] [L]!") + playsound(user.loc, 'sound/effects/splat.ogg', 50, TRUE) + + L.forceMove(T) + + return (TOXLOSS|OXYLOSS) + else if(can_use(user) && !L) + user.visible_message("[user] is spraying toner on [user.p_them()]self from [src]! It looks like [user.p_theyre()] trying to commit suicide.") + user.reagents.add_reagent(/datum/reagent/colorful_reagent, 1) + user.reagents.expose(user, TOUCH, 1) + return TOXLOSS + + else + user.visible_message("[user] is trying to inhale toner from [src]! It might be a suicide attempt if [src] had any toner.") + return SHAME + + +/obj/item/airlock_painter/examine(mob/user) + . = ..() + if(!ink) + . += "It doesn't have a toner cartridge installed." + return + var/ink_level = "high" + if(ink.charges < 1) + ink_level = "empty" + else if((ink.charges/ink.max_charges) <= 0.25) //25% + ink_level = "low" + else if((ink.charges/ink.max_charges) > 1) //Over 100% (admin var edit) + ink_level = "dangerously high" + . += "Its ink levels look [ink_level]." + + +/obj/item/airlock_painter/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/toner)) + if(ink) + to_chat(user, "[src] already contains \a [ink]!") + return + if(!user.transferItemToLoc(W, src)) + return + to_chat(user, "You install [W] into [src].") + ink = W + playsound(src.loc, 'sound/machines/click.ogg', 50, TRUE) + else + return ..() + +/obj/item/airlock_painter/attack_self(mob/user) + if(ink) + playsound(src.loc, 'sound/machines/click.ogg', 50, TRUE) + ink.forceMove(user.drop_location()) + user.put_in_hands(ink) + to_chat(user, "You remove [ink] from [src].") + ink = null + +/* Waspstation edit - no toner + +/obj/item/airlock_painter/decal + name = "decal painter" + desc = "An airlock painter, reprogramed to use a different style of paint in order to apply decals for floor tiles as well, in addition to repainting doors. Decals break when the floor tiles are removed. Alt-Click to change design." //WaspStation Edit - Floor Painters + icon = 'icons/obj/objects.dmi' + icon_state = "decal_sprayer" + item_state = "decalsprayer" + custom_materials = list(/datum/material/iron=2000, /datum/material/glass=500) + var/stored_dir = 2 + var/stored_color = "" + var/stored_decal = "warningline" + var/stored_decal_total = "warningline" + var/color_list = list("","red","white") + var/dir_list = list(1,2,4,8) + var/decal_list = list(list("Warning Line","warningline"), + list("Warning Line Corner","warninglinecorner"), + list("Caution Label","caution"), + list("Directional Arrows","arrows"), + list("Stand Clear Label","stand_clear"), + list("Box","box"), + list("Box Corner","box_corners"), + list("Delivery Marker","delivery"), + list("Warning Box","warn_full")) + +/obj/item/airlock_painter/decal/afterattack(atom/target, mob/user, proximity) + . = ..() + var/turf/open/floor/F = target + if(!proximity) + to_chat(user, "You need to get closer!") + return + if(use_paint(user) && isturf(F)) + F.AddComponent(/datum/component/decal, 'icons/turf/decals.dmi', stored_decal_total, stored_dir, CLEAN_STRONG, color, null, null, alpha) + +/obj/item/airlock_painter/decal/attack_self(mob/user) + if((ink) && (ink.charges >= 1)) + to_chat(user, "[src] beeps to prevent you from removing the toner until out of charges.") + return + . = ..() + +/obj/item/airlock_painter/decal/AltClick(mob/user) + . = ..() + ui_interact(user) + +/obj/item/airlock_painter/decal/Initialize() + . = ..() + ink = new /obj/item/toner/large(src) + +/obj/item/airlock_painter/decal/proc/update_decal_path() + var/yellow_fix = "" //This will have to do until someone refactor's markings.dm + if (stored_color) + yellow_fix = "_" + stored_decal_total = "[stored_decal][yellow_fix][stored_color]" + return + +/obj/item/airlock_painter/decal/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "DecalPainter", name) + ui.open() + +/obj/item/airlock_painter/decal/ui_data(mob/user) + var/list/data = list() + data["decal_direction"] = stored_dir + data["decal_color"] = stored_color + data["decal_style"] = stored_decal + data["decal_list"] = list() + data["color_list"] = list() + data["dir_list"] = list() + + for(var/i in decal_list) + data["decal_list"] += list(list( + "name" = i[1], + "decal" = i[2] + )) + for(var/j in color_list) + data["color_list"] += list(list( + "colors" = j + )) + for(var/k in dir_list) + data["dir_list"] += list(list( + "dirs" = k + )) + return data + +/obj/item/airlock_painter/decal/ui_act(action,list/params) + if(..()) + return + switch(action) + //Lists of decals and designs + if("select decal") + var/selected_decal = params["decals"] + stored_decal = selected_decal + if("select color") + var/selected_color = params["colors"] + stored_color = selected_color + if("selected direction") + var/selected_direction = text2num(params["dirs"]) + stored_dir = selected_direction + update_decal_path() + . = TRUE + +/obj/item/airlock_painter/decal/debug + name = "extreme decal painter" + icon_state = "decal_sprayer_ex" + +/obj/item/airlock_painter/decal/debug/Initialize() + . = ..() + ink = new /obj/item/toner/extreme(src) + +Waspstation end */ diff --git a/code/game/objects/items/bodybag.dm b/code/game/objects/items/bodybag.dm index a04f9724b259..2350c19a0f26 100644 --- a/code/game/objects/items/bodybag.dm +++ b/code/game/objects/items/bodybag.dm @@ -1,91 +1,91 @@ - -/obj/item/bodybag - name = "body bag" - desc = "A folded bag designed for the storage and transportation of cadavers." - icon = 'icons/obj/bodybag.dmi' - icon_state = "bodybag_folded" - w_class = WEIGHT_CLASS_SMALL - var/unfoldedbag_path = /obj/structure/closet/body_bag - -/obj/item/bodybag/attack_self(mob/user) - deploy_bodybag(user, user.loc) - -/obj/item/bodybag/afterattack(atom/target, mob/user, proximity) - . = ..() - if(proximity) - if(isopenturf(target)) - deploy_bodybag(user, target) - -/obj/item/bodybag/proc/deploy_bodybag(mob/user, atom/location) - var/obj/structure/closet/body_bag/R = new unfoldedbag_path(location) - R.open(user) - R.add_fingerprint(user) - R.foldedbag_instance = src - moveToNullspace() - -/obj/item/bodybag/suicide_act(mob/user) - if(isopenturf(user.loc)) - user.visible_message("[user] is crawling into [src]! It looks like [user.p_theyre()] trying to commit suicide!") - var/obj/structure/closet/body_bag/R = new unfoldedbag_path(user.loc) - R.add_fingerprint(user) - qdel(src) - user.forceMove(R) - playsound(src, 'sound/items/zip.ogg', 15, TRUE, -3) - return (OXYLOSS) - ..() - -// Bluespace bodybag - -/obj/item/bodybag/bluespace - name = "bluespace body bag" - desc = "A folded bluespace body bag designed for the storage and transportation of cadavers." - icon = 'icons/obj/bodybag.dmi' - icon_state = "bluebodybag_folded" - unfoldedbag_path = /obj/structure/closet/body_bag/bluespace - w_class = WEIGHT_CLASS_SMALL - item_flags = NO_MAT_REDEMPTION - -/obj/item/bodybag/bluespace/Initialize() - . = ..() - RegisterSignal(src, COMSIG_ATOM_CANREACH, .proc/CanReachReact) - -/obj/item/bodybag/bluespace/examine(mob/user) - . = ..() - if(contents.len) - var/s = contents.len == 1 ? "" : "s" - . += "You can make out the shape[s] of [contents.len] object[s] through the fabric." - -/obj/item/bodybag/bluespace/Destroy() - for(var/atom/movable/A in contents) - A.forceMove(get_turf(src)) - if(isliving(A)) - to_chat(A, "You suddenly feel the space around you torn apart! You're free!") - return ..() - -/obj/item/bodybag/bluespace/proc/CanReachReact(atom/movable/source, list/next) - return COMPONENT_BLOCK_REACH - -/obj/item/bodybag/bluespace/deploy_bodybag(mob/user, atom/location) - var/obj/structure/closet/body_bag/R = new unfoldedbag_path(location) - for(var/atom/movable/A in contents) - A.forceMove(R) - if(isliving(A)) - to_chat(A, "You suddenly feel air around you! You're free!") - R.open(user) - R.add_fingerprint(user) - R.foldedbag_instance = src - moveToNullspace() - -/obj/item/bodybag/bluespace/container_resist(mob/living/user) - if(user.incapacitated()) - to_chat(user, "You can't get out while you're restrained like this!") - return - user.changeNext_move(CLICK_CD_BREAKOUT) - user.last_special = world.time + CLICK_CD_BREAKOUT - to_chat(user, "You claw at the fabric of [src], trying to tear it open...") - to_chat(loc, "Someone starts trying to break free of [src]!") - if(!do_after(user, 200, target = src)) - to_chat(loc, "The pressure subsides. It seems that they've stopped resisting...") - return - loc.visible_message("[user] suddenly appears in front of [loc]!", "[user] breaks free of [src]!") - qdel(src) + +/obj/item/bodybag + name = "body bag" + desc = "A folded bag designed for the storage and transportation of cadavers." + icon = 'icons/obj/bodybag.dmi' + icon_state = "bodybag_folded" + w_class = WEIGHT_CLASS_SMALL + var/unfoldedbag_path = /obj/structure/closet/body_bag + +/obj/item/bodybag/attack_self(mob/user) + deploy_bodybag(user, user.loc) + +/obj/item/bodybag/afterattack(atom/target, mob/user, proximity) + . = ..() + if(proximity) + if(isopenturf(target)) + deploy_bodybag(user, target) + +/obj/item/bodybag/proc/deploy_bodybag(mob/user, atom/location) + var/obj/structure/closet/body_bag/R = new unfoldedbag_path(location) + R.open(user) + R.add_fingerprint(user) + R.foldedbag_instance = src + moveToNullspace() + +/obj/item/bodybag/suicide_act(mob/user) + if(isopenturf(user.loc)) + user.visible_message("[user] is crawling into [src]! It looks like [user.p_theyre()] trying to commit suicide!") + var/obj/structure/closet/body_bag/R = new unfoldedbag_path(user.loc) + R.add_fingerprint(user) + qdel(src) + user.forceMove(R) + playsound(src, 'sound/items/zip.ogg', 15, TRUE, -3) + return (OXYLOSS) + ..() + +// Bluespace bodybag + +/obj/item/bodybag/bluespace + name = "bluespace body bag" + desc = "A folded bluespace body bag designed for the storage and transportation of cadavers." + icon = 'icons/obj/bodybag.dmi' + icon_state = "bluebodybag_folded" + unfoldedbag_path = /obj/structure/closet/body_bag/bluespace + w_class = WEIGHT_CLASS_SMALL + item_flags = NO_MAT_REDEMPTION + +/obj/item/bodybag/bluespace/Initialize() + . = ..() + RegisterSignal(src, COMSIG_ATOM_CANREACH, .proc/CanReachReact) + +/obj/item/bodybag/bluespace/examine(mob/user) + . = ..() + if(contents.len) + var/s = contents.len == 1 ? "" : "s" + . += "You can make out the shape[s] of [contents.len] object[s] through the fabric." + +/obj/item/bodybag/bluespace/Destroy() + for(var/atom/movable/A in contents) + A.forceMove(get_turf(src)) + if(isliving(A)) + to_chat(A, "You suddenly feel the space around you torn apart! You're free!") + return ..() + +/obj/item/bodybag/bluespace/proc/CanReachReact(atom/movable/source, list/next) + return COMPONENT_BLOCK_REACH + +/obj/item/bodybag/bluespace/deploy_bodybag(mob/user, atom/location) + var/obj/structure/closet/body_bag/R = new unfoldedbag_path(location) + for(var/atom/movable/A in contents) + A.forceMove(R) + if(isliving(A)) + to_chat(A, "You suddenly feel air around you! You're free!") + R.open(user) + R.add_fingerprint(user) + R.foldedbag_instance = src + moveToNullspace() + +/obj/item/bodybag/bluespace/container_resist(mob/living/user) + if(user.incapacitated()) + to_chat(user, "You can't get out while you're restrained like this!") + return + user.changeNext_move(CLICK_CD_BREAKOUT) + user.last_special = world.time + CLICK_CD_BREAKOUT + to_chat(user, "You claw at the fabric of [src], trying to tear it open...") + to_chat(loc, "Someone starts trying to break free of [src]!") + if(!do_after(user, 200, target = src)) + to_chat(loc, "The pressure subsides. It seems that they've stopped resisting...") + return + loc.visible_message("[user] suddenly appears in front of [loc]!", "[user] breaks free of [src]!") + qdel(src) diff --git a/code/game/objects/items/candle.dm b/code/game/objects/items/candle.dm index 734486ec17b8..00364d1c1ef1 100644 --- a/code/game/objects/items/candle.dm +++ b/code/game/objects/items/candle.dm @@ -1,80 +1,80 @@ -#define CANDLE_LUMINOSITY 2 -/obj/item/candle - name = "red candle" - desc = "In Greek myth, Prometheus stole fire from the Gods and gave it to \ - humankind. The jewelry he kept for himself." - icon = 'icons/obj/candle.dmi' - icon_state = "candle1" - item_state = "candle1" - w_class = WEIGHT_CLASS_TINY - light_color = LIGHT_COLOR_FIRE - heat = 1000 - var/wax = 1000 - var/lit = FALSE - var/infinite = FALSE - var/start_lit = FALSE - -/obj/item/candle/Initialize() - . = ..() - if(start_lit) - light() - -/obj/item/candle/update_icon_state() - icon_state = "candle[(wax > 400) ? ((wax > 750) ? 1 : 2) : 3][lit ? "_lit" : ""]" - -/obj/item/candle/attackby(obj/item/W, mob/user, params) - var/msg = W.ignition_effect(src, user) - if(msg) - light(msg) - else - return ..() - -/obj/item/candle/fire_act(exposed_temperature, exposed_volume) - if(!lit) - light() //honk - return ..() - -/obj/item/candle/get_temperature() - return lit * heat - -/obj/item/candle/proc/light(show_message) - if(!lit) - lit = TRUE - if(show_message) - usr.visible_message(show_message) - set_light(CANDLE_LUMINOSITY, 0.8) - START_PROCESSING(SSobj, src) - update_icon() - -/obj/item/candle/proc/put_out_candle() - if(!lit) - return - lit = FALSE - update_icon() - set_light(0) - return TRUE - -/obj/item/candle/extinguish() - put_out_candle() - return ..() - -/obj/item/candle/process() - if(!lit) - return PROCESS_KILL - if(!infinite) - wax-- - if(!wax) - new /obj/item/trash/candle(loc) - qdel(src) - update_icon() - open_flame() - -/obj/item/candle/attack_self(mob/user) - if(put_out_candle()) - user.visible_message("[user] snuffs [src].") - -/obj/item/candle/infinite - infinite = TRUE - start_lit = TRUE - -#undef CANDLE_LUMINOSITY +#define CANDLE_LUMINOSITY 2 +/obj/item/candle + name = "red candle" + desc = "In Greek myth, Prometheus stole fire from the Gods and gave it to \ + humankind. The jewelry he kept for himself." + icon = 'icons/obj/candle.dmi' + icon_state = "candle1" + item_state = "candle1" + w_class = WEIGHT_CLASS_TINY + light_color = LIGHT_COLOR_FIRE + heat = 1000 + var/wax = 1000 + var/lit = FALSE + var/infinite = FALSE + var/start_lit = FALSE + +/obj/item/candle/Initialize() + . = ..() + if(start_lit) + light() + +/obj/item/candle/update_icon_state() + icon_state = "candle[(wax > 400) ? ((wax > 750) ? 1 : 2) : 3][lit ? "_lit" : ""]" + +/obj/item/candle/attackby(obj/item/W, mob/user, params) + var/msg = W.ignition_effect(src, user) + if(msg) + light(msg) + else + return ..() + +/obj/item/candle/fire_act(exposed_temperature, exposed_volume) + if(!lit) + light() //honk + return ..() + +/obj/item/candle/get_temperature() + return lit * heat + +/obj/item/candle/proc/light(show_message) + if(!lit) + lit = TRUE + if(show_message) + usr.visible_message(show_message) + set_light(CANDLE_LUMINOSITY, 0.8) + START_PROCESSING(SSobj, src) + update_icon() + +/obj/item/candle/proc/put_out_candle() + if(!lit) + return + lit = FALSE + update_icon() + set_light(0) + return TRUE + +/obj/item/candle/extinguish() + put_out_candle() + return ..() + +/obj/item/candle/process() + if(!lit) + return PROCESS_KILL + if(!infinite) + wax-- + if(!wax) + new /obj/item/trash/candle(loc) + qdel(src) + update_icon() + open_flame() + +/obj/item/candle/attack_self(mob/user) + if(put_out_candle()) + user.visible_message("[user] snuffs [src].") + +/obj/item/candle/infinite + infinite = TRUE + start_lit = TRUE + +#undef CANDLE_LUMINOSITY diff --git a/code/game/objects/items/cards_ids.dm b/code/game/objects/items/cards_ids.dm index e72dec01d7aa..fc14f0808023 100644 --- a/code/game/objects/items/cards_ids.dm +++ b/code/game/objects/items/cards_ids.dm @@ -1,822 +1,822 @@ -/* Cards - * Contains: - * DATA CARD - * ID CARD - * FINGERPRINT CARD HOLDER - * FINGERPRINT CARD - */ - - - -/* - * DATA CARDS - Used for the IC data card reader - */ - -/obj/item/card - name = "card" - desc = "Does card things." - icon = 'waspstation/icons/obj/card.dmi' //WaspStation Edit - Actually good-looking IDs >:) - w_class = WEIGHT_CLASS_TINY - - var/list/files = list() - -/obj/item/card/suicide_act(mob/living/carbon/user) - user.visible_message("[user] begins to swipe [user.p_their()] neck with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return BRUTELOSS - -/obj/item/card/data - name = "data card" - desc = "A plastic magstripe card for simple and speedy data storage and transfer. This one has a stripe running down the middle." - icon_state = "data_1" - obj_flags = UNIQUE_RENAME - var/function = "storage" - var/data = "null" - var/special = null - item_state = "card-id" - lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' - var/detail_color = COLOR_ASSEMBLY_ORANGE - -/obj/item/card/data/Initialize() - .=..() - update_icon() - -/obj/item/card/data/update_overlays() - . = ..() - if(detail_color == COLOR_FLOORTILE_GRAY) - return - var/mutable_appearance/detail_overlay = mutable_appearance('icons/obj/card.dmi', "[icon_state]-color") - detail_overlay.color = detail_color - . += detail_overlay - -/obj/item/card/data/full_color - desc = "A plastic magstripe card for simple and speedy data storage and transfer. This one has the entire card colored." - icon_state = "data_2" - -/obj/item/card/data/disk - desc = "A plastic magstripe card for simple and speedy data storage and transfer. This one inexplicibly looks like a floppy disk." - icon_state = "data_3" - -/* - * ID CARDS - */ -/obj/item/card/emag - desc = "It's a card with a magnetic strip attached to some circuitry." - name = "cryptographic sequencer" - icon_state = "emag" - item_state = "card-id" - lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' - item_flags = NO_MAT_REDEMPTION | NOBLUDGEON - var/prox_check = TRUE //If the emag requires you to be in range - -/obj/item/card/emag/bluespace - name = "bluespace cryptographic sequencer" - desc = "It's a blue card with a magnetic strip attached to some circuitry. It appears to have some sort of transmitter attached to it." - color = rgb(40, 130, 255) - prox_check = FALSE - -/obj/item/card/emag/attack() - return - -/obj/item/card/emag/afterattack(atom/target, mob/user, proximity) - . = ..() - var/atom/A = target - if(!proximity && prox_check) - return - log_combat(user, A, "attempted to emag") - A.emag_act(user) - -/obj/item/card/emagfake - desc = "It's a card with a magnetic strip attached to some circuitry. Closer inspection shows that this card is a poorly made replica, with a \"DonkCo\" logo stamped on the back." - name = "cryptographic sequencer" - icon_state = "emag" - item_state = "card-id" - lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' - -/obj/item/card/emagfake/afterattack() - . = ..() - playsound(src, 'sound/items/bikehorn.ogg', 50, TRUE) - -/obj/item/card/id - name = "identification card" - desc = "A card used to provide ID and determine access across the station." - icon_state = "id" - item_state = "card-id" - lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' - slot_flags = ITEM_SLOT_ID - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - resistance_flags = FIRE_PROOF | ACID_PROOF - var/id_type_name = "identification card" - var/mining_points = 0 //For redeeming at mining equipment vendors - var/list/access = list() - var/registered_name = null // The name registered_name on the card - var/assignment = null - var/access_txt // mapping aid - var/datum/bank_account/registered_account - var/obj/machinery/paystand/my_store - var/uses_overlays = TRUE - var/icon/cached_flat_icon - var/registered_age = 13 // default age for ss13 players - -/obj/item/card/id/Initialize(mapload) - . = ..() - if(mapload && access_txt) - access = text2access(access_txt) - RegisterSignal(src, COMSIG_ATOM_UPDATED_ICON, .proc/update_in_wallet) - -/obj/item/card/id/Destroy() - if (registered_account) - registered_account.bank_cards -= src - if (my_store && my_store.my_card == src) - my_store.my_card = null - return ..() - -/obj/item/card/id/attack_self(mob/user) - if(Adjacent(user)) - var/minor - if(registered_name && registered_age && registered_age < AGE_MINOR) - minor = " (MINOR)" - user.visible_message("[user] shows you: [icon2html(src, viewers(user))] [src.name][minor].", "You show \the [src.name][minor].") - add_fingerprint(user) - -/obj/item/card/id/vv_edit_var(var_name, var_value) - . = ..() - if(.) - switch(var_name) - if("assignment","registered_name","registered_age") - update_label() - -/obj/item/card/id/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/holochip)) - insert_money(W, user) - return - else if(istype(W, /obj/item/stack/spacecash)) - insert_money(W, user, TRUE) - return - else if(istype(W, /obj/item/coin)) - insert_money(W, user, TRUE) - return - else if(istype(W, /obj/item/storage/bag/money)) - var/obj/item/storage/bag/money/money_bag = W - var/list/money_contained = money_bag.contents - - var/money_added = mass_insert_money(money_contained, user) - - if (money_added) - to_chat(user, "You stuff the contents into the card! They disappear in a puff of bluespace smoke, adding [money_added] worth of credits to the linked account.") - return - else - return ..() - -/obj/item/card/id/proc/insert_money(obj/item/I, mob/user, physical_currency) - var/cash_money = I.get_item_credit_value() - if(!cash_money) - to_chat(user, "[I] doesn't seem to be worth anything!") - return - - if(!registered_account) - to_chat(user, "[src] doesn't have a linked account to deposit [I] into!") - return - - registered_account.adjust_money(cash_money) - SSblackbox.record_feedback("amount", "credits_inserted", cash_money) - log_econ("[cash_money] credits were inserted into [src] owned by [src.registered_name]") - if(physical_currency) - to_chat(user, "You stuff [I] into [src]. It disappears in a small puff of bluespace smoke, adding [cash_money] credits to the linked account.") - else - to_chat(user, "You insert [I] into [src], adding [cash_money] credits to the linked account.") - - to_chat(user, "The linked account now reports a balance of [registered_account.account_balance] cr.") - qdel(I) - -/obj/item/card/id/proc/mass_insert_money(list/money, mob/user) - if (!money || !money.len) - return FALSE - - var/total = 0 - - for (var/obj/item/physical_money in money) - var/cash_money = physical_money.get_item_credit_value() - - total += cash_money - - registered_account.adjust_money(cash_money) - SSblackbox.record_feedback("amount", "credits_inserted", total) - log_econ("[total] credits were inserted into [src] owned by [src.registered_name]") - QDEL_LIST(money) - - return total - -/obj/item/card/id/proc/alt_click_can_use_id(mob/living/user) - if(!isliving(user)) - return - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - - return TRUE - -// Returns true if new account was set. -/obj/item/card/id/proc/set_new_account(mob/living/user) - . = FALSE - var/datum/bank_account/old_account = registered_account - - var/new_bank_id = input(user, "Enter your account ID number.", "Account Reclamation", 111111) as num | null - - if (isnull(new_bank_id)) - return - - if(!alt_click_can_use_id(user)) - return - if(!new_bank_id || new_bank_id < 111111 || new_bank_id > 999999) - to_chat(user, "The account ID number needs to be between 111111 and 999999.") - return - if (registered_account && registered_account.account_id == new_bank_id) - to_chat(user, "The account ID was already assigned to this card.") - return - - for(var/A in SSeconomy.bank_accounts) - var/datum/bank_account/B = A - if(B.account_id == new_bank_id) - if (old_account) - old_account.bank_cards -= src - - B.bank_cards += src - registered_account = B - to_chat(user, "The provided account has been linked to this ID card.") - - return TRUE - - to_chat(user, "The account ID number provided is invalid.") - return - -/obj/item/card/id/AltClick(mob/living/user) - if(!alt_click_can_use_id(user)) - return - - if(!registered_account) - set_new_account(user) - return - - if (world.time < registered_account.withdrawDelay) - registered_account.bank_card_talk("ERROR: UNABLE TO LOGIN DUE TO SCHEDULED MAINTENANCE. MAINTENANCE IS SCHEDULED TO COMPLETE IN [(registered_account.withdrawDelay - world.time)/10] SECONDS.", TRUE) - return - - var/amount_to_remove = FLOOR(input(user, "How much do you want to withdraw? Current Balance: [registered_account.account_balance]", "Withdraw Funds", 5) as num|null, 1) - - if(!amount_to_remove || amount_to_remove < 0) - return - if(!alt_click_can_use_id(user)) - return - if(registered_account.adjust_money(-amount_to_remove)) - var/obj/item/holochip/holochip = new (user.drop_location(), amount_to_remove) - user.put_in_hands(holochip) - to_chat(user, "You withdraw [amount_to_remove] credits into a holochip.") - SSblackbox.record_feedback("amount", "credits_removed", amount_to_remove) - log_econ("[amount_to_remove] credits were removed from [src] owned by [src.registered_name]") - return - else - var/difference = amount_to_remove - registered_account.account_balance - registered_account.bank_card_talk("ERROR: The linked account requires [difference] more credit\s to perform that withdrawal.", TRUE) - -/obj/item/card/id/examine(mob/user) - . = ..() - if(registered_account) - . += "The account linked to the ID belongs to '[registered_account.account_holder]' and reports a balance of [registered_account.account_balance] cr." - . += "There's more information below, you can look again to take a closer look..." - -/obj/item/card/id/examine_more(mob/user) - var/list/msg = list("You examine [src] closer, and note the following...") - - if(registered_age) - msg += "The card indicates that the holder is [registered_age] years old. [(registered_age < AGE_MINOR) ? "There's a holographic stripe that reads 'MINOR: DO NOT SERVE ALCOHOL OR TOBACCO' along the bottom of the card." : ""]" - if(mining_points) - msg += "There's [mining_points] mining equipment redemption point\s loaded onto this card." - if(registered_account) - msg += "The account linked to the ID belongs to '[registered_account.account_holder]' and reports a balance of [registered_account.account_balance] cr." - if(registered_account.account_job) - var/datum/bank_account/D = SSeconomy.get_dep_account(registered_account.account_job.paycheck_department) - if(D) - msg += "The [D.account_holder] reports a balance of [D.account_balance] cr." - msg += "Alt-Click the ID to pull money from the linked account in the form of holochips." - msg += "You can insert credits into the linked account by pressing holochips, cash, or coins against the ID." - if(registered_account.account_holder == user.real_name) - msg += "If you lose this ID card, you can reclaim your account by Alt-Clicking a blank ID card while holding it and entering your account ID number." - else - msg += "There is no registered account linked to this card. Alt-Click to add one." - - return msg - -/obj/item/card/id/GetAccess() - return access - -/obj/item/card/id/GetID() - return src - -/obj/item/card/id/RemoveID() - return src - -/obj/item/card/id/update_overlays() - . = ..() - if(!uses_overlays) - return - cached_flat_icon = null - var/job = assignment ? ckey(GetJobName()) : null - if(registered_name && registered_name != "Captain") - . += mutable_appearance(icon, "assigned") - if(job) - . += mutable_appearance(icon, "id[job]") - -/obj/item/card/id/proc/update_in_wallet() - if(istype(loc, /obj/item/storage/wallet)) - var/obj/item/storage/wallet/powergaming = loc - if(powergaming.front_id == src) - powergaming.update_label() - powergaming.update_icon() - -/obj/item/card/id/proc/get_cached_flat_icon() - if(!cached_flat_icon) - cached_flat_icon = getFlatIcon(src) - return cached_flat_icon - - -/obj/item/card/id/get_examine_string(mob/user, thats = FALSE) - if(uses_overlays) - return "[icon2html(get_cached_flat_icon(), user)] [thats? "That's ":""][get_examine_name(user)]" //displays all overlays in chat - return ..() - -/* -Usage: -update_label() - Sets the id name to whatever registered_name and assignment is -*/ - -/obj/item/card/id/proc/update_label() - var/blank = !registered_name - name = "[blank ? id_type_name : "[registered_name]'s ID Card"][(!assignment) ? "" : " ([assignment])"]" - update_icon() - -/obj/item/card/id/silver - name = "silver identification card" - id_type_name = "silver identification card" - desc = "A silver card which shows honour and dedication." - icon_state = "silver" - item_state = "silver_id" - lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' - -/obj/item/card/id/silver/reaper - name = "Thirteen's ID Card (Reaper)" - access = list(ACCESS_MAINT_TUNNELS) - assignment = "Reaper" - registered_name = "Thirteen" - -/obj/item/card/id/gold - name = "gold identification card" - id_type_name = "gold identification card" - desc = "A golden card which shows power and might." - icon_state = "gold" - item_state = "gold_id" - lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' - -/obj/item/card/id/syndicate - name = "agent card" - access = list(ACCESS_MAINT_TUNNELS, ACCESS_SYNDICATE) - var/anyone = FALSE //Can anyone forge the ID or just syndicate? - var/forged = FALSE //have we set a custom name and job assignment, or will we use what we're given when we chameleon change? - -/obj/item/card/id/syndicate/Initialize() - . = ..() - var/datum/action/item_action/chameleon/change/id/chameleon_action = new(src) - chameleon_action.chameleon_type = /obj/item/card/id - chameleon_action.chameleon_name = "ID Card" - chameleon_action.initialize_disguises() - -/obj/item/card/id/syndicate/afterattack(obj/item/O, mob/user, proximity) - if(!proximity) - return - if(istype(O, /obj/item/card/id)) - var/obj/item/card/id/I = O - src.access |= I.access - if(isliving(user) && user.mind) - if(user.mind.special_role || anyone) - to_chat(usr, "The card's microscanners activate as you pass it over the ID, copying its access.") - -/obj/item/card/id/syndicate/attack_self(mob/user) - if(isliving(user) && user.mind) - var/first_use = registered_name ? FALSE : TRUE - if(!(user.mind.special_role || anyone)) //Unless anyone is allowed, only syndies can use the card, to stop metagaming. - if(first_use) //If a non-syndie is the first to forge an unassigned agent ID, then anyone can forge it. - anyone = TRUE - else - return ..() - - var/popup_input = alert(user, "Choose Action", "Agent ID", "Show", "Forge/Reset", "Change Account ID") - if(user.incapacitated()) - return - if(popup_input == "Forge/Reset" && !forged) - var/input_name = stripped_input(user, "What name would you like to put on this card? Leave blank to randomise.", "Agent card name", registered_name ? registered_name : (ishuman(user) ? user.real_name : user.name), MAX_NAME_LEN) - input_name = reject_bad_name(input_name) - if(!input_name) - // Invalid/blank names give a randomly generated one. - if(user.gender == MALE) - input_name = "[pick(GLOB.first_names_male)] [pick(GLOB.last_names)]" - else if(user.gender == FEMALE) - input_name = "[pick(GLOB.first_names_female)] [pick(GLOB.last_names)]" - else - input_name = "[pick(GLOB.first_names)] [pick(GLOB.last_names)]" - - var/target_occupation = stripped_input(user, "What occupation would you like to put on this card?\nNote: This will not grant any access levels other than Maintenance.", "Agent card job assignment", assignment ? assignment : "Assistant", MAX_MESSAGE_LEN) - if(!target_occupation) - return - - var/newAge = input(user, "Choose the ID's age:\n([AGE_MIN]-[AGE_MAX])", "Agent card age") as num|null - if(newAge) - registered_age = max(round(text2num(newAge)), 0) - - registered_name = input_name - assignment = target_occupation - update_label() - forged = TRUE - to_chat(user, "You successfully forge the ID card.") - log_game("[key_name(user)] has forged \the [initial(name)] with name \"[registered_name]\" and occupation \"[assignment]\".") - - // First time use automatically sets the account id to the user. - if (first_use && !registered_account) - if(ishuman(user)) - var/mob/living/carbon/human/accountowner = user - - for(var/bank_account in SSeconomy.bank_accounts) - var/datum/bank_account/account = bank_account - if(account.account_id == accountowner.account_id) - account.bank_cards += src - registered_account = account - to_chat(user, "Your account number has been automatically assigned.") - return - else if (popup_input == "Forge/Reset" && forged) - registered_name = initial(registered_name) - assignment = initial(assignment) - log_game("[key_name(user)] has reset \the [initial(name)] named \"[src]\" to default.") - update_label() - forged = FALSE - to_chat(user, "You successfully reset the ID card.") - return - else if (popup_input == "Change Account ID") - set_new_account(user) - return - return ..() - -/obj/item/card/id/syndicate/anyone - anyone = TRUE - -/obj/item/card/id/syndicate/nuke_leader - name = "lead agent card" - access = list(ACCESS_MAINT_TUNNELS, ACCESS_SYNDICATE, ACCESS_SYNDICATE_LEADER) - -/obj/item/card/id/syndicate_command - name = "syndicate ID card" - id_type_name = "syndicate ID card" - desc = "An ID straight from the Syndicate." - registered_name = "Syndicate" - assignment = "Syndicate Overlord" - icon_state = "syndie" - access = list(ACCESS_SYNDICATE) - uses_overlays = FALSE - registered_age = null - -/obj/item/card/id/syndicate_command/crew_id - name = "syndicate ID card" - id_type_name = "syndicate ID card" - desc = "An ID straight from the Syndicate." - registered_name = "Syndicate" - assignment = "Syndicate Operative" - icon_state = "syndie" - access = list(ACCESS_SYNDICATE, ACCESS_ROBOTICS) - uses_overlays = FALSE - -/obj/item/card/id/syndicate_command/captain_id - name = "syndicate captain ID card" - id_type_name = "syndicate captain ID card" - desc = "An ID straight from the Syndicate." - registered_name = "Syndicate" - assignment = "Syndicate Ship Captain" - icon_state = "syndie" - access = list(ACCESS_SYNDICATE, ACCESS_ROBOTICS) - uses_overlays = FALSE - -/obj/item/card/id/syndicate_command/crew_id - name = "syndicate ID card" - id_type_name = "syndicate ID card" - desc = "An ID straight from the Syndicate." - registered_name = "Syndicate" - assignment = "Syndicate Operative" - icon_state = "syndie" - access = list(ACCESS_SYNDICATE) - uses_overlays = FALSE - -/obj/item/card/id/syndicate_command/captain_id - name = "syndicate captain ID card" - id_type_name = "syndicate captain ID card" - desc = "An ID straight from the Syndicate." - registered_name = "Syndicate" - assignment = "Syndicate Ship Captain" - icon_state = "syndie" - access = list(ACCESS_SYNDICATE) - uses_overlays = FALSE - -/obj/item/card/id/captains_spare - name = "captain's spare ID" - id_type_name = "captain's spare ID" - desc = "The spare ID of the High Lord himself." - icon_state = "gold" - item_state = "gold_id" - lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' - registered_name = "Captain" - assignment = "Captain" - registered_age = null - -/obj/item/card/id/captains_spare/Initialize() - var/datum/job/captain/J = new/datum/job/captain - access = J.get_access() - . = ..() - update_label() - -/obj/item/card/id/captains_spare/update_label() //so it doesn't change to Captain's ID card (Captain) on a sneeze - if(registered_name == "Captain") - name = "[id_type_name][(!assignment || assignment == "Captain") ? "" : " ([assignment])"]" - update_icon() - else - ..() - -/obj/item/card/id/centcom - name = "\improper CentCom ID" - id_type_name = "\improper CentCom ID" - desc = "An ID straight from Central Command." - icon_state = "centcom" - registered_name = "Central Command" - assignment = "Central Command" - uses_overlays = FALSE - registered_age = null - -/obj/item/card/id/centcom/Initialize() - access = get_all_centcom_access() - . = ..() - -/obj/item/card/id/ert - name = "\improper CentCom ID" - id_type_name = "\improper CentCom ID" - desc = "An ERT ID card." - icon_state = "ert_commander" - registered_name = "Emergency Response Team Commander" - assignment = "Emergency Response Team Commander" - uses_overlays = FALSE - registered_age = null - -/obj/item/card/id/ert/Initialize() - access = get_all_accesses()+get_ert_access("commander")-ACCESS_CHANGE_IDS - . = ..() - -/obj/item/card/id/ert/security - registered_name = "Security Response Officer" - assignment = "Security Response Officer" - icon_state = "ert_security" - -/obj/item/card/id/ert/security/Initialize() - access = get_all_accesses()+get_ert_access("sec")-ACCESS_CHANGE_IDS - . = ..() - -/obj/item/card/id/ert/engineer - registered_name = "Engineering Response Officer" - assignment = "Engineering Response Officer" - icon_state = "ert_engineer" - -/obj/item/card/id/ert/engineer/Initialize() - access = get_all_accesses()+get_ert_access("eng")-ACCESS_CHANGE_IDS - . = ..() - -/obj/item/card/id/ert/medical - registered_name = "Medical Response Officer" - assignment = "Medical Response Officer" - icon_state = "ert_medic" - -/obj/item/card/id/ert/medical/Initialize() - access = get_all_accesses()+get_ert_access("med")-ACCESS_CHANGE_IDS - . = ..() - -/obj/item/card/id/ert/chaplain - registered_name = "Religious Response Officer" - assignment = "Religious Response Officer" - icon_state = "ert_chaplain" - -/obj/item/card/id/ert/chaplain/Initialize() - access = get_all_accesses()+get_ert_access("sec")-ACCESS_CHANGE_IDS - . = ..() - -/obj/item/card/id/ert/janitor - registered_name = "Janitorial Response Officer" - assignment = "Janitorial Response Officer" - icon_state = "ert_janitor" - -/obj/item/card/id/ert/janitor/Initialize() - access = get_all_accesses() - . = ..() - -/obj/item/card/id/ert/clown - registered_name = "Entertainment Response Officer" - assignment = "Entertainment Response Officer" - icon_state = "ert_clown" - -/obj/item/card/id/ert/clown/Initialize() - access = get_all_accesses() - . = ..() - -/obj/item/card/id/ert/deathsquad - name = "\improper Death Squad ID" - id_type_name = "\improper Death Squad ID" - desc = "A Death Squad ID card." - icon_state = "deathsquad" //NO NO SIR DEATH SQUADS ARENT A PART OF NANOTRASEN AT ALL - registered_name = "Death Commando" - assignment = "Death Commando" - uses_overlays = FALSE - -/obj/item/card/id/debug - name = "\improper Debug ID" - desc = "A debug ID card. Has ALL the all access, you really shouldn't have this." - icon_state = "ert_janitor" - assignment = "Jannie" - uses_overlays = FALSE - -/obj/item/card/id/debug/Initialize() - access = get_all_accesses()+get_all_centcom_access()+get_all_syndicate_access() - registered_account = SSeconomy.get_dep_account(ACCOUNT_CAR) - . = ..() - -/obj/item/card/id/prisoner - name = "prisoner ID card" - id_type_name = "prisoner ID card" - desc = "You are a number, you are not a free man." - icon_state = "orange" - item_state = "orange-id" - lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' - assignment = "Prisoner" - registered_name = "Scum" - uses_overlays = FALSE - var/goal = 0 //How far from freedom? - var/points = 0 - registered_age = null - -/obj/item/card/id/prisoner/attack_self(mob/user) - to_chat(usr, "You have accumulated [points] out of the [goal] points you need for freedom.") - -/obj/item/card/id/prisoner/one - name = "Prisoner #13-001" - registered_name = "Prisoner #13-001" - icon_state = "prisoner_001" - -/obj/item/card/id/prisoner/two - name = "Prisoner #13-002" - registered_name = "Prisoner #13-002" - icon_state = "prisoner_002" - -/obj/item/card/id/prisoner/three - name = "Prisoner #13-003" - registered_name = "Prisoner #13-003" - icon_state = "prisoner_003" - -/obj/item/card/id/prisoner/four - name = "Prisoner #13-004" - registered_name = "Prisoner #13-004" - icon_state = "prisoner_004" - -/obj/item/card/id/prisoner/five - name = "Prisoner #13-005" - registered_name = "Prisoner #13-005" - icon_state = "prisoner_005" - -/obj/item/card/id/prisoner/six - name = "Prisoner #13-006" - registered_name = "Prisoner #13-006" - icon_state = "prisoner_006" - -/obj/item/card/id/prisoner/seven - name = "Prisoner #13-007" - registered_name = "Prisoner #13-007" - icon_state = "prisoner_007" - -/obj/item/card/id/mining - name = "mining ID" - access = list(ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_MECH_MINING, ACCESS_MAILSORTING, ACCESS_MINERAL_STOREROOM) - -/obj/item/card/id/away - name = "\proper a perfectly generic identification card" - desc = "A perfectly generic identification card. Looks like it could use some flavor." - access = list(ACCESS_AWAY_GENERAL) - icon_state = "retro" - uses_overlays = FALSE - registered_age = null - -/obj/item/card/id/away/hotel - name = "Staff ID" - desc = "A staff ID used to access the hotel's doors." - access = list(ACCESS_AWAY_GENERAL, ACCESS_AWAY_MAINT) - -/obj/item/card/id/away/hotel/securty - name = "Officer ID" - access = list(ACCESS_AWAY_GENERAL, ACCESS_AWAY_MAINT, ACCESS_AWAY_SEC) - -/obj/item/card/id/away/old - name = "\proper a perfectly generic identification card" - desc = "A perfectly generic identification card. Looks like it could use some flavor." - -/obj/item/card/id/away/old/sec - name = "Charlie Station Security Officer's ID card" - desc = "A faded Charlie Station ID card. You can make out the rank \"Security Officer\"." - assignment = "Charlie Station Security Officer" - access = list(ACCESS_AWAY_GENERAL, ACCESS_AWAY_SEC) - -/obj/item/card/id/away/old/sci - name = "Charlie Station Scientist's ID card" - desc = "A faded Charlie Station ID card. You can make out the rank \"Scientist\"." - assignment = "Charlie Station Scientist" - access = list(ACCESS_AWAY_GENERAL) - -/obj/item/card/id/away/old/eng - name = "Charlie Station Engineer's ID card" - desc = "A faded Charlie Station ID card. You can make out the rank \"Station Engineer\"." - assignment = "Charlie Station Engineer" - access = list(ACCESS_AWAY_GENERAL, ACCESS_AWAY_ENGINE) - -/obj/item/card/id/away/old/apc - name = "APC Access ID" - desc = "A special ID card that allows access to APC terminals." - access = list(ACCESS_ENGINE_EQUIP) - -/obj/item/card/id/away/deep_storage //deepstorage.dmm space ruin - name = "bunker access ID" - -/obj/item/card/id/departmental_budget - name = "departmental card (FUCK)" - desc = "Provides access to the departmental budget." - icon_state = "budgetcard" - uses_overlays = FALSE - var/department_ID = ACCOUNT_CIV - var/department_name = ACCOUNT_CIV_NAME - registered_age = null - -/obj/item/card/id/departmental_budget/Initialize() - . = ..() - var/datum/bank_account/B = SSeconomy.get_dep_account(department_ID) - if(B) - registered_account = B - if(!B.bank_cards.Find(src)) - B.bank_cards += src - name = "departmental card ([department_name])" - desc = "Provides access to the [department_name]." - SSeconomy.dep_cards += src - -/obj/item/card/id/departmental_budget/Destroy() - SSeconomy.dep_cards -= src - return ..() - -/obj/item/card/id/departmental_budget/update_label() - return - -/obj/item/card/id/departmental_budget/civ - department_ID = ACCOUNT_CIV - department_name = ACCOUNT_CIV_NAME - icon_state = "civ_budget" - -/obj/item/card/id/departmental_budget/eng - department_ID = ACCOUNT_ENG - department_name = ACCOUNT_ENG_NAME - icon_state = "eng_budget" - -/obj/item/card/id/departmental_budget/sci - department_ID = ACCOUNT_SCI - department_name = ACCOUNT_SCI_NAME - icon_state = "sci_budget" - -/obj/item/card/id/departmental_budget/med - department_ID = ACCOUNT_MED - department_name = ACCOUNT_MED_NAME - icon_state = "med_budget" - -/obj/item/card/id/departmental_budget/srv - department_ID = ACCOUNT_SRV - department_name = ACCOUNT_SRV_NAME - icon_state = "srv_budget" - -/obj/item/card/id/departmental_budget/car - department_ID = ACCOUNT_CAR - department_name = ACCOUNT_CAR_NAME - icon_state = "car_budget" //saving up for a new tesla - -/obj/item/card/id/departmental_budget/sec - department_ID = ACCOUNT_SEC - department_name = ACCOUNT_SEC_NAME - icon_state = "sec_budget" +/* Cards + * Contains: + * DATA CARD + * ID CARD + * FINGERPRINT CARD HOLDER + * FINGERPRINT CARD + */ + + + +/* + * DATA CARDS - Used for the IC data card reader + */ + +/obj/item/card + name = "card" + desc = "Does card things." + icon = 'waspstation/icons/obj/card.dmi' //WaspStation Edit - Actually good-looking IDs >:) + w_class = WEIGHT_CLASS_TINY + + var/list/files = list() + +/obj/item/card/suicide_act(mob/living/carbon/user) + user.visible_message("[user] begins to swipe [user.p_their()] neck with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return BRUTELOSS + +/obj/item/card/data + name = "data card" + desc = "A plastic magstripe card for simple and speedy data storage and transfer. This one has a stripe running down the middle." + icon_state = "data_1" + obj_flags = UNIQUE_RENAME + var/function = "storage" + var/data = "null" + var/special = null + item_state = "card-id" + lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' + var/detail_color = COLOR_ASSEMBLY_ORANGE + +/obj/item/card/data/Initialize() + .=..() + update_icon() + +/obj/item/card/data/update_overlays() + . = ..() + if(detail_color == COLOR_FLOORTILE_GRAY) + return + var/mutable_appearance/detail_overlay = mutable_appearance('icons/obj/card.dmi', "[icon_state]-color") + detail_overlay.color = detail_color + . += detail_overlay + +/obj/item/card/data/full_color + desc = "A plastic magstripe card for simple and speedy data storage and transfer. This one has the entire card colored." + icon_state = "data_2" + +/obj/item/card/data/disk + desc = "A plastic magstripe card for simple and speedy data storage and transfer. This one inexplicibly looks like a floppy disk." + icon_state = "data_3" + +/* + * ID CARDS + */ +/obj/item/card/emag + desc = "It's a card with a magnetic strip attached to some circuitry." + name = "cryptographic sequencer" + icon_state = "emag" + item_state = "card-id" + lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' + item_flags = NO_MAT_REDEMPTION | NOBLUDGEON + var/prox_check = TRUE //If the emag requires you to be in range + +/obj/item/card/emag/bluespace + name = "bluespace cryptographic sequencer" + desc = "It's a blue card with a magnetic strip attached to some circuitry. It appears to have some sort of transmitter attached to it." + color = rgb(40, 130, 255) + prox_check = FALSE + +/obj/item/card/emag/attack() + return + +/obj/item/card/emag/afterattack(atom/target, mob/user, proximity) + . = ..() + var/atom/A = target + if(!proximity && prox_check) + return + log_combat(user, A, "attempted to emag") + A.emag_act(user) + +/obj/item/card/emagfake + desc = "It's a card with a magnetic strip attached to some circuitry. Closer inspection shows that this card is a poorly made replica, with a \"DonkCo\" logo stamped on the back." + name = "cryptographic sequencer" + icon_state = "emag" + item_state = "card-id" + lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' + +/obj/item/card/emagfake/afterattack() + . = ..() + playsound(src, 'sound/items/bikehorn.ogg', 50, TRUE) + +/obj/item/card/id + name = "identification card" + desc = "A card used to provide ID and determine access across the station." + icon_state = "id" + item_state = "card-id" + lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' + slot_flags = ITEM_SLOT_ID + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + resistance_flags = FIRE_PROOF | ACID_PROOF + var/id_type_name = "identification card" + var/mining_points = 0 //For redeeming at mining equipment vendors + var/list/access = list() + var/registered_name = null // The name registered_name on the card + var/assignment = null + var/access_txt // mapping aid + var/datum/bank_account/registered_account + var/obj/machinery/paystand/my_store + var/uses_overlays = TRUE + var/icon/cached_flat_icon + var/registered_age = 13 // default age for ss13 players + +/obj/item/card/id/Initialize(mapload) + . = ..() + if(mapload && access_txt) + access = text2access(access_txt) + RegisterSignal(src, COMSIG_ATOM_UPDATED_ICON, .proc/update_in_wallet) + +/obj/item/card/id/Destroy() + if (registered_account) + registered_account.bank_cards -= src + if (my_store && my_store.my_card == src) + my_store.my_card = null + return ..() + +/obj/item/card/id/attack_self(mob/user) + if(Adjacent(user)) + var/minor + if(registered_name && registered_age && registered_age < AGE_MINOR) + minor = " (MINOR)" + user.visible_message("[user] shows you: [icon2html(src, viewers(user))] [src.name][minor].", "You show \the [src.name][minor].") + add_fingerprint(user) + +/obj/item/card/id/vv_edit_var(var_name, var_value) + . = ..() + if(.) + switch(var_name) + if("assignment","registered_name","registered_age") + update_label() + +/obj/item/card/id/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/holochip)) + insert_money(W, user) + return + else if(istype(W, /obj/item/stack/spacecash)) + insert_money(W, user, TRUE) + return + else if(istype(W, /obj/item/coin)) + insert_money(W, user, TRUE) + return + else if(istype(W, /obj/item/storage/bag/money)) + var/obj/item/storage/bag/money/money_bag = W + var/list/money_contained = money_bag.contents + + var/money_added = mass_insert_money(money_contained, user) + + if (money_added) + to_chat(user, "You stuff the contents into the card! They disappear in a puff of bluespace smoke, adding [money_added] worth of credits to the linked account.") + return + else + return ..() + +/obj/item/card/id/proc/insert_money(obj/item/I, mob/user, physical_currency) + var/cash_money = I.get_item_credit_value() + if(!cash_money) + to_chat(user, "[I] doesn't seem to be worth anything!") + return + + if(!registered_account) + to_chat(user, "[src] doesn't have a linked account to deposit [I] into!") + return + + registered_account.adjust_money(cash_money) + SSblackbox.record_feedback("amount", "credits_inserted", cash_money) + log_econ("[cash_money] credits were inserted into [src] owned by [src.registered_name]") + if(physical_currency) + to_chat(user, "You stuff [I] into [src]. It disappears in a small puff of bluespace smoke, adding [cash_money] credits to the linked account.") + else + to_chat(user, "You insert [I] into [src], adding [cash_money] credits to the linked account.") + + to_chat(user, "The linked account now reports a balance of [registered_account.account_balance] cr.") + qdel(I) + +/obj/item/card/id/proc/mass_insert_money(list/money, mob/user) + if (!money || !money.len) + return FALSE + + var/total = 0 + + for (var/obj/item/physical_money in money) + var/cash_money = physical_money.get_item_credit_value() + + total += cash_money + + registered_account.adjust_money(cash_money) + SSblackbox.record_feedback("amount", "credits_inserted", total) + log_econ("[total] credits were inserted into [src] owned by [src.registered_name]") + QDEL_LIST(money) + + return total + +/obj/item/card/id/proc/alt_click_can_use_id(mob/living/user) + if(!isliving(user)) + return + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + + return TRUE + +// Returns true if new account was set. +/obj/item/card/id/proc/set_new_account(mob/living/user) + . = FALSE + var/datum/bank_account/old_account = registered_account + + var/new_bank_id = input(user, "Enter your account ID number.", "Account Reclamation", 111111) as num | null + + if (isnull(new_bank_id)) + return + + if(!alt_click_can_use_id(user)) + return + if(!new_bank_id || new_bank_id < 111111 || new_bank_id > 999999) + to_chat(user, "The account ID number needs to be between 111111 and 999999.") + return + if (registered_account && registered_account.account_id == new_bank_id) + to_chat(user, "The account ID was already assigned to this card.") + return + + for(var/A in SSeconomy.bank_accounts) + var/datum/bank_account/B = A + if(B.account_id == new_bank_id) + if (old_account) + old_account.bank_cards -= src + + B.bank_cards += src + registered_account = B + to_chat(user, "The provided account has been linked to this ID card.") + + return TRUE + + to_chat(user, "The account ID number provided is invalid.") + return + +/obj/item/card/id/AltClick(mob/living/user) + if(!alt_click_can_use_id(user)) + return + + if(!registered_account) + set_new_account(user) + return + + if (world.time < registered_account.withdrawDelay) + registered_account.bank_card_talk("ERROR: UNABLE TO LOGIN DUE TO SCHEDULED MAINTENANCE. MAINTENANCE IS SCHEDULED TO COMPLETE IN [(registered_account.withdrawDelay - world.time)/10] SECONDS.", TRUE) + return + + var/amount_to_remove = FLOOR(input(user, "How much do you want to withdraw? Current Balance: [registered_account.account_balance]", "Withdraw Funds", 5) as num|null, 1) + + if(!amount_to_remove || amount_to_remove < 0) + return + if(!alt_click_can_use_id(user)) + return + if(registered_account.adjust_money(-amount_to_remove)) + var/obj/item/holochip/holochip = new (user.drop_location(), amount_to_remove) + user.put_in_hands(holochip) + to_chat(user, "You withdraw [amount_to_remove] credits into a holochip.") + SSblackbox.record_feedback("amount", "credits_removed", amount_to_remove) + log_econ("[amount_to_remove] credits were removed from [src] owned by [src.registered_name]") + return + else + var/difference = amount_to_remove - registered_account.account_balance + registered_account.bank_card_talk("ERROR: The linked account requires [difference] more credit\s to perform that withdrawal.", TRUE) + +/obj/item/card/id/examine(mob/user) + . = ..() + if(registered_account) + . += "The account linked to the ID belongs to '[registered_account.account_holder]' and reports a balance of [registered_account.account_balance] cr." + . += "There's more information below, you can look again to take a closer look..." + +/obj/item/card/id/examine_more(mob/user) + var/list/msg = list("You examine [src] closer, and note the following...") + + if(registered_age) + msg += "The card indicates that the holder is [registered_age] years old. [(registered_age < AGE_MINOR) ? "There's a holographic stripe that reads 'MINOR: DO NOT SERVE ALCOHOL OR TOBACCO' along the bottom of the card." : ""]" + if(mining_points) + msg += "There's [mining_points] mining equipment redemption point\s loaded onto this card." + if(registered_account) + msg += "The account linked to the ID belongs to '[registered_account.account_holder]' and reports a balance of [registered_account.account_balance] cr." + if(registered_account.account_job) + var/datum/bank_account/D = SSeconomy.get_dep_account(registered_account.account_job.paycheck_department) + if(D) + msg += "The [D.account_holder] reports a balance of [D.account_balance] cr." + msg += "Alt-Click the ID to pull money from the linked account in the form of holochips." + msg += "You can insert credits into the linked account by pressing holochips, cash, or coins against the ID." + if(registered_account.account_holder == user.real_name) + msg += "If you lose this ID card, you can reclaim your account by Alt-Clicking a blank ID card while holding it and entering your account ID number." + else + msg += "There is no registered account linked to this card. Alt-Click to add one." + + return msg + +/obj/item/card/id/GetAccess() + return access + +/obj/item/card/id/GetID() + return src + +/obj/item/card/id/RemoveID() + return src + +/obj/item/card/id/update_overlays() + . = ..() + if(!uses_overlays) + return + cached_flat_icon = null + var/job = assignment ? ckey(GetJobName()) : null + if(registered_name && registered_name != "Captain") + . += mutable_appearance(icon, "assigned") + if(job) + . += mutable_appearance(icon, "id[job]") + +/obj/item/card/id/proc/update_in_wallet() + if(istype(loc, /obj/item/storage/wallet)) + var/obj/item/storage/wallet/powergaming = loc + if(powergaming.front_id == src) + powergaming.update_label() + powergaming.update_icon() + +/obj/item/card/id/proc/get_cached_flat_icon() + if(!cached_flat_icon) + cached_flat_icon = getFlatIcon(src) + return cached_flat_icon + + +/obj/item/card/id/get_examine_string(mob/user, thats = FALSE) + if(uses_overlays) + return "[icon2html(get_cached_flat_icon(), user)] [thats? "That's ":""][get_examine_name(user)]" //displays all overlays in chat + return ..() + +/* +Usage: +update_label() + Sets the id name to whatever registered_name and assignment is +*/ + +/obj/item/card/id/proc/update_label() + var/blank = !registered_name + name = "[blank ? id_type_name : "[registered_name]'s ID Card"][(!assignment) ? "" : " ([assignment])"]" + update_icon() + +/obj/item/card/id/silver + name = "silver identification card" + id_type_name = "silver identification card" + desc = "A silver card which shows honour and dedication." + icon_state = "silver" + item_state = "silver_id" + lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' + +/obj/item/card/id/silver/reaper + name = "Thirteen's ID Card (Reaper)" + access = list(ACCESS_MAINT_TUNNELS) + assignment = "Reaper" + registered_name = "Thirteen" + +/obj/item/card/id/gold + name = "gold identification card" + id_type_name = "gold identification card" + desc = "A golden card which shows power and might." + icon_state = "gold" + item_state = "gold_id" + lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' + +/obj/item/card/id/syndicate + name = "agent card" + access = list(ACCESS_MAINT_TUNNELS, ACCESS_SYNDICATE) + var/anyone = FALSE //Can anyone forge the ID or just syndicate? + var/forged = FALSE //have we set a custom name and job assignment, or will we use what we're given when we chameleon change? + +/obj/item/card/id/syndicate/Initialize() + . = ..() + var/datum/action/item_action/chameleon/change/id/chameleon_action = new(src) + chameleon_action.chameleon_type = /obj/item/card/id + chameleon_action.chameleon_name = "ID Card" + chameleon_action.initialize_disguises() + +/obj/item/card/id/syndicate/afterattack(obj/item/O, mob/user, proximity) + if(!proximity) + return + if(istype(O, /obj/item/card/id)) + var/obj/item/card/id/I = O + src.access |= I.access + if(isliving(user) && user.mind) + if(user.mind.special_role || anyone) + to_chat(usr, "The card's microscanners activate as you pass it over the ID, copying its access.") + +/obj/item/card/id/syndicate/attack_self(mob/user) + if(isliving(user) && user.mind) + var/first_use = registered_name ? FALSE : TRUE + if(!(user.mind.special_role || anyone)) //Unless anyone is allowed, only syndies can use the card, to stop metagaming. + if(first_use) //If a non-syndie is the first to forge an unassigned agent ID, then anyone can forge it. + anyone = TRUE + else + return ..() + + var/popup_input = alert(user, "Choose Action", "Agent ID", "Show", "Forge/Reset", "Change Account ID") + if(user.incapacitated()) + return + if(popup_input == "Forge/Reset" && !forged) + var/input_name = stripped_input(user, "What name would you like to put on this card? Leave blank to randomise.", "Agent card name", registered_name ? registered_name : (ishuman(user) ? user.real_name : user.name), MAX_NAME_LEN) + input_name = reject_bad_name(input_name) + if(!input_name) + // Invalid/blank names give a randomly generated one. + if(user.gender == MALE) + input_name = "[pick(GLOB.first_names_male)] [pick(GLOB.last_names)]" + else if(user.gender == FEMALE) + input_name = "[pick(GLOB.first_names_female)] [pick(GLOB.last_names)]" + else + input_name = "[pick(GLOB.first_names)] [pick(GLOB.last_names)]" + + var/target_occupation = stripped_input(user, "What occupation would you like to put on this card?\nNote: This will not grant any access levels other than Maintenance.", "Agent card job assignment", assignment ? assignment : "Assistant", MAX_MESSAGE_LEN) + if(!target_occupation) + return + + var/newAge = input(user, "Choose the ID's age:\n([AGE_MIN]-[AGE_MAX])", "Agent card age") as num|null + if(newAge) + registered_age = max(round(text2num(newAge)), 0) + + registered_name = input_name + assignment = target_occupation + update_label() + forged = TRUE + to_chat(user, "You successfully forge the ID card.") + log_game("[key_name(user)] has forged \the [initial(name)] with name \"[registered_name]\" and occupation \"[assignment]\".") + + // First time use automatically sets the account id to the user. + if (first_use && !registered_account) + if(ishuman(user)) + var/mob/living/carbon/human/accountowner = user + + for(var/bank_account in SSeconomy.bank_accounts) + var/datum/bank_account/account = bank_account + if(account.account_id == accountowner.account_id) + account.bank_cards += src + registered_account = account + to_chat(user, "Your account number has been automatically assigned.") + return + else if (popup_input == "Forge/Reset" && forged) + registered_name = initial(registered_name) + assignment = initial(assignment) + log_game("[key_name(user)] has reset \the [initial(name)] named \"[src]\" to default.") + update_label() + forged = FALSE + to_chat(user, "You successfully reset the ID card.") + return + else if (popup_input == "Change Account ID") + set_new_account(user) + return + return ..() + +/obj/item/card/id/syndicate/anyone + anyone = TRUE + +/obj/item/card/id/syndicate/nuke_leader + name = "lead agent card" + access = list(ACCESS_MAINT_TUNNELS, ACCESS_SYNDICATE, ACCESS_SYNDICATE_LEADER) + +/obj/item/card/id/syndicate_command + name = "syndicate ID card" + id_type_name = "syndicate ID card" + desc = "An ID straight from the Syndicate." + registered_name = "Syndicate" + assignment = "Syndicate Overlord" + icon_state = "syndie" + access = list(ACCESS_SYNDICATE) + uses_overlays = FALSE + registered_age = null + +/obj/item/card/id/syndicate_command/crew_id + name = "syndicate ID card" + id_type_name = "syndicate ID card" + desc = "An ID straight from the Syndicate." + registered_name = "Syndicate" + assignment = "Syndicate Operative" + icon_state = "syndie" + access = list(ACCESS_SYNDICATE, ACCESS_ROBOTICS) + uses_overlays = FALSE + +/obj/item/card/id/syndicate_command/captain_id + name = "syndicate captain ID card" + id_type_name = "syndicate captain ID card" + desc = "An ID straight from the Syndicate." + registered_name = "Syndicate" + assignment = "Syndicate Ship Captain" + icon_state = "syndie" + access = list(ACCESS_SYNDICATE, ACCESS_ROBOTICS) + uses_overlays = FALSE + +/obj/item/card/id/syndicate_command/crew_id + name = "syndicate ID card" + id_type_name = "syndicate ID card" + desc = "An ID straight from the Syndicate." + registered_name = "Syndicate" + assignment = "Syndicate Operative" + icon_state = "syndie" + access = list(ACCESS_SYNDICATE) + uses_overlays = FALSE + +/obj/item/card/id/syndicate_command/captain_id + name = "syndicate captain ID card" + id_type_name = "syndicate captain ID card" + desc = "An ID straight from the Syndicate." + registered_name = "Syndicate" + assignment = "Syndicate Ship Captain" + icon_state = "syndie" + access = list(ACCESS_SYNDICATE) + uses_overlays = FALSE + +/obj/item/card/id/captains_spare + name = "captain's spare ID" + id_type_name = "captain's spare ID" + desc = "The spare ID of the High Lord himself." + icon_state = "gold" + item_state = "gold_id" + lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' + registered_name = "Captain" + assignment = "Captain" + registered_age = null + +/obj/item/card/id/captains_spare/Initialize() + var/datum/job/captain/J = new/datum/job/captain + access = J.get_access() + . = ..() + update_label() + +/obj/item/card/id/captains_spare/update_label() //so it doesn't change to Captain's ID card (Captain) on a sneeze + if(registered_name == "Captain") + name = "[id_type_name][(!assignment || assignment == "Captain") ? "" : " ([assignment])"]" + update_icon() + else + ..() + +/obj/item/card/id/centcom + name = "\improper CentCom ID" + id_type_name = "\improper CentCom ID" + desc = "An ID straight from Central Command." + icon_state = "centcom" + registered_name = "Central Command" + assignment = "Central Command" + uses_overlays = FALSE + registered_age = null + +/obj/item/card/id/centcom/Initialize() + access = get_all_centcom_access() + . = ..() + +/obj/item/card/id/ert + name = "\improper CentCom ID" + id_type_name = "\improper CentCom ID" + desc = "An ERT ID card." + icon_state = "ert_commander" + registered_name = "Emergency Response Team Commander" + assignment = "Emergency Response Team Commander" + uses_overlays = FALSE + registered_age = null + +/obj/item/card/id/ert/Initialize() + access = get_all_accesses()+get_ert_access("commander")-ACCESS_CHANGE_IDS + . = ..() + +/obj/item/card/id/ert/security + registered_name = "Security Response Officer" + assignment = "Security Response Officer" + icon_state = "ert_security" + +/obj/item/card/id/ert/security/Initialize() + access = get_all_accesses()+get_ert_access("sec")-ACCESS_CHANGE_IDS + . = ..() + +/obj/item/card/id/ert/engineer + registered_name = "Engineering Response Officer" + assignment = "Engineering Response Officer" + icon_state = "ert_engineer" + +/obj/item/card/id/ert/engineer/Initialize() + access = get_all_accesses()+get_ert_access("eng")-ACCESS_CHANGE_IDS + . = ..() + +/obj/item/card/id/ert/medical + registered_name = "Medical Response Officer" + assignment = "Medical Response Officer" + icon_state = "ert_medic" + +/obj/item/card/id/ert/medical/Initialize() + access = get_all_accesses()+get_ert_access("med")-ACCESS_CHANGE_IDS + . = ..() + +/obj/item/card/id/ert/chaplain + registered_name = "Religious Response Officer" + assignment = "Religious Response Officer" + icon_state = "ert_chaplain" + +/obj/item/card/id/ert/chaplain/Initialize() + access = get_all_accesses()+get_ert_access("sec")-ACCESS_CHANGE_IDS + . = ..() + +/obj/item/card/id/ert/janitor + registered_name = "Janitorial Response Officer" + assignment = "Janitorial Response Officer" + icon_state = "ert_janitor" + +/obj/item/card/id/ert/janitor/Initialize() + access = get_all_accesses() + . = ..() + +/obj/item/card/id/ert/clown + registered_name = "Entertainment Response Officer" + assignment = "Entertainment Response Officer" + icon_state = "ert_clown" + +/obj/item/card/id/ert/clown/Initialize() + access = get_all_accesses() + . = ..() + +/obj/item/card/id/ert/deathsquad + name = "\improper Death Squad ID" + id_type_name = "\improper Death Squad ID" + desc = "A Death Squad ID card." + icon_state = "deathsquad" //NO NO SIR DEATH SQUADS ARENT A PART OF NANOTRASEN AT ALL + registered_name = "Death Commando" + assignment = "Death Commando" + uses_overlays = FALSE + +/obj/item/card/id/debug + name = "\improper Debug ID" + desc = "A debug ID card. Has ALL the all access, you really shouldn't have this." + icon_state = "ert_janitor" + assignment = "Jannie" + uses_overlays = FALSE + +/obj/item/card/id/debug/Initialize() + access = get_all_accesses()+get_all_centcom_access()+get_all_syndicate_access() + registered_account = SSeconomy.get_dep_account(ACCOUNT_CAR) + . = ..() + +/obj/item/card/id/prisoner + name = "prisoner ID card" + id_type_name = "prisoner ID card" + desc = "You are a number, you are not a free man." + icon_state = "orange" + item_state = "orange-id" + lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' + assignment = "Prisoner" + registered_name = "Scum" + uses_overlays = FALSE + var/goal = 0 //How far from freedom? + var/points = 0 + registered_age = null + +/obj/item/card/id/prisoner/attack_self(mob/user) + to_chat(usr, "You have accumulated [points] out of the [goal] points you need for freedom.") + +/obj/item/card/id/prisoner/one + name = "Prisoner #13-001" + registered_name = "Prisoner #13-001" + icon_state = "prisoner_001" + +/obj/item/card/id/prisoner/two + name = "Prisoner #13-002" + registered_name = "Prisoner #13-002" + icon_state = "prisoner_002" + +/obj/item/card/id/prisoner/three + name = "Prisoner #13-003" + registered_name = "Prisoner #13-003" + icon_state = "prisoner_003" + +/obj/item/card/id/prisoner/four + name = "Prisoner #13-004" + registered_name = "Prisoner #13-004" + icon_state = "prisoner_004" + +/obj/item/card/id/prisoner/five + name = "Prisoner #13-005" + registered_name = "Prisoner #13-005" + icon_state = "prisoner_005" + +/obj/item/card/id/prisoner/six + name = "Prisoner #13-006" + registered_name = "Prisoner #13-006" + icon_state = "prisoner_006" + +/obj/item/card/id/prisoner/seven + name = "Prisoner #13-007" + registered_name = "Prisoner #13-007" + icon_state = "prisoner_007" + +/obj/item/card/id/mining + name = "mining ID" + access = list(ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_MECH_MINING, ACCESS_MAILSORTING, ACCESS_MINERAL_STOREROOM) + +/obj/item/card/id/away + name = "\proper a perfectly generic identification card" + desc = "A perfectly generic identification card. Looks like it could use some flavor." + access = list(ACCESS_AWAY_GENERAL) + icon_state = "retro" + uses_overlays = FALSE + registered_age = null + +/obj/item/card/id/away/hotel + name = "Staff ID" + desc = "A staff ID used to access the hotel's doors." + access = list(ACCESS_AWAY_GENERAL, ACCESS_AWAY_MAINT) + +/obj/item/card/id/away/hotel/securty + name = "Officer ID" + access = list(ACCESS_AWAY_GENERAL, ACCESS_AWAY_MAINT, ACCESS_AWAY_SEC) + +/obj/item/card/id/away/old + name = "\proper a perfectly generic identification card" + desc = "A perfectly generic identification card. Looks like it could use some flavor." + +/obj/item/card/id/away/old/sec + name = "Charlie Station Security Officer's ID card" + desc = "A faded Charlie Station ID card. You can make out the rank \"Security Officer\"." + assignment = "Charlie Station Security Officer" + access = list(ACCESS_AWAY_GENERAL, ACCESS_AWAY_SEC) + +/obj/item/card/id/away/old/sci + name = "Charlie Station Scientist's ID card" + desc = "A faded Charlie Station ID card. You can make out the rank \"Scientist\"." + assignment = "Charlie Station Scientist" + access = list(ACCESS_AWAY_GENERAL) + +/obj/item/card/id/away/old/eng + name = "Charlie Station Engineer's ID card" + desc = "A faded Charlie Station ID card. You can make out the rank \"Station Engineer\"." + assignment = "Charlie Station Engineer" + access = list(ACCESS_AWAY_GENERAL, ACCESS_AWAY_ENGINE) + +/obj/item/card/id/away/old/apc + name = "APC Access ID" + desc = "A special ID card that allows access to APC terminals." + access = list(ACCESS_ENGINE_EQUIP) + +/obj/item/card/id/away/deep_storage //deepstorage.dmm space ruin + name = "bunker access ID" + +/obj/item/card/id/departmental_budget + name = "departmental card (FUCK)" + desc = "Provides access to the departmental budget." + icon_state = "budgetcard" + uses_overlays = FALSE + var/department_ID = ACCOUNT_CIV + var/department_name = ACCOUNT_CIV_NAME + registered_age = null + +/obj/item/card/id/departmental_budget/Initialize() + . = ..() + var/datum/bank_account/B = SSeconomy.get_dep_account(department_ID) + if(B) + registered_account = B + if(!B.bank_cards.Find(src)) + B.bank_cards += src + name = "departmental card ([department_name])" + desc = "Provides access to the [department_name]." + SSeconomy.dep_cards += src + +/obj/item/card/id/departmental_budget/Destroy() + SSeconomy.dep_cards -= src + return ..() + +/obj/item/card/id/departmental_budget/update_label() + return + +/obj/item/card/id/departmental_budget/civ + department_ID = ACCOUNT_CIV + department_name = ACCOUNT_CIV_NAME + icon_state = "civ_budget" + +/obj/item/card/id/departmental_budget/eng + department_ID = ACCOUNT_ENG + department_name = ACCOUNT_ENG_NAME + icon_state = "eng_budget" + +/obj/item/card/id/departmental_budget/sci + department_ID = ACCOUNT_SCI + department_name = ACCOUNT_SCI_NAME + icon_state = "sci_budget" + +/obj/item/card/id/departmental_budget/med + department_ID = ACCOUNT_MED + department_name = ACCOUNT_MED_NAME + icon_state = "med_budget" + +/obj/item/card/id/departmental_budget/srv + department_ID = ACCOUNT_SRV + department_name = ACCOUNT_SRV_NAME + icon_state = "srv_budget" + +/obj/item/card/id/departmental_budget/car + department_ID = ACCOUNT_CAR + department_name = ACCOUNT_CAR_NAME + icon_state = "car_budget" //saving up for a new tesla + +/obj/item/card/id/departmental_budget/sec + department_ID = ACCOUNT_SEC + department_name = ACCOUNT_SEC_NAME + icon_state = "sec_budget" diff --git a/code/game/objects/items/cigs_lighters.dm b/code/game/objects/items/cigs_lighters.dm index 934009e181bf..e89871493981 100644 --- a/code/game/objects/items/cigs_lighters.dm +++ b/code/game/objects/items/cigs_lighters.dm @@ -1,959 +1,959 @@ -//cleansed 9/15/2012 17:48 - -/* -CONTAINS: -MATCHES -CIGARETTES -CIGARS -SMOKING PIPES -CHEAP LIGHTERS -ZIPPO - -CIGARETTE PACKETS ARE IN FANCY.DM -*/ - -/////////// -//MATCHES// -/////////// -/obj/item/match - name = "match" - desc = "A simple match stick, used for lighting fine smokables." - icon = 'icons/obj/cigarettes.dmi' - icon_state = "match_unlit" - var/lit = FALSE - var/burnt = FALSE - var/smoketime = 5 // 10 seconds - w_class = WEIGHT_CLASS_TINY - heat = 1000 - grind_results = list(/datum/reagent/phosphorus = 2) - -/obj/item/match/process() - smoketime-- - if(smoketime < 1) - matchburnout() - else - open_flame(heat) - -/obj/item/match/fire_act(exposed_temperature, exposed_volume) - matchignite() - -/obj/item/match/proc/matchignite() - if(!lit && !burnt) - playsound(src, "sound/items/match_strike.ogg", 15, TRUE) - lit = TRUE - icon_state = "match_lit" - damtype = "fire" - force = 3 - hitsound = 'sound/items/welder.ogg' - item_state = "cigon" - name = "lit [initial(name)]" - desc = "A [initial(name)]. This one is lit." - attack_verb = list("burnt","singed") - START_PROCESSING(SSobj, src) - update_icon() - -/obj/item/match/proc/matchburnout() - if(lit) - lit = FALSE - burnt = TRUE - damtype = "brute" - force = initial(force) - icon_state = "match_burnt" - item_state = "cigoff" - name = "burnt [initial(name)]" - desc = "A [initial(name)]. This one has seen better days." - attack_verb = list("flicked") - STOP_PROCESSING(SSobj, src) - -/obj/item/match/extinguish() - matchburnout() - -/obj/item/match/dropped(mob/user) - matchburnout() - . = ..() - -/obj/item/match/attack(mob/living/carbon/M, mob/living/carbon/user) - if(!isliving(M)) - return - if(lit && M.IgniteMob()) - message_admins("[ADMIN_LOOKUPFLW(user)] set [key_name_admin(M)] on fire with [src] at [AREACOORD(user)]") - log_game("[key_name(user)] set [key_name(M)] on fire with [src] at [AREACOORD(user)]") - var/obj/item/clothing/mask/cigarette/cig = help_light_cig(M) - if(lit && cig && user.a_intent == INTENT_HELP) - if(cig.lit) - to_chat(user, "[cig] is already lit!") - if(M == user) - cig.attackby(src, user) - else - cig.light("[user] holds [src] out for [M], and lights [cig].") - else - ..() - -/obj/item/proc/help_light_cig(mob/living/M) - var/mask_item = M.get_item_by_slot(ITEM_SLOT_MASK) - if(istype(mask_item, /obj/item/clothing/mask/cigarette)) - return mask_item - -/obj/item/match/get_temperature() - return lit * heat - -/obj/item/match/firebrand - name = "firebrand" - desc = "An unlit firebrand. It makes you wonder why it's not just called a stick." - smoketime = 20 //40 seconds - custom_materials = list(/datum/material/wood = MINERAL_MATERIAL_AMOUNT) - grind_results = list(/datum/reagent/carbon = 2) - -/obj/item/match/firebrand/Initialize() - . = ..() - matchignite() - -////////////////// -//FINE SMOKABLES// -////////////////// -/obj/item/clothing/mask/cigarette - name = "cigarette" - desc = "A roll of tobacco and nicotine." - icon_state = "cigoff" - throw_speed = 0.5 - item_state = "cigoff" - w_class = WEIGHT_CLASS_TINY - body_parts_covered = null - grind_results = list() - heat = 1000 - var/dragtime = 100 - var/nextdragtime = 0 - var/lit = FALSE - var/starts_lit = FALSE - var/icon_on = "cigon" //Note - these are in masks.dmi not in cigarette.dmi - var/icon_off = "cigoff" - var/type_butt = /obj/item/cigbutt - var/lastHolder = null - var/smoketime = 180 // 1 is 2 seconds, so a single cigarette will last 6 minutes. - var/chem_volume = 30 - var/smoke_all = FALSE /// Should we smoke all of the chems in the cig before it runs out. Splits each puff to take a portion of the overall chems so by the end you'll always have consumed all of the chems inside. - var/list/list_reagents = list(/datum/reagent/drug/nicotine = 15) - var/lung_harm = 1 //How bad it is for you - -/obj/item/clothing/mask/cigarette/suicide_act(mob/user) - user.visible_message("[user] is huffing [src] as quickly as [user.p_they()] can! It looks like [user.p_theyre()] trying to give [user.p_them()]self cancer.") - return (TOXLOSS|OXYLOSS) - -/obj/item/clothing/mask/cigarette/Initialize() - . = ..() - create_reagents(chem_volume, INJECTABLE | NO_REACT) - if(list_reagents) - reagents.add_reagent_list(list_reagents) - if(starts_lit) - light() - AddComponent(/datum/component/knockoff,90,list(BODY_ZONE_PRECISE_MOUTH),list(ITEM_SLOT_MASK))//90% to knock off when wearing a mask - -/obj/item/clothing/mask/cigarette/Destroy() - STOP_PROCESSING(SSobj, src) - . = ..() - -/obj/item/clothing/mask/cigarette/attackby(obj/item/W, mob/user, params) - if(!lit && smoketime > 0) - var/lighting_text = W.ignition_effect(src, user) - if(lighting_text) - light(lighting_text) - else - return ..() - -/obj/item/clothing/mask/cigarette/afterattack(obj/item/reagent_containers/glass/glass, mob/user, proximity) - . = ..() - if(!proximity || lit) //can't dip if cigarette is lit (it will heat the reagents in the glass instead) - return - if(istype(glass)) //you can dip cigarettes into beakers - if(glass.reagents.trans_to(src, chem_volume, transfered_by = user)) //if reagents were transfered, show the message - to_chat(user, "You dip \the [src] into \the [glass].") - else //if not, either the beaker was empty, or the cigarette was full - if(!glass.reagents.total_volume) - to_chat(user, "[glass] is empty!") - else - to_chat(user, "[src] is full!") - - -/obj/item/clothing/mask/cigarette/proc/light(flavor_text = null) - if(lit) - return - if(!(flags_1 & INITIALIZED_1)) - icon_state = icon_on - item_state = icon_on - return - - lit = TRUE - name = "lit [name]" - attack_verb = list("burnt", "singed") - hitsound = 'sound/items/welder.ogg' - damtype = "fire" - force = 4 - if(reagents.get_reagent_amount(/datum/reagent/toxin/plasma)) // the plasma explodes when exposed to fire - var/datum/effect_system/reagents_explosion/e = new() - e.set_up(round(reagents.get_reagent_amount(/datum/reagent/toxin/plasma) / 2.5, 1), get_turf(src), 0, 0) - e.start() - qdel(src) - return - if(reagents.get_reagent_amount(/datum/reagent/fuel)) // the fuel explodes, too, but much less violently - var/datum/effect_system/reagents_explosion/e = new() - e.set_up(round(reagents.get_reagent_amount(/datum/reagent/fuel) / 5, 1), get_turf(src), 0, 0) - e.start() - qdel(src) - return - // allowing reagents to react after being lit - reagents.flags &= ~(NO_REACT) - reagents.handle_reactions() - icon_state = icon_on - item_state = icon_on - if(flavor_text) - var/turf/T = get_turf(src) - T.visible_message(flavor_text) - START_PROCESSING(SSobj, src) - - //can't think of any other way to update the overlays :< - if(ismob(loc)) - var/mob/M = loc - M.update_inv_wear_mask() - M.update_inv_hands() - - playsound(src, 'sound/items/cig_light.ogg', 25, 1) - -/obj/item/clothing/mask/cigarette/extinguish() - if(!lit) - return - name = copytext_char(name, 5) //5 == length_char("lit ") + 1 - attack_verb = null - hitsound = null - damtype = BRUTE - force = 0 - icon_state = icon_off - item_state = icon_off - STOP_PROCESSING(SSobj, src) - reagents.flags |= NO_REACT - lit = FALSE - if(ismob(loc)) - var/mob/living/M = loc - to_chat(M, "Your [name] goes out.") - M.update_inv_wear_mask() - M.update_inv_hands() - -/obj/item/clothing/mask/cigarette/proc/handle_reagents() - if(reagents.total_volume) - var/to_smoke = REAGENTS_METABOLISM - if(iscarbon(loc)) - var/mob/living/carbon/C = loc - if (src == C.wear_mask) // if it's in the human/monkey mouth, transfer reagents to the mob - var/fraction = min(REAGENTS_METABOLISM/reagents.total_volume, 1) - /* - * Given the amount of time the cig will last, and how often we take a hit, find the number - * of chems to give them each time so they'll have smoked it all by the end. - */ - if (smoke_all) - to_smoke = reagents.total_volume/((smoketime * 2) / (dragtime / 10)) - - reagents.expose(C, INGEST, fraction) - var/obj/item/organ/lungs/L = C.getorganslot(ORGAN_SLOT_LUNGS) - if(L && !(L.organ_flags & ORGAN_SYNTHETIC)) - C.adjustOrganLoss(ORGAN_SLOT_LUNGS, lung_harm) - if(!reagents.trans_to(C, to_smoke)) - reagents.remove_any(to_smoke) - return - reagents.remove_any(to_smoke) - -/obj/item/clothing/mask/cigarette/process() - var/turf/location = get_turf(src) - var/mob/living/M = loc - if(isliving(loc)) - M.IgniteMob() - smoketime-- - if(smoketime < 1) - new type_butt(location) - if(ismob(loc)) - to_chat(M, "Your [name] goes out.") - playsound(src, 'sound/items/cig_snuff.ogg', 25, 1) - qdel(src) - return - open_flame() - if((reagents && reagents.total_volume) && (nextdragtime <= world.time)) - nextdragtime = world.time + dragtime - handle_reagents() - -/obj/item/clothing/mask/cigarette/attack_self(mob/user) - if(lit) - user.visible_message("[user] calmly drops and treads on \the [src], putting it out instantly.") - playsound(src, 'sound/items/cig_snuff.ogg', 25, 1) - new type_butt(user.loc) - new /obj/effect/decal/cleanable/ash(user.loc) - qdel(src) - . = ..() - -/obj/item/clothing/mask/cigarette/attack(mob/living/carbon/M, mob/living/carbon/user) - if(!istype(M)) - return ..() - if(M.on_fire && !lit) - light("[user] lights [src] with [M]'s burning body. What a cold-blooded badass.") - return - var/obj/item/clothing/mask/cigarette/cig = help_light_cig(M) - if(lit && cig && user.a_intent == INTENT_HELP) - if(cig.lit) - to_chat(user, "The [cig.name] is already lit!") - if(M == user) - cig.attackby(src, user) - else - cig.light("[user] holds the [name] out for [M], and lights [M.p_their()] [cig.name].") - else - return ..() - -/obj/item/clothing/mask/cigarette/fire_act(exposed_temperature, exposed_volume) - light() - -/obj/item/clothing/mask/cigarette/get_temperature() - return lit * heat - -// Cigarette brands. - -/obj/item/clothing/mask/cigarette/space_cigarette - desc = "A Space Cigarette brand cigarette." - -/obj/item/clothing/mask/cigarette/dromedary - desc = "A DromedaryCo brand cigarette. Contrary to popular belief, does not contain Calomel, but is reported to have a watery taste." - list_reagents = list(/datum/reagent/drug/nicotine = 13, /datum/reagent/water = 5) //camel has water - -/obj/item/clothing/mask/cigarette/uplift - desc = "An Uplift Smooth brand cigarette. Smells refreshing." - list_reagents = list(/datum/reagent/drug/nicotine = 13, /datum/reagent/consumable/menthol = 5) - -/obj/item/clothing/mask/cigarette/robust - desc = "A Robust brand cigarette." - -/obj/item/clothing/mask/cigarette/robustgold - desc = "A Robust Gold brand cigarette." - list_reagents = list(/datum/reagent/drug/nicotine = 15, /datum/reagent/gold = 3) // Just enough to taste a hint of expensive metal. - -/obj/item/clothing/mask/cigarette/carp - desc = "A Carp Classic brand cigarette. A small label on its side indicates that it does NOT contain carpotoxin." - -/obj/item/clothing/mask/cigarette/carp/Initialize() - . = ..() - if(!prob(5)) - return - reagents?.add_reagent(/datum/reagent/toxin/carpotoxin , 3) // They lied - -/obj/item/clothing/mask/cigarette/syndicate - desc = "An unknown brand cigarette." - chem_volume = 60 - smoketime = 60 - smoke_all = TRUE - list_reagents = list(/datum/reagent/drug/nicotine = 10, /datum/reagent/medicine/omnizine = 15) - -/obj/item/clothing/mask/cigarette/shadyjims - desc = "A Shady Jim's Super Slims cigarette." - list_reagents = list(/datum/reagent/drug/nicotine = 15, /datum/reagent/toxin/lipolicide = 4, /datum/reagent/ammonia = 2, /datum/reagent/toxin/plantbgone = 1, /datum/reagent/toxin = 1.5) - -/obj/item/clothing/mask/cigarette/xeno - desc = "A Xeno Filtered brand cigarette." - list_reagents = list (/datum/reagent/drug/nicotine = 20, /datum/reagent/medicine/regen_jelly = 15, /datum/reagent/drug/krokodil = 4) - -// Rollies. - -/obj/item/clothing/mask/cigarette/rollie - name = "rollie" - desc = "A roll of dried plant matter wrapped in thin paper." - icon_state = "spliffoff" - icon_on = "spliffon" - icon_off = "spliffoff" - type_butt = /obj/item/cigbutt/roach - throw_speed = 0.5 - item_state = "spliffoff" - smoketime = 120 // four minutes - chem_volume = 50 - list_reagents = null - -/obj/item/clothing/mask/cigarette/rollie/Initialize() - . = ..() - pixel_x = rand(-5, 5) - pixel_y = rand(-5, 5) - -/obj/item/clothing/mask/cigarette/rollie/nicotine - list_reagents = list(/datum/reagent/drug/nicotine = 15) - -/obj/item/clothing/mask/cigarette/rollie/trippy - list_reagents = list(/datum/reagent/drug/nicotine = 15, /datum/reagent/drug/mushroomhallucinogen = 35) - starts_lit = TRUE - -/obj/item/clothing/mask/cigarette/rollie/cannabis - list_reagents = list(/datum/reagent/drug/space_drugs = 15, /datum/reagent/toxin/lipolicide = 35) - -/obj/item/clothing/mask/cigarette/rollie/mindbreaker - list_reagents = list(/datum/reagent/toxin/mindbreaker = 35, /datum/reagent/toxin/lipolicide = 15) - -/obj/item/clothing/mask/cigarette/candy - name = "Little Timmy's candy cigarette" - desc = "For all ages*! Doesn't contain any amount of nicotine. Health and safety risks can be read on the tip of the cigarette." - smoketime = 120 - icon_on = "candyon" - icon_off = "candyoff" //make sure to add positional sprites in icons/obj/cigarettes.dmi if you add more. - item_state = "candyoff" - icon_state = "candyoff" - type_butt = /obj/item/reagent_containers/food/snacks/candy_trash - list_reagents = list(/datum/reagent/consumable/sugar = 10, /datum/reagent/consumable/caramel = 10) - -/obj/item/clothing/mask/cigarette/candy/nicotine - desc = "For all ages*! Doesn't contain any* amount of nicotine. Health and safety risks can be read on the tip of the cigarette." - type_butt = /obj/item/reagent_containers/food/snacks/candy_trash/nicotine - list_reagents = list(/datum/reagent/consumable/sugar = 10, /datum/reagent/consumable/caramel = 10, /datum/reagent/drug/nicotine = 20) //oh no! - smoke_all = TRUE //timmy's not getting out of this one - -/obj/item/cigbutt/roach - name = "roach" - desc = "A manky old roach, or for non-stoners, a used rollup." - icon_state = "roach" - -/obj/item/cigbutt/roach/Initialize() - . = ..() - pixel_x = rand(-5, 5) - pixel_y = rand(-5, 5) - - -//////////// -// CIGARS // -//////////// -/obj/item/clothing/mask/cigarette/cigar - name = "premium cigar" - desc = "A brown roll of tobacco and... well, you're not quite sure. This thing's huge!" - icon_state = "cigaroff" - icon_on = "cigaron" - icon_off = "cigaroff" //make sure to add positional sprites in icons/obj/cigarettes.dmi if you add more. - type_butt = /obj/item/cigbutt/cigarbutt - throw_speed = 0.5 - item_state = "cigaroff" - smoketime = 300 // 11 minutes - chem_volume = 40 - list_reagents = list(/datum/reagent/drug/nicotine = 25) - -/obj/item/clothing/mask/cigarette/cigar/cohiba - name = "\improper Cohiba Robusto cigar" - desc = "There's little more you could want from a cigar." - icon_state = "cigar2off" - icon_on = "cigar2on" - icon_off = "cigar2off" - smoketime = 600 // 20 minutes - chem_volume = 80 - list_reagents =list(/datum/reagent/drug/nicotine = 40) - -/obj/item/clothing/mask/cigarette/cigar/havana - name = "premium Havanian cigar" - desc = "A cigar fit for only the best of the best." - icon_state = "cigar2off" - icon_on = "cigar2on" - icon_off = "cigar2off" - smoketime = 900 // 30 minutes - chem_volume = 50 - list_reagents =list(/datum/reagent/drug/nicotine = 15) - -/obj/item/cigbutt - name = "cigarette butt" - desc = "A manky old cigarette butt." - icon = 'icons/obj/clothing/masks.dmi' - icon_state = "cigbutt" - w_class = WEIGHT_CLASS_TINY - throwforce = 0 - grind_results = list(/datum/reagent/carbon = 2) - -/obj/item/cigbutt/cigarbutt - name = "cigar butt" - desc = "A manky old cigar butt." - icon_state = "cigarbutt" - -///////////////// -//SMOKING PIPES// -///////////////// -/obj/item/clothing/mask/cigarette/pipe - name = "smoking pipe" - desc = "A pipe, for smoking. Probably made of meerschaum or something." - icon_state = "pipeoff" - item_state = "pipeoff" - icon_on = "pipeon" //Note - these are in masks.dmi - icon_off = "pipeoff" - smoketime = 0 - chem_volume = 100 - list_reagents = null - var/packeditem = 0 - -/obj/item/clothing/mask/cigarette/pipe/Initialize() - . = ..() - name = "empty [initial(name)]" - -/obj/item/clothing/mask/cigarette/pipe/Destroy() - STOP_PROCESSING(SSobj, src) - . = ..() - -/obj/item/clothing/mask/cigarette/pipe/process() - var/turf/location = get_turf(src) - smoketime-- - if(smoketime < 1) - new /obj/effect/decal/cleanable/ash(location) - if(ismob(loc)) - var/mob/living/M = loc - to_chat(M, "Your [name] goes out.") - lit = 0 - icon_state = icon_off - item_state = icon_off - M.update_inv_wear_mask() - packeditem = 0 - name = "empty [initial(name)]" - STOP_PROCESSING(SSobj, src) - return - open_flame() - if(reagents && reagents.total_volume) // check if it has any reagents at all - handle_reagents() - - -/obj/item/clothing/mask/cigarette/pipe/attackby(obj/item/O, mob/user, params) - if(istype(O, /obj/item/reagent_containers/food/snacks/grown)) - var/obj/item/reagent_containers/food/snacks/grown/G = O - if(!packeditem) - if(G.dry == 1) - to_chat(user, "You stuff [O] into [src].") - smoketime = 400 - packeditem = 1 - name = "[O.name]-packed [initial(name)]" - if(O.reagents) - O.reagents.trans_to(src, O.reagents.total_volume, transfered_by = user) - qdel(O) - else - to_chat(user, "It has to be dried first!") - else - to_chat(user, "It is already packed!") - else - var/lighting_text = O.ignition_effect(src,user) - if(lighting_text) - if(smoketime > 0) - light(lighting_text) - else - to_chat(user, "There is nothing to smoke!") - else - return ..() - -/obj/item/clothing/mask/cigarette/pipe/attack_self(mob/user) - var/turf/location = get_turf(user) - if(lit) - user.visible_message("[user] puts out [src].", "You put out [src].") - lit = 0 - icon_state = icon_off - item_state = icon_off - STOP_PROCESSING(SSobj, src) - return - if(!lit && smoketime > 0) - to_chat(user, "You empty [src] onto [location].") - new /obj/effect/decal/cleanable/ash(location) - packeditem = 0 - smoketime = 0 - reagents.clear_reagents() - name = "empty [initial(name)]" - return - -/obj/item/clothing/mask/cigarette/pipe/cobpipe - name = "corn cob pipe" - desc = "A nicotine delivery system popularized by folksy backwoodsmen and kept popular in the modern age and beyond by space hipsters. Can be loaded with objects." - icon_state = "cobpipeoff" - item_state = "cobpipeoff" - icon_on = "cobpipeon" //Note - these are in masks.dmi - icon_off = "cobpipeoff" - smoketime = 0 - - -///////// -//ZIPPO// -///////// -/obj/item/lighter - name = "\improper Zippo lighter" - desc = "The zippo." - icon = 'icons/obj/cigarettes.dmi' - icon_state = "zippo" - item_state = "zippo" - w_class = WEIGHT_CLASS_TINY - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BELT - var/lit = 0 - var/fancy = TRUE - var/overlay_state - var/overlay_list = list( - "plain", - "dame", - "thirteen", - "snake" - ) - heat = 1500 - resistance_flags = FIRE_PROOF - light_color = LIGHT_COLOR_FIRE - grind_results = list(/datum/reagent/iron = 1, /datum/reagent/fuel = 5, /datum/reagent/fuel/oil = 5) - custom_price = 55 - -/obj/item/lighter/Initialize() - . = ..() - if(!overlay_state) - overlay_state = pick(overlay_list) - update_icon() - -/obj/item/lighter/cyborg_unequip(mob/user) - if(!lit) - return - set_lit(FALSE) - -/obj/item/lighter/suicide_act(mob/living/carbon/user) - if (lit) - user.visible_message("[user] begins holding \the [src]'s flame up to [user.p_their()] face! It looks like [user.p_theyre()] trying to commit suicide!") - playsound(src, 'sound/items/welder.ogg', 50, TRUE) - return FIRELOSS - else - user.visible_message("[user] begins whacking [user.p_them()]self with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return BRUTELOSS - -/obj/item/lighter/update_overlays() - . = ..() - . += create_lighter_overlay() - -/obj/item/lighter/update_icon_state() - icon_state = "[initial(icon_state)][lit ? "-on" : ""]" - -/obj/item/lighter/proc/create_lighter_overlay() - return mutable_appearance(icon, "lighter_overlay_[overlay_state][lit ? "-on" : ""]") - -/obj/item/lighter/ignition_effect(atom/A, mob/user) - if(get_temperature()) - . = "With a single flick of [user.p_their()] wrist, [user] smoothly lights [A] with [src]. Damn [user.p_theyre()] cool." - -/obj/item/lighter/proc/set_lit(new_lit) - lit = new_lit - if(lit) - force = 5 - damtype = "fire" - hitsound = 'sound/items/welder.ogg' - attack_verb = list("burnt", "singed") - set_light(2, 0.6, LIGHT_COLOR_FIRE) - START_PROCESSING(SSobj, src) - else - hitsound = "swing_hit" - force = 0 - attack_verb = null //human_defense.dm takes care of it - set_light(0) - STOP_PROCESSING(SSobj, src) - update_icon() - -/obj/item/lighter/extinguish() - set_lit(FALSE) - -/obj/item/lighter/attack_self(mob/living/user) - if(user.is_holding(src)) - if(!lit) - set_lit(TRUE) - if(fancy) - user.visible_message("Without even breaking stride, [user] flips open and lights [src] in one smooth movement.", "Without even breaking stride, you flip open and light [src] in one smooth movement.") - playsound(src.loc, 'sound/items/zippo_on.ogg', 100, 1) - else - var/prot = FALSE - var/mob/living/carbon/human/H = user - - if(istype(H) && H.gloves) - var/obj/item/clothing/gloves/G = H.gloves - if(G.max_heat_protection_temperature) - prot = (G.max_heat_protection_temperature > 360) - else - prot = TRUE - - if(prot || prob(75)) - user.visible_message("After a few attempts, [user] manages to light [src].", "After a few attempts, you manage to light [src].") - else - var/hitzone = user.held_index_to_dir(user.active_hand_index) == "r" ? BODY_ZONE_PRECISE_R_HAND : BODY_ZONE_PRECISE_L_HAND - user.apply_damage(5, BURN, hitzone) - user.visible_message("After a few attempts, [user] manages to light [src] - however, [user.p_they()] burn [user.p_their()] finger in the process.", "You burn yourself while lighting the lighter!") - SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "burnt_thumb", /datum/mood_event/burnt_thumb) - playsound(src.loc, 'sound/items/lighter_on.ogg', 100, 1) - - else - set_lit(FALSE) - if(fancy) - user.visible_message("You hear a quiet click, as [user] shuts off [src] without even looking at what [user.p_theyre()] doing. Wow.", "You quietly shut off [src] without even looking at what you're doing. Wow.") - playsound(src.loc, 'sound/items/zippo_off.ogg', 100, 1) - else - user.visible_message("[user] quietly shuts off [src].", "You quietly shut off [src].") - playsound(src.loc, 'sound/items/lighter_off.ogg', 100, 1) - else - . = ..() - -/obj/item/lighter/attack(mob/living/carbon/M, mob/living/carbon/user) - if(lit && M.IgniteMob()) - message_admins("[ADMIN_LOOKUPFLW(user)] set [key_name_admin(M)] on fire with [src] at [AREACOORD(user)]") - log_game("[key_name(user)] set [key_name(M)] on fire with [src] at [AREACOORD(user)]") - var/obj/item/clothing/mask/cigarette/cig = help_light_cig(M) - if(lit && cig && user.a_intent == INTENT_HELP) - if(cig.lit) - to_chat(user, "The [cig.name] is already lit!") - if(M == user) - cig.attackby(src, user) - else - if(fancy) - cig.light("[user] whips the [name] out and holds it for [M]. [user.p_their(TRUE)] arm is as steady as the unflickering flame [user.p_they()] light[user.p_s()] \the [cig] with.") - else - cig.light("[user] holds the [name] out for [M], and lights [M.p_their()] [cig.name].") - else - ..() - -/obj/item/lighter/process() - open_flame() - -/obj/item/lighter/get_temperature() - return lit * heat - - -/obj/item/lighter/greyscale - name = "cheap lighter" - desc = "A cheap lighter." - icon_state = "lighter" - fancy = FALSE - overlay_list = list( - "transp", - "tall", - "matte", - "zoppo" //u cant stoppo th zoppo - ) - var/lighter_color - var/list/color_list = list( //Same 16 color selection as electronic assemblies - COLOR_ASSEMBLY_BLACK, - COLOR_FLOORTILE_GRAY, - COLOR_ASSEMBLY_BGRAY, - COLOR_ASSEMBLY_WHITE, - COLOR_ASSEMBLY_RED, - COLOR_ASSEMBLY_ORANGE, - COLOR_ASSEMBLY_BEIGE, - COLOR_ASSEMBLY_BROWN, - COLOR_ASSEMBLY_GOLD, - COLOR_ASSEMBLY_YELLOW, - COLOR_ASSEMBLY_GURKHA, - COLOR_ASSEMBLY_LGREEN, - COLOR_ASSEMBLY_GREEN, - COLOR_ASSEMBLY_LBLUE, - COLOR_ASSEMBLY_BLUE, - COLOR_ASSEMBLY_PURPLE - ) - -/obj/item/lighter/greyscale/Initialize() - . = ..() - if(!lighter_color) - lighter_color = pick(color_list) - update_icon() - -/obj/item/lighter/greyscale/create_lighter_overlay() - var/mutable_appearance/lighter_overlay = ..() - lighter_overlay.color = lighter_color - return lighter_overlay - -/obj/item/lighter/greyscale/ignition_effect(atom/A, mob/user) - if(get_temperature()) - . = "After some fiddling, [user] manages to light [A] with [src]." - - -/obj/item/lighter/slime - name = "slime zippo" - desc = "A specialty zippo made from slimes and industry. Has a much hotter flame than normal." - icon_state = "slighter" - heat = 3000 //Blue flame! - light_color = LIGHT_COLOR_CYAN - overlay_state = "slime" - grind_results = list(/datum/reagent/iron = 1, /datum/reagent/fuel = 5, /datum/reagent/medicine/pyroxadone = 5) - - -/////////// -//ROLLING// -/////////// -/obj/item/rollingpaper - name = "rolling paper" - desc = "A thin piece of paper used to make fine smokeables." - icon = 'icons/obj/cigarettes.dmi' - icon_state = "cig_paper" - w_class = WEIGHT_CLASS_TINY - -/obj/item/rollingpaper/afterattack(atom/target, mob/user, proximity) - . = ..() - if(!proximity) - return - if(istype(target, /obj/item/reagent_containers/food/snacks/grown)) - var/obj/item/reagent_containers/food/snacks/grown/O = target - if(O.dry) - var/obj/item/clothing/mask/cigarette/rollie/R = new /obj/item/clothing/mask/cigarette/rollie(user.loc) - R.chem_volume = target.reagents.total_volume - target.reagents.trans_to(R, R.chem_volume, transfered_by = user) - qdel(target) - qdel(src) - user.put_in_active_hand(R) - to_chat(user, "You roll the [target.name] into a rolling paper.") - R.desc = "Dried [target.name] rolled up in a thin piece of paper." - else - to_chat(user, "You need to dry this first!") - -/////////////// -//VAPE NATION// -/////////////// -/obj/item/clothing/mask/vape - name = "\improper E-Cigarette" - desc = "A classy and highly sophisticated electronic cigarette, for classy and dignified gentlemen. A warning label reads \"Warning: Do not fill with flammable materials.\""//<<< i'd vape to that. - icon = 'icons/obj/clothing/masks.dmi' - icon_state = "red_vape" - item_state = null - w_class = WEIGHT_CLASS_TINY - var/chem_volume = 100 - var/vapetime = 0 //this so it won't puff out clouds every tick - var/screw = 0 // kinky - var/super = 0 //for the fattest vapes dude. - -/obj/item/clothing/mask/vape/suicide_act(mob/user) - user.visible_message("[user] is puffin hard on dat vape, [user.p_they()] trying to join the vape life on a whole notha plane!")//it doesn't give you cancer, it is cancer - return (TOXLOSS|OXYLOSS) - - -/obj/item/clothing/mask/vape/Initialize(mapload, param_color) - . = ..() - create_reagents(chem_volume, NO_REACT) - reagents.add_reagent(/datum/reagent/drug/nicotine, 50) - if(!param_color) - param_color = pick("red","blue","black","white","green","purple","yellow","orange") - icon_state = "[param_color]_vape" - item_state = "[param_color]_vape" - -/obj/item/clothing/mask/vape/attackby(obj/item/O, mob/user, params) - if(O.tool_behaviour == TOOL_SCREWDRIVER) - if(!screw) - screw = TRUE - to_chat(user, "You open the cap on [src].") - reagents.flags |= OPENCONTAINER - if(obj_flags & EMAGGED) - add_overlay("vapeopen_high") - else if(super) - add_overlay("vapeopen_med") - else - add_overlay("vapeopen_low") - else - screw = FALSE - to_chat(user, "You close the cap on [src].") - reagents.flags &= ~(OPENCONTAINER) - cut_overlays() - - if(O.tool_behaviour == TOOL_MULTITOOL) - if(screw && !(obj_flags & EMAGGED))//also kinky - if(!super) - cut_overlays() - super = 1 - to_chat(user, "You increase the voltage of [src].") - add_overlay("vapeopen_med") - else - cut_overlays() - super = 0 - to_chat(user, "You decrease the voltage of [src].") - add_overlay("vapeopen_low") - - if(screw && (obj_flags & EMAGGED)) - to_chat(user, "[src] can't be modified!") - else - ..() - - -/obj/item/clothing/mask/vape/emag_act(mob/user)// I WON'T REGRET WRITTING THIS, SURLY. - if(screw) - if(!(obj_flags & EMAGGED)) - cut_overlays() - obj_flags |= EMAGGED - super = 0 - to_chat(user, "You maximize the voltage of [src].") - add_overlay("vapeopen_high") - var/datum/effect_system/spark_spread/sp = new /datum/effect_system/spark_spread //for effect - sp.set_up(5, 1, src) - sp.start() - else - to_chat(user, "[src] is already emagged!") - else - to_chat(user, "You need to open the cap to do that!") - -/obj/item/clothing/mask/vape/attack_self(mob/user) - if(reagents.total_volume > 0) - to_chat(user, "You empty [src] of all reagents.") - reagents.clear_reagents() - -/obj/item/clothing/mask/vape/equipped(mob/user, slot) - . = ..() - if(slot == ITEM_SLOT_MASK) - if(!screw) - to_chat(user, "You start puffing on the vape.") - reagents.flags &= ~(NO_REACT) - START_PROCESSING(SSobj, src) - else //it will not start if the vape is opened. - to_chat(user, "You need to close the cap first!") - -/obj/item/clothing/mask/vape/dropped(mob/user) - . = ..() - if(user.get_item_by_slot(ITEM_SLOT_MASK) == src) - reagents.flags |= NO_REACT - STOP_PROCESSING(SSobj, src) - -/obj/item/clothing/mask/vape/proc/hand_reagents()//had to rename to avoid duplicate error - if(reagents.total_volume) - if(iscarbon(loc)) - var/mob/living/carbon/C = loc - if (src == C.wear_mask) // if it's in the human/monkey mouth, transfer reagents to the mob - var/fraction = min(REAGENTS_METABOLISM/reagents.total_volume, 1) //this will react instantly, making them a little more dangerous than cigarettes - reagents.expose(C, INGEST, fraction) - if(!reagents.trans_to(C, REAGENTS_METABOLISM)) - reagents.remove_any(REAGENTS_METABOLISM) - if(reagents.get_reagent_amount(/datum/reagent/fuel)) - //HOT STUFF - C.adjust_fire_stacks(2) - C.IgniteMob() - - if(reagents.get_reagent_amount(/datum/reagent/toxin/plasma)) // the plasma explodes when exposed to fire - var/datum/effect_system/reagents_explosion/e = new() - e.set_up(round(reagents.get_reagent_amount(/datum/reagent/toxin/plasma) / 2.5, 1), get_turf(src), 0, 0) - e.start() - qdel(src) - return - reagents.remove_any(REAGENTS_METABOLISM) - -/obj/item/clothing/mask/vape/process() - var/mob/living/M = loc - - if(isliving(loc)) - M.IgniteMob() - - vapetime++ - - if(!reagents.total_volume) - if(ismob(loc)) - to_chat(M, "[src] is empty!") - STOP_PROCESSING(SSobj, src) - //it's reusable so it won't unequip when empty - return - //open flame removed because vapes are a closed system, they wont light anything on fire - - if(super && vapetime > 3)//Time to start puffing those fat vapes, yo. - var/datum/effect_system/smoke_spread/chem/smoke_machine/s = new - s.set_up(reagents, 1, 24, loc) - s.start() - vapetime = 0 - - if((obj_flags & EMAGGED) && vapetime > 3) - var/datum/effect_system/smoke_spread/chem/smoke_machine/s = new - s.set_up(reagents, 4, 24, loc) - s.start() - vapetime = 0 - if(prob(5))//small chance for the vape to break and deal damage if it's emagged - playsound(get_turf(src), 'sound/effects/pop_expl.ogg', 50, FALSE) - M.apply_damage(20, BURN, BODY_ZONE_HEAD) - M.Paralyze(300, 1, 0) - var/datum/effect_system/spark_spread/sp = new /datum/effect_system/spark_spread - sp.set_up(5, 1, src) - sp.start() - to_chat(M, "[src] suddenly explodes in your mouth!") - qdel(src) - return - - if(reagents && reagents.total_volume) - hand_reagents() +//cleansed 9/15/2012 17:48 + +/* +CONTAINS: +MATCHES +CIGARETTES +CIGARS +SMOKING PIPES +CHEAP LIGHTERS +ZIPPO + +CIGARETTE PACKETS ARE IN FANCY.DM +*/ + +/////////// +//MATCHES// +/////////// +/obj/item/match + name = "match" + desc = "A simple match stick, used for lighting fine smokables." + icon = 'icons/obj/cigarettes.dmi' + icon_state = "match_unlit" + var/lit = FALSE + var/burnt = FALSE + var/smoketime = 5 // 10 seconds + w_class = WEIGHT_CLASS_TINY + heat = 1000 + grind_results = list(/datum/reagent/phosphorus = 2) + +/obj/item/match/process() + smoketime-- + if(smoketime < 1) + matchburnout() + else + open_flame(heat) + +/obj/item/match/fire_act(exposed_temperature, exposed_volume) + matchignite() + +/obj/item/match/proc/matchignite() + if(!lit && !burnt) + playsound(src, "sound/items/match_strike.ogg", 15, TRUE) + lit = TRUE + icon_state = "match_lit" + damtype = "fire" + force = 3 + hitsound = 'sound/items/welder.ogg' + item_state = "cigon" + name = "lit [initial(name)]" + desc = "A [initial(name)]. This one is lit." + attack_verb = list("burnt","singed") + START_PROCESSING(SSobj, src) + update_icon() + +/obj/item/match/proc/matchburnout() + if(lit) + lit = FALSE + burnt = TRUE + damtype = "brute" + force = initial(force) + icon_state = "match_burnt" + item_state = "cigoff" + name = "burnt [initial(name)]" + desc = "A [initial(name)]. This one has seen better days." + attack_verb = list("flicked") + STOP_PROCESSING(SSobj, src) + +/obj/item/match/extinguish() + matchburnout() + +/obj/item/match/dropped(mob/user) + matchburnout() + . = ..() + +/obj/item/match/attack(mob/living/carbon/M, mob/living/carbon/user) + if(!isliving(M)) + return + if(lit && M.IgniteMob()) + message_admins("[ADMIN_LOOKUPFLW(user)] set [key_name_admin(M)] on fire with [src] at [AREACOORD(user)]") + log_game("[key_name(user)] set [key_name(M)] on fire with [src] at [AREACOORD(user)]") + var/obj/item/clothing/mask/cigarette/cig = help_light_cig(M) + if(lit && cig && user.a_intent == INTENT_HELP) + if(cig.lit) + to_chat(user, "[cig] is already lit!") + if(M == user) + cig.attackby(src, user) + else + cig.light("[user] holds [src] out for [M], and lights [cig].") + else + ..() + +/obj/item/proc/help_light_cig(mob/living/M) + var/mask_item = M.get_item_by_slot(ITEM_SLOT_MASK) + if(istype(mask_item, /obj/item/clothing/mask/cigarette)) + return mask_item + +/obj/item/match/get_temperature() + return lit * heat + +/obj/item/match/firebrand + name = "firebrand" + desc = "An unlit firebrand. It makes you wonder why it's not just called a stick." + smoketime = 20 //40 seconds + custom_materials = list(/datum/material/wood = MINERAL_MATERIAL_AMOUNT) + grind_results = list(/datum/reagent/carbon = 2) + +/obj/item/match/firebrand/Initialize() + . = ..() + matchignite() + +////////////////// +//FINE SMOKABLES// +////////////////// +/obj/item/clothing/mask/cigarette + name = "cigarette" + desc = "A roll of tobacco and nicotine." + icon_state = "cigoff" + throw_speed = 0.5 + item_state = "cigoff" + w_class = WEIGHT_CLASS_TINY + body_parts_covered = null + grind_results = list() + heat = 1000 + var/dragtime = 100 + var/nextdragtime = 0 + var/lit = FALSE + var/starts_lit = FALSE + var/icon_on = "cigon" //Note - these are in masks.dmi not in cigarette.dmi + var/icon_off = "cigoff" + var/type_butt = /obj/item/cigbutt + var/lastHolder = null + var/smoketime = 180 // 1 is 2 seconds, so a single cigarette will last 6 minutes. + var/chem_volume = 30 + var/smoke_all = FALSE /// Should we smoke all of the chems in the cig before it runs out. Splits each puff to take a portion of the overall chems so by the end you'll always have consumed all of the chems inside. + var/list/list_reagents = list(/datum/reagent/drug/nicotine = 15) + var/lung_harm = 1 //How bad it is for you + +/obj/item/clothing/mask/cigarette/suicide_act(mob/user) + user.visible_message("[user] is huffing [src] as quickly as [user.p_they()] can! It looks like [user.p_theyre()] trying to give [user.p_them()]self cancer.") + return (TOXLOSS|OXYLOSS) + +/obj/item/clothing/mask/cigarette/Initialize() + . = ..() + create_reagents(chem_volume, INJECTABLE | NO_REACT) + if(list_reagents) + reagents.add_reagent_list(list_reagents) + if(starts_lit) + light() + AddComponent(/datum/component/knockoff,90,list(BODY_ZONE_PRECISE_MOUTH),list(ITEM_SLOT_MASK))//90% to knock off when wearing a mask + +/obj/item/clothing/mask/cigarette/Destroy() + STOP_PROCESSING(SSobj, src) + . = ..() + +/obj/item/clothing/mask/cigarette/attackby(obj/item/W, mob/user, params) + if(!lit && smoketime > 0) + var/lighting_text = W.ignition_effect(src, user) + if(lighting_text) + light(lighting_text) + else + return ..() + +/obj/item/clothing/mask/cigarette/afterattack(obj/item/reagent_containers/glass/glass, mob/user, proximity) + . = ..() + if(!proximity || lit) //can't dip if cigarette is lit (it will heat the reagents in the glass instead) + return + if(istype(glass)) //you can dip cigarettes into beakers + if(glass.reagents.trans_to(src, chem_volume, transfered_by = user)) //if reagents were transfered, show the message + to_chat(user, "You dip \the [src] into \the [glass].") + else //if not, either the beaker was empty, or the cigarette was full + if(!glass.reagents.total_volume) + to_chat(user, "[glass] is empty!") + else + to_chat(user, "[src] is full!") + + +/obj/item/clothing/mask/cigarette/proc/light(flavor_text = null) + if(lit) + return + if(!(flags_1 & INITIALIZED_1)) + icon_state = icon_on + item_state = icon_on + return + + lit = TRUE + name = "lit [name]" + attack_verb = list("burnt", "singed") + hitsound = 'sound/items/welder.ogg' + damtype = "fire" + force = 4 + if(reagents.get_reagent_amount(/datum/reagent/toxin/plasma)) // the plasma explodes when exposed to fire + var/datum/effect_system/reagents_explosion/e = new() + e.set_up(round(reagents.get_reagent_amount(/datum/reagent/toxin/plasma) / 2.5, 1), get_turf(src), 0, 0) + e.start() + qdel(src) + return + if(reagents.get_reagent_amount(/datum/reagent/fuel)) // the fuel explodes, too, but much less violently + var/datum/effect_system/reagents_explosion/e = new() + e.set_up(round(reagents.get_reagent_amount(/datum/reagent/fuel) / 5, 1), get_turf(src), 0, 0) + e.start() + qdel(src) + return + // allowing reagents to react after being lit + reagents.flags &= ~(NO_REACT) + reagents.handle_reactions() + icon_state = icon_on + item_state = icon_on + if(flavor_text) + var/turf/T = get_turf(src) + T.visible_message(flavor_text) + START_PROCESSING(SSobj, src) + + //can't think of any other way to update the overlays :< + if(ismob(loc)) + var/mob/M = loc + M.update_inv_wear_mask() + M.update_inv_hands() + + playsound(src, 'sound/items/cig_light.ogg', 25, 1) + +/obj/item/clothing/mask/cigarette/extinguish() + if(!lit) + return + name = copytext_char(name, 5) //5 == length_char("lit ") + 1 + attack_verb = null + hitsound = null + damtype = BRUTE + force = 0 + icon_state = icon_off + item_state = icon_off + STOP_PROCESSING(SSobj, src) + reagents.flags |= NO_REACT + lit = FALSE + if(ismob(loc)) + var/mob/living/M = loc + to_chat(M, "Your [name] goes out.") + M.update_inv_wear_mask() + M.update_inv_hands() + +/obj/item/clothing/mask/cigarette/proc/handle_reagents() + if(reagents.total_volume) + var/to_smoke = REAGENTS_METABOLISM + if(iscarbon(loc)) + var/mob/living/carbon/C = loc + if (src == C.wear_mask) // if it's in the human/monkey mouth, transfer reagents to the mob + var/fraction = min(REAGENTS_METABOLISM/reagents.total_volume, 1) + /* + * Given the amount of time the cig will last, and how often we take a hit, find the number + * of chems to give them each time so they'll have smoked it all by the end. + */ + if (smoke_all) + to_smoke = reagents.total_volume/((smoketime * 2) / (dragtime / 10)) + + reagents.expose(C, INGEST, fraction) + var/obj/item/organ/lungs/L = C.getorganslot(ORGAN_SLOT_LUNGS) + if(L && !(L.organ_flags & ORGAN_SYNTHETIC)) + C.adjustOrganLoss(ORGAN_SLOT_LUNGS, lung_harm) + if(!reagents.trans_to(C, to_smoke)) + reagents.remove_any(to_smoke) + return + reagents.remove_any(to_smoke) + +/obj/item/clothing/mask/cigarette/process() + var/turf/location = get_turf(src) + var/mob/living/M = loc + if(isliving(loc)) + M.IgniteMob() + smoketime-- + if(smoketime < 1) + new type_butt(location) + if(ismob(loc)) + to_chat(M, "Your [name] goes out.") + playsound(src, 'sound/items/cig_snuff.ogg', 25, 1) + qdel(src) + return + open_flame() + if((reagents && reagents.total_volume) && (nextdragtime <= world.time)) + nextdragtime = world.time + dragtime + handle_reagents() + +/obj/item/clothing/mask/cigarette/attack_self(mob/user) + if(lit) + user.visible_message("[user] calmly drops and treads on \the [src], putting it out instantly.") + playsound(src, 'sound/items/cig_snuff.ogg', 25, 1) + new type_butt(user.loc) + new /obj/effect/decal/cleanable/ash(user.loc) + qdel(src) + . = ..() + +/obj/item/clothing/mask/cigarette/attack(mob/living/carbon/M, mob/living/carbon/user) + if(!istype(M)) + return ..() + if(M.on_fire && !lit) + light("[user] lights [src] with [M]'s burning body. What a cold-blooded badass.") + return + var/obj/item/clothing/mask/cigarette/cig = help_light_cig(M) + if(lit && cig && user.a_intent == INTENT_HELP) + if(cig.lit) + to_chat(user, "The [cig.name] is already lit!") + if(M == user) + cig.attackby(src, user) + else + cig.light("[user] holds the [name] out for [M], and lights [M.p_their()] [cig.name].") + else + return ..() + +/obj/item/clothing/mask/cigarette/fire_act(exposed_temperature, exposed_volume) + light() + +/obj/item/clothing/mask/cigarette/get_temperature() + return lit * heat + +// Cigarette brands. + +/obj/item/clothing/mask/cigarette/space_cigarette + desc = "A Space Cigarette brand cigarette." + +/obj/item/clothing/mask/cigarette/dromedary + desc = "A DromedaryCo brand cigarette. Contrary to popular belief, does not contain Calomel, but is reported to have a watery taste." + list_reagents = list(/datum/reagent/drug/nicotine = 13, /datum/reagent/water = 5) //camel has water + +/obj/item/clothing/mask/cigarette/uplift + desc = "An Uplift Smooth brand cigarette. Smells refreshing." + list_reagents = list(/datum/reagent/drug/nicotine = 13, /datum/reagent/consumable/menthol = 5) + +/obj/item/clothing/mask/cigarette/robust + desc = "A Robust brand cigarette." + +/obj/item/clothing/mask/cigarette/robustgold + desc = "A Robust Gold brand cigarette." + list_reagents = list(/datum/reagent/drug/nicotine = 15, /datum/reagent/gold = 3) // Just enough to taste a hint of expensive metal. + +/obj/item/clothing/mask/cigarette/carp + desc = "A Carp Classic brand cigarette. A small label on its side indicates that it does NOT contain carpotoxin." + +/obj/item/clothing/mask/cigarette/carp/Initialize() + . = ..() + if(!prob(5)) + return + reagents?.add_reagent(/datum/reagent/toxin/carpotoxin , 3) // They lied + +/obj/item/clothing/mask/cigarette/syndicate + desc = "An unknown brand cigarette." + chem_volume = 60 + smoketime = 60 + smoke_all = TRUE + list_reagents = list(/datum/reagent/drug/nicotine = 10, /datum/reagent/medicine/omnizine = 15) + +/obj/item/clothing/mask/cigarette/shadyjims + desc = "A Shady Jim's Super Slims cigarette." + list_reagents = list(/datum/reagent/drug/nicotine = 15, /datum/reagent/toxin/lipolicide = 4, /datum/reagent/ammonia = 2, /datum/reagent/toxin/plantbgone = 1, /datum/reagent/toxin = 1.5) + +/obj/item/clothing/mask/cigarette/xeno + desc = "A Xeno Filtered brand cigarette." + list_reagents = list (/datum/reagent/drug/nicotine = 20, /datum/reagent/medicine/regen_jelly = 15, /datum/reagent/drug/krokodil = 4) + +// Rollies. + +/obj/item/clothing/mask/cigarette/rollie + name = "rollie" + desc = "A roll of dried plant matter wrapped in thin paper." + icon_state = "spliffoff" + icon_on = "spliffon" + icon_off = "spliffoff" + type_butt = /obj/item/cigbutt/roach + throw_speed = 0.5 + item_state = "spliffoff" + smoketime = 120 // four minutes + chem_volume = 50 + list_reagents = null + +/obj/item/clothing/mask/cigarette/rollie/Initialize() + . = ..() + pixel_x = rand(-5, 5) + pixel_y = rand(-5, 5) + +/obj/item/clothing/mask/cigarette/rollie/nicotine + list_reagents = list(/datum/reagent/drug/nicotine = 15) + +/obj/item/clothing/mask/cigarette/rollie/trippy + list_reagents = list(/datum/reagent/drug/nicotine = 15, /datum/reagent/drug/mushroomhallucinogen = 35) + starts_lit = TRUE + +/obj/item/clothing/mask/cigarette/rollie/cannabis + list_reagents = list(/datum/reagent/drug/space_drugs = 15, /datum/reagent/toxin/lipolicide = 35) + +/obj/item/clothing/mask/cigarette/rollie/mindbreaker + list_reagents = list(/datum/reagent/toxin/mindbreaker = 35, /datum/reagent/toxin/lipolicide = 15) + +/obj/item/clothing/mask/cigarette/candy + name = "Little Timmy's candy cigarette" + desc = "For all ages*! Doesn't contain any amount of nicotine. Health and safety risks can be read on the tip of the cigarette." + smoketime = 120 + icon_on = "candyon" + icon_off = "candyoff" //make sure to add positional sprites in icons/obj/cigarettes.dmi if you add more. + item_state = "candyoff" + icon_state = "candyoff" + type_butt = /obj/item/reagent_containers/food/snacks/candy_trash + list_reagents = list(/datum/reagent/consumable/sugar = 10, /datum/reagent/consumable/caramel = 10) + +/obj/item/clothing/mask/cigarette/candy/nicotine + desc = "For all ages*! Doesn't contain any* amount of nicotine. Health and safety risks can be read on the tip of the cigarette." + type_butt = /obj/item/reagent_containers/food/snacks/candy_trash/nicotine + list_reagents = list(/datum/reagent/consumable/sugar = 10, /datum/reagent/consumable/caramel = 10, /datum/reagent/drug/nicotine = 20) //oh no! + smoke_all = TRUE //timmy's not getting out of this one + +/obj/item/cigbutt/roach + name = "roach" + desc = "A manky old roach, or for non-stoners, a used rollup." + icon_state = "roach" + +/obj/item/cigbutt/roach/Initialize() + . = ..() + pixel_x = rand(-5, 5) + pixel_y = rand(-5, 5) + + +//////////// +// CIGARS // +//////////// +/obj/item/clothing/mask/cigarette/cigar + name = "premium cigar" + desc = "A brown roll of tobacco and... well, you're not quite sure. This thing's huge!" + icon_state = "cigaroff" + icon_on = "cigaron" + icon_off = "cigaroff" //make sure to add positional sprites in icons/obj/cigarettes.dmi if you add more. + type_butt = /obj/item/cigbutt/cigarbutt + throw_speed = 0.5 + item_state = "cigaroff" + smoketime = 300 // 11 minutes + chem_volume = 40 + list_reagents = list(/datum/reagent/drug/nicotine = 25) + +/obj/item/clothing/mask/cigarette/cigar/cohiba + name = "\improper Cohiba Robusto cigar" + desc = "There's little more you could want from a cigar." + icon_state = "cigar2off" + icon_on = "cigar2on" + icon_off = "cigar2off" + smoketime = 600 // 20 minutes + chem_volume = 80 + list_reagents =list(/datum/reagent/drug/nicotine = 40) + +/obj/item/clothing/mask/cigarette/cigar/havana + name = "premium Havanian cigar" + desc = "A cigar fit for only the best of the best." + icon_state = "cigar2off" + icon_on = "cigar2on" + icon_off = "cigar2off" + smoketime = 900 // 30 minutes + chem_volume = 50 + list_reagents =list(/datum/reagent/drug/nicotine = 15) + +/obj/item/cigbutt + name = "cigarette butt" + desc = "A manky old cigarette butt." + icon = 'icons/obj/clothing/masks.dmi' + icon_state = "cigbutt" + w_class = WEIGHT_CLASS_TINY + throwforce = 0 + grind_results = list(/datum/reagent/carbon = 2) + +/obj/item/cigbutt/cigarbutt + name = "cigar butt" + desc = "A manky old cigar butt." + icon_state = "cigarbutt" + +///////////////// +//SMOKING PIPES// +///////////////// +/obj/item/clothing/mask/cigarette/pipe + name = "smoking pipe" + desc = "A pipe, for smoking. Probably made of meerschaum or something." + icon_state = "pipeoff" + item_state = "pipeoff" + icon_on = "pipeon" //Note - these are in masks.dmi + icon_off = "pipeoff" + smoketime = 0 + chem_volume = 100 + list_reagents = null + var/packeditem = 0 + +/obj/item/clothing/mask/cigarette/pipe/Initialize() + . = ..() + name = "empty [initial(name)]" + +/obj/item/clothing/mask/cigarette/pipe/Destroy() + STOP_PROCESSING(SSobj, src) + . = ..() + +/obj/item/clothing/mask/cigarette/pipe/process() + var/turf/location = get_turf(src) + smoketime-- + if(smoketime < 1) + new /obj/effect/decal/cleanable/ash(location) + if(ismob(loc)) + var/mob/living/M = loc + to_chat(M, "Your [name] goes out.") + lit = 0 + icon_state = icon_off + item_state = icon_off + M.update_inv_wear_mask() + packeditem = 0 + name = "empty [initial(name)]" + STOP_PROCESSING(SSobj, src) + return + open_flame() + if(reagents && reagents.total_volume) // check if it has any reagents at all + handle_reagents() + + +/obj/item/clothing/mask/cigarette/pipe/attackby(obj/item/O, mob/user, params) + if(istype(O, /obj/item/reagent_containers/food/snacks/grown)) + var/obj/item/reagent_containers/food/snacks/grown/G = O + if(!packeditem) + if(G.dry == 1) + to_chat(user, "You stuff [O] into [src].") + smoketime = 400 + packeditem = 1 + name = "[O.name]-packed [initial(name)]" + if(O.reagents) + O.reagents.trans_to(src, O.reagents.total_volume, transfered_by = user) + qdel(O) + else + to_chat(user, "It has to be dried first!") + else + to_chat(user, "It is already packed!") + else + var/lighting_text = O.ignition_effect(src,user) + if(lighting_text) + if(smoketime > 0) + light(lighting_text) + else + to_chat(user, "There is nothing to smoke!") + else + return ..() + +/obj/item/clothing/mask/cigarette/pipe/attack_self(mob/user) + var/turf/location = get_turf(user) + if(lit) + user.visible_message("[user] puts out [src].", "You put out [src].") + lit = 0 + icon_state = icon_off + item_state = icon_off + STOP_PROCESSING(SSobj, src) + return + if(!lit && smoketime > 0) + to_chat(user, "You empty [src] onto [location].") + new /obj/effect/decal/cleanable/ash(location) + packeditem = 0 + smoketime = 0 + reagents.clear_reagents() + name = "empty [initial(name)]" + return + +/obj/item/clothing/mask/cigarette/pipe/cobpipe + name = "corn cob pipe" + desc = "A nicotine delivery system popularized by folksy backwoodsmen and kept popular in the modern age and beyond by space hipsters. Can be loaded with objects." + icon_state = "cobpipeoff" + item_state = "cobpipeoff" + icon_on = "cobpipeon" //Note - these are in masks.dmi + icon_off = "cobpipeoff" + smoketime = 0 + + +///////// +//ZIPPO// +///////// +/obj/item/lighter + name = "\improper Zippo lighter" + desc = "The zippo." + icon = 'icons/obj/cigarettes.dmi' + icon_state = "zippo" + item_state = "zippo" + w_class = WEIGHT_CLASS_TINY + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BELT + var/lit = 0 + var/fancy = TRUE + var/overlay_state + var/overlay_list = list( + "plain", + "dame", + "thirteen", + "snake" + ) + heat = 1500 + resistance_flags = FIRE_PROOF + light_color = LIGHT_COLOR_FIRE + grind_results = list(/datum/reagent/iron = 1, /datum/reagent/fuel = 5, /datum/reagent/fuel/oil = 5) + custom_price = 55 + +/obj/item/lighter/Initialize() + . = ..() + if(!overlay_state) + overlay_state = pick(overlay_list) + update_icon() + +/obj/item/lighter/cyborg_unequip(mob/user) + if(!lit) + return + set_lit(FALSE) + +/obj/item/lighter/suicide_act(mob/living/carbon/user) + if (lit) + user.visible_message("[user] begins holding \the [src]'s flame up to [user.p_their()] face! It looks like [user.p_theyre()] trying to commit suicide!") + playsound(src, 'sound/items/welder.ogg', 50, TRUE) + return FIRELOSS + else + user.visible_message("[user] begins whacking [user.p_them()]self with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return BRUTELOSS + +/obj/item/lighter/update_overlays() + . = ..() + . += create_lighter_overlay() + +/obj/item/lighter/update_icon_state() + icon_state = "[initial(icon_state)][lit ? "-on" : ""]" + +/obj/item/lighter/proc/create_lighter_overlay() + return mutable_appearance(icon, "lighter_overlay_[overlay_state][lit ? "-on" : ""]") + +/obj/item/lighter/ignition_effect(atom/A, mob/user) + if(get_temperature()) + . = "With a single flick of [user.p_their()] wrist, [user] smoothly lights [A] with [src]. Damn [user.p_theyre()] cool." + +/obj/item/lighter/proc/set_lit(new_lit) + lit = new_lit + if(lit) + force = 5 + damtype = "fire" + hitsound = 'sound/items/welder.ogg' + attack_verb = list("burnt", "singed") + set_light(2, 0.6, LIGHT_COLOR_FIRE) + START_PROCESSING(SSobj, src) + else + hitsound = "swing_hit" + force = 0 + attack_verb = null //human_defense.dm takes care of it + set_light(0) + STOP_PROCESSING(SSobj, src) + update_icon() + +/obj/item/lighter/extinguish() + set_lit(FALSE) + +/obj/item/lighter/attack_self(mob/living/user) + if(user.is_holding(src)) + if(!lit) + set_lit(TRUE) + if(fancy) + user.visible_message("Without even breaking stride, [user] flips open and lights [src] in one smooth movement.", "Without even breaking stride, you flip open and light [src] in one smooth movement.") + playsound(src.loc, 'sound/items/zippo_on.ogg', 100, 1) + else + var/prot = FALSE + var/mob/living/carbon/human/H = user + + if(istype(H) && H.gloves) + var/obj/item/clothing/gloves/G = H.gloves + if(G.max_heat_protection_temperature) + prot = (G.max_heat_protection_temperature > 360) + else + prot = TRUE + + if(prot || prob(75)) + user.visible_message("After a few attempts, [user] manages to light [src].", "After a few attempts, you manage to light [src].") + else + var/hitzone = user.held_index_to_dir(user.active_hand_index) == "r" ? BODY_ZONE_PRECISE_R_HAND : BODY_ZONE_PRECISE_L_HAND + user.apply_damage(5, BURN, hitzone) + user.visible_message("After a few attempts, [user] manages to light [src] - however, [user.p_they()] burn [user.p_their()] finger in the process.", "You burn yourself while lighting the lighter!") + SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "burnt_thumb", /datum/mood_event/burnt_thumb) + playsound(src.loc, 'sound/items/lighter_on.ogg', 100, 1) + + else + set_lit(FALSE) + if(fancy) + user.visible_message("You hear a quiet click, as [user] shuts off [src] without even looking at what [user.p_theyre()] doing. Wow.", "You quietly shut off [src] without even looking at what you're doing. Wow.") + playsound(src.loc, 'sound/items/zippo_off.ogg', 100, 1) + else + user.visible_message("[user] quietly shuts off [src].", "You quietly shut off [src].") + playsound(src.loc, 'sound/items/lighter_off.ogg', 100, 1) + else + . = ..() + +/obj/item/lighter/attack(mob/living/carbon/M, mob/living/carbon/user) + if(lit && M.IgniteMob()) + message_admins("[ADMIN_LOOKUPFLW(user)] set [key_name_admin(M)] on fire with [src] at [AREACOORD(user)]") + log_game("[key_name(user)] set [key_name(M)] on fire with [src] at [AREACOORD(user)]") + var/obj/item/clothing/mask/cigarette/cig = help_light_cig(M) + if(lit && cig && user.a_intent == INTENT_HELP) + if(cig.lit) + to_chat(user, "The [cig.name] is already lit!") + if(M == user) + cig.attackby(src, user) + else + if(fancy) + cig.light("[user] whips the [name] out and holds it for [M]. [user.p_their(TRUE)] arm is as steady as the unflickering flame [user.p_they()] light[user.p_s()] \the [cig] with.") + else + cig.light("[user] holds the [name] out for [M], and lights [M.p_their()] [cig.name].") + else + ..() + +/obj/item/lighter/process() + open_flame() + +/obj/item/lighter/get_temperature() + return lit * heat + + +/obj/item/lighter/greyscale + name = "cheap lighter" + desc = "A cheap lighter." + icon_state = "lighter" + fancy = FALSE + overlay_list = list( + "transp", + "tall", + "matte", + "zoppo" //u cant stoppo th zoppo + ) + var/lighter_color + var/list/color_list = list( //Same 16 color selection as electronic assemblies + COLOR_ASSEMBLY_BLACK, + COLOR_FLOORTILE_GRAY, + COLOR_ASSEMBLY_BGRAY, + COLOR_ASSEMBLY_WHITE, + COLOR_ASSEMBLY_RED, + COLOR_ASSEMBLY_ORANGE, + COLOR_ASSEMBLY_BEIGE, + COLOR_ASSEMBLY_BROWN, + COLOR_ASSEMBLY_GOLD, + COLOR_ASSEMBLY_YELLOW, + COLOR_ASSEMBLY_GURKHA, + COLOR_ASSEMBLY_LGREEN, + COLOR_ASSEMBLY_GREEN, + COLOR_ASSEMBLY_LBLUE, + COLOR_ASSEMBLY_BLUE, + COLOR_ASSEMBLY_PURPLE + ) + +/obj/item/lighter/greyscale/Initialize() + . = ..() + if(!lighter_color) + lighter_color = pick(color_list) + update_icon() + +/obj/item/lighter/greyscale/create_lighter_overlay() + var/mutable_appearance/lighter_overlay = ..() + lighter_overlay.color = lighter_color + return lighter_overlay + +/obj/item/lighter/greyscale/ignition_effect(atom/A, mob/user) + if(get_temperature()) + . = "After some fiddling, [user] manages to light [A] with [src]." + + +/obj/item/lighter/slime + name = "slime zippo" + desc = "A specialty zippo made from slimes and industry. Has a much hotter flame than normal." + icon_state = "slighter" + heat = 3000 //Blue flame! + light_color = LIGHT_COLOR_CYAN + overlay_state = "slime" + grind_results = list(/datum/reagent/iron = 1, /datum/reagent/fuel = 5, /datum/reagent/medicine/pyroxadone = 5) + + +/////////// +//ROLLING// +/////////// +/obj/item/rollingpaper + name = "rolling paper" + desc = "A thin piece of paper used to make fine smokeables." + icon = 'icons/obj/cigarettes.dmi' + icon_state = "cig_paper" + w_class = WEIGHT_CLASS_TINY + +/obj/item/rollingpaper/afterattack(atom/target, mob/user, proximity) + . = ..() + if(!proximity) + return + if(istype(target, /obj/item/reagent_containers/food/snacks/grown)) + var/obj/item/reagent_containers/food/snacks/grown/O = target + if(O.dry) + var/obj/item/clothing/mask/cigarette/rollie/R = new /obj/item/clothing/mask/cigarette/rollie(user.loc) + R.chem_volume = target.reagents.total_volume + target.reagents.trans_to(R, R.chem_volume, transfered_by = user) + qdel(target) + qdel(src) + user.put_in_active_hand(R) + to_chat(user, "You roll the [target.name] into a rolling paper.") + R.desc = "Dried [target.name] rolled up in a thin piece of paper." + else + to_chat(user, "You need to dry this first!") + +/////////////// +//VAPE NATION// +/////////////// +/obj/item/clothing/mask/vape + name = "\improper E-Cigarette" + desc = "A classy and highly sophisticated electronic cigarette, for classy and dignified gentlemen. A warning label reads \"Warning: Do not fill with flammable materials.\""//<<< i'd vape to that. + icon = 'icons/obj/clothing/masks.dmi' + icon_state = "red_vape" + item_state = null + w_class = WEIGHT_CLASS_TINY + var/chem_volume = 100 + var/vapetime = 0 //this so it won't puff out clouds every tick + var/screw = 0 // kinky + var/super = 0 //for the fattest vapes dude. + +/obj/item/clothing/mask/vape/suicide_act(mob/user) + user.visible_message("[user] is puffin hard on dat vape, [user.p_they()] trying to join the vape life on a whole notha plane!")//it doesn't give you cancer, it is cancer + return (TOXLOSS|OXYLOSS) + + +/obj/item/clothing/mask/vape/Initialize(mapload, param_color) + . = ..() + create_reagents(chem_volume, NO_REACT) + reagents.add_reagent(/datum/reagent/drug/nicotine, 50) + if(!param_color) + param_color = pick("red","blue","black","white","green","purple","yellow","orange") + icon_state = "[param_color]_vape" + item_state = "[param_color]_vape" + +/obj/item/clothing/mask/vape/attackby(obj/item/O, mob/user, params) + if(O.tool_behaviour == TOOL_SCREWDRIVER) + if(!screw) + screw = TRUE + to_chat(user, "You open the cap on [src].") + reagents.flags |= OPENCONTAINER + if(obj_flags & EMAGGED) + add_overlay("vapeopen_high") + else if(super) + add_overlay("vapeopen_med") + else + add_overlay("vapeopen_low") + else + screw = FALSE + to_chat(user, "You close the cap on [src].") + reagents.flags &= ~(OPENCONTAINER) + cut_overlays() + + if(O.tool_behaviour == TOOL_MULTITOOL) + if(screw && !(obj_flags & EMAGGED))//also kinky + if(!super) + cut_overlays() + super = 1 + to_chat(user, "You increase the voltage of [src].") + add_overlay("vapeopen_med") + else + cut_overlays() + super = 0 + to_chat(user, "You decrease the voltage of [src].") + add_overlay("vapeopen_low") + + if(screw && (obj_flags & EMAGGED)) + to_chat(user, "[src] can't be modified!") + else + ..() + + +/obj/item/clothing/mask/vape/emag_act(mob/user)// I WON'T REGRET WRITTING THIS, SURLY. + if(screw) + if(!(obj_flags & EMAGGED)) + cut_overlays() + obj_flags |= EMAGGED + super = 0 + to_chat(user, "You maximize the voltage of [src].") + add_overlay("vapeopen_high") + var/datum/effect_system/spark_spread/sp = new /datum/effect_system/spark_spread //for effect + sp.set_up(5, 1, src) + sp.start() + else + to_chat(user, "[src] is already emagged!") + else + to_chat(user, "You need to open the cap to do that!") + +/obj/item/clothing/mask/vape/attack_self(mob/user) + if(reagents.total_volume > 0) + to_chat(user, "You empty [src] of all reagents.") + reagents.clear_reagents() + +/obj/item/clothing/mask/vape/equipped(mob/user, slot) + . = ..() + if(slot == ITEM_SLOT_MASK) + if(!screw) + to_chat(user, "You start puffing on the vape.") + reagents.flags &= ~(NO_REACT) + START_PROCESSING(SSobj, src) + else //it will not start if the vape is opened. + to_chat(user, "You need to close the cap first!") + +/obj/item/clothing/mask/vape/dropped(mob/user) + . = ..() + if(user.get_item_by_slot(ITEM_SLOT_MASK) == src) + reagents.flags |= NO_REACT + STOP_PROCESSING(SSobj, src) + +/obj/item/clothing/mask/vape/proc/hand_reagents()//had to rename to avoid duplicate error + if(reagents.total_volume) + if(iscarbon(loc)) + var/mob/living/carbon/C = loc + if (src == C.wear_mask) // if it's in the human/monkey mouth, transfer reagents to the mob + var/fraction = min(REAGENTS_METABOLISM/reagents.total_volume, 1) //this will react instantly, making them a little more dangerous than cigarettes + reagents.expose(C, INGEST, fraction) + if(!reagents.trans_to(C, REAGENTS_METABOLISM)) + reagents.remove_any(REAGENTS_METABOLISM) + if(reagents.get_reagent_amount(/datum/reagent/fuel)) + //HOT STUFF + C.adjust_fire_stacks(2) + C.IgniteMob() + + if(reagents.get_reagent_amount(/datum/reagent/toxin/plasma)) // the plasma explodes when exposed to fire + var/datum/effect_system/reagents_explosion/e = new() + e.set_up(round(reagents.get_reagent_amount(/datum/reagent/toxin/plasma) / 2.5, 1), get_turf(src), 0, 0) + e.start() + qdel(src) + return + reagents.remove_any(REAGENTS_METABOLISM) + +/obj/item/clothing/mask/vape/process() + var/mob/living/M = loc + + if(isliving(loc)) + M.IgniteMob() + + vapetime++ + + if(!reagents.total_volume) + if(ismob(loc)) + to_chat(M, "[src] is empty!") + STOP_PROCESSING(SSobj, src) + //it's reusable so it won't unequip when empty + return + //open flame removed because vapes are a closed system, they wont light anything on fire + + if(super && vapetime > 3)//Time to start puffing those fat vapes, yo. + var/datum/effect_system/smoke_spread/chem/smoke_machine/s = new + s.set_up(reagents, 1, 24, loc) + s.start() + vapetime = 0 + + if((obj_flags & EMAGGED) && vapetime > 3) + var/datum/effect_system/smoke_spread/chem/smoke_machine/s = new + s.set_up(reagents, 4, 24, loc) + s.start() + vapetime = 0 + if(prob(5))//small chance for the vape to break and deal damage if it's emagged + playsound(get_turf(src), 'sound/effects/pop_expl.ogg', 50, FALSE) + M.apply_damage(20, BURN, BODY_ZONE_HEAD) + M.Paralyze(300, 1, 0) + var/datum/effect_system/spark_spread/sp = new /datum/effect_system/spark_spread + sp.set_up(5, 1, src) + sp.start() + to_chat(M, "[src] suddenly explodes in your mouth!") + qdel(src) + return + + if(reagents && reagents.total_volume) + hand_reagents() diff --git a/code/game/objects/items/circuitboards/machine_circuitboards.dm b/code/game/objects/items/circuitboards/machine_circuitboards.dm index f9b351faadb6..f1792c4428e3 100644 --- a/code/game/objects/items/circuitboards/machine_circuitboards.dm +++ b/code/game/objects/items/circuitboards/machine_circuitboards.dm @@ -1118,6 +1118,13 @@ WaspStation End */ 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 diff --git a/code/game/objects/items/clown_items.dm b/code/game/objects/items/clown_items.dm index 0200811a33f6..59852012f419 100644 --- a/code/game/objects/items/clown_items.dm +++ b/code/game/objects/items/clown_items.dm @@ -1,236 +1,236 @@ -/* Clown Items - * Contains: - * Soap - * Bike Horns - * Air Horns - * Canned Laughter - */ - -/* - * Soap - */ - -/obj/item/soap - name = "soap" - desc = "A cheap bar of soap. Doesn't smell." - gender = PLURAL - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "soap" - lefthand_file = 'icons/mob/inhands/equipment/custodial_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/custodial_righthand.dmi' - w_class = WEIGHT_CLASS_TINY - item_flags = NOBLUDGEON - throwforce = 0 - throw_speed = 3 - throw_range = 7 - grind_results = list(/datum/reagent/lye = 10) - var/cleanspeed = 35 //slower than mop - force_string = "robust... against germs" - var/uses = 100 - -/obj/item/soap/ComponentInitialize() - . = ..() - AddComponent(/datum/component/slippery, 80) - -/obj/item/soap/examine(mob/user) - . = ..() - var/max_uses = initial(uses) - var/msg = "It looks like it just came out of the package." - if(uses != max_uses) - var/percentage_left = uses / max_uses - switch(percentage_left) - if(0 to 0.15) - msg = "There's just a tiny bit left of what it used to be, you're not sure it'll last much longer." - if(0.15 to 0.30) - msg = "It's dissolved quite a bit, but there's still some life to it." - if(0.30 to 0.50) - msg = "It's past its prime, but it's definitely still good." - if(0.50 to 0.75) - msg = "It's started to get a little smaller than it used to be, but it'll definitely still last for a while." - else - msg = "It's seen some light use, but it's still pretty fresh." - . += "[msg]" - -/obj/item/soap/nanotrasen - desc = "A heavy duty bar of Nanotrasen brand soap. Smells of plasma." - grind_results = list(/datum/reagent/toxin/plasma = 10, /datum/reagent/lye = 10) - icon_state = "soapnt" - cleanspeed = 28 //janitor gets this - uses = 300 - -/obj/item/soap/homemade - desc = "A homemade bar of soap. Smells of... well...." - icon_state = "soapgibs" - cleanspeed = 30 // faster to reward chemists for going to the effort - -/obj/item/soap/deluxe - desc = "A deluxe Waffle Co. brand bar of soap. Smells of high-class luxury." - icon_state = "soapdeluxe" - cleanspeed = 20 //captain gets one of these - -/obj/item/soap/syndie - desc = "An untrustworthy bar of soap made of strong chemical agents that dissolve blood faster." - icon_state = "soapsyndie" - cleanspeed = 5 //faster than mop so it is useful for traitors who want to clean crime scenes - -/obj/item/soap/omega - name = "omega soap" - desc = "The most advanced soap known to mankind." - icon_state = "soapomega" - cleanspeed = 3 //Only the truest of mind soul and body get one of these - uses = 301 - -/obj/item/soap/omega/suicide_act(mob/user) - user.visible_message("[user] is using [src] to scrub themselves from the timeline! It looks like [user.p_theyre()] trying to commit suicide!") - new /obj/structure/chrono_field(user.loc, user) - return MANUAL_SUICIDE - -/obj/item/paper/fluff/stations/soap - name = "ancient janitorial poem" - desc = "An old paper that has passed many hands." - info = "The legend of the omega soap

                    Essence of potato. Juice, not grind.

                    A lizard's tail, turned into wine.

                    powder of monkey, to help the workload.

                    Some Krokodil, because meth would explode.

                    Nitric acid and Baldium, for organic dissolving.

                    A cup filled with Hooch, for sinful absolving

                    Some Bluespace Dust, for removal of stains.

                    A syringe full of Pump-up, it's security's bane.

                    Add a can of Space Cola, because we've been paid.

                    Heat as hot as you can, let the soap be your blade.

                    Ten units of each regent create a soap that could topple all others." - - -/obj/item/soap/suicide_act(mob/user) - user.say(";FFFFFFFFFFFFFFFFUUUUUUUDGE!!", forced="soap suicide") - user.visible_message("[user] lifts [src] to [user.p_their()] mouth and gnaws on it furiously, producing a thick froth! [user.p_they(TRUE)]'ll never get that BB gun now!") - new /obj/effect/particle_effect/foam(loc) - return (TOXLOSS) - -/obj/item/soap/proc/decreaseUses(mob/user) - var/skillcheck = user.mind.get_skill_modifier(/datum/skill/cleaning, SKILL_SPEED_MODIFIER) - if(prob(skillcheck*100)) //higher level = more uses assuming RNG is nice - uses-- - if(uses <= 0) - to_chat(user, "[src] crumbles into tiny bits!") - qdel(src) - -/obj/item/soap/afterattack(atom/target, mob/user, proximity) - . = ..() - if(!proximity || !check_allowed_items(target)) - return - var/clean_speedies = cleanspeed * min(user.mind.get_skill_modifier(/datum/skill/cleaning, SKILL_SPEED_MODIFIER)+0.1,1) //less scaling for soapies - //I couldn't feasibly fix the overlay bugs caused by cleaning items we are wearing. - //So this is a workaround. This also makes more sense from an IC standpoint. ~Carn - if(user.client && ((target in user.client.screen) && !user.is_holding(target))) - to_chat(user, "You need to take that [target.name] off before cleaning it!") - else if(istype(target, /obj/effect/decal/cleanable)) - user.visible_message("[user] begins to scrub \the [target.name] out with [src].", "You begin to scrub \the [target.name] out with [src]...") - if(do_after(user, clean_speedies, target = target)) - to_chat(user, "You scrub \the [target.name] out.") - var/obj/effect/decal/cleanable/cleanies = target - user?.mind.adjust_experience(/datum/skill/cleaning, max(round(cleanies.beauty/CLEAN_SKILL_BEAUTY_ADJUSTMENT),0)) //again, intentional that this does NOT round but mops do. - qdel(target) - decreaseUses(user) - - else if(ishuman(target) && user.zone_selected == BODY_ZONE_PRECISE_MOUTH) - var/mob/living/carbon/human/H = user - user.visible_message("\the [user] washes \the [target]'s mouth out with [src.name]!", "You wash \the [target]'s mouth out with [src.name]!") //washes mouth out with soap sounds better than 'the soap' here if(user.zone_selected == "mouth") - if(H.lip_style) - user?.mind.adjust_experience(/datum/skill/cleaning, CLEAN_SKILL_GENERIC_WASH_XP) - H.lip_style = null //removes lipstick - H.update_body() - decreaseUses(user) - return - else if(istype(target, /obj/structure/window)) - user.visible_message("[user] begins to clean \the [target.name] with [src]...", "You begin to clean \the [target.name] with [src]...") - if(do_after(user, clean_speedies, target = target)) - to_chat(user, "You clean \the [target.name].") - target.remove_atom_colour(WASHABLE_COLOUR_PRIORITY) - target.set_opacity(initial(target.opacity)) - user?.mind.adjust_experience(/datum/skill/cleaning, CLEAN_SKILL_GENERIC_WASH_XP) - decreaseUses(user) - else - user.visible_message("[user] begins to clean \the [target.name] with [src]...", "You begin to clean \the [target.name] with [src]...") - if(do_after(user, clean_speedies, target = target)) - to_chat(user, "You clean \the [target.name].") - for(var/obj/effect/decal/cleanable/C in target) - user?.mind.adjust_experience(/datum/skill/cleaning, round(C.beauty/CLEAN_SKILL_BEAUTY_ADJUSTMENT)) - qdel(C) - target.remove_atom_colour(WASHABLE_COLOUR_PRIORITY) - SEND_SIGNAL(target, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_MEDIUM) - user?.mind.adjust_experience(/datum/skill/cleaning, CLEAN_SKILL_GENERIC_WASH_XP) - decreaseUses(user) - return - - -/* - * Bike Horns - */ - -/obj/item/bikehorn - name = "bike horn" - desc = "A horn off of a bicycle." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "bike_horn" - item_state = "bike_horn" - lefthand_file = 'icons/mob/inhands/equipment/horns_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/horns_righthand.dmi' - throwforce = 0 - hitsound = null //To prevent tap.ogg playing, as the item lacks of force - w_class = WEIGHT_CLASS_TINY - slot_flags = ITEM_SLOT_BACK|ITEM_SLOT_BELT - throw_speed = 3 - throw_range = 7 - attack_verb = list("HONKED") - -/obj/item/bikehorn/Initialize() - . = ..() - AddComponent(/datum/component/squeak, list('sound/items/bikehorn.ogg'=1), 50) - -/obj/item/bikehorn/attack(mob/living/carbon/M, mob/living/carbon/user) - if(user != M && ishuman(user)) - var/mob/living/carbon/human/H = user - if (HAS_TRAIT(H, TRAIT_CLUMSY)) //only clowns can unlock its true powers - SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "honk", /datum/mood_event/honk) - return ..() - -/obj/item/bikehorn/suicide_act(mob/user) - user.visible_message("[user] solemnly points [src] at [user.p_their()] temple! It looks like [user.p_theyre()] trying to commit suicide!") - playsound(src, 'sound/items/bikehorn.ogg', 50, TRUE) - return (BRUTELOSS) - -//air horn -/obj/item/bikehorn/airhorn - name = "air horn" - desc = "Damn son, where'd you find this?" - icon_state = "air_horn" - -/obj/item/bikehorn/airhorn/Initialize() - . = ..() - AddComponent(/datum/component/squeak, list('sound/items/airhorn2.ogg'=1), 50) - -//golden bikehorn -/obj/item/bikehorn/golden - name = "golden bike horn" - desc = "Golden? Clearly, it's made with bananium! Honk!" - icon_state = "gold_horn" - item_state = "gold_horn" - var/flip_cooldown = 0 - -/obj/item/bikehorn/golden/attack() - if(flip_cooldown < world.time) - flip_mobs() - return ..() - -/obj/item/bikehorn/golden/attack_self(mob/user) - if(flip_cooldown < world.time) - flip_mobs() - ..() - -/obj/item/bikehorn/golden/proc/flip_mobs(mob/living/carbon/M, mob/user) - var/turf/T = get_turf(src) - for(M in ohearers(7, T)) - if(ishuman(M) && M.can_hear()) - var/mob/living/carbon/human/H = M - if(istype(H.ears, /obj/item/clothing/ears/earmuffs)) - continue - M.emote("flip") - flip_cooldown = world.time + 7 - -//canned laughter -/obj/item/reagent_containers/food/drinks/soda_cans/canned_laughter - name = "Canned Laughter" - desc = "Just looking at this makes you want to giggle." - icon_state = "laughter" - list_reagents = list(/datum/reagent/consumable/laughter = 50) +/* Clown Items + * Contains: + * Soap + * Bike Horns + * Air Horns + * Canned Laughter + */ + +/* + * Soap + */ + +/obj/item/soap + name = "soap" + desc = "A cheap bar of soap. Doesn't smell." + gender = PLURAL + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "soap" + lefthand_file = 'icons/mob/inhands/equipment/custodial_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/custodial_righthand.dmi' + w_class = WEIGHT_CLASS_TINY + item_flags = NOBLUDGEON + throwforce = 0 + throw_speed = 3 + throw_range = 7 + grind_results = list(/datum/reagent/lye = 10) + var/cleanspeed = 35 //slower than mop + force_string = "robust... against germs" + var/uses = 100 + +/obj/item/soap/ComponentInitialize() + . = ..() + AddComponent(/datum/component/slippery, 80) + +/obj/item/soap/examine(mob/user) + . = ..() + var/max_uses = initial(uses) + var/msg = "It looks like it just came out of the package." + if(uses != max_uses) + var/percentage_left = uses / max_uses + switch(percentage_left) + if(0 to 0.15) + msg = "There's just a tiny bit left of what it used to be, you're not sure it'll last much longer." + if(0.15 to 0.30) + msg = "It's dissolved quite a bit, but there's still some life to it." + if(0.30 to 0.50) + msg = "It's past its prime, but it's definitely still good." + if(0.50 to 0.75) + msg = "It's started to get a little smaller than it used to be, but it'll definitely still last for a while." + else + msg = "It's seen some light use, but it's still pretty fresh." + . += "[msg]" + +/obj/item/soap/nanotrasen + desc = "A heavy duty bar of Nanotrasen brand soap. Smells of plasma." + grind_results = list(/datum/reagent/toxin/plasma = 10, /datum/reagent/lye = 10) + icon_state = "soapnt" + cleanspeed = 28 //janitor gets this + uses = 300 + +/obj/item/soap/homemade + desc = "A homemade bar of soap. Smells of... well...." + icon_state = "soapgibs" + cleanspeed = 30 // faster to reward chemists for going to the effort + +/obj/item/soap/deluxe + desc = "A deluxe Waffle Co. brand bar of soap. Smells of high-class luxury." + icon_state = "soapdeluxe" + cleanspeed = 20 //captain gets one of these + +/obj/item/soap/syndie + desc = "An untrustworthy bar of soap made of strong chemical agents that dissolve blood faster." + icon_state = "soapsyndie" + cleanspeed = 5 //faster than mop so it is useful for traitors who want to clean crime scenes + +/obj/item/soap/omega + name = "omega soap" + desc = "The most advanced soap known to mankind." + icon_state = "soapomega" + cleanspeed = 3 //Only the truest of mind soul and body get one of these + uses = 301 + +/obj/item/soap/omega/suicide_act(mob/user) + user.visible_message("[user] is using [src] to scrub themselves from the timeline! It looks like [user.p_theyre()] trying to commit suicide!") + new /obj/structure/chrono_field(user.loc, user) + return MANUAL_SUICIDE + +/obj/item/paper/fluff/stations/soap + name = "ancient janitorial poem" + desc = "An old paper that has passed many hands." + info = "The legend of the omega soap

                    Essence of potato. Juice, not grind.

                    A lizard's tail, turned into wine.

                    powder of monkey, to help the workload.

                    Some Krokodil, because meth would explode.

                    Nitric acid and Baldium, for organic dissolving.

                    A cup filled with Hooch, for sinful absolving

                    Some Bluespace Dust, for removal of stains.

                    A syringe full of Pump-up, it's security's bane.

                    Add a can of Space Cola, because we've been paid.

                    Heat as hot as you can, let the soap be your blade.

                    Ten units of each regent create a soap that could topple all others." + + +/obj/item/soap/suicide_act(mob/user) + user.say(";FFFFFFFFFFFFFFFFUUUUUUUDGE!!", forced="soap suicide") + user.visible_message("[user] lifts [src] to [user.p_their()] mouth and gnaws on it furiously, producing a thick froth! [user.p_they(TRUE)]'ll never get that BB gun now!") + new /obj/effect/particle_effect/foam(loc) + return (TOXLOSS) + +/obj/item/soap/proc/decreaseUses(mob/user) + var/skillcheck = user.mind.get_skill_modifier(/datum/skill/cleaning, SKILL_SPEED_MODIFIER) + if(prob(skillcheck*100)) //higher level = more uses assuming RNG is nice + uses-- + if(uses <= 0) + to_chat(user, "[src] crumbles into tiny bits!") + qdel(src) + +/obj/item/soap/afterattack(atom/target, mob/user, proximity) + . = ..() + if(!proximity || !check_allowed_items(target)) + return + var/clean_speedies = cleanspeed * min(user.mind.get_skill_modifier(/datum/skill/cleaning, SKILL_SPEED_MODIFIER)+0.1,1) //less scaling for soapies + //I couldn't feasibly fix the overlay bugs caused by cleaning items we are wearing. + //So this is a workaround. This also makes more sense from an IC standpoint. ~Carn + if(user.client && ((target in user.client.screen) && !user.is_holding(target))) + to_chat(user, "You need to take that [target.name] off before cleaning it!") + else if(istype(target, /obj/effect/decal/cleanable)) + user.visible_message("[user] begins to scrub \the [target.name] out with [src].", "You begin to scrub \the [target.name] out with [src]...") + if(do_after(user, clean_speedies, target = target)) + to_chat(user, "You scrub \the [target.name] out.") + var/obj/effect/decal/cleanable/cleanies = target + user?.mind.adjust_experience(/datum/skill/cleaning, max(round(cleanies.beauty/CLEAN_SKILL_BEAUTY_ADJUSTMENT),0)) //again, intentional that this does NOT round but mops do. + qdel(target) + decreaseUses(user) + + else if(ishuman(target) && user.zone_selected == BODY_ZONE_PRECISE_MOUTH) + var/mob/living/carbon/human/H = user + user.visible_message("\the [user] washes \the [target]'s mouth out with [src.name]!", "You wash \the [target]'s mouth out with [src.name]!") //washes mouth out with soap sounds better than 'the soap' here if(user.zone_selected == "mouth") + if(H.lip_style) + user?.mind.adjust_experience(/datum/skill/cleaning, CLEAN_SKILL_GENERIC_WASH_XP) + H.lip_style = null //removes lipstick + H.update_body() + decreaseUses(user) + return + else if(istype(target, /obj/structure/window)) + user.visible_message("[user] begins to clean \the [target.name] with [src]...", "You begin to clean \the [target.name] with [src]...") + if(do_after(user, clean_speedies, target = target)) + to_chat(user, "You clean \the [target.name].") + target.remove_atom_colour(WASHABLE_COLOUR_PRIORITY) + target.set_opacity(initial(target.opacity)) + user?.mind.adjust_experience(/datum/skill/cleaning, CLEAN_SKILL_GENERIC_WASH_XP) + decreaseUses(user) + else + user.visible_message("[user] begins to clean \the [target.name] with [src]...", "You begin to clean \the [target.name] with [src]...") + if(do_after(user, clean_speedies, target = target)) + to_chat(user, "You clean \the [target.name].") + for(var/obj/effect/decal/cleanable/C in target) + user?.mind.adjust_experience(/datum/skill/cleaning, round(C.beauty/CLEAN_SKILL_BEAUTY_ADJUSTMENT)) + qdel(C) + target.remove_atom_colour(WASHABLE_COLOUR_PRIORITY) + SEND_SIGNAL(target, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_MEDIUM) + user?.mind.adjust_experience(/datum/skill/cleaning, CLEAN_SKILL_GENERIC_WASH_XP) + decreaseUses(user) + return + + +/* + * Bike Horns + */ + +/obj/item/bikehorn + name = "bike horn" + desc = "A horn off of a bicycle." + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "bike_horn" + item_state = "bike_horn" + lefthand_file = 'icons/mob/inhands/equipment/horns_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/horns_righthand.dmi' + throwforce = 0 + hitsound = null //To prevent tap.ogg playing, as the item lacks of force + w_class = WEIGHT_CLASS_TINY + slot_flags = ITEM_SLOT_BACK|ITEM_SLOT_BELT + throw_speed = 3 + throw_range = 7 + attack_verb = list("HONKED") + +/obj/item/bikehorn/Initialize() + . = ..() + AddComponent(/datum/component/squeak, list('sound/items/bikehorn.ogg'=1), 50) + +/obj/item/bikehorn/attack(mob/living/carbon/M, mob/living/carbon/user) + if(user != M && ishuman(user)) + var/mob/living/carbon/human/H = user + if (HAS_TRAIT(H, TRAIT_CLUMSY)) //only clowns can unlock its true powers + SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "honk", /datum/mood_event/honk) + return ..() + +/obj/item/bikehorn/suicide_act(mob/user) + user.visible_message("[user] solemnly points [src] at [user.p_their()] temple! It looks like [user.p_theyre()] trying to commit suicide!") + playsound(src, 'sound/items/bikehorn.ogg', 50, TRUE) + return (BRUTELOSS) + +//air horn +/obj/item/bikehorn/airhorn + name = "air horn" + desc = "Damn son, where'd you find this?" + icon_state = "air_horn" + +/obj/item/bikehorn/airhorn/Initialize() + . = ..() + AddComponent(/datum/component/squeak, list('sound/items/airhorn2.ogg'=1), 50) + +//golden bikehorn +/obj/item/bikehorn/golden + name = "golden bike horn" + desc = "Golden? Clearly, it's made with bananium! Honk!" + icon_state = "gold_horn" + item_state = "gold_horn" + var/flip_cooldown = 0 + +/obj/item/bikehorn/golden/attack() + if(flip_cooldown < world.time) + flip_mobs() + return ..() + +/obj/item/bikehorn/golden/attack_self(mob/user) + if(flip_cooldown < world.time) + flip_mobs() + ..() + +/obj/item/bikehorn/golden/proc/flip_mobs(mob/living/carbon/M, mob/user) + var/turf/T = get_turf(src) + for(M in ohearers(7, T)) + if(ishuman(M) && M.can_hear()) + var/mob/living/carbon/human/H = M + if(istype(H.ears, /obj/item/clothing/ears/earmuffs)) + continue + M.emote("flip") + flip_cooldown = world.time + 7 + +//canned laughter +/obj/item/reagent_containers/food/drinks/soda_cans/canned_laughter + name = "Canned Laughter" + desc = "Just looking at this makes you want to giggle." + icon_state = "laughter" + list_reagents = list(/datum/reagent/consumable/laughter = 50) diff --git a/code/game/objects/items/cosmetics.dm b/code/game/objects/items/cosmetics.dm index a64b87524404..ae55a0fbc8f8 100644 --- a/code/game/objects/items/cosmetics.dm +++ b/code/game/objects/items/cosmetics.dm @@ -1,232 +1,232 @@ -/obj/item/lipstick - gender = PLURAL - name = "red lipstick" - desc = "A generic brand of lipstick." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "lipstick" - w_class = WEIGHT_CLASS_TINY - var/colour = "red" - var/open = FALSE - -/obj/item/lipstick/purple - name = "purple lipstick" - colour = "purple" - -/obj/item/lipstick/jade - //It's still called Jade, but theres no HTML color for jade, so we use lime. - name = "jade lipstick" - colour = "lime" - -/obj/item/lipstick/black - name = "black lipstick" - colour = "black" - -/obj/item/lipstick/random - name = "lipstick" - icon_state = "random_lipstick" - -/obj/item/lipstick/random/Initialize() - . = ..() - icon_state = "lipstick" - colour = pick("red","purple","lime","black","green","blue","white") - name = "[colour] lipstick" - -/obj/item/lipstick/attack_self(mob/user) - cut_overlays() - to_chat(user, "You twist \the [src] [open ? "closed" : "open"].") - open = !open - if(open) - var/mutable_appearance/colored_overlay = mutable_appearance(icon, "lipstick_uncap_color") - colored_overlay.color = colour - icon_state = "lipstick_uncap" - add_overlay(colored_overlay) - else - icon_state = "lipstick" - -/obj/item/lipstick/attack(mob/M, mob/user) - if(!open) - return - - if(!ismob(M)) - return - - if(ishuman(M)) - var/mob/living/carbon/human/H = M - if(H.is_mouth_covered()) - to_chat(user, "Remove [ H == user ? "your" : "[H.p_their()]" ] mask!") - return - if(H.lip_style) //if they already have lipstick on - to_chat(user, "You need to wipe off the old lipstick first!") - return - if(H == user) - user.visible_message("[user] does [user.p_their()] lips with \the [src].", \ - "You take a moment to apply \the [src]. Perfect!") - H.lip_style = "lipstick" - H.lip_color = colour - H.update_body() - else - user.visible_message("[user] begins to do [H]'s lips with \the [src].", \ - "You begin to apply \the [src] on [H]'s lips...") - if(do_after(user, 20, target = H)) - user.visible_message("[user] does [H]'s lips with \the [src].", \ - "You apply \the [src] on [H]'s lips.") - H.lip_style = "lipstick" - H.lip_color = colour - H.update_body() - else - to_chat(user, "Where are the lips on that?") - -//you can wipe off lipstick with paper! -/obj/item/paper/attack(mob/M, mob/user) - if(user.zone_selected == BODY_ZONE_PRECISE_MOUTH) - if(!ismob(M)) - return - - if(ishuman(M)) - var/mob/living/carbon/human/H = M - if(H == user) - to_chat(user, "You wipe off the lipstick with [src].") - H.lip_style = null - H.update_body() - else - user.visible_message("[user] begins to wipe [H]'s lipstick off with \the [src].", \ - "You begin to wipe off [H]'s lipstick...") - if(do_after(user, 10, target = H)) - user.visible_message("[user] wipes [H]'s lipstick off with \the [src].", \ - "You wipe off [H]'s lipstick.") - H.lip_style = null - H.update_body() - else - ..() - -/obj/item/razor - name = "electric razor" - desc = "The latest and greatest power razor born from the science of shaving." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "razor" - flags_1 = CONDUCT_1 - w_class = WEIGHT_CLASS_TINY - -/obj/item/razor/suicide_act(mob/living/carbon/user) - user.visible_message("[user] begins shaving [user.p_them()]self without the razor guard! It looks like [user.p_theyre()] trying to commit suicide!") - shave(user, BODY_ZONE_PRECISE_MOUTH) - shave(user, BODY_ZONE_HEAD)//doesnt need to be BODY_ZONE_HEAD specifically, but whatever - return BRUTELOSS - -/obj/item/razor/proc/shave(mob/living/carbon/human/H, location = BODY_ZONE_PRECISE_MOUTH) - if(location == BODY_ZONE_PRECISE_MOUTH) - H.facial_hairstyle = "Shaved" - else - H.hairstyle = "Skinhead" - - H.update_hair() - playsound(loc, 'sound/items/welder2.ogg', 20, TRUE) - -/* Wasp edit - In modularized file -/obj/item/razor/attack(mob/M, mob/user) - if(ishuman(M)) - var/mob/living/carbon/human/H = M - var/location = user.zone_selected - if((location in list(BODY_ZONE_PRECISE_EYES, BODY_ZONE_PRECISE_MOUTH, BODY_ZONE_HEAD)) && !H.get_bodypart(BODY_ZONE_HEAD)) - to_chat(user, "[H] doesn't have a head!") - return - if(location == BODY_ZONE_PRECISE_MOUTH) - if(user.a_intent == INTENT_HELP) - if(H.gender == MALE) - if (H == user) - to_chat(user, "You need a mirror to properly style your own facial hair!") - return - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - var/new_style = input(user, "Select a facial hairstyle", "Grooming") as null|anything in GLOB.facial_hairstyles_list - if(!get_location_accessible(H, location)) - to_chat(user, "The mask is in the way!") - return - user.visible_message("[user] tries to change [H]'s facial hairstyle using [src].", "You try to change [H]'s facial hairstyle using [src].") - if(new_style && do_after(user, 60, target = H)) - user.visible_message("[user] successfully changes [H]'s facial hairstyle using [src].", "You successfully change [H]'s facial hairstyle using [src].") - H.facial_hairstyle = new_style - H.update_hair() - return - else - return - - else - if(!(FACEHAIR in H.dna.species.species_traits)) - to_chat(user, "There is no facial hair to shave!") - return - if(!get_location_accessible(H, location)) - to_chat(user, "The mask is in the way!") - return - if(H.facial_hairstyle == "Shaved") - to_chat(user, "Already clean-shaven!") - return - - if(H == user) //shaving yourself - user.visible_message("[user] starts to shave [user.p_their()] facial hair with [src].", \ - "You take a moment to shave your facial hair with [src]...") - if(do_after(user, 50, target = H)) - user.visible_message("[user] shaves [user.p_their()] facial hair clean with [src].", \ - "You finish shaving with [src]. Fast and clean!") - shave(H, location) - else - user.visible_message("[user] tries to shave [H]'s facial hair with [src].", \ - "You start shaving [H]'s facial hair...") - if(do_after(user, 50, target = H)) - user.visible_message("[user] shaves off [H]'s facial hair with [src].", \ - "You shave [H]'s facial hair clean off.") - shave(H, location) - - else if(location == BODY_ZONE_HEAD) - if(user.a_intent == INTENT_HELP) - if (H == user) - to_chat(user, "You need a mirror to properly style your own hair!") - return - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - var/new_style = input(user, "Select a hairstyle", "Grooming") as null|anything in GLOB.hairstyles_list - if(!get_location_accessible(H, location)) - to_chat(user, "The headgear is in the way!") - return - if(HAS_TRAIT(H, TRAIT_BALD)) - to_chat(H, "[H] is just way too bald. Like, really really bald.") - return - user.visible_message("[user] tries to change [H]'s hairstyle using [src].", "You try to change [H]'s hairstyle using [src].") - if(new_style && do_after(user, 60, target = H)) - user.visible_message("[user] successfully changes [H]'s hairstyle using [src].", "You successfully change [H]'s hairstyle using [src].") - H.hairstyle = new_style - H.update_hair() - return - - else - if(!(HAIR in H.dna.species.species_traits)) - to_chat(user, "There is no hair to shave!") - return - if(!get_location_accessible(H, location)) - to_chat(user, "The headgear is in the way!") - return - if(H.hairstyle == "Bald" || H.hairstyle == "Balding Hair" || H.hairstyle == "Skinhead") - to_chat(user, "There is not enough hair left to shave!") - return - - if(H == user) //shaving yourself - user.visible_message("[user] starts to shave [user.p_their()] head with [src].", \ - "You start to shave your head with [src]...") - if(do_after(user, 5, target = H)) - user.visible_message("[user] shaves [user.p_their()] head with [src].", \ - "You finish shaving with [src].") - shave(H, location) - else - var/turf/H_loc = H.loc - user.visible_message("[user] tries to shave [H]'s head with [src]!", \ - "You start shaving [H]'s head...") - if(do_after(user, 50, target = H)) - if(H_loc == H.loc) - user.visible_message("[user] shaves [H]'s head bald with [src]!", \ - "You shave [H]'s head bald.") - shave(H, location) - else - ..() - else - ..() -Wasp end */ +/obj/item/lipstick + gender = PLURAL + name = "red lipstick" + desc = "A generic brand of lipstick." + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "lipstick" + w_class = WEIGHT_CLASS_TINY + var/colour = "red" + var/open = FALSE + +/obj/item/lipstick/purple + name = "purple lipstick" + colour = "purple" + +/obj/item/lipstick/jade + //It's still called Jade, but theres no HTML color for jade, so we use lime. + name = "jade lipstick" + colour = "lime" + +/obj/item/lipstick/black + name = "black lipstick" + colour = "black" + +/obj/item/lipstick/random + name = "lipstick" + icon_state = "random_lipstick" + +/obj/item/lipstick/random/Initialize() + . = ..() + icon_state = "lipstick" + colour = pick("red","purple","lime","black","green","blue","white") + name = "[colour] lipstick" + +/obj/item/lipstick/attack_self(mob/user) + cut_overlays() + to_chat(user, "You twist \the [src] [open ? "closed" : "open"].") + open = !open + if(open) + var/mutable_appearance/colored_overlay = mutable_appearance(icon, "lipstick_uncap_color") + colored_overlay.color = colour + icon_state = "lipstick_uncap" + add_overlay(colored_overlay) + else + icon_state = "lipstick" + +/obj/item/lipstick/attack(mob/M, mob/user) + if(!open) + return + + if(!ismob(M)) + return + + if(ishuman(M)) + var/mob/living/carbon/human/H = M + if(H.is_mouth_covered()) + to_chat(user, "Remove [ H == user ? "your" : "[H.p_their()]" ] mask!") + return + if(H.lip_style) //if they already have lipstick on + to_chat(user, "You need to wipe off the old lipstick first!") + return + if(H == user) + user.visible_message("[user] does [user.p_their()] lips with \the [src].", \ + "You take a moment to apply \the [src]. Perfect!") + H.lip_style = "lipstick" + H.lip_color = colour + H.update_body() + else + user.visible_message("[user] begins to do [H]'s lips with \the [src].", \ + "You begin to apply \the [src] on [H]'s lips...") + if(do_after(user, 20, target = H)) + user.visible_message("[user] does [H]'s lips with \the [src].", \ + "You apply \the [src] on [H]'s lips.") + H.lip_style = "lipstick" + H.lip_color = colour + H.update_body() + else + to_chat(user, "Where are the lips on that?") + +//you can wipe off lipstick with paper! +/obj/item/paper/attack(mob/M, mob/user) + if(user.zone_selected == BODY_ZONE_PRECISE_MOUTH) + if(!ismob(M)) + return + + if(ishuman(M)) + var/mob/living/carbon/human/H = M + if(H == user) + to_chat(user, "You wipe off the lipstick with [src].") + H.lip_style = null + H.update_body() + else + user.visible_message("[user] begins to wipe [H]'s lipstick off with \the [src].", \ + "You begin to wipe off [H]'s lipstick...") + if(do_after(user, 10, target = H)) + user.visible_message("[user] wipes [H]'s lipstick off with \the [src].", \ + "You wipe off [H]'s lipstick.") + H.lip_style = null + H.update_body() + else + ..() + +/obj/item/razor + name = "electric razor" + desc = "The latest and greatest power razor born from the science of shaving." + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "razor" + flags_1 = CONDUCT_1 + w_class = WEIGHT_CLASS_TINY + +/obj/item/razor/suicide_act(mob/living/carbon/user) + user.visible_message("[user] begins shaving [user.p_them()]self without the razor guard! It looks like [user.p_theyre()] trying to commit suicide!") + shave(user, BODY_ZONE_PRECISE_MOUTH) + shave(user, BODY_ZONE_HEAD)//doesnt need to be BODY_ZONE_HEAD specifically, but whatever + return BRUTELOSS + +/obj/item/razor/proc/shave(mob/living/carbon/human/H, location = BODY_ZONE_PRECISE_MOUTH) + if(location == BODY_ZONE_PRECISE_MOUTH) + H.facial_hairstyle = "Shaved" + else + H.hairstyle = "Skinhead" + + H.update_hair() + playsound(loc, 'sound/items/welder2.ogg', 20, TRUE) + +/* Wasp edit - In modularized file +/obj/item/razor/attack(mob/M, mob/user) + if(ishuman(M)) + var/mob/living/carbon/human/H = M + var/location = user.zone_selected + if((location in list(BODY_ZONE_PRECISE_EYES, BODY_ZONE_PRECISE_MOUTH, BODY_ZONE_HEAD)) && !H.get_bodypart(BODY_ZONE_HEAD)) + to_chat(user, "[H] doesn't have a head!") + return + if(location == BODY_ZONE_PRECISE_MOUTH) + if(user.a_intent == INTENT_HELP) + if(H.gender == MALE) + if (H == user) + to_chat(user, "You need a mirror to properly style your own facial hair!") + return + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + var/new_style = input(user, "Select a facial hairstyle", "Grooming") as null|anything in GLOB.facial_hairstyles_list + if(!get_location_accessible(H, location)) + to_chat(user, "The mask is in the way!") + return + user.visible_message("[user] tries to change [H]'s facial hairstyle using [src].", "You try to change [H]'s facial hairstyle using [src].") + if(new_style && do_after(user, 60, target = H)) + user.visible_message("[user] successfully changes [H]'s facial hairstyle using [src].", "You successfully change [H]'s facial hairstyle using [src].") + H.facial_hairstyle = new_style + H.update_hair() + return + else + return + + else + if(!(FACEHAIR in H.dna.species.species_traits)) + to_chat(user, "There is no facial hair to shave!") + return + if(!get_location_accessible(H, location)) + to_chat(user, "The mask is in the way!") + return + if(H.facial_hairstyle == "Shaved") + to_chat(user, "Already clean-shaven!") + return + + if(H == user) //shaving yourself + user.visible_message("[user] starts to shave [user.p_their()] facial hair with [src].", \ + "You take a moment to shave your facial hair with [src]...") + if(do_after(user, 50, target = H)) + user.visible_message("[user] shaves [user.p_their()] facial hair clean with [src].", \ + "You finish shaving with [src]. Fast and clean!") + shave(H, location) + else + user.visible_message("[user] tries to shave [H]'s facial hair with [src].", \ + "You start shaving [H]'s facial hair...") + if(do_after(user, 50, target = H)) + user.visible_message("[user] shaves off [H]'s facial hair with [src].", \ + "You shave [H]'s facial hair clean off.") + shave(H, location) + + else if(location == BODY_ZONE_HEAD) + if(user.a_intent == INTENT_HELP) + if (H == user) + to_chat(user, "You need a mirror to properly style your own hair!") + return + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + var/new_style = input(user, "Select a hairstyle", "Grooming") as null|anything in GLOB.hairstyles_list + if(!get_location_accessible(H, location)) + to_chat(user, "The headgear is in the way!") + return + if(HAS_TRAIT(H, TRAIT_BALD)) + to_chat(H, "[H] is just way too bald. Like, really really bald.") + return + user.visible_message("[user] tries to change [H]'s hairstyle using [src].", "You try to change [H]'s hairstyle using [src].") + if(new_style && do_after(user, 60, target = H)) + user.visible_message("[user] successfully changes [H]'s hairstyle using [src].", "You successfully change [H]'s hairstyle using [src].") + H.hairstyle = new_style + H.update_hair() + return + + else + if(!(HAIR in H.dna.species.species_traits)) + to_chat(user, "There is no hair to shave!") + return + if(!get_location_accessible(H, location)) + to_chat(user, "The headgear is in the way!") + return + if(H.hairstyle == "Bald" || H.hairstyle == "Balding Hair" || H.hairstyle == "Skinhead") + to_chat(user, "There is not enough hair left to shave!") + return + + if(H == user) //shaving yourself + user.visible_message("[user] starts to shave [user.p_their()] head with [src].", \ + "You start to shave your head with [src]...") + if(do_after(user, 5, target = H)) + user.visible_message("[user] shaves [user.p_their()] head with [src].", \ + "You finish shaving with [src].") + shave(H, location) + else + var/turf/H_loc = H.loc + user.visible_message("[user] tries to shave [H]'s head with [src]!", \ + "You start shaving [H]'s head...") + if(do_after(user, 50, target = H)) + if(H_loc == H.loc) + user.visible_message("[user] shaves [H]'s head bald with [src]!", \ + "You shave [H]'s head bald.") + shave(H, location) + else + ..() + else + ..() +Wasp end */ diff --git a/code/game/objects/items/crab17.dm b/code/game/objects/items/crab17.dm index 13847dc517e3..82bf22367787 100644 --- a/code/game/objects/items/crab17.dm +++ b/code/game/objects/items/crab17.dm @@ -1,229 +1,229 @@ -/obj/item/suspiciousphone - name = "suspicious phone" - desc = "This device raises pink levels to unknown highs." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "suspiciousphone" - w_class = WEIGHT_CLASS_SMALL - attack_verb = list("dumped") - var/dumped = FALSE - -/obj/item/suspiciousphone/attack_self(mob/user) - if(!ishuman(user)) - to_chat(user, "This device is too advanced for you!") - return - if(dumped) - to_chat(user, "You already activated Protocol CRAB-17.") - return FALSE - if(alert(user, "Are you sure you want to crash this market with no survivors?", "Protocol CRAB-17", "Yes", "No") == "Yes") - if(dumped || QDELETED(src)) //Prevents fuckers from cheesing alert - return FALSE - var/turf/targetturf = get_safe_random_station_turf() - if (!targetturf) - return FALSE - new /obj/effect/dumpeetTarget(targetturf, user) - dumped = TRUE - -/obj/structure/checkoutmachine - name = "\improper Nanotrasen Space-Coin Market" - desc = "This is good for spacecoin because" - icon = 'icons/obj/money_machine.dmi' - icon_state = "bogdanoff" - layer = LARGE_MOB_LAYER - armor = list("melee" = 80, "bullet" = 30, "laser" = 30, "energy" = 60, "bomb" = 90, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 80) - density = TRUE - pixel_z = -8 - max_integrity = 5000 - var/list/accounts_to_rob - var/mob/living/carbon/human/bogdanoff - var/canwalk = FALSE - -/obj/structure/checkoutmachine/examine(mob/living/user) - . = ..() - . += "It's integrated integrity meter reads: HEALTH: [obj_integrity]." - -/obj/structure/checkoutmachine/proc/check_if_finished() - for(var/i in accounts_to_rob) - var/datum/bank_account/B = i - if (B.being_dumped) - return FALSE - return TRUE - -/obj/structure/checkoutmachine/attackby(obj/item/W, mob/user, params) - if(check_if_finished()) - qdel(src) - return - if(istype(W, /obj/item/card/id)) - var/obj/item/card/id/card = W - if(!card.registered_account) - to_chat(user, "This card does not have a registered account!") - return - if(!card.registered_account.being_dumped) - to_chat(user, "It appears that your funds are safe from draining!") - return - if(do_after(user, 40, target = src)) - if(!card.registered_account.being_dumped) - return - to_chat(user, "You quickly cash out your funds to a more secure banking location. Funds are safu.") // This is a reference and not a typo - card.registered_account.being_dumped = FALSE - card.registered_account.withdrawDelay = 0 - if(check_if_finished()) - qdel(src) - return - else - return ..() - -/obj/structure/checkoutmachine/Initialize(mapload, mob/living/user) - . = ..() - bogdanoff = user - add_overlay("flaps") - add_overlay("hatch") - add_overlay("legs_retracted") - addtimer(CALLBACK(src, .proc/startUp), 50) - QDEL_IN(src, 8 MINUTES) //Self destruct after 8 min - - -/obj/structure/checkoutmachine/proc/startUp() //very VERY snowflake code that adds a neat animation when the pod lands. - start_dumping() //The machine doesnt move during this time, giving people close by a small window to grab their funds before it starts running around - sleep(10) - if(QDELETED(src)) - return - playsound(src, 'sound/machines/click.ogg', 15, TRUE, -3) - cut_overlay("flaps") - sleep(10) - if(QDELETED(src)) - return - playsound(src, 'sound/machines/click.ogg', 15, TRUE, -3) - cut_overlay("hatch") - sleep(30) - if(QDELETED(src)) - return - playsound(src,'sound/machines/twobeep.ogg',50,FALSE) - var/mutable_appearance/hologram = mutable_appearance(icon, "hologram") - hologram.pixel_y = 16 - add_overlay(hologram) - var/mutable_appearance/holosign = mutable_appearance(icon, "holosign") - holosign.pixel_y = 16 - add_overlay(holosign) - add_overlay("legs_extending") - cut_overlay("legs_retracted") - pixel_z += 4 - sleep(5) - if(QDELETED(src)) - return - add_overlay("legs_extended") - cut_overlay("legs_extending") - pixel_z += 4 - sleep(20) - if(QDELETED(src)) - return - add_overlay("screen_lines") - sleep(5) - if(QDELETED(src)) - return - cut_overlay("screen_lines") - sleep(5) - if(QDELETED(src)) - return - add_overlay("screen_lines") - add_overlay("screen") - sleep(5) - if(QDELETED(src)) - return - playsound(src,'sound/machines/triple_beep.ogg',50,FALSE) - add_overlay("text") - sleep(10) - if(QDELETED(src)) - return - add_overlay("legs") - cut_overlay("legs_extended") - cut_overlay("screen") - add_overlay("screen") - cut_overlay("screen_lines") - add_overlay("screen_lines") - cut_overlay("text") - add_overlay("text") - canwalk = TRUE - START_PROCESSING(SSfastprocess, src) - -/obj/structure/checkoutmachine/Destroy() - stop_dumping() - STOP_PROCESSING(SSfastprocess, src) - priority_announce("The credit deposit machine at [get_area(src)] has been destroyed. Station funds have stopped draining!", sender_override = "CRAB-17 Protocol") - explosion(src, 0,0,1, flame_range = 2) - return ..() - -/obj/structure/checkoutmachine/proc/start_dumping() - accounts_to_rob = SSeconomy.bank_accounts.Copy() - accounts_to_rob -= bogdanoff.get_bank_account() - for(var/i in accounts_to_rob) - var/datum/bank_account/B = i - B.dumpeet() - dump() - -/obj/structure/checkoutmachine/proc/dump() - var/percentage_lost = (rand(5, 15) / 100) - for(var/i in accounts_to_rob) - var/datum/bank_account/B = i - if(!B.being_dumped) - continue - var/amount = B.account_balance * percentage_lost - var/datum/bank_account/account = bogdanoff.get_bank_account() - if (account) // get_bank_account() may return FALSE - account.transfer_money(B, amount) - B.bank_card_talk("You have lost [percentage_lost * 100]% of your funds! A spacecoin credit deposit machine is located at: [get_area(src)].") - addtimer(CALLBACK(src, .proc/dump), 150) //Drain every 15 seconds - -/obj/structure/checkoutmachine/process() - var/anydir = pick(GLOB.cardinals) - if(Process_Spacemove(anydir)) - Move(get_step(src, anydir), anydir) - -/obj/structure/checkoutmachine/proc/stop_dumping() - for(var/i in accounts_to_rob) - var/datum/bank_account/B = i - B.being_dumped = FALSE - -/obj/effect/dumpeetFall //Falling pod - name = "" - icon = 'icons/obj/money_machine_64.dmi' - pixel_z = 300 - desc = "Get out of the way!" - layer = FLY_LAYER//that wasnt flying, that was falling with style! - icon_state = "missile_blur" - -/obj/effect/dumpeetTarget - name = "Landing Zone Indicator" - desc = "A holographic projection designating the landing zone of something. It's probably best to stand back." - icon = 'icons/mob/actions/actions_items.dmi' - icon_state = "sniper_zoom" - layer = PROJECTILE_HIT_THRESHHOLD_LAYER - light_range = 2 - var/obj/effect/dumpeetFall/DF - var/obj/structure/checkoutmachine/dump - var/mob/living/carbon/human/bogdanoff - -/obj/effect/ex_act() - return - -/obj/effect/dumpeetTarget/Initialize(mapload, user) - . = ..() - bogdanoff = user - addtimer(CALLBACK(src, .proc/startLaunch), 100) - sound_to_playing_players('sound/items/dump_it.ogg', 20) - deadchat_broadcast("Protocol CRAB-17 has been activated. A space-coin market has been launched at the station!", turf_target = get_turf(src), message_type=DEADCHAT_ANNOUNCEMENT) - -/obj/effect/dumpeetTarget/proc/startLaunch() - DF = new /obj/effect/dumpeetFall(drop_location()) - dump = new /obj/structure/checkoutmachine(null, bogdanoff) - priority_announce("The spacecoin bubble has popped! Get to the credit deposit machine at [get_area(src)] and cash out before you lose all of your funds!", sender_override = "CRAB-17 Protocol") - animate(DF, pixel_z = -8, time = 5, , easing = LINEAR_EASING) - playsound(src, 'sound/weapons/mortar_whistle.ogg', 70, TRUE, 6) - addtimer(CALLBACK(src, .proc/endLaunch), 5, TIMER_CLIENT_TIME) //Go onto the last step after a very short falling animation - - - -/obj/effect/dumpeetTarget/proc/endLaunch() - QDEL_NULL(DF) //Delete the falling machine effect, because at this point its animation is over. We dont use temp_visual because we want to manually delete it as soon as the pod appears - playsound(src, "explosion", 80, TRUE) - dump.forceMove(get_turf(src)) - qdel(src) //The target's purpose is complete. It can rest easy now +/obj/item/suspiciousphone + name = "suspicious phone" + desc = "This device raises pink levels to unknown highs." + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "suspiciousphone" + w_class = WEIGHT_CLASS_SMALL + attack_verb = list("dumped") + var/dumped = FALSE + +/obj/item/suspiciousphone/attack_self(mob/user) + if(!ishuman(user)) + to_chat(user, "This device is too advanced for you!") + return + if(dumped) + to_chat(user, "You already activated Protocol CRAB-17.") + return FALSE + if(alert(user, "Are you sure you want to crash this market with no survivors?", "Protocol CRAB-17", "Yes", "No") == "Yes") + if(dumped || QDELETED(src)) //Prevents fuckers from cheesing alert + return FALSE + var/turf/targetturf = get_safe_random_station_turf() + if (!targetturf) + return FALSE + new /obj/effect/dumpeetTarget(targetturf, user) + dumped = TRUE + +/obj/structure/checkoutmachine + name = "\improper Nanotrasen Space-Coin Market" + desc = "This is good for spacecoin because" + icon = 'icons/obj/money_machine.dmi' + icon_state = "bogdanoff" + layer = LARGE_MOB_LAYER + armor = list("melee" = 80, "bullet" = 30, "laser" = 30, "energy" = 60, "bomb" = 90, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 80) + density = TRUE + pixel_z = -8 + max_integrity = 5000 + var/list/accounts_to_rob + var/mob/living/carbon/human/bogdanoff + var/canwalk = FALSE + +/obj/structure/checkoutmachine/examine(mob/living/user) + . = ..() + . += "It's integrated integrity meter reads: HEALTH: [obj_integrity]." + +/obj/structure/checkoutmachine/proc/check_if_finished() + for(var/i in accounts_to_rob) + var/datum/bank_account/B = i + if (B.being_dumped) + return FALSE + return TRUE + +/obj/structure/checkoutmachine/attackby(obj/item/W, mob/user, params) + if(check_if_finished()) + qdel(src) + return + if(istype(W, /obj/item/card/id)) + var/obj/item/card/id/card = W + if(!card.registered_account) + to_chat(user, "This card does not have a registered account!") + return + if(!card.registered_account.being_dumped) + to_chat(user, "It appears that your funds are safe from draining!") + return + if(do_after(user, 40, target = src)) + if(!card.registered_account.being_dumped) + return + to_chat(user, "You quickly cash out your funds to a more secure banking location. Funds are safu.") // This is a reference and not a typo + card.registered_account.being_dumped = FALSE + card.registered_account.withdrawDelay = 0 + if(check_if_finished()) + qdel(src) + return + else + return ..() + +/obj/structure/checkoutmachine/Initialize(mapload, mob/living/user) + . = ..() + bogdanoff = user + add_overlay("flaps") + add_overlay("hatch") + add_overlay("legs_retracted") + addtimer(CALLBACK(src, .proc/startUp), 50) + QDEL_IN(src, 8 MINUTES) //Self destruct after 8 min + + +/obj/structure/checkoutmachine/proc/startUp() //very VERY snowflake code that adds a neat animation when the pod lands. + start_dumping() //The machine doesnt move during this time, giving people close by a small window to grab their funds before it starts running around + sleep(10) + if(QDELETED(src)) + return + playsound(src, 'sound/machines/click.ogg', 15, TRUE, -3) + cut_overlay("flaps") + sleep(10) + if(QDELETED(src)) + return + playsound(src, 'sound/machines/click.ogg', 15, TRUE, -3) + cut_overlay("hatch") + sleep(30) + if(QDELETED(src)) + return + playsound(src,'sound/machines/twobeep.ogg',50,FALSE) + var/mutable_appearance/hologram = mutable_appearance(icon, "hologram") + hologram.pixel_y = 16 + add_overlay(hologram) + var/mutable_appearance/holosign = mutable_appearance(icon, "holosign") + holosign.pixel_y = 16 + add_overlay(holosign) + add_overlay("legs_extending") + cut_overlay("legs_retracted") + pixel_z += 4 + sleep(5) + if(QDELETED(src)) + return + add_overlay("legs_extended") + cut_overlay("legs_extending") + pixel_z += 4 + sleep(20) + if(QDELETED(src)) + return + add_overlay("screen_lines") + sleep(5) + if(QDELETED(src)) + return + cut_overlay("screen_lines") + sleep(5) + if(QDELETED(src)) + return + add_overlay("screen_lines") + add_overlay("screen") + sleep(5) + if(QDELETED(src)) + return + playsound(src,'sound/machines/triple_beep.ogg',50,FALSE) + add_overlay("text") + sleep(10) + if(QDELETED(src)) + return + add_overlay("legs") + cut_overlay("legs_extended") + cut_overlay("screen") + add_overlay("screen") + cut_overlay("screen_lines") + add_overlay("screen_lines") + cut_overlay("text") + add_overlay("text") + canwalk = TRUE + START_PROCESSING(SSfastprocess, src) + +/obj/structure/checkoutmachine/Destroy() + stop_dumping() + STOP_PROCESSING(SSfastprocess, src) + priority_announce("The credit deposit machine at [get_area(src)] has been destroyed. Station funds have stopped draining!", sender_override = "CRAB-17 Protocol") + explosion(src, 0,0,1, flame_range = 2) + return ..() + +/obj/structure/checkoutmachine/proc/start_dumping() + accounts_to_rob = SSeconomy.bank_accounts.Copy() + accounts_to_rob -= bogdanoff.get_bank_account() + for(var/i in accounts_to_rob) + var/datum/bank_account/B = i + B.dumpeet() + dump() + +/obj/structure/checkoutmachine/proc/dump() + var/percentage_lost = (rand(5, 15) / 100) + for(var/i in accounts_to_rob) + var/datum/bank_account/B = i + if(!B.being_dumped) + continue + var/amount = B.account_balance * percentage_lost + var/datum/bank_account/account = bogdanoff.get_bank_account() + if (account) // get_bank_account() may return FALSE + account.transfer_money(B, amount) + B.bank_card_talk("You have lost [percentage_lost * 100]% of your funds! A spacecoin credit deposit machine is located at: [get_area(src)].") + addtimer(CALLBACK(src, .proc/dump), 150) //Drain every 15 seconds + +/obj/structure/checkoutmachine/process() + var/anydir = pick(GLOB.cardinals) + if(Process_Spacemove(anydir)) + Move(get_step(src, anydir), anydir) + +/obj/structure/checkoutmachine/proc/stop_dumping() + for(var/i in accounts_to_rob) + var/datum/bank_account/B = i + B.being_dumped = FALSE + +/obj/effect/dumpeetFall //Falling pod + name = "" + icon = 'icons/obj/money_machine_64.dmi' + pixel_z = 300 + desc = "Get out of the way!" + layer = FLY_LAYER//that wasnt flying, that was falling with style! + icon_state = "missile_blur" + +/obj/effect/dumpeetTarget + name = "Landing Zone Indicator" + desc = "A holographic projection designating the landing zone of something. It's probably best to stand back." + icon = 'icons/mob/actions/actions_items.dmi' + icon_state = "sniper_zoom" + layer = PROJECTILE_HIT_THRESHHOLD_LAYER + light_range = 2 + var/obj/effect/dumpeetFall/DF + var/obj/structure/checkoutmachine/dump + var/mob/living/carbon/human/bogdanoff + +/obj/effect/ex_act() + return + +/obj/effect/dumpeetTarget/Initialize(mapload, user) + . = ..() + bogdanoff = user + addtimer(CALLBACK(src, .proc/startLaunch), 100) + sound_to_playing_players('sound/items/dump_it.ogg', 20) + deadchat_broadcast("Protocol CRAB-17 has been activated. A space-coin market has been launched at the station!", turf_target = get_turf(src), message_type=DEADCHAT_ANNOUNCEMENT) + +/obj/effect/dumpeetTarget/proc/startLaunch() + DF = new /obj/effect/dumpeetFall(drop_location()) + dump = new /obj/structure/checkoutmachine(null, bogdanoff) + priority_announce("The spacecoin bubble has popped! Get to the credit deposit machine at [get_area(src)] and cash out before you lose all of your funds!", sender_override = "CRAB-17 Protocol") + animate(DF, pixel_z = -8, time = 5, , easing = LINEAR_EASING) + playsound(src, 'sound/weapons/mortar_whistle.ogg', 70, TRUE, 6) + addtimer(CALLBACK(src, .proc/endLaunch), 5, TIMER_CLIENT_TIME) //Go onto the last step after a very short falling animation + + + +/obj/effect/dumpeetTarget/proc/endLaunch() + QDEL_NULL(DF) //Delete the falling machine effect, because at this point its animation is over. We dont use temp_visual because we want to manually delete it as soon as the pod appears + playsound(src, "explosion", 80, TRUE) + dump.forceMove(get_turf(src)) + qdel(src) //The target's purpose is complete. It can rest easy now diff --git a/code/game/objects/items/crayons.dm b/code/game/objects/items/crayons.dm index 547c3099b3ad..f8f39d7aad82 100644 --- a/code/game/objects/items/crayons.dm +++ b/code/game/objects/items/crayons.dm @@ -144,13 +144,15 @@ to_chat(user, "There is not enough of [src] left!") . = TRUE -/obj/item/toy/crayon/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.hands_state) - // tgui is a plague upon this codebase +/obj/item/toy/crayon/ui_state(mob/user) + return GLOB.hands_state - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/item/toy/crayon/ui_interact(mob/user, datum/tgui/ui) + // tgui is a plague upon this codebase + // no u + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "Crayon", name, 600, 600, - master_ui, state) + ui = new(user, src, "Crayon", name) ui.open() /obj/item/toy/crayon/spraycan/AltClick(mob/user) diff --git a/code/game/objects/items/devices/PDA/PDA.dm b/code/game/objects/items/devices/PDA/PDA.dm index 0f4f3600ccdc..9dc3e909ed07 100644 --- a/code/game/objects/items/devices/PDA/PDA.dm +++ b/code/game/objects/items/devices/PDA/PDA.dm @@ -1,1094 +1,1123 @@ - -//The advanced pea-green monochrome lcd of tomor- YESTARDAY, SCREW THAT, ~Affected - -GLOBAL_LIST_EMPTY(PDAs) - -#define PDA_SCANNER_NONE 0 -#define PDA_SCANNER_MEDICAL 1 -#define PDA_SCANNER_FORENSICS 2 //unused -#define PDA_SCANNER_REAGENT 3 -#define PDA_SCANNER_HALOGEN 4 -#define PDA_SCANNER_GAS 5 -#define PDA_SPAM_DELAY 2 MINUTES - -/obj/item/pda - name = "\improper PDA" - desc = "A portable microcomputer by Thinktronic Systems, LTD. Functionality determined by a preprogrammed ROM cartridge." - icon = 'waspstation/icons/obj/pda.dmi' //WaspStation Edit - Better PDAs from Eris(?) - icon_state = "pda" - item_state = "electronic" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - item_flags = NOBLUDGEON - w_class = WEIGHT_CLASS_TINY - slot_flags = ITEM_SLOT_ID | ITEM_SLOT_BELT - actions_types = list(/datum/action/item_action/toggle_light) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - resistance_flags = FIRE_PROOF | ACID_PROOF - - - //Main variables - var/owner = null // String name of owner - var/default_cartridge = 0 // Access level defined by cartridge - var/obj/item/cartridge/cartridge = null //current cartridge - var/mode = 0 //Controls what menu the PDA will display. 0 is hub; the rest are either built in or based on cartridge. - var/icon_alert = "pda-r" //Icon to be overlayed for message alerts. Taken from the pda icon file. - var/font_index = 0 //This int tells DM which font is currently selected and lets DM know when the last font has been selected so that it can cycle back to the first font when "toggle font" is pressed again. - var/font_mode = "font-family:monospace;" //The currently selected font. - var/background_color = "#808000" //The currently selected background color. - - #define FONT_MONO "font-family:monospace;" - #define FONT_SHARE "font-family:\"Share Tech Mono\", monospace;letter-spacing:0px;" - #define FONT_ORBITRON "font-family:\"Orbitron\", monospace;letter-spacing:0px; font-size:15px" - #define FONT_VT "font-family:\"VT323\", monospace;letter-spacing:1px;" - #define MODE_MONO 0 - #define MODE_SHARE 1 - #define MODE_ORBITRON 2 - #define MODE_VT 3 - - //Secondary variables - var/scanmode = PDA_SCANNER_NONE - var/fon = FALSE //Is the flashlight function on? - var/f_lum = 2.3 //Luminosity for the flashlight function - var/f_pow = 0.6 //Power for the flashlight function - var/f_col = "#FFCC66" //Color for the flashlight function - var/silent = FALSE //To beep or not to beep, that is the question - var/toff = FALSE //If TRUE, messenger disabled - var/tnote = null //Current Texts - var/last_text //No text spamming - var/last_everyone //No text for everyone spamming - var/last_noise //Also no honk spamming that's bad too - var/ttone = "beep" //The ringtone! - var/honkamt = 0 //How many honks left when infected with honk.exe - var/mimeamt = 0 //How many silence left when infected with mime.exe - var/note = "Congratulations, your station has chosen the Thinktronic 5230 Personal Data Assistant!" //Current note in the notepad function - var/notehtml = "" - var/notescanned = FALSE // True if what is in the notekeeper was from a paper. - var/hidden = FALSE // Is the PDA hidden from the PDA list? - var/emped = FALSE - var/equipped = FALSE //used here to determine if this is the first time its been picked up - var/allow_emojis = FALSE //if the pda can send emojis and actually have them parsed as such - var/sort_by_job = FALSE // If this is TRUE, will sort PDA list by job. - - var/obj/item/card/id/id = null //Making it possible to slot an ID card into the PDA so it can function as both. - var/ownjob = null //related to above - - var/obj/item/paicard/pai = null // A slot for a personal AI device - - var/datum/picture/picture //Scanned photo - - var/list/contained_item = list(/obj/item/pen, /obj/item/toy/crayon, /obj/item/lipstick, /obj/item/flashlight/pen, /obj/item/clothing/mask/cigarette) - var/obj/item/inserted_item //Used for pen, crayon, and lipstick insertion or removal. Same as above. - var/overlays_x_offset = 0 //x offset to use for certain overlays - - var/underline_flag = TRUE //flag for underline - -/obj/item/pda/suicide_act(mob/living/carbon/user) - var/deathMessage = msg_input(user) - if (!deathMessage) - deathMessage = "i ded" - user.visible_message("[user] is sending a message to the Grim Reaper! It looks like [user.p_theyre()] trying to commit suicide!") - tnote += "→ To The Grim Reaper:
                    [deathMessage]
                    "//records a message in their PDA as being sent to the grim reaper - return BRUTELOSS - -/obj/item/pda/examine(mob/user) - . = ..() - if(!id && !inserted_item) - return - - if(id) - . += "Alt-click to remove the ID." //won't name ID on examine in case it's stolen - - if(inserted_item && (!isturf(loc))) - . += "Ctrl-click to remove [inserted_item]." //traitor pens are disguised so we're fine naming them on examine - - if((!isnull(cartridge))) - . += "Ctrl+Shift-click to remove the cartridge." //won't name cart on examine in case it's Detomatix - -/obj/item/pda/Initialize() - . = ..() - if(fon) - set_light(f_lum, f_pow, f_col) - - GLOB.PDAs += src - if(default_cartridge) - cartridge = new default_cartridge(src) - if(inserted_item) - inserted_item = new inserted_item(src) - else - inserted_item = new /obj/item/pen(src) - update_icon() - -/obj/item/pda/equipped(mob/user, slot) - . = ..() - if(!equipped) - if(user.client) - background_color = user.client.prefs.pda_color - switch(user.client.prefs.pda_style) - if(MONO) - font_index = MODE_MONO - font_mode = FONT_MONO - if(SHARE) - font_index = MODE_SHARE - font_mode = FONT_SHARE - if(ORBITRON) - font_index = MODE_ORBITRON - font_mode = FONT_ORBITRON - if(VT) - font_index = MODE_VT - font_mode = FONT_VT - else - font_index = MODE_MONO - font_mode = FONT_MONO - equipped = TRUE - -/obj/item/pda/proc/update_label() - name = "PDA-[owner] ([ownjob])" //Name generalisation - -/obj/item/pda/GetAccess() - if(id) - return id.GetAccess() - else - return ..() - -/obj/item/pda/GetID() - return id - -/obj/item/pda/RemoveID() - return do_remove_id() - -/obj/item/pda/InsertID(obj/item/inserting_item) - var/obj/item/card/inserting_id = inserting_item.RemoveID() - if(!inserting_id) - return - insert_id(inserting_id) - if(id == inserting_id) - return TRUE - return FALSE - -/obj/item/pda/update_overlays() - . = ..() - var/mutable_appearance/overlay = new(icon) - overlay.pixel_x = overlays_x_offset - if(id) - overlay.icon_state = "id_overlay" - . += new /mutable_appearance(overlay) - if(inserted_item) - overlay.icon_state = "insert_overlay" - . += new /mutable_appearance(overlay) - if(fon) - overlay.icon_state = "light_overlay" - . += new /mutable_appearance(overlay) - if(pai) - if(pai.pai) - overlay.icon_state = "pai_overlay" - . += new /mutable_appearance(overlay) - else - overlay.icon_state = "pai_off_overlay" - . += new /mutable_appearance(overlay) - -/obj/item/pda/MouseDrop(mob/over, src_location, over_location) - var/mob/M = usr - if((M == over) && usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return attack_self(M) - return ..() - -/obj/item/pda/attack_self_tk(mob/user) - to_chat(user, "The PDA's capacitive touch screen doesn't seem to respond!") - return - -/obj/item/pda/interact(mob/user) - if(!user.IsAdvancedToolUser()) - to_chat(user, "You don't have the dexterity to do this!") - return - - ..() - - var/datum/asset/spritesheet/assets = get_asset_datum(/datum/asset/spritesheet/simple/pda) - assets.send(user) - - var/datum/asset/spritesheet/emoji_s = get_asset_datum(/datum/asset/spritesheet/goonchat) - emoji_s.send(user) //Already sent by chat but no harm doing this - - user.set_machine(src) - - var/dat = "" // WaspStation Edit - PDA Redesign - dat += assets.css_tag() - dat += emoji_s.css_tag() - - dat += "[PDAIMG(refresh)] Refresh" - - if((!isnull(cartridge)) && (mode == 0)) - dat += " | [PDAIMG(eject)] Eject [cartridge]" - if(mode) - dat += " | [PDAIMG(menu)] Return" - - dat += "
                    " - - if(!owner) - dat += "Warning: No owner information entered. Please swipe card.

                    " - dat += "[PDAIMG(refresh)] Retry" - else - switch (mode) - if(0) - dat += "

                    PERSONAL DATA ASSISTANT v.1.2

                    " - dat += "Owner: [owner], [ownjob]
                    " - - if(id) - dat += text("ID: [id ? "[id.registered_name], [id.assignment]" : "----------"] [id ? "Update PDA Info" : ""]

                    ") - - dat += "[worldtime2text()]
                    " //:[world.time / 100 % 6][world.time / 100 % 10]" - dat += "[time2text(world.realtime, "MMM DD")] [GLOB.year_integer+540]" - dat += "

                    " - dat += "

                    General Functions

                    " - dat += "
                      " - dat += "
                    • [PDAIMG(notes)] Notekeeper
                    • " - dat += "
                    • [PDAIMG(mail)] Messenger
                    • " - dat += "
                    • [PDAIMG(notes)] View Crew Manifest
                    • " - - if(cartridge) - if(cartridge.access) - dat += "

                      Job Specific Functions

                      " - if(cartridge.access & CART_CLOWN) - dat += "
                    • [PDAIMG(honk)] Honk Synthesizer
                    • " - dat += "
                    • [PDAIMG(honk)] Sad Trombone
                    • " - if(cartridge.access & CART_STATUS_DISPLAY) - dat += "
                    • [PDAIMG(status)] Set Status Display
                    • " - if(cartridge.access & CART_ENGINE) - dat += "
                    • [PDAIMG(power)] Power Monitor
                    • " - if(cartridge.access & CART_MEDICAL) - dat += "
                    • [PDAIMG(medical)] Medical Records
                    • " - if(cartridge.access & CART_SECURITY) - dat += "
                    • [PDAIMG(cuffs)] Security Records
                    • " - if(cartridge.access & CART_QUARTERMASTER) - dat += "
                    • [PDAIMG(crate)] Supply Records
                    • " - - dat += "

                      Utilities

                      " - if(cartridge) - if(cartridge.bot_access_flags) - dat += "
                    • [PDAIMG(medbot)]Bots Access
                    • " - if (cartridge.access & CART_JANITOR) - dat += "
                    • [PDAIMG(bucket)]Custodial Locator
                    • " - if(cartridge.access & CART_MIME) - dat += "
                    • [PDAIMG(emoji)]Emoji Guidebook
                    • " - if (istype(cartridge.radio)) - dat += "
                    • [PDAIMG(signaler)]Signaler System
                    • " - if (cartridge.access & CART_NEWSCASTER) - dat += "
                    • [PDAIMG(notes)]Newscaster Access
                    • " - if (cartridge.access & CART_REAGENT_SCANNER) - dat += "
                    • [PDAIMG(reagent)][scanmode == 3 ? "Disable" : "Enable"] Reagent Scanner
                    • " - if (cartridge.access & CART_ENGINE) - dat += "
                    • [PDAIMG(reagent)][scanmode == 4 ? "Disable" : "Enable"] Halogen Counter
                    • " - if (cartridge.access & CART_ATMOS) - dat += "
                    • [PDAIMG(reagent)][scanmode == 5 ? "Disable" : "Enable"] Gas Scanner
                    • " - if (cartridge.access & CART_REMOTE_DOOR) - dat += "
                    • [PDAIMG(rdoor)]Toggle Remote Door
                    • " - if (cartridge.access & CART_DRONEPHONE) - dat += "
                    • [PDAIMG(dronephone)]Drone Phone
                    • " - dat += "
                    • [PDAIMG(atmos)]Atmospheric Scan
                    • " - dat += "
                    • [PDAIMG(flashlight)][fon ? "Disable" : "Enable"] Flashlight
                    • " - if (pai) - if(pai.loc != src) - pai = null - update_icon() - else - dat += "
                    • [PDAIMG(status)] pAI Device Configuration
                    • " - dat += "
                    • [PDAIMG(status)] Eject pAI Device
                    • " - - if(1) - dat += "

                      [PDAIMG(notes)] Notekeeper V2.2

                      " - dat += "Edit
                      " - if(notescanned) - dat += "(This is a scanned image, editing it may cause some text formatting to change.)
                      " - dat += "
                      [(!notehtml ? note : notehtml)]" - - if(2) - dat += "

                      [PDAIMG(mail)] SpaceMessenger V3.9.6

                      " - dat += "[PDAIMG(bell)] Ringer: [silent == 1 ? "Off" : "On"]
                      " - dat += "[PDAIMG(mail)] Send / Receive: [toff == 1 ? "Off" : "On"]
                      " - dat += "[PDAIMG(bell)] Set Ringtone
                      " - dat += "[PDAIMG(mail)] View Message Log
                      " - - if(cartridge) - dat += cartridge.message_header() - - dat += "

                      [PDAIMG(mail)] Detected PDAs

                      " - - dat += "
                        " - var/count = 0 - - if(!toff) - for (var/obj/item/pda/P in sortNames(get_viewable_pdas())) - if(P == src) - continue - dat += "
                      • [P.owner] ([P.ownjob])" - if(cartridge) - dat += cartridge.message_special(P) - dat += "
                      • " - count++ - dat += "
                      " - if(count == 0) - dat += "None detected.
                      " - else if(cartridge && cartridge.spam_enabled) - dat += "[PDAIMG(mail)] Send To All" - - if(21) - dat += "

                      [PDAIMG(mail)] SpaceMessenger V3.9.6

                      " - dat += "[PDAIMG(blank)] Clear Messages" - - dat += "[PDAIMG(mail)] Messages" - - dat += tnote - dat += "
                      " - - if(41) //crew manifest - dat += "

                      Crew Manifest

                      " - dat += "
                      " - dat += GLOB.data_core.get_manifest_html() - dat += "
                      " - - if(3) - dat += "

                      [PDAIMG(atmos)] Atmospheric Readings

                      " - - var/turf/T = user.loc - if(isnull(T)) - dat += "Unable to obtain a reading.
                      " - else - var/datum/gas_mixture/environment = T.return_air() - - var/pressure = environment.return_pressure() - var/total_moles = environment.total_moles() - - dat += "Air Pressure: [round(pressure,0.1)] kPa
                      " - - if (total_moles) - for(var/id in environment.get_gases()) - var/gas_level = environment.get_moles(id)/total_moles - if(gas_level > 0) - dat += "[GLOB.meta_gas_info[id][META_GAS_NAME]]: [round(gas_level*100, 0.01)]%
                      " - - dat += "Temperature: [round(environment.return_temperature()-T0C)]°C
                      " - dat += "
                      " - else//Else it links to the cart menu proc. Although, it really uses menu hub 4--menu 4 doesn't really exist as it simply redirects to hub. - dat += cartridge.generate_menu() - - var/datum/browser/popup = new(user, "pda_ui", "
                      Personal Data Assistant
                      ", 500, 600) - popup.set_content(dat) - popup.open(0) - -/obj/item/pda/Topic(href, href_list) - ..() - var/mob/living/U = usr - //Looking for master was kind of pointless since PDAs don't appear to have one. - - if(usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK) && !href_list["close"]) - add_fingerprint(U) - U.set_machine(src) - - switch(href_list["choice"]) - -//BASIC FUNCTIONS=================================== - - if("Refresh")//Refresh, goes to the end of the proc. - - if("Return")//Return - if(mode<=9) - mode = 0 - else - mode = round(mode/10) - if(mode==4 || mode == 5)//Fix for cartridges. Redirects to hub. - mode = 0 - if("Authenticate")//Checks for ID - id_check(U) - if("UpdateInfo") - ownjob = id.assignment - if(istype(id, /obj/item/card/id/syndicate)) - owner = id.registered_name - update_label() - if("Eject")//Ejects the cart, only done from hub. - if(!isnull(cartridge)) - U.put_in_hands(cartridge) - to_chat(U, "You remove [cartridge] from [src].") - scanmode = 0 - cartridge.host_pda = null - cartridge = null - update_icon() - -//MENU FUNCTIONS=================================== - - if("0")//Hub - mode = 0 - if("1")//Notes - mode = 1 - if("2")//Messenger - mode = 2 - if("21")//Read messeges - mode = 21 - if("3")//Atmos scan - mode = 3 - if("4")//Redirects to hub - mode = 0 - -//MAIN FUNCTIONS=================================== - - if("Light") - toggle_light(U) - if("Medical Scan") - if(scanmode == PDA_SCANNER_MEDICAL) - scanmode = PDA_SCANNER_NONE - else if((!isnull(cartridge)) && (cartridge.access & CART_MEDICAL)) - scanmode = PDA_SCANNER_MEDICAL - if("Reagent Scan") - if(scanmode == PDA_SCANNER_REAGENT) - scanmode = PDA_SCANNER_NONE - else if((!isnull(cartridge)) && (cartridge.access & CART_REAGENT_SCANNER)) - scanmode = PDA_SCANNER_REAGENT - if("Halogen Counter") - if(scanmode == PDA_SCANNER_HALOGEN) - scanmode = PDA_SCANNER_NONE - else if((!isnull(cartridge)) && (cartridge.access & CART_ENGINE)) - scanmode = PDA_SCANNER_HALOGEN - if("Honk") - if( !(last_noise && world.time < last_noise + 20) ) - playsound(loc, 'sound/items/bikehorn.ogg', 50, 1) - last_noise = world.time - if("Trombone") - if( !(last_noise && world.time < last_noise + 20) ) - playsound(loc, 'sound/misc/sadtrombone.ogg', 50, 1) - last_noise = world.time - if("Gas Scan") - if(scanmode == PDA_SCANNER_GAS) - scanmode = PDA_SCANNER_NONE - else if((!isnull(cartridge)) && (cartridge.access & CART_ATMOS)) - scanmode = PDA_SCANNER_GAS - if("Drone Phone") - var/alert_s = input(U,"Alert severity level","Ping Drones",null) as null|anything in list("Low","Medium","High","Critical") - var/area/A = get_area(U) - if(A && alert_s && !QDELETED(U)) - var/msg = "NON-DRONE PING: [U.name]: [alert_s] priority alert in [A.name]!" - _alert_drones(msg, TRUE, U) - to_chat(U, msg) - - -//NOTEKEEPER FUNCTIONS=================================== - - if("Edit") - var/n = stripped_multiline_input(U, "Please enter message", name, note) - if(in_range(src, U) && loc == U) - if(mode == 1 && n) - note = n - notehtml = parsemarkdown(n, U) - notescanned = FALSE - else - U << browse(null, "window=pda") - return - -//MESSENGER FUNCTIONS=================================== - - if("Toggle Messenger") - toff = !toff - if("Toggle Ringer")//If viewing texts then erase them, if not then toggle silent status - silent = !silent - if("Clear")//Clears messages - tnote = null - if("Ringtone") - var/t = stripped_input(U, "Please enter new ringtone", name, ttone, 20) - if(in_range(src, U) && loc == U && t) - if(SEND_SIGNAL(src, COMSIG_PDA_CHANGE_RINGTONE, U, t) & COMPONENT_STOP_RINGTONE_CHANGE) - U << browse(null, "window=pda") - return - else - ttone = t - else - U << browse(null, "window=pda") - return - if("Message") - create_message(U, locate(href_list["target"]) in GLOB.PDAs) - - if("Sorting Mode") - sort_by_job = !sort_by_job - - if("MessageAll") - send_to_all(U) - - if("cart") - if(cartridge) - cartridge.special(U, href_list) - else - U << browse(null, "window=pda") - return - -//SYNDICATE FUNCTIONS=================================== - - if("Toggle Door") - if(cartridge && cartridge.access & CART_REMOTE_DOOR) - for(var/obj/machinery/door/poddoor/M in GLOB.machines) - if(M.id == cartridge.remote_door_id) - if(M.density) - M.open() - else - M.close() - -//pAI FUNCTIONS=================================== - if("pai") - switch(href_list["option"]) - if("1") // Configure pAI device - pai.attack_self(U) - if("2") // Eject pAI device - usr.put_in_hands(pai) - to_chat(usr, "You remove the pAI from the [name].") - -//LINK FUNCTIONS=================================== - - else//Cartridge menu linking - mode = max(text2num(href_list["choice"]), 0) - - else//If not in range, can't interact or not using the pda. - U.unset_machine() - U << browse(null, "window=pda") - return - -//EXTRA FUNCTIONS=================================== - - if(mode == 2 || mode == 21)//To clear message overlays. - update_icon() - - if((honkamt > 0) && (prob(60)))//For clown virus. - honkamt-- - playsound(src, 'sound/items/bikehorn.ogg', 30, TRUE) - - if(U.machine == src && href_list["skiprefresh"]!="1")//Final safety. - attack_self(U)//It auto-closes the menu prior if the user is not in range and so on. - else - U.unset_machine() - U << browse(null, "window=pda") - return - -/obj/item/pda/proc/remove_id(mob/user) - if(issilicon(user) || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - do_remove_id(user) - - -/obj/item/pda/proc/do_remove_id(mob/user) - if(!id) - return - if(user) - user.put_in_hands(id) - to_chat(user, "You remove the ID from the [name].") - else - id.forceMove(get_turf(src)) - - . = id - id = null - updateSelfDialog() - update_icon() - - if(ishuman(loc)) - var/mob/living/carbon/human/H = loc - if(H.wear_id == src) - H.sec_hud_set_ID() - - -/obj/item/pda/proc/msg_input(mob/living/U = usr) - var/t = stripped_input(U, "Please enter message", name, null, MAX_MESSAGE_LEN) - if(!t || toff) - return - if(!in_range(src, U) && loc != U) - return - if(emped) - t = Gibberish(t, TRUE) - return t - -/obj/item/pda/proc/send_message(mob/living/user, list/obj/item/pda/targets, everyone) - var/message = msg_input(user) - if(!message || !targets.len) - return - if((last_text && world.time < last_text + 10) || (everyone && last_everyone && world.time < last_everyone + PDA_SPAM_DELAY)) - return - if(prob(1)) - message += "\nSent from my PDA" - // Send the signal - var/list/string_targets = list() - for (var/obj/item/pda/P in targets) - if (P.owner && P.ownjob) // != src is checked by the UI - string_targets += "[P.owner] ([P.ownjob])" - for (var/obj/machinery/computer/message_monitor/M in targets) - // In case of "Reply" to a message from a console, this will make the - // message be logged successfully. If the console is impersonating - // someone by matching their name and job, the reply will reach the - // impersonated PDA. - string_targets += "[M.customsender] ([M.customjob])" - if (!string_targets.len) - return - - var/datum/signal/subspace/messaging/pda/signal = new(src, list( - "name" = "[owner]", - "job" = "[ownjob]", - "message" = message, - "targets" = string_targets, - "emojis" = allow_emojis, - )) - if (picture) - signal.data["photo"] = picture - signal.send_to_receivers() - - // If it didn't reach, note that fact - if (!signal.data["done"]) - to_chat(user, "ERROR: Server isn't responding.") - return - - var/target_text = signal.format_target() - if(allow_emojis) - message = emoji_parse(message)//already sent- this just shows the sent emoji as one to the sender in the to_chat - signal.data["message"] = emoji_parse(signal.data["message"]) - - // Log it in our logs - tnote += "→ To [target_text]:
                      [signal.format_message()]
                      " - // Show it to ghosts - var/ghost_message = "[owner] PDA Message --> [target_text]: [signal.format_message()]" - for(var/mob/M in GLOB.player_list) - if(isobserver(M) && (M.client.prefs.chat_toggles & CHAT_GHOSTPDA)) - to_chat(M, "[FOLLOW_LINK(M, user)] [ghost_message]") - // Log in the talk log - user.log_talk(message, LOG_PDA, tag="PDA: [initial(name)] to [target_text]") - to_chat(user, "PDA message sent to [target_text]: \"[message]\"") - // Reset the photo - picture = null - last_text = world.time - if (everyone) - last_everyone = world.time - -/obj/item/pda/proc/receive_message(datum/signal/subspace/messaging/pda/signal) - tnote += "← From [signal.data["name"]] ([signal.data["job"]]):
                      [signal.format_message()]
                      " - - if(!silent) - playsound(loc, 'sound/machines/twobeep.ogg', 50, 1) - audible_message("[icon2html(src, hearers(src))] *[ttone]*", null, 3) - //Search for holder of the PDA. - var/mob/living/L = null - if(loc && isliving(loc)) - L = loc - //Maybe they are a pAI! - else - L = get(src, /mob/living/silicon) - - if(L && L.stat != UNCONSCIOUS) - var/reply = "(Reply)" - var/hrefstart - var/hrefend - if (isAI(L)) - hrefstart = "" - hrefend = "" - - if(signal.data["automated"]) - reply = "\[Automated Message\]" - - var/inbound_message = signal.format_message() - if(signal.data["emojis"] == TRUE)//so will not parse emojis as such from pdas that don't send emojis - inbound_message = emoji_parse(inbound_message) - - to_chat(L, "[icon2html(src)] PDA message from [hrefstart][signal.data["name"]] ([signal.data["job"]])[hrefend], [inbound_message] [reply]") - - update_icon() - add_overlay(icon_alert) - -/obj/item/pda/proc/send_to_all(mob/living/U) - if (last_everyone && world.time < last_everyone + PDA_SPAM_DELAY) - to_chat(U,"Send To All function is still on cooldown.") - return - send_message(U,get_viewable_pdas(), TRUE) - -/obj/item/pda/proc/create_message(mob/living/U, obj/item/pda/P) - send_message(U,list(P)) - -/obj/item/pda/AltClick(mob/user) - ..() - - if(id) - remove_id(user) - else - remove_pen(user) - -/obj/item/pda/CtrlClick(mob/user) - ..() - - if(isturf(loc)) //stops the user from dragging the PDA by ctrl-clicking it. - return - - remove_pen(user) - -/obj/item/pda/CtrlShiftClick(mob/user) - ..() - eject_cart(user) - -/obj/item/pda/verb/verb_toggle_light() - set name = "Toggle light" - set category = "Object" - set src in oview(1) - - toggle_light(usr) - -/obj/item/pda/verb/verb_remove_id() - set category = "Object" - set name = "Eject ID" - set src in usr - - if(id) - remove_id(usr) - else - to_chat(usr, "This PDA does not have an ID in it!") - - if(usr.canUseTopic(src)) - if(id) - remove_id() - else - to_chat(usr, "This PDA does not have an ID in it!") - -/obj/item/pda/verb/verb_remove_pen() - set category = "Object" - set name = "Remove Pen" - set src in usr - - remove_pen(usr) - -/obj/item/pda/verb/verb_eject_cart() - set category = "Object" - set name = "Eject Cartridge" - set src in usr - - eject_cart(usr) - -/obj/item/pda/proc/toggle_light(mob/user) - if(issilicon(user) || !user.canUseTopic(src, BE_CLOSE)) - return - if(fon) - fon = FALSE - set_light(0) - else if(f_lum) - fon = TRUE - set_light(f_lum, f_pow, f_col) - update_icon() - for(var/X in actions) - var/datum/action/A = X - A.UpdateButtonIcon() - -/obj/item/pda/proc/remove_pen(mob/user) - - if(issilicon(user) || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) //TK doesn't work even with this removed but here for readability - return - - if(inserted_item) - user.put_in_hands(inserted_item) - to_chat(user, "You remove [inserted_item] from [src].") - inserted_item = null - update_icon() - else - to_chat(user, "This PDA does not have a pen in it!") - -/obj/item/pda/proc/eject_cart(mob/user) - if(issilicon(user) || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) //TK disabled to stop cartridge teleporting into hand - return - if (!isnull(cartridge)) - user.put_in_hands(cartridge) - to_chat(user, "You eject [cartridge] from [src].") - scanmode = PDA_SCANNER_NONE - cartridge.host_pda = null - cartridge = null - updateSelfDialog() - update_icon() - -//trying to insert or remove an id -/obj/item/pda/proc/id_check(mob/user, obj/item/card/id/I) - if(!I) - if(id && (src in user.contents)) - remove_id(user) - return TRUE - else - var/obj/item/card/id/C = user.get_active_held_item() - if(istype(C)) - I = C - - if(I?.registered_name) - if(!user.transferItemToLoc(I, src)) - return FALSE - insert_id(I, user) - update_icon() - return TRUE - - -/obj/item/pda/proc/insert_id(obj/item/card/id/inserting_id, mob/user) - var/obj/old_id = id - id = inserting_id - if(ishuman(loc)) - var/mob/living/carbon/human/human_wearer = loc - if(human_wearer.wear_id == src) - human_wearer.sec_hud_set_ID() - if(old_id) - if(user) - user.put_in_hands(old_id) - else - old_id.forceMove(get_turf(src)) - - -// access to status display signals -/obj/item/pda/attackby(obj/item/C, mob/user, params) - if(istype(C, /obj/item/cartridge)) - if(!user.transferItemToLoc(C, src)) - return - eject_cart(user) - cartridge = C - cartridge.host_pda = src - to_chat(user, "You insert [cartridge] into [src].") - updateSelfDialog() - update_icon() - - else if(istype(C, /obj/item/card/id)) - var/obj/item/card/id/idcard = C - if(!idcard.registered_name) - to_chat(user, "\The [src] rejects the ID!") - return - if(!owner) - owner = idcard.registered_name - ownjob = idcard.assignment - update_label() - to_chat(user, "Card scanned.") - else - if(!id_check(user, idcard)) - return - to_chat(user, "You put the ID into \the [src]'s slot.") - updateSelfDialog()//Update self dialog on success. - - return //Return in case of failed check or when successful. - updateSelfDialog()//For the non-input related code. - else if(istype(C, /obj/item/paicard) && !pai) - if(!user.transferItemToLoc(C, src)) - return - pai = C - to_chat(user, "You slot \the [C] into [src].") - update_icon() - updateUsrDialog() - else if(is_type_in_list(C, contained_item)) //Checks if there is a pen - if(inserted_item) - to_chat(user, "There is already \a [inserted_item] in \the [src]!") - else - if(!user.transferItemToLoc(C, src)) - return - to_chat(user, "You slide \the [C] into \the [src].") - inserted_item = C - update_icon() - else if(istype(C, /obj/item/photo)) - var/obj/item/photo/P = C - picture = P.picture - to_chat(user, "You scan \the [C].") - else - return ..() - -/obj/item/pda/attack(mob/living/carbon/C, mob/living/user) - if(istype(C)) - switch(scanmode) - - if(PDA_SCANNER_MEDICAL) - C.visible_message("[user] analyzes [C]'s vitals.") - healthscan(user, C, 1) - add_fingerprint(user) - - if(PDA_SCANNER_HALOGEN) - C.visible_message("[user] analyzes [C]'s radiation levels.") - - user.show_message("Analyzing Results for [C]:") - if(C.radiation) - user.show_message("\green Radiation Level: \black [C.radiation]") - else - user.show_message("No radiation detected.") - -/obj/item/pda/afterattack(atom/A as mob|obj|turf|area, mob/user, proximity) - . = ..() - if(!proximity) - return - switch(scanmode) - if(PDA_SCANNER_REAGENT) - if(!isnull(A.reagents)) - if(A.reagents.reagent_list.len > 0) - var/reagents_length = A.reagents.reagent_list.len - to_chat(user, "[reagents_length] chemical agent[reagents_length > 1 ? "s" : ""] found.") - for (var/re in A.reagents.reagent_list) - to_chat(user, "\t [re]") - else - to_chat(user, "No active chemical agents found in [A].") - else - to_chat(user, "No significant chemical agents found in [A].") - - if(5) - if(istype(A, /obj/item/tank)) - var/obj/item/tank/T = A - atmosanalyzer_scan(T.air_contents, user, T) - else if(istype(A, /obj/machinery/portable_atmospherics)) - var/obj/machinery/portable_atmospherics/PA = A - atmosanalyzer_scan(PA.air_contents, user, PA) - else if(istype(A, /obj/machinery/atmospherics/pipe)) - var/obj/machinery/atmospherics/pipe/P = A - atmosanalyzer_scan(P.parent.air, user, P) - else if(istype(A, /obj/machinery/power/rad_collector)) - var/obj/machinery/power/rad_collector/RC = A - if(RC.loaded_tank) - atmosanalyzer_scan(RC.loaded_tank.air_contents, user, RC) - else if(istype(A, /obj/item/flamethrower)) - var/obj/item/flamethrower/F = A - if(F.ptank) - atmosanalyzer_scan(F.ptank.air_contents, user, F) - - if(!scanmode && istype(A, /obj/item/paper) && owner) - var/obj/item/paper/PP = A - if(!PP.info) - to_chat(user, "Unable to scan! Paper is blank.") - return - notehtml = PP.info - note = replacetext(notehtml, "
                      ", "\[br\]") - note = replacetext(note, "
                    • ", "\[*\]") - note = replacetext(note, "
                        ", "\[list\]") - note = replacetext(note, "
                      ", "\[/list\]") - note = html_encode(note) - notescanned = TRUE - to_chat(user, "Paper scanned. Saved to PDA's notekeeper." ) - - -/obj/item/pda/proc/explode() //This needs tuning. - var/turf/T = get_turf(src) - - if(ismob(loc)) - var/mob/M = loc - M.show_message("Your [src] explodes!", MSG_VISUAL, "You hear a loud *pop*!", MSG_AUDIBLE) - else - visible_message("[src] explodes!", "You hear a loud *pop*!") - - if(T) - T.hotspot_expose(700,125) - if(istype(cartridge, /obj/item/cartridge/virus/syndicate)) - explosion(T, -1, 1, 3, 4) - else - explosion(T, -1, -1, 2, 3) - qdel(src) - return - -/obj/item/pda/Destroy() - GLOB.PDAs -= src - if(istype(id)) - QDEL_NULL(id) - if(istype(cartridge)) - QDEL_NULL(cartridge) - if(istype(pai)) - QDEL_NULL(pai) - if(istype(inserted_item)) - QDEL_NULL(inserted_item) - return ..() - -//AI verb and proc for sending PDA messages. - -/obj/item/pda/ai/verb/cmd_toggle_pda_receiver() - set category = "AI Commands" - set name = "PDA - Toggle Sender/Receiver" - - if(usr.stat == DEAD) - return //won't work if dead - var/mob/living/silicon/S = usr - if(istype(S) && !isnull(S.aiPDA)) - S.aiPDA.toff = !S.aiPDA.toff - to_chat(usr, "PDA sender/receiver toggled [(S.aiPDA.toff ? "Off" : "On")]!") - else - to_chat(usr, "You do not have a PDA. You should make an issue report about this.") - -/obj/item/pda/ai/verb/cmd_toggle_pda_silent() - set category = "AI Commands" - set name = "PDA - Toggle Ringer" - - if(usr.stat == DEAD) - return //won't work if dead - var/mob/living/silicon/S = usr - if(istype(S) && !isnull(S.aiPDA)) - //0 - S.aiPDA.silent = !S.aiPDA.silent - to_chat(usr, "PDA ringer toggled [(S.aiPDA.silent ? "Off" : "On")]!") - else - to_chat(usr, "You do not have a PDA. You should make an issue report about this.") - -/mob/living/silicon/proc/cmd_send_pdamesg(mob/user) - var/list/plist = list() - var/list/namecounts = list() - - if(aiPDA.toff) - to_chat(user, "Turn on your receiver in order to send messages.") - return - - for (var/obj/item/pda/P in get_viewable_pdas()) - if(P == src) - continue - else if(P == src.aiPDA) - continue - - plist[avoid_assoc_duplicate_keys(P.owner, namecounts)] = P - - var/c = input(user, "Please select a PDA") as null|anything in sortList(plist) - - if(!c) - return - - var/selected = plist[c] - - if(aicamera.stored.len) - var/add_photo = input(user,"Do you want to attach a photo?","Photo","No") as null|anything in list("Yes","No") - if(add_photo=="Yes") - var/datum/picture/Pic = aicamera.selectpicture(user) - aiPDA.picture = Pic - - if(incapacitated()) - return - - aiPDA.create_message(src, selected) - -/mob/living/silicon/proc/cmd_show_message_log(mob/user) - if(incapacitated()) - return - if(!isnull(aiPDA)) - var/HTML = "AI PDA Message Log[aiPDA.tnote]" - user << browse(HTML, "window=log;size=400x444;border=1;can_resize=1;can_close=1;can_minimize=0") - else - to_chat(user, "You do not have a PDA! You should make an issue report about this.") - -// Pass along the pulse to atoms in contents, largely added so pAIs are vulnerable to EMP -/obj/item/pda/emp_act(severity) - . = ..() - if (!(. & EMP_PROTECT_CONTENTS)) - for(var/atom/A in src) - A.emp_act(severity) - if (!(. & EMP_PROTECT_SELF)) - emped++ - addtimer(CALLBACK(src, .proc/emp_end), 200 * severity) - -/obj/item/pda/proc/emp_end() - emped-- - -/proc/get_viewable_pdas(sort_by_job = FALSE) - . = list() - // Returns a list of PDAs which can be viewed from another PDA/message monitor., - var/sortmode - if(sort_by_job) - sortmode = /proc/cmp_pdajob_asc - else - sortmode = /proc/cmp_pdaname_asc - - for(var/obj/item/pda/P in sortList(GLOB.PDAs, sortmode)) - if(!P.owner || P.toff || P.hidden) - continue - . += P - -/obj/item/pda/proc/pda_no_detonate() - return COMPONENT_PDA_NO_DETONATE - -#undef PDA_SCANNER_NONE -#undef PDA_SCANNER_MEDICAL -#undef PDA_SCANNER_FORENSICS -#undef PDA_SCANNER_REAGENT -#undef PDA_SCANNER_HALOGEN -#undef PDA_SCANNER_GAS -#undef PDA_SPAM_DELAY + +//The advanced pea-green monochrome lcd of tomor- YESTARDAY, SCREW THAT, ~Affected + +GLOBAL_LIST_EMPTY(PDAs) + +#define PDA_SCANNER_NONE 0 +#define PDA_SCANNER_MEDICAL 1 +#define PDA_SCANNER_FORENSICS 2 //unused +#define PDA_SCANNER_REAGENT 3 +#define PDA_SCANNER_HALOGEN 4 +#define PDA_SCANNER_GAS 5 +#define PDA_SPAM_DELAY 2 MINUTES + +/obj/item/pda + name = "\improper PDA" + desc = "A portable microcomputer by Thinktronic Systems, LTD. Functionality determined by a preprogrammed ROM cartridge." + icon = 'waspstation/icons/obj/pda.dmi' //WaspStation Edit - Better PDAs from Eris(?) + icon_state = "pda" + item_state = "electronic" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + item_flags = NOBLUDGEON + w_class = WEIGHT_CLASS_TINY + slot_flags = ITEM_SLOT_ID | ITEM_SLOT_BELT + actions_types = list(/datum/action/item_action/toggle_light) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + resistance_flags = FIRE_PROOF | ACID_PROOF + + + //Main variables + var/owner = null // String name of owner + var/default_cartridge = 0 // Access level defined by cartridge + var/obj/item/cartridge/cartridge = null //current cartridge + var/mode = 0 //Controls what menu the PDA will display. 0 is hub; the rest are either built in or based on cartridge. + var/icon_alert = "pda-r" //Icon to be overlayed for message alerts. Taken from the pda icon file. + var/font_index = 0 //This int tells DM which font is currently selected and lets DM know when the last font has been selected so that it can cycle back to the first font when "toggle font" is pressed again. + var/font_mode = "font-family:monospace;" //The currently selected font. + var/background_color = "#808000" //The currently selected background color. + + #define FONT_MONO "font-family:monospace;" + #define FONT_SHARE "font-family:\"Share Tech Mono\", monospace;letter-spacing:0px;" + #define FONT_ORBITRON "font-family:\"Orbitron\", monospace;letter-spacing:0px; font-size:15px" + #define FONT_VT "font-family:\"VT323\", monospace;letter-spacing:1px;" + #define MODE_MONO 0 + #define MODE_SHARE 1 + #define MODE_ORBITRON 2 + #define MODE_VT 3 + + //Secondary variables + var/scanmode = PDA_SCANNER_NONE + var/fon = FALSE //Is the flashlight function on? + var/f_lum = 2.3 //Luminosity for the flashlight function + var/f_pow = 0.6 //Power for the flashlight function + var/f_col = "#FFCC66" //Color for the flashlight function + var/silent = FALSE //To beep or not to beep, that is the question + var/toff = FALSE //If TRUE, messenger disabled + var/tnote = null //Current Texts + var/last_text //No text spamming + var/last_everyone //No text for everyone spamming + var/last_noise //Also no honk spamming that's bad too + var/ttone = "beep" //The ringtone! + var/honkamt = 0 //How many honks left when infected with honk.exe + var/mimeamt = 0 //How many silence left when infected with mime.exe + var/note = "Congratulations, your station has chosen the Thinktronic 5230 Personal Data Assistant!" //Current note in the notepad function + var/notehtml = "" + var/notescanned = FALSE // True if what is in the notekeeper was from a paper. + var/hidden = FALSE // Is the PDA hidden from the PDA list? + var/emped = FALSE + var/equipped = FALSE //used here to determine if this is the first time its been picked up + var/allow_emojis = FALSE //if the pda can send emojis and actually have them parsed as such + var/sort_by_job = FALSE // If this is TRUE, will sort PDA list by job. + + var/obj/item/card/id/id = null //Making it possible to slot an ID card into the PDA so it can function as both. + var/ownjob = null //related to above + + var/obj/item/paicard/pai = null // A slot for a personal AI device + + var/datum/picture/picture //Scanned photo + + var/list/contained_item = list(/obj/item/pen, /obj/item/toy/crayon, /obj/item/lipstick, /obj/item/flashlight/pen, /obj/item/clothing/mask/cigarette) + var/obj/item/inserted_item //Used for pen, crayon, and lipstick insertion or removal. Same as above. + var/overlays_x_offset = 0 //x offset to use for certain overlays + + var/underline_flag = TRUE //flag for underline + +/obj/item/pda/suicide_act(mob/living/carbon/user) + var/deathMessage = msg_input(user) + if (!deathMessage) + deathMessage = "i ded" + user.visible_message("[user] is sending a message to the Grim Reaper! It looks like [user.p_theyre()] trying to commit suicide!") + tnote += "→ To The Grim Reaper:
                      [deathMessage]
                      "//records a message in their PDA as being sent to the grim reaper + return BRUTELOSS + +/obj/item/pda/examine(mob/user) + . = ..() + if(!id && !inserted_item) + return + + if(id) + . += "Alt-click to remove the ID." //won't name ID on examine in case it's stolen + + if(inserted_item && (!isturf(loc))) + . += "Ctrl-click to remove [inserted_item]." //traitor pens are disguised so we're fine naming them on examine + + if((!isnull(cartridge))) + . += "Ctrl+Shift-click to remove the cartridge." //won't name cart on examine in case it's Detomatix + +/obj/item/pda/Initialize() + . = ..() + if(fon) + set_light(f_lum, f_pow, f_col) + + GLOB.PDAs += src + if(default_cartridge) + cartridge = new default_cartridge(src) + if(inserted_item) + inserted_item = new inserted_item(src) + else + inserted_item = new /obj/item/pen(src) + update_icon() + +/obj/item/pda/equipped(mob/user, slot) + . = ..() + if(!equipped) + if(user.client) + background_color = user.client.prefs.pda_color + switch(user.client.prefs.pda_style) + if(MONO) + font_index = MODE_MONO + font_mode = FONT_MONO + if(SHARE) + font_index = MODE_SHARE + font_mode = FONT_SHARE + if(ORBITRON) + font_index = MODE_ORBITRON + font_mode = FONT_ORBITRON + if(VT) + font_index = MODE_VT + font_mode = FONT_VT + else + font_index = MODE_MONO + font_mode = FONT_MONO + equipped = TRUE + +/obj/item/pda/proc/update_label() + name = "PDA-[owner] ([ownjob])" //Name generalisation + +/obj/item/pda/GetAccess() + if(id) + return id.GetAccess() + else + return ..() + +/obj/item/pda/GetID() + return id + +/obj/item/pda/RemoveID() + return do_remove_id() + +/obj/item/pda/InsertID(obj/item/inserting_item) + var/obj/item/card/inserting_id = inserting_item.RemoveID() + if(!inserting_id) + return + insert_id(inserting_id) + if(id == inserting_id) + return TRUE + return FALSE + +/obj/item/pda/update_overlays() + . = ..() + var/mutable_appearance/overlay = new(icon) + overlay.pixel_x = overlays_x_offset + if(id) + overlay.icon_state = "id_overlay" + . += new /mutable_appearance(overlay) + if(inserted_item) + overlay.icon_state = "insert_overlay" + . += new /mutable_appearance(overlay) + if(fon) + overlay.icon_state = "light_overlay" + . += new /mutable_appearance(overlay) + if(pai) + if(pai.pai) + overlay.icon_state = "pai_overlay" + . += new /mutable_appearance(overlay) + else + overlay.icon_state = "pai_off_overlay" + . += new /mutable_appearance(overlay) + +/obj/item/pda/MouseDrop(mob/over, src_location, over_location) + var/mob/M = usr + if((M == over) && usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return attack_self(M) + return ..() + +/obj/item/pda/attack_self_tk(mob/user) + to_chat(user, "The PDA's capacitive touch screen doesn't seem to respond!") + return + +/obj/item/pda/interact(mob/user) + if(!user.IsAdvancedToolUser()) + to_chat(user, "You don't have the dexterity to do this!") + return + + ..() + + var/datum/asset/spritesheet/assets = get_asset_datum(/datum/asset/spritesheet/simple/pda) + assets.send(user) + + var/datum/asset/spritesheet/emoji_s = get_asset_datum(/datum/asset/spritesheet/goonchat) + emoji_s.send(user) //Already sent by chat but no harm doing this + + user.set_machine(src) + + var/dat = "" // WaspStation Edit - PDA Redesign + dat += assets.css_tag() + dat += emoji_s.css_tag() + + dat += "[PDAIMG(refresh)] Refresh" + + if((!isnull(cartridge)) && (mode == 0)) + dat += " | [PDAIMG(eject)] Eject [cartridge]" + if(mode) + dat += " | [PDAIMG(menu)] Return" + + dat += "
                      " + + if(!owner) + dat += "Warning: No owner information entered. Please swipe card.

                      " + dat += "[PDAIMG(refresh)] Retry" + else + switch (mode) + if(0) + dat += "

                      PERSONAL DATA ASSISTANT v.1.2

                      " + dat += "Owner: [owner], [ownjob]
                      " + + if(id) + dat += text("ID: [id ? "[id.registered_name], [id.assignment]" : "----------"] [id ? "Update PDA Info" : ""]

                      ") + + dat += "[worldtime2text()]
                      " //:[world.time / 100 % 6][world.time / 100 % 10]" + dat += "[time2text(world.realtime, "MMM DD")] [GLOB.year_integer+540]" + dat += "

                      " + dat += "

                      General Functions

                      " + dat += "
                        " + dat += "
                      • [PDAIMG(notes)] Notekeeper
                      • " + dat += "
                      • [PDAIMG(mail)] Messenger
                      • " + dat += "
                      • [PDAIMG(notes)] View Crew Manifest
                      • " + dat += "
                      • [PDAIMG(skills)]Skill Tracker
                      • " + + if(cartridge) + if(cartridge.access) + dat += "

                        Job Specific Functions

                        " + if(cartridge.access & CART_CLOWN) + dat += "
                      • [PDAIMG(honk)] Honk Synthesizer
                      • " + dat += "
                      • [PDAIMG(honk)] Sad Trombone
                      • " + if(cartridge.access & CART_STATUS_DISPLAY) + dat += "
                      • [PDAIMG(status)] Set Status Display
                      • " + if(cartridge.access & CART_ENGINE) + dat += "
                      • [PDAIMG(power)] Power Monitor
                      • " + if(cartridge.access & CART_MEDICAL) + dat += "
                      • [PDAIMG(medical)] Medical Records
                      • " + if(cartridge.access & CART_SECURITY) + dat += "
                      • [PDAIMG(cuffs)] Security Records
                      • " + if(cartridge.access & CART_QUARTERMASTER) + dat += "
                      • [PDAIMG(crate)] Supply Records
                      • " + + dat += "

                        Utilities

                        " + if(cartridge) + if(cartridge.bot_access_flags) + dat += "
                      • [PDAIMG(medbot)]Bots Access
                      • " + if (cartridge.access & CART_JANITOR) + dat += "
                      • [PDAIMG(bucket)]Custodial Locator
                      • " + if(cartridge.access & CART_MIME) + dat += "
                      • [PDAIMG(emoji)]Emoji Guidebook
                      • " + if (istype(cartridge.radio)) + dat += "
                      • [PDAIMG(signaler)]Signaler System
                      • " + if (cartridge.access & CART_NEWSCASTER) + dat += "
                      • [PDAIMG(notes)]Newscaster Access
                      • " + if (cartridge.access & CART_REAGENT_SCANNER) + dat += "
                      • [PDAIMG(reagent)][scanmode == 3 ? "Disable" : "Enable"] Reagent Scanner
                      • " + if (cartridge.access & CART_ENGINE) + dat += "
                      • [PDAIMG(reagent)][scanmode == 4 ? "Disable" : "Enable"] Halogen Counter
                      • " + if (cartridge.access & CART_ATMOS) + dat += "
                      • [PDAIMG(reagent)][scanmode == 5 ? "Disable" : "Enable"] Gas Scanner
                      • " + if (cartridge.access & CART_REMOTE_DOOR) + dat += "
                      • [PDAIMG(rdoor)]Toggle Remote Door
                      • " + if (cartridge.access & CART_DRONEPHONE) + dat += "
                      • [PDAIMG(dronephone)]Drone Phone
                      • " + dat += "
                      • [PDAIMG(atmos)]Atmospheric Scan
                      • " + dat += "
                      • [PDAIMG(flashlight)][fon ? "Disable" : "Enable"] Flashlight
                      • " + if (pai) + if(pai.loc != src) + pai = null + update_icon() + else + dat += "
                      • [PDAIMG(status)] pAI Device Configuration
                      • " + dat += "
                      • [PDAIMG(status)] Eject pAI Device
                      • " + + if(1) + dat += "

                        [PDAIMG(notes)] Notekeeper V2.2

                        " + dat += "Edit
                        " + if(notescanned) + dat += "(This is a scanned image, editing it may cause some text formatting to change.)
                        " + dat += "
                        [(!notehtml ? note : notehtml)]" + + if(2) + dat += "

                        [PDAIMG(mail)] SpaceMessenger V3.9.6

                        " + dat += "[PDAIMG(bell)] Ringer: [silent == 1 ? "Off" : "On"]
                        " + dat += "[PDAIMG(mail)] Send / Receive: [toff == 1 ? "Off" : "On"]
                        " + dat += "[PDAIMG(bell)] Set Ringtone
                        " + dat += "[PDAIMG(mail)] View Message Log
                        " + + if(cartridge) + dat += cartridge.message_header() + + dat += "

                        [PDAIMG(mail)] Detected PDAs

                        " + + dat += "
                          " + var/count = 0 + + if(!toff) + for (var/obj/item/pda/P in sortNames(get_viewable_pdas())) + if(P == src) + continue + dat += "
                        • [P.owner] ([P.ownjob])" + if(cartridge) + dat += cartridge.message_special(P) + dat += "
                        • " + count++ + dat += "
                        " + if(count == 0) + dat += "None detected.
                        " + else if(cartridge && cartridge.spam_enabled) + dat += "[PDAIMG(mail)] Send To All" + + if(6) + dat += "

                        [PDAIMG(mail)] ExperTrak® Skill Tracker V4.26.2

                        " + dat += "Thank you for choosing ExperTrak® brand software! ExperTrak® inc. is proud to be a NanoTrasen employee expertise and effectiveness department subsidary!" + dat += "

                        This software is designed to track and monitor your skill development as a NanoTrasen employee. Your job performance across different fields has been quantified and categorized below.
                        " + var/datum/mind/targetmind = user.mind + for (var/type in GLOB.skill_types) + var/datum/skill/S = GetSkillRef(type) + var/lvl_num = targetmind.get_skill_level(type) + var/lvl_name = uppertext(targetmind.get_skill_level_name(type)) + var/exp = targetmind.get_skill_exp(type) + var/xp_prog_to_level = targetmind.exp_needed_to_level_up(type) + var/xp_req_to_level = 0 + if (xp_prog_to_level)//is it even possible to level up? + xp_req_to_level = SKILL_EXP_LIST[lvl_num+1] - SKILL_EXP_LIST[lvl_num] + dat += "
                        [S.name]" + dat += "
                        [S.desc]" + dat += "
                        • EMPLOYEE SKILL LEVEL: [lvl_name]" + if (exp && xp_req_to_level) + var/progress_percent = (xp_req_to_level-xp_prog_to_level)/xp_req_to_level + var/overall_percent = exp / SKILL_EXP_LIST[length(SKILL_EXP_LIST)] + dat += "
                          PROGRESS TO NEXT SKILL LEVEL:" + dat += "
                          " + num2loadingbar(progress_percent) + "([progress_percent*100])%" + dat += "
                          OVERALL DEVELOPMENT PROGRESS:" + dat += "
                          " + num2loadingbar(overall_percent) + "([overall_percent*100])%" + if (lvl_num >= length(SKILL_EXP_LIST) && !(type in targetmind.skills_rewarded)) + dat += "
                          Contact the Professional [S.title] Association" + dat += "
                        " + + if(21) + dat += "

                        [PDAIMG(mail)] SpaceMessenger V3.9.6

                        " + dat += "[PDAIMG(blank)] Clear Messages" + + dat += "[PDAIMG(mail)] Messages" + + dat += tnote + dat += "
                        " + + if(41) //crew manifest + dat += "

                        Crew Manifest

                        " + dat += "
                        " + dat += GLOB.data_core.get_manifest_html() + dat += "
                        " + + if(3) + dat += "

                        [PDAIMG(atmos)] Atmospheric Readings

                        " + + var/turf/T = user.loc + if(isnull(T)) + dat += "Unable to obtain a reading.
                        " + else + var/datum/gas_mixture/environment = T.return_air() + + var/pressure = environment.return_pressure() + var/total_moles = environment.total_moles() + + dat += "Air Pressure: [round(pressure,0.1)] kPa
                        " + + if (total_moles) + for(var/id in environment.get_gases()) + var/gas_level = environment.get_moles(id)/total_moles + if(gas_level > 0) + dat += "[GLOB.meta_gas_info[id][META_GAS_NAME]]: [round(gas_level*100, 0.01)]%
                        " + + dat += "Temperature: [round(environment.return_temperature()-T0C)]°C
                        " + dat += "
                        " + else//Else it links to the cart menu proc. Although, it really uses menu hub 4--menu 4 doesn't really exist as it simply redirects to hub. + dat += cartridge.generate_menu() + + var/datum/browser/popup = new(user, "pda_ui", "
                        Personal Data Assistant
                        ", 500, 600) + popup.set_content(dat) + popup.open(0) + +/obj/item/pda/Topic(href, href_list) + ..() + var/mob/living/U = usr + //Looking for master was kind of pointless since PDAs don't appear to have one. + + if(usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK) && !href_list["close"]) + add_fingerprint(U) + U.set_machine(src) + + switch(href_list["choice"]) + +//BASIC FUNCTIONS=================================== + + if("Refresh")//Refresh, goes to the end of the proc. + + if("Return")//Return + if(mode<=9) + mode = 0 + else + mode = round(mode/10) + if(mode==4 || mode == 5)//Fix for cartridges. Redirects to hub. + mode = 0 + if("Authenticate")//Checks for ID + id_check(U) + if("UpdateInfo") + ownjob = id.assignment + if(istype(id, /obj/item/card/id/syndicate)) + owner = id.registered_name + update_label() + if("Eject")//Ejects the cart, only done from hub. + if(!isnull(cartridge)) + U.put_in_hands(cartridge) + to_chat(U, "You remove [cartridge] from [src].") + scanmode = 0 + cartridge.host_pda = null + cartridge = null + update_icon() + +//MENU FUNCTIONS=================================== + + if("0")//Hub + mode = 0 + if("1")//Notes + mode = 1 + if("2")//Messenger + mode = 2 + if("21")//Read messeges + mode = 21 + if("3")//Atmos scan + mode = 3 + if("4")//Redirects to hub + mode = 0 + +//MAIN FUNCTIONS=================================== + + if("Light") + toggle_light(U) + if("Medical Scan") + if(scanmode == PDA_SCANNER_MEDICAL) + scanmode = PDA_SCANNER_NONE + else if((!isnull(cartridge)) && (cartridge.access & CART_MEDICAL)) + scanmode = PDA_SCANNER_MEDICAL + if("Reagent Scan") + if(scanmode == PDA_SCANNER_REAGENT) + scanmode = PDA_SCANNER_NONE + else if((!isnull(cartridge)) && (cartridge.access & CART_REAGENT_SCANNER)) + scanmode = PDA_SCANNER_REAGENT + if("Halogen Counter") + if(scanmode == PDA_SCANNER_HALOGEN) + scanmode = PDA_SCANNER_NONE + else if((!isnull(cartridge)) && (cartridge.access & CART_ENGINE)) + scanmode = PDA_SCANNER_HALOGEN + if("Honk") + if( !(last_noise && world.time < last_noise + 20) ) + playsound(loc, 'sound/items/bikehorn.ogg', 50, 1) + last_noise = world.time + if("Trombone") + if( !(last_noise && world.time < last_noise + 20) ) + playsound(loc, 'sound/misc/sadtrombone.ogg', 50, 1) + last_noise = world.time + if("Gas Scan") + if(scanmode == PDA_SCANNER_GAS) + scanmode = PDA_SCANNER_NONE + else if((!isnull(cartridge)) && (cartridge.access & CART_ATMOS)) + scanmode = PDA_SCANNER_GAS + if("Drone Phone") + var/alert_s = input(U,"Alert severity level","Ping Drones",null) as null|anything in list("Low","Medium","High","Critical") + var/area/A = get_area(U) + if(A && alert_s && !QDELETED(U)) + var/msg = "NON-DRONE PING: [U.name]: [alert_s] priority alert in [A.name]!" + _alert_drones(msg, TRUE, U) + to_chat(U, msg) + + +//NOTEKEEPER FUNCTIONS=================================== + + if("Edit") + var/n = stripped_multiline_input(U, "Please enter message", name, note) + if(in_range(src, U) && loc == U) + if(mode == 1 && n) + note = n + notehtml = parsemarkdown(n, U) + notescanned = FALSE + else + U << browse(null, "window=pda") + return + +//MESSENGER FUNCTIONS=================================== + + if("Toggle Messenger") + toff = !toff + if("Toggle Ringer")//If viewing texts then erase them, if not then toggle silent status + silent = !silent + if("Clear")//Clears messages + tnote = null + if("Ringtone") + var/t = stripped_input(U, "Please enter new ringtone", name, ttone, 20) + if(in_range(src, U) && loc == U && t) + if(SEND_SIGNAL(src, COMSIG_PDA_CHANGE_RINGTONE, U, t) & COMPONENT_STOP_RINGTONE_CHANGE) + U << browse(null, "window=pda") + return + else + ttone = t + else + U << browse(null, "window=pda") + return + if("Message") + create_message(U, locate(href_list["target"]) in GLOB.PDAs) + + if("Sorting Mode") + sort_by_job = !sort_by_job + + if("MessageAll") + send_to_all(U) + + if("cart") + if(cartridge) + cartridge.special(U, href_list) + else + U << browse(null, "window=pda") + return + +//SYNDICATE FUNCTIONS=================================== + + if("Toggle Door") + if(cartridge && cartridge.access & CART_REMOTE_DOOR) + for(var/obj/machinery/door/poddoor/M in GLOB.machines) + if(M.id == cartridge.remote_door_id) + if(M.density) + M.open() + else + M.close() + +//pAI FUNCTIONS=================================== + if("pai") + switch(href_list["option"]) + if("1") // Configure pAI device + pai.attack_self(U) + if("2") // Eject pAI device + usr.put_in_hands(pai) + to_chat(usr, "You remove the pAI from the [name].") + +//LINK FUNCTIONS=================================== + + else//Cartridge menu linking + mode = max(text2num(href_list["choice"]), 0) + + else//If not in range, can't interact or not using the pda. + U.unset_machine() + U << browse(null, "window=pda") + return + +//EXTRA FUNCTIONS=================================== + + if(mode == 2 || mode == 21)//To clear message overlays. + update_icon() + + if((honkamt > 0) && (prob(60)))//For clown virus. + honkamt-- + playsound(src, 'sound/items/bikehorn.ogg', 30, TRUE) + + if(U.machine == src && href_list["skiprefresh"]!="1")//Final safety. + attack_self(U)//It auto-closes the menu prior if the user is not in range and so on. + else + U.unset_machine() + U << browse(null, "window=pda") + return + +/obj/item/pda/proc/remove_id(mob/user) + if(issilicon(user) || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + do_remove_id(user) + + +/obj/item/pda/proc/do_remove_id(mob/user) + if(!id) + return + if(user) + user.put_in_hands(id) + to_chat(user, "You remove the ID from the [name].") + else + id.forceMove(get_turf(src)) + + . = id + id = null + updateSelfDialog() + update_icon() + + if(ishuman(loc)) + var/mob/living/carbon/human/H = loc + if(H.wear_id == src) + H.sec_hud_set_ID() + + +/obj/item/pda/proc/msg_input(mob/living/U = usr) + var/t = stripped_input(U, "Please enter message", name, null, MAX_MESSAGE_LEN) + if(!t || toff) + return + if(!in_range(src, U) && loc != U) + return + if(emped) + t = Gibberish(t, TRUE) + return t + +/obj/item/pda/proc/send_message(mob/living/user, list/obj/item/pda/targets, everyone) + var/message = msg_input(user) + if(!message || !targets.len) + return + if((last_text && world.time < last_text + 10) || (everyone && last_everyone && world.time < last_everyone + PDA_SPAM_DELAY)) + return + if(prob(1)) + message += "\nSent from my PDA" + // Send the signal + var/list/string_targets = list() + for (var/obj/item/pda/P in targets) + if (P.owner && P.ownjob) // != src is checked by the UI + string_targets += "[P.owner] ([P.ownjob])" + for (var/obj/machinery/computer/message_monitor/M in targets) + // In case of "Reply" to a message from a console, this will make the + // message be logged successfully. If the console is impersonating + // someone by matching their name and job, the reply will reach the + // impersonated PDA. + string_targets += "[M.customsender] ([M.customjob])" + if (!string_targets.len) + return + + var/datum/signal/subspace/messaging/pda/signal = new(src, list( + "name" = "[owner]", + "job" = "[ownjob]", + "message" = message, + "targets" = string_targets, + "emojis" = allow_emojis, + )) + if (picture) + signal.data["photo"] = picture + signal.send_to_receivers() + + // If it didn't reach, note that fact + if (!signal.data["done"]) + to_chat(user, "ERROR: Server isn't responding.") + return + + var/target_text = signal.format_target() + if(allow_emojis) + message = emoji_parse(message)//already sent- this just shows the sent emoji as one to the sender in the to_chat + signal.data["message"] = emoji_parse(signal.data["message"]) + + // Log it in our logs + tnote += "→ To [target_text]:
                        [signal.format_message()]
                        " + // Show it to ghosts + var/ghost_message = "[owner] PDA Message --> [target_text]: [signal.format_message()]" + for(var/mob/M in GLOB.player_list) + if(isobserver(M) && (M.client.prefs.chat_toggles & CHAT_GHOSTPDA)) + to_chat(M, "[FOLLOW_LINK(M, user)] [ghost_message]") + // Log in the talk log + user.log_talk(message, LOG_PDA, tag="PDA: [initial(name)] to [target_text]") + to_chat(user, "PDA message sent to [target_text]: \"[message]\"") + // Reset the photo + picture = null + last_text = world.time + if (everyone) + last_everyone = world.time + +/obj/item/pda/proc/receive_message(datum/signal/subspace/messaging/pda/signal) + tnote += "← From [signal.data["name"]] ([signal.data["job"]]):
                        [signal.format_message()]
                        " + + if(!silent) + playsound(loc, 'sound/machines/twobeep.ogg', 50, 1) + audible_message("[icon2html(src, hearers(src))] *[ttone]*", null, 3) + //Search for holder of the PDA. + var/mob/living/L = null + if(loc && isliving(loc)) + L = loc + //Maybe they are a pAI! + else + L = get(src, /mob/living/silicon) + + if(L && L.stat != UNCONSCIOUS) + var/reply = "(Reply)" + var/hrefstart + var/hrefend + if (isAI(L)) + hrefstart = "" + hrefend = "" + + if(signal.data["automated"]) + reply = "\[Automated Message\]" + + var/inbound_message = signal.format_message() + if(signal.data["emojis"] == TRUE)//so will not parse emojis as such from pdas that don't send emojis + inbound_message = emoji_parse(inbound_message) + + to_chat(L, "[icon2html(src)] PDA message from [hrefstart][signal.data["name"]] ([signal.data["job"]])[hrefend], [inbound_message] [reply]") + + update_icon() + add_overlay(icon_alert) + +/obj/item/pda/proc/send_to_all(mob/living/U) + if (last_everyone && world.time < last_everyone + PDA_SPAM_DELAY) + to_chat(U,"Send To All function is still on cooldown.") + return + send_message(U,get_viewable_pdas(), TRUE) + +/obj/item/pda/proc/create_message(mob/living/U, obj/item/pda/P) + send_message(U,list(P)) + +/obj/item/pda/AltClick(mob/user) + ..() + + if(id) + remove_id(user) + else + remove_pen(user) + +/obj/item/pda/CtrlClick(mob/user) + ..() + + if(isturf(loc)) //stops the user from dragging the PDA by ctrl-clicking it. + return + + remove_pen(user) + +/obj/item/pda/CtrlShiftClick(mob/user) + ..() + eject_cart(user) + +/obj/item/pda/verb/verb_toggle_light() + set name = "Toggle light" + set category = "Object" + set src in oview(1) + + toggle_light(usr) + +/obj/item/pda/verb/verb_remove_id() + set category = "Object" + set name = "Eject ID" + set src in usr + + if(id) + remove_id(usr) + else + to_chat(usr, "This PDA does not have an ID in it!") + + if(usr.canUseTopic(src)) + if(id) + remove_id() + else + to_chat(usr, "This PDA does not have an ID in it!") + +/obj/item/pda/verb/verb_remove_pen() + set category = "Object" + set name = "Remove Pen" + set src in usr + + remove_pen(usr) + +/obj/item/pda/verb/verb_eject_cart() + set category = "Object" + set name = "Eject Cartridge" + set src in usr + + eject_cart(usr) + +/obj/item/pda/proc/toggle_light(mob/user) + if(issilicon(user) || !user.canUseTopic(src, BE_CLOSE)) + return + if(fon) + fon = FALSE + set_light(0) + else if(f_lum) + fon = TRUE + set_light(f_lum, f_pow, f_col) + update_icon() + for(var/X in actions) + var/datum/action/A = X + A.UpdateButtonIcon() + +/obj/item/pda/proc/remove_pen(mob/user) + + if(issilicon(user) || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) //TK doesn't work even with this removed but here for readability + return + + if(inserted_item) + user.put_in_hands(inserted_item) + to_chat(user, "You remove [inserted_item] from [src].") + inserted_item = null + update_icon() + else + to_chat(user, "This PDA does not have a pen in it!") + +/obj/item/pda/proc/eject_cart(mob/user) + if(issilicon(user) || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) //TK disabled to stop cartridge teleporting into hand + return + if (!isnull(cartridge)) + user.put_in_hands(cartridge) + to_chat(user, "You eject [cartridge] from [src].") + scanmode = PDA_SCANNER_NONE + cartridge.host_pda = null + cartridge = null + updateSelfDialog() + update_icon() + +//trying to insert or remove an id +/obj/item/pda/proc/id_check(mob/user, obj/item/card/id/I) + if(!I) + if(id && (src in user.contents)) + remove_id(user) + return TRUE + else + var/obj/item/card/id/C = user.get_active_held_item() + if(istype(C)) + I = C + + if(I?.registered_name) + if(!user.transferItemToLoc(I, src)) + return FALSE + insert_id(I, user) + update_icon() + return TRUE + + +/obj/item/pda/proc/insert_id(obj/item/card/id/inserting_id, mob/user) + var/obj/old_id = id + id = inserting_id + if(ishuman(loc)) + var/mob/living/carbon/human/human_wearer = loc + if(human_wearer.wear_id == src) + human_wearer.sec_hud_set_ID() + if(old_id) + if(user) + user.put_in_hands(old_id) + else + old_id.forceMove(get_turf(src)) + + +// access to status display signals +/obj/item/pda/attackby(obj/item/C, mob/user, params) + if(istype(C, /obj/item/cartridge)) + if(!user.transferItemToLoc(C, src)) + return + eject_cart(user) + cartridge = C + cartridge.host_pda = src + to_chat(user, "You insert [cartridge] into [src].") + updateSelfDialog() + update_icon() + + else if(istype(C, /obj/item/card/id)) + var/obj/item/card/id/idcard = C + if(!idcard.registered_name) + to_chat(user, "\The [src] rejects the ID!") + return + if(!owner) + owner = idcard.registered_name + ownjob = idcard.assignment + update_label() + to_chat(user, "Card scanned.") + else + if(!id_check(user, idcard)) + return + to_chat(user, "You put the ID into \the [src]'s slot.") + updateSelfDialog()//Update self dialog on success. + + return //Return in case of failed check or when successful. + updateSelfDialog()//For the non-input related code. + else if(istype(C, /obj/item/paicard) && !pai) + if(!user.transferItemToLoc(C, src)) + return + pai = C + to_chat(user, "You slot \the [C] into [src].") + update_icon() + updateUsrDialog() + else if(is_type_in_list(C, contained_item)) //Checks if there is a pen + if(inserted_item) + to_chat(user, "There is already \a [inserted_item] in \the [src]!") + else + if(!user.transferItemToLoc(C, src)) + return + to_chat(user, "You slide \the [C] into \the [src].") + inserted_item = C + update_icon() + else if(istype(C, /obj/item/photo)) + var/obj/item/photo/P = C + picture = P.picture + to_chat(user, "You scan \the [C].") + else + return ..() + +/obj/item/pda/attack(mob/living/carbon/C, mob/living/user) + if(istype(C)) + switch(scanmode) + + if(PDA_SCANNER_MEDICAL) + C.visible_message("[user] analyzes [C]'s vitals.") + healthscan(user, C, 1) + add_fingerprint(user) + + if(PDA_SCANNER_HALOGEN) + C.visible_message("[user] analyzes [C]'s radiation levels.") + + user.show_message("Analyzing Results for [C]:") + if(C.radiation) + user.show_message("\green Radiation Level: \black [C.radiation]") + else + user.show_message("No radiation detected.") + +/obj/item/pda/afterattack(atom/A as mob|obj|turf|area, mob/user, proximity) + . = ..() + if(!proximity) + return + switch(scanmode) + if(PDA_SCANNER_REAGENT) + if(!isnull(A.reagents)) + if(A.reagents.reagent_list.len > 0) + var/reagents_length = A.reagents.reagent_list.len + to_chat(user, "[reagents_length] chemical agent[reagents_length > 1 ? "s" : ""] found.") + for (var/re in A.reagents.reagent_list) + to_chat(user, "\t [re]") + else + to_chat(user, "No active chemical agents found in [A].") + else + to_chat(user, "No significant chemical agents found in [A].") + + if(5) + if(istype(A, /obj/item/tank)) + var/obj/item/tank/T = A + atmosanalyzer_scan(T.air_contents, user, T) + else if(istype(A, /obj/machinery/portable_atmospherics)) + var/obj/machinery/portable_atmospherics/PA = A + atmosanalyzer_scan(PA.air_contents, user, PA) + else if(istype(A, /obj/machinery/atmospherics/pipe)) + var/obj/machinery/atmospherics/pipe/P = A + atmosanalyzer_scan(P.parent.air, user, P) + else if(istype(A, /obj/machinery/power/rad_collector)) + var/obj/machinery/power/rad_collector/RC = A + if(RC.loaded_tank) + atmosanalyzer_scan(RC.loaded_tank.air_contents, user, RC) + else if(istype(A, /obj/item/flamethrower)) + var/obj/item/flamethrower/F = A + if(F.ptank) + atmosanalyzer_scan(F.ptank.air_contents, user, F) + + if(!scanmode && istype(A, /obj/item/paper) && owner) + var/obj/item/paper/PP = A + if(!PP.info) + to_chat(user, "Unable to scan! Paper is blank.") + return + notehtml = PP.info + note = replacetext(notehtml, "
                        ", "\[br\]") + note = replacetext(note, "
                      • ", "\[*\]") + note = replacetext(note, "
                          ", "\[list\]") + note = replacetext(note, "
                        ", "\[/list\]") + note = html_encode(note) + notescanned = TRUE + to_chat(user, "Paper scanned. Saved to PDA's notekeeper." ) + + +/obj/item/pda/proc/explode() //This needs tuning. + var/turf/T = get_turf(src) + + if(ismob(loc)) + var/mob/M = loc + M.show_message("Your [src] explodes!", MSG_VISUAL, "You hear a loud *pop*!", MSG_AUDIBLE) + else + visible_message("[src] explodes!", "You hear a loud *pop*!") + + if(T) + T.hotspot_expose(700,125) + if(istype(cartridge, /obj/item/cartridge/virus/syndicate)) + explosion(T, -1, 1, 3, 4) + else + explosion(T, -1, -1, 2, 3) + qdel(src) + return + +/obj/item/pda/Destroy() + GLOB.PDAs -= src + if(istype(id)) + QDEL_NULL(id) + if(istype(cartridge)) + QDEL_NULL(cartridge) + if(istype(pai)) + QDEL_NULL(pai) + if(istype(inserted_item)) + QDEL_NULL(inserted_item) + return ..() + +//AI verb and proc for sending PDA messages. + +/obj/item/pda/ai/verb/cmd_toggle_pda_receiver() + set category = "AI Commands" + set name = "PDA - Toggle Sender/Receiver" + + if(usr.stat == DEAD) + return //won't work if dead + var/mob/living/silicon/S = usr + if(istype(S) && !isnull(S.aiPDA)) + S.aiPDA.toff = !S.aiPDA.toff + to_chat(usr, "PDA sender/receiver toggled [(S.aiPDA.toff ? "Off" : "On")]!") + else + to_chat(usr, "You do not have a PDA. You should make an issue report about this.") + +/obj/item/pda/ai/verb/cmd_toggle_pda_silent() + set category = "AI Commands" + set name = "PDA - Toggle Ringer" + + if(usr.stat == DEAD) + return //won't work if dead + var/mob/living/silicon/S = usr + if(istype(S) && !isnull(S.aiPDA)) + //0 + S.aiPDA.silent = !S.aiPDA.silent + to_chat(usr, "PDA ringer toggled [(S.aiPDA.silent ? "Off" : "On")]!") + else + to_chat(usr, "You do not have a PDA. You should make an issue report about this.") + +/mob/living/silicon/proc/cmd_send_pdamesg(mob/user) + var/list/plist = list() + var/list/namecounts = list() + + if(aiPDA.toff) + to_chat(user, "Turn on your receiver in order to send messages.") + return + + for (var/obj/item/pda/P in get_viewable_pdas()) + if(P == src) + continue + else if(P == src.aiPDA) + continue + + plist[avoid_assoc_duplicate_keys(P.owner, namecounts)] = P + + var/c = input(user, "Please select a PDA") as null|anything in sortList(plist) + + if(!c) + return + + var/selected = plist[c] + + if(aicamera.stored.len) + var/add_photo = input(user,"Do you want to attach a photo?","Photo","No") as null|anything in list("Yes","No") + if(add_photo=="Yes") + var/datum/picture/Pic = aicamera.selectpicture(user) + aiPDA.picture = Pic + + if(incapacitated()) + return + + aiPDA.create_message(src, selected) + +/mob/living/silicon/proc/cmd_show_message_log(mob/user) + if(incapacitated()) + return + if(!isnull(aiPDA)) + var/HTML = "AI PDA Message Log[aiPDA.tnote]" + user << browse(HTML, "window=log;size=400x444;border=1;can_resize=1;can_close=1;can_minimize=0") + else + to_chat(user, "You do not have a PDA! You should make an issue report about this.") + +// Pass along the pulse to atoms in contents, largely added so pAIs are vulnerable to EMP +/obj/item/pda/emp_act(severity) + . = ..() + if (!(. & EMP_PROTECT_CONTENTS)) + for(var/atom/A in src) + A.emp_act(severity) + if (!(. & EMP_PROTECT_SELF)) + emped++ + addtimer(CALLBACK(src, .proc/emp_end), 200 * severity) + +/obj/item/pda/proc/emp_end() + emped-- + +/proc/get_viewable_pdas(sort_by_job = FALSE) + . = list() + // Returns a list of PDAs which can be viewed from another PDA/message monitor., + var/sortmode + if(sort_by_job) + sortmode = /proc/cmp_pdajob_asc + else + sortmode = /proc/cmp_pdaname_asc + + for(var/obj/item/pda/P in sortList(GLOB.PDAs, sortmode)) + if(!P.owner || P.toff || P.hidden) + continue + . += P + +/obj/item/pda/proc/pda_no_detonate() + return COMPONENT_PDA_NO_DETONATE + +#undef PDA_SCANNER_NONE +#undef PDA_SCANNER_MEDICAL +#undef PDA_SCANNER_FORENSICS +#undef PDA_SCANNER_REAGENT +#undef PDA_SCANNER_HALOGEN +#undef PDA_SCANNER_GAS +#undef PDA_SPAM_DELAY diff --git a/code/game/objects/items/devices/PDA/cart.dm b/code/game/objects/items/devices/PDA/cart.dm index 0790691bb0f5..0bc2d97eb806 100644 --- a/code/game/objects/items/devices/PDA/cart.dm +++ b/code/game/objects/items/devices/PDA/cart.dm @@ -1,743 +1,743 @@ -#define CART_SECURITY (1<<0) -#define CART_ENGINE (1<<1) -#define CART_ATMOS (1<<2) -#define CART_MEDICAL (1<<3) -#define CART_CLOWN (1<<5) -#define CART_MIME (1<<6) -#define CART_JANITOR (1<<7) -#define CART_REAGENT_SCANNER (1<<8) -#define CART_NEWSCASTER (1<<9) -#define CART_REMOTE_DOOR (1<<10) -#define CART_STATUS_DISPLAY (1<<11) -#define CART_QUARTERMASTER (1<<12) -#define CART_HYDROPONICS (1<<13) -#define CART_DRONEPHONE (1<<14) - - -/obj/item/cartridge - name = "generic cartridge" - desc = "A data cartridge for portable microcomputers." - icon = 'icons/obj/pda.dmi' - icon_state = "cart" - item_state = "electronic" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - w_class = WEIGHT_CLASS_TINY - - var/obj/item/integrated_signaler/radio = null - - var/access = 0 //Bit flags for cartridge access - - var/remote_door_id = "" - - var/bot_access_flags = 0 //Bit flags. Selection: SEC_BOT | MULE_BOT | FLOOR_BOT | CLEAN_BOT | MED_BOT | FIRE_BOT - var/spam_enabled = 0 //Enables "Send to All" Option - - var/obj/item/pda/host_pda = null - var/menu - var/datum/data/record/active1 = null //General - var/datum/data/record/active2 = null //Medical - var/datum/data/record/active3 = null //Security - var/obj/machinery/computer/monitor/powmonitor = null // Power Monitor - var/list/powermonitors = list() - var/message1 // used for status_displays - var/message2 - var/list/stored_data = list() - var/current_channel - - var/mob/living/simple_animal/bot/active_bot - var/list/botlist = list() - -/obj/item/cartridge/Initialize() - . = ..() - var/obj/item/pda/pda = loc - if(istype(pda)) - host_pda = pda - -/obj/item/cartridge/engineering - name = "\improper Power-ON cartridge" - icon_state = "cart-e" - access = CART_ENGINE | CART_DRONEPHONE - bot_access_flags = FLOOR_BOT - -/obj/item/cartridge/atmos - name = "\improper BreatheDeep cartridge" - icon_state = "cart-a" - access = CART_ATMOS | CART_DRONEPHONE - bot_access_flags = FLOOR_BOT | FIRE_BOT - -/obj/item/cartridge/medical - name = "\improper Med-U cartridge" - icon_state = "cart-m" - access = CART_MEDICAL - bot_access_flags = MED_BOT - -/obj/item/cartridge/chemistry - name = "\improper ChemWhiz cartridge" - icon_state = "cart-chem" - access = CART_REAGENT_SCANNER - bot_access_flags = MED_BOT - -/obj/item/cartridge/security - name = "\improper R.O.B.U.S.T. cartridge" - icon_state = "cart-s" - access = CART_SECURITY - bot_access_flags = SEC_BOT - -/obj/item/cartridge/detective - name = "\improper D.E.T.E.C.T. cartridge" - icon_state = "cart-s" - access = CART_SECURITY | CART_MEDICAL - bot_access_flags = SEC_BOT - -/obj/item/cartridge/janitor - name = "\improper CustodiPRO cartridge" - desc = "The ultimate in clean-room design." - icon_state = "cart-j" - access = CART_JANITOR | CART_DRONEPHONE - bot_access_flags = CLEAN_BOT - -/obj/item/cartridge/lawyer - name = "\improper P.R.O.V.E. cartridge" - icon_state = "cart-s" - access = CART_SECURITY - spam_enabled = 1 - -// DISABLED BECAUSE IT DONT FUCKIN' WORK -/* -///obj/item/cartridge/curator -// name = "\improper Lib-Tweet cartridge" -// icon_state = "cart-s" -// access = CART_NEWSCASTER -*/ - -/obj/item/cartridge/roboticist - name = "\improper B.O.O.P. Remote Control cartridge" - desc = "Packed with heavy duty quad-bot interlink!" - bot_access_flags = FLOOR_BOT | CLEAN_BOT | MED_BOT | FIRE_BOT - access = CART_DRONEPHONE - -/obj/item/cartridge/signal - name = "generic signaler cartridge" - desc = "A data cartridge with an integrated radio signaler module." - -/obj/item/cartridge/signal/toxins - name = "\improper Signal Ace 2 cartridge" - desc = "Complete with integrated radio signaler!" - icon_state = "cart-tox" - access = CART_REAGENT_SCANNER | CART_ATMOS - -/obj/item/cartridge/signal/Initialize() - . = ..() - radio = new(src) - - - -/obj/item/cartridge/quartermaster - name = "space parts & space vendors cartridge" - desc = "Perfect for the Quartermaster on the go!" - icon_state = "cart-q" - access = CART_QUARTERMASTER - bot_access_flags = MULE_BOT - -/obj/item/cartridge/head - name = "\improper Easy-Record DELUXE cartridge" - icon_state = "cart-h" - access = CART_STATUS_DISPLAY - -/obj/item/cartridge/head_of_personnel - name = "\improper HumanResources9001 cartridge" - icon_state = "cart-h" - access = CART_STATUS_DISPLAY | CART_JANITOR | CART_SECURITY | CART_NEWSCASTER | CART_QUARTERMASTER | CART_DRONEPHONE - bot_access_flags = MULE_BOT | CLEAN_BOT - -/obj/item/cartridge/hos - name = "\improper R.O.B.U.S.T. DELUXE cartridge" - icon_state = "cart-hos" - access = CART_STATUS_DISPLAY | CART_SECURITY - bot_access_flags = SEC_BOT - - -/obj/item/cartridge/ce - name = "\improper Power-On DELUXE cartridge" - icon_state = "cart-ce" - access = CART_STATUS_DISPLAY | CART_ENGINE | CART_ATMOS | CART_DRONEPHONE - bot_access_flags = FLOOR_BOT | FIRE_BOT - -/obj/item/cartridge/cmo - name = "\improper Med-U DELUXE cartridge" - icon_state = "cart-cmo" - access = CART_STATUS_DISPLAY | CART_REAGENT_SCANNER | CART_MEDICAL - bot_access_flags = MED_BOT - -/obj/item/cartridge/rd - name = "\improper Signal Ace DELUXE cartridge" - icon_state = "cart-rd" - access = CART_STATUS_DISPLAY | CART_REAGENT_SCANNER | CART_ATMOS | CART_DRONEPHONE - bot_access_flags = FLOOR_BOT | CLEAN_BOT | MED_BOT | FIRE_BOT - -/obj/item/cartridge/rd/Initialize() - . = ..() - radio = new(src) - -/obj/item/cartridge/captain - name = "\improper Value-PAK cartridge" - desc = "Now with 350% more value!" //Give the Captain...EVERYTHING! (Except Mime, Clown, and Syndie) - icon_state = "cart-c" - access = ~(CART_CLOWN | CART_MIME | CART_REMOTE_DOOR | CART_NEWSCASTER) - bot_access_flags = SEC_BOT | MULE_BOT | FLOOR_BOT | CLEAN_BOT | MED_BOT | FIRE_BOT - spam_enabled = 1 - -/obj/item/cartridge/captain/Initialize() - . = ..() - radio = new(src) - -/obj/item/cartridge/proc/post_status(command, data1, data2) - - var/datum/radio_frequency/frequency = SSradio.return_frequency(FREQ_STATUS_DISPLAYS) - - if(!frequency) - return - - var/datum/signal/status_signal = new(list("command" = command)) - switch(command) - if("message") - status_signal.data["msg1"] = data1 - status_signal.data["msg2"] = data2 - if("alert") - status_signal.data["picture_state"] = data1 - - frequency.post_signal(src, status_signal) - -/obj/item/cartridge/proc/generate_menu(mob/user) - if(!host_pda) - return - switch(host_pda.mode) - if(40) //signaller - menu = "

                        [PDAIMG(signaler)] Remote Signaling System

                        " - - menu += {" -Frequency: -- -- -[format_frequency(radio.frequency)] -+ -+
                        -
                        -Code: -- -- -[radio.code] -+ -+

                        -Send Signal
                        "} - if (41) //crew manifest - menu = "

                        [PDAIMG(notes)] Crew Manifest

                        " - menu += "
                        [GLOB.data_core.get_manifest_html(monochrome=TRUE)]
                        " - - - if (42) //status displays - menu = "

                        [PDAIMG(status)] Station Status Display Interlink

                        " - - menu += "\[ Clear \]
                        " - menu += "\[ Shuttle ETA \]
                        " - menu += "\[ Message \]" - menu += "
                        " - menu += "\[ Alert: None |" - menu += " Red Alert |" - menu += " Lockdown |" - menu += " Biohazard \]
                        " - - if (43) - menu = "

                        [PDAIMG(power)] Power Monitors - Please select one


                        " - powmonitor = null - powermonitors = list() - var/powercount = 0 - - - - var/turf/pda_turf = get_turf(src) - for(var/obj/machinery/computer/monitor/pMon in GLOB.machines) - if(pMon.machine_stat & (NOPOWER | BROKEN)) //check to make sure the computer is functional - continue - if(pda_turf.z != pMon.z) //and that we're on the same zlevel as the computer (lore: limited signal strength) - continue - if(pMon.is_secret_monitor) //make sure it isn't a secret one (ie located on a ruin), allowing people to metagame that the location exists - continue - powercount++ - powermonitors += pMon - - - if(!powercount) - menu += "No connection
                        " - else - - menu += "" - var/count = 0 - for(var/obj/machinery/computer/monitor/pMon in powermonitors) - count++ - menu += "[pMon] - [get_area_name(pMon, TRUE)]
                        " - - menu += "
                        " - - if (433) - menu = "

                        [PDAIMG(power)] Power Monitor


                        " - if(!powmonitor || !powmonitor.get_powernet()) - menu += "No connection
                        " - else - var/list/L = list() - var/datum/powernet/connected_powernet = powmonitor.get_powernet() - for(var/obj/machinery/power/terminal/term in connected_powernet.nodes) - if(istype(term.master, /obj/machinery/power/apc)) - var/obj/machinery/power/apc/A = term.master - L += A - - menu += "
                        Location: [get_area_name(powmonitor, TRUE)]
                        Total power: [DisplayPower(connected_powernet.viewavail)]
                        Total load: [DisplayPower(connected_powernet.viewload)]
                        " - - menu += "" - - if(L.len > 0) - menu += "Area Eqp./Lgt./Env. Load Cell
                        " - - var/list/S = list(" Off","AOff"," On", " AOn") - var/list/chg = list("N","C","F") -//Neither copytext nor copytext_char is appropriate here; neither 30 UTF-8 code units nor 30 code points equates to 30 columns of output. -//Some glyphs are very tall or very wide while others are small or even take up no space at all. -//Emojis can take modifiers which are many characters but render as only one glyph. -//A proper solution here (as far as Unicode goes, maybe not ideal as far as markup goes, a table would be better) -//would be to use [A.area.name] - for(var/obj/machinery/power/apc/A in L) - menu += copytext_char(add_trailing(A.area.name, 30, " "), 1, 30) - menu += " [S[A.equipment+1]] [S[A.lighting+1]] [S[A.environ+1]] [add_leading(DisplayPower(A.lastused_total), 6, " ")] [A.cell ? "[add_leading(round(A.cell.percent()), 3, " ")]% [chg[A.charging+1]]" : " N/C"]
                        " - - menu += "
                        " - - if (44) //medical records //This thing only displays a single screen so it's hard to really get the sub-menu stuff working. - menu = "

                        [PDAIMG(medical)] Medical Record List

                        " - if(GLOB.data_core.general) - for(var/datum/data/record/R in sortRecord(GLOB.data_core.general)) - menu += "[PDAIMG(medical)] [R.fields["id"]]: [R.fields["name"]]
                        " - menu += "
                        " - if(441) - menu = "

                        [PDAIMG(medical)] Medical Record

                        " - - if(active1 in GLOB.data_core.general) - menu += "Name: [active1.fields["name"]] ID: [active1.fields["id"]]
                        " - menu += "Gender: [active1.fields["gender"]]
                        " - menu += "Age: [active1.fields["age"]]
                        " - menu += "Rank: [active1.fields["rank"]]
                        " - menu += "Fingerprint: [active1.fields["fingerprint"]]
                        " - menu += "Physical Status: [active1.fields["p_stat"]]
                        " - menu += "Mental Status: [active1.fields["m_stat"]]
                        " - else - menu += "Record Lost!
                        " - - menu += "
                        " - - menu += "

                        [PDAIMG(medical)] Medical Data

                        " - if(active2 in GLOB.data_core.medical) - menu += "Blood Type: [active2.fields["blood_type"]]

                        " - - menu += "Minor Disabilities: [active2.fields["mi_dis"]]
                        " - menu += "Details: [active2.fields["mi_dis_d"]]

                        " - - menu += "Major Disabilities: [active2.fields["ma_dis"]]
                        " - menu += "Details: [active2.fields["ma_dis_d"]]

                        " - - menu += "Allergies: [active2.fields["alg"]]
                        " - menu += "Details: [active2.fields["alg_d"]]

                        " - - menu += "Current Diseases: [active2.fields["cdi"]]
                        " - menu += "Details: [active2.fields["cdi_d"]]

                        " - - menu += "Important Notes: [active2.fields["notes"]]
                        " - else - menu += "Record Lost!
                        " - - menu += "
                        " - if (45) //security records - menu = "

                        [PDAIMG(cuffs)] Security Record List

                        " - if(GLOB.data_core.general) - for (var/datum/data/record/R in sortRecord(GLOB.data_core.general)) - menu += "[PDAIMG(cuffs)] [R.fields["id"]]: [R.fields["name"]]
                        " - - menu += "
                        " - if(451) - menu = "

                        [PDAIMG(cuffs)] Security Record

                        " - - if(active1 in GLOB.data_core.general) - menu += "Name: [active1.fields["name"]] ID: [active1.fields["id"]]
                        " - menu += "Gender: [active1.fields["gender"]]
                        " - menu += "Age: [active1.fields["age"]]
                        " - menu += "Rank: [active1.fields["rank"]]
                        " - menu += "Fingerprint: [active1.fields["fingerprint"]]
                        " - menu += "Physical Status: [active1.fields["p_stat"]]
                        " - menu += "Mental Status: [active1.fields["m_stat"]]
                        " - else - menu += "Record Lost!
                        " - - menu += "
                        " - - menu += "

                        [PDAIMG(cuffs)] Security Data

                        " - if(active3 in GLOB.data_core.security) - menu += "Criminal Status: [active3.fields["criminal"]]
                        " - - menu += text("
                        \nCrimes:") - - menu +={" - - - - - -"} - for(var/datum/data/crime/c in active3.fields["crim"]) - menu += "" - menu += "" - menu += "" - menu += "" - menu += "" - menu += "
                        CrimeDetailsAuthorTime Added
                        [c.crimeName][c.crimeDetails][c.author][c.time]
                        " - menu += "
                        \nImportant Notes:
                        " - menu += "[active3.fields["notes"]]" - else - menu += "Record Lost!
                        " - - menu += "
                        " - - if (47) //quartermaster order records - menu = "

                        [PDAIMG(crate)] Supply Record Interlink

                        " - - menu += "
                        Supply shuttle
                        " - menu += "Location: " - switch(SSshuttle.supply.mode) - if(SHUTTLE_CALL) - menu += "Moving to " - if(!is_station_level(SSshuttle.supply.z)) - menu += "station" - else - menu += "CentCom" - menu += " ([SSshuttle.supply.timeLeft(600)] Mins)" - else - menu += "At " - if(!is_station_level(SSshuttle.supply.z)) - menu += "CentCom" - else - menu += "station" - menu += "
                        Current approved orders:
                          " - for(var/S in SSshuttle.shoppinglist) - var/datum/supply_order/SO = S - menu += "
                        1. #[SO.id] - [SO.pack.name] approved by [SO.orderer] [SO.reason ? "([SO.reason])":""]
                        2. " - menu += "
                        " - - menu += "Current requests:
                          " - for(var/S in SSshuttle.requestlist) - var/datum/supply_order/SO = S - menu += "
                        1. #[SO.id] - [SO.pack.name] requested by [SO.orderer]
                        2. " - // menu += "
                        Upgrade NOW to Space Parts & Space Vendors PLUS for full remote order control and inventory management." DOESNT EXIST, SO COMMENTED OUT - - if (48) // quartermaster ore logs - menu = list("

                        [PDAIMG(crate)] Ore Silo Logs

                        ") - if (GLOB.ore_silo_default) - var/list/logs = GLOB.silo_access_logs[REF(GLOB.ore_silo_default)] - var/len = LAZYLEN(logs) - var/i = 0 - for(var/M in logs) - if (++i > 30) - menu += "(... older logs not shown ...)" - break - var/datum/ore_silo_log/entry = M - menu += "[len - i]. [entry.formatted]

                        " - if(i == 0) - menu += "Nothing!" - else - menu += "No ore silo detected!" - menu = jointext(menu, "") - - if (49) //janitorial locator - menu = "

                        [PDAIMG(bucket)] Persistent Custodial Object Locator

                        " - - var/turf/cl = get_turf(src) - if (cl) - menu += "Current Orbital Location: \[[cl.x],[cl.y]\]" - - menu += "

                        Located Mops:

                        " - - var/ldat - for (var/obj/item/mop/M in world) - var/turf/ml = get_turf(M) - - if(ml) - if (ml.z != cl.z) - continue - var/direction = get_dir(src, M) - ldat += "Mop - \[[ml.x],[ml.y] ([uppertext(dir2text(direction))])\] - [M.reagents.total_volume ? "Wet" : "Dry"]
                        " - - if (!ldat) - menu += "None" - else - menu += "[ldat]" - - menu += "

                        Located Janitorial Cart:

                        " - - ldat = null - for (var/obj/structure/janitorialcart/B in world) - var/turf/bl = get_turf(B) - - if(bl) - if (bl.z != cl.z) - continue - var/direction = get_dir(src, B) - ldat += "Cart - \[[bl.x],[bl.y] ([uppertext(dir2text(direction))])\] - Water level: [B.reagents.total_volume]/100
                        " - - if (!ldat) - menu += "None" - else - menu += "[ldat]" - - menu += "

                        Located Cleanbots:

                        " - - ldat = null - for (var/mob/living/simple_animal/bot/cleanbot/B in GLOB.alive_mob_list) - var/turf/bl = get_turf(B) - - if(bl) - if (bl.z != cl.z) - continue - var/direction = get_dir(src, B) - ldat += "Cleanbot - \[[bl.x],[bl.y] ([uppertext(dir2text(direction))])\] - [B.on ? "Online" : "Offline"]
                        " - - if (!ldat) - menu += "None" - else - menu += "[ldat]" - - else - menu += "ERROR: Unable to determine current location." - menu += "

                        Refresh GPS Locator" - - if (53) // Newscaster - menu = "

                        [PDAIMG(notes)] Newscaster Access

                        " - menu += "
                        Current Newsfeed: [current_channel ? current_channel : "None"]
                        " - var/datum/newscaster/feed_channel/current - for(var/datum/newscaster/feed_channel/chan in GLOB.news_network.network_channels) - if (chan.channel_name == current_channel) - current = chan - if(!current) - menu += "
                        ERROR : NO CHANNEL FOUND
                        " - return menu - var/i = 1 - for(var/datum/newscaster/feed_message/msg in current.messages) - menu +="-[msg.returnBody(-1)]
                        \[Story by [msg.returnAuthor(-1)]\]
                        " - menu +="[msg.comments.len] comment[msg.comments.len > 1 ? "s" : ""]
                        " - if(msg.img) - user << browse_rsc(msg.img, "tmp_photo[i].png") - menu +="
                        " - i++ - for(var/datum/newscaster/feed_comment/comment in msg.comments) - menu +="[comment.body]
                        [comment.author] [comment.time_stamp]
                        " - menu += "
                        Post Message" - - if (54) // Beepsky, Medibot, Floorbot, and Cleanbot access - menu = "

                        [PDAIMG(medbot)] Bots Interlink

                        " - bot_control() - if (55) // Emoji Guidebook for mimes - menu = "

                        [PDAIMG(emoji)] Emoji Guidebook

                        " - var/static/list/emoji_icon_states - var/static/emoji_table - if(!emoji_table) - var/datum/asset/spritesheet/sheet = get_asset_datum(/datum/asset/spritesheet/goonchat) - var/list/collate = list("
                        ") - for(var/emoji in sortList(icon_states(icon('icons/emoji.dmi')))) - var/tag = sheet.icon_tag("emoji-[emoji]") - collate += "" - collate += "
                        [emoji][tag]

                        " - emoji_table = collate.Join() - - menu += "
                        To use an emoji in a pda message, refer to the guide and add \":\" around the emoji. Your PDA supports the following emoji:
                        " - menu += emoji_table - - if (99) //Newscaster message permission error - menu = "
                        ERROR : NOT AUTHORIZED [host_pda.id ? "" : "- ID SLOT EMPTY"]
                        " - - return menu - -/obj/item/cartridge/Topic(href, href_list) - ..() - - if(!usr.canUseTopic(src, !issilicon(usr))) - usr.unset_machine() - usr << browse(null, "window=pda") - return - - switch(href_list["choice"]) - if("Medical Records") - active1 = find_record("id", href_list["target"], GLOB.data_core.general) - if(active1) - active2 = find_record("id", href_list["target"], GLOB.data_core.medical) - host_pda.mode = 441 - if(!active2) - active1 = null - - if("Security Records") - active1 = find_record("id", href_list["target"], GLOB.data_core.general) - if(active1) - active3 = find_record("id", href_list["target"], GLOB.data_core.security) - host_pda.mode = 451 - if(!active3) - active1 = null - - if("Send Signal") - INVOKE_ASYNC(radio, /obj/item/integrated_signaler.proc/send_activation) - - if("Signal Frequency") - var/new_frequency = sanitize_frequency(radio.frequency + text2num(href_list["sfreq"])) - radio.set_frequency(new_frequency) - - if("Signal Code") - radio.code += text2num(href_list["scode"]) - radio.code = round(radio.code) - radio.code = min(100, radio.code) - radio.code = max(1, radio.code) - - if("Status") - switch(href_list["statdisp"]) - if("message") - post_status("message", message1, message2) - if("alert") - post_status("alert", href_list["alert"]) - if("setmsg1") - message1 = reject_bad_text(input("Line 1", "Enter Message Text", message1) as text|null, 40) - updateSelfDialog() - if("setmsg2") - message2 = reject_bad_text(input("Line 2", "Enter Message Text", message2) as text|null, 40) - updateSelfDialog() - else - post_status(href_list["statdisp"]) - if("Power Select") - var/pnum = text2num(href_list["target"]) - powmonitor = powermonitors[pnum] - host_pda.mode = 433 - - if("Supply Orders") - host_pda.mode =47 - - if("Newscaster Access") - host_pda.mode = 53 - - if("Newscaster Message") - var/host_pda_owner_name = host_pda.id ? "[host_pda.id.registered_name] ([host_pda.id.assignment])" : "Unknown" - var/message = host_pda.msg_input() - var/datum/newscaster/feed_channel/current - for(var/datum/newscaster/feed_channel/chan in GLOB.news_network.network_channels) - if (chan.channel_name == current_channel) - current = chan - if(current.locked && current.author != host_pda_owner_name) - host_pda.mode = 99 - host_pda.Topic(null,list("choice"="Refresh")) - return - GLOB.news_network.SubmitArticle(message,host_pda.owner,current_channel) - host_pda.Topic(null,list("choice"=num2text(host_pda.mode))) - return - - if("Newscaster Switch Channel") - current_channel = host_pda.msg_input() - host_pda.Topic(null,list("choice"=num2text(host_pda.mode))) - return - - //emoji previews - if(href_list["emoji"]) - var/parse = emoji_parse(":[href_list["emoji"]]:") - to_chat(usr, parse) - - //Bot control section! Viciously ripped from radios for being laggy and terrible. - if(href_list["op"]) - switch(href_list["op"]) - - if("control") - active_bot = locate(href_list["bot"]) in GLOB.bots_list - - if("botlist") - active_bot = null - if("summon") //Args are in the correct order, they are stated here just as an easy reminder. - active_bot.bot_control("summon", usr, host_pda.GetAccess()) - else //Forward all other bot commands to the bot itself! - active_bot.bot_control(href_list["op"], usr) - - if(href_list["mule"]) //MULEbots are special snowflakes, and need different args due to how they work. - var/mob/living/simple_animal/bot/mulebot/mule = active_bot - if (istype(mule)) - mule.bot_control(href_list["mule"], usr, pda=TRUE) - - if(!host_pda) - return - host_pda.attack_self(usr) - - -/obj/item/cartridge/proc/bot_control() - if(active_bot) - menu += "[active_bot]
                        Status: ([PDAIMG(refresh)]refresh)
                        " - menu += "Model: [active_bot.model]
                        " - menu += "Location: [get_area(active_bot)]
                        " - menu += "Mode: [active_bot.get_mode()]" - if(active_bot.allow_pai) - menu += "
                        pAI: " - if(active_bot.paicard && active_bot.paicard.pai) - menu += "[active_bot.paicard.pai.name]" - if(active_bot.bot_core.allowed(usr)) - menu += " (Eject)" - else - menu += "None" - - //MULEs! - if(active_bot.bot_type == MULE_BOT) - var/mob/living/simple_animal/bot/mulebot/MULE = active_bot - var/atom/Load = MULE.load - menu += "
                        Current Load: [ !Load ? "None" : "[Load.name] (Unload)" ]
                        " - menu += "Destination: [MULE.destination ? MULE.destination : "None"] Set
                        " - menu += "Set ID: [MULE.suffix] Modify
                        " - menu += "Power: [MULE.cell ? MULE.cell.percent() : 0]%
                        " - menu += "Home: [!MULE.home_destination ? "None" : MULE.home_destination ]
                        " - menu += "Delivery Reporting: [MULE.report_delivery ? "On": "Off"]
                        " - menu += "Auto Return Home: [MULE.auto_return ? "On": "Off"]
                        " - menu += "Auto Pickup Crate: [MULE.auto_pickup ? "On": "Off"]

                        " //Hue. - - menu += "Stop " - menu += "Proceed " - menu += "Return Home
                        " - - else - menu += "
                        Stop Patrol" //patrolon - menu += "Start Patrol" //patroloff - menu += "Summon Bot
                        " //summon - menu += "Keep an ID inserted to upload access codes upon summoning." - - menu += "
                        [PDAIMG(back)]Return to bot list" - else - menu += "
                        [PDAIMG(refresh)]Scan for active bots

                        " - var/turf/current_turf = get_turf(src) - var/zlevel = current_turf.z - var/botcount = 0 - for(var/B in GLOB.bots_list) //Git da botz - var/mob/living/simple_animal/bot/Bot = B - if(!Bot.on || Bot.z != zlevel || Bot.remote_disabled || !(bot_access_flags & Bot.bot_type)) //Only non-emagged bots on the same Z-level are detected! - continue //Also, the PDA must have access to the bot type. - menu += "[PDAIMG(medbot)] [Bot.name] ([Bot.get_mode()])
                        " - botcount++ - if(!botcount) //No bots at all? Lame. - menu += "No bots found.
                        " - return - - return menu - -//If the cartridge adds a special line to the top of the messaging app -/obj/item/cartridge/proc/message_header() - return "" - -//If the cartridge adds something to each potetial messaging target -/obj/item/cartridge/proc/message_special(obj/item/pda/target) - return "" - -//This is called for special abilities of cartridges -/obj/item/cartridge/proc/special(mob/living/user, list/params) +#define CART_SECURITY (1<<0) +#define CART_ENGINE (1<<1) +#define CART_ATMOS (1<<2) +#define CART_MEDICAL (1<<3) +#define CART_CLOWN (1<<5) +#define CART_MIME (1<<6) +#define CART_JANITOR (1<<7) +#define CART_REAGENT_SCANNER (1<<8) +#define CART_NEWSCASTER (1<<9) +#define CART_REMOTE_DOOR (1<<10) +#define CART_STATUS_DISPLAY (1<<11) +#define CART_QUARTERMASTER (1<<12) +#define CART_HYDROPONICS (1<<13) +#define CART_DRONEPHONE (1<<14) + + +/obj/item/cartridge + name = "generic cartridge" + desc = "A data cartridge for portable microcomputers." + icon = 'icons/obj/pda.dmi' + icon_state = "cart" + item_state = "electronic" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + w_class = WEIGHT_CLASS_TINY + + var/obj/item/integrated_signaler/radio = null + + var/access = 0 //Bit flags for cartridge access + + var/remote_door_id = "" + + var/bot_access_flags = 0 //Bit flags. Selection: SEC_BOT | MULE_BOT | FLOOR_BOT | CLEAN_BOT | MED_BOT | FIRE_BOT + var/spam_enabled = 0 //Enables "Send to All" Option + + var/obj/item/pda/host_pda = null + var/menu + var/datum/data/record/active1 = null //General + var/datum/data/record/active2 = null //Medical + var/datum/data/record/active3 = null //Security + var/obj/machinery/computer/monitor/powmonitor = null // Power Monitor + var/list/powermonitors = list() + var/message1 // used for status_displays + var/message2 + var/list/stored_data = list() + var/current_channel + + var/mob/living/simple_animal/bot/active_bot + var/list/botlist = list() + +/obj/item/cartridge/Initialize() + . = ..() + var/obj/item/pda/pda = loc + if(istype(pda)) + host_pda = pda + +/obj/item/cartridge/engineering + name = "\improper Power-ON cartridge" + icon_state = "cart-e" + access = CART_ENGINE | CART_DRONEPHONE + bot_access_flags = FLOOR_BOT + +/obj/item/cartridge/atmos + name = "\improper BreatheDeep cartridge" + icon_state = "cart-a" + access = CART_ATMOS | CART_DRONEPHONE + bot_access_flags = FLOOR_BOT | FIRE_BOT + +/obj/item/cartridge/medical + name = "\improper Med-U cartridge" + icon_state = "cart-m" + access = CART_MEDICAL + bot_access_flags = MED_BOT + +/obj/item/cartridge/chemistry + name = "\improper ChemWhiz cartridge" + icon_state = "cart-chem" + access = CART_REAGENT_SCANNER + bot_access_flags = MED_BOT + +/obj/item/cartridge/security + name = "\improper R.O.B.U.S.T. cartridge" + icon_state = "cart-s" + access = CART_SECURITY + bot_access_flags = SEC_BOT + +/obj/item/cartridge/detective + name = "\improper D.E.T.E.C.T. cartridge" + icon_state = "cart-s" + access = CART_SECURITY | CART_MEDICAL + bot_access_flags = SEC_BOT + +/obj/item/cartridge/janitor + name = "\improper CustodiPRO cartridge" + desc = "The ultimate in clean-room design." + icon_state = "cart-j" + access = CART_JANITOR | CART_DRONEPHONE + bot_access_flags = CLEAN_BOT + +/obj/item/cartridge/lawyer + name = "\improper P.R.O.V.E. cartridge" + icon_state = "cart-s" + access = CART_SECURITY + spam_enabled = 1 + +// DISABLED BECAUSE IT DONT FUCKIN' WORK +/* +///obj/item/cartridge/curator +// name = "\improper Lib-Tweet cartridge" +// icon_state = "cart-s" +// access = CART_NEWSCASTER +*/ + +/obj/item/cartridge/roboticist + name = "\improper B.O.O.P. Remote Control cartridge" + desc = "Packed with heavy duty quad-bot interlink!" + bot_access_flags = FLOOR_BOT | CLEAN_BOT | MED_BOT | FIRE_BOT + access = CART_DRONEPHONE + +/obj/item/cartridge/signal + name = "generic signaler cartridge" + desc = "A data cartridge with an integrated radio signaler module." + +/obj/item/cartridge/signal/toxins + name = "\improper Signal Ace 2 cartridge" + desc = "Complete with integrated radio signaler!" + icon_state = "cart-tox" + access = CART_REAGENT_SCANNER | CART_ATMOS + +/obj/item/cartridge/signal/Initialize() + . = ..() + radio = new(src) + + + +/obj/item/cartridge/quartermaster + name = "space parts & space vendors cartridge" + desc = "Perfect for the Quartermaster on the go!" + icon_state = "cart-q" + access = CART_QUARTERMASTER + bot_access_flags = MULE_BOT + +/obj/item/cartridge/head + name = "\improper Easy-Record DELUXE cartridge" + icon_state = "cart-h" + access = CART_STATUS_DISPLAY + +/obj/item/cartridge/head_of_personnel + name = "\improper HumanResources9001 cartridge" + icon_state = "cart-h" + access = CART_STATUS_DISPLAY | CART_JANITOR | CART_SECURITY | CART_NEWSCASTER | CART_QUARTERMASTER | CART_DRONEPHONE + bot_access_flags = MULE_BOT | CLEAN_BOT + +/obj/item/cartridge/hos + name = "\improper R.O.B.U.S.T. DELUXE cartridge" + icon_state = "cart-hos" + access = CART_STATUS_DISPLAY | CART_SECURITY + bot_access_flags = SEC_BOT + + +/obj/item/cartridge/ce + name = "\improper Power-On DELUXE cartridge" + icon_state = "cart-ce" + access = CART_STATUS_DISPLAY | CART_ENGINE | CART_ATMOS | CART_DRONEPHONE + bot_access_flags = FLOOR_BOT | FIRE_BOT + +/obj/item/cartridge/cmo + name = "\improper Med-U DELUXE cartridge" + icon_state = "cart-cmo" + access = CART_STATUS_DISPLAY | CART_REAGENT_SCANNER | CART_MEDICAL + bot_access_flags = MED_BOT + +/obj/item/cartridge/rd + name = "\improper Signal Ace DELUXE cartridge" + icon_state = "cart-rd" + access = CART_STATUS_DISPLAY | CART_REAGENT_SCANNER | CART_ATMOS | CART_DRONEPHONE + bot_access_flags = FLOOR_BOT | CLEAN_BOT | MED_BOT | FIRE_BOT + +/obj/item/cartridge/rd/Initialize() + . = ..() + radio = new(src) + +/obj/item/cartridge/captain + name = "\improper Value-PAK cartridge" + desc = "Now with 350% more value!" //Give the Captain...EVERYTHING! (Except Mime, Clown, and Syndie) + icon_state = "cart-c" + access = ~(CART_CLOWN | CART_MIME | CART_REMOTE_DOOR | CART_NEWSCASTER) + bot_access_flags = SEC_BOT | MULE_BOT | FLOOR_BOT | CLEAN_BOT | MED_BOT | FIRE_BOT + spam_enabled = 1 + +/obj/item/cartridge/captain/Initialize() + . = ..() + radio = new(src) + +/obj/item/cartridge/proc/post_status(command, data1, data2) + + var/datum/radio_frequency/frequency = SSradio.return_frequency(FREQ_STATUS_DISPLAYS) + + if(!frequency) + return + + var/datum/signal/status_signal = new(list("command" = command)) + switch(command) + if("message") + status_signal.data["msg1"] = data1 + status_signal.data["msg2"] = data2 + if("alert") + status_signal.data["picture_state"] = data1 + + frequency.post_signal(src, status_signal) + +/obj/item/cartridge/proc/generate_menu(mob/user) + if(!host_pda) + return + switch(host_pda.mode) + if(40) //signaller + menu = "

                        [PDAIMG(signaler)] Remote Signaling System

                        " + + menu += {" +Frequency: +- +- +[format_frequency(radio.frequency)] ++ ++
                        +
                        +Code: +- +- +[radio.code] ++ ++

                        +Send Signal
                        "} + if (41) //crew manifest + menu = "

                        [PDAIMG(notes)] Crew Manifest

                        " + menu += "
                        [GLOB.data_core.get_manifest_html(monochrome=TRUE)]
                        " + + + if (42) //status displays + menu = "

                        [PDAIMG(status)] Station Status Display Interlink

                        " + + menu += "\[ Clear \]
                        " + menu += "\[ Shuttle ETA \]
                        " + menu += "\[ Message \]" + menu += "
                        " + menu += "\[ Alert: None |" + menu += " Red Alert |" + menu += " Lockdown |" + menu += " Biohazard \]
                        " + + if (43) + menu = "

                        [PDAIMG(power)] Power Monitors - Please select one


                        " + powmonitor = null + powermonitors = list() + var/powercount = 0 + + + + var/turf/pda_turf = get_turf(src) + for(var/obj/machinery/computer/monitor/pMon in GLOB.machines) + if(pMon.machine_stat & (NOPOWER | BROKEN)) //check to make sure the computer is functional + continue + if(pda_turf.z != pMon.z) //and that we're on the same zlevel as the computer (lore: limited signal strength) + continue + if(pMon.is_secret_monitor) //make sure it isn't a secret one (ie located on a ruin), allowing people to metagame that the location exists + continue + powercount++ + powermonitors += pMon + + + if(!powercount) + menu += "No connection
                        " + else + + menu += "" + var/count = 0 + for(var/obj/machinery/computer/monitor/pMon in powermonitors) + count++ + menu += "[pMon] - [get_area_name(pMon, TRUE)]
                        " + + menu += "
                        " + + if (433) + menu = "

                        [PDAIMG(power)] Power Monitor


                        " + if(!powmonitor || !powmonitor.get_powernet()) + menu += "No connection
                        " + else + var/list/L = list() + var/datum/powernet/connected_powernet = powmonitor.get_powernet() + for(var/obj/machinery/power/terminal/term in connected_powernet.nodes) + if(istype(term.master, /obj/machinery/power/apc)) + var/obj/machinery/power/apc/A = term.master + L += A + + menu += "
                        Location: [get_area_name(powmonitor, TRUE)]
                        Total power: [DisplayPower(connected_powernet.viewavail)]
                        Total load: [DisplayPower(connected_powernet.viewload)]
                        " + + menu += "" + + if(L.len > 0) + menu += "Area Eqp./Lgt./Env. Load Cell
                        " + + var/list/S = list(" Off","AOff"," On", " AOn") + var/list/chg = list("N","C","F") +//Neither copytext nor copytext_char is appropriate here; neither 30 UTF-8 code units nor 30 code points equates to 30 columns of output. +//Some glyphs are very tall or very wide while others are small or even take up no space at all. +//Emojis can take modifiers which are many characters but render as only one glyph. +//A proper solution here (as far as Unicode goes, maybe not ideal as far as markup goes, a table would be better) +//would be to use [A.area.name] + for(var/obj/machinery/power/apc/A in L) + menu += copytext_char(add_trailing(A.area.name, 30, " "), 1, 30) + menu += " [S[A.equipment+1]] [S[A.lighting+1]] [S[A.environ+1]] [add_leading(DisplayPower(A.lastused_total), 6, " ")] [A.cell ? "[add_leading(round(A.cell.percent()), 3, " ")]% [chg[A.charging+1]]" : " N/C"]
                        " + + menu += "
                        " + + if (44) //medical records //This thing only displays a single screen so it's hard to really get the sub-menu stuff working. + menu = "

                        [PDAIMG(medical)] Medical Record List

                        " + if(GLOB.data_core.general) + for(var/datum/data/record/R in sortRecord(GLOB.data_core.general)) + menu += "[PDAIMG(medical)] [R.fields["id"]]: [R.fields["name"]]
                        " + menu += "
                        " + if(441) + menu = "

                        [PDAIMG(medical)] Medical Record

                        " + + if(active1 in GLOB.data_core.general) + menu += "Name: [active1.fields["name"]] ID: [active1.fields["id"]]
                        " + menu += "Gender: [active1.fields["gender"]]
                        " + menu += "Age: [active1.fields["age"]]
                        " + menu += "Rank: [active1.fields["rank"]]
                        " + menu += "Fingerprint: [active1.fields["fingerprint"]]
                        " + menu += "Physical Status: [active1.fields["p_stat"]]
                        " + menu += "Mental Status: [active1.fields["m_stat"]]
                        " + else + menu += "Record Lost!
                        " + + menu += "
                        " + + menu += "

                        [PDAIMG(medical)] Medical Data

                        " + if(active2 in GLOB.data_core.medical) + menu += "Blood Type: [active2.fields["blood_type"]]

                        " + + menu += "Minor Disabilities: [active2.fields["mi_dis"]]
                        " + menu += "Details: [active2.fields["mi_dis_d"]]

                        " + + menu += "Major Disabilities: [active2.fields["ma_dis"]]
                        " + menu += "Details: [active2.fields["ma_dis_d"]]

                        " + + menu += "Allergies: [active2.fields["alg"]]
                        " + menu += "Details: [active2.fields["alg_d"]]

                        " + + menu += "Current Diseases: [active2.fields["cdi"]]
                        " + menu += "Details: [active2.fields["cdi_d"]]

                        " + + menu += "Important Notes: [active2.fields["notes"]]
                        " + else + menu += "Record Lost!
                        " + + menu += "
                        " + if (45) //security records + menu = "

                        [PDAIMG(cuffs)] Security Record List

                        " + if(GLOB.data_core.general) + for (var/datum/data/record/R in sortRecord(GLOB.data_core.general)) + menu += "[PDAIMG(cuffs)] [R.fields["id"]]: [R.fields["name"]]
                        " + + menu += "
                        " + if(451) + menu = "

                        [PDAIMG(cuffs)] Security Record

                        " + + if(active1 in GLOB.data_core.general) + menu += "Name: [active1.fields["name"]] ID: [active1.fields["id"]]
                        " + menu += "Gender: [active1.fields["gender"]]
                        " + menu += "Age: [active1.fields["age"]]
                        " + menu += "Rank: [active1.fields["rank"]]
                        " + menu += "Fingerprint: [active1.fields["fingerprint"]]
                        " + menu += "Physical Status: [active1.fields["p_stat"]]
                        " + menu += "Mental Status: [active1.fields["m_stat"]]
                        " + else + menu += "Record Lost!
                        " + + menu += "
                        " + + menu += "

                        [PDAIMG(cuffs)] Security Data

                        " + if(active3 in GLOB.data_core.security) + menu += "Criminal Status: [active3.fields["criminal"]]
                        " + + menu += text("
                        \nCrimes:") + + menu +={" + + + + + +"} + for(var/datum/data/crime/c in active3.fields["crim"]) + menu += "" + menu += "" + menu += "" + menu += "" + menu += "" + menu += "
                        CrimeDetailsAuthorTime Added
                        [c.crimeName][c.crimeDetails][c.author][c.time]
                        " + menu += "
                        \nImportant Notes:
                        " + menu += "[active3.fields["notes"]]" + else + menu += "Record Lost!
                        " + + menu += "
                        " + + if (47) //quartermaster order records + menu = "

                        [PDAIMG(crate)] Supply Record Interlink

                        " + + menu += "
                        Supply shuttle
                        " + menu += "Location: " + switch(SSshuttle.supply.mode) + if(SHUTTLE_CALL) + menu += "Moving to " + if(!is_station_level(SSshuttle.supply.z)) + menu += "station" + else + menu += "CentCom" + menu += " ([SSshuttle.supply.timeLeft(600)] Mins)" + else + menu += "At " + if(!is_station_level(SSshuttle.supply.z)) + menu += "CentCom" + else + menu += "station" + menu += "
                        Current approved orders:
                          " + for(var/S in SSshuttle.shoppinglist) + var/datum/supply_order/SO = S + menu += "
                        1. #[SO.id] - [SO.pack.name] approved by [SO.orderer] [SO.reason ? "([SO.reason])":""]
                        2. " + menu += "
                        " + + menu += "Current requests:
                          " + for(var/S in SSshuttle.requestlist) + var/datum/supply_order/SO = S + menu += "
                        1. #[SO.id] - [SO.pack.name] requested by [SO.orderer]
                        2. " + // menu += "
                        Upgrade NOW to Space Parts & Space Vendors PLUS for full remote order control and inventory management." DOESNT EXIST, SO COMMENTED OUT + + if (48) // quartermaster ore logs + menu = list("

                        [PDAIMG(crate)] Ore Silo Logs

                        ") + if (GLOB.ore_silo_default) + var/list/logs = GLOB.silo_access_logs[REF(GLOB.ore_silo_default)] + var/len = LAZYLEN(logs) + var/i = 0 + for(var/M in logs) + if (++i > 30) + menu += "(... older logs not shown ...)" + break + var/datum/ore_silo_log/entry = M + menu += "[len - i]. [entry.formatted]

                        " + if(i == 0) + menu += "Nothing!" + else + menu += "No ore silo detected!" + menu = jointext(menu, "") + + if (49) //janitorial locator + menu = "

                        [PDAIMG(bucket)] Persistent Custodial Object Locator

                        " + + var/turf/cl = get_turf(src) + if (cl) + menu += "Current Orbital Location: \[[cl.x],[cl.y]\]" + + menu += "

                        Located Mops:

                        " + + var/ldat + for (var/obj/item/mop/M in world) + var/turf/ml = get_turf(M) + + if(ml) + if (ml.z != cl.z) + continue + var/direction = get_dir(src, M) + ldat += "Mop - \[[ml.x],[ml.y] ([uppertext(dir2text(direction))])\] - [M.reagents.total_volume ? "Wet" : "Dry"]
                        " + + if (!ldat) + menu += "None" + else + menu += "[ldat]" + + menu += "

                        Located Janitorial Cart:

                        " + + ldat = null + for (var/obj/structure/janitorialcart/B in world) + var/turf/bl = get_turf(B) + + if(bl) + if (bl.z != cl.z) + continue + var/direction = get_dir(src, B) + ldat += "Cart - \[[bl.x],[bl.y] ([uppertext(dir2text(direction))])\] - Water level: [B.reagents.total_volume]/100
                        " + + if (!ldat) + menu += "None" + else + menu += "[ldat]" + + menu += "

                        Located Cleanbots:

                        " + + ldat = null + for (var/mob/living/simple_animal/bot/cleanbot/B in GLOB.alive_mob_list) + var/turf/bl = get_turf(B) + + if(bl) + if (bl.z != cl.z) + continue + var/direction = get_dir(src, B) + ldat += "Cleanbot - \[[bl.x],[bl.y] ([uppertext(dir2text(direction))])\] - [B.on ? "Online" : "Offline"]
                        " + + if (!ldat) + menu += "None" + else + menu += "[ldat]" + + else + menu += "ERROR: Unable to determine current location." + menu += "

                        Refresh GPS Locator" + + if (53) // Newscaster + menu = "

                        [PDAIMG(notes)] Newscaster Access

                        " + menu += "
                        Current Newsfeed: [current_channel ? current_channel : "None"]
                        " + var/datum/newscaster/feed_channel/current + for(var/datum/newscaster/feed_channel/chan in GLOB.news_network.network_channels) + if (chan.channel_name == current_channel) + current = chan + if(!current) + menu += "
                        ERROR : NO CHANNEL FOUND
                        " + return menu + var/i = 1 + for(var/datum/newscaster/feed_message/msg in current.messages) + menu +="-[msg.returnBody(-1)]
                        \[Story by [msg.returnAuthor(-1)]\]
                        " + menu +="[msg.comments.len] comment[msg.comments.len > 1 ? "s" : ""]
                        " + if(msg.img) + user << browse_rsc(msg.img, "tmp_photo[i].png") + menu +="
                        " + i++ + for(var/datum/newscaster/feed_comment/comment in msg.comments) + menu +="[comment.body]
                        [comment.author] [comment.time_stamp]
                        " + menu += "
                        Post Message" + + if (54) // Beepsky, Medibot, Floorbot, and Cleanbot access + menu = "

                        [PDAIMG(medbot)] Bots Interlink

                        " + bot_control() + if (55) // Emoji Guidebook for mimes + menu = "

                        [PDAIMG(emoji)] Emoji Guidebook

                        " + var/static/list/emoji_icon_states + var/static/emoji_table + if(!emoji_table) + var/datum/asset/spritesheet/sheet = get_asset_datum(/datum/asset/spritesheet/goonchat) + var/list/collate = list("
                        ") + for(var/emoji in sortList(icon_states(icon('icons/emoji.dmi')))) + var/tag = sheet.icon_tag("emoji-[emoji]") + collate += "" + collate += "
                        [emoji][tag]

                        " + emoji_table = collate.Join() + + menu += "
                        To use an emoji in a pda message, refer to the guide and add \":\" around the emoji. Your PDA supports the following emoji:
                        " + menu += emoji_table + + if (99) //Newscaster message permission error + menu = "
                        ERROR : NOT AUTHORIZED [host_pda.id ? "" : "- ID SLOT EMPTY"]
                        " + + return menu + +/obj/item/cartridge/Topic(href, href_list) + ..() + + if(!usr.canUseTopic(src, !issilicon(usr))) + usr.unset_machine() + usr << browse(null, "window=pda") + return + + switch(href_list["choice"]) + if("Medical Records") + active1 = find_record("id", href_list["target"], GLOB.data_core.general) + if(active1) + active2 = find_record("id", href_list["target"], GLOB.data_core.medical) + host_pda.mode = 441 + if(!active2) + active1 = null + + if("Security Records") + active1 = find_record("id", href_list["target"], GLOB.data_core.general) + if(active1) + active3 = find_record("id", href_list["target"], GLOB.data_core.security) + host_pda.mode = 451 + if(!active3) + active1 = null + + if("Send Signal") + INVOKE_ASYNC(radio, /obj/item/integrated_signaler.proc/send_activation) + + if("Signal Frequency") + var/new_frequency = sanitize_frequency(radio.frequency + text2num(href_list["sfreq"])) + radio.set_frequency(new_frequency) + + if("Signal Code") + radio.code += text2num(href_list["scode"]) + radio.code = round(radio.code) + radio.code = min(100, radio.code) + radio.code = max(1, radio.code) + + if("Status") + switch(href_list["statdisp"]) + if("message") + post_status("message", message1, message2) + if("alert") + post_status("alert", href_list["alert"]) + if("setmsg1") + message1 = reject_bad_text(input("Line 1", "Enter Message Text", message1) as text|null, 40) + updateSelfDialog() + if("setmsg2") + message2 = reject_bad_text(input("Line 2", "Enter Message Text", message2) as text|null, 40) + updateSelfDialog() + else + post_status(href_list["statdisp"]) + if("Power Select") + var/pnum = text2num(href_list["target"]) + powmonitor = powermonitors[pnum] + host_pda.mode = 433 + + if("Supply Orders") + host_pda.mode =47 + + if("Newscaster Access") + host_pda.mode = 53 + + if("Newscaster Message") + var/host_pda_owner_name = host_pda.id ? "[host_pda.id.registered_name] ([host_pda.id.assignment])" : "Unknown" + var/message = host_pda.msg_input() + var/datum/newscaster/feed_channel/current + for(var/datum/newscaster/feed_channel/chan in GLOB.news_network.network_channels) + if (chan.channel_name == current_channel) + current = chan + if(current.locked && current.author != host_pda_owner_name) + host_pda.mode = 99 + host_pda.Topic(null,list("choice"="Refresh")) + return + GLOB.news_network.SubmitArticle(message,host_pda.owner,current_channel) + host_pda.Topic(null,list("choice"=num2text(host_pda.mode))) + return + + if("Newscaster Switch Channel") + current_channel = host_pda.msg_input() + host_pda.Topic(null,list("choice"=num2text(host_pda.mode))) + return + + //emoji previews + if(href_list["emoji"]) + var/parse = emoji_parse(":[href_list["emoji"]]:") + to_chat(usr, parse) + + //Bot control section! Viciously ripped from radios for being laggy and terrible. + if(href_list["op"]) + switch(href_list["op"]) + + if("control") + active_bot = locate(href_list["bot"]) in GLOB.bots_list + + if("botlist") + active_bot = null + if("summon") //Args are in the correct order, they are stated here just as an easy reminder. + active_bot.bot_control("summon", usr, host_pda.GetAccess()) + else //Forward all other bot commands to the bot itself! + active_bot.bot_control(href_list["op"], usr) + + if(href_list["mule"]) //MULEbots are special snowflakes, and need different args due to how they work. + var/mob/living/simple_animal/bot/mulebot/mule = active_bot + if (istype(mule)) + mule.bot_control(href_list["mule"], usr, pda=TRUE) + + if(!host_pda) + return + host_pda.attack_self(usr) + + +/obj/item/cartridge/proc/bot_control() + if(active_bot) + menu += "[active_bot]
                        Status: ([PDAIMG(refresh)]refresh)
                        " + menu += "Model: [active_bot.model]
                        " + menu += "Location: [get_area(active_bot)]
                        " + menu += "Mode: [active_bot.get_mode()]" + if(active_bot.allow_pai) + menu += "
                        pAI: " + if(active_bot.paicard && active_bot.paicard.pai) + menu += "[active_bot.paicard.pai.name]" + if(active_bot.bot_core.allowed(usr)) + menu += " (Eject)" + else + menu += "None" + + //MULEs! + if(active_bot.bot_type == MULE_BOT) + var/mob/living/simple_animal/bot/mulebot/MULE = active_bot + var/atom/Load = MULE.load + menu += "
                        Current Load: [ !Load ? "None" : "[Load.name] (Unload)" ]
                        " + menu += "Destination: [MULE.destination ? MULE.destination : "None"] Set
                        " + menu += "Set ID: [MULE.suffix] Modify
                        " + menu += "Power: [MULE.cell ? MULE.cell.percent() : 0]%
                        " + menu += "Home: [!MULE.home_destination ? "None" : MULE.home_destination ]
                        " + menu += "Delivery Reporting: [MULE.report_delivery ? "On": "Off"]
                        " + menu += "Auto Return Home: [MULE.auto_return ? "On": "Off"]
                        " + menu += "Auto Pickup Crate: [MULE.auto_pickup ? "On": "Off"]

                        " //Hue. + + menu += "Stop " + menu += "Proceed " + menu += "Return Home
                        " + + else + menu += "
                        Stop Patrol" //patrolon + menu += "Start Patrol" //patroloff + menu += "Summon Bot
                        " //summon + menu += "Keep an ID inserted to upload access codes upon summoning." + + menu += "
                        [PDAIMG(back)]Return to bot list" + else + menu += "
                        [PDAIMG(refresh)]Scan for active bots

                        " + var/turf/current_turf = get_turf(src) + var/zlevel = current_turf.z + var/botcount = 0 + for(var/B in GLOB.bots_list) //Git da botz + var/mob/living/simple_animal/bot/Bot = B + if(!Bot.on || Bot.z != zlevel || Bot.remote_disabled || !(bot_access_flags & Bot.bot_type)) //Only non-emagged bots on the same Z-level are detected! + continue //Also, the PDA must have access to the bot type. + menu += "[PDAIMG(medbot)] [Bot.name] ([Bot.get_mode()])
                        " + botcount++ + if(!botcount) //No bots at all? Lame. + menu += "No bots found.
                        " + return + + return menu + +//If the cartridge adds a special line to the top of the messaging app +/obj/item/cartridge/proc/message_header() + return "" + +//If the cartridge adds something to each potetial messaging target +/obj/item/cartridge/proc/message_special(obj/item/pda/target) + return "" + +//This is called for special abilities of cartridges +/obj/item/cartridge/proc/special(mob/living/user, list/params) diff --git a/code/game/objects/items/devices/PDA/radio.dm b/code/game/objects/items/devices/PDA/radio.dm index 9e20dc455b0d..329631ff9e07 100644 --- a/code/game/objects/items/devices/PDA/radio.dm +++ b/code/game/objects/items/devices/PDA/radio.dm @@ -1,38 +1,38 @@ -// Radio Cartridge, essentially a remote signaler with limited spectrum. -/obj/item/integrated_signaler - name = "\improper PDA radio module" - desc = "An electronic radio system of Nanotrasen origin." - icon = 'icons/obj/module.dmi' - icon_state = "power_mod" - -/obj/item/integrated_signaler - var/frequency = FREQ_SIGNALER - var/code = DEFAULT_SIGNALER_CODE - var/last_transmission - var/datum/radio_frequency/radio_connection - -/obj/item/integrated_signaler/Destroy() - radio_connection = null - return ..() - -/obj/item/integrated_signaler/Initialize() - . = ..() - if (frequency < MIN_FREE_FREQ || frequency > MAX_FREE_FREQ) - frequency = sanitize_frequency(frequency) - set_frequency(frequency) - -/obj/item/integrated_signaler/proc/set_frequency(new_frequency) - frequency = new_frequency - radio_connection = SSradio.return_frequency(frequency) - -/obj/item/integrated_signaler/proc/send_activation() - if(last_transmission && world.time < (last_transmission + 5)) - return - last_transmission = world.time - - var/time = time2text(world.realtime,"hh:mm:ss") - var/turf/T = get_turf(src) - GLOB.lastsignalers.Add("[time] : [usr.key] used [src] @ location [AREACOORD(T)] : [format_frequency(frequency)]/[code]") - - var/datum/signal/signal = new(list("code" = code)) - radio_connection.post_signal(src, signal, filter = RADIO_SIGNALER) +// Radio Cartridge, essentially a remote signaler with limited spectrum. +/obj/item/integrated_signaler + name = "\improper PDA radio module" + desc = "An electronic radio system of Nanotrasen origin." + icon = 'icons/obj/module.dmi' + icon_state = "power_mod" + +/obj/item/integrated_signaler + var/frequency = FREQ_SIGNALER + var/code = DEFAULT_SIGNALER_CODE + var/last_transmission + var/datum/radio_frequency/radio_connection + +/obj/item/integrated_signaler/Destroy() + radio_connection = null + return ..() + +/obj/item/integrated_signaler/Initialize() + . = ..() + if (frequency < MIN_FREE_FREQ || frequency > MAX_FREE_FREQ) + frequency = sanitize_frequency(frequency) + set_frequency(frequency) + +/obj/item/integrated_signaler/proc/set_frequency(new_frequency) + frequency = new_frequency + radio_connection = SSradio.return_frequency(frequency) + +/obj/item/integrated_signaler/proc/send_activation() + if(last_transmission && world.time < (last_transmission + 5)) + return + last_transmission = world.time + + var/time = time2text(world.realtime,"hh:mm:ss") + var/turf/T = get_turf(src) + GLOB.lastsignalers.Add("[time] : [usr.key] used [src] @ location [AREACOORD(T)] : [format_frequency(frequency)]/[code]") + + var/datum/signal/signal = new(list("code" = code)) + radio_connection.post_signal(src, signal, filter = RADIO_SIGNALER) diff --git a/code/game/objects/items/devices/aicard.dm b/code/game/objects/items/devices/aicard.dm index ee9730d8601e..f2acbeb684ea 100644 --- a/code/game/objects/items/devices/aicard.dm +++ b/code/game/objects/items/devices/aicard.dm @@ -1,104 +1,105 @@ -/obj/item/aicard - name = "intelliCard" - desc = "A storage device for AIs. Patent pending." - icon = 'icons/obj/aicards.dmi' - icon_state = "aicard" // aicard-full - item_state = "electronic" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - w_class = WEIGHT_CLASS_SMALL - slot_flags = ITEM_SLOT_BELT - item_flags = NOBLUDGEON - var/flush = FALSE - var/mob/living/silicon/ai/AI - -/obj/item/aicard/aitater - name = "intelliTater" - desc = "A stylish upgrade (?) to the intelliCard." - icon_state = "aitater" - -/obj/item/aicard/aispook - name = "intelliLantern" - desc = "A spoOoOoky upgrade to the intelliCard." - icon_state = "aispook" - -/obj/item/aicard/suicide_act(mob/living/user) - user.visible_message("[user] is trying to upload [user.p_them()]self into [src]! That's not going to work out well!") - return BRUTELOSS - -/obj/item/aicard/afterattack(atom/target, mob/user, proximity) - . = ..() - if(!proximity || !target) - return - if(AI) //AI is on the card, implies user wants to upload it. - log_combat(user, AI, "uploaded", src, "to [target].") - target.transfer_ai(AI_TRANS_FROM_CARD, user, AI, src) - else //No AI on the card, therefore the user wants to download one. - target.transfer_ai(AI_TRANS_TO_CARD, user, null, src) - if(AI) - log_combat(user, AI, "carded", src) - update_icon() //Whatever happened, update the card's state (icon, name) to match. - -/obj/item/aicard/update_icon() - cut_overlays() - if(AI) - name = "[initial(name)] - [AI.name]" - if(AI.stat == DEAD) - icon_state = "[initial(icon_state)]-404" - else - icon_state = "[initial(icon_state)]-full" - if(!AI.control_disabled) - add_overlay("[initial(icon_state)]-on") - AI.cancel_camera() - else - name = initial(name) - icon_state = initial(icon_state) - -/obj/item/aicard/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.hands_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "Intellicard", name, 500, 500, master_ui, state) - ui.open() - -/obj/item/aicard/ui_data() - var/list/data = list() - if(AI) - data["name"] = AI.name - data["laws"] = AI.laws.get_law_list(include_zeroth = 1) - data["health"] = (AI.health + 100) / 2 - data["wireless"] = !AI.control_disabled //todo disabled->enabled - data["radio"] = AI.radio_enabled - data["isDead"] = AI.stat == DEAD - data["isBraindead"] = AI.client ? FALSE : TRUE - data["wiping"] = flush - return data - -/obj/item/aicard/ui_act(action,params) - if(..()) - return - switch(action) - if("wipe") - if(flush) - flush = FALSE - else - var/confirm = alert("Are you sure you want to wipe this card's memory?", name, "Yes", "No") - if(confirm == "Yes" && !..()) - flush = TRUE - if(AI && AI.loc == src) - to_chat(AI, "Your core files are being wiped!") - while(AI.stat != DEAD && flush) - AI.adjustOxyLoss(5) - AI.updatehealth() - sleep(5) - flush = FALSE - . = TRUE - if("wireless") - AI.control_disabled = !AI.control_disabled - to_chat(AI, "[src]'s wireless port has been [AI.control_disabled ? "disabled" : "enabled"]!") - . = TRUE - if("radio") - AI.radio_enabled = !AI.radio_enabled - to_chat(AI, "Your Subspace Transceiver has been [AI.radio_enabled ? "enabled" : "disabled"]!") - . = TRUE - update_icon() +/obj/item/aicard + name = "intelliCard" + desc = "A storage device for AIs. Patent pending." + icon = 'icons/obj/aicards.dmi' + icon_state = "aicard" // aicard-full + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + w_class = WEIGHT_CLASS_SMALL + slot_flags = ITEM_SLOT_BELT + item_flags = NOBLUDGEON + var/flush = FALSE + var/mob/living/silicon/ai/AI + +/obj/item/aicard/aitater + name = "intelliTater" + desc = "A stylish upgrade (?) to the intelliCard." + icon_state = "aitater" + +/obj/item/aicard/aispook + name = "intelliLantern" + desc = "A spoOoOoky upgrade to the intelliCard." + icon_state = "aispook" + +/obj/item/aicard/suicide_act(mob/living/user) + user.visible_message("[user] is trying to upload [user.p_them()]self into [src]! That's not going to work out well!") + return BRUTELOSS + +/obj/item/aicard/afterattack(atom/target, mob/user, proximity) + . = ..() + if(!proximity || !target) + return + if(AI) //AI is on the card, implies user wants to upload it. + log_combat(user, AI, "uploaded", src, "to [target].") + target.transfer_ai(AI_TRANS_FROM_CARD, user, AI, src) + else //No AI on the card, therefore the user wants to download one. + target.transfer_ai(AI_TRANS_TO_CARD, user, null, src) + if(AI) + log_combat(user, AI, "carded", src) + update_icon() //Whatever happened, update the card's state (icon, name) to match. + +/obj/item/aicard/update_icon() + cut_overlays() + if(AI) + name = "[initial(name)] - [AI.name]" + if(AI.stat == DEAD) + icon_state = "[initial(icon_state)]-404" + else + icon_state = "[initial(icon_state)]-full" + if(!AI.control_disabled) + add_overlay("[initial(icon_state)]-on") + AI.cancel_camera() + else + name = initial(name) + icon_state = initial(icon_state) + +/obj/item/aicard/ui_state(mob/user) + return GLOB.hands_state + +/obj/item/aicard/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "Intellicard", name) + ui.open() + +/obj/item/aicard/ui_data() + var/list/data = list() + if(AI) + data["name"] = AI.name + data["laws"] = AI.laws.get_law_list(include_zeroth = TRUE, render_html = FALSE) + data["health"] = (AI.health + 100) / 2 + data["wireless"] = !AI.control_disabled //todo disabled->enabled + data["radio"] = AI.radio_enabled + data["isDead"] = AI.stat == DEAD + data["isBraindead"] = AI.client ? FALSE : TRUE + data["wiping"] = flush + return data + +/obj/item/aicard/ui_act(action,params) + if(..()) + return + switch(action) + if("wipe") + if(flush) + flush = FALSE + else + var/confirm = alert("Are you sure you want to wipe this card's memory?", name, "Yes", "No") + if(confirm == "Yes" && !..()) + flush = TRUE + if(AI && AI.loc == src) + to_chat(AI, "Your core files are being wiped!") + while(AI.stat != DEAD && flush) + AI.adjustOxyLoss(5) + AI.updatehealth() + sleep(5) + flush = FALSE + . = TRUE + if("wireless") + AI.control_disabled = !AI.control_disabled + to_chat(AI, "[src]'s wireless port has been [AI.control_disabled ? "disabled" : "enabled"]!") + . = TRUE + if("radio") + AI.radio_enabled = !AI.radio_enabled + to_chat(AI, "Your Subspace Transceiver has been [AI.radio_enabled ? "enabled" : "disabled"]!") + . = TRUE + update_icon() diff --git a/code/game/objects/items/devices/camera_bug.dm b/code/game/objects/items/devices/camera_bug.dm index 92a8a0c2469f..253188ec3f4e 100644 --- a/code/game/objects/items/devices/camera_bug.dm +++ b/code/game/objects/items/devices/camera_bug.dm @@ -1,311 +1,311 @@ - -#define BUGMODE_LIST 0 -#define BUGMODE_MONITOR 1 -#define BUGMODE_TRACK 2 - - - -/obj/item/camera_bug - name = "camera bug" - desc = "For illicit snooping through the camera network." - icon = 'icons/obj/device.dmi' - icon_state = "camera_bug" - w_class = WEIGHT_CLASS_TINY - item_state = "camera_bug" - throw_speed = 4 - throw_range = 20 - item_flags = NOBLUDGEON - - var/obj/machinery/camera/current = null - - var/last_net_update = 0 - var/list/bugged_cameras = list() - - var/track_mode = BUGMODE_LIST - var/last_tracked = 0 - var/refresh_interval = 50 - - var/tracked_name = null - var/atom/tracking = null - - var/last_found = null - var/last_seen = null - -/obj/item/camera_bug/New() - ..() - START_PROCESSING(SSobj, src) - -/obj/item/camera_bug/Destroy() - get_cameras() - for(var/cam_tag in bugged_cameras) - var/obj/machinery/camera/camera = bugged_cameras[cam_tag] - if(camera && camera.bug == src) - camera.bug = null - bugged_cameras = list() - if(tracking) - tracking = null - return ..() - -/obj/item/camera_bug/interact(mob/user) - ui_interact(user) - -/obj/item/camera_bug/ui_interact(mob/user = usr) - . = ..() - var/datum/browser/popup = new(user, "camerabug","Camera Bug",nref=src) - popup.set_content(menu(get_cameras())) - popup.open() - -/obj/item/camera_bug/attack_self(mob/user) - user.set_machine(src) - interact(user) - -/obj/item/camera_bug/check_eye(mob/user) - if ( loc != user || user.incapacitated() || user.is_blind() || !current ) - user.unset_machine() - return 0 - var/turf/T_user = get_turf(user.loc) - var/turf/T_current = get_turf(current) - if(T_user.z != T_current.z || !current.can_use()) - to_chat(user, "[src] has lost the signal.") - current = null - user.unset_machine() - return 0 - return 1 -/obj/item/camera_bug/on_unset_machine(mob/user) - user.reset_perspective(null) - -/obj/item/camera_bug/proc/get_cameras() - if( world.time > (last_net_update + 100)) - bugged_cameras = list() - for(var/obj/machinery/camera/camera in GLOB.cameranet.cameras) - if(camera.machine_stat || !camera.can_use()) - continue - if(length(list("ss13","mine", "rd", "labor", "toxins", "minisat")&camera.network)) - bugged_cameras[camera.c_tag] = camera - return sortList(bugged_cameras) - - -/obj/item/camera_bug/proc/menu(list/cameras) - if(!cameras || !cameras.len) - return "No bugged cameras found." - - var/html - switch(track_mode) - if(BUGMODE_LIST) - html = "

                        Select a camera:

                        \[Cancel camera view\]
                        " - for(var/entry in cameras) - var/obj/machinery/camera/C = cameras[entry] - if(QDELETED(C)) - continue - var/functions = "" - if(C.bug == src) - functions = " - \[Monitor\]\[Disable\]" - else - functions = " - \[Monitor\]" - html += "" - - if(BUGMODE_MONITOR) - if(current) - html = "Analyzing Camera '[current.c_tag]' \[Select Camera\]
                        " - html += camera_report() - else - track_mode = BUGMODE_LIST - return .(cameras) - if(BUGMODE_TRACK) - if(tracking) - html = "Tracking '[tracked_name]' \[Cancel Tracking\]\[Cancel camera view\]
                        " - if(last_found) - var/time_diff = round((world.time - last_seen) / 150) - var/obj/machinery/camera/C = bugged_cameras[last_found] - var/outstring - if(C) - outstring = "[last_found]" - else - outstring = last_found - if(!time_diff) - html += "Last seen near [outstring] (now)
                        " - else - // 15 second intervals ~ 1/4 minute - var/m = round(time_diff/4) - var/s = (time_diff - 4*m) * 15 - if(!s) - s = "00" - html += "Last seen near [outstring] ([m]:[s] minute\s ago)
                        " - if( C && (C.bug == src)) //Checks to see if the camera has a bug - html += "\[Disable\]" - - else - html += "Not yet seen." - else - track_mode = BUGMODE_LIST - return .(cameras) - return html - -/obj/item/camera_bug/proc/get_seens() - if(current && current.can_use()) - var/list/seen = current.can_see() - return seen - -/obj/item/camera_bug/proc/camera_report() - // this should only be called if current exists - var/dat = "" - var/list/seen = get_seens() - if(seen && seen.len >= 1) - var/list/names = list() - for(var/obj/singularity/S in seen) // god help you if you see more than one - if(S.name in names) - names[S.name]++ - dat += "[S.name] ([names[S.name]])" - else - names[S.name] = 1 - dat += "[S.name]" - var/stage = round(S.current_size / 2)+1 - dat += " (Stage [stage])" - dat += " \[Track\]
                        " - - for(var/obj/mecha/M in seen) - if(M.name in names) - names[M.name]++ - dat += "[M.name] ([names[M.name]])" - else - names[M.name] = 1 - dat += "[M.name]" - dat += " \[Track\]
                        " - - - for(var/mob/living/M in seen) - if(M.name in names) - names[M.name]++ - dat += "[M.name] ([names[M.name]])" - else - names[M.name] = 1 - dat += "[M.name]" - if(!(M.mobility_flags & MOBILITY_STAND)) - if(M.buckled) - dat += " (Sitting)" - else - dat += " (Laying down)" - dat += " \[Track\]
                        " - if(length(dat) == 0) - dat += "No motion detected." - return dat - else - return "Camera Offline
                        " - -/obj/item/camera_bug/Topic(href,list/href_list) - if(usr != loc) - usr.unset_machine() - usr << browse(null, "window=camerabug") - return - usr.set_machine(src) - if("mode" in href_list) - track_mode = text2num(href_list["mode"]) - if("monitor" in href_list) - //You can't locate on a list with keys - var/list/cameras = flatten_list(bugged_cameras) - var/obj/machinery/camera/C = locate(href_list["monitor"]) in cameras - if(C && istype(C)) - if(!same_z_level(C)) - return - track_mode = BUGMODE_MONITOR - current = C - usr.reset_perspective(null) - interact() - if("track" in href_list) - var/list/seen = get_seens() - if(seen && seen.len >= 1) - var/atom/A = locate(href_list["track"]) in seen - if(A && istype(A)) - tracking = A - tracked_name = A.name - last_found = current.c_tag - last_seen = world.time - track_mode = BUGMODE_TRACK - if("emp" in href_list) - //You can't locate on a list with keys - var/list/cameras = flatten_list(bugged_cameras) - var/obj/machinery/camera/C = locate(href_list["emp"]) in cameras - if(C && istype(C) && C.bug == src) - if(!same_z_level(C)) - return - C.emp_act(EMP_HEAVY) - C.bug = null - bugged_cameras -= C.c_tag - interact() - return - if("close" in href_list) - usr.unset_machine() - current = null - return - if("view" in href_list) - //You can't locate on a list with keys - var/list/cameras = flatten_list(bugged_cameras) - var/obj/machinery/camera/C = locate(href_list["view"]) in cameras - if(C && istype(C)) - if(!same_z_level(C)) - return - if(!C.can_use()) - to_chat(usr, "Something's wrong with that camera! You can't get a feed.") - return - current = C - spawn(6) - if(src.check_eye(usr)) - usr.reset_perspective(C) - interact() - else - usr.unset_machine() - usr << browse(null, "window=camerabug") - return - else - usr.unset_machine() - - interact() - -/obj/item/camera_bug/process() - if(track_mode == BUGMODE_LIST || (world.time < (last_tracked + refresh_interval))) - return - last_tracked = world.time - if(track_mode == BUGMODE_TRACK ) // search for user - // Note that it will be tricked if your name appears to change. - // This is not optimal but it is better than tracking you relentlessly despite everything. - if(!tracking) - src.updateSelfDialog() - return - - if(tracking.name != tracked_name) // Hiding their identity, tricksy - var/mob/M = tracking - if(istype(M)) - if(!(tracked_name == "Unknown" && findtext(tracking.name,"Unknown"))) // we saw then disguised before - if(!(tracked_name == M.real_name && findtext(tracking.name,M.real_name))) // or they're still ID'd - src.updateSelfDialog()//But if it's neither of those cases - return // you won't find em on the cameras - else - src.updateSelfDialog() - return - - var/list/tracking_cams = list() - var/list/b_cams = get_cameras() - for(var/entry in b_cams) - tracking_cams += b_cams[entry] - var/list/target_region = view(tracking) - - for(var/obj/machinery/camera/C in (target_region & tracking_cams)) - if(!can_see(C,tracking)) // target may have xray, that doesn't make them visible to cameras - continue - if(C.can_use()) - last_found = C.c_tag - last_seen = world.time - break - src.updateSelfDialog() - -/obj/item/camera_bug/proc/same_z_level(var/obj/machinery/camera/C) - var/turf/T_cam = get_turf(C) - var/turf/T_bug = get_turf(loc) - if(!T_bug || T_cam.z != T_bug.z) - to_chat(usr, "You can't get a signal!") - return FALSE - return TRUE - -#undef BUGMODE_LIST -#undef BUGMODE_MONITOR -#undef BUGMODE_TRACK + +#define BUGMODE_LIST 0 +#define BUGMODE_MONITOR 1 +#define BUGMODE_TRACK 2 + + + +/obj/item/camera_bug + name = "camera bug" + desc = "For illicit snooping through the camera network." + icon = 'icons/obj/device.dmi' + icon_state = "camera_bug" + w_class = WEIGHT_CLASS_TINY + item_state = "camera_bug" + throw_speed = 4 + throw_range = 20 + item_flags = NOBLUDGEON + + var/obj/machinery/camera/current = null + + var/last_net_update = 0 + var/list/bugged_cameras = list() + + var/track_mode = BUGMODE_LIST + var/last_tracked = 0 + var/refresh_interval = 50 + + var/tracked_name = null + var/atom/tracking = null + + var/last_found = null + var/last_seen = null + +/obj/item/camera_bug/New() + ..() + START_PROCESSING(SSobj, src) + +/obj/item/camera_bug/Destroy() + get_cameras() + for(var/cam_tag in bugged_cameras) + var/obj/machinery/camera/camera = bugged_cameras[cam_tag] + if(camera && camera.bug == src) + camera.bug = null + bugged_cameras = list() + if(tracking) + tracking = null + return ..() + +/obj/item/camera_bug/interact(mob/user) + ui_interact(user) + +/obj/item/camera_bug/ui_interact(mob/user = usr) + . = ..() + var/datum/browser/popup = new(user, "camerabug","Camera Bug",nref=src) + popup.set_content(menu(get_cameras())) + popup.open() + +/obj/item/camera_bug/attack_self(mob/user) + user.set_machine(src) + interact(user) + +/obj/item/camera_bug/check_eye(mob/user) + if ( loc != user || user.incapacitated() || user.is_blind() || !current ) + user.unset_machine() + return 0 + var/turf/T_user = get_turf(user.loc) + var/turf/T_current = get_turf(current) + if(T_user.z != T_current.z || !current.can_use()) + to_chat(user, "[src] has lost the signal.") + current = null + user.unset_machine() + return 0 + return 1 +/obj/item/camera_bug/on_unset_machine(mob/user) + user.reset_perspective(null) + +/obj/item/camera_bug/proc/get_cameras() + if( world.time > (last_net_update + 100)) + bugged_cameras = list() + for(var/obj/machinery/camera/camera in GLOB.cameranet.cameras) + if(camera.machine_stat || !camera.can_use()) + continue + if(length(list("ss13","mine", "rd", "labor", "toxins", "minisat")&camera.network)) + bugged_cameras[camera.c_tag] = camera + return sortList(bugged_cameras) + + +/obj/item/camera_bug/proc/menu(list/cameras) + if(!cameras || !cameras.len) + return "No bugged cameras found." + + var/html + switch(track_mode) + if(BUGMODE_LIST) + html = "

                        Select a camera:

                        \[Cancel camera view\]
                        [entry][functions]
                        " + for(var/entry in cameras) + var/obj/machinery/camera/C = cameras[entry] + if(QDELETED(C)) + continue + var/functions = "" + if(C.bug == src) + functions = " - \[Monitor\]\[Disable\]" + else + functions = " - \[Monitor\]" + html += "" + + if(BUGMODE_MONITOR) + if(current) + html = "Analyzing Camera '[current.c_tag]' \[Select Camera\]
                        " + html += camera_report() + else + track_mode = BUGMODE_LIST + return .(cameras) + if(BUGMODE_TRACK) + if(tracking) + html = "Tracking '[tracked_name]' \[Cancel Tracking\]\[Cancel camera view\]
                        " + if(last_found) + var/time_diff = round((world.time - last_seen) / 150) + var/obj/machinery/camera/C = bugged_cameras[last_found] + var/outstring + if(C) + outstring = "[last_found]" + else + outstring = last_found + if(!time_diff) + html += "Last seen near [outstring] (now)
                        " + else + // 15 second intervals ~ 1/4 minute + var/m = round(time_diff/4) + var/s = (time_diff - 4*m) * 15 + if(!s) + s = "00" + html += "Last seen near [outstring] ([m]:[s] minute\s ago)
                        " + if( C && (C.bug == src)) //Checks to see if the camera has a bug + html += "\[Disable\]" + + else + html += "Not yet seen." + else + track_mode = BUGMODE_LIST + return .(cameras) + return html + +/obj/item/camera_bug/proc/get_seens() + if(current && current.can_use()) + var/list/seen = current.can_see() + return seen + +/obj/item/camera_bug/proc/camera_report() + // this should only be called if current exists + var/dat = "" + var/list/seen = get_seens() + if(seen && seen.len >= 1) + var/list/names = list() + for(var/obj/singularity/S in seen) // god help you if you see more than one + if(S.name in names) + names[S.name]++ + dat += "[S.name] ([names[S.name]])" + else + names[S.name] = 1 + dat += "[S.name]" + var/stage = round(S.current_size / 2)+1 + dat += " (Stage [stage])" + dat += " \[Track\]
                        " + + for(var/obj/mecha/M in seen) + if(M.name in names) + names[M.name]++ + dat += "[M.name] ([names[M.name]])" + else + names[M.name] = 1 + dat += "[M.name]" + dat += " \[Track\]
                        " + + + for(var/mob/living/M in seen) + if(M.name in names) + names[M.name]++ + dat += "[M.name] ([names[M.name]])" + else + names[M.name] = 1 + dat += "[M.name]" + if(!(M.mobility_flags & MOBILITY_STAND)) + if(M.buckled) + dat += " (Sitting)" + else + dat += " (Laying down)" + dat += " \[Track\]
                        " + if(length(dat) == 0) + dat += "No motion detected." + return dat + else + return "Camera Offline
                        " + +/obj/item/camera_bug/Topic(href,list/href_list) + if(usr != loc) + usr.unset_machine() + usr << browse(null, "window=camerabug") + return + usr.set_machine(src) + if("mode" in href_list) + track_mode = text2num(href_list["mode"]) + if("monitor" in href_list) + //You can't locate on a list with keys + var/list/cameras = flatten_list(bugged_cameras) + var/obj/machinery/camera/C = locate(href_list["monitor"]) in cameras + if(C && istype(C)) + if(!same_z_level(C)) + return + track_mode = BUGMODE_MONITOR + current = C + usr.reset_perspective(null) + interact() + if("track" in href_list) + var/list/seen = get_seens() + if(seen && seen.len >= 1) + var/atom/A = locate(href_list["track"]) in seen + if(A && istype(A)) + tracking = A + tracked_name = A.name + last_found = current.c_tag + last_seen = world.time + track_mode = BUGMODE_TRACK + if("emp" in href_list) + //You can't locate on a list with keys + var/list/cameras = flatten_list(bugged_cameras) + var/obj/machinery/camera/C = locate(href_list["emp"]) in cameras + if(C && istype(C) && C.bug == src) + if(!same_z_level(C)) + return + C.emp_act(EMP_HEAVY) + C.bug = null + bugged_cameras -= C.c_tag + interact() + return + if("close" in href_list) + usr.unset_machine() + current = null + return + if("view" in href_list) + //You can't locate on a list with keys + var/list/cameras = flatten_list(bugged_cameras) + var/obj/machinery/camera/C = locate(href_list["view"]) in cameras + if(C && istype(C)) + if(!same_z_level(C)) + return + if(!C.can_use()) + to_chat(usr, "Something's wrong with that camera! You can't get a feed.") + return + current = C + spawn(6) + if(src.check_eye(usr)) + usr.reset_perspective(C) + interact() + else + usr.unset_machine() + usr << browse(null, "window=camerabug") + return + else + usr.unset_machine() + + interact() + +/obj/item/camera_bug/process() + if(track_mode == BUGMODE_LIST || (world.time < (last_tracked + refresh_interval))) + return + last_tracked = world.time + if(track_mode == BUGMODE_TRACK ) // search for user + // Note that it will be tricked if your name appears to change. + // This is not optimal but it is better than tracking you relentlessly despite everything. + if(!tracking) + src.updateSelfDialog() + return + + if(tracking.name != tracked_name) // Hiding their identity, tricksy + var/mob/M = tracking + if(istype(M)) + if(!(tracked_name == "Unknown" && findtext(tracking.name,"Unknown"))) // we saw then disguised before + if(!(tracked_name == M.real_name && findtext(tracking.name,M.real_name))) // or they're still ID'd + src.updateSelfDialog()//But if it's neither of those cases + return // you won't find em on the cameras + else + src.updateSelfDialog() + return + + var/list/tracking_cams = list() + var/list/b_cams = get_cameras() + for(var/entry in b_cams) + tracking_cams += b_cams[entry] + var/list/target_region = view(tracking) + + for(var/obj/machinery/camera/C in (target_region & tracking_cams)) + if(!can_see(C,tracking)) // target may have xray, that doesn't make them visible to cameras + continue + if(C.can_use()) + last_found = C.c_tag + last_seen = world.time + break + src.updateSelfDialog() + +/obj/item/camera_bug/proc/same_z_level(var/obj/machinery/camera/C) + var/turf/T_cam = get_turf(C) + var/turf/T_bug = get_turf(loc) + if(!T_bug || T_cam.z != T_bug.z) + to_chat(usr, "You can't get a signal!") + return FALSE + return TRUE + +#undef BUGMODE_LIST +#undef BUGMODE_MONITOR +#undef BUGMODE_TRACK diff --git a/code/game/objects/items/devices/flashlight.dm b/code/game/objects/items/devices/flashlight.dm index 226eb270945d..2a439f8fe7bc 100644 --- a/code/game/objects/items/devices/flashlight.dm +++ b/code/game/objects/items/devices/flashlight.dm @@ -1,571 +1,571 @@ -/obj/item/flashlight - name = "flashlight" - desc = "A hand-held emergency light." - custom_price = 100 - icon = 'icons/obj/lighting.dmi' - icon_state = "flashlight" - item_state = "flashlight" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - w_class = WEIGHT_CLASS_SMALL - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BELT - custom_materials = list(/datum/material/iron=50, /datum/material/glass=20) - actions_types = list(/datum/action/item_action/toggle_light) - light_color = "#FFCC66" //Cit lighting - var/on = FALSE - var/brightness_on = 4 //range of light when on - var/flashlight_power = 0.8 //strength of the light when on - -/obj/item/flashlight/Initialize() - . = ..() - if(icon_state == "[initial(icon_state)]-on") - on = TRUE - update_brightness() - -/obj/item/flashlight/proc/update_brightness(mob/user = null) - if(on) - icon_state = "[initial(icon_state)]-on" - if(flashlight_power) - set_light(l_range = brightness_on, l_power = flashlight_power) - else - set_light(brightness_on) - else - icon_state = initial(icon_state) - set_light(0) - -/obj/item/flashlight/attack_self(mob/user) - on = !on - update_brightness(user) - for(var/X in actions) - var/datum/action/A = X - A.UpdateButtonIcon() - return 1 - -/obj/item/flashlight/suicide_act(mob/living/carbon/human/user) - if (user.is_blind()) - user.visible_message("[user] is putting [src] close to [user.p_their()] eyes and turning it on... but [user.p_theyre()] blind!") - return SHAME - user.visible_message("[user] is putting [src] close to [user.p_their()] eyes and turning it on! It looks like [user.p_theyre()] trying to commit suicide!") - return (FIRELOSS) - -/obj/item/flashlight/attack(mob/living/carbon/M, mob/living/carbon/human/user) - add_fingerprint(user) - if(istype(M) && on && (user.zone_selected in list(BODY_ZONE_PRECISE_EYES, BODY_ZONE_PRECISE_MOUTH))) - - if((HAS_TRAIT(user, TRAIT_CLUMSY) || HAS_TRAIT(user, TRAIT_DUMB)) && prob(50)) //too dumb to use flashlight properly - return ..() //just hit them in the head - - if(!user.IsAdvancedToolUser()) - to_chat(user, "You don't have the dexterity to do this!") - return - - if(!M.get_bodypart(BODY_ZONE_HEAD)) - to_chat(user, "[M] doesn't have a head!") - return - - if(flashlight_power < 1) - to_chat(user, "\The [src] isn't bright enough to see anything! ") - return - - switch(user.zone_selected) - if(BODY_ZONE_PRECISE_EYES) - if((M.head && M.head.flags_cover & HEADCOVERSEYES) || (M.wear_mask && M.wear_mask.flags_cover & MASKCOVERSEYES) || (M.glasses && M.glasses.flags_cover & GLASSESCOVERSEYES)) - to_chat(user, "You're going to need to remove that [(M.head && M.head.flags_cover & HEADCOVERSEYES) ? "helmet" : (M.wear_mask && M.wear_mask.flags_cover & MASKCOVERSEYES) ? "mask": "glasses"] first!") - return - - var/obj/item/organ/eyes/E = M.getorganslot(ORGAN_SLOT_EYES) - if(!E) - to_chat(user, "[M] doesn't have any eyes!") - return - - if(M == user) //they're using it on themselves - if(M.flash_act(visual = 1)) - M.visible_message("[M] directs [src] to [M.p_their()] eyes.", "You wave the light in front of your eyes! Trippy!") - else - M.visible_message("[M] directs [src] to [M.p_their()] eyes.", "You wave the light in front of your eyes.") - else - user.visible_message("[user] directs [src] to [M]'s eyes.", \ - "You direct [src] to [M]'s eyes.") - if(M.stat == DEAD || (M.is_blind()) || !M.flash_act(visual = 1)) //mob is dead or fully blind - to_chat(user, "[M]'s pupils don't react to the light!") - else if(M.dna && M.dna.check_mutation(XRAY)) //mob has X-ray vision - to_chat(user, "[M]'s pupils give an eerie glow!") - else //they're okay! - to_chat(user, "[M]'s pupils narrow.") - - if(BODY_ZONE_PRECISE_MOUTH) - - if(M.is_mouth_covered()) - to_chat(user, "You're going to need to remove that [(M.head && M.head.flags_cover & HEADCOVERSMOUTH) ? "helmet" : "mask"] first!") - return - - var/their = M.p_their() - - var/list/mouth_organs = new - for(var/obj/item/organ/O in M.internal_organs) - if(O.zone == BODY_ZONE_PRECISE_MOUTH) - mouth_organs.Add(O) - var/organ_list = "" - var/organ_count = LAZYLEN(mouth_organs) - if(organ_count) - for(var/I in 1 to organ_count) - if(I > 1) - if(I == mouth_organs.len) - organ_list += ", and " - else - organ_list += ", " - var/obj/item/organ/O = mouth_organs[I] - organ_list += (O.gender == "plural" ? O.name : "\an [O.name]") - - var/pill_count = 0 - for(var/datum/action/item_action/hands_free/activate_pill/AP in M.actions) - pill_count++ - - if(M == user) - var/can_use_mirror = FALSE - if(isturf(user.loc)) - var/obj/structure/mirror/mirror = locate(/obj/structure/mirror, user.loc) - if(mirror) - switch(user.dir) - if(NORTH) - can_use_mirror = mirror.pixel_y > 0 - if(SOUTH) - can_use_mirror = mirror.pixel_y < 0 - if(EAST) - can_use_mirror = mirror.pixel_x > 0 - if(WEST) - can_use_mirror = mirror.pixel_x < 0 - - M.visible_message("[M] directs [src] to [their] mouth.", \ - "You point [src] into your mouth.") - if(!can_use_mirror) - to_chat(user, "You can't see anything without a mirror.") - return - if(organ_count) - to_chat(user, "Inside your mouth [organ_count > 1 ? "are" : "is"] [organ_list].") - else - to_chat(user, "There's nothing inside your mouth.") - if(pill_count) - to_chat(user, "You have [pill_count] implanted pill[pill_count > 1 ? "s" : ""].") - - else - user.visible_message("[user] directs [src] to [M]'s mouth.",\ - "You direct [src] to [M]'s mouth.") - if(organ_count) - to_chat(user, "Inside [their] mouth [organ_count > 1 ? "are" : "is"] [organ_list].") - else - to_chat(user, "[M] doesn't have any organs in [their] mouth.") - if(pill_count) - to_chat(user, "[M] has [pill_count] pill[pill_count > 1 ? "s" : ""] implanted in [their] teeth.") - - else - return ..() - -/obj/item/flashlight/pen - name = "penlight" - desc = "A pen-sized light, used by medical staff. It can also be used to create a hologram to alert people of incoming medical assistance." - icon_state = "penlight" - item_state = "" - flags_1 = CONDUCT_1 - brightness_on = 2 - light_color = "#FFDDCC" - flashlight_power = 0.3 - var/holo_cooldown = 0 - -/obj/item/flashlight/pen/afterattack(atom/target, mob/user, proximity_flag) - . = ..() - if(!proximity_flag) - if(holo_cooldown > world.time) - to_chat(user, "[src] is not ready yet!") - return - var/T = get_turf(target) - if(locate(/mob/living) in T) - new /obj/effect/temp_visual/medical_holosign(T,user) //produce a holographic glow - holo_cooldown = world.time + 100 - return - -/obj/effect/temp_visual/medical_holosign - name = "medical holosign" - desc = "A small holographic glow that indicates a medic is coming to treat a patient." - icon_state = "medi_holo" - duration = 30 - -/obj/effect/temp_visual/medical_holosign/Initialize(mapload, creator) - . = ..() - playsound(loc, 'sound/machines/ping.ogg', 50, FALSE) //make some noise! - if(creator) - visible_message("[creator] created a medical hologram!") - - -/obj/item/flashlight/seclite - name = "seclite" - desc = "A robust flashlight used by security." - icon_state = "seclite" - item_state = "seclite" - lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' - force = 9 // Not as good as a stun baton. - brightness_on = 5 // A little better than the standard flashlight. - hitsound = 'sound/weapons/genhit1.ogg' - light_color = "#CDDDFF" //Cit lighting - flashlight_power = 0.9 //Cit lighting - -// the desk lamps are a bit special -/obj/item/flashlight/lamp - name = "desk lamp" - desc = "A desk lamp with an adjustable mount." - icon_state = "lamp" - item_state = "lamp" - lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' - righthand_file = 'icons/mob/inhands/items_righthand.dmi' - force = 10 - brightness_on = 5 - w_class = WEIGHT_CLASS_BULKY - flags_1 = CONDUCT_1 - custom_materials = null - on = TRUE - light_color = "#FFDDBB" //Cit lighting - flashlight_power = 0.8 //Cit lighting - - -// green-shaded desk lamp -/obj/item/flashlight/lamp/green - desc = "A classic green-shaded desk lamp." - icon_state = "lampgreen" - item_state = "lampgreen" - - - -/obj/item/flashlight/lamp/verb/toggle_light() - set name = "Toggle light" - set category = "Object" - set src in oview(1) - - if(!usr.stat) - attack_self(usr) - -//Bananalamp -/obj/item/flashlight/lamp/bananalamp - name = "banana lamp" - desc = "Only a clown would think to make a ghetto banana-shaped lamp. Even has a goofy pullstring." - icon_state = "bananalamp" - item_state = "bananalamp" - -// FLARES - -/obj/item/flashlight/flare - name = "flare" - desc = "A red Nanotrasen issued flare. There are instructions on the side, it reads 'pull cord, make light'." - w_class = WEIGHT_CLASS_SMALL - brightness_on = 7 // Pretty bright. - icon_state = "flare" - item_state = "flare" - actions_types = list() - var/fuel = 0 - var/on_damage = 7 - var/produce_heat = 1500 - heat = 1000 - light_color = LIGHT_COLOR_FLARE - grind_results = list(/datum/reagent/sulfur = 15) - light_color = "#FA421A" //Cit lighting - flashlight_power = 0.8 //Cit lighting - -/obj/item/flashlight/flare/Initialize() - . = ..() - fuel = rand(800, 1000) // Sorry for changing this so much but I keep under-estimating how long X number of ticks last in seconds. - -/obj/item/flashlight/flare/process() - open_flame(heat) - fuel = max(fuel - 1, 0) - if(!fuel || !on) - turn_off() - if(!fuel) - icon_state = "[initial(icon_state)]-empty" - STOP_PROCESSING(SSobj, src) - -/obj/item/flashlight/flare/ignition_effect(atom/A, mob/user) - . = fuel && on ? "[user] lights [A] with [src] like a real badass." : "" - -/obj/item/flashlight/flare/proc/turn_off() - on = FALSE - force = initial(src.force) - damtype = initial(src.damtype) - if(ismob(loc)) - var/mob/U = loc - update_brightness(U) - else - update_brightness(null) - -/obj/item/flashlight/flare/update_brightness(mob/user = null) - ..() - if(on) - item_state = "[initial(item_state)]-on" - else - item_state = "[initial(item_state)]" - -/obj/item/flashlight/flare/attack_self(mob/user) - - // Usual checks - if(!fuel) - to_chat(user, "[src] is out of fuel!") - return - if(on) - to_chat(user, "[src] is already on!") - return - - . = ..() - // All good, turn it on. - if(.) - user.visible_message("[user] lights \the [src].", "You light \the [src]!") - force = on_damage - damtype = "fire" - START_PROCESSING(SSobj, src) - -/obj/item/flashlight/flare/get_temperature() - return on * heat - -/obj/item/flashlight/flare/torch - name = "torch" - desc = "A torch fashioned from some leaves and a log." - w_class = WEIGHT_CLASS_BULKY - brightness_on = 4 - icon_state = "torch" - item_state = "torch" - lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' - righthand_file = 'icons/mob/inhands/items_righthand.dmi' - light_color = LIGHT_COLOR_ORANGE - on_damage = 10 - slot_flags = null - light_color = "#FAA44B" //Cit lighting - flashlight_power = 0.8 //Cit lighting - -/obj/item/flashlight/lantern - name = "lantern" - icon_state = "lantern" - item_state = "lantern" - lefthand_file = 'icons/mob/inhands/equipment/mining_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/mining_righthand.dmi' - desc = "A mining lantern." - brightness_on = 6 // luminosity when on - light_color = "#FFAA44" //Cit lighting - flashlight_power = 0.75 //Cit lighting - -/obj/item/flashlight/lantern/heirloom_moth - name = "old lantern" - desc = "An old lantern that has seen plenty of use." - brightness_on = 4 - -/obj/item/flashlight/lantern/syndicate - name = "suspicious lantern" - desc = "A suspicious looking lantern." - icon_state = "syndilantern" - item_state = "syndilantern" - brightness_on = 10 - -/obj/item/flashlight/slime - gender = PLURAL - name = "glowing slime extract" - desc = "Extract from a yellow slime. It emits a strong light when squeezed." - icon = 'icons/obj/lighting.dmi' - icon_state = "slime" - item_state = "slime" - w_class = WEIGHT_CLASS_SMALL - slot_flags = ITEM_SLOT_BELT - custom_materials = null - brightness_on = 6 //luminosity when on - light_color = "#FFEEAA" //Cit lighting - flashlight_power = 0.6 //Cit lighting - -/obj/item/flashlight/emp - var/emp_max_charges = 4 - var/emp_cur_charges = 4 - var/charge_tick = 0 - -/obj/item/flashlight/emp/New() - ..() - START_PROCESSING(SSobj, src) - -/obj/item/flashlight/emp/Destroy() - STOP_PROCESSING(SSobj, src) - . = ..() - -/obj/item/flashlight/emp/process() - charge_tick++ - if(charge_tick < 10) - return FALSE - charge_tick = 0 - emp_cur_charges = min(emp_cur_charges+1, emp_max_charges) - return TRUE - -/obj/item/flashlight/emp/attack(mob/living/M, mob/living/user) - if(on && (user.zone_selected in list(BODY_ZONE_PRECISE_EYES, BODY_ZONE_PRECISE_MOUTH))) // call original attack when examining organs - ..() - return - -/obj/item/flashlight/emp/afterattack(atom/movable/A, mob/user, proximity) - . = ..() - if(!proximity) - return - - if(emp_cur_charges > 0) - emp_cur_charges -= 1 - - if(ismob(A)) - var/mob/M = A - log_combat(user, M, "attacked", "EMP-light") - M.visible_message("[user] blinks \the [src] at \the [A].", \ - "[user] blinks \the [src] at you.") - else - A.visible_message("[user] blinks \the [src] at \the [A].") - to_chat(user, "\The [src] now has [emp_cur_charges] charge\s.") - A.emp_act(EMP_HEAVY) - else - to_chat(user, "\The [src] needs time to recharge!") - return - -/obj/item/flashlight/emp/debug //for testing emp_act() - name = "debug EMP flashlight" - emp_max_charges = 100 - emp_cur_charges = 100 - -// Glowsticks, in the uncomfortable range of similar to flares, -// but not similar enough to make it worth a refactor -/obj/item/flashlight/glowstick - name = "glowstick" - desc = "A military-grade glowstick." - custom_price = 50 - w_class = WEIGHT_CLASS_SMALL - brightness_on = 4 - color = LIGHT_COLOR_GREEN - icon_state = "glowstick" - item_state = "glowstick" - grind_results = list(/datum/reagent/phenol = 15, /datum/reagent/hydrogen = 10, /datum/reagent/oxygen = 5) //Meth-in-a-stick - var/fuel = 0 - -/obj/item/flashlight/glowstick/Initialize() - fuel = rand(1600, 2000) - light_color = color - . = ..() - -/obj/item/flashlight/glowstick/Destroy() - STOP_PROCESSING(SSobj, src) - . = ..() - -/obj/item/flashlight/glowstick/process() - fuel = max(fuel - 1, 0) - if(!fuel) - turn_off() - STOP_PROCESSING(SSobj, src) - update_icon() - -/obj/item/flashlight/glowstick/proc/turn_off() - on = FALSE - update_icon() - -/obj/item/flashlight/glowstick/update_icon() - item_state = "glowstick" - cut_overlays() - if(!fuel) - icon_state = "glowstick-empty" - cut_overlays() - set_light(0) - else if(on) - var/mutable_appearance/glowstick_overlay = mutable_appearance(icon, "glowstick-glow") - glowstick_overlay.color = color - add_overlay(glowstick_overlay) - item_state = "glowstick-on" - set_light(brightness_on) - else - icon_state = "glowstick" - cut_overlays() - -/obj/item/flashlight/glowstick/attack_self(mob/user) - if(!fuel) - to_chat(user, "[src] is spent.") - return - if(on) - to_chat(user, "[src] is already lit!") - return - - . = ..() - if(.) - user.visible_message("[user] cracks and shakes [src].", "You crack and shake [src], turning it on!") - START_PROCESSING(SSobj, src) - -/obj/item/flashlight/glowstick/suicide_act(mob/living/carbon/human/user) - if(!fuel) - user.visible_message("[user] is trying to squirt [src]'s fluids into [user.p_their()] eyes... but it's empty!") - return SHAME - var/obj/item/organ/eyes/eyes = user.getorganslot(ORGAN_SLOT_EYES) - if(!eyes) - user.visible_message("[user] is trying to squirt [src]'s fluids into [user.p_their()] eyes... but [user.p_they()] don't have any!") - return SHAME - user.visible_message("[user] is squirting [src]'s fluids into [user.p_their()] eyes! It looks like [user.p_theyre()] trying to commit suicide!") - fuel = 0 - return (FIRELOSS) - -/obj/item/flashlight/glowstick/red - name = "red glowstick" - color = LIGHT_COLOR_RED - -/obj/item/flashlight/glowstick/blue - name = "blue glowstick" - color = LIGHT_COLOR_BLUE - -/obj/item/flashlight/glowstick/cyan - name = "cyan glowstick" - color = LIGHT_COLOR_CYAN - -/obj/item/flashlight/glowstick/orange - name = "orange glowstick" - color = LIGHT_COLOR_ORANGE - -/obj/item/flashlight/glowstick/yellow - name = "yellow glowstick" - color = LIGHT_COLOR_YELLOW - -/obj/item/flashlight/glowstick/pink - name = "pink glowstick" - color = LIGHT_COLOR_PINK - -/obj/effect/spawner/lootdrop/glowstick - name = "random colored glowstick" - icon = 'icons/obj/lighting.dmi' - icon_state = "random_glowstick" - -/obj/effect/spawner/lootdrop/glowstick/Initialize() - loot = typesof(/obj/item/flashlight/glowstick) - . = ..() - -/obj/item/flashlight/spotlight //invisible lighting source - name = "disco light" - desc = "Groovy..." - icon_state = null - light_color = null - brightness_on = 0 - light_range = 0 - light_power = 10 - alpha = 0 - layer = 0 - on = TRUE - anchored = TRUE - var/range = null - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - -/obj/item/flashlight/flashdark - name = "flashdark" - desc = "A strange device manufactured with mysterious elements that somehow emits darkness. Or maybe it just sucks in light? Nobody knows for sure." - icon_state = "flashdark" - item_state = "flashdark" - brightness_on = 2.5 - flashlight_power = -3 - -/obj/item/flashlight/eyelight - name = "eyelight" - desc = "This shouldn't exist outside of someone's head, how are you seeing this?" - brightness_on = 15 - flashlight_power = 1 - flags_1 = CONDUCT_1 - item_flags = DROPDEL - actions_types = list() +/obj/item/flashlight + name = "flashlight" + desc = "A hand-held emergency light." + custom_price = 100 + icon = 'icons/obj/lighting.dmi' + icon_state = "flashlight" + item_state = "flashlight" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + w_class = WEIGHT_CLASS_SMALL + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BELT + custom_materials = list(/datum/material/iron=50, /datum/material/glass=20) + actions_types = list(/datum/action/item_action/toggle_light) + light_color = "#FFCC66" //Cit lighting + var/on = FALSE + var/brightness_on = 4 //range of light when on + var/flashlight_power = 0.8 //strength of the light when on + +/obj/item/flashlight/Initialize() + . = ..() + if(icon_state == "[initial(icon_state)]-on") + on = TRUE + update_brightness() + +/obj/item/flashlight/proc/update_brightness(mob/user = null) + if(on) + icon_state = "[initial(icon_state)]-on" + if(flashlight_power) + set_light(l_range = brightness_on, l_power = flashlight_power) + else + set_light(brightness_on) + else + icon_state = initial(icon_state) + set_light(0) + +/obj/item/flashlight/attack_self(mob/user) + on = !on + update_brightness(user) + for(var/X in actions) + var/datum/action/A = X + A.UpdateButtonIcon() + return 1 + +/obj/item/flashlight/suicide_act(mob/living/carbon/human/user) + if (user.is_blind()) + user.visible_message("[user] is putting [src] close to [user.p_their()] eyes and turning it on... but [user.p_theyre()] blind!") + return SHAME + user.visible_message("[user] is putting [src] close to [user.p_their()] eyes and turning it on! It looks like [user.p_theyre()] trying to commit suicide!") + return (FIRELOSS) + +/obj/item/flashlight/attack(mob/living/carbon/M, mob/living/carbon/human/user) + add_fingerprint(user) + if(istype(M) && on && (user.zone_selected in list(BODY_ZONE_PRECISE_EYES, BODY_ZONE_PRECISE_MOUTH))) + + if((HAS_TRAIT(user, TRAIT_CLUMSY) || HAS_TRAIT(user, TRAIT_DUMB)) && prob(50)) //too dumb to use flashlight properly + return ..() //just hit them in the head + + if(!user.IsAdvancedToolUser()) + to_chat(user, "You don't have the dexterity to do this!") + return + + if(!M.get_bodypart(BODY_ZONE_HEAD)) + to_chat(user, "[M] doesn't have a head!") + return + + if(flashlight_power < 1) + to_chat(user, "\The [src] isn't bright enough to see anything! ") + return + + switch(user.zone_selected) + if(BODY_ZONE_PRECISE_EYES) + if((M.head && M.head.flags_cover & HEADCOVERSEYES) || (M.wear_mask && M.wear_mask.flags_cover & MASKCOVERSEYES) || (M.glasses && M.glasses.flags_cover & GLASSESCOVERSEYES)) + to_chat(user, "You're going to need to remove that [(M.head && M.head.flags_cover & HEADCOVERSEYES) ? "helmet" : (M.wear_mask && M.wear_mask.flags_cover & MASKCOVERSEYES) ? "mask": "glasses"] first!") + return + + var/obj/item/organ/eyes/E = M.getorganslot(ORGAN_SLOT_EYES) + if(!E) + to_chat(user, "[M] doesn't have any eyes!") + return + + if(M == user) //they're using it on themselves + if(M.flash_act(visual = 1)) + M.visible_message("[M] directs [src] to [M.p_their()] eyes.", "You wave the light in front of your eyes! Trippy!") + else + M.visible_message("[M] directs [src] to [M.p_their()] eyes.", "You wave the light in front of your eyes.") + else + user.visible_message("[user] directs [src] to [M]'s eyes.", \ + "You direct [src] to [M]'s eyes.") + if(M.stat == DEAD || (M.is_blind()) || !M.flash_act(visual = 1)) //mob is dead or fully blind + to_chat(user, "[M]'s pupils don't react to the light!") + else if(M.dna && M.dna.check_mutation(XRAY)) //mob has X-ray vision + to_chat(user, "[M]'s pupils give an eerie glow!") + else //they're okay! + to_chat(user, "[M]'s pupils narrow.") + + if(BODY_ZONE_PRECISE_MOUTH) + + if(M.is_mouth_covered()) + to_chat(user, "You're going to need to remove that [(M.head && M.head.flags_cover & HEADCOVERSMOUTH) ? "helmet" : "mask"] first!") + return + + var/their = M.p_their() + + var/list/mouth_organs = new + for(var/obj/item/organ/O in M.internal_organs) + if(O.zone == BODY_ZONE_PRECISE_MOUTH) + mouth_organs.Add(O) + var/organ_list = "" + var/organ_count = LAZYLEN(mouth_organs) + if(organ_count) + for(var/I in 1 to organ_count) + if(I > 1) + if(I == mouth_organs.len) + organ_list += ", and " + else + organ_list += ", " + var/obj/item/organ/O = mouth_organs[I] + organ_list += (O.gender == "plural" ? O.name : "\an [O.name]") + + var/pill_count = 0 + for(var/datum/action/item_action/hands_free/activate_pill/AP in M.actions) + pill_count++ + + if(M == user) + var/can_use_mirror = FALSE + if(isturf(user.loc)) + var/obj/structure/mirror/mirror = locate(/obj/structure/mirror, user.loc) + if(mirror) + switch(user.dir) + if(NORTH) + can_use_mirror = mirror.pixel_y > 0 + if(SOUTH) + can_use_mirror = mirror.pixel_y < 0 + if(EAST) + can_use_mirror = mirror.pixel_x > 0 + if(WEST) + can_use_mirror = mirror.pixel_x < 0 + + M.visible_message("[M] directs [src] to [their] mouth.", \ + "You point [src] into your mouth.") + if(!can_use_mirror) + to_chat(user, "You can't see anything without a mirror.") + return + if(organ_count) + to_chat(user, "Inside your mouth [organ_count > 1 ? "are" : "is"] [organ_list].") + else + to_chat(user, "There's nothing inside your mouth.") + if(pill_count) + to_chat(user, "You have [pill_count] implanted pill[pill_count > 1 ? "s" : ""].") + + else + user.visible_message("[user] directs [src] to [M]'s mouth.",\ + "You direct [src] to [M]'s mouth.") + if(organ_count) + to_chat(user, "Inside [their] mouth [organ_count > 1 ? "are" : "is"] [organ_list].") + else + to_chat(user, "[M] doesn't have any organs in [their] mouth.") + if(pill_count) + to_chat(user, "[M] has [pill_count] pill[pill_count > 1 ? "s" : ""] implanted in [their] teeth.") + + else + return ..() + +/obj/item/flashlight/pen + name = "penlight" + desc = "A pen-sized light, used by medical staff. It can also be used to create a hologram to alert people of incoming medical assistance." + icon_state = "penlight" + item_state = "" + flags_1 = CONDUCT_1 + brightness_on = 2 + light_color = "#FFDDCC" + flashlight_power = 0.3 + var/holo_cooldown = 0 + +/obj/item/flashlight/pen/afterattack(atom/target, mob/user, proximity_flag) + . = ..() + if(!proximity_flag) + if(holo_cooldown > world.time) + to_chat(user, "[src] is not ready yet!") + return + var/T = get_turf(target) + if(locate(/mob/living) in T) + new /obj/effect/temp_visual/medical_holosign(T,user) //produce a holographic glow + holo_cooldown = world.time + 100 + return + +/obj/effect/temp_visual/medical_holosign + name = "medical holosign" + desc = "A small holographic glow that indicates a medic is coming to treat a patient." + icon_state = "medi_holo" + duration = 30 + +/obj/effect/temp_visual/medical_holosign/Initialize(mapload, creator) + . = ..() + playsound(loc, 'sound/machines/ping.ogg', 50, FALSE) //make some noise! + if(creator) + visible_message("[creator] created a medical hologram!") + + +/obj/item/flashlight/seclite + name = "seclite" + desc = "A robust flashlight used by security." + icon_state = "seclite" + item_state = "seclite" + lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' + force = 9 // Not as good as a stun baton. + brightness_on = 5 // A little better than the standard flashlight. + hitsound = 'sound/weapons/genhit1.ogg' + light_color = "#CDDDFF" //Cit lighting + flashlight_power = 0.9 //Cit lighting + +// the desk lamps are a bit special +/obj/item/flashlight/lamp + name = "desk lamp" + desc = "A desk lamp with an adjustable mount." + icon_state = "lamp" + item_state = "lamp" + lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' + righthand_file = 'icons/mob/inhands/items_righthand.dmi' + force = 10 + brightness_on = 5 + w_class = WEIGHT_CLASS_BULKY + flags_1 = CONDUCT_1 + custom_materials = null + on = TRUE + light_color = "#FFDDBB" //Cit lighting + flashlight_power = 0.8 //Cit lighting + + +// green-shaded desk lamp +/obj/item/flashlight/lamp/green + desc = "A classic green-shaded desk lamp." + icon_state = "lampgreen" + item_state = "lampgreen" + + + +/obj/item/flashlight/lamp/verb/toggle_light() + set name = "Toggle light" + set category = "Object" + set src in oview(1) + + if(!usr.stat) + attack_self(usr) + +//Bananalamp +/obj/item/flashlight/lamp/bananalamp + name = "banana lamp" + desc = "Only a clown would think to make a ghetto banana-shaped lamp. Even has a goofy pullstring." + icon_state = "bananalamp" + item_state = "bananalamp" + +// FLARES + +/obj/item/flashlight/flare + name = "flare" + desc = "A red Nanotrasen issued flare. There are instructions on the side, it reads 'pull cord, make light'." + w_class = WEIGHT_CLASS_SMALL + brightness_on = 7 // Pretty bright. + icon_state = "flare" + item_state = "flare" + actions_types = list() + var/fuel = 0 + var/on_damage = 7 + var/produce_heat = 1500 + heat = 1000 + light_color = LIGHT_COLOR_FLARE + grind_results = list(/datum/reagent/sulfur = 15) + light_color = "#FA421A" //Cit lighting + flashlight_power = 0.8 //Cit lighting + +/obj/item/flashlight/flare/Initialize() + . = ..() + fuel = rand(800, 1000) // Sorry for changing this so much but I keep under-estimating how long X number of ticks last in seconds. + +/obj/item/flashlight/flare/process() + open_flame(heat) + fuel = max(fuel - 1, 0) + if(!fuel || !on) + turn_off() + if(!fuel) + icon_state = "[initial(icon_state)]-empty" + STOP_PROCESSING(SSobj, src) + +/obj/item/flashlight/flare/ignition_effect(atom/A, mob/user) + . = fuel && on ? "[user] lights [A] with [src] like a real badass." : "" + +/obj/item/flashlight/flare/proc/turn_off() + on = FALSE + force = initial(src.force) + damtype = initial(src.damtype) + if(ismob(loc)) + var/mob/U = loc + update_brightness(U) + else + update_brightness(null) + +/obj/item/flashlight/flare/update_brightness(mob/user = null) + ..() + if(on) + item_state = "[initial(item_state)]-on" + else + item_state = "[initial(item_state)]" + +/obj/item/flashlight/flare/attack_self(mob/user) + + // Usual checks + if(!fuel) + to_chat(user, "[src] is out of fuel!") + return + if(on) + to_chat(user, "[src] is already on!") + return + + . = ..() + // All good, turn it on. + if(.) + user.visible_message("[user] lights \the [src].", "You light \the [src]!") + force = on_damage + damtype = "fire" + START_PROCESSING(SSobj, src) + +/obj/item/flashlight/flare/get_temperature() + return on * heat + +/obj/item/flashlight/flare/torch + name = "torch" + desc = "A torch fashioned from some leaves and a log." + w_class = WEIGHT_CLASS_BULKY + brightness_on = 4 + icon_state = "torch" + item_state = "torch" + lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' + righthand_file = 'icons/mob/inhands/items_righthand.dmi' + light_color = LIGHT_COLOR_ORANGE + on_damage = 10 + slot_flags = null + light_color = "#FAA44B" //Cit lighting + flashlight_power = 0.8 //Cit lighting + +/obj/item/flashlight/lantern + name = "lantern" + icon_state = "lantern" + item_state = "lantern" + lefthand_file = 'icons/mob/inhands/equipment/mining_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/mining_righthand.dmi' + desc = "A mining lantern." + brightness_on = 6 // luminosity when on + light_color = "#FFAA44" //Cit lighting + flashlight_power = 0.75 //Cit lighting + +/obj/item/flashlight/lantern/heirloom_moth + name = "old lantern" + desc = "An old lantern that has seen plenty of use." + brightness_on = 4 + +/obj/item/flashlight/lantern/syndicate + name = "suspicious lantern" + desc = "A suspicious looking lantern." + icon_state = "syndilantern" + item_state = "syndilantern" + brightness_on = 10 + +/obj/item/flashlight/slime + gender = PLURAL + name = "glowing slime extract" + desc = "Extract from a yellow slime. It emits a strong light when squeezed." + icon = 'icons/obj/lighting.dmi' + icon_state = "slime" + item_state = "slime" + w_class = WEIGHT_CLASS_SMALL + slot_flags = ITEM_SLOT_BELT + custom_materials = null + brightness_on = 6 //luminosity when on + light_color = "#FFEEAA" //Cit lighting + flashlight_power = 0.6 //Cit lighting + +/obj/item/flashlight/emp + var/emp_max_charges = 4 + var/emp_cur_charges = 4 + var/charge_tick = 0 + +/obj/item/flashlight/emp/New() + ..() + START_PROCESSING(SSobj, src) + +/obj/item/flashlight/emp/Destroy() + STOP_PROCESSING(SSobj, src) + . = ..() + +/obj/item/flashlight/emp/process() + charge_tick++ + if(charge_tick < 10) + return FALSE + charge_tick = 0 + emp_cur_charges = min(emp_cur_charges+1, emp_max_charges) + return TRUE + +/obj/item/flashlight/emp/attack(mob/living/M, mob/living/user) + if(on && (user.zone_selected in list(BODY_ZONE_PRECISE_EYES, BODY_ZONE_PRECISE_MOUTH))) // call original attack when examining organs + ..() + return + +/obj/item/flashlight/emp/afterattack(atom/movable/A, mob/user, proximity) + . = ..() + if(!proximity) + return + + if(emp_cur_charges > 0) + emp_cur_charges -= 1 + + if(ismob(A)) + var/mob/M = A + log_combat(user, M, "attacked", "EMP-light") + M.visible_message("[user] blinks \the [src] at \the [A].", \ + "[user] blinks \the [src] at you.") + else + A.visible_message("[user] blinks \the [src] at \the [A].") + to_chat(user, "\The [src] now has [emp_cur_charges] charge\s.") + A.emp_act(EMP_HEAVY) + else + to_chat(user, "\The [src] needs time to recharge!") + return + +/obj/item/flashlight/emp/debug //for testing emp_act() + name = "debug EMP flashlight" + emp_max_charges = 100 + emp_cur_charges = 100 + +// Glowsticks, in the uncomfortable range of similar to flares, +// but not similar enough to make it worth a refactor +/obj/item/flashlight/glowstick + name = "glowstick" + desc = "A military-grade glowstick." + custom_price = 50 + w_class = WEIGHT_CLASS_SMALL + brightness_on = 4 + color = LIGHT_COLOR_GREEN + icon_state = "glowstick" + item_state = "glowstick" + grind_results = list(/datum/reagent/phenol = 15, /datum/reagent/hydrogen = 10, /datum/reagent/oxygen = 5) //Meth-in-a-stick + var/fuel = 0 + +/obj/item/flashlight/glowstick/Initialize() + fuel = rand(1600, 2000) + light_color = color + . = ..() + +/obj/item/flashlight/glowstick/Destroy() + STOP_PROCESSING(SSobj, src) + . = ..() + +/obj/item/flashlight/glowstick/process() + fuel = max(fuel - 1, 0) + if(!fuel) + turn_off() + STOP_PROCESSING(SSobj, src) + update_icon() + +/obj/item/flashlight/glowstick/proc/turn_off() + on = FALSE + update_icon() + +/obj/item/flashlight/glowstick/update_icon() + item_state = "glowstick" + cut_overlays() + if(!fuel) + icon_state = "glowstick-empty" + cut_overlays() + set_light(0) + else if(on) + var/mutable_appearance/glowstick_overlay = mutable_appearance(icon, "glowstick-glow") + glowstick_overlay.color = color + add_overlay(glowstick_overlay) + item_state = "glowstick-on" + set_light(brightness_on) + else + icon_state = "glowstick" + cut_overlays() + +/obj/item/flashlight/glowstick/attack_self(mob/user) + if(!fuel) + to_chat(user, "[src] is spent.") + return + if(on) + to_chat(user, "[src] is already lit!") + return + + . = ..() + if(.) + user.visible_message("[user] cracks and shakes [src].", "You crack and shake [src], turning it on!") + START_PROCESSING(SSobj, src) + +/obj/item/flashlight/glowstick/suicide_act(mob/living/carbon/human/user) + if(!fuel) + user.visible_message("[user] is trying to squirt [src]'s fluids into [user.p_their()] eyes... but it's empty!") + return SHAME + var/obj/item/organ/eyes/eyes = user.getorganslot(ORGAN_SLOT_EYES) + if(!eyes) + user.visible_message("[user] is trying to squirt [src]'s fluids into [user.p_their()] eyes... but [user.p_they()] don't have any!") + return SHAME + user.visible_message("[user] is squirting [src]'s fluids into [user.p_their()] eyes! It looks like [user.p_theyre()] trying to commit suicide!") + fuel = 0 + return (FIRELOSS) + +/obj/item/flashlight/glowstick/red + name = "red glowstick" + color = LIGHT_COLOR_RED + +/obj/item/flashlight/glowstick/blue + name = "blue glowstick" + color = LIGHT_COLOR_BLUE + +/obj/item/flashlight/glowstick/cyan + name = "cyan glowstick" + color = LIGHT_COLOR_CYAN + +/obj/item/flashlight/glowstick/orange + name = "orange glowstick" + color = LIGHT_COLOR_ORANGE + +/obj/item/flashlight/glowstick/yellow + name = "yellow glowstick" + color = LIGHT_COLOR_YELLOW + +/obj/item/flashlight/glowstick/pink + name = "pink glowstick" + color = LIGHT_COLOR_PINK + +/obj/effect/spawner/lootdrop/glowstick + name = "random colored glowstick" + icon = 'icons/obj/lighting.dmi' + icon_state = "random_glowstick" + +/obj/effect/spawner/lootdrop/glowstick/Initialize() + loot = typesof(/obj/item/flashlight/glowstick) + . = ..() + +/obj/item/flashlight/spotlight //invisible lighting source + name = "disco light" + desc = "Groovy..." + icon_state = null + light_color = null + brightness_on = 0 + light_range = 0 + light_power = 10 + alpha = 0 + layer = 0 + on = TRUE + anchored = TRUE + var/range = null + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + +/obj/item/flashlight/flashdark + name = "flashdark" + desc = "A strange device manufactured with mysterious elements that somehow emits darkness. Or maybe it just sucks in light? Nobody knows for sure." + icon_state = "flashdark" + item_state = "flashdark" + brightness_on = 2.5 + flashlight_power = -3 + +/obj/item/flashlight/eyelight + name = "eyelight" + desc = "This shouldn't exist outside of someone's head, how are you seeing this?" + brightness_on = 15 + flashlight_power = 1 + flags_1 = CONDUCT_1 + item_flags = DROPDEL + actions_types = list() diff --git a/code/game/objects/items/devices/gps.dm b/code/game/objects/items/devices/gps.dm index 6195fc1fcda6..77d0f82baa10 100644 --- a/code/game/objects/items/devices/gps.dm +++ b/code/game/objects/items/devices/gps.dm @@ -1,76 +1,76 @@ - -/obj/item/gps - name = "global positioning system" - desc = "Helping lost spacemen find their way through the planets since 2016." - icon = 'icons/obj/telescience.dmi' - icon_state = "gps-c" - w_class = WEIGHT_CLASS_SMALL - slot_flags = ITEM_SLOT_BELT - obj_flags = UNIQUE_RENAME - var/gpstag - -/obj/item/gps/Initialize() - . = ..() - AddComponent(/datum/component/gps/item, gpstag) - -/obj/item/gps/science - icon_state = "gps-s" - gpstag = "SCI0" - -/obj/item/gps/engineering - icon_state = "gps-e" - gpstag = "ENG0" - -/obj/item/gps/mining - icon_state = "gps-m" - gpstag = "MINE0" - desc = "A positioning system helpful for rescuing trapped or injured miners, keeping one on you at all times while mining might just save your life." - -/obj/item/gps/cyborg - icon_state = "gps-b" - gpstag = "BORG0" - desc = "A mining cyborg internal positioning system. Used as a recovery beacon for damaged cyborg assets, or a collaboration tool for mining teams." - -/obj/item/gps/cyborg/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CYBORG_ITEM_TRAIT) - -/obj/item/gps/mining/internal - icon_state = "gps-m" - gpstag = "MINER" - desc = "A positioning system helpful for rescuing trapped or injured miners, keeping one on you at all times while mining might just save your life." - -/obj/item/gps/visible_debug - name = "visible GPS" - gpstag = "ADMIN" - desc = "This admin-spawn GPS unit leaves the coordinates visible \ - on any turf that it passes over, for debugging. Especially useful \ - for marking the area around the transition edges." - var/list/turf/tagged - -/obj/item/gps/visible_debug/Initialize() - . = ..() - tagged = list() - START_PROCESSING(SSfastprocess, src) - -/obj/item/gps/visible_debug/process() - var/turf/T = get_turf(src) - if(T) - // I assume it's faster to color,tag and OR the turf in, rather - // then checking if its there - T.color = RANDOM_COLOUR - T.maptext = "[T.x],[T.y],[T.z]" - tagged |= T - -/obj/item/gps/visible_debug/proc/clear() - while(tagged.len) - var/turf/T = pop(tagged) - T.color = initial(T.color) - T.maptext = initial(T.maptext) - -/obj/item/gps/visible_debug/Destroy() - if(tagged) - clear() - tagged = null - STOP_PROCESSING(SSfastprocess, src) - . = ..() + +/obj/item/gps + name = "global positioning system" + desc = "Helping lost spacemen find their way through the planets since 2016." + icon = 'icons/obj/telescience.dmi' + icon_state = "gps-c" + w_class = WEIGHT_CLASS_SMALL + slot_flags = ITEM_SLOT_BELT + obj_flags = UNIQUE_RENAME + var/gpstag + +/obj/item/gps/Initialize() + . = ..() + AddComponent(/datum/component/gps/item, gpstag) + +/obj/item/gps/science + icon_state = "gps-s" + gpstag = "SCI0" + +/obj/item/gps/engineering + icon_state = "gps-e" + gpstag = "ENG0" + +/obj/item/gps/mining + icon_state = "gps-m" + gpstag = "MINE0" + desc = "A positioning system helpful for rescuing trapped or injured miners, keeping one on you at all times while mining might just save your life." + +/obj/item/gps/cyborg + icon_state = "gps-b" + gpstag = "BORG0" + desc = "A mining cyborg internal positioning system. Used as a recovery beacon for damaged cyborg assets, or a collaboration tool for mining teams." + +/obj/item/gps/cyborg/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CYBORG_ITEM_TRAIT) + +/obj/item/gps/mining/internal + icon_state = "gps-m" + gpstag = "MINER" + desc = "A positioning system helpful for rescuing trapped or injured miners, keeping one on you at all times while mining might just save your life." + +/obj/item/gps/visible_debug + name = "visible GPS" + gpstag = "ADMIN" + desc = "This admin-spawn GPS unit leaves the coordinates visible \ + on any turf that it passes over, for debugging. Especially useful \ + for marking the area around the transition edges." + var/list/turf/tagged + +/obj/item/gps/visible_debug/Initialize() + . = ..() + tagged = list() + START_PROCESSING(SSfastprocess, src) + +/obj/item/gps/visible_debug/process() + var/turf/T = get_turf(src) + if(T) + // I assume it's faster to color,tag and OR the turf in, rather + // then checking if its there + T.color = RANDOM_COLOUR + T.maptext = "[T.x],[T.y],[T.z]" + tagged |= T + +/obj/item/gps/visible_debug/proc/clear() + while(tagged.len) + var/turf/T = pop(tagged) + T.color = initial(T.color) + T.maptext = initial(T.maptext) + +/obj/item/gps/visible_debug/Destroy() + if(tagged) + clear() + tagged = null + STOP_PROCESSING(SSfastprocess, src) + . = ..() diff --git a/code/game/objects/items/devices/instruments.dm b/code/game/objects/items/devices/instruments.dm index 985a372d99a7..4608940698e7 100644 --- a/code/game/objects/items/devices/instruments.dm +++ b/code/game/objects/items/devices/instruments.dm @@ -1,320 +1,320 @@ -//copy pasta of the space piano, don't hurt me -Pete -/obj/item/instrument - name = "generic instrument" - resistance_flags = FLAMMABLE - force = 10 - max_integrity = 100 - icon = 'icons/obj/musician.dmi' - lefthand_file = 'icons/mob/inhands/equipment/instruments_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/instruments_righthand.dmi' - var/datum/song/handheld/song - var/instrumentId = "generic" - var/instrumentExt = "mid" - var/instrumentRange = 15 - -/obj/item/instrument/Initialize() - . = ..() - song = new(instrumentId, src, instrumentExt, instrumentRange) - -/obj/item/instrument/Destroy() - QDEL_NULL(song) - . = ..() - -/obj/item/instrument/suicide_act(mob/user) - user.visible_message("[user] begins to play 'Gloomy Sunday'! It looks like [user.p_theyre()] trying to commit suicide!") - return (BRUTELOSS) - -/obj/item/instrument/Initialize(mapload) - . = ..() - if(mapload) - song.tempo = song.sanitize_tempo(song.tempo) // tick_lag isn't set when the map is loaded - -/obj/item/instrument/attack_self(mob/user) - if(!user.IsAdvancedToolUser()) - to_chat(user, "You don't have the dexterity to do this!") - return 1 - interact(user) - -/obj/item/instrument/interact(mob/user) - ui_interact(user) - -/obj/item/instrument/ui_interact(mob/living/user) - if(!isliving(user) || user.stat || user.restrained() || !(user.mobility_flags & MOBILITY_STAND)) - return - - user.set_machine(src) - song.interact(user) - -/obj/item/instrument/proc/start_playing() - return - -/obj/item/instrument/proc/stop_playing() - return - -/obj/item/instrument/violin - name = "space violin" - desc = "A wooden musical instrument with four strings and a bow. \"The devil went down to space, he was looking for an assistant to grief.\"" - icon_state = "violin" - item_state = "violin" - hitsound = "swing_hit" - instrumentId = "violin" - -/obj/item/instrument/violin/golden - name = "golden violin" - desc = "A golden musical instrument with four strings and a bow. \"The devil went down to space, he was looking for an assistant to grief.\"" - icon_state = "golden_violin" - item_state = "golden_violin" - resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF - -/obj/item/instrument/piano_synth - name = "synthesizer" - desc = "An advanced electronic synthesizer that can be used as various instruments." - icon_state = "synth" - item_state = "synth" - instrumentId = "piano" - instrumentExt = "ogg" - var/static/list/insTypes = list("accordion" = "mid", "bikehorn" = "ogg", "glockenspiel" = "mid", "banjo" = "ogg", "guitar" = "ogg", "harmonica" = "mid", "piano" = "ogg", "recorder" = "mid", "saxophone" = "mid", "trombone" = "mid", "violin" = "mid", "xylophone" = "mid") //No eguitar you ear-rapey fuckers. - actions_types = list(/datum/action/item_action/synthswitch) - -/obj/item/instrument/piano_synth/proc/changeInstrument(name = "piano") - song.instrumentDir = name - song.instrumentExt = insTypes[name] - -/obj/item/instrument/piano_synth/proc/selectInstrument() // Moved here so it can be used by the action and PAI software panel without copypasta - var/chosen = input("Choose the type of instrument you want to use", "Instrument Selection", song.instrumentDir) as null|anything in sortList(insTypes) - if(!insTypes[chosen]) - return - return changeInstrument(chosen) - -/obj/item/instrument/piano_synth/headphones - name = "headphones" - desc = "Unce unce unce unce. Boop!" - icon = 'icons/obj/clothing/accessories.dmi' - lefthand_file = 'icons/mob/inhands/clothing_lefthand.dmi' - righthand_file = 'icons/mob/inhands/clothing_righthand.dmi' - icon_state = "headphones" - item_state = "headphones" - slot_flags = ITEM_SLOT_EARS | ITEM_SLOT_HEAD - force = 0 - w_class = WEIGHT_CLASS_SMALL - custom_price = 125 - instrumentRange = 1 - -/obj/item/instrument/piano_synth/headphones/ComponentInitialize() - . = ..() - AddElement(/datum/element/update_icon_updates_onmob) - RegisterSignal(src, COMSIG_SONG_START, .proc/start_playing) - RegisterSignal(src, COMSIG_SONG_END, .proc/stop_playing) - -/obj/item/instrument/piano_synth/headphones/start_playing() - icon_state = "[initial(icon_state)]_on" - update_icon() - -/obj/item/instrument/piano_synth/headphones/stop_playing() - icon_state = "[initial(icon_state)]" - update_icon() - -/obj/item/instrument/piano_synth/headphones/spacepods - name = "\improper Nanotrasen space pods" - desc = "Flex your money, AND ignore what everyone else says, all at once!" - icon_state = "spacepods" - item_state = "spacepods" - slot_flags = ITEM_SLOT_EARS - strip_delay = 100 //air pods don't fall out - instrumentRange = 0 //you're paying for quality here - custom_premium_price = 1800 - -/obj/item/instrument/banjo - name = "banjo" - desc = "A 'Mura' brand banjo. It's pretty much just a drum with a neck and strings." - icon_state = "banjo" - item_state = "banjo" - instrumentExt = "ogg" - attack_verb = list("scruggs-styled", "hum-diggitied", "shin-digged", "clawhammered") - hitsound = 'sound/weapons/banjoslap.ogg' - instrumentId = "banjo" - -/obj/item/instrument/guitar - name = "guitar" - desc = "It's made out of wood and has bronze strings." - icon_state = "guitar" - item_state = "guitar" - instrumentExt = "ogg" - attack_verb = list("played metal on", "serenaded", "crashed", "smashed") - hitsound = 'sound/weapons/stringsmash.ogg' - instrumentId = "guitar" - -/obj/item/instrument/eguitar - name = "electric guitar" - desc = "Makes all your shredding needs possible." - icon_state = "eguitar" - item_state = "eguitar" - force = 12 - attack_verb = list("played metal on", "shredded", "crashed", "smashed") - hitsound = 'sound/weapons/stringsmash.ogg' - instrumentId = "eguitar" - instrumentExt = "ogg" - -/obj/item/instrument/glockenspiel - name = "glockenspiel" - desc = "Smooth metal bars perfect for any marching band." - icon_state = "glockenspiel" - item_state = "glockenspiel" - instrumentId = "glockenspiel" - -/obj/item/instrument/accordion - name = "accordion" - desc = "Pun-Pun not included." - icon_state = "accordion" - item_state = "accordion" - instrumentId = "accordion" - -/obj/item/instrument/trumpet - name = "trumpet" - desc = "To announce the arrival of the king!" - icon_state = "trumpet" - item_state = "trumpet" - instrumentId = "trombone" - -/obj/item/instrument/trumpet/spectral - name = "spectral trumpet" - desc = "Things are about to get spooky!" - icon_state = "spectral_trumpet" - item_state = "spectral_trumpet" - force = 0 - instrumentId = "trombone" - attack_verb = list("played","jazzed","trumpeted","mourned","dooted","spooked") - -/obj/item/instrument/trumpet/spectral/Initialize() - . = ..() - AddComponent(/datum/component/spooky) - -/obj/item/instrument/trumpet/spectral/attack(mob/living/carbon/C, mob/user) - playsound (loc, 'sound/instruments/trombone/En4.mid', 100,1,-1) - ..() - -/obj/item/instrument/saxophone - name = "saxophone" - desc = "This soothing sound will be sure to leave your audience in tears." - icon_state = "saxophone" - item_state = "saxophone" - instrumentId = "saxophone" - -/obj/item/instrument/saxophone/spectral - name = "spectral saxophone" - desc = "This spooky sound will be sure to leave mortals in bones." - icon_state = "saxophone" - item_state = "saxophone" - instrumentId = "saxophone" - force = 0 - attack_verb = list("played","jazzed","saxxed","mourned","dooted","spooked") - -/obj/item/instrument/saxophone/spectral/Initialize() - . = ..() - AddComponent(/datum/component/spooky) - -/obj/item/instrument/saxophone/spectral/attack(mob/living/carbon/C, mob/user) - playsound (loc, 'sound/instruments/saxophone/En4.mid', 100,1,-1) - ..() - -/obj/item/instrument/trombone - name = "trombone" - desc = "How can any pool table ever hope to compete?" - icon_state = "trombone" - item_state = "trombone" - instrumentId = "trombone" - -/obj/item/instrument/trombone/spectral - name = "spectral trombone" - desc = "A skeleton's favorite instrument. Apply directly on the mortals." - instrumentId = "trombone" - icon_state = "trombone" - item_state = "trombone" - force = 0 - attack_verb = list("played","jazzed","tromboned","mourned","dooted","spooked") - -/obj/item/instrument/trombone/spectral/Initialize() - . = ..() - AddComponent(/datum/component/spooky) - -/obj/item/instrument/trombone/spectral/attack(mob/living/carbon/C, mob/user) - playsound (loc, 'sound/instruments/trombone/Cn4.mid', 100,1,-1) - ..() - -/obj/item/instrument/recorder - name = "recorder" - desc = "Just like in school, playing ability and all." - force = 5 - icon_state = "recorder" - item_state = "recorder" - instrumentId = "recorder" - -/obj/item/instrument/harmonica - name = "harmonica" - desc = "For when you get a bad case of the space blues." - icon_state = "harmonica" - item_state = "harmonica" - instrumentId = "harmonica" - slot_flags = ITEM_SLOT_MASK - force = 5 - w_class = WEIGHT_CLASS_SMALL - actions_types = list(/datum/action/item_action/instrument) - -/obj/item/instrument/harmonica/proc/handle_speech(datum/source, list/speech_args) - if(song.playing && ismob(loc)) - to_chat(loc, "You stop playing the harmonica to talk...") - song.playing = FALSE - -/obj/item/instrument/harmonica/equipped(mob/M, slot) - . = ..() - RegisterSignal(M, COMSIG_MOB_SAY, .proc/handle_speech, override = TRUE) - -/obj/item/instrument/harmonica/dropped(mob/M) - . = ..() - UnregisterSignal(M, COMSIG_MOB_SAY) - -/obj/item/instrument/bikehorn - name = "gilded bike horn" - desc = "An exquisitely decorated bike horn, capable of honking in a variety of notes." - icon_state = "bike_horn" - item_state = "bike_horn" - lefthand_file = 'icons/mob/inhands/equipment/horns_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/horns_righthand.dmi' - attack_verb = list("beautifully honks") - instrumentId = "bikehorn" - instrumentExt = "ogg" - w_class = WEIGHT_CLASS_TINY - force = 0 - throw_speed = 3 - throw_range = 15 - hitsound = 'sound/items/bikehorn.ogg' - -/// - -/obj/item/choice_beacon/music - name = "instrument delivery beacon" - desc = "Summon your tool of art." - icon_state = "gangtool-red" - -/obj/item/choice_beacon/music/generate_display_names() - var/static/list/instruments - if(!instruments) - instruments = list() - var/list/templist = list(/obj/item/instrument/violin, - /obj/item/instrument/piano_synth, - /obj/item/instrument/banjo, - /obj/item/instrument/guitar, - /obj/item/instrument/eguitar, - /obj/item/instrument/glockenspiel, - /obj/item/instrument/accordion, - /obj/item/instrument/trumpet, - /obj/item/instrument/saxophone, - /obj/item/instrument/trombone, - /obj/item/instrument/recorder, - /obj/item/instrument/harmonica, - /obj/item/instrument/piano_synth/headphones - ) - for(var/V in templist) - var/atom/A = V - instruments[initial(A.name)] = A - return instruments +//copy pasta of the space piano, don't hurt me -Pete +/obj/item/instrument + name = "generic instrument" + resistance_flags = FLAMMABLE + force = 10 + max_integrity = 100 + icon = 'icons/obj/musician.dmi' + lefthand_file = 'icons/mob/inhands/equipment/instruments_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/instruments_righthand.dmi' + var/datum/song/handheld/song + var/instrumentId = "generic" + var/instrumentExt = "mid" + var/instrumentRange = 15 + +/obj/item/instrument/Initialize() + . = ..() + song = new(instrumentId, src, instrumentExt, instrumentRange) + +/obj/item/instrument/Destroy() + QDEL_NULL(song) + . = ..() + +/obj/item/instrument/suicide_act(mob/user) + user.visible_message("[user] begins to play 'Gloomy Sunday'! It looks like [user.p_theyre()] trying to commit suicide!") + return (BRUTELOSS) + +/obj/item/instrument/Initialize(mapload) + . = ..() + if(mapload) + song.tempo = song.sanitize_tempo(song.tempo) // tick_lag isn't set when the map is loaded + +/obj/item/instrument/attack_self(mob/user) + if(!user.IsAdvancedToolUser()) + to_chat(user, "You don't have the dexterity to do this!") + return 1 + interact(user) + +/obj/item/instrument/interact(mob/user) + ui_interact(user) + +/obj/item/instrument/ui_interact(mob/living/user) + if(!isliving(user) || user.stat || user.restrained() || !(user.mobility_flags & MOBILITY_STAND)) + return + + user.set_machine(src) + song.interact(user) + +/obj/item/instrument/proc/start_playing() + return + +/obj/item/instrument/proc/stop_playing() + return + +/obj/item/instrument/violin + name = "space violin" + desc = "A wooden musical instrument with four strings and a bow. \"The devil went down to space, he was looking for an assistant to grief.\"" + icon_state = "violin" + item_state = "violin" + hitsound = "swing_hit" + instrumentId = "violin" + +/obj/item/instrument/violin/golden + name = "golden violin" + desc = "A golden musical instrument with four strings and a bow. \"The devil went down to space, he was looking for an assistant to grief.\"" + icon_state = "golden_violin" + item_state = "golden_violin" + resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF + +/obj/item/instrument/piano_synth + name = "synthesizer" + desc = "An advanced electronic synthesizer that can be used as various instruments." + icon_state = "synth" + item_state = "synth" + instrumentId = "piano" + instrumentExt = "ogg" + var/static/list/insTypes = list("accordion" = "mid", "bikehorn" = "ogg", "glockenspiel" = "mid", "banjo" = "ogg", "guitar" = "ogg", "harmonica" = "mid", "piano" = "ogg", "recorder" = "mid", "saxophone" = "mid", "trombone" = "mid", "violin" = "mid", "xylophone" = "mid") //No eguitar you ear-rapey fuckers. + actions_types = list(/datum/action/item_action/synthswitch) + +/obj/item/instrument/piano_synth/proc/changeInstrument(name = "piano") + song.instrumentDir = name + song.instrumentExt = insTypes[name] + +/obj/item/instrument/piano_synth/proc/selectInstrument() // Moved here so it can be used by the action and PAI software panel without copypasta + var/chosen = input("Choose the type of instrument you want to use", "Instrument Selection", song.instrumentDir) as null|anything in sortList(insTypes) + if(!insTypes[chosen]) + return + return changeInstrument(chosen) + +/obj/item/instrument/piano_synth/headphones + name = "headphones" + desc = "Unce unce unce unce. Boop!" + icon = 'icons/obj/clothing/accessories.dmi' + lefthand_file = 'icons/mob/inhands/clothing_lefthand.dmi' + righthand_file = 'icons/mob/inhands/clothing_righthand.dmi' + icon_state = "headphones" + item_state = "headphones" + slot_flags = ITEM_SLOT_EARS | ITEM_SLOT_HEAD + force = 0 + w_class = WEIGHT_CLASS_SMALL + custom_price = 125 + instrumentRange = 1 + +/obj/item/instrument/piano_synth/headphones/ComponentInitialize() + . = ..() + AddElement(/datum/element/update_icon_updates_onmob) + RegisterSignal(src, COMSIG_SONG_START, .proc/start_playing) + RegisterSignal(src, COMSIG_SONG_END, .proc/stop_playing) + +/obj/item/instrument/piano_synth/headphones/start_playing() + icon_state = "[initial(icon_state)]_on" + update_icon() + +/obj/item/instrument/piano_synth/headphones/stop_playing() + icon_state = "[initial(icon_state)]" + update_icon() + +/obj/item/instrument/piano_synth/headphones/spacepods + name = "\improper Nanotrasen space pods" + desc = "Flex your money, AND ignore what everyone else says, all at once!" + icon_state = "spacepods" + item_state = "spacepods" + slot_flags = ITEM_SLOT_EARS + strip_delay = 100 //air pods don't fall out + instrumentRange = 0 //you're paying for quality here + custom_premium_price = 1800 + +/obj/item/instrument/banjo + name = "banjo" + desc = "A 'Mura' brand banjo. It's pretty much just a drum with a neck and strings." + icon_state = "banjo" + item_state = "banjo" + instrumentExt = "ogg" + attack_verb = list("scruggs-styled", "hum-diggitied", "shin-digged", "clawhammered") + hitsound = 'sound/weapons/banjoslap.ogg' + instrumentId = "banjo" + +/obj/item/instrument/guitar + name = "guitar" + desc = "It's made out of wood and has bronze strings." + icon_state = "guitar" + item_state = "guitar" + instrumentExt = "ogg" + attack_verb = list("played metal on", "serenaded", "crashed", "smashed") + hitsound = 'sound/weapons/stringsmash.ogg' + instrumentId = "guitar" + +/obj/item/instrument/eguitar + name = "electric guitar" + desc = "Makes all your shredding needs possible." + icon_state = "eguitar" + item_state = "eguitar" + force = 12 + attack_verb = list("played metal on", "shredded", "crashed", "smashed") + hitsound = 'sound/weapons/stringsmash.ogg' + instrumentId = "eguitar" + instrumentExt = "ogg" + +/obj/item/instrument/glockenspiel + name = "glockenspiel" + desc = "Smooth metal bars perfect for any marching band." + icon_state = "glockenspiel" + item_state = "glockenspiel" + instrumentId = "glockenspiel" + +/obj/item/instrument/accordion + name = "accordion" + desc = "Pun-Pun not included." + icon_state = "accordion" + item_state = "accordion" + instrumentId = "accordion" + +/obj/item/instrument/trumpet + name = "trumpet" + desc = "To announce the arrival of the king!" + icon_state = "trumpet" + item_state = "trumpet" + instrumentId = "trombone" + +/obj/item/instrument/trumpet/spectral + name = "spectral trumpet" + desc = "Things are about to get spooky!" + icon_state = "spectral_trumpet" + item_state = "spectral_trumpet" + force = 0 + instrumentId = "trombone" + attack_verb = list("played","jazzed","trumpeted","mourned","dooted","spooked") + +/obj/item/instrument/trumpet/spectral/Initialize() + . = ..() + AddComponent(/datum/component/spooky) + +/obj/item/instrument/trumpet/spectral/attack(mob/living/carbon/C, mob/user) + playsound (loc, 'sound/instruments/trombone/En4.mid', 100,1,-1) + ..() + +/obj/item/instrument/saxophone + name = "saxophone" + desc = "This soothing sound will be sure to leave your audience in tears." + icon_state = "saxophone" + item_state = "saxophone" + instrumentId = "saxophone" + +/obj/item/instrument/saxophone/spectral + name = "spectral saxophone" + desc = "This spooky sound will be sure to leave mortals in bones." + icon_state = "saxophone" + item_state = "saxophone" + instrumentId = "saxophone" + force = 0 + attack_verb = list("played","jazzed","saxxed","mourned","dooted","spooked") + +/obj/item/instrument/saxophone/spectral/Initialize() + . = ..() + AddComponent(/datum/component/spooky) + +/obj/item/instrument/saxophone/spectral/attack(mob/living/carbon/C, mob/user) + playsound (loc, 'sound/instruments/saxophone/En4.mid', 100,1,-1) + ..() + +/obj/item/instrument/trombone + name = "trombone" + desc = "How can any pool table ever hope to compete?" + icon_state = "trombone" + item_state = "trombone" + instrumentId = "trombone" + +/obj/item/instrument/trombone/spectral + name = "spectral trombone" + desc = "A skeleton's favorite instrument. Apply directly on the mortals." + instrumentId = "trombone" + icon_state = "trombone" + item_state = "trombone" + force = 0 + attack_verb = list("played","jazzed","tromboned","mourned","dooted","spooked") + +/obj/item/instrument/trombone/spectral/Initialize() + . = ..() + AddComponent(/datum/component/spooky) + +/obj/item/instrument/trombone/spectral/attack(mob/living/carbon/C, mob/user) + playsound (loc, 'sound/instruments/trombone/Cn4.mid', 100,1,-1) + ..() + +/obj/item/instrument/recorder + name = "recorder" + desc = "Just like in school, playing ability and all." + force = 5 + icon_state = "recorder" + item_state = "recorder" + instrumentId = "recorder" + +/obj/item/instrument/harmonica + name = "harmonica" + desc = "For when you get a bad case of the space blues." + icon_state = "harmonica" + item_state = "harmonica" + instrumentId = "harmonica" + slot_flags = ITEM_SLOT_MASK + force = 5 + w_class = WEIGHT_CLASS_SMALL + actions_types = list(/datum/action/item_action/instrument) + +/obj/item/instrument/harmonica/proc/handle_speech(datum/source, list/speech_args) + if(song.playing && ismob(loc)) + to_chat(loc, "You stop playing the harmonica to talk...") + song.playing = FALSE + +/obj/item/instrument/harmonica/equipped(mob/M, slot) + . = ..() + RegisterSignal(M, COMSIG_MOB_SAY, .proc/handle_speech, override = TRUE) + +/obj/item/instrument/harmonica/dropped(mob/M) + . = ..() + UnregisterSignal(M, COMSIG_MOB_SAY) + +/obj/item/instrument/bikehorn + name = "gilded bike horn" + desc = "An exquisitely decorated bike horn, capable of honking in a variety of notes." + icon_state = "bike_horn" + item_state = "bike_horn" + lefthand_file = 'icons/mob/inhands/equipment/horns_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/horns_righthand.dmi' + attack_verb = list("beautifully honks") + instrumentId = "bikehorn" + instrumentExt = "ogg" + w_class = WEIGHT_CLASS_TINY + force = 0 + throw_speed = 3 + throw_range = 15 + hitsound = 'sound/items/bikehorn.ogg' + +/// + +/obj/item/choice_beacon/music + name = "instrument delivery beacon" + desc = "Summon your tool of art." + icon_state = "gangtool-red" + +/obj/item/choice_beacon/music/generate_display_names() + var/static/list/instruments + if(!instruments) + instruments = list() + var/list/templist = list(/obj/item/instrument/violin, + /obj/item/instrument/piano_synth, + /obj/item/instrument/banjo, + /obj/item/instrument/guitar, + /obj/item/instrument/eguitar, + /obj/item/instrument/glockenspiel, + /obj/item/instrument/accordion, + /obj/item/instrument/trumpet, + /obj/item/instrument/saxophone, + /obj/item/instrument/trombone, + /obj/item/instrument/recorder, + /obj/item/instrument/harmonica, + /obj/item/instrument/piano_synth/headphones + ) + for(var/V in templist) + var/atom/A = V + instruments[initial(A.name)] = A + return instruments diff --git a/code/game/objects/items/devices/laserpointer.dm b/code/game/objects/items/devices/laserpointer.dm index fc14fcb3a699..75a2c3976a6d 100644 --- a/code/game/objects/items/devices/laserpointer.dm +++ b/code/game/objects/items/devices/laserpointer.dm @@ -1,199 +1,199 @@ -/obj/item/laser_pointer - name = "laser pointer" - desc = "Don't shine it in your eyes!" - icon = 'icons/obj/device.dmi' - icon_state = "pointer" - item_state = "pen" - var/pointer_icon_state - flags_1 = CONDUCT_1 - item_flags = NOBLUDGEON - slot_flags = ITEM_SLOT_BELT - custom_materials = list(/datum/material/iron=500, /datum/material/glass=500) - w_class = WEIGHT_CLASS_SMALL - var/turf/pointer_loc - var/energy = 10 - var/max_energy = 10 - var/effectchance = 30 - var/recharging = FALSE - var/recharge_locked = FALSE - var/obj/item/stock_parts/micro_laser/diode //used for upgrading! - - -/obj/item/laser_pointer/red - pointer_icon_state = "red_laser" -/obj/item/laser_pointer/green - pointer_icon_state = "green_laser" -/obj/item/laser_pointer/blue - pointer_icon_state = "blue_laser" -/obj/item/laser_pointer/purple - pointer_icon_state = "purple_laser" - -/obj/item/laser_pointer/Initialize() - . = ..() - diode = new(src) - if(!pointer_icon_state) - pointer_icon_state = pick("red_laser","green_laser","blue_laser","purple_laser") - -/obj/item/laser_pointer/upgraded/Initialize() - . = ..() - diode = new /obj/item/stock_parts/micro_laser/ultra - -/obj/item/laser_pointer/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/stock_parts/micro_laser)) - if(!diode) - if(!user.transferItemToLoc(W, src)) - return - diode = W - to_chat(user, "You install a [diode.name] in [src].") - else - to_chat(user, "[src] already has a diode installed!") - - else if(W.tool_behaviour == TOOL_SCREWDRIVER) - if(diode) - to_chat(user, "You remove the [diode.name] from \the [src].") - diode.forceMove(drop_location()) - diode = null - else - return ..() - -/obj/item/laser_pointer/examine(mob/user) - . = ..() - if(in_range(user, src) || isobserver(user)) - if(!diode) - . += "The diode is missing." - else - . += "A class [diode.rating] laser diode is installed. It is screwed in place." - -/obj/item/laser_pointer/afterattack(atom/target, mob/living/user, flag, params) - . = ..() - laser_act(target, user, params) - -/obj/item/laser_pointer/proc/laser_act(atom/target, mob/living/user, params) - if( !(user in (viewers(7,target))) ) - return - if (!diode) - to_chat(user, "You point [src] at [target], but nothing happens!") - return - if (!user.IsAdvancedToolUser()) - to_chat(user, "You don't have the dexterity to do this!") - return - if(HAS_TRAIT(user, TRAIT_CHUNKYFINGERS)) - to_chat(user, "Your fingers can't press the button!") - return - add_fingerprint(user) - - //nothing happens if the battery is drained - if(recharge_locked) - to_chat(user, "You point [src] at [target], but it's still charging.") - return - - var/outmsg - var/turf/targloc = get_turf(target) - - //human/alien mobs - if(iscarbon(target)) - var/mob/living/carbon/C = target - if(user.zone_selected == BODY_ZONE_PRECISE_EYES) - - var/severity = 1 - if(prob(33)) - severity = 2 - else if(prob(50)) - severity = 0 - - //chance to actually hit the eyes depends on internal component - if(prob(effectchance * diode.rating) && C.flash_act(severity)) - outmsg = "You blind [C] by shining [src] in [C.p_their()] eyes." - log_combat(user, C, "blinded with a laser pointer",src) - else - outmsg = "You fail to blind [C] by shining [src] at [C.p_their()] eyes!" - log_combat(user, C, "attempted to blind with a laser pointer",src) - - //robots - else if(iscyborg(target)) - var/mob/living/silicon/S = target - log_combat(user, S, "shone in the sensors", src) - //chance to actually hit the eyes depends on internal component - if(prob(effectchance * diode.rating)) - S.flash_act(affect_silicon = 1) - S.Paralyze(rand(100,200)) - to_chat(S, "Your sensors were overloaded by a laser!") - outmsg = "You overload [S] by shining [src] at [S.p_their()] sensors." - else - outmsg = "You fail to overload [S] by shining [src] at [S.p_their()] sensors!" - - //cameras - else if(istype(target, /obj/machinery/camera)) - var/obj/machinery/camera/C = target - if(prob(effectchance * diode.rating)) - C.emp_act(EMP_HEAVY) - outmsg = "You hit the lens of [C] with [src], temporarily disabling the camera!" - log_combat(user, C, "EMPed", src) - else - outmsg = "You miss the lens of [C] with [src]!" - - //catpeople - for(var/mob/living/carbon/human/H in view(1,targloc)) - if(!isfelinid(H) || H.incapacitated() || H.is_blind()) - continue - if(user.mobility_flags & MOBILITY_STAND) - H.setDir(get_dir(H,targloc)) // kitty always looks at the light - if(prob(effectchance * diode.rating)) - H.visible_message("[H] makes a grab for the light!","LIGHT!") - H.Move(targloc) - log_combat(user, H, "moved with a laser pointer",src) - else - H.visible_message("[H] looks briefly distracted by the light.", "You're briefly tempted by the shiny light...") - else - H.visible_message("[H] stares at the light.", "You stare at the light...") - - //cats! - for(var/mob/living/simple_animal/pet/cat/C in view(1,targloc)) - if(prob(effectchance * diode.rating)) - C.visible_message("[C] pounces on the light!","LIGHT!") - C.Move(targloc) - C.set_resting(TRUE, FALSE) - else - C.visible_message("[C] looks uninterested in your games.","You spot [user] shining [src] at you. How insulting!") - - //laser pointer image - icon_state = "pointer_[pointer_icon_state]" - var/image/I = image('icons/obj/projectiles.dmi',targloc,pointer_icon_state,10) - var/list/click_params = params2list(params) - if(click_params) - if(click_params["icon-x"]) - I.pixel_x = (text2num(click_params["icon-x"]) - 16) - if(click_params["icon-y"]) - I.pixel_y = (text2num(click_params["icon-y"]) - 16) - else - I.pixel_x = target.pixel_x + rand(-5,5) - I.pixel_y = target.pixel_y + rand(-5,5) - - if(outmsg) - to_chat(user, outmsg) - else - to_chat(user, "You point [src] at [target].") - - energy -= 1 - if(energy <= max_energy) - if(!recharging) - recharging = TRUE - START_PROCESSING(SSobj, src) - if(energy <= 0) - to_chat(user, "[src]'s battery is overused, it needs time to recharge!") - recharge_locked = TRUE - - flick_overlay_view(I, targloc, 10) - icon_state = "pointer" - -/obj/item/laser_pointer/process() - if(!diode) - recharging = FALSE - return PROCESS_KILL - if(prob(20 + diode.rating*20 - recharge_locked*2)) //t1 is 20, 2 40 - energy += 1 - if(energy >= max_energy) - energy = max_energy - recharging = FALSE - recharge_locked = FALSE - return ..() +/obj/item/laser_pointer + name = "laser pointer" + desc = "Don't shine it in your eyes!" + icon = 'icons/obj/device.dmi' + icon_state = "pointer" + item_state = "pen" + var/pointer_icon_state + flags_1 = CONDUCT_1 + item_flags = NOBLUDGEON + slot_flags = ITEM_SLOT_BELT + custom_materials = list(/datum/material/iron=500, /datum/material/glass=500) + w_class = WEIGHT_CLASS_SMALL + var/turf/pointer_loc + var/energy = 10 + var/max_energy = 10 + var/effectchance = 30 + var/recharging = FALSE + var/recharge_locked = FALSE + var/obj/item/stock_parts/micro_laser/diode //used for upgrading! + + +/obj/item/laser_pointer/red + pointer_icon_state = "red_laser" +/obj/item/laser_pointer/green + pointer_icon_state = "green_laser" +/obj/item/laser_pointer/blue + pointer_icon_state = "blue_laser" +/obj/item/laser_pointer/purple + pointer_icon_state = "purple_laser" + +/obj/item/laser_pointer/Initialize() + . = ..() + diode = new(src) + if(!pointer_icon_state) + pointer_icon_state = pick("red_laser","green_laser","blue_laser","purple_laser") + +/obj/item/laser_pointer/upgraded/Initialize() + . = ..() + diode = new /obj/item/stock_parts/micro_laser/ultra + +/obj/item/laser_pointer/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/stock_parts/micro_laser)) + if(!diode) + if(!user.transferItemToLoc(W, src)) + return + diode = W + to_chat(user, "You install a [diode.name] in [src].") + else + to_chat(user, "[src] already has a diode installed!") + + else if(W.tool_behaviour == TOOL_SCREWDRIVER) + if(diode) + to_chat(user, "You remove the [diode.name] from \the [src].") + diode.forceMove(drop_location()) + diode = null + else + return ..() + +/obj/item/laser_pointer/examine(mob/user) + . = ..() + if(in_range(user, src) || isobserver(user)) + if(!diode) + . += "The diode is missing." + else + . += "A class [diode.rating] laser diode is installed. It is screwed in place." + +/obj/item/laser_pointer/afterattack(atom/target, mob/living/user, flag, params) + . = ..() + laser_act(target, user, params) + +/obj/item/laser_pointer/proc/laser_act(atom/target, mob/living/user, params) + if( !(user in (viewers(7,target))) ) + return + if (!diode) + to_chat(user, "You point [src] at [target], but nothing happens!") + return + if (!user.IsAdvancedToolUser()) + to_chat(user, "You don't have the dexterity to do this!") + return + if(HAS_TRAIT(user, TRAIT_CHUNKYFINGERS)) + to_chat(user, "Your fingers can't press the button!") + return + add_fingerprint(user) + + //nothing happens if the battery is drained + if(recharge_locked) + to_chat(user, "You point [src] at [target], but it's still charging.") + return + + var/outmsg + var/turf/targloc = get_turf(target) + + //human/alien mobs + if(iscarbon(target)) + var/mob/living/carbon/C = target + if(user.zone_selected == BODY_ZONE_PRECISE_EYES) + + var/severity = 1 + if(prob(33)) + severity = 2 + else if(prob(50)) + severity = 0 + + //chance to actually hit the eyes depends on internal component + if(prob(effectchance * diode.rating) && C.flash_act(severity)) + outmsg = "You blind [C] by shining [src] in [C.p_their()] eyes." + log_combat(user, C, "blinded with a laser pointer",src) + else + outmsg = "You fail to blind [C] by shining [src] at [C.p_their()] eyes!" + log_combat(user, C, "attempted to blind with a laser pointer",src) + + //robots + else if(iscyborg(target)) + var/mob/living/silicon/S = target + log_combat(user, S, "shone in the sensors", src) + //chance to actually hit the eyes depends on internal component + if(prob(effectchance * diode.rating)) + S.flash_act(affect_silicon = 1) + S.Paralyze(rand(100,200)) + to_chat(S, "Your sensors were overloaded by a laser!") + outmsg = "You overload [S] by shining [src] at [S.p_their()] sensors." + else + outmsg = "You fail to overload [S] by shining [src] at [S.p_their()] sensors!" + + //cameras + else if(istype(target, /obj/machinery/camera)) + var/obj/machinery/camera/C = target + if(prob(effectchance * diode.rating)) + C.emp_act(EMP_HEAVY) + outmsg = "You hit the lens of [C] with [src], temporarily disabling the camera!" + log_combat(user, C, "EMPed", src) + else + outmsg = "You miss the lens of [C] with [src]!" + + //catpeople + for(var/mob/living/carbon/human/H in view(1,targloc)) + if(!isfelinid(H) || H.incapacitated() || H.is_blind()) + continue + if(user.mobility_flags & MOBILITY_STAND) + H.setDir(get_dir(H,targloc)) // kitty always looks at the light + if(prob(effectchance * diode.rating)) + H.visible_message("[H] makes a grab for the light!","LIGHT!") + H.Move(targloc) + log_combat(user, H, "moved with a laser pointer",src) + else + H.visible_message("[H] looks briefly distracted by the light.", "You're briefly tempted by the shiny light...") + else + H.visible_message("[H] stares at the light.", "You stare at the light...") + + //cats! + for(var/mob/living/simple_animal/pet/cat/C in view(1,targloc)) + if(prob(effectchance * diode.rating)) + C.visible_message("[C] pounces on the light!","LIGHT!") + C.Move(targloc) + C.set_resting(TRUE, FALSE) + else + C.visible_message("[C] looks uninterested in your games.","You spot [user] shining [src] at you. How insulting!") + + //laser pointer image + icon_state = "pointer_[pointer_icon_state]" + var/image/I = image('icons/obj/projectiles.dmi',targloc,pointer_icon_state,10) + var/list/click_params = params2list(params) + if(click_params) + if(click_params["icon-x"]) + I.pixel_x = (text2num(click_params["icon-x"]) - 16) + if(click_params["icon-y"]) + I.pixel_y = (text2num(click_params["icon-y"]) - 16) + else + I.pixel_x = target.pixel_x + rand(-5,5) + I.pixel_y = target.pixel_y + rand(-5,5) + + if(outmsg) + to_chat(user, outmsg) + else + to_chat(user, "You point [src] at [target].") + + energy -= 1 + if(energy <= max_energy) + if(!recharging) + recharging = TRUE + START_PROCESSING(SSobj, src) + if(energy <= 0) + to_chat(user, "[src]'s battery is overused, it needs time to recharge!") + recharge_locked = TRUE + + flick_overlay_view(I, targloc, 10) + icon_state = "pointer" + +/obj/item/laser_pointer/process() + if(!diode) + recharging = FALSE + return PROCESS_KILL + if(prob(20 + diode.rating*20 - recharge_locked*2)) //t1 is 20, 2 40 + energy += 1 + if(energy >= max_energy) + energy = max_energy + recharging = FALSE + recharge_locked = FALSE + return ..() diff --git a/code/game/objects/items/devices/lightreplacer.dm b/code/game/objects/items/devices/lightreplacer.dm index 5dd9c4db557f..b45655254513 100644 --- a/code/game/objects/items/devices/lightreplacer.dm +++ b/code/game/objects/items/devices/lightreplacer.dm @@ -1,266 +1,266 @@ - -// Light Replacer (LR) -// -// ABOUT THE DEVICE -// -// This is a device supposedly to be used by Janitors and Janitor Cyborgs which will -// allow them to easily replace lights. This was mostly designed for Janitor Cyborgs since -// they don't have hands or a way to replace lightbulbs. -// -// HOW IT WORKS -// -// You attack a light fixture with it, if the light fixture is broken it will replace the -// light fixture with a working light; the broken light is then placed on the floor for the -// user to then pickup with a trash bag. If it's empty then it will just place a light in the fixture. -// -// HOW TO REFILL THE DEVICE -// -// It will need to be manually refilled with lights. -// If it's part of a robot module, it will charge when the Robot is inside a Recharge Station. -// -// EMAGGED FEATURES -// -// NOTICE: The Cyborg cannot use the emagged Light Replacer and the light's explosion was nerfed. It cannot create holes in the station anymore. -// -// I'm not sure everyone will react the emag's features so please say what your opinions are of it. -// -// When emagged it will rig every light it replaces, which will explode when the light is on. -// This is VERY noticable, even the device's name changes when you emag it so if anyone -// examines you when you're holding it in your hand, you will be discovered. -// It will also be very obvious who is setting all these lights off, since only Janitor Borgs and Janitors have easy -// access to them, and only one of them can emag their device. -// -// The explosion cannot insta-kill anyone with 30% or more health. - -#define LIGHT_OK 0 -#define LIGHT_EMPTY 1 -#define LIGHT_BROKEN 2 -#define LIGHT_BURNED 3 - - -/obj/item/lightreplacer - - name = "light replacer" - desc = "A device to automatically replace lights. Refill with broken or working light bulbs, or sheets of glass." - - icon = 'icons/obj/janitor.dmi' - icon_state = "lightreplacer0" - item_state = "electronic" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - w_class = WEIGHT_CLASS_SMALL - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BELT - force = 8 - - var/max_uses = 20 - var/uses = 10 - // How much to increase per each glass? - var/increment = 5 - // How much to take from the glass? - var/decrement = 1 - var/charge = 1 - - // Eating used bulbs gives us bulb shards - var/bulb_shards = 0 - // when we get this many shards, we get a free bulb. - var/shards_required = 4 - -/obj/item/lightreplacer/examine(mob/user) - . = ..() - . += status_string() - -/obj/item/lightreplacer/attackby(obj/item/W, mob/user, params) - - if(istype(W, /obj/item/stack/sheet/glass)) - var/obj/item/stack/sheet/glass/G = W - if(uses >= max_uses) - to_chat(user, "[src.name] is full.") - return - else if(G.use(decrement)) - AddUses(increment) - to_chat(user, "You insert a piece of glass into \the [src.name]. You have [uses] light\s remaining.") - return - else - to_chat(user, "You need one sheet of glass to replace lights!") - - if(istype(W, /obj/item/shard)) - if(uses >= max_uses) - to_chat(user, "\The [src] is full.") - return - if(!user.temporarilyRemoveItemFromInventory(W)) - return - AddUses(round(increment*0.75)) - to_chat(user, "You insert a shard of glass into \the [src]. You have [uses] light\s remaining.") - qdel(W) - return - - if(istype(W, /obj/item/light)) - var/obj/item/light/L = W - if(L.status == 0) // LIGHT OKAY - if(uses < max_uses) - if(!user.temporarilyRemoveItemFromInventory(W)) - return - AddUses(1) - qdel(L) - else - if(!user.temporarilyRemoveItemFromInventory(W)) - return - to_chat(user, "You insert [L] into \the [src].") - AddShards(1, user) - qdel(L) - return - - if(istype(W, /obj/item/storage)) - var/obj/item/storage/S = W - var/found_lightbulbs = FALSE - var/replaced_something = TRUE - - for(var/obj/item/I in S.contents) - if(istype(I, /obj/item/light)) - var/obj/item/light/L = I - found_lightbulbs = TRUE - if(src.uses >= max_uses) - break - if(L.status == LIGHT_OK) - replaced_something = TRUE - AddUses(1) - qdel(L) - - else if(L.status == LIGHT_BROKEN || L.status == LIGHT_BURNED) - replaced_something = TRUE - AddShards(1, user) - qdel(L) - - if(!found_lightbulbs) - to_chat(user, "\The [S] contains no bulbs.") - return - - if(!replaced_something && src.uses == max_uses) - to_chat(user, "\The [src] is full!") - return - - to_chat(user, "You fill \the [src] with lights from \the [S]. " + status_string() + "") - -/obj/item/lightreplacer/emag_act() - if(obj_flags & EMAGGED) - return - Emag() - -/obj/item/lightreplacer/attack_self(mob/user) - for(var/obj/machinery/light/target in user.loc) - ReplaceLight(target, user) - to_chat(user, status_string()) - -/obj/item/lightreplacer/update_icon_state() - icon_state = "lightreplacer[(obj_flags & EMAGGED ? 1 : 0)]" - -/obj/item/lightreplacer/proc/status_string() - return "It has [uses] light\s remaining (plus [bulb_shards] fragment\s)." - -/obj/item/lightreplacer/proc/Use(mob/user) - playsound(src.loc, 'sound/machines/click.ogg', 50, TRUE) - AddUses(-1) - return 1 - -// Negative numbers will subtract -/obj/item/lightreplacer/proc/AddUses(amount = 1) - uses = clamp(uses + amount, 0, max_uses) - -/obj/item/lightreplacer/proc/AddShards(amount = 1, user) - bulb_shards += amount - var/new_bulbs = round(bulb_shards / shards_required) - if(new_bulbs > 0) - AddUses(new_bulbs) - bulb_shards = bulb_shards % shards_required - if(new_bulbs != 0) - to_chat(user, "\The [src] fabricates a new bulb from the broken glass it has stored. It now has [uses] uses.") - playsound(src.loc, 'sound/machines/ding.ogg', 50, TRUE) - return new_bulbs - -/obj/item/lightreplacer/proc/Charge(var/mob/user) - charge += 1 - if(charge > 3) - AddUses(1) - charge = 1 - -/obj/item/lightreplacer/proc/ReplaceLight(obj/machinery/light/target, mob/living/U) - - if(target.status != LIGHT_OK) - if(CanUse(U)) - if(!Use(U)) - return - to_chat(U, "You replace \the [target.fitting] with \the [src].") - - if(target.status != LIGHT_EMPTY) - AddShards(1, U) - target.status = LIGHT_EMPTY - target.update() - - var/obj/item/light/L2 = new target.light_type() - - target.status = L2.status - target.switchcount = L2.switchcount - target.rigged = (obj_flags & EMAGGED ? 1 : 0) - target.brightness = L2.brightness - target.on = target.has_power() - target.update() - qdel(L2) - - if(target.on && target.rigged) - target.explode() - return - - else - to_chat(U, "\The [src]'s refill light blinks red.") - return - else - to_chat(U, "There is a working [target.fitting] already inserted!") - return - -/obj/item/lightreplacer/proc/Emag() - obj_flags ^= EMAGGED - playsound(src.loc, "sparks", 100, TRUE) - if(obj_flags & EMAGGED) - name = "shortcircuited [initial(name)]" - else - name = initial(name) - update_icon() - -/obj/item/lightreplacer/proc/CanUse(mob/living/user) - src.add_fingerprint(user) - if(uses > 0) - return 1 - else - return 0 - -/obj/item/lightreplacer/afterattack(atom/T, mob/U, proximity) - . = ..() - if(!proximity) - return - if(!isturf(T)) - return - - var/used = FALSE - for(var/atom/A in T) - if(!CanUse(U)) - break - used = TRUE - if(istype(A, /obj/machinery/light)) - ReplaceLight(A, U) - - if(!used) - to_chat(U, "\The [src]'s refill light blinks red.") - -/obj/item/lightreplacer/proc/janicart_insert(mob/user, obj/structure/janitorialcart/J) - J.put_in_cart(src, user) - J.myreplacer = src - J.update_icon() - -/obj/item/lightreplacer/cyborg/janicart_insert(mob/user, obj/structure/janitorialcart/J) - return - -#undef LIGHT_OK -#undef LIGHT_EMPTY -#undef LIGHT_BROKEN -#undef LIGHT_BURNED + +// Light Replacer (LR) +// +// ABOUT THE DEVICE +// +// This is a device supposedly to be used by Janitors and Janitor Cyborgs which will +// allow them to easily replace lights. This was mostly designed for Janitor Cyborgs since +// they don't have hands or a way to replace lightbulbs. +// +// HOW IT WORKS +// +// You attack a light fixture with it, if the light fixture is broken it will replace the +// light fixture with a working light; the broken light is then placed on the floor for the +// user to then pickup with a trash bag. If it's empty then it will just place a light in the fixture. +// +// HOW TO REFILL THE DEVICE +// +// It will need to be manually refilled with lights. +// If it's part of a robot module, it will charge when the Robot is inside a Recharge Station. +// +// EMAGGED FEATURES +// +// NOTICE: The Cyborg cannot use the emagged Light Replacer and the light's explosion was nerfed. It cannot create holes in the station anymore. +// +// I'm not sure everyone will react the emag's features so please say what your opinions are of it. +// +// When emagged it will rig every light it replaces, which will explode when the light is on. +// This is VERY noticable, even the device's name changes when you emag it so if anyone +// examines you when you're holding it in your hand, you will be discovered. +// It will also be very obvious who is setting all these lights off, since only Janitor Borgs and Janitors have easy +// access to them, and only one of them can emag their device. +// +// The explosion cannot insta-kill anyone with 30% or more health. + +#define LIGHT_OK 0 +#define LIGHT_EMPTY 1 +#define LIGHT_BROKEN 2 +#define LIGHT_BURNED 3 + + +/obj/item/lightreplacer + + name = "light replacer" + desc = "A device to automatically replace lights. Refill with broken or working light bulbs, or sheets of glass." + + icon = 'icons/obj/janitor.dmi' + icon_state = "lightreplacer0" + item_state = "electronic" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + w_class = WEIGHT_CLASS_SMALL + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BELT + force = 8 + + var/max_uses = 20 + var/uses = 10 + // How much to increase per each glass? + var/increment = 5 + // How much to take from the glass? + var/decrement = 1 + var/charge = 1 + + // Eating used bulbs gives us bulb shards + var/bulb_shards = 0 + // when we get this many shards, we get a free bulb. + var/shards_required = 4 + +/obj/item/lightreplacer/examine(mob/user) + . = ..() + . += status_string() + +/obj/item/lightreplacer/attackby(obj/item/W, mob/user, params) + + if(istype(W, /obj/item/stack/sheet/glass)) + var/obj/item/stack/sheet/glass/G = W + if(uses >= max_uses) + to_chat(user, "[src.name] is full.") + return + else if(G.use(decrement)) + AddUses(increment) + to_chat(user, "You insert a piece of glass into \the [src.name]. You have [uses] light\s remaining.") + return + else + to_chat(user, "You need one sheet of glass to replace lights!") + + if(istype(W, /obj/item/shard)) + if(uses >= max_uses) + to_chat(user, "\The [src] is full.") + return + if(!user.temporarilyRemoveItemFromInventory(W)) + return + AddUses(round(increment*0.75)) + to_chat(user, "You insert a shard of glass into \the [src]. You have [uses] light\s remaining.") + qdel(W) + return + + if(istype(W, /obj/item/light)) + var/obj/item/light/L = W + if(L.status == 0) // LIGHT OKAY + if(uses < max_uses) + if(!user.temporarilyRemoveItemFromInventory(W)) + return + AddUses(1) + qdel(L) + else + if(!user.temporarilyRemoveItemFromInventory(W)) + return + to_chat(user, "You insert [L] into \the [src].") + AddShards(1, user) + qdel(L) + return + + if(istype(W, /obj/item/storage)) + var/obj/item/storage/S = W + var/found_lightbulbs = FALSE + var/replaced_something = TRUE + + for(var/obj/item/I in S.contents) + if(istype(I, /obj/item/light)) + var/obj/item/light/L = I + found_lightbulbs = TRUE + if(src.uses >= max_uses) + break + if(L.status == LIGHT_OK) + replaced_something = TRUE + AddUses(1) + qdel(L) + + else if(L.status == LIGHT_BROKEN || L.status == LIGHT_BURNED) + replaced_something = TRUE + AddShards(1, user) + qdel(L) + + if(!found_lightbulbs) + to_chat(user, "\The [S] contains no bulbs.") + return + + if(!replaced_something && src.uses == max_uses) + to_chat(user, "\The [src] is full!") + return + + to_chat(user, "You fill \the [src] with lights from \the [S]. " + status_string() + "") + +/obj/item/lightreplacer/emag_act() + if(obj_flags & EMAGGED) + return + Emag() + +/obj/item/lightreplacer/attack_self(mob/user) + for(var/obj/machinery/light/target in user.loc) + ReplaceLight(target, user) + to_chat(user, status_string()) + +/obj/item/lightreplacer/update_icon_state() + icon_state = "lightreplacer[(obj_flags & EMAGGED ? 1 : 0)]" + +/obj/item/lightreplacer/proc/status_string() + return "It has [uses] light\s remaining (plus [bulb_shards] fragment\s)." + +/obj/item/lightreplacer/proc/Use(mob/user) + playsound(src.loc, 'sound/machines/click.ogg', 50, TRUE) + AddUses(-1) + return 1 + +// Negative numbers will subtract +/obj/item/lightreplacer/proc/AddUses(amount = 1) + uses = clamp(uses + amount, 0, max_uses) + +/obj/item/lightreplacer/proc/AddShards(amount = 1, user) + bulb_shards += amount + var/new_bulbs = round(bulb_shards / shards_required) + if(new_bulbs > 0) + AddUses(new_bulbs) + bulb_shards = bulb_shards % shards_required + if(new_bulbs != 0) + to_chat(user, "\The [src] fabricates a new bulb from the broken glass it has stored. It now has [uses] uses.") + playsound(src.loc, 'sound/machines/ding.ogg', 50, TRUE) + return new_bulbs + +/obj/item/lightreplacer/proc/Charge(var/mob/user) + charge += 1 + if(charge > 3) + AddUses(1) + charge = 1 + +/obj/item/lightreplacer/proc/ReplaceLight(obj/machinery/light/target, mob/living/U) + + if(target.status != LIGHT_OK) + if(CanUse(U)) + if(!Use(U)) + return + to_chat(U, "You replace \the [target.fitting] with \the [src].") + + if(target.status != LIGHT_EMPTY) + AddShards(1, U) + target.status = LIGHT_EMPTY + target.update() + + var/obj/item/light/L2 = new target.light_type() + + target.status = L2.status + target.switchcount = L2.switchcount + target.rigged = (obj_flags & EMAGGED ? 1 : 0) + target.brightness = L2.brightness + target.on = target.has_power() + target.update() + qdel(L2) + + if(target.on && target.rigged) + target.explode() + return + + else + to_chat(U, "\The [src]'s refill light blinks red.") + return + else + to_chat(U, "There is a working [target.fitting] already inserted!") + return + +/obj/item/lightreplacer/proc/Emag() + obj_flags ^= EMAGGED + playsound(src.loc, "sparks", 100, TRUE) + if(obj_flags & EMAGGED) + name = "shortcircuited [initial(name)]" + else + name = initial(name) + update_icon() + +/obj/item/lightreplacer/proc/CanUse(mob/living/user) + src.add_fingerprint(user) + if(uses > 0) + return 1 + else + return 0 + +/obj/item/lightreplacer/afterattack(atom/T, mob/U, proximity) + . = ..() + if(!proximity) + return + if(!isturf(T)) + return + + var/used = FALSE + for(var/atom/A in T) + if(!CanUse(U)) + break + used = TRUE + if(istype(A, /obj/machinery/light)) + ReplaceLight(A, U) + + if(!used) + to_chat(U, "\The [src]'s refill light blinks red.") + +/obj/item/lightreplacer/proc/janicart_insert(mob/user, obj/structure/janitorialcart/J) + J.put_in_cart(src, user) + J.myreplacer = src + J.update_icon() + +/obj/item/lightreplacer/cyborg/janicart_insert(mob/user, obj/structure/janitorialcart/J) + return + +#undef LIGHT_OK +#undef LIGHT_EMPTY +#undef LIGHT_BROKEN +#undef LIGHT_BURNED diff --git a/code/game/objects/items/devices/multitool.dm b/code/game/objects/items/devices/multitool.dm index 2776ccb0daa6..fe01e27578a1 100644 --- a/code/game/objects/items/devices/multitool.dm +++ b/code/game/objects/items/devices/multitool.dm @@ -1,176 +1,176 @@ -#define PROXIMITY_NONE "" -#define PROXIMITY_ON_SCREEN "_red" -#define PROXIMITY_NEAR "_yellow" - -/** - * Multitool -- A multitool is used for hacking electronic devices. - * - */ - - - - -/obj/item/multitool - name = "multitool" - desc = "Used for pulsing wires to test which to cut. Not recommended by doctors." - icon = 'waspstation/icons/obj/tools.dmi' //WaspStation Edit - Better Tool Sprites - icon_state = "multitool" - item_state = "multitool" - lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' - force = 5 - w_class = WEIGHT_CLASS_SMALL - tool_behaviour = TOOL_MULTITOOL - throwforce = 0 - throw_range = 7 - throw_speed = 3 - drop_sound = 'sound/items/handling/multitool_drop.ogg' - pickup_sound = 'sound/items/handling/multitool_pickup.ogg' - custom_materials = list(/datum/material/iron=50, /datum/material/glass=20) - custom_premium_price = 450 - toolspeed = 1 - usesound = 'sound/weapons/empty.ogg' - var/obj/machinery/buffer // simple machine buffer for device linkage - var/mode = 0 - -/obj/item/multitool/examine(mob/user) - . = ..() - . += "Its buffer [buffer ? "contains [buffer]." : "is empty."]" - -/obj/item/multitool/suicide_act(mob/living/carbon/user) - user.visible_message("[user] puts the [src] to [user.p_their()] chest. It looks like [user.p_theyre()] trying to pulse [user.p_their()] heart off!") - return OXYLOSS//theres a reason it wasnt recommended by doctors - - -// Syndicate device disguised as a multitool; it will turn red when an AI camera is nearby. - -/obj/item/multitool/ai_detect - var/track_cooldown = 0 - var/track_delay = 10 //How often it checks for proximity - var/detect_state = PROXIMITY_NONE - var/rangealert = 8 //Glows red when inside - var/rangewarning = 20 //Glows yellow when inside - var/hud_type = DATA_HUD_AI_DETECT - var/hud_on = FALSE - var/mob/camera/aiEye/remote/ai_detector/eye - var/datum/action/item_action/toggle_multitool/toggle_action - -/obj/item/multitool/ai_detect/Initialize() - . = ..() - START_PROCESSING(SSobj, src) - eye = new /mob/camera/aiEye/remote/ai_detector() - toggle_action = new /datum/action/item_action/toggle_multitool(src) - -/obj/item/multitool/ai_detect/Destroy() - STOP_PROCESSING(SSobj, src) - if(hud_on && ismob(loc)) - remove_hud(loc) - QDEL_NULL(toggle_action) - QDEL_NULL(eye) - return ..() - -/obj/item/multitool/ai_detect/ui_action_click() - return - -/obj/item/multitool/ai_detect/equipped(mob/living/carbon/human/user, slot) - ..() - if(hud_on) - show_hud(user) - -/obj/item/multitool/ai_detect/dropped(mob/living/carbon/human/user) - ..() - if(hud_on) - remove_hud(user) - -/obj/item/multitool/ai_detect/process() - if(track_cooldown > world.time) - return - detect_state = PROXIMITY_NONE - if(eye.eye_user) - eye.setLoc(get_turf(src)) - multitool_detect() - update_icon() - track_cooldown = world.time + track_delay - -/obj/item/multitool/ai_detect/proc/toggle_hud(mob/user) - hud_on = !hud_on - if(user) - to_chat(user, "You toggle the ai detection HUD on [src] [hud_on ? "on" : "off"].") - if(hud_on) - show_hud(user) - else - remove_hud(user) - -/obj/item/multitool/ai_detect/proc/show_hud(mob/user) - if(user && hud_type) - var/obj/screen/plane_master/camera_static/PM = user.hud_used.plane_masters["[CAMERA_STATIC_PLANE]"] - PM.alpha = 150 - var/datum/atom_hud/H = GLOB.huds[hud_type] - if(!H.hudusers[user]) - H.add_hud_to(user) - eye.eye_user = user - eye.setLoc(get_turf(src)) - -/obj/item/multitool/ai_detect/proc/remove_hud(mob/user) - if(user && hud_type) - var/obj/screen/plane_master/camera_static/PM = user.hud_used.plane_masters["[CAMERA_STATIC_PLANE]"] - PM.alpha = 255 - var/datum/atom_hud/H = GLOB.huds[hud_type] - H.remove_hud_from(user) - if(eye) - eye.setLoc(null) - eye.eye_user = null - -/obj/item/multitool/ai_detect/proc/multitool_detect() - var/turf/our_turf = get_turf(src) - for(var/mob/living/silicon/ai/AI in GLOB.ai_list) - if(AI.cameraFollow == src) - detect_state = PROXIMITY_ON_SCREEN - break - - if(detect_state) - return - var/datum/camerachunk/chunk = GLOB.cameranet.chunkGenerated(our_turf.x, our_turf.y, our_turf.z) - if(chunk && chunk.seenby.len) - for(var/mob/camera/aiEye/A in chunk.seenby) - if(!A.ai_detector_visible) - continue - var/turf/detect_turf = get_turf(A) - if(get_dist(our_turf, detect_turf) < rangealert) - detect_state = PROXIMITY_ON_SCREEN - break - if(get_dist(our_turf, detect_turf) < rangewarning) - detect_state = PROXIMITY_NEAR - break - -/mob/camera/aiEye/remote/ai_detector - name = "AI detector eye" - ai_detector_visible = FALSE - use_static = USE_STATIC_TRANSPARENT - visible_icon = FALSE - -/datum/action/item_action/toggle_multitool - name = "Toggle AI detector HUD" - check_flags = NONE - -/datum/action/item_action/toggle_multitool/Trigger() - if(!..()) - return 0 - if(target) - var/obj/item/multitool/ai_detect/M = target - M.toggle_hud(owner) - return 1 - -/obj/item/multitool/abductor - name = "alien multitool" - desc = "An omni-technological interface." - icon = 'icons/obj/abductor.dmi' - icon_state = "multitool" - toolspeed = 0.1 - -/obj/item/multitool/cyborg - name = "electronic multitool" - desc = "Optimised version of a regular multitool. Streamlines processes handled by its internal microchip." - icon = 'icons/obj/items_cyborg.dmi' - icon_state = "multitool_cyborg" - toolspeed = 0.5 +#define PROXIMITY_NONE "" +#define PROXIMITY_ON_SCREEN "_red" +#define PROXIMITY_NEAR "_yellow" + +/** + * Multitool -- A multitool is used for hacking electronic devices. + * + */ + + + + +/obj/item/multitool + name = "multitool" + desc = "Used for pulsing wires to test which to cut. Not recommended by doctors." + icon = 'waspstation/icons/obj/tools.dmi' //WaspStation Edit - Better Tool Sprites + icon_state = "multitool" + item_state = "multitool" + lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' + force = 5 + w_class = WEIGHT_CLASS_SMALL + tool_behaviour = TOOL_MULTITOOL + throwforce = 0 + throw_range = 7 + throw_speed = 3 + drop_sound = 'sound/items/handling/multitool_drop.ogg' + pickup_sound = 'sound/items/handling/multitool_pickup.ogg' + custom_materials = list(/datum/material/iron=50, /datum/material/glass=20) + custom_premium_price = 450 + toolspeed = 1 + usesound = 'sound/weapons/empty.ogg' + var/obj/machinery/buffer // simple machine buffer for device linkage + var/mode = 0 + +/obj/item/multitool/examine(mob/user) + . = ..() + . += "Its buffer [buffer ? "contains [buffer]." : "is empty."]" + +/obj/item/multitool/suicide_act(mob/living/carbon/user) + user.visible_message("[user] puts the [src] to [user.p_their()] chest. It looks like [user.p_theyre()] trying to pulse [user.p_their()] heart off!") + return OXYLOSS//theres a reason it wasnt recommended by doctors + + +// Syndicate device disguised as a multitool; it will turn red when an AI camera is nearby. + +/obj/item/multitool/ai_detect + var/track_cooldown = 0 + var/track_delay = 10 //How often it checks for proximity + var/detect_state = PROXIMITY_NONE + var/rangealert = 8 //Glows red when inside + var/rangewarning = 20 //Glows yellow when inside + var/hud_type = DATA_HUD_AI_DETECT + var/hud_on = FALSE + var/mob/camera/aiEye/remote/ai_detector/eye + var/datum/action/item_action/toggle_multitool/toggle_action + +/obj/item/multitool/ai_detect/Initialize() + . = ..() + START_PROCESSING(SSobj, src) + eye = new /mob/camera/aiEye/remote/ai_detector() + toggle_action = new /datum/action/item_action/toggle_multitool(src) + +/obj/item/multitool/ai_detect/Destroy() + STOP_PROCESSING(SSobj, src) + if(hud_on && ismob(loc)) + remove_hud(loc) + QDEL_NULL(toggle_action) + QDEL_NULL(eye) + return ..() + +/obj/item/multitool/ai_detect/ui_action_click() + return + +/obj/item/multitool/ai_detect/equipped(mob/living/carbon/human/user, slot) + ..() + if(hud_on) + show_hud(user) + +/obj/item/multitool/ai_detect/dropped(mob/living/carbon/human/user) + ..() + if(hud_on) + remove_hud(user) + +/obj/item/multitool/ai_detect/process() + if(track_cooldown > world.time) + return + detect_state = PROXIMITY_NONE + if(eye.eye_user) + eye.setLoc(get_turf(src)) + multitool_detect() + update_icon() + track_cooldown = world.time + track_delay + +/obj/item/multitool/ai_detect/proc/toggle_hud(mob/user) + hud_on = !hud_on + if(user) + to_chat(user, "You toggle the ai detection HUD on [src] [hud_on ? "on" : "off"].") + if(hud_on) + show_hud(user) + else + remove_hud(user) + +/obj/item/multitool/ai_detect/proc/show_hud(mob/user) + if(user && hud_type) + var/obj/screen/plane_master/camera_static/PM = user.hud_used.plane_masters["[CAMERA_STATIC_PLANE]"] + PM.alpha = 150 + var/datum/atom_hud/H = GLOB.huds[hud_type] + if(!H.hudusers[user]) + H.add_hud_to(user) + eye.eye_user = user + eye.setLoc(get_turf(src)) + +/obj/item/multitool/ai_detect/proc/remove_hud(mob/user) + if(user && hud_type) + var/obj/screen/plane_master/camera_static/PM = user.hud_used.plane_masters["[CAMERA_STATIC_PLANE]"] + PM.alpha = 255 + var/datum/atom_hud/H = GLOB.huds[hud_type] + H.remove_hud_from(user) + if(eye) + eye.setLoc(null) + eye.eye_user = null + +/obj/item/multitool/ai_detect/proc/multitool_detect() + var/turf/our_turf = get_turf(src) + for(var/mob/living/silicon/ai/AI in GLOB.ai_list) + if(AI.cameraFollow == src) + detect_state = PROXIMITY_ON_SCREEN + break + + if(detect_state || !our_turf) + return + var/datum/camerachunk/chunk = GLOB.cameranet.chunkGenerated(our_turf.x, our_turf.y, our_turf.z) + if(chunk && chunk.seenby.len) + for(var/mob/camera/aiEye/A in chunk.seenby) + if(!A.ai_detector_visible) + continue + var/turf/detect_turf = get_turf(A) + if(get_dist(our_turf, detect_turf) < rangealert) + detect_state = PROXIMITY_ON_SCREEN + break + if(get_dist(our_turf, detect_turf) < rangewarning) + detect_state = PROXIMITY_NEAR + break + +/mob/camera/aiEye/remote/ai_detector + name = "AI detector eye" + ai_detector_visible = FALSE + use_static = USE_STATIC_TRANSPARENT + visible_icon = FALSE + +/datum/action/item_action/toggle_multitool + name = "Toggle AI detector HUD" + check_flags = NONE + +/datum/action/item_action/toggle_multitool/Trigger() + if(!..()) + return 0 + if(target) + var/obj/item/multitool/ai_detect/M = target + M.toggle_hud(owner) + return 1 + +/obj/item/multitool/abductor + name = "alien multitool" + desc = "An omni-technological interface." + icon = 'icons/obj/abductor.dmi' + icon_state = "multitool" + toolspeed = 0.1 + +/obj/item/multitool/cyborg + name = "electronic multitool" + desc = "Optimised version of a regular multitool. Streamlines processes handled by its internal microchip." + icon = 'icons/obj/items_cyborg.dmi' + icon_state = "multitool_cyborg" + toolspeed = 0.5 diff --git a/code/game/objects/items/devices/ocd.dm b/code/game/objects/items/devices/ocd.dm index 52bb7352c886..50032810abdd 100644 --- a/code/game/objects/items/devices/ocd.dm +++ b/code/game/objects/items/devices/ocd.dm @@ -1,11 +1,11 @@ -/obj/item/devices/ocd_device - name = "Occupational Corruption Device" - desc = "When you need to make the lives of new-hires that much more confusing, think OCD." - icon = 'icons/obj/device.dmi' - icon_state = "gangtool-white" - -/obj/item/devices/ocd_device/attack_self(mob/user) - var/datum/round_event/bureaucratic_error/event = new() - event.start() - deadchat_broadcast(" An OCD has been activated! ") - qdel(src) +/obj/item/devices/ocd_device + name = "Occupational Corruption Device" + desc = "When you need to make the lives of new-hires that much more confusing, think OCD." + icon = 'icons/obj/device.dmi' + icon_state = "gangtool-white" + +/obj/item/devices/ocd_device/attack_self(mob/user) + var/datum/round_event/bureaucratic_error/event = new() + event.start() + deadchat_broadcast(" An OCD has been activated! ") + qdel(src) diff --git a/code/game/objects/items/devices/paicard.dm b/code/game/objects/items/devices/paicard.dm index 7f8b74985408..f37ec2b3b1cd 100644 --- a/code/game/objects/items/devices/paicard.dm +++ b/code/game/objects/items/devices/paicard.dm @@ -1,174 +1,174 @@ -/obj/item/paicard - name = "personal AI device" - icon = 'icons/obj/aicards.dmi' - icon_state = "pai" - item_state = "electronic" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - w_class = WEIGHT_CLASS_SMALL - slot_flags = ITEM_SLOT_BELT - var/mob/living/silicon/pai/pai - resistance_flags = FIRE_PROOF | ACID_PROOF | INDESTRUCTIBLE - -/obj/item/paicard/suicide_act(mob/living/user) - user.visible_message("[user] is staring sadly at [src]! [user.p_they()] can't keep living without real human intimacy!") - return OXYLOSS - -/obj/item/paicard/Initialize() - SSpai.pai_card_list += src - add_overlay("pai-off") - return ..() - -/obj/item/paicard/Destroy() - //Will stop people throwing friend pAIs into the singularity so they can respawn - SSpai.pai_card_list -= src - if (!QDELETED(pai)) - QDEL_NULL(pai) - return ..() - -/obj/item/paicard/attack_self(mob/user) - if (!in_range(src, user)) - return - user.set_machine(src) - var/dat = "Personal AI Device
                        " - if(pai) - if(!pai.master_dna || !pai.master) - dat += "Imprint Master DNA
                        " - dat += "Installed Personality: [pai.name]
                        " - dat += "Prime directive:
                        [pai.laws.zeroth]
                        " - for(var/slaws in pai.laws.supplied) - dat += "Additional directives:
                        [slaws]
                        " - dat += "Configure Directives
                        " - dat += "
                        " - dat += "

                        Device Settings


                        " - if(pai.radio) - dat += "Radio Uplink
                        " - dat += "Transmit: \[[pai.can_transmit? "Disable" : "Enable"] Radio Transmission\]
                        " - dat += "Receive: \[[pai.can_receive? "Disable" : "Enable"] Radio Reception\]
                        " - else - dat += "Radio Uplink
                        " - dat += "Radio firmware not loaded. Please install a pAI personality to load firmware.
                        " - if(ishuman(user)) - var/mob/living/carbon/human/H = user - if(H.real_name == pai.master || H.dna.unique_enzymes == pai.master_dna) - dat += "\[[pai.canholo? "Disable" : "Enable"] holomatrix projectors\]
                        " - dat += "\[Reset speech synthesis module\]
                        " - dat += "\[Wipe current pAI personality\]
                        " - else - dat += "No personality installed.
                        " - dat += "Searching for a personality... Press view available personalities to notify potential candidates." - dat += "\[View available personalities\]
                        " - user << browse(dat, "window=paicard") - onclose(user, "paicard") - return - -/obj/item/paicard/Topic(href, href_list) - - if(!usr || usr.stat) - return - - if(href_list["request"]) - SSpai.findPAI(src, usr) - - if(pai) - if(!(loc == usr)) - return - if(href_list["setdna"]) - if(pai.master_dna) - return - if(!iscarbon(usr)) - to_chat(usr, "You don't have any DNA, or your DNA is incompatible with this device!") - else - var/mob/living/carbon/M = usr - pai.master = M.real_name - pai.master_dna = M.dna.unique_enzymes - to_chat(pai, "You have been bound to a new master.") - pai.emittersemicd = FALSE - if(href_list["wipe"]) - var/confirm = input("Are you CERTAIN you wish to delete the current personality? This action cannot be undone.", "Personality Wipe") in list("Yes", "No") - if(confirm == "Yes") - if(pai) - to_chat(pai, "You feel yourself slipping away from reality.") - to_chat(pai, "Byte by byte you lose your sense of self.") - to_chat(pai, "Your mental faculties leave you.") - to_chat(pai, "oblivion... ") - qdel(pai) - if(href_list["fix_speech"]) - pai.stuttering = 0 - pai.slurring = 0 - pai.derpspeech = 0 - if(href_list["toggle_transmit"] || href_list["toggle_receive"]) - var/transmitting = href_list["toggle_transmit"] //it can't be both so if we know it's not transmitting it must be receiving. - var/transmit_holder = (transmitting ? WIRE_TX : WIRE_RX) - if(transmitting) - pai.can_transmit = !pai.can_transmit - else //receiving - pai.can_receive = !pai.can_receive - pai.radio.wires.cut(transmit_holder)//wires.cut toggles cut and uncut states - transmit_holder = (transmitting ? pai.can_transmit : pai.can_receive) //recycling can be fun! - to_chat(usr,"You [transmit_holder ? "enable" : "disable"] your pAI's [transmitting ? "outgoing" : "incoming"] radio transmissions!") - to_chat(pai,"Your owner has [transmit_holder ? "enabled" : "disabled"] your [transmitting ? "outgoing" : "incoming"] radio transmissions!") - if(href_list["setlaws"]) - var/newlaws = stripped_multiline_input(usr, "Enter any additional directives you would like your pAI personality to follow. Note that these directives will not override the personality's allegiance to its imprinted master. Conflicting directives will be ignored.", "pAI Directive Configuration", pai.laws.supplied[1], MAX_MESSAGE_LEN) - if(newlaws && pai) - pai.add_supplied_law(0,newlaws) - if(href_list["toggle_holo"]) - if(pai.canholo) - to_chat(pai, "Your owner has disabled your holomatrix projectors!") - pai.canholo = FALSE - to_chat(usr, "You disable your pAI's holomatrix!") - else - to_chat(pai, "Your owner has enabled your holomatrix projectors!") - pai.canholo = TRUE - to_chat(usr, "You enable your pAI's holomatrix!") - - attack_self(usr) - -// WIRE_SIGNAL = 1 -// WIRE_RECEIVE = 2 -// WIRE_TRANSMIT = 4 - -/obj/item/paicard/proc/setPersonality(mob/living/silicon/pai/personality) - src.pai = personality - src.add_overlay("pai-null") - - playsound(loc, 'sound/effects/pai_boot.ogg', 50, TRUE, -1) - audible_message("\The [src] plays a cheerful startup noise!") - -/obj/item/paicard/proc/setEmotion(emotion) - if(pai) - src.cut_overlays() - switch(emotion) - if(1) - src.add_overlay("pai-happy") - if(2) - src.add_overlay("pai-cat") - if(3) - src.add_overlay("pai-extremely-happy") - if(4) - src.add_overlay("pai-face") - if(5) - src.add_overlay("pai-laugh") - if(6) - src.add_overlay("pai-off") - if(7) - src.add_overlay("pai-sad") - if(8) - src.add_overlay("pai-angry") - if(9) - src.add_overlay("pai-what") - if(10) - src.add_overlay("pai-null") - if(11) - src.add_overlay("pai-sunglasses") - -/obj/item/paicard/proc/alertUpdate() - audible_message("[src] flashes a message across its screen, \"Additional personalities available for download.\"", "[src] vibrates with an alert.") - -/obj/item/paicard/emp_act(severity) - . = ..() - if (. & EMP_PROTECT_SELF) - return - if(pai && !pai.holoform) - pai.emp_act(severity) - +/obj/item/paicard + name = "personal AI device" + icon = 'icons/obj/aicards.dmi' + icon_state = "pai" + item_state = "electronic" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + w_class = WEIGHT_CLASS_SMALL + slot_flags = ITEM_SLOT_BELT + var/mob/living/silicon/pai/pai + resistance_flags = FIRE_PROOF | ACID_PROOF | INDESTRUCTIBLE + +/obj/item/paicard/suicide_act(mob/living/user) + user.visible_message("[user] is staring sadly at [src]! [user.p_they()] can't keep living without real human intimacy!") + return OXYLOSS + +/obj/item/paicard/Initialize() + SSpai.pai_card_list += src + add_overlay("pai-off") + return ..() + +/obj/item/paicard/Destroy() + //Will stop people throwing friend pAIs into the singularity so they can respawn + SSpai.pai_card_list -= src + if (!QDELETED(pai)) + QDEL_NULL(pai) + return ..() + +/obj/item/paicard/attack_self(mob/user) + if (!in_range(src, user)) + return + user.set_machine(src) + var/dat = "Personal AI Device
                        " + if(pai) + if(!pai.master_dna || !pai.master) + dat += "Imprint Master DNA
                        " + dat += "Installed Personality: [pai.name]
                        " + dat += "Prime directive:
                        [pai.laws.zeroth]
                        " + for(var/slaws in pai.laws.supplied) + dat += "Additional directives:
                        [slaws]
                        " + dat += "Configure Directives
                        " + dat += "
                        " + dat += "

                        Device Settings


                        " + if(pai.radio) + dat += "Radio Uplink
                        " + dat += "Transmit: \[[pai.can_transmit? "Disable" : "Enable"] Radio Transmission\]
                        " + dat += "Receive: \[[pai.can_receive? "Disable" : "Enable"] Radio Reception\]
                        " + else + dat += "Radio Uplink
                        " + dat += "Radio firmware not loaded. Please install a pAI personality to load firmware.
                        " + if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(H.real_name == pai.master || H.dna.unique_enzymes == pai.master_dna) + dat += "\[[pai.canholo? "Disable" : "Enable"] holomatrix projectors\]
                        " + dat += "\[Reset speech synthesis module\]
                        " + dat += "\[Wipe current pAI personality\]
                        " + else + dat += "No personality installed.
                        " + dat += "Searching for a personality... Press view available personalities to notify potential candidates." + dat += "\[View available personalities\]
                        " + user << browse(dat, "window=paicard") + onclose(user, "paicard") + return + +/obj/item/paicard/Topic(href, href_list) + + if(!usr || usr.stat) + return + + if(href_list["request"]) + SSpai.findPAI(src, usr) + + if(pai) + if(!(loc == usr)) + return + if(href_list["setdna"]) + if(pai.master_dna) + return + if(!iscarbon(usr)) + to_chat(usr, "You don't have any DNA, or your DNA is incompatible with this device!") + else + var/mob/living/carbon/M = usr + pai.master = M.real_name + pai.master_dna = M.dna.unique_enzymes + to_chat(pai, "You have been bound to a new master.") + pai.emittersemicd = FALSE + if(href_list["wipe"]) + var/confirm = input("Are you CERTAIN you wish to delete the current personality? This action cannot be undone.", "Personality Wipe") in list("Yes", "No") + if(confirm == "Yes") + if(pai) + to_chat(pai, "You feel yourself slipping away from reality.") + to_chat(pai, "Byte by byte you lose your sense of self.") + to_chat(pai, "Your mental faculties leave you.") + to_chat(pai, "oblivion... ") + qdel(pai) + if(href_list["fix_speech"]) + pai.stuttering = 0 + pai.slurring = 0 + pai.derpspeech = 0 + if(href_list["toggle_transmit"] || href_list["toggle_receive"]) + var/transmitting = href_list["toggle_transmit"] //it can't be both so if we know it's not transmitting it must be receiving. + var/transmit_holder = (transmitting ? WIRE_TX : WIRE_RX) + if(transmitting) + pai.can_transmit = !pai.can_transmit + else //receiving + pai.can_receive = !pai.can_receive + pai.radio.wires.cut(transmit_holder)//wires.cut toggles cut and uncut states + transmit_holder = (transmitting ? pai.can_transmit : pai.can_receive) //recycling can be fun! + to_chat(usr,"You [transmit_holder ? "enable" : "disable"] your pAI's [transmitting ? "outgoing" : "incoming"] radio transmissions!") + to_chat(pai,"Your owner has [transmit_holder ? "enabled" : "disabled"] your [transmitting ? "outgoing" : "incoming"] radio transmissions!") + if(href_list["setlaws"]) + var/newlaws = stripped_multiline_input(usr, "Enter any additional directives you would like your pAI personality to follow. Note that these directives will not override the personality's allegiance to its imprinted master. Conflicting directives will be ignored.", "pAI Directive Configuration", pai.laws.supplied[1], MAX_MESSAGE_LEN) + if(newlaws && pai) + pai.add_supplied_law(0,newlaws) + if(href_list["toggle_holo"]) + if(pai.canholo) + to_chat(pai, "Your owner has disabled your holomatrix projectors!") + pai.canholo = FALSE + to_chat(usr, "You disable your pAI's holomatrix!") + else + to_chat(pai, "Your owner has enabled your holomatrix projectors!") + pai.canholo = TRUE + to_chat(usr, "You enable your pAI's holomatrix!") + + attack_self(usr) + +// WIRE_SIGNAL = 1 +// WIRE_RECEIVE = 2 +// WIRE_TRANSMIT = 4 + +/obj/item/paicard/proc/setPersonality(mob/living/silicon/pai/personality) + src.pai = personality + src.add_overlay("pai-null") + + playsound(loc, 'sound/effects/pai_boot.ogg', 50, TRUE, -1) + audible_message("\The [src] plays a cheerful startup noise!") + +/obj/item/paicard/proc/setEmotion(emotion) + if(pai) + src.cut_overlays() + switch(emotion) + if(1) + src.add_overlay("pai-happy") + if(2) + src.add_overlay("pai-cat") + if(3) + src.add_overlay("pai-extremely-happy") + if(4) + src.add_overlay("pai-face") + if(5) + src.add_overlay("pai-laugh") + if(6) + src.add_overlay("pai-off") + if(7) + src.add_overlay("pai-sad") + if(8) + src.add_overlay("pai-angry") + if(9) + src.add_overlay("pai-what") + if(10) + src.add_overlay("pai-null") + if(11) + src.add_overlay("pai-sunglasses") + +/obj/item/paicard/proc/alertUpdate() + audible_message("[src] flashes a message across its screen, \"Additional personalities available for download.\"", "[src] vibrates with an alert.") + +/obj/item/paicard/emp_act(severity) + . = ..() + if (. & EMP_PROTECT_SELF) + return + if(pai && !pai.holoform) + pai.emp_act(severity) + diff --git a/code/game/objects/items/devices/portable_chem_mixer.dm b/code/game/objects/items/devices/portable_chem_mixer.dm new file mode 100644 index 000000000000..ed827e024a06 --- /dev/null +++ b/code/game/objects/items/devices/portable_chem_mixer.dm @@ -0,0 +1,215 @@ +/obj/item/storage/portable_chem_mixer + name = "Portable Chemical Mixer" + desc = "A portable device that dispenses and mixes chemicals. Can be upgraded to hold more beakers by inserting a vortex anomaly core. All necessary reagents need to be supplied with beakers. A label indicates that a screwdriver is required to open it for refills. This device can be worn on a belt. The letters 'S&T' are imprinted on the side." + icon = 'icons/obj/chemical.dmi' + icon_state = "portablechemicalmixer_open" + w_class = WEIGHT_CLASS_HUGE + slot_flags = ITEM_SLOT_BELT + equip_sound = 'sound/items/equip/toolbelt_equip.ogg' + custom_price = 2000 + custom_premium_price = 2000 + + var/obj/item/reagent_containers/beaker = null ///Creating an empty slot for a beaker that can be added to dispense into + var/amount = 30 ///The amount of reagent that is to be dispensed currently + + var/list/dispensable_reagents = list() ///List in which all currently dispensable reagents go + +/obj/item/storage/portable_chem_mixer/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_combined_w_class = 200 + STR.max_items = 10 + STR.insert_preposition = "in" + STR.set_holdable(list( + /obj/item/reagent_containers/glass/beaker, + )) + +/obj/item/storage/portable_chem_mixer/Destroy() + QDEL_NULL(beaker) + return ..() + +/obj/item/storage/portable_chem_mixer/ex_act(severity, target) + if(severity < 3) + ..() + +/obj/item/storage/portable_chem_mixer/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/assembly/signaler/anomaly/vortex)) + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = STR.max_items + 40 + QDEL_NULL(I) + to_chat(user, "You insert the vortex anomaly core, and the storage space inside [src] seems to grow much larger!") + return + var/locked = SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED) + if (I.tool_behaviour == TOOL_SCREWDRIVER) + SEND_SIGNAL(src, COMSIG_TRY_STORAGE_SET_LOCKSTATE, !locked) + if (!locked) + update_contents() + if (locked) + replace_beaker(user) + update_icon() + I.play_tool_sound(src, 50) + return + + else if (istype(I, /obj/item/reagent_containers) && !(I.item_flags & ABSTRACT) && I.is_open_container() && locked) + var/obj/item/reagent_containers/B = I + . = TRUE //no afterattack + if(!user.transferItemToLoc(B, src)) + return + replace_beaker(user, B) + update_icon() + updateUsrDialog() + return + + return ..() + +/** + * Updates the contents of the portable chemical mixer + * + * A list of dispensable reagents is created by iterating through each source beaker in the portable chemical beaker and reading its contents + */ +/obj/item/storage/portable_chem_mixer/proc/update_contents() + dispensable_reagents.Cut() + + for (var/obj/item/reagent_containers/glass/beaker/B in contents) + var/key = B.reagents.get_master_reagent_id() + if (!(key in dispensable_reagents)) + dispensable_reagents[key] = list() + dispensable_reagents[key]["reagents"] = list() + dispensable_reagents[key]["reagents"] += B.reagents + + return + +/obj/item/storage/portable_chem_mixer/update_icon_state() + var/locked = SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED) + if (!locked) + icon_state = "portablechemicalmixer_open" + else if (beaker) + icon_state = "portablechemicalmixer_full" + else + icon_state = "portablechemicalmixer_empty" + + +/obj/item/storage/portable_chem_mixer/AltClick(mob/living/user) + var/locked = SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED) + if (!locked) + return ..() + if(!can_interact(user) || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + replace_beaker(user) + update_icon() + +/** + * Replaces the beaker of the portable chemical mixer with another beaker, or simply adds the new beaker if none is in currently + * + * Checks if a valid user and a valid new beaker exist and attempts to replace the current beaker in the portable chemical mixer with the one in hand. Simply places the new beaker in if no beaker is currently loaded + * Arguments: + * * mob/living/user - The user who is trying to exchange beakers + * * obj/item/reagent_containers/new_beaker - The new beaker that the user wants to put into the device + */ +/obj/item/storage/portable_chem_mixer/proc/replace_beaker(mob/living/user, obj/item/reagent_containers/new_beaker) + if(!user) + return FALSE + if(beaker) + user.put_in_hands(beaker) + beaker = null + if(new_beaker) + beaker = new_beaker + return TRUE + +/obj/item/storage/portable_chem_mixer/attack_hand(mob/user) + if(loc == user) + var/locked = SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED) + if (locked) + ui_interact(user) + return + return ..() + +/obj/item/storage/portable_chem_mixer/attack_self(mob/user) + if(loc == user) + var/locked = SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED) + if (locked) + ui_interact(user) + return + else + to_chat(user, "The portable chemical mixer is currently open and its contents can be accessed.") + return + return + +/obj/item/storage/portable_chem_mixer/MouseDrop(obj/over_object) + . = ..() + if(ismob(loc)) + var/mob/M = loc + if(!M.incapacitated() && istype(over_object, /obj/screen/inventory/hand)) + var/obj/screen/inventory/hand/H = over_object + M.putItemFromInventoryInHandIfPossible(src, H.held_index) + +/obj/item/storage/portable_chem_mixer/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "PortableChemMixer", name) + if(user.hallucinating()) + // to not ruin the immersion by constantly changing the fake chemicals + ui.set_autoupdate(FALSE) + ui.open() + +/obj/item/storage/portable_chem_mixer/ui_data(mob/user) + var/list/data = list() + data["amount"] = amount + data["isBeakerLoaded"] = beaker ? 1 : 0 + data["beakerCurrentVolume"] = beaker ? beaker.reagents.total_volume : null + data["beakerMaxVolume"] = beaker ? beaker.volume : null + data["beakerTransferAmounts"] = beaker ? beaker.possible_transfer_amounts : null + var/chemicals[0] + var/is_hallucinating = user.hallucinating() + if(user.hallucinating()) + is_hallucinating = TRUE + for(var/re in dispensable_reagents) + var/value = dispensable_reagents[re] + var/datum/reagent/temp = GLOB.chemical_reagents_list[re] + if(temp) + var/chemname = temp.name + var/total_volume = 0 + for (var/datum/reagents/rs in value["reagents"]) + total_volume += rs.total_volume + if(is_hallucinating && prob(5)) + chemname = "[pick_list_replacements("hallucination.json", "chemicals")]" + chemicals.Add(list(list("title" = chemname, "id" = ckey(temp.name), "volume" = total_volume ))) + data["chemicals"] = chemicals + var/beakerContents[0] + if(beaker) + for(var/datum/reagent/R in beaker.reagents.reagent_list) + beakerContents.Add(list(list("name" = R.name, "id" = ckey(R.name), "volume" = R.volume))) // list in a list because Byond merges the first list... + data["beakerContents"] = beakerContents + return data + +/obj/item/storage/portable_chem_mixer/ui_act(action, params) + if(..()) + return + switch(action) + if("amount") + var/target = text2num(params["target"]) + amount = target + . = TRUE + if("dispense") + var/reagent_name = params["reagent"] + var/datum/reagent/reagent = GLOB.name2reagent[reagent_name] + var/entry = dispensable_reagents[reagent] + if(beaker) + var/datum/reagents/R = beaker.reagents + var/actual = min(amount, 1000, R.maximum_volume - R.total_volume) + // todo: add check if we have enough reagent left + for (var/datum/reagents/source in entry["reagents"]) + var/to_transfer = min(source.total_volume, actual) + source.trans_to(beaker, to_transfer) + actual -= to_transfer + if (actual <= 0) + break + . = TRUE + if("remove") + var/amount = text2num(params["amount"]) + beaker.reagents.remove_all(amount) + . = TRUE + if("eject") + replace_beaker(usr) + update_icon() + . = TRUE diff --git a/code/game/objects/items/devices/powersink.dm b/code/game/objects/items/devices/powersink.dm index e02f79d3170f..8e216c5086f9 100644 --- a/code/game/objects/items/devices/powersink.dm +++ b/code/game/objects/items/devices/powersink.dm @@ -1,160 +1,160 @@ -// Powersink - used to drain station power - -/obj/item/powersink - name = "power sink" - desc = "A nulling power sink which drains energy from electrical systems." - icon = 'icons/obj/device.dmi' - icon_state = "powersink0" - item_state = "electronic" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - w_class = WEIGHT_CLASS_BULKY - flags_1 = CONDUCT_1 - throwforce = 5 - throw_speed = 1 - throw_range = 2 - custom_materials = list(/datum/material/iron=750) - var/drain_rate = 2000000 // amount of power to drain per tick - var/power_drained = 0 // has drained this much power - var/max_power = 6e8 // maximum power that can be drained before exploding - var/mode = 0 // 0 = off, 1=clamped (off), 2=operating - var/admins_warned = FALSE // stop spam, only warn the admins once that we are about to boom - - var/const/DISCONNECTED = 0 - var/const/CLAMPED_OFF = 1 - var/const/OPERATING = 2 - - var/obj/structure/cable/attached // the attached cable - -/obj/item/powersink/update_icon_state() - icon_state = "powersink[mode == OPERATING]" - -/obj/item/powersink/proc/set_mode(value) - if(value == mode) - return - switch(value) - if(DISCONNECTED) - attached = null - if(mode == OPERATING) - STOP_PROCESSING(SSobj, src) - anchored = FALSE - density = FALSE - - if(CLAMPED_OFF) - if(!attached) - return - if(mode == OPERATING) - STOP_PROCESSING(SSobj, src) - anchored = TRUE - density = TRUE - - if(OPERATING) - if(!attached) - return - START_PROCESSING(SSobj, src) - anchored = TRUE - density = TRUE - - mode = value - update_icon() - set_light(0) - -/obj/item/powersink/attackby(obj/item/I, mob/user, params) - if(I.tool_behaviour == TOOL_WRENCH) - if(mode == DISCONNECTED) - var/turf/T = loc - if(isturf(T) && !T.intact) - attached = locate() in T - if(!attached) - to_chat(user, "\The [src] must be placed over an exposed, powered cable node!") - else - set_mode(CLAMPED_OFF) - user.visible_message( \ - "[user] attaches \the [src] to the cable.", \ - "You bolt \the [src] into the floor and connect it to the cable.", - "You hear some wires being connected to something.") - else - to_chat(user, "\The [src] must be placed over an exposed, powered cable node!") - else - set_mode(DISCONNECTED) - user.visible_message( \ - "[user] detaches \the [src] from the cable.", \ - "You unbolt \the [src] from the floor and detach it from the cable.", - "You hear some wires being disconnected from something.") - - else if(I.tool_behaviour == TOOL_SCREWDRIVER) - user.visible_message( \ - "[user] messes with \the [src] for a bit.", \ - "You can't fit the screwdriver into \the [src]'s bolts! Try using a wrench.") - else - return ..() - -/obj/item/powersink/attack_paw() - return - -/obj/item/powersink/attack_ai() - return - -/obj/item/powersink/attack_hand(mob/user) - . = ..() - if(.) - return - switch(mode) - if(DISCONNECTED) - ..() - - if(CLAMPED_OFF) - user.visible_message( \ - "[user] activates \the [src]!", \ - "You activate \the [src].", - "You hear a click.") - message_admins("Power sink activated by [ADMIN_LOOKUPFLW(user)] at [ADMIN_VERBOSEJMP(src)]") - log_game("Power sink activated by [key_name(user)] at [AREACOORD(src)]") - set_mode(OPERATING) - - if(OPERATING) - user.visible_message( \ - "[user] deactivates \the [src]!", \ - "You deactivate \the [src].", - "You hear a click.") - set_mode(CLAMPED_OFF) - -/obj/item/powersink/process() - if(!attached) - set_mode(DISCONNECTED) - return - - var/datum/powernet/PN = attached.powernet - if(PN) - set_light(5) - - // found a powernet, so drain up to max power from it - - var/drained = min ( drain_rate, attached.newavail() ) - attached.add_delayedload(drained) - power_drained += drained - - // if tried to drain more than available on powernet - // now look for APCs and drain their cells - if(drained < drain_rate) - for(var/obj/machinery/power/terminal/T in PN.nodes) - if(istype(T.master, /obj/machinery/power/apc)) - var/obj/machinery/power/apc/A = T.master - if(A.operating && A.cell) - A.cell.charge = max(0, A.cell.charge - 50) - power_drained += 50 - if(A.charging == 2) // If the cell was full - A.charging = 1 // It's no longer full - if(drained >= drain_rate) - break - - if(power_drained > max_power * 0.98) - if (!admins_warned) - admins_warned = TRUE - message_admins("Power sink at ([x],[y],[z] - JMP) is 95% full. Explosion imminent.") - playsound(src, 'sound/effects/screech.ogg', 100, TRUE, TRUE) - - if(power_drained >= max_power) - STOP_PROCESSING(SSobj, src) - explosion(src.loc, 4,8,16,32) - qdel(src) +// Powersink - used to drain station power + +/obj/item/powersink + name = "power sink" + desc = "A nulling power sink which drains energy from electrical systems." + icon = 'icons/obj/device.dmi' + icon_state = "powersink0" + item_state = "electronic" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + w_class = WEIGHT_CLASS_BULKY + flags_1 = CONDUCT_1 + throwforce = 5 + throw_speed = 1 + throw_range = 2 + custom_materials = list(/datum/material/iron=750) + var/drain_rate = 2000000 // amount of power to drain per tick + var/power_drained = 0 // has drained this much power + var/max_power = 6e8 // maximum power that can be drained before exploding + var/mode = 0 // 0 = off, 1=clamped (off), 2=operating + var/admins_warned = FALSE // stop spam, only warn the admins once that we are about to boom + + var/const/DISCONNECTED = 0 + var/const/CLAMPED_OFF = 1 + var/const/OPERATING = 2 + + var/obj/structure/cable/attached // the attached cable + +/obj/item/powersink/update_icon_state() + icon_state = "powersink[mode == OPERATING]" + +/obj/item/powersink/proc/set_mode(value) + if(value == mode) + return + switch(value) + if(DISCONNECTED) + attached = null + if(mode == OPERATING) + STOP_PROCESSING(SSobj, src) + anchored = FALSE + density = FALSE + + if(CLAMPED_OFF) + if(!attached) + return + if(mode == OPERATING) + STOP_PROCESSING(SSobj, src) + anchored = TRUE + density = TRUE + + if(OPERATING) + if(!attached) + return + START_PROCESSING(SSobj, src) + anchored = TRUE + density = TRUE + + mode = value + update_icon() + set_light(0) + +/obj/item/powersink/attackby(obj/item/I, mob/user, params) + if(I.tool_behaviour == TOOL_WRENCH) + if(mode == DISCONNECTED) + var/turf/T = loc + if(isturf(T) && !T.intact) + attached = locate() in T + if(!attached) + to_chat(user, "\The [src] must be placed over an exposed, powered cable node!") + else + set_mode(CLAMPED_OFF) + user.visible_message( \ + "[user] attaches \the [src] to the cable.", \ + "You bolt \the [src] into the floor and connect it to the cable.", + "You hear some wires being connected to something.") + else + to_chat(user, "\The [src] must be placed over an exposed, powered cable node!") + else + set_mode(DISCONNECTED) + user.visible_message( \ + "[user] detaches \the [src] from the cable.", \ + "You unbolt \the [src] from the floor and detach it from the cable.", + "You hear some wires being disconnected from something.") + + else if(I.tool_behaviour == TOOL_SCREWDRIVER) + user.visible_message( \ + "[user] messes with \the [src] for a bit.", \ + "You can't fit the screwdriver into \the [src]'s bolts! Try using a wrench.") + else + return ..() + +/obj/item/powersink/attack_paw() + return + +/obj/item/powersink/attack_ai() + return + +/obj/item/powersink/attack_hand(mob/user) + . = ..() + if(.) + return + switch(mode) + if(DISCONNECTED) + ..() + + if(CLAMPED_OFF) + user.visible_message( \ + "[user] activates \the [src]!", \ + "You activate \the [src].", + "You hear a click.") + message_admins("Power sink activated by [ADMIN_LOOKUPFLW(user)] at [ADMIN_VERBOSEJMP(src)]") + log_game("Power sink activated by [key_name(user)] at [AREACOORD(src)]") + set_mode(OPERATING) + + if(OPERATING) + user.visible_message( \ + "[user] deactivates \the [src]!", \ + "You deactivate \the [src].", + "You hear a click.") + set_mode(CLAMPED_OFF) + +/obj/item/powersink/process() + if(!attached) + set_mode(DISCONNECTED) + return + + var/datum/powernet/PN = attached.powernet + if(PN) + set_light(5) + + // found a powernet, so drain up to max power from it + + var/drained = min ( drain_rate, attached.newavail() ) + attached.add_delayedload(drained) + power_drained += drained + + // if tried to drain more than available on powernet + // now look for APCs and drain their cells + if(drained < drain_rate) + for(var/obj/machinery/power/terminal/T in PN.nodes) + if(istype(T.master, /obj/machinery/power/apc)) + var/obj/machinery/power/apc/A = T.master + if(A.operating && A.cell) + A.cell.charge = max(0, A.cell.charge - 50) + power_drained += 50 + if(A.charging == 2) // If the cell was full + A.charging = 1 // It's no longer full + if(drained >= drain_rate) + break + + if(power_drained > max_power * 0.98) + if (!admins_warned) + admins_warned = TRUE + message_admins("Power sink at ([x],[y],[z] - JMP) is 95% full. Explosion imminent.") + playsound(src, 'sound/effects/screech.ogg', 100, TRUE, TRUE) + + if(power_drained >= max_power) + STOP_PROCESSING(SSobj, src) + explosion(src.loc, 4,8,16,32) + qdel(src) diff --git a/code/game/objects/items/devices/radio/electropack.dm b/code/game/objects/items/devices/radio/electropack.dm index f574aaea16bf..24d7000b998d 100644 --- a/code/game/objects/items/devices/radio/electropack.dm +++ b/code/game/objects/items/devices/radio/electropack.dm @@ -1,131 +1,132 @@ -/obj/item/electropack - name = "electropack" - desc = "Dance my monkeys! DANCE!!!" - icon = 'icons/obj/radio.dmi' - icon_state = "electropack0" - item_state = "electropack" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BACK - w_class = WEIGHT_CLASS_HUGE - custom_materials = list(/datum/material/iron=10000, /datum/material/glass=2500) - var/ui_x = 260 - var/ui_y = 137 - var/on = TRUE - var/code = 2 - var/frequency = FREQ_ELECTROPACK - var/shock_cooldown = FALSE - -/obj/item/electropack/Initialize() - . = ..() - set_frequency(frequency) - -/obj/item/electropack/Destroy() - SSradio.remove_object(src, frequency) - return ..() - -/obj/item/electropack/suicide_act(mob/user) - user.visible_message("[user] hooks [user.p_them()]self to the electropack and spams the trigger! It looks like [user.p_theyre()] trying to commit suicide!") - return (FIRELOSS) - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/item/electropack/attack_hand(mob/user) - if(iscarbon(user)) - var/mob/living/carbon/C = user - if(src == C.back) - to_chat(user, "You need help taking this off!") - return - return ..() - -/obj/item/electropack/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/clothing/head/helmet)) - var/obj/item/assembly/shock_kit/A = new /obj/item/assembly/shock_kit(user) - A.icon = 'icons/obj/assemblies.dmi' - - if(!user.transferItemToLoc(W, A)) - to_chat(user, "[W] is stuck to your hand, you cannot attach it to [src]!") - return - W.master = A - A.part1 = W - - user.transferItemToLoc(src, A, TRUE) - master = A - A.part2 = src - - user.put_in_hands(A) - A.add_fingerprint(user) - else - return ..() - -/obj/item/electropack/receive_signal(datum/signal/signal) - if(!signal || signal.data["code"] != code) - return - - if(isliving(loc) && on) - if(shock_cooldown) - return - shock_cooldown = TRUE - addtimer(VARSET_CALLBACK(src, shock_cooldown, FALSE), 100) - var/mob/living/L = loc - step(L, pick(GLOB.cardinals)) - - to_chat(L, "You feel a sharp shock!") - var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread - s.set_up(3, 1, L) - s.start() - - L.Paralyze(100) - - if(master) - master.receive_signal() - -/obj/item/electropack/proc/set_frequency(new_frequency) - SSradio.remove_object(src, frequency) - frequency = new_frequency - SSradio.add_object(src, frequency, RADIO_SIGNALER) - -/obj/item/electropack/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.hands_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "Electropack", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/item/electropack/ui_data(mob/user) - var/list/data = list() - data["power"] = on - data["frequency"] = frequency - data["code"] = code - data["minFrequency"] = MIN_FREE_FREQ - data["maxFrequency"] = MAX_FREE_FREQ - return data - -/obj/item/electropack/ui_act(action, params) - if(..()) - return - - switch(action) - if("power") - on = !on - icon_state = "electropack[on]" - . = TRUE - if("freq") - var/value = unformat_frequency(params["freq"]) - if(value) - frequency = sanitize_frequency(value, TRUE) - set_frequency(frequency) - . = TRUE - if("code") - var/value = text2num(params["code"]) - if(value) - value = round(value) - code = clamp(value, 1, 100) - . = TRUE - if("reset") - if(params["reset"] == "freq") - frequency = initial(frequency) - . = TRUE - else if(params["reset"] == "code") - code = initial(code) - . = TRUE +/obj/item/electropack + name = "electropack" + desc = "Dance my monkeys! DANCE!!!" + icon = 'icons/obj/radio.dmi' + icon_state = "electropack0" + item_state = "electropack" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BACK + w_class = WEIGHT_CLASS_HUGE + custom_materials = list(/datum/material/iron=10000, /datum/material/glass=2500) + + var/on = TRUE + var/code = 2 + var/frequency = FREQ_ELECTROPACK + var/shock_cooldown = FALSE + +/obj/item/electropack/Initialize() + . = ..() + set_frequency(frequency) + +/obj/item/electropack/Destroy() + SSradio.remove_object(src, frequency) + return ..() + +/obj/item/electropack/suicide_act(mob/user) + user.visible_message("[user] hooks [user.p_them()]self to the electropack and spams the trigger! It looks like [user.p_theyre()] trying to commit suicide!") + return (FIRELOSS) + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/electropack/attack_hand(mob/user) + if(iscarbon(user)) + var/mob/living/carbon/C = user + if(src == C.back) + to_chat(user, "You need help taking this off!") + return + return ..() + +/obj/item/electropack/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/clothing/head/helmet)) + var/obj/item/assembly/shock_kit/A = new /obj/item/assembly/shock_kit(user) + A.icon = 'icons/obj/assemblies.dmi' + + if(!user.transferItemToLoc(W, A)) + to_chat(user, "[W] is stuck to your hand, you cannot attach it to [src]!") + return + W.master = A + A.part1 = W + + user.transferItemToLoc(src, A, TRUE) + master = A + A.part2 = src + + user.put_in_hands(A) + A.add_fingerprint(user) + else + return ..() + +/obj/item/electropack/receive_signal(datum/signal/signal) + if(!signal || signal.data["code"] != code) + return + + if(isliving(loc) && on) + if(shock_cooldown) + return + shock_cooldown = TRUE + addtimer(VARSET_CALLBACK(src, shock_cooldown, FALSE), 100) + var/mob/living/L = loc + step(L, pick(GLOB.cardinals)) + + to_chat(L, "You feel a sharp shock!") + var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread + s.set_up(3, 1, L) + s.start() + + L.Paralyze(100) + + if(master) + master.receive_signal() + +/obj/item/electropack/proc/set_frequency(new_frequency) + SSradio.remove_object(src, frequency) + frequency = new_frequency + SSradio.add_object(src, frequency, RADIO_SIGNALER) + +/obj/item/electropack/ui_state(mob/user) + return GLOB.hands_state + +/obj/item/electropack/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "Electropack", name) + ui.open() + +/obj/item/electropack/ui_data(mob/user) + var/list/data = list() + data["power"] = on + data["frequency"] = frequency + data["code"] = code + data["minFrequency"] = MIN_FREE_FREQ + data["maxFrequency"] = MAX_FREE_FREQ + return data + +/obj/item/electropack/ui_act(action, params) + if(..()) + return + + switch(action) + if("power") + on = !on + icon_state = "electropack[on]" + . = TRUE + if("freq") + var/value = unformat_frequency(params["freq"]) + if(value) + frequency = sanitize_frequency(value, TRUE) + set_frequency(frequency) + . = TRUE + if("code") + var/value = text2num(params["code"]) + if(value) + value = round(value) + code = clamp(value, 1, 100) + . = TRUE + if("reset") + if(params["reset"] == "freq") + frequency = initial(frequency) + . = TRUE + else if(params["reset"] == "code") + code = initial(code) + . = TRUE diff --git a/code/game/objects/items/devices/radio/encryptionkey.dm b/code/game/objects/items/devices/radio/encryptionkey.dm index adcf20e0b71d..f2adf21244f1 100644 --- a/code/game/objects/items/devices/radio/encryptionkey.dm +++ b/code/game/objects/items/devices/radio/encryptionkey.dm @@ -1,137 +1,137 @@ -/obj/item/encryptionkey - name = "standard encryption key" - desc = "An encryption key for a radio headset." - icon = 'icons/obj/radio.dmi' - icon_state = "cypherkey" - w_class = WEIGHT_CLASS_TINY - var/translate_binary = FALSE - var/syndie = FALSE - var/independent = FALSE - var/list/channels = list() - -/obj/item/encryptionkey/Initialize() - . = ..() - if(!channels.len) - desc = "An encryption key for a radio headset. Has no special codes in it. You should probably tell a coder!" - -/obj/item/encryptionkey/examine(mob/user) - . = ..() - if(LAZYLEN(channels)) - var/list/examine_text_list = list() - for(var/i in channels) - examine_text_list += "[GLOB.channel_tokens[i]] - [lowertext(i)]" - - . += "It can access the following channels; [jointext(examine_text_list, ", ")]." - -/obj/item/encryptionkey/syndicate - name = "syndicate encryption key" - icon_state = "syn_cypherkey" - channels = list(RADIO_CHANNEL_SYNDICATE = 1) - syndie = TRUE//Signifies that it de-crypts Syndicate transmissions - -/obj/item/encryptionkey/binary - name = "binary translator key" - icon_state = "bin_cypherkey" - translate_binary = TRUE - -/obj/item/encryptionkey/headset_sec - name = "security radio encryption key" - icon_state = "sec_cypherkey" - channels = list(RADIO_CHANNEL_SECURITY = 1) - -/obj/item/encryptionkey/headset_eng - name = "engineering radio encryption key" - icon_state = "eng_cypherkey" - channels = list(RADIO_CHANNEL_ENGINEERING = 1) - -/obj/item/encryptionkey/headset_rob - name = "robotics radio encryption key" - icon_state = "rob_cypherkey" - channels = list(RADIO_CHANNEL_SCIENCE = 1, RADIO_CHANNEL_ENGINEERING = 1) - -/obj/item/encryptionkey/headset_med - name = "medical radio encryption key" - icon_state = "med_cypherkey" - channels = list(RADIO_CHANNEL_MEDICAL = 1) - -/obj/item/encryptionkey/headset_sci - name = "science radio encryption key" - icon_state = "sci_cypherkey" - channels = list(RADIO_CHANNEL_SCIENCE = 1) - -/obj/item/encryptionkey/headset_medsci - name = "medical research radio encryption key" - icon_state = "medsci_cypherkey" - channels = list(RADIO_CHANNEL_SCIENCE = 1, RADIO_CHANNEL_MEDICAL = 1) - -/obj/item/encryptionkey/headset_srvsec - name = "law and order radio encryption key" - icon_state = "srvsec_cypherkey" - channels = list(RADIO_CHANNEL_SERVICE = 1, RADIO_CHANNEL_SECURITY = 1) - -/obj/item/encryptionkey/headset_srvmed - name = "psychology radio encryption key" - icon_state = "srvmed_cypherkey" - channels = list(RADIO_CHANNEL_MEDICAL = 1, RADIO_CHANNEL_SERVICE = 1) - -/obj/item/encryptionkey/headset_com - name = "command radio encryption key" - icon_state = "com_cypherkey" - channels = list(RADIO_CHANNEL_COMMAND = 1) - -/obj/item/encryptionkey/heads/captain - name = "\proper the captain's encryption key" - icon_state = "cap_cypherkey" - channels = list(RADIO_CHANNEL_COMMAND = 1, RADIO_CHANNEL_SECURITY = 1, RADIO_CHANNEL_ENGINEERING = 0, RADIO_CHANNEL_SCIENCE = 0, RADIO_CHANNEL_MEDICAL = 0, RADIO_CHANNEL_SUPPLY = 0, RADIO_CHANNEL_SERVICE = 0) - -/obj/item/encryptionkey/heads/rd - name = "\proper the research director's encryption key" - icon_state = "rd_cypherkey" - channels = list(RADIO_CHANNEL_SCIENCE = 1, RADIO_CHANNEL_COMMAND = 1) - -/obj/item/encryptionkey/heads/hos - name = "\proper the head of security's encryption key" - icon_state = "hos_cypherkey" - channels = list(RADIO_CHANNEL_SECURITY = 1, RADIO_CHANNEL_COMMAND = 1) - -/obj/item/encryptionkey/heads/ce - name = "\proper the chief engineer's encryption key" - icon_state = "ce_cypherkey" - channels = list(RADIO_CHANNEL_ENGINEERING = 1, RADIO_CHANNEL_COMMAND = 1) - -/obj/item/encryptionkey/heads/cmo - name = "\proper the chief medical officer's encryption key" - icon_state = "cmo_cypherkey" - channels = list(RADIO_CHANNEL_MEDICAL = 1, RADIO_CHANNEL_COMMAND = 1) - -/obj/item/encryptionkey/heads/head_of_personnel - name = "\proper the head of personnel's encryption key" - icon_state = "hop_cypherkey" - channels = list(RADIO_CHANNEL_SUPPLY = 1, RADIO_CHANNEL_SERVICE = 1, RADIO_CHANNEL_COMMAND = 1) - -/obj/item/encryptionkey/headset_cargo - name = "supply radio encryption key" - icon_state = "cargo_cypherkey" - channels = list(RADIO_CHANNEL_SUPPLY = 1) - -/obj/item/encryptionkey/headset_mining - name = "mining radio encryption key" - icon_state = "cargo_cypherkey" - channels = list(RADIO_CHANNEL_SUPPLY = 1, RADIO_CHANNEL_SCIENCE = 1) - -/obj/item/encryptionkey/headset_service - name = "service radio encryption key" - icon_state = "srv_cypherkey" - channels = list(RADIO_CHANNEL_SERVICE = 1) - -/obj/item/encryptionkey/headset_cent - name = "\improper CentCom radio encryption key" - icon_state = "cent_cypherkey" - independent = TRUE - channels = list(RADIO_CHANNEL_CENTCOM = 1) - -/obj/item/encryptionkey/ai //ported from NT, this goes 'inside' the AI. - channels = list(RADIO_CHANNEL_COMMAND = 1, RADIO_CHANNEL_SECURITY = 1, RADIO_CHANNEL_ENGINEERING = 1, RADIO_CHANNEL_SCIENCE = 1, RADIO_CHANNEL_MEDICAL = 1, RADIO_CHANNEL_SUPPLY = 1, RADIO_CHANNEL_SERVICE = 1, RADIO_CHANNEL_AI_PRIVATE = 1) - -/obj/item/encryptionkey/secbot - channels = list(RADIO_CHANNEL_AI_PRIVATE = 1, RADIO_CHANNEL_SECURITY = 1) +/obj/item/encryptionkey + name = "standard encryption key" + desc = "An encryption key for a radio headset." + icon = 'icons/obj/radio.dmi' + icon_state = "cypherkey" + w_class = WEIGHT_CLASS_TINY + var/translate_binary = FALSE + var/syndie = FALSE + var/independent = FALSE + var/list/channels = list() + +/obj/item/encryptionkey/Initialize() + . = ..() + if(!channels.len) + desc = "An encryption key for a radio headset. Has no special codes in it. You should probably tell a coder!" + +/obj/item/encryptionkey/examine(mob/user) + . = ..() + if(LAZYLEN(channels)) + var/list/examine_text_list = list() + for(var/i in channels) + examine_text_list += "[GLOB.channel_tokens[i]] - [lowertext(i)]" + + . += "It can access the following channels; [jointext(examine_text_list, ", ")]." + +/obj/item/encryptionkey/syndicate + name = "syndicate encryption key" + icon_state = "syn_cypherkey" + channels = list(RADIO_CHANNEL_SYNDICATE = 1) + syndie = TRUE//Signifies that it de-crypts Syndicate transmissions + +/obj/item/encryptionkey/binary + name = "binary translator key" + icon_state = "bin_cypherkey" + translate_binary = TRUE + +/obj/item/encryptionkey/headset_sec + name = "security radio encryption key" + icon_state = "sec_cypherkey" + channels = list(RADIO_CHANNEL_SECURITY = 1) + +/obj/item/encryptionkey/headset_eng + name = "engineering radio encryption key" + icon_state = "eng_cypherkey" + channels = list(RADIO_CHANNEL_ENGINEERING = 1) + +/obj/item/encryptionkey/headset_rob + name = "robotics radio encryption key" + icon_state = "rob_cypherkey" + channels = list(RADIO_CHANNEL_SCIENCE = 1, RADIO_CHANNEL_ENGINEERING = 1) + +/obj/item/encryptionkey/headset_med + name = "medical radio encryption key" + icon_state = "med_cypherkey" + channels = list(RADIO_CHANNEL_MEDICAL = 1) + +/obj/item/encryptionkey/headset_sci + name = "science radio encryption key" + icon_state = "sci_cypherkey" + channels = list(RADIO_CHANNEL_SCIENCE = 1) + +/obj/item/encryptionkey/headset_medsci + name = "medical research radio encryption key" + icon_state = "medsci_cypherkey" + channels = list(RADIO_CHANNEL_SCIENCE = 1, RADIO_CHANNEL_MEDICAL = 1) + +/obj/item/encryptionkey/headset_srvsec + name = "law and order radio encryption key" + icon_state = "srvsec_cypherkey" + channels = list(RADIO_CHANNEL_SERVICE = 1, RADIO_CHANNEL_SECURITY = 1) + +/obj/item/encryptionkey/headset_srvmed + name = "psychology radio encryption key" + icon_state = "srvmed_cypherkey" + channels = list(RADIO_CHANNEL_MEDICAL = 1, RADIO_CHANNEL_SERVICE = 1) + +/obj/item/encryptionkey/headset_com + name = "command radio encryption key" + icon_state = "com_cypherkey" + channels = list(RADIO_CHANNEL_COMMAND = 1) + +/obj/item/encryptionkey/heads/captain + name = "\proper the captain's encryption key" + icon_state = "cap_cypherkey" + channels = list(RADIO_CHANNEL_COMMAND = 1, RADIO_CHANNEL_SECURITY = 1, RADIO_CHANNEL_ENGINEERING = 0, RADIO_CHANNEL_SCIENCE = 0, RADIO_CHANNEL_MEDICAL = 0, RADIO_CHANNEL_SUPPLY = 0, RADIO_CHANNEL_SERVICE = 0) + +/obj/item/encryptionkey/heads/rd + name = "\proper the research director's encryption key" + icon_state = "rd_cypherkey" + channels = list(RADIO_CHANNEL_SCIENCE = 1, RADIO_CHANNEL_COMMAND = 1) + +/obj/item/encryptionkey/heads/hos + name = "\proper the head of security's encryption key" + icon_state = "hos_cypherkey" + channels = list(RADIO_CHANNEL_SECURITY = 1, RADIO_CHANNEL_COMMAND = 1) + +/obj/item/encryptionkey/heads/ce + name = "\proper the chief engineer's encryption key" + icon_state = "ce_cypherkey" + channels = list(RADIO_CHANNEL_ENGINEERING = 1, RADIO_CHANNEL_COMMAND = 1) + +/obj/item/encryptionkey/heads/cmo + name = "\proper the chief medical officer's encryption key" + icon_state = "cmo_cypherkey" + channels = list(RADIO_CHANNEL_MEDICAL = 1, RADIO_CHANNEL_COMMAND = 1) + +/obj/item/encryptionkey/heads/head_of_personnel + name = "\proper the head of personnel's encryption key" + icon_state = "hop_cypherkey" + channels = list(RADIO_CHANNEL_SUPPLY = 1, RADIO_CHANNEL_SERVICE = 1, RADIO_CHANNEL_COMMAND = 1) + +/obj/item/encryptionkey/headset_cargo + name = "supply radio encryption key" + icon_state = "cargo_cypherkey" + channels = list(RADIO_CHANNEL_SUPPLY = 1) + +/obj/item/encryptionkey/headset_mining + name = "mining radio encryption key" + icon_state = "cargo_cypherkey" + channels = list(RADIO_CHANNEL_SUPPLY = 1, RADIO_CHANNEL_SCIENCE = 1) + +/obj/item/encryptionkey/headset_service + name = "service radio encryption key" + icon_state = "srv_cypherkey" + channels = list(RADIO_CHANNEL_SERVICE = 1) + +/obj/item/encryptionkey/headset_cent + name = "\improper CentCom radio encryption key" + icon_state = "cent_cypherkey" + independent = TRUE + channels = list(RADIO_CHANNEL_CENTCOM = 1) + +/obj/item/encryptionkey/ai //ported from NT, this goes 'inside' the AI. + channels = list(RADIO_CHANNEL_COMMAND = 1, RADIO_CHANNEL_SECURITY = 1, RADIO_CHANNEL_ENGINEERING = 1, RADIO_CHANNEL_SCIENCE = 1, RADIO_CHANNEL_MEDICAL = 1, RADIO_CHANNEL_SUPPLY = 1, RADIO_CHANNEL_SERVICE = 1, RADIO_CHANNEL_AI_PRIVATE = 1) + +/obj/item/encryptionkey/secbot + channels = list(RADIO_CHANNEL_AI_PRIVATE = 1, RADIO_CHANNEL_SECURITY = 1) diff --git a/code/game/objects/items/devices/radio/headset.dm b/code/game/objects/items/devices/radio/headset.dm index 4ca1417512a6..781e621db0e3 100644 --- a/code/game/objects/items/devices/radio/headset.dm +++ b/code/game/objects/items/devices/radio/headset.dm @@ -1,352 +1,352 @@ -// Used for translating channels to tokens on examination -GLOBAL_LIST_INIT(channel_tokens, list( - RADIO_CHANNEL_COMMON = RADIO_KEY_COMMON, - RADIO_CHANNEL_SCIENCE = RADIO_TOKEN_SCIENCE, - RADIO_CHANNEL_COMMAND = RADIO_TOKEN_COMMAND, - RADIO_CHANNEL_MEDICAL = RADIO_TOKEN_MEDICAL, - RADIO_CHANNEL_ENGINEERING = RADIO_TOKEN_ENGINEERING, - RADIO_CHANNEL_SECURITY = RADIO_TOKEN_SECURITY, - RADIO_CHANNEL_CENTCOM = RADIO_TOKEN_CENTCOM, - RADIO_CHANNEL_SYNDICATE = RADIO_TOKEN_SYNDICATE, - RADIO_CHANNEL_SUPPLY = RADIO_TOKEN_SUPPLY, - RADIO_CHANNEL_SERVICE = RADIO_TOKEN_SERVICE, - MODE_BINARY = MODE_TOKEN_BINARY, - RADIO_CHANNEL_AI_PRIVATE = RADIO_TOKEN_AI_PRIVATE -)) - -/obj/item/radio/headset - name = "radio headset" - desc = "An updated, modular intercom that fits over the head. Takes encryption keys." - icon_state = "headset" - item_state = "headset" - custom_materials = list(/datum/material/iron=75) - subspace_transmission = TRUE - canhear_range = 0 // can't hear headsets from very far away - - slot_flags = ITEM_SLOT_EARS - var/obj/item/encryptionkey/keyslot2 = null - dog_fashion = null - -/obj/item/radio/headset/suicide_act(mob/living/carbon/user) - user.visible_message("[user] begins putting \the [src]'s antenna up [user.p_their()] nose! It looks like [user.p_theyre()] trying to give [user.p_them()]self cancer!") - return TOXLOSS - -/obj/item/radio/headset/examine(mob/user) - . = ..() - - if(item_flags & IN_INVENTORY && loc == user) - // construction of frequency description - var/list/avail_chans = list("Use [RADIO_KEY_COMMON] for the currently tuned frequency") - if(translate_binary) - avail_chans += "use [MODE_TOKEN_BINARY] for [MODE_BINARY]" - if(length(channels)) - for(var/i in 1 to length(channels)) - if(i == 1) - avail_chans += "use [MODE_TOKEN_DEPARTMENT] or [GLOB.channel_tokens[channels[i]]] for [lowertext(channels[i])]" - else - avail_chans += "use [GLOB.channel_tokens[channels[i]]] for [lowertext(channels[i])]" - . += "A small screen on the headset displays the following available frequencies:\n[english_list(avail_chans)]." - - if(command) - . += "Alt-click to toggle the high-volume mode." - else - . += "A small screen on the headset flashes, it's too small to read without holding or wearing the headset." - -/obj/item/radio/headset/Initialize() - . = ..() - recalculateChannels() - -/obj/item/radio/headset/Destroy() - QDEL_NULL(keyslot2) - return ..() - -/obj/item/radio/headset/talk_into(mob/living/M, message, channel, list/spans,datum/language/language) - if (!listening) - return ITALICS | REDUCE_RANGE - return ..() - -/obj/item/radio/headset/can_receive(freq, level, AIuser) - if(ishuman(src.loc)) - var/mob/living/carbon/human/H = src.loc - if(H.ears == src) - return ..(freq, level) - else if(AIuser) - return ..(freq, level) - return FALSE - -/obj/item/radio/headset/ui_data(mob/user) - . = ..() - .["headset"] = TRUE - -/obj/item/radio/headset/syndicate //disguised to look like a normal headset for stealth ops - -/obj/item/radio/headset/syndicate/alt //undisguised bowman with flash protection - name = "syndicate headset" - desc = "A syndicate headset that can be used to hear all radio frequencies. Protects ears from flashbangs." - icon_state = "syndie_headset" - item_state = "syndie_headset" - -/obj/item/radio/headset/syndicate/alt/ComponentInitialize() - . = ..() - AddComponent(/datum/component/wearertargeting/earprotection, list(ITEM_SLOT_EARS)) - -/obj/item/radio/headset/syndicate/alt/leader - name = "team leader headset" - command = TRUE - -/obj/item/radio/headset/syndicate/Initialize() - . = ..() - make_syndie() - -/obj/item/radio/headset/binary -/obj/item/radio/headset/binary/Initialize() - . = ..() - qdel(keyslot) - keyslot = new /obj/item/encryptionkey/binary - recalculateChannels() - -/obj/item/radio/headset/headset_sec - name = "security radio headset" - desc = "This is used by your elite security force." - icon_state = "sec_headset" - keyslot = new /obj/item/encryptionkey/headset_sec - -/obj/item/radio/headset/headset_sec/alt - name = "security bowman headset" - desc = "This is used by your elite security force. Protects ears from flashbangs." - icon_state = "sec_headset_alt" - item_state = "sec_headset_alt" - -/obj/item/radio/headset/headset_sec/alt/ComponentInitialize() - . = ..() - AddComponent(/datum/component/wearertargeting/earprotection, list(ITEM_SLOT_EARS)) - -/obj/item/radio/headset/headset_eng - name = "engineering radio headset" - desc = "When the engineers wish to chat like girls." - icon_state = "eng_headset" - keyslot = new /obj/item/encryptionkey/headset_eng - -/obj/item/radio/headset/headset_rob - name = "robotics radio headset" - desc = "Made specifically for the roboticists, who cannot decide between departments." - icon_state = "rob_headset" - keyslot = new /obj/item/encryptionkey/headset_rob - -/obj/item/radio/headset/headset_med - name = "medical radio headset" - desc = "A headset for the trained staff of the medbay." - icon_state = "med_headset" - keyslot = new /obj/item/encryptionkey/headset_med - -/obj/item/radio/headset/headset_sci - name = "science radio headset" - desc = "A sciency headset. Like usual." - icon_state = "sci_headset" - keyslot = new /obj/item/encryptionkey/headset_sci - -/obj/item/radio/headset/headset_medsci - name = "medical research radio headset" - desc = "A headset that is a result of the mating between medical and science." - icon_state = "medsci_headset" - keyslot = new /obj/item/encryptionkey/headset_medsci - -/obj/item/radio/headset/headset_srvsec - name = "law and order headset" - desc = "In the criminal justice headset, the encryption key represents two separate but equally important groups. Sec, who investigate crime, and Service, who provide services. These are their comms." - icon_state = "srvsec_headset" - keyslot = new /obj/item/encryptionkey/headset_srvsec - -/obj/item/radio/headset/headset_srvmed - name = "psychology headset" - desc = "A headset allowing the wearer to communicate with medbay and service." - icon_state = "med_headset" - keyslot = new /obj/item/encryptionkey/headset_srvmed - -/obj/item/radio/headset/headset_com - name = "command radio headset" - desc = "A headset with a commanding channel." - icon_state = "com_headset" - keyslot = new /obj/item/encryptionkey/headset_com - -/obj/item/radio/headset/heads - command = TRUE - -/obj/item/radio/headset/heads/captain - name = "\proper the captain's headset" - desc = "The headset of the king." - icon_state = "com_headset" - keyslot = new /obj/item/encryptionkey/heads/captain - -/obj/item/radio/headset/heads/captain/alt - name = "\proper the captain's bowman headset" - desc = "The headset of the boss. Protects ears from flashbangs." - icon_state = "com_headset_alt" - item_state = "com_headset_alt" - -/obj/item/radio/headset/heads/captain/alt/ComponentInitialize() - . = ..() - AddComponent(/datum/component/wearertargeting/earprotection, list(ITEM_SLOT_EARS)) - -/obj/item/radio/headset/heads/rd - name = "\proper the research director's headset" - desc = "Headset of the fellow who keeps society marching towards technological singularity." - icon_state = "com_headset" - keyslot = new /obj/item/encryptionkey/heads/rd - -/obj/item/radio/headset/heads/hos - name = "\proper the head of security's headset" - desc = "The headset of the man in charge of keeping order and protecting the station." - icon_state = "com_headset" - keyslot = new /obj/item/encryptionkey/heads/hos - -/obj/item/radio/headset/heads/hos/alt - name = "\proper the head of security's bowman headset" - desc = "The headset of the man in charge of keeping order and protecting the station. Protects ears from flashbangs." - icon_state = "com_headset_alt" - item_state = "com_headset_alt" - -/obj/item/radio/headset/heads/hos/ComponentInitialize() - . = ..() - AddComponent(/datum/component/wearertargeting/earprotection, list(ITEM_SLOT_EARS)) - -/obj/item/radio/headset/heads/ce - name = "\proper the chief engineer's headset" - desc = "The headset of the guy in charge of keeping the station powered and undamaged." - icon_state = "com_headset" - keyslot = new /obj/item/encryptionkey/heads/ce - -/obj/item/radio/headset/heads/cmo - name = "\proper the chief medical officer's headset" - desc = "The headset of the highly trained medical chief." - icon_state = "com_headset" - keyslot = new /obj/item/encryptionkey/heads/cmo - -/obj/item/radio/headset/heads/head_of_personnel - name = "\proper the head of personnel's headset" - desc = "The headset of the guy who will one day be captain." - icon_state = "com_headset" - keyslot = new /obj/item/encryptionkey/heads/head_of_personnel - -/obj/item/radio/headset/headset_cargo - name = "supply radio headset" - desc = "A headset used by the QM and his slaves." - icon_state = "cargo_headset" - keyslot = new /obj/item/encryptionkey/headset_cargo - -/obj/item/radio/headset/headset_cargo/mining - name = "mining radio headset" - desc = "Headset used by shaft miners." - icon_state = "mine_headset" - keyslot = new /obj/item/encryptionkey/headset_mining - -/obj/item/radio/headset/headset_srv - name = "service radio headset" - desc = "Headset used by the service staff, tasked with keeping the station full, happy and clean." - icon_state = "srv_headset" - keyslot = new /obj/item/encryptionkey/headset_service - -/obj/item/radio/headset/headset_cent - name = "\improper CentCom headset" - desc = "A headset used by the upper echelons of Nanotrasen." - icon_state = "cent_headset" - keyslot = new /obj/item/encryptionkey/headset_com - keyslot2 = new /obj/item/encryptionkey/headset_cent - -/obj/item/radio/headset/headset_cent/empty - keyslot = null - keyslot2 = null - -/obj/item/radio/headset/headset_cent/commander - keyslot = new /obj/item/encryptionkey/heads/captain - -/obj/item/radio/headset/headset_cent/alt - name = "\improper CentCom bowman headset" - desc = "A headset especially for emergency response personnel. Protects ears from flashbangs." - icon_state = "cent_headset_alt" - item_state = "cent_headset_alt" - keyslot = null - -/obj/item/radio/headset/headset_cent/alt/ComponentInitialize() - . = ..() - AddComponent(/datum/component/wearertargeting/earprotection, list(ITEM_SLOT_EARS)) - -/obj/item/radio/headset/silicon/pai - name = "\proper mini Integrated Subspace Transceiver " - subspace_transmission = FALSE - - -/obj/item/radio/headset/silicon/ai - name = "\proper Integrated Subspace Transceiver " - keyslot2 = new /obj/item/encryptionkey/ai - command = TRUE - -/obj/item/radio/headset/silicon/can_receive(freq, level) - return ..(freq, level, TRUE) - -/obj/item/radio/headset/attackby(obj/item/W, mob/user, params) - user.set_machine(src) - - if(W.tool_behaviour == TOOL_SCREWDRIVER) - if(keyslot || keyslot2) - for(var/ch_name in channels) - SSradio.remove_object(src, GLOB.radiochannels[ch_name]) - secure_radio_connections[ch_name] = null - - if(keyslot) - user.put_in_hands(keyslot) - keyslot = null - if(keyslot2) - user.put_in_hands(keyslot2) - keyslot2 = null - - recalculateChannels() - to_chat(user, "You pop out the encryption keys in the headset.") - - else - to_chat(user, "This headset doesn't have any unique encryption keys! How useless...") - - else if(istype(W, /obj/item/encryptionkey)) - if(keyslot && keyslot2) - to_chat(user, "The headset can't hold another key!") - return - - if(!keyslot) - if(!user.transferItemToLoc(W, src)) - return - keyslot = W - - else - if(!user.transferItemToLoc(W, src)) - return - keyslot2 = W - - - recalculateChannels() - else - return ..() - - -/obj/item/radio/headset/recalculateChannels() - ..() - if(keyslot2) - for(var/ch_name in keyslot2.channels) - if(!(ch_name in src.channels)) - channels[ch_name] = keyslot2.channels[ch_name] - - if(keyslot2.translate_binary) - translate_binary = TRUE - if(keyslot2.syndie) - syndie = TRUE - if (keyslot2.independent) - independent = TRUE - - for(var/ch_name in channels) - secure_radio_connections[ch_name] = add_radio(src, GLOB.radiochannels[ch_name]) - -/obj/item/radio/headset/AltClick(mob/living/user) - if(!istype(user) || !Adjacent(user) || user.incapacitated()) - return - if (command) - use_command = !use_command - to_chat(user, "You toggle high-volume mode [use_command ? "on" : "off"].") +// Used for translating channels to tokens on examination +GLOBAL_LIST_INIT(channel_tokens, list( + RADIO_CHANNEL_COMMON = RADIO_KEY_COMMON, + RADIO_CHANNEL_SCIENCE = RADIO_TOKEN_SCIENCE, + RADIO_CHANNEL_COMMAND = RADIO_TOKEN_COMMAND, + RADIO_CHANNEL_MEDICAL = RADIO_TOKEN_MEDICAL, + RADIO_CHANNEL_ENGINEERING = RADIO_TOKEN_ENGINEERING, + RADIO_CHANNEL_SECURITY = RADIO_TOKEN_SECURITY, + RADIO_CHANNEL_CENTCOM = RADIO_TOKEN_CENTCOM, + RADIO_CHANNEL_SYNDICATE = RADIO_TOKEN_SYNDICATE, + RADIO_CHANNEL_SUPPLY = RADIO_TOKEN_SUPPLY, + RADIO_CHANNEL_SERVICE = RADIO_TOKEN_SERVICE, + MODE_BINARY = MODE_TOKEN_BINARY, + RADIO_CHANNEL_AI_PRIVATE = RADIO_TOKEN_AI_PRIVATE +)) + +/obj/item/radio/headset + name = "radio headset" + desc = "An updated, modular intercom that fits over the head. Takes encryption keys." + icon_state = "headset" + item_state = "headset" + custom_materials = list(/datum/material/iron=75) + subspace_transmission = TRUE + canhear_range = 0 // can't hear headsets from very far away + + slot_flags = ITEM_SLOT_EARS + var/obj/item/encryptionkey/keyslot2 = null + dog_fashion = null + +/obj/item/radio/headset/suicide_act(mob/living/carbon/user) + user.visible_message("[user] begins putting \the [src]'s antenna up [user.p_their()] nose! It looks like [user.p_theyre()] trying to give [user.p_them()]self cancer!") + return TOXLOSS + +/obj/item/radio/headset/examine(mob/user) + . = ..() + + if(item_flags & IN_INVENTORY && loc == user) + // construction of frequency description + var/list/avail_chans = list("Use [RADIO_KEY_COMMON] for the currently tuned frequency") + if(translate_binary) + avail_chans += "use [MODE_TOKEN_BINARY] for [MODE_BINARY]" + if(length(channels)) + for(var/i in 1 to length(channels)) + if(i == 1) + avail_chans += "use [MODE_TOKEN_DEPARTMENT] or [GLOB.channel_tokens[channels[i]]] for [lowertext(channels[i])]" + else + avail_chans += "use [GLOB.channel_tokens[channels[i]]] for [lowertext(channels[i])]" + . += "A small screen on the headset displays the following available frequencies:\n[english_list(avail_chans)]." + + if(command) + . += "Alt-click to toggle the high-volume mode." + else + . += "A small screen on the headset flashes, it's too small to read without holding or wearing the headset." + +/obj/item/radio/headset/Initialize() + . = ..() + recalculateChannels() + +/obj/item/radio/headset/Destroy() + QDEL_NULL(keyslot2) + return ..() + +/obj/item/radio/headset/talk_into(mob/living/M, message, channel, list/spans,datum/language/language) + if (!listening) + return ITALICS | REDUCE_RANGE + return ..() + +/obj/item/radio/headset/can_receive(freq, level, AIuser) + if(ishuman(src.loc)) + var/mob/living/carbon/human/H = src.loc + if(H.ears == src) + return ..(freq, level) + else if(AIuser) + return ..(freq, level) + return FALSE + +/obj/item/radio/headset/ui_data(mob/user) + . = ..() + .["headset"] = TRUE + +/obj/item/radio/headset/syndicate //disguised to look like a normal headset for stealth ops + +/obj/item/radio/headset/syndicate/alt //undisguised bowman with flash protection + name = "syndicate headset" + desc = "A syndicate headset that can be used to hear all radio frequencies. Protects ears from flashbangs." + icon_state = "syndie_headset" + item_state = "syndie_headset" + +/obj/item/radio/headset/syndicate/alt/ComponentInitialize() + . = ..() + AddComponent(/datum/component/wearertargeting/earprotection, list(ITEM_SLOT_EARS)) + +/obj/item/radio/headset/syndicate/alt/leader + name = "team leader headset" + command = TRUE + +/obj/item/radio/headset/syndicate/Initialize() + . = ..() + make_syndie() + +/obj/item/radio/headset/binary +/obj/item/radio/headset/binary/Initialize() + . = ..() + qdel(keyslot) + keyslot = new /obj/item/encryptionkey/binary + recalculateChannels() + +/obj/item/radio/headset/headset_sec + name = "security radio headset" + desc = "This is used by your elite security force." + icon_state = "sec_headset" + keyslot = new /obj/item/encryptionkey/headset_sec + +/obj/item/radio/headset/headset_sec/alt + name = "security bowman headset" + desc = "This is used by your elite security force. Protects ears from flashbangs." + icon_state = "sec_headset_alt" + item_state = "sec_headset_alt" + +/obj/item/radio/headset/headset_sec/alt/ComponentInitialize() + . = ..() + AddComponent(/datum/component/wearertargeting/earprotection, list(ITEM_SLOT_EARS)) + +/obj/item/radio/headset/headset_eng + name = "engineering radio headset" + desc = "When the engineers wish to chat like girls." + icon_state = "eng_headset" + keyslot = new /obj/item/encryptionkey/headset_eng + +/obj/item/radio/headset/headset_rob + name = "robotics radio headset" + desc = "Made specifically for the roboticists, who cannot decide between departments." + icon_state = "rob_headset" + keyslot = new /obj/item/encryptionkey/headset_rob + +/obj/item/radio/headset/headset_med + name = "medical radio headset" + desc = "A headset for the trained staff of the medbay." + icon_state = "med_headset" + keyslot = new /obj/item/encryptionkey/headset_med + +/obj/item/radio/headset/headset_sci + name = "science radio headset" + desc = "A sciency headset. Like usual." + icon_state = "sci_headset" + keyslot = new /obj/item/encryptionkey/headset_sci + +/obj/item/radio/headset/headset_medsci + name = "medical research radio headset" + desc = "A headset that is a result of the mating between medical and science." + icon_state = "medsci_headset" + keyslot = new /obj/item/encryptionkey/headset_medsci + +/obj/item/radio/headset/headset_srvsec + name = "law and order headset" + desc = "In the criminal justice headset, the encryption key represents two separate but equally important groups. Sec, who investigate crime, and Service, who provide services. These are their comms." + icon_state = "srvsec_headset" + keyslot = new /obj/item/encryptionkey/headset_srvsec + +/obj/item/radio/headset/headset_srvmed + name = "psychology headset" + desc = "A headset allowing the wearer to communicate with medbay and service." + icon_state = "med_headset" + keyslot = new /obj/item/encryptionkey/headset_srvmed + +/obj/item/radio/headset/headset_com + name = "command radio headset" + desc = "A headset with a commanding channel." + icon_state = "com_headset" + keyslot = new /obj/item/encryptionkey/headset_com + +/obj/item/radio/headset/heads + command = TRUE + +/obj/item/radio/headset/heads/captain + name = "\proper the captain's headset" + desc = "The headset of the king." + icon_state = "com_headset" + keyslot = new /obj/item/encryptionkey/heads/captain + +/obj/item/radio/headset/heads/captain/alt + name = "\proper the captain's bowman headset" + desc = "The headset of the boss. Protects ears from flashbangs." + icon_state = "com_headset_alt" + item_state = "com_headset_alt" + +/obj/item/radio/headset/heads/captain/alt/ComponentInitialize() + . = ..() + AddComponent(/datum/component/wearertargeting/earprotection, list(ITEM_SLOT_EARS)) + +/obj/item/radio/headset/heads/rd + name = "\proper the research director's headset" + desc = "Headset of the fellow who keeps society marching towards technological singularity." + icon_state = "com_headset" + keyslot = new /obj/item/encryptionkey/heads/rd + +/obj/item/radio/headset/heads/hos + name = "\proper the head of security's headset" + desc = "The headset of the man in charge of keeping order and protecting the station." + icon_state = "com_headset" + keyslot = new /obj/item/encryptionkey/heads/hos + +/obj/item/radio/headset/heads/hos/alt + name = "\proper the head of security's bowman headset" + desc = "The headset of the man in charge of keeping order and protecting the station. Protects ears from flashbangs." + icon_state = "com_headset_alt" + item_state = "com_headset_alt" + +/obj/item/radio/headset/heads/hos/ComponentInitialize() + . = ..() + AddComponent(/datum/component/wearertargeting/earprotection, list(ITEM_SLOT_EARS)) + +/obj/item/radio/headset/heads/ce + name = "\proper the chief engineer's headset" + desc = "The headset of the guy in charge of keeping the station powered and undamaged." + icon_state = "com_headset" + keyslot = new /obj/item/encryptionkey/heads/ce + +/obj/item/radio/headset/heads/cmo + name = "\proper the chief medical officer's headset" + desc = "The headset of the highly trained medical chief." + icon_state = "com_headset" + keyslot = new /obj/item/encryptionkey/heads/cmo + +/obj/item/radio/headset/heads/head_of_personnel + name = "\proper the head of personnel's headset" + desc = "The headset of the guy who will one day be captain." + icon_state = "com_headset" + keyslot = new /obj/item/encryptionkey/heads/head_of_personnel + +/obj/item/radio/headset/headset_cargo + name = "supply radio headset" + desc = "A headset used by the QM and his slaves." + icon_state = "cargo_headset" + keyslot = new /obj/item/encryptionkey/headset_cargo + +/obj/item/radio/headset/headset_cargo/mining + name = "mining radio headset" + desc = "Headset used by shaft miners." + icon_state = "mine_headset" + keyslot = new /obj/item/encryptionkey/headset_mining + +/obj/item/radio/headset/headset_srv + name = "service radio headset" + desc = "Headset used by the service staff, tasked with keeping the station full, happy and clean." + icon_state = "srv_headset" + keyslot = new /obj/item/encryptionkey/headset_service + +/obj/item/radio/headset/headset_cent + name = "\improper CentCom headset" + desc = "A headset used by the upper echelons of Nanotrasen." + icon_state = "cent_headset" + keyslot = new /obj/item/encryptionkey/headset_com + keyslot2 = new /obj/item/encryptionkey/headset_cent + +/obj/item/radio/headset/headset_cent/empty + keyslot = null + keyslot2 = null + +/obj/item/radio/headset/headset_cent/commander + keyslot = new /obj/item/encryptionkey/heads/captain + +/obj/item/radio/headset/headset_cent/alt + name = "\improper CentCom bowman headset" + desc = "A headset especially for emergency response personnel. Protects ears from flashbangs." + icon_state = "cent_headset_alt" + item_state = "cent_headset_alt" + keyslot = null + +/obj/item/radio/headset/headset_cent/alt/ComponentInitialize() + . = ..() + AddComponent(/datum/component/wearertargeting/earprotection, list(ITEM_SLOT_EARS)) + +/obj/item/radio/headset/silicon/pai + name = "\proper mini Integrated Subspace Transceiver " + subspace_transmission = FALSE + + +/obj/item/radio/headset/silicon/ai + name = "\proper Integrated Subspace Transceiver " + keyslot2 = new /obj/item/encryptionkey/ai + command = TRUE + +/obj/item/radio/headset/silicon/can_receive(freq, level) + return ..(freq, level, TRUE) + +/obj/item/radio/headset/attackby(obj/item/W, mob/user, params) + user.set_machine(src) + + if(W.tool_behaviour == TOOL_SCREWDRIVER) + if(keyslot || keyslot2) + for(var/ch_name in channels) + SSradio.remove_object(src, GLOB.radiochannels[ch_name]) + secure_radio_connections[ch_name] = null + + if(keyslot) + user.put_in_hands(keyslot) + keyslot = null + if(keyslot2) + user.put_in_hands(keyslot2) + keyslot2 = null + + recalculateChannels() + to_chat(user, "You pop out the encryption keys in the headset.") + + else + to_chat(user, "This headset doesn't have any unique encryption keys! How useless...") + + else if(istype(W, /obj/item/encryptionkey)) + if(keyslot && keyslot2) + to_chat(user, "The headset can't hold another key!") + return + + if(!keyslot) + if(!user.transferItemToLoc(W, src)) + return + keyslot = W + + else + if(!user.transferItemToLoc(W, src)) + return + keyslot2 = W + + + recalculateChannels() + else + return ..() + + +/obj/item/radio/headset/recalculateChannels() + ..() + if(keyslot2) + for(var/ch_name in keyslot2.channels) + if(!(ch_name in src.channels)) + channels[ch_name] = keyslot2.channels[ch_name] + + if(keyslot2.translate_binary) + translate_binary = TRUE + if(keyslot2.syndie) + syndie = TRUE + if (keyslot2.independent) + independent = TRUE + + for(var/ch_name in channels) + secure_radio_connections[ch_name] = add_radio(src, GLOB.radiochannels[ch_name]) + +/obj/item/radio/headset/AltClick(mob/living/user) + if(!istype(user) || !Adjacent(user) || user.incapacitated()) + return + if (command) + use_command = !use_command + to_chat(user, "You toggle high-volume mode [use_command ? "on" : "off"].") diff --git a/code/game/objects/items/devices/radio/intercom.dm b/code/game/objects/items/devices/radio/intercom.dm index 08ce36bceda6..28daf765a597 100644 --- a/code/game/objects/items/devices/radio/intercom.dm +++ b/code/game/objects/items/devices/radio/intercom.dm @@ -1,136 +1,135 @@ -/obj/item/radio/intercom - name = "station intercom" - desc = "Talk through this." - icon = 'waspstation/icons/obj/radio.dmi' - icon_state = "intercom" - anchored = TRUE - w_class = WEIGHT_CLASS_BULKY - canhear_range = 2 - dog_fashion = null - unscrewed = FALSE - -/obj/item/radio/intercom/unscrewed - unscrewed = TRUE - -/obj/item/radio/intercom/Initialize(mapload, ndir, building) - . = ..() - if(building) - setDir(ndir) - var/area/current_area = get_area(src) - if(!current_area) - return - RegisterSignal(current_area, COMSIG_AREA_POWER_CHANGE, .proc/AreaPowerCheck) - -/obj/item/radio/intercom/examine(mob/user) - . = ..() - . += "Use [MODE_TOKEN_INTERCOM] when nearby to speak into it." - if(!unscrewed) - . += "It's screwed and secured to the wall." - else - . += "It's unscrewed from the wall, and can be detached." - -/obj/item/radio/intercom/attackby(obj/item/I, mob/living/user, params) - if(I.tool_behaviour == TOOL_SCREWDRIVER) - if(unscrewed) - user.visible_message("[user] starts tightening [src]'s screws...", "You start screwing in [src]...") - if(I.use_tool(src, user, 30, volume=50)) - user.visible_message("[user] tightens [src]'s screws!", "You tighten [src]'s screws.") - unscrewed = FALSE - else - user.visible_message("[user] starts loosening [src]'s screws...", "You start unscrewing [src]...") - if(I.use_tool(src, user, 40, volume=50)) - user.visible_message("[user] loosens [src]'s screws!", "You unscrew [src], loosening it from the wall.") - unscrewed = TRUE - return - else if(I.tool_behaviour == TOOL_WRENCH) - if(!unscrewed) - to_chat(user, "You need to unscrew [src] from the wall first!") - return - user.visible_message("[user] starts unsecuring [src]...", "You start unsecuring [src]...") - I.play_tool_sound(src) - if(I.use_tool(src, user, 80)) - user.visible_message("[user] unsecures [src]!", "You detach [src] from the wall.") - playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE) - new/obj/item/wallframe/intercom(get_turf(src)) - qdel(src) - return - return ..() - -/obj/item/radio/intercom/attack_ai(mob/user) - interact(user) - -/obj/item/radio/intercom/attack_hand(mob/user) - . = ..() - if(.) - return - interact(user) - -/obj/item/radio/intercom/interact(mob/user) - ..() - ui_interact(user, state = GLOB.default_state) - -/obj/item/radio/intercom/can_receive(freq, level) - if(!on) - return FALSE - if(wires.is_cut(WIRE_RX)) - return FALSE - if(!(0 in level)) - var/turf/position = get_turf(src) - if(isnull(position) || !(position.z in level)) - return FALSE - if(!listening) - return FALSE - if(freq == FREQ_SYNDICATE) - if(!(syndie)) - return FALSE//Prevents broadcast of messages over devices lacking the encryption - - return TRUE - - -/obj/item/radio/intercom/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, list/spans, message_mode) - if(message_mode == MODE_INTERCOM) - return // Avoid hearing the same thing twice - return ..() - -/obj/item/radio/intercom/emp_act(severity) - . = ..() // Parent call here will set `on` to FALSE. - update_icon() - -/obj/item/radio/intercom/end_emp_effect(curremp) - . = ..() - AreaPowerCheck() // Make sure the area/local APC is powered first before we actually turn back on. - -/obj/item/radio/intercom/update_icon() - . = ..() - if(on) - icon_state = initial(icon_state) - else - icon_state = "intercom-p" - -/** - * Proc called whenever the intercom's area loses or gains power. Responsible for setting the `on` variable and calling `update_icon()`. - * - * Normally called after the intercom's area recieves the `COMSIG_AREA_POWER_CHANGE` signal, but it can also be called directly. - * Arguments: - * * source - the area that just had a power change. - */ -/obj/item/radio/intercom/proc/AreaPowerCheck(datum/source) - var/area/current_area = get_area(src) - if(!current_area) - on = FALSE - else - on = current_area.powered(AREA_USAGE_EQUIP) // set "on" to the equipment power status of our area. - update_icon() - -/obj/item/radio/intercom/add_blood_DNA(list/blood_dna) - return FALSE - -//Created through the autolathe or through deconstructing intercoms. Can be applied to wall to make a new intercom on it! -/obj/item/wallframe/intercom - name = "intercom frame" - desc = "A ready-to-go intercom. Just slap it on a wall and screw it in!" - icon_state = "intercom" - result_path = /obj/item/radio/intercom/unscrewed - pixel_shift = 29 - inverse = TRUE - custom_materials = list(/datum/material/iron = 75, /datum/material/glass = 25) +/obj/item/radio/intercom + name = "station intercom" + desc = "Talk through this." + icon = 'waspstation/icons/obj/radio.dmi' + icon_state = "intercom" + anchored = TRUE + w_class = WEIGHT_CLASS_BULKY + canhear_range = 2 + dog_fashion = null + unscrewed = FALSE + +/obj/item/radio/intercom/unscrewed + unscrewed = TRUE + +/obj/item/radio/intercom/Initialize(mapload, ndir, building) + . = ..() + if(building) + setDir(ndir) + var/area/current_area = get_area(src) + if(!current_area) + return + RegisterSignal(current_area, COMSIG_AREA_POWER_CHANGE, .proc/AreaPowerCheck) + +/obj/item/radio/intercom/examine(mob/user) + . = ..() + . += "Use [MODE_TOKEN_INTERCOM] when nearby to speak into it." + if(!unscrewed) + . += "It's screwed and secured to the wall." + else + . += "It's unscrewed from the wall, and can be detached." + +/obj/item/radio/intercom/attackby(obj/item/I, mob/living/user, params) + if(I.tool_behaviour == TOOL_SCREWDRIVER) + if(unscrewed) + user.visible_message("[user] starts tightening [src]'s screws...", "You start screwing in [src]...") + if(I.use_tool(src, user, 30, volume=50)) + user.visible_message("[user] tightens [src]'s screws!", "You tighten [src]'s screws.") + unscrewed = FALSE + else + user.visible_message("[user] starts loosening [src]'s screws...", "You start unscrewing [src]...") + if(I.use_tool(src, user, 40, volume=50)) + user.visible_message("[user] loosens [src]'s screws!", "You unscrew [src], loosening it from the wall.") + unscrewed = TRUE + return + else if(I.tool_behaviour == TOOL_WRENCH) + if(!unscrewed) + to_chat(user, "You need to unscrew [src] from the wall first!") + return + user.visible_message("[user] starts unsecuring [src]...", "You start unsecuring [src]...") + I.play_tool_sound(src) + if(I.use_tool(src, user, 80)) + user.visible_message("[user] unsecures [src]!", "You detach [src] from the wall.") + playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE) + new/obj/item/wallframe/intercom(get_turf(src)) + qdel(src) + return + return ..() + +/obj/item/radio/intercom/attack_ai(mob/user) + interact(user) + +/obj/item/radio/intercom/attack_hand(mob/user) + . = ..() + if(.) + return + interact(user) + +/obj/item/radio/intercom/ui_state(mob/user) + return GLOB.default_state + +/obj/item/radio/intercom/can_receive(freq, level) + if(!on) + return FALSE + if(wires.is_cut(WIRE_RX)) + return FALSE + if(!(0 in level)) + var/turf/position = get_turf(src) + if(isnull(position) || !(position.z in level)) + return FALSE + if(!listening) + return FALSE + if(freq == FREQ_SYNDICATE) + if(!(syndie)) + return FALSE//Prevents broadcast of messages over devices lacking the encryption + + return TRUE + + +/obj/item/radio/intercom/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, list/spans, message_mode) + if(message_mode == MODE_INTERCOM) + return // Avoid hearing the same thing twice + return ..() + +/obj/item/radio/intercom/emp_act(severity) + . = ..() // Parent call here will set `on` to FALSE. + update_icon() + +/obj/item/radio/intercom/end_emp_effect(curremp) + . = ..() + AreaPowerCheck() // Make sure the area/local APC is powered first before we actually turn back on. + +/obj/item/radio/intercom/update_icon() + . = ..() + if(on) + icon_state = initial(icon_state) + else + icon_state = "intercom-p" + +/** + * Proc called whenever the intercom's area loses or gains power. Responsible for setting the `on` variable and calling `update_icon()`. + * + * Normally called after the intercom's area recieves the `COMSIG_AREA_POWER_CHANGE` signal, but it can also be called directly. + * Arguments: + * * source - the area that just had a power change. + */ +/obj/item/radio/intercom/proc/AreaPowerCheck(datum/source) + var/area/current_area = get_area(src) + if(!current_area) + on = FALSE + else + on = current_area.powered(AREA_USAGE_EQUIP) // set "on" to the equipment power status of our area. + update_icon() + +/obj/item/radio/intercom/add_blood_DNA(list/blood_dna) + return FALSE + +//Created through the autolathe or through deconstructing intercoms. Can be applied to wall to make a new intercom on it! +/obj/item/wallframe/intercom + name = "intercom frame" + desc = "A ready-to-go intercom. Just slap it on a wall and screw it in!" + icon_state = "intercom" + result_path = /obj/item/radio/intercom/unscrewed + pixel_shift = 29 + inverse = TRUE + custom_materials = list(/datum/material/iron = 75, /datum/material/glass = 25) diff --git a/code/game/objects/items/devices/radio/radio.dm b/code/game/objects/items/devices/radio/radio.dm index ea218cd5146a..b1bd33cd1434 100644 --- a/code/game/objects/items/devices/radio/radio.dm +++ b/code/game/objects/items/devices/radio/radio.dm @@ -1,417 +1,413 @@ -/obj/item/radio - icon = 'icons/obj/radio.dmi' - name = "station bounced radio" - icon_state = "walkietalkie" - item_state = "walkietalkie" - desc = "A basic handheld radio that communicates with local telecommunication networks." - dog_fashion = /datum/dog_fashion/back - - flags_1 = CONDUCT_1 | HEAR_1 - slot_flags = ITEM_SLOT_BELT - throw_speed = 3 - throw_range = 7 - w_class = WEIGHT_CLASS_SMALL - custom_materials = list(/datum/material/iron=75, /datum/material/glass=25) - obj_flags = USES_TGUI - - var/on = TRUE - var/frequency = FREQ_COMMON - var/canhear_range = 3 // The range around the radio in which mobs can hear what it receives. - var/emped = 0 // Tracks the number of EMPs currently stacked. - - var/broadcasting = FALSE // Whether the radio will transmit dialogue it hears nearby. - var/listening = TRUE // Whether the radio is currently receiving. - var/prison_radio = FALSE // If true, the transmit wire starts cut. - var/unscrewed = FALSE // Whether wires are accessible. Toggleable by screwdrivering. - var/freerange = FALSE // If true, the radio has access to the full spectrum. - var/subspace_transmission = FALSE // If true, the radio transmits and receives on subspace exclusively. - var/subspace_switchable = FALSE // If true, subspace_transmission can be toggled at will. - var/freqlock = FALSE // Frequency lock to stop the user from untuning specialist radios. - var/use_command = FALSE // If true, broadcasts will be large and BOLD. - var/command = FALSE // If true, use_command can be toggled at will. - - // Encryption key handling - var/obj/item/encryptionkey/keyslot - var/translate_binary = FALSE // If true, can hear the special binary channel. - var/independent = FALSE // If true, can say/hear on the special CentCom channel. - var/syndie = FALSE // If true, hears all well-known channels automatically, and can say/hear on the Syndicate channel. - var/list/channels = list() // Map from name (see communications.dm) to on/off. First entry is current department (:h). - var/list/secure_radio_connections - - var/const/FREQ_LISTENING = 1 - //FREQ_BROADCASTING = 2 - -/obj/item/radio/suicide_act(mob/living/user) - user.visible_message("[user] starts bouncing [src] off [user.p_their()] head! It looks like [user.p_theyre()] trying to commit suicide!") - return BRUTELOSS - -/obj/item/radio/proc/set_frequency(new_frequency) - SEND_SIGNAL(src, COMSIG_RADIO_NEW_FREQUENCY, args) - remove_radio(src, frequency) - frequency = add_radio(src, new_frequency) - -/obj/item/radio/proc/recalculateChannels() - channels = list() - translate_binary = FALSE - syndie = FALSE - independent = FALSE - - if(keyslot) - for(var/ch_name in keyslot.channels) - if(!(ch_name in channels)) - channels[ch_name] = keyslot.channels[ch_name] - - if(keyslot.translate_binary) - translate_binary = TRUE - if(keyslot.syndie) - syndie = TRUE - if(keyslot.independent) - independent = TRUE - - for(var/ch_name in channels) - secure_radio_connections[ch_name] = add_radio(src, GLOB.radiochannels[ch_name]) - -/obj/item/radio/proc/make_syndie() // Turns normal radios into Syndicate radios! - qdel(keyslot) - keyslot = new /obj/item/encryptionkey/syndicate - syndie = 1 - recalculateChannels() - -/obj/item/radio/Destroy() - remove_radio_all(src) //Just to be sure - QDEL_NULL(wires) - QDEL_NULL(keyslot) - return ..() - -/obj/item/radio/Initialize() - wires = new /datum/wires/radio(src) - if(prison_radio) - wires.cut(WIRE_TX) // OH GOD WHY - secure_radio_connections = new - . = ..() - frequency = sanitize_frequency(frequency, freerange) - set_frequency(frequency) - - for(var/ch_name in channels) - secure_radio_connections[ch_name] = add_radio(src, GLOB.radiochannels[ch_name]) - -/obj/item/radio/ComponentInitialize() - . = ..() - AddComponent(/datum/component/empprotection, EMP_PROTECT_WIRES) - -/obj/item/radio/interact(mob/user) - if(unscrewed && !isAI(user)) - wires.interact(user) - add_fingerprint(user) - else - ..() - -/obj/item/radio/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.inventory_state) - . = ..() - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - var/ui_width = 360 - var/ui_height = 106 - if(subspace_transmission) - if (channels.len > 0) - ui_height += 6 + channels.len * 21 - else - ui_height += 24 - ui = new(user, src, ui_key, "Radio", name, ui_width, ui_height, master_ui, state) - ui.open() - -/obj/item/radio/ui_data(mob/user) - var/list/data = list() - - data["broadcasting"] = broadcasting - data["listening"] = listening - data["frequency"] = frequency - data["minFrequency"] = freerange ? MIN_FREE_FREQ : MIN_FREQ - data["maxFrequency"] = freerange ? MAX_FREE_FREQ : MAX_FREQ - data["freqlock"] = freqlock - data["channels"] = list() - for(var/channel in channels) - data["channels"][channel] = channels[channel] & FREQ_LISTENING - data["command"] = command - data["useCommand"] = use_command - data["subspace"] = subspace_transmission - data["subspaceSwitchable"] = subspace_switchable - data["headset"] = FALSE - - return data - -/obj/item/radio/ui_act(action, params, datum/tgui/ui) - if(..()) - return - switch(action) - if("frequency") - if(freqlock) - return - var/tune = params["tune"] - var/adjust = text2num(params["adjust"]) - if(adjust) - tune = frequency + adjust * 10 - . = TRUE - else if(text2num(tune) != null) - tune = tune * 10 - . = TRUE - if(.) - set_frequency(sanitize_frequency(tune, freerange)) - if("listen") - listening = !listening - . = TRUE - if("broadcast") - broadcasting = !broadcasting - . = TRUE - if("channel") - var/channel = params["channel"] - if(!(channel in channels)) - return - if(channels[channel] & FREQ_LISTENING) - channels[channel] &= ~FREQ_LISTENING - else - channels[channel] |= FREQ_LISTENING - . = TRUE - if("command") - use_command = !use_command - . = TRUE - if("subspace") - if(subspace_switchable) - subspace_transmission = !subspace_transmission - if(!subspace_transmission) - channels = list() - else - recalculateChannels() - . = TRUE - -/obj/item/radio/talk_into(atom/movable/M, message, channel, list/spans, datum/language/language) - if(!spans) - spans = list(M.speech_span) - if(!language) - language = M.get_selected_language() - INVOKE_ASYNC(src, .proc/talk_into_impl, M, message, channel, spans.Copy(), language) - return ITALICS | REDUCE_RANGE - -/obj/item/radio/proc/talk_into_impl(atom/movable/M, message, channel, list/spans, datum/language/language) - if(!on) - return // the device has to be on - if(!M || !message) - return - if(wires.is_cut(WIRE_TX)) // Permacell and otherwise tampered-with radios - return - if(!M.IsVocal()) - return - - if(use_command) - spans |= SPAN_COMMAND - - /* - Roughly speaking, radios attempt to make a subspace transmission (which - is received, processed, and rebroadcast by the telecomms satellite) and - if that fails, they send a mundane radio transmission. - - Headsets cannot send/receive mundane transmissions, only subspace. - Syndicate radios can hear transmissions on all well-known frequencies. - CentCom radios can hear the CentCom frequency no matter what. - */ - - // From the channel, determine the frequency and get a reference to it. - var/freq - if(channel && channels && channels.len > 0) - if(channel == MODE_DEPARTMENT) - channel = channels[1] - freq = secure_radio_connections[channel] - if (!channels[channel]) // if the channel is turned off, don't broadcast - return - else - freq = frequency - channel = null - - // Nearby active jammers prevent the message from transmitting - var/turf/position = get_turf(src) - for(var/obj/item/jammer/jammer in GLOB.active_jammers) - var/turf/jammer_turf = get_turf(jammer) - if(position.z == jammer_turf.z && (get_dist(position, jammer_turf) <= jammer.range)) - return - - // Determine the identity information which will be attached to the signal. - var/atom/movable/virtualspeaker/speaker = new(null, M, src) - - // Construct the signal - var/datum/signal/subspace/vocal/signal = new(src, freq, speaker, language, message, spans) - - // Independent radios, on the CentCom frequency, reach all independent radios - if (independent && (freq == FREQ_CENTCOM || freq == FREQ_CTF_RED || freq == FREQ_CTF_BLUE)) - signal.data["compression"] = 0 - signal.transmission_method = TRANSMISSION_SUPERSPACE - signal.levels = list(0) // reaches all Z-levels - signal.broadcast() - return - - // All radios make an attempt to use the subspace system first - signal.send_to_receivers() - - // If the radio is subspace-only, that's all it can do - if (subspace_transmission) - return - - // Non-subspace radios will check in a couple of seconds, and if the signal - // was never received, send a mundane broadcast (no headsets). - addtimer(CALLBACK(src, .proc/backup_transmission, signal), 20) - -/obj/item/radio/proc/backup_transmission(datum/signal/subspace/vocal/signal) - var/turf/T = get_turf(src) - if (signal.data["done"] && (T.z in signal.levels)) - return - - // Okay, the signal was never processed, send a mundane broadcast. - signal.data["compression"] = 0 - signal.transmission_method = TRANSMISSION_RADIO - signal.levels = list(T.z) - signal.broadcast() - -/obj/item/radio/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode) - . = ..() - if(radio_freq || !broadcasting || get_dist(src, speaker) > canhear_range) - return - - if(message_mode == MODE_WHISPER || message_mode == MODE_WHISPER_CRIT) - // radios don't pick up whispers very well - raw_message = stars(raw_message) - else if(message_mode == MODE_L_HAND || message_mode == MODE_R_HAND) - // try to avoid being heard double - if (loc == speaker && ismob(speaker)) - var/mob/M = speaker - var/idx = M.get_held_index_of_item(src) - // left hands are odd slots - if (idx && (idx % 2) == (message_mode == MODE_L_HAND)) - return - - talk_into(speaker, raw_message, , spans, language=message_language) - -// Checks if this radio can receive on the given frequency. -/obj/item/radio/proc/can_receive(freq, level) - // deny checks - if (!on || !listening || wires.is_cut(WIRE_RX)) - return FALSE - if (freq == FREQ_SYNDICATE && !syndie) - return FALSE - if (freq == FREQ_CENTCOM) - return independent // hard-ignores the z-level check - if (!(0 in level)) - var/turf/position = get_turf(src) - if(!position || !(position.z in level)) - return FALSE - - // allow checks: are we listening on that frequency? - if (freq == frequency) - return TRUE - for(var/ch_name in channels) - if(channels[ch_name] & FREQ_LISTENING) - //the GLOB.radiochannels list is located in communications.dm - if(GLOB.radiochannels[ch_name] == text2num(freq) || syndie) - return TRUE - return FALSE - - -/obj/item/radio/examine(mob/user) - . = ..() - if (frequency && in_range(src, user)) - . += "It is set to broadcast over the [frequency/10] frequency." - if (unscrewed) - . += "It can be attached and modified." - else - . += "It cannot be modified or attached." - -/obj/item/radio/attackby(obj/item/W, mob/user, params) - add_fingerprint(user) - if(W.tool_behaviour == TOOL_SCREWDRIVER) - unscrewed = !unscrewed - if(unscrewed) - to_chat(user, "The radio can now be attached and modified!") - else - to_chat(user, "The radio can no longer be modified or attached!") - else - return ..() - -/obj/item/radio/emp_act(severity) - . = ..() - if (. & EMP_PROTECT_SELF) - return - emped++ //There's been an EMP; better count it - var/curremp = emped //Remember which EMP this was - if (listening && ismob(loc)) // if the radio is turned on and on someone's person they notice - to_chat(loc, "\The [src] overloads.") - broadcasting = FALSE - listening = FALSE - for (var/ch_name in channels) - channels[ch_name] = 0 - on = FALSE - addtimer(CALLBACK(src, .proc/end_emp_effect, curremp), 200) - -/obj/item/radio/proc/end_emp_effect(curremp) - if(emped != curremp) //Don't fix it if it's been EMP'd again - return FALSE - emped = FALSE - on = TRUE - return TRUE - -/////////////////////////////// -//////////Borg Radios////////// -/////////////////////////////// -//Giving borgs their own radio to have some more room to work with -Sieve - -/obj/item/radio/borg - name = "cyborg radio" - subspace_switchable = TRUE - dog_fashion = null - -/obj/item/radio/borg/Initialize(mapload) - . = ..() - -/obj/item/radio/borg/syndicate - syndie = 1 - keyslot = new /obj/item/encryptionkey/syndicate - -/obj/item/radio/borg/syndicate/Initialize() - . = ..() - set_frequency(FREQ_SYNDICATE) - -/obj/item/radio/borg/attackby(obj/item/W, mob/user, params) - - if(W.tool_behaviour == TOOL_SCREWDRIVER) - if(keyslot) - for(var/ch_name in channels) - SSradio.remove_object(src, GLOB.radiochannels[ch_name]) - secure_radio_connections[ch_name] = null - - - if(keyslot) - var/turf/T = get_turf(user) - if(T) - keyslot.forceMove(T) - keyslot = null - - recalculateChannels() - to_chat(user, "You pop out the encryption key in the radio.") - - else - to_chat(user, "This radio doesn't have any encryption keys!") - - else if(istype(W, /obj/item/encryptionkey/)) - if(keyslot) - to_chat(user, "The radio can't hold another key!") - return - - if(!keyslot) - if(!user.transferItemToLoc(W, src)) - return - keyslot = W - - recalculateChannels() - - -/obj/item/radio/off // Station bounced radios, their only difference is spawning with the speakers off, this was made to help the lag. - listening = 0 // And it's nice to have a subtype too for future features. - dog_fashion = /datum/dog_fashion/back +/obj/item/radio + icon = 'icons/obj/radio.dmi' + name = "station bounced radio" + icon_state = "walkietalkie" + item_state = "walkietalkie" + desc = "A basic handheld radio that communicates with local telecommunication networks." + dog_fashion = /datum/dog_fashion/back + + flags_1 = CONDUCT_1 | HEAR_1 + slot_flags = ITEM_SLOT_BELT + throw_speed = 3 + throw_range = 7 + w_class = WEIGHT_CLASS_SMALL + custom_materials = list(/datum/material/iron=75, /datum/material/glass=25) + obj_flags = USES_TGUI + + var/on = TRUE + var/frequency = FREQ_COMMON + var/canhear_range = 3 // The range around the radio in which mobs can hear what it receives. + var/emped = 0 // Tracks the number of EMPs currently stacked. + + var/broadcasting = FALSE // Whether the radio will transmit dialogue it hears nearby. + var/listening = TRUE // Whether the radio is currently receiving. + var/prison_radio = FALSE // If true, the transmit wire starts cut. + var/unscrewed = FALSE // Whether wires are accessible. Toggleable by screwdrivering. + var/freerange = FALSE // If true, the radio has access to the full spectrum. + var/subspace_transmission = FALSE // If true, the radio transmits and receives on subspace exclusively. + var/subspace_switchable = FALSE // If true, subspace_transmission can be toggled at will. + var/freqlock = FALSE // Frequency lock to stop the user from untuning specialist radios. + var/use_command = FALSE // If true, broadcasts will be large and BOLD. + var/command = FALSE // If true, use_command can be toggled at will. + + // Encryption key handling + var/obj/item/encryptionkey/keyslot + var/translate_binary = FALSE // If true, can hear the special binary channel. + var/independent = FALSE // If true, can say/hear on the special CentCom channel. + var/syndie = FALSE // If true, hears all well-known channels automatically, and can say/hear on the Syndicate channel. + var/list/channels = list() // Map from name (see communications.dm) to on/off. First entry is current department (:h). + var/list/secure_radio_connections + + var/const/FREQ_LISTENING = 1 + //FREQ_BROADCASTING = 2 + +/obj/item/radio/suicide_act(mob/living/user) + user.visible_message("[user] starts bouncing [src] off [user.p_their()] head! It looks like [user.p_theyre()] trying to commit suicide!") + return BRUTELOSS + +/obj/item/radio/proc/set_frequency(new_frequency) + SEND_SIGNAL(src, COMSIG_RADIO_NEW_FREQUENCY, args) + remove_radio(src, frequency) + frequency = add_radio(src, new_frequency) + +/obj/item/radio/proc/recalculateChannels() + channels = list() + translate_binary = FALSE + syndie = FALSE + independent = FALSE + + if(keyslot) + for(var/ch_name in keyslot.channels) + if(!(ch_name in channels)) + channels[ch_name] = keyslot.channels[ch_name] + + if(keyslot.translate_binary) + translate_binary = TRUE + if(keyslot.syndie) + syndie = TRUE + if(keyslot.independent) + independent = TRUE + + for(var/ch_name in channels) + secure_radio_connections[ch_name] = add_radio(src, GLOB.radiochannels[ch_name]) + +/obj/item/radio/proc/make_syndie() // Turns normal radios into Syndicate radios! + qdel(keyslot) + keyslot = new /obj/item/encryptionkey/syndicate + syndie = 1 + recalculateChannels() + +/obj/item/radio/Destroy() + remove_radio_all(src) //Just to be sure + QDEL_NULL(wires) + QDEL_NULL(keyslot) + return ..() + +/obj/item/radio/Initialize() + wires = new /datum/wires/radio(src) + if(prison_radio) + wires.cut(WIRE_TX) // OH GOD WHY + secure_radio_connections = new + . = ..() + frequency = sanitize_frequency(frequency, freerange) + set_frequency(frequency) + + for(var/ch_name in channels) + secure_radio_connections[ch_name] = add_radio(src, GLOB.radiochannels[ch_name]) + +/obj/item/radio/ComponentInitialize() + . = ..() + AddComponent(/datum/component/empprotection, EMP_PROTECT_WIRES) + +/obj/item/radio/interact(mob/user) + if(unscrewed && !isAI(user)) + wires.interact(user) + add_fingerprint(user) + else + ..() + +/obj/item/radio/ui_state(mob/user) + return GLOB.inventory_state + +/obj/item/radio/ui_interact(mob/user, datum/tgui/ui, datum/ui_state/state) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "Radio", name) + if(state) + ui.set_state(state) + ui.open() + +/obj/item/radio/ui_data(mob/user) + var/list/data = list() + + data["broadcasting"] = broadcasting + data["listening"] = listening + data["frequency"] = frequency + data["minFrequency"] = freerange ? MIN_FREE_FREQ : MIN_FREQ + data["maxFrequency"] = freerange ? MAX_FREE_FREQ : MAX_FREQ + data["freqlock"] = freqlock + data["channels"] = list() + for(var/channel in channels) + data["channels"][channel] = channels[channel] & FREQ_LISTENING + data["command"] = command + data["useCommand"] = use_command + data["subspace"] = subspace_transmission + data["subspaceSwitchable"] = subspace_switchable + data["headset"] = FALSE + + return data + +/obj/item/radio/ui_act(action, params, datum/tgui/ui) + if(..()) + return + switch(action) + if("frequency") + if(freqlock) + return + var/tune = params["tune"] + var/adjust = text2num(params["adjust"]) + if(adjust) + tune = frequency + adjust * 10 + . = TRUE + else if(text2num(tune) != null) + tune = tune * 10 + . = TRUE + if(.) + set_frequency(sanitize_frequency(tune, freerange)) + if("listen") + listening = !listening + . = TRUE + if("broadcast") + broadcasting = !broadcasting + . = TRUE + if("channel") + var/channel = params["channel"] + if(!(channel in channels)) + return + if(channels[channel] & FREQ_LISTENING) + channels[channel] &= ~FREQ_LISTENING + else + channels[channel] |= FREQ_LISTENING + . = TRUE + if("command") + use_command = !use_command + . = TRUE + if("subspace") + if(subspace_switchable) + subspace_transmission = !subspace_transmission + if(!subspace_transmission) + channels = list() + else + recalculateChannels() + . = TRUE + +/obj/item/radio/talk_into(atom/movable/M, message, channel, list/spans, datum/language/language) + if(!spans) + spans = list(M.speech_span) + if(!language) + language = M.get_selected_language() + INVOKE_ASYNC(src, .proc/talk_into_impl, M, message, channel, spans.Copy(), language) + return ITALICS | REDUCE_RANGE + +/obj/item/radio/proc/talk_into_impl(atom/movable/M, message, channel, list/spans, datum/language/language) + if(!on) + return // the device has to be on + if(!M || !message) + return + if(wires.is_cut(WIRE_TX)) // Permacell and otherwise tampered-with radios + return + if(!M.IsVocal()) + return + + if(use_command) + spans |= SPAN_COMMAND + + /* + Roughly speaking, radios attempt to make a subspace transmission (which + is received, processed, and rebroadcast by the telecomms satellite) and + if that fails, they send a mundane radio transmission. + + Headsets cannot send/receive mundane transmissions, only subspace. + Syndicate radios can hear transmissions on all well-known frequencies. + CentCom radios can hear the CentCom frequency no matter what. + */ + + // From the channel, determine the frequency and get a reference to it. + var/freq + if(channel && channels && channels.len > 0) + if(channel == MODE_DEPARTMENT) + channel = channels[1] + freq = secure_radio_connections[channel] + if (!channels[channel]) // if the channel is turned off, don't broadcast + return + else + freq = frequency + channel = null + + // Nearby active jammers prevent the message from transmitting + var/turf/position = get_turf(src) + for(var/obj/item/jammer/jammer in GLOB.active_jammers) + var/turf/jammer_turf = get_turf(jammer) + if(position.z == jammer_turf.z && (get_dist(position, jammer_turf) <= jammer.range)) + return + + // Determine the identity information which will be attached to the signal. + var/atom/movable/virtualspeaker/speaker = new(null, M, src) + + // Construct the signal + var/datum/signal/subspace/vocal/signal = new(src, freq, speaker, language, message, spans) + + // Independent radios, on the CentCom frequency, reach all independent radios + if (independent && (freq == FREQ_CENTCOM || freq == FREQ_CTF_RED || freq == FREQ_CTF_BLUE)) + signal.data["compression"] = 0 + signal.transmission_method = TRANSMISSION_SUPERSPACE + signal.levels = list(0) // reaches all Z-levels + signal.broadcast() + return + + // All radios make an attempt to use the subspace system first + signal.send_to_receivers() + + // If the radio is subspace-only, that's all it can do + if (subspace_transmission) + return + + // Non-subspace radios will check in a couple of seconds, and if the signal + // was never received, send a mundane broadcast (no headsets). + addtimer(CALLBACK(src, .proc/backup_transmission, signal), 20) + +/obj/item/radio/proc/backup_transmission(datum/signal/subspace/vocal/signal) + var/turf/T = get_turf(src) + if (signal.data["done"] && (T.z in signal.levels)) + return + + // Okay, the signal was never processed, send a mundane broadcast. + signal.data["compression"] = 0 + signal.transmission_method = TRANSMISSION_RADIO + signal.levels = list(T.z) + signal.broadcast() + +/obj/item/radio/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode) + . = ..() + if(radio_freq || !broadcasting || get_dist(src, speaker) > canhear_range) + return + + if(message_mode == MODE_WHISPER || message_mode == MODE_WHISPER_CRIT) + // radios don't pick up whispers very well + raw_message = stars(raw_message) + else if(message_mode == MODE_L_HAND || message_mode == MODE_R_HAND) + // try to avoid being heard double + if (loc == speaker && ismob(speaker)) + var/mob/M = speaker + var/idx = M.get_held_index_of_item(src) + // left hands are odd slots + if (idx && (idx % 2) == (message_mode == MODE_L_HAND)) + return + + talk_into(speaker, raw_message, , spans, language=message_language) + +// Checks if this radio can receive on the given frequency. +/obj/item/radio/proc/can_receive(freq, level) + // deny checks + if (!on || !listening || wires.is_cut(WIRE_RX)) + return FALSE + if (freq == FREQ_SYNDICATE && !syndie) + return FALSE + if (freq == FREQ_CENTCOM) + return independent // hard-ignores the z-level check + if (!(0 in level)) + var/turf/position = get_turf(src) + if(!position || !(position.z in level)) + return FALSE + + // allow checks: are we listening on that frequency? + if (freq == frequency) + return TRUE + for(var/ch_name in channels) + if(channels[ch_name] & FREQ_LISTENING) + //the GLOB.radiochannels list is located in communications.dm + if(GLOB.radiochannels[ch_name] == text2num(freq) || syndie) + return TRUE + return FALSE + + +/obj/item/radio/examine(mob/user) + . = ..() + if (frequency && in_range(src, user)) + . += "It is set to broadcast over the [frequency/10] frequency." + if (unscrewed) + . += "It can be attached and modified." + else + . += "It cannot be modified or attached." + +/obj/item/radio/attackby(obj/item/W, mob/user, params) + add_fingerprint(user) + if(W.tool_behaviour == TOOL_SCREWDRIVER) + unscrewed = !unscrewed + if(unscrewed) + to_chat(user, "The radio can now be attached and modified!") + else + to_chat(user, "The radio can no longer be modified or attached!") + else + return ..() + +/obj/item/radio/emp_act(severity) + . = ..() + if (. & EMP_PROTECT_SELF) + return + emped++ //There's been an EMP; better count it + var/curremp = emped //Remember which EMP this was + if (listening && ismob(loc)) // if the radio is turned on and on someone's person they notice + to_chat(loc, "\The [src] overloads.") + broadcasting = FALSE + listening = FALSE + for (var/ch_name in channels) + channels[ch_name] = 0 + on = FALSE + addtimer(CALLBACK(src, .proc/end_emp_effect, curremp), 200) + +/obj/item/radio/proc/end_emp_effect(curremp) + if(emped != curremp) //Don't fix it if it's been EMP'd again + return FALSE + emped = FALSE + on = TRUE + return TRUE + +/////////////////////////////// +//////////Borg Radios////////// +/////////////////////////////// +//Giving borgs their own radio to have some more room to work with -Sieve + +/obj/item/radio/borg + name = "cyborg radio" + subspace_switchable = TRUE + dog_fashion = null + +/obj/item/radio/borg/Initialize(mapload) + . = ..() + +/obj/item/radio/borg/syndicate + syndie = 1 + keyslot = new /obj/item/encryptionkey/syndicate + +/obj/item/radio/borg/syndicate/Initialize() + . = ..() + set_frequency(FREQ_SYNDICATE) + +/obj/item/radio/borg/attackby(obj/item/W, mob/user, params) + + if(W.tool_behaviour == TOOL_SCREWDRIVER) + if(keyslot) + for(var/ch_name in channels) + SSradio.remove_object(src, GLOB.radiochannels[ch_name]) + secure_radio_connections[ch_name] = null + + + if(keyslot) + var/turf/T = get_turf(user) + if(T) + keyslot.forceMove(T) + keyslot = null + + recalculateChannels() + to_chat(user, "You pop out the encryption key in the radio.") + + else + to_chat(user, "This radio doesn't have any encryption keys!") + + else if(istype(W, /obj/item/encryptionkey/)) + if(keyslot) + to_chat(user, "The radio can't hold another key!") + return + + if(!keyslot) + if(!user.transferItemToLoc(W, src)) + return + keyslot = W + + recalculateChannels() + + +/obj/item/radio/off // Station bounced radios, their only difference is spawning with the speakers off, this was made to help the lag. + listening = 0 // And it's nice to have a subtype too for future features. + dog_fashion = /datum/dog_fashion/back diff --git a/code/game/objects/items/devices/scanners.dm b/code/game/objects/items/devices/scanners.dm index cb070414bf1a..5dee5b85405e 100644 --- a/code/game/objects/items/devices/scanners.dm +++ b/code/game/objects/items/devices/scanners.dm @@ -1,825 +1,825 @@ - -/* - -CONTAINS: -T-RAY -HEALTH ANALYZER -GAS ANALYZER -SLIME SCANNER -NANITE SCANNER -GENE SCANNER - -*/ - -// Describes the two modes of scanning available for health analyzers -#define SCANMODE_HEALTH 0 -#define SCANMODE_CHEMICAL 1 -#define SCANNER_CONDENSED 0 -#define SCANNER_VERBOSE 1 - -/obj/item/t_scanner - name = "\improper T-ray scanner" - desc = "A terahertz-ray emitter and scanner used to detect underfloor objects such as cables and pipes." - custom_price = 150 - icon = 'icons/obj/device.dmi' - icon_state = "t-ray0" - var/on = FALSE - slot_flags = ITEM_SLOT_BELT - w_class = WEIGHT_CLASS_SMALL - item_state = "electronic" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - custom_materials = list(/datum/material/iron=150) - -/obj/item/t_scanner/suicide_act(mob/living/carbon/user) - user.visible_message("[user] begins to emit terahertz-rays into [user.p_their()] brain with [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return TOXLOSS - -/obj/item/t_scanner/proc/toggle_on() - on = !on - icon_state = copytext_char(icon_state, 1, -1) + "[on]" - if(on) - START_PROCESSING(SSobj, src) - else - STOP_PROCESSING(SSobj, src) - -/obj/item/t_scanner/attack_self(mob/user) - toggle_on() - -/obj/item/t_scanner/cyborg_unequip(mob/user) - if(!on) - return - toggle_on() - -/obj/item/t_scanner/process() - if(!on) - STOP_PROCESSING(SSobj, src) - return null - scan() - -/obj/item/t_scanner/proc/scan() - t_ray_scan(loc) - -/proc/t_ray_scan(mob/viewer, flick_time = 8, distance = 3) - if(!ismob(viewer) || !viewer.client) - return - var/list/t_ray_images = list() - for(var/obj/O in orange(distance, viewer) ) - - if(HAS_TRAIT(O, TRAIT_T_RAY_VISIBLE)) - var/image/I = new(loc = get_turf(O)) - var/mutable_appearance/MA = new(O) - MA.alpha = 128 - MA.dir = O.dir - I.appearance = MA - t_ray_images += I - if(t_ray_images.len) - flick_overlay(t_ray_images, list(viewer.client), flick_time) - -/obj/item/healthanalyzer - name = "health analyzer" - icon = 'icons/obj/device.dmi' - icon_state = "health" - item_state = "healthanalyzer" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - desc = "A hand-held body scanner capable of distinguishing vital signs of the subject." - flags_1 = CONDUCT_1 - item_flags = NOBLUDGEON - slot_flags = ITEM_SLOT_BELT - throwforce = 3 - w_class = WEIGHT_CLASS_TINY - throw_speed = 3 - throw_range = 7 - custom_materials = list(/datum/material/iron=200) - var/mode = SCANNER_VERBOSE - var/scanmode = SCANMODE_HEALTH - var/advanced = FALSE - custom_price = 300 - -/obj/item/healthanalyzer/suicide_act(mob/living/carbon/user) - user.visible_message("[user] begins to analyze [user.p_them()]self with [src]! The display shows that [user.p_theyre()] dead!") - return BRUTELOSS - -/obj/item/healthanalyzer/attack_self(mob/user) - scanmode = !scanmode - to_chat(user, "You switch the health analyzer to [scanmode ? "scan chemical contents" : "check physical health"].") - -/obj/item/healthanalyzer/attack(mob/living/M, mob/living/carbon/human/user) - flick("[icon_state]-scan", src) //makes it so that it plays the scan animation upon scanning, including clumsy scanning - - // Clumsiness/brain damage check - if ((HAS_TRAIT(user, TRAIT_CLUMSY) || HAS_TRAIT(user, TRAIT_DUMB)) && prob(50)) - user.visible_message("[user] analyzes the floor's vitals!", \ - "You stupidly try to analyze the floor's vitals!") - to_chat(user, "Analyzing results for The floor:\n\tOverall status: Healthy\ - \nKey: Suffocation/Toxin/Burn/Brute\ - \n\tDamage specifics: 0-0-0-0\ - \nBody temperature: ???") - return - - user.visible_message("[user] analyzes [M]'s vitals.", \ - "You analyze [M]'s vitals.") - - if(scanmode == SCANMODE_HEALTH) - healthscan(user, M, mode, advanced) - else - chemscan(user, M) - - add_fingerprint(user) - - -// Used by the PDA medical scanner too -/proc/healthscan(mob/user, mob/living/M, mode = SCANNER_VERBOSE, advanced = FALSE) - if(isliving(user) && (user.incapacitated() || user.is_blind())) - return - - // the final list of strings to render - var/render_list = list() - - // Damage specifics - var/oxy_loss = M.getOxyLoss() - var/tox_loss = M.getToxLoss() - var/fire_loss = M.getFireLoss() - var/brute_loss = M.getBruteLoss() - var/mob_status = (M.stat == DEAD ? "Deceased" : "[round(M.health/M.maxHealth,0.01)*100]% healthy") - - if(HAS_TRAIT(M, TRAIT_FAKEDEATH) && !advanced) - mob_status = "Deceased" - oxy_loss = max(rand(1, 40), oxy_loss, (300 - (tox_loss + fire_loss + brute_loss))) // Random oxygen loss - - if(ishuman(M)) - var/mob/living/carbon/human/H = M - if(H.undergoing_cardiac_arrest() && H.stat != DEAD) - render_list += "Subject suffering from heart attack: Apply defibrillation or other electric shock immediately!\n" - - //Wasp begin - Borers - if(iscarbon(M)) - var/mob/living/carbon/C = M - if(C.has_brain_worms() && (!C.reagents.has_reagent(/datum/reagent/medicine/spaceacillin) || advanced)) - render_list += "Foreign organism detected in subject's cranium. Recommended treatment: Dosage of sucrose solution and removal of object via surgery.\n" - //Wasp end - - render_list += "Analyzing results for [M]:\nOverall status: [mob_status]\n" - - // Damage descriptions - if(brute_loss > 10) - render_list += "[brute_loss > 50 ? "Severe" : "Minor"] tissue damage detected.\n" - if(fire_loss > 10) - render_list += "[fire_loss > 50 ? "Severe" : "Minor"] burn damage detected.\n" - if(oxy_loss > 10) - render_list += "[oxy_loss > 50 ? "Severe" : "Minor"] oxygen deprivation detected.\n" - if(tox_loss > 10) - render_list += "[tox_loss > 50 ? "Severe" : "Minor"] amount of toxin damage detected.\n" - if(M.getStaminaLoss()) - render_list += "Subject appears to be suffering from fatigue.\n" - if(advanced) - render_list += "Fatigue Level: [M.getStaminaLoss()]%.\n" - if (M.getCloneLoss()) - render_list += "Subject appears to have [M.getCloneLoss() > 30 ? "Severe" : "Minor"] cellular damage.\n" - if(advanced) - render_list += "Cellular Damage Level: [M.getCloneLoss()].\n" - if (!M.getorgan(/obj/item/organ/brain)) - render_list += "Subject lacks a brain.\n" - if(iscarbon(M)) - var/mob/living/carbon/C = M - if(LAZYLEN(C.get_traumas())) - var/list/trauma_text = list() - for(var/datum/brain_trauma/B in C.get_traumas()) - var/trauma_desc = "" - switch(B.resilience) - if(TRAUMA_RESILIENCE_SURGERY) - trauma_desc += "severe " - if(TRAUMA_RESILIENCE_LOBOTOMY) - trauma_desc += "deep-rooted " - if(TRAUMA_RESILIENCE_MAGIC, TRAUMA_RESILIENCE_ABSOLUTE) - trauma_desc += "permanent " - trauma_desc += B.scan_desc - trauma_text += trauma_desc - render_list += "Cerebral traumas detected: subject appears to be suffering from [english_list(trauma_text)].\n" - if(C.roundstart_quirks.len) - render_list += "Subject has the following physiological traits: [C.get_trait_string()].\n" - if(advanced) - render_list += "Brain Activity Level: [(200 - M.getOrganLoss(ORGAN_SLOT_BRAIN))/2]%.\n" - - if (M.radiation) - render_list += "Subject is irradiated.\n" - if(advanced) - render_list += "Radiation Level: [M.radiation]%.\n" - - if(advanced && M.hallucinating()) - render_list += "Subject is hallucinating.\n" - - // Body part damage report - if(iscarbon(M) && mode == SCANNER_VERBOSE) - var/mob/living/carbon/C = M - var/list/damaged = C.get_damaged_bodyparts(1,1) - if(length(damaged)>0 || oxy_loss>0 || tox_loss>0 || fire_loss>0) - var/dmgreport = "General status:\ -
                        [entry][functions]
                        \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - " - - for(var/o in damaged) - var/obj/item/bodypart/org = o //head, left arm, right arm, etc. - dmgreport += "\ - \ - " - dmgreport += "
                        Damage:BruteBurnToxinSuffocation
                        Overall:[CEILING(brute_loss,1)][CEILING(fire_loss,1)][CEILING(tox_loss,1)][CEILING(oxy_loss,1)]
                        [capitalize(org.name)]:[(org.brute_dam > 0) ? "[CEILING(org.brute_dam,1)]" : "0"][(org.burn_dam > 0) ? "[CEILING(org.burn_dam,1)]" : "0"]
                        " - render_list += dmgreport // tables do not need extra linebreak - - //Eyes and ears - if(advanced && iscarbon(M)) - var/mob/living/carbon/C = M - - // Ear status - var/obj/item/organ/ears/ears = C.getorganslot(ORGAN_SLOT_EARS) - var/message = "\nSubject does not have ears." - if(istype(ears)) - message = "" - if(HAS_TRAIT_FROM(C, TRAIT_DEAF, GENETIC_MUTATION)) - message = "\nSubject is genetically deaf." - else if(HAS_TRAIT(C, TRAIT_DEAF)) - message = "\nSubject is deaf." - else - if(ears.damage) - message += "\nSubject has [ears.damage > ears.maxHealth ? "permanent ": "temporary "]hearing damage." - if(ears.deaf) - message += "\nSubject is [ears.damage > ears.maxHealth ? "permanently ": "temporarily "] deaf." - render_list += "Ear status:[message == "" ? "\nHealthy." : message]\n" - - // Eye status - var/obj/item/organ/eyes/eyes = C.getorganslot(ORGAN_SLOT_EYES) - message = "\nSubject does not have eyes." - if(istype(eyes)) - message = "" - if(C.is_blind()) - message += "\nSubject is blind." - if(HAS_TRAIT(C, TRAIT_NEARSIGHT)) - message += "\nSubject is nearsighted." - if(eyes.damage > 30) - message += "\nSubject has severe eye damage." - else if(eyes.damage > 20) - message += "\nSubject has significant eye damage." - else if(eyes.damage) - message += "\nSubject has minor eye damage." - render_list += "Eye status:[message == "" ? "\nHealthy." : message]\n" - - if(ishuman(M)) - var/mob/living/carbon/human/H = M - - // Organ damage - if (H.internal_organs && H.internal_organs.len) - var/render = FALSE - var/toReport = "Organs:\ - \ - \ - [advanced ? "" : ""]\ - " - - for(var/obj/item/organ/organ in H.internal_organs) - var/status = "" - if (organ.organ_flags & ORGAN_FAILING) status = "Non-Functional" - else if (organ.damage > organ.high_threshold) status = "Severely Damaged" - else if (organ.damage > organ.low_threshold) status = "Mildly Damaged" - if (status != "") - render = TRUE - toReport += "\ - [advanced ? "" : ""]\ - " - - if (render) - render_list += toReport + "
                        OrganDmgStatus
                        [organ.name][CEILING(organ.damage,1)][status]
                        " // tables do not need extra linebreak - - //Genetic damage - if(advanced && H.has_dna()) - render_list += "Genetic Stability: [H.dna.stability]%.\n" - - var/list/broken_stuff = list() // Wasp Edit Begin - Adds bone breakage - for(var/obj/item/bodypart/B in H.bodyparts) - if(B.bone_status >= BONE_FLAG_BROKEN) // Checks if bone is broken or splinted - broken_stuff += B.name - if(broken_stuff.len) - render_list += "\tBone fractures detected. Subject's [english_list(broken_stuff)] [broken_stuff.len > 1 ? "require" : "requires"] surgical treatment!\n" // Wasp Edit End - - // Species and body temperature - var/datum/species/S = H.dna.species - var/mutant = H.dna.check_mutation(HULK) \ - || S.mutantlungs != initial(S.mutantlungs) \ - || S.mutantbrain != initial(S.mutantbrain) \ - || S.mutantheart != initial(S.mutantheart) \ - || S.mutanteyes != initial(S.mutanteyes) \ - || S.mutantears != initial(S.mutantears) \ - || S.mutanthands != initial(S.mutanthands) \ - || S.mutanttongue != initial(S.mutanttongue) \ - || S.mutantliver != initial(S.mutantliver) \ - || S.mutantstomach != initial(S.mutantstomach) \ - || S.mutantappendix != initial(S.mutantappendix) \ - || S.flying_species != initial(S.flying_species) - - render_list += "Species: [S.name][mutant ? "-derived mutant" : ""]\n" - render_list += "Body temperature: [round(M.bodytemperature-T0C,0.1)] °C ([round(M.bodytemperature*1.8-459.67,0.1)] °F)\n" - - // Time of death - if(M.tod && (M.stat == DEAD || ((HAS_TRAIT(M, TRAIT_FAKEDEATH)) && !advanced))) - render_list += "Time of Death: [M.tod]\n" - var/tdelta = round(world.time - M.timeofdeath) - render_list += "Subject died [DisplayTimeText(tdelta)] ago.\n" - - for(var/thing in M.diseases) - var/datum/disease/D = thing - if(!(D.visibility_flags & HIDDEN_SCANNER)) - render_list += "Warning: [D.form] detected\n\ -
                        Name: [D.name].\nType: [D.spread_text].\nStage: [D.stage]/[D.max_stages].\nPossible Cure: [D.cure_text]
                        \ -
                        " // divs do not need extra linebreak - - // Blood Level - if(M.has_dna()) - var/mob/living/carbon/C = M - var/blood_id = C.get_blood_id() - if(blood_id) - if(ishuman(C)) - var/mob/living/carbon/human/H = C - if(H.bleed_rate) - render_list += "Subject is bleeding!\n" - var/blood_percent = round((C.blood_volume / BLOOD_VOLUME_NORMAL)*100) - var/blood_type = C.dna.blood_type - if(blood_id != /datum/reagent/blood) // special blood substance - var/datum/reagent/R = GLOB.chemical_reagents_list[blood_id] - blood_type = R ? R.name : blood_id - if(C.blood_volume <= BLOOD_VOLUME_SAFE && C.blood_volume > BLOOD_VOLUME_OKAY) - render_list += "Blood level: LOW [blood_percent] %, [C.blood_volume] cl, type: [blood_type]\n" - else if(C.blood_volume <= BLOOD_VOLUME_OKAY) - render_list += "Blood level: CRITICAL [blood_percent] %, [C.blood_volume] cl, type: [blood_type]\n" - else - render_list += "Blood level: [blood_percent] %, [C.blood_volume] cl, type: [blood_type]\n" - - var/cyberimp_detect - for(var/obj/item/organ/cyberimp/CI in C.internal_organs) - if(CI.status == ORGAN_ROBOTIC && !CI.syndicate_implant) - cyberimp_detect += "[!cyberimp_detect ? "[CI.get_examine_string(user)]" : ", [CI.get_examine_string(user)]"]" - if(cyberimp_detect) - render_list += "Detected cybernetic modifications:\n" - render_list += "[cyberimp_detect]\n" - - SEND_SIGNAL(M, COMSIG_NANITE_SCAN, user, FALSE) - to_chat(user, jointext(render_list, ""), trailing_newline = FALSE) // we handled the last
                        so we don't need handholding - -/proc/chemscan(mob/living/user, mob/living/M) - if(istype(M) && M.reagents) - var/render_list = list() - if(M.reagents.reagent_list.len) - render_list += "Subject contains the following reagents:\n" - for(var/datum/reagent/R in M.reagents.reagent_list) - render_list += "[round(R.volume, 0.001)] units of [R.name][R.overdosed == 1 ? " - OVERDOSING" : "."]\n" - else - render_list += "Subject contains no reagents.\n" - if(M.reagents.addiction_list.len) - render_list += "Subject is addicted to the following reagents:\n" - for(var/datum/reagent/R in M.reagents.addiction_list) - render_list += "[R.name]\n" - else - render_list += "Subject is not addicted to any reagents.\n" - - to_chat(user, jointext(render_list, ""), trailing_newline = FALSE) // we handled the last
                        so we don't need handholding - -/obj/item/healthanalyzer/verb/toggle_mode() - set name = "Switch Verbosity" - set category = "Object" - - if(usr.incapacitated()) - return - - mode = !mode - to_chat(usr, mode == SCANNER_VERBOSE ? "The scanner now shows specific limb damage." : "The scanner no longer shows limb damage.") - -/obj/item/healthanalyzer/advanced - name = "advanced health analyzer" - icon_state = "health_adv" - desc = "A hand-held body scanner able to distinguish vital signs of the subject with high accuracy." - advanced = TRUE - -/obj/item/analyzer - desc = "A hand-held environmental scanner which reports current gas levels. Alt-Click to use the built in barometer function." - name = "analyzer" - custom_price = 100 - icon = 'icons/obj/device.dmi' - icon_state = "analyzer" - item_state = "analyzer" - lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' - w_class = WEIGHT_CLASS_SMALL - flags_1 = CONDUCT_1 - item_flags = NOBLUDGEON - slot_flags = ITEM_SLOT_BELT - throwforce = 0 - throw_speed = 3 - throw_range = 7 - tool_behaviour = TOOL_ANALYZER - custom_materials = list(/datum/material/iron=30, /datum/material/glass=20) - grind_results = list(/datum/reagent/mercury = 5, /datum/reagent/iron = 5, /datum/reagent/silicon = 5) - var/cooldown = FALSE - var/cooldown_time = 250 - var/accuracy // 0 is the best accuracy. - -/obj/item/analyzer/examine(mob/user) - . = ..() - . += "Alt-click [src] to activate the barometer function." - -/obj/item/analyzer/suicide_act(mob/living/carbon/user) - user.visible_message("[user] begins to analyze [user.p_them()]self with [src]! The display shows that [user.p_theyre()] dead!") - return BRUTELOSS - -/obj/item/analyzer/attack_self(mob/user) - add_fingerprint(user) - - if (user.stat || user.is_blind()) - return - - var/turf/location = user.loc - if(!istype(location)) - return - - var/render_list = list() - var/datum/gas_mixture/environment = location.return_air() - var/pressure = environment.return_pressure() - var/total_moles = environment.total_moles() - - render_list += "Results:\ - \nPressure: [round(pressure, 0.01)] kPa\n" - if(total_moles) - var/o2_concentration = environment.get_moles(/datum/gas/oxygen)/total_moles - var/n2_concentration = environment.get_moles(/datum/gas/nitrogen)/total_moles - var/co2_concentration = environment.get_moles(/datum/gas/carbon_dioxide)/total_moles - var/plasma_concentration = environment.get_moles(/datum/gas/plasma)/total_moles - - if(abs(n2_concentration - N2STANDARD) < 20) - to_chat(user, "Nitrogen: [round(n2_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/nitrogen), 0.01)] mol)") - else - to_chat(user, "Nitrogen: [round(n2_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/nitrogen), 0.01)] mol)") - - if(abs(o2_concentration - O2STANDARD) < 2) - to_chat(user, "Oxygen: [round(o2_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/oxygen), 0.01)] mol)") - else - to_chat(user, "Oxygen: [round(o2_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/oxygen), 0.01)] mol)") - - if(co2_concentration > 0.01) - to_chat(user, "CO2: [round(co2_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/carbon_dioxide), 0.01)] mol)") - else - to_chat(user, "CO2: [round(co2_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/carbon_dioxide), 0.01)] mol)") - - if(plasma_concentration > 0.005) - to_chat(user, "Plasma: [round(plasma_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/plasma), 0.01)] mol)") - else - to_chat(user, "Plasma: [round(plasma_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/plasma), 0.01)] mol)") - - for(var/id in environment.get_gases()) - if(id in GLOB.hardcoded_gases) - continue - var/gas_concentration = environment.get_moles(id)/total_moles - to_chat(user, "[GLOB.meta_gas_info[id][META_GAS_NAME]]: [round(gas_concentration*100, 0.01)] % ([round(environment.get_moles(id), 0.01)] mol)") - to_chat(user, "Temperature: [round(environment.return_temperature()-T0C, 0.01)] °C ([round(environment.return_temperature(), 0.01)] K)") - -/obj/item/analyzer/AltClick(mob/user) //Barometer output for measuring when the next storm happens - ..() - - if(user.canUseTopic(src, BE_CLOSE)) - if(cooldown) - to_chat(user, "[src]'s barometer function is preparing itself.") - return - - var/turf/T = get_turf(user) - if(!T) - return - - playsound(src, 'sound/effects/pop.ogg', 100) - var/area/user_area = T.loc - var/datum/weather/ongoing_weather = null - - if(!user_area.outdoors) - to_chat(user, "[src]'s barometer function won't work indoors!") - return - - for(var/V in SSweather.processing) - var/datum/weather/W = V - if(W.barometer_predictable && (T.z in W.impacted_z_levels) && W.area_type == user_area.type && !(W.stage == END_STAGE)) - ongoing_weather = W - break - - if(ongoing_weather) - if((ongoing_weather.stage == MAIN_STAGE) || (ongoing_weather.stage == WIND_DOWN_STAGE)) - to_chat(user, "[src]'s barometer function can't trace anything while the storm is [ongoing_weather.stage == MAIN_STAGE ? "already here!" : "winding down."]") - return - - to_chat(user, "The next [ongoing_weather] will hit in [butchertime(ongoing_weather.next_hit_time - world.time)].") - if(ongoing_weather.aesthetic) - to_chat(user, "[src]'s barometer function says that the next storm will breeze on by.") - else - var/next_hit = SSweather.next_hit_by_zlevel["[T.z]"] - var/fixed = next_hit ? timeleft(next_hit) : -1 - if(fixed < 0) - to_chat(user, "[src]'s barometer function was unable to trace any weather patterns.") - else - to_chat(user, "[src]'s barometer function says a storm will land in approximately [butchertime(fixed)].") - cooldown = TRUE - addtimer(CALLBACK(src,/obj/item/analyzer/proc/ping), cooldown_time) - -/obj/item/analyzer/proc/ping() - if(isliving(loc)) - var/mob/living/L = loc - to_chat(L, "[src]'s barometer function is ready!") - playsound(src, 'sound/machines/click.ogg', 100) - cooldown = FALSE - -/obj/item/analyzer/proc/butchertime(amount) - if(!amount) - return - if(accuracy) - var/inaccurate = round(accuracy*(1/3)) - if(prob(50)) - amount -= inaccurate - if(prob(50)) - amount += inaccurate - return DisplayTimeText(max(1,amount)) - -/proc/atmosanalyzer_scan(mob/user, atom/target, silent=FALSE) - var/mixture = target.return_analyzable_air() - if(!mixture) - return FALSE - - var/icon = target - var/render_list = list() - if(!silent && isliving(user)) - user.visible_message("[user] uses the analyzer on [icon2html(icon, viewers(user))] [target].", "You use the analyzer on [icon2html(icon, user)] [target].") - render_list += "Results of analysis of [icon2html(icon, user)] [target]." - - var/list/airs = islist(mixture) ? mixture : list(mixture) - for(var/g in airs) - if(airs.len > 1) //not a unary gas mixture - render_list += "Node [airs.Find(g)]" - var/datum/gas_mixture/air_contents = g - - var/total_moles = air_contents.total_moles() - var/pressure = air_contents.return_pressure() - var/volume = air_contents.return_volume() //could just do mixture.volume... but safety, I guess? - var/temperature = air_contents.return_temperature() - var/cached_scan_results = air_contents.analyzer_results - - if(total_moles > 0) - render_list += "Moles: [round(total_moles, 0.01)] mol\ - \nVolume: [volume] L\ - \nPressure: [round(pressure,0.01)] kPa" - - for(var/id in air_contents.get_gases()) - var/gas_concentration = air_contents.get_moles(id)/total_moles - to_chat(user, "[GLOB.meta_gas_info[id][META_GAS_NAME]]: [round(gas_concentration*100, 0.01)] % ([round(air_contents.get_moles(id), 0.01)] mol)") - to_chat(user, "Temperature: [round(temperature - T0C,0.01)] °C ([round(temperature, 0.01)] K)") - - else - render_list += airs.len > 1 ? "This node is empty!" : "[target] is empty!" - - if(cached_scan_results && cached_scan_results["fusion"]) //notify the user if a fusion reaction was detected - render_list += "Large amounts of free neutrons detected in the air indicate that a fusion reaction took place.\ - \nInstability of the last fusion reaction: [round(cached_scan_results["fusion"], 0.01)]." - - to_chat(user, jointext(render_list, "\n")) // we let the join apply newlines so we do need handholding - return TRUE - -//slime scanner - -/obj/item/slime_scanner - name = "slime scanner" - desc = "A device that analyzes a slime's internal composition and measures its stats." - icon = 'icons/obj/device.dmi' - icon_state = "adv_spectrometer" - item_state = "analyzer" - lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' - w_class = WEIGHT_CLASS_SMALL - flags_1 = CONDUCT_1 - throwforce = 0 - throw_speed = 3 - throw_range = 7 - custom_materials = list(/datum/material/iron=30, /datum/material/glass=20) - -/obj/item/slime_scanner/attack(mob/living/M, mob/living/user) - if(user.stat || user.is_blind()) - return - if (!isslime(M)) - to_chat(user, "This device can only scan slimes!") - return - var/mob/living/simple_animal/slime/T = M - slime_scan(T, user) - -/proc/slime_scan(mob/living/simple_animal/slime/T, mob/living/user) - var/to_render = "========================\ - \nSlime scan results:\ - \n[T.colour] [T.is_adult ? "adult" : "baby"] slime\ - \nNutrition: [T.nutrition]/[T.get_max_nutrition()]" - if (T.nutrition < T.get_starve_nutrition()) - to_render += "\nWarning: slime is starving!" - else if (T.nutrition < T.get_hunger_nutrition()) - to_render += "\nWarning: slime is hungry" - to_render += "\nElectric change strength: [T.powerlevel]\nHealth: [round(T.health/T.maxHealth,0.01)*100]%" - if (T.slime_mutation[4] == T.colour) - to_render += "\nThis slime does not evolve any further." - else - if (T.slime_mutation[3] == T.slime_mutation[4]) - if (T.slime_mutation[2] == T.slime_mutation[1]) - to_render += "\nPossible mutation: [T.slime_mutation[3]]\ - \nGenetic destability: [T.mutation_chance/2] % chance of mutation on splitting" - else - to_render += "\nPossible mutations: [T.slime_mutation[1]], [T.slime_mutation[2]], [T.slime_mutation[3]] (x2)\ - \nGenetic destability: [T.mutation_chance] % chance of mutation on splitting" - else - to_render += "\nPossible mutations: [T.slime_mutation[1]], [T.slime_mutation[2]], [T.slime_mutation[3]], [T.slime_mutation[4]]\ - \nGenetic destability: [T.mutation_chance] % chance of mutation on splitting" - if (T.cores > 1) - to_render += "\nMultiple cores detected" - to_render += "\nGrowth progress: [T.amount_grown]/[SLIME_EVOLUTION_THRESHOLD]" - if(T.effectmod) - to_render += "\nCore mutation in progress: [T.effectmod]\ - \nProgress in core mutation: [T.applied] / [SLIME_EXTRACT_CROSSING_REQUIRED]" - to_chat(user, to_render + "\n========================") - - -/obj/item/nanite_scanner - name = "nanite scanner" - icon = 'icons/obj/device.dmi' - icon_state = "nanite_scanner" - item_state = "nanite_remote" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - desc = "A hand-held body scanner able to detect nanites and their programming." - flags_1 = CONDUCT_1 - item_flags = NOBLUDGEON - slot_flags = ITEM_SLOT_BELT - throwforce = 3 - w_class = WEIGHT_CLASS_TINY - throw_speed = 3 - throw_range = 7 - custom_materials = list(/datum/material/iron=200) - -/obj/item/nanite_scanner/attack(mob/living/M, mob/living/carbon/human/user) - user.visible_message("[user] analyzes [M]'s nanites.", \ - "You analyze [M]'s nanites.") - - add_fingerprint(user) - - var/response = SEND_SIGNAL(M, COMSIG_NANITE_SCAN, user, TRUE) - if(!response) - to_chat(user, "No nanites detected in the subject.") - -/obj/item/sequence_scanner - name = "genetic sequence scanner" - icon = 'icons/obj/device.dmi' - icon_state = "gene" - item_state = "healthanalyzer" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - desc = "A hand-held scanner for analyzing someones gene sequence on the fly. Hold near a DNA console to update the internal database." - flags_1 = CONDUCT_1 - item_flags = NOBLUDGEON - slot_flags = ITEM_SLOT_BELT - throwforce = 3 - w_class = WEIGHT_CLASS_TINY - throw_speed = 3 - throw_range = 7 - custom_materials = list(/datum/material/iron=200) - var/list/discovered = list() //hit a dna console to update the scanners database - var/list/buffer - var/ready = TRUE - var/cooldown = 200 - -/obj/item/sequence_scanner/attack(mob/living/M, mob/living/carbon/human/user) - add_fingerprint(user) - if (!HAS_TRAIT(M, TRAIT_GENELESS) && !HAS_TRAIT(M, TRAIT_BADDNA)) //no scanning if its a husk or DNA-less Species - user.visible_message("[user] analyzes [M]'s genetic sequence.", \ - "You analyze [M]'s genetic sequence.") - gene_scan(M, user) - - else - user.visible_message("[user] fails to analyze [M]'s genetic sequence.", "[M] has no readable genetic sequence!") - -/obj/item/sequence_scanner/attack_self(mob/user) - display_sequence(user) - -/obj/item/sequence_scanner/attack_self_tk(mob/user) - return - -/obj/item/sequence_scanner/afterattack(obj/O, mob/user, proximity) - . = ..() - if(!istype(O) || !proximity) - return - - if(istype(O, /obj/machinery/computer/scan_consolenew)) - var/obj/machinery/computer/scan_consolenew/C = O - if(C.stored_research) - to_chat(user, "[name] linked to central research database.") - discovered = C.stored_research.discovered_mutations - else - to_chat(user,"No database to update from.") - -/obj/item/sequence_scanner/proc/gene_scan(mob/living/carbon/C, mob/living/user) - if(!iscarbon(C) || !C.has_dna()) - return - buffer = C.dna.mutation_index - to_chat(user, "Subject [C.name]'s DNA sequence has been saved to buffer.") - if(LAZYLEN(buffer)) - for(var/A in buffer) - to_chat(user, "[get_display_name(A)]") - - -/obj/item/sequence_scanner/proc/display_sequence(mob/living/user) - if(!LAZYLEN(buffer) || !ready) - return - var/list/options = list() - for(var/A in buffer) - options += get_display_name(A) - - var/answer = input(user, "Analyze Potential", "Sequence Analyzer") as null|anything in sortList(options) - if(answer && ready && user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - var/sequence - for(var/A in buffer) //this physically hurts but i dont know what anything else short of an assoc list - if(get_display_name(A) == answer) - sequence = buffer[A] - break - - if(sequence) - var/display - for(var/i in 0 to length_char(sequence) / DNA_MUTATION_BLOCKS-1) - if(i) - display += "-" - display += copytext_char(sequence, 1 + i*DNA_MUTATION_BLOCKS, DNA_MUTATION_BLOCKS*(1+i) + 1) - - to_chat(user, "[display]
                        ") - - ready = FALSE - icon_state = "[icon_state]_recharging" - addtimer(CALLBACK(src, .proc/recharge), cooldown, TIMER_UNIQUE) - -/obj/item/sequence_scanner/proc/recharge() - icon_state = initial(icon_state) - ready = TRUE - -/obj/item/sequence_scanner/proc/get_display_name(mutation) - var/datum/mutation/human/HM = GET_INITIALIZED_MUTATION(mutation) - if(!HM) - return "ERROR" - if(mutation in discovered) - return "[HM.name] ([HM.alias])" - else - return HM.alias - -/obj/item/scanner_wand - name = "kiosk scanner wand" - icon = 'icons/obj/device.dmi' - icon_state = "scanner_wand" - item_state = "healthanalyzer" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - desc = "An wand for scanning someone else for a medical analysis. Insert into a kiosk is make the scanned patient the target of a health scan." - force = 0 - throwforce = 0 - w_class = WEIGHT_CLASS_TINY - var/selected_target = null - -/obj/item/scanner_wand/attack(mob/living/M, mob/living/carbon/human/user) - flick("[icon_state]_active", src) //nice little visual flash when scanning someone else. - - if((HAS_TRAIT(user, TRAIT_CLUMSY) || HAS_TRAIT(user, TRAIT_DUMB)) && prob(25)) - user.visible_message("[user] targets himself for scanning.", \ - to_chat(user, "You try scanning [M], before realizing you're holding the scanner backwards. Whoops.")) - selected_target = user - return - - if(!ishuman(M)) - to_chat(user, "You can only scan human-like, non-robotic beings.") - selected_target = null - return - - user.visible_message("[user] targets [M] for scanning.", \ - "You target [M] vitals.") - selected_target = M - return - -/obj/item/scanner_wand/attack_self(mob/user) - to_chat(user, "You clear the scanner's target.") - selected_target = null - -/obj/item/scanner_wand/proc/return_patient() - var/returned_target = selected_target - return returned_target - -#undef SCANMODE_HEALTH -#undef SCANMODE_CHEMICAL -#undef SCANNER_CONDENSED -#undef SCANNER_VERBOSE + +/* + +CONTAINS: +T-RAY +HEALTH ANALYZER +GAS ANALYZER +SLIME SCANNER +NANITE SCANNER +GENE SCANNER + +*/ + +// Describes the two modes of scanning available for health analyzers +#define SCANMODE_HEALTH 0 +#define SCANMODE_CHEMICAL 1 +#define SCANNER_CONDENSED 0 +#define SCANNER_VERBOSE 1 + +/obj/item/t_scanner + name = "\improper T-ray scanner" + desc = "A terahertz-ray emitter and scanner used to detect underfloor objects such as cables and pipes." + custom_price = 150 + icon = 'icons/obj/device.dmi' + icon_state = "t-ray0" + var/on = FALSE + slot_flags = ITEM_SLOT_BELT + w_class = WEIGHT_CLASS_SMALL + item_state = "electronic" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + custom_materials = list(/datum/material/iron=150) + +/obj/item/t_scanner/suicide_act(mob/living/carbon/user) + user.visible_message("[user] begins to emit terahertz-rays into [user.p_their()] brain with [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return TOXLOSS + +/obj/item/t_scanner/proc/toggle_on() + on = !on + icon_state = copytext_char(icon_state, 1, -1) + "[on]" + if(on) + START_PROCESSING(SSobj, src) + else + STOP_PROCESSING(SSobj, src) + +/obj/item/t_scanner/attack_self(mob/user) + toggle_on() + +/obj/item/t_scanner/cyborg_unequip(mob/user) + if(!on) + return + toggle_on() + +/obj/item/t_scanner/process() + if(!on) + STOP_PROCESSING(SSobj, src) + return null + scan() + +/obj/item/t_scanner/proc/scan() + t_ray_scan(loc) + +/proc/t_ray_scan(mob/viewer, flick_time = 8, distance = 3) + if(!ismob(viewer) || !viewer.client) + return + var/list/t_ray_images = list() + for(var/obj/O in orange(distance, viewer) ) + + if(HAS_TRAIT(O, TRAIT_T_RAY_VISIBLE)) + var/image/I = new(loc = get_turf(O)) + var/mutable_appearance/MA = new(O) + MA.alpha = 128 + MA.dir = O.dir + I.appearance = MA + t_ray_images += I + if(t_ray_images.len) + flick_overlay(t_ray_images, list(viewer.client), flick_time) + +/obj/item/healthanalyzer + name = "health analyzer" + icon = 'icons/obj/device.dmi' + icon_state = "health" + item_state = "healthanalyzer" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + desc = "A hand-held body scanner capable of distinguishing vital signs of the subject." + flags_1 = CONDUCT_1 + item_flags = NOBLUDGEON + slot_flags = ITEM_SLOT_BELT + throwforce = 3 + w_class = WEIGHT_CLASS_TINY + throw_speed = 3 + throw_range = 7 + custom_materials = list(/datum/material/iron=200) + var/mode = SCANNER_VERBOSE + var/scanmode = SCANMODE_HEALTH + var/advanced = FALSE + custom_price = 300 + +/obj/item/healthanalyzer/suicide_act(mob/living/carbon/user) + user.visible_message("[user] begins to analyze [user.p_them()]self with [src]! The display shows that [user.p_theyre()] dead!") + return BRUTELOSS + +/obj/item/healthanalyzer/attack_self(mob/user) + scanmode = !scanmode + to_chat(user, "You switch the health analyzer to [scanmode ? "scan chemical contents" : "check physical health"].") + +/obj/item/healthanalyzer/attack(mob/living/M, mob/living/carbon/human/user) + flick("[icon_state]-scan", src) //makes it so that it plays the scan animation upon scanning, including clumsy scanning + + // Clumsiness/brain damage check + if ((HAS_TRAIT(user, TRAIT_CLUMSY) || HAS_TRAIT(user, TRAIT_DUMB)) && prob(50)) + user.visible_message("[user] analyzes the floor's vitals!", \ + "You stupidly try to analyze the floor's vitals!") + to_chat(user, "Analyzing results for The floor:\n\tOverall status: Healthy\ + \nKey: Suffocation/Toxin/Burn/Brute\ + \n\tDamage specifics: 0-0-0-0\ + \nBody temperature: ???") + return + + user.visible_message("[user] analyzes [M]'s vitals.", \ + "You analyze [M]'s vitals.") + + if(scanmode == SCANMODE_HEALTH) + healthscan(user, M, mode, advanced) + else + chemscan(user, M) + + add_fingerprint(user) + + +// Used by the PDA medical scanner too +/proc/healthscan(mob/user, mob/living/M, mode = SCANNER_VERBOSE, advanced = FALSE) + if(isliving(user) && (user.incapacitated() || user.is_blind())) + return + + // the final list of strings to render + var/render_list = list() + + // Damage specifics + var/oxy_loss = M.getOxyLoss() + var/tox_loss = M.getToxLoss() + var/fire_loss = M.getFireLoss() + var/brute_loss = M.getBruteLoss() + var/mob_status = (M.stat == DEAD ? "Deceased" : "[round(M.health/M.maxHealth,0.01)*100]% healthy") + + if(HAS_TRAIT(M, TRAIT_FAKEDEATH) && !advanced) + mob_status = "Deceased" + oxy_loss = max(rand(1, 40), oxy_loss, (300 - (tox_loss + fire_loss + brute_loss))) // Random oxygen loss + + if(ishuman(M)) + var/mob/living/carbon/human/H = M + if(H.undergoing_cardiac_arrest() && H.stat != DEAD) + render_list += "Subject suffering from heart attack: Apply defibrillation or other electric shock immediately!\n" + + //Wasp begin - Borers + if(iscarbon(M)) + var/mob/living/carbon/C = M + if(C.has_brain_worms() && (!C.reagents.has_reagent(/datum/reagent/medicine/spaceacillin) || advanced)) + render_list += "Foreign organism detected in subject's cranium. Recommended treatment: Dosage of sucrose solution and removal of object via surgery.\n" + //Wasp end + + render_list += "Analyzing results for [M]:\nOverall status: [mob_status]\n" + + // Damage descriptions + if(brute_loss > 10) + render_list += "[brute_loss > 50 ? "Severe" : "Minor"] tissue damage detected.\n" + if(fire_loss > 10) + render_list += "[fire_loss > 50 ? "Severe" : "Minor"] burn damage detected.\n" + if(oxy_loss > 10) + render_list += "[oxy_loss > 50 ? "Severe" : "Minor"] oxygen deprivation detected.\n" + if(tox_loss > 10) + render_list += "[tox_loss > 50 ? "Severe" : "Minor"] amount of toxin damage detected.\n" + if(M.getStaminaLoss()) + render_list += "Subject appears to be suffering from fatigue.\n" + if(advanced) + render_list += "Fatigue Level: [M.getStaminaLoss()]%.\n" + if (M.getCloneLoss()) + render_list += "Subject appears to have [M.getCloneLoss() > 30 ? "Severe" : "Minor"] cellular damage.\n" + if(advanced) + render_list += "Cellular Damage Level: [M.getCloneLoss()].\n" + if (!M.getorgan(/obj/item/organ/brain)) + render_list += "Subject lacks a brain.\n" + if(iscarbon(M)) + var/mob/living/carbon/C = M + if(LAZYLEN(C.get_traumas())) + var/list/trauma_text = list() + for(var/datum/brain_trauma/B in C.get_traumas()) + var/trauma_desc = "" + switch(B.resilience) + if(TRAUMA_RESILIENCE_SURGERY) + trauma_desc += "severe " + if(TRAUMA_RESILIENCE_LOBOTOMY) + trauma_desc += "deep-rooted " + if(TRAUMA_RESILIENCE_MAGIC, TRAUMA_RESILIENCE_ABSOLUTE) + trauma_desc += "permanent " + trauma_desc += B.scan_desc + trauma_text += trauma_desc + render_list += "Cerebral traumas detected: subject appears to be suffering from [english_list(trauma_text)].\n" + if(C.roundstart_quirks.len) + render_list += "Subject has the following physiological traits: [C.get_trait_string()].\n" + if(advanced) + render_list += "Brain Activity Level: [(200 - M.getOrganLoss(ORGAN_SLOT_BRAIN))/2]%.\n" + + if (M.radiation) + render_list += "Subject is irradiated.\n" + if(advanced) + render_list += "Radiation Level: [M.radiation]%.\n" + + if(advanced && M.hallucinating()) + render_list += "Subject is hallucinating.\n" + + // Body part damage report + if(iscarbon(M) && mode == SCANNER_VERBOSE) + var/mob/living/carbon/C = M + var/list/damaged = C.get_damaged_bodyparts(1,1) + if(length(damaged)>0 || oxy_loss>0 || tox_loss>0 || fire_loss>0) + var/dmgreport = "General status:\ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + " + + for(var/o in damaged) + var/obj/item/bodypart/org = o //head, left arm, right arm, etc. + dmgreport += "\ + \ + " + dmgreport += "
                        Damage:BruteBurnToxinSuffocation
                        Overall:[CEILING(brute_loss,1)][CEILING(fire_loss,1)][CEILING(tox_loss,1)][CEILING(oxy_loss,1)]
                        [capitalize(org.name)]:[(org.brute_dam > 0) ? "[CEILING(org.brute_dam,1)]" : "0"][(org.burn_dam > 0) ? "[CEILING(org.burn_dam,1)]" : "0"]
                        " + render_list += dmgreport // tables do not need extra linebreak + + //Eyes and ears + if(advanced && iscarbon(M)) + var/mob/living/carbon/C = M + + // Ear status + var/obj/item/organ/ears/ears = C.getorganslot(ORGAN_SLOT_EARS) + var/message = "\nSubject does not have ears." + if(istype(ears)) + message = "" + if(HAS_TRAIT_FROM(C, TRAIT_DEAF, GENETIC_MUTATION)) + message = "\nSubject is genetically deaf." + else if(HAS_TRAIT(C, TRAIT_DEAF)) + message = "\nSubject is deaf." + else + if(ears.damage) + message += "\nSubject has [ears.damage > ears.maxHealth ? "permanent ": "temporary "]hearing damage." + if(ears.deaf) + message += "\nSubject is [ears.damage > ears.maxHealth ? "permanently ": "temporarily "] deaf." + render_list += "Ear status:[message == "" ? "\nHealthy." : message]\n" + + // Eye status + var/obj/item/organ/eyes/eyes = C.getorganslot(ORGAN_SLOT_EYES) + message = "\nSubject does not have eyes." + if(istype(eyes)) + message = "" + if(C.is_blind()) + message += "\nSubject is blind." + if(HAS_TRAIT(C, TRAIT_NEARSIGHT)) + message += "\nSubject is nearsighted." + if(eyes.damage > 30) + message += "\nSubject has severe eye damage." + else if(eyes.damage > 20) + message += "\nSubject has significant eye damage." + else if(eyes.damage) + message += "\nSubject has minor eye damage." + render_list += "Eye status:[message == "" ? "\nHealthy." : message]\n" + + if(ishuman(M)) + var/mob/living/carbon/human/H = M + + // Organ damage + if (H.internal_organs && H.internal_organs.len) + var/render = FALSE + var/toReport = "Organs:\ + \ + \ + [advanced ? "" : ""]\ + " + + for(var/obj/item/organ/organ in H.internal_organs) + var/status = "" + if (organ.organ_flags & ORGAN_FAILING) status = "Non-Functional" + else if (organ.damage > organ.high_threshold) status = "Severely Damaged" + else if (organ.damage > organ.low_threshold) status = "Mildly Damaged" + if (status != "") + render = TRUE + toReport += "\ + [advanced ? "" : ""]\ + " + + if (render) + render_list += toReport + "
                        OrganDmgStatus
                        [organ.name][CEILING(organ.damage,1)][status]
                        " // tables do not need extra linebreak + + //Genetic damage + if(advanced && H.has_dna()) + render_list += "Genetic Stability: [H.dna.stability]%.\n" + + var/list/broken_stuff = list() // Wasp Edit Begin - Adds bone breakage + for(var/obj/item/bodypart/B in H.bodyparts) + if(B.bone_status >= BONE_FLAG_BROKEN) // Checks if bone is broken or splinted + broken_stuff += B.name + if(broken_stuff.len) + render_list += "\tBone fractures detected. Subject's [english_list(broken_stuff)] [broken_stuff.len > 1 ? "require" : "requires"] surgical treatment!\n" // Wasp Edit End + + // Species and body temperature + var/datum/species/S = H.dna.species + var/mutant = H.dna.check_mutation(HULK) \ + || S.mutantlungs != initial(S.mutantlungs) \ + || S.mutantbrain != initial(S.mutantbrain) \ + || S.mutantheart != initial(S.mutantheart) \ + || S.mutanteyes != initial(S.mutanteyes) \ + || S.mutantears != initial(S.mutantears) \ + || S.mutanthands != initial(S.mutanthands) \ + || S.mutanttongue != initial(S.mutanttongue) \ + || S.mutantliver != initial(S.mutantliver) \ + || S.mutantstomach != initial(S.mutantstomach) \ + || S.mutantappendix != initial(S.mutantappendix) \ + || S.flying_species != initial(S.flying_species) + + render_list += "Species: [S.name][mutant ? "-derived mutant" : ""]\n" + render_list += "Body temperature: [round(M.bodytemperature-T0C,0.1)] °C ([round(M.bodytemperature*1.8-459.67,0.1)] °F)\n" + + // Time of death + if(M.tod && (M.stat == DEAD || ((HAS_TRAIT(M, TRAIT_FAKEDEATH)) && !advanced))) + render_list += "Time of Death: [M.tod]\n" + var/tdelta = round(world.time - M.timeofdeath) + render_list += "Subject died [DisplayTimeText(tdelta)] ago.\n" + + for(var/thing in M.diseases) + var/datum/disease/D = thing + if(!(D.visibility_flags & HIDDEN_SCANNER)) + render_list += "Warning: [D.form] detected\n\ +
                        Name: [D.name].\nType: [D.spread_text].\nStage: [D.stage]/[D.max_stages].\nPossible Cure: [D.cure_text]
                        \ +
                        " // divs do not need extra linebreak + + // Blood Level + if(M.has_dna()) + var/mob/living/carbon/C = M + var/blood_id = C.get_blood_id() + if(blood_id) + if(ishuman(C)) + var/mob/living/carbon/human/H = C + if(H.bleed_rate) + render_list += "Subject is bleeding!\n" + var/blood_percent = round((C.blood_volume / BLOOD_VOLUME_NORMAL)*100) + var/blood_type = C.dna.blood_type + if(blood_id != /datum/reagent/blood) // special blood substance + var/datum/reagent/R = GLOB.chemical_reagents_list[blood_id] + blood_type = R ? R.name : blood_id + if(C.blood_volume <= BLOOD_VOLUME_SAFE && C.blood_volume > BLOOD_VOLUME_OKAY) + render_list += "Blood level: LOW [blood_percent] %, [C.blood_volume] cl, type: [blood_type]\n" + else if(C.blood_volume <= BLOOD_VOLUME_OKAY) + render_list += "Blood level: CRITICAL [blood_percent] %, [C.blood_volume] cl, type: [blood_type]\n" + else + render_list += "Blood level: [blood_percent] %, [C.blood_volume] cl, type: [blood_type]\n" + + var/cyberimp_detect + for(var/obj/item/organ/cyberimp/CI in C.internal_organs) + if(CI.status == ORGAN_ROBOTIC && !CI.syndicate_implant) + cyberimp_detect += "[!cyberimp_detect ? "[CI.get_examine_string(user)]" : ", [CI.get_examine_string(user)]"]" + if(cyberimp_detect) + render_list += "Detected cybernetic modifications:\n" + render_list += "[cyberimp_detect]\n" + + SEND_SIGNAL(M, COMSIG_NANITE_SCAN, user, FALSE) + to_chat(user, jointext(render_list, ""), trailing_newline = FALSE) // we handled the last
                        so we don't need handholding + +/proc/chemscan(mob/living/user, mob/living/M) + if(istype(M) && M.reagents) + var/render_list = list() + if(M.reagents.reagent_list.len) + render_list += "Subject contains the following reagents:\n" + for(var/datum/reagent/R in M.reagents.reagent_list) + render_list += "[round(R.volume, 0.001)] units of [R.name][R.overdosed == 1 ? " - OVERDOSING" : ".
                        "]\n" + else + render_list += "Subject contains no reagents.\n" + if(M.reagents.addiction_list.len) + render_list += "Subject is addicted to the following reagents:\n" + for(var/datum/reagent/R in M.reagents.addiction_list) + render_list += "[R.name]\n" + else + render_list += "Subject is not addicted to any reagents.\n" + + to_chat(user, jointext(render_list, ""), trailing_newline = FALSE) // we handled the last
                        so we don't need handholding + +/obj/item/healthanalyzer/verb/toggle_mode() + set name = "Switch Verbosity" + set category = "Object" + + if(usr.incapacitated()) + return + + mode = !mode + to_chat(usr, mode == SCANNER_VERBOSE ? "The scanner now shows specific limb damage." : "The scanner no longer shows limb damage.") + +/obj/item/healthanalyzer/advanced + name = "advanced health analyzer" + icon_state = "health_adv" + desc = "A hand-held body scanner able to distinguish vital signs of the subject with high accuracy." + advanced = TRUE + +/obj/item/analyzer + desc = "A hand-held environmental scanner which reports current gas levels. Alt-Click to use the built in barometer function." + name = "analyzer" + custom_price = 100 + icon = 'icons/obj/device.dmi' + icon_state = "analyzer" + item_state = "analyzer" + lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' + w_class = WEIGHT_CLASS_SMALL + flags_1 = CONDUCT_1 + item_flags = NOBLUDGEON + slot_flags = ITEM_SLOT_BELT + throwforce = 0 + throw_speed = 3 + throw_range = 7 + tool_behaviour = TOOL_ANALYZER + custom_materials = list(/datum/material/iron=30, /datum/material/glass=20) + grind_results = list(/datum/reagent/mercury = 5, /datum/reagent/iron = 5, /datum/reagent/silicon = 5) + var/cooldown = FALSE + var/cooldown_time = 250 + var/accuracy // 0 is the best accuracy. + +/obj/item/analyzer/examine(mob/user) + . = ..() + . += "Alt-click [src] to activate the barometer function." + +/obj/item/analyzer/suicide_act(mob/living/carbon/user) + user.visible_message("[user] begins to analyze [user.p_them()]self with [src]! The display shows that [user.p_theyre()] dead!") + return BRUTELOSS + +/obj/item/analyzer/attack_self(mob/user) + add_fingerprint(user) + + if (user.stat || user.is_blind()) + return + + var/turf/location = user.loc + if(!istype(location)) + return + + var/render_list = list() + var/datum/gas_mixture/environment = location.return_air() + var/pressure = environment.return_pressure() + var/total_moles = environment.total_moles() + + render_list += "Results:\ + \nPressure: [round(pressure, 0.01)] kPa\n" + if(total_moles) + var/o2_concentration = environment.get_moles(/datum/gas/oxygen)/total_moles + var/n2_concentration = environment.get_moles(/datum/gas/nitrogen)/total_moles + var/co2_concentration = environment.get_moles(/datum/gas/carbon_dioxide)/total_moles + var/plasma_concentration = environment.get_moles(/datum/gas/plasma)/total_moles + + if(abs(n2_concentration - N2STANDARD) < 20) + to_chat(user, "Nitrogen: [round(n2_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/nitrogen), 0.01)] mol)") + else + to_chat(user, "Nitrogen: [round(n2_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/nitrogen), 0.01)] mol)") + + if(abs(o2_concentration - O2STANDARD) < 2) + to_chat(user, "Oxygen: [round(o2_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/oxygen), 0.01)] mol)") + else + to_chat(user, "Oxygen: [round(o2_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/oxygen), 0.01)] mol)") + + if(co2_concentration > 0.01) + to_chat(user, "CO2: [round(co2_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/carbon_dioxide), 0.01)] mol)") + else + to_chat(user, "CO2: [round(co2_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/carbon_dioxide), 0.01)] mol)") + + if(plasma_concentration > 0.005) + to_chat(user, "Plasma: [round(plasma_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/plasma), 0.01)] mol)") + else + to_chat(user, "Plasma: [round(plasma_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/plasma), 0.01)] mol)") + + for(var/id in environment.get_gases()) + if(id in GLOB.hardcoded_gases) + continue + var/gas_concentration = environment.get_moles(id)/total_moles + to_chat(user, "[GLOB.meta_gas_info[id][META_GAS_NAME]]: [round(gas_concentration*100, 0.01)] % ([round(environment.get_moles(id), 0.01)] mol)") + to_chat(user, "Temperature: [round(environment.return_temperature()-T0C, 0.01)] °C ([round(environment.return_temperature(), 0.01)] K)") + +/obj/item/analyzer/AltClick(mob/user) //Barometer output for measuring when the next storm happens + ..() + + if(user.canUseTopic(src, BE_CLOSE)) + if(cooldown) + to_chat(user, "[src]'s barometer function is preparing itself.") + return + + var/turf/T = get_turf(user) + if(!T) + return + + playsound(src, 'sound/effects/pop.ogg', 100) + var/area/user_area = T.loc + var/datum/weather/ongoing_weather = null + + if(!user_area.outdoors) + to_chat(user, "[src]'s barometer function won't work indoors!") + return + + for(var/V in SSweather.processing) + var/datum/weather/W = V + if(W.barometer_predictable && (T.z in W.impacted_z_levels) && W.area_type == user_area.type && !(W.stage == END_STAGE)) + ongoing_weather = W + break + + if(ongoing_weather) + if((ongoing_weather.stage == MAIN_STAGE) || (ongoing_weather.stage == WIND_DOWN_STAGE)) + to_chat(user, "[src]'s barometer function can't trace anything while the storm is [ongoing_weather.stage == MAIN_STAGE ? "already here!" : "winding down."]") + return + + to_chat(user, "The next [ongoing_weather] will hit in [butchertime(ongoing_weather.next_hit_time - world.time)].") + if(ongoing_weather.aesthetic) + to_chat(user, "[src]'s barometer function says that the next storm will breeze on by.") + else + var/next_hit = SSweather.next_hit_by_zlevel["[T.z]"] + var/fixed = next_hit ? timeleft(next_hit) : -1 + if(fixed < 0) + to_chat(user, "[src]'s barometer function was unable to trace any weather patterns.") + else + to_chat(user, "[src]'s barometer function says a storm will land in approximately [butchertime(fixed)].") + cooldown = TRUE + addtimer(CALLBACK(src,/obj/item/analyzer/proc/ping), cooldown_time) + +/obj/item/analyzer/proc/ping() + if(isliving(loc)) + var/mob/living/L = loc + to_chat(L, "[src]'s barometer function is ready!") + playsound(src, 'sound/machines/click.ogg', 100) + cooldown = FALSE + +/obj/item/analyzer/proc/butchertime(amount) + if(!amount) + return + if(accuracy) + var/inaccurate = round(accuracy*(1/3)) + if(prob(50)) + amount -= inaccurate + if(prob(50)) + amount += inaccurate + return DisplayTimeText(max(1,amount)) + +/proc/atmosanalyzer_scan(mob/user, atom/target, silent=FALSE) + var/mixture = target.return_analyzable_air() + if(!mixture) + return FALSE + + var/icon = target + var/render_list = list() + if(!silent && isliving(user)) + user.visible_message("[user] uses the analyzer on [icon2html(icon, viewers(user))] [target].", "You use the analyzer on [icon2html(icon, user)] [target].") + render_list += "Results of analysis of [icon2html(icon, user)] [target]." + + var/list/airs = islist(mixture) ? mixture : list(mixture) + for(var/g in airs) + if(airs.len > 1) //not a unary gas mixture + render_list += "Node [airs.Find(g)]" + var/datum/gas_mixture/air_contents = g + + var/total_moles = air_contents.total_moles() + var/pressure = air_contents.return_pressure() + var/volume = air_contents.return_volume() //could just do mixture.volume... but safety, I guess? + var/temperature = air_contents.return_temperature() + var/cached_scan_results = air_contents.analyzer_results + + if(total_moles > 0) + render_list += "Moles: [round(total_moles, 0.01)] mol\ + \nVolume: [volume] L\ + \nPressure: [round(pressure,0.01)] kPa" + + for(var/id in air_contents.get_gases()) + var/gas_concentration = air_contents.get_moles(id)/total_moles + to_chat(user, "[GLOB.meta_gas_info[id][META_GAS_NAME]]: [round(gas_concentration*100, 0.01)] % ([round(air_contents.get_moles(id), 0.01)] mol)") + to_chat(user, "Temperature: [round(temperature - T0C,0.01)] °C ([round(temperature, 0.01)] K)") + + else + render_list += airs.len > 1 ? "This node is empty!" : "[target] is empty!" + + if(cached_scan_results && cached_scan_results["fusion"]) //notify the user if a fusion reaction was detected + render_list += "Large amounts of free neutrons detected in the air indicate that a fusion reaction took place.\ + \nInstability of the last fusion reaction: [round(cached_scan_results["fusion"], 0.01)]." + + to_chat(user, jointext(render_list, "\n")) // we let the join apply newlines so we do need handholding + return TRUE + +//slime scanner + +/obj/item/slime_scanner + name = "slime scanner" + desc = "A device that analyzes a slime's internal composition and measures its stats." + icon = 'icons/obj/device.dmi' + icon_state = "adv_spectrometer" + item_state = "analyzer" + lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' + w_class = WEIGHT_CLASS_SMALL + flags_1 = CONDUCT_1 + throwforce = 0 + throw_speed = 3 + throw_range = 7 + custom_materials = list(/datum/material/iron=30, /datum/material/glass=20) + +/obj/item/slime_scanner/attack(mob/living/M, mob/living/user) + if(user.stat || user.is_blind()) + return + if (!isslime(M)) + to_chat(user, "This device can only scan slimes!") + return + var/mob/living/simple_animal/slime/T = M + slime_scan(T, user) + +/proc/slime_scan(mob/living/simple_animal/slime/T, mob/living/user) + var/to_render = "========================\ + \nSlime scan results:\ + \n[T.colour] [T.is_adult ? "adult" : "baby"] slime\ + \nNutrition: [T.nutrition]/[T.get_max_nutrition()]" + if (T.nutrition < T.get_starve_nutrition()) + to_render += "\nWarning: slime is starving!" + else if (T.nutrition < T.get_hunger_nutrition()) + to_render += "\nWarning: slime is hungry" + to_render += "\nElectric change strength: [T.powerlevel]\nHealth: [round(T.health/T.maxHealth,0.01)*100]%" + if (T.slime_mutation[4] == T.colour) + to_render += "\nThis slime does not evolve any further." + else + if (T.slime_mutation[3] == T.slime_mutation[4]) + if (T.slime_mutation[2] == T.slime_mutation[1]) + to_render += "\nPossible mutation: [T.slime_mutation[3]]\ + \nGenetic destability: [T.mutation_chance/2] % chance of mutation on splitting" + else + to_render += "\nPossible mutations: [T.slime_mutation[1]], [T.slime_mutation[2]], [T.slime_mutation[3]] (x2)\ + \nGenetic destability: [T.mutation_chance] % chance of mutation on splitting" + else + to_render += "\nPossible mutations: [T.slime_mutation[1]], [T.slime_mutation[2]], [T.slime_mutation[3]], [T.slime_mutation[4]]\ + \nGenetic destability: [T.mutation_chance] % chance of mutation on splitting" + if (T.cores > 1) + to_render += "\nMultiple cores detected" + to_render += "\nGrowth progress: [T.amount_grown]/[SLIME_EVOLUTION_THRESHOLD]" + if(T.effectmod) + to_render += "\nCore mutation in progress: [T.effectmod]\ + \nProgress in core mutation: [T.applied] / [SLIME_EXTRACT_CROSSING_REQUIRED]" + to_chat(user, to_render + "\n========================") + + +/obj/item/nanite_scanner + name = "nanite scanner" + icon = 'icons/obj/device.dmi' + icon_state = "nanite_scanner" + item_state = "nanite_remote" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + desc = "A hand-held body scanner able to detect nanites and their programming." + flags_1 = CONDUCT_1 + item_flags = NOBLUDGEON + slot_flags = ITEM_SLOT_BELT + throwforce = 3 + w_class = WEIGHT_CLASS_TINY + throw_speed = 3 + throw_range = 7 + custom_materials = list(/datum/material/iron=200) + +/obj/item/nanite_scanner/attack(mob/living/M, mob/living/carbon/human/user) + user.visible_message("[user] analyzes [M]'s nanites.", \ + "You analyze [M]'s nanites.") + + add_fingerprint(user) + + var/response = SEND_SIGNAL(M, COMSIG_NANITE_SCAN, user, TRUE) + if(!response) + to_chat(user, "No nanites detected in the subject.") + +/obj/item/sequence_scanner + name = "genetic sequence scanner" + icon = 'icons/obj/device.dmi' + icon_state = "gene" + item_state = "healthanalyzer" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + desc = "A hand-held scanner for analyzing someones gene sequence on the fly. Hold near a DNA console to update the internal database." + flags_1 = CONDUCT_1 + item_flags = NOBLUDGEON + slot_flags = ITEM_SLOT_BELT + throwforce = 3 + w_class = WEIGHT_CLASS_TINY + throw_speed = 3 + throw_range = 7 + custom_materials = list(/datum/material/iron=200) + var/list/discovered = list() //hit a dna console to update the scanners database + var/list/buffer + var/ready = TRUE + var/cooldown = 200 + +/obj/item/sequence_scanner/attack(mob/living/M, mob/living/carbon/human/user) + add_fingerprint(user) + if (!HAS_TRAIT(M, TRAIT_GENELESS) && !HAS_TRAIT(M, TRAIT_BADDNA)) //no scanning if its a husk or DNA-less Species + user.visible_message("[user] analyzes [M]'s genetic sequence.", \ + "You analyze [M]'s genetic sequence.") + gene_scan(M, user) + + else + user.visible_message("[user] fails to analyze [M]'s genetic sequence.", "[M] has no readable genetic sequence!") + +/obj/item/sequence_scanner/attack_self(mob/user) + display_sequence(user) + +/obj/item/sequence_scanner/attack_self_tk(mob/user) + return + +/obj/item/sequence_scanner/afterattack(obj/O, mob/user, proximity) + . = ..() + if(!istype(O) || !proximity) + return + + if(istype(O, /obj/machinery/computer/scan_consolenew)) + var/obj/machinery/computer/scan_consolenew/C = O + if(C.stored_research) + to_chat(user, "[name] linked to central research database.") + discovered = C.stored_research.discovered_mutations + else + to_chat(user,"No database to update from.") + +/obj/item/sequence_scanner/proc/gene_scan(mob/living/carbon/C, mob/living/user) + if(!iscarbon(C) || !C.has_dna()) + return + buffer = C.dna.mutation_index + to_chat(user, "Subject [C.name]'s DNA sequence has been saved to buffer.") + if(LAZYLEN(buffer)) + for(var/A in buffer) + to_chat(user, "[get_display_name(A)]") + + +/obj/item/sequence_scanner/proc/display_sequence(mob/living/user) + if(!LAZYLEN(buffer) || !ready) + return + var/list/options = list() + for(var/A in buffer) + options += get_display_name(A) + + var/answer = input(user, "Analyze Potential", "Sequence Analyzer") as null|anything in sortList(options) + if(answer && ready && user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + var/sequence + for(var/A in buffer) //this physically hurts but i dont know what anything else short of an assoc list + if(get_display_name(A) == answer) + sequence = buffer[A] + break + + if(sequence) + var/display + for(var/i in 0 to length_char(sequence) / DNA_MUTATION_BLOCKS-1) + if(i) + display += "-" + display += copytext_char(sequence, 1 + i*DNA_MUTATION_BLOCKS, DNA_MUTATION_BLOCKS*(1+i) + 1) + + to_chat(user, "[display]
                        ") + + ready = FALSE + icon_state = "[icon_state]_recharging" + addtimer(CALLBACK(src, .proc/recharge), cooldown, TIMER_UNIQUE) + +/obj/item/sequence_scanner/proc/recharge() + icon_state = initial(icon_state) + ready = TRUE + +/obj/item/sequence_scanner/proc/get_display_name(mutation) + var/datum/mutation/human/HM = GET_INITIALIZED_MUTATION(mutation) + if(!HM) + return "ERROR" + if(mutation in discovered) + return "[HM.name] ([HM.alias])" + else + return HM.alias + +/obj/item/scanner_wand + name = "kiosk scanner wand" + icon = 'icons/obj/device.dmi' + icon_state = "scanner_wand" + item_state = "healthanalyzer" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + desc = "An wand for scanning someone else for a medical analysis. Insert into a kiosk is make the scanned patient the target of a health scan." + force = 0 + throwforce = 0 + w_class = WEIGHT_CLASS_TINY + var/selected_target = null + +/obj/item/scanner_wand/attack(mob/living/M, mob/living/carbon/human/user) + flick("[icon_state]_active", src) //nice little visual flash when scanning someone else. + + if((HAS_TRAIT(user, TRAIT_CLUMSY) || HAS_TRAIT(user, TRAIT_DUMB)) && prob(25)) + user.visible_message("[user] targets himself for scanning.", \ + to_chat(user, "You try scanning [M], before realizing you're holding the scanner backwards. Whoops.")) + selected_target = user + return + + if(!ishuman(M)) + to_chat(user, "You can only scan human-like, non-robotic beings.") + selected_target = null + return + + user.visible_message("[user] targets [M] for scanning.", \ + "You target [M] vitals.") + selected_target = M + return + +/obj/item/scanner_wand/attack_self(mob/user) + to_chat(user, "You clear the scanner's target.") + selected_target = null + +/obj/item/scanner_wand/proc/return_patient() + var/returned_target = selected_target + return returned_target + +#undef SCANMODE_HEALTH +#undef SCANMODE_CHEMICAL +#undef SCANNER_CONDENSED +#undef SCANNER_VERBOSE diff --git a/code/game/objects/items/devices/taperecorder.dm b/code/game/objects/items/devices/taperecorder.dm index 69541305d1fb..4d51de0b89b5 100644 --- a/code/game/objects/items/devices/taperecorder.dm +++ b/code/game/objects/items/devices/taperecorder.dm @@ -1,286 +1,286 @@ -/obj/item/taperecorder - name = "universal recorder" - desc = "A device that can record to cassette tapes, and play them. It automatically translates the content in playback." - icon = 'icons/obj/device.dmi' - icon_state = "taperecorder_empty" - item_state = "analyzer" - lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' - w_class = WEIGHT_CLASS_SMALL - flags_1 = HEAR_1 - slot_flags = ITEM_SLOT_BELT - custom_materials = list(/datum/material/iron=60, /datum/material/glass=30) - force = 2 - throwforce = 0 - var/recording = 0 - var/playing = 0 - var/playsleepseconds = 0 - var/obj/item/tape/mytape - var/starting_tape_type = /obj/item/tape/random - var/open_panel = 0 - var/canprint = 1 - - -/obj/item/taperecorder/Initialize(mapload) - . = ..() - if(starting_tape_type) - mytape = new starting_tape_type(src) - update_icon() - - -/obj/item/taperecorder/examine(mob/user) - . = ..() - . += "The wire panel is [open_panel ? "opened" : "closed"]." - - -/obj/item/taperecorder/attackby(obj/item/I, mob/user, params) - if(!mytape && istype(I, /obj/item/tape)) - if(!user.transferItemToLoc(I,src)) - return - mytape = I - to_chat(user, "You insert [I] into [src].") - update_icon() - - -/obj/item/taperecorder/proc/eject(mob/user) - if(mytape) - to_chat(user, "You remove [mytape] from [src].") - stop() - user.put_in_hands(mytape) - mytape = null - update_icon() - -/obj/item/taperecorder/fire_act(exposed_temperature, exposed_volume) - mytape.ruin() //Fires destroy the tape - ..() - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/item/taperecorder/attack_hand(mob/user) - if(loc != user || !mytape || !user.is_holding(src)) - return ..() - eject(user) - -/obj/item/taperecorder/proc/can_use(mob/user) - if(user && ismob(user)) - if(!user.incapacitated()) - return TRUE - return FALSE - - -/obj/item/taperecorder/verb/ejectverb() - set name = "Eject Tape" - set category = "Object" - - if(!can_use(usr)) - return - if(!mytape) - return - - eject(usr) - - -/obj/item/taperecorder/update_icon_state() - if(!mytape) - icon_state = "taperecorder_empty" - else if(recording) - icon_state = "taperecorder_recording" - else if(playing) - icon_state = "taperecorder_playing" - else - icon_state = "taperecorder_idle" - - -/obj/item/taperecorder/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, spans, message_mode) - . = ..() - if(mytape && recording) - mytape.timestamp += mytape.used_capacity - mytape.storedinfo += "\[[time2text(mytape.used_capacity * 10,"mm:ss")]\] [message]" - -/obj/item/taperecorder/verb/record() - set name = "Start Recording" - set category = "Object" - - if(!can_use(usr)) - return - if(!mytape || mytape.ruined) - return - if(recording) - return - if(playing) - return - - if(mytape.used_capacity < mytape.max_capacity) - to_chat(usr, "Recording started.") - recording = 1 - update_icon() - mytape.timestamp += mytape.used_capacity - mytape.storedinfo += "\[[time2text(mytape.used_capacity * 10,"mm:ss")]\] Recording started." - var/used = mytape.used_capacity //to stop runtimes when you eject the tape - var/max = mytape.max_capacity - while(recording && used < max) - mytape.used_capacity++ - used++ - sleep(10) - recording = 0 - update_icon() - else - to_chat(usr, "The tape is full.") - - -/obj/item/taperecorder/verb/stop() - set name = "Stop" - set category = "Object" - - if(!can_use(usr)) - return - - if(recording) - recording = 0 - mytape.timestamp += mytape.used_capacity - mytape.storedinfo += "\[[time2text(mytape.used_capacity * 10,"mm:ss")]\] Recording stopped." - to_chat(usr, "Recording stopped.") - return - else if(playing) - playing = 0 - var/turf/T = get_turf(src) - T.visible_message("Tape Recorder: Playback stopped.") - update_icon() - - -/obj/item/taperecorder/verb/play() - set name = "Play Tape" - set category = "Object" - - if(!can_use(usr)) - return - if(!mytape || mytape.ruined) - return - if(recording) - return - if(playing) - return - - playing = 1 - update_icon() - to_chat(usr, "Playing started.") - var/used = mytape.used_capacity //to stop runtimes when you eject the tape - var/max = mytape.max_capacity - for(var/i = 1, used < max, sleep(10 * playsleepseconds)) - if(!mytape) - break - if(playing == 0) - break - if(mytape.storedinfo.len < i) - break - say(mytape.storedinfo[i]) - if(mytape.storedinfo.len < i + 1) - playsleepseconds = 1 - sleep(10) - say("End of recording.") - else - playsleepseconds = mytape.timestamp[i + 1] - mytape.timestamp[i] - if(playsleepseconds > 14) - sleep(10) - say("Skipping [playsleepseconds] seconds of silence") - playsleepseconds = 1 - i++ - - playing = 0 - update_icon() - - -/obj/item/taperecorder/attack_self(mob/user) - if(!mytape || mytape.ruined) - return - if(recording) - stop() - else - record() - - -/obj/item/taperecorder/verb/print_transcript() - set name = "Print Transcript" - set category = "Object" - - if(!can_use(usr)) - return - if(!mytape) - return - if(!canprint) - to_chat(usr, "The recorder can't print that fast!") - return - if(recording || playing) - return - - to_chat(usr, "Transcript printed.") - var/obj/item/paper/P = new /obj/item/paper(get_turf(src)) - var/t1 = "Transcript:

                        " - for(var/i = 1, mytape.storedinfo.len >= i, i++) - t1 += "[mytape.storedinfo[i]]
                        " - P.info = t1 - P.name = "paper- 'Transcript'" - usr.put_in_hands(P) - canprint = FALSE - addtimer(VARSET_CALLBACK(src, canprint, TRUE), 30 SECONDS) - - -//empty tape recorders -/obj/item/taperecorder/empty - starting_tape_type = null - - -/obj/item/tape - name = "tape" - desc = "A magnetic tape that can hold up to ten minutes of content." - icon_state = "tape_white" - icon = 'icons/obj/device.dmi' - item_state = "analyzer" - lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' - w_class = WEIGHT_CLASS_TINY - custom_materials = list(/datum/material/iron=20, /datum/material/glass=5) - force = 1 - throwforce = 0 - var/max_capacity = 600 - var/used_capacity = 0 - var/list/storedinfo = list() - var/list/timestamp = list() - var/ruined = 0 - -/obj/item/tape/fire_act(exposed_temperature, exposed_volume) - ruin() - ..() - -/obj/item/tape/attack_self(mob/user) - if(!ruined) - to_chat(user, "You pull out all the tape!") - ruin() - - -/obj/item/tape/proc/ruin() - //Lets not add infinite amounts of overlays when our fireact is called - //repeatedly - if(!ruined) - add_overlay("ribbonoverlay") - ruined = 1 - - -/obj/item/tape/proc/fix() - cut_overlay("ribbonoverlay") - ruined = 0 - - -/obj/item/tape/attackby(obj/item/I, mob/user, params) - if(ruined && I.tool_behaviour == TOOL_SCREWDRIVER || istype(I, /obj/item/pen)) - to_chat(user, "You start winding the tape back in...") - if(I.use_tool(src, user, 120)) - to_chat(user, "You wound the tape back in.") - fix() - -//Random colour tapes -/obj/item/tape/random - icon_state = "random_tape" - -/obj/item/tape/random/Initialize() - . = ..() - icon_state = "tape_[pick("white", "blue", "red", "yellow", "purple")]" +/obj/item/taperecorder + name = "universal recorder" + desc = "A device that can record to cassette tapes, and play them. It automatically translates the content in playback." + icon = 'icons/obj/device.dmi' + icon_state = "taperecorder_empty" + item_state = "analyzer" + lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' + w_class = WEIGHT_CLASS_SMALL + flags_1 = HEAR_1 + slot_flags = ITEM_SLOT_BELT + custom_materials = list(/datum/material/iron=60, /datum/material/glass=30) + force = 2 + throwforce = 0 + var/recording = 0 + var/playing = 0 + var/playsleepseconds = 0 + var/obj/item/tape/mytape + var/starting_tape_type = /obj/item/tape/random + var/open_panel = 0 + var/canprint = 1 + + +/obj/item/taperecorder/Initialize(mapload) + . = ..() + if(starting_tape_type) + mytape = new starting_tape_type(src) + update_icon() + + +/obj/item/taperecorder/examine(mob/user) + . = ..() + . += "The wire panel is [open_panel ? "opened" : "closed"]." + + +/obj/item/taperecorder/attackby(obj/item/I, mob/user, params) + if(!mytape && istype(I, /obj/item/tape)) + if(!user.transferItemToLoc(I,src)) + return + mytape = I + to_chat(user, "You insert [I] into [src].") + update_icon() + + +/obj/item/taperecorder/proc/eject(mob/user) + if(mytape) + to_chat(user, "You remove [mytape] from [src].") + stop() + user.put_in_hands(mytape) + mytape = null + update_icon() + +/obj/item/taperecorder/fire_act(exposed_temperature, exposed_volume) + mytape.ruin() //Fires destroy the tape + ..() + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/taperecorder/attack_hand(mob/user) + if(loc != user || !mytape || !user.is_holding(src)) + return ..() + eject(user) + +/obj/item/taperecorder/proc/can_use(mob/user) + if(user && ismob(user)) + if(!user.incapacitated()) + return TRUE + return FALSE + + +/obj/item/taperecorder/verb/ejectverb() + set name = "Eject Tape" + set category = "Object" + + if(!can_use(usr)) + return + if(!mytape) + return + + eject(usr) + + +/obj/item/taperecorder/update_icon_state() + if(!mytape) + icon_state = "taperecorder_empty" + else if(recording) + icon_state = "taperecorder_recording" + else if(playing) + icon_state = "taperecorder_playing" + else + icon_state = "taperecorder_idle" + + +/obj/item/taperecorder/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, spans, message_mode) + . = ..() + if(mytape && recording) + mytape.timestamp += mytape.used_capacity + mytape.storedinfo += "\[[time2text(mytape.used_capacity * 10,"mm:ss")]\] [message]" + +/obj/item/taperecorder/verb/record() + set name = "Start Recording" + set category = "Object" + + if(!can_use(usr)) + return + if(!mytape || mytape.ruined) + return + if(recording) + return + if(playing) + return + + if(mytape.used_capacity < mytape.max_capacity) + to_chat(usr, "Recording started.") + recording = 1 + update_icon() + mytape.timestamp += mytape.used_capacity + mytape.storedinfo += "\[[time2text(mytape.used_capacity * 10,"mm:ss")]\] Recording started." + var/used = mytape.used_capacity //to stop runtimes when you eject the tape + var/max = mytape.max_capacity + while(recording && used < max) + mytape.used_capacity++ + used++ + sleep(10) + recording = 0 + update_icon() + else + to_chat(usr, "The tape is full.") + + +/obj/item/taperecorder/verb/stop() + set name = "Stop" + set category = "Object" + + if(!can_use(usr)) + return + + if(recording) + recording = 0 + mytape.timestamp += mytape.used_capacity + mytape.storedinfo += "\[[time2text(mytape.used_capacity * 10,"mm:ss")]\] Recording stopped." + to_chat(usr, "Recording stopped.") + return + else if(playing) + playing = 0 + var/turf/T = get_turf(src) + T.visible_message("Tape Recorder: Playback stopped.") + update_icon() + + +/obj/item/taperecorder/verb/play() + set name = "Play Tape" + set category = "Object" + + if(!can_use(usr)) + return + if(!mytape || mytape.ruined) + return + if(recording) + return + if(playing) + return + + playing = 1 + update_icon() + to_chat(usr, "Playing started.") + var/used = mytape.used_capacity //to stop runtimes when you eject the tape + var/max = mytape.max_capacity + for(var/i = 1, used < max, sleep(10 * playsleepseconds)) + if(!mytape) + break + if(playing == 0) + break + if(mytape.storedinfo.len < i) + break + say(mytape.storedinfo[i]) + if(mytape.storedinfo.len < i + 1) + playsleepseconds = 1 + sleep(10) + say("End of recording.") + else + playsleepseconds = mytape.timestamp[i + 1] - mytape.timestamp[i] + if(playsleepseconds > 14) + sleep(10) + say("Skipping [playsleepseconds] seconds of silence") + playsleepseconds = 1 + i++ + + playing = 0 + update_icon() + + +/obj/item/taperecorder/attack_self(mob/user) + if(!mytape || mytape.ruined) + return + if(recording) + stop() + else + record() + + +/obj/item/taperecorder/verb/print_transcript() + set name = "Print Transcript" + set category = "Object" + + if(!can_use(usr)) + return + if(!mytape) + return + if(!canprint) + to_chat(usr, "The recorder can't print that fast!") + return + if(recording || playing) + return + + to_chat(usr, "Transcript printed.") + var/obj/item/paper/P = new /obj/item/paper(get_turf(src)) + var/t1 = "Transcript:

                        " + for(var/i = 1, mytape.storedinfo.len >= i, i++) + t1 += "[mytape.storedinfo[i]]
                        " + P.info = t1 + P.name = "paper- 'Transcript'" + usr.put_in_hands(P) + canprint = FALSE + addtimer(VARSET_CALLBACK(src, canprint, TRUE), 30 SECONDS) + + +//empty tape recorders +/obj/item/taperecorder/empty + starting_tape_type = null + + +/obj/item/tape + name = "tape" + desc = "A magnetic tape that can hold up to ten minutes of content." + icon_state = "tape_white" + icon = 'icons/obj/device.dmi' + item_state = "analyzer" + lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' + w_class = WEIGHT_CLASS_TINY + custom_materials = list(/datum/material/iron=20, /datum/material/glass=5) + force = 1 + throwforce = 0 + var/max_capacity = 600 + var/used_capacity = 0 + var/list/storedinfo = list() + var/list/timestamp = list() + var/ruined = 0 + +/obj/item/tape/fire_act(exposed_temperature, exposed_volume) + ruin() + ..() + +/obj/item/tape/attack_self(mob/user) + if(!ruined) + to_chat(user, "You pull out all the tape!") + ruin() + + +/obj/item/tape/proc/ruin() + //Lets not add infinite amounts of overlays when our fireact is called + //repeatedly + if(!ruined) + add_overlay("ribbonoverlay") + ruined = 1 + + +/obj/item/tape/proc/fix() + cut_overlay("ribbonoverlay") + ruined = 0 + + +/obj/item/tape/attackby(obj/item/I, mob/user, params) + if(ruined && I.tool_behaviour == TOOL_SCREWDRIVER || istype(I, /obj/item/pen)) + to_chat(user, "You start winding the tape back in...") + if(I.use_tool(src, user, 120)) + to_chat(user, "You wound the tape back in.") + fix() + +//Random colour tapes +/obj/item/tape/random + icon_state = "random_tape" + +/obj/item/tape/random/Initialize() + . = ..() + icon_state = "tape_[pick("white", "blue", "red", "yellow", "purple")]" diff --git a/code/game/objects/items/devices/traitordevices.dm b/code/game/objects/items/devices/traitordevices.dm index c5b09edaf1e4..f45c842983e5 100644 --- a/code/game/objects/items/devices/traitordevices.dm +++ b/code/game/objects/items/devices/traitordevices.dm @@ -70,8 +70,6 @@ effective or pretty fucking useless. /obj/item/healthanalyzer/rad_laser custom_materials = list(/datum/material/iron=400) - var/ui_x = 320 - var/ui_y = 335 var/irradiate = TRUE var/stealth = FALSE var/used = FALSE // is it cooling down? @@ -111,11 +109,13 @@ effective or pretty fucking useless. /obj/item/healthanalyzer/rad_laser/interact(mob/user) ui_interact(user) -/obj/item/healthanalyzer/rad_laser/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.hands_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/item/healthanalyzer/rad_laser/ui_state(mob/user) + return GLOB.hands_state + +/obj/item/healthanalyzer/rad_laser/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "RadioactiveMicrolaser", "Radioactive Microlaser", ui_x, ui_y, master_ui, state) + ui = new(user, src, "RadioactiveMicrolaser") ui.open() /obj/item/healthanalyzer/rad_laser/ui_data(mob/user) diff --git a/code/game/objects/items/devices/transfer_valve.dm b/code/game/objects/items/devices/transfer_valve.dm index b798509bc634..7ac62767d589 100644 --- a/code/game/objects/items/devices/transfer_valve.dm +++ b/code/game/objects/items/devices/transfer_valve.dm @@ -1,243 +1,244 @@ -/obj/item/transfer_valve - icon = 'icons/obj/assemblies.dmi' - name = "tank transfer valve" - icon_state = "valve_1" - item_state = "ttv" - lefthand_file = 'icons/mob/inhands/weapons/bombs_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/bombs_righthand.dmi' - desc = "Regulates the transfer of air between two tanks." - w_class = WEIGHT_CLASS_BULKY - var/ui_x = 310 - var/ui_y = 320 - var/obj/item/tank/tank_one - var/obj/item/tank/tank_two - var/obj/item/assembly/attached_device - var/mob/attacher = null - var/valve_open = FALSE - var/toggle = TRUE - -/obj/item/transfer_valve/IsAssemblyHolder() - return TRUE - -/obj/item/transfer_valve/attackby(obj/item/item, mob/user, params) - if(istype(item, /obj/item/tank)) - if(tank_one && tank_two) - to_chat(user, "There are already two tanks attached, remove one first!") - return - - if(!tank_one) - if(!user.transferItemToLoc(item, src)) - return - tank_one = item - to_chat(user, "You attach the tank to the transfer valve.") - else if(!tank_two) - if(!user.transferItemToLoc(item, src)) - return - tank_two = item - to_chat(user, "You attach the tank to the transfer valve.") - - update_icon() -//TODO: Have this take an assemblyholder - else if(isassembly(item)) - var/obj/item/assembly/A = item - if(A.secured) - to_chat(user, "The device is secured.") - return - if(attached_device) - to_chat(user, "There is already a device attached to the valve, remove it first!") - return - if(!user.transferItemToLoc(item, src)) - return - attached_device = A - to_chat(user, "You attach the [item] to the valve controls and secure it.") - A.holder = src - A.toggle_secure() //this calls update_icon(), which calls update_icon() on the holder (i.e. the bomb). - log_bomber(user, "attached a [item.name] to a ttv -", src, null, FALSE) - attacher = user - return - -//These keep attached devices synced up, for example a TTV with a mouse trap being found in a bag so it's triggered, or moving the TTV with an infrared beam sensor to update the beam's direction. -/obj/item/transfer_valve/Move() - . = ..() - if(attached_device) - attached_device.holder_movement() - -/obj/item/transfer_valve/dropped() - . = ..() - if(attached_device) - attached_device.dropped() - -/obj/item/transfer_valve/on_found(mob/finder) - if(attached_device) - attached_device.on_found(finder) - -/obj/item/transfer_valve/Crossed(atom/movable/AM as mob|obj) - . = ..() - if(attached_device) - attached_device.Crossed(AM) - -//Triggers mousetraps -/obj/item/transfer_valve/attack_hand() - . = ..() - if(.) - return - if(attached_device) - attached_device.attack_hand() - -/obj/item/transfer_valve/proc/process_activation(obj/item/D) - if(toggle) - toggle = FALSE - toggle_valve() - addtimer(CALLBACK(src, .proc/toggle_off), 5) //To stop a signal being spammed from a proxy sensor constantly going off or whatever - -/obj/item/transfer_valve/proc/toggle_off() - toggle = TRUE - -/obj/item/transfer_valve/update_icon() - cut_overlays() - - if(!tank_one && !tank_two && !attached_device) - icon_state = "valve_1" - return - icon_state = "valve" - - if(tank_one) - add_overlay("[tank_one.icon_state]") - if(tank_two) - var/mutable_appearance/J = mutable_appearance(icon, icon_state = "[tank_two.icon_state]") - var/matrix/T = matrix() - T.Translate(-13, 0) - J.transform = T - underlays = list(J) - else - underlays = null - if(attached_device) - add_overlay("device") - if(istype(attached_device, /obj/item/assembly/infra)) - var/obj/item/assembly/infra/sensor = attached_device - if(sensor.on && sensor.visible) - add_overlay("proxy_beam") - -/obj/item/transfer_valve/proc/merge_gases(datum/gas_mixture/target, change_volume = TRUE) - var/target_self = FALSE - if(!target || (target == tank_one.air_contents)) - target = tank_two.air_contents - if(target == tank_two.air_contents) - target_self = TRUE - if(change_volume) - if(!target_self) - target.set_volume(target.return_volume() + tank_two.air_contents.return_volume()) - target.set_volume(target.return_volume() + tank_one.air_contents.return_volume()) - var/datum/gas_mixture/temp - temp = tank_one.air_contents.remove_ratio(1) - target.merge(temp) - if(!target_self) - temp = tank_two.air_contents.remove_ratio(1) - target.merge(temp) - -/obj/item/transfer_valve/proc/split_gases() - if (!valve_open || !tank_one || !tank_two) - return - var/ratio1 = tank_one.air_contents.return_volume()/tank_two.air_contents.return_volume() - var/datum/gas_mixture/temp - temp = tank_two.air_contents.remove_ratio(ratio1) - tank_one.air_contents.merge(temp) - tank_two.air_contents.set_volume(tank_two.air_contents.return_volume() - tank_one.air_contents.return_volume()) - -/* - Exadv1: I know this isn't how it's going to work, but this was just to check - it explodes properly when it gets a signal (and it does). -*/ -/obj/item/transfer_valve/proc/toggle_valve() - if(!valve_open && tank_one && tank_two) - valve_open = TRUE - var/turf/bombturf = get_turf(src) - - var/attachment - if(attached_device) - if(istype(attached_device, /obj/item/assembly/signaler)) - attachment = "[attached_device]" - else - attachment = attached_device - - var/admin_attachment_message - var/attachment_message - if(attachment) - admin_attachment_message = " with [attachment] attached by [attacher ? ADMIN_LOOKUPFLW(attacher) : "Unknown"]" - attachment_message = " with [attachment] attached by [attacher ? key_name_admin(attacher) : "Unknown"]" - - var/mob/bomber = get_mob_by_key(fingerprintslast) - var/admin_bomber_message - var/bomber_message - if(bomber) - admin_bomber_message = " - Last touched by: [ADMIN_LOOKUPFLW(bomber)]" - bomber_message = " - Last touched by: [key_name_admin(bomber)]" - - var/admin_bomb_message = "Bomb valve opened in [ADMIN_VERBOSEJMP(bombturf)][admin_attachment_message][admin_bomber_message]" - GLOB.bombers += admin_bomb_message - message_admins(admin_bomb_message) - log_game("Bomb valve opened in [AREACOORD(bombturf)][attachment_message][bomber_message]") - - merge_gases() - for(var/i in 1 to 6) - addtimer(CALLBACK(src, /atom/.proc/update_icon), 20 + (i - 1) * 10) - - else if(valve_open && tank_one && tank_two) - split_gases() - valve_open = FALSE - update_icon() -/* - This doesn't do anything but the timer etc. expects it to be here - eventually maybe have it update icon to show state (timer, prox etc.) like old bombs -*/ -/obj/item/transfer_valve/proc/c_state() - return - -/obj/item/transfer_valve/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.hands_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "TransferValve", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/item/transfer_valve/ui_data(mob/user) - var/list/data = list() - data["tank_one"] = tank_one - data["tank_two"] = tank_two - data["attached_device"] = attached_device - data["valve"] = valve_open - return data - -/obj/item/transfer_valve/ui_act(action, params) - if(..()) - return - - switch(action) - if("tankone") - if(tank_one) - split_gases() - valve_open = FALSE - tank_one.forceMove(drop_location()) - tank_one = null - . = TRUE - if("tanktwo") - if(tank_two) - split_gases() - valve_open = FALSE - tank_two.forceMove(drop_location()) - tank_two = null - . = TRUE - if("toggle") - toggle_valve() - . = TRUE - if("device") - if(attached_device) - attached_device.attack_self(usr) - . = TRUE - if("remove_device") - if(attached_device) - attached_device.on_detach() - attached_device = null - . = TRUE - - update_icon() +/obj/item/transfer_valve + icon = 'icons/obj/assemblies.dmi' + name = "tank transfer valve" + icon_state = "valve_1" + item_state = "ttv" + lefthand_file = 'icons/mob/inhands/weapons/bombs_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/bombs_righthand.dmi' + desc = "Regulates the transfer of air between two tanks." + w_class = WEIGHT_CLASS_BULKY + + var/obj/item/tank/tank_one + var/obj/item/tank/tank_two + var/obj/item/assembly/attached_device + var/mob/attacher = null + var/valve_open = FALSE + var/toggle = TRUE + +/obj/item/transfer_valve/IsAssemblyHolder() + return TRUE + +/obj/item/transfer_valve/attackby(obj/item/item, mob/user, params) + if(istype(item, /obj/item/tank)) + if(tank_one && tank_two) + to_chat(user, "There are already two tanks attached, remove one first!") + return + + if(!tank_one) + if(!user.transferItemToLoc(item, src)) + return + tank_one = item + to_chat(user, "You attach the tank to the transfer valve.") + else if(!tank_two) + if(!user.transferItemToLoc(item, src)) + return + tank_two = item + to_chat(user, "You attach the tank to the transfer valve.") + + update_icon() +//TODO: Have this take an assemblyholder + else if(isassembly(item)) + var/obj/item/assembly/A = item + if(A.secured) + to_chat(user, "The device is secured.") + return + if(attached_device) + to_chat(user, "There is already a device attached to the valve, remove it first!") + return + if(!user.transferItemToLoc(item, src)) + return + attached_device = A + to_chat(user, "You attach the [item] to the valve controls and secure it.") + A.holder = src + A.toggle_secure() //this calls update_icon(), which calls update_icon() on the holder (i.e. the bomb). + log_bomber(user, "attached a [item.name] to a ttv -", src, null, FALSE) + attacher = user + return + +//These keep attached devices synced up, for example a TTV with a mouse trap being found in a bag so it's triggered, or moving the TTV with an infrared beam sensor to update the beam's direction. +/obj/item/transfer_valve/Move() + . = ..() + if(attached_device) + attached_device.holder_movement() + +/obj/item/transfer_valve/dropped() + . = ..() + if(attached_device) + attached_device.dropped() + +/obj/item/transfer_valve/on_found(mob/finder) + if(attached_device) + attached_device.on_found(finder) + +/obj/item/transfer_valve/Crossed(atom/movable/AM as mob|obj) + . = ..() + if(attached_device) + attached_device.Crossed(AM) + +//Triggers mousetraps +/obj/item/transfer_valve/attack_hand() + . = ..() + if(.) + return + if(attached_device) + attached_device.attack_hand() + +/obj/item/transfer_valve/proc/process_activation(obj/item/D) + if(toggle) + toggle = FALSE + toggle_valve() + addtimer(CALLBACK(src, .proc/toggle_off), 5) //To stop a signal being spammed from a proxy sensor constantly going off or whatever + +/obj/item/transfer_valve/proc/toggle_off() + toggle = TRUE + +/obj/item/transfer_valve/update_icon() + cut_overlays() + + if(!tank_one && !tank_two && !attached_device) + icon_state = "valve_1" + return + icon_state = "valve" + + if(tank_one) + add_overlay("[tank_one.icon_state]") + if(tank_two) + var/mutable_appearance/J = mutable_appearance(icon, icon_state = "[tank_two.icon_state]") + var/matrix/T = matrix() + T.Translate(-13, 0) + J.transform = T + underlays = list(J) + else + underlays = null + if(attached_device) + add_overlay("device") + if(istype(attached_device, /obj/item/assembly/infra)) + var/obj/item/assembly/infra/sensor = attached_device + if(sensor.on && sensor.visible) + add_overlay("proxy_beam") + +/obj/item/transfer_valve/proc/merge_gases(datum/gas_mixture/target, change_volume = TRUE) + var/target_self = FALSE + if(!target || (target == tank_one.air_contents)) + target = tank_two.air_contents + if(target == tank_two.air_contents) + target_self = TRUE + if(change_volume) + if(!target_self) + target.set_volume(target.return_volume() + tank_two.air_contents.return_volume()) + target.set_volume(target.return_volume() + tank_one.air_contents.return_volume()) + var/datum/gas_mixture/temp + temp = tank_one.air_contents.remove_ratio(1) + target.merge(temp) + if(!target_self) + temp = tank_two.air_contents.remove_ratio(1) + target.merge(temp) + +/obj/item/transfer_valve/proc/split_gases() + if (!valve_open || !tank_one || !tank_two) + return + var/ratio1 = tank_one.air_contents.return_volume()/tank_two.air_contents.return_volume() + var/datum/gas_mixture/temp + temp = tank_two.air_contents.remove_ratio(ratio1) + tank_one.air_contents.merge(temp) + tank_two.air_contents.set_volume(tank_two.air_contents.return_volume() - tank_one.air_contents.return_volume()) + +/* + Exadv1: I know this isn't how it's going to work, but this was just to check + it explodes properly when it gets a signal (and it does). +*/ +/obj/item/transfer_valve/proc/toggle_valve() + if(!valve_open && tank_one && tank_two) + valve_open = TRUE + var/turf/bombturf = get_turf(src) + + var/attachment + if(attached_device) + if(istype(attached_device, /obj/item/assembly/signaler)) + attachment = "[attached_device]" + else + attachment = attached_device + + var/admin_attachment_message + var/attachment_message + if(attachment) + admin_attachment_message = " with [attachment] attached by [attacher ? ADMIN_LOOKUPFLW(attacher) : "Unknown"]" + attachment_message = " with [attachment] attached by [attacher ? key_name_admin(attacher) : "Unknown"]" + + var/mob/bomber = get_mob_by_key(fingerprintslast) + var/admin_bomber_message + var/bomber_message + if(bomber) + admin_bomber_message = " - Last touched by: [ADMIN_LOOKUPFLW(bomber)]" + bomber_message = " - Last touched by: [key_name_admin(bomber)]" + + var/admin_bomb_message = "Bomb valve opened in [ADMIN_VERBOSEJMP(bombturf)][admin_attachment_message][admin_bomber_message]" + GLOB.bombers += admin_bomb_message + message_admins(admin_bomb_message) + log_game("Bomb valve opened in [AREACOORD(bombturf)][attachment_message][bomber_message]") + + merge_gases() + for(var/i in 1 to 6) + addtimer(CALLBACK(src, /atom/.proc/update_icon), 20 + (i - 1) * 10) + + else if(valve_open && tank_one && tank_two) + split_gases() + valve_open = FALSE + update_icon() +/* + This doesn't do anything but the timer etc. expects it to be here + eventually maybe have it update icon to show state (timer, prox etc.) like old bombs +*/ +/obj/item/transfer_valve/proc/c_state() + return + +/obj/item/transfer_valve/ui_state(mob/user) + return GLOB.hands_state + +/obj/item/transfer_valve/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "TransferValve", name) + ui.open() + +/obj/item/transfer_valve/ui_data(mob/user) + var/list/data = list() + data["tank_one"] = tank_one + data["tank_two"] = tank_two + data["attached_device"] = attached_device + data["valve"] = valve_open + return data + +/obj/item/transfer_valve/ui_act(action, params) + if(..()) + return + + switch(action) + if("tankone") + if(tank_one) + split_gases() + valve_open = FALSE + tank_one.forceMove(drop_location()) + tank_one = null + . = TRUE + if("tanktwo") + if(tank_two) + split_gases() + valve_open = FALSE + tank_two.forceMove(drop_location()) + tank_two = null + . = TRUE + if("toggle") + toggle_valve() + . = TRUE + if("device") + if(attached_device) + attached_device.attack_self(usr) + . = TRUE + if("remove_device") + if(attached_device) + attached_device.on_detach() + attached_device = null + . = TRUE + + update_icon() diff --git a/code/game/objects/items/dice.dm b/code/game/objects/items/dice.dm index 35d3e4694030..205caa3f600e 100644 --- a/code/game/objects/items/dice.dm +++ b/code/game/objects/items/dice.dm @@ -1,225 +1,225 @@ -/*****************************Dice Bags********************************/ - -/obj/item/storage/pill_bottle/dice - name = "bag of dice" - desc = "Contains all the luck you'll ever need." - icon = 'icons/obj/dice.dmi' - icon_state = "dicebag" - var/list/special_die = list( - /obj/item/dice/d1, - /obj/item/dice/d2, - /obj/item/dice/fudge, - /obj/item/dice/d6/space, - /obj/item/dice/d00, - /obj/item/dice/eightbd20, - /obj/item/dice/fourdd6, - /obj/item/dice/d100 - ) - -/obj/item/storage/pill_bottle/dice/PopulateContents() - new /obj/item/dice/d4(src) - new /obj/item/dice/d6(src) - new /obj/item/dice/d8(src) - new /obj/item/dice/d10(src) - new /obj/item/dice/d12(src) - new /obj/item/dice/d20(src) - var/picked = pick(special_die) - new picked(src) - -/obj/item/storage/pill_bottle/dice/suicide_act(mob/user) - user.visible_message("[user] is gambling with death! It looks like [user.p_theyre()] trying to commit suicide!") - return (OXYLOSS) - -/obj/item/storage/pill_bottle/dice/hazard - -/obj/item/storage/pill_bottle/dice/hazard/PopulateContents() - new /obj/item/dice/d6(src) - new /obj/item/dice/d6(src) - new /obj/item/dice/d6(src) - for(var/i in 1 to 2) - if(prob(7)) - new /obj/item/dice/d6/ebony(src) - else - new /obj/item/dice/d6(src) - -/*****************************Dice********************************/ - -/obj/item/dice //depreciated d6, use /obj/item/dice/d6 if you actually want a d6 - name = "die" - desc = "A die with six sides. Basic and serviceable." - icon = 'icons/obj/dice.dmi' - icon_state = "d6" - w_class = WEIGHT_CLASS_TINY - var/sides = 6 - var/result = null - var/list/special_faces = list() //entries should match up to sides var if used - var/microwave_riggable = TRUE - - var/rigged = DICE_NOT_RIGGED - var/rigged_value - -/obj/item/dice/Initialize() - . = ..() - if(!result) - result = roll(sides) - update_icon() - -/obj/item/dice/suicide_act(mob/user) - user.visible_message("[user] is gambling with death! It looks like [user.p_theyre()] trying to commit suicide!") - return (OXYLOSS) - -/obj/item/dice/d1 - name = "d1" - desc = "A die with only one side. Deterministic!" - icon_state = "d1" - sides = 1 - -/obj/item/dice/d2 - name = "d2" - desc = "A die with two sides. Coins are undignified!" - icon_state = "d2" - sides = 2 - -/obj/item/dice/d4 - name = "d4" - desc = "A die with four sides. The nerd's caltrop." - icon_state = "d4" - sides = 4 - -/obj/item/dice/d4/Initialize(mapload) - . = ..() - AddComponent(/datum/component/caltrop, 1, 4) //1d4 damage - -/obj/item/dice/d6 - name = "d6" - -obj/item/dice/d6/ebony - name = "ebony die" - desc = "A die with six sides made of dense black wood. It feels cold and heavy in your hand." - icon_state = "de6" - microwave_riggable = FALSE // You can't melt wood in the microwave - -/obj/item/dice/d6/space - name = "space cube" - desc = "A die with six sides. 6 TIMES 255 TIMES 255 TILE TOTAL EXISTENCE, SQUARE YOUR MIND OF EDUCATED STUPID: 2 DOES NOT EXIST." - icon_state = "spaced6" - -/obj/item/dice/d6/space/Initialize() - . = ..() - if(prob(10)) - name = "spess cube" - -/obj/item/dice/fudge - name = "fudge die" - desc = "A die with six sides but only three results. Is this a plus or a minus? Your mind is drawing a blank..." - sides = 3 //shhh - icon_state = "fudge" - special_faces = list("minus","blank","plus") - -/obj/item/dice/d8 - name = "d8" - desc = "A die with eight sides. It feels... lucky." - icon_state = "d8" - sides = 8 - -/obj/item/dice/d10 - name = "d10" - desc = "A die with ten sides. Useful for percentages." - icon_state = "d10" - sides = 10 - -/obj/item/dice/d00 - name = "d00" - desc = "A die with ten sides. Works better for d100 rolls than a golf ball." - icon_state = "d00" - sides = 10 - -/obj/item/dice/d12 - name = "d12" - desc = "A die with twelve sides. There's an air of neglect about it." - icon_state = "d12" - sides = 12 - -/obj/item/dice/d20 - name = "d20" - desc = "A die with twenty sides. The preferred die to throw at the GM." - icon_state = "d20" - sides = 20 - -/obj/item/dice/d100 - name = "d100" - desc = "A die with one hundred sides! Probably not fairly weighted..." - icon_state = "d100" - w_class = WEIGHT_CLASS_SMALL - sides = 100 - -/obj/item/dice/d100/ComponentInitialize() - . = ..() - AddElement(/datum/element/update_icon_blocker) - -/obj/item/dice/eightbd20 - name = "strange d20" - desc = "A weird die with raised text printed on the faces. Everything's white on white so reading it is a struggle. What poor design!" - icon_state = "8bd20" - sides = 20 - special_faces = list("It is certain","It is decidedly so","Without a doubt","Yes, definitely","You may rely on it","As I see it, yes","Most likely","Outlook good","Yes","Signs point to yes","Reply hazy try again","Ask again later","Better not tell you now","Cannot predict now","Concentrate and ask again","Don't count on it","My reply is no","My sources say no","Outlook not so good","Very doubtful") - -/obj/item/dice/eightbd20/ComponentInitialize() - . = ..() - AddElement(/datum/element/update_icon_blocker) - -/obj/item/dice/fourdd6 - name = "4d d6" - desc = "A die that exists in four dimensional space. Properly interpreting them can only be done with the help of a mathematician, a physicist, and a priest." - icon_state = "4dd6" - sides = 48 - special_faces = list("Cube-Side: 1-1","Cube-Side: 1-2","Cube-Side: 1-3","Cube-Side: 1-4","Cube-Side: 1-5","Cube-Side: 1-6","Cube-Side: 2-1","Cube-Side: 2-2","Cube-Side: 2-3","Cube-Side: 2-4","Cube-Side: 2-5","Cube-Side: 2-6","Cube-Side: 3-1","Cube-Side: 3-2","Cube-Side: 3-3","Cube-Side: 3-4","Cube-Side: 3-5","Cube-Side: 3-6","Cube-Side: 4-1","Cube-Side: 4-2","Cube-Side: 4-3","Cube-Side: 4-4","Cube-Side: 4-5","Cube-Side: 4-6","Cube-Side: 5-1","Cube-Side: 5-2","Cube-Side: 5-3","Cube-Side: 5-4","Cube-Side: 5-5","Cube-Side: 5-6","Cube-Side: 6-1","Cube-Side: 6-2","Cube-Side: 6-3","Cube-Side: 6-4","Cube-Side: 6-5","Cube-Side: 6-6","Cube-Side: 7-1","Cube-Side: 7-2","Cube-Side: 7-3","Cube-Side: 7-4","Cube-Side: 7-5","Cube-Side: 7-6","Cube-Side: 8-1","Cube-Side: 8-2","Cube-Side: 8-3","Cube-Side: 8-4","Cube-Side: 8-5","Cube-Side: 8-6") - -/obj/item/dice/fourdd6/ComponentInitialize() - . = ..() - AddElement(/datum/element/update_icon_blocker) - -/obj/item/dice/attack_self(mob/user) - diceroll(user) - -/obj/item/dice/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - diceroll(thrownby) - . = ..() - -/obj/item/dice/proc/diceroll(mob/user) - result = roll(sides) - if(rigged != DICE_NOT_RIGGED && result != rigged_value) - if(rigged == DICE_BASICALLY_RIGGED && prob(clamp(1/(sides - 1) * 100, 25, 80))) - result = rigged_value - else if(rigged == DICE_TOTALLY_RIGGED) - result = rigged_value - - . = result - - var/fake_result = roll(sides)//Daredevil isn't as good as he used to be - var/comment = "" - if(sides == 20 && result == 20) - comment = "NAT 20!" - else if(sides == 20 && result == 1) - comment = "Ouch, bad luck." - update_icon() - if(initial(icon_state) == "d00") - result = (result - 1)*10 - if(special_faces.len == sides) - result = special_faces[result] - if(user != null) //Dice was rolled in someone's hand - user.visible_message("[user] throws [src]. It lands on [result]. [comment]", \ - "You throw [src]. It lands on [result]. [comment]", \ - "You hear [src] rolling, it sounds like a [fake_result].") - else if(!src.throwing) //Dice was thrown and is coming to rest - visible_message("[src] rolls to a stop, landing on [result]. [comment]") - -/obj/item/dice/update_overlays() - . = ..() - . += "[icon_state]-[result]" - -/obj/item/dice/microwave_act(obj/machinery/microwave/M) - if(microwave_riggable) - rigged = DICE_BASICALLY_RIGGED - rigged_value = result - ..(M) +/*****************************Dice Bags********************************/ + +/obj/item/storage/pill_bottle/dice + name = "bag of dice" + desc = "Contains all the luck you'll ever need." + icon = 'icons/obj/dice.dmi' + icon_state = "dicebag" + var/list/special_die = list( + /obj/item/dice/d1, + /obj/item/dice/d2, + /obj/item/dice/fudge, + /obj/item/dice/d6/space, + /obj/item/dice/d00, + /obj/item/dice/eightbd20, + /obj/item/dice/fourdd6, + /obj/item/dice/d100 + ) + +/obj/item/storage/pill_bottle/dice/PopulateContents() + new /obj/item/dice/d4(src) + new /obj/item/dice/d6(src) + new /obj/item/dice/d8(src) + new /obj/item/dice/d10(src) + new /obj/item/dice/d12(src) + new /obj/item/dice/d20(src) + var/picked = pick(special_die) + new picked(src) + +/obj/item/storage/pill_bottle/dice/suicide_act(mob/user) + user.visible_message("[user] is gambling with death! It looks like [user.p_theyre()] trying to commit suicide!") + return (OXYLOSS) + +/obj/item/storage/pill_bottle/dice/hazard + +/obj/item/storage/pill_bottle/dice/hazard/PopulateContents() + new /obj/item/dice/d6(src) + new /obj/item/dice/d6(src) + new /obj/item/dice/d6(src) + for(var/i in 1 to 2) + if(prob(7)) + new /obj/item/dice/d6/ebony(src) + else + new /obj/item/dice/d6(src) + +/*****************************Dice********************************/ + +/obj/item/dice //depreciated d6, use /obj/item/dice/d6 if you actually want a d6 + name = "die" + desc = "A die with six sides. Basic and serviceable." + icon = 'icons/obj/dice.dmi' + icon_state = "d6" + w_class = WEIGHT_CLASS_TINY + var/sides = 6 + var/result = null + var/list/special_faces = list() //entries should match up to sides var if used + var/microwave_riggable = TRUE + + var/rigged = DICE_NOT_RIGGED + var/rigged_value + +/obj/item/dice/Initialize() + . = ..() + if(!result) + result = roll(sides) + update_icon() + +/obj/item/dice/suicide_act(mob/user) + user.visible_message("[user] is gambling with death! It looks like [user.p_theyre()] trying to commit suicide!") + return (OXYLOSS) + +/obj/item/dice/d1 + name = "d1" + desc = "A die with only one side. Deterministic!" + icon_state = "d1" + sides = 1 + +/obj/item/dice/d2 + name = "d2" + desc = "A die with two sides. Coins are undignified!" + icon_state = "d2" + sides = 2 + +/obj/item/dice/d4 + name = "d4" + desc = "A die with four sides. The nerd's caltrop." + icon_state = "d4" + sides = 4 + +/obj/item/dice/d4/Initialize(mapload) + . = ..() + AddComponent(/datum/component/caltrop, 1, 4) //1d4 damage + +/obj/item/dice/d6 + name = "d6" + +obj/item/dice/d6/ebony + name = "ebony die" + desc = "A die with six sides made of dense black wood. It feels cold and heavy in your hand." + icon_state = "de6" + microwave_riggable = FALSE // You can't melt wood in the microwave + +/obj/item/dice/d6/space + name = "space cube" + desc = "A die with six sides. 6 TIMES 255 TIMES 255 TILE TOTAL EXISTENCE, SQUARE YOUR MIND OF EDUCATED STUPID: 2 DOES NOT EXIST." + icon_state = "spaced6" + +/obj/item/dice/d6/space/Initialize() + . = ..() + if(prob(10)) + name = "spess cube" + +/obj/item/dice/fudge + name = "fudge die" + desc = "A die with six sides but only three results. Is this a plus or a minus? Your mind is drawing a blank..." + sides = 3 //shhh + icon_state = "fudge" + special_faces = list("minus","blank","plus") + +/obj/item/dice/d8 + name = "d8" + desc = "A die with eight sides. It feels... lucky." + icon_state = "d8" + sides = 8 + +/obj/item/dice/d10 + name = "d10" + desc = "A die with ten sides. Useful for percentages." + icon_state = "d10" + sides = 10 + +/obj/item/dice/d00 + name = "d00" + desc = "A die with ten sides. Works better for d100 rolls than a golf ball." + icon_state = "d00" + sides = 10 + +/obj/item/dice/d12 + name = "d12" + desc = "A die with twelve sides. There's an air of neglect about it." + icon_state = "d12" + sides = 12 + +/obj/item/dice/d20 + name = "d20" + desc = "A die with twenty sides. The preferred die to throw at the GM." + icon_state = "d20" + sides = 20 + +/obj/item/dice/d100 + name = "d100" + desc = "A die with one hundred sides! Probably not fairly weighted..." + icon_state = "d100" + w_class = WEIGHT_CLASS_SMALL + sides = 100 + +/obj/item/dice/d100/ComponentInitialize() + . = ..() + AddElement(/datum/element/update_icon_blocker) + +/obj/item/dice/eightbd20 + name = "strange d20" + desc = "A weird die with raised text printed on the faces. Everything's white on white so reading it is a struggle. What poor design!" + icon_state = "8bd20" + sides = 20 + special_faces = list("It is certain","It is decidedly so","Without a doubt","Yes, definitely","You may rely on it","As I see it, yes","Most likely","Outlook good","Yes","Signs point to yes","Reply hazy try again","Ask again later","Better not tell you now","Cannot predict now","Concentrate and ask again","Don't count on it","My reply is no","My sources say no","Outlook not so good","Very doubtful") + +/obj/item/dice/eightbd20/ComponentInitialize() + . = ..() + AddElement(/datum/element/update_icon_blocker) + +/obj/item/dice/fourdd6 + name = "4d d6" + desc = "A die that exists in four dimensional space. Properly interpreting them can only be done with the help of a mathematician, a physicist, and a priest." + icon_state = "4dd6" + sides = 48 + special_faces = list("Cube-Side: 1-1","Cube-Side: 1-2","Cube-Side: 1-3","Cube-Side: 1-4","Cube-Side: 1-5","Cube-Side: 1-6","Cube-Side: 2-1","Cube-Side: 2-2","Cube-Side: 2-3","Cube-Side: 2-4","Cube-Side: 2-5","Cube-Side: 2-6","Cube-Side: 3-1","Cube-Side: 3-2","Cube-Side: 3-3","Cube-Side: 3-4","Cube-Side: 3-5","Cube-Side: 3-6","Cube-Side: 4-1","Cube-Side: 4-2","Cube-Side: 4-3","Cube-Side: 4-4","Cube-Side: 4-5","Cube-Side: 4-6","Cube-Side: 5-1","Cube-Side: 5-2","Cube-Side: 5-3","Cube-Side: 5-4","Cube-Side: 5-5","Cube-Side: 5-6","Cube-Side: 6-1","Cube-Side: 6-2","Cube-Side: 6-3","Cube-Side: 6-4","Cube-Side: 6-5","Cube-Side: 6-6","Cube-Side: 7-1","Cube-Side: 7-2","Cube-Side: 7-3","Cube-Side: 7-4","Cube-Side: 7-5","Cube-Side: 7-6","Cube-Side: 8-1","Cube-Side: 8-2","Cube-Side: 8-3","Cube-Side: 8-4","Cube-Side: 8-5","Cube-Side: 8-6") + +/obj/item/dice/fourdd6/ComponentInitialize() + . = ..() + AddElement(/datum/element/update_icon_blocker) + +/obj/item/dice/attack_self(mob/user) + diceroll(user) + +/obj/item/dice/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + diceroll(thrownby) + . = ..() + +/obj/item/dice/proc/diceroll(mob/user) + result = roll(sides) + if(rigged != DICE_NOT_RIGGED && result != rigged_value) + if(rigged == DICE_BASICALLY_RIGGED && prob(clamp(1/(sides - 1) * 100, 25, 80))) + result = rigged_value + else if(rigged == DICE_TOTALLY_RIGGED) + result = rigged_value + + . = result + + var/fake_result = roll(sides)//Daredevil isn't as good as he used to be + var/comment = "" + if(sides == 20 && result == 20) + comment = "NAT 20!" + else if(sides == 20 && result == 1) + comment = "Ouch, bad luck." + update_icon() + if(initial(icon_state) == "d00") + result = (result - 1)*10 + if(special_faces.len == sides) + result = special_faces[result] + if(user != null) //Dice was rolled in someone's hand + user.visible_message("[user] throws [src]. It lands on [result]. [comment]", \ + "You throw [src]. It lands on [result]. [comment]", \ + "You hear [src] rolling, it sounds like a [fake_result].") + else if(!src.throwing) //Dice was thrown and is coming to rest + visible_message("[src] rolls to a stop, landing on [result]. [comment]") + +/obj/item/dice/update_overlays() + . = ..() + . += "[icon_state]-[result]" + +/obj/item/dice/microwave_act(obj/machinery/microwave/M) + if(microwave_riggable) + rigged = DICE_BASICALLY_RIGGED + rigged_value = result + ..(M) diff --git a/code/game/objects/items/dna_injector.dm b/code/game/objects/items/dna_injector.dm index 95a1634057ca..b63285b24f0e 100644 --- a/code/game/objects/items/dna_injector.dm +++ b/code/game/objects/items/dna_injector.dm @@ -1,533 +1,533 @@ -/obj/item/dnainjector - name = "\improper DNA injector" - desc = "This injects the person with DNA." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "dnainjector" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - throw_speed = 3 - throw_range = 5 - w_class = WEIGHT_CLASS_TINY - - var/damage_coeff = 1 - var/list/fields - var/list/add_mutations = list() - var/list/remove_mutations = list() - - var/used = 0 - -/obj/item/dnainjector/attack_paw(mob/user) - return attack_hand(user) - -/obj/item/dnainjector/proc/inject(mob/living/carbon/M, mob/user) - if(M.has_dna() && !HAS_TRAIT(M, TRAIT_GENELESS) && !HAS_TRAIT(M, TRAIT_BADDNA)) - M.radiation += rand(20/(damage_coeff ** 2),50/(damage_coeff ** 2)) - var/log_msg = "[key_name(user)] injected [key_name(M)] with the [name]" - for(var/HM in remove_mutations) - M.dna.remove_mutation(HM) - for(var/HM in add_mutations) - if(HM == RACEMUT) - message_admins("[ADMIN_LOOKUPFLW(user)] injected [key_name_admin(M)] with the [name] (MONKEY)") - log_msg += " (MONKEY)" - if(M.dna.mutation_in_sequence(HM)) - M.dna.activate_mutation(HM) - else - M.dna.add_mutation(HM, MUT_EXTRA) - if(fields) - if(fields["name"] && fields["UE"] && fields["blood_type"]) - M.real_name = fields["name"] - M.dna.unique_enzymes = fields["UE"] - M.name = M.real_name - M.dna.blood_type = fields["blood_type"] - if(fields["UI"]) //UI+UE - M.dna.uni_identity = merge_text(M.dna.uni_identity, fields["UI"]) - M.updateappearance(mutations_overlay_update=1) - log_attack("[log_msg] [loc_name(user)]") - return TRUE - return FALSE - -/obj/item/dnainjector/attack(mob/target, mob/user) - if(!user.IsAdvancedToolUser()) - to_chat(user, "You don't have the dexterity to do this!") - return - if(used) - to_chat(user, "This injector is used up!") - return - if(ishuman(target)) - var/mob/living/carbon/human/humantarget = target - if (!humantarget.can_inject(user, 1)) - return - log_combat(user, target, "attempted to inject", src) - - if(target != user) - target.visible_message("[user] is trying to inject [target] with [src]!", \ - "[user] is trying to inject you with [src]!") - if(!do_mob(user, target) || used) - return - target.visible_message("[user] injects [target] with the syringe with [src]!", \ - "[user] injects you with the syringe with [src]!") - - else - to_chat(user, "You inject yourself with [src].") - - log_combat(user, target, "injected", src) - - if(!inject(target, user)) //Now we actually do the heavy lifting. - to_chat(user, "It appears that [target] does not have compatible DNA.") - - used = 1 - icon_state = "dnainjector0" - desc += " This one is used up." - - -/obj/item/dnainjector/antihulk - name = "\improper DNA injector (Anti-Hulk)" - desc = "Cures green skin." - remove_mutations = list(HULK) - -/obj/item/dnainjector/hulkmut - name = "\improper DNA injector (Hulk)" - desc = "This will make you big and strong, but give you a bad skin condition." - add_mutations = list(HULK) - -/obj/item/dnainjector/firebreath - name = "\improper DNA injector (Fire Breath)" - desc = "Restores the dragon ancestry." - add_mutations = list(FIREBREATH) - -/obj/item/dnainjector/xraymut - name = "\improper DNA injector (X-ray)" - desc = "Finally you can see what the Captain does." - add_mutations = list(XRAY) - -/obj/item/dnainjector/antixray - name = "\improper DNA injector (Anti-X-ray)" - desc = "It will make you see harder." - remove_mutations = list(XRAY) - -///////////////////////////////////// -/obj/item/dnainjector/antiglasses - name = "\improper DNA injector (Anti-Glasses)" - desc = "Toss away those glasses!" - remove_mutations = list(BADSIGHT) - -/obj/item/dnainjector/glassesmut - name = "\improper DNA injector (Glasses)" - desc = "Will make you need dorkish glasses." - add_mutations = list(BADSIGHT) - -/obj/item/dnainjector/epimut - name = "\improper DNA injector (Epi.)" - desc = "Shake shake shake the room!" - add_mutations = list(EPILEPSY) - -/obj/item/dnainjector/antiepi - name = "\improper DNA injector (Anti-Epi.)" - desc = "Will fix you up from shaking the room." - remove_mutations = list(EPILEPSY) -//////////////////////////////////// -/obj/item/dnainjector/anticough - name = "\improper DNA injector (Anti-Cough)" - desc = "Will stop that awful noise." - remove_mutations = list(COUGH) - -/obj/item/dnainjector/coughmut - name = "\improper DNA injector (Cough)" - desc = "Will bring forth a sound of horror from your throat." - add_mutations = list(COUGH) - -/obj/item/dnainjector/antidwarf - name = "\improper DNA injector (Anti-Dwarfism)" - desc = "Helps you grow big and strong." - remove_mutations = list(DWARFISM) - -/obj/item/dnainjector/dwarf - name = "\improper DNA injector (Dwarfism)" - desc = "It's a small world after all." - add_mutations = list(DWARFISM) - -/obj/item/dnainjector/clumsymut - name = "\improper DNA injector (Clumsy)" - desc = "Makes clown minions." - add_mutations = list(CLOWNMUT) - -/obj/item/dnainjector/anticlumsy - name = "\improper DNA injector (Anti-Clumsy)" - desc = "Apply this for Security Clown." - remove_mutations = list(CLOWNMUT) - -/obj/item/dnainjector/antitour - name = "\improper DNA injector (Anti-Tour.)" - desc = "Will cure Tourette's." - remove_mutations = list(TOURETTES) - -/obj/item/dnainjector/tourmut - name = "\improper DNA injector (Tour.)" - desc = "Gives you a nasty case of Tourette's." - add_mutations = list(TOURETTES) - -/obj/item/dnainjector/stuttmut - name = "\improper DNA injector (Stutt.)" - desc = "Makes you s-s-stuttterrr." - add_mutations = list(NERVOUS) - -/obj/item/dnainjector/antistutt - name = "\improper DNA injector (Anti-Stutt.)" - desc = "Fixes that speaking impairment." - remove_mutations = list(NERVOUS) - -/obj/item/dnainjector/antifire - name = "\improper DNA injector (Anti-Fire)" - desc = "Cures fire." - remove_mutations = list(SPACEMUT) - -/obj/item/dnainjector/firemut - name = "\improper DNA injector (Fire)" - desc = "Gives you fire." - add_mutations = list(SPACEMUT) - -/obj/item/dnainjector/blindmut - name = "\improper DNA injector (Blind)" - desc = "Makes you not see anything." - add_mutations = list(BLINDMUT) - -/obj/item/dnainjector/antiblind - name = "\improper DNA injector (Anti-Blind)" - desc = "IT'S A MIRACLE!!!" - remove_mutations = list(BLINDMUT) - -/obj/item/dnainjector/antitele - name = "\improper DNA injector (Anti-Tele.)" - desc = "Will make you not able to control your mind." - remove_mutations = list(TK) - -/obj/item/dnainjector/telemut - name = "\improper DNA injector (Tele.)" - desc = "Super brain man!" - add_mutations = list(TK) - -/obj/item/dnainjector/telemut/darkbundle - name = "\improper DNA injector" - desc = "Good. Let the hate flow through you." - -/obj/item/dnainjector/deafmut - name = "\improper DNA injector (Deaf)" - desc = "Sorry, what did you say?" - add_mutations = list(DEAFMUT) - -/obj/item/dnainjector/antideaf - name = "\improper DNA injector (Anti-Deaf)" - desc = "Will make you hear once more." - remove_mutations = list(DEAFMUT) - -/obj/item/dnainjector/h2m - name = "\improper DNA injector (Human > Monkey)" - desc = "Will make you a flea bag." - add_mutations = list(RACEMUT) - -/obj/item/dnainjector/m2h - name = "\improper DNA injector (Monkey > Human)" - desc = "Will make you...less hairy." - remove_mutations = list(RACEMUT) - -/obj/item/dnainjector/antichameleon - name = "\improper DNA injector (Anti-Chameleon)" - remove_mutations = list(CHAMELEON) - -/obj/item/dnainjector/chameleonmut - name = "\improper DNA injector (Chameleon)" - add_mutations = list(CHAMELEON) - -/obj/item/dnainjector/antiwacky - name = "\improper DNA injector (Anti-Wacky)" - remove_mutations = list(WACKY) - -/obj/item/dnainjector/wackymut - name = "\improper DNA injector (Wacky)" - add_mutations = list(WACKY) - -/obj/item/dnainjector/antimute - name = "\improper DNA injector (Anti-Mute)" - remove_mutations = list(MUT_MUTE) - -/obj/item/dnainjector/mutemut - name = "\improper DNA injector (Mute)" - add_mutations = list(MUT_MUTE) - -/obj/item/dnainjector/unintelligiblemut - name = "\improper DNA injector (Unintelligible)" - add_mutations = list(UNINTELLIGIBLE) - -/obj/item/dnainjector/antiunintelligible - name = "\improper DNA injector (Anti-Unintelligible)" - remove_mutations = list(UNINTELLIGIBLE) - -/obj/item/dnainjector/swedishmut - name = "\improper DNA injector (Swedish)" - add_mutations = list(SWEDISH) - -/obj/item/dnainjector/antiswedish - name = "\improper DNA injector (Anti-Swedish)" - remove_mutations = list(SWEDISH) - -/obj/item/dnainjector/chavmut - name = "\improper DNA injector (Chav)" - add_mutations = list(CHAV) - -/obj/item/dnainjector/antichav - name = "\improper DNA injector (Anti-Chav)" - remove_mutations = list(CHAV) - -/obj/item/dnainjector/elvismut - name = "\improper DNA injector (Elvis)" - add_mutations = list(ELVIS) - -/obj/item/dnainjector/antielvis - name = "\improper DNA injector (Anti-Elvis)" - remove_mutations = list(ELVIS) - -/obj/item/dnainjector/lasereyesmut - name = "\improper DNA injector (Laser Eyes)" - add_mutations = list(LASEREYES) - -/obj/item/dnainjector/antilasereyes - name = "\improper DNA injector (Anti-Laser Eyes)" - remove_mutations = list(LASEREYES) - -/obj/item/dnainjector/void - name = "\improper DNA injector (Void)" - add_mutations = list(VOID) - -/obj/item/dnainjector/antivoid - name = "\improper DNA injector (Anti-Void)" - remove_mutations = list(VOID) - -/obj/item/dnainjector/antenna - name = "\improper DNA injector (Antenna)" - add_mutations = list(ANTENNA) - -/obj/item/dnainjector/antiantenna - name = "\improper DNA injector (Anti-Antenna)" - remove_mutations = list(ANTENNA) - -/obj/item/dnainjector/paranoia - name = "\improper DNA injector (Paranoia)" - add_mutations = list(PARANOIA) - -/obj/item/dnainjector/antiparanoia - name = "\improper DNA injector (Anti-Paranoia)" - remove_mutations = list(PARANOIA) - -/obj/item/dnainjector/mindread - name = "\improper DNA injector (Mindread)" - add_mutations = list(MINDREAD) - -/obj/item/dnainjector/antimindread - name = "\improper DNA injector (Anti-Mindread)" - remove_mutations = list(MINDREAD) - -/obj/item/dnainjector/radioactive - name = "\improper DNA injector (Radioactive)" - add_mutations = list(RADIOACTIVE) - -/obj/item/dnainjector/antiradioactive - name = "\improper DNA injector (Anti-Radioactive)" - remove_mutations = list(RADIOACTIVE) -/obj/item/dnainjector/olfaction - name = "\improper DNA injector (Olfaction)" - add_mutations = list(OLFACTION) - -/obj/item/dnainjector/antiolfaction - name = "\improper DNA injector (Anti-Olfaction)" - remove_mutations = list(OLFACTION) - -/obj/item/dnainjector/insulated - name = "\improper DNA injector (Insulated)" - add_mutations = list(INSULATED) - -/obj/item/dnainjector/antiinsulated - name = "\improper DNA injector (Anti-Insulated)" - remove_mutations = list(INSULATED) - -/obj/item/dnainjector/shock - name = "\improper DNA injector (Shock Touch)" - add_mutations = list(SHOCKTOUCH) - -/obj/item/dnainjector/antishock - name = "\improper DNA injector (Anti-Shock Touch)" - remove_mutations = list(SHOCKTOUCH) - -/obj/item/dnainjector/spacialinstability - name = "\improper DNA injector (Spacial Instability)" - add_mutations = list(BADBLINK) - -/obj/item/dnainjector/antispacialinstability - name = "\improper DNA injector (Anti-Spacial Instability)" - remove_mutations = list(BADBLINK) - -/obj/item/dnainjector/acidflesh - name = "\improper DNA injector (Acid Flesh)" - add_mutations = list(ACIDFLESH) - -/obj/item/dnainjector/antiacidflesh - name = "\improper DNA injector (Acid Flesh)" - remove_mutations = list(ACIDFLESH) - -/obj/item/dnainjector/gigantism - name = "\improper DNA injector (Gigantism)" - add_mutations = list(GIGANTISM) - -/obj/item/dnainjector/antigigantism - name = "\improper DNA injector (Anti-Gigantism)" - remove_mutations = list(GIGANTISM) - -/obj/item/dnainjector/spastic - name = "\improper DNA injector (Spastic)" - add_mutations = list(SPASTIC) - -/obj/item/dnainjector/antispastic - name = "\improper DNA injector (Anti-Spastic)" - remove_mutations = list(SPASTIC) - -/obj/item/dnainjector/twoleftfeet - name = "\improper DNA injector (Two Left Feet)" - add_mutations = list(EXTRASTUN) - -/obj/item/dnainjector/antitwoleftfeet - name = "\improper DNA injector (Anti-Two Left Feet)" - remove_mutations = list(EXTRASTUN) - -/obj/item/dnainjector/geladikinesis - name = "\improper DNA injector (Geladikinesis)" - add_mutations = list(GELADIKINESIS) - -/obj/item/dnainjector/antigeladikinesis - name = "\improper DNA injector (Anti-Geladikinesis)" - remove_mutations = list(GELADIKINESIS) - -/obj/item/dnainjector/cryokinesis - name = "\improper DNA injector (Cryokinesis)" - add_mutations = list(CRYOKINESIS) - -/obj/item/dnainjector/anticryokinesis - name = "\improper DNA injector (Anti-Cryokinesis)" - remove_mutations = list(CRYOKINESIS) - -/obj/item/dnainjector/thermal - name = "\improper DNA injector (Thermal Vision)" - add_mutations = list(THERMAL) - -/obj/item/dnainjector/antithermal - name = "\improper DNA injector (Anti-Thermal Vision)" - remove_mutations = list(THERMAL) - -/obj/item/dnainjector/glow - name = "\improper DNA injector (Glowy)" - add_mutations = list(GLOWY) - -/obj/item/dnainjector/removeglow - name = "\improper DNA injector (Anti-Glowy)" - remove_mutations = list(GLOWY) - -/obj/item/dnainjector/antiglow - name = "\improper DNA injector (Antiglowy)" - add_mutations = list(ANTIGLOWY) - -/obj/item/dnainjector/removeantiglow - name = "\improper DNA injector (Anti-Antiglowy)" - remove_mutations = list(ANTIGLOWY) - -/obj/item/dnainjector/timed - var/duration = 600 - -/obj/item/dnainjector/timed/inject(mob/living/carbon/M, mob/user) - if(M.stat == DEAD) //prevents dead people from having their DNA changed - to_chat(user, "You can't modify [M]'s DNA while [M.p_theyre()] dead.") - return FALSE - - if(M.has_dna() && !(HAS_TRAIT(M, TRAIT_BADDNA))) - M.radiation += rand(20/(damage_coeff ** 2),50/(damage_coeff ** 2)) - var/log_msg = "[key_name(user)] injected [key_name(M)] with the [name]" - var/endtime = world.time+duration - for(var/mutation in remove_mutations) - if(mutation == RACEMUT) - if(ishuman(M)) - continue - M = M.dna.remove_mutation(mutation) - else - M.dna.remove_mutation(mutation) - for(var/mutation in add_mutations) - if(M.dna.get_mutation(mutation)) - continue //Skip permanent mutations we already have. - if(mutation == RACEMUT && ishuman(M)) - message_admins("[ADMIN_LOOKUPFLW(user)] injected [key_name_admin(M)] with the [name] (MONKEY)") - log_msg += " (MONKEY)" - M = M.dna.add_mutation(mutation, MUT_OTHER, endtime) - else - M.dna.add_mutation(mutation, MUT_OTHER, endtime) - if(fields) - if(fields["name"] && fields["UE"] && fields["blood_type"]) - if(!M.dna.previous["name"]) - M.dna.previous["name"] = M.real_name - if(!M.dna.previous["UE"]) - M.dna.previous["UE"] = M.dna.unique_enzymes - if(!M.dna.previous["blood_type"]) - M.dna.previous["blood_type"] = M.dna.blood_type - M.real_name = fields["name"] - M.dna.unique_enzymes = fields["UE"] - M.name = M.real_name - M.dna.blood_type = fields["blood_type"] - M.dna.temporary_mutations[UE_CHANGED] = endtime - if(fields["UI"]) //UI+UE - if(!M.dna.previous["UI"]) - M.dna.previous["UI"] = M.dna.uni_identity - M.dna.uni_identity = merge_text(M.dna.uni_identity, fields["UI"]) - M.updateappearance(mutations_overlay_update=1) - M.dna.temporary_mutations[UI_CHANGED] = endtime - log_attack("[log_msg] [loc_name(user)]") - return TRUE - else - return FALSE - -/obj/item/dnainjector/timed/hulk - name = "\improper DNA injector (Hulk)" - desc = "This will make you big and strong, but give you a bad skin condition." - add_mutations = list(HULK) - -/obj/item/dnainjector/timed/h2m - name = "\improper DNA injector (Human > Monkey)" - desc = "Will make you a flea bag." - add_mutations = list(RACEMUT) - -/obj/item/dnainjector/activator - name = "\improper DNA activator" - desc = "Activates the current mutation on injection, if the subject has it." - var/doitanyway = FALSE - var/research = FALSE //Set to true to get expended and filled injectors for chromosomes - var/filled = FALSE - -/obj/item/dnainjector/activator/inject(mob/living/carbon/M, mob/user) - if(M.has_dna() && !HAS_TRAIT(M, TRAIT_GENELESS) && !HAS_TRAIT(M, TRAIT_BADDNA)) - M.radiation += rand(20/(damage_coeff ** 2),50/(damage_coeff ** 2)) - var/log_msg = "[key_name(user)] injected [key_name(M)] with the [name]" - var/pref = "" - for(var/mutation in add_mutations) - var/datum/mutation/human/HM = mutation - if(istype(HM, /datum/mutation/human)) - mutation = HM.type - if(!M.dna.activate_mutation(HM)) - if(!doitanyway) - log_msg += "(FAILED)" - else - M.dna.add_mutation(HM, MUT_EXTRA) - pref = "expended" - else if(research && M.client) - filled = TRUE - pref = "filled" - else - pref = "expended" - log_msg += "([mutation])" - name = "[pref] [name]" - log_attack("[log_msg] [loc_name(user)]") - return TRUE - return FALSE +/obj/item/dnainjector + name = "\improper DNA injector" + desc = "This injects the person with DNA." + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "dnainjector" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + throw_speed = 3 + throw_range = 5 + w_class = WEIGHT_CLASS_TINY + + var/damage_coeff = 1 + var/list/fields + var/list/add_mutations = list() + var/list/remove_mutations = list() + + var/used = 0 + +/obj/item/dnainjector/attack_paw(mob/user) + return attack_hand(user) + +/obj/item/dnainjector/proc/inject(mob/living/carbon/M, mob/user) + if(M.has_dna() && !HAS_TRAIT(M, TRAIT_GENELESS) && !HAS_TRAIT(M, TRAIT_BADDNA)) + M.radiation += rand(20/(damage_coeff ** 2),50/(damage_coeff ** 2)) + var/log_msg = "[key_name(user)] injected [key_name(M)] with the [name]" + for(var/HM in remove_mutations) + M.dna.remove_mutation(HM) + for(var/HM in add_mutations) + if(HM == RACEMUT) + message_admins("[ADMIN_LOOKUPFLW(user)] injected [key_name_admin(M)] with the [name] (MONKEY)") + log_msg += " (MONKEY)" + if(M.dna.mutation_in_sequence(HM)) + M.dna.activate_mutation(HM) + else + M.dna.add_mutation(HM, MUT_EXTRA) + if(fields) + if(fields["name"] && fields["UE"] && fields["blood_type"]) + M.real_name = fields["name"] + M.dna.unique_enzymes = fields["UE"] + M.name = M.real_name + M.dna.blood_type = fields["blood_type"] + if(fields["UI"]) //UI+UE + M.dna.uni_identity = merge_text(M.dna.uni_identity, fields["UI"]) + M.updateappearance(mutations_overlay_update=1) + log_attack("[log_msg] [loc_name(user)]") + return TRUE + return FALSE + +/obj/item/dnainjector/attack(mob/target, mob/user) + if(!user.IsAdvancedToolUser()) + to_chat(user, "You don't have the dexterity to do this!") + return + if(used) + to_chat(user, "This injector is used up!") + return + if(ishuman(target)) + var/mob/living/carbon/human/humantarget = target + if (!humantarget.can_inject(user, 1)) + return + log_combat(user, target, "attempted to inject", src) + + if(target != user) + target.visible_message("[user] is trying to inject [target] with [src]!", \ + "[user] is trying to inject you with [src]!") + if(!do_mob(user, target) || used) + return + target.visible_message("[user] injects [target] with the syringe with [src]!", \ + "[user] injects you with the syringe with [src]!") + + else + to_chat(user, "You inject yourself with [src].") + + log_combat(user, target, "injected", src) + + if(!inject(target, user)) //Now we actually do the heavy lifting. + to_chat(user, "It appears that [target] does not have compatible DNA.") + + used = 1 + icon_state = "dnainjector0" + desc += " This one is used up." + + +/obj/item/dnainjector/antihulk + name = "\improper DNA injector (Anti-Hulk)" + desc = "Cures green skin." + remove_mutations = list(HULK) + +/obj/item/dnainjector/hulkmut + name = "\improper DNA injector (Hulk)" + desc = "This will make you big and strong, but give you a bad skin condition." + add_mutations = list(HULK) + +/obj/item/dnainjector/firebreath + name = "\improper DNA injector (Fire Breath)" + desc = "Restores the dragon ancestry." + add_mutations = list(FIREBREATH) + +/obj/item/dnainjector/xraymut + name = "\improper DNA injector (X-ray)" + desc = "Finally you can see what the Captain does." + add_mutations = list(XRAY) + +/obj/item/dnainjector/antixray + name = "\improper DNA injector (Anti-X-ray)" + desc = "It will make you see harder." + remove_mutations = list(XRAY) + +///////////////////////////////////// +/obj/item/dnainjector/antiglasses + name = "\improper DNA injector (Anti-Glasses)" + desc = "Toss away those glasses!" + remove_mutations = list(BADSIGHT) + +/obj/item/dnainjector/glassesmut + name = "\improper DNA injector (Glasses)" + desc = "Will make you need dorkish glasses." + add_mutations = list(BADSIGHT) + +/obj/item/dnainjector/epimut + name = "\improper DNA injector (Epi.)" + desc = "Shake shake shake the room!" + add_mutations = list(EPILEPSY) + +/obj/item/dnainjector/antiepi + name = "\improper DNA injector (Anti-Epi.)" + desc = "Will fix you up from shaking the room." + remove_mutations = list(EPILEPSY) +//////////////////////////////////// +/obj/item/dnainjector/anticough + name = "\improper DNA injector (Anti-Cough)" + desc = "Will stop that awful noise." + remove_mutations = list(COUGH) + +/obj/item/dnainjector/coughmut + name = "\improper DNA injector (Cough)" + desc = "Will bring forth a sound of horror from your throat." + add_mutations = list(COUGH) + +/obj/item/dnainjector/antidwarf + name = "\improper DNA injector (Anti-Dwarfism)" + desc = "Helps you grow big and strong." + remove_mutations = list(DWARFISM) + +/obj/item/dnainjector/dwarf + name = "\improper DNA injector (Dwarfism)" + desc = "It's a small world after all." + add_mutations = list(DWARFISM) + +/obj/item/dnainjector/clumsymut + name = "\improper DNA injector (Clumsy)" + desc = "Makes clown minions." + add_mutations = list(CLOWNMUT) + +/obj/item/dnainjector/anticlumsy + name = "\improper DNA injector (Anti-Clumsy)" + desc = "Apply this for Security Clown." + remove_mutations = list(CLOWNMUT) + +/obj/item/dnainjector/antitour + name = "\improper DNA injector (Anti-Tour.)" + desc = "Will cure Tourette's." + remove_mutations = list(TOURETTES) + +/obj/item/dnainjector/tourmut + name = "\improper DNA injector (Tour.)" + desc = "Gives you a nasty case of Tourette's." + add_mutations = list(TOURETTES) + +/obj/item/dnainjector/stuttmut + name = "\improper DNA injector (Stutt.)" + desc = "Makes you s-s-stuttterrr." + add_mutations = list(NERVOUS) + +/obj/item/dnainjector/antistutt + name = "\improper DNA injector (Anti-Stutt.)" + desc = "Fixes that speaking impairment." + remove_mutations = list(NERVOUS) + +/obj/item/dnainjector/antifire + name = "\improper DNA injector (Anti-Fire)" + desc = "Cures fire." + remove_mutations = list(SPACEMUT) + +/obj/item/dnainjector/firemut + name = "\improper DNA injector (Fire)" + desc = "Gives you fire." + add_mutations = list(SPACEMUT) + +/obj/item/dnainjector/blindmut + name = "\improper DNA injector (Blind)" + desc = "Makes you not see anything." + add_mutations = list(BLINDMUT) + +/obj/item/dnainjector/antiblind + name = "\improper DNA injector (Anti-Blind)" + desc = "IT'S A MIRACLE!!!" + remove_mutations = list(BLINDMUT) + +/obj/item/dnainjector/antitele + name = "\improper DNA injector (Anti-Tele.)" + desc = "Will make you not able to control your mind." + remove_mutations = list(TK) + +/obj/item/dnainjector/telemut + name = "\improper DNA injector (Tele.)" + desc = "Super brain man!" + add_mutations = list(TK) + +/obj/item/dnainjector/telemut/darkbundle + name = "\improper DNA injector" + desc = "Good. Let the hate flow through you." + +/obj/item/dnainjector/deafmut + name = "\improper DNA injector (Deaf)" + desc = "Sorry, what did you say?" + add_mutations = list(DEAFMUT) + +/obj/item/dnainjector/antideaf + name = "\improper DNA injector (Anti-Deaf)" + desc = "Will make you hear once more." + remove_mutations = list(DEAFMUT) + +/obj/item/dnainjector/h2m + name = "\improper DNA injector (Human > Monkey)" + desc = "Will make you a flea bag." + add_mutations = list(RACEMUT) + +/obj/item/dnainjector/m2h + name = "\improper DNA injector (Monkey > Human)" + desc = "Will make you...less hairy." + remove_mutations = list(RACEMUT) + +/obj/item/dnainjector/antichameleon + name = "\improper DNA injector (Anti-Chameleon)" + remove_mutations = list(CHAMELEON) + +/obj/item/dnainjector/chameleonmut + name = "\improper DNA injector (Chameleon)" + add_mutations = list(CHAMELEON) + +/obj/item/dnainjector/antiwacky + name = "\improper DNA injector (Anti-Wacky)" + remove_mutations = list(WACKY) + +/obj/item/dnainjector/wackymut + name = "\improper DNA injector (Wacky)" + add_mutations = list(WACKY) + +/obj/item/dnainjector/antimute + name = "\improper DNA injector (Anti-Mute)" + remove_mutations = list(MUT_MUTE) + +/obj/item/dnainjector/mutemut + name = "\improper DNA injector (Mute)" + add_mutations = list(MUT_MUTE) + +/obj/item/dnainjector/unintelligiblemut + name = "\improper DNA injector (Unintelligible)" + add_mutations = list(UNINTELLIGIBLE) + +/obj/item/dnainjector/antiunintelligible + name = "\improper DNA injector (Anti-Unintelligible)" + remove_mutations = list(UNINTELLIGIBLE) + +/obj/item/dnainjector/swedishmut + name = "\improper DNA injector (Swedish)" + add_mutations = list(SWEDISH) + +/obj/item/dnainjector/antiswedish + name = "\improper DNA injector (Anti-Swedish)" + remove_mutations = list(SWEDISH) + +/obj/item/dnainjector/chavmut + name = "\improper DNA injector (Chav)" + add_mutations = list(CHAV) + +/obj/item/dnainjector/antichav + name = "\improper DNA injector (Anti-Chav)" + remove_mutations = list(CHAV) + +/obj/item/dnainjector/elvismut + name = "\improper DNA injector (Elvis)" + add_mutations = list(ELVIS) + +/obj/item/dnainjector/antielvis + name = "\improper DNA injector (Anti-Elvis)" + remove_mutations = list(ELVIS) + +/obj/item/dnainjector/lasereyesmut + name = "\improper DNA injector (Laser Eyes)" + add_mutations = list(LASEREYES) + +/obj/item/dnainjector/antilasereyes + name = "\improper DNA injector (Anti-Laser Eyes)" + remove_mutations = list(LASEREYES) + +/obj/item/dnainjector/void + name = "\improper DNA injector (Void)" + add_mutations = list(VOID) + +/obj/item/dnainjector/antivoid + name = "\improper DNA injector (Anti-Void)" + remove_mutations = list(VOID) + +/obj/item/dnainjector/antenna + name = "\improper DNA injector (Antenna)" + add_mutations = list(ANTENNA) + +/obj/item/dnainjector/antiantenna + name = "\improper DNA injector (Anti-Antenna)" + remove_mutations = list(ANTENNA) + +/obj/item/dnainjector/paranoia + name = "\improper DNA injector (Paranoia)" + add_mutations = list(PARANOIA) + +/obj/item/dnainjector/antiparanoia + name = "\improper DNA injector (Anti-Paranoia)" + remove_mutations = list(PARANOIA) + +/obj/item/dnainjector/mindread + name = "\improper DNA injector (Mindread)" + add_mutations = list(MINDREAD) + +/obj/item/dnainjector/antimindread + name = "\improper DNA injector (Anti-Mindread)" + remove_mutations = list(MINDREAD) + +/obj/item/dnainjector/radioactive + name = "\improper DNA injector (Radioactive)" + add_mutations = list(RADIOACTIVE) + +/obj/item/dnainjector/antiradioactive + name = "\improper DNA injector (Anti-Radioactive)" + remove_mutations = list(RADIOACTIVE) +/obj/item/dnainjector/olfaction + name = "\improper DNA injector (Olfaction)" + add_mutations = list(OLFACTION) + +/obj/item/dnainjector/antiolfaction + name = "\improper DNA injector (Anti-Olfaction)" + remove_mutations = list(OLFACTION) + +/obj/item/dnainjector/insulated + name = "\improper DNA injector (Insulated)" + add_mutations = list(INSULATED) + +/obj/item/dnainjector/antiinsulated + name = "\improper DNA injector (Anti-Insulated)" + remove_mutations = list(INSULATED) + +/obj/item/dnainjector/shock + name = "\improper DNA injector (Shock Touch)" + add_mutations = list(SHOCKTOUCH) + +/obj/item/dnainjector/antishock + name = "\improper DNA injector (Anti-Shock Touch)" + remove_mutations = list(SHOCKTOUCH) + +/obj/item/dnainjector/spacialinstability + name = "\improper DNA injector (Spacial Instability)" + add_mutations = list(BADBLINK) + +/obj/item/dnainjector/antispacialinstability + name = "\improper DNA injector (Anti-Spacial Instability)" + remove_mutations = list(BADBLINK) + +/obj/item/dnainjector/acidflesh + name = "\improper DNA injector (Acid Flesh)" + add_mutations = list(ACIDFLESH) + +/obj/item/dnainjector/antiacidflesh + name = "\improper DNA injector (Acid Flesh)" + remove_mutations = list(ACIDFLESH) + +/obj/item/dnainjector/gigantism + name = "\improper DNA injector (Gigantism)" + add_mutations = list(GIGANTISM) + +/obj/item/dnainjector/antigigantism + name = "\improper DNA injector (Anti-Gigantism)" + remove_mutations = list(GIGANTISM) + +/obj/item/dnainjector/spastic + name = "\improper DNA injector (Spastic)" + add_mutations = list(SPASTIC) + +/obj/item/dnainjector/antispastic + name = "\improper DNA injector (Anti-Spastic)" + remove_mutations = list(SPASTIC) + +/obj/item/dnainjector/twoleftfeet + name = "\improper DNA injector (Two Left Feet)" + add_mutations = list(EXTRASTUN) + +/obj/item/dnainjector/antitwoleftfeet + name = "\improper DNA injector (Anti-Two Left Feet)" + remove_mutations = list(EXTRASTUN) + +/obj/item/dnainjector/geladikinesis + name = "\improper DNA injector (Geladikinesis)" + add_mutations = list(GELADIKINESIS) + +/obj/item/dnainjector/antigeladikinesis + name = "\improper DNA injector (Anti-Geladikinesis)" + remove_mutations = list(GELADIKINESIS) + +/obj/item/dnainjector/cryokinesis + name = "\improper DNA injector (Cryokinesis)" + add_mutations = list(CRYOKINESIS) + +/obj/item/dnainjector/anticryokinesis + name = "\improper DNA injector (Anti-Cryokinesis)" + remove_mutations = list(CRYOKINESIS) + +/obj/item/dnainjector/thermal + name = "\improper DNA injector (Thermal Vision)" + add_mutations = list(THERMAL) + +/obj/item/dnainjector/antithermal + name = "\improper DNA injector (Anti-Thermal Vision)" + remove_mutations = list(THERMAL) + +/obj/item/dnainjector/glow + name = "\improper DNA injector (Glowy)" + add_mutations = list(GLOWY) + +/obj/item/dnainjector/removeglow + name = "\improper DNA injector (Anti-Glowy)" + remove_mutations = list(GLOWY) + +/obj/item/dnainjector/antiglow + name = "\improper DNA injector (Antiglowy)" + add_mutations = list(ANTIGLOWY) + +/obj/item/dnainjector/removeantiglow + name = "\improper DNA injector (Anti-Antiglowy)" + remove_mutations = list(ANTIGLOWY) + +/obj/item/dnainjector/timed + var/duration = 600 + +/obj/item/dnainjector/timed/inject(mob/living/carbon/M, mob/user) + if(M.stat == DEAD) //prevents dead people from having their DNA changed + to_chat(user, "You can't modify [M]'s DNA while [M.p_theyre()] dead.") + return FALSE + + if(M.has_dna() && !(HAS_TRAIT(M, TRAIT_BADDNA))) + M.radiation += rand(20/(damage_coeff ** 2),50/(damage_coeff ** 2)) + var/log_msg = "[key_name(user)] injected [key_name(M)] with the [name]" + var/endtime = world.time+duration + for(var/mutation in remove_mutations) + if(mutation == RACEMUT) + if(ishuman(M)) + continue + M = M.dna.remove_mutation(mutation) + else + M.dna.remove_mutation(mutation) + for(var/mutation in add_mutations) + if(M.dna.get_mutation(mutation)) + continue //Skip permanent mutations we already have. + if(mutation == RACEMUT && ishuman(M)) + message_admins("[ADMIN_LOOKUPFLW(user)] injected [key_name_admin(M)] with the [name] (MONKEY)") + log_msg += " (MONKEY)" + M = M.dna.add_mutation(mutation, MUT_OTHER, endtime) + else + M.dna.add_mutation(mutation, MUT_OTHER, endtime) + if(fields) + if(fields["name"] && fields["UE"] && fields["blood_type"]) + if(!M.dna.previous["name"]) + M.dna.previous["name"] = M.real_name + if(!M.dna.previous["UE"]) + M.dna.previous["UE"] = M.dna.unique_enzymes + if(!M.dna.previous["blood_type"]) + M.dna.previous["blood_type"] = M.dna.blood_type + M.real_name = fields["name"] + M.dna.unique_enzymes = fields["UE"] + M.name = M.real_name + M.dna.blood_type = fields["blood_type"] + M.dna.temporary_mutations[UE_CHANGED] = endtime + if(fields["UI"]) //UI+UE + if(!M.dna.previous["UI"]) + M.dna.previous["UI"] = M.dna.uni_identity + M.dna.uni_identity = merge_text(M.dna.uni_identity, fields["UI"]) + M.updateappearance(mutations_overlay_update=1) + M.dna.temporary_mutations[UI_CHANGED] = endtime + log_attack("[log_msg] [loc_name(user)]") + return TRUE + else + return FALSE + +/obj/item/dnainjector/timed/hulk + name = "\improper DNA injector (Hulk)" + desc = "This will make you big and strong, but give you a bad skin condition." + add_mutations = list(HULK) + +/obj/item/dnainjector/timed/h2m + name = "\improper DNA injector (Human > Monkey)" + desc = "Will make you a flea bag." + add_mutations = list(RACEMUT) + +/obj/item/dnainjector/activator + name = "\improper DNA activator" + desc = "Activates the current mutation on injection, if the subject has it." + var/doitanyway = FALSE + var/research = FALSE //Set to true to get expended and filled injectors for chromosomes + var/filled = FALSE + +/obj/item/dnainjector/activator/inject(mob/living/carbon/M, mob/user) + if(M.has_dna() && !HAS_TRAIT(M, TRAIT_GENELESS) && !HAS_TRAIT(M, TRAIT_BADDNA)) + M.radiation += rand(20/(damage_coeff ** 2),50/(damage_coeff ** 2)) + var/log_msg = "[key_name(user)] injected [key_name(M)] with the [name]" + var/pref = "" + for(var/mutation in add_mutations) + var/datum/mutation/human/HM = mutation + if(istype(HM, /datum/mutation/human)) + mutation = HM.type + if(!M.dna.activate_mutation(HM)) + if(!doitanyway) + log_msg += "(FAILED)" + else + M.dna.add_mutation(HM, MUT_EXTRA) + pref = "expended" + else if(research && M.client) + filled = TRUE + pref = "filled" + else + pref = "expended" + log_msg += "([mutation])" + name = "[pref] [name]" + log_attack("[log_msg] [loc_name(user)]") + return TRUE + return FALSE diff --git a/code/game/objects/items/eightball.dm b/code/game/objects/items/eightball.dm index 775fdd044683..8c34d9352942 100644 --- a/code/game/objects/items/eightball.dm +++ b/code/game/objects/items/eightball.dm @@ -192,11 +192,13 @@ return top_vote -/obj/item/toy/eightball/haunted/ui_interact(mob/user, ui_key="main", datum/tgui/ui=null, force_open=0, datum/tgui/master_ui=null, datum/ui_state/state = GLOB.always_state) +/obj/item/toy/eightball/haunted/ui_state(mob/user) + return GLOB.observer_state - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/item/toy/eightball/haunted/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "EightBallVote", name, 400, 600, master_ui, state) + ui = new(user, src, "EightBallVote", name) ui.open() /obj/item/toy/eightball/haunted/ui_data(mob/user) diff --git a/code/game/objects/items/extinguisher.dm b/code/game/objects/items/extinguisher.dm index 2b4e1111e425..89075c7a7e7e 100644 --- a/code/game/objects/items/extinguisher.dm +++ b/code/game/objects/items/extinguisher.dm @@ -1,249 +1,249 @@ -/obj/item/extinguisher - name = "fire extinguisher" - desc = "A traditional red fire extinguisher." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "fire_extinguisher0" - item_state = "fire_extinguisher" - hitsound = 'sound/weapons/smash.ogg' - flags_1 = CONDUCT_1 - throwforce = 10 - w_class = WEIGHT_CLASS_NORMAL - throw_speed = 2 - throw_range = 7 - force = 10 - custom_materials = list(/datum/material/iron = 90) - attack_verb = list("slammed", "whacked", "bashed", "thunked", "battered", "bludgeoned", "thrashed") - dog_fashion = /datum/dog_fashion/back - resistance_flags = FIRE_PROOF - var/max_water = 50 - var/last_use = 1 - var/chem = /datum/reagent/water - var/safety = TRUE - var/refilling = FALSE - var/tanktype = /obj/structure/reagent_dispensers/watertank - var/sprite_name = "fire_extinguisher" - var/power = 5 //Maximum distance launched water will travel - var/precision = FALSE //By default, turfs picked from a spray are random, set to 1 to make it always have at least one water effect per row - var/cooling_power = 2 //Sets the cooling_temperature of the water reagent datum inside of the extinguisher when it is refilled - -/obj/item/extinguisher/mini - name = "pocket fire extinguisher" - desc = "A light and compact fibreglass-framed model fire extinguisher." - icon_state = "miniFE0" - item_state = "miniFE" - hitsound = null //it is much lighter, after all. - flags_1 = null //doesn't CONDUCT_1 - throwforce = 2 - w_class = WEIGHT_CLASS_SMALL - force = 3 - custom_materials = list(/datum/material/iron = 50, /datum/material/glass = 40) - max_water = 30 - sprite_name = "miniFE" - dog_fashion = null - -/obj/item/extinguisher/proc/refill() - create_reagents(max_water, AMOUNT_VISIBLE) - reagents.add_reagent(chem, max_water) - -/obj/item/extinguisher/Initialize() - . = ..() - refill() - -/obj/item/extinguisher/advanced - name = "advanced fire extinguisher" - desc = "Used to stop thermonuclear fires from spreading inside your engine." - icon_state = "foam_extinguisher0" - //item_state = "foam_extinguisher" needs sprite - dog_fashion = null - chem = /datum/reagent/firefighting_foam - tanktype = /obj/structure/reagent_dispensers/foamtank - sprite_name = "foam_extinguisher" - precision = TRUE - -/obj/item/extinguisher/suicide_act(mob/living/carbon/user) - if (!safety && (reagents.total_volume >= 1)) - user.visible_message("[user] puts the nozzle to [user.p_their()] mouth. It looks like [user.p_theyre()] trying to extinguish the spark of life!") - afterattack(user,user) - return OXYLOSS - else if (safety && (reagents.total_volume >= 1)) - user.visible_message("[user] puts the nozzle to [user.p_their()] mouth... The safety's still on!") - return SHAME - else - user.visible_message("[user] puts the nozzle to [user.p_their()] mouth... [src] is empty!") - return SHAME - -/obj/item/extinguisher/attack_self(mob/user) - safety = !safety - src.icon_state = "[sprite_name][!safety]" - to_chat(user, "The safety is [safety ? "on" : "off"].") - return - -/obj/item/extinguisher/attack(mob/M, mob/user) - if(user.a_intent == INTENT_HELP && !safety) //If we're on help intent and going to spray people, don't bash them. - return FALSE - else - return ..() - -/obj/item/extinguisher/attack_obj(obj/O, mob/living/user) - if(AttemptRefill(O, user)) - refilling = TRUE - return FALSE - else - return ..() - -/obj/item/extinguisher/examine(mob/user) - . = ..() - . += "The safety is [safety ? "on" : "off"]." - - if(reagents.total_volume) - . += "Alt-click to empty it." - -/obj/item/extinguisher/proc/AttemptRefill(atom/target, mob/user) - if(istype(target, tanktype) && target.Adjacent(user)) - var/safety_save = safety - safety = TRUE - if(reagents.total_volume == reagents.maximum_volume) - to_chat(user, "\The [src] is already full!") - safety = safety_save - return 1 - var/obj/structure/reagent_dispensers/W = target //will it work? - var/transferred = W.reagents.trans_to(src, max_water, transfered_by = user) - if(transferred > 0) - to_chat(user, "\The [src] has been refilled by [transferred] units.") - playsound(src.loc, 'sound/effects/refill.ogg', 50, TRUE, -6) - for(var/datum/reagent/water/R in reagents.reagent_list) - R.cooling_temperature = cooling_power - else - to_chat(user, "\The [W] is empty!") - safety = safety_save - return 1 - else - return 0 - -/obj/item/extinguisher/afterattack(atom/target, mob/user , flag) - . = ..() - // Make it so the extinguisher doesn't spray yourself when you click your inventory items - if (target.loc == user) - return - //TODO; Add support for reagents in water. - - if(refilling) - refilling = FALSE - return - if (!safety) - - - if (src.reagents.total_volume < 1) - to_chat(usr, "\The [src] is empty!") - return - - if (world.time < src.last_use + 12) - return - - src.last_use = world.time - - playsound(src.loc, 'sound/effects/extinguish.ogg', 75, TRUE, -3) - - var/direction = get_dir(src,target) - - if(user.buckled && isobj(user.buckled) && !user.buckled.anchored) - var/obj/B = user.buckled - var/movementdirection = turn(direction,180) - addtimer(CALLBACK(src, /obj/item/extinguisher/proc/move_chair, B, movementdirection), 1) - - else user.newtonian_move(turn(direction, 180)) - - //Get all the turfs that can be shot at - var/turf/T = get_turf(target) - var/turf/T1 = get_step(T,turn(direction, 90)) - var/turf/T2 = get_step(T,turn(direction, -90)) - var/list/the_targets = list(T,T1,T2) - if(precision) - var/turf/T3 = get_step(T1, turn(direction, 90)) - var/turf/T4 = get_step(T2,turn(direction, -90)) - the_targets.Add(T3,T4) - - var/list/water_particles=list() - for(var/a=0, a<5, a++) - var/obj/effect/particle_effect/water/W = new /obj/effect/particle_effect/water(get_turf(src)) - var/my_target = pick(the_targets) - water_particles[W] = my_target - // If precise, remove turf from targets so it won't be picked more than once - if(precision) - the_targets -= my_target - var/datum/reagents/R = new/datum/reagents(5) - W.reagents = R - R.my_atom = W - reagents.trans_to(W,1, transfered_by = user) - - //Make em move dat ass, hun - addtimer(CALLBACK(src, /obj/item/extinguisher/proc/move_particles, water_particles), 2) - -//Particle movement loop -/obj/item/extinguisher/proc/move_particles(list/particles, repetition=0) - //Check if there's anything in here first - if(!particles || particles.len == 0) - return - // Second loop: Get all the water particles and make them move to their target - for(var/obj/effect/particle_effect/water/W in particles) - var/turf/my_target = particles[W] - if(!W) - continue - step_towards(W,my_target) - if(!W.reagents) - continue - W.reagents.expose(get_turf(W)) - for(var/A in get_turf(W)) - W.reagents.expose(A) - if(W.loc == my_target) - particles -= W - if(repetition < power) - repetition++ - addtimer(CALLBACK(src, /obj/item/extinguisher/proc/move_particles, particles, repetition), 2) - -//Chair movement loop -/obj/item/extinguisher/proc/move_chair(obj/B, movementdirection, repetition=0) - step(B, movementdirection) - - var/timer_seconds - switch(repetition) - if(0 to 2) - timer_seconds = 1 - if(3 to 4) - timer_seconds = 2 - if(5 to 8) - timer_seconds = 3 - else - return - - repetition++ - addtimer(CALLBACK(src, /obj/item/extinguisher/proc/move_chair, B, movementdirection, repetition), timer_seconds) - -/obj/item/extinguisher/AltClick(mob/user) - if(!user.canUseTopic(src, BE_CLOSE, ismonkey(user))) - return - if(!user.is_holding(src)) - to_chat(user, "You must be holding the [src] in your hands do this!") - return - EmptyExtinguisher(user) - -/obj/item/extinguisher/proc/EmptyExtinguisher(var/mob/user) - if(loc == user && reagents.total_volume) - reagents.clear_reagents() - - var/turf/T = get_turf(loc) - if(isopenturf(T)) - var/turf/open/theturf = T - theturf.MakeSlippery(TURF_WET_WATER, min_wet_time = 10 SECONDS, wet_time_to_add = 5 SECONDS) - - user.visible_message("[user] empties out \the [src] onto the floor using the release valve.", "You quietly empty out \the [src] using its release valve.") - -//firebot assembly -/obj/item/extinguisher/attackby(obj/O, mob/user, params) - if(istype(O, /obj/item/bodypart/l_arm/robot) || istype(O, /obj/item/bodypart/r_arm/robot)) - to_chat(user, "You add [O] to [src].") - qdel(O) - qdel(src) - user.put_in_hands(new /obj/item/bot_assembly/firebot) - else - ..() +/obj/item/extinguisher + name = "fire extinguisher" + desc = "A traditional red fire extinguisher." + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "fire_extinguisher0" + item_state = "fire_extinguisher" + hitsound = 'sound/weapons/smash.ogg' + flags_1 = CONDUCT_1 + throwforce = 10 + w_class = WEIGHT_CLASS_NORMAL + throw_speed = 2 + throw_range = 7 + force = 10 + custom_materials = list(/datum/material/iron = 90) + attack_verb = list("slammed", "whacked", "bashed", "thunked", "battered", "bludgeoned", "thrashed") + dog_fashion = /datum/dog_fashion/back + resistance_flags = FIRE_PROOF + var/max_water = 50 + var/last_use = 1 + var/chem = /datum/reagent/water + var/safety = TRUE + var/refilling = FALSE + var/tanktype = /obj/structure/reagent_dispensers/watertank + var/sprite_name = "fire_extinguisher" + var/power = 5 //Maximum distance launched water will travel + var/precision = FALSE //By default, turfs picked from a spray are random, set to 1 to make it always have at least one water effect per row + var/cooling_power = 2 //Sets the cooling_temperature of the water reagent datum inside of the extinguisher when it is refilled + +/obj/item/extinguisher/mini + name = "pocket fire extinguisher" + desc = "A light and compact fibreglass-framed model fire extinguisher." + icon_state = "miniFE0" + item_state = "miniFE" + hitsound = null //it is much lighter, after all. + flags_1 = null //doesn't CONDUCT_1 + throwforce = 2 + w_class = WEIGHT_CLASS_SMALL + force = 3 + custom_materials = list(/datum/material/iron = 50, /datum/material/glass = 40) + max_water = 30 + sprite_name = "miniFE" + dog_fashion = null + +/obj/item/extinguisher/proc/refill() + create_reagents(max_water, AMOUNT_VISIBLE) + reagents.add_reagent(chem, max_water) + +/obj/item/extinguisher/Initialize() + . = ..() + refill() + +/obj/item/extinguisher/advanced + name = "advanced fire extinguisher" + desc = "Used to stop thermonuclear fires from spreading inside your engine." + icon_state = "foam_extinguisher0" + //item_state = "foam_extinguisher" needs sprite + dog_fashion = null + chem = /datum/reagent/firefighting_foam + tanktype = /obj/structure/reagent_dispensers/foamtank + sprite_name = "foam_extinguisher" + precision = TRUE + +/obj/item/extinguisher/suicide_act(mob/living/carbon/user) + if (!safety && (reagents.total_volume >= 1)) + user.visible_message("[user] puts the nozzle to [user.p_their()] mouth. It looks like [user.p_theyre()] trying to extinguish the spark of life!") + afterattack(user,user) + return OXYLOSS + else if (safety && (reagents.total_volume >= 1)) + user.visible_message("[user] puts the nozzle to [user.p_their()] mouth... The safety's still on!") + return SHAME + else + user.visible_message("[user] puts the nozzle to [user.p_their()] mouth... [src] is empty!") + return SHAME + +/obj/item/extinguisher/attack_self(mob/user) + safety = !safety + src.icon_state = "[sprite_name][!safety]" + to_chat(user, "The safety is [safety ? "on" : "off"].") + return + +/obj/item/extinguisher/attack(mob/M, mob/user) + if(user.a_intent == INTENT_HELP && !safety) //If we're on help intent and going to spray people, don't bash them. + return FALSE + else + return ..() + +/obj/item/extinguisher/attack_obj(obj/O, mob/living/user) + if(AttemptRefill(O, user)) + refilling = TRUE + return FALSE + else + return ..() + +/obj/item/extinguisher/examine(mob/user) + . = ..() + . += "The safety is [safety ? "on" : "off"]." + + if(reagents.total_volume) + . += "Alt-click to empty it." + +/obj/item/extinguisher/proc/AttemptRefill(atom/target, mob/user) + if(istype(target, tanktype) && target.Adjacent(user)) + var/safety_save = safety + safety = TRUE + if(reagents.total_volume == reagents.maximum_volume) + to_chat(user, "\The [src] is already full!") + safety = safety_save + return 1 + var/obj/structure/reagent_dispensers/W = target //will it work? + var/transferred = W.reagents.trans_to(src, max_water, transfered_by = user) + if(transferred > 0) + to_chat(user, "\The [src] has been refilled by [transferred] units.") + playsound(src.loc, 'sound/effects/refill.ogg', 50, TRUE, -6) + for(var/datum/reagent/water/R in reagents.reagent_list) + R.cooling_temperature = cooling_power + else + to_chat(user, "\The [W] is empty!") + safety = safety_save + return 1 + else + return 0 + +/obj/item/extinguisher/afterattack(atom/target, mob/user , flag) + . = ..() + // Make it so the extinguisher doesn't spray yourself when you click your inventory items + if (target.loc == user) + return + //TODO; Add support for reagents in water. + + if(refilling) + refilling = FALSE + return + if (!safety) + + + if (src.reagents.total_volume < 1) + to_chat(usr, "\The [src] is empty!") + return + + if (world.time < src.last_use + 12) + return + + src.last_use = world.time + + playsound(src.loc, 'sound/effects/extinguish.ogg', 75, TRUE, -3) + + var/direction = get_dir(src,target) + + if(user.buckled && isobj(user.buckled) && !user.buckled.anchored) + var/obj/B = user.buckled + var/movementdirection = turn(direction,180) + addtimer(CALLBACK(src, /obj/item/extinguisher/proc/move_chair, B, movementdirection), 1) + + else user.newtonian_move(turn(direction, 180)) + + //Get all the turfs that can be shot at + var/turf/T = get_turf(target) + var/turf/T1 = get_step(T,turn(direction, 90)) + var/turf/T2 = get_step(T,turn(direction, -90)) + var/list/the_targets = list(T,T1,T2) + if(precision) + var/turf/T3 = get_step(T1, turn(direction, 90)) + var/turf/T4 = get_step(T2,turn(direction, -90)) + the_targets.Add(T3,T4) + + var/list/water_particles=list() + for(var/a=0, a<5, a++) + var/obj/effect/particle_effect/water/W = new /obj/effect/particle_effect/water(get_turf(src)) + var/my_target = pick(the_targets) + water_particles[W] = my_target + // If precise, remove turf from targets so it won't be picked more than once + if(precision) + the_targets -= my_target + var/datum/reagents/R = new/datum/reagents(5) + W.reagents = R + R.my_atom = W + reagents.trans_to(W,1, transfered_by = user) + + //Make em move dat ass, hun + addtimer(CALLBACK(src, /obj/item/extinguisher/proc/move_particles, water_particles), 2) + +//Particle movement loop +/obj/item/extinguisher/proc/move_particles(list/particles, repetition=0) + //Check if there's anything in here first + if(!particles || particles.len == 0) + return + // Second loop: Get all the water particles and make them move to their target + for(var/obj/effect/particle_effect/water/W in particles) + var/turf/my_target = particles[W] + if(!W) + continue + step_towards(W,my_target) + if(!W.reagents) + continue + W.reagents.expose(get_turf(W)) + for(var/A in get_turf(W)) + W.reagents.expose(A) + if(W.loc == my_target) + particles -= W + if(repetition < power) + repetition++ + addtimer(CALLBACK(src, /obj/item/extinguisher/proc/move_particles, particles, repetition), 2) + +//Chair movement loop +/obj/item/extinguisher/proc/move_chair(obj/B, movementdirection, repetition=0) + step(B, movementdirection) + + var/timer_seconds + switch(repetition) + if(0 to 2) + timer_seconds = 1 + if(3 to 4) + timer_seconds = 2 + if(5 to 8) + timer_seconds = 3 + else + return + + repetition++ + addtimer(CALLBACK(src, /obj/item/extinguisher/proc/move_chair, B, movementdirection, repetition), timer_seconds) + +/obj/item/extinguisher/AltClick(mob/user) + if(!user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + return + if(!user.is_holding(src)) + to_chat(user, "You must be holding the [src] in your hands do this!") + return + EmptyExtinguisher(user) + +/obj/item/extinguisher/proc/EmptyExtinguisher(var/mob/user) + if(loc == user && reagents.total_volume) + reagents.clear_reagents() + + var/turf/T = get_turf(loc) + if(isopenturf(T)) + var/turf/open/theturf = T + theturf.MakeSlippery(TURF_WET_WATER, min_wet_time = 10 SECONDS, wet_time_to_add = 5 SECONDS) + + user.visible_message("[user] empties out \the [src] onto the floor using the release valve.", "You quietly empty out \the [src] using its release valve.") + +//firebot assembly +/obj/item/extinguisher/attackby(obj/O, mob/user, params) + if(istype(O, /obj/item/bodypart/l_arm/robot) || istype(O, /obj/item/bodypart/r_arm/robot)) + to_chat(user, "You add [O] to [src].") + qdel(O) + qdel(src) + user.put_in_hands(new /obj/item/bot_assembly/firebot) + else + ..() diff --git a/code/game/objects/items/grenades/chem_grenade.dm b/code/game/objects/items/grenades/chem_grenade.dm index c39d9783710e..56b743f1184f 100644 --- a/code/game/objects/items/grenades/chem_grenade.dm +++ b/code/game/objects/items/grenades/chem_grenade.dm @@ -1,584 +1,584 @@ -/obj/item/grenade/chem_grenade - name = "chemical grenade" - desc = "A custom made grenade." - icon_state = "chemg" - item_state = "flashbang" - w_class = WEIGHT_CLASS_SMALL - force = 2 - var/stage = GRENADE_EMPTY - var/list/obj/item/reagent_containers/glass/beakers = list() - var/list/allowed_containers = list(/obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/glass/bottle) - var/list/banned_containers = list(/obj/item/reagent_containers/glass/beaker/bluespace) //Containers to exclude from specific grenade subtypes - var/affected_area = 3 - var/ignition_temp = 10 // The amount of heat added to the reagents when this grenade goes off. - var/threatscale = 1 // Used by advanced grenades to make them slightly more worthy. - var/no_splash = FALSE //If the grenade deletes even if it has no reagents to splash with. Used for slime core reactions. - var/casedesc = "This basic model accepts both beakers and bottles. It heats contents by 10°K upon ignition." // Appears when examining empty casings. - var/obj/item/assembly/prox_sensor/landminemode = null - -/obj/item/grenade/chem_grenade/ComponentInitialize() - . = ..() - AddComponent(/datum/component/empprotection, EMP_PROTECT_WIRES) - -/obj/item/grenade/chem_grenade/Initialize() - . = ..() - create_reagents(1000) - stage_change() // If no argument is set, it will change the stage to the current stage, useful for stock grenades that start READY. - wires = new /datum/wires/explosive/chem_grenade(src) - -/obj/item/grenade/chem_grenade/examine(mob/user) - display_timer = (stage == GRENADE_READY) //show/hide the timer based on assembly state - . = ..() - if(user.can_see_reagents()) - if(beakers.len) - . += "You scan the grenade and detect the following reagents:" - for(var/obj/item/reagent_containers/glass/G in beakers) - for(var/datum/reagent/R in G.reagents.reagent_list) - . += "[R.volume] units of [R.name] in the [G.name]." - if(beakers.len == 1) - . += "You detect no second beaker in the grenade." - else - . += "You scan the grenade, but detect nothing." - else if(stage != GRENADE_READY && beakers.len) - if(beakers.len == 2 && beakers[1].name == beakers[2].name) - . += "You see two [beakers[1].name]s inside the grenade." - else - for(var/obj/item/reagent_containers/glass/G in beakers) - . += "You see a [G.name] inside the grenade." - -/obj/item/grenade/chem_grenade/attack_self(mob/user) - if(stage == GRENADE_READY && !active) - ..() - if(stage == GRENADE_WIRED) - wires.interact(user) - -/obj/item/grenade/chem_grenade/attackby(obj/item/I, mob/user, params) - if(istype(I,/obj/item/assembly) && stage == GRENADE_WIRED) - wires.interact(user) - if(I.tool_behaviour == TOOL_SCREWDRIVER) - if(stage == GRENADE_WIRED) - if(beakers.len) - stage_change(GRENADE_READY) - to_chat(user, "You lock the [initial(name)] assembly.") - I.play_tool_sound(src, 25) - else - to_chat(user, "You need to add at least one beaker before locking the [initial(name)] assembly!") - else if(stage == GRENADE_READY) - det_time = det_time == 50 ? 30 : 50 //toggle between 30 and 50 - if(landminemode) - landminemode.time = det_time * 0.1 //overwrites the proxy sensor activation timer - - to_chat(user, "You modify the time delay. It's set for [DisplayTimeText(det_time)].") - else - to_chat(user, "You need to add a wire!") - return - else if(stage == GRENADE_WIRED && is_type_in_list(I, allowed_containers)) - . = TRUE //no afterattack - if(is_type_in_list(I, banned_containers)) - to_chat(user, "[src] is too small to fit [I]!") // this one hits home huh anon? - return - if(beakers.len == 2) - to_chat(user, "[src] can not hold more containers!") - return - else - if(I.reagents.total_volume) - if(!user.transferItemToLoc(I, src)) - return - to_chat(user, "You add [I] to the [initial(name)] assembly.") - beakers += I - var/reagent_list = pretty_string_from_reagent_list(I.reagents) - user.log_message("inserted [I] ([reagent_list]) into [src]",LOG_GAME) - else - to_chat(user, "[I] is empty!") - - else if(stage == GRENADE_EMPTY && istype(I, /obj/item/stack/cable_coil)) - var/obj/item/stack/cable_coil/C = I - if (C.use(1)) - det_time = 50 // In case the cable_coil was removed and readded. - stage_change(GRENADE_WIRED) - to_chat(user, "You rig the [initial(name)] assembly.") - else - to_chat(user, "You need one length of coil to wire the assembly!") - return - - else if(stage == GRENADE_READY && I.tool_behaviour == TOOL_WIRECUTTER && !active) - stage_change(GRENADE_WIRED) - to_chat(user, "You unlock the [initial(name)] assembly.") - - else if(stage == GRENADE_WIRED && I.tool_behaviour == TOOL_WRENCH) - if(beakers.len) - for(var/obj/O in beakers) - O.forceMove(drop_location()) - if(!O.reagents) - continue - var/reagent_list = pretty_string_from_reagent_list(O.reagents) - user.log_message("removed [O] ([reagent_list]) from [src]", LOG_GAME) - beakers = list() - to_chat(user, "You open the [initial(name)] assembly and remove the payload.") - return - wires.detach_assembly(wires.get_wire(1)) - new /obj/item/stack/cable_coil(get_turf(src),1) - stage_change(GRENADE_EMPTY) - to_chat(user, "You remove the activation mechanism from the [initial(name)] assembly.") - else - return ..() - -/obj/item/grenade/chem_grenade/proc/stage_change(N) - if(N) - stage = N - if(stage == GRENADE_EMPTY) - name = "[initial(name)] casing" - desc = "A do it yourself [initial(name)]! [initial(casedesc)]" - icon_state = initial(icon_state) - else if(stage == GRENADE_WIRED) - name = "unsecured [initial(name)]" - desc = "An unsecured [initial(name)] assembly." - icon_state = "[initial(icon_state)]_ass" - else if(stage == GRENADE_READY) - name = initial(name) - desc = initial(desc) - icon_state = "[initial(icon_state)]_locked" - -/obj/item/grenade/chem_grenade/on_found(mob/finder) - var/obj/item/assembly/A = wires.get_attached(wires.get_wire(1)) - if(A) - A.on_found(finder) - -/obj/item/grenade/chem_grenade/log_grenade(mob/user, turf/T) - var/reagent_string = "" - var/beaker_number = 1 - for(var/obj/exploded_beaker in beakers) - if(!exploded_beaker.reagents) - continue - reagent_string += " ([exploded_beaker.name] [beaker_number++] : " + pretty_string_from_reagent_list(exploded_beaker.reagents.reagent_list) + ");" - if(landminemode) - log_bomber(user, "activated a proxy", src, "containing:[reagent_string]") - else - log_bomber(user, "primed a", src, "containing:[reagent_string]") - -/obj/item/grenade/chem_grenade/preprime(mob/user, delayoverride, msg = TRUE, volume = 60) - var/turf/T = get_turf(src) - log_grenade(user, T) //Inbuilt admin procs already handle null users - if(user) - add_fingerprint(user) - if(msg) - if(landminemode) - to_chat(user, "You prime [src], activating its proximity sensor.") - else - to_chat(user, "You prime [src]! [DisplayTimeText(det_time)]!") - playsound(src, 'sound/weapons/armbomb.ogg', volume, TRUE) - icon_state = initial(icon_state) + "_active" - if(landminemode) - landminemode.activate() - return - active = TRUE - addtimer(CALLBACK(src, .proc/prime), isnull(delayoverride)? det_time : delayoverride) - -/obj/item/grenade/chem_grenade/prime() - if(stage != GRENADE_READY) - return - - . = ..() - var/list/datum/reagents/reactants = list() - for(var/obj/item/reagent_containers/glass/G in beakers) - reactants += G.reagents - - var/turf/detonation_turf = get_turf(src) - - if(!chem_splash(detonation_turf, affected_area, reactants, ignition_temp, threatscale) && !no_splash) - playsound(src, 'sound/items/screwdriver2.ogg', 50, TRUE) - if(beakers.len) - for(var/obj/O in beakers) - O.forceMove(drop_location()) - beakers = list() - stage_change(GRENADE_EMPTY) - active = FALSE - return -// logs from custom assemblies priming are handled by the wire component - log_game("A grenade detonated at [AREACOORD(detonation_turf)]") - - update_mob() - - qdel(src) - -//Large chem grenades accept slime cores and use the appropriately. -/obj/item/grenade/chem_grenade/large - name = "large grenade" - desc = "A custom made large grenade. Larger splash range and increased ignition temperature compared to basic grenades. Fits exotic and bluespace based containers." - casedesc = "This casing affects a larger area than the basic model and can fit exotic containers, including slime cores and bluespace beakers. Heats contents by 25°K upon ignition." - icon_state = "large_grenade" - allowed_containers = list(/obj/item/reagent_containers/glass, /obj/item/reagent_containers/food/condiment, /obj/item/reagent_containers/food/drinks) - banned_containers = list() - affected_area = 5 - ignition_temp = 25 // Large grenades are slightly more effective at setting off heat-sensitive mixtures than smaller grenades. - threatscale = 1.1 // 10% more effective. - -/obj/item/grenade/chem_grenade/large/prime() - if(stage != GRENADE_READY) - return - - for(var/obj/item/slime_extract/S in beakers) - if(S.Uses) - for(var/obj/item/reagent_containers/glass/G in beakers) - G.reagents.trans_to(S, G.reagents.total_volume) - - //If there is still a core (sometimes it's used up) - //and there are reagents left, behave normally, - //otherwise drop it on the ground for timed reactions like gold. - - if(S) - if(S.reagents && S.reagents.total_volume) - for(var/obj/item/reagent_containers/glass/G in beakers) - S.reagents.trans_to(G, S.reagents.total_volume) - else - S.forceMove(get_turf(src)) - no_splash = TRUE - ..() - - //I tried to just put it in the allowed_containers list but - //if you do that it must have reagents. If you're going to - //make a special case you might as well do it explicitly. -Sayu -/obj/item/grenade/chem_grenade/large/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/slime_extract) && stage == GRENADE_WIRED) - if(!user.transferItemToLoc(I, src)) - return - to_chat(user, "You add [I] to the [initial(name)] assembly.") - beakers += I - else - return ..() - -/obj/item/grenade/chem_grenade/cryo // Intended for rare cryogenic mixes. Cools the area moderately upon detonation. - name = "cryo grenade" - desc = "A custom made cryogenic grenade. Rapidly cools contents upon ignition." - casedesc = "Upon ignition, it rapidly cools contents by 100°K. Smaller splash range than regular casings." - icon_state = "cryog" - affected_area = 2 - ignition_temp = -100 - -/obj/item/grenade/chem_grenade/pyro // Intended for pyrotechnical mixes. Produces a small fire upon detonation, igniting potentially flammable mixtures. - name = "pyro grenade" - desc = "A custom made pyrotechnical grenade. Heats up contents upon ignition." - casedesc = "Upon ignition, it rapidly heats contents by 500°K." - icon_state = "pyrog" - ignition_temp = 500 // This is enough to expose a hotspot. - -/obj/item/grenade/chem_grenade/adv_release // Intended for weaker, but longer lasting effects. Could have some interesting uses. - name = "advanced release grenade" - desc = "A custom made advanced release grenade. It is able to be detonated more than once. Can be configured using a multitool." - casedesc = "This casing is able to detonate more than once. Can be configured using a multitool." - icon_state = "timeg" - var/unit_spread = 10 // Amount of units per repeat. Can be altered with a multitool. - -/obj/item/grenade/chem_grenade/adv_release/attackby(obj/item/I, mob/user, params) - if(I.tool_behaviour == TOOL_MULTITOOL && !active) - var/newspread = text2num(stripped_input(user, "Please enter a new spread amount", name)) - if (newspread != null && user.canUseTopic(src, BE_CLOSE)) - newspread = round(newspread) - unit_spread = clamp(newspread, 5, 100) - to_chat(user, "You set the time release to [unit_spread] units per detonation.") - if (newspread != unit_spread) - to_chat(user, "The new value is out of bounds. Minimum spread is 5 units, maximum is 100 units.") - ..() - -/obj/item/grenade/chem_grenade/adv_release/prime() - if(stage != GRENADE_READY) - return - - var/total_volume = 0 - for(var/obj/item/reagent_containers/RC in beakers) - total_volume += RC.reagents.total_volume - if(!total_volume) - qdel(src) - return - var/fraction = unit_spread/total_volume - var/datum/reagents/reactants = new(unit_spread) - reactants.my_atom = src - for(var/obj/item/reagent_containers/RC in beakers) - RC.reagents.trans_to(reactants, RC.reagents.total_volume*fraction, threatscale, 1, 1) - chem_splash(get_turf(src), affected_area, list(reactants), ignition_temp, threatscale) - - var/turf/DT = get_turf(src) - addtimer(CALLBACK(src, .proc/prime), det_time) - log_game("A grenade detonated at [AREACOORD(DT)]") - - - - -////////////////////////////// -////// PREMADE GRENADES ////// -////////////////////////////// - -/obj/item/grenade/chem_grenade/metalfoam - name = "metal foam grenade" - desc = "Used for emergency sealing of hull breaches." - stage = GRENADE_READY - -/obj/item/grenade/chem_grenade/metalfoam/Initialize() - . = ..() - var/obj/item/reagent_containers/glass/beaker/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/B2 = new(src) - - B1.reagents.add_reagent(/datum/reagent/aluminium, 30) - B2.reagents.add_reagent(/datum/reagent/foaming_agent, 10) - B2.reagents.add_reagent(/datum/reagent/toxin/acid/fluacid, 10) - - beakers += B1 - beakers += B2 - - -/obj/item/grenade/chem_grenade/smart_metal_foam - name = "smart metal foam grenade" - desc = "Used for emergency sealing of hull breaches, while keeping areas accessible." - stage = GRENADE_READY - -/obj/item/grenade/chem_grenade/smart_metal_foam/Initialize() - . = ..() - var/obj/item/reagent_containers/glass/beaker/large/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/B2 = new(src) - - B1.reagents.add_reagent(/datum/reagent/aluminium, 75) - B2.reagents.add_reagent(/datum/reagent/smart_foaming_agent, 25) - B2.reagents.add_reagent(/datum/reagent/toxin/acid/fluacid, 25) - - beakers += B1 - beakers += B2 - - -/obj/item/grenade/chem_grenade/incendiary - name = "incendiary grenade" - desc = "Used for clearing rooms of living things." - stage = GRENADE_READY - -/obj/item/grenade/chem_grenade/incendiary/Initialize() - . = ..() - var/obj/item/reagent_containers/glass/beaker/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/B2 = new(src) - - B1.reagents.add_reagent(/datum/reagent/phosphorus, 25) - B2.reagents.add_reagent(/datum/reagent/stable_plasma, 25) - B2.reagents.add_reagent(/datum/reagent/toxin/acid, 25) - - beakers += B1 - beakers += B2 - - -/obj/item/grenade/chem_grenade/antiweed - name = "weedkiller grenade" - desc = "Used for purging large areas of invasive plant species. Contents under pressure. Do not directly inhale contents." - stage = GRENADE_READY - -/obj/item/grenade/chem_grenade/antiweed/Initialize() - . = ..() - var/obj/item/reagent_containers/glass/beaker/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/B2 = new(src) - - B1.reagents.add_reagent(/datum/reagent/toxin/plantbgone, 25) - B1.reagents.add_reagent(/datum/reagent/potassium, 25) - B2.reagents.add_reagent(/datum/reagent/phosphorus, 25) - B2.reagents.add_reagent(/datum/reagent/consumable/sugar, 25) - - beakers += B1 - beakers += B2 - - -/obj/item/grenade/chem_grenade/cleaner - name = "cleaner grenade" - desc = "BLAM!-brand foaming space cleaner. In a special applicator for rapid cleaning of wide areas." - stage = GRENADE_READY - -/obj/item/grenade/chem_grenade/cleaner/Initialize() - . = ..() - var/obj/item/reagent_containers/glass/beaker/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/B2 = new(src) - - B1.reagents.add_reagent(/datum/reagent/fluorosurfactant, 40) - B2.reagents.add_reagent(/datum/reagent/water, 40) - B2.reagents.add_reagent(/datum/reagent/space_cleaner, 10) - - beakers += B1 - beakers += B2 - - -/obj/item/grenade/chem_grenade/ez_clean - name = "cleaner grenade" - desc = "Waffle Co.-brand foaming space cleaner. In a special applicator for rapid cleaning of wide areas." - stage = GRENADE_READY - -/obj/item/grenade/chem_grenade/ez_clean/Initialize() - . = ..() - var/obj/item/reagent_containers/glass/beaker/large/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/large/B2 = new(src) - - B1.reagents.add_reagent(/datum/reagent/fluorosurfactant, 40) - B2.reagents.add_reagent(/datum/reagent/water, 40) - B2.reagents.add_reagent(/datum/reagent/space_cleaner/ez_clean, 60) //ensures a t h i c c distribution - - beakers += B1 - beakers += B2 - - - -/obj/item/grenade/chem_grenade/teargas - name = "teargas grenade" - desc = "Used for nonlethal riot control. Contents under pressure. Do not directly inhale contents." - stage = GRENADE_READY - -/obj/item/grenade/chem_grenade/teargas/Initialize() - . = ..() - var/obj/item/reagent_containers/glass/beaker/large/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/large/B2 = new(src) - - B1.reagents.add_reagent(/datum/reagent/consumable/condensedcapsaicin, 60) - B1.reagents.add_reagent(/datum/reagent/potassium, 40) - B2.reagents.add_reagent(/datum/reagent/phosphorus, 40) - B2.reagents.add_reagent(/datum/reagent/consumable/sugar, 40) - - beakers += B1 - beakers += B2 - - -/obj/item/grenade/chem_grenade/facid - name = "acid grenade" - desc = "Used for melting armoured opponents." - stage = GRENADE_READY - -/obj/item/grenade/chem_grenade/facid/Initialize() - . = ..() - var/obj/item/reagent_containers/glass/beaker/bluespace/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/bluespace/B2 = new(src) - - B1.reagents.add_reagent(/datum/reagent/toxin/acid/fluacid, 290) - B1.reagents.add_reagent(/datum/reagent/potassium, 10) - B2.reagents.add_reagent(/datum/reagent/phosphorus, 10) - B2.reagents.add_reagent(/datum/reagent/consumable/sugar, 10) - B2.reagents.add_reagent(/datum/reagent/toxin/acid/fluacid, 280) - - beakers += B1 - beakers += B2 - - -/obj/item/grenade/chem_grenade/colorful - name = "colorful grenade" - desc = "Used for wide scale painting projects." - stage = GRENADE_READY - -/obj/item/grenade/chem_grenade/colorful/Initialize() - . = ..() - var/obj/item/reagent_containers/glass/beaker/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/B2 = new(src) - - B1.reagents.add_reagent(/datum/reagent/colorful_reagent, 25) - B1.reagents.add_reagent(/datum/reagent/potassium, 25) - B2.reagents.add_reagent(/datum/reagent/phosphorus, 25) - B2.reagents.add_reagent(/datum/reagent/consumable/sugar, 25) - - beakers += B1 - beakers += B2 - -/obj/item/grenade/chem_grenade/glitter - name = "generic glitter grenade" - desc = "You shouldn't see this description." - stage = GRENADE_READY - var/glitter_type = /datum/reagent/glitter - -/obj/item/grenade/chem_grenade/glitter/Initialize() - . = ..() - var/obj/item/reagent_containers/glass/beaker/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/B2 = new(src) - - B1.reagents.add_reagent(glitter_type, 25) - B1.reagents.add_reagent(/datum/reagent/potassium, 25) - B2.reagents.add_reagent(/datum/reagent/phosphorus, 25) - B2.reagents.add_reagent(/datum/reagent/consumable/sugar, 25) - - beakers += B1 - beakers += B2 - -/obj/item/grenade/chem_grenade/glitter/pink - name = "pink glitter bomb" - desc = "For that HOT glittery look." - glitter_type = /datum/reagent/glitter/pink - -/obj/item/grenade/chem_grenade/glitter/blue - name = "blue glitter bomb" - desc = "For that COOL glittery look." - glitter_type = /datum/reagent/glitter/blue - -/obj/item/grenade/chem_grenade/glitter/white - name = "white glitter bomb" - desc = "For that somnolent glittery look." - glitter_type = /datum/reagent/glitter/white - -/obj/item/grenade/chem_grenade/clf3 - name = "clf3 grenade" - desc = "BURN!-brand foaming clf3. In a special applicator for rapid purging of wide areas." - stage = GRENADE_READY - -/obj/item/grenade/chem_grenade/clf3/Initialize() - . = ..() - var/obj/item/reagent_containers/glass/beaker/bluespace/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/bluespace/B2 = new(src) - - B1.reagents.add_reagent(/datum/reagent/fluorosurfactant, 250) - B1.reagents.add_reagent(/datum/reagent/clf3, 50) - B2.reagents.add_reagent(/datum/reagent/water, 250) - B2.reagents.add_reagent(/datum/reagent/clf3, 50) - - beakers += B1 - beakers += B2 - -/obj/item/grenade/chem_grenade/bioterrorfoam - name = "Bio terror foam grenade" - desc = "Tiger Cooperative chemical foam grenade. Causes temporary irration, blindness, confusion, mutism, and mutations to carbon based life forms. Contains additional spore toxin." - stage = GRENADE_READY - -/obj/item/grenade/chem_grenade/bioterrorfoam/Initialize() - . = ..() - var/obj/item/reagent_containers/glass/beaker/bluespace/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/bluespace/B2 = new(src) - - B1.reagents.add_reagent(/datum/reagent/cryptobiolin, 75) - B1.reagents.add_reagent(/datum/reagent/water, 50) - B1.reagents.add_reagent(/datum/reagent/toxin/mutetoxin, 50) - B1.reagents.add_reagent(/datum/reagent/toxin/spore, 75) - B1.reagents.add_reagent(/datum/reagent/toxin/itching_powder, 50) - B2.reagents.add_reagent(/datum/reagent/fluorosurfactant, 150) - B2.reagents.add_reagent(/datum/reagent/toxin/mutagen, 150) - beakers += B1 - beakers += B2 - -/obj/item/grenade/chem_grenade/tuberculosis - name = "Fungal tuberculosis grenade" - desc = "WARNING: GRENADE WILL RELEASE DEADLY SPORES CONTAINING ACTIVE AGENTS. SEAL SUIT AND AIRFLOW BEFORE USE." - stage = GRENADE_READY - -/obj/item/grenade/chem_grenade/tuberculosis/Initialize() - . = ..() - var/obj/item/reagent_containers/glass/beaker/bluespace/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/bluespace/B2 = new(src) - - B1.reagents.add_reagent(/datum/reagent/potassium, 50) - B1.reagents.add_reagent(/datum/reagent/phosphorus, 50) - B1.reagents.add_reagent(/datum/reagent/fungalspores, 200) - B2.reagents.add_reagent(/datum/reagent/blood, 250) - B2.reagents.add_reagent(/datum/reagent/consumable/sugar, 50) - - beakers += B1 - beakers += B2 - -/obj/item/grenade/chem_grenade/holy - name = "holy hand grenade" - desc = "A vessel of concentrated religious might." - icon_state = "holy_grenade" - stage = GRENADE_READY - -/obj/item/grenade/chem_grenade/holy/Initialize() - . = ..() - var/obj/item/reagent_containers/glass/beaker/large/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/large/B2 = new(src) - - B1.reagents.add_reagent(/datum/reagent/potassium, 100) - B2.reagents.add_reagent(/datum/reagent/water/holywater, 100) - - beakers += B1 - beakers += B2 +/obj/item/grenade/chem_grenade + name = "chemical grenade" + desc = "A custom made grenade." + icon_state = "chemg" + item_state = "flashbang" + w_class = WEIGHT_CLASS_SMALL + force = 2 + var/stage = GRENADE_EMPTY + var/list/obj/item/reagent_containers/glass/beakers = list() + var/list/allowed_containers = list(/obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/glass/bottle) + var/list/banned_containers = list(/obj/item/reagent_containers/glass/beaker/bluespace) //Containers to exclude from specific grenade subtypes + var/affected_area = 3 + var/ignition_temp = 10 // The amount of heat added to the reagents when this grenade goes off. + var/threatscale = 1 // Used by advanced grenades to make them slightly more worthy. + var/no_splash = FALSE //If the grenade deletes even if it has no reagents to splash with. Used for slime core reactions. + var/casedesc = "This basic model accepts both beakers and bottles. It heats contents by 10°K upon ignition." // Appears when examining empty casings. + var/obj/item/assembly/prox_sensor/landminemode = null + +/obj/item/grenade/chem_grenade/ComponentInitialize() + . = ..() + AddComponent(/datum/component/empprotection, EMP_PROTECT_WIRES) + +/obj/item/grenade/chem_grenade/Initialize() + . = ..() + create_reagents(1000) + stage_change() // If no argument is set, it will change the stage to the current stage, useful for stock grenades that start READY. + wires = new /datum/wires/explosive/chem_grenade(src) + +/obj/item/grenade/chem_grenade/examine(mob/user) + display_timer = (stage == GRENADE_READY) //show/hide the timer based on assembly state + . = ..() + if(user.can_see_reagents()) + if(beakers.len) + . += "You scan the grenade and detect the following reagents:" + for(var/obj/item/reagent_containers/glass/G in beakers) + for(var/datum/reagent/R in G.reagents.reagent_list) + . += "[R.volume] units of [R.name] in the [G.name]." + if(beakers.len == 1) + . += "You detect no second beaker in the grenade." + else + . += "You scan the grenade, but detect nothing." + else if(stage != GRENADE_READY && beakers.len) + if(beakers.len == 2 && beakers[1].name == beakers[2].name) + . += "You see two [beakers[1].name]s inside the grenade." + else + for(var/obj/item/reagent_containers/glass/G in beakers) + . += "You see a [G.name] inside the grenade." + +/obj/item/grenade/chem_grenade/attack_self(mob/user) + if(stage == GRENADE_READY && !active) + ..() + if(stage == GRENADE_WIRED) + wires.interact(user) + +/obj/item/grenade/chem_grenade/attackby(obj/item/I, mob/user, params) + if(istype(I,/obj/item/assembly) && stage == GRENADE_WIRED) + wires.interact(user) + if(I.tool_behaviour == TOOL_SCREWDRIVER) + if(stage == GRENADE_WIRED) + if(beakers.len) + stage_change(GRENADE_READY) + to_chat(user, "You lock the [initial(name)] assembly.") + I.play_tool_sound(src, 25) + else + to_chat(user, "You need to add at least one beaker before locking the [initial(name)] assembly!") + else if(stage == GRENADE_READY) + det_time = det_time == 50 ? 30 : 50 //toggle between 30 and 50 + if(landminemode) + landminemode.time = det_time * 0.1 //overwrites the proxy sensor activation timer + + to_chat(user, "You modify the time delay. It's set for [DisplayTimeText(det_time)].") + else + to_chat(user, "You need to add a wire!") + return + else if(stage == GRENADE_WIRED && is_type_in_list(I, allowed_containers)) + . = TRUE //no afterattack + if(is_type_in_list(I, banned_containers)) + to_chat(user, "[src] is too small to fit [I]!") // this one hits home huh anon? + return + if(beakers.len == 2) + to_chat(user, "[src] can not hold more containers!") + return + else + if(I.reagents.total_volume) + if(!user.transferItemToLoc(I, src)) + return + to_chat(user, "You add [I] to the [initial(name)] assembly.") + beakers += I + var/reagent_list = pretty_string_from_reagent_list(I.reagents) + user.log_message("inserted [I] ([reagent_list]) into [src]",LOG_GAME) + else + to_chat(user, "[I] is empty!") + + else if(stage == GRENADE_EMPTY && istype(I, /obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/C = I + if (C.use(1)) + det_time = 50 // In case the cable_coil was removed and readded. + stage_change(GRENADE_WIRED) + to_chat(user, "You rig the [initial(name)] assembly.") + else + to_chat(user, "You need one length of coil to wire the assembly!") + return + + else if(stage == GRENADE_READY && I.tool_behaviour == TOOL_WIRECUTTER && !active) + stage_change(GRENADE_WIRED) + to_chat(user, "You unlock the [initial(name)] assembly.") + + else if(stage == GRENADE_WIRED && I.tool_behaviour == TOOL_WRENCH) + if(beakers.len) + for(var/obj/O in beakers) + O.forceMove(drop_location()) + if(!O.reagents) + continue + var/reagent_list = pretty_string_from_reagent_list(O.reagents) + user.log_message("removed [O] ([reagent_list]) from [src]", LOG_GAME) + beakers = list() + to_chat(user, "You open the [initial(name)] assembly and remove the payload.") + return + wires.detach_assembly(wires.get_wire(1)) + new /obj/item/stack/cable_coil(get_turf(src),1) + stage_change(GRENADE_EMPTY) + to_chat(user, "You remove the activation mechanism from the [initial(name)] assembly.") + else + return ..() + +/obj/item/grenade/chem_grenade/proc/stage_change(N) + if(N) + stage = N + if(stage == GRENADE_EMPTY) + name = "[initial(name)] casing" + desc = "A do it yourself [initial(name)]! [initial(casedesc)]" + icon_state = initial(icon_state) + else if(stage == GRENADE_WIRED) + name = "unsecured [initial(name)]" + desc = "An unsecured [initial(name)] assembly." + icon_state = "[initial(icon_state)]_ass" + else if(stage == GRENADE_READY) + name = initial(name) + desc = initial(desc) + icon_state = "[initial(icon_state)]_locked" + +/obj/item/grenade/chem_grenade/on_found(mob/finder) + var/obj/item/assembly/A = wires.get_attached(wires.get_wire(1)) + if(A) + A.on_found(finder) + +/obj/item/grenade/chem_grenade/log_grenade(mob/user, turf/T) + var/reagent_string = "" + var/beaker_number = 1 + for(var/obj/exploded_beaker in beakers) + if(!exploded_beaker.reagents) + continue + reagent_string += " ([exploded_beaker.name] [beaker_number++] : " + pretty_string_from_reagent_list(exploded_beaker.reagents.reagent_list) + ");" + if(landminemode) + log_bomber(user, "activated a proxy", src, "containing:[reagent_string]") + else + log_bomber(user, "primed a", src, "containing:[reagent_string]") + +/obj/item/grenade/chem_grenade/preprime(mob/user, delayoverride, msg = TRUE, volume = 60) + var/turf/T = get_turf(src) + log_grenade(user, T) //Inbuilt admin procs already handle null users + if(user) + add_fingerprint(user) + if(msg) + if(landminemode) + to_chat(user, "You prime [src], activating its proximity sensor.") + else + to_chat(user, "You prime [src]! [DisplayTimeText(det_time)]!") + playsound(src, 'sound/weapons/armbomb.ogg', volume, TRUE) + icon_state = initial(icon_state) + "_active" + if(landminemode) + landminemode.activate() + return + active = TRUE + addtimer(CALLBACK(src, .proc/prime), isnull(delayoverride)? det_time : delayoverride) + +/obj/item/grenade/chem_grenade/prime() + if(stage != GRENADE_READY) + return + + . = ..() + var/list/datum/reagents/reactants = list() + for(var/obj/item/reagent_containers/glass/G in beakers) + reactants += G.reagents + + var/turf/detonation_turf = get_turf(src) + + if(!chem_splash(detonation_turf, affected_area, reactants, ignition_temp, threatscale) && !no_splash) + playsound(src, 'sound/items/screwdriver2.ogg', 50, TRUE) + if(beakers.len) + for(var/obj/O in beakers) + O.forceMove(drop_location()) + beakers = list() + stage_change(GRENADE_EMPTY) + active = FALSE + return +// logs from custom assemblies priming are handled by the wire component + log_game("A grenade detonated at [AREACOORD(detonation_turf)]") + + update_mob() + + qdel(src) + +//Large chem grenades accept slime cores and use the appropriately. +/obj/item/grenade/chem_grenade/large + name = "large grenade" + desc = "A custom made large grenade. Larger splash range and increased ignition temperature compared to basic grenades. Fits exotic and bluespace based containers." + casedesc = "This casing affects a larger area than the basic model and can fit exotic containers, including slime cores and bluespace beakers. Heats contents by 25°K upon ignition." + icon_state = "large_grenade" + allowed_containers = list(/obj/item/reagent_containers/glass, /obj/item/reagent_containers/food/condiment, /obj/item/reagent_containers/food/drinks) + banned_containers = list() + affected_area = 5 + ignition_temp = 25 // Large grenades are slightly more effective at setting off heat-sensitive mixtures than smaller grenades. + threatscale = 1.1 // 10% more effective. + +/obj/item/grenade/chem_grenade/large/prime() + if(stage != GRENADE_READY) + return + + for(var/obj/item/slime_extract/S in beakers) + if(S.Uses) + for(var/obj/item/reagent_containers/glass/G in beakers) + G.reagents.trans_to(S, G.reagents.total_volume) + + //If there is still a core (sometimes it's used up) + //and there are reagents left, behave normally, + //otherwise drop it on the ground for timed reactions like gold. + + if(S) + if(S.reagents && S.reagents.total_volume) + for(var/obj/item/reagent_containers/glass/G in beakers) + S.reagents.trans_to(G, S.reagents.total_volume) + else + S.forceMove(get_turf(src)) + no_splash = TRUE + ..() + + //I tried to just put it in the allowed_containers list but + //if you do that it must have reagents. If you're going to + //make a special case you might as well do it explicitly. -Sayu +/obj/item/grenade/chem_grenade/large/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/slime_extract) && stage == GRENADE_WIRED) + if(!user.transferItemToLoc(I, src)) + return + to_chat(user, "You add [I] to the [initial(name)] assembly.") + beakers += I + else + return ..() + +/obj/item/grenade/chem_grenade/cryo // Intended for rare cryogenic mixes. Cools the area moderately upon detonation. + name = "cryo grenade" + desc = "A custom made cryogenic grenade. Rapidly cools contents upon ignition." + casedesc = "Upon ignition, it rapidly cools contents by 100°K. Smaller splash range than regular casings." + icon_state = "cryog" + affected_area = 2 + ignition_temp = -100 + +/obj/item/grenade/chem_grenade/pyro // Intended for pyrotechnical mixes. Produces a small fire upon detonation, igniting potentially flammable mixtures. + name = "pyro grenade" + desc = "A custom made pyrotechnical grenade. Heats up contents upon ignition." + casedesc = "Upon ignition, it rapidly heats contents by 500°K." + icon_state = "pyrog" + ignition_temp = 500 // This is enough to expose a hotspot. + +/obj/item/grenade/chem_grenade/adv_release // Intended for weaker, but longer lasting effects. Could have some interesting uses. + name = "advanced release grenade" + desc = "A custom made advanced release grenade. It is able to be detonated more than once. Can be configured using a multitool." + casedesc = "This casing is able to detonate more than once. Can be configured using a multitool." + icon_state = "timeg" + var/unit_spread = 10 // Amount of units per repeat. Can be altered with a multitool. + +/obj/item/grenade/chem_grenade/adv_release/attackby(obj/item/I, mob/user, params) + if(I.tool_behaviour == TOOL_MULTITOOL && !active) + var/newspread = text2num(stripped_input(user, "Please enter a new spread amount", name)) + if (newspread != null && user.canUseTopic(src, BE_CLOSE)) + newspread = round(newspread) + unit_spread = clamp(newspread, 5, 100) + to_chat(user, "You set the time release to [unit_spread] units per detonation.") + if (newspread != unit_spread) + to_chat(user, "The new value is out of bounds. Minimum spread is 5 units, maximum is 100 units.") + ..() + +/obj/item/grenade/chem_grenade/adv_release/prime() + if(stage != GRENADE_READY) + return + + var/total_volume = 0 + for(var/obj/item/reagent_containers/RC in beakers) + total_volume += RC.reagents.total_volume + if(!total_volume) + qdel(src) + return + var/fraction = unit_spread/total_volume + var/datum/reagents/reactants = new(unit_spread) + reactants.my_atom = src + for(var/obj/item/reagent_containers/RC in beakers) + RC.reagents.trans_to(reactants, RC.reagents.total_volume*fraction, threatscale, 1, 1) + chem_splash(get_turf(src), affected_area, list(reactants), ignition_temp, threatscale) + + var/turf/DT = get_turf(src) + addtimer(CALLBACK(src, .proc/prime), det_time) + log_game("A grenade detonated at [AREACOORD(DT)]") + + + + +////////////////////////////// +////// PREMADE GRENADES ////// +////////////////////////////// + +/obj/item/grenade/chem_grenade/metalfoam + name = "metal foam grenade" + desc = "Used for emergency sealing of hull breaches." + stage = GRENADE_READY + +/obj/item/grenade/chem_grenade/metalfoam/Initialize() + . = ..() + var/obj/item/reagent_containers/glass/beaker/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/B2 = new(src) + + B1.reagents.add_reagent(/datum/reagent/aluminium, 30) + B2.reagents.add_reagent(/datum/reagent/foaming_agent, 10) + B2.reagents.add_reagent(/datum/reagent/toxin/acid/fluacid, 10) + + beakers += B1 + beakers += B2 + + +/obj/item/grenade/chem_grenade/smart_metal_foam + name = "smart metal foam grenade" + desc = "Used for emergency sealing of hull breaches, while keeping areas accessible." + stage = GRENADE_READY + +/obj/item/grenade/chem_grenade/smart_metal_foam/Initialize() + . = ..() + var/obj/item/reagent_containers/glass/beaker/large/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/B2 = new(src) + + B1.reagents.add_reagent(/datum/reagent/aluminium, 75) + B2.reagents.add_reagent(/datum/reagent/smart_foaming_agent, 25) + B2.reagents.add_reagent(/datum/reagent/toxin/acid/fluacid, 25) + + beakers += B1 + beakers += B2 + + +/obj/item/grenade/chem_grenade/incendiary + name = "incendiary grenade" + desc = "Used for clearing rooms of living things." + stage = GRENADE_READY + +/obj/item/grenade/chem_grenade/incendiary/Initialize() + . = ..() + var/obj/item/reagent_containers/glass/beaker/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/B2 = new(src) + + B1.reagents.add_reagent(/datum/reagent/phosphorus, 25) + B2.reagents.add_reagent(/datum/reagent/stable_plasma, 25) + B2.reagents.add_reagent(/datum/reagent/toxin/acid, 25) + + beakers += B1 + beakers += B2 + + +/obj/item/grenade/chem_grenade/antiweed + name = "weedkiller grenade" + desc = "Used for purging large areas of invasive plant species. Contents under pressure. Do not directly inhale contents." + stage = GRENADE_READY + +/obj/item/grenade/chem_grenade/antiweed/Initialize() + . = ..() + var/obj/item/reagent_containers/glass/beaker/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/B2 = new(src) + + B1.reagents.add_reagent(/datum/reagent/toxin/plantbgone, 25) + B1.reagents.add_reagent(/datum/reagent/potassium, 25) + B2.reagents.add_reagent(/datum/reagent/phosphorus, 25) + B2.reagents.add_reagent(/datum/reagent/consumable/sugar, 25) + + beakers += B1 + beakers += B2 + + +/obj/item/grenade/chem_grenade/cleaner + name = "cleaner grenade" + desc = "BLAM!-brand foaming space cleaner. In a special applicator for rapid cleaning of wide areas." + stage = GRENADE_READY + +/obj/item/grenade/chem_grenade/cleaner/Initialize() + . = ..() + var/obj/item/reagent_containers/glass/beaker/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/B2 = new(src) + + B1.reagents.add_reagent(/datum/reagent/fluorosurfactant, 40) + B2.reagents.add_reagent(/datum/reagent/water, 40) + B2.reagents.add_reagent(/datum/reagent/space_cleaner, 10) + + beakers += B1 + beakers += B2 + + +/obj/item/grenade/chem_grenade/ez_clean + name = "cleaner grenade" + desc = "Waffle Co.-brand foaming space cleaner. In a special applicator for rapid cleaning of wide areas." + stage = GRENADE_READY + +/obj/item/grenade/chem_grenade/ez_clean/Initialize() + . = ..() + var/obj/item/reagent_containers/glass/beaker/large/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/large/B2 = new(src) + + B1.reagents.add_reagent(/datum/reagent/fluorosurfactant, 40) + B2.reagents.add_reagent(/datum/reagent/water, 40) + B2.reagents.add_reagent(/datum/reagent/space_cleaner/ez_clean, 60) //ensures a t h i c c distribution + + beakers += B1 + beakers += B2 + + + +/obj/item/grenade/chem_grenade/teargas + name = "teargas grenade" + desc = "Used for nonlethal riot control. Contents under pressure. Do not directly inhale contents." + stage = GRENADE_READY + +/obj/item/grenade/chem_grenade/teargas/Initialize() + . = ..() + var/obj/item/reagent_containers/glass/beaker/large/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/large/B2 = new(src) + + B1.reagents.add_reagent(/datum/reagent/consumable/condensedcapsaicin, 60) + B1.reagents.add_reagent(/datum/reagent/potassium, 40) + B2.reagents.add_reagent(/datum/reagent/phosphorus, 40) + B2.reagents.add_reagent(/datum/reagent/consumable/sugar, 40) + + beakers += B1 + beakers += B2 + + +/obj/item/grenade/chem_grenade/facid + name = "acid grenade" + desc = "Used for melting armoured opponents." + stage = GRENADE_READY + +/obj/item/grenade/chem_grenade/facid/Initialize() + . = ..() + var/obj/item/reagent_containers/glass/beaker/bluespace/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/bluespace/B2 = new(src) + + B1.reagents.add_reagent(/datum/reagent/toxin/acid/fluacid, 290) + B1.reagents.add_reagent(/datum/reagent/potassium, 10) + B2.reagents.add_reagent(/datum/reagent/phosphorus, 10) + B2.reagents.add_reagent(/datum/reagent/consumable/sugar, 10) + B2.reagents.add_reagent(/datum/reagent/toxin/acid/fluacid, 280) + + beakers += B1 + beakers += B2 + + +/obj/item/grenade/chem_grenade/colorful + name = "colorful grenade" + desc = "Used for wide scale painting projects." + stage = GRENADE_READY + +/obj/item/grenade/chem_grenade/colorful/Initialize() + . = ..() + var/obj/item/reagent_containers/glass/beaker/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/B2 = new(src) + + B1.reagents.add_reagent(/datum/reagent/colorful_reagent, 25) + B1.reagents.add_reagent(/datum/reagent/potassium, 25) + B2.reagents.add_reagent(/datum/reagent/phosphorus, 25) + B2.reagents.add_reagent(/datum/reagent/consumable/sugar, 25) + + beakers += B1 + beakers += B2 + +/obj/item/grenade/chem_grenade/glitter + name = "generic glitter grenade" + desc = "You shouldn't see this description." + stage = GRENADE_READY + var/glitter_type = /datum/reagent/glitter + +/obj/item/grenade/chem_grenade/glitter/Initialize() + . = ..() + var/obj/item/reagent_containers/glass/beaker/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/B2 = new(src) + + B1.reagents.add_reagent(glitter_type, 25) + B1.reagents.add_reagent(/datum/reagent/potassium, 25) + B2.reagents.add_reagent(/datum/reagent/phosphorus, 25) + B2.reagents.add_reagent(/datum/reagent/consumable/sugar, 25) + + beakers += B1 + beakers += B2 + +/obj/item/grenade/chem_grenade/glitter/pink + name = "pink glitter bomb" + desc = "For that HOT glittery look." + glitter_type = /datum/reagent/glitter/pink + +/obj/item/grenade/chem_grenade/glitter/blue + name = "blue glitter bomb" + desc = "For that COOL glittery look." + glitter_type = /datum/reagent/glitter/blue + +/obj/item/grenade/chem_grenade/glitter/white + name = "white glitter bomb" + desc = "For that somnolent glittery look." + glitter_type = /datum/reagent/glitter/white + +/obj/item/grenade/chem_grenade/clf3 + name = "clf3 grenade" + desc = "BURN!-brand foaming clf3. In a special applicator for rapid purging of wide areas." + stage = GRENADE_READY + +/obj/item/grenade/chem_grenade/clf3/Initialize() + . = ..() + var/obj/item/reagent_containers/glass/beaker/bluespace/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/bluespace/B2 = new(src) + + B1.reagents.add_reagent(/datum/reagent/fluorosurfactant, 250) + B1.reagents.add_reagent(/datum/reagent/clf3, 50) + B2.reagents.add_reagent(/datum/reagent/water, 250) + B2.reagents.add_reagent(/datum/reagent/clf3, 50) + + beakers += B1 + beakers += B2 + +/obj/item/grenade/chem_grenade/bioterrorfoam + name = "Bio terror foam grenade" + desc = "Tiger Cooperative chemical foam grenade. Causes temporary irration, blindness, confusion, mutism, and mutations to carbon based life forms. Contains additional spore toxin." + stage = GRENADE_READY + +/obj/item/grenade/chem_grenade/bioterrorfoam/Initialize() + . = ..() + var/obj/item/reagent_containers/glass/beaker/bluespace/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/bluespace/B2 = new(src) + + B1.reagents.add_reagent(/datum/reagent/cryptobiolin, 75) + B1.reagents.add_reagent(/datum/reagent/water, 50) + B1.reagents.add_reagent(/datum/reagent/toxin/mutetoxin, 50) + B1.reagents.add_reagent(/datum/reagent/toxin/spore, 75) + B1.reagents.add_reagent(/datum/reagent/toxin/itching_powder, 50) + B2.reagents.add_reagent(/datum/reagent/fluorosurfactant, 150) + B2.reagents.add_reagent(/datum/reagent/toxin/mutagen, 150) + beakers += B1 + beakers += B2 + +/obj/item/grenade/chem_grenade/tuberculosis + name = "Fungal tuberculosis grenade" + desc = "WARNING: GRENADE WILL RELEASE DEADLY SPORES CONTAINING ACTIVE AGENTS. SEAL SUIT AND AIRFLOW BEFORE USE." + stage = GRENADE_READY + +/obj/item/grenade/chem_grenade/tuberculosis/Initialize() + . = ..() + var/obj/item/reagent_containers/glass/beaker/bluespace/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/bluespace/B2 = new(src) + + B1.reagents.add_reagent(/datum/reagent/potassium, 50) + B1.reagents.add_reagent(/datum/reagent/phosphorus, 50) + B1.reagents.add_reagent(/datum/reagent/fungalspores, 200) + B2.reagents.add_reagent(/datum/reagent/blood, 250) + B2.reagents.add_reagent(/datum/reagent/consumable/sugar, 50) + + beakers += B1 + beakers += B2 + +/obj/item/grenade/chem_grenade/holy + name = "holy hand grenade" + desc = "A vessel of concentrated religious might." + icon_state = "holy_grenade" + stage = GRENADE_READY + +/obj/item/grenade/chem_grenade/holy/Initialize() + . = ..() + var/obj/item/reagent_containers/glass/beaker/large/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/large/B2 = new(src) + + B1.reagents.add_reagent(/datum/reagent/potassium, 100) + B2.reagents.add_reagent(/datum/reagent/water/holywater, 100) + + beakers += B1 + beakers += B2 diff --git a/code/game/objects/items/grenades/emgrenade.dm b/code/game/objects/items/grenades/emgrenade.dm index afb0ab48c574..8cc4e4bea620 100644 --- a/code/game/objects/items/grenades/emgrenade.dm +++ b/code/game/objects/items/grenades/emgrenade.dm @@ -1,14 +1,14 @@ -/obj/item/grenade/empgrenade - name = "classic EMP grenade" - desc = "It is designed to wreak havoc on electronic systems." - icon_state = "emp" - item_state = "emp" - -/obj/item/grenade/empgrenade/prime() - . = ..() - update_mob() - for(var/obj/machinery/light/L in range(10, src)) - L.on = 1 - L.break_light_tube() - empulse(src, 4, 10) - resolve() +/obj/item/grenade/empgrenade + name = "classic EMP grenade" + desc = "It is designed to wreak havoc on electronic systems." + icon_state = "emp" + item_state = "emp" + +/obj/item/grenade/empgrenade/prime() + . = ..() + update_mob() + for(var/obj/machinery/light/L in range(10, src)) + L.on = 1 + L.break_light_tube() + empulse(src, 4, 10) + resolve() diff --git a/code/game/objects/items/grenades/festive.dm b/code/game/objects/items/grenades/festive.dm index af68f7566a3e..f5b534d95787 100644 --- a/code/game/objects/items/grenades/festive.dm +++ b/code/game/objects/items/grenades/festive.dm @@ -1,118 +1,118 @@ -//~*~*~*~*SPARKLER*~*~*~*~*~*~ - -/obj/item/sparkler - name = "sparkler" - desc = "A little stick coated with metal power and barium nitrate, burns with a pleasing sparkle." - icon = 'icons/obj/holiday_misc.dmi' - icon_state = "sparkler" - w_class = WEIGHT_CLASS_TINY - heat = 1000 - var/burntime = 60 - var/lit = FALSE - -/obj/item/sparkler/fire_act(exposed_temperature, exposed_volume) - light() - -/obj/item/sparkler/attackby(obj/item/W, mob/user, params) - var/ignition_msg = W.ignition_effect(src, user) - if(ignition_msg) - light(user, ignition_msg) - else - return ..() - -/obj/item/sparkler/proc/light(mob/user, message) - if(lit) - return - if(user && message) - user.visible_message(message) - lit = TRUE - icon_state = "sparkler_on" - force = 6 - hitsound = 'sound/items/welder.ogg' - name = "lit [initial(name)]" - attack_verb = list("burnt") - set_light(l_range = 2, l_power = 2) - damtype = "fire" - START_PROCESSING(SSobj, src) - playsound(src, 'sound/effects/fuse.ogg', 20, TRUE) - update_icon() - -/obj/item/sparkler/process() - burntime-- - if(burntime < 1) - new /obj/item/stack/rods(drop_location()) - qdel(src) - else - open_flame(heat) - -/obj/item/sparkler/Destroy() - STOP_PROCESSING(SSobj, src) - ..() - -/obj/item/sparkler/ignition_effect(atom/A, mob/user) - . = "[user] gracefully lights [A] with [src]." - -/obj/item/sparkler/get_temperature() - return lit * heat - -//~*~*~*~*FIRECRACKER*~*~*~*~*~*~ - -/obj/item/grenade/firecracker - name = "large firecracker" - desc = "Outlawed in most of the sector. Doubles as an excellent finger remover." - icon = 'icons/obj/holiday_misc.dmi' - icon_state = "firecracker" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - w_class = WEIGHT_CLASS_SMALL - item_state = "flare" - throw_speed = 3 - throw_range = 7 - det_time = 30 - -/obj/item/grenade/firecracker/attack_self(mob/user) // You need to light it manually. - return - -/obj/item/grenade/firecracker/attackby(obj/item/W, mob/user, params) - var/ignition_msg = W.ignition_effect(src, user) - if(ignition_msg && !active) - visible_message(ignition_msg) - preprime(user) - else - return ..() - -/obj/item/grenade/firecracker/fire_act(exposed_temperature, exposed_volume) - prime() - -obj/item/grenade/firecracker/wirecutter_act(mob/living/user, obj/item/I) - if(active) - return - if(det_time) - det_time -= 10 - to_chat(user, "You shorten the fuse of [src] with [I].") - playsound(src, 'sound/items/wirecutter.ogg', 20, TRUE) - icon_state = initial(icon_state) + "_[det_time]" - update_icon() - else - to_chat(user, "You've already removed all of the fuse!") - -obj/item/grenade/firecracker/preprime(mob/user, delayoverride, msg = TRUE, volume = 80) - var/turf/T = get_turf(src) - log_grenade(user, T) - if(user) - add_fingerprint(user) - if(msg) - to_chat(user, "You prime [src]! [capitalize(DisplayTimeText(det_time))]!") - playsound(src, 'sound/effects/fuse.ogg', volume, TRUE) - active = TRUE - icon_state = initial(icon_state) + "_active" - addtimer(CALLBACK(src, .proc/prime), isnull(delayoverride)? det_time : delayoverride) - -/obj/item/grenade/firecracker/prime() - . = ..() - update_mob() - var/explosion_loc = get_turf(src) - resolve() - explosion(explosion_loc,-1,-1,2) - - +//~*~*~*~*SPARKLER*~*~*~*~*~*~ + +/obj/item/sparkler + name = "sparkler" + desc = "A little stick coated with metal power and barium nitrate, burns with a pleasing sparkle." + icon = 'icons/obj/holiday_misc.dmi' + icon_state = "sparkler" + w_class = WEIGHT_CLASS_TINY + heat = 1000 + var/burntime = 60 + var/lit = FALSE + +/obj/item/sparkler/fire_act(exposed_temperature, exposed_volume) + light() + +/obj/item/sparkler/attackby(obj/item/W, mob/user, params) + var/ignition_msg = W.ignition_effect(src, user) + if(ignition_msg) + light(user, ignition_msg) + else + return ..() + +/obj/item/sparkler/proc/light(mob/user, message) + if(lit) + return + if(user && message) + user.visible_message(message) + lit = TRUE + icon_state = "sparkler_on" + force = 6 + hitsound = 'sound/items/welder.ogg' + name = "lit [initial(name)]" + attack_verb = list("burnt") + set_light(l_range = 2, l_power = 2) + damtype = "fire" + START_PROCESSING(SSobj, src) + playsound(src, 'sound/effects/fuse.ogg', 20, TRUE) + update_icon() + +/obj/item/sparkler/process() + burntime-- + if(burntime < 1) + new /obj/item/stack/rods(drop_location()) + qdel(src) + else + open_flame(heat) + +/obj/item/sparkler/Destroy() + STOP_PROCESSING(SSobj, src) + ..() + +/obj/item/sparkler/ignition_effect(atom/A, mob/user) + . = "[user] gracefully lights [A] with [src]." + +/obj/item/sparkler/get_temperature() + return lit * heat + +//~*~*~*~*FIRECRACKER*~*~*~*~*~*~ + +/obj/item/grenade/firecracker + name = "large firecracker" + desc = "Outlawed in most of the sector. Doubles as an excellent finger remover." + icon = 'icons/obj/holiday_misc.dmi' + icon_state = "firecracker" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + w_class = WEIGHT_CLASS_SMALL + item_state = "flare" + throw_speed = 3 + throw_range = 7 + det_time = 30 + +/obj/item/grenade/firecracker/attack_self(mob/user) // You need to light it manually. + return + +/obj/item/grenade/firecracker/attackby(obj/item/W, mob/user, params) + var/ignition_msg = W.ignition_effect(src, user) + if(ignition_msg && !active) + visible_message(ignition_msg) + preprime(user) + else + return ..() + +/obj/item/grenade/firecracker/fire_act(exposed_temperature, exposed_volume) + prime() + +obj/item/grenade/firecracker/wirecutter_act(mob/living/user, obj/item/I) + if(active) + return + if(det_time) + det_time -= 10 + to_chat(user, "You shorten the fuse of [src] with [I].") + playsound(src, 'sound/items/wirecutter.ogg', 20, TRUE) + icon_state = initial(icon_state) + "_[det_time]" + update_icon() + else + to_chat(user, "You've already removed all of the fuse!") + +obj/item/grenade/firecracker/preprime(mob/user, delayoverride, msg = TRUE, volume = 80) + var/turf/T = get_turf(src) + log_grenade(user, T) + if(user) + add_fingerprint(user) + if(msg) + to_chat(user, "You prime [src]! [capitalize(DisplayTimeText(det_time))]!") + playsound(src, 'sound/effects/fuse.ogg', volume, TRUE) + active = TRUE + icon_state = initial(icon_state) + "_active" + addtimer(CALLBACK(src, .proc/prime), isnull(delayoverride)? det_time : delayoverride) + +/obj/item/grenade/firecracker/prime() + . = ..() + update_mob() + var/explosion_loc = get_turf(src) + resolve() + explosion(explosion_loc,-1,-1,2) + + diff --git a/code/game/objects/items/grenades/flashbang.dm b/code/game/objects/items/grenades/flashbang.dm index cbce3cff51f1..9f5209ac54f4 100644 --- a/code/game/objects/items/grenades/flashbang.dm +++ b/code/game/objects/items/grenades/flashbang.dm @@ -1,130 +1,130 @@ -/obj/item/grenade/flashbang - name = "flashbang" - icon_state = "flashbang" - item_state = "flashbang" - lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' - var/flashbang_range = 7 //how many tiles away the mob will be stunned. - -/obj/item/grenade/flashbang/prime() - . = ..() - update_mob() - var/flashbang_turf = get_turf(src) - if(!flashbang_turf) - return - do_sparks(rand(5, 9), FALSE, src) - playsound(flashbang_turf, 'sound/weapons/flashbang.ogg', 100, TRUE, 8, 0.9) - new /obj/effect/dummy/lighting_obj (flashbang_turf, LIGHT_COLOR_WHITE, (flashbang_range + 2), 4, 2) - for(var/mob/living/M in get_hearers_in_view(flashbang_range, flashbang_turf)) - bang(get_turf(M), M) - resolve() - -/obj/item/grenade/flashbang/proc/bang(turf/T , mob/living/M) - if(M.stat == DEAD) //They're dead! - return - M.show_message("BANG", MSG_AUDIBLE) - var/distance = max(0,get_dist(get_turf(src),T)) - -//Flash - if(M.flash_act(affect_silicon = 1)) - M.Paralyze(max(20/max(1,distance), 5)) - M.Knockdown(max(200/max(1,distance), 60)) - -//Bang - if(!distance || loc == M || loc == M.loc) //Stop allahu akbarring rooms with this. - M.Paralyze(20) - M.Knockdown(200) - M.soundbang_act(1, 200, 10, 15) - else - if(distance <= 1) // Adds more stun as to not prime n' pull (#45381) - M.Paralyze(5) - M.Knockdown(30) - M.soundbang_act(1, max(200/max(1,distance), 60), rand(0, 5)) - -/obj/item/grenade/stingbang - name = "stingbang" - icon_state = "timeg" - item_state = "flashbang" - lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' - var/flashbang_range = 1 //how many tiles away the mob will be stunned. - shrapnel_type = /obj/projectile/bullet/pellet/stingball - shrapnel_radius = 5 - custom_premium_price = 700 // mostly gotten through cargo, but throw in one for the sec vendor ;) - -/obj/item/grenade/stingbang/mega - name = "mega stingbang" - shrapnel_type = /obj/projectile/bullet/pellet/stingball/mega - shrapnel_radius = 12 - -/obj/item/grenade/stingbang/prime() - if(iscarbon(loc)) - var/mob/living/carbon/C = loc - var/obj/item/bodypart/B = C.get_holding_bodypart_of_item(src) - if(B) - C.visible_message("[src] goes off in [C]'s hand, blowing [C.p_their()] [B.name] to bloody shreds!", "[src] goes off in your hand, blowing your [B.name] to bloody shreds!") - B.dismember() - - . = ..() - update_mob() - var/flashbang_turf = get_turf(src) - if(!flashbang_turf) - return - do_sparks(rand(5, 9), FALSE, src) - playsound(flashbang_turf, 'sound/weapons/flashbang.ogg', 50, TRUE, 8, 0.9) - new /obj/effect/dummy/lighting_obj (flashbang_turf, LIGHT_COLOR_WHITE, (flashbang_range + 2), 2, 1) - for(var/mob/living/M in get_hearers_in_view(flashbang_range, flashbang_turf)) - pop(get_turf(M), M) - resolve() - -/obj/item/grenade/stingbang/proc/pop(turf/T , mob/living/M) - if(M.stat == DEAD) //They're dead! - return - M.show_message("POP", MSG_AUDIBLE) - var/distance = max(0,get_dist(get_turf(src),T)) -//Flash - if(M.flash_act(affect_silicon = 1)) - M.Paralyze(max(10/max(1,distance), 5)) - M.Knockdown(max(100/max(1,distance), 60)) - -//Bang - if(!distance || loc == M || loc == M.loc) //Stop allahu akbarring rooms with this. - M.Paralyze(20) - M.Knockdown(200) - M.soundbang_act(1, 200, 10, 15) - if(M.apply_damages(10, 10)) - to_chat(M, "The blast from \the [src] bruises and burns you!") - - // only checking if they're on top of the tile, cause being one tile over will be its own punishment - -// Grenade that releases more shrapnel the more times you use it in hand between priming and detonation (sorta like the 9bang from MW3), for admin goofs -/obj/item/grenade/primer - name = "rotfrag grenade" - desc = "A grenade that generates more shrapnel the more you rotate it in your hand after pulling the pin. This one releases shrapnel shards." - icon_state = "timeg" - item_state = "flashbang" - lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' - var/rots_per_mag = 3 /// how many times we need to "rotate" the charge in hand per extra tile of magnitude - shrapnel_type = /obj/projectile/bullet/shrapnel - var/rots = 1 /// how many times we've "rotated" the charge - -/obj/item/grenade/primer/attack_self(mob/user) - . = ..() - if(active) - user.playsound_local(user, 'sound/misc/box_deploy.ogg', 50, TRUE) - rots++ - user.changeNext_move(CLICK_CD_RAPID) - -/obj/item/grenade/primer/prime() - shrapnel_radius = round(rots / rots_per_mag) - . = ..() - resolve() - -/obj/item/grenade/primer/stingbang - name = "rotsting" - desc = "A grenade that generates more shrapnel the more you rotate it in your hand after pulling the pin. This one releases stingballs." - lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' - rots_per_mag = 2 - shrapnel_type = /obj/projectile/bullet/pellet/stingball +/obj/item/grenade/flashbang + name = "flashbang" + icon_state = "flashbang" + item_state = "flashbang" + lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' + var/flashbang_range = 7 //how many tiles away the mob will be stunned. + +/obj/item/grenade/flashbang/prime() + . = ..() + update_mob() + var/flashbang_turf = get_turf(src) + if(!flashbang_turf) + return + do_sparks(rand(5, 9), FALSE, src) + playsound(flashbang_turf, 'sound/weapons/flashbang.ogg', 100, TRUE, 8, 0.9) + new /obj/effect/dummy/lighting_obj (flashbang_turf, LIGHT_COLOR_WHITE, (flashbang_range + 2), 4, 2) + for(var/mob/living/M in get_hearers_in_view(flashbang_range, flashbang_turf)) + bang(get_turf(M), M) + resolve() + +/obj/item/grenade/flashbang/proc/bang(turf/T , mob/living/M) + if(M.stat == DEAD) //They're dead! + return + M.show_message("BANG", MSG_AUDIBLE) + var/distance = max(0,get_dist(get_turf(src),T)) + +//Flash + if(M.flash_act(affect_silicon = 1)) + M.Paralyze(max(20/max(1,distance), 5)) + M.Knockdown(max(200/max(1,distance), 60)) + +//Bang + if(!distance || loc == M || loc == M.loc) //Stop allahu akbarring rooms with this. + M.Paralyze(20) + M.Knockdown(200) + M.soundbang_act(1, 200, 10, 15) + else + if(distance <= 1) // Adds more stun as to not prime n' pull (#45381) + M.Paralyze(5) + M.Knockdown(30) + M.soundbang_act(1, max(200/max(1,distance), 60), rand(0, 5)) + +/obj/item/grenade/stingbang + name = "stingbang" + icon_state = "timeg" + item_state = "flashbang" + lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' + var/flashbang_range = 1 //how many tiles away the mob will be stunned. + shrapnel_type = /obj/projectile/bullet/pellet/stingball + shrapnel_radius = 5 + custom_premium_price = 700 // mostly gotten through cargo, but throw in one for the sec vendor ;) + +/obj/item/grenade/stingbang/mega + name = "mega stingbang" + shrapnel_type = /obj/projectile/bullet/pellet/stingball/mega + shrapnel_radius = 12 + +/obj/item/grenade/stingbang/prime() + if(iscarbon(loc)) + var/mob/living/carbon/C = loc + var/obj/item/bodypart/B = C.get_holding_bodypart_of_item(src) + if(B) + C.visible_message("[src] goes off in [C]'s hand, blowing [C.p_their()] [B.name] to bloody shreds!", "[src] goes off in your hand, blowing your [B.name] to bloody shreds!") + B.dismember() + + . = ..() + update_mob() + var/flashbang_turf = get_turf(src) + if(!flashbang_turf) + return + do_sparks(rand(5, 9), FALSE, src) + playsound(flashbang_turf, 'sound/weapons/flashbang.ogg', 50, TRUE, 8, 0.9) + new /obj/effect/dummy/lighting_obj (flashbang_turf, LIGHT_COLOR_WHITE, (flashbang_range + 2), 2, 1) + for(var/mob/living/M in get_hearers_in_view(flashbang_range, flashbang_turf)) + pop(get_turf(M), M) + resolve() + +/obj/item/grenade/stingbang/proc/pop(turf/T , mob/living/M) + if(M.stat == DEAD) //They're dead! + return + M.show_message("POP", MSG_AUDIBLE) + var/distance = max(0,get_dist(get_turf(src),T)) +//Flash + if(M.flash_act(affect_silicon = 1)) + M.Paralyze(max(10/max(1,distance), 5)) + M.Knockdown(max(100/max(1,distance), 60)) + +//Bang + if(!distance || loc == M || loc == M.loc) //Stop allahu akbarring rooms with this. + M.Paralyze(20) + M.Knockdown(200) + M.soundbang_act(1, 200, 10, 15) + if(M.apply_damages(10, 10)) + to_chat(M, "The blast from \the [src] bruises and burns you!") + + // only checking if they're on top of the tile, cause being one tile over will be its own punishment + +// Grenade that releases more shrapnel the more times you use it in hand between priming and detonation (sorta like the 9bang from MW3), for admin goofs +/obj/item/grenade/primer + name = "rotfrag grenade" + desc = "A grenade that generates more shrapnel the more you rotate it in your hand after pulling the pin. This one releases shrapnel shards." + icon_state = "timeg" + item_state = "flashbang" + lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' + var/rots_per_mag = 3 /// how many times we need to "rotate" the charge in hand per extra tile of magnitude + shrapnel_type = /obj/projectile/bullet/shrapnel + var/rots = 1 /// how many times we've "rotated" the charge + +/obj/item/grenade/primer/attack_self(mob/user) + . = ..() + if(active) + user.playsound_local(user, 'sound/misc/box_deploy.ogg', 50, TRUE) + rots++ + user.changeNext_move(CLICK_CD_RAPID) + +/obj/item/grenade/primer/prime() + shrapnel_radius = round(rots / rots_per_mag) + . = ..() + resolve() + +/obj/item/grenade/primer/stingbang + name = "rotsting" + desc = "A grenade that generates more shrapnel the more you rotate it in your hand after pulling the pin. This one releases stingballs." + lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' + rots_per_mag = 2 + shrapnel_type = /obj/projectile/bullet/pellet/stingball diff --git a/code/game/objects/items/grenades/ghettobomb.dm b/code/game/objects/items/grenades/ghettobomb.dm index 7eb71d05db78..915011b81b11 100644 --- a/code/game/objects/items/grenades/ghettobomb.dm +++ b/code/game/objects/items/grenades/ghettobomb.dm @@ -1,77 +1,77 @@ -//improvised explosives// - -/obj/item/grenade/iedcasing - name = "improvised firebomb" - desc = "A weak, improvised incendiary device." - w_class = WEIGHT_CLASS_SMALL - icon = 'icons/obj/grenade.dmi' - icon_state = "improvised_grenade" - item_state = "flashbang" - lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' - throw_speed = 3 - throw_range = 7 - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BELT - active = 0 - det_time = 50 - display_timer = 0 - var/check_parts = FALSE - var/range = 3 - var/list/times - -/obj/item/grenade/iedcasing/Initialize() - . = ..() - add_overlay("improvised_grenade_filled") - add_overlay("improvised_grenade_wired") - times = list("5" = 10, "-1" = 20, "[rand(30,80)]" = 50, "[rand(65,180)]" = 20)// "Premature, Dud, Short Fuse, Long Fuse"=[weighting value] - det_time = text2num(pickweight(times)) - if(det_time < 0) //checking for 'duds' - range = 1 - det_time = rand(30,80) - else - range = pick(2,2,2,3,3,3,4) - if(check_parts) //since construction code calls this itself, no need to always call it. This does have the downside that adminspawned ones can potentially not have cans if they don't use the /spawned subtype. - CheckParts() - -/obj/item/grenade/iedcasing/spawned - check_parts = TRUE - -/obj/item/grenade/iedcasing/spawned/Initialize() - new /obj/item/reagent_containers/food/drinks/soda_cans/random(src) - return ..() - -/obj/item/grenade/iedcasing/CheckParts(list/parts_list) - ..() - var/obj/item/reagent_containers/food/drinks/soda_cans/can = locate() in contents - if(!can) - stack_trace("[src] generated without a soda can!") //this shouldn't happen. - qdel(src) - return - can.pixel_x = 0 //Reset the sprite's position to make it consistent with the rest of the IED - can.pixel_y = 0 - var/mutable_appearance/can_underlay = new(can) - can_underlay.layer = FLOAT_LAYER - can_underlay.plane = FLOAT_PLANE - underlays += can_underlay - - -/obj/item/grenade/iedcasing/attack_self(mob/user) // - if(!active) - if(!botch_check(user)) - to_chat(user, "You light the [name]!") - cut_overlay("improvised_grenade_filled") - preprime(user, null, FALSE) - -/obj/item/grenade/iedcasing/prime() //Blowing that can up - . = ..() - update_mob() - explosion(src.loc,-1,-1,2, flame_range = 4) // small explosion, plus a very large fireball. - resolve() - -/obj/item/grenade/iedcasing/change_det_time() - return //always be random. - -/obj/item/grenade/iedcasing/examine(mob/user) - . = ..() - . += "You can't tell when it will explode!" +//improvised explosives// + +/obj/item/grenade/iedcasing + name = "improvised firebomb" + desc = "A weak, improvised incendiary device." + w_class = WEIGHT_CLASS_SMALL + icon = 'icons/obj/grenade.dmi' + icon_state = "improvised_grenade" + item_state = "flashbang" + lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' + throw_speed = 3 + throw_range = 7 + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BELT + active = 0 + det_time = 50 + display_timer = 0 + var/check_parts = FALSE + var/range = 3 + var/list/times + +/obj/item/grenade/iedcasing/Initialize() + . = ..() + add_overlay("improvised_grenade_filled") + add_overlay("improvised_grenade_wired") + times = list("5" = 10, "-1" = 20, "[rand(30,80)]" = 50, "[rand(65,180)]" = 20)// "Premature, Dud, Short Fuse, Long Fuse"=[weighting value] + det_time = text2num(pickweight(times)) + if(det_time < 0) //checking for 'duds' + range = 1 + det_time = rand(30,80) + else + range = pick(2,2,2,3,3,3,4) + if(check_parts) //since construction code calls this itself, no need to always call it. This does have the downside that adminspawned ones can potentially not have cans if they don't use the /spawned subtype. + CheckParts() + +/obj/item/grenade/iedcasing/spawned + check_parts = TRUE + +/obj/item/grenade/iedcasing/spawned/Initialize() + new /obj/item/reagent_containers/food/drinks/soda_cans/random(src) + return ..() + +/obj/item/grenade/iedcasing/CheckParts(list/parts_list) + ..() + var/obj/item/reagent_containers/food/drinks/soda_cans/can = locate() in contents + if(!can) + stack_trace("[src] generated without a soda can!") //this shouldn't happen. + qdel(src) + return + can.pixel_x = 0 //Reset the sprite's position to make it consistent with the rest of the IED + can.pixel_y = 0 + var/mutable_appearance/can_underlay = new(can) + can_underlay.layer = FLOAT_LAYER + can_underlay.plane = FLOAT_PLANE + underlays += can_underlay + + +/obj/item/grenade/iedcasing/attack_self(mob/user) // + if(!active) + if(!botch_check(user)) + to_chat(user, "You light the [name]!") + cut_overlay("improvised_grenade_filled") + preprime(user, null, FALSE) + +/obj/item/grenade/iedcasing/prime() //Blowing that can up + . = ..() + update_mob() + explosion(src.loc,-1,-1,2, flame_range = 4) // small explosion, plus a very large fireball. + resolve() + +/obj/item/grenade/iedcasing/change_det_time() + return //always be random. + +/obj/item/grenade/iedcasing/examine(mob/user) + . = ..() + . += "You can't tell when it will explode!" diff --git a/code/game/objects/items/grenades/grenade.dm b/code/game/objects/items/grenades/grenade.dm index 641b0f656272..5318ff33a0da 100644 --- a/code/game/objects/items/grenades/grenade.dm +++ b/code/game/objects/items/grenades/grenade.dm @@ -1,179 +1,179 @@ -/obj/item/grenade - name = "grenade" - desc = "It has an adjustable timer." - w_class = WEIGHT_CLASS_SMALL - icon = 'icons/obj/grenade.dmi' - icon_state = "grenade" - item_state = "flashbang" - lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' - throw_speed = 3 - throw_range = 7 - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BELT - resistance_flags = FLAMMABLE - max_integrity = 40 - var/active = 0 - var/det_time = 50 - var/display_timer = 1 - var/clumsy_check = GRENADE_CLUMSY_FUMBLE - var/sticky = FALSE - // I moved the explosion vars and behavior to base grenades because we want all grenades to call [/obj/item/grenade/proc/prime] so we can send COMSIG_GRENADE_PRIME - ///how big of a devastation explosion radius on prime - var/ex_dev = 0 - ///how big of a heavy explosion radius on prime - var/ex_heavy = 0 - ///how big of a light explosion radius on prime - var/ex_light = 0 - ///how big of a flame explosion radius on prime - var/ex_flame = 0 - - // dealing with creating a [/datum/component/pellet_cloud] on prime - /// if set, will spew out projectiles of this type - var/shrapnel_type - /// the higher this number, the more projectiles are created as shrapnel - var/shrapnel_radius - var/shrapnel_initialized - -/obj/item/grenade/suicide_act(mob/living/carbon/user) - user.visible_message("[user] primes [src], then eats it! It looks like [user.p_theyre()] trying to commit suicide!") - playsound(src, 'sound/items/eatfood.ogg', 50, TRUE) - preprime(user, det_time) - user.transferItemToLoc(src, user, TRUE)//>eat a grenade set to 5 seconds >rush captain - sleep(det_time)//so you dont die instantly - return BRUTELOSS - -/obj/item/grenade/deconstruct(disassembled = TRUE) - if(!disassembled) - prime() - if(!QDELETED(src)) - qdel(src) - -/obj/item/grenade/proc/botch_check(mob/living/carbon/human/user) - var/clumsy = HAS_TRAIT(user, TRAIT_CLUMSY) - if(clumsy && (clumsy_check == GRENADE_CLUMSY_FUMBLE)) - if(prob(50)) - to_chat(user, "Huh? How does this thing work?") - preprime(user, 5, FALSE) - return TRUE - else if(!clumsy && (clumsy_check == GRENADE_NONCLUMSY_FUMBLE)) - to_chat(user, "You pull the pin on [src]. Attached to it is a pink ribbon that says, \"HONK\"") - preprime(user, 5, FALSE) - return TRUE - else if(sticky && prob(50)) // to add risk to sticky tape grenade cheese, no return cause we still prime as normal after - to_chat(user, "What the... [src] is stuck to your hand!") - ADD_TRAIT(src, TRAIT_NODROP, STICKY_NODROP) - -/obj/item/grenade/examine(mob/user) - . = ..() - if(display_timer) - if(det_time > 0) - . += "The timer is set to [DisplayTimeText(det_time)]." - else - . += "\The [src] is set for instant detonation." - - -/obj/item/grenade/attack_self(mob/user) - if(HAS_TRAIT(src, TRAIT_NODROP)) - to_chat(user, "You try prying [src] off your hand...") - if(do_after(user, 70, target=src)) - to_chat(user, "You manage to remove [src] from your hand.") - REMOVE_TRAIT(src, TRAIT_NODROP, STICKY_NODROP) - - return - - if(!active) - if(!botch_check(user)) // if they botch the prime, it'll be handled in botch_check - preprime(user) - -/obj/item/grenade/proc/log_grenade(mob/user, turf/T) - log_bomber(user, "has primed a", src, "for detonation") - -/obj/item/grenade/proc/preprime(mob/user, delayoverride, msg = TRUE, volume = 60) - var/turf/T = get_turf(src) - log_grenade(user, T) //Inbuilt admin procs already handle null users - if(user) - add_fingerprint(user) - if(msg) - to_chat(user, "You prime [src]! [capitalize(DisplayTimeText(det_time))]!") - if(shrapnel_type && shrapnel_radius) - shrapnel_initialized = TRUE - AddComponent(/datum/component/pellet_cloud, projectile_type=shrapnel_type, magnitude=shrapnel_radius) - playsound(src, 'sound/weapons/armbomb.ogg', volume, TRUE) - active = TRUE - icon_state = initial(icon_state) + "_active" - SEND_SIGNAL(src, COMSIG_GRENADE_ARMED, det_time, delayoverride) - addtimer(CALLBACK(src, .proc/prime), isnull(delayoverride)? det_time : delayoverride) - -/obj/item/grenade/proc/prime() - if(shrapnel_type && shrapnel_radius && !shrapnel_initialized) // add a second check for adding the component in case whatever triggered the grenade went straight to prime (badminnery for example) - shrapnel_initialized = TRUE - AddComponent(/datum/component/pellet_cloud, projectile_type=shrapnel_type, magnitude=shrapnel_radius) - - SEND_SIGNAL(src, COMSIG_GRENADE_PRIME) - if(ex_dev || ex_heavy || ex_light || ex_flame) - explosion(loc, ex_dev, ex_heavy, ex_light, flame_range = ex_flame) - -/obj/item/grenade/proc/update_mob() - if(ismob(loc)) - var/mob/M = loc - M.dropItemToGround(src) - -/obj/item/grenade/attackby(obj/item/W, mob/user, params) - if(!active) - if(W.tool_behaviour == TOOL_MULTITOOL) - var/newtime = text2num(stripped_input(user, "Please enter a new detonation time", name)) - if (newtime != null && user.canUseTopic(src, BE_CLOSE)) - if(change_det_time(newtime)) - to_chat(user, "You modify the time delay. It's set for [DisplayTimeText(det_time)].") - if (round(newtime * 10) != det_time) - to_chat(user, "The new value is out of bounds. The lowest possible time is 3 seconds and highest is 5 seconds. Instant detonations are also possible.") - return - else if(W.tool_behaviour == TOOL_SCREWDRIVER) - if(change_det_time()) - to_chat(user, "You modify the time delay. It's set for [DisplayTimeText(det_time)].") - else - return ..() - -/obj/item/grenade/proc/change_det_time(time) //Time uses real time. - . = TRUE - if(time != null) - if(time < 3) - time = 3 - det_time = round(clamp(time * 10, 0, 50)) - else - var/previous_time = det_time - switch(det_time) - if (0) - det_time = 30 - if (30) - det_time = 50 - if (50) - det_time = 0 - if(det_time == previous_time) - det_time = 50 - -/obj/item/grenade/attack_paw(mob/user) - return attack_hand(user) - -/obj/item/grenade/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - var/obj/projectile/P = hitby - if(damage && attack_type == PROJECTILE_ATTACK && P.damage_type != STAMINA && prob(15)) - owner.visible_message("[attack_text] hits [owner]'s [src], setting it off! What a shot!") - var/turf/T = get_turf(src) - log_game("A projectile ([hitby]) detonated a grenade held by [key_name(owner)] at [COORD(T)]") - message_admins("A projectile ([hitby]) detonated a grenade held by [key_name_admin(owner)] at [ADMIN_COORDJMP(T)]") - prime() - return TRUE //It hit the grenade, not them - -/obj/item/grenade/afterattack(atom/target, mob/user) - . = ..() - if(active) - user.throw_item(target) - -/// Don't call qdel() directly on the grenade after it booms, call this instead so it can still resolve its pellet_cloud component if it has shrapnel, then the component will qdel it -/obj/item/grenade/proc/resolve() - if(shrapnel_type) - moveToNullspace() - else - qdel(src) +/obj/item/grenade + name = "grenade" + desc = "It has an adjustable timer." + w_class = WEIGHT_CLASS_SMALL + icon = 'icons/obj/grenade.dmi' + icon_state = "grenade" + item_state = "flashbang" + lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' + throw_speed = 3 + throw_range = 7 + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BELT + resistance_flags = FLAMMABLE + max_integrity = 40 + var/active = 0 + var/det_time = 50 + var/display_timer = 1 + var/clumsy_check = GRENADE_CLUMSY_FUMBLE + var/sticky = FALSE + // I moved the explosion vars and behavior to base grenades because we want all grenades to call [/obj/item/grenade/proc/prime] so we can send COMSIG_GRENADE_PRIME + ///how big of a devastation explosion radius on prime + var/ex_dev = 0 + ///how big of a heavy explosion radius on prime + var/ex_heavy = 0 + ///how big of a light explosion radius on prime + var/ex_light = 0 + ///how big of a flame explosion radius on prime + var/ex_flame = 0 + + // dealing with creating a [/datum/component/pellet_cloud] on prime + /// if set, will spew out projectiles of this type + var/shrapnel_type + /// the higher this number, the more projectiles are created as shrapnel + var/shrapnel_radius + var/shrapnel_initialized + +/obj/item/grenade/suicide_act(mob/living/carbon/user) + user.visible_message("[user] primes [src], then eats it! It looks like [user.p_theyre()] trying to commit suicide!") + playsound(src, 'sound/items/eatfood.ogg', 50, TRUE) + preprime(user, det_time) + user.transferItemToLoc(src, user, TRUE)//>eat a grenade set to 5 seconds >rush captain + sleep(det_time)//so you dont die instantly + return BRUTELOSS + +/obj/item/grenade/deconstruct(disassembled = TRUE) + if(!disassembled) + prime() + if(!QDELETED(src)) + qdel(src) + +/obj/item/grenade/proc/botch_check(mob/living/carbon/human/user) + var/clumsy = HAS_TRAIT(user, TRAIT_CLUMSY) + if(clumsy && (clumsy_check == GRENADE_CLUMSY_FUMBLE)) + if(prob(50)) + to_chat(user, "Huh? How does this thing work?") + preprime(user, 5, FALSE) + return TRUE + else if(!clumsy && (clumsy_check == GRENADE_NONCLUMSY_FUMBLE)) + to_chat(user, "You pull the pin on [src]. Attached to it is a pink ribbon that says, \"HONK\"") + preprime(user, 5, FALSE) + return TRUE + else if(sticky && prob(50)) // to add risk to sticky tape grenade cheese, no return cause we still prime as normal after + to_chat(user, "What the... [src] is stuck to your hand!") + ADD_TRAIT(src, TRAIT_NODROP, STICKY_NODROP) + +/obj/item/grenade/examine(mob/user) + . = ..() + if(display_timer) + if(det_time > 0) + . += "The timer is set to [DisplayTimeText(det_time)]." + else + . += "\The [src] is set for instant detonation." + + +/obj/item/grenade/attack_self(mob/user) + if(HAS_TRAIT(src, TRAIT_NODROP)) + to_chat(user, "You try prying [src] off your hand...") + if(do_after(user, 70, target=src)) + to_chat(user, "You manage to remove [src] from your hand.") + REMOVE_TRAIT(src, TRAIT_NODROP, STICKY_NODROP) + + return + + if(!active) + if(!botch_check(user)) // if they botch the prime, it'll be handled in botch_check + preprime(user) + +/obj/item/grenade/proc/log_grenade(mob/user, turf/T) + log_bomber(user, "has primed a", src, "for detonation") + +/obj/item/grenade/proc/preprime(mob/user, delayoverride, msg = TRUE, volume = 60) + var/turf/T = get_turf(src) + log_grenade(user, T) //Inbuilt admin procs already handle null users + if(user) + add_fingerprint(user) + if(msg) + to_chat(user, "You prime [src]! [capitalize(DisplayTimeText(det_time))]!") + if(shrapnel_type && shrapnel_radius) + shrapnel_initialized = TRUE + AddComponent(/datum/component/pellet_cloud, projectile_type=shrapnel_type, magnitude=shrapnel_radius) + playsound(src, 'sound/weapons/armbomb.ogg', volume, TRUE) + active = TRUE + icon_state = initial(icon_state) + "_active" + SEND_SIGNAL(src, COMSIG_GRENADE_ARMED, det_time, delayoverride) + addtimer(CALLBACK(src, .proc/prime), isnull(delayoverride)? det_time : delayoverride) + +/obj/item/grenade/proc/prime() + if(shrapnel_type && shrapnel_radius && !shrapnel_initialized) // add a second check for adding the component in case whatever triggered the grenade went straight to prime (badminnery for example) + shrapnel_initialized = TRUE + AddComponent(/datum/component/pellet_cloud, projectile_type=shrapnel_type, magnitude=shrapnel_radius) + + SEND_SIGNAL(src, COMSIG_GRENADE_PRIME) + if(ex_dev || ex_heavy || ex_light || ex_flame) + explosion(loc, ex_dev, ex_heavy, ex_light, flame_range = ex_flame) + +/obj/item/grenade/proc/update_mob() + if(ismob(loc)) + var/mob/M = loc + M.dropItemToGround(src) + +/obj/item/grenade/attackby(obj/item/W, mob/user, params) + if(!active) + if(W.tool_behaviour == TOOL_MULTITOOL) + var/newtime = text2num(stripped_input(user, "Please enter a new detonation time", name)) + if (newtime != null && user.canUseTopic(src, BE_CLOSE)) + if(change_det_time(newtime)) + to_chat(user, "You modify the time delay. It's set for [DisplayTimeText(det_time)].") + if (round(newtime * 10) != det_time) + to_chat(user, "The new value is out of bounds. The lowest possible time is 3 seconds and highest is 5 seconds. Instant detonations are also possible.") + return + else if(W.tool_behaviour == TOOL_SCREWDRIVER) + if(change_det_time()) + to_chat(user, "You modify the time delay. It's set for [DisplayTimeText(det_time)].") + else + return ..() + +/obj/item/grenade/proc/change_det_time(time) //Time uses real time. + . = TRUE + if(time != null) + if(time < 3) + time = 3 + det_time = round(clamp(time * 10, 0, 50)) + else + var/previous_time = det_time + switch(det_time) + if (0) + det_time = 30 + if (30) + det_time = 50 + if (50) + det_time = 0 + if(det_time == previous_time) + det_time = 50 + +/obj/item/grenade/attack_paw(mob/user) + return attack_hand(user) + +/obj/item/grenade/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + var/obj/projectile/P = hitby + if(damage && attack_type == PROJECTILE_ATTACK && P.damage_type != STAMINA && prob(15)) + owner.visible_message("[attack_text] hits [owner]'s [src], setting it off! What a shot!") + var/turf/T = get_turf(src) + log_game("A projectile ([hitby]) detonated a grenade held by [key_name(owner)] at [COORD(T)]") + message_admins("A projectile ([hitby]) detonated a grenade held by [key_name_admin(owner)] at [ADMIN_COORDJMP(T)]") + prime() + return TRUE //It hit the grenade, not them + +/obj/item/grenade/afterattack(atom/target, mob/user) + . = ..() + if(active) + user.throw_item(target) + +/// Don't call qdel() directly on the grenade after it booms, call this instead so it can still resolve its pellet_cloud component if it has shrapnel, then the component will qdel it +/obj/item/grenade/proc/resolve() + if(shrapnel_type) + moveToNullspace() + else + qdel(src) diff --git a/code/game/objects/items/grenades/smokebomb.dm b/code/game/objects/items/grenades/smokebomb.dm index 590312772efc..95a3cafe9d87 100644 --- a/code/game/objects/items/grenades/smokebomb.dm +++ b/code/game/objects/items/grenades/smokebomb.dm @@ -1,33 +1,33 @@ -/** - *This is smoke bomb, mezum koman. It is a grenade subtype. All craftmanship is of the highest quality. - *It menaces with spikes of iron. On it is a depiction of an assistant. - *The assistant is bleeding. The assistant has a painful expression. The assistant is dead. - */ -/obj/item/grenade/smokebomb - name = "smoke grenade" - desc = "Real bruh moment if you ever see this. Probably tell a c*der or something." - icon = 'icons/obj/grenade.dmi' - icon_state = "smokewhite" - item_state = "smoke" - slot_flags = ITEM_SLOT_BELT - ///It's extremely important to keep this list up to date. It helps to generate the insightful description of the smokebomb - var/static/list/bruh_moment = list("Dank", "Hip", "Lit", "Based", "Robust", "Bruh", "Nyagger") - -///Here we generate the extremely insightful description. -/obj/item/grenade/smokebomb/Initialize() - . = ..() - desc = "The word '[pick(bruh_moment)]' is scribbled on it in crayon." - -///Here we generate some smoke and also damage blobs??? for some reason. Honestly not sure why we do that. -/obj/item/grenade/smokebomb/prime() - . = ..() - update_mob() - playsound(src, 'sound/effects/smoke.ogg', 50, TRUE, -3) - var/datum/effect_system/smoke_spread/bad/smoke = new - smoke.set_up(4, src) - smoke.start() - qdel(smoke) //And deleted again. Sad really. - for(var/obj/structure/blob/B in view(8,src)) - var/damage = round(30/(get_dist(B,src)+1)) - B.take_damage(damage, BURN, "melee", 0) - resolve() +/** + *This is smoke bomb, mezum koman. It is a grenade subtype. All craftmanship is of the highest quality. + *It menaces with spikes of iron. On it is a depiction of an assistant. + *The assistant is bleeding. The assistant has a painful expression. The assistant is dead. + */ +/obj/item/grenade/smokebomb + name = "smoke grenade" + desc = "Real bruh moment if you ever see this. Probably tell a c*der or something." + icon = 'icons/obj/grenade.dmi' + icon_state = "smokewhite" + item_state = "smoke" + slot_flags = ITEM_SLOT_BELT + ///It's extremely important to keep this list up to date. It helps to generate the insightful description of the smokebomb + var/static/list/bruh_moment = list("Dank", "Hip", "Lit", "Based", "Robust", "Bruh", "Nyagger") + +///Here we generate the extremely insightful description. +/obj/item/grenade/smokebomb/Initialize() + . = ..() + desc = "The word '[pick(bruh_moment)]' is scribbled on it in crayon." + +///Here we generate some smoke and also damage blobs??? for some reason. Honestly not sure why we do that. +/obj/item/grenade/smokebomb/prime() + . = ..() + update_mob() + playsound(src, 'sound/effects/smoke.ogg', 50, TRUE, -3) + var/datum/effect_system/smoke_spread/bad/smoke = new + smoke.set_up(4, src) + smoke.start() + qdel(smoke) //And deleted again. Sad really. + for(var/obj/structure/blob/B in view(8,src)) + var/damage = round(30/(get_dist(B,src)+1)) + B.take_damage(damage, BURN, "melee", 0) + resolve() diff --git a/code/game/objects/items/grenades/spawnergrenade.dm b/code/game/objects/items/grenades/spawnergrenade.dm index dfa0b6b63d18..72f378c38907 100644 --- a/code/game/objects/items/grenades/spawnergrenade.dm +++ b/code/game/objects/items/grenades/spawnergrenade.dm @@ -1,64 +1,64 @@ -/obj/item/grenade/spawnergrenade - desc = "It will unleash an unspecified anomaly in the surrounding vicinity." - name = "delivery grenade" - icon = 'icons/obj/grenade.dmi' - icon_state = "delivery" - item_state = "flashbang" - var/spawner_type = null // must be an object path - var/deliveryamt = 1 // amount of type to deliver - -/obj/item/grenade/spawnergrenade/prime() // Prime now just handles the two loops that query for people in lockers and people who can see it. - . = ..() - update_mob() - if(spawner_type && deliveryamt) - // Make a quick flash - var/turf/T = get_turf(src) - playsound(T, 'sound/effects/phasein.ogg', 100, TRUE) - for(var/mob/living/carbon/C in viewers(T, null)) - C.flash_act() - - // Spawn some hostile syndicate critters and spread them out - var/list/spawned = spawn_and_random_walk(spawner_type, T, deliveryamt, walk_chance=50, admin_spawn=((flags_1 & ADMIN_SPAWNED_1) ? TRUE : FALSE)) - afterspawn(spawned) - - resolve() - -/obj/item/grenade/spawnergrenade/proc/afterspawn(list/mob/spawned) - return - -/obj/item/grenade/spawnergrenade/manhacks - name = "viscerator delivery grenade" - spawner_type = /mob/living/simple_animal/hostile/viscerator - deliveryamt = 10 - -/obj/item/grenade/spawnergrenade/spesscarp - name = "carp delivery grenade" - spawner_type = /mob/living/simple_animal/hostile/carp - deliveryamt = 5 - -/obj/item/grenade/spawnergrenade/syndiesoap - name = "Mister Scrubby" - spawner_type = /obj/item/soap/syndie - -/obj/item/grenade/spawnergrenade/buzzkill - name = "Buzzkill grenade" - desc = "The label reads: \"WARNING: DEVICE WILL RELEASE LIVE SPECIMENS UPON ACTIVATION. SEAL SUIT BEFORE USE.\" It is warm to the touch and vibrates faintly." - icon_state = "holy_grenade" - spawner_type = /mob/living/simple_animal/hostile/poison/bees/toxin - deliveryamt = 10 - -/obj/item/grenade/spawnergrenade/clown - name = "C.L.U.W.N.E." - desc = "A sleek device often given to clowns on their 10th birthdays for protection. You can hear faint scratching coming from within." - icon_state = "clown_ball" - item_state = "clown_ball" - spawner_type = list(/mob/living/simple_animal/hostile/retaliate/clown/fleshclown, /mob/living/simple_animal/hostile/retaliate/clown/clownhulk, /mob/living/simple_animal/hostile/retaliate/clown/longface, /mob/living/simple_animal/hostile/retaliate/clown/clownhulk/chlown, /mob/living/simple_animal/hostile/retaliate/clown/clownhulk/honcmunculus, /mob/living/simple_animal/hostile/retaliate/clown/mutant/blob, /mob/living/simple_animal/hostile/retaliate/clown/banana, /mob/living/simple_animal/hostile/retaliate/clown/honkling, /mob/living/simple_animal/hostile/retaliate/clown/lube) - deliveryamt = 1 - -/obj/item/grenade/spawnergrenade/clown_broken - name = "stuffed C.L.U.W.N.E." - desc = "A sleek device often given to clowns on their 10th birthdays for protection. While a typical C.L.U.W.N.E only holds one creature, sometimes foolish young clowns try to cram more in, often to disasterous effect." - icon_state = "clown_broken" - item_state = "clown_broken" - spawner_type = /mob/living/simple_animal/hostile/retaliate/clown/mutant - deliveryamt = 5 +/obj/item/grenade/spawnergrenade + desc = "It will unleash an unspecified anomaly in the surrounding vicinity." + name = "delivery grenade" + icon = 'icons/obj/grenade.dmi' + icon_state = "delivery" + item_state = "flashbang" + var/spawner_type = null // must be an object path + var/deliveryamt = 1 // amount of type to deliver + +/obj/item/grenade/spawnergrenade/prime() // Prime now just handles the two loops that query for people in lockers and people who can see it. + . = ..() + update_mob() + if(spawner_type && deliveryamt) + // Make a quick flash + var/turf/T = get_turf(src) + playsound(T, 'sound/effects/phasein.ogg', 100, TRUE) + for(var/mob/living/carbon/C in viewers(T, null)) + C.flash_act() + + // Spawn some hostile syndicate critters and spread them out + var/list/spawned = spawn_and_random_walk(spawner_type, T, deliveryamt, walk_chance=50, admin_spawn=((flags_1 & ADMIN_SPAWNED_1) ? TRUE : FALSE)) + afterspawn(spawned) + + resolve() + +/obj/item/grenade/spawnergrenade/proc/afterspawn(list/mob/spawned) + return + +/obj/item/grenade/spawnergrenade/manhacks + name = "viscerator delivery grenade" + spawner_type = /mob/living/simple_animal/hostile/viscerator + deliveryamt = 10 + +/obj/item/grenade/spawnergrenade/spesscarp + name = "carp delivery grenade" + spawner_type = /mob/living/simple_animal/hostile/carp + deliveryamt = 5 + +/obj/item/grenade/spawnergrenade/syndiesoap + name = "Mister Scrubby" + spawner_type = /obj/item/soap/syndie + +/obj/item/grenade/spawnergrenade/buzzkill + name = "Buzzkill grenade" + desc = "The label reads: \"WARNING: DEVICE WILL RELEASE LIVE SPECIMENS UPON ACTIVATION. SEAL SUIT BEFORE USE.\" It is warm to the touch and vibrates faintly." + icon_state = "holy_grenade" + spawner_type = /mob/living/simple_animal/hostile/poison/bees/toxin + deliveryamt = 10 + +/obj/item/grenade/spawnergrenade/clown + name = "C.L.U.W.N.E." + desc = "A sleek device often given to clowns on their 10th birthdays for protection. You can hear faint scratching coming from within." + icon_state = "clown_ball" + item_state = "clown_ball" + spawner_type = list(/mob/living/simple_animal/hostile/retaliate/clown/fleshclown, /mob/living/simple_animal/hostile/retaliate/clown/clownhulk, /mob/living/simple_animal/hostile/retaliate/clown/longface, /mob/living/simple_animal/hostile/retaliate/clown/clownhulk/chlown, /mob/living/simple_animal/hostile/retaliate/clown/clownhulk/honcmunculus, /mob/living/simple_animal/hostile/retaliate/clown/mutant/blob, /mob/living/simple_animal/hostile/retaliate/clown/banana, /mob/living/simple_animal/hostile/retaliate/clown/honkling, /mob/living/simple_animal/hostile/retaliate/clown/lube) + deliveryamt = 1 + +/obj/item/grenade/spawnergrenade/clown_broken + name = "stuffed C.L.U.W.N.E." + desc = "A sleek device often given to clowns on their 10th birthdays for protection. While a typical C.L.U.W.N.E only holds one creature, sometimes foolish young clowns try to cram more in, often to disasterous effect." + icon_state = "clown_broken" + item_state = "clown_broken" + spawner_type = /mob/living/simple_animal/hostile/retaliate/clown/mutant + deliveryamt = 5 diff --git a/code/game/objects/items/grenades/syndieminibomb.dm b/code/game/objects/items/grenades/syndieminibomb.dm index 446b27d5a404..d820ada940e9 100644 --- a/code/game/objects/items/grenades/syndieminibomb.dm +++ b/code/game/objects/items/grenades/syndieminibomb.dm @@ -1,68 +1,68 @@ -/obj/item/grenade/syndieminibomb - desc = "A syndicate manufactured explosive used to sow destruction and chaos." - name = "syndicate minibomb" - icon = 'icons/obj/grenade.dmi' - icon_state = "syndicate" - item_state = "flashbang" - ex_dev = 1 - ex_heavy = 2 - ex_light = 4 - ex_flame = 2 - -/obj/item/grenade/syndieminibomb/prime() - . = ..() - update_mob() - resolve() - -/obj/item/grenade/syndieminibomb/concussion - name = "HE Grenade" - desc = "A compact shrapnel grenade meant to devastate nearby organisms and cause some damage in the process. Pull pin and throw opposite direction." - icon_state = "concussion" - ex_heavy = 2 - ex_light = 3 - ex_flame = 3 - -/obj/item/grenade/frag - name = "frag grenade" - desc = "An anti-personnel fragmentation grenade, this weapon excels at killing soft targets by shredding them with metal shrapnel." - icon_state = "frag" - shrapnel_type = /obj/projectile/bullet/shrapnel - shrapnel_radius = 4 - ex_heavy = 1 - ex_light = 3 - ex_flame = 4 - -/obj/item/grenade/frag/mega - name = "FRAG grenade" - desc = "An anti-everything fragmentation grenade, this weapon excels at killing anything any everything by shredding them with metal shrapnel." - shrapnel_type = /obj/projectile/bullet/shrapnel/mega - shrapnel_radius = 12 - -/obj/item/grenade/frag/prime() - . = ..() - update_mob() - resolve() - -/obj/item/grenade/gluon - desc = "An advanced grenade that releases a harmful stream of gluons inducing radiation in those nearby. These gluon streams will also make victims feel exhausted, and induce shivering. This extreme coldness will also likely wet any nearby floors." - name = "gluon frag grenade" - icon = 'icons/obj/grenade.dmi' - icon_state = "bluefrag" - item_state = "flashbang" - var/freeze_range = 4 - var/rad_damage = 350 - var/stamina_damage = 30 - -/obj/item/grenade/gluon/prime() - . = ..() - update_mob() - playsound(loc, 'sound/effects/empulse.ogg', 50, TRUE) - radiation_pulse(src, rad_damage) - for(var/turf/T in view(freeze_range,loc)) - if(isfloorturf(T)) - var/turf/open/floor/F = T - F.MakeSlippery(TURF_WET_PERMAFROST, 6 MINUTES) - for(var/mob/living/carbon/L in T) - L.adjustStaminaLoss(stamina_damage) - L.adjust_bodytemperature(-230) - resolve() +/obj/item/grenade/syndieminibomb + desc = "A syndicate manufactured explosive used to sow destruction and chaos." + name = "syndicate minibomb" + icon = 'icons/obj/grenade.dmi' + icon_state = "syndicate" + item_state = "flashbang" + ex_dev = 1 + ex_heavy = 2 + ex_light = 4 + ex_flame = 2 + +/obj/item/grenade/syndieminibomb/prime() + . = ..() + update_mob() + resolve() + +/obj/item/grenade/syndieminibomb/concussion + name = "HE Grenade" + desc = "A compact shrapnel grenade meant to devastate nearby organisms and cause some damage in the process. Pull pin and throw opposite direction." + icon_state = "concussion" + ex_heavy = 2 + ex_light = 3 + ex_flame = 3 + +/obj/item/grenade/frag + name = "frag grenade" + desc = "An anti-personnel fragmentation grenade, this weapon excels at killing soft targets by shredding them with metal shrapnel." + icon_state = "frag" + shrapnel_type = /obj/projectile/bullet/shrapnel + shrapnel_radius = 4 + ex_heavy = 1 + ex_light = 3 + ex_flame = 4 + +/obj/item/grenade/frag/mega + name = "FRAG grenade" + desc = "An anti-everything fragmentation grenade, this weapon excels at killing anything any everything by shredding them with metal shrapnel." + shrapnel_type = /obj/projectile/bullet/shrapnel/mega + shrapnel_radius = 12 + +/obj/item/grenade/frag/prime() + . = ..() + update_mob() + resolve() + +/obj/item/grenade/gluon + desc = "An advanced grenade that releases a harmful stream of gluons inducing radiation in those nearby. These gluon streams will also make victims feel exhausted, and induce shivering. This extreme coldness will also likely wet any nearby floors." + name = "gluon frag grenade" + icon = 'icons/obj/grenade.dmi' + icon_state = "bluefrag" + item_state = "flashbang" + var/freeze_range = 4 + var/rad_damage = 350 + var/stamina_damage = 30 + +/obj/item/grenade/gluon/prime() + . = ..() + update_mob() + playsound(loc, 'sound/effects/empulse.ogg', 50, TRUE) + radiation_pulse(src, rad_damage) + for(var/turf/T in view(freeze_range,loc)) + if(isfloorturf(T)) + var/turf/open/floor/F = T + F.MakeSlippery(TURF_WET_PERMAFROST, 6 MINUTES) + for(var/mob/living/carbon/L in T) + L.adjustStaminaLoss(stamina_damage) + L.adjust_bodytemperature(-230) + resolve() diff --git a/code/game/objects/items/handcuffs.dm b/code/game/objects/items/handcuffs.dm index 6b3c14dcf09d..18a7046644fa 100644 --- a/code/game/objects/items/handcuffs.dm +++ b/code/game/objects/items/handcuffs.dm @@ -1,409 +1,409 @@ -/obj/item/restraints - breakouttime = 600 - -/obj/item/restraints/suicide_act(mob/living/carbon/user) - 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/restraints/Destroy() - if(iscarbon(loc)) - var/mob/living/carbon/M = loc - if(M.handcuffed == src) - M.handcuffed = null - M.update_handcuffed() - if(M.buckled && M.buckled.buckle_requires_restraints) - M.buckled.unbuckle_mob(M) - if(M.legcuffed == src) - M.legcuffed = null - M.update_inv_legcuffed() - return ..() - -//Handcuffs - -/obj/item/restraints/handcuffs - name = "handcuffs" - desc = "Use this to keep prisoners in line." - gender = PLURAL - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "handcuff" - lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BELT - throwforce = 0 - w_class = WEIGHT_CLASS_SMALL - throw_speed = 3 - throw_range = 5 - custom_materials = list(/datum/material/iron=500) - breakouttime = 1 MINUTES - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - var/cuffsound = 'sound/weapons/handcuffs.ogg' - var/trashtype = null //for disposable cuffs - -/obj/item/restraints/handcuffs/attack(mob/living/carbon/C, mob/living/user) - if(!istype(C)) - return - - if(iscarbon(user) && (HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50))) - to_chat(user, "Uh... how do those things work?!") - apply_cuffs(user,user) - return - - // chance of monkey retaliation - if(ismonkey(C) && prob(MONKEY_CUFF_RETALIATION_PROB)) - var/mob/living/carbon/monkey/M - M = C - M.retaliate(user) - - if(!C.handcuffed) - if(C.get_num_arms(FALSE) >= 2 || C.get_arm_ignore()) - C.visible_message("[user] is trying to put [src.name] on [C]!", \ - "[user] is trying to put [src.name] on you!") - - playsound(loc, cuffsound, 30, TRUE, -2) - if(do_mob(user, C, 30) && (C.get_num_arms(FALSE) >= 2 || C.get_arm_ignore())) - if(iscyborg(user)) - apply_cuffs(C, user, TRUE) - else - apply_cuffs(C, user) - C.visible_message("[user] handcuffs [C].", \ - "[user] handcuffs you.") - SSblackbox.record_feedback("tally", "handcuffs", 1, type) - - log_combat(user, C, "handcuffed") - else - to_chat(user, "You fail to handcuff [C]!") - else - to_chat(user, "[C] doesn't have two hands...") - -/obj/item/restraints/handcuffs/proc/apply_cuffs(mob/living/carbon/target, mob/user, dispense = 0) - if(target.handcuffed) - return - - if(!user.temporarilyRemoveItemFromInventory(src) && !dispense) - return - - var/obj/item/restraints/handcuffs/cuffs = src - if(trashtype) - cuffs = new trashtype() - else if(dispense) - cuffs = new type() - - cuffs.forceMove(target) - target.handcuffed = cuffs - - target.update_handcuffed() - if(trashtype && !dispense) - qdel(src) - return - -/obj/item/restraints/handcuffs/cable/sinew - name = "sinew restraints" - desc = "A pair of restraints fashioned from long strands of flesh." - icon = 'icons/obj/mining.dmi' - icon_state = "sinewcuff" - item_state = "sinewcuff" - custom_materials = null - color = null - -/obj/item/restraints/handcuffs/cable - name = "cable restraints" - desc = "Looks like some cables tied together. Could be used to tie something up." - icon_state = "cuff" - item_state = "coil" - color = "#ff0000" - lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' - custom_materials = list(/datum/material/iron=150, /datum/material/glass=75) - breakouttime = 30 SECONDS - cuffsound = 'sound/weapons/cablecuff.ogg' - -/*Wasp - Smartwire Revert -/obj/item/restraints/handcuffs/cable/Initialize(mapload, param_color) - . = ..() - - var/list/cable_colors = GLOB.cable_colors - cable_color = param_color || cable_color || pick(cable_colors) - if(cable_colors[cable_color]) - cable_color = cable_colors[cable_color] - update_icon() - -/obj/item/restraints/handcuffs/cable/update_icon() - color = null - add_atom_colour(cable_color, FIXED_COLOUR_PRIORITY) -*/ - -/obj/item/restraints/handcuffs/cable/red - color = "#ff0000" - -/obj/item/restraints/handcuffs/cable/yellow - color = "#ffff00" - -/obj/item/restraints/handcuffs/cable/blue - color = "#1919c8" - -/obj/item/restraints/handcuffs/cable/green - color = "#00aa00" - -/obj/item/restraints/handcuffs/cable/pink - color = "#ff3ccd" - -/obj/item/restraints/handcuffs/cable/orange - color = "#ff8000" - -/obj/item/restraints/handcuffs/cable/cyan - color = "#00ffff" - -/obj/item/restraints/handcuffs/cable/white - color = null - -/obj/item/restraints/handcuffs/alien - icon_state = "handcuffAlien" - -/obj/item/restraints/handcuffs/fake - name = "fake handcuffs" - desc = "Fake handcuffs meant for gag purposes." - breakouttime = 1 SECONDS - -/obj/item/restraints/handcuffs/cable/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/stack/rods)) - var/obj/item/stack/rods/R = I - if (R.use(1)) - var/obj/item/wirerod/W = new /obj/item/wirerod - remove_item_from_storage(user) - user.put_in_hands(W) - to_chat(user, "You wrap [src] around the top of [I].") - qdel(src) - else - to_chat(user, "You need one rod to make a wired rod!") - return - else if(istype(I, /obj/item/stack/sheet/metal)) - var/obj/item/stack/sheet/metal/M = I - if(M.get_amount() < 6) - to_chat(user, "You need at least six metal sheets to make good enough weights!") - return - to_chat(user, "You begin to apply [I] to [src]...") - if(do_after(user, 35, target = src)) - if(M.get_amount() < 6 || !M) - return - var/obj/item/restraints/legcuffs/bola/S = new /obj/item/restraints/legcuffs/bola - M.use(6) - user.put_in_hands(S) - to_chat(user, "You make some weights out of [I] and tie them to [src].") - remove_item_from_storage(user) - qdel(src) - else - return ..() - -/obj/item/restraints/handcuffs/cable/zipties - name = "zipties" - desc = "Plastic, disposable zipties that can be used to restrain temporarily but are destroyed after use." - icon_state = "cuff" - lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' - custom_materials = null - breakouttime = 45 SECONDS - trashtype = /obj/item/restraints/handcuffs/cable/zipties/used - color = null - -/obj/item/restraints/handcuffs/cable/zipties/used - desc = "A pair of broken zipties." - icon_state = "cuff_used" - item_state = "cuff" - -/obj/item/restraints/handcuffs/cable/zipties/used/attack() - return - -//Legcuffs - -/obj/item/restraints/legcuffs - name = "leg cuffs" - desc = "Use this to keep prisoners in line." - gender = PLURAL - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "handcuff" - lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' - flags_1 = CONDUCT_1 - throwforce = 0 - w_class = WEIGHT_CLASS_NORMAL - slowdown = 7 - breakouttime = 30 SECONDS - -/obj/item/restraints/legcuffs/beartrap - name = "bear trap" - throw_speed = 1 - throw_range = 1 - icon_state = "beartrap" - desc = "A trap used to catch bears and other legged creatures." - var/armed = 0 - var/trap_damage = 20 - -/obj/item/restraints/legcuffs/beartrap/Initialize() - . = ..() - update_icon() - -/obj/item/restraints/legcuffs/beartrap/update_icon_state() - icon_state = "[initial(icon_state)][armed]" - -/obj/item/restraints/legcuffs/beartrap/suicide_act(mob/user) - user.visible_message("[user] is sticking [user.p_their()] head in the [src.name]! It looks like [user.p_theyre()] trying to commit suicide!") - playsound(loc, 'sound/weapons/bladeslice.ogg', 50, TRUE, -1) - return (BRUTELOSS) - -/obj/item/restraints/legcuffs/beartrap/attack_self(mob/user) - ..() - if(ishuman(user) && !user.stat && !user.restrained()) - armed = !armed - update_icon() - to_chat(user, "[src] is now [armed ? "armed" : "disarmed"]") - -/obj/item/restraints/legcuffs/beartrap/proc/close_trap() - armed = FALSE - update_icon() - playsound(src, 'sound/effects/snap.ogg', 50, TRUE) - -/obj/item/restraints/legcuffs/beartrap/Crossed(AM as mob|obj) - if(armed && isturf(loc)) - if(isliving(AM)) - var/mob/living/L = AM - var/snap = TRUE - if(istype(L.buckled, /obj/vehicle)) - var/obj/vehicle/ridden_vehicle = L.buckled - if(!ridden_vehicle.are_legs_exposed) //close the trap without injuring/trapping the rider if their legs are inside the vehicle at all times. - close_trap() - ridden_vehicle.visible_message("[ridden_vehicle] triggers \the [src].") - return ..() - - if(L.movement_type & (FLYING|FLOATING)) //don't close the trap if they're flying/floating over it. - snap = FALSE - - var/def_zone = BODY_ZONE_CHEST - if(snap && iscarbon(L)) - var/mob/living/carbon/C = L - if(C.mobility_flags & MOBILITY_STAND) - def_zone = pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) - if(!C.legcuffed && C.get_num_legs(FALSE) >= 2) //beartrap can't cuff your leg if there's already a beartrap or legcuffs, or you don't have two legs. - C.legcuffed = src - forceMove(C) - C.update_equipment_speed_mods() - C.update_inv_legcuffed() - SSblackbox.record_feedback("tally", "handcuffs", 1, type) - else if(snap && isanimal(L)) - var/mob/living/simple_animal/SA = L - if(SA.mob_size <= MOB_SIZE_TINY) //don't close the trap if they're as small as a mouse. - snap = FALSE - if(snap) - close_trap() - L.visible_message("[L] triggers \the [src].", \ - "You trigger \the [src]!") - L.apply_damage(trap_damage, BRUTE, def_zone) - ..() - -/obj/item/restraints/legcuffs/beartrap/energy - name = "energy snare" - armed = 1 - icon_state = "e_snare" - trap_damage = 0 - breakouttime = 30 - item_flags = DROPDEL - flags_1 = NONE - -/obj/item/restraints/legcuffs/beartrap/energy/Initialize() - . = ..() - addtimer(CALLBACK(src, .proc/dissipate), 100) - -/obj/item/restraints/legcuffs/beartrap/energy/proc/dissipate() - if(!ismob(loc)) - do_sparks(1, TRUE, src) - qdel(src) - -/obj/item/restraints/legcuffs/beartrap/energy/attack_hand(mob/user) - Crossed(user) //honk - return ..() - -/obj/item/restraints/legcuffs/beartrap/energy/cyborg - breakouttime = 20 // Cyborgs shouldn't have a strong restraint - -/obj/item/restraints/legcuffs/bola - name = "bola" - desc = "A restraining device designed to be thrown at the target. Upon connecting with said target, it will wrap around their legs, making it difficult for them to move quickly." - icon_state = "bola" - item_state = "bola" - lefthand_file = 'icons/mob/inhands/weapons/thrown_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/thrown_righthand.dmi' - breakouttime = 35//easy to apply, easy to break out of - gender = NEUTER - var/knockdown = 0 - -/obj/item/restraints/legcuffs/bola/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, gentle = FALSE, quickstart = TRUE) - if(!..()) - return - playsound(src.loc,'sound/weapons/bolathrow.ogg', 75, TRUE) - -/obj/item/restraints/legcuffs/bola/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - if(..() || !iscarbon(hit_atom))//if it gets caught or the target can't be cuffed, - return//abort - ensnare(hit_atom) - -/** - * Attempts to legcuff someone with the bola - * - * Arguments: - * * C - the carbon that we will try to ensnare - */ -/obj/item/restraints/legcuffs/bola/proc/ensnare(mob/living/carbon/C) - if(!C.legcuffed && C.get_num_legs(FALSE) >= 2) - visible_message("\The [src] ensnares [C]!") - C.legcuffed = src - forceMove(C) - C.update_equipment_speed_mods() - C.update_inv_legcuffed() - SSblackbox.record_feedback("tally", "handcuffs", 1, type) - to_chat(C, "\The [src] ensnares you!") - C.Knockdown(knockdown) - playsound(src, 'sound/effects/snap.ogg', 50, TRUE) - -/obj/item/restraints/legcuffs/bola/tactical//traitor variant - name = "reinforced bola" - desc = "A strong bola, made with a long steel chain. It looks heavy, enough so that it could trip somebody." - icon_state = "bola_r" - item_state = "bola_r" - breakouttime = 70 - knockdown = 35 - -/obj/item/restraints/legcuffs/bola/energy //For Security - name = "energy bola" - desc = "A specialized hard-light bola designed to ensnare fleeing criminals and aid in arrests." - icon_state = "ebola" - item_state = "ebola" - hitsound = 'sound/weapons/taserhit.ogg' - w_class = WEIGHT_CLASS_SMALL - breakouttime = 60 - -/obj/item/restraints/legcuffs/bola/energy/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - if(iscarbon(hit_atom)) - var/obj/item/restraints/legcuffs/beartrap/B = new /obj/item/restraints/legcuffs/beartrap/energy/cyborg(get_turf(hit_atom)) - B.Crossed(hit_atom) - qdel(src) - ..() - -/obj/item/restraints/legcuffs/bola/gonbola - name = "gonbola" - desc = "Hey, if you have to be hugged in the legs by anything, it might as well be this little guy." - icon_state = "gonbola" - item_state = "bola_r" - breakouttime = 300 - slowdown = 0 - var/datum/status_effect/gonbolaPacify/effectReference - -/obj/item/restraints/legcuffs/bola/gonbola/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - . = ..() - if(iscarbon(hit_atom)) - var/mob/living/carbon/C = hit_atom - effectReference = C.apply_status_effect(STATUS_EFFECT_GONBOLAPACIFY) - -/obj/item/restraints/legcuffs/bola/gonbola/dropped(mob/user) - . = ..() - if(effectReference) - QDEL_NULL(effectReference) +/obj/item/restraints + breakouttime = 600 + +/obj/item/restraints/suicide_act(mob/living/carbon/user) + 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/restraints/Destroy() + if(iscarbon(loc)) + var/mob/living/carbon/M = loc + if(M.handcuffed == src) + M.handcuffed = null + M.update_handcuffed() + if(M.buckled && M.buckled.buckle_requires_restraints) + M.buckled.unbuckle_mob(M) + if(M.legcuffed == src) + M.legcuffed = null + M.update_inv_legcuffed() + return ..() + +//Handcuffs + +/obj/item/restraints/handcuffs + name = "handcuffs" + desc = "Use this to keep prisoners in line." + gender = PLURAL + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "handcuff" + lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BELT + throwforce = 0 + w_class = WEIGHT_CLASS_SMALL + throw_speed = 3 + throw_range = 5 + custom_materials = list(/datum/material/iron=500) + breakouttime = 1 MINUTES + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + var/cuffsound = 'sound/weapons/handcuffs.ogg' + var/trashtype = null //for disposable cuffs + +/obj/item/restraints/handcuffs/attack(mob/living/carbon/C, mob/living/user) + if(!istype(C)) + return + + if(iscarbon(user) && (HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50))) + to_chat(user, "Uh... how do those things work?!") + apply_cuffs(user,user) + return + + // chance of monkey retaliation + if(ismonkey(C) && prob(MONKEY_CUFF_RETALIATION_PROB)) + var/mob/living/carbon/monkey/M + M = C + M.retaliate(user) + + if(!C.handcuffed) + if(C.get_num_arms(FALSE) >= 2 || C.get_arm_ignore()) + C.visible_message("[user] is trying to put [src.name] on [C]!", \ + "[user] is trying to put [src.name] on you!") + + playsound(loc, cuffsound, 30, TRUE, -2) + if(do_mob(user, C, 30) && (C.get_num_arms(FALSE) >= 2 || C.get_arm_ignore())) + if(iscyborg(user)) + apply_cuffs(C, user, TRUE) + else + apply_cuffs(C, user) + C.visible_message("[user] handcuffs [C].", \ + "[user] handcuffs you.") + SSblackbox.record_feedback("tally", "handcuffs", 1, type) + + log_combat(user, C, "handcuffed") + else + to_chat(user, "You fail to handcuff [C]!") + else + to_chat(user, "[C] doesn't have two hands...") + +/obj/item/restraints/handcuffs/proc/apply_cuffs(mob/living/carbon/target, mob/user, dispense = 0) + if(target.handcuffed) + return + + if(!user.temporarilyRemoveItemFromInventory(src) && !dispense) + return + + var/obj/item/restraints/handcuffs/cuffs = src + if(trashtype) + cuffs = new trashtype() + else if(dispense) + cuffs = new type() + + cuffs.forceMove(target) + target.handcuffed = cuffs + + target.update_handcuffed() + if(trashtype && !dispense) + qdel(src) + return + +/obj/item/restraints/handcuffs/cable/sinew + name = "sinew restraints" + desc = "A pair of restraints fashioned from long strands of flesh." + icon = 'icons/obj/mining.dmi' + icon_state = "sinewcuff" + item_state = "sinewcuff" + custom_materials = null + color = null + +/obj/item/restraints/handcuffs/cable + name = "cable restraints" + desc = "Looks like some cables tied together. Could be used to tie something up." + icon_state = "cuff" + item_state = "coil" + color = "#ff0000" + lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' + custom_materials = list(/datum/material/iron=150, /datum/material/glass=75) + breakouttime = 30 SECONDS + cuffsound = 'sound/weapons/cablecuff.ogg' + +/*Wasp - Smartwire Revert +/obj/item/restraints/handcuffs/cable/Initialize(mapload, param_color) + . = ..() + + var/list/cable_colors = GLOB.cable_colors + cable_color = param_color || cable_color || pick(cable_colors) + if(cable_colors[cable_color]) + cable_color = cable_colors[cable_color] + update_icon() + +/obj/item/restraints/handcuffs/cable/update_icon() + color = null + add_atom_colour(cable_color, FIXED_COLOUR_PRIORITY) +*/ + +/obj/item/restraints/handcuffs/cable/red + color = "#ff0000" + +/obj/item/restraints/handcuffs/cable/yellow + color = "#ffff00" + +/obj/item/restraints/handcuffs/cable/blue + color = "#1919c8" + +/obj/item/restraints/handcuffs/cable/green + color = "#00aa00" + +/obj/item/restraints/handcuffs/cable/pink + color = "#ff3ccd" + +/obj/item/restraints/handcuffs/cable/orange + color = "#ff8000" + +/obj/item/restraints/handcuffs/cable/cyan + color = "#00ffff" + +/obj/item/restraints/handcuffs/cable/white + color = null + +/obj/item/restraints/handcuffs/alien + icon_state = "handcuffAlien" + +/obj/item/restraints/handcuffs/fake + name = "fake handcuffs" + desc = "Fake handcuffs meant for gag purposes." + breakouttime = 1 SECONDS + +/obj/item/restraints/handcuffs/cable/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/stack/rods)) + var/obj/item/stack/rods/R = I + if (R.use(1)) + var/obj/item/wirerod/W = new /obj/item/wirerod + remove_item_from_storage(user) + user.put_in_hands(W) + to_chat(user, "You wrap [src] around the top of [I].") + qdel(src) + else + to_chat(user, "You need one rod to make a wired rod!") + return + else if(istype(I, /obj/item/stack/sheet/metal)) + var/obj/item/stack/sheet/metal/M = I + if(M.get_amount() < 6) + to_chat(user, "You need at least six metal sheets to make good enough weights!") + return + to_chat(user, "You begin to apply [I] to [src]...") + if(do_after(user, 35, target = src)) + if(M.get_amount() < 6 || !M) + return + var/obj/item/restraints/legcuffs/bola/S = new /obj/item/restraints/legcuffs/bola + M.use(6) + user.put_in_hands(S) + to_chat(user, "You make some weights out of [I] and tie them to [src].") + remove_item_from_storage(user) + qdel(src) + else + return ..() + +/obj/item/restraints/handcuffs/cable/zipties + name = "zipties" + desc = "Plastic, disposable zipties that can be used to restrain temporarily but are destroyed after use." + icon_state = "cuff" + lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' + custom_materials = null + breakouttime = 45 SECONDS + trashtype = /obj/item/restraints/handcuffs/cable/zipties/used + color = null + +/obj/item/restraints/handcuffs/cable/zipties/used + desc = "A pair of broken zipties." + icon_state = "cuff_used" + item_state = "cuff" + +/obj/item/restraints/handcuffs/cable/zipties/used/attack() + return + +//Legcuffs + +/obj/item/restraints/legcuffs + name = "leg cuffs" + desc = "Use this to keep prisoners in line." + gender = PLURAL + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "handcuff" + lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' + flags_1 = CONDUCT_1 + throwforce = 0 + w_class = WEIGHT_CLASS_NORMAL + slowdown = 7 + breakouttime = 30 SECONDS + +/obj/item/restraints/legcuffs/beartrap + name = "bear trap" + throw_speed = 1 + throw_range = 1 + icon_state = "beartrap" + desc = "A trap used to catch bears and other legged creatures." + var/armed = 0 + var/trap_damage = 20 + +/obj/item/restraints/legcuffs/beartrap/Initialize() + . = ..() + update_icon() + +/obj/item/restraints/legcuffs/beartrap/update_icon_state() + icon_state = "[initial(icon_state)][armed]" + +/obj/item/restraints/legcuffs/beartrap/suicide_act(mob/user) + user.visible_message("[user] is sticking [user.p_their()] head in the [src.name]! It looks like [user.p_theyre()] trying to commit suicide!") + playsound(loc, 'sound/weapons/bladeslice.ogg', 50, TRUE, -1) + return (BRUTELOSS) + +/obj/item/restraints/legcuffs/beartrap/attack_self(mob/user) + ..() + if(ishuman(user) && !user.stat && !user.restrained()) + armed = !armed + update_icon() + to_chat(user, "[src] is now [armed ? "armed" : "disarmed"]") + +/obj/item/restraints/legcuffs/beartrap/proc/close_trap() + armed = FALSE + update_icon() + playsound(src, 'sound/effects/snap.ogg', 50, TRUE) + +/obj/item/restraints/legcuffs/beartrap/Crossed(AM as mob|obj) + if(armed && isturf(loc)) + if(isliving(AM)) + var/mob/living/L = AM + var/snap = TRUE + if(istype(L.buckled, /obj/vehicle)) + var/obj/vehicle/ridden_vehicle = L.buckled + if(!ridden_vehicle.are_legs_exposed) //close the trap without injuring/trapping the rider if their legs are inside the vehicle at all times. + close_trap() + ridden_vehicle.visible_message("[ridden_vehicle] triggers \the [src].") + return ..() + + if(L.movement_type & (FLYING|FLOATING)) //don't close the trap if they're flying/floating over it. + snap = FALSE + + var/def_zone = BODY_ZONE_CHEST + if(snap && iscarbon(L)) + var/mob/living/carbon/C = L + if(C.mobility_flags & MOBILITY_STAND) + def_zone = pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) + if(!C.legcuffed && C.get_num_legs(FALSE) >= 2) //beartrap can't cuff your leg if there's already a beartrap or legcuffs, or you don't have two legs. + C.legcuffed = src + forceMove(C) + C.update_equipment_speed_mods() + C.update_inv_legcuffed() + SSblackbox.record_feedback("tally", "handcuffs", 1, type) + else if(snap && isanimal(L)) + var/mob/living/simple_animal/SA = L + if(SA.mob_size <= MOB_SIZE_TINY) //don't close the trap if they're as small as a mouse. + snap = FALSE + if(snap) + close_trap() + L.visible_message("[L] triggers \the [src].", \ + "You trigger \the [src]!") + L.apply_damage(trap_damage, BRUTE, def_zone) + ..() + +/obj/item/restraints/legcuffs/beartrap/energy + name = "energy snare" + armed = 1 + icon_state = "e_snare" + trap_damage = 0 + breakouttime = 30 + item_flags = DROPDEL + flags_1 = NONE + +/obj/item/restraints/legcuffs/beartrap/energy/Initialize() + . = ..() + addtimer(CALLBACK(src, .proc/dissipate), 100) + +/obj/item/restraints/legcuffs/beartrap/energy/proc/dissipate() + if(!ismob(loc)) + do_sparks(1, TRUE, src) + qdel(src) + +/obj/item/restraints/legcuffs/beartrap/energy/attack_hand(mob/user) + Crossed(user) //honk + return ..() + +/obj/item/restraints/legcuffs/beartrap/energy/cyborg + breakouttime = 20 // Cyborgs shouldn't have a strong restraint + +/obj/item/restraints/legcuffs/bola + name = "bola" + desc = "A restraining device designed to be thrown at the target. Upon connecting with said target, it will wrap around their legs, making it difficult for them to move quickly." + icon_state = "bola" + item_state = "bola" + lefthand_file = 'icons/mob/inhands/weapons/thrown_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/thrown_righthand.dmi' + breakouttime = 35//easy to apply, easy to break out of + gender = NEUTER + var/knockdown = 0 + +/obj/item/restraints/legcuffs/bola/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, gentle = FALSE, quickstart = TRUE) + if(!..()) + return + playsound(src.loc,'sound/weapons/bolathrow.ogg', 75, TRUE) + +/obj/item/restraints/legcuffs/bola/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + if(..() || !iscarbon(hit_atom))//if it gets caught or the target can't be cuffed, + return//abort + ensnare(hit_atom) + +/** + * Attempts to legcuff someone with the bola + * + * Arguments: + * * C - the carbon that we will try to ensnare + */ +/obj/item/restraints/legcuffs/bola/proc/ensnare(mob/living/carbon/C) + if(!C.legcuffed && C.get_num_legs(FALSE) >= 2) + visible_message("\The [src] ensnares [C]!") + C.legcuffed = src + forceMove(C) + C.update_equipment_speed_mods() + C.update_inv_legcuffed() + SSblackbox.record_feedback("tally", "handcuffs", 1, type) + to_chat(C, "\The [src] ensnares you!") + C.Knockdown(knockdown) + playsound(src, 'sound/effects/snap.ogg', 50, TRUE) + +/obj/item/restraints/legcuffs/bola/tactical//traitor variant + name = "reinforced bola" + desc = "A strong bola, made with a long steel chain. It looks heavy, enough so that it could trip somebody." + icon_state = "bola_r" + item_state = "bola_r" + breakouttime = 70 + knockdown = 35 + +/obj/item/restraints/legcuffs/bola/energy //For Security + name = "energy bola" + desc = "A specialized hard-light bola designed to ensnare fleeing criminals and aid in arrests." + icon_state = "ebola" + item_state = "ebola" + hitsound = 'sound/weapons/taserhit.ogg' + w_class = WEIGHT_CLASS_SMALL + breakouttime = 60 + +/obj/item/restraints/legcuffs/bola/energy/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + if(iscarbon(hit_atom)) + var/obj/item/restraints/legcuffs/beartrap/B = new /obj/item/restraints/legcuffs/beartrap/energy/cyborg(get_turf(hit_atom)) + B.Crossed(hit_atom) + qdel(src) + ..() + +/obj/item/restraints/legcuffs/bola/gonbola + name = "gonbola" + desc = "Hey, if you have to be hugged in the legs by anything, it might as well be this little guy." + icon_state = "gonbola" + item_state = "bola_r" + breakouttime = 300 + slowdown = 0 + var/datum/status_effect/gonbolaPacify/effectReference + +/obj/item/restraints/legcuffs/bola/gonbola/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + . = ..() + if(iscarbon(hit_atom)) + var/mob/living/carbon/C = hit_atom + effectReference = C.apply_status_effect(STATUS_EFFECT_GONBOLAPACIFY) + +/obj/item/restraints/legcuffs/bola/gonbola/dropped(mob/user) + . = ..() + if(effectReference) + QDEL_NULL(effectReference) diff --git a/code/game/objects/items/hot_potato.dm b/code/game/objects/items/hot_potato.dm index 50ca27defc3e..3893cc997b47 100644 --- a/code/game/objects/items/hot_potato.dm +++ b/code/game/objects/items/hot_potato.dm @@ -1,174 +1,174 @@ -//CREATOR'S NOTE: DO NOT FUCKING GIVE THIS TO BOTANY! -/obj/item/hot_potato - name = "hot potato" - desc = "A label on the side of this potato reads \"Product of DonkCo Service Wing. Activate far away from populated areas. Device will only attach to sapient creatures.\" You can attack anyone with it to force it on them instead of yourself!" - icon = 'icons/obj/hydroponics/harvest.dmi' - icon_state = "potato" - item_flags = NOBLUDGEON - force = 0 - var/icon_off = "potato" - var/icon_on = "potato_active" - var/detonation_timerid - var/activation_time = 0 - var/timer = 600 //deciseconds - var/show_timer = FALSE - var/reusable = FALSE //absolute madman - var/sticky = TRUE - var/forceful_attachment = TRUE - var/stimulant = TRUE - var/detonate_explosion = TRUE - var/detonate_dev_range = 0 - var/detonate_heavy_range = 0 - var/detonate_light_range = 2 - var/detonate_flash_range = 5 - var/detonate_fire_range = 5 - - var/active = FALSE - - var/color_val = FALSE - - var/datum/weakref/current - -/obj/item/hot_potato/Destroy() - if(active) - deactivate() - return ..() - -/obj/item/hot_potato/proc/colorize(mob/target) - //Clear color from old target - if(current) - var/mob/M = current.resolve() - if(istype(M)) - M.remove_atom_colour(FIXED_COLOUR_PRIORITY) - //Give to new target - current = null - //Swap colors - color_val = !color_val - if(istype(target)) - current = WEAKREF(target) - target.add_atom_colour(color_val? "#ffff00" : "#00ffff", FIXED_COLOUR_PRIORITY) - -/obj/item/hot_potato/proc/detonate() - var/atom/location = loc - location.visible_message("[src] [detonate_explosion? "explodes" : "activates"]!", "[src] activates! You've ran out of time!") - if(detonate_explosion) - explosion(src, detonate_dev_range, detonate_heavy_range, detonate_light_range, detonate_flash_range, flame_range = detonate_fire_range) - deactivate() - if(!reusable) - var/mob/M = loc - if(istype(M)) - M.dropItemToGround(src, TRUE) - qdel(src) - -/obj/item/hot_potato/attack_self(mob/user) - if(activate(timer, user)) - user.visible_message("[user] squeezes [src], which promptly starts to flash red-hot colors!", "You squeeze [src], activating its countdown and attachment mechanism!", - "You hear a mechanical click and a loud beeping!") - return - return ..() - -/obj/item/hot_potato/process() - if(stimulant) - if(isliving(loc)) - var/mob/living/L = loc - L.SetStun(0) - L.SetKnockdown(0) - L.SetSleeping(0) - L.SetImmobilized(0) - L.SetParalyzed(0) - L.SetUnconscious(0) - L.reagents.add_reagent(/datum/reagent/medicine/muscle_stimulant, clamp(5 - L.reagents.get_reagent_amount(/datum/reagent/medicine/muscle_stimulant), 0, 5)) //If you don't have legs or get bola'd, tough luck! - colorize(L) - -/obj/item/hot_potato/examine(mob/user) - . = ..() - if(active) - . += "[src] is flashing red-hot! You should probably get rid of it!" - if(show_timer) - . += "[src]'s timer looks to be at [DisplayTimeText(activation_time - world.time)]!" - -/obj/item/hot_potato/equipped(mob/user) - . = ..() - if(active) - to_chat(user, "You have a really bad feeling about [src]!") - -/obj/item/hot_potato/afterattack(atom/target, mob/user, adjacent, params) - . = ..() - if(!adjacent || !ismob(target)) - return - force_onto(target, user) - -/obj/item/hot_potato/proc/force_onto(mob/living/victim, mob/user) - if(!istype(victim) || user != loc || victim == user) - return FALSE - if(!victim.client) - to_chat(user, "[src] refuses to attach to a non-sapient creature!") - if(victim.stat != CONSCIOUS || !victim.get_num_legs()) - to_chat(user, "[src] refuses to attach to someone incapable of using it!") - user.temporarilyRemoveItemFromInventory(src, TRUE) - . = FALSE - if(!victim.put_in_hands(src)) - if(forceful_attachment) - victim.dropItemToGround(victim.get_inactive_held_item()) - if(!victim.put_in_hands(src)) - victim.dropItemToGround(victim.get_active_held_item()) - if(victim.put_in_hands(src)) - . = TRUE - else - . = TRUE - else - . = TRUE - if(.) - log_combat(user, victim, "forced a hot potato with explosive variables ([detonate_explosion]-[detonate_dev_range]/[detonate_heavy_range]/[detonate_light_range]/[detonate_flash_range]/[detonate_fire_range]) onto") - user.visible_message("[user] forces [src] onto [victim]!", "You force [src] onto [victim]!", "You hear a mechanical click and a beep.") - colorize(null) - else - log_combat(user, victim, "tried to force a hot potato with explosive variables ([detonate_explosion]-[detonate_dev_range]/[detonate_heavy_range]/[detonate_light_range]/[detonate_flash_range]/[detonate_fire_range]) onto") - user.visible_message("[user] tried to force [src] onto [victim], but it could not attach!", "You try to force [src] onto [victim], but it is unable to attach!", "You hear a mechanical click and two buzzes.") - user.put_in_hands(src) - -/obj/item/hot_potato/dropped(mob/user) - . = ..() - colorize(null) - -/obj/item/hot_potato/proc/activate(delay, mob/user) - if(active) - return - update_icon() - if(sticky) - ADD_TRAIT(src, TRAIT_NODROP, HOT_POTATO_TRAIT) - name = "primed [name]" - activation_time = timer + world.time - detonation_timerid = addtimer(CALLBACK(src, .proc/detonate), delay, TIMER_STOPPABLE) - START_PROCESSING(SSfastprocess, src) - if(user) - log_bomber(user, "has primed a", src, "for detonation (Timer:[delay],Explosive:[detonate_explosion],Range:[detonate_dev_range]/[detonate_heavy_range]/[detonate_light_range]/[detonate_fire_range])") - else - log_bomber(null, null, src, "was primed for detonation (Timer:[delay],Explosive:[detonate_explosion],Range:[detonate_dev_range]/[detonate_heavy_range]/[detonate_light_range]/[detonate_fire_range])") - active = TRUE - -/obj/item/hot_potato/proc/deactivate() - update_icon() - name = initial(name) - REMOVE_TRAIT(src, TRAIT_NODROP, HOT_POTATO_TRAIT) - deltimer(detonation_timerid) - STOP_PROCESSING(SSfastprocess, src) - detonation_timerid = null - colorize(null) - active = FALSE - -/obj/item/hot_potato/update_icon_state() - icon_state = active? icon_on : icon_off - -/obj/item/hot_potato/syndicate - detonate_light_range = 4 - detonate_fire_range = 5 - -/obj/item/hot_potato/harmless - detonate_explosion = FALSE - -/obj/item/hot_potato/harmless/toy - desc = "A label on the side of this potato reads \"Product of DonkCo Toys and Recreation department.\" You can attack anyone with it to put it on them instead, if they have a free hand to take it!" - sticky = FALSE - reusable = TRUE - forceful_attachment = FALSE +//CREATOR'S NOTE: DO NOT FUCKING GIVE THIS TO BOTANY! +/obj/item/hot_potato + name = "hot potato" + desc = "A label on the side of this potato reads \"Product of DonkCo Service Wing. Activate far away from populated areas. Device will only attach to sapient creatures.\" You can attack anyone with it to force it on them instead of yourself!" + icon = 'icons/obj/hydroponics/harvest.dmi' + icon_state = "potato" + item_flags = NOBLUDGEON + force = 0 + var/icon_off = "potato" + var/icon_on = "potato_active" + var/detonation_timerid + var/activation_time = 0 + var/timer = 600 //deciseconds + var/show_timer = FALSE + var/reusable = FALSE //absolute madman + var/sticky = TRUE + var/forceful_attachment = TRUE + var/stimulant = TRUE + var/detonate_explosion = TRUE + var/detonate_dev_range = 0 + var/detonate_heavy_range = 0 + var/detonate_light_range = 2 + var/detonate_flash_range = 5 + var/detonate_fire_range = 5 + + var/active = FALSE + + var/color_val = FALSE + + var/datum/weakref/current + +/obj/item/hot_potato/Destroy() + if(active) + deactivate() + return ..() + +/obj/item/hot_potato/proc/colorize(mob/target) + //Clear color from old target + if(current) + var/mob/M = current.resolve() + if(istype(M)) + M.remove_atom_colour(FIXED_COLOUR_PRIORITY) + //Give to new target + current = null + //Swap colors + color_val = !color_val + if(istype(target)) + current = WEAKREF(target) + target.add_atom_colour(color_val? "#ffff00" : "#00ffff", FIXED_COLOUR_PRIORITY) + +/obj/item/hot_potato/proc/detonate() + var/atom/location = loc + location.visible_message("[src] [detonate_explosion? "explodes" : "activates"]!", "[src] activates! You've ran out of time!") + if(detonate_explosion) + explosion(src, detonate_dev_range, detonate_heavy_range, detonate_light_range, detonate_flash_range, flame_range = detonate_fire_range) + deactivate() + if(!reusable) + var/mob/M = loc + if(istype(M)) + M.dropItemToGround(src, TRUE) + qdel(src) + +/obj/item/hot_potato/attack_self(mob/user) + if(activate(timer, user)) + user.visible_message("[user] squeezes [src], which promptly starts to flash red-hot colors!", "You squeeze [src], activating its countdown and attachment mechanism!", + "You hear a mechanical click and a loud beeping!") + return + return ..() + +/obj/item/hot_potato/process() + if(stimulant) + if(isliving(loc)) + var/mob/living/L = loc + L.SetStun(0) + L.SetKnockdown(0) + L.SetSleeping(0) + L.SetImmobilized(0) + L.SetParalyzed(0) + L.SetUnconscious(0) + L.reagents.add_reagent(/datum/reagent/medicine/muscle_stimulant, clamp(5 - L.reagents.get_reagent_amount(/datum/reagent/medicine/muscle_stimulant), 0, 5)) //If you don't have legs or get bola'd, tough luck! + colorize(L) + +/obj/item/hot_potato/examine(mob/user) + . = ..() + if(active) + . += "[src] is flashing red-hot! You should probably get rid of it!" + if(show_timer) + . += "[src]'s timer looks to be at [DisplayTimeText(activation_time - world.time)]!" + +/obj/item/hot_potato/equipped(mob/user) + . = ..() + if(active) + to_chat(user, "You have a really bad feeling about [src]!") + +/obj/item/hot_potato/afterattack(atom/target, mob/user, adjacent, params) + . = ..() + if(!adjacent || !ismob(target)) + return + force_onto(target, user) + +/obj/item/hot_potato/proc/force_onto(mob/living/victim, mob/user) + if(!istype(victim) || user != loc || victim == user) + return FALSE + if(!victim.client) + to_chat(user, "[src] refuses to attach to a non-sapient creature!") + if(victim.stat != CONSCIOUS || !victim.get_num_legs()) + to_chat(user, "[src] refuses to attach to someone incapable of using it!") + user.temporarilyRemoveItemFromInventory(src, TRUE) + . = FALSE + if(!victim.put_in_hands(src)) + if(forceful_attachment) + victim.dropItemToGround(victim.get_inactive_held_item()) + if(!victim.put_in_hands(src)) + victim.dropItemToGround(victim.get_active_held_item()) + if(victim.put_in_hands(src)) + . = TRUE + else + . = TRUE + else + . = TRUE + if(.) + log_combat(user, victim, "forced a hot potato with explosive variables ([detonate_explosion]-[detonate_dev_range]/[detonate_heavy_range]/[detonate_light_range]/[detonate_flash_range]/[detonate_fire_range]) onto") + user.visible_message("[user] forces [src] onto [victim]!", "You force [src] onto [victim]!", "You hear a mechanical click and a beep.") + colorize(null) + else + log_combat(user, victim, "tried to force a hot potato with explosive variables ([detonate_explosion]-[detonate_dev_range]/[detonate_heavy_range]/[detonate_light_range]/[detonate_flash_range]/[detonate_fire_range]) onto") + user.visible_message("[user] tried to force [src] onto [victim], but it could not attach!", "You try to force [src] onto [victim], but it is unable to attach!", "You hear a mechanical click and two buzzes.") + user.put_in_hands(src) + +/obj/item/hot_potato/dropped(mob/user) + . = ..() + colorize(null) + +/obj/item/hot_potato/proc/activate(delay, mob/user) + if(active) + return + update_icon() + if(sticky) + ADD_TRAIT(src, TRAIT_NODROP, HOT_POTATO_TRAIT) + name = "primed [name]" + activation_time = timer + world.time + detonation_timerid = addtimer(CALLBACK(src, .proc/detonate), delay, TIMER_STOPPABLE) + START_PROCESSING(SSfastprocess, src) + if(user) + log_bomber(user, "has primed a", src, "for detonation (Timer:[delay],Explosive:[detonate_explosion],Range:[detonate_dev_range]/[detonate_heavy_range]/[detonate_light_range]/[detonate_fire_range])") + else + log_bomber(null, null, src, "was primed for detonation (Timer:[delay],Explosive:[detonate_explosion],Range:[detonate_dev_range]/[detonate_heavy_range]/[detonate_light_range]/[detonate_fire_range])") + active = TRUE + +/obj/item/hot_potato/proc/deactivate() + update_icon() + name = initial(name) + REMOVE_TRAIT(src, TRAIT_NODROP, HOT_POTATO_TRAIT) + deltimer(detonation_timerid) + STOP_PROCESSING(SSfastprocess, src) + detonation_timerid = null + colorize(null) + active = FALSE + +/obj/item/hot_potato/update_icon_state() + icon_state = active? icon_on : icon_off + +/obj/item/hot_potato/syndicate + detonate_light_range = 4 + detonate_fire_range = 5 + +/obj/item/hot_potato/harmless + detonate_explosion = FALSE + +/obj/item/hot_potato/harmless/toy + desc = "A label on the side of this potato reads \"Product of DonkCo Toys and Recreation department.\" You can attack anyone with it to put it on them instead, if they have a free hand to take it!" + sticky = FALSE + reusable = TRUE + forceful_attachment = FALSE diff --git a/code/game/objects/items/implants/implant.dm b/code/game/objects/items/implants/implant.dm index 07e0ab2c8f4a..fd79c112c9b1 100644 --- a/code/game/objects/items/implants/implant.dm +++ b/code/game/objects/items/implants/implant.dm @@ -1,114 +1,114 @@ -/obj/item/implant - name = "implant" - icon = 'icons/obj/implants.dmi' - icon_state = "generic" //Shows up as the action button icon - actions_types = list(/datum/action/item_action/hands_free/activate) - var/activated = TRUE //1 for implant types that can be activated, 0 for ones that are "always on" like mindshield implants - var/mob/living/imp_in = null - var/implant_color = "b" - var/allow_multiple = FALSE - var/uses = -1 - item_flags = DROPDEL - - -/obj/item/implant/proc/trigger(emote, mob/living/carbon/source) - return - -/obj/item/implant/proc/on_death(emote, mob/living/carbon/source) - return - -/obj/item/implant/proc/activate() - SEND_SIGNAL(src, COMSIG_IMPLANT_ACTIVATED) - -/obj/item/implant/ui_action_click() - activate("action_button") - -/obj/item/implant/proc/can_be_implanted_in(mob/living/target) // for human-only and other special requirements - return TRUE - -/mob/living/proc/can_be_implanted() - return TRUE - -/mob/living/silicon/can_be_implanted() - return FALSE - -/mob/living/simple_animal/can_be_implanted() - return healable //Applies to robots and most non-organics, exceptions can override. - - - -//What does the implant do upon injection? -//return 1 if the implant injects -//return 0 if there is no room for implant / it fails -/obj/item/implant/proc/implant(mob/living/target, mob/user, silent = FALSE, force = FALSE) - if(SEND_SIGNAL(src, COMSIG_IMPLANT_IMPLANTING, args) & COMPONENT_STOP_IMPLANTING) - return - LAZYINITLIST(target.implants) - if(!force && (!target.can_be_implanted() || !can_be_implanted_in(target))) - return FALSE - for(var/X in target.implants) - var/obj/item/implant/imp_e = X - var/flags = SEND_SIGNAL(imp_e, COMSIG_IMPLANT_OTHER, args, src) - if(flags & COMPONENT_DELETE_NEW_IMPLANT) - UNSETEMPTY(target.implants) - qdel(src) - return TRUE - if(flags & COMPONENT_DELETE_OLD_IMPLANT) - qdel(imp_e) - continue - if(flags & COMPONENT_STOP_IMPLANTING) - UNSETEMPTY(target.implants) - return FALSE - - if(istype(imp_e, type)) - if(!allow_multiple) - if(imp_e.uses < initial(imp_e.uses)*2) - if(uses == -1) - imp_e.uses = -1 - else - imp_e.uses = min(imp_e.uses + uses, initial(imp_e.uses)*2) - qdel(src) - return TRUE - else - return FALSE - - forceMove(target) - imp_in = target - target.implants += src - if(activated) - for(var/X in actions) - var/datum/action/A = X - A.Grant(target) - if(ishuman(target)) - var/mob/living/carbon/human/H = target - H.sec_hud_set_implants() - - if(user) - log_combat(user, target, "implanted", "\a [name]") - - return TRUE - -/obj/item/implant/proc/removed(mob/living/source, silent = FALSE, special = 0) - moveToNullspace() - imp_in = null - source.implants -= src - for(var/X in actions) - var/datum/action/A = X - A.Grant(source) - if(ishuman(source)) - var/mob/living/carbon/human/H = source - H.sec_hud_set_implants() - - return 1 - -/obj/item/implant/Destroy() - if(imp_in) - removed(imp_in) - return ..() - -/obj/item/implant/proc/get_data() - return "No information available" - -/obj/item/implant/dropped(mob/user) - . = 1 - ..() +/obj/item/implant + name = "implant" + icon = 'icons/obj/implants.dmi' + icon_state = "generic" //Shows up as the action button icon + actions_types = list(/datum/action/item_action/hands_free/activate) + var/activated = TRUE //1 for implant types that can be activated, 0 for ones that are "always on" like mindshield implants + var/mob/living/imp_in = null + var/implant_color = "b" + var/allow_multiple = FALSE + var/uses = -1 + item_flags = DROPDEL + + +/obj/item/implant/proc/trigger(emote, mob/living/carbon/source) + return + +/obj/item/implant/proc/on_death(emote, mob/living/carbon/source) + return + +/obj/item/implant/proc/activate() + SEND_SIGNAL(src, COMSIG_IMPLANT_ACTIVATED) + +/obj/item/implant/ui_action_click() + activate("action_button") + +/obj/item/implant/proc/can_be_implanted_in(mob/living/target) // for human-only and other special requirements + return TRUE + +/mob/living/proc/can_be_implanted() + return TRUE + +/mob/living/silicon/can_be_implanted() + return FALSE + +/mob/living/simple_animal/can_be_implanted() + return healable //Applies to robots and most non-organics, exceptions can override. + + + +//What does the implant do upon injection? +//return 1 if the implant injects +//return 0 if there is no room for implant / it fails +/obj/item/implant/proc/implant(mob/living/target, mob/user, silent = FALSE, force = FALSE) + if(SEND_SIGNAL(src, COMSIG_IMPLANT_IMPLANTING, args) & COMPONENT_STOP_IMPLANTING) + return + LAZYINITLIST(target.implants) + if(!force && (!target.can_be_implanted() || !can_be_implanted_in(target))) + return FALSE + for(var/X in target.implants) + var/obj/item/implant/imp_e = X + var/flags = SEND_SIGNAL(imp_e, COMSIG_IMPLANT_OTHER, args, src) + if(flags & COMPONENT_DELETE_NEW_IMPLANT) + UNSETEMPTY(target.implants) + qdel(src) + return TRUE + if(flags & COMPONENT_DELETE_OLD_IMPLANT) + qdel(imp_e) + continue + if(flags & COMPONENT_STOP_IMPLANTING) + UNSETEMPTY(target.implants) + return FALSE + + if(istype(imp_e, type)) + if(!allow_multiple) + if(imp_e.uses < initial(imp_e.uses)*2) + if(uses == -1) + imp_e.uses = -1 + else + imp_e.uses = min(imp_e.uses + uses, initial(imp_e.uses)*2) + qdel(src) + return TRUE + else + return FALSE + + forceMove(target) + imp_in = target + target.implants += src + if(activated) + for(var/X in actions) + var/datum/action/A = X + A.Grant(target) + if(ishuman(target)) + var/mob/living/carbon/human/H = target + H.sec_hud_set_implants() + + if(user) + log_combat(user, target, "implanted", "\a [name]") + + return TRUE + +/obj/item/implant/proc/removed(mob/living/source, silent = FALSE, special = 0) + moveToNullspace() + imp_in = null + source.implants -= src + for(var/X in actions) + var/datum/action/A = X + A.Grant(source) + if(ishuman(source)) + var/mob/living/carbon/human/H = source + H.sec_hud_set_implants() + + return 1 + +/obj/item/implant/Destroy() + if(imp_in) + removed(imp_in) + return ..() + +/obj/item/implant/proc/get_data() + return "No information available" + +/obj/item/implant/dropped(mob/user) + . = 1 + ..() diff --git a/code/game/objects/items/implants/implant_misc.dm b/code/game/objects/items/implants/implant_misc.dm index 40b13f91159f..bca5fc89515c 100644 --- a/code/game/objects/items/implants/implant_misc.dm +++ b/code/game/objects/items/implants/implant_misc.dm @@ -97,7 +97,7 @@ /obj/item/implant/radio/activate() . = ..() // needs to be GLOB.deep_inventory_state otherwise it won't open - radio.ui_interact(usr, "main", null, FALSE, null, GLOB.deep_inventory_state) + radio.ui_interact(usr, state = GLOB.deep_inventory_state) /obj/item/implant/radio/Initialize(mapload) . = ..() diff --git a/code/game/objects/items/implants/implantcase.dm b/code/game/objects/items/implants/implantcase.dm index 5368b6e1bb56..70baa76a7fe6 100644 --- a/code/game/objects/items/implants/implantcase.dm +++ b/code/game/objects/items/implants/implantcase.dm @@ -1,84 +1,84 @@ -/obj/item/implantcase - name = "implant case" - desc = "A glass case containing an implant." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "implantcase-0" - item_state = "implantcase" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - throw_speed = 2 - throw_range = 5 - w_class = WEIGHT_CLASS_TINY - custom_materials = list(/datum/material/glass=500) - var/obj/item/implant/imp = null - var/imp_type - - -/obj/item/implantcase/update_icon_state() - if(imp) - icon_state = "implantcase-[imp.implant_color]" - else - icon_state = "implantcase-0" - - -/obj/item/implantcase/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/pen)) - if(!user.is_literate()) - to_chat(user, "You scribble illegibly on the side of [src]!") - return - var/t = stripped_input(user, "What would you like the label to be?", name, null) - if(user.get_active_held_item() != W) - return - if(!user.canUseTopic(src, BE_CLOSE)) - return - if(t) - name = "implant case - '[t]'" - else - name = "implant case" - else if(istype(W, /obj/item/implanter)) - var/obj/item/implanter/I = W - if(I.imp) - if(imp || I.imp.imp_in) - return - I.imp.forceMove(src) - imp = I.imp - I.imp = null - update_icon() - reagents = imp.reagents - I.update_icon() - else - if(imp) - if(I.imp) - return - imp.forceMove(I) - I.imp = imp - imp = null - reagents = null - update_icon() - I.update_icon() - - else - return ..() - -/obj/item/implantcase/Initialize(mapload) - . = ..() - if(imp_type) - imp = new imp_type(src) - update_icon() - reagents = imp.reagents - - -/obj/item/implantcase/tracking - name = "implant case - 'Tracking'" - desc = "A glass case containing a tracking implant." - imp_type = /obj/item/implant/tracking - -/obj/item/implantcase/weapons_auth - name = "implant case - 'Firearms Authentication'" - desc = "A glass case containing a firearms authentication implant." - imp_type = /obj/item/implant/weapons_auth - -/obj/item/implantcase/adrenaline - name = "implant case - 'Adrenaline'" - desc = "A glass case containing an adrenaline implant." - imp_type = /obj/item/implant/adrenalin +/obj/item/implantcase + name = "implant case" + desc = "A glass case containing an implant." + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "implantcase-0" + item_state = "implantcase" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + throw_speed = 2 + throw_range = 5 + w_class = WEIGHT_CLASS_TINY + custom_materials = list(/datum/material/glass=500) + var/obj/item/implant/imp = null + var/imp_type + + +/obj/item/implantcase/update_icon_state() + if(imp) + icon_state = "implantcase-[imp.implant_color]" + else + icon_state = "implantcase-0" + + +/obj/item/implantcase/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/pen)) + if(!user.is_literate()) + to_chat(user, "You scribble illegibly on the side of [src]!") + return + var/t = stripped_input(user, "What would you like the label to be?", name, null) + if(user.get_active_held_item() != W) + return + if(!user.canUseTopic(src, BE_CLOSE)) + return + if(t) + name = "implant case - '[t]'" + else + name = "implant case" + else if(istype(W, /obj/item/implanter)) + var/obj/item/implanter/I = W + if(I.imp) + if(imp || I.imp.imp_in) + return + I.imp.forceMove(src) + imp = I.imp + I.imp = null + update_icon() + reagents = imp.reagents + I.update_icon() + else + if(imp) + if(I.imp) + return + imp.forceMove(I) + I.imp = imp + imp = null + reagents = null + update_icon() + I.update_icon() + + else + return ..() + +/obj/item/implantcase/Initialize(mapload) + . = ..() + if(imp_type) + imp = new imp_type(src) + update_icon() + reagents = imp.reagents + + +/obj/item/implantcase/tracking + name = "implant case - 'Tracking'" + desc = "A glass case containing a tracking implant." + imp_type = /obj/item/implant/tracking + +/obj/item/implantcase/weapons_auth + name = "implant case - 'Firearms Authentication'" + desc = "A glass case containing a firearms authentication implant." + imp_type = /obj/item/implant/weapons_auth + +/obj/item/implantcase/adrenaline + name = "implant case - 'Adrenaline'" + desc = "A glass case containing an adrenaline implant." + imp_type = /obj/item/implant/adrenalin diff --git a/code/game/objects/items/implants/implantchair.dm b/code/game/objects/items/implants/implantchair.dm index 69d7bb2b1bb2..50707e4e93d1 100644 --- a/code/game/objects/items/implants/implantchair.dm +++ b/code/game/objects/items/implants/implantchair.dm @@ -1,200 +1,199 @@ -/obj/machinery/implantchair - name = "mindshield implanter" - desc = "Used to implant occupants with mindshield implants." - icon = 'icons/obj/machines/implantchair.dmi' - icon_state = "implantchair" - density = TRUE - opacity = 0 - ui_x = 375 - ui_y = 280 - - var/ready = TRUE - var/replenishing = FALSE - - var/ready_implants = 5 - var/max_implants = 5 - var/injection_cooldown = 600 - var/replenish_cooldown = 6000 - var/implant_type = /obj/item/implant/mindshield - var/auto_inject = FALSE - var/auto_replenish = TRUE - var/special = FALSE - var/special_name = "special function" - var/message_cooldown - var/breakout_time = 600 - -/obj/machinery/implantchair/Initialize() - . = ..() - open_machine() - update_icon() - - -/obj/machinery/implantchair/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.notcontained_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "implantchair", name, ui_x, ui_y, master_ui, state) - ui.open() - - -/obj/machinery/implantchair/ui_data() - var/list/data = list() - data["occupied"] = occupant ? 1 : 0 - data["open"] = state_open - - data["occupant"] = list() - if(occupant) - var/mob/living/mob_occupant = occupant - data["occupant"]["name"] = mob_occupant.name - data["occupant"]["stat"] = mob_occupant.stat - - data["special_name"] = special ? special_name : null - data["ready_implants"] = ready_implants - data["ready"] = ready - data["replenishing"] = replenishing - - return data - -/obj/machinery/implantchair/ui_act(action, params) - if(..()) - return - switch(action) - if("door") - if(state_open) - close_machine() - else - open_machine() - . = TRUE - if("implant") - implant(occupant,usr) - . = TRUE - -/obj/machinery/implantchair/proc/implant(mob/living/M,mob/user) - if (!istype(M)) - return - if(!ready_implants || !ready) - return - if(implant_action(M,user)) - ready_implants-- - if(!replenishing && auto_replenish) - replenishing = TRUE - addtimer(CALLBACK(src,.proc/replenish),replenish_cooldown) - if(injection_cooldown > 0) - ready = FALSE - addtimer(CALLBACK(src,.proc/set_ready),injection_cooldown) - else - playsound(get_turf(src), 'sound/machines/buzz-sigh.ogg', 25, TRUE) - update_icon() - -/obj/machinery/implantchair/proc/implant_action(mob/living/M) - var/obj/item/I = new implant_type - if(istype(I, /obj/item/implant)) - var/obj/item/implant/P = I - if(P.implant(M)) - visible_message("[M] is implanted by [src].") - return TRUE - else if(istype(I, /obj/item/organ)) - var/obj/item/organ/P = I - P.Insert(M, FALSE, FALSE) - visible_message("[M] is implanted by [src].") - return TRUE - -/obj/machinery/implantchair/update_icon_state() - icon_state = initial(icon_state) - if(state_open) - icon_state += "_open" - if(occupant) - icon_state += "_occupied" - -/obj/machinery/implantchair/update_overlays() - . = ..() - if(ready) - . += "ready" - -/obj/machinery/implantchair/proc/replenish() - if(ready_implants < max_implants) - ready_implants++ - if(ready_implants < max_implants) - addtimer(CALLBACK(src,"replenish"),replenish_cooldown) - else - replenishing = FALSE - -/obj/machinery/implantchair/proc/set_ready() - ready = TRUE - update_icon() - -/obj/machinery/implantchair/container_resist(mob/living/user) - user.changeNext_move(CLICK_CD_BREAKOUT) - user.last_special = world.time + CLICK_CD_BREAKOUT - user.visible_message("You see [user] kicking against the door of [src]!", \ - "You lean on the back of [src] and start pushing the door open... (this will take about [DisplayTimeText(breakout_time)].)", \ - "You hear a metallic creaking from [src].") - if(do_after(user,(breakout_time), target = src)) - if(!user || user.stat != CONSCIOUS || user.loc != src || state_open) - return - user.visible_message("[user] successfully broke out of [src]!", \ - "You successfully break out of [src]!") - open_machine() - -/obj/machinery/implantchair/relaymove(mob/user) - if(message_cooldown <= world.time) - message_cooldown = world.time + 50 - to_chat(user, "[src]'s door won't budge!") - -/obj/machinery/implantchair/MouseDrop_T(mob/target, mob/user) - if(user.stat || !Adjacent(user) || !user.Adjacent(target) || !isliving(target) || !user.IsAdvancedToolUser()) - return - if(isliving(user)) - var/mob/living/L = user - if(!(L.mobility_flags & MOBILITY_STAND)) - return - close_machine(target) - -/obj/machinery/implantchair/close_machine(mob/living/user) - if((isnull(user) || istype(user)) && state_open) - ..(user) - if(auto_inject && ready && ready_implants > 0) - implant(user,null) - -/obj/machinery/implantchair/genepurge - name = "Genetic purifier" - desc = "Used to purge a human genome of foreign influences." - special = TRUE - special_name = "Purge genome" - injection_cooldown = 0 - replenish_cooldown = 300 - -/obj/machinery/implantchair/genepurge/implant_action(mob/living/carbon/human/H,mob/user) - if(!istype(H)) - return 0 - H.set_species(/datum/species/human, 1)//lizards go home - purrbation_remove(H)//remove cats - H.dna.remove_all_mutations()//hulks out - return 1 - - -/obj/machinery/implantchair/brainwash - name = "Neural Imprinter" - desc = "Used to indoctrinate rehabilitate hardened recidivists." - special_name = "Imprint" - injection_cooldown = 3000 - auto_inject = FALSE - auto_replenish = FALSE - special = TRUE - var/objective = "Obey the law. Praise Nanotrasen." - var/custom = FALSE - -/obj/machinery/implantchair/brainwash/implant_action(mob/living/C,mob/user) - if(!istype(C) || !C.mind) // I don't know how this makes any sense for silicons but laws trump objectives anyway. - return FALSE - if(custom) - if(!user || !user.Adjacent(src)) - return FALSE - objective = stripped_input(usr,"What order do you want to imprint on [C]?","Enter the order","",120) - message_admins("[ADMIN_LOOKUPFLW(user)] set brainwash machine objective to '[objective]'.") - log_game("[key_name(user)] set brainwash machine objective to '[objective]'.") - if(HAS_TRAIT(C, TRAIT_MINDSHIELD)) - return FALSE - brainwash(C, objective) - message_admins("[ADMIN_LOOKUPFLW(user)] brainwashed [key_name_admin(C)] with objective '[objective]'.") - log_game("[key_name(user)] brainwashed [key_name(C)] with objective '[objective]'.") - return TRUE +/obj/machinery/implantchair + name = "mindshield implanter" + desc = "Used to implant occupants with mindshield implants." + icon = 'icons/obj/machines/implantchair.dmi' + icon_state = "implantchair" + density = TRUE + opacity = 0 + + var/ready = TRUE + var/replenishing = FALSE + + var/ready_implants = 5 + var/max_implants = 5 + var/injection_cooldown = 600 + var/replenish_cooldown = 6000 + var/implant_type = /obj/item/implant/mindshield + var/auto_inject = FALSE + var/auto_replenish = TRUE + var/special = FALSE + var/special_name = "special function" + var/message_cooldown + var/breakout_time = 600 + +/obj/machinery/implantchair/Initialize() + . = ..() + open_machine() + update_icon() + +/obj/machinery/implantchair/ui_state(mob/user) + return GLOB.notcontained_state + +/obj/machinery/implantchair/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "ImplantChair", name) + ui.open() + +/obj/machinery/implantchair/ui_data() + var/list/data = list() + data["occupied"] = occupant ? 1 : 0 + data["open"] = state_open + + data["occupant"] = list() + if(occupant) + var/mob/living/mob_occupant = occupant + data["occupant"]["name"] = mob_occupant.name + data["occupant"]["stat"] = mob_occupant.stat + + data["special_name"] = special ? special_name : null + data["ready_implants"] = ready_implants + data["ready"] = ready + data["replenishing"] = replenishing + + return data + +/obj/machinery/implantchair/ui_act(action, params) + if(..()) + return + switch(action) + if("door") + if(state_open) + close_machine() + else + open_machine() + . = TRUE + if("implant") + implant(occupant,usr) + . = TRUE + +/obj/machinery/implantchair/proc/implant(mob/living/M,mob/user) + if (!istype(M)) + return + if(!ready_implants || !ready) + return + if(implant_action(M,user)) + ready_implants-- + if(!replenishing && auto_replenish) + replenishing = TRUE + addtimer(CALLBACK(src,.proc/replenish),replenish_cooldown) + if(injection_cooldown > 0) + ready = FALSE + addtimer(CALLBACK(src,.proc/set_ready),injection_cooldown) + else + playsound(get_turf(src), 'sound/machines/buzz-sigh.ogg', 25, TRUE) + update_icon() + +/obj/machinery/implantchair/proc/implant_action(mob/living/M) + var/obj/item/I = new implant_type + if(istype(I, /obj/item/implant)) + var/obj/item/implant/P = I + if(P.implant(M)) + visible_message("[M] is implanted by [src].") + return TRUE + else if(istype(I, /obj/item/organ)) + var/obj/item/organ/P = I + P.Insert(M, FALSE, FALSE) + visible_message("[M] is implanted by [src].") + return TRUE + +/obj/machinery/implantchair/update_icon_state() + icon_state = initial(icon_state) + if(state_open) + icon_state += "_open" + if(occupant) + icon_state += "_occupied" + +/obj/machinery/implantchair/update_overlays() + . = ..() + if(ready) + . += "ready" + +/obj/machinery/implantchair/proc/replenish() + if(ready_implants < max_implants) + ready_implants++ + if(ready_implants < max_implants) + addtimer(CALLBACK(src,"replenish"),replenish_cooldown) + else + replenishing = FALSE + +/obj/machinery/implantchair/proc/set_ready() + ready = TRUE + update_icon() + +/obj/machinery/implantchair/container_resist(mob/living/user) + user.changeNext_move(CLICK_CD_BREAKOUT) + user.last_special = world.time + CLICK_CD_BREAKOUT + user.visible_message("You see [user] kicking against the door of [src]!", \ + "You lean on the back of [src] and start pushing the door open... (this will take about [DisplayTimeText(breakout_time)].)", \ + "You hear a metallic creaking from [src].") + if(do_after(user,(breakout_time), target = src)) + if(!user || user.stat != CONSCIOUS || user.loc != src || state_open) + return + user.visible_message("[user] successfully broke out of [src]!", \ + "You successfully break out of [src]!") + open_machine() + +/obj/machinery/implantchair/relaymove(mob/user) + if(message_cooldown <= world.time) + message_cooldown = world.time + 50 + to_chat(user, "[src]'s door won't budge!") + +/obj/machinery/implantchair/MouseDrop_T(mob/target, mob/user) + if(user.stat || !Adjacent(user) || !user.Adjacent(target) || !isliving(target) || !user.IsAdvancedToolUser()) + return + if(isliving(user)) + var/mob/living/L = user + if(!(L.mobility_flags & MOBILITY_STAND)) + return + close_machine(target) + +/obj/machinery/implantchair/close_machine(mob/living/user) + if((isnull(user) || istype(user)) && state_open) + ..(user) + if(auto_inject && ready && ready_implants > 0) + implant(user,null) + +/obj/machinery/implantchair/genepurge + name = "Genetic purifier" + desc = "Used to purge a human genome of foreign influences." + special = TRUE + special_name = "Purge genome" + injection_cooldown = 0 + replenish_cooldown = 300 + +/obj/machinery/implantchair/genepurge/implant_action(mob/living/carbon/human/H,mob/user) + if(!istype(H)) + return 0 + H.set_species(/datum/species/human, 1)//lizards go home + purrbation_remove(H)//remove cats + H.dna.remove_all_mutations()//hulks out + return 1 + + +/obj/machinery/implantchair/brainwash + name = "Neural Imprinter" + desc = "Used to indoctrinate rehabilitate hardened recidivists." + special_name = "Imprint" + injection_cooldown = 3000 + auto_inject = FALSE + auto_replenish = FALSE + special = TRUE + var/objective = "Obey the law. Praise Nanotrasen." + var/custom = FALSE + +/obj/machinery/implantchair/brainwash/implant_action(mob/living/C,mob/user) + if(!istype(C) || !C.mind) // I don't know how this makes any sense for silicons but laws trump objectives anyway. + return FALSE + if(custom) + if(!user || !user.Adjacent(src)) + return FALSE + objective = stripped_input(usr,"What order do you want to imprint on [C]?","Enter the order","",120) + message_admins("[ADMIN_LOOKUPFLW(user)] set brainwash machine objective to '[objective]'.") + log_game("[key_name(user)] set brainwash machine objective to '[objective]'.") + if(HAS_TRAIT(C, TRAIT_MINDSHIELD)) + return FALSE + brainwash(C, objective) + message_admins("[ADMIN_LOOKUPFLW(user)] brainwashed [key_name_admin(C)] with objective '[objective]'.") + log_game("[key_name(user)] brainwashed [key_name(C)] with objective '[objective]'.") + return TRUE diff --git a/code/game/objects/items/implants/implanter.dm b/code/game/objects/items/implants/implanter.dm index ffa2ddf349a5..6731673d1c16 100644 --- a/code/game/objects/items/implants/implanter.dm +++ b/code/game/objects/items/implants/implanter.dm @@ -1,65 +1,65 @@ -/obj/item/implanter - name = "implanter" - desc = "A sterile automatic implant injector." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "implanter0" - item_state = "syringe_0" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - throw_speed = 3 - throw_range = 5 - w_class = WEIGHT_CLASS_SMALL - custom_materials = list(/datum/material/iron=600, /datum/material/glass=200) - var/obj/item/implant/imp = null - var/imp_type = null - - -/obj/item/implanter/update_icon_state() - if(imp) - icon_state = "implanter1" - else - icon_state = "implanter0" - - -/obj/item/implanter/attack(mob/living/M, mob/user) - if(!istype(M)) - return - if(user && imp) - if(M != user) - M.visible_message("[user] is attempting to implant [M].") - - var/turf/T = get_turf(M) - if(T && (M == user || do_mob(user, M, 50))) - if(src && imp) - if(imp.implant(M, user)) - if (M == user) - to_chat(user, "You implant yourself.") - else - M.visible_message("[user] implants [M].", "[user] implants you.") - imp = null - update_icon() - else - to_chat(user, "[src] fails to implant [M].") - -/obj/item/implanter/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/pen)) - if(!user.is_literate()) - to_chat(user, "You prod at [src] with [W]!") - return - var/t = stripped_input(user, "What would you like the label to be?", name, null) - if(user.get_active_held_item() != W) - return - if(!user.canUseTopic(src, BE_CLOSE)) - return - if(t) - name = "implanter ([t])" - else - name = "implanter" - else - return ..() - -/obj/item/implanter/Initialize(mapload) - . = ..() - if(imp_type) - imp = new imp_type(src) - update_icon() +/obj/item/implanter + name = "implanter" + desc = "A sterile automatic implant injector." + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "implanter0" + item_state = "syringe_0" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + throw_speed = 3 + throw_range = 5 + w_class = WEIGHT_CLASS_SMALL + custom_materials = list(/datum/material/iron=600, /datum/material/glass=200) + var/obj/item/implant/imp = null + var/imp_type = null + + +/obj/item/implanter/update_icon_state() + if(imp) + icon_state = "implanter1" + else + icon_state = "implanter0" + + +/obj/item/implanter/attack(mob/living/M, mob/user) + if(!istype(M)) + return + if(user && imp) + if(M != user) + M.visible_message("[user] is attempting to implant [M].") + + var/turf/T = get_turf(M) + if(T && (M == user || do_mob(user, M, 50))) + if(src && imp) + if(imp.implant(M, user)) + if (M == user) + to_chat(user, "You implant yourself.") + else + M.visible_message("[user] implants [M].", "[user] implants you.") + imp = null + update_icon() + else + to_chat(user, "[src] fails to implant [M].") + +/obj/item/implanter/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/pen)) + if(!user.is_literate()) + to_chat(user, "You prod at [src] with [W]!") + return + var/t = stripped_input(user, "What would you like the label to be?", name, null) + if(user.get_active_held_item() != W) + return + if(!user.canUseTopic(src, BE_CLOSE)) + return + if(t) + name = "implanter ([t])" + else + name = "implanter" + else + return ..() + +/obj/item/implanter/Initialize(mapload) + . = ..() + if(imp_type) + imp = new imp_type(src) + update_icon() diff --git a/code/game/objects/items/implants/implantpad.dm b/code/game/objects/items/implants/implantpad.dm index 6973aa4beffc..13d1a5302726 100644 --- a/code/game/objects/items/implants/implantpad.dm +++ b/code/game/objects/items/implants/implantpad.dm @@ -1,78 +1,78 @@ -/obj/item/implantpad - name = "implant pad" - desc = "Used to modify implants." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "implantpad-0" - item_state = "electronic" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - throw_speed = 3 - throw_range = 5 - w_class = WEIGHT_CLASS_SMALL - var/obj/item/implantcase/case = null - -/obj/item/implantpad/update_icon_state() - icon_state = "implantpad-[!QDELETED(case)]" - -/obj/item/implantpad/examine(mob/user) - . = ..() - if(Adjacent(user)) - . += "It [case ? "contains \a [case]" : "is currently empty"]." - if(case) - . += "Alt-click to remove [case]." - else - if(case) - . += "There seems to be something inside it, but you can't quite tell what from here..." - -/obj/item/implantpad/handle_atom_del(atom/A) - if(A == case) - case = null - update_icon() - updateSelfDialog() - . = ..() - -/obj/item/implantpad/AltClick(mob/user) - ..() - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - if(!case) - to_chat(user, "There's no implant to remove from [src].") - return - - user.put_in_hands(case) - - add_fingerprint(user) - case.add_fingerprint(user) - case = null - - updateSelfDialog() - update_icon() - -/obj/item/implantpad/attackby(obj/item/implantcase/C, mob/user, params) - if(istype(C, /obj/item/implantcase) && !case) - if(!user.transferItemToLoc(C, src)) - return - case = C - updateSelfDialog() - update_icon() - else - return ..() - -/obj/item/implantpad/ui_interact(mob/user) - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - user.unset_machine(src) - user << browse(null, "window=implantpad") - return - - user.set_machine(src) - var/dat = "Implant Mini-Computer:
                        " - if(case) - if(case.imp) - if(istype(case.imp, /obj/item/implant)) - dat += case.imp.get_data() - else - dat += "The implant casing is empty." - else - dat += "Please insert an implant casing!" - user << browse(dat, "window=implantpad") - onclose(user, "implantpad") +/obj/item/implantpad + name = "implant pad" + desc = "Used to modify implants." + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "implantpad-0" + item_state = "electronic" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + throw_speed = 3 + throw_range = 5 + w_class = WEIGHT_CLASS_SMALL + var/obj/item/implantcase/case = null + +/obj/item/implantpad/update_icon_state() + icon_state = "implantpad-[!QDELETED(case)]" + +/obj/item/implantpad/examine(mob/user) + . = ..() + if(Adjacent(user)) + . += "It [case ? "contains \a [case]" : "is currently empty"]." + if(case) + . += "Alt-click to remove [case]." + else + if(case) + . += "There seems to be something inside it, but you can't quite tell what from here..." + +/obj/item/implantpad/handle_atom_del(atom/A) + if(A == case) + case = null + update_icon() + updateSelfDialog() + . = ..() + +/obj/item/implantpad/AltClick(mob/user) + ..() + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + if(!case) + to_chat(user, "There's no implant to remove from [src].") + return + + user.put_in_hands(case) + + add_fingerprint(user) + case.add_fingerprint(user) + case = null + + updateSelfDialog() + update_icon() + +/obj/item/implantpad/attackby(obj/item/implantcase/C, mob/user, params) + if(istype(C, /obj/item/implantcase) && !case) + if(!user.transferItemToLoc(C, src)) + return + case = C + updateSelfDialog() + update_icon() + else + return ..() + +/obj/item/implantpad/ui_interact(mob/user) + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + user.unset_machine(src) + user << browse(null, "window=implantpad") + return + + user.set_machine(src) + var/dat = "Implant Mini-Computer:
                        " + if(case) + if(case.imp) + if(istype(case.imp, /obj/item/implant)) + dat += case.imp.get_data() + else + dat += "The implant casing is empty." + else + dat += "Please insert an implant casing!" + user << browse(dat, "window=implantpad") + onclose(user, "implantpad") diff --git a/code/game/objects/items/implants/implantuplink.dm b/code/game/objects/items/implants/implantuplink.dm index 9000fbbe3433..0cac8f838acb 100644 --- a/code/game/objects/items/implants/implantuplink.dm +++ b/code/game/objects/items/implants/implantuplink.dm @@ -1,23 +1,23 @@ -/obj/item/implant/uplink - name = "uplink implant" - desc = "Sneeki breeki." - icon = 'icons/obj/radio.dmi' - icon_state = "radio" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - var/starting_tc = 0 - -/obj/item/implant/uplink/Initialize(mapload, _owner) - . = ..() - AddComponent(/datum/component/uplink, _owner, TRUE, FALSE, null, starting_tc) - -/obj/item/implanter/uplink - name = "implanter (uplink)" - imp_type = /obj/item/implant/uplink - -/obj/item/implanter/uplink/precharged - name = "implanter (precharged uplink)" - imp_type = /obj/item/implant/uplink/precharged - -/obj/item/implant/uplink/precharged - starting_tc = 10 +/obj/item/implant/uplink + name = "uplink implant" + desc = "Sneeki breeki." + icon = 'icons/obj/radio.dmi' + icon_state = "radio" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + var/starting_tc = 0 + +/obj/item/implant/uplink/Initialize(mapload, _owner) + . = ..() + AddComponent(/datum/component/uplink, _owner, TRUE, FALSE, null, starting_tc) + +/obj/item/implanter/uplink + name = "implanter (uplink)" + imp_type = /obj/item/implant/uplink + +/obj/item/implanter/uplink/precharged + name = "implanter (precharged uplink)" + imp_type = /obj/item/implant/uplink/precharged + +/obj/item/implant/uplink/precharged + starting_tc = 10 diff --git a/code/game/objects/items/kitchen.dm b/code/game/objects/items/kitchen.dm index e679ed5636b4..64a580cd1acf 100644 --- a/code/game/objects/items/kitchen.dm +++ b/code/game/objects/items/kitchen.dm @@ -1,254 +1,254 @@ -/* Kitchen tools - * Contains: - * Fork - * Kitchen knives - * Ritual Knife - * Butcher's cleaver - * Combat Knife - * Rolling Pins - * Plastic Utensils - */ - -/obj/item/kitchen - icon = 'icons/obj/kitchen.dmi' - lefthand_file = 'icons/mob/inhands/equipment/kitchen_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/kitchen_righthand.dmi' - -/obj/item/kitchen/fork - name = "fork" - desc = "Pointy." - icon_state = "fork" - force = 5 - w_class = WEIGHT_CLASS_TINY - throwforce = 0 - throw_speed = 3 - throw_range = 5 - custom_materials = list(/datum/material/iron=80) - flags_1 = CONDUCT_1 - attack_verb = list("attacked", "stabbed", "poked") - hitsound = 'sound/weapons/bladeslice.ogg' - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30) - item_flags = EYE_STAB - var/datum/reagent/forkload //used to eat omelette - -/obj/item/kitchen/fork/suicide_act(mob/living/carbon/user) - user.visible_message("[user] stabs \the [src] into [user.p_their()] chest! It looks like [user.p_theyre()] trying to take a bite out of [user.p_them()]self!") - playsound(src, 'sound/items/eatfood.ogg', 50, TRUE) - return BRUTELOSS - -/obj/item/kitchen/fork/attack(mob/living/carbon/M, mob/living/carbon/user) - if(!istype(M)) - return ..() - - if(forkload) - if(M == user) - M.visible_message("[user] eats a delicious forkful of omelette!") - M.reagents.add_reagent(forkload.type, 1) - else - M.visible_message("[user] feeds [M] a delicious forkful of omelette!") - M.reagents.add_reagent(forkload.type, 1) - icon_state = "fork" - forkload = null - else - return ..() - -/obj/item/kitchen/fork/plastic - name = "plastic fork" - desc = "Really takes you back to highschool lunch." - icon_state = "plastic_fork" - force = 0 - w_class = WEIGHT_CLASS_TINY - throwforce = 0 - custom_materials = list(/datum/material/plastic=80) - custom_price = 50 - var/break_chance = 25 - -/obj/item/kitchen/fork/plastic/afterattack(mob/living/carbon/user) - .=..() - if(prob(break_chance)) - user.visible_message("[user]'s fork snaps into tiny pieces in their hand.") - qdel(src) - -/obj/item/kitchen/knife - name = "kitchen knife" - icon_state = "knife" - item_state = "knife" - desc = "A general purpose Chef's Knife made by SpaceCook Incorporated. Guaranteed to stay sharp for years to come." - flags_1 = CONDUCT_1 - force = 10 - w_class = WEIGHT_CLASS_SMALL - throwforce = 10 - hitsound = 'sound/weapons/bladeslice.ogg' - throw_speed = 3 - throw_range = 6 - custom_materials = list(/datum/material/iron=12000) - attack_verb = list("slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") - sharpness = IS_SHARP_ACCURATE - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - item_flags = EYE_STAB - var/bayonet = FALSE //Can this be attached to a gun? - custom_price = 250 - -/obj/item/kitchen/knife/ComponentInitialize() - . = ..() - set_butchering() - -///Adds the butchering component, used to override stats for special cases -/obj/item/kitchen/knife/proc/set_butchering() - AddComponent(/datum/component/butchering, 80 - force, 100, force - 10) //bonus chance increases depending on force - -/obj/item/kitchen/knife/suicide_act(mob/user) - user.visible_message(pick("[user] is slitting [user.p_their()] wrists with the [src.name]! It looks like [user.p_theyre()] trying to commit suicide.", \ - "[user] is slitting [user.p_their()] throat with the [src.name]! It looks like [user.p_theyre()] trying to commit suicide.", \ - "[user] is slitting [user.p_their()] stomach open with the [src.name]! It looks like [user.p_theyre()] trying to commit seppuku.")) - return (BRUTELOSS) - -/obj/item/kitchen/knife/plastic - name = "plastic knife" - icon_state = "plastic_knife" - item_state = "knife" - desc = "A very safe, barely sharp knife made of plastic. Good for cutting food and not much else." - force = 0 - w_class = WEIGHT_CLASS_TINY - throwforce = 0 - throw_range = 5 - custom_materials = list(/datum/material/plastic = 100) - attack_verb = list("prodded", "whiffed","scratched", "poked") - sharpness = IS_SHARP - custom_price = 50 - var/break_chance = 25 - -/obj/item/kitchen/knife/plastic/afterattack(mob/living/carbon/user) - .=..() - if(prob(break_chance)) - user.visible_message("[user]'s knife snaps into tiny pieces in their hand.") - qdel(src) - -/obj/item/kitchen/knife/ritual - name = "ritual knife" - desc = "The unearthly energies that once powered this blade are now dormant." - icon = 'icons/obj/wizard.dmi' - icon_state = "render" - lefthand_file = 'icons/mob/inhands/equipment/kitchen_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/kitchen_righthand.dmi' - w_class = WEIGHT_CLASS_NORMAL - -/obj/item/kitchen/knife/butcher - name = "butcher's cleaver" - icon_state = "butch" - item_state = "butch" - desc = "A huge thing used for chopping and chopping up meat. This includes clowns and clown by-products." - flags_1 = CONDUCT_1 - force = 15 - throwforce = 10 - custom_materials = list(/datum/material/iron=18000) - attack_verb = list("cleaved", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") - w_class = WEIGHT_CLASS_NORMAL - custom_price = 600 - -/obj/item/kitchen/knife/hunting - name = "hunting knife" - desc = "Despite its name, it's mainly used for cutting meat from dead prey rather than actual hunting." - item_state = "huntingknife" - icon_state = "huntingknife" - -/obj/item/kitchen/knife/hunting/set_butchering() - AddComponent(/datum/component/butchering, 80 - force, 100, force + 10) - -/obj/item/kitchen/knife/combat - name = "combat knife" - icon_state = "buckknife" - desc = "A military combat utility survival knife." - embedding = list("pain_mult" = 4, "embed_chance" = 65, "fall_chance" = 10, "ignore_throwspeed_threshold" = TRUE) - force = 20 - throwforce = 20 - attack_verb = list("slashed", "stabbed", "sliced", "torn", "ripped", "cut") - bayonet = TRUE - -/obj/item/kitchen/knife/combat/survival - name = "survival knife" - icon_state = "survivalknife" - embedding = list("pain_mult" = 4, "embed_chance" = 35, "fall_chance" = 10) - desc = "A hunting grade survival knife." - force = 15 - throwforce = 15 - bayonet = TRUE - -/obj/item/kitchen/knife/combat/bone - name = "bone dagger" - item_state = "bone_dagger" - icon_state = "bone_dagger" - lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' - desc = "A sharpened bone. The bare minimum in survival." - embedding = list("pain_mult" = 4, "embed_chance" = 35, "fall_chance" = 10) - force = 15 - throwforce = 15 - custom_materials = null - -/obj/item/kitchen/knife/combat/cyborg - name = "cyborg knife" - icon = 'icons/obj/items_cyborg.dmi' - icon_state = "knife_cyborg" - desc = "A cyborg-mounted plasteel knife. Extremely sharp and durable." - -/obj/item/kitchen/knife/shiv - name = "glass shiv" - icon = 'icons/obj/shards.dmi' - icon_state = "shiv" - item_state = "shiv" - lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' - desc = "A makeshift glass shiv." - force = 8 - throwforce = 12 - attack_verb = list("shanked", "shivved") - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - custom_materials = list(/datum/material/glass=400) - -/obj/item/kitchen/knife/shiv/carrot - name = "carrot shiv" - icon_state = "carrotshiv" - item_state = "carrotshiv" - icon = 'icons/obj/kitchen.dmi' - desc = "Unlike other carrots, you should probably keep this far away from your eyes." - custom_materials = null - -/obj/item/kitchen/knife/shiv/carrot/suicide_act(mob/living/carbon/user) - user.visible_message("[user] forcefully drives \the [src] into [user.p_their()] eye! It looks like [user.p_theyre()] trying to commit suicide!") - return BRUTELOSS - -/obj/item/kitchen/rollingpin - name = "rolling pin" - desc = "Used to knock out the Bartender." - icon_state = "rolling_pin" - force = 8 - throwforce = 5 - throw_speed = 3 - throw_range = 7 - custom_materials = list(/datum/material/wood = MINERAL_MATERIAL_AMOUNT * 1.5) - w_class = WEIGHT_CLASS_NORMAL - attack_verb = list("bashed", "battered", "bludgeoned", "thrashed", "whacked") - custom_price = 200 - -/obj/item/kitchen/rollingpin/suicide_act(mob/living/carbon/user) - user.visible_message("[user] begins flattening [user.p_their()] head with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return BRUTELOSS -/* Trays moved to /obj/item/storage/bag */ - -/obj/item/kitchen/spoon/plastic - name = "plastic spoon" - desc = "Just be careful your food doesn't melt the spoon first." - icon_state = "plastic_spoon" - force = 0 - w_class = WEIGHT_CLASS_TINY - throwforce = 0 - custom_materials = list(/datum/material/plastic=120) - custom_price = 50 - var/break_chance = 25 - -/obj/item/kitchen/knife/plastic/afterattack(mob/living/carbon/user) - .=..() - if(prob(break_chance)) - user.visible_message("[user]'s spoon snaps into tiny pieces in their hand.") - qdel(src) +/* Kitchen tools + * Contains: + * Fork + * Kitchen knives + * Ritual Knife + * Butcher's cleaver + * Combat Knife + * Rolling Pins + * Plastic Utensils + */ + +/obj/item/kitchen + icon = 'icons/obj/kitchen.dmi' + lefthand_file = 'icons/mob/inhands/equipment/kitchen_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/kitchen_righthand.dmi' + +/obj/item/kitchen/fork + name = "fork" + desc = "Pointy." + icon_state = "fork" + force = 5 + w_class = WEIGHT_CLASS_TINY + throwforce = 0 + throw_speed = 3 + throw_range = 5 + custom_materials = list(/datum/material/iron=80) + flags_1 = CONDUCT_1 + attack_verb = list("attacked", "stabbed", "poked") + hitsound = 'sound/weapons/bladeslice.ogg' + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30) + item_flags = EYE_STAB + var/datum/reagent/forkload //used to eat omelette + +/obj/item/kitchen/fork/suicide_act(mob/living/carbon/user) + user.visible_message("[user] stabs \the [src] into [user.p_their()] chest! It looks like [user.p_theyre()] trying to take a bite out of [user.p_them()]self!") + playsound(src, 'sound/items/eatfood.ogg', 50, TRUE) + return BRUTELOSS + +/obj/item/kitchen/fork/attack(mob/living/carbon/M, mob/living/carbon/user) + if(!istype(M)) + return ..() + + if(forkload) + if(M == user) + M.visible_message("[user] eats a delicious forkful of omelette!") + M.reagents.add_reagent(forkload.type, 1) + else + M.visible_message("[user] feeds [M] a delicious forkful of omelette!") + M.reagents.add_reagent(forkload.type, 1) + icon_state = "fork" + forkload = null + else + return ..() + +/obj/item/kitchen/fork/plastic + name = "plastic fork" + desc = "Really takes you back to highschool lunch." + icon_state = "plastic_fork" + force = 0 + w_class = WEIGHT_CLASS_TINY + throwforce = 0 + custom_materials = list(/datum/material/plastic=80) + custom_price = 50 + var/break_chance = 25 + +/obj/item/kitchen/fork/plastic/afterattack(mob/living/carbon/user) + .=..() + if(prob(break_chance)) + user.visible_message("[user]'s fork snaps into tiny pieces in their hand.") + qdel(src) + +/obj/item/kitchen/knife + name = "kitchen knife" + icon_state = "knife" + item_state = "knife" + desc = "A general purpose Chef's Knife made by SpaceCook Incorporated. Guaranteed to stay sharp for years to come." + flags_1 = CONDUCT_1 + force = 10 + w_class = WEIGHT_CLASS_SMALL + throwforce = 10 + hitsound = 'sound/weapons/bladeslice.ogg' + throw_speed = 3 + throw_range = 6 + custom_materials = list(/datum/material/iron=12000) + attack_verb = list("slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") + sharpness = IS_SHARP_ACCURATE + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + item_flags = EYE_STAB + var/bayonet = FALSE //Can this be attached to a gun? + custom_price = 250 + +/obj/item/kitchen/knife/ComponentInitialize() + . = ..() + set_butchering() + +///Adds the butchering component, used to override stats for special cases +/obj/item/kitchen/knife/proc/set_butchering() + AddComponent(/datum/component/butchering, 80 - force, 100, force - 10) //bonus chance increases depending on force + +/obj/item/kitchen/knife/suicide_act(mob/user) + user.visible_message(pick("[user] is slitting [user.p_their()] wrists with the [src.name]! It looks like [user.p_theyre()] trying to commit suicide.", \ + "[user] is slitting [user.p_their()] throat with the [src.name]! It looks like [user.p_theyre()] trying to commit suicide.", \ + "[user] is slitting [user.p_their()] stomach open with the [src.name]! It looks like [user.p_theyre()] trying to commit seppuku.")) + return (BRUTELOSS) + +/obj/item/kitchen/knife/plastic + name = "plastic knife" + icon_state = "plastic_knife" + item_state = "knife" + desc = "A very safe, barely sharp knife made of plastic. Good for cutting food and not much else." + force = 0 + w_class = WEIGHT_CLASS_TINY + throwforce = 0 + throw_range = 5 + custom_materials = list(/datum/material/plastic = 100) + attack_verb = list("prodded", "whiffed","scratched", "poked") + sharpness = IS_SHARP + custom_price = 50 + var/break_chance = 25 + +/obj/item/kitchen/knife/plastic/afterattack(mob/living/carbon/user) + .=..() + if(prob(break_chance)) + user.visible_message("[user]'s knife snaps into tiny pieces in their hand.") + qdel(src) + +/obj/item/kitchen/knife/ritual + name = "ritual knife" + desc = "The unearthly energies that once powered this blade are now dormant." + icon = 'icons/obj/wizard.dmi' + icon_state = "render" + lefthand_file = 'icons/mob/inhands/equipment/kitchen_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/kitchen_righthand.dmi' + w_class = WEIGHT_CLASS_NORMAL + +/obj/item/kitchen/knife/butcher + name = "butcher's cleaver" + icon_state = "butch" + item_state = "butch" + desc = "A huge thing used for chopping and chopping up meat. This includes clowns and clown by-products." + flags_1 = CONDUCT_1 + force = 15 + throwforce = 10 + custom_materials = list(/datum/material/iron=18000) + attack_verb = list("cleaved", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") + w_class = WEIGHT_CLASS_NORMAL + custom_price = 600 + +/obj/item/kitchen/knife/hunting + name = "hunting knife" + desc = "Despite its name, it's mainly used for cutting meat from dead prey rather than actual hunting." + item_state = "huntingknife" + icon_state = "huntingknife" + +/obj/item/kitchen/knife/hunting/set_butchering() + AddComponent(/datum/component/butchering, 80 - force, 100, force + 10) + +/obj/item/kitchen/knife/combat + name = "combat knife" + icon_state = "buckknife" + desc = "A military combat utility survival knife." + embedding = list("pain_mult" = 4, "embed_chance" = 65, "fall_chance" = 10, "ignore_throwspeed_threshold" = TRUE) + force = 20 + throwforce = 20 + attack_verb = list("slashed", "stabbed", "sliced", "torn", "ripped", "cut") + bayonet = TRUE + +/obj/item/kitchen/knife/combat/survival + name = "survival knife" + icon_state = "survivalknife" + embedding = list("pain_mult" = 4, "embed_chance" = 35, "fall_chance" = 10) + desc = "A hunting grade survival knife." + force = 15 + throwforce = 15 + bayonet = TRUE + +/obj/item/kitchen/knife/combat/bone + name = "bone dagger" + item_state = "bone_dagger" + icon_state = "bone_dagger" + lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' + desc = "A sharpened bone. The bare minimum in survival." + embedding = list("pain_mult" = 4, "embed_chance" = 35, "fall_chance" = 10) + force = 15 + throwforce = 15 + custom_materials = null + +/obj/item/kitchen/knife/combat/cyborg + name = "cyborg knife" + icon = 'icons/obj/items_cyborg.dmi' + icon_state = "knife_cyborg" + desc = "A cyborg-mounted plasteel knife. Extremely sharp and durable." + +/obj/item/kitchen/knife/shiv + name = "glass shiv" + icon = 'icons/obj/shards.dmi' + icon_state = "shiv" + item_state = "shiv" + lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' + desc = "A makeshift glass shiv." + force = 8 + throwforce = 12 + attack_verb = list("shanked", "shivved") + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + custom_materials = list(/datum/material/glass=400) + +/obj/item/kitchen/knife/shiv/carrot + name = "carrot shiv" + icon_state = "carrotshiv" + item_state = "carrotshiv" + icon = 'icons/obj/kitchen.dmi' + desc = "Unlike other carrots, you should probably keep this far away from your eyes." + custom_materials = null + +/obj/item/kitchen/knife/shiv/carrot/suicide_act(mob/living/carbon/user) + user.visible_message("[user] forcefully drives \the [src] into [user.p_their()] eye! It looks like [user.p_theyre()] trying to commit suicide!") + return BRUTELOSS + +/obj/item/kitchen/rollingpin + name = "rolling pin" + desc = "Used to knock out the Bartender." + icon_state = "rolling_pin" + force = 8 + throwforce = 5 + throw_speed = 3 + throw_range = 7 + custom_materials = list(/datum/material/wood = MINERAL_MATERIAL_AMOUNT * 1.5) + w_class = WEIGHT_CLASS_NORMAL + attack_verb = list("bashed", "battered", "bludgeoned", "thrashed", "whacked") + custom_price = 200 + +/obj/item/kitchen/rollingpin/suicide_act(mob/living/carbon/user) + user.visible_message("[user] begins flattening [user.p_their()] head with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return BRUTELOSS +/* Trays moved to /obj/item/storage/bag */ + +/obj/item/kitchen/spoon/plastic + name = "plastic spoon" + desc = "Just be careful your food doesn't melt the spoon first." + icon_state = "plastic_spoon" + force = 0 + w_class = WEIGHT_CLASS_TINY + throwforce = 0 + custom_materials = list(/datum/material/plastic=120) + custom_price = 50 + var/break_chance = 25 + +/obj/item/kitchen/knife/plastic/afterattack(mob/living/carbon/user) + .=..() + if(prob(break_chance)) + user.visible_message("[user]'s spoon snaps into tiny pieces in their hand.") + qdel(src) diff --git a/code/game/objects/items/latexballoon.dm b/code/game/objects/items/latexballoon.dm index 06981729dec0..adbccafaa141 100644 --- a/code/game/objects/items/latexballoon.dm +++ b/code/game/objects/items/latexballoon.dm @@ -1,58 +1,58 @@ -/obj/item/latexballon - name = "latex glove" - desc = "Sterile and airtight." - icon_state = "latexballon" - item_state = "lgloves" - force = 0 - throwforce = 0 - w_class = WEIGHT_CLASS_TINY - throw_speed = 1 - throw_range = 7 - var/state - var/datum/gas_mixture/air_contents = null - -/obj/item/latexballon/proc/blow(obj/item/tank/tank, mob/user) - if (icon_state == "latexballon_bursted") - return - icon_state = "latexballon_blow" - item_state = "latexballon" - user.update_inv_hands() - to_chat(user, "You blow up [src] with [tank].") - air_contents = tank.remove_air_volume(3) - -/obj/item/latexballon/proc/burst() - if (!air_contents || icon_state != "latexballon_blow") - return - playsound(src, 'sound/weapons/gun/pistol/shot.ogg', 100, TRUE) - icon_state = "latexballon_bursted" - item_state = "lgloves" - if(isliving(loc)) - var/mob/living/user = src.loc - user.update_inv_hands() - loc.assume_air(air_contents) - -/obj/item/latexballon/ex_act(severity, target) - burst() - switch(severity) - if (1) - qdel(src) - if (2) - if (prob(50)) - qdel(src) - -/obj/item/latexballon/bullet_act(obj/projectile/P) - if(!P.nodamage) - burst() - return ..() - -/obj/item/latexballon/temperature_expose(datum/gas_mixture/air, temperature, volume) - if(temperature > T0C+100) - burst() - -/obj/item/latexballon/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/tank)) - var/obj/item/tank/T = W - blow(T, user) - return - if (W.get_sharpness() || W.get_temperature() || is_pointed(W)) - burst() +/obj/item/latexballon + name = "latex glove" + desc = "Sterile and airtight." + icon_state = "latexballon" + item_state = "lgloves" + force = 0 + throwforce = 0 + w_class = WEIGHT_CLASS_TINY + throw_speed = 1 + throw_range = 7 + var/state + var/datum/gas_mixture/air_contents = null + +/obj/item/latexballon/proc/blow(obj/item/tank/tank, mob/user) + if (icon_state == "latexballon_bursted") + return + icon_state = "latexballon_blow" + item_state = "latexballon" + user.update_inv_hands() + to_chat(user, "You blow up [src] with [tank].") + air_contents = tank.remove_air_volume(3) + +/obj/item/latexballon/proc/burst() + if (!air_contents || icon_state != "latexballon_blow") + return + playsound(src, 'sound/weapons/gun/pistol/shot.ogg', 100, TRUE) + icon_state = "latexballon_bursted" + item_state = "lgloves" + if(isliving(loc)) + var/mob/living/user = src.loc + user.update_inv_hands() + loc.assume_air(air_contents) + +/obj/item/latexballon/ex_act(severity, target) + burst() + switch(severity) + if (1) + qdel(src) + if (2) + if (prob(50)) + qdel(src) + +/obj/item/latexballon/bullet_act(obj/projectile/P) + if(!P.nodamage) + burst() + return ..() + +/obj/item/latexballon/temperature_expose(datum/gas_mixture/air, temperature, volume) + if(temperature > T0C+100) + burst() + +/obj/item/latexballon/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/tank)) + var/obj/item/tank/T = W + blow(T, user) + return + if (W.get_sharpness() || W.get_temperature() || is_pointed(W)) + burst() diff --git a/code/game/objects/items/manuals.dm b/code/game/objects/items/manuals.dm index 0bee29268729..57ff519ceb9c 100644 --- a/code/game/objects/items/manuals.dm +++ b/code/game/objects/items/manuals.dm @@ -1,455 +1,455 @@ -/*********************MANUALS (BOOKS)***********************/ - -//Oh god what the fuck I am not good at computer -/obj/item/book/manual - icon = 'icons/obj/library.dmi' - due_date = 0 // Game time in 1/10th seconds - unique = TRUE // FALSE - Normal book, TRUE - Should not be treated as normal book, unable to be copied, unable to be modified - -/obj/item/book/manual/hydroponics_pod_people - name = "The Human Harvest - From seed to market" - icon_state ="bookHydroponicsPodPeople" - author = "Farmer John" // Whoever wrote the paper or book, can be changed by pen or PC. It is not automatically assigned. - title = "The Human Harvest - From seed to market" - //book contents below - dat = {" - - - - - -

                        Growing Humans

                        - - Why would you want to grow humans? Well I'm expecting most readers to be in the slave trade, but a few might actually - want to revive fallen comrades. Growing pod people is easy, but prone to disaster. -

                        -

                          -
                        1. Find a dead person who is in need of cloning.
                        2. -
                        3. Take a blood sample with a syringe.
                        4. -
                        5. Inject a seed pack with the blood sample.
                        6. -
                        7. Plant the seeds.
                        8. -
                        9. Tend to the plants water and nutrition levels until it is time to harvest the cloned human.
                        10. -
                        -

                        - It really is that easy! Good luck! - - - - "} - -/obj/item/book/manual/ripley_build_and_repair - name = "APLU \"Ripley\" Construction and Operation Manual" - icon_state ="book" - author = "Weyland-Yutani Corp" - title = "APLU \"Ripley\" Construction and Operation Manual" - dat = {" - - - - - -

                        - Weyland-Yutani - Building Better Worlds -

                        Autonomous Power Loader Unit \"Ripley\"

                        -
                        -

                        Specifications:

                        -
                          -
                        • Class: Autonomous Power Loader
                        • -
                        • Scope: Logistics and Construction
                        • -
                        • Weight: 820kg (without operator and with empty cargo compartment)
                        • -
                        • Height: 2.5m
                        • -
                        • Width: 1.8m
                        • -
                        • Top speed: 5km/hour
                        • -
                        • Operation in vacuum/hostile environment: Possible -
                        • Airtank Volume: 500liters
                        • -
                        • Devices: -
                            -
                          • Hydraulic Clamp
                          • -
                          • High-speed Drill
                          • -
                          -
                        • -
                        • Propulsion Device: Powercell-powered electro-hydraulic system.
                        • -
                        • Powercell capacity: Varies.
                        • -
                        - -

                        Construction:

                        -
                          -
                        1. Connect all exosuit parts to the chassis frame
                        2. -
                        3. Connect all hydraulic fittings and tighten them up with a wrench
                        4. -
                        5. Adjust the servohydraulics with a screwdriver
                        6. -
                        7. Wire the chassis. (Cable is not included.)
                        8. -
                        9. Use the wirecutters to remove the excess cable if needed.
                        10. -
                        11. Install the central control module (Not included. Use supplied datadisk to create one).
                        12. -
                        13. Secure the mainboard with a screwdriver.
                        14. -
                        15. Install the peripherals control module (Not included. Use supplied datadisk to create one).
                        16. -
                        17. Secure the peripherals control module with a screwdriver
                        18. -
                        19. Install the internal armor plating (Not included due to Nanotrasen regulations. Can be made using 5 metal sheets.)
                        20. -
                        21. Secure the internal armor plating with a wrench
                        22. -
                        23. Weld the internal armor plating to the chassis
                        24. -
                        25. Install the external reinforced armor plating (Not included due to Nanotrasen regulations. Can be made using 5 reinforced metal sheets.)
                        26. -
                        27. Secure the external reinforced armor plating with a wrench
                        28. -
                        29. Weld the external reinforced armor plating to the chassis
                        30. -
                        31. -
                        32. Additional Information:
                        33. -
                        34. The firefighting variation is made in a similar fashion.
                        35. -
                        36. A firesuit must be connected to the Firefighter chassis for heat shielding.
                        37. -
                        38. Internal armor is plasteel for additional strength.
                        39. -
                        40. External armor must be installed in 2 parts, totaling 10 sheets.
                        41. -
                        42. Completed mech is more resiliant against fire, and is a bit more durable overall
                        43. -
                        44. Nanotrasen is determined to the safety of its investments employees.
                        45. -
                        - - - -

                        Operation

                        - Please consult the Nanotrasen compendium "Robotics for Dummies". - "} - -/obj/item/book/manual/chef_recipes - name = "Chef Recipes" - icon_state = "cooked_book" - author = "Lord Frenrir Cageth" - title = "Chef Recipes" - dat = {" - - - - - - -

                        Food for Dummies

                        - Here is a guide on basic food recipes and also how to not poison your customers accidentally. - - -

                        Basic ingredients preparation:

                        - - Dough: 10u water + 15u flour for simple dough.
                        - 15u egg yolk + 15u flour + 5u sugar for cake batter.
                        - Doughs can be transformed by using a knife and rolling pin.
                        - All doughs can be microwaved.
                        - Bowl: Add water to it for soup preparation.
                        - Meat: Microwave it, process it, slice it into microwavable cutlets with your knife, or use it raw.
                        - Cheese: Add 5u universal enzyme (catalyst) to milk and soy milk to prepare cheese (sliceable) and tofu.
                        - Rice: Mix 10u rice with 10u water in a bowl then microwave it. - -

                        Custom food:

                        - Add ingredients to a base item to prepare a custom meal.
                        - The bases are:
                        - - bun (burger)
                        - - breadslices(sandwich)
                        - - plain bread
                        - - plain pie
                        - - vanilla cake
                        - - empty bowl (salad)
                        - - bowl with 10u water (soup)
                        - - boiled spaghetti
                        - - pizza bread
                        - - metal rod (kebab) - -

                        Table Craft:

                        - Put ingredients on table, then click and drag the table onto yourself to see what recipes you can prepare. - -

                        Microwave:

                        - Use it to cook or boil food ingredients (meats, doughs, egg, spaghetti, donkpocket, etc...). - It can cook multiple items at once. - -

                        Processor:

                        - Use it to process certain ingredients (meat into meatballs, doughslice into spaghetti, potato into fries,etc...) - -

                        Gibber:

                        - Stuff an animal in it to grind it into meat. - -

                        Meat spike:

                        - Stick an animal on it then begin collecting its meat. - - -

                        Example recipes:

                        - Vanilla Cake: Microwave cake batter.
                        - Burger: 1 bun + 1 meat steak
                        - Bread: Microwave dough.
                        - Waffles: 2 pastry base
                        - Popcorn: Microwave corn.
                        - Meat Steak: Microwave meat.
                        - Meat Pie: 1 plain pie + 1u black pepper + 1u salt + 2 meat cutlets
                        - Boiled Spagetti: Microwave spaghetti.
                        - Donuts: 1u sugar + 1 pastry base
                        - Fries: Process potato. - -

                        Sharing your food:

                        - You can put your meals on your kitchen counter or load them in the snack vending machines. - - - "} - -/obj/item/book/manual/nuclear - name = "Fission Mailed: Nuclear Sabotage 101" - icon_state ="bookNuclear" - author = "Syndicate" - title = "Fission Mailed: Nuclear Sabotage 101" - dat = {" - - - - - Nuclear Explosives 101:
                        - Hello and thank you for choosing the Syndicate for your nuclear information needs.
                        - Today's crash course will deal with the operation of a Fusion Class Nanotrasen made Nuclear Device.
                        - First and foremost, DO NOT TOUCH ANYTHING UNTIL THE BOMB IS IN PLACE.
                        - Pressing any button on the compacted bomb will cause it to extend and bolt itself into place.
                        - If this is done to unbolt it one must completely log in which at this time may not be possible.
                        - To make the nuclear device functional:
                        -
                      • Place the nuclear device in the designated detonation zone.
                      • -
                      • Extend and anchor the nuclear device from its interface.
                      • -
                      • Insert the nuclear authorisation disk into slot.
                      • -
                      • Type numeric authorisation code into the keypad. This should have been provided. Note: If you make a mistake press R to reset the device. -
                      • Press the E button to log onto the device.
                      • - You now have activated the device. To deactivate the buttons at anytime for example when you've already prepped the bomb for detonation remove the auth disk OR press the R on the keypad.
                        - Now the bomb CAN ONLY be detonated using the timer. Manual detonation is not an option.
                        - Note: Nanotrasen is a pain in the neck.
                        - Toggle off the SAFETY.
                        - Note: You wouldn't believe how many Syndicate Operatives with doctorates have forgotten this step.
                        - So use the - - and + + to set a det time between 5 seconds and 10 minutes.
                        - Then press the timer toggle button to start the countdown.
                        - Now remove the auth. disk so that the buttons deactivate.
                        - Note: THE BOMB IS STILL SET AND WILL DETONATE
                        - Now before you remove the disk if you need to move the bomb you can:
                        - Toggle off the anchor, move it, and re-anchor.

                        - Good luck. Remember the order:
                        - Disk, Code, Safety, Timer, Disk, RUN!
                        - Intelligence Analysts believe that normal Nanotrasen procedure is for the Captain to secure the nuclear authorisation disk.
                        - Good luck! - - "} - -// Wiki books that are linked to the configured wiki link. - -// A book that links to the wiki -/obj/item/book/manual/wiki - var/page_link = "" - window_size = "970x710" - -/obj/item/book/manual/wiki/attack_self() - if(!dat) - initialize_wikibook() - return ..() - -/obj/item/book/manual/wiki/proc/initialize_wikibook() - var/wikiurl = CONFIG_GET(string/wikiurl) - if(wikiurl) - dat = {" - - - - - - - - -

                        You start skimming through the manual...

                        - - - - - - "} - -/obj/item/book/manual/wiki/chemistry - name = "Chemistry Textbook" - icon_state ="chemistrybook" - author = "Nanotrasen" - title = "Chemistry Textbook" - page_link = "Guide_to_chemistry" - -/obj/item/book/manual/wiki/engineering_construction - name = "Station Repairs and Construction" - icon_state ="bookEngineering" - author = "Engineering Encyclopedia" - title = "Station Repairs and Construction" - page_link = "Guide_to_construction" - -/obj/item/book/manual/wiki/engineering_guide - name = "Engineering Textbook" - icon_state ="bookEngineering2" - author = "Engineering Encyclopedia" - title = "Engineering Textbook" - page_link = "Guide_to_engineering" - -/obj/item/book/manual/wiki/engineering_singulo_tesla - name = "Singularity and Tesla for Dummies" - icon_state ="bookEngineeringSingularitySafety" - author = "Engineering Encyclopedia" - title = "Singularity and Tesla for Dummies" - page_link = "Singularity_and_Tesla_engines" - -/obj/item/book/manual/wiki/security_space_law - name = "Space Law" - desc = "A set of Nanotrasen guidelines for keeping law and order on their space stations." - icon_state = "bookSpaceLaw" - author = "Nanotrasen" - title = "Space Law" - page_link = "Space_Law" - -/obj/item/book/manual/wiki/security_space_law/suicide_act(mob/living/user) - user.visible_message("[user] pretends to read \the [src] intently... then promptly dies of laughter!") - return OXYLOSS - -/obj/item/book/manual/wiki/infections - name = "Infections - Making your own pandemic!" - icon_state = "bookInfections" - author = "Infections Encyclopedia" - title = "Infections - Making your own pandemic!" - page_link = "Infections" - -/obj/item/book/manual/wiki/telescience - name = "Teleportation Science - Bluespace for dummies!" - icon_state = "book7" - author = "University of Bluespace" - title = "Teleportation Science - Bluespace for dummies!" - page_link = "Guide_to_telescience" - -/obj/item/book/manual/wiki/engineering_hacking - name = "Hacking" - icon_state ="bookHacking" - author = "Engineering Encyclopedia" - title = "Hacking" - page_link = "Hacking" - -/obj/item/book/manual/wiki/detective - name = "The Film Noir: Proper Procedures for Investigations" - icon_state ="bookDetective" - author = "Nanotrasen" - title = "The Film Noir: Proper Procedures for Investigations" - page_link = "Detective" - -/obj/item/book/manual/wiki/barman_recipes - name = "Barman Recipes: Mixing Drinks and Changing Lives" - icon_state = "barbook" - author = "Sir John Rose" - title = "Barman Recipes: Mixing Drinks and Changing Lives" - page_link = "Guide_to_food_and_drinks" - -/obj/item/book/manual/wiki/robotics_cyborgs - name = "Robotics for Dummies" - icon_state = "borgbook" - author = "XISC" - title = "Robotics for Dummies" - page_link = "Guide_to_robotics" - -/obj/item/book/manual/wiki/research_and_development - name = "Research and Development 101" - icon_state = "rdbook" - author = "Dr. L. Ight" - title = "Research and Development 101" - page_link = "Guide_to_Research_and_Development" - -/obj/item/book/manual/wiki/experimentor - name = "Mentoring your Experiments" - icon_state = "rdbook" - author = "Dr. H.P. Kritz" - title = "Mentoring your Experiments" - page_link = "Experimentor" - -/obj/item/book/manual/wiki/cooking_to_serve_man - name = "To Serve Man" - desc = "It's a cookbook!" - icon_state ="cooked_book" - author = "the Kanamitan Empire" - title = "To Serve Man" - page_link = "Guide_to_food_and_drinks" - -/obj/item/book/manual/wiki/tcomms - name = "Subspace Telecommunications And You" - icon_state = "book3" - author = "Engineering Encyclopedia" - title = "Subspace Telecommunications And You" - page_link = "Guide_to_Telecommunications" - -/obj/item/book/manual/wiki/atmospherics - name = "Lexica Atmosia" - icon_state = "book5" - author = "the City-state of Atmosia" - title = "Lexica Atmosia" - page_link = "Guide_to_Atmospherics" - -/obj/item/book/manual/wiki/medicine - name = "Medical Space Compendium, Volume 638" - icon_state = "book8" - author = "Medical Journal" - title = "Medical Space Compendium, Volume 638" - page_link = "Guide_to_medicine" - -/obj/item/book/manual/wiki/surgery - name = "Brain Surgery for Dummies" - icon_state = "book4" - author = "Dr. F. Fran" - title = "Brain Surgery for Dummies" - page_link = "Surgery" - -/obj/item/book/manual/wiki/grenades - name = "DIY Chemical Grenades" - icon_state = "book2" - author = "W. Powell" - title = "DIY Chemical Grenades" - page_link = "Grenade" - -/obj/item/book/manual/wiki/toxins - name = "Toxins or: How I Learned to Stop Worrying and Love the Maxcap" - icon_state = "book6" - author = "Cuban Pete" - title = "Toxins or: How I Learned to Stop Worrying and Love the Maxcap" - page_link = "Guide_to_toxins" - -/obj/item/book/manual/wiki/toxins/suicide_act(mob/user) - var/mob/living/carbon/human/H = user - user.visible_message("[user] starts dancing to the Rhumba Beat! It looks like [user.p_theyre()] trying to commit suicide!") - playsound(loc, 'sound/effects/spray.ogg', 10, TRUE, -3) - if (!QDELETED(H)) - H.emote("spin") - sleep(20) - for(var/obj/item/W in H) - H.dropItemToGround(W) - if(prob(50)) - step(W, pick(GLOB.alldirs)) - ADD_TRAIT(H, TRAIT_DISFIGURED, TRAIT_GENERIC) - H.bleed_rate = 5 - H.gib_animation() - sleep(3) - H.adjustBruteLoss(1000) //to make the body super-bloody - H.spawn_gibs() - H.spill_organs() - H.spread_bodyparts() - return (BRUTELOSS) - -/obj/item/book/manual/wiki/plumbing - name = "Chemical Factories Without Narcotics" - icon_state ="plumbingbook" - author = "Nanotrasen" - title = "Chemical Factories Without Narcotics" - page_link = "Guide_to_plumbing" +/*********************MANUALS (BOOKS)***********************/ + +//Oh god what the fuck I am not good at computer +/obj/item/book/manual + icon = 'icons/obj/library.dmi' + due_date = 0 // Game time in 1/10th seconds + unique = TRUE // FALSE - Normal book, TRUE - Should not be treated as normal book, unable to be copied, unable to be modified + +/obj/item/book/manual/hydroponics_pod_people + name = "The Human Harvest - From seed to market" + icon_state ="bookHydroponicsPodPeople" + author = "Farmer John" // Whoever wrote the paper or book, can be changed by pen or PC. It is not automatically assigned. + title = "The Human Harvest - From seed to market" + //book contents below + dat = {" + + + + + +

                        Growing Humans

                        + + Why would you want to grow humans? Well I'm expecting most readers to be in the slave trade, but a few might actually + want to revive fallen comrades. Growing pod people is easy, but prone to disaster. +

                        +

                          +
                        1. Find a dead person who is in need of cloning.
                        2. +
                        3. Take a blood sample with a syringe.
                        4. +
                        5. Inject a seed pack with the blood sample.
                        6. +
                        7. Plant the seeds.
                        8. +
                        9. Tend to the plants water and nutrition levels until it is time to harvest the cloned human.
                        10. +
                        +

                        + It really is that easy! Good luck! + + + + "} + +/obj/item/book/manual/ripley_build_and_repair + name = "APLU \"Ripley\" Construction and Operation Manual" + icon_state ="book" + author = "Weyland-Yutani Corp" + title = "APLU \"Ripley\" Construction and Operation Manual" + dat = {" + + + + + +

                        + Weyland-Yutani - Building Better Worlds +

                        Autonomous Power Loader Unit \"Ripley\"

                        +
                        +

                        Specifications:

                        +
                          +
                        • Class: Autonomous Power Loader
                        • +
                        • Scope: Logistics and Construction
                        • +
                        • Weight: 820kg (without operator and with empty cargo compartment)
                        • +
                        • Height: 2.5m
                        • +
                        • Width: 1.8m
                        • +
                        • Top speed: 5km/hour
                        • +
                        • Operation in vacuum/hostile environment: Possible +
                        • Airtank Volume: 500liters
                        • +
                        • Devices: +
                            +
                          • Hydraulic Clamp
                          • +
                          • High-speed Drill
                          • +
                          +
                        • +
                        • Propulsion Device: Powercell-powered electro-hydraulic system.
                        • +
                        • Powercell capacity: Varies.
                        • +
                        + +

                        Construction:

                        +
                          +
                        1. Connect all exosuit parts to the chassis frame
                        2. +
                        3. Connect all hydraulic fittings and tighten them up with a wrench
                        4. +
                        5. Adjust the servohydraulics with a screwdriver
                        6. +
                        7. Wire the chassis. (Cable is not included.)
                        8. +
                        9. Use the wirecutters to remove the excess cable if needed.
                        10. +
                        11. Install the central control module (Not included. Use supplied datadisk to create one).
                        12. +
                        13. Secure the mainboard with a screwdriver.
                        14. +
                        15. Install the peripherals control module (Not included. Use supplied datadisk to create one).
                        16. +
                        17. Secure the peripherals control module with a screwdriver
                        18. +
                        19. Install the internal armor plating (Not included due to Nanotrasen regulations. Can be made using 5 metal sheets.)
                        20. +
                        21. Secure the internal armor plating with a wrench
                        22. +
                        23. Weld the internal armor plating to the chassis
                        24. +
                        25. Install the external reinforced armor plating (Not included due to Nanotrasen regulations. Can be made using 5 reinforced metal sheets.)
                        26. +
                        27. Secure the external reinforced armor plating with a wrench
                        28. +
                        29. Weld the external reinforced armor plating to the chassis
                        30. +
                        31. +
                        32. Additional Information:
                        33. +
                        34. The firefighting variation is made in a similar fashion.
                        35. +
                        36. A firesuit must be connected to the Firefighter chassis for heat shielding.
                        37. +
                        38. Internal armor is plasteel for additional strength.
                        39. +
                        40. External armor must be installed in 2 parts, totaling 10 sheets.
                        41. +
                        42. Completed mech is more resiliant against fire, and is a bit more durable overall
                        43. +
                        44. Nanotrasen is determined to the safety of its investments employees.
                        45. +
                        + + + +

                        Operation

                        + Please consult the Nanotrasen compendium "Robotics for Dummies". + "} + +/obj/item/book/manual/chef_recipes + name = "Chef Recipes" + icon_state = "cooked_book" + author = "Lord Frenrir Cageth" + title = "Chef Recipes" + dat = {" + + + + + + +

                        Food for Dummies

                        + Here is a guide on basic food recipes and also how to not poison your customers accidentally. + + +

                        Basic ingredients preparation:

                        + + Dough: 10u water + 15u flour for simple dough.
                        + 15u egg yolk + 15u flour + 5u sugar for cake batter.
                        + Doughs can be transformed by using a knife and rolling pin.
                        + All doughs can be microwaved.
                        + Bowl: Add water to it for soup preparation.
                        + Meat: Microwave it, process it, slice it into microwavable cutlets with your knife, or use it raw.
                        + Cheese: Add 5u universal enzyme (catalyst) to milk and soy milk to prepare cheese (sliceable) and tofu.
                        + Rice: Mix 10u rice with 10u water in a bowl then microwave it. + +

                        Custom food:

                        + Add ingredients to a base item to prepare a custom meal.
                        + The bases are:
                        + - bun (burger)
                        + - breadslices(sandwich)
                        + - plain bread
                        + - plain pie
                        + - vanilla cake
                        + - empty bowl (salad)
                        + - bowl with 10u water (soup)
                        + - boiled spaghetti
                        + - pizza bread
                        + - metal rod (kebab) + +

                        Table Craft:

                        + Put ingredients on table, then click and drag the table onto yourself to see what recipes you can prepare. + +

                        Microwave:

                        + Use it to cook or boil food ingredients (meats, doughs, egg, spaghetti, donkpocket, etc...). + It can cook multiple items at once. + +

                        Processor:

                        + Use it to process certain ingredients (meat into meatballs, doughslice into spaghetti, potato into fries,etc...) + +

                        Gibber:

                        + Stuff an animal in it to grind it into meat. + +

                        Meat spike:

                        + Stick an animal on it then begin collecting its meat. + + +

                        Example recipes:

                        + Vanilla Cake: Microwave cake batter.
                        + Burger: 1 bun + 1 meat steak
                        + Bread: Microwave dough.
                        + Waffles: 2 pastry base
                        + Popcorn: Microwave corn.
                        + Meat Steak: Microwave meat.
                        + Meat Pie: 1 plain pie + 1u black pepper + 1u salt + 2 meat cutlets
                        + Boiled Spagetti: Microwave spaghetti.
                        + Donuts: 1u sugar + 1 pastry base
                        + Fries: Process potato. + +

                        Sharing your food:

                        + You can put your meals on your kitchen counter or load them in the snack vending machines. + + + "} + +/obj/item/book/manual/nuclear + name = "Fission Mailed: Nuclear Sabotage 101" + icon_state ="bookNuclear" + author = "Syndicate" + title = "Fission Mailed: Nuclear Sabotage 101" + dat = {" + + + + + Nuclear Explosives 101:
                        + Hello and thank you for choosing the Syndicate for your nuclear information needs.
                        + Today's crash course will deal with the operation of a Fusion Class Nanotrasen made Nuclear Device.
                        + First and foremost, DO NOT TOUCH ANYTHING UNTIL THE BOMB IS IN PLACE.
                        + Pressing any button on the compacted bomb will cause it to extend and bolt itself into place.
                        + If this is done to unbolt it one must completely log in which at this time may not be possible.
                        + To make the nuclear device functional:
                        +
                      • Place the nuclear device in the designated detonation zone.
                      • +
                      • Extend and anchor the nuclear device from its interface.
                      • +
                      • Insert the nuclear authorisation disk into slot.
                      • +
                      • Type numeric authorisation code into the keypad. This should have been provided. Note: If you make a mistake press R to reset the device. +
                      • Press the E button to log onto the device.
                      • + You now have activated the device. To deactivate the buttons at anytime for example when you've already prepped the bomb for detonation remove the auth disk OR press the R on the keypad.
                        + Now the bomb CAN ONLY be detonated using the timer. Manual detonation is not an option.
                        + Note: Nanotrasen is a pain in the neck.
                        + Toggle off the SAFETY.
                        + Note: You wouldn't believe how many Syndicate Operatives with doctorates have forgotten this step.
                        + So use the - - and + + to set a det time between 5 seconds and 10 minutes.
                        + Then press the timer toggle button to start the countdown.
                        + Now remove the auth. disk so that the buttons deactivate.
                        + Note: THE BOMB IS STILL SET AND WILL DETONATE
                        + Now before you remove the disk if you need to move the bomb you can:
                        + Toggle off the anchor, move it, and re-anchor.

                        + Good luck. Remember the order:
                        + Disk, Code, Safety, Timer, Disk, RUN!
                        + Intelligence Analysts believe that normal Nanotrasen procedure is for the Captain to secure the nuclear authorisation disk.
                        + Good luck! + + "} + +// Wiki books that are linked to the configured wiki link. + +// A book that links to the wiki +/obj/item/book/manual/wiki + var/page_link = "" + window_size = "970x710" + +/obj/item/book/manual/wiki/attack_self() + if(!dat) + initialize_wikibook() + return ..() + +/obj/item/book/manual/wiki/proc/initialize_wikibook() + var/wikiurl = CONFIG_GET(string/wikiurl) + if(wikiurl) + dat = {" + + + + + + + + +

                        You start skimming through the manual...

                        + + + + + + "} + +/obj/item/book/manual/wiki/chemistry + name = "Chemistry Textbook" + icon_state ="chemistrybook" + author = "Nanotrasen" + title = "Chemistry Textbook" + page_link = "Guide_to_chemistry" + +/obj/item/book/manual/wiki/engineering_construction + name = "Station Repairs and Construction" + icon_state ="bookEngineering" + author = "Engineering Encyclopedia" + title = "Station Repairs and Construction" + page_link = "Guide_to_construction" + +/obj/item/book/manual/wiki/engineering_guide + name = "Engineering Textbook" + icon_state ="bookEngineering2" + author = "Engineering Encyclopedia" + title = "Engineering Textbook" + page_link = "Guide_to_engineering" + +/obj/item/book/manual/wiki/engineering_singulo_tesla + name = "Singularity and Tesla for Dummies" + icon_state ="bookEngineeringSingularitySafety" + author = "Engineering Encyclopedia" + title = "Singularity and Tesla for Dummies" + page_link = "Singularity_and_Tesla_engines" + +/obj/item/book/manual/wiki/security_space_law + name = "Space Law" + desc = "A set of Nanotrasen guidelines for keeping law and order on their space stations." + icon_state = "bookSpaceLaw" + author = "Nanotrasen" + title = "Space Law" + page_link = "Space_Law" + +/obj/item/book/manual/wiki/security_space_law/suicide_act(mob/living/user) + user.visible_message("[user] pretends to read \the [src] intently... then promptly dies of laughter!") + return OXYLOSS + +/obj/item/book/manual/wiki/infections + name = "Infections - Making your own pandemic!" + icon_state = "bookInfections" + author = "Infections Encyclopedia" + title = "Infections - Making your own pandemic!" + page_link = "Infections" + +/obj/item/book/manual/wiki/telescience + name = "Teleportation Science - Bluespace for dummies!" + icon_state = "book7" + author = "University of Bluespace" + title = "Teleportation Science - Bluespace for dummies!" + page_link = "Guide_to_telescience" + +/obj/item/book/manual/wiki/engineering_hacking + name = "Hacking" + icon_state ="bookHacking" + author = "Engineering Encyclopedia" + title = "Hacking" + page_link = "Hacking" + +/obj/item/book/manual/wiki/detective + name = "The Film Noir: Proper Procedures for Investigations" + icon_state ="bookDetective" + author = "Nanotrasen" + title = "The Film Noir: Proper Procedures for Investigations" + page_link = "Detective" + +/obj/item/book/manual/wiki/barman_recipes + name = "Barman Recipes: Mixing Drinks and Changing Lives" + icon_state = "barbook" + author = "Sir John Rose" + title = "Barman Recipes: Mixing Drinks and Changing Lives" + page_link = "Guide_to_food_and_drinks" + +/obj/item/book/manual/wiki/robotics_cyborgs + name = "Robotics for Dummies" + icon_state = "borgbook" + author = "XISC" + title = "Robotics for Dummies" + page_link = "Guide_to_robotics" + +/obj/item/book/manual/wiki/research_and_development + name = "Research and Development 101" + icon_state = "rdbook" + author = "Dr. L. Ight" + title = "Research and Development 101" + page_link = "Guide_to_Research_and_Development" + +/obj/item/book/manual/wiki/experimentor + name = "Mentoring your Experiments" + icon_state = "rdbook" + author = "Dr. H.P. Kritz" + title = "Mentoring your Experiments" + page_link = "Experimentor" + +/obj/item/book/manual/wiki/cooking_to_serve_man + name = "To Serve Man" + desc = "It's a cookbook!" + icon_state ="cooked_book" + author = "the Kanamitan Empire" + title = "To Serve Man" + page_link = "Guide_to_food_and_drinks" + +/obj/item/book/manual/wiki/tcomms + name = "Subspace Telecommunications And You" + icon_state = "book3" + author = "Engineering Encyclopedia" + title = "Subspace Telecommunications And You" + page_link = "Guide_to_Telecommunications" + +/obj/item/book/manual/wiki/atmospherics + name = "Lexica Atmosia" + icon_state = "book5" + author = "the City-state of Atmosia" + title = "Lexica Atmosia" + page_link = "Guide_to_Atmospherics" + +/obj/item/book/manual/wiki/medicine + name = "Medical Space Compendium, Volume 638" + icon_state = "book8" + author = "Medical Journal" + title = "Medical Space Compendium, Volume 638" + page_link = "Guide_to_medicine" + +/obj/item/book/manual/wiki/surgery + name = "Brain Surgery for Dummies" + icon_state = "book4" + author = "Dr. F. Fran" + title = "Brain Surgery for Dummies" + page_link = "Surgery" + +/obj/item/book/manual/wiki/grenades + name = "DIY Chemical Grenades" + icon_state = "book2" + author = "W. Powell" + title = "DIY Chemical Grenades" + page_link = "Grenade" + +/obj/item/book/manual/wiki/toxins + name = "Toxins or: How I Learned to Stop Worrying and Love the Maxcap" + icon_state = "book6" + author = "Cuban Pete" + title = "Toxins or: How I Learned to Stop Worrying and Love the Maxcap" + page_link = "Guide_to_toxins" + +/obj/item/book/manual/wiki/toxins/suicide_act(mob/user) + var/mob/living/carbon/human/H = user + user.visible_message("[user] starts dancing to the Rhumba Beat! It looks like [user.p_theyre()] trying to commit suicide!") + playsound(loc, 'sound/effects/spray.ogg', 10, TRUE, -3) + if (!QDELETED(H)) + H.emote("spin") + sleep(20) + for(var/obj/item/W in H) + H.dropItemToGround(W) + if(prob(50)) + step(W, pick(GLOB.alldirs)) + ADD_TRAIT(H, TRAIT_DISFIGURED, TRAIT_GENERIC) + H.bleed_rate = 5 + H.gib_animation() + sleep(3) + H.adjustBruteLoss(1000) //to make the body super-bloody + H.spawn_gibs() + H.spill_organs() + H.spread_bodyparts() + return (BRUTELOSS) + +/obj/item/book/manual/wiki/plumbing + name = "Chemical Factories Without Narcotics" + icon_state ="plumbingbook" + author = "Nanotrasen" + title = "Chemical Factories Without Narcotics" + page_link = "Guide_to_plumbing" diff --git a/code/game/objects/items/melee/energy.dm b/code/game/objects/items/melee/energy.dm index 7f178e43b982..13ac4b127e03 100644 --- a/code/game/objects/items/melee/energy.dm +++ b/code/game/objects/items/melee/energy.dm @@ -1,238 +1,238 @@ -/obj/item/melee/transforming/energy - icon = 'icons/obj/transforming_energy.dmi' - hitsound_on = 'sound/weapons/blade1.ogg' - heat = 3500 - max_integrity = 200 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 30) - resistance_flags = FIRE_PROOF - var/brightness_on = 3 - var/sword_color - -/obj/item/melee/transforming/energy/Initialize() - . = ..() - if(active) - set_light(brightness_on) - START_PROCESSING(SSobj, src) - -/obj/item/melee/transforming/energy/Destroy() - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/item/melee/transforming/energy/suicide_act(mob/user) - if(!active) - transform_weapon(user, TRUE) - user.visible_message("[user] is [pick("slitting [user.p_their()] stomach open with", "falling on")] [src]! It looks like [user.p_theyre()] trying to commit seppuku!") - return (BRUTELOSS|FIRELOSS) - -/obj/item/melee/transforming/energy/add_blood_DNA(list/blood_dna) - return FALSE - -/obj/item/melee/transforming/energy/get_sharpness() - return active * sharpness - -/obj/item/melee/transforming/energy/process() - open_flame() - -/obj/item/melee/transforming/energy/transform_weapon(mob/living/user, supress_message_text) - . = ..() - if(.) - if(active) - if(sword_color) - icon_state = "sword[sword_color]" - START_PROCESSING(SSobj, src) - set_light(brightness_on) - else - STOP_PROCESSING(SSobj, src) - set_light(0) - -/obj/item/melee/transforming/energy/get_temperature() - return active * heat - -/obj/item/melee/transforming/energy/ignition_effect(atom/A, mob/user) - if(!active) - return "" - - var/in_mouth = "" - if(iscarbon(user)) - var/mob/living/carbon/C = user - if(C.wear_mask) - in_mouth = ", barely missing [C.p_their()] nose" - . = "[user] swings [user.p_their()] [name][in_mouth]. [user.p_they(TRUE)] light[user.p_s()] [user.p_their()] [A.name] in the process." - playsound(loc, hitsound, get_clamped_volume(), TRUE, -1) - add_fingerprint(user) - -/obj/item/melee/transforming/energy/axe - name = "energy axe" - desc = "An energized battle axe." - icon_state = "axe0" - lefthand_file = 'icons/mob/inhands/weapons/axes_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/axes_righthand.dmi' - force = 40 - force_on = 150 - throwforce = 25 - throwforce_on = 30 - hitsound = 'sound/weapons/bladeslice.ogg' - throw_speed = 3 - throw_range = 5 - w_class = WEIGHT_CLASS_NORMAL - w_class_on = WEIGHT_CLASS_HUGE - flags_1 = CONDUCT_1 - armour_penetration = 100 - attack_verb_off = list("attacked", "chopped", "cleaved", "torn", "cut") - attack_verb_on = list() - light_color = "#40ceff" - -/obj/item/melee/transforming/energy/axe/suicide_act(mob/user) - user.visible_message("[user] swings [src] towards [user.p_their()] head! It looks like [user.p_theyre()] trying to commit suicide!") - return (BRUTELOSS|FIRELOSS) - -/obj/item/melee/transforming/energy/sword - name = "energy sword" - desc = "May the force be within you." - icon_state = "sword0" - lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' - force = 3 - throwforce = 5 - hitsound = "swing_hit" //it starts deactivated - attack_verb_off = list("tapped", "poked") - throw_speed = 3 - throw_range = 5 - sharpness = IS_SHARP - embedding = list("embed_chance" = 75, "impact_pain_mult" = 10) - armour_penetration = 35 - block_chance = 50 - -/obj/item/melee/transforming/energy/sword/transform_weapon(mob/living/user, supress_message_text) - . = ..() - if(. && active && sword_color) - icon_state = "sword[sword_color]" - -/obj/item/melee/transforming/energy/sword/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(active) - return ..() - return 0 - -/obj/item/melee/transforming/energy/sword/cyborg - sword_color = "red" - var/hitcost = 50 - -/obj/item/melee/transforming/energy/sword/cyborg/attack(mob/M, mob/living/silicon/robot/R) - if(R.cell) - var/obj/item/stock_parts/cell/C = R.cell - if(active && !(C.use(hitcost))) - attack_self(R) - to_chat(R, "It's out of charge!") - return - return ..() - -/obj/item/melee/transforming/energy/sword/cyborg/saw //Used by medical Syndicate cyborgs - name = "energy saw" - desc = "For heavy duty cutting. It has a carbon-fiber blade in addition to a toggleable hard-light edge to dramatically increase sharpness." - force_on = 30 - force = 18 //About as much as a spear - hitsound = 'sound/weapons/circsawhit.ogg' - icon = 'icons/obj/surgery.dmi' - icon_state = "esaw_0" - icon_state_on = "esaw_1" - sword_color = null //stops icon from breaking when turned on. - hitcost = 75 //Costs more than a standard cyborg esword - w_class = WEIGHT_CLASS_NORMAL - sharpness = IS_SHARP - light_color = "#40ceff" - tool_behaviour = TOOL_SAW - toolspeed = 0.7 //faster as a saw - -/obj/item/melee/transforming/energy/sword/cyborg/saw/cyborg_unequip(mob/user) - if(!active) - return - transform_weapon(user, TRUE) - -/obj/item/melee/transforming/energy/sword/cyborg/saw/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - return 0 - -/obj/item/melee/transforming/energy/sword/saber - var/list/possible_colors = list("red" = LIGHT_COLOR_RED, "blue" = LIGHT_COLOR_LIGHT_CYAN, "green" = LIGHT_COLOR_GREEN, "purple" = LIGHT_COLOR_LAVENDER) - var/hacked = FALSE - -/obj/item/melee/transforming/energy/sword/saber/Initialize(mapload) - . = ..() - if(LAZYLEN(possible_colors)) - var/set_color = pick(possible_colors) - sword_color = set_color - light_color = possible_colors[set_color] - -/obj/item/melee/transforming/energy/sword/saber/process() - . = ..() - if(hacked) - var/set_color = pick(possible_colors) - light_color = possible_colors[set_color] - update_light() - -/obj/item/melee/transforming/energy/sword/saber/red - possible_colors = list("red" = LIGHT_COLOR_RED) - -/obj/item/melee/transforming/energy/sword/saber/blue - possible_colors = list("blue" = LIGHT_COLOR_LIGHT_CYAN) - -/obj/item/melee/transforming/energy/sword/saber/green - possible_colors = list("green" = LIGHT_COLOR_GREEN) - -/obj/item/melee/transforming/energy/sword/saber/purple - possible_colors = list("purple" = LIGHT_COLOR_LAVENDER) - -/obj/item/melee/transforming/energy/sword/saber/attackby(obj/item/W, mob/living/user, params) - if(W.tool_behaviour == TOOL_MULTITOOL) - if(!hacked) - hacked = TRUE - sword_color = "rainbow" - to_chat(user, "RNBW_ENGAGE") - - if(active) - icon_state = "swordrainbow" - user.update_inv_hands() - else - to_chat(user, "It's already fabulous!") - else - return ..() - -/obj/item/melee/transforming/energy/sword/pirate - name = "energy cutlass" - desc = "Arrrr matey." - icon_state = "cutlass0" - lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' - icon_state_on = "cutlass1" - light_color = "#ff0000" - -/obj/item/melee/transforming/energy/blade - name = "energy blade" - desc = "A concentrated beam of energy in the shape of a blade. Very stylish... and lethal." - icon_state = "blade" - lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' - force = 30 //Normal attacks deal esword damage - hitsound = 'sound/weapons/blade1.ogg' - active = 1 - throwforce = 1 //Throwing or dropping the item deletes it. - throw_speed = 3 - throw_range = 1 - w_class = WEIGHT_CLASS_BULKY//So you can't hide it in your pocket or some such. - var/datum/effect_system/spark_spread/spark_system - sharpness = IS_SHARP - -//Most of the other special functions are handled in their own files. aka special snowflake code so kewl -/obj/item/melee/transforming/energy/blade/Initialize() - . = ..() - spark_system = new /datum/effect_system/spark_spread() - spark_system.set_up(5, 0, src) - spark_system.attach(src) - -/obj/item/melee/transforming/energy/blade/transform_weapon(mob/living/user, supress_message_text) - return - -/obj/item/melee/transforming/energy/blade/hardlight - name = "hardlight blade" - desc = "An extremely sharp blade made out of hard light. Packs quite a punch." - icon_state = "lightblade" - item_state = "lightblade" +/obj/item/melee/transforming/energy + icon = 'icons/obj/transforming_energy.dmi' + hitsound_on = 'sound/weapons/blade1.ogg' + heat = 3500 + max_integrity = 200 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 30) + resistance_flags = FIRE_PROOF + var/brightness_on = 3 + var/sword_color + +/obj/item/melee/transforming/energy/Initialize() + . = ..() + if(active) + set_light(brightness_on) + START_PROCESSING(SSobj, src) + +/obj/item/melee/transforming/energy/Destroy() + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/melee/transforming/energy/suicide_act(mob/user) + if(!active) + transform_weapon(user, TRUE) + user.visible_message("[user] is [pick("slitting [user.p_their()] stomach open with", "falling on")] [src]! It looks like [user.p_theyre()] trying to commit seppuku!") + return (BRUTELOSS|FIRELOSS) + +/obj/item/melee/transforming/energy/add_blood_DNA(list/blood_dna) + return FALSE + +/obj/item/melee/transforming/energy/get_sharpness() + return active * sharpness + +/obj/item/melee/transforming/energy/process() + open_flame() + +/obj/item/melee/transforming/energy/transform_weapon(mob/living/user, supress_message_text) + . = ..() + if(.) + if(active) + if(sword_color) + icon_state = "sword[sword_color]" + START_PROCESSING(SSobj, src) + set_light(brightness_on) + else + STOP_PROCESSING(SSobj, src) + set_light(0) + +/obj/item/melee/transforming/energy/get_temperature() + return active * heat + +/obj/item/melee/transforming/energy/ignition_effect(atom/A, mob/user) + if(!active) + return "" + + var/in_mouth = "" + if(iscarbon(user)) + var/mob/living/carbon/C = user + if(C.wear_mask) + in_mouth = ", barely missing [C.p_their()] nose" + . = "[user] swings [user.p_their()] [name][in_mouth]. [user.p_they(TRUE)] light[user.p_s()] [user.p_their()] [A.name] in the process." + playsound(loc, hitsound, get_clamped_volume(), TRUE, -1) + add_fingerprint(user) + +/obj/item/melee/transforming/energy/axe + name = "energy axe" + desc = "An energized battle axe." + icon_state = "axe0" + lefthand_file = 'icons/mob/inhands/weapons/axes_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/axes_righthand.dmi' + force = 40 + force_on = 150 + throwforce = 25 + throwforce_on = 30 + hitsound = 'sound/weapons/bladeslice.ogg' + throw_speed = 3 + throw_range = 5 + w_class = WEIGHT_CLASS_NORMAL + w_class_on = WEIGHT_CLASS_HUGE + flags_1 = CONDUCT_1 + armour_penetration = 100 + attack_verb_off = list("attacked", "chopped", "cleaved", "torn", "cut") + attack_verb_on = list() + light_color = "#40ceff" + +/obj/item/melee/transforming/energy/axe/suicide_act(mob/user) + user.visible_message("[user] swings [src] towards [user.p_their()] head! It looks like [user.p_theyre()] trying to commit suicide!") + return (BRUTELOSS|FIRELOSS) + +/obj/item/melee/transforming/energy/sword + name = "energy sword" + desc = "May the force be within you." + icon_state = "sword0" + lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' + force = 3 + throwforce = 5 + hitsound = "swing_hit" //it starts deactivated + attack_verb_off = list("tapped", "poked") + throw_speed = 3 + throw_range = 5 + sharpness = IS_SHARP + embedding = list("embed_chance" = 75, "impact_pain_mult" = 10) + armour_penetration = 35 + block_chance = 50 + +/obj/item/melee/transforming/energy/sword/transform_weapon(mob/living/user, supress_message_text) + . = ..() + if(. && active && sword_color) + icon_state = "sword[sword_color]" + +/obj/item/melee/transforming/energy/sword/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + if(active) + return ..() + return 0 + +/obj/item/melee/transforming/energy/sword/cyborg + sword_color = "red" + var/hitcost = 50 + +/obj/item/melee/transforming/energy/sword/cyborg/attack(mob/M, mob/living/silicon/robot/R) + if(R.cell) + var/obj/item/stock_parts/cell/C = R.cell + if(active && !(C.use(hitcost))) + attack_self(R) + to_chat(R, "It's out of charge!") + return + return ..() + +/obj/item/melee/transforming/energy/sword/cyborg/saw //Used by medical Syndicate cyborgs + name = "energy saw" + desc = "For heavy duty cutting. It has a carbon-fiber blade in addition to a toggleable hard-light edge to dramatically increase sharpness." + force_on = 30 + force = 18 //About as much as a spear + hitsound = 'sound/weapons/circsawhit.ogg' + icon = 'icons/obj/surgery.dmi' + icon_state = "esaw_0" + icon_state_on = "esaw_1" + sword_color = null //stops icon from breaking when turned on. + hitcost = 75 //Costs more than a standard cyborg esword + w_class = WEIGHT_CLASS_NORMAL + sharpness = IS_SHARP + light_color = "#40ceff" + tool_behaviour = TOOL_SAW + toolspeed = 0.7 //faster as a saw + +/obj/item/melee/transforming/energy/sword/cyborg/saw/cyborg_unequip(mob/user) + if(!active) + return + transform_weapon(user, TRUE) + +/obj/item/melee/transforming/energy/sword/cyborg/saw/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + return 0 + +/obj/item/melee/transforming/energy/sword/saber + var/list/possible_colors = list("red" = LIGHT_COLOR_RED, "blue" = LIGHT_COLOR_LIGHT_CYAN, "green" = LIGHT_COLOR_GREEN, "purple" = LIGHT_COLOR_LAVENDER) + var/hacked = FALSE + +/obj/item/melee/transforming/energy/sword/saber/Initialize(mapload) + . = ..() + if(LAZYLEN(possible_colors)) + var/set_color = pick(possible_colors) + sword_color = set_color + light_color = possible_colors[set_color] + +/obj/item/melee/transforming/energy/sword/saber/process() + . = ..() + if(hacked) + var/set_color = pick(possible_colors) + light_color = possible_colors[set_color] + update_light() + +/obj/item/melee/transforming/energy/sword/saber/red + possible_colors = list("red" = LIGHT_COLOR_RED) + +/obj/item/melee/transforming/energy/sword/saber/blue + possible_colors = list("blue" = LIGHT_COLOR_LIGHT_CYAN) + +/obj/item/melee/transforming/energy/sword/saber/green + possible_colors = list("green" = LIGHT_COLOR_GREEN) + +/obj/item/melee/transforming/energy/sword/saber/purple + possible_colors = list("purple" = LIGHT_COLOR_LAVENDER) + +/obj/item/melee/transforming/energy/sword/saber/attackby(obj/item/W, mob/living/user, params) + if(W.tool_behaviour == TOOL_MULTITOOL) + if(!hacked) + hacked = TRUE + sword_color = "rainbow" + to_chat(user, "RNBW_ENGAGE") + + if(active) + icon_state = "swordrainbow" + user.update_inv_hands() + else + to_chat(user, "It's already fabulous!") + else + return ..() + +/obj/item/melee/transforming/energy/sword/pirate + name = "energy cutlass" + desc = "Arrrr matey." + icon_state = "cutlass0" + lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' + icon_state_on = "cutlass1" + light_color = "#ff0000" + +/obj/item/melee/transforming/energy/blade + name = "energy blade" + desc = "A concentrated beam of energy in the shape of a blade. Very stylish... and lethal." + icon_state = "blade" + lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' + force = 30 //Normal attacks deal esword damage + hitsound = 'sound/weapons/blade1.ogg' + active = 1 + throwforce = 1 //Throwing or dropping the item deletes it. + throw_speed = 3 + throw_range = 1 + w_class = WEIGHT_CLASS_BULKY//So you can't hide it in your pocket or some such. + var/datum/effect_system/spark_spread/spark_system + sharpness = IS_SHARP + +//Most of the other special functions are handled in their own files. aka special snowflake code so kewl +/obj/item/melee/transforming/energy/blade/Initialize() + . = ..() + spark_system = new /datum/effect_system/spark_spread() + spark_system.set_up(5, 0, src) + spark_system.attach(src) + +/obj/item/melee/transforming/energy/blade/transform_weapon(mob/living/user, supress_message_text) + return + +/obj/item/melee/transforming/energy/blade/hardlight + name = "hardlight blade" + desc = "An extremely sharp blade made out of hard light. Packs quite a punch." + icon_state = "lightblade" + item_state = "lightblade" diff --git a/code/game/objects/items/paiwire.dm b/code/game/objects/items/paiwire.dm index 9c86a22a0389..0dbf23e937f9 100644 --- a/code/game/objects/items/paiwire.dm +++ b/code/game/objects/items/paiwire.dm @@ -1,13 +1,13 @@ -/obj/item/pai_cable - desc = "A flexible coated cable with a universal jack on one end." - name = "data cable" - icon = 'icons/obj/power.dmi' - icon_state = "wire1" - item_flags = NOBLUDGEON - var/obj/machinery/machine - -/obj/item/pai_cable/proc/plugin(obj/machinery/M, mob/living/user) - if(!user.transferItemToLoc(src, M)) - return - user.visible_message("[user] inserts [src] into a data port on [M].", "You insert [src] into a data port on [M].", "You hear the satisfying click of a wire jack fastening into place.") - machine = M +/obj/item/pai_cable + desc = "A flexible coated cable with a universal jack on one end." + name = "data cable" + icon = 'icons/obj/power.dmi' + icon_state = "wire1" + item_flags = NOBLUDGEON + var/obj/machinery/machine + +/obj/item/pai_cable/proc/plugin(obj/machinery/M, mob/living/user) + if(!user.transferItemToLoc(src, M)) + return + user.visible_message("[user] inserts [src] into a data port on [M].", "You insert [src] into a data port on [M].", "You hear the satisfying click of a wire jack fastening into place.") + machine = M diff --git a/code/game/objects/items/robot/robot_items.dm b/code/game/objects/items/robot/robot_items.dm index 59741ae2c4ba..d6a4c3d6c7ef 100644 --- a/code/game/objects/items/robot/robot_items.dm +++ b/code/game/objects/items/robot/robot_items.dm @@ -1,934 +1,934 @@ -/********************************************************************** - Cyborg Spec Items -***********************************************************************/ -/obj/item/borg - icon = 'icons/mob/robot_items.dmi' - - -/obj/item/borg/stun - name = "electrically-charged arm" - icon_state = "elecarm" - var/charge_cost = 30 - -/obj/item/borg/stun/attack(mob/living/M, mob/living/user) - if(ishuman(M)) - var/mob/living/carbon/human/H = M - if(H.check_shields(src, 0, "[M]'s [name]", MELEE_ATTACK)) - playsound(M, 'sound/weapons/genhit.ogg', 50, TRUE) - return FALSE - if(iscyborg(user)) - var/mob/living/silicon/robot/R = user - if(!R.cell.use(charge_cost)) - return - - user.do_attack_animation(M) - M.Paralyze(100) - M.apply_effect(EFFECT_STUTTER, 5) - - M.visible_message("[user] prods [M] with [src]!", \ - "[user] prods you with [src]!") - - playsound(loc, 'sound/weapons/egloves.ogg', 50, TRUE, -1) - - log_combat(user, M, "stunned", src, "(INTENT: [uppertext(user.a_intent)])") - -/obj/item/borg/cyborghug - name = "hugging module" - icon_state = "hugmodule" - desc = "For when a someone really needs a hug." - var/mode = 0 //0 = Hugs 1 = "Hug" 2 = Shock 3 = CRUSH - var/ccooldown = 0 - var/scooldown = 0 - var/shockallowed = FALSE//Can it be a stunarm when emagged. Only PK borgs get this by default. - var/boop = FALSE - -/obj/item/borg/cyborghug/attack_self(mob/living/user) - if(iscyborg(user)) - var/mob/living/silicon/robot/P = user - if(P.emagged&&shockallowed == 1) - if(mode < 3) - mode++ - else - mode = 0 - else if(mode < 1) - mode++ - else - mode = 0 - switch(mode) - if(0) - to_chat(user, "Power reset. Hugs!") - if(1) - to_chat(user, "Power increased!") - if(2) - to_chat(user, "BZZT. Electrifying arms...") - if(3) - to_chat(user, "ERROR: ARM ACTUATORS OVERLOADED.") - -/obj/item/borg/cyborghug/attack(mob/living/M, mob/living/silicon/robot/user) - if(M == user) - return - switch(mode) - if(0) - if(M.health >= 0) - if(user.zone_selected == BODY_ZONE_HEAD) - user.visible_message("[user] playfully boops [M] on the head!", \ - "You playfully boop [M] on the head!") - user.do_attack_animation(M, ATTACK_EFFECT_BOOP) - playsound(loc, 'sound/weapons/tap.ogg', 50, TRUE, -1) - else if(ishuman(M)) - if(!(user.mobility_flags & MOBILITY_STAND)) - user.visible_message("[user] shakes [M] trying to get [M.p_them()] up!", \ - "You shake [M] trying to get [M.p_them()] up!") - else - user.visible_message("[user] hugs [M] to make [M.p_them()] feel better!", \ - "You hug [M] to make [M.p_them()] feel better!") - if(M.resting) - M.set_resting(FALSE, TRUE) - else - user.visible_message("[user] pets [M]!", \ - "You pet [M]!") - playsound(loc, 'sound/weapons/thudswoosh.ogg', 50, TRUE, -1) - if(1) - if(M.health >= 0) - if(ishuman(M)) - if(!(M.mobility_flags & MOBILITY_STAND)) - user.visible_message("[user] shakes [M] trying to get [M.p_them()] up!", \ - "You shake [M] trying to get [M.p_them()] up!") - else if(user.zone_selected == BODY_ZONE_HEAD) - user.visible_message("[user] bops [M] on the head!", \ - "You bop [M] on the head!") - user.do_attack_animation(M, ATTACK_EFFECT_PUNCH) - else - user.visible_message("[user] hugs [M] in a firm bear-hug! [M] looks uncomfortable...", \ - "You hug [M] firmly to make [M.p_them()] feel better! [M] looks uncomfortable...") - if(M.resting) - M.set_resting(FALSE, TRUE) - else - user.visible_message("[user] bops [M] on the head!", \ - "You bop [M] on the head!") - playsound(loc, 'sound/weapons/tap.ogg', 50, TRUE, -1) - if(2) - if(scooldown < world.time) - if(M.health >= 0) - if(ishuman(M)||ismonkey(M)) - M.electrocute_act(5, "[user]", flags = SHOCK_NOGLOVES) - user.visible_message("[user] electrocutes [M] with [user.p_their()] touch!", \ - "You electrocute [M] with your touch!") - M.update_mobility() - else - if(!iscyborg(M)) - M.adjustFireLoss(10) - user.visible_message("[user] shocks [M]!", \ - "You shock [M]!") - else - user.visible_message("[user] shocks [M]. It does not seem to have an effect", \ - "You shock [M] to no effect.") - playsound(loc, 'sound/effects/sparks2.ogg', 50, TRUE, -1) - user.cell.charge -= 500 - scooldown = world.time + 20 - if(3) - if(ccooldown < world.time) - if(M.health >= 0) - if(ishuman(M)) - user.visible_message("[user] crushes [M] in [user.p_their()] grip!", \ - "You crush [M] in your grip!") - else - user.visible_message("[user] crushes [M]!", \ - "You crush [M]!") - playsound(loc, 'sound/weapons/smash.ogg', 50, TRUE, -1) - M.adjustBruteLoss(15) - user.cell.charge -= 300 - ccooldown = world.time + 10 - -/obj/item/borg/cyborghug/peacekeeper - shockallowed = TRUE - -/obj/item/borg/cyborghug/medical - boop = TRUE - -/obj/item/borg/charger - name = "power connector" - icon_state = "charger_draw" - item_flags = NOBLUDGEON - var/mode = "draw" - var/static/list/charge_machines = typecacheof(list(/obj/machinery/cell_charger, /obj/machinery/recharger, /obj/machinery/recharge_station, /obj/machinery/mech_bay_recharge_port)) - var/static/list/charge_items = typecacheof(list(/obj/item/stock_parts/cell, /obj/item/gun/energy)) - -/obj/item/borg/charger/update_icon_state() - icon_state = "charger_[mode]" - -/obj/item/borg/charger/attack_self(mob/user) - if(mode == "draw") - mode = "charge" - else - mode = "draw" - to_chat(user, "You toggle [src] to \"[mode]\" mode.") - update_icon() - -/obj/item/borg/charger/afterattack(obj/item/target, mob/living/silicon/robot/user, proximity_flag) - . = ..() - if(!proximity_flag || !iscyborg(user)) - return - if(mode == "draw") - if(is_type_in_list(target, charge_machines)) - var/obj/machinery/M = target - if((M.machine_stat & (NOPOWER|BROKEN)) || !M.anchored) - to_chat(user, "[M] is unpowered!") - return - - to_chat(user, "You connect to [M]'s power line...") - while(do_after(user, 15, target = M, progress = 0)) - if(!user || !user.cell || mode != "draw") - return - - if((M.machine_stat & (NOPOWER|BROKEN)) || !M.anchored) - break - - if(!user.cell.give(150)) - break - - M.use_power(200) - - to_chat(user, "You stop charging yourself.") - - else if(is_type_in_list(target, charge_items)) - var/obj/item/stock_parts/cell/cell = target - if(!istype(cell)) - cell = locate(/obj/item/stock_parts/cell) in target - if(!cell) - to_chat(user, "[target] has no power cell!") - return - - if(istype(target, /obj/item/gun/energy)) - var/obj/item/gun/energy/E = target - if(!E.can_charge) - to_chat(user, "[target] has no power port!") - return - - if(!cell.charge) - to_chat(user, "[target] has no power!") - - - to_chat(user, "You connect to [target]'s power port...") - - while(do_after(user, 15, target = target, progress = 0)) - if(!user || !user.cell || mode != "draw") - return - - if(!cell || !target) - return - - if(cell != target && cell.loc != target) - return - - var/draw = min(cell.charge, cell.chargerate*0.5, user.cell.maxcharge-user.cell.charge) - if(!cell.use(draw)) - break - if(!user.cell.give(draw)) - break - target.update_icon() - - to_chat(user, "You stop charging yourself.") - - else if(is_type_in_list(target, charge_items)) - var/obj/item/stock_parts/cell/cell = target - if(!istype(cell)) - cell = locate(/obj/item/stock_parts/cell) in target - if(!cell) - to_chat(user, "[target] has no power cell!") - return - - if(istype(target, /obj/item/gun/energy)) - var/obj/item/gun/energy/E = target - if(!E.can_charge) - to_chat(user, "[target] has no power port!") - return - - if(cell.charge >= cell.maxcharge) - to_chat(user, "[target] is already charged!") - - to_chat(user, "You connect to [target]'s power port...") - - while(do_after(user, 15, target = target, progress = 0)) - if(!user || !user.cell || mode != "charge") - return - - if(!cell || !target) - return - - if(cell != target && cell.loc != target) - return - - var/draw = min(user.cell.charge, cell.chargerate*0.5, cell.maxcharge-cell.charge) - if(!user.cell.use(draw)) - break - if(!cell.give(draw)) - break - target.update_icon() - - to_chat(user, "You stop charging [target].") - -/obj/item/harmalarm - name = "\improper Sonic Harm Prevention Tool" - desc = "Releases a harmless blast that confuses most organics. For when the harm is JUST TOO MUCH." - icon = 'icons/obj/device.dmi' - icon_state = "megaphone" - var/cooldown = 0 - -/obj/item/harmalarm/emag_act(mob/user) - obj_flags ^= EMAGGED - if(obj_flags & EMAGGED) - to_chat(user, "You short out the safeties on [src]!") - else - to_chat(user, "You reset the safeties on [src]!") - -/obj/item/harmalarm/attack_self(mob/user) - var/safety = !(obj_flags & EMAGGED) - if(cooldown > world.time) - to_chat(user, "The device is still recharging!") - return - - if(iscyborg(user)) - var/mob/living/silicon/robot/R = user - if(!R.cell || R.cell.charge < 1200) - to_chat(user, "You don't have enough charge to do this!") - return - R.cell.charge -= 1000 - if(R.emagged) - safety = FALSE - - if(safety == TRUE) - user.visible_message("[user] blares out a near-deafening siren from its speakers!", \ - "The siren pierces your hearing and confuses you!", \ - "The siren pierces your hearing!") - for(var/mob/living/carbon/M in get_hearers_in_view(9, user)) - if(M.get_ear_protection() == FALSE) - M.confused += 6 - audible_message("HUMAN HARM") - playsound(get_turf(src), 'sound/ai/harmalarm.ogg', 70, 3) - cooldown = world.time + 200 - user.log_message("used a Cyborg Harm Alarm in [AREACOORD(user)]", LOG_ATTACK) - if(iscyborg(user)) - var/mob/living/silicon/robot/R = user - to_chat(R.connected_ai, "
                        NOTICE - Peacekeeping 'HARM ALARM' used by: [user]
                        ") - - return - - if(safety == FALSE) - user.audible_message("BZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZT") - for(var/mob/living/carbon/C in get_hearers_in_view(9, user)) - var/bang_effect = C.soundbang_act(2, 0, 0, 5) - switch(bang_effect) - if(1) - C.confused += 5 - C.stuttering += 10 - C.Jitter(10) - if(2) - C.Paralyze(40) - C.confused += 10 - C.stuttering += 15 - C.Jitter(25) - playsound(get_turf(src), 'sound/machines/warning-buzzer.ogg', 130, 3) - cooldown = world.time + 600 - user.log_message("used an emagged Cyborg Harm Alarm in [AREACOORD(user)]", LOG_ATTACK) - -#define DISPENSE_LOLLIPOP_MODE 1 -#define THROW_LOLLIPOP_MODE 2 -#define THROW_GUMBALL_MODE 3 -#define DISPENSE_ICECREAM_MODE 4 - -/obj/item/borg/lollipop - name = "treat fabricator" - desc = "Reward humans with various treats. Toggle in-module to switch between dispensing and high velocity ejection modes." - icon_state = "lollipop" - var/candy = 30 - var/candymax = 30 - var/charge_delay = 10 - var/charging = FALSE - var/mode = DISPENSE_LOLLIPOP_MODE - - var/firedelay = 0 - var/hitspeed = 2 - var/hitdamage = 0 - var/emaggedhitdamage = 3 - -/obj/item/borg/lollipop/clown - emaggedhitdamage = 0 - -/obj/item/borg/lollipop/equipped() - . = ..() - check_amount() - -/obj/item/borg/lollipop/dropped() - . = ..() - check_amount() - -/obj/item/borg/lollipop/proc/check_amount() //Doesn't even use processing ticks. - if(charging) - return - if(candy < candymax) - addtimer(CALLBACK(src, .proc/charge_lollipops), charge_delay) - charging = TRUE - -/obj/item/borg/lollipop/proc/charge_lollipops() - candy++ - charging = FALSE - check_amount() - -/obj/item/borg/lollipop/proc/dispense(atom/A, mob/user) - if(candy <= 0) - to_chat(user, "No treats left in storage!") - return FALSE - var/turf/T = get_turf(A) - if(!T || !istype(T) || !isopenturf(T)) - return FALSE - if(isobj(A)) - var/obj/O = A - if(O.density) - return FALSE - - var/obj/item/reagent_containers/food/snacks/L - switch(mode) - if(DISPENSE_LOLLIPOP_MODE) - L = new /obj/item/reagent_containers/food/snacks/chewable/lollipop(T) - if(DISPENSE_ICECREAM_MODE) - L = new /obj/item/reagent_containers/food/snacks/icecream(T) - var/obj/item/reagent_containers/food/snacks/icecream/I = L - I.add_ice_cream("vanilla") - I.desc = "Eat the ice cream." - - var/into_hands = FALSE - if(ismob(A)) - var/mob/M = A - into_hands = M.put_in_hands(L) - - candy-- - check_amount() - - if(into_hands) - user.visible_message("[user] dispenses a treat into the hands of [A].", "You dispense a treat into the hands of [A].", "You hear a click.") - else - user.visible_message("[user] dispenses a treat.", "You dispense a treat.", "You hear a click.") - - playsound(src.loc, 'sound/machines/click.ogg', 50, TRUE) - return TRUE - -/obj/item/borg/lollipop/proc/shootL(atom/target, mob/living/user, params) - if(candy <= 0) - to_chat(user, "Not enough lollipops left!") - return FALSE - candy-- - var/obj/item/ammo_casing/caseless/lollipop/A = new /obj/item/ammo_casing/caseless/lollipop(src) - A.BB.damage = hitdamage - if(hitdamage) - A.BB.nodamage = FALSE - A.BB.speed = 0.5 - playsound(src.loc, 'sound/machines/click.ogg', 50, TRUE) - A.fire_casing(target, user, params, 0, 0, null, 0, src) - user.visible_message("[user] blasts a flying lollipop at [target]!") - check_amount() - -/obj/item/borg/lollipop/proc/shootG(atom/target, mob/living/user, params) //Most certainly a good idea. - if(candy <= 0) - to_chat(user, "Not enough gumballs left!") - return FALSE - candy-- - var/obj/item/ammo_casing/caseless/gumball/A = new /obj/item/ammo_casing/caseless/gumball(src) - A.BB.damage = hitdamage - if(hitdamage) - A.BB.nodamage = FALSE - A.BB.speed = 0.5 - A.BB.color = rgb(rand(0, 255), rand(0, 255), rand(0, 255)) - playsound(src.loc, 'sound/weapons/bulletflyby3.ogg', 50, TRUE) - A.fire_casing(target, user, params, 0, 0, null, 0, src) - user.visible_message("[user] shoots a high-velocity gumball at [target]!") - check_amount() - -/obj/item/borg/lollipop/afterattack(atom/target, mob/living/user, proximity, click_params) - . = ..() - check_amount() - if(iscyborg(user)) - var/mob/living/silicon/robot/R = user - if(!R.cell.use(12)) - to_chat(user, "Not enough power.") - return FALSE - if(R.emagged) - hitdamage = emaggedhitdamage - switch(mode) - if(DISPENSE_LOLLIPOP_MODE, DISPENSE_ICECREAM_MODE) - if(!proximity) - return FALSE - dispense(target, user) - if(THROW_LOLLIPOP_MODE) - shootL(target, user, click_params) - if(THROW_GUMBALL_MODE) - shootG(target, user, click_params) - hitdamage = initial(hitdamage) - -/obj/item/borg/lollipop/attack_self(mob/living/user) - switch(mode) - if(DISPENSE_LOLLIPOP_MODE) - mode = THROW_LOLLIPOP_MODE - to_chat(user, "Module is now throwing lollipops.") - if(THROW_LOLLIPOP_MODE) - mode = THROW_GUMBALL_MODE - to_chat(user, "Module is now blasting gumballs.") - if(THROW_GUMBALL_MODE) - mode = DISPENSE_ICECREAM_MODE - to_chat(user, "Module is now dispensing ice cream.") - if(DISPENSE_ICECREAM_MODE) - mode = DISPENSE_LOLLIPOP_MODE - to_chat(user, "Module is now dispensing lollipops.") - ..() - -#undef DISPENSE_LOLLIPOP_MODE -#undef THROW_LOLLIPOP_MODE -#undef THROW_GUMBALL_MODE -#undef DISPENSE_ICECREAM_MODE - -/obj/item/ammo_casing/caseless/gumball - name = "Gumball" - desc = "Why are you seeing this?!" - projectile_type = /obj/projectile/bullet/reusable/gumball - - -/obj/projectile/bullet/reusable/gumball - name = "gumball" - desc = "Oh noes! A fast-moving gumball!" - icon_state = "gumball" - ammo_type = /obj/item/reagent_containers/food/snacks/gumball/cyborg - nodamage = TRUE - -/obj/projectile/bullet/reusable/gumball/handle_drop() - if(!dropped) - var/turf/T = get_turf(src) - var/obj/item/reagent_containers/food/snacks/gumball/S = new ammo_type(T) - S.color = color - dropped = TRUE - -/obj/item/ammo_casing/caseless/lollipop //NEEDS RANDOMIZED COLOR LOGIC. - name = "Lollipop" - desc = "Why are you seeing this?!" - projectile_type = /obj/projectile/bullet/reusable/lollipop - -/obj/projectile/bullet/reusable/lollipop - name = "lollipop" - desc = "Oh noes! A fast-moving lollipop!" - icon_state = "lollipop_1" - ammo_type = /obj/item/reagent_containers/food/snacks/chewable/lollipop/cyborg - var/color2 = rgb(0, 0, 0) - nodamage = TRUE - -/obj/projectile/bullet/reusable/lollipop/Initialize() - . = ..() - var/obj/item/reagent_containers/food/snacks/chewable/lollipop/S = new ammo_type(src) - color2 = S.headcolor - var/mutable_appearance/head = mutable_appearance('icons/obj/projectiles.dmi', "lollipop_2") - head.color = color2 - add_overlay(head) - -/obj/projectile/bullet/reusable/lollipop/handle_drop() - if(!dropped) - var/turf/T = get_turf(src) - var/obj/item/reagent_containers/food/snacks/chewable/lollipop/S = new ammo_type(T) - S.change_head_color(color2) - dropped = TRUE - -#define PKBORG_DAMPEN_CYCLE_DELAY 20 - -//Peacekeeper Cyborg Projectile Dampenening Field -/obj/item/borg/projectile_dampen - name = "\improper Hyperkinetic Dampening projector" - desc = "A device that projects a dampening field that weakens kinetic energy above a certain threshold. Projects a field that drains power per second while active, that will weaken and slow damaging projectiles inside its field. Still being a prototype, it tends to induce a charge on ungrounded metallic surfaces." - icon = 'icons/obj/device.dmi' - icon_state = "shield" - var/maxenergy = 1500 - var/energy = 1500 - var/energy_recharge = 7.5 - var/energy_recharge_cyborg_drain_coefficient = 0.4 - var/cyborg_cell_critical_percentage = 0.05 - var/mob/living/silicon/robot/host = null - var/datum/proximity_monitor/advanced/dampening_field - var/projectile_damage_coefficient = 0.5 - var/projectile_damage_tick_ecost_coefficient = 2 //Lasers get half their damage chopped off, drains 50 power/tick. Note that fields are processed 5 times per second. - var/projectile_speed_coefficient = 1.5 //Higher the coefficient slower the projectile. - var/projectile_tick_speed_ecost = 15 - var/list/obj/projectile/tracked - var/image/projectile_effect - var/field_radius = 3 - var/active = FALSE - var/cycle_delay = 0 - -/obj/item/borg/projectile_dampen/debug - maxenergy = 50000 - energy = 50000 - energy_recharge = 5000 - -/obj/item/borg/projectile_dampen/Initialize() - . = ..() - projectile_effect = image('icons/effects/fields.dmi', "projectile_dampen_effect") - tracked = list() - icon_state = "shield0" - START_PROCESSING(SSfastprocess, src) - host = loc - -/obj/item/borg/projectile_dampen/Destroy() - STOP_PROCESSING(SSfastprocess, src) - return ..() - -/obj/item/borg/projectile_dampen/attack_self(mob/user) - if(cycle_delay > world.time) - to_chat(user, "[src] is still recycling its projectors!") - return - cycle_delay = world.time + PKBORG_DAMPEN_CYCLE_DELAY - if(!active) - if(!user.has_buckled_mobs()) - activate_field() - else - to_chat(user, "[src]'s safety cutoff prevents you from activating it due to living beings being ontop of you!") - else - deactivate_field() - update_icon() - to_chat(user, "You [active? "activate":"deactivate"] [src].") - -/obj/item/borg/projectile_dampen/update_icon_state() - icon_state = "[initial(icon_state)][active]" - -/obj/item/borg/projectile_dampen/proc/activate_field() - if(istype(dampening_field)) - QDEL_NULL(dampening_field) - dampening_field = make_field(/datum/proximity_monitor/advanced/peaceborg_dampener, list("current_range" = field_radius, "host" = src, "projector" = src)) - var/mob/living/silicon/robot/owner = get_host() - if(owner) - owner.module.allow_riding = FALSE - active = TRUE - -/obj/item/borg/projectile_dampen/proc/deactivate_field() - QDEL_NULL(dampening_field) - visible_message("\The [src] shuts off!") - for(var/P in tracked) - restore_projectile(P) - active = FALSE - - var/mob/living/silicon/robot/owner = get_host() - if(owner) - owner.module.allow_riding = TRUE - -/obj/item/borg/projectile_dampen/proc/get_host() - if(istype(host)) - return host - else - if(iscyborg(host.loc)) - return host.loc - return null - -/obj/item/borg/projectile_dampen/dropped() - . = ..() - host = loc - -/obj/item/borg/projectile_dampen/equipped() - . = ..() - host = loc - -/obj/item/borg/projectile_dampen/cyborg_unequip(mob/user) - deactivate_field() - . = ..() - -/obj/item/borg/projectile_dampen/on_mob_death() - deactivate_field() - . = ..() - -/obj/item/borg/projectile_dampen/process() - process_recharge() - process_usage() - update_location() - -/obj/item/borg/projectile_dampen/proc/update_location() - if(dampening_field) - dampening_field.HandleMove() - -/obj/item/borg/projectile_dampen/proc/process_usage() - var/usage = 0 - for(var/I in tracked) - var/obj/projectile/P = I - if(!P.stun && P.nodamage) //No damage - continue - usage += projectile_tick_speed_ecost - usage += (tracked[I] * projectile_damage_tick_ecost_coefficient) - energy = clamp(energy - usage, 0, maxenergy) - if(energy <= 0) - deactivate_field() - visible_message("[src] blinks \"ENERGY DEPLETED\".") - -/obj/item/borg/projectile_dampen/proc/process_recharge() - if(!istype(host)) - if(iscyborg(host.loc)) - host = host.loc - else - energy = clamp(energy + energy_recharge, 0, maxenergy) - return - if(host.cell && (host.cell.charge >= (host.cell.maxcharge * cyborg_cell_critical_percentage)) && (energy < maxenergy)) - host.cell.use(energy_recharge*energy_recharge_cyborg_drain_coefficient) - energy += energy_recharge - -/obj/item/borg/projectile_dampen/proc/dampen_projectile(obj/projectile/P, track_projectile = TRUE) - if(tracked[P]) - return - if(track_projectile) - tracked[P] = P.damage - P.damage *= projectile_damage_coefficient - P.speed *= projectile_speed_coefficient - P.add_overlay(projectile_effect) - -/obj/item/borg/projectile_dampen/proc/restore_projectile(obj/projectile/P) - tracked -= P - P.damage *= (1/projectile_damage_coefficient) - P.speed *= (1/projectile_speed_coefficient) - P.cut_overlay(projectile_effect) - -/********************************************************************** - HUD/SIGHT things -***********************************************************************/ -/obj/item/borg/sight - var/sight_mode = null - - -/obj/item/borg/sight/xray - name = "\proper X-ray vision" - icon = 'icons/obj/decals.dmi' - icon_state = "securearea" - sight_mode = BORGXRAY - -/obj/item/borg/sight/thermal - name = "\proper thermal vision" - sight_mode = BORGTHERM - icon_state = "thermal" - - -/obj/item/borg/sight/meson - name = "\proper meson vision" - sight_mode = BORGMESON - icon_state = "meson" - -/obj/item/borg/sight/material - name = "\proper material vision" - sight_mode = BORGMATERIAL - icon_state = "material" - -/obj/item/borg/sight/hud - name = "hud" - var/obj/item/clothing/glasses/hud/hud = null - - -/obj/item/borg/sight/hud/med - name = "medical hud" - icon_state = "healthhud" - -/obj/item/borg/sight/hud/med/Initialize() - . = ..() - hud = new /obj/item/clothing/glasses/hud/health(src) - - -/obj/item/borg/sight/hud/sec - name = "security hud" - icon_state = "securityhud" - -/obj/item/borg/sight/hud/sec/Initialize() - . = ..() - hud = new /obj/item/clothing/glasses/hud/security(src) - - -/********************************************************************** - Borg apparatus -***********************************************************************/ -//These are tools that can hold only specific items. For example, the mediborg gets one that can only hold beakers and bottles. - -/obj/item/borg/apparatus/ - name = "unknown storage apparatus" - desc = "This device seems nonfunctional." - icon = 'icons/mob/robot_items.dmi' - icon_state = "hugmodule" - var/obj/item/stored - var/list/storable = list() - -/obj/item/borg/apparatus/Initialize() - . = ..() - RegisterSignal(loc.loc, COMSIG_BORG_SAFE_DECONSTRUCT, .proc/safedecon) - -/obj/item/borg/apparatus/Destroy() - if(stored) - qdel(stored) - . = ..() - -///If we're safely deconstructed, we put the item neatly onto the ground, rather than deleting it. -/obj/item/borg/apparatus/proc/safedecon() - if(stored) - stored.forceMove(get_turf(src)) - stored = null - -/obj/item/borg/apparatus/Exited(atom/A) - if(A == stored) //sanity check - UnregisterSignal(stored, COMSIG_ATOM_UPDATE_ICON) - stored = null - update_icon() - . = ..() - -///A right-click verb, for those not using hotkey mode. -/obj/item/borg/apparatus/verb/verb_dropHeld() - set category = "Object" - set name = "Drop" - - if(usr != loc || !stored) - return - stored.forceMove(get_turf(usr)) - return - -/obj/item/borg/apparatus/attack_self(mob/living/silicon/robot/user) - if(!stored) - return ..() - if(user.client?.keys_held["Alt"]) - stored.forceMove(get_turf(user)) - return - stored.attack_self(user) - -/obj/item/borg/apparatus/pre_attack(atom/A, mob/living/user, params) - if(!stored) - var/itemcheck = FALSE - for(var/i in storable) - if(istype(A, i)) - itemcheck = TRUE - break - if(itemcheck) - var/obj/item/O = A - O.forceMove(src) - stored = O - RegisterSignal(stored, COMSIG_ATOM_UPDATE_ICON, /atom/.proc/update_icon) - update_icon() - return - else - stored.melee_attack_chain(user, A, params) - return - . = ..() - -/obj/item/borg/apparatus/attackby(obj/item/W, mob/user, params) - if(stored) - W.melee_attack_chain(user, stored, params) - return - . = ..() - -///////////////// -//beaker holder// -///////////////// - -/obj/item/borg/apparatus/beaker - name = "beaker storage apparatus" - desc = "A special apparatus for carrying beakers without spilling the contents. Alt-Z or right-click to drop the beaker." - icon_state = "borg_beaker_apparatus" - storable = list(/obj/item/reagent_containers/glass/beaker, - /obj/item/reagent_containers/glass/bottle) - -/obj/item/borg/apparatus/beaker/Initialize() - . = ..() - stored = new /obj/item/reagent_containers/glass/beaker/large(src) - RegisterSignal(stored, COMSIG_ATOM_UPDATE_ICON, /atom/.proc/update_icon) - update_icon() - -/obj/item/borg/apparatus/beaker/Destroy() - if(stored) - var/obj/item/reagent_containers/C = stored - C.SplashReagents(get_turf(src)) - qdel(stored) - . = ..() - -/obj/item/borg/apparatus/beaker/examine() - . = ..() - if(stored) - var/obj/item/reagent_containers/C = stored - . += "The apparatus currently has [C] secured, which contains:" - if(length(C.reagents.reagent_list)) - for(var/datum/reagent/R in C.reagents.reagent_list) - . += "[R.volume] units of [R.name]" - else - . += "Nothing." - -/obj/item/borg/apparatus/beaker/update_overlays() - . = ..() - var/mutable_appearance/arm = mutable_appearance(icon = icon, icon_state = "borg_beaker_apparatus_arm") - if(stored) - COMPILE_OVERLAYS(stored) - stored.pixel_x = 0 - stored.pixel_y = 0 - var/mutable_appearance/stored_copy = new /mutable_appearance(stored) - if(istype(stored, /obj/item/reagent_containers/glass/beaker)) - arm.pixel_y = arm.pixel_y - 3 - stored_copy.layer = FLOAT_LAYER - stored_copy.plane = FLOAT_PLANE - . += stored_copy - else - arm.pixel_y = arm.pixel_y - 5 - . += arm - -/obj/item/borg/apparatus/beaker/attack_self(mob/living/silicon/robot/user) - if(stored && !user.client?.keys_held["Alt"] && user.a_intent != "help") - var/obj/item/reagent_containers/C = stored - C.SplashReagents(get_turf(user)) - loc.visible_message("[user] spills the contents of the [C] all over the floor.") - return - . = ..() - -/obj/item/borg/apparatus/beaker/extra - name = "secondary beaker storage apparatus" - desc = "A supplementary beaker storage apparatus." - -/obj/item/borg/apparatus/beaker/service - name = "beverage storage apparatus" - desc = "A special apparatus for carrying drinks without spilling the contents. Alt-Z or right-click to drop the beaker." - icon_state = "borg_beaker_apparatus" - storable = list(/obj/item/reagent_containers/food/drinks/, - /obj/item/reagent_containers/food/condiment) - -/obj/item/borg/apparatus/beaker/service/Initialize() - . = ..() - stored = new /obj/item/reagent_containers/food/drinks/drinkingglass(src) - RegisterSignal(stored, COMSIG_ATOM_UPDATE_ICON, /atom/.proc/update_icon) - update_icon() - -//////////////////// -//engi part holder// -//////////////////// - -/obj/item/borg/apparatus/circuit - name = "circuit manipulation apparatus" - desc = "A special apparatus for carrying and manipulating circuit boards. Alt-Z or right-click to drop the stored object." - icon_state = "borg_hardware_apparatus" - storable = list(/obj/item/circuitboard, - /obj/item/electronics) - -/obj/item/borg/apparatus/circuit/Initialize() - . = ..() - update_icon() - -/obj/item/borg/apparatus/circuit/update_overlays() - . = ..() - var/mutable_appearance/arm = mutable_appearance(icon, "borg_hardware_apparatus_arm1") - if(stored) - COMPILE_OVERLAYS(stored) - stored.pixel_x = -3 - stored.pixel_y = 0 - if(!istype(stored, /obj/item/circuitboard)) - arm.icon_state = "borg_hardware_apparatus_arm2" - var/mutable_appearance/stored_copy = new /mutable_appearance(stored) - stored_copy.layer = FLOAT_LAYER - stored_copy.plane = FLOAT_PLANE - . += stored_copy - . += arm - -/obj/item/borg/apparatus/circuit/examine() - . = ..() - if(stored) - . += "The apparatus currently has [stored] secured." - -/obj/item/borg/apparatus/circuit/pre_attack(atom/A, mob/living/user, params) - . = ..() - if(istype(A, /obj/item/aiModule) && !stored) //If an admin wants a borg to upload laws, who am I to stop them? Otherwise, we can hint that it fails - to_chat(user, "This circuit board doesn't seem to have standard robot apparatus pin holes. You're unable to pick it up.") +/********************************************************************** + Cyborg Spec Items +***********************************************************************/ +/obj/item/borg + icon = 'icons/mob/robot_items.dmi' + + +/obj/item/borg/stun + name = "electrically-charged arm" + icon_state = "elecarm" + var/charge_cost = 30 + +/obj/item/borg/stun/attack(mob/living/M, mob/living/user) + if(ishuman(M)) + var/mob/living/carbon/human/H = M + if(H.check_shields(src, 0, "[M]'s [name]", MELEE_ATTACK)) + playsound(M, 'sound/weapons/genhit.ogg', 50, TRUE) + return FALSE + if(iscyborg(user)) + var/mob/living/silicon/robot/R = user + if(!R.cell.use(charge_cost)) + return + + user.do_attack_animation(M) + M.Paralyze(100) + M.apply_effect(EFFECT_STUTTER, 5) + + M.visible_message("[user] prods [M] with [src]!", \ + "[user] prods you with [src]!") + + playsound(loc, 'sound/weapons/egloves.ogg', 50, TRUE, -1) + + log_combat(user, M, "stunned", src, "(INTENT: [uppertext(user.a_intent)])") + +/obj/item/borg/cyborghug + name = "hugging module" + icon_state = "hugmodule" + desc = "For when a someone really needs a hug." + var/mode = 0 //0 = Hugs 1 = "Hug" 2 = Shock 3 = CRUSH + var/ccooldown = 0 + var/scooldown = 0 + var/shockallowed = FALSE//Can it be a stunarm when emagged. Only PK borgs get this by default. + var/boop = FALSE + +/obj/item/borg/cyborghug/attack_self(mob/living/user) + if(iscyborg(user)) + var/mob/living/silicon/robot/P = user + if(P.emagged&&shockallowed == 1) + if(mode < 3) + mode++ + else + mode = 0 + else if(mode < 1) + mode++ + else + mode = 0 + switch(mode) + if(0) + to_chat(user, "Power reset. Hugs!") + if(1) + to_chat(user, "Power increased!") + if(2) + to_chat(user, "BZZT. Electrifying arms...") + if(3) + to_chat(user, "ERROR: ARM ACTUATORS OVERLOADED.") + +/obj/item/borg/cyborghug/attack(mob/living/M, mob/living/silicon/robot/user) + if(M == user) + return + switch(mode) + if(0) + if(M.health >= 0) + if(user.zone_selected == BODY_ZONE_HEAD) + user.visible_message("[user] playfully boops [M] on the head!", \ + "You playfully boop [M] on the head!") + user.do_attack_animation(M, ATTACK_EFFECT_BOOP) + playsound(loc, 'sound/weapons/tap.ogg', 50, TRUE, -1) + else if(ishuman(M)) + if(!(user.mobility_flags & MOBILITY_STAND)) + user.visible_message("[user] shakes [M] trying to get [M.p_them()] up!", \ + "You shake [M] trying to get [M.p_them()] up!") + else + user.visible_message("[user] hugs [M] to make [M.p_them()] feel better!", \ + "You hug [M] to make [M.p_them()] feel better!") + if(M.resting) + M.set_resting(FALSE, TRUE) + else + user.visible_message("[user] pets [M]!", \ + "You pet [M]!") + playsound(loc, 'sound/weapons/thudswoosh.ogg', 50, TRUE, -1) + if(1) + if(M.health >= 0) + if(ishuman(M)) + if(!(M.mobility_flags & MOBILITY_STAND)) + user.visible_message("[user] shakes [M] trying to get [M.p_them()] up!", \ + "You shake [M] trying to get [M.p_them()] up!") + else if(user.zone_selected == BODY_ZONE_HEAD) + user.visible_message("[user] bops [M] on the head!", \ + "You bop [M] on the head!") + user.do_attack_animation(M, ATTACK_EFFECT_PUNCH) + else + user.visible_message("[user] hugs [M] in a firm bear-hug! [M] looks uncomfortable...", \ + "You hug [M] firmly to make [M.p_them()] feel better! [M] looks uncomfortable...") + if(M.resting) + M.set_resting(FALSE, TRUE) + else + user.visible_message("[user] bops [M] on the head!", \ + "You bop [M] on the head!") + playsound(loc, 'sound/weapons/tap.ogg', 50, TRUE, -1) + if(2) + if(scooldown < world.time) + if(M.health >= 0) + if(ishuman(M)||ismonkey(M)) + M.electrocute_act(5, "[user]", flags = SHOCK_NOGLOVES) + user.visible_message("[user] electrocutes [M] with [user.p_their()] touch!", \ + "You electrocute [M] with your touch!") + M.update_mobility() + else + if(!iscyborg(M)) + M.adjustFireLoss(10) + user.visible_message("[user] shocks [M]!", \ + "You shock [M]!") + else + user.visible_message("[user] shocks [M]. It does not seem to have an effect", \ + "You shock [M] to no effect.") + playsound(loc, 'sound/effects/sparks2.ogg', 50, TRUE, -1) + user.cell.charge -= 500 + scooldown = world.time + 20 + if(3) + if(ccooldown < world.time) + if(M.health >= 0) + if(ishuman(M)) + user.visible_message("[user] crushes [M] in [user.p_their()] grip!", \ + "You crush [M] in your grip!") + else + user.visible_message("[user] crushes [M]!", \ + "You crush [M]!") + playsound(loc, 'sound/weapons/smash.ogg', 50, TRUE, -1) + M.adjustBruteLoss(15) + user.cell.charge -= 300 + ccooldown = world.time + 10 + +/obj/item/borg/cyborghug/peacekeeper + shockallowed = TRUE + +/obj/item/borg/cyborghug/medical + boop = TRUE + +/obj/item/borg/charger + name = "power connector" + icon_state = "charger_draw" + item_flags = NOBLUDGEON + var/mode = "draw" + var/static/list/charge_machines = typecacheof(list(/obj/machinery/cell_charger, /obj/machinery/recharger, /obj/machinery/recharge_station, /obj/machinery/mech_bay_recharge_port)) + var/static/list/charge_items = typecacheof(list(/obj/item/stock_parts/cell, /obj/item/gun/energy)) + +/obj/item/borg/charger/update_icon_state() + icon_state = "charger_[mode]" + +/obj/item/borg/charger/attack_self(mob/user) + if(mode == "draw") + mode = "charge" + else + mode = "draw" + to_chat(user, "You toggle [src] to \"[mode]\" mode.") + update_icon() + +/obj/item/borg/charger/afterattack(obj/item/target, mob/living/silicon/robot/user, proximity_flag) + . = ..() + if(!proximity_flag || !iscyborg(user)) + return + if(mode == "draw") + if(is_type_in_list(target, charge_machines)) + var/obj/machinery/M = target + if((M.machine_stat & (NOPOWER|BROKEN)) || !M.anchored) + to_chat(user, "[M] is unpowered!") + return + + to_chat(user, "You connect to [M]'s power line...") + while(do_after(user, 15, target = M, progress = 0)) + if(!user || !user.cell || mode != "draw") + return + + if((M.machine_stat & (NOPOWER|BROKEN)) || !M.anchored) + break + + if(!user.cell.give(150)) + break + + M.use_power(200) + + to_chat(user, "You stop charging yourself.") + + else if(is_type_in_list(target, charge_items)) + var/obj/item/stock_parts/cell/cell = target + if(!istype(cell)) + cell = locate(/obj/item/stock_parts/cell) in target + if(!cell) + to_chat(user, "[target] has no power cell!") + return + + if(istype(target, /obj/item/gun/energy)) + var/obj/item/gun/energy/E = target + if(!E.can_charge) + to_chat(user, "[target] has no power port!") + return + + if(!cell.charge) + to_chat(user, "[target] has no power!") + + + to_chat(user, "You connect to [target]'s power port...") + + while(do_after(user, 15, target = target, progress = 0)) + if(!user || !user.cell || mode != "draw") + return + + if(!cell || !target) + return + + if(cell != target && cell.loc != target) + return + + var/draw = min(cell.charge, cell.chargerate*0.5, user.cell.maxcharge-user.cell.charge) + if(!cell.use(draw)) + break + if(!user.cell.give(draw)) + break + target.update_icon() + + to_chat(user, "You stop charging yourself.") + + else if(is_type_in_list(target, charge_items)) + var/obj/item/stock_parts/cell/cell = target + if(!istype(cell)) + cell = locate(/obj/item/stock_parts/cell) in target + if(!cell) + to_chat(user, "[target] has no power cell!") + return + + if(istype(target, /obj/item/gun/energy)) + var/obj/item/gun/energy/E = target + if(!E.can_charge) + to_chat(user, "[target] has no power port!") + return + + if(cell.charge >= cell.maxcharge) + to_chat(user, "[target] is already charged!") + + to_chat(user, "You connect to [target]'s power port...") + + while(do_after(user, 15, target = target, progress = 0)) + if(!user || !user.cell || mode != "charge") + return + + if(!cell || !target) + return + + if(cell != target && cell.loc != target) + return + + var/draw = min(user.cell.charge, cell.chargerate*0.5, cell.maxcharge-cell.charge) + if(!user.cell.use(draw)) + break + if(!cell.give(draw)) + break + target.update_icon() + + to_chat(user, "You stop charging [target].") + +/obj/item/harmalarm + name = "\improper Sonic Harm Prevention Tool" + desc = "Releases a harmless blast that confuses most organics. For when the harm is JUST TOO MUCH." + icon = 'icons/obj/device.dmi' + icon_state = "megaphone" + var/cooldown = 0 + +/obj/item/harmalarm/emag_act(mob/user) + obj_flags ^= EMAGGED + if(obj_flags & EMAGGED) + to_chat(user, "You short out the safeties on [src]!") + else + to_chat(user, "You reset the safeties on [src]!") + +/obj/item/harmalarm/attack_self(mob/user) + var/safety = !(obj_flags & EMAGGED) + if(cooldown > world.time) + to_chat(user, "The device is still recharging!") + return + + if(iscyborg(user)) + var/mob/living/silicon/robot/R = user + if(!R.cell || R.cell.charge < 1200) + to_chat(user, "You don't have enough charge to do this!") + return + R.cell.charge -= 1000 + if(R.emagged) + safety = FALSE + + if(safety == TRUE) + user.visible_message("[user] blares out a near-deafening siren from its speakers!", \ + "The siren pierces your hearing and confuses you!", \ + "The siren pierces your hearing!") + for(var/mob/living/carbon/M in get_hearers_in_view(9, user)) + if(M.get_ear_protection() == FALSE) + M.confused += 6 + audible_message("HUMAN HARM") + playsound(get_turf(src), 'sound/ai/harmalarm.ogg', 70, 3) + cooldown = world.time + 200 + user.log_message("used a Cyborg Harm Alarm in [AREACOORD(user)]", LOG_ATTACK) + if(iscyborg(user)) + var/mob/living/silicon/robot/R = user + to_chat(R.connected_ai, "
                        NOTICE - Peacekeeping 'HARM ALARM' used by: [user]
                        ") + + return + + if(safety == FALSE) + user.audible_message("BZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZT") + for(var/mob/living/carbon/C in get_hearers_in_view(9, user)) + var/bang_effect = C.soundbang_act(2, 0, 0, 5) + switch(bang_effect) + if(1) + C.confused += 5 + C.stuttering += 10 + C.Jitter(10) + if(2) + C.Paralyze(40) + C.confused += 10 + C.stuttering += 15 + C.Jitter(25) + playsound(get_turf(src), 'sound/machines/warning-buzzer.ogg', 130, 3) + cooldown = world.time + 600 + user.log_message("used an emagged Cyborg Harm Alarm in [AREACOORD(user)]", LOG_ATTACK) + +#define DISPENSE_LOLLIPOP_MODE 1 +#define THROW_LOLLIPOP_MODE 2 +#define THROW_GUMBALL_MODE 3 +#define DISPENSE_ICECREAM_MODE 4 + +/obj/item/borg/lollipop + name = "treat fabricator" + desc = "Reward humans with various treats. Toggle in-module to switch between dispensing and high velocity ejection modes." + icon_state = "lollipop" + var/candy = 30 + var/candymax = 30 + var/charge_delay = 10 + var/charging = FALSE + var/mode = DISPENSE_LOLLIPOP_MODE + + var/firedelay = 0 + var/hitspeed = 2 + var/hitdamage = 0 + var/emaggedhitdamage = 3 + +/obj/item/borg/lollipop/clown + emaggedhitdamage = 0 + +/obj/item/borg/lollipop/equipped() + . = ..() + check_amount() + +/obj/item/borg/lollipop/dropped() + . = ..() + check_amount() + +/obj/item/borg/lollipop/proc/check_amount() //Doesn't even use processing ticks. + if(charging) + return + if(candy < candymax) + addtimer(CALLBACK(src, .proc/charge_lollipops), charge_delay) + charging = TRUE + +/obj/item/borg/lollipop/proc/charge_lollipops() + candy++ + charging = FALSE + check_amount() + +/obj/item/borg/lollipop/proc/dispense(atom/A, mob/user) + if(candy <= 0) + to_chat(user, "No treats left in storage!") + return FALSE + var/turf/T = get_turf(A) + if(!T || !istype(T) || !isopenturf(T)) + return FALSE + if(isobj(A)) + var/obj/O = A + if(O.density) + return FALSE + + var/obj/item/reagent_containers/food/snacks/L + switch(mode) + if(DISPENSE_LOLLIPOP_MODE) + L = new /obj/item/reagent_containers/food/snacks/chewable/lollipop(T) + if(DISPENSE_ICECREAM_MODE) + L = new /obj/item/reagent_containers/food/snacks/icecream(T) + var/obj/item/reagent_containers/food/snacks/icecream/I = L + I.add_ice_cream("vanilla") + I.desc = "Eat the ice cream." + + var/into_hands = FALSE + if(ismob(A)) + var/mob/M = A + into_hands = M.put_in_hands(L) + + candy-- + check_amount() + + if(into_hands) + user.visible_message("[user] dispenses a treat into the hands of [A].", "You dispense a treat into the hands of [A].", "You hear a click.") + else + user.visible_message("[user] dispenses a treat.", "You dispense a treat.", "You hear a click.") + + playsound(src.loc, 'sound/machines/click.ogg', 50, TRUE) + return TRUE + +/obj/item/borg/lollipop/proc/shootL(atom/target, mob/living/user, params) + if(candy <= 0) + to_chat(user, "Not enough lollipops left!") + return FALSE + candy-- + var/obj/item/ammo_casing/caseless/lollipop/A = new /obj/item/ammo_casing/caseless/lollipop(src) + A.BB.damage = hitdamage + if(hitdamage) + A.BB.nodamage = FALSE + A.BB.speed = 0.5 + playsound(src.loc, 'sound/machines/click.ogg', 50, TRUE) + A.fire_casing(target, user, params, 0, 0, null, 0, src) + user.visible_message("[user] blasts a flying lollipop at [target]!") + check_amount() + +/obj/item/borg/lollipop/proc/shootG(atom/target, mob/living/user, params) //Most certainly a good idea. + if(candy <= 0) + to_chat(user, "Not enough gumballs left!") + return FALSE + candy-- + var/obj/item/ammo_casing/caseless/gumball/A = new /obj/item/ammo_casing/caseless/gumball(src) + A.BB.damage = hitdamage + if(hitdamage) + A.BB.nodamage = FALSE + A.BB.speed = 0.5 + A.BB.color = rgb(rand(0, 255), rand(0, 255), rand(0, 255)) + playsound(src.loc, 'sound/weapons/bulletflyby3.ogg', 50, TRUE) + A.fire_casing(target, user, params, 0, 0, null, 0, src) + user.visible_message("[user] shoots a high-velocity gumball at [target]!") + check_amount() + +/obj/item/borg/lollipop/afterattack(atom/target, mob/living/user, proximity, click_params) + . = ..() + check_amount() + if(iscyborg(user)) + var/mob/living/silicon/robot/R = user + if(!R.cell.use(12)) + to_chat(user, "Not enough power.") + return FALSE + if(R.emagged) + hitdamage = emaggedhitdamage + switch(mode) + if(DISPENSE_LOLLIPOP_MODE, DISPENSE_ICECREAM_MODE) + if(!proximity) + return FALSE + dispense(target, user) + if(THROW_LOLLIPOP_MODE) + shootL(target, user, click_params) + if(THROW_GUMBALL_MODE) + shootG(target, user, click_params) + hitdamage = initial(hitdamage) + +/obj/item/borg/lollipop/attack_self(mob/living/user) + switch(mode) + if(DISPENSE_LOLLIPOP_MODE) + mode = THROW_LOLLIPOP_MODE + to_chat(user, "Module is now throwing lollipops.") + if(THROW_LOLLIPOP_MODE) + mode = THROW_GUMBALL_MODE + to_chat(user, "Module is now blasting gumballs.") + if(THROW_GUMBALL_MODE) + mode = DISPENSE_ICECREAM_MODE + to_chat(user, "Module is now dispensing ice cream.") + if(DISPENSE_ICECREAM_MODE) + mode = DISPENSE_LOLLIPOP_MODE + to_chat(user, "Module is now dispensing lollipops.") + ..() + +#undef DISPENSE_LOLLIPOP_MODE +#undef THROW_LOLLIPOP_MODE +#undef THROW_GUMBALL_MODE +#undef DISPENSE_ICECREAM_MODE + +/obj/item/ammo_casing/caseless/gumball + name = "Gumball" + desc = "Why are you seeing this?!" + projectile_type = /obj/projectile/bullet/reusable/gumball + + +/obj/projectile/bullet/reusable/gumball + name = "gumball" + desc = "Oh noes! A fast-moving gumball!" + icon_state = "gumball" + ammo_type = /obj/item/reagent_containers/food/snacks/gumball/cyborg + nodamage = TRUE + +/obj/projectile/bullet/reusable/gumball/handle_drop() + if(!dropped) + var/turf/T = get_turf(src) + var/obj/item/reagent_containers/food/snacks/gumball/S = new ammo_type(T) + S.color = color + dropped = TRUE + +/obj/item/ammo_casing/caseless/lollipop //NEEDS RANDOMIZED COLOR LOGIC. + name = "Lollipop" + desc = "Why are you seeing this?!" + projectile_type = /obj/projectile/bullet/reusable/lollipop + +/obj/projectile/bullet/reusable/lollipop + name = "lollipop" + desc = "Oh noes! A fast-moving lollipop!" + icon_state = "lollipop_1" + ammo_type = /obj/item/reagent_containers/food/snacks/chewable/lollipop/cyborg + var/color2 = rgb(0, 0, 0) + nodamage = TRUE + +/obj/projectile/bullet/reusable/lollipop/Initialize() + . = ..() + var/obj/item/reagent_containers/food/snacks/chewable/lollipop/S = new ammo_type(src) + color2 = S.headcolor + var/mutable_appearance/head = mutable_appearance('icons/obj/projectiles.dmi', "lollipop_2") + head.color = color2 + add_overlay(head) + +/obj/projectile/bullet/reusable/lollipop/handle_drop() + if(!dropped) + var/turf/T = get_turf(src) + var/obj/item/reagent_containers/food/snacks/chewable/lollipop/S = new ammo_type(T) + S.change_head_color(color2) + dropped = TRUE + +#define PKBORG_DAMPEN_CYCLE_DELAY 20 + +//Peacekeeper Cyborg Projectile Dampenening Field +/obj/item/borg/projectile_dampen + name = "\improper Hyperkinetic Dampening projector" + desc = "A device that projects a dampening field that weakens kinetic energy above a certain threshold. Projects a field that drains power per second while active, that will weaken and slow damaging projectiles inside its field. Still being a prototype, it tends to induce a charge on ungrounded metallic surfaces." + icon = 'icons/obj/device.dmi' + icon_state = "shield" + var/maxenergy = 1500 + var/energy = 1500 + var/energy_recharge = 7.5 + var/energy_recharge_cyborg_drain_coefficient = 0.4 + var/cyborg_cell_critical_percentage = 0.05 + var/mob/living/silicon/robot/host = null + var/datum/proximity_monitor/advanced/dampening_field + var/projectile_damage_coefficient = 0.5 + var/projectile_damage_tick_ecost_coefficient = 2 //Lasers get half their damage chopped off, drains 50 power/tick. Note that fields are processed 5 times per second. + var/projectile_speed_coefficient = 1.5 //Higher the coefficient slower the projectile. + var/projectile_tick_speed_ecost = 15 + var/list/obj/projectile/tracked + var/image/projectile_effect + var/field_radius = 3 + var/active = FALSE + var/cycle_delay = 0 + +/obj/item/borg/projectile_dampen/debug + maxenergy = 50000 + energy = 50000 + energy_recharge = 5000 + +/obj/item/borg/projectile_dampen/Initialize() + . = ..() + projectile_effect = image('icons/effects/fields.dmi', "projectile_dampen_effect") + tracked = list() + icon_state = "shield0" + START_PROCESSING(SSfastprocess, src) + host = loc + +/obj/item/borg/projectile_dampen/Destroy() + STOP_PROCESSING(SSfastprocess, src) + return ..() + +/obj/item/borg/projectile_dampen/attack_self(mob/user) + if(cycle_delay > world.time) + to_chat(user, "[src] is still recycling its projectors!") + return + cycle_delay = world.time + PKBORG_DAMPEN_CYCLE_DELAY + if(!active) + if(!user.has_buckled_mobs()) + activate_field() + else + to_chat(user, "[src]'s safety cutoff prevents you from activating it due to living beings being ontop of you!") + else + deactivate_field() + update_icon() + to_chat(user, "You [active? "activate":"deactivate"] [src].") + +/obj/item/borg/projectile_dampen/update_icon_state() + icon_state = "[initial(icon_state)][active]" + +/obj/item/borg/projectile_dampen/proc/activate_field() + if(istype(dampening_field)) + QDEL_NULL(dampening_field) + dampening_field = make_field(/datum/proximity_monitor/advanced/peaceborg_dampener, list("current_range" = field_radius, "host" = src, "projector" = src)) + var/mob/living/silicon/robot/owner = get_host() + if(owner) + owner.module.allow_riding = FALSE + active = TRUE + +/obj/item/borg/projectile_dampen/proc/deactivate_field() + QDEL_NULL(dampening_field) + visible_message("\The [src] shuts off!") + for(var/P in tracked) + restore_projectile(P) + active = FALSE + + var/mob/living/silicon/robot/owner = get_host() + if(owner) + owner.module.allow_riding = TRUE + +/obj/item/borg/projectile_dampen/proc/get_host() + if(istype(host)) + return host + else + if(iscyborg(host.loc)) + return host.loc + return null + +/obj/item/borg/projectile_dampen/dropped() + . = ..() + host = loc + +/obj/item/borg/projectile_dampen/equipped() + . = ..() + host = loc + +/obj/item/borg/projectile_dampen/cyborg_unequip(mob/user) + deactivate_field() + . = ..() + +/obj/item/borg/projectile_dampen/on_mob_death() + deactivate_field() + . = ..() + +/obj/item/borg/projectile_dampen/process() + process_recharge() + process_usage() + update_location() + +/obj/item/borg/projectile_dampen/proc/update_location() + if(dampening_field) + dampening_field.HandleMove() + +/obj/item/borg/projectile_dampen/proc/process_usage() + var/usage = 0 + for(var/I in tracked) + var/obj/projectile/P = I + if(!P.stun && P.nodamage) //No damage + continue + usage += projectile_tick_speed_ecost + usage += (tracked[I] * projectile_damage_tick_ecost_coefficient) + energy = clamp(energy - usage, 0, maxenergy) + if(energy <= 0) + deactivate_field() + visible_message("[src] blinks \"ENERGY DEPLETED\".") + +/obj/item/borg/projectile_dampen/proc/process_recharge() + if(!istype(host)) + if(iscyborg(host.loc)) + host = host.loc + else + energy = clamp(energy + energy_recharge, 0, maxenergy) + return + if(host.cell && (host.cell.charge >= (host.cell.maxcharge * cyborg_cell_critical_percentage)) && (energy < maxenergy)) + host.cell.use(energy_recharge*energy_recharge_cyborg_drain_coefficient) + energy += energy_recharge + +/obj/item/borg/projectile_dampen/proc/dampen_projectile(obj/projectile/P, track_projectile = TRUE) + if(tracked[P]) + return + if(track_projectile) + tracked[P] = P.damage + P.damage *= projectile_damage_coefficient + P.speed *= projectile_speed_coefficient + P.add_overlay(projectile_effect) + +/obj/item/borg/projectile_dampen/proc/restore_projectile(obj/projectile/P) + tracked -= P + P.damage *= (1/projectile_damage_coefficient) + P.speed *= (1/projectile_speed_coefficient) + P.cut_overlay(projectile_effect) + +/********************************************************************** + HUD/SIGHT things +***********************************************************************/ +/obj/item/borg/sight + var/sight_mode = null + + +/obj/item/borg/sight/xray + name = "\proper X-ray vision" + icon = 'icons/obj/decals.dmi' + icon_state = "securearea" + sight_mode = BORGXRAY + +/obj/item/borg/sight/thermal + name = "\proper thermal vision" + sight_mode = BORGTHERM + icon_state = "thermal" + + +/obj/item/borg/sight/meson + name = "\proper meson vision" + sight_mode = BORGMESON + icon_state = "meson" + +/obj/item/borg/sight/material + name = "\proper material vision" + sight_mode = BORGMATERIAL + icon_state = "material" + +/obj/item/borg/sight/hud + name = "hud" + var/obj/item/clothing/glasses/hud/hud = null + + +/obj/item/borg/sight/hud/med + name = "medical hud" + icon_state = "healthhud" + +/obj/item/borg/sight/hud/med/Initialize() + . = ..() + hud = new /obj/item/clothing/glasses/hud/health(src) + + +/obj/item/borg/sight/hud/sec + name = "security hud" + icon_state = "securityhud" + +/obj/item/borg/sight/hud/sec/Initialize() + . = ..() + hud = new /obj/item/clothing/glasses/hud/security(src) + + +/********************************************************************** + Borg apparatus +***********************************************************************/ +//These are tools that can hold only specific items. For example, the mediborg gets one that can only hold beakers and bottles. + +/obj/item/borg/apparatus/ + name = "unknown storage apparatus" + desc = "This device seems nonfunctional." + icon = 'icons/mob/robot_items.dmi' + icon_state = "hugmodule" + var/obj/item/stored + var/list/storable = list() + +/obj/item/borg/apparatus/Initialize() + . = ..() + RegisterSignal(loc.loc, COMSIG_BORG_SAFE_DECONSTRUCT, .proc/safedecon) + +/obj/item/borg/apparatus/Destroy() + if(stored) + qdel(stored) + . = ..() + +///If we're safely deconstructed, we put the item neatly onto the ground, rather than deleting it. +/obj/item/borg/apparatus/proc/safedecon() + if(stored) + stored.forceMove(get_turf(src)) + stored = null + +/obj/item/borg/apparatus/Exited(atom/A) + if(A == stored) //sanity check + UnregisterSignal(stored, COMSIG_ATOM_UPDATE_ICON) + stored = null + update_icon() + . = ..() + +///A right-click verb, for those not using hotkey mode. +/obj/item/borg/apparatus/verb/verb_dropHeld() + set category = "Object" + set name = "Drop" + + if(usr != loc || !stored) + return + stored.forceMove(get_turf(usr)) + return + +/obj/item/borg/apparatus/attack_self(mob/living/silicon/robot/user) + if(!stored) + return ..() + if(user.client?.keys_held["Alt"]) + stored.forceMove(get_turf(user)) + return + stored.attack_self(user) + +/obj/item/borg/apparatus/pre_attack(atom/A, mob/living/user, params) + if(!stored) + var/itemcheck = FALSE + for(var/i in storable) + if(istype(A, i)) + itemcheck = TRUE + break + if(itemcheck) + var/obj/item/O = A + O.forceMove(src) + stored = O + RegisterSignal(stored, COMSIG_ATOM_UPDATE_ICON, /atom/.proc/update_icon) + update_icon() + return + else + stored.melee_attack_chain(user, A, params) + return + . = ..() + +/obj/item/borg/apparatus/attackby(obj/item/W, mob/user, params) + if(stored) + W.melee_attack_chain(user, stored, params) + return + . = ..() + +///////////////// +//beaker holder// +///////////////// + +/obj/item/borg/apparatus/beaker + name = "beaker storage apparatus" + desc = "A special apparatus for carrying beakers without spilling the contents. Alt-Z or right-click to drop the beaker." + icon_state = "borg_beaker_apparatus" + storable = list(/obj/item/reagent_containers/glass/beaker, + /obj/item/reagent_containers/glass/bottle) + +/obj/item/borg/apparatus/beaker/Initialize() + . = ..() + stored = new /obj/item/reagent_containers/glass/beaker/large(src) + RegisterSignal(stored, COMSIG_ATOM_UPDATE_ICON, /atom/.proc/update_icon) + update_icon() + +/obj/item/borg/apparatus/beaker/Destroy() + if(stored) + var/obj/item/reagent_containers/C = stored + C.SplashReagents(get_turf(src)) + qdel(stored) + . = ..() + +/obj/item/borg/apparatus/beaker/examine() + . = ..() + if(stored) + var/obj/item/reagent_containers/C = stored + . += "The apparatus currently has [C] secured, which contains:" + if(length(C.reagents.reagent_list)) + for(var/datum/reagent/R in C.reagents.reagent_list) + . += "[R.volume] units of [R.name]" + else + . += "Nothing." + +/obj/item/borg/apparatus/beaker/update_overlays() + . = ..() + var/mutable_appearance/arm = mutable_appearance(icon = icon, icon_state = "borg_beaker_apparatus_arm") + if(stored) + COMPILE_OVERLAYS(stored) + stored.pixel_x = 0 + stored.pixel_y = 0 + var/mutable_appearance/stored_copy = new /mutable_appearance(stored) + if(istype(stored, /obj/item/reagent_containers/glass/beaker)) + arm.pixel_y = arm.pixel_y - 3 + stored_copy.layer = FLOAT_LAYER + stored_copy.plane = FLOAT_PLANE + . += stored_copy + else + arm.pixel_y = arm.pixel_y - 5 + . += arm + +/obj/item/borg/apparatus/beaker/attack_self(mob/living/silicon/robot/user) + if(stored && !user.client?.keys_held["Alt"] && user.a_intent != "help") + var/obj/item/reagent_containers/C = stored + C.SplashReagents(get_turf(user)) + loc.visible_message("[user] spills the contents of the [C] all over the floor.") + return + . = ..() + +/obj/item/borg/apparatus/beaker/extra + name = "secondary beaker storage apparatus" + desc = "A supplementary beaker storage apparatus." + +/obj/item/borg/apparatus/beaker/service + name = "beverage storage apparatus" + desc = "A special apparatus for carrying drinks without spilling the contents. Alt-Z or right-click to drop the beaker." + icon_state = "borg_beaker_apparatus" + storable = list(/obj/item/reagent_containers/food/drinks/, + /obj/item/reagent_containers/food/condiment) + +/obj/item/borg/apparatus/beaker/service/Initialize() + . = ..() + stored = new /obj/item/reagent_containers/food/drinks/drinkingglass(src) + RegisterSignal(stored, COMSIG_ATOM_UPDATE_ICON, /atom/.proc/update_icon) + update_icon() + +//////////////////// +//engi part holder// +//////////////////// + +/obj/item/borg/apparatus/circuit + name = "circuit manipulation apparatus" + desc = "A special apparatus for carrying and manipulating circuit boards. Alt-Z or right-click to drop the stored object." + icon_state = "borg_hardware_apparatus" + storable = list(/obj/item/circuitboard, + /obj/item/electronics) + +/obj/item/borg/apparatus/circuit/Initialize() + . = ..() + update_icon() + +/obj/item/borg/apparatus/circuit/update_overlays() + . = ..() + var/mutable_appearance/arm = mutable_appearance(icon, "borg_hardware_apparatus_arm1") + if(stored) + COMPILE_OVERLAYS(stored) + stored.pixel_x = -3 + stored.pixel_y = 0 + if(!istype(stored, /obj/item/circuitboard)) + arm.icon_state = "borg_hardware_apparatus_arm2" + var/mutable_appearance/stored_copy = new /mutable_appearance(stored) + stored_copy.layer = FLOAT_LAYER + stored_copy.plane = FLOAT_PLANE + . += stored_copy + . += arm + +/obj/item/borg/apparatus/circuit/examine() + . = ..() + if(stored) + . += "The apparatus currently has [stored] secured." + +/obj/item/borg/apparatus/circuit/pre_attack(atom/A, mob/living/user, params) + . = ..() + if(istype(A, /obj/item/aiModule) && !stored) //If an admin wants a borg to upload laws, who am I to stop them? Otherwise, we can hint that it fails + to_chat(user, "This circuit board doesn't seem to have standard robot apparatus pin holes. You're unable to pick it up.") diff --git a/code/game/objects/items/robot/robot_parts.dm b/code/game/objects/items/robot/robot_parts.dm index 9e04802698b0..86595e46924c 100644 --- a/code/game/objects/items/robot/robot_parts.dm +++ b/code/game/objects/items/robot/robot_parts.dm @@ -1,401 +1,401 @@ - - -//The robot bodyparts have been moved to code/module/surgery/bodyparts/robot_bodyparts.dm - - -/obj/item/robot_suit - name = "cyborg endoskeleton" - desc = "A complex metal backbone with standard limb sockets and pseudomuscle anchors." - icon = 'icons/mob/augmentation/augments.dmi' - icon_state = "robo_suit" - var/obj/item/bodypart/l_arm/robot/l_arm = null - var/obj/item/bodypart/r_arm/robot/r_arm = null - var/obj/item/bodypart/l_leg/robot/l_leg = null - var/obj/item/bodypart/r_leg/robot/r_leg = null - var/obj/item/bodypart/chest/robot/chest = null - var/obj/item/bodypart/head/robot/head = null - - var/created_name = "" - var/mob/living/silicon/ai/forced_ai - var/locomotion = 1 - var/lawsync = 1 - var/aisync = 1 - var/panel_locked = TRUE - -/obj/item/robot_suit/Initialize() - . = ..() - update_icon() - -/obj/item/robot_suit/prebuilt/Initialize() - . = ..() - l_arm = new(src) - r_arm = new(src) - l_leg = new(src) - r_leg = new(src) - head = new(src) - head.flash1 = new(head) - head.flash2 = new(head) - chest = new(src) - chest.wired = TRUE - chest.cell = new /obj/item/stock_parts/cell/high/plus(chest) - update_icon() - -/obj/item/robot_suit/update_overlays() - . = ..() - if(l_arm) - . += "[l_arm.icon_state]+o" - if(r_arm) - . += "[r_arm.icon_state]+o" - if(chest) - . += "[chest.icon_state]+o" - if(l_leg) - . += "[l_leg.icon_state]+o" - if(r_leg) - . += "[r_leg.icon_state]+o" - if(head) - . += "[head.icon_state]+o" - -/obj/item/robot_suit/proc/check_completion() - if(src.l_arm && src.r_arm) - if(src.l_leg && src.r_leg) - if(src.chest && src.head) - SSblackbox.record_feedback("amount", "cyborg_frames_built", 1) - return 1 - return 0 - -/obj/item/robot_suit/wrench_act(mob/living/user, obj/item/I) //Deconstucts empty borg shell. Flashes remain unbroken because they haven't been used yet - . = ..() - var/turf/T = get_turf(src) - if(l_leg || r_leg || chest || l_arm || r_arm || head) - if(I.use_tool(src, user, 5, volume=50)) - if(l_leg) - l_leg.forceMove(T) - l_leg = null - if(r_leg) - r_leg.forceMove(T) - r_leg = null - if(chest) - if (chest.cell) //Sanity check. - chest.cell.forceMove(T) - chest.cell = null - chest.forceMove(T) - new /obj/item/stack/cable_coil(T, 1) - chest.wired = FALSE - chest = null - if(l_arm) - l_arm.forceMove(T) - l_arm = null - if(r_arm) - r_arm.forceMove(T) - r_arm = null - if(head) - head.forceMove(T) - head.flash1.forceMove(T) - head.flash1 = null - head.flash2.forceMove(T) - head.flash2 = null - head = null - to_chat(user, "You disassemble the cyborg shell.") - else - to_chat(user, "There is nothing to remove from the endoskeleton!") - update_icon() - -/obj/item/robot_suit/proc/put_in_hand_or_drop(mob/living/user, obj/item/I) //normal put_in_hands() drops the item ontop of the player, this drops it at the suit's loc - if(!user.put_in_hands(I)) - I.forceMove(drop_location()) - return FALSE - return TRUE - -/obj/item/robot_suit/screwdriver_act(mob/living/user, obj/item/I) //Swaps the power cell if you're holding a new one in your other hand. - . = ..() - if(.) - return TRUE - - if(!chest) //can't remove a cell if there's no chest to remove it from. - to_chat(user, "[src] has no attached torso!") - return - - var/obj/item/stock_parts/cell/temp_cell = user.is_holding_item_of_type(/obj/item/stock_parts/cell) - var/swap_failed - if(!temp_cell) //if we're not holding a cell - swap_failed = TRUE - else if(!user.transferItemToLoc(temp_cell, chest)) - swap_failed = TRUE - to_chat(user, "[temp_cell] is stuck to your hand, you can't put it in [src]!") - - if(chest.cell) //drop the chest's current cell no matter what. - put_in_hand_or_drop(user, chest.cell) - - if(swap_failed) //we didn't transfer any new items. - if(chest.cell) //old cell ejected, nothing inserted. - to_chat(user, "You remove [chest.cell] from [src].") - chest.cell = null - else - to_chat(user, "The power cell slot in [src]'s torso is empty!") - return - - to_chat(user, "You [chest.cell ? "replace [src]'s [chest.cell.name] with [temp_cell]" : "insert [temp_cell] into [src]"].") - chest.cell = temp_cell - return TRUE - -/obj/item/robot_suit/attackby(obj/item/W, mob/user, params) - - if(istype(W, /obj/item/stack/sheet/metal)) - var/obj/item/stack/sheet/metal/M = W - if(!l_arm && !r_arm && !l_leg && !r_leg && !chest && !head) - if (M.use(1)) - var/obj/item/bot_assembly/ed209/B = new - B.forceMove(drop_location()) - to_chat(user, "You arm the robot frame.") - var/holding_this = user.get_inactive_held_item()==src - qdel(src) - if (holding_this) - user.put_in_inactive_hand(B) - else - to_chat(user, "You need one sheet of metal to start building ED-209!") - return - else if(istype(W, /obj/item/bodypart/l_leg/robot)) - if(l_leg) - return - if(!user.transferItemToLoc(W, src)) - return - W.icon_state = initial(W.icon_state) - W.cut_overlays() - l_leg = W - update_icon() - - else if(istype(W, /obj/item/bodypart/r_leg/robot)) - if(src.r_leg) - return - if(!user.transferItemToLoc(W, src)) - return - W.icon_state = initial(W.icon_state) - W.cut_overlays() - r_leg = W - update_icon() - - else if(istype(W, /obj/item/bodypart/l_arm/robot)) - if(l_arm) - return - if(!user.transferItemToLoc(W, src)) - return - W.icon_state = initial(W.icon_state) - W.cut_overlays() - l_arm = W - update_icon() - - else if(istype(W, /obj/item/bodypart/r_arm/robot)) - if(r_arm) - return - if(!user.transferItemToLoc(W, src)) - return - W.icon_state = initial(W.icon_state)//in case it is a dismembered robotic limb - W.cut_overlays() - r_arm = W - update_icon() - - else if(istype(W, /obj/item/bodypart/chest/robot)) - var/obj/item/bodypart/chest/robot/CH = W - if(chest) - return - if(CH.wired && CH.cell) - if(!user.transferItemToLoc(CH, src)) - return - CH.icon_state = initial(CH.icon_state) //in case it is a dismembered robotic limb - CH.cut_overlays() - chest = CH - update_icon() - else if(!CH.wired) - to_chat(user, "You need to attach wires to it first!") - else - to_chat(user, "You need to attach a cell to it first!") - - else if(istype(W, /obj/item/bodypart/head/robot)) - var/obj/item/bodypart/head/robot/HD = W - for(var/X in HD.contents) - if(istype(X, /obj/item/organ)) - to_chat(user, "There are organs inside [HD]!") - return - if(head) - return - if(HD.flash2 && HD.flash1) - if(!user.transferItemToLoc(HD, src)) - return - HD.icon_state = initial(HD.icon_state)//in case it is a dismembered robotic limb - HD.cut_overlays() - head = HD - update_icon() - else - to_chat(user, "You need to attach a flash to it first!") - - else if (W.tool_behaviour == TOOL_MULTITOOL) - if(check_completion()) - Interact(user) - else - to_chat(user, "The endoskeleton must be assembled before debugging can begin!") - - else if(istype(W, /obj/item/mmi)) - var/obj/item/mmi/M = W - if(check_completion()) - if(!chest.cell) - to_chat(user, "The endoskeleton still needs a power cell!") - return - if(!isturf(loc)) - to_chat(user, "You can't put [M] in, the frame has to be standing on the ground to be perfectly precise!") - return - if(!M.brain_check(user)) - return - - var/mob/living/brain/B = M.brainmob - if(is_banned_from(B.ckey, "Cyborg") || QDELETED(src) || QDELETED(B) || QDELETED(user) || QDELETED(M) || !Adjacent(user)) - if(!QDELETED(M)) - to_chat(user, "This [M.name] does not seem to fit!") - return - if(!user.temporarilyRemoveItemFromInventory(W)) - return - - var/mob/living/silicon/robot/O = new /mob/living/silicon/robot/nocell(get_turf(loc)) - if(!O) - return - if(M.laws && M.laws.id != DEFAULT_AI_LAWID) - aisync = 0 - lawsync = 0 - O.laws = M.laws - M.laws.associate(O) - - O.invisibility = 0 - //Transfer debug settings to new mob - O.custom_name = created_name - O.locked = panel_locked - if(!aisync) - lawsync = 0 - O.connected_ai = null - else - O.notify_ai(NEW_BORG) - if(forced_ai) - O.connected_ai = forced_ai - if(!lawsync) - O.lawupdate = 0 - if(M.laws.id == DEFAULT_AI_LAWID) - O.make_laws() - - SSticker.mode.remove_antag_for_borging(B.mind) - O.job = "Cyborg" - - O.cell = chest.cell - chest.cell.forceMove(O) - chest.cell = null - W.forceMove(O)//Should fix cybros run time erroring when blown up. It got deleted before, along with the frame. - if(O.mmi) //we delete the mmi created by robot/New() - qdel(O.mmi) - O.mmi = W //and give the real mmi to the borg. - - O.updatename(B.client) - - B.mind.transfer_to(O) - - if(O.mind && O.mind.special_role) - O.mind.store_memory("As a cyborg, you must obey your silicon laws and master AI above all else. Your objectives will consider you to be dead.") - to_chat(O, "You have been robotized!") - to_chat(O, "You must obey your silicon laws and master AI above all else. Your objectives will consider you to be dead.") - - SSblackbox.record_feedback("amount", "cyborg_birth", 1) - forceMove(O) - O.robot_suit = src - - log_game("[key_name(user)] has put the MMI/posibrain of [key_name(M.brainmob)] into a cyborg shell at [AREACOORD(src)]") - - if(!locomotion) - O.lockcharge = TRUE - O.update_mobility() - to_chat(O, "Error: Servo motors unresponsive.") - - else - to_chat(user, "The MMI must go in after everything else!") - - else if(istype(W, /obj/item/borg/upgrade/ai)) - var/obj/item/borg/upgrade/ai/M = W - if(check_completion()) - if(!isturf(loc)) - to_chat(user, "You cannot install[M], the frame has to be standing on the ground to be perfectly precise!") - return - if(!user.temporarilyRemoveItemFromInventory(M)) - to_chat(user, "[M] is stuck to your hand!") - return - qdel(M) - var/mob/living/silicon/robot/O = new /mob/living/silicon/robot/shell(get_turf(src)) - - if(!aisync) - lawsync = FALSE - O.connected_ai = null - else - if(forced_ai) - O.connected_ai = forced_ai - O.notify_ai(AI_SHELL) - if(!lawsync) - O.lawupdate = FALSE - O.make_laws() - - O.cell = chest.cell - chest.cell.forceMove(O) - chest.cell = null - O.locked = panel_locked - O.job = "Cyborg" - forceMove(O) - O.robot_suit = src - if(!locomotion) - O.lockcharge = TRUE - O.update_mobility() - - else if(istype(W, /obj/item/pen)) - to_chat(user, "You need to use a multitool to name [src]!") - else - return ..() - -/obj/item/robot_suit/proc/Interact(mob/user) - var/t1 = "Designation: [(created_name ? "[created_name]" : "Default Cyborg")]
                        \n" - t1 += "Master AI: [(forced_ai ? "[forced_ai.name]" : "Automatic")]

                        \n" - - t1 += "LawSync Port: [(lawsync ? "Open" : "Closed")]
                        \n" - t1 += "AI Connection Port: [(aisync ? "Open" : "Closed")]
                        \n" - t1 += "Servo Motor Functions: [(locomotion ? "Unlocked" : "Locked")]
                        \n" - t1 += "Panel Lock: [(panel_locked ? "Engaged" : "Disengaged")]
                        \n" - var/datum/browser/popup = new(user, "robotdebug", "Cyborg Boot Debug", 310, 220) - popup.set_content(t1) - popup.open() - -/obj/item/robot_suit/Topic(href, href_list) - if(usr.incapacitated() || !Adjacent(usr)) - return - - var/mob/living/living_user = usr - var/obj/item/item_in_hand = living_user.get_active_held_item() - if(!item_in_hand || item_in_hand.tool_behaviour != TOOL_MULTITOOL) - to_chat(living_user, "You need a multitool!") - return - - if(href_list["Name"]) - var/new_name = reject_bad_name(input(usr, "Enter new designation. Set to blank to reset to default.", "Cyborg Debug", src.created_name),1) - if(!in_range(src, usr) && src.loc != usr) - return - if(new_name) - created_name = new_name - else - created_name = "" - - else if(href_list["Master"]) - forced_ai = select_active_ai(usr, z) - if(!forced_ai) - to_chat(usr, "No active AIs detected.") - - else if(href_list["Law"]) - lawsync = !lawsync - else if(href_list["AI"]) - aisync = !aisync - else if(href_list["Loco"]) - locomotion = !locomotion - else if(href_list["Panel"]) - panel_locked = !panel_locked - - add_fingerprint(usr) - Interact(usr) + + +//The robot bodyparts have been moved to code/module/surgery/bodyparts/robot_bodyparts.dm + + +/obj/item/robot_suit + name = "cyborg endoskeleton" + desc = "A complex metal backbone with standard limb sockets and pseudomuscle anchors." + icon = 'icons/mob/augmentation/augments.dmi' + icon_state = "robo_suit" + var/obj/item/bodypart/l_arm/robot/l_arm = null + var/obj/item/bodypart/r_arm/robot/r_arm = null + var/obj/item/bodypart/l_leg/robot/l_leg = null + var/obj/item/bodypart/r_leg/robot/r_leg = null + var/obj/item/bodypart/chest/robot/chest = null + var/obj/item/bodypart/head/robot/head = null + + var/created_name = "" + var/mob/living/silicon/ai/forced_ai + var/locomotion = 1 + var/lawsync = 1 + var/aisync = 1 + var/panel_locked = TRUE + +/obj/item/robot_suit/Initialize() + . = ..() + update_icon() + +/obj/item/robot_suit/prebuilt/Initialize() + . = ..() + l_arm = new(src) + r_arm = new(src) + l_leg = new(src) + r_leg = new(src) + head = new(src) + head.flash1 = new(head) + head.flash2 = new(head) + chest = new(src) + chest.wired = TRUE + chest.cell = new /obj/item/stock_parts/cell/high/plus(chest) + update_icon() + +/obj/item/robot_suit/update_overlays() + . = ..() + if(l_arm) + . += "[l_arm.icon_state]+o" + if(r_arm) + . += "[r_arm.icon_state]+o" + if(chest) + . += "[chest.icon_state]+o" + if(l_leg) + . += "[l_leg.icon_state]+o" + if(r_leg) + . += "[r_leg.icon_state]+o" + if(head) + . += "[head.icon_state]+o" + +/obj/item/robot_suit/proc/check_completion() + if(src.l_arm && src.r_arm) + if(src.l_leg && src.r_leg) + if(src.chest && src.head) + SSblackbox.record_feedback("amount", "cyborg_frames_built", 1) + return 1 + return 0 + +/obj/item/robot_suit/wrench_act(mob/living/user, obj/item/I) //Deconstucts empty borg shell. Flashes remain unbroken because they haven't been used yet + . = ..() + var/turf/T = get_turf(src) + if(l_leg || r_leg || chest || l_arm || r_arm || head) + if(I.use_tool(src, user, 5, volume=50)) + if(l_leg) + l_leg.forceMove(T) + l_leg = null + if(r_leg) + r_leg.forceMove(T) + r_leg = null + if(chest) + if (chest.cell) //Sanity check. + chest.cell.forceMove(T) + chest.cell = null + chest.forceMove(T) + new /obj/item/stack/cable_coil(T, 1) + chest.wired = FALSE + chest = null + if(l_arm) + l_arm.forceMove(T) + l_arm = null + if(r_arm) + r_arm.forceMove(T) + r_arm = null + if(head) + head.forceMove(T) + head.flash1.forceMove(T) + head.flash1 = null + head.flash2.forceMove(T) + head.flash2 = null + head = null + to_chat(user, "You disassemble the cyborg shell.") + else + to_chat(user, "There is nothing to remove from the endoskeleton!") + update_icon() + +/obj/item/robot_suit/proc/put_in_hand_or_drop(mob/living/user, obj/item/I) //normal put_in_hands() drops the item ontop of the player, this drops it at the suit's loc + if(!user.put_in_hands(I)) + I.forceMove(drop_location()) + return FALSE + return TRUE + +/obj/item/robot_suit/screwdriver_act(mob/living/user, obj/item/I) //Swaps the power cell if you're holding a new one in your other hand. + . = ..() + if(.) + return TRUE + + if(!chest) //can't remove a cell if there's no chest to remove it from. + to_chat(user, "[src] has no attached torso!") + return + + var/obj/item/stock_parts/cell/temp_cell = user.is_holding_item_of_type(/obj/item/stock_parts/cell) + var/swap_failed + if(!temp_cell) //if we're not holding a cell + swap_failed = TRUE + else if(!user.transferItemToLoc(temp_cell, chest)) + swap_failed = TRUE + to_chat(user, "[temp_cell] is stuck to your hand, you can't put it in [src]!") + + if(chest.cell) //drop the chest's current cell no matter what. + put_in_hand_or_drop(user, chest.cell) + + if(swap_failed) //we didn't transfer any new items. + if(chest.cell) //old cell ejected, nothing inserted. + to_chat(user, "You remove [chest.cell] from [src].") + chest.cell = null + else + to_chat(user, "The power cell slot in [src]'s torso is empty!") + return + + to_chat(user, "You [chest.cell ? "replace [src]'s [chest.cell.name] with [temp_cell]" : "insert [temp_cell] into [src]"].") + chest.cell = temp_cell + return TRUE + +/obj/item/robot_suit/attackby(obj/item/W, mob/user, params) + + if(istype(W, /obj/item/stack/sheet/metal)) + var/obj/item/stack/sheet/metal/M = W + if(!l_arm && !r_arm && !l_leg && !r_leg && !chest && !head) + if (M.use(1)) + var/obj/item/bot_assembly/ed209/B = new + B.forceMove(drop_location()) + to_chat(user, "You arm the robot frame.") + var/holding_this = user.get_inactive_held_item()==src + qdel(src) + if (holding_this) + user.put_in_inactive_hand(B) + else + to_chat(user, "You need one sheet of metal to start building ED-209!") + return + else if(istype(W, /obj/item/bodypart/l_leg/robot)) + if(l_leg) + return + if(!user.transferItemToLoc(W, src)) + return + W.icon_state = initial(W.icon_state) + W.cut_overlays() + l_leg = W + update_icon() + + else if(istype(W, /obj/item/bodypart/r_leg/robot)) + if(src.r_leg) + return + if(!user.transferItemToLoc(W, src)) + return + W.icon_state = initial(W.icon_state) + W.cut_overlays() + r_leg = W + update_icon() + + else if(istype(W, /obj/item/bodypart/l_arm/robot)) + if(l_arm) + return + if(!user.transferItemToLoc(W, src)) + return + W.icon_state = initial(W.icon_state) + W.cut_overlays() + l_arm = W + update_icon() + + else if(istype(W, /obj/item/bodypart/r_arm/robot)) + if(r_arm) + return + if(!user.transferItemToLoc(W, src)) + return + W.icon_state = initial(W.icon_state)//in case it is a dismembered robotic limb + W.cut_overlays() + r_arm = W + update_icon() + + else if(istype(W, /obj/item/bodypart/chest/robot)) + var/obj/item/bodypart/chest/robot/CH = W + if(chest) + return + if(CH.wired && CH.cell) + if(!user.transferItemToLoc(CH, src)) + return + CH.icon_state = initial(CH.icon_state) //in case it is a dismembered robotic limb + CH.cut_overlays() + chest = CH + update_icon() + else if(!CH.wired) + to_chat(user, "You need to attach wires to it first!") + else + to_chat(user, "You need to attach a cell to it first!") + + else if(istype(W, /obj/item/bodypart/head/robot)) + var/obj/item/bodypart/head/robot/HD = W + for(var/X in HD.contents) + if(istype(X, /obj/item/organ)) + to_chat(user, "There are organs inside [HD]!") + return + if(head) + return + if(HD.flash2 && HD.flash1) + if(!user.transferItemToLoc(HD, src)) + return + HD.icon_state = initial(HD.icon_state)//in case it is a dismembered robotic limb + HD.cut_overlays() + head = HD + update_icon() + else + to_chat(user, "You need to attach a flash to it first!") + + else if (W.tool_behaviour == TOOL_MULTITOOL) + if(check_completion()) + Interact(user) + else + to_chat(user, "The endoskeleton must be assembled before debugging can begin!") + + else if(istype(W, /obj/item/mmi)) + var/obj/item/mmi/M = W + if(check_completion()) + if(!chest.cell) + to_chat(user, "The endoskeleton still needs a power cell!") + return + if(!isturf(loc)) + to_chat(user, "You can't put [M] in, the frame has to be standing on the ground to be perfectly precise!") + return + if(!M.brain_check(user)) + return + + var/mob/living/brain/B = M.brainmob + if(is_banned_from(B.ckey, "Cyborg") || QDELETED(src) || QDELETED(B) || QDELETED(user) || QDELETED(M) || !Adjacent(user)) + if(!QDELETED(M)) + to_chat(user, "This [M.name] does not seem to fit!") + return + if(!user.temporarilyRemoveItemFromInventory(W)) + return + + var/mob/living/silicon/robot/O = new /mob/living/silicon/robot/nocell(get_turf(loc)) + if(!O) + return + if(M.laws && M.laws.id != DEFAULT_AI_LAWID) + aisync = 0 + lawsync = 0 + O.laws = M.laws + M.laws.associate(O) + + O.invisibility = 0 + //Transfer debug settings to new mob + O.custom_name = created_name + O.locked = panel_locked + if(!aisync) + lawsync = 0 + O.connected_ai = null + else + O.notify_ai(NEW_BORG) + if(forced_ai) + O.connected_ai = forced_ai + if(!lawsync) + O.lawupdate = 0 + if(M.laws.id == DEFAULT_AI_LAWID) + O.make_laws() + + SSticker.mode.remove_antag_for_borging(B.mind) + O.job = "Cyborg" + + O.cell = chest.cell + chest.cell.forceMove(O) + chest.cell = null + W.forceMove(O)//Should fix cybros run time erroring when blown up. It got deleted before, along with the frame. + if(O.mmi) //we delete the mmi created by robot/New() + qdel(O.mmi) + O.mmi = W //and give the real mmi to the borg. + + O.updatename(B.client) + + B.mind.transfer_to(O) + + if(O.mind && O.mind.special_role) + O.mind.store_memory("As a cyborg, you must obey your silicon laws and master AI above all else. Your objectives will consider you to be dead.") + to_chat(O, "You have been robotized!") + to_chat(O, "You must obey your silicon laws and master AI above all else. Your objectives will consider you to be dead.") + + SSblackbox.record_feedback("amount", "cyborg_birth", 1) + forceMove(O) + O.robot_suit = src + + log_game("[key_name(user)] has put the MMI/posibrain of [key_name(M.brainmob)] into a cyborg shell at [AREACOORD(src)]") + + if(!locomotion) + O.lockcharge = TRUE + O.update_mobility() + to_chat(O, "Error: Servo motors unresponsive.") + + else + to_chat(user, "The MMI must go in after everything else!") + + else if(istype(W, /obj/item/borg/upgrade/ai)) + var/obj/item/borg/upgrade/ai/M = W + if(check_completion()) + if(!isturf(loc)) + to_chat(user, "You cannot install[M], the frame has to be standing on the ground to be perfectly precise!") + return + if(!user.temporarilyRemoveItemFromInventory(M)) + to_chat(user, "[M] is stuck to your hand!") + return + qdel(M) + var/mob/living/silicon/robot/O = new /mob/living/silicon/robot/shell(get_turf(src)) + + if(!aisync) + lawsync = FALSE + O.connected_ai = null + else + if(forced_ai) + O.connected_ai = forced_ai + O.notify_ai(AI_SHELL) + if(!lawsync) + O.lawupdate = FALSE + O.make_laws() + + O.cell = chest.cell + chest.cell.forceMove(O) + chest.cell = null + O.locked = panel_locked + O.job = "Cyborg" + forceMove(O) + O.robot_suit = src + if(!locomotion) + O.lockcharge = TRUE + O.update_mobility() + + else if(istype(W, /obj/item/pen)) + to_chat(user, "You need to use a multitool to name [src]!") + else + return ..() + +/obj/item/robot_suit/proc/Interact(mob/user) + var/t1 = "Designation: [(created_name ? "[created_name]" : "Default Cyborg")]
                        \n" + t1 += "Master AI: [(forced_ai ? "[forced_ai.name]" : "Automatic")]

                        \n" + + t1 += "LawSync Port: [(lawsync ? "Open" : "Closed")]
                        \n" + t1 += "AI Connection Port: [(aisync ? "Open" : "Closed")]
                        \n" + t1 += "Servo Motor Functions: [(locomotion ? "Unlocked" : "Locked")]
                        \n" + t1 += "Panel Lock: [(panel_locked ? "Engaged" : "Disengaged")]
                        \n" + var/datum/browser/popup = new(user, "robotdebug", "Cyborg Boot Debug", 310, 220) + popup.set_content(t1) + popup.open() + +/obj/item/robot_suit/Topic(href, href_list) + if(usr.incapacitated() || !Adjacent(usr)) + return + + var/mob/living/living_user = usr + var/obj/item/item_in_hand = living_user.get_active_held_item() + if(!item_in_hand || item_in_hand.tool_behaviour != TOOL_MULTITOOL) + to_chat(living_user, "You need a multitool!") + return + + if(href_list["Name"]) + var/new_name = reject_bad_name(input(usr, "Enter new designation. Set to blank to reset to default.", "Cyborg Debug", src.created_name),1) + if(!in_range(src, usr) && src.loc != usr) + return + if(new_name) + created_name = new_name + else + created_name = "" + + else if(href_list["Master"]) + forced_ai = select_active_ai(usr, z) + if(!forced_ai) + to_chat(usr, "No active AIs detected.") + + else if(href_list["Law"]) + lawsync = !lawsync + else if(href_list["AI"]) + aisync = !aisync + else if(href_list["Loco"]) + locomotion = !locomotion + else if(href_list["Panel"]) + panel_locked = !panel_locked + + add_fingerprint(usr) + Interact(usr) diff --git a/code/game/objects/items/robot/robot_upgrades.dm b/code/game/objects/items/robot/robot_upgrades.dm index 6e86b79dd6e0..99aa832d2fc8 100644 --- a/code/game/objects/items/robot/robot_upgrades.dm +++ b/code/game/objects/items/robot/robot_upgrades.dm @@ -1,681 +1,681 @@ -// robot_upgrades.dm -// Contains various borg upgrades. - -/obj/item/borg/upgrade - name = "borg upgrade module." - desc = "Protected by FRM." - icon = 'icons/obj/module.dmi' - icon_state = "cyborg_upgrade" - w_class = WEIGHT_CLASS_SMALL - var/locked = FALSE - var/installed = 0 - var/require_module = 0 - var/list/module_type = null - // if true, is not stored in the robot to be ejected - // if module is reset - var/one_use = FALSE - -/obj/item/borg/upgrade/proc/action(mob/living/silicon/robot/R, user = usr) - if(R.stat == DEAD) - to_chat(user, "[src] will not function on a deceased cyborg!") - return FALSE - if(module_type && !is_type_in_list(R.module, module_type)) - to_chat(R, "Upgrade mounting error! No suitable hardpoint detected.") - to_chat(user, "There's no mounting point for the module!") - return FALSE - return TRUE - -/obj/item/borg/upgrade/proc/deactivate(mob/living/silicon/robot/R, user = usr) - if (!(src in R.upgrades)) - return FALSE - return TRUE - -/obj/item/borg/upgrade/rename - name = "cyborg reclassification board" - desc = "Used to rename a cyborg." - icon_state = "cyborg_upgrade1" - var/heldname = "" - one_use = TRUE - -/obj/item/borg/upgrade/rename/attack_self(mob/user) - heldname = sanitize_name(stripped_input(user, "Enter new robot name", "Cyborg Reclassification", heldname, MAX_NAME_LEN)) - -/obj/item/borg/upgrade/rename/action(mob/living/silicon/robot/R) - . = ..() - if(.) - var/oldname = R.real_name - R.custom_name = heldname - R.updatename() - if(oldname == R.real_name) - R.notify_ai(RENAME, oldname, R.real_name) - -/obj/item/borg/upgrade/restart - name = "cyborg emergency reboot module" - desc = "Used to force a reboot of a disabled-but-repaired cyborg, bringing it back online." - icon_state = "cyborg_upgrade1" - one_use = TRUE - -/obj/item/borg/upgrade/restart/action(mob/living/silicon/robot/R, user = usr) - if(R.health < 0) - to_chat(user, "You have to repair the cyborg before using this module!") - return FALSE - - if(R.mind) - R.mind.grab_ghost() - playsound(loc, 'sound/voice/liveagain.ogg', 75, TRUE) - - R.revive(full_heal = FALSE, admin_revive = FALSE) - -/obj/item/borg/upgrade/disablercooler - name = "cyborg rapid disabler cooling module" - desc = "Used to cool a mounted disabler, increasing the potential current in it and thus its recharge rate." - icon_state = "cyborg_upgrade3" - require_module = 1 - module_type = list(/obj/item/robot_module/security) - -/obj/item/borg/upgrade/disablercooler/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - var/obj/item/gun/energy/disabler/cyborg/T = locate() in R.module.modules - if(!T) - to_chat(user, "There's no disabler in this unit!") - return FALSE - if(T.charge_delay <= 2) - to_chat(R, "A cooling unit is already installed!") - to_chat(user, "There's no room for another cooling unit!") - return FALSE - - T.charge_delay = max(2 , T.charge_delay - 4) - -/obj/item/borg/upgrade/disablercooler/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - var/obj/item/gun/energy/disabler/cyborg/T = locate() in R.module.modules - if(!T) - return FALSE - T.charge_delay = initial(T.charge_delay) - -/obj/item/borg/upgrade/thrusters - name = "ion thruster upgrade" - desc = "An energy-operated thruster system for cyborgs." - icon_state = "cyborg_upgrade3" - -/obj/item/borg/upgrade/thrusters/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - if(R.ionpulse) - to_chat(user, "This unit already has ion thrusters installed!") - return FALSE - - R.ionpulse = TRUE - -/obj/item/borg/upgrade/thrusters/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - R.ionpulse = FALSE - -/obj/item/borg/upgrade/ddrill - name = "mining cyborg diamond drill" - desc = "A diamond drill replacement for the mining module's standard drill." - icon_state = "cyborg_upgrade3" - require_module = 1 - module_type = list(/obj/item/robot_module/miner) - -/obj/item/borg/upgrade/ddrill/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - for(var/obj/item/pickaxe/drill/cyborg/D in R.module) - R.module.remove_module(D, TRUE) - for(var/obj/item/shovel/S in R.module) - R.module.remove_module(S, TRUE) - - var/obj/item/pickaxe/drill/cyborg/diamond/DD = new /obj/item/pickaxe/drill/cyborg/diamond(R.module) - R.module.basic_modules += DD - R.module.add_module(DD, FALSE, TRUE) - -/obj/item/borg/upgrade/ddrill/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - for(var/obj/item/pickaxe/drill/cyborg/diamond/DD in R.module) - R.module.remove_module(DD, TRUE) - - var/obj/item/pickaxe/drill/cyborg/D = new (R.module) - R.module.basic_modules += D - R.module.add_module(D, FALSE, TRUE) - var/obj/item/shovel/S = new (R.module) - R.module.basic_modules += S - R.module.add_module(S, FALSE, TRUE) - -/obj/item/borg/upgrade/soh - name = "mining cyborg satchel of holding" - desc = "A satchel of holding replacement for mining cyborg's ore satchel module." - icon_state = "cyborg_upgrade3" - require_module = 1 - module_type = list(/obj/item/robot_module/miner) - -/obj/item/borg/upgrade/soh/action(mob/living/silicon/robot/R) - . = ..() - if(.) - for(var/obj/item/storage/bag/ore/cyborg/S in R.module) - R.module.remove_module(S, TRUE) - - var/obj/item/storage/bag/ore/holding/H = new /obj/item/storage/bag/ore/holding(R.module) - R.module.basic_modules += H - R.module.add_module(H, FALSE, TRUE) - -/obj/item/borg/upgrade/soh/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - for(var/obj/item/storage/bag/ore/holding/H in R.module) - R.module.remove_module(H, TRUE) - - var/obj/item/storage/bag/ore/cyborg/S = new (R.module) - R.module.basic_modules += S - R.module.add_module(S, FALSE, TRUE) - -/obj/item/borg/upgrade/tboh - name = "janitor cyborg trash bag of holding" - desc = "A trash bag of holding replacement for the janiborg's standard trash bag." - icon_state = "cyborg_upgrade3" - require_module = 1 - module_type = list(/obj/item/robot_module/janitor) - -/obj/item/borg/upgrade/tboh/action(mob/living/silicon/robot/R) - . = ..() - if(.) - for(var/obj/item/storage/bag/trash/cyborg/TB in R.module.modules) - R.module.remove_module(TB, TRUE) - - var/obj/item/storage/bag/trash/bluespace/cyborg/B = new /obj/item/storage/bag/trash/bluespace/cyborg(R.module) - R.module.basic_modules += B - R.module.add_module(B, FALSE, TRUE) - -/obj/item/borg/upgrade/tboh/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - for(var/obj/item/storage/bag/trash/bluespace/cyborg/B in R.module.modules) - R.module.remove_module(B, TRUE) - - var/obj/item/storage/bag/trash/cyborg/TB = new (R.module) - R.module.basic_modules += TB - R.module.add_module(TB, FALSE, TRUE) - -/obj/item/borg/upgrade/amop - name = "janitor cyborg advanced mop" - desc = "An advanced mop replacement for the janiborg's standard mop." - icon_state = "cyborg_upgrade3" - require_module = 1 - module_type = list(/obj/item/robot_module/janitor) - -/obj/item/borg/upgrade/amop/action(mob/living/silicon/robot/R) - . = ..() - if(.) - for(var/obj/item/mop/cyborg/M in R.module.modules) - R.module.remove_module(M, TRUE) - - var/obj/item/mop/advanced/cyborg/A = new /obj/item/mop/advanced/cyborg(R.module) - R.module.basic_modules += A - R.module.add_module(A, FALSE, TRUE) - -/obj/item/borg/upgrade/amop/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - for(var/obj/item/mop/advanced/cyborg/A in R.module.modules) - R.module.remove_module(A, TRUE) - - var/obj/item/mop/cyborg/M = new (R.module) - R.module.basic_modules += M - R.module.add_module(M, FALSE, TRUE) - -/obj/item/borg/upgrade/syndicate - name = "illegal equipment module" - desc = "Unlocks the hidden, deadlier functions of a cyborg." - icon_state = "cyborg_upgrade3" - require_module = 1 - -/obj/item/borg/upgrade/syndicate/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - if(R.emagged) - return FALSE - - R.SetEmagged(1) - - return TRUE - -/obj/item/borg/upgrade/syndicate/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - R.SetEmagged(FALSE) - -/obj/item/borg/upgrade/lavaproof - name = "mining cyborg lavaproof chassis" - desc = "An upgrade kit to apply specialized coolant systems and insulation layers to a mining cyborg's chassis, enabling them to withstand exposure to molten rock." - icon_state = "ash_plating" - resistance_flags = LAVA_PROOF | FIRE_PROOF - require_module = 1 - module_type = list(/obj/item/robot_module/miner) - -/obj/item/borg/upgrade/lavaproof/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - R.weather_immunities += "lava" - -/obj/item/borg/upgrade/lavaproof/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - R.weather_immunities -= "lava" - -/obj/item/borg/upgrade/selfrepair - name = "self-repair module" - desc = "This module will repair the cyborg over time." - icon_state = "cyborg_upgrade5" - require_module = 1 - var/repair_amount = -1 - var/repair_tick = 1 - var/msg_cooldown = 0 - var/on = FALSE - var/powercost = 10 - var/datum/action/toggle_action - -/obj/item/borg/upgrade/selfrepair/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - var/obj/item/borg/upgrade/selfrepair/U = locate() in R - if(U) - to_chat(user, "This unit is already equipped with a self-repair module!") - return FALSE - - icon_state = "selfrepair_off" - toggle_action = new /datum/action/item_action/toggle(src) - toggle_action.Grant(R) - -/obj/item/borg/upgrade/selfrepair/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - toggle_action.Remove(R) - QDEL_NULL(toggle_action) - deactivate_sr() - -/obj/item/borg/upgrade/selfrepair/ui_action_click() - if(on) - to_chat(toggle_action.owner, "You deactivate the self-repair module.") - deactivate_sr() - else - to_chat(toggle_action.owner, "You activate the self-repair module.") - activate_sr() - - -/obj/item/borg/upgrade/selfrepair/update_icon_state() - if(toggle_action) - icon_state = "selfrepair_[on ? "on" : "off"]" - else - icon_state = "cyborg_upgrade5" - -/obj/item/borg/upgrade/selfrepair/proc/activate_sr() - START_PROCESSING(SSobj, src) - on = TRUE - update_icon() - -/obj/item/borg/upgrade/selfrepair/proc/deactivate_sr() - STOP_PROCESSING(SSobj, src) - on = FALSE - update_icon() - -/obj/item/borg/upgrade/selfrepair/process() - if(!repair_tick) - repair_tick = 1 - return - - var/mob/living/silicon/robot/cyborg = toggle_action.owner - - if(istype(cyborg) && (cyborg.stat != DEAD) && on) - if(!cyborg.cell) - to_chat(cyborg, "Self-repair module deactivated. Please insert power cell.") - deactivate_sr() - return - - if(cyborg.cell.charge < powercost * 2) - to_chat(cyborg, "Self-repair module deactivated. Please recharge.") - deactivate_sr() - return - - if(cyborg.health < cyborg.maxHealth) - if(cyborg.health < 0) - repair_amount = -2.5 - powercost = 30 - else - repair_amount = -1 - powercost = 10 - cyborg.adjustBruteLoss(repair_amount) - cyborg.adjustFireLoss(repair_amount) - cyborg.updatehealth() - cyborg.cell.use(powercost) - else - cyborg.cell.use(5) - repair_tick = 0 - - if((world.time - 2000) > msg_cooldown ) - var/msgmode = "standby" - if(cyborg.health < 0) - msgmode = "critical" - else if(cyborg.health < cyborg.maxHealth) - msgmode = "normal" - to_chat(cyborg, "Self-repair is active in [msgmode] mode.") - msg_cooldown = world.time - else - deactivate_sr() - -/obj/item/borg/upgrade/hypospray - name = "medical cyborg hypospray advanced synthesiser" - desc = "An upgrade to the Medical module cyborg's hypospray, allowing it \ - to produce more advanced and complex medical reagents." - icon_state = "cyborg_upgrade3" - require_module = 1 - module_type = list(/obj/item/robot_module/medical) - var/list/additional_reagents = list() - -/obj/item/borg/upgrade/hypospray/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - for(var/obj/item/reagent_containers/borghypo/H in R.module.modules) - if(H.accepts_reagent_upgrades) - for(var/re in additional_reagents) - H.add_reagent(re) - -/obj/item/borg/upgrade/hypospray/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - for(var/obj/item/reagent_containers/borghypo/H in R.module.modules) - if(H.accepts_reagent_upgrades) - for(var/re in additional_reagents) - H.del_reagent(re) - -/obj/item/borg/upgrade/hypospray/expanded - name = "medical cyborg expanded hypospray" - desc = "An upgrade to the Medical module's hypospray, allowing it \ - to treat a wider range of conditions and problems." - additional_reagents = list(/datum/reagent/medicine/mannitol, /datum/reagent/medicine/oculine, /datum/reagent/medicine/inacusiate, - /datum/reagent/medicine/mutadone, /datum/reagent/medicine/haloperidol, /datum/reagent/medicine/oxandrolone, /datum/reagent/medicine/sal_acid, /datum/reagent/medicine/rezadone, - /datum/reagent/medicine/pen_acid) - -/obj/item/borg/upgrade/piercing_hypospray - name = "cyborg piercing hypospray" - desc = "An upgrade to a cyborg's hypospray, allowing it to \ - pierce armor and thick material." - icon_state = "cyborg_upgrade3" - -/obj/item/borg/upgrade/piercing_hypospray/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - var/found_hypo = FALSE - for(var/obj/item/reagent_containers/borghypo/H in R.module.modules) - H.bypass_protection = TRUE - found_hypo = TRUE - - if(!found_hypo) - return FALSE - -/obj/item/borg/upgrade/piercing_hypospray/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - for(var/obj/item/reagent_containers/borghypo/H in R.module.modules) - H.bypass_protection = initial(H.bypass_protection) - -/obj/item/borg/upgrade/defib - name = "medical cyborg defibrillator" - desc = "An upgrade to the Medical module, installing a built-in \ - defibrillator, for on the scene revival." - icon_state = "cyborg_upgrade3" - require_module = 1 - module_type = list(/obj/item/robot_module/medical) - var/backpack = FALSE //True if we get the defib from a physical backpack unit rather than an upgrade card, so that we can return that upon deactivate() - -/obj/item/borg/upgrade/defib/backpack - backpack = TRUE - -/obj/item/borg/upgrade/defib/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - var/obj/item/borg/upgrade/defib/backpack/BP = locate() in R //If a full defib unit was used to upgrade prior, we can just pop it out now and replace - if(BP) - BP.deactivate(R, user) - to_chat(user, "You remove the defibrillator unit to make room for the compact upgrade.") - var/obj/item/shockpaddles/cyborg/S = new(R.module) - R.module.basic_modules += S - R.module.add_module(S, FALSE, TRUE) - -/obj/item/borg/upgrade/defib/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - var/obj/item/shockpaddles/cyborg/S = locate() in R.module - R.module.remove_module(S, TRUE) - if(backpack) - new /obj/item/defibrillator(get_turf(R)) - qdel(src) - - -/obj/item/borg/upgrade/processor - name = "medical cyborg surgical processor" - desc = "An upgrade to the Medical module, installing a processor \ - capable of scanning surgery disks and carrying \ - out procedures" - icon_state = "cyborg_upgrade3" - require_module = 1 - module_type = list(/obj/item/robot_module/medical, /obj/item/robot_module/syndicate_medical) - -/obj/item/borg/upgrade/processor/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - var/obj/item/surgical_processor/SP = new(R.module) - R.module.basic_modules += SP - R.module.add_module(SP, FALSE, TRUE) - -/obj/item/borg/upgrade/processor/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - var/obj/item/surgical_processor/SP = locate() in R.module - R.module.remove_module(SP, TRUE) - -/obj/item/borg/upgrade/ai - name = "B.O.R.I.S. module" - desc = "Bluespace Optimized Remote Intelligence Synchronization. An uplink device which takes the place of an MMI in cyborg endoskeletons, creating a robotic shell controlled by an AI." - icon_state = "boris" - -/obj/item/borg/upgrade/ai/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - if(R.shell) - to_chat(user, "This unit is already an AI shell!") - return FALSE - if(R.key) //You cannot replace a player unless the key is completely removed. - to_chat(user, "Intelligence patterns detected in this [R.braintype]. Aborting.") - return FALSE - - R.make_shell(src) - -/obj/item/borg/upgrade/ai/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - if(R.shell) - R.undeploy() - R.notify_ai(DISCONNECT) - -/obj/item/borg/upgrade/expand - name = "borg expander" - desc = "A cyborg resizer, it makes a cyborg huge." - icon_state = "cyborg_upgrade3" - -/obj/item/borg/upgrade/expand/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - - if(R.hasExpanded) - to_chat(usr, "This unit already has an expand module installed!") - return FALSE - - R.notransform = TRUE - var/prev_lockcharge = R.lockcharge - R.SetLockdown(1) - R.anchored = TRUE - var/datum/effect_system/smoke_spread/smoke = new - smoke.set_up(1, R.loc) - smoke.start() - sleep(2) - for(var/i in 1 to 4) - playsound(R, pick('sound/items/drill_use.ogg', 'sound/items/jaws_cut.ogg', 'sound/items/jaws_pry.ogg', 'sound/items/welder.ogg', 'sound/items/ratchet.ogg'), 80, TRUE, -1) - sleep(12) - if(!prev_lockcharge) - R.SetLockdown(0) - R.anchored = FALSE - R.notransform = FALSE - R.resize = 2 - R.hasExpanded = TRUE - R.update_transform() - -/obj/item/borg/upgrade/expand/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - if (R.hasExpanded) - R.hasExpanded = FALSE - R.resize = 0.5 - R.update_transform() - -/obj/item/borg/upgrade/rped - name = "engineering cyborg RPED" - desc = "A rapid part exchange device for the engineering cyborg." - icon = 'icons/obj/storage.dmi' - icon_state = "borgrped" - require_module = TRUE - module_type = list(/obj/item/robot_module/engineering, /obj/item/robot_module/saboteur) - -/obj/item/borg/upgrade/rped/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - - var/obj/item/storage/part_replacer/cyborg/RPED = locate() in R - if(RPED) - to_chat(user, "This unit is already equipped with a RPED module!") - return FALSE - - RPED = new(R.module) - R.module.basic_modules += RPED - R.module.add_module(RPED, FALSE, TRUE) - -/obj/item/borg/upgrade/rped/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - var/obj/item/storage/part_replacer/cyborg/RPED = locate() in R.module - if (RPED) - R.module.remove_module(RPED, TRUE) - -/obj/item/borg/upgrade/pinpointer - name = "medical cyborg crew pinpointer" - desc = "A crew pinpointer module for the medical cyborg. Permits remote access to the crew monitor." - icon = 'icons/obj/device.dmi' - icon_state = "pinpointer_crew" - require_module = TRUE - module_type = list(/obj/item/robot_module/medical, /obj/item/robot_module/syndicate_medical) - var/datum/action/crew_monitor - -/obj/item/borg/upgrade/pinpointer/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - - var/obj/item/pinpointer/crew/PP = locate() in R.module - if(PP) - to_chat(user, "This unit is already equipped with a pinpointer module!") - return FALSE - - PP = new(R.module) - R.module.basic_modules += PP - R.module.add_module(PP, FALSE, TRUE) - crew_monitor = new /datum/action/item_action/crew_monitor(src) - crew_monitor.Grant(R) - icon_state = "scanner" - - -/obj/item/borg/upgrade/pinpointer/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - icon_state = "pinpointer_crew" - crew_monitor.Remove(R) - QDEL_NULL(crew_monitor) - var/obj/item/pinpointer/crew/PP = locate() in R.module - R.module.remove_module(PP, TRUE) - -/obj/item/borg/upgrade/pinpointer/ui_action_click() - if(..()) - return - var/mob/living/silicon/robot/Cyborg = usr - GLOB.crewmonitor.show(Cyborg,Cyborg) - - -/obj/item/borg/upgrade/transform - name = "borg module picker (Standard)" - desc = "Allows you to to turn a cyborg into a standard cyborg." - icon_state = "cyborg_upgrade3" - var/obj/item/robot_module/new_module = /obj/item/robot_module/standard - -/obj/item/borg/upgrade/transform/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - R.module.transform_to(new_module) - -/obj/item/borg/upgrade/transform/clown - name = "borg module picker (Clown)" - desc = "Allows you to to turn a cyborg into a clown, honk." - icon_state = "cyborg_upgrade3" - new_module = /obj/item/robot_module/clown - -/obj/item/borg/upgrade/circuit_app - name = "circuit manipulation apparatus" - desc = "An engineering cyborg upgrade allowing for manipulation of circuit boards." - icon_state = "cyborg_upgrade3" - require_module = TRUE - module_type = list(/obj/item/robot_module/engineering, /obj/item/robot_module/saboteur) - -/obj/item/borg/upgrade/circuit_app/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - var/obj/item/borg/apparatus/circuit/C = locate() in R.module.modules - if(C) - to_chat(user, "This unit is already equipped with a circuit apparatus!") - return FALSE - - C = new(R.module) - R.module.basic_modules += C - R.module.add_module(C, FALSE, TRUE) - -/obj/item/borg/upgrade/circuit_app/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - var/obj/item/borg/apparatus/circuit/C = locate() in R.module.modules - if (C) - R.module.remove_module(C, TRUE) - -/obj/item/borg/upgrade/beaker_app - name = "beaker storage apparatus" - desc = "A supplementary beaker storage apparatus for medical cyborgs." - icon_state = "cyborg_upgrade3" - require_module = TRUE - module_type = list(/obj/item/robot_module/medical) - -/obj/item/borg/upgrade/beaker_app/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - var/obj/item/borg/apparatus/beaker/extra/E = locate() in R.module.modules - if(E) - to_chat(user, "This unit has no room for additional beaker storage!") - return FALSE - - E = new(R.module) - R.module.basic_modules += E - R.module.add_module(E, FALSE, TRUE) - -/obj/item/borg/upgrade/beaker_app/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - var/obj/item/borg/apparatus/beaker/extra/E = locate() in R.module.modules - if (E) - R.module.remove_module(E, TRUE) +// robot_upgrades.dm +// Contains various borg upgrades. + +/obj/item/borg/upgrade + name = "borg upgrade module." + desc = "Protected by FRM." + icon = 'icons/obj/module.dmi' + icon_state = "cyborg_upgrade" + w_class = WEIGHT_CLASS_SMALL + var/locked = FALSE + var/installed = 0 + var/require_module = 0 + var/list/module_type = null + // if true, is not stored in the robot to be ejected + // if module is reset + var/one_use = FALSE + +/obj/item/borg/upgrade/proc/action(mob/living/silicon/robot/R, user = usr) + if(R.stat == DEAD) + to_chat(user, "[src] will not function on a deceased cyborg!") + return FALSE + if(module_type && !is_type_in_list(R.module, module_type)) + to_chat(R, "Upgrade mounting error! No suitable hardpoint detected.") + to_chat(user, "There's no mounting point for the module!") + return FALSE + return TRUE + +/obj/item/borg/upgrade/proc/deactivate(mob/living/silicon/robot/R, user = usr) + if (!(src in R.upgrades)) + return FALSE + return TRUE + +/obj/item/borg/upgrade/rename + name = "cyborg reclassification board" + desc = "Used to rename a cyborg." + icon_state = "cyborg_upgrade1" + var/heldname = "" + one_use = TRUE + +/obj/item/borg/upgrade/rename/attack_self(mob/user) + heldname = sanitize_name(stripped_input(user, "Enter new robot name", "Cyborg Reclassification", heldname, MAX_NAME_LEN)) + +/obj/item/borg/upgrade/rename/action(mob/living/silicon/robot/R) + . = ..() + if(.) + var/oldname = R.real_name + R.custom_name = heldname + R.updatename() + if(oldname == R.real_name) + R.notify_ai(RENAME, oldname, R.real_name) + +/obj/item/borg/upgrade/restart + name = "cyborg emergency reboot module" + desc = "Used to force a reboot of a disabled-but-repaired cyborg, bringing it back online." + icon_state = "cyborg_upgrade1" + one_use = TRUE + +/obj/item/borg/upgrade/restart/action(mob/living/silicon/robot/R, user = usr) + if(R.health < 0) + to_chat(user, "You have to repair the cyborg before using this module!") + return FALSE + + if(R.mind) + R.mind.grab_ghost() + playsound(loc, 'sound/voice/liveagain.ogg', 75, TRUE) + + R.revive(full_heal = FALSE, admin_revive = FALSE) + +/obj/item/borg/upgrade/disablercooler + name = "cyborg rapid disabler cooling module" + desc = "Used to cool a mounted disabler, increasing the potential current in it and thus its recharge rate." + icon_state = "cyborg_upgrade3" + require_module = 1 + module_type = list(/obj/item/robot_module/security) + +/obj/item/borg/upgrade/disablercooler/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + var/obj/item/gun/energy/disabler/cyborg/T = locate() in R.module.modules + if(!T) + to_chat(user, "There's no disabler in this unit!") + return FALSE + if(T.charge_delay <= 2) + to_chat(R, "A cooling unit is already installed!") + to_chat(user, "There's no room for another cooling unit!") + return FALSE + + T.charge_delay = max(2 , T.charge_delay - 4) + +/obj/item/borg/upgrade/disablercooler/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + var/obj/item/gun/energy/disabler/cyborg/T = locate() in R.module.modules + if(!T) + return FALSE + T.charge_delay = initial(T.charge_delay) + +/obj/item/borg/upgrade/thrusters + name = "ion thruster upgrade" + desc = "An energy-operated thruster system for cyborgs." + icon_state = "cyborg_upgrade3" + +/obj/item/borg/upgrade/thrusters/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + if(R.ionpulse) + to_chat(user, "This unit already has ion thrusters installed!") + return FALSE + + R.ionpulse = TRUE + +/obj/item/borg/upgrade/thrusters/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + R.ionpulse = FALSE + +/obj/item/borg/upgrade/ddrill + name = "mining cyborg diamond drill" + desc = "A diamond drill replacement for the mining module's standard drill." + icon_state = "cyborg_upgrade3" + require_module = 1 + module_type = list(/obj/item/robot_module/miner) + +/obj/item/borg/upgrade/ddrill/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + for(var/obj/item/pickaxe/drill/cyborg/D in R.module) + R.module.remove_module(D, TRUE) + for(var/obj/item/shovel/S in R.module) + R.module.remove_module(S, TRUE) + + var/obj/item/pickaxe/drill/cyborg/diamond/DD = new /obj/item/pickaxe/drill/cyborg/diamond(R.module) + R.module.basic_modules += DD + R.module.add_module(DD, FALSE, TRUE) + +/obj/item/borg/upgrade/ddrill/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + for(var/obj/item/pickaxe/drill/cyborg/diamond/DD in R.module) + R.module.remove_module(DD, TRUE) + + var/obj/item/pickaxe/drill/cyborg/D = new (R.module) + R.module.basic_modules += D + R.module.add_module(D, FALSE, TRUE) + var/obj/item/shovel/S = new (R.module) + R.module.basic_modules += S + R.module.add_module(S, FALSE, TRUE) + +/obj/item/borg/upgrade/soh + name = "mining cyborg satchel of holding" + desc = "A satchel of holding replacement for mining cyborg's ore satchel module." + icon_state = "cyborg_upgrade3" + require_module = 1 + module_type = list(/obj/item/robot_module/miner) + +/obj/item/borg/upgrade/soh/action(mob/living/silicon/robot/R) + . = ..() + if(.) + for(var/obj/item/storage/bag/ore/cyborg/S in R.module) + R.module.remove_module(S, TRUE) + + var/obj/item/storage/bag/ore/holding/H = new /obj/item/storage/bag/ore/holding(R.module) + R.module.basic_modules += H + R.module.add_module(H, FALSE, TRUE) + +/obj/item/borg/upgrade/soh/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + for(var/obj/item/storage/bag/ore/holding/H in R.module) + R.module.remove_module(H, TRUE) + + var/obj/item/storage/bag/ore/cyborg/S = new (R.module) + R.module.basic_modules += S + R.module.add_module(S, FALSE, TRUE) + +/obj/item/borg/upgrade/tboh + name = "janitor cyborg trash bag of holding" + desc = "A trash bag of holding replacement for the janiborg's standard trash bag." + icon_state = "cyborg_upgrade3" + require_module = 1 + module_type = list(/obj/item/robot_module/janitor) + +/obj/item/borg/upgrade/tboh/action(mob/living/silicon/robot/R) + . = ..() + if(.) + for(var/obj/item/storage/bag/trash/cyborg/TB in R.module.modules) + R.module.remove_module(TB, TRUE) + + var/obj/item/storage/bag/trash/bluespace/cyborg/B = new /obj/item/storage/bag/trash/bluespace/cyborg(R.module) + R.module.basic_modules += B + R.module.add_module(B, FALSE, TRUE) + +/obj/item/borg/upgrade/tboh/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + for(var/obj/item/storage/bag/trash/bluespace/cyborg/B in R.module.modules) + R.module.remove_module(B, TRUE) + + var/obj/item/storage/bag/trash/cyborg/TB = new (R.module) + R.module.basic_modules += TB + R.module.add_module(TB, FALSE, TRUE) + +/obj/item/borg/upgrade/amop + name = "janitor cyborg advanced mop" + desc = "An advanced mop replacement for the janiborg's standard mop." + icon_state = "cyborg_upgrade3" + require_module = 1 + module_type = list(/obj/item/robot_module/janitor) + +/obj/item/borg/upgrade/amop/action(mob/living/silicon/robot/R) + . = ..() + if(.) + for(var/obj/item/mop/cyborg/M in R.module.modules) + R.module.remove_module(M, TRUE) + + var/obj/item/mop/advanced/cyborg/A = new /obj/item/mop/advanced/cyborg(R.module) + R.module.basic_modules += A + R.module.add_module(A, FALSE, TRUE) + +/obj/item/borg/upgrade/amop/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + for(var/obj/item/mop/advanced/cyborg/A in R.module.modules) + R.module.remove_module(A, TRUE) + + var/obj/item/mop/cyborg/M = new (R.module) + R.module.basic_modules += M + R.module.add_module(M, FALSE, TRUE) + +/obj/item/borg/upgrade/syndicate + name = "illegal equipment module" + desc = "Unlocks the hidden, deadlier functions of a cyborg." + icon_state = "cyborg_upgrade3" + require_module = 1 + +/obj/item/borg/upgrade/syndicate/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + if(R.emagged) + return FALSE + + R.SetEmagged(1) + + return TRUE + +/obj/item/borg/upgrade/syndicate/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + R.SetEmagged(FALSE) + +/obj/item/borg/upgrade/lavaproof + name = "mining cyborg lavaproof chassis" + desc = "An upgrade kit to apply specialized coolant systems and insulation layers to a mining cyborg's chassis, enabling them to withstand exposure to molten rock." + icon_state = "ash_plating" + resistance_flags = LAVA_PROOF | FIRE_PROOF + require_module = 1 + module_type = list(/obj/item/robot_module/miner) + +/obj/item/borg/upgrade/lavaproof/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + R.weather_immunities += "lava" + +/obj/item/borg/upgrade/lavaproof/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + R.weather_immunities -= "lava" + +/obj/item/borg/upgrade/selfrepair + name = "self-repair module" + desc = "This module will repair the cyborg over time." + icon_state = "cyborg_upgrade5" + require_module = 1 + var/repair_amount = -1 + var/repair_tick = 1 + var/msg_cooldown = 0 + var/on = FALSE + var/powercost = 10 + var/datum/action/toggle_action + +/obj/item/borg/upgrade/selfrepair/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + var/obj/item/borg/upgrade/selfrepair/U = locate() in R + if(U) + to_chat(user, "This unit is already equipped with a self-repair module!") + return FALSE + + icon_state = "selfrepair_off" + toggle_action = new /datum/action/item_action/toggle(src) + toggle_action.Grant(R) + +/obj/item/borg/upgrade/selfrepair/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + toggle_action.Remove(R) + QDEL_NULL(toggle_action) + deactivate_sr() + +/obj/item/borg/upgrade/selfrepair/ui_action_click() + if(on) + to_chat(toggle_action.owner, "You deactivate the self-repair module.") + deactivate_sr() + else + to_chat(toggle_action.owner, "You activate the self-repair module.") + activate_sr() + + +/obj/item/borg/upgrade/selfrepair/update_icon_state() + if(toggle_action) + icon_state = "selfrepair_[on ? "on" : "off"]" + else + icon_state = "cyborg_upgrade5" + +/obj/item/borg/upgrade/selfrepair/proc/activate_sr() + START_PROCESSING(SSobj, src) + on = TRUE + update_icon() + +/obj/item/borg/upgrade/selfrepair/proc/deactivate_sr() + STOP_PROCESSING(SSobj, src) + on = FALSE + update_icon() + +/obj/item/borg/upgrade/selfrepair/process() + if(!repair_tick) + repair_tick = 1 + return + + var/mob/living/silicon/robot/cyborg = toggle_action.owner + + if(istype(cyborg) && (cyborg.stat != DEAD) && on) + if(!cyborg.cell) + to_chat(cyborg, "Self-repair module deactivated. Please insert power cell.") + deactivate_sr() + return + + if(cyborg.cell.charge < powercost * 2) + to_chat(cyborg, "Self-repair module deactivated. Please recharge.") + deactivate_sr() + return + + if(cyborg.health < cyborg.maxHealth) + if(cyborg.health < 0) + repair_amount = -2.5 + powercost = 30 + else + repair_amount = -1 + powercost = 10 + cyborg.adjustBruteLoss(repair_amount) + cyborg.adjustFireLoss(repair_amount) + cyborg.updatehealth() + cyborg.cell.use(powercost) + else + cyborg.cell.use(5) + repair_tick = 0 + + if((world.time - 2000) > msg_cooldown ) + var/msgmode = "standby" + if(cyborg.health < 0) + msgmode = "critical" + else if(cyborg.health < cyborg.maxHealth) + msgmode = "normal" + to_chat(cyborg, "Self-repair is active in [msgmode] mode.") + msg_cooldown = world.time + else + deactivate_sr() + +/obj/item/borg/upgrade/hypospray + name = "medical cyborg hypospray advanced synthesiser" + desc = "An upgrade to the Medical module cyborg's hypospray, allowing it \ + to produce more advanced and complex medical reagents." + icon_state = "cyborg_upgrade3" + require_module = 1 + module_type = list(/obj/item/robot_module/medical) + var/list/additional_reagents = list() + +/obj/item/borg/upgrade/hypospray/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + for(var/obj/item/reagent_containers/borghypo/H in R.module.modules) + if(H.accepts_reagent_upgrades) + for(var/re in additional_reagents) + H.add_reagent(re) + +/obj/item/borg/upgrade/hypospray/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + for(var/obj/item/reagent_containers/borghypo/H in R.module.modules) + if(H.accepts_reagent_upgrades) + for(var/re in additional_reagents) + H.del_reagent(re) + +/obj/item/borg/upgrade/hypospray/expanded + name = "medical cyborg expanded hypospray" + desc = "An upgrade to the Medical module's hypospray, allowing it \ + to treat a wider range of conditions and problems." + additional_reagents = list(/datum/reagent/medicine/mannitol, /datum/reagent/medicine/oculine, /datum/reagent/medicine/inacusiate, + /datum/reagent/medicine/mutadone, /datum/reagent/medicine/haloperidol, /datum/reagent/medicine/oxandrolone, /datum/reagent/medicine/sal_acid, /datum/reagent/medicine/rezadone, + /datum/reagent/medicine/pen_acid) + +/obj/item/borg/upgrade/piercing_hypospray + name = "cyborg piercing hypospray" + desc = "An upgrade to a cyborg's hypospray, allowing it to \ + pierce armor and thick material." + icon_state = "cyborg_upgrade3" + +/obj/item/borg/upgrade/piercing_hypospray/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + var/found_hypo = FALSE + for(var/obj/item/reagent_containers/borghypo/H in R.module.modules) + H.bypass_protection = TRUE + found_hypo = TRUE + + if(!found_hypo) + return FALSE + +/obj/item/borg/upgrade/piercing_hypospray/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + for(var/obj/item/reagent_containers/borghypo/H in R.module.modules) + H.bypass_protection = initial(H.bypass_protection) + +/obj/item/borg/upgrade/defib + name = "medical cyborg defibrillator" + desc = "An upgrade to the Medical module, installing a built-in \ + defibrillator, for on the scene revival." + icon_state = "cyborg_upgrade3" + require_module = 1 + module_type = list(/obj/item/robot_module/medical) + var/backpack = FALSE //True if we get the defib from a physical backpack unit rather than an upgrade card, so that we can return that upon deactivate() + +/obj/item/borg/upgrade/defib/backpack + backpack = TRUE + +/obj/item/borg/upgrade/defib/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + var/obj/item/borg/upgrade/defib/backpack/BP = locate() in R //If a full defib unit was used to upgrade prior, we can just pop it out now and replace + if(BP) + BP.deactivate(R, user) + to_chat(user, "You remove the defibrillator unit to make room for the compact upgrade.") + var/obj/item/shockpaddles/cyborg/S = new(R.module) + R.module.basic_modules += S + R.module.add_module(S, FALSE, TRUE) + +/obj/item/borg/upgrade/defib/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + var/obj/item/shockpaddles/cyborg/S = locate() in R.module + R.module.remove_module(S, TRUE) + if(backpack) + new /obj/item/defibrillator(get_turf(R)) + qdel(src) + + +/obj/item/borg/upgrade/processor + name = "medical cyborg surgical processor" + desc = "An upgrade to the Medical module, installing a processor \ + capable of scanning surgery disks and carrying \ + out procedures" + icon_state = "cyborg_upgrade3" + require_module = 1 + module_type = list(/obj/item/robot_module/medical, /obj/item/robot_module/syndicate_medical) + +/obj/item/borg/upgrade/processor/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + var/obj/item/surgical_processor/SP = new(R.module) + R.module.basic_modules += SP + R.module.add_module(SP, FALSE, TRUE) + +/obj/item/borg/upgrade/processor/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + var/obj/item/surgical_processor/SP = locate() in R.module + R.module.remove_module(SP, TRUE) + +/obj/item/borg/upgrade/ai + name = "B.O.R.I.S. module" + desc = "Bluespace Optimized Remote Intelligence Synchronization. An uplink device which takes the place of an MMI in cyborg endoskeletons, creating a robotic shell controlled by an AI." + icon_state = "boris" + +/obj/item/borg/upgrade/ai/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + if(R.shell) + to_chat(user, "This unit is already an AI shell!") + return FALSE + if(R.key) //You cannot replace a player unless the key is completely removed. + to_chat(user, "Intelligence patterns detected in this [R.braintype]. Aborting.") + return FALSE + + R.make_shell(src) + +/obj/item/borg/upgrade/ai/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + if(R.shell) + R.undeploy() + R.notify_ai(DISCONNECT) + +/obj/item/borg/upgrade/expand + name = "borg expander" + desc = "A cyborg resizer, it makes a cyborg huge." + icon_state = "cyborg_upgrade3" + +/obj/item/borg/upgrade/expand/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + + if(R.hasExpanded) + to_chat(usr, "This unit already has an expand module installed!") + return FALSE + + R.notransform = TRUE + var/prev_lockcharge = R.lockcharge + R.SetLockdown(1) + R.anchored = TRUE + var/datum/effect_system/smoke_spread/smoke = new + smoke.set_up(1, R.loc) + smoke.start() + sleep(2) + for(var/i in 1 to 4) + playsound(R, pick('sound/items/drill_use.ogg', 'sound/items/jaws_cut.ogg', 'sound/items/jaws_pry.ogg', 'sound/items/welder.ogg', 'sound/items/ratchet.ogg'), 80, TRUE, -1) + sleep(12) + if(!prev_lockcharge) + R.SetLockdown(0) + R.anchored = FALSE + R.notransform = FALSE + R.resize = 2 + R.hasExpanded = TRUE + R.update_transform() + +/obj/item/borg/upgrade/expand/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + if (R.hasExpanded) + R.hasExpanded = FALSE + R.resize = 0.5 + R.update_transform() + +/obj/item/borg/upgrade/rped + name = "engineering cyborg RPED" + desc = "A rapid part exchange device for the engineering cyborg." + icon = 'icons/obj/storage.dmi' + icon_state = "borgrped" + require_module = TRUE + module_type = list(/obj/item/robot_module/engineering, /obj/item/robot_module/saboteur) + +/obj/item/borg/upgrade/rped/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + + var/obj/item/storage/part_replacer/cyborg/RPED = locate() in R + if(RPED) + to_chat(user, "This unit is already equipped with a RPED module!") + return FALSE + + RPED = new(R.module) + R.module.basic_modules += RPED + R.module.add_module(RPED, FALSE, TRUE) + +/obj/item/borg/upgrade/rped/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + var/obj/item/storage/part_replacer/cyborg/RPED = locate() in R.module + if (RPED) + R.module.remove_module(RPED, TRUE) + +/obj/item/borg/upgrade/pinpointer + name = "medical cyborg crew pinpointer" + desc = "A crew pinpointer module for the medical cyborg. Permits remote access to the crew monitor." + icon = 'icons/obj/device.dmi' + icon_state = "pinpointer_crew" + require_module = TRUE + module_type = list(/obj/item/robot_module/medical, /obj/item/robot_module/syndicate_medical) + var/datum/action/crew_monitor + +/obj/item/borg/upgrade/pinpointer/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + + var/obj/item/pinpointer/crew/PP = locate() in R.module + if(PP) + to_chat(user, "This unit is already equipped with a pinpointer module!") + return FALSE + + PP = new(R.module) + R.module.basic_modules += PP + R.module.add_module(PP, FALSE, TRUE) + crew_monitor = new /datum/action/item_action/crew_monitor(src) + crew_monitor.Grant(R) + icon_state = "scanner" + + +/obj/item/borg/upgrade/pinpointer/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + icon_state = "pinpointer_crew" + crew_monitor.Remove(R) + QDEL_NULL(crew_monitor) + var/obj/item/pinpointer/crew/PP = locate() in R.module + R.module.remove_module(PP, TRUE) + +/obj/item/borg/upgrade/pinpointer/ui_action_click() + if(..()) + return + var/mob/living/silicon/robot/Cyborg = usr + GLOB.crewmonitor.show(Cyborg,Cyborg) + + +/obj/item/borg/upgrade/transform + name = "borg module picker (Standard)" + desc = "Allows you to to turn a cyborg into a standard cyborg." + icon_state = "cyborg_upgrade3" + var/obj/item/robot_module/new_module = /obj/item/robot_module/standard + +/obj/item/borg/upgrade/transform/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + R.module.transform_to(new_module) + +/obj/item/borg/upgrade/transform/clown + name = "borg module picker (Clown)" + desc = "Allows you to to turn a cyborg into a clown, honk." + icon_state = "cyborg_upgrade3" + new_module = /obj/item/robot_module/clown + +/obj/item/borg/upgrade/circuit_app + name = "circuit manipulation apparatus" + desc = "An engineering cyborg upgrade allowing for manipulation of circuit boards." + icon_state = "cyborg_upgrade3" + require_module = TRUE + module_type = list(/obj/item/robot_module/engineering, /obj/item/robot_module/saboteur) + +/obj/item/borg/upgrade/circuit_app/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + var/obj/item/borg/apparatus/circuit/C = locate() in R.module.modules + if(C) + to_chat(user, "This unit is already equipped with a circuit apparatus!") + return FALSE + + C = new(R.module) + R.module.basic_modules += C + R.module.add_module(C, FALSE, TRUE) + +/obj/item/borg/upgrade/circuit_app/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + var/obj/item/borg/apparatus/circuit/C = locate() in R.module.modules + if (C) + R.module.remove_module(C, TRUE) + +/obj/item/borg/upgrade/beaker_app + name = "beaker storage apparatus" + desc = "A supplementary beaker storage apparatus for medical cyborgs." + icon_state = "cyborg_upgrade3" + require_module = TRUE + module_type = list(/obj/item/robot_module/medical) + +/obj/item/borg/upgrade/beaker_app/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + var/obj/item/borg/apparatus/beaker/extra/E = locate() in R.module.modules + if(E) + to_chat(user, "This unit has no room for additional beaker storage!") + return FALSE + + E = new(R.module) + R.module.basic_modules += E + R.module.add_module(E, FALSE, TRUE) + +/obj/item/borg/upgrade/beaker_app/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + var/obj/item/borg/apparatus/beaker/extra/E = locate() in R.module.modules + if (E) + R.module.remove_module(E, TRUE) diff --git a/code/game/objects/items/scrolls.dm b/code/game/objects/items/scrolls.dm index 28a4664a2449..d58f670dc4e4 100644 --- a/code/game/objects/items/scrolls.dm +++ b/code/game/objects/items/scrolls.dm @@ -1,73 +1,73 @@ -/obj/item/teleportation_scroll - name = "scroll of teleportation" - desc = "A scroll for moving around." - icon = 'icons/obj/wizard.dmi' - icon_state = "scroll" - var/uses = 4 - w_class = WEIGHT_CLASS_SMALL - item_state = "paper" - throw_speed = 3 - throw_range = 7 - resistance_flags = FLAMMABLE - -/obj/item/teleportation_scroll/apprentice - name = "lesser scroll of teleportation" - uses = 1 - - - -/obj/item/teleportation_scroll/attack_self(mob/user) - user.set_machine(src) - var/dat = "Teleportation Scroll:
                        " - dat += "Number of uses: [src.uses]
                        " - dat += "
                        " - dat += "Four uses, use them wisely:
                        " - dat += "Teleport
                        " - dat += "Kind regards,
                        Wizards Federation

                        P.S. Don't forget to bring your gear, you'll need it to cast most spells.
                        " - user << browse(dat, "window=scroll") - onclose(user, "scroll") - return - -/obj/item/teleportation_scroll/Topic(href, href_list) - ..() - if (usr.stat || usr.restrained() || src.loc != usr) - return - if (!ishuman(usr)) - return 1 - var/mob/living/carbon/human/H = usr - if(H.is_holding(src)) - H.set_machine(src) - if (href_list["spell_teleport"]) - if(uses) - teleportscroll(H) - if(H) - attack_self(H) - return - -/obj/item/teleportation_scroll/proc/teleportscroll(mob/user) - - var/A - - A = input(user, "Area to jump to", "BOOYEA", A) as null|anything in GLOB.teleportlocs - if(!src || QDELETED(src) || !user || !user.is_holding(src) || user.incapacitated() || !A || !uses) - return - var/area/thearea = GLOB.teleportlocs[A] - - var/datum/effect_system/smoke_spread/smoke = new - smoke.set_up(2, user.loc) - smoke.attach(user) - smoke.start() - var/list/L = list() - for(var/turf/T in get_area_turfs(thearea.type)) - if(!is_blocked_turf(T)) - L += T - - if(!L.len) - to_chat(user, "The spell matrix was unable to locate a suitable teleport destination for an unknown reason. Sorry.") - return - - if(do_teleport(user, pick(L), forceMove = TRUE, channel = TELEPORT_CHANNEL_MAGIC, forced = TRUE)) - smoke.start() - uses-- - else - to_chat(user, "The spell matrix was disrupted by something near the destination.") +/obj/item/teleportation_scroll + name = "scroll of teleportation" + desc = "A scroll for moving around." + icon = 'icons/obj/wizard.dmi' + icon_state = "scroll" + var/uses = 4 + w_class = WEIGHT_CLASS_SMALL + item_state = "paper" + throw_speed = 3 + throw_range = 7 + resistance_flags = FLAMMABLE + +/obj/item/teleportation_scroll/apprentice + name = "lesser scroll of teleportation" + uses = 1 + + + +/obj/item/teleportation_scroll/attack_self(mob/user) + user.set_machine(src) + var/dat = "Teleportation Scroll:
                        " + dat += "Number of uses: [src.uses]
                        " + dat += "
                        " + dat += "Four uses, use them wisely:
                        " + dat += "Teleport
                        " + dat += "Kind regards,
                        Wizards Federation

                        P.S. Don't forget to bring your gear, you'll need it to cast most spells.
                        " + user << browse(dat, "window=scroll") + onclose(user, "scroll") + return + +/obj/item/teleportation_scroll/Topic(href, href_list) + ..() + if (usr.stat || usr.restrained() || src.loc != usr) + return + if (!ishuman(usr)) + return 1 + var/mob/living/carbon/human/H = usr + if(H.is_holding(src)) + H.set_machine(src) + if (href_list["spell_teleport"]) + if(uses) + teleportscroll(H) + if(H) + attack_self(H) + return + +/obj/item/teleportation_scroll/proc/teleportscroll(mob/user) + + var/A + + A = input(user, "Area to jump to", "BOOYEA", A) as null|anything in GLOB.teleportlocs + if(!src || QDELETED(src) || !user || !user.is_holding(src) || user.incapacitated() || !A || !uses) + return + var/area/thearea = GLOB.teleportlocs[A] + + var/datum/effect_system/smoke_spread/smoke = new + smoke.set_up(2, user.loc) + smoke.attach(user) + smoke.start() + var/list/L = list() + for(var/turf/T in get_area_turfs(thearea.type)) + if(!is_blocked_turf(T)) + L += T + + if(!L.len) + to_chat(user, "The spell matrix was unable to locate a suitable teleport destination for an unknown reason. Sorry.") + return + + if(do_teleport(user, pick(L), forceMove = TRUE, channel = TELEPORT_CHANNEL_MAGIC, forced = TRUE)) + smoke.start() + uses-- + else + to_chat(user, "The spell matrix was disrupted by something near the destination.") diff --git a/code/game/objects/items/shields.dm b/code/game/objects/items/shields.dm index d3685d31ee55..4aed102e12e3 100644 --- a/code/game/objects/items/shields.dm +++ b/code/game/objects/items/shields.dm @@ -1,279 +1,279 @@ -/obj/item/shield - name = "shield" - icon = 'icons/obj/shields.dmi' - block_chance = 50 - armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 70) - var/transparent = FALSE // makes beam projectiles pass through the shield - -/obj/item/shield/proc/on_shield_block(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", damage = 0, attack_type = MELEE_ATTACK) - return TRUE - -/obj/item/shield/riot - name = "riot shield" - desc = "A shield adept at blocking blunt objects from connecting with the torso of the shield wielder." - icon_state = "riot" - lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi' - slot_flags = ITEM_SLOT_BACK - force = 10 - throwforce = 5 - throw_speed = 2 - throw_range = 3 - w_class = WEIGHT_CLASS_BULKY - custom_materials = list(/datum/material/glass=7500, /datum/material/iron=1000) - attack_verb = list("shoved", "bashed") - var/cooldown = 0 //shield bash cooldown. based on world.time - transparent = TRUE - max_integrity = 75 - material_flags = MATERIAL_NO_EFFECTS - -/obj/item/shield/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(transparent && (hitby.pass_flags & PASSGLASS)) - return FALSE - if(attack_type == THROWN_PROJECTILE_ATTACK) - final_block_chance += 30 - if(attack_type == LEAP_ATTACK) - final_block_chance = 100 - . = ..() - if(.) - on_shield_block(owner, hitby, attack_text, damage, attack_type) - -/obj/item/shield/riot/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/melee/baton)) - if(cooldown < world.time - 25) - user.visible_message("[user] bashes [src] with [W]!") - playsound(user.loc, 'sound/effects/shieldbash.ogg', 50, TRUE) - cooldown = world.time - else if(istype(W, /obj/item/stack/sheet/mineral/titanium)) - if (obj_integrity >= max_integrity) - to_chat(user, "[src] is already in perfect condition.") - else - var/obj/item/stack/sheet/mineral/titanium/T = W - T.use(1) - obj_integrity = max_integrity - to_chat(user, "You repair [src] with [T].") - else - return ..() - -/obj/item/shield/riot/examine(mob/user) - . = ..() - var/healthpercent = round((obj_integrity/max_integrity) * 100, 1) - switch(healthpercent) - if(50 to 99) - . += "It looks slightly damaged." - if(25 to 50) - . += "It appears heavily damaged." - if(0 to 25) - . += "It's falling apart!" - -/obj/item/shield/riot/proc/shatter(mob/living/carbon/human/owner) - playsound(owner, 'sound/effects/glassbr3.ogg', 100) - new /obj/item/shard((get_turf(src))) - -/obj/item/shield/riot/on_shield_block(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", damage = 0, attack_type = MELEE_ATTACK) - if (obj_integrity <= damage) - var/turf/T = get_turf(owner) - T.visible_message("[hitby] destroys [src]!") - shatter(owner) - qdel(src) - return FALSE - take_damage(damage) - return ..() - -/obj/item/shield/riot/roman - name = "\improper Roman shield" - desc = "Bears an inscription on the inside: \"Romanes venio domus\"." - icon_state = "roman_shield" - item_state = "roman_shield" - lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi' - transparent = FALSE - custom_materials = list(/datum/material/iron=8500) - max_integrity = 65 - -/obj/item/shield/riot/roman/fake - desc = "Bears an inscription on the inside: \"Romanes venio domus\". It appears to be a bit flimsy." - block_chance = 0 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - max_integrity = 30 - -/obj/item/shield/riot/roman/shatter(mob/living/carbon/human/owner) - playsound(owner, 'sound/effects/grillehit.ogg', 100) - new /obj/item/stack/sheet/metal(get_turf(src)) - -/obj/item/shield/riot/buckler - name = "wooden buckler" - desc = "A medieval wooden buckler." - icon_state = "buckler" - item_state = "buckler" - lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi' - custom_materials = list(/datum/material/wood = MINERAL_MATERIAL_AMOUNT * 10) - resistance_flags = FLAMMABLE - block_chance = 30 - transparent = FALSE - max_integrity = 55 - w_class = WEIGHT_CLASS_NORMAL - -/obj/item/shield/riot/buckler/shatter(mob/living/carbon/human/owner) - playsound(owner, 'sound/effects/bang.ogg', 50) - new /obj/item/stack/sheet/mineral/wood(get_turf(src)) - -/obj/item/shield/riot/flash - name = "strobe shield" - desc = "A shield with a built in, high intensity light capable of blinding and disorienting suspects. Takes regular handheld flashes as bulbs." - icon_state = "flashshield" - item_state = "flashshield" - var/obj/item/assembly/flash/handheld/embedded_flash - -/obj/item/shield/riot/flash/Initialize() - . = ..() - embedded_flash = new(src) - -/obj/item/shield/riot/flash/ComponentInitialize() - . = .. () - AddElement(/datum/element/update_icon_updates_onmob) - -/obj/item/shield/riot/flash/attack(mob/living/M, mob/user) - . = embedded_flash.attack(M, user) - update_icon() - -/obj/item/shield/riot/flash/attack_self(mob/living/carbon/user) - . = embedded_flash.attack_self(user) - update_icon() - -/obj/item/shield/riot/flash/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - . = ..() - if (. && !embedded_flash.burnt_out) - embedded_flash.activate() - update_icon() - - -/obj/item/shield/riot/flash/attackby(obj/item/W, mob/user) - if(istype(W, /obj/item/assembly/flash/handheld)) - var/obj/item/assembly/flash/handheld/flash = W - if(flash.burnt_out) - to_chat(user, "No sense replacing it with a broken bulb!") - return - else - to_chat(user, "You begin to replace the bulb...") - if(do_after(user, 20, target = user)) - if(flash.burnt_out || !flash || QDELETED(flash)) - return - playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE) - qdel(embedded_flash) - embedded_flash = flash - flash.forceMove(src) - update_icon() - return - ..() - -/obj/item/shield/riot/flash/emp_act(severity) - . = ..() - embedded_flash.emp_act(severity) - update_icon() - -/obj/item/shield/riot/flash/update_icon_state() - if(!embedded_flash || embedded_flash.burnt_out) - icon_state = "riot" - item_state = "riot" - else - icon_state = "flashshield" - item_state = "flashshield" - -/obj/item/shield/riot/flash/examine(mob/user) - . = ..() - if (embedded_flash?.burnt_out) - . += "The mounted bulb has burnt out. You can try replacing it with a new one." - -/obj/item/shield/energy - name = "energy combat shield" - desc = "A shield that reflects almost all energy projectiles, but is useless against physical attacks. It can be retracted, expanded, and stored anywhere." - lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi' - w_class = WEIGHT_CLASS_TINY - attack_verb = list("shoved", "bashed") - throw_range = 5 - force = 3 - throwforce = 3 - throw_speed = 3 - var/base_icon_state = "eshield" // [base_icon_state]1 for expanded, [base_icon_state]0 for contracted - var/on_force = 10 - var/on_throwforce = 8 - var/on_throw_speed = 2 - var/active = 0 - var/clumsy_check = TRUE - -/obj/item/shield/energy/Initialize() - . = ..() - icon_state = "[base_icon_state]0" - -/obj/item/shield/energy/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - return 0 - -/obj/item/shield/energy/IsReflect() - return (active) - -/obj/item/shield/energy/attack_self(mob/living/carbon/human/user) - if(clumsy_check && HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50)) - to_chat(user, "You beat yourself in the head with [src]!") - user.take_bodypart_damage(5) - active = !active - icon_state = "[base_icon_state][active]" - - if(active) - force = on_force - throwforce = on_throwforce - throw_speed = on_throw_speed - w_class = WEIGHT_CLASS_BULKY - playsound(user, 'sound/weapons/saberon.ogg', 35, TRUE) - to_chat(user, "[src] is now active.") - else - force = initial(force) - throwforce = initial(throwforce) - throw_speed = initial(throw_speed) - w_class = WEIGHT_CLASS_TINY - playsound(user, 'sound/weapons/saberoff.ogg', 35, TRUE) - to_chat(user, "[src] can now be concealed.") - add_fingerprint(user) - -/obj/item/shield/riot/tele - name = "telescopic shield" - desc = "An advanced riot shield made of lightweight materials that collapses for easy storage." - icon_state = "teleriot0" - lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi' - custom_materials = list(/datum/material/iron = 3600, /datum/material/glass = 3600, /datum/material/silver = 270, /datum/material/titanium = 180) - slot_flags = null - force = 3 - throwforce = 3 - throw_speed = 3 - throw_range = 4 - w_class = WEIGHT_CLASS_NORMAL - var/active = 0 - -/obj/item/shield/riot/tele/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(active) - return ..() - return 0 - -/obj/item/shield/riot/tele/attack_self(mob/living/user) - active = !active - icon_state = "teleriot[active]" - playsound(src.loc, 'sound/weapons/batonextend.ogg', 50, TRUE) - - if(active) - force = 8 - throwforce = 5 - throw_speed = 2 - w_class = WEIGHT_CLASS_BULKY - slot_flags = ITEM_SLOT_BACK - to_chat(user, "You extend \the [src].") - else - force = 3 - throwforce = 3 - throw_speed = 3 - w_class = WEIGHT_CLASS_NORMAL - slot_flags = null - to_chat(user, "[src] can now be concealed.") - add_fingerprint(user) +/obj/item/shield + name = "shield" + icon = 'icons/obj/shields.dmi' + block_chance = 50 + armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 70) + var/transparent = FALSE // makes beam projectiles pass through the shield + +/obj/item/shield/proc/on_shield_block(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", damage = 0, attack_type = MELEE_ATTACK) + return TRUE + +/obj/item/shield/riot + name = "riot shield" + desc = "A shield adept at blocking blunt objects from connecting with the torso of the shield wielder." + icon_state = "riot" + lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi' + slot_flags = ITEM_SLOT_BACK + force = 10 + throwforce = 5 + throw_speed = 2 + throw_range = 3 + w_class = WEIGHT_CLASS_BULKY + custom_materials = list(/datum/material/glass=7500, /datum/material/iron=1000) + attack_verb = list("shoved", "bashed") + var/cooldown = 0 //shield bash cooldown. based on world.time + transparent = TRUE + max_integrity = 75 + material_flags = MATERIAL_NO_EFFECTS + +/obj/item/shield/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + if(transparent && (hitby.pass_flags & PASSGLASS)) + return FALSE + if(attack_type == THROWN_PROJECTILE_ATTACK) + final_block_chance += 30 + if(attack_type == LEAP_ATTACK) + final_block_chance = 100 + . = ..() + if(.) + on_shield_block(owner, hitby, attack_text, damage, attack_type) + +/obj/item/shield/riot/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/melee/baton)) + if(cooldown < world.time - 25) + user.visible_message("[user] bashes [src] with [W]!") + playsound(user.loc, 'sound/effects/shieldbash.ogg', 50, TRUE) + cooldown = world.time + else if(istype(W, /obj/item/stack/sheet/mineral/titanium)) + if (obj_integrity >= max_integrity) + to_chat(user, "[src] is already in perfect condition.") + else + var/obj/item/stack/sheet/mineral/titanium/T = W + T.use(1) + obj_integrity = max_integrity + to_chat(user, "You repair [src] with [T].") + else + return ..() + +/obj/item/shield/riot/examine(mob/user) + . = ..() + var/healthpercent = round((obj_integrity/max_integrity) * 100, 1) + switch(healthpercent) + if(50 to 99) + . += "It looks slightly damaged." + if(25 to 50) + . += "It appears heavily damaged." + if(0 to 25) + . += "It's falling apart!" + +/obj/item/shield/riot/proc/shatter(mob/living/carbon/human/owner) + playsound(owner, 'sound/effects/glassbr3.ogg', 100) + new /obj/item/shard((get_turf(src))) + +/obj/item/shield/riot/on_shield_block(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", damage = 0, attack_type = MELEE_ATTACK) + if (obj_integrity <= damage) + var/turf/T = get_turf(owner) + T.visible_message("[hitby] destroys [src]!") + shatter(owner) + qdel(src) + return FALSE + take_damage(damage) + return ..() + +/obj/item/shield/riot/roman + name = "\improper Roman shield" + desc = "Bears an inscription on the inside: \"Romanes venio domus\"." + icon_state = "roman_shield" + item_state = "roman_shield" + lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi' + transparent = FALSE + custom_materials = list(/datum/material/iron=8500) + max_integrity = 65 + +/obj/item/shield/riot/roman/fake + desc = "Bears an inscription on the inside: \"Romanes venio domus\". It appears to be a bit flimsy." + block_chance = 0 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + max_integrity = 30 + +/obj/item/shield/riot/roman/shatter(mob/living/carbon/human/owner) + playsound(owner, 'sound/effects/grillehit.ogg', 100) + new /obj/item/stack/sheet/metal(get_turf(src)) + +/obj/item/shield/riot/buckler + name = "wooden buckler" + desc = "A medieval wooden buckler." + icon_state = "buckler" + item_state = "buckler" + lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi' + custom_materials = list(/datum/material/wood = MINERAL_MATERIAL_AMOUNT * 10) + resistance_flags = FLAMMABLE + block_chance = 30 + transparent = FALSE + max_integrity = 55 + w_class = WEIGHT_CLASS_NORMAL + +/obj/item/shield/riot/buckler/shatter(mob/living/carbon/human/owner) + playsound(owner, 'sound/effects/bang.ogg', 50) + new /obj/item/stack/sheet/mineral/wood(get_turf(src)) + +/obj/item/shield/riot/flash + name = "strobe shield" + desc = "A shield with a built in, high intensity light capable of blinding and disorienting suspects. Takes regular handheld flashes as bulbs." + icon_state = "flashshield" + item_state = "flashshield" + var/obj/item/assembly/flash/handheld/embedded_flash + +/obj/item/shield/riot/flash/Initialize() + . = ..() + embedded_flash = new(src) + +/obj/item/shield/riot/flash/ComponentInitialize() + . = .. () + AddElement(/datum/element/update_icon_updates_onmob) + +/obj/item/shield/riot/flash/attack(mob/living/M, mob/user) + . = embedded_flash.attack(M, user) + update_icon() + +/obj/item/shield/riot/flash/attack_self(mob/living/carbon/user) + . = embedded_flash.attack_self(user) + update_icon() + +/obj/item/shield/riot/flash/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + . = ..() + if (. && !embedded_flash.burnt_out) + embedded_flash.activate() + update_icon() + + +/obj/item/shield/riot/flash/attackby(obj/item/W, mob/user) + if(istype(W, /obj/item/assembly/flash/handheld)) + var/obj/item/assembly/flash/handheld/flash = W + if(flash.burnt_out) + to_chat(user, "No sense replacing it with a broken bulb!") + return + else + to_chat(user, "You begin to replace the bulb...") + if(do_after(user, 20, target = user)) + if(flash.burnt_out || !flash || QDELETED(flash)) + return + playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE) + qdel(embedded_flash) + embedded_flash = flash + flash.forceMove(src) + update_icon() + return + ..() + +/obj/item/shield/riot/flash/emp_act(severity) + . = ..() + embedded_flash.emp_act(severity) + update_icon() + +/obj/item/shield/riot/flash/update_icon_state() + if(!embedded_flash || embedded_flash.burnt_out) + icon_state = "riot" + item_state = "riot" + else + icon_state = "flashshield" + item_state = "flashshield" + +/obj/item/shield/riot/flash/examine(mob/user) + . = ..() + if (embedded_flash?.burnt_out) + . += "The mounted bulb has burnt out. You can try replacing it with a new one." + +/obj/item/shield/energy + name = "energy combat shield" + desc = "A shield that reflects almost all energy projectiles, but is useless against physical attacks. It can be retracted, expanded, and stored anywhere." + lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi' + w_class = WEIGHT_CLASS_TINY + attack_verb = list("shoved", "bashed") + throw_range = 5 + force = 3 + throwforce = 3 + throw_speed = 3 + var/base_icon_state = "eshield" // [base_icon_state]1 for expanded, [base_icon_state]0 for contracted + var/on_force = 10 + var/on_throwforce = 8 + var/on_throw_speed = 2 + var/active = 0 + var/clumsy_check = TRUE + +/obj/item/shield/energy/Initialize() + . = ..() + icon_state = "[base_icon_state]0" + +/obj/item/shield/energy/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + return 0 + +/obj/item/shield/energy/IsReflect() + return (active) + +/obj/item/shield/energy/attack_self(mob/living/carbon/human/user) + if(clumsy_check && HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50)) + to_chat(user, "You beat yourself in the head with [src]!") + user.take_bodypart_damage(5) + active = !active + icon_state = "[base_icon_state][active]" + + if(active) + force = on_force + throwforce = on_throwforce + throw_speed = on_throw_speed + w_class = WEIGHT_CLASS_BULKY + playsound(user, 'sound/weapons/saberon.ogg', 35, TRUE) + to_chat(user, "[src] is now active.") + else + force = initial(force) + throwforce = initial(throwforce) + throw_speed = initial(throw_speed) + w_class = WEIGHT_CLASS_TINY + playsound(user, 'sound/weapons/saberoff.ogg', 35, TRUE) + to_chat(user, "[src] can now be concealed.") + add_fingerprint(user) + +/obj/item/shield/riot/tele + name = "telescopic shield" + desc = "An advanced riot shield made of lightweight materials that collapses for easy storage." + icon_state = "teleriot0" + lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi' + custom_materials = list(/datum/material/iron = 3600, /datum/material/glass = 3600, /datum/material/silver = 270, /datum/material/titanium = 180) + slot_flags = null + force = 3 + throwforce = 3 + throw_speed = 3 + throw_range = 4 + w_class = WEIGHT_CLASS_NORMAL + var/active = 0 + +/obj/item/shield/riot/tele/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + if(active) + return ..() + return 0 + +/obj/item/shield/riot/tele/attack_self(mob/living/user) + active = !active + icon_state = "teleriot[active]" + playsound(src.loc, 'sound/weapons/batonextend.ogg', 50, TRUE) + + if(active) + force = 8 + throwforce = 5 + throw_speed = 2 + w_class = WEIGHT_CLASS_BULKY + slot_flags = ITEM_SLOT_BACK + to_chat(user, "You extend \the [src].") + else + force = 3 + throwforce = 3 + throw_speed = 3 + w_class = WEIGHT_CLASS_NORMAL + slot_flags = null + to_chat(user, "[src] can now be concealed.") + add_fingerprint(user) diff --git a/code/game/objects/items/shooting_range.dm b/code/game/objects/items/shooting_range.dm index 412e7df34140..51bd0ab42551 100644 --- a/code/game/objects/items/shooting_range.dm +++ b/code/game/objects/items/shooting_range.dm @@ -1,97 +1,97 @@ -/obj/item/target - name = "shooting target" - desc = "A shooting target." - icon = 'icons/obj/objects.dmi' - icon_state = "target_h" - density = FALSE - var/hp = 1800 - var/obj/structure/target_stake/pinnedLoc - -/obj/item/target/Destroy() - removeOverlays() - if(pinnedLoc) - pinnedLoc.nullPinnedTarget() - return ..() - -/obj/item/target/proc/nullPinnedLoc() - pinnedLoc = null - density = FALSE - -/obj/item/target/proc/removeOverlays() - cut_overlays() - -/obj/item/target/Move() - . = ..() - if(pinnedLoc) - pinnedLoc.forceMove(loc) - -/obj/item/target/welder_act(mob/living/user, obj/item/I) - ..() - if(I.use_tool(src, user, 0, volume=40)) - removeOverlays() - to_chat(user, "You slice off [src]'s uneven chunks of aluminium and scorch marks.") - return TRUE - -/obj/item/target/attack_hand(mob/user) - . = ..() - if(.) - return - if(pinnedLoc) - pinnedLoc.removeTarget(user) - -/obj/item/target/syndicate - icon_state = "target_s" - desc = "A shooting target that looks like syndicate scum." - hp = 2600 - -/obj/item/target/alien - icon_state = "target_q" - desc = "A shooting target that looks like a xenomorphic alien." - hp = 2350 - -/obj/item/target/alien/anchored - anchored = TRUE - -/obj/item/target/clown - icon_state = "target_c" - desc = "A shooting target that looks like a useless clown." - hp = 2000 - -#define DECALTYPE_SCORCH 1 -#define DECALTYPE_BULLET 2 - -/obj/item/target/clown/bullet_act(obj/projectile/P) - . = ..() - playsound(src.loc, 'sound/items/bikehorn.ogg', 50, TRUE) - -/obj/item/target/bullet_act(obj/projectile/P) - if(istype(P, /obj/projectile/bullet/reusable)) // If it's a foam dart, don't bother with any of this other shit - return P.on_hit(src, 0) - var/p_x = P.p_x + pick(0,0,0,0,0,-1,1) // really ugly way of coding "sometimes offset P.p_x!" - var/p_y = P.p_y + pick(0,0,0,0,0,-1,1) - var/decaltype = DECALTYPE_SCORCH - if(istype(P, /obj/projectile/bullet)) - decaltype = DECALTYPE_BULLET - var/icon/C = icon(icon,icon_state) - if(C.GetPixel(p_x, p_y) && P.original == src && overlays.len <= 35) // if the located pixel isn't blank (null) - hp -= P.damage - if(hp <= 0) - visible_message("[src] breaks into tiny pieces and collapses!") - qdel(src) - var/image/bullet_hole = image('icons/effects/effects.dmi', "scorch", OBJ_LAYER + 0.5) - bullet_hole.pixel_x = p_x - 1 //offset correction - bullet_hole.pixel_y = p_y - 1 - if(decaltype == DECALTYPE_SCORCH) - bullet_hole.setDir(pick(NORTH,SOUTH,EAST,WEST))// random scorch design - if(P.damage >= 20 || istype(P, /obj/projectile/beam/practice)) - bullet_hole.setDir(pick(NORTH,SOUTH,EAST,WEST)) - else - bullet_hole.icon_state = "light_scorch" - else - bullet_hole.icon_state = "dent" - add_overlay(bullet_hole) - return BULLET_ACT_HIT - return BULLET_ACT_FORCE_PIERCE - -#undef DECALTYPE_SCORCH -#undef DECALTYPE_BULLET +/obj/item/target + name = "shooting target" + desc = "A shooting target." + icon = 'icons/obj/objects.dmi' + icon_state = "target_h" + density = FALSE + var/hp = 1800 + var/obj/structure/target_stake/pinnedLoc + +/obj/item/target/Destroy() + removeOverlays() + if(pinnedLoc) + pinnedLoc.nullPinnedTarget() + return ..() + +/obj/item/target/proc/nullPinnedLoc() + pinnedLoc = null + density = FALSE + +/obj/item/target/proc/removeOverlays() + cut_overlays() + +/obj/item/target/Move() + . = ..() + if(pinnedLoc) + pinnedLoc.forceMove(loc) + +/obj/item/target/welder_act(mob/living/user, obj/item/I) + ..() + if(I.use_tool(src, user, 0, volume=40)) + removeOverlays() + to_chat(user, "You slice off [src]'s uneven chunks of aluminium and scorch marks.") + return TRUE + +/obj/item/target/attack_hand(mob/user) + . = ..() + if(.) + return + if(pinnedLoc) + pinnedLoc.removeTarget(user) + +/obj/item/target/syndicate + icon_state = "target_s" + desc = "A shooting target that looks like syndicate scum." + hp = 2600 + +/obj/item/target/alien + icon_state = "target_q" + desc = "A shooting target that looks like a xenomorphic alien." + hp = 2350 + +/obj/item/target/alien/anchored + anchored = TRUE + +/obj/item/target/clown + icon_state = "target_c" + desc = "A shooting target that looks like a useless clown." + hp = 2000 + +#define DECALTYPE_SCORCH 1 +#define DECALTYPE_BULLET 2 + +/obj/item/target/clown/bullet_act(obj/projectile/P) + . = ..() + playsound(src.loc, 'sound/items/bikehorn.ogg', 50, TRUE) + +/obj/item/target/bullet_act(obj/projectile/P) + if(istype(P, /obj/projectile/bullet/reusable)) // If it's a foam dart, don't bother with any of this other shit + return P.on_hit(src, 0) + var/p_x = P.p_x + pick(0,0,0,0,0,-1,1) // really ugly way of coding "sometimes offset P.p_x!" + var/p_y = P.p_y + pick(0,0,0,0,0,-1,1) + var/decaltype = DECALTYPE_SCORCH + if(istype(P, /obj/projectile/bullet)) + decaltype = DECALTYPE_BULLET + var/icon/C = icon(icon,icon_state) + if(C.GetPixel(p_x, p_y) && P.original == src && overlays.len <= 35) // if the located pixel isn't blank (null) + hp -= P.damage + if(hp <= 0) + visible_message("[src] breaks into tiny pieces and collapses!") + qdel(src) + var/image/bullet_hole = image('icons/effects/effects.dmi', "scorch", OBJ_LAYER + 0.5) + bullet_hole.pixel_x = p_x - 1 //offset correction + bullet_hole.pixel_y = p_y - 1 + if(decaltype == DECALTYPE_SCORCH) + bullet_hole.setDir(pick(NORTH,SOUTH,EAST,WEST))// random scorch design + if(P.damage >= 20 || istype(P, /obj/projectile/beam/practice)) + bullet_hole.setDir(pick(NORTH,SOUTH,EAST,WEST)) + else + bullet_hole.icon_state = "light_scorch" + else + bullet_hole.icon_state = "dent" + add_overlay(bullet_hole) + return BULLET_ACT_HIT + return BULLET_ACT_FORCE_PIERCE + +#undef DECALTYPE_SCORCH +#undef DECALTYPE_BULLET diff --git a/code/game/objects/items/singularityhammer.dm b/code/game/objects/items/singularityhammer.dm index 7cea1ff502c8..1464181c01df 100644 --- a/code/game/objects/items/singularityhammer.dm +++ b/code/game/objects/items/singularityhammer.dm @@ -1,138 +1,138 @@ -/obj/item/singularityhammer - name = "singularity hammer" - desc = "The pinnacle of close combat technology, the hammer harnesses the power of a miniaturized singularity to deal crushing blows." - icon_state = "mjollnir0" - lefthand_file = 'icons/mob/inhands/weapons/hammers_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/hammers_righthand.dmi' - color = "#212121" - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BACK - force = 5 - throwforce = 15 - throw_range = 1 - w_class = WEIGHT_CLASS_HUGE - armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 0, "bomb" = 50, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - resistance_flags = FIRE_PROOF | ACID_PROOF - force_string = "LORD SINGULOTH HIMSELF" - var/charged = 5 - var/wielded = FALSE // track wielded status on item - -/obj/item/singularityhammer/Initialize() - . = ..() - RegisterSignal(src, COMSIG_TWOHANDED_WIELD, .proc/on_wield) - RegisterSignal(src, COMSIG_TWOHANDED_UNWIELD, .proc/on_unwield) - START_PROCESSING(SSobj, src) - -/obj/item/singularityhammer/ComponentInitialize() - . = ..() - AddComponent(/datum/component/two_handed, force_multiplier=4, icon_wielded="mjollnir1") - -/// triggered on wield of two handed item -/obj/item/singularityhammer/proc/on_wield(obj/item/source, mob/user) - wielded = TRUE - -/// triggered on unwield of two handed item -/obj/item/singularityhammer/proc/on_unwield(obj/item/source, mob/user) - wielded = FALSE - -/obj/item/singularityhammer/update_icon_state() - icon_state = "mjollnir0" - -/obj/item/singularityhammer/Destroy() - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/item/singularityhammer/process() - if(charged < 5) - charged++ - -/obj/item/singularityhammer/proc/vortex(turf/pull, mob/wielder) - for(var/atom/X in orange(5,pull)) - if(ismovable(X)) - var/atom/movable/A = X - if(A == wielder) - continue - if(A && !A.anchored && !ishuman(X) && !isobserver(X)) - step_towards(A,pull) - step_towards(A,pull) - step_towards(A,pull) - else if(ishuman(X)) - var/mob/living/carbon/human/H = X - if(istype(H.shoes, /obj/item/clothing/shoes/magboots)) - var/obj/item/clothing/shoes/magboots/M = H.shoes - if(M.magpulse) - continue - H.apply_effect(20, EFFECT_PARALYZE, 0) - step_towards(H,pull) - step_towards(H,pull) - step_towards(H,pull) - -/obj/item/singularityhammer/afterattack(atom/A as mob|obj|turf|area, mob/user, proximity) - . = ..() - if(!proximity) - return - if(wielded) - if(charged == 5) - charged = 0 - if(istype(A, /mob/living/)) - var/mob/living/Z = A - Z.take_bodypart_damage(20,0) - playsound(user, 'sound/weapons/marauder.ogg', 50, TRUE) - var/turf/target = get_turf(A) - vortex(target,user) - -/obj/item/mjollnir - name = "Mjolnir" - desc = "A weapon worthy of a god, able to strike with the force of a lightning bolt. It crackles with barely contained energy." - icon_state = "mjollnir0" - lefthand_file = 'icons/mob/inhands/weapons/hammers_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/hammers_righthand.dmi' - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BACK - force = 5 - throwforce = 30 - throw_range = 7 - w_class = WEIGHT_CLASS_HUGE - var/wielded = FALSE // track wielded status on item - -/obj/item/mjollnir/Initialize() - . = ..() - RegisterSignal(src, COMSIG_TWOHANDED_WIELD, .proc/on_wield) - RegisterSignal(src, COMSIG_TWOHANDED_UNWIELD, .proc/on_unwield) - -/obj/item/mjollnir/ComponentInitialize() - . = ..() - AddComponent(/datum/component/two_handed, force_multiplier=5, icon_wielded="mjollnir1", attacksound="sparks") - -/// triggered on wield of two handed item -/obj/item/mjollnir/proc/on_wield(obj/item/source, mob/user) - wielded = TRUE - -/// triggered on unwield of two handed item -/obj/item/mjollnir/proc/on_unwield(obj/item/source, mob/user) - wielded = FALSE - -/obj/item/mjollnir/update_icon_state() - icon_state = "mjollnir0" - -/obj/item/mjollnir/proc/shock(mob/living/target) - target.Stun(60) - var/datum/effect_system/lightning_spread/s = new /datum/effect_system/lightning_spread - s.set_up(5, 1, target.loc) - s.start() - target.visible_message("[target.name] is shocked by [src]!", \ - "You feel a powerful shock course through your body sending you flying!", \ - "You hear a heavy electrical crack!") - var/atom/throw_target = get_edge_target_turf(target, get_dir(src, get_step_away(target, src))) - target.throw_at(throw_target, 200, 4) - return - -/obj/item/mjollnir/attack(mob/living/M, mob/user) - ..() - if(wielded) - shock(M) - -/obj/item/mjollnir/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - . = ..() - if(isliving(hit_atom)) - shock(hit_atom) +/obj/item/singularityhammer + name = "singularity hammer" + desc = "The pinnacle of close combat technology, the hammer harnesses the power of a miniaturized singularity to deal crushing blows." + icon_state = "mjollnir0" + lefthand_file = 'icons/mob/inhands/weapons/hammers_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/hammers_righthand.dmi' + color = "#212121" + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BACK + force = 5 + throwforce = 15 + throw_range = 1 + w_class = WEIGHT_CLASS_HUGE + armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 0, "bomb" = 50, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + resistance_flags = FIRE_PROOF | ACID_PROOF + force_string = "LORD SINGULOTH HIMSELF" + var/charged = 5 + var/wielded = FALSE // track wielded status on item + +/obj/item/singularityhammer/Initialize() + . = ..() + RegisterSignal(src, COMSIG_TWOHANDED_WIELD, .proc/on_wield) + RegisterSignal(src, COMSIG_TWOHANDED_UNWIELD, .proc/on_unwield) + START_PROCESSING(SSobj, src) + +/obj/item/singularityhammer/ComponentInitialize() + . = ..() + AddComponent(/datum/component/two_handed, force_multiplier=4, icon_wielded="mjollnir1") + +/// triggered on wield of two handed item +/obj/item/singularityhammer/proc/on_wield(obj/item/source, mob/user) + wielded = TRUE + +/// triggered on unwield of two handed item +/obj/item/singularityhammer/proc/on_unwield(obj/item/source, mob/user) + wielded = FALSE + +/obj/item/singularityhammer/update_icon_state() + icon_state = "mjollnir0" + +/obj/item/singularityhammer/Destroy() + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/singularityhammer/process() + if(charged < 5) + charged++ + +/obj/item/singularityhammer/proc/vortex(turf/pull, mob/wielder) + for(var/atom/X in orange(5,pull)) + if(ismovable(X)) + var/atom/movable/A = X + if(A == wielder) + continue + if(A && !A.anchored && !ishuman(X) && !isobserver(X)) + step_towards(A,pull) + step_towards(A,pull) + step_towards(A,pull) + else if(ishuman(X)) + var/mob/living/carbon/human/H = X + if(istype(H.shoes, /obj/item/clothing/shoes/magboots)) + var/obj/item/clothing/shoes/magboots/M = H.shoes + if(M.magpulse) + continue + H.apply_effect(20, EFFECT_PARALYZE, 0) + step_towards(H,pull) + step_towards(H,pull) + step_towards(H,pull) + +/obj/item/singularityhammer/afterattack(atom/A as mob|obj|turf|area, mob/user, proximity) + . = ..() + if(!proximity) + return + if(wielded) + if(charged == 5) + charged = 0 + if(istype(A, /mob/living/)) + var/mob/living/Z = A + Z.take_bodypart_damage(20,0) + playsound(user, 'sound/weapons/marauder.ogg', 50, TRUE) + var/turf/target = get_turf(A) + vortex(target,user) + +/obj/item/mjollnir + name = "Mjolnir" + desc = "A weapon worthy of a god, able to strike with the force of a lightning bolt. It crackles with barely contained energy." + icon_state = "mjollnir0" + lefthand_file = 'icons/mob/inhands/weapons/hammers_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/hammers_righthand.dmi' + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BACK + force = 5 + throwforce = 30 + throw_range = 7 + w_class = WEIGHT_CLASS_HUGE + var/wielded = FALSE // track wielded status on item + +/obj/item/mjollnir/Initialize() + . = ..() + RegisterSignal(src, COMSIG_TWOHANDED_WIELD, .proc/on_wield) + RegisterSignal(src, COMSIG_TWOHANDED_UNWIELD, .proc/on_unwield) + +/obj/item/mjollnir/ComponentInitialize() + . = ..() + AddComponent(/datum/component/two_handed, force_multiplier=5, icon_wielded="mjollnir1", attacksound="sparks") + +/// triggered on wield of two handed item +/obj/item/mjollnir/proc/on_wield(obj/item/source, mob/user) + wielded = TRUE + +/// triggered on unwield of two handed item +/obj/item/mjollnir/proc/on_unwield(obj/item/source, mob/user) + wielded = FALSE + +/obj/item/mjollnir/update_icon_state() + icon_state = "mjollnir0" + +/obj/item/mjollnir/proc/shock(mob/living/target) + target.Stun(60) + var/datum/effect_system/lightning_spread/s = new /datum/effect_system/lightning_spread + s.set_up(5, 1, target.loc) + s.start() + target.visible_message("[target.name] is shocked by [src]!", \ + "You feel a powerful shock course through your body sending you flying!", \ + "You hear a heavy electrical crack!") + var/atom/throw_target = get_edge_target_turf(target, get_dir(src, get_step_away(target, src))) + target.throw_at(throw_target, 200, 4) + return + +/obj/item/mjollnir/attack(mob/living/M, mob/user) + ..() + if(wielded) + shock(M) + +/obj/item/mjollnir/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + . = ..() + if(isliving(hit_atom)) + shock(hit_atom) diff --git a/code/game/objects/items/stacks/bscrystal.dm b/code/game/objects/items/stacks/bscrystal.dm index 52daa47679e3..f3656222e1a7 100644 --- a/code/game/objects/items/stacks/bscrystal.dm +++ b/code/game/objects/items/stacks/bscrystal.dm @@ -1,100 +1,100 @@ -//Bluespace crystals, used in telescience and when crushed it will blink you to a random turf. -/obj/item/stack/ore/bluespace_crystal - name = "bluespace crystal" - desc = "A glowing bluespace crystal, not much is known about how they work. It looks very delicate." - icon = 'icons/obj/telescience.dmi' - icon_state = "bluespace_crystal" - singular_name = "bluespace crystal" - dye_color = DYE_COSMIC - w_class = WEIGHT_CLASS_TINY - custom_materials = list(/datum/material/bluespace=MINERAL_MATERIAL_AMOUNT) - points = 50 - var/blink_range = 8 // The teleport range when crushed/thrown at someone. - refined_type = /obj/item/stack/sheet/bluespace_crystal - grind_results = list(/datum/reagent/bluespace = 20) - scan_state = "rock_BScrystal" - -/obj/item/stack/ore/bluespace_crystal/refined - name = "refined bluespace crystal" - points = 0 - refined_type = null - -/obj/item/stack/ore/bluespace_crystal/Initialize() - . = ..() - pixel_x = rand(-5, 5) - pixel_y = rand(-5, 5) - -/obj/item/stack/ore/bluespace_crystal/get_part_rating() - return 1 - -/obj/item/stack/ore/bluespace_crystal/attack_self(mob/user) - user.visible_message("[user] crushes [src]!", "You crush [src]!") - new /obj/effect/particle_effect/sparks(loc) - playsound(loc, "sparks", 50, TRUE) - blink_mob(user) - use(1) - -/obj/item/stack/ore/bluespace_crystal/proc/blink_mob(mob/living/L) - do_teleport(L, get_turf(L), blink_range, asoundin = 'sound/effects/phasein.ogg', channel = TELEPORT_CHANNEL_BLUESPACE) - -/obj/item/stack/ore/bluespace_crystal/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - if(!..()) // not caught in mid-air - visible_message("[src] fizzles and disappears upon impact!") - var/turf/T = get_turf(hit_atom) - new /obj/effect/particle_effect/sparks(T) - playsound(loc, "sparks", 50, TRUE) - if(isliving(hit_atom)) - blink_mob(hit_atom) - use(1) - -//Artificial bluespace crystal, doesn't give you much research. -/obj/item/stack/ore/bluespace_crystal/artificial - name = "artificial bluespace crystal" - desc = "An artificially made bluespace crystal, it looks delicate." - custom_materials = list(/datum/material/bluespace=MINERAL_MATERIAL_AMOUNT*0.5) - blink_range = 4 // Not as good as the organic stuff! - points = 0 //nice try - refined_type = null - grind_results = list(/datum/reagent/bluespace = 10, /datum/reagent/silicon = 20) - -//Polycrystals, aka stacks -/obj/item/stack/sheet/bluespace_crystal - name = "bluespace polycrystal" - icon = 'icons/obj/telescience.dmi' - icon_state = "polycrystal" - item_state = "sheet-polycrystal" - singular_name = "bluespace polycrystal" - desc = "A stable polycrystal, made of fused-together bluespace crystals. You could probably break one off." - custom_materials = list(/datum/material/bluespace=MINERAL_MATERIAL_AMOUNT) - attack_verb = list("bluespace polybashed", "bluespace polybattered", "bluespace polybludgeoned", "bluespace polythrashed", "bluespace polysmashed") - novariants = TRUE - grind_results = list(/datum/reagent/bluespace = 20) - point_value = 30 - var/crystal_type = /obj/item/stack/ore/bluespace_crystal/refined - -/obj/item/stack/sheet/bluespace_crystal/fifty - amount = 50 - -/obj/item/stack/sheet/bluespace_crystal/twenty - amount = 20 - -/obj/item/stack/sheet/bluespace_crystal/five - amount = 5 - -/obj/item/stack/sheet/bluespace_crystal/attack_self(mob/user)// to prevent the construction menu from ever happening - to_chat(user, "You cannot crush the polycrystal in-hand, try breaking one off.") - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/item/stack/sheet/bluespace_crystal/attack_hand(mob/user) - if(user.get_inactive_held_item() == src) - if(zero_amount()) - return - var/BC = new crystal_type(src) - user.put_in_hands(BC) - use(1) - if(!amount) - to_chat(user, "You break the final crystal off.") - else - to_chat(user, "You break off a crystal.") - else - ..() +//Bluespace crystals, used in telescience and when crushed it will blink you to a random turf. +/obj/item/stack/ore/bluespace_crystal + name = "bluespace crystal" + desc = "A glowing bluespace crystal, not much is known about how they work. It looks very delicate." + icon = 'icons/obj/telescience.dmi' + icon_state = "bluespace_crystal" + singular_name = "bluespace crystal" + dye_color = DYE_COSMIC + w_class = WEIGHT_CLASS_TINY + custom_materials = list(/datum/material/bluespace=MINERAL_MATERIAL_AMOUNT) + points = 50 + var/blink_range = 8 // The teleport range when crushed/thrown at someone. + refined_type = /obj/item/stack/sheet/bluespace_crystal + grind_results = list(/datum/reagent/bluespace = 20) + scan_state = "rock_BScrystal" + +/obj/item/stack/ore/bluespace_crystal/refined + name = "refined bluespace crystal" + points = 0 + refined_type = null + +/obj/item/stack/ore/bluespace_crystal/Initialize() + . = ..() + pixel_x = rand(-5, 5) + pixel_y = rand(-5, 5) + +/obj/item/stack/ore/bluespace_crystal/get_part_rating() + return 1 + +/obj/item/stack/ore/bluespace_crystal/attack_self(mob/user) + user.visible_message("[user] crushes [src]!", "You crush [src]!") + new /obj/effect/particle_effect/sparks(loc) + playsound(loc, "sparks", 50, TRUE) + blink_mob(user) + use(1) + +/obj/item/stack/ore/bluespace_crystal/proc/blink_mob(mob/living/L) + do_teleport(L, get_turf(L), blink_range, asoundin = 'sound/effects/phasein.ogg', channel = TELEPORT_CHANNEL_BLUESPACE) + +/obj/item/stack/ore/bluespace_crystal/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + if(!..()) // not caught in mid-air + visible_message("[src] fizzles and disappears upon impact!") + var/turf/T = get_turf(hit_atom) + new /obj/effect/particle_effect/sparks(T) + playsound(loc, "sparks", 50, TRUE) + if(isliving(hit_atom)) + blink_mob(hit_atom) + use(1) + +//Artificial bluespace crystal, doesn't give you much research. +/obj/item/stack/ore/bluespace_crystal/artificial + name = "artificial bluespace crystal" + desc = "An artificially made bluespace crystal, it looks delicate." + custom_materials = list(/datum/material/bluespace=MINERAL_MATERIAL_AMOUNT*0.5) + blink_range = 4 // Not as good as the organic stuff! + points = 0 //nice try + refined_type = null + grind_results = list(/datum/reagent/bluespace = 10, /datum/reagent/silicon = 20) + +//Polycrystals, aka stacks +/obj/item/stack/sheet/bluespace_crystal + name = "bluespace polycrystal" + icon = 'icons/obj/telescience.dmi' + icon_state = "polycrystal" + item_state = "sheet-polycrystal" + singular_name = "bluespace polycrystal" + desc = "A stable polycrystal, made of fused-together bluespace crystals. You could probably break one off." + custom_materials = list(/datum/material/bluespace=MINERAL_MATERIAL_AMOUNT) + attack_verb = list("bluespace polybashed", "bluespace polybattered", "bluespace polybludgeoned", "bluespace polythrashed", "bluespace polysmashed") + novariants = TRUE + grind_results = list(/datum/reagent/bluespace = 20) + point_value = 30 + var/crystal_type = /obj/item/stack/ore/bluespace_crystal/refined + +/obj/item/stack/sheet/bluespace_crystal/fifty + amount = 50 + +/obj/item/stack/sheet/bluespace_crystal/twenty + amount = 20 + +/obj/item/stack/sheet/bluespace_crystal/five + amount = 5 + +/obj/item/stack/sheet/bluespace_crystal/attack_self(mob/user)// to prevent the construction menu from ever happening + to_chat(user, "You cannot crush the polycrystal in-hand, try breaking one off.") + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/stack/sheet/bluespace_crystal/attack_hand(mob/user) + if(user.get_inactive_held_item() == src) + if(zero_amount()) + return + var/BC = new crystal_type(src) + user.put_in_hands(BC) + use(1) + if(!amount) + to_chat(user, "You break the final crystal off.") + else + to_chat(user, "You break off a crystal.") + else + ..() diff --git a/code/game/objects/items/stacks/medical.dm b/code/game/objects/items/stacks/medical.dm index 8e877449079f..a7c31197cb21 100644 --- a/code/game/objects/items/stacks/medical.dm +++ b/code/game/objects/items/stacks/medical.dm @@ -1,372 +1,373 @@ -/obj/item/stack/medical - name = "medical pack" - singular_name = "medical pack" - icon = 'icons/obj/stack_objects.dmi' - amount = 6 - max_amount = 6 - w_class = WEIGHT_CLASS_TINY - full_w_class = WEIGHT_CLASS_TINY - throw_speed = 3 - throw_range = 7 - resistance_flags = FLAMMABLE - max_integrity = 40 - novariants = FALSE - item_flags = NOBLUDGEON - var/splint_fracture = FALSE //WaspStation Edit- Splints - var/failure_chance //Waspstation Edit - Failure chance - var/self_delay = 50 - var/other_delay = 0 - var/repeating = FALSE - var/experience_given = 1 - -/obj/item/stack/medical/attack(mob/living/M, mob/user) - . = ..() - try_heal(M, user) - - -/obj/item/stack/medical/proc/try_heal(mob/living/M, mob/user, silent = FALSE) - if(!M.can_inject(user, TRUE)) - return - if(M == user) - if(!silent) - user.visible_message("[user] starts to apply \the [src] on [user.p_them()]self...", "You begin applying \the [src] on yourself...") - if(!do_mob(user, M, self_delay, extra_checks=CALLBACK(M, /mob/living/proc/can_inject, user, TRUE))) - return - else if(other_delay) - if(!silent) - user.visible_message("[user] starts to apply \the [src] on [M].", "You begin applying \the [src] on [M]...") - if(!do_mob(user, M, other_delay, extra_checks=CALLBACK(M, /mob/living/proc/can_inject, user, TRUE))) - return - if(heal(M, user)) - user?.mind.adjust_experience(/datum/skill/medical, experience_given) - log_combat(user, M, "healed", src.name) - use(1) - if(repeating && amount > 0) - try_heal(M, user, TRUE) - -/obj/item/stack/medical/proc/heal(mob/living/M, mob/user) - return - -/obj/item/stack/medical/proc/heal_carbon(mob/living/carbon/C, mob/user, brute, burn) - var/obj/item/bodypart/affecting = C.get_bodypart(check_zone(user.zone_selected)) - if(!affecting) //Missing limb? - to_chat(user, "[C] doesn't have \a [parse_zone(user.zone_selected)]!") - return - if(affecting.status != BODYPART_ORGANIC) //Limb must be organic to be healed - RR - to_chat(user, "\The [src] won't work on a robotic limb!") - return - - //Waspstation begin - failure chance - if(prob(failure_chance)) - user.visible_message("[user] tries to apply \the [src] on [C]'s [affecting.name], but fails!", "You try to apply \the [src] on on [C]'s [affecting.name], but fail!") - return - //Waspstation end - - if(affecting.brute_dam && brute || affecting.burn_dam && burn) - user.visible_message("[user] applies \the [src] on [C]'s [affecting.name].", "You apply \the [src] on [C]'s [affecting.name].") - var/brute2heal = brute - var/burn2heal = burn - var/skill_mod = user?.mind?.get_skill_modifier(/datum/skill/medical, SKILL_SPEED_MODIFIER) - if(skill_mod) - brute2heal *= (2-skill_mod) - burn2heal *= (2-skill_mod) - if(affecting.heal_damage(brute2heal, burn2heal)) - C.update_damage_overlays() - return TRUE - - - //WaspStation Begin - Splints - if(splint_fracture) //Check if it's a splint and the bone is broken - if(affecting.body_part in list(CHEST, HEAD)) // Check if it isn't the head or chest - to_chat(user, "You can't splint that bodypart!") - return - else if(affecting.bone_status == BONE_FLAG_SPLINTED) // Check if it isn't already splinted - to_chat(user, "[C]'s [affecting.name] is already splinted!") - return - else if(!(affecting.bone_status == BONE_FLAG_BROKEN)) // Check if it's actually broken - to_chat(user, "[C]'s [affecting.name] isn't broken!") - return - affecting.bone_status = BONE_FLAG_SPLINTED - C.update_inv_splints() - user.visible_message("[user] applies [src] on [C].", "You apply [src] on [C]'s [affecting.name].") - return TRUE - //WaspStation End - - - to_chat(user, "[C]'s [affecting.name] can not be healed with \the [src]!") - - -/obj/item/stack/medical/bruise_pack - name = "bruise pack" - singular_name = "bruise pack" - desc = "A therapeutic gel pack and bandages designed to treat blunt-force trauma." - icon_state = "brutepack" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - var/heal_brute = 40 - self_delay = 20 - grind_results = list(/datum/reagent/medicine/styptic_powder = 10) - -/obj/item/stack/medical/bruise_pack/heal(mob/living/M, mob/user) - if(M.stat == DEAD) - to_chat(user, "[M] is dead! You can not help [M.p_them()].") - return - if(isanimal(M)) - var/mob/living/simple_animal/critter = M - if (!(critter.healable)) - to_chat(user, "You cannot use \the [src] on [M]!") - return FALSE - else if (critter.health == critter.maxHealth) - to_chat(user, "[M] is at full health.") - return FALSE - user.visible_message("[user] applies \the [src] on [M].", "You apply \the [src] on [M].") - M.heal_bodypart_damage((heal_brute/2)) - return TRUE - if(iscarbon(M)) - return heal_carbon(M, user, heal_brute, 0) - to_chat(user, "You can't heal [M] with the \the [src]!") - -/obj/item/stack/medical/bruise_pack/suicide_act(mob/user) - user.visible_message("[user] is bludgeoning [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return (BRUTELOSS) - -/obj/item/stack/medical/gauze - name = "medical gauze" - desc = "A roll of elastic cloth that is extremely effective at stopping bleeding, but does not heal wounds." - gender = PLURAL - singular_name = "medical gauze" - icon_state = "gauze" - var/stop_bleeding = 1800 - self_delay = 20 - max_amount = 12 - grind_results = list(/datum/reagent/cellulose = 2) - custom_price = 100 - -/obj/item/stack/medical/gauze/twelve - amount = 12 - -/obj/item/stack/medical/gauze/heal(mob/living/M, mob/user) - if(ishuman(M)) - var/mob/living/carbon/human/H = M - if(!H.bleedsuppress && H.bleed_rate) //so you can't stack bleed suppression - H.suppress_bloodloss(stop_bleeding) - to_chat(user, "You stop the bleeding of [M]!") - return TRUE - to_chat(user, "You can not use \the [src] on [M]!") - -/obj/item/stack/medical/gauze/attackby(obj/item/I, mob/user, params) - if(I.tool_behaviour == TOOL_WIRECUTTER || I.get_sharpness()) - if(get_amount() < 2) - to_chat(user, "You need at least two gauzes to do this!") - return - new /obj/item/stack/sheet/cloth(user.drop_location()) - user.visible_message("[user] cuts [src] into pieces of cloth with [I].", \ - "You cut [src] into pieces of cloth with [I].", \ - "You hear cutting.") - use(2) - else - return ..() - -/obj/item/stack/medical/gauze/suicide_act(mob/living/user) - user.visible_message("[user] begins tightening \the [src] around [user.p_their()] neck! It looks like [user.p_they()] forgot how to use medical supplies!") - return OXYLOSS - -/obj/item/stack/medical/gauze/improvised - name = "improvised gauze" - singular_name = "improvised gauze" - desc = "A roll of cloth roughly cut from something that can stop bleeding, but does not heal wounds." - stop_bleeding = 900 - -/obj/item/stack/medical/gauze/cyborg - custom_materials = null - is_cyborg = 1 - cost = 250 - -/obj/item/stack/medical/ointment - name = "ointment" - desc = "Used to treat those nasty burn wounds." - gender = PLURAL - singular_name = "ointment" - icon_state = "ointment" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - var/heal_burn = 40 - self_delay = 20 - grind_results = list(/datum/reagent/medicine/silver_sulfadiazine = 10) - -/obj/item/stack/medical/ointment/heal(mob/living/M, mob/user) - if(M.stat == DEAD) - to_chat(user, "[M] is dead! You can not help [M.p_them()].") - return - if(iscarbon(M)) - return heal_carbon(M, user, 0, heal_burn) - to_chat(user, "You can't heal [M] with the \the [src]!") - -/obj/item/stack/medical/ointment/suicide_act(mob/living/user) - user.visible_message("[user] is squeezing \the [src] into [user.p_their()] mouth! [user.p_do(TRUE)]n't [user.p_they()] know that stuff is toxic?") - return TOXLOSS - -/obj/item/stack/medical/suture - name = "suture" - desc = "Sterile sutures used to seal up cuts and lacerations." - gender = PLURAL - singular_name = "suture" - icon_state = "suture" - self_delay = 30 - other_delay = 10 - amount = 15 - max_amount = 15 - repeating = TRUE - var/heal_brute = 10 - grind_results = list(/datum/reagent/medicine/spaceacillin = 2) - -/obj/item/stack/medical/suture/medicated - name = "medicated suture" - icon_state = "suture_purp" - desc = "A suture infused with drugs that speed up wound healing of the treated laceration." - heal_brute = 15 - grind_results = list(/datum/reagent/medicine/polypyr = 2) - -/obj/item/stack/medical/suture/heal(mob/living/M, mob/user) - . = ..() - if(M.stat == DEAD) - to_chat(user, "[M] is dead! You can not help [M.p_them()].") - return - if(iscarbon(M)) - return heal_carbon(M, user, heal_brute, 0) - if(isanimal(M)) - var/mob/living/simple_animal/critter = M - if (!(critter.healable)) - to_chat(user, "You cannot use \the [src] on [M]!") - return FALSE - else if (critter.health == critter.maxHealth) - to_chat(user, "[M] is at full health.") - return FALSE - user.visible_message("[user] applies \the [src] on [M].", "You apply \the [src] on [M].") - M.heal_bodypart_damage(heal_brute) - return TRUE - - to_chat(user, "You can't heal [M] with the \the [src]!") - -/obj/item/stack/medical/mesh - name = "regenerative mesh" - desc = "A bacteriostatic mesh used to dress burns." - gender = PLURAL - singular_name = "regenerative mesh" - icon_state = "regen_mesh" - self_delay = 30 - other_delay = 10 - amount = 15 - max_amount = 15 - repeating = TRUE - var/heal_burn = 10 - var/is_open = TRUE ///This var determines if the sterile packaging of the mesh has been opened. - grind_results = list(/datum/reagent/medicine/spaceacillin = 2) - -/obj/item/stack/medical/mesh/Initialize() - . = ..() - if(amount == max_amount) //only seal full mesh packs - is_open = FALSE - update_icon() - -/obj/item/stack/medical/mesh/update_icon_state() - if(!is_open) - icon_state = "regen_mesh_closed" - else - return ..() - -/obj/item/stack/medical/mesh/heal(mob/living/M, mob/user) - . = ..() - if(M.stat == DEAD) - to_chat(user, "[M] is dead! You can not help [M.p_them()].") - return - if(iscarbon(M)) - return heal_carbon(M, user, 0, heal_burn) - to_chat(user, "You can't heal [M] with the \the [src]!") - - -/obj/item/stack/medical/mesh/try_heal(mob/living/M, mob/user, silent = FALSE) - if(!is_open) - to_chat(user, "You need to open [src] first.") - return - . = ..() - -/obj/item/stack/medical/mesh/AltClick(mob/living/user) - if(!is_open) - to_chat(user, "You need to open [src] first.") - return - . = ..() - -/obj/item/stack/medical/mesh/attack_hand(mob/user) - if(!is_open & user.get_inactive_held_item() == src) - to_chat(user, "You need to open [src] first.") - return - . = ..() - -/obj/item/stack/medical/mesh/attack_self(mob/user) - if(!is_open) - is_open = TRUE - to_chat(user, "You open the sterile mesh package.") - update_icon() - playsound(src, 'sound/items/poster_ripped.ogg', 20, TRUE) - return - . = ..() - -/obj/item/stack/medical/mesh/advanced - name = "advanced regenerative mesh" - desc = "An advanced mesh made with aloe extracts and sterilizing chemicals, used to treat burns." - - gender = PLURAL - singular_name = "advanced regenerative mesh" - icon_state = "aloe_mesh" - heal_burn = 15 - grind_results = list(/datum/reagent/consumable/aloejuice = 1) - -/obj/item/stack/medical/mesh/advanced/update_icon_state() - if(!is_open) - icon_state = "aloe_mesh_closed" - else - return ..() - -/obj/item/stack/medical/aloe - name = "aloe cream" - desc = "A healing paste you can apply on wounds." - - icon_state = "aloe_paste" - self_delay = 20 - other_delay = 10 - novariants = TRUE - amount = 20 - max_amount = 20 - var/heal = 3 - grind_results = list(/datum/reagent/consumable/aloejuice = 1) - -/obj/item/stack/medical/aloe/heal(mob/living/M, mob/user) - . = ..() - if(M.stat == DEAD) - to_chat(user, "[M] is dead! You can not help [M.p_them()].") - return FALSE - if(iscarbon(M)) - return heal_carbon(M, user, heal, heal) - if(isanimal(M)) - var/mob/living/simple_animal/critter = M - if (!(critter.healable)) - to_chat(user, "You cannot use \the [src] on [M]!") - return FALSE - else if (critter.health == critter.maxHealth) - to_chat(user, "[M] is at full health.") - return FALSE - user.visible_message("[user] applies \the [src] on [M].", "You apply \the [src] on [M].") - M.heal_bodypart_damage(heal, heal) - return TRUE - - to_chat(user, "You can't heal [M] with the \the [src]!") - - - /* - The idea is for these medical devices to work like a hybrid of the old brute packs and tend wounds, - they heal a little at a time, have reduced healing density and does not allow for rapid healing while in combat. - However they provice graunular control of where the healing is directed, this makes them better for curing work-related cuts and scrapes. - - The interesting limb targeting mechanic is retained and i still believe they will be a viable choice, especially when healing others in the field. - */ +/obj/item/stack/medical + name = "medical pack" + singular_name = "medical pack" + icon = 'icons/obj/stack_objects.dmi' + amount = 6 + max_amount = 6 + w_class = WEIGHT_CLASS_TINY + full_w_class = WEIGHT_CLASS_TINY + throw_speed = 3 + throw_range = 7 + resistance_flags = FLAMMABLE + max_integrity = 40 + novariants = FALSE + item_flags = NOBLUDGEON + var/splint_fracture = FALSE //WaspStation Edit- Splints + var/failure_chance //Waspstation Edit - Failure chance + var/self_delay = 50 + var/other_delay = 0 + var/repeating = FALSE + var/experience_given = 1 + +/obj/item/stack/medical/attack(mob/living/M, mob/user) + . = ..() + try_heal(M, user) + + +/obj/item/stack/medical/proc/try_heal(mob/living/M, mob/user, silent = FALSE) + if(!M.can_inject(user, TRUE)) + return + if(M == user) + if(!silent) + user.visible_message("[user] starts to apply \the [src] on [user.p_them()]self...", "You begin applying \the [src] on yourself...") + if(!do_mob(user, M, self_delay, extra_checks=CALLBACK(M, /mob/living/proc/can_inject, user, TRUE))) + return + else if(other_delay) + if(!silent) + user.visible_message("[user] starts to apply \the [src] on [M].", "You begin applying \the [src] on [M]...") + if(!do_mob(user, M, other_delay, extra_checks=CALLBACK(M, /mob/living/proc/can_inject, user, TRUE))) + return + + if(heal(M, user)) + user?.mind.adjust_experience(/datum/skill/healing, experience_given) + log_combat(user, M, "healed", src.name) + use(1) + if(repeating && amount > 0) + try_heal(M, user, TRUE) + +/obj/item/stack/medical/proc/heal(mob/living/M, mob/user) + return + +/obj/item/stack/medical/proc/heal_carbon(mob/living/carbon/C, mob/user, brute, burn) + var/obj/item/bodypart/affecting = C.get_bodypart(check_zone(user.zone_selected)) + if(!affecting) //Missing limb? + to_chat(user, "[C] doesn't have \a [parse_zone(user.zone_selected)]!") + return + if(affecting.status != BODYPART_ORGANIC) //Limb must be organic to be healed - RR + to_chat(user, "\The [src] won't work on a robotic limb!") + return + + //Waspstation begin - failure chance + if(prob(failure_chance)) + user.visible_message("[user] tries to apply \the [src] on [C]'s [affecting.name], but fails!", "You try to apply \the [src] on on [C]'s [affecting.name], but fail!") + return + //Waspstation end + + if(affecting.brute_dam && brute || affecting.burn_dam && burn) + user.visible_message("[user] applies \the [src] on [C]'s [affecting.name].", "You apply \the [src] on [C]'s [affecting.name].") + var/brute2heal = brute + var/burn2heal = burn + var/skill_mod = user?.mind?.get_skill_modifier(/datum/skill/healing, SKILL_SPEED_MODIFIER) + if(skill_mod) + brute2heal *= (2-skill_mod) + burn2heal *= (2-skill_mod) + if(affecting.heal_damage(brute2heal, burn2heal)) + C.update_damage_overlays() + return TRUE + + + //WaspStation Begin - Splints + if(splint_fracture) //Check if it's a splint and the bone is broken + if(affecting.body_part in list(CHEST, HEAD)) // Check if it isn't the head or chest + to_chat(user, "You can't splint that bodypart!") + return + else if(affecting.bone_status == BONE_FLAG_SPLINTED) // Check if it isn't already splinted + to_chat(user, "[C]'s [affecting.name] is already splinted!") + return + else if(!(affecting.bone_status == BONE_FLAG_BROKEN)) // Check if it's actually broken + to_chat(user, "[C]'s [affecting.name] isn't broken!") + return + affecting.bone_status = BONE_FLAG_SPLINTED + C.update_inv_splints() + user.visible_message("[user] applies [src] on [C].", "You apply [src] on [C]'s [affecting.name].") + return TRUE + //WaspStation End + + + to_chat(user, "[C]'s [affecting.name] can not be healed with \the [src]!") + + +/obj/item/stack/medical/bruise_pack + name = "bruise pack" + singular_name = "bruise pack" + desc = "A therapeutic gel pack and bandages designed to treat blunt-force trauma." + icon_state = "brutepack" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + var/heal_brute = 40 + self_delay = 20 + grind_results = list(/datum/reagent/medicine/styptic_powder = 10) + +/obj/item/stack/medical/bruise_pack/heal(mob/living/M, mob/user) + if(M.stat == DEAD) + to_chat(user, "[M] is dead! You can not help [M.p_them()].") + return + if(isanimal(M)) + var/mob/living/simple_animal/critter = M + if (!(critter.healable)) + to_chat(user, "You cannot use \the [src] on [M]!") + return FALSE + else if (critter.health == critter.maxHealth) + to_chat(user, "[M] is at full health.") + return FALSE + user.visible_message("[user] applies \the [src] on [M].", "You apply \the [src] on [M].") + M.heal_bodypart_damage((heal_brute/2)) + return TRUE + if(iscarbon(M)) + return heal_carbon(M, user, heal_brute, 0) + to_chat(user, "You can't heal [M] with the \the [src]!") + +/obj/item/stack/medical/bruise_pack/suicide_act(mob/user) + user.visible_message("[user] is bludgeoning [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return (BRUTELOSS) + +/obj/item/stack/medical/gauze + name = "medical gauze" + desc = "A roll of elastic cloth that is extremely effective at stopping bleeding, but does not heal wounds." + gender = PLURAL + singular_name = "medical gauze" + icon_state = "gauze" + var/stop_bleeding = 1800 + self_delay = 20 + max_amount = 12 + grind_results = list(/datum/reagent/cellulose = 2) + custom_price = 100 + +/obj/item/stack/medical/gauze/twelve + amount = 12 + +/obj/item/stack/medical/gauze/heal(mob/living/M, mob/user) + if(ishuman(M)) + var/mob/living/carbon/human/H = M + if(!H.bleedsuppress && H.bleed_rate) //so you can't stack bleed suppression + H.suppress_bloodloss(stop_bleeding) + to_chat(user, "You stop the bleeding of [M]!") + return TRUE + to_chat(user, "You can not use \the [src] on [M]!") + +/obj/item/stack/medical/gauze/attackby(obj/item/I, mob/user, params) + if(I.tool_behaviour == TOOL_WIRECUTTER || I.get_sharpness()) + if(get_amount() < 2) + to_chat(user, "You need at least two gauzes to do this!") + return + new /obj/item/stack/sheet/cloth(user.drop_location()) + user.visible_message("[user] cuts [src] into pieces of cloth with [I].", \ + "You cut [src] into pieces of cloth with [I].", \ + "You hear cutting.") + use(2) + else + return ..() + +/obj/item/stack/medical/gauze/suicide_act(mob/living/user) + user.visible_message("[user] begins tightening \the [src] around [user.p_their()] neck! It looks like [user.p_they()] forgot how to use medical supplies!") + return OXYLOSS + +/obj/item/stack/medical/gauze/improvised + name = "improvised gauze" + singular_name = "improvised gauze" + desc = "A roll of cloth roughly cut from something that can stop bleeding, but does not heal wounds." + stop_bleeding = 900 + +/obj/item/stack/medical/gauze/cyborg + custom_materials = null + is_cyborg = 1 + cost = 250 + +/obj/item/stack/medical/ointment + name = "ointment" + desc = "Used to treat those nasty burn wounds." + gender = PLURAL + singular_name = "ointment" + icon_state = "ointment" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + var/heal_burn = 40 + self_delay = 20 + grind_results = list(/datum/reagent/medicine/silver_sulfadiazine = 10) + +/obj/item/stack/medical/ointment/heal(mob/living/M, mob/user) + if(M.stat == DEAD) + to_chat(user, "[M] is dead! You can not help [M.p_them()].") + return + if(iscarbon(M)) + return heal_carbon(M, user, 0, heal_burn) + to_chat(user, "You can't heal [M] with the \the [src]!") + +/obj/item/stack/medical/ointment/suicide_act(mob/living/user) + user.visible_message("[user] is squeezing \the [src] into [user.p_their()] mouth! [user.p_do(TRUE)]n't [user.p_they()] know that stuff is toxic?") + return TOXLOSS + +/obj/item/stack/medical/suture + name = "suture" + desc = "Sterile sutures used to seal up cuts and lacerations." + gender = PLURAL + singular_name = "suture" + icon_state = "suture" + self_delay = 30 + other_delay = 10 + amount = 15 + max_amount = 15 + repeating = TRUE + var/heal_brute = 10 + grind_results = list(/datum/reagent/medicine/spaceacillin = 2) + +/obj/item/stack/medical/suture/medicated + name = "medicated suture" + icon_state = "suture_purp" + desc = "A suture infused with drugs that speed up wound healing of the treated laceration." + heal_brute = 15 + grind_results = list(/datum/reagent/medicine/polypyr = 2) + +/obj/item/stack/medical/suture/heal(mob/living/M, mob/user) + . = ..() + if(M.stat == DEAD) + to_chat(user, "[M] is dead! You can not help [M.p_them()].") + return + if(iscarbon(M)) + return heal_carbon(M, user, heal_brute, 0) + if(isanimal(M)) + var/mob/living/simple_animal/critter = M + if (!(critter.healable)) + to_chat(user, "You cannot use \the [src] on [M]!") + return FALSE + else if (critter.health == critter.maxHealth) + to_chat(user, "[M] is at full health.") + return FALSE + user.visible_message("[user] applies \the [src] on [M].", "You apply \the [src] on [M].") + M.heal_bodypart_damage(heal_brute) + return TRUE + + to_chat(user, "You can't heal [M] with the \the [src]!") + +/obj/item/stack/medical/mesh + name = "regenerative mesh" + desc = "A bacteriostatic mesh used to dress burns." + gender = PLURAL + singular_name = "regenerative mesh" + icon_state = "regen_mesh" + self_delay = 30 + other_delay = 10 + amount = 15 + max_amount = 15 + repeating = TRUE + var/heal_burn = 10 + var/is_open = TRUE ///This var determines if the sterile packaging of the mesh has been opened. + grind_results = list(/datum/reagent/medicine/spaceacillin = 2) + +/obj/item/stack/medical/mesh/Initialize() + . = ..() + if(amount == max_amount) //only seal full mesh packs + is_open = FALSE + update_icon() + +/obj/item/stack/medical/mesh/update_icon_state() + if(!is_open) + icon_state = "regen_mesh_closed" + else + return ..() + +/obj/item/stack/medical/mesh/heal(mob/living/M, mob/user) + . = ..() + if(M.stat == DEAD) + to_chat(user, "[M] is dead! You can not help [M.p_them()].") + return + if(iscarbon(M)) + return heal_carbon(M, user, 0, heal_burn) + to_chat(user, "You can't heal [M] with the \the [src]!") + + +/obj/item/stack/medical/mesh/try_heal(mob/living/M, mob/user, silent = FALSE) + if(!is_open) + to_chat(user, "You need to open [src] first.") + return + . = ..() + +/obj/item/stack/medical/mesh/AltClick(mob/living/user) + if(!is_open) + to_chat(user, "You need to open [src] first.") + return + . = ..() + +/obj/item/stack/medical/mesh/attack_hand(mob/user) + if(!is_open && user.get_inactive_held_item() == src) + to_chat(user, "You need to open [src] first.") + return + . = ..() + +/obj/item/stack/medical/mesh/attack_self(mob/user) + if(!is_open) + is_open = TRUE + to_chat(user, "You open the sterile mesh package.") + update_icon() + playsound(src, 'sound/items/poster_ripped.ogg', 20, TRUE) + return + . = ..() + +/obj/item/stack/medical/mesh/advanced + name = "advanced regenerative mesh" + desc = "An advanced mesh made with aloe extracts and sterilizing chemicals, used to treat burns." + + gender = PLURAL + singular_name = "advanced regenerative mesh" + icon_state = "aloe_mesh" + heal_burn = 15 + grind_results = list(/datum/reagent/consumable/aloejuice = 1) + +/obj/item/stack/medical/mesh/advanced/update_icon_state() + if(!is_open) + icon_state = "aloe_mesh_closed" + else + return ..() + +/obj/item/stack/medical/aloe + name = "aloe cream" + desc = "A healing paste you can apply on wounds." + + icon_state = "aloe_paste" + self_delay = 20 + other_delay = 10 + novariants = TRUE + amount = 20 + max_amount = 20 + var/heal = 3 + grind_results = list(/datum/reagent/consumable/aloejuice = 1) + +/obj/item/stack/medical/aloe/heal(mob/living/M, mob/user) + . = ..() + if(M.stat == DEAD) + to_chat(user, "[M] is dead! You can not help [M.p_them()].") + return FALSE + if(iscarbon(M)) + return heal_carbon(M, user, heal, heal) + if(isanimal(M)) + var/mob/living/simple_animal/critter = M + if (!(critter.healable)) + to_chat(user, "You cannot use \the [src] on [M]!") + return FALSE + else if (critter.health == critter.maxHealth) + to_chat(user, "[M] is at full health.") + return FALSE + user.visible_message("[user] applies \the [src] on [M].", "You apply \the [src] on [M].") + M.heal_bodypart_damage(heal, heal) + return TRUE + + to_chat(user, "You can't heal [M] with the \the [src]!") + + + /* + The idea is for these medical devices to work like a hybrid of the old brute packs and tend wounds, + they heal a little at a time, have reduced healing density and does not allow for rapid healing while in combat. + However they provice graunular control of where the healing is directed, this makes them better for curing work-related cuts and scrapes. + + The interesting limb targeting mechanic is retained and i still believe they will be a viable choice, especially when healing others in the field. + */ diff --git a/code/game/objects/items/stacks/rods.dm b/code/game/objects/items/stacks/rods.dm index b210a1e27399..4ade2d74806f 100644 --- a/code/game/objects/items/stacks/rods.dm +++ b/code/game/objects/items/stacks/rods.dm @@ -1,117 +1,117 @@ -GLOBAL_LIST_INIT(rod_recipes, list ( \ - new/datum/stack_recipe("grille", /obj/structure/grille, 2, time = 10, one_per_turf = TRUE, on_floor = FALSE), \ - new/datum/stack_recipe("table frame", /obj/structure/table_frame, 2, time = 10, one_per_turf = 1, on_floor = 1), \ - new/datum/stack_recipe("scooter frame", /obj/item/scooter_frame, 10, time = 25, one_per_turf = 0), \ - new/datum/stack_recipe("linen bin", /obj/structure/bedsheetbin/empty, 2, time = 5, one_per_turf = 0), \ - // Wasp start - null, \ - new/datum/stack_recipe("fore port spacepod frame", /obj/item/pod_parts/pod_frame/fore_port, 15, time = 30, one_per_turf = 0), \ - new/datum/stack_recipe("fore starboard spacepod frame", /obj/item/pod_parts/pod_frame/fore_starboard, 15, time = 30, one_per_turf = 0), \ - new/datum/stack_recipe("aft port spacepod frame", /obj/item/pod_parts/pod_frame/aft_port, 15, time = 30, one_per_turf = 0), \ - new/datum/stack_recipe("aft starboard spacepod frame", /obj/item/pod_parts/pod_frame/aft_starboard, 15, time = 30, one_per_turf = 0), \ - null, \ - // Wasp end - new/datum/stack_recipe("railing", /obj/structure/railing, 3, time = 18, window_checks = TRUE), \ - )) - -/obj/item/stack/rods - name = "metal rod" - desc = "Some rods. Can be used for building or something." - singular_name = "metal rod" - icon_state = "rods" - item_state = "rods" - flags_1 = CONDUCT_1 - w_class = WEIGHT_CLASS_NORMAL - force = 9 - throwforce = 10 - throw_speed = 3 - throw_range = 7 - custom_materials = list(/datum/material/iron=1000) - max_amount = 50 - attack_verb = list("hit", "bludgeoned", "whacked") - hitsound = 'sound/weapons/gun/general/grenade_launch.ogg' - embedding = list() - novariants = TRUE - -/obj/item/stack/rods/suicide_act(mob/living/carbon/user) - user.visible_message("[user] begins to stuff \the [src] down [user.p_their()] throat! It looks like [user.p_theyre()] trying to commit suicide!")//it looks like theyre ur mum - return BRUTELOSS - -/obj/item/stack/rods/Initialize(mapload, new_amount, merge = TRUE) - . = ..() - update_icon() - -/obj/item/stack/rods/get_main_recipes() - . = ..() - . += GLOB.rod_recipes - -/obj/item/stack/rods/update_icon_state() - var/amount = get_amount() - if(amount <= 5) - icon_state = "rods-[amount]" - else - icon_state = "rods" - -/obj/item/stack/rods/attackby(obj/item/W, mob/user, params) - if(W.tool_behaviour == TOOL_WELDER) - if(get_amount() < 2) - to_chat(user, "You need at least two rods to do this!") - return - - if(W.use_tool(src, user, 0, volume=40)) - var/obj/item/stack/sheet/metal/new_item = new(usr.loc) - user.visible_message("[user.name] shaped [src] into metal with [W].", \ - "You shape [src] into metal with [W].", \ - "You hear welding.") - var/obj/item/stack/rods/R = src - src = null - var/replace = (user.get_inactive_held_item()==R) - R.use(2) - if (!R && replace) - user.put_in_hands(new_item) - - else if(istype(W, /obj/item/reagent_containers/food/snacks)) - var/obj/item/reagent_containers/food/snacks/S = W - if(amount != 1) - to_chat(user, "You must use a single rod!") - else if(S.w_class > WEIGHT_CLASS_SMALL) - to_chat(user, "The ingredient is too big for [src]!") - else - var/obj/item/reagent_containers/food/snacks/customizable/A = new/obj/item/reagent_containers/food/snacks/customizable/kebab(get_turf(src)) - A.initialize_custom_food(src, S, user) - else - return ..() - -/obj/item/stack/rods/cyborg - custom_materials = null - is_cyborg = 1 - cost = 250 - -/obj/item/stack/rods/cyborg/ComponentInitialize() - . = ..() - AddElement(/datum/element/update_icon_blocker) - -/obj/item/stack/rods/ten - amount = 10 - -/obj/item/stack/rods/twentyfive - amount = 25 - -/obj/item/stack/rods/fifty - amount = 50 - -/obj/item/stack/rods/lava - name = "heat resistant rod" - desc = "Treated, specialized metal rods. When exposed to the vaccum of space their coating breaks off, but they can hold up against the extreme heat of active lava." - singular_name = "heat resistant rod" - icon_state = "rods" - item_state = "rods" - color = "#5286b9ff" - flags_1 = CONDUCT_1 - w_class = WEIGHT_CLASS_NORMAL - custom_materials = list(/datum/material/iron=1000, /datum/material/plasma=500, /datum/material/titanium=2000) - max_amount = 30 - resistance_flags = FIRE_PROOF | LAVA_PROOF - -/obj/item/stack/rods/lava/thirty - amount = 30 +GLOBAL_LIST_INIT(rod_recipes, list ( \ + new/datum/stack_recipe("grille", /obj/structure/grille, 2, time = 10, one_per_turf = TRUE, on_floor = FALSE), \ + new/datum/stack_recipe("table frame", /obj/structure/table_frame, 2, time = 10, one_per_turf = 1, on_floor = 1), \ + new/datum/stack_recipe("scooter frame", /obj/item/scooter_frame, 10, time = 25, one_per_turf = 0), \ + new/datum/stack_recipe("linen bin", /obj/structure/bedsheetbin/empty, 2, time = 5, one_per_turf = 0), \ + // Wasp start + null, \ + new/datum/stack_recipe("fore port spacepod frame", /obj/item/pod_parts/pod_frame/fore_port, 15, time = 30, one_per_turf = 0), \ + new/datum/stack_recipe("fore starboard spacepod frame", /obj/item/pod_parts/pod_frame/fore_starboard, 15, time = 30, one_per_turf = 0), \ + new/datum/stack_recipe("aft port spacepod frame", /obj/item/pod_parts/pod_frame/aft_port, 15, time = 30, one_per_turf = 0), \ + new/datum/stack_recipe("aft starboard spacepod frame", /obj/item/pod_parts/pod_frame/aft_starboard, 15, time = 30, one_per_turf = 0), \ + null, \ + // Wasp end + new/datum/stack_recipe("railing", /obj/structure/railing, 3, time = 18, window_checks = TRUE), \ + )) + +/obj/item/stack/rods + name = "metal rod" + desc = "Some rods. Can be used for building or something." + singular_name = "metal rod" + icon_state = "rods" + item_state = "rods" + flags_1 = CONDUCT_1 + w_class = WEIGHT_CLASS_NORMAL + force = 9 + throwforce = 10 + throw_speed = 3 + throw_range = 7 + custom_materials = list(/datum/material/iron=1000) + max_amount = 50 + attack_verb = list("hit", "bludgeoned", "whacked") + hitsound = 'sound/weapons/gun/general/grenade_launch.ogg' + embedding = list() + novariants = TRUE + +/obj/item/stack/rods/suicide_act(mob/living/carbon/user) + user.visible_message("[user] begins to stuff \the [src] down [user.p_their()] throat! It looks like [user.p_theyre()] trying to commit suicide!")//it looks like theyre ur mum + return BRUTELOSS + +/obj/item/stack/rods/Initialize(mapload, new_amount, merge = TRUE) + . = ..() + update_icon() + +/obj/item/stack/rods/get_main_recipes() + . = ..() + . += GLOB.rod_recipes + +/obj/item/stack/rods/update_icon_state() + var/amount = get_amount() + if(amount <= 5) + icon_state = "rods-[amount]" + else + icon_state = "rods" + +/obj/item/stack/rods/attackby(obj/item/W, mob/user, params) + if(W.tool_behaviour == TOOL_WELDER) + if(get_amount() < 2) + to_chat(user, "You need at least two rods to do this!") + return + + if(W.use_tool(src, user, 0, volume=40)) + var/obj/item/stack/sheet/metal/new_item = new(usr.loc) + user.visible_message("[user.name] shaped [src] into metal with [W].", \ + "You shape [src] into metal with [W].", \ + "You hear welding.") + var/obj/item/stack/rods/R = src + src = null + var/replace = (user.get_inactive_held_item()==R) + R.use(2) + if (!R && replace) + user.put_in_hands(new_item) + + else if(istype(W, /obj/item/reagent_containers/food/snacks)) + var/obj/item/reagent_containers/food/snacks/S = W + if(amount != 1) + to_chat(user, "You must use a single rod!") + else if(S.w_class > WEIGHT_CLASS_SMALL) + to_chat(user, "The ingredient is too big for [src]!") + else + var/obj/item/reagent_containers/food/snacks/customizable/A = new/obj/item/reagent_containers/food/snacks/customizable/kebab(get_turf(src)) + A.initialize_custom_food(src, S, user) + else + return ..() + +/obj/item/stack/rods/cyborg + custom_materials = null + is_cyborg = 1 + cost = 250 + +/obj/item/stack/rods/cyborg/ComponentInitialize() + . = ..() + AddElement(/datum/element/update_icon_blocker) + +/obj/item/stack/rods/ten + amount = 10 + +/obj/item/stack/rods/twentyfive + amount = 25 + +/obj/item/stack/rods/fifty + amount = 50 + +/obj/item/stack/rods/lava + name = "heat resistant rod" + desc = "Treated, specialized metal rods. When exposed to the vaccum of space their coating breaks off, but they can hold up against the extreme heat of active lava." + singular_name = "heat resistant rod" + icon_state = "rods" + item_state = "rods" + color = "#5286b9ff" + flags_1 = CONDUCT_1 + w_class = WEIGHT_CLASS_NORMAL + custom_materials = list(/datum/material/iron=1000, /datum/material/plasma=500, /datum/material/titanium=2000) + max_amount = 30 + resistance_flags = FIRE_PROOF | LAVA_PROOF + +/obj/item/stack/rods/lava/thirty + amount = 30 diff --git a/code/game/objects/items/stacks/sheets/glass.dm b/code/game/objects/items/stacks/sheets/glass.dm index 77db5fdcd474..b9c972b7ebed 100644 --- a/code/game/objects/items/stacks/sheets/glass.dm +++ b/code/game/objects/items/stacks/sheets/glass.dm @@ -1,361 +1,361 @@ -/* Glass stack types - * Contains: - * Glass sheets - * Reinforced glass sheets - * Glass shards - TODO: Move this into code/game/object/item/weapons - */ - -/* - * Glass sheets - */ -GLOBAL_LIST_INIT(glass_recipes, list ( \ - new/datum/stack_recipe("directional window", /obj/structure/window/unanchored, time = 0, on_floor = TRUE, window_checks = TRUE), \ - new/datum/stack_recipe("fulltile window", /obj/structure/window/fulltile/unanchored, 2, time = 0, on_floor = TRUE, window_checks = TRUE) \ -)) - -/obj/item/stack/sheet/glass - name = "glass" - desc = "HOLY SHEET! That is a lot of glass." - singular_name = "glass sheet" - icon_state = "sheet-glass" - item_state = "sheet-glass" - custom_materials = list(/datum/material/glass=MINERAL_MATERIAL_AMOUNT) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 100) - resistance_flags = ACID_PROOF - merge_type = /obj/item/stack/sheet/glass - grind_results = list(/datum/reagent/silicon = 20) - material_type = /datum/material/glass - point_value = 1 - tableVariant = /obj/structure/table/glass - -/obj/item/stack/sheet/glass/suicide_act(mob/living/carbon/user) - user.visible_message("[user] begins to slice [user.p_their()] neck with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return BRUTELOSS - -/obj/item/stack/sheet/glass/cyborg - custom_materials = null - is_cyborg = 1 - cost = 500 - -/obj/item/stack/sheet/glass/fifty - amount = 50 - -/obj/item/stack/sheet/glass/get_main_recipes() - . = ..() - . += GLOB.glass_recipes - -/obj/item/stack/sheet/glass/attackby(obj/item/W, mob/user, params) - add_fingerprint(user) - if(istype(W, /obj/item/stack/cable_coil)) - var/obj/item/stack/cable_coil/CC = W - 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)) - var/obj/item/stack/rods/V = W - if (V.get_amount() >= 1 && get_amount() >= 1) - var/obj/item/stack/sheet/rglass/RG = new (get_turf(user)) - RG.add_fingerprint(user) - var/replace = user.get_inactive_held_item()==src - V.use(1) - use(1) - if(QDELETED(src) && replace) - 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 ..() - - - -GLOBAL_LIST_INIT(pglass_recipes, list ( \ - new/datum/stack_recipe("directional window", /obj/structure/window/plasma/unanchored, time = 0, on_floor = TRUE, window_checks = TRUE), \ - new/datum/stack_recipe("fulltile window", /obj/structure/window/plasma/fulltile/unanchored, 2, time = 0, on_floor = TRUE, window_checks = TRUE) \ -)) - -/obj/item/stack/sheet/plasmaglass - name = "plasma glass" - desc = "A glass sheet made out of a plasma-silicate alloy. It looks extremely tough and heavily fire resistant." - singular_name = "plasma glass sheet" - icon_state = "sheet-pglass" - item_state = "sheet-pglass" - custom_materials = list(/datum/material/plasma=MINERAL_MATERIAL_AMOUNT * 0.5, /datum/material/glass=MINERAL_MATERIAL_AMOUNT) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 75, "acid" = 100) - resistance_flags = ACID_PROOF - merge_type = /obj/item/stack/sheet/plasmaglass - grind_results = list(/datum/reagent/silicon = 20, /datum/reagent/toxin/plasma = 10) - material_flags = MATERIAL_NO_EFFECTS - -/obj/item/stack/sheet/plasmaglass/fifty - amount = 50 - -/obj/item/stack/sheet/plasmaglass/get_main_recipes() - . = ..() - . += GLOB.pglass_recipes - -/obj/item/stack/sheet/plasmaglass/attackby(obj/item/W, mob/user, params) - add_fingerprint(user) - - 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/plasmarglass/RG = new (get_turf(user)) - RG.add_fingerprint(user) - var/replace = user.get_inactive_held_item()==src - V.use(1) - use(1) - if(QDELETED(src) && replace) - user.put_in_hands(RG) - else - to_chat(user, "You need one rod and one sheet of plasma glass to make reinforced plasma glass!") - return - else - return ..() - - - -/* - * Reinforced glass sheets - */ -GLOBAL_LIST_INIT(reinforced_glass_recipes, list ( \ - new/datum/stack_recipe("windoor frame", /obj/structure/windoor_assembly, 5, time = 0, on_floor = TRUE, window_checks = TRUE), \ - new/datum/stack_recipe("window firelock frame", /obj/structure/firelock_frame/window, 3, time = 50, one_per_turf = TRUE, on_floor = TRUE), - null, \ - new/datum/stack_recipe("directional reinforced window", /obj/structure/window/reinforced/unanchored, time = 0, on_floor = TRUE, window_checks = TRUE), \ - new/datum/stack_recipe("fulltile reinforced window", /obj/structure/window/reinforced/fulltile/unanchored, 2, time = 0, on_floor = TRUE, window_checks = TRUE) \ -)) - - -/obj/item/stack/sheet/rglass - name = "reinforced glass" - desc = "Glass which seems to have rods or something stuck in them." - singular_name = "reinforced glass sheet" - icon_state = "sheet-rglass" - item_state = "sheet-rglass" - custom_materials = list(/datum/material/iron=MINERAL_MATERIAL_AMOUNT * 0.5, /datum/material/glass=MINERAL_MATERIAL_AMOUNT) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 100) - resistance_flags = ACID_PROOF - merge_type = /obj/item/stack/sheet/rglass - grind_results = list(/datum/reagent/silicon = 20, /datum/reagent/iron = 10) - point_value = 4 - -/obj/item/stack/sheet/rglass/attackby(obj/item/W, mob/user, params) - add_fingerprint(user) - ..() - -/obj/item/stack/sheet/rglass/cyborg - custom_materials = null - var/datum/robot_energy_storage/glasource - var/metcost = 250 - var/glacost = 500 - -/obj/item/stack/sheet/rglass/cyborg/get_amount() - return min(round(source.energy / metcost), round(glasource.energy / glacost)) - -/obj/item/stack/sheet/rglass/cyborg/use(used, transfer = FALSE) // Requires special checks, because it uses two storages - if(get_amount(used)) //ensure we still have enough energy if called in a do_after chain - source.use_charge(used * metcost) - glasource.use_charge(used * glacost) - return TRUE - -/obj/item/stack/sheet/rglass/cyborg/add(amount) - source.add_charge(amount * metcost) - glasource.add_charge(amount * glacost) - -/obj/item/stack/sheet/rglass/get_main_recipes() - . = ..() - . += GLOB.reinforced_glass_recipes - -GLOBAL_LIST_INIT(prglass_recipes, list ( \ - new/datum/stack_recipe("directional reinforced window", /obj/structure/window/plasma/reinforced/unanchored, time = 0, on_floor = TRUE, window_checks = TRUE), \ - new/datum/stack_recipe("fulltile reinforced window", /obj/structure/window/plasma/reinforced/fulltile/unanchored, 2, time = 0, on_floor = TRUE, window_checks = TRUE) \ -)) - -/obj/item/stack/sheet/plasmarglass - name = "reinforced plasma glass" - desc = "A glass sheet made out of a plasma-silicate alloy and a rod matrix. It looks hopelessly tough and nearly fire-proof!" - singular_name = "reinforced plasma glass sheet" - icon_state = "sheet-prglass" - item_state = "sheet-prglass" - custom_materials = list(/datum/material/plasma=MINERAL_MATERIAL_AMOUNT * 0.5, /datum/material/glass=MINERAL_MATERIAL_AMOUNT, /datum/material/iron = MINERAL_MATERIAL_AMOUNT * 0.5) - armor = list("melee" = 20, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100) - resistance_flags = ACID_PROOF - material_flags = MATERIAL_NO_EFFECTS - 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 - -/obj/item/stack/sheet/plasmarglass/get_main_recipes() - . = ..() - . += GLOB.prglass_recipes - -GLOBAL_LIST_INIT(titaniumglass_recipes, list( - new/datum/stack_recipe("shuttle window", /obj/structure/window/shuttle/unanchored, 2, time = 0, on_floor = TRUE, window_checks = TRUE) - )) - -/obj/item/stack/sheet/titaniumglass - name = "titanium glass" - desc = "A glass sheet made out of a titanium-silicate alloy." - singular_name = "titanium glass sheet" - icon_state = "sheet-titaniumglass" - item_state = "sheet-titaniumglass" - custom_materials = list(/datum/material/titanium=MINERAL_MATERIAL_AMOUNT * 0.5, /datum/material/glass=MINERAL_MATERIAL_AMOUNT) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100) - resistance_flags = ACID_PROOF - merge_type = /obj/item/stack/sheet/titaniumglass - -/obj/item/stack/sheet/titaniumglass/get_main_recipes() - . = ..() - . += GLOB.titaniumglass_recipes - -GLOBAL_LIST_INIT(plastitaniumglass_recipes, list( - new/datum/stack_recipe("plastitanium window", /obj/structure/window/plasma/reinforced/plastitanium/unanchored, 2, time = 0, on_floor = TRUE, window_checks = TRUE) - )) - -/obj/item/stack/sheet/plastitaniumglass - name = "plastitanium glass" - desc = "A glass sheet made out of a plasma-titanium-silicate alloy." - singular_name = "plastitanium glass sheet" - icon_state = "sheet-plastitaniumglass" - item_state = "sheet-plastitaniumglass" - custom_materials = list(/datum/material/titanium=MINERAL_MATERIAL_AMOUNT * 0.5, /datum/material/plasma=MINERAL_MATERIAL_AMOUNT * 0.5, /datum/material/glass=MINERAL_MATERIAL_AMOUNT) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100) - material_flags = MATERIAL_NO_EFFECTS - resistance_flags = ACID_PROOF - merge_type = /obj/item/stack/sheet/plastitaniumglass - -/obj/item/stack/sheet/plastitaniumglass/get_main_recipes() - . = ..() - . += GLOB.plastitaniumglass_recipes - -/obj/item/shard - name = "shard" - desc = "A nasty looking shard of glass." - icon = 'icons/obj/shards.dmi' - icon_state = "large" - w_class = WEIGHT_CLASS_TINY - force = 5 - throwforce = 10 - item_state = "shard-glass" - lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' - custom_materials = list(/datum/material/glass=MINERAL_MATERIAL_AMOUNT) - attack_verb = list("stabbed", "slashed", "sliced", "cut") - hitsound = 'sound/weapons/bladeslice.ogg' - resistance_flags = ACID_PROOF - armor = list("melee" = 100, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 100) - max_integrity = 40 - sharpness = IS_SHARP - var/icon_prefix - var/obj/item/stack/sheet/weld_material = /obj/item/stack/sheet/glass - embedding = list("embed_chance" = 65) - - -/obj/item/shard/suicide_act(mob/user) - user.visible_message("[user] is slitting [user.p_their()] [pick("wrists", "throat")] with the shard of glass! It looks like [user.p_theyre()] trying to commit suicide.") - return (BRUTELOSS) - - -/obj/item/shard/Initialize() - . = ..() - AddComponent(/datum/component/caltrop, force) - AddComponent(/datum/component/butchering, 150, 65) - icon_state = pick("large", "medium", "small") - switch(icon_state) - if("small") - pixel_x = rand(-12, 12) - pixel_y = rand(-12, 12) - if("medium") - pixel_x = rand(-8, 8) - pixel_y = rand(-8, 8) - if("large") - pixel_x = rand(-5, 5) - pixel_y = rand(-5, 5) - if (icon_prefix) - icon_state = "[icon_prefix][icon_state]" - - var/turf/T = get_turf(src) - if(T && is_station_level(T.z)) - SSblackbox.record_feedback("tally", "station_mess_created", 1, name) - -/obj/item/shard/Destroy() - . = ..() - - var/turf/T = get_turf(src) - if(T && is_station_level(T.z)) - SSblackbox.record_feedback("tally", "station_mess_destroyed", 1, name) - -/obj/item/shard/afterattack(atom/A as mob|obj, mob/user, proximity) - . = ..() - if(!proximity || !(src in user)) - return - if(isturf(A)) - return - if(istype(A, /obj/item/storage)) - return - var/hit_hand = ((user.active_hand_index % 2 == 0) ? "r_" : "l_") + "arm" - if(ishuman(user)) - var/mob/living/carbon/human/H = user - if(!H.gloves && !HAS_TRAIT(H, TRAIT_PIERCEIMMUNE)) // golems, etc - to_chat(H, "[src] cuts into your hand!") - H.apply_damage(force*0.5, BRUTE, hit_hand) - else if(ismonkey(user)) - var/mob/living/carbon/monkey/M = user - if(!HAS_TRAIT(M, TRAIT_PIERCEIMMUNE)) - to_chat(M, "[src] cuts into your hand!") - M.apply_damage(force*0.5, BRUTE, hit_hand) - -/obj/item/shard/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/lightreplacer)) - var/obj/item/lightreplacer/L = I - L.attackby(src, user) - else if(istype(I, /obj/item/stack/sheet/cloth)) - var/obj/item/stack/sheet/cloth/C = I - to_chat(user, "You begin to wrap the [C] around the [src]...") - if(do_after(user, 35, target = src)) - var/obj/item/kitchen/knife/shiv/S = new /obj/item/kitchen/knife/shiv - C.use(1) - to_chat(user, "You wrap the [C] around the [src] forming a makeshift weapon.") - remove_item_from_storage(src) - qdel(src) - user.put_in_hands(S) - - else - return ..() - -/obj/item/shard/welder_act(mob/living/user, obj/item/I) - ..() - if(I.use_tool(src, user, 0, volume=50)) - var/obj/item/stack/sheet/NG = new weld_material(user.loc) - for(var/obj/item/stack/sheet/G in user.loc) - if(G == NG) - continue - if(G.amount >= G.max_amount) - continue - G.attackby(NG, user) - to_chat(user, "You add the newly-formed [NG.name] to the stack. It now contains [NG.amount] sheet\s.") - qdel(src) - return TRUE - -/obj/item/shard/Crossed(atom/movable/AM) - if(isliving(AM)) - var/mob/living/L = AM - if(!(L.is_flying() || L.is_floating() || L.buckled)) - playsound(src, 'sound/effects/glass_step.ogg', HAS_TRAIT(L, TRAIT_LIGHT_STEP) ? 30 : 50, TRUE) - return ..() - -/obj/item/shard/plasma - name = "purple shard" - desc = "A nasty looking shard of plasma glass." - force = 6 - throwforce = 11 - icon_state = "plasmalarge" - custom_materials = list(/datum/material/plasma=MINERAL_MATERIAL_AMOUNT * 0.5, /datum/material/glass=MINERAL_MATERIAL_AMOUNT) - icon_prefix = "plasma" - weld_material = /obj/item/stack/sheet/plasmaglass +/* Glass stack types + * Contains: + * Glass sheets + * Reinforced glass sheets + * Glass shards - TODO: Move this into code/game/object/item/weapons + */ + +/* + * Glass sheets + */ +GLOBAL_LIST_INIT(glass_recipes, list ( \ + new/datum/stack_recipe("directional window", /obj/structure/window/unanchored, time = 0, on_floor = TRUE, window_checks = TRUE), \ + new/datum/stack_recipe("fulltile window", /obj/structure/window/fulltile/unanchored, 2, time = 0, on_floor = TRUE, window_checks = TRUE) \ +)) + +/obj/item/stack/sheet/glass + name = "glass" + desc = "HOLY SHEET! That is a lot of glass." + singular_name = "glass sheet" + icon_state = "sheet-glass" + item_state = "sheet-glass" + custom_materials = list(/datum/material/glass=MINERAL_MATERIAL_AMOUNT) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 100) + resistance_flags = ACID_PROOF + merge_type = /obj/item/stack/sheet/glass + grind_results = list(/datum/reagent/silicon = 20) + material_type = /datum/material/glass + point_value = 1 + tableVariant = /obj/structure/table/glass + +/obj/item/stack/sheet/glass/suicide_act(mob/living/carbon/user) + user.visible_message("[user] begins to slice [user.p_their()] neck with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return BRUTELOSS + +/obj/item/stack/sheet/glass/cyborg + custom_materials = null + is_cyborg = 1 + cost = 500 + +/obj/item/stack/sheet/glass/fifty + amount = 50 + +/obj/item/stack/sheet/glass/get_main_recipes() + . = ..() + . += GLOB.glass_recipes + +/obj/item/stack/sheet/glass/attackby(obj/item/W, mob/user, params) + add_fingerprint(user) + if(istype(W, /obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/CC = W + 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)) + var/obj/item/stack/rods/V = W + if (V.get_amount() >= 1 && get_amount() >= 1) + var/obj/item/stack/sheet/rglass/RG = new (get_turf(user)) + RG.add_fingerprint(user) + var/replace = user.get_inactive_held_item()==src + V.use(1) + use(1) + if(QDELETED(src) && replace) + 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 ..() + + + +GLOBAL_LIST_INIT(pglass_recipes, list ( \ + new/datum/stack_recipe("directional window", /obj/structure/window/plasma/unanchored, time = 0, on_floor = TRUE, window_checks = TRUE), \ + new/datum/stack_recipe("fulltile window", /obj/structure/window/plasma/fulltile/unanchored, 2, time = 0, on_floor = TRUE, window_checks = TRUE) \ +)) + +/obj/item/stack/sheet/plasmaglass + name = "plasma glass" + desc = "A glass sheet made out of a plasma-silicate alloy. It looks extremely tough and heavily fire resistant." + singular_name = "plasma glass sheet" + icon_state = "sheet-pglass" + item_state = "sheet-pglass" + custom_materials = list(/datum/material/plasma=MINERAL_MATERIAL_AMOUNT * 0.5, /datum/material/glass=MINERAL_MATERIAL_AMOUNT) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 75, "acid" = 100) + resistance_flags = ACID_PROOF + merge_type = /obj/item/stack/sheet/plasmaglass + grind_results = list(/datum/reagent/silicon = 20, /datum/reagent/toxin/plasma = 10) + material_flags = MATERIAL_NO_EFFECTS + +/obj/item/stack/sheet/plasmaglass/fifty + amount = 50 + +/obj/item/stack/sheet/plasmaglass/get_main_recipes() + . = ..() + . += GLOB.pglass_recipes + +/obj/item/stack/sheet/plasmaglass/attackby(obj/item/W, mob/user, params) + add_fingerprint(user) + + 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/plasmarglass/RG = new (get_turf(user)) + RG.add_fingerprint(user) + var/replace = user.get_inactive_held_item()==src + V.use(1) + use(1) + if(QDELETED(src) && replace) + user.put_in_hands(RG) + else + to_chat(user, "You need one rod and one sheet of plasma glass to make reinforced plasma glass!") + return + else + return ..() + + + +/* + * Reinforced glass sheets + */ +GLOBAL_LIST_INIT(reinforced_glass_recipes, list ( \ + new/datum/stack_recipe("windoor frame", /obj/structure/windoor_assembly, 5, time = 0, on_floor = TRUE, window_checks = TRUE), \ + new/datum/stack_recipe("window firelock frame", /obj/structure/firelock_frame/window, 3, time = 50, one_per_turf = TRUE, on_floor = TRUE), + null, \ + new/datum/stack_recipe("directional reinforced window", /obj/structure/window/reinforced/unanchored, time = 0, on_floor = TRUE, window_checks = TRUE), \ + new/datum/stack_recipe("fulltile reinforced window", /obj/structure/window/reinforced/fulltile/unanchored, 2, time = 0, on_floor = TRUE, window_checks = TRUE) \ +)) + + +/obj/item/stack/sheet/rglass + name = "reinforced glass" + desc = "Glass which seems to have rods or something stuck in them." + singular_name = "reinforced glass sheet" + icon_state = "sheet-rglass" + item_state = "sheet-rglass" + custom_materials = list(/datum/material/iron=MINERAL_MATERIAL_AMOUNT * 0.5, /datum/material/glass=MINERAL_MATERIAL_AMOUNT) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 100) + resistance_flags = ACID_PROOF + merge_type = /obj/item/stack/sheet/rglass + grind_results = list(/datum/reagent/silicon = 20, /datum/reagent/iron = 10) + point_value = 4 + +/obj/item/stack/sheet/rglass/attackby(obj/item/W, mob/user, params) + add_fingerprint(user) + ..() + +/obj/item/stack/sheet/rglass/cyborg + custom_materials = null + var/datum/robot_energy_storage/glasource + var/metcost = 250 + var/glacost = 500 + +/obj/item/stack/sheet/rglass/cyborg/get_amount() + return min(round(source.energy / metcost), round(glasource.energy / glacost)) + +/obj/item/stack/sheet/rglass/cyborg/use(used, transfer = FALSE) // Requires special checks, because it uses two storages + if(get_amount(used)) //ensure we still have enough energy if called in a do_after chain + source.use_charge(used * metcost) + glasource.use_charge(used * glacost) + return TRUE + +/obj/item/stack/sheet/rglass/cyborg/add(amount) + source.add_charge(amount * metcost) + glasource.add_charge(amount * glacost) + +/obj/item/stack/sheet/rglass/get_main_recipes() + . = ..() + . += GLOB.reinforced_glass_recipes + +GLOBAL_LIST_INIT(prglass_recipes, list ( \ + new/datum/stack_recipe("directional reinforced window", /obj/structure/window/plasma/reinforced/unanchored, time = 0, on_floor = TRUE, window_checks = TRUE), \ + new/datum/stack_recipe("fulltile reinforced window", /obj/structure/window/plasma/reinforced/fulltile/unanchored, 2, time = 0, on_floor = TRUE, window_checks = TRUE) \ +)) + +/obj/item/stack/sheet/plasmarglass + name = "reinforced plasma glass" + desc = "A glass sheet made out of a plasma-silicate alloy and a rod matrix. It looks hopelessly tough and nearly fire-proof!" + singular_name = "reinforced plasma glass sheet" + icon_state = "sheet-prglass" + item_state = "sheet-prglass" + custom_materials = list(/datum/material/plasma=MINERAL_MATERIAL_AMOUNT * 0.5, /datum/material/glass=MINERAL_MATERIAL_AMOUNT, /datum/material/iron = MINERAL_MATERIAL_AMOUNT * 0.5) + armor = list("melee" = 20, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100) + resistance_flags = ACID_PROOF + material_flags = MATERIAL_NO_EFFECTS + 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 + +/obj/item/stack/sheet/plasmarglass/get_main_recipes() + . = ..() + . += GLOB.prglass_recipes + +GLOBAL_LIST_INIT(titaniumglass_recipes, list( + new/datum/stack_recipe("shuttle window", /obj/structure/window/shuttle/unanchored, 2, time = 0, on_floor = TRUE, window_checks = TRUE) + )) + +/obj/item/stack/sheet/titaniumglass + name = "titanium glass" + desc = "A glass sheet made out of a titanium-silicate alloy." + singular_name = "titanium glass sheet" + icon_state = "sheet-titaniumglass" + item_state = "sheet-titaniumglass" + custom_materials = list(/datum/material/titanium=MINERAL_MATERIAL_AMOUNT * 0.5, /datum/material/glass=MINERAL_MATERIAL_AMOUNT) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100) + resistance_flags = ACID_PROOF + merge_type = /obj/item/stack/sheet/titaniumglass + +/obj/item/stack/sheet/titaniumglass/get_main_recipes() + . = ..() + . += GLOB.titaniumglass_recipes + +GLOBAL_LIST_INIT(plastitaniumglass_recipes, list( + new/datum/stack_recipe("plastitanium window", /obj/structure/window/plasma/reinforced/plastitanium/unanchored, 2, time = 0, on_floor = TRUE, window_checks = TRUE) + )) + +/obj/item/stack/sheet/plastitaniumglass + name = "plastitanium glass" + desc = "A glass sheet made out of a plasma-titanium-silicate alloy." + singular_name = "plastitanium glass sheet" + icon_state = "sheet-plastitaniumglass" + item_state = "sheet-plastitaniumglass" + custom_materials = list(/datum/material/titanium=MINERAL_MATERIAL_AMOUNT * 0.5, /datum/material/plasma=MINERAL_MATERIAL_AMOUNT * 0.5, /datum/material/glass=MINERAL_MATERIAL_AMOUNT) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100) + material_flags = MATERIAL_NO_EFFECTS + resistance_flags = ACID_PROOF + merge_type = /obj/item/stack/sheet/plastitaniumglass + +/obj/item/stack/sheet/plastitaniumglass/get_main_recipes() + . = ..() + . += GLOB.plastitaniumglass_recipes + +/obj/item/shard + name = "shard" + desc = "A nasty looking shard of glass." + icon = 'icons/obj/shards.dmi' + icon_state = "large" + w_class = WEIGHT_CLASS_TINY + force = 5 + throwforce = 10 + item_state = "shard-glass" + lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' + custom_materials = list(/datum/material/glass=MINERAL_MATERIAL_AMOUNT) + attack_verb = list("stabbed", "slashed", "sliced", "cut") + hitsound = 'sound/weapons/bladeslice.ogg' + resistance_flags = ACID_PROOF + armor = list("melee" = 100, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 100) + max_integrity = 40 + sharpness = IS_SHARP + var/icon_prefix + var/obj/item/stack/sheet/weld_material = /obj/item/stack/sheet/glass + embedding = list("embed_chance" = 65) + + +/obj/item/shard/suicide_act(mob/user) + user.visible_message("[user] is slitting [user.p_their()] [pick("wrists", "throat")] with the shard of glass! It looks like [user.p_theyre()] trying to commit suicide.") + return (BRUTELOSS) + + +/obj/item/shard/Initialize() + . = ..() + AddComponent(/datum/component/caltrop, force) + AddComponent(/datum/component/butchering, 150, 65) + icon_state = pick("large", "medium", "small") + switch(icon_state) + if("small") + pixel_x = rand(-12, 12) + pixel_y = rand(-12, 12) + if("medium") + pixel_x = rand(-8, 8) + pixel_y = rand(-8, 8) + if("large") + pixel_x = rand(-5, 5) + pixel_y = rand(-5, 5) + if (icon_prefix) + icon_state = "[icon_prefix][icon_state]" + + var/turf/T = get_turf(src) + if(T && is_station_level(T.z)) + SSblackbox.record_feedback("tally", "station_mess_created", 1, name) + +/obj/item/shard/Destroy() + . = ..() + + var/turf/T = get_turf(src) + if(T && is_station_level(T.z)) + SSblackbox.record_feedback("tally", "station_mess_destroyed", 1, name) + +/obj/item/shard/afterattack(atom/A as mob|obj, mob/user, proximity) + . = ..() + if(!proximity || !(src in user)) + return + if(isturf(A)) + return + if(istype(A, /obj/item/storage)) + return + var/hit_hand = ((user.active_hand_index % 2 == 0) ? "r_" : "l_") + "arm" + if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(!H.gloves && !HAS_TRAIT(H, TRAIT_PIERCEIMMUNE)) // golems, etc + to_chat(H, "[src] cuts into your hand!") + H.apply_damage(force*0.5, BRUTE, hit_hand) + else if(ismonkey(user)) + var/mob/living/carbon/monkey/M = user + if(!HAS_TRAIT(M, TRAIT_PIERCEIMMUNE)) + to_chat(M, "[src] cuts into your hand!") + M.apply_damage(force*0.5, BRUTE, hit_hand) + +/obj/item/shard/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/lightreplacer)) + var/obj/item/lightreplacer/L = I + L.attackby(src, user) + else if(istype(I, /obj/item/stack/sheet/cloth)) + var/obj/item/stack/sheet/cloth/C = I + to_chat(user, "You begin to wrap the [C] around the [src]...") + if(do_after(user, 35, target = src)) + var/obj/item/kitchen/knife/shiv/S = new /obj/item/kitchen/knife/shiv + C.use(1) + to_chat(user, "You wrap the [C] around the [src] forming a makeshift weapon.") + remove_item_from_storage(src) + qdel(src) + user.put_in_hands(S) + + else + return ..() + +/obj/item/shard/welder_act(mob/living/user, obj/item/I) + ..() + if(I.use_tool(src, user, 0, volume=50)) + var/obj/item/stack/sheet/NG = new weld_material(user.loc) + for(var/obj/item/stack/sheet/G in user.loc) + if(G == NG) + continue + if(G.amount >= G.max_amount) + continue + G.attackby(NG, user) + to_chat(user, "You add the newly-formed [NG.name] to the stack. It now contains [NG.amount] sheet\s.") + qdel(src) + return TRUE + +/obj/item/shard/Crossed(atom/movable/AM) + if(isliving(AM)) + var/mob/living/L = AM + if(!(L.is_flying() || L.is_floating() || L.buckled)) + playsound(src, 'sound/effects/glass_step.ogg', HAS_TRAIT(L, TRAIT_LIGHT_STEP) ? 30 : 50, TRUE) + return ..() + +/obj/item/shard/plasma + name = "purple shard" + desc = "A nasty looking shard of plasma glass." + force = 6 + throwforce = 11 + icon_state = "plasmalarge" + custom_materials = list(/datum/material/plasma=MINERAL_MATERIAL_AMOUNT * 0.5, /datum/material/glass=MINERAL_MATERIAL_AMOUNT) + icon_prefix = "plasma" + weld_material = /obj/item/stack/sheet/plasmaglass diff --git a/code/game/objects/items/stacks/sheets/leather.dm b/code/game/objects/items/stacks/sheets/leather.dm index dab60955c180..9e2b0cca61a6 100644 --- a/code/game/objects/items/stacks/sheets/leather.dm +++ b/code/game/objects/items/stacks/sheets/leather.dm @@ -1,247 +1,247 @@ -/obj/item/stack/sheet/animalhide - name = "hide" - desc = "Something went wrong." - icon_state = "sheet-hide" - item_state = "sheet-hide" - novariants = TRUE - -/obj/item/stack/sheet/animalhide/human - name = "human skin" - desc = "The by-product of human farming." - singular_name = "human skin piece" - novariants = FALSE - -GLOBAL_LIST_INIT(human_recipes, list( \ - new/datum/stack_recipe("bloated human costume", /obj/item/clothing/suit/hooded/bloated_human, 5), \ - )) - -/obj/item/stack/sheet/animalhide/human/get_main_recipes() - . = ..() - . += GLOB.human_recipes - -/obj/item/stack/sheet/animalhide/generic - name = "skin" - desc = "A piece of skin." - singular_name = "skin piece" - novariants = FALSE - -/obj/item/stack/sheet/animalhide/corgi - name = "corgi hide" - desc = "The by-product of corgi farming." - singular_name = "corgi hide piece" - icon_state = "sheet-corgi" - item_state = "sheet-corgi" - - -GLOBAL_LIST_INIT(gondola_recipes, list ( \ - new/datum/stack_recipe("gondola mask", /obj/item/clothing/mask/gondola, 1), \ - new/datum/stack_recipe("gondola suit", /obj/item/clothing/under/costume/gondola, 2), \ - )) - -/obj/item/stack/sheet/animalhide/gondola - name = "gondola hide" - desc = "The extremely valuable product of gondola hunting." - singular_name = "gondola hide piece" - icon_state = "sheet-gondola" - item_state = "sheet-gondola" - -/obj/item/stack/sheet/animalhide/gondola/get_main_recipes() - . = ..() - . += GLOB.gondola_recipes - -GLOBAL_LIST_INIT(corgi_recipes, list ( \ - new/datum/stack_recipe("corgi costume", /obj/item/clothing/suit/hooded/ian_costume, 3), \ - )) - -/obj/item/stack/sheet/animalhide/corgi/get_main_recipes() - . = ..() - . += GLOB.corgi_recipes - -/obj/item/stack/sheet/animalhide/cat - name = "cat hide" - desc = "The by-product of cat farming." - singular_name = "cat hide piece" - icon_state = "sheet-cat" - item_state = "sheet-cat" - -/obj/item/stack/sheet/animalhide/monkey - name = "monkey hide" - desc = "The by-product of monkey farming." - singular_name = "monkey hide piece" - icon_state = "sheet-monkey" - item_state = "sheet-monkey" - -GLOBAL_LIST_INIT(monkey_recipes, list ( \ - new/datum/stack_recipe("monkey mask", /obj/item/clothing/mask/gas/monkeymask, 1), \ - new/datum/stack_recipe("monkey suit", /obj/item/clothing/suit/monkeysuit, 2), \ - )) - -/obj/item/stack/sheet/animalhide/monkey/get_main_recipes() - . = ..() - . += GLOB.monkey_recipes - -/obj/item/stack/sheet/animalhide/lizard - name = "lizard skin" - desc = "Sssssss..." - singular_name = "lizard skin piece" - icon_state = "sheet-lizard" - item_state = "sheet-lizard" - -/obj/item/stack/sheet/animalhide/xeno - name = "alien hide" - desc = "The skin of a terrible creature." - singular_name = "alien hide piece" - icon_state = "sheet-xeno" - item_state = "sheet-xeno" - -GLOBAL_LIST_INIT(xeno_recipes, list ( \ - new/datum/stack_recipe("alien helmet", /obj/item/clothing/head/xenos, 1), \ - new/datum/stack_recipe("alien suit", /obj/item/clothing/suit/xenos, 2), \ - )) - -/obj/item/stack/sheet/animalhide/xeno/get_main_recipes() - . = ..() - . += GLOB.xeno_recipes - -//don't see anywhere else to put these, maybe together they could be used to make the xenos suit? -/obj/item/stack/sheet/xenochitin - name = "alien chitin" - desc = "A piece of the hide of a terrible creature." - singular_name = "alien hide piece" - icon = 'icons/mob/alien.dmi' - icon_state = "chitin" - novariants = TRUE - -/obj/item/xenos_claw - name = "alien claw" - desc = "The claw of a terrible creature." - icon = 'icons/mob/alien.dmi' - icon_state = "claw" - -/obj/item/weed_extract - name = "weed extract" - desc = "A piece of slimy, purplish weed." - icon = 'icons/mob/alien.dmi' - icon_state = "weed_extract" - -/obj/item/stack/sheet/hairlesshide - name = "hairless hide" - desc = "This hide was stripped of its hair, but still needs washing and tanning." - singular_name = "hairless hide piece" - icon_state = "sheet-hairlesshide" - item_state = "sheet-hairlesshide" - -/obj/item/stack/sheet/wethide - name = "wet hide" - desc = "This hide has been cleaned but still needs to be dried." - singular_name = "wet hide piece" - icon_state = "sheet-wetleather" - item_state = "sheet-wetleather" - var/wetness = 30 //Reduced when exposed to high temperautres - var/drying_threshold_temperature = 500 //Kelvin to start drying - -/* - * Leather SHeet - */ -/obj/item/stack/sheet/leather - name = "leather" - desc = "The by-product of mob grinding." - singular_name = "leather piece" - icon_state = "sheet-leather" - item_state = "sheet-leather" - -GLOBAL_LIST_INIT(leather_recipes, list ( \ - new/datum/stack_recipe("wallet", /obj/item/storage/wallet, 1), \ - new/datum/stack_recipe("muzzle", /obj/item/clothing/mask/muzzle, 2), \ - new/datum/stack_recipe("botany gloves", /obj/item/clothing/gloves/botanic_leather, 3), \ - new/datum/stack_recipe("toolbelt", /obj/item/storage/belt/utility, 4), \ - new/datum/stack_recipe("leather satchel", /obj/item/storage/backpack/satchel/leather, 5), \ - new/datum/stack_recipe("bandolier", /obj/item/storage/belt/bandolier, 5), \ - new/datum/stack_recipe("leather jacket", /obj/item/clothing/suit/jacket/leather, 7), \ - new/datum/stack_recipe("leather shoes", /obj/item/clothing/shoes/laceup, 2), \ - new/datum/stack_recipe("leather overcoat", /obj/item/clothing/suit/jacket/leather/overcoat, 10), \ - new/datum/stack_recipe("saddle", /obj/item/saddle, 5), \ -)) - -/obj/item/stack/sheet/leather/get_main_recipes() - . = ..() - . += GLOB.leather_recipes -/* - * Sinew - */ -/obj/item/stack/sheet/sinew - name = "watcher sinew" - icon = 'icons/obj/mining.dmi' - desc = "Long stringy filaments which presumably came from a watcher's wings." - singular_name = "watcher sinew" - icon_state = "sinew" - novariants = TRUE - - -GLOBAL_LIST_INIT(sinew_recipes, list ( \ - new/datum/stack_recipe("sinew restraints", /obj/item/restraints/handcuffs/cable/sinew, 1), \ -)) - -/obj/item/stack/sheet/sinew/get_main_recipes() - . = ..() - . += GLOB.sinew_recipes - - /* - * Plates - */ -/obj/item/stack/sheet/animalhide/goliath_hide - name = "goliath hide plates" - desc = "Pieces of a goliath's rocky hide, these might be able to make your suit a bit more durable to attack from the local fauna." - icon = 'icons/obj/mining.dmi' - icon_state = "goliath_hide" - singular_name = "hide plate" - max_amount = 6 - novariants = FALSE - item_flags = NOBLUDGEON - w_class = WEIGHT_CLASS_NORMAL - layer = MOB_LAYER - - -/obj/item/stack/sheet/animalhide/ashdrake - name = "ash drake hide" - desc = "The strong, scaled hide of an ash drake." - icon = 'icons/obj/mining.dmi' - icon_state = "dragon_hide" - singular_name = "drake plate" - max_amount = 10 - novariants = FALSE - item_flags = NOBLUDGEON - w_class = WEIGHT_CLASS_NORMAL - layer = MOB_LAYER - - -//Step one - dehairing. - -/obj/item/stack/sheet/animalhide/attackby(obj/item/W, mob/user, params) - if(W.get_sharpness()) - playsound(loc, 'sound/weapons/slice.ogg', 50, TRUE, -1) - user.visible_message("[user] starts cutting hair off \the [src].", "You start cutting the hair off \the [src]...", "You hear the sound of a knife rubbing against flesh.") - if(do_after(user, 50, target = src)) - to_chat(user, "You cut the hair from this [src.singular_name].") - new /obj/item/stack/sheet/hairlesshide(user.drop_location(), 1) - use(1) - else - return ..() - - -//Step two - washing..... it's actually in washing machine code. - -//Step three - drying -/obj/item/stack/sheet/wethide/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) - ..() - if(exposed_temperature >= drying_threshold_temperature) - wetness-- - if(wetness == 0) - new /obj/item/stack/sheet/leather(drop_location(), 1) - wetness = initial(wetness) - use(1) - -/obj/item/stack/sheet/wethide/microwave_act(obj/machinery/microwave/MW) - ..() - new /obj/item/stack/sheet/leather(drop_location(), amount) - qdel(src) +/obj/item/stack/sheet/animalhide + name = "hide" + desc = "Something went wrong." + icon_state = "sheet-hide" + item_state = "sheet-hide" + novariants = TRUE + +/obj/item/stack/sheet/animalhide/human + name = "human skin" + desc = "The by-product of human farming." + singular_name = "human skin piece" + novariants = FALSE + +GLOBAL_LIST_INIT(human_recipes, list( \ + new/datum/stack_recipe("bloated human costume", /obj/item/clothing/suit/hooded/bloated_human, 5), \ + )) + +/obj/item/stack/sheet/animalhide/human/get_main_recipes() + . = ..() + . += GLOB.human_recipes + +/obj/item/stack/sheet/animalhide/generic + name = "skin" + desc = "A piece of skin." + singular_name = "skin piece" + novariants = FALSE + +/obj/item/stack/sheet/animalhide/corgi + name = "corgi hide" + desc = "The by-product of corgi farming." + singular_name = "corgi hide piece" + icon_state = "sheet-corgi" + item_state = "sheet-corgi" + + +GLOBAL_LIST_INIT(gondola_recipes, list ( \ + new/datum/stack_recipe("gondola mask", /obj/item/clothing/mask/gondola, 1), \ + new/datum/stack_recipe("gondola suit", /obj/item/clothing/under/costume/gondola, 2), \ + )) + +/obj/item/stack/sheet/animalhide/gondola + name = "gondola hide" + desc = "The extremely valuable product of gondola hunting." + singular_name = "gondola hide piece" + icon_state = "sheet-gondola" + item_state = "sheet-gondola" + +/obj/item/stack/sheet/animalhide/gondola/get_main_recipes() + . = ..() + . += GLOB.gondola_recipes + +GLOBAL_LIST_INIT(corgi_recipes, list ( \ + new/datum/stack_recipe("corgi costume", /obj/item/clothing/suit/hooded/ian_costume, 3), \ + )) + +/obj/item/stack/sheet/animalhide/corgi/get_main_recipes() + . = ..() + . += GLOB.corgi_recipes + +/obj/item/stack/sheet/animalhide/cat + name = "cat hide" + desc = "The by-product of cat farming." + singular_name = "cat hide piece" + icon_state = "sheet-cat" + item_state = "sheet-cat" + +/obj/item/stack/sheet/animalhide/monkey + name = "monkey hide" + desc = "The by-product of monkey farming." + singular_name = "monkey hide piece" + icon_state = "sheet-monkey" + item_state = "sheet-monkey" + +GLOBAL_LIST_INIT(monkey_recipes, list ( \ + new/datum/stack_recipe("monkey mask", /obj/item/clothing/mask/gas/monkeymask, 1), \ + new/datum/stack_recipe("monkey suit", /obj/item/clothing/suit/monkeysuit, 2), \ + )) + +/obj/item/stack/sheet/animalhide/monkey/get_main_recipes() + . = ..() + . += GLOB.monkey_recipes + +/obj/item/stack/sheet/animalhide/lizard + name = "lizard skin" + desc = "Sssssss..." + singular_name = "lizard skin piece" + icon_state = "sheet-lizard" + item_state = "sheet-lizard" + +/obj/item/stack/sheet/animalhide/xeno + name = "alien hide" + desc = "The skin of a terrible creature." + singular_name = "alien hide piece" + icon_state = "sheet-xeno" + item_state = "sheet-xeno" + +GLOBAL_LIST_INIT(xeno_recipes, list ( \ + new/datum/stack_recipe("alien helmet", /obj/item/clothing/head/xenos, 1), \ + new/datum/stack_recipe("alien suit", /obj/item/clothing/suit/xenos, 2), \ + )) + +/obj/item/stack/sheet/animalhide/xeno/get_main_recipes() + . = ..() + . += GLOB.xeno_recipes + +//don't see anywhere else to put these, maybe together they could be used to make the xenos suit? +/obj/item/stack/sheet/xenochitin + name = "alien chitin" + desc = "A piece of the hide of a terrible creature." + singular_name = "alien hide piece" + icon = 'icons/mob/alien.dmi' + icon_state = "chitin" + novariants = TRUE + +/obj/item/xenos_claw + name = "alien claw" + desc = "The claw of a terrible creature." + icon = 'icons/mob/alien.dmi' + icon_state = "claw" + +/obj/item/weed_extract + name = "weed extract" + desc = "A piece of slimy, purplish weed." + icon = 'icons/mob/alien.dmi' + icon_state = "weed_extract" + +/obj/item/stack/sheet/hairlesshide + name = "hairless hide" + desc = "This hide was stripped of its hair, but still needs washing and tanning." + singular_name = "hairless hide piece" + icon_state = "sheet-hairlesshide" + item_state = "sheet-hairlesshide" + +/obj/item/stack/sheet/wethide + name = "wet hide" + desc = "This hide has been cleaned but still needs to be dried." + singular_name = "wet hide piece" + icon_state = "sheet-wetleather" + item_state = "sheet-wetleather" + var/wetness = 30 //Reduced when exposed to high temperautres + var/drying_threshold_temperature = 500 //Kelvin to start drying + +/* + * Leather SHeet + */ +/obj/item/stack/sheet/leather + name = "leather" + desc = "The by-product of mob grinding." + singular_name = "leather piece" + icon_state = "sheet-leather" + item_state = "sheet-leather" + +GLOBAL_LIST_INIT(leather_recipes, list ( \ + new/datum/stack_recipe("wallet", /obj/item/storage/wallet, 1), \ + new/datum/stack_recipe("muzzle", /obj/item/clothing/mask/muzzle, 2), \ + new/datum/stack_recipe("botany gloves", /obj/item/clothing/gloves/botanic_leather, 3), \ + new/datum/stack_recipe("toolbelt", /obj/item/storage/belt/utility, 4), \ + new/datum/stack_recipe("leather satchel", /obj/item/storage/backpack/satchel/leather, 5), \ + new/datum/stack_recipe("bandolier", /obj/item/storage/belt/bandolier, 5), \ + new/datum/stack_recipe("leather jacket", /obj/item/clothing/suit/jacket/leather, 7), \ + new/datum/stack_recipe("leather shoes", /obj/item/clothing/shoes/laceup, 2), \ + new/datum/stack_recipe("leather overcoat", /obj/item/clothing/suit/jacket/leather/overcoat, 10), \ + new/datum/stack_recipe("saddle", /obj/item/saddle, 5), \ +)) + +/obj/item/stack/sheet/leather/get_main_recipes() + . = ..() + . += GLOB.leather_recipes +/* + * Sinew + */ +/obj/item/stack/sheet/sinew + name = "watcher sinew" + icon = 'icons/obj/mining.dmi' + desc = "Long stringy filaments which presumably came from a watcher's wings." + singular_name = "watcher sinew" + icon_state = "sinew" + novariants = TRUE + + +GLOBAL_LIST_INIT(sinew_recipes, list ( \ + new/datum/stack_recipe("sinew restraints", /obj/item/restraints/handcuffs/cable/sinew, 1), \ +)) + +/obj/item/stack/sheet/sinew/get_main_recipes() + . = ..() + . += GLOB.sinew_recipes + + /* + * Plates + */ +/obj/item/stack/sheet/animalhide/goliath_hide + name = "goliath hide plates" + desc = "Pieces of a goliath's rocky hide, these might be able to make your suit a bit more durable to attack from the local fauna." + icon = 'icons/obj/mining.dmi' + icon_state = "goliath_hide" + singular_name = "hide plate" + max_amount = 6 + novariants = FALSE + item_flags = NOBLUDGEON + w_class = WEIGHT_CLASS_NORMAL + layer = MOB_LAYER + + +/obj/item/stack/sheet/animalhide/ashdrake + name = "ash drake hide" + desc = "The strong, scaled hide of an ash drake." + icon = 'icons/obj/mining.dmi' + icon_state = "dragon_hide" + singular_name = "drake plate" + max_amount = 10 + novariants = FALSE + item_flags = NOBLUDGEON + w_class = WEIGHT_CLASS_NORMAL + layer = MOB_LAYER + + +//Step one - dehairing. + +/obj/item/stack/sheet/animalhide/attackby(obj/item/W, mob/user, params) + if(W.get_sharpness()) + playsound(loc, 'sound/weapons/slice.ogg', 50, TRUE, -1) + user.visible_message("[user] starts cutting hair off \the [src].", "You start cutting the hair off \the [src]...", "You hear the sound of a knife rubbing against flesh.") + if(do_after(user, 50, target = src)) + to_chat(user, "You cut the hair from this [src.singular_name].") + new /obj/item/stack/sheet/hairlesshide(user.drop_location(), 1) + use(1) + else + return ..() + + +//Step two - washing..... it's actually in washing machine code. + +//Step three - drying +/obj/item/stack/sheet/wethide/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) + ..() + if(exposed_temperature >= drying_threshold_temperature) + wetness-- + if(wetness == 0) + new /obj/item/stack/sheet/leather(drop_location(), 1) + wetness = initial(wetness) + use(1) + +/obj/item/stack/sheet/wethide/microwave_act(obj/machinery/microwave/MW) + ..() + new /obj/item/stack/sheet/leather(drop_location(), amount) + qdel(src) diff --git a/code/game/objects/items/stacks/sheets/light.dm b/code/game/objects/items/stacks/sheets/light.dm index da6cc64c9e35..84062a0ce694 100644 --- a/code/game/objects/items/stacks/sheets/light.dm +++ b/code/game/objects/items/stacks/sheets/light.dm @@ -1,36 +1,36 @@ -/obj/item/stack/light_w - name = "wired glass tile" - singular_name = "wired glass floor tile" - desc = "A glass tile, which is wired, somehow." - icon = 'icons/obj/tiles.dmi' - icon_state = "glass_wire" - w_class = WEIGHT_CLASS_NORMAL - force = 3 - throwforce = 5 - throw_speed = 3 - throw_range = 7 - flags_1 = CONDUCT_1 - max_amount = 60 - grind_results = list(/datum/reagent/silicon = 20, /datum/reagent/copper = 5) - -/obj/item/stack/light_w/attackby(obj/item/O, mob/user, params) - if(istype(O, /obj/item/stack/sheet/metal)) - var/obj/item/stack/sheet/metal/M = O - if (M.use(1)) - var/obj/item/L = new /obj/item/stack/tile/light(user.drop_location()) - to_chat(user, "You make a light tile.") - L.add_fingerprint(user) - use(1) - else - to_chat(user, "You need one metal sheet to finish the light tile!") - else - return ..() - -/obj/item/stack/light_w/wirecutter_act(mob/living/user, obj/item/I) - . = ..() - var/atom/Tsec = user.drop_location() - var/obj/item/stack/cable_coil/CC = new (Tsec, 5) - CC.add_fingerprint(user) - var/obj/item/stack/sheet/glass/G = new (Tsec) - G.add_fingerprint(user) - use(1) +/obj/item/stack/light_w + name = "wired glass tile" + singular_name = "wired glass floor tile" + desc = "A glass tile, which is wired, somehow." + icon = 'icons/obj/tiles.dmi' + icon_state = "glass_wire" + w_class = WEIGHT_CLASS_NORMAL + force = 3 + throwforce = 5 + throw_speed = 3 + throw_range = 7 + flags_1 = CONDUCT_1 + max_amount = 60 + grind_results = list(/datum/reagent/silicon = 20, /datum/reagent/copper = 5) + +/obj/item/stack/light_w/attackby(obj/item/O, mob/user, params) + if(istype(O, /obj/item/stack/sheet/metal)) + var/obj/item/stack/sheet/metal/M = O + if (M.use(1)) + var/obj/item/L = new /obj/item/stack/tile/light(user.drop_location()) + to_chat(user, "You make a light tile.") + L.add_fingerprint(user) + use(1) + else + to_chat(user, "You need one metal sheet to finish the light tile!") + else + return ..() + +/obj/item/stack/light_w/wirecutter_act(mob/living/user, obj/item/I) + . = ..() + var/atom/Tsec = user.drop_location() + var/obj/item/stack/cable_coil/CC = new (Tsec, 5) + CC.add_fingerprint(user) + var/obj/item/stack/sheet/glass/G = new (Tsec) + G.add_fingerprint(user) + use(1) diff --git a/code/game/objects/items/stacks/sheets/sheets.dm b/code/game/objects/items/stacks/sheets/sheets.dm index 53dfc44ca15f..1ec596137522 100644 --- a/code/game/objects/items/stacks/sheets/sheets.dm +++ b/code/game/objects/items/stacks/sheets/sheets.dm @@ -1,21 +1,21 @@ -/obj/item/stack/sheet - name = "sheet" - lefthand_file = 'icons/mob/inhands/misc/sheets_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/sheets_righthand.dmi' - full_w_class = WEIGHT_CLASS_NORMAL - force = 5 - throwforce = 5 - max_amount = 50 - throw_speed = 1 - throw_range = 3 - attack_verb = list("bashed", "battered", "bludgeoned", "thrashed", "smashed") - novariants = FALSE - var/sheettype = null //this is used for girders in the creation of walls/false walls - var/point_value = 0 //turn-in value for the gulag stacker - loosely relative to its rarity. - ///What type of wall does this sheet spawn - var/walltype - -/obj/item/stack/sheet/Initialize(mapload, new_amount, merge) - . = ..() - pixel_x = rand(-4, 4) - pixel_y = rand(-4, 4) +/obj/item/stack/sheet + name = "sheet" + lefthand_file = 'icons/mob/inhands/misc/sheets_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/sheets_righthand.dmi' + full_w_class = WEIGHT_CLASS_NORMAL + force = 5 + throwforce = 5 + max_amount = 50 + throw_speed = 1 + throw_range = 3 + attack_verb = list("bashed", "battered", "bludgeoned", "thrashed", "smashed") + novariants = FALSE + var/sheettype = null //this is used for girders in the creation of walls/false walls + var/point_value = 0 //turn-in value for the gulag stacker - loosely relative to its rarity. + ///What type of wall does this sheet spawn + var/walltype + +/obj/item/stack/sheet/Initialize(mapload, new_amount, merge) + . = ..() + pixel_x = rand(-4, 4) + pixel_y = rand(-4, 4) diff --git a/code/game/objects/items/storage/backpack.dm b/code/game/objects/items/storage/backpack.dm index 657a957e21e8..0f2f23bb1693 100644 --- a/code/game/objects/items/storage/backpack.dm +++ b/code/game/objects/items/storage/backpack.dm @@ -1,603 +1,603 @@ -/* Backpacks - * Contains: - * Backpack - * Backpack Types - * Satchel Types - */ - -/* - * Backpack - */ - -/obj/item/storage/backpack - name = "backpack" - desc = "You wear this on your back and put items into it." - icon_state = "backpack" - item_state = "backpack" - lefthand_file = 'icons/mob/inhands/equipment/backpack_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/backpack_righthand.dmi' - w_class = WEIGHT_CLASS_BULKY - slot_flags = ITEM_SLOT_BACK //ERROOOOO - resistance_flags = NONE - max_integrity = 300 - -/obj/item/storage/backpack/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_combined_w_class = 21 - STR.max_w_class = WEIGHT_CLASS_NORMAL - STR.max_items = 21 - -/* - * Backpack Types - */ - -/obj/item/storage/backpack/old/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_combined_w_class = 12 - -/obj/item/storage/backpack/holding - name = "bag of holding" - desc = "A backpack that opens into a localized pocket of bluespace." - icon_state = "holdingpack" - item_state = "holdingpack" - resistance_flags = FIRE_PROOF - item_flags = NO_MAT_REDEMPTION - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 60, "acid" = 50) - component_type = /datum/component/storage/concrete/bluespace/bag_of_holding - -/obj/item/storage/backpack/holding/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.allow_big_nesting = TRUE - STR.max_w_class = WEIGHT_CLASS_GIGANTIC - STR.max_combined_w_class = 35 - -/obj/item/storage/backpack/holding/suicide_act(mob/living/user) - user.visible_message("[user] is jumping into [src]! It looks like [user.p_theyre()] trying to commit suicide.") - user.dropItemToGround(src, TRUE) - user.Stun(100, ignore_canstun = TRUE) - sleep(20) - playsound(src, "rustle", 50, TRUE, -5) - qdel(user) - -/obj/item/storage/backpack/santabag - name = "Santa's Gift Bag" - desc = "Space Santa uses this to deliver presents to all the nice children in space in Christmas! Wow, it's pretty big!" - icon_state = "giftbag0" - item_state = "giftbag" - w_class = WEIGHT_CLASS_BULKY - -/obj/item/storage/backpack/santabag/Initialize() - . = ..() - regenerate_presents() - -/obj/item/storage/backpack/santabag/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_NORMAL - STR.max_combined_w_class = 60 - -/obj/item/storage/backpack/santabag/suicide_act(mob/user) - user.visible_message("[user] places [src] over [user.p_their()] head and pulls it tight! It looks like [user.p_they()] [user.p_are()]n't in the Christmas spirit...") - return (OXYLOSS) - -/obj/item/storage/backpack/santabag/proc/regenerate_presents() - addtimer(CALLBACK(src, .proc/regenerate_presents), 30 SECONDS) - - var/mob/M = get(loc, /mob) - if(!istype(M)) - return - if(M.mind && HAS_TRAIT(M.mind, TRAIT_CANNOT_OPEN_PRESENTS)) - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - var/turf/floor = get_turf(src) - var/obj/item/I = new /obj/item/a_gift/anything(floor) - if(STR.can_be_inserted(I, stop_messages=TRUE)) - STR.handle_item_insertion(I, prevent_warning=TRUE) - else - qdel(I) - - -/obj/item/storage/backpack/cultpack - name = "trophy rack" - desc = "It's useful for both carrying extra gear and proudly declaring your insanity." - icon_state = "cultpack" - item_state = "backpack" - -/obj/item/storage/backpack/clown - name = "Giggles von Honkerton" - desc = "It's a backpack made by Honk! Co." - icon_state = "clownpack" - item_state = "clownpack" - -/obj/item/storage/backpack/explorer - name = "explorer bag" - desc = "A robust backpack for stashing your loot." - icon_state = "explorerpack" - item_state = "explorerpack" - -/obj/item/storage/backpack/mime - name = "Parcel Parceaux" - desc = "A silent backpack made for those silent workers. Silence Co." - icon_state = "mimepack" - item_state = "mimepack" - -/obj/item/storage/backpack/medic - name = "medical backpack" - desc = "It's a backpack especially designed for use in a sterile environment." - icon_state = "medicalpack" - item_state = "medicalpack" - -/obj/item/storage/backpack/security - name = "security backpack" - desc = "It's a very robust backpack." - icon_state = "securitypack" - item_state = "securitypack" - -/obj/item/storage/backpack/captain - name = "captain's backpack" - desc = "It's a special backpack made exclusively for Nanotrasen officers." - icon_state = "captainpack" - item_state = "captainpack" - -/obj/item/storage/backpack/industrial - name = "industrial backpack" - desc = "It's a tough backpack for the daily grind of station life." - icon_state = "engiepack" - item_state = "engiepack" - resistance_flags = FIRE_PROOF - -/obj/item/storage/backpack/botany - name = "botany backpack" - desc = "It's a backpack made of all-natural fibers." - icon_state = "botpack" - item_state = "botpack" - -/obj/item/storage/backpack/chemistry - name = "chemistry backpack" - desc = "A backpack specially designed to repel stains and hazardous liquids." - icon_state = "chempack" - item_state = "chempack" - -/obj/item/storage/backpack/genetics - name = "genetics backpack" - desc = "A bag designed to be super tough, just in case someone hulks out on you." - icon_state = "genepack" - item_state = "genepack" - -/obj/item/storage/backpack/science - name = "science backpack" - desc = "A specially designed backpack. It's fire resistant and smells vaguely of plasma." - icon_state = "toxpack" - item_state = "toxpack" - -/obj/item/storage/backpack/virology - name = "virology backpack" - desc = "A backpack made of hypo-allergenic fibers. It's designed to help prevent the spread of disease. Smells like monkey." - icon_state = "viropack" - item_state = "viropack" - -/obj/item/storage/backpack/ert - name = "emergency response team commander backpack" - desc = "A spacious backpack with lots of pockets, worn by the Commander of an Emergency Response Team." - icon_state = "ert_commander" - item_state = "securitypack" - resistance_flags = FIRE_PROOF - -/obj/item/storage/backpack/ert/security - name = "emergency response team security backpack" - desc = "A spacious backpack with lots of pockets, worn by Security Officers of an Emergency Response Team." - icon_state = "ert_security" - -/obj/item/storage/backpack/ert/medical - name = "emergency response team medical backpack" - desc = "A spacious backpack with lots of pockets, worn by Medical Officers of an Emergency Response Team." - icon_state = "ert_medical" - -/obj/item/storage/backpack/ert/engineer - name = "emergency response team engineer backpack" - desc = "A spacious backpack with lots of pockets, worn by Engineers of an Emergency Response Team." - icon_state = "ert_engineering" - -/obj/item/storage/backpack/ert/janitor - name = "emergency response team janitor backpack" - desc = "A spacious backpack with lots of pockets, worn by Janitors of an Emergency Response Team." - icon_state = "ert_janitor" - -/obj/item/storage/backpack/ert/clown - name = "emergency response team clown backpack" - desc = "A spacious backpack with lots of pockets, worn by Clowns of an Emergency Response Team." - icon_state = "ert_clown" -/* - * Satchel Types - */ - -/obj/item/storage/backpack/satchel - name = "satchel" - desc = "A trendy looking satchel." - icon_state = "satchel-norm" - item_state = "satchel-norm" - -/obj/item/storage/backpack/satchel/leather - name = "leather satchel" - desc = "It's a very fancy satchel made with fine leather." - icon_state = "satchel" - item_state = "satchel" - -/obj/item/storage/backpack/satchel/leather/withwallet/PopulateContents() - new /obj/item/storage/wallet/random(src) - -/obj/item/storage/backpack/satchel/fireproof - resistance_flags = FIRE_PROOF - -/obj/item/storage/backpack/satchel/eng - name = "industrial satchel" - desc = "A tough satchel with extra pockets." - icon_state = "satchel-eng" - item_state = "satchel-eng" - resistance_flags = FIRE_PROOF - -/obj/item/storage/backpack/satchel/med - name = "medical satchel" - desc = "A sterile satchel used in medical departments." - icon_state = "satchel-med" - item_state = "satchel-med" - -/obj/item/storage/backpack/satchel/vir - name = "virologist satchel" - desc = "A sterile satchel with virologist colours." - icon_state = "satchel-vir" - item_state = "satchel-vir" - -/obj/item/storage/backpack/satchel/chem - name = "chemist satchel" - desc = "A sterile satchel with chemist colours." - icon_state = "satchel-chem" - item_state = "satchel-chem" - -/obj/item/storage/backpack/satchel/gen - name = "geneticist satchel" - desc = "A sterile satchel with geneticist colours." - icon_state = "satchel-gen" - item_state = "satchel-gen" - -/obj/item/storage/backpack/satchel/tox - name = "scientist satchel" - desc = "Useful for holding research materials." - icon_state = "satchel-tox" - item_state = "satchel-tox" - -/obj/item/storage/backpack/satchel/hyd - name = "botanist satchel" - desc = "A satchel made of all natural fibers." - icon_state = "satchel-hyd" - item_state = "satchel-hyd" - -/obj/item/storage/backpack/satchel/sec - name = "security satchel" - desc = "A robust satchel for security related needs." - icon_state = "satchel-sec" - item_state = "satchel-sec" - -/obj/item/storage/backpack/satchel/explorer - name = "explorer satchel" - desc = "A robust satchel for stashing your loot." - icon_state = "satchel-explorer" - item_state = "satchel-explorer" - -/obj/item/storage/backpack/satchel/cap - name = "captain's satchel" - desc = "An exclusive satchel for Nanotrasen officers." - icon_state = "satchel-cap" - item_state = "satchel-cap" - -/obj/item/storage/backpack/satchel/flat - name = "smuggler's satchel" - desc = "A very slim satchel that can easily fit into tight spaces." - icon_state = "satchel-flat" - item_state = "satchel-flat" - w_class = WEIGHT_CLASS_NORMAL //Can fit in backpacks itself. - -/obj/item/storage/backpack/satchel/flat/Initialize(mapload) - . = ..() - AddElement(/datum/element/undertile, TRAIT_T_RAY_VISIBLE, INVISIBILITY_OBSERVER, use_anchor = TRUE) - -/obj/item/storage/backpack/satchel/flat/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_combined_w_class = 15 - STR.set_holdable(null, list(/obj/item/storage/backpack/satchel/flat)) //muh recursive backpacks) - -/obj/item/storage/backpack/satchel/flat/PopulateContents() - var/datum/supply_pack/costumes_toys/randomised/contraband/C = new - for(var/i in 1 to 2) - var/ctype = pick(C.contains) - new ctype(src) - - qdel(C) - -/obj/item/storage/backpack/satchel/flat/with_tools/PopulateContents() - new /obj/item/stack/tile/plasteel(src) - new /obj/item/crowbar(src) - - ..() - -/obj/item/storage/backpack/satchel/flat/empty/PopulateContents() - return - -/obj/item/storage/backpack/duffelbag - name = "duffel bag" - desc = "A large duffel bag for holding extra things." - icon_state = "duffel" - item_state = "duffel" - slowdown = 1 - -/obj/item/storage/backpack/duffelbag/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_combined_w_class = 30 - -/obj/item/storage/backpack/duffelbag/captain - name = "captain's duffel bag" - desc = "A large duffel bag for holding extra captainly goods." - icon_state = "duffel-captain" - item_state = "duffel-captain" - -/obj/item/storage/backpack/duffelbag/med - name = "medical duffel bag" - desc = "A large duffel bag for holding extra medical supplies." - icon_state = "duffel-med" - item_state = "duffel-med" - -/obj/item/storage/backpack/duffelbag/med/surgery - name = "surgical duffel bag" - desc = "A large duffel bag for holding extra medical supplies - this one seems to be designed for holding surgical tools." - -/obj/item/storage/backpack/duffelbag/med/surgery/PopulateContents() - new /obj/item/scalpel(src) - new /obj/item/hemostat(src) - new /obj/item/retractor(src) - new /obj/item/circular_saw(src) - new /obj/item/surgicaldrill(src) - new /obj/item/cautery(src) - new /obj/item/clothing/mask/surgical(src) - new /obj/item/razor(src) - -/obj/item/storage/backpack/duffelbag/sec - name = "security duffel bag" - desc = "A large duffel bag for holding extra security supplies and ammunition." - icon_state = "duffel-sec" - item_state = "duffel-sec" - -/obj/item/storage/backpack/duffelbag/sec/surgery - name = "surgical duffel bag" - desc = "A large duffel bag for holding extra supplies - this one has a material inlay with space for various sharp-looking tools." - -/obj/item/storage/backpack/duffelbag/sec/surgery/PopulateContents() - new /obj/item/scalpel(src) - new /obj/item/hemostat(src) - new /obj/item/retractor(src) - new /obj/item/circular_saw(src) - new /obj/item/surgicaldrill(src) - new /obj/item/cautery(src) - new /obj/item/clothing/mask/surgical(src) - -/obj/item/storage/backpack/duffelbag/engineering - name = "industrial duffel bag" - desc = "A large duffel bag for holding extra tools and supplies." - icon_state = "duffel-eng" - item_state = "duffel-eng" - resistance_flags = FIRE_PROOF - -/obj/item/storage/backpack/duffelbag/drone - name = "drone duffel bag" - desc = "A large duffel bag for holding tools and hats." - icon_state = "duffel-drone" - item_state = "duffel-drone" - resistance_flags = FIRE_PROOF - -/obj/item/storage/backpack/duffelbag/drone/PopulateContents() - new /obj/item/screwdriver(src) - new /obj/item/wrench(src) - new /obj/item/weldingtool(src) - new /obj/item/crowbar(src) - new /obj/item/stack/cable_coil/random(src) //Random from Wasp Smartwire Revert - new /obj/item/wirecutters(src) - new /obj/item/multitool(src) - -/obj/item/storage/backpack/duffelbag/clown - name = "clown's duffel bag" - desc = "A large duffel bag for holding lots of funny gags!" - icon_state = "duffel-clown" - item_state = "duffel-clown" - -/obj/item/storage/backpack/duffelbag/clown/cream_pie/PopulateContents() - for(var/i in 1 to 10) - new /obj/item/reagent_containers/food/snacks/pie/cream(src) - -/obj/item/storage/backpack/fireproof - resistance_flags = FIRE_PROOF - -/obj/item/storage/backpack/duffelbag/syndie - name = "suspicious looking duffel bag" - desc = "A large duffel bag for holding extra tactical supplies." - icon_state = "duffel-syndie" - item_state = "duffel-syndieammo" - slowdown = 0 - resistance_flags = FIRE_PROOF - -/obj/item/storage/backpack/duffelbag/syndie/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.silent = TRUE - -/obj/item/storage/backpack/duffelbag/syndie/hitman - desc = "A large duffel bag for holding extra things. There is a Nanotrasen logo on the back." - icon_state = "duffel-syndieammo" - item_state = "duffel-syndieammo" - -/obj/item/storage/backpack/duffelbag/syndie/hitman/PopulateContents() - new /obj/item/clothing/under/suit/black(src) - new /obj/item/clothing/accessory/waistcoat(src) - new /obj/item/clothing/suit/toggle/lawyer/black(src) - new /obj/item/clothing/shoes/laceup(src) - new /obj/item/clothing/gloves/color/black(src) - new /obj/item/clothing/glasses/sunglasses(src) - new /obj/item/clothing/head/fedora(src) - -/obj/item/storage/backpack/duffelbag/syndie/med - name = "medical duffel bag" - desc = "A large duffel bag for holding extra tactical medical supplies." - icon_state = "duffel-syndiemed" - item_state = "duffel-syndiemed" - -/obj/item/storage/backpack/duffelbag/syndie/surgery - name = "surgery duffel bag" - desc = "A suspicious looking duffel bag for holding surgery tools." - icon_state = "duffel-syndiemed" - item_state = "duffel-syndiemed" - -/obj/item/storage/backpack/duffelbag/syndie/surgery/PopulateContents() - new /obj/item/surgicaldrill/advanced(src) - new /obj/item/scalpel/advanced(src) - new /obj/item/retractor/advanced(src) - new /obj/item/clothing/suit/straight_jacket(src) - new /obj/item/clothing/mask/muzzle(src) - new /obj/item/mmi/syndie(src) - -/obj/item/storage/backpack/duffelbag/syndie/ammo - name = "ammunition duffel bag" - desc = "A large duffel bag for holding extra weapons ammunition and supplies." - icon_state = "duffel-syndieammo" - item_state = "duffel-syndieammo" - -/obj/item/storage/backpack/duffelbag/syndie/ammo/shotgun - desc = "A large duffel bag, packed to the brim with Bulldog shotgun magazines." - -/obj/item/storage/backpack/duffelbag/syndie/ammo/shotgun/PopulateContents() - for(var/i in 1 to 6) - new /obj/item/ammo_box/magazine/m12g(src) - new /obj/item/ammo_box/magazine/m12g/slug(src) - new /obj/item/ammo_box/magazine/m12g/slug(src) - new /obj/item/ammo_box/magazine/m12g/dragon(src) - -/obj/item/storage/backpack/duffelbag/syndie/ammo/smg - desc = "A large duffel bag, packed to the brim with C-20r magazines." - -/obj/item/storage/backpack/duffelbag/syndie/ammo/smg/PopulateContents() - for(var/i in 1 to 9) - new /obj/item/ammo_box/magazine/smgm45(src) - -/obj/item/storage/backpack/duffelbag/syndie/ammo/mech - desc = "A large duffel bag, packed to the brim with various exosuit ammo." - -/obj/item/storage/backpack/duffelbag/syndie/ammo/mech/PopulateContents() - new /obj/item/mecha_ammo/scattershot(src) - new /obj/item/mecha_ammo/scattershot(src) - new /obj/item/mecha_ammo/scattershot(src) - new /obj/item/mecha_ammo/scattershot(src) - new /obj/item/storage/belt/utility/syndicate(src) - -/obj/item/storage/backpack/duffelbag/syndie/ammo/mauler - desc = "A large duffel bag, packed to the brim with various exosuit ammo." - -/obj/item/storage/backpack/duffelbag/syndie/ammo/mauler/PopulateContents() - new /obj/item/mecha_ammo/lmg(src) - new /obj/item/mecha_ammo/lmg(src) - new /obj/item/mecha_ammo/lmg(src) - new /obj/item/mecha_ammo/scattershot(src) - new /obj/item/mecha_ammo/scattershot(src) - new /obj/item/mecha_ammo/scattershot(src) - new /obj/item/mecha_ammo/missiles_he(src) - new /obj/item/mecha_ammo/missiles_he(src) - new /obj/item/mecha_ammo/missiles_he(src) - -/obj/item/storage/backpack/duffelbag/syndie/c20rbundle - desc = "A large duffel bag containing a C-20r, some magazines, and a cheap looking suppressor." - -/obj/item/storage/backpack/duffelbag/syndie/c20rbundle/PopulateContents() - new /obj/item/ammo_box/magazine/smgm45(src) - new /obj/item/ammo_box/magazine/smgm45(src) - new /obj/item/gun/ballistic/automatic/c20r(src) - new /obj/item/suppressor/specialoffer(src) - -/obj/item/storage/backpack/duffelbag/syndie/bulldogbundle - desc = "A large duffel bag containing a Bulldog, some drums, and a pair of thermal imaging glasses." - -/obj/item/storage/backpack/duffelbag/syndie/bulldogbundle/PopulateContents() - new /obj/item/gun/ballistic/shotgun/bulldog(src) - new /obj/item/ammo_box/magazine/m12g(src) - new /obj/item/ammo_box/magazine/m12g(src) - new /obj/item/clothing/glasses/thermal/syndi(src) - -/obj/item/storage/backpack/duffelbag/syndie/med/medicalbundle - desc = "A large duffel bag containing a medical equipment, a Donksoft LMG, a big jumbo box of riot darts, and a knock-off pair of magboots." - -/obj/item/storage/backpack/duffelbag/syndie/med/medicalbundle/PopulateContents() - new /obj/item/clothing/shoes/magboots/syndie(src) - new /obj/item/storage/firstaid/tactical(src) - new /obj/item/gun/ballistic/automatic/l6_saw/toy(src) - new /obj/item/ammo_box/foambox/riot(src) - -/obj/item/storage/backpack/duffelbag/syndie/med/bioterrorbundle - desc = "A large duffel bag containing deadly chemicals, a handheld chem sprayer, Bioterror foam grenade, a Donksoft assault rifle, box of riot grade darts, a dart pistol, and a box of syringes." - -/obj/item/storage/backpack/duffelbag/syndie/med/bioterrorbundle/PopulateContents() - new /obj/item/reagent_containers/spray/chemsprayer/bioterror(src) - new /obj/item/storage/box/syndie_kit/chemical(src) - new /obj/item/gun/syringe/syndicate(src) - new /obj/item/gun/ballistic/automatic/c20r/toy(src) - new /obj/item/storage/box/syringes(src) - new /obj/item/ammo_box/foambox/riot(src) - new /obj/item/grenade/chem_grenade/bioterrorfoam(src) - if(prob(5)) - new /obj/item/reagent_containers/food/snacks/pizza/pineapple(src) - -/obj/item/storage/backpack/duffelbag/syndie/c4/PopulateContents() - for(var/i in 1 to 10) - new /obj/item/grenade/c4(src) - -/obj/item/storage/backpack/duffelbag/syndie/x4/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/grenade/c4/x4(src) - -/obj/item/storage/backpack/duffelbag/syndie/firestarter - desc = "A large duffel bag containing a New Russian pyro backpack sprayer, Elite hardsuit, a Stechkin APS pistol, minibomb, ammo, and other equipment." - -/obj/item/storage/backpack/duffelbag/syndie/firestarter/PopulateContents() - new /obj/item/clothing/under/syndicate/soviet(src) - new /obj/item/watertank/op(src) - new /obj/item/clothing/suit/space/hardsuit/syndi/elite(src) - new /obj/item/gun/ballistic/automatic/pistol/APS(src) - new /obj/item/ammo_box/magazine/pistolm9mm(src) - new /obj/item/ammo_box/magazine/pistolm9mm(src) - new /obj/item/reagent_containers/food/drinks/bottle/vodka/badminka(src) - new /obj/item/reagent_containers/hypospray/medipen/stimulants(src) - new /obj/item/grenade/syndieminibomb(src) - -// For ClownOps. -/obj/item/storage/backpack/duffelbag/clown/syndie/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - slowdown = 0 - STR.silent = TRUE - -/obj/item/storage/backpack/duffelbag/clown/syndie/PopulateContents() - new /obj/item/pda/clown(src) - new /obj/item/clothing/under/rank/civilian/clown(src) - new /obj/item/clothing/shoes/clown_shoes(src) - new /obj/item/clothing/mask/gas/clown_hat(src) - new /obj/item/bikehorn(src) - new /obj/item/implanter/sad_trombone(src) - -/obj/item/storage/backpack/henchmen - name = "wings" - desc = "Granted to the henchmen who deserve it. This probably doesn't include you." - icon_state = "henchmen" - item_state = "henchmen" - -/obj/item/storage/backpack/duffelbag/cops - name = "police bag" - desc = "A large duffel bag for holding extra police gear." - slowdown = 0 +/* Backpacks + * Contains: + * Backpack + * Backpack Types + * Satchel Types + */ + +/* + * Backpack + */ + +/obj/item/storage/backpack + name = "backpack" + desc = "You wear this on your back and put items into it." + icon_state = "backpack" + item_state = "backpack" + lefthand_file = 'icons/mob/inhands/equipment/backpack_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/backpack_righthand.dmi' + w_class = WEIGHT_CLASS_BULKY + slot_flags = ITEM_SLOT_BACK //ERROOOOO + resistance_flags = NONE + max_integrity = 300 + +/obj/item/storage/backpack/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_combined_w_class = 21 + STR.max_w_class = WEIGHT_CLASS_NORMAL + STR.max_items = 21 + +/* + * Backpack Types + */ + +/obj/item/storage/backpack/old/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_combined_w_class = 12 + +/obj/item/storage/backpack/holding + name = "bag of holding" + desc = "A backpack that opens into a localized pocket of bluespace." + icon_state = "holdingpack" + item_state = "holdingpack" + resistance_flags = FIRE_PROOF + item_flags = NO_MAT_REDEMPTION + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 60, "acid" = 50) + component_type = /datum/component/storage/concrete/bluespace/bag_of_holding + +/obj/item/storage/backpack/holding/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.allow_big_nesting = TRUE + STR.max_w_class = WEIGHT_CLASS_GIGANTIC + STR.max_combined_w_class = 35 + +/obj/item/storage/backpack/holding/suicide_act(mob/living/user) + user.visible_message("[user] is jumping into [src]! It looks like [user.p_theyre()] trying to commit suicide.") + user.dropItemToGround(src, TRUE) + user.Stun(100, ignore_canstun = TRUE) + sleep(20) + playsound(src, "rustle", 50, TRUE, -5) + qdel(user) + +/obj/item/storage/backpack/santabag + name = "Santa's Gift Bag" + desc = "Space Santa uses this to deliver presents to all the nice children in space in Christmas! Wow, it's pretty big!" + icon_state = "giftbag0" + item_state = "giftbag" + w_class = WEIGHT_CLASS_BULKY + +/obj/item/storage/backpack/santabag/Initialize() + . = ..() + regenerate_presents() + +/obj/item/storage/backpack/santabag/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_NORMAL + STR.max_combined_w_class = 60 + +/obj/item/storage/backpack/santabag/suicide_act(mob/user) + user.visible_message("[user] places [src] over [user.p_their()] head and pulls it tight! It looks like [user.p_they()] [user.p_are()]n't in the Christmas spirit...") + return (OXYLOSS) + +/obj/item/storage/backpack/santabag/proc/regenerate_presents() + addtimer(CALLBACK(src, .proc/regenerate_presents), 30 SECONDS) + + var/mob/M = get(loc, /mob) + if(!istype(M)) + return + if(M.mind && HAS_TRAIT(M.mind, TRAIT_CANNOT_OPEN_PRESENTS)) + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + var/turf/floor = get_turf(src) + var/obj/item/I = new /obj/item/a_gift/anything(floor) + if(STR.can_be_inserted(I, stop_messages=TRUE)) + STR.handle_item_insertion(I, prevent_warning=TRUE) + else + qdel(I) + + +/obj/item/storage/backpack/cultpack + name = "trophy rack" + desc = "It's useful for both carrying extra gear and proudly declaring your insanity." + icon_state = "cultpack" + item_state = "backpack" + +/obj/item/storage/backpack/clown + name = "Giggles von Honkerton" + desc = "It's a backpack made by Honk! Co." + icon_state = "clownpack" + item_state = "clownpack" + +/obj/item/storage/backpack/explorer + name = "explorer bag" + desc = "A robust backpack for stashing your loot." + icon_state = "explorerpack" + item_state = "explorerpack" + +/obj/item/storage/backpack/mime + name = "Parcel Parceaux" + desc = "A silent backpack made for those silent workers. Silence Co." + icon_state = "mimepack" + item_state = "mimepack" + +/obj/item/storage/backpack/medic + name = "medical backpack" + desc = "It's a backpack especially designed for use in a sterile environment." + icon_state = "medicalpack" + item_state = "medicalpack" + +/obj/item/storage/backpack/security + name = "security backpack" + desc = "It's a very robust backpack." + icon_state = "securitypack" + item_state = "securitypack" + +/obj/item/storage/backpack/captain + name = "captain's backpack" + desc = "It's a special backpack made exclusively for Nanotrasen officers." + icon_state = "captainpack" + item_state = "captainpack" + +/obj/item/storage/backpack/industrial + name = "industrial backpack" + desc = "It's a tough backpack for the daily grind of station life." + icon_state = "engiepack" + item_state = "engiepack" + resistance_flags = FIRE_PROOF + +/obj/item/storage/backpack/botany + name = "botany backpack" + desc = "It's a backpack made of all-natural fibers." + icon_state = "botpack" + item_state = "botpack" + +/obj/item/storage/backpack/chemistry + name = "chemistry backpack" + desc = "A backpack specially designed to repel stains and hazardous liquids." + icon_state = "chempack" + item_state = "chempack" + +/obj/item/storage/backpack/genetics + name = "genetics backpack" + desc = "A bag designed to be super tough, just in case someone hulks out on you." + icon_state = "genepack" + item_state = "genepack" + +/obj/item/storage/backpack/science + name = "science backpack" + desc = "A specially designed backpack. It's fire resistant and smells vaguely of plasma." + icon_state = "toxpack" + item_state = "toxpack" + +/obj/item/storage/backpack/virology + name = "virology backpack" + desc = "A backpack made of hypo-allergenic fibers. It's designed to help prevent the spread of disease. Smells like monkey." + icon_state = "viropack" + item_state = "viropack" + +/obj/item/storage/backpack/ert + name = "emergency response team commander backpack" + desc = "A spacious backpack with lots of pockets, worn by the Commander of an Emergency Response Team." + icon_state = "ert_commander" + item_state = "securitypack" + resistance_flags = FIRE_PROOF + +/obj/item/storage/backpack/ert/security + name = "emergency response team security backpack" + desc = "A spacious backpack with lots of pockets, worn by Security Officers of an Emergency Response Team." + icon_state = "ert_security" + +/obj/item/storage/backpack/ert/medical + name = "emergency response team medical backpack" + desc = "A spacious backpack with lots of pockets, worn by Medical Officers of an Emergency Response Team." + icon_state = "ert_medical" + +/obj/item/storage/backpack/ert/engineer + name = "emergency response team engineer backpack" + desc = "A spacious backpack with lots of pockets, worn by Engineers of an Emergency Response Team." + icon_state = "ert_engineering" + +/obj/item/storage/backpack/ert/janitor + name = "emergency response team janitor backpack" + desc = "A spacious backpack with lots of pockets, worn by Janitors of an Emergency Response Team." + icon_state = "ert_janitor" + +/obj/item/storage/backpack/ert/clown + name = "emergency response team clown backpack" + desc = "A spacious backpack with lots of pockets, worn by Clowns of an Emergency Response Team." + icon_state = "ert_clown" +/* + * Satchel Types + */ + +/obj/item/storage/backpack/satchel + name = "satchel" + desc = "A trendy looking satchel." + icon_state = "satchel-norm" + item_state = "satchel-norm" + +/obj/item/storage/backpack/satchel/leather + name = "leather satchel" + desc = "It's a very fancy satchel made with fine leather." + icon_state = "satchel" + item_state = "satchel" + +/obj/item/storage/backpack/satchel/leather/withwallet/PopulateContents() + new /obj/item/storage/wallet/random(src) + +/obj/item/storage/backpack/satchel/fireproof + resistance_flags = FIRE_PROOF + +/obj/item/storage/backpack/satchel/eng + name = "industrial satchel" + desc = "A tough satchel with extra pockets." + icon_state = "satchel-eng" + item_state = "satchel-eng" + resistance_flags = FIRE_PROOF + +/obj/item/storage/backpack/satchel/med + name = "medical satchel" + desc = "A sterile satchel used in medical departments." + icon_state = "satchel-med" + item_state = "satchel-med" + +/obj/item/storage/backpack/satchel/vir + name = "virologist satchel" + desc = "A sterile satchel with virologist colours." + icon_state = "satchel-vir" + item_state = "satchel-vir" + +/obj/item/storage/backpack/satchel/chem + name = "chemist satchel" + desc = "A sterile satchel with chemist colours." + icon_state = "satchel-chem" + item_state = "satchel-chem" + +/obj/item/storage/backpack/satchel/gen + name = "geneticist satchel" + desc = "A sterile satchel with geneticist colours." + icon_state = "satchel-gen" + item_state = "satchel-gen" + +/obj/item/storage/backpack/satchel/tox + name = "scientist satchel" + desc = "Useful for holding research materials." + icon_state = "satchel-tox" + item_state = "satchel-tox" + +/obj/item/storage/backpack/satchel/hyd + name = "botanist satchel" + desc = "A satchel made of all natural fibers." + icon_state = "satchel-hyd" + item_state = "satchel-hyd" + +/obj/item/storage/backpack/satchel/sec + name = "security satchel" + desc = "A robust satchel for security related needs." + icon_state = "satchel-sec" + item_state = "satchel-sec" + +/obj/item/storage/backpack/satchel/explorer + name = "explorer satchel" + desc = "A robust satchel for stashing your loot." + icon_state = "satchel-explorer" + item_state = "satchel-explorer" + +/obj/item/storage/backpack/satchel/cap + name = "captain's satchel" + desc = "An exclusive satchel for Nanotrasen officers." + icon_state = "satchel-cap" + item_state = "satchel-cap" + +/obj/item/storage/backpack/satchel/flat + name = "smuggler's satchel" + desc = "A very slim satchel that can easily fit into tight spaces." + icon_state = "satchel-flat" + item_state = "satchel-flat" + w_class = WEIGHT_CLASS_NORMAL //Can fit in backpacks itself. + +/obj/item/storage/backpack/satchel/flat/Initialize(mapload) + . = ..() + AddElement(/datum/element/undertile, TRAIT_T_RAY_VISIBLE, INVISIBILITY_OBSERVER, use_anchor = TRUE) + +/obj/item/storage/backpack/satchel/flat/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_combined_w_class = 15 + STR.set_holdable(null, list(/obj/item/storage/backpack/satchel/flat)) //muh recursive backpacks) + +/obj/item/storage/backpack/satchel/flat/PopulateContents() + var/datum/supply_pack/costumes_toys/randomised/contraband/C = new + for(var/i in 1 to 2) + var/ctype = pick(C.contains) + new ctype(src) + + qdel(C) + +/obj/item/storage/backpack/satchel/flat/with_tools/PopulateContents() + new /obj/item/stack/tile/plasteel(src) + new /obj/item/crowbar(src) + + ..() + +/obj/item/storage/backpack/satchel/flat/empty/PopulateContents() + return + +/obj/item/storage/backpack/duffelbag + name = "duffel bag" + desc = "A large duffel bag for holding extra things." + icon_state = "duffel" + item_state = "duffel" + slowdown = 1 + +/obj/item/storage/backpack/duffelbag/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_combined_w_class = 30 + +/obj/item/storage/backpack/duffelbag/captain + name = "captain's duffel bag" + desc = "A large duffel bag for holding extra captainly goods." + icon_state = "duffel-captain" + item_state = "duffel-captain" + +/obj/item/storage/backpack/duffelbag/med + name = "medical duffel bag" + desc = "A large duffel bag for holding extra medical supplies." + icon_state = "duffel-med" + item_state = "duffel-med" + +/obj/item/storage/backpack/duffelbag/med/surgery + name = "surgical duffel bag" + desc = "A large duffel bag for holding extra medical supplies - this one seems to be designed for holding surgical tools." + +/obj/item/storage/backpack/duffelbag/med/surgery/PopulateContents() + new /obj/item/scalpel(src) + new /obj/item/hemostat(src) + new /obj/item/retractor(src) + new /obj/item/circular_saw(src) + new /obj/item/surgicaldrill(src) + new /obj/item/cautery(src) + new /obj/item/clothing/mask/surgical(src) + new /obj/item/razor(src) + +/obj/item/storage/backpack/duffelbag/sec + name = "security duffel bag" + desc = "A large duffel bag for holding extra security supplies and ammunition." + icon_state = "duffel-sec" + item_state = "duffel-sec" + +/obj/item/storage/backpack/duffelbag/sec/surgery + name = "surgical duffel bag" + desc = "A large duffel bag for holding extra supplies - this one has a material inlay with space for various sharp-looking tools." + +/obj/item/storage/backpack/duffelbag/sec/surgery/PopulateContents() + new /obj/item/scalpel(src) + new /obj/item/hemostat(src) + new /obj/item/retractor(src) + new /obj/item/circular_saw(src) + new /obj/item/surgicaldrill(src) + new /obj/item/cautery(src) + new /obj/item/clothing/mask/surgical(src) + +/obj/item/storage/backpack/duffelbag/engineering + name = "industrial duffel bag" + desc = "A large duffel bag for holding extra tools and supplies." + icon_state = "duffel-eng" + item_state = "duffel-eng" + resistance_flags = FIRE_PROOF + +/obj/item/storage/backpack/duffelbag/drone + name = "drone duffel bag" + desc = "A large duffel bag for holding tools and hats." + icon_state = "duffel-drone" + item_state = "duffel-drone" + resistance_flags = FIRE_PROOF + +/obj/item/storage/backpack/duffelbag/drone/PopulateContents() + new /obj/item/screwdriver(src) + new /obj/item/wrench(src) + new /obj/item/weldingtool(src) + new /obj/item/crowbar(src) + new /obj/item/stack/cable_coil/random(src) //Random from Wasp Smartwire Revert + new /obj/item/wirecutters(src) + new /obj/item/multitool(src) + +/obj/item/storage/backpack/duffelbag/clown + name = "clown's duffel bag" + desc = "A large duffel bag for holding lots of funny gags!" + icon_state = "duffel-clown" + item_state = "duffel-clown" + +/obj/item/storage/backpack/duffelbag/clown/cream_pie/PopulateContents() + for(var/i in 1 to 10) + new /obj/item/reagent_containers/food/snacks/pie/cream(src) + +/obj/item/storage/backpack/fireproof + resistance_flags = FIRE_PROOF + +/obj/item/storage/backpack/duffelbag/syndie + name = "suspicious looking duffel bag" + desc = "A large duffel bag for holding extra tactical supplies." + icon_state = "duffel-syndie" + item_state = "duffel-syndieammo" + slowdown = 0 + resistance_flags = FIRE_PROOF + +/obj/item/storage/backpack/duffelbag/syndie/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.silent = TRUE + +/obj/item/storage/backpack/duffelbag/syndie/hitman + desc = "A large duffel bag for holding extra things. There is a Nanotrasen logo on the back." + icon_state = "duffel-syndieammo" + item_state = "duffel-syndieammo" + +/obj/item/storage/backpack/duffelbag/syndie/hitman/PopulateContents() + new /obj/item/clothing/under/suit/black(src) + new /obj/item/clothing/accessory/waistcoat(src) + new /obj/item/clothing/suit/toggle/lawyer/black(src) + new /obj/item/clothing/shoes/laceup(src) + new /obj/item/clothing/gloves/color/black(src) + new /obj/item/clothing/glasses/sunglasses(src) + new /obj/item/clothing/head/fedora(src) + +/obj/item/storage/backpack/duffelbag/syndie/med + name = "medical duffel bag" + desc = "A large duffel bag for holding extra tactical medical supplies." + icon_state = "duffel-syndiemed" + item_state = "duffel-syndiemed" + +/obj/item/storage/backpack/duffelbag/syndie/surgery + name = "surgery duffel bag" + desc = "A suspicious looking duffel bag for holding surgery tools." + icon_state = "duffel-syndiemed" + item_state = "duffel-syndiemed" + +/obj/item/storage/backpack/duffelbag/syndie/surgery/PopulateContents() + new /obj/item/surgicaldrill/advanced(src) + new /obj/item/scalpel/advanced(src) + new /obj/item/retractor/advanced(src) + new /obj/item/clothing/suit/straight_jacket(src) + new /obj/item/clothing/mask/muzzle(src) + new /obj/item/mmi/syndie(src) + +/obj/item/storage/backpack/duffelbag/syndie/ammo + name = "ammunition duffel bag" + desc = "A large duffel bag for holding extra weapons ammunition and supplies." + icon_state = "duffel-syndieammo" + item_state = "duffel-syndieammo" + +/obj/item/storage/backpack/duffelbag/syndie/ammo/shotgun + desc = "A large duffel bag, packed to the brim with Bulldog shotgun magazines." + +/obj/item/storage/backpack/duffelbag/syndie/ammo/shotgun/PopulateContents() + for(var/i in 1 to 6) + new /obj/item/ammo_box/magazine/m12g(src) + new /obj/item/ammo_box/magazine/m12g/slug(src) + new /obj/item/ammo_box/magazine/m12g/slug(src) + new /obj/item/ammo_box/magazine/m12g/dragon(src) + +/obj/item/storage/backpack/duffelbag/syndie/ammo/smg + desc = "A large duffel bag, packed to the brim with C-20r magazines." + +/obj/item/storage/backpack/duffelbag/syndie/ammo/smg/PopulateContents() + for(var/i in 1 to 9) + new /obj/item/ammo_box/magazine/smgm45(src) + +/obj/item/storage/backpack/duffelbag/syndie/ammo/mech + desc = "A large duffel bag, packed to the brim with various exosuit ammo." + +/obj/item/storage/backpack/duffelbag/syndie/ammo/mech/PopulateContents() + new /obj/item/mecha_ammo/scattershot(src) + new /obj/item/mecha_ammo/scattershot(src) + new /obj/item/mecha_ammo/scattershot(src) + new /obj/item/mecha_ammo/scattershot(src) + new /obj/item/storage/belt/utility/syndicate(src) + +/obj/item/storage/backpack/duffelbag/syndie/ammo/mauler + desc = "A large duffel bag, packed to the brim with various exosuit ammo." + +/obj/item/storage/backpack/duffelbag/syndie/ammo/mauler/PopulateContents() + new /obj/item/mecha_ammo/lmg(src) + new /obj/item/mecha_ammo/lmg(src) + new /obj/item/mecha_ammo/lmg(src) + new /obj/item/mecha_ammo/scattershot(src) + new /obj/item/mecha_ammo/scattershot(src) + new /obj/item/mecha_ammo/scattershot(src) + new /obj/item/mecha_ammo/missiles_he(src) + new /obj/item/mecha_ammo/missiles_he(src) + new /obj/item/mecha_ammo/missiles_he(src) + +/obj/item/storage/backpack/duffelbag/syndie/c20rbundle + desc = "A large duffel bag containing a C-20r, some magazines, and a cheap looking suppressor." + +/obj/item/storage/backpack/duffelbag/syndie/c20rbundle/PopulateContents() + new /obj/item/ammo_box/magazine/smgm45(src) + new /obj/item/ammo_box/magazine/smgm45(src) + new /obj/item/gun/ballistic/automatic/c20r(src) + new /obj/item/suppressor/specialoffer(src) + +/obj/item/storage/backpack/duffelbag/syndie/bulldogbundle + desc = "A large duffel bag containing a Bulldog, some drums, and a pair of thermal imaging glasses." + +/obj/item/storage/backpack/duffelbag/syndie/bulldogbundle/PopulateContents() + new /obj/item/gun/ballistic/shotgun/bulldog(src) + new /obj/item/ammo_box/magazine/m12g(src) + new /obj/item/ammo_box/magazine/m12g(src) + new /obj/item/clothing/glasses/thermal/syndi(src) + +/obj/item/storage/backpack/duffelbag/syndie/med/medicalbundle + desc = "A large duffel bag containing a medical equipment, a Donksoft LMG, a big jumbo box of riot darts, and a knock-off pair of magboots." + +/obj/item/storage/backpack/duffelbag/syndie/med/medicalbundle/PopulateContents() + new /obj/item/clothing/shoes/magboots/syndie(src) + new /obj/item/storage/firstaid/tactical(src) + new /obj/item/gun/ballistic/automatic/l6_saw/toy(src) + new /obj/item/ammo_box/foambox/riot(src) + +/obj/item/storage/backpack/duffelbag/syndie/med/bioterrorbundle + desc = "A large duffel bag containing deadly chemicals, a handheld chem sprayer, Bioterror foam grenade, a Donksoft assault rifle, box of riot grade darts, a dart pistol, and a box of syringes." + +/obj/item/storage/backpack/duffelbag/syndie/med/bioterrorbundle/PopulateContents() + new /obj/item/reagent_containers/spray/chemsprayer/bioterror(src) + new /obj/item/storage/box/syndie_kit/chemical(src) + new /obj/item/gun/syringe/syndicate(src) + new /obj/item/gun/ballistic/automatic/c20r/toy(src) + new /obj/item/storage/box/syringes(src) + new /obj/item/ammo_box/foambox/riot(src) + new /obj/item/grenade/chem_grenade/bioterrorfoam(src) + if(prob(5)) + new /obj/item/reagent_containers/food/snacks/pizza/pineapple(src) + +/obj/item/storage/backpack/duffelbag/syndie/c4/PopulateContents() + for(var/i in 1 to 10) + new /obj/item/grenade/c4(src) + +/obj/item/storage/backpack/duffelbag/syndie/x4/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/grenade/c4/x4(src) + +/obj/item/storage/backpack/duffelbag/syndie/firestarter + desc = "A large duffel bag containing a New Russian pyro backpack sprayer, Elite hardsuit, a Stechkin APS pistol, minibomb, ammo, and other equipment." + +/obj/item/storage/backpack/duffelbag/syndie/firestarter/PopulateContents() + new /obj/item/clothing/under/syndicate/soviet(src) + new /obj/item/watertank/op(src) + new /obj/item/clothing/suit/space/hardsuit/syndi/elite(src) + new /obj/item/gun/ballistic/automatic/pistol/APS(src) + new /obj/item/ammo_box/magazine/pistolm9mm(src) + new /obj/item/ammo_box/magazine/pistolm9mm(src) + new /obj/item/reagent_containers/food/drinks/bottle/vodka/badminka(src) + new /obj/item/reagent_containers/hypospray/medipen/stimulants(src) + new /obj/item/grenade/syndieminibomb(src) + +// For ClownOps. +/obj/item/storage/backpack/duffelbag/clown/syndie/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + slowdown = 0 + STR.silent = TRUE + +/obj/item/storage/backpack/duffelbag/clown/syndie/PopulateContents() + new /obj/item/pda/clown(src) + new /obj/item/clothing/under/rank/civilian/clown(src) + new /obj/item/clothing/shoes/clown_shoes(src) + new /obj/item/clothing/mask/gas/clown_hat(src) + new /obj/item/bikehorn(src) + new /obj/item/implanter/sad_trombone(src) + +/obj/item/storage/backpack/henchmen + name = "wings" + desc = "Granted to the henchmen who deserve it. This probably doesn't include you." + icon_state = "henchmen" + item_state = "henchmen" + +/obj/item/storage/backpack/duffelbag/cops + name = "police bag" + desc = "A large duffel bag for holding extra police gear." + slowdown = 0 diff --git a/code/game/objects/items/storage/bags.dm b/code/game/objects/items/storage/bags.dm index e33aa961ac36..9f0ffa7c6afa 100644 --- a/code/game/objects/items/storage/bags.dm +++ b/code/game/objects/items/storage/bags.dm @@ -1,455 +1,455 @@ -/* - * These absorb the functionality of the plant bag, ore satchel, etc. - * They use the use_to_pickup, quick_gather, and quick_empty functions - * that were already defined in weapon/storage, but which had been - * re-implemented in other classes. - * - * Contains: - * Trash Bag - * Mining Satchel - * Plant Bag - * Sheet Snatcher - * Book Bag - * Biowaste Bag - * - * -Sayu - */ - -// Generic non-item -/obj/item/storage/bag - slot_flags = ITEM_SLOT_BELT - w_class = WEIGHT_CLASS_BULKY - -/obj/item/storage/bag/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.allow_quick_gather = TRUE - STR.allow_quick_empty = TRUE - STR.display_numerical_stacking = TRUE - STR.click_gather = TRUE - -// ----------------------------- -// Trash bag -// ----------------------------- -/obj/item/storage/bag/trash - name = "trash bag" - desc = "It's the heavy-duty black polymer kind. Time to take out the trash!" - icon = 'icons/obj/janitor.dmi' - icon_state = "trashbag" - item_state = "trashbag" - lefthand_file = 'icons/mob/inhands/equipment/custodial_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/custodial_righthand.dmi' - var/insertable = TRUE - -/obj/item/storage/bag/trash/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_SMALL - STR.max_combined_w_class = 30 - STR.max_items = 30 - STR.set_holdable(null, list(/obj/item/disk/nuclear)) - -/obj/item/storage/bag/trash/suicide_act(mob/user) - user.visible_message("[user] puts [src] over [user.p_their()] head and starts chomping at the insides! Disgusting!") - playsound(loc, 'sound/items/eatfood.ogg', 50, TRUE, -1) - return (TOXLOSS) - -/obj/item/storage/bag/trash/update_icon_state() - switch(contents.len) - if(20 to INFINITY) - icon_state = "[initial(icon_state)]3" - if(11 to 20) - icon_state = "[initial(icon_state)]2" - if(1 to 11) - icon_state = "[initial(icon_state)]1" - else - icon_state = "[initial(icon_state)]" - -/obj/item/storage/bag/trash/cyborg - insertable = FALSE - -/obj/item/storage/bag/trash/proc/janicart_insert(mob/user, obj/structure/janitorialcart/J) - if(insertable) - J.put_in_cart(src, user) - J.mybag=src - J.update_icon() - else - to_chat(user, "You are unable to fit your [name] into the [J.name].") - return - -/obj/item/storage/bag/trash/bluespace - name = "trash bag of holding" - desc = "The latest and greatest in custodial convenience, a trashbag that is capable of holding vast quantities of garbage." - icon_state = "bluetrashbag" - item_flags = NO_MAT_REDEMPTION - -/obj/item/storage/bag/trash/bluespace/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_combined_w_class = 60 - STR.max_items = 60 - -/obj/item/storage/bag/trash/bluespace/cyborg - insertable = FALSE - -// ----------------------------- -// Mining Satchel -// ----------------------------- - -/obj/item/storage/bag/ore - name = "mining satchel" - desc = "This little bugger can be used to store and transport ores." - //WaspStation Begin - Better bag sprites - icon = 'waspstation/icons/obj/bags.dmi' - icon_state = "minebag" - //WaspStation end - slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_POCKETS - w_class = WEIGHT_CLASS_NORMAL - component_type = /datum/component/storage/concrete/stack - var/spam_protection = FALSE //If this is TRUE, the holder won't receive any messages when they fail to pick up ore through crossing it - var/mob/listeningTo - -/obj/item/storage/bag/ore/ComponentInitialize() - . = ..() - AddComponent(/datum/component/rad_insulation, 0.01) //please datum mats no more cancer - var/datum/component/storage/concrete/stack/STR = GetComponent(/datum/component/storage/concrete/stack) - STR.allow_quick_empty = TRUE - STR.set_holdable(list(/obj/item/stack/ore)) - STR.max_w_class = WEIGHT_CLASS_HUGE - STR.max_combined_stack_amount = 50 - -/obj/item/storage/bag/ore/equipped(mob/user) - . = ..() - if(listeningTo == user) - return - if(listeningTo) - UnregisterSignal(listeningTo, COMSIG_MOVABLE_MOVED) - RegisterSignal(user, COMSIG_MOVABLE_MOVED, .proc/Pickup_ores) - listeningTo = user - -/obj/item/storage/bag/ore/dropped() - . = ..() - if(listeningTo) - UnregisterSignal(listeningTo, COMSIG_MOVABLE_MOVED) - listeningTo = null - -/obj/item/storage/bag/ore/proc/Pickup_ores(mob/living/user) - var/show_message = FALSE - var/obj/structure/ore_box/box - var/turf/tile = user.loc - if (!isturf(tile)) - return - if (istype(user.pulling, /obj/structure/ore_box)) - box = user.pulling - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - if(STR) - for(var/A in tile) - if (!is_type_in_typecache(A, STR.can_hold)) - continue - if (box) - user.transferItemToLoc(A, box) - show_message = TRUE - else if(SEND_SIGNAL(src, COMSIG_TRY_STORAGE_INSERT, A, user, TRUE)) - show_message = TRUE - else - if(!spam_protection) - to_chat(user, "Your [name] is full and can't hold any more!") - spam_protection = TRUE - continue - if(show_message) - playsound(user, "rustle", 50, TRUE) - if (box) - user.visible_message("[user] offloads the ores beneath [user.p_them()] into [box].", \ - "You offload the ores beneath you into your [box].") - else - user.visible_message("[user] scoops up the ores beneath [user.p_them()].", \ - "You scoop up the ores beneath you with your [name].") - spam_protection = FALSE - -/obj/item/storage/bag/ore/cyborg - name = "cyborg mining satchel" - -/obj/item/storage/bag/ore/holding //miners, your messiah has arrived - name = "mining satchel of holding" - desc = "A revolution in convenience, this satchel allows for huge amounts of ore storage. It's been outfitted with anti-malfunction safety measures." - //WaspStation Begin - Better bag sprites - icon = 'waspstation/icons/obj/bags.dmi' - icon_state = "minebagbs" - //WaspStation end - -/obj/item/storage/bag/ore/holding/ComponentInitialize() - . = ..() - var/datum/component/storage/concrete/stack/STR = GetComponent(/datum/component/storage/concrete/stack) - STR.max_items = INFINITY - STR.max_combined_w_class = INFINITY - STR.max_combined_stack_amount = INFINITY - -// ----------------------------- -// Plant bag -// ----------------------------- - -/obj/item/storage/bag/plants - name = "plant bag" - //WaspStation Begin - Better bag sprites - icon = 'waspstation/icons/obj/bags.dmi' - icon_state = "plantbag" - w_class = WEIGHT_CLASS_TINY - //WaspStation end - resistance_flags = FLAMMABLE - -/obj/item/storage/bag/plants/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_NORMAL - STR.max_combined_w_class = 100 - STR.max_items = 100 - STR.set_holdable(list( - /obj/item/reagent_containers/food/snacks/grown, - /obj/item/seeds, - /obj/item/grown, - /obj/item/reagent_containers/honeycomb, - /obj/item/disk/plantgene - )) -//////// - -/obj/item/storage/bag/plants/portaseeder - name = "portable seed extractor" - desc = "For the enterprising botanist on the go. Less efficient than the stationary model, it creates one seed per plant." - icon_state = "portaseeder" - -/obj/item/storage/bag/plants/portaseeder/verb/dissolve_contents() - set name = "Activate Seed Extraction" - set category = "Object" - set desc = "Activate to convert your plants into plantable seeds." - if(usr.incapacitated()) - return - for(var/obj/item/O in contents) - seedify(O, 1) - -// ----------------------------- -// Sheet Snatcher -// ----------------------------- -// Because it stacks stacks, this doesn't operate normally. -// However, making it a storage/bag allows us to reuse existing code in some places. -Sayu - -/obj/item/storage/bag/sheetsnatcher - name = "sheet snatcher" - desc = "A patented Nanotrasen storage system designed for any kind of mineral sheet." - icon = 'icons/obj/mining.dmi' - icon_state = "sheetsnatcher" - - var/capacity = 300; //the number of sheets it can carry. - component_type = /datum/component/storage/concrete/stack - -/obj/item/storage/bag/sheetsnatcher/ComponentInitialize() - . = ..() - var/datum/component/storage/concrete/stack/STR = GetComponent(/datum/component/storage/concrete/stack) - STR.allow_quick_empty = TRUE - STR.set_holdable(list( - /obj/item/stack/sheet, - /obj/item/stack/tile/bronze - ), - list( - /obj/item/stack/sheet/mineral/sandstone, - /obj/item/stack/sheet/mineral/wood - )) - STR.max_combined_stack_amount = 300 - -// ----------------------------- -// Sheet Snatcher (Cyborg) -// ----------------------------- - -/obj/item/storage/bag/sheetsnatcher/borg - name = "sheet snatcher 9000" - desc = "" - capacity = 500//Borgs get more because >specialization - -/obj/item/storage/bag/sheetsnatcher/borg/ComponentInitialize() - . = ..() - var/datum/component/storage/concrete/stack/STR = GetComponent(/datum/component/storage/concrete/stack) - STR.max_combined_stack_amount = 500 - -// ----------------------------- -// Book bag -// ----------------------------- - -/obj/item/storage/bag/books - name = "book bag" - desc = "A bag for books." - icon = 'icons/obj/library.dmi' - icon_state = "bookbag" - resistance_flags = FLAMMABLE - -/obj/item/storage/bag/books/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_NORMAL - STR.max_combined_w_class = 21 - STR.max_items = 7 - STR.display_numerical_stacking = FALSE - STR.set_holdable(list( - /obj/item/book, - /obj/item/storage/book, - /obj/item/spellbook - )) - -/* - * Trays - Agouri - */ -/obj/item/storage/bag/tray - name = "serving tray" - icon = 'icons/obj/food/containers.dmi' - icon_state = "tray" - desc = "A metal tray to lay food on." - force = 5 - throwforce = 10 - throw_speed = 3 - throw_range = 5 - flags_1 = CONDUCT_1 - custom_materials = list(/datum/material/iron=3000) - -/obj/item/storage/bag/tray/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.insert_preposition = "on" - -/obj/item/storage/bag/tray/attack(mob/living/M, mob/living/user) - . = ..() - // Drop all the things. All of them. - var/list/obj/item/oldContents = contents.Copy() - SEND_SIGNAL(src, COMSIG_TRY_STORAGE_QUICK_EMPTY) - // Make each item scatter a bit - for(var/obj/item/I in oldContents) - INVOKE_ASYNC(src, .proc/do_scatter, I) - - if(prob(50)) - playsound(M, 'sound/items/trayhit1.ogg', 50, TRUE) - else - playsound(M, 'sound/items/trayhit2.ogg', 50, TRUE) - - if(ishuman(M) || ismonkey(M)) - if(prob(10)) - M.Paralyze(40) - update_icon() - -/obj/item/storage/bag/tray/proc/do_scatter(obj/item/I) - for(var/i in 1 to rand(1,2)) - if(I) - step(I, pick(NORTH,SOUTH,EAST,WEST)) - sleep(rand(2,4)) - -/obj/item/storage/bag/tray/update_overlays() - . = ..() - for(var/obj/item/I in contents) - var/mutable_appearance/I_copy = new(I) - I_copy.plane = FLOAT_PLANE - I_copy.layer = FLOAT_LAYER - . += I_copy - -/obj/item/storage/bag/tray/Entered() - . = ..() - update_icon() - -/obj/item/storage/bag/tray/Exited() - . = ..() - update_icon() - -/obj/item/storage/bag/tray/cafeteria - name = "cafeteria tray" - icon = 'icons/obj/food/containers.dmi' - icon_state = "foodtray" - desc = "A cheap metal tray to pile today's meal onto." - -/* - * Chemistry bag - */ - -/obj/item/storage/bag/chemistry - name = "chemistry bag" - //WaspStation Begin - Better bag sprites - icon = 'waspstation/icons/obj/bags.dmi' - icon_state = "chembag" - //WaspStation end - desc = "A bag for storing pills, patches, and bottles." - resistance_flags = FLAMMABLE - -/obj/item/storage/bag/chemistry/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_combined_w_class = 200 - STR.max_items = 50 - STR.insert_preposition = "in" - STR.set_holdable(list( - /obj/item/reagent_containers/pill, - /obj/item/reagent_containers/glass/beaker, - /obj/item/reagent_containers/glass/bottle, - /obj/item/reagent_containers/food/drinks/waterbottle, - /obj/item/reagent_containers/medigel, - /obj/item/reagent_containers/syringe, - /obj/item/reagent_containers/dropper, - /obj/item/reagent_containers/chem_pack - )) - -/* - * Biowaste bag (mostly for xenobiologists) - */ - -/obj/item/storage/bag/bio - name = "bio bag" - //WaspStation Begin - Better bag sprites - icon = 'waspstation/icons/obj/bags.dmi' - icon_state = "virobag" - //WaspStation end - desc = "A bag for the safe transportation and disposal of biowaste and other biological materials." - resistance_flags = FLAMMABLE - -/obj/item/storage/bag/bio/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_combined_w_class = 200 - STR.max_items = 25 - STR.insert_preposition = "in" - STR.set_holdable(list( - /obj/item/slime_extract, - /obj/item/reagent_containers/syringe, - /obj/item/reagent_containers/dropper, - /obj/item/reagent_containers/glass/beaker, - /obj/item/reagent_containers/glass/bottle, - /obj/item/reagent_containers/blood, - /obj/item/reagent_containers/hypospray/medipen, - /obj/item/reagent_containers/food/snacks/deadmouse, - /obj/item/reagent_containers/food/snacks/monkeycube, - /obj/item/organ, - /obj/item/bodypart - )) - -/* - * Construction bag (for engineering, holds stock parts and electronics) - */ - -/obj/item/storage/bag/construction - name = "construction bag" - //WaspStation Begin - Better bag sprites - icon = 'waspstation/icons/obj/bags.dmi' - icon_state = "engbag" - //WaspStation end - desc = "A bag for storing small construction components." - resistance_flags = FLAMMABLE - -/obj/item/storage/bag/construction/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_combined_w_class = 100 - STR.max_items = 50 - STR.max_w_class = WEIGHT_CLASS_SMALL - STR.insert_preposition = "in" - STR.set_holdable(list( - /obj/item/stack/ore/bluespace_crystal, - /obj/item/assembly, - /obj/item/stock_parts, - /obj/item/reagent_containers/glass/beaker, - /obj/item/stack/cable_coil, - /obj/item/circuitboard, - /obj/item/electronics, - /obj/item/wallframe/camera - )) +/* + * These absorb the functionality of the plant bag, ore satchel, etc. + * They use the use_to_pickup, quick_gather, and quick_empty functions + * that were already defined in weapon/storage, but which had been + * re-implemented in other classes. + * + * Contains: + * Trash Bag + * Mining Satchel + * Plant Bag + * Sheet Snatcher + * Book Bag + * Biowaste Bag + * + * -Sayu + */ + +// Generic non-item +/obj/item/storage/bag + slot_flags = ITEM_SLOT_BELT + w_class = WEIGHT_CLASS_BULKY + +/obj/item/storage/bag/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.allow_quick_gather = TRUE + STR.allow_quick_empty = TRUE + STR.display_numerical_stacking = TRUE + STR.click_gather = TRUE + +// ----------------------------- +// Trash bag +// ----------------------------- +/obj/item/storage/bag/trash + name = "trash bag" + desc = "It's the heavy-duty black polymer kind. Time to take out the trash!" + icon = 'icons/obj/janitor.dmi' + icon_state = "trashbag" + item_state = "trashbag" + lefthand_file = 'icons/mob/inhands/equipment/custodial_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/custodial_righthand.dmi' + var/insertable = TRUE + +/obj/item/storage/bag/trash/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_SMALL + STR.max_combined_w_class = 30 + STR.max_items = 30 + STR.set_holdable(null, list(/obj/item/disk/nuclear)) + +/obj/item/storage/bag/trash/suicide_act(mob/user) + user.visible_message("[user] puts [src] over [user.p_their()] head and starts chomping at the insides! Disgusting!") + playsound(loc, 'sound/items/eatfood.ogg', 50, TRUE, -1) + return (TOXLOSS) + +/obj/item/storage/bag/trash/update_icon_state() + switch(contents.len) + if(20 to INFINITY) + icon_state = "[initial(icon_state)]3" + if(11 to 20) + icon_state = "[initial(icon_state)]2" + if(1 to 11) + icon_state = "[initial(icon_state)]1" + else + icon_state = "[initial(icon_state)]" + +/obj/item/storage/bag/trash/cyborg + insertable = FALSE + +/obj/item/storage/bag/trash/proc/janicart_insert(mob/user, obj/structure/janitorialcart/J) + if(insertable) + J.put_in_cart(src, user) + J.mybag=src + J.update_icon() + else + to_chat(user, "You are unable to fit your [name] into the [J.name].") + return + +/obj/item/storage/bag/trash/bluespace + name = "trash bag of holding" + desc = "The latest and greatest in custodial convenience, a trashbag that is capable of holding vast quantities of garbage." + icon_state = "bluetrashbag" + item_flags = NO_MAT_REDEMPTION + +/obj/item/storage/bag/trash/bluespace/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_combined_w_class = 60 + STR.max_items = 60 + +/obj/item/storage/bag/trash/bluespace/cyborg + insertable = FALSE + +// ----------------------------- +// Mining Satchel +// ----------------------------- + +/obj/item/storage/bag/ore + name = "mining satchel" + desc = "This little bugger can be used to store and transport ores." + //WaspStation Begin - Better bag sprites + icon = 'waspstation/icons/obj/bags.dmi' + icon_state = "minebag" + //WaspStation end + slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_POCKETS + w_class = WEIGHT_CLASS_NORMAL + component_type = /datum/component/storage/concrete/stack + var/spam_protection = FALSE //If this is TRUE, the holder won't receive any messages when they fail to pick up ore through crossing it + var/mob/listeningTo + +/obj/item/storage/bag/ore/ComponentInitialize() + . = ..() + AddComponent(/datum/component/rad_insulation, 0.01) //please datum mats no more cancer + var/datum/component/storage/concrete/stack/STR = GetComponent(/datum/component/storage/concrete/stack) + STR.allow_quick_empty = TRUE + STR.set_holdable(list(/obj/item/stack/ore)) + STR.max_w_class = WEIGHT_CLASS_HUGE + STR.max_combined_stack_amount = 50 + +/obj/item/storage/bag/ore/equipped(mob/user) + . = ..() + if(listeningTo == user) + return + if(listeningTo) + UnregisterSignal(listeningTo, COMSIG_MOVABLE_MOVED) + RegisterSignal(user, COMSIG_MOVABLE_MOVED, .proc/Pickup_ores) + listeningTo = user + +/obj/item/storage/bag/ore/dropped() + . = ..() + if(listeningTo) + UnregisterSignal(listeningTo, COMSIG_MOVABLE_MOVED) + listeningTo = null + +/obj/item/storage/bag/ore/proc/Pickup_ores(mob/living/user) + var/show_message = FALSE + var/obj/structure/ore_box/box + var/turf/tile = user.loc + if (!isturf(tile)) + return + if (istype(user.pulling, /obj/structure/ore_box)) + box = user.pulling + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + if(STR) + for(var/A in tile) + if (!is_type_in_typecache(A, STR.can_hold)) + continue + if (box) + user.transferItemToLoc(A, box) + show_message = TRUE + else if(SEND_SIGNAL(src, COMSIG_TRY_STORAGE_INSERT, A, user, TRUE)) + show_message = TRUE + else + if(!spam_protection) + to_chat(user, "Your [name] is full and can't hold any more!") + spam_protection = TRUE + continue + if(show_message) + playsound(user, "rustle", 50, TRUE) + if (box) + user.visible_message("[user] offloads the ores beneath [user.p_them()] into [box].", \ + "You offload the ores beneath you into your [box].") + else + user.visible_message("[user] scoops up the ores beneath [user.p_them()].", \ + "You scoop up the ores beneath you with your [name].") + spam_protection = FALSE + +/obj/item/storage/bag/ore/cyborg + name = "cyborg mining satchel" + +/obj/item/storage/bag/ore/holding //miners, your messiah has arrived + name = "mining satchel of holding" + desc = "A revolution in convenience, this satchel allows for huge amounts of ore storage. It's been outfitted with anti-malfunction safety measures." + //WaspStation Begin - Better bag sprites + icon = 'waspstation/icons/obj/bags.dmi' + icon_state = "minebagbs" + //WaspStation end + +/obj/item/storage/bag/ore/holding/ComponentInitialize() + . = ..() + var/datum/component/storage/concrete/stack/STR = GetComponent(/datum/component/storage/concrete/stack) + STR.max_items = INFINITY + STR.max_combined_w_class = INFINITY + STR.max_combined_stack_amount = INFINITY + +// ----------------------------- +// Plant bag +// ----------------------------- + +/obj/item/storage/bag/plants + name = "plant bag" + //WaspStation Begin - Better bag sprites + icon = 'waspstation/icons/obj/bags.dmi' + icon_state = "plantbag" + w_class = WEIGHT_CLASS_TINY + //WaspStation end + resistance_flags = FLAMMABLE + +/obj/item/storage/bag/plants/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_NORMAL + STR.max_combined_w_class = 100 + STR.max_items = 100 + STR.set_holdable(list( + /obj/item/reagent_containers/food/snacks/grown, + /obj/item/seeds, + /obj/item/grown, + /obj/item/reagent_containers/honeycomb, + /obj/item/disk/plantgene + )) +//////// + +/obj/item/storage/bag/plants/portaseeder + name = "portable seed extractor" + desc = "For the enterprising botanist on the go. Less efficient than the stationary model, it creates one seed per plant." + icon_state = "portaseeder" + +/obj/item/storage/bag/plants/portaseeder/verb/dissolve_contents() + set name = "Activate Seed Extraction" + set category = "Object" + set desc = "Activate to convert your plants into plantable seeds." + if(usr.incapacitated()) + return + for(var/obj/item/O in contents) + seedify(O, 1) + +// ----------------------------- +// Sheet Snatcher +// ----------------------------- +// Because it stacks stacks, this doesn't operate normally. +// However, making it a storage/bag allows us to reuse existing code in some places. -Sayu + +/obj/item/storage/bag/sheetsnatcher + name = "sheet snatcher" + desc = "A patented Nanotrasen storage system designed for any kind of mineral sheet." + icon = 'icons/obj/mining.dmi' + icon_state = "sheetsnatcher" + + var/capacity = 300; //the number of sheets it can carry. + component_type = /datum/component/storage/concrete/stack + +/obj/item/storage/bag/sheetsnatcher/ComponentInitialize() + . = ..() + var/datum/component/storage/concrete/stack/STR = GetComponent(/datum/component/storage/concrete/stack) + STR.allow_quick_empty = TRUE + STR.set_holdable(list( + /obj/item/stack/sheet, + /obj/item/stack/tile/bronze + ), + list( + /obj/item/stack/sheet/mineral/sandstone, + /obj/item/stack/sheet/mineral/wood + )) + STR.max_combined_stack_amount = 300 + +// ----------------------------- +// Sheet Snatcher (Cyborg) +// ----------------------------- + +/obj/item/storage/bag/sheetsnatcher/borg + name = "sheet snatcher 9000" + desc = "" + capacity = 500//Borgs get more because >specialization + +/obj/item/storage/bag/sheetsnatcher/borg/ComponentInitialize() + . = ..() + var/datum/component/storage/concrete/stack/STR = GetComponent(/datum/component/storage/concrete/stack) + STR.max_combined_stack_amount = 500 + +// ----------------------------- +// Book bag +// ----------------------------- + +/obj/item/storage/bag/books + name = "book bag" + desc = "A bag for books." + icon = 'icons/obj/library.dmi' + icon_state = "bookbag" + resistance_flags = FLAMMABLE + +/obj/item/storage/bag/books/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_NORMAL + STR.max_combined_w_class = 21 + STR.max_items = 7 + STR.display_numerical_stacking = FALSE + STR.set_holdable(list( + /obj/item/book, + /obj/item/storage/book, + /obj/item/spellbook + )) + +/* + * Trays - Agouri + */ +/obj/item/storage/bag/tray + name = "serving tray" + icon = 'icons/obj/food/containers.dmi' + icon_state = "tray" + desc = "A metal tray to lay food on." + force = 5 + throwforce = 10 + throw_speed = 3 + throw_range = 5 + flags_1 = CONDUCT_1 + custom_materials = list(/datum/material/iron=3000) + +/obj/item/storage/bag/tray/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.insert_preposition = "on" + +/obj/item/storage/bag/tray/attack(mob/living/M, mob/living/user) + . = ..() + // Drop all the things. All of them. + var/list/obj/item/oldContents = contents.Copy() + SEND_SIGNAL(src, COMSIG_TRY_STORAGE_QUICK_EMPTY) + // Make each item scatter a bit + for(var/obj/item/I in oldContents) + INVOKE_ASYNC(src, .proc/do_scatter, I) + + if(prob(50)) + playsound(M, 'sound/items/trayhit1.ogg', 50, TRUE) + else + playsound(M, 'sound/items/trayhit2.ogg', 50, TRUE) + + if(ishuman(M) || ismonkey(M)) + if(prob(10)) + M.Paralyze(40) + update_icon() + +/obj/item/storage/bag/tray/proc/do_scatter(obj/item/I) + for(var/i in 1 to rand(1,2)) + if(I) + step(I, pick(NORTH,SOUTH,EAST,WEST)) + sleep(rand(2,4)) + +/obj/item/storage/bag/tray/update_overlays() + . = ..() + for(var/obj/item/I in contents) + var/mutable_appearance/I_copy = new(I) + I_copy.plane = FLOAT_PLANE + I_copy.layer = FLOAT_LAYER + . += I_copy + +/obj/item/storage/bag/tray/Entered() + . = ..() + update_icon() + +/obj/item/storage/bag/tray/Exited() + . = ..() + update_icon() + +/obj/item/storage/bag/tray/cafeteria + name = "cafeteria tray" + icon = 'icons/obj/food/containers.dmi' + icon_state = "foodtray" + desc = "A cheap metal tray to pile today's meal onto." + +/* + * Chemistry bag + */ + +/obj/item/storage/bag/chemistry + name = "chemistry bag" + //WaspStation Begin - Better bag sprites + icon = 'waspstation/icons/obj/bags.dmi' + icon_state = "chembag" + //WaspStation end + desc = "A bag for storing pills, patches, and bottles." + resistance_flags = FLAMMABLE + +/obj/item/storage/bag/chemistry/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_combined_w_class = 200 + STR.max_items = 50 + STR.insert_preposition = "in" + STR.set_holdable(list( + /obj/item/reagent_containers/pill, + /obj/item/reagent_containers/glass/beaker, + /obj/item/reagent_containers/glass/bottle, + /obj/item/reagent_containers/food/drinks/waterbottle, + /obj/item/reagent_containers/medigel, + /obj/item/reagent_containers/syringe, + /obj/item/reagent_containers/dropper, + /obj/item/reagent_containers/chem_pack + )) + +/* + * Biowaste bag (mostly for xenobiologists) + */ + +/obj/item/storage/bag/bio + name = "bio bag" + //WaspStation Begin - Better bag sprites + icon = 'waspstation/icons/obj/bags.dmi' + icon_state = "virobag" + //WaspStation end + desc = "A bag for the safe transportation and disposal of biowaste and other biological materials." + resistance_flags = FLAMMABLE + +/obj/item/storage/bag/bio/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_combined_w_class = 200 + STR.max_items = 25 + STR.insert_preposition = "in" + STR.set_holdable(list( + /obj/item/slime_extract, + /obj/item/reagent_containers/syringe, + /obj/item/reagent_containers/dropper, + /obj/item/reagent_containers/glass/beaker, + /obj/item/reagent_containers/glass/bottle, + /obj/item/reagent_containers/blood, + /obj/item/reagent_containers/hypospray/medipen, + /obj/item/reagent_containers/food/snacks/deadmouse, + /obj/item/reagent_containers/food/snacks/monkeycube, + /obj/item/organ, + /obj/item/bodypart + )) + +/* + * Construction bag (for engineering, holds stock parts and electronics) + */ + +/obj/item/storage/bag/construction + name = "construction bag" + //WaspStation Begin - Better bag sprites + icon = 'waspstation/icons/obj/bags.dmi' + icon_state = "engbag" + //WaspStation end + desc = "A bag for storing small construction components." + resistance_flags = FLAMMABLE + +/obj/item/storage/bag/construction/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_combined_w_class = 100 + STR.max_items = 50 + STR.max_w_class = WEIGHT_CLASS_SMALL + STR.insert_preposition = "in" + STR.set_holdable(list( + /obj/item/stack/ore/bluespace_crystal, + /obj/item/assembly, + /obj/item/stock_parts, + /obj/item/reagent_containers/glass/beaker, + /obj/item/stack/cable_coil, + /obj/item/circuitboard, + /obj/item/electronics, + /obj/item/wallframe/camera + )) diff --git a/code/game/objects/items/storage/belt.dm b/code/game/objects/items/storage/belt.dm index 2ca1440f79d4..6397e54aebda 100644 --- a/code/game/objects/items/storage/belt.dm +++ b/code/game/objects/items/storage/belt.dm @@ -1,676 +1,676 @@ -/obj/item/storage/belt - name = "belt" - desc = "Can hold various things." - icon = 'icons/obj/clothing/belts.dmi' - icon_state = "utilitybelt" - item_state = "utility" - lefthand_file = 'icons/mob/inhands/equipment/belt_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/belt_righthand.dmi' - slot_flags = ITEM_SLOT_BELT - attack_verb = list("whipped", "lashed", "disciplined") - max_integrity = 300 - equip_sound = 'sound/items/equip/toolbelt_equip.ogg' - var/content_overlays = FALSE //If this is true, the belt will gain overlays based on what it's holding - -/obj/item/storage/belt/suicide_act(mob/living/carbon/user) - user.visible_message("[user] begins belting [user.p_them()]self with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return BRUTELOSS - -/obj/item/storage/belt/update_overlays() - . = ..() - if(content_overlays) - for(var/obj/item/I in contents) - . += I.get_belt_overlay() - -/obj/item/storage/belt/Initialize() - . = ..() - update_icon() - -/obj/item/storage/belt/utility - name = "toolbelt" //Carn: utility belt is nicer, but it bamboozles the text parsing. - desc = "Holds tools." - icon_state = "utilitybelt" - item_state = "utility" - content_overlays = TRUE - custom_premium_price = 300 - drop_sound = 'sound/items/handling/toolbelt_drop.ogg' - pickup_sound = 'sound/items/handling/toolbelt_pickup.ogg' - -/obj/item/storage/belt/utility/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_NORMAL - STR.max_combined_w_class = 21 - STR.set_holdable(list( - /obj/item/crowbar, - /obj/item/screwdriver, - /obj/item/weldingtool, - /obj/item/wirecutters, - /obj/item/wrench, - /obj/item/multitool, - /obj/item/flashlight, - /obj/item/stack/cable_coil, - /obj/item/t_scanner, - /obj/item/analyzer, - /obj/item/geiger_counter, - /obj/item/extinguisher/mini, - /obj/item/radio, - /obj/item/clothing/gloves, - /obj/item/holosign_creator/atmos, - /obj/item/holosign_creator/engineering, - /obj/item/forcefield_projector, - /obj/item/assembly/signaler, - /obj/item/lightreplacer, - /obj/item/construction/rcd, - /obj/item/pipe_dispenser, - /obj/item/inducer, - /obj/item/plunger - )) - -/obj/item/storage/belt/utility/chief - name = "\improper Chief Engineer's toolbelt" //"the Chief Engineer's toolbelt", because "Chief Engineer's toolbelt" is not a proper noun - desc = "Holds tools, looks snazzy." - icon_state = "utilitybelt_ce" - item_state = "utility_ce" - -/obj/item/storage/belt/utility/chief/full/PopulateContents() - new /obj/item/screwdriver/power(src) - new /obj/item/crowbar/power(src) - new /obj/item/weldingtool/experimental(src)//This can be changed if this is too much - new /obj/item/multitool(src) - new /obj/item/stack/cable_coil(src,MAXCOIL,pick("red","yellow","orange")) - new /obj/item/extinguisher/mini(src) - new /obj/item/analyzer(src) - //much roomier now that we've managed to remove two tools - -/obj/item/storage/belt/utility/full/PopulateContents() - new /obj/item/screwdriver(src) - new /obj/item/wrench(src) - new /obj/item/weldingtool(src) - new /obj/item/crowbar(src) - new /obj/item/wirecutters(src) - new /obj/item/multitool(src) - new /obj/item/stack/cable_coil(src,MAXCOIL,pick("red","yellow","orange")) - -/obj/item/storage/belt/utility/full/engi/PopulateContents() - new /obj/item/screwdriver(src) - new /obj/item/wrench(src) - new /obj/item/weldingtool/largetank(src) - new /obj/item/crowbar(src) - new /obj/item/wirecutters(src) - new /obj/item/multitool(src) - new /obj/item/stack/cable_coil(src,MAXCOIL,pick("red","yellow","orange")) - - -/obj/item/storage/belt/utility/atmostech/PopulateContents() - new /obj/item/screwdriver(src) - new /obj/item/wrench(src) - new /obj/item/weldingtool(src) - new /obj/item/crowbar(src) - new /obj/item/wirecutters(src) - new /obj/item/t_scanner(src) - new /obj/item/extinguisher/mini(src) - -/obj/item/storage/belt/utility/syndicate/PopulateContents() - new /obj/item/screwdriver/nuke(src) - new /obj/item/wrench/combat(src) - new /obj/item/weldingtool/largetank(src) - new /obj/item/crowbar(src) - new /obj/item/wirecutters(src) - new /obj/item/multitool(src) - new /obj/item/inducer/syndicate(src) - -/obj/item/storage/belt/medical - name = "medical belt" - desc = "Can hold various medical equipment." - icon_state = "medicalbelt" - item_state = "medical" - -/obj/item/storage/belt/medical/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_BULKY - STR.max_combined_w_class = 21 - STR.set_holdable(list( - /obj/item/healthanalyzer, - /obj/item/dnainjector, - /obj/item/reagent_containers/dropper, - /obj/item/reagent_containers/glass/beaker, - /obj/item/reagent_containers/glass/bottle, - /obj/item/reagent_containers/pill, - /obj/item/reagent_containers/syringe, - /obj/item/reagent_containers/medigel, - /obj/item/lighter, - /obj/item/storage/fancy/cigarettes, - /obj/item/storage/pill_bottle, - /obj/item/stack/medical, - /obj/item/flashlight/pen, - /obj/item/extinguisher/mini, - /obj/item/reagent_containers/hypospray, - /obj/item/sensor_device, - /obj/item/radio, - /obj/item/clothing/gloves/, - /obj/item/lazarus_injector, - /obj/item/bikehorn/rubberducky, - /obj/item/clothing/mask/surgical, - /obj/item/clothing/mask/breath, - /obj/item/clothing/mask/breath/medical, - /obj/item/scalpel, - /obj/item/circular_saw, - /obj/item/surgicaldrill, - /obj/item/retractor, - /obj/item/cautery, - /obj/item/hemostat, - /obj/item/geiger_counter, - /obj/item/clothing/neck/stethoscope, - /obj/item/stamp, - /obj/item/clothing/glasses, - /obj/item/wrench/medical, - /obj/item/clothing/mask/muzzle, - /obj/item/storage/bag/chemistry, - /obj/item/storage/bag/bio, - /obj/item/reagent_containers/blood, - /obj/item/tank/internals/emergency_oxygen, - /obj/item/gun/syringe/syndicate, - /obj/item/implantcase, - /obj/item/implant, - /obj/item/implanter, - /obj/item/pinpointer/crew, - /obj/item/holosign_creator/medical, - /obj/item/construction/plumbing, - /obj/item/plunger, - /obj/item/reagent_containers/spray, - /obj/item/shears - )) - -/obj/item/storage/belt/medical/paramedic/PopulateContents() - new /obj/item/sensor_device(src) - new /obj/item/pinpointer/crew/prox(src) - new /obj/item/stack/medical/gauze/twelve(src) - new /obj/item/reagent_containers/syringe(src) - new /obj/item/reagent_containers/glass/bottle/epinephrine(src) - new /obj/item/reagent_containers/glass/bottle/formaldehyde(src) - update_icon() - -/obj/item/storage/belt/security - name = "security belt" - desc = "Can hold security gear like handcuffs and flashes." - icon_state = "securitybelt" - item_state = "security"//Could likely use a better one. - content_overlays = TRUE - -/obj/item/storage/belt/security/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 5 - STR.max_w_class = WEIGHT_CLASS_NORMAL - STR.set_holdable(list( - /obj/item/melee/baton, - /obj/item/melee/classic_baton, - /obj/item/grenade, - /obj/item/reagent_containers/spray/pepper, - /obj/item/restraints/handcuffs, - /obj/item/assembly/flash/handheld, - /obj/item/clothing/glasses, - /obj/item/ammo_casing/shotgun, - /obj/item/ammo_box, - /obj/item/reagent_containers/food/snacks/donut, - /obj/item/kitchen/knife/combat, - /obj/item/flashlight/seclite, - /obj/item/melee/classic_baton/telescopic, - /obj/item/radio, - /obj/item/clothing/gloves, - /obj/item/restraints/legcuffs/bola, - /obj/item/holosign_creator/security - )) - -/obj/item/storage/belt/security/full/PopulateContents() - new /obj/item/reagent_containers/spray/pepper(src) - new /obj/item/restraints/handcuffs(src) - new /obj/item/grenade/flashbang(src) - new /obj/item/assembly/flash/handheld(src) - new /obj/item/melee/baton/loaded(src) - update_icon() - -/obj/item/storage/belt/security/webbing - name = "security webbing" - desc = "Unique and versatile chest rig, can hold security gear." - icon_state = "securitywebbing" - item_state = "securitywebbing" - content_overlays = FALSE - custom_premium_price = 900 - -/obj/item/storage/belt/security/webbing/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 6 - -/obj/item/storage/belt/mining - name = "explorer's webbing" - desc = "A versatile chest rig, cherished by miners and hunters alike." - icon_state = "explorer1" - item_state = "explorer1" - w_class = WEIGHT_CLASS_BULKY - -/obj/item/storage/belt/mining/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 6 - STR.max_w_class = WEIGHT_CLASS_BULKY - STR.max_combined_w_class = 20 - STR.set_holdable(list( - /obj/item/crowbar, - /obj/item/screwdriver, - /obj/item/weldingtool, - /obj/item/wirecutters, - /obj/item/wrench, - /obj/item/multitool, - /obj/item/flashlight, - /obj/item/stack/cable_coil, - /obj/item/analyzer, - /obj/item/extinguisher/mini, - /obj/item/radio, - /obj/item/clothing/gloves, - /obj/item/resonator, - /obj/item/mining_scanner, - /obj/item/pickaxe, - /obj/item/shovel, - /obj/item/stack/sheet/animalhide, - /obj/item/stack/sheet/sinew, - /obj/item/stack/sheet/bone, - /obj/item/lighter, - /obj/item/storage/fancy/cigarettes, - /obj/item/reagent_containers/food/drinks/bottle, - /obj/item/stack/medical, - /obj/item/kitchen/knife, - /obj/item/reagent_containers/hypospray, - /obj/item/gps, - /obj/item/storage/bag/ore, - /obj/item/survivalcapsule, - /obj/item/t_scanner/adv_mining_scanner, - /obj/item/reagent_containers/pill, - /obj/item/storage/pill_bottle, - /obj/item/stack/ore, - /obj/item/reagent_containers/food/drinks, - /obj/item/organ/regenerative_core, - /obj/item/wormhole_jaunter, - /obj/item/storage/bag/plants, - /obj/item/stack/marker_beacon, - /obj/item/key/lasso - )) - - -/obj/item/storage/belt/mining/vendor - contents = newlist(/obj/item/survivalcapsule) - -/obj/item/storage/belt/mining/alt - icon_state = "explorer2" - item_state = "explorer2" - -/obj/item/storage/belt/mining/primitive - name = "hunter's belt" - desc = "A versatile belt, woven from sinew." - icon_state = "ebelt" - item_state = "ebelt" - -/obj/item/storage/belt/mining/primitive/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 5 - -/obj/item/storage/belt/soulstone - name = "soul stone belt" - desc = "Designed for ease of access to the shards during a fight, as to not let a single enemy spirit slip away." - icon_state = "soulstonebelt" - item_state = "soulstonebelt" - -/obj/item/storage/belt/soulstone/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 6 - STR.set_holdable(list( - /obj/item/soulstone - )) - -/obj/item/storage/belt/soulstone/full/PopulateContents() - for(var/i in 1 to 6) - new /obj/item/soulstone(src) - -/obj/item/storage/belt/soulstone/full/chappy/PopulateContents() - for(var/i in 1 to 6) - new /obj/item/soulstone/anybody/chaplain(src) - -/obj/item/storage/belt/soulstone/full/purified/PopulateContents() - for(var/i in 1 to 6) - new /obj/item/soulstone/anybody/purified(src) - -/obj/item/storage/belt/champion - name = "championship belt" - desc = "Proves to the world that you are the strongest!" - icon_state = "championbelt" - item_state = "championbelt" - custom_materials = list(/datum/material/gold=400) - -/obj/item/storage/belt/champion/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 1 - STR.set_holdable(list( - /obj/item/clothing/mask/luchador - )) - -/obj/item/storage/belt/military - name = "chest rig" - desc = "A set of tactical webbing worn by Syndicate boarding parties." - icon_state = "militarywebbing" - item_state = "militarywebbing" - resistance_flags = FIRE_PROOF - -/obj/item/storage/belt/military/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_SMALL - -/obj/item/storage/belt/military/snack - name = "tactical snack rig" - -/obj/item/storage/belt/military/snack/Initialize() - . = ..() - var/sponsor = pick("DonkCo", "Waffle Co.", "Roffle Co.", "Gorlax Marauders", "Tiger Cooperative") - desc = "A set of snack-tical webbing worn by athletes of the [sponsor] VR sports division." - -/obj/item/storage/belt/military/snack/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 6 - STR.max_w_class = WEIGHT_CLASS_SMALL - STR.set_holdable(list( - /obj/item/reagent_containers/food/snacks, - /obj/item/reagent_containers/food/drinks - )) - - var/amount = 5 - var/rig_snacks - while(contents.len <= amount) - rig_snacks = pick(list( - /obj/item/reagent_containers/food/snacks/candy, - /obj/item/reagent_containers/food/drinks/dry_ramen, - /obj/item/reagent_containers/food/snacks/chips, - /obj/item/reagent_containers/food/snacks/sosjerky, - /obj/item/reagent_containers/food/snacks/syndicake, - /obj/item/reagent_containers/food/snacks/spacetwinkie, - /obj/item/reagent_containers/food/snacks/cheesiehonkers, - /obj/item/reagent_containers/food/snacks/nachos, - /obj/item/reagent_containers/food/snacks/cheesynachos, - /obj/item/reagent_containers/food/snacks/cubannachos, - /obj/item/reagent_containers/food/snacks/nugget, - /obj/item/reagent_containers/food/snacks/spaghetti/pastatomato, - /obj/item/reagent_containers/food/snacks/rofflewaffles, - /obj/item/reagent_containers/food/snacks/donkpocket, - /obj/item/reagent_containers/food/drinks/soda_cans/cola, - /obj/item/reagent_containers/food/drinks/soda_cans/space_mountain_wind, - /obj/item/reagent_containers/food/drinks/soda_cans/dr_gibb, - /obj/item/reagent_containers/food/drinks/soda_cans/starkist, - /obj/item/reagent_containers/food/drinks/soda_cans/space_up, - /obj/item/reagent_containers/food/drinks/soda_cans/pwr_game, - /obj/item/reagent_containers/food/drinks/soda_cans/lemon_lime, - /obj/item/reagent_containers/food/drinks/drinkingglass/filled/nuka_cola - )) - new rig_snacks(src) - -/obj/item/storage/belt/military/abductor - name = "agent belt" - desc = "A belt used by abductor agents." - icon = 'icons/obj/abductor.dmi' - icon_state = "belt" - item_state = "security" - -/obj/item/storage/belt/military/abductor/full/PopulateContents() - new /obj/item/screwdriver/abductor(src) - new /obj/item/wrench/abductor(src) - new /obj/item/weldingtool/abductor(src) - new /obj/item/crowbar/abductor(src) - new /obj/item/wirecutters/abductor(src) - new /obj/item/multitool/abductor(src) - new /obj/item/stack/cable_coil(src,MAXCOIL,"white") - -/obj/item/storage/belt/military/army - name = "army belt" - desc = "A belt used by military forces." - icon_state = "grenadebeltold" - item_state = "security" - -/obj/item/storage/belt/military/assault - name = "assault belt" - desc = "A tactical assault belt." - icon_state = "assaultbelt" - item_state = "security" - -/obj/item/storage/belt/military/assault/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 6 - -/obj/item/storage/belt/grenade - name = "grenadier belt" - desc = "A belt for holding grenades." - icon_state = "grenadebeltnew" - item_state = "security" - -/obj/item/storage/belt/grenade/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 30 - STR.display_numerical_stacking = TRUE - STR.max_combined_w_class = 60 - STR.max_w_class = WEIGHT_CLASS_BULKY - STR.set_holdable(list( - /obj/item/grenade, - /obj/item/screwdriver, - /obj/item/lighter, - /obj/item/multitool, - /obj/item/reagent_containers/food/drinks/bottle/molotov, - /obj/item/grenade/c4, - /obj/item/reagent_containers/food/snacks/grown/cherry_bomb, - /obj/item/reagent_containers/food/snacks/grown/firelemon - )) - -/obj/item/storage/belt/grenade/full/PopulateContents() - var/static/items_inside = list( - /obj/item/grenade/flashbang = 1, - /obj/item/grenade/smokebomb = 4, - /obj/item/grenade/empgrenade = 1, - /obj/item/grenade/empgrenade = 1, - /obj/item/grenade/frag = 10, - /obj/item/grenade/gluon = 4, - /obj/item/grenade/chem_grenade/incendiary = 2, - /obj/item/grenade/chem_grenade/facid = 1, - /obj/item/grenade/syndieminibomb = 2, - /obj/item/screwdriver = 1, - /obj/item/multitool = 1) - generate_items_inside(items_inside,src) - - -/obj/item/storage/belt/wands - name = "wand belt" - desc = "A belt designed to hold various rods of power. A veritable fanny pack of exotic magic." - icon_state = "soulstonebelt" - item_state = "soulstonebelt" - -/obj/item/storage/belt/wands/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 6 - STR.set_holdable(list( - /obj/item/gun/magic/wand - )) - -/obj/item/storage/belt/wands/full/PopulateContents() - new /obj/item/gun/magic/wand/death(src) - new /obj/item/gun/magic/wand/resurrection(src) - new /obj/item/gun/magic/wand/polymorph(src) - new /obj/item/gun/magic/wand/teleport(src) - new /obj/item/gun/magic/wand/door(src) - new /obj/item/gun/magic/wand/fireball(src) - - for(var/obj/item/gun/magic/wand/W in contents) //All wands in this pack come in the best possible condition - W.max_charges = initial(W.max_charges) - W.charges = W.max_charges - -/obj/item/storage/belt/janitor - name = "janibelt" - desc = "A belt used to hold most janitorial supplies." - icon_state = "janibelt" - item_state = "janibelt" - -/obj/item/storage/belt/janitor/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 6 - STR.max_w_class = WEIGHT_CLASS_BULKY // Set to this so the light replacer can fit. - STR.set_holdable(list( - /obj/item/grenade/chem_grenade, - /obj/item/lightreplacer, - /obj/item/flashlight, - /obj/item/reagent_containers/spray, - /obj/item/soap, - /obj/item/holosign_creator, - /obj/item/forcefield_projector, - /obj/item/key/janitor, - /obj/item/clothing/gloves, - /obj/item/melee/flyswatter, - /obj/item/assembly/mousetrap, - /obj/item/paint/paint_remover, - /obj/item/pushbroom - )) - -/obj/item/storage/belt/janitor/full/PopulateContents() - new /obj/item/lightreplacer(src) - new /obj/item/reagent_containers/spray/cleaner(src) - new /obj/item/soap/nanotrasen(src) - new /obj/item/holosign_creator(src) - new /obj/item/melee/flyswatter(src) - -/obj/item/storage/belt/bandolier - name = "bandolier" - desc = "A bandolier for holding shotgun ammunition." - icon_state = "bandolier" - item_state = "bandolier" - -/obj/item/storage/belt/bandolier/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 18 - STR.display_numerical_stacking = TRUE - STR.set_holdable(list( - /obj/item/ammo_casing/shotgun - )) - -/obj/item/storage/belt/fannypack - name = "fannypack" - desc = "A dorky fannypack for keeping small items in." - icon_state = "fannypack_leather" - item_state = "fannypack_leather" - dying_key = DYE_REGISTRY_FANNYPACK - custom_price = 100 - -/obj/item/storage/belt/fannypack/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 3 - STR.max_w_class = WEIGHT_CLASS_SMALL - -/obj/item/storage/belt/fannypack/black - name = "black fannypack" - icon_state = "fannypack_black" - item_state = "fannypack_black" - -/obj/item/storage/belt/fannypack/red - name = "red fannypack" - icon_state = "fannypack_red" - item_state = "fannypack_red" - -/obj/item/storage/belt/fannypack/purple - name = "purple fannypack" - icon_state = "fannypack_purple" - item_state = "fannypack_purple" - -/obj/item/storage/belt/fannypack/blue - name = "blue fannypack" - icon_state = "fannypack_blue" - item_state = "fannypack_blue" - -/obj/item/storage/belt/fannypack/orange - name = "orange fannypack" - icon_state = "fannypack_orange" - item_state = "fannypack_orange" - -/obj/item/storage/belt/fannypack/white - name = "white fannypack" - icon_state = "fannypack_white" - item_state = "fannypack_white" - -/obj/item/storage/belt/fannypack/green - name = "green fannypack" - icon_state = "fannypack_green" - item_state = "fannypack_green" - -/obj/item/storage/belt/fannypack/pink - name = "pink fannypack" - icon_state = "fannypack_pink" - item_state = "fannypack_pink" - -/obj/item/storage/belt/fannypack/cyan - name = "cyan fannypack" - icon_state = "fannypack_cyan" - item_state = "fannypack_cyan" - -/obj/item/storage/belt/fannypack/yellow - name = "yellow fannypack" - icon_state = "fannypack_yellow" - item_state = "fannypack_yellow" - -/obj/item/storage/belt/sabre - name = "sabre sheath" - desc = "An ornate sheath designed to hold an officer's blade." - icon_state = "sheath" - item_state = "sheath" - w_class = WEIGHT_CLASS_BULKY - -/obj/item/storage/belt/sabre/ComponentInitialize() - . = ..() - AddElement(/datum/element/update_icon_updates_onmob) - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 1 - STR.rustle_sound = FALSE - STR.max_w_class = WEIGHT_CLASS_BULKY - STR.set_holdable(list( - /obj/item/melee/sabre - )) - -/obj/item/storage/belt/sabre/examine(mob/user) - . = ..() - if(length(contents)) - . += "Alt-click it to quickly draw the blade." - -/obj/item/storage/belt/sabre/AltClick(mob/user) - if(!iscarbon(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) - return - if(length(contents)) - var/obj/item/I = contents[1] - user.visible_message("[user] takes [I] out of [src].", "You take [I] out of [src].") - user.put_in_hands(I) - update_icon() - else - to_chat(user, "[src] is empty!") - -/obj/item/storage/belt/sabre/update_icon_state() - icon_state = "sheath" - item_state = "sheath" - if(contents.len) - icon_state += "-sabre" - item_state += "-sabre" - -/obj/item/storage/belt/sabre/PopulateContents() - new /obj/item/melee/sabre(src) - update_icon() +/obj/item/storage/belt + name = "belt" + desc = "Can hold various things." + icon = 'icons/obj/clothing/belts.dmi' + icon_state = "utilitybelt" + item_state = "utility" + lefthand_file = 'icons/mob/inhands/equipment/belt_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/belt_righthand.dmi' + slot_flags = ITEM_SLOT_BELT + attack_verb = list("whipped", "lashed", "disciplined") + max_integrity = 300 + equip_sound = 'sound/items/equip/toolbelt_equip.ogg' + var/content_overlays = FALSE //If this is true, the belt will gain overlays based on what it's holding + +/obj/item/storage/belt/suicide_act(mob/living/carbon/user) + user.visible_message("[user] begins belting [user.p_them()]self with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return BRUTELOSS + +/obj/item/storage/belt/update_overlays() + . = ..() + if(content_overlays) + for(var/obj/item/I in contents) + . += I.get_belt_overlay() + +/obj/item/storage/belt/Initialize() + . = ..() + update_icon() + +/obj/item/storage/belt/utility + name = "toolbelt" //Carn: utility belt is nicer, but it bamboozles the text parsing. + desc = "Holds tools." + icon_state = "utilitybelt" + item_state = "utility" + content_overlays = TRUE + custom_premium_price = 300 + drop_sound = 'sound/items/handling/toolbelt_drop.ogg' + pickup_sound = 'sound/items/handling/toolbelt_pickup.ogg' + +/obj/item/storage/belt/utility/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_NORMAL + STR.max_combined_w_class = 21 + STR.set_holdable(list( + /obj/item/crowbar, + /obj/item/screwdriver, + /obj/item/weldingtool, + /obj/item/wirecutters, + /obj/item/wrench, + /obj/item/multitool, + /obj/item/flashlight, + /obj/item/stack/cable_coil, + /obj/item/t_scanner, + /obj/item/analyzer, + /obj/item/geiger_counter, + /obj/item/extinguisher/mini, + /obj/item/radio, + /obj/item/clothing/gloves, + /obj/item/holosign_creator/atmos, + /obj/item/holosign_creator/engineering, + /obj/item/forcefield_projector, + /obj/item/assembly/signaler, + /obj/item/lightreplacer, + /obj/item/construction/rcd, + /obj/item/pipe_dispenser, + /obj/item/inducer, + /obj/item/plunger + )) + +/obj/item/storage/belt/utility/chief + name = "\improper Chief Engineer's toolbelt" //"the Chief Engineer's toolbelt", because "Chief Engineer's toolbelt" is not a proper noun + desc = "Holds tools, looks snazzy." + icon_state = "utilitybelt_ce" + item_state = "utility_ce" + +/obj/item/storage/belt/utility/chief/full/PopulateContents() + new /obj/item/screwdriver/power(src) + new /obj/item/crowbar/power(src) + new /obj/item/weldingtool/experimental(src)//This can be changed if this is too much + new /obj/item/multitool(src) + new /obj/item/stack/cable_coil(src,MAXCOIL,pick("red","yellow","orange")) + new /obj/item/extinguisher/mini(src) + new /obj/item/analyzer(src) + //much roomier now that we've managed to remove two tools + +/obj/item/storage/belt/utility/full/PopulateContents() + new /obj/item/screwdriver(src) + new /obj/item/wrench(src) + new /obj/item/weldingtool(src) + new /obj/item/crowbar(src) + new /obj/item/wirecutters(src) + new /obj/item/multitool(src) + new /obj/item/stack/cable_coil(src,MAXCOIL,pick("red","yellow","orange")) + +/obj/item/storage/belt/utility/full/engi/PopulateContents() + new /obj/item/screwdriver(src) + new /obj/item/wrench(src) + new /obj/item/weldingtool/largetank(src) + new /obj/item/crowbar(src) + new /obj/item/wirecutters(src) + new /obj/item/multitool(src) + new /obj/item/stack/cable_coil(src,MAXCOIL,pick("red","yellow","orange")) + + +/obj/item/storage/belt/utility/atmostech/PopulateContents() + new /obj/item/screwdriver(src) + new /obj/item/wrench(src) + new /obj/item/weldingtool(src) + new /obj/item/crowbar(src) + new /obj/item/wirecutters(src) + new /obj/item/t_scanner(src) + new /obj/item/extinguisher/mini(src) + +/obj/item/storage/belt/utility/syndicate/PopulateContents() + new /obj/item/screwdriver/nuke(src) + new /obj/item/wrench/combat(src) + new /obj/item/weldingtool/largetank(src) + new /obj/item/crowbar(src) + new /obj/item/wirecutters(src) + new /obj/item/multitool(src) + new /obj/item/inducer/syndicate(src) + +/obj/item/storage/belt/medical + name = "medical belt" + desc = "Can hold various medical equipment." + icon_state = "medicalbelt" + item_state = "medical" + +/obj/item/storage/belt/medical/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_BULKY + STR.max_combined_w_class = 21 + STR.set_holdable(list( + /obj/item/healthanalyzer, + /obj/item/dnainjector, + /obj/item/reagent_containers/dropper, + /obj/item/reagent_containers/glass/beaker, + /obj/item/reagent_containers/glass/bottle, + /obj/item/reagent_containers/pill, + /obj/item/reagent_containers/syringe, + /obj/item/reagent_containers/medigel, + /obj/item/lighter, + /obj/item/storage/fancy/cigarettes, + /obj/item/storage/pill_bottle, + /obj/item/stack/medical, + /obj/item/flashlight/pen, + /obj/item/extinguisher/mini, + /obj/item/reagent_containers/hypospray, + /obj/item/sensor_device, + /obj/item/radio, + /obj/item/clothing/gloves/, + /obj/item/lazarus_injector, + /obj/item/bikehorn/rubberducky, + /obj/item/clothing/mask/surgical, + /obj/item/clothing/mask/breath, + /obj/item/clothing/mask/breath/medical, + /obj/item/scalpel, + /obj/item/circular_saw, + /obj/item/surgicaldrill, + /obj/item/retractor, + /obj/item/cautery, + /obj/item/hemostat, + /obj/item/geiger_counter, + /obj/item/clothing/neck/stethoscope, + /obj/item/stamp, + /obj/item/clothing/glasses, + /obj/item/wrench/medical, + /obj/item/clothing/mask/muzzle, + /obj/item/storage/bag/chemistry, + /obj/item/storage/bag/bio, + /obj/item/reagent_containers/blood, + /obj/item/tank/internals/emergency_oxygen, + /obj/item/gun/syringe/syndicate, + /obj/item/implantcase, + /obj/item/implant, + /obj/item/implanter, + /obj/item/pinpointer/crew, + /obj/item/holosign_creator/medical, + /obj/item/construction/plumbing, + /obj/item/plunger, + /obj/item/reagent_containers/spray, + /obj/item/shears + )) + +/obj/item/storage/belt/medical/paramedic/PopulateContents() + new /obj/item/sensor_device(src) + new /obj/item/pinpointer/crew/prox(src) + new /obj/item/stack/medical/gauze/twelve(src) + new /obj/item/reagent_containers/syringe(src) + new /obj/item/reagent_containers/glass/bottle/epinephrine(src) + new /obj/item/reagent_containers/glass/bottle/formaldehyde(src) + update_icon() + +/obj/item/storage/belt/security + name = "security belt" + desc = "Can hold security gear like handcuffs and flashes." + icon_state = "securitybelt" + item_state = "security"//Could likely use a better one. + content_overlays = TRUE + +/obj/item/storage/belt/security/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 5 + STR.max_w_class = WEIGHT_CLASS_NORMAL + STR.set_holdable(list( + /obj/item/melee/baton, + /obj/item/melee/classic_baton, + /obj/item/grenade, + /obj/item/reagent_containers/spray/pepper, + /obj/item/restraints/handcuffs, + /obj/item/assembly/flash/handheld, + /obj/item/clothing/glasses, + /obj/item/ammo_casing/shotgun, + /obj/item/ammo_box, + /obj/item/reagent_containers/food/snacks/donut, + /obj/item/kitchen/knife/combat, + /obj/item/flashlight/seclite, + /obj/item/melee/classic_baton/telescopic, + /obj/item/radio, + /obj/item/clothing/gloves, + /obj/item/restraints/legcuffs/bola, + /obj/item/holosign_creator/security + )) + +/obj/item/storage/belt/security/full/PopulateContents() + new /obj/item/reagent_containers/spray/pepper(src) + new /obj/item/restraints/handcuffs(src) + new /obj/item/grenade/flashbang(src) + new /obj/item/assembly/flash/handheld(src) + new /obj/item/melee/baton/loaded(src) + update_icon() + +/obj/item/storage/belt/security/webbing + name = "security webbing" + desc = "Unique and versatile chest rig, can hold security gear." + icon_state = "securitywebbing" + item_state = "securitywebbing" + content_overlays = FALSE + custom_premium_price = 900 + +/obj/item/storage/belt/security/webbing/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 6 + +/obj/item/storage/belt/mining + name = "explorer's webbing" + desc = "A versatile chest rig, cherished by miners and hunters alike." + icon_state = "explorer1" + item_state = "explorer1" + w_class = WEIGHT_CLASS_BULKY + +/obj/item/storage/belt/mining/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 6 + STR.max_w_class = WEIGHT_CLASS_BULKY + STR.max_combined_w_class = 20 + STR.set_holdable(list( + /obj/item/crowbar, + /obj/item/screwdriver, + /obj/item/weldingtool, + /obj/item/wirecutters, + /obj/item/wrench, + /obj/item/multitool, + /obj/item/flashlight, + /obj/item/stack/cable_coil, + /obj/item/analyzer, + /obj/item/extinguisher/mini, + /obj/item/radio, + /obj/item/clothing/gloves, + /obj/item/resonator, + /obj/item/mining_scanner, + /obj/item/pickaxe, + /obj/item/shovel, + /obj/item/stack/sheet/animalhide, + /obj/item/stack/sheet/sinew, + /obj/item/stack/sheet/bone, + /obj/item/lighter, + /obj/item/storage/fancy/cigarettes, + /obj/item/reagent_containers/food/drinks/bottle, + /obj/item/stack/medical, + /obj/item/kitchen/knife, + /obj/item/reagent_containers/hypospray, + /obj/item/gps, + /obj/item/storage/bag/ore, + /obj/item/survivalcapsule, + /obj/item/t_scanner/adv_mining_scanner, + /obj/item/reagent_containers/pill, + /obj/item/storage/pill_bottle, + /obj/item/stack/ore, + /obj/item/reagent_containers/food/drinks, + /obj/item/organ/regenerative_core, + /obj/item/wormhole_jaunter, + /obj/item/storage/bag/plants, + /obj/item/stack/marker_beacon, + /obj/item/key/lasso + )) + + +/obj/item/storage/belt/mining/vendor + contents = newlist(/obj/item/survivalcapsule) + +/obj/item/storage/belt/mining/alt + icon_state = "explorer2" + item_state = "explorer2" + +/obj/item/storage/belt/mining/primitive + name = "hunter's belt" + desc = "A versatile belt, woven from sinew." + icon_state = "ebelt" + item_state = "ebelt" + +/obj/item/storage/belt/mining/primitive/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 5 + +/obj/item/storage/belt/soulstone + name = "soul stone belt" + desc = "Designed for ease of access to the shards during a fight, as to not let a single enemy spirit slip away." + icon_state = "soulstonebelt" + item_state = "soulstonebelt" + +/obj/item/storage/belt/soulstone/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 6 + STR.set_holdable(list( + /obj/item/soulstone + )) + +/obj/item/storage/belt/soulstone/full/PopulateContents() + for(var/i in 1 to 6) + new /obj/item/soulstone(src) + +/obj/item/storage/belt/soulstone/full/chappy/PopulateContents() + for(var/i in 1 to 6) + new /obj/item/soulstone/anybody/chaplain(src) + +/obj/item/storage/belt/soulstone/full/purified/PopulateContents() + for(var/i in 1 to 6) + new /obj/item/soulstone/anybody/purified(src) + +/obj/item/storage/belt/champion + name = "championship belt" + desc = "Proves to the world that you are the strongest!" + icon_state = "championbelt" + item_state = "championbelt" + custom_materials = list(/datum/material/gold=400) + +/obj/item/storage/belt/champion/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 1 + STR.set_holdable(list( + /obj/item/clothing/mask/luchador + )) + +/obj/item/storage/belt/military + name = "chest rig" + desc = "A set of tactical webbing worn by Syndicate boarding parties." + icon_state = "militarywebbing" + item_state = "militarywebbing" + resistance_flags = FIRE_PROOF + +/obj/item/storage/belt/military/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_SMALL + +/obj/item/storage/belt/military/snack + name = "tactical snack rig" + +/obj/item/storage/belt/military/snack/Initialize() + . = ..() + var/sponsor = pick("DonkCo", "Waffle Co.", "Roffle Co.", "Gorlax Marauders", "Tiger Cooperative") + desc = "A set of snack-tical webbing worn by athletes of the [sponsor] VR sports division." + +/obj/item/storage/belt/military/snack/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 6 + STR.max_w_class = WEIGHT_CLASS_SMALL + STR.set_holdable(list( + /obj/item/reagent_containers/food/snacks, + /obj/item/reagent_containers/food/drinks + )) + + var/amount = 5 + var/rig_snacks + while(contents.len <= amount) + rig_snacks = pick(list( + /obj/item/reagent_containers/food/snacks/candy, + /obj/item/reagent_containers/food/drinks/dry_ramen, + /obj/item/reagent_containers/food/snacks/chips, + /obj/item/reagent_containers/food/snacks/sosjerky, + /obj/item/reagent_containers/food/snacks/syndicake, + /obj/item/reagent_containers/food/snacks/spacetwinkie, + /obj/item/reagent_containers/food/snacks/cheesiehonkers, + /obj/item/reagent_containers/food/snacks/nachos, + /obj/item/reagent_containers/food/snacks/cheesynachos, + /obj/item/reagent_containers/food/snacks/cubannachos, + /obj/item/reagent_containers/food/snacks/nugget, + /obj/item/reagent_containers/food/snacks/spaghetti/pastatomato, + /obj/item/reagent_containers/food/snacks/rofflewaffles, + /obj/item/reagent_containers/food/snacks/donkpocket, + /obj/item/reagent_containers/food/drinks/soda_cans/cola, + /obj/item/reagent_containers/food/drinks/soda_cans/space_mountain_wind, + /obj/item/reagent_containers/food/drinks/soda_cans/dr_gibb, + /obj/item/reagent_containers/food/drinks/soda_cans/starkist, + /obj/item/reagent_containers/food/drinks/soda_cans/space_up, + /obj/item/reagent_containers/food/drinks/soda_cans/pwr_game, + /obj/item/reagent_containers/food/drinks/soda_cans/lemon_lime, + /obj/item/reagent_containers/food/drinks/drinkingglass/filled/nuka_cola + )) + new rig_snacks(src) + +/obj/item/storage/belt/military/abductor + name = "agent belt" + desc = "A belt used by abductor agents." + icon = 'icons/obj/abductor.dmi' + icon_state = "belt" + item_state = "security" + +/obj/item/storage/belt/military/abductor/full/PopulateContents() + new /obj/item/screwdriver/abductor(src) + new /obj/item/wrench/abductor(src) + new /obj/item/weldingtool/abductor(src) + new /obj/item/crowbar/abductor(src) + new /obj/item/wirecutters/abductor(src) + new /obj/item/multitool/abductor(src) + new /obj/item/stack/cable_coil(src,MAXCOIL,"white") + +/obj/item/storage/belt/military/army + name = "army belt" + desc = "A belt used by military forces." + icon_state = "grenadebeltold" + item_state = "security" + +/obj/item/storage/belt/military/assault + name = "assault belt" + desc = "A tactical assault belt." + icon_state = "assaultbelt" + item_state = "security" + +/obj/item/storage/belt/military/assault/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 6 + +/obj/item/storage/belt/grenade + name = "grenadier belt" + desc = "A belt for holding grenades." + icon_state = "grenadebeltnew" + item_state = "security" + +/obj/item/storage/belt/grenade/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 30 + STR.display_numerical_stacking = TRUE + STR.max_combined_w_class = 60 + STR.max_w_class = WEIGHT_CLASS_BULKY + STR.set_holdable(list( + /obj/item/grenade, + /obj/item/screwdriver, + /obj/item/lighter, + /obj/item/multitool, + /obj/item/reagent_containers/food/drinks/bottle/molotov, + /obj/item/grenade/c4, + /obj/item/reagent_containers/food/snacks/grown/cherry_bomb, + /obj/item/reagent_containers/food/snacks/grown/firelemon + )) + +/obj/item/storage/belt/grenade/full/PopulateContents() + var/static/items_inside = list( + /obj/item/grenade/flashbang = 1, + /obj/item/grenade/smokebomb = 4, + /obj/item/grenade/empgrenade = 1, + /obj/item/grenade/empgrenade = 1, + /obj/item/grenade/frag = 10, + /obj/item/grenade/gluon = 4, + /obj/item/grenade/chem_grenade/incendiary = 2, + /obj/item/grenade/chem_grenade/facid = 1, + /obj/item/grenade/syndieminibomb = 2, + /obj/item/screwdriver = 1, + /obj/item/multitool = 1) + generate_items_inside(items_inside,src) + + +/obj/item/storage/belt/wands + name = "wand belt" + desc = "A belt designed to hold various rods of power. A veritable fanny pack of exotic magic." + icon_state = "soulstonebelt" + item_state = "soulstonebelt" + +/obj/item/storage/belt/wands/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 6 + STR.set_holdable(list( + /obj/item/gun/magic/wand + )) + +/obj/item/storage/belt/wands/full/PopulateContents() + new /obj/item/gun/magic/wand/death(src) + new /obj/item/gun/magic/wand/resurrection(src) + new /obj/item/gun/magic/wand/polymorph(src) + new /obj/item/gun/magic/wand/teleport(src) + new /obj/item/gun/magic/wand/door(src) + new /obj/item/gun/magic/wand/fireball(src) + + for(var/obj/item/gun/magic/wand/W in contents) //All wands in this pack come in the best possible condition + W.max_charges = initial(W.max_charges) + W.charges = W.max_charges + +/obj/item/storage/belt/janitor + name = "janibelt" + desc = "A belt used to hold most janitorial supplies." + icon_state = "janibelt" + item_state = "janibelt" + +/obj/item/storage/belt/janitor/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 6 + STR.max_w_class = WEIGHT_CLASS_BULKY // Set to this so the light replacer can fit. + STR.set_holdable(list( + /obj/item/grenade/chem_grenade, + /obj/item/lightreplacer, + /obj/item/flashlight, + /obj/item/reagent_containers/spray, + /obj/item/soap, + /obj/item/holosign_creator, + /obj/item/forcefield_projector, + /obj/item/key/janitor, + /obj/item/clothing/gloves, + /obj/item/melee/flyswatter, + /obj/item/assembly/mousetrap, + /obj/item/paint/paint_remover, + /obj/item/pushbroom + )) + +/obj/item/storage/belt/janitor/full/PopulateContents() + new /obj/item/lightreplacer(src) + new /obj/item/reagent_containers/spray/cleaner(src) + new /obj/item/soap/nanotrasen(src) + new /obj/item/holosign_creator(src) + new /obj/item/melee/flyswatter(src) + +/obj/item/storage/belt/bandolier + name = "bandolier" + desc = "A bandolier for holding shotgun ammunition." + icon_state = "bandolier" + item_state = "bandolier" + +/obj/item/storage/belt/bandolier/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 18 + STR.display_numerical_stacking = TRUE + STR.set_holdable(list( + /obj/item/ammo_casing/shotgun + )) + +/obj/item/storage/belt/fannypack + name = "fannypack" + desc = "A dorky fannypack for keeping small items in." + icon_state = "fannypack_leather" + item_state = "fannypack_leather" + dying_key = DYE_REGISTRY_FANNYPACK + custom_price = 100 + +/obj/item/storage/belt/fannypack/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 3 + STR.max_w_class = WEIGHT_CLASS_SMALL + +/obj/item/storage/belt/fannypack/black + name = "black fannypack" + icon_state = "fannypack_black" + item_state = "fannypack_black" + +/obj/item/storage/belt/fannypack/red + name = "red fannypack" + icon_state = "fannypack_red" + item_state = "fannypack_red" + +/obj/item/storage/belt/fannypack/purple + name = "purple fannypack" + icon_state = "fannypack_purple" + item_state = "fannypack_purple" + +/obj/item/storage/belt/fannypack/blue + name = "blue fannypack" + icon_state = "fannypack_blue" + item_state = "fannypack_blue" + +/obj/item/storage/belt/fannypack/orange + name = "orange fannypack" + icon_state = "fannypack_orange" + item_state = "fannypack_orange" + +/obj/item/storage/belt/fannypack/white + name = "white fannypack" + icon_state = "fannypack_white" + item_state = "fannypack_white" + +/obj/item/storage/belt/fannypack/green + name = "green fannypack" + icon_state = "fannypack_green" + item_state = "fannypack_green" + +/obj/item/storage/belt/fannypack/pink + name = "pink fannypack" + icon_state = "fannypack_pink" + item_state = "fannypack_pink" + +/obj/item/storage/belt/fannypack/cyan + name = "cyan fannypack" + icon_state = "fannypack_cyan" + item_state = "fannypack_cyan" + +/obj/item/storage/belt/fannypack/yellow + name = "yellow fannypack" + icon_state = "fannypack_yellow" + item_state = "fannypack_yellow" + +/obj/item/storage/belt/sabre + name = "sabre sheath" + desc = "An ornate sheath designed to hold an officer's blade." + icon_state = "sheath" + item_state = "sheath" + w_class = WEIGHT_CLASS_BULKY + +/obj/item/storage/belt/sabre/ComponentInitialize() + . = ..() + AddElement(/datum/element/update_icon_updates_onmob) + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 1 + STR.rustle_sound = FALSE + STR.max_w_class = WEIGHT_CLASS_BULKY + STR.set_holdable(list( + /obj/item/melee/sabre + )) + +/obj/item/storage/belt/sabre/examine(mob/user) + . = ..() + if(length(contents)) + . += "Alt-click it to quickly draw the blade." + +/obj/item/storage/belt/sabre/AltClick(mob/user) + if(!iscarbon(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + return + if(length(contents)) + var/obj/item/I = contents[1] + user.visible_message("[user] takes [I] out of [src].", "You take [I] out of [src].") + user.put_in_hands(I) + update_icon() + else + to_chat(user, "[src] is empty!") + +/obj/item/storage/belt/sabre/update_icon_state() + icon_state = "sheath" + item_state = "sheath" + if(contents.len) + icon_state += "-sabre" + item_state += "-sabre" + +/obj/item/storage/belt/sabre/PopulateContents() + new /obj/item/melee/sabre(src) + update_icon() diff --git a/code/game/objects/items/storage/boxes.dm b/code/game/objects/items/storage/boxes.dm index f91a4f7f975d..e0ece01b02eb 100644 --- a/code/game/objects/items/storage/boxes.dm +++ b/code/game/objects/items/storage/boxes.dm @@ -1,1393 +1,1393 @@ -/* - * Everything derived from the common cardboard box. - * Basically everything except the original is a kit (starts full). - * - * Contains: - * Empty box, starter boxes (survival/engineer), - * Latex glove and sterile mask boxes, - * Syringe, beaker, dna injector boxes, - * Blanks, flashbangs, and EMP grenade boxes, - * Tracking and chemical implant boxes, - * Prescription glasses and drinking glass boxes, - * Condiment bottle and silly cup boxes, - * Donkpocket and monkeycube boxes, - * ID and security PDA cart boxes, - * Handcuff, mousetrap, and pillbottle boxes, - * Snap-pops and matchboxes, - * Replacement light boxes. - * Action Figure Boxes - * Various paper bags. - * - * For syndicate call-ins see uplink_kits.dm - */ - -/obj/item/storage/box - name = "box" - desc = "It's just an ordinary box." - icon_state = "box" - item_state = "syringe_kit" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - resistance_flags = FLAMMABLE - drop_sound = 'sound/items/handling/cardboardbox_drop.ogg' - pickup_sound = 'sound/items/handling/cardboardbox_pickup.ogg' - var/foldable = /obj/item/stack/sheet/cardboard - var/illustration = "writing" - -/obj/item/storage/box/Initialize(mapload) - . = ..() - update_icon() - -/obj/item/storage/box/suicide_act(mob/living/carbon/user) - var/obj/item/bodypart/head/myhead = user.get_bodypart(BODY_ZONE_HEAD) - if(myhead) - user.visible_message("[user] puts [user.p_their()] head into \the [src], and begins closing it! It looks like [user.p_theyre()] trying to commit suicide!") - myhead.dismember() - myhead.forceMove(src)//force your enemies to kill themselves with your head collection box! - playsound(user, "desceration-01.ogg", 50, TRUE, -1) - return BRUTELOSS - user.visible_message("[user] beating [user.p_them()]self with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return BRUTELOSS - -/obj/item/storage/box/update_overlays() - . = ..() - if(illustration) - . += illustration - -/obj/item/storage/box/attack_self(mob/user) - ..() - - if(!foldable) - return - if(contents.len) - to_chat(user, "You can't fold this box with items still inside!") - return - if(!ispath(foldable)) - return - - to_chat(user, "You fold [src] flat.") - var/obj/item/I = new foldable - qdel(src) - user.put_in_hands(I) - -/obj/item/storage/box/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/stack/packageWrap)) - return 0 - return ..() - -//Mime spell boxes - -/obj/item/storage/box/mime - name = "invisible box" - desc = "Unfortunately not large enough to trap the mime." - foldable = null - icon_state = "box" - item_state = null - alpha = 0 - -/obj/item/storage/box/mime/attack_hand(mob/user) - ..() - if(user.mind.miming) - alpha = 255 - -/obj/item/storage/box/mime/Moved(oldLoc, dir) - if (iscarbon(oldLoc)) - alpha = 0 - ..() - -//Disk boxes - -/obj/item/storage/box/disks - name = "diskette box" - illustration = "disk_kit" - -/obj/item/storage/box/disks/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/disk/data(src) - - -/obj/item/storage/box/disks_plantgene - name = "plant data disks box" - illustration = "disk_kit" - -/obj/item/storage/box/disks_plantgene/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/disk/plantgene(src) - -/obj/item/storage/box/disks_nanite - name = "nanite program disks box" - illustration = "disk_kit" - -/obj/item/storage/box/disks_nanite/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/disk/nanite_program(src) - -// Ordinary survival box -/obj/item/storage/box/survival - var/mask_type = /obj/item/clothing/mask/breath - var/internal_type = /obj/item/tank/internals/emergency_oxygen - var/medipen_type = /obj/item/reagent_containers/hypospray/medipen - -/obj/item/storage/box/survival/PopulateContents() - new mask_type(src) - if(!isnull(medipen_type)) - new medipen_type(src) - - if(!isplasmaman(loc)) - new internal_type(src) - else - new /obj/item/tank/internals/plasmaman/belt(src) - -/obj/item/storage/box/survival/radio/PopulateContents() - ..() // we want the survival stuff too. - new /obj/item/radio/off(src) - -// Mining survival box -/obj/item/storage/box/survival/mining - mask_type = /obj/item/clothing/mask/gas/explorer - -/obj/item/storage/box/survival/mining/PopulateContents() - ..() - new /obj/item/crowbar/red(src) - -// Engineer survival box -/obj/item/storage/box/survival/engineer - internal_type = /obj/item/tank/internals/emergency_oxygen/engi - -/obj/item/storage/box/survival/engineer/radio/PopulateContents() - ..() // we want the regular items too. - new /obj/item/radio/off(src) - -// Syndie survival box -/obj/item/storage/box/survival/syndie - mask_type = /obj/item/clothing/mask/gas/syndicate - internal_type = /obj/item/tank/internals/emergency_oxygen/engi - medipen_type = null - -// Security survival box -/obj/item/storage/box/survival/security - mask_type = /obj/item/clothing/mask/gas/sechailer - -/obj/item/storage/box/survival/security/radio/PopulateContents() - ..() // we want the regular stuff too - new /obj/item/radio/off(src) - -// Medical survival box -/obj/item/storage/box/survival/medical - mask_type = /obj/item/clothing/mask/breath/medical - -/obj/item/storage/box/gloves - name = "box of latex gloves" - desc = "Contains sterile latex gloves." - illustration = "latex" - -/obj/item/storage/box/gloves/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/clothing/gloves/color/latex(src) - -/obj/item/storage/box/masks - name = "box of sterile masks" - desc = "This box contains sterile medical masks." - illustration = "sterile" - -/obj/item/storage/box/masks/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/clothing/mask/surgical(src) - -/obj/item/storage/box/syringes - name = "box of syringes" - desc = "A box full of syringes." - illustration = "syringe" - -/obj/item/storage/box/syringes/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/syringe(src) - -/obj/item/storage/box/syringes/variety - name = "syringe variety box" - -/obj/item/storage/box/syringes/variety/PopulateContents() - new /obj/item/reagent_containers/syringe(src) - new /obj/item/reagent_containers/syringe/lethal(src) - new /obj/item/reagent_containers/syringe/piercing(src) - new /obj/item/reagent_containers/syringe/bluespace(src) - -/obj/item/storage/box/medipens - name = "box of medipens" - desc = "A box full of epinephrine MediPens." - illustration = "syringe" - -/obj/item/storage/box/medipens/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/hypospray/medipen(src) - -/obj/item/storage/box/medipens/utility - name = "stimpack value kit" - desc = "A box with several stimpack medipens for the economical miner." - illustration = "syringe" - -/obj/item/storage/box/medipens/utility/PopulateContents() - ..() // includes regular medipens. - for(var/i in 1 to 5) - new /obj/item/reagent_containers/hypospray/medipen/stimpack(src) - -/obj/item/storage/box/beakers - name = "box of beakers" - illustration = "beaker" - -/obj/item/storage/box/beakers/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/glass/beaker( src ) - -/obj/item/storage/box/beakers/bluespace - name = "box of bluespace beakers" - illustration = "beaker" - -/obj/item/storage/box/beakers/bluespace/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/glass/beaker/bluespace(src) - -/obj/item/storage/box/beakers/variety - name = "beaker variety box" - -/obj/item/storage/box/beakers/variety/PopulateContents() - new /obj/item/reagent_containers/glass/beaker(src) - new /obj/item/reagent_containers/glass/beaker/large(src) - new /obj/item/reagent_containers/glass/beaker/plastic(src) - new /obj/item/reagent_containers/glass/beaker/meta(src) - new /obj/item/reagent_containers/glass/beaker/noreact(src) - new /obj/item/reagent_containers/glass/beaker/bluespace(src) - -/obj/item/storage/box/hypospray - name = "hypospray mk. II kit" - icon_state = "medbriefcase" - illustration = null - -/obj/item/storage/box/hypospray/PopulateContents() - new /obj/item/hypospray/mkii(src) - new /obj/item/reagent_containers/glass/bottle/vial/small/preloaded/bicaridine(src) - new /obj/item/reagent_containers/glass/bottle/vial/small/preloaded/antitoxin(src) - new /obj/item/reagent_containers/glass/bottle/vial/small/preloaded/kelotane(src) - new /obj/item/reagent_containers/glass/bottle/vial/small/preloaded/dexalin(src) - -/obj/item/storage/box/hypospray/CMO - name = "advanced hypospray mk. II kit" - -/obj/item/storage/box/hypospray/CMO/PopulateContents() - new /obj/item/hypospray/mkii/CMO(src) - new /obj/item/reagent_containers/glass/bottle/vial/large/preloaded/bicaridine(src) - new /obj/item/reagent_containers/glass/bottle/vial/large/preloaded/antitoxin(src) - new /obj/item/reagent_containers/glass/bottle/vial/large/preloaded/kelotane(src) - new /obj/item/reagent_containers/glass/bottle/vial/large/preloaded/dexalin(src) - -/obj/item/storage/box/medigels - name = "box of medical gels" - desc = "A box full of medical gel applicators, with unscrewable caps and precision spray heads." - illustration = "medgel" - -/obj/item/storage/box/medigels/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/medigel( src ) - -/obj/item/storage/box/injectors - name = "box of DNA injectors" - desc = "This box contains injectors, it seems." - illustration = "dna" - -/obj/item/storage/box/injectors/PopulateContents() - var/static/items_inside = list( - /obj/item/dnainjector/h2m = 3, - /obj/item/dnainjector/m2h = 3) - generate_items_inside(items_inside,src) - -/obj/item/storage/box/flashbangs - name = "box of flashbangs (WARNING)" - desc = "WARNING: These devices are extremely dangerous and can cause blindness or deafness in repeated use." - icon_state = "secbox" - illustration = "flashbang" - -/obj/item/storage/box/flashbangs/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/grenade/flashbang(src) - -/obj/item/storage/box/stingbangs - name = "box of stingbangs (WARNING)" - desc = "WARNING: These devices are extremely dangerous and can cause severe injuries or death in repeated use." - icon_state = "secbox" - illustration = "flashbang" - -/obj/item/storage/box/stingbangs/PopulateContents() - for(var/i in 1 to 5) - new /obj/item/grenade/stingbang(src) - -/obj/item/storage/box/flashes - name = "box of flashbulbs" - desc = "WARNING: Flashes can cause serious eye damage, protective eyewear is required." - icon_state = "secbox" - illustration = "flash" - -/obj/item/storage/box/flashes/PopulateContents() - for(var/i in 1 to 6) - new /obj/item/assembly/flash/handheld(src) - -/obj/item/storage/box/wall_flash - name = "wall-mounted flash kit" - desc = "This box contains everything necessary to build a wall-mounted flash. WARNING: Flashes can cause serious eye damage, protective eyewear is required." - icon_state = "secbox" - illustration = "flash" - -/obj/item/storage/box/wall_flash/PopulateContents() - var/id = rand(1000, 9999) - // FIXME what if this conflicts with an existing one? - - new /obj/item/wallframe/button(src) - new /obj/item/electronics/airlock(src) - var/obj/item/assembly/control/flasher/remote = new(src) - remote.id = id - var/obj/item/wallframe/flasher/frame = new(src) - frame.id = id - new /obj/item/assembly/flash/handheld(src) - new /obj/item/screwdriver(src) - - -/obj/item/storage/box/teargas - name = "box of tear gas grenades (WARNING)" - desc = "WARNING: These devices are extremely dangerous and can cause blindness and skin irritation." - icon_state = "secbox" - illustration = "grenade" - -/obj/item/storage/box/teargas/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/grenade/chem_grenade/teargas(src) - -/obj/item/storage/box/emps - name = "box of emp grenades" - desc = "A box with 5 emp grenades." - illustration = "emp" - -/obj/item/storage/box/emps/PopulateContents() - for(var/i in 1 to 5) - new /obj/item/grenade/empgrenade(src) - -/obj/item/storage/box/trackimp - name = "boxed tracking implant kit" - desc = "Box full of scum-bag tracking utensils." - icon_state = "secbox" - illustration = "implant" - -/obj/item/storage/box/trackimp/PopulateContents() - var/static/items_inside = list( - /obj/item/implantcase/tracking = 4, - /obj/item/implanter = 1, - /obj/item/implantpad = 1, - /obj/item/locator = 1) - generate_items_inside(items_inside,src) - -/obj/item/storage/box/minertracker - name = "boxed tracking implant kit" - desc = "For finding those who have died on the accursed lavaworld." - illustration = "implant" - -/obj/item/storage/box/minertracker/PopulateContents() - var/static/items_inside = list( - /obj/item/implantcase/tracking = 3, - /obj/item/implanter = 1, - /obj/item/implantpad = 1, - /obj/item/locator = 1) - generate_items_inside(items_inside,src) - -/obj/item/storage/box/chemimp - name = "boxed chemical implant kit" - desc = "Box of stuff used to implant chemicals." - illustration = "implant" - -/obj/item/storage/box/chemimp/PopulateContents() - var/static/items_inside = list( - /obj/item/implantcase/chem = 5, - /obj/item/implanter = 1, - /obj/item/implantpad = 1) - generate_items_inside(items_inside,src) - -/obj/item/storage/box/exileimp - name = "boxed exile implant kit" - desc = "Box of exile implants. It has a picture of a clown being booted through the Gateway." - illustration = "implant" - -/obj/item/storage/box/exileimp/PopulateContents() - var/static/items_inside = list( - /obj/item/implantcase/exile = 5, - /obj/item/implanter = 1) - generate_items_inside(items_inside,src) - -/obj/item/storage/box/bodybags - name = "body bags" - desc = "The label indicates that it contains body bags." - illustration = "bodybags" - -/obj/item/storage/box/bodybags/PopulateContents() - ..() - for(var/i in 1 to 7) - new /obj/item/bodybag(src) - -/obj/item/storage/box/rxglasses - name = "box of prescription glasses" - desc = "This box contains nerd glasses." - illustration = "glasses" - -/obj/item/storage/box/rxglasses/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/clothing/glasses/regular(src) - -/obj/item/storage/box/drinkingglasses - name = "box of drinking glasses" - desc = "It has a picture of drinking glasses on it." - illustration = "drinkglass" - -/obj/item/storage/box/drinkingglasses/PopulateContents() - for(var/i in 1 to 6) - new /obj/item/reagent_containers/food/drinks/drinkingglass(src) - -/obj/item/storage/box/condimentbottles - name = "box of condiment bottles" - desc = "It has a large ketchup smear on it." - illustration = "condiment" - -/obj/item/storage/box/condimentbottles/PopulateContents() - for(var/i in 1 to 6) - new /obj/item/reagent_containers/food/condiment(src) - -/obj/item/storage/box/cups - name = "box of paper cups" - desc = "It has pictures of paper cups on the front." - illustration = "cup" - -/obj/item/storage/box/cups/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/food/drinks/sillycup( src ) - -/obj/item/storage/box/donkpockets - name = "box of donk-pockets" - desc = "Instructions: Heat in microwave. Product will cool if not eaten within seven minutes." - icon_state = "donkpocketbox" - illustration=null - var/donktype = /obj/item/reagent_containers/food/snacks/donkpocket - -/obj/item/storage/box/donkpockets/PopulateContents() - for(var/i in 1 to 6) - new donktype(src) - -/obj/item/storage/box/donkpockets/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.set_holdable(list(/obj/item/reagent_containers/food/snacks/donkpocket)) - -/obj/item/storage/box/donkpockets/donkpocketspicy - name = "box of spicy-flavoured donk-pockets" - icon_state = "donkpocketboxspicy" - donktype = /obj/item/reagent_containers/food/snacks/donkpocket/spicy - -/obj/item/storage/box/donkpockets/donkpocketteriyaki - name = "box of teriyaki-flavoured donk-pockets" - icon_state = "donkpocketboxteriyaki" - donktype = /obj/item/reagent_containers/food/snacks/donkpocket/teriyaki - -/obj/item/storage/box/donkpockets/donkpocketpizza - name = "box of pizza-flavoured donk-pockets" - icon_state = "donkpocketboxpizza" - donktype = /obj/item/reagent_containers/food/snacks/donkpocket/pizza - -/obj/item/storage/box/donkpockets/donkpocketgondola - name = "box of gondola-flavoured donk-pockets" - icon_state = "donkpocketboxgondola" - donktype = /obj/item/reagent_containers/food/snacks/donkpocket/gondola - -/obj/item/storage/box/donkpockets/donkpocketberry - name = "box of berry-flavoured donk-pockets" - icon_state = "donkpocketboxberry" - donktype = /obj/item/reagent_containers/food/snacks/donkpocket/berry - -/obj/item/storage/box/donkpockets/donkpockethonk - name = "box of banana-flavoured donk-pockets" - icon_state = "donkpocketboxbanana" - donktype = /obj/item/reagent_containers/food/snacks/donkpocket/honk - -/obj/item/storage/box/monkeycubes - name = "monkey cube box" - desc = "Drymate brand monkey cubes. Just add water!" - icon_state = "monkeycubebox" - illustration = null - var/cube_type = /obj/item/reagent_containers/food/snacks/monkeycube - -/obj/item/storage/box/monkeycubes/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 7 - STR.set_holdable(list(/obj/item/reagent_containers/food/snacks/monkeycube)) - -/obj/item/storage/box/monkeycubes/PopulateContents() - for(var/i in 1 to 5) - new cube_type(src) - -/obj/item/storage/box/monkeycubes/syndicate - desc = "Waffle Co. brand monkey cubes. Just add water and a dash of subterfuge!" - cube_type = /obj/item/reagent_containers/food/snacks/monkeycube/syndicate - -/obj/item/storage/box/gorillacubes - name = "gorilla cube box" - desc = "Waffle Co. brand gorilla cubes. Do not taunt." - icon_state = "monkeycubebox" - illustration = null - -/obj/item/storage/box/gorillacubes/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 3 - STR.set_holdable(list(/obj/item/reagent_containers/food/snacks/monkeycube)) - -/obj/item/storage/box/gorillacubes/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/reagent_containers/food/snacks/monkeycube/gorilla(src) - -/obj/item/storage/box/ids - name = "box of spare IDs" - desc = "Has so many empty IDs." - illustration = "id" - -/obj/item/storage/box/ids/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/card/id(src) - -//Some spare PDAs in a box -/obj/item/storage/box/PDAs - name = "spare PDAs" - desc = "A box of spare PDA microcomputers." - illustration = "pda" - -/obj/item/storage/box/PDAs/PopulateContents() - for(var/i in 1 to 4) - new /obj/item/pda(src) - new /obj/item/cartridge/head(src) - - var/newcart = pick( /obj/item/cartridge/engineering, - /obj/item/cartridge/security, - /obj/item/cartridge/medical, - /obj/item/cartridge/signal/toxins, - /obj/item/cartridge/quartermaster) - new newcart(src) - -/obj/item/storage/box/silver_ids - name = "box of spare silver IDs" - desc = "Shiny IDs for important people." - illustration = "id" - -/obj/item/storage/box/silver_ids/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/card/id/silver(src) - -/obj/item/storage/box/prisoner - name = "box of prisoner IDs" - desc = "Take away their last shred of dignity, their name." - icon_state = "secbox" - illustration = "id" - -/obj/item/storage/box/prisoner/PopulateContents() - ..() - new /obj/item/card/id/prisoner/one(src) - new /obj/item/card/id/prisoner/two(src) - new /obj/item/card/id/prisoner/three(src) - new /obj/item/card/id/prisoner/four(src) - new /obj/item/card/id/prisoner/five(src) - new /obj/item/card/id/prisoner/six(src) - new /obj/item/card/id/prisoner/seven(src) - -/obj/item/storage/box/seccarts - name = "box of PDA security cartridges" - desc = "A box full of PDA cartridges used by Security." - icon_state = "secbox" - illustration = "pda" - -/obj/item/storage/box/seccarts/PopulateContents() - new /obj/item/cartridge/detective(src) - for(var/i in 1 to 6) - new /obj/item/cartridge/security(src) - -/obj/item/storage/box/firingpins - name = "box of standard firing pins" - desc = "A box full of standard firing pins, to allow newly-developed firearms to operate." - icon_state = "secbox" - illustration = "firingpin" - -/obj/item/storage/box/firingpins/PopulateContents() - for(var/i in 1 to 5) - new /obj/item/firing_pin(src) - -/obj/item/storage/box/firingpins/paywall - name = "box of paywall firing pins" - desc = "A box full of paywall firing pins, to allow newly-developed firearms to operate behind a custom-set paywall." - illustration = "firingpin" - -/obj/item/storage/box/firingpins/paywall/PopulateContents() - for(var/i in 1 to 5) - new /obj/item/firing_pin/paywall(src) - -/obj/item/storage/box/lasertagpins - name = "box of laser tag firing pins" - desc = "A box full of laser tag firing pins, to allow newly-developed firearms to require wearing brightly coloured plastic armor before being able to be used." - illustration = "firingpin" - -/obj/item/storage/box/lasertagpins/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/firing_pin/tag/red(src) - new /obj/item/firing_pin/tag/blue(src) - -/obj/item/storage/box/handcuffs - name = "box of spare handcuffs" - desc = "A box full of handcuffs." - icon_state = "secbox" - illustration = "handcuff" - -/obj/item/storage/box/handcuffs/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/restraints/handcuffs(src) - -/obj/item/storage/box/zipties - name = "box of spare zipties" - desc = "A box full of zipties." - icon_state = "secbox" - illustration = "handcuff" - -/obj/item/storage/box/zipties/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/restraints/handcuffs/cable/zipties(src) - -/obj/item/storage/box/alienhandcuffs - name = "box of spare handcuffs" - desc = "A box full of handcuffs." - icon_state = "alienbox" - illustration = "handcuff" - -/obj/item/storage/box/alienhandcuffs/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/restraints/handcuffs/alien(src) - -/obj/item/storage/box/fakesyndiesuit - name = "boxed space suit and helmet" - desc = "A sleek, sturdy box used to hold replica spacesuits." - icon_state = "syndiebox" - illustration = "syndiesuit" - -/obj/item/storage/box/fakesyndiesuit/PopulateContents() - new /obj/item/clothing/head/syndicatefake(src) - new /obj/item/clothing/suit/syndicatefake(src) - -/obj/item/storage/box/mousetraps - name = "box of Pest-B-Gon mousetraps" - desc = "Keep out of reach of children." - illustration = "mousetrap" - -/obj/item/storage/box/mousetraps/PopulateContents() - for(var/i in 1 to 6) - new /obj/item/assembly/mousetrap(src) - -/obj/item/storage/box/pillbottles - name = "box of pill bottles" - desc = "It has pictures of pill bottles on its front." - illustration = "pillbox" - -/obj/item/storage/box/pillbottles/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/storage/pill_bottle(src) - -/obj/item/storage/box/snappops - name = "snap pop box" - desc = "Eight wrappers of fun! Ages 8 and up. Not suitable for children." - icon = 'icons/obj/toy.dmi' - icon_state = "spbox" - -/obj/item/storage/box/snappops/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.set_holdable(list(/obj/item/toy/snappop)) - STR.max_items = 8 - -/obj/item/storage/box/snappops/PopulateContents() - SEND_SIGNAL(src, COMSIG_TRY_STORAGE_FILL_TYPE, /obj/item/toy/snappop) - -/obj/item/storage/box/matches - name = "matchbox" - desc = "A small box of Almost But Not Quite Plasma Premium Matches." - icon = 'icons/obj/cigarettes.dmi' - icon_state = "matchbox" - item_state = "zippo" - w_class = WEIGHT_CLASS_TINY - slot_flags = ITEM_SLOT_BELT - drop_sound = 'sound/items/handling/matchbox_drop.ogg' - pickup_sound = 'sound/items/handling/matchbox_pickup.ogg' - custom_price = 20 - -/obj/item/storage/box/matches/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 10 - STR.set_holdable(list(/obj/item/match)) - -/obj/item/storage/box/matches/PopulateContents() - SEND_SIGNAL(src, COMSIG_TRY_STORAGE_FILL_TYPE, /obj/item/match) - -/obj/item/storage/box/matches/attackby(obj/item/match/W as obj, mob/user as mob, params) - if(istype(W, /obj/item/match)) - W.matchignite() - -/obj/item/storage/box/lights - name = "box of replacement bulbs" - icon = 'icons/obj/storage.dmi' - illustration = "light" - desc = "This box is shaped on the inside so that only light tubes and bulbs fit." - item_state = "syringe_kit" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - foldable = /obj/item/stack/sheet/cardboard //BubbleWrap - -/obj/item/storage/box/lights/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 21 - STR.set_holdable(list(/obj/item/light/tube, /obj/item/light/bulb)) - STR.max_combined_w_class = 21 - STR.click_gather = FALSE //temp workaround to re-enable filling the light replacer with the box - -/obj/item/storage/box/lights/bulbs/PopulateContents() - for(var/i in 1 to 21) - new /obj/item/light/bulb(src) - -/obj/item/storage/box/lights/tubes - name = "box of replacement tubes" - illustration = "lighttube" - -/obj/item/storage/box/lights/tubes/PopulateContents() - for(var/i in 1 to 21) - new /obj/item/light/tube(src) - -/obj/item/storage/box/lights/mixed - name = "box of replacement lights" - illustration = "lightmixed" - -/obj/item/storage/box/lights/mixed/PopulateContents() - for(var/i in 1 to 14) - new /obj/item/light/tube(src) - for(var/i in 1 to 7) - new /obj/item/light/bulb(src) - - -/obj/item/storage/box/deputy - name = "box of deputy armbands" - desc = "To be issued to those authorized to act as deputy of security." - icon_state = "secbox" - illustration = "depband" - -/obj/item/storage/box/deputy/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/clothing/accessory/armband/deputy(src) - -/obj/item/storage/box/metalfoam - name = "box of metal foam grenades" - desc = "To be used to rapidly seal hull breaches." - illustration = "grenade" - -/obj/item/storage/box/metalfoam/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/grenade/chem_grenade/metalfoam(src) - -/obj/item/storage/box/smart_metal_foam - name = "box of smart metal foam grenades" - desc = "Used to rapidly seal hull breaches. This variety conforms to the walls of its area." - illustration = "grenade" - -/obj/item/storage/box/smart_metal_foam/PopulateContents() - for(var/i in 1 to 7) - new/obj/item/grenade/chem_grenade/smart_metal_foam(src) - -/obj/item/storage/box/hug - name = "box of hugs" - desc = "A special box for sensitive people." - icon_state = "hugbox" - illustration = "heart" - foldable = null - -/obj/item/storage/box/hug/suicide_act(mob/user) - user.visible_message("[user] clamps the box of hugs on [user.p_their()] jugular! Guess it wasn't such a hugbox after all..") - return (BRUTELOSS) - -/obj/item/storage/box/hug/attack_self(mob/user) - ..() - user.changeNext_move(CLICK_CD_MELEE) - playsound(loc, "rustle", 50, TRUE, -5) - user.visible_message("[user] hugs \the [src].","You hug \the [src].") - -/////clown box & honkbot assembly -/obj/item/storage/box/clown - name = "clown box" - desc = "A colorful cardboard box for the clown" - illustration = "clown" - -/obj/item/storage/box/clown/attackby(obj/item/I, mob/user, params) - if((istype(I, /obj/item/bodypart/l_arm/robot)) || (istype(I, /obj/item/bodypart/r_arm/robot))) - if(contents.len) //prevent accidently deleting contents - to_chat(user, "You need to empty [src] out first!") - return - if(!user.temporarilyRemoveItemFromInventory(I)) - return - qdel(I) - to_chat(user, "You add some wheels to the [src]! You've got a honkbot assembly now! Honk!") - var/obj/item/bot_assembly/honkbot/A = new - qdel(src) - user.put_in_hands(A) - else - return ..() - -////// -/obj/item/storage/box/hug/medical/PopulateContents() - new /obj/item/stack/medical/bruise_pack(src) - new /obj/item/stack/medical/ointment(src) - new /obj/item/reagent_containers/hypospray/medipen(src) - -// Clown survival box -/obj/item/storage/box/hug/survival/PopulateContents() - new /obj/item/clothing/mask/breath(src) - new /obj/item/reagent_containers/hypospray/medipen(src) - - if(!isplasmaman(loc)) - new /obj/item/tank/internals/emergency_oxygen(src) - else - new /obj/item/tank/internals/plasmaman/belt(src) - -/obj/item/storage/box/rubbershot - name = "box of rubber shots" - desc = "A box full of rubber shots, designed for riot shotguns." - icon_state = "rubbershot_box" - illustration = null - -/obj/item/storage/box/rubbershot/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/ammo_casing/shotgun/rubbershot(src) - -/obj/item/storage/box/lethalshot - name = "box of lethal shotgun shots" - desc = "A box full of lethal shots, designed for riot shotguns." - icon_state = "lethalshot_box" - illustration = null - -/obj/item/storage/box/lethalshot/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/ammo_casing/shotgun/buckshot(src) - -/obj/item/storage/box/beanbag - name = "box of beanbags" - desc = "A box full of beanbag shells." - icon_state = "rubbershot_box" - illustration = null - -/obj/item/storage/box/beanbag/PopulateContents() - for(var/i in 1 to 6) - new /obj/item/ammo_casing/shotgun/beanbag(src) - -/obj/item/storage/box/actionfigure - name = "box of action figures" - desc = "The latest set of collectable action figures." - icon_state = "box" - -/obj/item/storage/box/actionfigure/PopulateContents() - for(var/i in 1 to 4) - var/randomFigure = pick(subtypesof(/obj/item/toy/figure)) - new randomFigure(src) - -/obj/item/storage/box/papersack - name = "paper sack" - desc = "A sack neatly crafted out of paper." - icon_state = "paperbag_None" - item_state = "paperbag_None" - illustration = null - resistance_flags = FLAMMABLE - foldable = null - /// A list of all available papersack reskins - var/list/papersack_designs = list() - -/obj/item/storage/box/papersack/Initialize(mapload) - . = ..() - papersack_designs = sortList(list( - "None" = image(icon = src.icon, icon_state = "paperbag_None"), - "NanotrasenStandard" = image(icon = src.icon, icon_state = "paperbag_NanotrasenStandard"), - "SyndiSnacks" = image(icon = src.icon, icon_state = "paperbag_SyndiSnacks"), - "Heart" = image(icon = src.icon, icon_state = "paperbag_Heart"), - "SmileyFace" = image(icon = src.icon, icon_state = "paperbag_SmileyFace") - )) - -/obj/item/storage/box/papersack/update_icon_state() - if(contents.len == 0) - icon_state = "[item_state]" - else - icon_state = "[item_state]_closed" - -/obj/item/storage/box/papersack/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/pen)) - var/choice = show_radial_menu(user, src , papersack_designs, custom_check = CALLBACK(src, .proc/check_menu, user, W), radius = 36, require_near = TRUE) - if(!choice) - return FALSE - if(icon_state == "paperbag_[choice]") - return FALSE - switch(choice) - if("None") - desc = "A sack neatly crafted out of paper." - if("NanotrasenStandard") - desc = "A standard Nanotrasen paper lunch sack for loyal employees on the go." - if("SyndiSnacks") - desc = "The design on this paper sack is a remnant of the notorious 'SyndieSnacks' program." - if("Heart") - desc = "A paper sack with a heart etched onto the side." - if("SmileyFace") - desc = "A paper sack with a crude smile etched onto the side." - else - return FALSE - to_chat(user, "You make some modifications to [src] using your pen.") - icon_state = "paperbag_[choice]" - item_state = "paperbag_[choice]" - return FALSE - else if(W.get_sharpness()) - if(!contents.len) - if(item_state == "paperbag_None") - user.show_message("You cut eyeholes into [src].", MSG_VISUAL) - new /obj/item/clothing/head/papersack(user.loc) - qdel(src) - return FALSE - else if(item_state == "paperbag_SmileyFace") - user.show_message("You cut eyeholes into [src] and modify the design.", MSG_VISUAL) - new /obj/item/clothing/head/papersack/smiley(user.loc) - qdel(src) - return FALSE - return ..() - -/** - * check_menu: Checks if we are allowed to interact with a radial menu - * - * Arguments: - * * user The mob interacting with a menu - * * P The pen used to interact with a menu - */ -/obj/item/storage/box/papersack/proc/check_menu(mob/user, obj/item/pen/P) - if(!istype(user)) - return FALSE - if(user.incapacitated()) - return FALSE - if(contents.len) - to_chat(user, "You can't modify [src] with items still inside!") - return FALSE - if(!P || !user.is_holding(P)) - to_chat(user, "You need a pen to modify [src]!") - return FALSE - return TRUE - -/obj/item/storage/box/papersack/meat - desc = "It's slightly moist and smells like a slaughterhouse." - -/obj/item/storage/box/papersack/meat/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/food/snacks/meat/slab(src) - -/obj/item/storage/box/ingredients //This box is for the randomely chosen version the chef spawns with, it shouldn't actually exist. - name = "ingredients box" - illustration = "fruit" - var/theme_name - -/obj/item/storage/box/ingredients/Initialize() - . = ..() - if(theme_name) - name = "[name] ([theme_name])" - desc = "A box containing supplementary ingredients for the aspiring chef. The box's theme is '[theme_name]'." - item_state = "syringe_kit" - -/obj/item/storage/box/ingredients/wildcard - theme_name = "wildcard" - -/obj/item/storage/box/ingredients/wildcard/PopulateContents() - for(var/i in 1 to 7) - var/randomFood = pick(/obj/item/reagent_containers/food/snacks/grown/chili, - /obj/item/reagent_containers/food/snacks/grown/tomato, - /obj/item/reagent_containers/food/snacks/grown/carrot, - /obj/item/reagent_containers/food/snacks/grown/potato, - /obj/item/reagent_containers/food/snacks/grown/potato/sweet, - /obj/item/reagent_containers/food/snacks/grown/apple, - /obj/item/reagent_containers/food/snacks/chocolatebar, - /obj/item/reagent_containers/food/snacks/grown/cherries, - /obj/item/reagent_containers/food/snacks/grown/banana, - /obj/item/reagent_containers/food/snacks/grown/cabbage, - /obj/item/reagent_containers/food/snacks/grown/soybeans, - /obj/item/reagent_containers/food/snacks/grown/corn, - /obj/item/reagent_containers/food/snacks/grown/mushroom/plumphelmet, - /obj/item/reagent_containers/food/snacks/grown/mushroom/chanterelle) - new randomFood(src) - -/obj/item/storage/box/ingredients/fiesta - theme_name = "fiesta" - -/obj/item/storage/box/ingredients/fiesta/PopulateContents() - new /obj/item/reagent_containers/food/snacks/tortilla(src) - for(var/i in 1 to 2) - new /obj/item/reagent_containers/food/snacks/grown/corn(src) - new /obj/item/reagent_containers/food/snacks/grown/soybeans(src) - new /obj/item/reagent_containers/food/snacks/grown/chili(src) - -/obj/item/storage/box/ingredients/italian - theme_name = "italian" - -/obj/item/storage/box/ingredients/italian/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/reagent_containers/food/snacks/grown/tomato(src) - new /obj/item/reagent_containers/food/snacks/meatball(src) - new /obj/item/reagent_containers/food/drinks/bottle/wine(src) - -/obj/item/storage/box/ingredients/vegetarian - theme_name = "vegetarian" - -/obj/item/storage/box/ingredients/vegetarian/PopulateContents() - for(var/i in 1 to 2) - new /obj/item/reagent_containers/food/snacks/grown/carrot(src) - new /obj/item/reagent_containers/food/snacks/grown/eggplant(src) - new /obj/item/reagent_containers/food/snacks/grown/potato(src) - new /obj/item/reagent_containers/food/snacks/grown/apple(src) - new /obj/item/reagent_containers/food/snacks/grown/corn(src) - new /obj/item/reagent_containers/food/snacks/grown/tomato(src) - -/obj/item/storage/box/ingredients/american - theme_name = "american" - -/obj/item/storage/box/ingredients/american/PopulateContents() - for(var/i in 1 to 2) - new /obj/item/reagent_containers/food/snacks/grown/potato(src) - new /obj/item/reagent_containers/food/snacks/grown/tomato(src) - new /obj/item/reagent_containers/food/snacks/grown/corn(src) - new /obj/item/reagent_containers/food/snacks/meatball(src) - -/obj/item/storage/box/ingredients/fruity - theme_name = "fruity" - -/obj/item/storage/box/ingredients/fruity/PopulateContents() - for(var/i in 1 to 2) - new /obj/item/reagent_containers/food/snacks/grown/apple(src) - new /obj/item/reagent_containers/food/snacks/grown/citrus/orange(src) - new /obj/item/reagent_containers/food/snacks/grown/citrus/lemon(src) - new /obj/item/reagent_containers/food/snacks/grown/citrus/lime(src) - new /obj/item/reagent_containers/food/snacks/grown/watermelon(src) - -/obj/item/storage/box/ingredients/sweets - theme_name = "sweets" - -/obj/item/storage/box/ingredients/sweets/PopulateContents() - for(var/i in 1 to 2) - new /obj/item/reagent_containers/food/snacks/grown/cherries(src) - new /obj/item/reagent_containers/food/snacks/grown/banana(src) - new /obj/item/reagent_containers/food/snacks/chocolatebar(src) - new /obj/item/reagent_containers/food/snacks/grown/cocoapod(src) - new /obj/item/reagent_containers/food/snacks/grown/apple(src) - -/obj/item/storage/box/ingredients/delights - theme_name = "delights" - -/obj/item/storage/box/ingredients/delights/PopulateContents() - for(var/i in 1 to 2) - new /obj/item/reagent_containers/food/snacks/grown/potato/sweet(src) - new /obj/item/reagent_containers/food/snacks/grown/bluecherries(src) - new /obj/item/reagent_containers/food/snacks/grown/vanillapod(src) - new /obj/item/reagent_containers/food/snacks/grown/cocoapod(src) - new /obj/item/reagent_containers/food/snacks/grown/berries(src) - -/obj/item/storage/box/ingredients/grains - theme_name = "grains" - -/obj/item/storage/box/ingredients/grains/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/reagent_containers/food/snacks/grown/oat(src) - new /obj/item/reagent_containers/food/snacks/grown/wheat(src) - new /obj/item/reagent_containers/food/snacks/grown/cocoapod(src) - new /obj/item/reagent_containers/honeycomb(src) - new /obj/item/seeds/poppy(src) - -/obj/item/storage/box/ingredients/carnivore - theme_name = "carnivore" - -/obj/item/storage/box/ingredients/carnivore/PopulateContents() - new /obj/item/reagent_containers/food/snacks/meat/slab/bear(src) - new /obj/item/reagent_containers/food/snacks/meat/slab/spider(src) - new /obj/item/reagent_containers/food/snacks/spidereggs(src) - new /obj/item/reagent_containers/food/snacks/carpmeat(src) - new /obj/item/reagent_containers/food/snacks/meat/slab/xeno(src) - new /obj/item/reagent_containers/food/snacks/meat/slab/corgi(src) - new /obj/item/reagent_containers/food/snacks/meatball(src) - -/obj/item/storage/box/ingredients/exotic - theme_name = "exotic" - -/obj/item/storage/box/ingredients/exotic/PopulateContents() - for(var/i in 1 to 2) - new /obj/item/reagent_containers/food/snacks/carpmeat(src) - new /obj/item/reagent_containers/food/snacks/grown/soybeans(src) - new /obj/item/reagent_containers/food/snacks/grown/cabbage(src) - new /obj/item/reagent_containers/food/snacks/grown/chili(src) - -/obj/item/storage/box/emptysandbags - name = "box of empty sandbags" - illustration = "sandbag" - -/obj/item/storage/box/emptysandbags/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/emptysandbag(src) - -/obj/item/storage/box/rndboards - name = "\proper the liberator's legacy" - desc = "A box containing a gift for worthy golems." - illustration = "scicircuit" - -/obj/item/storage/box/rndboards/PopulateContents() - new /obj/item/circuitboard/machine/protolathe(src) - new /obj/item/circuitboard/machine/destructive_analyzer(src) - new /obj/item/circuitboard/machine/circuit_imprinter(src) - new /obj/item/circuitboard/computer/rdconsole(src) - -/obj/item/storage/box/silver_sulf - name = "box of silver sulfadiazine patches" - desc = "Contains patches used to treat burns." - illustration = "firepatch" - -/obj/item/storage/box/silver_sulf/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/pill/patch/silver_sulf(src) - -/obj/item/storage/box/fountainpens - name = "box of fountain pens" - illustration = "fpen" - -/obj/item/storage/box/fountainpens/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/pen/fountain(src) - -/obj/item/storage/box/holy_grenades - name = "box of holy hand grenades" - desc = "Contains several grenades used to rapidly purge heresy." - illustration = "grenade" - -/obj/item/storage/box/holy_grenades/PopulateContents() - for(var/i in 1 to 7) - new/obj/item/grenade/chem_grenade/holy(src) - -/obj/item/storage/box/stockparts/basic //for ruins where it's a bad idea to give access to an autolathe/protolathe, but still want to make stock parts accessible - name = "box of stock parts" - desc = "Contains a variety of basic stock parts." - -/obj/item/storage/box/stockparts/basic/PopulateContents() - var/static/items_inside = list( - /obj/item/stock_parts/capacitor = 3, - /obj/item/stock_parts/scanning_module = 3, - /obj/item/stock_parts/manipulator = 3, - /obj/item/stock_parts/micro_laser = 3, - /obj/item/stock_parts/matter_bin = 3) - generate_items_inside(items_inside,src) - -/obj/item/storage/box/stockparts/deluxe - name = "box of deluxe stock parts" - desc = "Contains a variety of deluxe stock parts." - icon_state = "syndiebox" - -/obj/item/storage/box/stockparts/deluxe/PopulateContents() - var/static/items_inside = list( - /obj/item/stock_parts/capacitor/quadratic = 3, - /obj/item/stock_parts/scanning_module/triphasic = 3, - /obj/item/stock_parts/manipulator/femto = 3, - /obj/item/stock_parts/micro_laser/quadultra = 3, - /obj/item/stock_parts/matter_bin/bluespace = 3) - generate_items_inside(items_inside,src) - -/obj/item/storage/box/dishdrive - name = "DIY Dish Drive Kit" - desc = "Contains everything you need to build your own Dish Drive!" - custom_premium_price = 1000 - -/obj/item/storage/box/dishdrive/PopulateContents() - var/static/items_inside = list( - /obj/item/stack/sheet/metal/five = 1, - /obj/item/stack/cable_coil/random/five = 1, //Random from Smartwire Revert - Waspie Bois - /obj/item/circuitboard/machine/dish_drive = 1, - /obj/item/stack/sheet/glass = 1, - /obj/item/stock_parts/manipulator = 1, - /obj/item/stock_parts/matter_bin = 2, - /obj/item/screwdriver = 1) - generate_items_inside(items_inside,src) - -/obj/item/storage/box/material - name = "box of materials" - illustration = "implant" - -/obj/item/storage/box/material/PopulateContents() //less uranium because radioactive - var/static/items_inside = list( - /obj/item/stack/sheet/metal/fifty=1,\ - /obj/item/stack/sheet/glass/fifty=1,\ - /obj/item/stack/sheet/rglass=50,\ - /obj/item/stack/sheet/plasmaglass=50,\ - /obj/item/stack/sheet/titaniumglass=50,\ - /obj/item/stack/sheet/plastitaniumglass=50,\ - /obj/item/stack/sheet/plasteel=50,\ - /obj/item/stack/sheet/mineral/plastitanium=50,\ - /obj/item/stack/sheet/mineral/titanium=50,\ - /obj/item/stack/sheet/mineral/gold=50,\ - /obj/item/stack/sheet/mineral/silver=50,\ - /obj/item/stack/sheet/mineral/plasma=50,\ - /obj/item/stack/sheet/mineral/uranium=20,\ - /obj/item/stack/sheet/mineral/diamond=50,\ - /obj/item/stack/sheet/bluespace_crystal=50,\ - /obj/item/stack/sheet/mineral/bananium=50,\ - /obj/item/stack/sheet/mineral/wood=50,\ - /obj/item/stack/sheet/plastic/fifty=1,\ - /obj/item/stack/sheet/runed_metal/fifty=1 - ) - generate_items_inside(items_inside,src) - -/obj/item/storage/box/debugtools - name = "box of debug tools" - icon_state = "syndiebox" - -/obj/item/storage/box/debugtools/PopulateContents() - var/static/items_inside = list( - /obj/item/flashlight/emp/debug=1,\ - /obj/item/pda=1,\ - /obj/item/modular_computer/tablet/preset/advanced=1,\ - /obj/item/geiger_counter=1,\ - /obj/item/construction/rcd/combat/admin=1,\ - /obj/item/pipe_dispenser=1,\ - /obj/item/card/emag=1,\ - /obj/item/stack/spacecash/c1000=50,\ - /obj/item/healthanalyzer/advanced=1,\ - /obj/item/disk/tech_disk/debug=1,\ - /obj/item/uplink/debug=1,\ - /obj/item/uplink/nuclear/debug=1,\ - /obj/item/storage/box/beakers/bluespace=1,\ - /obj/item/storage/box/beakers/variety=1,\ - /obj/item/storage/box/material=1 - ) - generate_items_inside(items_inside,src) - -/obj/item/storage/box/plastic - name = "plastic box" - desc = "It's a solid, plastic shell box." - icon_state = "plasticbox" - foldable = null - illustration = "writing" - custom_materials = list(/datum/material/plastic = 1000) //You lose most if recycled. - - -/obj/item/storage/box/fireworks - name = "box of fireworks" - desc = "Contains an assortment of fireworks." - illustration = "sparkler" - -/obj/item/storage/box/fireworks/PopulateContents() - for(var/i in 1 to 3) - new/obj/item/sparkler(src) - new/obj/item/grenade/firecracker(src) - new /obj/item/toy/snappop(src) - -/obj/item/storage/box/fireworks/dangerous - -/obj/item/storage/box/fireworks/dangerous/PopulateContents() - for(var/i in 1 to 3) - new/obj/item/sparkler(src) - new/obj/item/grenade/firecracker(src) - if(prob(20)) - new /obj/item/grenade/frag(src) - else - new /obj/item/toy/snappop(src) - -/obj/item/storage/box/firecrackers - name = "box of firecrackers" - desc = "A box filled with illegal firecracker. You wonder who still makes these." - icon_state = "syndiebox" - illustration = "firecracker" - -/obj/item/storage/box/firecrackers/PopulateContents() - for(var/i in 1 to 7) - new/obj/item/grenade/firecracker(src) - -/obj/item/storage/box/sparklers - name = "box of sparklers" - desc = "A box of NT brand sparklers, burns hot even in the cold of space-winter." - illustration = "sparkler" - -/obj/item/storage/box/sparklers/PopulateContents() - for(var/i in 1 to 7) - new/obj/item/sparkler(src) - -/obj/item/storage/box/gum - name = "bubblegum packet" - desc = "The packaging is entirely in japanese, apparently. You can't make out a single word of it." - icon_state = "bubblegum_generic" - w_class = WEIGHT_CLASS_TINY - illustration = null - foldable = null - custom_price = 120 - -/obj/item/storage/box/gum/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.set_holdable(list(/obj/item/reagent_containers/food/snacks/chewable/bubblegum)) - STR.max_items = 4 - -/obj/item/storage/box/gum/PopulateContents() - for(var/i in 1 to 4) - new/obj/item/reagent_containers/food/snacks/chewable/bubblegum(src) - -/obj/item/storage/box/gum/nicotine - name = "nicotine gum packet" - desc = "Designed to help with nicotine addiction and oral fixation all at once without destroying your lungs in the process. Mint flavored!" - icon_state = "bubblegum_nicotine" - custom_premium_price = 275 - -/obj/item/storage/box/gum/nicotine/PopulateContents() - for(var/i in 1 to 4) - new/obj/item/reagent_containers/food/snacks/chewable/bubblegum/nicotine(src) - -/obj/item/storage/box/gum/happiness - name = "HP+ gum packet" - desc = "A seemingly homemade packaging with an odd smell. It has a weird drawing of a smiling face sticking out its tongue." - icon_state = "bubblegum_happiness" - custom_price = 300 - custom_premium_price = 300 - -/obj/item/storage/box/gum/happiness/Initialize() - . = ..() - if (prob(25)) - desc += "You can faintly make out the word 'Hemopagopril' was once scribbled on it." - -/obj/item/storage/box/gum/happiness/PopulateContents() - for(var/i in 1 to 4) - new/obj/item/reagent_containers/food/snacks/chewable/bubblegum/happiness(src) - -/obj/item/storage/box/gum/bubblegum - name = "bubblegum gum packet" - desc = "The packaging is entirely in Demonic, apparently. You feel like even opening this would be a sin." - icon_state = "bubblegum_bubblegum" - -/obj/item/storage/box/gum/bubblegum/PopulateContents() - for(var/i in 1 to 4) - new/obj/item/reagent_containers/food/snacks/chewable/bubblegum/bubblegum(src) - -/obj/item/storage/box/shipping - name = "box of shipping supplies" - desc = "Contains several scanners and labelers for shipping things. Wrapping Paper not included." - illustration = "shipping" - -/obj/item/storage/box/shipping/PopulateContents() - var/static/items_inside = list( - /obj/item/destTagger=1,\ - /obj/item/sales_tagger=1,\ - /obj/item/export_scanner=1,\ - /obj/item/stack/packageWrap/small=2,\ - /obj/item/stack/wrapping_paper/small=1 - ) - generate_items_inside(items_inside,src) +/* + * Everything derived from the common cardboard box. + * Basically everything except the original is a kit (starts full). + * + * Contains: + * Empty box, starter boxes (survival/engineer), + * Latex glove and sterile mask boxes, + * Syringe, beaker, dna injector boxes, + * Blanks, flashbangs, and EMP grenade boxes, + * Tracking and chemical implant boxes, + * Prescription glasses and drinking glass boxes, + * Condiment bottle and silly cup boxes, + * Donkpocket and monkeycube boxes, + * ID and security PDA cart boxes, + * Handcuff, mousetrap, and pillbottle boxes, + * Snap-pops and matchboxes, + * Replacement light boxes. + * Action Figure Boxes + * Various paper bags. + * + * For syndicate call-ins see uplink_kits.dm + */ + +/obj/item/storage/box + name = "box" + desc = "It's just an ordinary box." + icon_state = "box" + item_state = "syringe_kit" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + resistance_flags = FLAMMABLE + drop_sound = 'sound/items/handling/cardboardbox_drop.ogg' + pickup_sound = 'sound/items/handling/cardboardbox_pickup.ogg' + var/foldable = /obj/item/stack/sheet/cardboard + var/illustration = "writing" + +/obj/item/storage/box/Initialize(mapload) + . = ..() + update_icon() + +/obj/item/storage/box/suicide_act(mob/living/carbon/user) + var/obj/item/bodypart/head/myhead = user.get_bodypart(BODY_ZONE_HEAD) + if(myhead) + user.visible_message("[user] puts [user.p_their()] head into \the [src], and begins closing it! It looks like [user.p_theyre()] trying to commit suicide!") + myhead.dismember() + myhead.forceMove(src)//force your enemies to kill themselves with your head collection box! + playsound(user, "desceration-01.ogg", 50, TRUE, -1) + return BRUTELOSS + user.visible_message("[user] beating [user.p_them()]self with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return BRUTELOSS + +/obj/item/storage/box/update_overlays() + . = ..() + if(illustration) + . += illustration + +/obj/item/storage/box/attack_self(mob/user) + ..() + + if(!foldable) + return + if(contents.len) + to_chat(user, "You can't fold this box with items still inside!") + return + if(!ispath(foldable)) + return + + to_chat(user, "You fold [src] flat.") + var/obj/item/I = new foldable + qdel(src) + user.put_in_hands(I) + +/obj/item/storage/box/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/stack/packageWrap)) + return 0 + return ..() + +//Mime spell boxes + +/obj/item/storage/box/mime + name = "invisible box" + desc = "Unfortunately not large enough to trap the mime." + foldable = null + icon_state = "box" + item_state = null + alpha = 0 + +/obj/item/storage/box/mime/attack_hand(mob/user) + ..() + if(user.mind.miming) + alpha = 255 + +/obj/item/storage/box/mime/Moved(oldLoc, dir) + if (iscarbon(oldLoc)) + alpha = 0 + ..() + +//Disk boxes + +/obj/item/storage/box/disks + name = "diskette box" + illustration = "disk_kit" + +/obj/item/storage/box/disks/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/disk/data(src) + + +/obj/item/storage/box/disks_plantgene + name = "plant data disks box" + illustration = "disk_kit" + +/obj/item/storage/box/disks_plantgene/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/disk/plantgene(src) + +/obj/item/storage/box/disks_nanite + name = "nanite program disks box" + illustration = "disk_kit" + +/obj/item/storage/box/disks_nanite/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/disk/nanite_program(src) + +// Ordinary survival box +/obj/item/storage/box/survival + var/mask_type = /obj/item/clothing/mask/breath + var/internal_type = /obj/item/tank/internals/emergency_oxygen + var/medipen_type = /obj/item/reagent_containers/hypospray/medipen + +/obj/item/storage/box/survival/PopulateContents() + new mask_type(src) + if(!isnull(medipen_type)) + new medipen_type(src) + + if(!isplasmaman(loc)) + new internal_type(src) + else + new /obj/item/tank/internals/plasmaman/belt(src) + +/obj/item/storage/box/survival/radio/PopulateContents() + ..() // we want the survival stuff too. + new /obj/item/radio/off(src) + +// Mining survival box +/obj/item/storage/box/survival/mining + mask_type = /obj/item/clothing/mask/gas/explorer + +/obj/item/storage/box/survival/mining/PopulateContents() + ..() + new /obj/item/crowbar/red(src) + +// Engineer survival box +/obj/item/storage/box/survival/engineer + internal_type = /obj/item/tank/internals/emergency_oxygen/engi + +/obj/item/storage/box/survival/engineer/radio/PopulateContents() + ..() // we want the regular items too. + new /obj/item/radio/off(src) + +// Syndie survival box +/obj/item/storage/box/survival/syndie + mask_type = /obj/item/clothing/mask/gas/syndicate + internal_type = /obj/item/tank/internals/emergency_oxygen/engi + medipen_type = null + +// Security survival box +/obj/item/storage/box/survival/security + mask_type = /obj/item/clothing/mask/gas/sechailer + +/obj/item/storage/box/survival/security/radio/PopulateContents() + ..() // we want the regular stuff too + new /obj/item/radio/off(src) + +// Medical survival box +/obj/item/storage/box/survival/medical + mask_type = /obj/item/clothing/mask/breath/medical + +/obj/item/storage/box/gloves + name = "box of latex gloves" + desc = "Contains sterile latex gloves." + illustration = "latex" + +/obj/item/storage/box/gloves/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/clothing/gloves/color/latex(src) + +/obj/item/storage/box/masks + name = "box of sterile masks" + desc = "This box contains sterile medical masks." + illustration = "sterile" + +/obj/item/storage/box/masks/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/clothing/mask/surgical(src) + +/obj/item/storage/box/syringes + name = "box of syringes" + desc = "A box full of syringes." + illustration = "syringe" + +/obj/item/storage/box/syringes/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/syringe(src) + +/obj/item/storage/box/syringes/variety + name = "syringe variety box" + +/obj/item/storage/box/syringes/variety/PopulateContents() + new /obj/item/reagent_containers/syringe(src) + new /obj/item/reagent_containers/syringe/lethal(src) + new /obj/item/reagent_containers/syringe/piercing(src) + new /obj/item/reagent_containers/syringe/bluespace(src) + +/obj/item/storage/box/medipens + name = "box of medipens" + desc = "A box full of epinephrine MediPens." + illustration = "syringe" + +/obj/item/storage/box/medipens/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/hypospray/medipen(src) + +/obj/item/storage/box/medipens/utility + name = "stimpack value kit" + desc = "A box with several stimpack medipens for the economical miner." + illustration = "syringe" + +/obj/item/storage/box/medipens/utility/PopulateContents() + ..() // includes regular medipens. + for(var/i in 1 to 5) + new /obj/item/reagent_containers/hypospray/medipen/stimpack(src) + +/obj/item/storage/box/beakers + name = "box of beakers" + illustration = "beaker" + +/obj/item/storage/box/beakers/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/glass/beaker( src ) + +/obj/item/storage/box/beakers/bluespace + name = "box of bluespace beakers" + illustration = "beaker" + +/obj/item/storage/box/beakers/bluespace/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/glass/beaker/bluespace(src) + +/obj/item/storage/box/beakers/variety + name = "beaker variety box" + +/obj/item/storage/box/beakers/variety/PopulateContents() + new /obj/item/reagent_containers/glass/beaker(src) + new /obj/item/reagent_containers/glass/beaker/large(src) + new /obj/item/reagent_containers/glass/beaker/plastic(src) + new /obj/item/reagent_containers/glass/beaker/meta(src) + new /obj/item/reagent_containers/glass/beaker/noreact(src) + new /obj/item/reagent_containers/glass/beaker/bluespace(src) + +/obj/item/storage/box/hypospray + name = "hypospray mk. II kit" + icon_state = "medbriefcase" + illustration = null + +/obj/item/storage/box/hypospray/PopulateContents() + new /obj/item/hypospray/mkii(src) + new /obj/item/reagent_containers/glass/bottle/vial/small/preloaded/bicaridine(src) + new /obj/item/reagent_containers/glass/bottle/vial/small/preloaded/antitoxin(src) + new /obj/item/reagent_containers/glass/bottle/vial/small/preloaded/kelotane(src) + new /obj/item/reagent_containers/glass/bottle/vial/small/preloaded/dexalin(src) + +/obj/item/storage/box/hypospray/CMO + name = "advanced hypospray mk. II kit" + +/obj/item/storage/box/hypospray/CMO/PopulateContents() + new /obj/item/hypospray/mkii/CMO(src) + new /obj/item/reagent_containers/glass/bottle/vial/large/preloaded/bicaridine(src) + new /obj/item/reagent_containers/glass/bottle/vial/large/preloaded/antitoxin(src) + new /obj/item/reagent_containers/glass/bottle/vial/large/preloaded/kelotane(src) + new /obj/item/reagent_containers/glass/bottle/vial/large/preloaded/dexalin(src) + +/obj/item/storage/box/medigels + name = "box of medical gels" + desc = "A box full of medical gel applicators, with unscrewable caps and precision spray heads." + illustration = "medgel" + +/obj/item/storage/box/medigels/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/medigel( src ) + +/obj/item/storage/box/injectors + name = "box of DNA injectors" + desc = "This box contains injectors, it seems." + illustration = "dna" + +/obj/item/storage/box/injectors/PopulateContents() + var/static/items_inside = list( + /obj/item/dnainjector/h2m = 3, + /obj/item/dnainjector/m2h = 3) + generate_items_inside(items_inside,src) + +/obj/item/storage/box/flashbangs + name = "box of flashbangs (WARNING)" + desc = "WARNING: These devices are extremely dangerous and can cause blindness or deafness in repeated use." + icon_state = "secbox" + illustration = "flashbang" + +/obj/item/storage/box/flashbangs/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/grenade/flashbang(src) + +/obj/item/storage/box/stingbangs + name = "box of stingbangs (WARNING)" + desc = "WARNING: These devices are extremely dangerous and can cause severe injuries or death in repeated use." + icon_state = "secbox" + illustration = "flashbang" + +/obj/item/storage/box/stingbangs/PopulateContents() + for(var/i in 1 to 5) + new /obj/item/grenade/stingbang(src) + +/obj/item/storage/box/flashes + name = "box of flashbulbs" + desc = "WARNING: Flashes can cause serious eye damage, protective eyewear is required." + icon_state = "secbox" + illustration = "flash" + +/obj/item/storage/box/flashes/PopulateContents() + for(var/i in 1 to 6) + new /obj/item/assembly/flash/handheld(src) + +/obj/item/storage/box/wall_flash + name = "wall-mounted flash kit" + desc = "This box contains everything necessary to build a wall-mounted flash. WARNING: Flashes can cause serious eye damage, protective eyewear is required." + icon_state = "secbox" + illustration = "flash" + +/obj/item/storage/box/wall_flash/PopulateContents() + var/id = rand(1000, 9999) + // FIXME what if this conflicts with an existing one? + + new /obj/item/wallframe/button(src) + new /obj/item/electronics/airlock(src) + var/obj/item/assembly/control/flasher/remote = new(src) + remote.id = id + var/obj/item/wallframe/flasher/frame = new(src) + frame.id = id + new /obj/item/assembly/flash/handheld(src) + new /obj/item/screwdriver(src) + + +/obj/item/storage/box/teargas + name = "box of tear gas grenades (WARNING)" + desc = "WARNING: These devices are extremely dangerous and can cause blindness and skin irritation." + icon_state = "secbox" + illustration = "grenade" + +/obj/item/storage/box/teargas/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/grenade/chem_grenade/teargas(src) + +/obj/item/storage/box/emps + name = "box of emp grenades" + desc = "A box with 5 emp grenades." + illustration = "emp" + +/obj/item/storage/box/emps/PopulateContents() + for(var/i in 1 to 5) + new /obj/item/grenade/empgrenade(src) + +/obj/item/storage/box/trackimp + name = "boxed tracking implant kit" + desc = "Box full of scum-bag tracking utensils." + icon_state = "secbox" + illustration = "implant" + +/obj/item/storage/box/trackimp/PopulateContents() + var/static/items_inside = list( + /obj/item/implantcase/tracking = 4, + /obj/item/implanter = 1, + /obj/item/implantpad = 1, + /obj/item/locator = 1) + generate_items_inside(items_inside,src) + +/obj/item/storage/box/minertracker + name = "boxed tracking implant kit" + desc = "For finding those who have died on the accursed lavaworld." + illustration = "implant" + +/obj/item/storage/box/minertracker/PopulateContents() + var/static/items_inside = list( + /obj/item/implantcase/tracking = 3, + /obj/item/implanter = 1, + /obj/item/implantpad = 1, + /obj/item/locator = 1) + generate_items_inside(items_inside,src) + +/obj/item/storage/box/chemimp + name = "boxed chemical implant kit" + desc = "Box of stuff used to implant chemicals." + illustration = "implant" + +/obj/item/storage/box/chemimp/PopulateContents() + var/static/items_inside = list( + /obj/item/implantcase/chem = 5, + /obj/item/implanter = 1, + /obj/item/implantpad = 1) + generate_items_inside(items_inside,src) + +/obj/item/storage/box/exileimp + name = "boxed exile implant kit" + desc = "Box of exile implants. It has a picture of a clown being booted through the Gateway." + illustration = "implant" + +/obj/item/storage/box/exileimp/PopulateContents() + var/static/items_inside = list( + /obj/item/implantcase/exile = 5, + /obj/item/implanter = 1) + generate_items_inside(items_inside,src) + +/obj/item/storage/box/bodybags + name = "body bags" + desc = "The label indicates that it contains body bags." + illustration = "bodybags" + +/obj/item/storage/box/bodybags/PopulateContents() + ..() + for(var/i in 1 to 7) + new /obj/item/bodybag(src) + +/obj/item/storage/box/rxglasses + name = "box of prescription glasses" + desc = "This box contains nerd glasses." + illustration = "glasses" + +/obj/item/storage/box/rxglasses/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/clothing/glasses/regular(src) + +/obj/item/storage/box/drinkingglasses + name = "box of drinking glasses" + desc = "It has a picture of drinking glasses on it." + illustration = "drinkglass" + +/obj/item/storage/box/drinkingglasses/PopulateContents() + for(var/i in 1 to 6) + new /obj/item/reagent_containers/food/drinks/drinkingglass(src) + +/obj/item/storage/box/condimentbottles + name = "box of condiment bottles" + desc = "It has a large ketchup smear on it." + illustration = "condiment" + +/obj/item/storage/box/condimentbottles/PopulateContents() + for(var/i in 1 to 6) + new /obj/item/reagent_containers/food/condiment(src) + +/obj/item/storage/box/cups + name = "box of paper cups" + desc = "It has pictures of paper cups on the front." + illustration = "cup" + +/obj/item/storage/box/cups/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/food/drinks/sillycup( src ) + +/obj/item/storage/box/donkpockets + name = "box of donk-pockets" + desc = "Instructions: Heat in microwave. Product will cool if not eaten within seven minutes." + icon_state = "donkpocketbox" + illustration=null + var/donktype = /obj/item/reagent_containers/food/snacks/donkpocket + +/obj/item/storage/box/donkpockets/PopulateContents() + for(var/i in 1 to 6) + new donktype(src) + +/obj/item/storage/box/donkpockets/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.set_holdable(list(/obj/item/reagent_containers/food/snacks/donkpocket)) + +/obj/item/storage/box/donkpockets/donkpocketspicy + name = "box of spicy-flavoured donk-pockets" + icon_state = "donkpocketboxspicy" + donktype = /obj/item/reagent_containers/food/snacks/donkpocket/spicy + +/obj/item/storage/box/donkpockets/donkpocketteriyaki + name = "box of teriyaki-flavoured donk-pockets" + icon_state = "donkpocketboxteriyaki" + donktype = /obj/item/reagent_containers/food/snacks/donkpocket/teriyaki + +/obj/item/storage/box/donkpockets/donkpocketpizza + name = "box of pizza-flavoured donk-pockets" + icon_state = "donkpocketboxpizza" + donktype = /obj/item/reagent_containers/food/snacks/donkpocket/pizza + +/obj/item/storage/box/donkpockets/donkpocketgondola + name = "box of gondola-flavoured donk-pockets" + icon_state = "donkpocketboxgondola" + donktype = /obj/item/reagent_containers/food/snacks/donkpocket/gondola + +/obj/item/storage/box/donkpockets/donkpocketberry + name = "box of berry-flavoured donk-pockets" + icon_state = "donkpocketboxberry" + donktype = /obj/item/reagent_containers/food/snacks/donkpocket/berry + +/obj/item/storage/box/donkpockets/donkpockethonk + name = "box of banana-flavoured donk-pockets" + icon_state = "donkpocketboxbanana" + donktype = /obj/item/reagent_containers/food/snacks/donkpocket/honk + +/obj/item/storage/box/monkeycubes + name = "monkey cube box" + desc = "Drymate brand monkey cubes. Just add water!" + icon_state = "monkeycubebox" + illustration = null + var/cube_type = /obj/item/reagent_containers/food/snacks/monkeycube + +/obj/item/storage/box/monkeycubes/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 7 + STR.set_holdable(list(/obj/item/reagent_containers/food/snacks/monkeycube)) + +/obj/item/storage/box/monkeycubes/PopulateContents() + for(var/i in 1 to 5) + new cube_type(src) + +/obj/item/storage/box/monkeycubes/syndicate + desc = "Waffle Co. brand monkey cubes. Just add water and a dash of subterfuge!" + cube_type = /obj/item/reagent_containers/food/snacks/monkeycube/syndicate + +/obj/item/storage/box/gorillacubes + name = "gorilla cube box" + desc = "Waffle Co. brand gorilla cubes. Do not taunt." + icon_state = "monkeycubebox" + illustration = null + +/obj/item/storage/box/gorillacubes/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 3 + STR.set_holdable(list(/obj/item/reagent_containers/food/snacks/monkeycube)) + +/obj/item/storage/box/gorillacubes/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/reagent_containers/food/snacks/monkeycube/gorilla(src) + +/obj/item/storage/box/ids + name = "box of spare IDs" + desc = "Has so many empty IDs." + illustration = "id" + +/obj/item/storage/box/ids/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/card/id(src) + +//Some spare PDAs in a box +/obj/item/storage/box/PDAs + name = "spare PDAs" + desc = "A box of spare PDA microcomputers." + illustration = "pda" + +/obj/item/storage/box/PDAs/PopulateContents() + for(var/i in 1 to 4) + new /obj/item/pda(src) + new /obj/item/cartridge/head(src) + + var/newcart = pick( /obj/item/cartridge/engineering, + /obj/item/cartridge/security, + /obj/item/cartridge/medical, + /obj/item/cartridge/signal/toxins, + /obj/item/cartridge/quartermaster) + new newcart(src) + +/obj/item/storage/box/silver_ids + name = "box of spare silver IDs" + desc = "Shiny IDs for important people." + illustration = "id" + +/obj/item/storage/box/silver_ids/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/card/id/silver(src) + +/obj/item/storage/box/prisoner + name = "box of prisoner IDs" + desc = "Take away their last shred of dignity, their name." + icon_state = "secbox" + illustration = "id" + +/obj/item/storage/box/prisoner/PopulateContents() + ..() + new /obj/item/card/id/prisoner/one(src) + new /obj/item/card/id/prisoner/two(src) + new /obj/item/card/id/prisoner/three(src) + new /obj/item/card/id/prisoner/four(src) + new /obj/item/card/id/prisoner/five(src) + new /obj/item/card/id/prisoner/six(src) + new /obj/item/card/id/prisoner/seven(src) + +/obj/item/storage/box/seccarts + name = "box of PDA security cartridges" + desc = "A box full of PDA cartridges used by Security." + icon_state = "secbox" + illustration = "pda" + +/obj/item/storage/box/seccarts/PopulateContents() + new /obj/item/cartridge/detective(src) + for(var/i in 1 to 6) + new /obj/item/cartridge/security(src) + +/obj/item/storage/box/firingpins + name = "box of standard firing pins" + desc = "A box full of standard firing pins, to allow newly-developed firearms to operate." + icon_state = "secbox" + illustration = "firingpin" + +/obj/item/storage/box/firingpins/PopulateContents() + for(var/i in 1 to 5) + new /obj/item/firing_pin(src) + +/obj/item/storage/box/firingpins/paywall + name = "box of paywall firing pins" + desc = "A box full of paywall firing pins, to allow newly-developed firearms to operate behind a custom-set paywall." + illustration = "firingpin" + +/obj/item/storage/box/firingpins/paywall/PopulateContents() + for(var/i in 1 to 5) + new /obj/item/firing_pin/paywall(src) + +/obj/item/storage/box/lasertagpins + name = "box of laser tag firing pins" + desc = "A box full of laser tag firing pins, to allow newly-developed firearms to require wearing brightly coloured plastic armor before being able to be used." + illustration = "firingpin" + +/obj/item/storage/box/lasertagpins/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/firing_pin/tag/red(src) + new /obj/item/firing_pin/tag/blue(src) + +/obj/item/storage/box/handcuffs + name = "box of spare handcuffs" + desc = "A box full of handcuffs." + icon_state = "secbox" + illustration = "handcuff" + +/obj/item/storage/box/handcuffs/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/restraints/handcuffs(src) + +/obj/item/storage/box/zipties + name = "box of spare zipties" + desc = "A box full of zipties." + icon_state = "secbox" + illustration = "handcuff" + +/obj/item/storage/box/zipties/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/restraints/handcuffs/cable/zipties(src) + +/obj/item/storage/box/alienhandcuffs + name = "box of spare handcuffs" + desc = "A box full of handcuffs." + icon_state = "alienbox" + illustration = "handcuff" + +/obj/item/storage/box/alienhandcuffs/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/restraints/handcuffs/alien(src) + +/obj/item/storage/box/fakesyndiesuit + name = "boxed space suit and helmet" + desc = "A sleek, sturdy box used to hold replica spacesuits." + icon_state = "syndiebox" + illustration = "syndiesuit" + +/obj/item/storage/box/fakesyndiesuit/PopulateContents() + new /obj/item/clothing/head/syndicatefake(src) + new /obj/item/clothing/suit/syndicatefake(src) + +/obj/item/storage/box/mousetraps + name = "box of Pest-B-Gon mousetraps" + desc = "Keep out of reach of children." + illustration = "mousetrap" + +/obj/item/storage/box/mousetraps/PopulateContents() + for(var/i in 1 to 6) + new /obj/item/assembly/mousetrap(src) + +/obj/item/storage/box/pillbottles + name = "box of pill bottles" + desc = "It has pictures of pill bottles on its front." + illustration = "pillbox" + +/obj/item/storage/box/pillbottles/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/storage/pill_bottle(src) + +/obj/item/storage/box/snappops + name = "snap pop box" + desc = "Eight wrappers of fun! Ages 8 and up. Not suitable for children." + icon = 'icons/obj/toy.dmi' + icon_state = "spbox" + +/obj/item/storage/box/snappops/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.set_holdable(list(/obj/item/toy/snappop)) + STR.max_items = 8 + +/obj/item/storage/box/snappops/PopulateContents() + SEND_SIGNAL(src, COMSIG_TRY_STORAGE_FILL_TYPE, /obj/item/toy/snappop) + +/obj/item/storage/box/matches + name = "matchbox" + desc = "A small box of Almost But Not Quite Plasma Premium Matches." + icon = 'icons/obj/cigarettes.dmi' + icon_state = "matchbox" + item_state = "zippo" + w_class = WEIGHT_CLASS_TINY + slot_flags = ITEM_SLOT_BELT + drop_sound = 'sound/items/handling/matchbox_drop.ogg' + pickup_sound = 'sound/items/handling/matchbox_pickup.ogg' + custom_price = 20 + +/obj/item/storage/box/matches/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 10 + STR.set_holdable(list(/obj/item/match)) + +/obj/item/storage/box/matches/PopulateContents() + SEND_SIGNAL(src, COMSIG_TRY_STORAGE_FILL_TYPE, /obj/item/match) + +/obj/item/storage/box/matches/attackby(obj/item/match/W as obj, mob/user as mob, params) + if(istype(W, /obj/item/match)) + W.matchignite() + +/obj/item/storage/box/lights + name = "box of replacement bulbs" + icon = 'icons/obj/storage.dmi' + illustration = "light" + desc = "This box is shaped on the inside so that only light tubes and bulbs fit." + item_state = "syringe_kit" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + foldable = /obj/item/stack/sheet/cardboard //BubbleWrap + +/obj/item/storage/box/lights/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 21 + STR.set_holdable(list(/obj/item/light/tube, /obj/item/light/bulb)) + STR.max_combined_w_class = 21 + STR.click_gather = FALSE //temp workaround to re-enable filling the light replacer with the box + +/obj/item/storage/box/lights/bulbs/PopulateContents() + for(var/i in 1 to 21) + new /obj/item/light/bulb(src) + +/obj/item/storage/box/lights/tubes + name = "box of replacement tubes" + illustration = "lighttube" + +/obj/item/storage/box/lights/tubes/PopulateContents() + for(var/i in 1 to 21) + new /obj/item/light/tube(src) + +/obj/item/storage/box/lights/mixed + name = "box of replacement lights" + illustration = "lightmixed" + +/obj/item/storage/box/lights/mixed/PopulateContents() + for(var/i in 1 to 14) + new /obj/item/light/tube(src) + for(var/i in 1 to 7) + new /obj/item/light/bulb(src) + + +/obj/item/storage/box/deputy + name = "box of deputy armbands" + desc = "To be issued to those authorized to act as deputy of security." + icon_state = "secbox" + illustration = "depband" + +/obj/item/storage/box/deputy/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/clothing/accessory/armband/deputy(src) + +/obj/item/storage/box/metalfoam + name = "box of metal foam grenades" + desc = "To be used to rapidly seal hull breaches." + illustration = "grenade" + +/obj/item/storage/box/metalfoam/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/grenade/chem_grenade/metalfoam(src) + +/obj/item/storage/box/smart_metal_foam + name = "box of smart metal foam grenades" + desc = "Used to rapidly seal hull breaches. This variety conforms to the walls of its area." + illustration = "grenade" + +/obj/item/storage/box/smart_metal_foam/PopulateContents() + for(var/i in 1 to 7) + new/obj/item/grenade/chem_grenade/smart_metal_foam(src) + +/obj/item/storage/box/hug + name = "box of hugs" + desc = "A special box for sensitive people." + icon_state = "hugbox" + illustration = "heart" + foldable = null + +/obj/item/storage/box/hug/suicide_act(mob/user) + user.visible_message("[user] clamps the box of hugs on [user.p_their()] jugular! Guess it wasn't such a hugbox after all..") + return (BRUTELOSS) + +/obj/item/storage/box/hug/attack_self(mob/user) + ..() + user.changeNext_move(CLICK_CD_MELEE) + playsound(loc, "rustle", 50, TRUE, -5) + user.visible_message("[user] hugs \the [src].","You hug \the [src].") + +/////clown box & honkbot assembly +/obj/item/storage/box/clown + name = "clown box" + desc = "A colorful cardboard box for the clown" + illustration = "clown" + +/obj/item/storage/box/clown/attackby(obj/item/I, mob/user, params) + if((istype(I, /obj/item/bodypart/l_arm/robot)) || (istype(I, /obj/item/bodypart/r_arm/robot))) + if(contents.len) //prevent accidently deleting contents + to_chat(user, "You need to empty [src] out first!") + return + if(!user.temporarilyRemoveItemFromInventory(I)) + return + qdel(I) + to_chat(user, "You add some wheels to the [src]! You've got a honkbot assembly now! Honk!") + var/obj/item/bot_assembly/honkbot/A = new + qdel(src) + user.put_in_hands(A) + else + return ..() + +////// +/obj/item/storage/box/hug/medical/PopulateContents() + new /obj/item/stack/medical/bruise_pack(src) + new /obj/item/stack/medical/ointment(src) + new /obj/item/reagent_containers/hypospray/medipen(src) + +// Clown survival box +/obj/item/storage/box/hug/survival/PopulateContents() + new /obj/item/clothing/mask/breath(src) + new /obj/item/reagent_containers/hypospray/medipen(src) + + if(!isplasmaman(loc)) + new /obj/item/tank/internals/emergency_oxygen(src) + else + new /obj/item/tank/internals/plasmaman/belt(src) + +/obj/item/storage/box/rubbershot + name = "box of rubber shots" + desc = "A box full of rubber shots, designed for riot shotguns." + icon_state = "rubbershot_box" + illustration = null + +/obj/item/storage/box/rubbershot/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/ammo_casing/shotgun/rubbershot(src) + +/obj/item/storage/box/lethalshot + name = "box of lethal shotgun shots" + desc = "A box full of lethal shots, designed for riot shotguns." + icon_state = "lethalshot_box" + illustration = null + +/obj/item/storage/box/lethalshot/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/ammo_casing/shotgun/buckshot(src) + +/obj/item/storage/box/beanbag + name = "box of beanbags" + desc = "A box full of beanbag shells." + icon_state = "rubbershot_box" + illustration = null + +/obj/item/storage/box/beanbag/PopulateContents() + for(var/i in 1 to 6) + new /obj/item/ammo_casing/shotgun/beanbag(src) + +/obj/item/storage/box/actionfigure + name = "box of action figures" + desc = "The latest set of collectable action figures." + icon_state = "box" + +/obj/item/storage/box/actionfigure/PopulateContents() + for(var/i in 1 to 4) + var/randomFigure = pick(subtypesof(/obj/item/toy/figure)) + new randomFigure(src) + +/obj/item/storage/box/papersack + name = "paper sack" + desc = "A sack neatly crafted out of paper." + icon_state = "paperbag_None" + item_state = "paperbag_None" + illustration = null + resistance_flags = FLAMMABLE + foldable = null + /// A list of all available papersack reskins + var/list/papersack_designs = list() + +/obj/item/storage/box/papersack/Initialize(mapload) + . = ..() + papersack_designs = sortList(list( + "None" = image(icon = src.icon, icon_state = "paperbag_None"), + "NanotrasenStandard" = image(icon = src.icon, icon_state = "paperbag_NanotrasenStandard"), + "SyndiSnacks" = image(icon = src.icon, icon_state = "paperbag_SyndiSnacks"), + "Heart" = image(icon = src.icon, icon_state = "paperbag_Heart"), + "SmileyFace" = image(icon = src.icon, icon_state = "paperbag_SmileyFace") + )) + +/obj/item/storage/box/papersack/update_icon_state() + if(contents.len == 0) + icon_state = "[item_state]" + else + icon_state = "[item_state]_closed" + +/obj/item/storage/box/papersack/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/pen)) + var/choice = show_radial_menu(user, src , papersack_designs, custom_check = CALLBACK(src, .proc/check_menu, user, W), radius = 36, require_near = TRUE) + if(!choice) + return FALSE + if(icon_state == "paperbag_[choice]") + return FALSE + switch(choice) + if("None") + desc = "A sack neatly crafted out of paper." + if("NanotrasenStandard") + desc = "A standard Nanotrasen paper lunch sack for loyal employees on the go." + if("SyndiSnacks") + desc = "The design on this paper sack is a remnant of the notorious 'SyndieSnacks' program." + if("Heart") + desc = "A paper sack with a heart etched onto the side." + if("SmileyFace") + desc = "A paper sack with a crude smile etched onto the side." + else + return FALSE + to_chat(user, "You make some modifications to [src] using your pen.") + icon_state = "paperbag_[choice]" + item_state = "paperbag_[choice]" + return FALSE + else if(W.get_sharpness()) + if(!contents.len) + if(item_state == "paperbag_None") + user.show_message("You cut eyeholes into [src].", MSG_VISUAL) + new /obj/item/clothing/head/papersack(user.loc) + qdel(src) + return FALSE + else if(item_state == "paperbag_SmileyFace") + user.show_message("You cut eyeholes into [src] and modify the design.", MSG_VISUAL) + new /obj/item/clothing/head/papersack/smiley(user.loc) + qdel(src) + return FALSE + return ..() + +/** + * check_menu: Checks if we are allowed to interact with a radial menu + * + * Arguments: + * * user The mob interacting with a menu + * * P The pen used to interact with a menu + */ +/obj/item/storage/box/papersack/proc/check_menu(mob/user, obj/item/pen/P) + if(!istype(user)) + return FALSE + if(user.incapacitated()) + return FALSE + if(contents.len) + to_chat(user, "You can't modify [src] with items still inside!") + return FALSE + if(!P || !user.is_holding(P)) + to_chat(user, "You need a pen to modify [src]!") + return FALSE + return TRUE + +/obj/item/storage/box/papersack/meat + desc = "It's slightly moist and smells like a slaughterhouse." + +/obj/item/storage/box/papersack/meat/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/food/snacks/meat/slab(src) + +/obj/item/storage/box/ingredients //This box is for the randomely chosen version the chef spawns with, it shouldn't actually exist. + name = "ingredients box" + illustration = "fruit" + var/theme_name + +/obj/item/storage/box/ingredients/Initialize() + . = ..() + if(theme_name) + name = "[name] ([theme_name])" + desc = "A box containing supplementary ingredients for the aspiring chef. The box's theme is '[theme_name]'." + item_state = "syringe_kit" + +/obj/item/storage/box/ingredients/wildcard + theme_name = "wildcard" + +/obj/item/storage/box/ingredients/wildcard/PopulateContents() + for(var/i in 1 to 7) + var/randomFood = pick(/obj/item/reagent_containers/food/snacks/grown/chili, + /obj/item/reagent_containers/food/snacks/grown/tomato, + /obj/item/reagent_containers/food/snacks/grown/carrot, + /obj/item/reagent_containers/food/snacks/grown/potato, + /obj/item/reagent_containers/food/snacks/grown/potato/sweet, + /obj/item/reagent_containers/food/snacks/grown/apple, + /obj/item/reagent_containers/food/snacks/chocolatebar, + /obj/item/reagent_containers/food/snacks/grown/cherries, + /obj/item/reagent_containers/food/snacks/grown/banana, + /obj/item/reagent_containers/food/snacks/grown/cabbage, + /obj/item/reagent_containers/food/snacks/grown/soybeans, + /obj/item/reagent_containers/food/snacks/grown/corn, + /obj/item/reagent_containers/food/snacks/grown/mushroom/plumphelmet, + /obj/item/reagent_containers/food/snacks/grown/mushroom/chanterelle) + new randomFood(src) + +/obj/item/storage/box/ingredients/fiesta + theme_name = "fiesta" + +/obj/item/storage/box/ingredients/fiesta/PopulateContents() + new /obj/item/reagent_containers/food/snacks/tortilla(src) + for(var/i in 1 to 2) + new /obj/item/reagent_containers/food/snacks/grown/corn(src) + new /obj/item/reagent_containers/food/snacks/grown/soybeans(src) + new /obj/item/reagent_containers/food/snacks/grown/chili(src) + +/obj/item/storage/box/ingredients/italian + theme_name = "italian" + +/obj/item/storage/box/ingredients/italian/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/reagent_containers/food/snacks/grown/tomato(src) + new /obj/item/reagent_containers/food/snacks/meatball(src) + new /obj/item/reagent_containers/food/drinks/bottle/wine(src) + +/obj/item/storage/box/ingredients/vegetarian + theme_name = "vegetarian" + +/obj/item/storage/box/ingredients/vegetarian/PopulateContents() + for(var/i in 1 to 2) + new /obj/item/reagent_containers/food/snacks/grown/carrot(src) + new /obj/item/reagent_containers/food/snacks/grown/eggplant(src) + new /obj/item/reagent_containers/food/snacks/grown/potato(src) + new /obj/item/reagent_containers/food/snacks/grown/apple(src) + new /obj/item/reagent_containers/food/snacks/grown/corn(src) + new /obj/item/reagent_containers/food/snacks/grown/tomato(src) + +/obj/item/storage/box/ingredients/american + theme_name = "american" + +/obj/item/storage/box/ingredients/american/PopulateContents() + for(var/i in 1 to 2) + new /obj/item/reagent_containers/food/snacks/grown/potato(src) + new /obj/item/reagent_containers/food/snacks/grown/tomato(src) + new /obj/item/reagent_containers/food/snacks/grown/corn(src) + new /obj/item/reagent_containers/food/snacks/meatball(src) + +/obj/item/storage/box/ingredients/fruity + theme_name = "fruity" + +/obj/item/storage/box/ingredients/fruity/PopulateContents() + for(var/i in 1 to 2) + new /obj/item/reagent_containers/food/snacks/grown/apple(src) + new /obj/item/reagent_containers/food/snacks/grown/citrus/orange(src) + new /obj/item/reagent_containers/food/snacks/grown/citrus/lemon(src) + new /obj/item/reagent_containers/food/snacks/grown/citrus/lime(src) + new /obj/item/reagent_containers/food/snacks/grown/watermelon(src) + +/obj/item/storage/box/ingredients/sweets + theme_name = "sweets" + +/obj/item/storage/box/ingredients/sweets/PopulateContents() + for(var/i in 1 to 2) + new /obj/item/reagent_containers/food/snacks/grown/cherries(src) + new /obj/item/reagent_containers/food/snacks/grown/banana(src) + new /obj/item/reagent_containers/food/snacks/chocolatebar(src) + new /obj/item/reagent_containers/food/snacks/grown/cocoapod(src) + new /obj/item/reagent_containers/food/snacks/grown/apple(src) + +/obj/item/storage/box/ingredients/delights + theme_name = "delights" + +/obj/item/storage/box/ingredients/delights/PopulateContents() + for(var/i in 1 to 2) + new /obj/item/reagent_containers/food/snacks/grown/potato/sweet(src) + new /obj/item/reagent_containers/food/snacks/grown/bluecherries(src) + new /obj/item/reagent_containers/food/snacks/grown/vanillapod(src) + new /obj/item/reagent_containers/food/snacks/grown/cocoapod(src) + new /obj/item/reagent_containers/food/snacks/grown/berries(src) + +/obj/item/storage/box/ingredients/grains + theme_name = "grains" + +/obj/item/storage/box/ingredients/grains/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/reagent_containers/food/snacks/grown/oat(src) + new /obj/item/reagent_containers/food/snacks/grown/wheat(src) + new /obj/item/reagent_containers/food/snacks/grown/cocoapod(src) + new /obj/item/reagent_containers/honeycomb(src) + new /obj/item/seeds/poppy(src) + +/obj/item/storage/box/ingredients/carnivore + theme_name = "carnivore" + +/obj/item/storage/box/ingredients/carnivore/PopulateContents() + new /obj/item/reagent_containers/food/snacks/meat/slab/bear(src) + new /obj/item/reagent_containers/food/snacks/meat/slab/spider(src) + new /obj/item/reagent_containers/food/snacks/spidereggs(src) + new /obj/item/reagent_containers/food/snacks/carpmeat(src) + new /obj/item/reagent_containers/food/snacks/meat/slab/xeno(src) + new /obj/item/reagent_containers/food/snacks/meat/slab/corgi(src) + new /obj/item/reagent_containers/food/snacks/meatball(src) + +/obj/item/storage/box/ingredients/exotic + theme_name = "exotic" + +/obj/item/storage/box/ingredients/exotic/PopulateContents() + for(var/i in 1 to 2) + new /obj/item/reagent_containers/food/snacks/carpmeat(src) + new /obj/item/reagent_containers/food/snacks/grown/soybeans(src) + new /obj/item/reagent_containers/food/snacks/grown/cabbage(src) + new /obj/item/reagent_containers/food/snacks/grown/chili(src) + +/obj/item/storage/box/emptysandbags + name = "box of empty sandbags" + illustration = "sandbag" + +/obj/item/storage/box/emptysandbags/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/emptysandbag(src) + +/obj/item/storage/box/rndboards + name = "\proper the liberator's legacy" + desc = "A box containing a gift for worthy golems." + illustration = "scicircuit" + +/obj/item/storage/box/rndboards/PopulateContents() + new /obj/item/circuitboard/machine/protolathe(src) + new /obj/item/circuitboard/machine/destructive_analyzer(src) + new /obj/item/circuitboard/machine/circuit_imprinter(src) + new /obj/item/circuitboard/computer/rdconsole(src) + +/obj/item/storage/box/silver_sulf + name = "box of silver sulfadiazine patches" + desc = "Contains patches used to treat burns." + illustration = "firepatch" + +/obj/item/storage/box/silver_sulf/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/pill/patch/silver_sulf(src) + +/obj/item/storage/box/fountainpens + name = "box of fountain pens" + illustration = "fpen" + +/obj/item/storage/box/fountainpens/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/pen/fountain(src) + +/obj/item/storage/box/holy_grenades + name = "box of holy hand grenades" + desc = "Contains several grenades used to rapidly purge heresy." + illustration = "grenade" + +/obj/item/storage/box/holy_grenades/PopulateContents() + for(var/i in 1 to 7) + new/obj/item/grenade/chem_grenade/holy(src) + +/obj/item/storage/box/stockparts/basic //for ruins where it's a bad idea to give access to an autolathe/protolathe, but still want to make stock parts accessible + name = "box of stock parts" + desc = "Contains a variety of basic stock parts." + +/obj/item/storage/box/stockparts/basic/PopulateContents() + var/static/items_inside = list( + /obj/item/stock_parts/capacitor = 3, + /obj/item/stock_parts/scanning_module = 3, + /obj/item/stock_parts/manipulator = 3, + /obj/item/stock_parts/micro_laser = 3, + /obj/item/stock_parts/matter_bin = 3) + generate_items_inside(items_inside,src) + +/obj/item/storage/box/stockparts/deluxe + name = "box of deluxe stock parts" + desc = "Contains a variety of deluxe stock parts." + icon_state = "syndiebox" + +/obj/item/storage/box/stockparts/deluxe/PopulateContents() + var/static/items_inside = list( + /obj/item/stock_parts/capacitor/quadratic = 3, + /obj/item/stock_parts/scanning_module/triphasic = 3, + /obj/item/stock_parts/manipulator/femto = 3, + /obj/item/stock_parts/micro_laser/quadultra = 3, + /obj/item/stock_parts/matter_bin/bluespace = 3) + generate_items_inside(items_inside,src) + +/obj/item/storage/box/dishdrive + name = "DIY Dish Drive Kit" + desc = "Contains everything you need to build your own Dish Drive!" + custom_premium_price = 1000 + +/obj/item/storage/box/dishdrive/PopulateContents() + var/static/items_inside = list( + /obj/item/stack/sheet/metal/five = 1, + /obj/item/stack/cable_coil/random/five = 1, //Random from Smartwire Revert - Waspie Bois + /obj/item/circuitboard/machine/dish_drive = 1, + /obj/item/stack/sheet/glass = 1, + /obj/item/stock_parts/manipulator = 1, + /obj/item/stock_parts/matter_bin = 2, + /obj/item/screwdriver = 1) + generate_items_inside(items_inside,src) + +/obj/item/storage/box/material + name = "box of materials" + illustration = "implant" + +/obj/item/storage/box/material/PopulateContents() //less uranium because radioactive + var/static/items_inside = list( + /obj/item/stack/sheet/metal/fifty=1,\ + /obj/item/stack/sheet/glass/fifty=1,\ + /obj/item/stack/sheet/rglass=50,\ + /obj/item/stack/sheet/plasmaglass=50,\ + /obj/item/stack/sheet/titaniumglass=50,\ + /obj/item/stack/sheet/plastitaniumglass=50,\ + /obj/item/stack/sheet/plasteel=50,\ + /obj/item/stack/sheet/mineral/plastitanium=50,\ + /obj/item/stack/sheet/mineral/titanium=50,\ + /obj/item/stack/sheet/mineral/gold=50,\ + /obj/item/stack/sheet/mineral/silver=50,\ + /obj/item/stack/sheet/mineral/plasma=50,\ + /obj/item/stack/sheet/mineral/uranium=20,\ + /obj/item/stack/sheet/mineral/diamond=50,\ + /obj/item/stack/sheet/bluespace_crystal=50,\ + /obj/item/stack/sheet/mineral/bananium=50,\ + /obj/item/stack/sheet/mineral/wood=50,\ + /obj/item/stack/sheet/plastic/fifty=1,\ + /obj/item/stack/sheet/runed_metal/fifty=1 + ) + generate_items_inside(items_inside,src) + +/obj/item/storage/box/debugtools + name = "box of debug tools" + icon_state = "syndiebox" + +/obj/item/storage/box/debugtools/PopulateContents() + var/static/items_inside = list( + /obj/item/flashlight/emp/debug=1,\ + /obj/item/pda=1,\ + /obj/item/modular_computer/tablet/preset/advanced=1,\ + /obj/item/geiger_counter=1,\ + /obj/item/construction/rcd/combat/admin=1,\ + /obj/item/pipe_dispenser=1,\ + /obj/item/card/emag=1,\ + /obj/item/stack/spacecash/c1000=50,\ + /obj/item/healthanalyzer/advanced=1,\ + /obj/item/disk/tech_disk/debug=1,\ + /obj/item/uplink/debug=1,\ + /obj/item/uplink/nuclear/debug=1,\ + /obj/item/storage/box/beakers/bluespace=1,\ + /obj/item/storage/box/beakers/variety=1,\ + /obj/item/storage/box/material=1 + ) + generate_items_inside(items_inside,src) + +/obj/item/storage/box/plastic + name = "plastic box" + desc = "It's a solid, plastic shell box." + icon_state = "plasticbox" + foldable = null + illustration = "writing" + custom_materials = list(/datum/material/plastic = 1000) //You lose most if recycled. + + +/obj/item/storage/box/fireworks + name = "box of fireworks" + desc = "Contains an assortment of fireworks." + illustration = "sparkler" + +/obj/item/storage/box/fireworks/PopulateContents() + for(var/i in 1 to 3) + new/obj/item/sparkler(src) + new/obj/item/grenade/firecracker(src) + new /obj/item/toy/snappop(src) + +/obj/item/storage/box/fireworks/dangerous + +/obj/item/storage/box/fireworks/dangerous/PopulateContents() + for(var/i in 1 to 3) + new/obj/item/sparkler(src) + new/obj/item/grenade/firecracker(src) + if(prob(20)) + new /obj/item/grenade/frag(src) + else + new /obj/item/toy/snappop(src) + +/obj/item/storage/box/firecrackers + name = "box of firecrackers" + desc = "A box filled with illegal firecracker. You wonder who still makes these." + icon_state = "syndiebox" + illustration = "firecracker" + +/obj/item/storage/box/firecrackers/PopulateContents() + for(var/i in 1 to 7) + new/obj/item/grenade/firecracker(src) + +/obj/item/storage/box/sparklers + name = "box of sparklers" + desc = "A box of NT brand sparklers, burns hot even in the cold of space-winter." + illustration = "sparkler" + +/obj/item/storage/box/sparklers/PopulateContents() + for(var/i in 1 to 7) + new/obj/item/sparkler(src) + +/obj/item/storage/box/gum + name = "bubblegum packet" + desc = "The packaging is entirely in japanese, apparently. You can't make out a single word of it." + icon_state = "bubblegum_generic" + w_class = WEIGHT_CLASS_TINY + illustration = null + foldable = null + custom_price = 120 + +/obj/item/storage/box/gum/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.set_holdable(list(/obj/item/reagent_containers/food/snacks/chewable/bubblegum)) + STR.max_items = 4 + +/obj/item/storage/box/gum/PopulateContents() + for(var/i in 1 to 4) + new/obj/item/reagent_containers/food/snacks/chewable/bubblegum(src) + +/obj/item/storage/box/gum/nicotine + name = "nicotine gum packet" + desc = "Designed to help with nicotine addiction and oral fixation all at once without destroying your lungs in the process. Mint flavored!" + icon_state = "bubblegum_nicotine" + custom_premium_price = 275 + +/obj/item/storage/box/gum/nicotine/PopulateContents() + for(var/i in 1 to 4) + new/obj/item/reagent_containers/food/snacks/chewable/bubblegum/nicotine(src) + +/obj/item/storage/box/gum/happiness + name = "HP+ gum packet" + desc = "A seemingly homemade packaging with an odd smell. It has a weird drawing of a smiling face sticking out its tongue." + icon_state = "bubblegum_happiness" + custom_price = 300 + custom_premium_price = 300 + +/obj/item/storage/box/gum/happiness/Initialize() + . = ..() + if (prob(25)) + desc += "You can faintly make out the word 'Hemopagopril' was once scribbled on it." + +/obj/item/storage/box/gum/happiness/PopulateContents() + for(var/i in 1 to 4) + new/obj/item/reagent_containers/food/snacks/chewable/bubblegum/happiness(src) + +/obj/item/storage/box/gum/bubblegum + name = "bubblegum gum packet" + desc = "The packaging is entirely in Demonic, apparently. You feel like even opening this would be a sin." + icon_state = "bubblegum_bubblegum" + +/obj/item/storage/box/gum/bubblegum/PopulateContents() + for(var/i in 1 to 4) + new/obj/item/reagent_containers/food/snacks/chewable/bubblegum/bubblegum(src) + +/obj/item/storage/box/shipping + name = "box of shipping supplies" + desc = "Contains several scanners and labelers for shipping things. Wrapping Paper not included." + illustration = "shipping" + +/obj/item/storage/box/shipping/PopulateContents() + var/static/items_inside = list( + /obj/item/destTagger=1,\ + /obj/item/sales_tagger=1,\ + /obj/item/export_scanner=1,\ + /obj/item/stack/packageWrap/small=2,\ + /obj/item/stack/wrapping_paper/small=1 + ) + generate_items_inside(items_inside,src) diff --git a/code/game/objects/items/storage/briefcase.dm b/code/game/objects/items/storage/briefcase.dm index 62d2227d7914..bc08ec556e1a 100644 --- a/code/game/objects/items/storage/briefcase.dm +++ b/code/game/objects/items/storage/briefcase.dm @@ -1,49 +1,49 @@ -/obj/item/storage/briefcase - name = "briefcase" - desc = "It's made of AUTHENTIC faux-leather and has a price-tag still attached. Its owner must be a real professional." - icon_state = "briefcase" - lefthand_file = 'icons/mob/inhands/equipment/briefcase_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/briefcase_righthand.dmi' - flags_1 = CONDUCT_1 - force = 8 - hitsound = "swing_hit" - throw_speed = 2 - throw_range = 4 - w_class = WEIGHT_CLASS_BULKY - attack_verb = list("bashed", "battered", "bludgeoned", "thrashed", "whacked") - resistance_flags = FLAMMABLE - max_integrity = 150 - var/folder_path = /obj/item/folder //this is the path of the folder that gets spawned in New() - -/obj/item/storage/briefcase/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_NORMAL - STR.max_combined_w_class = 21 - -/obj/item/storage/briefcase/PopulateContents() - new /obj/item/pen(src) - var/obj/item/folder/folder = new folder_path(src) - for(var/i in 1 to 6) - new /obj/item/paper(folder) - -/obj/item/storage/briefcase/lawyer - folder_path = /obj/item/folder/blue - -/obj/item/storage/briefcase/lawyer/PopulateContents() - new /obj/item/stamp/law(src) - ..() - -/obj/item/storage/briefcase/sniperbundle - desc = "Its label reads \"genuine hardened Captain leather\", but suspiciously has no other tags or branding. Smells like L'Air du Temps." - force = 10 - -/obj/item/storage/briefcase/sniperbundle/PopulateContents() - ..() // in case you need any paperwork done after your rampage - new /obj/item/gun/ballistic/automatic/sniper_rifle/syndicate(src) - new /obj/item/clothing/neck/tie/red(src) - new /obj/item/clothing/under/syndicate/sniper(src) - new /obj/item/ammo_box/magazine/sniper_rounds/soporific(src) - new /obj/item/ammo_box/magazine/sniper_rounds/soporific(src) - new /obj/item/suppressor/specialoffer(src) - +/obj/item/storage/briefcase + name = "briefcase" + desc = "It's made of AUTHENTIC faux-leather and has a price-tag still attached. Its owner must be a real professional." + icon_state = "briefcase" + lefthand_file = 'icons/mob/inhands/equipment/briefcase_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/briefcase_righthand.dmi' + flags_1 = CONDUCT_1 + force = 8 + hitsound = "swing_hit" + throw_speed = 2 + throw_range = 4 + w_class = WEIGHT_CLASS_BULKY + attack_verb = list("bashed", "battered", "bludgeoned", "thrashed", "whacked") + resistance_flags = FLAMMABLE + max_integrity = 150 + var/folder_path = /obj/item/folder //this is the path of the folder that gets spawned in New() + +/obj/item/storage/briefcase/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_NORMAL + STR.max_combined_w_class = 21 + +/obj/item/storage/briefcase/PopulateContents() + new /obj/item/pen(src) + var/obj/item/folder/folder = new folder_path(src) + for(var/i in 1 to 6) + new /obj/item/paper(folder) + +/obj/item/storage/briefcase/lawyer + folder_path = /obj/item/folder/blue + +/obj/item/storage/briefcase/lawyer/PopulateContents() + new /obj/item/stamp/law(src) + ..() + +/obj/item/storage/briefcase/sniperbundle + desc = "Its label reads \"genuine hardened Captain leather\", but suspiciously has no other tags or branding. Smells like L'Air du Temps." + force = 10 + +/obj/item/storage/briefcase/sniperbundle/PopulateContents() + ..() // in case you need any paperwork done after your rampage + new /obj/item/gun/ballistic/automatic/sniper_rifle/syndicate(src) + new /obj/item/clothing/neck/tie/red(src) + new /obj/item/clothing/under/syndicate/sniper(src) + new /obj/item/ammo_box/magazine/sniper_rounds/soporific(src) + new /obj/item/ammo_box/magazine/sniper_rounds/soporific(src) + new /obj/item/suppressor/specialoffer(src) + diff --git a/code/game/objects/items/storage/fancy.dm b/code/game/objects/items/storage/fancy.dm index 9086dc0ec5f4..bdd792db06cb 100644 --- a/code/game/objects/items/storage/fancy.dm +++ b/code/game/objects/items/storage/fancy.dm @@ -1,425 +1,425 @@ -/* - * The 'fancy' path is for objects like donut boxes that show how many items are in the storage item on the sprite itself - * .. Sorry for the shitty path name, I couldnt think of a better one. - * - * WARNING: var/icon_type is used for both examine text and sprite name. Please look at the procs below and adjust your sprite names accordingly - * TODO: Cigarette boxes should be ported to this standard - * - * Contains: - * Donut Box - * Egg Box - * Candle Box - * Cigarette Box - * Cigar Case - * Heart Shaped Box w/ Chocolates - */ - -/obj/item/storage/fancy - icon = 'icons/obj/food/containers.dmi' - resistance_flags = FLAMMABLE - var/icon_type = "donut" - var/spawn_type = null - var/fancy_open = FALSE - -/obj/item/storage/fancy/PopulateContents() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - for(var/i = 1 to STR.max_items) - new spawn_type(src) - -/obj/item/storage/fancy/update_icon_state() - if(fancy_open) - icon_state = "[icon_type]box[contents.len]" - else - icon_state = "[icon_type]box" - -/obj/item/storage/fancy/examine(mob/user) - . = ..() - if(fancy_open) - if(length(contents) == 1) - . += "There is one [icon_type] left." - else - . += "There are [contents.len <= 0 ? "no" : "[contents.len]"] [icon_type]s left." - -/obj/item/storage/fancy/attack_self(mob/user) - fancy_open = !fancy_open - update_icon() - . = ..() - -/obj/item/storage/fancy/Exited() - . = ..() - fancy_open = TRUE - update_icon() - -/obj/item/storage/fancy/Entered() - . = ..() - fancy_open = TRUE - update_icon() - -#define DONUT_INBOX_SPRITE_WIDTH 3 - -/* - * Donut Box - */ - -/obj/item/storage/fancy/donut_box - name = "donut box" - desc = "Mmm. Donuts." - icon = 'icons/obj/food/donuts.dmi' - icon_state = "donutbox_inner" - icon_type = "donut" - spawn_type = /obj/item/reagent_containers/food/snacks/donut - fancy_open = TRUE - appearance_flags = KEEP_TOGETHER - -/obj/item/storage/fancy/donut_box/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 6 - STR.set_holdable(list(/obj/item/reagent_containers/food/snacks/donut)) - -/obj/item/storage/fancy/donut_box/PopulateContents() - . = ..() - update_icon() - -/obj/item/storage/fancy/donut_box/update_icon_state() - if(fancy_open) - icon_state = "donutbox_inner" - else - icon_state = "donutbox" - -/obj/item/storage/fancy/donut_box/update_overlays() - . = ..() - - if (!fancy_open) - return - - var/donuts = 0 - - for (var/_donut in contents) - var/obj/item/reagent_containers/food/snacks/donut/donut = _donut - if (!istype(donut)) - continue - - . += image(icon = initial(icon), icon_state = donut.in_box_sprite(), pixel_x = donuts * DONUT_INBOX_SPRITE_WIDTH) - donuts += 1 - - . += image(icon = initial(icon), icon_state = "donutbox_top") - -#undef DONUT_INBOX_SPRITE_WIDTH - -/* - * Egg Box - */ - -/obj/item/storage/fancy/egg_box - icon = 'icons/obj/food/containers.dmi' - item_state = "eggbox" - icon_state = "eggbox" - icon_type = "egg" - lefthand_file = 'icons/mob/inhands/misc/food_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/food_righthand.dmi' - name = "egg box" - desc = "A carton for containing eggs." - spawn_type = /obj/item/reagent_containers/food/snacks/egg - -/obj/item/storage/fancy/egg_box/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 12 - STR.set_holdable(list(/obj/item/reagent_containers/food/snacks/egg)) - -/* - * Candle Box - */ - -/obj/item/storage/fancy/candle_box - name = "candle pack" - desc = "A pack of red candles." - icon = 'icons/obj/candle.dmi' - icon_state = "candlebox5" - icon_type = "candle" - item_state = "candlebox5" - throwforce = 2 - slot_flags = ITEM_SLOT_BELT - spawn_type = /obj/item/candle - fancy_open = TRUE - -/obj/item/storage/fancy/candle_box/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 5 - -/obj/item/storage/fancy/candle_box/attack_self(mob_user) - return - -//////////// -//CIG PACK// -//////////// -/obj/item/storage/fancy/cigarettes - name = "\improper Space Cigarettes packet" - desc = "The most popular brand of cigarettes, sponsors of the Space Olympics." - icon = 'icons/obj/cigarettes.dmi' - icon_state = "cig" - item_state = "cigpacket" - w_class = WEIGHT_CLASS_TINY - throwforce = 0 - slot_flags = ITEM_SLOT_BELT - icon_type = "cigarette" - spawn_type = /obj/item/clothing/mask/cigarette/space_cigarette - var/candy = FALSE //for cigarette overlay - custom_price = 75 - age_restricted = TRUE - -/obj/item/storage/fancy/cigarettes/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 6 - STR.set_holdable(list(/obj/item/clothing/mask/cigarette, /obj/item/lighter)) - -/obj/item/storage/fancy/cigarettes/examine(mob/user) - . = ..() - . += "Alt-click to extract contents." - -/obj/item/storage/fancy/cigarettes/AltClick(mob/living/carbon/user) - if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) - return - var/obj/item/clothing/mask/cigarette/W = locate(/obj/item/clothing/mask/cigarette) in contents - if(W && contents.len > 0) - SEND_SIGNAL(src, COMSIG_TRY_STORAGE_TAKE, W, user) - user.put_in_hands(W) - contents -= W - to_chat(user, "You take \a [W] out of the pack.") - else - to_chat(user, "There are no [icon_type]s left in the pack.") - -/obj/item/storage/fancy/cigarettes/update_icon_state() - if(fancy_open || !contents.len) - if(!contents.len) - icon_state = "[initial(icon_state)]_empty" - else - icon_state = initial(icon_state) - -/obj/item/storage/fancy/cigarettes/update_overlays() - . = ..() - if(fancy_open && contents.len) - . += "[icon_state]_open" - var/cig_position = 1 - for(var/C in contents) - var/mutable_appearance/inserted_overlay = mutable_appearance(icon) - - if(istype(C, /obj/item/lighter/greyscale)) - inserted_overlay.icon_state = "lighter_in" - else if(istype(C, /obj/item/lighter)) - inserted_overlay.icon_state = "zippo_in" - else if(candy) - inserted_overlay.icon_state = "candy" - else - inserted_overlay.icon_state = "cigarette" - - inserted_overlay.icon_state = "[inserted_overlay.icon_state]_[cig_position]" - . += inserted_overlay - cig_position++ - -/obj/item/storage/fancy/cigarettes/attack(mob/living/carbon/M as mob, mob/living/carbon/user as mob) - if(!ismob(M)) - return - var/obj/item/clothing/mask/cigarette/cig = locate(/obj/item/clothing/mask/cigarette) in contents - if(cig) - if(M == user && contents.len > 0 && !user.wear_mask) - var/obj/item/clothing/mask/cigarette/W = cig - SEND_SIGNAL(src, COMSIG_TRY_STORAGE_TAKE, W, M) - M.equip_to_slot_if_possible(W, ITEM_SLOT_MASK) - contents -= W - to_chat(user, "You take \a [W] out of the pack.") - else - ..() - else - to_chat(user, "There are no [icon_type]s left in the pack.") - -/obj/item/storage/fancy/cigarettes/dromedaryco - name = "\improper DromedaryCo packet" - desc = "A packet of six imported DromedaryCo cancer sticks. A label on the packaging reads, \"Wouldn't a slow death make a change?\"" - icon_state = "dromedary" - spawn_type = /obj/item/clothing/mask/cigarette/dromedary - -/obj/item/storage/fancy/cigarettes/cigpack_uplift - name = "\improper Uplift Smooth packet" - desc = "Your favorite brand, now menthol flavored." - icon_state = "uplift" - spawn_type = /obj/item/clothing/mask/cigarette/uplift - -/obj/item/storage/fancy/cigarettes/cigpack_robust - name = "\improper Robust packet" - desc = "Smoked by the robust." - icon_state = "robust" - spawn_type = /obj/item/clothing/mask/cigarette/robust - -/obj/item/storage/fancy/cigarettes/cigpack_robustgold - name = "\improper Robust Gold packet" - desc = "Smoked by the truly robust." - icon_state = "robustg" - spawn_type = /obj/item/clothing/mask/cigarette/robustgold - -/obj/item/storage/fancy/cigarettes/cigpack_carp - name = "\improper Carp Classic packet" - desc = "Since 2313." - icon_state = "carp" - spawn_type = /obj/item/clothing/mask/cigarette/carp - -/obj/item/storage/fancy/cigarettes/cigpack_syndicate - name = "cigarette packet" - desc = "An obscure brand of cigarettes." - icon_state = "syndie" - spawn_type = /obj/item/clothing/mask/cigarette/syndicate - -/obj/item/storage/fancy/cigarettes/cigpack_midori - name = "\improper Midori Tabako packet" - desc = "You can't understand the runes, but the packet smells funny." - icon_state = "midori" - spawn_type = /obj/item/clothing/mask/cigarette/rollie/nicotine - -/obj/item/storage/fancy/cigarettes/cigpack_candy - name = "\improper Timmy's First Candy Smokes packet" - desc = "Unsure about smoking? Want to bring your children safely into the family tradition? Look no more with this special packet! Includes 100%* Nicotine-Free candy cigarettes." - icon_state = "candy" - icon_type = "candy cigarette" - spawn_type = /obj/item/clothing/mask/cigarette/candy - candy = TRUE - age_restricted = FALSE - -/obj/item/storage/fancy/cigarettes/cigpack_candy/Initialize() - . = ..() - if(prob(7)) - spawn_type = /obj/item/clothing/mask/cigarette/candy/nicotine //uh oh! - -/obj/item/storage/fancy/cigarettes/cigpack_shadyjims - name = "\improper Shady Jim's Super Slims packet" - desc = "Is your weight slowing you down? Having trouble running away from gravitational singularities? Can't stop stuffing your mouth? Smoke Shady Jim's Super Slims and watch all that fat burn away. Guaranteed results!" - icon_state = "shadyjim" - spawn_type = /obj/item/clothing/mask/cigarette/shadyjims - -/obj/item/storage/fancy/cigarettes/cigpack_xeno - name = "\improper Xeno Filtered packet" - desc = "Loaded with 100% pure slime. And also nicotine." - icon_state = "slime" - spawn_type = /obj/item/clothing/mask/cigarette/xeno - -/obj/item/storage/fancy/cigarettes/cigpack_cannabis - name = "\improper Freak Brothers' Special packet" - desc = "A label on the packaging reads, \"Endorsed by Phineas, Freddy and Franklin.\"" - icon_state = "midori" - spawn_type = /obj/item/clothing/mask/cigarette/rollie/cannabis - -/obj/item/storage/fancy/cigarettes/cigpack_mindbreaker - name = "\improper Leary's Delight packet" - desc = "Banned in over 36 galaxies." - icon_state = "shadyjim" - spawn_type = /obj/item/clothing/mask/cigarette/rollie/mindbreaker - -/obj/item/storage/fancy/rollingpapers - name = "rolling paper pack" - desc = "A pack of Nanotrasen brand rolling papers." - w_class = WEIGHT_CLASS_TINY - icon = 'icons/obj/cigarettes.dmi' - icon_state = "cig_paper_pack" - ///The value in here has NOTHING to do with icons. It needs to be this for the proper examine. - icon_type = "rolling paper" - spawn_type = /obj/item/rollingpaper - custom_price = 25 - -/obj/item/storage/fancy/rollingpapers/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 10 - STR.set_holdable(list(/obj/item/rollingpaper)) - -///Overrides to do nothing because fancy boxes are fucking insane. -/obj/item/storage/fancy/rollingpapers/update_icon_state() - return - -/obj/item/storage/fancy/rollingpapers/update_overlays() - . = ..() - if(!contents.len) - . += "[icon_state]_empty" - -///////////// -//CIGAR BOX// -///////////// - -/obj/item/storage/fancy/cigarettes/cigars - name = "\improper premium cigar case" - desc = "A case of premium cigars. Very expensive." - icon = 'icons/obj/cigarettes.dmi' - icon_state = "cigarcase" - w_class = WEIGHT_CLASS_NORMAL - icon_type = "premium cigar" - spawn_type = /obj/item/clothing/mask/cigarette/cigar - -/obj/item/storage/fancy/cigarettes/cigars/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 5 - STR.set_holdable(list(/obj/item/clothing/mask/cigarette/cigar)) - -/obj/item/storage/fancy/cigarettes/cigars/update_icon_state() - if(fancy_open) - icon_state = "[initial(icon_state)]_open" - else - icon_state = "[initial(icon_state)]" - -/obj/item/storage/fancy/cigarettes/cigars/update_overlays() - . = ..() - if(fancy_open) - var/cigar_position = 1 //generate sprites for cigars in the box - for(var/obj/item/clothing/mask/cigarette/cigar/smokes in contents) - var/mutable_appearance/cigar_overlay = mutable_appearance(icon, "[smokes.icon_off]_[cigar_position]") - . += cigar_overlay - cigar_position++ - -/obj/item/storage/fancy/cigarettes/cigars/cohiba - name = "\improper Cohiba Robusto cigar case" - desc = "A case of imported Cohiba cigars, renowned for their strong flavor." - icon_state = "cohibacase" - spawn_type = /obj/item/clothing/mask/cigarette/cigar/cohiba - -/obj/item/storage/fancy/cigarettes/cigars/havana - name = "\improper premium Havanian cigar case" - desc = "A case of classy Havanian cigars." - icon_state = "cohibacase" - spawn_type = /obj/item/clothing/mask/cigarette/cigar/havana - -/* - * Heart Shaped Box w/ Chocolates - */ - -/obj/item/storage/fancy/heart_box - name = "heart-shaped box" - desc = "A heart-shaped box for holding tiny chocolates." - icon = 'icons/obj/food/containers.dmi' - item_state = "chocolatebox" - icon_state = "chocolatebox" - icon_type = "chocolate" - lefthand_file = 'icons/mob/inhands/misc/food_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/food_righthand.dmi' - spawn_type = /obj/item/reagent_containers/food/snacks/tinychocolate - -/obj/item/storage/fancy/heart_box/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 8 - STR.set_holdable(list(/obj/item/reagent_containers/food/snacks/tinychocolate)) - - -/obj/item/storage/fancy/nugget_box - name = "nugget box" - desc = "A cardboard box used for holding chicken nuggies." - icon = 'icons/obj/food/containers.dmi' - icon_state = "nuggetbox" - icon_type = "nugget" - spawn_type = /obj/item/reagent_containers/food/snacks/nugget - -/obj/item/storage/fancy/nugget_box/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 6 - STR.set_holdable(list(/obj/item/reagent_containers/food/snacks/nugget)) +/* + * The 'fancy' path is for objects like donut boxes that show how many items are in the storage item on the sprite itself + * .. Sorry for the shitty path name, I couldnt think of a better one. + * + * WARNING: var/icon_type is used for both examine text and sprite name. Please look at the procs below and adjust your sprite names accordingly + * TODO: Cigarette boxes should be ported to this standard + * + * Contains: + * Donut Box + * Egg Box + * Candle Box + * Cigarette Box + * Cigar Case + * Heart Shaped Box w/ Chocolates + */ + +/obj/item/storage/fancy + icon = 'icons/obj/food/containers.dmi' + resistance_flags = FLAMMABLE + var/icon_type = "donut" + var/spawn_type = null + var/fancy_open = FALSE + +/obj/item/storage/fancy/PopulateContents() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + for(var/i = 1 to STR.max_items) + new spawn_type(src) + +/obj/item/storage/fancy/update_icon_state() + if(fancy_open) + icon_state = "[icon_type]box[contents.len]" + else + icon_state = "[icon_type]box" + +/obj/item/storage/fancy/examine(mob/user) + . = ..() + if(fancy_open) + if(length(contents) == 1) + . += "There is one [icon_type] left." + else + . += "There are [contents.len <= 0 ? "no" : "[contents.len]"] [icon_type]s left." + +/obj/item/storage/fancy/attack_self(mob/user) + fancy_open = !fancy_open + update_icon() + . = ..() + +/obj/item/storage/fancy/Exited() + . = ..() + fancy_open = TRUE + update_icon() + +/obj/item/storage/fancy/Entered() + . = ..() + fancy_open = TRUE + update_icon() + +#define DONUT_INBOX_SPRITE_WIDTH 3 + +/* + * Donut Box + */ + +/obj/item/storage/fancy/donut_box + name = "donut box" + desc = "Mmm. Donuts." + icon = 'icons/obj/food/donuts.dmi' + icon_state = "donutbox_inner" + icon_type = "donut" + spawn_type = /obj/item/reagent_containers/food/snacks/donut + fancy_open = TRUE + appearance_flags = KEEP_TOGETHER + +/obj/item/storage/fancy/donut_box/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 6 + STR.set_holdable(list(/obj/item/reagent_containers/food/snacks/donut)) + +/obj/item/storage/fancy/donut_box/PopulateContents() + . = ..() + update_icon() + +/obj/item/storage/fancy/donut_box/update_icon_state() + if(fancy_open) + icon_state = "donutbox_inner" + else + icon_state = "donutbox" + +/obj/item/storage/fancy/donut_box/update_overlays() + . = ..() + + if (!fancy_open) + return + + var/donuts = 0 + + for (var/_donut in contents) + var/obj/item/reagent_containers/food/snacks/donut/donut = _donut + if (!istype(donut)) + continue + + . += image(icon = initial(icon), icon_state = donut.in_box_sprite(), pixel_x = donuts * DONUT_INBOX_SPRITE_WIDTH) + donuts += 1 + + . += image(icon = initial(icon), icon_state = "donutbox_top") + +#undef DONUT_INBOX_SPRITE_WIDTH + +/* + * Egg Box + */ + +/obj/item/storage/fancy/egg_box + icon = 'icons/obj/food/containers.dmi' + item_state = "eggbox" + icon_state = "eggbox" + icon_type = "egg" + lefthand_file = 'icons/mob/inhands/misc/food_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/food_righthand.dmi' + name = "egg box" + desc = "A carton for containing eggs." + spawn_type = /obj/item/reagent_containers/food/snacks/egg + +/obj/item/storage/fancy/egg_box/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 12 + STR.set_holdable(list(/obj/item/reagent_containers/food/snacks/egg)) + +/* + * Candle Box + */ + +/obj/item/storage/fancy/candle_box + name = "candle pack" + desc = "A pack of red candles." + icon = 'icons/obj/candle.dmi' + icon_state = "candlebox5" + icon_type = "candle" + item_state = "candlebox5" + throwforce = 2 + slot_flags = ITEM_SLOT_BELT + spawn_type = /obj/item/candle + fancy_open = TRUE + +/obj/item/storage/fancy/candle_box/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 5 + +/obj/item/storage/fancy/candle_box/attack_self(mob_user) + return + +//////////// +//CIG PACK// +//////////// +/obj/item/storage/fancy/cigarettes + name = "\improper Space Cigarettes packet" + desc = "The most popular brand of cigarettes, sponsors of the Space Olympics." + icon = 'icons/obj/cigarettes.dmi' + icon_state = "cig" + item_state = "cigpacket" + w_class = WEIGHT_CLASS_TINY + throwforce = 0 + slot_flags = ITEM_SLOT_BELT + icon_type = "cigarette" + spawn_type = /obj/item/clothing/mask/cigarette/space_cigarette + var/candy = FALSE //for cigarette overlay + custom_price = 75 + age_restricted = TRUE + +/obj/item/storage/fancy/cigarettes/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 6 + STR.set_holdable(list(/obj/item/clothing/mask/cigarette, /obj/item/lighter)) + +/obj/item/storage/fancy/cigarettes/examine(mob/user) + . = ..() + . += "Alt-click to extract contents." + +/obj/item/storage/fancy/cigarettes/AltClick(mob/living/carbon/user) + if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + return + var/obj/item/clothing/mask/cigarette/W = locate(/obj/item/clothing/mask/cigarette) in contents + if(W && contents.len > 0) + SEND_SIGNAL(src, COMSIG_TRY_STORAGE_TAKE, W, user) + user.put_in_hands(W) + contents -= W + to_chat(user, "You take \a [W] out of the pack.") + else + to_chat(user, "There are no [icon_type]s left in the pack.") + +/obj/item/storage/fancy/cigarettes/update_icon_state() + if(fancy_open || !contents.len) + if(!contents.len) + icon_state = "[initial(icon_state)]_empty" + else + icon_state = initial(icon_state) + +/obj/item/storage/fancy/cigarettes/update_overlays() + . = ..() + if(fancy_open && contents.len) + . += "[icon_state]_open" + var/cig_position = 1 + for(var/C in contents) + var/mutable_appearance/inserted_overlay = mutable_appearance(icon) + + if(istype(C, /obj/item/lighter/greyscale)) + inserted_overlay.icon_state = "lighter_in" + else if(istype(C, /obj/item/lighter)) + inserted_overlay.icon_state = "zippo_in" + else if(candy) + inserted_overlay.icon_state = "candy" + else + inserted_overlay.icon_state = "cigarette" + + inserted_overlay.icon_state = "[inserted_overlay.icon_state]_[cig_position]" + . += inserted_overlay + cig_position++ + +/obj/item/storage/fancy/cigarettes/attack(mob/living/carbon/M as mob, mob/living/carbon/user as mob) + if(!ismob(M)) + return + var/obj/item/clothing/mask/cigarette/cig = locate(/obj/item/clothing/mask/cigarette) in contents + if(cig) + if(M == user && contents.len > 0 && !user.wear_mask) + var/obj/item/clothing/mask/cigarette/W = cig + SEND_SIGNAL(src, COMSIG_TRY_STORAGE_TAKE, W, M) + M.equip_to_slot_if_possible(W, ITEM_SLOT_MASK) + contents -= W + to_chat(user, "You take \a [W] out of the pack.") + else + ..() + else + to_chat(user, "There are no [icon_type]s left in the pack.") + +/obj/item/storage/fancy/cigarettes/dromedaryco + name = "\improper DromedaryCo packet" + desc = "A packet of six imported DromedaryCo cancer sticks. A label on the packaging reads, \"Wouldn't a slow death make a change?\"" + icon_state = "dromedary" + spawn_type = /obj/item/clothing/mask/cigarette/dromedary + +/obj/item/storage/fancy/cigarettes/cigpack_uplift + name = "\improper Uplift Smooth packet" + desc = "Your favorite brand, now menthol flavored." + icon_state = "uplift" + spawn_type = /obj/item/clothing/mask/cigarette/uplift + +/obj/item/storage/fancy/cigarettes/cigpack_robust + name = "\improper Robust packet" + desc = "Smoked by the robust." + icon_state = "robust" + spawn_type = /obj/item/clothing/mask/cigarette/robust + +/obj/item/storage/fancy/cigarettes/cigpack_robustgold + name = "\improper Robust Gold packet" + desc = "Smoked by the truly robust." + icon_state = "robustg" + spawn_type = /obj/item/clothing/mask/cigarette/robustgold + +/obj/item/storage/fancy/cigarettes/cigpack_carp + name = "\improper Carp Classic packet" + desc = "Since 2313." + icon_state = "carp" + spawn_type = /obj/item/clothing/mask/cigarette/carp + +/obj/item/storage/fancy/cigarettes/cigpack_syndicate + name = "cigarette packet" + desc = "An obscure brand of cigarettes." + icon_state = "syndie" + spawn_type = /obj/item/clothing/mask/cigarette/syndicate + +/obj/item/storage/fancy/cigarettes/cigpack_midori + name = "\improper Midori Tabako packet" + desc = "You can't understand the runes, but the packet smells funny." + icon_state = "midori" + spawn_type = /obj/item/clothing/mask/cigarette/rollie/nicotine + +/obj/item/storage/fancy/cigarettes/cigpack_candy + name = "\improper Timmy's First Candy Smokes packet" + desc = "Unsure about smoking? Want to bring your children safely into the family tradition? Look no more with this special packet! Includes 100%* Nicotine-Free candy cigarettes." + icon_state = "candy" + icon_type = "candy cigarette" + spawn_type = /obj/item/clothing/mask/cigarette/candy + candy = TRUE + age_restricted = FALSE + +/obj/item/storage/fancy/cigarettes/cigpack_candy/Initialize() + . = ..() + if(prob(7)) + spawn_type = /obj/item/clothing/mask/cigarette/candy/nicotine //uh oh! + +/obj/item/storage/fancy/cigarettes/cigpack_shadyjims + name = "\improper Shady Jim's Super Slims packet" + desc = "Is your weight slowing you down? Having trouble running away from gravitational singularities? Can't stop stuffing your mouth? Smoke Shady Jim's Super Slims and watch all that fat burn away. Guaranteed results!" + icon_state = "shadyjim" + spawn_type = /obj/item/clothing/mask/cigarette/shadyjims + +/obj/item/storage/fancy/cigarettes/cigpack_xeno + name = "\improper Xeno Filtered packet" + desc = "Loaded with 100% pure slime. And also nicotine." + icon_state = "slime" + spawn_type = /obj/item/clothing/mask/cigarette/xeno + +/obj/item/storage/fancy/cigarettes/cigpack_cannabis + name = "\improper Freak Brothers' Special packet" + desc = "A label on the packaging reads, \"Endorsed by Phineas, Freddy and Franklin.\"" + icon_state = "midori" + spawn_type = /obj/item/clothing/mask/cigarette/rollie/cannabis + +/obj/item/storage/fancy/cigarettes/cigpack_mindbreaker + name = "\improper Leary's Delight packet" + desc = "Banned in over 36 galaxies." + icon_state = "shadyjim" + spawn_type = /obj/item/clothing/mask/cigarette/rollie/mindbreaker + +/obj/item/storage/fancy/rollingpapers + name = "rolling paper pack" + desc = "A pack of Nanotrasen brand rolling papers." + w_class = WEIGHT_CLASS_TINY + icon = 'icons/obj/cigarettes.dmi' + icon_state = "cig_paper_pack" + ///The value in here has NOTHING to do with icons. It needs to be this for the proper examine. + icon_type = "rolling paper" + spawn_type = /obj/item/rollingpaper + custom_price = 25 + +/obj/item/storage/fancy/rollingpapers/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 10 + STR.set_holdable(list(/obj/item/rollingpaper)) + +///Overrides to do nothing because fancy boxes are fucking insane. +/obj/item/storage/fancy/rollingpapers/update_icon_state() + return + +/obj/item/storage/fancy/rollingpapers/update_overlays() + . = ..() + if(!contents.len) + . += "[icon_state]_empty" + +///////////// +//CIGAR BOX// +///////////// + +/obj/item/storage/fancy/cigarettes/cigars + name = "\improper premium cigar case" + desc = "A case of premium cigars. Very expensive." + icon = 'icons/obj/cigarettes.dmi' + icon_state = "cigarcase" + w_class = WEIGHT_CLASS_NORMAL + icon_type = "premium cigar" + spawn_type = /obj/item/clothing/mask/cigarette/cigar + +/obj/item/storage/fancy/cigarettes/cigars/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 5 + STR.set_holdable(list(/obj/item/clothing/mask/cigarette/cigar)) + +/obj/item/storage/fancy/cigarettes/cigars/update_icon_state() + if(fancy_open) + icon_state = "[initial(icon_state)]_open" + else + icon_state = "[initial(icon_state)]" + +/obj/item/storage/fancy/cigarettes/cigars/update_overlays() + . = ..() + if(fancy_open) + var/cigar_position = 1 //generate sprites for cigars in the box + for(var/obj/item/clothing/mask/cigarette/cigar/smokes in contents) + var/mutable_appearance/cigar_overlay = mutable_appearance(icon, "[smokes.icon_off]_[cigar_position]") + . += cigar_overlay + cigar_position++ + +/obj/item/storage/fancy/cigarettes/cigars/cohiba + name = "\improper Cohiba Robusto cigar case" + desc = "A case of imported Cohiba cigars, renowned for their strong flavor." + icon_state = "cohibacase" + spawn_type = /obj/item/clothing/mask/cigarette/cigar/cohiba + +/obj/item/storage/fancy/cigarettes/cigars/havana + name = "\improper premium Havanian cigar case" + desc = "A case of classy Havanian cigars." + icon_state = "cohibacase" + spawn_type = /obj/item/clothing/mask/cigarette/cigar/havana + +/* + * Heart Shaped Box w/ Chocolates + */ + +/obj/item/storage/fancy/heart_box + name = "heart-shaped box" + desc = "A heart-shaped box for holding tiny chocolates." + icon = 'icons/obj/food/containers.dmi' + item_state = "chocolatebox" + icon_state = "chocolatebox" + icon_type = "chocolate" + lefthand_file = 'icons/mob/inhands/misc/food_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/food_righthand.dmi' + spawn_type = /obj/item/reagent_containers/food/snacks/tinychocolate + +/obj/item/storage/fancy/heart_box/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 8 + STR.set_holdable(list(/obj/item/reagent_containers/food/snacks/tinychocolate)) + + +/obj/item/storage/fancy/nugget_box + name = "nugget box" + desc = "A cardboard box used for holding chicken nuggies." + icon = 'icons/obj/food/containers.dmi' + icon_state = "nuggetbox" + icon_type = "nugget" + spawn_type = /obj/item/reagent_containers/food/snacks/nugget + +/obj/item/storage/fancy/nugget_box/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 6 + STR.set_holdable(list(/obj/item/reagent_containers/food/snacks/nugget)) diff --git a/code/game/objects/items/storage/firstaid.dm b/code/game/objects/items/storage/firstaid.dm index 72632db9cbf0..01503e8015a0 100644 --- a/code/game/objects/items/storage/firstaid.dm +++ b/code/game/objects/items/storage/firstaid.dm @@ -1,502 +1,502 @@ -/* First aid storage - * Contains: - * First Aid Kits - * Pill Bottles - * Dice Pack (in a pill bottle) - */ - -/* - * First Aid Kits - */ - -/* WaspStation Edit - In Modularized File - -/obj/item/storage/firstaid - name = "first-aid kit" - desc = "It's an emergency medical kit for those serious boo-boos." - icon_state = "firstaid" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - throw_speed = 3 - throw_range = 7 - var/empty = FALSE - var/damagetype_healed //defines damage type of the medkit. General ones stay null. Used for medibot healing bonuses - -/obj/item/storage/firstaid/regular - icon_state = "firstaid" - desc = "A first aid kit with the ability to heal common types of injuries." - -/obj/item/storage/firstaid/regular/suicide_act(mob/living/carbon/user) - user.visible_message("[user] begins giving [user.p_them()]self aids with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return BRUTELOSS - -/obj/item/storage/firstaid/regular/PopulateContents() - if(empty) - return - var/static/items_inside = list( - /obj/item/stack/medical/gauze = 1, - /obj/item/stack/medical/suture = 2, - /obj/item/stack/medical/mesh = 2, - /obj/item/reagent_containers/hypospray/medipen = 1) - generate_items_inside(items_inside,src) - -/obj/item/storage/firstaid/medical - name = "medical aid kit" - icon_state = "firstaid_surgery" - item_state = "firstaid" - desc = "A high capacity aid kit for doctors, full of medical supplies and basic surgical equipment" - -/obj/item/storage/firstaid/medical/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_NORMAL //holds the same equipment as a medibelt - STR.max_items = 12 - STR.max_combined_w_class = 24 - STR.set_holdable(list( - /obj/item/healthanalyzer, - /obj/item/dnainjector, - /obj/item/reagent_containers/dropper, - /obj/item/reagent_containers/glass/beaker, - /obj/item/reagent_containers/glass/bottle, - /obj/item/reagent_containers/pill, - /obj/item/reagent_containers/syringe, - /obj/item/reagent_containers/medigel, - /obj/item/lighter, - /obj/item/storage/fancy/cigarettes, - /obj/item/storage/pill_bottle, - /obj/item/stack/medical, - /obj/item/flashlight/pen, - /obj/item/extinguisher/mini, - /obj/item/reagent_containers/hypospray, - /obj/item/sensor_device, - /obj/item/radio, - /obj/item/clothing/gloves/, - /obj/item/lazarus_injector, - /obj/item/bikehorn/rubberducky, - /obj/item/clothing/mask/surgical, - /obj/item/clothing/mask/breath, - /obj/item/clothing/mask/breath/medical, - /obj/item/scalpel, - /obj/item/circular_saw, - /obj/item/surgicaldrill, - /obj/item/retractor, - /obj/item/cautery, - /obj/item/hemostat, - /obj/item/geiger_counter, - /obj/item/clothing/neck/stethoscope, - /obj/item/stamp, - /obj/item/clothing/glasses, - /obj/item/wrench/medical, - /obj/item/clothing/mask/muzzle, - /obj/item/storage/bag/chemistry, - /obj/item/storage/bag/bio, - /obj/item/reagent_containers/blood, - /obj/item/tank/internals/emergency_oxygen, - /obj/item/gun/syringe/syndicate, - /obj/item/implantcase, - /obj/item/implant, - /obj/item/implanter, - /obj/item/pinpointer/crew, - /obj/item/holosign_creator/medical - )) - -/obj/item/storage/firstaid/medical/PopulateContents() - if(empty) - return - var/static/items_inside = list( - /obj/item/stack/medical/gauze = 1, - /obj/item/stack/medical/suture = 2, - /obj/item/stack/medical/mesh = 2, - /obj/item/reagent_containers/hypospray/medipen = 1, - /obj/item/scalpel = 1, - /obj/item/hemostat = 1, - /obj/item/cautery = 1, - /obj/item/healthanalyzer = 1) - generate_items_inside(items_inside,src) - -/obj/item/storage/firstaid/ancient - icon_state = "oldfirstaid" - desc = "A first aid kit with the ability to heal common types of injuries." - -/obj/item/storage/firstaid/ancient/PopulateContents() - if(empty) - return - var/static/items_inside = list( - /obj/item/stack/medical/gauze = 1, - /obj/item/stack/medical/bruise_pack = 3, - /obj/item/stack/medical/ointment= 3) - generate_items_inside(items_inside,src) - -/obj/item/storage/firstaid/ancient/heirloom - desc = "A first aid kit with the ability to heal common types of injuries. You start thinking of the good old days just by looking at it." - empty = TRUE // long since been ransacked by hungry powergaming assistants breaking into med storage - -/obj/item/storage/firstaid/fire - name = "burn treatment kit" - desc = "A specialized medical kit for when the toxins lab -spontaneously- burns down." - icon_state = "ointment" - item_state = "firstaid-ointment" - damagetype_healed = BURN - -/obj/item/storage/firstaid/fire/suicide_act(mob/living/carbon/user) - user.visible_message("[user] begins rubbing \the [src] against [user.p_them()]self! It looks like [user.p_theyre()] trying to start a fire!") - return FIRELOSS - -/obj/item/storage/firstaid/fire/Initialize(mapload) - . = ..() - icon_state = pick("ointment","firefirstaid") - -/obj/item/storage/firstaid/fire/PopulateContents() - if(empty) - return - var/static/items_inside = list( - /obj/item/reagent_containers/pill/patch/aiuri = 3, - /obj/item/reagent_containers/spray/hercuri = 1, - /obj/item/reagent_containers/hypospray/medipen/oxandrolone = 1, - /obj/item/reagent_containers/hypospray/medipen = 1) - generate_items_inside(items_inside,src) - -/obj/item/storage/firstaid/toxin - name = "toxin treatment kit" - desc = "Used to treat toxic blood content and radiation poisoning." - icon_state = "antitoxin" - item_state = "firstaid-toxin" - damagetype_healed = TOX - -/obj/item/storage/firstaid/toxin/suicide_act(mob/living/carbon/user) - user.visible_message("[user] begins licking the lead paint off \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return TOXLOSS - -/obj/item/storage/firstaid/toxin/Initialize(mapload) - . = ..() - icon_state = pick("antitoxin","antitoxfirstaid","antitoxfirstaid2") - -/obj/item/storage/firstaid/toxin/PopulateContents() - if(empty) - return - var/static/items_inside = list( - /obj/item/storage/pill_bottle/charcoal/less = 1, - /obj/item/reagent_containers/syringe/thializid = 3, - /obj/item/storage/pill_bottle/potassiodide = 1, - /obj/item/reagent_containers/hypospray/medipen/penacid = 1) - generate_items_inside(items_inside,src) - -/obj/item/storage/firstaid/o2 - name = "oxygen deprivation treatment kit" - desc = "A box full of oxygen goodies." - icon_state = "o2" - item_state = "firstaid-o2" - damagetype_healed = OXY - -/obj/item/storage/firstaid/o2/suicide_act(mob/living/carbon/user) - user.visible_message("[user] begins hitting [user.p_their()] neck with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return OXYLOSS - -/obj/item/storage/firstaid/o2/Initialize(mapload) - . = ..() - icon_state = pick("o2","o2second") - -/obj/item/storage/firstaid/o2/PopulateContents() - if(empty) - return - var/static/items_inside = list( - /obj/item/reagent_containers/syringe/perfluorodecalin = 3, - /obj/item/reagent_containers/hypospray/medipen/salbutamol = 1, - /obj/item/reagent_containers/hypospray/medipen = 1, - /obj/item/storage/pill_bottle/iron = 1) - generate_items_inside(items_inside,src) - -/obj/item/storage/firstaid/brute - name = "brute trauma treatment kit" - desc = "A first aid kit for when you get toolboxed." - icon_state = "brute" - item_state = "firstaid-brute" - damagetype_healed = BRUTE - -/obj/item/storage/firstaid/brute/suicide_act(mob/living/carbon/user) - user.visible_message("[user] begins beating [user.p_them()]self over the head with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return BRUTELOSS - -/obj/item/storage/firstaid/brute/Initialize(mapload) - . = ..() - icon_state = pick("brute","brute2") - -/obj/item/storage/firstaid/brute/PopulateContents() - if(empty) - return - var/static/items_inside = list( - /obj/item/reagent_containers/pill/patch/styptic = 3, - /obj/item/stack/medical/gauze = 1, - /obj/item/storage/pill_bottle/C2/probital = 1, - /obj/item/reagent_containers/hypospray/medipen/salacid = 1) - generate_items_inside(items_inside,src) - -/obj/item/storage/firstaid/advanced - name = "advanced first aid kit" - desc = "An advanced kit to help deal with advanced wounds." - icon_state = "radfirstaid" - item_state = "firstaid-rad" - custom_premium_price = 1100 - -/obj/item/storage/firstaid/advanced/PopulateContents() - if(empty) - return - var/static/items_inside = list( - /obj/item/reagent_containers/pill/patch/synthflesh = 3, - /obj/item/reagent_containers/hypospray/medipen/atropine = 2, - /obj/item/stack/medical/gauze = 1, - /obj/item/storage/pill_bottle/penacid = 1) - generate_items_inside(items_inside,src) - -/obj/item/storage/firstaid/tactical - name = "combat medical kit" - desc = "I hope you've got insurance." - icon_state = "bezerk" - -/obj/item/storage/firstaid/tactical/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_NORMAL - -/obj/item/storage/firstaid/tactical/PopulateContents() - if(empty) - return - new /obj/item/stack/medical/gauze(src) - new /obj/item/defibrillator/compact/combat/loaded(src) - new /obj/item/reagent_containers/hypospray/combat(src) - new /obj/item/reagent_containers/pill/patch/styptic(src) - new /obj/item/reagent_containers/pill/patch/styptic(src) - new /obj/item/reagent_containers/pill/patch/silver_sulf(src) - new /obj/item/reagent_containers/pill/patch/silver_sulf(src) - new /obj/item/clothing/glasses/hud/health/night(src) - -//medibot assembly -/obj/item/storage/firstaid/attackby(obj/item/bodypart/S, mob/user, params) - if((!istype(S, /obj/item/bodypart/l_arm/robot)) && (!istype(S, /obj/item/bodypart/r_arm/robot))) - return ..() - - //Making a medibot! - if(contents.len >= 1) - to_chat(user, "You need to empty [src] out first!") - return - - var/obj/item/bot_assembly/medbot/A = new - if(istype(src, /obj/item/storage/firstaid/fire)) - A.set_skin("ointment") - else if(istype(src, /obj/item/storage/firstaid/toxin)) - A.set_skin("tox") - else if(istype(src, /obj/item/storage/firstaid/o2)) - A.set_skin("o2") - else if(istype(src, /obj/item/storage/firstaid/brute)) - A.set_skin("brute") - user.put_in_hands(A) - to_chat(user, "You add [S] to [src].") - A.robot_arm = S.type - A.firstaid = type - qdel(S) - qdel(src) - -/* - * Pill Bottles - */ - -/obj/item/storage/pill_bottle - name = "pill bottle" - desc = "It's an airtight container for storing medication." - icon_state = "pill_canister" - icon = 'icons/obj/chemical.dmi' - item_state = "contsolid" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - w_class = WEIGHT_CLASS_SMALL - -/obj/item/storage/pill_bottle/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.allow_quick_gather = TRUE - STR.click_gather = TRUE - STR.set_holdable(list(/obj/item/reagent_containers/pill, /obj/item/dice)) - -/obj/item/storage/pill_bottle/suicide_act(mob/user) - user.visible_message("[user] is trying to get the cap off [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return (TOXLOSS) - -/obj/item/storage/pill_bottle/charcoal - name = "bottle of charcoal pills" - desc = "Contains pills used to counter toxins." - -/obj/item/storage/pill_bottle/charcoal/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/pill/charcoal(src) - -/obj/item/storage/pill_bottle/charcoal/less - -/obj/item/storage/pill_bottle/charcoal/less/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/reagent_containers/pill/charcoal(src) - -/obj/item/storage/pill_bottle/epinephrine - name = "bottle of epinephrine pills" - desc = "Contains pills used to stabilize patients." - -/obj/item/storage/pill_bottle/epinephrine/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/pill/epinephrine(src) - -/obj/item/storage/pill_bottle/mutadone - name = "bottle of mutadone pills" - desc = "Contains pills used to treat genetic abnormalities." - -/obj/item/storage/pill_bottle/mutadone/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/pill/mutadone(src) - -/obj/item/storage/pill_bottle/potassiodide - name = "bottle of potassium iodide pills" - desc = "Contains pills used to reduce radiation damage." - -/obj/item/storage/pill_bottle/potassiodide/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/reagent_containers/pill/potassiodide(src) - -/obj/item/storage/pill_bottle/C2/probital - name = "bottle of probital pills" - desc = "Contains pills used to treat brute damage.The tag in the bottle states 'Eat before ingesting, may cause fatigue'." - -/obj/item/storage/pill_bottle/C2/probital/PopulateContents() - for(var/i in 1 to 4) - new /obj/item/reagent_containers/pill/C2/probital(src) - -/obj/item/storage/pill_bottle/iron - name = "bottle of iron pills" - desc = "Contains pills used to reduce blood loss slowly.The tag in the bottle states 'Only take one each five minutes'." - -/obj/item/storage/pill_bottle/iron/PopulateContents() - for(var/i in 1 to 4) - new /obj/item/reagent_containers/pill/iron(src) - -/obj/item/storage/pill_bottle/mannitol - name = "bottle of mannitol pills" - desc = "Contains pills used to treat brain damage." - -/obj/item/storage/pill_bottle/mannitol/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/pill/mannitol(src) - -/obj/item/storage/pill_bottle/stimulant - name = "bottle of stimulant pills" - desc = "Guaranteed to give you that extra burst of energy during a long shift!" - -/obj/item/storage/pill_bottle/stimulant/PopulateContents() - for(var/i in 1 to 5) - new /obj/item/reagent_containers/pill/stimulant(src) - -/obj/item/storage/pill_bottle/mining - name = "bottle of patches" - desc = "Contains patches used to treat brute and burn damage." - -/obj/item/storage/pill_bottle/mining/PopulateContents() - new /obj/item/reagent_containers/pill/patch/silver_sulf(src) - for(var/i in 1 to 3) - new /obj/item/reagent_containers/pill/patch/styptic(src) - -/obj/item/storage/pill_bottle/zoom - name = "suspicious pill bottle" - desc = "The label is pretty old and almost unreadable, you recognize some chemical compounds." - -/obj/item/storage/pill_bottle/zoom/PopulateContents() - for(var/i in 1 to 5) - new /obj/item/reagent_containers/pill/zoom(src) - -/obj/item/storage/pill_bottle/happy - name = "suspicious pill bottle" - desc = "There is a smiley on the top." - -/obj/item/storage/pill_bottle/happy/PopulateContents() - for(var/i in 1 to 5) - new /obj/item/reagent_containers/pill/happy(src) - -/obj/item/storage/pill_bottle/lsd - name = "suspicious pill bottle" - desc = "There is a crude drawing which could be either a mushroom, or a deformed moon." - -/obj/item/storage/pill_bottle/lsd/PopulateContents() - for(var/i in 1 to 5) - new /obj/item/reagent_containers/pill/lsd(src) - -/obj/item/storage/pill_bottle/aranesp - name = "suspicious pill bottle" - desc = "The label has 'fuck disablers' hastily scrawled in black marker." - -/obj/item/storage/pill_bottle/aranesp/PopulateContents() - for(var/i in 1 to 5) - new /obj/item/reagent_containers/pill/aranesp(src) - -/obj/item/storage/pill_bottle/psicodine - name = "bottle of psicodine pills" - desc = "Contains pills used to treat mental distress and traumas." - -/obj/item/storage/pill_bottle/psicodine/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/pill/psicodine(src) - -/obj/item/storage/pill_bottle/penacid - name = "bottle of pentetic acid pills" - desc = "Contains pills to expunge radiation and toxins." - -/obj/item/storage/pill_bottle/penacid/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/reagent_containers/pill/penacid(src) - - -/obj/item/storage/pill_bottle/neurine - name = "bottle of neurine pills" - desc = "Contains pills to treat non-severe mental traumas." - -/obj/item/storage/pill_bottle/neurine/PopulateContents() - for(var/i in 1 to 5) - new /obj/item/reagent_containers/pill/neurine(src) - -/obj/item/storage/pill_bottle/floorpill - name = "bottle of floorpills" - desc = "An old pill bottle. It smells musty." - -/obj/item/storage/pill_bottle/floorpill/Initialize() - . = ..() - var/obj/item/reagent_containers/pill/P = locate() in src - name = "bottle of [P.name]s" - -/obj/item/storage/pill_bottle/floorpill/PopulateContents() - for(var/i in 1 to rand(1,7)) - new /obj/item/reagent_containers/pill/floorpill(src) - -/obj/item/storage/pill_bottle/floorpill/full/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/pill/floorpill(src) - -///////////////////////////////////////// Psychologist inventory pillbottles -/obj/item/storage/pill_bottle/happinesspsych - name = "happiness pills" - desc = "Contains pills used as a last resort means to temporarily stabilize depression and anxiety. WARNING: side effects may include slurred speech, drooling, and severe addiction." - -/obj/item/storage/pill_bottle/happinesspsych/PopulateContents() - for(var/i in 1 to 5) - new /obj/item/reagent_containers/pill/happinesspsych(src) - -/obj/item/storage/pill_bottle/lsdpsych - name = "mindbreaker toxin pills" - desc = "!FOR THERAPEUTIC USE ONLY! Contains pills used to alleviate the symptoms of Reality Dissociation Syndrome." - -/obj/item/storage/pill_bottle/lsdpsych/PopulateContents() - for(var/i in 1 to 5) - new /obj/item/reagent_containers/pill/lsdpsych(src) - -/obj/item/storage/pill_bottle/paxpsych - name = "pax pills" - desc = "Contains pills used to temporarily pacify patients that are deemed a harm to themselves or others." - -/obj/item/storage/pill_bottle/paxpsych/PopulateContents() - for(var/i in 1 to 5) - new /obj/item/reagent_containers/pill/paxpsych(src) - -WaspStation End */ +/* First aid storage + * Contains: + * First Aid Kits + * Pill Bottles + * Dice Pack (in a pill bottle) + */ + +/* + * First Aid Kits + */ + +/* WaspStation Edit - In Modularized File + +/obj/item/storage/firstaid + name = "first-aid kit" + desc = "It's an emergency medical kit for those serious boo-boos." + icon_state = "firstaid" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + throw_speed = 3 + throw_range = 7 + var/empty = FALSE + var/damagetype_healed //defines damage type of the medkit. General ones stay null. Used for medibot healing bonuses + +/obj/item/storage/firstaid/regular + icon_state = "firstaid" + desc = "A first aid kit with the ability to heal common types of injuries." + +/obj/item/storage/firstaid/regular/suicide_act(mob/living/carbon/user) + user.visible_message("[user] begins giving [user.p_them()]self aids with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return BRUTELOSS + +/obj/item/storage/firstaid/regular/PopulateContents() + if(empty) + return + var/static/items_inside = list( + /obj/item/stack/medical/gauze = 1, + /obj/item/stack/medical/suture = 2, + /obj/item/stack/medical/mesh = 2, + /obj/item/reagent_containers/hypospray/medipen = 1) + generate_items_inside(items_inside,src) + +/obj/item/storage/firstaid/medical + name = "medical aid kit" + icon_state = "firstaid_surgery" + item_state = "firstaid" + desc = "A high capacity aid kit for doctors, full of medical supplies and basic surgical equipment" + +/obj/item/storage/firstaid/medical/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_NORMAL //holds the same equipment as a medibelt + STR.max_items = 12 + STR.max_combined_w_class = 24 + STR.set_holdable(list( + /obj/item/healthanalyzer, + /obj/item/dnainjector, + /obj/item/reagent_containers/dropper, + /obj/item/reagent_containers/glass/beaker, + /obj/item/reagent_containers/glass/bottle, + /obj/item/reagent_containers/pill, + /obj/item/reagent_containers/syringe, + /obj/item/reagent_containers/medigel, + /obj/item/lighter, + /obj/item/storage/fancy/cigarettes, + /obj/item/storage/pill_bottle, + /obj/item/stack/medical, + /obj/item/flashlight/pen, + /obj/item/extinguisher/mini, + /obj/item/reagent_containers/hypospray, + /obj/item/sensor_device, + /obj/item/radio, + /obj/item/clothing/gloves/, + /obj/item/lazarus_injector, + /obj/item/bikehorn/rubberducky, + /obj/item/clothing/mask/surgical, + /obj/item/clothing/mask/breath, + /obj/item/clothing/mask/breath/medical, + /obj/item/scalpel, + /obj/item/circular_saw, + /obj/item/surgicaldrill, + /obj/item/retractor, + /obj/item/cautery, + /obj/item/hemostat, + /obj/item/geiger_counter, + /obj/item/clothing/neck/stethoscope, + /obj/item/stamp, + /obj/item/clothing/glasses, + /obj/item/wrench/medical, + /obj/item/clothing/mask/muzzle, + /obj/item/storage/bag/chemistry, + /obj/item/storage/bag/bio, + /obj/item/reagent_containers/blood, + /obj/item/tank/internals/emergency_oxygen, + /obj/item/gun/syringe/syndicate, + /obj/item/implantcase, + /obj/item/implant, + /obj/item/implanter, + /obj/item/pinpointer/crew, + /obj/item/holosign_creator/medical + )) + +/obj/item/storage/firstaid/medical/PopulateContents() + if(empty) + return + var/static/items_inside = list( + /obj/item/stack/medical/gauze = 1, + /obj/item/stack/medical/suture = 2, + /obj/item/stack/medical/mesh = 2, + /obj/item/reagent_containers/hypospray/medipen = 1, + /obj/item/scalpel = 1, + /obj/item/hemostat = 1, + /obj/item/cautery = 1, + /obj/item/healthanalyzer = 1) + generate_items_inside(items_inside,src) + +/obj/item/storage/firstaid/ancient + icon_state = "oldfirstaid" + desc = "A first aid kit with the ability to heal common types of injuries." + +/obj/item/storage/firstaid/ancient/PopulateContents() + if(empty) + return + var/static/items_inside = list( + /obj/item/stack/medical/gauze = 1, + /obj/item/stack/medical/bruise_pack = 3, + /obj/item/stack/medical/ointment= 3) + generate_items_inside(items_inside,src) + +/obj/item/storage/firstaid/ancient/heirloom + desc = "A first aid kit with the ability to heal common types of injuries. You start thinking of the good old days just by looking at it." + empty = TRUE // long since been ransacked by hungry powergaming assistants breaking into med storage + +/obj/item/storage/firstaid/fire + name = "burn treatment kit" + desc = "A specialized medical kit for when the toxins lab -spontaneously- burns down." + icon_state = "ointment" + item_state = "firstaid-ointment" + damagetype_healed = BURN + +/obj/item/storage/firstaid/fire/suicide_act(mob/living/carbon/user) + user.visible_message("[user] begins rubbing \the [src] against [user.p_them()]self! It looks like [user.p_theyre()] trying to start a fire!") + return FIRELOSS + +/obj/item/storage/firstaid/fire/Initialize(mapload) + . = ..() + icon_state = pick("ointment","firefirstaid") + +/obj/item/storage/firstaid/fire/PopulateContents() + if(empty) + return + var/static/items_inside = list( + /obj/item/reagent_containers/pill/patch/aiuri = 3, + /obj/item/reagent_containers/spray/hercuri = 1, + /obj/item/reagent_containers/hypospray/medipen/oxandrolone = 1, + /obj/item/reagent_containers/hypospray/medipen = 1) + generate_items_inside(items_inside,src) + +/obj/item/storage/firstaid/toxin + name = "toxin treatment kit" + desc = "Used to treat toxic blood content and radiation poisoning." + icon_state = "antitoxin" + item_state = "firstaid-toxin" + damagetype_healed = TOX + +/obj/item/storage/firstaid/toxin/suicide_act(mob/living/carbon/user) + user.visible_message("[user] begins licking the lead paint off \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return TOXLOSS + +/obj/item/storage/firstaid/toxin/Initialize(mapload) + . = ..() + icon_state = pick("antitoxin","antitoxfirstaid","antitoxfirstaid2") + +/obj/item/storage/firstaid/toxin/PopulateContents() + if(empty) + return + var/static/items_inside = list( + /obj/item/storage/pill_bottle/charcoal/less = 1, + /obj/item/reagent_containers/syringe/thializid = 3, + /obj/item/storage/pill_bottle/potassiodide = 1, + /obj/item/reagent_containers/hypospray/medipen/penacid = 1) + generate_items_inside(items_inside,src) + +/obj/item/storage/firstaid/o2 + name = "oxygen deprivation treatment kit" + desc = "A box full of oxygen goodies." + icon_state = "o2" + item_state = "firstaid-o2" + damagetype_healed = OXY + +/obj/item/storage/firstaid/o2/suicide_act(mob/living/carbon/user) + user.visible_message("[user] begins hitting [user.p_their()] neck with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return OXYLOSS + +/obj/item/storage/firstaid/o2/Initialize(mapload) + . = ..() + icon_state = pick("o2","o2second") + +/obj/item/storage/firstaid/o2/PopulateContents() + if(empty) + return + var/static/items_inside = list( + /obj/item/reagent_containers/syringe/perfluorodecalin = 3, + /obj/item/reagent_containers/hypospray/medipen/salbutamol = 1, + /obj/item/reagent_containers/hypospray/medipen = 1, + /obj/item/storage/pill_bottle/iron = 1) + generate_items_inside(items_inside,src) + +/obj/item/storage/firstaid/brute + name = "brute trauma treatment kit" + desc = "A first aid kit for when you get toolboxed." + icon_state = "brute" + item_state = "firstaid-brute" + damagetype_healed = BRUTE + +/obj/item/storage/firstaid/brute/suicide_act(mob/living/carbon/user) + user.visible_message("[user] begins beating [user.p_them()]self over the head with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return BRUTELOSS + +/obj/item/storage/firstaid/brute/Initialize(mapload) + . = ..() + icon_state = pick("brute","brute2") + +/obj/item/storage/firstaid/brute/PopulateContents() + if(empty) + return + var/static/items_inside = list( + /obj/item/reagent_containers/pill/patch/styptic = 3, + /obj/item/stack/medical/gauze = 1, + /obj/item/storage/pill_bottle/C2/probital = 1, + /obj/item/reagent_containers/hypospray/medipen/salacid = 1) + generate_items_inside(items_inside,src) + +/obj/item/storage/firstaid/advanced + name = "advanced first aid kit" + desc = "An advanced kit to help deal with advanced wounds." + icon_state = "radfirstaid" + item_state = "firstaid-rad" + custom_premium_price = 1100 + +/obj/item/storage/firstaid/advanced/PopulateContents() + if(empty) + return + var/static/items_inside = list( + /obj/item/reagent_containers/pill/patch/synthflesh = 3, + /obj/item/reagent_containers/hypospray/medipen/atropine = 2, + /obj/item/stack/medical/gauze = 1, + /obj/item/storage/pill_bottle/penacid = 1) + generate_items_inside(items_inside,src) + +/obj/item/storage/firstaid/tactical + name = "combat medical kit" + desc = "I hope you've got insurance." + icon_state = "bezerk" + +/obj/item/storage/firstaid/tactical/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_NORMAL + +/obj/item/storage/firstaid/tactical/PopulateContents() + if(empty) + return + new /obj/item/stack/medical/gauze(src) + new /obj/item/defibrillator/compact/combat/loaded(src) + new /obj/item/reagent_containers/hypospray/combat(src) + new /obj/item/reagent_containers/pill/patch/styptic(src) + new /obj/item/reagent_containers/pill/patch/styptic(src) + new /obj/item/reagent_containers/pill/patch/silver_sulf(src) + new /obj/item/reagent_containers/pill/patch/silver_sulf(src) + new /obj/item/clothing/glasses/hud/health/night(src) + +//medibot assembly +/obj/item/storage/firstaid/attackby(obj/item/bodypart/S, mob/user, params) + if((!istype(S, /obj/item/bodypart/l_arm/robot)) && (!istype(S, /obj/item/bodypart/r_arm/robot))) + return ..() + + //Making a medibot! + if(contents.len >= 1) + to_chat(user, "You need to empty [src] out first!") + return + + var/obj/item/bot_assembly/medbot/A = new + if(istype(src, /obj/item/storage/firstaid/fire)) + A.set_skin("ointment") + else if(istype(src, /obj/item/storage/firstaid/toxin)) + A.set_skin("tox") + else if(istype(src, /obj/item/storage/firstaid/o2)) + A.set_skin("o2") + else if(istype(src, /obj/item/storage/firstaid/brute)) + A.set_skin("brute") + user.put_in_hands(A) + to_chat(user, "You add [S] to [src].") + A.robot_arm = S.type + A.firstaid = type + qdel(S) + qdel(src) + +/* + * Pill Bottles + */ + +/obj/item/storage/pill_bottle + name = "pill bottle" + desc = "It's an airtight container for storing medication." + icon_state = "pill_canister" + icon = 'icons/obj/chemical.dmi' + item_state = "contsolid" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + w_class = WEIGHT_CLASS_SMALL + +/obj/item/storage/pill_bottle/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.allow_quick_gather = TRUE + STR.click_gather = TRUE + STR.set_holdable(list(/obj/item/reagent_containers/pill, /obj/item/dice)) + +/obj/item/storage/pill_bottle/suicide_act(mob/user) + user.visible_message("[user] is trying to get the cap off [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return (TOXLOSS) + +/obj/item/storage/pill_bottle/charcoal + name = "bottle of charcoal pills" + desc = "Contains pills used to counter toxins." + +/obj/item/storage/pill_bottle/charcoal/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/pill/charcoal(src) + +/obj/item/storage/pill_bottle/charcoal/less + +/obj/item/storage/pill_bottle/charcoal/less/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/reagent_containers/pill/charcoal(src) + +/obj/item/storage/pill_bottle/epinephrine + name = "bottle of epinephrine pills" + desc = "Contains pills used to stabilize patients." + +/obj/item/storage/pill_bottle/epinephrine/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/pill/epinephrine(src) + +/obj/item/storage/pill_bottle/mutadone + name = "bottle of mutadone pills" + desc = "Contains pills used to treat genetic abnormalities." + +/obj/item/storage/pill_bottle/mutadone/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/pill/mutadone(src) + +/obj/item/storage/pill_bottle/potassiodide + name = "bottle of potassium iodide pills" + desc = "Contains pills used to reduce radiation damage." + +/obj/item/storage/pill_bottle/potassiodide/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/reagent_containers/pill/potassiodide(src) + +/obj/item/storage/pill_bottle/C2/probital + name = "bottle of probital pills" + desc = "Contains pills used to treat brute damage.The tag in the bottle states 'Eat before ingesting, may cause fatigue'." + +/obj/item/storage/pill_bottle/C2/probital/PopulateContents() + for(var/i in 1 to 4) + new /obj/item/reagent_containers/pill/C2/probital(src) + +/obj/item/storage/pill_bottle/iron + name = "bottle of iron pills" + desc = "Contains pills used to reduce blood loss slowly.The tag in the bottle states 'Only take one each five minutes'." + +/obj/item/storage/pill_bottle/iron/PopulateContents() + for(var/i in 1 to 4) + new /obj/item/reagent_containers/pill/iron(src) + +/obj/item/storage/pill_bottle/mannitol + name = "bottle of mannitol pills" + desc = "Contains pills used to treat brain damage." + +/obj/item/storage/pill_bottle/mannitol/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/pill/mannitol(src) + +/obj/item/storage/pill_bottle/stimulant + name = "bottle of stimulant pills" + desc = "Guaranteed to give you that extra burst of energy during a long shift!" + +/obj/item/storage/pill_bottle/stimulant/PopulateContents() + for(var/i in 1 to 5) + new /obj/item/reagent_containers/pill/stimulant(src) + +/obj/item/storage/pill_bottle/mining + name = "bottle of patches" + desc = "Contains patches used to treat brute and burn damage." + +/obj/item/storage/pill_bottle/mining/PopulateContents() + new /obj/item/reagent_containers/pill/patch/silver_sulf(src) + for(var/i in 1 to 3) + new /obj/item/reagent_containers/pill/patch/styptic(src) + +/obj/item/storage/pill_bottle/zoom + name = "suspicious pill bottle" + desc = "The label is pretty old and almost unreadable, you recognize some chemical compounds." + +/obj/item/storage/pill_bottle/zoom/PopulateContents() + for(var/i in 1 to 5) + new /obj/item/reagent_containers/pill/zoom(src) + +/obj/item/storage/pill_bottle/happy + name = "suspicious pill bottle" + desc = "There is a smiley on the top." + +/obj/item/storage/pill_bottle/happy/PopulateContents() + for(var/i in 1 to 5) + new /obj/item/reagent_containers/pill/happy(src) + +/obj/item/storage/pill_bottle/lsd + name = "suspicious pill bottle" + desc = "There is a crude drawing which could be either a mushroom, or a deformed moon." + +/obj/item/storage/pill_bottle/lsd/PopulateContents() + for(var/i in 1 to 5) + new /obj/item/reagent_containers/pill/lsd(src) + +/obj/item/storage/pill_bottle/aranesp + name = "suspicious pill bottle" + desc = "The label has 'fuck disablers' hastily scrawled in black marker." + +/obj/item/storage/pill_bottle/aranesp/PopulateContents() + for(var/i in 1 to 5) + new /obj/item/reagent_containers/pill/aranesp(src) + +/obj/item/storage/pill_bottle/psicodine + name = "bottle of psicodine pills" + desc = "Contains pills used to treat mental distress and traumas." + +/obj/item/storage/pill_bottle/psicodine/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/pill/psicodine(src) + +/obj/item/storage/pill_bottle/penacid + name = "bottle of pentetic acid pills" + desc = "Contains pills to expunge radiation and toxins." + +/obj/item/storage/pill_bottle/penacid/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/reagent_containers/pill/penacid(src) + + +/obj/item/storage/pill_bottle/neurine + name = "bottle of neurine pills" + desc = "Contains pills to treat non-severe mental traumas." + +/obj/item/storage/pill_bottle/neurine/PopulateContents() + for(var/i in 1 to 5) + new /obj/item/reagent_containers/pill/neurine(src) + +/obj/item/storage/pill_bottle/floorpill + name = "bottle of floorpills" + desc = "An old pill bottle. It smells musty." + +/obj/item/storage/pill_bottle/floorpill/Initialize() + . = ..() + var/obj/item/reagent_containers/pill/P = locate() in src + name = "bottle of [P.name]s" + +/obj/item/storage/pill_bottle/floorpill/PopulateContents() + for(var/i in 1 to rand(1,7)) + new /obj/item/reagent_containers/pill/floorpill(src) + +/obj/item/storage/pill_bottle/floorpill/full/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/pill/floorpill(src) + +///////////////////////////////////////// Psychologist inventory pillbottles +/obj/item/storage/pill_bottle/happinesspsych + name = "happiness pills" + desc = "Contains pills used as a last resort means to temporarily stabilize depression and anxiety. WARNING: side effects may include slurred speech, drooling, and severe addiction." + +/obj/item/storage/pill_bottle/happinesspsych/PopulateContents() + for(var/i in 1 to 5) + new /obj/item/reagent_containers/pill/happinesspsych(src) + +/obj/item/storage/pill_bottle/lsdpsych + name = "mindbreaker toxin pills" + desc = "!FOR THERAPEUTIC USE ONLY! Contains pills used to alleviate the symptoms of Reality Dissociation Syndrome." + +/obj/item/storage/pill_bottle/lsdpsych/PopulateContents() + for(var/i in 1 to 5) + new /obj/item/reagent_containers/pill/lsdpsych(src) + +/obj/item/storage/pill_bottle/paxpsych + name = "pax pills" + desc = "Contains pills used to temporarily pacify patients that are deemed a harm to themselves or others." + +/obj/item/storage/pill_bottle/paxpsych/PopulateContents() + for(var/i in 1 to 5) + new /obj/item/reagent_containers/pill/paxpsych(src) + +WaspStation End */ diff --git a/code/game/objects/items/storage/lockbox.dm b/code/game/objects/items/storage/lockbox.dm index 51dc47103d51..09118e0d1389 100644 --- a/code/game/objects/items/storage/lockbox.dm +++ b/code/game/objects/items/storage/lockbox.dm @@ -1,192 +1,192 @@ -/obj/item/storage/lockbox - name = "lockbox" - desc = "A locked box." - icon_state = "lockbox+l" - item_state = "syringe_kit" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - w_class = WEIGHT_CLASS_BULKY - req_access = list(ACCESS_ARMORY) - var/broken = FALSE - var/open = FALSE - var/icon_locked = "lockbox+l" - var/icon_closed = "lockbox" - var/icon_broken = "lockbox+b" - -/obj/item/storage/lockbox/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_NORMAL - STR.max_combined_w_class = 14 - STR.max_items = 4 - STR.locked = TRUE - -/obj/item/storage/lockbox/attackby(obj/item/W, mob/user, params) - var/locked = SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED) - if(W.GetID()) - if(broken) - to_chat(user, "It appears to be broken.") - return - if(allowed(user)) - SEND_SIGNAL(src, COMSIG_TRY_STORAGE_SET_LOCKSTATE, !locked) - locked = SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED) - if(locked) - icon_state = icon_locked - to_chat(user, "You lock the [src.name]!") - SEND_SIGNAL(src, COMSIG_TRY_STORAGE_HIDE_ALL) - return - else - icon_state = icon_closed - to_chat(user, "You unlock the [src.name]!") - return - else - to_chat(user, "Access Denied.") - return - if(!locked) - return ..() - else - to_chat(user, "It's locked!") - -/obj/item/storage/lockbox/emag_act(mob/user) - if(!broken) - broken = TRUE - SEND_SIGNAL(src, COMSIG_TRY_STORAGE_SET_LOCKSTATE, FALSE) - desc += "It appears to be broken." - icon_state = src.icon_broken - if(user) - visible_message("\The [src] is broken by [user] with an electromagnetic card!") - return - -/obj/item/storage/lockbox/Entered() - . = ..() - open = TRUE - update_icon() - -/obj/item/storage/lockbox/Exited() - . = ..() - open = TRUE - update_icon() - -/obj/item/storage/lockbox/loyalty - name = "lockbox of mindshield implants" - req_access = list(ACCESS_SECURITY) - -/obj/item/storage/lockbox/loyalty/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/implantcase/mindshield(src) - new /obj/item/implanter/mindshield(src) - -/obj/item/storage/lockbox/clusterbang - name = "lockbox of clusterbangs" - desc = "You have a bad feeling about opening this." - req_access = list(ACCESS_SECURITY) - -/obj/item/storage/lockbox/clusterbang/PopulateContents() - new /obj/item/grenade/clusterbuster(src) - -/obj/item/storage/lockbox/medal - name = "medal box" - desc = "A locked box used to store medals of honor." - icon_state = "medalbox+l" - item_state = "syringe_kit" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - w_class = WEIGHT_CLASS_NORMAL - req_access = list(ACCESS_CAPTAIN) - icon_locked = "medalbox+l" - icon_closed = "medalbox" - icon_broken = "medalbox+b" - -/obj/item/storage/lockbox/medal/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_SMALL - STR.max_items = 10 - STR.max_combined_w_class = 20 - STR.set_holdable(list(/obj/item/clothing/accessory/medal)) - -/obj/item/storage/lockbox/medal/examine(mob/user) - . = ..() - if(!SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED)) - . += "Alt-click to [open ? "close":"open"] it." - -/obj/item/storage/lockbox/medal/AltClick(mob/user) - if(user.canUseTopic(src, BE_CLOSE)) - if(!SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED)) - open = (open ? FALSE : TRUE) - update_icon() - ..() - -/obj/item/storage/lockbox/medal/PopulateContents() - new /obj/item/clothing/accessory/medal/gold/captain(src) - new /obj/item/clothing/accessory/medal/silver/valor(src) - new /obj/item/clothing/accessory/medal/silver/valor(src) - new /obj/item/clothing/accessory/medal/silver/security(src) - new /obj/item/clothing/accessory/medal/bronze_heart(src) - new /obj/item/clothing/accessory/medal/plasma/nobel_science(src) - new /obj/item/clothing/accessory/medal/plasma/nobel_science(src) - for(var/i in 1 to 3) - new /obj/item/clothing/accessory/medal/conduct(src) - -/obj/item/storage/lockbox/medal/update_icon_state() - var/locked = SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED) - if(locked) - icon_state = "medalbox+l" - else - icon_state = "medalbox" - if(open) - icon_state += "open" - if(broken) - icon_state += "+b" - -/obj/item/storage/lockbox/medal/update_overlays() - . = ..() - if(!contents || !open) - return - var/locked = SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED) - if(locked) - return - for(var/i in 1 to contents.len) - var/obj/item/clothing/accessory/medal/M = contents[i] - var/mutable_appearance/medalicon = mutable_appearance(initial(icon), M.medaltype) - if(i > 1 && i <= 5) - medalicon.pixel_x += ((i-1)*3) - else if(i > 5) - medalicon.pixel_y -= 7 - medalicon.pixel_x -= 2 - medalicon.pixel_x += ((i-6)*3) - . += medalicon - -/obj/item/storage/lockbox/medal/sec - name = "security medal box" - desc = "A locked box used to store medals to be given to members of the security department." - req_access = list(ACCESS_HOS) - -/obj/item/storage/lockbox/medal/sec/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/clothing/accessory/medal/silver/security(src) - -/obj/item/storage/lockbox/medal/cargo - name = "cargo award box" - desc = "A locked box used to store awards to be given to members of the cargo department." - req_access = list(ACCESS_QM) - -/obj/item/storage/lockbox/medal/cargo/PopulateContents() - new /obj/item/clothing/accessory/medal/ribbon/cargo(src) - -/obj/item/storage/lockbox/medal/service - name = "service award box" - desc = "A locked box used to store awards to be given to members of the service department." - req_access = list(ACCESS_HOP) - -/obj/item/storage/lockbox/medal/service/PopulateContents() - new /obj/item/clothing/accessory/medal/silver/excellence(src) - -/obj/item/storage/lockbox/medal/sci - name = "science medal box" - desc = "A locked box used to store medals to be given to members of the science department." - req_access = list(ACCESS_RD) - -/obj/item/storage/lockbox/medal/sci/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/clothing/accessory/medal/plasma/nobel_science(src) +/obj/item/storage/lockbox + name = "lockbox" + desc = "A locked box." + icon_state = "lockbox+l" + item_state = "syringe_kit" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + w_class = WEIGHT_CLASS_BULKY + req_access = list(ACCESS_ARMORY) + var/broken = FALSE + var/open = FALSE + var/icon_locked = "lockbox+l" + var/icon_closed = "lockbox" + var/icon_broken = "lockbox+b" + +/obj/item/storage/lockbox/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_NORMAL + STR.max_combined_w_class = 14 + STR.max_items = 4 + STR.locked = TRUE + +/obj/item/storage/lockbox/attackby(obj/item/W, mob/user, params) + var/locked = SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED) + if(W.GetID()) + if(broken) + to_chat(user, "It appears to be broken.") + return + if(allowed(user)) + SEND_SIGNAL(src, COMSIG_TRY_STORAGE_SET_LOCKSTATE, !locked) + locked = SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED) + if(locked) + icon_state = icon_locked + to_chat(user, "You lock the [src.name]!") + SEND_SIGNAL(src, COMSIG_TRY_STORAGE_HIDE_ALL) + return + else + icon_state = icon_closed + to_chat(user, "You unlock the [src.name]!") + return + else + to_chat(user, "Access Denied.") + return + if(!locked) + return ..() + else + to_chat(user, "It's locked!") + +/obj/item/storage/lockbox/emag_act(mob/user) + if(!broken) + broken = TRUE + SEND_SIGNAL(src, COMSIG_TRY_STORAGE_SET_LOCKSTATE, FALSE) + desc += "It appears to be broken." + icon_state = src.icon_broken + if(user) + visible_message("\The [src] is broken by [user] with an electromagnetic card!") + return + +/obj/item/storage/lockbox/Entered() + . = ..() + open = TRUE + update_icon() + +/obj/item/storage/lockbox/Exited() + . = ..() + open = TRUE + update_icon() + +/obj/item/storage/lockbox/loyalty + name = "lockbox of mindshield implants" + req_access = list(ACCESS_SECURITY) + +/obj/item/storage/lockbox/loyalty/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/implantcase/mindshield(src) + new /obj/item/implanter/mindshield(src) + +/obj/item/storage/lockbox/clusterbang + name = "lockbox of clusterbangs" + desc = "You have a bad feeling about opening this." + req_access = list(ACCESS_SECURITY) + +/obj/item/storage/lockbox/clusterbang/PopulateContents() + new /obj/item/grenade/clusterbuster(src) + +/obj/item/storage/lockbox/medal + name = "medal box" + desc = "A locked box used to store medals of honor." + icon_state = "medalbox+l" + item_state = "syringe_kit" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + w_class = WEIGHT_CLASS_NORMAL + req_access = list(ACCESS_CAPTAIN) + icon_locked = "medalbox+l" + icon_closed = "medalbox" + icon_broken = "medalbox+b" + +/obj/item/storage/lockbox/medal/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_SMALL + STR.max_items = 10 + STR.max_combined_w_class = 20 + STR.set_holdable(list(/obj/item/clothing/accessory/medal)) + +/obj/item/storage/lockbox/medal/examine(mob/user) + . = ..() + if(!SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED)) + . += "Alt-click to [open ? "close":"open"] it." + +/obj/item/storage/lockbox/medal/AltClick(mob/user) + if(user.canUseTopic(src, BE_CLOSE)) + if(!SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED)) + open = (open ? FALSE : TRUE) + update_icon() + ..() + +/obj/item/storage/lockbox/medal/PopulateContents() + new /obj/item/clothing/accessory/medal/gold/captain(src) + new /obj/item/clothing/accessory/medal/silver/valor(src) + new /obj/item/clothing/accessory/medal/silver/valor(src) + new /obj/item/clothing/accessory/medal/silver/security(src) + new /obj/item/clothing/accessory/medal/bronze_heart(src) + new /obj/item/clothing/accessory/medal/plasma/nobel_science(src) + new /obj/item/clothing/accessory/medal/plasma/nobel_science(src) + for(var/i in 1 to 3) + new /obj/item/clothing/accessory/medal/conduct(src) + +/obj/item/storage/lockbox/medal/update_icon_state() + var/locked = SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED) + if(locked) + icon_state = "medalbox+l" + else + icon_state = "medalbox" + if(open) + icon_state += "open" + if(broken) + icon_state += "+b" + +/obj/item/storage/lockbox/medal/update_overlays() + . = ..() + if(!contents || !open) + return + var/locked = SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED) + if(locked) + return + for(var/i in 1 to contents.len) + var/obj/item/clothing/accessory/medal/M = contents[i] + var/mutable_appearance/medalicon = mutable_appearance(initial(icon), M.medaltype) + if(i > 1 && i <= 5) + medalicon.pixel_x += ((i-1)*3) + else if(i > 5) + medalicon.pixel_y -= 7 + medalicon.pixel_x -= 2 + medalicon.pixel_x += ((i-6)*3) + . += medalicon + +/obj/item/storage/lockbox/medal/sec + name = "security medal box" + desc = "A locked box used to store medals to be given to members of the security department." + req_access = list(ACCESS_HOS) + +/obj/item/storage/lockbox/medal/sec/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/clothing/accessory/medal/silver/security(src) + +/obj/item/storage/lockbox/medal/cargo + name = "cargo award box" + desc = "A locked box used to store awards to be given to members of the cargo department." + req_access = list(ACCESS_QM) + +/obj/item/storage/lockbox/medal/cargo/PopulateContents() + new /obj/item/clothing/accessory/medal/ribbon/cargo(src) + +/obj/item/storage/lockbox/medal/service + name = "service award box" + desc = "A locked box used to store awards to be given to members of the service department." + req_access = list(ACCESS_HOP) + +/obj/item/storage/lockbox/medal/service/PopulateContents() + new /obj/item/clothing/accessory/medal/silver/excellence(src) + +/obj/item/storage/lockbox/medal/sci + name = "science medal box" + desc = "A locked box used to store medals to be given to members of the science department." + req_access = list(ACCESS_RD) + +/obj/item/storage/lockbox/medal/sci/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/clothing/accessory/medal/plasma/nobel_science(src) diff --git a/code/game/objects/items/storage/secure.dm b/code/game/objects/items/storage/secure.dm index 1f21436bc450..cd00fb951bc4 100644 --- a/code/game/objects/items/storage/secure.dm +++ b/code/game/objects/items/storage/secure.dm @@ -1,188 +1,188 @@ -/* - * Absorbs /obj/item/secstorage. - * Reimplements it only slightly to use existing storage functionality. - * - * Contains: - * Secure Briefcase - * Wall Safe - */ - -// ----------------------------- -// Generic Item -// ----------------------------- -/obj/item/storage/secure - name = "secstorage" - var/icon_locking = "secureb" - var/icon_sparking = "securespark" - var/icon_opened = "secure0" - var/code = "" - var/l_code = null - var/l_set = 0 - var/l_setshort = 0 - var/l_hacking = 0 - var/open = FALSE - w_class = WEIGHT_CLASS_NORMAL - desc = "This shouldn't exist. If it does, create an issue report." - -/obj/item/storage/secure/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_SMALL - STR.max_combined_w_class = 14 - -/obj/item/storage/secure/examine(mob/user) - . = ..() - . += "The service panel is currently [open ? "unscrewed" : "screwed shut"]." - -/obj/item/storage/secure/attackby(obj/item/W, mob/user, params) - if(SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED)) - if (W.tool_behaviour == TOOL_SCREWDRIVER) - if (W.use_tool(src, user, 20)) - open =! open - to_chat(user, "You [open ? "open" : "close"] the service panel.") - return - if (W.tool_behaviour == TOOL_WIRECUTTER) - to_chat(user, "[src] is protected from this sort of tampering, yet it appears the internal memory wires can still be pulsed.") - if ((W.tool_behaviour == TOOL_MULTITOOL) && (!l_hacking)) - if(open == 1) - to_chat(user, "Now attempting to reset internal memory, please hold.") - l_hacking = 1 - if (W.use_tool(src, user, 400)) - to_chat(user, "Internal memory reset - lock has been disengaged.") - l_set = 0 - l_hacking = 0 - else - l_hacking = 0 - else - to_chat(user, "You must unscrew the service panel before you can pulse the wiring!") - return - //At this point you have exhausted all the special things to do when locked - // ... but it's still locked. - return - - // -> storage/attackby() what with handle insertion, etc - return ..() - -/obj/item/storage/secure/attack_self(mob/user) - var/locked = SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED) - user.set_machine(src) - var/dat = text("[]
                        \n\nLock Status: []",src, (locked ? "LOCKED" : "UNLOCKED")) - var/message = "Code" - if ((l_set == 0) && (!l_setshort)) - dat += text("

                        \n5-DIGIT PASSCODE NOT SET.
                        ENTER NEW PASSCODE.
                        ") - if (l_setshort) - dat += text("

                        \nALERT: MEMORY SYSTEM ERROR - 6040 201") - message = text("[]", code) - if (!locked) - message = "*****" - dat += text("


                        \n>[]
                        \n1-2-3
                        \n4-5-6
                        \n7-8-9
                        \nR-0-E
                        \n
                        ", message) - user << browse(dat, "window=caselock;size=300x280") - -/obj/item/storage/secure/Topic(href, href_list) - ..() - if ((usr.stat || usr.restrained()) || (get_dist(src, usr) > 1)) - return - if (href_list["type"]) - if (href_list["type"] == "E") - if ((l_set == 0) && (length(code) == 5) && (!l_setshort) && (code != "ERROR")) - l_code = code - l_set = 1 - else if ((code == l_code) && (l_set == 1)) - SEND_SIGNAL(src, COMSIG_TRY_STORAGE_SET_LOCKSTATE, FALSE) - cut_overlays() - add_overlay(icon_opened) - code = null - else - code = "ERROR" - else - if ((href_list["type"] == "R") && (!l_setshort)) - SEND_SIGNAL(src, COMSIG_TRY_STORAGE_SET_LOCKSTATE, TRUE) - cut_overlays() - code = null - SEND_SIGNAL(src, COMSIG_TRY_STORAGE_HIDE_FROM, usr) - else - code += text("[]", sanitize_text(href_list["type"])) - if (length(code) > 5) - code = "ERROR" - add_fingerprint(usr) - for(var/mob/M in viewers(1, loc)) - if ((M.client && M.machine == src)) - attack_self(M) - return - return - - -// ----------------------------- -// Secure Briefcase -// ----------------------------- -/obj/item/storage/secure/briefcase - name = "secure briefcase" - icon = 'icons/obj/storage.dmi' - icon_state = "secure" - item_state = "sec-case" - lefthand_file = 'icons/mob/inhands/equipment/briefcase_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/briefcase_righthand.dmi' - desc = "A large briefcase with a digital locking system." - force = 8 - hitsound = "swing_hit" - throw_speed = 2 - throw_range = 4 - w_class = WEIGHT_CLASS_BULKY - attack_verb = list("bashed", "battered", "bludgeoned", "thrashed", "whacked") - -/obj/item/storage/secure/briefcase/PopulateContents() - new /obj/item/paper(src) - new /obj/item/pen(src) - -/obj/item/storage/secure/briefcase/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_combined_w_class = 21 - STR.max_w_class = WEIGHT_CLASS_NORMAL - -//Syndie variant of Secure Briefcase. Contains space cash, slightly more robust. -/obj/item/storage/secure/briefcase/syndie - force = 15 - -/obj/item/storage/secure/briefcase/syndie/PopulateContents() - ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - for(var/i = 0, i < STR.max_items - 2, i++) - new /obj/item/stack/spacecash/c1000(src) - - -// ----------------------------- -// Secure Safe -// ----------------------------- - -/obj/item/storage/secure/safe - name = "secure safe" - icon = 'icons/obj/storage.dmi' - icon_state = "safe" - icon_opened = "safe0" - icon_locking = "safeb" - icon_sparking = "safespark" - desc = "Excellent for securing things away from grubby hands." - force = 8 - w_class = WEIGHT_CLASS_GIGANTIC - anchored = TRUE - density = FALSE - -/obj/item/storage/secure/safe/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.set_holdable(null, list(/obj/item/storage/secure/briefcase)) - STR.max_w_class = 8 //?? - -/obj/item/storage/secure/safe/PopulateContents() - new /obj/item/paper(src) - new /obj/item/pen(src) - -/obj/item/storage/secure/safe/attack_hand(mob/user) - . = ..() - if(.) - return - return attack_self(user) - -/obj/item/storage/secure/safe/HoS - name = "head of security's safe" +/* + * Absorbs /obj/item/secstorage. + * Reimplements it only slightly to use existing storage functionality. + * + * Contains: + * Secure Briefcase + * Wall Safe + */ + +// ----------------------------- +// Generic Item +// ----------------------------- +/obj/item/storage/secure + name = "secstorage" + var/icon_locking = "secureb" + var/icon_sparking = "securespark" + var/icon_opened = "secure0" + var/code = "" + var/l_code = null + var/l_set = 0 + var/l_setshort = 0 + var/l_hacking = 0 + var/open = FALSE + w_class = WEIGHT_CLASS_NORMAL + desc = "This shouldn't exist. If it does, create an issue report." + +/obj/item/storage/secure/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_SMALL + STR.max_combined_w_class = 14 + +/obj/item/storage/secure/examine(mob/user) + . = ..() + . += "The service panel is currently [open ? "unscrewed" : "screwed shut"]." + +/obj/item/storage/secure/attackby(obj/item/W, mob/user, params) + if(SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED)) + if (W.tool_behaviour == TOOL_SCREWDRIVER) + if (W.use_tool(src, user, 20)) + open =! open + to_chat(user, "You [open ? "open" : "close"] the service panel.") + return + if (W.tool_behaviour == TOOL_WIRECUTTER) + to_chat(user, "[src] is protected from this sort of tampering, yet it appears the internal memory wires can still be pulsed.") + if ((W.tool_behaviour == TOOL_MULTITOOL) && (!l_hacking)) + if(open == 1) + to_chat(user, "Now attempting to reset internal memory, please hold.") + l_hacking = 1 + if (W.use_tool(src, user, 400)) + to_chat(user, "Internal memory reset - lock has been disengaged.") + l_set = 0 + l_hacking = 0 + else + l_hacking = 0 + else + to_chat(user, "You must unscrew the service panel before you can pulse the wiring!") + return + //At this point you have exhausted all the special things to do when locked + // ... but it's still locked. + return + + // -> storage/attackby() what with handle insertion, etc + return ..() + +/obj/item/storage/secure/attack_self(mob/user) + var/locked = SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED) + user.set_machine(src) + var/dat = text("[]
                        \n\nLock Status: []",src, (locked ? "LOCKED" : "UNLOCKED")) + var/message = "Code" + if ((l_set == 0) && (!l_setshort)) + dat += text("

                        \n5-DIGIT PASSCODE NOT SET.
                        ENTER NEW PASSCODE.
                        ") + if (l_setshort) + dat += text("

                        \nALERT: MEMORY SYSTEM ERROR - 6040 201") + message = text("[]", code) + if (!locked) + message = "*****" + dat += text("


                        \n>[]
                        \n1-2-3
                        \n4-5-6
                        \n7-8-9
                        \nR-0-E
                        \n
                        ", message) + user << browse(dat, "window=caselock;size=300x280") + +/obj/item/storage/secure/Topic(href, href_list) + ..() + if ((usr.stat || usr.restrained()) || (get_dist(src, usr) > 1)) + return + if (href_list["type"]) + if (href_list["type"] == "E") + if ((l_set == 0) && (length(code) == 5) && (!l_setshort) && (code != "ERROR")) + l_code = code + l_set = 1 + else if ((code == l_code) && (l_set == 1)) + SEND_SIGNAL(src, COMSIG_TRY_STORAGE_SET_LOCKSTATE, FALSE) + cut_overlays() + add_overlay(icon_opened) + code = null + else + code = "ERROR" + else + if ((href_list["type"] == "R") && (!l_setshort)) + SEND_SIGNAL(src, COMSIG_TRY_STORAGE_SET_LOCKSTATE, TRUE) + cut_overlays() + code = null + SEND_SIGNAL(src, COMSIG_TRY_STORAGE_HIDE_FROM, usr) + else + code += text("[]", sanitize_text(href_list["type"])) + if (length(code) > 5) + code = "ERROR" + add_fingerprint(usr) + for(var/mob/M in viewers(1, loc)) + if ((M.client && M.machine == src)) + attack_self(M) + return + return + + +// ----------------------------- +// Secure Briefcase +// ----------------------------- +/obj/item/storage/secure/briefcase + name = "secure briefcase" + icon = 'icons/obj/storage.dmi' + icon_state = "secure" + item_state = "sec-case" + lefthand_file = 'icons/mob/inhands/equipment/briefcase_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/briefcase_righthand.dmi' + desc = "A large briefcase with a digital locking system." + force = 8 + hitsound = "swing_hit" + throw_speed = 2 + throw_range = 4 + w_class = WEIGHT_CLASS_BULKY + attack_verb = list("bashed", "battered", "bludgeoned", "thrashed", "whacked") + +/obj/item/storage/secure/briefcase/PopulateContents() + new /obj/item/paper(src) + new /obj/item/pen(src) + +/obj/item/storage/secure/briefcase/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_combined_w_class = 21 + STR.max_w_class = WEIGHT_CLASS_NORMAL + +//Syndie variant of Secure Briefcase. Contains space cash, slightly more robust. +/obj/item/storage/secure/briefcase/syndie + force = 15 + +/obj/item/storage/secure/briefcase/syndie/PopulateContents() + ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + for(var/i = 0, i < STR.max_items - 2, i++) + new /obj/item/stack/spacecash/c1000(src) + + +// ----------------------------- +// Secure Safe +// ----------------------------- + +/obj/item/storage/secure/safe + name = "secure safe" + icon = 'icons/obj/storage.dmi' + icon_state = "safe" + icon_opened = "safe0" + icon_locking = "safeb" + icon_sparking = "safespark" + desc = "Excellent for securing things away from grubby hands." + force = 8 + w_class = WEIGHT_CLASS_GIGANTIC + anchored = TRUE + density = FALSE + +/obj/item/storage/secure/safe/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.set_holdable(null, list(/obj/item/storage/secure/briefcase)) + STR.max_w_class = 8 //?? + +/obj/item/storage/secure/safe/PopulateContents() + new /obj/item/paper(src) + new /obj/item/pen(src) + +/obj/item/storage/secure/safe/attack_hand(mob/user) + . = ..() + if(.) + return + return attack_self(user) + +/obj/item/storage/secure/safe/HoS + name = "head of security's safe" diff --git a/code/game/objects/items/storage/storage.dm b/code/game/objects/items/storage/storage.dm index ee3bc568bac3..1e679adbc685 100644 --- a/code/game/objects/items/storage/storage.dm +++ b/code/game/objects/items/storage/storage.dm @@ -1,50 +1,50 @@ -/obj/item/storage - name = "storage" - icon = 'icons/obj/storage.dmi' - w_class = WEIGHT_CLASS_NORMAL - var/rummage_if_nodrop = TRUE - var/component_type = /datum/component/storage/concrete - -/obj/item/storage/get_dumping_location(obj/item/storage/source,mob/user) - return src - -/obj/item/storage/Initialize() - . = ..() - PopulateContents() - -/obj/item/storage/ComponentInitialize() - AddComponent(component_type) - -/obj/item/storage/AllowDrop() - return FALSE - -/obj/item/storage/contents_explosion(severity, target) - for(var/atom/A in contents) - switch(severity) - if(EXPLODE_DEVASTATE) - SSexplosions.highobj += A - if(EXPLODE_HEAVY) - SSexplosions.medobj += A - if(EXPLODE_LIGHT) - SSexplosions.lowobj += A - -/obj/item/storage/canStrip(mob/who) - . = ..() - if(!. && rummage_if_nodrop) - return TRUE - -/obj/item/storage/doStrip(mob/who) - if(HAS_TRAIT(src, TRAIT_NODROP) && rummage_if_nodrop) - var/datum/component/storage/CP = GetComponent(/datum/component/storage) - CP.do_quick_empty() - return TRUE - return ..() - -/obj/item/storage/contents_explosion(severity, target) -//Cyberboss says: "USE THIS TO FILL IT, NOT INITIALIZE OR NEW" - -/obj/item/storage/proc/PopulateContents() - -/obj/item/storage/proc/emptyStorage() - var/datum/component/storage/ST = GetComponent(/datum/component/storage) - ST.do_quick_empty() +/obj/item/storage + name = "storage" + icon = 'icons/obj/storage.dmi' + w_class = WEIGHT_CLASS_NORMAL + var/rummage_if_nodrop = TRUE + var/component_type = /datum/component/storage/concrete + +/obj/item/storage/get_dumping_location(obj/item/storage/source,mob/user) + return src + +/obj/item/storage/Initialize() + . = ..() + PopulateContents() + +/obj/item/storage/ComponentInitialize() + AddComponent(component_type) + +/obj/item/storage/AllowDrop() + return FALSE + +/obj/item/storage/contents_explosion(severity, target) + for(var/atom/A in contents) + switch(severity) + if(EXPLODE_DEVASTATE) + SSexplosions.highobj += A + if(EXPLODE_HEAVY) + SSexplosions.medobj += A + if(EXPLODE_LIGHT) + SSexplosions.lowobj += A + +/obj/item/storage/canStrip(mob/who) + . = ..() + if(!. && rummage_if_nodrop) + return TRUE + +/obj/item/storage/doStrip(mob/who) + if(HAS_TRAIT(src, TRAIT_NODROP) && rummage_if_nodrop) + var/datum/component/storage/CP = GetComponent(/datum/component/storage) + CP.do_quick_empty() + return TRUE + return ..() + +/obj/item/storage/contents_explosion(severity, target) +//Cyberboss says: "USE THIS TO FILL IT, NOT INITIALIZE OR NEW" + +/obj/item/storage/proc/PopulateContents() + +/obj/item/storage/proc/emptyStorage() + var/datum/component/storage/ST = GetComponent(/datum/component/storage) + ST.do_quick_empty() diff --git a/code/game/objects/items/storage/toolbox.dm b/code/game/objects/items/storage/toolbox.dm index 21b429bdba42..9e4b07d9a833 100644 --- a/code/game/objects/items/storage/toolbox.dm +++ b/code/game/objects/items/storage/toolbox.dm @@ -1,305 +1,305 @@ -/obj/item/storage/toolbox - name = "toolbox" - desc = "Danger. Very robust." - icon_state = "toolbox_default" - item_state = "toolbox_default" - lefthand_file = 'icons/mob/inhands/equipment/toolbox_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/toolbox_righthand.dmi' - flags_1 = CONDUCT_1 - force = 12 - throwforce = 12 - throw_speed = 2 - throw_range = 7 - w_class = WEIGHT_CLASS_BULKY - custom_materials = list(/datum/material/iron = 500) - attack_verb = list("robusted") - hitsound = 'sound/weapons/smash.ogg' - drop_sound = 'sound/items/handling/toolbox_drop.ogg' - pickup_sound = 'sound/items/handling/toolbox_pickup.ogg' - material_flags = MATERIAL_COLOR - var/latches = "single_latch" - var/has_latches = TRUE - -/obj/item/storage/toolbox/Initialize() - . = ..() - if(has_latches) - if(prob(10)) - latches = "double_latch" - if(prob(1)) - latches = "triple_latch" - update_icon() - -/obj/item/storage/toolbox/update_overlays() - . = ..() - if(has_latches) - . += latches - - -/obj/item/storage/toolbox/suicide_act(mob/user) - user.visible_message("[user] robusts [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return (BRUTELOSS) - -/obj/item/storage/toolbox/emergency - name = "emergency toolbox" - icon_state = "red" - item_state = "toolbox_red" - material_flags = NONE - -/obj/item/storage/toolbox/emergency/PopulateContents() - new /obj/item/crowbar/red(src) - new /obj/item/weldingtool/mini(src) - new /obj/item/extinguisher/mini(src) - switch(rand(1,3)) - if(1) - new /obj/item/flashlight(src) - if(2) - new /obj/item/flashlight/glowstick(src) - if(3) - new /obj/item/flashlight/flare(src) - new /obj/item/radio/off(src) - -/obj/item/storage/toolbox/emergency/old - name = "rusty red toolbox" - icon_state = "toolbox_red_old" - has_latches = FALSE - material_flags = NONE - -/obj/item/storage/toolbox/mechanical - name = "mechanical toolbox" - icon_state = "blue" - item_state = "toolbox_blue" - material_flags = NONE - -/obj/item/storage/toolbox/mechanical/PopulateContents() - //WaspStation Edit - Better Tool sprites - if(prob(50)) - new /obj/item/wrench(src) - else - new /obj/item/wrench/crescent(src) - //WaspStation End - new /obj/item/screwdriver(src) - new /obj/item/weldingtool(src) - new /obj/item/crowbar(src) - new /obj/item/analyzer(src) - new /obj/item/wirecutters(src) - -/obj/item/storage/toolbox/mechanical/old - name = "rusty blue toolbox" - icon_state = "toolbox_blue_old" - has_latches = FALSE - material_flags = NONE - -/obj/item/storage/toolbox/mechanical/old/heirloom - name = "toolbox" //this will be named "X family toolbox" - desc = "It's seen better days." - force = 5 - w_class = WEIGHT_CLASS_NORMAL - -/obj/item/storage/toolbox/mechanical/old/heirloom/PopulateContents() - return - -/obj/item/storage/toolbox/mechanical/old/clean - name = "toolbox" - desc = "A old, blue toolbox, it looks robust." - icon_state = "oldtoolboxclean" - item_state = "toolbox_blue" - has_latches = FALSE - force = 19 - throwforce = 22 - -/obj/item/storage/toolbox/mechanical/old/clean/proc/calc_damage() - var/power = 0 - for (var/obj/item/stack/telecrystal/TC in GetAllContents()) - power += TC.amount - force = 19 + power - throwforce = 22 + power - -/obj/item/storage/toolbox/mechanical/old/clean/attack(mob/target, mob/living/user) - calc_damage() - ..() - -/obj/item/storage/toolbox/mechanical/old/clean/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - calc_damage() - ..() - -/obj/item/storage/toolbox/mechanical/old/clean/PopulateContents() - new /obj/item/screwdriver/old(src) - new /obj/item/wrench/old(src) - new /obj/item/weldingtool/old(src) - new /obj/item/crowbar/old(src) - new /obj/item/wirecutters/old(src) - new /obj/item/multitool(src) - new /obj/item/clothing/gloves/color/yellow(src) - -/obj/item/storage/toolbox/electrical - name = "electrical toolbox" - icon_state = "yellow" - item_state = "toolbox_yellow" - material_flags = NONE - -/obj/item/storage/toolbox/electrical/PopulateContents() - var/pickedcolor = pick("red","yellow","green","blue","pink","orange","cyan","white") - new /obj/item/screwdriver(src) - new /obj/item/wirecutters(src) - new /obj/item/t_scanner(src) - new /obj/item/crowbar(src) - new /obj/item/stack/cable_coil(src,MAXCOIL,pickedcolor) - new /obj/item/stack/cable_coil(src,MAXCOIL,pickedcolor) - if(prob(5)) - new /obj/item/clothing/gloves/color/yellow(src) - else - new /obj/item/stack/cable_coil(src,MAXCOIL,pickedcolor) - -/obj/item/storage/toolbox/syndicate - name = "suspicious looking toolbox" - icon_state = "syndicate" - item_state = "toolbox_syndi" - force = 15 - throwforce = 18 - material_flags = NONE - -/obj/item/storage/toolbox/syndicate/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.silent = TRUE - -/obj/item/storage/toolbox/syndicate/PopulateContents() - new /obj/item/screwdriver/nuke(src) - new /obj/item/wrench/syndie(src) //WaspStation Edit - Cool Syndie Tools - new /obj/item/weldingtool/largetank(src) - new /obj/item/crowbar/syndie(src) //WaspStation Begin - Cool Syndie Tools - new /obj/item/wirecutters/syndie(src) - new /obj/item/multitool/syndie(src) //WaspStation End - new /obj/item/clothing/gloves/combat(src) - -/obj/item/storage/toolbox/drone - name = "mechanical toolbox" - icon_state = "blue" - item_state = "toolbox_blue" - material_flags = NONE - -/obj/item/storage/toolbox/drone/PopulateContents() - var/pickedcolor = pick("red","yellow","green","blue","pink","orange","cyan","white") - new /obj/item/screwdriver(src) - new /obj/item/wrench(src) - new /obj/item/weldingtool(src) - new /obj/item/crowbar(src) - new /obj/item/stack/cable_coil(src,MAXCOIL,pickedcolor) - new /obj/item/wirecutters(src) - new /obj/item/multitool(src) - -/obj/item/storage/toolbox/artistic - name = "artistic toolbox" - desc = "A toolbox painted bright green. Why anyone would store art supplies in a toolbox is beyond you, but it has plenty of extra space." - icon_state = "green" - item_state = "artistic_toolbox" - w_class = WEIGHT_CLASS_GIGANTIC //Holds more than a regular toolbox! - material_flags = NONE - -/obj/item/storage/toolbox/artistic/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_combined_w_class = 20 - STR.max_items = 10 - -/obj/item/storage/toolbox/artistic/PopulateContents() - new /obj/item/storage/crayons(src) - new /obj/item/crowbar(src) - new /obj/item/stack/cable_coil/red(src) - new /obj/item/stack/cable_coil/yellow(src) - new /obj/item/stack/cable_coil/blue(src) - new /obj/item/stack/cable_coil/green(src) - new /obj/item/stack/cable_coil/pink(src) - new /obj/item/stack/cable_coil/orange(src) - new /obj/item/stack/cable_coil/cyan(src) - new /obj/item/stack/cable_coil/white(src) - -/obj/item/storage/toolbox/ammo - name = "ammo box" - desc = "It contains a few clips." - icon_state = "ammobox" - item_state = "ammobox" - drop_sound = 'sound/items/handling/ammobox_drop.ogg' - pickup_sound = 'sound/items/handling/ammobox_pickup.ogg' - -/obj/item/storage/toolbox/ammo/PopulateContents() - new /obj/item/ammo_box/a762(src) - new /obj/item/ammo_box/a762(src) - new /obj/item/ammo_box/a762(src) - new /obj/item/ammo_box/a762(src) - new /obj/item/ammo_box/a762(src) - new /obj/item/ammo_box/a762(src) - new /obj/item/ammo_box/a762(src) - -/obj/item/storage/toolbox/infiltrator - name = "insidious case" - desc = "Bearing the emblem of the Syndicate, this case contains a full infiltrator stealth suit, and has enough room to fit weaponry if necessary." - icon_state = "infiltrator_case" - item_state = "infiltrator_case" - force = 15 - throwforce = 18 - w_class = WEIGHT_CLASS_NORMAL - has_latches = FALSE - -/obj/item/storage/toolbox/infiltrator/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 10 - STR.max_w_class = WEIGHT_CLASS_NORMAL - STR.set_holdable(list( - /obj/item/clothing/head/helmet/infiltrator, - /obj/item/clothing/suit/armor/vest/infiltrator, - /obj/item/clothing/under/syndicate/bloodred, - /obj/item/clothing/gloves/color/latex/nitrile/infiltrator, - /obj/item/clothing/mask/infiltrator, - /obj/item/clothing/shoes/combat/sneakboots, - /obj/item/gun/ballistic/automatic/pistol, - /obj/item/gun/ballistic/revolver, - /obj/item/ammo_box - )) - -/obj/item/storage/toolbox/infiltrator/PopulateContents() - new /obj/item/clothing/head/helmet/infiltrator(src) - new /obj/item/clothing/suit/armor/vest/infiltrator(src) - new /obj/item/clothing/under/syndicate/bloodred(src) - new /obj/item/clothing/gloves/color/latex/nitrile/infiltrator(src) - new /obj/item/clothing/mask/infiltrator(src) - new /obj/item/clothing/shoes/combat/sneakboots(src) - -//floorbot assembly -/obj/item/storage/toolbox/attackby(obj/item/stack/tile/plasteel/T, mob/user, params) - var/list/allowed_toolbox = list(/obj/item/storage/toolbox/emergency, //which toolboxes can be made into floorbots - /obj/item/storage/toolbox/electrical, - /obj/item/storage/toolbox/mechanical, - /obj/item/storage/toolbox/artistic, - /obj/item/storage/toolbox/syndicate) - - if(!istype(T, /obj/item/stack/tile/plasteel)) - ..() - return - if(!is_type_in_list(src, allowed_toolbox) && (type != /obj/item/storage/toolbox)) - return - if(contents.len >= 1) - to_chat(user, "They won't fit in, as there is already stuff inside!") - return - if(T.use(10)) - var/obj/item/bot_assembly/floorbot/B = new - B.toolbox = type - switch(B.toolbox) - if(/obj/item/storage/toolbox) - B.toolbox_color = "r" - if(/obj/item/storage/toolbox/emergency) - B.toolbox_color = "r" - if(/obj/item/storage/toolbox/electrical) - B.toolbox_color = "y" - if(/obj/item/storage/toolbox/artistic) - B.toolbox_color = "g" - if(/obj/item/storage/toolbox/syndicate) - B.toolbox_color = "s" - user.put_in_hands(B) - B.update_icon() - to_chat(user, "You add the tiles into the empty [name]. They protrude from the top.") - qdel(src) - else - to_chat(user, "You need 10 floor tiles to start building a floorbot!") - return +/obj/item/storage/toolbox + name = "toolbox" + desc = "Danger. Very robust." + icon_state = "toolbox_default" + item_state = "toolbox_default" + lefthand_file = 'icons/mob/inhands/equipment/toolbox_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/toolbox_righthand.dmi' + flags_1 = CONDUCT_1 + force = 12 + throwforce = 12 + throw_speed = 2 + throw_range = 7 + w_class = WEIGHT_CLASS_BULKY + custom_materials = list(/datum/material/iron = 500) + attack_verb = list("robusted") + hitsound = 'sound/weapons/smash.ogg' + drop_sound = 'sound/items/handling/toolbox_drop.ogg' + pickup_sound = 'sound/items/handling/toolbox_pickup.ogg' + material_flags = MATERIAL_COLOR + var/latches = "single_latch" + var/has_latches = TRUE + +/obj/item/storage/toolbox/Initialize() + . = ..() + if(has_latches) + if(prob(10)) + latches = "double_latch" + if(prob(1)) + latches = "triple_latch" + update_icon() + +/obj/item/storage/toolbox/update_overlays() + . = ..() + if(has_latches) + . += latches + + +/obj/item/storage/toolbox/suicide_act(mob/user) + user.visible_message("[user] robusts [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return (BRUTELOSS) + +/obj/item/storage/toolbox/emergency + name = "emergency toolbox" + icon_state = "red" + item_state = "toolbox_red" + material_flags = NONE + +/obj/item/storage/toolbox/emergency/PopulateContents() + new /obj/item/crowbar/red(src) + new /obj/item/weldingtool/mini(src) + new /obj/item/extinguisher/mini(src) + switch(rand(1,3)) + if(1) + new /obj/item/flashlight(src) + if(2) + new /obj/item/flashlight/glowstick(src) + if(3) + new /obj/item/flashlight/flare(src) + new /obj/item/radio/off(src) + +/obj/item/storage/toolbox/emergency/old + name = "rusty red toolbox" + icon_state = "toolbox_red_old" + has_latches = FALSE + material_flags = NONE + +/obj/item/storage/toolbox/mechanical + name = "mechanical toolbox" + icon_state = "blue" + item_state = "toolbox_blue" + material_flags = NONE + +/obj/item/storage/toolbox/mechanical/PopulateContents() + //WaspStation Edit - Better Tool sprites + if(prob(50)) + new /obj/item/wrench(src) + else + new /obj/item/wrench/crescent(src) + //WaspStation End + new /obj/item/screwdriver(src) + new /obj/item/weldingtool(src) + new /obj/item/crowbar(src) + new /obj/item/analyzer(src) + new /obj/item/wirecutters(src) + +/obj/item/storage/toolbox/mechanical/old + name = "rusty blue toolbox" + icon_state = "toolbox_blue_old" + has_latches = FALSE + material_flags = NONE + +/obj/item/storage/toolbox/mechanical/old/heirloom + name = "toolbox" //this will be named "X family toolbox" + desc = "It's seen better days." + force = 5 + w_class = WEIGHT_CLASS_NORMAL + +/obj/item/storage/toolbox/mechanical/old/heirloom/PopulateContents() + return + +/obj/item/storage/toolbox/mechanical/old/clean + name = "toolbox" + desc = "A old, blue toolbox, it looks robust." + icon_state = "oldtoolboxclean" + item_state = "toolbox_blue" + has_latches = FALSE + force = 19 + throwforce = 22 + +/obj/item/storage/toolbox/mechanical/old/clean/proc/calc_damage() + var/power = 0 + for (var/obj/item/stack/telecrystal/TC in GetAllContents()) + power += TC.amount + force = 19 + power + throwforce = 22 + power + +/obj/item/storage/toolbox/mechanical/old/clean/attack(mob/target, mob/living/user) + calc_damage() + ..() + +/obj/item/storage/toolbox/mechanical/old/clean/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + calc_damage() + ..() + +/obj/item/storage/toolbox/mechanical/old/clean/PopulateContents() + new /obj/item/screwdriver/old(src) + new /obj/item/wrench/old(src) + new /obj/item/weldingtool/old(src) + new /obj/item/crowbar/old(src) + new /obj/item/wirecutters/old(src) + new /obj/item/multitool(src) + new /obj/item/clothing/gloves/color/yellow(src) + +/obj/item/storage/toolbox/electrical + name = "electrical toolbox" + icon_state = "yellow" + item_state = "toolbox_yellow" + material_flags = NONE + +/obj/item/storage/toolbox/electrical/PopulateContents() + var/pickedcolor = pick("red","yellow","green","blue","pink","orange","cyan","white") + new /obj/item/screwdriver(src) + new /obj/item/wirecutters(src) + new /obj/item/t_scanner(src) + new /obj/item/crowbar(src) + new /obj/item/stack/cable_coil(src,MAXCOIL,pickedcolor) + new /obj/item/stack/cable_coil(src,MAXCOIL,pickedcolor) + if(prob(5)) + new /obj/item/clothing/gloves/color/yellow(src) + else + new /obj/item/stack/cable_coil(src,MAXCOIL,pickedcolor) + +/obj/item/storage/toolbox/syndicate + name = "suspicious looking toolbox" + icon_state = "syndicate" + item_state = "toolbox_syndi" + force = 15 + throwforce = 18 + material_flags = NONE + +/obj/item/storage/toolbox/syndicate/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.silent = TRUE + +/obj/item/storage/toolbox/syndicate/PopulateContents() + new /obj/item/screwdriver/nuke(src) + new /obj/item/wrench/syndie(src) //WaspStation Edit - Cool Syndie Tools + new /obj/item/weldingtool/largetank(src) + new /obj/item/crowbar/syndie(src) //WaspStation Begin - Cool Syndie Tools + new /obj/item/wirecutters/syndie(src) + new /obj/item/multitool/syndie(src) //WaspStation End + new /obj/item/clothing/gloves/combat(src) + +/obj/item/storage/toolbox/drone + name = "mechanical toolbox" + icon_state = "blue" + item_state = "toolbox_blue" + material_flags = NONE + +/obj/item/storage/toolbox/drone/PopulateContents() + var/pickedcolor = pick("red","yellow","green","blue","pink","orange","cyan","white") + new /obj/item/screwdriver(src) + new /obj/item/wrench(src) + new /obj/item/weldingtool(src) + new /obj/item/crowbar(src) + new /obj/item/stack/cable_coil(src,MAXCOIL,pickedcolor) + new /obj/item/wirecutters(src) + new /obj/item/multitool(src) + +/obj/item/storage/toolbox/artistic + name = "artistic toolbox" + desc = "A toolbox painted bright green. Why anyone would store art supplies in a toolbox is beyond you, but it has plenty of extra space." + icon_state = "green" + item_state = "artistic_toolbox" + w_class = WEIGHT_CLASS_GIGANTIC //Holds more than a regular toolbox! + material_flags = NONE + +/obj/item/storage/toolbox/artistic/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_combined_w_class = 20 + STR.max_items = 10 + +/obj/item/storage/toolbox/artistic/PopulateContents() + new /obj/item/storage/crayons(src) + new /obj/item/crowbar(src) + new /obj/item/stack/cable_coil/red(src) + new /obj/item/stack/cable_coil/yellow(src) + new /obj/item/stack/cable_coil/blue(src) + new /obj/item/stack/cable_coil/green(src) + new /obj/item/stack/cable_coil/pink(src) + new /obj/item/stack/cable_coil/orange(src) + new /obj/item/stack/cable_coil/cyan(src) + new /obj/item/stack/cable_coil/white(src) + +/obj/item/storage/toolbox/ammo + name = "ammo box" + desc = "It contains a few clips." + icon_state = "ammobox" + item_state = "ammobox" + drop_sound = 'sound/items/handling/ammobox_drop.ogg' + pickup_sound = 'sound/items/handling/ammobox_pickup.ogg' + +/obj/item/storage/toolbox/ammo/PopulateContents() + new /obj/item/ammo_box/a762(src) + new /obj/item/ammo_box/a762(src) + new /obj/item/ammo_box/a762(src) + new /obj/item/ammo_box/a762(src) + new /obj/item/ammo_box/a762(src) + new /obj/item/ammo_box/a762(src) + new /obj/item/ammo_box/a762(src) + +/obj/item/storage/toolbox/infiltrator + name = "insidious case" + desc = "Bearing the emblem of the Syndicate, this case contains a full infiltrator stealth suit, and has enough room to fit weaponry if necessary." + icon_state = "infiltrator_case" + item_state = "infiltrator_case" + force = 15 + throwforce = 18 + w_class = WEIGHT_CLASS_NORMAL + has_latches = FALSE + +/obj/item/storage/toolbox/infiltrator/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 10 + STR.max_w_class = WEIGHT_CLASS_NORMAL + STR.set_holdable(list( + /obj/item/clothing/head/helmet/infiltrator, + /obj/item/clothing/suit/armor/vest/infiltrator, + /obj/item/clothing/under/syndicate/bloodred, + /obj/item/clothing/gloves/color/latex/nitrile/infiltrator, + /obj/item/clothing/mask/infiltrator, + /obj/item/clothing/shoes/combat/sneakboots, + /obj/item/gun/ballistic/automatic/pistol, + /obj/item/gun/ballistic/revolver, + /obj/item/ammo_box + )) + +/obj/item/storage/toolbox/infiltrator/PopulateContents() + new /obj/item/clothing/head/helmet/infiltrator(src) + new /obj/item/clothing/suit/armor/vest/infiltrator(src) + new /obj/item/clothing/under/syndicate/bloodred(src) + new /obj/item/clothing/gloves/color/latex/nitrile/infiltrator(src) + new /obj/item/clothing/mask/infiltrator(src) + new /obj/item/clothing/shoes/combat/sneakboots(src) + +//floorbot assembly +/obj/item/storage/toolbox/attackby(obj/item/stack/tile/plasteel/T, mob/user, params) + var/list/allowed_toolbox = list(/obj/item/storage/toolbox/emergency, //which toolboxes can be made into floorbots + /obj/item/storage/toolbox/electrical, + /obj/item/storage/toolbox/mechanical, + /obj/item/storage/toolbox/artistic, + /obj/item/storage/toolbox/syndicate) + + if(!istype(T, /obj/item/stack/tile/plasteel)) + ..() + return + if(!is_type_in_list(src, allowed_toolbox) && (type != /obj/item/storage/toolbox)) + return + if(contents.len >= 1) + to_chat(user, "They won't fit in, as there is already stuff inside!") + return + if(T.use(10)) + var/obj/item/bot_assembly/floorbot/B = new + B.toolbox = type + switch(B.toolbox) + if(/obj/item/storage/toolbox) + B.toolbox_color = "r" + if(/obj/item/storage/toolbox/emergency) + B.toolbox_color = "r" + if(/obj/item/storage/toolbox/electrical) + B.toolbox_color = "y" + if(/obj/item/storage/toolbox/artistic) + B.toolbox_color = "g" + if(/obj/item/storage/toolbox/syndicate) + B.toolbox_color = "s" + user.put_in_hands(B) + B.update_icon() + to_chat(user, "You add the tiles into the empty [name]. They protrude from the top.") + qdel(src) + else + to_chat(user, "You need 10 floor tiles to start building a floorbot!") + return diff --git a/code/game/objects/items/storage/wallets.dm b/code/game/objects/items/storage/wallets.dm index 0a16d24ccb84..521e52ab4d7b 100644 --- a/code/game/objects/items/storage/wallets.dm +++ b/code/game/objects/items/storage/wallets.dm @@ -1,125 +1,125 @@ -/obj/item/storage/wallet - name = "wallet" - desc = "It can hold a few small and personal things." - icon_state = "wallet" - w_class = WEIGHT_CLASS_SMALL - resistance_flags = FLAMMABLE - slot_flags = ITEM_SLOT_ID - component_type = /datum/component/storage/concrete/wallet - - var/obj/item/card/id/front_id = null - var/list/combined_access - var/cached_flat_icon - -/obj/item/storage/wallet/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage/concrete/wallet) - STR.max_items = 4 - STR.set_holdable(list( - /obj/item/stack/spacecash, - /obj/item/holochip, - /obj/item/card, - /obj/item/clothing/mask/cigarette, - /obj/item/flashlight/pen, - /obj/item/seeds, - /obj/item/stack/medical, - /obj/item/toy/crayon, - /obj/item/coin, - /obj/item/dice, - /obj/item/disk, - /obj/item/implanter, - /obj/item/lighter, - /obj/item/lipstick, - /obj/item/match, - /obj/item/paper, - /obj/item/pen, - /obj/item/photo, - /obj/item/reagent_containers/dropper, - /obj/item/reagent_containers/syringe, - /obj/item/screwdriver, - /obj/item/stamp), - list(/obj/item/screwdriver/power)) - -/obj/item/storage/wallet/Exited(atom/movable/AM) - . = ..() - refreshID() - -/obj/item/storage/wallet/proc/refreshID() - LAZYCLEARLIST(combined_access) - if(!(front_id in src)) - front_id = null - for(var/obj/item/card/id/I in contents) - if(!front_id) - front_id = I - LAZYINITLIST(combined_access) - combined_access |= I.access - if(ishuman(loc)) - var/mob/living/carbon/human/H = loc - if(H.wear_id == src) - H.sec_hud_set_ID() - update_icon() - update_label() - -/obj/item/storage/wallet/Entered(atom/movable/AM) - . = ..() - refreshID() - -/obj/item/storage/wallet/update_overlays() - . = ..() - cached_flat_icon = null - if(front_id) - . += mutable_appearance(front_id.icon, front_id.icon_state) - . += front_id.overlays - . += mutable_appearance(icon, "wallet_overlay") - -/obj/item/storage/wallet/proc/get_cached_flat_icon() - if(!cached_flat_icon) - cached_flat_icon = getFlatIcon(src) - return cached_flat_icon - -/obj/item/storage/wallet/get_examine_string(mob/user, thats = FALSE) - if(front_id) - return "[icon2html(get_cached_flat_icon(), user)] [thats? "That's ":""][get_examine_name(user)]" //displays all overlays in chat - return ..() - -/obj/item/storage/wallet/proc/update_label() - if(front_id) - name = "wallet displaying [front_id]" - else - name = "wallet" - -/obj/item/storage/wallet/examine() - . = ..() - if(front_id) - . += "Alt-click to remove the id." - -/obj/item/storage/wallet/GetID() - return front_id - -/obj/item/storage/wallet/RemoveID() - if(!front_id) - return - . = front_id - front_id.forceMove(get_turf(src)) - -/obj/item/storage/wallet/InsertID(obj/item/inserting_item) - var/obj/item/card/inserting_id = inserting_item.RemoveID() - if(!inserting_id) - return FALSE - attackby(inserting_id) - if(inserting_id in contents) - return TRUE - return FALSE - -/obj/item/storage/wallet/GetAccess() - if(LAZYLEN(combined_access)) - return combined_access - else - return ..() - -/obj/item/storage/wallet/random - icon_state = "random_wallet" - -/obj/item/storage/wallet/random/PopulateContents() - new /obj/item/holochip(src, rand(5,30)) - icon_state = "wallet" +/obj/item/storage/wallet + name = "wallet" + desc = "It can hold a few small and personal things." + icon_state = "wallet" + w_class = WEIGHT_CLASS_SMALL + resistance_flags = FLAMMABLE + slot_flags = ITEM_SLOT_ID + component_type = /datum/component/storage/concrete/wallet + + var/obj/item/card/id/front_id = null + var/list/combined_access + var/cached_flat_icon + +/obj/item/storage/wallet/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage/concrete/wallet) + STR.max_items = 4 + STR.set_holdable(list( + /obj/item/stack/spacecash, + /obj/item/holochip, + /obj/item/card, + /obj/item/clothing/mask/cigarette, + /obj/item/flashlight/pen, + /obj/item/seeds, + /obj/item/stack/medical, + /obj/item/toy/crayon, + /obj/item/coin, + /obj/item/dice, + /obj/item/disk, + /obj/item/implanter, + /obj/item/lighter, + /obj/item/lipstick, + /obj/item/match, + /obj/item/paper, + /obj/item/pen, + /obj/item/photo, + /obj/item/reagent_containers/dropper, + /obj/item/reagent_containers/syringe, + /obj/item/screwdriver, + /obj/item/stamp), + list(/obj/item/screwdriver/power)) + +/obj/item/storage/wallet/Exited(atom/movable/AM) + . = ..() + refreshID() + +/obj/item/storage/wallet/proc/refreshID() + LAZYCLEARLIST(combined_access) + if(!(front_id in src)) + front_id = null + for(var/obj/item/card/id/I in contents) + if(!front_id) + front_id = I + LAZYINITLIST(combined_access) + combined_access |= I.access + if(ishuman(loc)) + var/mob/living/carbon/human/H = loc + if(H.wear_id == src) + H.sec_hud_set_ID() + update_icon() + update_label() + +/obj/item/storage/wallet/Entered(atom/movable/AM) + . = ..() + refreshID() + +/obj/item/storage/wallet/update_overlays() + . = ..() + cached_flat_icon = null + if(front_id) + . += mutable_appearance(front_id.icon, front_id.icon_state) + . += front_id.overlays + . += mutable_appearance(icon, "wallet_overlay") + +/obj/item/storage/wallet/proc/get_cached_flat_icon() + if(!cached_flat_icon) + cached_flat_icon = getFlatIcon(src) + return cached_flat_icon + +/obj/item/storage/wallet/get_examine_string(mob/user, thats = FALSE) + if(front_id) + return "[icon2html(get_cached_flat_icon(), user)] [thats? "That's ":""][get_examine_name(user)]" //displays all overlays in chat + return ..() + +/obj/item/storage/wallet/proc/update_label() + if(front_id) + name = "wallet displaying [front_id]" + else + name = "wallet" + +/obj/item/storage/wallet/examine() + . = ..() + if(front_id) + . += "Alt-click to remove the id." + +/obj/item/storage/wallet/GetID() + return front_id + +/obj/item/storage/wallet/RemoveID() + if(!front_id) + return + . = front_id + front_id.forceMove(get_turf(src)) + +/obj/item/storage/wallet/InsertID(obj/item/inserting_item) + var/obj/item/card/inserting_id = inserting_item.RemoveID() + if(!inserting_id) + return FALSE + attackby(inserting_id) + if(inserting_id in contents) + return TRUE + return FALSE + +/obj/item/storage/wallet/GetAccess() + if(LAZYLEN(combined_access)) + return combined_access + else + return ..() + +/obj/item/storage/wallet/random + icon_state = "random_wallet" + +/obj/item/storage/wallet/random/PopulateContents() + new /obj/item/holochip(src, rand(5,30)) + icon_state = "wallet" diff --git a/code/game/objects/items/stunbaton.dm b/code/game/objects/items/stunbaton.dm index 79c92c471265..9f958700754f 100644 --- a/code/game/objects/items/stunbaton.dm +++ b/code/game/objects/items/stunbaton.dm @@ -1,351 +1,347 @@ -/obj/item/melee/baton - name = "stun baton" - desc = "A stun baton for incapacitating people with." - - icon_state = "stunbaton" - item_state = "baton" - lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' - - force = 10 - attack_verb = list("beaten") - - w_class = WEIGHT_CLASS_NORMAL - slot_flags = ITEM_SLOT_BELT - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 50, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80) - - throwforce = 7 - var/throw_stun_chance = 35 - - var/obj/item/stock_parts/cell/cell - var/preload_cell_type //if not empty the baton starts with this type of cell - var/cell_hit_cost = 1000 - var/can_remove_cell = TRUE - - var/turned_on = FALSE - var/activate_sound = "sparks" - - var/attack_cooldown_check = 0 SECONDS - var/attack_cooldown = 2.5 SECONDS - var/stun_sound = 'sound/weapons/egloves.ogg' - - var/confusion_amt = 10 - var/stamina_loss_amt = 60 - var/apply_stun_delay = 2 SECONDS - var/stun_time = 5 SECONDS - - var/convertible = TRUE //if it can be converted with a conversion kit - -/obj/item/melee/baton/get_cell() - return cell - -/obj/item/melee/baton/suicide_act(mob/user) - if(cell && cell.charge && turned_on) - user.visible_message("[user] is putting the live [name] in [user.p_their()] mouth! It looks like [user.p_theyre()] trying to commit suicide!") - . = (FIRELOSS) - attack(user,user) - else - user.visible_message("[user] is shoving the [name] down their throat! It looks like [user.p_theyre()] trying to commit suicide!") - . = (OXYLOSS) - -/obj/item/melee/baton/Initialize() - . = ..() - if(preload_cell_type) - if(!ispath(preload_cell_type,/obj/item/stock_parts/cell)) - log_mapping("[src] at [AREACOORD(src)] had an invalid preload_cell_type: [preload_cell_type].") - else - cell = new preload_cell_type(src) - update_icon() - RegisterSignal(src, COMSIG_PARENT_ATTACKBY, .proc/convert) - - -/obj/item/melee/baton/Destroy() - if(cell) - QDEL_NULL(cell) - UnregisterSignal(src, COMSIG_PARENT_ATTACKBY) - return ..() - -/obj/item/melee/baton/proc/convert(datum/source, obj/item/I, mob/user) - if(istype(I,/obj/item/conversion_kit) && convertible) - var/turf/T = get_turf(src) - var/obj/item/melee/classic_baton/B = new /obj/item/melee/classic_baton (T) - B.alpha = 20 - playsound(T, 'sound/items/drill_use.ogg', 80, TRUE, -1) - animate(src, alpha = 0, time = 10) - animate(B, alpha = 255, time = 10) - qdel(I) - qdel(src) - -/obj/item/melee/baton/handle_atom_del(atom/A) - if(A == cell) - cell = null - turned_on = FALSE - update_icon() - return ..() - -/obj/item/melee/baton/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - ..() - //Only mob/living types have stun handling - if(turned_on && prob(throw_stun_chance) && iscarbon(hit_atom)) - baton_effect(hit_atom) - -/obj/item/melee/baton/loaded //this one starts with a cell pre-installed. - preload_cell_type = /obj/item/stock_parts/cell/high - -/obj/item/melee/baton/proc/deductcharge(chrgdeductamt) - if(cell) - //Note this value returned is significant, as it will determine - //if a stun is applied or not - . = cell.use(chrgdeductamt) - if(turned_on && cell.charge < cell_hit_cost) - //we're below minimum, turn off - turned_on = FALSE - update_icon() - playsound(src, activate_sound, 75, TRUE, -1) - - -/obj/item/melee/baton/update_icon_state() - if(turned_on) - icon_state = "[initial(icon_state)]_active" - else if(!cell) - icon_state = "[initial(icon_state)]_nocell" - else - icon_state = "[initial(icon_state)]" - -/obj/item/melee/baton/examine(mob/user) - . = ..() - if(cell) - . += "\The [src] is [round(cell.percent())]% charged." - else - . += "\The [src] does not have a power source installed." - -/obj/item/melee/baton/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/stock_parts/cell)) - var/obj/item/stock_parts/cell/C = W - if(cell) - to_chat(user, "[src] already has a cell!") - else - if(C.maxcharge < cell_hit_cost) - to_chat(user, "[src] requires a higher capacity cell.") - return - if(!user.transferItemToLoc(W, src)) - return - cell = W - to_chat(user, "You install a cell in [src].") - update_icon() - - else if(W.tool_behaviour == TOOL_SCREWDRIVER) - tryremovecell(user) - else - return ..() - -/obj/item/melee/baton/proc/tryremovecell(mob/user) - if(cell && can_remove_cell) - cell.update_icon() - cell.forceMove(get_turf(src)) - cell = null - to_chat(user, "You remove the cell from [src].") - turned_on = FALSE - update_icon() - -/obj/item/melee/baton/attack_self(mob/user) - toggle_on(user) - -/obj/item/melee/baton/proc/toggle_on(mob/user) - if(cell && cell.charge > cell_hit_cost) - turned_on = !turned_on - to_chat(user, "[src] is now [turned_on ? "on" : "off"].") - playsound(src, activate_sound, 75, TRUE, -1) - else - turned_on = FALSE - if(!cell) - to_chat(user, "[src] does not have a power source!") - else - to_chat(user, "[src] is out of charge.") - update_icon() - add_fingerprint(user) - -/obj/item/melee/baton/proc/clumsy_check(mob/living/carbon/human/user) - if(turned_on && HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50)) - playsound(src, stun_sound, 75, TRUE, -1) - user.visible_message("[user] accidentally hits [user.p_them()]self with [src]!", \ - "You accidentally hit yourself with [src]!") - user.Knockdown(stun_time*3) //should really be an equivalent to attack(user,user) - deductcharge(cell_hit_cost) - return TRUE - return FALSE - -/obj/item/melee/baton/attack(mob/M, mob/living/carbon/human/user) - if(clumsy_check(user)) - return FALSE - - if(iscyborg(M)) - ..() - return - - - if(ishuman(M)) - var/mob/living/carbon/human/L = M - if(check_martial_counter(L, user)) - return - - if(user.a_intent != INTENT_HARM) - if(turned_on) - if(attack_cooldown_check <= world.time) - if(baton_effect(M, user)) - user.do_attack_animation(M) - return - else - to_chat(user, "The baton is still charging!") - else - M.visible_message("[user] prods [M] with [src]. Luckily it was off.", \ - "[user] prods you with [src]. Luckily it was off.") - else - if(turned_on) - if(attack_cooldown_check <= world.time) - baton_effect(M, user) - ..() - - -/obj/item/melee/baton/proc/baton_effect(mob/living/L, mob/user) - if(shields_blocked(L, user)) - return FALSE - if(HAS_TRAIT_FROM(L, TRAIT_IWASBATONED, user)) //no doublebaton abuse anon! - to_chat(user, "[L] manages to avoid the attack!") - return FALSE - if(iscyborg(loc)) - var/mob/living/silicon/robot/R = loc - if(!R || !R.cell || !R.cell.use(cell_hit_cost)) - return FALSE - else - if(!deductcharge(cell_hit_cost)) - return FALSE - /// After a target is hit, we do a chunk of stamina damage, along with other effects. - /// After a period of time, we then check to see what stun duration we give. - L.Jitter(20) - L.confused = max(confusion_amt, L.confused) - L.stuttering = max(8, L.stuttering) - L.apply_damage(stamina_loss_amt, STAMINA, BODY_ZONE_CHEST) - - SEND_SIGNAL(L, COMSIG_LIVING_MINOR_SHOCK) - addtimer(CALLBACK(src, .proc/apply_stun_effect_end, L), apply_stun_delay) - - if(user) - L.lastattacker = user.real_name - L.lastattackerckey = user.ckey - L.visible_message("[user] stuns [L] with [src]!", \ - "[user] stuns you with [src]!") - log_combat(user, L, "stunned") - - playsound(src, stun_sound, 50, TRUE, -1) - - if(ishuman(L)) - var/mob/living/carbon/human/H = L - H.forcesay(GLOB.hit_appends) - - attack_cooldown_check = world.time + attack_cooldown - - ADD_TRAIT(L, TRAIT_IWASBATONED, user) - addtimer(TRAIT_CALLBACK_REMOVE(L, TRAIT_IWASBATONED, user), attack_cooldown) - - return 1 - -/// After the initial stun period, we check to see if the target needs to have the stun applied. -/obj/item/melee/baton/proc/apply_stun_effect_end(mob/living/target) - var/trait_check = HAS_TRAIT(target, TRAIT_STUNRESISTANCE) //var since we check it in out to_chat as well as determine stun duration - if(!target.IsKnockdown()) - to_chat(target, "Your muscles seize, making you collapse[trait_check ? ", but your body quickly recovers..." : "!"]") - - if(trait_check) - target.Knockdown(stun_time * 0.1) - else - target.Knockdown(stun_time) - -/obj/item/melee/baton/emp_act(severity) - . = ..() - if (!(. & EMP_PROTECT_SELF)) - deductcharge(1000 / severity) - -/obj/item/melee/baton/proc/shields_blocked(mob/living/L, mob/user) - if(ishuman(L)) - var/mob/living/carbon/human/H = L - if(H.check_shields(src, 0, "[user]'s [name]", MELEE_ATTACK)) //No message; check_shields() handles that - playsound(H, 'sound/weapons/genhit.ogg', 50, TRUE) - return TRUE - return FALSE - -//Makeshift stun baton. Replacement for stun gloves. -/obj/item/melee/baton/cattleprod - name = "stunprod" - desc = "An improvised stun baton." - icon_state = "stunprod" - item_state = "prod" - lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' - w_class = WEIGHT_CLASS_BULKY - force = 3 - throwforce = 5 - stun_time = 5 SECONDS - cell_hit_cost = 2000 - throw_stun_chance = 10 - slot_flags = ITEM_SLOT_BACK - convertible = FALSE - var/obj/item/assembly/igniter/sparkler = 0 - -/obj/item/melee/baton/cattleprod/Initialize() - . = ..() - sparkler = new (src) - -/obj/item/melee/baton/cattleprod/baton_effect() - if(sparkler.activate()) - ..() - -/obj/item/melee/baton/cattleprod/Destroy() - if(sparkler) - QDEL_NULL(sparkler) - return ..() - -/obj/item/melee/baton/boomerang - name = "\improper OZtek Boomerang" - desc = "A device invented in 2486 for the great Space Emu War by the confederacy of Australicus, these high-tech boomerangs also work exceptionally well at stunning crewmembers. Just be careful to catch it when thrown!" - throw_speed = 1 - icon_state = "boomerang" - item_state = "boomerang" - force = 5 - throwforce = 5 - throw_range = 5 - cell_hit_cost = 2000 - throw_stun_chance = 99 //Have you prayed today? - convertible = FALSE - custom_materials = list(/datum/material/iron = 10000, /datum/material/glass = 4000, /datum/material/silver = 10000, /datum/material/gold = 2000) - -/obj/item/melee/baton/boomerang/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, gentle = FALSE, quickstart = TRUE) - if(turned_on) - if(ishuman(thrower)) - var/mob/living/carbon/human/H = thrower - H.throw_mode_off() //so they can catch it on the return. - return ..() - -/obj/item/melee/baton/boomerang/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - if(turned_on) - var/caught = hit_atom.hitby(src, FALSE, FALSE, throwingdatum=throwingdatum) - if(ishuman(hit_atom) && !caught && prob(throw_stun_chance))//if they are a carbon and they didn't catch it - baton_effect(hit_atom) - if(thrownby && !caught) - sleep(1) - if(!QDELETED(src)) - throw_at(thrownby, throw_range+2, throw_speed, null, TRUE) - else - return ..() - - -/obj/item/melee/baton/boomerang/update_icon_state() - if(turned_on) - icon_state = "[initial(icon_state)]_active" - else if(!cell) - icon_state = "[initial(icon_state)]_nocell" - else - icon_state = "[initial(icon_state)]" - -/obj/item/melee/baton/boomerang/loaded //Same as above, comes with a cell. - preload_cell_type = /obj/item/stock_parts/cell/high +/obj/item/melee/baton + name = "stun baton" + desc = "A stun baton for incapacitating people with." + + icon_state = "stunbaton" + item_state = "baton" + lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' + + force = 10 + attack_verb = list("beaten") + + w_class = WEIGHT_CLASS_NORMAL + slot_flags = ITEM_SLOT_BELT + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 50, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80) + + throwforce = 7 + var/throw_stun_chance = 35 + + var/obj/item/stock_parts/cell/cell + var/preload_cell_type //if not empty the baton starts with this type of cell + var/cell_hit_cost = 1000 + var/can_remove_cell = TRUE + + var/turned_on = FALSE + var/activate_sound = "sparks" + + var/attack_cooldown_check = 0 SECONDS + var/attack_cooldown = 2.5 SECONDS + var/stun_sound = 'sound/weapons/egloves.ogg' + + var/confusion_amt = 10 + var/stamina_loss_amt = 60 + var/apply_stun_delay = 2 SECONDS + var/stun_time = 5 SECONDS + + var/convertible = TRUE //if it can be converted with a conversion kit + +/obj/item/melee/baton/get_cell() + return cell + +/obj/item/melee/baton/suicide_act(mob/user) + if(cell && cell.charge && turned_on) + user.visible_message("[user] is putting the live [name] in [user.p_their()] mouth! It looks like [user.p_theyre()] trying to commit suicide!") + . = (FIRELOSS) + attack(user,user) + else + user.visible_message("[user] is shoving the [name] down their throat! It looks like [user.p_theyre()] trying to commit suicide!") + . = (OXYLOSS) + +/obj/item/melee/baton/Initialize() + . = ..() + if(preload_cell_type) + if(!ispath(preload_cell_type,/obj/item/stock_parts/cell)) + log_mapping("[src] at [AREACOORD(src)] had an invalid preload_cell_type: [preload_cell_type].") + else + cell = new preload_cell_type(src) + update_icon() + RegisterSignal(src, COMSIG_PARENT_ATTACKBY, .proc/convert) + + +/obj/item/melee/baton/Destroy() + if(cell) + QDEL_NULL(cell) + UnregisterSignal(src, COMSIG_PARENT_ATTACKBY) + return ..() + +/obj/item/melee/baton/proc/convert(datum/source, obj/item/I, mob/user) + if(istype(I,/obj/item/conversion_kit) && convertible) + var/turf/T = get_turf(src) + var/obj/item/melee/classic_baton/B = new /obj/item/melee/classic_baton (T) + B.alpha = 20 + playsound(T, 'sound/items/drill_use.ogg', 80, TRUE, -1) + animate(src, alpha = 0, time = 10) + animate(B, alpha = 255, time = 10) + qdel(I) + qdel(src) + +/obj/item/melee/baton/handle_atom_del(atom/A) + if(A == cell) + cell = null + turned_on = FALSE + update_icon() + return ..() + +/obj/item/melee/baton/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + ..() + //Only mob/living types have stun handling + if(turned_on && prob(throw_stun_chance) && iscarbon(hit_atom)) + baton_effect(hit_atom) + +/obj/item/melee/baton/loaded //this one starts with a cell pre-installed. + preload_cell_type = /obj/item/stock_parts/cell/high + +/obj/item/melee/baton/proc/deductcharge(chrgdeductamt) + if(cell) + //Note this value returned is significant, as it will determine + //if a stun is applied or not + . = cell.use(chrgdeductamt) + if(turned_on && cell.charge < cell_hit_cost) + //we're below minimum, turn off + turned_on = FALSE + update_icon() + playsound(src, activate_sound, 75, TRUE, -1) + + +/obj/item/melee/baton/update_icon_state() + if(turned_on) + icon_state = "[initial(icon_state)]_active" + else if(!cell) + icon_state = "[initial(icon_state)]_nocell" + else + icon_state = "[initial(icon_state)]" + +/obj/item/melee/baton/examine(mob/user) + . = ..() + if(cell) + . += "\The [src] is [round(cell.percent())]% charged." + else + . += "\The [src] does not have a power source installed." + +/obj/item/melee/baton/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/stock_parts/cell)) + var/obj/item/stock_parts/cell/C = W + if(cell) + to_chat(user, "[src] already has a cell!") + else + if(C.maxcharge < cell_hit_cost) + to_chat(user, "[src] requires a higher capacity cell.") + return + if(!user.transferItemToLoc(W, src)) + return + cell = W + to_chat(user, "You install a cell in [src].") + update_icon() + + else if(W.tool_behaviour == TOOL_SCREWDRIVER) + tryremovecell(user) + else + return ..() + +/obj/item/melee/baton/proc/tryremovecell(mob/user) + if(cell && can_remove_cell) + cell.update_icon() + cell.forceMove(get_turf(src)) + cell = null + to_chat(user, "You remove the cell from [src].") + turned_on = FALSE + update_icon() + +/obj/item/melee/baton/attack_self(mob/user) + toggle_on(user) + +/obj/item/melee/baton/proc/toggle_on(mob/user) + if(cell && cell.charge > cell_hit_cost) + turned_on = !turned_on + to_chat(user, "[src] is now [turned_on ? "on" : "off"].") + playsound(src, activate_sound, 75, TRUE, -1) + else + turned_on = FALSE + if(!cell) + to_chat(user, "[src] does not have a power source!") + else + to_chat(user, "[src] is out of charge.") + update_icon() + add_fingerprint(user) + +/obj/item/melee/baton/proc/clumsy_check(mob/living/carbon/human/user) + if(turned_on && HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50)) + playsound(src, stun_sound, 75, TRUE, -1) + user.visible_message("[user] accidentally hits [user.p_them()]self with [src]!", \ + "You accidentally hit yourself with [src]!") + user.Knockdown(stun_time*3) //should really be an equivalent to attack(user,user) + deductcharge(cell_hit_cost) + return TRUE + return FALSE + +/obj/item/melee/baton/attack(mob/M, mob/living/carbon/human/user) + if(clumsy_check(user)) + return FALSE + + if(iscyborg(M)) + ..() + return + + + if(ishuman(M)) + var/mob/living/carbon/human/L = M + if(check_martial_counter(L, user)) + return + + if(user.a_intent != INTENT_HARM) + if(turned_on) + if(attack_cooldown_check <= world.time) + if(baton_effect(M, user)) + user.do_attack_animation(M) + return + else + to_chat(user, "The baton is still charging!") + else + M.visible_message("[user] prods [M] with [src]. Luckily it was off.", \ + "[user] prods you with [src]. Luckily it was off.") + else + if(turned_on) + if(attack_cooldown_check <= world.time) + baton_effect(M, user) + ..() + + +/obj/item/melee/baton/proc/baton_effect(mob/living/L, mob/user) + if(shields_blocked(L, user)) + return FALSE + if(HAS_TRAIT_FROM(L, TRAIT_IWASBATONED, user)) //no doublebaton abuse anon! + to_chat(user, "[L] manages to avoid the attack!") + return FALSE + if(iscyborg(loc)) + var/mob/living/silicon/robot/R = loc + if(!R || !R.cell || !R.cell.use(cell_hit_cost)) + return FALSE + else + if(!deductcharge(cell_hit_cost)) + return FALSE + /// After a target is hit, we do a chunk of stamina damage, along with other effects. + /// After a period of time, we then check to see what stun duration we give. + L.Jitter(20) + L.confused = max(confusion_amt, L.confused) + L.stuttering = max(8, L.stuttering) + L.apply_damage(stamina_loss_amt, STAMINA, BODY_ZONE_CHEST) + + SEND_SIGNAL(L, COMSIG_LIVING_MINOR_SHOCK) + addtimer(CALLBACK(src, .proc/apply_stun_effect_end, L), apply_stun_delay) + + if(user) + L.lastattacker = user.real_name + L.lastattackerckey = user.ckey + L.visible_message("[user] stuns [L] with [src]!", \ + "[user] stuns you with [src]!") + log_combat(user, L, "stunned") + + playsound(src, stun_sound, 50, TRUE, -1) + + attack_cooldown_check = world.time + attack_cooldown + + ADD_TRAIT(L, TRAIT_IWASBATONED, user) + addtimer(TRAIT_CALLBACK_REMOVE(L, TRAIT_IWASBATONED, user), attack_cooldown) + + return 1 + +/// After the initial stun period, we check to see if the target needs to have the stun applied. +/obj/item/melee/baton/proc/apply_stun_effect_end(mob/living/target) + var/trait_check = HAS_TRAIT(target, TRAIT_STUNRESISTANCE) //var since we check it in out to_chat as well as determine stun duration + if(!target.IsKnockdown()) + to_chat(target, "Your muscles seize, making you collapse[trait_check ? ", but your body quickly recovers..." : "!"]") + + if(trait_check) + target.Knockdown(stun_time * 0.1) + else + target.Knockdown(stun_time) + +/obj/item/melee/baton/emp_act(severity) + . = ..() + if (!(. & EMP_PROTECT_SELF)) + deductcharge(1000 / severity) + +/obj/item/melee/baton/proc/shields_blocked(mob/living/L, mob/user) + if(ishuman(L)) + var/mob/living/carbon/human/H = L + if(H.check_shields(src, 0, "[user]'s [name]", MELEE_ATTACK)) //No message; check_shields() handles that + playsound(H, 'sound/weapons/genhit.ogg', 50, TRUE) + return TRUE + return FALSE + +//Makeshift stun baton. Replacement for stun gloves. +/obj/item/melee/baton/cattleprod + name = "stunprod" + desc = "An improvised stun baton." + icon_state = "stunprod" + item_state = "prod" + lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' + w_class = WEIGHT_CLASS_BULKY + force = 3 + throwforce = 5 + stun_time = 5 SECONDS + cell_hit_cost = 2000 + throw_stun_chance = 10 + slot_flags = ITEM_SLOT_BACK + convertible = FALSE + var/obj/item/assembly/igniter/sparkler = 0 + +/obj/item/melee/baton/cattleprod/Initialize() + . = ..() + sparkler = new (src) + +/obj/item/melee/baton/cattleprod/baton_effect() + if(sparkler.activate()) + ..() + +/obj/item/melee/baton/cattleprod/Destroy() + if(sparkler) + QDEL_NULL(sparkler) + return ..() + +/obj/item/melee/baton/boomerang + name = "\improper OZtek Boomerang" + desc = "A device invented in 2486 for the great Space Emu War by the confederacy of Australicus, these high-tech boomerangs also work exceptionally well at stunning crewmembers. Just be careful to catch it when thrown!" + throw_speed = 1 + icon_state = "boomerang" + item_state = "boomerang" + force = 5 + throwforce = 5 + throw_range = 5 + cell_hit_cost = 2000 + throw_stun_chance = 99 //Have you prayed today? + convertible = FALSE + custom_materials = list(/datum/material/iron = 10000, /datum/material/glass = 4000, /datum/material/silver = 10000, /datum/material/gold = 2000) + +/obj/item/melee/baton/boomerang/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, gentle = FALSE, quickstart = TRUE) + if(turned_on) + if(ishuman(thrower)) + var/mob/living/carbon/human/H = thrower + H.throw_mode_off() //so they can catch it on the return. + return ..() + +/obj/item/melee/baton/boomerang/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + if(turned_on) + var/caught = hit_atom.hitby(src, FALSE, FALSE, throwingdatum=throwingdatum) + if(ishuman(hit_atom) && !caught && prob(throw_stun_chance))//if they are a carbon and they didn't catch it + baton_effect(hit_atom) + if(thrownby && !caught) + sleep(1) + if(!QDELETED(src)) + throw_at(thrownby, throw_range+2, throw_speed, null, TRUE) + else + return ..() + + +/obj/item/melee/baton/boomerang/update_icon_state() + if(turned_on) + icon_state = "[initial(icon_state)]_active" + else if(!cell) + icon_state = "[initial(icon_state)]_nocell" + else + icon_state = "[initial(icon_state)]" + +/obj/item/melee/baton/boomerang/loaded //Same as above, comes with a cell. + preload_cell_type = /obj/item/stock_parts/cell/high diff --git a/code/game/objects/items/tanks/jetpack.dm b/code/game/objects/items/tanks/jetpack.dm index 070854f4bf10..384d0d3d5382 100644 --- a/code/game/objects/items/tanks/jetpack.dm +++ b/code/game/objects/items/tanks/jetpack.dm @@ -108,7 +108,7 @@ /obj/item/tank/jetpack/suicide_act(mob/user) if (istype(user, /mob/living/carbon/human/)) var/mob/living/carbon/human/H = user - H.forcesay("WHAT THE FUCK IS CARBON DIOXIDE?") + H.say("WHAT THE FUCK IS CARBON DIOXIDE?") H.visible_message("[user] is suffocating [user.p_them()]self with [src]! It looks like [user.p_they()] didn't read what that jetpack says!") return (OXYLOSS) else diff --git a/code/game/objects/items/tanks/tank_types.dm b/code/game/objects/items/tanks/tank_types.dm index d676a60a2890..961b2c7ef170 100644 --- a/code/game/objects/items/tanks/tank_types.dm +++ b/code/game/objects/items/tanks/tank_types.dm @@ -1,175 +1,175 @@ -/* Types of tanks! - * Contains: - * Oxygen - * Anesthetic - * Air - * Plasma - * Emergency Oxygen - * Generic - */ - -/* - * Oxygen - */ -/obj/item/tank/internals/oxygen - name = "oxygen tank" - desc = "A tank of oxygen, this one is blue." - icon_state = "oxygen" - distribute_pressure = TANK_DEFAULT_RELEASE_PRESSURE - force = 10 - dog_fashion = /datum/dog_fashion/back - - -/obj/item/tank/internals/oxygen/populate_gas() - air_contents.set_moles(/datum/gas/oxygen, (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C)) - -/obj/item/tank/internals/oxygen/yellow - desc = "A tank of oxygen, this one is yellow." - icon_state = "oxygen_f" - dog_fashion = null - -/obj/item/tank/internals/oxygen/red - desc = "A tank of oxygen, this one is red." - icon_state = "oxygen_fr" - dog_fashion = null - -/obj/item/tank/internals/oxygen/empty/populate_gas() - return - -/* - * Anesthetic - */ -/obj/item/tank/internals/anesthetic - name = "anesthetic tank" - desc = "A tank with an N2O/O2 gas mix." - icon_state = "anesthetic" - item_state = "an_tank" - force = 10 - -/obj/item/tank/internals/anesthetic/populate_gas() - air_contents.set_moles(/datum/gas/oxygen, (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * O2STANDARD) - air_contents.set_moles(/datum/gas/nitrous_oxide, (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * N2STANDARD) - -/* - * Plasma - */ -/obj/item/tank/internals/plasma - name = "plasma tank" - desc = "Contains dangerous plasma. Do not inhale. Warning: extremely flammable." - icon_state = "plasma" - flags_1 = CONDUCT_1 - slot_flags = null //they have no straps! - force = 8 - - -/obj/item/tank/internals/plasma/populate_gas() - air_contents.set_moles(/datum/gas/plasma, (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C)) - -/obj/item/tank/internals/plasma/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/flamethrower)) - var/obj/item/flamethrower/F = W - if ((!F.status)||(F.ptank)) - return - if(!user.transferItemToLoc(src, F)) - return - src.master = F - F.ptank = src - F.update_icon() - else - return ..() - -/obj/item/tank/internals/plasma/full/populate_gas() - air_contents.set_moles(/datum/gas/plasma, (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C)) - -/obj/item/tank/internals/plasma/empty/populate_gas() - return - -/* - * Plasmaman Plasma Tank - */ - -/obj/item/tank/internals/plasmaman - name = "plasma internals tank" - desc = "A tank of plasma gas designed specifically for use as internals, particularly for plasma-based lifeforms. If you're not a Plasmaman, you probably shouldn't use this." - icon_state = "plasmaman_tank" - item_state = "plasmaman_tank" - force = 10 - distribute_pressure = TANK_DEFAULT_RELEASE_PRESSURE - -/obj/item/tank/internals/plasmaman/populate_gas() - air_contents.set_moles(/datum/gas/plasma, (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C)) - -/obj/item/tank/internals/plasmaman/full/populate_gas() - air_contents.set_moles(/datum/gas/plasma, (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C)) - - -/obj/item/tank/internals/plasmaman/belt - icon_state = "plasmaman_tank_belt" - item_state = "plasmaman_tank_belt" - slot_flags = ITEM_SLOT_BELT - force = 5 - volume = 6 - w_class = WEIGHT_CLASS_SMALL //thanks i forgot this - -/obj/item/tank/internals/plasmaman/belt/full/populate_gas() - air_contents.set_moles(/datum/gas/plasma, (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C)) - -/obj/item/tank/internals/plasmaman/belt/empty/populate_gas() - return - - - -/* - * Emergency Oxygen - */ -/obj/item/tank/internals/emergency_oxygen - name = "emergency oxygen tank" - desc = "Used for emergencies. Contains very little oxygen, so try to conserve it until you actually need it." - icon_state = "emergency" - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BELT - w_class = WEIGHT_CLASS_SMALL - force = 4 - distribute_pressure = TANK_DEFAULT_RELEASE_PRESSURE - volume = 1 //Tiny. Real life equivalents only have 21 breaths of oxygen in them. They're EMERGENCY tanks anyway -errorage (dangercon 2011) - - -/obj/item/tank/internals/emergency_oxygen/populate_gas() - air_contents.set_moles(/datum/gas/oxygen, (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C)) - -/obj/item/tank/internals/emergency_oxygen/empty/populate_gas() - return - -/obj/item/tank/internals/emergency_oxygen/engi - name = "extended-capacity emergency oxygen tank" - icon_state = "emergency_engi" - volume = 2 // should last a bit over 30 minutes if full - -/obj/item/tank/internals/emergency_oxygen/engi/empty/populate_gas() - return - -/obj/item/tank/internals/emergency_oxygen/double - name = "double emergency oxygen tank" - icon_state = "emergency_double" - volume = 8 - -/obj/item/tank/internals/emergency_oxygen/double/empty/populate_gas() - return - -// * -// * GENERIC -// * - -/obj/item/tank/internals/generic - name = "gas tank" - desc = "A generic tank used for storing and transporting gasses. Can be used for internals." - icon_state = "generic" - distribute_pressure = TANK_DEFAULT_RELEASE_PRESSURE - force = 10 - dog_fashion = /datum/dog_fashion/back - -/obj/item/tank/internals/generic/populate_gas() - return - -/obj/item/tank/internals/emergency_oxygen/double/empty/populate_gas() - return +/* Types of tanks! + * Contains: + * Oxygen + * Anesthetic + * Air + * Plasma + * Emergency Oxygen + * Generic + */ + +/* + * Oxygen + */ +/obj/item/tank/internals/oxygen + name = "oxygen tank" + desc = "A tank of oxygen, this one is blue." + icon_state = "oxygen" + distribute_pressure = TANK_DEFAULT_RELEASE_PRESSURE + force = 10 + dog_fashion = /datum/dog_fashion/back + + +/obj/item/tank/internals/oxygen/populate_gas() + air_contents.set_moles(/datum/gas/oxygen, (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C)) + +/obj/item/tank/internals/oxygen/yellow + desc = "A tank of oxygen, this one is yellow." + icon_state = "oxygen_f" + dog_fashion = null + +/obj/item/tank/internals/oxygen/red + desc = "A tank of oxygen, this one is red." + icon_state = "oxygen_fr" + dog_fashion = null + +/obj/item/tank/internals/oxygen/empty/populate_gas() + return + +/* + * Anesthetic + */ +/obj/item/tank/internals/anesthetic + name = "anesthetic tank" + desc = "A tank with an N2O/O2 gas mix." + icon_state = "anesthetic" + item_state = "an_tank" + force = 10 + +/obj/item/tank/internals/anesthetic/populate_gas() + air_contents.set_moles(/datum/gas/oxygen, (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * O2STANDARD) + air_contents.set_moles(/datum/gas/nitrous_oxide, (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * N2STANDARD) + +/* + * Plasma + */ +/obj/item/tank/internals/plasma + name = "plasma tank" + desc = "Contains dangerous plasma. Do not inhale. Warning: extremely flammable." + icon_state = "plasma" + flags_1 = CONDUCT_1 + slot_flags = null //they have no straps! + force = 8 + + +/obj/item/tank/internals/plasma/populate_gas() + air_contents.set_moles(/datum/gas/plasma, (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C)) + +/obj/item/tank/internals/plasma/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/flamethrower)) + var/obj/item/flamethrower/F = W + if ((!F.status)||(F.ptank)) + return + if(!user.transferItemToLoc(src, F)) + return + src.master = F + F.ptank = src + F.update_icon() + else + return ..() + +/obj/item/tank/internals/plasma/full/populate_gas() + air_contents.set_moles(/datum/gas/plasma, (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C)) + +/obj/item/tank/internals/plasma/empty/populate_gas() + return + +/* + * Plasmaman Plasma Tank + */ + +/obj/item/tank/internals/plasmaman + name = "plasma internals tank" + desc = "A tank of plasma gas designed specifically for use as internals, particularly for plasma-based lifeforms. If you're not a Plasmaman, you probably shouldn't use this." + icon_state = "plasmaman_tank" + item_state = "plasmaman_tank" + force = 10 + distribute_pressure = TANK_DEFAULT_RELEASE_PRESSURE + +/obj/item/tank/internals/plasmaman/populate_gas() + air_contents.set_moles(/datum/gas/plasma, (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C)) + +/obj/item/tank/internals/plasmaman/full/populate_gas() + air_contents.set_moles(/datum/gas/plasma, (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C)) + + +/obj/item/tank/internals/plasmaman/belt + icon_state = "plasmaman_tank_belt" + item_state = "plasmaman_tank_belt" + slot_flags = ITEM_SLOT_BELT + force = 5 + volume = 6 + w_class = WEIGHT_CLASS_SMALL //thanks i forgot this + +/obj/item/tank/internals/plasmaman/belt/full/populate_gas() + air_contents.set_moles(/datum/gas/plasma, (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C)) + +/obj/item/tank/internals/plasmaman/belt/empty/populate_gas() + return + + + +/* + * Emergency Oxygen + */ +/obj/item/tank/internals/emergency_oxygen + name = "emergency oxygen tank" + desc = "Used for emergencies. Contains very little oxygen, so try to conserve it until you actually need it." + icon_state = "emergency" + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BELT + w_class = WEIGHT_CLASS_SMALL + force = 4 + distribute_pressure = TANK_DEFAULT_RELEASE_PRESSURE + volume = 1 //Tiny. Real life equivalents only have 21 breaths of oxygen in them. They're EMERGENCY tanks anyway -errorage (dangercon 2011) + + +/obj/item/tank/internals/emergency_oxygen/populate_gas() + air_contents.set_moles(/datum/gas/oxygen, (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C)) + +/obj/item/tank/internals/emergency_oxygen/empty/populate_gas() + return + +/obj/item/tank/internals/emergency_oxygen/engi + name = "extended-capacity emergency oxygen tank" + icon_state = "emergency_engi" + volume = 2 // should last a bit over 30 minutes if full + +/obj/item/tank/internals/emergency_oxygen/engi/empty/populate_gas() + return + +/obj/item/tank/internals/emergency_oxygen/double + name = "double emergency oxygen tank" + icon_state = "emergency_double" + volume = 8 + +/obj/item/tank/internals/emergency_oxygen/double/empty/populate_gas() + return + +// * +// * GENERIC +// * + +/obj/item/tank/internals/generic + name = "gas tank" + desc = "A generic tank used for storing and transporting gasses. Can be used for internals." + icon_state = "generic" + distribute_pressure = TANK_DEFAULT_RELEASE_PRESSURE + force = 10 + dog_fashion = /datum/dog_fashion/back + +/obj/item/tank/internals/generic/populate_gas() + return + +/obj/item/tank/internals/emergency_oxygen/double/empty/populate_gas() + return diff --git a/code/game/objects/items/tanks/tanks.dm b/code/game/objects/items/tanks/tanks.dm index 9dbfea8e08b2..722bf8062243 100644 --- a/code/game/objects/items/tanks/tanks.dm +++ b/code/game/objects/items/tanks/tanks.dm @@ -148,11 +148,13 @@ else . = ..() -/obj/item/tank/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.hands_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/item/tank/ui_state(mob/user) + return GLOB.hands_state + +/obj/item/tank/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "Tank", name, 400, 120, master_ui, state) + ui = new(user, src, "Tank", name) ui.open() /obj/item/tank/ui_data(mob/user) diff --git a/code/game/objects/items/teleportation.dm b/code/game/objects/items/teleportation.dm index 1d3c412f0b27..37495e52b51e 100644 --- a/code/game/objects/items/teleportation.dm +++ b/code/game/objects/items/teleportation.dm @@ -1,221 +1,221 @@ -#define SOURCE_PORTAL 1 -#define DESTINATION_PORTAL 2 - -/* Teleportation devices. - * Contains: - * Locator - * Hand-tele - */ - -/* - * Locator - */ -/obj/item/locator - name = "bluespace locator" - desc = "Used to track portable teleportation beacons and targets with embedded tracking implants." - icon = 'icons/obj/device.dmi' - icon_state = "locator" - var/temp = null - flags_1 = CONDUCT_1 - w_class = WEIGHT_CLASS_SMALL - item_state = "electronic" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - throw_speed = 3 - throw_range = 7 - custom_materials = list(/datum/material/iron=400) - var/tracking_range = 20 - -/obj/item/locator/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "BluespaceLocator", name, 300, 300, master_ui, state) - ui.open() - -/obj/item/locator/ui_data(mob/user) - var/list/data = list() - - data["trackingrange"] = tracking_range; - - // Get our current turf location. - var/turf/sr = get_turf(src) - - if (sr) - // Check every teleport beacon. - var/list/tele_beacons = list() - for(var/obj/item/beacon/W in GLOB.teleportbeacons) - - // Get the tracking beacon's turf location. - var/turf/tr = get_turf(W) - - // Make sure it's on a turf and that its Z-level matches the tracker's Z-level - if (tr && tr.z == sr.z) - // Get the distance between the beacon's turf and our turf - var/distance = max(abs(tr.x - sr.x), abs(tr.y - sr.y)) - - // If the target is too far away, skip over this beacon. - if(distance > tracking_range) - continue - - var/beacon_name - - if(W.renamed) - beacon_name = W.name - else - var/area/A = get_area(W) - beacon_name = A.name - - var/D = dir2text(get_dir(sr, tr)) - tele_beacons += list(list(name = beacon_name, direction = D, distance = distance)) - - data["telebeacons"] = tele_beacons - - var/list/track_implants = list() - - for (var/obj/item/implant/tracking/W in GLOB.tracked_implants) - if (!W.imp_in || !isliving(W.loc)) - continue - else - var/mob/living/M = W.loc - if (M.stat == DEAD) - if (M.timeofdeath + W.lifespan_postmortem < world.time) - continue - var/turf/tr = get_turf(W) - var/distance = max(abs(tr.x - sr.x), abs(tr.y - sr.y)) - - if(distance > tracking_range) - continue - - var/D = dir2text(get_dir(sr, tr)) - track_implants += list(list(name = W.imp_in.name, direction = D, distance = distance)) - data["trackimplants"] = track_implants - return data - -/obj/machinery/my_machine/ui_act(action, params) - if(..()) return - -/* - * Hand-tele - */ -/obj/item/hand_tele - name = "hand tele" - desc = "A portable item using blue-space technology." - icon = 'icons/obj/device.dmi' - icon_state = "hand_tele" - item_state = "electronic" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - throwforce = 0 - w_class = WEIGHT_CLASS_SMALL - throw_speed = 3 - throw_range = 5 - custom_materials = list(/datum/material/iron=10000) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF - var/list/active_portal_pairs - var/max_portal_pairs = 3 - var/atmos_link_override - -/obj/item/hand_tele/Initialize() - . = ..() - active_portal_pairs = list() - -/obj/item/hand_tele/pre_attack(atom/target, mob/user, params) - if(try_dispel_portal(target, user)) - return TRUE - return ..() - -/obj/item/hand_tele/proc/try_dispel_portal(atom/target, mob/user) - if(is_parent_of_portal(target)) - qdel(target) - to_chat(user, "You dispel [target] with \the [src]!") - return TRUE - return FALSE - -/obj/item/hand_tele/afterattack(atom/target, mob/user) - try_dispel_portal(target, user) - . = ..() - -/obj/item/hand_tele/attack_self(mob/user) - var/turf/current_location = get_turf(user)//What turf is the user on? - var/area/current_area = current_location.loc - if(!current_location || current_area.noteleport || is_away_level(current_location.z) || !isturf(user.loc))//If turf was not found or they're on z level 2 or >7 which does not currently exist. or if user is not located on a turf - to_chat(user, "\The [src] is malfunctioning.") - return - var/list/L = list( ) - for(var/obj/machinery/computer/teleporter/com in GLOB.machines) - if(com.target) - var/area/A = get_area(com.target) - if(!A || A.noteleport) - continue - if(com.power_station && com.power_station.teleporter_hub && com.power_station.engaged) - L["[get_area(com.target)] (Active)"] = com.target - else - L["[get_area(com.target)] (Inactive)"] = com.target - var/list/turfs = list( ) - for(var/turf/T in urange(10, orange=1)) - if(T.x>world.maxx-8 || T.x<8) - continue //putting them at the edge is dumb - if(T.y>world.maxy-8 || T.y<8) - continue - var/area/A = T.loc - if(A.noteleport) - continue - turfs += T - if(turfs.len) - L["None (Dangerous)"] = pick(turfs) - var/t1 = input(user, "Please select a teleporter to lock in on.", "Hand Teleporter") as null|anything in L - if (!t1 || user.get_active_held_item() != src || user.incapacitated()) - return - if(active_portal_pairs.len >= max_portal_pairs) - user.show_message("\The [src] is recharging!") - return - var/atom/T = L[t1] - var/area/A = get_area(T) - if(A.noteleport) - to_chat(user, "\The [src] is malfunctioning.") - return - current_location = get_turf(user) //Recheck. - current_area = current_location.loc - if(!current_location || current_area.noteleport || is_away_level(current_location.z) || !isturf(user.loc))//If turf was not found or they're on z level 2 or >7 which does not currently exist. or if user is not located on a turf - to_chat(user, "\The [src] is malfunctioning.") - return - user.show_message("Locked In.", MSG_AUDIBLE) - var/list/obj/effect/portal/created = create_portal_pair(current_location, get_teleport_turf(get_turf(T)), 300, 1, null, atmos_link_override) - if(!(LAZYLEN(created) == 2)) - return - RegisterSignal(created[1], COMSIG_PARENT_QDELETING, .proc/on_portal_destroy) //Gosh darn it kevinz. - RegisterSignal(created[2], COMSIG_PARENT_QDELETING, .proc/on_portal_destroy) - try_move_adjacent(created[1], user.dir) - active_portal_pairs[created[1]] = created[2] - var/obj/effect/portal/c1 = created[1] - var/obj/effect/portal/c2 = created[2] - investigate_log("was used by [key_name(user)] at [AREACOORD(user)] to create a portal pair with destinations [AREACOORD(c1)] and [AREACOORD(c2)].", INVESTIGATE_PORTAL) - add_fingerprint(user) - -/obj/item/hand_tele/proc/on_portal_destroy(obj/effect/portal/P) - active_portal_pairs -= P //If this portal pair is made by us it'll be erased along with the other portal by the portal. - -/obj/item/hand_tele/proc/is_parent_of_portal(obj/effect/portal/P) - if(!istype(P)) - return FALSE - if(active_portal_pairs[P]) - return SOURCE_PORTAL - for(var/i in active_portal_pairs) - if(active_portal_pairs[i] == P) - return DESTINATION_PORTAL - return FALSE - -/obj/item/hand_tele/suicide_act(mob/user) - if(iscarbon(user)) - user.visible_message("[user] is creating a weak portal and sticking [user.p_their()] head through! It looks like [user.p_theyre()] trying to commit suicide!") - var/mob/living/carbon/itemUser = user - var/obj/item/bodypart/head/head = itemUser.get_bodypart(BODY_ZONE_HEAD) - if(head) - head.drop_limb() - var/list/safeLevels = SSmapping.levels_by_any_trait(list(ZTRAIT_SPACE_RUINS, ZTRAIT_LAVA_RUINS, ZTRAIT_STATION, ZTRAIT_MINING)) - head.forceMove(locate(rand(1, world.maxx), rand(1, world.maxy), pick(safeLevels))) - itemUser.visible_message("The portal snaps closed taking [user]'s head with it!") - else - itemUser.visible_message("[user] looks even further depressed as they realize they do not have a head...and suddenly dies of shame!") - return (BRUTELOSS) +#define SOURCE_PORTAL 1 +#define DESTINATION_PORTAL 2 + +/* Teleportation devices. + * Contains: + * Locator + * Hand-tele + */ + +/* + * Locator + */ +/obj/item/locator + name = "bluespace locator" + desc = "Used to track portable teleportation beacons and targets with embedded tracking implants." + icon = 'icons/obj/device.dmi' + icon_state = "locator" + var/temp = null + flags_1 = CONDUCT_1 + w_class = WEIGHT_CLASS_SMALL + item_state = "electronic" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + throw_speed = 3 + throw_range = 7 + custom_materials = list(/datum/material/iron=400) + var/tracking_range = 20 + +/obj/item/locator/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "BluespaceLocator", name) + ui.open() + +/obj/item/locator/ui_data(mob/user) + var/list/data = list() + + data["trackingrange"] = tracking_range; + + // Get our current turf location. + var/turf/sr = get_turf(src) + + if (sr) + // Check every teleport beacon. + var/list/tele_beacons = list() + for(var/obj/item/beacon/W in GLOB.teleportbeacons) + + // Get the tracking beacon's turf location. + var/turf/tr = get_turf(W) + + // Make sure it's on a turf and that its Z-level matches the tracker's Z-level + if (tr && tr.z == sr.z) + // Get the distance between the beacon's turf and our turf + var/distance = max(abs(tr.x - sr.x), abs(tr.y - sr.y)) + + // If the target is too far away, skip over this beacon. + if(distance > tracking_range) + continue + + var/beacon_name + + if(W.renamed) + beacon_name = W.name + else + var/area/A = get_area(W) + beacon_name = A.name + + var/D = dir2text(get_dir(sr, tr)) + tele_beacons += list(list(name = beacon_name, direction = D, distance = distance)) + + data["telebeacons"] = tele_beacons + + var/list/track_implants = list() + + for (var/obj/item/implant/tracking/W in GLOB.tracked_implants) + if (!W.imp_in || !isliving(W.loc)) + continue + else + var/mob/living/M = W.loc + if (M.stat == DEAD) + if (M.timeofdeath + W.lifespan_postmortem < world.time) + continue + var/turf/tr = get_turf(W) + var/distance = max(abs(tr.x - sr.x), abs(tr.y - sr.y)) + + if(distance > tracking_range) + continue + + var/D = dir2text(get_dir(sr, tr)) + track_implants += list(list(name = W.imp_in.name, direction = D, distance = distance)) + data["trackimplants"] = track_implants + return data + +/obj/machinery/my_machine/ui_act(action, params) + if(..()) return + +/* + * Hand-tele + */ +/obj/item/hand_tele + name = "hand tele" + desc = "A portable item using blue-space technology." + icon = 'icons/obj/device.dmi' + icon_state = "hand_tele" + item_state = "electronic" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + throwforce = 0 + w_class = WEIGHT_CLASS_SMALL + throw_speed = 3 + throw_range = 5 + custom_materials = list(/datum/material/iron=10000) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF + var/list/active_portal_pairs + var/max_portal_pairs = 3 + var/atmos_link_override + +/obj/item/hand_tele/Initialize() + . = ..() + active_portal_pairs = list() + +/obj/item/hand_tele/pre_attack(atom/target, mob/user, params) + if(try_dispel_portal(target, user)) + return TRUE + return ..() + +/obj/item/hand_tele/proc/try_dispel_portal(atom/target, mob/user) + if(is_parent_of_portal(target)) + qdel(target) + to_chat(user, "You dispel [target] with \the [src]!") + return TRUE + return FALSE + +/obj/item/hand_tele/afterattack(atom/target, mob/user) + try_dispel_portal(target, user) + . = ..() + +/obj/item/hand_tele/attack_self(mob/user) + var/turf/current_location = get_turf(user)//What turf is the user on? + var/area/current_area = current_location.loc + if(!current_location || current_area.noteleport || is_away_level(current_location.z) || !isturf(user.loc))//If turf was not found or they're on z level 2 or >7 which does not currently exist. or if user is not located on a turf + to_chat(user, "\The [src] is malfunctioning.") + return + var/list/L = list( ) + for(var/obj/machinery/computer/teleporter/com in GLOB.machines) + if(com.target) + var/area/A = get_area(com.target) + if(!A || A.noteleport) + continue + if(com.power_station && com.power_station.teleporter_hub && com.power_station.engaged) + L["[get_area(com.target)] (Active)"] = com.target + else + L["[get_area(com.target)] (Inactive)"] = com.target + var/list/turfs = list( ) + for(var/turf/T in urange(10, orange=1)) + if(T.x>world.maxx-8 || T.x<8) + continue //putting them at the edge is dumb + if(T.y>world.maxy-8 || T.y<8) + continue + var/area/A = T.loc + if(A.noteleport) + continue + turfs += T + if(turfs.len) + L["None (Dangerous)"] = pick(turfs) + var/t1 = input(user, "Please select a teleporter to lock in on.", "Hand Teleporter") as null|anything in L + if (!t1 || user.get_active_held_item() != src || user.incapacitated()) + return + if(active_portal_pairs.len >= max_portal_pairs) + user.show_message("\The [src] is recharging!") + return + var/atom/T = L[t1] + var/area/A = get_area(T) + if(A.noteleport) + to_chat(user, "\The [src] is malfunctioning.") + return + current_location = get_turf(user) //Recheck. + current_area = current_location.loc + if(!current_location || current_area.noteleport || is_away_level(current_location.z) || !isturf(user.loc))//If turf was not found or they're on z level 2 or >7 which does not currently exist. or if user is not located on a turf + to_chat(user, "\The [src] is malfunctioning.") + return + user.show_message("Locked In.", MSG_AUDIBLE) + var/list/obj/effect/portal/created = create_portal_pair(current_location, get_teleport_turf(get_turf(T)), 300, 1, null, atmos_link_override) + if(!(LAZYLEN(created) == 2)) + return + RegisterSignal(created[1], COMSIG_PARENT_QDELETING, .proc/on_portal_destroy) //Gosh darn it kevinz. + RegisterSignal(created[2], COMSIG_PARENT_QDELETING, .proc/on_portal_destroy) + try_move_adjacent(created[1], user.dir) + active_portal_pairs[created[1]] = created[2] + var/obj/effect/portal/c1 = created[1] + var/obj/effect/portal/c2 = created[2] + investigate_log("was used by [key_name(user)] at [AREACOORD(user)] to create a portal pair with destinations [AREACOORD(c1)] and [AREACOORD(c2)].", INVESTIGATE_PORTAL) + add_fingerprint(user) + +/obj/item/hand_tele/proc/on_portal_destroy(obj/effect/portal/P) + active_portal_pairs -= P //If this portal pair is made by us it'll be erased along with the other portal by the portal. + +/obj/item/hand_tele/proc/is_parent_of_portal(obj/effect/portal/P) + if(!istype(P)) + return FALSE + if(active_portal_pairs[P]) + return SOURCE_PORTAL + for(var/i in active_portal_pairs) + if(active_portal_pairs[i] == P) + return DESTINATION_PORTAL + return FALSE + +/obj/item/hand_tele/suicide_act(mob/user) + if(iscarbon(user)) + user.visible_message("[user] is creating a weak portal and sticking [user.p_their()] head through! It looks like [user.p_theyre()] trying to commit suicide!") + var/mob/living/carbon/itemUser = user + var/obj/item/bodypart/head/head = itemUser.get_bodypart(BODY_ZONE_HEAD) + if(head) + head.drop_limb() + var/list/safeLevels = SSmapping.levels_by_any_trait(list(ZTRAIT_SPACE_RUINS, ZTRAIT_LAVA_RUINS, ZTRAIT_STATION, ZTRAIT_MINING)) + head.forceMove(locate(rand(1, world.maxx), rand(1, world.maxy), pick(safeLevels))) + itemUser.visible_message("The portal snaps closed taking [user]'s head with it!") + else + itemUser.visible_message("[user] looks even further depressed as they realize they do not have a head...and suddenly dies of shame!") + return (BRUTELOSS) diff --git a/code/game/objects/items/tools/crowbar.dm b/code/game/objects/items/tools/crowbar.dm index 531fb3045d7d..393590cd5c09 100644 --- a/code/game/objects/items/tools/crowbar.dm +++ b/code/game/objects/items/tools/crowbar.dm @@ -1,114 +1,114 @@ -/obj/item/crowbar - name = "pocket crowbar" - desc = "A small crowbar. This handy tool is useful for lots of things, such as prying floor tiles or opening unpowered doors." - icon = 'waspstation/icons/obj/tools.dmi' //WaspStation Edit - Better Tool Sprites - icon_state = "crowbar" - lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' - usesound = 'sound/items/crowbar.ogg' - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BELT - force = 5 - throwforce = 7 - w_class = WEIGHT_CLASS_SMALL - custom_materials = list(/datum/material/iron=50) - drop_sound = 'sound/items/handling/crowbar_drop.ogg' - pickup_sound = 'sound/items/handling/crowbar_pickup.ogg' - - attack_verb = list("attacked", "bashed", "battered", "bludgeoned", "whacked") - tool_behaviour = TOOL_CROWBAR - toolspeed = 1 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30) - var/force_opens = FALSE - -/obj/item/crowbar/suicide_act(mob/user) - user.visible_message("[user] is beating [user.p_them()]self to death with [src]! It looks like [user.p_theyre()] trying to commit suicide!") - playsound(loc, 'sound/weapons/genhit.ogg', 50, TRUE, -1) - return (BRUTELOSS) - -/obj/item/crowbar/red - icon_state = "crowbar_red" - force = 8 - -/obj/item/crowbar/abductor - name = "alien crowbar" - desc = "A hard-light crowbar. It appears to pry by itself, without any effort required." - icon = 'icons/obj/abductor.dmi' - usesound = 'sound/weapons/sonic_jackhammer.ogg' - icon_state = "crowbar" - toolspeed = 0.1 - - -/obj/item/crowbar/large - name = "crowbar" - desc = "It's a big crowbar. It doesn't fit in your pockets, because it's big." - force = 12 - w_class = WEIGHT_CLASS_NORMAL - throw_speed = 3 - throw_range = 3 - custom_materials = list(/datum/material/iron=70) - icon_state = "crowbar_large" - item_state = "crowbar" - toolspeed = 0.7 - -/obj/item/crowbar/power - name = "jaws of life" - desc = "A set of jaws of life, compressed through the magic of science." - icon_state = "jaws_pry" - item_state = "jawsoflife" - lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' - custom_materials = list(/datum/material/iron=150,/datum/material/silver=50,/datum/material/titanium=25) - usesound = 'sound/items/jaws_pry.ogg' - force = 15 - toolspeed = 0.7 - force_opens = TRUE - -/obj/item/crowbar/power/examine() - . = ..() - . += " It's fitted with a [tool_behaviour == TOOL_CROWBAR ? "prying" : "cutting"] head." - -/obj/item/crowbar/power/suicide_act(mob/user) - if(tool_behaviour == TOOL_CROWBAR) - user.visible_message("[user] is putting [user.p_their()] head in [src], it looks like [user.p_theyre()] trying to commit suicide!") - playsound(loc, 'sound/items/jaws_pry.ogg', 50, TRUE, -1) - else - user.visible_message("[user] is wrapping \the [src] around [user.p_their()] neck. It looks like [user.p_theyre()] trying to rip [user.p_their()] head off!") - playsound(loc, 'sound/items/jaws_cut.ogg', 50, TRUE, -1) - if(iscarbon(user)) - var/mob/living/carbon/C = user - var/obj/item/bodypart/BP = C.get_bodypart(BODY_ZONE_HEAD) - if(BP) - BP.drop_limb() - playsound(loc, "desceration", 50, TRUE, -1) - return (BRUTELOSS) - -/obj/item/crowbar/power/attack_self(mob/user) - playsound(get_turf(user), 'sound/items/change_jaws.ogg', 50, TRUE) - if(tool_behaviour == TOOL_CROWBAR) - tool_behaviour = TOOL_WIRECUTTER - to_chat(user, "You attach the cutting jaws to [src].") - usesound = 'sound/items/jaws_cut.ogg' - icon_state = "jaws_cutter" - else - tool_behaviour = TOOL_CROWBAR - to_chat(user, "You attach the prying jaws to [src].") - usesound = 'sound/items/jaws_pry.ogg' - icon_state = "jaws_pry" - -/obj/item/crowbar/power/attack(mob/living/carbon/C, mob/user) - if(istype(C) && C.handcuffed && tool_behaviour == TOOL_WIRECUTTER) - user.visible_message("[user] cuts [C]'s restraints with [src]!") - qdel(C.handcuffed) - return - else - ..() - -/obj/item/crowbar/cyborg - name = "hydraulic crowbar" - desc = "A hydraulic prying tool, simple but powerful." - icon = 'icons/obj/items_cyborg.dmi' - icon_state = "crowbar_cyborg" - usesound = 'sound/items/jaws_pry.ogg' - force = 10 - toolspeed = 0.5 +/obj/item/crowbar + name = "pocket crowbar" + desc = "A small crowbar. This handy tool is useful for lots of things, such as prying floor tiles or opening unpowered doors." + icon = 'waspstation/icons/obj/tools.dmi' //WaspStation Edit - Better Tool Sprites + icon_state = "crowbar" + lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' + usesound = 'sound/items/crowbar.ogg' + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BELT + force = 5 + throwforce = 7 + w_class = WEIGHT_CLASS_SMALL + custom_materials = list(/datum/material/iron=50) + drop_sound = 'sound/items/handling/crowbar_drop.ogg' + pickup_sound = 'sound/items/handling/crowbar_pickup.ogg' + + attack_verb = list("attacked", "bashed", "battered", "bludgeoned", "whacked") + tool_behaviour = TOOL_CROWBAR + toolspeed = 1 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30) + var/force_opens = FALSE + +/obj/item/crowbar/suicide_act(mob/user) + user.visible_message("[user] is beating [user.p_them()]self to death with [src]! It looks like [user.p_theyre()] trying to commit suicide!") + playsound(loc, 'sound/weapons/genhit.ogg', 50, TRUE, -1) + return (BRUTELOSS) + +/obj/item/crowbar/red + icon_state = "crowbar_red" + force = 8 + +/obj/item/crowbar/abductor + name = "alien crowbar" + desc = "A hard-light crowbar. It appears to pry by itself, without any effort required." + icon = 'icons/obj/abductor.dmi' + usesound = 'sound/weapons/sonic_jackhammer.ogg' + icon_state = "crowbar" + toolspeed = 0.1 + + +/obj/item/crowbar/large + name = "crowbar" + desc = "It's a big crowbar. It doesn't fit in your pockets, because it's big." + force = 12 + w_class = WEIGHT_CLASS_NORMAL + throw_speed = 3 + throw_range = 3 + custom_materials = list(/datum/material/iron=70) + icon_state = "crowbar_large" + item_state = "crowbar" + toolspeed = 0.7 + +/obj/item/crowbar/power + name = "jaws of life" + desc = "A set of jaws of life, compressed through the magic of science." + icon_state = "jaws_pry" + item_state = "jawsoflife" + lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' + custom_materials = list(/datum/material/iron=150,/datum/material/silver=50,/datum/material/titanium=25) + usesound = 'sound/items/jaws_pry.ogg' + force = 15 + toolspeed = 0.7 + force_opens = TRUE + +/obj/item/crowbar/power/examine() + . = ..() + . += " It's fitted with a [tool_behaviour == TOOL_CROWBAR ? "prying" : "cutting"] head." + +/obj/item/crowbar/power/suicide_act(mob/user) + if(tool_behaviour == TOOL_CROWBAR) + user.visible_message("[user] is putting [user.p_their()] head in [src], it looks like [user.p_theyre()] trying to commit suicide!") + playsound(loc, 'sound/items/jaws_pry.ogg', 50, TRUE, -1) + else + user.visible_message("[user] is wrapping \the [src] around [user.p_their()] neck. It looks like [user.p_theyre()] trying to rip [user.p_their()] head off!") + playsound(loc, 'sound/items/jaws_cut.ogg', 50, TRUE, -1) + if(iscarbon(user)) + var/mob/living/carbon/C = user + var/obj/item/bodypart/BP = C.get_bodypart(BODY_ZONE_HEAD) + if(BP) + BP.drop_limb() + playsound(loc, "desceration", 50, TRUE, -1) + return (BRUTELOSS) + +/obj/item/crowbar/power/attack_self(mob/user) + playsound(get_turf(user), 'sound/items/change_jaws.ogg', 50, TRUE) + if(tool_behaviour == TOOL_CROWBAR) + tool_behaviour = TOOL_WIRECUTTER + to_chat(user, "You attach the cutting jaws to [src].") + usesound = 'sound/items/jaws_cut.ogg' + icon_state = "jaws_cutter" + else + tool_behaviour = TOOL_CROWBAR + to_chat(user, "You attach the prying jaws to [src].") + usesound = 'sound/items/jaws_pry.ogg' + icon_state = "jaws_pry" + +/obj/item/crowbar/power/attack(mob/living/carbon/C, mob/user) + if(istype(C) && C.handcuffed && tool_behaviour == TOOL_WIRECUTTER) + user.visible_message("[user] cuts [C]'s restraints with [src]!") + qdel(C.handcuffed) + return + else + ..() + +/obj/item/crowbar/cyborg + name = "hydraulic crowbar" + desc = "A hydraulic prying tool, simple but powerful." + icon = 'icons/obj/items_cyborg.dmi' + icon_state = "crowbar_cyborg" + usesound = 'sound/items/jaws_pry.ogg' + force = 10 + toolspeed = 0.5 diff --git a/code/game/objects/items/tools/screwdriver.dm b/code/game/objects/items/tools/screwdriver.dm index 613d3be39dc8..b599f7668407 100644 --- a/code/game/objects/items/tools/screwdriver.dm +++ b/code/game/objects/items/tools/screwdriver.dm @@ -1,139 +1,139 @@ -/obj/item/screwdriver - name = "screwdriver" - desc = "You can be totally screwy with this." - icon = 'waspstation/icons/obj/tools.dmi' //WaspStation Edit - Better Tool Sprites - icon_state = "screwdriver_map" - item_state = "screwdriver" - lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BELT - force = 5 - w_class = WEIGHT_CLASS_TINY - throwforce = 5 - throw_speed = 3 - throw_range = 5 - custom_materials = list(/datum/material/iron=75) - attack_verb = list("stabbed") - hitsound = 'sound/weapons/bladeslice.ogg' - usesound = list('sound/items/screwdriver.ogg', 'sound/items/screwdriver2.ogg') - tool_behaviour = TOOL_SCREWDRIVER - toolspeed = 1 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30) - drop_sound = 'sound/items/handling/screwdriver_drop.ogg' - pickup_sound = 'sound/items/handling/screwdriver_pickup.ogg' - item_flags = EYE_STAB - var/random_color = TRUE //if the screwdriver uses random coloring - var/static/list/screwdriver_colors = list( - "blue" = rgb(24, 97, 213), - "red" = rgb(255, 0, 0), - "pink" = rgb(213, 24, 141), - "brown" = rgb(160, 82, 18), - "green" = rgb(14, 127, 27), - "cyan" = rgb(24, 162, 213), - "yellow" = rgb(255, 165, 0) - ) - -/obj/item/screwdriver/suicide_act(mob/user) - user.visible_message("[user] is stabbing [src] into [user.p_their()] [pick("temple", "heart")]! It looks like [user.p_theyre()] trying to commit suicide!") - return(BRUTELOSS) - -/obj/item/screwdriver/Initialize() - . = ..() - if(random_color) //random colors! - icon_state = "screwdriver" - var/our_color = pick(screwdriver_colors) - add_atom_colour(screwdriver_colors[our_color], FIXED_COLOUR_PRIORITY) - update_icon() - if(prob(75)) - pixel_y = rand(0, 16) - -/obj/item/screwdriver/update_overlays() - . = ..() - if(!random_color) //icon override - return - var/mutable_appearance/base_overlay = mutable_appearance(icon, "screwdriver_screwybits") - base_overlay.appearance_flags = RESET_COLOR - . += base_overlay - -/obj/item/screwdriver/worn_overlays(isinhands = FALSE, icon_file) - . = list() - if(isinhands && random_color) - var/mutable_appearance/M = mutable_appearance(icon_file, "screwdriver_head") - M.appearance_flags = RESET_COLOR - . += M - -/obj/item/screwdriver/get_belt_overlay() - if(random_color) - var/mutable_appearance/body = mutable_appearance('icons/obj/clothing/belt_overlays.dmi', "screwdriver") - var/mutable_appearance/head = mutable_appearance('icons/obj/clothing/belt_overlays.dmi', "screwdriver_head") - body.color = color - head.add_overlay(body) - return head - else - return mutable_appearance('icons/obj/clothing/belt_overlays.dmi', icon_state) - -/obj/item/screwdriver/abductor - name = "alien screwdriver" - desc = "An ultrasonic screwdriver." - icon = 'icons/obj/abductor.dmi' - icon_state = "screwdriver_a" - item_state = "screwdriver_nuke" - usesound = 'sound/items/pshoom.ogg' - toolspeed = 0.1 - random_color = FALSE - -/obj/item/screwdriver/abductor/get_belt_overlay() - return mutable_appearance('icons/obj/clothing/belt_overlays.dmi', "screwdriver_nuke") - -/obj/item/screwdriver/power - name = "hand drill" - desc = "A simple powered hand drill." - icon_state = "drill_screw" - item_state = "drill" - lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' - custom_materials = list(/datum/material/iron=150,/datum/material/silver=50,/datum/material/titanium=25) //done for balance reasons, making them high value for research, but harder to get - force = 8 //might or might not be too high, subject to change - w_class = WEIGHT_CLASS_SMALL - throwforce = 8 - throw_speed = 2 - throw_range = 3//it's heavier than a screw driver/wrench, so it does more damage, but can't be thrown as far - attack_verb = list("drilled", "screwed", "jabbed","whacked") - hitsound = 'sound/items/drill_hit.ogg' - usesound = 'sound/items/drill_use.ogg' - toolspeed = 0.7 - random_color = FALSE - -/obj/item/screwdriver/power/examine() - . = ..() - . += " It's fitted with a [tool_behaviour == TOOL_SCREWDRIVER ? "screw" : "bolt"] bit." - -/obj/item/screwdriver/power/suicide_act(mob/user) - if(tool_behaviour == TOOL_SCREWDRIVER) - user.visible_message("[user] is putting [src] to [user.p_their()] temple. It looks like [user.p_theyre()] trying to commit suicide!") - else - user.visible_message("[user] is pressing [src] against [user.p_their()] head! It looks like [user.p_theyre()] trying to commit suicide!") - playsound(loc, 'sound/items/drill_use.ogg', 50, TRUE, -1) - return(BRUTELOSS) - -/obj/item/screwdriver/power/attack_self(mob/user) - playsound(get_turf(user), 'sound/items/change_drill.ogg', 50, TRUE) - if(tool_behaviour == TOOL_SCREWDRIVER) - tool_behaviour = TOOL_WRENCH - to_chat(user, "You attach the bolt bit to [src].") - icon_state = "drill_bolt" - else - tool_behaviour = TOOL_SCREWDRIVER - to_chat(user, "You attach the screw bit to [src].") - icon_state = "drill_screw" - -/obj/item/screwdriver/cyborg - name = "automated screwdriver" - desc = "A powerful automated screwdriver, designed to be both precise and quick." - icon = 'icons/obj/items_cyborg.dmi' - icon_state = "screwdriver_cyborg" - hitsound = 'sound/items/drill_hit.ogg' - usesound = 'sound/items/drill_use.ogg' - toolspeed = 0.5 - random_color = FALSE +/obj/item/screwdriver + name = "screwdriver" + desc = "You can be totally screwy with this." + icon = 'waspstation/icons/obj/tools.dmi' //WaspStation Edit - Better Tool Sprites + icon_state = "screwdriver_map" + item_state = "screwdriver" + lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BELT + force = 5 + w_class = WEIGHT_CLASS_TINY + throwforce = 5 + throw_speed = 3 + throw_range = 5 + custom_materials = list(/datum/material/iron=75) + attack_verb = list("stabbed") + hitsound = 'sound/weapons/bladeslice.ogg' + usesound = list('sound/items/screwdriver.ogg', 'sound/items/screwdriver2.ogg') + tool_behaviour = TOOL_SCREWDRIVER + toolspeed = 1 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30) + drop_sound = 'sound/items/handling/screwdriver_drop.ogg' + pickup_sound = 'sound/items/handling/screwdriver_pickup.ogg' + item_flags = EYE_STAB + var/random_color = TRUE //if the screwdriver uses random coloring + var/static/list/screwdriver_colors = list( + "blue" = rgb(24, 97, 213), + "red" = rgb(255, 0, 0), + "pink" = rgb(213, 24, 141), + "brown" = rgb(160, 82, 18), + "green" = rgb(14, 127, 27), + "cyan" = rgb(24, 162, 213), + "yellow" = rgb(255, 165, 0) + ) + +/obj/item/screwdriver/suicide_act(mob/user) + user.visible_message("[user] is stabbing [src] into [user.p_their()] [pick("temple", "heart")]! It looks like [user.p_theyre()] trying to commit suicide!") + return(BRUTELOSS) + +/obj/item/screwdriver/Initialize() + . = ..() + if(random_color) //random colors! + icon_state = "screwdriver" + var/our_color = pick(screwdriver_colors) + add_atom_colour(screwdriver_colors[our_color], FIXED_COLOUR_PRIORITY) + update_icon() + if(prob(75)) + pixel_y = rand(0, 16) + +/obj/item/screwdriver/update_overlays() + . = ..() + if(!random_color) //icon override + return + var/mutable_appearance/base_overlay = mutable_appearance(icon, "screwdriver_screwybits") + base_overlay.appearance_flags = RESET_COLOR + . += base_overlay + +/obj/item/screwdriver/worn_overlays(isinhands = FALSE, icon_file) + . = list() + if(isinhands && random_color) + var/mutable_appearance/M = mutable_appearance(icon_file, "screwdriver_head") + M.appearance_flags = RESET_COLOR + . += M + +/obj/item/screwdriver/get_belt_overlay() + if(random_color) + var/mutable_appearance/body = mutable_appearance('icons/obj/clothing/belt_overlays.dmi', "screwdriver") + var/mutable_appearance/head = mutable_appearance('icons/obj/clothing/belt_overlays.dmi', "screwdriver_head") + body.color = color + head.add_overlay(body) + return head + else + return mutable_appearance('icons/obj/clothing/belt_overlays.dmi', icon_state) + +/obj/item/screwdriver/abductor + name = "alien screwdriver" + desc = "An ultrasonic screwdriver." + icon = 'icons/obj/abductor.dmi' + icon_state = "screwdriver_a" + item_state = "screwdriver_nuke" + usesound = 'sound/items/pshoom.ogg' + toolspeed = 0.1 + random_color = FALSE + +/obj/item/screwdriver/abductor/get_belt_overlay() + return mutable_appearance('icons/obj/clothing/belt_overlays.dmi', "screwdriver_nuke") + +/obj/item/screwdriver/power + name = "hand drill" + desc = "A simple powered hand drill." + icon_state = "drill_screw" + item_state = "drill" + lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' + custom_materials = list(/datum/material/iron=150,/datum/material/silver=50,/datum/material/titanium=25) //done for balance reasons, making them high value for research, but harder to get + force = 8 //might or might not be too high, subject to change + w_class = WEIGHT_CLASS_SMALL + throwforce = 8 + throw_speed = 2 + throw_range = 3//it's heavier than a screw driver/wrench, so it does more damage, but can't be thrown as far + attack_verb = list("drilled", "screwed", "jabbed","whacked") + hitsound = 'sound/items/drill_hit.ogg' + usesound = 'sound/items/drill_use.ogg' + toolspeed = 0.7 + random_color = FALSE + +/obj/item/screwdriver/power/examine() + . = ..() + . += " It's fitted with a [tool_behaviour == TOOL_SCREWDRIVER ? "screw" : "bolt"] bit." + +/obj/item/screwdriver/power/suicide_act(mob/user) + if(tool_behaviour == TOOL_SCREWDRIVER) + user.visible_message("[user] is putting [src] to [user.p_their()] temple. It looks like [user.p_theyre()] trying to commit suicide!") + else + user.visible_message("[user] is pressing [src] against [user.p_their()] head! It looks like [user.p_theyre()] trying to commit suicide!") + playsound(loc, 'sound/items/drill_use.ogg', 50, TRUE, -1) + return(BRUTELOSS) + +/obj/item/screwdriver/power/attack_self(mob/user) + playsound(get_turf(user), 'sound/items/change_drill.ogg', 50, TRUE) + if(tool_behaviour == TOOL_SCREWDRIVER) + tool_behaviour = TOOL_WRENCH + to_chat(user, "You attach the bolt bit to [src].") + icon_state = "drill_bolt" + else + tool_behaviour = TOOL_SCREWDRIVER + to_chat(user, "You attach the screw bit to [src].") + icon_state = "drill_screw" + +/obj/item/screwdriver/cyborg + name = "automated screwdriver" + desc = "A powerful automated screwdriver, designed to be both precise and quick." + icon = 'icons/obj/items_cyborg.dmi' + icon_state = "screwdriver_cyborg" + hitsound = 'sound/items/drill_hit.ogg' + usesound = 'sound/items/drill_use.ogg' + toolspeed = 0.5 + random_color = FALSE diff --git a/code/game/objects/items/tools/wirecutters.dm b/code/game/objects/items/tools/wirecutters.dm index 51118b896350..f7c6cbb4b058 100644 --- a/code/game/objects/items/tools/wirecutters.dm +++ b/code/game/objects/items/tools/wirecutters.dm @@ -1,85 +1,85 @@ -/obj/item/wirecutters - name = "wirecutters" - desc = "This cuts wires." - icon = 'waspstation/icons/obj/tools.dmi' //WaspStation Edit - Better Tool Sprites - icon_state = "cutters_map" - item_state = "cutters" - lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BELT - force = 6 - throw_speed = 3 - throw_range = 7 - w_class = WEIGHT_CLASS_SMALL - custom_materials = list(/datum/material/iron=80) - attack_verb = list("pinched", "nipped") - hitsound = 'sound/items/wirecutter.ogg' - usesound = 'sound/items/wirecutter.ogg' - drop_sound = 'sound/items/handling/wirecutter_drop.ogg' - pickup_sound = 'sound/items/handling/wirecutter_pickup.ogg' - - tool_behaviour = TOOL_WIRECUTTER - toolspeed = 1 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30) - var/random_color = TRUE - var/static/list/wirecutter_colors = list( - "blue" = "#1861d5", - "red" = "#951710", - "pink" = "#d5188d", - "brown" = "#a05212", - "green" = "#0e7f1b", - "cyan" = "#18a2d5", - "yellow" = "#d58c18" - ) - - -/obj/item/wirecutters/Initialize() - . = ..() - if(random_color) //random colors! - icon_state = "cutters" - var/our_color = pick(wirecutter_colors) - add_atom_colour(wirecutter_colors[our_color], FIXED_COLOUR_PRIORITY) - update_icon() - -/obj/item/wirecutters/update_overlays() - . = ..() - if(!random_color) //icon override - return - var/mutable_appearance/base_overlay = mutable_appearance(icon, "cutters_cutty_thingy") - base_overlay.appearance_flags = RESET_COLOR - . += base_overlay - -/obj/item/wirecutters/attack(mob/living/carbon/C, mob/user) - if(istype(C) && C.handcuffed && istype(C.handcuffed, /obj/item/restraints/handcuffs/cable)) - user.visible_message("[user] cuts [C]'s restraints with [src]!") - qdel(C.handcuffed) - return - else if(istype(C) && C.has_status_effect(STATUS_EFFECT_CHOKINGSTRAND)) - to_chat(C, "You attempt to remove the durathread strand from around your neck.") - if(do_after(user, 15, null, C)) - to_chat(C, "You succesfuly remove the durathread strand.") - C.remove_status_effect(STATUS_EFFECT_CHOKINGSTRAND) - else - ..() - -/obj/item/wirecutters/suicide_act(mob/user) - user.visible_message("[user] is cutting at [user.p_their()] arteries with [src]! It looks like [user.p_theyre()] trying to commit suicide!") - playsound(loc, usesound, 50, TRUE, -1) - return (BRUTELOSS) - -/obj/item/wirecutters/abductor - name = "alien wirecutters" - desc = "Extremely sharp wirecutters, made out of a silvery-green metal." - icon = 'icons/obj/abductor.dmi' - icon_state = "cutters" - toolspeed = 0.1 - random_color = FALSE - -/obj/item/wirecutters/cyborg - name = "powered wirecutters" - desc = "Cuts wires with the power of ELECTRICITY. Faster than normal wirecutters." - icon = 'icons/obj/items_cyborg.dmi' - icon_state = "wirecutters_cyborg" - toolspeed = 0.5 - random_color = FALSE +/obj/item/wirecutters + name = "wirecutters" + desc = "This cuts wires." + icon = 'waspstation/icons/obj/tools.dmi' //WaspStation Edit - Better Tool Sprites + icon_state = "cutters_map" + item_state = "cutters" + lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BELT + force = 6 + throw_speed = 3 + throw_range = 7 + w_class = WEIGHT_CLASS_SMALL + custom_materials = list(/datum/material/iron=80) + attack_verb = list("pinched", "nipped") + hitsound = 'sound/items/wirecutter.ogg' + usesound = 'sound/items/wirecutter.ogg' + drop_sound = 'sound/items/handling/wirecutter_drop.ogg' + pickup_sound = 'sound/items/handling/wirecutter_pickup.ogg' + + tool_behaviour = TOOL_WIRECUTTER + toolspeed = 1 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30) + var/random_color = TRUE + var/static/list/wirecutter_colors = list( + "blue" = "#1861d5", + "red" = "#951710", + "pink" = "#d5188d", + "brown" = "#a05212", + "green" = "#0e7f1b", + "cyan" = "#18a2d5", + "yellow" = "#d58c18" + ) + + +/obj/item/wirecutters/Initialize() + . = ..() + if(random_color) //random colors! + icon_state = "cutters" + var/our_color = pick(wirecutter_colors) + add_atom_colour(wirecutter_colors[our_color], FIXED_COLOUR_PRIORITY) + update_icon() + +/obj/item/wirecutters/update_overlays() + . = ..() + if(!random_color) //icon override + return + var/mutable_appearance/base_overlay = mutable_appearance(icon, "cutters_cutty_thingy") + base_overlay.appearance_flags = RESET_COLOR + . += base_overlay + +/obj/item/wirecutters/attack(mob/living/carbon/C, mob/user) + if(istype(C) && C.handcuffed && istype(C.handcuffed, /obj/item/restraints/handcuffs/cable)) + user.visible_message("[user] cuts [C]'s restraints with [src]!") + qdel(C.handcuffed) + return + else if(istype(C) && C.has_status_effect(STATUS_EFFECT_CHOKINGSTRAND)) + to_chat(C, "You attempt to remove the durathread strand from around your neck.") + if(do_after(user, 15, null, C)) + to_chat(C, "You succesfuly remove the durathread strand.") + C.remove_status_effect(STATUS_EFFECT_CHOKINGSTRAND) + else + ..() + +/obj/item/wirecutters/suicide_act(mob/user) + user.visible_message("[user] is cutting at [user.p_their()] arteries with [src]! It looks like [user.p_theyre()] trying to commit suicide!") + playsound(loc, usesound, 50, TRUE, -1) + return (BRUTELOSS) + +/obj/item/wirecutters/abductor + name = "alien wirecutters" + desc = "Extremely sharp wirecutters, made out of a silvery-green metal." + icon = 'icons/obj/abductor.dmi' + icon_state = "cutters" + toolspeed = 0.1 + random_color = FALSE + +/obj/item/wirecutters/cyborg + name = "powered wirecutters" + desc = "Cuts wires with the power of ELECTRICITY. Faster than normal wirecutters." + icon = 'icons/obj/items_cyborg.dmi' + icon_state = "wirecutters_cyborg" + toolspeed = 0.5 + random_color = FALSE diff --git a/code/game/objects/items/tools/wrench.dm b/code/game/objects/items/tools/wrench.dm index 96fc0d8eb0e5..91aa5dbc5e7c 100644 --- a/code/game/objects/items/tools/wrench.dm +++ b/code/game/objects/items/tools/wrench.dm @@ -1,119 +1,119 @@ -/obj/item/wrench - name = "wrench" - desc = "A wrench with common uses. Can be found in your hand." - icon = 'waspstation/icons/obj/tools.dmi' - icon_state = "wrench" - lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BELT - force = 5 - throwforce = 7 - w_class = WEIGHT_CLASS_SMALL - usesound = 'sound/items/ratchet.ogg' - custom_materials = list(/datum/material/iron=150) - drop_sound = 'sound/items/handling/wrench_drop.ogg' - pickup_sound = 'sound/items/handling/wrench_pickup.ogg' - - attack_verb = list("bashed", "battered", "bludgeoned", "whacked") - tool_behaviour = TOOL_WRENCH - toolspeed = 1 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30) - -/obj/item/wrench/suicide_act(mob/user) - user.visible_message("[user] is beating [user.p_them()]self to death with [src]! It looks like [user.p_theyre()] trying to commit suicide!") - playsound(loc, 'sound/weapons/genhit.ogg', 50, TRUE, -1) - return (BRUTELOSS) - -/obj/item/wrench/abductor - name = "alien wrench" - desc = "A polarized wrench. It causes anything placed between the jaws to turn." - icon = 'icons/obj/abductor.dmi' - icon_state = "wrench" - usesound = 'sound/effects/empulse.ogg' - toolspeed = 0.1 - - -/obj/item/wrench/medical - name = "medical wrench" - desc = "A medical wrench with common(medical?) uses. Can be found in your hand." - icon_state = "wrench_medical" - force = 2 //MEDICAL - throwforce = 4 - attack_verb = list("healed", "medicaled", "tapped", "poked", "analyzed") //"cobbyed" - ///var to hold the name of the person who suicided - var/suicider - -/obj/item/wrench/medical/examine(mob/user) - . = ..() - if(suicider) - . += "For some reason, it reminds you of [suicider]." - -/obj/item/wrench/medical/suicide_act(mob/living/user) - user.visible_message("[user] is praying to the medical wrench to take [user.p_their()] soul. It looks like [user.p_theyre()] trying to commit suicide!") - user.Stun(100, ignore_canstun = TRUE)// Stun stops them from wandering off - user.light_color = "#FAE48E" - user.set_light(2) - user.add_overlay(mutable_appearance('icons/effects/genetics.dmi', "servitude", -MUTATIONS_LAYER)) - playsound(loc, 'sound/effects/pray.ogg', 50, TRUE, -1) - - // Let the sound effect finish playing - add_fingerprint(user) - sleep(20) - if(!user) - return - for(var/obj/item/W in user) - user.dropItemToGround(W) - suicider = user.real_name - user.dust() - return OXYLOSS - -/obj/item/wrench/cyborg - name = "hydraulic wrench" - desc = "An advanced robotic wrench, powered by internal hydraulics. Twice as fast as the handheld version." - icon = 'icons/obj/items_cyborg.dmi' - icon_state = "wrench_cyborg" - toolspeed = 0.5 - -/obj/item/wrench/combat - name = "combat wrench" - desc = "It's like a normal wrench but edgier. Can be found on the battlefield." - icon_state = "wrench_combat" - item_state = "wrench_combat" - attack_verb = list("devastated", "brutalized", "committed a war crime against", "obliterated", "humiliated") - tool_behaviour = null - toolspeed = null - var/on = FALSE - -/obj/item/wrench/combat/ComponentInitialize() - . = ..() - AddElement(/datum/element/update_icon_updates_onmob) - -/obj/item/wrench/combat/attack_self(mob/living/user) - if(on) - on = FALSE - force = initial(force) - w_class = initial(w_class) - throwforce = initial(throwforce) - tool_behaviour = initial(tool_behaviour) - toolspeed = initial(toolspeed) - playsound(user, 'sound/weapons/saberoff.ogg', 5, TRUE) - to_chat(user, "[src] can now be kept at bay.") - else - on = TRUE - force = 6 - w_class = WEIGHT_CLASS_NORMAL - throwforce = 8 - tool_behaviour = TOOL_WRENCH - toolspeed = 1 - playsound(user, 'sound/weapons/saberon.ogg', 5, TRUE) - to_chat(user, "[src] is now active. Woe onto your enemies!") - update_icon() - -/obj/item/wrench/combat/update_icon_state() - if(on) - icon_state = "[initial(icon_state)]_on" - item_state = "[initial(item_state)]1" - else - icon_state = "[initial(icon_state)]" - item_state = "[initial(item_state)]" +/obj/item/wrench + name = "wrench" + desc = "A wrench with common uses. Can be found in your hand." + icon = 'waspstation/icons/obj/tools.dmi' + icon_state = "wrench" + lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BELT + force = 5 + throwforce = 7 + w_class = WEIGHT_CLASS_SMALL + usesound = 'sound/items/ratchet.ogg' + custom_materials = list(/datum/material/iron=150) + drop_sound = 'sound/items/handling/wrench_drop.ogg' + pickup_sound = 'sound/items/handling/wrench_pickup.ogg' + + attack_verb = list("bashed", "battered", "bludgeoned", "whacked") + tool_behaviour = TOOL_WRENCH + toolspeed = 1 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30) + +/obj/item/wrench/suicide_act(mob/user) + user.visible_message("[user] is beating [user.p_them()]self to death with [src]! It looks like [user.p_theyre()] trying to commit suicide!") + playsound(loc, 'sound/weapons/genhit.ogg', 50, TRUE, -1) + return (BRUTELOSS) + +/obj/item/wrench/abductor + name = "alien wrench" + desc = "A polarized wrench. It causes anything placed between the jaws to turn." + icon = 'icons/obj/abductor.dmi' + icon_state = "wrench" + usesound = 'sound/effects/empulse.ogg' + toolspeed = 0.1 + + +/obj/item/wrench/medical + name = "medical wrench" + desc = "A medical wrench with common(medical?) uses. Can be found in your hand." + icon_state = "wrench_medical" + force = 2 //MEDICAL + throwforce = 4 + attack_verb = list("healed", "medicaled", "tapped", "poked", "analyzed") //"cobbyed" + ///var to hold the name of the person who suicided + var/suicider + +/obj/item/wrench/medical/examine(mob/user) + . = ..() + if(suicider) + . += "For some reason, it reminds you of [suicider]." + +/obj/item/wrench/medical/suicide_act(mob/living/user) + user.visible_message("[user] is praying to the medical wrench to take [user.p_their()] soul. It looks like [user.p_theyre()] trying to commit suicide!") + user.Stun(100, ignore_canstun = TRUE)// Stun stops them from wandering off + user.light_color = "#FAE48E" + user.set_light(2) + user.add_overlay(mutable_appearance('icons/effects/genetics.dmi', "servitude", -MUTATIONS_LAYER)) + playsound(loc, 'sound/effects/pray.ogg', 50, TRUE, -1) + + // Let the sound effect finish playing + add_fingerprint(user) + sleep(20) + if(!user) + return + for(var/obj/item/W in user) + user.dropItemToGround(W) + suicider = user.real_name + user.dust() + return OXYLOSS + +/obj/item/wrench/cyborg + name = "hydraulic wrench" + desc = "An advanced robotic wrench, powered by internal hydraulics. Twice as fast as the handheld version." + icon = 'icons/obj/items_cyborg.dmi' + icon_state = "wrench_cyborg" + toolspeed = 0.5 + +/obj/item/wrench/combat + name = "combat wrench" + desc = "It's like a normal wrench but edgier. Can be found on the battlefield." + icon_state = "wrench_combat" + item_state = "wrench_combat" + attack_verb = list("devastated", "brutalized", "committed a war crime against", "obliterated", "humiliated") + tool_behaviour = null + toolspeed = null + var/on = FALSE + +/obj/item/wrench/combat/ComponentInitialize() + . = ..() + AddElement(/datum/element/update_icon_updates_onmob) + +/obj/item/wrench/combat/attack_self(mob/living/user) + if(on) + on = FALSE + force = initial(force) + w_class = initial(w_class) + throwforce = initial(throwforce) + tool_behaviour = initial(tool_behaviour) + toolspeed = initial(toolspeed) + playsound(user, 'sound/weapons/saberoff.ogg', 5, TRUE) + to_chat(user, "[src] can now be kept at bay.") + else + on = TRUE + force = 6 + w_class = WEIGHT_CLASS_NORMAL + throwforce = 8 + tool_behaviour = TOOL_WRENCH + toolspeed = 1 + playsound(user, 'sound/weapons/saberon.ogg', 5, TRUE) + to_chat(user, "[src] is now active. Woe onto your enemies!") + update_icon() + +/obj/item/wrench/combat/update_icon_state() + if(on) + icon_state = "[initial(icon_state)]_on" + item_state = "[initial(item_state)]1" + else + icon_state = "[initial(icon_state)]" + item_state = "[initial(item_state)]" diff --git a/code/game/objects/items/trash.dm b/code/game/objects/items/trash.dm index 926f8154a1f2..aa57cae5a3a9 100644 --- a/code/game/objects/items/trash.dm +++ b/code/game/objects/items/trash.dm @@ -1,112 +1,112 @@ -//Added by Jack Rost -/obj/item/trash - icon = 'icons/obj/janitor.dmi' - lefthand_file = 'icons/mob/inhands/misc/food_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/food_righthand.dmi' - desc = "This is rubbish." - w_class = WEIGHT_CLASS_TINY - resistance_flags = FLAMMABLE - -/obj/item/trash/Initialize(mapload) - var/turf/T = get_turf(src) - if(T && is_station_level(T.z)) - SSblackbox.record_feedback("tally", "station_mess_created", 1, name) - return ..() - -/obj/item/trash/Destroy() - var/turf/T = get_turf(src) - if(T && is_station_level(T.z)) - SSblackbox.record_feedback("tally", "station_mess_destroyed", 1, name) - return ..() - -/obj/item/trash/raisins - name = "\improper 4no raisins" - icon_state= "4no_raisins" - -/obj/item/trash/candy - name = "candy" - icon_state= "candy" - -/obj/item/trash/cheesie - name = "cheesie honkers" - icon_state = "cheesie_honkers" - -/obj/item/trash/chips - name = "chips" - icon_state = "chips" - -/obj/item/trash/boritos - name = "boritos bag" - icon_state = "boritos" - grind_results = list(/datum/reagent/aluminium = 1) //from the mylar bag - -/obj/item/trash/popcorn - name = "popcorn" - icon_state = "popcorn" - -/obj/item/trash/sosjerky - name = "\improper Scaredy's Private Reserve Beef Jerky" - icon_state = "sosjerky" - -/obj/item/trash/syndi_cakes - name = "syndi-cakes" - icon_state = "syndi_cakes" - -/obj/item/trash/energybar - name = "energybar wrapper" - icon_state = "energybar" - -/obj/item/trash/waffles - name = "waffles tray" - icon_state = "waffles" - -/obj/item/trash/plate - name = "plate" - icon_state = "plate" - resistance_flags = NONE - -/obj/item/trash/pistachios - name = "pistachios pack" - icon_state = "pistachios_pack" - -/obj/item/trash/semki - name = "semki pack" - icon_state = "semki_pack" - -/obj/item/trash/tray - name = "tray" - icon_state = "tray" - resistance_flags = NONE - -/obj/item/trash/candle - name = "candle" - icon = 'icons/obj/candle.dmi' - icon_state = "candle4" - -/obj/item/trash/can - name = "crushed can" - icon_state = "cola" - resistance_flags = NONE - grind_results = list(/datum/reagent/aluminium = 10) - -/obj/item/trash/can/food/peaches - name = "canned peaches" - icon = 'icons/obj/food/food.dmi' - icon_state = "peachcan_empty" - -/obj/item/trash/can/food/peaches/maint - name = "Maintenance Peaches" - icon_state = "peachcanmaint_empty" - -/obj/item/trash/can/food/beans - name = "tin of beans" - icon = 'icons/obj/food/food.dmi' - icon_state = "beans_empty" - -/obj/item/trash/can/Initialize() - . = ..() - pixel_x = rand(-4,4) - pixel_y = rand(-4,4) - -/obj/item/trash/attack(mob/M, mob/living/user) - return +//Added by Jack Rost +/obj/item/trash + icon = 'icons/obj/janitor.dmi' + lefthand_file = 'icons/mob/inhands/misc/food_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/food_righthand.dmi' + desc = "This is rubbish." + w_class = WEIGHT_CLASS_TINY + resistance_flags = FLAMMABLE + +/obj/item/trash/Initialize(mapload) + var/turf/T = get_turf(src) + if(T && is_station_level(T.z)) + SSblackbox.record_feedback("tally", "station_mess_created", 1, name) + return ..() + +/obj/item/trash/Destroy() + var/turf/T = get_turf(src) + if(T && is_station_level(T.z)) + SSblackbox.record_feedback("tally", "station_mess_destroyed", 1, name) + return ..() + +/obj/item/trash/raisins + name = "\improper 4no raisins" + icon_state= "4no_raisins" + +/obj/item/trash/candy + name = "candy" + icon_state= "candy" + +/obj/item/trash/cheesie + name = "cheesie honkers" + icon_state = "cheesie_honkers" + +/obj/item/trash/chips + name = "chips" + icon_state = "chips" + +/obj/item/trash/boritos + name = "boritos bag" + icon_state = "boritos" + grind_results = list(/datum/reagent/aluminium = 1) //from the mylar bag + +/obj/item/trash/popcorn + name = "popcorn" + icon_state = "popcorn" + +/obj/item/trash/sosjerky + name = "\improper Scaredy's Private Reserve Beef Jerky" + icon_state = "sosjerky" + +/obj/item/trash/syndi_cakes + name = "syndi-cakes" + icon_state = "syndi_cakes" + +/obj/item/trash/energybar + name = "energybar wrapper" + icon_state = "energybar" + +/obj/item/trash/waffles + name = "waffles tray" + icon_state = "waffles" + +/obj/item/trash/plate + name = "plate" + icon_state = "plate" + resistance_flags = NONE + +/obj/item/trash/pistachios + name = "pistachios pack" + icon_state = "pistachios_pack" + +/obj/item/trash/semki + name = "semki pack" + icon_state = "semki_pack" + +/obj/item/trash/tray + name = "tray" + icon_state = "tray" + resistance_flags = NONE + +/obj/item/trash/candle + name = "candle" + icon = 'icons/obj/candle.dmi' + icon_state = "candle4" + +/obj/item/trash/can + name = "crushed can" + icon_state = "cola" + resistance_flags = NONE + grind_results = list(/datum/reagent/aluminium = 10) + +/obj/item/trash/can/food/peaches + name = "canned peaches" + icon = 'icons/obj/food/food.dmi' + icon_state = "peachcan_empty" + +/obj/item/trash/can/food/peaches/maint + name = "Maintenance Peaches" + icon_state = "peachcanmaint_empty" + +/obj/item/trash/can/food/beans + name = "tin of beans" + icon = 'icons/obj/food/food.dmi' + icon_state = "beans_empty" + +/obj/item/trash/can/Initialize() + . = ..() + pixel_x = rand(-4,4) + pixel_y = rand(-4,4) + +/obj/item/trash/attack(mob/M, mob/living/user) + return diff --git a/code/game/objects/items/vending_items.dm b/code/game/objects/items/vending_items.dm index af647550ea0f..2964d31259d9 100644 --- a/code/game/objects/items/vending_items.dm +++ b/code/game/objects/items/vending_items.dm @@ -1,51 +1,51 @@ -/* - Vending machine refills can be found at /code/modules/vending/ within each vending machine's respective file -*/ -/obj/item/vending_refill - name = "resupply canister" - var/machine_name = "Generic" - - icon = 'icons/obj/vending_restock.dmi' - icon_state = "refill_snack" - item_state = "restock_unit" - desc = "A vending machine restock cart." - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - flags_1 = CONDUCT_1 - force = 7 - throwforce = 10 - throw_speed = 1 - throw_range = 7 - w_class = WEIGHT_CLASS_BULKY - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 30) - - // Built automatically from the corresponding vending machine. - // If null, considered to be full. Otherwise, is list(/typepath = amount). - var/list/products - var/list/contraband - var/list/premium - -/obj/item/vending_refill/Initialize(mapload) - . = ..() - name = "\improper [machine_name] restocking unit" - -/obj/item/vending_refill/examine(mob/user) - . = ..() - var/num = get_part_rating() - if (num == INFINITY) - . += "It's sealed tight, completely full of supplies." - else if (num == 0) - . += "It's empty!" - else - . += "It can restock [num] item\s." - -/obj/item/vending_refill/get_part_rating() - if (!products || !contraband || !premium) - return INFINITY - . = 0 - for(var/key in products) - . += products[key] - for(var/key in contraband) - . += contraband[key] - for(var/key in premium) - . += premium[key] +/* + Vending machine refills can be found at /code/modules/vending/ within each vending machine's respective file +*/ +/obj/item/vending_refill + name = "resupply canister" + var/machine_name = "Generic" + + icon = 'icons/obj/vending_restock.dmi' + icon_state = "refill_snack" + item_state = "restock_unit" + desc = "A vending machine restock cart." + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + flags_1 = CONDUCT_1 + force = 7 + throwforce = 10 + throw_speed = 1 + throw_range = 7 + w_class = WEIGHT_CLASS_BULKY + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 30) + + // Built automatically from the corresponding vending machine. + // If null, considered to be full. Otherwise, is list(/typepath = amount). + var/list/products + var/list/contraband + var/list/premium + +/obj/item/vending_refill/Initialize(mapload) + . = ..() + name = "\improper [machine_name] restocking unit" + +/obj/item/vending_refill/examine(mob/user) + . = ..() + var/num = get_part_rating() + if (num == INFINITY) + . += "It's sealed tight, completely full of supplies." + else if (num == 0) + . += "It's empty!" + else + . += "It can restock [num] item\s." + +/obj/item/vending_refill/get_part_rating() + if (!products || !contraband || !premium) + return INFINITY + . = 0 + for(var/key in products) + . += products[key] + for(var/key in contraband) + . += contraband[key] + for(var/key in premium) + . += premium[key] diff --git a/code/game/objects/items/weaponry.dm b/code/game/objects/items/weaponry.dm index f1573dbfd4bd..84f9654a19d1 100644 --- a/code/game/objects/items/weaponry.dm +++ b/code/game/objects/items/weaponry.dm @@ -1,880 +1,880 @@ -/obj/item/banhammer - desc = "A banhammer." - name = "banhammer" - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "toyhammer" - slot_flags = ITEM_SLOT_BELT - throwforce = 0 - force = 1 - w_class = WEIGHT_CLASS_TINY - throw_speed = 3 - throw_range = 7 - attack_verb = list("banned") - max_integrity = 200 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 70) - resistance_flags = FIRE_PROOF - -/obj/item/banhammer/suicide_act(mob/user) - user.visible_message("[user] is hitting [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to ban [user.p_them()]self from life.") - return (BRUTELOSS|FIRELOSS|TOXLOSS|OXYLOSS) -/* -oranges says: This is a meme relating to the english translation of the ss13 russian wiki page on lurkmore. -mrdoombringer sez: and remember kids, if you try and PR a fix for this item's grammar, you are admitting that you are, indeed, a newfriend. -for further reading, please see: https://github.com/tgstation/tgstation/pull/30173 and https://translate.google.com/translate?sl=auto&tl=en&js=y&prev=_t&hl=en&ie=UTF-8&u=%2F%2Flurkmore.to%2FSS13&edit-text=&act=url -*/ -/obj/item/banhammer/attack(mob/M, mob/user) - if(user.zone_selected == BODY_ZONE_HEAD) - M.visible_message("[user] is stroking the head of [M] with a banhammer.", "[user] is stroking your head with a banhammer.", "You hear a banhammer stroking a head.") - 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.") - 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) - -/obj/item/sord - name = "\improper SORD" - desc = "This thing is so unspeakably shitty you are having a hard time even holding it." - icon_state = "sord" - item_state = "sord" - lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' - slot_flags = ITEM_SLOT_BELT - force = 2 - throwforce = 1 - w_class = WEIGHT_CLASS_NORMAL - hitsound = 'sound/weapons/bladeslice.ogg' - attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") - -/obj/item/sord/suicide_act(mob/user) - user.visible_message("[user] is trying to impale [user.p_them()]self with [src]! It might be a suicide attempt if it weren't so shitty.", \ - "You try to impale yourself with [src], but it's USELESS...") - return SHAME - -/obj/item/claymore - name = "claymore" - desc = "What are you standing around staring at this for? Get to killing!" - icon_state = "claymore" - item_state = "claymore" - lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' - hitsound = 'sound/weapons/bladeslice.ogg' - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_BACK - force = 40 - throwforce = 10 - w_class = WEIGHT_CLASS_NORMAL - attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") - block_chance = 50 - sharpness = IS_SHARP - max_integrity = 200 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50) - resistance_flags = FIRE_PROOF - -/obj/item/claymore/Initialize() - . = ..() - AddComponent(/datum/component/butchering, 40, 105) - -/obj/item/claymore/suicide_act(mob/user) - user.visible_message("[user] is falling on [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return(BRUTELOSS) - -/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 //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 - attack_verb = list("brutalized", "eviscerated", "disemboweled", "hacked", "carved", "cleaved") //ONLY THE MOST VISCERAL ATTACK VERBS - var/notches = 0 //HOW MANY PEOPLE HAVE BEEN SLAIN WITH THIS BLADE - var/obj/item/disk/nuclear/nuke_disk //OUR STORED NUKE DISK - -/obj/item/claymore/highlander/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, HIGHLANDER) - START_PROCESSING(SSobj, src) - -/obj/item/claymore/highlander/Destroy() - if(nuke_disk) - nuke_disk.forceMove(get_turf(src)) - nuke_disk.visible_message("The nuke disk is vulnerable!") - nuke_disk = null - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/item/claymore/highlander/process() - if(ishuman(loc)) - var/mob/living/carbon/human/H = loc - loc.layer = LARGE_MOB_LAYER //NO HIDING BEHIND PLANTS FOR YOU, DICKWEED (HA GET IT, BECAUSE WEEDS ARE PLANTS) - H.bleedsuppress = TRUE //AND WE WON'T BLEED OUT LIKE COWARDS - else - if(!(flags_1 & ADMIN_SPAWNED_1)) - qdel(src) - - -/obj/item/claymore/highlander/pickup(mob/living/user) - . = ..() - to_chat(user, "The power of Scotland protects you! You are shielded from all stuns and knockdowns.") - user.add_stun_absorption("highlander", INFINITY, 1, " is protected by the power of Scotland!", "The power of Scotland absorbs the stun!", " is protected by the power of Scotland!") - user.ignore_slowdown(HIGHLANDER) - -/obj/item/claymore/highlander/dropped(mob/living/user) - . = ..() - user.unignore_slowdown(HIGHLANDER) - -/obj/item/claymore/highlander/examine(mob/user) - . = ..() - . += "It has [!notches ? "nothing" : "[notches] notches"] scratched into the blade." - if(nuke_disk) - . += "It's holding the nuke disk!" - -/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(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() - -/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.mind.special_role == "highlander" && (!closest_victim || get_dist(user, closest_victim) < closest_distance)) - closest_victim = H - if(!closest_victim) - to_chat(user, "[src] thrums for a moment and falls dark. Perhaps there's nobody nearby.") - return - to_chat(user, "[src] thrums and points to the [dir2text(get_dir(user, closest_victim))].") - -/obj/item/claymore/highlander/IsReflect() - return 1 //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++ - force++ - var/new_name = name - switch(notches) - if(1) - to_chat(user, "Your first kill - hopefully one of many. You scratch a notch into [src]'s blade.") - to_chat(user, "You feel your fallen foe's soul entering your blade, restoring your wounds!") - new_name = "notched claymore" - if(2) - to_chat(user, "Another falls before you. Another soul fuses with your own. Another notch in the blade.") - new_name = "double-notched claymore" - add_atom_colour(rgb(255, 235, 235), ADMIN_COLOUR_PRIORITY) - if(3) - to_chat(user, "You're beginning to relish the thrill of battle.") - new_name = "triple-notched claymore" - add_atom_colour(rgb(255, 215, 215), ADMIN_COLOUR_PRIORITY) - if(4) - to_chat(user, "You've lost count of how many you've killed.") - new_name = "many-notched claymore" - add_atom_colour(rgb(255, 195, 195), ADMIN_COLOUR_PRIORITY) - if(5) - to_chat(user, "Five voices now echo in your mind, cheering the slaughter.") - new_name = "battle-tested claymore" - add_atom_colour(rgb(255, 175, 175), ADMIN_COLOUR_PRIORITY) - if(6) - to_chat(user, "Is this what the vikings felt like? Visions of glory fill your head as you slay your sixth foe.") - new_name = "battle-scarred claymore" - add_atom_colour(rgb(255, 155, 155), ADMIN_COLOUR_PRIORITY) - if(7) - to_chat(user, "Kill. Butcher. Conquer.") - new_name = "vicious claymore" - add_atom_colour(rgb(255, 135, 135), ADMIN_COLOUR_PRIORITY) - if(8) - to_chat(user, "IT NEVER GETS OLD. THE SCREAMING. THE BLOOD AS IT SPRAYS ACROSS YOUR FACE.") - new_name = "bloodthirsty claymore" - add_atom_colour(rgb(255, 115, 115), ADMIN_COLOUR_PRIORITY) - if(9) - to_chat(user, "ANOTHER ONE FALLS TO YOUR BLOWS. ANOTHER WEAKLING UNFIT TO LIVE.") - new_name = "gore-stained claymore" - add_atom_colour(rgb(255, 95, 95), ADMIN_COLOUR_PRIORITY) - if(10) - user.visible_message("[user]'s eyes light up with a vengeful fire!", \ - "YOU FEEL THE POWER OF VALHALLA FLOWING THROUGH YOU! THERE CAN BE ONLY ONE!!!") - user.update_icons() - new_name = "GORE-DRENCHED CLAYMORE OF [pick("THE WHIMSICAL SLAUGHTER", "A THOUSAND SLAUGHTERED CATTLE", "GLORY AND VALHALLA", "ANNIHILATION", "OBLITERATION")]" - icon_state = "claymore_gold" - item_state = "cultblade" - remove_atom_colour(ADMIN_COLOUR_PRIORITY) - - name = new_name - playsound(user, 'sound/items/screwdriver2.ogg', 50, TRUE) - -/obj/item/katana - name = "katana" - desc = "Woefully underpowered in D20." - icon_state = "katana" - item_state = "katana" - lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_BACK - force = 40 - throwforce = 10 - w_class = WEIGHT_CLASS_HUGE - hitsound = 'sound/weapons/bladeslice.ogg' - attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") - block_chance = 50 - sharpness = IS_SHARP - max_integrity = 200 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50) - resistance_flags = FIRE_PROOF - -/obj/item/katana/cursed - slot_flags = null - -/obj/item/katana/suicide_act(mob/user) - user.visible_message("[user] is slitting [user.p_their()] stomach open with [src]! It looks like [user.p_theyre()] trying to commit seppuku!") - return(BRUTELOSS) - -/obj/item/wirerod - name = "wired rod" - desc = "A rod with some wire wrapped around the top. It'd be easy to attach something to the top bit." - icon_state = "wiredrod" - item_state = "rods" - flags_1 = CONDUCT_1 - force = 9 - throwforce = 10 - w_class = WEIGHT_CLASS_NORMAL - custom_materials = list(/datum/material/iron=1150, /datum/material/glass=75) - attack_verb = list("hit", "bludgeoned", "whacked", "bonked") - -/obj/item/wirerod/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/shard)) - var/obj/item/spear/S = new /obj/item/spear - - remove_item_from_storage(user) - if (!user.transferItemToLoc(I, S)) - return - S.CheckParts(list(I)) - qdel(src) - - 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))) - var/obj/item/melee/baton/cattleprod/P = new /obj/item/melee/baton/cattleprod - - remove_item_from_storage(user) - - to_chat(user, "You fasten [I] to the top of the rod with the cable.") - - qdel(I) - qdel(src) - - user.put_in_hands(P) - else - return ..() - - -/obj/item/throwing_star - name = "throwing star" - desc = "An ancient weapon still used to this day, due to its ease of lodging itself into its victim's body parts." - icon_state = "throwingstar" - item_state = "eshield0" - lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi' - force = 2 - throwforce = 20 //20 + 2 (WEIGHT_CLASS_SMALL) * 4 (EMBEDDED_IMPACT_PAIN_MULTIPLIER) = 28 damage on hit due to guaranteed embedding - 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 = IS_SHARP - custom_materials = list(/datum/material/iron=500, /datum/material/glass=500) - resistance_flags = FIRE_PROOF - -/obj/item/throwing_star/stamina - name = "shock throwing star" - desc = "An aerodynamic disc designed to cause excruciating pain when stuck inside fleeing targets, hopefully without causing fatal harm." - throwforce = 5 - embedding = list("pain_chance" = 5, "embed_chance" = 100, "fall_chance" = 0, "jostle_chance" = 10, "pain_stam_pct" = 0.8, "jostle_pain_mult" = 3) - -/obj/item/throwing_star/toy - name = "toy throwing star" - desc = "An aerodynamic disc strapped with adhesive for sticking to people, good for playing pranks and getting yourself killed by security." - sharpness = IS_BLUNT - force = 0 - throwforce = 0 - embedding = list("pain_mult" = 0, "jostle_pain_mult" = 0, "embed_chance" = 100, "fall_chance" = 0) - -/obj/item/throwing_star/magspear - name = "magnetic spear" - desc = "A reusable spear that is typically loaded into kinetic spearguns." - icon = 'icons/obj/ammo.dmi' - icon_state = "magspear" - throwforce = 25 //kills regular carps in one hit - force = 10 - throw_range = 0 //throwing these invalidates the speargun - attack_verb = list("stabbed", "ripped", "gored", "impaled") - embedding = list("pain_mult" = 8, "embed_chance" = 100, "fall_chance" = 0, "impact_pain_mult" = 15) //55 damage+embed on hit - -/obj/item/switchblade - name = "switchblade" - icon_state = "switchblade" - lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' - desc = "A sharp, concealable, spring-loaded knife." - flags_1 = CONDUCT_1 - force = 3 - w_class = WEIGHT_CLASS_SMALL - throwforce = 5 - throw_speed = 3 - throw_range = 6 - custom_materials = list(/datum/material/iron=12000) - hitsound = 'sound/weapons/genhit.ogg' - attack_verb = list("stubbed", "poked") - resistance_flags = FIRE_PROOF - var/extended = 0 - -/obj/item/switchblade/attack_self(mob/user) - extended = !extended - playsound(src.loc, 'sound/weapons/batonextend.ogg', 50, TRUE) - if(extended) - force = 20 - w_class = WEIGHT_CLASS_NORMAL - throwforce = 23 - icon_state = "switchblade_ext" - attack_verb = list("slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") - hitsound = 'sound/weapons/bladeslice.ogg' - sharpness = IS_SHARP - else - force = 3 - w_class = WEIGHT_CLASS_SMALL - throwforce = 5 - icon_state = "switchblade" - attack_verb = list("stubbed", "poked") - hitsound = 'sound/weapons/genhit.ogg' - sharpness = IS_BLUNT - -/obj/item/switchblade/suicide_act(mob/user) - user.visible_message("[user] is slitting [user.p_their()] own throat with [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return (BRUTELOSS) - -/obj/item/phone - name = "red phone" - desc = "Should anything ever go wrong..." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "red_phone" - force = 3 - throwforce = 2 - throw_speed = 3 - throw_range = 4 - w_class = WEIGHT_CLASS_SMALL - attack_verb = list("called", "rang") - hitsound = 'sound/weapons/ring.ogg' - -/obj/item/phone/suicide_act(mob/user) - if(locate(/obj/structure/chair/stool) in user.loc) - user.visible_message("[user] begins to tie a noose with [src]'s cord! It looks like [user.p_theyre()] trying to commit suicide!") - else - user.visible_message("[user] is strangling [user.p_them()]self with [src]'s cord! It looks like [user.p_theyre()] trying to commit suicide!") - return(OXYLOSS) - -/obj/item/cane - name = "cane" - desc = "A cane used by a true gentleman. Or a clown." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "cane" - item_state = "stick" - lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' - force = 5 - throwforce = 5 - w_class = WEIGHT_CLASS_SMALL - custom_materials = list(/datum/material/iron=50) - attack_verb = list("bludgeoned", "whacked", "disciplined", "thrashed") - -/obj/item/staff - name = "wizard staff" - desc = "Apparently a staff used by the wizard." - icon = 'icons/obj/wizard.dmi' - icon_state = "staff" - lefthand_file = 'icons/mob/inhands/weapons/staves_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/staves_righthand.dmi' - force = 3 - throwforce = 5 - throw_speed = 2 - throw_range = 5 - w_class = WEIGHT_CLASS_SMALL - armour_penetration = 100 - attack_verb = list("bludgeoned", "whacked", "disciplined") - resistance_flags = FLAMMABLE - -/obj/item/staff/broom - name = "broom" - desc = "Used for sweeping, and flying into the night while cackling. Black cat not included." - icon = 'icons/obj/wizard.dmi' - icon_state = "broom" - resistance_flags = FLAMMABLE - -/obj/item/staff/stick - name = "stick" - desc = "A great tool to drag someone else's drinks across the bar." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "cane" - item_state = "stick" - lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' - force = 3 - throwforce = 5 - throw_speed = 2 - throw_range = 5 - w_class = WEIGHT_CLASS_SMALL - -/obj/item/ectoplasm - name = "ectoplasm" - desc = "Spooky." - gender = PLURAL - icon = 'icons/obj/wizard.dmi' - icon_state = "ectoplasm" - -/obj/item/ectoplasm/suicide_act(mob/user) - 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." - icon_state = "chainsaw_on" - item_state = "mounted_chainsaw" - 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_HUGE - force = 24 - throwforce = 0 - throw_range = 0 - throw_speed = 0 - sharpness = IS_SHARP - attack_verb = list("sawed", "torn", "cut", "chopped", "diced") - hitsound = 'sound/weapons/chainsawhit.ogg' - tool_behaviour = TOOL_SAW - toolspeed = 1 - -/obj/item/mounted_chainsaw/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT) - -/obj/item/mounted_chainsaw/Destroy() - var/obj/item/bodypart/part - new /obj/item/chainsaw(get_turf(src)) - if(iscarbon(loc)) - var/mob/living/carbon/holder = loc - var/index = holder.get_held_index_of_item(src) - if(index) - part = holder.hand_bodyparts[index] - . = ..() - if(part) - part.drop_limb() - -/obj/item/statuebust - name = "bust" - desc = "A priceless ancient marble bust, the kind that belongs in a museum." //or you can hit people with it - icon = 'icons/obj/statue.dmi' - icon_state = "bust" - force = 15 - throwforce = 10 - throw_speed = 5 - throw_range = 2 - attack_verb = list("busted") - var/impressiveness = 45 - -/obj/item/statuebust/Initialize() - . = ..() - AddComponent(/datum/component/art, impressiveness) - addtimer(CALLBACK(src, /datum.proc/_AddComponent, list(/datum/component/beauty, 1000)), 0) - -/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" - desc = "For the beating to death of lizards with their own tails." - icon_state = "tailclub" - force = 14 - throwforce = 1 // why are you throwing a club do you even weapon - throw_speed = 1 - throw_range = 1 - attack_verb = list("clubbed", "bludgeoned") - -/obj/item/melee/chainofcommand/tailwhip - name = "liz o' nine tails" - desc = "A whip fashioned from the severed tails of lizards." - icon_state = "tailwhip" - item_state = "tailwhip" - item_flags = NONE - -/obj/item/melee/chainofcommand/tailwhip/kitty - name = "cat o' nine tails" - desc = "A whip fashioned from the severed tails of cats." - icon_state = "catwhip" - item_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." - icon_state = "skateboard" - item_state = "skateboard" - force = 12 - throwforce = 4 - w_class = WEIGHT_CLASS_NORMAL - attack_verb = list("smacked", "whacked", "slammed", "smashed") - ///The vehicle counterpart for the board - var/board_item_type = /obj/vehicle/ridden/scooter/skateboard - -/obj/item/melee/skateboard/attack_self(mob/user) - var/obj/vehicle/ridden/scooter/skateboard/S = new board_item_type(get_turf(user))//this probably has fucky interactions with telekinesis but for the record it wasnt my fault - S.buckle_mob(user) - qdel(src) - -/obj/item/melee/skateboard/pro - name = "skateboard" - desc = "A RaDSTORMz 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 - -/obj/item/melee/skateboard/hoverboard - name = "hoverboard" - desc = "A blast from the past, so retro!" - icon_state = "hoverboard_red" - item_state = "hoverboard_red" - board_item_type = /obj/vehicle/ridden/scooter/skateboard/hoverboard - custom_premium_price = 2015 - -/obj/item/melee/skateboard/hoverboard/admin - name = "\improper 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" - board_item_type = /obj/vehicle/ridden/scooter/skateboard/hoverboard/admin - -/obj/item/melee/baseball_bat - name = "baseball bat" - desc = "There ain't a skull in the league that can withstand a swatter." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "baseball_bat" - item_state = "baseball_bat" - lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' - force = 10 - throwforce = 12 - attack_verb = list("beat", "smacked") - custom_materials = list(/datum/material/wood = MINERAL_MATERIAL_AMOUNT * 3.5) - w_class = WEIGHT_CLASS_HUGE - var/homerun_ready = 0 - var/homerun_able = 0 - -/obj/item/melee/baseball_bat/homerun - name = "home run bat" - desc = "This thing looks dangerous... Dangerously good at baseball, that is." - homerun_able = 1 - -/obj/item/melee/baseball_bat/attack_self(mob/user) - if(!homerun_able) - ..() - return - if(homerun_ready) - 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, TRUE) - if(do_after(user, 90, target = src)) - to_chat(user, "You gather power! Time for a home run!") - homerun_ready = 1 - ..() - -/obj/item/melee/baseball_bat/attack(mob/living/target, mob/living/user) - . = ..() - 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) - SSexplosions.medturf += throw_target - playsound(get_turf(src), 'sound/weapons/homerun.ogg', 100, TRUE) - homerun_ready = 0 - return - else if(!target.anchored) - target.throw_at(throw_target, rand(1,2), 7, user) - -/obj/item/melee/baseball_bat/ablative - name = "metal baseball bat" - desc = "This bat is made of highly reflective, highly armored material." - icon_state = "baseball_bat_metal" - item_state = "baseball_bat_metal" - force = 12 - throwforce = 15 - -/obj/item/melee/baseball_bat/ablative/IsReflect()//some day this will reflect thrown items instead of lasers - var/picksound = rand(1,2) - var/turf = get_turf(src) - if(picksound == 1) - playsound(turf, 'sound/weapons/effects/batreflect1.ogg', 50, TRUE) - if(picksound == 2) - playsound(turf, 'sound/weapons/effects/batreflect2.ogg', 50, TRUE) - return 1 - -/obj/item/melee/flyswatter - name = "flyswatter" - desc = "Useful for killing insects of all sizes." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "flyswatter" - item_state = "flyswatter" - lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' - force = 1 - throwforce = 1 - attack_verb = list("swatted", "smacked") - hitsound = 'sound/effects/snap.ogg' - 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 - -/obj/item/melee/flyswatter/Initialize() - . = ..() - strong_against = typecacheof(list( - /mob/living/simple_animal/hostile/poison/bees/, - /mob/living/simple_animal/butterfly, - /mob/living/simple_animal/hostile/cockroach, - /obj/item/queen_bee - )) - - -/obj/item/melee/flyswatter/afterattack(atom/target, mob/user, proximity_flag) - . = ..() - if(proximity_flag) - if(is_type_in_typecache(target, strong_against)) - new /obj/effect/decal/cleanable/insectguts(target.drop_location()) - to_chat(user, "You easily splat the [target].") - if(istype(target, /mob/living/)) - var/mob/living/bug = target - bug.death(1) - else - qdel(target) - -/obj/item/circlegame - name = "circled hand" - desc = "If somebody looks at this while it's below your waist, you get to bop them." - icon_state = "madeyoulook" - force = 0 - throwforce = 0 - item_flags = DROPDEL | ABSTRACT - attack_verb = list("bopped") - -/obj/item/circlegame/Initialize() - . = ..() - var/mob/living/owner = loc - if(!istype(owner)) - return - RegisterSignal(owner, COMSIG_PARENT_EXAMINE, .proc/ownerExamined) - -/obj/item/circlegame/Destroy() - var/mob/owner = loc - if(!istype(owner)) - return - UnregisterSignal(owner, COMSIG_PARENT_EXAMINE) - . = ..() - -/// Stage 1: The mistake is made -/obj/item/circlegame/proc/ownerExamined(mob/living/owner, mob/living/sucker) - if(!istype(sucker) || !in_range(owner, sucker)) - return - addtimer(CALLBACK(src, .proc/waitASecond, owner, sucker), 4) - -/// Stage 2: Fear sets in -/obj/item/circlegame/proc/waitASecond(mob/living/owner, mob/living/sucker) - if(QDELETED(sucker) || QDELETED(src) || QDELETED(owner)) - return - - if(owner == sucker) // big mood - to_chat(owner, "Wait a second... you just looked at your own [src.name]!") - addtimer(CALLBACK(src, .proc/selfGottem, owner), 10) - else - to_chat(sucker, "Wait a second... was that a-") - addtimer(CALLBACK(src, .proc/GOTTEM, owner, sucker), 6) - -/// Stage 3A: We face our own failures -/obj/item/circlegame/proc/selfGottem(mob/living/owner) - if(QDELETED(src) || QDELETED(owner)) - return - - playsound(get_turf(owner), 'sound/effects/hit_punch.ogg', 50, TRUE, -1) - owner.visible_message("[owner] shamefully bops [owner.p_them()]self with [owner.p_their()] [src.name].", "You shamefully bop yourself with your [src.name].", \ - "You hear a dull thud!") - log_combat(owner, owner, "bopped", src.name, "(self)") - owner.do_attack_animation(owner) - owner.apply_damage(100, STAMINA) - owner.Knockdown(10) - qdel(src) - -/// Stage 3B: We face our reckoning (unless we moved away or they're incapacitated) -/obj/item/circlegame/proc/GOTTEM(mob/living/owner, mob/living/sucker) - if(QDELETED(sucker)) - return - - if(QDELETED(src) || QDELETED(owner)) - to_chat(sucker, "Nevermind... must've been your imagination...") - return - - if(!in_range(owner, sucker) || !(owner.mobility_flags & MOBILITY_USE)) - to_chat(sucker, "Phew... you moved away before [owner] noticed you saw [owner.p_their()] [src.name]...") - return - - 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) - - if(HAS_TRAIT(owner, TRAIT_HULK)) - owner.visible_message("[owner] bops [sucker] with [owner.p_their()] [src.name] much harder than intended, sending [sucker.p_them()] flying!", \ - "You bop [sucker] with your [src.name] much harder than intended, sending [sucker.p_them()] flying!", "You hear a sickening sound of flesh hitting flesh!", ignored_mobs=list(sucker)) - to_chat(sucker, "[owner] bops you incredibly hard with [owner.p_their()] [src.name], sending you flying!") - sucker.apply_damage(50, STAMINA) - sucker.Knockdown(50) - log_combat(owner, sucker, "bopped", src.name, "(setup- Hulk)") - var/atom/throw_target = get_edge_target_turf(sucker, owner.dir) - sucker.throw_at(throw_target, 6, 3, owner) - else - owner.visible_message("[owner] bops [sucker] with [owner.p_their()] [src.name]!", "You bop [sucker] with your [src.name]!", \ - "You hear a dull thud!", ignored_mobs=list(sucker)) - sucker.apply_damage(15, STAMINA) - log_combat(owner, sucker, "bopped", src.name, "(setup)") - to_chat(sucker, "[owner] bops you with [owner.p_their()] [src.name]!") - qdel(src) - -/obj/item/slapper - name = "slapper" - desc = "This is how real men fight." - icon_state = "latexballon" - item_state = "nothing" - force = 0 - throwforce = 0 - item_flags = DROPDEL | ABSTRACT - attack_verb = list("slapped") - hitsound = 'sound/effects/snap.ogg' - -/obj/item/slapper/attack(mob/M, mob/living/carbon/human/user) - if(ishuman(M)) - var/mob/living/carbon/human/L = M - if(L && L.dna && L.dna.species) - L.dna.species.stop_wagging_tail(M) - 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 - return TRUE - -/obj/item/extendohand - name = "extendo-hand" - desc = "Futuristic tech has allowed these classic spring-boxing toys to essentially act as a fully functional hand-operated hand prosthetic." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "extendohand" - item_state = "extendohand" - lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' - force = 0 - throwforce = 5 - reach = 2 - var/min_reach = 2 - -/obj/item/extendohand/acme - name = "\improper ACME Extendo-Hand" - desc = "A novelty extendo-hand produced by the ACME corporation. Originally designed to knock out roadrunners." - -/obj/item/extendohand/attack(atom/M, mob/living/carbon/human/user) - var/dist = get_dist(M, user) - if(dist < min_reach) - to_chat(user, "[M] is too close to use [src] on.") - return - M.attack_hand(user) - -/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 = list("whacked", "thwacked", "walloped", "socked") - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "gohei" - item_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' - righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' - name = "vibro sword" - desc = "A potent weapon capable of cutting through nearly anything. Wielding it in two hands will allow you to deflect gunfire." - armour_penetration = 100 - block_chance = 40 - force = 20 - throwforce = 20 - throw_speed = 4 - sharpness = IS_SHARP - attack_verb = list("cut", "sliced", "diced") - w_class = WEIGHT_CLASS_BULKY - slot_flags = ITEM_SLOT_BACK - hitsound = 'sound/weapons/bladeslice.ogg' - var/wielded = FALSE // track wielded status on item - -/obj/item/vibro_weapon/Initialize() - . = ..() - RegisterSignal(src, COMSIG_TWOHANDED_WIELD, .proc/on_wield) - RegisterSignal(src, COMSIG_TWOHANDED_UNWIELD, .proc/on_unwield) - -/obj/item/vibro_weapon/ComponentInitialize() - . = ..() - AddComponent(/datum/component/butchering, 20, 105) - AddComponent(/datum/component/two_handed, force_multiplier=2, icon_wielded="hfrequency1") - -/// triggered on wield of two handed item -/obj/item/vibro_weapon/proc/on_wield(obj/item/source, mob/user) - wielded = TRUE - -/// triggered on unwield of two handed item -/obj/item/vibro_weapon/proc/on_unwield(obj/item/source, mob/user) - wielded = FALSE - -/obj/item/vibro_weapon/update_icon_state() - icon_state = "hfrequency0" - -/obj/item/vibro_weapon/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(wielded) - final_block_chance *= 2 - if(wielded || attack_type != PROJECTILE_ATTACK) - if(prob(final_block_chance)) - if(attack_type == PROJECTILE_ATTACK) - owner.visible_message("[owner] deflects [attack_text] with [src]!") - playsound(src, pick('sound/weapons/bulletflyby.ogg', 'sound/weapons/bulletflyby2.ogg', 'sound/weapons/bulletflyby3.ogg'), 75, TRUE) - return 1 - else - owner.visible_message("[owner] parries [attack_text] with [src]!") - return 1 - return 0 +/obj/item/banhammer + desc = "A banhammer." + name = "banhammer" + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "toyhammer" + slot_flags = ITEM_SLOT_BELT + throwforce = 0 + force = 1 + w_class = WEIGHT_CLASS_TINY + throw_speed = 3 + throw_range = 7 + attack_verb = list("banned") + max_integrity = 200 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 70) + resistance_flags = FIRE_PROOF + +/obj/item/banhammer/suicide_act(mob/user) + user.visible_message("[user] is hitting [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to ban [user.p_them()]self from life.") + return (BRUTELOSS|FIRELOSS|TOXLOSS|OXYLOSS) +/* +oranges says: This is a meme relating to the english translation of the ss13 russian wiki page on lurkmore. +mrdoombringer sez: and remember kids, if you try and PR a fix for this item's grammar, you are admitting that you are, indeed, a newfriend. +for further reading, please see: https://github.com/tgstation/tgstation/pull/30173 and https://translate.google.com/translate?sl=auto&tl=en&js=y&prev=_t&hl=en&ie=UTF-8&u=%2F%2Flurkmore.to%2FSS13&edit-text=&act=url +*/ +/obj/item/banhammer/attack(mob/M, mob/user) + if(user.zone_selected == BODY_ZONE_HEAD) + M.visible_message("[user] is stroking the head of [M] with a banhammer.", "[user] is stroking your head with a banhammer.", "You hear a banhammer stroking a head.") + 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.") + 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) + +/obj/item/sord + name = "\improper SORD" + desc = "This thing is so unspeakably shitty you are having a hard time even holding it." + icon_state = "sord" + item_state = "sord" + lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' + slot_flags = ITEM_SLOT_BELT + force = 2 + throwforce = 1 + w_class = WEIGHT_CLASS_NORMAL + hitsound = 'sound/weapons/bladeslice.ogg' + attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") + +/obj/item/sord/suicide_act(mob/user) + user.visible_message("[user] is trying to impale [user.p_them()]self with [src]! It might be a suicide attempt if it weren't so shitty.", \ + "You try to impale yourself with [src], but it's USELESS...") + return SHAME + +/obj/item/claymore + name = "claymore" + desc = "What are you standing around staring at this for? Get to killing!" + icon_state = "claymore" + item_state = "claymore" + lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' + hitsound = 'sound/weapons/bladeslice.ogg' + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_BACK + force = 40 + throwforce = 10 + w_class = WEIGHT_CLASS_NORMAL + attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") + block_chance = 50 + sharpness = IS_SHARP + max_integrity = 200 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50) + resistance_flags = FIRE_PROOF + +/obj/item/claymore/Initialize() + . = ..() + AddComponent(/datum/component/butchering, 40, 105) + +/obj/item/claymore/suicide_act(mob/user) + user.visible_message("[user] is falling on [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return(BRUTELOSS) + +/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 //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 + attack_verb = list("brutalized", "eviscerated", "disemboweled", "hacked", "carved", "cleaved") //ONLY THE MOST VISCERAL ATTACK VERBS + var/notches = 0 //HOW MANY PEOPLE HAVE BEEN SLAIN WITH THIS BLADE + var/obj/item/disk/nuclear/nuke_disk //OUR STORED NUKE DISK + +/obj/item/claymore/highlander/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, HIGHLANDER) + START_PROCESSING(SSobj, src) + +/obj/item/claymore/highlander/Destroy() + if(nuke_disk) + nuke_disk.forceMove(get_turf(src)) + nuke_disk.visible_message("The nuke disk is vulnerable!") + nuke_disk = null + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/claymore/highlander/process() + if(ishuman(loc)) + var/mob/living/carbon/human/H = loc + loc.layer = LARGE_MOB_LAYER //NO HIDING BEHIND PLANTS FOR YOU, DICKWEED (HA GET IT, BECAUSE WEEDS ARE PLANTS) + H.bleedsuppress = TRUE //AND WE WON'T BLEED OUT LIKE COWARDS + else + if(!(flags_1 & ADMIN_SPAWNED_1)) + qdel(src) + + +/obj/item/claymore/highlander/pickup(mob/living/user) + . = ..() + to_chat(user, "The power of Scotland protects you! You are shielded from all stuns and knockdowns.") + user.add_stun_absorption("highlander", INFINITY, 1, " is protected by the power of Scotland!", "The power of Scotland absorbs the stun!", " is protected by the power of Scotland!") + user.ignore_slowdown(HIGHLANDER) + +/obj/item/claymore/highlander/dropped(mob/living/user) + . = ..() + user.unignore_slowdown(HIGHLANDER) + +/obj/item/claymore/highlander/examine(mob/user) + . = ..() + . += "It has [!notches ? "nothing" : "[notches] notches"] scratched into the blade." + if(nuke_disk) + . += "It's holding the nuke disk!" + +/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(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() + +/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.mind.special_role == "highlander" && (!closest_victim || get_dist(user, closest_victim) < closest_distance)) + closest_victim = H + if(!closest_victim) + to_chat(user, "[src] thrums for a moment and falls dark. Perhaps there's nobody nearby.") + return + to_chat(user, "[src] thrums and points to the [dir2text(get_dir(user, closest_victim))].") + +/obj/item/claymore/highlander/IsReflect() + return 1 //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++ + force++ + var/new_name = name + switch(notches) + if(1) + to_chat(user, "Your first kill - hopefully one of many. You scratch a notch into [src]'s blade.") + to_chat(user, "You feel your fallen foe's soul entering your blade, restoring your wounds!") + new_name = "notched claymore" + if(2) + to_chat(user, "Another falls before you. Another soul fuses with your own. Another notch in the blade.") + new_name = "double-notched claymore" + add_atom_colour(rgb(255, 235, 235), ADMIN_COLOUR_PRIORITY) + if(3) + to_chat(user, "You're beginning to relish the thrill of battle.") + new_name = "triple-notched claymore" + add_atom_colour(rgb(255, 215, 215), ADMIN_COLOUR_PRIORITY) + if(4) + to_chat(user, "You've lost count of how many you've killed.") + new_name = "many-notched claymore" + add_atom_colour(rgb(255, 195, 195), ADMIN_COLOUR_PRIORITY) + if(5) + to_chat(user, "Five voices now echo in your mind, cheering the slaughter.") + new_name = "battle-tested claymore" + add_atom_colour(rgb(255, 175, 175), ADMIN_COLOUR_PRIORITY) + if(6) + to_chat(user, "Is this what the vikings felt like? Visions of glory fill your head as you slay your sixth foe.") + new_name = "battle-scarred claymore" + add_atom_colour(rgb(255, 155, 155), ADMIN_COLOUR_PRIORITY) + if(7) + to_chat(user, "Kill. Butcher. Conquer.") + new_name = "vicious claymore" + add_atom_colour(rgb(255, 135, 135), ADMIN_COLOUR_PRIORITY) + if(8) + to_chat(user, "IT NEVER GETS OLD. THE SCREAMING. THE BLOOD AS IT SPRAYS ACROSS YOUR FACE.") + new_name = "bloodthirsty claymore" + add_atom_colour(rgb(255, 115, 115), ADMIN_COLOUR_PRIORITY) + if(9) + to_chat(user, "ANOTHER ONE FALLS TO YOUR BLOWS. ANOTHER WEAKLING UNFIT TO LIVE.") + new_name = "gore-stained claymore" + add_atom_colour(rgb(255, 95, 95), ADMIN_COLOUR_PRIORITY) + if(10) + user.visible_message("[user]'s eyes light up with a vengeful fire!", \ + "YOU FEEL THE POWER OF VALHALLA FLOWING THROUGH YOU! THERE CAN BE ONLY ONE!!!") + user.update_icons() + new_name = "GORE-DRENCHED CLAYMORE OF [pick("THE WHIMSICAL SLAUGHTER", "A THOUSAND SLAUGHTERED CATTLE", "GLORY AND VALHALLA", "ANNIHILATION", "OBLITERATION")]" + icon_state = "claymore_gold" + item_state = "cultblade" + remove_atom_colour(ADMIN_COLOUR_PRIORITY) + + name = new_name + playsound(user, 'sound/items/screwdriver2.ogg', 50, TRUE) + +/obj/item/katana + name = "katana" + desc = "Woefully underpowered in D20." + icon_state = "katana" + item_state = "katana" + lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_BACK + force = 40 + throwforce = 10 + w_class = WEIGHT_CLASS_HUGE + hitsound = 'sound/weapons/bladeslice.ogg' + attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") + block_chance = 50 + sharpness = IS_SHARP + max_integrity = 200 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50) + resistance_flags = FIRE_PROOF + +/obj/item/katana/cursed + slot_flags = null + +/obj/item/katana/suicide_act(mob/user) + user.visible_message("[user] is slitting [user.p_their()] stomach open with [src]! It looks like [user.p_theyre()] trying to commit seppuku!") + return(BRUTELOSS) + +/obj/item/wirerod + name = "wired rod" + desc = "A rod with some wire wrapped around the top. It'd be easy to attach something to the top bit." + icon_state = "wiredrod" + item_state = "rods" + flags_1 = CONDUCT_1 + force = 9 + throwforce = 10 + w_class = WEIGHT_CLASS_NORMAL + custom_materials = list(/datum/material/iron=1150, /datum/material/glass=75) + attack_verb = list("hit", "bludgeoned", "whacked", "bonked") + +/obj/item/wirerod/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/shard)) + var/obj/item/spear/S = new /obj/item/spear + + remove_item_from_storage(user) + if (!user.transferItemToLoc(I, S)) + return + S.CheckParts(list(I)) + qdel(src) + + 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))) + var/obj/item/melee/baton/cattleprod/P = new /obj/item/melee/baton/cattleprod + + remove_item_from_storage(user) + + to_chat(user, "You fasten [I] to the top of the rod with the cable.") + + qdel(I) + qdel(src) + + user.put_in_hands(P) + else + return ..() + + +/obj/item/throwing_star + name = "throwing star" + desc = "An ancient weapon still used to this day, due to its ease of lodging itself into its victim's body parts." + icon_state = "throwingstar" + item_state = "eshield0" + lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi' + force = 2 + throwforce = 20 //20 + 2 (WEIGHT_CLASS_SMALL) * 4 (EMBEDDED_IMPACT_PAIN_MULTIPLIER) = 28 damage on hit due to guaranteed embedding + 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 = IS_SHARP + custom_materials = list(/datum/material/iron=500, /datum/material/glass=500) + resistance_flags = FIRE_PROOF + +/obj/item/throwing_star/stamina + name = "shock throwing star" + desc = "An aerodynamic disc designed to cause excruciating pain when stuck inside fleeing targets, hopefully without causing fatal harm." + throwforce = 5 + embedding = list("pain_chance" = 5, "embed_chance" = 100, "fall_chance" = 0, "jostle_chance" = 10, "pain_stam_pct" = 0.8, "jostle_pain_mult" = 3) + +/obj/item/throwing_star/toy + name = "toy throwing star" + desc = "An aerodynamic disc strapped with adhesive for sticking to people, good for playing pranks and getting yourself killed by security." + sharpness = IS_BLUNT + force = 0 + throwforce = 0 + embedding = list("pain_mult" = 0, "jostle_pain_mult" = 0, "embed_chance" = 100, "fall_chance" = 0) + +/obj/item/throwing_star/magspear + name = "magnetic spear" + desc = "A reusable spear that is typically loaded into kinetic spearguns." + icon = 'icons/obj/ammo.dmi' + icon_state = "magspear" + throwforce = 25 //kills regular carps in one hit + force = 10 + throw_range = 0 //throwing these invalidates the speargun + attack_verb = list("stabbed", "ripped", "gored", "impaled") + embedding = list("pain_mult" = 8, "embed_chance" = 100, "fall_chance" = 0, "impact_pain_mult" = 15) //55 damage+embed on hit + +/obj/item/switchblade + name = "switchblade" + icon_state = "switchblade" + lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' + desc = "A sharp, concealable, spring-loaded knife." + flags_1 = CONDUCT_1 + force = 3 + w_class = WEIGHT_CLASS_SMALL + throwforce = 5 + throw_speed = 3 + throw_range = 6 + custom_materials = list(/datum/material/iron=12000) + hitsound = 'sound/weapons/genhit.ogg' + attack_verb = list("stubbed", "poked") + resistance_flags = FIRE_PROOF + var/extended = 0 + +/obj/item/switchblade/attack_self(mob/user) + extended = !extended + playsound(src.loc, 'sound/weapons/batonextend.ogg', 50, TRUE) + if(extended) + force = 20 + w_class = WEIGHT_CLASS_NORMAL + throwforce = 23 + icon_state = "switchblade_ext" + attack_verb = list("slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") + hitsound = 'sound/weapons/bladeslice.ogg' + sharpness = IS_SHARP + else + force = 3 + w_class = WEIGHT_CLASS_SMALL + throwforce = 5 + icon_state = "switchblade" + attack_verb = list("stubbed", "poked") + hitsound = 'sound/weapons/genhit.ogg' + sharpness = IS_BLUNT + +/obj/item/switchblade/suicide_act(mob/user) + user.visible_message("[user] is slitting [user.p_their()] own throat with [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return (BRUTELOSS) + +/obj/item/phone + name = "red phone" + desc = "Should anything ever go wrong..." + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "red_phone" + force = 3 + throwforce = 2 + throw_speed = 3 + throw_range = 4 + w_class = WEIGHT_CLASS_SMALL + attack_verb = list("called", "rang") + hitsound = 'sound/weapons/ring.ogg' + +/obj/item/phone/suicide_act(mob/user) + if(locate(/obj/structure/chair/stool) in user.loc) + user.visible_message("[user] begins to tie a noose with [src]'s cord! It looks like [user.p_theyre()] trying to commit suicide!") + else + user.visible_message("[user] is strangling [user.p_them()]self with [src]'s cord! It looks like [user.p_theyre()] trying to commit suicide!") + return(OXYLOSS) + +/obj/item/cane + name = "cane" + desc = "A cane used by a true gentleman. Or a clown." + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "cane" + item_state = "stick" + lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' + force = 5 + throwforce = 5 + w_class = WEIGHT_CLASS_SMALL + custom_materials = list(/datum/material/iron=50) + attack_verb = list("bludgeoned", "whacked", "disciplined", "thrashed") + +/obj/item/staff + name = "wizard staff" + desc = "Apparently a staff used by the wizard." + icon = 'icons/obj/wizard.dmi' + icon_state = "staff" + lefthand_file = 'icons/mob/inhands/weapons/staves_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/staves_righthand.dmi' + force = 3 + throwforce = 5 + throw_speed = 2 + throw_range = 5 + w_class = WEIGHT_CLASS_SMALL + armour_penetration = 100 + attack_verb = list("bludgeoned", "whacked", "disciplined") + resistance_flags = FLAMMABLE + +/obj/item/staff/broom + name = "broom" + desc = "Used for sweeping, and flying into the night while cackling. Black cat not included." + icon = 'icons/obj/wizard.dmi' + icon_state = "broom" + resistance_flags = FLAMMABLE + +/obj/item/staff/stick + name = "stick" + desc = "A great tool to drag someone else's drinks across the bar." + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "cane" + item_state = "stick" + lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' + force = 3 + throwforce = 5 + throw_speed = 2 + throw_range = 5 + w_class = WEIGHT_CLASS_SMALL + +/obj/item/ectoplasm + name = "ectoplasm" + desc = "Spooky." + gender = PLURAL + icon = 'icons/obj/wizard.dmi' + icon_state = "ectoplasm" + +/obj/item/ectoplasm/suicide_act(mob/user) + 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." + icon_state = "chainsaw_on" + item_state = "mounted_chainsaw" + 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_HUGE + force = 24 + throwforce = 0 + throw_range = 0 + throw_speed = 0 + sharpness = IS_SHARP + attack_verb = list("sawed", "torn", "cut", "chopped", "diced") + hitsound = 'sound/weapons/chainsawhit.ogg' + tool_behaviour = TOOL_SAW + toolspeed = 1 + +/obj/item/mounted_chainsaw/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT) + +/obj/item/mounted_chainsaw/Destroy() + var/obj/item/bodypart/part + new /obj/item/chainsaw(get_turf(src)) + if(iscarbon(loc)) + var/mob/living/carbon/holder = loc + var/index = holder.get_held_index_of_item(src) + if(index) + part = holder.hand_bodyparts[index] + . = ..() + if(part) + part.drop_limb() + +/obj/item/statuebust + name = "bust" + desc = "A priceless ancient marble bust, the kind that belongs in a museum." //or you can hit people with it + icon = 'icons/obj/statue.dmi' + icon_state = "bust" + force = 15 + throwforce = 10 + throw_speed = 5 + throw_range = 2 + attack_verb = list("busted") + var/impressiveness = 45 + +/obj/item/statuebust/Initialize() + . = ..() + AddComponent(/datum/component/art, impressiveness) + addtimer(CALLBACK(src, /datum.proc/_AddComponent, list(/datum/component/beauty, 1000)), 0) + +/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" + desc = "For the beating to death of lizards with their own tails." + icon_state = "tailclub" + force = 14 + throwforce = 1 // why are you throwing a club do you even weapon + throw_speed = 1 + throw_range = 1 + attack_verb = list("clubbed", "bludgeoned") + +/obj/item/melee/chainofcommand/tailwhip + name = "liz o' nine tails" + desc = "A whip fashioned from the severed tails of lizards." + icon_state = "tailwhip" + item_state = "tailwhip" + item_flags = NONE + +/obj/item/melee/chainofcommand/tailwhip/kitty + name = "cat o' nine tails" + desc = "A whip fashioned from the severed tails of cats." + icon_state = "catwhip" + item_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." + icon_state = "skateboard" + item_state = "skateboard" + force = 12 + throwforce = 4 + w_class = WEIGHT_CLASS_NORMAL + attack_verb = list("smacked", "whacked", "slammed", "smashed") + ///The vehicle counterpart for the board + var/board_item_type = /obj/vehicle/ridden/scooter/skateboard + +/obj/item/melee/skateboard/attack_self(mob/user) + var/obj/vehicle/ridden/scooter/skateboard/S = new board_item_type(get_turf(user))//this probably has fucky interactions with telekinesis but for the record it wasnt my fault + S.buckle_mob(user) + qdel(src) + +/obj/item/melee/skateboard/pro + name = "skateboard" + desc = "A RaDSTORMz 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 + +/obj/item/melee/skateboard/hoverboard + name = "hoverboard" + desc = "A blast from the past, so retro!" + icon_state = "hoverboard_red" + item_state = "hoverboard_red" + board_item_type = /obj/vehicle/ridden/scooter/skateboard/hoverboard + custom_premium_price = 2015 + +/obj/item/melee/skateboard/hoverboard/admin + name = "\improper 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" + board_item_type = /obj/vehicle/ridden/scooter/skateboard/hoverboard/admin + +/obj/item/melee/baseball_bat + name = "baseball bat" + desc = "There ain't a skull in the league that can withstand a swatter." + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "baseball_bat" + item_state = "baseball_bat" + lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' + force = 10 + throwforce = 12 + attack_verb = list("beat", "smacked") + custom_materials = list(/datum/material/wood = MINERAL_MATERIAL_AMOUNT * 3.5) + w_class = WEIGHT_CLASS_HUGE + var/homerun_ready = 0 + var/homerun_able = 0 + +/obj/item/melee/baseball_bat/homerun + name = "home run bat" + desc = "This thing looks dangerous... Dangerously good at baseball, that is." + homerun_able = 1 + +/obj/item/melee/baseball_bat/attack_self(mob/user) + if(!homerun_able) + ..() + return + if(homerun_ready) + 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, TRUE) + if(do_after(user, 90, target = src)) + to_chat(user, "You gather power! Time for a home run!") + homerun_ready = 1 + ..() + +/obj/item/melee/baseball_bat/attack(mob/living/target, mob/living/user) + . = ..() + 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) + SSexplosions.medturf += throw_target + playsound(get_turf(src), 'sound/weapons/homerun.ogg', 100, TRUE) + homerun_ready = 0 + return + else if(!target.anchored) + target.throw_at(throw_target, rand(1,2), 7, user) + +/obj/item/melee/baseball_bat/ablative + name = "metal baseball bat" + desc = "This bat is made of highly reflective, highly armored material." + icon_state = "baseball_bat_metal" + item_state = "baseball_bat_metal" + force = 12 + throwforce = 15 + +/obj/item/melee/baseball_bat/ablative/IsReflect()//some day this will reflect thrown items instead of lasers + var/picksound = rand(1,2) + var/turf = get_turf(src) + if(picksound == 1) + playsound(turf, 'sound/weapons/effects/batreflect1.ogg', 50, TRUE) + if(picksound == 2) + playsound(turf, 'sound/weapons/effects/batreflect2.ogg', 50, TRUE) + return 1 + +/obj/item/melee/flyswatter + name = "flyswatter" + desc = "Useful for killing insects of all sizes." + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "flyswatter" + item_state = "flyswatter" + lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' + force = 1 + throwforce = 1 + attack_verb = list("swatted", "smacked") + hitsound = 'sound/effects/snap.ogg' + 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 + +/obj/item/melee/flyswatter/Initialize() + . = ..() + strong_against = typecacheof(list( + /mob/living/simple_animal/hostile/poison/bees/, + /mob/living/simple_animal/butterfly, + /mob/living/simple_animal/hostile/cockroach, + /obj/item/queen_bee + )) + + +/obj/item/melee/flyswatter/afterattack(atom/target, mob/user, proximity_flag) + . = ..() + if(proximity_flag) + if(is_type_in_typecache(target, strong_against)) + new /obj/effect/decal/cleanable/insectguts(target.drop_location()) + to_chat(user, "You easily splat the [target].") + if(istype(target, /mob/living/)) + var/mob/living/bug = target + bug.death(1) + else + qdel(target) + +/obj/item/circlegame + name = "circled hand" + desc = "If somebody looks at this while it's below your waist, you get to bop them." + icon_state = "madeyoulook" + force = 0 + throwforce = 0 + item_flags = DROPDEL | ABSTRACT + attack_verb = list("bopped") + +/obj/item/circlegame/Initialize() + . = ..() + var/mob/living/owner = loc + if(!istype(owner)) + return + RegisterSignal(owner, COMSIG_PARENT_EXAMINE, .proc/ownerExamined) + +/obj/item/circlegame/Destroy() + var/mob/owner = loc + if(!istype(owner)) + return + UnregisterSignal(owner, COMSIG_PARENT_EXAMINE) + . = ..() + +/// Stage 1: The mistake is made +/obj/item/circlegame/proc/ownerExamined(mob/living/owner, mob/living/sucker) + if(!istype(sucker) || !in_range(owner, sucker)) + return + addtimer(CALLBACK(src, .proc/waitASecond, owner, sucker), 4) + +/// Stage 2: Fear sets in +/obj/item/circlegame/proc/waitASecond(mob/living/owner, mob/living/sucker) + if(QDELETED(sucker) || QDELETED(src) || QDELETED(owner)) + return + + if(owner == sucker) // big mood + to_chat(owner, "Wait a second... you just looked at your own [src.name]!") + addtimer(CALLBACK(src, .proc/selfGottem, owner), 10) + else + to_chat(sucker, "Wait a second... was that a-") + addtimer(CALLBACK(src, .proc/GOTTEM, owner, sucker), 6) + +/// Stage 3A: We face our own failures +/obj/item/circlegame/proc/selfGottem(mob/living/owner) + if(QDELETED(src) || QDELETED(owner)) + return + + playsound(get_turf(owner), 'sound/effects/hit_punch.ogg', 50, TRUE, -1) + owner.visible_message("[owner] shamefully bops [owner.p_them()]self with [owner.p_their()] [src.name].", "You shamefully bop yourself with your [src.name].", \ + "You hear a dull thud!") + log_combat(owner, owner, "bopped", src.name, "(self)") + owner.do_attack_animation(owner) + owner.apply_damage(100, STAMINA) + owner.Knockdown(10) + qdel(src) + +/// Stage 3B: We face our reckoning (unless we moved away or they're incapacitated) +/obj/item/circlegame/proc/GOTTEM(mob/living/owner, mob/living/sucker) + if(QDELETED(sucker)) + return + + if(QDELETED(src) || QDELETED(owner)) + to_chat(sucker, "Nevermind... must've been your imagination...") + return + + if(!in_range(owner, sucker) || !(owner.mobility_flags & MOBILITY_USE)) + to_chat(sucker, "Phew... you moved away before [owner] noticed you saw [owner.p_their()] [src.name]...") + return + + 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) + + if(HAS_TRAIT(owner, TRAIT_HULK)) + owner.visible_message("[owner] bops [sucker] with [owner.p_their()] [src.name] much harder than intended, sending [sucker.p_them()] flying!", \ + "You bop [sucker] with your [src.name] much harder than intended, sending [sucker.p_them()] flying!", "You hear a sickening sound of flesh hitting flesh!", ignored_mobs=list(sucker)) + to_chat(sucker, "[owner] bops you incredibly hard with [owner.p_their()] [src.name], sending you flying!") + sucker.apply_damage(50, STAMINA) + sucker.Knockdown(50) + log_combat(owner, sucker, "bopped", src.name, "(setup- Hulk)") + var/atom/throw_target = get_edge_target_turf(sucker, owner.dir) + sucker.throw_at(throw_target, 6, 3, owner) + else + owner.visible_message("[owner] bops [sucker] with [owner.p_their()] [src.name]!", "You bop [sucker] with your [src.name]!", \ + "You hear a dull thud!", ignored_mobs=list(sucker)) + sucker.apply_damage(15, STAMINA) + log_combat(owner, sucker, "bopped", src.name, "(setup)") + to_chat(sucker, "[owner] bops you with [owner.p_their()] [src.name]!") + qdel(src) + +/obj/item/slapper + name = "slapper" + desc = "This is how real men fight." + icon_state = "latexballon" + item_state = "nothing" + force = 0 + throwforce = 0 + item_flags = DROPDEL | ABSTRACT + attack_verb = list("slapped") + hitsound = 'sound/effects/snap.ogg' + +/obj/item/slapper/attack(mob/M, mob/living/carbon/human/user) + if(ishuman(M)) + var/mob/living/carbon/human/L = M + if(L && L.dna && L.dna.species) + L.dna.species.stop_wagging_tail(M) + 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 + return TRUE + +/obj/item/extendohand + name = "extendo-hand" + desc = "Futuristic tech has allowed these classic spring-boxing toys to essentially act as a fully functional hand-operated hand prosthetic." + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "extendohand" + item_state = "extendohand" + lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' + force = 0 + throwforce = 5 + reach = 2 + var/min_reach = 2 + +/obj/item/extendohand/acme + name = "\improper ACME Extendo-Hand" + desc = "A novelty extendo-hand produced by the ACME corporation. Originally designed to knock out roadrunners." + +/obj/item/extendohand/attack(atom/M, mob/living/carbon/human/user) + var/dist = get_dist(M, user) + if(dist < min_reach) + to_chat(user, "[M] is too close to use [src] on.") + return + M.attack_hand(user) + +/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 = list("whacked", "thwacked", "walloped", "socked") + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "gohei" + item_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' + righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' + name = "vibro sword" + desc = "A potent weapon capable of cutting through nearly anything. Wielding it in two hands will allow you to deflect gunfire." + armour_penetration = 100 + block_chance = 40 + force = 20 + throwforce = 20 + throw_speed = 4 + sharpness = IS_SHARP + attack_verb = list("cut", "sliced", "diced") + w_class = WEIGHT_CLASS_BULKY + slot_flags = ITEM_SLOT_BACK + hitsound = 'sound/weapons/bladeslice.ogg' + var/wielded = FALSE // track wielded status on item + +/obj/item/vibro_weapon/Initialize() + . = ..() + RegisterSignal(src, COMSIG_TWOHANDED_WIELD, .proc/on_wield) + RegisterSignal(src, COMSIG_TWOHANDED_UNWIELD, .proc/on_unwield) + +/obj/item/vibro_weapon/ComponentInitialize() + . = ..() + AddComponent(/datum/component/butchering, 20, 105) + AddComponent(/datum/component/two_handed, force_multiplier=2, icon_wielded="hfrequency1") + +/// triggered on wield of two handed item +/obj/item/vibro_weapon/proc/on_wield(obj/item/source, mob/user) + wielded = TRUE + +/// triggered on unwield of two handed item +/obj/item/vibro_weapon/proc/on_unwield(obj/item/source, mob/user) + wielded = FALSE + +/obj/item/vibro_weapon/update_icon_state() + icon_state = "hfrequency0" + +/obj/item/vibro_weapon/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + if(wielded) + final_block_chance *= 2 + if(wielded || attack_type != PROJECTILE_ATTACK) + if(prob(final_block_chance)) + if(attack_type == PROJECTILE_ATTACK) + owner.visible_message("[owner] deflects [attack_text] with [src]!") + playsound(src, pick('sound/weapons/bulletflyby.ogg', 'sound/weapons/bulletflyby2.ogg', 'sound/weapons/bulletflyby3.ogg'), 75, TRUE) + return 1 + else + owner.visible_message("[owner] parries [attack_text] with [src]!") + return 1 + return 0 diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm index bbfb0b15641b..c4af6c3bcfb3 100644 --- a/code/game/objects/objs.dm +++ b/code/game/objects/objs.dm @@ -1,351 +1,351 @@ - -/obj - animate_movement = SLIDE_STEPS - speech_span = SPAN_ROBOT - var/obj_flags = CAN_BE_HIT - var/set_obj_flags // ONLY FOR MAPPING: Sets flags from a string list, handled in Initialize. Usage: set_obj_flags = "EMAGGED;!CAN_BE_HIT" to set EMAGGED and clear CAN_BE_HIT. - - var/damtype = BRUTE - var/force = 0 - - var/datum/armor/armor - var/obj_integrity //defaults to max_integrity - var/max_integrity = 500 - var/integrity_failure = 0 //0 if we have no special broken behavior, otherwise is a percentage of at what point the obj breaks. 0.5 being 50% - ///Damage under this value will be completely ignored - var/damage_deflection = 0 - - var/resistance_flags = NONE // INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ON_FIRE | UNACIDABLE | ACID_PROOF - - var/acid_level = 0 //how much acid is on that obj - - var/persistence_replacement //have something WAY too amazing to live to the next round? Set a new path here. Overuse of this var will make me upset. - var/current_skin //Has the item been reskinned? - var/list/unique_reskin //List of options to reskin. - - // Access levels, used in modules\jobs\access.dm - var/list/req_access - var/req_access_txt = "0" - var/list/req_one_access - var/req_one_access_txt = "0" - /// Custom fire overlay icon - var/custom_fire_overlay - - var/renamedByPlayer = FALSE //set when a player uses a pen on a renamable object - - var/drag_slowdown // Amont of multiplicative slowdown applied if pulled. >1 makes you slower, <1 makes you faster. - - vis_flags = VIS_INHERIT_PLANE //when this be added to vis_contents of something it inherit something.plane, important for visualisation of obj in openspace. - -/obj/vv_edit_var(vname, vval) - switch(vname) - if("anchored") - setAnchored(vval) - return TRUE - if("obj_flags") - if ((obj_flags & DANGEROUS_POSSESSION) && !(vval & DANGEROUS_POSSESSION)) - return FALSE - if("control_object") - var/obj/O = vval - if(istype(O) && (O.obj_flags & DANGEROUS_POSSESSION)) - return FALSE - return ..() - -/obj/Initialize() - if (islist(armor)) - armor = getArmor(arglist(armor)) - else if (!armor) - armor = getArmor() - else if (!istype(armor, /datum/armor)) - stack_trace("Invalid type [armor.type] found in .armor during /obj Initialize()") - if(obj_integrity == null) - obj_integrity = max_integrity - - . = ..() //Do this after, else mat datums is mad. - - if (set_obj_flags) - var/flagslist = splittext(set_obj_flags,";") - var/list/string_to_objflag = GLOB.bitfields["obj_flags"] - for (var/flag in flagslist) - if(flag[1] == "!") - flag = copytext(flag, length(flag[1]) + 1) // Get all but the initial ! - obj_flags &= ~string_to_objflag[flag] - else - obj_flags |= string_to_objflag[flag] - if((obj_flags & ON_BLUEPRINTS) && isturf(loc)) - var/turf/T = loc - T.add_blueprints_preround(src) - -/obj/Destroy(force=FALSE) - if(!ismachinery(src)) - STOP_PROCESSING(SSobj, src) // TODO: Have a processing bitflag to reduce on unnecessary loops through the processing lists - SStgui.close_uis(src) - . = ..() - -/obj/proc/setAnchored(anchorvalue) - SEND_SIGNAL(src, COMSIG_OBJ_SETANCHORED, anchorvalue) - anchored = anchorvalue - -/obj/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, gentle = FALSE, quickstart = TRUE) - ..() - if(obj_flags & FROZEN) - visible_message("[src] shatters into a million pieces!") - qdel(src) - - -/obj/assume_air(datum/gas_mixture/giver) - if(loc) - return loc.assume_air(giver) - else - return null - -/obj/remove_air(amount) - if(loc) - return loc.remove_air(amount) - else - return null - -/obj/return_air() - if(loc) - return loc.return_air() - else - return null - -/obj/proc/handle_internal_lifeform(mob/lifeform_inside_me, breath_request) - //Return: (NONSTANDARD) - // null if object handles breathing logic for lifeform - // datum/air_group to tell lifeform to process using that breath return - //DEFAULT: Take air from turf to give to have mob process - - if(breath_request>0) - var/datum/gas_mixture/environment = return_air() - var/breath_percentage = BREATH_VOLUME / environment.return_volume() - return remove_air(environment.total_moles() * breath_percentage) - else - return null - -/obj/proc/updateUsrDialog() - if((obj_flags & IN_USE) && !(obj_flags & USES_TGUI)) - var/is_in_use = FALSE - var/list/nearby = viewers(1, src) - for(var/mob/M in nearby) - if ((M.client && M.machine == src)) - is_in_use = TRUE - ui_interact(M) - if(issilicon(usr) || IsAdminGhost(usr)) - if (!(usr in nearby)) - if (usr.client && usr.machine==src) // && M.machine == src is omitted because if we triggered this by using the dialog, it doesn't matter if our machine changed in between triggering it and this - the dialog is probably still supposed to refresh. - is_in_use = TRUE - ui_interact(usr) - - // check for TK users - - if(ishuman(usr)) - var/mob/living/carbon/human/H = usr - if(!(usr in nearby)) - if(usr.client && usr.machine==src) - if(H.dna.check_mutation(TK)) - is_in_use = TRUE - ui_interact(usr) - if (is_in_use) - obj_flags |= IN_USE - else - obj_flags &= ~IN_USE - -/obj/proc/updateDialog(update_viewers = TRUE,update_ais = TRUE) - // Check that people are actually using the machine. If not, don't update anymore. - if(obj_flags & IN_USE) - var/is_in_use = FALSE - if(update_viewers) - for(var/mob/M in viewers(1, src)) - if ((M.client && M.machine == src)) - is_in_use = TRUE - src.interact(M) - var/ai_in_use = FALSE - if(update_ais) - ai_in_use = AutoUpdateAI(src) - - if(update_viewers && update_ais) //State change is sure only if we check both - if(!ai_in_use && !is_in_use) - obj_flags &= ~IN_USE - - -/obj/attack_ghost(mob/user) - . = ..() - if(.) - return - ui_interact(user) - -/obj/proc/container_resist(mob/living/user) - return - -/mob/proc/unset_machine() - if(machine) - machine.on_unset_machine(src) - machine = null - -//called when the user unsets the machine. -/atom/movable/proc/on_unset_machine(mob/user) - return - -/mob/proc/set_machine(obj/O) - if(src.machine) - unset_machine() - src.machine = O - if(istype(O)) - O.obj_flags |= IN_USE - -/obj/item/proc/updateSelfDialog() - var/mob/M = src.loc - if(istype(M) && M.client && M.machine == src) - src.attack_self(M) - -/obj/singularity_pull(S, current_size) - ..() - if(!anchored || current_size >= STAGE_FIVE) - step_towards(src,S) - -/obj/get_dumping_location(datum/component/storage/source,mob/user) - return get_turf(src) - -/obj/proc/CanAStarPass() - . = !density - -/obj/proc/check_uplink_validity() - return 1 - -/obj/vv_get_dropdown() - . = ..() - VV_DROPDOWN_OPTION("", "---") - VV_DROPDOWN_OPTION(VV_HK_MASS_DEL_TYPE, "Delete all of type") - VV_DROPDOWN_OPTION(VV_HK_OSAY, "Object Say") - VV_DROPDOWN_OPTION(VV_HK_ARMOR_MOD, "Modify armor values") - -/obj/vv_do_topic(list/href_list) - if(!(. = ..())) - return - if(href_list[VV_HK_OSAY]) - if(check_rights(R_FUN, FALSE)) - usr.client.object_say(src) - if(href_list[VV_HK_ARMOR_MOD]) - var/list/pickerlist = list() - var/list/armorlist = armor.getList() - - for (var/i in armorlist) - pickerlist += list(list("value" = armorlist[i], "name" = i)) - - var/list/result = presentpicker(usr, "Modify armor", "Modify armor: [src]", Button1="Save", Button2 = "Cancel", Timeout=FALSE, inputtype = "text", values = pickerlist) - - if (islist(result)) - if (result["button"] != 2) // If the user pressed the cancel button - // text2num conveniently returns a null on invalid values - armor = armor.setRating(melee = text2num(result["values"]["melee"]),\ - bullet = text2num(result["values"]["bullet"]),\ - laser = text2num(result["values"]["laser"]),\ - energy = text2num(result["values"]["energy"]),\ - bomb = text2num(result["values"]["bomb"]),\ - bio = text2num(result["values"]["bio"]),\ - rad = text2num(result["values"]["rad"]),\ - fire = text2num(result["values"]["fire"]),\ - acid = text2num(result["values"]["acid"])) - log_admin("[key_name(usr)] modified the armor on [src] ([type]) to melee: [armor.melee], bullet: [armor.bullet], laser: [armor.laser], energy: [armor.energy], bomb: [armor.bomb], bio: [armor.bio], rad: [armor.rad], fire: [armor.fire], acid: [armor.acid]") - message_admins("[key_name_admin(usr)] modified the armor on [src] ([type]) to melee: [armor.melee], bullet: [armor.bullet], laser: [armor.laser], energy: [armor.energy], bomb: [armor.bomb], bio: [armor.bio], rad: [armor.rad], fire: [armor.fire], acid: [armor.acid]") - if(href_list[VV_HK_MASS_DEL_TYPE]) - if(check_rights(R_DEBUG|R_SERVER)) - var/action_type = alert("Strict type ([type]) or type and all subtypes?",,"Strict type","Type and subtypes","Cancel") - if(action_type == "Cancel" || !action_type) - return - - if(alert("Are you really sure you want to delete all objects of type [type]?",,"Yes","No") != "Yes") - return - - if(alert("Second confirmation required. Delete?",,"Yes","No") != "Yes") - return - - var/O_type = type - switch(action_type) - if("Strict type") - var/i = 0 - for(var/obj/Obj in world) - if(Obj.type == O_type) - i++ - qdel(Obj) - CHECK_TICK - if(!i) - to_chat(usr, "No objects of this type exist") - return - log_admin("[key_name(usr)] deleted all objects of type [O_type] ([i] objects deleted) ") - message_admins("[key_name(usr)] deleted all objects of type [O_type] ([i] objects deleted) ") - if("Type and subtypes") - var/i = 0 - for(var/obj/Obj in world) - if(istype(Obj,O_type)) - i++ - qdel(Obj) - CHECK_TICK - if(!i) - to_chat(usr, "No objects of this type exist") - return - log_admin("[key_name(usr)] deleted all objects of type or subtype of [O_type] ([i] objects deleted) ") - message_admins("[key_name(usr)] deleted all objects of type or subtype of [O_type] ([i] objects deleted) ") - -/obj/examine(mob/user) - . = ..() - if(obj_flags & UNIQUE_RENAME) - . += "Use a pen on it to rename it or change its description." - if(unique_reskin && !current_skin) - . += "Alt-click it to reskin it." - -/obj/AltClick(mob/user) - . = ..() - if(unique_reskin && !current_skin && user.canUseTopic(src, BE_CLOSE, NO_DEXTERITY)) - reskin_obj(user) - -/obj/proc/reskin_obj(mob/M) - if(!LAZYLEN(unique_reskin)) - return - to_chat(M, "Reskin options for [name]:") - for(var/V in unique_reskin) - var/output = icon2html(src, M, unique_reskin[V]) - to_chat(M, "[V]: [output]") - - var/choice = input(M,"Warning, you can only reskin [src] once!","Reskin Object") as null|anything in sortList(unique_reskin) - if(!QDELETED(src) && choice && !current_skin && !M.incapacitated() && in_range(M,src)) - if(!unique_reskin[choice]) - return - current_skin = choice - icon_state = unique_reskin[choice] - to_chat(M, "[src] is now skinned as '[choice].'") - -/obj/analyzer_act(mob/living/user, obj/item/I) - if(atmosanalyzer_scan(user, src)) - return TRUE - return ..() - -/obj/proc/plunger_act(obj/item/plunger/P, mob/living/user, reinforced) - return - -// Should move all contained objects to it's location. -/obj/proc/dump_contents() - CRASH("Unimplemented.") - -/obj/handle_ricochet(obj/projectile/P) - . = ..() - if(. && ricochet_damage_mod) - take_damage(P.damage * ricochet_damage_mod, P.damage_type, P.flag, 0, turn(P.dir, 180), P.armour_penetration) // pass along ricochet_damage_mod damage to the structure for the ricochet - -/obj/update_overlays() - . = ..() - if(acid_level) - . += GLOB.acid_overlay - if(resistance_flags & ON_FIRE) - . += custom_fire_overlay ? custom_fire_overlay : GLOB.fire_overlay - -/// Handles exposing an object to reagents. -/obj/expose_reagents(list/reagents, datum/reagents/source, method=TOUCH, volume_modifier=1, show_message=TRUE) - if((. = ..()) & COMPONENT_NO_EXPOSE_REAGENTS) - return - - for(var/reagent in reagents) - var/datum/reagent/R = reagent - . |= R.expose_obj(src, reagents[R]) + +/obj + animate_movement = SLIDE_STEPS + speech_span = SPAN_ROBOT + var/obj_flags = CAN_BE_HIT + var/set_obj_flags // ONLY FOR MAPPING: Sets flags from a string list, handled in Initialize. Usage: set_obj_flags = "EMAGGED;!CAN_BE_HIT" to set EMAGGED and clear CAN_BE_HIT. + + var/damtype = BRUTE + var/force = 0 + + var/datum/armor/armor + var/obj_integrity //defaults to max_integrity + var/max_integrity = 500 + var/integrity_failure = 0 //0 if we have no special broken behavior, otherwise is a percentage of at what point the obj breaks. 0.5 being 50% + ///Damage under this value will be completely ignored + var/damage_deflection = 0 + + var/resistance_flags = NONE // INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ON_FIRE | UNACIDABLE | ACID_PROOF + + var/acid_level = 0 //how much acid is on that obj + + var/persistence_replacement //have something WAY too amazing to live to the next round? Set a new path here. Overuse of this var will make me upset. + var/current_skin //Has the item been reskinned? + var/list/unique_reskin //List of options to reskin. + + // Access levels, used in modules\jobs\access.dm + var/list/req_access + var/req_access_txt = "0" + var/list/req_one_access + var/req_one_access_txt = "0" + /// Custom fire overlay icon + var/custom_fire_overlay + + var/renamedByPlayer = FALSE //set when a player uses a pen on a renamable object + + var/drag_slowdown // Amont of multiplicative slowdown applied if pulled. >1 makes you slower, <1 makes you faster. + + vis_flags = VIS_INHERIT_PLANE //when this be added to vis_contents of something it inherit something.plane, important for visualisation of obj in openspace. + +/obj/vv_edit_var(vname, vval) + switch(vname) + if("anchored") + setAnchored(vval) + return TRUE + if("obj_flags") + if ((obj_flags & DANGEROUS_POSSESSION) && !(vval & DANGEROUS_POSSESSION)) + return FALSE + if("control_object") + var/obj/O = vval + if(istype(O) && (O.obj_flags & DANGEROUS_POSSESSION)) + return FALSE + return ..() + +/obj/Initialize() + if (islist(armor)) + armor = getArmor(arglist(armor)) + else if (!armor) + armor = getArmor() + else if (!istype(armor, /datum/armor)) + stack_trace("Invalid type [armor.type] found in .armor during /obj Initialize()") + if(obj_integrity == null) + obj_integrity = max_integrity + + . = ..() //Do this after, else mat datums is mad. + + if (set_obj_flags) + var/flagslist = splittext(set_obj_flags,";") + var/list/string_to_objflag = GLOB.bitfields["obj_flags"] + for (var/flag in flagslist) + if(flag[1] == "!") + flag = copytext(flag, length(flag[1]) + 1) // Get all but the initial ! + obj_flags &= ~string_to_objflag[flag] + else + obj_flags |= string_to_objflag[flag] + if((obj_flags & ON_BLUEPRINTS) && isturf(loc)) + var/turf/T = loc + T.add_blueprints_preround(src) + +/obj/Destroy(force=FALSE) + if(!ismachinery(src)) + STOP_PROCESSING(SSobj, src) // TODO: Have a processing bitflag to reduce on unnecessary loops through the processing lists + SStgui.close_uis(src) + . = ..() + +/obj/proc/setAnchored(anchorvalue) + SEND_SIGNAL(src, COMSIG_OBJ_SETANCHORED, anchorvalue) + anchored = anchorvalue + +/obj/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, gentle = FALSE, quickstart = TRUE) + ..() + if(obj_flags & FROZEN) + visible_message("[src] shatters into a million pieces!") + qdel(src) + + +/obj/assume_air(datum/gas_mixture/giver) + if(loc) + return loc.assume_air(giver) + else + return null + +/obj/remove_air(amount) + if(loc) + return loc.remove_air(amount) + else + return null + +/obj/return_air() + if(loc) + return loc.return_air() + else + return null + +/obj/proc/handle_internal_lifeform(mob/lifeform_inside_me, breath_request) + //Return: (NONSTANDARD) + // null if object handles breathing logic for lifeform + // datum/air_group to tell lifeform to process using that breath return + //DEFAULT: Take air from turf to give to have mob process + + if(breath_request>0) + var/datum/gas_mixture/environment = return_air() + var/breath_percentage = BREATH_VOLUME / environment.return_volume() + return remove_air(environment.total_moles() * breath_percentage) + else + return null + +/obj/proc/updateUsrDialog() + if((obj_flags & IN_USE) && !(obj_flags & USES_TGUI)) + var/is_in_use = FALSE + var/list/nearby = viewers(1, src) + for(var/mob/M in nearby) + if ((M.client && M.machine == src)) + is_in_use = TRUE + ui_interact(M) + if(issilicon(usr) || IsAdminGhost(usr)) + if (!(usr in nearby)) + if (usr.client && usr.machine==src) // && M.machine == src is omitted because if we triggered this by using the dialog, it doesn't matter if our machine changed in between triggering it and this - the dialog is probably still supposed to refresh. + is_in_use = TRUE + ui_interact(usr) + + // check for TK users + + if(ishuman(usr)) + var/mob/living/carbon/human/H = usr + if(!(usr in nearby)) + if(usr.client && usr.machine==src) + if(H.dna.check_mutation(TK)) + is_in_use = TRUE + ui_interact(usr) + if (is_in_use) + obj_flags |= IN_USE + else + obj_flags &= ~IN_USE + +/obj/proc/updateDialog(update_viewers = TRUE,update_ais = TRUE) + // Check that people are actually using the machine. If not, don't update anymore. + if(obj_flags & IN_USE) + var/is_in_use = FALSE + if(update_viewers) + for(var/mob/M in viewers(1, src)) + if ((M.client && M.machine == src)) + is_in_use = TRUE + src.interact(M) + var/ai_in_use = FALSE + if(update_ais) + ai_in_use = AutoUpdateAI(src) + + if(update_viewers && update_ais) //State change is sure only if we check both + if(!ai_in_use && !is_in_use) + obj_flags &= ~IN_USE + + +/obj/attack_ghost(mob/user) + . = ..() + if(.) + return + ui_interact(user) + +/obj/proc/container_resist(mob/living/user) + return + +/mob/proc/unset_machine() + if(machine) + machine.on_unset_machine(src) + machine = null + +//called when the user unsets the machine. +/atom/movable/proc/on_unset_machine(mob/user) + return + +/mob/proc/set_machine(obj/O) + if(src.machine) + unset_machine() + src.machine = O + if(istype(O)) + O.obj_flags |= IN_USE + +/obj/item/proc/updateSelfDialog() + var/mob/M = src.loc + if(istype(M) && M.client && M.machine == src) + src.attack_self(M) + +/obj/singularity_pull(S, current_size) + ..() + if(!anchored || current_size >= STAGE_FIVE) + step_towards(src,S) + +/obj/get_dumping_location(datum/component/storage/source,mob/user) + return get_turf(src) + +/obj/proc/CanAStarPass() + . = !density + +/obj/proc/check_uplink_validity() + return 1 + +/obj/vv_get_dropdown() + . = ..() + VV_DROPDOWN_OPTION("", "---") + VV_DROPDOWN_OPTION(VV_HK_MASS_DEL_TYPE, "Delete all of type") + VV_DROPDOWN_OPTION(VV_HK_OSAY, "Object Say") + VV_DROPDOWN_OPTION(VV_HK_ARMOR_MOD, "Modify armor values") + +/obj/vv_do_topic(list/href_list) + if(!(. = ..())) + return + if(href_list[VV_HK_OSAY]) + if(check_rights(R_FUN, FALSE)) + usr.client.object_say(src) + if(href_list[VV_HK_ARMOR_MOD]) + var/list/pickerlist = list() + var/list/armorlist = armor.getList() + + for (var/i in armorlist) + pickerlist += list(list("value" = armorlist[i], "name" = i)) + + var/list/result = presentpicker(usr, "Modify armor", "Modify armor: [src]", Button1="Save", Button2 = "Cancel", Timeout=FALSE, inputtype = "text", values = pickerlist) + + if (islist(result)) + if (result["button"] != 2) // If the user pressed the cancel button + // text2num conveniently returns a null on invalid values + armor = armor.setRating(melee = text2num(result["values"]["melee"]),\ + bullet = text2num(result["values"]["bullet"]),\ + laser = text2num(result["values"]["laser"]),\ + energy = text2num(result["values"]["energy"]),\ + bomb = text2num(result["values"]["bomb"]),\ + bio = text2num(result["values"]["bio"]),\ + rad = text2num(result["values"]["rad"]),\ + fire = text2num(result["values"]["fire"]),\ + acid = text2num(result["values"]["acid"])) + log_admin("[key_name(usr)] modified the armor on [src] ([type]) to melee: [armor.melee], bullet: [armor.bullet], laser: [armor.laser], energy: [armor.energy], bomb: [armor.bomb], bio: [armor.bio], rad: [armor.rad], fire: [armor.fire], acid: [armor.acid]") + message_admins("[key_name_admin(usr)] modified the armor on [src] ([type]) to melee: [armor.melee], bullet: [armor.bullet], laser: [armor.laser], energy: [armor.energy], bomb: [armor.bomb], bio: [armor.bio], rad: [armor.rad], fire: [armor.fire], acid: [armor.acid]") + if(href_list[VV_HK_MASS_DEL_TYPE]) + if(check_rights(R_DEBUG|R_SERVER)) + var/action_type = alert("Strict type ([type]) or type and all subtypes?",,"Strict type","Type and subtypes","Cancel") + if(action_type == "Cancel" || !action_type) + return + + if(alert("Are you really sure you want to delete all objects of type [type]?",,"Yes","No") != "Yes") + return + + if(alert("Second confirmation required. Delete?",,"Yes","No") != "Yes") + return + + var/O_type = type + switch(action_type) + if("Strict type") + var/i = 0 + for(var/obj/Obj in world) + if(Obj.type == O_type) + i++ + qdel(Obj) + CHECK_TICK + if(!i) + to_chat(usr, "No objects of this type exist") + return + log_admin("[key_name(usr)] deleted all objects of type [O_type] ([i] objects deleted) ") + message_admins("[key_name(usr)] deleted all objects of type [O_type] ([i] objects deleted) ") + if("Type and subtypes") + var/i = 0 + for(var/obj/Obj in world) + if(istype(Obj,O_type)) + i++ + qdel(Obj) + CHECK_TICK + if(!i) + to_chat(usr, "No objects of this type exist") + return + log_admin("[key_name(usr)] deleted all objects of type or subtype of [O_type] ([i] objects deleted) ") + message_admins("[key_name(usr)] deleted all objects of type or subtype of [O_type] ([i] objects deleted) ") + +/obj/examine(mob/user) + . = ..() + if(obj_flags & UNIQUE_RENAME) + . += "Use a pen on it to rename it or change its description." + if(unique_reskin && !current_skin) + . += "Alt-click it to reskin it." + +/obj/AltClick(mob/user) + . = ..() + if(unique_reskin && !current_skin && user.canUseTopic(src, BE_CLOSE, NO_DEXTERITY)) + reskin_obj(user) + +/obj/proc/reskin_obj(mob/M) + if(!LAZYLEN(unique_reskin)) + return + to_chat(M, "Reskin options for [name]:") + for(var/V in unique_reskin) + var/output = icon2html(src, M, unique_reskin[V]) + to_chat(M, "[V]: [output]") + + var/choice = input(M,"Warning, you can only reskin [src] once!","Reskin Object") as null|anything in sortList(unique_reskin) + if(!QDELETED(src) && choice && !current_skin && !M.incapacitated() && in_range(M,src)) + if(!unique_reskin[choice]) + return + current_skin = choice + icon_state = unique_reskin[choice] + to_chat(M, "[src] is now skinned as '[choice].'") + +/obj/analyzer_act(mob/living/user, obj/item/I) + if(atmosanalyzer_scan(user, src)) + return TRUE + return ..() + +/obj/proc/plunger_act(obj/item/plunger/P, mob/living/user, reinforced) + return + +// Should move all contained objects to it's location. +/obj/proc/dump_contents() + CRASH("Unimplemented.") + +/obj/handle_ricochet(obj/projectile/P) + . = ..() + if(. && ricochet_damage_mod) + take_damage(P.damage * ricochet_damage_mod, P.damage_type, P.flag, 0, turn(P.dir, 180), P.armour_penetration) // pass along ricochet_damage_mod damage to the structure for the ricochet + +/obj/update_overlays() + . = ..() + if(acid_level) + . += GLOB.acid_overlay + if(resistance_flags & ON_FIRE) + . += custom_fire_overlay ? custom_fire_overlay : GLOB.fire_overlay + +/// Handles exposing an object to reagents. +/obj/expose_reagents(list/reagents, datum/reagents/source, method=TOUCH, volume_modifier=1, show_message=TRUE) + if((. = ..()) & COMPONENT_NO_EXPOSE_REAGENTS) + return + + for(var/reagent in reagents) + var/datum/reagent/R = reagent + . |= R.expose_obj(src, reagents[R]) diff --git a/code/game/objects/structures.dm b/code/game/objects/structures.dm index b391c86016a1..ab7a4eface12 100644 --- a/code/game/objects/structures.dm +++ b/code/game/objects/structures.dm @@ -1,122 +1,122 @@ -/obj/structure - icon = 'icons/obj/structures.dmi' - pressure_resistance = 8 - max_integrity = 300 - interaction_flags_atom = INTERACT_ATOM_ATTACK_HAND | INTERACT_ATOM_UI_INTERACT - layer = BELOW_OBJ_LAYER - flags_ricochet = RICOCHET_HARD - ricochet_chance_mod = 0.5 - - var/climb_time = 20 - var/climbable = FALSE - var/mob/living/structureclimber - var/broken = 0 //similar to machinery's stat BROKEN - - -/obj/structure/Initialize() - if (!armor) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - . = ..() - if(smooth) - queue_smooth(src) - queue_smooth_neighbors(src) - icon_state = "" - GLOB.cameranet.updateVisibility(src) - -/obj/structure/Destroy() - GLOB.cameranet.updateVisibility(src) - if(smooth) - queue_smooth_neighbors(src) - return ..() - -/obj/structure/attack_hand(mob/user) - . = ..() - if(.) - return - if(structureclimber && structureclimber != user) - user.changeNext_move(CLICK_CD_MELEE) - user.do_attack_animation(src) - structureclimber.Paralyze(40) - structureclimber.visible_message("[structureclimber] is knocked off [src].", "You're knocked off [src]!", "You see [structureclimber] get knocked off [src].") - -/obj/structure/ui_act(action, params) - . = ..() - add_fingerprint(usr) - -/obj/structure/MouseDrop_T(atom/movable/O, mob/user) - . = ..() - if(!climbable) - return - if(user == O && isliving(O)) - var/mob/living/L = O - if(isanimal(L)) - var/mob/living/simple_animal/A = L - if (!A.dextrous) - return - if(L.mobility_flags & MOBILITY_MOVE) - climb_structure(user) - return - if(!istype(O, /obj/item) || user.get_active_held_item() != O) - return - if(iscyborg(user)) - return - if(!user.dropItemToGround(O)) - return - if (O.loc != src.loc) - step(O, get_dir(O, src)) - -/obj/structure/proc/do_climb(atom/movable/A) - if(climbable) - if(A.loc == src.loc) - var/where_to_climb = get_step(A,dir) - if(!(is_blocked_turf(where_to_climb))) - A.forceMove(where_to_climb) - return TRUE - density = FALSE - . = step(A,get_dir(A,src.loc)) - density = TRUE - -/obj/structure/proc/climb_structure(mob/living/user) - src.add_fingerprint(user) - user.visible_message("[user] starts climbing onto [src].", \ - "You start climbing onto [src]...") - var/adjusted_climb_time = climb_time - if(user.restrained()) //climbing takes twice as long when restrained. - adjusted_climb_time *= 2 - if(isalien(user)) - adjusted_climb_time *= 0.25 //aliens are terrifyingly fast - if(HAS_TRAIT(user, TRAIT_FREERUNNING)) //do you have any idea how fast I am??? - adjusted_climb_time *= 0.8 - structureclimber = user - if(do_mob(user, user, adjusted_climb_time)) - if(src.loc) //Checking if structure has been destroyed - if(do_climb(user)) - user.visible_message("[user] climbs onto [src].", \ - "You climb onto [src].") - log_combat(user, src, "climbed onto") - . = 1 - else - to_chat(user, "You fail to climb onto [src].") - structureclimber = null - -/obj/structure/examine(mob/user) - . = ..() - if(!(resistance_flags & INDESTRUCTIBLE)) - if(resistance_flags & ON_FIRE) - . += "It's on fire!" - if(broken) - . += "It appears to be broken." - var/examine_status = examine_status(user) - if(examine_status) - . += examine_status - -/obj/structure/proc/examine_status(mob/user) //An overridable proc, mostly for falsewalls. - var/healthpercent = (obj_integrity/max_integrity) * 100 - switch(healthpercent) - if(50 to 99) - return "It looks slightly damaged." - if(25 to 50) - return "It appears heavily damaged." - if(0 to 25) - if(!broken) - return "It's falling apart!" +/obj/structure + icon = 'icons/obj/structures.dmi' + pressure_resistance = 8 + max_integrity = 300 + interaction_flags_atom = INTERACT_ATOM_ATTACK_HAND | INTERACT_ATOM_UI_INTERACT + layer = BELOW_OBJ_LAYER + flags_ricochet = RICOCHET_HARD + ricochet_chance_mod = 0.5 + + var/climb_time = 20 + var/climbable = FALSE + var/mob/living/structureclimber + var/broken = 0 //similar to machinery's stat BROKEN + + +/obj/structure/Initialize() + if (!armor) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + . = ..() + if(smooth) + queue_smooth(src) + queue_smooth_neighbors(src) + icon_state = "" + GLOB.cameranet.updateVisibility(src) + +/obj/structure/Destroy() + GLOB.cameranet.updateVisibility(src) + if(smooth) + queue_smooth_neighbors(src) + return ..() + +/obj/structure/attack_hand(mob/user) + . = ..() + if(.) + return + if(structureclimber && structureclimber != user) + user.changeNext_move(CLICK_CD_MELEE) + user.do_attack_animation(src) + structureclimber.Paralyze(40) + structureclimber.visible_message("[structureclimber] is knocked off [src].", "You're knocked off [src]!", "You see [structureclimber] get knocked off [src].") + +/obj/structure/ui_act(action, params) + . = ..() + add_fingerprint(usr) + +/obj/structure/MouseDrop_T(atom/movable/O, mob/user) + . = ..() + if(!climbable) + return + if(user == O && isliving(O)) + var/mob/living/L = O + if(isanimal(L)) + var/mob/living/simple_animal/A = L + if (!A.dextrous) + return + if(L.mobility_flags & MOBILITY_MOVE) + climb_structure(user) + return + if(!istype(O, /obj/item) || user.get_active_held_item() != O) + return + if(iscyborg(user)) + return + if(!user.dropItemToGround(O)) + return + if (O.loc != src.loc) + step(O, get_dir(O, src)) + +/obj/structure/proc/do_climb(atom/movable/A) + if(climbable) + if(A.loc == src.loc) + var/where_to_climb = get_step(A,dir) + if(!(is_blocked_turf(where_to_climb))) + A.forceMove(where_to_climb) + return TRUE + density = FALSE + . = step(A,get_dir(A,src.loc)) + density = TRUE + +/obj/structure/proc/climb_structure(mob/living/user) + src.add_fingerprint(user) + user.visible_message("[user] starts climbing onto [src].", \ + "You start climbing onto [src]...") + var/adjusted_climb_time = climb_time + if(user.restrained()) //climbing takes twice as long when restrained. + adjusted_climb_time *= 2 + if(isalien(user)) + adjusted_climb_time *= 0.25 //aliens are terrifyingly fast + if(HAS_TRAIT(user, TRAIT_FREERUNNING)) //do you have any idea how fast I am??? + adjusted_climb_time *= 0.8 + structureclimber = user + if(do_mob(user, user, adjusted_climb_time)) + if(src.loc) //Checking if structure has been destroyed + if(do_climb(user)) + user.visible_message("[user] climbs onto [src].", \ + "You climb onto [src].") + log_combat(user, src, "climbed onto") + . = 1 + else + to_chat(user, "You fail to climb onto [src].") + structureclimber = null + +/obj/structure/examine(mob/user) + . = ..() + if(!(resistance_flags & INDESTRUCTIBLE)) + if(resistance_flags & ON_FIRE) + . += "It's on fire!" + if(broken) + . += "It appears to be broken." + var/examine_status = examine_status(user) + if(examine_status) + . += examine_status + +/obj/structure/proc/examine_status(mob/user) //An overridable proc, mostly for falsewalls. + var/healthpercent = (obj_integrity/max_integrity) * 100 + switch(healthpercent) + if(50 to 99) + return "It looks slightly damaged." + if(25 to 50) + return "It appears heavily damaged." + if(0 to 25) + if(!broken) + return "It's falling apart!" diff --git a/code/game/objects/structures/ai_core.dm b/code/game/objects/structures/ai_core.dm index facf0d301fba..f7da265218ee 100644 --- a/code/game/objects/structures/ai_core.dm +++ b/code/game/objects/structures/ai_core.dm @@ -1,331 +1,331 @@ -/obj/structure/AIcore - density = TRUE - anchored = FALSE - name = "\improper AI core" - icon = 'icons/mob/ai.dmi' - icon_state = "0" - desc = "The framework for an artificial intelligence core." - max_integrity = 500 - var/state = EMPTY_CORE - var/datum/ai_laws/laws - var/obj/item/circuitboard/aicore/circuit - var/obj/item/mmi/brain - var/can_deconstruct = TRUE - -/obj/structure/AIcore/Initialize() - . = ..() - laws = new - laws.set_laws_config() - -/obj/structure/AIcore/handle_atom_del(atom/A) - if(A == circuit) - circuit = null - if((state != GLASS_CORE) && (state != AI_READY_CORE)) - state = EMPTY_CORE - update_icon() - if(A == brain) - brain = null - . = ..() - - -/obj/structure/AIcore/Destroy() - if(circuit) - qdel(circuit) - circuit = null - if(brain) - qdel(brain) - brain = null - return ..() - -/obj/structure/AIcore/latejoin_inactive - name = "networked AI core" - desc = "This AI core is connected by bluespace transmitters to NTNet, allowing for an AI personality to be downloaded to it on the fly mid-shift." - can_deconstruct = FALSE - icon_state = "ai-empty" - anchored = TRUE - state = AI_READY_CORE - var/available = TRUE - var/safety_checks = TRUE - var/active = TRUE - -/obj/structure/AIcore/latejoin_inactive/examine(mob/user) - . = ..() - . += "Its transmitter seems to be [active? "on" : "off"]." - . += "You could [active? "deactivate" : "activate"] it with a multitool." - -/obj/structure/AIcore/latejoin_inactive/proc/is_available() //If people still manage to use this feature to spawn-kill AI latejoins ahelp them. - if(!available) - return FALSE - if(!safety_checks) - return TRUE - if(!active) - return FALSE - var/turf/T = get_turf(src) - var/area/A = get_area(src) - if(!A.blob_allowed) - return FALSE - if(!A.power_equip) - return FALSE - if(!SSmapping.level_trait(T.z,ZTRAIT_STATION)) - return FALSE - if(!istype(T, /turf/open/floor)) - return FALSE - return TRUE - -/obj/structure/AIcore/latejoin_inactive/attackby(obj/item/P, mob/user, params) - if(P.tool_behaviour == TOOL_MULTITOOL) - active = !active - to_chat(user, "You [active? "activate" : "deactivate"] \the [src]'s transmitters.") - return - return ..() - -/obj/structure/AIcore/latejoin_inactive/Initialize() - . = ..() - GLOB.latejoin_ai_cores += src - -/obj/structure/AIcore/latejoin_inactive/Destroy() - GLOB.latejoin_ai_cores -= src - return ..() - -/obj/structure/AIcore/attackby(obj/item/P, mob/user, params) - if(P.tool_behaviour == TOOL_WRENCH) - return default_unfasten_wrench(user, P, 20) - if(!anchored) - if(P.tool_behaviour == TOOL_WELDER && can_deconstruct) - if(state != EMPTY_CORE) - to_chat(user, "The core must be empty to deconstruct it!") - return - - if(!P.tool_start_check(user, amount=0)) - return - - to_chat(user, "You start to deconstruct the frame...") - if(P.use_tool(src, user, 20, volume=50) && state == EMPTY_CORE) - to_chat(user, "You deconstruct the frame.") - deconstruct(TRUE) - return - else - switch(state) - if(EMPTY_CORE) - if(istype(P, /obj/item/circuitboard/aicore)) - if(!user.transferItemToLoc(P, src)) - return - playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE) - to_chat(user, "You place the circuit board inside the frame.") - update_icon() - state = CIRCUIT_CORE - circuit = P - return - if(CIRCUIT_CORE) - if(P.tool_behaviour == TOOL_SCREWDRIVER) - P.play_tool_sound(src) - to_chat(user, "You screw the circuit board into place.") - state = SCREWED_CORE - update_icon() - return - if(P.tool_behaviour == TOOL_CROWBAR) - P.play_tool_sound(src) - to_chat(user, "You remove the circuit board.") - state = EMPTY_CORE - update_icon() - circuit.forceMove(loc) - circuit = null - return - if(SCREWED_CORE) - if(P.tool_behaviour == TOOL_SCREWDRIVER && circuit) - P.play_tool_sound(src) - to_chat(user, "You unfasten the circuit board.") - state = CIRCUIT_CORE - update_icon() - return - if(istype(P, /obj/item/stack/cable_coil)) - var/obj/item/stack/cable_coil/C = P - if(C.get_amount() >= 5) - playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE) - to_chat(user, "You start to add cables to the frame...") - if(do_after(user, 20, target = src) && state == SCREWED_CORE && C.use(5)) - to_chat(user, "You add cables to the frame.") - state = CABLED_CORE - update_icon() - else - to_chat(user, "You need five lengths of cable to wire the AI core!") - return - if(CABLED_CORE) - if(P.tool_behaviour == TOOL_WIRECUTTER) - if(brain) - to_chat(user, "Get that [brain.name] out of there first!") - else - P.play_tool_sound(src) - to_chat(user, "You remove the cables.") - state = SCREWED_CORE - update_icon() - new /obj/item/stack/cable_coil(drop_location(), 5) - return - - if(istype(P, /obj/item/stack/sheet/rglass)) - var/obj/item/stack/sheet/rglass/G = P - if(G.get_amount() >= 2) - playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE) - to_chat(user, "You start to put in the glass panel...") - if(do_after(user, 20, target = src) && state == CABLED_CORE && G.use(2)) - to_chat(user, "You put in the glass panel.") - state = GLASS_CORE - update_icon() - else - to_chat(user, "You need two sheets of reinforced glass to insert them into the AI core!") - return - - if(istype(P, /obj/item/aiModule)) - if(brain && brain.laws.id != DEFAULT_AI_LAWID) - to_chat(user, "The installed [brain.name] already has set laws!") - return - var/obj/item/aiModule/module = P - module.install(laws, user) - return - - if(istype(P, /obj/item/mmi) && !brain) - var/obj/item/mmi/M = P - if(!M.brain_check(user)) - return - - var/mob/living/brain/B = M.brainmob - if(!CONFIG_GET(flag/allow_ai) || (is_banned_from(B.ckey, "AI") && !QDELETED(src) && !QDELETED(user) && !QDELETED(M) && !QDELETED(user) && Adjacent(user))) - if(!QDELETED(M)) - to_chat(user, "This [M.name] does not seem to fit!") - return - if(!user.transferItemToLoc(M,src)) - return - - brain = M - to_chat(user, "You add [M.name] to the frame.") - update_icon() - return - - if(P.tool_behaviour == TOOL_CROWBAR && brain) - P.play_tool_sound(src) - to_chat(user, "You remove the brain.") - brain.forceMove(loc) - brain = null - update_icon() - return - - if(GLASS_CORE) - if(P.tool_behaviour == TOOL_CROWBAR) - P.play_tool_sound(src) - to_chat(user, "You remove the glass panel.") - state = CABLED_CORE - update_icon() - new /obj/item/stack/sheet/rglass(loc, 2) - return - - if(P.tool_behaviour == TOOL_SCREWDRIVER) - P.play_tool_sound(src) - to_chat(user, "You connect the monitor.") - if(brain) - var/mob/living/brain/B = brain.brainmob - SSticker.mode.remove_antag_for_borging(B.mind) - - var/mob/living/silicon/ai/A = null - - if (brain.overrides_aicore_laws) - A = new /mob/living/silicon/ai(loc, brain.laws, B) - else - A = new /mob/living/silicon/ai(loc, laws, B) - - if(brain.force_replace_ai_name) - A.fully_replace_character_name(A.name, brain.replacement_ai_name()) - SSblackbox.record_feedback("amount", "ais_created", 1) - deadchat_broadcast(" has been brought online at [get_area_name(A, TRUE)].", "[A]", follow_target=A, message_type=DEADCHAT_ANNOUNCEMENT) - qdel(src) - else - state = AI_READY_CORE - update_icon() - return - - if(AI_READY_CORE) - if(istype(P, /obj/item/aicard)) - P.transfer_ai("INACTIVE", "AICARD", src, user) - return - - if(P.tool_behaviour == TOOL_SCREWDRIVER) - P.play_tool_sound(src) - to_chat(user, "You disconnect the monitor.") - state = GLASS_CORE - update_icon() - return - return ..() - -/obj/structure/AIcore/update_icon_state() - switch(state) - if(EMPTY_CORE) - icon_state = "0" - if(CIRCUIT_CORE) - icon_state = "1" - if(SCREWED_CORE) - icon_state = "2" - if(CABLED_CORE) - if(brain) - icon_state = "3b" - else - icon_state = "3" - if(GLASS_CORE) - icon_state = "4" - if(AI_READY_CORE) - icon_state = "ai-empty" - -/obj/structure/AIcore/deconstruct(disassembled = TRUE) - if(state == GLASS_CORE) - new /obj/item/stack/sheet/rglass(loc, 2) - if(state >= CABLED_CORE) - new /obj/item/stack/cable_coil(loc, 5) - if(circuit) - circuit.forceMove(loc) - circuit = null - new /obj/item/stack/sheet/plasteel(loc, 4) - qdel(src) - -/obj/structure/AIcore/deactivated - name = "inactive AI" - icon_state = "ai-empty" - anchored = TRUE - state = AI_READY_CORE - -/obj/structure/AIcore/deactivated/Initialize() - . = ..() - circuit = new(src) - - -/* -This is a good place for AI-related object verbs so I'm sticking it here. -If adding stuff to this, don't forget that an AI need to cancel_camera() whenever it physically moves to a different location. -That prevents a few funky behaviors. -*/ -//The type of interaction, the player performing the operation, the AI itself, and the card object, if any. - - -/atom/proc/transfer_ai(interaction, mob/user, mob/living/silicon/ai/AI, obj/item/aicard/card) - if(istype(card)) - if(card.flush) - to_chat(user, "ERROR: AI flush is in progress, cannot execute transfer protocol.") - return FALSE - return TRUE - -/obj/structure/AIcore/transfer_ai(interaction, mob/user, mob/living/silicon/ai/AI, obj/item/aicard/card) - if(state != AI_READY_CORE || !..()) - return - //Transferring a carded AI to a core. - if(interaction == AI_TRANS_FROM_CARD) - AI.control_disabled = FALSE - AI.radio_enabled = TRUE - AI.forceMove(loc) // to replace the terminal. - to_chat(AI, "You have been uploaded to a stationary terminal. Remote device connection restored.") - to_chat(user, "Transfer successful: [AI.name] ([rand(1000,9999)].exe) installed and executed successfully. Local copy has been removed.") - card.AI = null - AI.battery = circuit.battery - qdel(src) - else //If for some reason you use an empty card on an empty AI terminal. - to_chat(user, "There is no AI loaded on this terminal.") - -/obj/item/circuitboard/aicore - name = "AI core (AI Core Board)" //Well, duh, but best to be consistent - var/battery = 200 //backup battery for when the AI loses power. Copied to/from AI mobs when carding, and placed here to avoid recharge via deconning the core +/obj/structure/AIcore + density = TRUE + anchored = FALSE + name = "\improper AI core" + icon = 'icons/mob/ai.dmi' + icon_state = "0" + desc = "The framework for an artificial intelligence core." + max_integrity = 500 + var/state = EMPTY_CORE + var/datum/ai_laws/laws + var/obj/item/circuitboard/aicore/circuit + var/obj/item/mmi/brain + var/can_deconstruct = TRUE + +/obj/structure/AIcore/Initialize() + . = ..() + laws = new + laws.set_laws_config() + +/obj/structure/AIcore/handle_atom_del(atom/A) + if(A == circuit) + circuit = null + if((state != GLASS_CORE) && (state != AI_READY_CORE)) + state = EMPTY_CORE + update_icon() + if(A == brain) + brain = null + . = ..() + + +/obj/structure/AIcore/Destroy() + if(circuit) + qdel(circuit) + circuit = null + if(brain) + qdel(brain) + brain = null + return ..() + +/obj/structure/AIcore/latejoin_inactive + name = "networked AI core" + desc = "This AI core is connected by bluespace transmitters to NTNet, allowing for an AI personality to be downloaded to it on the fly mid-shift." + can_deconstruct = FALSE + icon_state = "ai-empty" + anchored = TRUE + state = AI_READY_CORE + var/available = TRUE + var/safety_checks = TRUE + var/active = TRUE + +/obj/structure/AIcore/latejoin_inactive/examine(mob/user) + . = ..() + . += "Its transmitter seems to be [active? "on" : "off"]." + . += "You could [active? "deactivate" : "activate"] it with a multitool." + +/obj/structure/AIcore/latejoin_inactive/proc/is_available() //If people still manage to use this feature to spawn-kill AI latejoins ahelp them. + if(!available) + return FALSE + if(!safety_checks) + return TRUE + if(!active) + return FALSE + var/turf/T = get_turf(src) + var/area/A = get_area(src) + if(!A.blob_allowed) + return FALSE + if(!A.power_equip) + return FALSE + if(!SSmapping.level_trait(T.z,ZTRAIT_STATION)) + return FALSE + if(!istype(T, /turf/open/floor)) + return FALSE + return TRUE + +/obj/structure/AIcore/latejoin_inactive/attackby(obj/item/P, mob/user, params) + if(P.tool_behaviour == TOOL_MULTITOOL) + active = !active + to_chat(user, "You [active? "activate" : "deactivate"] \the [src]'s transmitters.") + return + return ..() + +/obj/structure/AIcore/latejoin_inactive/Initialize() + . = ..() + GLOB.latejoin_ai_cores += src + +/obj/structure/AIcore/latejoin_inactive/Destroy() + GLOB.latejoin_ai_cores -= src + return ..() + +/obj/structure/AIcore/attackby(obj/item/P, mob/user, params) + if(P.tool_behaviour == TOOL_WRENCH) + return default_unfasten_wrench(user, P, 20) + if(!anchored) + if(P.tool_behaviour == TOOL_WELDER && can_deconstruct) + if(state != EMPTY_CORE) + to_chat(user, "The core must be empty to deconstruct it!") + return + + if(!P.tool_start_check(user, amount=0)) + return + + to_chat(user, "You start to deconstruct the frame...") + if(P.use_tool(src, user, 20, volume=50) && state == EMPTY_CORE) + to_chat(user, "You deconstruct the frame.") + deconstruct(TRUE) + return + else + switch(state) + if(EMPTY_CORE) + if(istype(P, /obj/item/circuitboard/aicore)) + if(!user.transferItemToLoc(P, src)) + return + playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE) + to_chat(user, "You place the circuit board inside the frame.") + update_icon() + state = CIRCUIT_CORE + circuit = P + return + if(CIRCUIT_CORE) + if(P.tool_behaviour == TOOL_SCREWDRIVER) + P.play_tool_sound(src) + to_chat(user, "You screw the circuit board into place.") + state = SCREWED_CORE + update_icon() + return + if(P.tool_behaviour == TOOL_CROWBAR) + P.play_tool_sound(src) + to_chat(user, "You remove the circuit board.") + state = EMPTY_CORE + update_icon() + circuit.forceMove(loc) + circuit = null + return + if(SCREWED_CORE) + if(P.tool_behaviour == TOOL_SCREWDRIVER && circuit) + P.play_tool_sound(src) + to_chat(user, "You unfasten the circuit board.") + state = CIRCUIT_CORE + update_icon() + return + if(istype(P, /obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/C = P + if(C.get_amount() >= 5) + playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE) + to_chat(user, "You start to add cables to the frame...") + if(do_after(user, 20, target = src) && state == SCREWED_CORE && C.use(5)) + to_chat(user, "You add cables to the frame.") + state = CABLED_CORE + update_icon() + else + to_chat(user, "You need five lengths of cable to wire the AI core!") + return + if(CABLED_CORE) + if(P.tool_behaviour == TOOL_WIRECUTTER) + if(brain) + to_chat(user, "Get that [brain.name] out of there first!") + else + P.play_tool_sound(src) + to_chat(user, "You remove the cables.") + state = SCREWED_CORE + update_icon() + new /obj/item/stack/cable_coil(drop_location(), 5) + return + + if(istype(P, /obj/item/stack/sheet/rglass)) + var/obj/item/stack/sheet/rglass/G = P + if(G.get_amount() >= 2) + playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE) + to_chat(user, "You start to put in the glass panel...") + if(do_after(user, 20, target = src) && state == CABLED_CORE && G.use(2)) + to_chat(user, "You put in the glass panel.") + state = GLASS_CORE + update_icon() + else + to_chat(user, "You need two sheets of reinforced glass to insert them into the AI core!") + return + + if(istype(P, /obj/item/aiModule)) + if(brain && brain.laws.id != DEFAULT_AI_LAWID) + to_chat(user, "The installed [brain.name] already has set laws!") + return + var/obj/item/aiModule/module = P + module.install(laws, user) + return + + if(istype(P, /obj/item/mmi) && !brain) + var/obj/item/mmi/M = P + if(!M.brain_check(user)) + return + + var/mob/living/brain/B = M.brainmob + if(!CONFIG_GET(flag/allow_ai) || (is_banned_from(B.ckey, "AI") && !QDELETED(src) && !QDELETED(user) && !QDELETED(M) && !QDELETED(user) && Adjacent(user))) + if(!QDELETED(M)) + to_chat(user, "This [M.name] does not seem to fit!") + return + if(!user.transferItemToLoc(M,src)) + return + + brain = M + to_chat(user, "You add [M.name] to the frame.") + update_icon() + return + + if(P.tool_behaviour == TOOL_CROWBAR && brain) + P.play_tool_sound(src) + to_chat(user, "You remove the brain.") + brain.forceMove(loc) + brain = null + update_icon() + return + + if(GLASS_CORE) + if(P.tool_behaviour == TOOL_CROWBAR) + P.play_tool_sound(src) + to_chat(user, "You remove the glass panel.") + state = CABLED_CORE + update_icon() + new /obj/item/stack/sheet/rglass(loc, 2) + return + + if(P.tool_behaviour == TOOL_SCREWDRIVER) + P.play_tool_sound(src) + to_chat(user, "You connect the monitor.") + if(brain) + var/mob/living/brain/B = brain.brainmob + SSticker.mode.remove_antag_for_borging(B.mind) + + var/mob/living/silicon/ai/A = null + + if (brain.overrides_aicore_laws) + A = new /mob/living/silicon/ai(loc, brain.laws, B) + else + A = new /mob/living/silicon/ai(loc, laws, B) + + if(brain.force_replace_ai_name) + A.fully_replace_character_name(A.name, brain.replacement_ai_name()) + SSblackbox.record_feedback("amount", "ais_created", 1) + deadchat_broadcast(" has been brought online at [get_area_name(A, TRUE)].", "[A]", follow_target=A, message_type=DEADCHAT_ANNOUNCEMENT) + qdel(src) + else + state = AI_READY_CORE + update_icon() + return + + if(AI_READY_CORE) + if(istype(P, /obj/item/aicard)) + P.transfer_ai("INACTIVE", "AICARD", src, user) + return + + if(P.tool_behaviour == TOOL_SCREWDRIVER) + P.play_tool_sound(src) + to_chat(user, "You disconnect the monitor.") + state = GLASS_CORE + update_icon() + return + return ..() + +/obj/structure/AIcore/update_icon_state() + switch(state) + if(EMPTY_CORE) + icon_state = "0" + if(CIRCUIT_CORE) + icon_state = "1" + if(SCREWED_CORE) + icon_state = "2" + if(CABLED_CORE) + if(brain) + icon_state = "3b" + else + icon_state = "3" + if(GLASS_CORE) + icon_state = "4" + if(AI_READY_CORE) + icon_state = "ai-empty" + +/obj/structure/AIcore/deconstruct(disassembled = TRUE) + if(state == GLASS_CORE) + new /obj/item/stack/sheet/rglass(loc, 2) + if(state >= CABLED_CORE) + new /obj/item/stack/cable_coil(loc, 5) + if(circuit) + circuit.forceMove(loc) + circuit = null + new /obj/item/stack/sheet/plasteel(loc, 4) + qdel(src) + +/obj/structure/AIcore/deactivated + name = "inactive AI" + icon_state = "ai-empty" + anchored = TRUE + state = AI_READY_CORE + +/obj/structure/AIcore/deactivated/Initialize() + . = ..() + circuit = new(src) + + +/* +This is a good place for AI-related object verbs so I'm sticking it here. +If adding stuff to this, don't forget that an AI need to cancel_camera() whenever it physically moves to a different location. +That prevents a few funky behaviors. +*/ +//The type of interaction, the player performing the operation, the AI itself, and the card object, if any. + + +/atom/proc/transfer_ai(interaction, mob/user, mob/living/silicon/ai/AI, obj/item/aicard/card) + if(istype(card)) + if(card.flush) + to_chat(user, "ERROR: AI flush is in progress, cannot execute transfer protocol.") + return FALSE + return TRUE + +/obj/structure/AIcore/transfer_ai(interaction, mob/user, mob/living/silicon/ai/AI, obj/item/aicard/card) + if(state != AI_READY_CORE || !..()) + return + //Transferring a carded AI to a core. + if(interaction == AI_TRANS_FROM_CARD) + AI.control_disabled = FALSE + AI.radio_enabled = TRUE + AI.forceMove(loc) // to replace the terminal. + to_chat(AI, "You have been uploaded to a stationary terminal. Remote device connection restored.") + to_chat(user, "Transfer successful: [AI.name] ([rand(1000,9999)].exe) installed and executed successfully. Local copy has been removed.") + card.AI = null + AI.battery = circuit.battery + qdel(src) + else //If for some reason you use an empty card on an empty AI terminal. + to_chat(user, "There is no AI loaded on this terminal.") + +/obj/item/circuitboard/aicore + name = "AI core (AI Core Board)" //Well, duh, but best to be consistent + var/battery = 200 //backup battery for when the AI loses power. Copied to/from AI mobs when carding, and placed here to avoid recharge via deconning the core diff --git a/code/game/objects/structures/artstuff.dm b/code/game/objects/structures/artstuff.dm index 8e5631904945..f1cf151c3638 100644 --- a/code/game/objects/structures/artstuff.dm +++ b/code/game/objects/structures/artstuff.dm @@ -45,8 +45,6 @@ var/height = 11 var/list/grid var/canvas_color = "#ffffff" //empty canvas color - var/ui_x = 400 - var/ui_y = 400 var/used = FALSE var/painting_name //Painting name, this is set after framing. var/finalized = FALSE //Blocks edits @@ -75,12 +73,16 @@ . = ..() ui_interact(user) -/obj/item/canvas/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) +/obj/item/canvas/ui_state(mob/user) + if(finalized) + return GLOB.physical_obscured_state + else + return GLOB.default_state - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/item/canvas/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "Canvas", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, "Canvas", name) ui.set_autoupdate(FALSE) ui.open() @@ -190,8 +192,6 @@ icon_state = "19x19" width = 19 height = 19 - ui_x = 600 - ui_y = 600 pixel_x = 6 pixel_y = 9 framed_offset_x = 8 @@ -201,8 +201,6 @@ icon_state = "23x19" width = 23 height = 19 - ui_x = 800 - ui_y = 600 pixel_x = 4 pixel_y = 10 framed_offset_x = 6 @@ -212,8 +210,6 @@ icon_state = "23x23" width = 23 height = 23 - ui_x = 800 - ui_y = 800 pixel_x = 5 pixel_y = 9 framed_offset_x = 5 @@ -262,7 +258,7 @@ /obj/structure/sign/painting/examine(mob/user) . = ..() if(C) - C.ui_interact(user,state = GLOB.physical_obscured_state) + C.ui_interact(user) /obj/structure/sign/painting/wirecutter_act(mob/living/user, obj/item/I) . = ..() @@ -292,11 +288,6 @@ else icon_state = "frame-empty" -/obj/structure/sign/painting/examine(mob/user) - . = ..() - if(C) - C.ui_interact(user,state = GLOB.physical_obscured_state) - /obj/structure/sign/painting/update_overlays() . = ..() if(C && C.generated_icon) diff --git a/code/game/objects/structures/bedsheet_bin.dm b/code/game/objects/structures/bedsheet_bin.dm index 020b9829ce7b..54d408e262e8 100644 --- a/code/game/objects/structures/bedsheet_bin.dm +++ b/code/game/objects/structures/bedsheet_bin.dm @@ -1,413 +1,413 @@ -/* -CONTAINS: -BEDSHEETS -LINEN BINS -*/ - -/obj/item/bedsheet - name = "bedsheet" - desc = "A surprisingly soft linen bedsheet." - icon = 'icons/obj/bedsheets.dmi' - lefthand_file = 'icons/mob/inhands/misc/bedsheet_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/bedsheet_righthand.dmi' - icon_state = "sheetwhite" - item_state = "sheetwhite" - slot_flags = ITEM_SLOT_NECK - layer = MOB_LAYER - throwforce = 0 - throw_speed = 1 - throw_range = 2 - w_class = WEIGHT_CLASS_TINY - resistance_flags = FLAMMABLE - dying_key = DYE_REGISTRY_BEDSHEET - - dog_fashion = /datum/dog_fashion/head/ghost - var/list/dream_messages = list("white") - -/obj/item/bedsheet/attack_self(mob/user) - if(!user.CanReach(src)) //No telekenetic grabbing. - return - if(!user.dropItemToGround(src)) - return - if(layer == initial(layer)) - layer = ABOVE_MOB_LAYER - to_chat(user, "You cover yourself with [src].") - pixel_x = 0 - pixel_y = 0 - else - layer = initial(layer) - to_chat(user, "You smooth [src] out beneath you.") - add_fingerprint(user) - return - -/obj/item/bedsheet/attackby(obj/item/I, mob/user, params) - if(I.tool_behaviour == TOOL_WIRECUTTER || I.get_sharpness()) - var/obj/item/stack/sheet/cloth/C = new (get_turf(src), 3) - transfer_fingerprints_to(C) - C.add_fingerprint(user) - qdel(src) - to_chat(user, "You tear [src] up.") - else - return ..() - -/obj/item/bedsheet/blue - icon_state = "sheetblue" - item_state = "sheetblue" - dream_messages = list("blue") - -/obj/item/bedsheet/green - icon_state = "sheetgreen" - item_state = "sheetgreen" - dream_messages = list("green") - -/obj/item/bedsheet/grey - icon_state = "sheetgrey" - item_state = "sheetgrey" - dream_messages = list("grey") - -/obj/item/bedsheet/orange - icon_state = "sheetorange" - item_state = "sheetorange" - dream_messages = list("orange") - -/obj/item/bedsheet/purple - icon_state = "sheetpurple" - item_state = "sheetpurple" - dream_messages = list("purple") - -/obj/item/bedsheet/patriot - name = "patriotic bedsheet" - desc = "You've never felt more free than when sleeping on this." - icon_state = "sheetUSA" - item_state = "sheetUSA" - dream_messages = list("America", "freedom", "fireworks", "bald eagles") - -/obj/item/bedsheet/rainbow - name = "rainbow bedsheet" - desc = "A multicolored blanket. It's actually several different sheets cut up and sewn together." - icon_state = "sheetrainbow" - item_state = "sheetrainbow" - dream_messages = list("red", "orange", "yellow", "green", "blue", "purple", "a rainbow") - -/obj/item/bedsheet/red - icon_state = "sheetred" - item_state = "sheetred" - dream_messages = list("red") - -/obj/item/bedsheet/yellow - icon_state = "sheetyellow" - item_state = "sheetyellow" - dream_messages = list("yellow") - -/obj/item/bedsheet/mime - name = "mime's blanket" - desc = "A very soothing striped blanket. All the noise just seems to fade out when you're under the covers in this." - icon_state = "sheetmime" - item_state = "sheetmime" - dream_messages = list("silence", "gestures", "a pale face", "a gaping mouth", "the mime") - -/obj/item/bedsheet/clown - name = "clown's blanket" - desc = "A rainbow blanket with a clown mask woven in. It smells faintly of bananas." - icon_state = "sheetclown" - item_state = "sheetrainbow" - dream_messages = list("honk", "laughter", "a prank", "a joke", "a smiling face", "the clown") - -/obj/item/bedsheet/captain - name = "captain's bedsheet" - desc = "It has a Nanotrasen symbol on it, and was woven with a revolutionary new kind of thread guaranteed to have 0.01% permeability for most non-chemical substances, popular among most modern captains." - icon_state = "sheetcaptain" - item_state = "sheetcaptain" - dream_messages = list("authority", "a golden ID", "sunglasses", "a green disc", "an antique gun", "the captain") - -/obj/item/bedsheet/rd - name = "research director's bedsheet" - desc = "It appears to have a beaker emblem, and is made out of fire-resistant material, although it probably won't protect you in the event of fires you're familiar with every day." - icon_state = "sheetrd" - item_state = "sheetrd" - dream_messages = list("authority", "a silvery ID", "a bomb", "a mech", "a facehugger", "maniacal laughter", "the research director") - -// for Free Golems. -/obj/item/bedsheet/rd/royal_cape - name = "Royal Cape of the Liberator" - desc = "Majestic." - dream_messages = list("mining", "stone", "a golem", "freedom", "doing whatever") - -/obj/item/bedsheet/medical - name = "medical blanket" - desc = "It's a sterilized* blanket commonly used in the Medbay. *Sterilization is voided if a virologist is present onboard the station." - icon_state = "sheetmedical" - item_state = "sheetmedical" - dream_messages = list("healing", "life", "surgery", "a doctor") - -/obj/item/bedsheet/cmo - name = "chief medical officer's bedsheet" - desc = "It's a sterilized blanket that has a cross emblem. There's some cat fur on it, likely from Runtime." - icon_state = "sheetcmo" - item_state = "sheetcmo" - dream_messages = list("authority", "a silvery ID", "healing", "life", "surgery", "a cat", "the chief medical officer") - -/obj/item/bedsheet/hos - name = "head of security's bedsheet" - desc = "It is decorated with a shield emblem. While crime doesn't sleep, you do, but you are still THE LAW!" - icon_state = "sheethos" - item_state = "sheethos" - dream_messages = list("authority", "a silvery ID", "handcuffs", "a baton", "a flashbang", "sunglasses", "the head of security") - -/obj/item/bedsheet/head_of_personnel - name = "head of personnel's bedsheet" - desc = "It is decorated with a key emblem. For those rare moments when you can rest and cuddle with Ian without someone screaming for you over the radio." - icon_state = "sheethop" - item_state = "sheethop" - dream_messages = list("authority", "a silvery ID", "obligation", "a computer", "an ID", "a corgi", "the head of personnel") - -/obj/item/bedsheet/ce - name = "chief engineer's bedsheet" - desc = "It is decorated with a wrench emblem. It's highly reflective and stain resistant, so you don't need to worry about ruining it with oil." - icon_state = "sheetce" - item_state = "sheetce" - dream_messages = list("authority", "a silvery ID", "the engine", "power tools", "an APC", "a parrot", "the chief engineer") - -/obj/item/bedsheet/qm - name = "quartermaster's bedsheet" - desc = "It is decorated with a crate emblem in silver lining. It's rather tough, and just the thing to lie on after a hard day of pushing paper." - icon_state = "sheetqm" - item_state = "sheetqm" - dream_messages = list("a grey ID", "a shuttle", "a crate", "a sloth", "the quartermaster") - -/obj/item/bedsheet/chaplain - name = "chaplain's blanket" - desc = "A blanket woven with the hearts of gods themselves... Wait, that's just linen." - icon_state = "sheetchap" - item_state = "sheetchap" - dream_messages = list("a grey ID", "the gods", "a fulfilled prayer", "a cult", "the chaplain") - -/obj/item/bedsheet/brown - icon_state = "sheetbrown" - item_state = "sheetbrown" - dream_messages = list("brown") - -/obj/item/bedsheet/black - icon_state = "sheetblack" - item_state = "sheetblack" - dream_messages = list("black") - -/obj/item/bedsheet/centcom - name = "\improper CentCom bedsheet" - desc = "Woven with advanced nanothread for warmth as well as being very decorated, essential for all officials." - icon_state = "sheetcentcom" - item_state = "sheetcentcom" - dream_messages = list("a unique ID", "authority", "artillery", "an ending") - -/obj/item/bedsheet/syndie - name = "syndicate bedsheet" - desc = "It has a syndicate emblem and it has an aura of evil." - icon_state = "sheetsyndie" - item_state = "sheetsyndie" - dream_messages = list("a green disc", "a red crystal", "a glowing blade", "a wire-covered ID") - -/obj/item/bedsheet/cult - name = "cultist's bedsheet" - desc = "You might dream of Nar'Sie if you sleep with this. It seems rather tattered and glows of an eldritch presence." - icon_state = "sheetcult" - item_state = "sheetcult" - dream_messages = list("a tome", "a floating red crystal", "a glowing sword", "a bloody symbol", "a massive humanoid figure") - -/obj/item/bedsheet/wiz - name = "wizard's bedsheet" - desc = "A special fabric enchanted with magic so you can have an enchanted night. It even glows!" - icon_state = "sheetwiz" - item_state = "sheetwiz" - dream_messages = list("a book", "an explosion", "lightning", "a staff", "a skeleton", "a robe", "magic") - -/obj/item/bedsheet/nanotrasen - name = "\improper Nanotrasen bedsheet" - desc = "It has the Nanotrasen logo on it and has an aura of duty." - icon_state = "sheetNT" - item_state = "sheetNT" - dream_messages = list("authority", "an ending") - -/obj/item/bedsheet/ian - icon_state = "sheetian" - item_state = "sheetian" - dream_messages = list("a dog", "a corgi", "woof", "bark", "arf") - -/obj/item/bedsheet/cosmos - name = "cosmic space bedsheet" - desc = "Made from the dreams of those who wonder at the stars." - icon_state = "sheetcosmos" - item_state = "sheetcosmos" - dream_messages = list("the infinite cosmos", "Hans Zimmer music", "a flight through space", "the galaxy", "being fabulous", "shooting stars") - light_power = 2 - light_range = 1.4 - -/obj/item/bedsheet/random - icon_state = "random_bedsheet" - name = "random bedsheet" - desc = "If you're reading this description ingame, something has gone wrong! Honk!" - -/obj/item/bedsheet/random/Initialize() - ..() - var/type = pick(typesof(/obj/item/bedsheet) - /obj/item/bedsheet/random) - new type(loc) - return INITIALIZE_HINT_QDEL - -/obj/item/bedsheet/dorms - icon_state = "random_bedsheet" - name = "random dorms bedsheet" - desc = "If you're reading this description ingame, something has gone wrong! Honk!" - -/obj/item/bedsheet/dorms/Initialize() - ..() - var/type = pickweight(list("Colors" = 80, "Special" = 20)) - switch(type) - if("Colors") - type = pick(list(/obj/item/bedsheet, - /obj/item/bedsheet/blue, - /obj/item/bedsheet/green, - /obj/item/bedsheet/grey, - /obj/item/bedsheet/orange, - /obj/item/bedsheet/purple, - /obj/item/bedsheet/red, - /obj/item/bedsheet/yellow, - /obj/item/bedsheet/brown, - /obj/item/bedsheet/black)) - if("Special") - type = pick(list(/obj/item/bedsheet/patriot, - /obj/item/bedsheet/rainbow, - /obj/item/bedsheet/ian, - /obj/item/bedsheet/cosmos, - /obj/item/bedsheet/nanotrasen)) - new type(loc) - return INITIALIZE_HINT_QDEL - -/obj/structure/bedsheetbin - name = "linen bin" - desc = "It looks rather cosy." - icon = 'icons/obj/structures.dmi' - icon_state = "linenbin-full" - anchored = TRUE - resistance_flags = FLAMMABLE - max_integrity = 70 - var/amount = 10 - var/list/sheets = list() - var/obj/item/hidden = null - -/obj/structure/bedsheetbin/empty - amount = 0 - icon_state = "linenbin-empty" - anchored = FALSE - - -/obj/structure/bedsheetbin/examine(mob/user) - . = ..() - if(amount < 1) - . += "There are no bed sheets in the bin." - else if(amount == 1) - . += "There is one bed sheet in the bin." - else - . += "There are [amount] bed sheets in the bin." - - -/obj/structure/bedsheetbin/update_icon_state() - switch(amount) - if(0) - icon_state = "linenbin-empty" - if(1 to 5) - icon_state = "linenbin-half" - else - icon_state = "linenbin-full" - -/obj/structure/bedsheetbin/fire_act(exposed_temperature, exposed_volume) - if(amount) - amount = 0 - update_icon() - ..() - -/obj/structure/bedsheetbin/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/bedsheet)) - if(!user.transferItemToLoc(I, src)) - return - sheets.Add(I) - amount++ - to_chat(user, "You put [I] in [src].") - update_icon() - - else if(default_unfasten_wrench(user, I, 5)) - return - - else if(I.tool_behaviour == TOOL_SCREWDRIVER) - if(flags_1 & NODECONSTRUCT_1) - return - if(amount) - to_chat(user, "The [src] must be empty first!") - return - if(I.use_tool(src, user, 5, volume=50)) - to_chat(user, "You disassemble the [src].") - new /obj/item/stack/rods(loc, 2) - qdel(src) - - else if(amount && !hidden && I.w_class < WEIGHT_CLASS_BULKY) //make sure there's sheets to hide it among, make sure nothing else is hidden in there. - if(!user.transferItemToLoc(I, src)) - to_chat(user, "\The [I] is stuck to your hand, you cannot hide it among the sheets!") - return - hidden = I - to_chat(user, "You hide [I] among the sheets.") - - -/obj/structure/bedsheetbin/attack_paw(mob/user) - return attack_hand(user) - -/obj/structure/bedsheetbin/attack_hand(mob/user) - . = ..() - if(.) - return - if(isliving(user)) - var/mob/living/L = user - if(!(L.mobility_flags & MOBILITY_PICKUP)) - return - if(amount >= 1) - amount-- - - var/obj/item/bedsheet/B - if(sheets.len > 0) - B = sheets[sheets.len] - sheets.Remove(B) - - else - B = new /obj/item/bedsheet(loc) - - B.forceMove(drop_location()) - user.put_in_hands(B) - to_chat(user, "You take [B] out of [src].") - update_icon() - - if(hidden) - hidden.forceMove(drop_location()) - to_chat(user, "[hidden] falls out of [B]!") - hidden = null - - - add_fingerprint(user) -/obj/structure/bedsheetbin/attack_tk(mob/user) - if(amount >= 1) - amount-- - - var/obj/item/bedsheet/B - if(sheets.len > 0) - B = sheets[sheets.len] - sheets.Remove(B) - - else - B = new /obj/item/bedsheet(loc) - - B.forceMove(drop_location()) - to_chat(user, "You telekinetically remove [B] from [src].") - update_icon() - - if(hidden) - hidden.forceMove(drop_location()) - hidden = null - - - add_fingerprint(user) +/* +CONTAINS: +BEDSHEETS +LINEN BINS +*/ + +/obj/item/bedsheet + name = "bedsheet" + desc = "A surprisingly soft linen bedsheet." + icon = 'icons/obj/bedsheets.dmi' + lefthand_file = 'icons/mob/inhands/misc/bedsheet_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/bedsheet_righthand.dmi' + icon_state = "sheetwhite" + item_state = "sheetwhite" + slot_flags = ITEM_SLOT_NECK + layer = MOB_LAYER + throwforce = 0 + throw_speed = 1 + throw_range = 2 + w_class = WEIGHT_CLASS_TINY + resistance_flags = FLAMMABLE + dying_key = DYE_REGISTRY_BEDSHEET + + dog_fashion = /datum/dog_fashion/head/ghost + var/list/dream_messages = list("white") + +/obj/item/bedsheet/attack_self(mob/user) + if(!user.CanReach(src)) //No telekenetic grabbing. + return + if(!user.dropItemToGround(src)) + return + if(layer == initial(layer)) + layer = ABOVE_MOB_LAYER + to_chat(user, "You cover yourself with [src].") + pixel_x = 0 + pixel_y = 0 + else + layer = initial(layer) + to_chat(user, "You smooth [src] out beneath you.") + add_fingerprint(user) + return + +/obj/item/bedsheet/attackby(obj/item/I, mob/user, params) + if(I.tool_behaviour == TOOL_WIRECUTTER || I.get_sharpness()) + var/obj/item/stack/sheet/cloth/C = new (get_turf(src), 3) + transfer_fingerprints_to(C) + C.add_fingerprint(user) + qdel(src) + to_chat(user, "You tear [src] up.") + else + return ..() + +/obj/item/bedsheet/blue + icon_state = "sheetblue" + item_state = "sheetblue" + dream_messages = list("blue") + +/obj/item/bedsheet/green + icon_state = "sheetgreen" + item_state = "sheetgreen" + dream_messages = list("green") + +/obj/item/bedsheet/grey + icon_state = "sheetgrey" + item_state = "sheetgrey" + dream_messages = list("grey") + +/obj/item/bedsheet/orange + icon_state = "sheetorange" + item_state = "sheetorange" + dream_messages = list("orange") + +/obj/item/bedsheet/purple + icon_state = "sheetpurple" + item_state = "sheetpurple" + dream_messages = list("purple") + +/obj/item/bedsheet/patriot + name = "patriotic bedsheet" + desc = "You've never felt more free than when sleeping on this." + icon_state = "sheetUSA" + item_state = "sheetUSA" + dream_messages = list("America", "freedom", "fireworks", "bald eagles") + +/obj/item/bedsheet/rainbow + name = "rainbow bedsheet" + desc = "A multicolored blanket. It's actually several different sheets cut up and sewn together." + icon_state = "sheetrainbow" + item_state = "sheetrainbow" + dream_messages = list("red", "orange", "yellow", "green", "blue", "purple", "a rainbow") + +/obj/item/bedsheet/red + icon_state = "sheetred" + item_state = "sheetred" + dream_messages = list("red") + +/obj/item/bedsheet/yellow + icon_state = "sheetyellow" + item_state = "sheetyellow" + dream_messages = list("yellow") + +/obj/item/bedsheet/mime + name = "mime's blanket" + desc = "A very soothing striped blanket. All the noise just seems to fade out when you're under the covers in this." + icon_state = "sheetmime" + item_state = "sheetmime" + dream_messages = list("silence", "gestures", "a pale face", "a gaping mouth", "the mime") + +/obj/item/bedsheet/clown + name = "clown's blanket" + desc = "A rainbow blanket with a clown mask woven in. It smells faintly of bananas." + icon_state = "sheetclown" + item_state = "sheetrainbow" + dream_messages = list("honk", "laughter", "a prank", "a joke", "a smiling face", "the clown") + +/obj/item/bedsheet/captain + name = "captain's bedsheet" + desc = "It has a Nanotrasen symbol on it, and was woven with a revolutionary new kind of thread guaranteed to have 0.01% permeability for most non-chemical substances, popular among most modern captains." + icon_state = "sheetcaptain" + item_state = "sheetcaptain" + dream_messages = list("authority", "a golden ID", "sunglasses", "a green disc", "an antique gun", "the captain") + +/obj/item/bedsheet/rd + name = "research director's bedsheet" + desc = "It appears to have a beaker emblem, and is made out of fire-resistant material, although it probably won't protect you in the event of fires you're familiar with every day." + icon_state = "sheetrd" + item_state = "sheetrd" + dream_messages = list("authority", "a silvery ID", "a bomb", "a mech", "a facehugger", "maniacal laughter", "the research director") + +// for Free Golems. +/obj/item/bedsheet/rd/royal_cape + name = "Royal Cape of the Liberator" + desc = "Majestic." + dream_messages = list("mining", "stone", "a golem", "freedom", "doing whatever") + +/obj/item/bedsheet/medical + name = "medical blanket" + desc = "It's a sterilized* blanket commonly used in the Medbay. *Sterilization is voided if a virologist is present onboard the station." + icon_state = "sheetmedical" + item_state = "sheetmedical" + dream_messages = list("healing", "life", "surgery", "a doctor") + +/obj/item/bedsheet/cmo + name = "chief medical officer's bedsheet" + desc = "It's a sterilized blanket that has a cross emblem. There's some cat fur on it, likely from Runtime." + icon_state = "sheetcmo" + item_state = "sheetcmo" + dream_messages = list("authority", "a silvery ID", "healing", "life", "surgery", "a cat", "the chief medical officer") + +/obj/item/bedsheet/hos + name = "head of security's bedsheet" + desc = "It is decorated with a shield emblem. While crime doesn't sleep, you do, but you are still THE LAW!" + icon_state = "sheethos" + item_state = "sheethos" + dream_messages = list("authority", "a silvery ID", "handcuffs", "a baton", "a flashbang", "sunglasses", "the head of security") + +/obj/item/bedsheet/head_of_personnel + name = "head of personnel's bedsheet" + desc = "It is decorated with a key emblem. For those rare moments when you can rest and cuddle with Ian without someone screaming for you over the radio." + icon_state = "sheethop" + item_state = "sheethop" + dream_messages = list("authority", "a silvery ID", "obligation", "a computer", "an ID", "a corgi", "the head of personnel") + +/obj/item/bedsheet/ce + name = "chief engineer's bedsheet" + desc = "It is decorated with a wrench emblem. It's highly reflective and stain resistant, so you don't need to worry about ruining it with oil." + icon_state = "sheetce" + item_state = "sheetce" + dream_messages = list("authority", "a silvery ID", "the engine", "power tools", "an APC", "a parrot", "the chief engineer") + +/obj/item/bedsheet/qm + name = "quartermaster's bedsheet" + desc = "It is decorated with a crate emblem in silver lining. It's rather tough, and just the thing to lie on after a hard day of pushing paper." + icon_state = "sheetqm" + item_state = "sheetqm" + dream_messages = list("a grey ID", "a shuttle", "a crate", "a sloth", "the quartermaster") + +/obj/item/bedsheet/chaplain + name = "chaplain's blanket" + desc = "A blanket woven with the hearts of gods themselves... Wait, that's just linen." + icon_state = "sheetchap" + item_state = "sheetchap" + dream_messages = list("a grey ID", "the gods", "a fulfilled prayer", "a cult", "the chaplain") + +/obj/item/bedsheet/brown + icon_state = "sheetbrown" + item_state = "sheetbrown" + dream_messages = list("brown") + +/obj/item/bedsheet/black + icon_state = "sheetblack" + item_state = "sheetblack" + dream_messages = list("black") + +/obj/item/bedsheet/centcom + name = "\improper CentCom bedsheet" + desc = "Woven with advanced nanothread for warmth as well as being very decorated, essential for all officials." + icon_state = "sheetcentcom" + item_state = "sheetcentcom" + dream_messages = list("a unique ID", "authority", "artillery", "an ending") + +/obj/item/bedsheet/syndie + name = "syndicate bedsheet" + desc = "It has a syndicate emblem and it has an aura of evil." + icon_state = "sheetsyndie" + item_state = "sheetsyndie" + dream_messages = list("a green disc", "a red crystal", "a glowing blade", "a wire-covered ID") + +/obj/item/bedsheet/cult + name = "cultist's bedsheet" + desc = "You might dream of Nar'Sie if you sleep with this. It seems rather tattered and glows of an eldritch presence." + icon_state = "sheetcult" + item_state = "sheetcult" + dream_messages = list("a tome", "a floating red crystal", "a glowing sword", "a bloody symbol", "a massive humanoid figure") + +/obj/item/bedsheet/wiz + name = "wizard's bedsheet" + desc = "A special fabric enchanted with magic so you can have an enchanted night. It even glows!" + icon_state = "sheetwiz" + item_state = "sheetwiz" + dream_messages = list("a book", "an explosion", "lightning", "a staff", "a skeleton", "a robe", "magic") + +/obj/item/bedsheet/nanotrasen + name = "\improper Nanotrasen bedsheet" + desc = "It has the Nanotrasen logo on it and has an aura of duty." + icon_state = "sheetNT" + item_state = "sheetNT" + dream_messages = list("authority", "an ending") + +/obj/item/bedsheet/ian + icon_state = "sheetian" + item_state = "sheetian" + dream_messages = list("a dog", "a corgi", "woof", "bark", "arf") + +/obj/item/bedsheet/cosmos + name = "cosmic space bedsheet" + desc = "Made from the dreams of those who wonder at the stars." + icon_state = "sheetcosmos" + item_state = "sheetcosmos" + dream_messages = list("the infinite cosmos", "Hans Zimmer music", "a flight through space", "the galaxy", "being fabulous", "shooting stars") + light_power = 2 + light_range = 1.4 + +/obj/item/bedsheet/random + icon_state = "random_bedsheet" + name = "random bedsheet" + desc = "If you're reading this description ingame, something has gone wrong! Honk!" + +/obj/item/bedsheet/random/Initialize() + ..() + var/type = pick(typesof(/obj/item/bedsheet) - /obj/item/bedsheet/random) + new type(loc) + return INITIALIZE_HINT_QDEL + +/obj/item/bedsheet/dorms + icon_state = "random_bedsheet" + name = "random dorms bedsheet" + desc = "If you're reading this description ingame, something has gone wrong! Honk!" + +/obj/item/bedsheet/dorms/Initialize() + ..() + var/type = pickweight(list("Colors" = 80, "Special" = 20)) + switch(type) + if("Colors") + type = pick(list(/obj/item/bedsheet, + /obj/item/bedsheet/blue, + /obj/item/bedsheet/green, + /obj/item/bedsheet/grey, + /obj/item/bedsheet/orange, + /obj/item/bedsheet/purple, + /obj/item/bedsheet/red, + /obj/item/bedsheet/yellow, + /obj/item/bedsheet/brown, + /obj/item/bedsheet/black)) + if("Special") + type = pick(list(/obj/item/bedsheet/patriot, + /obj/item/bedsheet/rainbow, + /obj/item/bedsheet/ian, + /obj/item/bedsheet/cosmos, + /obj/item/bedsheet/nanotrasen)) + new type(loc) + return INITIALIZE_HINT_QDEL + +/obj/structure/bedsheetbin + name = "linen bin" + desc = "It looks rather cosy." + icon = 'icons/obj/structures.dmi' + icon_state = "linenbin-full" + anchored = TRUE + resistance_flags = FLAMMABLE + max_integrity = 70 + var/amount = 10 + var/list/sheets = list() + var/obj/item/hidden = null + +/obj/structure/bedsheetbin/empty + amount = 0 + icon_state = "linenbin-empty" + anchored = FALSE + + +/obj/structure/bedsheetbin/examine(mob/user) + . = ..() + if(amount < 1) + . += "There are no bed sheets in the bin." + else if(amount == 1) + . += "There is one bed sheet in the bin." + else + . += "There are [amount] bed sheets in the bin." + + +/obj/structure/bedsheetbin/update_icon_state() + switch(amount) + if(0) + icon_state = "linenbin-empty" + if(1 to 5) + icon_state = "linenbin-half" + else + icon_state = "linenbin-full" + +/obj/structure/bedsheetbin/fire_act(exposed_temperature, exposed_volume) + if(amount) + amount = 0 + update_icon() + ..() + +/obj/structure/bedsheetbin/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/bedsheet)) + if(!user.transferItemToLoc(I, src)) + return + sheets.Add(I) + amount++ + to_chat(user, "You put [I] in [src].") + update_icon() + + else if(default_unfasten_wrench(user, I, 5)) + return + + else if(I.tool_behaviour == TOOL_SCREWDRIVER) + if(flags_1 & NODECONSTRUCT_1) + return + if(amount) + to_chat(user, "The [src] must be empty first!") + return + if(I.use_tool(src, user, 5, volume=50)) + to_chat(user, "You disassemble the [src].") + new /obj/item/stack/rods(loc, 2) + qdel(src) + + else if(amount && !hidden && I.w_class < WEIGHT_CLASS_BULKY) //make sure there's sheets to hide it among, make sure nothing else is hidden in there. + if(!user.transferItemToLoc(I, src)) + to_chat(user, "\The [I] is stuck to your hand, you cannot hide it among the sheets!") + return + hidden = I + to_chat(user, "You hide [I] among the sheets.") + + +/obj/structure/bedsheetbin/attack_paw(mob/user) + return attack_hand(user) + +/obj/structure/bedsheetbin/attack_hand(mob/user) + . = ..() + if(.) + return + if(isliving(user)) + var/mob/living/L = user + if(!(L.mobility_flags & MOBILITY_PICKUP)) + return + if(amount >= 1) + amount-- + + var/obj/item/bedsheet/B + if(sheets.len > 0) + B = sheets[sheets.len] + sheets.Remove(B) + + else + B = new /obj/item/bedsheet(loc) + + B.forceMove(drop_location()) + user.put_in_hands(B) + to_chat(user, "You take [B] out of [src].") + update_icon() + + if(hidden) + hidden.forceMove(drop_location()) + to_chat(user, "[hidden] falls out of [B]!") + hidden = null + + + add_fingerprint(user) +/obj/structure/bedsheetbin/attack_tk(mob/user) + if(amount >= 1) + amount-- + + var/obj/item/bedsheet/B + if(sheets.len > 0) + B = sheets[sheets.len] + sheets.Remove(B) + + else + B = new /obj/item/bedsheet(loc) + + B.forceMove(drop_location()) + to_chat(user, "You telekinetically remove [B] from [src].") + update_icon() + + if(hidden) + hidden.forceMove(drop_location()) + hidden = null + + + add_fingerprint(user) diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm index a54d42f989d2..7fc5b862c269 100644 --- a/code/game/objects/structures/crates_lockers/closets.dm +++ b/code/game/objects/structures/crates_lockers/closets.dm @@ -1,538 +1,538 @@ -/obj/structure/closet - name = "closet" - desc = "It's a basic storage unit." - icon = 'goon/icons/obj/closet.dmi' - icon_state = "generic" - density = TRUE - drag_slowdown = 1.5 // Same as a prone mob - max_integrity = 200 - integrity_failure = 0.25 - armor = list("melee" = 20, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 60) - - var/icon_door = null - var/icon_door_override = FALSE //override to have open overlay use icon different to its base's - var/secure = FALSE //secure locker or not, also used if overriding a non-secure locker with a secure door overlay to add fancy lights - var/opened = FALSE - var/welded = FALSE - var/locked = FALSE - var/large = TRUE - var/wall_mounted = 0 //never solid (You can always pass over it) - var/breakout_time = 1200 - var/message_cooldown - var/can_weld_shut = TRUE - var/horizontal = FALSE - var/allow_objects = FALSE - var/allow_dense = FALSE - var/dense_when_open = FALSE //if it's dense when open or not - var/max_mob_size = MOB_SIZE_HUMAN //Biggest mob_size accepted by the container - var/mob_storage_capacity = 3 // how many human sized mob/living can fit together inside a closet. - var/storage_capacity = 30 //This is so that someone can't pack hundreds of items in a locker/crate then open it in a populated area to crash clients. - var/cutting_tool = /obj/item/weldingtool - var/open_sound = 'sound/machines/closet_open.ogg' - var/close_sound = 'sound/machines/closet_close.ogg' - var/open_sound_volume = 35 - var/close_sound_volume = 50 - var/material_drop = /obj/item/stack/sheet/metal - var/material_drop_amount = 2 - var/delivery_icon = "deliverycloset" //which icon to use when packagewrapped. null to be unwrappable. - var/anchorable = TRUE - var/icon_welded = "welded" - - -/obj/structure/closet/Initialize(mapload) - if(mapload && !opened) // if closed, any item at the crate's loc is put in the contents - addtimer(CALLBACK(src, .proc/take_contents), 0) - . = ..() - update_icon() - PopulateContents() - - RegisterSignal(src, COMSIG_ATOM_CANREACH, .proc/canreach_react) - -/obj/structure/closet/proc/canreach_react(datum/source, list/next) - return COMPONENT_BLOCK_REACH //closed block, open have nothing inside. - -//USE THIS TO FILL IT, NOT INITIALIZE OR NEW -/obj/structure/closet/proc/PopulateContents() - return - -/obj/structure/closet/Destroy() - dump_contents() - return ..() - -/obj/structure/closet/update_icon() - . = ..() - if(!opened) - layer = OBJ_LAYER - else - layer = BELOW_OBJ_LAYER - -/obj/structure/closet/update_overlays() - . = ..() - closet_update_overlays(.) - -/obj/structure/closet/proc/closet_update_overlays(list/new_overlays) - . = new_overlays - SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays) - luminosity = 0 - if(!opened) - if(icon_door) - . += "[icon_door]_door" - else - . += "[icon_state]_door" - if(welded) - . += icon_welded - if(secure && !broken) - //Overlay is similar enough for both that we can use the same mask for both - luminosity = 1 - SSvis_overlays.add_vis_overlay(src, icon, "locked", EMISSIVE_LAYER, EMISSIVE_PLANE, dir, alpha) - if(locked) - . += "locked" - else - . += "unlocked" - else - if(icon_door_override) - . += "[icon_door]_open" - else - . += "[icon_state]_open" - -/obj/structure/closet/examine(mob/user) - . = ..() - if(welded) - . += "It's welded shut." - if(anchored) - . += "It is bolted to the ground." - if(opened) - . += "The parts are welded together." - else if(secure && !opened) - . += "Alt-click to [locked ? "unlock" : "lock"]." - if(isliving(user)) - var/mob/living/L = user - if(HAS_TRAIT(L, TRAIT_SKITTISH)) - . += "Ctrl-Shift-click [src] to jump inside." - -/obj/structure/closet/CanAllowThrough(atom/movable/mover, turf/target) - . = ..() - if(wall_mounted) - return TRUE - -/obj/structure/closet/proc/can_open(mob/living/user, force = FALSE) - if(force) - return TRUE - if(welded || locked) - return FALSE - var/turf/T = get_turf(src) - for(var/mob/living/L in T) - if(L.anchored || horizontal && L.mob_size > MOB_SIZE_TINY && L.density) - if(user) - to_chat(user, "There's something large on top of [src], preventing it from opening." ) - return FALSE - return TRUE - -/obj/structure/closet/proc/can_close(mob/living/user) - var/turf/T = get_turf(src) - for(var/obj/structure/closet/closet in T) - if(closet != src && !closet.wall_mounted) - return FALSE - for(var/mob/living/L in T) - if(L.anchored || horizontal && L.mob_size > MOB_SIZE_TINY && L.density) - if(user) - to_chat(user, "There's something too large in [src], preventing it from closing.") - return FALSE - return TRUE - -/obj/structure/closet/dump_contents() - var/atom/L = drop_location() - for(var/atom/movable/AM in src) - AM.forceMove(L) - if(throwing) // you keep some momentum when getting out of a thrown closet - step(AM, dir) - if(throwing) - throwing.finalize(FALSE) - -/obj/structure/closet/proc/take_contents() - var/atom/L = drop_location() - for(var/atom/movable/AM in L) - if(AM != src && insert(AM) == -1) // limit reached - break - -/obj/structure/closet/proc/open(mob/living/user, force = FALSE) - if(!can_open(user, force)) - return - if(opened) - return - welded = FALSE - locked = FALSE - playsound(loc, open_sound, open_sound_volume, TRUE, -3) - opened = TRUE - if(!dense_when_open) - density = FALSE - climb_time *= 0.5 //it's faster to climb onto an open thing - dump_contents() - update_icon() - return TRUE - -/obj/structure/closet/proc/insert(atom/movable/AM) - if(contents.len >= storage_capacity) - return -1 - if(insertion_allowed(AM)) - AM.forceMove(src) - return TRUE - else - return FALSE - -/obj/structure/closet/proc/insertion_allowed(atom/movable/AM) - if(ismob(AM)) - if(!isliving(AM)) //let's not put ghosts or camera mobs inside closets... - return FALSE - var/mob/living/L = AM - if(L.anchored || L.buckled || L.incorporeal_move || L.has_buckled_mobs()) - return FALSE - if(L.mob_size > MOB_SIZE_TINY) // Tiny mobs are treated as items. - if(horizontal && L.density) - return FALSE - if(L.mob_size > max_mob_size) - return FALSE - var/mobs_stored = 0 - for(var/mob/living/M in contents) - if(++mobs_stored >= mob_storage_capacity) - return FALSE - L.stop_pulling() - - else if(istype(AM, /obj/structure/closet)) - return FALSE - else if(isobj(AM)) - if((!allow_dense && AM.density) || AM.anchored || AM.has_buckled_mobs()) - return FALSE - else if(isitem(AM) && !HAS_TRAIT(AM, TRAIT_NODROP)) - return TRUE - else if(!allow_objects && !istype(AM, /obj/effect/dummy/chameleon)) - return FALSE - else - return FALSE - - return TRUE - -/obj/structure/closet/proc/close(mob/living/user) - if(!opened || !can_close(user)) - return FALSE - take_contents() - playsound(loc, close_sound, close_sound_volume, TRUE, -3) - climb_time = initial(climb_time) - opened = FALSE - density = TRUE - update_icon() - return TRUE - -/obj/structure/closet/proc/toggle(mob/living/user) - if(opened) - return close(user) - else - return open(user) - -/obj/structure/closet/deconstruct(disassembled = TRUE) - if(ispath(material_drop) && material_drop_amount && !(flags_1 & NODECONSTRUCT_1)) - new material_drop(loc, material_drop_amount) - qdel(src) - -/obj/structure/closet/obj_break(damage_flag) - if(!broken && !(flags_1 & NODECONSTRUCT_1)) - bust_open() - -/obj/structure/closet/attackby(obj/item/W, mob/user, params) - if(user in src) - return - if(src.tool_interact(W,user)) - return 1 // No afterattack - else - return ..() - -/obj/structure/closet/proc/tool_interact(obj/item/W, mob/user)//returns TRUE if attackBy call shouldnt be continued (because tool was used/closet was of wrong type), FALSE if otherwise - . = TRUE - if(opened) - if(istype(W, cutting_tool)) - if(W.tool_behaviour == TOOL_WELDER) - if(!W.tool_start_check(user, amount=0)) - return - - to_chat(user, "You begin cutting \the [src] apart...") - if(W.use_tool(src, user, 40, volume=50)) - if(!opened) - return - user.visible_message("[user] slices apart \the [src].", - "You cut \the [src] apart with \the [W].", - "You hear welding.") - deconstruct(TRUE) - return - else // for example cardboard box is cut with wirecutters - user.visible_message("[user] cut apart \the [src].", \ - "You cut \the [src] apart with \the [W].") - deconstruct(TRUE) - return - if(user.transferItemToLoc(W, drop_location())) // so we put in unlit welder too - return - else if(W.tool_behaviour == TOOL_WELDER && can_weld_shut) - if(!W.tool_start_check(user, amount=0)) - return - - to_chat(user, "You begin [welded ? "unwelding":"welding"] \the [src]...") - if(W.use_tool(src, user, 40, volume=50)) - if(opened) - return - welded = !welded - after_weld(welded) - user.visible_message("[user] [welded ? "welds shut" : "unwelded"] \the [src].", - "You [welded ? "weld" : "unwelded"] \the [src] with \the [W].", - "You hear welding.") - update_icon() - else if(W.tool_behaviour == TOOL_WRENCH && anchorable) - if(isinspace() && !anchored) - return - setAnchored(!anchored) - W.play_tool_sound(src, 75) - user.visible_message("[user] [anchored ? "anchored" : "unanchored"] \the [src] [anchored ? "to" : "from"] the ground.", \ - "You [anchored ? "anchored" : "unanchored"] \the [src] [anchored ? "to" : "from"] the ground.", \ - "You hear a ratchet.") - else if(user.a_intent != INTENT_HARM) - var/item_is_id = W.GetID() - if(!item_is_id) - return FALSE - if(item_is_id || !toggle(user)) - togglelock(user) - else - return FALSE - -/obj/structure/closet/proc/after_weld(weld_state) - return - -/obj/structure/closet/MouseDrop_T(atom/movable/O, mob/living/user) - if(!istype(O) || O.anchored || istype(O, /obj/screen)) - return - if(!istype(user) || user.incapacitated() || !(user.mobility_flags & MOBILITY_STAND)) - return - if(!Adjacent(user) || !user.Adjacent(O)) - return - if(user == O) //try to climb onto it - return ..() - if(!opened) - return - if(!isturf(O.loc)) - return - - var/actuallyismob = 0 - if(isliving(O)) - actuallyismob = 1 - else if(!isitem(O)) - return - var/turf/T = get_turf(src) - var/list/targets = list(O, src) - add_fingerprint(user) - user.visible_message("[user] [actuallyismob ? "tries to ":""]stuff [O] into [src].", \ - "You [actuallyismob ? "try to ":""]stuff [O] into [src].", \ - "You hear clanging.") - if(actuallyismob) - if(do_after_mob(user, targets, 40)) - user.visible_message("[user] stuffs [O] into [src].", \ - "You stuff [O] into [src].", \ - "You hear a loud metal bang.") - var/mob/living/L = O - if(!issilicon(L)) - L.Paralyze(40) - O.forceMove(T) - close() - else - O.forceMove(T) - return 1 - -/obj/structure/closet/relaymove(mob/user) - if(user.stat || !isturf(loc) || !isliving(user)) - return - if(locked) - if(message_cooldown <= world.time) - message_cooldown = world.time + 50 - to_chat(user, "[src]'s door won't budge!") - return - container_resist(user) - -/obj/structure/closet/attack_hand(mob/living/user) - . = ..() - if(.) - return - if(!(user.mobility_flags & MOBILITY_STAND) && get_dist(src, user) > 0) - return - - if(!toggle(user)) - togglelock(user) - -/obj/structure/closet/attack_paw(mob/user) - return attack_hand(user) - -/obj/structure/closet/attack_robot(mob/user) - if(user.Adjacent(src)) - return attack_hand(user) - -// tk grab then use on self -/obj/structure/closet/attack_self_tk(mob/user) - return attack_hand(user) - -/obj/structure/closet/verb/verb_toggleopen() - set src in view(1) - set category = "Object" - set name = "Toggle Open" - - if(!usr.canUseTopic(src, BE_CLOSE) || !isturf(loc)) - return - - if(iscarbon(usr) || issilicon(usr) || isdrone(usr)) - return toggle(usr) - else - to_chat(usr, "This mob type can't use this verb.") - -// Objects that try to exit a locker by stepping were doing so successfully, -// and due to an oversight in turf/Enter() were going through walls. That -// should be independently resolved, but this is also an interesting twist. -/obj/structure/closet/Exit(atom/movable/AM) - open() - if(AM.loc == src) - return 0 - return 1 - -/obj/structure/closet/container_resist(mob/living/user) - if(opened) - return - if(ismovable(loc)) - user.changeNext_move(CLICK_CD_BREAKOUT) - user.last_special = world.time + CLICK_CD_BREAKOUT - var/atom/movable/AM = loc - AM.relay_container_resist(user, src) - return - if(!welded && !locked) - open() - return - - //okay, so the closet is either welded or locked... resist!!! - user.changeNext_move(CLICK_CD_BREAKOUT) - user.last_special = world.time + CLICK_CD_BREAKOUT - user.visible_message("[src] begins to shake violently!", \ - "You lean on the back of [src] and start pushing the door open... (this will take about [DisplayTimeText(breakout_time)].)", \ - "You hear banging from [src].") - if(do_after(user,(breakout_time), target = src)) - if(!user || user.stat != CONSCIOUS || user.loc != src || opened || (!locked && !welded) ) - return - //we check after a while whether there is a point of resisting anymore and whether the user is capable of resisting - user.visible_message("[user] successfully broke out of [src]!", - "You successfully break out of [src]!") - bust_open() - else - if(user.loc == src) //so we don't get the message if we resisted multiple times and succeeded. - to_chat(user, "You fail to break out of [src]!") - -/obj/structure/closet/proc/bust_open() - welded = FALSE //applies to all lockers - locked = FALSE //applies to critter crates and secure lockers only - broken = TRUE //applies to secure lockers only - open() - -/obj/structure/closet/AltClick(mob/user) - ..() - if(!user.canUseTopic(src, BE_CLOSE) || !isturf(loc)) - return - if(opened || !secure) - return - else - togglelock(user) - -/obj/structure/closet/CtrlShiftClick(mob/living/user) - if(!HAS_TRAIT(user, TRAIT_SKITTISH)) - return ..() - if(!user.canUseTopic(src, BE_CLOSE) || !isturf(user.loc)) - return - dive_into(user) - -/obj/structure/closet/proc/togglelock(mob/living/user, silent) - if(secure && !broken) - if(allowed(user)) - if(iscarbon(user)) - add_fingerprint(user) - locked = !locked - user.visible_message("[user] [locked ? null : "un"]locks [src].", - "You [locked ? null : "un"]lock [src].") - update_icon() - else if(!silent) - to_chat(user, "Access Denied.") - else if(secure && broken) - to_chat(user, "\The [src] is broken!") - -/obj/structure/closet/emag_act(mob/user) - if(secure && !broken) - if(user) - user.visible_message("Sparks fly from [src]!", - "You scramble [src]'s lock, breaking it open!", - "You hear a faint electrical spark.") - playsound(src, "sparks", 50, TRUE) - broken = TRUE - locked = FALSE - update_icon() - -/obj/structure/closet/get_remote_view_fullscreens(mob/user) - if(user.stat == DEAD || !(user.sight & (SEEOBJS|SEEMOBS))) - user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 1) - -/obj/structure/closet/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - if (!(. & EMP_PROTECT_CONTENTS)) - for(var/obj/O in src) - O.emp_act(severity) - if(secure && !broken && !(. & EMP_PROTECT_SELF)) - if(prob(50 / severity)) - locked = !locked - update_icon() - if(prob(20 / severity) && !opened) - if(!locked) - open() - else - req_access = list() - req_access += pick(get_all_accesses()) - -/obj/structure/closet/contents_explosion(severity, target) - for(var/atom/A in contents) - switch(severity) - if(EXPLODE_DEVASTATE) - SSexplosions.highobj += A - if(EXPLODE_HEAVY) - SSexplosions.medobj += A - if(EXPLODE_LIGHT) - SSexplosions.lowobj += A - -/obj/structure/closet/singularity_act() - dump_contents() - ..() - -/obj/structure/closet/AllowDrop() - return TRUE - - -/obj/structure/closet/return_temperature() - return - -/obj/structure/closet/proc/dive_into(mob/living/user) - var/turf/T1 = get_turf(user) - var/turf/T2 = get_turf(src) - if(!opened) - if(locked) - togglelock(user, TRUE) - if(!open(user)) - to_chat(user, "It won't budge!") - return - step_towards(user, T2) - T1 = get_turf(user) - if(T1 == T2) - user.resting = TRUE //so people can jump into crates without slamming the lid on their head - if(!close(user)) - to_chat(user, "You can't get [src] to close!") - user.resting = FALSE - return - user.resting = FALSE - togglelock(user) - T1.visible_message("[user] dives into [src]!") +/obj/structure/closet + name = "closet" + desc = "It's a basic storage unit." + icon = 'goon/icons/obj/closet.dmi' + icon_state = "generic" + density = TRUE + drag_slowdown = 1.5 // Same as a prone mob + max_integrity = 200 + integrity_failure = 0.25 + armor = list("melee" = 20, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 60) + + var/icon_door = null + var/icon_door_override = FALSE //override to have open overlay use icon different to its base's + var/secure = FALSE //secure locker or not, also used if overriding a non-secure locker with a secure door overlay to add fancy lights + var/opened = FALSE + var/welded = FALSE + var/locked = FALSE + var/large = TRUE + var/wall_mounted = 0 //never solid (You can always pass over it) + var/breakout_time = 1200 + var/message_cooldown + var/can_weld_shut = TRUE + var/horizontal = FALSE + var/allow_objects = FALSE + var/allow_dense = FALSE + var/dense_when_open = FALSE //if it's dense when open or not + var/max_mob_size = MOB_SIZE_HUMAN //Biggest mob_size accepted by the container + var/mob_storage_capacity = 3 // how many human sized mob/living can fit together inside a closet. + var/storage_capacity = 30 //This is so that someone can't pack hundreds of items in a locker/crate then open it in a populated area to crash clients. + var/cutting_tool = /obj/item/weldingtool + var/open_sound = 'sound/machines/closet_open.ogg' + var/close_sound = 'sound/machines/closet_close.ogg' + var/open_sound_volume = 35 + var/close_sound_volume = 50 + var/material_drop = /obj/item/stack/sheet/metal + var/material_drop_amount = 2 + var/delivery_icon = "deliverycloset" //which icon to use when packagewrapped. null to be unwrappable. + var/anchorable = TRUE + var/icon_welded = "welded" + + +/obj/structure/closet/Initialize(mapload) + if(mapload && !opened) // if closed, any item at the crate's loc is put in the contents + addtimer(CALLBACK(src, .proc/take_contents), 0) + . = ..() + update_icon() + PopulateContents() + + RegisterSignal(src, COMSIG_ATOM_CANREACH, .proc/canreach_react) + +/obj/structure/closet/proc/canreach_react(datum/source, list/next) + return COMPONENT_BLOCK_REACH //closed block, open have nothing inside. + +//USE THIS TO FILL IT, NOT INITIALIZE OR NEW +/obj/structure/closet/proc/PopulateContents() + return + +/obj/structure/closet/Destroy() + dump_contents() + return ..() + +/obj/structure/closet/update_icon() + . = ..() + if(!opened) + layer = OBJ_LAYER + else + layer = BELOW_OBJ_LAYER + +/obj/structure/closet/update_overlays() + . = ..() + closet_update_overlays(.) + +/obj/structure/closet/proc/closet_update_overlays(list/new_overlays) + . = new_overlays + SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays) + luminosity = 0 + if(!opened) + if(icon_door) + . += "[icon_door]_door" + else + . += "[icon_state]_door" + if(welded) + . += icon_welded + if(secure && !broken) + //Overlay is similar enough for both that we can use the same mask for both + luminosity = 1 + SSvis_overlays.add_vis_overlay(src, icon, "locked", EMISSIVE_LAYER, EMISSIVE_PLANE, dir, alpha) + if(locked) + . += "locked" + else + . += "unlocked" + else + if(icon_door_override) + . += "[icon_door]_open" + else + . += "[icon_state]_open" + +/obj/structure/closet/examine(mob/user) + . = ..() + if(welded) + . += "It's welded shut." + if(anchored) + . += "It is bolted to the ground." + if(opened) + . += "The parts are welded together." + else if(secure && !opened) + . += "Alt-click to [locked ? "unlock" : "lock"]." + if(isliving(user)) + var/mob/living/L = user + if(HAS_TRAIT(L, TRAIT_SKITTISH)) + . += "Ctrl-Shift-click [src] to jump inside." + +/obj/structure/closet/CanAllowThrough(atom/movable/mover, turf/target) + . = ..() + if(wall_mounted) + return TRUE + +/obj/structure/closet/proc/can_open(mob/living/user, force = FALSE) + if(force) + return TRUE + if(welded || locked) + return FALSE + var/turf/T = get_turf(src) + for(var/mob/living/L in T) + if(L.anchored || horizontal && L.mob_size > MOB_SIZE_TINY && L.density) + if(user) + to_chat(user, "There's something large on top of [src], preventing it from opening." ) + return FALSE + return TRUE + +/obj/structure/closet/proc/can_close(mob/living/user) + var/turf/T = get_turf(src) + for(var/obj/structure/closet/closet in T) + if(closet != src && !closet.wall_mounted) + return FALSE + for(var/mob/living/L in T) + if(L.anchored || horizontal && L.mob_size > MOB_SIZE_TINY && L.density) + if(user) + to_chat(user, "There's something too large in [src], preventing it from closing.") + return FALSE + return TRUE + +/obj/structure/closet/dump_contents() + var/atom/L = drop_location() + for(var/atom/movable/AM in src) + AM.forceMove(L) + if(throwing) // you keep some momentum when getting out of a thrown closet + step(AM, dir) + if(throwing) + throwing.finalize(FALSE) + +/obj/structure/closet/proc/take_contents() + var/atom/L = drop_location() + for(var/atom/movable/AM in L) + if(AM != src && insert(AM) == -1) // limit reached + break + +/obj/structure/closet/proc/open(mob/living/user, force = FALSE) + if(!can_open(user, force)) + return + if(opened) + return + welded = FALSE + locked = FALSE + playsound(loc, open_sound, open_sound_volume, TRUE, -3) + opened = TRUE + if(!dense_when_open) + density = FALSE + climb_time *= 0.5 //it's faster to climb onto an open thing + dump_contents() + update_icon() + return TRUE + +/obj/structure/closet/proc/insert(atom/movable/AM) + if(contents.len >= storage_capacity) + return -1 + if(insertion_allowed(AM)) + AM.forceMove(src) + return TRUE + else + return FALSE + +/obj/structure/closet/proc/insertion_allowed(atom/movable/AM) + if(ismob(AM)) + if(!isliving(AM)) //let's not put ghosts or camera mobs inside closets... + return FALSE + var/mob/living/L = AM + if(L.anchored || L.buckled || L.incorporeal_move || L.has_buckled_mobs()) + return FALSE + if(L.mob_size > MOB_SIZE_TINY) // Tiny mobs are treated as items. + if(horizontal && L.density) + return FALSE + if(L.mob_size > max_mob_size) + return FALSE + var/mobs_stored = 0 + for(var/mob/living/M in contents) + if(++mobs_stored >= mob_storage_capacity) + return FALSE + L.stop_pulling() + + else if(istype(AM, /obj/structure/closet)) + return FALSE + else if(isobj(AM)) + if((!allow_dense && AM.density) || AM.anchored || AM.has_buckled_mobs()) + return FALSE + else if(isitem(AM) && !HAS_TRAIT(AM, TRAIT_NODROP)) + return TRUE + else if(!allow_objects && !istype(AM, /obj/effect/dummy/chameleon)) + return FALSE + else + return FALSE + + return TRUE + +/obj/structure/closet/proc/close(mob/living/user) + if(!opened || !can_close(user)) + return FALSE + take_contents() + playsound(loc, close_sound, close_sound_volume, TRUE, -3) + climb_time = initial(climb_time) + opened = FALSE + density = TRUE + update_icon() + return TRUE + +/obj/structure/closet/proc/toggle(mob/living/user) + if(opened) + return close(user) + else + return open(user) + +/obj/structure/closet/deconstruct(disassembled = TRUE) + if(ispath(material_drop) && material_drop_amount && !(flags_1 & NODECONSTRUCT_1)) + new material_drop(loc, material_drop_amount) + qdel(src) + +/obj/structure/closet/obj_break(damage_flag) + if(!broken && !(flags_1 & NODECONSTRUCT_1)) + bust_open() + +/obj/structure/closet/attackby(obj/item/W, mob/user, params) + if(user in src) + return + if(src.tool_interact(W,user)) + return 1 // No afterattack + else + return ..() + +/obj/structure/closet/proc/tool_interact(obj/item/W, mob/user)//returns TRUE if attackBy call shouldnt be continued (because tool was used/closet was of wrong type), FALSE if otherwise + . = TRUE + if(opened) + if(istype(W, cutting_tool)) + if(W.tool_behaviour == TOOL_WELDER) + if(!W.tool_start_check(user, amount=0)) + return + + to_chat(user, "You begin cutting \the [src] apart...") + if(W.use_tool(src, user, 40, volume=50)) + if(!opened) + return + user.visible_message("[user] slices apart \the [src].", + "You cut \the [src] apart with \the [W].", + "You hear welding.") + deconstruct(TRUE) + return + else // for example cardboard box is cut with wirecutters + user.visible_message("[user] cut apart \the [src].", \ + "You cut \the [src] apart with \the [W].") + deconstruct(TRUE) + return + if(user.transferItemToLoc(W, drop_location())) // so we put in unlit welder too + return + else if(W.tool_behaviour == TOOL_WELDER && can_weld_shut) + if(!W.tool_start_check(user, amount=0)) + return + + to_chat(user, "You begin [welded ? "unwelding":"welding"] \the [src]...") + if(W.use_tool(src, user, 40, volume=50)) + if(opened) + return + welded = !welded + after_weld(welded) + user.visible_message("[user] [welded ? "welds shut" : "unwelded"] \the [src].", + "You [welded ? "weld" : "unwelded"] \the [src] with \the [W].", + "You hear welding.") + update_icon() + else if(W.tool_behaviour == TOOL_WRENCH && anchorable) + if(isinspace() && !anchored) + return + setAnchored(!anchored) + W.play_tool_sound(src, 75) + user.visible_message("[user] [anchored ? "anchored" : "unanchored"] \the [src] [anchored ? "to" : "from"] the ground.", \ + "You [anchored ? "anchored" : "unanchored"] \the [src] [anchored ? "to" : "from"] the ground.", \ + "You hear a ratchet.") + else if(user.a_intent != INTENT_HARM) + var/item_is_id = W.GetID() + if(!item_is_id) + return FALSE + if(item_is_id || !toggle(user)) + togglelock(user) + else + return FALSE + +/obj/structure/closet/proc/after_weld(weld_state) + return + +/obj/structure/closet/MouseDrop_T(atom/movable/O, mob/living/user) + if(!istype(O) || O.anchored || istype(O, /obj/screen)) + return + if(!istype(user) || user.incapacitated() || !(user.mobility_flags & MOBILITY_STAND)) + return + if(!Adjacent(user) || !user.Adjacent(O)) + return + if(user == O) //try to climb onto it + return ..() + if(!opened) + return + if(!isturf(O.loc)) + return + + var/actuallyismob = 0 + if(isliving(O)) + actuallyismob = 1 + else if(!isitem(O)) + return + var/turf/T = get_turf(src) + var/list/targets = list(O, src) + add_fingerprint(user) + user.visible_message("[user] [actuallyismob ? "tries to ":""]stuff [O] into [src].", \ + "You [actuallyismob ? "try to ":""]stuff [O] into [src].", \ + "You hear clanging.") + if(actuallyismob) + if(do_after_mob(user, targets, 40)) + user.visible_message("[user] stuffs [O] into [src].", \ + "You stuff [O] into [src].", \ + "You hear a loud metal bang.") + var/mob/living/L = O + if(!issilicon(L)) + L.Paralyze(40) + O.forceMove(T) + close() + else + O.forceMove(T) + return 1 + +/obj/structure/closet/relaymove(mob/user) + if(user.stat || !isturf(loc) || !isliving(user)) + return + if(locked) + if(message_cooldown <= world.time) + message_cooldown = world.time + 50 + to_chat(user, "[src]'s door won't budge!") + return + container_resist(user) + +/obj/structure/closet/attack_hand(mob/living/user) + . = ..() + if(.) + return + if(!(user.mobility_flags & MOBILITY_STAND) && get_dist(src, user) > 0) + return + + if(!toggle(user)) + togglelock(user) + +/obj/structure/closet/attack_paw(mob/user) + return attack_hand(user) + +/obj/structure/closet/attack_robot(mob/user) + if(user.Adjacent(src)) + return attack_hand(user) + +// tk grab then use on self +/obj/structure/closet/attack_self_tk(mob/user) + return attack_hand(user) + +/obj/structure/closet/verb/verb_toggleopen() + set src in view(1) + set category = "Object" + set name = "Toggle Open" + + if(!usr.canUseTopic(src, BE_CLOSE) || !isturf(loc)) + return + + if(iscarbon(usr) || issilicon(usr) || isdrone(usr)) + return toggle(usr) + else + to_chat(usr, "This mob type can't use this verb.") + +// Objects that try to exit a locker by stepping were doing so successfully, +// and due to an oversight in turf/Enter() were going through walls. That +// should be independently resolved, but this is also an interesting twist. +/obj/structure/closet/Exit(atom/movable/AM) + open() + if(AM.loc == src) + return 0 + return 1 + +/obj/structure/closet/container_resist(mob/living/user) + if(opened) + return + if(ismovable(loc)) + user.changeNext_move(CLICK_CD_BREAKOUT) + user.last_special = world.time + CLICK_CD_BREAKOUT + var/atom/movable/AM = loc + AM.relay_container_resist(user, src) + return + if(!welded && !locked) + open() + return + + //okay, so the closet is either welded or locked... resist!!! + user.changeNext_move(CLICK_CD_BREAKOUT) + user.last_special = world.time + CLICK_CD_BREAKOUT + user.visible_message("[src] begins to shake violently!", \ + "You lean on the back of [src] and start pushing the door open... (this will take about [DisplayTimeText(breakout_time)].)", \ + "You hear banging from [src].") + if(do_after(user,(breakout_time), target = src)) + if(!user || user.stat != CONSCIOUS || user.loc != src || opened || (!locked && !welded) ) + return + //we check after a while whether there is a point of resisting anymore and whether the user is capable of resisting + user.visible_message("[user] successfully broke out of [src]!", + "You successfully break out of [src]!") + bust_open() + else + if(user.loc == src) //so we don't get the message if we resisted multiple times and succeeded. + to_chat(user, "You fail to break out of [src]!") + +/obj/structure/closet/proc/bust_open() + welded = FALSE //applies to all lockers + locked = FALSE //applies to critter crates and secure lockers only + broken = TRUE //applies to secure lockers only + open() + +/obj/structure/closet/AltClick(mob/user) + ..() + if(!user.canUseTopic(src, BE_CLOSE) || !isturf(loc)) + return + if(opened || !secure) + return + else + togglelock(user) + +/obj/structure/closet/CtrlShiftClick(mob/living/user) + if(!HAS_TRAIT(user, TRAIT_SKITTISH)) + return ..() + if(!user.canUseTopic(src, BE_CLOSE) || !isturf(user.loc)) + return + dive_into(user) + +/obj/structure/closet/proc/togglelock(mob/living/user, silent) + if(secure && !broken) + if(allowed(user)) + if(iscarbon(user)) + add_fingerprint(user) + locked = !locked + user.visible_message("[user] [locked ? null : "un"]locks [src].", + "You [locked ? null : "un"]lock [src].") + update_icon() + else if(!silent) + to_chat(user, "Access Denied.") + else if(secure && broken) + to_chat(user, "\The [src] is broken!") + +/obj/structure/closet/emag_act(mob/user) + if(secure && !broken) + if(user) + user.visible_message("Sparks fly from [src]!", + "You scramble [src]'s lock, breaking it open!", + "You hear a faint electrical spark.") + playsound(src, "sparks", 50, TRUE) + broken = TRUE + locked = FALSE + update_icon() + +/obj/structure/closet/get_remote_view_fullscreens(mob/user) + if(user.stat == DEAD || !(user.sight & (SEEOBJS|SEEMOBS))) + user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 1) + +/obj/structure/closet/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + if (!(. & EMP_PROTECT_CONTENTS)) + for(var/obj/O in src) + O.emp_act(severity) + if(secure && !broken && !(. & EMP_PROTECT_SELF)) + if(prob(50 / severity)) + locked = !locked + update_icon() + if(prob(20 / severity) && !opened) + if(!locked) + open() + else + req_access = list() + req_access += pick(get_all_accesses()) + +/obj/structure/closet/contents_explosion(severity, target) + for(var/atom/A in contents) + switch(severity) + if(EXPLODE_DEVASTATE) + SSexplosions.highobj += A + if(EXPLODE_HEAVY) + SSexplosions.medobj += A + if(EXPLODE_LIGHT) + SSexplosions.lowobj += A + +/obj/structure/closet/singularity_act() + dump_contents() + ..() + +/obj/structure/closet/AllowDrop() + return TRUE + + +/obj/structure/closet/return_temperature() + return + +/obj/structure/closet/proc/dive_into(mob/living/user) + var/turf/T1 = get_turf(user) + var/turf/T2 = get_turf(src) + if(!opened) + if(locked) + togglelock(user, TRUE) + if(!open(user)) + to_chat(user, "It won't budge!") + return + step_towards(user, T2) + T1 = get_turf(user) + if(T1 == T2) + user.resting = TRUE //so people can jump into crates without slamming the lid on their head + if(!close(user)) + to_chat(user, "You can't get [src] to close!") + user.resting = FALSE + return + user.resting = FALSE + togglelock(user) + T1.visible_message("[user] dives into [src]!") diff --git a/code/game/objects/structures/crates_lockers/closets/fitness.dm b/code/game/objects/structures/crates_lockers/closets/fitness.dm index 7f22a9a49982..b0dd6db262a5 100644 --- a/code/game/objects/structures/crates_lockers/closets/fitness.dm +++ b/code/game/objects/structures/crates_lockers/closets/fitness.dm @@ -1,65 +1,65 @@ -/obj/structure/closet/athletic_mixed - name = "athletic wardrobe" - desc = "It's a storage unit for athletic wear." - icon_door = "mixed" - -/obj/structure/closet/athletic_mixed/PopulateContents() - ..() - new /obj/item/clothing/under/shorts/purple(src) - new /obj/item/clothing/under/shorts/grey(src) - new /obj/item/clothing/under/shorts/black(src) - new /obj/item/clothing/under/shorts/red(src) - new /obj/item/clothing/under/shorts/blue(src) - new /obj/item/clothing/under/shorts/green(src) - new /obj/item/clothing/under/costume/jabroni(src) - - -/obj/structure/closet/boxinggloves - name = "boxing gloves" - desc = "It's a storage unit for gloves for use in the boxing ring." - -/obj/structure/closet/boxinggloves/PopulateContents() - ..() - new /obj/item/clothing/gloves/boxing/blue(src) - new /obj/item/clothing/gloves/boxing/green(src) - new /obj/item/clothing/gloves/boxing/yellow(src) - new /obj/item/clothing/gloves/boxing(src) - - -/obj/structure/closet/masks - name = "mask closet" - desc = "IT'S A STORAGE UNIT FOR FIGHTER MASKS OLE!" - -/obj/structure/closet/masks/PopulateContents() - ..() - new /obj/item/clothing/mask/luchador(src) - new /obj/item/clothing/mask/luchador/rudos(src) - new /obj/item/clothing/mask/luchador/tecnicos(src) - - -/obj/structure/closet/lasertag/red - name = "red laser tag equipment" - desc = "It's a storage unit for laser tag equipment." - icon_door = "red" - -/obj/structure/closet/lasertag/red/PopulateContents() - ..() - for(var/i in 1 to 3) - new /obj/item/gun/energy/laser/redtag(src) - for(var/i in 1 to 3) - new /obj/item/clothing/suit/redtag(src) - new /obj/item/clothing/head/helmet/redtaghelm(src) - - -/obj/structure/closet/lasertag/blue - name = "blue laser tag equipment" - desc = "It's a storage unit for laser tag equipment." - icon_door = "blue" - -/obj/structure/closet/lasertag/blue/PopulateContents() - ..() - for(var/i in 1 to 3) - new /obj/item/gun/energy/laser/bluetag(src) - for(var/i in 1 to 3) - new /obj/item/clothing/suit/bluetag(src) - new /obj/item/clothing/head/helmet/bluetaghelm(src) +/obj/structure/closet/athletic_mixed + name = "athletic wardrobe" + desc = "It's a storage unit for athletic wear." + icon_door = "mixed" + +/obj/structure/closet/athletic_mixed/PopulateContents() + ..() + new /obj/item/clothing/under/shorts/purple(src) + new /obj/item/clothing/under/shorts/grey(src) + new /obj/item/clothing/under/shorts/black(src) + new /obj/item/clothing/under/shorts/red(src) + new /obj/item/clothing/under/shorts/blue(src) + new /obj/item/clothing/under/shorts/green(src) + new /obj/item/clothing/under/costume/jabroni(src) + + +/obj/structure/closet/boxinggloves + name = "boxing gloves" + desc = "It's a storage unit for gloves for use in the boxing ring." + +/obj/structure/closet/boxinggloves/PopulateContents() + ..() + new /obj/item/clothing/gloves/boxing/blue(src) + new /obj/item/clothing/gloves/boxing/green(src) + new /obj/item/clothing/gloves/boxing/yellow(src) + new /obj/item/clothing/gloves/boxing(src) + + +/obj/structure/closet/masks + name = "mask closet" + desc = "IT'S A STORAGE UNIT FOR FIGHTER MASKS OLE!" + +/obj/structure/closet/masks/PopulateContents() + ..() + new /obj/item/clothing/mask/luchador(src) + new /obj/item/clothing/mask/luchador/rudos(src) + new /obj/item/clothing/mask/luchador/tecnicos(src) + + +/obj/structure/closet/lasertag/red + name = "red laser tag equipment" + desc = "It's a storage unit for laser tag equipment." + icon_door = "red" + +/obj/structure/closet/lasertag/red/PopulateContents() + ..() + for(var/i in 1 to 3) + new /obj/item/gun/energy/laser/redtag(src) + for(var/i in 1 to 3) + new /obj/item/clothing/suit/redtag(src) + new /obj/item/clothing/head/helmet/redtaghelm(src) + + +/obj/structure/closet/lasertag/blue + name = "blue laser tag equipment" + desc = "It's a storage unit for laser tag equipment." + icon_door = "blue" + +/obj/structure/closet/lasertag/blue/PopulateContents() + ..() + for(var/i in 1 to 3) + new /obj/item/gun/energy/laser/bluetag(src) + for(var/i in 1 to 3) + new /obj/item/clothing/suit/bluetag(src) + new /obj/item/clothing/head/helmet/bluetaghelm(src) diff --git a/code/game/objects/structures/crates_lockers/closets/gimmick.dm b/code/game/objects/structures/crates_lockers/closets/gimmick.dm index 2320c39acd21..6c389bc160c4 100644 --- a/code/game/objects/structures/crates_lockers/closets/gimmick.dm +++ b/code/game/objects/structures/crates_lockers/closets/gimmick.dm @@ -1,111 +1,111 @@ -/obj/structure/closet/cabinet - name = "cabinet" - desc = "Old will forever be in fashion." - icon_state = "cabinet" - resistance_flags = FLAMMABLE - open_sound = 'sound/machines/wooden_closet_open.ogg' - close_sound = 'sound/machines/wooden_closet_close.ogg' - open_sound_volume = 25 - close_sound_volume = 50 - max_integrity = 70 - -/obj/structure/closet/acloset - name = "strange closet" - desc = "It looks alien!" - icon_state = "alien" - - -/obj/structure/closet/gimmick - name = "administrative supply closet" - desc = "It's a storage unit for things that have no right being here." - icon_state = "syndicate" - -/obj/structure/closet/gimmick/russian - name = "\improper Russian surplus closet" - desc = "It's a storage unit for Russian standard-issue surplus." - -/obj/structure/closet/gimmick/russian/PopulateContents() - ..() - for(var/i in 1 to 5) - new /obj/item/clothing/head/ushanka(src) - for(var/i in 1 to 5) - new /obj/item/clothing/under/costume/soviet(src) - -/obj/structure/closet/gimmick/tacticool - name = "tacticool gear closet" - desc = "It's a storage unit for Tacticool gear." - -/obj/structure/closet/gimmick/tacticool/PopulateContents() - ..() - new /obj/item/clothing/glasses/eyepatch(src) - new /obj/item/clothing/glasses/sunglasses(src) - new /obj/item/clothing/gloves/tackler/combat(src) - new /obj/item/clothing/gloves/tackler/combat(src) - new /obj/item/clothing/head/helmet/swat(src) - new /obj/item/clothing/head/helmet/swat(src) - new /obj/item/clothing/mask/gas/sechailer/swat(src) - new /obj/item/clothing/mask/gas/sechailer/swat(src) - new /obj/item/clothing/shoes/combat/swat(src) - new /obj/item/clothing/shoes/combat/swat(src) - new /obj/item/clothing/suit/space/hardsuit/deathsquad(src) - new /obj/item/clothing/suit/space/hardsuit/deathsquad(src) - new /obj/item/clothing/under/syndicate/tacticool(src) - new /obj/item/clothing/under/syndicate/tacticool(src) - - -/obj/structure/closet/thunderdome - name = "\improper Thunderdome closet" - desc = "Everything you need!" - anchored = TRUE - -/obj/structure/closet/thunderdome/tdred - name = "red-team Thunderdome closet" - icon_door = "red" - -/obj/structure/closet/thunderdome/tdred/PopulateContents() - ..() - for(var/i in 1 to 3) - new /obj/item/clothing/suit/armor/tdome/red(src) - for(var/i in 1 to 3) - new /obj/item/melee/transforming/energy/sword/saber(src) - for(var/i in 1 to 3) - new /obj/item/gun/energy/laser(src) - for(var/i in 1 to 3) - new /obj/item/melee/baton/loaded(src) - for(var/i in 1 to 3) - new /obj/item/storage/box/flashbangs(src) - for(var/i in 1 to 3) - new /obj/item/clothing/head/helmet/thunderdome(src) - -/obj/structure/closet/thunderdome/tdgreen - name = "green-team Thunderdome closet" - icon_door = "green" - -/obj/structure/closet/thunderdome/tdgreen/PopulateContents() - ..() - for(var/i in 1 to 3) - new /obj/item/clothing/suit/armor/tdome/green(src) - for(var/i in 1 to 3) - new /obj/item/melee/transforming/energy/sword/saber(src) - for(var/i in 1 to 3) - new /obj/item/gun/energy/laser(src) - for(var/i in 1 to 3) - new /obj/item/melee/baton/loaded(src) - for(var/i in 1 to 3) - new /obj/item/storage/box/flashbangs(src) - for(var/i in 1 to 3) - new /obj/item/clothing/head/helmet/thunderdome(src) - -/obj/structure/closet/malf/suits - desc = "It's a storage unit for operational gear." - icon_state = "syndicate" - -/obj/structure/closet/malf/suits/PopulateContents() - ..() - new /obj/item/tank/jetpack/void(src) - new /obj/item/clothing/mask/breath(src) - new /obj/item/clothing/head/helmet/space/nasavoid(src) - new /obj/item/clothing/suit/space/nasavoid(src) - new /obj/item/crowbar(src) - new /obj/item/stock_parts/cell(src) - new /obj/item/multitool(src) +/obj/structure/closet/cabinet + name = "cabinet" + desc = "Old will forever be in fashion." + icon_state = "cabinet" + resistance_flags = FLAMMABLE + open_sound = 'sound/machines/wooden_closet_open.ogg' + close_sound = 'sound/machines/wooden_closet_close.ogg' + open_sound_volume = 25 + close_sound_volume = 50 + max_integrity = 70 + +/obj/structure/closet/acloset + name = "strange closet" + desc = "It looks alien!" + icon_state = "alien" + + +/obj/structure/closet/gimmick + name = "administrative supply closet" + desc = "It's a storage unit for things that have no right being here." + icon_state = "syndicate" + +/obj/structure/closet/gimmick/russian + name = "\improper Russian surplus closet" + desc = "It's a storage unit for Russian standard-issue surplus." + +/obj/structure/closet/gimmick/russian/PopulateContents() + ..() + for(var/i in 1 to 5) + new /obj/item/clothing/head/ushanka(src) + for(var/i in 1 to 5) + new /obj/item/clothing/under/costume/soviet(src) + +/obj/structure/closet/gimmick/tacticool + name = "tacticool gear closet" + desc = "It's a storage unit for Tacticool gear." + +/obj/structure/closet/gimmick/tacticool/PopulateContents() + ..() + new /obj/item/clothing/glasses/eyepatch(src) + new /obj/item/clothing/glasses/sunglasses(src) + new /obj/item/clothing/gloves/tackler/combat(src) + new /obj/item/clothing/gloves/tackler/combat(src) + new /obj/item/clothing/head/helmet/swat(src) + new /obj/item/clothing/head/helmet/swat(src) + new /obj/item/clothing/mask/gas/sechailer/swat(src) + new /obj/item/clothing/mask/gas/sechailer/swat(src) + new /obj/item/clothing/shoes/combat/swat(src) + new /obj/item/clothing/shoes/combat/swat(src) + new /obj/item/clothing/suit/space/hardsuit/deathsquad(src) + new /obj/item/clothing/suit/space/hardsuit/deathsquad(src) + new /obj/item/clothing/under/syndicate/tacticool(src) + new /obj/item/clothing/under/syndicate/tacticool(src) + + +/obj/structure/closet/thunderdome + name = "\improper Thunderdome closet" + desc = "Everything you need!" + anchored = TRUE + +/obj/structure/closet/thunderdome/tdred + name = "red-team Thunderdome closet" + icon_door = "red" + +/obj/structure/closet/thunderdome/tdred/PopulateContents() + ..() + for(var/i in 1 to 3) + new /obj/item/clothing/suit/armor/tdome/red(src) + for(var/i in 1 to 3) + new /obj/item/melee/transforming/energy/sword/saber(src) + for(var/i in 1 to 3) + new /obj/item/gun/energy/laser(src) + for(var/i in 1 to 3) + new /obj/item/melee/baton/loaded(src) + for(var/i in 1 to 3) + new /obj/item/storage/box/flashbangs(src) + for(var/i in 1 to 3) + new /obj/item/clothing/head/helmet/thunderdome(src) + +/obj/structure/closet/thunderdome/tdgreen + name = "green-team Thunderdome closet" + icon_door = "green" + +/obj/structure/closet/thunderdome/tdgreen/PopulateContents() + ..() + for(var/i in 1 to 3) + new /obj/item/clothing/suit/armor/tdome/green(src) + for(var/i in 1 to 3) + new /obj/item/melee/transforming/energy/sword/saber(src) + for(var/i in 1 to 3) + new /obj/item/gun/energy/laser(src) + for(var/i in 1 to 3) + new /obj/item/melee/baton/loaded(src) + for(var/i in 1 to 3) + new /obj/item/storage/box/flashbangs(src) + for(var/i in 1 to 3) + new /obj/item/clothing/head/helmet/thunderdome(src) + +/obj/structure/closet/malf/suits + desc = "It's a storage unit for operational gear." + icon_state = "syndicate" + +/obj/structure/closet/malf/suits/PopulateContents() + ..() + new /obj/item/tank/jetpack/void(src) + new /obj/item/clothing/mask/breath(src) + new /obj/item/clothing/head/helmet/space/nasavoid(src) + new /obj/item/clothing/suit/space/nasavoid(src) + new /obj/item/crowbar(src) + new /obj/item/stock_parts/cell(src) + new /obj/item/multitool(src) diff --git a/code/game/objects/structures/crates_lockers/closets/job_closets.dm b/code/game/objects/structures/crates_lockers/closets/job_closets.dm index 57f9845f5f5d..9833eeeadb42 100644 --- a/code/game/objects/structures/crates_lockers/closets/job_closets.dm +++ b/code/game/objects/structures/crates_lockers/closets/job_closets.dm @@ -1,319 +1,319 @@ -// Closets for specific jobs - -/obj/structure/closet/gmcloset - name = "formal closet" - desc = "It's a storage unit for formal clothing." - icon_door = "black" - -/obj/structure/closet/gmcloset/PopulateContents() - ..() - var/static/items_inside = list( - /obj/item/clothing/head/that = 2, - /obj/item/radio/headset/headset_srv = 2, - /obj/item/clothing/under/suit/sl = 2, - /obj/item/clothing/under/rank/civilian/bartender = 2, - /obj/item/clothing/accessory/waistcoat = 2, - /obj/item/clothing/head/soft/black = 2, - /obj/item/clothing/shoes/sneakers/black = 2, - /obj/item/reagent_containers/glass/rag = 2, - /obj/item/storage/box/beanbag = 1, - /obj/item/clothing/suit/armor/vest/alt = 1, - /obj/item/circuitboard/machine/dish_drive = 1, - /obj/item/clothing/glasses/sunglasses/reagent = 1, - /obj/item/clothing/neck/petcollar = 1, - /obj/item/storage/belt/bandolier = 1) - generate_items_inside(items_inside,src) - -/obj/structure/closet/chefcloset - name = "\proper chef's closet" - desc = "It's a storage unit for foodservice garments and mouse traps." - icon_door = "black" - -/obj/structure/closet/chefcloset/PopulateContents() - ..() - var/static/items_inside = list( - /obj/item/clothing/under/suit/waiter = 2, - /obj/item/radio/headset/headset_srv = 2, - /obj/item/clothing/accessory/waistcoat = 2, - /obj/item/clothing/suit/apron/chef = 3, - /obj/item/clothing/head/soft/mime = 2, - /obj/item/storage/box/mousetraps = 2, - /obj/item/circuitboard/machine/dish_drive = 1, - /obj/item/clothing/suit/toggle/chef = 1, - /obj/item/clothing/under/rank/civilian/chef = 1, - /obj/item/clothing/head/chefhat = 1, - /obj/item/reagent_containers/glass/rag = 1) - generate_items_inside(items_inside,src) - -/obj/structure/closet/jcloset - name = "custodial closet" - desc = "It's a storage unit for janitorial clothes and gear." - icon_door = "mixed" - -/obj/structure/closet/jcloset/PopulateContents() - ..() - new /obj/item/clothing/under/rank/civilian/janitor(src) - new /obj/item/cartridge/janitor(src) - new /obj/item/clothing/gloves/color/black(src) - new /obj/item/clothing/head/soft/purple(src) - new /obj/item/paint/paint_remover(src) - new /obj/item/melee/flyswatter(src) - new /obj/item/flashlight(src) - for(var/i in 1 to 3) - new /obj/item/clothing/suit/caution(src) - new /obj/item/holosign_creator(src) - new /obj/item/lightreplacer(src) - new /obj/item/soap(src) - new /obj/item/storage/bag/trash(src) - new /obj/item/clothing/shoes/galoshes(src) - new /obj/item/watertank/janitor(src) - new /obj/item/storage/belt/janitor(src) - - -/obj/structure/closet/lawcloset - name = "legal closet" - desc = "It's a storage unit for courtroom apparel and items." - icon_door = "blue" - -/obj/structure/closet/lawcloset/PopulateContents() - ..() - new /obj/item/clothing/under/suit/blacktwopiece(src) - new /obj/item/clothing/under/rank/civilian/lawyer/female(src) - new /obj/item/clothing/under/rank/civilian/lawyer/black(src) - new /obj/item/clothing/under/rank/civilian/lawyer/red(src) - new /obj/item/clothing/under/rank/civilian/lawyer/bluesuit(src) - new /obj/item/clothing/suit/toggle/lawyer(src) - new /obj/item/clothing/under/rank/civilian/lawyer/purpsuit(src) - new /obj/item/clothing/suit/toggle/lawyer/purple(src) - new /obj/item/clothing/under/suit/black(src) - new /obj/item/clothing/suit/toggle/lawyer/black(src) - new /obj/item/clothing/shoes/laceup(src) - new /obj/item/clothing/shoes/laceup(src) - new /obj/item/clothing/accessory/lawyers_badge(src) - new /obj/item/clothing/accessory/lawyers_badge(src) - -/obj/structure/closet/wardrobe/chaplain_black - name = "chapel wardrobe" - desc = "It's a storage unit for Nanotrasen-approved religious attire." - icon_door = "black" - -/obj/structure/closet/wardrobe/chaplain_black/PopulateContents() - new /obj/item/choice_beacon/holy(src) - new /obj/item/clothing/accessory/pocketprotector/cosmetology(src) - new /obj/item/clothing/under/rank/civilian/chaplain(src) - new /obj/item/clothing/shoes/sneakers/black(src) - new /obj/item/clothing/suit/chaplainsuit/nun(src) - new /obj/item/clothing/head/nun_hood(src) - new /obj/item/clothing/suit/hooded/chaplainsuit/monkhabit(src) - new /obj/item/clothing/suit/chaplainsuit/holidaypriest(src) - new /obj/item/storage/backpack/cultpack(src) - new /obj/item/storage/fancy/candle_box(src) - new /obj/item/storage/fancy/candle_box(src) - return - -/obj/structure/closet/wardrobe/red - name = "security wardrobe" - icon_door = "red" - -/obj/structure/closet/wardrobe/red/PopulateContents() - var/static/items_inside = list( - /obj/item/clothing/suit/hooded/wintercoat/security = 1, - /obj/item/storage/backpack/security = 1, - /obj/item/storage/backpack/satchel/sec = 1, - /obj/item/storage/backpack/duffelbag/sec = 2, - /obj/item/clothing/under/rank/security/officer = 3, - /obj/item/clothing/under/rank/security/officer/skirt = 2, - /obj/item/clothing/shoes/jackboots = 3, - /obj/item/clothing/head/beret/sec = 3, // Waspstation edit - Berets - /obj/item/clothing/head/soft/sec = 3, - /obj/item/clothing/mask/bandana/red = 2) - generate_items_inside(items_inside,src) - return - -/obj/structure/closet/wardrobe/cargotech - name = "cargo wardrobe" - icon_door = "orange" - -/obj/structure/closet/wardrobe/cargotech/PopulateContents() - var/static/items_inside = list( - /obj/item/clothing/suit/hooded/wintercoat/cargo = 1, - /obj/item/clothing/under/rank/cargo/tech = 3, - /obj/item/clothing/shoes/sneakers/black = 3, - /obj/item/clothing/gloves/fingerless = 3, - /obj/item/clothing/head/soft = 3, - /obj/item/radio/headset/headset_cargo = 1) - generate_items_inside(items_inside,src) - -/obj/structure/closet/wardrobe/atmospherics_yellow - name = "atmospherics wardrobe" - icon_door = "atmos_wardrobe" - -/obj/structure/closet/wardrobe/atmospherics_yellow/PopulateContents() - var/static/items_inside = list( - /obj/item/clothing/head/beret/atmos = 3, // Waspstation edit - Berets - /obj/item/clothing/accessory/pocketprotector = 1, - /obj/item/storage/backpack/duffelbag/engineering = 1, - /obj/item/storage/backpack/satchel/eng = 1, - /obj/item/storage/backpack/industrial = 1, - /obj/item/clothing/suit/hooded/wintercoat/engineering/atmos = 3, - /obj/item/clothing/under/rank/engineering/atmospheric_technician = 3, - /obj/item/clothing/shoes/sneakers/black = 3) - generate_items_inside(items_inside,src) - return - -/obj/structure/closet/wardrobe/engineering_yellow - name = "engineering wardrobe" - icon_door = "yellow" - -/obj/structure/closet/wardrobe/engineering_yellow/PopulateContents() - var/static/items_inside = list( - /obj/item/clothing/accessory/pocketprotector = 1, - /obj/item/storage/backpack/duffelbag/engineering = 1, - /obj/item/storage/backpack/industrial = 1, - /obj/item/storage/backpack/satchel/eng = 1, - /obj/item/clothing/suit/hooded/wintercoat/engineering = 1, - /obj/item/clothing/under/rank/engineering/engineer = 3, - /obj/item/clothing/suit/hazardvest = 3, - /obj/item/clothing/shoes/workboots = 3, - /obj/item/clothing/head/beret/eng = 3, // Waspstation edit - Berets - /obj/item/clothing/head/hardhat = 3) - generate_items_inside(items_inside,src) - return - -/obj/structure/closet/wardrobe/white/medical - name = "medical doctor's wardrobe" - -/obj/structure/closet/wardrobe/white/medical/PopulateContents() - var/static/items_inside = list( - /obj/item/clothing/accessory/pocketprotector = 1, - /obj/item/storage/backpack/duffelbag/med = 1, - /obj/item/storage/backpack/medic = 1, - /obj/item/storage/backpack/satchel/med = 1, - /obj/item/clothing/suit/hooded/wintercoat/medical = 1, - /obj/item/clothing/under/rank/medical/doctor/nurse = 1, - /obj/item/clothing/head/nursehat = 1, - /obj/item/clothing/under/rank/medical/doctor/blue = 1, - /obj/item/clothing/under/rank/medical/doctor/green = 1, - /obj/item/clothing/under/rank/medical/doctor/purple = 1, - /obj/item/clothing/under/rank/medical/doctor = 3, - /obj/item/clothing/suit/toggle/labcoat = 3, - /obj/item/clothing/suit/toggle/labcoat/paramedic = 3, - /obj/item/clothing/shoes/sneakers/white = 3, - /obj/item/clothing/head/beret/med = 3, // Waspstation edit - Berets - /obj/item/clothing/head/soft/paramedic = 3) - generate_items_inside(items_inside,src) - return - -/obj/structure/closet/wardrobe/robotics_black - name = "robotics wardrobe" - icon_door = "black" - -/obj/structure/closet/wardrobe/robotics_black/PopulateContents() - var/static/items_inside = list( - /obj/item/clothing/glasses/hud/diagnostic = 2, - /obj/item/clothing/under/rank/rnd/roboticist = 2, - /obj/item/clothing/suit/toggle/labcoat = 2, - /obj/item/clothing/shoes/sneakers/black = 2, - /obj/item/clothing/gloves/fingerless = 2, - /obj/item/clothing/head/beret/sci = 2, // Waspstation edit - Berets - /obj/item/clothing/head/soft/black = 2) - generate_items_inside(items_inside,src) - if(prob(40)) - new /obj/item/clothing/mask/bandana/skull(src) - if(prob(40)) - new /obj/item/clothing/mask/bandana/skull(src) - return - - -/obj/structure/closet/wardrobe/chemistry_white - name = "chemistry wardrobe" - icon_door = "white" - -/obj/structure/closet/wardrobe/chemistry_white/PopulateContents() - var/static/items_inside = list( - /obj/item/clothing/under/rank/medical/chemist = 2, - /obj/item/clothing/shoes/sneakers/white = 2, - /obj/item/clothing/suit/toggle/labcoat/chemist = 2, - /obj/item/storage/backpack/chemistry = 2, - /obj/item/storage/backpack/satchel/chem = 2, - /obj/item/storage/bag/chemistry = 2) - generate_items_inside(items_inside,src) - return - - -/obj/structure/closet/wardrobe/genetics_white - name = "genetics wardrobe" - icon_door = "white" - -/obj/structure/closet/wardrobe/genetics_white/PopulateContents() - var/static/items_inside = list( - /obj/item/clothing/under/rank/medical/geneticist = 2, - /obj/item/clothing/shoes/sneakers/white = 2, - /obj/item/clothing/suit/toggle/labcoat/genetics = 2, - /obj/item/storage/backpack/genetics = 2, - /obj/item/storage/backpack/satchel/gen = 2) - generate_items_inside(items_inside,src) - return - - -/obj/structure/closet/wardrobe/virology_white - name = "virology wardrobe" - icon_door = "white" - -/obj/structure/closet/wardrobe/virology_white/PopulateContents() - var/static/items_inside = list( - /obj/item/clothing/under/rank/medical/virologist = 2, - /obj/item/clothing/shoes/sneakers/white = 2, - /obj/item/clothing/suit/toggle/labcoat/virologist = 2, - /obj/item/clothing/mask/surgical = 2, - /obj/item/clothing/head/beret/med = 2, // Waspstation edit - Berets - /obj/item/storage/backpack/virology = 2, - /obj/item/storage/backpack/satchel/vir = 2) - generate_items_inside(items_inside,src) - return - -/obj/structure/closet/wardrobe/science_white - name = "science wardrobe" - icon_door = "white" - -/obj/structure/closet/wardrobe/science_white/PopulateContents() - var/static/items_inside = list( - /obj/item/clothing/accessory/pocketprotector = 1, - /obj/item/storage/backpack/science = 2, - /obj/item/storage/backpack/satchel/tox = 2, - /obj/item/clothing/suit/hooded/wintercoat/science = 1, - /obj/item/clothing/under/rank/rnd/scientist = 3, - /obj/item/clothing/suit/toggle/labcoat/science = 3, - /obj/item/clothing/shoes/sneakers/white = 3, - /obj/item/radio/headset/headset_sci = 2, - /obj/item/clothing/head/beret/sci = 3, // Waspstation edit - Berets - /obj/item/clothing/mask/gas = 3) - generate_items_inside(items_inside,src) - return - -/obj/structure/closet/wardrobe/botanist - name = "botanist wardrobe" - icon_door = "green" - -/obj/structure/closet/wardrobe/botanist/PopulateContents() - var/static/items_inside = list( - /obj/item/storage/backpack/botany = 2, - /obj/item/storage/backpack/satchel/hyd = 2, - /obj/item/clothing/suit/hooded/wintercoat/hydro = 1, - /obj/item/clothing/suit/apron = 2, - /obj/item/clothing/suit/apron/overalls = 2, - /obj/item/clothing/under/rank/civilian/hydroponics = 3, - /obj/item/clothing/mask/bandana = 3) - generate_items_inside(items_inside,src) - -/obj/structure/closet/wardrobe/curator - name = "treasure hunting wardrobe" - icon_door = "black" - -/obj/structure/closet/wardrobe/curator/PopulateContents() - new /obj/item/clothing/head/fedora/curator(src) - new /obj/item/clothing/suit/curator(src) - new /obj/item/clothing/under/rank/civilian/curator/treasure_hunter(src) - new /obj/item/clothing/shoes/workboots/mining(src) - new /obj/item/storage/backpack/satchel/explorer(src) - +// Closets for specific jobs + +/obj/structure/closet/gmcloset + name = "formal closet" + desc = "It's a storage unit for formal clothing." + icon_door = "black" + +/obj/structure/closet/gmcloset/PopulateContents() + ..() + var/static/items_inside = list( + /obj/item/clothing/head/that = 2, + /obj/item/radio/headset/headset_srv = 2, + /obj/item/clothing/under/suit/sl = 2, + /obj/item/clothing/under/rank/civilian/bartender = 2, + /obj/item/clothing/accessory/waistcoat = 2, + /obj/item/clothing/head/soft/black = 2, + /obj/item/clothing/shoes/sneakers/black = 2, + /obj/item/reagent_containers/glass/rag = 2, + /obj/item/storage/box/beanbag = 1, + /obj/item/clothing/suit/armor/vest/alt = 1, + /obj/item/circuitboard/machine/dish_drive = 1, + /obj/item/clothing/glasses/sunglasses/reagent = 1, + /obj/item/clothing/neck/petcollar = 1, + /obj/item/storage/belt/bandolier = 1) + generate_items_inside(items_inside,src) + +/obj/structure/closet/chefcloset + name = "\proper chef's closet" + desc = "It's a storage unit for foodservice garments and mouse traps." + icon_door = "black" + +/obj/structure/closet/chefcloset/PopulateContents() + ..() + var/static/items_inside = list( + /obj/item/clothing/under/suit/waiter = 2, + /obj/item/radio/headset/headset_srv = 2, + /obj/item/clothing/accessory/waistcoat = 2, + /obj/item/clothing/suit/apron/chef = 3, + /obj/item/clothing/head/soft/mime = 2, + /obj/item/storage/box/mousetraps = 2, + /obj/item/circuitboard/machine/dish_drive = 1, + /obj/item/clothing/suit/toggle/chef = 1, + /obj/item/clothing/under/rank/civilian/chef = 1, + /obj/item/clothing/head/chefhat = 1, + /obj/item/reagent_containers/glass/rag = 1) + generate_items_inside(items_inside,src) + +/obj/structure/closet/jcloset + name = "custodial closet" + desc = "It's a storage unit for janitorial clothes and gear." + icon_door = "mixed" + +/obj/structure/closet/jcloset/PopulateContents() + ..() + new /obj/item/clothing/under/rank/civilian/janitor(src) + new /obj/item/cartridge/janitor(src) + new /obj/item/clothing/gloves/color/black(src) + new /obj/item/clothing/head/soft/purple(src) + new /obj/item/paint/paint_remover(src) + new /obj/item/melee/flyswatter(src) + new /obj/item/flashlight(src) + for(var/i in 1 to 3) + new /obj/item/clothing/suit/caution(src) + new /obj/item/holosign_creator(src) + new /obj/item/lightreplacer(src) + new /obj/item/soap(src) + new /obj/item/storage/bag/trash(src) + new /obj/item/clothing/shoes/galoshes(src) + new /obj/item/watertank/janitor(src) + new /obj/item/storage/belt/janitor(src) + + +/obj/structure/closet/lawcloset + name = "legal closet" + desc = "It's a storage unit for courtroom apparel and items." + icon_door = "blue" + +/obj/structure/closet/lawcloset/PopulateContents() + ..() + new /obj/item/clothing/under/suit/blacktwopiece(src) + new /obj/item/clothing/under/rank/civilian/lawyer/female(src) + new /obj/item/clothing/under/rank/civilian/lawyer/black(src) + new /obj/item/clothing/under/rank/civilian/lawyer/red(src) + new /obj/item/clothing/under/rank/civilian/lawyer/bluesuit(src) + new /obj/item/clothing/suit/toggle/lawyer(src) + new /obj/item/clothing/under/rank/civilian/lawyer/purpsuit(src) + new /obj/item/clothing/suit/toggle/lawyer/purple(src) + new /obj/item/clothing/under/suit/black(src) + new /obj/item/clothing/suit/toggle/lawyer/black(src) + new /obj/item/clothing/shoes/laceup(src) + new /obj/item/clothing/shoes/laceup(src) + new /obj/item/clothing/accessory/lawyers_badge(src) + new /obj/item/clothing/accessory/lawyers_badge(src) + +/obj/structure/closet/wardrobe/chaplain_black + name = "chapel wardrobe" + desc = "It's a storage unit for Nanotrasen-approved religious attire." + icon_door = "black" + +/obj/structure/closet/wardrobe/chaplain_black/PopulateContents() + new /obj/item/choice_beacon/holy(src) + new /obj/item/clothing/accessory/pocketprotector/cosmetology(src) + new /obj/item/clothing/under/rank/civilian/chaplain(src) + new /obj/item/clothing/shoes/sneakers/black(src) + new /obj/item/clothing/suit/chaplainsuit/nun(src) + new /obj/item/clothing/head/nun_hood(src) + new /obj/item/clothing/suit/hooded/chaplainsuit/monkhabit(src) + new /obj/item/clothing/suit/chaplainsuit/holidaypriest(src) + new /obj/item/storage/backpack/cultpack(src) + new /obj/item/storage/fancy/candle_box(src) + new /obj/item/storage/fancy/candle_box(src) + return + +/obj/structure/closet/wardrobe/red + name = "security wardrobe" + icon_door = "red" + +/obj/structure/closet/wardrobe/red/PopulateContents() + var/static/items_inside = list( + /obj/item/clothing/suit/hooded/wintercoat/security = 1, + /obj/item/storage/backpack/security = 1, + /obj/item/storage/backpack/satchel/sec = 1, + /obj/item/storage/backpack/duffelbag/sec = 2, + /obj/item/clothing/under/rank/security/officer = 3, + /obj/item/clothing/under/rank/security/officer/skirt = 2, + /obj/item/clothing/shoes/jackboots = 3, + /obj/item/clothing/head/beret/sec = 3, // Waspstation edit - Berets + /obj/item/clothing/head/soft/sec = 3, + /obj/item/clothing/mask/bandana/red = 2) + generate_items_inside(items_inside,src) + return + +/obj/structure/closet/wardrobe/cargotech + name = "cargo wardrobe" + icon_door = "orange" + +/obj/structure/closet/wardrobe/cargotech/PopulateContents() + var/static/items_inside = list( + /obj/item/clothing/suit/hooded/wintercoat/cargo = 1, + /obj/item/clothing/under/rank/cargo/tech = 3, + /obj/item/clothing/shoes/sneakers/black = 3, + /obj/item/clothing/gloves/fingerless = 3, + /obj/item/clothing/head/soft = 3, + /obj/item/radio/headset/headset_cargo = 1) + generate_items_inside(items_inside,src) + +/obj/structure/closet/wardrobe/atmospherics_yellow + name = "atmospherics wardrobe" + icon_door = "atmos_wardrobe" + +/obj/structure/closet/wardrobe/atmospherics_yellow/PopulateContents() + var/static/items_inside = list( + /obj/item/clothing/head/beret/atmos = 3, // Waspstation edit - Berets + /obj/item/clothing/accessory/pocketprotector = 1, + /obj/item/storage/backpack/duffelbag/engineering = 1, + /obj/item/storage/backpack/satchel/eng = 1, + /obj/item/storage/backpack/industrial = 1, + /obj/item/clothing/suit/hooded/wintercoat/engineering/atmos = 3, + /obj/item/clothing/under/rank/engineering/atmospheric_technician = 3, + /obj/item/clothing/shoes/sneakers/black = 3) + generate_items_inside(items_inside,src) + return + +/obj/structure/closet/wardrobe/engineering_yellow + name = "engineering wardrobe" + icon_door = "yellow" + +/obj/structure/closet/wardrobe/engineering_yellow/PopulateContents() + var/static/items_inside = list( + /obj/item/clothing/accessory/pocketprotector = 1, + /obj/item/storage/backpack/duffelbag/engineering = 1, + /obj/item/storage/backpack/industrial = 1, + /obj/item/storage/backpack/satchel/eng = 1, + /obj/item/clothing/suit/hooded/wintercoat/engineering = 1, + /obj/item/clothing/under/rank/engineering/engineer = 3, + /obj/item/clothing/suit/hazardvest = 3, + /obj/item/clothing/shoes/workboots = 3, + /obj/item/clothing/head/beret/eng = 3, // Waspstation edit - Berets + /obj/item/clothing/head/hardhat = 3) + generate_items_inside(items_inside,src) + return + +/obj/structure/closet/wardrobe/white/medical + name = "medical doctor's wardrobe" + +/obj/structure/closet/wardrobe/white/medical/PopulateContents() + var/static/items_inside = list( + /obj/item/clothing/accessory/pocketprotector = 1, + /obj/item/storage/backpack/duffelbag/med = 1, + /obj/item/storage/backpack/medic = 1, + /obj/item/storage/backpack/satchel/med = 1, + /obj/item/clothing/suit/hooded/wintercoat/medical = 1, + /obj/item/clothing/under/rank/medical/doctor/nurse = 1, + /obj/item/clothing/head/nursehat = 1, + /obj/item/clothing/under/rank/medical/doctor/blue = 1, + /obj/item/clothing/under/rank/medical/doctor/green = 1, + /obj/item/clothing/under/rank/medical/doctor/purple = 1, + /obj/item/clothing/under/rank/medical/doctor = 3, + /obj/item/clothing/suit/toggle/labcoat = 3, + /obj/item/clothing/suit/toggle/labcoat/paramedic = 3, + /obj/item/clothing/shoes/sneakers/white = 3, + /obj/item/clothing/head/beret/med = 3, // Waspstation edit - Berets + /obj/item/clothing/head/soft/paramedic = 3) + generate_items_inside(items_inside,src) + return + +/obj/structure/closet/wardrobe/robotics_black + name = "robotics wardrobe" + icon_door = "black" + +/obj/structure/closet/wardrobe/robotics_black/PopulateContents() + var/static/items_inside = list( + /obj/item/clothing/glasses/hud/diagnostic = 2, + /obj/item/clothing/under/rank/rnd/roboticist = 2, + /obj/item/clothing/suit/toggle/labcoat = 2, + /obj/item/clothing/shoes/sneakers/black = 2, + /obj/item/clothing/gloves/fingerless = 2, + /obj/item/clothing/head/beret/sci = 2, // Waspstation edit - Berets + /obj/item/clothing/head/soft/black = 2) + generate_items_inside(items_inside,src) + if(prob(40)) + new /obj/item/clothing/mask/bandana/skull(src) + if(prob(40)) + new /obj/item/clothing/mask/bandana/skull(src) + return + + +/obj/structure/closet/wardrobe/chemistry_white + name = "chemistry wardrobe" + icon_door = "white" + +/obj/structure/closet/wardrobe/chemistry_white/PopulateContents() + var/static/items_inside = list( + /obj/item/clothing/under/rank/medical/chemist = 2, + /obj/item/clothing/shoes/sneakers/white = 2, + /obj/item/clothing/suit/toggle/labcoat/chemist = 2, + /obj/item/storage/backpack/chemistry = 2, + /obj/item/storage/backpack/satchel/chem = 2, + /obj/item/storage/bag/chemistry = 2) + generate_items_inside(items_inside,src) + return + + +/obj/structure/closet/wardrobe/genetics_white + name = "genetics wardrobe" + icon_door = "white" + +/obj/structure/closet/wardrobe/genetics_white/PopulateContents() + var/static/items_inside = list( + /obj/item/clothing/under/rank/medical/geneticist = 2, + /obj/item/clothing/shoes/sneakers/white = 2, + /obj/item/clothing/suit/toggle/labcoat/genetics = 2, + /obj/item/storage/backpack/genetics = 2, + /obj/item/storage/backpack/satchel/gen = 2) + generate_items_inside(items_inside,src) + return + + +/obj/structure/closet/wardrobe/virology_white + name = "virology wardrobe" + icon_door = "white" + +/obj/structure/closet/wardrobe/virology_white/PopulateContents() + var/static/items_inside = list( + /obj/item/clothing/under/rank/medical/virologist = 2, + /obj/item/clothing/shoes/sneakers/white = 2, + /obj/item/clothing/suit/toggle/labcoat/virologist = 2, + /obj/item/clothing/mask/surgical = 2, + /obj/item/clothing/head/beret/med = 2, // Waspstation edit - Berets + /obj/item/storage/backpack/virology = 2, + /obj/item/storage/backpack/satchel/vir = 2) + generate_items_inside(items_inside,src) + return + +/obj/structure/closet/wardrobe/science_white + name = "science wardrobe" + icon_door = "white" + +/obj/structure/closet/wardrobe/science_white/PopulateContents() + var/static/items_inside = list( + /obj/item/clothing/accessory/pocketprotector = 1, + /obj/item/storage/backpack/science = 2, + /obj/item/storage/backpack/satchel/tox = 2, + /obj/item/clothing/suit/hooded/wintercoat/science = 1, + /obj/item/clothing/under/rank/rnd/scientist = 3, + /obj/item/clothing/suit/toggle/labcoat/science = 3, + /obj/item/clothing/shoes/sneakers/white = 3, + /obj/item/radio/headset/headset_sci = 2, + /obj/item/clothing/head/beret/sci = 3, // Waspstation edit - Berets + /obj/item/clothing/mask/gas = 3) + generate_items_inside(items_inside,src) + return + +/obj/structure/closet/wardrobe/botanist + name = "botanist wardrobe" + icon_door = "green" + +/obj/structure/closet/wardrobe/botanist/PopulateContents() + var/static/items_inside = list( + /obj/item/storage/backpack/botany = 2, + /obj/item/storage/backpack/satchel/hyd = 2, + /obj/item/clothing/suit/hooded/wintercoat/hydro = 1, + /obj/item/clothing/suit/apron = 2, + /obj/item/clothing/suit/apron/overalls = 2, + /obj/item/clothing/under/rank/civilian/hydroponics = 3, + /obj/item/clothing/mask/bandana = 3) + generate_items_inside(items_inside,src) + +/obj/structure/closet/wardrobe/curator + name = "treasure hunting wardrobe" + icon_door = "black" + +/obj/structure/closet/wardrobe/curator/PopulateContents() + new /obj/item/clothing/head/fedora/curator(src) + new /obj/item/clothing/suit/curator(src) + new /obj/item/clothing/under/rank/civilian/curator/treasure_hunter(src) + new /obj/item/clothing/shoes/workboots/mining(src) + new /obj/item/storage/backpack/satchel/explorer(src) + diff --git a/code/game/objects/structures/crates_lockers/closets/l3closet.dm b/code/game/objects/structures/crates_lockers/closets/l3closet.dm index f53398e67850..22d996c5ef76 100644 --- a/code/game/objects/structures/crates_lockers/closets/l3closet.dm +++ b/code/game/objects/structures/crates_lockers/closets/l3closet.dm @@ -1,54 +1,54 @@ -/obj/structure/closet/l3closet - name = "level 3 biohazard gear closet" - desc = "It's a storage unit for level 3 biohazard gear." - icon_state = "bio" - -/obj/structure/closet/l3closet/PopulateContents() - new /obj/item/storage/bag/bio(src) - new /obj/item/clothing/suit/bio_suit/general(src) - new /obj/item/clothing/head/bio_hood/general(src) - new /obj/item/clothing/mask/breath(src) - new /obj/item/tank/internals/oxygen(src) - - -/obj/structure/closet/l3closet/virology - icon_state = "bio_viro" - -/obj/structure/closet/l3closet/virology/PopulateContents() - new /obj/item/storage/bag/bio(src) - new /obj/item/clothing/suit/bio_suit/virology(src) - new /obj/item/clothing/head/bio_hood/virology(src) - new /obj/item/clothing/mask/breath(src) - new /obj/item/tank/internals/oxygen(src) - - -/obj/structure/closet/l3closet/security - icon_state = "bio_sec" - -/obj/structure/closet/l3closet/security/PopulateContents() - new /obj/item/clothing/suit/bio_suit/security(src) - new /obj/item/clothing/head/bio_hood/security(src) - new /obj/item/clothing/mask/breath(src) - new /obj/item/tank/internals/oxygen(src) - - -/obj/structure/closet/l3closet/janitor - icon_state = "bio_jan" - -/obj/structure/closet/l3closet/janitor/PopulateContents() - new /obj/item/clothing/suit/bio_suit/janitor(src) - new /obj/item/clothing/head/bio_hood/janitor(src) - new /obj/item/clothing/mask/breath(src) - new /obj/item/tank/internals/oxygen(src) - - -/obj/structure/closet/l3closet/scientist - icon_state = "bio_viro" - -/obj/structure/closet/l3closet/scientist/PopulateContents() - new /obj/item/storage/bag/bio(src) - new /obj/item/clothing/suit/bio_suit/scientist(src) - new /obj/item/clothing/head/bio_hood/scientist(src) - new /obj/item/clothing/mask/breath(src) - new /obj/item/tank/internals/oxygen(src) - +/obj/structure/closet/l3closet + name = "level 3 biohazard gear closet" + desc = "It's a storage unit for level 3 biohazard gear." + icon_state = "bio" + +/obj/structure/closet/l3closet/PopulateContents() + new /obj/item/storage/bag/bio(src) + new /obj/item/clothing/suit/bio_suit/general(src) + new /obj/item/clothing/head/bio_hood/general(src) + new /obj/item/clothing/mask/breath(src) + new /obj/item/tank/internals/oxygen(src) + + +/obj/structure/closet/l3closet/virology + icon_state = "bio_viro" + +/obj/structure/closet/l3closet/virology/PopulateContents() + new /obj/item/storage/bag/bio(src) + new /obj/item/clothing/suit/bio_suit/virology(src) + new /obj/item/clothing/head/bio_hood/virology(src) + new /obj/item/clothing/mask/breath(src) + new /obj/item/tank/internals/oxygen(src) + + +/obj/structure/closet/l3closet/security + icon_state = "bio_sec" + +/obj/structure/closet/l3closet/security/PopulateContents() + new /obj/item/clothing/suit/bio_suit/security(src) + new /obj/item/clothing/head/bio_hood/security(src) + new /obj/item/clothing/mask/breath(src) + new /obj/item/tank/internals/oxygen(src) + + +/obj/structure/closet/l3closet/janitor + icon_state = "bio_jan" + +/obj/structure/closet/l3closet/janitor/PopulateContents() + new /obj/item/clothing/suit/bio_suit/janitor(src) + new /obj/item/clothing/head/bio_hood/janitor(src) + new /obj/item/clothing/mask/breath(src) + new /obj/item/tank/internals/oxygen(src) + + +/obj/structure/closet/l3closet/scientist + icon_state = "bio_viro" + +/obj/structure/closet/l3closet/scientist/PopulateContents() + new /obj/item/storage/bag/bio(src) + new /obj/item/clothing/suit/bio_suit/scientist(src) + new /obj/item/clothing/head/bio_hood/scientist(src) + new /obj/item/clothing/mask/breath(src) + new /obj/item/tank/internals/oxygen(src) + diff --git a/code/game/objects/structures/crates_lockers/closets/secure/bar.dm b/code/game/objects/structures/crates_lockers/closets/secure/bar.dm index 465d488973d2..4bbee2f969af 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/bar.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/bar.dm @@ -1,17 +1,17 @@ -/obj/structure/closet/secure_closet/bar - name = "booze storage" - req_access = list(ACCESS_BAR) - icon_state = "cabinet" - resistance_flags = FLAMMABLE - max_integrity = 70 - open_sound = 'sound/machines/wooden_closet_open.ogg' - close_sound = 'sound/machines/wooden_closet_close.ogg' - open_sound_volume = 25 - close_sound_volume = 50 - -/obj/structure/closet/secure_closet/bar/PopulateContents() - ..() - for(var/i in 1 to 10) - new /obj/item/reagent_containers/food/drinks/beer( src ) - new /obj/item/etherealballdeployer(src) - new /obj/item/roulette_wheel_beacon(src) +/obj/structure/closet/secure_closet/bar + name = "booze storage" + req_access = list(ACCESS_BAR) + icon_state = "cabinet" + resistance_flags = FLAMMABLE + max_integrity = 70 + open_sound = 'sound/machines/wooden_closet_open.ogg' + close_sound = 'sound/machines/wooden_closet_close.ogg' + open_sound_volume = 25 + close_sound_volume = 50 + +/obj/structure/closet/secure_closet/bar/PopulateContents() + ..() + for(var/i in 1 to 10) + new /obj/item/reagent_containers/food/drinks/beer( src ) + new /obj/item/etherealballdeployer(src) + new /obj/item/roulette_wheel_beacon(src) diff --git a/code/game/objects/structures/crates_lockers/closets/secure/cargo.dm b/code/game/objects/structures/crates_lockers/closets/secure/cargo.dm index 8a19d67b1a73..e4f6800b5fd1 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/cargo.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/cargo.dm @@ -1,25 +1,25 @@ -/obj/structure/closet/secure_closet/quartermaster - name = "\proper quartermaster's locker" - req_access = list(ACCESS_QM) - icon_state = "qm" - -/obj/structure/closet/secure_closet/quartermaster/PopulateContents() - ..() - new /obj/item/card/id/departmental_budget/car(src)//WaspStation Edit - Budget Cards - new /obj/item/clothing/neck/cloak/qm(src) - new /obj/item/storage/lockbox/medal/cargo(src) - new /obj/item/clothing/under/rank/cargo/qm(src) - new /obj/item/clothing/under/rank/cargo/qm/skirt(src) - new /obj/item/clothing/shoes/sneakers/brown(src) - new /obj/item/radio/headset/headset_cargo(src) - new /obj/item/clothing/suit/fire/firefighter(src) - new /obj/item/clothing/gloves/fingerless(src) - new /obj/item/megaphone/cargo(src) - new /obj/item/tank/internals/emergency_oxygen(src) - new /obj/item/clothing/mask/gas(src) - new /obj/item/clothing/head/soft(src) - new /obj/item/export_scanner(src) - new /obj/item/door_remote/quartermaster(src) - new /obj/item/circuitboard/machine/techfab/department/cargo(src) - new /obj/item/storage/photo_album/QM(src) - new /obj/item/circuitboard/machine/ore_silo(src) +/obj/structure/closet/secure_closet/quartermaster + name = "\proper quartermaster's locker" + req_access = list(ACCESS_QM) + icon_state = "qm" + +/obj/structure/closet/secure_closet/quartermaster/PopulateContents() + ..() + new /obj/item/card/id/departmental_budget/car(src)//WaspStation Edit - Budget Cards + new /obj/item/clothing/neck/cloak/qm(src) + new /obj/item/storage/lockbox/medal/cargo(src) + new /obj/item/clothing/under/rank/cargo/qm(src) + new /obj/item/clothing/under/rank/cargo/qm/skirt(src) + new /obj/item/clothing/shoes/sneakers/brown(src) + new /obj/item/radio/headset/headset_cargo(src) + new /obj/item/clothing/suit/fire/firefighter(src) + new /obj/item/clothing/gloves/fingerless(src) + new /obj/item/megaphone/cargo(src) + new /obj/item/tank/internals/emergency_oxygen(src) + new /obj/item/clothing/mask/gas(src) + new /obj/item/clothing/head/soft(src) + new /obj/item/export_scanner(src) + new /obj/item/door_remote/quartermaster(src) + new /obj/item/circuitboard/machine/techfab/department/cargo(src) + new /obj/item/storage/photo_album/QM(src) + new /obj/item/circuitboard/machine/ore_silo(src) diff --git a/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm b/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm index 949509b3abe5..62df292f8ce7 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm @@ -1,99 +1,99 @@ -/obj/structure/closet/secure_closet/engineering_chief - name = "\proper chief engineer's locker" - req_access = list(ACCESS_CE) - icon_state = "ce" - -/obj/structure/closet/secure_closet/engineering_chief/PopulateContents() - ..() - //WaspStation Begin - new /obj/item/clothing/head/beret/ce(src) //Berets - new /obj/item/clothing/under/rank/command(src) //Better command uniforms - new /obj/item/card/id/departmental_budget/eng(src) //Budger Cards - //WaspStation End - new /obj/item/clothing/neck/cloak/ce(src) - new /obj/item/clothing/under/rank/engineering/chief_engineer(src) - new /obj/item/clothing/under/rank/engineering/chief_engineer/skirt(src) - new /obj/item/clothing/head/hardhat/white(src) - new /obj/item/clothing/head/hardhat/weldhat/white(src) - new /obj/item/clothing/gloves/color/yellow(src) - new /obj/item/clothing/shoes/sneakers/brown(src) - new /obj/item/tank/jetpack/suit(src) - new /obj/item/cartridge/ce(src) - new /obj/item/radio/headset/heads/ce(src) - new /obj/item/megaphone/command(src) - new /obj/item/areaeditor/blueprints(src) - new /obj/item/holosign_creator/engineering(src) - new /obj/item/assembly/flash/handheld(src) - new /obj/item/clothing/glasses/meson/engine(src) - new /obj/item/door_remote/chief_engineer(src) - new /obj/item/pipe_dispenser(src) - new /obj/item/circuitboard/machine/techfab/department/engineering(src) - new /obj/item/extinguisher/advanced(src) - new /obj/item/storage/photo_album/CE(src) - -/obj/structure/closet/secure_closet/engineering_electrical - name = "electrical supplies locker" - req_access = list(ACCESS_ENGINE_EQUIP) - icon_state = "eng" - icon_door = "eng_elec" - -/obj/structure/closet/secure_closet/engineering_electrical/PopulateContents() - ..() - var/static/items_inside = list( - /obj/item/clothing/gloves/color/yellow = 2, - /obj/item/inducer = 2, - /obj/item/storage/toolbox/electrical = 3, - /obj/item/electronics/apc = 3, - /obj/item/multitool = 3) - generate_items_inside(items_inside,src) - -/obj/structure/closet/secure_closet/engineering_welding - name = "welding supplies locker" - req_access = list(ACCESS_ENGINE_EQUIP) - icon_state = "eng" - icon_door = "eng_weld" - -/obj/structure/closet/secure_closet/engineering_welding/PopulateContents() - ..() - for(var/i in 1 to 3) - new /obj/item/clothing/head/welding(src) - for(var/i in 1 to 3) - new /obj/item/weldingtool(src) - -/obj/structure/closet/secure_closet/engineering_personal - name = "engineer's locker" - req_access = list(ACCESS_ENGINE_EQUIP) - icon_state = "eng_secure" - -/obj/structure/closet/secure_closet/engineering_personal/PopulateContents() - ..() - new /obj/item/clothing/head/beret/eng(src) // Waspstation edit - Berets - new /obj/item/clothing/glasses/meson/prescription(src) //Waspstation edit - Prescription HUDs - new /obj/item/radio/headset/headset_eng(src) - new /obj/item/storage/toolbox/mechanical(src) - new /obj/item/tank/internals/emergency_oxygen/engi(src) - new /obj/item/holosign_creator/engineering(src) - new /obj/item/clothing/mask/gas(src) - new /obj/item/clothing/glasses/meson/engine(src) - new /obj/item/storage/box/emptysandbags(src) - new /obj/item/storage/bag/construction(src) - - -/obj/structure/closet/secure_closet/atmospherics - name = "\proper atmospheric technician's locker" - req_access = list(ACCESS_ATMOSPHERICS) - icon_state = "atmos" - -/obj/structure/closet/secure_closet/atmospherics/PopulateContents() - ..() - new /obj/item/radio/headset/headset_eng(src) - new /obj/item/pipe_dispenser(src) - new /obj/item/storage/toolbox/mechanical(src) - new /obj/item/tank/internals/emergency_oxygen/engi(src) - new /obj/item/holosign_creator/atmos(src) - new /obj/item/watertank/atmos(src) - new /obj/item/clothing/suit/fire/atmos(src) - new /obj/item/clothing/mask/gas/atmos(src) - new /obj/item/clothing/head/hardhat/atmos(src) - new /obj/item/clothing/glasses/meson/engine/tray(src) - new /obj/item/extinguisher/advanced(src) +/obj/structure/closet/secure_closet/engineering_chief + name = "\proper chief engineer's locker" + req_access = list(ACCESS_CE) + icon_state = "ce" + +/obj/structure/closet/secure_closet/engineering_chief/PopulateContents() + ..() + //WaspStation Begin + new /obj/item/clothing/head/beret/ce(src) //Berets + new /obj/item/clothing/under/rank/command(src) //Better command uniforms + new /obj/item/card/id/departmental_budget/eng(src) //Budger Cards + //WaspStation End + new /obj/item/clothing/neck/cloak/ce(src) + new /obj/item/clothing/under/rank/engineering/chief_engineer(src) + new /obj/item/clothing/under/rank/engineering/chief_engineer/skirt(src) + new /obj/item/clothing/head/hardhat/white(src) + new /obj/item/clothing/head/hardhat/weldhat/white(src) + new /obj/item/clothing/gloves/color/yellow(src) + new /obj/item/clothing/shoes/sneakers/brown(src) + new /obj/item/tank/jetpack/suit(src) + new /obj/item/cartridge/ce(src) + new /obj/item/radio/headset/heads/ce(src) + new /obj/item/megaphone/command(src) + new /obj/item/areaeditor/blueprints(src) + new /obj/item/holosign_creator/engineering(src) + new /obj/item/assembly/flash/handheld(src) + new /obj/item/clothing/glasses/meson/engine(src) + new /obj/item/door_remote/chief_engineer(src) + new /obj/item/pipe_dispenser(src) + new /obj/item/circuitboard/machine/techfab/department/engineering(src) + new /obj/item/extinguisher/advanced(src) + new /obj/item/storage/photo_album/CE(src) + +/obj/structure/closet/secure_closet/engineering_electrical + name = "electrical supplies locker" + req_access = list(ACCESS_ENGINE_EQUIP) + icon_state = "eng" + icon_door = "eng_elec" + +/obj/structure/closet/secure_closet/engineering_electrical/PopulateContents() + ..() + var/static/items_inside = list( + /obj/item/clothing/gloves/color/yellow = 2, + /obj/item/inducer = 2, + /obj/item/storage/toolbox/electrical = 3, + /obj/item/electronics/apc = 3, + /obj/item/multitool = 3) + generate_items_inside(items_inside,src) + +/obj/structure/closet/secure_closet/engineering_welding + name = "welding supplies locker" + req_access = list(ACCESS_ENGINE_EQUIP) + icon_state = "eng" + icon_door = "eng_weld" + +/obj/structure/closet/secure_closet/engineering_welding/PopulateContents() + ..() + for(var/i in 1 to 3) + new /obj/item/clothing/head/welding(src) + for(var/i in 1 to 3) + new /obj/item/weldingtool(src) + +/obj/structure/closet/secure_closet/engineering_personal + name = "engineer's locker" + req_access = list(ACCESS_ENGINE_EQUIP) + icon_state = "eng_secure" + +/obj/structure/closet/secure_closet/engineering_personal/PopulateContents() + ..() + new /obj/item/clothing/head/beret/eng(src) // Waspstation edit - Berets + new /obj/item/clothing/glasses/meson/prescription(src) //Waspstation edit - Prescription HUDs + new /obj/item/radio/headset/headset_eng(src) + new /obj/item/storage/toolbox/mechanical(src) + new /obj/item/tank/internals/emergency_oxygen/engi(src) + new /obj/item/holosign_creator/engineering(src) + new /obj/item/clothing/mask/gas(src) + new /obj/item/clothing/glasses/meson/engine(src) + new /obj/item/storage/box/emptysandbags(src) + new /obj/item/storage/bag/construction(src) + + +/obj/structure/closet/secure_closet/atmospherics + name = "\proper atmospheric technician's locker" + req_access = list(ACCESS_ATMOSPHERICS) + icon_state = "atmos" + +/obj/structure/closet/secure_closet/atmospherics/PopulateContents() + ..() + new /obj/item/radio/headset/headset_eng(src) + new /obj/item/pipe_dispenser(src) + new /obj/item/storage/toolbox/mechanical(src) + new /obj/item/tank/internals/emergency_oxygen/engi(src) + new /obj/item/holosign_creator/atmos(src) + new /obj/item/watertank/atmos(src) + new /obj/item/clothing/suit/fire/atmos(src) + new /obj/item/clothing/mask/gas/atmos(src) + new /obj/item/clothing/head/hardhat/atmos(src) + new /obj/item/clothing/glasses/meson/engine/tray(src) + new /obj/item/extinguisher/advanced(src) diff --git a/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm b/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm index 6507e5efea10..1cb8b878c6b8 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm @@ -1,116 +1,116 @@ -/obj/structure/closet/secure_closet/freezer - icon_state = "freezer" - var/jones = FALSE - -/obj/structure/closet/secure_closet/freezer/Destroy() - recursive_organ_check(src) - ..() - -/obj/structure/closet/secure_closet/freezer/Initialize() - . = ..() - recursive_organ_check(src) - -/obj/structure/closet/secure_closet/freezer/open(mob/living/user, force = TRUE) - if(opened || !can_open(user, force)) //dupe check just so we don't let the organs decay when someone fails to open the locker - return FALSE - recursive_organ_check(src) - return ..() - -/obj/structure/closet/secure_closet/freezer/close(mob/living/user) - if(..()) //if we actually closed the locker - recursive_organ_check(src) - -/obj/structure/closet/secure_closet/freezer/ex_act() - if(!jones) - jones = TRUE - else - ..() - -/obj/structure/closet/secure_closet/freezer/kitchen - name = "kitchen cabinet" - req_access = list(ACCESS_KITCHEN) - -/obj/structure/closet/secure_closet/freezer/kitchen/PopulateContents() - ..() - for(var/i = 0, i < 3, i++) - new /obj/item/reagent_containers/food/condiment/flour(src) - new /obj/item/reagent_containers/food/condiment/rice(src) - new /obj/item/reagent_containers/food/condiment/sugar(src) - -/obj/structure/closet/secure_closet/freezer/kitchen/maintenance - name = "maintenance refrigerator" - desc = "This refrigerator looks quite dusty, is there anything edible still inside?" - req_access = list() - -/obj/structure/closet/secure_closet/freezer/kitchen/maintenance/PopulateContents() - ..() - for(var/i = 0, i < 5, i++) - new /obj/item/reagent_containers/food/condiment/milk(src) - for(var/i = 0, i < 5, i++) - new /obj/item/reagent_containers/food/condiment/soymilk(src) - for(var/i = 0, i < 2, i++) - new /obj/item/storage/fancy/egg_box(src) - -/obj/structure/closet/secure_closet/freezer/kitchen/mining - req_access = list() - -/obj/structure/closet/secure_closet/freezer/meat - name = "meat fridge" - req_access = list(ACCESS_KITCHEN) - -/obj/structure/closet/secure_closet/freezer/meat/PopulateContents() - ..() - for(var/i = 0, i < 4, i++) - new /obj/item/reagent_containers/food/snacks/meat/slab/monkey(src) - -/obj/structure/closet/secure_closet/freezer/meat/open - req_access = null - locked = FALSE - -/obj/structure/closet/secure_closet/freezer/gulag_fridge - name = "refrigerator" - -/obj/structure/closet/secure_closet/freezer/gulag_fridge/PopulateContents() - ..() - for(var/i in 1 to 3) - new /obj/item/reagent_containers/food/drinks/beer/light(src) - -/obj/structure/closet/secure_closet/freezer/fridge - name = "refrigerator" - req_access = list(ACCESS_KITCHEN) - -/obj/structure/closet/secure_closet/freezer/fridge/PopulateContents() - ..() - for(var/i = 0, i < 5, i++) - new /obj/item/reagent_containers/food/condiment/milk(src) - for(var/i = 0, i < 5, i++) - new /obj/item/reagent_containers/food/condiment/soymilk(src) - for(var/i = 0, i < 2, i++) - new /obj/item/storage/fancy/egg_box(src) - -/obj/structure/closet/secure_closet/freezer/fridge/open - req_access = null - locked = FALSE - -/obj/structure/closet/secure_closet/freezer/money - name = "freezer" - desc = "This contains cold hard cash." - req_access = list(ACCESS_VAULT) - -/obj/structure/closet/secure_closet/freezer/money/PopulateContents() - ..() - for(var/i = 0, i < 3, i++) - new /obj/item/stack/spacecash/c1000(src) - for(var/i = 0, i < 5, i++) - new /obj/item/stack/spacecash/c500(src) - for(var/i = 0, i < 6, i++) - new /obj/item/stack/spacecash/c200(src) - -/obj/structure/closet/secure_closet/freezer/cream_pie - name = "cream pie closet" - desc = "Contains pies filled with cream and/or custard, you sickos." - req_access = list(ACCESS_THEATRE) - -/obj/structure/closet/secure_closet/freezer/cream_pie/PopulateContents() - ..() - new /obj/item/reagent_containers/food/snacks/pie/cream(src) +/obj/structure/closet/secure_closet/freezer + icon_state = "freezer" + var/jones = FALSE + +/obj/structure/closet/secure_closet/freezer/Destroy() + recursive_organ_check(src) + ..() + +/obj/structure/closet/secure_closet/freezer/Initialize() + . = ..() + recursive_organ_check(src) + +/obj/structure/closet/secure_closet/freezer/open(mob/living/user, force = TRUE) + if(opened || !can_open(user, force)) //dupe check just so we don't let the organs decay when someone fails to open the locker + return FALSE + recursive_organ_check(src) + return ..() + +/obj/structure/closet/secure_closet/freezer/close(mob/living/user) + if(..()) //if we actually closed the locker + recursive_organ_check(src) + +/obj/structure/closet/secure_closet/freezer/ex_act() + if(!jones) + jones = TRUE + else + ..() + +/obj/structure/closet/secure_closet/freezer/kitchen + name = "kitchen cabinet" + req_access = list(ACCESS_KITCHEN) + +/obj/structure/closet/secure_closet/freezer/kitchen/PopulateContents() + ..() + for(var/i = 0, i < 3, i++) + new /obj/item/reagent_containers/food/condiment/flour(src) + new /obj/item/reagent_containers/food/condiment/rice(src) + new /obj/item/reagent_containers/food/condiment/sugar(src) + +/obj/structure/closet/secure_closet/freezer/kitchen/maintenance + name = "maintenance refrigerator" + desc = "This refrigerator looks quite dusty, is there anything edible still inside?" + req_access = list() + +/obj/structure/closet/secure_closet/freezer/kitchen/maintenance/PopulateContents() + ..() + for(var/i = 0, i < 5, i++) + new /obj/item/reagent_containers/food/condiment/milk(src) + for(var/i = 0, i < 5, i++) + new /obj/item/reagent_containers/food/condiment/soymilk(src) + for(var/i = 0, i < 2, i++) + new /obj/item/storage/fancy/egg_box(src) + +/obj/structure/closet/secure_closet/freezer/kitchen/mining + req_access = list() + +/obj/structure/closet/secure_closet/freezer/meat + name = "meat fridge" + req_access = list(ACCESS_KITCHEN) + +/obj/structure/closet/secure_closet/freezer/meat/PopulateContents() + ..() + for(var/i = 0, i < 4, i++) + new /obj/item/reagent_containers/food/snacks/meat/slab/monkey(src) + +/obj/structure/closet/secure_closet/freezer/meat/open + req_access = null + locked = FALSE + +/obj/structure/closet/secure_closet/freezer/gulag_fridge + name = "refrigerator" + +/obj/structure/closet/secure_closet/freezer/gulag_fridge/PopulateContents() + ..() + for(var/i in 1 to 3) + new /obj/item/reagent_containers/food/drinks/beer/light(src) + +/obj/structure/closet/secure_closet/freezer/fridge + name = "refrigerator" + req_access = list(ACCESS_KITCHEN) + +/obj/structure/closet/secure_closet/freezer/fridge/PopulateContents() + ..() + for(var/i = 0, i < 5, i++) + new /obj/item/reagent_containers/food/condiment/milk(src) + for(var/i = 0, i < 5, i++) + new /obj/item/reagent_containers/food/condiment/soymilk(src) + for(var/i = 0, i < 2, i++) + new /obj/item/storage/fancy/egg_box(src) + +/obj/structure/closet/secure_closet/freezer/fridge/open + req_access = null + locked = FALSE + +/obj/structure/closet/secure_closet/freezer/money + name = "freezer" + desc = "This contains cold hard cash." + req_access = list(ACCESS_VAULT) + +/obj/structure/closet/secure_closet/freezer/money/PopulateContents() + ..() + for(var/i = 0, i < 3, i++) + new /obj/item/stack/spacecash/c1000(src) + for(var/i = 0, i < 5, i++) + new /obj/item/stack/spacecash/c500(src) + for(var/i = 0, i < 6, i++) + new /obj/item/stack/spacecash/c200(src) + +/obj/structure/closet/secure_closet/freezer/cream_pie + name = "cream pie closet" + desc = "Contains pies filled with cream and/or custard, you sickos." + req_access = list(ACCESS_THEATRE) + +/obj/structure/closet/secure_closet/freezer/cream_pie/PopulateContents() + ..() + new /obj/item/reagent_containers/food/snacks/pie/cream(src) diff --git a/code/game/objects/structures/crates_lockers/closets/secure/hydroponics.dm b/code/game/objects/structures/crates_lockers/closets/secure/hydroponics.dm index 5c122eb04514..c554cad034b9 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/hydroponics.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/hydroponics.dm @@ -1,13 +1,13 @@ -/obj/structure/closet/secure_closet/hydroponics - name = "botanist's locker" - req_access = list(ACCESS_HYDROPONICS) - icon_state = "hydro" - -/obj/structure/closet/secure_closet/hydroponics/PopulateContents() - ..() - new /obj/item/storage/bag/plants/portaseeder(src) - new /obj/item/plant_analyzer(src) - new /obj/item/radio/headset/headset_srv(src) - new /obj/item/cultivator(src) - new /obj/item/hatchet(src) - new /obj/item/storage/box/disks_plantgene(src) +/obj/structure/closet/secure_closet/hydroponics + name = "botanist's locker" + req_access = list(ACCESS_HYDROPONICS) + icon_state = "hydro" + +/obj/structure/closet/secure_closet/hydroponics/PopulateContents() + ..() + new /obj/item/storage/bag/plants/portaseeder(src) + new /obj/item/plant_analyzer(src) + new /obj/item/radio/headset/headset_srv(src) + new /obj/item/cultivator(src) + new /obj/item/hatchet(src) + new /obj/item/storage/box/disks_plantgene(src) diff --git a/code/game/objects/structures/crates_lockers/closets/secure/medical.dm b/code/game/objects/structures/crates_lockers/closets/secure/medical.dm index 3c0b4e060555..9550025a76af 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/medical.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/medical.dm @@ -1,147 +1,147 @@ -/obj/structure/closet/secure_closet/medical1 - name = "medicine closet" - desc = "Filled to the brim with medical junk." - icon_state = "med" - req_access = list(ACCESS_MEDICAL) - -/obj/structure/closet/secure_closet/medical1/PopulateContents() - ..() - var/static/items_inside = list( - /obj/item/reagent_containers/glass/beaker = 2, - /obj/item/reagent_containers/dropper = 2, - /obj/item/storage/belt/medical = 1, - /obj/item/storage/box/syringes = 1, - /obj/item/reagent_containers/glass/bottle/toxin = 1, - /obj/item/reagent_containers/glass/bottle/morphine = 2, - /obj/item/reagent_containers/glass/bottle/epinephrine= 3, - /obj/item/reagent_containers/glass/bottle/charcoal = 3, - /obj/item/storage/box/rxglasses = 1) - generate_items_inside(items_inside,src) - -/obj/structure/closet/secure_closet/medical2 - name = "anesthetic closet" - desc = "Used to knock people out." - req_access = list(ACCESS_SURGERY) - -/obj/structure/closet/secure_closet/medical2/PopulateContents() - ..() - for(var/i in 1 to 3) - new /obj/item/tank/internals/anesthetic(src) - for(var/i in 1 to 3) - new /obj/item/clothing/mask/breath/medical(src) - -/obj/structure/closet/secure_closet/medical3 - name = "medical doctor's locker" - req_access = list(ACCESS_SURGERY) - icon_state = "med_secure" - -/obj/structure/closet/secure_closet/medical3/PopulateContents() - ..() - //WaspStation Begin - new /obj/item/storage/box/hypospray(src) //Hypo Mk. 2s - new /obj/item/storage/bag/medical(src) //Medibags - new /obj/item/clothing/head/beret/med(src) //Berets - new /obj/item/clothing/glasses/hud/health/prescription(src) //Prescription HUDs - //WaspStation End - new /obj/item/radio/headset/headset_med(src) - new /obj/item/defibrillator/loaded(src) - new /obj/item/clothing/gloves/color/latex/nitrile(src) - new /obj/item/storage/belt/medical(src) - new /obj/item/clothing/glasses/hud/health(src) - return - -/obj/structure/closet/secure_closet/psychology - name = "psychology locker" - req_access = list(ACCESS_PSYCHOLOGY) - icon_state = "cabinet" - open_sound = 'sound/machines/wooden_closet_open.ogg' - close_sound = 'sound/machines/wooden_closet_close.ogg' - open_sound_volume = 25 - close_sound_volume = 50 - -/obj/structure/closet/secure_closet/psychology/PopulateContents() - ..() - new /obj/item/clothing/under/suit/black(src) - new /obj/item/clothing/under/suit/black/skirt(src) - new /obj/item/clothing/shoes/laceup(src) - new /obj/item/storage/backpack/medic(src) - new /obj/item/radio/headset/headset_srvmed(src) - new /obj/item/clipboard(src) - new /obj/item/clothing/suit/straight_jacket(src) - new /obj/item/clothing/ears/earmuffs(src) - new /obj/item/clothing/mask/muzzle(src) - new /obj/item/clothing/glasses/blindfold(src) - -/obj/structure/closet/secure_closet/CMO - name = "\proper chief medical officer's locker" - req_access = list(ACCESS_CMO) - icon_state = "cmo" - -/obj/structure/closet/secure_closet/CMO/PopulateContents() - ..() - //WaspStation Begin - new /obj/item/storage/belt/medical(src) //Gives the CMO a belt - new /obj/item/storage/bag/medical(src) //Medibags - new /obj/item/clothing/head/beret/cmo(src) //Berets - new /obj/item/clothing/under/rank/command(src) //Better command uniforms - new /obj/item/storage/box/hypospray/CMO(src) //Hypo mk. 2s - new /obj/item/card/id/departmental_budget/med(src) //Budget cards - //WaspStation End - new /obj/item/clothing/neck/cloak/cmo(src) - new /obj/item/clothing/suit/bio_suit/cmo(src) - new /obj/item/clothing/head/bio_hood/cmo(src) - new /obj/item/clothing/suit/toggle/labcoat/cmo(src) - new /obj/item/clothing/under/rank/medical/chief_medical_officer(src) - new /obj/item/clothing/under/rank/medical/chief_medical_officer/skirt(src) - new /obj/item/clothing/shoes/sneakers/brown (src) - new /obj/item/cartridge/cmo(src) - new /obj/item/radio/headset/heads/cmo(src) - new /obj/item/megaphone/command(src) - new /obj/item/defibrillator/compact/loaded(src) - new /obj/item/clothing/gloves/color/latex/nitrile(src) - new /obj/item/healthanalyzer/advanced(src) - new /obj/item/assembly/flash/handheld(src) - new /obj/item/reagent_containers/hypospray/CMO(src) - new /obj/item/autosurgeon/cmo(src) - new /obj/item/door_remote/chief_medical_officer(src) - new /obj/item/clothing/neck/petcollar(src) - new /obj/item/pet_carrier(src) - new /obj/item/wallframe/defib_mount(src) - new /obj/item/circuitboard/machine/techfab/department/medical(src) - new /obj/item/storage/photo_album/CMO(src) - -/obj/structure/closet/secure_closet/animal - name = "animal control" - req_access = list(ACCESS_SURGERY) - -/obj/structure/closet/secure_closet/animal/PopulateContents() - ..() - new /obj/item/assembly/signaler(src) - for(var/i in 1 to 3) - new /obj/item/electropack(src) - -/obj/structure/closet/secure_closet/chemical - name = "chemical closet" - desc = "Store dangerous chemicals in here." - req_access = list(ACCESS_CHEMISTRY) - icon_door = "chemical" - -/obj/structure/closet/secure_closet/chemical/PopulateContents() - ..() - new /obj/item/storage/box/pillbottles(src) - new /obj/item/storage/box/pillbottles(src) - new /obj/item/storage/box/medigels(src) - new /obj/item/storage/box/medigels(src) - -/obj/structure/closet/secure_closet/chemical/heisenberg //contains one of each beaker, syringe etc. - name = "advanced chemical closet" - -/obj/structure/closet/secure_closet/chemical/heisenberg/PopulateContents() - ..() - new /obj/item/reagent_containers/dropper(src) - new /obj/item/reagent_containers/dropper(src) - new /obj/item/storage/box/syringes/variety(src) - new /obj/item/storage/box/beakers/variety(src) - new /obj/item/clothing/head/beret/chem(src) // Wasp edit - Berets - new /obj/item/clothing/glasses/science/prescription(src) // Wasp Edit - Prescription HUDs - new /obj/item/clothing/glasses/science(src) +/obj/structure/closet/secure_closet/medical1 + name = "medicine closet" + desc = "Filled to the brim with medical junk." + icon_state = "med" + req_access = list(ACCESS_MEDICAL) + +/obj/structure/closet/secure_closet/medical1/PopulateContents() + ..() + var/static/items_inside = list( + /obj/item/reagent_containers/glass/beaker = 2, + /obj/item/reagent_containers/dropper = 2, + /obj/item/storage/belt/medical = 1, + /obj/item/storage/box/syringes = 1, + /obj/item/reagent_containers/glass/bottle/toxin = 1, + /obj/item/reagent_containers/glass/bottle/morphine = 2, + /obj/item/reagent_containers/glass/bottle/epinephrine= 3, + /obj/item/reagent_containers/glass/bottle/charcoal = 3, + /obj/item/storage/box/rxglasses = 1) + generate_items_inside(items_inside,src) + +/obj/structure/closet/secure_closet/medical2 + name = "anesthetic closet" + desc = "Used to knock people out." + req_access = list(ACCESS_SURGERY) + +/obj/structure/closet/secure_closet/medical2/PopulateContents() + ..() + for(var/i in 1 to 3) + new /obj/item/tank/internals/anesthetic(src) + for(var/i in 1 to 3) + new /obj/item/clothing/mask/breath/medical(src) + +/obj/structure/closet/secure_closet/medical3 + name = "medical doctor's locker" + req_access = list(ACCESS_SURGERY) + icon_state = "med_secure" + +/obj/structure/closet/secure_closet/medical3/PopulateContents() + ..() + //WaspStation Begin + new /obj/item/storage/box/hypospray(src) //Hypo Mk. 2s + new /obj/item/storage/bag/medical(src) //Medibags + new /obj/item/clothing/head/beret/med(src) //Berets + new /obj/item/clothing/glasses/hud/health/prescription(src) //Prescription HUDs + //WaspStation End + new /obj/item/radio/headset/headset_med(src) + new /obj/item/defibrillator/loaded(src) + new /obj/item/clothing/gloves/color/latex/nitrile(src) + new /obj/item/storage/belt/medical(src) + new /obj/item/clothing/glasses/hud/health(src) + return + +/obj/structure/closet/secure_closet/psychology + name = "psychology locker" + req_access = list(ACCESS_PSYCHOLOGY) + icon_state = "cabinet" + open_sound = 'sound/machines/wooden_closet_open.ogg' + close_sound = 'sound/machines/wooden_closet_close.ogg' + open_sound_volume = 25 + close_sound_volume = 50 + +/obj/structure/closet/secure_closet/psychology/PopulateContents() + ..() + new /obj/item/clothing/under/suit/black(src) + new /obj/item/clothing/under/suit/black/skirt(src) + new /obj/item/clothing/shoes/laceup(src) + new /obj/item/storage/backpack/medic(src) + new /obj/item/radio/headset/headset_srvmed(src) + new /obj/item/clipboard(src) + new /obj/item/clothing/suit/straight_jacket(src) + new /obj/item/clothing/ears/earmuffs(src) + new /obj/item/clothing/mask/muzzle(src) + new /obj/item/clothing/glasses/blindfold(src) + +/obj/structure/closet/secure_closet/CMO + name = "\proper chief medical officer's locker" + req_access = list(ACCESS_CMO) + icon_state = "cmo" + +/obj/structure/closet/secure_closet/CMO/PopulateContents() + ..() + //WaspStation Begin + new /obj/item/storage/belt/medical(src) //Gives the CMO a belt + new /obj/item/storage/bag/medical(src) //Medibags + new /obj/item/clothing/head/beret/cmo(src) //Berets + new /obj/item/clothing/under/rank/command(src) //Better command uniforms + new /obj/item/storage/box/hypospray/CMO(src) //Hypo mk. 2s + new /obj/item/card/id/departmental_budget/med(src) //Budget cards + //WaspStation End + new /obj/item/clothing/neck/cloak/cmo(src) + new /obj/item/clothing/suit/bio_suit/cmo(src) + new /obj/item/clothing/head/bio_hood/cmo(src) + new /obj/item/clothing/suit/toggle/labcoat/cmo(src) + new /obj/item/clothing/under/rank/medical/chief_medical_officer(src) + new /obj/item/clothing/under/rank/medical/chief_medical_officer/skirt(src) + new /obj/item/clothing/shoes/sneakers/brown (src) + new /obj/item/cartridge/cmo(src) + new /obj/item/radio/headset/heads/cmo(src) + new /obj/item/megaphone/command(src) + new /obj/item/defibrillator/compact/loaded(src) + new /obj/item/clothing/gloves/color/latex/nitrile(src) + new /obj/item/healthanalyzer/advanced(src) + new /obj/item/assembly/flash/handheld(src) + new /obj/item/reagent_containers/hypospray/CMO(src) + new /obj/item/autosurgeon/cmo(src) + new /obj/item/door_remote/chief_medical_officer(src) + new /obj/item/clothing/neck/petcollar(src) + new /obj/item/pet_carrier(src) + new /obj/item/wallframe/defib_mount(src) + new /obj/item/circuitboard/machine/techfab/department/medical(src) + new /obj/item/storage/photo_album/CMO(src) + +/obj/structure/closet/secure_closet/animal + name = "animal control" + req_access = list(ACCESS_SURGERY) + +/obj/structure/closet/secure_closet/animal/PopulateContents() + ..() + new /obj/item/assembly/signaler(src) + for(var/i in 1 to 3) + new /obj/item/electropack(src) + +/obj/structure/closet/secure_closet/chemical + name = "chemical closet" + desc = "Store dangerous chemicals in here." + req_access = list(ACCESS_CHEMISTRY) + icon_door = "chemical" + +/obj/structure/closet/secure_closet/chemical/PopulateContents() + ..() + new /obj/item/storage/box/pillbottles(src) + new /obj/item/storage/box/pillbottles(src) + new /obj/item/storage/box/medigels(src) + new /obj/item/storage/box/medigels(src) + +/obj/structure/closet/secure_closet/chemical/heisenberg //contains one of each beaker, syringe etc. + name = "advanced chemical closet" + +/obj/structure/closet/secure_closet/chemical/heisenberg/PopulateContents() + ..() + new /obj/item/reagent_containers/dropper(src) + new /obj/item/reagent_containers/dropper(src) + new /obj/item/storage/box/syringes/variety(src) + new /obj/item/storage/box/beakers/variety(src) + new /obj/item/clothing/head/beret/chem(src) // Wasp edit - Berets + new /obj/item/clothing/glasses/science/prescription(src) // Wasp Edit - Prescription HUDs + new /obj/item/clothing/glasses/science(src) diff --git a/code/game/objects/structures/crates_lockers/closets/secure/personal.dm b/code/game/objects/structures/crates_lockers/closets/secure/personal.dm index 1253c55a3fdb..40f057f5f895 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/personal.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/personal.dm @@ -1,59 +1,59 @@ -/obj/structure/closet/secure_closet/personal - desc = "It's a secure locker for personnel. The first card swiped gains control." - name = "personal closet" - req_access = list(ACCESS_ALL_PERSONAL_LOCKERS) - var/registered_name = null - -/obj/structure/closet/secure_closet/personal/PopulateContents() - ..() - if(prob(40)) - new /obj/item/storage/backpack/duffelbag(src) - if(prob(40)) - new /obj/item/storage/backpack(src) - if(prob(40)) - new /obj/item/storage/backpack/messenger(src) - else - new /obj/item/storage/backpack/satchel(src) - new /obj/item/radio/headset( src ) - -/obj/structure/closet/secure_closet/personal/patient - name = "patient's closet" - -/obj/structure/closet/secure_closet/personal/patient/PopulateContents() - new /obj/item/clothing/under/color/white( src ) - new /obj/item/clothing/shoes/sneakers/white( src ) - -/obj/structure/closet/secure_closet/personal/cabinet - icon_state = "cabinet" - resistance_flags = FLAMMABLE - max_integrity = 70 - open_sound = 'sound/machines/wooden_closet_open.ogg' - close_sound = 'sound/machines/wooden_closet_close.ogg' - open_sound_volume = 25 - close_sound_volume = 50 - -/obj/structure/closet/secure_closet/personal/cabinet/PopulateContents() - new /obj/item/storage/backpack/satchel/leather/withwallet( src ) - new /obj/item/instrument/piano_synth(src) - new /obj/item/radio/headset( src ) - -/obj/structure/closet/secure_closet/personal/attackby(obj/item/W, mob/user, params) - var/obj/item/card/id/I = W.GetID() - if(istype(I)) - if(broken) - to_chat(user, "It appears to be broken.") - return - if(!I || !I.registered_name) - return - if(allowed(user) || !registered_name || (istype(I) && (registered_name == I.registered_name))) - //they can open all lockers, or nobody owns this, or they own this locker - locked = !locked - update_icon() - - if(!registered_name) - registered_name = I.registered_name - desc = "Owned by [I.registered_name]." - else - to_chat(user, "Access Denied.") - else - return ..() +/obj/structure/closet/secure_closet/personal + desc = "It's a secure locker for personnel. The first card swiped gains control." + name = "personal closet" + req_access = list(ACCESS_ALL_PERSONAL_LOCKERS) + var/registered_name = null + +/obj/structure/closet/secure_closet/personal/PopulateContents() + ..() + if(prob(40)) + new /obj/item/storage/backpack/duffelbag(src) + if(prob(40)) + new /obj/item/storage/backpack(src) + if(prob(40)) + new /obj/item/storage/backpack/messenger(src) + else + new /obj/item/storage/backpack/satchel(src) + new /obj/item/radio/headset( src ) + +/obj/structure/closet/secure_closet/personal/patient + name = "patient's closet" + +/obj/structure/closet/secure_closet/personal/patient/PopulateContents() + new /obj/item/clothing/under/color/white( src ) + new /obj/item/clothing/shoes/sneakers/white( src ) + +/obj/structure/closet/secure_closet/personal/cabinet + icon_state = "cabinet" + resistance_flags = FLAMMABLE + max_integrity = 70 + open_sound = 'sound/machines/wooden_closet_open.ogg' + close_sound = 'sound/machines/wooden_closet_close.ogg' + open_sound_volume = 25 + close_sound_volume = 50 + +/obj/structure/closet/secure_closet/personal/cabinet/PopulateContents() + new /obj/item/storage/backpack/satchel/leather/withwallet( src ) + new /obj/item/instrument/piano_synth(src) + new /obj/item/radio/headset( src ) + +/obj/structure/closet/secure_closet/personal/attackby(obj/item/W, mob/user, params) + var/obj/item/card/id/I = W.GetID() + if(istype(I)) + if(broken) + to_chat(user, "It appears to be broken.") + return + if(!I || !I.registered_name) + return + if(allowed(user) || !registered_name || (istype(I) && (registered_name == I.registered_name))) + //they can open all lockers, or nobody owns this, or they own this locker + locked = !locked + update_icon() + + if(!registered_name) + registered_name = I.registered_name + desc = "Owned by [I.registered_name]." + else + to_chat(user, "Access Denied.") + else + return ..() diff --git a/code/game/objects/structures/crates_lockers/closets/secure/scientist.dm b/code/game/objects/structures/crates_lockers/closets/secure/scientist.dm index 6988233e483c..e5e946c9f7d0 100755 --- a/code/game/objects/structures/crates_lockers/closets/secure/scientist.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/scientist.dm @@ -1,32 +1,32 @@ -/obj/structure/closet/secure_closet/RD - name = "\proper research director's locker" - req_access = list(ACCESS_RD) - icon_state = "rd" - -/obj/structure/closet/secure_closet/RD/PopulateContents() - ..() - new /obj/item/clothing/head/beret/rd(src) // Waspstation edit - Berets - new /obj/item/card/id/departmental_budget/sci(src) // WaspStation Edit - Budget Cards - new /obj/item/clothing/under/rank/command(src) // WaspStation edit - better command uniforms - new /obj/item/clothing/neck/cloak/rd(src) - new /obj/item/clothing/suit/bio_suit/scientist(src) - new /obj/item/clothing/head/bio_hood/scientist(src) - new /obj/item/clothing/suit/toggle/labcoat(src) - new /obj/item/clothing/under/rank/rnd/research_director(src) - new /obj/item/clothing/under/rank/rnd/research_director/skirt(src) - new /obj/item/clothing/under/rank/rnd/research_director/alt(src) - new /obj/item/clothing/under/rank/rnd/research_director/alt/skirt(src) - new /obj/item/clothing/under/rank/rnd/research_director/turtleneck(src) - new /obj/item/clothing/under/rank/rnd/research_director/turtleneck/skirt(src) - new /obj/item/clothing/shoes/sneakers/brown(src) - new /obj/item/cartridge/rd(src) - new /obj/item/radio/headset/heads/rd(src) - new /obj/item/megaphone/command(src) - new /obj/item/storage/lockbox/medal/sci(src) - new /obj/item/clothing/suit/armor/reactive/teleport(src) - new /obj/item/assembly/flash/handheld(src) - new /obj/item/laser_pointer(src) - new /obj/item/door_remote/research_director(src) - new /obj/item/circuitboard/machine/techfab/department/science(src) - new /obj/item/storage/photo_album/RD(src) - +/obj/structure/closet/secure_closet/RD + name = "\proper research director's locker" + req_access = list(ACCESS_RD) + icon_state = "rd" + +/obj/structure/closet/secure_closet/RD/PopulateContents() + ..() + new /obj/item/clothing/head/beret/rd(src) // Waspstation edit - Berets + new /obj/item/card/id/departmental_budget/sci(src) // WaspStation Edit - Budget Cards + new /obj/item/clothing/under/rank/command(src) // WaspStation edit - better command uniforms + new /obj/item/clothing/neck/cloak/rd(src) + new /obj/item/clothing/suit/bio_suit/scientist(src) + new /obj/item/clothing/head/bio_hood/scientist(src) + new /obj/item/clothing/suit/toggle/labcoat(src) + new /obj/item/clothing/under/rank/rnd/research_director(src) + new /obj/item/clothing/under/rank/rnd/research_director/skirt(src) + new /obj/item/clothing/under/rank/rnd/research_director/alt(src) + new /obj/item/clothing/under/rank/rnd/research_director/alt/skirt(src) + new /obj/item/clothing/under/rank/rnd/research_director/turtleneck(src) + new /obj/item/clothing/under/rank/rnd/research_director/turtleneck/skirt(src) + new /obj/item/clothing/shoes/sneakers/brown(src) + new /obj/item/cartridge/rd(src) + new /obj/item/radio/headset/heads/rd(src) + new /obj/item/megaphone/command(src) + new /obj/item/storage/lockbox/medal/sci(src) + new /obj/item/clothing/suit/armor/reactive/teleport(src) + new /obj/item/assembly/flash/handheld(src) + new /obj/item/laser_pointer(src) + new /obj/item/door_remote/research_director(src) + new /obj/item/circuitboard/machine/techfab/department/science(src) + new /obj/item/storage/photo_album/RD(src) + diff --git a/code/game/objects/structures/crates_lockers/closets/secure/secure_closets.dm b/code/game/objects/structures/crates_lockers/closets/secure/secure_closets.dm index c555c5985125..2a12cfa870aa 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/secure_closets.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/secure_closets.dm @@ -1,9 +1,9 @@ -/obj/structure/closet/secure_closet - name = "secure locker" - desc = "It's a card-locked storage unit." - locked = TRUE - icon_state = "secure" - max_integrity = 250 - armor = list("melee" = 30, "bullet" = 50, "laser" = 50, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80) - secure = TRUE - damage_deflection = 20 +/obj/structure/closet/secure_closet + name = "secure locker" + desc = "It's a card-locked storage unit." + locked = TRUE + icon_state = "secure" + max_integrity = 250 + armor = list("melee" = 30, "bullet" = 50, "laser" = 50, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80) + secure = TRUE + damage_deflection = 20 diff --git a/code/game/objects/structures/crates_lockers/closets/secure/security.dm b/code/game/objects/structures/crates_lockers/closets/secure/security.dm index f5eea5076783..1448f80f6dbc 100755 --- a/code/game/objects/structures/crates_lockers/closets/secure/security.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/security.dm @@ -1,355 +1,355 @@ -/obj/structure/closet/secure_closet/captains - name = "\proper captain's locker" - req_access = list(ACCESS_CAPTAIN) - icon_state = "cap" - -/obj/structure/closet/secure_closet/captains/PopulateContents() - ..() - //WaspStation Begin - new /obj/item/clothing/head/beret/captain(src) //Berets - new /obj/item/card/id/departmental_budget/civ(src) //Budget Cards - new /obj/item/storage/backpack/messenger/com(src) //Messenger Bags - //WaspStation End - new /obj/item/clothing/suit/hooded/wintercoat/captain(src) - if(prob(33)) - new /obj/item/storage/backpack/captain(src) - else if(prob(50)) - new /obj/item/storage/backpack/satchel/cap(src) - new /obj/item/storage/backpack/duffelbag/captain(src) - new /obj/item/clothing/neck/cloak/cap(src) - new /obj/item/clothing/neck/petcollar(src) - new /obj/item/pet_carrier(src) - new /obj/item/clothing/shoes/sneakers/brown(src) - new /obj/item/clothing/under/rank/command/captain(src) - new /obj/item/clothing/under/rank/command/captain/skirt(src) - new /obj/item/clothing/suit/armor/vest/capcarapace(src) - new /obj/item/clothing/head/caphat(src) - new /obj/item/clothing/under/rank/command/captain/parade(src) - new /obj/item/clothing/suit/armor/vest/capcarapace/alt(src) - new /obj/item/clothing/head/caphat/parade(src) - new /obj/item/clothing/suit/captunic(src) - new /obj/item/clothing/head/crown/fancy(src) - new /obj/item/cartridge/captain(src) - new /obj/item/storage/box/silver_ids(src) - new /obj/item/radio/headset/heads/captain/alt(src) - new /obj/item/radio/headset/heads/captain(src) - new /obj/item/clothing/glasses/sunglasses/gar/supergar(src) - new /obj/item/clothing/gloves/color/captain(src) - new /obj/item/storage/belt/sabre(src) - new /obj/item/gun/energy/e_gun(src) - new /obj/item/door_remote/captain(src) - new /obj/item/card/id/captains_spare(src) - new /obj/item/storage/photo_album/Captain(src) - -/obj/structure/closet/secure_closet/head_of_personnel - name = "\proper head of personnel's locker" - req_access = list(ACCESS_HOP) - icon_state = "hop" - -/obj/structure/closet/secure_closet/head_of_personnel/PopulateContents() - ..() - new /obj/item/card/id/departmental_budget/srv(src) //WaspStation Edit - Budget Cards - new /obj/item/clothing/neck/cloak/head_of_personnel(src) - new /obj/item/storage/lockbox/medal/service(src) - new /obj/item/clothing/head/beret/hop(src) //Waspstation edit - More Berets - new /obj/item/clothing/under/rank/command/head_of_personnel(src) //WaspStation Edit - Better Command Uniforms - new /obj/item/clothing/under/rank/command/head_of_personnel/skirt(src) //WaspStation Edit - Better Command Uniforms - new /obj/item/clothing/head/hopcap(src) - new /obj/item/cartridge/head_of_personnel(src) - new /obj/item/radio/headset/heads/head_of_personnel(src) - new /obj/item/clothing/shoes/sneakers/brown(src) - new /obj/item/storage/box/ids(src) - new /obj/item/storage/box/ids(src) - new /obj/item/megaphone/command(src) - new /obj/item/clothing/suit/armor/vest/alt(src) - new /obj/item/assembly/flash/handheld(src) - new /obj/item/clothing/glasses/sunglasses(src) - new /obj/item/gun/energy/e_gun/mini(src) //WaspStation Edit - Gives HoP a mini egun - new /obj/item/clothing/neck/petcollar(src) - new /obj/item/pet_carrier(src) - new /obj/item/door_remote/civilian(src) - new /obj/item/circuitboard/machine/techfab/department/service(src) - new /obj/item/storage/photo_album/HoP(src) - -/obj/structure/closet/secure_closet/hos - name = "\proper head of security's locker" - req_access = list(ACCESS_HOS) - icon_state = "hos" - -/obj/structure/closet/secure_closet/hos/PopulateContents() - ..() - new /obj/item/card/id/departmental_budget/sec(src) //WaspStation edit - budget card - new /obj/item/storage/box/deputy(src) // WaspStation edit - Small QoL Brig additions - new /obj/item/clothing/neck/cloak/hos(src) - new /obj/item/clothing/under/rank/command(src) // WaspStation edit - better command uniforms - new /obj/item/cartridge/hos(src) - new /obj/item/radio/headset/heads/hos/alt(src) // WaspStation edit - Small QoL Brig additions - new /obj/item/radio/headset/heads/hos(src) - new /obj/item/clothing/under/rank/security/head_of_security/parade/female(src) - new /obj/item/clothing/under/rank/security/head_of_security/parade(src) - new /obj/item/clothing/suit/armor/vest/leather(src) - new /obj/item/clothing/suit/armor/hos/trenchcoat(src) // WaspStation edit - Small QoL Brig additions - new /obj/item/clothing/suit/armor/hos(src) - new /obj/item/clothing/under/rank/security/head_of_security/skirt(src) - new /obj/item/clothing/under/rank/security/head_of_security/alt(src) - new /obj/item/clothing/under/rank/security/head_of_security/alt/skirt(src) - new /obj/item/clothing/head/HoS(src) - new /obj/item/clothing/glasses/hud/security/sunglasses/eyepatch(src) - new /obj/item/clothing/glasses/hud/security/sunglasses/gars/supergars(src) - //new /obj/item/clothing/under/rank/security/head_of_security/grey(src) - new /obj/item/clothing/under/rank/security/head_of_security/white(src)//WaspStation Edit - Better security jumpsuit sprites - new /obj/item/storage/lockbox/medal/sec(src) - new /obj/item/megaphone/sec(src) - new /obj/item/holosign_creator/security(src) - new /obj/item/storage/lockbox/loyalty(src) - new /obj/item/clothing/mask/gas/sechailer/swat(src) - new /obj/item/storage/box/flashbangs(src) - new /obj/item/shield/riot/tele(src) - new /obj/item/storage/belt/security/full(src) - new /obj/item/gun/energy/e_gun/hos(src) - new /obj/item/gun/ballistic/automatic/pistol/commander(src) // Waspstation edit - free lethals - new /obj/item/pinpointer/nuke(src) - new /obj/item/circuitboard/machine/techfab/department/security(src) - new /obj/item/storage/photo_album/HoS(src) - -/obj/structure/closet/secure_closet/warden - name = "\proper warden's locker" - req_access = list(ACCESS_ARMORY) - icon_state = "warden" - -/obj/structure/closet/secure_closet/warden/PopulateContents() - ..() - new /obj/item/radio/headset/headset_sec(src) - new /obj/item/clothing/suit/armor/vest/warden(src) - new /obj/item/clothing/head/warden(src) - new /obj/item/clothing/head/warden/drill(src) - new /obj/item/clothing/head/beret/sec/navywarden(src) - new /obj/item/clothing/head/beret/sec/navywarden/black(src) - new /obj/item/clothing/suit/armor/vest/warden/alt(src) - new /obj/item/clothing/under/rank/security/warden/formal(src) - new /obj/item/clothing/under/rank/security/warden/skirt(src) - new /obj/item/clothing/glasses/hud/security/sunglasses(src) - new /obj/item/holosign_creator/security(src) - new /obj/item/clothing/mask/gas/sechailer(src) - new /obj/item/storage/box/zipties(src) - new /obj/item/storage/box/flashbangs(src) - new /obj/item/storage/belt/security/full(src) - new /obj/item/flashlight/seclite(src) - new /obj/item/megaphone/sec(src) // WaspStation edit - Small QoL Brig additions - new /obj/item/clothing/gloves/krav_maga/sec(src) - new /obj/item/door_remote/head_of_security(src) - new /obj/item/gun/ballistic/shotgun/automatic/combat/compact(src) - new /obj/item/gun/ballistic/automatic/pistol/commander(src) // Waspstation edit - free lethals - -/obj/structure/closet/secure_closet/security - name = "security officer's locker" - req_access = list(ACCESS_SECURITY) - icon_state = "sec" - -/obj/structure/closet/secure_closet/security/PopulateContents() - ..() - new /obj/item/clothing/suit/armor/vest(src) - new /obj/item/clothing/head/helmet/sec(src) - new /obj/item/radio/headset/headset_sec(src) - new /obj/item/radio/headset/headset_sec/alt(src) - //Waspstation begin - Prescription HUDs - if(prob(75)) - new /obj/item/clothing/glasses/hud/security/sunglasses(src) - else - new /obj/item/clothing/glasses/hud/security/prescription(src) - //Waspstation end - new /obj/item/flashlight/seclite(src) - -/obj/structure/closet/secure_closet/security/sec - -/obj/structure/closet/secure_closet/security/sec/PopulateContents() - ..() - new /obj/item/storage/belt/security/full(src) - new /obj/item/gun/ballistic/automatic/pistol/commander/no_mag(src) // Waspstation edit - free lethals - -/obj/structure/closet/secure_closet/security/cargo - -/obj/structure/closet/secure_closet/security/cargo/PopulateContents() - ..() - new /obj/item/clothing/accessory/armband/cargo(src) - new /obj/item/encryptionkey/headset_cargo(src) - -/obj/structure/closet/secure_closet/security/engine - -/obj/structure/closet/secure_closet/security/engine/PopulateContents() - ..() - new /obj/item/clothing/accessory/armband/engine(src) - new /obj/item/encryptionkey/headset_eng(src) - -/obj/structure/closet/secure_closet/security/science - -/obj/structure/closet/secure_closet/security/science/PopulateContents() - ..() - new /obj/item/clothing/accessory/armband/science(src) - new /obj/item/encryptionkey/headset_sci(src) - -/obj/structure/closet/secure_closet/security/med - -/obj/structure/closet/secure_closet/security/med/PopulateContents() - ..() - new /obj/item/clothing/accessory/armband/medblue(src) - new /obj/item/encryptionkey/headset_med(src) - -/obj/structure/closet/secure_closet/detective - name = "\improper detective's cabinet" - req_access = list(ACCESS_FORENSICS_LOCKERS) - icon_state = "cabinet" - resistance_flags = FLAMMABLE - max_integrity = 70 - open_sound = 'sound/machines/wooden_closet_open.ogg' - close_sound = 'sound/machines/wooden_closet_close.ogg' - -/obj/structure/closet/secure_closet/detective/PopulateContents() - ..() - new /obj/item/storage/box/evidence(src) - new /obj/item/radio/headset/headset_sec(src) - new /obj/item/detective_scanner(src) - new /obj/item/flashlight/seclite(src) - new /obj/item/holosign_creator/security(src) - new /obj/item/reagent_containers/spray/pepper(src) - new /obj/item/clothing/suit/armor/vest/det_suit(src) - new /obj/item/clothing/accessory/holster/detective(src) //WaspStation Edit - Made Holsters Accessories - new /obj/item/pinpointer/crew(src) - new /obj/item/binoculars(src) - new /obj/item/clothing/neck/tie/red(src) - new /obj/item/clothing/neck/tie/black(src) - new /obj/item/clothing/neck/tie/detective(src) - new /obj/item/storage/box/rxglasses/spyglasskit(src) - -/obj/structure/closet/secure_closet/injection - name = "lethal injections" - req_access = list(ACCESS_HOS) - -/obj/structure/closet/secure_closet/injection/PopulateContents() - ..() - for(var/i in 1 to 5) - new /obj/item/reagent_containers/syringe/lethal/execution(src) - -/obj/structure/closet/secure_closet/brig - name = "brig locker" - req_access = list(ACCESS_BRIG) - anchored = TRUE - var/id = null - -/obj/structure/closet/secure_closet/evidence - anchored = TRUE - name = "Secure Evidence Closet" - req_access_txt = "0" - req_one_access_txt = list(ACCESS_ARMORY, ACCESS_FORENSICS_LOCKERS) - -/obj/structure/closet/secure_closet/brig/PopulateContents() - ..() - new /obj/item/clothing/under/rank/prisoner( src ) - new /obj/item/clothing/under/rank/prisoner/skirt( src ) - new /obj/item/clothing/shoes/sneakers/orange( src ) - -/obj/structure/closet/secure_closet/courtroom - name = "courtroom locker" - req_access = list(ACCESS_COURT) - -/obj/structure/closet/secure_closet/courtroom/PopulateContents() - ..() - new /obj/item/clothing/shoes/sneakers/brown(src) - for(var/i in 1 to 3) - new /obj/item/paper/fluff/jobs/security/court_judgement (src) - new /obj/item/pen (src) - new /obj/item/clothing/suit/judgerobe (src) - new /obj/item/clothing/head/powdered_wig (src) - new /obj/item/storage/briefcase(src) - -/obj/structure/closet/secure_closet/contraband/armory - anchored = TRUE - name = "Contraband Locker" - req_access = list(ACCESS_ARMORY) - -/obj/structure/closet/secure_closet/contraband/heads - anchored = TRUE - name = "Contraband Locker" - req_access = list(ACCESS_HEADS) - -/obj/structure/closet/secure_closet/armory1 - name = "armory armor locker" - req_access = list(ACCESS_ARMORY) - icon_state = "armory" - -/obj/structure/closet/secure_closet/armory1/PopulateContents() - ..() - new /obj/item/clothing/suit/hooded/ablative(src) - for(var/i in 1 to 3) - new /obj/item/clothing/suit/armor/riot(src) - for(var/i in 1 to 3) - new /obj/item/clothing/head/helmet/riot(src) - for(var/i in 1 to 3) - new /obj/item/shield/riot(src) - -/obj/structure/closet/secure_closet/armory2 - name = "armory ballistics locker" - req_access = list(ACCESS_ARMORY) - icon_state = "armory" - -/obj/structure/closet/secure_closet/armory2/PopulateContents() - ..() - new /obj/item/storage/box/firingpins(src) - for(var/i in 1 to 3) - new /obj/item/storage/box/rubbershot(src) - for(var/i in 1 to 3) - new /obj/item/gun/ballistic/shotgun/riot(src) - -/obj/structure/closet/secure_closet/armory3 - name = "armory energy gun locker" - req_access = list(ACCESS_ARMORY) - icon_state = "armory" - -/obj/structure/closet/secure_closet/armory3/PopulateContents() - ..() - new /obj/item/storage/box/firingpins(src) - new /obj/item/gun/energy/ionrifle(src) - for(var/i in 1 to 3) - new /obj/item/gun/energy/e_gun(src) - for(var/i in 1 to 3) - new /obj/item/gun/energy/laser(src) - -/obj/structure/closet/secure_closet/tac - name = "armory tac locker" - req_access = list(ACCESS_ARMORY) - icon_state = "tac" - -/obj/structure/closet/secure_closet/tac/PopulateContents() - ..() - new /obj/item/gun/ballistic/automatic/wt550(src) - new /obj/item/clothing/head/helmet/alt(src) - new /obj/item/clothing/mask/gas/sechailer(src) - new /obj/item/clothing/suit/armor/bulletproof(src) - -/obj/structure/closet/secure_closet/lethalshots - name = "lethal ammunition" - req_access = list(ACCESS_ARMORY) - icon_state = "tac" - -/obj/structure/closet/secure_closet/lethalshots/PopulateContents() - ..() - for(var/i in 1 to 3) - new /obj/item/storage/box/lethalshot(src) - new /obj/item/ammo_box/magazine/co9mm(src) // Waspstation edit - begin - better safe than sorry - new /obj/item/ammo_box/magazine/co9mm(src) - new /obj/item/ammo_box/magazine/co9mm(src) - new /obj/item/ammo_box/magazine/co9mm(src) // Waspstation edit - end - -/obj/structure/closet/secure_closet/labor_camp_security - name = "labor camp security locker" - req_access = list(ACCESS_SECURITY) - icon_state = "sec" - -/obj/structure/closet/secure_closet/labor_camp_security/PopulateContents() - ..() - new /obj/item/clothing/suit/armor/vest(src) - new /obj/item/clothing/head/helmet/sec(src) - new /obj/item/clothing/under/rank/security/officer(src) - new /obj/item/clothing/under/rank/security/officer/skirt(src) - new /obj/item/clothing/glasses/hud/security/sunglasses(src) - new /obj/item/flashlight/seclite(src) +/obj/structure/closet/secure_closet/captains + name = "\proper captain's locker" + req_access = list(ACCESS_CAPTAIN) + icon_state = "cap" + +/obj/structure/closet/secure_closet/captains/PopulateContents() + ..() + //WaspStation Begin + new /obj/item/clothing/head/beret/captain(src) //Berets + new /obj/item/card/id/departmental_budget/civ(src) //Budget Cards + new /obj/item/storage/backpack/messenger/com(src) //Messenger Bags + //WaspStation End + new /obj/item/clothing/suit/hooded/wintercoat/captain(src) + if(prob(33)) + new /obj/item/storage/backpack/captain(src) + else if(prob(50)) + new /obj/item/storage/backpack/satchel/cap(src) + new /obj/item/storage/backpack/duffelbag/captain(src) + new /obj/item/clothing/neck/cloak/cap(src) + new /obj/item/clothing/neck/petcollar(src) + new /obj/item/pet_carrier(src) + new /obj/item/clothing/shoes/sneakers/brown(src) + new /obj/item/clothing/under/rank/command/captain(src) + new /obj/item/clothing/under/rank/command/captain/skirt(src) + new /obj/item/clothing/suit/armor/vest/capcarapace(src) + new /obj/item/clothing/head/caphat(src) + new /obj/item/clothing/under/rank/command/captain/parade(src) + new /obj/item/clothing/suit/armor/vest/capcarapace/alt(src) + new /obj/item/clothing/head/caphat/parade(src) + new /obj/item/clothing/suit/captunic(src) + new /obj/item/clothing/head/crown/fancy(src) + new /obj/item/cartridge/captain(src) + new /obj/item/storage/box/silver_ids(src) + new /obj/item/radio/headset/heads/captain/alt(src) + new /obj/item/radio/headset/heads/captain(src) + new /obj/item/clothing/glasses/sunglasses/gar/supergar(src) + new /obj/item/clothing/gloves/color/captain(src) + new /obj/item/storage/belt/sabre(src) + new /obj/item/gun/energy/e_gun(src) + new /obj/item/door_remote/captain(src) + new /obj/item/card/id/captains_spare(src) + new /obj/item/storage/photo_album/Captain(src) + +/obj/structure/closet/secure_closet/head_of_personnel + name = "\proper head of personnel's locker" + req_access = list(ACCESS_HOP) + icon_state = "hop" + +/obj/structure/closet/secure_closet/head_of_personnel/PopulateContents() + ..() + new /obj/item/card/id/departmental_budget/srv(src) //WaspStation Edit - Budget Cards + new /obj/item/clothing/neck/cloak/head_of_personnel(src) + new /obj/item/storage/lockbox/medal/service(src) + new /obj/item/clothing/head/beret/hop(src) //Waspstation edit - More Berets + new /obj/item/clothing/under/rank/command/head_of_personnel(src) //WaspStation Edit - Better Command Uniforms + new /obj/item/clothing/under/rank/command/head_of_personnel/skirt(src) //WaspStation Edit - Better Command Uniforms + new /obj/item/clothing/head/hopcap(src) + new /obj/item/cartridge/head_of_personnel(src) + new /obj/item/radio/headset/heads/head_of_personnel(src) + new /obj/item/clothing/shoes/sneakers/brown(src) + new /obj/item/storage/box/ids(src) + new /obj/item/storage/box/ids(src) + new /obj/item/megaphone/command(src) + new /obj/item/clothing/suit/armor/vest/alt(src) + new /obj/item/assembly/flash/handheld(src) + new /obj/item/clothing/glasses/sunglasses(src) + new /obj/item/gun/energy/e_gun/mini(src) //WaspStation Edit - Gives HoP a mini egun + new /obj/item/clothing/neck/petcollar(src) + new /obj/item/pet_carrier(src) + new /obj/item/door_remote/civilian(src) + new /obj/item/circuitboard/machine/techfab/department/service(src) + new /obj/item/storage/photo_album/HoP(src) + +/obj/structure/closet/secure_closet/hos + name = "\proper head of security's locker" + req_access = list(ACCESS_HOS) + icon_state = "hos" + +/obj/structure/closet/secure_closet/hos/PopulateContents() + ..() + new /obj/item/card/id/departmental_budget/sec(src) //WaspStation edit - budget card + new /obj/item/storage/box/deputy(src) // WaspStation edit - Small QoL Brig additions + new /obj/item/clothing/neck/cloak/hos(src) + new /obj/item/clothing/under/rank/command(src) // WaspStation edit - better command uniforms + new /obj/item/cartridge/hos(src) + new /obj/item/radio/headset/heads/hos/alt(src) // WaspStation edit - Small QoL Brig additions + new /obj/item/radio/headset/heads/hos(src) + new /obj/item/clothing/under/rank/security/head_of_security/parade/female(src) + new /obj/item/clothing/under/rank/security/head_of_security/parade(src) + new /obj/item/clothing/suit/armor/vest/leather(src) + new /obj/item/clothing/suit/armor/hos/trenchcoat(src) // WaspStation edit - Small QoL Brig additions + new /obj/item/clothing/suit/armor/hos(src) + new /obj/item/clothing/under/rank/security/head_of_security/skirt(src) + new /obj/item/clothing/under/rank/security/head_of_security/alt(src) + new /obj/item/clothing/under/rank/security/head_of_security/alt/skirt(src) + new /obj/item/clothing/head/HoS(src) + new /obj/item/clothing/glasses/hud/security/sunglasses/eyepatch(src) + new /obj/item/clothing/glasses/hud/security/sunglasses/gars/supergars(src) + //new /obj/item/clothing/under/rank/security/head_of_security/grey(src) + new /obj/item/clothing/under/rank/security/head_of_security/white(src)//WaspStation Edit - Better security jumpsuit sprites + new /obj/item/storage/lockbox/medal/sec(src) + new /obj/item/megaphone/sec(src) + new /obj/item/holosign_creator/security(src) + new /obj/item/storage/lockbox/loyalty(src) + new /obj/item/clothing/mask/gas/sechailer/swat(src) + new /obj/item/storage/box/flashbangs(src) + new /obj/item/shield/riot/tele(src) + new /obj/item/storage/belt/security/full(src) + new /obj/item/gun/energy/e_gun/hos(src) + new /obj/item/gun/ballistic/automatic/pistol/commander(src) // Waspstation edit - free lethals + new /obj/item/pinpointer/nuke(src) + new /obj/item/circuitboard/machine/techfab/department/security(src) + new /obj/item/storage/photo_album/HoS(src) + +/obj/structure/closet/secure_closet/warden + name = "\proper warden's locker" + req_access = list(ACCESS_ARMORY) + icon_state = "warden" + +/obj/structure/closet/secure_closet/warden/PopulateContents() + ..() + new /obj/item/radio/headset/headset_sec(src) + new /obj/item/clothing/suit/armor/vest/warden(src) + new /obj/item/clothing/head/warden(src) + new /obj/item/clothing/head/warden/drill(src) + new /obj/item/clothing/head/beret/sec/navywarden(src) + new /obj/item/clothing/head/beret/sec/navywarden/black(src) + new /obj/item/clothing/suit/armor/vest/warden/alt(src) + new /obj/item/clothing/under/rank/security/warden/formal(src) + new /obj/item/clothing/under/rank/security/warden/skirt(src) + new /obj/item/clothing/glasses/hud/security/sunglasses(src) + new /obj/item/holosign_creator/security(src) + new /obj/item/clothing/mask/gas/sechailer(src) + new /obj/item/storage/box/zipties(src) + new /obj/item/storage/box/flashbangs(src) + new /obj/item/storage/belt/security/full(src) + new /obj/item/flashlight/seclite(src) + new /obj/item/megaphone/sec(src) // WaspStation edit - Small QoL Brig additions + new /obj/item/clothing/gloves/krav_maga/sec(src) + new /obj/item/door_remote/head_of_security(src) + new /obj/item/gun/ballistic/shotgun/automatic/combat/compact(src) + new /obj/item/gun/ballistic/automatic/pistol/commander(src) // Waspstation edit - free lethals + +/obj/structure/closet/secure_closet/security + name = "security officer's locker" + req_access = list(ACCESS_SECURITY) + icon_state = "sec" + +/obj/structure/closet/secure_closet/security/PopulateContents() + ..() + new /obj/item/clothing/suit/armor/vest(src) + new /obj/item/clothing/head/helmet/sec(src) + new /obj/item/radio/headset/headset_sec(src) + new /obj/item/radio/headset/headset_sec/alt(src) + //Waspstation begin - Prescription HUDs + if(prob(75)) + new /obj/item/clothing/glasses/hud/security/sunglasses(src) + else + new /obj/item/clothing/glasses/hud/security/prescription(src) + //Waspstation end + new /obj/item/flashlight/seclite(src) + +/obj/structure/closet/secure_closet/security/sec + +/obj/structure/closet/secure_closet/security/sec/PopulateContents() + ..() + new /obj/item/storage/belt/security/full(src) + new /obj/item/gun/ballistic/automatic/pistol/commander/no_mag(src) // Waspstation edit - free lethals + +/obj/structure/closet/secure_closet/security/cargo + +/obj/structure/closet/secure_closet/security/cargo/PopulateContents() + ..() + new /obj/item/clothing/accessory/armband/cargo(src) + new /obj/item/encryptionkey/headset_cargo(src) + +/obj/structure/closet/secure_closet/security/engine + +/obj/structure/closet/secure_closet/security/engine/PopulateContents() + ..() + new /obj/item/clothing/accessory/armband/engine(src) + new /obj/item/encryptionkey/headset_eng(src) + +/obj/structure/closet/secure_closet/security/science + +/obj/structure/closet/secure_closet/security/science/PopulateContents() + ..() + new /obj/item/clothing/accessory/armband/science(src) + new /obj/item/encryptionkey/headset_sci(src) + +/obj/structure/closet/secure_closet/security/med + +/obj/structure/closet/secure_closet/security/med/PopulateContents() + ..() + new /obj/item/clothing/accessory/armband/medblue(src) + new /obj/item/encryptionkey/headset_med(src) + +/obj/structure/closet/secure_closet/detective + name = "\improper detective's cabinet" + req_access = list(ACCESS_FORENSICS_LOCKERS) + icon_state = "cabinet" + resistance_flags = FLAMMABLE + max_integrity = 70 + open_sound = 'sound/machines/wooden_closet_open.ogg' + close_sound = 'sound/machines/wooden_closet_close.ogg' + +/obj/structure/closet/secure_closet/detective/PopulateContents() + ..() + new /obj/item/storage/box/evidence(src) + new /obj/item/radio/headset/headset_sec(src) + new /obj/item/detective_scanner(src) + new /obj/item/flashlight/seclite(src) + new /obj/item/holosign_creator/security(src) + new /obj/item/reagent_containers/spray/pepper(src) + new /obj/item/clothing/suit/armor/vest/det_suit(src) + new /obj/item/clothing/accessory/holster/detective(src) //WaspStation Edit - Made Holsters Accessories + new /obj/item/pinpointer/crew(src) + new /obj/item/binoculars(src) + new /obj/item/clothing/neck/tie/red(src) + new /obj/item/clothing/neck/tie/black(src) + new /obj/item/clothing/neck/tie/detective(src) + new /obj/item/storage/box/rxglasses/spyglasskit(src) + +/obj/structure/closet/secure_closet/injection + name = "lethal injections" + req_access = list(ACCESS_HOS) + +/obj/structure/closet/secure_closet/injection/PopulateContents() + ..() + for(var/i in 1 to 5) + new /obj/item/reagent_containers/syringe/lethal/execution(src) + +/obj/structure/closet/secure_closet/brig + name = "brig locker" + req_access = list(ACCESS_BRIG) + anchored = TRUE + var/id = null + +/obj/structure/closet/secure_closet/evidence + anchored = TRUE + name = "Secure Evidence Closet" + req_access_txt = "0" + req_one_access_txt = list(ACCESS_ARMORY, ACCESS_FORENSICS_LOCKERS) + +/obj/structure/closet/secure_closet/brig/PopulateContents() + ..() + new /obj/item/clothing/under/rank/prisoner( src ) + new /obj/item/clothing/under/rank/prisoner/skirt( src ) + new /obj/item/clothing/shoes/sneakers/orange( src ) + +/obj/structure/closet/secure_closet/courtroom + name = "courtroom locker" + req_access = list(ACCESS_COURT) + +/obj/structure/closet/secure_closet/courtroom/PopulateContents() + ..() + new /obj/item/clothing/shoes/sneakers/brown(src) + for(var/i in 1 to 3) + new /obj/item/paper/fluff/jobs/security/court_judgement (src) + new /obj/item/pen (src) + new /obj/item/clothing/suit/judgerobe (src) + new /obj/item/clothing/head/powdered_wig (src) + new /obj/item/storage/briefcase(src) + +/obj/structure/closet/secure_closet/contraband/armory + anchored = TRUE + name = "Contraband Locker" + req_access = list(ACCESS_ARMORY) + +/obj/structure/closet/secure_closet/contraband/heads + anchored = TRUE + name = "Contraband Locker" + req_access = list(ACCESS_HEADS) + +/obj/structure/closet/secure_closet/armory1 + name = "armory armor locker" + req_access = list(ACCESS_ARMORY) + icon_state = "armory" + +/obj/structure/closet/secure_closet/armory1/PopulateContents() + ..() + new /obj/item/clothing/suit/hooded/ablative(src) + for(var/i in 1 to 3) + new /obj/item/clothing/suit/armor/riot(src) + for(var/i in 1 to 3) + new /obj/item/clothing/head/helmet/riot(src) + for(var/i in 1 to 3) + new /obj/item/shield/riot(src) + +/obj/structure/closet/secure_closet/armory2 + name = "armory ballistics locker" + req_access = list(ACCESS_ARMORY) + icon_state = "armory" + +/obj/structure/closet/secure_closet/armory2/PopulateContents() + ..() + new /obj/item/storage/box/firingpins(src) + for(var/i in 1 to 3) + new /obj/item/storage/box/rubbershot(src) + for(var/i in 1 to 3) + new /obj/item/gun/ballistic/shotgun/riot(src) + +/obj/structure/closet/secure_closet/armory3 + name = "armory energy gun locker" + req_access = list(ACCESS_ARMORY) + icon_state = "armory" + +/obj/structure/closet/secure_closet/armory3/PopulateContents() + ..() + new /obj/item/storage/box/firingpins(src) + new /obj/item/gun/energy/ionrifle(src) + for(var/i in 1 to 3) + new /obj/item/gun/energy/e_gun(src) + for(var/i in 1 to 3) + new /obj/item/gun/energy/laser(src) + +/obj/structure/closet/secure_closet/tac + name = "armory tac locker" + req_access = list(ACCESS_ARMORY) + icon_state = "tac" + +/obj/structure/closet/secure_closet/tac/PopulateContents() + ..() + new /obj/item/gun/ballistic/automatic/wt550(src) + new /obj/item/clothing/head/helmet/alt(src) + new /obj/item/clothing/mask/gas/sechailer(src) + new /obj/item/clothing/suit/armor/bulletproof(src) + +/obj/structure/closet/secure_closet/lethalshots + name = "lethal ammunition" + req_access = list(ACCESS_ARMORY) + icon_state = "tac" + +/obj/structure/closet/secure_closet/lethalshots/PopulateContents() + ..() + for(var/i in 1 to 3) + new /obj/item/storage/box/lethalshot(src) + new /obj/item/ammo_box/magazine/co9mm(src) // Waspstation edit - begin - better safe than sorry + new /obj/item/ammo_box/magazine/co9mm(src) + new /obj/item/ammo_box/magazine/co9mm(src) + new /obj/item/ammo_box/magazine/co9mm(src) // Waspstation edit - end + +/obj/structure/closet/secure_closet/labor_camp_security + name = "labor camp security locker" + req_access = list(ACCESS_SECURITY) + icon_state = "sec" + +/obj/structure/closet/secure_closet/labor_camp_security/PopulateContents() + ..() + new /obj/item/clothing/suit/armor/vest(src) + new /obj/item/clothing/head/helmet/sec(src) + new /obj/item/clothing/under/rank/security/officer(src) + new /obj/item/clothing/under/rank/security/officer/skirt(src) + new /obj/item/clothing/glasses/hud/security/sunglasses(src) + new /obj/item/flashlight/seclite(src) diff --git a/code/game/objects/structures/crates_lockers/closets/syndicate.dm b/code/game/objects/structures/crates_lockers/closets/syndicate.dm index 8ed6af4d01bc..f1be43587393 100644 --- a/code/game/objects/structures/crates_lockers/closets/syndicate.dm +++ b/code/game/objects/structures/crates_lockers/closets/syndicate.dm @@ -1,121 +1,121 @@ -/obj/structure/closet/syndicate - name = "armory closet" - desc = "Why is this here?" - icon_state = "syndicate" - -/obj/structure/closet/syndicate/personal - desc = "It's a personal storage unit for operative gear." - -/obj/structure/closet/syndicate/personal/PopulateContents() - ..() - new /obj/item/clothing/under/syndicate(src) - new /obj/item/clothing/under/syndicate/skirt(src) - new /obj/item/clothing/shoes/sneakers/black(src) - new /obj/item/radio/headset/syndicate(src) - new /obj/item/ammo_box/magazine/m10mm(src) - new /obj/item/storage/belt/military(src) - new /obj/item/crowbar/syndie(src) - new /obj/item/clothing/glasses/night(src) - new /obj/item/clothing/accessory/holster/nukie(src) //WASPSTATION EDIT - HOLSTERS AS ACCESSORIES - -/obj/structure/closet/syndicate/nuclear - desc = "It's a storage unit for a Syndicate boarding party." - -/obj/structure/closet/syndicate/nuclear/PopulateContents() - for(var/i in 1 to 5) - new /obj/item/ammo_box/magazine/m10mm(src) - new /obj/item/storage/box/flashbangs(src) - new /obj/item/storage/box/teargas(src) - new /obj/item/storage/backpack/duffelbag/syndie/med(src) - new /obj/item/pda/syndicate(src) - -/obj/structure/closet/syndicate/resources - desc = "An old, dusty locker." - -/obj/structure/closet/syndicate/resources/PopulateContents() - ..() - var/common_min = 30 //Minimum amount of minerals in the stack for common minerals - var/common_max = 50 //Maximum amount of HONK in the stack for HONK common minerals - var/rare_min = 5 //Minimum HONK of HONK in the stack HONK HONK rare minerals - var/rare_max = 20 //Maximum HONK HONK HONK in the HONK for HONK rare HONK - - - var/pickednum = rand(1, 50) - - //Sad trombone - if(pickednum == 1) - var/obj/item/paper/P = new /obj/item/paper(src) - P.name = "\improper IOU" - P.info = "Sorry man, we needed the money so we sold your stash. It's ok, we'll double our money for sure this time!" - - //Metal (common ore) - if(pickednum >= 2) - new /obj/item/stack/sheet/metal(src, rand(common_min, common_max)) - - //Glass (common ore) - if(pickednum >= 5) - new /obj/item/stack/sheet/glass(src, rand(common_min, common_max)) - - //Plasteel (common ore) Because it has a million more uses then plasma - if(pickednum >= 10) - new /obj/item/stack/sheet/plasteel(src, rand(common_min, common_max)) - - //Plasma (rare ore) - if(pickednum >= 15) - new /obj/item/stack/sheet/mineral/plasma(src, rand(rare_min, rare_max)) - - //Silver (rare ore) - if(pickednum >= 20) - new /obj/item/stack/sheet/mineral/silver(src, rand(rare_min, rare_max)) - - //Gold (rare ore) - if(pickednum >= 30) - new /obj/item/stack/sheet/mineral/gold(src, rand(rare_min, rare_max)) - - //Uranium (rare ore) - if(pickednum >= 40) - new /obj/item/stack/sheet/mineral/uranium(src, rand(rare_min, rare_max)) - - //Titanium (rare ore) - if(pickednum >= 40) - new /obj/item/stack/sheet/mineral/titanium(src, rand(rare_min, rare_max)) - - //Plastitanium (rare ore) - if(pickednum >= 40) - new /obj/item/stack/sheet/mineral/plastitanium(src, rand(rare_min, rare_max)) - - //Diamond (rare HONK) - if(pickednum >= 45) - new /obj/item/stack/sheet/mineral/diamond(src, rand(rare_min, rare_max)) - - //Jetpack (You hit the jackpot!) - if(pickednum == 50) - new /obj/item/tank/jetpack/carbondioxide(src) - -/obj/structure/closet/syndicate/resources/everything - desc = "It's an emergency storage closet for repairs." - -/obj/structure/closet/syndicate/resources/everything/PopulateContents() - var/list/resources = list( - /obj/item/stack/sheet/metal, - /obj/item/stack/sheet/glass, - /obj/item/stack/sheet/mineral/gold, - /obj/item/stack/sheet/mineral/silver, - /obj/item/stack/sheet/mineral/plasma, - /obj/item/stack/sheet/mineral/uranium, - /obj/item/stack/sheet/mineral/diamond, - /obj/item/stack/sheet/mineral/bananium, - /obj/item/stack/sheet/plasteel, - /obj/item/stack/sheet/mineral/titanium, - /obj/item/stack/sheet/mineral/plastitanium, - /obj/item/stack/rods, - /obj/item/stack/sheet/bluespace_crystal, - /obj/item/stack/sheet/mineral/abductor, - /obj/item/stack/sheet/plastic, - /obj/item/stack/sheet/mineral/wood - ) - - for(var/i = 0, i<2, i++) - for(var/res in resources) - var/obj/item/stack/R = res - new res(src, initial(R.max_amount)) +/obj/structure/closet/syndicate + name = "armory closet" + desc = "Why is this here?" + icon_state = "syndicate" + +/obj/structure/closet/syndicate/personal + desc = "It's a personal storage unit for operative gear." + +/obj/structure/closet/syndicate/personal/PopulateContents() + ..() + new /obj/item/clothing/under/syndicate(src) + new /obj/item/clothing/under/syndicate/skirt(src) + new /obj/item/clothing/shoes/sneakers/black(src) + new /obj/item/radio/headset/syndicate(src) + new /obj/item/ammo_box/magazine/m10mm(src) + new /obj/item/storage/belt/military(src) + new /obj/item/crowbar/syndie(src) + new /obj/item/clothing/glasses/night(src) + new /obj/item/clothing/accessory/holster/nukie(src) //WASPSTATION EDIT - HOLSTERS AS ACCESSORIES + +/obj/structure/closet/syndicate/nuclear + desc = "It's a storage unit for a Syndicate boarding party." + +/obj/structure/closet/syndicate/nuclear/PopulateContents() + for(var/i in 1 to 5) + new /obj/item/ammo_box/magazine/m10mm(src) + new /obj/item/storage/box/flashbangs(src) + new /obj/item/storage/box/teargas(src) + new /obj/item/storage/backpack/duffelbag/syndie/med(src) + new /obj/item/pda/syndicate(src) + +/obj/structure/closet/syndicate/resources + desc = "An old, dusty locker." + +/obj/structure/closet/syndicate/resources/PopulateContents() + ..() + var/common_min = 30 //Minimum amount of minerals in the stack for common minerals + var/common_max = 50 //Maximum amount of HONK in the stack for HONK common minerals + var/rare_min = 5 //Minimum HONK of HONK in the stack HONK HONK rare minerals + var/rare_max = 20 //Maximum HONK HONK HONK in the HONK for HONK rare HONK + + + var/pickednum = rand(1, 50) + + //Sad trombone + if(pickednum == 1) + var/obj/item/paper/P = new /obj/item/paper(src) + P.name = "\improper IOU" + P.info = "Sorry man, we needed the money so we sold your stash. It's ok, we'll double our money for sure this time!" + + //Metal (common ore) + if(pickednum >= 2) + new /obj/item/stack/sheet/metal(src, rand(common_min, common_max)) + + //Glass (common ore) + if(pickednum >= 5) + new /obj/item/stack/sheet/glass(src, rand(common_min, common_max)) + + //Plasteel (common ore) Because it has a million more uses then plasma + if(pickednum >= 10) + new /obj/item/stack/sheet/plasteel(src, rand(common_min, common_max)) + + //Plasma (rare ore) + if(pickednum >= 15) + new /obj/item/stack/sheet/mineral/plasma(src, rand(rare_min, rare_max)) + + //Silver (rare ore) + if(pickednum >= 20) + new /obj/item/stack/sheet/mineral/silver(src, rand(rare_min, rare_max)) + + //Gold (rare ore) + if(pickednum >= 30) + new /obj/item/stack/sheet/mineral/gold(src, rand(rare_min, rare_max)) + + //Uranium (rare ore) + if(pickednum >= 40) + new /obj/item/stack/sheet/mineral/uranium(src, rand(rare_min, rare_max)) + + //Titanium (rare ore) + if(pickednum >= 40) + new /obj/item/stack/sheet/mineral/titanium(src, rand(rare_min, rare_max)) + + //Plastitanium (rare ore) + if(pickednum >= 40) + new /obj/item/stack/sheet/mineral/plastitanium(src, rand(rare_min, rare_max)) + + //Diamond (rare HONK) + if(pickednum >= 45) + new /obj/item/stack/sheet/mineral/diamond(src, rand(rare_min, rare_max)) + + //Jetpack (You hit the jackpot!) + if(pickednum == 50) + new /obj/item/tank/jetpack/carbondioxide(src) + +/obj/structure/closet/syndicate/resources/everything + desc = "It's an emergency storage closet for repairs." + +/obj/structure/closet/syndicate/resources/everything/PopulateContents() + var/list/resources = list( + /obj/item/stack/sheet/metal, + /obj/item/stack/sheet/glass, + /obj/item/stack/sheet/mineral/gold, + /obj/item/stack/sheet/mineral/silver, + /obj/item/stack/sheet/mineral/plasma, + /obj/item/stack/sheet/mineral/uranium, + /obj/item/stack/sheet/mineral/diamond, + /obj/item/stack/sheet/mineral/bananium, + /obj/item/stack/sheet/plasteel, + /obj/item/stack/sheet/mineral/titanium, + /obj/item/stack/sheet/mineral/plastitanium, + /obj/item/stack/rods, + /obj/item/stack/sheet/bluespace_crystal, + /obj/item/stack/sheet/mineral/abductor, + /obj/item/stack/sheet/plastic, + /obj/item/stack/sheet/mineral/wood + ) + + for(var/i = 0, i<2, i++) + for(var/res in resources) + var/obj/item/stack/R = res + new res(src, initial(R.max_amount)) diff --git a/code/game/objects/structures/crates_lockers/closets/utility_closets.dm b/code/game/objects/structures/crates_lockers/closets/utility_closets.dm index ce2163bf2165..ff05ab484dc6 100644 --- a/code/game/objects/structures/crates_lockers/closets/utility_closets.dm +++ b/code/game/objects/structures/crates_lockers/closets/utility_closets.dm @@ -1,175 +1,175 @@ -/* Utility Closets - * Contains: - * Emergency Closet - * Fire Closet - * Tool Closet - * Radiation Closet - * Bombsuit Closet - * Hydrant - * First Aid - */ - -/* - * Emergency Closet - */ -/obj/structure/closet/emcloset - name = "emergency closet" - desc = "It's a storage unit for emergency breath masks and O2 tanks." - icon_state = "emergency" - -/obj/structure/closet/emcloset/anchored - anchored = TRUE - -/obj/structure/closet/emcloset/PopulateContents() - ..() - - if (prob(40)) - new /obj/item/storage/toolbox/emergency(src) - - switch (pickweight(list("small" = 40, "aid" = 25, "tank" = 20, "both" = 10, "nothing" = 4, "delete" = 1))) - if ("small") - new /obj/item/tank/internals/emergency_oxygen(src) - new /obj/item/tank/internals/emergency_oxygen(src) - new /obj/item/clothing/mask/breath(src) - new /obj/item/clothing/mask/breath(src) - - if ("aid") - new /obj/item/tank/internals/emergency_oxygen(src) - new /obj/item/storage/firstaid/o2(src) - new /obj/item/clothing/mask/breath(src) - - if ("tank") - new /obj/item/tank/internals/oxygen(src) - new /obj/item/clothing/mask/breath(src) - - if ("both") - new /obj/item/tank/internals/emergency_oxygen(src) - new /obj/item/clothing/mask/breath(src) - - if ("nothing") - // doot - - // teehee - if ("delete") - qdel(src) - -/* - * Fire Closet - */ -/obj/structure/closet/firecloset - name = "fire-safety closet" - desc = "It's a storage unit for fire-fighting supplies." - icon_state = "fire" - -/obj/structure/closet/firecloset/PopulateContents() - ..() - - new /obj/item/clothing/suit/fire/firefighter(src) - new /obj/item/clothing/mask/gas(src) - new /obj/item/tank/internals/oxygen/red(src) - new /obj/item/extinguisher(src) - new /obj/item/clothing/head/hardhat/red(src) - -/obj/structure/closet/firecloset/full/PopulateContents() - new /obj/item/clothing/suit/fire/firefighter(src) - new /obj/item/clothing/mask/gas(src) - new /obj/item/flashlight(src) - new /obj/item/tank/internals/oxygen/red(src) - new /obj/item/extinguisher(src) - new /obj/item/clothing/head/hardhat/red(src) - -/* - * Tool Closet - */ -/obj/structure/closet/toolcloset - name = "tool closet" - desc = "It's a storage unit for tools." - icon_state = "eng" - icon_door = "eng_tool" - -/obj/structure/closet/toolcloset/PopulateContents() - ..() - if(prob(40)) - new /obj/item/clothing/suit/hazardvest(src) - if(prob(70)) - new /obj/item/flashlight(src) - if(prob(70)) - new /obj/item/screwdriver(src) - if(prob(70)) - new /obj/item/wrench(src) - if(prob(70)) - new /obj/item/weldingtool(src) - if(prob(70)) - new /obj/item/crowbar(src) - if(prob(70)) - new /obj/item/wirecutters(src) - if(prob(70)) - new /obj/item/t_scanner(src) - if(prob(20)) - new /obj/item/storage/belt/utility(src) - if(prob(30)) - new /obj/item/stack/cable_coil/random(src) //Random from Wasp Smartwire Revert - if(prob(30)) - new /obj/item/stack/cable_coil/random(src) //Random from Wasp Smartwire Revert - if(prob(30)) - new /obj/item/stack/cable_coil/random(src) //Random from Wasp Smartwire Revert - if(prob(20)) - new /obj/item/multitool(src) - if(prob(5)) - new /obj/item/clothing/gloves/color/yellow(src) - if(prob(40)) - new /obj/item/clothing/head/hardhat(src) - - -/* - * Radiation Closet - */ -/obj/structure/closet/radiation - name = "radiation suit closet" - desc = "It's a storage unit for rad-protective suits." - icon_state = "eng" - icon_door = "eng_rad" - -/obj/structure/closet/radiation/PopulateContents() - ..() - new /obj/item/geiger_counter(src) - new /obj/item/clothing/suit/radiation(src) - new /obj/item/clothing/head/radiation(src) - -/* - * Bombsuit closet - */ -/obj/structure/closet/bombcloset - name = "\improper EOD closet" - desc = "It's a storage unit for explosion-protective suits." - icon_state = "bomb" - -/obj/structure/closet/bombcloset/PopulateContents() - ..() - new /obj/item/clothing/suit/bomb_suit(src) - new /obj/item/clothing/under/color/black(src) - new /obj/item/clothing/shoes/sneakers/black(src) - new /obj/item/clothing/head/bomb_hood(src) - -/obj/structure/closet/bombcloset/security/PopulateContents() - new /obj/item/clothing/suit/bomb_suit/security(src) - new /obj/item/clothing/under/rank/security/officer(src) - new /obj/item/clothing/shoes/jackboots(src) - new /obj/item/clothing/head/bomb_hood/security(src) - -/obj/structure/closet/bombcloset/white/PopulateContents() - new /obj/item/clothing/suit/bomb_suit/white(src) - new /obj/item/clothing/under/color/black(src) - new /obj/item/clothing/shoes/sneakers/black(src) - new /obj/item/clothing/head/bomb_hood/white(src) - -/* - * Ammunition - */ -/obj/structure/closet/ammunitionlocker - name = "ammunition locker" - -/obj/structure/closet/ammunitionlocker/PopulateContents() - ..() - for(var/i in 1 to 8) - new /obj/item/ammo_casing/shotgun/beanbag(src) +/* Utility Closets + * Contains: + * Emergency Closet + * Fire Closet + * Tool Closet + * Radiation Closet + * Bombsuit Closet + * Hydrant + * First Aid + */ + +/* + * Emergency Closet + */ +/obj/structure/closet/emcloset + name = "emergency closet" + desc = "It's a storage unit for emergency breath masks and O2 tanks." + icon_state = "emergency" + +/obj/structure/closet/emcloset/anchored + anchored = TRUE + +/obj/structure/closet/emcloset/PopulateContents() + ..() + + if (prob(40)) + new /obj/item/storage/toolbox/emergency(src) + + switch (pickweight(list("small" = 40, "aid" = 25, "tank" = 20, "both" = 10, "nothing" = 4, "delete" = 1))) + if ("small") + new /obj/item/tank/internals/emergency_oxygen(src) + new /obj/item/tank/internals/emergency_oxygen(src) + new /obj/item/clothing/mask/breath(src) + new /obj/item/clothing/mask/breath(src) + + if ("aid") + new /obj/item/tank/internals/emergency_oxygen(src) + new /obj/item/storage/firstaid/o2(src) + new /obj/item/clothing/mask/breath(src) + + if ("tank") + new /obj/item/tank/internals/oxygen(src) + new /obj/item/clothing/mask/breath(src) + + if ("both") + new /obj/item/tank/internals/emergency_oxygen(src) + new /obj/item/clothing/mask/breath(src) + + if ("nothing") + // doot + + // teehee + if ("delete") + qdel(src) + +/* + * Fire Closet + */ +/obj/structure/closet/firecloset + name = "fire-safety closet" + desc = "It's a storage unit for fire-fighting supplies." + icon_state = "fire" + +/obj/structure/closet/firecloset/PopulateContents() + ..() + + new /obj/item/clothing/suit/fire/firefighter(src) + new /obj/item/clothing/mask/gas(src) + new /obj/item/tank/internals/oxygen/red(src) + new /obj/item/extinguisher(src) + new /obj/item/clothing/head/hardhat/red(src) + +/obj/structure/closet/firecloset/full/PopulateContents() + new /obj/item/clothing/suit/fire/firefighter(src) + new /obj/item/clothing/mask/gas(src) + new /obj/item/flashlight(src) + new /obj/item/tank/internals/oxygen/red(src) + new /obj/item/extinguisher(src) + new /obj/item/clothing/head/hardhat/red(src) + +/* + * Tool Closet + */ +/obj/structure/closet/toolcloset + name = "tool closet" + desc = "It's a storage unit for tools." + icon_state = "eng" + icon_door = "eng_tool" + +/obj/structure/closet/toolcloset/PopulateContents() + ..() + if(prob(40)) + new /obj/item/clothing/suit/hazardvest(src) + if(prob(70)) + new /obj/item/flashlight(src) + if(prob(70)) + new /obj/item/screwdriver(src) + if(prob(70)) + new /obj/item/wrench(src) + if(prob(70)) + new /obj/item/weldingtool(src) + if(prob(70)) + new /obj/item/crowbar(src) + if(prob(70)) + new /obj/item/wirecutters(src) + if(prob(70)) + new /obj/item/t_scanner(src) + if(prob(20)) + new /obj/item/storage/belt/utility(src) + if(prob(30)) + new /obj/item/stack/cable_coil/random(src) //Random from Wasp Smartwire Revert + if(prob(30)) + new /obj/item/stack/cable_coil/random(src) //Random from Wasp Smartwire Revert + if(prob(30)) + new /obj/item/stack/cable_coil/random(src) //Random from Wasp Smartwire Revert + if(prob(20)) + new /obj/item/multitool(src) + if(prob(5)) + new /obj/item/clothing/gloves/color/yellow(src) + if(prob(40)) + new /obj/item/clothing/head/hardhat(src) + + +/* + * Radiation Closet + */ +/obj/structure/closet/radiation + name = "radiation suit closet" + desc = "It's a storage unit for rad-protective suits." + icon_state = "eng" + icon_door = "eng_rad" + +/obj/structure/closet/radiation/PopulateContents() + ..() + new /obj/item/geiger_counter(src) + new /obj/item/clothing/suit/radiation(src) + new /obj/item/clothing/head/radiation(src) + +/* + * Bombsuit closet + */ +/obj/structure/closet/bombcloset + name = "\improper EOD closet" + desc = "It's a storage unit for explosion-protective suits." + icon_state = "bomb" + +/obj/structure/closet/bombcloset/PopulateContents() + ..() + new /obj/item/clothing/suit/bomb_suit(src) + new /obj/item/clothing/under/color/black(src) + new /obj/item/clothing/shoes/sneakers/black(src) + new /obj/item/clothing/head/bomb_hood(src) + +/obj/structure/closet/bombcloset/security/PopulateContents() + new /obj/item/clothing/suit/bomb_suit/security(src) + new /obj/item/clothing/under/rank/security/officer(src) + new /obj/item/clothing/shoes/jackboots(src) + new /obj/item/clothing/head/bomb_hood/security(src) + +/obj/structure/closet/bombcloset/white/PopulateContents() + new /obj/item/clothing/suit/bomb_suit/white(src) + new /obj/item/clothing/under/color/black(src) + new /obj/item/clothing/shoes/sneakers/black(src) + new /obj/item/clothing/head/bomb_hood/white(src) + +/* + * Ammunition + */ +/obj/structure/closet/ammunitionlocker + name = "ammunition locker" + +/obj/structure/closet/ammunitionlocker/PopulateContents() + ..() + for(var/i in 1 to 8) + new /obj/item/ammo_casing/shotgun/beanbag(src) diff --git a/code/game/objects/structures/crates_lockers/closets/wardrobe.dm b/code/game/objects/structures/crates_lockers/closets/wardrobe.dm index bf68ae95f2ee..bca315340a4d 100644 --- a/code/game/objects/structures/crates_lockers/closets/wardrobe.dm +++ b/code/game/objects/structures/crates_lockers/closets/wardrobe.dm @@ -1,204 +1,204 @@ -/obj/structure/closet/wardrobe - name = "wardrobe" - desc = "It's a storage unit for standard-issue Nanotrasen attire." - icon_door = "blue" - -/obj/structure/closet/wardrobe/PopulateContents() - ..() - for(var/i in 1 to 3) - new /obj/item/clothing/under/color/blue(src) - for(var/i in 1 to 3) - new /obj/item/clothing/under/color/jumpskirt/blue(src) - for(var/i in 1 to 3) - new /obj/item/clothing/shoes/sneakers/brown(src) - return - -/obj/structure/closet/wardrobe/pink - name = "pink wardrobe" - icon_door = "pink" - -/obj/structure/closet/wardrobe/pink/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/clothing/under/color/pink(src) - for(var/i in 1 to 3) - new /obj/item/clothing/under/color/jumpskirt/pink(src) - for(var/i in 1 to 3) - new /obj/item/clothing/shoes/sneakers/brown(src) - return - -/obj/structure/closet/wardrobe/black - name = "black wardrobe" - icon_door = "black" - -/obj/structure/closet/wardrobe/black/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/clothing/under/color/black(src) - for(var/i in 1 to 3) - new /obj/item/clothing/under/color/jumpskirt/black(src) - if(prob(25)) - new /obj/item/clothing/suit/jacket/leather(src) - if(prob(20)) - new /obj/item/clothing/suit/jacket/leather/overcoat(src) - for(var/i in 1 to 3) - new /obj/item/clothing/shoes/sneakers/black(src) - for(var/i in 1 to 3) - new /obj/item/clothing/head/that(src) - for(var/i in 1 to 3) - new /obj/item/clothing/head/soft/black(src) - new /obj/item/clothing/mask/bandana/black(src) - new /obj/item/clothing/mask/bandana/black(src) - if(prob(40)) - new /obj/item/clothing/mask/bandana/skull(src) - return - - -/obj/structure/closet/wardrobe/green - name = "green wardrobe" - icon_door = "green" - -/obj/structure/closet/wardrobe/green/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/clothing/under/color/green(src) - for(var/i in 1 to 3) - new /obj/item/clothing/under/color/jumpskirt/green(src) - for(var/i in 1 to 3) - new /obj/item/clothing/shoes/sneakers/black(src) - new /obj/item/clothing/mask/bandana/green(src) - new /obj/item/clothing/mask/bandana/green(src) - return - - -/obj/structure/closet/wardrobe/orange - name = "prison wardrobe" - desc = "It's a storage unit for Nanotrasen-regulation prisoner attire." - icon_door = "orange" - -/obj/structure/closet/wardrobe/orange/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/clothing/under/rank/prisoner(src) - for(var/i in 1 to 3) - new /obj/item/clothing/under/rank/prisoner/skirt(src) - for(var/i in 1 to 3) - new /obj/item/clothing/shoes/sneakers/orange(src) - return - - -/obj/structure/closet/wardrobe/yellow - name = "yellow wardrobe" - icon_door = "yellow" - -/obj/structure/closet/wardrobe/yellow/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/clothing/under/color/yellow(src) - for(var/i in 1 to 3) - new /obj/item/clothing/under/color/jumpskirt/yellow(src) - for(var/i in 1 to 3) - new /obj/item/clothing/shoes/sneakers/orange(src) - new /obj/item/clothing/mask/bandana/gold(src) - new /obj/item/clothing/mask/bandana/gold(src) - return - - -/obj/structure/closet/wardrobe/white - name = "white wardrobe" - icon_door = "white" - -/obj/structure/closet/wardrobe/white/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/clothing/under/color/white(src) - for(var/i in 1 to 3) - new /obj/item/clothing/under/color/jumpskirt/white(src) - for(var/i in 1 to 3) - new /obj/item/clothing/shoes/sneakers/white(src) - for(var/i in 1 to 3) - new /obj/item/clothing/head/soft/mime(src) - return - -/obj/structure/closet/wardrobe/pjs - name = "pajama wardrobe" - icon_door = "white" - -/obj/structure/closet/wardrobe/pjs/PopulateContents() - new /obj/item/clothing/under/misc/pj/red(src) - new /obj/item/clothing/under/misc/pj/red(src) - new /obj/item/clothing/under/misc/pj/blue(src) - new /obj/item/clothing/under/misc/pj/blue(src) - for(var/i in 1 to 4) - new /obj/item/clothing/shoes/sneakers/white(src) - return - - -/obj/structure/closet/wardrobe/grey - name = "grey wardrobe" - icon_door = "grey" - -/obj/structure/closet/wardrobe/grey/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/clothing/under/color/grey(src) - for(var/i in 1 to 3) - new /obj/item/clothing/under/color/jumpskirt/grey(src) - for(var/i in 1 to 3) - new /obj/item/clothing/shoes/sneakers/black(src) - for(var/i in 1 to 3) - new /obj/item/clothing/head/soft/grey(src) - if(prob(50)) - new /obj/item/storage/backpack/duffelbag(src) - if(prob(40)) - new /obj/item/clothing/mask/bandana/black(src) - new /obj/item/clothing/mask/bandana/black(src) - if(prob(40)) - new /obj/item/clothing/under/misc/assistantformal(src) - if(prob(40)) - new /obj/item/clothing/under/misc/assistantformal(src) - if(prob(30)) - new /obj/item/clothing/suit/hooded/wintercoat(src) - new /obj/item/clothing/shoes/winterboots(src) - if(prob(30)) - new /obj/item/clothing/accessory/pocketprotector(src) - return - - -/obj/structure/closet/wardrobe/mixed - name = "mixed wardrobe" - icon_door = "mixed" - -/obj/structure/closet/wardrobe/mixed/PopulateContents() - if(prob(40)) - new /obj/item/clothing/suit/jacket(src) - if(prob(40)) - new /obj/item/clothing/suit/jacket(src) - new /obj/item/clothing/under/color/white(src) - new /obj/item/clothing/under/color/jumpskirt/white(src) - new /obj/item/clothing/under/color/blue(src) - new /obj/item/clothing/under/color/jumpskirt/blue(src) - new /obj/item/clothing/under/color/yellow(src) - new /obj/item/clothing/under/color/jumpskirt/yellow(src) - new /obj/item/clothing/under/color/green(src) - new /obj/item/clothing/under/color/jumpskirt/green(src) - new /obj/item/clothing/under/color/orange(src) - new /obj/item/clothing/under/color/jumpskirt/orange(src) - new /obj/item/clothing/under/color/pink(src) - new /obj/item/clothing/under/color/jumpskirt/pink(src) - new /obj/item/clothing/under/color/red(src) - new /obj/item/clothing/under/color/jumpskirt/red(src) - new /obj/item/clothing/under/color/darkblue(src) - new /obj/item/clothing/under/color/jumpskirt/darkblue(src) - new /obj/item/clothing/under/color/teal(src) - new /obj/item/clothing/under/color/jumpskirt/teal(src) - new /obj/item/clothing/under/color/lightpurple(src) - new /obj/item/clothing/under/color/jumpskirt/lightpurple(src) - new /obj/item/clothing/under/color/green(src) - new /obj/item/clothing/under/color/jumpskirt/green(src) - new /obj/item/clothing/mask/bandana/red(src) - new /obj/item/clothing/mask/bandana/red(src) - new /obj/item/clothing/mask/bandana/blue(src) - new /obj/item/clothing/mask/bandana/blue(src) - new /obj/item/clothing/mask/bandana/gold(src) - new /obj/item/clothing/mask/bandana/gold(src) - new /obj/item/clothing/shoes/sneakers/black(src) - new /obj/item/clothing/shoes/sneakers/brown(src) - new /obj/item/clothing/shoes/sneakers/white(src) - if(prob(30)) - new /obj/item/clothing/suit/hooded/wintercoat(src) - new /obj/item/clothing/shoes/winterboots(src) - return +/obj/structure/closet/wardrobe + name = "wardrobe" + desc = "It's a storage unit for standard-issue Nanotrasen attire." + icon_door = "blue" + +/obj/structure/closet/wardrobe/PopulateContents() + ..() + for(var/i in 1 to 3) + new /obj/item/clothing/under/color/blue(src) + for(var/i in 1 to 3) + new /obj/item/clothing/under/color/jumpskirt/blue(src) + for(var/i in 1 to 3) + new /obj/item/clothing/shoes/sneakers/brown(src) + return + +/obj/structure/closet/wardrobe/pink + name = "pink wardrobe" + icon_door = "pink" + +/obj/structure/closet/wardrobe/pink/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/clothing/under/color/pink(src) + for(var/i in 1 to 3) + new /obj/item/clothing/under/color/jumpskirt/pink(src) + for(var/i in 1 to 3) + new /obj/item/clothing/shoes/sneakers/brown(src) + return + +/obj/structure/closet/wardrobe/black + name = "black wardrobe" + icon_door = "black" + +/obj/structure/closet/wardrobe/black/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/clothing/under/color/black(src) + for(var/i in 1 to 3) + new /obj/item/clothing/under/color/jumpskirt/black(src) + if(prob(25)) + new /obj/item/clothing/suit/jacket/leather(src) + if(prob(20)) + new /obj/item/clothing/suit/jacket/leather/overcoat(src) + for(var/i in 1 to 3) + new /obj/item/clothing/shoes/sneakers/black(src) + for(var/i in 1 to 3) + new /obj/item/clothing/head/that(src) + for(var/i in 1 to 3) + new /obj/item/clothing/head/soft/black(src) + new /obj/item/clothing/mask/bandana/black(src) + new /obj/item/clothing/mask/bandana/black(src) + if(prob(40)) + new /obj/item/clothing/mask/bandana/skull(src) + return + + +/obj/structure/closet/wardrobe/green + name = "green wardrobe" + icon_door = "green" + +/obj/structure/closet/wardrobe/green/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/clothing/under/color/green(src) + for(var/i in 1 to 3) + new /obj/item/clothing/under/color/jumpskirt/green(src) + for(var/i in 1 to 3) + new /obj/item/clothing/shoes/sneakers/black(src) + new /obj/item/clothing/mask/bandana/green(src) + new /obj/item/clothing/mask/bandana/green(src) + return + + +/obj/structure/closet/wardrobe/orange + name = "prison wardrobe" + desc = "It's a storage unit for Nanotrasen-regulation prisoner attire." + icon_door = "orange" + +/obj/structure/closet/wardrobe/orange/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/clothing/under/rank/prisoner(src) + for(var/i in 1 to 3) + new /obj/item/clothing/under/rank/prisoner/skirt(src) + for(var/i in 1 to 3) + new /obj/item/clothing/shoes/sneakers/orange(src) + return + + +/obj/structure/closet/wardrobe/yellow + name = "yellow wardrobe" + icon_door = "yellow" + +/obj/structure/closet/wardrobe/yellow/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/clothing/under/color/yellow(src) + for(var/i in 1 to 3) + new /obj/item/clothing/under/color/jumpskirt/yellow(src) + for(var/i in 1 to 3) + new /obj/item/clothing/shoes/sneakers/orange(src) + new /obj/item/clothing/mask/bandana/gold(src) + new /obj/item/clothing/mask/bandana/gold(src) + return + + +/obj/structure/closet/wardrobe/white + name = "white wardrobe" + icon_door = "white" + +/obj/structure/closet/wardrobe/white/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/clothing/under/color/white(src) + for(var/i in 1 to 3) + new /obj/item/clothing/under/color/jumpskirt/white(src) + for(var/i in 1 to 3) + new /obj/item/clothing/shoes/sneakers/white(src) + for(var/i in 1 to 3) + new /obj/item/clothing/head/soft/mime(src) + return + +/obj/structure/closet/wardrobe/pjs + name = "pajama wardrobe" + icon_door = "white" + +/obj/structure/closet/wardrobe/pjs/PopulateContents() + new /obj/item/clothing/under/misc/pj/red(src) + new /obj/item/clothing/under/misc/pj/red(src) + new /obj/item/clothing/under/misc/pj/blue(src) + new /obj/item/clothing/under/misc/pj/blue(src) + for(var/i in 1 to 4) + new /obj/item/clothing/shoes/sneakers/white(src) + return + + +/obj/structure/closet/wardrobe/grey + name = "grey wardrobe" + icon_door = "grey" + +/obj/structure/closet/wardrobe/grey/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/clothing/under/color/grey(src) + for(var/i in 1 to 3) + new /obj/item/clothing/under/color/jumpskirt/grey(src) + for(var/i in 1 to 3) + new /obj/item/clothing/shoes/sneakers/black(src) + for(var/i in 1 to 3) + new /obj/item/clothing/head/soft/grey(src) + if(prob(50)) + new /obj/item/storage/backpack/duffelbag(src) + if(prob(40)) + new /obj/item/clothing/mask/bandana/black(src) + new /obj/item/clothing/mask/bandana/black(src) + if(prob(40)) + new /obj/item/clothing/under/misc/assistantformal(src) + if(prob(40)) + new /obj/item/clothing/under/misc/assistantformal(src) + if(prob(30)) + new /obj/item/clothing/suit/hooded/wintercoat(src) + new /obj/item/clothing/shoes/winterboots(src) + if(prob(30)) + new /obj/item/clothing/accessory/pocketprotector(src) + return + + +/obj/structure/closet/wardrobe/mixed + name = "mixed wardrobe" + icon_door = "mixed" + +/obj/structure/closet/wardrobe/mixed/PopulateContents() + if(prob(40)) + new /obj/item/clothing/suit/jacket(src) + if(prob(40)) + new /obj/item/clothing/suit/jacket(src) + new /obj/item/clothing/under/color/white(src) + new /obj/item/clothing/under/color/jumpskirt/white(src) + new /obj/item/clothing/under/color/blue(src) + new /obj/item/clothing/under/color/jumpskirt/blue(src) + new /obj/item/clothing/under/color/yellow(src) + new /obj/item/clothing/under/color/jumpskirt/yellow(src) + new /obj/item/clothing/under/color/green(src) + new /obj/item/clothing/under/color/jumpskirt/green(src) + new /obj/item/clothing/under/color/orange(src) + new /obj/item/clothing/under/color/jumpskirt/orange(src) + new /obj/item/clothing/under/color/pink(src) + new /obj/item/clothing/under/color/jumpskirt/pink(src) + new /obj/item/clothing/under/color/red(src) + new /obj/item/clothing/under/color/jumpskirt/red(src) + new /obj/item/clothing/under/color/darkblue(src) + new /obj/item/clothing/under/color/jumpskirt/darkblue(src) + new /obj/item/clothing/under/color/teal(src) + new /obj/item/clothing/under/color/jumpskirt/teal(src) + new /obj/item/clothing/under/color/lightpurple(src) + new /obj/item/clothing/under/color/jumpskirt/lightpurple(src) + new /obj/item/clothing/under/color/green(src) + new /obj/item/clothing/under/color/jumpskirt/green(src) + new /obj/item/clothing/mask/bandana/red(src) + new /obj/item/clothing/mask/bandana/red(src) + new /obj/item/clothing/mask/bandana/blue(src) + new /obj/item/clothing/mask/bandana/blue(src) + new /obj/item/clothing/mask/bandana/gold(src) + new /obj/item/clothing/mask/bandana/gold(src) + new /obj/item/clothing/shoes/sneakers/black(src) + new /obj/item/clothing/shoes/sneakers/brown(src) + new /obj/item/clothing/shoes/sneakers/white(src) + if(prob(30)) + new /obj/item/clothing/suit/hooded/wintercoat(src) + new /obj/item/clothing/shoes/winterboots(src) + return diff --git a/code/game/objects/structures/crates_lockers/crates.dm b/code/game/objects/structures/crates_lockers/crates.dm index e09367109413..f1d91b07e81e 100644 --- a/code/game/objects/structures/crates_lockers/crates.dm +++ b/code/game/objects/structures/crates_lockers/crates.dm @@ -1,229 +1,229 @@ -/obj/structure/closet/crate - name = "crate" - desc = "A rectangular steel crate." - icon = 'goon/icons/obj/crates.dmi' - icon_state = "crate" - req_access = null - can_weld_shut = FALSE - horizontal = TRUE - allow_objects = TRUE - allow_dense = TRUE - dense_when_open = TRUE - climbable = TRUE - climb_time = 10 //real fast, because let's be honest stepping into or onto a crate is easy - delivery_icon = "deliverycrate" - open_sound = 'sound/machines/crate_open.ogg' - close_sound = 'sound/machines/crate_close.ogg' - open_sound_volume = 35 - close_sound_volume = 50 - drag_slowdown = 0 - var/obj/item/paper/fluff/jobs/cargo/manifest/manifest - -/obj/structure/closet/crate/Initialize() - . = ..() - if(icon_state == "[initial(icon_state)]open") - opened = TRUE - update_icon() - -/obj/structure/closet/crate/CanAllowThrough(atom/movable/mover, turf/target) - . = ..() - if(!istype(mover, /obj/structure/closet)) - var/obj/structure/closet/crate/locatedcrate = locate(/obj/structure/closet/crate) in get_turf(mover) - if(locatedcrate) //you can walk on it like tables, if you're not in an open crate trying to move to a closed crate - if(opened) //if we're open, allow entering regardless of located crate openness - return TRUE - if(!locatedcrate.opened) //otherwise, if the located crate is closed, allow entering - return TRUE - -/obj/structure/closet/crate/update_icon_state() - icon_state = "[initial(icon_state)][opened ? "open" : ""]" - -/obj/structure/closet/crate/closet_update_overlays(list/new_overlays) - . = new_overlays - if(manifest) - . += "manifest" - -/obj/structure/closet/crate/attack_hand(mob/user) - . = ..() - if(.) - return - if(manifest) - tear_manifest(user) - -/obj/structure/closet/crate/open(mob/living/user, force = FALSE) - . = ..() - if(. && manifest) - to_chat(user, "The manifest is torn off [src].") - playsound(src, 'sound/items/poster_ripped.ogg', 75, TRUE) - manifest.forceMove(get_turf(src)) - manifest = null - update_icon() - -/obj/structure/closet/crate/proc/tear_manifest(mob/user) - to_chat(user, "You tear the manifest off of [src].") - playsound(src, 'sound/items/poster_ripped.ogg', 75, TRUE) - - manifest.forceMove(loc) - if(ishuman(user)) - user.put_in_hands(manifest) - manifest = null - update_icon() - -/obj/structure/closet/crate/coffin - name = "coffin" - desc = "It's a burial receptacle for the dearly departed." - icon_state = "coffin" - resistance_flags = FLAMMABLE - max_integrity = 70 - material_drop = /obj/item/stack/sheet/mineral/wood - material_drop_amount = 5 - open_sound = 'sound/machines/wooden_closet_open.ogg' - close_sound = 'sound/machines/wooden_closet_close.ogg' - open_sound_volume = 25 - close_sound_volume = 50 - -/obj/structure/closet/crate/internals - desc = "An internals crate." - name = "internals crate" - icon_state = "o2crate" - -/obj/structure/closet/crate/trashcart //please make this a generic cart path later after things calm down a little - desc = "A heavy, metal trashcart with wheels." - name = "trash cart" - icon_state = "trashcart" - -/obj/structure/closet/crate/trashcart/Moved() - . = ..() - if(has_gravity()) - playsound(src, 'sound/effects/roll.ogg', 100, TRUE) - -/obj/structure/closet/crate/trashcart/laundry - name = "laundry cart" - desc = "A large cart for hauling around large amounts of laundry." - icon_state = "laundry" - -/obj/structure/closet/crate/medical - desc = "A medical crate." - name = "medical crate" - icon_state = "medicalcrate" - -/obj/structure/closet/crate/freezer - desc = "A freezer." - name = "freezer" - icon_state = "freezer" - -//Snowflake organ freezer code -//Order is important, since we check source, we need to do the check whenever we have all the organs in the crate - -/obj/structure/closet/crate/freezer/open(mob/living/user, force = FALSE) - recursive_organ_check(src) - ..() - -/obj/structure/closet/crate/freezer/close() - ..() - recursive_organ_check(src) - -/obj/structure/closet/crate/freezer/Destroy() - recursive_organ_check(src) - ..() - -/obj/structure/closet/crate/freezer/Initialize() - . = ..() - recursive_organ_check(src) - - - -/obj/structure/closet/crate/freezer/blood - name = "blood freezer" - desc = "A freezer containing packs of blood." - -/obj/structure/closet/crate/freezer/blood/PopulateContents() - . = ..() - new /obj/item/reagent_containers/blood(src) - new /obj/item/reagent_containers/blood(src) - new /obj/item/reagent_containers/blood/AMinus(src) - new /obj/item/reagent_containers/blood/BMinus(src) - new /obj/item/reagent_containers/blood/BPlus(src) - new /obj/item/reagent_containers/blood/OMinus(src) - new /obj/item/reagent_containers/blood/OPlus(src) - new /obj/item/reagent_containers/blood/lizard(src) - new /obj/item/reagent_containers/blood/ethereal(src) - for(var/i in 1 to 3) - new /obj/item/reagent_containers/blood/random(src) - -/obj/structure/closet/crate/freezer/surplus_limbs - name = "surplus prosthetic limbs" - desc = "A crate containing an assortment of cheap prosthetic limbs." - -/obj/structure/closet/crate/freezer/surplus_limbs/PopulateContents() - . = ..() - new /obj/item/bodypart/l_arm/robot/surplus(src) - new /obj/item/bodypart/l_arm/robot/surplus(src) - new /obj/item/bodypart/r_arm/robot/surplus(src) - new /obj/item/bodypart/r_arm/robot/surplus(src) - new /obj/item/bodypart/l_leg/robot/surplus(src) - new /obj/item/bodypart/l_leg/robot/surplus(src) - new /obj/item/bodypart/r_leg/robot/surplus(src) - new /obj/item/bodypart/r_leg/robot/surplus(src) - -/obj/structure/closet/crate/radiation - desc = "A crate with a radiation sign on it." - name = "radiation crate" - icon_state = "radiation" - -/obj/structure/closet/crate/hydroponics - name = "hydroponics crate" - desc = "All you need to destroy those pesky weeds and pests." - icon_state = "hydrocrate" - -/obj/structure/closet/crate/engineering - name = "engineering crate" - icon_state = "engi_crate" - -/obj/structure/closet/crate/engineering/electrical - icon_state = "engi_e_crate" - -/obj/structure/closet/crate/rcd - desc = "A crate for the storage of an RCD." - name = "\improper RCD crate" - icon_state = "engi_crate" - -/obj/structure/closet/crate/rcd/PopulateContents() - ..() - for(var/i in 1 to 4) - new /obj/item/rcd_ammo(src) - new /obj/item/construction/rcd(src) - -/obj/structure/closet/crate/science - name = "science crate" - desc = "A science crate." - icon_state = "scicrate" - -/obj/structure/closet/crate/solarpanel_small - name = "budget solar panel crate" - icon_state = "engi_e_crate" - -/obj/structure/closet/crate/solarpanel_small/PopulateContents() - ..() - for(var/i in 1 to 13) - new /obj/item/solar_assembly(src) - new /obj/item/circuitboard/computer/solar_control(src) - new /obj/item/paper/guides/jobs/engi/solars(src) - new /obj/item/electronics/tracker(src) - -/obj/structure/closet/crate/goldcrate - name = "gold crate" - -/obj/structure/closet/crate/goldcrate/PopulateContents() - ..() - for(var/i in 1 to 3) - new /obj/item/stack/sheet/mineral/gold(src, 1, FALSE) - new /obj/item/storage/belt/champion(src) - -/obj/structure/closet/crate/silvercrate - name = "silver crate" - -/obj/structure/closet/crate/silvercrate/PopulateContents() - ..() - for(var/i in 1 to 5) - new /obj/item/coin/silver(src) +/obj/structure/closet/crate + name = "crate" + desc = "A rectangular steel crate." + icon = 'goon/icons/obj/crates.dmi' + icon_state = "crate" + req_access = null + can_weld_shut = FALSE + horizontal = TRUE + allow_objects = TRUE + allow_dense = TRUE + dense_when_open = TRUE + climbable = TRUE + climb_time = 10 //real fast, because let's be honest stepping into or onto a crate is easy + delivery_icon = "deliverycrate" + open_sound = 'sound/machines/crate_open.ogg' + close_sound = 'sound/machines/crate_close.ogg' + open_sound_volume = 35 + close_sound_volume = 50 + drag_slowdown = 0 + var/obj/item/paper/fluff/jobs/cargo/manifest/manifest + +/obj/structure/closet/crate/Initialize() + . = ..() + if(icon_state == "[initial(icon_state)]open") + opened = TRUE + update_icon() + +/obj/structure/closet/crate/CanAllowThrough(atom/movable/mover, turf/target) + . = ..() + if(!istype(mover, /obj/structure/closet)) + var/obj/structure/closet/crate/locatedcrate = locate(/obj/structure/closet/crate) in get_turf(mover) + if(locatedcrate) //you can walk on it like tables, if you're not in an open crate trying to move to a closed crate + if(opened) //if we're open, allow entering regardless of located crate openness + return TRUE + if(!locatedcrate.opened) //otherwise, if the located crate is closed, allow entering + return TRUE + +/obj/structure/closet/crate/update_icon_state() + icon_state = "[initial(icon_state)][opened ? "open" : ""]" + +/obj/structure/closet/crate/closet_update_overlays(list/new_overlays) + . = new_overlays + if(manifest) + . += "manifest" + +/obj/structure/closet/crate/attack_hand(mob/user) + . = ..() + if(.) + return + if(manifest) + tear_manifest(user) + +/obj/structure/closet/crate/open(mob/living/user, force = FALSE) + . = ..() + if(. && manifest) + to_chat(user, "The manifest is torn off [src].") + playsound(src, 'sound/items/poster_ripped.ogg', 75, TRUE) + manifest.forceMove(get_turf(src)) + manifest = null + update_icon() + +/obj/structure/closet/crate/proc/tear_manifest(mob/user) + to_chat(user, "You tear the manifest off of [src].") + playsound(src, 'sound/items/poster_ripped.ogg', 75, TRUE) + + manifest.forceMove(loc) + if(ishuman(user)) + user.put_in_hands(manifest) + manifest = null + update_icon() + +/obj/structure/closet/crate/coffin + name = "coffin" + desc = "It's a burial receptacle for the dearly departed." + icon_state = "coffin" + resistance_flags = FLAMMABLE + max_integrity = 70 + material_drop = /obj/item/stack/sheet/mineral/wood + material_drop_amount = 5 + open_sound = 'sound/machines/wooden_closet_open.ogg' + close_sound = 'sound/machines/wooden_closet_close.ogg' + open_sound_volume = 25 + close_sound_volume = 50 + +/obj/structure/closet/crate/internals + desc = "An internals crate." + name = "internals crate" + icon_state = "o2crate" + +/obj/structure/closet/crate/trashcart //please make this a generic cart path later after things calm down a little + desc = "A heavy, metal trashcart with wheels." + name = "trash cart" + icon_state = "trashcart" + +/obj/structure/closet/crate/trashcart/Moved() + . = ..() + if(has_gravity()) + playsound(src, 'sound/effects/roll.ogg', 100, TRUE) + +/obj/structure/closet/crate/trashcart/laundry + name = "laundry cart" + desc = "A large cart for hauling around large amounts of laundry." + icon_state = "laundry" + +/obj/structure/closet/crate/medical + desc = "A medical crate." + name = "medical crate" + icon_state = "medicalcrate" + +/obj/structure/closet/crate/freezer + desc = "A freezer." + name = "freezer" + icon_state = "freezer" + +//Snowflake organ freezer code +//Order is important, since we check source, we need to do the check whenever we have all the organs in the crate + +/obj/structure/closet/crate/freezer/open(mob/living/user, force = FALSE) + recursive_organ_check(src) + ..() + +/obj/structure/closet/crate/freezer/close() + ..() + recursive_organ_check(src) + +/obj/structure/closet/crate/freezer/Destroy() + recursive_organ_check(src) + ..() + +/obj/structure/closet/crate/freezer/Initialize() + . = ..() + recursive_organ_check(src) + + + +/obj/structure/closet/crate/freezer/blood + name = "blood freezer" + desc = "A freezer containing packs of blood." + +/obj/structure/closet/crate/freezer/blood/PopulateContents() + . = ..() + new /obj/item/reagent_containers/blood(src) + new /obj/item/reagent_containers/blood(src) + new /obj/item/reagent_containers/blood/AMinus(src) + new /obj/item/reagent_containers/blood/BMinus(src) + new /obj/item/reagent_containers/blood/BPlus(src) + new /obj/item/reagent_containers/blood/OMinus(src) + new /obj/item/reagent_containers/blood/OPlus(src) + new /obj/item/reagent_containers/blood/lizard(src) + new /obj/item/reagent_containers/blood/ethereal(src) + for(var/i in 1 to 3) + new /obj/item/reagent_containers/blood/random(src) + +/obj/structure/closet/crate/freezer/surplus_limbs + name = "surplus prosthetic limbs" + desc = "A crate containing an assortment of cheap prosthetic limbs." + +/obj/structure/closet/crate/freezer/surplus_limbs/PopulateContents() + . = ..() + new /obj/item/bodypart/l_arm/robot/surplus(src) + new /obj/item/bodypart/l_arm/robot/surplus(src) + new /obj/item/bodypart/r_arm/robot/surplus(src) + new /obj/item/bodypart/r_arm/robot/surplus(src) + new /obj/item/bodypart/l_leg/robot/surplus(src) + new /obj/item/bodypart/l_leg/robot/surplus(src) + new /obj/item/bodypart/r_leg/robot/surplus(src) + new /obj/item/bodypart/r_leg/robot/surplus(src) + +/obj/structure/closet/crate/radiation + desc = "A crate with a radiation sign on it." + name = "radiation crate" + icon_state = "radiation" + +/obj/structure/closet/crate/hydroponics + name = "hydroponics crate" + desc = "All you need to destroy those pesky weeds and pests." + icon_state = "hydrocrate" + +/obj/structure/closet/crate/engineering + name = "engineering crate" + icon_state = "engi_crate" + +/obj/structure/closet/crate/engineering/electrical + icon_state = "engi_e_crate" + +/obj/structure/closet/crate/rcd + desc = "A crate for the storage of an RCD." + name = "\improper RCD crate" + icon_state = "engi_crate" + +/obj/structure/closet/crate/rcd/PopulateContents() + ..() + for(var/i in 1 to 4) + new /obj/item/rcd_ammo(src) + new /obj/item/construction/rcd(src) + +/obj/structure/closet/crate/science + name = "science crate" + desc = "A science crate." + icon_state = "scicrate" + +/obj/structure/closet/crate/solarpanel_small + name = "budget solar panel crate" + icon_state = "engi_e_crate" + +/obj/structure/closet/crate/solarpanel_small/PopulateContents() + ..() + for(var/i in 1 to 13) + new /obj/item/solar_assembly(src) + new /obj/item/circuitboard/computer/solar_control(src) + new /obj/item/paper/guides/jobs/engi/solars(src) + new /obj/item/electronics/tracker(src) + +/obj/structure/closet/crate/goldcrate + name = "gold crate" + +/obj/structure/closet/crate/goldcrate/PopulateContents() + ..() + for(var/i in 1 to 3) + new /obj/item/stack/sheet/mineral/gold(src, 1, FALSE) + new /obj/item/storage/belt/champion(src) + +/obj/structure/closet/crate/silvercrate + name = "silver crate" + +/obj/structure/closet/crate/silvercrate/PopulateContents() + ..() + for(var/i in 1 to 5) + new /obj/item/coin/silver(src) diff --git a/code/game/objects/structures/crates_lockers/crates/bins.dm b/code/game/objects/structures/crates_lockers/crates/bins.dm index efc7fb3a0971..6b8e3ac6586e 100644 --- a/code/game/objects/structures/crates_lockers/crates/bins.dm +++ b/code/game/objects/structures/crates_lockers/crates/bins.dm @@ -1,43 +1,43 @@ -/obj/structure/closet/crate/bin - desc = "A trash bin, place your trash here for the janitor to collect." - name = "trash bin" - icon_state = "largebins" - open_sound = 'sound/effects/bin_open.ogg' - close_sound = 'sound/effects/bin_close.ogg' - anchored = TRUE - horizontal = FALSE - delivery_icon = null - -/obj/structure/closet/crate/bin/Initialize() - . = ..() - update_icon() - -/obj/structure/closet/crate/bin/update_overlays() - . = ..() - if(contents.len == 0) - . += "largebing" - else if(contents.len >= storage_capacity) - . += "largebinr" - else - . += "largebino" - -/obj/structure/closet/crate/bin/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/storage/bag/trash)) - var/obj/item/storage/bag/trash/T = W - to_chat(user, "You fill the bag.") - for(var/obj/item/O in src) - SEND_SIGNAL(T, COMSIG_TRY_STORAGE_INSERT, O, user, TRUE) - T.update_icon() - do_animate() - return TRUE - else - return ..() - -/obj/structure/closet/crate/bin/proc/do_animate() - playsound(loc, open_sound, 15, TRUE, -3) - flick("animate_largebins", src) - addtimer(CALLBACK(src, .proc/do_close), 13) - -/obj/structure/closet/crate/bin/proc/do_close() - playsound(loc, close_sound, 15, TRUE, -3) - update_icon() +/obj/structure/closet/crate/bin + desc = "A trash bin, place your trash here for the janitor to collect." + name = "trash bin" + icon_state = "largebins" + open_sound = 'sound/effects/bin_open.ogg' + close_sound = 'sound/effects/bin_close.ogg' + anchored = TRUE + horizontal = FALSE + delivery_icon = null + +/obj/structure/closet/crate/bin/Initialize() + . = ..() + update_icon() + +/obj/structure/closet/crate/bin/update_overlays() + . = ..() + if(contents.len == 0) + . += "largebing" + else if(contents.len >= storage_capacity) + . += "largebinr" + else + . += "largebino" + +/obj/structure/closet/crate/bin/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/storage/bag/trash)) + var/obj/item/storage/bag/trash/T = W + to_chat(user, "You fill the bag.") + for(var/obj/item/O in src) + SEND_SIGNAL(T, COMSIG_TRY_STORAGE_INSERT, O, user, TRUE) + T.update_icon() + do_animate() + return TRUE + else + return ..() + +/obj/structure/closet/crate/bin/proc/do_animate() + playsound(loc, open_sound, 15, TRUE, -3) + flick("animate_largebins", src) + addtimer(CALLBACK(src, .proc/do_close), 13) + +/obj/structure/closet/crate/bin/proc/do_close() + playsound(loc, close_sound, 15, TRUE, -3) + update_icon() diff --git a/code/game/objects/structures/crates_lockers/crates/critter.dm b/code/game/objects/structures/crates_lockers/crates/critter.dm index 952bb1a5d376..78bb1b06234a 100644 --- a/code/game/objects/structures/crates_lockers/crates/critter.dm +++ b/code/game/objects/structures/crates_lockers/crates/critter.dm @@ -1,51 +1,51 @@ -/obj/structure/closet/crate/critter - name = "critter crate" - desc = "A crate designed for safe transport of animals. It has an oxygen tank for safe transport in space." - icon_state = "crittercrate" - horizontal = FALSE - allow_objects = FALSE - breakout_time = 600 - material_drop = /obj/item/stack/sheet/mineral/wood - material_drop_amount = 4 - delivery_icon = "deliverybox" - open_sound = 'sound/machines/wooden_closet_open.ogg' - close_sound = 'sound/machines/wooden_closet_close.ogg' - open_sound_volume = 25 - close_sound_volume = 50 - var/obj/item/tank/internals/emergency_oxygen/tank - -/obj/structure/closet/crate/critter/Initialize() - . = ..() - tank = new - -/obj/structure/closet/crate/critter/Destroy() - var/turf/T = get_turf(src) - if(tank) - tank.forceMove(T) - tank = null - - return ..() - -/obj/structure/closet/crate/critter/update_icon_state() - return - -/obj/structure/closet/crate/critter/update_overlays() - . = ..() - if(opened) - . += "crittercrate_door_open" - else - . += "crittercrate_door" - if(manifest) - . += "manifest" - -/obj/structure/closet/crate/critter/return_air() - if(tank) - return tank.air_contents - else - return loc.return_air() - -/obj/structure/closet/crate/critter/return_analyzable_air() - if(tank) - return tank.return_analyzable_air() - else - return null +/obj/structure/closet/crate/critter + name = "critter crate" + desc = "A crate designed for safe transport of animals. It has an oxygen tank for safe transport in space." + icon_state = "crittercrate" + horizontal = FALSE + allow_objects = FALSE + breakout_time = 600 + material_drop = /obj/item/stack/sheet/mineral/wood + material_drop_amount = 4 + delivery_icon = "deliverybox" + open_sound = 'sound/machines/wooden_closet_open.ogg' + close_sound = 'sound/machines/wooden_closet_close.ogg' + open_sound_volume = 25 + close_sound_volume = 50 + var/obj/item/tank/internals/emergency_oxygen/tank + +/obj/structure/closet/crate/critter/Initialize() + . = ..() + tank = new + +/obj/structure/closet/crate/critter/Destroy() + var/turf/T = get_turf(src) + if(tank) + tank.forceMove(T) + tank = null + + return ..() + +/obj/structure/closet/crate/critter/update_icon_state() + return + +/obj/structure/closet/crate/critter/update_overlays() + . = ..() + if(opened) + . += "crittercrate_door_open" + else + . += "crittercrate_door" + if(manifest) + . += "manifest" + +/obj/structure/closet/crate/critter/return_air() + if(tank) + return tank.air_contents + else + return loc.return_air() + +/obj/structure/closet/crate/critter/return_analyzable_air() + if(tank) + return tank.return_analyzable_air() + else + return null diff --git a/code/game/objects/structures/crates_lockers/crates/large.dm b/code/game/objects/structures/crates_lockers/crates/large.dm index 04a6aa53dc8a..bd21cc5f0bba 100644 --- a/code/game/objects/structures/crates_lockers/crates/large.dm +++ b/code/game/objects/structures/crates_lockers/crates/large.dm @@ -1,47 +1,47 @@ -/obj/structure/closet/crate/large - name = "large crate" - desc = "A hefty wooden crate. You'll need a crowbar to get it open." - icon_state = "largecrate" - density = TRUE - material_drop = /obj/item/stack/sheet/mineral/wood - material_drop_amount = 4 - delivery_icon = "deliverybox" - integrity_failure = 0 //Makes the crate break when integrity reaches 0, instead of opening and becoming an invisible sprite. - open_sound = 'sound/machines/wooden_closet_open.ogg' - close_sound = 'sound/machines/wooden_closet_close.ogg' - open_sound_volume = 25 - close_sound_volume = 50 - -/obj/structure/closet/crate/large/attack_hand(mob/user) - add_fingerprint(user) - if(manifest) - tear_manifest(user) - else - to_chat(user, "You need a crowbar to pry this open!") - -/obj/structure/closet/crate/large/attackby(obj/item/W, mob/user, params) - if(W.tool_behaviour == TOOL_CROWBAR) - if(manifest) - tear_manifest(user) - - user.visible_message("[user] pries \the [src] open.", \ - "You pry open \the [src].", \ - "You hear splitting wood.") - playsound(src.loc, 'sound/weapons/slashmiss.ogg', 75, TRUE) - - var/turf/T = get_turf(src) - for(var/i in 1 to material_drop_amount) - new material_drop(src) - for(var/atom/movable/AM in contents) - AM.forceMove(T) - - qdel(src) - - else - if(user.a_intent == INTENT_HARM) //Only return ..() if intent is harm, otherwise return 0 or just end it. - return ..() //Stops it from opening and turning invisible when items are used on it. - - else - to_chat(user, "You need a crowbar to pry this open!") - return FALSE //Just stop. Do nothing. Don't turn into an invisible sprite. Don't open like a locker. - //The large crate has no non-attack interactions other than the crowbar, anyway. +/obj/structure/closet/crate/large + name = "large crate" + desc = "A hefty wooden crate. You'll need a crowbar to get it open." + icon_state = "largecrate" + density = TRUE + material_drop = /obj/item/stack/sheet/mineral/wood + material_drop_amount = 4 + delivery_icon = "deliverybox" + integrity_failure = 0 //Makes the crate break when integrity reaches 0, instead of opening and becoming an invisible sprite. + open_sound = 'sound/machines/wooden_closet_open.ogg' + close_sound = 'sound/machines/wooden_closet_close.ogg' + open_sound_volume = 25 + close_sound_volume = 50 + +/obj/structure/closet/crate/large/attack_hand(mob/user) + add_fingerprint(user) + if(manifest) + tear_manifest(user) + else + to_chat(user, "You need a crowbar to pry this open!") + +/obj/structure/closet/crate/large/attackby(obj/item/W, mob/user, params) + if(W.tool_behaviour == TOOL_CROWBAR) + if(manifest) + tear_manifest(user) + + user.visible_message("[user] pries \the [src] open.", \ + "You pry open \the [src].", \ + "You hear splitting wood.") + playsound(src.loc, 'sound/weapons/slashmiss.ogg', 75, TRUE) + + var/turf/T = get_turf(src) + for(var/i in 1 to material_drop_amount) + new material_drop(src) + for(var/atom/movable/AM in contents) + AM.forceMove(T) + + qdel(src) + + else + if(user.a_intent == INTENT_HARM) //Only return ..() if intent is harm, otherwise return 0 or just end it. + return ..() //Stops it from opening and turning invisible when items are used on it. + + else + to_chat(user, "You need a crowbar to pry this open!") + return FALSE //Just stop. Do nothing. Don't turn into an invisible sprite. Don't open like a locker. + //The large crate has no non-attack interactions other than the crowbar, anyway. diff --git a/code/game/objects/structures/crates_lockers/crates/secure.dm b/code/game/objects/structures/crates_lockers/crates/secure.dm index baf607799972..f83d99672d1c 100644 --- a/code/game/objects/structures/crates_lockers/crates/secure.dm +++ b/code/game/objects/structures/crates_lockers/crates/secure.dm @@ -1,104 +1,104 @@ -/obj/structure/closet/crate/secure - desc = "A secure crate." - name = "secure crate" - icon_state = "securecrate" - secure = TRUE - locked = TRUE - max_integrity = 500 - armor = list("melee" = 30, "bullet" = 50, "laser" = 50, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80) - var/tamperproof = 0 - damage_deflection = 25 - -/obj/structure/closet/crate/secure/update_overlays() - . = ..() - if(broken) - . += "securecrateemag" - else if(locked) - . += "securecrater" - else - . += "securecrateg" - -/obj/structure/closet/crate/secure/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1) - if(prob(tamperproof) && damage_amount >= DAMAGE_PRECISION) - boom() - else - return ..() - - -/obj/structure/closet/crate/secure/proc/boom(mob/user) - if(user) - to_chat(user, "The crate's anti-tamper system activates!") - log_bomber(user, "has detonated a", src) - for(var/atom/movable/AM in src) - qdel(AM) - explosion(get_turf(src), 0, 1, 5, 5) - qdel(src) - -/obj/structure/closet/crate/secure/weapon - desc = "A secure weapons crate." - name = "weapons crate" - icon_state = "weaponcrate" - -/obj/structure/closet/crate/secure/plasma - desc = "A secure plasma crate." - name = "plasma crate" - icon_state = "plasmacrate" - -/obj/structure/closet/crate/secure/gear - desc = "A secure gear crate." - name = "gear crate" - icon_state = "secgearcrate" - -/obj/structure/closet/crate/secure/hydroponics - desc = "A crate with a lock on it, painted in the scheme of the station's botanists." - name = "secure hydroponics crate" - icon_state = "hydrosecurecrate" - -/obj/structure/closet/crate/secure/engineering - desc = "A crate with a lock on it, painted in the scheme of the station's engineers." - name = "secure engineering crate" - icon_state = "engi_secure_crate" - -/obj/structure/closet/crate/secure/science - name = "secure science crate" - desc = "A crate with a lock on it, painted in the scheme of the station's scientists." - icon_state = "scisecurecrate" - -/obj/structure/closet/crate/secure/owned - name = "private crate" - desc = "A crate cover designed to only open for who purchased its contents." - icon_state = "privatecrate" - var/datum/bank_account/buyer_account - var/privacy_lock = TRUE - -/obj/structure/closet/crate/secure/owned/examine(mob/user) - . = ..() - . += "It's locked with a privacy lock, and can only be unlocked by the buyer's ID." - -/obj/structure/closet/crate/secure/owned/Initialize(mapload, datum/bank_account/_buyer_account) - . = ..() - buyer_account = _buyer_account - -/obj/structure/closet/crate/secure/owned/togglelock(mob/living/user, silent) - if(privacy_lock) - if(!broken) - var/obj/item/card/id/id_card = user.get_idcard(TRUE) - if(id_card) - if(id_card.registered_account) - if(id_card.registered_account == buyer_account) - if(iscarbon(user)) - add_fingerprint(user) - locked = !locked - user.visible_message("[user] unlocks [src]'s privacy lock.", - "You unlock [src]'s privacy lock.") - privacy_lock = FALSE - update_icon() - else if(!silent) - to_chat(user, "Bank account does not match with buyer!") - else if(!silent) - to_chat(user, "No linked bank account detected!") - else if(!silent) - to_chat(user, "No ID detected!") - else if(!silent) - to_chat(user, "[src] is broken!") - else ..() +/obj/structure/closet/crate/secure + desc = "A secure crate." + name = "secure crate" + icon_state = "securecrate" + secure = TRUE + locked = TRUE + max_integrity = 500 + armor = list("melee" = 30, "bullet" = 50, "laser" = 50, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80) + var/tamperproof = 0 + damage_deflection = 25 + +/obj/structure/closet/crate/secure/update_overlays() + . = ..() + if(broken) + . += "securecrateemag" + else if(locked) + . += "securecrater" + else + . += "securecrateg" + +/obj/structure/closet/crate/secure/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1) + if(prob(tamperproof) && damage_amount >= DAMAGE_PRECISION) + boom() + else + return ..() + + +/obj/structure/closet/crate/secure/proc/boom(mob/user) + if(user) + to_chat(user, "The crate's anti-tamper system activates!") + log_bomber(user, "has detonated a", src) + for(var/atom/movable/AM in src) + qdel(AM) + explosion(get_turf(src), 0, 1, 5, 5) + qdel(src) + +/obj/structure/closet/crate/secure/weapon + desc = "A secure weapons crate." + name = "weapons crate" + icon_state = "weaponcrate" + +/obj/structure/closet/crate/secure/plasma + desc = "A secure plasma crate." + name = "plasma crate" + icon_state = "plasmacrate" + +/obj/structure/closet/crate/secure/gear + desc = "A secure gear crate." + name = "gear crate" + icon_state = "secgearcrate" + +/obj/structure/closet/crate/secure/hydroponics + desc = "A crate with a lock on it, painted in the scheme of the station's botanists." + name = "secure hydroponics crate" + icon_state = "hydrosecurecrate" + +/obj/structure/closet/crate/secure/engineering + desc = "A crate with a lock on it, painted in the scheme of the station's engineers." + name = "secure engineering crate" + icon_state = "engi_secure_crate" + +/obj/structure/closet/crate/secure/science + name = "secure science crate" + desc = "A crate with a lock on it, painted in the scheme of the station's scientists." + icon_state = "scisecurecrate" + +/obj/structure/closet/crate/secure/owned + name = "private crate" + desc = "A crate cover designed to only open for who purchased its contents." + icon_state = "privatecrate" + var/datum/bank_account/buyer_account + var/privacy_lock = TRUE + +/obj/structure/closet/crate/secure/owned/examine(mob/user) + . = ..() + . += "It's locked with a privacy lock, and can only be unlocked by the buyer's ID." + +/obj/structure/closet/crate/secure/owned/Initialize(mapload, datum/bank_account/_buyer_account) + . = ..() + buyer_account = _buyer_account + +/obj/structure/closet/crate/secure/owned/togglelock(mob/living/user, silent) + if(privacy_lock) + if(!broken) + var/obj/item/card/id/id_card = user.get_idcard(TRUE) + if(id_card) + if(id_card.registered_account) + if(id_card.registered_account == buyer_account) + if(iscarbon(user)) + add_fingerprint(user) + locked = !locked + user.visible_message("[user] unlocks [src]'s privacy lock.", + "You unlock [src]'s privacy lock.") + privacy_lock = FALSE + update_icon() + else if(!silent) + to_chat(user, "Bank account does not match with buyer!") + else if(!silent) + to_chat(user, "No linked bank account detected!") + else if(!silent) + to_chat(user, "No ID detected!") + else if(!silent) + to_chat(user, "[src] is broken!") + else ..() diff --git a/code/game/objects/structures/displaycase.dm b/code/game/objects/structures/displaycase.dm index b53a4292535f..9fa6dceca2fc 100644 --- a/code/game/objects/structures/displaycase.dm +++ b/code/game/objects/structures/displaycase.dm @@ -1,505 +1,572 @@ -/obj/structure/displaycase - name = "display case" - icon = 'icons/obj/stationobjs.dmi' - icon_state = "glassbox0" - desc = "A display case for prized possessions." - density = TRUE - anchored = TRUE - resistance_flags = ACID_PROOF - armor = list("melee" = 30, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 100) - max_integrity = 200 - integrity_failure = 0.25 - var/obj/item/showpiece = null - var/obj/item/showpiece_type = null //This allows for showpieces that can only hold items if they're the same istype as this. - var/alert = TRUE - var/open = FALSE - var/openable = TRUE - var/obj/item/electronics/airlock/electronics - var/start_showpiece_type = null //add type for items on display - var/list/start_showpieces = list() //Takes sublists in the form of list("type" = /obj/item/bikehorn, "trophy_message" = "henk") - var/trophy_message = "" - var/glass_fix = TRUE - -/obj/structure/displaycase/Initialize() - . = ..() - if(start_showpieces.len && !start_showpiece_type) - var/list/showpiece_entry = pick(start_showpieces) - if (showpiece_entry && showpiece_entry["type"]) - start_showpiece_type = showpiece_entry["type"] - if (showpiece_entry["trophy_message"]) - trophy_message = showpiece_entry["trophy_message"] - if(start_showpiece_type) - showpiece = new start_showpiece_type (src) - update_icon() - -/obj/structure/displaycase/Destroy() - if(electronics) - QDEL_NULL(electronics) - if(showpiece) - QDEL_NULL(showpiece) - return ..() - -/obj/structure/displaycase/examine(mob/user) - . = ..() - if(alert) - . += "Hooked up with an anti-theft system." - if(showpiece) - . += "There's [showpiece] inside." - if(trophy_message) - . += "The plaque reads:\n [trophy_message]" - - -/obj/structure/displaycase/proc/dump() - if (showpiece) - showpiece.forceMove(loc) - showpiece = null - -/obj/structure/displaycase/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) - switch(damage_type) - if(BRUTE) - playsound(src.loc, 'sound/effects/glasshit.ogg', 75, TRUE) - if(BURN) - playsound(src.loc, 'sound/items/welder.ogg', 100, TRUE) - -/obj/structure/displaycase/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - dump() - if(!disassembled) - new /obj/item/shard( src.loc ) - trigger_alarm() - qdel(src) - -/obj/structure/displaycase/obj_break(damage_flag) - if(!broken && !(flags_1 & NODECONSTRUCT_1)) - density = FALSE - broken = 1 - new /obj/item/shard( src.loc ) - playsound(src, "shatter", 70, TRUE) - update_icon() - trigger_alarm() - -/obj/structure/displaycase/proc/trigger_alarm() - //Activate Anti-theft - if(alert) - var/area/alarmed = get_area(src) - alarmed.burglaralert(src) - playsound(src, 'sound/effects/alert.ogg', 50, TRUE) - -/obj/structure/displaycase/update_icon() - var/icon/I - if(open) - I = icon('icons/obj/stationobjs.dmi',"glassbox_open") - else - I = icon('icons/obj/stationobjs.dmi',"glassbox0") - if(broken) - I = icon('icons/obj/stationobjs.dmi',"glassboxb0") - if(showpiece) - var/icon/S = getFlatIcon(showpiece) - S.Scale(17,17) - I.Blend(S,ICON_UNDERLAY,8,8) - src.icon = I - return - -/obj/structure/displaycase/attackby(obj/item/W, mob/user, params) - if(W.GetID() && !broken && openable) - if(allowed(user)) - to_chat(user, "You [open ? "close":"open"] [src].") - toggle_lock(user) - else - to_chat(user, "Access denied.") - else if(W.tool_behaviour == TOOL_WELDER && user.a_intent == INTENT_HELP && !broken) - if(obj_integrity < max_integrity) - if(!W.tool_start_check(user, amount=5)) - return - - to_chat(user, "You begin repairing [src]...") - if(W.use_tool(src, user, 40, amount=5, volume=50)) - obj_integrity = max_integrity - update_icon() - to_chat(user, "You repair [src].") - else - to_chat(user, "[src] is already in good condition!") - return - else if(!alert && W.tool_behaviour == TOOL_CROWBAR && openable) //Only applies to the lab cage and player made display cases - if(broken) - if(showpiece) - to_chat(user, "Remove the displayed object first!") - else - to_chat(user, "You remove the destroyed case.") - qdel(src) - else - to_chat(user, "You start to [open ? "close":"open"] [src]...") - if(W.use_tool(src, user, 20)) - to_chat(user, "You [open ? "close":"open"] [src].") - toggle_lock(user) - else if(open && !showpiece) - if(showpiece_type && !istype(W, showpiece_type)) - to_chat(user, "This doesn't belong in this kind of display.") - return TRUE - if(user.transferItemToLoc(W, src)) - showpiece = W - to_chat(user, "You put [W] on display.") - update_icon() - else if(glass_fix && broken && istype(W, /obj/item/stack/sheet/glass)) - var/obj/item/stack/sheet/glass/G = W - if(G.get_amount() < 2) - to_chat(user, "You need two glass sheets to fix the case!") - return - to_chat(user, "You start fixing [src]...") - if(do_after(user, 20, target = src)) - G.use(2) - broken = 0 - obj_integrity = max_integrity - update_icon() - else - return ..() - -/obj/structure/displaycase/proc/toggle_lock(mob/user) - open = !open - update_icon() - -/obj/structure/displaycase/attack_paw(mob/user) - return attack_hand(user) - -/obj/structure/displaycase/attack_hand(mob/user) - . = ..() - if(.) - return - user.changeNext_move(CLICK_CD_MELEE) - if (showpiece && (broken || open)) - to_chat(user, "You deactivate the hover field built into the case.") - log_combat(user, src, "deactivates the hover field of") - dump() - src.add_fingerprint(user) - update_icon() - return - else - //prevents remote "kicks" with TK - if (!Adjacent(user)) - return - if (user.a_intent == INTENT_HELP) - user.examinate(src) - return - user.visible_message("[user] kicks the display case.", null, null, COMBAT_MESSAGE_RANGE) - log_combat(user, src, "kicks") - user.do_attack_animation(src, ATTACK_EFFECT_KICK) - take_damage(2) - -/obj/structure/displaycase_chassis - anchored = TRUE - density = FALSE - name = "display case chassis" - desc = "The wooden base of a display case." - icon = 'icons/obj/stationobjs.dmi' - icon_state = "glassbox_chassis" - var/obj/item/electronics/airlock/electronics - - -/obj/structure/displaycase_chassis/attackby(obj/item/I, mob/user, params) - if(I.tool_behaviour == TOOL_WRENCH) //The player can only deconstruct the wooden frame - to_chat(user, "You start disassembling [src]...") - I.play_tool_sound(src) - if(I.use_tool(src, user, 30)) - playsound(src.loc, 'sound/items/deconstruct.ogg', 50, TRUE) - new /obj/item/stack/sheet/mineral/wood(get_turf(src), 5) - qdel(src) - - else if(istype(I, /obj/item/electronics/airlock)) - to_chat(user, "You start installing the electronics into [src]...") - I.play_tool_sound(src) - if(do_after(user, 30, target = src) && user.transferItemToLoc(I,src)) - electronics = I - to_chat(user, "You install the airlock electronics.") - - else if(istype(I, /obj/item/stock_parts/card_reader)) - var/obj/item/stock_parts/card_reader/C = I - to_chat(user, "You start adding [C] to [src]...") - if(do_after(user, 20, target = src)) - var/obj/structure/displaycase/forsale/sale = new(src.loc) - if(electronics) - electronics.forceMove(sale) - sale.electronics = electronics - if(electronics.one_access) - sale.req_one_access = electronics.accesses - else - sale.req_access = electronics.accesses - qdel(src) - qdel(C) - - else if(istype(I, /obj/item/stack/sheet/glass)) - var/obj/item/stack/sheet/glass/G = I - if(G.get_amount() < 10) - to_chat(user, "You need ten glass sheets to do this!") - return - to_chat(user, "You start adding [G] to [src]...") - if(do_after(user, 20, target = src)) - G.use(10) - var/obj/structure/displaycase/noalert/display = new(src.loc) - if(electronics) - electronics.forceMove(display) - display.electronics = electronics - if(electronics.one_access) - display.req_one_access = electronics.accesses - else - display.req_access = electronics.accesses - qdel(src) - else - return ..() - -//The lab cage and captain's display case do not spawn with electronics, which is why req_access is needed. -/obj/structure/displaycase/captain - start_showpiece_type = /obj/item/gun/energy/laser/captain - req_access = list(ACCESS_CENT_SPECOPS) //this was intentional, presumably to make it slightly harder for caps to grab their gun roundstart - -/obj/structure/displaycase/labcage - name = "lab cage" - desc = "A glass lab container for storing interesting creatures." - start_showpiece_type = /obj/item/clothing/mask/facehugger/lamarr - req_access = list(ACCESS_RD) - -/obj/structure/displaycase/noalert - alert = FALSE - -/obj/structure/displaycase/trophy - name = "trophy display case" - desc = "Store your trophies of accomplishment in here, and they will stay forever." - var/placer_key = "" - var/added_roundstart = TRUE - var/is_locked = TRUE - integrity_failure = 0 - openable = FALSE - -/obj/structure/displaycase/trophy/Initialize() - . = ..() - GLOB.trophy_cases += src - -/obj/structure/displaycase/trophy/Destroy() - GLOB.trophy_cases -= src - return ..() - -/obj/structure/displaycase/trophy/attackby(obj/item/W, mob/user, params) - - if(!user.Adjacent(src)) //no TK museology - return - if(user.a_intent == INTENT_HARM) - return ..() - - if(user.is_holding_item_of_type(/obj/item/key/displaycase)) - if(added_roundstart) - is_locked = !is_locked - to_chat(user, "You [!is_locked ? "un" : ""]lock the case.") - else - to_chat(user, "The lock is stuck shut!") - return - - if(is_locked) - to_chat(user, "The case is shut tight with an old fashioned physical lock. Maybe you should ask the curator for the key?") - return - - if(!added_roundstart) - to_chat(user, "You've already put something new in this case!") - return - - if(is_type_in_typecache(W, GLOB.blacklisted_cargo_types)) - to_chat(user, "The case rejects the [W]!") - return - - for(var/a in W.GetAllContents()) - if(is_type_in_typecache(a, GLOB.blacklisted_cargo_types)) - to_chat(user, "The case rejects the [W]!") - return - - if(user.transferItemToLoc(W, src)) - - if(showpiece) - to_chat(user, "You press a button, and [showpiece] descends into the floor of the case.") - QDEL_NULL(showpiece) - - to_chat(user, "You insert [W] into the case.") - showpiece = W - added_roundstart = FALSE - update_icon() - - placer_key = user.ckey - - trophy_message = W.desc //default value - - var/chosen_plaque = stripped_input(user, "What would you like the plaque to say? Default value is item's description.", "Trophy Plaque") - if(chosen_plaque) - if(user.Adjacent(src)) - trophy_message = chosen_plaque - to_chat(user, "You set the plaque's text.") - else - to_chat(user, "You are too far to set the plaque's text!") - - SSpersistence.SaveTrophy(src) - return TRUE - - else - to_chat(user, "\The [W] is stuck to your hand, you can't put it in the [src.name]!") - - return - -/obj/structure/displaycase/trophy/dump() - if (showpiece) - if(added_roundstart) - visible_message("The [showpiece] crumbles to dust!") - new /obj/effect/decal/cleanable/ash(loc) - QDEL_NULL(showpiece) - else - ..() - -/obj/item/key/displaycase - name = "display case key" - desc = "The key to the curator's display cases." - -/obj/item/showpiece_dummy - name = "Cheap replica" - -/obj/item/showpiece_dummy/Initialize(mapload, path) - . = ..() - var/obj/item/I = path - name = initial(I.name) - icon = initial(I.icon) - icon_state = initial(I.icon_state) - -/obj/structure/displaycase/forsale - name = "vend-a-tray" - icon = 'icons/obj/stationobjs.dmi' - icon_state = "laserbox0" - desc = "A display case with an ID-card swiper. Use your ID to purchase the contents." - density = FALSE - max_integrity = 100 - req_access = list(ACCESS_KITCHEN) - 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. - ///The price of the item being sold. Altered by grab intent ID use. - var/sale_price = 20 - ///The Account which will recieve payment for purchases. Set by the first ID to swipe the tray. - var/datum/bank_account/payments_acc = null - -/obj/structure/displaycase/forsale/update_icon() //remind me to fix my shitcode later - var/icon/I - if(open) - I = icon('icons/obj/stationobjs.dmi',"laserboxb0") - else - I = icon('icons/obj/stationobjs.dmi',"laserbox0") - if(!showpiece && !open) - I = icon('icons/obj/stationobjs.dmi',"laserbox_open") - if(broken) - I = icon('icons/obj/stationobjs.dmi',"laserbox_broken") - if(showpiece) - var/icon/S = getFlatIcon(showpiece) - S.Scale(17,17) - I.Blend(S,ICON_UNDERLAY,8,12) - src.icon = I - return - -/obj/structure/displaycase/forsale/attackby(obj/item/I, mob/living/user, params) - if(isidcard(I)) - //Card Registration - var/obj/item/card/id/potential_acc = I - if(!potential_acc.registered_account) - to_chat(user, "This ID card has no account registered!") - return - if(!payments_acc && potential_acc.registered_account) - payments_acc = potential_acc.registered_account - playsound(src, 'sound/machines/click.ogg', 20, TRUE) - to_chat(user, "Vend-a-tray registered. Use your ID on grab intent to change the sale price, or disarm intent to open the tray.") - return - //Buying the contained item with the ID. - switch(user.a_intent) - if(INTENT_HELP) - if(!showpiece) - to_chat(user, "There's nothing for sale.") - return TRUE - if(broken) - to_chat(user, "[src] appears to be broken.") - return TRUE - var/confirm = alert(user, "Purchase [showpiece] for [sale_price]?", "Purchase?", "Confirm", "Cancel") - if(confirm == "Cancel" || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return TRUE - var/datum/bank_account/account = potential_acc.registered_account - if(!account.has_money(sale_price)) - to_chat(user, "You do not possess the funds to purchase this.") - return TRUE - else - account.adjust_money(-sale_price) - if(payments_acc) - payments_acc.adjust_money(sale_price) - user.put_in_hands(showpiece) - to_chat(user, "You purchase [showpiece] for [sale_price] credits.") - playsound(src, 'sound/effects/cashregister.ogg', 40, TRUE) - icon = 'icons/obj/stationobjs.dmi' - flick("laserbox_vend", src) - showpiece = null - update_icon() - return TRUE - //Setting the object's price. - if(INTENT_GRAB) - var/new_price_input = input(user,"Set the sale price for this vend-a-tray.","new price",0) as num|null - if(isnull(new_price_input) || (payments_acc != potential_acc.registered_account)) - to_chat(user, "The vend-a-tray rejects your new price.") - return - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK) ) - to_chat(user, "You need to get closer!") - return - new_price_input = clamp(round(new_price_input, 1), 10, 1000) - sale_price = new_price_input - to_chat(user, "The cost is now set to [sale_price].") - return TRUE - if(INTENT_DISARM || INTENT_HARM) - if(payments_acc && payments_acc != potential_acc.registered_account) - to_chat(user, "This Vend-a-tray is already registered!") - return - if(I.tool_behaviour == TOOL_WRENCH && open && user.a_intent == INTENT_HELP ) - if(anchored) - to_chat(user, "You start unsecuring [src]...") - else - to_chat(user, "You start securing [src]...") - if(I.use_tool(src, user, 16, volume=50)) - if(QDELETED(I)) - return - if(anchored) - to_chat(user, "You unsecure [src].") - else - to_chat(user, "You secure [src].") - anchored = !anchored - return - else if(I.tool_behaviour == TOOL_WRENCH && !open && user.a_intent == INTENT_HELP) - to_chat(user, "[src] must be open to move it.") - return - if(istype(I, /obj/item/pda)) - return TRUE - . = ..() - -/obj/structure/displaycase/forsale/multitool_act(mob/living/user, obj/item/I) - . = ..() - if(obj_integrity <= (integrity_failure * max_integrity)) - to_chat(user, "You start recalibrating [src]'s hover field...") - if(do_after(user, 20, target = src)) - broken = 0 - obj_integrity = max_integrity - update_icon() - return TRUE - -/obj/structure/displaycase/forsale/emag_act(mob/user) - . = ..() - payments_acc = null - req_access = list() - to_chat(user, "[src]'s card reader fizzles and smokes, and the account owner is reset.") - -/obj/structure/displaycase/forsale/examine(mob/user) - . = ..() - if(showpiece && !open) - . += "[showpiece] is for sale for [sale_price] credits." - if(broken) - . += "[src] is sparking and the hover field generator seems to be overloaded. Use a multitool to fix it." - -/obj/structure/displaycase/forsale/obj_break(damage_flag) - if(!broken && !(flags_1 & NODECONSTRUCT_1)) - broken = TRUE - playsound(src, "shatter", 70, TRUE) - update_icon() - trigger_alarm() //In case it's given an alarm anyway. +/obj/structure/displaycase + name = "display case" + icon = 'icons/obj/stationobjs.dmi' + icon_state = "glassbox0" + desc = "A display case for prized possessions." + density = TRUE + anchored = TRUE + resistance_flags = ACID_PROOF + armor = list("melee" = 30, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 100) + max_integrity = 200 + integrity_failure = 0.25 + var/obj/item/showpiece = null + var/obj/item/showpiece_type = null //This allows for showpieces that can only hold items if they're the same istype as this. + var/alert = TRUE + var/open = FALSE + var/openable = TRUE + var/obj/item/electronics/airlock/electronics + var/start_showpiece_type = null //add type for items on display + var/list/start_showpieces = list() //Takes sublists in the form of list("type" = /obj/item/bikehorn, "trophy_message" = "henk") + var/trophy_message = "" + var/glass_fix = TRUE + +/obj/structure/displaycase/Initialize() + . = ..() + if(start_showpieces.len && !start_showpiece_type) + var/list/showpiece_entry = pick(start_showpieces) + if (showpiece_entry && showpiece_entry["type"]) + start_showpiece_type = showpiece_entry["type"] + if (showpiece_entry["trophy_message"]) + trophy_message = showpiece_entry["trophy_message"] + if(start_showpiece_type) + showpiece = new start_showpiece_type (src) + update_icon() + +/obj/structure/displaycase/Destroy() + if(electronics) + QDEL_NULL(electronics) + if(showpiece) + QDEL_NULL(showpiece) + return ..() + +/obj/structure/displaycase/examine(mob/user) + . = ..() + if(alert) + . += "Hooked up with an anti-theft system." + if(showpiece) + . += "There's [showpiece] inside." + if(trophy_message) + . += "The plaque reads:\n [trophy_message]" + + +/obj/structure/displaycase/proc/dump() + if (showpiece) + showpiece.forceMove(loc) + showpiece = null + +/obj/structure/displaycase/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) + switch(damage_type) + if(BRUTE) + playsound(src.loc, 'sound/effects/glasshit.ogg', 75, TRUE) + if(BURN) + playsound(src.loc, 'sound/items/welder.ogg', 100, TRUE) + +/obj/structure/displaycase/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + dump() + if(!disassembled) + new /obj/item/shard( src.loc ) + trigger_alarm() + qdel(src) + +/obj/structure/displaycase/obj_break(damage_flag) + if(!broken && !(flags_1 & NODECONSTRUCT_1)) + density = FALSE + broken = 1 + new /obj/item/shard( src.loc ) + playsound(src, "shatter", 70, TRUE) + update_icon() + trigger_alarm() + +/obj/structure/displaycase/proc/trigger_alarm() + //Activate Anti-theft + if(alert) + var/area/alarmed = get_area(src) + alarmed.burglaralert(src) + playsound(src, 'sound/effects/alert.ogg', 50, TRUE) + +/obj/structure/displaycase/update_icon() + var/icon/I + if(open) + I = icon('icons/obj/stationobjs.dmi',"glassbox_open") + else + I = icon('icons/obj/stationobjs.dmi',"glassbox0") + if(broken) + I = icon('icons/obj/stationobjs.dmi',"glassboxb0") + if(showpiece) + var/icon/S = getFlatIcon(showpiece) + S.Scale(17,17) + I.Blend(S,ICON_UNDERLAY,8,8) + src.icon = I + return + +/obj/structure/displaycase/attackby(obj/item/W, mob/user, params) + if(W.GetID() && !broken && openable) + if(allowed(user)) + to_chat(user, "You [open ? "close":"open"] [src].") + toggle_lock(user) + else + to_chat(user, "Access denied.") + else if(W.tool_behaviour == TOOL_WELDER && user.a_intent == INTENT_HELP && !broken) + if(obj_integrity < max_integrity) + if(!W.tool_start_check(user, amount=5)) + return + + to_chat(user, "You begin repairing [src]...") + if(W.use_tool(src, user, 40, amount=5, volume=50)) + obj_integrity = max_integrity + update_icon() + to_chat(user, "You repair [src].") + else + to_chat(user, "[src] is already in good condition!") + return + else if(!alert && W.tool_behaviour == TOOL_CROWBAR && openable) //Only applies to the lab cage and player made display cases + if(broken) + if(showpiece) + to_chat(user, "Remove the displayed object first!") + else + to_chat(user, "You remove the destroyed case.") + qdel(src) + else + to_chat(user, "You start to [open ? "close":"open"] [src]...") + if(W.use_tool(src, user, 20)) + to_chat(user, "You [open ? "close":"open"] [src].") + toggle_lock(user) + else if(open && !showpiece) + if(showpiece_type && !istype(W, showpiece_type)) + to_chat(user, "This doesn't belong in this kind of display.") + return TRUE + if(user.transferItemToLoc(W, src)) + showpiece = W + to_chat(user, "You put [W] on display.") + update_icon() + else if(glass_fix && broken && istype(W, /obj/item/stack/sheet/glass)) + var/obj/item/stack/sheet/glass/G = W + if(G.get_amount() < 2) + to_chat(user, "You need two glass sheets to fix the case!") + return + to_chat(user, "You start fixing [src]...") + if(do_after(user, 20, target = src)) + G.use(2) + broken = 0 + obj_integrity = max_integrity + update_icon() + else + return ..() + +/obj/structure/displaycase/proc/toggle_lock(mob/user) + open = !open + update_icon() + +/obj/structure/displaycase/attack_paw(mob/user) + return attack_hand(user) + +/obj/structure/displaycase/attack_hand(mob/user) + . = ..() + if(.) + return + user.changeNext_move(CLICK_CD_MELEE) + if (showpiece && (broken || open)) + to_chat(user, "You deactivate the hover field built into the case.") + log_combat(user, src, "deactivates the hover field of") + dump() + src.add_fingerprint(user) + update_icon() + return + else + //prevents remote "kicks" with TK + if (!Adjacent(user)) + return + if (user.a_intent == INTENT_HELP) + user.examinate(src) + return + user.visible_message("[user] kicks the display case.", null, null, COMBAT_MESSAGE_RANGE) + log_combat(user, src, "kicks") + user.do_attack_animation(src, ATTACK_EFFECT_KICK) + take_damage(2) + +/obj/structure/displaycase_chassis + anchored = TRUE + density = FALSE + name = "display case chassis" + desc = "The wooden base of a display case." + icon = 'icons/obj/stationobjs.dmi' + icon_state = "glassbox_chassis" + var/obj/item/electronics/airlock/electronics + + +/obj/structure/displaycase_chassis/attackby(obj/item/I, mob/user, params) + if(I.tool_behaviour == TOOL_WRENCH) //The player can only deconstruct the wooden frame + to_chat(user, "You start disassembling [src]...") + I.play_tool_sound(src) + if(I.use_tool(src, user, 30)) + playsound(src.loc, 'sound/items/deconstruct.ogg', 50, TRUE) + new /obj/item/stack/sheet/mineral/wood(get_turf(src), 5) + qdel(src) + + else if(istype(I, /obj/item/electronics/airlock)) + to_chat(user, "You start installing the electronics into [src]...") + I.play_tool_sound(src) + if(do_after(user, 30, target = src) && user.transferItemToLoc(I,src)) + electronics = I + to_chat(user, "You install the airlock electronics.") + + else if(istype(I, /obj/item/stock_parts/card_reader)) + var/obj/item/stock_parts/card_reader/C = I + to_chat(user, "You start adding [C] to [src]...") + if(do_after(user, 20, target = src)) + var/obj/structure/displaycase/forsale/sale = new(src.loc) + if(electronics) + electronics.forceMove(sale) + sale.electronics = electronics + if(electronics.one_access) + sale.req_one_access = electronics.accesses + else + sale.req_access = electronics.accesses + qdel(src) + qdel(C) + + else if(istype(I, /obj/item/stack/sheet/glass)) + var/obj/item/stack/sheet/glass/G = I + if(G.get_amount() < 10) + to_chat(user, "You need ten glass sheets to do this!") + return + to_chat(user, "You start adding [G] to [src]...") + if(do_after(user, 20, target = src)) + G.use(10) + var/obj/structure/displaycase/noalert/display = new(src.loc) + if(electronics) + electronics.forceMove(display) + display.electronics = electronics + if(electronics.one_access) + display.req_one_access = electronics.accesses + else + display.req_access = electronics.accesses + qdel(src) + else + return ..() + +//The lab cage and captain's display case do not spawn with electronics, which is why req_access is needed. +/obj/structure/displaycase/captain + start_showpiece_type = /obj/item/gun/energy/laser/captain + req_access = list(ACCESS_CENT_SPECOPS) //this was intentional, presumably to make it slightly harder for caps to grab their gun roundstart + +/obj/structure/displaycase/labcage + name = "lab cage" + desc = "A glass lab container for storing interesting creatures." + start_showpiece_type = /obj/item/clothing/mask/facehugger/lamarr + req_access = list(ACCESS_RD) + +/obj/structure/displaycase/noalert + alert = FALSE + +/obj/structure/displaycase/trophy + name = "trophy display case" + desc = "Store your trophies of accomplishment in here, and they will stay forever." + var/placer_key = "" + var/added_roundstart = TRUE + var/is_locked = TRUE + integrity_failure = 0 + openable = FALSE + +/obj/structure/displaycase/trophy/Initialize() + . = ..() + GLOB.trophy_cases += src + +/obj/structure/displaycase/trophy/Destroy() + GLOB.trophy_cases -= src + return ..() + +/obj/structure/displaycase/trophy/attackby(obj/item/W, mob/user, params) + + if(!user.Adjacent(src)) //no TK museology + return + if(user.a_intent == INTENT_HARM) + return ..() + + if(user.is_holding_item_of_type(/obj/item/key/displaycase)) + if(added_roundstart) + is_locked = !is_locked + to_chat(user, "You [!is_locked ? "un" : ""]lock the case.") + else + to_chat(user, "The lock is stuck shut!") + return + + if(is_locked) + to_chat(user, "The case is shut tight with an old-fashioned physical lock. Maybe you should ask the curator for the key?") + return + + if(!added_roundstart) + to_chat(user, "You've already put something new in this case!") + return + + if(is_type_in_typecache(W, GLOB.blacklisted_cargo_types)) + to_chat(user, "The case rejects the [W]!") + return + + for(var/a in W.GetAllContents()) + if(is_type_in_typecache(a, GLOB.blacklisted_cargo_types)) + to_chat(user, "The case rejects the [W]!") + return + + if(user.transferItemToLoc(W, src)) + + if(showpiece) + to_chat(user, "You press a button, and [showpiece] descends into the floor of the case.") + QDEL_NULL(showpiece) + + to_chat(user, "You insert [W] into the case.") + showpiece = W + added_roundstart = FALSE + update_icon() + + placer_key = user.ckey + + trophy_message = W.desc //default value + + var/chosen_plaque = stripped_input(user, "What would you like the plaque to say? Default value is item's description.", "Trophy Plaque") + if(chosen_plaque) + if(user.Adjacent(src)) + trophy_message = chosen_plaque + to_chat(user, "You set the plaque's text.") + else + to_chat(user, "You are too far to set the plaque's text!") + + SSpersistence.SaveTrophy(src) + return TRUE + + else + to_chat(user, "\The [W] is stuck to your hand, you can't put it in the [src.name]!") + + return + +/obj/structure/displaycase/trophy/dump() + if (showpiece) + if(added_roundstart) + visible_message("The [showpiece] crumbles to dust!") + new /obj/effect/decal/cleanable/ash(loc) + QDEL_NULL(showpiece) + else + ..() + +/obj/item/key/displaycase + name = "display case key" + desc = "The key to the curator's display cases." + +/obj/item/showpiece_dummy + name = "Cheap replica" + +/obj/item/showpiece_dummy/Initialize(mapload, path) + . = ..() + var/obj/item/I = path + name = initial(I.name) + icon = initial(I.icon) + icon_state = initial(I.icon_state) + +/obj/structure/displaycase/forsale + name = "vend-a-tray" + icon = 'icons/obj/stationobjs.dmi' + icon_state = "laserbox0" + desc = "A display case with an ID-card swiper. Use your ID to purchase the contents." + density = FALSE + max_integrity = 100 + req_access = null + 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. + ///The price of the item being sold. Altered by grab intent ID use. + var/sale_price = 20 + ///The Account which will receive payment for purchases. Set by the first ID to swipe the tray. + var/datum/bank_account/payments_acc = null + ///We're using the same trick as paper does in order to cache the image, and only load the UI when messed with. + var/list/viewing_ui = list() + +/obj/structure/displaycase/forsale/update_icon() //remind me to fix my shitcode later + var/icon/I + if(open) + I = icon('icons/obj/stationobjs.dmi',"laserboxb0") + else + I = icon('icons/obj/stationobjs.dmi',"laserbox0") + if(!showpiece && !open) + I = icon('icons/obj/stationobjs.dmi',"laserbox_open") + if(broken) + I = icon('icons/obj/stationobjs.dmi',"laserbox_broken") + if(showpiece) + var/icon/S = getFlatIcon(showpiece) + S.Scale(17,17) + I.Blend(S,ICON_UNDERLAY,8,12) + src.icon = I + return + +/obj/structure/displaycase/forsale/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "Vendatray", name) + ui.set_autoupdate(FALSE) + viewing_ui[user] = ui + ui.open() + +/obj/structure/displaycase/forsale/ui_data(mob/user) + var/list/data = list() + var/register = FALSE + if(payments_acc) + register = TRUE + data["owner_name"] = payments_acc.account_holder + if(showpiece) + data["product_name"] = capitalize(showpiece.name) + var/base64 = icon2base64(icon(showpiece.icon, showpiece.icon_state)) + data["product_icon"] = base64 + data["registered"] = register + data["product_cost"] = sale_price + data["tray_open"] = open + return data + +/obj/structure/displaycase/forsale/ui_act(action, params) + if(..()) + return + var/obj/item/card/id/potential_acc = usr.get_idcard(hand_first = TRUE) + switch(action) + if("Buy") + if(!showpiece) + to_chat(usr, "There's nothing for sale.") + return TRUE + if(broken) + to_chat(usr, "[src] appears to be broken.") + return TRUE + if(!payments_acc) + to_chat(usr, "[src] hasn't been registered yet.") + return TRUE + if(!usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return TRUE + if(!potential_acc) + to_chat(usr, "No ID card detected.") + return + var/datum/bank_account/account = potential_acc.registered_account + if(!account) + to_chat(usr, "[potential_acc] has no account registered!") + return + if(!account.has_money(sale_price)) + to_chat(usr, "You do not possess the funds to purchase this.") + return TRUE + else + account.adjust_money(-sale_price) + if(payments_acc) + payments_acc.adjust_money(sale_price) + usr.put_in_hands(showpiece) + to_chat(usr, "You purchase [showpiece] for [sale_price] credits.") + playsound(src, 'sound/effects/cashregister.ogg', 40, TRUE) + icon = 'icons/obj/stationobjs.dmi' + flick("laserbox_vend", src) + showpiece = null + update_icon() + SStgui.update_uis(src) + return TRUE + if("Open") + if(!payments_acc) + to_chat(usr, "[src] hasn't been registered yet.") + return TRUE + if(!potential_acc || !potential_acc.registered_account) + return + if(!check_access(potential_acc)) + playsound(src, 'sound/machines/buzz-sigh.ogg', 50, TRUE) + return + toggle_lock() + SStgui.update_uis(src) + if("Register") + if(payments_acc) + return + if(!potential_acc || !potential_acc.registered_account) + return + if(!check_access(potential_acc)) + playsound(src, 'sound/machines/buzz-sigh.ogg', 50, TRUE) + return + payments_acc = potential_acc.registered_account + playsound(src, 'sound/machines/click.ogg', 20, TRUE) + if("Adjust") + if(!check_access(potential_acc) || potential_acc.registered_account != payments_acc) + playsound(src, 'sound/machines/buzz-sigh.ogg', 50, TRUE) + return + + var/new_price_input = input(usr,"Set the sale price for this vend-a-tray.","new price",0) as num|null + if(isnull(new_price_input) || (payments_acc != potential_acc.registered_account)) + to_chat(usr, "[src] rejects your new price.") + return + if(!usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK) ) + to_chat(usr, "You need to get closer!") + return + new_price_input = clamp(round(new_price_input, 1), 10, 1000) + sale_price = new_price_input + to_chat(usr, "The cost is now set to [sale_price].") + SStgui.update_uis(src) + return TRUE + . = TRUE +/obj/structure/displaycase/forsale/attackby(obj/item/I, mob/living/user, params) + if(isidcard(I)) + //Card Registration + var/obj/item/card/id/potential_acc = I + if(!potential_acc.registered_account) + to_chat(user, "This ID card has no account registered!") + return + if(payments_acc == potential_acc.registered_account) + playsound(src, 'sound/machines/click.ogg', 20, TRUE) + toggle_lock() + return + if(istype(I, /obj/item/pda)) + return TRUE + SStgui.update_uis(src) + . = ..() + + +/obj/structure/displaycase/forsale/multitool_act(mob/living/user, obj/item/I) + . = ..() + if(obj_integrity <= (integrity_failure * max_integrity)) + to_chat(user, "You start recalibrating [src]'s hover field...") + if(do_after(user, 20, target = src)) + broken = 0 + obj_integrity = max_integrity + update_icon() + return TRUE + +/obj/structure/displaycase/forsale/wrench_act(mob/living/user, obj/item/I) + . = ..() + if(open && user.a_intent == INTENT_HELP ) + if(anchored) + to_chat(user, "You start unsecuring [src]...") + else + to_chat(user, "You start securing [src]...") + if(I.use_tool(src, user, 16, volume=50)) + if(QDELETED(I)) + return + if(anchored) + to_chat(user, "You unsecure [src].") + else + to_chat(user, "You secure [src].") + anchored = !anchored + return + else if(!open && user.a_intent == INTENT_HELP) + to_chat(user, "[src] must be open to move it.") + return + +/obj/structure/displaycase/forsale/emag_act(mob/user) + . = ..() + payments_acc = null + req_access = list() + to_chat(user, "[src]'s card reader fizzles and smokes, and the account owner is reset.") + +/obj/structure/displaycase/forsale/examine(mob/user) + . = ..() + if(showpiece && !open) + . += "[showpiece] is for sale for [sale_price] credits." + if(broken) + . += "[src] is sparking and the hover field generator seems to be overloaded. Use a multitool to fix it." + +/obj/structure/displaycase/forsale/obj_break(damage_flag) + if(!broken && !(flags_1 & NODECONSTRUCT_1)) + broken = TRUE + playsound(src, "shatter", 70, TRUE) + update_icon() + trigger_alarm() //In case it's given an alarm anyway. + +/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_access = list(ACCESS_KITCHEN) diff --git a/code/game/objects/structures/dresser.dm b/code/game/objects/structures/dresser.dm index b7621500e3a3..f7eb4c0a29d4 100644 --- a/code/game/objects/structures/dresser.dm +++ b/code/game/objects/structures/dresser.dm @@ -1,58 +1,58 @@ -/obj/structure/dresser - name = "dresser" - desc = "A nicely-crafted wooden dresser. It's filled with lots of undies." - icon = 'icons/obj/stationobjs.dmi' - icon_state = "dresser" - density = TRUE - anchored = TRUE - -/obj/structure/dresser/attackby(obj/item/I, mob/user, params) - if(I.tool_behaviour == TOOL_WRENCH) - to_chat(user, "You begin to [anchored ? "unwrench" : "wrench"] [src].") - if(I.use_tool(src, user, 20, volume=50)) - to_chat(user, "You successfully [anchored ? "unwrench" : "wrench"] [src].") - setAnchored(!anchored) - else - return ..() - -/obj/structure/dresser/deconstruct(disassembled = TRUE) - new /obj/item/stack/sheet/mineral/wood(drop_location(), 10) - qdel(src) - -/obj/structure/dresser/attack_hand(mob/user) - . = ..() - if(.) - return - if(!Adjacent(user))//no tele-grooming - return - if(ishuman(user)) - var/mob/living/carbon/human/H = user - - if(H.dna && H.dna.species && (NO_UNDERWEAR in H.dna.species.species_traits)) - to_chat(user, "You are not capable of wearing underwear.") - return - - var/choice = input(user, "Underwear, Undershirt, or Socks?", "Changing") as null|anything in list("Underwear","Underwear Color","Undershirt","Socks") - - if(!Adjacent(user)) - return - switch(choice) - if("Underwear") - var/new_undies = input(user, "Select your underwear", "Changing") as null|anything in GLOB.underwear_list - if(new_undies) - H.underwear = new_undies - if("Underwear Color") - var/new_underwear_color = input(H, "Choose your underwear color", "Underwear Color","#"+H.underwear_color) as color|null - if(new_underwear_color) - H.underwear_color = sanitize_hexcolor(new_underwear_color) - if("Undershirt") - var/new_undershirt = input(user, "Select your undershirt", "Changing") as null|anything in GLOB.undershirt_list - if(new_undershirt) - H.undershirt = new_undershirt - if("Socks") - var/new_socks = input(user, "Select your socks", "Changing") as null|anything in GLOB.socks_list - if(new_socks) - H.socks= new_socks - - add_fingerprint(H) - H.update_body() +/obj/structure/dresser + name = "dresser" + desc = "A nicely-crafted wooden dresser. It's filled with lots of undies." + icon = 'icons/obj/stationobjs.dmi' + icon_state = "dresser" + density = TRUE + anchored = TRUE + +/obj/structure/dresser/attackby(obj/item/I, mob/user, params) + if(I.tool_behaviour == TOOL_WRENCH) + to_chat(user, "You begin to [anchored ? "unwrench" : "wrench"] [src].") + if(I.use_tool(src, user, 20, volume=50)) + to_chat(user, "You successfully [anchored ? "unwrench" : "wrench"] [src].") + setAnchored(!anchored) + else + return ..() + +/obj/structure/dresser/deconstruct(disassembled = TRUE) + new /obj/item/stack/sheet/mineral/wood(drop_location(), 10) + qdel(src) + +/obj/structure/dresser/attack_hand(mob/user) + . = ..() + if(.) + return + if(!Adjacent(user))//no tele-grooming + return + if(ishuman(user)) + var/mob/living/carbon/human/H = user + + if(H.dna && H.dna.species && (NO_UNDERWEAR in H.dna.species.species_traits)) + to_chat(user, "You are not capable of wearing underwear.") + return + + var/choice = input(user, "Underwear, Undershirt, or Socks?", "Changing") as null|anything in list("Underwear","Underwear Color","Undershirt","Socks") + + if(!Adjacent(user)) + return + switch(choice) + if("Underwear") + var/new_undies = input(user, "Select your underwear", "Changing") as null|anything in GLOB.underwear_list + if(new_undies) + H.underwear = new_undies + if("Underwear Color") + var/new_underwear_color = input(H, "Choose your underwear color", "Underwear Color","#"+H.underwear_color) as color|null + if(new_underwear_color) + H.underwear_color = sanitize_hexcolor(new_underwear_color) + if("Undershirt") + var/new_undershirt = input(user, "Select your undershirt", "Changing") as null|anything in GLOB.undershirt_list + if(new_undershirt) + H.undershirt = new_undershirt + if("Socks") + var/new_socks = input(user, "Select your socks", "Changing") as null|anything in GLOB.socks_list + if(new_socks) + H.socks= new_socks + + add_fingerprint(H) + H.update_body() diff --git a/code/game/objects/structures/electricchair.dm b/code/game/objects/structures/electricchair.dm index 8004d5733fe3..a5235f461a4f 100644 --- a/code/game/objects/structures/electricchair.dm +++ b/code/game/objects/structures/electricchair.dm @@ -1,52 +1,52 @@ -/obj/structure/chair/e_chair - name = "electric chair" - desc = "Looks absolutely SHOCKING!" - icon_state = "echair0" - var/obj/item/assembly/shock_kit/part = null - var/last_time = 1 - item_chair = null - -/obj/structure/chair/e_chair/Initialize() - . = ..() - add_overlay(mutable_appearance('icons/obj/chairs.dmi', "echair_over", MOB_LAYER + 1)) - -/obj/structure/chair/e_chair/attackby(obj/item/W, mob/user, params) - if(W.tool_behaviour == TOOL_WRENCH) - var/obj/structure/chair/C = new /obj/structure/chair(loc) - W.play_tool_sound(src) - C.setDir(dir) - part.forceMove(loc) - part.master = null - part = null - qdel(src) - -/obj/structure/chair/e_chair/proc/shock() - if(last_time + 50 > world.time) - return - last_time = world.time - - // special power handling - var/area/A = get_area(src) - if(!isarea(A)) - return - if(!A.powered(AREA_USAGE_EQUIP)) - return - A.use_power(AREA_USAGE_EQUIP, 5000) - - flick("echair_shock", src) - var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread - s.set_up(12, 1, src) - s.start() - if(has_buckled_mobs()) - for(var/m in buckled_mobs) - var/mob/living/buckled_mob = m - buckled_mob.electrocute_act(85, src, 1) - to_chat(buckled_mob, "You feel a deep shock course through your body!") - addtimer(CALLBACK(buckled_mob, /mob/living.proc/electrocute_act, 85, src, 1), 1) - visible_message("The electric chair went off!", "You hear a deep sharp shock!") - -/obj/structure/chair/e_chair/post_buckle_mob(mob/living/L) - SEND_SIGNAL(L, COMSIG_ADD_MOOD_EVENT, "dying", /datum/mood_event/deaths_door) - -/obj/structure/chair/e_chair/post_unbuckle_mob(mob/living/L) - SEND_SIGNAL(L, COMSIG_CLEAR_MOOD_EVENT, "dying") +/obj/structure/chair/e_chair + name = "electric chair" + desc = "Looks absolutely SHOCKING!" + icon_state = "echair0" + var/obj/item/assembly/shock_kit/part = null + var/last_time = 1 + item_chair = null + +/obj/structure/chair/e_chair/Initialize() + . = ..() + add_overlay(mutable_appearance('icons/obj/chairs.dmi', "echair_over", MOB_LAYER + 1)) + +/obj/structure/chair/e_chair/attackby(obj/item/W, mob/user, params) + if(W.tool_behaviour == TOOL_WRENCH) + var/obj/structure/chair/C = new /obj/structure/chair(loc) + W.play_tool_sound(src) + C.setDir(dir) + part.forceMove(loc) + part.master = null + part = null + qdel(src) + +/obj/structure/chair/e_chair/proc/shock() + if(last_time + 50 > world.time) + return + last_time = world.time + + // special power handling + var/area/A = get_area(src) + if(!isarea(A)) + return + if(!A.powered(AREA_USAGE_EQUIP)) + return + A.use_power(AREA_USAGE_EQUIP, 5000) + + flick("echair_shock", src) + var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread + s.set_up(12, 1, src) + s.start() + if(has_buckled_mobs()) + for(var/m in buckled_mobs) + var/mob/living/buckled_mob = m + buckled_mob.electrocute_act(85, src, 1) + to_chat(buckled_mob, "You feel a deep shock course through your body!") + addtimer(CALLBACK(buckled_mob, /mob/living.proc/electrocute_act, 85, src, 1), 1) + visible_message("The electric chair went off!", "You hear a deep sharp shock!") + +/obj/structure/chair/e_chair/post_buckle_mob(mob/living/L) + SEND_SIGNAL(L, COMSIG_ADD_MOOD_EVENT, "dying", /datum/mood_event/deaths_door) + +/obj/structure/chair/e_chair/post_unbuckle_mob(mob/living/L) + SEND_SIGNAL(L, COMSIG_CLEAR_MOOD_EVENT, "dying") diff --git a/code/game/objects/structures/extinguisher.dm b/code/game/objects/structures/extinguisher.dm index faee61e18ea7..62a17fc0cc9f 100644 --- a/code/game/objects/structures/extinguisher.dm +++ b/code/game/objects/structures/extinguisher.dm @@ -1,159 +1,159 @@ -/obj/structure/extinguisher_cabinet - name = "extinguisher cabinet" - desc = "A small wall mounted cabinet designed to hold a fire extinguisher." - icon = 'waspstation/icons/obj/wallmounts.dmi' //WaspStation Edit - Better Icons - icon_state = "extinguisher_closed" - anchored = TRUE - density = FALSE - max_integrity = 200 - integrity_failure = 0.25 - var/obj/item/extinguisher/stored_extinguisher - var/opened = FALSE - -/obj/structure/extinguisher_cabinet/Initialize(mapload, ndir, building) - . = ..() - if(building) - setDir(ndir) - pixel_x = (dir & 3)? 0 : (dir == 4 ? -27 : 27) - pixel_y = (dir & 3)? (dir ==1 ? -30 : 30) : 0 - opened = TRUE - icon_state = "extinguisher_empty" - else - stored_extinguisher = new /obj/item/extinguisher(src) - -/obj/structure/extinguisher_cabinet/examine(mob/user) - . = ..() - . += "Alt-click to [opened ? "close":"open"] it." - -/obj/structure/extinguisher_cabinet/Destroy() - if(stored_extinguisher) - qdel(stored_extinguisher) - stored_extinguisher = null - return ..() - -/obj/structure/extinguisher_cabinet/contents_explosion(severity, target) - if(stored_extinguisher) - switch(severity) - if(EXPLODE_DEVASTATE) - SSexplosions.highobj += stored_extinguisher - if(EXPLODE_HEAVY) - SSexplosions.medobj += stored_extinguisher - if(EXPLODE_LIGHT) - SSexplosions.lowobj += stored_extinguisher - -/obj/structure/extinguisher_cabinet/handle_atom_del(atom/A) - if(A == stored_extinguisher) - stored_extinguisher = null - update_icon() - -/obj/structure/extinguisher_cabinet/attackby(obj/item/I, mob/user, params) - if(I.tool_behaviour == TOOL_WRENCH && !stored_extinguisher) - to_chat(user, "You start unsecuring [name]...") - I.play_tool_sound(src) - if(I.use_tool(src, user, 60)) - playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE) - to_chat(user, "You unsecure [name].") - deconstruct(TRUE) - return - - if(iscyborg(user) || isalien(user)) - return - if(istype(I, /obj/item/extinguisher)) - if(!stored_extinguisher && opened) - if(!user.transferItemToLoc(I, src)) - return - stored_extinguisher = I - to_chat(user, "You place [I] in [src].") - update_icon() - return TRUE - else - toggle_cabinet(user) - else if(user.a_intent != INTENT_HARM) - toggle_cabinet(user) - else - return ..() - - -/obj/structure/extinguisher_cabinet/attack_hand(mob/user) - . = ..() - if(.) - return - if(iscyborg(user) || isalien(user)) - return - if(stored_extinguisher) - user.put_in_hands(stored_extinguisher) - to_chat(user, "You take [stored_extinguisher] from [src].") - stored_extinguisher = null - if(!opened) - opened = 1 - playsound(loc, 'sound/machines/click.ogg', 15, TRUE, -3) - update_icon() - else - toggle_cabinet(user) - - -/obj/structure/extinguisher_cabinet/attack_tk(mob/user) - if(stored_extinguisher) - stored_extinguisher.forceMove(loc) - to_chat(user, "You telekinetically remove [stored_extinguisher] from [src].") - stored_extinguisher = null - opened = 1 - playsound(loc, 'sound/machines/click.ogg', 15, TRUE, -3) - update_icon() - else - toggle_cabinet(user) - - -/obj/structure/extinguisher_cabinet/attack_paw(mob/user) - return attack_hand(user) - -/obj/structure/extinguisher_cabinet/AltClick(mob/living/user) - if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) - return - toggle_cabinet(user) - -/obj/structure/extinguisher_cabinet/proc/toggle_cabinet(mob/user) - if(opened && broken) - to_chat(user, "[src] is broken open.") - else - playsound(loc, 'sound/machines/click.ogg', 15, TRUE, -3) - opened = !opened - update_icon() - -/obj/structure/extinguisher_cabinet/update_icon_state() - if(!opened) - icon_state = "extinguisher_closed" - else if(stored_extinguisher) - if(istype(stored_extinguisher, /obj/item/extinguisher/mini)) - icon_state = "extinguisher_mini" - else - icon_state = "extinguisher_full" - else - icon_state = "extinguisher_empty" - -/obj/structure/extinguisher_cabinet/obj_break(damage_flag) - if(!broken && !(flags_1 & NODECONSTRUCT_1)) - broken = 1 - opened = 1 - if(stored_extinguisher) - stored_extinguisher.forceMove(loc) - stored_extinguisher = null - update_icon() - - -/obj/structure/extinguisher_cabinet/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - if(disassembled) - new /obj/item/wallframe/extinguisher_cabinet(loc) - else - new /obj/item/stack/sheet/metal (loc, 2) - if(stored_extinguisher) - stored_extinguisher.forceMove(loc) - stored_extinguisher = null - qdel(src) - -/obj/item/wallframe/extinguisher_cabinet - name = "extinguisher cabinet frame" - desc = "Used for building wall-mounted extinguisher cabinets." - icon_state = "extinguisher" - result_path = /obj/structure/extinguisher_cabinet +/obj/structure/extinguisher_cabinet + name = "extinguisher cabinet" + desc = "A small wall mounted cabinet designed to hold a fire extinguisher." + icon = 'waspstation/icons/obj/wallmounts.dmi' //WaspStation Edit - Better Icons + icon_state = "extinguisher_closed" + anchored = TRUE + density = FALSE + max_integrity = 200 + integrity_failure = 0.25 + var/obj/item/extinguisher/stored_extinguisher + var/opened = FALSE + +/obj/structure/extinguisher_cabinet/Initialize(mapload, ndir, building) + . = ..() + if(building) + setDir(ndir) + pixel_x = (dir & 3)? 0 : (dir == 4 ? -27 : 27) + pixel_y = (dir & 3)? (dir ==1 ? -30 : 30) : 0 + opened = TRUE + icon_state = "extinguisher_empty" + else + stored_extinguisher = new /obj/item/extinguisher(src) + +/obj/structure/extinguisher_cabinet/examine(mob/user) + . = ..() + . += "Alt-click to [opened ? "close":"open"] it." + +/obj/structure/extinguisher_cabinet/Destroy() + if(stored_extinguisher) + qdel(stored_extinguisher) + stored_extinguisher = null + return ..() + +/obj/structure/extinguisher_cabinet/contents_explosion(severity, target) + if(stored_extinguisher) + switch(severity) + if(EXPLODE_DEVASTATE) + SSexplosions.highobj += stored_extinguisher + if(EXPLODE_HEAVY) + SSexplosions.medobj += stored_extinguisher + if(EXPLODE_LIGHT) + SSexplosions.lowobj += stored_extinguisher + +/obj/structure/extinguisher_cabinet/handle_atom_del(atom/A) + if(A == stored_extinguisher) + stored_extinguisher = null + update_icon() + +/obj/structure/extinguisher_cabinet/attackby(obj/item/I, mob/user, params) + if(I.tool_behaviour == TOOL_WRENCH && !stored_extinguisher) + to_chat(user, "You start unsecuring [name]...") + I.play_tool_sound(src) + if(I.use_tool(src, user, 60)) + playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE) + to_chat(user, "You unsecure [name].") + deconstruct(TRUE) + return + + if(iscyborg(user) || isalien(user)) + return + if(istype(I, /obj/item/extinguisher)) + if(!stored_extinguisher && opened) + if(!user.transferItemToLoc(I, src)) + return + stored_extinguisher = I + to_chat(user, "You place [I] in [src].") + update_icon() + return TRUE + else + toggle_cabinet(user) + else if(user.a_intent != INTENT_HARM) + toggle_cabinet(user) + else + return ..() + + +/obj/structure/extinguisher_cabinet/attack_hand(mob/user) + . = ..() + if(.) + return + if(iscyborg(user) || isalien(user)) + return + if(stored_extinguisher) + user.put_in_hands(stored_extinguisher) + to_chat(user, "You take [stored_extinguisher] from [src].") + stored_extinguisher = null + if(!opened) + opened = 1 + playsound(loc, 'sound/machines/click.ogg', 15, TRUE, -3) + update_icon() + else + toggle_cabinet(user) + + +/obj/structure/extinguisher_cabinet/attack_tk(mob/user) + if(stored_extinguisher) + stored_extinguisher.forceMove(loc) + to_chat(user, "You telekinetically remove [stored_extinguisher] from [src].") + stored_extinguisher = null + opened = 1 + playsound(loc, 'sound/machines/click.ogg', 15, TRUE, -3) + update_icon() + else + toggle_cabinet(user) + + +/obj/structure/extinguisher_cabinet/attack_paw(mob/user) + return attack_hand(user) + +/obj/structure/extinguisher_cabinet/AltClick(mob/living/user) + if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + return + toggle_cabinet(user) + +/obj/structure/extinguisher_cabinet/proc/toggle_cabinet(mob/user) + if(opened && broken) + to_chat(user, "[src] is broken open.") + else + playsound(loc, 'sound/machines/click.ogg', 15, TRUE, -3) + opened = !opened + update_icon() + +/obj/structure/extinguisher_cabinet/update_icon_state() + if(!opened) + icon_state = "extinguisher_closed" + else if(stored_extinguisher) + if(istype(stored_extinguisher, /obj/item/extinguisher/mini)) + icon_state = "extinguisher_mini" + else + icon_state = "extinguisher_full" + else + icon_state = "extinguisher_empty" + +/obj/structure/extinguisher_cabinet/obj_break(damage_flag) + if(!broken && !(flags_1 & NODECONSTRUCT_1)) + broken = 1 + opened = 1 + if(stored_extinguisher) + stored_extinguisher.forceMove(loc) + stored_extinguisher = null + update_icon() + + +/obj/structure/extinguisher_cabinet/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + if(disassembled) + new /obj/item/wallframe/extinguisher_cabinet(loc) + else + new /obj/item/stack/sheet/metal (loc, 2) + if(stored_extinguisher) + stored_extinguisher.forceMove(loc) + stored_extinguisher = null + qdel(src) + +/obj/item/wallframe/extinguisher_cabinet + name = "extinguisher cabinet frame" + desc = "Used for building wall-mounted extinguisher cabinets." + icon_state = "extinguisher" + result_path = /obj/structure/extinguisher_cabinet diff --git a/code/game/objects/structures/flora.dm b/code/game/objects/structures/flora.dm index bd7196185e72..f560d628b9c0 100644 --- a/code/game/objects/structures/flora.dm +++ b/code/game/objects/structures/flora.dm @@ -1,450 +1,450 @@ -/obj/structure/flora - resistance_flags = FLAMMABLE - max_integrity = 150 - anchored = TRUE - -//trees -/obj/structure/flora/tree - name = "tree" - desc = "A large tree." - density = TRUE - pixel_x = -16 - layer = FLY_LAYER - var/log_amount = 10 - -/obj/structure/flora/tree/attackby(obj/item/W, mob/user, params) - if(log_amount && (!(flags_1 & NODECONSTRUCT_1))) - if(W.get_sharpness() && W.force > 0) - if(W.hitsound) - playsound(get_turf(src), W.hitsound, 100, FALSE, FALSE) - user.visible_message("[user] begins to cut down [src] with [W].","You begin to cut down [src] with [W].", "You hear the sound of sawing.") - if(do_after(user, 1000/W.force, target = src)) //5 seconds with 20 force, 8 seconds with a hatchet, 20 seconds with a shard. - user.visible_message("[user] fells [src] with the [W].","You fell [src] with the [W].", "You hear the sound of a tree falling.") - playsound(get_turf(src), 'sound/effects/meteorimpact.ogg', 100 , FALSE, FALSE) - user.log_message("cut down [src] at [AREACOORD(src)]", LOG_ATTACK) - for(var/i=1 to log_amount) - new /obj/item/grown/log/tree(get_turf(src)) - var/obj/structure/flora/stump/S = new(loc) - S.name = "[name] stump" - qdel(src) - else - return ..() - -/obj/structure/flora/stump - name = "stump" - desc = "This represents our promise to the crew, and the station itself, to cut down as many trees as possible." //running naked through the trees - icon = 'icons/obj/flora/pinetrees.dmi' - icon_state = "tree_stump" - density = FALSE - pixel_x = -16 - -/obj/structure/flora/tree/pine - name = "pine tree" - desc = "A coniferous pine tree." - icon = 'icons/obj/flora/pinetrees.dmi' - icon_state = "pine_1" - var/list/icon_states = list("pine_1", "pine_2", "pine_3") - -/obj/structure/flora/tree/pine/Initialize() - . = ..() - - if(islist(icon_states && icon_states.len)) - icon_state = pick(icon_states) - -/obj/structure/flora/tree/pine/xmas - name = "xmas tree" - desc = "A wondrous decorated Christmas tree." - icon_state = "pine_c" - icon_states = null - flags_1 = NODECONSTRUCT_1 //protected by the christmas spirit - -/obj/structure/flora/tree/pine/xmas/presents - icon_state = "pinepresents" - desc = "A wondrous decorated Christmas tree. It has presents!" - var/gift_type = /obj/item/a_gift/anything - var/unlimited = FALSE - var/static/list/took_presents //shared between all xmas trees - -/obj/structure/flora/tree/pine/xmas/presents/Initialize() - . = ..() - if(!took_presents) - took_presents = list() - -/obj/structure/flora/tree/pine/xmas/presents/attack_hand(mob/living/user) - . = ..() - if(.) - return - if(!user.ckey) - return - - if(took_presents[user.ckey] && !unlimited) - to_chat(user, "There are no presents with your name on.") - return - to_chat(user, "After a bit of rummaging, you locate a gift with your name on it!") - - if(!unlimited) - took_presents[user.ckey] = TRUE - - var/obj/item/G = new gift_type(src) - user.put_in_hands(G) - -/obj/structure/flora/tree/pine/xmas/presents/unlimited - desc = "A wonderous decorated Christmas tree. It has a seemly endless supply of presents!" - unlimited = TRUE - -/obj/structure/flora/tree/dead - icon = 'icons/obj/flora/deadtrees.dmi' - desc = "A dead tree. How it died, you know not." - icon_state = "tree_1" - -/obj/structure/flora/tree/palm - icon = 'icons/misc/beach2.dmi' - desc = "A tree straight from the tropics." - icon_state = "palm1" - -/obj/structure/flora/tree/palm/Initialize() - . = ..() - icon_state = pick("palm1","palm2") - pixel_x = 0 - -/obj/structure/festivus - name = "festivus pole" - icon = 'icons/obj/flora/pinetrees.dmi' - icon_state = "festivus_pole" - desc = "During last year's Feats of Strength the Research Director was able to suplex this passing immobile rod into a planter." - -/obj/structure/festivus/anchored - name = "suplexed rod" - desc = "A true feat of strength, almost as good as last year." - icon_state = "anchored_rod" - anchored = TRUE - -/obj/structure/flora/tree/dead/Initialize() - icon_state = "tree_[rand(1, 6)]" - . = ..() - -/obj/structure/flora/tree/jungle - name = "tree" - icon_state = "tree" - desc = "It's seriously hampering your view of the jungle." - icon = 'icons/obj/flora/jungletrees.dmi' - pixel_x = -48 - pixel_y = -20 - -/obj/structure/flora/tree/jungle/Initialize() - icon_state = "[icon_state][rand(1, 6)]" - . = ..() - -/obj/structure/flora/tree/jungle/small - pixel_y = 0 - pixel_x = -32 - icon = 'icons/obj/flora/jungletreesmall.dmi' - -//grass -/obj/structure/flora/grass - name = "grass" - desc = "A patch of overgrown grass." - icon = 'icons/obj/flora/snowflora.dmi' - gender = PLURAL //"this is grass" not "this is a grass" - -/obj/structure/flora/grass/brown - icon_state = "snowgrass1bb" - -/obj/structure/flora/grass/brown/Initialize() - icon_state = "snowgrass[rand(1, 3)]bb" - . = ..() - - -/obj/structure/flora/grass/green - icon_state = "snowgrass1gb" - -/obj/structure/flora/grass/green/Initialize() - icon_state = "snowgrass[rand(1, 3)]gb" - . = ..() - -/obj/structure/flora/grass/both - icon_state = "snowgrassall1" - -/obj/structure/flora/grass/both/Initialize() - icon_state = "snowgrassall[rand(1, 3)]" - . = ..() - - -//bushes -/obj/structure/flora/bush - name = "bush" - desc = "Some type of shrub." - icon = 'icons/obj/flora/snowflora.dmi' - icon_state = "snowbush1" - anchored = TRUE - -/obj/structure/flora/bush/Initialize() - icon_state = "snowbush[rand(1, 6)]" - . = ..() - -//newbushes - -/obj/structure/flora/ausbushes - name = "bush" - desc = "Some kind of plant." - icon = 'icons/obj/flora/ausflora.dmi' - icon_state = "firstbush_1" - -/obj/structure/flora/ausbushes/Initialize() - if(icon_state == "firstbush_1") - icon_state = "firstbush_[rand(1, 4)]" - . = ..() - -/obj/structure/flora/ausbushes/reedbush - icon_state = "reedbush_1" - -/obj/structure/flora/ausbushes/reedbush/Initialize() - icon_state = "reedbush_[rand(1, 4)]" - . = ..() - -/obj/structure/flora/ausbushes/leafybush - icon_state = "leafybush_1" - -/obj/structure/flora/ausbushes/leafybush/Initialize() - icon_state = "leafybush_[rand(1, 3)]" - . = ..() - -/obj/structure/flora/ausbushes/palebush - icon_state = "palebush_1" - -/obj/structure/flora/ausbushes/palebush/Initialize() - icon_state = "palebush_[rand(1, 4)]" - . = ..() - -/obj/structure/flora/ausbushes/stalkybush - icon_state = "stalkybush_1" - -/obj/structure/flora/ausbushes/stalkybush/Initialize() - icon_state = "stalkybush_[rand(1, 3)]" - . = ..() - -/obj/structure/flora/ausbushes/grassybush - icon_state = "grassybush_1" - -/obj/structure/flora/ausbushes/grassybush/Initialize() - icon_state = "grassybush_[rand(1, 4)]" - . = ..() - -/obj/structure/flora/ausbushes/fernybush - icon_state = "fernybush_1" - -/obj/structure/flora/ausbushes/fernybush/Initialize() - icon_state = "fernybush_[rand(1, 3)]" - . = ..() - -/obj/structure/flora/ausbushes/sunnybush - icon_state = "sunnybush_1" - -/obj/structure/flora/ausbushes/sunnybush/Initialize() - icon_state = "sunnybush_[rand(1, 3)]" - . = ..() - -/obj/structure/flora/ausbushes/genericbush - icon_state = "genericbush_1" - -/obj/structure/flora/ausbushes/genericbush/Initialize() - icon_state = "genericbush_[rand(1, 4)]" - . = ..() - -/obj/structure/flora/ausbushes/pointybush - icon_state = "pointybush_1" - -/obj/structure/flora/ausbushes/pointybush/Initialize() - icon_state = "pointybush_[rand(1, 4)]" - . = ..() - -/obj/structure/flora/ausbushes/lavendergrass - icon_state = "lavendergrass_1" - -/obj/structure/flora/ausbushes/lavendergrass/Initialize() - icon_state = "lavendergrass_[rand(1, 4)]" - . = ..() - -/obj/structure/flora/ausbushes/ywflowers - icon_state = "ywflowers_1" - -/obj/structure/flora/ausbushes/ywflowers/Initialize() - icon_state = "ywflowers_[rand(1, 3)]" - . = ..() - -/obj/structure/flora/ausbushes/brflowers - icon_state = "brflowers_1" - -/obj/structure/flora/ausbushes/brflowers/Initialize() - icon_state = "brflowers_[rand(1, 3)]" - . = ..() - -/obj/structure/flora/ausbushes/ppflowers - icon_state = "ppflowers_1" - -/obj/structure/flora/ausbushes/ppflowers/Initialize() - icon_state = "ppflowers_[rand(1, 3)]" - . = ..() - -/obj/structure/flora/ausbushes/sparsegrass - icon_state = "sparsegrass_1" - -/obj/structure/flora/ausbushes/sparsegrass/Initialize() - icon_state = "sparsegrass_[rand(1, 3)]" - . = ..() - -/obj/structure/flora/ausbushes/fullgrass - icon_state = "fullgrass_1" - -/obj/structure/flora/ausbushes/fullgrass/Initialize() - icon_state = "fullgrass_[rand(1, 3)]" - . = ..() - -/obj/item/kirbyplants - name = "potted plant" - icon = 'icons/obj/flora/plants.dmi' - icon_state = "plant-01" - desc = "A little bit of nature contained in a pot." - layer = ABOVE_MOB_LAYER - w_class = WEIGHT_CLASS_HUGE - force = 10 - throwforce = 13 - throw_speed = 2 - throw_range = 4 - -/obj/item/kirbyplants/ComponentInitialize() - . = ..() - AddComponent(/datum/component/tactical) - addtimer(CALLBACK(src, /datum.proc/_AddComponent, list(/datum/component/beauty, 500)), 0) - AddComponent(/datum/component/two_handed, require_twohands=TRUE, force_unwielded=10, force_wielded=10) - -/obj/item/kirbyplants/random - icon = 'icons/obj/flora/_flora.dmi' - icon_state = "random_plant" - var/list/static/states - -/obj/item/kirbyplants/random/Initialize() - . = ..() - icon = 'icons/obj/flora/plants.dmi' - if(!states) - generate_states() - icon_state = pick(states) - -/obj/item/kirbyplants/random/proc/generate_states() - states = list() - for(var/i in 1 to 25) - var/number - if(i < 10) - number = "0[i]" - else - number = "[i]" - states += "plant-[number]" - states += "applebush" - - -/obj/item/kirbyplants/dead - name = "RD's potted plant" - desc = "A gift from the botanical staff, presented after the RD's reassignment. There's a tag on it that says \"Y'all come back now, y'hear?\"\nIt doesn't look very healthy..." - icon_state = "plant-25" - -/obj/item/kirbyplants/photosynthetic - name = "photosynthetic potted plant" - desc = "A bioluminescent plant." - icon_state = "plant-09" - light_color = "#2cb2e8" - light_range = 3 - -/obj/item/kirbyplants/fullysynthetic - name = "plastic potted plant" - desc = "A fake, cheap looking, plastic tree. Perfect for people who kill every plant they touch." - icon_state = "plant-26" - custom_materials = (list(/datum/material/plastic = 8000)) - -/obj/item/kirbyplants/fullysynthetic/Initialize() - . = ..() - icon_state = "plant-[rand(26, 29)]" - -//a rock is flora according to where the icon file is -//and now these defines - -/obj/structure/flora/rock - icon_state = "basalt" - desc = "A volcanic rock. Pioneers used to ride these babies for miles." - icon = 'icons/obj/flora/rocks.dmi' - resistance_flags = FIRE_PROOF - density = TRUE - -/obj/structure/flora/rock/Initialize() - . = ..() - icon_state = "[icon_state][rand(1,3)]" - -/obj/structure/flora/rock/pile - icon_state = "lavarocks" - desc = "A pile of rocks." - -//Jungle grass - -/obj/structure/flora/grass/jungle - name = "jungle grass" - desc = "Thick alien flora." - icon = 'icons/obj/flora/jungleflora.dmi' - icon_state = "grassa" - - -/obj/structure/flora/grass/jungle/Initialize() - icon_state = "[icon_state][rand(1, 5)]" - . = ..() - -/obj/structure/flora/grass/jungle/b - icon_state = "grassb" - -//Jungle rocks - -/obj/structure/flora/rock/jungle - icon_state = "rock" - desc = "A pile of rocks." - icon = 'icons/obj/flora/jungleflora.dmi' - density = FALSE - -/obj/structure/flora/rock/jungle/Initialize() - . = ..() - icon_state = "[initial(icon_state)][rand(1,5)]" - - -//Jungle bushes - -/obj/structure/flora/junglebush - name = "bush" - desc = "A wild plant that is found in jungles." - icon = 'icons/obj/flora/jungleflora.dmi' - icon_state = "busha" - -/obj/structure/flora/junglebush/Initialize() - icon_state = "[icon_state][rand(1, 3)]" - . = ..() - -/obj/structure/flora/junglebush/b - icon_state = "bushb" - -/obj/structure/flora/junglebush/c - icon_state = "bushc" - -/obj/structure/flora/junglebush/large - icon_state = "bush" - icon = 'icons/obj/flora/largejungleflora.dmi' - pixel_x = -16 - pixel_y = -12 - layer = ABOVE_ALL_MOB_LAYER - -/obj/structure/flora/rock/pile/largejungle - name = "rocks" - icon_state = "rocks" - icon = 'icons/obj/flora/largejungleflora.dmi' - density = FALSE - pixel_x = -16 - pixel_y = -16 - -/obj/structure/flora/rock/pile/largejungle/Initialize() - . = ..() - icon_state = "[initial(icon_state)][rand(1,3)]" - +/obj/structure/flora + resistance_flags = FLAMMABLE + max_integrity = 150 + anchored = TRUE + +//trees +/obj/structure/flora/tree + name = "tree" + desc = "A large tree." + density = TRUE + pixel_x = -16 + layer = FLY_LAYER + var/log_amount = 10 + +/obj/structure/flora/tree/attackby(obj/item/W, mob/user, params) + if(log_amount && (!(flags_1 & NODECONSTRUCT_1))) + if(W.get_sharpness() && W.force > 0) + if(W.hitsound) + playsound(get_turf(src), W.hitsound, 100, FALSE, FALSE) + user.visible_message("[user] begins to cut down [src] with [W].","You begin to cut down [src] with [W].", "You hear the sound of sawing.") + if(do_after(user, 1000/W.force, target = src)) //5 seconds with 20 force, 8 seconds with a hatchet, 20 seconds with a shard. + user.visible_message("[user] fells [src] with the [W].","You fell [src] with the [W].", "You hear the sound of a tree falling.") + playsound(get_turf(src), 'sound/effects/meteorimpact.ogg', 100 , FALSE, FALSE) + user.log_message("cut down [src] at [AREACOORD(src)]", LOG_ATTACK) + for(var/i=1 to log_amount) + new /obj/item/grown/log/tree(get_turf(src)) + var/obj/structure/flora/stump/S = new(loc) + S.name = "[name] stump" + qdel(src) + else + return ..() + +/obj/structure/flora/stump + name = "stump" + desc = "This represents our promise to the crew, and the station itself, to cut down as many trees as possible." //running naked through the trees + icon = 'icons/obj/flora/pinetrees.dmi' + icon_state = "tree_stump" + density = FALSE + pixel_x = -16 + +/obj/structure/flora/tree/pine + name = "pine tree" + desc = "A coniferous pine tree." + icon = 'icons/obj/flora/pinetrees.dmi' + icon_state = "pine_1" + var/list/icon_states = list("pine_1", "pine_2", "pine_3") + +/obj/structure/flora/tree/pine/Initialize() + . = ..() + + if(islist(icon_states && icon_states.len)) + icon_state = pick(icon_states) + +/obj/structure/flora/tree/pine/xmas + name = "xmas tree" + desc = "A wondrous decorated Christmas tree." + icon_state = "pine_c" + icon_states = null + flags_1 = NODECONSTRUCT_1 //protected by the christmas spirit + +/obj/structure/flora/tree/pine/xmas/presents + icon_state = "pinepresents" + desc = "A wondrous decorated Christmas tree. It has presents!" + var/gift_type = /obj/item/a_gift/anything + var/unlimited = FALSE + var/static/list/took_presents //shared between all xmas trees + +/obj/structure/flora/tree/pine/xmas/presents/Initialize() + . = ..() + if(!took_presents) + took_presents = list() + +/obj/structure/flora/tree/pine/xmas/presents/attack_hand(mob/living/user) + . = ..() + if(.) + return + if(!user.ckey) + return + + if(took_presents[user.ckey] && !unlimited) + to_chat(user, "There are no presents with your name on.") + return + to_chat(user, "After a bit of rummaging, you locate a gift with your name on it!") + + if(!unlimited) + took_presents[user.ckey] = TRUE + + var/obj/item/G = new gift_type(src) + user.put_in_hands(G) + +/obj/structure/flora/tree/pine/xmas/presents/unlimited + desc = "A wonderous decorated Christmas tree. It has a seemly endless supply of presents!" + unlimited = TRUE + +/obj/structure/flora/tree/dead + icon = 'icons/obj/flora/deadtrees.dmi' + desc = "A dead tree. How it died, you know not." + icon_state = "tree_1" + +/obj/structure/flora/tree/palm + icon = 'icons/misc/beach2.dmi' + desc = "A tree straight from the tropics." + icon_state = "palm1" + +/obj/structure/flora/tree/palm/Initialize() + . = ..() + icon_state = pick("palm1","palm2") + pixel_x = 0 + +/obj/structure/festivus + name = "festivus pole" + icon = 'icons/obj/flora/pinetrees.dmi' + icon_state = "festivus_pole" + desc = "During last year's Feats of Strength the Research Director was able to suplex this passing immobile rod into a planter." + +/obj/structure/festivus/anchored + name = "suplexed rod" + desc = "A true feat of strength, almost as good as last year." + icon_state = "anchored_rod" + anchored = TRUE + +/obj/structure/flora/tree/dead/Initialize() + icon_state = "tree_[rand(1, 6)]" + . = ..() + +/obj/structure/flora/tree/jungle + name = "tree" + icon_state = "tree" + desc = "It's seriously hampering your view of the jungle." + icon = 'icons/obj/flora/jungletrees.dmi' + pixel_x = -48 + pixel_y = -20 + +/obj/structure/flora/tree/jungle/Initialize() + icon_state = "[icon_state][rand(1, 6)]" + . = ..() + +/obj/structure/flora/tree/jungle/small + pixel_y = 0 + pixel_x = -32 + icon = 'icons/obj/flora/jungletreesmall.dmi' + +//grass +/obj/structure/flora/grass + name = "grass" + desc = "A patch of overgrown grass." + icon = 'icons/obj/flora/snowflora.dmi' + gender = PLURAL //"this is grass" not "this is a grass" + +/obj/structure/flora/grass/brown + icon_state = "snowgrass1bb" + +/obj/structure/flora/grass/brown/Initialize() + icon_state = "snowgrass[rand(1, 3)]bb" + . = ..() + + +/obj/structure/flora/grass/green + icon_state = "snowgrass1gb" + +/obj/structure/flora/grass/green/Initialize() + icon_state = "snowgrass[rand(1, 3)]gb" + . = ..() + +/obj/structure/flora/grass/both + icon_state = "snowgrassall1" + +/obj/structure/flora/grass/both/Initialize() + icon_state = "snowgrassall[rand(1, 3)]" + . = ..() + + +//bushes +/obj/structure/flora/bush + name = "bush" + desc = "Some type of shrub." + icon = 'icons/obj/flora/snowflora.dmi' + icon_state = "snowbush1" + anchored = TRUE + +/obj/structure/flora/bush/Initialize() + icon_state = "snowbush[rand(1, 6)]" + . = ..() + +//newbushes + +/obj/structure/flora/ausbushes + name = "bush" + desc = "Some kind of plant." + icon = 'icons/obj/flora/ausflora.dmi' + icon_state = "firstbush_1" + +/obj/structure/flora/ausbushes/Initialize() + if(icon_state == "firstbush_1") + icon_state = "firstbush_[rand(1, 4)]" + . = ..() + +/obj/structure/flora/ausbushes/reedbush + icon_state = "reedbush_1" + +/obj/structure/flora/ausbushes/reedbush/Initialize() + icon_state = "reedbush_[rand(1, 4)]" + . = ..() + +/obj/structure/flora/ausbushes/leafybush + icon_state = "leafybush_1" + +/obj/structure/flora/ausbushes/leafybush/Initialize() + icon_state = "leafybush_[rand(1, 3)]" + . = ..() + +/obj/structure/flora/ausbushes/palebush + icon_state = "palebush_1" + +/obj/structure/flora/ausbushes/palebush/Initialize() + icon_state = "palebush_[rand(1, 4)]" + . = ..() + +/obj/structure/flora/ausbushes/stalkybush + icon_state = "stalkybush_1" + +/obj/structure/flora/ausbushes/stalkybush/Initialize() + icon_state = "stalkybush_[rand(1, 3)]" + . = ..() + +/obj/structure/flora/ausbushes/grassybush + icon_state = "grassybush_1" + +/obj/structure/flora/ausbushes/grassybush/Initialize() + icon_state = "grassybush_[rand(1, 4)]" + . = ..() + +/obj/structure/flora/ausbushes/fernybush + icon_state = "fernybush_1" + +/obj/structure/flora/ausbushes/fernybush/Initialize() + icon_state = "fernybush_[rand(1, 3)]" + . = ..() + +/obj/structure/flora/ausbushes/sunnybush + icon_state = "sunnybush_1" + +/obj/structure/flora/ausbushes/sunnybush/Initialize() + icon_state = "sunnybush_[rand(1, 3)]" + . = ..() + +/obj/structure/flora/ausbushes/genericbush + icon_state = "genericbush_1" + +/obj/structure/flora/ausbushes/genericbush/Initialize() + icon_state = "genericbush_[rand(1, 4)]" + . = ..() + +/obj/structure/flora/ausbushes/pointybush + icon_state = "pointybush_1" + +/obj/structure/flora/ausbushes/pointybush/Initialize() + icon_state = "pointybush_[rand(1, 4)]" + . = ..() + +/obj/structure/flora/ausbushes/lavendergrass + icon_state = "lavendergrass_1" + +/obj/structure/flora/ausbushes/lavendergrass/Initialize() + icon_state = "lavendergrass_[rand(1, 4)]" + . = ..() + +/obj/structure/flora/ausbushes/ywflowers + icon_state = "ywflowers_1" + +/obj/structure/flora/ausbushes/ywflowers/Initialize() + icon_state = "ywflowers_[rand(1, 3)]" + . = ..() + +/obj/structure/flora/ausbushes/brflowers + icon_state = "brflowers_1" + +/obj/structure/flora/ausbushes/brflowers/Initialize() + icon_state = "brflowers_[rand(1, 3)]" + . = ..() + +/obj/structure/flora/ausbushes/ppflowers + icon_state = "ppflowers_1" + +/obj/structure/flora/ausbushes/ppflowers/Initialize() + icon_state = "ppflowers_[rand(1, 3)]" + . = ..() + +/obj/structure/flora/ausbushes/sparsegrass + icon_state = "sparsegrass_1" + +/obj/structure/flora/ausbushes/sparsegrass/Initialize() + icon_state = "sparsegrass_[rand(1, 3)]" + . = ..() + +/obj/structure/flora/ausbushes/fullgrass + icon_state = "fullgrass_1" + +/obj/structure/flora/ausbushes/fullgrass/Initialize() + icon_state = "fullgrass_[rand(1, 3)]" + . = ..() + +/obj/item/kirbyplants + name = "potted plant" + icon = 'icons/obj/flora/plants.dmi' + icon_state = "plant-01" + desc = "A little bit of nature contained in a pot." + layer = ABOVE_MOB_LAYER + w_class = WEIGHT_CLASS_HUGE + force = 10 + throwforce = 13 + throw_speed = 2 + throw_range = 4 + +/obj/item/kirbyplants/ComponentInitialize() + . = ..() + AddComponent(/datum/component/tactical) + addtimer(CALLBACK(src, /datum.proc/_AddComponent, list(/datum/component/beauty, 500)), 0) + AddComponent(/datum/component/two_handed, require_twohands=TRUE, force_unwielded=10, force_wielded=10) + +/obj/item/kirbyplants/random + icon = 'icons/obj/flora/_flora.dmi' + icon_state = "random_plant" + var/list/static/states + +/obj/item/kirbyplants/random/Initialize() + . = ..() + icon = 'icons/obj/flora/plants.dmi' + if(!states) + generate_states() + icon_state = pick(states) + +/obj/item/kirbyplants/random/proc/generate_states() + states = list() + for(var/i in 1 to 25) + var/number + if(i < 10) + number = "0[i]" + else + number = "[i]" + states += "plant-[number]" + states += "applebush" + + +/obj/item/kirbyplants/dead + name = "RD's potted plant" + desc = "A gift from the botanical staff, presented after the RD's reassignment. There's a tag on it that says \"Y'all come back now, y'hear?\"\nIt doesn't look very healthy..." + icon_state = "plant-25" + +/obj/item/kirbyplants/photosynthetic + name = "photosynthetic potted plant" + desc = "A bioluminescent plant." + icon_state = "plant-09" + light_color = "#2cb2e8" + light_range = 3 + +/obj/item/kirbyplants/fullysynthetic + name = "plastic potted plant" + desc = "A fake, cheap looking, plastic tree. Perfect for people who kill every plant they touch." + icon_state = "plant-26" + custom_materials = (list(/datum/material/plastic = 8000)) + +/obj/item/kirbyplants/fullysynthetic/Initialize() + . = ..() + icon_state = "plant-[rand(26, 29)]" + +//a rock is flora according to where the icon file is +//and now these defines + +/obj/structure/flora/rock + icon_state = "basalt" + desc = "A volcanic rock. Pioneers used to ride these babies for miles." + icon = 'icons/obj/flora/rocks.dmi' + resistance_flags = FIRE_PROOF + density = TRUE + +/obj/structure/flora/rock/Initialize() + . = ..() + icon_state = "[icon_state][rand(1,3)]" + +/obj/structure/flora/rock/pile + icon_state = "lavarocks" + desc = "A pile of rocks." + +//Jungle grass + +/obj/structure/flora/grass/jungle + name = "jungle grass" + desc = "Thick alien flora." + icon = 'icons/obj/flora/jungleflora.dmi' + icon_state = "grassa" + + +/obj/structure/flora/grass/jungle/Initialize() + icon_state = "[icon_state][rand(1, 5)]" + . = ..() + +/obj/structure/flora/grass/jungle/b + icon_state = "grassb" + +//Jungle rocks + +/obj/structure/flora/rock/jungle + icon_state = "rock" + desc = "A pile of rocks." + icon = 'icons/obj/flora/jungleflora.dmi' + density = FALSE + +/obj/structure/flora/rock/jungle/Initialize() + . = ..() + icon_state = "[initial(icon_state)][rand(1,5)]" + + +//Jungle bushes + +/obj/structure/flora/junglebush + name = "bush" + desc = "A wild plant that is found in jungles." + icon = 'icons/obj/flora/jungleflora.dmi' + icon_state = "busha" + +/obj/structure/flora/junglebush/Initialize() + icon_state = "[icon_state][rand(1, 3)]" + . = ..() + +/obj/structure/flora/junglebush/b + icon_state = "bushb" + +/obj/structure/flora/junglebush/c + icon_state = "bushc" + +/obj/structure/flora/junglebush/large + icon_state = "bush" + icon = 'icons/obj/flora/largejungleflora.dmi' + pixel_x = -16 + pixel_y = -12 + layer = ABOVE_ALL_MOB_LAYER + +/obj/structure/flora/rock/pile/largejungle + name = "rocks" + icon_state = "rocks" + icon = 'icons/obj/flora/largejungleflora.dmi' + density = FALSE + pixel_x = -16 + pixel_y = -16 + +/obj/structure/flora/rock/pile/largejungle/Initialize() + . = ..() + icon_state = "[initial(icon_state)][rand(1,3)]" + diff --git a/code/game/objects/structures/ghost_role_spawners.dm b/code/game/objects/structures/ghost_role_spawners.dm index 0daf7d7e05fc..a5748085468b 100644 --- a/code/game/objects/structures/ghost_role_spawners.dm +++ b/code/game/objects/structures/ghost_role_spawners.dm @@ -307,7 +307,7 @@ /datum/outfit/hermit name = "Lavaland hermit" - uniform = /obj/item/clothing/under/color/grey/glorf + uniform = /obj/item/clothing/under/color/grey/ancient shoes = /obj/item/clothing/shoes/sneakers/black back = /obj/item/storage/backpack mask = /obj/item/clothing/mask/breath diff --git a/code/game/objects/structures/guncase.dm b/code/game/objects/structures/guncase.dm index d826cc9034db..9159d10351d5 100644 --- a/code/game/objects/structures/guncase.dm +++ b/code/game/objects/structures/guncase.dm @@ -60,36 +60,57 @@ if(iscyborg(user) || isalien(user)) return if(contents.len && open) - ShowWindow(user) + show_menu(user) else open = !open update_icon() -/obj/structure/guncase/proc/ShowWindow(mob/user) - var/dat = {"
                        -

                        Stored Guns

                        - "} - if(LAZYLEN(contents)) - for(var/i in 1 to contents.len) - var/obj/item/I = contents[i] - dat += "[I.name]
                        " - dat += "
                        " +/** + * show_menu: Shows a radial menu to a user consisting of an available weaponry for taking + * + * Arguments: + * * user The mob to which we are showing the radial menu + */ +/obj/structure/guncase/proc/show_menu(mob/user) + if(!LAZYLEN(contents)) + return - var/datum/browser/popup = new(user, "gunlocker", "
                        [name]
                        ", 350, 300) - popup.set_content(dat) - popup.open(FALSE) + var/list/display_names = list() + var/list/items = list() + for(var/i in 1 to length(contents)) + var/obj/item/thing = contents[i] + display_names["[thing.name] ([i])"] = REF(thing) + var/image/item_image = image(icon = thing.icon, icon_state = thing.icon_state) + if(length(thing.overlays)) + item_image.copy_overlays(thing) + items += list("[thing.name] ([i])" = item_image) -/obj/structure/guncase/Topic(href, href_list) - if(href_list["retrieve"]) - var/obj/item/O = locate(href_list["retrieve"]) in contents - if(!O || !istype(O)) - return - if(!usr.canUseTopic(src, BE_CLOSE) || !open) - return - if(ishuman(usr)) - if(!usr.put_in_hands(O)) - O.forceMove(get_turf(src)) - update_icon() + var/pick = show_radial_menu(user, src, items, custom_check = CALLBACK(src, .proc/check_menu, user), radius = 36, require_near = TRUE) + if(!pick) + return + + var/weapon_reference = display_names[pick] + var/obj/item/weapon = locate(weapon_reference) in contents + if(!istype(weapon)) + return + if(!user.put_in_hands(weapon)) + weapon.forceMove(get_turf(src)) + update_icon() + +/** + * check_menu: Checks if we are allowed to interact with a radial menu + * + * Arguments: + * * user The mob interacting with a menu + */ +/obj/structure/guncase/proc/check_menu(mob/living/carbon/human/user) + if(!open) + return FALSE + if(!istype(user)) + return FALSE + if(user.incapacitated()) + return FALSE + return TRUE /obj/structure/guncase/handle_atom_del(atom/A) update_icon() diff --git a/code/game/objects/structures/hivebot.dm b/code/game/objects/structures/hivebot.dm index 6813b6f38488..51107d7411e1 100644 --- a/code/game/objects/structures/hivebot.dm +++ b/code/game/objects/structures/hivebot.dm @@ -1,36 +1,36 @@ -/obj/structure/hivebot_beacon - name = "beacon" - desc = "Some odd beacon thing." - icon = 'icons/mob/hivebot.dmi' - icon_state = "def_radar-off" - anchored = TRUE - density = TRUE - var/bot_type = "norm" - var/bot_amt = 10 - -/obj/structure/hivebot_beacon/Initialize() - . = ..() - var/datum/effect_system/smoke_spread/smoke = new - smoke.set_up(2, loc) - smoke.start() - visible_message("[src] warps in!") - playsound(src.loc, 'sound/effects/empulse.ogg', 25, TRUE) - addtimer(CALLBACK(src, .proc/warpbots), rand(10, 600)) - -/obj/structure/hivebot_beacon/proc/warpbots() - icon_state = "def_radar" - visible_message("[src] turns on!") - while(bot_amt > 0) - bot_amt-- - switch(bot_type) - if("norm") - new /mob/living/simple_animal/hostile/hivebot(get_turf(src)) - if("range") - new /mob/living/simple_animal/hostile/hivebot/range(get_turf(src)) - if("rapid") - new /mob/living/simple_animal/hostile/hivebot/rapid(get_turf(src)) - sleep(100) - visible_message("[src] warps out!") - playsound(src.loc, 'sound/effects/empulse.ogg', 25, TRUE) - qdel(src) - return +/obj/structure/hivebot_beacon + name = "beacon" + desc = "Some odd beacon thing." + icon = 'icons/mob/hivebot.dmi' + icon_state = "def_radar-off" + anchored = TRUE + density = TRUE + var/bot_type = "norm" + var/bot_amt = 10 + +/obj/structure/hivebot_beacon/Initialize() + . = ..() + var/datum/effect_system/smoke_spread/smoke = new + smoke.set_up(2, loc) + smoke.start() + visible_message("[src] warps in!") + playsound(src.loc, 'sound/effects/empulse.ogg', 25, TRUE) + addtimer(CALLBACK(src, .proc/warpbots), rand(10, 600)) + +/obj/structure/hivebot_beacon/proc/warpbots() + icon_state = "def_radar" + visible_message("[src] turns on!") + while(bot_amt > 0) + bot_amt-- + switch(bot_type) + if("norm") + new /mob/living/simple_animal/hostile/hivebot(get_turf(src)) + if("range") + new /mob/living/simple_animal/hostile/hivebot/range(get_turf(src)) + if("rapid") + new /mob/living/simple_animal/hostile/hivebot/rapid(get_turf(src)) + sleep(100) + visible_message("[src] warps out!") + playsound(src.loc, 'sound/effects/empulse.ogg', 25, TRUE) + qdel(src) + return diff --git a/code/game/objects/structures/kitchen_spike.dm b/code/game/objects/structures/kitchen_spike.dm index 888ef6134574..9cde07690138 100644 --- a/code/game/objects/structures/kitchen_spike.dm +++ b/code/game/objects/structures/kitchen_spike.dm @@ -1,149 +1,149 @@ -//////Kitchen Spike -#define VIABLE_MOB_CHECK(X) (isliving(X) && !issilicon(X) && !isbot(X)) - -/obj/structure/kitchenspike_frame - name = "meatspike frame" - icon = 'icons/obj/kitchen.dmi' - icon_state = "spikeframe" - desc = "The frame of a meat spike." - density = TRUE - anchored = FALSE - max_integrity = 200 - -/obj/structure/kitchenspike_frame/attackby(obj/item/I, mob/user, params) - add_fingerprint(user) - if(default_unfasten_wrench(user, I)) - return - else if(istype(I, /obj/item/stack/rods)) - var/obj/item/stack/rods/R = I - if(R.get_amount() >= 4) - R.use(4) - to_chat(user, "You add spikes to the frame.") - var/obj/F = new /obj/structure/kitchenspike(src.loc) - transfer_fingerprints_to(F) - qdel(src) - else if(I.tool_behaviour == TOOL_WELDER) - if(!I.tool_start_check(user, amount=0)) - return - to_chat(user, "You begin cutting \the [src] apart...") - if(I.use_tool(src, user, 50, volume=50)) - visible_message("[user] slices apart \the [src].", - "You cut \the [src] apart with \the [I].", - "You hear welding.") - new /obj/item/stack/sheet/metal(src.loc, 4) - qdel(src) - return - else - return ..() - -/obj/structure/kitchenspike - name = "meat spike" - icon = 'icons/obj/kitchen.dmi' - icon_state = "spike" - desc = "A spike for collecting meat from animals." - density = TRUE - anchored = TRUE - buckle_lying = 0 - can_buckle = 1 - max_integrity = 250 - -/obj/structure/kitchenspike/attack_paw(mob/user) - return attack_hand(user) - -/obj/structure/kitchenspike/crowbar_act(mob/living/user, obj/item/I) - if(has_buckled_mobs()) - to_chat(user, "You can't do that while something's on the spike!") - return TRUE - - if(I.use_tool(src, user, 20, volume=100)) - to_chat(user, "You pry the spikes out of the frame.") - deconstruct(TRUE) - return TRUE - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/structure/kitchenspike/attack_hand(mob/user) - if(VIABLE_MOB_CHECK(user.pulling) && user.a_intent == INTENT_GRAB && !has_buckled_mobs()) - var/mob/living/L = user.pulling - if(do_mob(user, src, 120)) - if(has_buckled_mobs()) //to prevent spam/queing up attacks - return - if(L.buckled) - return - if(user.pulling != L) - return - playsound(src.loc, 'sound/effects/splat.ogg', 25, TRUE) - L.visible_message("[user] slams [L] onto the meat spike!", "[user] slams you onto the meat spike!", "You hear a squishy wet noise.") - L.forceMove(drop_location()) - L.emote("scream") - L.add_splatter_floor() - L.adjustBruteLoss(30) - L.setDir(2) - buckle_mob(L, force=1) - var/matrix/m180 = matrix(L.transform) - m180.Turn(180) - animate(L, transform = m180, time = 3) - L.pixel_y = L.get_standard_pixel_y_offset(180) - else if (has_buckled_mobs()) - for(var/mob/living/L in buckled_mobs) - user_unbuckle_mob(L, user) - else - ..() - - - -/obj/structure/kitchenspike/user_buckle_mob(mob/living/M, mob/living/user) //Don't want them getting put on the rack other than by spiking - return - -/obj/structure/kitchenspike/user_unbuckle_mob(mob/living/buckled_mob, mob/living/carbon/human/user) - if(buckled_mob) - var/mob/living/M = buckled_mob - if(M != user) - M.visible_message("[user] tries to pull [M] free of [src]!",\ - "[user] is trying to pull you off [src], opening up fresh wounds!",\ - "You hear a squishy wet noise.") - if(!do_after(user, 300, target = src)) - if(M && M.buckled) - M.visible_message("[user] fails to free [M]!",\ - "[user] fails to pull you off of [src].") - return - - else - M.visible_message("[M] struggles to break free from [src]!",\ - "You struggle to break free from [src], exacerbating your wounds! (Stay still for two minutes.)",\ - "You hear a wet squishing noise..") - M.adjustBruteLoss(30) - if(!do_after(M, 1200, target = src)) - if(M && M.buckled) - to_chat(M, "You fail to free yourself!") - return - if(!M.buckled) - return - release_mob(M) - -/obj/structure/kitchenspike/proc/release_mob(mob/living/M) - var/matrix/m180 = matrix(M.transform) - m180.Turn(180) - animate(M, transform = m180, time = 3) - M.pixel_y = M.get_standard_pixel_y_offset(180) - M.adjustBruteLoss(30) - src.visible_message(text("[M] falls free of [src]!")) - unbuckle_mob(M,force=1) - M.emote("scream") - M.AdjustParalyzed(20) - -/obj/structure/kitchenspike/Destroy() - if(has_buckled_mobs()) - for(var/mob/living/L in buckled_mobs) - release_mob(L) - return ..() - -/obj/structure/kitchenspike/deconstruct(disassembled = TRUE) - if(disassembled) - var/obj/F = new /obj/structure/kitchenspike_frame(src.loc) - transfer_fingerprints_to(F) - else - new /obj/item/stack/sheet/metal(src.loc, 4) - new /obj/item/stack/rods(loc, 4) - qdel(src) - -#undef VIABLE_MOB_CHECK +//////Kitchen Spike +#define VIABLE_MOB_CHECK(X) (isliving(X) && !issilicon(X) && !isbot(X)) + +/obj/structure/kitchenspike_frame + name = "meatspike frame" + icon = 'icons/obj/kitchen.dmi' + icon_state = "spikeframe" + desc = "The frame of a meat spike." + density = TRUE + anchored = FALSE + max_integrity = 200 + +/obj/structure/kitchenspike_frame/attackby(obj/item/I, mob/user, params) + add_fingerprint(user) + if(default_unfasten_wrench(user, I)) + return + else if(istype(I, /obj/item/stack/rods)) + var/obj/item/stack/rods/R = I + if(R.get_amount() >= 4) + R.use(4) + to_chat(user, "You add spikes to the frame.") + var/obj/F = new /obj/structure/kitchenspike(src.loc) + transfer_fingerprints_to(F) + qdel(src) + else if(I.tool_behaviour == TOOL_WELDER) + if(!I.tool_start_check(user, amount=0)) + return + to_chat(user, "You begin cutting \the [src] apart...") + if(I.use_tool(src, user, 50, volume=50)) + visible_message("[user] slices apart \the [src].", + "You cut \the [src] apart with \the [I].", + "You hear welding.") + new /obj/item/stack/sheet/metal(src.loc, 4) + qdel(src) + return + else + return ..() + +/obj/structure/kitchenspike + name = "meat spike" + icon = 'icons/obj/kitchen.dmi' + icon_state = "spike" + desc = "A spike for collecting meat from animals." + density = TRUE + anchored = TRUE + buckle_lying = 0 + can_buckle = 1 + max_integrity = 250 + +/obj/structure/kitchenspike/attack_paw(mob/user) + return attack_hand(user) + +/obj/structure/kitchenspike/crowbar_act(mob/living/user, obj/item/I) + if(has_buckled_mobs()) + to_chat(user, "You can't do that while something's on the spike!") + return TRUE + + if(I.use_tool(src, user, 20, volume=100)) + to_chat(user, "You pry the spikes out of the frame.") + deconstruct(TRUE) + return TRUE + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/structure/kitchenspike/attack_hand(mob/user) + if(VIABLE_MOB_CHECK(user.pulling) && user.a_intent == INTENT_GRAB && !has_buckled_mobs()) + var/mob/living/L = user.pulling + if(do_mob(user, src, 120)) + if(has_buckled_mobs()) //to prevent spam/queing up attacks + return + if(L.buckled) + return + if(user.pulling != L) + return + playsound(src.loc, 'sound/effects/splat.ogg', 25, TRUE) + L.visible_message("[user] slams [L] onto the meat spike!", "[user] slams you onto the meat spike!", "You hear a squishy wet noise.") + L.forceMove(drop_location()) + L.emote("scream") + L.add_splatter_floor() + L.adjustBruteLoss(30) + L.setDir(2) + buckle_mob(L, force=1) + var/matrix/m180 = matrix(L.transform) + m180.Turn(180) + animate(L, transform = m180, time = 3) + L.pixel_y = L.get_standard_pixel_y_offset(180) + else if (has_buckled_mobs()) + for(var/mob/living/L in buckled_mobs) + user_unbuckle_mob(L, user) + else + ..() + + + +/obj/structure/kitchenspike/user_buckle_mob(mob/living/M, mob/living/user) //Don't want them getting put on the rack other than by spiking + return + +/obj/structure/kitchenspike/user_unbuckle_mob(mob/living/buckled_mob, mob/living/carbon/human/user) + if(buckled_mob) + var/mob/living/M = buckled_mob + if(M != user) + M.visible_message("[user] tries to pull [M] free of [src]!",\ + "[user] is trying to pull you off [src], opening up fresh wounds!",\ + "You hear a squishy wet noise.") + if(!do_after(user, 300, target = src)) + if(M && M.buckled) + M.visible_message("[user] fails to free [M]!",\ + "[user] fails to pull you off of [src].") + return + + else + M.visible_message("[M] struggles to break free from [src]!",\ + "You struggle to break free from [src], exacerbating your wounds! (Stay still for two minutes.)",\ + "You hear a wet squishing noise..") + M.adjustBruteLoss(30) + if(!do_after(M, 1200, target = src)) + if(M && M.buckled) + to_chat(M, "You fail to free yourself!") + return + if(!M.buckled) + return + release_mob(M) + +/obj/structure/kitchenspike/proc/release_mob(mob/living/M) + var/matrix/m180 = matrix(M.transform) + m180.Turn(180) + animate(M, transform = m180, time = 3) + M.pixel_y = M.get_standard_pixel_y_offset(180) + M.adjustBruteLoss(30) + src.visible_message(text("[M] falls free of [src]!")) + unbuckle_mob(M,force=1) + M.emote("scream") + M.AdjustParalyzed(20) + +/obj/structure/kitchenspike/Destroy() + if(has_buckled_mobs()) + for(var/mob/living/L in buckled_mobs) + release_mob(L) + return ..() + +/obj/structure/kitchenspike/deconstruct(disassembled = TRUE) + if(disassembled) + var/obj/F = new /obj/structure/kitchenspike_frame(src.loc) + transfer_fingerprints_to(F) + else + new /obj/item/stack/sheet/metal(src.loc, 4) + new /obj/item/stack/rods(loc, 4) + qdel(src) + +#undef VIABLE_MOB_CHECK diff --git a/code/game/objects/structures/ladders.dm b/code/game/objects/structures/ladders.dm index 63fae9f4548b..46e606b84933 100644 --- a/code/game/objects/structures/ladders.dm +++ b/code/game/objects/structures/ladders.dm @@ -1,190 +1,190 @@ -// Basic ladder. By default links to the z-level above/below. -/obj/structure/ladder - name = "ladder" - desc = "A sturdy metal ladder." - icon = 'icons/obj/structures.dmi' - icon_state = "ladder11" - anchored = TRUE - var/obj/structure/ladder/down //the ladder below this one - var/obj/structure/ladder/up //the ladder above this one - -/obj/structure/ladder/Initialize(mapload, obj/structure/ladder/up, obj/structure/ladder/down) - ..() - if (up) - src.up = up - up.down = src - up.update_icon() - if (down) - src.down = down - down.up = src - down.update_icon() - return INITIALIZE_HINT_LATELOAD - -/obj/structure/ladder/Destroy(force) - if ((resistance_flags & INDESTRUCTIBLE) && !force) - return QDEL_HINT_LETMELIVE - disconnect() - return ..() - -/obj/structure/ladder/LateInitialize() - // By default, discover ladders above and below us vertically - var/turf/T = get_turf(src) - var/obj/structure/ladder/L - - if (!down) - L = locate() in SSmapping.get_turf_below(T) - if (L) - down = L - L.up = src // Don't waste effort looping the other way - L.update_icon() - if (!up) - L = locate() in SSmapping.get_turf_above(T) - if (L) - up = L - L.down = src // Don't waste effort looping the other way - L.update_icon() - - update_icon() - -/obj/structure/ladder/proc/disconnect() - if(up && up.down == src) - up.down = null - up.update_icon() - if(down && down.up == src) - down.up = null - down.update_icon() - up = down = null - -/obj/structure/ladder/update_icon_state() - if(up && down) - icon_state = "ladder11" - else if(up) - icon_state = "ladder10" - else if(down) - icon_state = "ladder01" - else //wtf make your ladders properly assholes - icon_state = "ladder00" - -/obj/structure/ladder/singularity_pull() - if (!(resistance_flags & INDESTRUCTIBLE)) - visible_message("[src] is torn to pieces by the gravitational pull!") - qdel(src) - -/obj/structure/ladder/proc/travel(going_up, mob/user, is_ghost, obj/structure/ladder/ladder) - if(!is_ghost) - show_fluff_message(going_up, user) - ladder.add_fingerprint(user) - - var/turf/T = get_turf(ladder) - var/atom/movable/AM - if(user.pulling) - AM = user.pulling - AM.forceMove(T) - user.forceMove(T) - if(AM) - user.start_pulling(AM) - -/obj/structure/ladder/proc/use(mob/user, is_ghost=FALSE) - if (!is_ghost && !in_range(src, user)) - return - - var/list/tool_list = list( - "Up" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = NORTH), - "Down" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = SOUTH) - ) - - if (up && down) - var/result = show_radial_menu(user, src, tool_list, custom_check = CALLBACK(src, .proc/check_menu, user), require_near = TRUE, tooltips = TRUE) - if (!is_ghost && !in_range(src, user)) - return // nice try - switch(result) - if("Up") - travel(TRUE, user, is_ghost, up) - if("Down") - travel(FALSE, user, is_ghost, down) - if("Cancel") - return - else if(up) - travel(TRUE, user, is_ghost, up) - else if(down) - travel(FALSE, user, is_ghost, down) - else - to_chat(user, "[src] doesn't seem to lead anywhere!") - - if(!is_ghost) - add_fingerprint(user) - -/obj/structure/ladder/proc/check_menu(mob/user) - if(user.incapacitated() || !user.Adjacent(src)) - return FALSE - return TRUE - -/obj/structure/ladder/attack_hand(mob/user) - . = ..() - if(.) - return - use(user) - -/obj/structure/ladder/attack_paw(mob/user) - return use(user) - -/obj/structure/ladder/attackby(obj/item/W, mob/user, params) - return use(user) - -/obj/structure/ladder/attack_robot(mob/living/silicon/robot/R) - if(R.Adjacent(src)) - return use(R) - -//ATTACK GHOST IGNORING PARENT RETURN VALUE -/obj/structure/ladder/attack_ghost(mob/dead/observer/user) - use(user, TRUE) - return ..() - -/obj/structure/ladder/proc/show_fluff_message(going_up, mob/user) - if(going_up) - user.visible_message("[user] climbs up [src].", "You climb up [src].") - else - user.visible_message("[user] climbs down [src].", "You climb down [src].") - - -// Indestructible away mission ladders which link based on a mapped ID and height value rather than X/Y/Z. -/obj/structure/ladder/unbreakable - name = "sturdy ladder" - desc = "An extremely sturdy metal ladder." - resistance_flags = INDESTRUCTIBLE - var/id - var/height = 0 // higher numbers are considered physically higher - -/obj/structure/ladder/unbreakable/Initialize() - GLOB.ladders += src - return ..() - -/obj/structure/ladder/unbreakable/Destroy() - . = ..() - if (. != QDEL_HINT_LETMELIVE) - GLOB.ladders -= src - -/obj/structure/ladder/unbreakable/LateInitialize() - // Override the parent to find ladders based on being height-linked - if (!id || (up && down)) - update_icon() - return - - for (var/O in GLOB.ladders) - var/obj/structure/ladder/unbreakable/L = O - if (L.id != id) - continue // not one of our pals - if (!down && L.height == height - 1) - down = L - L.up = src - L.update_icon() - if (up) - break // break if both our connections are filled - else if (!up && L.height == height + 1) - up = L - L.down = src - L.update_icon() - if (down) - break // break if both our connections are filled - - update_icon() +// Basic ladder. By default links to the z-level above/below. +/obj/structure/ladder + name = "ladder" + desc = "A sturdy metal ladder." + icon = 'icons/obj/structures.dmi' + icon_state = "ladder11" + anchored = TRUE + var/obj/structure/ladder/down //the ladder below this one + var/obj/structure/ladder/up //the ladder above this one + +/obj/structure/ladder/Initialize(mapload, obj/structure/ladder/up, obj/structure/ladder/down) + ..() + if (up) + src.up = up + up.down = src + up.update_icon() + if (down) + src.down = down + down.up = src + down.update_icon() + return INITIALIZE_HINT_LATELOAD + +/obj/structure/ladder/Destroy(force) + if ((resistance_flags & INDESTRUCTIBLE) && !force) + return QDEL_HINT_LETMELIVE + disconnect() + return ..() + +/obj/structure/ladder/LateInitialize() + // By default, discover ladders above and below us vertically + var/turf/T = get_turf(src) + var/obj/structure/ladder/L + + if (!down) + L = locate() in SSmapping.get_turf_below(T) + if (L) + down = L + L.up = src // Don't waste effort looping the other way + L.update_icon() + if (!up) + L = locate() in SSmapping.get_turf_above(T) + if (L) + up = L + L.down = src // Don't waste effort looping the other way + L.update_icon() + + update_icon() + +/obj/structure/ladder/proc/disconnect() + if(up && up.down == src) + up.down = null + up.update_icon() + if(down && down.up == src) + down.up = null + down.update_icon() + up = down = null + +/obj/structure/ladder/update_icon_state() + if(up && down) + icon_state = "ladder11" + else if(up) + icon_state = "ladder10" + else if(down) + icon_state = "ladder01" + else //wtf make your ladders properly assholes + icon_state = "ladder00" + +/obj/structure/ladder/singularity_pull() + if (!(resistance_flags & INDESTRUCTIBLE)) + visible_message("[src] is torn to pieces by the gravitational pull!") + qdel(src) + +/obj/structure/ladder/proc/travel(going_up, mob/user, is_ghost, obj/structure/ladder/ladder) + if(!is_ghost) + show_fluff_message(going_up, user) + ladder.add_fingerprint(user) + + var/turf/T = get_turf(ladder) + var/atom/movable/AM + if(user.pulling) + AM = user.pulling + AM.forceMove(T) + user.forceMove(T) + if(AM) + user.start_pulling(AM) + +/obj/structure/ladder/proc/use(mob/user, is_ghost=FALSE) + if (!is_ghost && !in_range(src, user)) + return + + var/list/tool_list = list( + "Up" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = NORTH), + "Down" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = SOUTH) + ) + + if (up && down) + var/result = show_radial_menu(user, src, tool_list, custom_check = CALLBACK(src, .proc/check_menu, user), require_near = TRUE, tooltips = TRUE) + if (!is_ghost && !in_range(src, user)) + return // nice try + switch(result) + if("Up") + travel(TRUE, user, is_ghost, up) + if("Down") + travel(FALSE, user, is_ghost, down) + if("Cancel") + return + else if(up) + travel(TRUE, user, is_ghost, up) + else if(down) + travel(FALSE, user, is_ghost, down) + else + to_chat(user, "[src] doesn't seem to lead anywhere!") + + if(!is_ghost) + add_fingerprint(user) + +/obj/structure/ladder/proc/check_menu(mob/user) + if(user.incapacitated() || !user.Adjacent(src)) + return FALSE + return TRUE + +/obj/structure/ladder/attack_hand(mob/user) + . = ..() + if(.) + return + use(user) + +/obj/structure/ladder/attack_paw(mob/user) + return use(user) + +/obj/structure/ladder/attackby(obj/item/W, mob/user, params) + return use(user) + +/obj/structure/ladder/attack_robot(mob/living/silicon/robot/R) + if(R.Adjacent(src)) + return use(R) + +//ATTACK GHOST IGNORING PARENT RETURN VALUE +/obj/structure/ladder/attack_ghost(mob/dead/observer/user) + use(user, TRUE) + return ..() + +/obj/structure/ladder/proc/show_fluff_message(going_up, mob/user) + if(going_up) + user.visible_message("[user] climbs up [src].", "You climb up [src].") + else + user.visible_message("[user] climbs down [src].", "You climb down [src].") + + +// Indestructible away mission ladders which link based on a mapped ID and height value rather than X/Y/Z. +/obj/structure/ladder/unbreakable + name = "sturdy ladder" + desc = "An extremely sturdy metal ladder." + resistance_flags = INDESTRUCTIBLE + var/id + var/height = 0 // higher numbers are considered physically higher + +/obj/structure/ladder/unbreakable/Initialize() + GLOB.ladders += src + return ..() + +/obj/structure/ladder/unbreakable/Destroy() + . = ..() + if (. != QDEL_HINT_LETMELIVE) + GLOB.ladders -= src + +/obj/structure/ladder/unbreakable/LateInitialize() + // Override the parent to find ladders based on being height-linked + if (!id || (up && down)) + update_icon() + return + + for (var/O in GLOB.ladders) + var/obj/structure/ladder/unbreakable/L = O + if (L.id != id) + continue // not one of our pals + if (!down && L.height == height - 1) + down = L + L.up = src + L.update_icon() + if (up) + break // break if both our connections are filled + else if (!up && L.height == height + 1) + up = L + L.down = src + L.update_icon() + if (down) + break // break if both our connections are filled + + update_icon() diff --git a/code/game/objects/structures/manned_turret.dm b/code/game/objects/structures/manned_turret.dm index 4a71e4f26b75..a153af88a243 100644 --- a/code/game/objects/structures/manned_turret.dm +++ b/code/game/objects/structures/manned_turret.dm @@ -1,216 +1,216 @@ -/////// MANNED TURRET //////// - -/obj/machinery/manned_turret - name = "machine gun turret" - desc = "While the trigger is held down, this gun will redistribute recoil to allow its user to easily shift targets." - icon = 'icons/obj/turrets.dmi' - icon_state = "machinegun" - can_buckle = TRUE - anchored = FALSE - density = TRUE - max_integrity = 100 - buckle_lying = FALSE - layer = ABOVE_MOB_LAYER - var/view_range = 10 - var/cooldown = 0 - var/projectile_type = /obj/projectile/bullet/manned_turret - var/rate_of_fire = 1 - var/number_of_shots = 40 - var/cooldown_duration = 90 - var/atom/target - var/turf/target_turf - var/warned = FALSE - var/list/calculated_projectile_vars - -/obj/machinery/manned_turret/Destroy() - target = null - target_turf = null - ..() - -//BUCKLE HOOKS - -/obj/machinery/manned_turret/unbuckle_mob(mob/living/buckled_mob,force = FALSE) - playsound(src,'sound/mecha/mechmove01.ogg', 50, TRUE) - for(var/obj/item/I in buckled_mob.held_items) - if(istype(I, /obj/item/gun_control)) - qdel(I) - if(istype(buckled_mob)) - buckled_mob.pixel_x = 0 - buckled_mob.pixel_y = 0 - if(buckled_mob.client) - buckled_mob.client.change_view(CONFIG_GET(string/default_view)) - anchored = FALSE - . = ..() - STOP_PROCESSING(SSfastprocess, src) - -/obj/machinery/manned_turret/user_buckle_mob(mob/living/M, mob/living/carbon/user) - if(user.incapacitated() || !istype(user)) - return - M.forceMove(get_turf(src)) - . = ..() - if(!.) - return - for(var/V in M.held_items) - var/obj/item/I = V - if(istype(I)) - if(M.dropItemToGround(I)) - var/obj/item/gun_control/TC = new(src) - M.put_in_hands(TC) - else //Entries in the list should only ever be items or null, so if it's not an item, we can assume it's an empty hand - var/obj/item/gun_control/TC = new(src) - M.put_in_hands(TC) - M.pixel_y = 14 - layer = ABOVE_MOB_LAYER - setDir(SOUTH) - playsound(src,'sound/mecha/mechmove01.ogg', 50, TRUE) - anchored = TRUE - if(M.client) - M.client.change_view(view_range) - START_PROCESSING(SSfastprocess, src) - -/obj/machinery/manned_turret/process() - if (!update_positioning()) - return PROCESS_KILL - -/obj/machinery/manned_turret/proc/update_positioning() - if (!LAZYLEN(buckled_mobs)) - return FALSE - var/mob/living/controller = buckled_mobs[1] - if(!istype(controller)) - return FALSE - var/client/C = controller.client - if(C) - var/atom/A = C.mouseObject - var/turf/T = get_turf(A) - if(istype(T)) //They're hovering over something in the map. - direction_track(controller, T) - calculated_projectile_vars = calculate_projectile_angle_and_pixel_offsets(controller, C.mouseParams) - -/obj/machinery/manned_turret/proc/direction_track(mob/user, atom/targeted) - if(user.incapacitated()) - return - setDir(get_dir(src,targeted)) - user.setDir(dir) - switch(dir) - if(NORTH) - layer = BELOW_MOB_LAYER - user.pixel_x = 0 - user.pixel_y = -14 - if(NORTHEAST) - layer = BELOW_MOB_LAYER - user.pixel_x = -8 - user.pixel_y = -4 - if(EAST) - layer = ABOVE_MOB_LAYER - user.pixel_x = -14 - user.pixel_y = 0 - if(SOUTHEAST) - layer = BELOW_MOB_LAYER - user.pixel_x = -8 - user.pixel_y = 4 - if(SOUTH) - layer = ABOVE_MOB_LAYER - user.pixel_x = 0 - user.pixel_y = 14 - if(SOUTHWEST) - layer = BELOW_MOB_LAYER - user.pixel_x = 8 - user.pixel_y = 4 - if(WEST) - layer = ABOVE_MOB_LAYER - user.pixel_x = 14 - user.pixel_y = 0 - if(NORTHWEST) - layer = BELOW_MOB_LAYER - user.pixel_x = 8 - user.pixel_y = -4 - -/obj/machinery/manned_turret/proc/checkfire(atom/targeted_atom, mob/user) - target = targeted_atom - if(target == user || user.incapacitated() || target == get_turf(src)) - return - if(world.time < cooldown) - if(!warned && world.time > (cooldown - cooldown_duration + rate_of_fire*number_of_shots)) // To capture the window where one is done firing - warned = TRUE - playsound(src, 'sound/weapons/sear.ogg', 100, TRUE) - return - else - cooldown = world.time + cooldown_duration - warned = FALSE - volley(user) - -/obj/machinery/manned_turret/proc/volley(mob/user) - target_turf = get_turf(target) - for(var/i in 1 to number_of_shots) - addtimer(CALLBACK(src, /obj/machinery/manned_turret/.proc/fire_helper, user), i*rate_of_fire) - -/obj/machinery/manned_turret/proc/fire_helper(mob/user) - if(user.incapacitated() || !(user in buckled_mobs)) - return - update_positioning() //REFRESH MOUSE TRACKING!! - var/turf/targets_from = get_turf(src) - if(QDELETED(target)) - target = target_turf - var/obj/projectile/P = new projectile_type(targets_from) - P.starting = targets_from - P.firer = user - P.original = target - playsound(src, 'sound/weapons/gun/smg/shot.ogg', 75, TRUE) - P.xo = target.x - targets_from.x - P.yo = target.y - targets_from.y - P.Angle = calculated_projectile_vars[1] + rand(-9, 9) - P.p_x = calculated_projectile_vars[2] - P.p_y = calculated_projectile_vars[3] - P.fire() - -/obj/machinery/manned_turret/ultimate // Admin-only proof of concept for autoclicker automatics - name = "Infinity Gun" - view_range = 12 - projectile_type = /obj/projectile/bullet/manned_turret - -/obj/machinery/manned_turret/ultimate/checkfire(atom/targeted_atom, mob/user) - target = targeted_atom - if(target == user || target == get_turf(src)) - return - target_turf = get_turf(target) - fire_helper(user) - -/obj/item/gun_control - name = "turret controls" - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "offhand" - w_class = WEIGHT_CLASS_HUGE - item_flags = ABSTRACT | NOBLUDGEON | DROPDEL - resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF - var/obj/machinery/manned_turret/turret - -/obj/item/gun_control/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT) - turret = loc - if(!istype(turret)) - return INITIALIZE_HINT_QDEL - -/obj/item/gun_control/Destroy() - turret = null - ..() - -/obj/item/gun_control/CanItemAutoclick() - return TRUE - -/obj/item/gun_control/attack_obj(obj/O, mob/living/user) - user.changeNext_move(CLICK_CD_MELEE) - O.attacked_by(src, user) - -/obj/item/gun_control/attack(mob/living/M, mob/living/user) - M.lastattacker = user.real_name - M.lastattackerckey = user.ckey - M.attacked_by(src, user) - add_fingerprint(user) - -/obj/item/gun_control/afterattack(atom/targeted_atom, mob/user, flag, params) - . = ..() - var/obj/machinery/manned_turret/E = user.buckled - E.calculated_projectile_vars = calculate_projectile_angle_and_pixel_offsets(user, params) - E.direction_track(user, targeted_atom) - E.checkfire(targeted_atom, user) +/////// MANNED TURRET //////// + +/obj/machinery/manned_turret + name = "machine gun turret" + desc = "While the trigger is held down, this gun will redistribute recoil to allow its user to easily shift targets." + icon = 'icons/obj/turrets.dmi' + icon_state = "machinegun" + can_buckle = TRUE + anchored = FALSE + density = TRUE + max_integrity = 100 + buckle_lying = FALSE + layer = ABOVE_MOB_LAYER + var/view_range = 10 + var/cooldown = 0 + var/projectile_type = /obj/projectile/bullet/manned_turret + var/rate_of_fire = 1 + var/number_of_shots = 40 + var/cooldown_duration = 90 + var/atom/target + var/turf/target_turf + var/warned = FALSE + var/list/calculated_projectile_vars + +/obj/machinery/manned_turret/Destroy() + target = null + target_turf = null + ..() + +//BUCKLE HOOKS + +/obj/machinery/manned_turret/unbuckle_mob(mob/living/buckled_mob,force = FALSE) + playsound(src,'sound/mecha/mechmove01.ogg', 50, TRUE) + for(var/obj/item/I in buckled_mob.held_items) + if(istype(I, /obj/item/gun_control)) + qdel(I) + if(istype(buckled_mob)) + buckled_mob.pixel_x = 0 + buckled_mob.pixel_y = 0 + if(buckled_mob.client) + buckled_mob.client.change_view(CONFIG_GET(string/default_view)) + anchored = FALSE + . = ..() + STOP_PROCESSING(SSfastprocess, src) + +/obj/machinery/manned_turret/user_buckle_mob(mob/living/M, mob/living/carbon/user) + if(user.incapacitated() || !istype(user)) + return + M.forceMove(get_turf(src)) + . = ..() + if(!.) + return + for(var/V in M.held_items) + var/obj/item/I = V + if(istype(I)) + if(M.dropItemToGround(I)) + var/obj/item/gun_control/TC = new(src) + M.put_in_hands(TC) + else //Entries in the list should only ever be items or null, so if it's not an item, we can assume it's an empty hand + var/obj/item/gun_control/TC = new(src) + M.put_in_hands(TC) + M.pixel_y = 14 + layer = ABOVE_MOB_LAYER + setDir(SOUTH) + playsound(src,'sound/mecha/mechmove01.ogg', 50, TRUE) + anchored = TRUE + if(M.client) + M.client.change_view(view_range) + START_PROCESSING(SSfastprocess, src) + +/obj/machinery/manned_turret/process() + if (!update_positioning()) + return PROCESS_KILL + +/obj/machinery/manned_turret/proc/update_positioning() + if (!LAZYLEN(buckled_mobs)) + return FALSE + var/mob/living/controller = buckled_mobs[1] + if(!istype(controller)) + return FALSE + var/client/C = controller.client + if(C) + var/atom/A = C.mouseObject + var/turf/T = get_turf(A) + if(istype(T)) //They're hovering over something in the map. + direction_track(controller, T) + calculated_projectile_vars = calculate_projectile_angle_and_pixel_offsets(controller, C.mouseParams) + +/obj/machinery/manned_turret/proc/direction_track(mob/user, atom/targeted) + if(user.incapacitated()) + return + setDir(get_dir(src,targeted)) + user.setDir(dir) + switch(dir) + if(NORTH) + layer = BELOW_MOB_LAYER + user.pixel_x = 0 + user.pixel_y = -14 + if(NORTHEAST) + layer = BELOW_MOB_LAYER + user.pixel_x = -8 + user.pixel_y = -4 + if(EAST) + layer = ABOVE_MOB_LAYER + user.pixel_x = -14 + user.pixel_y = 0 + if(SOUTHEAST) + layer = BELOW_MOB_LAYER + user.pixel_x = -8 + user.pixel_y = 4 + if(SOUTH) + layer = ABOVE_MOB_LAYER + user.pixel_x = 0 + user.pixel_y = 14 + if(SOUTHWEST) + layer = BELOW_MOB_LAYER + user.pixel_x = 8 + user.pixel_y = 4 + if(WEST) + layer = ABOVE_MOB_LAYER + user.pixel_x = 14 + user.pixel_y = 0 + if(NORTHWEST) + layer = BELOW_MOB_LAYER + user.pixel_x = 8 + user.pixel_y = -4 + +/obj/machinery/manned_turret/proc/checkfire(atom/targeted_atom, mob/user) + target = targeted_atom + if(target == user || user.incapacitated() || target == get_turf(src)) + return + if(world.time < cooldown) + if(!warned && world.time > (cooldown - cooldown_duration + rate_of_fire*number_of_shots)) // To capture the window where one is done firing + warned = TRUE + playsound(src, 'sound/weapons/sear.ogg', 100, TRUE) + return + else + cooldown = world.time + cooldown_duration + warned = FALSE + volley(user) + +/obj/machinery/manned_turret/proc/volley(mob/user) + target_turf = get_turf(target) + for(var/i in 1 to number_of_shots) + addtimer(CALLBACK(src, /obj/machinery/manned_turret/.proc/fire_helper, user), i*rate_of_fire) + +/obj/machinery/manned_turret/proc/fire_helper(mob/user) + if(user.incapacitated() || !(user in buckled_mobs)) + return + update_positioning() //REFRESH MOUSE TRACKING!! + var/turf/targets_from = get_turf(src) + if(QDELETED(target)) + target = target_turf + var/obj/projectile/P = new projectile_type(targets_from) + P.starting = targets_from + P.firer = user + P.original = target + playsound(src, 'sound/weapons/gun/smg/shot.ogg', 75, TRUE) + P.xo = target.x - targets_from.x + P.yo = target.y - targets_from.y + P.Angle = calculated_projectile_vars[1] + rand(-9, 9) + P.p_x = calculated_projectile_vars[2] + P.p_y = calculated_projectile_vars[3] + P.fire() + +/obj/machinery/manned_turret/ultimate // Admin-only proof of concept for autoclicker automatics + name = "Infinity Gun" + view_range = 12 + projectile_type = /obj/projectile/bullet/manned_turret + +/obj/machinery/manned_turret/ultimate/checkfire(atom/targeted_atom, mob/user) + target = targeted_atom + if(target == user || target == get_turf(src)) + return + target_turf = get_turf(target) + fire_helper(user) + +/obj/item/gun_control + name = "turret controls" + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "offhand" + w_class = WEIGHT_CLASS_HUGE + item_flags = ABSTRACT | NOBLUDGEON | DROPDEL + resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF + var/obj/machinery/manned_turret/turret + +/obj/item/gun_control/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT) + turret = loc + if(!istype(turret)) + return INITIALIZE_HINT_QDEL + +/obj/item/gun_control/Destroy() + turret = null + ..() + +/obj/item/gun_control/CanItemAutoclick() + return TRUE + +/obj/item/gun_control/attack_obj(obj/O, mob/living/user) + user.changeNext_move(CLICK_CD_MELEE) + O.attacked_by(src, user) + +/obj/item/gun_control/attack(mob/living/M, mob/living/user) + M.lastattacker = user.real_name + M.lastattackerckey = user.ckey + M.attacked_by(src, user) + add_fingerprint(user) + +/obj/item/gun_control/afterattack(atom/targeted_atom, mob/user, flag, params) + . = ..() + var/obj/machinery/manned_turret/E = user.buckled + E.calculated_projectile_vars = calculate_projectile_angle_and_pixel_offsets(user, params) + E.direction_track(user, targeted_atom) + E.checkfire(targeted_atom, user) diff --git a/code/game/objects/structures/mineral_doors.dm b/code/game/objects/structures/mineral_doors.dm index 1098f8cedd74..3bb560eedf91 100644 --- a/code/game/objects/structures/mineral_doors.dm +++ b/code/game/objects/structures/mineral_doors.dm @@ -1,350 +1,350 @@ -//NOT using the existing /obj/machinery/door type, since that has some complications on its own, mainly based on its -//machineryness - -/obj/structure/mineral_door - name = "metal door" - density = TRUE - anchored = TRUE - opacity = TRUE - layer = CLOSED_DOOR_LAYER - - icon = 'icons/obj/doors/mineral_doors.dmi' - icon_state = "metal" - max_integrity = 200 - armor = list("melee" = 10, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 50, "acid" = 50) - CanAtmosPass = ATMOS_PASS_DENSITY - flags_1 = RAD_PROTECT_CONTENTS_1 | RAD_NO_CONTAMINATE_1 - rad_insulation = RAD_MEDIUM_INSULATION - - var/door_opened = FALSE //if it's open or not. - var/isSwitchingStates = FALSE //don't try to change stats if we're already opening - - var/close_delay = -1 //-1 if does not auto close. - var/openSound = 'sound/effects/stonedoor_openclose.ogg' - var/closeSound = 'sound/effects/stonedoor_openclose.ogg' - - var/sheetType = /obj/item/stack/sheet/metal //what we're made of - var/sheetAmount = 7 //how much we drop when deconstructed - -/obj/structure/mineral_door/Initialize() - . = ..() - - air_update_turf(TRUE) - -/obj/structure/mineral_door/Move() - var/turf/T = loc - . = ..() - move_update_air(T) - -/obj/structure/mineral_door/Bumped(atom/movable/AM) - ..() - if(!door_opened) - return TryToSwitchState(AM) - -/obj/structure/mineral_door/attack_ai(mob/user) //those aren't machinery, they're just big fucking slabs of a mineral - if(isAI(user)) //so the AI can't open it - return - else if(iscyborg(user)) //but cyborgs can - if(get_dist(user,src) <= 1) //not remotely though - return TryToSwitchState(user) - -/obj/structure/mineral_door/attack_paw(mob/user) - return attack_hand(user) - -/obj/structure/mineral_door/attack_hand(mob/user) - . = ..() - if(.) - return - return TryToSwitchState(user) - -/obj/structure/mineral_door/CanAllowThrough(atom/movable/mover, turf/target) - . = ..() - if(istype(mover, /obj/effect/beam)) - return !opacity - -/obj/structure/mineral_door/proc/TryToSwitchState(atom/user) - if(isSwitchingStates || !anchored) - return - if(isliving(user)) - var/mob/living/M = user - if(world.time - M.last_bumped <= 60) - return //NOTE do we really need that? - if(M.client) - if(iscarbon(M)) - var/mob/living/carbon/C = M - if(!C.handcuffed) - SwitchState() - else - SwitchState() - else if(ismecha(user)) - SwitchState() - -/obj/structure/mineral_door/proc/SwitchState() - if(door_opened) - Close() - else - Open() - -/obj/structure/mineral_door/proc/Open() - isSwitchingStates = TRUE - playsound(src, openSound, 100, TRUE) - set_opacity(FALSE) - flick("[initial(icon_state)]opening",src) - sleep(10) - density = FALSE - door_opened = TRUE - layer = OPEN_DOOR_LAYER - air_update_turf(1) - update_icon() - isSwitchingStates = FALSE - - if(close_delay != -1) - addtimer(CALLBACK(src, .proc/Close), close_delay) - -/obj/structure/mineral_door/proc/Close() - if(isSwitchingStates || !door_opened) - return - var/turf/T = get_turf(src) - for(var/mob/living/L in T) - return - isSwitchingStates = TRUE - playsound(src, closeSound, 100, TRUE) - flick("[initial(icon_state)]closing",src) - sleep(10) - density = TRUE - set_opacity(TRUE) - door_opened = FALSE - layer = initial(layer) - air_update_turf(1) - update_icon() - isSwitchingStates = FALSE - -/obj/structure/mineral_door/update_icon_state() - icon_state = "[initial(icon_state)][door_opened ? "open":""]" - -/obj/structure/mineral_door/attackby(obj/item/I, mob/user) - if(pickaxe_door(user, I)) - return - else if(user.a_intent != INTENT_HARM) - return attack_hand(user) - else - return ..() - -/obj/structure/mineral_door/setAnchored(anchorvalue) //called in default_unfasten_wrench() chain - . = ..() - set_opacity(anchored ? !door_opened : FALSE) - air_update_turf(TRUE) - -/obj/structure/mineral_door/wrench_act(mob/living/user, obj/item/I) - ..() - default_unfasten_wrench(user, I, 40) - return TRUE - - -/////////////////////// TOOL OVERRIDES /////////////////////// - - -/obj/structure/mineral_door/proc/pickaxe_door(mob/living/user, obj/item/I) //override if the door isn't supposed to be a minable mineral. - if(!istype(user)) - return - if(I.tool_behaviour != TOOL_MINING) - return - . = TRUE - to_chat(user, "You start digging [src]...") - if(I.use_tool(src, user, 40, volume=50)) - to_chat(user, "You finish digging.") - deconstruct(TRUE) - -/obj/structure/mineral_door/welder_act(mob/living/user, obj/item/I) //override if the door is supposed to be flammable. - ..() - . = TRUE - if(anchored) - to_chat(user, "[src] is still firmly secured to the ground!") - return - - user.visible_message("[user] starts to weld apart [src]!", "You start welding apart [src].") - if(!I.use_tool(src, user, 60, 5, 50)) - to_chat(user, "You failed to weld apart [src]!") - return - - user.visible_message("[user] welded [src] into pieces!", "You welded apart [src]!") - deconstruct(TRUE) - -/obj/structure/mineral_door/proc/crowbar_door(mob/living/user, obj/item/I) //if the door is flammable, call this in crowbar_act() so we can still decon it - . = TRUE - if(anchored) - to_chat(user, "[src] is still firmly secured to the ground!") - return - - user.visible_message("[user] starts to pry apart [src]!", "You start prying apart [src].") - if(!I.use_tool(src, user, 60, volume = 50)) - to_chat(user, "You failed to pry apart [src]!") - return - - user.visible_message("[user] pried [src] into pieces!", "You pried apart [src]!") - deconstruct(TRUE) - - -/////////////////////// END TOOL OVERRIDES /////////////////////// - - -/obj/structure/mineral_door/deconstruct(disassembled = TRUE) - var/turf/T = get_turf(src) - if(disassembled) - new sheetType(T, sheetAmount) - else - new sheetType(T, max(sheetAmount - 2, 1)) - qdel(src) - - -/obj/structure/mineral_door/iron - name = "iron door" - max_integrity = 300 - -/obj/structure/mineral_door/silver - name = "silver door" - icon_state = "silver" - sheetType = /obj/item/stack/sheet/mineral/silver - max_integrity = 300 - rad_insulation = RAD_HEAVY_INSULATION - -/obj/structure/mineral_door/gold - name = "gold door" - icon_state = "gold" - sheetType = /obj/item/stack/sheet/mineral/gold - rad_insulation = RAD_HEAVY_INSULATION - -/obj/structure/mineral_door/uranium - name = "uranium door" - icon_state = "uranium" - sheetType = /obj/item/stack/sheet/mineral/uranium - max_integrity = 300 - light_range = 2 - -/obj/structure/mineral_door/uranium/ComponentInitialize() - return - -/obj/structure/mineral_door/sandstone - name = "sandstone door" - icon_state = "sandstone" - sheetType = /obj/item/stack/sheet/mineral/sandstone - max_integrity = 100 - -/obj/structure/mineral_door/transparent - opacity = FALSE - rad_insulation = RAD_VERY_LIGHT_INSULATION - -/obj/structure/mineral_door/transparent/Close() - ..() - set_opacity(FALSE) - -/obj/structure/mineral_door/transparent/plasma - name = "plasma door" - icon_state = "plasma" - sheetType = /obj/item/stack/sheet/mineral/plasma - -/obj/structure/mineral_door/transparent/plasma/ComponentInitialize() - return - -/obj/structure/mineral_door/transparent/plasma/welder_act(mob/living/user, obj/item/I) - return - -/obj/structure/mineral_door/transparent/plasma/attackby(obj/item/W, mob/user, params) - if(W.get_temperature()) - var/turf/T = get_turf(src) - message_admins("Plasma mineral door ignited by [ADMIN_LOOKUPFLW(user)] in [ADMIN_VERBOSEJMP(T)]") - log_game("Plasma mineral door ignited by [key_name(user)] in [AREACOORD(T)]") - TemperatureAct() - else - return ..() - -/obj/structure/mineral_door/transparent/plasma/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) - if(exposed_temperature > 300) - TemperatureAct() - -/obj/structure/mineral_door/transparent/plasma/proc/TemperatureAct() - atmos_spawn_air("plasma=500;TEMP=1000") - deconstruct(FALSE) - -/obj/structure/mineral_door/transparent/diamond - name = "diamond door" - icon_state = "diamond" - sheetType = /obj/item/stack/sheet/mineral/diamond - max_integrity = 1000 - rad_insulation = RAD_EXTREME_INSULATION - -/obj/structure/mineral_door/wood - name = "wood door" - icon_state = "wood" - openSound = 'sound/effects/doorcreaky.ogg' - closeSound = 'sound/effects/doorcreaky.ogg' - sheetType = /obj/item/stack/sheet/mineral/wood - resistance_flags = FLAMMABLE - max_integrity = 200 - rad_insulation = RAD_VERY_LIGHT_INSULATION - -/obj/structure/mineral_door/wood/pickaxe_door(mob/living/user, obj/item/I) - return - -/obj/structure/mineral_door/wood/welder_act(mob/living/user, obj/item/I) - return - -/obj/structure/mineral_door/wood/crowbar_act(mob/living/user, obj/item/I) - return crowbar_door(user, I) - -/obj/structure/mineral_door/wood/attackby(obj/item/I, mob/living/user) - if(I.get_temperature()) - fire_act(I.get_temperature()) - return - - return ..() - -/obj/structure/mineral_door/paperframe - name = "paper frame door" - icon_state = "paperframe" - openSound = 'sound/effects/doorcreaky.ogg' - closeSound = 'sound/effects/doorcreaky.ogg' - sheetType = /obj/item/stack/sheet/paperframes - sheetAmount = 3 - resistance_flags = FLAMMABLE - max_integrity = 20 - -/obj/structure/mineral_door/paperframe/Initialize() - . = ..() - queue_smooth_neighbors(src) - -/obj/structure/mineral_door/paperframe/examine(mob/user) - . = ..() - if(obj_integrity < max_integrity) - . += "It looks a bit damaged, you may be able to fix it with some paper." - -/obj/structure/mineral_door/paperframe/pickaxe_door(mob/living/user, obj/item/I) - return - -/obj/structure/mineral_door/paperframe/welder_act(mob/living/user, obj/item/I) - return - -/obj/structure/mineral_door/paperframe/crowbar_act(mob/living/user, obj/item/I) - return crowbar_door(user, I) - -/obj/structure/mineral_door/paperframe/attackby(obj/item/I, mob/living/user) - if(I.get_temperature()) //BURN IT ALL DOWN JIM - fire_act(I.get_temperature()) - return - - if((user.a_intent != INTENT_HARM) && istype(I, /obj/item/paper) && (obj_integrity < max_integrity)) - user.visible_message("[user] starts to patch the holes in [src].", "You start patching some of the holes in [src]!") - if(do_after(user, 20, TRUE, src)) - obj_integrity = min(obj_integrity+4,max_integrity) - qdel(I) - user.visible_message("[user] patches some of the holes in [src].", "You patch some of the holes in [src]!") - return TRUE - - return ..() - -/obj/structure/mineral_door/paperframe/ComponentInitialize() - return - -/obj/structure/mineral_door/paperframe/Destroy() - queue_smooth_neighbors(src) - return ..() +//NOT using the existing /obj/machinery/door type, since that has some complications on its own, mainly based on its +//machineryness + +/obj/structure/mineral_door + name = "metal door" + density = TRUE + anchored = TRUE + opacity = TRUE + layer = CLOSED_DOOR_LAYER + + icon = 'icons/obj/doors/mineral_doors.dmi' + icon_state = "metal" + max_integrity = 200 + armor = list("melee" = 10, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 50, "acid" = 50) + CanAtmosPass = ATMOS_PASS_DENSITY + flags_1 = RAD_PROTECT_CONTENTS_1 | RAD_NO_CONTAMINATE_1 + rad_insulation = RAD_MEDIUM_INSULATION + + var/door_opened = FALSE //if it's open or not. + var/isSwitchingStates = FALSE //don't try to change stats if we're already opening + + var/close_delay = -1 //-1 if does not auto close. + var/openSound = 'sound/effects/stonedoor_openclose.ogg' + var/closeSound = 'sound/effects/stonedoor_openclose.ogg' + + var/sheetType = /obj/item/stack/sheet/metal //what we're made of + var/sheetAmount = 7 //how much we drop when deconstructed + +/obj/structure/mineral_door/Initialize() + . = ..() + + air_update_turf(TRUE) + +/obj/structure/mineral_door/Move() + var/turf/T = loc + . = ..() + move_update_air(T) + +/obj/structure/mineral_door/Bumped(atom/movable/AM) + ..() + if(!door_opened) + return TryToSwitchState(AM) + +/obj/structure/mineral_door/attack_ai(mob/user) //those aren't machinery, they're just big fucking slabs of a mineral + if(isAI(user)) //so the AI can't open it + return + else if(iscyborg(user)) //but cyborgs can + if(get_dist(user,src) <= 1) //not remotely though + return TryToSwitchState(user) + +/obj/structure/mineral_door/attack_paw(mob/user) + return attack_hand(user) + +/obj/structure/mineral_door/attack_hand(mob/user) + . = ..() + if(.) + return + return TryToSwitchState(user) + +/obj/structure/mineral_door/CanAllowThrough(atom/movable/mover, turf/target) + . = ..() + if(istype(mover, /obj/effect/beam)) + return !opacity + +/obj/structure/mineral_door/proc/TryToSwitchState(atom/user) + if(isSwitchingStates || !anchored) + return + if(isliving(user)) + var/mob/living/M = user + if(world.time - M.last_bumped <= 60) + return //NOTE do we really need that? + if(M.client) + if(iscarbon(M)) + var/mob/living/carbon/C = M + if(!C.handcuffed) + SwitchState() + else + SwitchState() + else if(ismecha(user)) + SwitchState() + +/obj/structure/mineral_door/proc/SwitchState() + if(door_opened) + Close() + else + Open() + +/obj/structure/mineral_door/proc/Open() + isSwitchingStates = TRUE + playsound(src, openSound, 100, TRUE) + set_opacity(FALSE) + flick("[initial(icon_state)]opening",src) + sleep(10) + density = FALSE + door_opened = TRUE + layer = OPEN_DOOR_LAYER + air_update_turf(1) + update_icon() + isSwitchingStates = FALSE + + if(close_delay != -1) + addtimer(CALLBACK(src, .proc/Close), close_delay) + +/obj/structure/mineral_door/proc/Close() + if(isSwitchingStates || !door_opened) + return + var/turf/T = get_turf(src) + for(var/mob/living/L in T) + return + isSwitchingStates = TRUE + playsound(src, closeSound, 100, TRUE) + flick("[initial(icon_state)]closing",src) + sleep(10) + density = TRUE + set_opacity(TRUE) + door_opened = FALSE + layer = initial(layer) + air_update_turf(1) + update_icon() + isSwitchingStates = FALSE + +/obj/structure/mineral_door/update_icon_state() + icon_state = "[initial(icon_state)][door_opened ? "open":""]" + +/obj/structure/mineral_door/attackby(obj/item/I, mob/user) + if(pickaxe_door(user, I)) + return + else if(user.a_intent != INTENT_HARM) + return attack_hand(user) + else + return ..() + +/obj/structure/mineral_door/setAnchored(anchorvalue) //called in default_unfasten_wrench() chain + . = ..() + set_opacity(anchored ? !door_opened : FALSE) + air_update_turf(TRUE) + +/obj/structure/mineral_door/wrench_act(mob/living/user, obj/item/I) + ..() + default_unfasten_wrench(user, I, 40) + return TRUE + + +/////////////////////// TOOL OVERRIDES /////////////////////// + + +/obj/structure/mineral_door/proc/pickaxe_door(mob/living/user, obj/item/I) //override if the door isn't supposed to be a minable mineral. + if(!istype(user)) + return + if(I.tool_behaviour != TOOL_MINING) + return + . = TRUE + to_chat(user, "You start digging [src]...") + if(I.use_tool(src, user, 40, volume=50)) + to_chat(user, "You finish digging.") + deconstruct(TRUE) + +/obj/structure/mineral_door/welder_act(mob/living/user, obj/item/I) //override if the door is supposed to be flammable. + ..() + . = TRUE + if(anchored) + to_chat(user, "[src] is still firmly secured to the ground!") + return + + user.visible_message("[user] starts to weld apart [src]!", "You start welding apart [src].") + if(!I.use_tool(src, user, 60, 5, 50)) + to_chat(user, "You failed to weld apart [src]!") + return + + user.visible_message("[user] welded [src] into pieces!", "You welded apart [src]!") + deconstruct(TRUE) + +/obj/structure/mineral_door/proc/crowbar_door(mob/living/user, obj/item/I) //if the door is flammable, call this in crowbar_act() so we can still decon it + . = TRUE + if(anchored) + to_chat(user, "[src] is still firmly secured to the ground!") + return + + user.visible_message("[user] starts to pry apart [src]!", "You start prying apart [src].") + if(!I.use_tool(src, user, 60, volume = 50)) + to_chat(user, "You failed to pry apart [src]!") + return + + user.visible_message("[user] pried [src] into pieces!", "You pried apart [src]!") + deconstruct(TRUE) + + +/////////////////////// END TOOL OVERRIDES /////////////////////// + + +/obj/structure/mineral_door/deconstruct(disassembled = TRUE) + var/turf/T = get_turf(src) + if(disassembled) + new sheetType(T, sheetAmount) + else + new sheetType(T, max(sheetAmount - 2, 1)) + qdel(src) + + +/obj/structure/mineral_door/iron + name = "iron door" + max_integrity = 300 + +/obj/structure/mineral_door/silver + name = "silver door" + icon_state = "silver" + sheetType = /obj/item/stack/sheet/mineral/silver + max_integrity = 300 + rad_insulation = RAD_HEAVY_INSULATION + +/obj/structure/mineral_door/gold + name = "gold door" + icon_state = "gold" + sheetType = /obj/item/stack/sheet/mineral/gold + rad_insulation = RAD_HEAVY_INSULATION + +/obj/structure/mineral_door/uranium + name = "uranium door" + icon_state = "uranium" + sheetType = /obj/item/stack/sheet/mineral/uranium + max_integrity = 300 + light_range = 2 + +/obj/structure/mineral_door/uranium/ComponentInitialize() + return + +/obj/structure/mineral_door/sandstone + name = "sandstone door" + icon_state = "sandstone" + sheetType = /obj/item/stack/sheet/mineral/sandstone + max_integrity = 100 + +/obj/structure/mineral_door/transparent + opacity = FALSE + rad_insulation = RAD_VERY_LIGHT_INSULATION + +/obj/structure/mineral_door/transparent/Close() + ..() + set_opacity(FALSE) + +/obj/structure/mineral_door/transparent/plasma + name = "plasma door" + icon_state = "plasma" + sheetType = /obj/item/stack/sheet/mineral/plasma + +/obj/structure/mineral_door/transparent/plasma/ComponentInitialize() + return + +/obj/structure/mineral_door/transparent/plasma/welder_act(mob/living/user, obj/item/I) + return + +/obj/structure/mineral_door/transparent/plasma/attackby(obj/item/W, mob/user, params) + if(W.get_temperature()) + var/turf/T = get_turf(src) + message_admins("Plasma mineral door ignited by [ADMIN_LOOKUPFLW(user)] in [ADMIN_VERBOSEJMP(T)]") + log_game("Plasma mineral door ignited by [key_name(user)] in [AREACOORD(T)]") + TemperatureAct() + else + return ..() + +/obj/structure/mineral_door/transparent/plasma/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) + if(exposed_temperature > 300) + TemperatureAct() + +/obj/structure/mineral_door/transparent/plasma/proc/TemperatureAct() + atmos_spawn_air("plasma=500;TEMP=1000") + deconstruct(FALSE) + +/obj/structure/mineral_door/transparent/diamond + name = "diamond door" + icon_state = "diamond" + sheetType = /obj/item/stack/sheet/mineral/diamond + max_integrity = 1000 + rad_insulation = RAD_EXTREME_INSULATION + +/obj/structure/mineral_door/wood + name = "wood door" + icon_state = "wood" + openSound = 'sound/effects/doorcreaky.ogg' + closeSound = 'sound/effects/doorcreaky.ogg' + sheetType = /obj/item/stack/sheet/mineral/wood + resistance_flags = FLAMMABLE + max_integrity = 200 + rad_insulation = RAD_VERY_LIGHT_INSULATION + +/obj/structure/mineral_door/wood/pickaxe_door(mob/living/user, obj/item/I) + return + +/obj/structure/mineral_door/wood/welder_act(mob/living/user, obj/item/I) + return + +/obj/structure/mineral_door/wood/crowbar_act(mob/living/user, obj/item/I) + return crowbar_door(user, I) + +/obj/structure/mineral_door/wood/attackby(obj/item/I, mob/living/user) + if(I.get_temperature()) + fire_act(I.get_temperature()) + return + + return ..() + +/obj/structure/mineral_door/paperframe + name = "paper frame door" + icon_state = "paperframe" + openSound = 'sound/effects/doorcreaky.ogg' + closeSound = 'sound/effects/doorcreaky.ogg' + sheetType = /obj/item/stack/sheet/paperframes + sheetAmount = 3 + resistance_flags = FLAMMABLE + max_integrity = 20 + +/obj/structure/mineral_door/paperframe/Initialize() + . = ..() + queue_smooth_neighbors(src) + +/obj/structure/mineral_door/paperframe/examine(mob/user) + . = ..() + if(obj_integrity < max_integrity) + . += "It looks a bit damaged, you may be able to fix it with some paper." + +/obj/structure/mineral_door/paperframe/pickaxe_door(mob/living/user, obj/item/I) + return + +/obj/structure/mineral_door/paperframe/welder_act(mob/living/user, obj/item/I) + return + +/obj/structure/mineral_door/paperframe/crowbar_act(mob/living/user, obj/item/I) + return crowbar_door(user, I) + +/obj/structure/mineral_door/paperframe/attackby(obj/item/I, mob/living/user) + if(I.get_temperature()) //BURN IT ALL DOWN JIM + fire_act(I.get_temperature()) + return + + if((user.a_intent != INTENT_HARM) && istype(I, /obj/item/paper) && (obj_integrity < max_integrity)) + user.visible_message("[user] starts to patch the holes in [src].", "You start patching some of the holes in [src]!") + if(do_after(user, 20, TRUE, src)) + obj_integrity = min(obj_integrity+4,max_integrity) + qdel(I) + user.visible_message("[user] patches some of the holes in [src].", "You patch some of the holes in [src]!") + return TRUE + + return ..() + +/obj/structure/mineral_door/paperframe/ComponentInitialize() + return + +/obj/structure/mineral_door/paperframe/Destroy() + queue_smooth_neighbors(src) + return ..() diff --git a/code/game/objects/structures/mirror.dm b/code/game/objects/structures/mirror.dm index cda38c08e7bc..7a14733b4454 100644 --- a/code/game/objects/structures/mirror.dm +++ b/code/game/objects/structures/mirror.dm @@ -1,247 +1,247 @@ -//wip wip wup -/obj/structure/mirror - name = "mirror" - desc = "Mirror mirror on the wall, who's the most robust of them all?" - icon = 'icons/obj/watercloset.dmi' - icon_state = "mirror" - density = FALSE - anchored = TRUE - max_integrity = 200 - integrity_failure = 0.5 - -/obj/structure/mirror/Initialize(mapload) - . = ..() - if(icon_state == "mirror_broke" && !broken) - obj_break(null, mapload) - -/obj/structure/mirror/attack_hand(mob/user) - . = ..() - if(.) - return - if(broken || !Adjacent(user)) - return - - if(ishuman(user)) - var/mob/living/carbon/human/H = user - - //see code/modules/mob/dead/new_player/preferences.dm at approx line 545 for comments! - //this is largely copypasted from there. - - //handle facial hair (if necessary) - if(H.gender != FEMALE) - var/new_style = input(user, "Select a facial hairstyle", "Grooming") as null|anything in GLOB.facial_hairstyles_list - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return //no tele-grooming - if(new_style) - H.facial_hairstyle = new_style - else - H.facial_hairstyle = "Shaved" - - //handle normal hair - var/new_style = input(user, "Select a hairstyle", "Grooming") as null|anything in GLOB.hairstyles_list - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return //no tele-grooming - if(HAS_TRAIT(H, TRAIT_BALD)) - to_chat(H, "If only growing back hair were that easy for you...") - if(new_style) - H.hairstyle = new_style - - H.update_hair() - -/obj/structure/mirror/examine_status(mob/user) - if(broken) - return list()// no message spam - return ..() - -/obj/structure/mirror/obj_break(damage_flag, mapload) - if(!broken && !(flags_1 & NODECONSTRUCT_1)) - icon_state = "mirror_broke" - if(!mapload) - playsound(src, "shatter", 70, TRUE) - if(desc == initial(desc)) - desc = "Oh no, seven years of bad luck!" - broken = TRUE - -/obj/structure/mirror/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - if(!disassembled) - new /obj/item/shard( src.loc ) - qdel(src) - -/obj/structure/mirror/welder_act(mob/living/user, obj/item/I) - ..() - if(user.a_intent == INTENT_HARM) - return FALSE - - if(!broken) - return TRUE - - if(!I.tool_start_check(user, amount=0)) - return TRUE - - to_chat(user, "You begin repairing [src]...") - if(I.use_tool(src, user, 10, volume=50)) - to_chat(user, "You repair [src].") - broken = 0 - icon_state = initial(icon_state) - desc = initial(desc) - - return TRUE - -/obj/structure/mirror/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) - switch(damage_type) - if(BRUTE) - playsound(src, 'sound/effects/hit_on_shattered_glass.ogg', 70, TRUE) - if(BURN) - playsound(src, 'sound/effects/hit_on_shattered_glass.ogg', 70, TRUE) - - -/obj/structure/mirror/magic - name = "magic mirror" - desc = "Turn and face the strange... face." - icon_state = "magic_mirror" - var/list/choosable_races = list() - -/obj/structure/mirror/magic/New() - if(!choosable_races.len) - for(var/speciestype in subtypesof(/datum/species)) - var/datum/species/S = speciestype - if(initial(S.changesource_flags) & MIRROR_MAGIC) - choosable_races += initial(S.id) - choosable_races = sortList(choosable_races) - ..() - -/obj/structure/mirror/magic/lesser/New() - choosable_races = GLOB.roundstart_races.Copy() - ..() - -/obj/structure/mirror/magic/badmin/New() - for(var/speciestype in subtypesof(/datum/species)) - var/datum/species/S = speciestype - if(initial(S.changesource_flags) & MIRROR_BADMIN) - choosable_races += initial(S.id) - ..() - -/obj/structure/mirror/magic/attack_hand(mob/user) - . = ..() - if(.) - return - if(!ishuman(user)) - return - - var/mob/living/carbon/human/H = user - - var/choice = input(user, "Something to change?", "Magical Grooming") as null|anything in list("name", "race", "gender", "hair", "eyes") - - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - - switch(choice) - if("name") - var/newname = sanitize_name(reject_bad_text(stripped_input(H, "Who are we again?", "Name change", H.name, MAX_NAME_LEN))) - if(!newname) - return - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - H.real_name = newname - H.name = newname - if(H.dna) - H.dna.real_name = newname - if(H.mind) - H.mind.name = newname - - if("race") - var/newrace - var/racechoice = input(H, "What are we again?", "Race change") as null|anything in choosable_races - newrace = GLOB.species_list[racechoice] - - if(!newrace) - return - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - H.set_species(newrace, icon_update=0) - - if(H.dna.species.use_skintones) - var/new_s_tone = input(user, "Choose your skin tone:", "Race change") as null|anything in GLOB.skin_tones - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - - if(new_s_tone) - H.skin_tone = new_s_tone - H.dna.update_ui_block(DNA_SKIN_TONE_BLOCK) - - if(MUTCOLORS in H.dna.species.species_traits) - var/new_mutantcolor = input(user, "Choose your skin color:", "Race change","#"+H.dna.features["mcolor"]) as color|null - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - if(new_mutantcolor) - var/temp_hsv = RGBtoHSV(new_mutantcolor) - - if(ReadHSV(temp_hsv)[3] >= ReadHSV("#7F7F7F")[3]) // mutantcolors must be bright - H.dna.features["mcolor"] = sanitize_hexcolor(new_mutantcolor) - - else - to_chat(H, "Invalid color. Your color is not bright enough.") - - H.update_body() - H.update_hair() - H.update_body_parts() - H.update_mutations_overlay() // no hulk lizard - - if("gender") - if(!(H.gender in list("male", "female"))) //blame the patriarchy - return - if(H.gender == "male") - if(alert(H, "Become a Witch?", "Confirmation", "Yes", "No") == "Yes") - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - H.gender = "female" - to_chat(H, "Man, you feel like a woman!") - else - return - - else - if(alert(H, "Become a Warlock?", "Confirmation", "Yes", "No") == "Yes") - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - H.gender = "male" - to_chat(H, "Whoa man, you feel like a man!") - else - return - H.dna.update_ui_block(DNA_GENDER_BLOCK) - H.update_body() - H.update_mutations_overlay() //(hulk male/female) - - if("hair") - var/hairchoice = alert(H, "Hairstyle or hair color?", "Change Hair", "Style", "Color") - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - if(hairchoice == "Style") //So you just want to use a mirror then? - ..() - else - var/new_hair_color = input(H, "Choose your hair color", "Hair Color","#"+H.hair_color) as color|null - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - if(new_hair_color) - H.hair_color = sanitize_hexcolor(new_hair_color) - H.dna.update_ui_block(DNA_HAIR_COLOR_BLOCK) - if(H.gender == "male") - var/new_face_color = input(H, "Choose your facial hair color", "Hair Color","#"+H.facial_hair_color) as color|null - if(new_face_color) - H.facial_hair_color = sanitize_hexcolor(new_face_color) - H.dna.update_ui_block(DNA_FACIAL_HAIR_COLOR_BLOCK) - H.update_hair() - - if(BODY_ZONE_PRECISE_EYES) - var/new_eye_color = input(H, "Choose your eye color", "Eye Color","#"+H.eye_color) as color|null - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - if(new_eye_color) - H.eye_color = sanitize_hexcolor(new_eye_color) - H.dna.update_ui_block(DNA_EYE_COLOR_BLOCK) - H.update_body() - if(choice) - curse(user) - -/obj/structure/mirror/magic/proc/curse(mob/living/user) - return +//wip wip wup +/obj/structure/mirror + name = "mirror" + desc = "Mirror mirror on the wall, who's the most robust of them all?" + icon = 'icons/obj/watercloset.dmi' + icon_state = "mirror" + density = FALSE + anchored = TRUE + max_integrity = 200 + integrity_failure = 0.5 + +/obj/structure/mirror/Initialize(mapload) + . = ..() + if(icon_state == "mirror_broke" && !broken) + obj_break(null, mapload) + +/obj/structure/mirror/attack_hand(mob/user) + . = ..() + if(.) + return + if(broken || !Adjacent(user)) + return + + if(ishuman(user)) + var/mob/living/carbon/human/H = user + + //see code/modules/mob/dead/new_player/preferences.dm at approx line 545 for comments! + //this is largely copypasted from there. + + //handle facial hair (if necessary) + if(H.gender != FEMALE) + var/new_style = input(user, "Select a facial hairstyle", "Grooming") as null|anything in GLOB.facial_hairstyles_list + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return //no tele-grooming + if(new_style) + H.facial_hairstyle = new_style + else + H.facial_hairstyle = "Shaved" + + //handle normal hair + var/new_style = input(user, "Select a hairstyle", "Grooming") as null|anything in GLOB.hairstyles_list + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return //no tele-grooming + if(HAS_TRAIT(H, TRAIT_BALD)) + to_chat(H, "If only growing back hair were that easy for you...") + if(new_style) + H.hairstyle = new_style + + H.update_hair() + +/obj/structure/mirror/examine_status(mob/user) + if(broken) + return list()// no message spam + return ..() + +/obj/structure/mirror/obj_break(damage_flag, mapload) + if(!broken && !(flags_1 & NODECONSTRUCT_1)) + icon_state = "mirror_broke" + if(!mapload) + playsound(src, "shatter", 70, TRUE) + if(desc == initial(desc)) + desc = "Oh no, seven years of bad luck!" + broken = TRUE + +/obj/structure/mirror/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + if(!disassembled) + new /obj/item/shard( src.loc ) + qdel(src) + +/obj/structure/mirror/welder_act(mob/living/user, obj/item/I) + ..() + if(user.a_intent == INTENT_HARM) + return FALSE + + if(!broken) + return TRUE + + if(!I.tool_start_check(user, amount=0)) + return TRUE + + to_chat(user, "You begin repairing [src]...") + if(I.use_tool(src, user, 10, volume=50)) + to_chat(user, "You repair [src].") + broken = 0 + icon_state = initial(icon_state) + desc = initial(desc) + + return TRUE + +/obj/structure/mirror/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) + switch(damage_type) + if(BRUTE) + playsound(src, 'sound/effects/hit_on_shattered_glass.ogg', 70, TRUE) + if(BURN) + playsound(src, 'sound/effects/hit_on_shattered_glass.ogg', 70, TRUE) + + +/obj/structure/mirror/magic + name = "magic mirror" + desc = "Turn and face the strange... face." + icon_state = "magic_mirror" + var/list/choosable_races = list() + +/obj/structure/mirror/magic/New() + if(!choosable_races.len) + for(var/speciestype in subtypesof(/datum/species)) + var/datum/species/S = speciestype + if(initial(S.changesource_flags) & MIRROR_MAGIC) + choosable_races += initial(S.id) + choosable_races = sortList(choosable_races) + ..() + +/obj/structure/mirror/magic/lesser/New() + choosable_races = GLOB.roundstart_races.Copy() + ..() + +/obj/structure/mirror/magic/badmin/New() + for(var/speciestype in subtypesof(/datum/species)) + var/datum/species/S = speciestype + if(initial(S.changesource_flags) & MIRROR_BADMIN) + choosable_races += initial(S.id) + ..() + +/obj/structure/mirror/magic/attack_hand(mob/user) + . = ..() + if(.) + return + if(!ishuman(user)) + return + + var/mob/living/carbon/human/H = user + + var/choice = input(user, "Something to change?", "Magical Grooming") as null|anything in list("name", "race", "gender", "hair", "eyes") + + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + + switch(choice) + if("name") + var/newname = sanitize_name(reject_bad_text(stripped_input(H, "Who are we again?", "Name change", H.name, MAX_NAME_LEN))) + if(!newname) + return + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + H.real_name = newname + H.name = newname + if(H.dna) + H.dna.real_name = newname + if(H.mind) + H.mind.name = newname + + if("race") + var/newrace + var/racechoice = input(H, "What are we again?", "Race change") as null|anything in choosable_races + newrace = GLOB.species_list[racechoice] + + if(!newrace) + return + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + H.set_species(newrace, icon_update=0) + + if(H.dna.species.use_skintones) + var/new_s_tone = input(user, "Choose your skin tone:", "Race change") as null|anything in GLOB.skin_tones + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + + if(new_s_tone) + H.skin_tone = new_s_tone + H.dna.update_ui_block(DNA_SKIN_TONE_BLOCK) + + if(MUTCOLORS in H.dna.species.species_traits) + var/new_mutantcolor = input(user, "Choose your skin color:", "Race change","#"+H.dna.features["mcolor"]) as color|null + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + if(new_mutantcolor) + var/temp_hsv = RGBtoHSV(new_mutantcolor) + + if(ReadHSV(temp_hsv)[3] >= ReadHSV("#7F7F7F")[3]) // mutantcolors must be bright + H.dna.features["mcolor"] = sanitize_hexcolor(new_mutantcolor) + + else + to_chat(H, "Invalid color. Your color is not bright enough.") + + H.update_body() + H.update_hair() + H.update_body_parts() + H.update_mutations_overlay() // no hulk lizard + + if("gender") + if(!(H.gender in list("male", "female"))) //blame the patriarchy + return + if(H.gender == "male") + if(alert(H, "Become a Witch?", "Confirmation", "Yes", "No") == "Yes") + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + H.gender = "female" + to_chat(H, "Man, you feel like a woman!") + else + return + + else + if(alert(H, "Become a Warlock?", "Confirmation", "Yes", "No") == "Yes") + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + H.gender = "male" + to_chat(H, "Whoa man, you feel like a man!") + else + return + H.dna.update_ui_block(DNA_GENDER_BLOCK) + H.update_body() + H.update_mutations_overlay() //(hulk male/female) + + if("hair") + var/hairchoice = alert(H, "Hairstyle or hair color?", "Change Hair", "Style", "Color") + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + if(hairchoice == "Style") //So you just want to use a mirror then? + ..() + else + var/new_hair_color = input(H, "Choose your hair color", "Hair Color","#"+H.hair_color) as color|null + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + if(new_hair_color) + H.hair_color = sanitize_hexcolor(new_hair_color) + H.dna.update_ui_block(DNA_HAIR_COLOR_BLOCK) + if(H.gender == "male") + var/new_face_color = input(H, "Choose your facial hair color", "Hair Color","#"+H.facial_hair_color) as color|null + if(new_face_color) + H.facial_hair_color = sanitize_hexcolor(new_face_color) + H.dna.update_ui_block(DNA_FACIAL_HAIR_COLOR_BLOCK) + H.update_hair() + + if(BODY_ZONE_PRECISE_EYES) + var/new_eye_color = input(H, "Choose your eye color", "Eye Color","#"+H.eye_color) as color|null + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + if(new_eye_color) + H.eye_color = sanitize_hexcolor(new_eye_color) + H.dna.update_ui_block(DNA_EYE_COLOR_BLOCK) + H.update_body() + if(choice) + curse(user) + +/obj/structure/mirror/magic/proc/curse(mob/living/user) + return diff --git a/code/game/objects/structures/mop_bucket.dm b/code/game/objects/structures/mop_bucket.dm index a5f84b9bc142..457bd0faf4d0 100644 --- a/code/game/objects/structures/mop_bucket.dm +++ b/code/game/objects/structures/mop_bucket.dm @@ -1,30 +1,30 @@ -/obj/structure/mopbucket - name = "mop bucket" - desc = "Fill it with water, but don't forget a mop!" - icon = 'icons/obj/janitor.dmi' - icon_state = "mopbucket" - density = TRUE - var/amount_per_transfer_from_this = 5 //shit I dunno, adding this so syringes stop runtime erroring. --NeoFite - - -/obj/structure/mopbucket/Initialize() - . = ..() - create_reagents(100, OPENCONTAINER) - -/obj/structure/mopbucket/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/mop)) - if(reagents.total_volume < 1) - to_chat(user, "[src] is out of water!") - else - reagents.trans_to(I, 5, transfered_by = user) - to_chat(user, "You wet [I] in [src].") - playsound(loc, 'sound/effects/slosh.ogg', 25, TRUE) - update_icon() - else - . = ..() - update_icon() - -/obj/structure/mopbucket/update_overlays() - . = ..() - if(reagents.total_volume > 0) - . += "mopbucket_water" +/obj/structure/mopbucket + name = "mop bucket" + desc = "Fill it with water, but don't forget a mop!" + icon = 'icons/obj/janitor.dmi' + icon_state = "mopbucket" + density = TRUE + var/amount_per_transfer_from_this = 5 //shit I dunno, adding this so syringes stop runtime erroring. --NeoFite + + +/obj/structure/mopbucket/Initialize() + . = ..() + create_reagents(100, OPENCONTAINER) + +/obj/structure/mopbucket/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/mop)) + if(reagents.total_volume < 1) + to_chat(user, "[src] is out of water!") + else + reagents.trans_to(I, 5, transfered_by = user) + to_chat(user, "You wet [I] in [src].") + playsound(loc, 'sound/effects/slosh.ogg', 25, TRUE) + update_icon() + else + . = ..() + update_icon() + +/obj/structure/mopbucket/update_overlays() + . = ..() + if(reagents.total_volume > 0) + . += "mopbucket_water" diff --git a/code/game/objects/structures/morgue.dm b/code/game/objects/structures/morgue.dm index 4c2e3fd296d3..2243bfced175 100644 --- a/code/game/objects/structures/morgue.dm +++ b/code/game/objects/structures/morgue.dm @@ -1,392 +1,392 @@ -/* Morgue stuff - * Contains: - * Morgue - * Morgue tray - * Crematorium - * Creamatorium - * Crematorium tray - * Crematorium button - */ - -/* - * Bodycontainer - * Parent class for morgue and crematorium - * For overriding only - */ -GLOBAL_LIST_EMPTY(bodycontainers) //Let them act as spawnpoints for revenants and other ghosties. - -/obj/structure/bodycontainer - icon = 'icons/obj/stationobjs.dmi' - icon_state = "morgue1" - density = TRUE - anchored = TRUE - max_integrity = 400 - - var/obj/structure/tray/connected = null - var/locked = FALSE - dir = SOUTH - var/message_cooldown - var/breakout_time = 600 - -/obj/structure/bodycontainer/Initialize() - . = ..() - GLOB.bodycontainers += src - recursive_organ_check(src) - -/obj/structure/bodycontainer/Destroy() - GLOB.bodycontainers -= src - open() - if(connected) - qdel(connected) - connected = null - return ..() - -/obj/structure/bodycontainer/on_log(login) - ..() - update_icon() - -/obj/structure/bodycontainer/update_icon() - return - -/obj/structure/bodycontainer/relaymove(mob/user) - if(user.stat || !isturf(loc)) - return - if(locked) - if(message_cooldown <= world.time) - message_cooldown = world.time + 50 - to_chat(user, "[src]'s door won't budge!") - return - open() - -/obj/structure/bodycontainer/attack_paw(mob/user) - return attack_hand(user) - -/obj/structure/bodycontainer/attack_hand(mob/user) - . = ..() - if(.) - return - if(locked) - to_chat(user, "It's locked.") - return - if(!connected) - to_chat(user, "That doesn't appear to have a tray.") - return - if(connected.loc == src) - open() - else - close() - add_fingerprint(user) - -/obj/structure/bodycontainer/attack_robot(mob/user) - if(!user.Adjacent(src)) - return - return attack_hand(user) - -/obj/structure/bodycontainer/attackby(obj/P, mob/user, params) - add_fingerprint(user) - if(istype(P, /obj/item/pen)) - if(!user.is_literate()) - to_chat(user, "You scribble illegibly on the side of [src]!") - return - var/t = stripped_input(user, "What would you like the label to be?", text("[]", name), null) - if (user.get_active_held_item() != P) - return - if(!user.canUseTopic(src, BE_CLOSE)) - return - if (t) - name = text("[]- '[]'", initial(name), t) - else - name = initial(name) - else - return ..() - -/obj/structure/bodycontainer/deconstruct(disassembled = TRUE) - new /obj/item/stack/sheet/metal (loc, 5) - recursive_organ_check(src) - qdel(src) - -/obj/structure/bodycontainer/container_resist(mob/living/user) - if(!locked) - open() - return - user.changeNext_move(CLICK_CD_BREAKOUT) - user.last_special = world.time + CLICK_CD_BREAKOUT - user.visible_message(null, \ - "You lean on the back of [src] and start pushing the tray open... (this will take about [DisplayTimeText(breakout_time)].)", \ - "You hear a metallic creaking from [src].") - if(do_after(user,(breakout_time), target = src)) - if(!user || user.stat != CONSCIOUS || user.loc != src ) - return - user.visible_message("[user] successfully broke out of [src]!", \ - "You successfully break out of [src]!") - open() - -/obj/structure/bodycontainer/proc/open() - recursive_organ_check(src) - playsound(src.loc, 'sound/items/deconstruct.ogg', 50, TRUE) - playsound(src, 'sound/effects/roll.ogg', 5, TRUE) - var/turf/T = get_step(src, dir) - connected.setDir(dir) - for(var/atom/movable/AM in src) - AM.forceMove(T) - update_icon() - -/obj/structure/bodycontainer/proc/close() - playsound(src, 'sound/effects/roll.ogg', 5, TRUE) - playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE) - for(var/atom/movable/AM in connected.loc) - if(!AM.anchored || AM == connected) - if(ismob(AM) && !isliving(AM)) - continue - AM.forceMove(src) - recursive_organ_check(src) - update_icon() - -/obj/structure/bodycontainer/get_remote_view_fullscreens(mob/user) - if(user.stat == DEAD || !(user.sight & (SEEOBJS|SEEMOBS))) - user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 2) -/* - * Morgue - */ -/obj/structure/bodycontainer/morgue - name = "morgue" - desc = "Used to keep bodies in until someone fetches them. Now includes a high-tech alert system." - icon_state = "morgue1" - dir = EAST - var/beeper = TRUE - var/beep_cooldown = 50 - var/next_beep = 0 - -/obj/structure/bodycontainer/morgue/Initialize() - . = ..() - connected = new/obj/structure/tray/m_tray(src) - connected.connected = src - -/obj/structure/bodycontainer/morgue/examine(mob/user) - . = ..() - . += "The speaker is [beeper ? "enabled" : "disabled"]. Alt-click to toggle it." - -/obj/structure/bodycontainer/morgue/AltClick(mob/user) - ..() - if(!user.canUseTopic(src, !issilicon(user))) - return - beeper = !beeper - to_chat(user, "You turn the speaker function [beeper ? "on" : "off"].") - -/obj/structure/bodycontainer/morgue/update_icon() - if (!connected || connected.loc != src) // Open or tray is gone. - icon_state = "morgue0" - else - if(contents.len == 1) // Empty - icon_state = "morgue1" - else - icon_state = "morgue2" // Dead, brainded mob. - var/list/compiled = GetAllContents(/mob/living) // Search for mobs in all contents. - if(!length(compiled)) // No mobs? - icon_state = "morgue3" - return - - for(var/mob/living/M in compiled) - var/mob/living/mob_occupant = get_mob_or_brainmob(M) - if(mob_occupant.client && !mob_occupant.suiciding && !(HAS_TRAIT(mob_occupant, TRAIT_BADDNA)) && !mob_occupant.hellbound) - icon_state = "morgue4" // Revivable - if(mob_occupant.stat == DEAD && beeper) - if(world.time > next_beep) - playsound(src, 'sound/weapons/gun/general/empty_alarm.ogg', 50, FALSE) //Revive them you blind fucks - next_beep = world.time + beep_cooldown - break - - -/obj/item/paper/guides/jobs/medical/morgue - name = "morgue memo" - info = "Since this station's medbay never seems to fail to be staffed by the mindless monkeys meant for genetics experiments, I'm leaving a reminder here for anyone handling the pile of cadavers the quacks are sure to leave.

                        Red lights mean there's a plain ol' dead body inside.

                        Yellow lights mean there's non-body objects inside.
                        Probably stuff pried off a corpse someone grabbed, or if you're lucky it's stashed booze.

                        Green lights mean the morgue system detects the body may be able to be brought back to life.

                        I don't know how that works, but keep it away from the kitchen and go yell at the geneticists.

                        - CentCom medical inspector" - -/* - * Crematorium - */ -GLOBAL_LIST_EMPTY(crematoriums) -/obj/structure/bodycontainer/crematorium - name = "crematorium" - desc = "A human incinerator. Works well on barbecue nights." - icon_state = "crema1" - dir = SOUTH - var/id = 1 - -/obj/structure/bodycontainer/crematorium/attack_robot(mob/user) //Borgs can't use crematoriums without help - to_chat(user, "[src] is locked against you.") - return - -/obj/structure/bodycontainer/crematorium/Destroy() - GLOB.crematoriums.Remove(src) - return ..() - -/obj/structure/bodycontainer/crematorium/New() - GLOB.crematoriums.Add(src) - ..() - -/obj/structure/bodycontainer/crematorium/Initialize() - . = ..() - connected = new /obj/structure/tray/c_tray(src) - connected.connected = src - -/obj/structure/bodycontainer/crematorium/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) - id = "[idnum][id]" - -/obj/structure/bodycontainer/crematorium/update_icon() - if(!connected || connected.loc != src) - icon_state = "crema0" - else - - if(src.contents.len > 1) - src.icon_state = "crema2" - else - src.icon_state = "crema1" - - if(locked) - src.icon_state = "crema_active" - - return - -/obj/structure/bodycontainer/crematorium/proc/cremate(mob/user) - if(locked) - return //don't let you cremate something twice or w/e - // Make sure we don't delete the actual morgue and its tray - var/list/conts = GetAllContents() - src - connected - - if(!conts.len) - audible_message("You hear a hollow crackle.") - return - - else - audible_message("You hear a roar as the crematorium activates.") - - locked = TRUE - update_icon() - - for(var/mob/living/M in conts) - if (M.stat != DEAD) - M.emote("scream") - if(user) - log_combat(user, M, "cremated") - else - M.log_message("was cremated", LOG_ATTACK) - - M.death(1) - if(M) //some animals get automatically deleted on death. - M.ghostize() - qdel(M) - - for(var/obj/O in conts) //conts defined above, ignores crematorium and tray - qdel(O) - - if(!locate(/obj/effect/decal/cleanable/ash) in get_step(src, dir))//prevent pile-up - new/obj/effect/decal/cleanable/ash/crematorium(src) - - sleep(30) - - if(!QDELETED(src)) - locked = FALSE - update_icon() - playsound(src.loc, 'sound/machines/ding.ogg', 50, TRUE) //you horrible people - -/obj/structure/bodycontainer/crematorium/creamatorium - name = "creamatorium" - desc = "A human incinerator. Works well during ice cream socials." - -/obj/structure/bodycontainer/crematorium/creamatorium/cremate(mob/user) - var/list/icecreams = new() - for(var/i_scream in GetAllContents(/mob/living)) - var/obj/item/reagent_containers/food/snacks/icecream/IC = new() - IC.set_cone_type("waffle") - IC.add_mob_flavor(i_scream) - icecreams += IC - . = ..() - for(var/obj/IC in icecreams) - IC.forceMove(src) - -/* - * Generic Tray - * Parent class for morguetray and crematoriumtray - * For overriding only - */ -/obj/structure/tray - icon = 'icons/obj/stationobjs.dmi' - density = TRUE - var/obj/structure/bodycontainer/connected = null - anchored = TRUE - pass_flags = LETPASSTHROW - max_integrity = 350 - -/obj/structure/tray/Destroy() - if(connected) - connected.connected = null - connected.update_icon() - connected = null - return ..() - -/obj/structure/tray/deconstruct(disassembled = TRUE) - new /obj/item/stack/sheet/metal (loc, 2) - qdel(src) - -/obj/structure/tray/attack_paw(mob/user) - return attack_hand(user) - -/obj/structure/tray/attack_hand(mob/user) - . = ..() - if(.) - return - if (src.connected) - connected.close() - add_fingerprint(user) - else - to_chat(user, "That's not connected to anything!") - -/obj/structure/tray/MouseDrop_T(atom/movable/O as mob|obj, mob/user) - if(!ismovable(O) || O.anchored || !Adjacent(user) || !user.Adjacent(O) || O.loc == user) - return - if(!ismob(O)) - if(!istype(O, /obj/structure/closet/body_bag)) - return - else - var/mob/M = O - if(M.buckled) - return - if(!ismob(user) || user.incapacitated()) - return - if(isliving(user)) - var/mob/living/L = user - if(!(L.mobility_flags & MOBILITY_STAND)) - return - O.forceMove(src.loc) - if (user != O) - visible_message("[user] stuffs [O] into [src].") - return - -/* - * Crematorium tray - */ -/obj/structure/tray/c_tray - name = "crematorium tray" - desc = "Apply body before burning." - icon_state = "cremat" - -/* - * Morgue tray - */ -/obj/structure/tray/m_tray - name = "morgue tray" - desc = "Apply corpse before closing." - icon_state = "morguet" - -/obj/structure/tray/m_tray/CanAllowThrough(atom/movable/mover, turf/target) - . = ..() - if(istype(mover) && (mover.pass_flags & PASSTABLE)) - return TRUE - if(locate(/obj/structure/table) in get_turf(mover)) - return TRUE - -/obj/structure/tray/m_tray/CanAStarPass(ID, dir, caller) - . = !density - if(ismovable(caller)) - var/atom/movable/mover = caller - . = . || (mover.pass_flags & PASSTABLE) +/* Morgue stuff + * Contains: + * Morgue + * Morgue tray + * Crematorium + * Creamatorium + * Crematorium tray + * Crematorium button + */ + +/* + * Bodycontainer + * Parent class for morgue and crematorium + * For overriding only + */ +GLOBAL_LIST_EMPTY(bodycontainers) //Let them act as spawnpoints for revenants and other ghosties. + +/obj/structure/bodycontainer + icon = 'icons/obj/stationobjs.dmi' + icon_state = "morgue1" + density = TRUE + anchored = TRUE + max_integrity = 400 + + var/obj/structure/tray/connected = null + var/locked = FALSE + dir = SOUTH + var/message_cooldown + var/breakout_time = 600 + +/obj/structure/bodycontainer/Initialize() + . = ..() + GLOB.bodycontainers += src + recursive_organ_check(src) + +/obj/structure/bodycontainer/Destroy() + GLOB.bodycontainers -= src + open() + if(connected) + qdel(connected) + connected = null + return ..() + +/obj/structure/bodycontainer/on_log(login) + ..() + update_icon() + +/obj/structure/bodycontainer/update_icon() + return + +/obj/structure/bodycontainer/relaymove(mob/user) + if(user.stat || !isturf(loc)) + return + if(locked) + if(message_cooldown <= world.time) + message_cooldown = world.time + 50 + to_chat(user, "[src]'s door won't budge!") + return + open() + +/obj/structure/bodycontainer/attack_paw(mob/user) + return attack_hand(user) + +/obj/structure/bodycontainer/attack_hand(mob/user) + . = ..() + if(.) + return + if(locked) + to_chat(user, "It's locked.") + return + if(!connected) + to_chat(user, "That doesn't appear to have a tray.") + return + if(connected.loc == src) + open() + else + close() + add_fingerprint(user) + +/obj/structure/bodycontainer/attack_robot(mob/user) + if(!user.Adjacent(src)) + return + return attack_hand(user) + +/obj/structure/bodycontainer/attackby(obj/P, mob/user, params) + add_fingerprint(user) + if(istype(P, /obj/item/pen)) + if(!user.is_literate()) + to_chat(user, "You scribble illegibly on the side of [src]!") + return + var/t = stripped_input(user, "What would you like the label to be?", text("[]", name), null) + if (user.get_active_held_item() != P) + return + if(!user.canUseTopic(src, BE_CLOSE)) + return + if (t) + name = text("[]- '[]'", initial(name), t) + else + name = initial(name) + else + return ..() + +/obj/structure/bodycontainer/deconstruct(disassembled = TRUE) + new /obj/item/stack/sheet/metal (loc, 5) + recursive_organ_check(src) + qdel(src) + +/obj/structure/bodycontainer/container_resist(mob/living/user) + if(!locked) + open() + return + user.changeNext_move(CLICK_CD_BREAKOUT) + user.last_special = world.time + CLICK_CD_BREAKOUT + user.visible_message(null, \ + "You lean on the back of [src] and start pushing the tray open... (this will take about [DisplayTimeText(breakout_time)].)", \ + "You hear a metallic creaking from [src].") + if(do_after(user,(breakout_time), target = src)) + if(!user || user.stat != CONSCIOUS || user.loc != src ) + return + user.visible_message("[user] successfully broke out of [src]!", \ + "You successfully break out of [src]!") + open() + +/obj/structure/bodycontainer/proc/open() + recursive_organ_check(src) + playsound(src.loc, 'sound/items/deconstruct.ogg', 50, TRUE) + playsound(src, 'sound/effects/roll.ogg', 5, TRUE) + var/turf/T = get_step(src, dir) + connected.setDir(dir) + for(var/atom/movable/AM in src) + AM.forceMove(T) + update_icon() + +/obj/structure/bodycontainer/proc/close() + playsound(src, 'sound/effects/roll.ogg', 5, TRUE) + playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE) + for(var/atom/movable/AM in connected.loc) + if(!AM.anchored || AM == connected) + if(ismob(AM) && !isliving(AM)) + continue + AM.forceMove(src) + recursive_organ_check(src) + update_icon() + +/obj/structure/bodycontainer/get_remote_view_fullscreens(mob/user) + if(user.stat == DEAD || !(user.sight & (SEEOBJS|SEEMOBS))) + user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 2) +/* + * Morgue + */ +/obj/structure/bodycontainer/morgue + name = "morgue" + desc = "Used to keep bodies in until someone fetches them. Now includes a high-tech alert system." + icon_state = "morgue1" + dir = EAST + var/beeper = TRUE + var/beep_cooldown = 50 + var/next_beep = 0 + +/obj/structure/bodycontainer/morgue/Initialize() + . = ..() + connected = new/obj/structure/tray/m_tray(src) + connected.connected = src + +/obj/structure/bodycontainer/morgue/examine(mob/user) + . = ..() + . += "The speaker is [beeper ? "enabled" : "disabled"]. Alt-click to toggle it." + +/obj/structure/bodycontainer/morgue/AltClick(mob/user) + ..() + if(!user.canUseTopic(src, !issilicon(user))) + return + beeper = !beeper + to_chat(user, "You turn the speaker function [beeper ? "on" : "off"].") + +/obj/structure/bodycontainer/morgue/update_icon() + if (!connected || connected.loc != src) // Open or tray is gone. + icon_state = "morgue0" + else + if(contents.len == 1) // Empty + icon_state = "morgue1" + else + icon_state = "morgue2" // Dead, brainded mob. + var/list/compiled = GetAllContents(/mob/living) // Search for mobs in all contents. + if(!length(compiled)) // No mobs? + icon_state = "morgue3" + return + + for(var/mob/living/M in compiled) + var/mob/living/mob_occupant = get_mob_or_brainmob(M) + if(mob_occupant.client && !mob_occupant.suiciding && !(HAS_TRAIT(mob_occupant, TRAIT_BADDNA)) && !mob_occupant.hellbound) + icon_state = "morgue4" // Revivable + if(mob_occupant.stat == DEAD && beeper) + if(world.time > next_beep) + playsound(src, 'sound/weapons/gun/general/empty_alarm.ogg', 50, FALSE) //Revive them you blind fucks + next_beep = world.time + beep_cooldown + break + + +/obj/item/paper/guides/jobs/medical/morgue + name = "morgue memo" + info = "Since this station's medbay never seems to fail to be staffed by the mindless monkeys meant for genetics experiments, I'm leaving a reminder here for anyone handling the pile of cadavers the quacks are sure to leave.

                        Red lights mean there's a plain ol' dead body inside.

                        Yellow lights mean there's non-body objects inside.
                        Probably stuff pried off a corpse someone grabbed, or if you're lucky it's stashed booze.

                        Green lights mean the morgue system detects the body may be able to be brought back to life.

                        I don't know how that works, but keep it away from the kitchen and go yell at the geneticists.

                        - CentCom medical inspector" + +/* + * Crematorium + */ +GLOBAL_LIST_EMPTY(crematoriums) +/obj/structure/bodycontainer/crematorium + name = "crematorium" + desc = "A human incinerator. Works well on barbecue nights." + icon_state = "crema1" + dir = SOUTH + var/id = 1 + +/obj/structure/bodycontainer/crematorium/attack_robot(mob/user) //Borgs can't use crematoriums without help + to_chat(user, "[src] is locked against you.") + return + +/obj/structure/bodycontainer/crematorium/Destroy() + GLOB.crematoriums.Remove(src) + return ..() + +/obj/structure/bodycontainer/crematorium/New() + GLOB.crematoriums.Add(src) + ..() + +/obj/structure/bodycontainer/crematorium/Initialize() + . = ..() + connected = new /obj/structure/tray/c_tray(src) + connected.connected = src + +/obj/structure/bodycontainer/crematorium/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) + id = "[idnum][id]" + +/obj/structure/bodycontainer/crematorium/update_icon() + if(!connected || connected.loc != src) + icon_state = "crema0" + else + + if(src.contents.len > 1) + src.icon_state = "crema2" + else + src.icon_state = "crema1" + + if(locked) + src.icon_state = "crema_active" + + return + +/obj/structure/bodycontainer/crematorium/proc/cremate(mob/user) + if(locked) + return //don't let you cremate something twice or w/e + // Make sure we don't delete the actual morgue and its tray + var/list/conts = GetAllContents() - src - connected + + if(!conts.len) + audible_message("You hear a hollow crackle.") + return + + else + audible_message("You hear a roar as the crematorium activates.") + + locked = TRUE + update_icon() + + for(var/mob/living/M in conts) + if (M.stat != DEAD) + M.emote("scream") + if(user) + log_combat(user, M, "cremated") + else + M.log_message("was cremated", LOG_ATTACK) + + M.death(1) + if(M) //some animals get automatically deleted on death. + M.ghostize() + qdel(M) + + for(var/obj/O in conts) //conts defined above, ignores crematorium and tray + qdel(O) + + if(!locate(/obj/effect/decal/cleanable/ash) in get_step(src, dir))//prevent pile-up + new/obj/effect/decal/cleanable/ash/crematorium(src) + + sleep(30) + + if(!QDELETED(src)) + locked = FALSE + update_icon() + playsound(src.loc, 'sound/machines/ding.ogg', 50, TRUE) //you horrible people + +/obj/structure/bodycontainer/crematorium/creamatorium + name = "creamatorium" + desc = "A human incinerator. Works well during ice cream socials." + +/obj/structure/bodycontainer/crematorium/creamatorium/cremate(mob/user) + var/list/icecreams = new() + for(var/i_scream in GetAllContents(/mob/living)) + var/obj/item/reagent_containers/food/snacks/icecream/IC = new() + IC.set_cone_type("waffle") + IC.add_mob_flavor(i_scream) + icecreams += IC + . = ..() + for(var/obj/IC in icecreams) + IC.forceMove(src) + +/* + * Generic Tray + * Parent class for morguetray and crematoriumtray + * For overriding only + */ +/obj/structure/tray + icon = 'icons/obj/stationobjs.dmi' + density = TRUE + var/obj/structure/bodycontainer/connected = null + anchored = TRUE + pass_flags = LETPASSTHROW + max_integrity = 350 + +/obj/structure/tray/Destroy() + if(connected) + connected.connected = null + connected.update_icon() + connected = null + return ..() + +/obj/structure/tray/deconstruct(disassembled = TRUE) + new /obj/item/stack/sheet/metal (loc, 2) + qdel(src) + +/obj/structure/tray/attack_paw(mob/user) + return attack_hand(user) + +/obj/structure/tray/attack_hand(mob/user) + . = ..() + if(.) + return + if (src.connected) + connected.close() + add_fingerprint(user) + else + to_chat(user, "That's not connected to anything!") + +/obj/structure/tray/MouseDrop_T(atom/movable/O as mob|obj, mob/user) + if(!ismovable(O) || O.anchored || !Adjacent(user) || !user.Adjacent(O) || O.loc == user) + return + if(!ismob(O)) + if(!istype(O, /obj/structure/closet/body_bag)) + return + else + var/mob/M = O + if(M.buckled) + return + if(!ismob(user) || user.incapacitated()) + return + if(isliving(user)) + var/mob/living/L = user + if(!(L.mobility_flags & MOBILITY_STAND)) + return + O.forceMove(src.loc) + if (user != O) + visible_message("[user] stuffs [O] into [src].") + return + +/* + * Crematorium tray + */ +/obj/structure/tray/c_tray + name = "crematorium tray" + desc = "Apply body before burning." + icon_state = "cremat" + +/* + * Morgue tray + */ +/obj/structure/tray/m_tray + name = "morgue tray" + desc = "Apply corpse before closing." + icon_state = "morguet" + +/obj/structure/tray/m_tray/CanAllowThrough(atom/movable/mover, turf/target) + . = ..() + if(istype(mover) && (mover.pass_flags & PASSTABLE)) + return TRUE + if(locate(/obj/structure/table) in get_turf(mover)) + return TRUE + +/obj/structure/tray/m_tray/CanAStarPass(ID, dir, caller) + . = !density + if(ismovable(caller)) + var/atom/movable/mover = caller + . = . || (mover.pass_flags & PASSTABLE) diff --git a/code/game/objects/structures/musician.dm b/code/game/objects/structures/musician.dm index aa2bb8b6148c..35dbf9b69209 100644 --- a/code/game/objects/structures/musician.dm +++ b/code/game/objects/structures/musician.dm @@ -1,393 +1,393 @@ - -#define MUSICIAN_HEARCHECK_MINDELAY 4 -#define MUSIC_MAXLINES 300 -#define MUSIC_MAXLINECHARS 50 - -/datum/song - var/name = "Untitled" - var/list/lines = new() - var/tempo = 5 // delay between notes - - var/playing = 0 // if we're playing - var/help = 0 // if help is open - var/edit = 1 // if we're in editing mode - var/repeat = 0 // number of times remaining to repeat - var/max_repeats = 10 // maximum times we can repeat - - var/instrumentDir = "piano" // the folder with the sounds - var/instrumentExt = "ogg" // the file extension - var/instrumentRange = 15 // how far the sound can be heard - var/obj/instrumentObj = null // the associated obj playing the sound - var/last_hearcheck = 0 - var/list/hearing_mobs - -/datum/song/New(dir, obj, ext = "ogg", range) - tempo = sanitize_tempo(tempo) - instrumentDir = dir - instrumentObj = obj - instrumentExt = ext - instrumentRange = range - -/datum/song/Destroy() - instrumentObj = null - return ..() - -// note is a number from 1-7 for A-G -// acc is either "b", "n", or "#" -// oct is 1-8 (or 9 for C) -/datum/song/proc/playnote(mob/user, note, acc as text, oct) - // handle accidental -> B<>C of E<>F - if(acc == "b" && (note == 3 || note == 6)) // C or F - if(note == 3) - oct-- - note-- - acc = "n" - else if(acc == "#" && (note == 2 || note == 5)) // B or E - if(note == 2) - oct++ - note++ - acc = "n" - else if(acc == "#" && (note == 7)) //G# - note = 1 - acc = "b" - else if(acc == "#") // mass convert all sharps to flats, octave jump already handled - acc = "b" - note++ - - // check octave, C is allowed to go to 9 - if(oct < 1 || (note == 3 ? oct > 9 : oct > 8)) - return - - // now generate name - var/soundfile = "sound/instruments/[instrumentDir]/[ascii2text(note+64)][acc][oct].[instrumentExt]" - soundfile = file(soundfile) - // make sure the note exists - if(!fexists(soundfile)) - return - // and play - var/turf/source = get_turf(instrumentObj) - if((world.time - MUSICIAN_HEARCHECK_MINDELAY) > last_hearcheck) - LAZYCLEARLIST(hearing_mobs) - for(var/mob/M in get_hearers_in_view(instrumentRange, source)) - LAZYADD(hearing_mobs, M) - last_hearcheck = world.time - - var/sound/music_played = sound(soundfile) - for(var/i in hearing_mobs) - var/mob/M = i - if(HAS_TRAIT(user, TRAIT_MUSICIAN) && isliving(M)) - var/mob/living/L = M - L.apply_status_effect(STATUS_EFFECT_GOOD_MUSIC) - if(!M.client || !(M.client.prefs.toggles & SOUND_INSTRUMENTS)) - continue - M.playsound_local(source, null, 100, falloff = 5, S = music_played) - -/datum/song/proc/updateDialog(mob/user) - instrumentObj.updateDialog() // assumes it's an object in world, override if otherwise - -/datum/song/proc/shouldStopPlaying(mob/user) - if(instrumentObj) - if(!user.canUseTopic(instrumentObj, BE_CLOSE, FALSE, NO_TK)) - return TRUE - return !instrumentObj.anchored // add special cases to stop in subclasses - else - return TRUE - -/datum/song/proc/playsong(mob/user) - while(repeat >= 0) - var/cur_oct[7] - var/cur_acc[7] - for(var/i = 1 to 7) - cur_oct[i] = 3 - cur_acc[i] = "n" - - for(var/line in lines) - for(var/beat in splittext(lowertext(line), ",")) - var/list/notes = splittext(beat, "/") - for(var/note in splittext(notes[1], "-")) - if(!playing || shouldStopPlaying(user))//If the instrument is playing, or special case - toggle_playing(user, FALSE) - return - if(!length(note)) - continue - var/cur_note = text2ascii(note) - 96 - if(cur_note < 1 || cur_note > 7) - continue - var/notelen = length(note) - var/ni = "" - for(var/i = length(note[1]) + 1, i <= notelen, i += length(ni)) - ni = note[i] - if(!text2num(ni)) - if(ni == "#" || ni == "b" || ni == "n") - cur_acc[cur_note] = ni - else if(ni == "s") - cur_acc[cur_note] = "#" // so shift is never required - else - cur_oct[cur_note] = text2num(ni) - if(user.dizziness > 0 && prob(user.dizziness / 2)) - cur_note = clamp(cur_note + rand(round(-user.dizziness / 10), round(user.dizziness / 10)), 1, 7) - if(user.dizziness > 0 && prob(user.dizziness / 5)) - if(prob(30)) - cur_acc[cur_note] = "#" - else if(prob(42)) - cur_acc[cur_note] = "b" - else if(prob(75)) - cur_acc[cur_note] = "n" - playnote(user, cur_note, cur_acc[cur_note], cur_oct[cur_note]) - if(notes.len >= 2 && text2num(notes[2])) - sleep(sanitize_tempo(tempo / text2num(notes[2]))) - else - sleep(tempo) - repeat-- - toggle_playing(user, FALSE) - repeat = 0 - updateDialog(user) - -/datum/song/proc/interact(mob/user) - var/dat = "" - - if(lines.len > 0) - dat += "

                        Playback

                        " - if(!playing) - dat += "Play Stop

                        " - dat += "Repeat Song: " - dat += repeat > 0 ? "--" : "--" - dat += " [repeat] times " - dat += repeat < max_repeats ? "++" : "++" - dat += "
                        " - else - dat += "Play Stop
                        " - dat += "Repeats left: [repeat]
                        " - if(!edit) - dat += "
                        Show Editor
                        " - else - dat += "

                        Editing

                        " - dat += "Hide Editor" - dat += " Start a New Song" - dat += " Import a Song

                        " - var/bpm = round(600 / tempo) - dat += "Tempo: - [bpm] BPM +

                        " - var/linecount = 0 - for(var/line in lines) - linecount += 1 - dat += "Line [linecount]: Edit X [line]
                        " - dat += "Add Line

                        " - if(help) - dat += "Hide Help
                        " - dat += {" - Lines are a series of chords, separated by commas (,), each with notes separated by hyphens (-).
                        - Every note in a chord will play together, with chord timed by the tempo.
                        -
                        - Notes are played by the names of the note, and optionally, the accidental, and/or the octave number.
                        - By default, every note is natural and in octave 3. Defining otherwise is remembered for each note.
                        - Example: C,D,E,F,G,A,B will play a C major scale.
                        - After a note has an accidental placed, it will be remembered: C,C4,C,C3 is C3,C4,C4,C3
                        - Chords can be played simply by seperating each note with a hyphon: A-C#,Cn-E,E-G#,Gn-B
                        - A pause may be denoted by an empty chord: C,E,,C,G
                        - To make a chord be a different time, end it with /x, where the chord length will be length
                        - defined by tempo / x: C,G/2,E/4
                        - Combined, an example is: E-E4/4,F#/2,G#/8,B/8,E3-E4/4 -
                        - Lines may be up to [MUSIC_MAXLINECHARS] characters.
                        - A song may only contain up to [MUSIC_MAXLINES] lines.
                        - "} - else - dat += "Show Help
                        " - - var/datum/browser/popup = new(user, "instrument", instrumentObj.name, 700, 500) - popup.set_content(dat) - popup.set_title_image(user.browse_rsc_icon(instrumentObj.icon, instrumentObj.icon_state)) - popup.open() - -/datum/song/proc/ParseSong(text) - set waitfor = FALSE - //split into lines - lines = splittext(text, "\n") - if(lines.len) - var/bpm_string = "BPM: " - if(findtext(lines[1], bpm_string, 1, length(bpm_string) + 1)) - tempo = sanitize_tempo(600 / text2num(copytext(lines[1], length(bpm_string) + 1))) - lines.Cut(1, 2) - else - tempo = sanitize_tempo(5) // default 120 BPM - if(lines.len > MUSIC_MAXLINES) - to_chat(usr, "Too many lines!") - lines.Cut(MUSIC_MAXLINES + 1) - var/linenum = 1 - for(var/l in lines) - if(length_char(l) > MUSIC_MAXLINECHARS) - to_chat(usr, "Line [linenum] too long!") - lines.Remove(l) - else - linenum++ - updateDialog(usr) // make sure updates when complete - -/datum/song/Topic(href, href_list) - if(!usr.canUseTopic(instrumentObj, BE_CLOSE, FALSE, NO_TK)) - usr << browse(null, "window=instrument") - usr.unset_machine() - return - - instrumentObj.add_fingerprint(usr) - - if(href_list["newsong"]) - lines = new() - tempo = sanitize_tempo(5) // default 120 BPM - name = "" - - else if(href_list["import"]) - var/t = "" - do - t = html_encode(input(usr, "Please paste the entire song, formatted:", text("[]", name), t) as message) - if(!usr.canUseTopic(instrumentObj, BE_CLOSE, FALSE, NO_TK)) - return - - if(length(t) >= MUSIC_MAXLINES * MUSIC_MAXLINECHARS) - var/cont = input(usr, "Your message is too long! Would you like to continue editing it?", "", "yes") in list("yes", "no") - if(!usr.canUseTopic(instrumentObj, BE_CLOSE, FALSE, NO_TK)) - return - if(cont == "no") - break - while(length(t) > MUSIC_MAXLINES * MUSIC_MAXLINECHARS) - ParseSong(t) - - else if(href_list["help"]) - help = text2num(href_list["help"]) - 1 - - else if(href_list["edit"]) - edit = text2num(href_list["edit"]) - 1 - - if(href_list["repeat"]) //Changing this from a toggle to a number of repeats to avoid infinite loops. - if(playing) - return //So that people cant keep adding to repeat. If the do it intentionally, it could result in the server crashing. - repeat += round(text2num(href_list["repeat"])) - if(repeat < 0) - repeat = 0 - if(repeat > max_repeats) - repeat = max_repeats - - else if(href_list["tempo"]) - tempo = sanitize_tempo(tempo + text2num(href_list["tempo"])) - - else if(href_list["play"]) - toggle_playing(usr, TRUE) - - else if(href_list["newline"]) - var/newline = html_encode(input("Enter your line: ", instrumentObj.name) as text|null) - if(!newline || !usr.canUseTopic(instrumentObj, BE_CLOSE, FALSE, NO_TK)) - return - if(lines.len > MUSIC_MAXLINES) - return - if(length_char(newline) > MUSIC_MAXLINECHARS) - newline = copytext_char(newline, 1, MUSIC_MAXLINECHARS) - lines.Add(newline) - - else if(href_list["deleteline"]) - var/num = round(text2num(href_list["deleteline"])) - if(num > lines.len || num < 1) - return - lines.Cut(num, num+1) - - else if(href_list["modifyline"]) - var/num = round(text2num(href_list["modifyline"]),1) - var/content = stripped_input(usr, "Enter your line: ", instrumentObj.name, lines[num], MUSIC_MAXLINECHARS) - if(!content || !usr.canUseTopic(instrumentObj, BE_CLOSE, FALSE, NO_TK)) - return - if(num > lines.len || num < 1) - return - lines[num] = content - - else if(href_list["stop"]) - toggle_playing(usr, FALSE) - - updateDialog(usr) - return - -/datum/song/proc/sanitize_tempo(new_tempo) - new_tempo = abs(new_tempo) - return max(round(new_tempo, world.tick_lag), world.tick_lag) - -/datum/song/proc/toggle_playing(user, new_play_state) - playing = new_play_state - if(playing) - INVOKE_ASYNC(src, .proc/playsong, user) - SEND_SIGNAL(instrumentObj, COMSIG_SONG_START) - else - hearing_mobs = null - SEND_SIGNAL(instrumentObj, COMSIG_SONG_END) - -// subclass for handheld instruments, like violin -/datum/song/handheld - -/datum/song/handheld/updateDialog(mob/user) - instrumentObj.interact(user) - -/datum/song/handheld/shouldStopPlaying() - if(instrumentObj) - return !isliving(instrumentObj.loc) - else - return TRUE - -////////////////////////////////////////////////////////////////////////// - - -/obj/structure/piano - name = "space minimoog" - icon = 'icons/obj/musician.dmi' - icon_state = "minimoog" - anchored = TRUE - density = TRUE - var/datum/song/song - -/obj/structure/piano/unanchored - anchored = FALSE - -/obj/structure/piano/Initialize() - . = ..() - song = new("piano", src) - - if(prob(50) && icon_state == initial(icon_state)) - name = "space minimoog" - desc = "This is a minimoog, like a space piano, but more spacey!" - icon_state = "minimoog" - else - name = "space piano" - desc = "This is a space piano, like a regular piano, but always in tune! Even if the musician isn't." - icon_state = "piano" - -/obj/structure/piano/Destroy() - qdel(song) - song = null - return ..() - -/obj/structure/piano/Initialize(mapload) - . = ..() - if(mapload) - song.tempo = song.sanitize_tempo(song.tempo) // tick_lag isn't set when the map is loaded - -/obj/structure/piano/attack_hand(mob/user) - . = ..() - if(.) - return - interact(user) - -/obj/structure/piano/attack_paw(mob/user) - return attack_hand(user) - -/obj/structure/piano/interact(mob/user) - ui_interact(user) - -/obj/structure/piano/ui_interact(mob/user) - if(!user || !anchored) - return - - if(!user.IsAdvancedToolUser()) - to_chat(user, "You don't have the dexterity to do this!") - return 1 - user.set_machine(src) - song.interact(user) - -/obj/structure/piano/wrench_act(mob/living/user, obj/item/I) - ..() - default_unfasten_wrench(user, I, 40) - return TRUE + +#define MUSICIAN_HEARCHECK_MINDELAY 4 +#define MUSIC_MAXLINES 300 +#define MUSIC_MAXLINECHARS 50 + +/datum/song + var/name = "Untitled" + var/list/lines = new() + var/tempo = 5 // delay between notes + + var/playing = 0 // if we're playing + var/help = 0 // if help is open + var/edit = 1 // if we're in editing mode + var/repeat = 0 // number of times remaining to repeat + var/max_repeats = 10 // maximum times we can repeat + + var/instrumentDir = "piano" // the folder with the sounds + var/instrumentExt = "ogg" // the file extension + var/instrumentRange = 15 // how far the sound can be heard + var/obj/instrumentObj = null // the associated obj playing the sound + var/last_hearcheck = 0 + var/list/hearing_mobs + +/datum/song/New(dir, obj, ext = "ogg", range) + tempo = sanitize_tempo(tempo) + instrumentDir = dir + instrumentObj = obj + instrumentExt = ext + instrumentRange = range + +/datum/song/Destroy() + instrumentObj = null + return ..() + +// note is a number from 1-7 for A-G +// acc is either "b", "n", or "#" +// oct is 1-8 (or 9 for C) +/datum/song/proc/playnote(mob/user, note, acc as text, oct) + // handle accidental -> B<>C of E<>F + if(acc == "b" && (note == 3 || note == 6)) // C or F + if(note == 3) + oct-- + note-- + acc = "n" + else if(acc == "#" && (note == 2 || note == 5)) // B or E + if(note == 2) + oct++ + note++ + acc = "n" + else if(acc == "#" && (note == 7)) //G# + note = 1 + acc = "b" + else if(acc == "#") // mass convert all sharps to flats, octave jump already handled + acc = "b" + note++ + + // check octave, C is allowed to go to 9 + if(oct < 1 || (note == 3 ? oct > 9 : oct > 8)) + return + + // now generate name + var/soundfile = "sound/instruments/[instrumentDir]/[ascii2text(note+64)][acc][oct].[instrumentExt]" + soundfile = file(soundfile) + // make sure the note exists + if(!fexists(soundfile)) + return + // and play + var/turf/source = get_turf(instrumentObj) + if((world.time - MUSICIAN_HEARCHECK_MINDELAY) > last_hearcheck) + LAZYCLEARLIST(hearing_mobs) + for(var/mob/M in get_hearers_in_view(instrumentRange, source)) + LAZYADD(hearing_mobs, M) + last_hearcheck = world.time + + var/sound/music_played = sound(soundfile) + for(var/i in hearing_mobs) + var/mob/M = i + if(HAS_TRAIT(user, TRAIT_MUSICIAN) && isliving(M)) + var/mob/living/L = M + L.apply_status_effect(STATUS_EFFECT_GOOD_MUSIC) + if(!M.client || !(M.client.prefs.toggles & SOUND_INSTRUMENTS)) + continue + M.playsound_local(source, null, 100, falloff = 5, S = music_played) + +/datum/song/proc/updateDialog(mob/user) + instrumentObj.updateDialog() // assumes it's an object in world, override if otherwise + +/datum/song/proc/shouldStopPlaying(mob/user) + if(instrumentObj) + if(!user.canUseTopic(instrumentObj, BE_CLOSE, FALSE, NO_TK)) + return TRUE + return !instrumentObj.anchored // add special cases to stop in subclasses + else + return TRUE + +/datum/song/proc/playsong(mob/user) + while(repeat >= 0) + var/cur_oct[7] + var/cur_acc[7] + for(var/i = 1 to 7) + cur_oct[i] = 3 + cur_acc[i] = "n" + + for(var/line in lines) + for(var/beat in splittext(lowertext(line), ",")) + var/list/notes = splittext(beat, "/") + for(var/note in splittext(notes[1], "-")) + if(!playing || shouldStopPlaying(user))//If the instrument is playing, or special case + toggle_playing(user, FALSE) + return + if(!length(note)) + continue + var/cur_note = text2ascii(note) - 96 + if(cur_note < 1 || cur_note > 7) + continue + var/notelen = length(note) + var/ni = "" + for(var/i = length(note[1]) + 1, i <= notelen, i += length(ni)) + ni = note[i] + if(!text2num(ni)) + if(ni == "#" || ni == "b" || ni == "n") + cur_acc[cur_note] = ni + else if(ni == "s") + cur_acc[cur_note] = "#" // so shift is never required + else + cur_oct[cur_note] = text2num(ni) + if(user.dizziness > 0 && prob(user.dizziness / 2)) + cur_note = clamp(cur_note + rand(round(-user.dizziness / 10), round(user.dizziness / 10)), 1, 7) + if(user.dizziness > 0 && prob(user.dizziness / 5)) + if(prob(30)) + cur_acc[cur_note] = "#" + else if(prob(42)) + cur_acc[cur_note] = "b" + else if(prob(75)) + cur_acc[cur_note] = "n" + playnote(user, cur_note, cur_acc[cur_note], cur_oct[cur_note]) + if(notes.len >= 2 && text2num(notes[2])) + sleep(sanitize_tempo(tempo / text2num(notes[2]))) + else + sleep(tempo) + repeat-- + toggle_playing(user, FALSE) + repeat = 0 + updateDialog(user) + +/datum/song/proc/interact(mob/user) + var/dat = "" + + if(lines.len > 0) + dat += "

                        Playback

                        " + if(!playing) + dat += "Play Stop

                        " + dat += "Repeat Song: " + dat += repeat > 0 ? "--" : "--" + dat += " [repeat] times " + dat += repeat < max_repeats ? "++" : "++" + dat += "
                        " + else + dat += "Play Stop
                        " + dat += "Repeats left: [repeat]
                        " + if(!edit) + dat += "
                        Show Editor
                        " + else + dat += "

                        Editing

                        " + dat += "Hide Editor" + dat += " Start a New Song" + dat += " Import a Song

                        " + var/bpm = round(600 / tempo) + dat += "Tempo: - [bpm] BPM +

                        " + var/linecount = 0 + for(var/line in lines) + linecount += 1 + dat += "Line [linecount]: Edit X [line]
                        " + dat += "Add Line

                        " + if(help) + dat += "Hide Help
                        " + dat += {" + Lines are a series of chords, separated by commas (,), each with notes separated by hyphens (-).
                        + Every note in a chord will play together, with chord timed by the tempo.
                        +
                        + Notes are played by the names of the note, and optionally, the accidental, and/or the octave number.
                        + By default, every note is natural and in octave 3. Defining otherwise is remembered for each note.
                        + Example: C,D,E,F,G,A,B will play a C major scale.
                        + After a note has an accidental placed, it will be remembered: C,C4,C,C3 is C3,C4,C4,C3
                        + Chords can be played simply by seperating each note with a hyphon: A-C#,Cn-E,E-G#,Gn-B
                        + A pause may be denoted by an empty chord: C,E,,C,G
                        + To make a chord be a different time, end it with /x, where the chord length will be length
                        + defined by tempo / x: C,G/2,E/4
                        + Combined, an example is: E-E4/4,F#/2,G#/8,B/8,E3-E4/4 +
                        + Lines may be up to [MUSIC_MAXLINECHARS] characters.
                        + A song may only contain up to [MUSIC_MAXLINES] lines.
                        + "} + else + dat += "Show Help
                        " + + var/datum/browser/popup = new(user, "instrument", instrumentObj.name, 700, 500) + popup.set_content(dat) + popup.set_title_image(user.browse_rsc_icon(instrumentObj.icon, instrumentObj.icon_state)) + popup.open() + +/datum/song/proc/ParseSong(text) + set waitfor = FALSE + //split into lines + lines = splittext(text, "\n") + if(lines.len) + var/bpm_string = "BPM: " + if(findtext(lines[1], bpm_string, 1, length(bpm_string) + 1)) + tempo = sanitize_tempo(600 / text2num(copytext(lines[1], length(bpm_string) + 1))) + lines.Cut(1, 2) + else + tempo = sanitize_tempo(5) // default 120 BPM + if(lines.len > MUSIC_MAXLINES) + to_chat(usr, "Too many lines!") + lines.Cut(MUSIC_MAXLINES + 1) + var/linenum = 1 + for(var/l in lines) + if(length_char(l) > MUSIC_MAXLINECHARS) + to_chat(usr, "Line [linenum] too long!") + lines.Remove(l) + else + linenum++ + updateDialog(usr) // make sure updates when complete + +/datum/song/Topic(href, href_list) + if(!usr.canUseTopic(instrumentObj, BE_CLOSE, FALSE, NO_TK)) + usr << browse(null, "window=instrument") + usr.unset_machine() + return + + instrumentObj.add_fingerprint(usr) + + if(href_list["newsong"]) + lines = new() + tempo = sanitize_tempo(5) // default 120 BPM + name = "" + + else if(href_list["import"]) + var/t = "" + do + t = html_encode(input(usr, "Please paste the entire song, formatted:", text("[]", name), t) as message) + if(!usr.canUseTopic(instrumentObj, BE_CLOSE, FALSE, NO_TK)) + return + + if(length(t) >= MUSIC_MAXLINES * MUSIC_MAXLINECHARS) + var/cont = input(usr, "Your message is too long! Would you like to continue editing it?", "", "yes") in list("yes", "no") + if(!usr.canUseTopic(instrumentObj, BE_CLOSE, FALSE, NO_TK)) + return + if(cont == "no") + break + while(length(t) > MUSIC_MAXLINES * MUSIC_MAXLINECHARS) + ParseSong(t) + + else if(href_list["help"]) + help = text2num(href_list["help"]) - 1 + + else if(href_list["edit"]) + edit = text2num(href_list["edit"]) - 1 + + if(href_list["repeat"]) //Changing this from a toggle to a number of repeats to avoid infinite loops. + if(playing) + return //So that people cant keep adding to repeat. If the do it intentionally, it could result in the server crashing. + repeat += round(text2num(href_list["repeat"])) + if(repeat < 0) + repeat = 0 + if(repeat > max_repeats) + repeat = max_repeats + + else if(href_list["tempo"]) + tempo = sanitize_tempo(tempo + text2num(href_list["tempo"])) + + else if(href_list["play"]) + toggle_playing(usr, TRUE) + + else if(href_list["newline"]) + var/newline = html_encode(input("Enter your line: ", instrumentObj.name) as text|null) + if(!newline || !usr.canUseTopic(instrumentObj, BE_CLOSE, FALSE, NO_TK)) + return + if(lines.len > MUSIC_MAXLINES) + return + if(length_char(newline) > MUSIC_MAXLINECHARS) + newline = copytext_char(newline, 1, MUSIC_MAXLINECHARS) + lines.Add(newline) + + else if(href_list["deleteline"]) + var/num = round(text2num(href_list["deleteline"])) + if(num > lines.len || num < 1) + return + lines.Cut(num, num+1) + + else if(href_list["modifyline"]) + var/num = round(text2num(href_list["modifyline"]),1) + var/content = stripped_input(usr, "Enter your line: ", instrumentObj.name, lines[num], MUSIC_MAXLINECHARS) + if(!content || !usr.canUseTopic(instrumentObj, BE_CLOSE, FALSE, NO_TK)) + return + if(num > lines.len || num < 1) + return + lines[num] = content + + else if(href_list["stop"]) + toggle_playing(usr, FALSE) + + updateDialog(usr) + return + +/datum/song/proc/sanitize_tempo(new_tempo) + new_tempo = abs(new_tempo) + return max(round(new_tempo, world.tick_lag), world.tick_lag) + +/datum/song/proc/toggle_playing(user, new_play_state) + playing = new_play_state + if(playing) + INVOKE_ASYNC(src, .proc/playsong, user) + SEND_SIGNAL(instrumentObj, COMSIG_SONG_START) + else + hearing_mobs = null + SEND_SIGNAL(instrumentObj, COMSIG_SONG_END) + +// subclass for handheld instruments, like violin +/datum/song/handheld + +/datum/song/handheld/updateDialog(mob/user) + instrumentObj.interact(user) + +/datum/song/handheld/shouldStopPlaying() + if(instrumentObj) + return !isliving(instrumentObj.loc) + else + return TRUE + +////////////////////////////////////////////////////////////////////////// + + +/obj/structure/piano + name = "space minimoog" + icon = 'icons/obj/musician.dmi' + icon_state = "minimoog" + anchored = TRUE + density = TRUE + var/datum/song/song + +/obj/structure/piano/unanchored + anchored = FALSE + +/obj/structure/piano/Initialize() + . = ..() + song = new("piano", src) + + if(prob(50) && icon_state == initial(icon_state)) + name = "space minimoog" + desc = "This is a minimoog, like a space piano, but more spacey!" + icon_state = "minimoog" + else + name = "space piano" + desc = "This is a space piano, like a regular piano, but always in tune! Even if the musician isn't." + icon_state = "piano" + +/obj/structure/piano/Destroy() + qdel(song) + song = null + return ..() + +/obj/structure/piano/Initialize(mapload) + . = ..() + if(mapload) + song.tempo = song.sanitize_tempo(song.tempo) // tick_lag isn't set when the map is loaded + +/obj/structure/piano/attack_hand(mob/user) + . = ..() + if(.) + return + interact(user) + +/obj/structure/piano/attack_paw(mob/user) + return attack_hand(user) + +/obj/structure/piano/interact(mob/user) + ui_interact(user) + +/obj/structure/piano/ui_interact(mob/user) + if(!user || !anchored) + return + + if(!user.IsAdvancedToolUser()) + to_chat(user, "You don't have the dexterity to do this!") + return 1 + user.set_machine(src) + song.interact(user) + +/obj/structure/piano/wrench_act(mob/living/user, obj/item/I) + ..() + default_unfasten_wrench(user, I, 40) + return TRUE diff --git a/code/game/objects/structures/noticeboard.dm b/code/game/objects/structures/noticeboard.dm index 2660245de461..43fb687a31bb 100644 --- a/code/game/objects/structures/noticeboard.dm +++ b/code/game/objects/structures/noticeboard.dm @@ -1,132 +1,132 @@ -/obj/structure/noticeboard - name = "notice board" - desc = "A board for pinning important notices upon." - icon = 'icons/obj/stationobjs.dmi' - icon_state = "nboard00" - density = FALSE - anchored = TRUE - max_integrity = 150 - var/notices = 0 - -/obj/structure/noticeboard/Initialize(mapload) - . = ..() - - if(!mapload) - return - - for(var/obj/item/I in loc) - if(notices > 4) - break - if(istype(I, /obj/item/paper)) - I.forceMove(src) - notices++ - icon_state = "nboard0[notices]" - -//attaching papers!! -/obj/structure/noticeboard/attackby(obj/item/O, mob/user, params) - if(istype(O, /obj/item/paper) || istype(O, /obj/item/photo)) - if(!allowed(user)) - to_chat(user, "You are not authorized to add notices!") - return - if(notices < 5) - if(!user.transferItemToLoc(O, src)) - return - notices++ - icon_state = "nboard0[notices]" - to_chat(user, "You pin the [O] to the noticeboard.") - else - to_chat(user, "The notice board is full!") - else - return ..() - -/obj/structure/noticeboard/interact(mob/user) - ui_interact(user) - -/obj/structure/noticeboard/ui_interact(mob/user) - . = ..() - var/auth = allowed(user) - var/dat = "[name]
                        " - for(var/obj/item/P in src) - if(istype(P, /obj/item/paper)) - dat += "[P.name] [auth ? "Write Remove" : ""]
                        " - else - dat += "[P.name] [auth ? "Remove" : ""]
                        " - user << browse("Notices[dat]","window=noticeboard") - onclose(user, "noticeboard") - -/obj/structure/noticeboard/Topic(href, href_list) - ..() - usr.set_machine(src) - if(href_list["remove"]) - if((usr.stat || usr.restrained())) //For when a player is handcuffed while they have the notice window open - return - var/obj/item/I = locate(href_list["remove"]) in contents - if(istype(I) && I.loc == src) - I.forceMove(usr.loc) - usr.put_in_hands(I) - notices-- - icon_state = "nboard0[notices]" - - if(href_list["write"]) - if((usr.stat || usr.restrained())) //For when a player is handcuffed while they have the notice window open - return - var/obj/item/P = locate(href_list["write"]) in contents - if(istype(P) && P.loc == src) - var/obj/item/I = usr.is_holding_item_of_type(/obj/item/pen) - if(I) - add_fingerprint(usr) - P.attackby(I, usr) - else - to_chat(usr, "You'll need something to write with!") - - if(href_list["read"]) - var/obj/item/I = locate(href_list["read"]) in contents - if(istype(I) && I.loc == src) - usr.examinate(I) - -/obj/structure/noticeboard/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - new /obj/item/stack/sheet/metal (loc, 1) - qdel(src) - -// Notice boards for the heads of staff (plus the qm) - -/obj/structure/noticeboard/captain - name = "Captain's Notice Board" - desc = "Important notices from the Captain." - req_access = list(ACCESS_CAPTAIN) - -/obj/structure/noticeboard/head_of_personnel - name = "Head of Personnel's Notice Board" - desc = "Important notices from the Head of Personnel." - req_access = list(ACCESS_HOP) - -/obj/structure/noticeboard/ce - name = "Chief Engineer's Notice Board" - desc = "Important notices from the Chief Engineer." - req_access = list(ACCESS_CE) - -/obj/structure/noticeboard/hos - name = "Head of Security's Notice Board" - desc = "Important notices from the Head of Security." - req_access = list(ACCESS_HOS) - -/obj/structure/noticeboard/cmo - name = "Chief Medical Officer's Notice Board" - desc = "Important notices from the Chief Medical Officer." - req_access = list(ACCESS_CMO) - -/obj/structure/noticeboard/rd - name = "Research Director's Notice Board" - desc = "Important notices from the Research Director." - req_access = list(ACCESS_RD) - -/obj/structure/noticeboard/qm - name = "Quartermaster's Notice Board" - desc = "Important notices from the Quartermaster." - req_access = list(ACCESS_QM) - -/obj/structure/noticeboard/staff - name = "Staff Notice Board" - desc = "Important notices from the heads of staff." - req_access = list(ACCESS_HEADS) +/obj/structure/noticeboard + name = "notice board" + desc = "A board for pinning important notices upon." + icon = 'icons/obj/stationobjs.dmi' + icon_state = "nboard00" + density = FALSE + anchored = TRUE + max_integrity = 150 + var/notices = 0 + +/obj/structure/noticeboard/Initialize(mapload) + . = ..() + + if(!mapload) + return + + for(var/obj/item/I in loc) + if(notices > 4) + break + if(istype(I, /obj/item/paper)) + I.forceMove(src) + notices++ + icon_state = "nboard0[notices]" + +//attaching papers!! +/obj/structure/noticeboard/attackby(obj/item/O, mob/user, params) + if(istype(O, /obj/item/paper) || istype(O, /obj/item/photo)) + if(!allowed(user)) + to_chat(user, "You are not authorized to add notices!") + return + if(notices < 5) + if(!user.transferItemToLoc(O, src)) + return + notices++ + icon_state = "nboard0[notices]" + to_chat(user, "You pin the [O] to the noticeboard.") + else + to_chat(user, "The notice board is full!") + else + return ..() + +/obj/structure/noticeboard/interact(mob/user) + ui_interact(user) + +/obj/structure/noticeboard/ui_interact(mob/user) + . = ..() + var/auth = allowed(user) + var/dat = "[name]
                        " + for(var/obj/item/P in src) + if(istype(P, /obj/item/paper)) + dat += "[P.name] [auth ? "Write Remove" : ""]
                        " + else + dat += "[P.name] [auth ? "Remove" : ""]
                        " + user << browse("Notices[dat]","window=noticeboard") + onclose(user, "noticeboard") + +/obj/structure/noticeboard/Topic(href, href_list) + ..() + usr.set_machine(src) + if(href_list["remove"]) + if((usr.stat || usr.restrained())) //For when a player is handcuffed while they have the notice window open + return + var/obj/item/I = locate(href_list["remove"]) in contents + if(istype(I) && I.loc == src) + I.forceMove(usr.loc) + usr.put_in_hands(I) + notices-- + icon_state = "nboard0[notices]" + + if(href_list["write"]) + if((usr.stat || usr.restrained())) //For when a player is handcuffed while they have the notice window open + return + var/obj/item/P = locate(href_list["write"]) in contents + if(istype(P) && P.loc == src) + var/obj/item/I = usr.is_holding_item_of_type(/obj/item/pen) + if(I) + add_fingerprint(usr) + P.attackby(I, usr) + else + to_chat(usr, "You'll need something to write with!") + + if(href_list["read"]) + var/obj/item/I = locate(href_list["read"]) in contents + if(istype(I) && I.loc == src) + usr.examinate(I) + +/obj/structure/noticeboard/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + new /obj/item/stack/sheet/metal (loc, 1) + qdel(src) + +// Notice boards for the heads of staff (plus the qm) + +/obj/structure/noticeboard/captain + name = "Captain's Notice Board" + desc = "Important notices from the Captain." + req_access = list(ACCESS_CAPTAIN) + +/obj/structure/noticeboard/head_of_personnel + name = "Head of Personnel's Notice Board" + desc = "Important notices from the Head of Personnel." + req_access = list(ACCESS_HOP) + +/obj/structure/noticeboard/ce + name = "Chief Engineer's Notice Board" + desc = "Important notices from the Chief Engineer." + req_access = list(ACCESS_CE) + +/obj/structure/noticeboard/hos + name = "Head of Security's Notice Board" + desc = "Important notices from the Head of Security." + req_access = list(ACCESS_HOS) + +/obj/structure/noticeboard/cmo + name = "Chief Medical Officer's Notice Board" + desc = "Important notices from the Chief Medical Officer." + req_access = list(ACCESS_CMO) + +/obj/structure/noticeboard/rd + name = "Research Director's Notice Board" + desc = "Important notices from the Research Director." + req_access = list(ACCESS_RD) + +/obj/structure/noticeboard/qm + name = "Quartermaster's Notice Board" + desc = "Important notices from the Quartermaster." + req_access = list(ACCESS_QM) + +/obj/structure/noticeboard/staff + name = "Staff Notice Board" + desc = "Important notices from the heads of staff." + req_access = list(ACCESS_HEADS) diff --git a/code/game/objects/structures/safe.dm b/code/game/objects/structures/safe.dm index 0496aa49ed35..73323d920482 100644 --- a/code/game/objects/structures/safe.dm +++ b/code/game/objects/structures/safe.dm @@ -1,205 +1,205 @@ -/* -CONTAINS: -SAFES -FLOOR SAFES -*/ - -//SAFES -/obj/structure/safe - name = "safe" - desc = "A huge chunk of metal with a dial embedded in it. Fine print on the dial reads \"Scarborough Arms - 2 tumbler safe, guaranteed thermite resistant, explosion resistant, and assistant resistant.\"" - icon = 'icons/obj/structures.dmi' - icon_state = "safe" - anchored = TRUE - 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/Initialize() - . = ..() - tumbler_1_pos = rand(0, 71) - tumbler_1_open = rand(0, 71) - - tumbler_2_pos = rand(0, 71) - tumbler_2_open = rand(0, 71) - - -/obj/structure/safe/Initialize(mapload) - . = ..() - - if(!mapload) - return - - for(var/obj/item/I in loc) - if(space >= maxspace) - return - if(I.w_class + space <= maxspace) - 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, BE_CLOSE)) - 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 - 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() - return - else - to_chat(user, "[I] won't fit in [src].") - 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) - explosion_count++ - switch(explosion_count) - if(1) - desc = initial(desc) + "\nIt looks a little banged up." - if(2) - desc = initial(desc) + "\nIt's pretty heavily damaged." - if(3) - desc = initial(desc) + "\nThe lock seems to be broken." - - -//FLOOR SAFES -/obj/structure/safe/floor - name = "floor safe" - icon_state = "floorsafe" - density = FALSE - layer = LOW_OBJ_LAYER - - -/obj/structure/safe/floor/Initialize(mapload) - . = ..() - - AddElement(/datum/element/undertile) +/* +CONTAINS: +SAFES +FLOOR SAFES +*/ + +//SAFES +/obj/structure/safe + name = "safe" + desc = "A huge chunk of metal with a dial embedded in it. Fine print on the dial reads \"Scarborough Arms - 2 tumbler safe, guaranteed thermite resistant, explosion resistant, and assistant resistant.\"" + icon = 'icons/obj/structures.dmi' + icon_state = "safe" + anchored = TRUE + 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/Initialize() + . = ..() + tumbler_1_pos = rand(0, 71) + tumbler_1_open = rand(0, 71) + + tumbler_2_pos = rand(0, 71) + tumbler_2_open = rand(0, 71) + + +/obj/structure/safe/Initialize(mapload) + . = ..() + + if(!mapload) + return + + for(var/obj/item/I in loc) + if(space >= maxspace) + return + if(I.w_class + space <= maxspace) + 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, BE_CLOSE)) + 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 + 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() + return + else + to_chat(user, "[I] won't fit in [src].") + 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) + explosion_count++ + switch(explosion_count) + if(1) + desc = initial(desc) + "\nIt looks a little banged up." + if(2) + desc = initial(desc) + "\nIt's pretty heavily damaged." + if(3) + desc = initial(desc) + "\nThe lock seems to be broken." + + +//FLOOR SAFES +/obj/structure/safe/floor + name = "floor safe" + icon_state = "floorsafe" + density = FALSE + layer = LOW_OBJ_LAYER + + +/obj/structure/safe/floor/Initialize(mapload) + . = ..() + + AddElement(/datum/element/undertile) diff --git a/code/game/objects/structures/tables_racks.dm b/code/game/objects/structures/tables_racks.dm index 664947df1884..248d86bd8912 100644 --- a/code/game/objects/structures/tables_racks.dm +++ b/code/game/objects/structures/tables_racks.dm @@ -1,681 +1,681 @@ -/* Tables and Racks - * Contains: - * Tables - * Glass Tables - * Wooden Tables - * Reinforced Tables - * Racks - * Rack Parts - */ - -/* - * Tables - */ - -/obj/structure/table - name = "table" - desc = "A square piece of metal standing on four metal legs. It can not move." - icon = 'icons/obj/smooth_structures/table.dmi' - icon_state = "table" - density = TRUE - anchored = TRUE - layer = TABLE_LAYER - climbable = TRUE - pass_flags = LETPASSTHROW //You can throw objects over this, despite it's density.") - var/frame = /obj/structure/table_frame - var/framestack = /obj/item/stack/rods - var/buildstack = /obj/item/stack/sheet/metal - var/busy = FALSE - var/buildstackamount = 1 - var/framestackamount = 2 - var/deconstruction_ready = 1 - custom_materials = list(/datum/material/iron = 2000) - max_integrity = 100 - integrity_failure = 0.33 - smooth = SMOOTH_TRUE - canSmoothWith = list(/obj/structure/table, /obj/structure/table/reinforced, /obj/structure/table/greyscale) - -/obj/structure/table/examine(mob/user) - . = ..() - . += deconstruction_hints(user) - -/obj/structure/table/proc/deconstruction_hints(mob/user) - return "The top is screwed on, but the main bolts are also visible." - -/obj/structure/table/update_icon() - if(smooth) - queue_smooth(src) - queue_smooth_neighbors(src) - -/obj/structure/table/narsie_act() - var/atom/A = loc - qdel(src) - new /obj/structure/table/wood(A) - -/obj/structure/table/attack_paw(mob/user) - return attack_hand(user) - -/obj/structure/table/attack_hand(mob/living/user) - if(Adjacent(user) && user.pulling) - if(isliving(user.pulling)) - var/mob/living/pushed_mob = user.pulling - if(pushed_mob.buckled) - to_chat(user, "[pushed_mob] is buckled to [pushed_mob.buckled]!") - return - if(user.a_intent == INTENT_GRAB) - if(user.grab_state < GRAB_AGGRESSIVE) - to_chat(user, "You need a better grip to do that!") - return - if(user.grab_state >= GRAB_NECK) - tableheadsmash(user, pushed_mob) - else - tablepush(user, pushed_mob) - if(user.a_intent == INTENT_HELP) - pushed_mob.visible_message("[user] begins to place [pushed_mob] onto [src]...", \ - "[user] begins to place [pushed_mob] onto [src]...") - if(do_after(user, 35, target = pushed_mob)) - tableplace(user, pushed_mob) - else - return - user.stop_pulling() - else if(user.pulling.pass_flags & PASSTABLE) - user.Move_Pulled(src) - if (user.pulling.loc == loc) - user.visible_message("[user] places [user.pulling] onto [src].", - "You place [user.pulling] onto [src].") - user.stop_pulling() - return ..() - -/obj/structure/table/attack_tk() - return FALSE - -/obj/structure/table/CanAllowThrough(atom/movable/mover, turf/target) - . = ..() - - if(istype(mover) && (mover.pass_flags & PASSTABLE)) - return TRUE - if(mover.throwing) - return TRUE - if(locate(/obj/structure/table) in get_turf(mover)) - return TRUE - -/obj/structure/table/CanAStarPass(ID, dir, caller) - . = !density - if(ismovable(caller)) - var/atom/movable/mover = caller - . = . || (mover.pass_flags & PASSTABLE) - -/obj/structure/table/proc/tableplace(mob/living/user, mob/living/pushed_mob) - pushed_mob.forceMove(loc) - pushed_mob.set_resting(TRUE, TRUE) - pushed_mob.visible_message("[user] places [pushed_mob] onto [src].", \ - "[user] places [pushed_mob] onto [src].") - log_combat(user, pushed_mob, "places", null, "onto [src]") - -/obj/structure/table/proc/tablepush(mob/living/user, mob/living/pushed_mob) - if(HAS_TRAIT(user, TRAIT_PACIFISM)) - to_chat(user, "Throwing [pushed_mob] onto the table might hurt them!") - return - var/added_passtable = FALSE - if(!pushed_mob.pass_flags & PASSTABLE) - added_passtable = TRUE - pushed_mob.pass_flags |= PASSTABLE - pushed_mob.Move(src.loc) - if(added_passtable) - pushed_mob.pass_flags &= ~PASSTABLE - if(pushed_mob.loc != loc) //Something prevented the tabling - return - pushed_mob.Knockdown(30) - pushed_mob.apply_damage(10, BRUTE) - pushed_mob.apply_damage(40, STAMINA) - if(user.mind?.martial_art.smashes_tables && user.mind?.martial_art.can_use(user)) - deconstruct(FALSE) - playsound(pushed_mob, "sound/effects/tableslam.ogg", 90, TRUE) - pushed_mob.visible_message("[user] slams [pushed_mob] onto \the [src]!", \ - "[user] slams you onto \the [src]!") - log_combat(user, pushed_mob, "tabled", null, "onto [src]") - SEND_SIGNAL(pushed_mob, COMSIG_ADD_MOOD_EVENT, "table", /datum/mood_event/table) - -/obj/structure/table/proc/tableheadsmash(mob/living/user, mob/living/pushed_mob) - pushed_mob.Knockdown(30) - pushed_mob.apply_damage(30, BRUTE, BODY_ZONE_HEAD) - pushed_mob.apply_damage(40, STAMINA) - take_damage(50) - if(user.mind?.martial_art.smashes_tables && user.mind?.martial_art.can_use(user)) - deconstruct(FALSE) - playsound(pushed_mob, "sound/effects/tableheadsmash.ogg", 90, TRUE) - pushed_mob.visible_message("[user] smashes [pushed_mob]'s head against \the [src]!", - "[user] smashes your head against \the [src]") - log_combat(user, pushed_mob, "head slammed", null, "against [src]") - SEND_SIGNAL(pushed_mob, COMSIG_ADD_MOOD_EVENT, "table", /datum/mood_event/table_headsmash) - -/obj/structure/table/attackby(obj/item/I, mob/user, params) - if(!(flags_1 & NODECONSTRUCT_1) && user.a_intent != INTENT_HELP) - if(I.tool_behaviour == TOOL_SCREWDRIVER && deconstruction_ready) - to_chat(user, "You start disassembling [src]...") - if(I.use_tool(src, user, 20, volume=50)) - deconstruct(TRUE) - return - - if(I.tool_behaviour == TOOL_WRENCH && deconstruction_ready) - to_chat(user, "You start deconstructing [src]...") - if(I.use_tool(src, user, 40, volume=50)) - playsound(src.loc, 'sound/items/deconstruct.ogg', 50, TRUE) - deconstruct(TRUE, 1) - return - - if(istype(I, /obj/item/storage/bag/tray)) - var/obj/item/storage/bag/tray/T = I - if(T.contents.len > 0) // If the tray isn't empty - for(var/x in T.contents) - var/obj/item/item = x - AfterPutItemOnTable(item, user) - SEND_SIGNAL(I, COMSIG_TRY_STORAGE_QUICK_EMPTY, drop_location()) - user.visible_message("[user] empties [I] on [src].") - return - // If the tray IS empty, continue on (tray will be placed on the table like other items) - - if(istype(I, /obj/item/riding_offhand)) - var/obj/item/riding_offhand/riding_item = I - var/mob/living/carried_mob = riding_item.rider - if(carried_mob == user) //Piggyback user. - return - switch(user.a_intent) - if(INTENT_HARM) - user.unbuckle_mob(carried_mob) - tableheadsmash(user, carried_mob) - if(INTENT_HELP) - var/tableplace_delay = 3.5 SECONDS - var/skills_space = "" - if(HAS_TRAIT(user, TRAIT_QUICKER_CARRY)) - tableplace_delay = 2 SECONDS - skills_space = " expertly" - else if(HAS_TRAIT(user, TRAIT_QUICK_CARRY)) - tableplace_delay = 2.75 SECONDS - skills_space = " quickly" - carried_mob.visible_message("[user] begins to[skills_space] place [carried_mob] onto [src]...", - "[user] begins to[skills_space] place [carried_mob] onto [src]...") - if(do_after(user, tableplace_delay, target = carried_mob)) - user.unbuckle_mob(carried_mob) - tableplace(user, carried_mob) - else - user.unbuckle_mob(carried_mob) - tablepush(user, carried_mob) - return TRUE - - if(user.a_intent != INTENT_HARM && !(I.item_flags & ABSTRACT)) - if(user.transferItemToLoc(I, drop_location(), silent = FALSE)) - var/list/click_params = params2list(params) - //Center the icon where the user clicked. - if(!click_params || !click_params["icon-x"] || !click_params["icon-y"]) - return - //Clamp it so that the icon never moves more than 16 pixels in either direction (thus leaving the table turf) - I.pixel_x = clamp(text2num(click_params["icon-x"]) - 16, -(world.icon_size/2), world.icon_size/2) - I.pixel_y = clamp(text2num(click_params["icon-y"]) - 16, -(world.icon_size/2), world.icon_size/2) - AfterPutItemOnTable(I, user) - return TRUE - else - return ..() - -/obj/structure/table/proc/AfterPutItemOnTable(obj/item/I, mob/living/user) - return - -/obj/structure/table/deconstruct(disassembled = TRUE, wrench_disassembly = 0) - if(!(flags_1 & NODECONSTRUCT_1)) - var/turf/T = get_turf(src) - if(buildstack) - new buildstack(T, buildstackamount) - else - for(var/i in custom_materials) - var/datum/material/M = i - new M.sheet_type(T, FLOOR(custom_materials[M] / MINERAL_MATERIAL_AMOUNT, 1)) - if(!wrench_disassembly) - new frame(T) - else - new framestack(T, framestackamount) - qdel(src) - - -/obj/structure/table/greyscale - icon = 'icons/obj/smooth_structures/table_greyscale.dmi' - icon_state = "table" - material_flags = MATERIAL_ADD_PREFIX | MATERIAL_COLOR | MATERIAL_AFFECT_STATISTICS - buildstack = null //No buildstack, so generate from mat datums - -///Table on wheels -/obj/structure/table/rolling - name = "Rolling table" - desc = "A NT brand \"Rolly poly\" rolling table. It can and will move." - anchored = FALSE - smooth = SMOOTH_FALSE - canSmoothWith = list() - icon = 'icons/obj/smooth_structures/rollingtable.dmi' - icon_state = "rollingtable" - var/list/attached_items = list() - -/obj/structure/table/rolling/AfterPutItemOnTable(obj/item/I, mob/living/user) - . = ..() - attached_items += I - RegisterSignal(I, COMSIG_MOVABLE_MOVED, .proc/RemoveItemFromTable) //Listen for the pickup event, unregister on pick-up so we aren't moved - -/obj/structure/table/rolling/proc/RemoveItemFromTable(datum/source, newloc, dir) - if(newloc != loc) //Did we not move with the table? because that shit's ok - return FALSE - attached_items -= source - UnregisterSignal(source, COMSIG_MOVABLE_MOVED) - -/obj/structure/table/rolling/Moved(atom/OldLoc, Dir) - . = ..() - for(var/mob/M in OldLoc.contents)//Kidnap everyone on top - M.forceMove(loc) - for(var/x in attached_items) - var/atom/movable/AM = x - if(!AM.Move(loc)) - RemoveItemFromTable(AM, AM.loc) - -/* - * Glass tables - */ -/obj/structure/table/glass - name = "glass table" - desc = "What did I say about leaning on the glass tables? Now you need surgery." - icon = 'icons/obj/smooth_structures/glass_table.dmi' - icon_state = "glass_table" - buildstack = /obj/item/stack/sheet/glass - canSmoothWith = null - max_integrity = 70 - resistance_flags = ACID_PROOF - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100) - var/list/debris = list() - -/obj/structure/table/glass/Initialize() - . = ..() - debris += new frame - debris += new /obj/item/shard - -/obj/structure/table/glass/Destroy() - QDEL_LIST(debris) - . = ..() - -/obj/structure/table/glass/Crossed(atom/movable/AM) - . = ..() - if(flags_1 & NODECONSTRUCT_1) - return - if(!isliving(AM)) - return - // Don't break if they're just flying past - if(AM.throwing) - addtimer(CALLBACK(src, .proc/throw_check, AM), 5) - else - check_break(AM) - -/obj/structure/table/glass/proc/throw_check(mob/living/M) - if(M.loc == get_turf(src)) - check_break(M) - -/obj/structure/table/glass/proc/check_break(mob/living/M) - if(M.has_gravity() && M.mob_size > MOB_SIZE_SMALL && !(M.movement_type & FLYING)) - table_shatter(M) - -/obj/structure/table/glass/proc/table_shatter(mob/living/L) - visible_message("[src] breaks!", - "You hear breaking glass.") - var/turf/T = get_turf(src) - playsound(T, "shatter", 50, TRUE) - for(var/I in debris) - var/atom/movable/AM = I - AM.forceMove(T) - debris -= AM - if(istype(AM, /obj/item/shard)) - AM.throw_impact(L) - L.Paralyze(100) - qdel(src) - -/obj/structure/table/glass/deconstruct(disassembled = TRUE, wrench_disassembly = 0) - if(!(flags_1 & NODECONSTRUCT_1)) - if(disassembled) - ..() - return - else - var/turf/T = get_turf(src) - playsound(T, "shatter", 50, TRUE) - for(var/X in debris) - var/atom/movable/AM = X - AM.forceMove(T) - debris -= AM - qdel(src) - -/obj/structure/table/glass/narsie_act() - color = NARSIE_WINDOW_COLOUR - for(var/obj/item/shard/S in debris) - S.color = NARSIE_WINDOW_COLOUR - -/* - * Wooden tables - */ - -/obj/structure/table/wood - name = "wooden table" - desc = "Do not apply fire to this. Rumour says it burns easily." - icon = 'icons/obj/smooth_structures/wood_table.dmi' - icon_state = "wood_table" - frame = /obj/structure/table_frame/wood - framestack = /obj/item/stack/sheet/mineral/wood - buildstack = /obj/item/stack/sheet/mineral/wood - resistance_flags = FLAMMABLE - max_integrity = 70 - canSmoothWith = list(/obj/structure/table/wood, - /obj/structure/table/wood/poker, - /obj/structure/table/wood/bar) - -/obj/structure/table/wood/narsie_act(total_override = TRUE) - if(!total_override) - ..() - -/obj/structure/table/wood/poker //No specialties, Just a mapping object. - name = "gambling table" - desc = "A seedy table for seedy dealings in seedy places." - icon = 'icons/obj/smooth_structures/poker_table.dmi' - icon_state = "poker_table" - buildstack = /obj/item/stack/tile/carpet - -/obj/structure/table/wood/poker/narsie_act() - ..(FALSE) - -/obj/structure/table/wood/fancy - name = "fancy table" - desc = "A standard metal table frame covered with an amazingly fancy, patterned cloth." - icon = 'icons/obj/structures.dmi' - icon_state = "fancy_table" - frame = /obj/structure/table_frame - framestack = /obj/item/stack/rods - buildstack = /obj/item/stack/tile/carpet - canSmoothWith = list(/obj/structure/table/wood/fancy, - /obj/structure/table/wood/fancy/black, - /obj/structure/table/wood/fancy/blue, - /obj/structure/table/wood/fancy/cyan, - /obj/structure/table/wood/fancy/green, - /obj/structure/table/wood/fancy/orange, - /obj/structure/table/wood/fancy/purple, - /obj/structure/table/wood/fancy/red, - /obj/structure/table/wood/fancy/royalblack, - /obj/structure/table/wood/fancy/royalblue) - var/smooth_icon = 'icons/obj/smooth_structures/fancy_table.dmi' // see Initialize() - -/obj/structure/table/wood/fancy/Initialize() - . = ..() - // Needs to be set dynamically because table smooth sprites are 32x34, - // which the editor treats as a two-tile-tall object. The sprites are that - // size so that the north/south corners look nice - examine the detail on - // the sprites in the editor to see why. - icon = smooth_icon - -/obj/structure/table/wood/fancy/black - icon_state = "fancy_table_black" - buildstack = /obj/item/stack/tile/carpet/black - smooth_icon = 'icons/obj/smooth_structures/fancy_table_black.dmi' - -/obj/structure/table/wood/fancy/blue - icon_state = "fancy_table_blue" - buildstack = /obj/item/stack/tile/carpet/blue - smooth_icon = 'icons/obj/smooth_structures/fancy_table_blue.dmi' - -/obj/structure/table/wood/fancy/cyan - icon_state = "fancy_table_cyan" - buildstack = /obj/item/stack/tile/carpet/cyan - smooth_icon = 'icons/obj/smooth_structures/fancy_table_cyan.dmi' - -/obj/structure/table/wood/fancy/green - icon_state = "fancy_table_green" - buildstack = /obj/item/stack/tile/carpet/green - smooth_icon = 'icons/obj/smooth_structures/fancy_table_green.dmi' - -/obj/structure/table/wood/fancy/orange - icon_state = "fancy_table_orange" - buildstack = /obj/item/stack/tile/carpet/orange - smooth_icon = 'icons/obj/smooth_structures/fancy_table_orange.dmi' - -/obj/structure/table/wood/fancy/purple - icon_state = "fancy_table_purple" - buildstack = /obj/item/stack/tile/carpet/purple - smooth_icon = 'icons/obj/smooth_structures/fancy_table_purple.dmi' - -/obj/structure/table/wood/fancy/red - icon_state = "fancy_table_red" - buildstack = /obj/item/stack/tile/carpet/red - smooth_icon = 'icons/obj/smooth_structures/fancy_table_red.dmi' - -/obj/structure/table/wood/fancy/royalblack - icon_state = "fancy_table_royalblack" - buildstack = /obj/item/stack/tile/carpet/royalblack - smooth_icon = 'icons/obj/smooth_structures/fancy_table_royalblack.dmi' - -/obj/structure/table/wood/fancy/royalblue - icon_state = "fancy_table_royalblue" - buildstack = /obj/item/stack/tile/carpet/royalblue - smooth_icon = 'icons/obj/smooth_structures/fancy_table_royalblue.dmi' - -/* - * Reinforced tables - */ -/obj/structure/table/reinforced - name = "reinforced table" - desc = "A reinforced version of the four legged table." - icon = 'icons/obj/smooth_structures/reinforced_table.dmi' - icon_state = "r_table" - deconstruction_ready = 0 - buildstack = /obj/item/stack/sheet/plasteel - canSmoothWith = list(/obj/structure/table/reinforced, /obj/structure/table) - max_integrity = 200 - integrity_failure = 0.25 - armor = list("melee" = 10, "bullet" = 30, "laser" = 30, "energy" = 100, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 70) - -/obj/structure/table/reinforced/deconstruction_hints(mob/user) - if(deconstruction_ready) - return "The top cover has been welded loose and the main frame's bolts are exposed." - else - return "The top cover is firmly welded on." - -/obj/structure/table/reinforced/attackby(obj/item/W, mob/user, params) - if(W.tool_behaviour == TOOL_WELDER && user.a_intent != INTENT_HELP) - if(!W.tool_start_check(user, amount=0)) - return - - if(deconstruction_ready) - to_chat(user, "You start strengthening the reinforced table...") - if (W.use_tool(src, user, 50, volume=50)) - to_chat(user, "You strengthen the table.") - deconstruction_ready = 0 - else - to_chat(user, "You start weakening the reinforced table...") - if (W.use_tool(src, user, 50, volume=50)) - to_chat(user, "You weaken the table.") - deconstruction_ready = 1 - else - . = ..() - -/obj/structure/table/bronze - name = "bronze table" - desc = "A solid table made out of bronze." - icon = 'icons/obj/smooth_structures/brass_table.dmi' - icon_state = "brass_table" - resistance_flags = FIRE_PROOF | ACID_PROOF - buildstack = /obj/item/stack/tile/bronze - canSmoothWith = list(/obj/structure/table/bronze) - -/obj/structure/table/bronze/tablepush(mob/living/user, mob/living/pushed_mob) - ..() - playsound(src, 'sound/magic/clockwork/fellowship_armory.ogg', 50, TRUE) - -/* - * Surgery Tables - */ - -/obj/structure/table/optable - name = "operating table" - desc = "Used for advanced medical procedures." - icon = 'icons/obj/surgery.dmi' - icon_state = "optable" - buildstack = /obj/item/stack/sheet/mineral/silver - smooth = SMOOTH_FALSE - can_buckle = 1 - buckle_lying = -1 - buckle_requires_restraints = 1 - var/mob/living/carbon/human/patient = null - var/obj/machinery/computer/operating/computer = null - -/obj/structure/table/optable/Initialize() - . = ..() - for(var/direction in GLOB.alldirs) - computer = locate(/obj/machinery/computer/operating) in get_step(src, direction) - if(computer) - computer.table = src - break - -/obj/structure/table/optable/Destroy() - . = ..() - if(computer && computer.table == src) - computer.table = null - -/obj/structure/table/optable/tablepush(mob/living/user, mob/living/pushed_mob) - pushed_mob.forceMove(loc) - pushed_mob.set_resting(TRUE, TRUE) - visible_message("[user] lays [pushed_mob] on [src].") - get_patient() - -/obj/structure/table/optable/proc/get_patient() - var/mob/living/carbon/M = locate(/mob/living/carbon) in loc - if(M) - if(M.resting) - patient = M - else - patient = null - -/obj/structure/table/optable/proc/check_eligible_patient() - get_patient() - if(!patient) - return FALSE - if(ishuman(patient) || ismonkey(patient)) - return TRUE - return FALSE - -/* - * Racks - */ -/obj/structure/rack - name = "rack" - desc = "Different from the Middle Ages version." - icon = 'icons/obj/objects.dmi' - icon_state = "rack" - layer = TABLE_LAYER - density = TRUE - anchored = TRUE - pass_flags = LETPASSTHROW //You can throw objects over this, despite it's density. - max_integrity = 20 - -/obj/structure/rack/examine(mob/user) - . = ..() - . += "It's held together by a couple of bolts." - -/obj/structure/rack/CanAllowThrough(atom/movable/mover, turf/target) - . = ..() - if(.) - return - if(istype(mover) && (mover.pass_flags & PASSTABLE)) - return TRUE - -/obj/structure/rack/CanAStarPass(ID, dir, caller) - . = !density - if(ismovable(caller)) - var/atom/movable/mover = caller - . = . || (mover.pass_flags & PASSTABLE) - -/obj/structure/rack/MouseDrop_T(obj/O, mob/user) - . = ..() - if ((!( istype(O, /obj/item) ) || user.get_active_held_item() != O)) - return - if(!user.dropItemToGround(O)) - return - if(O.loc != src.loc) - step(O, get_dir(O, src)) - -/obj/structure/rack/attackby(obj/item/W, mob/user, params) - if (W.tool_behaviour == TOOL_WRENCH && !(flags_1&NODECONSTRUCT_1) && user.a_intent != INTENT_HELP) - W.play_tool_sound(src) - deconstruct(TRUE) - return - if(user.a_intent == INTENT_HARM) - return ..() - if(user.transferItemToLoc(W, drop_location())) - return 1 - -/obj/structure/rack/attack_paw(mob/living/user) - attack_hand(user) - -/obj/structure/rack/attack_hand(mob/living/user) - . = ..() - if(.) - return - if(!(user.mobility_flags & MOBILITY_STAND) || user.get_num_legs() < 2) - return - user.changeNext_move(CLICK_CD_MELEE) - user.do_attack_animation(src, ATTACK_EFFECT_KICK) - user.visible_message("[user] kicks [src].", null, null, COMBAT_MESSAGE_RANGE) - take_damage(rand(4,8), BRUTE, "melee", 1) - -/obj/structure/rack/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) - switch(damage_type) - if(BRUTE) - if(damage_amount) - playsound(loc, 'sound/items/dodgeball.ogg', 80, TRUE) - else - playsound(loc, 'sound/weapons/tap.ogg', 50, TRUE) - if(BURN) - playsound(loc, 'sound/items/welder.ogg', 40, TRUE) - -/* - * Rack destruction - */ - -/obj/structure/rack/deconstruct(disassembled = TRUE) - if(!(flags_1&NODECONSTRUCT_1)) - density = FALSE - var/obj/item/rack_parts/newparts = new(loc) - transfer_fingerprints_to(newparts) - qdel(src) - - -/* - * Rack Parts - */ - -/obj/item/rack_parts - name = "rack parts" - desc = "Parts of a rack." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "rack_parts" - flags_1 = CONDUCT_1 - custom_materials = list(/datum/material/iron=2000) - var/building = FALSE - -/obj/item/rack_parts/attackby(obj/item/W, mob/user, params) - if (W.tool_behaviour == TOOL_WRENCH) - new /obj/item/stack/sheet/metal(user.loc) - qdel(src) - else - . = ..() - -/obj/item/rack_parts/attack_self(mob/user) - if(building) - return - building = TRUE - to_chat(user, "You start constructing a rack...") - if(do_after(user, 50, target = user, progress=TRUE)) - if(!user.temporarilyRemoveItemFromInventory(src)) - return - var/obj/structure/rack/R = new /obj/structure/rack(user.loc) - user.visible_message("[user] assembles \a [R].\ - ", "You assemble \a [R].") - R.add_fingerprint(user) - qdel(src) - building = FALSE +/* Tables and Racks + * Contains: + * Tables + * Glass Tables + * Wooden Tables + * Reinforced Tables + * Racks + * Rack Parts + */ + +/* + * Tables + */ + +/obj/structure/table + name = "table" + desc = "A square piece of metal standing on four metal legs. It can not move." + icon = 'icons/obj/smooth_structures/table.dmi' + icon_state = "table" + density = TRUE + anchored = TRUE + layer = TABLE_LAYER + climbable = TRUE + pass_flags = LETPASSTHROW //You can throw objects over this, despite it's density.") + var/frame = /obj/structure/table_frame + var/framestack = /obj/item/stack/rods + var/buildstack = /obj/item/stack/sheet/metal + var/busy = FALSE + var/buildstackamount = 1 + var/framestackamount = 2 + var/deconstruction_ready = 1 + custom_materials = list(/datum/material/iron = 2000) + max_integrity = 100 + integrity_failure = 0.33 + smooth = SMOOTH_TRUE + canSmoothWith = list(/obj/structure/table, /obj/structure/table/reinforced, /obj/structure/table/greyscale) + +/obj/structure/table/examine(mob/user) + . = ..() + . += deconstruction_hints(user) + +/obj/structure/table/proc/deconstruction_hints(mob/user) + return "The top is screwed on, but the main bolts are also visible." + +/obj/structure/table/update_icon() + if(smooth) + queue_smooth(src) + queue_smooth_neighbors(src) + +/obj/structure/table/narsie_act() + var/atom/A = loc + qdel(src) + new /obj/structure/table/wood(A) + +/obj/structure/table/attack_paw(mob/user) + return attack_hand(user) + +/obj/structure/table/attack_hand(mob/living/user) + if(Adjacent(user) && user.pulling) + if(isliving(user.pulling)) + var/mob/living/pushed_mob = user.pulling + if(pushed_mob.buckled) + to_chat(user, "[pushed_mob] is buckled to [pushed_mob.buckled]!") + return + if(user.a_intent == INTENT_GRAB) + if(user.grab_state < GRAB_AGGRESSIVE) + to_chat(user, "You need a better grip to do that!") + return + if(user.grab_state >= GRAB_NECK) + tableheadsmash(user, pushed_mob) + else + tablepush(user, pushed_mob) + if(user.a_intent == INTENT_HELP) + pushed_mob.visible_message("[user] begins to place [pushed_mob] onto [src]...", \ + "[user] begins to place [pushed_mob] onto [src]...") + if(do_after(user, 35, target = pushed_mob)) + tableplace(user, pushed_mob) + else + return + user.stop_pulling() + else if(user.pulling.pass_flags & PASSTABLE) + user.Move_Pulled(src) + if (user.pulling.loc == loc) + user.visible_message("[user] places [user.pulling] onto [src].", + "You place [user.pulling] onto [src].") + user.stop_pulling() + return ..() + +/obj/structure/table/attack_tk() + return FALSE + +/obj/structure/table/CanAllowThrough(atom/movable/mover, turf/target) + . = ..() + + if(istype(mover) && (mover.pass_flags & PASSTABLE)) + return TRUE + if(mover.throwing) + return TRUE + if(locate(/obj/structure/table) in get_turf(mover)) + return TRUE + +/obj/structure/table/CanAStarPass(ID, dir, caller) + . = !density + if(ismovable(caller)) + var/atom/movable/mover = caller + . = . || (mover.pass_flags & PASSTABLE) + +/obj/structure/table/proc/tableplace(mob/living/user, mob/living/pushed_mob) + pushed_mob.forceMove(loc) + pushed_mob.set_resting(TRUE, TRUE) + pushed_mob.visible_message("[user] places [pushed_mob] onto [src].", \ + "[user] places [pushed_mob] onto [src].") + log_combat(user, pushed_mob, "places", null, "onto [src]") + +/obj/structure/table/proc/tablepush(mob/living/user, mob/living/pushed_mob) + if(HAS_TRAIT(user, TRAIT_PACIFISM)) + to_chat(user, "Throwing [pushed_mob] onto the table might hurt them!") + return + var/added_passtable = FALSE + if(!pushed_mob.pass_flags & PASSTABLE) + added_passtable = TRUE + pushed_mob.pass_flags |= PASSTABLE + pushed_mob.Move(src.loc) + if(added_passtable) + pushed_mob.pass_flags &= ~PASSTABLE + if(pushed_mob.loc != loc) //Something prevented the tabling + return + pushed_mob.Knockdown(30) + pushed_mob.apply_damage(10, BRUTE) + pushed_mob.apply_damage(40, STAMINA) + if(user.mind?.martial_art.smashes_tables && user.mind?.martial_art.can_use(user)) + deconstruct(FALSE) + playsound(pushed_mob, "sound/effects/tableslam.ogg", 90, TRUE) + pushed_mob.visible_message("[user] slams [pushed_mob] onto \the [src]!", \ + "[user] slams you onto \the [src]!") + log_combat(user, pushed_mob, "tabled", null, "onto [src]") + SEND_SIGNAL(pushed_mob, COMSIG_ADD_MOOD_EVENT, "table", /datum/mood_event/table) + +/obj/structure/table/proc/tableheadsmash(mob/living/user, mob/living/pushed_mob) + pushed_mob.Knockdown(30) + pushed_mob.apply_damage(30, BRUTE, BODY_ZONE_HEAD) + pushed_mob.apply_damage(40, STAMINA) + take_damage(50) + if(user.mind?.martial_art.smashes_tables && user.mind?.martial_art.can_use(user)) + deconstruct(FALSE) + playsound(pushed_mob, "sound/effects/tableheadsmash.ogg", 90, TRUE) + pushed_mob.visible_message("[user] smashes [pushed_mob]'s head against \the [src]!", + "[user] smashes your head against \the [src]") + log_combat(user, pushed_mob, "head slammed", null, "against [src]") + SEND_SIGNAL(pushed_mob, COMSIG_ADD_MOOD_EVENT, "table", /datum/mood_event/table_headsmash) + +/obj/structure/table/attackby(obj/item/I, mob/user, params) + if(!(flags_1 & NODECONSTRUCT_1) && user.a_intent != INTENT_HELP) + if(I.tool_behaviour == TOOL_SCREWDRIVER && deconstruction_ready) + to_chat(user, "You start disassembling [src]...") + if(I.use_tool(src, user, 20, volume=50)) + deconstruct(TRUE) + return + + if(I.tool_behaviour == TOOL_WRENCH && deconstruction_ready) + to_chat(user, "You start deconstructing [src]...") + if(I.use_tool(src, user, 40, volume=50)) + playsound(src.loc, 'sound/items/deconstruct.ogg', 50, TRUE) + deconstruct(TRUE, 1) + return + + if(istype(I, /obj/item/storage/bag/tray)) + var/obj/item/storage/bag/tray/T = I + if(T.contents.len > 0) // If the tray isn't empty + for(var/x in T.contents) + var/obj/item/item = x + AfterPutItemOnTable(item, user) + SEND_SIGNAL(I, COMSIG_TRY_STORAGE_QUICK_EMPTY, drop_location()) + user.visible_message("[user] empties [I] on [src].") + return + // If the tray IS empty, continue on (tray will be placed on the table like other items) + + if(istype(I, /obj/item/riding_offhand)) + var/obj/item/riding_offhand/riding_item = I + var/mob/living/carried_mob = riding_item.rider + if(carried_mob == user) //Piggyback user. + return + switch(user.a_intent) + if(INTENT_HARM) + user.unbuckle_mob(carried_mob) + tableheadsmash(user, carried_mob) + if(INTENT_HELP) + var/tableplace_delay = 3.5 SECONDS + var/skills_space = "" + if(HAS_TRAIT(user, TRAIT_QUICKER_CARRY)) + tableplace_delay = 2 SECONDS + skills_space = " expertly" + else if(HAS_TRAIT(user, TRAIT_QUICK_CARRY)) + tableplace_delay = 2.75 SECONDS + skills_space = " quickly" + carried_mob.visible_message("[user] begins to[skills_space] place [carried_mob] onto [src]...", + "[user] begins to[skills_space] place [carried_mob] onto [src]...") + if(do_after(user, tableplace_delay, target = carried_mob)) + user.unbuckle_mob(carried_mob) + tableplace(user, carried_mob) + else + user.unbuckle_mob(carried_mob) + tablepush(user, carried_mob) + return TRUE + + if(user.a_intent != INTENT_HARM && !(I.item_flags & ABSTRACT)) + if(user.transferItemToLoc(I, drop_location(), silent = FALSE)) + var/list/click_params = params2list(params) + //Center the icon where the user clicked. + if(!click_params || !click_params["icon-x"] || !click_params["icon-y"]) + return + //Clamp it so that the icon never moves more than 16 pixels in either direction (thus leaving the table turf) + I.pixel_x = clamp(text2num(click_params["icon-x"]) - 16, -(world.icon_size/2), world.icon_size/2) + I.pixel_y = clamp(text2num(click_params["icon-y"]) - 16, -(world.icon_size/2), world.icon_size/2) + AfterPutItemOnTable(I, user) + return TRUE + else + return ..() + +/obj/structure/table/proc/AfterPutItemOnTable(obj/item/I, mob/living/user) + return + +/obj/structure/table/deconstruct(disassembled = TRUE, wrench_disassembly = 0) + if(!(flags_1 & NODECONSTRUCT_1)) + var/turf/T = get_turf(src) + if(buildstack) + new buildstack(T, buildstackamount) + else + for(var/i in custom_materials) + var/datum/material/M = i + new M.sheet_type(T, FLOOR(custom_materials[M] / MINERAL_MATERIAL_AMOUNT, 1)) + if(!wrench_disassembly) + new frame(T) + else + new framestack(T, framestackamount) + qdel(src) + + +/obj/structure/table/greyscale + icon = 'icons/obj/smooth_structures/table_greyscale.dmi' + icon_state = "table" + material_flags = MATERIAL_ADD_PREFIX | MATERIAL_COLOR | MATERIAL_AFFECT_STATISTICS + buildstack = null //No buildstack, so generate from mat datums + +///Table on wheels +/obj/structure/table/rolling + name = "Rolling table" + desc = "A NT brand \"Rolly poly\" rolling table. It can and will move." + anchored = FALSE + smooth = SMOOTH_FALSE + canSmoothWith = list() + icon = 'icons/obj/smooth_structures/rollingtable.dmi' + icon_state = "rollingtable" + var/list/attached_items = list() + +/obj/structure/table/rolling/AfterPutItemOnTable(obj/item/I, mob/living/user) + . = ..() + attached_items += I + RegisterSignal(I, COMSIG_MOVABLE_MOVED, .proc/RemoveItemFromTable) //Listen for the pickup event, unregister on pick-up so we aren't moved + +/obj/structure/table/rolling/proc/RemoveItemFromTable(datum/source, newloc, dir) + if(newloc != loc) //Did we not move with the table? because that shit's ok + return FALSE + attached_items -= source + UnregisterSignal(source, COMSIG_MOVABLE_MOVED) + +/obj/structure/table/rolling/Moved(atom/OldLoc, Dir) + . = ..() + for(var/mob/M in OldLoc.contents)//Kidnap everyone on top + M.forceMove(loc) + for(var/x in attached_items) + var/atom/movable/AM = x + if(!AM.Move(loc)) + RemoveItemFromTable(AM, AM.loc) + +/* + * Glass tables + */ +/obj/structure/table/glass + name = "glass table" + desc = "What did I say about leaning on the glass tables? Now you need surgery." + icon = 'icons/obj/smooth_structures/glass_table.dmi' + icon_state = "glass_table" + buildstack = /obj/item/stack/sheet/glass + canSmoothWith = null + max_integrity = 70 + resistance_flags = ACID_PROOF + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100) + var/list/debris = list() + +/obj/structure/table/glass/Initialize() + . = ..() + debris += new frame + debris += new /obj/item/shard + +/obj/structure/table/glass/Destroy() + QDEL_LIST(debris) + . = ..() + +/obj/structure/table/glass/Crossed(atom/movable/AM) + . = ..() + if(flags_1 & NODECONSTRUCT_1) + return + if(!isliving(AM)) + return + // Don't break if they're just flying past + if(AM.throwing) + addtimer(CALLBACK(src, .proc/throw_check, AM), 5) + else + check_break(AM) + +/obj/structure/table/glass/proc/throw_check(mob/living/M) + if(M.loc == get_turf(src)) + check_break(M) + +/obj/structure/table/glass/proc/check_break(mob/living/M) + if(M.has_gravity() && M.mob_size > MOB_SIZE_SMALL && !(M.movement_type & FLYING)) + table_shatter(M) + +/obj/structure/table/glass/proc/table_shatter(mob/living/L) + visible_message("[src] breaks!", + "You hear breaking glass.") + var/turf/T = get_turf(src) + playsound(T, "shatter", 50, TRUE) + for(var/I in debris) + var/atom/movable/AM = I + AM.forceMove(T) + debris -= AM + if(istype(AM, /obj/item/shard)) + AM.throw_impact(L) + L.Paralyze(100) + qdel(src) + +/obj/structure/table/glass/deconstruct(disassembled = TRUE, wrench_disassembly = 0) + if(!(flags_1 & NODECONSTRUCT_1)) + if(disassembled) + ..() + return + else + var/turf/T = get_turf(src) + playsound(T, "shatter", 50, TRUE) + for(var/X in debris) + var/atom/movable/AM = X + AM.forceMove(T) + debris -= AM + qdel(src) + +/obj/structure/table/glass/narsie_act() + color = NARSIE_WINDOW_COLOUR + for(var/obj/item/shard/S in debris) + S.color = NARSIE_WINDOW_COLOUR + +/* + * Wooden tables + */ + +/obj/structure/table/wood + name = "wooden table" + desc = "Do not apply fire to this. Rumour says it burns easily." + icon = 'icons/obj/smooth_structures/wood_table.dmi' + icon_state = "wood_table" + frame = /obj/structure/table_frame/wood + framestack = /obj/item/stack/sheet/mineral/wood + buildstack = /obj/item/stack/sheet/mineral/wood + resistance_flags = FLAMMABLE + max_integrity = 70 + canSmoothWith = list(/obj/structure/table/wood, + /obj/structure/table/wood/poker, + /obj/structure/table/wood/bar) + +/obj/structure/table/wood/narsie_act(total_override = TRUE) + if(!total_override) + ..() + +/obj/structure/table/wood/poker //No specialties, Just a mapping object. + name = "gambling table" + desc = "A seedy table for seedy dealings in seedy places." + icon = 'icons/obj/smooth_structures/poker_table.dmi' + icon_state = "poker_table" + buildstack = /obj/item/stack/tile/carpet + +/obj/structure/table/wood/poker/narsie_act() + ..(FALSE) + +/obj/structure/table/wood/fancy + name = "fancy table" + desc = "A standard metal table frame covered with an amazingly fancy, patterned cloth." + icon = 'icons/obj/structures.dmi' + icon_state = "fancy_table" + frame = /obj/structure/table_frame + framestack = /obj/item/stack/rods + buildstack = /obj/item/stack/tile/carpet + canSmoothWith = list(/obj/structure/table/wood/fancy, + /obj/structure/table/wood/fancy/black, + /obj/structure/table/wood/fancy/blue, + /obj/structure/table/wood/fancy/cyan, + /obj/structure/table/wood/fancy/green, + /obj/structure/table/wood/fancy/orange, + /obj/structure/table/wood/fancy/purple, + /obj/structure/table/wood/fancy/red, + /obj/structure/table/wood/fancy/royalblack, + /obj/structure/table/wood/fancy/royalblue) + var/smooth_icon = 'icons/obj/smooth_structures/fancy_table.dmi' // see Initialize() + +/obj/structure/table/wood/fancy/Initialize() + . = ..() + // Needs to be set dynamically because table smooth sprites are 32x34, + // which the editor treats as a two-tile-tall object. The sprites are that + // size so that the north/south corners look nice - examine the detail on + // the sprites in the editor to see why. + icon = smooth_icon + +/obj/structure/table/wood/fancy/black + icon_state = "fancy_table_black" + buildstack = /obj/item/stack/tile/carpet/black + smooth_icon = 'icons/obj/smooth_structures/fancy_table_black.dmi' + +/obj/structure/table/wood/fancy/blue + icon_state = "fancy_table_blue" + buildstack = /obj/item/stack/tile/carpet/blue + smooth_icon = 'icons/obj/smooth_structures/fancy_table_blue.dmi' + +/obj/structure/table/wood/fancy/cyan + icon_state = "fancy_table_cyan" + buildstack = /obj/item/stack/tile/carpet/cyan + smooth_icon = 'icons/obj/smooth_structures/fancy_table_cyan.dmi' + +/obj/structure/table/wood/fancy/green + icon_state = "fancy_table_green" + buildstack = /obj/item/stack/tile/carpet/green + smooth_icon = 'icons/obj/smooth_structures/fancy_table_green.dmi' + +/obj/structure/table/wood/fancy/orange + icon_state = "fancy_table_orange" + buildstack = /obj/item/stack/tile/carpet/orange + smooth_icon = 'icons/obj/smooth_structures/fancy_table_orange.dmi' + +/obj/structure/table/wood/fancy/purple + icon_state = "fancy_table_purple" + buildstack = /obj/item/stack/tile/carpet/purple + smooth_icon = 'icons/obj/smooth_structures/fancy_table_purple.dmi' + +/obj/structure/table/wood/fancy/red + icon_state = "fancy_table_red" + buildstack = /obj/item/stack/tile/carpet/red + smooth_icon = 'icons/obj/smooth_structures/fancy_table_red.dmi' + +/obj/structure/table/wood/fancy/royalblack + icon_state = "fancy_table_royalblack" + buildstack = /obj/item/stack/tile/carpet/royalblack + smooth_icon = 'icons/obj/smooth_structures/fancy_table_royalblack.dmi' + +/obj/structure/table/wood/fancy/royalblue + icon_state = "fancy_table_royalblue" + buildstack = /obj/item/stack/tile/carpet/royalblue + smooth_icon = 'icons/obj/smooth_structures/fancy_table_royalblue.dmi' + +/* + * Reinforced tables + */ +/obj/structure/table/reinforced + name = "reinforced table" + desc = "A reinforced version of the four legged table." + icon = 'icons/obj/smooth_structures/reinforced_table.dmi' + icon_state = "r_table" + deconstruction_ready = 0 + buildstack = /obj/item/stack/sheet/plasteel + canSmoothWith = list(/obj/structure/table/reinforced, /obj/structure/table) + max_integrity = 200 + integrity_failure = 0.25 + armor = list("melee" = 10, "bullet" = 30, "laser" = 30, "energy" = 100, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 70) + +/obj/structure/table/reinforced/deconstruction_hints(mob/user) + if(deconstruction_ready) + return "The top cover has been welded loose and the main frame's bolts are exposed." + else + return "The top cover is firmly welded on." + +/obj/structure/table/reinforced/attackby(obj/item/W, mob/user, params) + if(W.tool_behaviour == TOOL_WELDER && user.a_intent != INTENT_HELP) + if(!W.tool_start_check(user, amount=0)) + return + + if(deconstruction_ready) + to_chat(user, "You start strengthening the reinforced table...") + if (W.use_tool(src, user, 50, volume=50)) + to_chat(user, "You strengthen the table.") + deconstruction_ready = 0 + else + to_chat(user, "You start weakening the reinforced table...") + if (W.use_tool(src, user, 50, volume=50)) + to_chat(user, "You weaken the table.") + deconstruction_ready = 1 + else + . = ..() + +/obj/structure/table/bronze + name = "bronze table" + desc = "A solid table made out of bronze." + icon = 'icons/obj/smooth_structures/brass_table.dmi' + icon_state = "brass_table" + resistance_flags = FIRE_PROOF | ACID_PROOF + buildstack = /obj/item/stack/tile/bronze + canSmoothWith = list(/obj/structure/table/bronze) + +/obj/structure/table/bronze/tablepush(mob/living/user, mob/living/pushed_mob) + ..() + playsound(src, 'sound/magic/clockwork/fellowship_armory.ogg', 50, TRUE) + +/* + * Surgery Tables + */ + +/obj/structure/table/optable + name = "operating table" + desc = "Used for advanced medical procedures." + icon = 'icons/obj/surgery.dmi' + icon_state = "optable" + buildstack = /obj/item/stack/sheet/mineral/silver + smooth = SMOOTH_FALSE + can_buckle = 1 + buckle_lying = -1 + buckle_requires_restraints = 1 + var/mob/living/carbon/human/patient = null + var/obj/machinery/computer/operating/computer = null + +/obj/structure/table/optable/Initialize() + . = ..() + for(var/direction in GLOB.alldirs) + computer = locate(/obj/machinery/computer/operating) in get_step(src, direction) + if(computer) + computer.table = src + break + +/obj/structure/table/optable/Destroy() + . = ..() + if(computer && computer.table == src) + computer.table = null + +/obj/structure/table/optable/tablepush(mob/living/user, mob/living/pushed_mob) + pushed_mob.forceMove(loc) + pushed_mob.set_resting(TRUE, TRUE) + visible_message("[user] lays [pushed_mob] on [src].") + get_patient() + +/obj/structure/table/optable/proc/get_patient() + var/mob/living/carbon/M = locate(/mob/living/carbon) in loc + if(M) + if(M.resting) + patient = M + else + patient = null + +/obj/structure/table/optable/proc/check_eligible_patient() + get_patient() + if(!patient) + return FALSE + if(ishuman(patient) || ismonkey(patient)) + return TRUE + return FALSE + +/* + * Racks + */ +/obj/structure/rack + name = "rack" + desc = "Different from the Middle Ages version." + icon = 'icons/obj/objects.dmi' + icon_state = "rack" + layer = TABLE_LAYER + density = TRUE + anchored = TRUE + pass_flags = LETPASSTHROW //You can throw objects over this, despite it's density. + max_integrity = 20 + +/obj/structure/rack/examine(mob/user) + . = ..() + . += "It's held together by a couple of bolts." + +/obj/structure/rack/CanAllowThrough(atom/movable/mover, turf/target) + . = ..() + if(.) + return + if(istype(mover) && (mover.pass_flags & PASSTABLE)) + return TRUE + +/obj/structure/rack/CanAStarPass(ID, dir, caller) + . = !density + if(ismovable(caller)) + var/atom/movable/mover = caller + . = . || (mover.pass_flags & PASSTABLE) + +/obj/structure/rack/MouseDrop_T(obj/O, mob/user) + . = ..() + if ((!( istype(O, /obj/item) ) || user.get_active_held_item() != O)) + return + if(!user.dropItemToGround(O)) + return + if(O.loc != src.loc) + step(O, get_dir(O, src)) + +/obj/structure/rack/attackby(obj/item/W, mob/user, params) + if (W.tool_behaviour == TOOL_WRENCH && !(flags_1&NODECONSTRUCT_1) && user.a_intent != INTENT_HELP) + W.play_tool_sound(src) + deconstruct(TRUE) + return + if(user.a_intent == INTENT_HARM) + return ..() + if(user.transferItemToLoc(W, drop_location())) + return 1 + +/obj/structure/rack/attack_paw(mob/living/user) + attack_hand(user) + +/obj/structure/rack/attack_hand(mob/living/user) + . = ..() + if(.) + return + if(!(user.mobility_flags & MOBILITY_STAND) || user.get_num_legs() < 2) + return + user.changeNext_move(CLICK_CD_MELEE) + user.do_attack_animation(src, ATTACK_EFFECT_KICK) + user.visible_message("[user] kicks [src].", null, null, COMBAT_MESSAGE_RANGE) + take_damage(rand(4,8), BRUTE, "melee", 1) + +/obj/structure/rack/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) + switch(damage_type) + if(BRUTE) + if(damage_amount) + playsound(loc, 'sound/items/dodgeball.ogg', 80, TRUE) + else + playsound(loc, 'sound/weapons/tap.ogg', 50, TRUE) + if(BURN) + playsound(loc, 'sound/items/welder.ogg', 40, TRUE) + +/* + * Rack destruction + */ + +/obj/structure/rack/deconstruct(disassembled = TRUE) + if(!(flags_1&NODECONSTRUCT_1)) + density = FALSE + var/obj/item/rack_parts/newparts = new(loc) + transfer_fingerprints_to(newparts) + qdel(src) + + +/* + * Rack Parts + */ + +/obj/item/rack_parts + name = "rack parts" + desc = "Parts of a rack." + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "rack_parts" + flags_1 = CONDUCT_1 + custom_materials = list(/datum/material/iron=2000) + var/building = FALSE + +/obj/item/rack_parts/attackby(obj/item/W, mob/user, params) + if (W.tool_behaviour == TOOL_WRENCH) + new /obj/item/stack/sheet/metal(user.loc) + qdel(src) + else + . = ..() + +/obj/item/rack_parts/attack_self(mob/user) + if(building) + return + building = TRUE + to_chat(user, "You start constructing a rack...") + if(do_after(user, 50, target = user, progress=TRUE)) + if(!user.temporarilyRemoveItemFromInventory(src)) + return + var/obj/structure/rack/R = new /obj/structure/rack(user.loc) + user.visible_message("[user] assembles \a [R].\ + ", "You assemble \a [R].") + R.add_fingerprint(user) + qdel(src) + building = FALSE diff --git a/code/game/objects/structures/tank_dispenser.dm b/code/game/objects/structures/tank_dispenser.dm index 1988510b7b80..6b5e24089d43 100644 --- a/code/game/objects/structures/tank_dispenser.dm +++ b/code/game/objects/structures/tank_dispenser.dm @@ -1,111 +1,113 @@ -#define TANK_DISPENSER_CAPACITY 10 - -/obj/structure/tank_dispenser - name = "tank dispenser" - desc = "A simple yet bulky storage device for gas tanks. Holds up to 10 oxygen tanks and 10 plasma tanks." - icon = 'icons/obj/objects.dmi' - icon_state = "dispenser" - density = TRUE - anchored = TRUE - max_integrity = 300 - var/oxygentanks = TANK_DISPENSER_CAPACITY - var/plasmatanks = TANK_DISPENSER_CAPACITY - -/obj/structure/tank_dispenser/oxygen - plasmatanks = 0 - -/obj/structure/tank_dispenser/plasma - oxygentanks = 0 - -/obj/structure/tank_dispenser/Initialize() - . = ..() - for(var/i in 1 to oxygentanks) - new /obj/item/tank/internals/oxygen(src) - for(var/i in 1 to plasmatanks) - new /obj/item/tank/internals/plasma(src) - update_icon() - -/obj/structure/tank_dispenser/update_overlays() - . = ..() - switch(oxygentanks) - if(1 to 3) - . += "oxygen-[oxygentanks]" - if(4 to TANK_DISPENSER_CAPACITY) - . += "oxygen-4" - switch(plasmatanks) - if(1 to 4) - . += "plasma-[plasmatanks]" - if(5 to TANK_DISPENSER_CAPACITY) - . += "plasma-5" - -/obj/structure/tank_dispenser/attackby(obj/item/I, mob/user, params) - var/full - if(istype(I, /obj/item/tank/internals/plasma)) - if(plasmatanks < TANK_DISPENSER_CAPACITY) - plasmatanks++ - else - full = TRUE - else if(istype(I, /obj/item/tank/internals/oxygen)) - if(oxygentanks < TANK_DISPENSER_CAPACITY) - oxygentanks++ - else - full = TRUE - else if(I.tool_behaviour == TOOL_WRENCH) - default_unfasten_wrench(user, I, time = 20) - return - else if(user.a_intent != INTENT_HARM) - to_chat(user, "[I] does not fit into [src].") - return - else - return ..() - if(full) - to_chat(user, "[src] can't hold any more of [I].") - return - - if(!user.transferItemToLoc(I, src)) - return - to_chat(user, "You put [I] in [src].") - update_icon() - -/obj/structure/tank_dispenser/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.physical_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "TankDispenser", name, 275, 103, master_ui, state) - ui.open() - -/obj/structure/tank_dispenser/ui_data(mob/user) - var/list/data = list() - data["oxygen"] = oxygentanks - data["plasma"] = plasmatanks - - return data - -/obj/structure/tank_dispenser/ui_act(action, params) - if(..()) - return - switch(action) - if("plasma") - var/obj/item/tank/internals/plasma/tank = locate() in src - if(tank && Adjacent(usr)) - usr.put_in_hands(tank) - plasmatanks-- - . = TRUE - if("oxygen") - var/obj/item/tank/internals/oxygen/tank = locate() in src - if(tank && Adjacent(usr)) - usr.put_in_hands(tank) - oxygentanks-- - . = TRUE - update_icon() - - -/obj/structure/tank_dispenser/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - for(var/X in src) - var/obj/item/I = X - I.forceMove(loc) - new /obj/item/stack/sheet/metal (loc, 2) - qdel(src) - -#undef TANK_DISPENSER_CAPACITY +#define TANK_DISPENSER_CAPACITY 10 + +/obj/structure/tank_dispenser + name = "tank dispenser" + desc = "A simple yet bulky storage device for gas tanks. Holds up to 10 oxygen tanks and 10 plasma tanks." + icon = 'icons/obj/objects.dmi' + icon_state = "dispenser" + density = TRUE + anchored = TRUE + max_integrity = 300 + var/oxygentanks = TANK_DISPENSER_CAPACITY + var/plasmatanks = TANK_DISPENSER_CAPACITY + +/obj/structure/tank_dispenser/oxygen + plasmatanks = 0 + +/obj/structure/tank_dispenser/plasma + oxygentanks = 0 + +/obj/structure/tank_dispenser/Initialize() + . = ..() + for(var/i in 1 to oxygentanks) + new /obj/item/tank/internals/oxygen(src) + for(var/i in 1 to plasmatanks) + new /obj/item/tank/internals/plasma(src) + update_icon() + +/obj/structure/tank_dispenser/update_overlays() + . = ..() + switch(oxygentanks) + if(1 to 3) + . += "oxygen-[oxygentanks]" + if(4 to TANK_DISPENSER_CAPACITY) + . += "oxygen-4" + switch(plasmatanks) + if(1 to 4) + . += "plasma-[plasmatanks]" + if(5 to TANK_DISPENSER_CAPACITY) + . += "plasma-5" + +/obj/structure/tank_dispenser/attackby(obj/item/I, mob/user, params) + var/full + if(istype(I, /obj/item/tank/internals/plasma)) + if(plasmatanks < TANK_DISPENSER_CAPACITY) + plasmatanks++ + else + full = TRUE + else if(istype(I, /obj/item/tank/internals/oxygen)) + if(oxygentanks < TANK_DISPENSER_CAPACITY) + oxygentanks++ + else + full = TRUE + else if(I.tool_behaviour == TOOL_WRENCH) + default_unfasten_wrench(user, I, time = 20) + return + else if(user.a_intent != INTENT_HARM) + to_chat(user, "[I] does not fit into [src].") + return + else + return ..() + if(full) + to_chat(user, "[src] can't hold any more of [I].") + return + + if(!user.transferItemToLoc(I, src)) + return + to_chat(user, "You put [I] in [src].") + update_icon() + +/obj/structure/tank_dispenser/ui_state(mob/user) + return GLOB.physical_state + +/obj/structure/tank_dispenser/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "TankDispenser", name) + ui.open() + +/obj/structure/tank_dispenser/ui_data(mob/user) + var/list/data = list() + data["oxygen"] = oxygentanks + data["plasma"] = plasmatanks + + return data + +/obj/structure/tank_dispenser/ui_act(action, params) + if(..()) + return + switch(action) + if("plasma") + var/obj/item/tank/internals/plasma/tank = locate() in src + if(tank && Adjacent(usr)) + usr.put_in_hands(tank) + plasmatanks-- + . = TRUE + if("oxygen") + var/obj/item/tank/internals/oxygen/tank = locate() in src + if(tank && Adjacent(usr)) + usr.put_in_hands(tank) + oxygentanks-- + . = TRUE + update_icon() + + +/obj/structure/tank_dispenser/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + for(var/X in src) + var/obj/item/I = X + I.forceMove(loc) + new /obj/item/stack/sheet/metal (loc, 2) + qdel(src) + +#undef TANK_DISPENSER_CAPACITY diff --git a/code/game/objects/structures/target_stake.dm b/code/game/objects/structures/target_stake.dm index 342d4f0c49d5..6f5d5463cfb8 100644 --- a/code/game/objects/structures/target_stake.dm +++ b/code/game/objects/structures/target_stake.dm @@ -1,76 +1,76 @@ -/obj/structure/target_stake - name = "target stake" - desc = "A thin platform with negatively-magnetized wheels." - icon = 'icons/obj/objects.dmi' - icon_state = "target_stake" - density = FALSE - flags_1 = CONDUCT_1 - can_buckle = TRUE - max_buckled_mobs = 1 - buckle_lying = FALSE - var/obj/item/target/pinned_target - -/obj/structure/target_stake/Destroy() - if(pinned_target) - pinned_target.nullPinnedLoc() - return ..() - -/obj/structure/target_stake/proc/handle_density() - if(length(buckled_mobs) || pinned_target) - density = TRUE - else - density = FALSE - -/obj/structure/target_stake/post_buckle_mob() - handle_density() - return ..() - -/obj/structure/target_stake/post_unbuckle_mob() - handle_density() - return ..() - -/obj/structure/target_stake/proc/nullPinnedTarget() - pinned_target = null - -/obj/structure/target_stake/Move() - . = ..() - if(pinned_target) - pinned_target.forceMove(loc) - -/obj/structure/target_stake/attackby(obj/item/target/T, mob/user) - if(pinned_target) - return - if(istype(T) && user.transferItemToLoc(T, drop_location())) - pinned_target = T - T.pinnedLoc = src - T.density = TRUE - T.layer = OBJ_LAYER + 0.01 - handle_density() - to_chat(user, "You slide the target into the stake.") - -/obj/structure/target_stake/attack_hand(mob/user) - . = ..() - if(.) - return - if(pinned_target) - removeTarget(user) - -/obj/structure/target_stake/proc/removeTarget(mob/user) - pinned_target.layer = OBJ_LAYER - pinned_target.forceMove(user.loc) - pinned_target.nullPinnedLoc() - nullPinnedTarget() - handle_density() - if(ishuman(user)) - if(!user.get_active_held_item()) - user.put_in_hands(pinned_target) - to_chat(user, "You take the target out of the stake.") - else - pinned_target.forceMove(user.drop_location()) - to_chat(user, "You take the target out of the stake.") - -/obj/structure/target_stake/bullet_act(obj/projectile/P) - if(pinned_target) - pinned_target.bullet_act(P) - else - . = ..() +/obj/structure/target_stake + name = "target stake" + desc = "A thin platform with negatively-magnetized wheels." + icon = 'icons/obj/objects.dmi' + icon_state = "target_stake" + density = FALSE + flags_1 = CONDUCT_1 + can_buckle = TRUE + max_buckled_mobs = 1 + buckle_lying = FALSE + var/obj/item/target/pinned_target + +/obj/structure/target_stake/Destroy() + if(pinned_target) + pinned_target.nullPinnedLoc() + return ..() + +/obj/structure/target_stake/proc/handle_density() + if(length(buckled_mobs) || pinned_target) + density = TRUE + else + density = FALSE + +/obj/structure/target_stake/post_buckle_mob() + handle_density() + return ..() + +/obj/structure/target_stake/post_unbuckle_mob() + handle_density() + return ..() + +/obj/structure/target_stake/proc/nullPinnedTarget() + pinned_target = null + +/obj/structure/target_stake/Move() + . = ..() + if(pinned_target) + pinned_target.forceMove(loc) + +/obj/structure/target_stake/attackby(obj/item/target/T, mob/user) + if(pinned_target) + return + if(istype(T) && user.transferItemToLoc(T, drop_location())) + pinned_target = T + T.pinnedLoc = src + T.density = TRUE + T.layer = OBJ_LAYER + 0.01 + handle_density() + to_chat(user, "You slide the target into the stake.") + +/obj/structure/target_stake/attack_hand(mob/user) + . = ..() + if(.) + return + if(pinned_target) + removeTarget(user) + +/obj/structure/target_stake/proc/removeTarget(mob/user) + pinned_target.layer = OBJ_LAYER + pinned_target.forceMove(user.loc) + pinned_target.nullPinnedLoc() + nullPinnedTarget() + handle_density() + if(ishuman(user)) + if(!user.get_active_held_item()) + user.put_in_hands(pinned_target) + to_chat(user, "You take the target out of the stake.") + else + pinned_target.forceMove(user.drop_location()) + to_chat(user, "You take the target out of the stake.") + +/obj/structure/target_stake/bullet_act(obj/projectile/P) + if(pinned_target) + pinned_target.bullet_act(P) + else + . = ..() diff --git a/code/game/objects/structures/transit_tubes/transit_tube_construction.dm b/code/game/objects/structures/transit_tubes/transit_tube_construction.dm index bd445d29d871..61a93dab7f14 100644 --- a/code/game/objects/structures/transit_tubes/transit_tube_construction.dm +++ b/code/game/objects/structures/transit_tubes/transit_tube_construction.dm @@ -1,165 +1,165 @@ -// transit tube construction - -// normal transit tubes -/obj/structure/c_transit_tube - name = "unattached transit tube" - icon = 'icons/obj/atmospherics/pipes/transit_tube.dmi' - icon_state = "straight" - desc = "An unattached segment of transit tube." - density = FALSE - layer = LOW_ITEM_LAYER //same as the built tube - anchored = FALSE - var/const/time_to_unwrench = 2 SECONDS - var/flipped = 0 - var/build_type = /obj/structure/transit_tube - var/flipped_build_type - var/base_icon - -/obj/structure/c_transit_tube/proc/can_wrench_in_loc(mob/user) - var/turf/source_turf = get_turf(loc) - var/existing_tubes = 0 - for(var/obj/structure/transit_tube/tube in source_turf) - existing_tubes +=1 - if(existing_tubes >= 2) - to_chat(user, "You cannot wrench any more transit tubes! ") - return FALSE - return TRUE - -/obj/structure/c_transit_tube/ComponentInitialize() - . = ..() - AddComponent(/datum/component/simple_rotation,ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_FLIP | ROTATION_VERBS,null,null,CALLBACK(src,.proc/after_rot)) - -/obj/structure/c_transit_tube/proc/after_rot(mob/user,rotation_type) - if(flipped_build_type && rotation_type == ROTATION_FLIP) - setDir(turn(dir,-180)) //Turn back we don't actually flip - flipped = !flipped - var/cur_flip = initial(flipped) ? !flipped : flipped - if(cur_flip) - build_type = flipped_build_type - else - build_type = initial(build_type) - icon_state = "[base_icon][flipped]" - -/obj/structure/c_transit_tube/wrench_act(mob/living/user, obj/item/I) - ..() - if(!can_wrench_in_loc(user)) - return - to_chat(user, "You start attaching the [name]...") - add_fingerprint(user) - if(I.use_tool(src, user, time_to_unwrench, volume=50, extra_checks=CALLBACK(src, .proc/can_wrench_in_loc, user))) - to_chat(user, "You attach the [name].") - var/obj/structure/transit_tube/R = new build_type(loc, dir) - transfer_fingerprints_to(R) - qdel(src) - return TRUE - -// transit tube station -/obj/structure/c_transit_tube/station - name = "unattached through station" - icon_state = "closed_station0" - build_type = /obj/structure/transit_tube/station - flipped_build_type = /obj/structure/transit_tube/station/flipped - base_icon = "closed_station" - -/obj/structure/c_transit_tube/station/flipped - icon_state = "closed_station1" - flipped = 1 - build_type = /obj/structure/transit_tube/station/flipped - flipped_build_type = /obj/structure/transit_tube/station - - -// reverser station, used for the terminus -/obj/structure/c_transit_tube/station/reverse - name = "unattached terminus station" - icon_state = "closed_terminus0" - build_type = /obj/structure/transit_tube/station/reverse - flipped_build_type = /obj/structure/transit_tube/station/reverse/flipped - base_icon = "closed_terminus" - -/obj/structure/c_transit_tube/station/reverse/flipped - icon_state = "closed_terminus1" - flipped = 1 - build_type = /obj/structure/transit_tube/station/reverse/flipped - flipped_build_type = /obj/structure/transit_tube/station/reverse - -//all the dispenser stations - -/obj/structure/c_transit_tube/station/dispenser - icon_state = "closed_dispenser0" - name = "unattached dispenser station" - build_type = /obj/structure/transit_tube/station/dispenser - flipped_build_type = /obj/structure/transit_tube/station/dispenser/flipped - -/obj/structure/c_transit_tube/station/dispenser/flipped - icon_state = "closed_station1" - flipped = 1 - build_type = /obj/structure/transit_tube/station/dispenser/flipped - flipped_build_type = /obj/structure/transit_tube/station/dispenser - -//and the ones that reverse - -/obj/structure/c_transit_tube/station/dispenser/reverse - name = "unattached terminus dispenser station" - icon_state = "closed_terminus0" - build_type = /obj/structure/transit_tube/station/dispenser/reverse - flipped_build_type = /obj/structure/transit_tube/station/dispenser/reverse/flipped - base_icon = "closed_terminus" - -/obj/structure/c_transit_tube/station/dispenser/reverse/flipped - icon_state = "closed_terminus1" - flipped = 1 - build_type = /obj/structure/transit_tube/station/dispenser/reverse/flipped - flipped_build_type = /obj/structure/transit_tube/station/dispenser/reverse - -//onto some special tube types - -/obj/structure/c_transit_tube/crossing - icon_state = "crossing" - build_type = /obj/structure/transit_tube/crossing - - -/obj/structure/c_transit_tube/diagonal - icon_state = "diagonal" - build_type = /obj/structure/transit_tube/diagonal - -/obj/structure/c_transit_tube/diagonal/crossing - icon_state = "diagonal_crossing" - build_type = /obj/structure/transit_tube/diagonal/crossing - - -/obj/structure/c_transit_tube/curved - icon_state = "curved0" - build_type = /obj/structure/transit_tube/curved - flipped_build_type = /obj/structure/transit_tube/curved/flipped - base_icon = "curved" - -/obj/structure/c_transit_tube/curved/flipped - icon_state = "curved1" - build_type = /obj/structure/transit_tube/curved/flipped - flipped_build_type = /obj/structure/transit_tube/curved - flipped = 1 - - -/obj/structure/c_transit_tube/junction - icon_state = "junction0" - build_type = /obj/structure/transit_tube/junction - flipped_build_type = /obj/structure/transit_tube/junction/flipped - base_icon = "junction" - - -/obj/structure/c_transit_tube/junction/flipped - icon_state = "junction1" - flipped = 1 - build_type = /obj/structure/transit_tube/junction/flipped - flipped_build_type = /obj/structure/transit_tube/junction - - -//transit tube pod -//see station.dm for the logic -/obj/structure/c_transit_tube_pod - name = "unattached transit tube pod" - icon = 'icons/obj/atmospherics/pipes/transit_tube.dmi' - icon_state = "pod" - desc = "Could probably be dragged into an open Transit Tube." - anchored = FALSE - density = FALSE +// transit tube construction + +// normal transit tubes +/obj/structure/c_transit_tube + name = "unattached transit tube" + icon = 'icons/obj/atmospherics/pipes/transit_tube.dmi' + icon_state = "straight" + desc = "An unattached segment of transit tube." + density = FALSE + layer = LOW_ITEM_LAYER //same as the built tube + anchored = FALSE + var/const/time_to_unwrench = 2 SECONDS + var/flipped = 0 + var/build_type = /obj/structure/transit_tube + var/flipped_build_type + var/base_icon + +/obj/structure/c_transit_tube/proc/can_wrench_in_loc(mob/user) + var/turf/source_turf = get_turf(loc) + var/existing_tubes = 0 + for(var/obj/structure/transit_tube/tube in source_turf) + existing_tubes +=1 + if(existing_tubes >= 2) + to_chat(user, "You cannot wrench any more transit tubes! ") + return FALSE + return TRUE + +/obj/structure/c_transit_tube/ComponentInitialize() + . = ..() + AddComponent(/datum/component/simple_rotation,ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_FLIP | ROTATION_VERBS,null,null,CALLBACK(src,.proc/after_rot)) + +/obj/structure/c_transit_tube/proc/after_rot(mob/user,rotation_type) + if(flipped_build_type && rotation_type == ROTATION_FLIP) + setDir(turn(dir,-180)) //Turn back we don't actually flip + flipped = !flipped + var/cur_flip = initial(flipped) ? !flipped : flipped + if(cur_flip) + build_type = flipped_build_type + else + build_type = initial(build_type) + icon_state = "[base_icon][flipped]" + +/obj/structure/c_transit_tube/wrench_act(mob/living/user, obj/item/I) + ..() + if(!can_wrench_in_loc(user)) + return + to_chat(user, "You start attaching the [name]...") + add_fingerprint(user) + if(I.use_tool(src, user, time_to_unwrench, volume=50, extra_checks=CALLBACK(src, .proc/can_wrench_in_loc, user))) + to_chat(user, "You attach the [name].") + var/obj/structure/transit_tube/R = new build_type(loc, dir) + transfer_fingerprints_to(R) + qdel(src) + return TRUE + +// transit tube station +/obj/structure/c_transit_tube/station + name = "unattached through station" + icon_state = "closed_station0" + build_type = /obj/structure/transit_tube/station + flipped_build_type = /obj/structure/transit_tube/station/flipped + base_icon = "closed_station" + +/obj/structure/c_transit_tube/station/flipped + icon_state = "closed_station1" + flipped = 1 + build_type = /obj/structure/transit_tube/station/flipped + flipped_build_type = /obj/structure/transit_tube/station + + +// reverser station, used for the terminus +/obj/structure/c_transit_tube/station/reverse + name = "unattached terminus station" + icon_state = "closed_terminus0" + build_type = /obj/structure/transit_tube/station/reverse + flipped_build_type = /obj/structure/transit_tube/station/reverse/flipped + base_icon = "closed_terminus" + +/obj/structure/c_transit_tube/station/reverse/flipped + icon_state = "closed_terminus1" + flipped = 1 + build_type = /obj/structure/transit_tube/station/reverse/flipped + flipped_build_type = /obj/structure/transit_tube/station/reverse + +//all the dispenser stations + +/obj/structure/c_transit_tube/station/dispenser + icon_state = "closed_dispenser0" + name = "unattached dispenser station" + build_type = /obj/structure/transit_tube/station/dispenser + flipped_build_type = /obj/structure/transit_tube/station/dispenser/flipped + +/obj/structure/c_transit_tube/station/dispenser/flipped + icon_state = "closed_station1" + flipped = 1 + build_type = /obj/structure/transit_tube/station/dispenser/flipped + flipped_build_type = /obj/structure/transit_tube/station/dispenser + +//and the ones that reverse + +/obj/structure/c_transit_tube/station/dispenser/reverse + name = "unattached terminus dispenser station" + icon_state = "closed_terminus0" + build_type = /obj/structure/transit_tube/station/dispenser/reverse + flipped_build_type = /obj/structure/transit_tube/station/dispenser/reverse/flipped + base_icon = "closed_terminus" + +/obj/structure/c_transit_tube/station/dispenser/reverse/flipped + icon_state = "closed_terminus1" + flipped = 1 + build_type = /obj/structure/transit_tube/station/dispenser/reverse/flipped + flipped_build_type = /obj/structure/transit_tube/station/dispenser/reverse + +//onto some special tube types + +/obj/structure/c_transit_tube/crossing + icon_state = "crossing" + build_type = /obj/structure/transit_tube/crossing + + +/obj/structure/c_transit_tube/diagonal + icon_state = "diagonal" + build_type = /obj/structure/transit_tube/diagonal + +/obj/structure/c_transit_tube/diagonal/crossing + icon_state = "diagonal_crossing" + build_type = /obj/structure/transit_tube/diagonal/crossing + + +/obj/structure/c_transit_tube/curved + icon_state = "curved0" + build_type = /obj/structure/transit_tube/curved + flipped_build_type = /obj/structure/transit_tube/curved/flipped + base_icon = "curved" + +/obj/structure/c_transit_tube/curved/flipped + icon_state = "curved1" + build_type = /obj/structure/transit_tube/curved/flipped + flipped_build_type = /obj/structure/transit_tube/curved + flipped = 1 + + +/obj/structure/c_transit_tube/junction + icon_state = "junction0" + build_type = /obj/structure/transit_tube/junction + flipped_build_type = /obj/structure/transit_tube/junction/flipped + base_icon = "junction" + + +/obj/structure/c_transit_tube/junction/flipped + icon_state = "junction1" + flipped = 1 + build_type = /obj/structure/transit_tube/junction/flipped + flipped_build_type = /obj/structure/transit_tube/junction + + +//transit tube pod +//see station.dm for the logic +/obj/structure/c_transit_tube_pod + name = "unattached transit tube pod" + icon = 'icons/obj/atmospherics/pipes/transit_tube.dmi' + icon_state = "pod" + desc = "Could probably be dragged into an open Transit Tube." + anchored = FALSE + density = FALSE diff --git a/code/game/objects/structures/votingbox.dm b/code/game/objects/structures/votingbox.dm index 6e33f024355d..403ad9e1e638 100644 --- a/code/game/objects/structures/votingbox.dm +++ b/code/game/objects/structures/votingbox.dm @@ -35,7 +35,7 @@ ..() ui_interact(user) -/obj/structure/votebox/ui_interact(mob/user, ui_key, datum/tgui/ui, force_open, datum/tgui/master_ui, datum/ui_state/state) +/obj/structure/votebox/ui_interact(mob/user) . = ..() var/list/dat = list() diff --git a/code/game/objects/structures/windoor_assembly.dm b/code/game/objects/structures/windoor_assembly.dm index 666ea864260e..3739cb7aaf52 100644 --- a/code/game/objects/structures/windoor_assembly.dm +++ b/code/game/objects/structures/windoor_assembly.dm @@ -1,357 +1,357 @@ -/* Windoor (window door) assembly -Nodrak - * Step 1: Create a windoor out of rglass - * Step 2: Add r-glass to the assembly to make a secure windoor (Optional) - * Step 3: Rotate or Flip the assembly to face and open the way you want - * Step 4: Wrench the assembly in place - * Step 5: Add cables to the assembly - * Step 6: Set access for the door. - * Step 7: Screwdriver the door to complete - */ - - -/obj/structure/windoor_assembly - icon = 'icons/obj/doors/windoor.dmi' - - name = "windoor Assembly" - icon_state = "l_windoor_assembly01" - desc = "A small glass and wire assembly for windoors." - anchored = FALSE - density = FALSE - dir = NORTH - - var/ini_dir - var/obj/item/electronics/airlock/electronics = null - var/created_name = null - - //Vars to help with the icon's name - var/facing = "l" //Does the windoor open to the left or right? - var/secure = FALSE //Whether or not this creates a secure windoor - var/state = "01" //How far the door assembly has progressed - CanAtmosPass = ATMOS_PASS_PROC - -/obj/structure/windoor_assembly/New(loc, set_dir) - ..() - if(set_dir) - setDir(set_dir) - ini_dir = dir - air_update_turf(1) - -/obj/structure/windoor_assembly/Destroy() - density = FALSE - air_update_turf(1) - return ..() - -/obj/structure/windoor_assembly/Move() - var/turf/T = loc - . = ..() - setDir(ini_dir) - move_update_air(T) - -/obj/structure/windoor_assembly/update_icon_state() - icon_state = "[facing]_[secure ? "secure_" : ""]windoor_assembly[state]" - -/obj/structure/windoor_assembly/CanAllowThrough(atom/movable/mover, turf/target) - . = ..() - if(istype(mover) && (mover.pass_flags & PASSGLASS)) - return TRUE - if(get_dir(loc, target) == dir) //Make sure looking at appropriate border - return - if(istype(mover, /obj/structure/window)) - var/obj/structure/window/W = mover - if(!valid_window_location(loc, W.ini_dir)) - return FALSE - else if(istype(mover, /obj/structure/windoor_assembly)) - var/obj/structure/windoor_assembly/W = mover - if(!valid_window_location(loc, W.ini_dir)) - return FALSE - else if(istype(mover, /obj/machinery/door/window) && !valid_window_location(loc, mover.dir)) - return FALSE - -/obj/structure/windoor_assembly/CanAtmosPass(turf/T) - if(get_dir(loc, T) == dir) - return !density - else - return 1 - -/obj/structure/windoor_assembly/CheckExit(atom/movable/mover as mob|obj, turf/target) - if(istype(mover) && (mover.pass_flags & PASSGLASS)) - return 1 - if(get_dir(loc, target) == dir) - return !density - else - return 1 - - -/obj/structure/windoor_assembly/attackby(obj/item/W, mob/user, params) - //I really should have spread this out across more states but thin little windoors are hard to sprite. - add_fingerprint(user) - switch(state) - if("01") - if(W.tool_behaviour == TOOL_WELDER && !anchored) - if(!W.tool_start_check(user, amount=0)) - return - - user.visible_message("[user] disassembles the windoor assembly.", - "You start to disassemble the windoor assembly...") - - if(W.use_tool(src, user, 40, volume=50)) - to_chat(user, "You disassemble the windoor assembly.") - var/obj/item/stack/sheet/rglass/RG = new (get_turf(src), 5) - RG.add_fingerprint(user) - if(secure) - var/obj/item/stack/rods/R = new (get_turf(src), 4) - R.add_fingerprint(user) - qdel(src) - return - - //Wrenching an unsecure assembly anchors it in place. Step 4 complete - if(W.tool_behaviour == TOOL_WRENCH && !anchored) - for(var/obj/machinery/door/window/WD in loc) - if(WD.dir == dir) - to_chat(user, "There is already a windoor in that location!") - return - user.visible_message("[user] secures the windoor assembly to the floor.", - "You start to secure the windoor assembly to the floor...") - - if(W.use_tool(src, user, 40, volume=100)) - if(anchored) - return - for(var/obj/machinery/door/window/WD in loc) - if(WD.dir == dir) - to_chat(user, "There is already a windoor in that location!") - return - to_chat(user, "You secure the windoor assembly.") - setAnchored(TRUE) - if(secure) - name = "secure anchored windoor assembly" - else - name = "anchored windoor assembly" - - //Unwrenching an unsecure assembly un-anchors it. Step 4 undone - else if(W.tool_behaviour == TOOL_WRENCH && anchored) - user.visible_message("[user] unsecures the windoor assembly to the floor.", - "You start to unsecure the windoor assembly to the floor...") - - if(W.use_tool(src, user, 40, volume=100)) - if(!anchored) - return - to_chat(user, "You unsecure the windoor assembly.") - setAnchored(FALSE) - if(secure) - name = "secure windoor assembly" - else - name = "windoor assembly" - - //Adding plasteel makes the assembly a secure windoor assembly. Step 2 (optional) complete. - else if(istype(W, /obj/item/stack/sheet/plasteel) && !secure) - var/obj/item/stack/sheet/plasteel/P = W - if(P.get_amount() < 2) - to_chat(user, "You need more plasteel to do this!") - return - to_chat(user, "You start to reinforce the windoor with plasteel...") - - if(do_after(user,40, target = src)) - if(!src || secure || P.get_amount() < 2) - return - - P.use(2) - to_chat(user, "You reinforce the windoor.") - secure = TRUE - if(anchored) - name = "secure anchored windoor assembly" - else - name = "secure windoor assembly" - - //Adding cable to the assembly. Step 5 complete. - else if(istype(W, /obj/item/stack/cable_coil) && anchored) - user.visible_message("[user] wires the windoor assembly.", "You start to wire the windoor assembly...") - - if(do_after(user, 40, target = src)) - if(!src || !anchored || src.state != "01") - return - var/obj/item/stack/cable_coil/CC = W - if(!CC.use(1)) - to_chat(user, "You need more cable to do this!") - return - to_chat(user, "You wire the windoor.") - state = "02" - if(secure) - name = "secure wired windoor assembly" - else - name = "wired windoor assembly" - else - return ..() - - if("02") - - //Removing wire from the assembly. Step 5 undone. - if(W.tool_behaviour == TOOL_WIRECUTTER) - user.visible_message("[user] cuts the wires from the airlock assembly.", "You start to cut the wires from airlock assembly...") - - if(W.use_tool(src, user, 40, volume=100)) - if(state != "02") - return - - to_chat(user, "You cut the windoor wires.") - new/obj/item/stack/cable_coil(get_turf(user), 1) - state = "01" - if(secure) - name = "secure anchored windoor assembly" - else - name = "anchored windoor assembly" - - //Adding airlock electronics for access. Step 6 complete. - else if(istype(W, /obj/item/electronics/airlock)) - if(!user.transferItemToLoc(W, src)) - return - W.play_tool_sound(src, 100) - user.visible_message("[user] installs the electronics into the airlock assembly.", - "You start to install electronics into the airlock assembly...") - - if(do_after(user, 40, target = src)) - if(!src || electronics) - W.forceMove(drop_location()) - return - to_chat(user, "You install the airlock electronics.") - name = "near finished windoor assembly" - electronics = W - else - W.forceMove(drop_location()) - - //Screwdriver to remove airlock electronics. Step 6 undone. - else if(W.tool_behaviour == TOOL_SCREWDRIVER) - if(!electronics) - return - - user.visible_message("[user] removes the electronics from the airlock assembly.", - "You start to uninstall electronics from the airlock assembly...") - - if(W.use_tool(src, user, 40, volume=100) && electronics) - to_chat(user, "You remove the airlock electronics.") - name = "wired windoor assembly" - var/obj/item/electronics/airlock/ae - ae = electronics - electronics = null - ae.forceMove(drop_location()) - - else if(istype(W, /obj/item/pen)) - var/t = stripped_input(user, "Enter the name for the door.", name, created_name,MAX_NAME_LEN) - if(!t) - return - if(!in_range(src, usr) && loc != usr) - return - created_name = t - return - - - - //Crowbar to complete the assembly, Step 7 complete. - else if(W.tool_behaviour == TOOL_CROWBAR) - if(!electronics) - to_chat(usr, "The assembly is missing electronics!") - return - user << browse(null, "window=windoor_access") - user.visible_message("[user] pries the windoor into the frame.", - "You start prying the windoor into the frame...") - - if(W.use_tool(src, user, 40, volume=100) && electronics) - - density = TRUE //Shouldn't matter but just incase - to_chat(user, "You finish the windoor.") - - if(secure) - var/obj/machinery/door/window/brigdoor/windoor = new /obj/machinery/door/window/brigdoor(loc) - if(facing == "l") - windoor.icon_state = "leftsecureopen" - windoor.base_state = "leftsecure" - else - windoor.icon_state = "rightsecureopen" - windoor.base_state = "rightsecure" - windoor.setDir(dir) - windoor.density = FALSE - - if(electronics.one_access) - windoor.req_one_access = electronics.accesses - else - windoor.req_access = electronics.accesses - windoor.electronics = electronics - electronics.forceMove(windoor) - if(created_name) - windoor.name = created_name - qdel(src) - windoor.close() - - - else - var/obj/machinery/door/window/windoor = new /obj/machinery/door/window(loc) - if(facing == "l") - windoor.icon_state = "leftopen" - windoor.base_state = "left" - else - windoor.icon_state = "rightopen" - windoor.base_state = "right" - windoor.setDir(dir) - windoor.density = FALSE - - if(electronics.one_access) - windoor.req_one_access = electronics.accesses - else - windoor.req_access = electronics.accesses - windoor.electronics = electronics - electronics.loc = windoor - if(created_name) - windoor.name = created_name - qdel(src) - windoor.close() - - - else - return ..() - - //Update to reflect changes(if applicable) - update_icon() - - - -/obj/structure/windoor_assembly/ComponentInitialize() - . = ..() - var/static/rotation_flags = ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS - AddComponent(/datum/component/simple_rotation, rotation_flags, can_be_rotated=CALLBACK(src, .proc/can_be_rotated), after_rotation=CALLBACK(src,.proc/after_rotation)) - -/obj/structure/windoor_assembly/proc/can_be_rotated(mob/user,rotation_type) - if(anchored) - to_chat(user, "[src] cannot be rotated while it is fastened to the floor!") - return FALSE - var/target_dir = turn(dir, rotation_type == ROTATION_CLOCKWISE ? -90 : 90) - - if(!valid_window_location(loc, target_dir)) - to_chat(user, "[src] cannot be rotated in that direction!") - return FALSE - return TRUE - -/obj/structure/windoor_assembly/proc/after_rotation(mob/user) - ini_dir = dir - update_icon() - -//Flips the windoor assembly, determines whather the door opens to the left or the right -/obj/structure/windoor_assembly/verb/flip() - set name = "Flip Windoor Assembly" - set category = "Object" - set src in oview(1) - if(usr.stat || usr.restrained()) - return - - if(isliving(usr)) - var/mob/living/L = usr - if(!(L.mobility_flags & MOBILITY_USE)) - return - - if(facing == "l") - to_chat(usr, "The windoor will now slide to the right.") - facing = "r" - else - facing = "l" - to_chat(usr, "The windoor will now slide to the left.") - - update_icon() - return +/* Windoor (window door) assembly -Nodrak + * Step 1: Create a windoor out of rglass + * Step 2: Add r-glass to the assembly to make a secure windoor (Optional) + * Step 3: Rotate or Flip the assembly to face and open the way you want + * Step 4: Wrench the assembly in place + * Step 5: Add cables to the assembly + * Step 6: Set access for the door. + * Step 7: Screwdriver the door to complete + */ + + +/obj/structure/windoor_assembly + icon = 'icons/obj/doors/windoor.dmi' + + name = "windoor Assembly" + icon_state = "l_windoor_assembly01" + desc = "A small glass and wire assembly for windoors." + anchored = FALSE + density = FALSE + dir = NORTH + + var/ini_dir + var/obj/item/electronics/airlock/electronics = null + var/created_name = null + + //Vars to help with the icon's name + var/facing = "l" //Does the windoor open to the left or right? + var/secure = FALSE //Whether or not this creates a secure windoor + var/state = "01" //How far the door assembly has progressed + CanAtmosPass = ATMOS_PASS_PROC + +/obj/structure/windoor_assembly/New(loc, set_dir) + ..() + if(set_dir) + setDir(set_dir) + ini_dir = dir + air_update_turf(1) + +/obj/structure/windoor_assembly/Destroy() + density = FALSE + air_update_turf(1) + return ..() + +/obj/structure/windoor_assembly/Move() + var/turf/T = loc + . = ..() + setDir(ini_dir) + move_update_air(T) + +/obj/structure/windoor_assembly/update_icon_state() + icon_state = "[facing]_[secure ? "secure_" : ""]windoor_assembly[state]" + +/obj/structure/windoor_assembly/CanAllowThrough(atom/movable/mover, turf/target) + . = ..() + if(istype(mover) && (mover.pass_flags & PASSGLASS)) + return TRUE + if(get_dir(loc, target) == dir) //Make sure looking at appropriate border + return + if(istype(mover, /obj/structure/window)) + var/obj/structure/window/W = mover + if(!valid_window_location(loc, W.ini_dir)) + return FALSE + else if(istype(mover, /obj/structure/windoor_assembly)) + var/obj/structure/windoor_assembly/W = mover + if(!valid_window_location(loc, W.ini_dir)) + return FALSE + else if(istype(mover, /obj/machinery/door/window) && !valid_window_location(loc, mover.dir)) + return FALSE + +/obj/structure/windoor_assembly/CanAtmosPass(turf/T) + if(get_dir(loc, T) == dir) + return !density + else + return 1 + +/obj/structure/windoor_assembly/CheckExit(atom/movable/mover as mob|obj, turf/target) + if(istype(mover) && (mover.pass_flags & PASSGLASS)) + return 1 + if(get_dir(loc, target) == dir) + return !density + else + return 1 + + +/obj/structure/windoor_assembly/attackby(obj/item/W, mob/user, params) + //I really should have spread this out across more states but thin little windoors are hard to sprite. + add_fingerprint(user) + switch(state) + if("01") + if(W.tool_behaviour == TOOL_WELDER && !anchored) + if(!W.tool_start_check(user, amount=0)) + return + + user.visible_message("[user] disassembles the windoor assembly.", + "You start to disassemble the windoor assembly...") + + if(W.use_tool(src, user, 40, volume=50)) + to_chat(user, "You disassemble the windoor assembly.") + var/obj/item/stack/sheet/rglass/RG = new (get_turf(src), 5) + RG.add_fingerprint(user) + if(secure) + var/obj/item/stack/rods/R = new (get_turf(src), 4) + R.add_fingerprint(user) + qdel(src) + return + + //Wrenching an unsecure assembly anchors it in place. Step 4 complete + if(W.tool_behaviour == TOOL_WRENCH && !anchored) + for(var/obj/machinery/door/window/WD in loc) + if(WD.dir == dir) + to_chat(user, "There is already a windoor in that location!") + return + user.visible_message("[user] secures the windoor assembly to the floor.", + "You start to secure the windoor assembly to the floor...") + + if(W.use_tool(src, user, 40, volume=100)) + if(anchored) + return + for(var/obj/machinery/door/window/WD in loc) + if(WD.dir == dir) + to_chat(user, "There is already a windoor in that location!") + return + to_chat(user, "You secure the windoor assembly.") + setAnchored(TRUE) + if(secure) + name = "secure anchored windoor assembly" + else + name = "anchored windoor assembly" + + //Unwrenching an unsecure assembly un-anchors it. Step 4 undone + else if(W.tool_behaviour == TOOL_WRENCH && anchored) + user.visible_message("[user] unsecures the windoor assembly to the floor.", + "You start to unsecure the windoor assembly to the floor...") + + if(W.use_tool(src, user, 40, volume=100)) + if(!anchored) + return + to_chat(user, "You unsecure the windoor assembly.") + setAnchored(FALSE) + if(secure) + name = "secure windoor assembly" + else + name = "windoor assembly" + + //Adding plasteel makes the assembly a secure windoor assembly. Step 2 (optional) complete. + else if(istype(W, /obj/item/stack/sheet/plasteel) && !secure) + var/obj/item/stack/sheet/plasteel/P = W + if(P.get_amount() < 2) + to_chat(user, "You need more plasteel to do this!") + return + to_chat(user, "You start to reinforce the windoor with plasteel...") + + if(do_after(user,40, target = src)) + if(!src || secure || P.get_amount() < 2) + return + + P.use(2) + to_chat(user, "You reinforce the windoor.") + secure = TRUE + if(anchored) + name = "secure anchored windoor assembly" + else + name = "secure windoor assembly" + + //Adding cable to the assembly. Step 5 complete. + else if(istype(W, /obj/item/stack/cable_coil) && anchored) + user.visible_message("[user] wires the windoor assembly.", "You start to wire the windoor assembly...") + + if(do_after(user, 40, target = src)) + if(!src || !anchored || src.state != "01") + return + var/obj/item/stack/cable_coil/CC = W + if(!CC.use(1)) + to_chat(user, "You need more cable to do this!") + return + to_chat(user, "You wire the windoor.") + state = "02" + if(secure) + name = "secure wired windoor assembly" + else + name = "wired windoor assembly" + else + return ..() + + if("02") + + //Removing wire from the assembly. Step 5 undone. + if(W.tool_behaviour == TOOL_WIRECUTTER) + user.visible_message("[user] cuts the wires from the airlock assembly.", "You start to cut the wires from airlock assembly...") + + if(W.use_tool(src, user, 40, volume=100)) + if(state != "02") + return + + to_chat(user, "You cut the windoor wires.") + new/obj/item/stack/cable_coil(get_turf(user), 1) + state = "01" + if(secure) + name = "secure anchored windoor assembly" + else + name = "anchored windoor assembly" + + //Adding airlock electronics for access. Step 6 complete. + else if(istype(W, /obj/item/electronics/airlock)) + if(!user.transferItemToLoc(W, src)) + return + W.play_tool_sound(src, 100) + user.visible_message("[user] installs the electronics into the airlock assembly.", + "You start to install electronics into the airlock assembly...") + + if(do_after(user, 40, target = src)) + if(!src || electronics) + W.forceMove(drop_location()) + return + to_chat(user, "You install the airlock electronics.") + name = "near finished windoor assembly" + electronics = W + else + W.forceMove(drop_location()) + + //Screwdriver to remove airlock electronics. Step 6 undone. + else if(W.tool_behaviour == TOOL_SCREWDRIVER) + if(!electronics) + return + + user.visible_message("[user] removes the electronics from the airlock assembly.", + "You start to uninstall electronics from the airlock assembly...") + + if(W.use_tool(src, user, 40, volume=100) && electronics) + to_chat(user, "You remove the airlock electronics.") + name = "wired windoor assembly" + var/obj/item/electronics/airlock/ae + ae = electronics + electronics = null + ae.forceMove(drop_location()) + + else if(istype(W, /obj/item/pen)) + var/t = stripped_input(user, "Enter the name for the door.", name, created_name,MAX_NAME_LEN) + if(!t) + return + if(!in_range(src, usr) && loc != usr) + return + created_name = t + return + + + + //Crowbar to complete the assembly, Step 7 complete. + else if(W.tool_behaviour == TOOL_CROWBAR) + if(!electronics) + to_chat(usr, "The assembly is missing electronics!") + return + user << browse(null, "window=windoor_access") + user.visible_message("[user] pries the windoor into the frame.", + "You start prying the windoor into the frame...") + + if(W.use_tool(src, user, 40, volume=100) && electronics) + + density = TRUE //Shouldn't matter but just incase + to_chat(user, "You finish the windoor.") + + if(secure) + var/obj/machinery/door/window/brigdoor/windoor = new /obj/machinery/door/window/brigdoor(loc) + if(facing == "l") + windoor.icon_state = "leftsecureopen" + windoor.base_state = "leftsecure" + else + windoor.icon_state = "rightsecureopen" + windoor.base_state = "rightsecure" + windoor.setDir(dir) + windoor.density = FALSE + + if(electronics.one_access) + windoor.req_one_access = electronics.accesses + else + windoor.req_access = electronics.accesses + windoor.electronics = electronics + electronics.forceMove(windoor) + if(created_name) + windoor.name = created_name + qdel(src) + windoor.close() + + + else + var/obj/machinery/door/window/windoor = new /obj/machinery/door/window(loc) + if(facing == "l") + windoor.icon_state = "leftopen" + windoor.base_state = "left" + else + windoor.icon_state = "rightopen" + windoor.base_state = "right" + windoor.setDir(dir) + windoor.density = FALSE + + if(electronics.one_access) + windoor.req_one_access = electronics.accesses + else + windoor.req_access = electronics.accesses + windoor.electronics = electronics + electronics.loc = windoor + if(created_name) + windoor.name = created_name + qdel(src) + windoor.close() + + + else + return ..() + + //Update to reflect changes(if applicable) + update_icon() + + + +/obj/structure/windoor_assembly/ComponentInitialize() + . = ..() + var/static/rotation_flags = ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS + AddComponent(/datum/component/simple_rotation, rotation_flags, can_be_rotated=CALLBACK(src, .proc/can_be_rotated), after_rotation=CALLBACK(src,.proc/after_rotation)) + +/obj/structure/windoor_assembly/proc/can_be_rotated(mob/user,rotation_type) + if(anchored) + to_chat(user, "[src] cannot be rotated while it is fastened to the floor!") + return FALSE + var/target_dir = turn(dir, rotation_type == ROTATION_CLOCKWISE ? -90 : 90) + + if(!valid_window_location(loc, target_dir)) + to_chat(user, "[src] cannot be rotated in that direction!") + return FALSE + return TRUE + +/obj/structure/windoor_assembly/proc/after_rotation(mob/user) + ini_dir = dir + update_icon() + +//Flips the windoor assembly, determines whather the door opens to the left or the right +/obj/structure/windoor_assembly/verb/flip() + set name = "Flip Windoor Assembly" + set category = "Object" + set src in oview(1) + if(usr.stat || usr.restrained()) + return + + if(isliving(usr)) + var/mob/living/L = usr + if(!(L.mobility_flags & MOBILITY_USE)) + return + + if(facing == "l") + to_chat(usr, "The windoor will now slide to the right.") + facing = "r" + else + facing = "l" + to_chat(usr, "The windoor will now slide to the left.") + + update_icon() + return diff --git a/code/game/shuttle_engines.dm b/code/game/shuttle_engines.dm index 4967a675bd3d..78cabdab2aa2 100644 --- a/code/game/shuttle_engines.dm +++ b/code/game/shuttle_engines.dm @@ -1,158 +1,158 @@ -#define ENGINE_UNWRENCHED 0 -#define ENGINE_WRENCHED 1 -#define ENGINE_WELDED 2 -#define ENGINE_WELDTIME 200 - -/obj/structure/shuttle - name = "shuttle" - icon = 'icons/turf/shuttle.dmi' - resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF - max_integrity = 500 - armor = list("melee" = 100, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 70) //default + ignores melee - -/obj/structure/shuttle/engine - name = "engine" - desc = "A bluespace engine used to make shuttles move." - density = TRUE - anchored = TRUE - var/engine_power = 1 - var/state = ENGINE_WELDED //welding shmelding - -//Ugh this is a lot of copypasta from emitters, welding need some boilerplate reduction -/obj/structure/shuttle/engine/can_be_unfasten_wrench(mob/user, silent) - if(state == ENGINE_WELDED) - if(!silent) - to_chat(user, "[src] is welded to the floor!") - return FAILED_UNFASTEN - return ..() - -/obj/structure/shuttle/engine/default_unfasten_wrench(mob/user, obj/item/I, time = 20) - . = ..() - if(. == SUCCESSFUL_UNFASTEN) - if(anchored) - state = ENGINE_WRENCHED - else - state = ENGINE_UNWRENCHED - -/obj/structure/shuttle/engine/wrench_act(mob/living/user, obj/item/I) - ..() - default_unfasten_wrench(user, I) - return TRUE - -/obj/structure/shuttle/engine/welder_act(mob/living/user, obj/item/I) - . = ..() - switch(state) - if(ENGINE_UNWRENCHED) - to_chat(user, "The [src.name] needs to be wrenched to the floor!") - if(ENGINE_WRENCHED) - if(!I.tool_start_check(user, amount=0)) - return TRUE - - user.visible_message("[user.name] starts to weld the [name] to the floor.", \ - "You start to weld \the [src] to the floor...", \ - "You hear welding.") - - if(I.use_tool(src, user, ENGINE_WELDTIME, volume=50)) - state = ENGINE_WELDED - to_chat(user, "You weld \the [src] to the floor.") - alter_engine_power(engine_power) - - if(ENGINE_WELDED) - if(!I.tool_start_check(user, amount=0)) - return TRUE - - user.visible_message("[user.name] starts to cut the [name] free from the floor.", \ - "You start to cut \the [src] free from the floor...", \ - "You hear welding.") - - if(I.use_tool(src, user, ENGINE_WELDTIME, volume=50)) - state = ENGINE_WRENCHED - to_chat(user, "You cut \the [src] free from the floor.") - alter_engine_power(-engine_power) - return TRUE - -/obj/structure/shuttle/engine/Destroy() - if(state == ENGINE_WELDED) - alter_engine_power(-engine_power) - . = ..() - -//Propagates the change to the shuttle. -/obj/structure/shuttle/engine/proc/alter_engine_power(mod) - if(mod == 0) - return - if(SSshuttle.is_in_shuttle_bounds(src)) - var/obj/docking_port/mobile/M = SSshuttle.get_containing_shuttle(src) - if(M) - M.alter_engines(mod) - -/obj/structure/shuttle/engine/heater - name = "engine heater" - icon_state = "heater" - desc = "Directs energy into compressed particles in order to power engines." - engine_power = 0 // todo make these into 2x1 parts - -/obj/structure/shuttle/engine/platform - name = "engine platform" - icon_state = "platform" - desc = "A platform for engine components." - engine_power = 0 - -/obj/structure/shuttle/engine/propulsion - name = "propulsion engine" - icon_state = "propulsion" - desc = "A standard reliable bluespace engine used by many forms of shuttles." - opacity = 1 - -/obj/structure/shuttle/engine/propulsion/left - name = "left propulsion engine" - icon_state = "propulsion_l" - -/obj/structure/shuttle/engine/propulsion/right - name = "right propulsion engine" - icon_state = "propulsion_r" - -/obj/structure/shuttle/engine/propulsion/burst - name = "burst engine" - desc = "An engine that releases a large bluespace burst to propel it." - -/obj/structure/shuttle/engine/propulsion/burst/cargo - state = ENGINE_UNWRENCHED - anchored = FALSE - -/obj/structure/shuttle/engine/propulsion/burst/left - name = "left burst engine" - icon_state = "burst_l" - -/obj/structure/shuttle/engine/propulsion/burst/right - name = "right burst engine" - icon_state = "burst_r" - -/obj/structure/shuttle/engine/router - name = "engine router" - icon_state = "router" - desc = "Redirects around energized particles in engine structures." - -/obj/structure/shuttle/engine/large - name = "engine" - opacity = 1 - icon = 'icons/obj/2x2.dmi' - icon_state = "large_engine" - desc = "A very large bluespace engine used to propel very large ships." - bound_width = 64 - bound_height = 64 - appearance_flags = 0 - -/obj/structure/shuttle/engine/huge - name = "engine" - opacity = 1 - icon = 'icons/obj/3x3.dmi' - icon_state = "huge_engine" - desc = "An extremely large bluespace engine used to propel extremely large ships." - bound_width = 96 - bound_height = 96 - appearance_flags = 0 - -#undef ENGINE_UNWRENCHED -#undef ENGINE_WRENCHED -#undef ENGINE_WELDED -#undef ENGINE_WELDTIME +#define ENGINE_UNWRENCHED 0 +#define ENGINE_WRENCHED 1 +#define ENGINE_WELDED 2 +#define ENGINE_WELDTIME 200 + +/obj/structure/shuttle + name = "shuttle" + icon = 'icons/turf/shuttle.dmi' + resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF + max_integrity = 500 + armor = list("melee" = 100, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 70) //default + ignores melee + +/obj/structure/shuttle/engine + name = "engine" + desc = "A bluespace engine used to make shuttles move." + density = TRUE + anchored = TRUE + var/engine_power = 1 + var/state = ENGINE_WELDED //welding shmelding + +//Ugh this is a lot of copypasta from emitters, welding need some boilerplate reduction +/obj/structure/shuttle/engine/can_be_unfasten_wrench(mob/user, silent) + if(state == ENGINE_WELDED) + if(!silent) + to_chat(user, "[src] is welded to the floor!") + return FAILED_UNFASTEN + return ..() + +/obj/structure/shuttle/engine/default_unfasten_wrench(mob/user, obj/item/I, time = 20) + . = ..() + if(. == SUCCESSFUL_UNFASTEN) + if(anchored) + state = ENGINE_WRENCHED + else + state = ENGINE_UNWRENCHED + +/obj/structure/shuttle/engine/wrench_act(mob/living/user, obj/item/I) + ..() + default_unfasten_wrench(user, I) + return TRUE + +/obj/structure/shuttle/engine/welder_act(mob/living/user, obj/item/I) + . = ..() + switch(state) + if(ENGINE_UNWRENCHED) + to_chat(user, "The [src.name] needs to be wrenched to the floor!") + if(ENGINE_WRENCHED) + if(!I.tool_start_check(user, amount=0)) + return TRUE + + user.visible_message("[user.name] starts to weld the [name] to the floor.", \ + "You start to weld \the [src] to the floor...", \ + "You hear welding.") + + if(I.use_tool(src, user, ENGINE_WELDTIME, volume=50)) + state = ENGINE_WELDED + to_chat(user, "You weld \the [src] to the floor.") + alter_engine_power(engine_power) + + if(ENGINE_WELDED) + if(!I.tool_start_check(user, amount=0)) + return TRUE + + user.visible_message("[user.name] starts to cut the [name] free from the floor.", \ + "You start to cut \the [src] free from the floor...", \ + "You hear welding.") + + if(I.use_tool(src, user, ENGINE_WELDTIME, volume=50)) + state = ENGINE_WRENCHED + to_chat(user, "You cut \the [src] free from the floor.") + alter_engine_power(-engine_power) + return TRUE + +/obj/structure/shuttle/engine/Destroy() + if(state == ENGINE_WELDED) + alter_engine_power(-engine_power) + . = ..() + +//Propagates the change to the shuttle. +/obj/structure/shuttle/engine/proc/alter_engine_power(mod) + if(mod == 0) + return + if(SSshuttle.is_in_shuttle_bounds(src)) + var/obj/docking_port/mobile/M = SSshuttle.get_containing_shuttle(src) + if(M) + M.alter_engines(mod) + +/obj/structure/shuttle/engine/heater + name = "engine heater" + icon_state = "heater" + desc = "Directs energy into compressed particles in order to power engines." + engine_power = 0 // todo make these into 2x1 parts + +/obj/structure/shuttle/engine/platform + name = "engine platform" + icon_state = "platform" + desc = "A platform for engine components." + engine_power = 0 + +/obj/structure/shuttle/engine/propulsion + name = "propulsion engine" + icon_state = "propulsion" + desc = "A standard reliable bluespace engine used by many forms of shuttles." + opacity = 1 + +/obj/structure/shuttle/engine/propulsion/left + name = "left propulsion engine" + icon_state = "propulsion_l" + +/obj/structure/shuttle/engine/propulsion/right + name = "right propulsion engine" + icon_state = "propulsion_r" + +/obj/structure/shuttle/engine/propulsion/burst + name = "burst engine" + desc = "An engine that releases a large bluespace burst to propel it." + +/obj/structure/shuttle/engine/propulsion/burst/cargo + state = ENGINE_UNWRENCHED + anchored = FALSE + +/obj/structure/shuttle/engine/propulsion/burst/left + name = "left burst engine" + icon_state = "burst_l" + +/obj/structure/shuttle/engine/propulsion/burst/right + name = "right burst engine" + icon_state = "burst_r" + +/obj/structure/shuttle/engine/router + name = "engine router" + icon_state = "router" + desc = "Redirects around energized particles in engine structures." + +/obj/structure/shuttle/engine/large + name = "engine" + opacity = 1 + icon = 'icons/obj/2x2.dmi' + icon_state = "large_engine" + desc = "A very large bluespace engine used to propel very large ships." + bound_width = 64 + bound_height = 64 + appearance_flags = 0 + +/obj/structure/shuttle/engine/huge + name = "engine" + opacity = 1 + icon = 'icons/obj/3x3.dmi' + icon_state = "huge_engine" + desc = "An extremely large bluespace engine used to propel extremely large ships." + bound_width = 96 + bound_height = 96 + appearance_flags = 0 + +#undef ENGINE_UNWRENCHED +#undef ENGINE_WRENCHED +#undef ENGINE_WELDED +#undef ENGINE_WELDTIME diff --git a/code/game/turfs/open/openspace.dm b/code/game/turfs/open/openspace.dm index 6d2a5c372115..bec1f754fc45 100644 --- a/code/game/turfs/open/openspace.dm +++ b/code/game/turfs/open/openspace.dm @@ -1,159 +1,159 @@ -GLOBAL_DATUM_INIT(openspace_backdrop_one_for_all, /atom/movable/openspace_backdrop, new) - -/atom/movable/openspace_backdrop - name = "openspace_backdrop" - - anchored = TRUE - - icon = 'icons/turf/floors.dmi' - icon_state = "grey" - plane = OPENSPACE_BACKDROP_PLANE - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - layer = SPLASHSCREEN_LAYER - -/turf/open/openspace - name = "open space" - desc = "Watch your step!" - icon_state = "transparent" - baseturfs = /turf/open/openspace - CanAtmosPassVertical = ATMOS_PASS_YES - //mouse_opacity = MOUSE_OPACITY_TRANSPARENT - var/can_cover_up = TRUE - var/can_build_on = TRUE - - intact = 0 -/turf/open/openspace/airless - initial_gas_mix = AIRLESS_ATMOS - -/turf/open/openspace/debug/update_multiz() - ..() - return TRUE - -/turf/open/openspace/Initialize() // handle plane and layer here so that they don't cover other obs/turfs in Dream Maker - . = ..() - plane = OPENSPACE_PLANE - layer = OPENSPACE_LAYER - - vis_contents += GLOB.openspace_backdrop_one_for_all //Special grey square for projecting backdrop darkness filter on it. - - return INITIALIZE_HINT_LATELOAD - -/turf/open/openspace/LateInitialize() - update_multiz(TRUE, TRUE) - -/turf/open/openspace/Destroy() - vis_contents.len = 0 - return ..() - -/turf/open/openspace/can_have_cabling() - if(locate(/obj/structure/lattice/catwalk, src)) - return TRUE - return FALSE - -/turf/open/openspace/update_multiz(prune_on_fail = FALSE, init = FALSE) - . = ..() - var/turf/T = below() - if(!T) - vis_contents.len = 0 - if(prune_on_fail) - ChangeTurf(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR) - return FALSE - if(init) - vis_contents += T - return TRUE - -/turf/open/openspace/multiz_turf_del(turf/T, dir) - if(dir != DOWN) - return - update_multiz() - -/turf/open/openspace/multiz_turf_new(turf/T, dir) - if(dir != DOWN) - return - update_multiz() - -/turf/open/openspace/zAirIn() - return TRUE - -/turf/open/openspace/zAirOut() - return TRUE - -/turf/open/openspace/zPassIn(atom/movable/A, direction, turf/source) - return TRUE - -/turf/open/openspace/zPassOut(atom/movable/A, direction, turf/destination) - if(A.anchored) - return FALSE - for(var/obj/O in contents) - if(O.obj_flags & BLOCK_Z_FALL) - return FALSE - return TRUE - -/turf/open/openspace/proc/CanCoverUp() - return can_cover_up - -/turf/open/openspace/proc/CanBuildHere() - return can_build_on - -/turf/open/openspace/attackby(obj/item/C, mob/user, params) - ..() - if(!CanBuildHere()) - return - if(istype(C, /obj/item/stack/rods)) - var/obj/item/stack/rods/R = C - var/obj/structure/lattice/L = locate(/obj/structure/lattice, src) - var/obj/structure/lattice/catwalk/W = locate(/obj/structure/lattice/catwalk, src) - if(W) - to_chat(user, "There is already a catwalk here!") - return - if(L) - if(R.use(1)) - to_chat(user, "You construct a catwalk.") - playsound(src, 'sound/weapons/genhit.ogg', 50, TRUE) - new/obj/structure/lattice/catwalk(src) - else - to_chat(user, "You need two rods to build a catwalk!") - return - if(R.use(1)) - to_chat(user, "You construct a lattice.") - playsound(src, 'sound/weapons/genhit.ogg', 50, TRUE) - ReplaceWithLattice() - else - to_chat(user, "You need one rod to build a lattice.") - return - if(istype(C, /obj/item/stack/tile/plasteel)) - if(!CanCoverUp()) - return - var/obj/structure/lattice/L = locate(/obj/structure/lattice, src) - if(L) - var/obj/item/stack/tile/plasteel/S = C - if(S.use(1)) - qdel(L) - playsound(src, 'sound/weapons/genhit.ogg', 50, TRUE) - to_chat(user, "You build a floor.") - PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR) - else - to_chat(user, "You need one floor tile to build a floor!") - else - to_chat(user, "The plating is going to need some support! Place metal rods first.") - -/turf/open/openspace/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd) - if(!CanBuildHere()) - return FALSE - - switch(the_rcd.mode) - if(RCD_FLOORWALL) - var/obj/structure/lattice/L = locate(/obj/structure/lattice, src) - if(L) - return list("mode" = RCD_FLOORWALL, "delay" = 0, "cost" = 1) - else - return list("mode" = RCD_FLOORWALL, "delay" = 0, "cost" = 3) - return FALSE - -/turf/open/openspace/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode) - switch(passed_mode) - if(RCD_FLOORWALL) - to_chat(user, "You build a floor.") - PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR) - return TRUE - return FALSE +GLOBAL_DATUM_INIT(openspace_backdrop_one_for_all, /atom/movable/openspace_backdrop, new) + +/atom/movable/openspace_backdrop + name = "openspace_backdrop" + + anchored = TRUE + + icon = 'icons/turf/floors.dmi' + icon_state = "grey" + plane = OPENSPACE_BACKDROP_PLANE + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + layer = SPLASHSCREEN_LAYER + +/turf/open/openspace + name = "open space" + desc = "Watch your step!" + icon_state = "transparent" + baseturfs = /turf/open/openspace + CanAtmosPassVertical = ATMOS_PASS_YES + //mouse_opacity = MOUSE_OPACITY_TRANSPARENT + var/can_cover_up = TRUE + var/can_build_on = TRUE + + intact = 0 +/turf/open/openspace/airless + initial_gas_mix = AIRLESS_ATMOS + +/turf/open/openspace/debug/update_multiz() + ..() + return TRUE + +/turf/open/openspace/Initialize() // handle plane and layer here so that they don't cover other obs/turfs in Dream Maker + . = ..() + plane = OPENSPACE_PLANE + layer = OPENSPACE_LAYER + + vis_contents += GLOB.openspace_backdrop_one_for_all //Special grey square for projecting backdrop darkness filter on it. + + return INITIALIZE_HINT_LATELOAD + +/turf/open/openspace/LateInitialize() + update_multiz(TRUE, TRUE) + +/turf/open/openspace/Destroy() + vis_contents.len = 0 + return ..() + +/turf/open/openspace/can_have_cabling() + if(locate(/obj/structure/lattice/catwalk, src)) + return TRUE + return FALSE + +/turf/open/openspace/update_multiz(prune_on_fail = FALSE, init = FALSE) + . = ..() + var/turf/T = below() + if(!T) + vis_contents.len = 0 + if(prune_on_fail) + ChangeTurf(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR) + return FALSE + if(init) + vis_contents += T + return TRUE + +/turf/open/openspace/multiz_turf_del(turf/T, dir) + if(dir != DOWN) + return + update_multiz() + +/turf/open/openspace/multiz_turf_new(turf/T, dir) + if(dir != DOWN) + return + update_multiz() + +/turf/open/openspace/zAirIn() + return TRUE + +/turf/open/openspace/zAirOut() + return TRUE + +/turf/open/openspace/zPassIn(atom/movable/A, direction, turf/source) + return TRUE + +/turf/open/openspace/zPassOut(atom/movable/A, direction, turf/destination) + if(A.anchored) + return FALSE + for(var/obj/O in contents) + if(O.obj_flags & BLOCK_Z_FALL) + return FALSE + return TRUE + +/turf/open/openspace/proc/CanCoverUp() + return can_cover_up + +/turf/open/openspace/proc/CanBuildHere() + return can_build_on + +/turf/open/openspace/attackby(obj/item/C, mob/user, params) + ..() + if(!CanBuildHere()) + return + if(istype(C, /obj/item/stack/rods)) + var/obj/item/stack/rods/R = C + var/obj/structure/lattice/L = locate(/obj/structure/lattice, src) + var/obj/structure/lattice/catwalk/W = locate(/obj/structure/lattice/catwalk, src) + if(W) + to_chat(user, "There is already a catwalk here!") + return + if(L) + if(R.use(1)) + to_chat(user, "You construct a catwalk.") + playsound(src, 'sound/weapons/genhit.ogg', 50, TRUE) + new/obj/structure/lattice/catwalk(src) + else + to_chat(user, "You need two rods to build a catwalk!") + return + if(R.use(1)) + to_chat(user, "You construct a lattice.") + playsound(src, 'sound/weapons/genhit.ogg', 50, TRUE) + ReplaceWithLattice() + else + to_chat(user, "You need one rod to build a lattice.") + return + if(istype(C, /obj/item/stack/tile/plasteel)) + if(!CanCoverUp()) + return + var/obj/structure/lattice/L = locate(/obj/structure/lattice, src) + if(L) + var/obj/item/stack/tile/plasteel/S = C + if(S.use(1)) + qdel(L) + playsound(src, 'sound/weapons/genhit.ogg', 50, TRUE) + to_chat(user, "You build a floor.") + PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR) + else + to_chat(user, "You need one floor tile to build a floor!") + else + to_chat(user, "The plating is going to need some support! Place metal rods first.") + +/turf/open/openspace/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd) + if(!CanBuildHere()) + return FALSE + + switch(the_rcd.mode) + if(RCD_FLOORWALL) + var/obj/structure/lattice/L = locate(/obj/structure/lattice, src) + if(L) + return list("mode" = RCD_FLOORWALL, "delay" = 0, "cost" = 1) + else + return list("mode" = RCD_FLOORWALL, "delay" = 0, "cost" = 3) + return FALSE + +/turf/open/openspace/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode) + switch(passed_mode) + if(RCD_FLOORWALL) + to_chat(user, "You build a floor.") + PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR) + return TRUE + return FALSE diff --git a/code/game/turfs/open/space/space.dm b/code/game/turfs/open/space/space.dm index d6a7dd3bf8b9..2e762c8a93df 100644 --- a/code/game/turfs/open/space/space.dm +++ b/code/game/turfs/open/space/space.dm @@ -187,6 +187,7 @@ //now we're on the new z_level, proceed the space drifting stoplag()//Let a diagonal move finish, if necessary A.newtonian_move(A.inertia_dir) + A.inertia_moving = TRUE /turf/open/space/MakeSlippery(wet_setting, min_wet_time, wet_time_to_add, max_wet_time, permanent) diff --git a/code/game/world.dm b/code/game/world.dm index 6d239d03e08c..79753cc646e0 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -1,384 +1,390 @@ -#define RESTART_COUNTER_PATH "data/round_counter.txt" - -GLOBAL_VAR(restart_counter) - -/** - * World creation - * - * Here is where a round itself is actually begun and setup, lots of important config changes happen here - * * db connection setup - * * config loaded from files - * * loads admins - * * Sets up the dynamic menu system - * * and most importantly, calls initialize on the master subsystem, starting the game loop that causes the rest of the game to begin processing and setting up - * - * Note this happens after the Master subsystem is created (as that is a global datum), this means all the subsystems exist, - * but they have not been Initialized at this point, only their New proc has run - * - * Nothing happens until something moves. ~Albert Einstein - * - */ -/world/New() - var/extools = world.GetConfig("env", "EXTOOLS_DLL") || (world.system_type == MS_WINDOWS ? "./byond-extools.dll" : "./libbyond-extools.so") - if (fexists(extools)) - call(extools, "maptick_initialize")() - enable_debugger() - - //Early profile for auto-profiler - will be stopped on profiler init if necessary. -#if DM_BUILD >= 1506 - world.Profile(PROFILE_START) -#endif - - log_world("World loaded at [time_stamp()]!") - - SetupExternalRSC() - - GLOB.config_error_log = GLOB.world_manifest_log = GLOB.world_pda_log = GLOB.world_job_debug_log = GLOB.sql_error_log = GLOB.world_href_log = GLOB.world_runtime_log = GLOB.world_attack_log = GLOB.world_game_log = GLOB.world_shuttle_log = "data/logs/config_error.[GUID()].log" //temporary file used to record errors with loading config, moved to log directory once logging is set bl - - make_datum_references_lists() //initialises global lists for referencing frequently used datums (so that we only ever do it once) - - TgsNew(minimum_required_security_level = TGS_SECURITY_TRUSTED) - - GLOB.revdata = new - - config.Load(params[OVERRIDE_CONFIG_DIRECTORY_PARAMETER]) - - //SetupLogs depends on the RoundID, so lets check - //DB schema and set RoundID if we can - SSdbcore.CheckSchemaVersion() - SSdbcore.SetRoundID() - SetupLogs() - load_poll_data() - - populate_gear_list() - -#ifndef USE_CUSTOM_ERROR_HANDLER - world.log = file("[GLOB.log_directory]/dd.log") -#else - if (TgsAvailable()) - world.log = file("[GLOB.log_directory]/dd.log") //not all runtimes trigger world/Error, so this is the only way to ensure we can see all of them. -#endif - - load_admins() - load_mentors() - LoadVerbs(/datum/verbs/menu) - if(CONFIG_GET(flag/usewhitelist)) - load_whitelist() - - GLOB.timezoneOffset = text2num(time2text(0,"hh")) * 36000 - - if(fexists(RESTART_COUNTER_PATH)) - GLOB.restart_counter = text2num(trim(file2text(RESTART_COUNTER_PATH))) - fdel(RESTART_COUNTER_PATH) - - if(NO_INIT_PARAMETER in params) - return - - Master.Initialize(10, FALSE, TRUE) - - if(TEST_RUN_PARAMETER in params) - HandleTestRun() - -/world/proc/HandleTestRun() - //trigger things to run the whole process - Master.sleep_offline_after_initializations = FALSE - SSticker.start_immediately = TRUE - CONFIG_SET(number/round_end_countdown, 0) - var/datum/callback/cb -#ifdef UNIT_TESTS - cb = CALLBACK(GLOBAL_PROC, /proc/RunUnitTests) -#else - cb = VARSET_CALLBACK(SSticker, force_ending, TRUE) -#endif - SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, /proc/addtimer, cb, 10 SECONDS)) - -/world/proc/SetupExternalRSC() -#if (PRELOAD_RSC == 0) - GLOB.external_rsc_urls = world.file2list("[global.config.directory]/external_rsc_urls.txt","\n") - var/i=1 - while(i<=GLOB.external_rsc_urls.len) - if(GLOB.external_rsc_urls[i]) - i++ - else - GLOB.external_rsc_urls.Cut(i,i+1) -#endif - -/world/proc/SetupLogs() - var/override_dir = params[OVERRIDE_LOG_DIRECTORY_PARAMETER] - if(!override_dir) - var/realtime = world.realtime - var/texttime = time2text(realtime, "YYYY/MM/DD") - GLOB.log_directory = "data/logs/[texttime]/round-" - GLOB.picture_logging_prefix = "L_[time2text(realtime, "YYYYMMDD")]_" - GLOB.picture_log_directory = "data/picture_logs/[texttime]/round-" - if(GLOB.round_id) - GLOB.log_directory += "[GLOB.round_id]" - GLOB.picture_logging_prefix += "R_[GLOB.round_id]_" - GLOB.picture_log_directory += "[GLOB.round_id]" - else - var/timestamp = replacetext(time_stamp(), ":", ".") - GLOB.log_directory += "[timestamp]" - GLOB.picture_log_directory += "[timestamp]" - GLOB.picture_logging_prefix += "T_[timestamp]_" - else - GLOB.log_directory = "data/logs/[override_dir]" - GLOB.picture_logging_prefix = "O_[override_dir]_" - GLOB.picture_log_directory = "data/picture_logs/[override_dir]" - - GLOB.world_game_log = "[GLOB.log_directory]/game.log" - GLOB.world_mecha_log = "[GLOB.log_directory]/mecha.log" - GLOB.world_virus_log = "[GLOB.log_directory]/virus.log" - GLOB.world_cloning_log = "[GLOB.log_directory]/cloning.log" - GLOB.world_asset_log = "[GLOB.log_directory]/asset.log" - GLOB.world_attack_log = "[GLOB.log_directory]/attack.log" - GLOB.world_pda_log = "[GLOB.log_directory]/pda.log" - GLOB.world_telecomms_log = "[GLOB.log_directory]/telecomms.log" - GLOB.world_manifest_log = "[GLOB.log_directory]/manifest.log" - GLOB.world_href_log = "[GLOB.log_directory]/hrefs.log" - GLOB.sql_error_log = "[GLOB.log_directory]/sql.log" - GLOB.world_qdel_log = "[GLOB.log_directory]/qdel.log" - GLOB.world_map_error_log = "[GLOB.log_directory]/map_errors.log" - GLOB.world_runtime_log = "[GLOB.log_directory]/runtime.log" - GLOB.query_debug_log = "[GLOB.log_directory]/query_debug.log" - GLOB.world_job_debug_log = "[GLOB.log_directory]/job_debug.log" - GLOB.world_paper_log = "[GLOB.log_directory]/paper.log" - GLOB.tgui_log = "[GLOB.log_directory]/tgui.log" - GLOB.world_shuttle_log = "[GLOB.log_directory]/shuttle.log" - GLOB.discord_api_log = "[GLOB.log_directory]/discord_api_log.log" - - GLOB.demo_log = "[GLOB.log_directory]/demo.log" - -#ifdef UNIT_TESTS - GLOB.test_log = file("[GLOB.log_directory]/tests.log") - start_log(GLOB.test_log) -#endif - start_log(GLOB.world_game_log) - start_log(GLOB.world_attack_log) - start_log(GLOB.world_pda_log) - start_log(GLOB.world_telecomms_log) - start_log(GLOB.world_manifest_log) - start_log(GLOB.world_href_log) - start_log(GLOB.world_qdel_log) - start_log(GLOB.world_runtime_log) - start_log(GLOB.world_job_debug_log) - start_log(GLOB.tgui_log) - start_log(GLOB.world_shuttle_log) - start_log(GLOB.discord_api_log) - - GLOB.changelog_hash = md5('html/changelog.html') //for telling if the changelog has changed recently - if(fexists(GLOB.config_error_log)) - fcopy(GLOB.config_error_log, "[GLOB.log_directory]/config_error.log") - fdel(GLOB.config_error_log) - - if(GLOB.round_id) - log_game("Round ID: [GLOB.round_id]") - - // This was printed early in startup to the world log and config_error.log, - // but those are both private, so let's put the commit info in the runtime - // log which is ultimately public. - log_runtime(GLOB.revdata.get_log_message()) - -/world/Topic(T, addr, master, key) - TGS_TOPIC //redirect to server tools if necessary - - var/static/list/topic_handlers = TopicHandlers() - - var/list/input = params2list(T) - var/datum/world_topic/handler - for(var/I in topic_handlers) - if(I in input) - handler = topic_handlers[I] - break - - if((!handler || initial(handler.log)) && config && CONFIG_GET(flag/log_world_topic)) - log_topic("\"[T]\", from:[addr], master:[master], key:[key]") - - if(!handler) - return - - handler = new handler() - return handler.TryRun(input) - -/world/proc/AnnouncePR(announcement, list/payload) - var/static/list/PRcounts = list() //PR id -> number of times announced this round - var/id = "[payload["pull_request"]["id"]]" - if(!PRcounts[id]) - PRcounts[id] = 1 - else - ++PRcounts[id] - if(PRcounts[id] > PR_ANNOUNCEMENTS_PER_ROUND) - return - - var/final_composed = "PR: [announcement]" - for(var/client/C in GLOB.clients) - C.AnnouncePR(final_composed) - -/world/proc/FinishTestRun() - set waitfor = FALSE - var/list/fail_reasons - if(GLOB) - if(GLOB.total_runtimes != 0) - fail_reasons = list("Total runtimes: [GLOB.total_runtimes]") -#ifdef UNIT_TESTS - if(GLOB.failed_any_test) - LAZYADD(fail_reasons, "Unit Tests failed!") -#endif - if(!GLOB.log_directory) - LAZYADD(fail_reasons, "Missing GLOB.log_directory!") - else - fail_reasons = list("Missing GLOB!") - if(!fail_reasons) - text2file("Success!", "[GLOB.log_directory]/clean_run.lk") - else - log_world("Test run failed!\n[fail_reasons.Join("\n")]") - sleep(0) //yes, 0, this'll let Reboot finish and prevent byond memes - qdel(src) //shut it down - -/world/Reboot(reason = 0, fast_track = FALSE) - if (reason || fast_track) //special reboot, do none of the normal stuff - if (usr) - log_admin("[key_name(usr)] Has requested an immediate world restart via client side debugging tools") - message_admins("[key_name_admin(usr)] Has requested an immediate world restart via client side debugging tools") - to_chat(world, "Rebooting World immediately due to host request.") - else - to_chat(world, "Rebooting world...") - Master.Shutdown() //run SS shutdowns - - TgsReboot() - - if(TEST_RUN_PARAMETER in params) - FinishTestRun() - return - - if(TgsAvailable()) - var/do_hard_reboot - // check the hard reboot counter - var/ruhr = CONFIG_GET(number/rounds_until_hard_restart) - switch(ruhr) - if(-1) - do_hard_reboot = FALSE - if(0) - do_hard_reboot = TRUE - else - if(GLOB.restart_counter >= ruhr) - do_hard_reboot = TRUE - else - text2file("[++GLOB.restart_counter]", RESTART_COUNTER_PATH) - do_hard_reboot = FALSE - - if(do_hard_reboot) - log_world("World hard rebooted at [time_stamp()]") - shutdown_logging() // See comment below. - TgsEndProcess() - - log_world("World rebooted at [time_stamp()]") - shutdown_logging() // Past this point, no logging procs can be used, at risk of data loss. - ..() - -/world/Del() - // memory leaks bad - var/num_deleted = 0 - for(var/datum/gas_mixture/GM) - GM.__gasmixture_unregister() - num_deleted++ - log_world("Deallocated [num_deleted] gas mixtures") - ..() - -/world/proc/update_status() - - var/list/features = list() - - if(GLOB.master_mode) - features += GLOB.master_mode - - if (!GLOB.enter_allowed) - features += "closed" - - var/s = "" - var/hostedby - if(config) - var/server_name = CONFIG_GET(string/servername) - if (server_name) - s += "[server_name] — " - features += "[CONFIG_GET(flag/norespawn) ? "no " : ""]respawn" - if(CONFIG_GET(flag/allow_vote_mode)) - features += "vote" - if(CONFIG_GET(flag/allow_ai)) - features += "AI allowed" - hostedby = CONFIG_GET(string/hostedby) - - s += "[station_name()]"; - s += " (" - s += "" //Change this to wherever you want the hub to link to. - s += "Discord" //Replace this with something else. Or ever better, delete it and uncomment the game version. - s += "" - s += ")" - s += " (" - s += "" //Change this to wherever you want the hub to link to. - s += "Github" //Replace this with something else. Or ever better, delete it and uncomment the game version. - s += "" - s += ")" - - var/players = GLOB.clients.len - - var/popcaptext = "" - var/popcap = max(CONFIG_GET(number/extreme_popcap), CONFIG_GET(number/hard_popcap), CONFIG_GET(number/soft_popcap)) - if (popcap) - popcaptext = "/[popcap]" - - if (players > 1) - features += "[players][popcaptext] players" - else if (players > 0) - features += "[players][popcaptext] player" - - game_state = (CONFIG_GET(number/extreme_popcap) && players >= CONFIG_GET(number/extreme_popcap)) //tells the hub if we are full - - if (!host && hostedby) - features += "hosted by [hostedby]" - - if (features) - s += ": [jointext(features, ", ")]" - - status = s - -/world/proc/update_hub_visibility(new_visibility) - if(new_visibility == GLOB.hub_visibility) - return - GLOB.hub_visibility = new_visibility - if(GLOB.hub_visibility) - hub_password = "kMZy3U5jJHSiBQjr" - else - hub_password = "SORRYNOPASSWORD" - -/world/proc/incrementMaxZ() - maxz++ - SSmobs.MaxZChanged() - SSidlenpcpool.MaxZChanged() - world.refresh_atmos_grid() - - -/world/proc/change_fps(new_value = 20) - if(new_value <= 0) - CRASH("change_fps() called with [new_value] new_value.") - if(fps == new_value) - return //No change required. - - fps = new_value - on_tickrate_change() - - -/world/proc/change_tick_lag(new_value = 0.5) - if(new_value <= 0) - CRASH("change_tick_lag() called with [new_value] new_value.") - if(tick_lag == new_value) - return //No change required. - - tick_lag = new_value - on_tickrate_change() - - -/world/proc/on_tickrate_change() - SStimer?.reset_buckets() - - -/world/proc/refresh_atmos_grid() +#define RESTART_COUNTER_PATH "data/round_counter.txt" + +GLOBAL_VAR(restart_counter) + +/** + * World creation + * + * Here is where a round itself is actually begun and setup, lots of important config changes happen here + * * db connection setup + * * config loaded from files + * * loads admins + * * Sets up the dynamic menu system + * * and most importantly, calls initialize on the master subsystem, starting the game loop that causes the rest of the game to begin processing and setting up + * + * Note this happens after the Master subsystem is created (as that is a global datum), this means all the subsystems exist, + * but they have not been Initialized at this point, only their New proc has run + * + * Nothing happens until something moves. ~Albert Einstein + * + */ +/world/New() + var/extools = world.GetConfig("env", "EXTOOLS_DLL") || (world.system_type == MS_WINDOWS ? "./byond-extools.dll" : "./libbyond-extools.so") + if (fexists(extools)) + call(extools, "maptick_initialize")() + enable_debugger() +#ifdef REFERENCE_TRACKING + enable_reference_tracking() +#endif + + //Early profile for auto-profiler - will be stopped on profiler init if necessary. +#if DM_BUILD >= 1506 + world.Profile(PROFILE_START) +#endif + + log_world("World loaded at [time_stamp()]!") + + SetupExternalRSC() + + make_datum_references_lists() //initialises global lists for referencing frequently used datums (so that we only ever do it once) + + GLOB.config_error_log = GLOB.world_manifest_log = GLOB.world_pda_log = GLOB.world_job_debug_log = GLOB.sql_error_log = GLOB.world_href_log = GLOB.world_runtime_log = GLOB.world_attack_log = GLOB.world_game_log = GLOB.world_shuttle_log = "data/logs/config_error.[GUID()].log" //temporary file used to record errors with loading config, moved to log directory once logging is set bl + + GLOB.revdata = new + + InitTgs() + + config.Load(params[OVERRIDE_CONFIG_DIRECTORY_PARAMETER]) + + load_admins() + load_mentors() //Wasp edit - Mentors + + //SetupLogs depends on the RoundID, so lets check + //DB schema and set RoundID if we can + SSdbcore.CheckSchemaVersion() + SSdbcore.SetRoundID() + SetupLogs() + populate_gear_list() //Wasp edit - Loadouts + +#ifndef USE_CUSTOM_ERROR_HANDLER + world.log = file("[GLOB.log_directory]/dd.log") +#else + if (TgsAvailable()) + world.log = file("[GLOB.log_directory]/dd.log") //not all runtimes trigger world/Error, so this is the only way to ensure we can see all of them. +#endif + + LoadVerbs(/datum/verbs/menu) + if(CONFIG_GET(flag/usewhitelist)) + load_whitelist() + + GLOB.timezoneOffset = text2num(time2text(0,"hh")) * 36000 + + if(fexists(RESTART_COUNTER_PATH)) + GLOB.restart_counter = text2num(trim(file2text(RESTART_COUNTER_PATH))) + fdel(RESTART_COUNTER_PATH) + + if(NO_INIT_PARAMETER in params) + return + + Master.Initialize(10, FALSE, TRUE) + + if(TEST_RUN_PARAMETER in params) + HandleTestRun() + +/world/proc/InitTgs() + TgsNew(new /datum/tgs_event_handler/impl, TGS_SECURITY_TRUSTED) + GLOB.revdata.load_tgs_info() + +/world/proc/HandleTestRun() + //trigger things to run the whole process + Master.sleep_offline_after_initializations = FALSE + SSticker.start_immediately = TRUE + CONFIG_SET(number/round_end_countdown, 0) + var/datum/callback/cb +#ifdef UNIT_TESTS + cb = CALLBACK(GLOBAL_PROC, /proc/RunUnitTests) +#else + cb = VARSET_CALLBACK(SSticker, force_ending, TRUE) +#endif + SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, /proc/addtimer, cb, 10 SECONDS)) + +/world/proc/SetupExternalRSC() +#if (PRELOAD_RSC == 0) + GLOB.external_rsc_urls = world.file2list("[global.config.directory]/external_rsc_urls.txt","\n") + var/i=1 + while(i<=GLOB.external_rsc_urls.len) + if(GLOB.external_rsc_urls[i]) + i++ + else + GLOB.external_rsc_urls.Cut(i,i+1) +#endif + +/world/proc/SetupLogs() + var/override_dir = params[OVERRIDE_LOG_DIRECTORY_PARAMETER] + if(!override_dir) + var/realtime = world.realtime + var/texttime = time2text(realtime, "YYYY/MM/DD") + GLOB.log_directory = "data/logs/[texttime]/round-" + GLOB.picture_logging_prefix = "L_[time2text(realtime, "YYYYMMDD")]_" + GLOB.picture_log_directory = "data/picture_logs/[texttime]/round-" + if(GLOB.round_id) + GLOB.log_directory += "[GLOB.round_id]" + GLOB.picture_logging_prefix += "R_[GLOB.round_id]_" + GLOB.picture_log_directory += "[GLOB.round_id]" + else + var/timestamp = replacetext(time_stamp(), ":", ".") + GLOB.log_directory += "[timestamp]" + GLOB.picture_log_directory += "[timestamp]" + GLOB.picture_logging_prefix += "T_[timestamp]_" + else + GLOB.log_directory = "data/logs/[override_dir]" + GLOB.picture_logging_prefix = "O_[override_dir]_" + GLOB.picture_log_directory = "data/picture_logs/[override_dir]" + + GLOB.world_game_log = "[GLOB.log_directory]/game.log" + GLOB.world_mecha_log = "[GLOB.log_directory]/mecha.log" + GLOB.world_virus_log = "[GLOB.log_directory]/virus.log" + GLOB.world_cloning_log = "[GLOB.log_directory]/cloning.log" + GLOB.world_asset_log = "[GLOB.log_directory]/asset.log" + GLOB.world_attack_log = "[GLOB.log_directory]/attack.log" + GLOB.world_pda_log = "[GLOB.log_directory]/pda.log" + GLOB.world_telecomms_log = "[GLOB.log_directory]/telecomms.log" + GLOB.world_manifest_log = "[GLOB.log_directory]/manifest.log" + GLOB.world_href_log = "[GLOB.log_directory]/hrefs.log" + GLOB.sql_error_log = "[GLOB.log_directory]/sql.log" + GLOB.world_qdel_log = "[GLOB.log_directory]/qdel.log" + GLOB.world_map_error_log = "[GLOB.log_directory]/map_errors.log" + GLOB.world_runtime_log = "[GLOB.log_directory]/runtime.log" + GLOB.query_debug_log = "[GLOB.log_directory]/query_debug.log" + GLOB.world_job_debug_log = "[GLOB.log_directory]/job_debug.log" + GLOB.world_paper_log = "[GLOB.log_directory]/paper.log" + GLOB.tgui_log = "[GLOB.log_directory]/tgui.log" + GLOB.world_shuttle_log = "[GLOB.log_directory]/shuttle.log" + GLOB.discord_api_log = "[GLOB.log_directory]/discord_api_log.log" + + GLOB.demo_log = "[GLOB.log_directory]/demo.log" + +#ifdef UNIT_TESTS + GLOB.test_log = file("[GLOB.log_directory]/tests.log") + start_log(GLOB.test_log) +#endif + start_log(GLOB.world_game_log) + start_log(GLOB.world_attack_log) + start_log(GLOB.world_pda_log) + start_log(GLOB.world_telecomms_log) + start_log(GLOB.world_manifest_log) + start_log(GLOB.world_href_log) + start_log(GLOB.world_qdel_log) + start_log(GLOB.world_runtime_log) + start_log(GLOB.world_job_debug_log) + start_log(GLOB.tgui_log) + start_log(GLOB.world_shuttle_log) + start_log(GLOB.discord_api_log) + + GLOB.changelog_hash = md5('html/changelog.html') //for telling if the changelog has changed recently + if(fexists(GLOB.config_error_log)) + fcopy(GLOB.config_error_log, "[GLOB.log_directory]/config_error.log") + fdel(GLOB.config_error_log) + + if(GLOB.round_id) + log_game("Round ID: [GLOB.round_id]") + + // This was printed early in startup to the world log and config_error.log, + // but those are both private, so let's put the commit info in the runtime + // log which is ultimately public. + log_runtime(GLOB.revdata.get_log_message()) + +/world/Topic(T, addr, master, key) + TGS_TOPIC //redirect to server tools if necessary + + var/static/list/topic_handlers = TopicHandlers() + + var/list/input = params2list(T) + var/datum/world_topic/handler + for(var/I in topic_handlers) + if(I in input) + handler = topic_handlers[I] + break + + if((!handler || initial(handler.log)) && config && CONFIG_GET(flag/log_world_topic)) + log_topic("\"[T]\", from:[addr], master:[master], key:[key]") + + if(!handler) + return + + handler = new handler() + return handler.TryRun(input) + +/world/proc/AnnouncePR(announcement, list/payload) + var/static/list/PRcounts = list() //PR id -> number of times announced this round + var/id = "[payload["pull_request"]["id"]]" + if(!PRcounts[id]) + PRcounts[id] = 1 + else + ++PRcounts[id] + if(PRcounts[id] > PR_ANNOUNCEMENTS_PER_ROUND) + return + + var/final_composed = "PR: [announcement]" + for(var/client/C in GLOB.clients) + C.AnnouncePR(final_composed) + +/world/proc/FinishTestRun() + set waitfor = FALSE + var/list/fail_reasons + if(GLOB) + if(GLOB.total_runtimes != 0) + fail_reasons = list("Total runtimes: [GLOB.total_runtimes]") +#ifdef UNIT_TESTS + if(GLOB.failed_any_test) + LAZYADD(fail_reasons, "Unit Tests failed!") +#endif + if(!GLOB.log_directory) + LAZYADD(fail_reasons, "Missing GLOB.log_directory!") + else + fail_reasons = list("Missing GLOB!") + if(!fail_reasons) + text2file("Success!", "[GLOB.log_directory]/clean_run.lk") + else + log_world("Test run failed!\n[fail_reasons.Join("\n")]") + sleep(0) //yes, 0, this'll let Reboot finish and prevent byond memes + qdel(src) //shut it down + +/world/Reboot(reason = 0, fast_track = FALSE) + if (reason || fast_track) //special reboot, do none of the normal stuff + if (usr) + log_admin("[key_name(usr)] Has requested an immediate world restart via client side debugging tools") + message_admins("[key_name_admin(usr)] Has requested an immediate world restart via client side debugging tools") + to_chat(world, "Rebooting World immediately due to host request.") + else + to_chat(world, "Rebooting world...") + Master.Shutdown() //run SS shutdowns + + TgsReboot() + + if(TEST_RUN_PARAMETER in params) + FinishTestRun() + return + + if(TgsAvailable()) + var/do_hard_reboot + // check the hard reboot counter + var/ruhr = CONFIG_GET(number/rounds_until_hard_restart) + switch(ruhr) + if(-1) + do_hard_reboot = FALSE + if(0) + do_hard_reboot = TRUE + else + if(GLOB.restart_counter >= ruhr) + do_hard_reboot = TRUE + else + text2file("[++GLOB.restart_counter]", RESTART_COUNTER_PATH) + do_hard_reboot = FALSE + + if(do_hard_reboot) + log_world("World hard rebooted at [time_stamp()]") + shutdown_logging() // See comment below. + TgsEndProcess() + + log_world("World rebooted at [time_stamp()]") + shutdown_logging() // Past this point, no logging procs can be used, at risk of data loss. + ..() + +/world/Del() + // memory leaks bad + var/num_deleted = 0 + for(var/datum/gas_mixture/GM) + GM.__gasmixture_unregister() + num_deleted++ + log_world("Deallocated [num_deleted] gas mixtures") + ..() + +/world/proc/update_status() + + var/list/features = list() + + if(GLOB.master_mode) + features += GLOB.master_mode + + if (!GLOB.enter_allowed) + features += "closed" + + var/s = "" + var/hostedby + if(config) + var/server_name = CONFIG_GET(string/servername) + if (server_name) + s += "[server_name] — " + features += "[CONFIG_GET(flag/norespawn) ? "no " : ""]respawn" + if(CONFIG_GET(flag/allow_vote_mode)) + features += "vote" + if(CONFIG_GET(flag/allow_ai)) + features += "AI allowed" + hostedby = CONFIG_GET(string/hostedby) + + s += "[station_name()]"; + s += " (" + s += "" //Change this to wherever you want the hub to link to. + s += "Discord" //Replace this with something else. Or ever better, delete it and uncomment the game version. + s += "" + s += ")" + s += " (" + s += "" //Change this to wherever you want the hub to link to. + s += "Github" //Replace this with something else. Or ever better, delete it and uncomment the game version. + s += "" + s += ")" + + var/players = GLOB.clients.len + + var/popcaptext = "" + var/popcap = max(CONFIG_GET(number/extreme_popcap), CONFIG_GET(number/hard_popcap), CONFIG_GET(number/soft_popcap)) + if (popcap) + popcaptext = "/[popcap]" + + if (players > 1) + features += "[players][popcaptext] players" + else if (players > 0) + features += "[players][popcaptext] player" + + game_state = (CONFIG_GET(number/extreme_popcap) && players >= CONFIG_GET(number/extreme_popcap)) //tells the hub if we are full + + if (!host && hostedby) + features += "hosted by [hostedby]" + + if (features) + s += ": [jointext(features, ", ")]" + + status = s + +/world/proc/update_hub_visibility(new_visibility) + if(new_visibility == GLOB.hub_visibility) + return + GLOB.hub_visibility = new_visibility + if(GLOB.hub_visibility) + hub_password = "kMZy3U5jJHSiBQjr" + else + hub_password = "SORRYNOPASSWORD" + +/world/proc/incrementMaxZ() + maxz++ + SSmobs.MaxZChanged() + SSidlenpcpool.MaxZChanged() + world.refresh_atmos_grid() + + +/world/proc/change_fps(new_value = 20) + if(new_value <= 0) + CRASH("change_fps() called with [new_value] new_value.") + if(fps == new_value) + return //No change required. + + fps = new_value + on_tickrate_change() + + +/world/proc/change_tick_lag(new_value = 0.5) + if(new_value <= 0) + CRASH("change_tick_lag() called with [new_value] new_value.") + if(tick_lag == new_value) + return //No change required. + + tick_lag = new_value + on_tickrate_change() + + +/world/proc/on_tickrate_change() + SStimer?.reset_buckets() + + +/world/proc/refresh_atmos_grid() diff --git a/code/modules/NTNet/relays.dm b/code/modules/NTNet/relays.dm index 6289158901e0..0fbb9d0bc0bb 100644 --- a/code/modules/NTNet/relays.dm +++ b/code/modules/NTNet/relays.dm @@ -9,8 +9,6 @@ icon_state = "bus" density = TRUE circuit = /obj/item/circuitboard/machine/ntnet_relay - ui_x = 400 - ui_y = 300 var/datum/ntnet/NTNet = null // This is mostly for backwards reference and to allow varedit modifications from ingame. var/enabled = 1 // Set to 0 if the relay was turned off @@ -64,15 +62,12 @@ SSnetworks.station_network.add_log("Quantum relay switched from overload recovery mode to normal operation mode.") ..() -/obj/machinery/ntnet_relay/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - +/obj/machinery/ntnet_relay/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "NtnetRelay", "NTNet Quantum Relay", ui_x, ui_y, master_ui, state) + ui = new(user, src, "NtnetRelay") ui.open() - /obj/machinery/ntnet_relay/ui_data(mob/user) var/list/data = list() data["enabled"] = enabled @@ -81,7 +76,6 @@ data["dos_crashed"] = dos_failure return data - /obj/machinery/ntnet_relay/ui_act(action, params) if(..()) return diff --git a/code/modules/NTNet/services/_service.dm b/code/modules/NTNet/services/_service.dm index 3622dc38810c..75059d9992bd 100644 --- a/code/modules/NTNet/services/_service.dm +++ b/code/modules/NTNet/services/_service.dm @@ -1,38 +1,38 @@ -/datum/ntnet_service - var/name = "Unidentified Network Service" - var/id - var/list/networks_by_id = list() //Yes we support multinetwork services! - -/datum/ntnet_service/New() - var/datum/component/ntnet_interface/N = AddComponent(/datum/component/ntnet_interface, id, name, FALSE) - id = N.hardware_id - -/datum/ntnet_service/Destroy() - for(var/i in networks_by_id) - var/datum/ntnet/N = i - disconnect(N, TRUE) - networks_by_id = null - return ..() - -/datum/ntnet_service/proc/connect(datum/ntnet/net) - if(!istype(net)) - return FALSE - var/datum/component/ntnet_interface/interface = GetComponent(/datum/component/ntnet_interface) - if(!interface.register_connection(net)) - return FALSE - if(!net.register_service(src)) - interface.unregister_connection(net) - return FALSE - networks_by_id[net.network_id] = net - return TRUE - -/datum/ntnet_service/proc/disconnect(datum/ntnet/net, force = FALSE) - if(!istype(net) || (!net.unregister_service(src) && !force)) - return FALSE - var/datum/component/ntnet_interface/interface = GetComponent(/datum/component/ntnet_interface) - interface.unregister_connection(net) - networks_by_id -= net.network_id - return TRUE - -/datum/ntnet_service/proc/ntnet_intercept(datum/netdata/data, datum/ntnet/net, datum/component/ntnet_interface/sender) - return +/datum/ntnet_service + var/name = "Unidentified Network Service" + var/id + var/list/networks_by_id = list() //Yes we support multinetwork services! + +/datum/ntnet_service/New() + var/datum/component/ntnet_interface/N = AddComponent(/datum/component/ntnet_interface, id, name, FALSE) + id = N.hardware_id + +/datum/ntnet_service/Destroy() + for(var/i in networks_by_id) + var/datum/ntnet/N = i + disconnect(N, TRUE) + networks_by_id = null + return ..() + +/datum/ntnet_service/proc/connect(datum/ntnet/net) + if(!istype(net)) + return FALSE + var/datum/component/ntnet_interface/interface = GetComponent(/datum/component/ntnet_interface) + if(!interface.register_connection(net)) + return FALSE + if(!net.register_service(src)) + interface.unregister_connection(net) + return FALSE + networks_by_id[net.network_id] = net + return TRUE + +/datum/ntnet_service/proc/disconnect(datum/ntnet/net, force = FALSE) + if(!istype(net) || (!net.unregister_service(src) && !force)) + return FALSE + var/datum/component/ntnet_interface/interface = GetComponent(/datum/component/ntnet_interface) + interface.unregister_connection(net) + networks_by_id -= net.network_id + return TRUE + +/datum/ntnet_service/proc/ntnet_intercept(datum/netdata/data, datum/ntnet/net, datum/component/ntnet_interface/sender) + return diff --git a/code/modules/admin/IsBanned.dm b/code/modules/admin/IsBanned.dm index ada9cc654a78..b6d8dd219b52 100644 --- a/code/modules/admin/IsBanned.dm +++ b/code/modules/admin/IsBanned.dm @@ -1,234 +1,243 @@ -//Blocks an attempt to connect before even creating our client datum thing. - -//How many new ckey matches before we revert the stickyban to it's roundstart state -//These are exclusive, so once it goes over one of these numbers, it reverts the ban -#define STICKYBAN_MAX_MATCHES 15 -#define STICKYBAN_MAX_EXISTING_USER_MATCHES 3 //ie, users who were connected before the ban triggered -#define STICKYBAN_MAX_ADMIN_MATCHES 1 - -/world/IsBanned(key, address, computer_id, type, real_bans_only=FALSE) - debug_world_log("isbanned(): '[args.Join("', '")]'") - if (!key || (!real_bans_only && (!address || !computer_id))) - if(real_bans_only) - return FALSE - log_access("Failed Login (invalid data): [key] [address]-[computer_id]") - return list("reason"="invalid login data", "desc"="Error: Could not check ban status, Please try again. Error message: Your computer provided invalid or blank information to the server on connection (byond username, IP, and Computer ID.) Provided information for reference: Username:'[key]' IP:'[address]' Computer ID:'[computer_id]'. (If you continue to get this error, please restart byond or contact byond support.)") - - if (type == "world") - return ..() //shunt world topic banchecks to purely to byond's internal ban system - - var/admin = FALSE - var/ckey = ckey(key) - - var/client/C = GLOB.directory[ckey] - if (C && ckey == C.ckey && computer_id == C.computer_id && address == C.address) - return //don't recheck connected clients. - - //IsBanned can get re-called on a user in certain situations, this prevents that leading to repeated messages to admins. - var/static/list/checkedckeys = list() - //magic voodo to check for a key in a list while also adding that key to the list without having to do two associated lookups - var/message = !checkedckeys[ckey]++ - - if(GLOB.admin_datums[ckey] || GLOB.deadmins[ckey]) - admin = TRUE - - - //Whitelist - if(!real_bans_only && !C && CONFIG_GET(flag/usewhitelist)) - if(!check_whitelist(ckey)) - if (admin) - log_admin("The admin [key] has been allowed to bypass the whitelist") - if (message) - message_admins("The admin [key] has been allowed to bypass the whitelist") - addclientmessage(ckey,"You have been allowed to bypass the whitelist") - else - log_access("Failed Login: [key] - Not on whitelist") - return list("reason"="whitelist", "desc" = "\nReason: You are not on the white list for this server") - - //Guest Checking - if(!real_bans_only && !C && IsGuestKey(key)) - if (CONFIG_GET(flag/guest_ban)) - log_access("Failed Login: [key] - Guests not allowed") - return list("reason"="guest", "desc"="\nReason: Guests not allowed. Please sign in with a byond account.") - if (CONFIG_GET(flag/panic_bunker) && SSdbcore.Connect()) - log_access("Failed Login: [key] - Guests not allowed during panic bunker") - return list("reason"="guest", "desc"="\nReason: Sorry but the server is currently not accepting connections from never before seen players or guests. If you have played on this server with a byond account before, please log in to the byond account you have played from.") - - //Population Cap Checking - var/extreme_popcap = CONFIG_GET(number/extreme_popcap) - if(!real_bans_only && !C && extreme_popcap && !admin) - var/popcap_value = GLOB.clients.len - if(popcap_value >= extreme_popcap && !GLOB.joined_player_list.Find(ckey)) - if(!CONFIG_GET(flag/byond_member_bypass_popcap) || !world.IsSubscribed(ckey, "BYOND")) - log_access("Failed Login: [key] - Population cap reached") - return list("reason"="popcap", "desc"= "\nReason: [CONFIG_GET(string/extreme_popcap_message)]") - - if(CONFIG_GET(flag/sql_enabled)) - if(!SSdbcore.Connect()) - var/msg = "Ban database connection failure. Key [ckey] not checked" - log_world(msg) - if (message) - message_admins(msg) - else - var/list/ban_details = is_banned_from_with_details(ckey, address, computer_id, "Server") - for(var/i in ban_details) - if(admin) - if(text2num(i["applies_to_admins"])) - var/msg = "Admin [key] is admin banned, and has been disallowed access." - log_admin(msg) - if (message) - message_admins(msg) - else - var/msg = "Admin [key] has been allowed to bypass a matching non-admin ban on [i["key"]] [i["ip"]]-[i["computerid"]]." - log_admin(msg) - if (message) - message_admins(msg) - addclientmessage(ckey,"Admin [key] has been allowed to bypass a matching non-admin ban on [i["key"]] [i["ip"]]-[i["computerid"]].") - continue - var/expires = "This is a permanent ban." - if(i["expiration_time"]) - expires = " The ban is for [DisplayTimeText(text2num(i["duration"]) MINUTES)] and expires on [i["expiration_time"]] (server time)." - var/desc = {"You, or another user of this computer or connection ([i["key"]]) is banned from playing here. - The ban reason is: [i["reason"]] - This ban (BanID #[i["id"]]) was applied by [i["admin_key"]] on [i["bantime"]] during round ID [i["round_id"]]. - [expires]"} - log_access("Failed Login: [key] [computer_id] [address] - Banned (#[i["id"]])") - return list("reason"="Banned","desc"="[desc]") - if (admin) - if (GLOB.directory[ckey]) - return - - //oh boy, so basically, because of a bug in byond, sometimes stickyban matches don't trigger here, so we can't exempt admins. - // Whitelisting the ckey with the byond whitelist field doesn't work. - // So we instead have to remove every stickyban than later re-add them. - if (!length(GLOB.stickybanadminexemptions)) - for (var/banned_ckey in world.GetConfig("ban")) - GLOB.stickybanadmintexts[banned_ckey] = world.GetConfig("ban", banned_ckey) - world.SetConfig("ban", banned_ckey, null) - if (!SSstickyban.initialized) - return - GLOB.stickybanadminexemptions[ckey] = world.time - stoplag() // sleep a byond tick - GLOB.stickbanadminexemptiontimerid = addtimer(CALLBACK(GLOBAL_PROC, /proc/restore_stickybans), 5 SECONDS, TIMER_STOPPABLE|TIMER_UNIQUE|TIMER_OVERRIDE) - return - var/list/ban = ..() //default pager ban stuff - - if (ban) - if (!admin) - . = ban - if (real_bans_only) - return - var/bannedckey = "ERROR" - if (ban["ckey"]) - bannedckey = ban["ckey"] - - var/newmatch = FALSE - var/list/cachedban = SSstickyban.cache[bannedckey] - //rogue ban in the process of being reverted. - if (cachedban && (cachedban["reverting"] || cachedban["timeout"])) - world.SetConfig("ban", bannedckey, null) - return null - - if (cachedban && ckey != bannedckey) - newmatch = TRUE - if (cachedban["keys"]) - if (cachedban["keys"][ckey]) - newmatch = FALSE - if (cachedban["matches_this_round"][ckey]) - newmatch = FALSE - - if (newmatch && cachedban) - var/list/newmatches = cachedban["matches_this_round"] - var/list/pendingmatches = cachedban["matches_this_round"] - var/list/newmatches_connected = cachedban["existing_user_matches_this_round"] - var/list/newmatches_admin = cachedban["admin_matches_this_round"] - - if (C) - newmatches_connected[ckey] = ckey - newmatches_connected = cachedban["existing_user_matches_this_round"] - pendingmatches[ckey] = ckey - sleep(STICKYBAN_ROGUE_CHECK_TIME) - pendingmatches -= ckey - if (admin) - newmatches_admin[ckey] = ckey - - if (cachedban["reverting"] || cachedban["timeout"]) - return null - - newmatches[ckey] = ckey - - - if (\ - newmatches.len+pendingmatches.len > STICKYBAN_MAX_MATCHES || \ - newmatches_connected.len > STICKYBAN_MAX_EXISTING_USER_MATCHES || \ - newmatches_admin.len > STICKYBAN_MAX_ADMIN_MATCHES \ - ) - - var/action - if (ban["fromdb"]) - cachedban["timeout"] = TRUE - action = "putting it on timeout for the remainder of the round" - else - cachedban["reverting"] = TRUE - action = "reverting to its roundstart state" - - world.SetConfig("ban", bannedckey, null) - - //we always report this - log_game("Stickyban on [bannedckey] detected as rogue, [action]") - message_admins("Stickyban on [bannedckey] detected as rogue, [action]") - //do not convert to timer. - spawn (5) - world.SetConfig("ban", bannedckey, null) - sleep(1) - world.SetConfig("ban", bannedckey, null) - if (!ban["fromdb"]) - cachedban = cachedban.Copy() //so old references to the list still see the ban as reverting - cachedban["matches_this_round"] = list() - cachedban["existing_user_matches_this_round"] = list() - cachedban["admin_matches_this_round"] = list() - cachedban -= "reverting" - SSstickyban.cache[bannedckey] = cachedban - world.SetConfig("ban", bannedckey, list2stickyban(cachedban)) - return null - - if (ban["fromdb"]) - if(SSdbcore.Connect()) - INVOKE_ASYNC(SSdbcore, /datum/controller/subsystem/dbcore/proc.QuerySelect, list( - SSdbcore.NewQuery("INSERT INTO [format_table_name("stickyban_matched_ckey")] (matched_ckey, stickyban) VALUES ('[sanitizeSQL(ckey)]', '[sanitizeSQL(bannedckey)]') ON DUPLICATE KEY UPDATE last_matched = now()"), - SSdbcore.NewQuery("INSERT INTO [format_table_name("stickyban_matched_ip")] (matched_ip, stickyban) VALUES ( INET_ATON('[sanitizeSQL(address)]'), '[sanitizeSQL(bannedckey)]') ON DUPLICATE KEY UPDATE last_matched = now()"), - SSdbcore.NewQuery("INSERT INTO [format_table_name("stickyban_matched_cid")] (matched_cid, stickyban) VALUES ('[sanitizeSQL(computer_id)]', '[sanitizeSQL(bannedckey)]') ON DUPLICATE KEY UPDATE last_matched = now()") - ), FALSE, TRUE) - - - //byond will not trigger isbanned() for "global" host bans, - //ie, ones where the "apply to this game only" checkbox is not checked (defaults to not checked) - //So it's safe to let admins walk thru host/sticky bans here - if (admin) - log_admin("The admin [key] has been allowed to bypass a matching host/sticky ban on [bannedckey]") - if (message) - message_admins("The admin [key] has been allowed to bypass a matching host/sticky ban on [bannedckey]") - addclientmessage(ckey,"You have been allowed to bypass a matching host/sticky ban on [bannedckey]") - return null - - if (C) //user is already connected!. - to_chat(C, "You are about to get disconnected for matching a sticky ban after you connected. If this turns out to be the ban evasion detection system going haywire, we will automatically detect this and revert the matches. if you feel that this is the case, please wait EXACTLY 6 seconds then reconnect using file -> reconnect to see if the match was automatically reversed.", confidential = TRUE) - - var/desc = "\nReason:(StickyBan) You, or another user of this computer or connection ([bannedckey]) is banned from playing here. The ban reason is:\n[ban["message"]]\nThis ban was applied by [ban["admin"]]\nThis is a BanEvasion Detection System ban, if you think this ban is a mistake, please wait EXACTLY 6 seconds, then try again before filing an appeal.\n" - . = list("reason" = "Stickyban", "desc" = desc) - log_access("Failed Login: [key] [computer_id] [address] - StickyBanned [ban["message"]] Target Username: [bannedckey] Placed by [ban["admin"]]") - - return . - -/proc/restore_stickybans() - for (var/banned_ckey in GLOB.stickybanadmintexts) - world.SetConfig("ban", banned_ckey, GLOB.stickybanadmintexts[banned_ckey]) - GLOB.stickybanadminexemptions = list() - GLOB.stickybanadmintexts = list() - if (GLOB.stickbanadminexemptiontimerid) - deltimer(GLOB.stickbanadminexemptiontimerid) - GLOB.stickbanadminexemptiontimerid = null - -#undef STICKYBAN_MAX_MATCHES -#undef STICKYBAN_MAX_EXISTING_USER_MATCHES -#undef STICKYBAN_MAX_ADMIN_MATCHES +//Blocks an attempt to connect before even creating our client datum thing. + +//How many new ckey matches before we revert the stickyban to it's roundstart state +//These are exclusive, so once it goes over one of these numbers, it reverts the ban +#define STICKYBAN_MAX_MATCHES 15 +#define STICKYBAN_MAX_EXISTING_USER_MATCHES 3 //ie, users who were connected before the ban triggered +#define STICKYBAN_MAX_ADMIN_MATCHES 1 + +/world/IsBanned(key, address, computer_id, type, real_bans_only=FALSE) + debug_world_log("isbanned(): '[args.Join("', '")]'") + if (!key || (!real_bans_only && (!address || !computer_id))) + if(real_bans_only) + return FALSE + log_access("Failed Login (invalid data): [key] [address]-[computer_id]") + return list("reason"="invalid login data", "desc"="Error: Could not check ban status, Please try again. Error message: Your computer provided invalid or blank information to the server on connection (byond username, IP, and Computer ID.) Provided information for reference: Username:'[key]' IP:'[address]' Computer ID:'[computer_id]'. (If you continue to get this error, please restart byond or contact byond support.)") + + if (type == "world") + return ..() //shunt world topic banchecks to purely to byond's internal ban system + + var/admin = FALSE + var/ckey = ckey(key) + + var/client/C = GLOB.directory[ckey] + if (C && ckey == C.ckey && computer_id == C.computer_id && address == C.address) + return //don't recheck connected clients. + + //IsBanned can get re-called on a user in certain situations, this prevents that leading to repeated messages to admins. + var/static/list/checkedckeys = list() + //magic voodo to check for a key in a list while also adding that key to the list without having to do two associated lookups + var/message = !checkedckeys[ckey]++ + + if(GLOB.admin_datums[ckey] || GLOB.deadmins[ckey]) + admin = TRUE + + + //Whitelist + if(!real_bans_only && !C && CONFIG_GET(flag/usewhitelist)) + if(!check_whitelist(ckey)) + if (admin) + log_admin("The admin [key] has been allowed to bypass the whitelist") + if (message) + message_admins("The admin [key] has been allowed to bypass the whitelist") + addclientmessage(ckey,"You have been allowed to bypass the whitelist") + else + log_access("Failed Login: [key] - Not on whitelist") + return list("reason"="whitelist", "desc" = "\nReason: You are not on the white list for this server") + + //Guest Checking + if(!real_bans_only && !C && IsGuestKey(key)) + if (CONFIG_GET(flag/guest_ban)) + log_access("Failed Login: [key] - Guests not allowed") + return list("reason"="guest", "desc"="\nReason: Guests not allowed. Please sign in with a byond account.") + if (CONFIG_GET(flag/panic_bunker) && SSdbcore.Connect()) + log_access("Failed Login: [key] - Guests not allowed during panic bunker") + return list("reason"="guest", "desc"="\nReason: Sorry but the server is currently not accepting connections from never before seen players or guests. If you have played on this server with a byond account before, please log in to the byond account you have played from.") + + //Population Cap Checking + var/extreme_popcap = CONFIG_GET(number/extreme_popcap) + if(!real_bans_only && !C && extreme_popcap && !admin) + var/popcap_value = GLOB.clients.len + if(popcap_value >= extreme_popcap && !GLOB.joined_player_list.Find(ckey)) + if(!CONFIG_GET(flag/byond_member_bypass_popcap) || !world.IsSubscribed(ckey, "BYOND")) + log_access("Failed Login: [key] - Population cap reached") + return list("reason"="popcap", "desc"= "\nReason: [CONFIG_GET(string/extreme_popcap_message)]") + + if(CONFIG_GET(flag/sql_enabled)) + if(!SSdbcore.Connect()) + var/msg = "Ban database connection failure. Key [ckey] not checked" + log_world(msg) + if (message) + message_admins(msg) + else + var/list/ban_details = is_banned_from_with_details(ckey, address, computer_id, "Server") + for(var/i in ban_details) + if(admin) + if(text2num(i["applies_to_admins"])) + var/msg = "Admin [key] is admin banned, and has been disallowed access." + log_admin(msg) + if (message) + message_admins(msg) + else + var/msg = "Admin [key] has been allowed to bypass a matching non-admin ban on [i["key"]] [i["ip"]]-[i["computerid"]]." + log_admin(msg) + if (message) + message_admins(msg) + addclientmessage(ckey,"Admin [key] has been allowed to bypass a matching non-admin ban on [i["key"]] [i["ip"]]-[i["computerid"]].") + continue + var/expires = "This is a permanent ban." + if(i["expiration_time"]) + expires = " The ban is for [DisplayTimeText(text2num(i["duration"]) MINUTES)] and expires on [i["expiration_time"]] (server time)." + var/desc = {"You, or another user of this computer or connection ([i["key"]]) is banned from playing here. + The ban reason is: [i["reason"]] + This ban (BanID #[i["id"]]) was applied by [i["admin_key"]] on [i["bantime"]] during round ID [i["round_id"]]. + [expires]"} + log_access("Failed Login: [key] [computer_id] [address] - Banned (#[i["id"]])") + return list("reason"="Banned","desc"="[desc]") + if (admin) + if (GLOB.directory[ckey]) + return + + //oh boy, so basically, because of a bug in byond, sometimes stickyban matches don't trigger here, so we can't exempt admins. + // Whitelisting the ckey with the byond whitelist field doesn't work. + // So we instead have to remove every stickyban than later re-add them. + if (!length(GLOB.stickybanadminexemptions)) + for (var/banned_ckey in world.GetConfig("ban")) + GLOB.stickybanadmintexts[banned_ckey] = world.GetConfig("ban", banned_ckey) + world.SetConfig("ban", banned_ckey, null) + if (!SSstickyban.initialized) + return + GLOB.stickybanadminexemptions[ckey] = world.time + stoplag() // sleep a byond tick + GLOB.stickbanadminexemptiontimerid = addtimer(CALLBACK(GLOBAL_PROC, /proc/restore_stickybans), 5 SECONDS, TIMER_STOPPABLE|TIMER_UNIQUE|TIMER_OVERRIDE) + return + var/list/ban = ..() //default pager ban stuff + + if (ban) + if (!admin) + . = ban + if (real_bans_only) + return + var/bannedckey = "ERROR" + if (ban["ckey"]) + bannedckey = ban["ckey"] + + var/newmatch = FALSE + var/list/cachedban = SSstickyban.cache[bannedckey] + //rogue ban in the process of being reverted. + if (cachedban && (cachedban["reverting"] || cachedban["timeout"])) + world.SetConfig("ban", bannedckey, null) + return null + + if (cachedban && ckey != bannedckey) + newmatch = TRUE + if (cachedban["keys"]) + if (cachedban["keys"][ckey]) + newmatch = FALSE + if (cachedban["matches_this_round"][ckey]) + newmatch = FALSE + + if (newmatch && cachedban) + var/list/newmatches = cachedban["matches_this_round"] + var/list/pendingmatches = cachedban["matches_this_round"] + var/list/newmatches_connected = cachedban["existing_user_matches_this_round"] + var/list/newmatches_admin = cachedban["admin_matches_this_round"] + + if (C) + newmatches_connected[ckey] = ckey + newmatches_connected = cachedban["existing_user_matches_this_round"] + pendingmatches[ckey] = ckey + sleep(STICKYBAN_ROGUE_CHECK_TIME) + pendingmatches -= ckey + if (admin) + newmatches_admin[ckey] = ckey + + if (cachedban["reverting"] || cachedban["timeout"]) + return null + + newmatches[ckey] = ckey + + + if (\ + newmatches.len+pendingmatches.len > STICKYBAN_MAX_MATCHES || \ + newmatches_connected.len > STICKYBAN_MAX_EXISTING_USER_MATCHES || \ + newmatches_admin.len > STICKYBAN_MAX_ADMIN_MATCHES \ + ) + + var/action + if (ban["fromdb"]) + cachedban["timeout"] = TRUE + action = "putting it on timeout for the remainder of the round" + else + cachedban["reverting"] = TRUE + action = "reverting to its roundstart state" + + world.SetConfig("ban", bannedckey, null) + + //we always report this + log_game("Stickyban on [bannedckey] detected as rogue, [action]") + message_admins("Stickyban on [bannedckey] detected as rogue, [action]") + //do not convert to timer. + spawn (5) + world.SetConfig("ban", bannedckey, null) + sleep(1) + world.SetConfig("ban", bannedckey, null) + if (!ban["fromdb"]) + cachedban = cachedban.Copy() //so old references to the list still see the ban as reverting + cachedban["matches_this_round"] = list() + cachedban["existing_user_matches_this_round"] = list() + cachedban["admin_matches_this_round"] = list() + cachedban -= "reverting" + SSstickyban.cache[bannedckey] = cachedban + world.SetConfig("ban", bannedckey, list2stickyban(cachedban)) + return null + + if (ban["fromdb"]) + if(SSdbcore.Connect()) + INVOKE_ASYNC(SSdbcore, /datum/controller/subsystem/dbcore/proc.QuerySelect, list( + SSdbcore.NewQuery( + "INSERT INTO [format_table_name("stickyban_matched_ckey")] (matched_ckey, stickyban) VALUES (:ckey, :bannedckey) ON DUPLICATE KEY UPDATE last_matched = now()", + list("ckey" = ckey, "bannedckey" = bannedckey) + ), + SSdbcore.NewQuery( + "INSERT INTO [format_table_name("stickyban_matched_ip")] (matched_ip, stickyban) VALUES (INET_ATON(:address), :bannedckey) ON DUPLICATE KEY UPDATE last_matched = now()", + list("address" = address, "bannedckey" = bannedckey) + ), + SSdbcore.NewQuery( + "INSERT INTO [format_table_name("stickyban_matched_cid")] (matched_cid, stickyban) VALUES (:computer_id, :bannedckey) ON DUPLICATE KEY UPDATE last_matched = now()", + list("computer_id" = computer_id, "bannedckey" = bannedckey) + ) + ), FALSE, TRUE) + + + //byond will not trigger isbanned() for "global" host bans, + //ie, ones where the "apply to this game only" checkbox is not checked (defaults to not checked) + //So it's safe to let admins walk thru host/sticky bans here + if (admin) + log_admin("The admin [key] has been allowed to bypass a matching host/sticky ban on [bannedckey]") + if (message) + message_admins("The admin [key] has been allowed to bypass a matching host/sticky ban on [bannedckey]") + addclientmessage(ckey,"You have been allowed to bypass a matching host/sticky ban on [bannedckey]") + return null + + if (C) //user is already connected!. + to_chat(C, "You are about to get disconnected for matching a sticky ban after you connected. If this turns out to be the ban evasion detection system going haywire, we will automatically detect this and revert the matches. if you feel that this is the case, please wait EXACTLY 6 seconds then reconnect using file -> reconnect to see if the match was automatically reversed.", confidential = TRUE) + + var/desc = "\nReason:(StickyBan) You, or another user of this computer or connection ([bannedckey]) is banned from playing here. The ban reason is:\n[ban["message"]]\nThis ban was applied by [ban["admin"]]\nThis is a BanEvasion Detection System ban, if you think this ban is a mistake, please wait EXACTLY 6 seconds, then try again before filing an appeal.\n" + . = list("reason" = "Stickyban", "desc" = desc) + log_access("Failed Login: [key] [computer_id] [address] - StickyBanned [ban["message"]] Target Username: [bannedckey] Placed by [ban["admin"]]") + + return . + +/proc/restore_stickybans() + for (var/banned_ckey in GLOB.stickybanadmintexts) + world.SetConfig("ban", banned_ckey, GLOB.stickybanadmintexts[banned_ckey]) + GLOB.stickybanadminexemptions = list() + GLOB.stickybanadmintexts = list() + if (GLOB.stickbanadminexemptiontimerid) + deltimer(GLOB.stickbanadminexemptiontimerid) + GLOB.stickbanadminexemptiontimerid = null + +#undef STICKYBAN_MAX_MATCHES +#undef STICKYBAN_MAX_EXISTING_USER_MATCHES +#undef STICKYBAN_MAX_ADMIN_MATCHES diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm index 428de589274e..074062bb1a5a 100644 --- a/code/modules/admin/admin.dm +++ b/code/modules/admin/admin.dm @@ -1,1026 +1,1042 @@ - -//////////////////////////////// -/proc/message_admins(msg) - msg = "ADMIN LOG: [msg]" - to_chat(GLOB.admins, msg, confidential = TRUE) - -/proc/relay_msg_admins(msg) - msg = "RELAY: [msg]" - to_chat(GLOB.admins, msg, confidential = TRUE) - - -///////////////////////////////////////////////////////////////////////////////////////////////Panels - -/datum/admins/proc/show_player_panel(mob/M in GLOB.mob_list) - set category = "Admin - Game" - set name = "Show Player Panel" - set desc="Edit player (respawn, ban, heal, etc)" - - if(!check_rights()) - return - - log_admin("[key_name(usr)] checked the individual player panel for [key_name(M)][isobserver(usr)?"":" while in game"].") - - if(!M) - to_chat(usr, "You seem to be selecting a mob that doesn't exist anymore.", confidential = TRUE) - return - - var/body = "Options for [M.key]" - body += "Options panel for [M]" - if(M.client) - body += " played by [M.client] " - body += "[M.client.holder ? M.client.holder.rank : "Player"]" - if(CONFIG_GET(flag/use_exp_tracking)) - body += "" + M.client.get_exp_living(FALSE) + "" - - if(isnewplayer(M)) - body += " Hasn't Entered Game " - else - body += " Heal " - - if(M.client) - body += "
                        First Seen: [M.client.player_join_date]Byond account registered on: [M.client.account_join_date]" - body += "

                        Show related accounts by: " - body += " CID" - body += "IP " - body += "

                        CentCom Galactic Ban DB: " - if(CONFIG_GET(string/centcom_ban_db)) - body += "Search" - else - body += "Disabled" - var/rep = 0 - rep += SSpersistence.antag_rep[M.ckey] - body += "

                        Antagonist reputation: [rep]" - body += "
                        + " - body += "- " - body += "= " - body += "0" - //WaspStation Begin - Metacoins - var/metabalance = M.client.get_metabalance() - body += "

                        [CONFIG_GET(string/metacurrency_name)]s: [metabalance] " - body += "
                        + " - body += "- " - body += "= " - body += "0" - //Antag Tokens - var/antag_tokens = M.client.get_antag_token_count() - body += "

                        Antag Tokens: [antag_tokens]" - body += "
                        + " - body += "- " - body += "= " - body += "0" - //WaspStation End - var/full_version = "Unknown" - if(M.client.byond_version) - full_version = "[M.client.byond_version].[M.client.byond_build ? M.client.byond_build : "xxx"]" - body += "
                        Byond version: [full_version]
                        " - - - body += "

                        " - body += "VV - " - if(M.mind) - body += "TP - " - else - body += "Init Mind - " - if (iscyborg(M)) - body += "BP - " - body += "PM - " - body += "SM - " - if (ishuman(M) && M.mind) - body += "HM - " - body += "FLW - " - //Default to client logs if available - var/source = LOGSRC_MOB - if(M.client) - source = LOGSRC_CLIENT - body += "LOGS
                        " - - body += "Mob type = [M.type]

                        " - - body += "Kick" - if(M.client) - body += "Ban" - else - body += "Ban" - - body += "Notes / Watchlist" - if(M.client) - body += "| Prison" - body += "\ Send back to Lobby" - var/muted = M.client.prefs.muted - body += "
                        Mute: " - body += "IC" - body += "OOC" - body += "PRAY" - body += "ADMINHELP" - body += "MENTORHELP" - body += "DEADCHAT" - body += "(toggle all)" - - body += "

                        " - body += "Jump to" - body += "Get" - body += "Send To" - - body += "

                        " - body += "Traitor panel" - body += "Narrate to" - body += "Subtle message" - body += "Play sound to" - body += "Language Menu" - - if (M.client) - if(!isnewplayer(M)) - body += "

                        " - body += "Transformation:" - body += "
                        " - - //Human - if(ishuman(M)) - body += "Human" - else - body += "Humanize" - - //Monkey - if(ismonkey(M)) - body += "Monkeyized" - else - body += "Monkeyize" - - //Corgi - if(iscorgi(M)) - body += "Corgized" - else - body += "Corgize" - - //AI / Cyborg - if(isAI(M)) - body += "Is an AI " - else if(ishuman(M)) - body += "Make AI" - body += "Make Robot" - body += "Make Alien" - body += "Make Slime" - body += "Make Blob" - - //Simple Animals - if(isanimal(M)) - body += "Re-Animalize" - else - body += "Animalize" - - body += "

                        " - body += "Rudimentary transformation:
                        These transformations only create a new mob type and copy stuff over. They do not take into account MMIs and similar mob-specific things. The buttons in 'Transformations' are preferred, when possible.

                        " - body += "Observer" - body += " Alien: Drone, " - body += "Hunter, " - body += "Sentinel, " - body += "Praetorian, " - body += "Queen, " - body += "Larva " - body += "Human " - body += " slime: Baby, " - body += "Adult " - body += "Monkey" - body += "Cyborg" - body += "Cat" - body += "Runtime" - body += "Corgi" - body += "Ian" - body += "Crab" - body += "Coffee" - body += " Construct: Juggernaut , " - body += "Artificer , " - body += "Wraith " - body += "Shade" - body += "
                        " - - if (M.client) - body += "

                        " - body += "Other actions:" - body += "
                        " - body += "Forcesay" - body += "Thunderdome 1" - body += "Thunderdome 2" - body += "Thunderdome Admin" - body += "Thunderdome Observer" - - body += "
                        " - body += "" - - - //WaspStation Begin - Better Looking Admin Panels - var/datum/browser/popup = new(usr, "adminplayeropts-[REF(M)]", "
                        Options for [M.key]
                        ", 700, 600) - popup.set_content(body) - popup.open(0) - //WaspStation End - - /*WaspStation Begin - Better Looking Admin Panels - usr << browse(body, "window=adminplayeropts-[REF(M)];size=550x515") - */ - - SSblackbox.record_feedback("tally", "admin_verb", 1, "Player Panel") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - -/datum/admins/proc/access_news_network() //MARKER - set category = "Admin - Events" - set name = "Access Newscaster Network" - set desc = "Allows you to view, add and edit news feeds." - - if (!istype(src, /datum/admins)) - src = usr.client.holder - if (!istype(src, /datum/admins)) - to_chat(usr, "Error: you are not an admin!", confidential = TRUE) - return - var/dat - dat = text("Admin Newscaster

                        Admin Newscaster Unit

                        ") - - switch(admincaster_screen) - if(0) - dat += "Welcome to the admin newscaster.
                        Here you can add, edit and censor every newspiece on the network." - dat += "
                        Feed channels and stories entered through here will be uneditable and handled as official news by the rest of the units." - dat += "
                        Note that this panel allows full freedom over the news network, there are no constrictions except the few basic ones. Don't break things!" - if(GLOB.news_network.wanted_issue.active) - dat+= "
                        Read Wanted Issue" - dat+= "

                        Create Feed Channel" - dat+= "
                        View Feed Channels" - dat+= "
                        Submit new Feed story" - dat+= "

                        Exit" - var/wanted_already = 0 - if(GLOB.news_network.wanted_issue.active) - wanted_already = 1 - dat+="
                        Feed Security functions:
                        " - dat+="
                        [(wanted_already) ? ("Manage") : ("Publish")] \"Wanted\" Issue" - dat+="
                        Censor Feed Stories" - dat+="
                        Mark Feed Channel with Nanotrasen D-Notice (disables and locks the channel)." - dat+="

                        The newscaster recognises you as:
                        [src.admin_signature]
                        " - if(1) - dat+= "Station Feed Channels
                        " - if( !length(GLOB.news_network.network_channels) ) - dat+="No active channels found..." - else - for(var/datum/newscaster/feed_channel/CHANNEL in GLOB.news_network.network_channels) - if(CHANNEL.is_admin_channel) - dat+="[CHANNEL.channel_name]
                        " - else - dat+="[CHANNEL.channel_name] [(CHANNEL.censored) ? ("***") : ""]
                        " - dat+="

                        Refresh" - dat+="
                        Back" - if(2) - dat+="Creating new Feed Channel..." - dat+="
                        Channel Name: [src.admincaster_feed_channel.channel_name]
                        " - dat+="Channel Author: [src.admin_signature]
                        " - dat+="Will Accept Public Feeds: [(src.admincaster_feed_channel.locked) ? ("NO") : ("YES")]

                        " - dat+="
                        Submit

                        Cancel
                        " - if(3) - dat+="Creating new Feed Message..." - dat+="
                        Receiving Channel: [src.admincaster_feed_channel.channel_name]
                        " //MARK - dat+="Message Author: [src.admin_signature]
                        " - dat+="Message Body: [src.admincaster_feed_message.returnBody(-1)]
                        " - dat+="
                        Submit

                        Cancel
                        " - if(4) - dat+="Feed story successfully submitted to [src.admincaster_feed_channel.channel_name].

                        " - dat+="
                        Return
                        " - if(5) - dat+="Feed Channel [src.admincaster_feed_channel.channel_name] created successfully.

                        " - dat+="
                        Return
                        " - if(6) - dat+="ERROR: Could not submit Feed story to Network.

                        " - if(src.admincaster_feed_channel.channel_name=="") - dat+="•Invalid receiving channel name.
                        " - if(src.admincaster_feed_message.returnBody(-1) == "" || src.admincaster_feed_message.returnBody(-1) == "REDACTED") - dat+="•Invalid message body.
                        " - dat+="
                        Return
                        " - if(7) - dat+="ERROR: Could not submit Feed Channel to Network.

                        " - if(src.admincaster_feed_channel.channel_name =="" || src.admincaster_feed_channel.channel_name == "REDACTED") - dat+="•Invalid channel name.
                        " - var/check = 0 - for(var/datum/newscaster/feed_channel/FC in GLOB.news_network.network_channels) - if(FC.channel_name == src.admincaster_feed_channel.channel_name) - check = 1 - break - if(check) - dat+="•Channel name already in use.
                        " - dat+="
                        Return
                        " - if(9) - dat+="[admincaster_feed_channel.channel_name]: created by: [admincaster_feed_channel.returnAuthor(-1)]
                        " - if(src.admincaster_feed_channel.censored) - dat+="ATTENTION: This channel has been deemed as threatening to the welfare of the station, and marked with a Nanotrasen D-Notice.
                        " - dat+="No further feed story additions are allowed while the D-Notice is in effect.

                        " - else - if( !length(src.admincaster_feed_channel.messages) ) - dat+="No feed messages found in channel...
                        " - else - var/i = 0 - for(var/datum/newscaster/feed_message/MESSAGE in src.admincaster_feed_channel.messages) - i++ - dat+="-[MESSAGE.returnBody(-1)]
                        " - if(MESSAGE.img) - usr << browse_rsc(MESSAGE.img, "tmp_photo[i].png") - dat+="

                        " - dat+="Story by [MESSAGE.returnAuthor(-1)]
                        " - dat+="[MESSAGE.comments.len] comment[MESSAGE.comments.len > 1 ? "s" : ""]:
                        " - for(var/datum/newscaster/feed_comment/comment in MESSAGE.comments) - dat+="[comment.body]
                        [comment.author] [comment.time_stamp]
                        " - dat+="
                        " - dat+="

                        Refresh" - dat+="
                        Back" - if(10) - dat+="Nanotrasen Feed Censorship Tool
                        " - dat+="NOTE: Due to the nature of news Feeds, total deletion of a Feed Story is not possible.
                        " - dat+="Keep in mind that users attempting to view a censored feed will instead see the REDACTED tag above it.
                        " - dat+="
                        Select Feed channel to get Stories from:
                        " - if(!length(GLOB.news_network.network_channels)) - dat+="No feed channels found active...
                        " - else - for(var/datum/newscaster/feed_channel/CHANNEL in GLOB.news_network.network_channels) - dat+="[CHANNEL.channel_name] [(CHANNEL.censored) ? ("***") : ""]
                        " - dat+="
                        Cancel" - if(11) - dat+="Nanotrasen D-Notice Handler
                        " - dat+="A D-Notice is to be bestowed upon the channel if the handling Authority deems it as harmful for the station's" - dat+="morale, integrity or disciplinary behaviour. A D-Notice will render a channel unable to be updated by anyone, without deleting any feed" - dat+="stories it might contain at the time. You can lift a D-Notice if you have the required access at any time.
                        " - if(!length(GLOB.news_network.network_channels)) - dat+="No feed channels found active...
                        " - else - for(var/datum/newscaster/feed_channel/CHANNEL in GLOB.news_network.network_channels) - dat+="[CHANNEL.channel_name] [(CHANNEL.censored) ? ("***") : ""]
                        " - - dat+="
                        Back" - if(12) - dat+="[src.admincaster_feed_channel.channel_name]: created by: [src.admincaster_feed_channel.returnAuthor(-1)]
                        " - dat+="[(src.admincaster_feed_channel.authorCensor) ? ("Undo Author censorship") : ("Censor channel Author")]
                        " - - if( !length(src.admincaster_feed_channel.messages) ) - dat+="No feed messages found in channel...
                        " - else - for(var/datum/newscaster/feed_message/MESSAGE in src.admincaster_feed_channel.messages) - dat+="-[MESSAGE.returnBody(-1)]
                        Story by [MESSAGE.returnAuthor(-1)]
                        " - dat+="[(MESSAGE.bodyCensor) ? ("Undo story censorship") : ("Censor story")] - [(MESSAGE.authorCensor) ? ("Undo Author Censorship") : ("Censor message Author")]
                        " - dat+="[MESSAGE.comments.len] comment[MESSAGE.comments.len > 1 ? "s" : ""]: [MESSAGE.locked ? "Unlock" : "Lock"]
                        " - for(var/datum/newscaster/feed_comment/comment in MESSAGE.comments) - dat+="[comment.body] X
                        [comment.author] [comment.time_stamp]
                        " - dat+="
                        Back" - if(13) - dat+="[src.admincaster_feed_channel.channel_name]: created by: [src.admincaster_feed_channel.returnAuthor(-1)]
                        " - dat+="Channel messages listed below. If you deem them dangerous to the station, you can Bestow a D-Notice upon the channel.
                        " - if(src.admincaster_feed_channel.censored) - dat+="ATTENTION: This channel has been deemed as threatening to the welfare of the station, and marked with a Nanotrasen D-Notice.
                        " - dat+="No further feed story additions are allowed while the D-Notice is in effect.

                        " - else - if( !length(src.admincaster_feed_channel.messages) ) - dat+="No feed messages found in channel...
                        " - else - for(var/datum/newscaster/feed_message/MESSAGE in src.admincaster_feed_channel.messages) - dat+="-[MESSAGE.returnBody(-1)]
                        Story by [MESSAGE.returnAuthor(-1)]
                        " - dat+="
                        Back" - if(14) - dat+="Wanted Issue Handler:" - var/wanted_already = 0 - var/end_param = 1 - if(GLOB.news_network.wanted_issue.active) - wanted_already = 1 - end_param = 2 - if(wanted_already) - dat+="
                        A wanted issue is already in Feed Circulation. You can edit or cancel it below.
                        " - dat+="
                        " - dat+="Criminal Name: [src.admincaster_wanted_message.criminal]
                        " - dat+="Description: [src.admincaster_wanted_message.body]
                        " - if(wanted_already) - dat+="Wanted Issue created by:[GLOB.news_network.wanted_issue.scannedUser]
                        " - else - dat+="Wanted Issue will be created under prosecutor:[src.admin_signature]
                        " - dat+="
                        [(wanted_already) ? ("Edit Issue") : ("Submit")]" - if(wanted_already) - dat+="
                        Take down Issue" - dat+="
                        Cancel" - if(15) - dat+="Wanted issue for [src.admincaster_wanted_message.criminal] is now in Network Circulation.

                        " - dat+="
                        Return
                        " - if(16) - dat+="ERROR: Wanted Issue rejected by Network.

                        " - if(src.admincaster_wanted_message.criminal =="" || src.admincaster_wanted_message.criminal == "REDACTED") - dat+="•Invalid name for person wanted.
                        " - if(src.admincaster_wanted_message.body == "" || src.admincaster_wanted_message.body == "REDACTED") - dat+="•Invalid description.
                        " - dat+="
                        Return
                        " - if(17) - dat+="Wanted Issue successfully deleted from Circulation
                        " - dat+="
                        Return
                        " - if(18) - dat+="-- STATIONWIDE WANTED ISSUE --
                        Submitted by: [GLOB.news_network.wanted_issue.scannedUser]
                        " - dat+="Criminal: [GLOB.news_network.wanted_issue.criminal]
                        " - dat+="Description: [GLOB.news_network.wanted_issue.body]
                        " - dat+="Photo:: " - if(GLOB.news_network.wanted_issue.img) - usr << browse_rsc(GLOB.news_network.wanted_issue.img, "tmp_photow.png") - dat+="
                        " - else - dat+="None" - dat+="
                        Back
                        " - if(19) - dat+="Wanted issue for [src.admincaster_wanted_message.criminal] successfully edited.

                        " - dat+="
                        Return
                        " - else - dat+="I'm sorry to break your immersion. This shit's bugged. Report this bug to Agouri, polyxenitopalidou@gmail.com" - - usr << browse(dat, "window=admincaster_main;size=400x600") - onclose(usr, "admincaster_main") - - -/datum/admins/proc/Game() - if(!check_rights(0)) - return - - var/dat = {" -
                        Game Panel

                        \n - Change Game Mode
                        - "} - if(GLOB.master_mode == "secret") - dat += "(Force Secret Mode)
                        " - if(GLOB.master_mode == "dynamic") - if(SSticker.current_state <= GAME_STATE_PREGAME) - dat += "(Force Roundstart Rulesets)
                        " - if (GLOB.dynamic_forced_roundstart_ruleset.len > 0) - for(var/datum/dynamic_ruleset/roundstart/rule in GLOB.dynamic_forced_roundstart_ruleset) - dat += {"-> [rule.name] <-
                        "} - dat += "(Clear Rulesets)
                        " - dat += "(Dynamic mode options)
                        " - else if (SSticker.IsRoundInProgress()) - dat += "(Force Next Latejoin Ruleset)
                        " - if (SSticker && SSticker.mode && istype(SSticker.mode,/datum/game_mode/dynamic)) - var/datum/game_mode/dynamic/mode = SSticker.mode - if (mode.forced_latejoin_rule) - dat += {"-> [mode.forced_latejoin_rule.name] <-
                        "} - dat += "(Execute Midround Ruleset!)
                        " - dat += "
                        " - if(SSticker.IsRoundInProgress()) - dat += "(Game Mode Panel)
                        " - dat += {" -
                        - Create Object
                        - Quick Create Object
                        - Create Turf
                        - Create Mob
                        - "} - - if(marked_datum && istype(marked_datum, /atom)) - dat += "Duplicate Marked Datum
                        " - - var/datum/browser/popup = new(usr, "admin2", null, 240, 280) - popup.set_content(dat) - popup.open() -// usr << browse(dat, "window=admin2;size=240x280") - return - -/////////////////////////////////////////////////////////////////////////////////////////////////admins2.dm merge -//i.e. buttons/verbs - - -/datum/admins/proc/restart() - set category = "Server" - set name = "Reboot World" - set desc="Restarts the world immediately" - if (!usr.client.holder) - return - - var/localhost_addresses = list("127.0.0.1", "::1") - var/list/options = list("Regular Restart", "Regular Restart (with delay)", "Hard Restart (No Delay/Feeback Reason)", "Hardest Restart (No actions, just reboot)") - if(world.TgsAvailable()) - options += "Server Restart (Kill and restart DD)"; - - if(SSticker.admin_delay_notice) - if(alert(usr, "Are you sure? An admin has already delayed the round end for the following reason: [SSticker.admin_delay_notice]", "Confirmation", "Yes", "No") != "Yes") - return FALSE - - var/result = input(usr, "Select reboot method", "World Reboot", options[1]) as null|anything in options - if(result) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Reboot World") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - var/init_by = "Initiated by [usr.client.holder.fakekey ? "Admin" : usr.key]." - switch(result) - if("Regular Restart") - if(!(isnull(usr.client.address) || (usr.client.address in localhost_addresses))) - if(alert("Are you sure you want to restart the server?","This server is live","Restart","Cancel") != "Restart") - return FALSE - SSticker.Reboot(init_by, "admin reboot - by [usr.key] [usr.client.holder.fakekey ? "(stealth)" : ""]", 10) - if("Regular Restart (with delay)") - var/delay = input("What delay should the restart have (in seconds)?", "Restart Delay", 5) as num|null - if(!delay) - return FALSE - if(!(isnull(usr.client.address) || (usr.client.address in localhost_addresses))) - if(alert("Are you sure you want to restart the server?","This server is live","Restart","Cancel") != "Restart") - return FALSE - SSticker.Reboot(init_by, "admin reboot - by [usr.key] [usr.client.holder.fakekey ? "(stealth)" : ""]", delay * 10) - if("Hard Restart (No Delay, No Feeback Reason)") - to_chat(world, "World reboot - [init_by]") - world.Reboot() - if("Hardest Restart (No actions, just reboot)") - to_chat(world, "Hard world reboot - [init_by]") - world.Reboot(fast_track = TRUE) - if("Server Restart (Kill and restart DD)") - to_chat(world, "Server restart - [init_by]") - world.TgsEndProcess() - -/datum/admins/proc/end_round() - set category = "Server" - set name = "End Round" - set desc = "Attempts to produce a round end report and then restart the server organically." - - if (!usr.client.holder) - return - var/confirm = alert("End the round and restart the game world?", "End Round", "Yes", "Cancel") - if(confirm == "Cancel") - return - if(confirm == "Yes") - SSticker.force_ending = 1 - SSblackbox.record_feedback("tally", "admin_verb", 1, "End Round") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - -/datum/admins/proc/announce() - set category = "Admin" - set name = "Announce" - set desc="Announce your desires to the world" - if(!check_rights(0)) - return - - var/message = input("Global message to send:", "Admin Announce", null, null) as message - if(message) - if(!check_rights(R_SERVER,0)) - message = adminscrub(message,500) - to_chat(world, "[usr.client.holder.fakekey ? "Administrator" : usr.key] Announces:\n \t [message]", confidential = TRUE) - log_admin("Announce: [key_name(usr)] : [message]") - SSredbot.send_discord_message("ooc", "**[usr.client.holder.fakekey ? "Administrator" : usr.key] Announces:**\n [message]") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Announce") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/datum/admins/proc/set_admin_notice() - set category = "Server" - set name = "Set Admin Notice" - set desc ="Set an announcement that appears to everyone who joins the server. Only lasts this round" - if(!check_rights(0)) - return - - var/new_admin_notice = input(src,"Set a public notice for this round. Everyone who joins the server will see it.\n(Leaving it blank will delete the current notice):","Set Notice",GLOB.admin_notice) as message|null - if(new_admin_notice == null) - return - if(new_admin_notice == GLOB.admin_notice) - return - if(new_admin_notice == "") - message_admins("[key_name(usr)] removed the admin notice.") - log_admin("[key_name(usr)] removed the admin notice:\n[GLOB.admin_notice]") - else - message_admins("[key_name(usr)] set the admin notice.") - log_admin("[key_name(usr)] set the admin notice:\n[new_admin_notice]") - to_chat(world, "Admin Notice:\n \t [new_admin_notice]", confidential = TRUE) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Set Admin Notice") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - GLOB.admin_notice = new_admin_notice - return - -/datum/admins/proc/toggleooc() - set category = "Server" - set desc="Toggle dis bitch" - set name="Toggle OOC" - toggle_ooc() - log_admin("[key_name(usr)] toggled OOC.") - message_admins("[key_name_admin(usr)] toggled OOC.") - SSredbot.send_discord_message("ooc", "**OOC has been [GLOB.ooc_allowed ? "enabled" : "disabled"] on the server.**") - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle OOC", "[GLOB.ooc_allowed ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -//Begin Wasp Edit -/datum/admins/proc/toggleooclocal() - set category = "Server" - set desc="Toggle dat bitch" - set name="Toggle Local OOC" - toggle_looc() - log_admin("[key_name(usr)] toggled LOOC.") - message_admins("[key_name_admin(usr)] toggled LOOC.") - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Local OOC", "[GLOB.looc_allowed ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! -//End Wasp Edit - -/datum/admins/proc/toggleoocdead() - set category = "Server" - set desc="Toggle dis bitch" - set name="Toggle Dead OOC" - toggle_dooc() - - log_admin("[key_name(usr)] toggled OOC.") - message_admins("[key_name_admin(usr)] toggled Dead OOC.") - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Dead OOC", "[GLOB.dooc_allowed ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/datum/admins/proc/startnow() - set category = "Server" - set desc="Start the round RIGHT NOW" - set name="Start Now" - if(SSticker.current_state == GAME_STATE_PREGAME || SSticker.current_state == GAME_STATE_STARTUP) - if(!SSticker.start_immediately) - var/localhost_addresses = list("127.0.0.1", "::1") - if(!(isnull(usr.client.address) || (usr.client.address in localhost_addresses))) - if(alert("Are you sure you want to start the round?","Start Now","Start Now","Cancel") != "Start Now") - return FALSE - SSticker.start_immediately = TRUE - log_admin("[usr.key] has started the game.") - var/msg = "" - if(SSticker.current_state == GAME_STATE_STARTUP) - msg = " (The server is still setting up, but the round will be \ - started as soon as possible.)" - message_admins("[usr.key] has started the game.[msg]") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Start Now") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - return TRUE - SSticker.start_immediately = FALSE - SSticker.SetTimeLeft(1800) - to_chat(world, "The game will start in 180 seconds.") - SEND_SOUND(world, sound('sound/ai/attention.ogg')) - message_admins("[usr.key] has cancelled immediate game start. Game will start in 180 seconds.") - log_admin("[usr.key] has cancelled immediate game start.") - else - to_chat(usr, "Error: Start Now: Game has already started.") - return FALSE - -/datum/admins/proc/toggleenter() - set category = "Server" - set desc="People can't enter" - set name="Toggle Entering" - GLOB.enter_allowed = !( GLOB.enter_allowed ) - if (!( GLOB.enter_allowed )) - to_chat(world, "New players may no longer enter the game.", confidential = TRUE) - else - to_chat(world, "New players may now enter the game.", confidential = TRUE) - log_admin("[key_name(usr)] toggled new player game entering.") - message_admins("[key_name_admin(usr)] toggled new player game entering.") - world.update_status() - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Entering", "[GLOB.enter_allowed ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/datum/admins/proc/toggleAI() - set category = "Server" - set desc="People can't be AI" - set name="Toggle AI" - var/alai = CONFIG_GET(flag/allow_ai) - CONFIG_SET(flag/allow_ai, !alai) - if (alai) - to_chat(world, "The AI job is no longer chooseable.", confidential = TRUE) - else - to_chat(world, "The AI job is chooseable now.", confidential = TRUE) - log_admin("[key_name(usr)] toggled AI allowed.") - world.update_status() - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle AI", "[!alai ? "Disabled" : "Enabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/datum/admins/proc/toggleaban() - set category = "Server" - set desc="Respawn basically" - set name="Toggle Respawn" - var/new_nores = !CONFIG_GET(flag/norespawn) - CONFIG_SET(flag/norespawn, new_nores) - if (!new_nores) - to_chat(world, "You may now respawn.", confidential = TRUE) - else - to_chat(world, "You may no longer respawn :(", confidential = TRUE) - message_admins("[key_name_admin(usr)] toggled respawn to [!new_nores ? "On" : "Off"].") - log_admin("[key_name(usr)] toggled respawn to [!new_nores ? "On" : "Off"].") - world.update_status() - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Respawn", "[!new_nores ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/datum/admins/proc/delay() - set category = "Server" - set desc="Delay the game start" - set name="Delay Pre-Game" - - var/newtime = input("Set a new time in seconds. Set -1 for indefinite delay.","Set Delay",round(SSticker.GetTimeLeft()/10)) as num|null - if(SSticker.current_state > GAME_STATE_PREGAME) - return alert("Too late... The game has already started!") - if(newtime) - newtime = newtime*10 - SSticker.SetTimeLeft(newtime) - SSticker.start_immediately = FALSE - if(newtime < 0) - to_chat(world, "The game start has been delayed.", confidential = TRUE) - log_admin("[key_name(usr)] delayed the round start.") - else - to_chat(world, "The game will start in [DisplayTimeText(newtime)].", confidential = TRUE) - SEND_SOUND(world, sound('sound/ai/attention.ogg')) - log_admin("[key_name(usr)] set the pre-game delay to [DisplayTimeText(newtime)].") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Delay Game Start") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/datum/admins/proc/unprison(mob/M in GLOB.mob_list) - set category = "Admin" - set name = "Unprison" - if (is_centcom_level(M.z)) - SSjob.SendToLateJoin(M) - message_admins("[key_name_admin(usr)] has unprisoned [key_name_admin(M)]") - log_admin("[key_name(usr)] has unprisoned [key_name(M)]") - else - alert("[M.name] is not prisoned.") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Unprison") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -////////////////////////////////////////////////////////////////////////////////////////////////ADMIN HELPER PROCS - -/datum/admins/proc/spawn_atom(object as text) - set category = "Debug" - set desc = "(atom path) Spawn an atom" - set name = "Spawn" - - if(!check_rights(R_SPAWN) || !object) - return - - var/list/preparsed = splittext(object,":") - var/path = preparsed[1] - var/amount = 1 - if(preparsed.len > 1) - amount = clamp(text2num(preparsed[2]),1,ADMIN_SPAWN_CAP) - - var/chosen = pick_closest_path(path) - if(!chosen) - return - var/turf/T = get_turf(usr) - - if(ispath(chosen, /turf)) - T.ChangeTurf(chosen) - else - for(var/i in 1 to amount) - var/atom/A = new chosen(T) - A.flags_1 |= ADMIN_SPAWNED_1 - - log_admin("[key_name(usr)] spawned [amount] x [chosen] at [AREACOORD(usr)]") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Spawn Atom") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/datum/admins/proc/podspawn_atom(object as text) - set category = "Debug" - set desc = "(atom path) Spawn an atom via supply drop" - set name = "Podspawn" - - if(!check_rights(R_SPAWN)) - return - - var/chosen = pick_closest_path(object) - if(!chosen) - return - var/turf/T = get_turf(usr) - - if(ispath(chosen, /turf)) - T.ChangeTurf(chosen) - else - var/obj/structure/closet/supplypod/centcompod/pod = new() - var/atom/A = new chosen(pod) - A.flags_1 |= ADMIN_SPAWNED_1 - new /obj/effect/DPtarget(T, pod) - - log_admin("[key_name(usr)] pod-spawned [chosen] at [AREACOORD(usr)]") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Podspawn Atom") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/datum/admins/proc/spawn_cargo(object as text) - set category = "Debug" - set desc = "(atom path) Spawn a cargo crate" - set name = "Spawn Cargo" - - if(!check_rights(R_SPAWN)) - return - - var/chosen = pick_closest_path(object, make_types_fancy(subtypesof(/datum/supply_pack))) - if(!chosen) - return - var/datum/supply_pack/S = new chosen - S.admin_spawned = TRUE - S.generate(get_turf(usr)) - - log_admin("[key_name(usr)] spawned cargo pack [chosen] at [AREACOORD(usr)]") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Spawn Cargo") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - -/datum/admins/proc/show_traitor_panel(mob/M in GLOB.mob_list) - set category = "Admin - Game" - set desc = "Edit mobs's memory and role" - set name = "Show Traitor Panel" - - if(!istype(M)) - to_chat(usr, "This can only be used on instances of type /mob", confidential = TRUE) - return - if(!M.mind) - to_chat(usr, "This mob has no mind!", confidential = TRUE) - return - - M.mind.traitor_panel() - SSblackbox.record_feedback("tally", "admin_verb", 1, "Traitor Panel") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - -/datum/admins/proc/toggletintedweldhelmets() - set category = "Debug" - set desc="Reduces view range when wearing welding helmets" - set name="Toggle tinted welding helmes" - GLOB.tinted_weldhelh = !( GLOB.tinted_weldhelh ) - if (GLOB.tinted_weldhelh) - to_chat(world, "The tinted_weldhelh has been enabled!", confidential = TRUE) - else - to_chat(world, "The tinted_weldhelh has been disabled!", confidential = TRUE) - log_admin("[key_name(usr)] toggled tinted_weldhelh.") - message_admins("[key_name_admin(usr)] toggled tinted_weldhelh.") - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Tinted Welding Helmets", "[GLOB.tinted_weldhelh ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/datum/admins/proc/toggleguests() - set category = "Server" - set desc="Guests can't enter" - set name="Toggle guests" - var/new_guest_ban = !CONFIG_GET(flag/guest_ban) - CONFIG_SET(flag/guest_ban, new_guest_ban) - if (new_guest_ban) - to_chat(world, "Guests may no longer enter the game.", confidential = TRUE) - else - to_chat(world, "Guests may now enter the game.", confidential = TRUE) - log_admin("[key_name(usr)] toggled guests game entering [!new_guest_ban ? "" : "dis"]allowed.") - message_admins("[key_name_admin(usr)] toggled guests game entering [!new_guest_ban ? "" : "dis"]allowed.") - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Guests", "[!new_guest_ban ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/datum/admins/proc/output_ai_laws() - var/ai_number = 0 - for(var/i in GLOB.silicon_mobs) - var/mob/living/silicon/S = i - ai_number++ - if(isAI(S)) - to_chat(usr, "AI [key_name(S, usr)]'s laws:", confidential = TRUE) - else if(iscyborg(S)) - var/mob/living/silicon/robot/R = S - to_chat(usr, "CYBORG [key_name(S, usr)] [R.connected_ai?"(Slaved to: [key_name(R.connected_ai)])":"(Independent)"]: laws:", confidential = TRUE) - else if (ispAI(S)) - to_chat(usr, "pAI [key_name(S, usr)]'s laws:", confidential = TRUE) - else - to_chat(usr, "SOMETHING SILICON [key_name(S, usr)]'s laws:", confidential = TRUE) - - if (S.laws == null) - to_chat(usr, "[key_name(S, usr)]'s laws are null?? Contact a coder.", confidential = TRUE) - else - S.laws.show_laws(usr) - if(!ai_number) - to_chat(usr, "No AIs located" , confidential = TRUE) - -/datum/admins/proc/output_all_devil_info() - var/devil_number = 0 - for(var/datum/mind/D in SSticker.mode.devils) - devil_number++ - var/datum/antagonist/devil/devil = D.has_antag_datum(/datum/antagonist/devil) - to_chat(usr, "Devil #[devil_number]:

                        " + devil.printdevilinfo(), confidential = TRUE) - if(!devil_number) - to_chat(usr, "No Devils located" , confidential = TRUE) - -/datum/admins/proc/output_devil_info(mob/living/M) - if(is_devil(M)) - var/datum/antagonist/devil/devil = M.mind.has_antag_datum(/datum/antagonist/devil) - to_chat(usr, devil.printdevilinfo(), confidential = TRUE) - else - to_chat(usr, "[M] is not a devil.", confidential = TRUE) - -/datum/admins/proc/manage_free_slots() - if(!check_rights()) - return - var/datum/browser/browser = new(usr, "jobmanagement", "Manage Free Slots", 520) - var/list/dat = list() - var/count = 0 - - if(!SSjob.initialized) - alert(usr, "You cannot manage jobs before the job subsystem is initialized!") - return - - dat += "" - - for(var/j in SSjob.occupations) - var/datum/job/job = j - count++ - var/J_title = html_encode(job.title) - var/J_opPos = html_encode(job.total_positions - (job.total_positions - job.current_positions)) - var/J_totPos = html_encode(job.total_positions) - dat += "" - dat += "" - else - dat += "Limit" - - browser.height = min(100 + count * 20, 650) - browser.set_content(dat.Join()) - browser.open() - -/datum/admins/proc/dynamic_mode_options(mob/user) - var/dat = {" -

                        Dynamic Mode Options


                        -
                        -

                        Common options

                        - All these options can be changed midround.
                        -
                        - Force extended: - Option is [GLOB.dynamic_forced_extended ? "ON" : "OFF"]. -
                        This will force the round to be extended. No rulesets will be drafted.
                        -
                        - No stacking: - Option is [GLOB.dynamic_no_stacking ? "ON" : "OFF"]. -
                        Unless the threat goes above [GLOB.dynamic_stacking_limit], only one "round-ender" ruleset will be drafted.
                        -
                        - Classic secret mode: - Option is [GLOB.dynamic_classic_secret ? "ON" : "OFF"]. -
                        Only one roundstart ruleset will be drafted. Only traitors and minor roles will latespawn.
                        -
                        -
                        - Forced threat level: Current value : [GLOB.dynamic_forced_threat_level]. -
                        The value threat is set to if it is higher than -1.
                        -
                        - High population limit: Current value : [GLOB.dynamic_high_pop_limit]. -
                        The threshold at which "high population override" will be in effect.
                        -
                        - Stacking threeshold: Current value : [GLOB.dynamic_stacking_limit]. -
                        The threshold at which "round-ender" rulesets will stack. A value higher than 100 ensure this never happens.
                        -

                        Advanced parameters

                        - Curve centre: -> [GLOB.dynamic_curve_centre] <-
                        - Curve width: -> [GLOB.dynamic_curve_width] <-
                        - Latejoin injection delay:
                        - Minimum: -> [GLOB.dynamic_latejoin_delay_min / 60 / 10] <- Minutes
                        - Maximum: -> [GLOB.dynamic_latejoin_delay_max / 60 / 10] <- Minutes
                        - Midround injection delay:
                        - Minimum: -> [GLOB.dynamic_midround_delay_min / 60 / 10] <- Minutes
                        - Maximum: -> [GLOB.dynamic_midround_delay_max / 60 / 10] <- Minutes
                        - "} - - user << browse(dat, "window=dyn_mode_options;size=900x650") - -/datum/admins/proc/create_or_modify_area() - set category = "Debug" - set name = "Create or modify area" - create_area(usr) - -// -// -//ALL DONE -//********************************************************************************************************* -//TO-DO: -// -// - -//RIP ferry snowflakes - -//Kicks all the clients currently in the lobby. The second parameter (kick_only_afk) determins if an is_afk() check is ran, or if all clients are kicked -//defaults to kicking everyone (afk + non afk clients in the lobby) -//returns a list of ckeys of the kicked clients -/proc/kick_clients_in_lobby(message, kick_only_afk = 0) - var/list/kicked_client_names = list() - for(var/client/C in GLOB.clients) - if(isnewplayer(C.mob)) - if(kick_only_afk && !C.is_afk()) //Ignore clients who are not afk - continue - if(message) - to_chat(C, message, confidential = TRUE) - kicked_client_names.Add("[C.key]") - qdel(C) - return kicked_client_names - -//returns TRUE to let the dragdrop code know we are trapping this event -//returns FALSE if we don't plan to trap the event -/datum/admins/proc/cmd_ghost_drag(mob/dead/observer/frommob, mob/tomob) - - //this is the exact two check rights checks required to edit a ckey with vv. - if (!check_rights(R_VAREDIT,0) || !check_rights(R_SPAWN|R_DEBUG,0)) - return FALSE - - if (!frommob.ckey) - return FALSE - - var/question = "" - if (tomob.ckey) - question = "This mob already has a user ([tomob.key]) in control of it! " - question += "Are you sure you want to place [frommob.name]([frommob.key]) in control of [tomob.name]?" - - var/ask = alert(question, "Place ghost in control of mob?", "Yes", "No") - if (ask != "Yes") - return TRUE - - if (!frommob || !tomob) //make sure the mobs don't go away while we waited for a response - return TRUE - - // Disassociates observer mind from the body mind - if(tomob.client) - tomob.ghostize(FALSE) - else - for(var/mob/dead/observer/ghost in GLOB.dead_mob_list) - if(tomob.mind == ghost.mind) - ghost.mind = null - - message_admins("[key_name_admin(usr)] has put [frommob.key] in control of [tomob.name].") - log_admin("[key_name(usr)] stuffed [frommob.key] into [tomob.name].") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Ghost Drag Control") - - tomob.ckey = frommob.ckey - qdel(frommob) - - return TRUE - -/client/proc/adminGreet(logout) - if(SSticker.HasRoundStarted()) - var/string - if(logout && CONFIG_GET(flag/announce_admin_logout)) - string = pick( - "Admin logout: [key_name(src)]") - else if(!logout && CONFIG_GET(flag/announce_admin_login) && (prefs.toggles & ANNOUNCE_LOGIN)) - string = pick( - "Admin login: [key_name(src)]") - if(string) - message_admins("[string]") + +//////////////////////////////// +/proc/message_admins(msg) + msg = "ADMIN LOG: [msg]" + to_chat(GLOB.admins, msg, confidential = TRUE) + +/proc/relay_msg_admins(msg) + msg = "RELAY: [msg]" + to_chat(GLOB.admins, msg, confidential = TRUE) + + +///////////////////////////////////////////////////////////////////////////////////////////////Panels + +/datum/admins/proc/show_player_panel(mob/M in GLOB.mob_list) + set category = "Admin - Game" + set name = "Show Player Panel" + set desc="Edit player (respawn, ban, heal, etc)" + + if(!check_rights()) + return + + log_admin("[key_name(usr)] checked the individual player panel for [key_name(M)][isobserver(usr)?"":" while in game"].") + + if(!M) + to_chat(usr, "You seem to be selecting a mob that doesn't exist anymore.", confidential = TRUE) + return + + var/body = "Options for [M.key]" + body += "Options panel for [M]" + if(M.client) + body += " played by [M.client] " + body += "[M.client.holder ? M.client.holder.rank : "Player"]" + if(CONFIG_GET(flag/use_exp_tracking)) + body += "" + M.client.get_exp_living(FALSE) + "" + + if(isnewplayer(M)) + body += " Hasn't Entered Game " + else + body += " Heal " + + if(M.client) + body += "
                        First Seen: [M.client.player_join_date]Byond account registered on: [M.client.account_join_date]" + body += "

                        Show related accounts by: " + body += " CID" + body += "IP " + body += "

                        CentCom Galactic Ban DB: " + if(CONFIG_GET(string/centcom_ban_db)) + body += "Search" + else + body += "Disabled" + var/rep = 0 + rep += SSpersistence.antag_rep[M.ckey] + body += "

                        Antagonist reputation: [rep]" + body += "
                        + " + body += "- " + body += "= " + body += "0" + //WaspStation Begin - Metacoins + var/metabalance = M.client.get_metabalance() + body += "

                        [CONFIG_GET(string/metacurrency_name)]s: [metabalance] " + body += "
                        + " + body += "- " + body += "= " + body += "0" + //Antag Tokens + var/antag_tokens = M.client.get_antag_token_count() + body += "

                        Antag Tokens: [antag_tokens]" + body += "
                        + " + body += "- " + body += "= " + body += "0" + //WaspStation End + var/full_version = "Unknown" + if(M.client.byond_version) + full_version = "[M.client.byond_version].[M.client.byond_build ? M.client.byond_build : "xxx"]" + body += "
                        Byond version: [full_version]
                        " + + + body += "

                        " + body += "VV - " + if(M.mind) + body += "TP - " + body += "SKILLS - " + else + body += "Init Mind - " + if (iscyborg(M)) + body += "BP - " + body += "PM - " + body += "SM - " + if (ishuman(M) && M.mind) + body += "HM - " + body += "FLW - " + //Default to client logs if available + var/source = LOGSRC_MOB + if(M.client) + source = LOGSRC_CLIENT + body += "LOGS
                        " + + body += "Mob type = [M.type]

                        " + + body += "Kick" + if(M.client) + body += "Ban" + else + body += "Ban" + + body += "Notes / Watchlist" + if(M.client) + body += "| Prison" + body += "\ Send back to Lobby" + var/muted = M.client.prefs.muted + body += "
                        Mute: " + body += "IC" + body += "OOC" + body += "PRAY" + body += "ADMINHELP" + body += "MENTORHELP" + body += "DEADCHAT" + body += "(toggle all)" + + body += "

                        " + body += "Jump to" + body += "Get" + body += "Send To" + + body += "

                        " + body += "Traitor panel" + body += "Narrate to" + body += "Subtle message" + body += "Play sound to" + body += "Language Menu" + + if (M.client) + if(!isnewplayer(M)) + body += "

                        " + body += "Transformation:" + body += "
                        " + + //Human + if(ishuman(M)) + body += "Human" + else + body += "Humanize" + + //Monkey + if(ismonkey(M)) + body += "Monkeyized" + else + body += "Monkeyize" + + //Corgi + if(iscorgi(M)) + body += "Corgized" + else + body += "Corgize" + + //AI / Cyborg + if(isAI(M)) + body += "Is an AI " + else if(ishuman(M)) + body += "Make AI" + body += "Make Robot" + body += "Make Alien" + body += "Make Slime" + body += "Make Blob" + + //Simple Animals + if(isanimal(M)) + body += "Re-Animalize" + else + body += "Animalize" + + body += "

                        " + body += "Rudimentary transformation:
                        These transformations only create a new mob type and copy stuff over. They do not take into account MMIs and similar mob-specific things. The buttons in 'Transformations' are preferred, when possible.

                        " + body += "Observer" + body += " Alien: Drone, " + body += "Hunter, " + body += "Sentinel, " + body += "Praetorian, " + body += "Queen, " + body += "Larva " + body += "Human " + body += " slime: Baby, " + body += "Adult " + body += "Monkey" + body += "Cyborg" + body += "Cat" + body += "Runtime" + body += "Corgi" + body += "Ian" + body += "Crab" + body += "Coffee" + body += " Construct: Juggernaut , " + body += "Artificer , " + body += "Wraith " + body += "Shade" + body += "
                        " + + if (M.client) + body += "

                        " + body += "Other actions:" + body += "
                        " + body += "Forcesay" + body += "Thunderdome 1" + body += "Thunderdome 2" + body += "Thunderdome Admin" + body += "Thunderdome Observer" + + body += "
                        " + body += "" + + + //WaspStation Begin - Better Looking Admin Panels + var/datum/browser/popup = new(usr, "adminplayeropts-[REF(M)]", "
                        Options for [M.key]
                        ", 700, 600) + popup.set_content(body) + popup.open(0) + //WaspStation End + + /*WaspStation Begin - Better Looking Admin Panels + usr << browse(body, "window=adminplayeropts-[REF(M)];size=550x515") + */ + + SSblackbox.record_feedback("tally", "admin_verb", 1, "Player Panel") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + +/datum/admins/proc/access_news_network() //MARKER + set category = "Admin - Events" + set name = "Access Newscaster Network" + set desc = "Allows you to view, add and edit news feeds." + + if (!istype(src, /datum/admins)) + src = usr.client.holder + if (!istype(src, /datum/admins)) + to_chat(usr, "Error: you are not an admin!", confidential = TRUE) + return + var/dat + dat = text("Admin Newscaster

                        Admin Newscaster Unit

                        ") + + switch(admincaster_screen) + if(0) + dat += "Welcome to the admin newscaster.
                        Here you can add, edit and censor every newspiece on the network." + dat += "
                        Feed channels and stories entered through here will be uneditable and handled as official news by the rest of the units." + dat += "
                        Note that this panel allows full freedom over the news network, there are no constrictions except the few basic ones. Don't break things!" + if(GLOB.news_network.wanted_issue.active) + dat+= "
                        Read Wanted Issue" + dat+= "

                        Create Feed Channel" + dat+= "
                        View Feed Channels" + dat+= "
                        Submit new Feed story" + dat+= "

                        Exit" + var/wanted_already = 0 + if(GLOB.news_network.wanted_issue.active) + wanted_already = 1 + dat+="
                        Feed Security functions:
                        " + dat+="
                        [(wanted_already) ? ("Manage") : ("Publish")] \"Wanted\" Issue" + dat+="
                        Censor Feed Stories" + dat+="
                        Mark Feed Channel with Nanotrasen D-Notice (disables and locks the channel)." + dat+="

                        The newscaster recognises you as:
                        [src.admin_signature]
                        " + if(1) + dat+= "Station Feed Channels
                        " + if( !length(GLOB.news_network.network_channels) ) + dat+="No active channels found..." + else + for(var/datum/newscaster/feed_channel/CHANNEL in GLOB.news_network.network_channels) + if(CHANNEL.is_admin_channel) + dat+="[CHANNEL.channel_name]
                        " + else + dat+="[CHANNEL.channel_name] [(CHANNEL.censored) ? ("***") : ""]
                        " + dat+="

                        Refresh" + dat+="
                        Back" + if(2) + dat+="Creating new Feed Channel..." + dat+="
                        Channel Name: [src.admincaster_feed_channel.channel_name]
                        " + dat+="Channel Author: [src.admin_signature]
                        " + dat+="Will Accept Public Feeds: [(src.admincaster_feed_channel.locked) ? ("NO") : ("YES")]

                        " + dat+="
                        Submit

                        Cancel
                        " + if(3) + dat+="Creating new Feed Message..." + dat+="
                        Receiving Channel: [src.admincaster_feed_channel.channel_name]
                        " //MARK + dat+="Message Author: [src.admin_signature]
                        " + dat+="Message Body: [src.admincaster_feed_message.returnBody(-1)]
                        " + dat+="
                        Submit

                        Cancel
                        " + if(4) + dat+="Feed story successfully submitted to [src.admincaster_feed_channel.channel_name].

                        " + dat+="
                        Return
                        " + if(5) + dat+="Feed Channel [src.admincaster_feed_channel.channel_name] created successfully.

                        " + dat+="
                        Return
                        " + if(6) + dat+="ERROR: Could not submit Feed story to Network.

                        " + if(src.admincaster_feed_channel.channel_name=="") + dat+="•Invalid receiving channel name.
                        " + if(src.admincaster_feed_message.returnBody(-1) == "" || src.admincaster_feed_message.returnBody(-1) == "REDACTED") + dat+="•Invalid message body.
                        " + dat+="
                        Return
                        " + if(7) + dat+="ERROR: Could not submit Feed Channel to Network.

                        " + if(src.admincaster_feed_channel.channel_name =="" || src.admincaster_feed_channel.channel_name == "REDACTED") + dat+="•Invalid channel name.
                        " + var/check = 0 + for(var/datum/newscaster/feed_channel/FC in GLOB.news_network.network_channels) + if(FC.channel_name == src.admincaster_feed_channel.channel_name) + check = 1 + break + if(check) + dat+="•Channel name already in use.
                        " + dat+="
                        Return
                        " + if(9) + dat+="[admincaster_feed_channel.channel_name]: created by: [admincaster_feed_channel.returnAuthor(-1)]
                        " + if(src.admincaster_feed_channel.censored) + dat+="ATTENTION: This channel has been deemed as threatening to the welfare of the station, and marked with a Nanotrasen D-Notice.
                        " + dat+="No further feed story additions are allowed while the D-Notice is in effect.

                        " + else + if( !length(src.admincaster_feed_channel.messages) ) + dat+="No feed messages found in channel...
                        " + else + var/i = 0 + for(var/datum/newscaster/feed_message/MESSAGE in src.admincaster_feed_channel.messages) + i++ + dat+="-[MESSAGE.returnBody(-1)]
                        " + if(MESSAGE.img) + usr << browse_rsc(MESSAGE.img, "tmp_photo[i].png") + dat+="

                        " + dat+="Story by [MESSAGE.returnAuthor(-1)]
                        " + dat+="[MESSAGE.comments.len] comment[MESSAGE.comments.len > 1 ? "s" : ""]:
                        " + for(var/datum/newscaster/feed_comment/comment in MESSAGE.comments) + dat+="[comment.body]
                        [comment.author] [comment.time_stamp]
                        " + dat+="
                        " + dat+="

                        Refresh" + dat+="
                        Back" + if(10) + dat+="Nanotrasen Feed Censorship Tool
                        " + dat+="NOTE: Due to the nature of news Feeds, total deletion of a Feed Story is not possible.
                        " + dat+="Keep in mind that users attempting to view a censored feed will instead see the REDACTED tag above it.
                        " + dat+="
                        Select Feed channel to get Stories from:
                        " + if(!length(GLOB.news_network.network_channels)) + dat+="No feed channels found active...
                        " + else + for(var/datum/newscaster/feed_channel/CHANNEL in GLOB.news_network.network_channels) + dat+="[CHANNEL.channel_name] [(CHANNEL.censored) ? ("***") : ""]
                        " + dat+="
                        Cancel" + if(11) + dat+="Nanotrasen D-Notice Handler
                        " + dat+="A D-Notice is to be bestowed upon the channel if the handling Authority deems it as harmful for the station's" + dat+="morale, integrity or disciplinary behaviour. A D-Notice will render a channel unable to be updated by anyone, without deleting any feed" + dat+="stories it might contain at the time. You can lift a D-Notice if you have the required access at any time.
                        " + if(!length(GLOB.news_network.network_channels)) + dat+="No feed channels found active...
                        " + else + for(var/datum/newscaster/feed_channel/CHANNEL in GLOB.news_network.network_channels) + dat+="[CHANNEL.channel_name] [(CHANNEL.censored) ? ("***") : ""]
                        " + + dat+="
                        Back" + if(12) + dat+="[src.admincaster_feed_channel.channel_name]: created by: [src.admincaster_feed_channel.returnAuthor(-1)]
                        " + dat+="[(src.admincaster_feed_channel.authorCensor) ? ("Undo Author censorship") : ("Censor channel Author")]
                        " + + if( !length(src.admincaster_feed_channel.messages) ) + dat+="No feed messages found in channel...
                        " + else + for(var/datum/newscaster/feed_message/MESSAGE in src.admincaster_feed_channel.messages) + dat+="-[MESSAGE.returnBody(-1)]
                        Story by [MESSAGE.returnAuthor(-1)]
                        " + dat+="[(MESSAGE.bodyCensor) ? ("Undo story censorship") : ("Censor story")] - [(MESSAGE.authorCensor) ? ("Undo Author Censorship") : ("Censor message Author")]
                        " + dat+="[MESSAGE.comments.len] comment[MESSAGE.comments.len > 1 ? "s" : ""]: [MESSAGE.locked ? "Unlock" : "Lock"]
                        " + for(var/datum/newscaster/feed_comment/comment in MESSAGE.comments) + dat+="[comment.body] X
                        [comment.author] [comment.time_stamp]
                        " + dat+="
                        Back" + if(13) + dat+="[src.admincaster_feed_channel.channel_name]: created by: [src.admincaster_feed_channel.returnAuthor(-1)]
                        " + dat+="Channel messages listed below. If you deem them dangerous to the station, you can Bestow a D-Notice upon the channel.
                        " + if(src.admincaster_feed_channel.censored) + dat+="ATTENTION: This channel has been deemed as threatening to the welfare of the station, and marked with a Nanotrasen D-Notice.
                        " + dat+="No further feed story additions are allowed while the D-Notice is in effect.

                        " + else + if( !length(src.admincaster_feed_channel.messages) ) + dat+="No feed messages found in channel...
                        " + else + for(var/datum/newscaster/feed_message/MESSAGE in src.admincaster_feed_channel.messages) + dat+="-[MESSAGE.returnBody(-1)]
                        Story by [MESSAGE.returnAuthor(-1)]
                        " + dat+="
                        Back" + if(14) + dat+="Wanted Issue Handler:" + var/wanted_already = 0 + var/end_param = 1 + if(GLOB.news_network.wanted_issue.active) + wanted_already = 1 + end_param = 2 + if(wanted_already) + dat+="
                        A wanted issue is already in Feed Circulation. You can edit or cancel it below.
                        " + dat+="
                        " + dat+="Criminal Name: [src.admincaster_wanted_message.criminal]
                        " + dat+="Description: [src.admincaster_wanted_message.body]
                        " + if(wanted_already) + dat+="Wanted Issue created by:[GLOB.news_network.wanted_issue.scannedUser]
                        " + else + dat+="Wanted Issue will be created under prosecutor:[src.admin_signature]
                        " + dat+="
                        [(wanted_already) ? ("Edit Issue") : ("Submit")]" + if(wanted_already) + dat+="
                        Take down Issue" + dat+="
                        Cancel" + if(15) + dat+="Wanted issue for [src.admincaster_wanted_message.criminal] is now in Network Circulation.

                        " + dat+="
                        Return
                        " + if(16) + dat+="ERROR: Wanted Issue rejected by Network.

                        " + if(src.admincaster_wanted_message.criminal =="" || src.admincaster_wanted_message.criminal == "REDACTED") + dat+="•Invalid name for person wanted.
                        " + if(src.admincaster_wanted_message.body == "" || src.admincaster_wanted_message.body == "REDACTED") + dat+="•Invalid description.
                        " + dat+="
                        Return
                        " + if(17) + dat+="Wanted Issue successfully deleted from Circulation
                        " + dat+="
                        Return
                        " + if(18) + dat+="-- STATIONWIDE WANTED ISSUE --
                        Submitted by: [GLOB.news_network.wanted_issue.scannedUser]
                        " + dat+="Criminal: [GLOB.news_network.wanted_issue.criminal]
                        " + dat+="Description: [GLOB.news_network.wanted_issue.body]
                        " + dat+="Photo:: " + if(GLOB.news_network.wanted_issue.img) + usr << browse_rsc(GLOB.news_network.wanted_issue.img, "tmp_photow.png") + dat+="
                        " + else + dat+="None" + dat+="
                        Back
                        " + if(19) + dat+="Wanted issue for [src.admincaster_wanted_message.criminal] successfully edited.

                        " + dat+="
                        Return
                        " + else + dat+="I'm sorry to break your immersion. This shit's bugged. Report this bug to Agouri, polyxenitopalidou@gmail.com" + + usr << browse(dat, "window=admincaster_main;size=400x600") + onclose(usr, "admincaster_main") + + +/datum/admins/proc/Game() + if(!check_rights(0)) + return + + var/dat = {" +
                        Game Panel

                        \n + Change Game Mode
                        + "} + if(GLOB.master_mode == "secret") + dat += "(Force Secret Mode)
                        " + if(GLOB.master_mode == "dynamic") + if(SSticker.current_state <= GAME_STATE_PREGAME) + dat += "(Force Roundstart Rulesets)
                        " + if (GLOB.dynamic_forced_roundstart_ruleset.len > 0) + for(var/datum/dynamic_ruleset/roundstart/rule in GLOB.dynamic_forced_roundstart_ruleset) + dat += {"-> [rule.name] <-
                        "} + dat += "(Clear Rulesets)
                        " + dat += "(Dynamic mode options)
                        " + else if (SSticker.IsRoundInProgress()) + dat += "(Force Next Latejoin Ruleset)
                        " + if (SSticker && SSticker.mode && istype(SSticker.mode,/datum/game_mode/dynamic)) + var/datum/game_mode/dynamic/mode = SSticker.mode + if (mode.forced_latejoin_rule) + dat += {"-> [mode.forced_latejoin_rule.name] <-
                        "} + dat += "(Execute Midround Ruleset!)
                        " + dat += "
                        " + if(SSticker.IsRoundInProgress()) + dat += "(Game Mode Panel)
                        " + dat += {" +
                        + Create Object
                        + Quick Create Object
                        + Create Turf
                        + Create Mob
                        + "} + + if(marked_datum && istype(marked_datum, /atom)) + dat += "Duplicate Marked Datum
                        " + + var/datum/browser/popup = new(usr, "admin2", null, 240, 280) + popup.set_content(dat) + popup.open() +// usr << browse(dat, "window=admin2;size=240x280") + return + +/////////////////////////////////////////////////////////////////////////////////////////////////admins2.dm merge +//i.e. buttons/verbs + + +/datum/admins/proc/restart() + set category = "Server" + set name = "Reboot World" + set desc="Restarts the world immediately" + if (!usr.client.holder) + return + + var/localhost_addresses = list("127.0.0.1", "::1") + var/list/options = list("Regular Restart", "Regular Restart (with delay)", "Hard Restart (No Delay/Feeback Reason)", "Hardest Restart (No actions, just reboot)") + if(world.TgsAvailable()) + options += "Server Restart (Kill and restart DD)"; + + if(SSticker.admin_delay_notice) + if(alert(usr, "Are you sure? An admin has already delayed the round end for the following reason: [SSticker.admin_delay_notice]", "Confirmation", "Yes", "No") != "Yes") + return FALSE + + var/result = input(usr, "Select reboot method", "World Reboot", options[1]) as null|anything in options + if(result) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Reboot World") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + var/init_by = "Initiated by [usr.client.holder.fakekey ? "Admin" : usr.key]." + switch(result) + if("Regular Restart") + if(!(isnull(usr.client.address) || (usr.client.address in localhost_addresses))) + if(alert("Are you sure you want to restart the server?","This server is live","Restart","Cancel") != "Restart") + return FALSE + SSticker.Reboot(init_by, "admin reboot - by [usr.key] [usr.client.holder.fakekey ? "(stealth)" : ""]", 10) + if("Regular Restart (with delay)") + var/delay = input("What delay should the restart have (in seconds)?", "Restart Delay", 5) as num|null + if(!delay) + return FALSE + if(!(isnull(usr.client.address) || (usr.client.address in localhost_addresses))) + if(alert("Are you sure you want to restart the server?","This server is live","Restart","Cancel") != "Restart") + return FALSE + SSticker.Reboot(init_by, "admin reboot - by [usr.key] [usr.client.holder.fakekey ? "(stealth)" : ""]", delay * 10) + if("Hard Restart (No Delay, No Feeback Reason)") + to_chat(world, "World reboot - [init_by]") + world.Reboot() + if("Hardest Restart (No actions, just reboot)") + to_chat(world, "Hard world reboot - [init_by]") + world.Reboot(fast_track = TRUE) + if("Server Restart (Kill and restart DD)") + to_chat(world, "Server restart - [init_by]") + world.TgsEndProcess() + +/datum/admins/proc/end_round() + set category = "Server" + set name = "End Round" + set desc = "Attempts to produce a round end report and then restart the server organically." + + if (!usr.client.holder) + return + var/confirm = alert("End the round and restart the game world?", "End Round", "Yes", "Cancel") + if(confirm == "Cancel") + return + if(confirm == "Yes") + SSticker.force_ending = 1 + SSblackbox.record_feedback("tally", "admin_verb", 1, "End Round") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + +/datum/admins/proc/announce() + set category = "Admin" + set name = "Announce" + set desc="Announce your desires to the world" + if(!check_rights(0)) + return + + var/message = input("Global message to send:", "Admin Announce", null, null) as message + if(message) + if(!check_rights(R_SERVER,0)) + message = adminscrub(message,500) + to_chat(world, "[usr.client.holder.fakekey ? "Administrator" : usr.key] Announces:\n \t [message]", confidential = TRUE) + log_admin("Announce: [key_name(usr)] : [message]") + SSredbot.send_discord_message("ooc", "**[usr.client.holder.fakekey ? "Administrator" : usr.key] Announces:**\n [message]") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Announce") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/datum/admins/proc/set_admin_notice() + set category = "Server" + set name = "Set Admin Notice" + set desc ="Set an announcement that appears to everyone who joins the server. Only lasts this round" + if(!check_rights(0)) + return + + var/new_admin_notice = input(src,"Set a public notice for this round. Everyone who joins the server will see it.\n(Leaving it blank will delete the current notice):","Set Notice",GLOB.admin_notice) as message|null + if(new_admin_notice == null) + return + if(new_admin_notice == GLOB.admin_notice) + return + if(new_admin_notice == "") + message_admins("[key_name(usr)] removed the admin notice.") + log_admin("[key_name(usr)] removed the admin notice:\n[GLOB.admin_notice]") + else + message_admins("[key_name(usr)] set the admin notice.") + log_admin("[key_name(usr)] set the admin notice:\n[new_admin_notice]") + to_chat(world, "Admin Notice:\n \t [new_admin_notice]", confidential = TRUE) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Set Admin Notice") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + GLOB.admin_notice = new_admin_notice + return + +/datum/admins/proc/toggleooc() + set category = "Server" + set desc="Toggle dis bitch" + set name="Toggle OOC" + toggle_ooc() + log_admin("[key_name(usr)] toggled OOC.") + message_admins("[key_name_admin(usr)] toggled OOC.") + SSredbot.send_discord_message("ooc", "**OOC has been [GLOB.ooc_allowed ? "enabled" : "disabled"] on the server.**") + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle OOC", "[GLOB.ooc_allowed ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +//Begin Wasp Edit +/datum/admins/proc/toggleooclocal() + set category = "Server" + set desc="Toggle dat bitch" + set name="Toggle Local OOC" + toggle_looc() + log_admin("[key_name(usr)] toggled LOOC.") + message_admins("[key_name_admin(usr)] toggled LOOC.") + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Local OOC", "[GLOB.looc_allowed ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! +//End Wasp Edit + +/datum/admins/proc/toggleoocdead() + set category = "Server" + set desc="Toggle dis bitch" + set name="Toggle Dead OOC" + toggle_dooc() + + log_admin("[key_name(usr)] toggled OOC.") + message_admins("[key_name_admin(usr)] toggled Dead OOC.") + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Dead OOC", "[GLOB.dooc_allowed ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/datum/admins/proc/startnow() + set category = "Server" + set desc="Start the round RIGHT NOW" + set name="Start Now" + if(SSticker.current_state == GAME_STATE_PREGAME || SSticker.current_state == GAME_STATE_STARTUP) + if(!SSticker.start_immediately) + var/localhost_addresses = list("127.0.0.1", "::1") + if(!(isnull(usr.client.address) || (usr.client.address in localhost_addresses))) + if(alert("Are you sure you want to start the round?","Start Now","Start Now","Cancel") != "Start Now") + return FALSE + SSticker.start_immediately = TRUE + log_admin("[usr.key] has started the game.") + var/msg = "" + if(SSticker.current_state == GAME_STATE_STARTUP) + msg = " (The server is still setting up, but the round will be \ + started as soon as possible.)" + message_admins("[usr.key] has started the game.[msg]") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Start Now") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + return TRUE + SSticker.start_immediately = FALSE + SSticker.SetTimeLeft(1800) + to_chat(world, "The game will start in 180 seconds.") + SEND_SOUND(world, sound('sound/ai/attention.ogg')) + message_admins("[usr.key] has cancelled immediate game start. Game will start in 180 seconds.") + log_admin("[usr.key] has cancelled immediate game start.") + else + to_chat(usr, "Error: Start Now: Game has already started.") + return FALSE + +/datum/admins/proc/toggleenter() + set category = "Server" + set desc="People can't enter" + set name="Toggle Entering" + GLOB.enter_allowed = !( GLOB.enter_allowed ) + if (!( GLOB.enter_allowed )) + to_chat(world, "New players may no longer enter the game.", confidential = TRUE) + else + to_chat(world, "New players may now enter the game.", confidential = TRUE) + log_admin("[key_name(usr)] toggled new player game entering.") + message_admins("[key_name_admin(usr)] toggled new player game entering.") + world.update_status() + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Entering", "[GLOB.enter_allowed ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/datum/admins/proc/toggleAI() + set category = "Server" + set desc="People can't be AI" + set name="Toggle AI" + var/alai = CONFIG_GET(flag/allow_ai) + CONFIG_SET(flag/allow_ai, !alai) + if (alai) + to_chat(world, "The AI job is no longer chooseable.", confidential = TRUE) + else + to_chat(world, "The AI job is chooseable now.", confidential = TRUE) + log_admin("[key_name(usr)] toggled AI allowed.") + world.update_status() + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle AI", "[!alai ? "Disabled" : "Enabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/datum/admins/proc/toggleaban() + set category = "Server" + set desc="Respawn basically" + set name="Toggle Respawn" + var/new_nores = !CONFIG_GET(flag/norespawn) + CONFIG_SET(flag/norespawn, new_nores) + if (!new_nores) + to_chat(world, "You may now respawn.", confidential = TRUE) + else + to_chat(world, "You may no longer respawn :(", confidential = TRUE) + message_admins("[key_name_admin(usr)] toggled respawn to [!new_nores ? "On" : "Off"].") + log_admin("[key_name(usr)] toggled respawn to [!new_nores ? "On" : "Off"].") + world.update_status() + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Respawn", "[!new_nores ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/datum/admins/proc/delay() + set category = "Server" + set desc="Delay the game start" + set name="Delay Pre-Game" + + var/newtime = input("Set a new time in seconds. Set -1 for indefinite delay.","Set Delay",round(SSticker.GetTimeLeft()/10)) as num|null + if(SSticker.current_state > GAME_STATE_PREGAME) + return alert("Too late... The game has already started!") + if(newtime) + newtime = newtime*10 + SSticker.SetTimeLeft(newtime) + SSticker.start_immediately = FALSE + if(newtime < 0) + to_chat(world, "The game start has been delayed.", confidential = TRUE) + log_admin("[key_name(usr)] delayed the round start.") + else + to_chat(world, "The game will start in [DisplayTimeText(newtime)].", confidential = TRUE) + SEND_SOUND(world, sound('sound/ai/attention.ogg')) + log_admin("[key_name(usr)] set the pre-game delay to [DisplayTimeText(newtime)].") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Delay Game Start") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/datum/admins/proc/unprison(mob/M in GLOB.mob_list) + set category = "Admin" + set name = "Unprison" + if (is_centcom_level(M.z)) + SSjob.SendToLateJoin(M) + message_admins("[key_name_admin(usr)] has unprisoned [key_name_admin(M)]") + log_admin("[key_name(usr)] has unprisoned [key_name(M)]") + else + alert("[M.name] is not prisoned.") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Unprison") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +////////////////////////////////////////////////////////////////////////////////////////////////ADMIN HELPER PROCS + +/datum/admins/proc/spawn_atom(object as text) + set category = "Debug" + set desc = "(atom path) Spawn an atom" + set name = "Spawn" + + if(!check_rights(R_SPAWN) || !object) + return + + var/list/preparsed = splittext(object,":") + var/path = preparsed[1] + var/amount = 1 + if(preparsed.len > 1) + amount = clamp(text2num(preparsed[2]),1,ADMIN_SPAWN_CAP) + + var/chosen = pick_closest_path(path) + if(!chosen) + return + var/turf/T = get_turf(usr) + + if(ispath(chosen, /turf)) + T.ChangeTurf(chosen) + else + for(var/i in 1 to amount) + var/atom/A = new chosen(T) + A.flags_1 |= ADMIN_SPAWNED_1 + + log_admin("[key_name(usr)] spawned [amount] x [chosen] at [AREACOORD(usr)]") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Spawn Atom") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/datum/admins/proc/podspawn_atom(object as text) + set category = "Debug" + set desc = "(atom path) Spawn an atom via supply drop" + set name = "Podspawn" + + if(!check_rights(R_SPAWN)) + return + + var/chosen = pick_closest_path(object) + if(!chosen) + return + var/turf/T = get_turf(usr) + + if(ispath(chosen, /turf)) + T.ChangeTurf(chosen) + else + var/obj/structure/closet/supplypod/centcompod/pod = new() + var/atom/A = new chosen(pod) + A.flags_1 |= ADMIN_SPAWNED_1 + new /obj/effect/DPtarget(T, pod) + + log_admin("[key_name(usr)] pod-spawned [chosen] at [AREACOORD(usr)]") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Podspawn Atom") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/datum/admins/proc/spawn_cargo(object as text) + set category = "Debug" + set desc = "(atom path) Spawn a cargo crate" + set name = "Spawn Cargo" + + if(!check_rights(R_SPAWN)) + return + + var/chosen = pick_closest_path(object, make_types_fancy(subtypesof(/datum/supply_pack))) + if(!chosen) + return + var/datum/supply_pack/S = new chosen + S.admin_spawned = TRUE + S.generate(get_turf(usr)) + + log_admin("[key_name(usr)] spawned cargo pack [chosen] at [AREACOORD(usr)]") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Spawn Cargo") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + + +/datum/admins/proc/show_traitor_panel(mob/target_mob in GLOB.mob_list) + set category = "Admin - Game" + set desc = "Edit mobs's memory and role" + set name = "Show Traitor Panel" + var/datum/mind/target_mind = target_mob.mind + if(!target_mind) + to_chat(usr, "This mob has no mind!", confidential = TRUE) + return + if(!istype(target_mob) && !istype(target_mind)) + to_chat(usr, "This can only be used on instances of type /mob and /mind", confidential = TRUE) + return + target_mind.traitor_panel() + SSblackbox.record_feedback("tally", "admin_verb", 1, "Traitor Panel") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/datum/admins/proc/show_skill_panel(var/target) + set category = "Admin - Game" + set desc = "Edit mobs's experience and skill levels" + set name = "Show Skill Panel" + var/datum/mind/target_mind + if(ismob(target)) + var/mob/target_mob = target + target_mind = target_mob.mind + else if (istype(target, /datum/mind)) + target_mind = target + else + to_chat(usr, "This can only be used on instances of type /mob and /mind", confidential = TRUE) + return + var/datum/skill_panel/SP = new(usr, target_mind) + SP.ui_interact(usr) + +/datum/admins/proc/toggletintedweldhelmets() + set category = "Debug" + set desc="Reduces view range when wearing welding helmets" + set name="Toggle tinted welding helmes" + GLOB.tinted_weldhelh = !( GLOB.tinted_weldhelh ) + if (GLOB.tinted_weldhelh) + to_chat(world, "The tinted_weldhelh has been enabled!", confidential = TRUE) + else + to_chat(world, "The tinted_weldhelh has been disabled!", confidential = TRUE) + log_admin("[key_name(usr)] toggled tinted_weldhelh.") + message_admins("[key_name_admin(usr)] toggled tinted_weldhelh.") + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Tinted Welding Helmets", "[GLOB.tinted_weldhelh ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/datum/admins/proc/toggleguests() + set category = "Server" + set desc="Guests can't enter" + set name="Toggle guests" + var/new_guest_ban = !CONFIG_GET(flag/guest_ban) + CONFIG_SET(flag/guest_ban, new_guest_ban) + if (new_guest_ban) + to_chat(world, "Guests may no longer enter the game.", confidential = TRUE) + else + to_chat(world, "Guests may now enter the game.", confidential = TRUE) + log_admin("[key_name(usr)] toggled guests game entering [!new_guest_ban ? "" : "dis"]allowed.") + message_admins("[key_name_admin(usr)] toggled guests game entering [!new_guest_ban ? "" : "dis"]allowed.") + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Guests", "[!new_guest_ban ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/datum/admins/proc/output_ai_laws() + var/ai_number = 0 + for(var/i in GLOB.silicon_mobs) + var/mob/living/silicon/S = i + ai_number++ + if(isAI(S)) + to_chat(usr, "AI [key_name(S, usr)]'s laws:", confidential = TRUE) + else if(iscyborg(S)) + var/mob/living/silicon/robot/R = S + to_chat(usr, "CYBORG [key_name(S, usr)] [R.connected_ai?"(Slaved to: [key_name(R.connected_ai)])":"(Independent)"]: laws:", confidential = TRUE) + else if (ispAI(S)) + to_chat(usr, "pAI [key_name(S, usr)]'s laws:", confidential = TRUE) + else + to_chat(usr, "SOMETHING SILICON [key_name(S, usr)]'s laws:", confidential = TRUE) + + if (S.laws == null) + to_chat(usr, "[key_name(S, usr)]'s laws are null?? Contact a coder.", confidential = TRUE) + else + S.laws.show_laws(usr) + if(!ai_number) + to_chat(usr, "No AIs located" , confidential = TRUE) + +/datum/admins/proc/output_all_devil_info() + var/devil_number = 0 + for(var/datum/mind/D in SSticker.mode.devils) + devil_number++ + var/datum/antagonist/devil/devil = D.has_antag_datum(/datum/antagonist/devil) + to_chat(usr, "Devil #[devil_number]:

                        " + devil.printdevilinfo(), confidential = TRUE) + if(!devil_number) + to_chat(usr, "No Devils located" , confidential = TRUE) + +/datum/admins/proc/output_devil_info(mob/living/M) + if(is_devil(M)) + var/datum/antagonist/devil/devil = M.mind.has_antag_datum(/datum/antagonist/devil) + to_chat(usr, devil.printdevilinfo(), confidential = TRUE) + else + to_chat(usr, "[M] is not a devil.", confidential = TRUE) + +/datum/admins/proc/manage_free_slots() + if(!check_rights()) + return + var/datum/browser/browser = new(usr, "jobmanagement", "Manage Free Slots", 520) + var/list/dat = list() + var/count = 0 + + if(!SSjob.initialized) + alert(usr, "You cannot manage jobs before the job subsystem is initialized!") + return + + dat += "
                        [J_title]: [J_opPos]/[job.total_positions < 0 ? " (unlimited)" : J_totPos]" - - dat += "" - if(job.total_positions >= 0) - dat += "Custom" - dat += "Add 1" - if(job.total_positions > job.current_positions) - dat += "Remove" - else - dat += "Remove" - dat += "Unlimit
                        " + + for(var/j in SSjob.occupations) + var/datum/job/job = j + count++ + var/J_title = html_encode(job.title) + var/J_opPos = html_encode(job.total_positions - (job.total_positions - job.current_positions)) + var/J_totPos = html_encode(job.total_positions) + dat += "" + dat += "" + else + dat += "Limit" + + browser.height = min(100 + count * 20, 650) + browser.set_content(dat.Join()) + browser.open() + +/datum/admins/proc/dynamic_mode_options(mob/user) + var/dat = {" +

                        Dynamic Mode Options


                        +
                        +

                        Common options

                        + All these options can be changed midround.
                        +
                        + Force extended: - Option is [GLOB.dynamic_forced_extended ? "ON" : "OFF"]. +
                        This will force the round to be extended. No rulesets will be drafted.
                        +
                        + No stacking: - Option is [GLOB.dynamic_no_stacking ? "ON" : "OFF"]. +
                        Unless the threat goes above [GLOB.dynamic_stacking_limit], only one "round-ender" ruleset will be drafted.
                        +
                        + Classic secret mode: - Option is [GLOB.dynamic_classic_secret ? "ON" : "OFF"]. +
                        Only one roundstart ruleset will be drafted. Only traitors and minor roles will latespawn.
                        +
                        +
                        + Forced threat level: Current value : [GLOB.dynamic_forced_threat_level]. +
                        The value threat is set to if it is higher than -1.
                        +
                        + High population limit: Current value : [GLOB.dynamic_high_pop_limit]. +
                        The threshold at which "high population override" will be in effect.
                        +
                        + Stacking threeshold: Current value : [GLOB.dynamic_stacking_limit]. +
                        The threshold at which "round-ender" rulesets will stack. A value higher than 100 ensure this never happens.
                        +

                        Advanced parameters

                        + Curve centre: -> [GLOB.dynamic_curve_centre] <-
                        + Curve width: -> [GLOB.dynamic_curve_width] <-
                        + Latejoin injection delay:
                        + Minimum: -> [GLOB.dynamic_latejoin_delay_min / 60 / 10] <- Minutes
                        + Maximum: -> [GLOB.dynamic_latejoin_delay_max / 60 / 10] <- Minutes
                        + Midround injection delay:
                        + Minimum: -> [GLOB.dynamic_midround_delay_min / 60 / 10] <- Minutes
                        + Maximum: -> [GLOB.dynamic_midround_delay_max / 60 / 10] <- Minutes
                        + "} + + user << browse(dat, "window=dyn_mode_options;size=900x650") + +/datum/admins/proc/create_or_modify_area() + set category = "Debug" + set name = "Create or modify area" + create_area(usr) + +// +// +//ALL DONE +//********************************************************************************************************* +//TO-DO: +// +// + +//RIP ferry snowflakes + +//Kicks all the clients currently in the lobby. The second parameter (kick_only_afk) determins if an is_afk() check is ran, or if all clients are kicked +//defaults to kicking everyone (afk + non afk clients in the lobby) +//returns a list of ckeys of the kicked clients +/proc/kick_clients_in_lobby(message, kick_only_afk = 0) + var/list/kicked_client_names = list() + for(var/client/C in GLOB.clients) + if(isnewplayer(C.mob)) + if(kick_only_afk && !C.is_afk()) //Ignore clients who are not afk + continue + if(message) + to_chat(C, message, confidential = TRUE) + kicked_client_names.Add("[C.key]") + qdel(C) + return kicked_client_names + +//returns TRUE to let the dragdrop code know we are trapping this event +//returns FALSE if we don't plan to trap the event +/datum/admins/proc/cmd_ghost_drag(mob/dead/observer/frommob, mob/tomob) + + //this is the exact two check rights checks required to edit a ckey with vv. + if (!check_rights(R_VAREDIT,0) || !check_rights(R_SPAWN|R_DEBUG,0)) + return FALSE + + if (!frommob.ckey) + return FALSE + + var/question = "" + if (tomob.ckey) + question = "This mob already has a user ([tomob.key]) in control of it! " + question += "Are you sure you want to place [frommob.name]([frommob.key]) in control of [tomob.name]?" + + var/ask = alert(question, "Place ghost in control of mob?", "Yes", "No") + if (ask != "Yes") + return TRUE + + if (!frommob || !tomob) //make sure the mobs don't go away while we waited for a response + return TRUE + + // Disassociates observer mind from the body mind + if(tomob.client) + tomob.ghostize(FALSE) + else + for(var/mob/dead/observer/ghost in GLOB.dead_mob_list) + if(tomob.mind == ghost.mind) + ghost.mind = null + + message_admins("[key_name_admin(usr)] has put [frommob.key] in control of [tomob.name].") + log_admin("[key_name(usr)] stuffed [frommob.key] into [tomob.name].") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Ghost Drag Control") + + tomob.ckey = frommob.ckey + qdel(frommob) + + return TRUE + +/client/proc/adminGreet(logout) + if(SSticker.HasRoundStarted()) + var/string + if(logout && CONFIG_GET(flag/announce_admin_logout)) + string = pick( + "Admin logout: [key_name(src)]") + else if(!logout && CONFIG_GET(flag/announce_admin_login) && (prefs.toggles & ANNOUNCE_LOGIN)) + string = pick( + "Admin login: [key_name(src)]") + if(string) + message_admins("[string]") diff --git a/code/modules/admin/admin_investigate.dm b/code/modules/admin/admin_investigate.dm index c89d3d0f0264..e278d885c8eb 100644 --- a/code/modules/admin/admin_investigate.dm +++ b/code/modules/admin/admin_investigate.dm @@ -1,42 +1,42 @@ -/atom/proc/investigate_log(message, subject) - if(!message || !subject) - return - var/F = file("[GLOB.log_directory]/[subject].html") - WRITE_FILE(F, "[time_stamp()] [REF(src)] ([x],[y],[z]) || [src] [message]
                        ") - -/client/proc/investigate_show() - set name = "Investigate" - set category = "Admin - Game" - if(!holder) - return - - var/list/investigates = list(INVESTIGATE_RESEARCH, INVESTIGATE_EXONET, INVESTIGATE_PORTAL, INVESTIGATE_SINGULO, INVESTIGATE_WIRES, INVESTIGATE_TELESCI, INVESTIGATE_GRAVITY, INVESTIGATE_RECORDS, INVESTIGATE_CARGO, INVESTIGATE_SUPERMATTER, INVESTIGATE_ATMOS, INVESTIGATE_EXPERIMENTOR, INVESTIGATE_BOTANY, INVESTIGATE_HALLUCINATIONS, INVESTIGATE_RADIATION, INVESTIGATE_NANITES, INVESTIGATE_PRESENTS) - - var/list/logs_present = list("notes, memos, watchlist") - var/list/logs_missing = list("---") - - for(var/subject in investigates) - var/temp_file = file("[GLOB.log_directory]/[subject].html") - if(fexists(temp_file)) - logs_present += subject - else - logs_missing += "[subject] (empty)" - - var/list/combined = sortList(logs_present) + sortList(logs_missing) - - var/selected = input("Investigate what?", "Investigate") as null|anything in combined - - if(!(selected in combined) || selected == "---") - return - - selected = replacetext(selected, " (empty)", "") - - if(selected == "notes, memos, watchlist" && check_rights(R_ADMIN)) - browse_messages() - return - - var/F = file("[GLOB.log_directory]/[selected].html") - if(!fexists(F)) - to_chat(src, "No [selected] logfile was found.", confidential = TRUE) - return - src << browse(F,"window=investigate[selected];size=800x300") +/atom/proc/investigate_log(message, subject) + if(!message || !subject) + return + var/F = file("[GLOB.log_directory]/[subject].html") + WRITE_FILE(F, "[time_stamp()] [REF(src)] ([x],[y],[z]) || [src] [message]
                        ") + +/client/proc/investigate_show() + set name = "Investigate" + set category = "Admin - Game" + if(!holder) + return + + var/list/investigates = list(INVESTIGATE_RESEARCH, INVESTIGATE_EXONET, INVESTIGATE_PORTAL, INVESTIGATE_SINGULO, INVESTIGATE_WIRES, INVESTIGATE_TELESCI, INVESTIGATE_GRAVITY, INVESTIGATE_RECORDS, INVESTIGATE_CARGO, INVESTIGATE_SUPERMATTER, INVESTIGATE_ATMOS, INVESTIGATE_EXPERIMENTOR, INVESTIGATE_BOTANY, INVESTIGATE_HALLUCINATIONS, INVESTIGATE_RADIATION, INVESTIGATE_NANITES, INVESTIGATE_PRESENTS) + + var/list/logs_present = list("notes, memos, watchlist") + var/list/logs_missing = list("---") + + for(var/subject in investigates) + var/temp_file = file("[GLOB.log_directory]/[subject].html") + if(fexists(temp_file)) + logs_present += subject + else + logs_missing += "[subject] (empty)" + + var/list/combined = sortList(logs_present) + sortList(logs_missing) + + var/selected = input("Investigate what?", "Investigate") as null|anything in combined + + if(!(selected in combined) || selected == "---") + return + + selected = replacetext(selected, " (empty)", "") + + if(selected == "notes, memos, watchlist" && check_rights(R_ADMIN)) + browse_messages() + return + + var/F = file("[GLOB.log_directory]/[selected].html") + if(!fexists(F)) + to_chat(src, "No [selected] logfile was found.", confidential = TRUE) + return + src << browse(F,"window=investigate[selected];size=800x300") diff --git a/code/modules/admin/admin_ranks.dm b/code/modules/admin/admin_ranks.dm index a5d991f33f25..09a456ff809d 100644 --- a/code/modules/admin/admin_ranks.dm +++ b/code/modules/admin/admin_ranks.dm @@ -1,298 +1,294 @@ -GLOBAL_LIST_EMPTY(admin_ranks) //list of all admin_rank datums -GLOBAL_PROTECT(admin_ranks) - -GLOBAL_LIST_EMPTY(protected_ranks) //admin ranks loaded from txt -GLOBAL_PROTECT(protected_ranks) - -/datum/admin_rank - var/name = "NoRank" - var/rights = R_DEFAULT - var/exclude_rights = 0 - var/include_rights = 0 - var/can_edit_rights = 0 - -/datum/admin_rank/New(init_name, init_rights, init_exclude_rights, init_edit_rights) - if(IsAdminAdvancedProcCall()) - var/msg = " has tried to elevate permissions!" - message_admins("[key_name_admin(usr)][msg]") - log_admin("[key_name(usr)][msg]") - if (name == "NoRank") //only del if this is a true creation (and not just a New() proc call), other wise trialmins/coders could abuse this to deadmin other admins - QDEL_IN(src, 0) - CRASH("Admin proc call creation of admin datum") - return - name = init_name - if(!name) - qdel(src) - CRASH("Admin rank created without name.") - if(init_rights) - rights = init_rights - include_rights = rights - if(init_exclude_rights) - exclude_rights = init_exclude_rights - rights &= ~exclude_rights - if(init_edit_rights) - can_edit_rights = init_edit_rights - -/datum/admin_rank/Destroy() - if(IsAdminAdvancedProcCall()) - var/msg = " has tried to elevate permissions!" - message_admins("[key_name_admin(usr)][msg]") - log_admin("[key_name(usr)][msg]") - return QDEL_HINT_LETMELIVE - . = ..() - -/datum/admin_rank/vv_edit_var(var_name, var_value) - return FALSE - -// Adds/removes rights to this admin_rank -/datum/admin_rank/proc/process_keyword(group, group_count, datum/admin_rank/previous_rank) - if(IsAdminAdvancedProcCall()) - var/msg = " has tried to elevate permissions!" - message_admins("[key_name_admin(usr)][msg]") - log_admin("[key_name(usr)][msg]") - return - var/list/keywords = splittext(group, " ") - var/flag = 0 - for(var/k in keywords) - switch(k) - if("BUILD") - flag = R_BUILD - if("ADMIN") - flag = R_ADMIN - if("MENTOR") - flag = R_MENTOR - if("BAN") - flag = R_BAN - if("FUN") - flag = R_FUN - if("SERVER") - flag = R_SERVER - if("DEBUG") - flag = R_DEBUG - if("PERMISSIONS") - flag = R_PERMISSIONS - if("POSSESS") - flag = R_POSSESS - if("STEALTH") - flag = R_STEALTH - if("POLL") - flag = R_POLL - if("VAREDIT") - flag = R_VAREDIT - if("EVERYTHING") - flag = R_EVERYTHING - if("SOUND") - flag = R_SOUND - if("SPAWN") - flag = R_SPAWN - if("AUTOADMIN") - flag = R_AUTOADMIN - if("DBRANKS") - flag = R_DBRANKS - if("@") - if(previous_rank) - switch(group_count) - if(1) - flag = previous_rank.include_rights - if(2) - flag = previous_rank.exclude_rights - if(3) - flag = previous_rank.can_edit_rights - else - continue - switch(group_count) - if(1) - rights |= flag - include_rights |= flag - if(2) - rights &= ~flag - exclude_rights |= flag - if(3) - can_edit_rights |= flag - -/proc/sync_ranks_with_db() - set waitfor = FALSE - - if(IsAdminAdvancedProcCall()) - 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]")) - SSdbcore.MassInsert(format_table_name("admin_ranks"), sql_ranks, duplicate_key = TRUE) - -//load our rank - > rights associations -/proc/load_admin_ranks(dbfail, no_update) - if(IsAdminAdvancedProcCall()) - to_chat(usr, "Admin Reload blocked: Advanced ProcCall detected.", confidential = TRUE) - return - GLOB.admin_ranks.Cut() - GLOB.protected_ranks.Cut() - //load text from file and process each entry - var/ranks_text = file2text("[global.config.directory]/admin_ranks.txt") - var/datum/admin_rank/previous_rank - var/regex/admin_ranks_regex = new(@"^Name\s*=\s*(.+?)\s*\n+Include\s*=\s*([\l @]*?)\s*\n+Exclude\s*=\s*([\l @]*?)\s*\n+Edit\s*=\s*([\l @]*?)\s*\n*$", "gm") - while(admin_ranks_regex.Find(ranks_text)) - var/datum/admin_rank/R = new(admin_ranks_regex.group[1]) - if(!R) - continue - var/count = 1 - for(var/i in admin_ranks_regex.group - admin_ranks_regex.group[1]) - if(i) - R.process_keyword(i, count, previous_rank) - count++ - GLOB.admin_ranks += R - GLOB.protected_ranks += R - previous_rank = R - if(!CONFIG_GET(flag/admin_legacy_system) || dbfail) - if(CONFIG_GET(flag/load_legacy_ranks_only)) - 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")]") - 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.") - dbfail = 1 - else - while(query_load_admin_ranks.NextRow()) - var/skip - 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 - break - if(!skip) - var/rank_flags = text2num(query_load_admin_ranks.item[2]) - var/rank_exclude_flags = text2num(query_load_admin_ranks.item[3]) - var/rank_can_edit_flags = text2num(query_load_admin_ranks.item[4]) - var/datum/admin_rank/R = new(rank_name, rank_flags, rank_exclude_flags, rank_can_edit_flags) - if(!R) - continue - GLOB.admin_ranks += R - qdel(query_load_admin_ranks) - //load ranks from backup file - if(dbfail) - var/backup_file = file2text("data/admins_backup.json") - if(backup_file == null) - log_world("Unable to locate admins backup file.") - return FALSE - var/list/json = json_decode(backup_file) - for(var/J in json["ranks"]) - var/skip - for(var/datum/admin_rank/R in GLOB.admin_ranks) - if(R.name == "[J]") //this rank was already loaded from txt override - skip = TRUE - if(skip) - continue - var/datum/admin_rank/R = new("[J]", json["ranks"]["[J]"]["include rights"], json["ranks"]["[J]"]["exclude rights"], json["ranks"]["[J]"]["can edit rights"]) - if(!R) - continue - GLOB.admin_ranks += R - return json - #ifdef TESTING - var/msg = "Permission Sets Built:\n" - for(var/datum/admin_rank/R in GLOB.admin_ranks) - msg += "\t[R.name]" - var/rights = rights2text(R.rights,"\n\t\t") - if(rights) - msg += "\t\t[rights]\n" - testing(msg) - #endif - -/proc/load_admins(no_update) - var/dbfail - if(!CONFIG_GET(flag/admin_legacy_system) && !SSdbcore.Connect()) - message_admins("Failed to connect to database while loading admins. Loading from backup.") - log_sql("Failed to connect to database while loading admins. Loading from backup.") - dbfail = 1 - //clear the datums references - GLOB.admin_datums.Cut() - for(var/client/C in GLOB.admins) - C.remove_admin_verbs() - C.holder = null - GLOB.admins.Cut() - GLOB.protected_admins.Cut() - GLOB.deadmins.Cut() - var/list/backup_file_json = load_admin_ranks(dbfail, no_update) - dbfail = backup_file_json != null - //Clear profile access - for(var/A in world.GetConfig("admin")) - world.SetConfig("APP/admin", A, null) - var/list/rank_names = list() - 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/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`") - 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.") - dbfail = 1 - else - while(query_load_admins.NextRow()) - var/admin_ckey = ckey(query_load_admins.item[1]) - 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].") - skip = 1 - if(GLOB.admin_datums[admin_ckey] || GLOB.deadmins[admin_ckey]) - skip = 1 - if(!skip) - new /datum/admins(rank_names[admin_rank], admin_ckey) - qdel(query_load_admins) - //load admins from backup file - if(dbfail) - if(!backup_file_json) - if(backup_file_json != null) - //already tried - return - var/backup_file = file2text("data/admins_backup.json") - if(backup_file == null) - log_world("Unable to locate admins backup file.") - return - backup_file_json = json_decode(backup_file) - for(var/J in backup_file_json["admins"]) - var/skip - for(var/A in GLOB.admin_datums + GLOB.deadmins) - if(A == "[J]") //this admin was already loaded from txt override - skip = TRUE - if(skip) - continue - new /datum/admins(rank_names[backup_file_json["admins"]["[J]"]], ckey("[J]")) - #ifdef TESTING - var/msg = "Admins Built:\n" - for(var/ckey in GLOB.admin_datums) - var/datum/admins/D = GLOB.admin_datums[ckey] - msg += "\t[ckey] - [D.rank.name]\n" - testing(msg) - #endif - return dbfail - -#ifdef TESTING -/client/verb/changerank(newrank in GLOB.admin_ranks) - if(holder) - holder.rank = newrank - else - holder = new /datum/admins(newrank, ckey) - remove_admin_verbs() - holder.associate(src) - -/client/verb/changerights(newrights as num) - if(holder) - holder.rank.rights = newrights - else - holder = new /datum/admins("testing", newrights, ckey) - remove_admin_verbs() - holder.associate(src) -#endif +GLOBAL_LIST_EMPTY(admin_ranks) //list of all admin_rank datums +GLOBAL_PROTECT(admin_ranks) + +GLOBAL_LIST_EMPTY(protected_ranks) //admin ranks loaded from txt +GLOBAL_PROTECT(protected_ranks) + +/datum/admin_rank + var/name = "NoRank" + var/rights = R_DEFAULT + var/exclude_rights = 0 + var/include_rights = 0 + var/can_edit_rights = 0 + +/datum/admin_rank/New(init_name, init_rights, init_exclude_rights, init_edit_rights) + if(IsAdminAdvancedProcCall()) + var/msg = " has tried to elevate permissions!" + message_admins("[key_name_admin(usr)][msg]") + log_admin("[key_name(usr)][msg]") + if (name == "NoRank") //only del if this is a true creation (and not just a New() proc call), other wise trialmins/coders could abuse this to deadmin other admins + QDEL_IN(src, 0) + CRASH("Admin proc call creation of admin datum") + return + name = init_name + if(!name) + qdel(src) + CRASH("Admin rank created without name.") + if(init_rights) + rights = init_rights + include_rights = rights + if(init_exclude_rights) + exclude_rights = init_exclude_rights + rights &= ~exclude_rights + if(init_edit_rights) + can_edit_rights = init_edit_rights + +/datum/admin_rank/Destroy() + if(IsAdminAdvancedProcCall()) + var/msg = " has tried to elevate permissions!" + message_admins("[key_name_admin(usr)][msg]") + log_admin("[key_name(usr)][msg]") + return QDEL_HINT_LETMELIVE + . = ..() + +/datum/admin_rank/vv_edit_var(var_name, var_value) + return FALSE + +// Adds/removes rights to this admin_rank +/datum/admin_rank/proc/process_keyword(group, group_count, datum/admin_rank/previous_rank) + if(IsAdminAdvancedProcCall()) + var/msg = " has tried to elevate permissions!" + message_admins("[key_name_admin(usr)][msg]") + log_admin("[key_name(usr)][msg]") + return + var/list/keywords = splittext(group, " ") + var/flag = 0 + for(var/k in keywords) + switch(k) + if("BUILD") + flag = R_BUILD + if("ADMIN") + flag = R_ADMIN + if("MENTOR") + flag = R_MENTOR + if("BAN") + flag = R_BAN + if("FUN") + flag = R_FUN + if("SERVER") + flag = R_SERVER + if("DEBUG") + flag = R_DEBUG + if("PERMISSIONS") + flag = R_PERMISSIONS + if("POSSESS") + flag = R_POSSESS + if("STEALTH") + flag = R_STEALTH + if("POLL") + flag = R_POLL + if("VAREDIT") + flag = R_VAREDIT + if("EVERYTHING") + flag = R_EVERYTHING + if("SOUND") + flag = R_SOUND + if("SPAWN") + flag = R_SPAWN + if("AUTOADMIN") + flag = R_AUTOADMIN + if("DBRANKS") + flag = R_DBRANKS + if("@") + if(previous_rank) + switch(group_count) + if(1) + flag = previous_rank.include_rights + if(2) + flag = previous_rank.exclude_rights + if(3) + flag = previous_rank.can_edit_rights + else + continue + switch(group_count) + if(1) + rights |= flag + include_rights |= flag + if(2) + rights &= ~flag + exclude_rights |= flag + if(3) + can_edit_rights |= flag + +/proc/sync_ranks_with_db() + set waitfor = FALSE + + if(IsAdminAdvancedProcCall()) + 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) + 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 +/proc/load_admin_ranks(dbfail, no_update) + if(IsAdminAdvancedProcCall()) + to_chat(usr, "Admin Reload blocked: Advanced ProcCall detected.", confidential = TRUE) + return + GLOB.admin_ranks.Cut() + GLOB.protected_ranks.Cut() + //load text from file and process each entry + var/ranks_text = file2text("[global.config.directory]/admin_ranks.txt") + var/datum/admin_rank/previous_rank + var/regex/admin_ranks_regex = new(@"^Name\s*=\s*(.+?)\s*\n+Include\s*=\s*([\l @]*?)\s*\n+Exclude\s*=\s*([\l @]*?)\s*\n+Edit\s*=\s*([\l @]*?)\s*\n*$", "gm") + while(admin_ranks_regex.Find(ranks_text)) + var/datum/admin_rank/R = new(admin_ranks_regex.group[1]) + if(!R) + continue + var/count = 1 + for(var/i in admin_ranks_regex.group - admin_ranks_regex.group[1]) + if(i) + R.process_keyword(i, count, previous_rank) + count++ + GLOB.admin_ranks += R + GLOB.protected_ranks += R + previous_rank = R + if(!CONFIG_GET(flag/admin_legacy_system) || dbfail) + if(CONFIG_GET(flag/load_legacy_ranks_only)) + 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")]") + 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.") + dbfail = 1 + else + while(query_load_admin_ranks.NextRow()) + var/skip + 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 + break + if(!skip) + var/rank_flags = text2num(query_load_admin_ranks.item[2]) + var/rank_exclude_flags = text2num(query_load_admin_ranks.item[3]) + var/rank_can_edit_flags = text2num(query_load_admin_ranks.item[4]) + var/datum/admin_rank/R = new(rank_name, rank_flags, rank_exclude_flags, rank_can_edit_flags) + if(!R) + continue + GLOB.admin_ranks += R + qdel(query_load_admin_ranks) + //load ranks from backup file + if(dbfail) + var/backup_file = file2text("data/admins_backup.json") + if(backup_file == null) + log_world("Unable to locate admins backup file.") + return FALSE + var/list/json = json_decode(backup_file) + for(var/J in json["ranks"]) + var/skip + for(var/datum/admin_rank/R in GLOB.admin_ranks) + if(R.name == "[J]") //this rank was already loaded from txt override + skip = TRUE + if(skip) + continue + var/datum/admin_rank/R = new("[J]", json["ranks"]["[J]"]["include rights"], json["ranks"]["[J]"]["exclude rights"], json["ranks"]["[J]"]["can edit rights"]) + if(!R) + continue + GLOB.admin_ranks += R + return json + #ifdef TESTING + var/msg = "Permission Sets Built:\n" + for(var/datum/admin_rank/R in GLOB.admin_ranks) + msg += "\t[R.name]" + var/rights = rights2text(R.rights,"\n\t\t") + if(rights) + msg += "\t\t[rights]\n" + testing(msg) + #endif + +/proc/load_admins(no_update) + var/dbfail + if(!CONFIG_GET(flag/admin_legacy_system) && !SSdbcore.Connect()) + message_admins("Failed to connect to database while loading admins. Loading from backup.") + log_sql("Failed to connect to database while loading admins. Loading from backup.") + dbfail = 1 + //clear the datums references + GLOB.admin_datums.Cut() + for(var/client/C in GLOB.admins) + C.remove_admin_verbs() + C.holder = null + GLOB.admins.Cut() + GLOB.protected_admins.Cut() + GLOB.deadmins.Cut() + var/list/backup_file_json = load_admin_ranks(dbfail, no_update) + dbfail = backup_file_json != null + //Clear profile access + for(var/A in world.GetConfig("admin")) + world.SetConfig("APP/admin", A, null) + var/list/rank_names = list() + 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/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`") + 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.") + dbfail = 1 + else + while(query_load_admins.NextRow()) + var/admin_ckey = ckey(query_load_admins.item[1]) + 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].") + skip = 1 + if(GLOB.admin_datums[admin_ckey] || GLOB.deadmins[admin_ckey]) + skip = 1 + if(!skip) + new /datum/admins(rank_names[admin_rank], admin_ckey) + qdel(query_load_admins) + //load admins from backup file + if(dbfail) + if(!backup_file_json) + if(backup_file_json != null) + //already tried + return + var/backup_file = file2text("data/admins_backup.json") + if(backup_file == null) + log_world("Unable to locate admins backup file.") + return + backup_file_json = json_decode(backup_file) + for(var/J in backup_file_json["admins"]) + var/skip + for(var/A in GLOB.admin_datums + GLOB.deadmins) + if(A == "[J]") //this admin was already loaded from txt override + skip = TRUE + if(skip) + continue + new /datum/admins(rank_names[backup_file_json["admins"]["[J]"]], ckey("[J]")) + #ifdef TESTING + var/msg = "Admins Built:\n" + for(var/ckey in GLOB.admin_datums) + var/datum/admins/D = GLOB.admin_datums[ckey] + msg += "\t[ckey] - [D.rank.name]\n" + testing(msg) + #endif + return dbfail + +#ifdef TESTING +/client/verb/changerank(newrank in GLOB.admin_ranks) + if(holder) + holder.rank = newrank + else + holder = new /datum/admins(newrank, ckey) + remove_admin_verbs() + holder.associate(src) + +/client/verb/changerights(newrights as num) + if(holder) + holder.rank.rights = newrights + else + holder = new /datum/admins("testing", newrights, ckey) + remove_admin_verbs() + holder.associate(src) +#endif diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index bbeae4485fe8..32402953511a 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -171,6 +171,10 @@ GLOBAL_PROTECT(admin_verbs_debug) /client/proc/cmd_display_overlay_log, /client/proc/reload_configuration, /datum/admins/proc/create_or_modify_area, +#ifdef REFERENCE_TRACKING + /datum/admins/proc/view_refs, + /datum/admins/proc/view_del_failures, +#endif ) GLOBAL_LIST_INIT(admin_verbs_possess, list(/proc/possess, /proc/release)) GLOBAL_PROTECT(admin_verbs_possess) @@ -185,6 +189,7 @@ GLOBAL_LIST_INIT(admin_verbs_hideable, list( /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, diff --git a/code/modules/admin/chat_commands.dm b/code/modules/admin/chat_commands.dm index e469302dc8f2..2103879df0cd 100644 --- a/code/modules/admin/chat_commands.dm +++ b/code/modules/admin/chat_commands.dm @@ -1,142 +1,142 @@ -#define TGS_STATUS_THROTTLE 5 - -/datum/tgs_chat_command/restart - name = "restart" - help_text = "Restarts the server if there are no active admins on." - -/datum/tgs_chat_command/restart/Run(datum/tgs_chat_user/sender, params) - var/active_admins = FALSE - for(var/client/C in GLOB.admins) - if(!C.is_afk() && check_rights_for(C, R_SERVER)) - active_admins = TRUE - break - if(!active_admins) - SSticker.Reboot("Restart requested from the discord.", "discord") - return "Rebooting..." - else - return "There are active admins on the server! Ask them to restart." - -/datum/tgs_chat_command/join - name = "join" - help_text = "Sends a join link." - -/datum/tgs_chat_command/join/Run(datum/tgs_chat_user/sender, params) - return "<[world.internet_address]:[world.port]>" - -/datum/tgs_chat_command/tgsstatus - name = "status" - help_text = "Gets the admincount, playercount, gamemode, and true game mode of the server" - admin_only = TRUE - var/last_tgs_status = 0 - -/datum/tgs_chat_command/tgsstatus/Run(datum/tgs_chat_user/sender, params) - var/rtod = REALTIMEOFDAY - if(rtod - last_tgs_status < TGS_STATUS_THROTTLE) - return - last_tgs_status = rtod - var/list/adm = get_admin_counts() - var/list/allmins = adm["total"] - var/status = "Admins: [allmins.len] (Active: [english_list(adm["present"])] AFK: [english_list(adm["afk"])] Stealth: [english_list(adm["stealth"])] Skipped: [english_list(adm["noflags"])]). " - status += "Players: [GLOB.clients.len] (Active: [get_active_player_count(0,1,0)]). Mode: [SSticker.mode ? SSticker.mode.name : "Not started"]." - return status - -/datum/tgs_chat_command/tgscheck - name = "check" - help_text = "Gets the playercount, gamemode, and address of the server" - var/last_tgs_check = 0 - -/datum/tgs_chat_command/tgscheck/Run(datum/tgs_chat_user/sender, params) - var/rtod = REALTIMEOFDAY - if(rtod - last_tgs_check < TGS_STATUS_THROTTLE) - return - last_tgs_check = rtod - var/server = CONFIG_GET(string/server) - return "[GLOB.round_id ? "Round #[GLOB.round_id]: " : ""][GLOB.clients.len] players on [SSmapping.config.map_name], Mode: [GLOB.master_mode]; Round [SSticker.HasRoundStarted() ? (SSticker.IsRoundInProgress() ? "Active" : "Finishing") : "Starting"] -- [server ? server : "[world.internet_address]:[world.port]"]" - -/datum/tgs_chat_command/ahelp - name = "ahelp" - help_text = " |list>>" - admin_only = TRUE - -/datum/tgs_chat_command/ahelp/Run(datum/tgs_chat_user/sender, params) - var/list/all_params = splittext(params, " ") - if(all_params.len < 2) - return "Insufficient parameters" - var/target = all_params[1] - all_params.Cut(1, 2) - var/id = text2num(target) - if(id != null) - var/datum/admin_help/AH = GLOB.ahelp_tickets.TicketByID(id) - if(AH) - target = AH.initiator_ckey - else - return "Ticket #[id] not found!" - var/res = TgsPm(target, all_params.Join(" "), sender.friendly_name) - if(res != "Message Successful") - return res - -/datum/tgs_chat_command/namecheck - name = "namecheck" - help_text = "Returns info on the specified target" - admin_only = TRUE - -/datum/tgs_chat_command/namecheck/Run(datum/tgs_chat_user/sender, params) - params = trim(params) - if(!params) - return "Insufficient parameters" - log_admin("Chat Name Check: [sender.friendly_name] on [params]") - message_admins("Name checking [params] from [sender.friendly_name]") - return keywords_lookup(params, 1) - -/datum/tgs_chat_command/adminwho - name = "adminwho" - help_text = "Lists administrators currently on the server" - admin_only = TRUE - -/datum/tgs_chat_command/adminwho/Run(datum/tgs_chat_user/sender, params) - return tgsadminwho() - -GLOBAL_LIST(round_end_notifiees) - -/datum/tgs_chat_command/endnotify - name = "endnotify" - help_text = "Pings the invoker when the round ends" - admin_only = TRUE - -/datum/tgs_chat_command/endnotify/Run(datum/tgs_chat_user/sender, params) - if(!SSticker.IsRoundInProgress() && SSticker.HasRoundStarted()) - return "[sender.mention], the round has already ended!" - LAZYINITLIST(GLOB.round_end_notifiees) - GLOB.round_end_notifiees[sender.mention] = TRUE - return "I will notify [sender.mention] when the round ends." - -/datum/tgs_chat_command/sdql - name = "sdql" - help_text = "Runs an SDQL query" - admin_only = TRUE - -/datum/tgs_chat_command/sdql/Run(datum/tgs_chat_user/sender, params) - if(GLOB.AdminProcCaller) - return "Unable to run query, another admin proc call is in progress. Try again later." - GLOB.AdminProcCaller = "CHAT_[sender.friendly_name]" //_ won't show up in ckeys so it'll never match with a real admin - var/list/results = world.SDQL2_query(params, GLOB.AdminProcCaller, GLOB.AdminProcCaller) - GLOB.AdminProcCaller = null - if(!results) - return "Query produced no output" - var/list/text_res = results.Copy(1, 3) - var/list/refs = results.len > 3 ? results.Copy(4) : null - . = "[text_res.Join("\n")][refs ? "\nRefs: [refs.Join(" ")]" : ""]" - -/datum/tgs_chat_command/reload_admins - name = "reload_admins" - help_text = "Forces the server to reload admins." - admin_only = TRUE - -/datum/tgs_chat_command/reload_admins/Run(datum/tgs_chat_user/sender, params) - ReloadAsync() - log_admin("[sender.friendly_name] reloaded admins via chat command.") - return "Admins reloaded." - -/datum/tgs_chat_command/reload_admins/proc/ReloadAsync() - set waitfor = FALSE - load_admins() +#define TGS_STATUS_THROTTLE 5 + +/datum/tgs_chat_command/restart + name = "restart" + help_text = "Restarts the server if there are no active admins on." + +/datum/tgs_chat_command/restart/Run(datum/tgs_chat_user/sender, params) + var/active_admins = FALSE + for(var/client/C in GLOB.admins) + if(!C.is_afk() && check_rights_for(C, R_SERVER)) + active_admins = TRUE + break + if(!active_admins) + SSticker.Reboot("Restart requested from the discord.", "discord") + return "Rebooting..." + else + return "There are active admins on the server! Ask them to restart." + +/datum/tgs_chat_command/join + name = "join" + help_text = "Sends a join link." + +/datum/tgs_chat_command/join/Run(datum/tgs_chat_user/sender, params) + return "<[world.internet_address]:[world.port]>" + +/datum/tgs_chat_command/tgsstatus + name = "status" + help_text = "Gets the admincount, playercount, gamemode, and true game mode of the server" + admin_only = TRUE + var/last_tgs_status = 0 + +/datum/tgs_chat_command/tgsstatus/Run(datum/tgs_chat_user/sender, params) + var/rtod = REALTIMEOFDAY + if(rtod - last_tgs_status < TGS_STATUS_THROTTLE) + return + last_tgs_status = rtod + var/list/adm = get_admin_counts() + var/list/allmins = adm["total"] + var/status = "Admins: [allmins.len] (Active: [english_list(adm["present"])] AFK: [english_list(adm["afk"])] Stealth: [english_list(adm["stealth"])] Skipped: [english_list(adm["noflags"])]). " + status += "Players: [GLOB.clients.len] (Active: [get_active_player_count(0,1,0)]). Mode: [SSticker.mode ? SSticker.mode.name : "Not started"]." + return status + +/datum/tgs_chat_command/tgscheck + name = "check" + help_text = "Gets the playercount, gamemode, and address of the server" + var/last_tgs_check = 0 + +/datum/tgs_chat_command/tgscheck/Run(datum/tgs_chat_user/sender, params) + var/rtod = REALTIMEOFDAY + if(rtod - last_tgs_check < TGS_STATUS_THROTTLE) + return + last_tgs_check = rtod + var/server = CONFIG_GET(string/server) + return "[GLOB.round_id ? "Round #[GLOB.round_id]: " : ""][GLOB.clients.len] players on [SSmapping.config.map_name], Mode: [GLOB.master_mode]; Round [SSticker.HasRoundStarted() ? (SSticker.IsRoundInProgress() ? "Active" : "Finishing") : "Starting"] -- [server ? server : "[world.internet_address]:[world.port]"]" + +/datum/tgs_chat_command/ahelp + name = "ahelp" + help_text = " |list>>" + admin_only = TRUE + +/datum/tgs_chat_command/ahelp/Run(datum/tgs_chat_user/sender, params) + var/list/all_params = splittext(params, " ") + if(all_params.len < 2) + return "Insufficient parameters" + var/target = all_params[1] + all_params.Cut(1, 2) + var/id = text2num(target) + if(id != null) + var/datum/admin_help/AH = GLOB.ahelp_tickets.TicketByID(id) + if(AH) + target = AH.initiator_ckey + else + return "Ticket #[id] not found!" + var/res = TgsPm(target, all_params.Join(" "), sender.friendly_name) + if(res != "Message Successful") + return res + +/datum/tgs_chat_command/namecheck + name = "namecheck" + help_text = "Returns info on the specified target" + admin_only = TRUE + +/datum/tgs_chat_command/namecheck/Run(datum/tgs_chat_user/sender, params) + params = trim(params) + if(!params) + return "Insufficient parameters" + log_admin("Chat Name Check: [sender.friendly_name] on [params]") + message_admins("Name checking [params] from [sender.friendly_name]") + return keywords_lookup(params, 1) + +/datum/tgs_chat_command/adminwho + name = "adminwho" + help_text = "Lists administrators currently on the server" + admin_only = TRUE + +/datum/tgs_chat_command/adminwho/Run(datum/tgs_chat_user/sender, params) + return tgsadminwho() + +GLOBAL_LIST(round_end_notifiees) + +/datum/tgs_chat_command/endnotify + name = "endnotify" + help_text = "Pings the invoker when the round ends" + admin_only = TRUE + +/datum/tgs_chat_command/endnotify/Run(datum/tgs_chat_user/sender, params) + if(!SSticker.IsRoundInProgress() && SSticker.HasRoundStarted()) + return "[sender.mention], the round has already ended!" + LAZYINITLIST(GLOB.round_end_notifiees) + GLOB.round_end_notifiees[sender.mention] = TRUE + return "I will notify [sender.mention] when the round ends." + +/datum/tgs_chat_command/sdql + name = "sdql" + help_text = "Runs an SDQL query" + admin_only = TRUE + +/datum/tgs_chat_command/sdql/Run(datum/tgs_chat_user/sender, params) + if(GLOB.AdminProcCaller) + return "Unable to run query, another admin proc call is in progress. Try again later." + GLOB.AdminProcCaller = "CHAT_[sender.friendly_name]" //_ won't show up in ckeys so it'll never match with a real admin + var/list/results = world.SDQL2_query(params, GLOB.AdminProcCaller, GLOB.AdminProcCaller) + GLOB.AdminProcCaller = null + if(!results) + return "Query produced no output" + var/list/text_res = results.Copy(1, 3) + var/list/refs = results.len > 3 ? results.Copy(4) : null + . = "[text_res.Join("\n")][refs ? "\nRefs: [refs.Join(" ")]" : ""]" + +/datum/tgs_chat_command/reload_admins + name = "reload_admins" + help_text = "Forces the server to reload admins." + admin_only = TRUE + +/datum/tgs_chat_command/reload_admins/Run(datum/tgs_chat_user/sender, params) + ReloadAsync() + log_admin("[sender.friendly_name] reloaded admins via chat command.") + return "Admins reloaded." + +/datum/tgs_chat_command/reload_admins/proc/ReloadAsync() + set waitfor = FALSE + load_admins() diff --git a/code/modules/admin/create_mob.dm b/code/modules/admin/create_mob.dm index fb0157c64180..06a715d9ef30 100644 --- a/code/modules/admin/create_mob.dm +++ b/code/modules/admin/create_mob.dm @@ -1,45 +1,45 @@ - -/datum/admins/proc/create_mob(mob/user) - var/static/create_mob_html - if (!create_mob_html) - var/mobjs = null - mobjs = jointext(typesof(/mob), ";") - create_mob_html = file2text('html/create_object.html') - create_mob_html = replacetext(create_mob_html, "Create Object", "Create Mob") - create_mob_html = replacetext(create_mob_html, "null /* object types */", "\"[mobjs]\"") - - user << browse(create_panel_helper(create_mob_html), "window=create_mob;size=425x475") - -/proc/randomize_human(mob/living/carbon/human/H) - H.gender = pick(MALE, FEMALE) - H.real_name = random_unique_name(H.gender) - H.name = H.real_name - H.underwear = random_underwear(H.gender) - H.underwear_color = random_short_color() - H.skin_tone = random_skin_tone() - H.hairstyle = random_hairstyle(H.gender) - H.facial_hairstyle = random_facial_hairstyle(H.gender) - H.hair_color = random_short_color() - H.facial_hair_color = H.hair_color - H.eye_color = random_eye_color() - H.dna.blood_type = random_blood_type() - - // Mutant randomizing, doesn't affect the mob appearance unless it's the specific mutant. - H.dna.features["mcolor"] = random_short_color() - H.dna.features["ethcolor"] = GLOB.color_list_ethereal[pick(GLOB.color_list_ethereal)] - H.dna.features["tail_lizard"] = pick(GLOB.tails_list_lizard) - H.dna.features["snout"] = pick(GLOB.snouts_list) - H.dna.features["horns"] = pick(GLOB.horns_list) - H.dna.features["frills"] = pick(GLOB.frills_list) - H.dna.features["spines"] = pick(GLOB.spines_list) - H.dna.features["body_markings"] = pick(GLOB.body_markings_list) - H.dna.features["moth_wings"] = pick(GLOB.moth_wings_list) - H.dna.features["moth_fluff"] = pick(GLOB.moth_fluff_list) - H.dna.features["spider_legs"] = pick(GLOB.spider_legs_list) - H.dna.features["spider_spinneret"] = pick(GLOB.spider_spinneret_list) - H.dna.features["spider_mandibles"] = pick(GLOB.spider_mandibles_list) - H.dna.features["squid_face"] = pick(GLOB.squid_face_list) - - H.update_body() - H.update_hair() - H.update_body_parts() + +/datum/admins/proc/create_mob(mob/user) + var/static/create_mob_html + if (!create_mob_html) + var/mobjs = null + mobjs = jointext(typesof(/mob), ";") + create_mob_html = file2text('html/create_object.html') + create_mob_html = replacetext(create_mob_html, "Create Object", "Create Mob") + create_mob_html = replacetext(create_mob_html, "null /* object types */", "\"[mobjs]\"") + + user << browse(create_panel_helper(create_mob_html), "window=create_mob;size=425x475") + +/proc/randomize_human(mob/living/carbon/human/H) + H.gender = pick(MALE, FEMALE) + H.real_name = random_unique_name(H.gender) + H.name = H.real_name + H.underwear = random_underwear(H.gender) + H.underwear_color = random_short_color() + H.skin_tone = random_skin_tone() + H.hairstyle = random_hairstyle(H.gender) + H.facial_hairstyle = random_facial_hairstyle(H.gender) + H.hair_color = random_short_color() + H.facial_hair_color = H.hair_color + H.eye_color = random_eye_color() + H.dna.blood_type = random_blood_type() + + // Mutant randomizing, doesn't affect the mob appearance unless it's the specific mutant. + H.dna.features["mcolor"] = random_short_color() + H.dna.features["ethcolor"] = GLOB.color_list_ethereal[pick(GLOB.color_list_ethereal)] + H.dna.features["tail_lizard"] = pick(GLOB.tails_list_lizard) + H.dna.features["snout"] = pick(GLOB.snouts_list) + H.dna.features["horns"] = pick(GLOB.horns_list) + H.dna.features["frills"] = pick(GLOB.frills_list) + H.dna.features["spines"] = pick(GLOB.spines_list) + H.dna.features["body_markings"] = pick(GLOB.body_markings_list) + H.dna.features["moth_wings"] = pick(GLOB.moth_wings_list) + H.dna.features["moth_fluff"] = pick(GLOB.moth_fluff_list) + H.dna.features["spider_legs"] = pick(GLOB.spider_legs_list) + H.dna.features["spider_spinneret"] = pick(GLOB.spider_spinneret_list) + H.dna.features["spider_mandibles"] = pick(GLOB.spider_mandibles_list) + H.dna.features["squid_face"] = pick(GLOB.squid_face_list) + + H.update_body() + H.update_hair() + H.update_body_parts() diff --git a/code/modules/admin/create_object.dm b/code/modules/admin/create_object.dm index 83baf826638d..00f642c79460 100644 --- a/code/modules/admin/create_object.dm +++ b/code/modules/admin/create_object.dm @@ -1,32 +1,32 @@ -/datum/admins/proc/create_panel_helper(template) - var/final_html = replacetext(template, "/* ref src */", "[REF(src)];[HrefToken()]") - final_html = replacetext(final_html,"/* hreftokenfield */","[HrefTokenFormField()]") - return final_html - -/datum/admins/proc/create_object(mob/user) - var/static/create_object_html = null - if (!create_object_html) - var/objectjs = null - objectjs = jointext(typesof(/obj), ";") - create_object_html = file2text('html/create_object.html') - create_object_html = replacetext(create_object_html, "null /* object types */", "\"[objectjs]\"") - - user << browse(create_panel_helper(create_object_html), "window=create_object;size=425x475") - -/datum/admins/proc/quick_create_object(mob/user) - var/static/list/create_object_forms = list( - /obj, /obj/structure, /obj/machinery, /obj/effect, - /obj/item, /obj/item/clothing, /obj/item/stack, /obj/item, - /obj/item/reagent_containers, /obj/item/gun) - - var/path = input("Select the path of the object you wish to create.", "Path", /obj) in sortList(create_object_forms, /proc/cmp_typepaths_asc) - var/html_form = create_object_forms[path] - - if (!html_form) - var/objectjs = jointext(typesof(path), ";") - html_form = file2text('html/create_object.html') - html_form = replacetext(html_form, "Create Object", "Create [path]") - html_form = replacetext(html_form, "null /* object types */", "\"[objectjs]\"") - create_object_forms[path] = html_form - - user << browse(create_panel_helper(html_form), "window=qco[path];size=425x475") +/datum/admins/proc/create_panel_helper(template) + var/final_html = replacetext(template, "/* ref src */", "[REF(src)];[HrefToken()]") + final_html = replacetext(final_html,"/* hreftokenfield */","[HrefTokenFormField()]") + return final_html + +/datum/admins/proc/create_object(mob/user) + var/static/create_object_html = null + if (!create_object_html) + var/objectjs = null + objectjs = jointext(typesof(/obj), ";") + create_object_html = file2text('html/create_object.html') + create_object_html = replacetext(create_object_html, "null /* object types */", "\"[objectjs]\"") + + user << browse(create_panel_helper(create_object_html), "window=create_object;size=425x475") + +/datum/admins/proc/quick_create_object(mob/user) + var/static/list/create_object_forms = list( + /obj, /obj/structure, /obj/machinery, /obj/effect, + /obj/item, /obj/item/clothing, /obj/item/stack, /obj/item, + /obj/item/reagent_containers, /obj/item/gun) + + var/path = input("Select the path of the object you wish to create.", "Path", /obj) in sortList(create_object_forms, /proc/cmp_typepaths_asc) + var/html_form = create_object_forms[path] + + if (!html_form) + var/objectjs = jointext(typesof(path), ";") + html_form = file2text('html/create_object.html') + html_form = replacetext(html_form, "Create Object", "Create [path]") + html_form = replacetext(html_form, "null /* object types */", "\"[objectjs]\"") + create_object_forms[path] = html_form + + user << browse(create_panel_helper(html_form), "window=qco[path];size=425x475") diff --git a/code/modules/admin/create_turf.dm b/code/modules/admin/create_turf.dm index 4742cac4420e..86e83f38aee0 100644 --- a/code/modules/admin/create_turf.dm +++ b/code/modules/admin/create_turf.dm @@ -1,10 +1,10 @@ -/datum/admins/proc/create_turf(mob/user) - var/static/create_turf_html - if (!create_turf_html) - var/turfjs = null - turfjs = jointext(typesof(/turf), ";") - create_turf_html = file2text('html/create_object.html') - create_turf_html = replacetext(create_turf_html, "Create Object", "Create Turf") - create_turf_html = replacetext(create_turf_html, "null /* object types */", "\"[turfjs]\"") - - user << browse(create_panel_helper(create_turf_html), "window=create_turf;size=425x475") +/datum/admins/proc/create_turf(mob/user) + var/static/create_turf_html + if (!create_turf_html) + var/turfjs = null + turfjs = jointext(typesof(/turf), ";") + create_turf_html = file2text('html/create_object.html') + create_turf_html = replacetext(create_turf_html, "Create Object", "Create Turf") + create_turf_html = replacetext(create_turf_html, "null /* object types */", "\"[turfjs]\"") + + user << browse(create_panel_helper(create_turf_html), "window=create_turf;size=425x475") diff --git a/code/modules/admin/holder2.dm b/code/modules/admin/holder2.dm index 25eb784f82fa..0960d0d31309 100644 --- a/code/modules/admin/holder2.dm +++ b/code/modules/admin/holder2.dm @@ -1,209 +1,209 @@ -GLOBAL_LIST_EMPTY(admin_datums) -GLOBAL_PROTECT(admin_datums) -GLOBAL_LIST_EMPTY(protected_admins) -GLOBAL_PROTECT(protected_admins) - -GLOBAL_VAR_INIT(href_token, GenerateToken()) -GLOBAL_PROTECT(href_token) - -/datum/admins - var/datum/admin_rank/rank - - var/target - var/name = "nobody's admin datum (no rank)" //Makes for better runtimes - var/client/owner = null - var/fakekey = null - var/following = null - - var/datum/marked_datum - - var/spamcooldown = 0 - - var/admincaster_screen = 0 //TODO: remove all these 5 variables, they are completly unacceptable - var/datum/newscaster/feed_message/admincaster_feed_message = new /datum/newscaster/feed_message - var/datum/newscaster/wanted_message/admincaster_wanted_message = new /datum/newscaster/wanted_message - var/datum/newscaster/feed_channel/admincaster_feed_channel = new /datum/newscaster/feed_channel - var/admin_signature - - var/href_token - - var/deadmined - -/datum/admins/New(datum/admin_rank/R, ckey, force_active = FALSE, protected) - if(IsAdminAdvancedProcCall()) - var/msg = " has tried to elevate permissions!" - message_admins("[key_name_admin(usr)][msg]") - log_admin("[key_name(usr)][msg]") - if (!target) //only del if this is a true creation (and not just a New() proc call), other wise trialmins/coders could abuse this to deadmin other admins - QDEL_IN(src, 0) - CRASH("Admin proc call creation of admin datum") - return - if(!ckey) - QDEL_IN(src, 0) - CRASH("Admin datum created without a ckey") - if(!istype(R)) - QDEL_IN(src, 0) - CRASH("Admin datum created without a rank") - target = ckey - name = "[ckey]'s admin datum ([R])" - rank = R - admin_signature = "Nanotrasen Officer #[rand(0,9)][rand(0,9)][rand(0,9)]" - href_token = GenerateToken() - if(R.rights & R_DEBUG) //grant profile access - world.SetConfig("APP/admin", ckey, "role=admin") - //only admins with +ADMIN start admined - if(protected) - GLOB.protected_admins[target] = src - if (force_active || (R.rights & R_AUTOADMIN)) - activate() - else - deactivate() - -/datum/admins/Destroy() - if(IsAdminAdvancedProcCall()) - var/msg = " has tried to elevate permissions!" - message_admins("[key_name_admin(usr)][msg]") - log_admin("[key_name(usr)][msg]") - return QDEL_HINT_LETMELIVE - . = ..() - -/datum/admins/proc/activate() - if(IsAdminAdvancedProcCall()) - var/msg = " has tried to elevate permissions!" - message_admins("[key_name_admin(usr)][msg]") - log_admin("[key_name(usr)][msg]") - return - GLOB.deadmins -= target - GLOB.admin_datums[target] = src - deadmined = FALSE - if (GLOB.directory[target]) - associate(GLOB.directory[target]) //find the client for a ckey if they are connected and associate them with us - - -/datum/admins/proc/deactivate() - if(IsAdminAdvancedProcCall()) - var/msg = " has tried to elevate permissions!" - message_admins("[key_name_admin(usr)][msg]") - log_admin("[key_name(usr)][msg]") - return - GLOB.deadmins[target] = src - GLOB.admin_datums -= target - deadmined = TRUE - var/client/C - if ((C = owner) || (C = GLOB.directory[target])) - disassociate() - C.verbs += /client/proc/readmin - -/datum/admins/proc/associate(client/C) - if(IsAdminAdvancedProcCall()) - var/msg = " has tried to elevate permissions!" - message_admins("[key_name_admin(usr)][msg]") - log_admin("[key_name(usr)][msg]") - return - - if(istype(C)) - if(C.ckey != target) - var/msg = " has attempted to associate with [target]'s admin datum" - message_admins("[key_name_admin(C)][msg]") - log_admin("[key_name(C)][msg]") - return - if (deadmined) - activate() - owner = C - owner.holder = src - owner.add_admin_verbs() //TODO <--- todo what? the proc clearly exists and works since its the backbone to our entire admin system - owner.verbs -= /client/proc/readmin - GLOB.admins |= C - -/datum/admins/proc/disassociate() - if(IsAdminAdvancedProcCall()) - var/msg = " has tried to elevate permissions!" - message_admins("[key_name_admin(usr)][msg]") - log_admin("[key_name(usr)][msg]") - return - if(owner) - GLOB.admins -= owner - owner.remove_admin_verbs() - owner.holder = null - owner = null - -/datum/admins/proc/check_for_rights(rights_required) - if(rights_required && !(rights_required & rank.rights)) - return 0 - return 1 - - -/datum/admins/proc/check_if_greater_rights_than_holder(datum/admins/other) - if(!other) - return 1 //they have no rights - if(rank.rights == R_EVERYTHING) - return 1 //we have all the rights - if(src == other) - return 1 //you always have more rights than yourself - if(rank.rights != other.rank.rights) - if( (rank.rights & other.rank.rights) == other.rank.rights ) - return 1 //we have all the rights they have and more - return 0 - -/datum/admins/vv_edit_var(var_name, var_value) - return FALSE //nice try trialmin - -/* -checks if usr is an admin with at least ONE of the flags in rights_required. (Note, they don't need all the flags) -if rights_required == 0, then it simply checks if they are an admin. -if it doesn't return 1 and show_msg=1 it will prints a message explaining why the check has failed -generally it would be used like so: - -/proc/admin_proc() - if(!check_rights(R_ADMIN)) - return - to_chat(world, "you have enough rights!", confidential = TRUE) - -NOTE: it checks usr! not src! So if you're checking somebody's rank in a proc which they did not call -you will have to do something like if(client.rights & R_ADMIN) yourself. -*/ -/proc/check_rights(rights_required, show_msg=1) - if(usr && usr.client) - if (check_rights_for(usr.client, rights_required)) - return 1 - else - if(show_msg) - to_chat(usr, "Error: You do not have sufficient rights to do that. You require one of the following flags:[rights2text(rights_required," ")].", confidential = TRUE) - return 0 - -//probably a bit iffy - will hopefully figure out a better solution -/proc/check_if_greater_rights_than(client/other) - if(usr && usr.client) - if(usr.client.holder) - if(!other || !other.holder) - return 1 - return usr.client.holder.check_if_greater_rights_than_holder(other.holder) - return 0 - -//This proc checks whether subject has at least ONE of the rights specified in rights_required. -/proc/check_rights_for(client/subject, rights_required) - if(subject && subject.holder) - return subject.holder.check_for_rights(rights_required) - return 0 - -/proc/GenerateToken() - . = "" - for(var/I in 1 to 32) - . += "[rand(10)]" - -/proc/RawHrefToken(forceGlobal = FALSE) - var/tok = GLOB.href_token - if(!forceGlobal && usr) - var/client/C = usr.client - if(!C) - CRASH("No client for HrefToken()!") - var/datum/admins/holder = C.holder - if(holder) - tok = holder.href_token - return tok - -/proc/HrefToken(forceGlobal = FALSE) - return "admin_token=[RawHrefToken(forceGlobal)]" - -/proc/HrefTokenFormField(forceGlobal = FALSE) - return "" +GLOBAL_LIST_EMPTY(admin_datums) +GLOBAL_PROTECT(admin_datums) +GLOBAL_LIST_EMPTY(protected_admins) +GLOBAL_PROTECT(protected_admins) + +GLOBAL_VAR_INIT(href_token, GenerateToken()) +GLOBAL_PROTECT(href_token) + +/datum/admins + var/datum/admin_rank/rank + + var/target + var/name = "nobody's admin datum (no rank)" //Makes for better runtimes + var/client/owner = null + var/fakekey = null + var/following = null + + var/datum/marked_datum + + var/spamcooldown = 0 + + var/admincaster_screen = 0 //TODO: remove all these 5 variables, they are completly unacceptable + var/datum/newscaster/feed_message/admincaster_feed_message = new /datum/newscaster/feed_message + var/datum/newscaster/wanted_message/admincaster_wanted_message = new /datum/newscaster/wanted_message + var/datum/newscaster/feed_channel/admincaster_feed_channel = new /datum/newscaster/feed_channel + var/admin_signature + + var/href_token + + var/deadmined + +/datum/admins/New(datum/admin_rank/R, ckey, force_active = FALSE, protected) + if(IsAdminAdvancedProcCall()) + var/msg = " has tried to elevate permissions!" + message_admins("[key_name_admin(usr)][msg]") + log_admin("[key_name(usr)][msg]") + if (!target) //only del if this is a true creation (and not just a New() proc call), other wise trialmins/coders could abuse this to deadmin other admins + QDEL_IN(src, 0) + CRASH("Admin proc call creation of admin datum") + return + if(!ckey) + QDEL_IN(src, 0) + CRASH("Admin datum created without a ckey") + if(!istype(R)) + QDEL_IN(src, 0) + CRASH("Admin datum created without a rank") + target = ckey + name = "[ckey]'s admin datum ([R])" + rank = R + admin_signature = "Nanotrasen Officer #[rand(0,9)][rand(0,9)][rand(0,9)]" + href_token = GenerateToken() + if(R.rights & R_DEBUG) //grant profile access + world.SetConfig("APP/admin", ckey, "role=admin") + //only admins with +ADMIN start admined + if(protected) + GLOB.protected_admins[target] = src + if (force_active || (R.rights & R_AUTOADMIN)) + activate() + else + deactivate() + +/datum/admins/Destroy() + if(IsAdminAdvancedProcCall()) + var/msg = " has tried to elevate permissions!" + message_admins("[key_name_admin(usr)][msg]") + log_admin("[key_name(usr)][msg]") + return QDEL_HINT_LETMELIVE + . = ..() + +/datum/admins/proc/activate() + if(IsAdminAdvancedProcCall()) + var/msg = " has tried to elevate permissions!" + message_admins("[key_name_admin(usr)][msg]") + log_admin("[key_name(usr)][msg]") + return + GLOB.deadmins -= target + GLOB.admin_datums[target] = src + deadmined = FALSE + if (GLOB.directory[target]) + associate(GLOB.directory[target]) //find the client for a ckey if they are connected and associate them with us + + +/datum/admins/proc/deactivate() + if(IsAdminAdvancedProcCall()) + var/msg = " has tried to elevate permissions!" + message_admins("[key_name_admin(usr)][msg]") + log_admin("[key_name(usr)][msg]") + return + GLOB.deadmins[target] = src + GLOB.admin_datums -= target + deadmined = TRUE + var/client/C + if ((C = owner) || (C = GLOB.directory[target])) + disassociate() + C.verbs += /client/proc/readmin + +/datum/admins/proc/associate(client/C) + if(IsAdminAdvancedProcCall()) + var/msg = " has tried to elevate permissions!" + message_admins("[key_name_admin(usr)][msg]") + log_admin("[key_name(usr)][msg]") + return + + if(istype(C)) + if(C.ckey != target) + var/msg = " has attempted to associate with [target]'s admin datum" + message_admins("[key_name_admin(C)][msg]") + log_admin("[key_name(C)][msg]") + return + if (deadmined) + activate() + owner = C + owner.holder = src + owner.add_admin_verbs() //TODO <--- todo what? the proc clearly exists and works since its the backbone to our entire admin system + owner.verbs -= /client/proc/readmin + GLOB.admins |= C + +/datum/admins/proc/disassociate() + if(IsAdminAdvancedProcCall()) + var/msg = " has tried to elevate permissions!" + message_admins("[key_name_admin(usr)][msg]") + log_admin("[key_name(usr)][msg]") + return + if(owner) + GLOB.admins -= owner + owner.remove_admin_verbs() + owner.holder = null + owner = null + +/datum/admins/proc/check_for_rights(rights_required) + if(rights_required && !(rights_required & rank.rights)) + return 0 + return 1 + + +/datum/admins/proc/check_if_greater_rights_than_holder(datum/admins/other) + if(!other) + return 1 //they have no rights + if(rank.rights == R_EVERYTHING) + return 1 //we have all the rights + if(src == other) + return 1 //you always have more rights than yourself + if(rank.rights != other.rank.rights) + if( (rank.rights & other.rank.rights) == other.rank.rights ) + return 1 //we have all the rights they have and more + return 0 + +/datum/admins/vv_edit_var(var_name, var_value) + return FALSE //nice try trialmin + +/* +checks if usr is an admin with at least ONE of the flags in rights_required. (Note, they don't need all the flags) +if rights_required == 0, then it simply checks if they are an admin. +if it doesn't return 1 and show_msg=1 it will prints a message explaining why the check has failed +generally it would be used like so: + +/proc/admin_proc() + if(!check_rights(R_ADMIN)) + return + to_chat(world, "you have enough rights!", confidential = TRUE) + +NOTE: it checks usr! not src! So if you're checking somebody's rank in a proc which they did not call +you will have to do something like if(client.rights & R_ADMIN) yourself. +*/ +/proc/check_rights(rights_required, show_msg=1) + if(usr && usr.client) + if (check_rights_for(usr.client, rights_required)) + return 1 + else + if(show_msg) + to_chat(usr, "Error: You do not have sufficient rights to do that. You require one of the following flags:[rights2text(rights_required," ")].", confidential = TRUE) + return 0 + +//probably a bit iffy - will hopefully figure out a better solution +/proc/check_if_greater_rights_than(client/other) + if(usr && usr.client) + if(usr.client.holder) + if(!other || !other.holder) + return 1 + return usr.client.holder.check_if_greater_rights_than_holder(other.holder) + return 0 + +//This proc checks whether subject has at least ONE of the rights specified in rights_required. +/proc/check_rights_for(client/subject, rights_required) + if(subject && subject.holder) + return subject.holder.check_for_rights(rights_required) + return 0 + +/proc/GenerateToken() + . = "" + for(var/I in 1 to 32) + . += "[rand(10)]" + +/proc/RawHrefToken(forceGlobal = FALSE) + var/tok = GLOB.href_token + if(!forceGlobal && usr) + var/client/C = usr.client + if(!C) + CRASH("No client for HrefToken()!") + var/datum/admins/holder = C.holder + if(holder) + tok = holder.href_token + return tok + +/proc/HrefToken(forceGlobal = FALSE) + return "admin_token=[RawHrefToken(forceGlobal)]" + +/proc/HrefTokenFormField(forceGlobal = FALSE) + return "" diff --git a/code/modules/admin/ipintel.dm b/code/modules/admin/ipintel.dm index 6ce53ac7df47..7faa0edbfa2b 100644 --- a/code/modules/admin/ipintel.dm +++ b/code/modules/admin/ipintel.dm @@ -39,17 +39,17 @@ 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,7 +67,10 @@ 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/DBQuery/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) diff --git a/code/modules/admin/permissionedit.dm b/code/modules/admin/permissionedit.dm index 9d5066d5f174..5e6c25ed0386 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/DBQuery/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/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 + 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 @@ -165,7 +170,6 @@ return if(use_db == "Permanent") use_db = TRUE - admin_ckey = sanitizeSQL(admin_ckey) else use_db = FALSE if(QDELETED(usr)) @@ -212,9 +216,11 @@ 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/DBQuery/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 @@ -223,12 +229,18 @@ 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/DBQuery/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/DBQuery/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, '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/DBQuery/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/DBQuery/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, '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 @@ -303,10 +321,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/DBQuery/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 @@ -317,29 +337,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/DBQuery/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/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') + "}, 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/DBQuery/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(:admin_ip), 'add rank', :new_rank, '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/DBQuery/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/DBQuery/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, '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 @@ -368,11 +403,14 @@ 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) - var/rank_name = sanitizeSQL(D.rank.name) + 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` = '[rank_name]'") + var/datum/DBQuery/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 @@ -381,12 +419,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` = '[rank_name]'") + 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` = :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', '[rank_name]', '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/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/DBQuery/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 @@ -438,8 +483,10 @@ if(CONFIG_GET(flag/load_legacy_ranks_only)) 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/DBQuery/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 @@ -451,12 +498,18 @@ 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/DBQuery/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/DBQuery/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, '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 @@ -467,9 +520,11 @@ /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/DBQuery/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 diff --git a/code/modules/admin/player_panel.dm b/code/modules/admin/player_panel.dm index c6c80119a9ab..46b742eab90f 100644 --- a/code/modules/admin/player_panel.dm +++ b/code/modules/admin/player_panel.dm @@ -1,327 +1,328 @@ -/datum/admins/proc/player_panel_new()//The new one - if(!check_rights()) - return - log_admin("[key_name(usr)] checked the player panel.") - var/dat = "Player Panel" - - //javascript, the part that does most of the work~ - dat += {" - - - - - - - "} - - //body tag start + onload and onkeypress (onkeyup) javascript event calls - dat += "" - - //title + search bar - dat += {" - -
                        [J_title]: [J_opPos]/[job.total_positions < 0 ? " (unlimited)" : J_totPos]" + + dat += "" + if(job.total_positions >= 0) + dat += "Custom" + dat += "Add 1" + if(job.total_positions > job.current_positions) + dat += "Remove" + else + dat += "Remove" + dat += "Unlimit
                        - - - - - - -
                        - Player panel
                        - Hover over a line to see more information - Check antagonists - Kick everyone/AFKers in lobby -

                        -

                        - Search: -
                        - - "} - - //player table header - dat += {" - - "} - - var/list/mobs = sortmobs() - var/i = 1 - for(var/mob/M in mobs) - if(M.ckey) - - var/color = "#e6e6e6" - if(i%2 == 0) - color = "#f2f2f2" - var/is_antagonist = is_special_character(M) - - var/M_job = "" - - if(isliving(M)) - - if(iscarbon(M)) //Carbon stuff - if(ishuman(M)) - M_job = M.job - else if(ismonkey(M)) - M_job = "Monkey" - else if(isalien(M)) //aliens - if(islarva(M)) - M_job = "Alien larva" - else - M_job = ROLE_ALIEN - else - M_job = "Carbon-based" - - else if(issilicon(M)) //silicon - if(isAI(M)) - M_job = "AI" - else if(ispAI(M)) - M_job = ROLE_PAI - else if(iscyborg(M)) - M_job = "Cyborg" - else - M_job = "Silicon-based" - - else if(isanimal(M)) //simple animals - if(iscorgi(M)) - M_job = "Corgi" - else if(isslime(M)) - M_job = "slime" - else - M_job = "Animal" - - else - M_job = "Living" - - else if(isnewplayer(M)) - M_job = "New player" - - else if(isobserver(M)) - var/mob/dead/observer/O = M - if(O.started_as_observer)//Did they get BTFO or are they just not trying? - M_job = "Observer" - else - M_job = "Ghost" - - var/M_name = html_encode(M.name) - var/M_rname = html_encode(M.real_name) - var/M_key = html_encode(M.key) - var/previous_names = "" - if(M_key) - var/datum/player_details/P = GLOB.player_details[ckey(M_key)] - if(P) - previous_names = P.played_names.Join(",") - previous_names = html_encode(previous_names) - - //output for each mob - dat += {" - - - - - - "} - - i++ - - - //player table ending - dat += {" -
                        - - - [M_name] - [M_rname] - [M_key] ([M_job]) - - - - - - - - - - -
                        -
                        -
                        - - - - "} - - usr << browse(dat, "window=players;size=600x480") +/datum/admins/proc/player_panel_new()//The new one + if(!check_rights()) + return + log_admin("[key_name(usr)] checked the player panel.") + var/dat = "Player Panel" + + //javascript, the part that does most of the work~ + dat += {" + + + + + + + "} + + //body tag start + onload and onkeypress (onkeyup) javascript event calls + dat += "" + + //title + search bar + dat += {" + + + + + + + + +
                        + Player panel
                        + Hover over a line to see more information - Check antagonists - Kick everyone/AFKers in lobby +

                        +

                        + Search: +
                        + + "} + + //player table header + dat += {" + + "} + + var/list/mobs = sortmobs() + var/i = 1 + for(var/mob/M in mobs) + if(M.ckey) + + var/color = "#e6e6e6" + if(i%2 == 0) + color = "#f2f2f2" + var/is_antagonist = is_special_character(M) + + var/M_job = "" + + if(isliving(M)) + + if(iscarbon(M)) //Carbon stuff + if(ishuman(M)) + M_job = M.job + else if(ismonkey(M)) + M_job = "Monkey" + else if(isalien(M)) //aliens + if(islarva(M)) + M_job = "Alien larva" + else + M_job = ROLE_ALIEN + else + M_job = "Carbon-based" + + else if(issilicon(M)) //silicon + if(isAI(M)) + M_job = "AI" + else if(ispAI(M)) + M_job = ROLE_PAI + else if(iscyborg(M)) + M_job = "Cyborg" + else + M_job = "Silicon-based" + + else if(isanimal(M)) //simple animals + if(iscorgi(M)) + M_job = "Corgi" + else if(isslime(M)) + M_job = "slime" + else + M_job = "Animal" + + else + M_job = "Living" + + else if(isnewplayer(M)) + M_job = "New player" + + else if(isobserver(M)) + var/mob/dead/observer/O = M + if(O.started_as_observer)//Did they get BTFO or are they just not trying? + M_job = "Observer" + else + M_job = "Ghost" + + var/M_name = html_encode(M.name) + var/M_rname = html_encode(M.real_name) + var/M_key = html_encode(M.key) + var/previous_names = "" + if(M_key) + var/datum/player_details/P = GLOB.player_details[ckey(M_key)] + if(P) + previous_names = P.played_names.Join(",") + previous_names = html_encode(previous_names) + + //output for each mob + dat += {" + + + + + + "} + + i++ + + + //player table ending + dat += {" +
                        + + + [M_name] - [M_rname] - [M_key] ([M_job]) + + + + + + + + + + +
                        +
                        +
                        + + + + "} + + usr << browse(dat, "window=players;size=600x480") diff --git a/code/modules/admin/poll_management.dm b/code/modules/admin/poll_management.dm index f7062ad1722b..16dbaf0d356d 100644 --- a/code/modules/admin/poll_management.dm +++ b/code/modules/admin/poll_management.dm @@ -356,7 +356,10 @@ if(!SSdbcore.Connect()) to_chat(usr, "Failed to establish database connection.", confidential = TRUE) return - var/datum/DBQuery/query_delete_poll = SSdbcore.NewQuery("CALL set_poll_deleted('[sanitizeSQL(poll_id)]')") + var/datum/DBQuery/query_delete_poll = SSdbcore.NewQuery( + "CALL set_poll_deleted(:poll_id)", + list("poll_id" = poll_id) + ) if(!query_delete_poll.warn_execute()) qdel(query_delete_poll) return @@ -382,51 +385,46 @@ if(!SSdbcore.Connect()) to_chat(usr, "Failed to establish database connection.", confidential = TRUE) return - var/poll_id_sql = "[sanitizeSQL(poll_id)]" - var/new_poll = FALSE - if(!poll_id_sql) - poll_id_sql = "NULL" - new_poll = TRUE - var/poll_type_sql = sanitizeSQL(poll_type) - var/question_sql = sanitizeSQL(question) - var/subtitle_sql = sanitizeSQL(subtitle) - var/admin_only_sql = sanitizeSQL(admin_only) - var/options_allowed_sql = "[sanitizeSQL(options_allowed)]" + var/new_poll = !poll_id if(poll_type != POLLTYPE_MULTI) - options_allowed_sql = "NULL" - var/dont_show_sql = sanitizeSQL(dont_show) - var/allow_revoting_sql = sanitizeSQL(allow_revoting) - var/admin_ckey = sanitizeSQL(created_by) - var/admin_ip = sanitizeSQL(usr.client.address) + options_allowed = null + var/admin_ckey = created_by + var/admin_ip = usr.client.address + var/end_datetime_sql - if(interval) - end_datetime_sql = "NOW() + INTERVAL [sanitizeSQL(duration)] [sanitizeSQL(interval)]" - else - end_datetime_sql = "'[sanitizeSQL(duration)]'" - var/start_datetime_sql - if(!start_datetime) - start_datetime_sql = "NOW()" + if (interval in list("SECOND", "MINUTE", "HOUR", "DAY", "WEEK", "MONTH", "YEAR")) + end_datetime_sql = "NOW() + INTERVAL :duration [interval]" else - start_datetime_sql = "'[sanitizeSQL(start_datetime)]'" + end_datetime_sql = ":duration" + var/kn = key_name(usr) var/kna = key_name_admin(usr) - var/datum/DBQuery/query_save_poll = SSdbcore.NewQuery("INSERT INTO [format_table_name("poll_question")] (id, polltype, created_datetime, starttime, endtime, question, subtitle, adminonly, multiplechoiceoptions, createdby_ckey, createdby_ip, dontshow, allow_revoting) VALUES ([poll_id_sql], '[poll_type_sql]', NOW(), [start_datetime_sql], [end_datetime_sql], '[question_sql]', '[subtitle_sql]', '[admin_only_sql]', [options_allowed_sql], '[admin_ckey]', INET_ATON('[admin_ip]'), '[dont_show_sql]', '[allow_revoting_sql]') ON DUPLICATE KEY UPDATE starttime = [start_datetime_sql], endtime = [end_datetime_sql], question = '[question_sql]', subtitle = '[subtitle_sql]', adminonly = '[admin_only_sql]', multiplechoiceoptions = [options_allowed_sql], dontshow = '[dont_show_sql]', allow_revoting = '[allow_revoting_sql]'") + var/datum/DBQuery/query_save_poll = SSdbcore.NewQuery({" + INSERT INTO [format_table_name("poll_question")] (id, polltype, created_datetime, starttime, endtime, question, subtitle, adminonly, multiplechoiceoptions, createdby_ckey, createdby_ip, dontshow, allow_revoting) + VALUES (:poll_id, :poll_type, NOW(), COALESCE(:start_datetime, NOW()), [end_datetime_sql], :question, :subtitle, :admin_only, :options_allowed, :admin_ckey, INET_ATON(:admin_ip), :dont_show, :allow_revoting) + ON DUPLICATE KEY UPDATE starttime = :start_datetime, endtime = [end_datetime_sql], question = :question, subtitle = :subtitle, adminonly = :admin_only, multiplechoiceoptions = :options_allowed, dontshow = :dont_show, allow_revoting = :allow_revoting + "}, list( + "poll_id" = poll_id, "poll_type" = poll_type, "start_datetime" = start_datetime, "duration" = duration, + "question" = question, "subtitle" = subtitle, "admin_only" = admin_only, "options_allowed" = options_allowed, + "admin_ckey" = admin_ckey, "admin_ip" = admin_ip, "dont_show" = dont_show, "allow_revoting" = allow_revoting + )) if(!query_save_poll.warn_execute()) qdel(query_save_poll) return + if (!poll_id) + poll_id = query_save_poll.last_insert_id qdel(query_save_poll) - if(poll_id_sql == "NULL") - poll_id_sql = "LAST_INSERT_ID()" - var/datum/DBQuery/query_get_poll_id_start_endtime = SSdbcore.NewQuery("SELECT LAST_INSERT_ID(), starttime, endtime, IF(starttime > NOW(), 1, 0) FROM [format_table_name("poll_question")] WHERE id = [poll_id_sql]") + var/datum/DBQuery/query_get_poll_id_start_endtime = SSdbcore.NewQuery( + "SELECT starttime, endtime, IF(starttime > NOW(), 1, 0) FROM [format_table_name("poll_question")] WHERE id = :poll_id", + list("poll_id" = poll_id) + ) if(!query_get_poll_id_start_endtime.warn_execute()) qdel(query_get_poll_id_start_endtime) return if(query_get_poll_id_start_endtime.NextRow()) - if(!poll_id) - poll_id = text2num(query_get_poll_id_start_endtime.item[1]) - start_datetime = query_get_poll_id_start_endtime.item[2] - end_datetime = query_get_poll_id_start_endtime.item[3] - future_poll = text2num(query_get_poll_id_start_endtime.item[4]) + start_datetime = query_get_poll_id_start_endtime.item[1] + end_datetime = query_get_poll_id_start_endtime.item[2] + future_poll = text2num(query_get_poll_id_start_endtime.item[3]) qdel(query_get_poll_id_start_endtime) if(clear_votes) clear_poll_votes() @@ -453,13 +451,6 @@ for(var/o in options) var/datum/poll_option/option = o option.save_option() - var/datum/DBQuery/query_get_option_id = SSdbcore.NewQuery("SELECT LAST_INSERT_ID()") - if(!query_get_option_id.warn_execute()) - qdel(query_get_option_id) - return - if(query_get_option_id.NextRow()) - option.option_id = text2num(query_get_option_id.item[1]) - qdel(query_get_option_id) /** * Deletes all votes or text replies for this poll, depending on its type. @@ -474,7 +465,10 @@ var/table = "poll_vote" if(poll_type == POLLTYPE_TEXT) table = "poll_textreply" - var/datum/DBQuery/query_clear_poll_votes = SSdbcore.NewQuery("UPDATE [format_table_name("[table]")] SET deleted = 1 WHERE pollid = [sanitizeSQL(poll_id)]") + var/datum/DBQuery/query_clear_poll_votes = SSdbcore.NewQuery( + "UPDATE [format_table_name(table)] SET deleted = 1 WHERE pollid = :poll_id", + list("poll_id" = poll_id) + ) if(!query_clear_poll_votes.warn_execute()) qdel(query_clear_poll_votes) return @@ -649,38 +643,28 @@ if(!SSdbcore.Connect()) to_chat(usr, "Failed to establish database connection.", confidential = TRUE) return - var/list/columns = list("text", "default_percentage_calc", "pollid", "id") - var/list/values = list("'[sanitizeSQL(text)]'", "[sanitizeSQL(default_percentage_calc)]", "[sanitizeSQL(parent_poll.poll_id)]") - if(option_id) - values += "[sanitizeSQL(option_id)]" - else - values += "NULL" + + var/list/values = list("text" = text, "default_percentage_calc" = default_percentage_calc, "pollid" = parent_poll.poll_id, "id" = option_id) if(parent_poll.poll_type == POLLTYPE_RATING) - columns.Add("minval", "maxval", "descmin", "descmid", "descmax") - values.Add("[sanitizeSQL(min_val)]", "[sanitizeSQL(max_val)]") - if(desc_min) - values += "'[sanitizeSQL(desc_min)]'" - else - values += "NULL" - if(desc_mid) - values += "'[sanitizeSQL(desc_mid)]'" - else - values += "NULL" - if(desc_max) - values += "'[sanitizeSQL(desc_max)]'" - else - values += "NULL" - var/list/update_data = list() - var/count = 0 - for(var/i in columns) - count++ - if(i == "pollid" || i == "id") //we don't want to update the pollid or option id so skip including those - continue - update_data += "[i] = [values[count]]" - var/datum/DBQuery/query_update_poll_option = SSdbcore.NewQuery("INSERT INTO [format_table_name("poll_option")] ([jointext(columns, ",")]) VALUES ([jointext(values, ",")]) ON DUPLICATE KEY UPDATE [jointext(update_data, ", ")]") + values["minval"] = min_val + values["maxval"] = max_val + values["descmin"] = desc_min + values["descmid"] = desc_mid + values["descmax"] = desc_max + + var/update_data = list() + for (var/k in values) + update_data += "[k] = VALUES([k])" + + var/datum/DBQuery/query_update_poll_option = SSdbcore.NewQuery( + "INSERT INTO [format_table_name("poll_option")] ([jointext(values, ",")]) VALUES (:[jointext(values, ",:")]) ON DUPLICATE KEY UPDATE [jointext(update_data, ", ")]", + values + ) if(!query_update_poll_option.warn_execute()) qdel(query_update_poll_option) return + if (!option_id) + option_id = query_update_poll_option.last_insert_id qdel(query_update_poll_option) /** @@ -695,7 +679,10 @@ if(!SSdbcore.Connect()) to_chat(usr, "Failed to establish database connection.", confidential = TRUE) return - var/datum/DBQuery/query_delete_poll_option = SSdbcore.NewQuery("UPDATE [format_table_name("poll_option")] AS o INNER JOIN [format_table_name("poll_vote")] AS v ON o.id = v.optionid SET o.deleted = 1, v.deleted = 1 WHERE o.id = [sanitizeSQL(option_id)]") + var/datum/DBQuery/query_delete_poll_option = SSdbcore.NewQuery( + "UPDATE [format_table_name("poll_option")] AS o INNER JOIN [format_table_name("poll_vote")] AS v ON o.id = v.optionid SET o.deleted = 1, v.deleted = 1 WHERE o.id = :option_id", + list("option_id" = option_id) + ) if(!query_delete_poll_option.warn_execute()) qdel(query_delete_poll_option) return diff --git a/code/modules/admin/skill_panel.dm b/code/modules/admin/skill_panel.dm new file mode 100644 index 000000000000..5cffd773e9ab --- /dev/null +++ b/code/modules/admin/skill_panel.dm @@ -0,0 +1,57 @@ +/datum/skill_panel + var/datum/mind/targetmind + var/client/holder //client of whoever is using this datum + +/datum/skill_panel/New(user, datum/mind/mind)//H can either be a client or a mob due to byondcode(tm) + targetmind = mind + if (istype(user,/client)) + var/client/userClient = user + holder = userClient //if its a client, assign it to holder + else + var/mob/userMob = user + holder = userMob.client //if its a mob, assign the mob's client to holder + +/datum/skill_panel/ui_state(mob/user) + return GLOB.admin_state + +/datum/skill_panel/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "SkillPanel") + ui.open() + +/datum/skill_panel/ui_data(mob/user) //Sends info about the skills to UI + . = list() + for (var/type in GLOB.skill_types) + var/datum/skill/S = GetSkillRef(type) + var/lvl_num = targetmind.get_skill_level(type) + var/lvl_name = uppertext(targetmind.get_skill_level_name(type)) + var/exp = targetmind.get_skill_exp(type) + var/xp_prog_to_level = targetmind.exp_needed_to_level_up(type) + var/xp_req_to_level = 0 + if (xp_prog_to_level)//is it even possible to level up? + xp_req_to_level = SKILL_EXP_LIST[lvl_num+1] - SKILL_EXP_LIST[lvl_num] + var/exp_percent = exp / SKILL_EXP_LIST[SKILL_LEVEL_LEGENDARY] + .["skills"] += list(list("playername" = targetmind.current, "path" = type, "name" = S.name, "desc" = S.desc, "lvlnum" = lvl_num, "lvl" = lvl_name, "exp" = exp, "exp_prog" = xp_req_to_level - xp_prog_to_level, "exp_req" = xp_req_to_level, "exp_percent" = exp_percent, "max_exp" = SKILL_EXP_LIST[length(SKILL_EXP_LIST)])) + +/datum/skill_panel/ui_act(action, params) + . = ..() + if(.) + return + switch (action) + if ("adj_exp") + var/skill = text2path(params["skill"]) + var/number = input("Please insert the amount of experience you'd like to add/subtract:") as num|null + if (number) + targetmind.adjust_experience(skill, number) + if ("set_exp") + var/skill = text2path(params["skill"]) + var/number = input("Please insert the number you want to set the player's exp to:") as num|null + if (number) + targetmind.set_experience(skill, number) + if ("set_lvl") + var/skill = text2path(params["skill"]) + var/max_skill = length(SKILL_EXP_LIST) + var/number = input("Please insert a whole number between 1 (NONE) and [max_skill] (LEGENDARY) corresponding to the level you'd like to set the player to.") as num|null + if (number > 0 && number <= max_skill ) + targetmind.set_level(skill, number) diff --git a/code/modules/admin/sql_ban_system.dm b/code/modules/admin/sql_ban_system.dm index 800a4cbcc9c5..5257e07d6f33 100644 --- a/code/modules/admin/sql_ban_system.dm +++ b/code/modules/admin/sql_ban_system.dm @@ -3,7 +3,7 @@ //checks client ban cache or DB ban table if ckey is banned from one or more roles //doesn't return any details, use only for if statements -/proc/is_banned_from(player_ckey, roles) +/proc/is_banned_from(player_ckey, list/roles) if(!player_ckey) return var/client/C = GLOB.directory[player_ckey] @@ -17,17 +17,30 @@ else if(roles in C.ban_cache) return TRUE else - player_ckey = sanitizeSQL(player_ckey) - var/admin_where - if(GLOB.admin_datums[player_ckey] || GLOB.deadmins[player_ckey]) - admin_where = " AND applies_to_admins = 1" + var/values = list( + "player_ckey" = player_ckey, + "must_apply_to_admins" = !!(GLOB.admin_datums[player_ckey] || GLOB.deadmins[player_ckey]), + ) var/sql_roles if(islist(roles)) - sql_roles = jointext(roles, "', '") + var/list/sql_roles_list = list() + for (var/i in 1 to roles.len) + values["role[i]"] = roles[i] + sql_roles_list += ":role[i]" + sql_roles = sql_roles_list.Join(", ") else - sql_roles = roles - sql_roles = sanitizeSQL(sql_roles) - var/datum/DBQuery/query_check_ban = SSdbcore.NewQuery("SELECT 1 FROM [format_table_name("ban")] WHERE ckey = '[player_ckey]' AND role IN ('[sql_roles]') AND unbanned_datetime IS NULL AND (expiration_time IS NULL OR expiration_time > NOW())[admin_where]") + values["role"] = roles + sql_roles = ":role" + var/datum/DBQuery/query_check_ban = SSdbcore.NewQuery({" + SELECT 1 + FROM [format_table_name("ban")] + WHERE + ckey = :player_ckey AND + role IN ([sql_roles]) AND + unbanned_datetime IS NULL AND + (expiration_time IS NULL OR expiration_time > NOW()) + AND (NOT :must_apply_to_admins OR applies_to_admins = 1) + "}, values) if(!query_check_ban.warn_execute()) qdel(query_check_ban) return @@ -41,19 +54,26 @@ /proc/is_banned_from_with_details(player_ckey, player_ip, player_cid, role) if(!player_ckey && !player_ip && !player_cid) return - role = sanitizeSQL(role) - var/list/where_list = list() - if(player_ckey) - player_ckey = sanitizeSQL(player_ckey) - where_list += "ckey = '[player_ckey]'" - if(player_ip) - player_ip = sanitizeSQL(player_ip) - where_list += "ip = INET_ATON('[player_ip]')" - if(player_cid) - player_cid = sanitizeSQL(player_cid) - where_list += "computerid = '[player_cid]'" - var/where = "([where_list.Join(" OR ")])" - var/datum/DBQuery/query_check_ban = SSdbcore.NewQuery("SELECT id, bantime, round_id, expiration_time, TIMESTAMPDIFF(MINUTE, bantime, expiration_time), applies_to_admins, reason, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].ckey), ckey), INET_NTOA(ip), computerid, 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 role = '[role]' AND [where] AND unbanned_datetime IS NULL AND (expiration_time IS NULL OR expiration_time > NOW()) ORDER BY bantime DESC") + var/datum/DBQuery/query_check_ban = SSdbcore.NewQuery({" + SELECT + id, + bantime, + round_id, + expiration_time, + TIMESTAMPDIFF(MINUTE, bantime, expiration_time), + applies_to_admins, + reason, + IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].ckey), ckey), + INET_NTOA(ip), + computerid, + 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 role = :role + AND (ckey = :ckey OR ip = INET_ATON(:ip) OR computerid = :computerid) + AND unbanned_datetime IS NULL + AND (expiration_time IS NULL OR expiration_time > NOW()) + ORDER BY bantime DESC + "}, list("role" = role, "ckey" = player_ckey, "ip" = player_ip, "computerid" = player_cid)) if(!query_check_ban.warn_execute()) qdel(query_check_ban) return @@ -67,11 +87,13 @@ return if(C && istype(C)) C.ban_cache = list() - var/player_key = sanitizeSQL(C.ckey) var/is_admin = FALSE if(GLOB.admin_datums[C.ckey] || GLOB.deadmins[C.ckey]) is_admin = TRUE - var/datum/DBQuery/query_build_ban_cache = SSdbcore.NewQuery("SELECT role, applies_to_admins FROM [format_table_name("ban")] WHERE ckey = '[player_key]' AND unbanned_datetime IS NULL AND (expiration_time IS NULL OR expiration_time > NOW())") + var/datum/DBQuery/query_build_ban_cache = SSdbcore.NewQuery( + "SELECT role, applies_to_admins FROM [format_table_name("ban")] WHERE ckey = :ckey AND unbanned_datetime IS NULL AND (expiration_time IS NULL OR expiration_time > NOW())", + list("ckey" = C.ckey) + ) if(!query_build_ban_cache.warn_execute()) qdel(query_build_ban_cache) return @@ -194,8 +216,15 @@ //there's not always a client to use the bancache of so to avoid many individual queries from using is_banned_form we'll build a cache to use here var/banned_from = list() if(player_key) - var/player_ckey = sanitizeSQL(ckey(player_key)) - var/datum/DBQuery/query_get_banned_roles = SSdbcore.NewQuery("SELECT role FROM [format_table_name("ban")] WHERE ckey = '[player_ckey]' AND role <> 'server' AND unbanned_datetime IS NULL AND (expiration_time IS NULL OR expiration_time > NOW())") + var/datum/DBQuery/query_get_banned_roles = SSdbcore.NewQuery({" + SELECT role + FROM [format_table_name("ban")] + WHERE + ckey = :player_ckey AND + role <> 'server' + AND unbanned_datetime IS NULL + AND (expiration_time IS NULL OR expiration_time > NOW()) + "}, list("player_ckey" = ckey(player_key))) if(!query_get_banned_roles.warn_execute()) qdel(query_get_banned_roles) return @@ -402,11 +431,11 @@ if(!SSdbcore.Connect()) to_chat(usr, "Failed to establish database connection.", confidential = TRUE) return - var/player_ckey = sanitizeSQL(ckey(player_key)) - player_ip = sanitizeSQL(player_ip) - player_cid = sanitizeSQL(player_cid) + var/player_ckey = ckey(player_key) if(player_ckey) - var/datum/DBQuery/query_create_ban_get_player = SSdbcore.NewQuery("SELECT byond_key, INET_NTOA(ip), computerid FROM [format_table_name("player")] WHERE ckey = '[player_ckey]'") + var/datum/DBQuery/query_create_ban_get_player = SSdbcore.NewQuery({" + SELECT byond_key, INET_NTOA(ip), computerid FROM [format_table_name("player")] WHERE ckey = :player_ckey + "}, list("player_ckey" = player_ckey)) if(!query_create_ban_get_player.warn_execute()) qdel(query_create_ban_get_player) return @@ -427,9 +456,17 @@ qdel(query_create_ban_get_player) return qdel(query_create_ban_get_player) - var/admin_ckey = sanitizeSQL(usr.client.ckey) + var/admin_ckey = usr.client.ckey if(applies_to_admins) - var/datum/DBQuery/query_check_adminban_count = SSdbcore.NewQuery("SELECT COUNT(DISTINCT bantime) FROM [format_table_name("ban")] WHERE a_ckey = '[admin_ckey]' AND applies_to_admins = 1 AND unbanned_datetime IS NULL AND (expiration_time IS NULL OR expiration_time > NOW())") + var/datum/DBQuery/query_check_adminban_count = SSdbcore.NewQuery({" + SELECT COUNT(DISTINCT bantime) + FROM [format_table_name("ban")] + WHERE + a_ckey = :admin_ckey AND + applies_to_admins = 1 AND + unbanned_datetime IS NULL AND + (expiration_time IS NULL OR expiration_time > NOW()) + "}, list("admin_ckey" = admin_ckey)) if(!query_check_adminban_count.warn_execute()) //count distinct bantime to treat rolebans made at the same time as one ban qdel(query_check_adminban_count) return @@ -443,18 +480,15 @@ qdel(query_check_adminban_count) return qdel(query_check_adminban_count) - var/admin_ip = sanitizeSQL(usr.client.address) - var/admin_cid = sanitizeSQL(usr.client.computer_id) + var/admin_ip = usr.client.address + var/admin_cid = usr.client.computer_id duration = text2num(duration) - if(interval) - interval = sanitizeSQL(interval) - else + if (!(interval in list("SECOND", "MINUTE", "HOUR", "DAY", "WEEK", "MONTH", "YEAR"))) interval = "MINUTE" var/time_message = "[duration] [lowertext(interval)]" //no DisplayTimeText because our duration is of variable interval type if(duration > 1) //pluralize the interval if necessary time_message += "s" var/note_reason = "Banned from [roles_to_ban[1] == "Server" ? "the server" : " Roles: [roles_to_ban.Join(", ")]"] [isnull(duration) ? "permanently" : "for [time_message]"] - [reason]" - reason = sanitizeSQL(reason) var/list/clients_online = GLOB.clients.Copy() var/list/admins_online = list() for(var/client/C in clients_online) @@ -464,26 +498,34 @@ var/adminwho = admins_online.Join(", ") var/kn = key_name(usr) var/kna = key_name_admin(usr) - var/sql_ban + + var/special_columns = list( + "bantime" = "NOW()", + "server_ip" = "INET_ATON(?)", + "ip" = "INET_ATON(?)", + "a_ip" = "INET_ATON(?)", + "expiration_time" = "IF(? IS NULL, NULL, NOW() + INTERVAL ? [interval])" + ) + var/sql_ban = list() for(var/role in roles_to_ban) - sql_ban += list(list("bantime" = "NOW()", - "server_ip" = "INET_ATON(IF('[world.internet_address]' LIKE '', '0', '[world.internet_address]'))", - "server_port" = sanitizeSQL(world.port), - "round_id" = sanitizeSQL(GLOB.round_id), - "role" = "'[sanitizeSQL(role)]'", - "expiration_time" = "IF('[duration]' LIKE '', NULL, NOW() + INTERVAL [duration ? "[duration]" : "0"] [interval])", - "applies_to_admins" = sanitizeSQL(applies_to_admins), - "reason" = "'[reason]'", - "ckey" = "IF('[player_ckey]' LIKE '', NULL, '[player_ckey]')", - "ip" = "INET_ATON(IF('[player_ip]' LIKE '', NULL, '[player_ip]'))", - "computerid" = "IF('[player_cid]' LIKE '', NULL, '[player_cid]')", - "a_ckey" = "'[admin_ckey]'", - "a_ip" = "INET_ATON(IF('[admin_ip]' LIKE '', NULL, '[admin_ip]'))", - "a_computerid" = "'[admin_cid]'", - "who" = "'[who]'", - "adminwho" = "'[adminwho]'" + sql_ban += list(list( + "server_ip" = world.internet_address || 0, + "server_port" = world.port, + "round_id" = GLOB.round_id, + "role" = role, + "expiration_time" = duration, + "applies_to_admins" = applies_to_admins, + "reason" = reason, + "ckey" = player_ckey || null, + "ip" = player_ip || null, + "computerid" = player_cid || null, + "a_ckey" = admin_ckey, + "a_ip" = admin_ip || null, + "a_computerid" = admin_cid, + "who" = who, + "adminwho" = adminwho, )) - if(!SSdbcore.MassInsert(format_table_name("ban"), sql_ban, warn = 1)) + if(!SSdbcore.MassInsert(format_table_name("ban"), sql_ban, warn = TRUE, special_columns = special_columns)) return var/target = ban_target_string(player_key, player_ip, player_cid) var/msg = "has created a [isnull(duration) ? "permanent" : "temporary [time_message]"] [applies_to_admins ? "admin " : ""][roles_to_ban[1] == "Server" ? "server ban" : "role ban from [roles_to_ban.len] roles"] for [target]." @@ -538,20 +580,23 @@
                        "} if(player_key || admin_key || player_ip || player_cid) - var/list/searchlist = list() - if(player_key) - searchlist += "ckey = '[sanitizeSQL(ckey(player_key))]'" - if(admin_key) - searchlist += "a_ckey = '[sanitizeSQL(ckey(admin_key))]'" - if(player_ip) - searchlist += "ip = INET_ATON('[sanitizeSQL(player_ip)]')" - if(player_cid) - searchlist += "computerid = '[sanitizeSQL(player_cid)]'" - var/search = searchlist.Join(" AND ") var/bancount = 0 var/bansperpage = 10 page = text2num(page) - var/datum/DBQuery/query_unban_count_bans = SSdbcore.NewQuery("SELECT COUNT(id) FROM [format_table_name("ban")] WHERE [search]") + var/datum/DBQuery/query_unban_count_bans = SSdbcore.NewQuery({" + SELECT COUNT(id) + FROM [format_table_name("ban")] + WHERE + (:player_key IS NULL OR ckey = :player_key) AND + (:admin_key IS NULL OR a_ckey = :admin_key) AND + (:player_ip IS NULL OR ip = INET_ATON(:player_ip)) AND + (:player_cid IS NULL OR computerid = :player_cid) + "}, list( + "player_key" = ckey(player_key), + "admin_key" = ckey(admin_key), + "player_ip" = player_ip, + "player_cid" = player_cid, + )) if(!query_unban_count_bans.warn_execute()) qdel(query_unban_count_bans) return @@ -567,8 +612,53 @@ bancount -= bansperpage pagecount++ output += pagelist.Join(" | ") - var/limit = " LIMIT [bansperpage * page], [bansperpage]" - var/datum/DBQuery/query_unban_search_bans = SSdbcore.NewQuery({"SELECT id, bantime, round_id, role, expiration_time, TIMESTAMPDIFF(MINUTE, bantime, expiration_time), IF(expiration_time < NOW(), 1, NULL), applies_to_admins, reason, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].ckey), ckey), INET_NTOA(ip), computerid, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].a_ckey), a_ckey), IF(edits IS NOT NULL, 1, NULL), unbanned_datetime, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].unbanned_ckey), unbanned_ckey), unbanned_round_id FROM [format_table_name("ban")] WHERE [search] ORDER BY id DESC[limit]"}) + var/datum/DBQuery/query_unban_search_bans = SSdbcore.NewQuery({" + SELECT + id, + bantime, + round_id, + role, + expiration_time, + TIMESTAMPDIFF(MINUTE, bantime, expiration_time), + IF(expiration_time < NOW(), 1, NULL), + applies_to_admins, + reason, + IFNULL(( + SELECT byond_key + FROM [format_table_name("player")] + WHERE [format_table_name("player")].ckey = [format_table_name("ban")].ckey + ), ckey), + INET_NTOA(ip), + computerid, + IFNULL(( + SELECT byond_key + FROM [format_table_name("player")] + WHERE [format_table_name("player")].ckey = [format_table_name("ban")].a_ckey + ), a_ckey), + IF(edits IS NOT NULL, 1, NULL), + unbanned_datetime, + IFNULL(( + SELECT byond_key + FROM [format_table_name("player")] + WHERE [format_table_name("player")].ckey = [format_table_name("ban")].unbanned_ckey + ), unbanned_ckey), + unbanned_round_id + FROM [format_table_name("ban")] + WHERE + (:player_key IS NULL OR ckey = :player_key) AND + (:admin_key IS NULL OR a_ckey = :admin_key) AND + (:player_ip IS NULL OR ip = INET_ATON(:player_ip)) AND + (:player_cid IS NULL OR computerid = :player_cid) + ORDER BY id DESC + LIMIT :skip, :take + "}, list( + "player_key" = ckey(player_key), + "admin_key" = ckey(admin_key), + "player_ip" = player_ip, + "player_cid" = player_cid, + "skip" = bansperpage * page, + "take" = bansperpage, + )) if(!query_unban_search_bans.warn_execute()) qdel(query_unban_search_bans) return @@ -621,13 +711,17 @@ var/target = ban_target_string(player_key, player_ip, player_cid) if(alert(usr, "Please confirm unban of [target] from [role].", "Unban confirmation", "Yes", "No") == "No") return - ban_id = sanitizeSQL(ban_id) - var/admin_ckey = sanitizeSQL(usr.client.ckey) - var/admin_ip = sanitizeSQL(usr.client.address) - var/admin_cid = sanitizeSQL(usr.client.computer_id) var/kn = key_name(usr) var/kna = key_name_admin(usr) - var/datum/DBQuery/query_unban = SSdbcore.NewQuery("UPDATE [format_table_name("ban")] SET unbanned_datetime = NOW(), unbanned_ckey = '[admin_ckey]', unbanned_ip = INET_ATON('[admin_ip]'), unbanned_computerid = '[admin_cid]', unbanned_round_id = '[GLOB.round_id]' WHERE id = [ban_id]") + var/datum/DBQuery/query_unban = SSdbcore.NewQuery({" + UPDATE [format_table_name("ban")] SET + unbanned_datetime = NOW(), + unbanned_ckey = :admin_ckey, + unbanned_ip = INET_ATON(:admin_ip), + unbanned_computerid = :admin_cid, + unbanned_round_id = :round_id + WHERE id = :ban_id + "}, list("ban_id" = ban_id, "admin_ckey" = usr.client.ckey, "admin_ip" = usr.client.address, "admin_cid" = usr.client.computer_id, "round_id" = GLOB.round_id)) if(!query_unban.warn_execute()) qdel(query_unban) return @@ -650,13 +744,18 @@ if(!SSdbcore.Connect()) to_chat(usr, "Failed to establish database connection.", confidential = TRUE) return - ban_id = sanitizeSQL(ban_id) - var/player_ckey = sanitizeSQL(ckey(player_key)) - player_ip = sanitizeSQL(player_ip) - player_cid = sanitizeSQL(player_cid) + var/player_ckey = ckey(player_key) var/bantime if(player_ckey) - var/datum/DBQuery/query_edit_ban_get_player = SSdbcore.NewQuery("SELECT byond_key, (SELECT bantime FROM [format_table_name("ban")] WHERE id = [ban_id]), ip, computerid FROM [format_table_name("player")] WHERE ckey = '[player_ckey]'") + var/datum/DBQuery/query_edit_ban_get_player = SSdbcore.NewQuery({" + SELECT + byond_key, + (SELECT bantime FROM [format_table_name("ban")] WHERE id = :ban_id), + ip, + computerid + FROM [format_table_name("player")] + WHERE ckey = :player_ckey + "}, list("player_ckey" = player_ckey, "ban_id" = ban_id)) if(!query_edit_ban_get_player.warn_execute()) qdel(query_edit_ban_get_player) return @@ -679,8 +778,14 @@ return qdel(query_edit_ban_get_player) if(applies_to_admins && (applies_to_admins != old_applies)) - var/admin_ckey = sanitizeSQL(usr.client.ckey) - var/datum/DBQuery/query_check_adminban_count = SSdbcore.NewQuery("SELECT COUNT(DISTINCT bantime) FROM [format_table_name("ban")] WHERE a_ckey = '[admin_ckey]' AND applies_to_admins = 1 AND unbanned_datetime IS NULL AND (expiration_time IS NULL OR expiration_time > NOW())") + var/datum/DBQuery/query_check_adminban_count = SSdbcore.NewQuery({" + SELECT COUNT(DISTINCT bantime) + FROM [format_table_name("ban")] + WHERE a_ckey = :admin_ckey + AND applies_to_admins = 1 + AND unbanned_datetime IS NULL + AND (expiration_time IS NULL OR expiration_time > NOW()) + "}, list("admin_ckey" = usr.client.ckey)) if(!query_check_adminban_count.warn_execute()) //count distinct bantime to treat rolebans made at the same time as one ban qdel(query_check_adminban_count) return @@ -694,37 +799,63 @@ qdel(query_check_adminban_count) return qdel(query_check_adminban_count) - applies_to_admins = sanitizeSQL(applies_to_admins) - duration = sanitizeSQL(duration) - if(interval) - interval = sanitizeSQL(interval) - else + + if (!(interval in list("SECOND", "MINUTE", "HOUR", "DAY", "WEEK", "MONTH", "YEAR"))) interval = "MINUTE" - reason = sanitizeSQL(reason) - var/kn = key_name(usr) - var/kna = key_name_admin(usr) - var/list/changes_text= list() + + var/list/changes_text = list() var/list/changes_keys = list() for(var/i in changes) - changes_text += "[sanitizeSQL(i)]: [sanitizeSQL(changes[i])]" + changes_text += "[i]: [changes[i]]" changes_keys += i - var/where = "id = [sanitizeSQL(ban_id)]" + var/change_message = "[usr.client.key] edited the following [jointext(changes_text, ", ")]
                        " + + var/list/arguments = list( + "duration" = duration || null, + "reason" = reason, + "applies_to_admins" = applies_to_admins, + "ckey" = player_ckey || null, + "ip" = player_ip || null, + "cid" = player_cid || null, + "change_message" = change_message, + ) + var/where if(text2num(mirror_edit)) var/list/wherelist = list("bantime = '[bantime]'") if(old_key) - wherelist += "ckey = '[sanitizeSQL(ckey(old_key))]'" + wherelist += "ckey = :old_ckey" + arguments["old_ckey"] = ckey(old_key) if(old_ip) - old_ip = sanitizeSQL(old_ip) - wherelist += "ip = INET_ATON(IF('[old_ip]' LIKE '', NULL, '[old_ip]'))" + wherelist += "ip = INET_ATON(:old_ip)" + arguments["old_ip"] = old_ip || null if(old_cid) - wherelist += "computerid = '[sanitizeSQL(old_cid)]'" + wherelist += "computerid = :old_cid" + arguments["old_cid"] = old_cid where = wherelist.Join(" AND ") - var/datum/DBQuery/query_edit_ban = SSdbcore.NewQuery("UPDATE [format_table_name("ban")] SET expiration_time = IF('[duration]' LIKE '', NULL, bantime + INTERVAL [duration ? "[duration]" : "0"] [interval]), applies_to_admins = [applies_to_admins], reason = '[reason]', ckey = IF('[player_ckey]' LIKE '', NULL, '[player_ckey]'), ip = INET_ATON(IF('[player_ip]' LIKE '', NULL, '[player_ip]')), computerid = IF('[player_cid]' LIKE '', NULL, '[player_cid]'), edits = CONCAT(IFNULL(edits,''),'[sanitizeSQL(usr.client.key)] edited the following [jointext(changes_text, ", ")]
                        ') WHERE [where]") + else + where = "id = :ban_id" + arguments["ban_id"] = ban_id + + var/datum/DBQuery/query_edit_ban = SSdbcore.NewQuery({" + UPDATE [format_table_name("ban")] + SET + expiration_time = IF(:duration IS NULL, NULL, bantime + INTERVAL :duration [interval]) + applies_to_admins = :applies_to_admins, + reason = :reason, + ckey = :ckey, + ip = INET_ATON(:ip), + computerid = :ci + edits = CONCAT(IFNULL(edits,''), :change_message) + WHERE [where] + "}, arguments) if(!query_edit_ban.warn_execute()) qdel(query_edit_ban) return qdel(query_edit_ban) + var/changes_keys_text = jointext(changes_keys, ", ") + var/kn = key_name(usr) + var/kna = key_name_admin(usr) log_admin_private("[kn] has edited the [changes_keys_text] of a ban for [old_key ? "[old_key]" : "[old_ip]-[old_cid]"].") //if a ban doesn't have a key it must have an ip and/or a cid to have reached this point normally message_admins("[kna] has edited the [changes_keys_text] of a ban for [old_key ? "[old_key]" : "[old_ip]-[old_cid]"].") if(changes["Applies to admins"]) @@ -745,8 +876,9 @@ if(!SSdbcore.Connect()) to_chat(usr, "Failed to establish database connection.", confidential = TRUE) return - ban_id = sanitizeSQL(ban_id) - var/datum/DBQuery/query_get_ban_edits = SSdbcore.NewQuery("SELECT edits FROM [format_table_name("ban")] WHERE id = '[ban_id]'") + var/datum/DBQuery/query_get_ban_edits = SSdbcore.NewQuery({" + SELECT edits FROM [format_table_name("ban")] WHERE id = :ban_id + "}, list("ban_id" = ban_id)) if(!query_get_ban_edits.warn_execute()) qdel(query_get_ban_edits) return diff --git a/code/modules/admin/sql_message_system.dm b/code/modules/admin/sql_message_system.dm index f112759944a8..e08f29a9c528 100644 --- a/code/modules/admin/sql_message_system.dm +++ b/code/modules/admin/sql_message_system.dm @@ -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/DBQuery/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,8 +57,10 @@ 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/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)", + list("expire_time" = expire_time) + ) if(!query_validate_expire_time.warn_execute()) qdel(query_validate_expire_time) return @@ -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/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(: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, + "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()) @@ -106,8 +121,11 @@ var/text var/user_key_name = key_name(usr) var/user_name_admin = key_name_admin(usr) - var/deleted_by_ckey = sanitizeSQL(usr.ckey) - 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/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 = :id AND deleted = 0", + list("id" = message_id) + ) if(!query_find_del_message.warn_execute()) qdel(query_find_del_message) return @@ -116,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, deleted_ckey = '[deleted_by_ckey]' WHERE id = [message_id]") + var/datum/DBQuery/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 @@ -138,11 +159,19 @@ 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/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 = :id AND deleted = 0 + "}, list("id" = message_id)) if(!query_find_edit_message.warn_execute()) qdel(query_find_edit_message) return @@ -155,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/DBQuery/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 @@ -177,11 +209,19 @@ 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/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 = :id AND deleted = 0 + "}, list("id" = message_id)) if(!query_find_edit_expiry_message.warn_execute()) qdel(query_find_edit_expiry_message) return @@ -198,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/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) + "}, 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) @@ -213,8 +254,12 @@ 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/DBQuery/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) @@ -237,7 +282,15 @@ 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/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 = :id AND deleted = 0 + "}, list("id" = message_id)) if(!query_find_edit_note_severity.warn_execute()) qdel(query_find_edit_note_severity) return @@ -248,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/DBQuery/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)) @@ -274,11 +331,19 @@ 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/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 = :id AND deleted = 0 + "}, list("id" = message_id)) if(!query_find_message_secret.warn_execute()) qdel(query_find_message_secret) return @@ -288,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/DBQuery/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) @@ -328,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/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) + "}, list("type" = type)) if(!query_get_type_messages.warn_execute()) qdel(query_get_type_messages) return @@ -361,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/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 = :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 @@ -441,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/DBQuery/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 @@ -478,8 +577,6 @@ var/search output += "
                    Add messageAdd watchlist entryAdd note
                    " output += ruler - if(!isnum(index)) - index = sanitizeSQL(index) switch(index) if(1) search = "^." @@ -487,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/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 + "}, list("search" = search)) if(!query_list_messages.warn_execute()) qdel(query_list_messages) return @@ -516,12 +623,19 @@ 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/DBQuery/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 @@ -535,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/DBQuery/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) @@ -575,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/DBQuery/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 diff --git a/code/modules/admin/stickyban.dm b/code/modules/admin/stickyban.dm index ab185dc2dbb2..0cad328a97a2 100644 --- a/code/modules/admin/stickyban.dm +++ b/code/modules/admin/stickyban.dm @@ -33,7 +33,10 @@ ban["message"] = "[reason]" if(SSdbcore.Connect()) - var/datum/DBQuery/query_create_stickyban = SSdbcore.NewQuery("INSERT INTO [format_table_name("stickyban")] (ckey, reason, banning_admin) VALUES ('[sanitizeSQL(ckey)]', '[sanitizeSQL(ban["message"])]', '[sanitizeSQL(usr.ckey)]')") + var/datum/DBQuery/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)) if (query_create_stickyban.warn_execute()) ban["fromdb"] = TRUE qdel(query_create_stickyban) @@ -68,10 +71,10 @@ if (SSdbcore.Connect()) SSdbcore.QuerySelect(list( - SSdbcore.NewQuery("DELETE FROM [format_table_name("stickyban")] WHERE ckey = '[sanitizeSQL(ckey)]'"), - SSdbcore.NewQuery("DELETE FROM [format_table_name("stickyban_matched_ckey")] WHERE stickyban = '[sanitizeSQL(ckey)]'"), - SSdbcore.NewQuery("DELETE FROM [format_table_name("stickyban_matched_cid")] WHERE stickyban = '[sanitizeSQL(ckey)]'"), - SSdbcore.NewQuery("DELETE FROM [format_table_name("stickyban_matched_ip")] WHERE stickyban = '[sanitizeSQL(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) @@ -116,7 +119,10 @@ SSstickyban.cache[ckey] = ban if (SSdbcore.Connect()) - var/datum/DBQuery/query_remove_stickyban_alt = SSdbcore.NewQuery("DELETE FROM [format_table_name("stickyban_matched_ckey")] WHERE stickyban = '[sanitizeSQL(ckey)]' AND matched_ckey = '[sanitizeSQL(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", + list("ckey" = ckey, "alt" = alt) + ) query_remove_stickyban_alt.warn_execute() qdel(query_remove_stickyban_alt) @@ -147,7 +153,10 @@ SSstickyban.cache[ckey] = ban if (SSdbcore.Connect()) - var/datum/DBQuery/query_edit_stickyban = SSdbcore.NewQuery("UPDATE [format_table_name("stickyban")] SET reason = '[sanitizeSQL(reason)]' WHERE ckey = '[sanitizeSQL(ckey)]'") + var/datum/DBQuery/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) @@ -194,7 +203,10 @@ SSstickyban.cache[ckey] = ban if (SSdbcore.Connect()) - var/datum/DBQuery/query_exempt_stickyban_alt = SSdbcore.NewQuery("UPDATE [format_table_name("stickyban_matched_ckey")] SET exempt = 1 WHERE stickyban = '[sanitizeSQL(ckey)]' AND matched_ckey = '[sanitizeSQL(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", + list("ckey" = ckey, "alt" = alt) + ) query_exempt_stickyban_alt.warn_execute() qdel(query_exempt_stickyban_alt) @@ -241,7 +253,10 @@ SSstickyban.cache[ckey] = ban if (SSdbcore.Connect()) - var/datum/DBQuery/query_unexempt_stickyban_alt = SSdbcore.NewQuery("UPDATE [format_table_name("stickyban_matched_ckey")] SET exempt = 0 WHERE stickyban = '[sanitizeSQL(ckey)]' AND matched_ckey = '[sanitizeSQL(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", + 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 03ff8e4168c8..22d972166aae 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -1,2535 +1,2557 @@ -/datum/admins/proc/CheckAdminHref(href, href_list) - var/auth = href_list["admin_token"] - . = auth && (auth == href_token || auth == GLOB.href_token) - if(.) - return - var/msg = !auth ? "no" : "a bad" - message_admins("[key_name_admin(usr)] clicked an href with [msg] authorization key!") - if(CONFIG_GET(flag/debug_admin_hrefs)) - message_admins("Debug mode enabled, call not blocked. Please ask your coders to review this round's logs.") - log_world("UAH: [href]") - return TRUE - log_admin_private("[key_name(usr)] clicked an href with [msg] authorization key! [href]") - -/datum/admins/Topic(href, href_list) - ..() - - if(usr.client != src.owner || !check_rights(0)) - message_admins("[usr.key] has attempted to override the admin panel!") - log_admin("[key_name(usr)] tried to use the admin panel without authorization.") - return - - if(!CheckAdminHref(href, href_list)) - return - - if(href_list["ahelp"]) - if(!check_rights(R_ADMIN, TRUE)) - return - - var/ahelp_ref = href_list["ahelp"] - var/datum/admin_help/AH = locate(ahelp_ref) - if(AH) - AH.Action(href_list["ahelp_action"]) - else - to_chat(usr, "Ticket [ahelp_ref] has been deleted!", confidential = TRUE) - - else if(href_list["ahelp_tickets"]) - GLOB.ahelp_tickets.BrowseTickets(text2num(href_list["ahelp_tickets"])) - - else if(href_list["stickyban"]) - stickyban(href_list["stickyban"],href_list) - - else if(href_list["getplaytimewindow"]) - if(!check_rights(R_ADMIN)) - return - var/mob/M = locate(href_list["getplaytimewindow"]) in GLOB.mob_list - if(!M) - to_chat(usr, "ERROR: Mob not found.", confidential = TRUE) - return - cmd_show_exp_panel(M.client) - - else if(href_list["toggleexempt"]) - if(!check_rights(R_ADMIN)) - return - var/client/C = locate(href_list["toggleexempt"]) in GLOB.clients - if(!C) - to_chat(usr, "ERROR: Client not found.", confidential = TRUE) - return - toggle_exempt_status(C) - - else if(href_list["makeAntag"]) - if(!check_rights(R_ADMIN)) - return - if (!SSticker.mode) - to_chat(usr, "Not until the round starts!", confidential = TRUE) - return - switch(href_list["makeAntag"]) - if("traitors") - if(src.makeTraitors()) - message_admins("[key_name_admin(usr)] created traitors.") - log_admin("[key_name(usr)] created traitors.") - else - message_admins("[key_name_admin(usr)] tried to create traitors. Unfortunately, there were no candidates available.") - log_admin("[key_name(usr)] failed to create traitors.") - if("changelings") - if(src.makeChangelings()) - message_admins("[key_name(usr)] created changelings.") - log_admin("[key_name(usr)] created changelings.") - else - message_admins("[key_name_admin(usr)] tried to create changelings. Unfortunately, there were no candidates available.") - log_admin("[key_name(usr)] failed to create changelings.") - if("revs") - if(src.makeRevs()) - message_admins("[key_name(usr)] started a revolution.") - log_admin("[key_name(usr)] started a revolution.") - else - message_admins("[key_name_admin(usr)] tried to start a revolution. Unfortunately, there were no candidates available.") - log_admin("[key_name(usr)] failed to start a revolution.") - if("cult") - if(src.makeCult()) - message_admins("[key_name(usr)] started a cult.") - log_admin("[key_name(usr)] started a cult.") - else - message_admins("[key_name_admin(usr)] tried to start a cult. Unfortunately, there were no candidates available.") - log_admin("[key_name(usr)] failed to start a cult.") - if("wizard") - message_admins("[key_name(usr)] is creating a wizard...") - if(src.makeWizard()) - message_admins("[key_name(usr)] created a wizard.") - log_admin("[key_name(usr)] created a wizard.") - else - message_admins("[key_name_admin(usr)] tried to create a wizard. Unfortunately, there were no candidates available.") - log_admin("[key_name(usr)] failed to create a wizard.") - if("nukeops") - message_admins("[key_name(usr)] is creating a nuke team...") - if(src.makeNukeTeam()) - message_admins("[key_name(usr)] created a nuke team.") - log_admin("[key_name(usr)] created a nuke team.") - else - message_admins("[key_name_admin(usr)] tried to create a nuke team. Unfortunately, there were not enough candidates available.") - log_admin("[key_name(usr)] failed to create a nuke team.") - if("ninja") - message_admins("[key_name(usr)] spawned a ninja.") - log_admin("[key_name(usr)] spawned a ninja.") - src.makeSpaceNinja() - if("aliens") - message_admins("[key_name(usr)] started an alien infestation.") - log_admin("[key_name(usr)] started an alien infestation.") - src.makeAliens() - if("deathsquad") - message_admins("[key_name(usr)] is creating a death squad...") - if(src.makeDeathsquad()) - message_admins("[key_name(usr)] created a death squad.") - log_admin("[key_name(usr)] created a death squad.") - else - message_admins("[key_name_admin(usr)] tried to create a death squad. Unfortunately, there were not enough candidates available.") - log_admin("[key_name(usr)] failed to create a death squad.") - if("blob") - var/strength = input("Set Blob Resource Gain Rate","Set Resource Rate",1) as num|null - if(!strength) - return - message_admins("[key_name(usr)] spawned a blob with base resource gain [strength].") - log_admin("[key_name(usr)] spawned a blob with base resource gain [strength].") - new/datum/round_event/ghost_role/blob(TRUE, strength) - if("centcom") - message_admins("[key_name(usr)] is creating a CentCom response team...") - if(src.makeEmergencyresponseteam()) - message_admins("[key_name(usr)] created a CentCom response team.") - log_admin("[key_name(usr)] created a CentCom response team.") - else - message_admins("[key_name_admin(usr)] tried to create a CentCom response team. Unfortunately, there were not enough candidates available.") - log_admin("[key_name(usr)] failed to create a CentCom response team.") - if("abductors") - message_admins("[key_name(usr)] is creating an abductor team...") - if(src.makeAbductorTeam()) - message_admins("[key_name(usr)] created an abductor team.") - log_admin("[key_name(usr)] created an abductor team.") - else - message_admins("[key_name_admin(usr)] tried to create an abductor team. Unfortunatly there were not enough candidates available.") - log_admin("[key_name(usr)] failed to create an abductor team.") - if("revenant") - if(src.makeRevenant()) - message_admins("[key_name(usr)] created a revenant.") - log_admin("[key_name(usr)] created a revenant.") - else - message_admins("[key_name_admin(usr)] tried to create a revenant. Unfortunately, there were no candidates available.") - log_admin("[key_name(usr)] failed to create a revenant.") - - else if(href_list["forceevent"]) - if(!check_rights(R_FUN)) - return - var/datum/round_event_control/E = locate(href_list["forceevent"]) in SSevents.control - if(E) - E.admin_setup(usr) - var/datum/round_event/event = E.runEvent() - if(event.announceWhen>0) - event.processing = FALSE - var/prompt = alert(usr, "Would you like to alert the crew?", "Alert", "Yes", "No", "Cancel") - switch(prompt) - if("Yes") - event.announceChance = 100 - if("Cancel") - event.kill() - return - if("No") - event.announceChance = 0 - event.processing = TRUE - message_admins("[key_name_admin(usr)] has triggered an event. ([E.name])") - log_admin("[key_name(usr)] has triggered an event. ([E.name])") - return - - else if(href_list["editrightsbrowser"]) - edit_admin_permissions(0) - - else if(href_list["editrightsbrowserlog"]) - edit_admin_permissions(1, href_list["editrightstarget"], href_list["editrightsoperation"], href_list["editrightspage"]) - - if(href_list["editrightsbrowsermanage"]) - if(href_list["editrightschange"]) - change_admin_rank(ckey(href_list["editrightschange"]), href_list["editrightschange"], TRUE) - else if(href_list["editrightsremove"]) - remove_admin(ckey(href_list["editrightsremove"]), href_list["editrightsremove"], TRUE) - else if(href_list["editrightsremoverank"]) - remove_rank(href_list["editrightsremoverank"]) - edit_admin_permissions(2) - - else if(href_list["editrights"]) - edit_rights_topic(href_list) - - else if(href_list["gamemode_panel"]) - if(!check_rights(R_ADMIN)) - return - SSticker.mode.admin_panel() - - else if(href_list["call_shuttle"]) - if(!check_rights(R_ADMIN)) - return - - - switch(href_list["call_shuttle"]) - if("1") - if(EMERGENCY_AT_LEAST_DOCKED) - return - SSshuttle.emergency.request() - log_admin("[key_name(usr)] called the Emergency Shuttle.") - message_admins("[key_name_admin(usr)] called the Emergency Shuttle to the station.") - - if("2") - if(EMERGENCY_AT_LEAST_DOCKED) - return - switch(SSshuttle.emergency.mode) - if(SHUTTLE_CALL) - SSshuttle.emergency.cancel() - log_admin("[key_name(usr)] sent the Emergency Shuttle back.") - message_admins("[key_name_admin(usr)] sent the Emergency Shuttle back.") - else - SSshuttle.emergency.cancel() - log_admin("[key_name(usr)] called the Emergency Shuttle.") - message_admins("[key_name_admin(usr)] called the Emergency Shuttle to the station.") - - - - else if(href_list["edit_shuttle_time"]) - if(!check_rights(R_SERVER)) - return - - var/timer = input("Enter new shuttle duration (seconds):","Edit Shuttle Timeleft", SSshuttle.emergency.timeLeft() ) as num|null - if(!timer) - return - SSshuttle.emergency.setTimer(timer*10) - log_admin("[key_name(usr)] edited the Emergency Shuttle's timeleft to [timer] seconds.") - minor_announce("The emergency shuttle will reach its destination in [round(SSshuttle.emergency.timeLeft(600))] minutes.") - message_admins("[key_name_admin(usr)] edited the Emergency Shuttle's timeleft to [timer] seconds.") - else if(href_list["trigger_centcom_recall"]) - if(!check_rights(R_ADMIN)) - return - - usr.client.trigger_centcom_recall() - - else if(href_list["toggle_continuous"]) - if(!check_rights(R_ADMIN)) - return - var/list/continuous = CONFIG_GET(keyed_list/continuous) - if(!continuous[SSticker.mode.config_tag]) - continuous[SSticker.mode.config_tag] = TRUE - else - continuous[SSticker.mode.config_tag] = FALSE - - message_admins("[key_name_admin(usr)] toggled the round to [continuous[SSticker.mode.config_tag] ? "continue if all antagonists die" : "end with the antagonists"].") - check_antagonists() - - else if(href_list["toggle_midround_antag"]) - if(!check_rights(R_ADMIN)) - return - - var/list/midround_antag = CONFIG_GET(keyed_list/midround_antag) - if(!midround_antag[SSticker.mode.config_tag]) - midround_antag[SSticker.mode.config_tag] = TRUE - else - midround_antag[SSticker.mode.config_tag] = FALSE - - message_admins("[key_name_admin(usr)] toggled the round to [midround_antag[SSticker.mode.config_tag] ? "use" : "skip"] the midround antag system.") - check_antagonists() - - else if(href_list["alter_midround_time_limit"]) - if(!check_rights(R_ADMIN)) - return - - var/timer = input("Enter new maximum time",, CONFIG_GET(number/midround_antag_time_check)) as num|null - if(!timer) - return - CONFIG_SET(number/midround_antag_time_check, timer) - message_admins("[key_name_admin(usr)] edited the maximum midround antagonist time to [timer] minutes.") - check_antagonists() - - else if(href_list["alter_midround_life_limit"]) - if(!check_rights(R_ADMIN)) - return - - var/ratio = input("Enter new life ratio",, CONFIG_GET(number/midround_antag_life_check) * 100) as num|null - if(!ratio) - return - CONFIG_SET(number/midround_antag_life_check, ratio / 100) - - message_admins("[key_name_admin(usr)] edited the midround antagonist living crew ratio to [ratio]% alive.") - check_antagonists() - - else if(href_list["toggle_noncontinuous_behavior"]) - if(!check_rights(R_ADMIN)) - return - - if(!SSticker.mode.round_ends_with_antag_death) - SSticker.mode.round_ends_with_antag_death = 1 - else - SSticker.mode.round_ends_with_antag_death = 0 - - message_admins("[key_name_admin(usr)] edited the midround antagonist system to [SSticker.mode.round_ends_with_antag_death ? "end the round" : "continue as extended"] upon failure.") - check_antagonists() - - else if(href_list["delay_round_end"]) - if(!check_rights(R_SERVER)) - return - if(!SSticker.delay_end) - SSticker.admin_delay_notice = input(usr, "Enter a reason for delaying the round end", "Round Delay Reason") as null|text - if(isnull(SSticker.admin_delay_notice)) - return - else - if(alert(usr, "Really cancel current round end delay? The reason for the current delay is: \"[SSticker.admin_delay_notice]\"", "Undelay round end", "Yes", "No") != "Yes") - return - SSticker.admin_delay_notice = null - SSticker.delay_end = !SSticker.delay_end - var/reason = SSticker.delay_end ? "for reason: [SSticker.admin_delay_notice]" : "."//laziness - var/msg = "[SSticker.delay_end ? "delayed" : "undelayed"] the round end [reason]" - log_admin("[key_name(usr)] [msg]") - message_admins("[key_name_admin(usr)] [msg]") - if(SSticker.ready_for_reboot && !SSticker.delay_end) //we undelayed after standard reboot would occur - SSticker.standard_reboot() - - else if(href_list["end_round"]) - if(!check_rights(R_ADMIN)) - return - - message_admins("[key_name_admin(usr)] is considering ending the round.") - if(alert(usr, "This will end the round, are you SURE you want to do this?", "Confirmation", "Yes", "No") == "Yes") - if(alert(usr, "Final Confirmation: End the round NOW?", "Confirmation", "Yes", "No") == "Yes") - message_admins("[key_name_admin(usr)] has ended the round.") - SSticker.force_ending = 1 //Yeah there we go APC destroyed mission accomplished - return - else - message_admins("[key_name_admin(usr)] decided against ending the round.") - else - message_admins("[key_name_admin(usr)] decided against ending the round.") - - else if(href_list["simplemake"]) - if(!check_rights(R_SPAWN)) - return - - var/mob/M = locate(href_list["mob"]) - if(!ismob(M)) - to_chat(usr, "This can only be used on instances of type /mob.", confidential = TRUE) - return - - var/delmob = TRUE - if(!isobserver(M)) - switch(alert("Delete old mob?","Message","Yes","No","Cancel")) - if("Cancel") - return - if("No") - delmob = FALSE - - log_admin("[key_name(usr)] has used rudimentary transformation on [key_name(M)]. Transforming to [href_list["simplemake"]].; deletemob=[delmob]") - message_admins("[key_name_admin(usr)] has used rudimentary transformation on [key_name_admin(M)]. Transforming to [href_list["simplemake"]].; deletemob=[delmob]") - switch(href_list["simplemake"]) - if("observer") - M.change_mob_type( /mob/dead/observer , null, null, delmob ) - if("drone") - M.change_mob_type( /mob/living/carbon/alien/humanoid/drone , null, null, delmob ) - if("hunter") - M.change_mob_type( /mob/living/carbon/alien/humanoid/hunter , null, null, delmob ) - if("queen") - M.change_mob_type( /mob/living/carbon/alien/humanoid/royal/queen , null, null, delmob ) - if("praetorian") - M.change_mob_type( /mob/living/carbon/alien/humanoid/royal/praetorian , null, null, delmob ) - if("sentinel") - M.change_mob_type( /mob/living/carbon/alien/humanoid/sentinel , null, null, delmob ) - if("larva") - M.change_mob_type( /mob/living/carbon/alien/larva , null, null, delmob ) - if("human") - var/posttransformoutfit = usr.client.robust_dress_shop() - if (!posttransformoutfit) - return - var/mob/living/carbon/human/newmob = M.change_mob_type( /mob/living/carbon/human , null, null, delmob ) - if(posttransformoutfit && istype(newmob)) - newmob.equipOutfit(posttransformoutfit) - if("slime") - M.change_mob_type( /mob/living/simple_animal/slime , null, null, delmob ) - if("monkey") - M.change_mob_type( /mob/living/carbon/monkey , null, null, delmob ) - if("robot") - M.change_mob_type( /mob/living/silicon/robot , null, null, delmob ) - if("cat") - M.change_mob_type( /mob/living/simple_animal/pet/cat , null, null, delmob ) - if("runtime") - M.change_mob_type( /mob/living/simple_animal/pet/cat/Runtime , null, null, delmob ) - if("corgi") - M.change_mob_type( /mob/living/simple_animal/pet/dog/corgi , null, null, delmob ) - if("ian") - M.change_mob_type( /mob/living/simple_animal/pet/dog/corgi/Ian , null, null, delmob ) - if("pug") - M.change_mob_type( /mob/living/simple_animal/pet/dog/pug , null, null, delmob ) - if("crab") - M.change_mob_type( /mob/living/simple_animal/crab , null, null, delmob ) - if("coffee") - M.change_mob_type( /mob/living/simple_animal/crab/Coffee , null, null, delmob ) - if("parrot") - M.change_mob_type( /mob/living/simple_animal/parrot , null, null, delmob ) - if("polyparrot") - M.change_mob_type( /mob/living/simple_animal/parrot/Poly , null, null, delmob ) - if("constructjuggernaut") - M.change_mob_type( /mob/living/simple_animal/hostile/construct/juggernaut , null, null, delmob ) - if("constructartificer") - M.change_mob_type( /mob/living/simple_animal/hostile/construct/artificer , null, null, delmob ) - if("constructwraith") - M.change_mob_type( /mob/living/simple_animal/hostile/construct/wraith , null, null, delmob ) - if("shade") - M.change_mob_type( /mob/living/simple_animal/shade , null, null, delmob ) - - else if(href_list["boot2"]) - if(!check_rights(R_ADMIN)) - return - var/mob/M = locate(href_list["boot2"]) - if(ismob(M)) - if(!check_if_greater_rights_than(M.client)) - to_chat(usr, "Error: They have more rights than you do.", confidential = TRUE) - return - if(alert(usr, "Kick [key_name(M)]?", "Confirm", "Yes", "No") != "Yes") - return - if(!M) - to_chat(usr, "Error: [M] no longer exists!", confidential = TRUE) - return - if(!M.client) - to_chat(usr, "Error: [M] no longer has a client!", confidential = TRUE) - return - to_chat(M, "You have been kicked from the server by [usr.client.holder.fakekey ? "an Administrator" : "[usr.client.key]"].", confidential = TRUE) - log_admin("[key_name(usr)] kicked [key_name(M)].") - message_admins("[key_name_admin(usr)] kicked [key_name_admin(M)].") - qdel(M.client) - - else if(href_list["addmessage"]) - if(!check_rights(R_ADMIN)) - return - var/target_key = href_list["addmessage"] - create_message("message", target_key, secret = 0) - - else if(href_list["addnote"]) - if(!check_rights(R_ADMIN)) - return - var/target_key = href_list["addnote"] - create_message("note", target_key) - - else if(href_list["addwatch"]) - if(!check_rights(R_ADMIN)) - return - var/target_key = href_list["addwatch"] - create_message("watchlist entry", target_key, secret = 1) - - else if(href_list["addmemo"]) - if(!check_rights(R_ADMIN)) - return - create_message("memo", secret = 0, browse = 1) - - else if(href_list["addmessageempty"]) - if(!check_rights(R_ADMIN)) - return - create_message("message", secret = 0) - - else if(href_list["addnoteempty"]) - if(!check_rights(R_ADMIN)) - return - create_message("note") - - else if(href_list["addwatchempty"]) - if(!check_rights(R_ADMIN)) - return - create_message("watchlist entry", secret = 1) - - else if(href_list["deletemessage"]) - if(!check_rights(R_ADMIN)) - return - var/safety = alert("Delete message/note?",,"Yes","No"); - if (safety == "Yes") - var/message_id = href_list["deletemessage"] - delete_message(message_id) - - else if(href_list["deletemessageempty"]) - if(!check_rights(R_ADMIN)) - return - var/safety = alert("Delete message/note?",,"Yes","No"); - if (safety == "Yes") - var/message_id = href_list["deletemessageempty"] - delete_message(message_id, browse = TRUE) - - else if(href_list["editmessage"]) - if(!check_rights(R_ADMIN)) - return - var/message_id = href_list["editmessage"] - edit_message(message_id) - - else if(href_list["editmessageempty"]) - if(!check_rights(R_ADMIN)) - return - var/message_id = href_list["editmessageempty"] - edit_message(message_id, browse = 1) - - else if(href_list["editmessageexpiry"]) - if(!check_rights(R_ADMIN)) - return - var/message_id = href_list["editmessageexpiry"] - edit_message_expiry(message_id) - - else if(href_list["editmessageexpiryempty"]) - if(!check_rights(R_ADMIN)) - return - var/message_id = href_list["editmessageexpiryempty"] - edit_message_expiry(message_id, browse = 1) - - else if(href_list["editmessageseverity"]) - if(!check_rights(R_ADMIN)) - return - var/message_id = href_list["editmessageseverity"] - edit_message_severity(message_id) - - else if(href_list["secretmessage"]) - if(!check_rights(R_ADMIN)) - return - var/message_id = href_list["secretmessage"] - toggle_message_secrecy(message_id) - - else if(href_list["searchmessages"]) - if(!check_rights(R_ADMIN)) - return - var/target = href_list["searchmessages"] - browse_messages(index = target) - - else if(href_list["nonalpha"]) - if(!check_rights(R_ADMIN)) - return - var/target = href_list["nonalpha"] - target = text2num(target) - browse_messages(index = target) - - else if(href_list["showmessages"]) - if(!check_rights(R_ADMIN)) - return - var/target = href_list["showmessages"] - browse_messages(index = target) - - else if(href_list["showmemo"]) - if(!check_rights(R_ADMIN)) - return - browse_messages("memo") - - else if(href_list["showwatch"]) - if(!check_rights(R_ADMIN)) - return - browse_messages("watchlist entry") - - else if(href_list["showwatchfilter"]) - if(!check_rights(R_ADMIN)) - return - browse_messages("watchlist entry", filter = 1) - - else if(href_list["showmessageckey"]) - if(!check_rights(R_ADMIN)) - return - var/target = href_list["showmessageckey"] - var/agegate = TRUE - if (href_list["showall"]) - agegate = FALSE - browse_messages(target_ckey = target, agegate = agegate) - - else if(href_list["showmessageckeylinkless"]) - var/target = href_list["showmessageckeylinkless"] - browse_messages(target_ckey = target, linkless = 1) - - 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]'") - if(!query_get_message_edits.warn_execute()) - qdel(query_get_message_edits) - return - if(query_get_message_edits.NextRow()) - var/edit_log = query_get_message_edits.item[1] - if(!QDELETED(usr)) - var/datum/browser/browser = new(usr, "Note edits", "Note edits") - browser.set_content(jointext(edit_log, "")) - browser.open() - qdel(query_get_message_edits) - - else if(href_list["mute"]) - if(!check_rights(R_ADMIN)) - return - cmd_admin_mute(href_list["mute"], text2num(href_list["mute_type"])) - - else if(href_list["c_mode"]) - return HandleCMode() - - else if(href_list["f_secret"]) - return HandleFSecret() - - else if(href_list["f_dynamic_roundstart"]) - if(!check_rights(R_ADMIN)) - return - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode.", null, null, null, null) - var/roundstart_rules = list() - for (var/rule in subtypesof(/datum/dynamic_ruleset/roundstart)) - var/datum/dynamic_ruleset/roundstart/newrule = new rule() - roundstart_rules[newrule.name] = newrule - var/added_rule = input(usr,"What ruleset do you want to force? This will bypass threat level and population restrictions.", "Rigging Roundstart", null) as null|anything in sortList(roundstart_rules) - if (added_rule) - GLOB.dynamic_forced_roundstart_ruleset += roundstart_rules[added_rule] - log_admin("[key_name(usr)] set [added_rule] to be a forced roundstart ruleset.") - message_admins("[key_name(usr)] set [added_rule] to be a forced roundstart ruleset.", 1) - Game() - - else if(href_list["f_dynamic_roundstart_clear"]) - if(!check_rights(R_ADMIN)) - return - GLOB.dynamic_forced_roundstart_ruleset = list() - Game() - log_admin("[key_name(usr)] cleared the rigged roundstart rulesets. The mode will pick them as normal.") - message_admins("[key_name(usr)] cleared the rigged roundstart rulesets. The mode will pick them as normal.", 1) - - else if(href_list["f_dynamic_roundstart_remove"]) - if(!check_rights(R_ADMIN)) - return - var/datum/dynamic_ruleset/roundstart/rule = locate(href_list["f_dynamic_roundstart_remove"]) - GLOB.dynamic_forced_roundstart_ruleset -= rule - Game() - log_admin("[key_name(usr)] removed [rule] from the forced roundstart rulesets.") - message_admins("[key_name(usr)] removed [rule] from the forced roundstart rulesets.", 1) - - else if(href_list["f_dynamic_latejoin"]) - if(!check_rights(R_ADMIN)) - return - if(!SSticker || !SSticker.mode) - return alert(usr, "The game must start first.", null, null, null, null) - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - var/latejoin_rules = list() - for (var/rule in subtypesof(/datum/dynamic_ruleset/latejoin)) - var/datum/dynamic_ruleset/latejoin/newrule = new rule() - latejoin_rules[newrule.name] = newrule - var/added_rule = input(usr,"What ruleset do you want to force upon the next latejoiner? This will bypass threat level and population restrictions.", "Rigging Latejoin", null) as null|anything in sortList(latejoin_rules) - if (added_rule) - var/datum/game_mode/dynamic/mode = SSticker.mode - mode.forced_latejoin_rule = latejoin_rules[added_rule] - log_admin("[key_name(usr)] set [added_rule] to proc on the next latejoin.") - message_admins("[key_name(usr)] set [added_rule] to proc on the next latejoin.", 1) - Game() - - else if(href_list["f_dynamic_latejoin_clear"]) - if(!check_rights(R_ADMIN)) - return - if (SSticker && SSticker.mode && istype(SSticker.mode,/datum/game_mode/dynamic)) - var/datum/game_mode/dynamic/mode = SSticker.mode - mode.forced_latejoin_rule = null - Game() - log_admin("[key_name(usr)] cleared the forced latejoin ruleset.") - message_admins("[key_name(usr)] cleared the forced latejoin ruleset.", 1) - - else if(href_list["f_dynamic_midround"]) - if(!check_rights(R_ADMIN)) - return - if(!SSticker || !SSticker.mode) - return alert(usr, "The game must start first.", null, null, null, null) - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - var/midround_rules = list() - for (var/rule in subtypesof(/datum/dynamic_ruleset/midround)) - var/datum/dynamic_ruleset/midround/newrule = new rule() - midround_rules[newrule.name] = rule - var/added_rule = input(usr,"What ruleset do you want to force right now? This will bypass threat level and population restrictions.", "Execute Ruleset", null) as null|anything in sortList(midround_rules) - if (added_rule) - var/datum/game_mode/dynamic/mode = SSticker.mode - log_admin("[key_name(usr)] executed the [added_rule] ruleset.") - message_admins("[key_name(usr)] executed the [added_rule] ruleset.", 1) - mode.picking_specific_rule(midround_rules[added_rule],1) - - else if (href_list["f_dynamic_options"]) - if(!check_rights(R_ADMIN)) - return - - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_roundstart_centre"]) - if(!check_rights(R_ADMIN)) - return - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - - var/new_centre = input(usr,"Change the centre of the dynamic mode threat curve. A negative value will give a more peaceful round ; a positive value, a round with higher threat. Any number between -5 and +5 is allowed.", "Change curve centre", null) as num - if (new_centre < -5 || new_centre > 5) - return alert(usr, "Only values between -5 and +5 are allowed.", null, null, null, null) - - log_admin("[key_name(usr)] changed the distribution curve center to [new_centre].") - message_admins("[key_name(usr)] changed the distribution curve center to [new_centre]", 1) - GLOB.dynamic_curve_centre = new_centre - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_roundstart_width"]) - if(!check_rights(R_ADMIN)) - return - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - - var/new_width = input(usr,"Change the width of the dynamic mode threat curve. A higher value will favour extreme rounds ; a lower value, a round closer to the average. Any Number between 0.5 and 4 are allowed.", "Change curve width", null) as num - if (new_width < 0.5 || new_width > 4) - return alert(usr, "Only values between 0.5 and +2.5 are allowed.", null, null, null, null) - - log_admin("[key_name(usr)] changed the distribution curve width to [new_width].") - message_admins("[key_name(usr)] changed the distribution curve width to [new_width]", 1) - GLOB.dynamic_curve_width = new_width - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_roundstart_latejoin_min"]) - if(!check_rights(R_ADMIN)) - return - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - var/new_min = input(usr,"Change the minimum delay of latejoin injection in minutes.", "Change latejoin injection delay minimum", null) as num - if(new_min <= 0) - return alert(usr, "The minimum can't be zero or lower.", null, null, null, null) - if((new_min MINUTES) > GLOB.dynamic_latejoin_delay_max) - return alert(usr, "The minimum must be lower than the maximum.", null, null, null, null) - - log_admin("[key_name(usr)] changed the latejoin injection minimum delay to [new_min] minutes.") - message_admins("[key_name(usr)] changed the latejoin injection minimum delay to [new_min] minutes", 1) - GLOB.dynamic_latejoin_delay_min = (new_min MINUTES) - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_roundstart_latejoin_max"]) - if(!check_rights(R_ADMIN)) - return - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - var/new_max = input(usr,"Change the maximum delay of latejoin injection in minutes.", "Change latejoin injection delay maximum", null) as num - if(new_max <= 0) - return alert(usr, "The maximum can't be zero or lower.", null, null, null, null) - if((new_max MINUTES) < GLOB.dynamic_latejoin_delay_min) - return alert(usr, "The maximum must be higher than the minimum.", null, null, null, null) - - log_admin("[key_name(usr)] changed the latejoin injection maximum delay to [new_max] minutes.") - message_admins("[key_name(usr)] changed the latejoin injection maximum delay to [new_max] minutes", 1) - GLOB.dynamic_latejoin_delay_max = (new_max MINUTES) - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_roundstart_midround_min"]) - if(!check_rights(R_ADMIN)) - return - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - var/new_min = input(usr,"Change the minimum delay of midround injection in minutes.", "Change midround injection delay minimum", null) as num - if(new_min <= 0) - return alert(usr, "The minimum can't be zero or lower.", null, null, null, null) - if((new_min MINUTES) > GLOB.dynamic_midround_delay_max) - return alert(usr, "The minimum must be lower than the maximum.", null, null, null, null) - - log_admin("[key_name(usr)] changed the midround injection minimum delay to [new_min] minutes.") - message_admins("[key_name(usr)] changed the midround injection minimum delay to [new_min] minutes", 1) - GLOB.dynamic_midround_delay_min = (new_min MINUTES) - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_roundstart_midround_max"]) - if(!check_rights(R_ADMIN)) - return - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - var/new_max = input(usr,"Change the maximum delay of midround injection in minutes.", "Change midround injection delay maximum", null) as num - if(new_max <= 0) - return alert(usr, "The maximum can't be zero or lower.", null, null, null, null) - if((new_max MINUTES) > GLOB.dynamic_midround_delay_max) - return alert(usr, "The maximum must be higher than the minimum.", null, null, null, null) - - log_admin("[key_name(usr)] changed the midround injection maximum delay to [new_max] minutes.") - message_admins("[key_name(usr)] changed the midround injection maximum delay to [new_max] minutes", 1) - GLOB.dynamic_midround_delay_max = (new_max MINUTES) - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_force_extended"]) - if(!check_rights(R_ADMIN)) - return - - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - - GLOB.dynamic_forced_extended = !GLOB.dynamic_forced_extended - log_admin("[key_name(usr)] set 'forced_extended' to [GLOB.dynamic_forced_extended].") - message_admins("[key_name(usr)] set 'forced_extended' to [GLOB.dynamic_forced_extended].") - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_no_stacking"]) - if(!check_rights(R_ADMIN)) - return - - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - - GLOB.dynamic_no_stacking = !GLOB.dynamic_no_stacking - log_admin("[key_name(usr)] set 'no_stacking' to [GLOB.dynamic_no_stacking].") - message_admins("[key_name(usr)] set 'no_stacking' to [GLOB.dynamic_no_stacking].") - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_classic_secret"]) - if(!check_rights(R_ADMIN)) - return - - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - - GLOB.dynamic_classic_secret = !GLOB.dynamic_classic_secret - log_admin("[key_name(usr)] set 'classic_secret' to [GLOB.dynamic_classic_secret].") - message_admins("[key_name(usr)] set 'classic_secret' to [GLOB.dynamic_classic_secret].") - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_stacking_limit"]) - if(!check_rights(R_ADMIN)) - return - - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - - GLOB.dynamic_stacking_limit = input(usr,"Change the threat limit at which round-endings rulesets will start to stack.", "Change stacking limit", null) as num - log_admin("[key_name(usr)] set 'stacking_limit' to [GLOB.dynamic_stacking_limit].") - message_admins("[key_name(usr)] set 'stacking_limit' to [GLOB.dynamic_stacking_limit].") - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_high_pop_limit"]) - if(!check_rights(R_ADMIN)) - return - - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - - var/new_value = input(usr, "Enter the high-pop override threshold for dynamic mode.", "High pop override") as num - if (new_value < 0) - return alert(usr, "Only positive values allowed!", null, null, null, null) - GLOB.dynamic_high_pop_limit = new_value - - log_admin("[key_name(usr)] set 'high_pop_limit' to [GLOB.dynamic_high_pop_limit].") - message_admins("[key_name(usr)] set 'high_pop_limit' to [GLOB.dynamic_high_pop_limit].") - dynamic_mode_options(usr) - - else if(href_list["f_dynamic_forced_threat"]) - if(!check_rights(R_ADMIN)) - return - - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - - if(GLOB.master_mode != "dynamic") - return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) - - var/new_value = input(usr, "Enter the forced threat level for dynamic mode.", "Forced threat level") as num - if (new_value > 100) - return alert(usr, "The value must be be under 100.", null, null, null, null) - GLOB.dynamic_forced_threat_level = new_value - - log_admin("[key_name(usr)] set 'forced_threat_level' to [GLOB.dynamic_forced_threat_level].") - message_admins("[key_name(usr)] set 'forced_threat_level' to [GLOB.dynamic_forced_threat_level].") - dynamic_mode_options(usr) - - else if(href_list["c_mode2"]) - if(!check_rights(R_ADMIN|R_SERVER)) - return - - if (SSticker.HasRoundStarted()) - if (askuser(usr, "The game has already started. Would you like to save this as the default mode effective next round?", "Save mode", "Yes", "Cancel", Timeout = null) == 1) - SSticker.save_mode(href_list["c_mode2"]) - HandleCMode() - return - GLOB.master_mode = href_list["c_mode2"] - log_admin("[key_name(usr)] set the mode as [GLOB.master_mode].") - message_admins("[key_name_admin(usr)] set the mode as [GLOB.master_mode].") - to_chat(world, "The mode is now: [GLOB.master_mode]", confidential = TRUE) - Game() // updates the main game menu - if (askuser(usr, "Would you like to save this as the default mode for the server?", "Save mode", "Yes", "No", Timeout = null) == 1) - SSticker.save_mode(GLOB.master_mode) - HandleCMode() - - else if(href_list["f_secret2"]) - if(!check_rights(R_ADMIN|R_SERVER)) - return - - if(SSticker.HasRoundStarted()) - return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "secret") - return alert(usr, "The game mode has to be secret!", null, null, null, null) - GLOB.secret_force_mode = href_list["f_secret2"] - log_admin("[key_name(usr)] set the forced secret mode as [GLOB.secret_force_mode].") - message_admins("[key_name_admin(usr)] set the forced secret mode as [GLOB.secret_force_mode].") - Game() // updates the main game menu - HandleFSecret() - - else if(href_list["monkeyone"]) - if(!check_rights(R_SPAWN)) - return - - var/mob/living/carbon/human/H = locate(href_list["monkeyone"]) - if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.", confidential = TRUE) - return - - log_admin("[key_name(usr)] attempting to monkeyize [key_name(H)].") - message_admins("[key_name_admin(usr)] attempting to monkeyize [key_name_admin(H)].") - H.monkeyize() - - else if(href_list["humanone"]) - if(!check_rights(R_SPAWN)) - return - - var/mob/living/carbon/monkey/Mo = locate(href_list["humanone"]) - if(!istype(Mo)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/monkey.", confidential = TRUE) - return - - log_admin("[key_name(usr)] attempting to humanize [key_name(Mo)].") - message_admins("[key_name_admin(usr)] attempting to humanize [key_name_admin(Mo)].") - Mo.humanize() - - else if(href_list["corgione"]) - if(!check_rights(R_SPAWN)) - return - - var/mob/living/carbon/human/H = locate(href_list["corgione"]) - if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.", confidential = TRUE) - return - - log_admin("[key_name(usr)] attempting to corgize [key_name(H)].") - message_admins("[key_name_admin(usr)] attempting to corgize [key_name_admin(H)].") - H.corgize() - - - else if(href_list["forcespeech"]) - if(!check_rights(R_FUN)) - return - - var/mob/M = locate(href_list["forcespeech"]) - if(!ismob(M)) - to_chat(usr, "this can only be used on instances of type /mob.", confidential = TRUE) - - var/speech = input("What will [key_name(M)] say?", "Force speech", "")// Don't need to sanitize, since it does that in say(), we also trust our admins. - if(!speech) - return - M.say(speech, forced = "admin speech") - speech = sanitize(speech) // Nah, we don't trust them - log_admin("[key_name(usr)] forced [key_name(M)] to say: [speech]") - message_admins("[key_name_admin(usr)] forced [key_name_admin(M)] to say: [speech]") - - else if(href_list["sendtoprison"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/M = locate(href_list["sendtoprison"]) - if(!ismob(M)) - to_chat(usr, "This can only be used on instances of type /mob.", confidential = TRUE) - return - if(isAI(M)) - to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai.", confidential = TRUE) - return - - if(alert(usr, "Send [key_name(M)] to Prison?", "Message", "Yes", "No") != "Yes") - return - - M.forceMove(pick(GLOB.prisonwarp)) - to_chat(M, "You have been sent to Prison!", confidential = TRUE) - - log_admin("[key_name(usr)] has sent [key_name(M)] to Prison!") - message_admins("[key_name_admin(usr)] has sent [key_name_admin(M)] to Prison!") - - else if(href_list["sendbacktolobby"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/M = locate(href_list["sendbacktolobby"]) - - if(!isobserver(M)) - to_chat(usr, "You can only send ghost players back to the Lobby.", confidential = TRUE) - return - - if(!M.client) - to_chat(usr, "[M] doesn't seem to have an active client.", confidential = TRUE) - return - - if(alert(usr, "Send [key_name(M)] back to Lobby?", "Message", "Yes", "No") != "Yes") - return - - log_admin("[key_name(usr)] has sent [key_name(M)] back to the Lobby.") - message_admins("[key_name(usr)] has sent [key_name(M)] back to the Lobby.") - - var/mob/dead/new_player/NP = new() - NP.ckey = M.ckey - qdel(M) - - else if(href_list["tdome1"]) - if(!check_rights(R_FUN)) - return - - if(alert(usr, "Confirm?", "Message", "Yes", "No") != "Yes") - return - - var/mob/M = locate(href_list["tdome1"]) - if(!isliving(M)) - to_chat(usr, "This can only be used on instances of type /mob/living.", confidential = TRUE) - return - if(isAI(M)) - to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai.", confidential = TRUE) - return - var/mob/living/L = M - - for(var/obj/item/I in L) - L.dropItemToGround(I, TRUE) - - L.Unconscious(100) - sleep(5) - L.forceMove(pick(GLOB.tdome1)) - addtimer(CALLBACK(GLOBAL_PROC, /proc/to_chat, L, "You have been sent to the Thunderdome."), 5 SECONDS) - log_admin("[key_name(usr)] has sent [key_name(L)] to the thunderdome. (Team 1)") - message_admins("[key_name_admin(usr)] has sent [key_name_admin(L)] to the thunderdome. (Team 1)") - - else if(href_list["tdome2"]) - if(!check_rights(R_FUN)) - return - - if(alert(usr, "Confirm?", "Message", "Yes", "No") != "Yes") - return - - var/mob/M = locate(href_list["tdome2"]) - if(!isliving(M)) - to_chat(usr, "This can only be used on instances of type /mob/living.", confidential = TRUE) - return - if(isAI(M)) - to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai.", confidential = TRUE) - return - var/mob/living/L = M - - for(var/obj/item/I in L) - L.dropItemToGround(I, TRUE) - - L.Unconscious(100) - sleep(5) - L.forceMove(pick(GLOB.tdome2)) - addtimer(CALLBACK(GLOBAL_PROC, /proc/to_chat, L, "You have been sent to the Thunderdome."), 5 SECONDS) - log_admin("[key_name(usr)] has sent [key_name(L)] to the thunderdome. (Team 2)") - message_admins("[key_name_admin(usr)] has sent [key_name_admin(L)] to the thunderdome. (Team 2)") - - else if(href_list["tdomeadmin"]) - if(!check_rights(R_FUN)) - return - - if(alert(usr, "Confirm?", "Message", "Yes", "No") != "Yes") - return - - var/mob/M = locate(href_list["tdomeadmin"]) - if(!isliving(M)) - to_chat(usr, "This can only be used on instances of type /mob/living.", confidential = TRUE) - return - if(isAI(M)) - to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai.", confidential = TRUE) - return - var/mob/living/L = M - - L.Unconscious(100) - sleep(5) - L.forceMove(pick(GLOB.tdomeadmin)) - addtimer(CALLBACK(GLOBAL_PROC, /proc/to_chat, L, "You have been sent to the Thunderdome."), 5 SECONDS) - log_admin("[key_name(usr)] has sent [key_name(L)] to the thunderdome. (Admin.)") - message_admins("[key_name_admin(usr)] has sent [key_name_admin(L)] to the thunderdome. (Admin.)") - - else if(href_list["tdomeobserve"]) - if(!check_rights(R_FUN)) - return - - if(alert(usr, "Confirm?", "Message", "Yes", "No") != "Yes") - return - - var/mob/M = locate(href_list["tdomeobserve"]) - if(!isliving(M)) - to_chat(usr, "This can only be used on instances of type /mob/living.", confidential = TRUE) - return - if(isAI(M)) - to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai.", confidential = TRUE) - return - var/mob/living/L = M - - for(var/obj/item/I in L) - L.dropItemToGround(I, TRUE) - - if(ishuman(L)) - var/mob/living/carbon/human/observer = L - observer.equip_to_slot_or_del(new /obj/item/clothing/under/suit/black(observer), ITEM_SLOT_ICLOTHING) - observer.equip_to_slot_or_del(new /obj/item/clothing/shoes/sneakers/black(observer), ITEM_SLOT_FEET) - L.Unconscious(100) - sleep(5) - L.forceMove(pick(GLOB.tdomeobserve)) - addtimer(CALLBACK(GLOBAL_PROC, /proc/to_chat, L, "You have been sent to the Thunderdome."), 5 SECONDS) - log_admin("[key_name(usr)] has sent [key_name(L)] to the thunderdome. (Observer.)") - message_admins("[key_name_admin(usr)] has sent [key_name_admin(L)] to the thunderdome. (Observer.)") - - else if(href_list["revive"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/living/L = locate(href_list["revive"]) - if(!istype(L)) - to_chat(usr, "This can only be used on instances of type /mob/living.", confidential = TRUE) - return - - L.revive(full_heal = TRUE, admin_revive = TRUE) - message_admins("Admin [key_name_admin(usr)] healed / revived [key_name_admin(L)]!") - log_admin("[key_name(usr)] healed / Revived [key_name(L)].") - - else if(href_list["makeai"]) - if(!check_rights(R_SPAWN)) - return - - var/mob/living/carbon/human/H = locate(href_list["makeai"]) - if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.", confidential = TRUE) - return - - message_admins("Admin [key_name_admin(usr)] AIized [key_name_admin(H)]!") - log_admin("[key_name(usr)] AIized [key_name(H)].") - H.AIize(TRUE, H.client) - - else if(href_list["makealien"]) - if(!check_rights(R_SPAWN)) - return - - var/mob/living/carbon/human/H = locate(href_list["makealien"]) - if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.", confidential = TRUE) - return - - usr.client.cmd_admin_alienize(H) - - else if(href_list["makeslime"]) - if(!check_rights(R_SPAWN)) - return - - var/mob/living/carbon/human/H = locate(href_list["makeslime"]) - if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.", confidential = TRUE) - return - - usr.client.cmd_admin_slimeize(H) - - else if(href_list["makeblob"]) - if(!check_rights(R_SPAWN)) - return - - var/mob/living/carbon/human/H = locate(href_list["makeblob"]) - if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.", confidential = TRUE) - return - - usr.client.cmd_admin_blobize(H) - - - else if(href_list["makerobot"]) - if(!check_rights(R_SPAWN)) - return - - var/mob/living/carbon/human/H = locate(href_list["makerobot"]) - if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.", confidential = TRUE) - return - - usr.client.cmd_admin_robotize(H) - - else if(href_list["makeanimal"]) - if(!check_rights(R_SPAWN)) - return - - var/mob/M = locate(href_list["makeanimal"]) - if(isnewplayer(M)) - to_chat(usr, "This cannot be used on instances of type /mob/dead/new_player.", confidential = TRUE) - return - - usr.client.cmd_admin_animalize(M) - - else if(href_list["adminplayeropts"]) - var/mob/M = locate(href_list["adminplayeropts"]) - show_player_panel(M) - - else if(href_list["adminplayerobservefollow"]) - if(!isobserver(usr) && !check_rights(R_ADMIN)) - return - - var/atom/movable/AM = locate(href_list["adminplayerobservefollow"]) - - var/client/C = usr.client - var/can_ghost = TRUE - if(!isobserver(usr)) - can_ghost = C.admin_ghost() - - if(!can_ghost) - return - var/mob/dead/observer/A = C.mob - A.ManualFollow(AM) - - else if(href_list["admingetmovable"]) - if(!check_rights(R_ADMIN)) - return - - var/atom/movable/AM = locate(href_list["admingetmovable"]) - if(QDELETED(AM)) - return - AM.forceMove(get_turf(usr)) - - else if(href_list["adminplayerobservecoodjump"]) - if(!isobserver(usr) && !check_rights(R_ADMIN)) - return - - var/x = text2num(href_list["X"]) - var/y = text2num(href_list["Y"]) - var/z = text2num(href_list["Z"]) - - var/client/C = usr.client - if(!isobserver(usr)) - C.admin_ghost() - sleep(2) - C.jumptocoord(x,y,z) - - else if(href_list["adminchecklaws"]) - if(!check_rights(R_ADMIN)) - return - output_ai_laws() - - else if(href_list["admincheckdevilinfo"]) - if(!check_rights(R_ADMIN)) - return - var/mob/M = locate(href_list["admincheckdevilinfo"]) - output_devil_info(M) - - else if(href_list["adminmoreinfo"]) - var/mob/M = locate(href_list["adminmoreinfo"]) in GLOB.mob_list - if(!ismob(M)) - to_chat(usr, "This can only be used on instances of type /mob.", confidential = TRUE) - return - - var/location_description = "" - var/special_role_description = "" - var/health_description = "" - var/gender_description = "" - var/turf/T = get_turf(M) - - //Location - if(isturf(T)) - if(isarea(T.loc)) - location_description = "([M.loc == T ? "at coordinates " : "in [M.loc] at coordinates "] [T.x], [T.y], [T.z] in area [T.loc])" - else - location_description = "([M.loc == T ? "at coordinates " : "in [M.loc] at coordinates "] [T.x], [T.y], [T.z])" - - //Job + antagonist - if(M.mind) - special_role_description = "Role: [M.mind.assigned_role]; Antagonist: [M.mind.special_role]" - else - special_role_description = "Role: Mind datum missing Antagonist: Mind datum missing" - - //Health - if(isliving(M)) - var/mob/living/L = M - var/status - switch (M.stat) - if(CONSCIOUS) - status = "Alive" - if(SOFT_CRIT) - status = "Dying" - if(UNCONSCIOUS) - status = "[L.InCritical() ? "Unconscious and Dying" : "Unconscious"]" - if(DEAD) - status = "Dead" - health_description = "Status = [status]" - health_description += "
                    Oxy: [L.getOxyLoss()] - Tox: [L.getToxLoss()] - Fire: [L.getFireLoss()] - Brute: [L.getBruteLoss()] - Clone: [L.getCloneLoss()] - Brain: [L.getOrganLoss(ORGAN_SLOT_BRAIN)] - Stamina: [L.getStaminaLoss()]" - else - health_description = "This mob type has no health to speak of." - - //Gender - switch(M.gender) - if(MALE,FEMALE,PLURAL) - gender_description = "[M.gender]" - else - gender_description = "[M.gender]" - - to_chat(src.owner, "Info about [M.name]: ", confidential = TRUE) - to_chat(src.owner, "Mob type = [M.type]; Gender = [gender_description] Damage = [health_description]", confidential = TRUE) - to_chat(src.owner, "Name = [M.name]; Real_name = [M.real_name]; Mind_name = [M.mind?"[M.mind.name]":""]; Key = [M.key];", confidential = TRUE) - to_chat(src.owner, "Location = [location_description];", confidential = TRUE) - to_chat(src.owner, "[special_role_description]", confidential = TRUE) - to_chat(src.owner, ADMIN_FULLMONTY_NONAME(M), confidential = TRUE) - - else if(href_list["addjobslot"]) - if(!check_rights(R_ADMIN)) - return - - var/Add = href_list["addjobslot"] - - for(var/datum/job/job in SSjob.occupations) - if(job.title == Add) - job.total_positions += 1 - break - - src.manage_free_slots() - - - else if(href_list["customjobslot"]) - if(!check_rights(R_ADMIN)) - return - - var/Add = href_list["customjobslot"] - - for(var/datum/job/job in SSjob.occupations) - if(job.title == Add) - var/newtime = null - newtime = input(usr, "How many jebs do you want?", "Add wanted posters", "[newtime]") as num|null - if(!newtime) - to_chat(src.owner, "Setting to amount of positions filled for the job", confidential = TRUE) - job.total_positions = job.current_positions - break - job.total_positions = newtime - - src.manage_free_slots() - - else if(href_list["removejobslot"]) - if(!check_rights(R_ADMIN)) - return - - var/Remove = href_list["removejobslot"] - - for(var/datum/job/job in SSjob.occupations) - if(job.title == Remove && job.total_positions - job.current_positions > 0) - job.total_positions -= 1 - break - - src.manage_free_slots() - - else if(href_list["unlimitjobslot"]) - if(!check_rights(R_ADMIN)) - return - - var/Unlimit = href_list["unlimitjobslot"] - - for(var/datum/job/job in SSjob.occupations) - if(job.title == Unlimit) - job.total_positions = -1 - break - - src.manage_free_slots() - - else if(href_list["limitjobslot"]) - if(!check_rights(R_ADMIN)) - return - - var/Limit = href_list["limitjobslot"] - - for(var/datum/job/job in SSjob.occupations) - if(job.title == Limit) - job.total_positions = job.current_positions - break - - src.manage_free_slots() - - - else if(href_list["adminspawncookie"]) - if(!check_rights(R_ADMIN|R_FUN)) - return - - var/mob/living/carbon/human/H = locate(href_list["adminspawncookie"]) - if(!ishuman(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.", confidential = TRUE) - return - //let's keep it simple - //milk to plasmemes and skeletons, meat to lizards, electricity bars to ethereals, cookies to everyone else - var/cookiealt = /obj/item/reagent_containers/food/snacks/cookie - if(isskeleton(H)) - cookiealt = /obj/item/reagent_containers/food/condiment/milk - else if(isplasmaman(H)) - cookiealt = /obj/item/reagent_containers/food/condiment/milk - else if(isethereal(H)) - cookiealt = /obj/item/reagent_containers/food/snacks/energybar - else if(islizard(H)) - cookiealt = /obj/item/reagent_containers/food/snacks/meat/slab - var/obj/item/new_item = new cookiealt(H) - if(H.put_in_hands(new_item)) - H.update_inv_hands() - else - qdel(new_item) - log_admin("[key_name(H)] has their hands full, so they did not receive their [new_item.name], spawned by [key_name(src.owner)].") - message_admins("[key_name(H)] has their hands full, so they did not receive their [new_item.name], spawned by [key_name(src.owner)].") - return - - log_admin("[key_name(H)] got their [new_item], spawned by [key_name(src.owner)].") - message_admins("[key_name(H)] got their [new_item], spawned by [key_name(src.owner)].") - SSblackbox.record_feedback("amount", "admin_cookies_spawned", 1) - to_chat(H, "Your prayers have been answered!! You received the best [new_item.name]!", confidential = TRUE) - SEND_SOUND(H, sound('sound/effects/pray_chaplain.ogg')) - - else if(href_list["adminsmite"]) - if(!check_rights(R_ADMIN|R_FUN)) - return - - var/mob/living/carbon/human/H = locate(href_list["adminsmite"]) in GLOB.mob_list - if(!H || !istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human", confidential = TRUE) - return - - usr.client.smite(H) - - else if(href_list["CentComReply"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/M = locate(href_list["CentComReply"]) - usr.client.admin_headset_message(M, RADIO_CHANNEL_CENTCOM) - - else if(href_list["SyndicateReply"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/M = locate(href_list["SyndicateReply"]) - usr.client.admin_headset_message(M, RADIO_CHANNEL_SYNDICATE) - - else if(href_list["HeadsetMessage"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/M = locate(href_list["HeadsetMessage"]) - usr.client.admin_headset_message(M) - - else if(href_list["reject_custom_name"]) - if(!check_rights(R_ADMIN)) - return - var/obj/item/station_charter/charter = locate(href_list["reject_custom_name"]) - if(istype(charter)) - charter.reject_proposed(usr) - else if(href_list["jumpto"]) - if(!isobserver(usr) && !check_rights(R_ADMIN)) - return - - var/mob/M = locate(href_list["jumpto"]) - usr.client.jumptomob(M) - - else if(href_list["getmob"]) - if(!check_rights(R_ADMIN)) - return - - if(alert(usr, "Confirm?", "Message", "Yes", "No") != "Yes") - return - var/mob/M = locate(href_list["getmob"]) - usr.client.Getmob(M) - - else if(href_list["sendmob"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/M = locate(href_list["sendmob"]) - usr.client.sendmob(M) - - else if(href_list["narrateto"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/M = locate(href_list["narrateto"]) - usr.client.cmd_admin_direct_narrate(M) - - else if(href_list["subtlemessage"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/M = locate(href_list["subtlemessage"]) - usr.client.cmd_admin_subtle_message(M) - - else if(href_list["playsoundto"]) - if(!check_rights(R_SOUND)) - return - - var/mob/M = locate(href_list["playsoundto"]) - var/S = input("", "Select a sound file",) as null|sound - if(S) - usr.client.play_direct_mob_sound(S, M) - - else if(href_list["individuallog"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/M = locate(href_list["individuallog"]) in GLOB.mob_list - if(!ismob(M)) - to_chat(usr, "This can only be used on instances of type /mob.", confidential = TRUE) - return - - show_individual_logging_panel(M, href_list["log_src"], href_list["log_type"]) - else if(href_list["languagemenu"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/M = locate(href_list["languagemenu"]) in GLOB.mob_list - if(!ismob(M)) - to_chat(usr, "This can only be used on instances of type /mob.", confidential = TRUE) - return - var/datum/language_holder/H = M.get_language_holder() - H.open_language_menu(usr) - - else if(href_list["traitor"]) - if(!check_rights(R_ADMIN)) - return - - if(!SSticker.HasRoundStarted()) - alert("The game hasn't started yet!") - return - - var/mob/M = locate(href_list["traitor"]) - if(!ismob(M)) - var/datum/mind/D = M - if(!istype(D)) - to_chat(usr, "This can only be used on instances of type /mob and /mind", confidential = TRUE) - return - else - D.traitor_panel() - else - show_traitor_panel(M) - - else if(href_list["borgpanel"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/M = locate(href_list["borgpanel"]) - if(!iscyborg(M)) - to_chat(usr, "This can only be used on cyborgs", confidential = TRUE) - else - open_borgopanel(M) - - else if(href_list["initmind"]) - if(!check_rights(R_ADMIN)) - return - var/mob/M = locate(href_list["initmind"]) - if(!ismob(M) || M.mind) - to_chat(usr, "This can only be used on instances on mindless mobs", confidential = TRUE) - return - M.mind_initialize() - - else if(href_list["create_object"]) - if(!check_rights(R_SPAWN)) - return - return create_object(usr) - - else if(href_list["quick_create_object"]) - if(!check_rights(R_SPAWN)) - return - return quick_create_object(usr) - - else if(href_list["create_turf"]) - if(!check_rights(R_SPAWN)) - return - return create_turf(usr) - - else if(href_list["create_mob"]) - if(!check_rights(R_SPAWN)) - return - return create_mob(usr) - - else if(href_list["dupe_marked_datum"]) - if(!check_rights(R_SPAWN)) - return - return DuplicateObject(marked_datum, perfectcopy=1, newloc=get_turf(usr)) - - else if(href_list["object_list"]) //this is the laggiest thing ever - if(!check_rights(R_SPAWN)) - return - - var/atom/loc = usr.loc - - var/dirty_paths - if (istext(href_list["object_list"])) - dirty_paths = list(href_list["object_list"]) - else if (istype(href_list["object_list"], /list)) - dirty_paths = href_list["object_list"] - - var/paths = list() - - for(var/dirty_path in dirty_paths) - var/path = text2path(dirty_path) - if(!path) - continue - else if(!ispath(path, /obj) && !ispath(path, /turf) && !ispath(path, /mob)) - continue - paths += path - - if(!paths) - alert("The path list you sent is empty.") - return - if(length(paths) > 5) - alert("Select fewer object types, (max 5).") - return - - var/list/offset = splittext(href_list["offset"],",") - var/number = clamp(text2num(href_list["object_count"]), 1, ADMIN_SPAWN_CAP) - var/X = offset.len > 0 ? text2num(offset[1]) : 0 - var/Y = offset.len > 1 ? text2num(offset[2]) : 0 - var/Z = offset.len > 2 ? text2num(offset[3]) : 0 - var/obj_dir = text2num(href_list["object_dir"]) - if(obj_dir && !(obj_dir in list(1,2,4,8,5,6,9,10))) - obj_dir = null - var/obj_name = sanitize(href_list["object_name"]) - - - var/atom/target //Where the object will be spawned - var/where = href_list["object_where"] - if (!( where in list("onfloor","frompod","inhand","inmarked") )) - where = "onfloor" - - - switch(where) - if("inhand") - if (!iscarbon(usr) && !iscyborg(usr)) - to_chat(usr, "Can only spawn in hand when you're a carbon mob or cyborg.", confidential = TRUE) - where = "onfloor" - target = usr - - if("onfloor", "frompod") - switch(href_list["offset_type"]) - if ("absolute") - target = locate(0 + X,0 + Y,0 + Z) - if ("relative") - target = locate(loc.x + X,loc.y + Y,loc.z + Z) - if("inmarked") - if(!marked_datum) - to_chat(usr, "You don't have any object marked. Abandoning spawn.", confidential = TRUE) - return - else if(!istype(marked_datum, /atom)) - to_chat(usr, "The object you have marked cannot be used as a target. Target must be of type /atom. Abandoning spawn.", confidential = TRUE) - return - else - target = marked_datum - - var/obj/structure/closet/supplypod/centcompod/pod - - if(target) - if(where == "frompod") - pod = new() - - for (var/path in paths) - for (var/i = 0; i < number; i++) - if(path in typesof(/turf)) - var/turf/O = target - var/turf/N = O.ChangeTurf(path) - if(N && obj_name) - N.name = obj_name - else - var/atom/O - if(where == "frompod") - O = new path(pod) - else - O = new path(target) - - if(!QDELETED(O)) - O.flags_1 |= ADMIN_SPAWNED_1 - if(obj_dir) - O.setDir(obj_dir) - if(obj_name) - O.name = obj_name - if(ismob(O)) - var/mob/M = O - M.real_name = obj_name - if(where == "inhand" && isliving(usr) && isitem(O)) - var/mob/living/L = usr - var/obj/item/I = O - L.put_in_hands(I) - if(iscyborg(L)) - var/mob/living/silicon/robot/R = L - if(R.module) - R.module.add_module(I, TRUE, TRUE) - R.activate_module(I) - - if(pod) - new /obj/effect/DPtarget(target, pod) - - if (number == 1) - log_admin("[key_name(usr)] created a [english_list(paths)]") - for(var/path in paths) - if(ispath(path, /mob)) - message_admins("[key_name_admin(usr)] created a [english_list(paths)]") - break - else - log_admin("[key_name(usr)] created [number]ea [english_list(paths)]") - for(var/path in paths) - if(ispath(path, /mob)) - message_admins("[key_name_admin(usr)] created [number]ea [english_list(paths)]") - 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 - src.admincaster_screen = 18 //The ac_ prefix before the hrefs stands for AdminCaster. - src.access_news_network() - - else if(href_list["ac_set_channel_name"]) - if(!check_rights(R_ADMIN)) - return - src.admincaster_feed_channel.channel_name = stripped_input(usr, "Provide a Feed Channel Name.", "Network Channel Handler", "") - src.access_news_network() - - else if(href_list["ac_set_channel_lock"]) - if(!check_rights(R_ADMIN)) - return - src.admincaster_feed_channel.locked = !src.admincaster_feed_channel.locked - src.access_news_network() - - else if(href_list["ac_submit_new_channel"]) - if(!check_rights(R_ADMIN)) - return - var/check = 0 - for(var/datum/newscaster/feed_channel/FC in GLOB.news_network.network_channels) - if(FC.channel_name == src.admincaster_feed_channel.channel_name) - check = 1 - break - if(src.admincaster_feed_channel.channel_name == "" || src.admincaster_feed_channel.channel_name == "\[REDACTED\]" || check ) - src.admincaster_screen=7 - else - var/choice = alert("Please confirm Feed channel creation.","Network Channel Handler","Confirm","Cancel") - if(choice=="Confirm") - GLOB.news_network.CreateFeedChannel(src.admincaster_feed_channel.channel_name, src.admin_signature, src.admincaster_feed_channel.locked, 1) - SSblackbox.record_feedback("tally", "newscaster_channels", 1, src.admincaster_feed_channel.channel_name) - log_admin("[key_name(usr)] created command feed channel: [src.admincaster_feed_channel.channel_name]!") - src.admincaster_screen=5 - src.access_news_network() - - else if(href_list["ac_set_channel_receiving"]) - if(!check_rights(R_ADMIN)) - return - var/list/available_channels = list() - for(var/datum/newscaster/feed_channel/F in GLOB.news_network.network_channels) - available_channels += F.channel_name - src.admincaster_feed_channel.channel_name = adminscrub(input(usr, "Choose receiving Feed Channel.", "Network Channel Handler") in sortList(available_channels) ) - src.access_news_network() - - else if(href_list["ac_set_new_message"]) - if(!check_rights(R_ADMIN)) - return - src.admincaster_feed_message.body = adminscrub(stripped_input(usr, "Write your Feed story.", "Network Channel Handler", "")) - src.access_news_network() - - else if(href_list["ac_submit_new_message"]) - if(!check_rights(R_ADMIN)) - return - if(src.admincaster_feed_message.returnBody(-1) =="" || src.admincaster_feed_message.returnBody(-1) =="\[REDACTED\]" || src.admincaster_feed_channel.channel_name == "" ) - src.admincaster_screen = 6 - else - GLOB.news_network.SubmitArticle(src.admincaster_feed_message.returnBody(-1), src.admin_signature, src.admincaster_feed_channel.channel_name, null, 1) - SSblackbox.record_feedback("amount", "newscaster_stories", 1) - src.admincaster_screen=4 - - for(var/obj/machinery/newscaster/NEWSCASTER in GLOB.allCasters) - NEWSCASTER.newsAlert(src.admincaster_feed_channel.channel_name) - - log_admin("[key_name(usr)] submitted a feed story to channel: [src.admincaster_feed_channel.channel_name]!") - src.access_news_network() - - else if(href_list["ac_create_channel"]) - if(!check_rights(R_ADMIN)) - return - src.admincaster_screen=2 - src.access_news_network() - - else if(href_list["ac_create_feed_story"]) - if(!check_rights(R_ADMIN)) - return - src.admincaster_screen=3 - src.access_news_network() - - else if(href_list["ac_menu_censor_story"]) - if(!check_rights(R_ADMIN)) - return - src.admincaster_screen=10 - src.access_news_network() - - else if(href_list["ac_menu_censor_channel"]) - if(!check_rights(R_ADMIN)) - return - src.admincaster_screen=11 - src.access_news_network() - - else if(href_list["ac_menu_wanted"]) - if(!check_rights(R_ADMIN)) - return - var/already_wanted = 0 - if(GLOB.news_network.wanted_issue.active) - already_wanted = 1 - - if(already_wanted) - src.admincaster_wanted_message.criminal = GLOB.news_network.wanted_issue.criminal - src.admincaster_wanted_message.body = GLOB.news_network.wanted_issue.body - src.admincaster_screen = 14 - src.access_news_network() - - else if(href_list["ac_set_wanted_name"]) - if(!check_rights(R_ADMIN)) - return - src.admincaster_wanted_message.criminal = adminscrub(stripped_input(usr, "Provide the name of the Wanted person.", "Network Security Handler", "")) - src.access_news_network() - - else if(href_list["ac_set_wanted_desc"]) - if(!check_rights(R_ADMIN)) - return - src.admincaster_wanted_message.body = adminscrub(stripped_input(usr, "Provide the a description of the Wanted person and any other details you deem important.", "Network Security Handler", "")) - src.access_news_network() - - else if(href_list["ac_submit_wanted"]) - if(!check_rights(R_ADMIN)) - return - var/input_param = text2num(href_list["ac_submit_wanted"]) - if(src.admincaster_wanted_message.criminal == "" || src.admincaster_wanted_message.body == "") - src.admincaster_screen = 16 - else - var/choice = alert("Please confirm Wanted Issue [(input_param==1) ? ("creation.") : ("edit.")]","Network Security Handler","Confirm","Cancel") - if(choice=="Confirm") - if(input_param==1) //If input_param == 1 we're submitting a new wanted issue. At 2 we're just editing an existing one. See the else below - GLOB.news_network.submitWanted(admincaster_wanted_message.criminal, admincaster_wanted_message.body, admin_signature, null, 1, 1) - src.admincaster_screen = 15 - else - GLOB.news_network.submitWanted(admincaster_wanted_message.criminal, admincaster_wanted_message.body, admin_signature) - src.admincaster_screen = 19 - log_admin("[key_name(usr)] issued a Station-wide Wanted Notification for [src.admincaster_wanted_message.criminal]!") - src.access_news_network() - - else if(href_list["ac_cancel_wanted"]) - if(!check_rights(R_ADMIN)) - return - var/choice = alert("Please confirm Wanted Issue removal.","Network Security Handler","Confirm","Cancel") - if(choice=="Confirm") - GLOB.news_network.deleteWanted() - src.admincaster_screen=17 - src.access_news_network() - - else if(href_list["ac_censor_channel_author"]) - if(!check_rights(R_ADMIN)) - return - var/datum/newscaster/feed_channel/FC = locate(href_list["ac_censor_channel_author"]) - FC.toggleCensorAuthor() - src.access_news_network() - - else if(href_list["ac_censor_channel_story_author"]) - if(!check_rights(R_ADMIN)) - return - var/datum/newscaster/feed_message/MSG = locate(href_list["ac_censor_channel_story_author"]) - MSG.toggleCensorAuthor() - src.access_news_network() - - else if(href_list["ac_censor_channel_story_body"]) - if(!check_rights(R_ADMIN)) - return - var/datum/newscaster/feed_message/MSG = locate(href_list["ac_censor_channel_story_body"]) - MSG.toggleCensorBody() - src.access_news_network() - - else if(href_list["ac_pick_d_notice"]) - if(!check_rights(R_ADMIN)) - return - var/datum/newscaster/feed_channel/FC = locate(href_list["ac_pick_d_notice"]) - src.admincaster_feed_channel = FC - src.admincaster_screen=13 - src.access_news_network() - - else if(href_list["ac_toggle_d_notice"]) - if(!check_rights(R_ADMIN)) - return - var/datum/newscaster/feed_channel/FC = locate(href_list["ac_toggle_d_notice"]) - FC.toggleCensorDclass() - src.access_news_network() - - else if(href_list["ac_view"]) - if(!check_rights(R_ADMIN)) - return - src.admincaster_screen=1 - src.access_news_network() - - else if(href_list["ac_setScreen"]) //Brings us to the main menu and resets all fields~ - if(!check_rights(R_ADMIN)) - return - src.admincaster_screen = text2num(href_list["ac_setScreen"]) - if (src.admincaster_screen == 0) - if(src.admincaster_feed_channel) - src.admincaster_feed_channel = new /datum/newscaster/feed_channel - if(src.admincaster_feed_message) - src.admincaster_feed_message = new /datum/newscaster/feed_message - if(admincaster_wanted_message) - admincaster_wanted_message = new /datum/newscaster/wanted_message - src.access_news_network() - - else if(href_list["ac_show_channel"]) - if(!check_rights(R_ADMIN)) - return - var/datum/newscaster/feed_channel/FC = locate(href_list["ac_show_channel"]) - src.admincaster_feed_channel = FC - src.admincaster_screen = 9 - src.access_news_network() - - else if(href_list["ac_pick_censor_channel"]) - if(!check_rights(R_ADMIN)) - return - var/datum/newscaster/feed_channel/FC = locate(href_list["ac_pick_censor_channel"]) - src.admincaster_feed_channel = FC - src.admincaster_screen = 12 - src.access_news_network() - - else if(href_list["ac_refresh"]) - if(!check_rights(R_ADMIN)) - return - src.access_news_network() - - else if(href_list["ac_set_signature"]) - if(!check_rights(R_ADMIN)) - return - src.admin_signature = adminscrub(input(usr, "Provide your desired signature.", "Network Identity Handler", "")) - src.access_news_network() - - else if(href_list["ac_del_comment"]) - if(!check_rights(R_ADMIN)) - return - var/datum/newscaster/feed_comment/FC = locate(href_list["ac_del_comment"]) - var/datum/newscaster/feed_message/FM = locate(href_list["ac_del_comment_msg"]) - FM.comments -= FC - qdel(FC) - src.access_news_network() - - else if(href_list["ac_lock_comment"]) - if(!check_rights(R_ADMIN)) - return - var/datum/newscaster/feed_message/FM = locate(href_list["ac_lock_comment"]) - FM.locked ^= 1 - src.access_news_network() - - else if(href_list["check_antagonist"]) - if(!check_rights(R_ADMIN)) - return - usr.client.check_antagonists() - - else if(href_list["kick_all_from_lobby"]) - if(!check_rights(R_ADMIN)) - return - if(SSticker.IsRoundInProgress()) - var/afkonly = text2num(href_list["afkonly"]) - if(alert("Are you sure you want to kick all [afkonly ? "AFK" : ""] clients from the lobby??","Message","Yes","Cancel") != "Yes") - to_chat(usr, "Kick clients from lobby aborted", confidential = TRUE) - return - var/list/listkicked = kick_clients_in_lobby("You were kicked from the lobby by [usr.client.holder.fakekey ? "an Administrator" : "[usr.client.key]"].", afkonly) - - var/strkicked = "" - for(var/name in listkicked) - strkicked += "[name], " - message_admins("[key_name_admin(usr)] has kicked [afkonly ? "all AFK" : "all"] clients from the lobby. [length(listkicked)] clients kicked: [strkicked ? strkicked : "--"]") - log_admin("[key_name(usr)] has kicked [afkonly ? "all AFK" : "all"] clients from the lobby. [length(listkicked)] clients kicked: [strkicked ? strkicked : "--"]") - else - to_chat(usr, "You may only use this when the game is running.", confidential = TRUE) - - else if(href_list["create_outfit_finalize"]) - if(!check_rights(R_ADMIN)) - return - create_outfit_finalize(usr,href_list) - else if(href_list["load_outfit"]) - if(!check_rights(R_ADMIN)) - return - load_outfit(usr) - else if(href_list["create_outfit_menu"]) - if(!check_rights(R_ADMIN)) - return - create_outfit(usr) - else if(href_list["delete_outfit"]) - if(!check_rights(R_ADMIN)) - return - var/datum/outfit/O = locate(href_list["chosen_outfit"]) in GLOB.custom_outfits - delete_outfit(usr,O) - else if(href_list["save_outfit"]) - if(!check_rights(R_ADMIN)) - return - var/datum/outfit/O = locate(href_list["chosen_outfit"]) in GLOB.custom_outfits - save_outfit(usr,O) - else if(href_list["set_selfdestruct_code"]) - if(!check_rights(R_ADMIN)) - return - var/code = random_nukecode() - for(var/obj/machinery/nuclearbomb/selfdestruct/SD in GLOB.nuke_list) - SD.r_code = code - message_admins("[key_name_admin(usr)] has set the self-destruct \ - code to \"[code]\".") - - else if(href_list["add_station_goal"]) - if(!check_rights(R_ADMIN)) - return - var/list/type_choices = typesof(/datum/station_goal) - var/picked = input("Choose goal type") in type_choices|null - if(!picked) - return - var/datum/station_goal/G = new picked() - if(picked == /datum/station_goal) - var/newname = input("Enter goal name:") as text|null - if(!newname) - return - G.name = newname - var/description = input("Enter CentCom message contents:") as message|null - if(!description) - return - G.report_message = description - message_admins("[key_name(usr)] created \"[G.name]\" station goal.") - SSticker.mode.station_goals += G - modify_goals() - - else if(href_list["viewruntime"]) - var/datum/error_viewer/error_viewer = locate(href_list["viewruntime"]) - if(!istype(error_viewer)) - to_chat(usr, "That runtime viewer no longer exists.", confidential = TRUE) - return - - if(href_list["viewruntime_backto"]) - error_viewer.show_to(owner, locate(href_list["viewruntime_backto"]), href_list["viewruntime_linear"]) - else - error_viewer.show_to(owner, null, href_list["viewruntime_linear"]) - - else if(href_list["showrelatedacc"]) - if(!check_rights(R_ADMIN)) - return - var/client/C = locate(href_list["client"]) in GLOB.clients - var/thing_to_check - if(href_list["showrelatedacc"] == "cid") - thing_to_check = C.related_accounts_cid - else - thing_to_check = C.related_accounts_ip - thing_to_check = splittext(thing_to_check, ", ") - - - var/list/dat = list() - dat += thing_to_check - -// usr << browse(dat.Join("
                    "), "window=related_[C];size=420x300") - - var/datum/browser/popup = new(usr, "related_[C]", "Related accounts by [uppertext(href_list["showrelatedacc"])]:", 425, 300) - popup.set_content(dat.Join("
                    ")) - popup.open() - - else if(href_list["centcomlookup"]) - if(!check_rights(R_ADMIN)) - return - - if(!CONFIG_GET(string/centcom_ban_db)) - to_chat(usr, "Centcom Galactic Ban DB is disabled!") - return - - var/ckey = href_list["centcomlookup"] - - // Make the request - var/datum/http_request/request = new() - request.prepare(RUSTG_HTTP_METHOD_GET, "[CONFIG_GET(string/centcom_ban_db)]/[ckey]", "", "") - request.begin_async() - UNTIL(request.is_complete() || !usr) - if (!usr) - return - var/datum/http_response/response = request.into_response() - - var/list/bans - - var/list/dat = list("") - - if(response.errored) - dat += "
                    Failed to connect to CentCom." - else if(response.status_code != 200) - dat += "
                    Failed to connect to CentCom. Status code: [response.status_code]" - else - if(response.body == "[]") - dat += "
                    0 bans detected for [ckey]
                    " - else - bans = json_decode(response["body"]) - dat += "
                    [bans.len] ban\s detected for [ckey]
                    " - for(var/list/ban in bans) - dat += "Server: [sanitize(ban["sourceName"])]
                    " - dat += "RP Level: [sanitize(ban["sourceRoleplayLevel"])]
                    " - dat += "Type: [sanitize(ban["type"])]
                    " - dat += "Banned By: [sanitize(ban["bannedBy"])]
                    " - dat += "Reason: [sanitize(ban["reason"])]
                    " - dat += "Datetime: [sanitize(ban["bannedOn"])]
                    " - var/expiration = ban["expires"] - dat += "Expires: [expiration ? "[sanitize(expiration)]" : "Permanent"]
                    " - if(ban["type"] == "job") - dat += "Jobs: " - var/list/jobs = ban["jobs"] - dat += sanitize(jobs.Join(", ")) - dat += "
                    " - dat += "
                    " - - dat += "
                    " - var/datum/browser/popup = new(usr, "centcomlookup-[ckey]", "
                    Central Command Galactic Ban Database
                    ", 700, 600) - popup.set_content(dat.Join()) - popup.open(0) - - else if(href_list["modantagrep"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/M = locate(href_list["mob"]) in GLOB.mob_list - var/client/C = M.client - usr.client.cmd_admin_mod_antag_rep(C, href_list["modantagrep"]) - show_player_panel(M) - - else if(href_list["slowquery"]) - if(!check_rights(R_ADMIN)) - return - var/answer = href_list["slowquery"] - if(answer == "yes") - log_query_debug("[usr.key] | Reported a server hang") - if(alert(usr, "Had you just press any admin buttons?", "Query server hang report", "Yes", "No") == "Yes") - var/response = input(usr,"What were you just doing?","Query server hang report") as null|text - if(response) - log_query_debug("[usr.key] | [response]") - else if(answer == "no") - log_query_debug("[usr.key] | Reported no server hang") - - else if(href_list["ctf_toggle"]) - if(!check_rights(R_ADMIN)) - return - toggle_all_ctf(usr) - - else if(href_list["rebootworld"]) - if(!check_rights(R_ADMIN)) - return - var/confirm = alert("Are you sure you want to reboot the server?", "Confirm Reboot", "Yes", "No") - if(confirm == "No") - return - if(confirm == "Yes") - restart() - - else if(href_list["check_teams"]) - if(!check_rights(R_ADMIN)) - return - check_teams() - - else if(href_list["team_command"]) - if(!check_rights(R_ADMIN)) - return - switch(href_list["team_command"]) - if("create_team") - admin_create_team(usr) - if("rename_team") - var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams - if(T) - T.admin_rename(usr) - if("communicate") - var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams - if(T) - T.admin_communicate(usr) - if("delete_team") - var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams - if(T) - T.admin_delete(usr) - if("add_objective") - var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams - if(T) - T.admin_add_objective(usr) - if("remove_objective") - var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams - if(!T) - return - var/datum/objective/O = locate(href_list["tobjective"]) in T.objectives - if(O) - T.admin_remove_objective(usr,O) - if("add_member") - var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams - if(T) - T.admin_add_member(usr) - if("remove_member") - var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams - if(!T) - return - var/datum/mind/M = locate(href_list["tmember"]) in T.members - if(M) - T.admin_remove_member(usr,M) - check_teams() - - else if(href_list["newbankey"]) - var/player_key = href_list["newbankey"] - var/player_ip = href_list["newbanip"] - var/player_cid = href_list["newbancid"] - ban_panel(player_key, player_ip, player_cid) - - else if(href_list["intervaltype"]) //check for ban panel, intervaltype is used as it's the only value which will always be present - if(href_list["roleban_delimiter"]) - ban_parse_href(href_list) - else - ban_parse_href(href_list, TRUE) - - else if(href_list["searchunbankey"] || href_list["searchunbanadminkey"] || href_list["searchunbanip"] || href_list["searchunbancid"]) - var/player_key = href_list["searchunbankey"] - var/admin_key = href_list["searchunbanadminkey"] - var/player_ip = href_list["searchunbanip"] - var/player_cid = href_list["searchunbancid"] - unban_panel(player_key, admin_key, player_ip, player_cid) - - else if(href_list["unbanpagecount"]) - var/page = href_list["unbanpagecount"] - var/player_key = href_list["unbankey"] - var/admin_key = href_list["unbanadminkey"] - var/player_ip = href_list["unbanip"] - var/player_cid = href_list["unbancid"] - unban_panel(player_key, admin_key, player_ip, player_cid, page) - - else if(href_list["editbanid"]) - var/edit_id = href_list["editbanid"] - var/player_key = href_list["editbankey"] - var/player_ip = href_list["editbanip"] - var/player_cid = href_list["editbancid"] - var/role = href_list["editbanrole"] - var/duration = href_list["editbanduration"] - var/applies_to_admins = text2num(href_list["editbanadmins"]) - var/reason = url_decode(href_list["editbanreason"]) - var/page = href_list["editbanpage"] - var/admin_key = href_list["editbanadminkey"] - ban_panel(player_key, player_ip, player_cid, role, duration, applies_to_admins, reason, edit_id, page, admin_key) - - else if(href_list["unbanid"]) - var/ban_id = href_list["unbanid"] - var/player_key = href_list["unbankey"] - var/player_ip = href_list["unbanip"] - var/player_cid = href_list["unbancid"] - var/role = href_list["unbanrole"] - var/page = href_list["unbanpage"] - var/admin_key = href_list["unbanadminkey"] - unban(ban_id, player_key, player_ip, player_cid, role, page, admin_key) - - else if(href_list["unbanlog"]) - var/ban_id = href_list["unbanlog"] - ban_log(ban_id) - - else if(href_list["beakerpanel"]) - beaker_panel_act(href_list) - - else if(href_list["reloadpolls"]) - GLOB.polls.Cut() - GLOB.poll_options.Cut() - load_poll_data() - poll_list_panel() - - else if(href_list["newpoll"]) - poll_management_panel() - - else if(href_list["editpoll"]) - var/datum/poll_question/poll = locate(href_list["editpoll"]) in GLOB.polls - poll_management_panel(poll) - - else if(href_list["deletepoll"]) - var/datum/poll_question/poll = locate(href_list["deletepoll"]) in GLOB.polls - poll.delete_poll() - poll_list_panel() - - else if(href_list["initializepoll"]) - poll_parse_href(href_list) - - else if(href_list["submitpoll"]) - var/datum/poll_question/poll = locate(href_list["submitpoll"]) in GLOB.polls - poll_parse_href(href_list, poll) - - else if(href_list["clearpollvotes"]) - var/datum/poll_question/poll = locate(href_list["clearpollvotes"]) in GLOB.polls - poll.clear_poll_votes() - poll_management_panel(poll) - - else if(href_list["addpolloption"]) - var/datum/poll_question/poll = locate(href_list["addpolloption"]) in GLOB.polls - poll_option_panel(poll) - - else if(href_list["editpolloption"]) - var/datum/poll_option/option = locate(href_list["editpolloption"]) in GLOB.poll_options - var/datum/poll_question/poll = locate(href_list["parentpoll"]) in GLOB.polls - poll_option_panel(poll, option) - - else if(href_list["deletepolloption"]) - var/datum/poll_option/option = locate(href_list["deletepolloption"]) in GLOB.poll_options - var/datum/poll_question/poll = option.delete_option() - poll_management_panel(poll) - - else if(href_list["submitoption"]) - var/datum/poll_option/option = locate(href_list["submitoption"]) in GLOB.poll_options - var/datum/poll_question/poll = locate(href_list["submitoptionpoll"]) in GLOB.polls - poll_option_parse_href(href_list, poll, option) - - //Topics relating to Faxes - else if(href_list["AdminFaxCreate"]) - if(!check_rights(R_FUN)) - return - - var/mob/sender = locate(href_list["AdminFaxCreate"]) - var/obj/machinery/photocopier/faxmachine/fax = locate(href_list["originfax"]) - var/faxtype = href_list["faxtype"] - var/reply_to = locate(href_list["replyto"]) - var/destination - var/notify - - var/obj/item/paper/P = new /obj/item/paper(null) //hopefully the null loc won't cause trouble for us - - if(!fax) - var/list/departmentoptions = GLOB.alldepartments + "All Departments" - destination = input(usr, "To which department?", "Choose a department", "") as null|anything in departmentoptions - if(!destination) - qdel(P) - return - - for(var/thing in GLOB.allfaxes) - var/obj/machinery/photocopier/faxmachine/F = thing - if(destination != "All Departments" && F.department == destination) - fax = F - - - var/input_text = input(src.owner, "Please enter a message to send a fax via secure connection. Use
                    for line breaks. Both pencode and HTML work.", "Outgoing message from CentCom", "") as message|null - if(!input_text) - qdel(P) - - var/customname = input(src.owner, "Pick a title for the fax.", "Fax Title") as text|null - if(!customname) - customname = "paper" - - var/sendername - switch(faxtype) - if("Central Command") - sendername = "Central Command" - if("Syndicate") - sendername = "UNKNOWN" - if("Custom") - sendername = input(owner, "What organization does the fax come from? This determines the prefix of the paper (i.e. Central Command- Title). This is optional.", "Organization") as text|null - - if(sender) - notify = alert(owner, "Would you like to inform the original sender that a fax has arrived?","Notify Sender","Yes","No") - - // Create the reply message - if(sendername) - P.name = "[sendername]- [customname]" - else - P.name = "[customname]" - P.info = input_text - P.update_icon() - P.x = rand(-2, 0) - P.y = rand(-1, 2) - - if(destination != "All Departments") - if(fax.receivefax(P) == FALSE) - to_chat(owner, "Message transmission failed.") - return - else - for(var/thing in GLOB.allfaxes) - var/obj/machinery/photocopier/faxmachine/F = thing - if(F.z in SSmapping.levels_by_trait(ZTRAIT_STATION)) - addtimer(CALLBACK(src, .proc/handle_sendall, F, P), 0) - - var/datum/fax/admin/A = new /datum/fax/admin() - A.name = P.name - A.from_department = faxtype - if(destination != "All Departments") - A.to_department = fax.department - else - A.to_department = "All Departments" - A.origin = "Custom" - A.message = P - A.reply_to = reply_to - A.sent_by = usr - A.sent_at = world.time - - to_chat(src.owner, "Message transmitted successfully.") - if(notify == "Yes") - var/mob/living/carbon/human/H = sender - if(istype(H) && H.stat == CONSCIOUS && (istype(H.ears, /obj/item/radio/headset))) - to_chat(sender, "Your headset pings, notifying you that a reply to your fax has arrived.") - if(sender) - log_admin("[key_name(src.owner)] replied to a fax message from [key_name(sender)]: [input_text]") - message_admins("[key_name_admin(src.owner)] replied to a fax message from [key_name_admin(sender)] (VIEW).", 1) - else - log_admin("[key_name(src.owner)] sent a fax message to [destination]: [input_text]") - message_admins("[key_name_admin(src.owner)] sent a fax message to [destination] (VIEW).", 1) - return - - else if(href_list["refreshfaxpanel"]) - if(!check_rights(R_FUN)) - return - - fax_panel(usr) - return - - else if(href_list["EvilFax"]) - if(!check_rights(R_FUN)) - return - var/mob/living/carbon/human/H = locate(href_list["EvilFax"]) - if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.") - return - var/etypes = list("Borgification","Corgification","Death By Fire","Demotion Notice") - var/eviltype = input(src.owner, "Which type of evil fax do you wish to send [H]?","Its good to be baaaad...", "") as null|anything in etypes - if(!(eviltype in etypes)) - return - var/customname = input(src.owner, "Pick a title for the evil fax.", "Fax Title") as text|null - if(!customname) - customname = "paper" - var/obj/item/paper/evilfax/P = new /obj/item/paper/evilfax(null) - var/obj/machinery/photocopier/faxmachine/fax = locate(href_list["originfax"]) - - P.name = "Central Command - [customname]" - P.info = "You really should've known better." - P.myeffect = eviltype - P.mytarget = H - if(alert("Do you want the Evil Fax to activate automatically if [H] tries to ignore it?",,"Yes", "No") == "Yes") - P.activate_on_timeout = TRUE - P.x = rand(-2, 0) - P.y = rand(-1, 2) - P.update_icon() - //we have to physically teleport the fax paper - fax.handle_animation() - P.forceMove(fax.loc) - if(istype(H) && H.stat == CONSCIOUS && (istype(H.ears, /obj/item/radio/headset))) - to_chat(H, "Your headset pings, notifying you that a reply to your fax has arrived.") - to_chat(src.owner, "You sent a [eviltype] fax to [H].") - log_admin("[key_name(src.owner)] sent [key_name(H)] a [eviltype] fax") - message_admins("[key_name_admin(src.owner)] replied to [key_name_admin(H)] with a [eviltype] fax") - return - - else if(href_list["FaxReplyTemplate"]) - if(!check_rights(R_FUN)) - return - var/mob/living/carbon/human/H = locate(href_list["FaxReplyTemplate"]) - if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.") - return - var/obj/item/paper/P = new /obj/item/paper(null) - var/obj/machinery/photocopier/faxmachine/fax = locate(href_list["originfax"]) - P.name = "Central Command - paper" - var/stypes = list("Handle it yourselves!","Illegible fax","Fax not signed","Not Right Now","You are wasting our time", "Keep up the good work") - var/stype = input(src.owner, "Which type of standard reply do you wish to send to [H]?","Choose your paperwork", "") as null|anything in stypes - var/tmsg = "

                    [GLOB.station_name]


                    Nanotrasen Communications Department Report


                    " - if(stype == "Handle it yourselves!") - tmsg += "Greetings, esteemed crewmember. Your fax has been DECLINED automatically by the Communications Department Fax Registration System.

                    Please proceed in accordance with Standard Operating Procedure and/or Space Law. You are fully trained to handle this situation without Central Command intervention.

                    This is an automatic message." - else if(stype == "Illegible fax") - tmsg += "Greetings, esteemed crewmember. Your fax has been DECLINED automatically by the Communications Department Fax Registration System.

                    Your fax's grammar, syntax and/or typography are of a sub-par level and do not allow us to understand the contents of the message.

                    Please consult your nearest dictionary and/or thesaurus and try again.

                    This is an automatic message." - else if(stype == "Fax not signed") - tmsg += "Greetings, esteemed crewmember. Your fax has been DECLINED automatically by the Communications Department Fax Registration System.

                    Your fax has not been correctly signed and, as such, we cannot verify your identity.

                    Please sign your faxes before sending them so that we may verify your identity.

                    This is an automatic message." - else if(stype == "Not Right Now") - tmsg += "Greetings, esteemed crewmember. Your fax has been DECLINED automatically by the Communications Department Fax Registration System.

                    Due to pressing concerns of a matter above your current paygrade, we are unable to provide assistance in whatever matter your fax referenced.

                    This can be either due to a power outage, bureaucratic audit, pest infestation, Ascendance Event, corgi outbreak, or any other situation that would affect the proper functioning of the Communications Department Fax Registration System.

                    Please try again later.

                    This is an automatic message." - else if(stype == "You are wasting our time") - tmsg += "Greetings, esteemed crewmember. Your fax has been DECLINED automatically by the Communications Department Fax Registration System.

                    In the interest of preventing further mismanagement of company resources, please avoid wasting our time with such petty drivel.

                    Do kindly remember that we expect our workforce to maintain at least a semi-decent level of profesionalism. Do not test our patience.

                    This is an automatic message." - else if(stype == "Keep up the good work") - tmsg += "Greetings, esteemed crewmember. Your fax has been received successfully by the Communications Department Fax Registration System.

                    We at Central Command appreciate the good work that you have done here, and sincerely recommend that you continue such a display of dedication to the company.

                    This is absolutely not an automated message." - else - return - tmsg += "
                    " - P.info = tmsg - P.x = rand(-2, 0) - P.y = rand(-1, 2) - P.update_icon() - fax.receivefax(P) - if(istype(H) && H.stat == CONSCIOUS && (istype(H.ears, /obj/item/radio/headset))) - to_chat(H, "Your headset pings, notifying you that a reply to your fax has arrived.") - to_chat(src.owner, "You sent a standard '[stype]' fax to [H].") - log_admin("[key_name(src.owner)] sent [key_name(H)] a standard '[stype]' fax") - message_admins("[key_name_admin(src.owner)] replied to [key_name_admin(H)] with a standard '[stype]' fax") - return - - else if(href_list["AdminFaxView"]) - if(!check_rights(R_FUN)) - return - - var/obj/item/fax = locate(href_list["AdminFaxView"]) - if(istype(fax, /obj/item/paper)) - var/obj/item/paper/P = fax - usr.examinate(P) - else if(istype(fax, /obj/item/photo)) - var/obj/item/photo/H = fax - H.show(usr) - else - to_chat(usr, "The faxed item is not viewable. This is probably a bug, and should be reported on the tracker: [fax.type]") - return - -/datum/admins/proc/handle_sendall(var/obj/machinery/photocopier/faxmachine/F, var/obj/item/paper/P) - if(F.receivefax(P) == FALSE) - to_chat(owner, "Message transmission to [F.department] failed.") - -/datum/admins/proc/HandleCMode() - if(!check_rights(R_ADMIN)) - return - - var/dat = {"What mode do you wish to play?
                    "} - for(var/mode in config.modes) - dat += {"[config.mode_names[mode]]
                    "} - dat += {"Secret
                    "} - dat += {"Random
                    "} - dat += {"Now: [GLOB.master_mode]"} - var/datum/browser/popup = new(usr, "c_mode", "Gamemode Panel", 500, 600) - popup.set_content(dat) - popup.open() -// usr << browse(dat, "window=c_mode") - -/datum/admins/proc/HandleFSecret() - if(!check_rights(R_ADMIN)) - return - - if(SSticker.HasRoundStarted()) - return alert(usr, "The game has already started.", null, null, null, null) - if(GLOB.master_mode != "secret") - return alert(usr, "The game mode has to be secret!", null, null, null, null) - var/dat = {"What game mode do you want to force secret to be? Use this if you want to change the game mode, but want the players to believe it's secret. This will only work if the current game mode is secret.
                    "} - for(var/mode in config.modes) - dat += {"[config.mode_names[mode]]
                    "} - dat += {"Random (default)
                    "} - dat += {"Now: [GLOB.secret_force_mode]"} - usr << browse(dat, "window=f_secret") +/datum/admins/proc/CheckAdminHref(href, href_list) + var/auth = href_list["admin_token"] + . = auth && (auth == href_token || auth == GLOB.href_token) + if(.) + return + var/msg = !auth ? "no" : "a bad" + message_admins("[key_name_admin(usr)] clicked an href with [msg] authorization key!") + if(CONFIG_GET(flag/debug_admin_hrefs)) + message_admins("Debug mode enabled, call not blocked. Please ask your coders to review this round's logs.") + log_world("UAH: [href]") + return TRUE + log_admin_private("[key_name(usr)] clicked an href with [msg] authorization key! [href]") + +/datum/admins/Topic(href, href_list) + ..() + + if(usr.client != src.owner || !check_rights(0)) + message_admins("[usr.key] has attempted to override the admin panel!") + log_admin("[key_name(usr)] tried to use the admin panel without authorization.") + return + + if(!CheckAdminHref(href, href_list)) + return + + if(href_list["ahelp"]) + if(!check_rights(R_ADMIN, TRUE)) + return + + var/ahelp_ref = href_list["ahelp"] + var/datum/admin_help/AH = locate(ahelp_ref) + if(AH) + AH.Action(href_list["ahelp_action"]) + else + to_chat(usr, "Ticket [ahelp_ref] has been deleted!", confidential = TRUE) + + else if(href_list["ahelp_tickets"]) + GLOB.ahelp_tickets.BrowseTickets(text2num(href_list["ahelp_tickets"])) + + else if(href_list["stickyban"]) + stickyban(href_list["stickyban"],href_list) + + else if(href_list["getplaytimewindow"]) + if(!check_rights(R_ADMIN)) + return + var/mob/M = locate(href_list["getplaytimewindow"]) in GLOB.mob_list + if(!M) + to_chat(usr, "ERROR: Mob not found.", confidential = TRUE) + return + cmd_show_exp_panel(M.client) + + else if(href_list["toggleexempt"]) + if(!check_rights(R_ADMIN)) + return + var/client/C = locate(href_list["toggleexempt"]) in GLOB.clients + if(!C) + to_chat(usr, "ERROR: Client not found.", confidential = TRUE) + return + toggle_exempt_status(C) + + else if(href_list["makeAntag"]) + if(!check_rights(R_ADMIN)) + return + if (!SSticker.mode) + to_chat(usr, "Not until the round starts!", confidential = TRUE) + return + switch(href_list["makeAntag"]) + if("traitors") + if(src.makeTraitors()) + message_admins("[key_name_admin(usr)] created traitors.") + log_admin("[key_name(usr)] created traitors.") + else + message_admins("[key_name_admin(usr)] tried to create traitors. Unfortunately, there were no candidates available.") + log_admin("[key_name(usr)] failed to create traitors.") + if("changelings") + if(src.makeChangelings()) + message_admins("[key_name(usr)] created changelings.") + log_admin("[key_name(usr)] created changelings.") + else + message_admins("[key_name_admin(usr)] tried to create changelings. Unfortunately, there were no candidates available.") + log_admin("[key_name(usr)] failed to create changelings.") + if("revs") + if(src.makeRevs()) + message_admins("[key_name(usr)] started a revolution.") + log_admin("[key_name(usr)] started a revolution.") + else + message_admins("[key_name_admin(usr)] tried to start a revolution. Unfortunately, there were no candidates available.") + log_admin("[key_name(usr)] failed to start a revolution.") + if("cult") + if(src.makeCult()) + message_admins("[key_name(usr)] started a cult.") + log_admin("[key_name(usr)] started a cult.") + else + message_admins("[key_name_admin(usr)] tried to start a cult. Unfortunately, there were no candidates available.") + log_admin("[key_name(usr)] failed to start a cult.") + if("wizard") + message_admins("[key_name(usr)] is creating a wizard...") + if(src.makeWizard()) + message_admins("[key_name(usr)] created a wizard.") + log_admin("[key_name(usr)] created a wizard.") + else + message_admins("[key_name_admin(usr)] tried to create a wizard. Unfortunately, there were no candidates available.") + log_admin("[key_name(usr)] failed to create a wizard.") + if("nukeops") + message_admins("[key_name(usr)] is creating a nuke team...") + if(src.makeNukeTeam()) + message_admins("[key_name(usr)] created a nuke team.") + log_admin("[key_name(usr)] created a nuke team.") + else + message_admins("[key_name_admin(usr)] tried to create a nuke team. Unfortunately, there were not enough candidates available.") + log_admin("[key_name(usr)] failed to create a nuke team.") + if("ninja") + message_admins("[key_name(usr)] spawned a ninja.") + log_admin("[key_name(usr)] spawned a ninja.") + src.makeSpaceNinja() + if("aliens") + message_admins("[key_name(usr)] started an alien infestation.") + log_admin("[key_name(usr)] started an alien infestation.") + src.makeAliens() + if("deathsquad") + message_admins("[key_name(usr)] is creating a death squad...") + if(src.makeDeathsquad()) + message_admins("[key_name(usr)] created a death squad.") + log_admin("[key_name(usr)] created a death squad.") + else + message_admins("[key_name_admin(usr)] tried to create a death squad. Unfortunately, there were not enough candidates available.") + log_admin("[key_name(usr)] failed to create a death squad.") + if("blob") + var/strength = input("Set Blob Resource Gain Rate","Set Resource Rate",1) as num|null + if(!strength) + return + message_admins("[key_name(usr)] spawned a blob with base resource gain [strength].") + log_admin("[key_name(usr)] spawned a blob with base resource gain [strength].") + new/datum/round_event/ghost_role/blob(TRUE, strength) + if("centcom") + message_admins("[key_name(usr)] is creating a CentCom response team...") + if(src.makeEmergencyresponseteam()) + message_admins("[key_name(usr)] created a CentCom response team.") + log_admin("[key_name(usr)] created a CentCom response team.") + else + message_admins("[key_name_admin(usr)] tried to create a CentCom response team. Unfortunately, there were not enough candidates available.") + log_admin("[key_name(usr)] failed to create a CentCom response team.") + if("abductors") + message_admins("[key_name(usr)] is creating an abductor team...") + if(src.makeAbductorTeam()) + message_admins("[key_name(usr)] created an abductor team.") + log_admin("[key_name(usr)] created an abductor team.") + else + message_admins("[key_name_admin(usr)] tried to create an abductor team. Unfortunately there were not enough candidates available.") + log_admin("[key_name(usr)] failed to create an abductor team.") + if("revenant") + if(src.makeRevenant()) + message_admins("[key_name(usr)] created a revenant.") + log_admin("[key_name(usr)] created a revenant.") + else + message_admins("[key_name_admin(usr)] tried to create a revenant. Unfortunately, there were no candidates available.") + log_admin("[key_name(usr)] failed to create a revenant.") + + else if(href_list["forceevent"]) + if(!check_rights(R_FUN)) + return + var/datum/round_event_control/E = locate(href_list["forceevent"]) in SSevents.control + if(E) + E.admin_setup(usr) + var/datum/round_event/event = E.runEvent() + if(event.announceWhen>0) + event.processing = FALSE + var/prompt = alert(usr, "Would you like to alert the crew?", "Alert", "Yes", "No", "Cancel") + switch(prompt) + if("Yes") + event.announceChance = 100 + if("Cancel") + event.kill() + return + if("No") + event.announceChance = 0 + event.processing = TRUE + message_admins("[key_name_admin(usr)] has triggered an event. ([E.name])") + log_admin("[key_name(usr)] has triggered an event. ([E.name])") + return + + else if(href_list["editrightsbrowser"]) + edit_admin_permissions(0) + + else if(href_list["editrightsbrowserlog"]) + edit_admin_permissions(1, href_list["editrightstarget"], href_list["editrightsoperation"], href_list["editrightspage"]) + + if(href_list["editrightsbrowsermanage"]) + if(href_list["editrightschange"]) + change_admin_rank(ckey(href_list["editrightschange"]), href_list["editrightschange"], TRUE) + else if(href_list["editrightsremove"]) + remove_admin(ckey(href_list["editrightsremove"]), href_list["editrightsremove"], TRUE) + else if(href_list["editrightsremoverank"]) + remove_rank(href_list["editrightsremoverank"]) + edit_admin_permissions(2) + + else if(href_list["editrights"]) + edit_rights_topic(href_list) + + else if(href_list["gamemode_panel"]) + if(!check_rights(R_ADMIN)) + return + SSticker.mode.admin_panel() + + else if(href_list["call_shuttle"]) + if(!check_rights(R_ADMIN)) + return + + + switch(href_list["call_shuttle"]) + if("1") + if(EMERGENCY_AT_LEAST_DOCKED) + return + SSshuttle.emergency.request() + log_admin("[key_name(usr)] called the Emergency Shuttle.") + message_admins("[key_name_admin(usr)] called the Emergency Shuttle to the station.") + + if("2") + if(EMERGENCY_AT_LEAST_DOCKED) + return + switch(SSshuttle.emergency.mode) + if(SHUTTLE_CALL) + SSshuttle.emergency.cancel() + log_admin("[key_name(usr)] sent the Emergency Shuttle back.") + message_admins("[key_name_admin(usr)] sent the Emergency Shuttle back.") + else + SSshuttle.emergency.cancel() + log_admin("[key_name(usr)] called the Emergency Shuttle.") + message_admins("[key_name_admin(usr)] called the Emergency Shuttle to the station.") + + + + else if(href_list["edit_shuttle_time"]) + if(!check_rights(R_SERVER)) + return + + var/timer = input("Enter new shuttle duration (seconds):","Edit Shuttle Timeleft", SSshuttle.emergency.timeLeft() ) as num|null + if(!timer) + return + SSshuttle.emergency.setTimer(timer*10) + log_admin("[key_name(usr)] edited the Emergency Shuttle's timeleft to [timer] seconds.") + minor_announce("The emergency shuttle will reach its destination in [round(SSshuttle.emergency.timeLeft(600))] minutes.") + message_admins("[key_name_admin(usr)] edited the Emergency Shuttle's timeleft to [timer] seconds.") + else if(href_list["trigger_centcom_recall"]) + if(!check_rights(R_ADMIN)) + return + + usr.client.trigger_centcom_recall() + + else if(href_list["toggle_continuous"]) + if(!check_rights(R_ADMIN)) + return + var/list/continuous = CONFIG_GET(keyed_list/continuous) + if(!continuous[SSticker.mode.config_tag]) + continuous[SSticker.mode.config_tag] = TRUE + else + continuous[SSticker.mode.config_tag] = FALSE + + message_admins("[key_name_admin(usr)] toggled the round to [continuous[SSticker.mode.config_tag] ? "continue if all antagonists die" : "end with the antagonists"].") + check_antagonists() + + else if(href_list["toggle_midround_antag"]) + if(!check_rights(R_ADMIN)) + return + + var/list/midround_antag = CONFIG_GET(keyed_list/midround_antag) + if(!midround_antag[SSticker.mode.config_tag]) + midround_antag[SSticker.mode.config_tag] = TRUE + else + midround_antag[SSticker.mode.config_tag] = FALSE + + message_admins("[key_name_admin(usr)] toggled the round to [midround_antag[SSticker.mode.config_tag] ? "use" : "skip"] the midround antag system.") + check_antagonists() + + else if(href_list["alter_midround_time_limit"]) + if(!check_rights(R_ADMIN)) + return + + var/timer = input("Enter new maximum time",, CONFIG_GET(number/midround_antag_time_check)) as num|null + if(!timer) + return + CONFIG_SET(number/midround_antag_time_check, timer) + message_admins("[key_name_admin(usr)] edited the maximum midround antagonist time to [timer] minutes.") + check_antagonists() + + else if(href_list["alter_midround_life_limit"]) + if(!check_rights(R_ADMIN)) + return + + var/ratio = input("Enter new life ratio",, CONFIG_GET(number/midround_antag_life_check) * 100) as num|null + if(!ratio) + return + CONFIG_SET(number/midround_antag_life_check, ratio / 100) + + message_admins("[key_name_admin(usr)] edited the midround antagonist living crew ratio to [ratio]% alive.") + check_antagonists() + + else if(href_list["toggle_noncontinuous_behavior"]) + if(!check_rights(R_ADMIN)) + return + + if(!SSticker.mode.round_ends_with_antag_death) + SSticker.mode.round_ends_with_antag_death = 1 + else + SSticker.mode.round_ends_with_antag_death = 0 + + message_admins("[key_name_admin(usr)] edited the midround antagonist system to [SSticker.mode.round_ends_with_antag_death ? "end the round" : "continue as extended"] upon failure.") + check_antagonists() + + else if(href_list["delay_round_end"]) + if(!check_rights(R_SERVER)) + return + if(!SSticker.delay_end) + SSticker.admin_delay_notice = input(usr, "Enter a reason for delaying the round end", "Round Delay Reason") as null|text + if(isnull(SSticker.admin_delay_notice)) + return + else + if(alert(usr, "Really cancel current round end delay? The reason for the current delay is: \"[SSticker.admin_delay_notice]\"", "Undelay round end", "Yes", "No") != "Yes") + return + SSticker.admin_delay_notice = null + SSticker.delay_end = !SSticker.delay_end + var/reason = SSticker.delay_end ? "for reason: [SSticker.admin_delay_notice]" : "."//laziness + var/msg = "[SSticker.delay_end ? "delayed" : "undelayed"] the round end [reason]" + log_admin("[key_name(usr)] [msg]") + message_admins("[key_name_admin(usr)] [msg]") + if(SSticker.ready_for_reboot && !SSticker.delay_end) //we undelayed after standard reboot would occur + SSticker.standard_reboot() + + else if(href_list["end_round"]) + if(!check_rights(R_ADMIN)) + return + + message_admins("[key_name_admin(usr)] is considering ending the round.") + if(alert(usr, "This will end the round, are you SURE you want to do this?", "Confirmation", "Yes", "No") == "Yes") + if(alert(usr, "Final Confirmation: End the round NOW?", "Confirmation", "Yes", "No") == "Yes") + message_admins("[key_name_admin(usr)] has ended the round.") + SSticker.force_ending = 1 //Yeah there we go APC destroyed mission accomplished + return + else + message_admins("[key_name_admin(usr)] decided against ending the round.") + else + message_admins("[key_name_admin(usr)] decided against ending the round.") + + else if(href_list["simplemake"]) + if(!check_rights(R_SPAWN)) + return + + var/mob/M = locate(href_list["mob"]) + if(!ismob(M)) + to_chat(usr, "This can only be used on instances of type /mob.", confidential = TRUE) + return + + var/delmob = TRUE + if(!isobserver(M)) + switch(alert("Delete old mob?","Message","Yes","No","Cancel")) + if("Cancel") + return + if("No") + delmob = FALSE + + log_admin("[key_name(usr)] has used rudimentary transformation on [key_name(M)]. Transforming to [href_list["simplemake"]].; deletemob=[delmob]") + message_admins("[key_name_admin(usr)] has used rudimentary transformation on [key_name_admin(M)]. Transforming to [href_list["simplemake"]].; deletemob=[delmob]") + switch(href_list["simplemake"]) + if("observer") + M.change_mob_type( /mob/dead/observer , null, null, delmob ) + if("drone") + M.change_mob_type( /mob/living/carbon/alien/humanoid/drone , null, null, delmob ) + if("hunter") + M.change_mob_type( /mob/living/carbon/alien/humanoid/hunter , null, null, delmob ) + if("queen") + M.change_mob_type( /mob/living/carbon/alien/humanoid/royal/queen , null, null, delmob ) + if("praetorian") + M.change_mob_type( /mob/living/carbon/alien/humanoid/royal/praetorian , null, null, delmob ) + if("sentinel") + M.change_mob_type( /mob/living/carbon/alien/humanoid/sentinel , null, null, delmob ) + if("larva") + M.change_mob_type( /mob/living/carbon/alien/larva , null, null, delmob ) + if("human") + var/posttransformoutfit = usr.client.robust_dress_shop() + if (!posttransformoutfit) + return + var/mob/living/carbon/human/newmob = M.change_mob_type( /mob/living/carbon/human , null, null, delmob ) + if(posttransformoutfit && istype(newmob)) + newmob.equipOutfit(posttransformoutfit) + if("slime") + M.change_mob_type( /mob/living/simple_animal/slime , null, null, delmob ) + if("monkey") + M.change_mob_type( /mob/living/carbon/monkey , null, null, delmob ) + if("robot") + M.change_mob_type( /mob/living/silicon/robot , null, null, delmob ) + if("cat") + M.change_mob_type( /mob/living/simple_animal/pet/cat , null, null, delmob ) + if("runtime") + M.change_mob_type( /mob/living/simple_animal/pet/cat/Runtime , null, null, delmob ) + if("corgi") + M.change_mob_type( /mob/living/simple_animal/pet/dog/corgi , null, null, delmob ) + if("ian") + M.change_mob_type( /mob/living/simple_animal/pet/dog/corgi/Ian , null, null, delmob ) + if("pug") + M.change_mob_type( /mob/living/simple_animal/pet/dog/pug , null, null, delmob ) + if("crab") + M.change_mob_type( /mob/living/simple_animal/crab , null, null, delmob ) + if("coffee") + M.change_mob_type( /mob/living/simple_animal/crab/Coffee , null, null, delmob ) + if("parrot") + M.change_mob_type( /mob/living/simple_animal/parrot , null, null, delmob ) + if("polyparrot") + M.change_mob_type( /mob/living/simple_animal/parrot/Poly , null, null, delmob ) + if("constructjuggernaut") + M.change_mob_type( /mob/living/simple_animal/hostile/construct/juggernaut , null, null, delmob ) + if("constructartificer") + M.change_mob_type( /mob/living/simple_animal/hostile/construct/artificer , null, null, delmob ) + if("constructwraith") + M.change_mob_type( /mob/living/simple_animal/hostile/construct/wraith , null, null, delmob ) + if("shade") + M.change_mob_type( /mob/living/simple_animal/shade , null, null, delmob ) + + else if(href_list["boot2"]) + if(!check_rights(R_ADMIN)) + return + var/mob/M = locate(href_list["boot2"]) + if(ismob(M)) + if(!check_if_greater_rights_than(M.client)) + to_chat(usr, "Error: They have more rights than you do.", confidential = TRUE) + return + if(alert(usr, "Kick [key_name(M)]?", "Confirm", "Yes", "No") != "Yes") + return + if(!M) + to_chat(usr, "Error: [M] no longer exists!", confidential = TRUE) + return + if(!M.client) + to_chat(usr, "Error: [M] no longer has a client!", confidential = TRUE) + return + to_chat(M, "You have been kicked from the server by [usr.client.holder.fakekey ? "an Administrator" : "[usr.client.key]"].", confidential = TRUE) + log_admin("[key_name(usr)] kicked [key_name(M)].") + message_admins("[key_name_admin(usr)] kicked [key_name_admin(M)].") + qdel(M.client) + + else if(href_list["addmessage"]) + if(!check_rights(R_ADMIN)) + return + var/target_key = href_list["addmessage"] + create_message("message", target_key, secret = 0) + + else if(href_list["addnote"]) + if(!check_rights(R_ADMIN)) + return + var/target_key = href_list["addnote"] + create_message("note", target_key) + + else if(href_list["addwatch"]) + if(!check_rights(R_ADMIN)) + return + var/target_key = href_list["addwatch"] + create_message("watchlist entry", target_key, secret = 1) + + else if(href_list["addmemo"]) + if(!check_rights(R_ADMIN)) + return + create_message("memo", secret = 0, browse = 1) + + else if(href_list["addmessageempty"]) + if(!check_rights(R_ADMIN)) + return + create_message("message", secret = 0) + + else if(href_list["addnoteempty"]) + if(!check_rights(R_ADMIN)) + return + create_message("note") + + else if(href_list["addwatchempty"]) + if(!check_rights(R_ADMIN)) + return + create_message("watchlist entry", secret = 1) + + else if(href_list["deletemessage"]) + if(!check_rights(R_ADMIN)) + return + var/safety = alert("Delete message/note?",,"Yes","No"); + if (safety == "Yes") + var/message_id = href_list["deletemessage"] + delete_message(message_id) + + else if(href_list["deletemessageempty"]) + if(!check_rights(R_ADMIN)) + return + var/safety = alert("Delete message/note?",,"Yes","No"); + if (safety == "Yes") + var/message_id = href_list["deletemessageempty"] + delete_message(message_id, browse = TRUE) + + else if(href_list["editmessage"]) + if(!check_rights(R_ADMIN)) + return + var/message_id = href_list["editmessage"] + edit_message(message_id) + + else if(href_list["editmessageempty"]) + if(!check_rights(R_ADMIN)) + return + var/message_id = href_list["editmessageempty"] + edit_message(message_id, browse = 1) + + else if(href_list["editmessageexpiry"]) + if(!check_rights(R_ADMIN)) + return + var/message_id = href_list["editmessageexpiry"] + edit_message_expiry(message_id) + + else if(href_list["editmessageexpiryempty"]) + if(!check_rights(R_ADMIN)) + return + var/message_id = href_list["editmessageexpiryempty"] + edit_message_expiry(message_id, browse = 1) + + else if(href_list["editmessageseverity"]) + if(!check_rights(R_ADMIN)) + return + var/message_id = href_list["editmessageseverity"] + edit_message_severity(message_id) + + else if(href_list["secretmessage"]) + if(!check_rights(R_ADMIN)) + return + var/message_id = href_list["secretmessage"] + toggle_message_secrecy(message_id) + + else if(href_list["searchmessages"]) + if(!check_rights(R_ADMIN)) + return + var/target = href_list["searchmessages"] + browse_messages(index = target) + + else if(href_list["nonalpha"]) + if(!check_rights(R_ADMIN)) + return + var/target = href_list["nonalpha"] + target = text2num(target) + browse_messages(index = target) + + else if(href_list["showmessages"]) + if(!check_rights(R_ADMIN)) + return + var/target = href_list["showmessages"] + browse_messages(index = target) + + else if(href_list["showmemo"]) + if(!check_rights(R_ADMIN)) + return + browse_messages("memo") + + else if(href_list["showwatch"]) + if(!check_rights(R_ADMIN)) + return + browse_messages("watchlist entry") + + else if(href_list["showwatchfilter"]) + if(!check_rights(R_ADMIN)) + return + browse_messages("watchlist entry", filter = 1) + + else if(href_list["showmessageckey"]) + if(!check_rights(R_ADMIN)) + return + var/target = href_list["showmessageckey"] + var/agegate = TRUE + if (href_list["showall"]) + agegate = FALSE + browse_messages(target_ckey = target, agegate = agegate) + + else if(href_list["showmessageckeylinkless"]) + var/target = href_list["showmessageckeylinkless"] + browse_messages(target_ckey = target, linkless = 1) + + else if(href_list["messageedits"]) + if(!check_rights(R_ADMIN)) + return + var/datum/DBQuery/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 + if(query_get_message_edits.NextRow()) + var/edit_log = query_get_message_edits.item[1] + if(!QDELETED(usr)) + var/datum/browser/browser = new(usr, "Note edits", "Note edits") + browser.set_content(jointext(edit_log, "")) + browser.open() + qdel(query_get_message_edits) + + else if(href_list["mute"]) + if(!check_rights(R_ADMIN)) + return + cmd_admin_mute(href_list["mute"], text2num(href_list["mute_type"])) + + else if(href_list["c_mode"]) + return HandleCMode() + + else if(href_list["f_secret"]) + return HandleFSecret() + + else if(href_list["f_dynamic_roundstart"]) + if(!check_rights(R_ADMIN)) + return + if(SSticker && SSticker.mode) + return alert(usr, "The game has already started.", null, null, null, null) + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode.", null, null, null, null) + var/roundstart_rules = list() + for (var/rule in subtypesof(/datum/dynamic_ruleset/roundstart)) + var/datum/dynamic_ruleset/roundstart/newrule = new rule() + roundstart_rules[newrule.name] = newrule + var/added_rule = input(usr,"What ruleset do you want to force? This will bypass threat level and population restrictions.", "Rigging Roundstart", null) as null|anything in sortList(roundstart_rules) + if (added_rule) + GLOB.dynamic_forced_roundstart_ruleset += roundstart_rules[added_rule] + log_admin("[key_name(usr)] set [added_rule] to be a forced roundstart ruleset.") + message_admins("[key_name(usr)] set [added_rule] to be a forced roundstart ruleset.", 1) + Game() + + else if(href_list["f_dynamic_roundstart_clear"]) + if(!check_rights(R_ADMIN)) + return + GLOB.dynamic_forced_roundstart_ruleset = list() + Game() + log_admin("[key_name(usr)] cleared the rigged roundstart rulesets. The mode will pick them as normal.") + message_admins("[key_name(usr)] cleared the rigged roundstart rulesets. The mode will pick them as normal.", 1) + + else if(href_list["f_dynamic_roundstart_remove"]) + if(!check_rights(R_ADMIN)) + return + var/datum/dynamic_ruleset/roundstart/rule = locate(href_list["f_dynamic_roundstart_remove"]) + GLOB.dynamic_forced_roundstart_ruleset -= rule + Game() + log_admin("[key_name(usr)] removed [rule] from the forced roundstart rulesets.") + message_admins("[key_name(usr)] removed [rule] from the forced roundstart rulesets.", 1) + + else if(href_list["f_dynamic_latejoin"]) + if(!check_rights(R_ADMIN)) + return + if(!SSticker || !SSticker.mode) + return alert(usr, "The game must start first.", null, null, null, null) + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + var/latejoin_rules = list() + for (var/rule in subtypesof(/datum/dynamic_ruleset/latejoin)) + var/datum/dynamic_ruleset/latejoin/newrule = new rule() + latejoin_rules[newrule.name] = newrule + var/added_rule = input(usr,"What ruleset do you want to force upon the next latejoiner? This will bypass threat level and population restrictions.", "Rigging Latejoin", null) as null|anything in sortList(latejoin_rules) + if (added_rule) + var/datum/game_mode/dynamic/mode = SSticker.mode + mode.forced_latejoin_rule = latejoin_rules[added_rule] + log_admin("[key_name(usr)] set [added_rule] to proc on the next latejoin.") + message_admins("[key_name(usr)] set [added_rule] to proc on the next latejoin.", 1) + Game() + + else if(href_list["f_dynamic_latejoin_clear"]) + if(!check_rights(R_ADMIN)) + return + if (SSticker && SSticker.mode && istype(SSticker.mode,/datum/game_mode/dynamic)) + var/datum/game_mode/dynamic/mode = SSticker.mode + mode.forced_latejoin_rule = null + Game() + log_admin("[key_name(usr)] cleared the forced latejoin ruleset.") + message_admins("[key_name(usr)] cleared the forced latejoin ruleset.", 1) + + else if(href_list["f_dynamic_midround"]) + if(!check_rights(R_ADMIN)) + return + if(!SSticker || !SSticker.mode) + return alert(usr, "The game must start first.", null, null, null, null) + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + var/midround_rules = list() + for (var/rule in subtypesof(/datum/dynamic_ruleset/midround)) + var/datum/dynamic_ruleset/midround/newrule = new rule() + midround_rules[newrule.name] = rule + var/added_rule = input(usr,"What ruleset do you want to force right now? This will bypass threat level and population restrictions.", "Execute Ruleset", null) as null|anything in sortList(midround_rules) + if (added_rule) + var/datum/game_mode/dynamic/mode = SSticker.mode + log_admin("[key_name(usr)] executed the [added_rule] ruleset.") + message_admins("[key_name(usr)] executed the [added_rule] ruleset.", 1) + mode.picking_specific_rule(midround_rules[added_rule],1) + + else if (href_list["f_dynamic_options"]) + if(!check_rights(R_ADMIN)) + return + + if(SSticker && SSticker.mode) + return alert(usr, "The game has already started.", null, null, null, null) + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + + dynamic_mode_options(usr) + + else if(href_list["f_dynamic_roundstart_centre"]) + if(!check_rights(R_ADMIN)) + return + if(SSticker && SSticker.mode) + return alert(usr, "The game has already started.", null, null, null, null) + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + + var/new_centre = input(usr,"Change the centre of the dynamic mode threat curve. A negative value will give a more peaceful round ; a positive value, a round with higher threat. Any number between -5 and +5 is allowed.", "Change curve centre", null) as num + if (new_centre < -5 || new_centre > 5) + return alert(usr, "Only values between -5 and +5 are allowed.", null, null, null, null) + + log_admin("[key_name(usr)] changed the distribution curve center to [new_centre].") + message_admins("[key_name(usr)] changed the distribution curve center to [new_centre]", 1) + GLOB.dynamic_curve_centre = new_centre + dynamic_mode_options(usr) + + else if(href_list["f_dynamic_roundstart_width"]) + if(!check_rights(R_ADMIN)) + return + if(SSticker && SSticker.mode) + return alert(usr, "The game has already started.", null, null, null, null) + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + + var/new_width = input(usr,"Change the width of the dynamic mode threat curve. A higher value will favour extreme rounds ; a lower value, a round closer to the average. Any Number between 0.5 and 4 are allowed.", "Change curve width", null) as num + if (new_width < 0.5 || new_width > 4) + return alert(usr, "Only values between 0.5 and +2.5 are allowed.", null, null, null, null) + + log_admin("[key_name(usr)] changed the distribution curve width to [new_width].") + message_admins("[key_name(usr)] changed the distribution curve width to [new_width]", 1) + GLOB.dynamic_curve_width = new_width + dynamic_mode_options(usr) + + else if(href_list["f_dynamic_roundstart_latejoin_min"]) + if(!check_rights(R_ADMIN)) + return + if(SSticker && SSticker.mode) + return alert(usr, "The game has already started.", null, null, null, null) + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + var/new_min = input(usr,"Change the minimum delay of latejoin injection in minutes.", "Change latejoin injection delay minimum", null) as num + if(new_min <= 0) + return alert(usr, "The minimum can't be zero or lower.", null, null, null, null) + if((new_min MINUTES) > GLOB.dynamic_latejoin_delay_max) + return alert(usr, "The minimum must be lower than the maximum.", null, null, null, null) + + log_admin("[key_name(usr)] changed the latejoin injection minimum delay to [new_min] minutes.") + message_admins("[key_name(usr)] changed the latejoin injection minimum delay to [new_min] minutes", 1) + GLOB.dynamic_latejoin_delay_min = (new_min MINUTES) + dynamic_mode_options(usr) + + else if(href_list["f_dynamic_roundstart_latejoin_max"]) + if(!check_rights(R_ADMIN)) + return + if(SSticker && SSticker.mode) + return alert(usr, "The game has already started.", null, null, null, null) + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + var/new_max = input(usr,"Change the maximum delay of latejoin injection in minutes.", "Change latejoin injection delay maximum", null) as num + if(new_max <= 0) + return alert(usr, "The maximum can't be zero or lower.", null, null, null, null) + if((new_max MINUTES) < GLOB.dynamic_latejoin_delay_min) + return alert(usr, "The maximum must be higher than the minimum.", null, null, null, null) + + log_admin("[key_name(usr)] changed the latejoin injection maximum delay to [new_max] minutes.") + message_admins("[key_name(usr)] changed the latejoin injection maximum delay to [new_max] minutes", 1) + GLOB.dynamic_latejoin_delay_max = (new_max MINUTES) + dynamic_mode_options(usr) + + else if(href_list["f_dynamic_roundstart_midround_min"]) + if(!check_rights(R_ADMIN)) + return + if(SSticker && SSticker.mode) + return alert(usr, "The game has already started.", null, null, null, null) + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + var/new_min = input(usr,"Change the minimum delay of midround injection in minutes.", "Change midround injection delay minimum", null) as num + if(new_min <= 0) + return alert(usr, "The minimum can't be zero or lower.", null, null, null, null) + if((new_min MINUTES) > GLOB.dynamic_midround_delay_max) + return alert(usr, "The minimum must be lower than the maximum.", null, null, null, null) + + log_admin("[key_name(usr)] changed the midround injection minimum delay to [new_min] minutes.") + message_admins("[key_name(usr)] changed the midround injection minimum delay to [new_min] minutes", 1) + GLOB.dynamic_midround_delay_min = (new_min MINUTES) + dynamic_mode_options(usr) + + else if(href_list["f_dynamic_roundstart_midround_max"]) + if(!check_rights(R_ADMIN)) + return + if(SSticker && SSticker.mode) + return alert(usr, "The game has already started.", null, null, null, null) + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + var/new_max = input(usr,"Change the maximum delay of midround injection in minutes.", "Change midround injection delay maximum", null) as num + if(new_max <= 0) + return alert(usr, "The maximum can't be zero or lower.", null, null, null, null) + if((new_max MINUTES) > GLOB.dynamic_midround_delay_max) + return alert(usr, "The maximum must be higher than the minimum.", null, null, null, null) + + log_admin("[key_name(usr)] changed the midround injection maximum delay to [new_max] minutes.") + message_admins("[key_name(usr)] changed the midround injection maximum delay to [new_max] minutes", 1) + GLOB.dynamic_midround_delay_max = (new_max MINUTES) + dynamic_mode_options(usr) + + else if(href_list["f_dynamic_force_extended"]) + if(!check_rights(R_ADMIN)) + return + + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + + GLOB.dynamic_forced_extended = !GLOB.dynamic_forced_extended + log_admin("[key_name(usr)] set 'forced_extended' to [GLOB.dynamic_forced_extended].") + message_admins("[key_name(usr)] set 'forced_extended' to [GLOB.dynamic_forced_extended].") + dynamic_mode_options(usr) + + else if(href_list["f_dynamic_no_stacking"]) + if(!check_rights(R_ADMIN)) + return + + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + + GLOB.dynamic_no_stacking = !GLOB.dynamic_no_stacking + log_admin("[key_name(usr)] set 'no_stacking' to [GLOB.dynamic_no_stacking].") + message_admins("[key_name(usr)] set 'no_stacking' to [GLOB.dynamic_no_stacking].") + dynamic_mode_options(usr) + + else if(href_list["f_dynamic_classic_secret"]) + if(!check_rights(R_ADMIN)) + return + + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + + GLOB.dynamic_classic_secret = !GLOB.dynamic_classic_secret + log_admin("[key_name(usr)] set 'classic_secret' to [GLOB.dynamic_classic_secret].") + message_admins("[key_name(usr)] set 'classic_secret' to [GLOB.dynamic_classic_secret].") + dynamic_mode_options(usr) + + else if(href_list["f_dynamic_stacking_limit"]) + if(!check_rights(R_ADMIN)) + return + + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + + GLOB.dynamic_stacking_limit = input(usr,"Change the threat limit at which round-endings rulesets will start to stack.", "Change stacking limit", null) as num + log_admin("[key_name(usr)] set 'stacking_limit' to [GLOB.dynamic_stacking_limit].") + message_admins("[key_name(usr)] set 'stacking_limit' to [GLOB.dynamic_stacking_limit].") + dynamic_mode_options(usr) + + else if(href_list["f_dynamic_high_pop_limit"]) + if(!check_rights(R_ADMIN)) + return + + if(SSticker && SSticker.mode) + return alert(usr, "The game has already started.", null, null, null, null) + + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + + var/new_value = input(usr, "Enter the high-pop override threshold for dynamic mode.", "High pop override") as num + if (new_value < 0) + return alert(usr, "Only positive values allowed!", null, null, null, null) + GLOB.dynamic_high_pop_limit = new_value + + log_admin("[key_name(usr)] set 'high_pop_limit' to [GLOB.dynamic_high_pop_limit].") + message_admins("[key_name(usr)] set 'high_pop_limit' to [GLOB.dynamic_high_pop_limit].") + dynamic_mode_options(usr) + + else if(href_list["f_dynamic_forced_threat"]) + if(!check_rights(R_ADMIN)) + return + + if(SSticker && SSticker.mode) + return alert(usr, "The game has already started.", null, null, null, null) + + if(GLOB.master_mode != "dynamic") + return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null) + + var/new_value = input(usr, "Enter the forced threat level for dynamic mode.", "Forced threat level") as num + if (new_value > 100) + return alert(usr, "The value must be be under 100.", null, null, null, null) + GLOB.dynamic_forced_threat_level = new_value + + log_admin("[key_name(usr)] set 'forced_threat_level' to [GLOB.dynamic_forced_threat_level].") + message_admins("[key_name(usr)] set 'forced_threat_level' to [GLOB.dynamic_forced_threat_level].") + dynamic_mode_options(usr) + + else if(href_list["c_mode2"]) + if(!check_rights(R_ADMIN|R_SERVER)) + return + + if (SSticker.HasRoundStarted()) + if (askuser(usr, "The game has already started. Would you like to save this as the default mode effective next round?", "Save mode", "Yes", "Cancel", Timeout = null) == 1) + SSticker.save_mode(href_list["c_mode2"]) + HandleCMode() + return + GLOB.master_mode = href_list["c_mode2"] + log_admin("[key_name(usr)] set the mode as [GLOB.master_mode].") + message_admins("[key_name_admin(usr)] set the mode as [GLOB.master_mode].") + to_chat(world, "The mode is now: [GLOB.master_mode]", confidential = TRUE) + Game() // updates the main game menu + if (askuser(usr, "Would you like to save this as the default mode for the server?", "Save mode", "Yes", "No", Timeout = null) == 1) + SSticker.save_mode(GLOB.master_mode) + HandleCMode() + + else if(href_list["f_secret2"]) + if(!check_rights(R_ADMIN|R_SERVER)) + return + + if(SSticker.HasRoundStarted()) + return alert(usr, "The game has already started.", null, null, null, null) + if(GLOB.master_mode != "secret") + return alert(usr, "The game mode has to be secret!", null, null, null, null) + GLOB.secret_force_mode = href_list["f_secret2"] + log_admin("[key_name(usr)] set the forced secret mode as [GLOB.secret_force_mode].") + message_admins("[key_name_admin(usr)] set the forced secret mode as [GLOB.secret_force_mode].") + Game() // updates the main game menu + HandleFSecret() + + else if(href_list["monkeyone"]) + if(!check_rights(R_SPAWN)) + return + + var/mob/living/carbon/human/H = locate(href_list["monkeyone"]) + if(!istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.", confidential = TRUE) + return + + log_admin("[key_name(usr)] attempting to monkeyize [key_name(H)].") + message_admins("[key_name_admin(usr)] attempting to monkeyize [key_name_admin(H)].") + H.monkeyize() + + else if(href_list["humanone"]) + if(!check_rights(R_SPAWN)) + return + + var/mob/living/carbon/monkey/Mo = locate(href_list["humanone"]) + if(!istype(Mo)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/monkey.", confidential = TRUE) + return + + log_admin("[key_name(usr)] attempting to humanize [key_name(Mo)].") + message_admins("[key_name_admin(usr)] attempting to humanize [key_name_admin(Mo)].") + Mo.humanize() + + else if(href_list["corgione"]) + if(!check_rights(R_SPAWN)) + return + + var/mob/living/carbon/human/H = locate(href_list["corgione"]) + if(!istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.", confidential = TRUE) + return + + log_admin("[key_name(usr)] attempting to corgize [key_name(H)].") + message_admins("[key_name_admin(usr)] attempting to corgize [key_name_admin(H)].") + H.corgize() + + + else if(href_list["forcespeech"]) + if(!check_rights(R_FUN)) + return + + var/mob/M = locate(href_list["forcespeech"]) + if(!ismob(M)) + to_chat(usr, "this can only be used on instances of type /mob.", confidential = TRUE) + + var/speech = input("What will [key_name(M)] say?", "Force speech", "")// Don't need to sanitize, since it does that in say(), we also trust our admins. + if(!speech) + return + M.say(speech, forced = "admin speech") + speech = sanitize(speech) // Nah, we don't trust them + log_admin("[key_name(usr)] forced [key_name(M)] to say: [speech]") + message_admins("[key_name_admin(usr)] forced [key_name_admin(M)] to say: [speech]") + + else if(href_list["sendtoprison"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locate(href_list["sendtoprison"]) + if(!ismob(M)) + to_chat(usr, "This can only be used on instances of type /mob.", confidential = TRUE) + return + if(isAI(M)) + to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai.", confidential = TRUE) + return + + if(alert(usr, "Send [key_name(M)] to Prison?", "Message", "Yes", "No") != "Yes") + return + + M.forceMove(pick(GLOB.prisonwarp)) + to_chat(M, "You have been sent to Prison!", confidential = TRUE) + + log_admin("[key_name(usr)] has sent [key_name(M)] to Prison!") + message_admins("[key_name_admin(usr)] has sent [key_name_admin(M)] to Prison!") + + else if(href_list["sendbacktolobby"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locate(href_list["sendbacktolobby"]) + + if(!isobserver(M)) + to_chat(usr, "You can only send ghost players back to the Lobby.", confidential = TRUE) + return + + if(!M.client) + to_chat(usr, "[M] doesn't seem to have an active client.", confidential = TRUE) + return + + if(alert(usr, "Send [key_name(M)] back to Lobby?", "Message", "Yes", "No") != "Yes") + return + + log_admin("[key_name(usr)] has sent [key_name(M)] back to the Lobby.") + message_admins("[key_name(usr)] has sent [key_name(M)] back to the Lobby.") + + var/mob/dead/new_player/NP = new() + NP.ckey = M.ckey + qdel(M) + + else if(href_list["tdome1"]) + if(!check_rights(R_FUN)) + return + + if(alert(usr, "Confirm?", "Message", "Yes", "No") != "Yes") + return + + var/mob/M = locate(href_list["tdome1"]) + if(!isliving(M)) + to_chat(usr, "This can only be used on instances of type /mob/living.", confidential = TRUE) + return + if(isAI(M)) + to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai.", confidential = TRUE) + return + var/mob/living/L = M + + for(var/obj/item/I in L) + L.dropItemToGround(I, TRUE) + + L.Unconscious(100) + sleep(5) + L.forceMove(pick(GLOB.tdome1)) + addtimer(CALLBACK(GLOBAL_PROC, /proc/to_chat, L, "You have been sent to the Thunderdome."), 5 SECONDS) + log_admin("[key_name(usr)] has sent [key_name(L)] to the thunderdome. (Team 1)") + message_admins("[key_name_admin(usr)] has sent [key_name_admin(L)] to the thunderdome. (Team 1)") + + else if(href_list["tdome2"]) + if(!check_rights(R_FUN)) + return + + if(alert(usr, "Confirm?", "Message", "Yes", "No") != "Yes") + return + + var/mob/M = locate(href_list["tdome2"]) + if(!isliving(M)) + to_chat(usr, "This can only be used on instances of type /mob/living.", confidential = TRUE) + return + if(isAI(M)) + to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai.", confidential = TRUE) + return + var/mob/living/L = M + + for(var/obj/item/I in L) + L.dropItemToGround(I, TRUE) + + L.Unconscious(100) + sleep(5) + L.forceMove(pick(GLOB.tdome2)) + addtimer(CALLBACK(GLOBAL_PROC, /proc/to_chat, L, "You have been sent to the Thunderdome."), 5 SECONDS) + log_admin("[key_name(usr)] has sent [key_name(L)] to the thunderdome. (Team 2)") + message_admins("[key_name_admin(usr)] has sent [key_name_admin(L)] to the thunderdome. (Team 2)") + + else if(href_list["tdomeadmin"]) + if(!check_rights(R_FUN)) + return + + if(alert(usr, "Confirm?", "Message", "Yes", "No") != "Yes") + return + + var/mob/M = locate(href_list["tdomeadmin"]) + if(!isliving(M)) + to_chat(usr, "This can only be used on instances of type /mob/living.", confidential = TRUE) + return + if(isAI(M)) + to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai.", confidential = TRUE) + return + var/mob/living/L = M + + L.Unconscious(100) + sleep(5) + L.forceMove(pick(GLOB.tdomeadmin)) + addtimer(CALLBACK(GLOBAL_PROC, /proc/to_chat, L, "You have been sent to the Thunderdome."), 5 SECONDS) + log_admin("[key_name(usr)] has sent [key_name(L)] to the thunderdome. (Admin.)") + message_admins("[key_name_admin(usr)] has sent [key_name_admin(L)] to the thunderdome. (Admin.)") + + else if(href_list["tdomeobserve"]) + if(!check_rights(R_FUN)) + return + + if(alert(usr, "Confirm?", "Message", "Yes", "No") != "Yes") + return + + var/mob/M = locate(href_list["tdomeobserve"]) + if(!isliving(M)) + to_chat(usr, "This can only be used on instances of type /mob/living.", confidential = TRUE) + return + if(isAI(M)) + to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai.", confidential = TRUE) + return + var/mob/living/L = M + + for(var/obj/item/I in L) + L.dropItemToGround(I, TRUE) + + if(ishuman(L)) + var/mob/living/carbon/human/observer = L + observer.equip_to_slot_or_del(new /obj/item/clothing/under/suit/black(observer), ITEM_SLOT_ICLOTHING) + observer.equip_to_slot_or_del(new /obj/item/clothing/shoes/sneakers/black(observer), ITEM_SLOT_FEET) + L.Unconscious(100) + sleep(5) + L.forceMove(pick(GLOB.tdomeobserve)) + addtimer(CALLBACK(GLOBAL_PROC, /proc/to_chat, L, "You have been sent to the Thunderdome."), 5 SECONDS) + log_admin("[key_name(usr)] has sent [key_name(L)] to the thunderdome. (Observer.)") + message_admins("[key_name_admin(usr)] has sent [key_name_admin(L)] to the thunderdome. (Observer.)") + + else if(href_list["revive"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/living/L = locate(href_list["revive"]) + if(!istype(L)) + to_chat(usr, "This can only be used on instances of type /mob/living.", confidential = TRUE) + return + + L.revive(full_heal = TRUE, admin_revive = TRUE) + message_admins("Admin [key_name_admin(usr)] healed / revived [key_name_admin(L)]!") + log_admin("[key_name(usr)] healed / Revived [key_name(L)].") + + else if(href_list["makeai"]) + if(!check_rights(R_SPAWN)) + return + + var/mob/living/carbon/human/H = locate(href_list["makeai"]) + if(!istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.", confidential = TRUE) + return + + message_admins("Admin [key_name_admin(usr)] AIized [key_name_admin(H)]!") + log_admin("[key_name(usr)] AIized [key_name(H)].") + H.AIize(TRUE, H.client) + + else if(href_list["makealien"]) + if(!check_rights(R_SPAWN)) + return + + var/mob/living/carbon/human/H = locate(href_list["makealien"]) + if(!istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.", confidential = TRUE) + return + + usr.client.cmd_admin_alienize(H) + + else if(href_list["makeslime"]) + if(!check_rights(R_SPAWN)) + return + + var/mob/living/carbon/human/H = locate(href_list["makeslime"]) + if(!istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.", confidential = TRUE) + return + + usr.client.cmd_admin_slimeize(H) + + else if(href_list["makeblob"]) + if(!check_rights(R_SPAWN)) + return + + var/mob/living/carbon/human/H = locate(href_list["makeblob"]) + if(!istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.", confidential = TRUE) + return + + usr.client.cmd_admin_blobize(H) + + + else if(href_list["makerobot"]) + if(!check_rights(R_SPAWN)) + return + + var/mob/living/carbon/human/H = locate(href_list["makerobot"]) + if(!istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.", confidential = TRUE) + return + + usr.client.cmd_admin_robotize(H) + + else if(href_list["makeanimal"]) + if(!check_rights(R_SPAWN)) + return + + var/mob/M = locate(href_list["makeanimal"]) + if(isnewplayer(M)) + to_chat(usr, "This cannot be used on instances of type /mob/dead/new_player.", confidential = TRUE) + return + + usr.client.cmd_admin_animalize(M) + + else if(href_list["adminplayeropts"]) + var/mob/M = locate(href_list["adminplayeropts"]) + show_player_panel(M) + + else if(href_list["adminplayerobservefollow"]) + if(!isobserver(usr) && !check_rights(R_ADMIN)) + return + + var/atom/movable/AM = locate(href_list["adminplayerobservefollow"]) + + var/client/C = usr.client + var/can_ghost = TRUE + if(!isobserver(usr)) + can_ghost = C.admin_ghost() + + if(!can_ghost) + return + var/mob/dead/observer/A = C.mob + A.ManualFollow(AM) + + else if(href_list["admingetmovable"]) + if(!check_rights(R_ADMIN)) + return + + var/atom/movable/AM = locate(href_list["admingetmovable"]) + if(QDELETED(AM)) + return + AM.forceMove(get_turf(usr)) + + else if(href_list["adminplayerobservecoodjump"]) + if(!isobserver(usr) && !check_rights(R_ADMIN)) + return + + var/x = text2num(href_list["X"]) + var/y = text2num(href_list["Y"]) + var/z = text2num(href_list["Z"]) + + var/client/C = usr.client + if(!isobserver(usr)) + C.admin_ghost() + sleep(2) + C.jumptocoord(x,y,z) + + else if(href_list["adminchecklaws"]) + if(!check_rights(R_ADMIN)) + return + output_ai_laws() + + else if(href_list["admincheckdevilinfo"]) + if(!check_rights(R_ADMIN)) + return + var/mob/M = locate(href_list["admincheckdevilinfo"]) + output_devil_info(M) + + else if(href_list["adminmoreinfo"]) + var/mob/M = locate(href_list["adminmoreinfo"]) in GLOB.mob_list + if(!ismob(M)) + to_chat(usr, "This can only be used on instances of type /mob.", confidential = TRUE) + return + + var/location_description = "" + var/special_role_description = "" + var/health_description = "" + var/gender_description = "" + var/turf/T = get_turf(M) + + //Location + if(isturf(T)) + if(isarea(T.loc)) + location_description = "([M.loc == T ? "at coordinates " : "in [M.loc] at coordinates "] [T.x], [T.y], [T.z] in area [T.loc])" + else + location_description = "([M.loc == T ? "at coordinates " : "in [M.loc] at coordinates "] [T.x], [T.y], [T.z])" + + //Job + antagonist + if(M.mind) + special_role_description = "Role: [M.mind.assigned_role]; Antagonist: [M.mind.special_role]" + else + special_role_description = "Role: Mind datum missing Antagonist: Mind datum missing" + + //Health + if(isliving(M)) + var/mob/living/L = M + var/status + switch (M.stat) + if(CONSCIOUS) + status = "Alive" + if(SOFT_CRIT) + status = "Dying" + if(UNCONSCIOUS) + status = "[L.InCritical() ? "Unconscious and Dying" : "Unconscious"]" + if(DEAD) + status = "Dead" + health_description = "Status = [status]" + health_description += "
                    Oxy: [L.getOxyLoss()] - Tox: [L.getToxLoss()] - Fire: [L.getFireLoss()] - Brute: [L.getBruteLoss()] - Clone: [L.getCloneLoss()] - Brain: [L.getOrganLoss(ORGAN_SLOT_BRAIN)] - Stamina: [L.getStaminaLoss()]" + else + health_description = "This mob type has no health to speak of." + + //Gender + switch(M.gender) + if(MALE,FEMALE,PLURAL) + gender_description = "[M.gender]" + else + gender_description = "[M.gender]" + + to_chat(src.owner, "Info about [M.name]: ", confidential = TRUE) + to_chat(src.owner, "Mob type = [M.type]; Gender = [gender_description] Damage = [health_description]", confidential = TRUE) + to_chat(src.owner, "Name = [M.name]; Real_name = [M.real_name]; Mind_name = [M.mind?"[M.mind.name]":""]; Key = [M.key];", confidential = TRUE) + to_chat(src.owner, "Location = [location_description];", confidential = TRUE) + to_chat(src.owner, "[special_role_description]", confidential = TRUE) + to_chat(src.owner, ADMIN_FULLMONTY_NONAME(M), confidential = TRUE) + + else if(href_list["addjobslot"]) + if(!check_rights(R_ADMIN)) + return + + var/Add = href_list["addjobslot"] + + for(var/datum/job/job in SSjob.occupations) + if(job.title == Add) + job.total_positions += 1 + break + + src.manage_free_slots() + + + else if(href_list["customjobslot"]) + if(!check_rights(R_ADMIN)) + return + + var/Add = href_list["customjobslot"] + + for(var/datum/job/job in SSjob.occupations) + if(job.title == Add) + var/newtime = null + newtime = input(usr, "How many jebs do you want?", "Add wanted posters", "[newtime]") as num|null + if(!newtime) + to_chat(src.owner, "Setting to amount of positions filled for the job", confidential = TRUE) + job.total_positions = job.current_positions + break + job.total_positions = newtime + + src.manage_free_slots() + + else if(href_list["removejobslot"]) + if(!check_rights(R_ADMIN)) + return + + var/Remove = href_list["removejobslot"] + + for(var/datum/job/job in SSjob.occupations) + if(job.title == Remove && job.total_positions - job.current_positions > 0) + job.total_positions -= 1 + break + + src.manage_free_slots() + + else if(href_list["unlimitjobslot"]) + if(!check_rights(R_ADMIN)) + return + + var/Unlimit = href_list["unlimitjobslot"] + + for(var/datum/job/job in SSjob.occupations) + if(job.title == Unlimit) + job.total_positions = -1 + break + + src.manage_free_slots() + + else if(href_list["limitjobslot"]) + if(!check_rights(R_ADMIN)) + return + + var/Limit = href_list["limitjobslot"] + + for(var/datum/job/job in SSjob.occupations) + if(job.title == Limit) + job.total_positions = job.current_positions + break + + src.manage_free_slots() + + + else if(href_list["adminspawncookie"]) + if(!check_rights(R_ADMIN|R_FUN)) + return + + var/mob/living/carbon/human/H = locate(href_list["adminspawncookie"]) + if(!ishuman(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.", confidential = TRUE) + return + //let's keep it simple + //milk to plasmemes and skeletons, meat to lizards, electricity bars to ethereals, cookies to everyone else + var/cookiealt = /obj/item/reagent_containers/food/snacks/cookie + if(isskeleton(H)) + cookiealt = /obj/item/reagent_containers/food/condiment/milk + else if(isplasmaman(H)) + cookiealt = /obj/item/reagent_containers/food/condiment/milk + else if(isethereal(H)) + cookiealt = /obj/item/reagent_containers/food/snacks/energybar + else if(islizard(H)) + cookiealt = /obj/item/reagent_containers/food/snacks/meat/slab + var/obj/item/new_item = new cookiealt(H) + if(H.put_in_hands(new_item)) + H.update_inv_hands() + else + qdel(new_item) + log_admin("[key_name(H)] has their hands full, so they did not receive their [new_item.name], spawned by [key_name(src.owner)].") + message_admins("[key_name(H)] has their hands full, so they did not receive their [new_item.name], spawned by [key_name(src.owner)].") + return + + log_admin("[key_name(H)] got their [new_item], spawned by [key_name(src.owner)].") + message_admins("[key_name(H)] got their [new_item], spawned by [key_name(src.owner)].") + SSblackbox.record_feedback("amount", "admin_cookies_spawned", 1) + to_chat(H, "Your prayers have been answered!! You received the best [new_item.name]!", confidential = TRUE) + SEND_SOUND(H, sound('sound/effects/pray_chaplain.ogg')) + + else if(href_list["adminsmite"]) + if(!check_rights(R_ADMIN|R_FUN)) + return + + var/mob/living/carbon/human/H = locate(href_list["adminsmite"]) in GLOB.mob_list + if(!H || !istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human", confidential = TRUE) + return + + usr.client.smite(H) + + else if(href_list["CentComReply"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locate(href_list["CentComReply"]) + usr.client.admin_headset_message(M, RADIO_CHANNEL_CENTCOM) + + else if(href_list["SyndicateReply"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locate(href_list["SyndicateReply"]) + usr.client.admin_headset_message(M, RADIO_CHANNEL_SYNDICATE) + + else if(href_list["HeadsetMessage"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locate(href_list["HeadsetMessage"]) + usr.client.admin_headset_message(M) + + else if(href_list["reject_custom_name"]) + if(!check_rights(R_ADMIN)) + return + var/obj/item/station_charter/charter = locate(href_list["reject_custom_name"]) + if(istype(charter)) + charter.reject_proposed(usr) + else if(href_list["jumpto"]) + if(!isobserver(usr) && !check_rights(R_ADMIN)) + return + + var/mob/M = locate(href_list["jumpto"]) + usr.client.jumptomob(M) + + else if(href_list["getmob"]) + if(!check_rights(R_ADMIN)) + return + + if(alert(usr, "Confirm?", "Message", "Yes", "No") != "Yes") + return + var/mob/M = locate(href_list["getmob"]) + usr.client.Getmob(M) + + else if(href_list["sendmob"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locate(href_list["sendmob"]) + usr.client.sendmob(M) + + else if(href_list["narrateto"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locate(href_list["narrateto"]) + usr.client.cmd_admin_direct_narrate(M) + + else if(href_list["subtlemessage"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locate(href_list["subtlemessage"]) + usr.client.cmd_admin_subtle_message(M) + + else if(href_list["playsoundto"]) + if(!check_rights(R_SOUND)) + return + + var/mob/M = locate(href_list["playsoundto"]) + var/S = input("", "Select a sound file",) as null|sound + if(S) + usr.client.play_direct_mob_sound(S, M) + + else if(href_list["individuallog"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locate(href_list["individuallog"]) in GLOB.mob_list + if(!ismob(M)) + to_chat(usr, "This can only be used on instances of type /mob.", confidential = TRUE) + return + + show_individual_logging_panel(M, href_list["log_src"], href_list["log_type"]) + else if(href_list["languagemenu"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locate(href_list["languagemenu"]) in GLOB.mob_list + if(!ismob(M)) + to_chat(usr, "This can only be used on instances of type /mob.", confidential = TRUE) + return + var/datum/language_holder/H = M.get_language_holder() + H.open_language_menu(usr) + + else if(href_list["traitor"]) + if(!check_rights(R_ADMIN)) + return + + if(!SSticker.HasRoundStarted()) + alert("The game hasn't started yet!") + return + + var/mob/M = locate(href_list["traitor"]) + if(!ismob(M)) + var/datum/mind/D = M + if(!istype(D)) + to_chat(usr, "This can only be used on instances of type /mob and /mind", confidential = TRUE) + return + else + D.traitor_panel() + else + show_traitor_panel(M) + + else if(href_list["skill"]) + if(!check_rights(R_ADMIN)) + return + + if(!SSticker.HasRoundStarted()) + alert("The game hasn't started yet!") + return + + var/target = locate(href_list["skill"]) + var/datum/mind/target_mind + if(ismob(target)) + var/mob/target_mob = target + target_mind = target_mob.mind + else if (istype(target, /datum/mind)) + target_mind = target + else + to_chat(usr, "This can only be used on instances of type /mob and /mind", confidential = TRUE) + return + show_skill_panel(target_mind) + + else if(href_list["borgpanel"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locate(href_list["borgpanel"]) + if(!iscyborg(M)) + to_chat(usr, "This can only be used on cyborgs", confidential = TRUE) + else + open_borgopanel(M) + + else if(href_list["initmind"]) + if(!check_rights(R_ADMIN)) + return + var/mob/M = locate(href_list["initmind"]) + if(!ismob(M) || M.mind) + to_chat(usr, "This can only be used on instances on mindless mobs", confidential = TRUE) + return + M.mind_initialize() + + else if(href_list["create_object"]) + if(!check_rights(R_SPAWN)) + return + return create_object(usr) + + else if(href_list["quick_create_object"]) + if(!check_rights(R_SPAWN)) + return + return quick_create_object(usr) + + else if(href_list["create_turf"]) + if(!check_rights(R_SPAWN)) + return + return create_turf(usr) + + else if(href_list["create_mob"]) + if(!check_rights(R_SPAWN)) + return + return create_mob(usr) + + else if(href_list["dupe_marked_datum"]) + if(!check_rights(R_SPAWN)) + return + return DuplicateObject(marked_datum, perfectcopy=1, newloc=get_turf(usr)) + + else if(href_list["object_list"]) //this is the laggiest thing ever + if(!check_rights(R_SPAWN)) + return + + var/atom/loc = usr.loc + + var/dirty_paths + if (istext(href_list["object_list"])) + dirty_paths = list(href_list["object_list"]) + else if (istype(href_list["object_list"], /list)) + dirty_paths = href_list["object_list"] + + var/paths = list() + + for(var/dirty_path in dirty_paths) + var/path = text2path(dirty_path) + if(!path) + continue + else if(!ispath(path, /obj) && !ispath(path, /turf) && !ispath(path, /mob)) + continue + paths += path + + if(!paths) + alert("The path list you sent is empty.") + return + if(length(paths) > 5) + alert("Select fewer object types, (max 5).") + return + + var/list/offset = splittext(href_list["offset"],",") + var/number = clamp(text2num(href_list["object_count"]), 1, ADMIN_SPAWN_CAP) + var/X = offset.len > 0 ? text2num(offset[1]) : 0 + var/Y = offset.len > 1 ? text2num(offset[2]) : 0 + var/Z = offset.len > 2 ? text2num(offset[3]) : 0 + var/obj_dir = text2num(href_list["object_dir"]) + if(obj_dir && !(obj_dir in list(1,2,4,8,5,6,9,10))) + obj_dir = null + var/obj_name = sanitize(href_list["object_name"]) + + + var/atom/target //Where the object will be spawned + var/where = href_list["object_where"] + if (!( where in list("onfloor","frompod","inhand","inmarked") )) + where = "onfloor" + + + switch(where) + if("inhand") + if (!iscarbon(usr) && !iscyborg(usr)) + to_chat(usr, "Can only spawn in hand when you're a carbon mob or cyborg.", confidential = TRUE) + where = "onfloor" + target = usr + + if("onfloor", "frompod") + switch(href_list["offset_type"]) + if ("absolute") + target = locate(0 + X,0 + Y,0 + Z) + if ("relative") + target = locate(loc.x + X,loc.y + Y,loc.z + Z) + if("inmarked") + if(!marked_datum) + to_chat(usr, "You don't have any object marked. Abandoning spawn.", confidential = TRUE) + return + else if(!istype(marked_datum, /atom)) + to_chat(usr, "The object you have marked cannot be used as a target. Target must be of type /atom. Abandoning spawn.", confidential = TRUE) + return + else + target = marked_datum + + var/obj/structure/closet/supplypod/centcompod/pod + + if(target) + if(where == "frompod") + pod = new() + + for (var/path in paths) + for (var/i = 0; i < number; i++) + if(path in typesof(/turf)) + var/turf/O = target + var/turf/N = O.ChangeTurf(path) + if(N && obj_name) + N.name = obj_name + else + var/atom/O + if(where == "frompod") + O = new path(pod) + else + O = new path(target) + + if(!QDELETED(O)) + O.flags_1 |= ADMIN_SPAWNED_1 + if(obj_dir) + O.setDir(obj_dir) + if(obj_name) + O.name = obj_name + if(ismob(O)) + var/mob/M = O + M.real_name = obj_name + if(where == "inhand" && isliving(usr) && isitem(O)) + var/mob/living/L = usr + var/obj/item/I = O + L.put_in_hands(I) + if(iscyborg(L)) + var/mob/living/silicon/robot/R = L + if(R.module) + R.module.add_module(I, TRUE, TRUE) + R.activate_module(I) + + if(pod) + new /obj/effect/DPtarget(target, pod) + + if (number == 1) + log_admin("[key_name(usr)] created a [english_list(paths)]") + for(var/path in paths) + if(ispath(path, /mob)) + message_admins("[key_name_admin(usr)] created a [english_list(paths)]") + break + else + log_admin("[key_name(usr)] created [number]ea [english_list(paths)]") + for(var/path in paths) + if(ispath(path, /mob)) + message_admins("[key_name_admin(usr)] created [number]ea [english_list(paths)]") + 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 + src.admincaster_screen = 18 //The ac_ prefix before the hrefs stands for AdminCaster. + src.access_news_network() + + else if(href_list["ac_set_channel_name"]) + if(!check_rights(R_ADMIN)) + return + src.admincaster_feed_channel.channel_name = stripped_input(usr, "Provide a Feed Channel Name.", "Network Channel Handler", "") + src.access_news_network() + + else if(href_list["ac_set_channel_lock"]) + if(!check_rights(R_ADMIN)) + return + src.admincaster_feed_channel.locked = !src.admincaster_feed_channel.locked + src.access_news_network() + + else if(href_list["ac_submit_new_channel"]) + if(!check_rights(R_ADMIN)) + return + var/check = 0 + for(var/datum/newscaster/feed_channel/FC in GLOB.news_network.network_channels) + if(FC.channel_name == src.admincaster_feed_channel.channel_name) + check = 1 + break + if(src.admincaster_feed_channel.channel_name == "" || src.admincaster_feed_channel.channel_name == "\[REDACTED\]" || check ) + src.admincaster_screen=7 + else + var/choice = alert("Please confirm Feed channel creation.","Network Channel Handler","Confirm","Cancel") + if(choice=="Confirm") + GLOB.news_network.CreateFeedChannel(src.admincaster_feed_channel.channel_name, src.admin_signature, src.admincaster_feed_channel.locked, 1) + SSblackbox.record_feedback("tally", "newscaster_channels", 1, src.admincaster_feed_channel.channel_name) + log_admin("[key_name(usr)] created command feed channel: [src.admincaster_feed_channel.channel_name]!") + src.admincaster_screen=5 + src.access_news_network() + + else if(href_list["ac_set_channel_receiving"]) + if(!check_rights(R_ADMIN)) + return + var/list/available_channels = list() + for(var/datum/newscaster/feed_channel/F in GLOB.news_network.network_channels) + available_channels += F.channel_name + src.admincaster_feed_channel.channel_name = adminscrub(input(usr, "Choose receiving Feed Channel.", "Network Channel Handler") in sortList(available_channels) ) + src.access_news_network() + + else if(href_list["ac_set_new_message"]) + if(!check_rights(R_ADMIN)) + return + src.admincaster_feed_message.body = adminscrub(stripped_input(usr, "Write your Feed story.", "Network Channel Handler", "")) + src.access_news_network() + + else if(href_list["ac_submit_new_message"]) + if(!check_rights(R_ADMIN)) + return + if(src.admincaster_feed_message.returnBody(-1) =="" || src.admincaster_feed_message.returnBody(-1) =="\[REDACTED\]" || src.admincaster_feed_channel.channel_name == "" ) + src.admincaster_screen = 6 + else + GLOB.news_network.SubmitArticle(src.admincaster_feed_message.returnBody(-1), src.admin_signature, src.admincaster_feed_channel.channel_name, null, 1) + SSblackbox.record_feedback("amount", "newscaster_stories", 1) + src.admincaster_screen=4 + + for(var/obj/machinery/newscaster/NEWSCASTER in GLOB.allCasters) + NEWSCASTER.newsAlert(src.admincaster_feed_channel.channel_name) + + log_admin("[key_name(usr)] submitted a feed story to channel: [src.admincaster_feed_channel.channel_name]!") + src.access_news_network() + + else if(href_list["ac_create_channel"]) + if(!check_rights(R_ADMIN)) + return + src.admincaster_screen=2 + src.access_news_network() + + else if(href_list["ac_create_feed_story"]) + if(!check_rights(R_ADMIN)) + return + src.admincaster_screen=3 + src.access_news_network() + + else if(href_list["ac_menu_censor_story"]) + if(!check_rights(R_ADMIN)) + return + src.admincaster_screen=10 + src.access_news_network() + + else if(href_list["ac_menu_censor_channel"]) + if(!check_rights(R_ADMIN)) + return + src.admincaster_screen=11 + src.access_news_network() + + else if(href_list["ac_menu_wanted"]) + if(!check_rights(R_ADMIN)) + return + var/already_wanted = 0 + if(GLOB.news_network.wanted_issue.active) + already_wanted = 1 + + if(already_wanted) + src.admincaster_wanted_message.criminal = GLOB.news_network.wanted_issue.criminal + src.admincaster_wanted_message.body = GLOB.news_network.wanted_issue.body + src.admincaster_screen = 14 + src.access_news_network() + + else if(href_list["ac_set_wanted_name"]) + if(!check_rights(R_ADMIN)) + return + src.admincaster_wanted_message.criminal = adminscrub(stripped_input(usr, "Provide the name of the Wanted person.", "Network Security Handler", "")) + src.access_news_network() + + else if(href_list["ac_set_wanted_desc"]) + if(!check_rights(R_ADMIN)) + return + src.admincaster_wanted_message.body = adminscrub(stripped_input(usr, "Provide the a description of the Wanted person and any other details you deem important.", "Network Security Handler", "")) + src.access_news_network() + + else if(href_list["ac_submit_wanted"]) + if(!check_rights(R_ADMIN)) + return + var/input_param = text2num(href_list["ac_submit_wanted"]) + if(src.admincaster_wanted_message.criminal == "" || src.admincaster_wanted_message.body == "") + src.admincaster_screen = 16 + else + var/choice = alert("Please confirm Wanted Issue [(input_param==1) ? ("creation.") : ("edit.")]","Network Security Handler","Confirm","Cancel") + if(choice=="Confirm") + if(input_param==1) //If input_param == 1 we're submitting a new wanted issue. At 2 we're just editing an existing one. See the else below + GLOB.news_network.submitWanted(admincaster_wanted_message.criminal, admincaster_wanted_message.body, admin_signature, null, 1, 1) + src.admincaster_screen = 15 + else + GLOB.news_network.submitWanted(admincaster_wanted_message.criminal, admincaster_wanted_message.body, admin_signature) + src.admincaster_screen = 19 + log_admin("[key_name(usr)] issued a Station-wide Wanted Notification for [src.admincaster_wanted_message.criminal]!") + src.access_news_network() + + else if(href_list["ac_cancel_wanted"]) + if(!check_rights(R_ADMIN)) + return + var/choice = alert("Please confirm Wanted Issue removal.","Network Security Handler","Confirm","Cancel") + if(choice=="Confirm") + GLOB.news_network.deleteWanted() + src.admincaster_screen=17 + src.access_news_network() + + else if(href_list["ac_censor_channel_author"]) + if(!check_rights(R_ADMIN)) + return + var/datum/newscaster/feed_channel/FC = locate(href_list["ac_censor_channel_author"]) + FC.toggleCensorAuthor() + src.access_news_network() + + else if(href_list["ac_censor_channel_story_author"]) + if(!check_rights(R_ADMIN)) + return + var/datum/newscaster/feed_message/MSG = locate(href_list["ac_censor_channel_story_author"]) + MSG.toggleCensorAuthor() + src.access_news_network() + + else if(href_list["ac_censor_channel_story_body"]) + if(!check_rights(R_ADMIN)) + return + var/datum/newscaster/feed_message/MSG = locate(href_list["ac_censor_channel_story_body"]) + MSG.toggleCensorBody() + src.access_news_network() + + else if(href_list["ac_pick_d_notice"]) + if(!check_rights(R_ADMIN)) + return + var/datum/newscaster/feed_channel/FC = locate(href_list["ac_pick_d_notice"]) + src.admincaster_feed_channel = FC + src.admincaster_screen=13 + src.access_news_network() + + else if(href_list["ac_toggle_d_notice"]) + if(!check_rights(R_ADMIN)) + return + var/datum/newscaster/feed_channel/FC = locate(href_list["ac_toggle_d_notice"]) + FC.toggleCensorDclass() + src.access_news_network() + + else if(href_list["ac_view"]) + if(!check_rights(R_ADMIN)) + return + src.admincaster_screen=1 + src.access_news_network() + + else if(href_list["ac_setScreen"]) //Brings us to the main menu and resets all fields~ + if(!check_rights(R_ADMIN)) + return + src.admincaster_screen = text2num(href_list["ac_setScreen"]) + if (src.admincaster_screen == 0) + if(src.admincaster_feed_channel) + src.admincaster_feed_channel = new /datum/newscaster/feed_channel + if(src.admincaster_feed_message) + src.admincaster_feed_message = new /datum/newscaster/feed_message + if(admincaster_wanted_message) + admincaster_wanted_message = new /datum/newscaster/wanted_message + src.access_news_network() + + else if(href_list["ac_show_channel"]) + if(!check_rights(R_ADMIN)) + return + var/datum/newscaster/feed_channel/FC = locate(href_list["ac_show_channel"]) + src.admincaster_feed_channel = FC + src.admincaster_screen = 9 + src.access_news_network() + + else if(href_list["ac_pick_censor_channel"]) + if(!check_rights(R_ADMIN)) + return + var/datum/newscaster/feed_channel/FC = locate(href_list["ac_pick_censor_channel"]) + src.admincaster_feed_channel = FC + src.admincaster_screen = 12 + src.access_news_network() + + else if(href_list["ac_refresh"]) + if(!check_rights(R_ADMIN)) + return + src.access_news_network() + + else if(href_list["ac_set_signature"]) + if(!check_rights(R_ADMIN)) + return + src.admin_signature = adminscrub(input(usr, "Provide your desired signature.", "Network Identity Handler", "")) + src.access_news_network() + + else if(href_list["ac_del_comment"]) + if(!check_rights(R_ADMIN)) + return + var/datum/newscaster/feed_comment/FC = locate(href_list["ac_del_comment"]) + var/datum/newscaster/feed_message/FM = locate(href_list["ac_del_comment_msg"]) + FM.comments -= FC + qdel(FC) + src.access_news_network() + + else if(href_list["ac_lock_comment"]) + if(!check_rights(R_ADMIN)) + return + var/datum/newscaster/feed_message/FM = locate(href_list["ac_lock_comment"]) + FM.locked ^= 1 + src.access_news_network() + + else if(href_list["check_antagonist"]) + if(!check_rights(R_ADMIN)) + return + usr.client.check_antagonists() + + else if(href_list["kick_all_from_lobby"]) + if(!check_rights(R_ADMIN)) + return + if(SSticker.IsRoundInProgress()) + var/afkonly = text2num(href_list["afkonly"]) + if(alert("Are you sure you want to kick all [afkonly ? "AFK" : ""] clients from the lobby??","Message","Yes","Cancel") != "Yes") + to_chat(usr, "Kick clients from lobby aborted", confidential = TRUE) + return + var/list/listkicked = kick_clients_in_lobby("You were kicked from the lobby by [usr.client.holder.fakekey ? "an Administrator" : "[usr.client.key]"].", afkonly) + + var/strkicked = "" + for(var/name in listkicked) + strkicked += "[name], " + message_admins("[key_name_admin(usr)] has kicked [afkonly ? "all AFK" : "all"] clients from the lobby. [length(listkicked)] clients kicked: [strkicked ? strkicked : "--"]") + log_admin("[key_name(usr)] has kicked [afkonly ? "all AFK" : "all"] clients from the lobby. [length(listkicked)] clients kicked: [strkicked ? strkicked : "--"]") + else + to_chat(usr, "You may only use this when the game is running.", confidential = TRUE) + + else if(href_list["create_outfit_finalize"]) + if(!check_rights(R_ADMIN)) + return + create_outfit_finalize(usr,href_list) + else if(href_list["load_outfit"]) + if(!check_rights(R_ADMIN)) + return + load_outfit(usr) + else if(href_list["create_outfit_menu"]) + if(!check_rights(R_ADMIN)) + return + create_outfit(usr) + else if(href_list["delete_outfit"]) + if(!check_rights(R_ADMIN)) + return + var/datum/outfit/O = locate(href_list["chosen_outfit"]) in GLOB.custom_outfits + delete_outfit(usr,O) + else if(href_list["save_outfit"]) + if(!check_rights(R_ADMIN)) + return + var/datum/outfit/O = locate(href_list["chosen_outfit"]) in GLOB.custom_outfits + save_outfit(usr,O) + else if(href_list["set_selfdestruct_code"]) + if(!check_rights(R_ADMIN)) + return + var/code = random_nukecode() + for(var/obj/machinery/nuclearbomb/selfdestruct/SD in GLOB.nuke_list) + SD.r_code = code + message_admins("[key_name_admin(usr)] has set the self-destruct \ + code to \"[code]\".") + + else if(href_list["add_station_goal"]) + if(!check_rights(R_ADMIN)) + return + var/list/type_choices = typesof(/datum/station_goal) + var/picked = input("Choose goal type") in type_choices|null + if(!picked) + return + var/datum/station_goal/G = new picked() + if(picked == /datum/station_goal) + var/newname = input("Enter goal name:") as text|null + if(!newname) + return + G.name = newname + var/description = input("Enter CentCom message contents:") as message|null + if(!description) + return + G.report_message = description + message_admins("[key_name(usr)] created \"[G.name]\" station goal.") + SSticker.mode.station_goals += G + modify_goals() + + else if(href_list["viewruntime"]) + var/datum/error_viewer/error_viewer = locate(href_list["viewruntime"]) + if(!istype(error_viewer)) + to_chat(usr, "That runtime viewer no longer exists.", confidential = TRUE) + return + + if(href_list["viewruntime_backto"]) + error_viewer.show_to(owner, locate(href_list["viewruntime_backto"]), href_list["viewruntime_linear"]) + else + error_viewer.show_to(owner, null, href_list["viewruntime_linear"]) + + else if(href_list["showrelatedacc"]) + if(!check_rights(R_ADMIN)) + return + var/client/C = locate(href_list["client"]) in GLOB.clients + var/thing_to_check + if(href_list["showrelatedacc"] == "cid") + thing_to_check = C.related_accounts_cid + else + thing_to_check = C.related_accounts_ip + thing_to_check = splittext(thing_to_check, ", ") + + + var/list/dat = list() + dat += thing_to_check + +// usr << browse(dat.Join("
                    "), "window=related_[C];size=420x300") + + var/datum/browser/popup = new(usr, "related_[C]", "Related accounts by [uppertext(href_list["showrelatedacc"])]:", 425, 300) + popup.set_content(dat.Join("
                    ")) + popup.open() + + else if(href_list["centcomlookup"]) + if(!check_rights(R_ADMIN)) + return + + if(!CONFIG_GET(string/centcom_ban_db)) + to_chat(usr, "Centcom Galactic Ban DB is disabled!") + return + + var/ckey = href_list["centcomlookup"] + + // Make the request + var/datum/http_request/request = new() + request.prepare(RUSTG_HTTP_METHOD_GET, "[CONFIG_GET(string/centcom_ban_db)]/[ckey]", "", "") + request.begin_async() + UNTIL(request.is_complete() || !usr) + if (!usr) + return + var/datum/http_response/response = request.into_response() + + var/list/bans + + var/list/dat = list("") + + if(response.errored) + dat += "
                    Failed to connect to CentCom." + else if(response.status_code != 200) + dat += "
                    Failed to connect to CentCom. Status code: [response.status_code]" + else + if(response.body == "[]") + dat += "
                    0 bans detected for [ckey]
                    " + else + bans = json_decode(response["body"]) + dat += "
                    [bans.len] ban\s detected for [ckey]
                    " + for(var/list/ban in bans) + dat += "Server: [sanitize(ban["sourceName"])]
                    " + dat += "RP Level: [sanitize(ban["sourceRoleplayLevel"])]
                    " + dat += "Type: [sanitize(ban["type"])]
                    " + dat += "Banned By: [sanitize(ban["bannedBy"])]
                    " + dat += "Reason: [sanitize(ban["reason"])]
                    " + dat += "Datetime: [sanitize(ban["bannedOn"])]
                    " + var/expiration = ban["expires"] + dat += "Expires: [expiration ? "[sanitize(expiration)]" : "Permanent"]
                    " + if(ban["type"] == "job") + dat += "Jobs: " + var/list/jobs = ban["jobs"] + dat += sanitize(jobs.Join(", ")) + dat += "
                    " + dat += "
                    " + + dat += "
                    " + var/datum/browser/popup = new(usr, "centcomlookup-[ckey]", "
                    Central Command Galactic Ban Database
                    ", 700, 600) + popup.set_content(dat.Join()) + popup.open(0) + + else if(href_list["modantagrep"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locate(href_list["mob"]) in GLOB.mob_list + var/client/C = M.client + usr.client.cmd_admin_mod_antag_rep(C, href_list["modantagrep"]) + show_player_panel(M) + + else if(href_list["slowquery"]) + if(!check_rights(R_ADMIN)) + return + var/answer = href_list["slowquery"] + if(answer == "yes") + log_query_debug("[usr.key] | Reported a server hang") + if(alert(usr, "Had you just press any admin buttons?", "Query server hang report", "Yes", "No") == "Yes") + var/response = input(usr,"What were you just doing?","Query server hang report") as null|text + if(response) + log_query_debug("[usr.key] | [response]") + else if(answer == "no") + log_query_debug("[usr.key] | Reported no server hang") + + else if(href_list["ctf_toggle"]) + if(!check_rights(R_ADMIN)) + return + toggle_all_ctf(usr) + + else if(href_list["rebootworld"]) + if(!check_rights(R_ADMIN)) + return + var/confirm = alert("Are you sure you want to reboot the server?", "Confirm Reboot", "Yes", "No") + if(confirm == "No") + return + if(confirm == "Yes") + restart() + + else if(href_list["check_teams"]) + if(!check_rights(R_ADMIN)) + return + check_teams() + + else if(href_list["team_command"]) + if(!check_rights(R_ADMIN)) + return + switch(href_list["team_command"]) + if("create_team") + admin_create_team(usr) + if("rename_team") + var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams + if(T) + T.admin_rename(usr) + if("communicate") + var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams + if(T) + T.admin_communicate(usr) + if("delete_team") + var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams + if(T) + T.admin_delete(usr) + if("add_objective") + var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams + if(T) + T.admin_add_objective(usr) + if("remove_objective") + var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams + if(!T) + return + var/datum/objective/O = locate(href_list["tobjective"]) in T.objectives + if(O) + T.admin_remove_objective(usr,O) + if("add_member") + var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams + if(T) + T.admin_add_member(usr) + if("remove_member") + var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams + if(!T) + return + var/datum/mind/M = locate(href_list["tmember"]) in T.members + if(M) + T.admin_remove_member(usr,M) + check_teams() + + else if(href_list["newbankey"]) + var/player_key = href_list["newbankey"] + var/player_ip = href_list["newbanip"] + var/player_cid = href_list["newbancid"] + ban_panel(player_key, player_ip, player_cid) + + else if(href_list["intervaltype"]) //check for ban panel, intervaltype is used as it's the only value which will always be present + if(href_list["roleban_delimiter"]) + ban_parse_href(href_list) + else + ban_parse_href(href_list, TRUE) + + else if(href_list["searchunbankey"] || href_list["searchunbanadminkey"] || href_list["searchunbanip"] || href_list["searchunbancid"]) + var/player_key = href_list["searchunbankey"] + var/admin_key = href_list["searchunbanadminkey"] + var/player_ip = href_list["searchunbanip"] + var/player_cid = href_list["searchunbancid"] + unban_panel(player_key, admin_key, player_ip, player_cid) + + else if(href_list["unbanpagecount"]) + var/page = href_list["unbanpagecount"] + var/player_key = href_list["unbankey"] + var/admin_key = href_list["unbanadminkey"] + var/player_ip = href_list["unbanip"] + var/player_cid = href_list["unbancid"] + unban_panel(player_key, admin_key, player_ip, player_cid, page) + + else if(href_list["editbanid"]) + var/edit_id = href_list["editbanid"] + var/player_key = href_list["editbankey"] + var/player_ip = href_list["editbanip"] + var/player_cid = href_list["editbancid"] + var/role = href_list["editbanrole"] + var/duration = href_list["editbanduration"] + var/applies_to_admins = text2num(href_list["editbanadmins"]) + var/reason = url_decode(href_list["editbanreason"]) + var/page = href_list["editbanpage"] + var/admin_key = href_list["editbanadminkey"] + ban_panel(player_key, player_ip, player_cid, role, duration, applies_to_admins, reason, edit_id, page, admin_key) + + else if(href_list["unbanid"]) + var/ban_id = href_list["unbanid"] + var/player_key = href_list["unbankey"] + var/player_ip = href_list["unbanip"] + var/player_cid = href_list["unbancid"] + var/role = href_list["unbanrole"] + var/page = href_list["unbanpage"] + var/admin_key = href_list["unbanadminkey"] + unban(ban_id, player_key, player_ip, player_cid, role, page, admin_key) + + else if(href_list["unbanlog"]) + var/ban_id = href_list["unbanlog"] + ban_log(ban_id) + + else if(href_list["beakerpanel"]) + beaker_panel_act(href_list) + + else if(href_list["reloadpolls"]) + GLOB.polls.Cut() + GLOB.poll_options.Cut() + load_poll_data() + poll_list_panel() + + else if(href_list["newpoll"]) + poll_management_panel() + + else if(href_list["editpoll"]) + var/datum/poll_question/poll = locate(href_list["editpoll"]) in GLOB.polls + poll_management_panel(poll) + + else if(href_list["deletepoll"]) + var/datum/poll_question/poll = locate(href_list["deletepoll"]) in GLOB.polls + poll.delete_poll() + poll_list_panel() + + else if(href_list["initializepoll"]) + poll_parse_href(href_list) + + else if(href_list["submitpoll"]) + var/datum/poll_question/poll = locate(href_list["submitpoll"]) in GLOB.polls + poll_parse_href(href_list, poll) + + else if(href_list["clearpollvotes"]) + var/datum/poll_question/poll = locate(href_list["clearpollvotes"]) in GLOB.polls + poll.clear_poll_votes() + poll_management_panel(poll) + + else if(href_list["addpolloption"]) + var/datum/poll_question/poll = locate(href_list["addpolloption"]) in GLOB.polls + poll_option_panel(poll) + + else if(href_list["editpolloption"]) + var/datum/poll_option/option = locate(href_list["editpolloption"]) in GLOB.poll_options + var/datum/poll_question/poll = locate(href_list["parentpoll"]) in GLOB.polls + poll_option_panel(poll, option) + + else if(href_list["deletepolloption"]) + var/datum/poll_option/option = locate(href_list["deletepolloption"]) in GLOB.poll_options + var/datum/poll_question/poll = option.delete_option() + poll_management_panel(poll) + + else if(href_list["submitoption"]) + var/datum/poll_option/option = locate(href_list["submitoption"]) in GLOB.poll_options + var/datum/poll_question/poll = locate(href_list["submitoptionpoll"]) in GLOB.polls + poll_option_parse_href(href_list, poll, option) + + //Topics relating to Faxes + else if(href_list["AdminFaxCreate"]) + if(!check_rights(R_FUN)) + return + + var/mob/sender = locate(href_list["AdminFaxCreate"]) + var/obj/machinery/photocopier/faxmachine/fax = locate(href_list["originfax"]) + var/faxtype = href_list["faxtype"] + var/reply_to = locate(href_list["replyto"]) + var/destination + var/notify + + var/obj/item/paper/P = new /obj/item/paper(null) //hopefully the null loc won't cause trouble for us + + if(!fax) + var/list/departmentoptions = GLOB.alldepartments + "All Departments" + destination = input(usr, "To which department?", "Choose a department", "") as null|anything in departmentoptions + if(!destination) + qdel(P) + return + + for(var/thing in GLOB.allfaxes) + var/obj/machinery/photocopier/faxmachine/F = thing + if(destination != "All Departments" && F.department == destination) + fax = F + + + var/input_text = input(src.owner, "Please enter a message to send a fax via secure connection. Use
                    for line breaks. Both pencode and HTML work.", "Outgoing message from CentCom", "") as message|null + if(!input_text) + qdel(P) + + var/customname = input(src.owner, "Pick a title for the fax.", "Fax Title") as text|null + if(!customname) + customname = "paper" + + var/sendername + switch(faxtype) + if("Central Command") + sendername = "Central Command" + if("Syndicate") + sendername = "UNKNOWN" + if("Custom") + sendername = input(owner, "What organization does the fax come from? This determines the prefix of the paper (i.e. Central Command- Title). This is optional.", "Organization") as text|null + + if(sender) + notify = alert(owner, "Would you like to inform the original sender that a fax has arrived?","Notify Sender","Yes","No") + + // Create the reply message + if(sendername) + P.name = "[sendername]- [customname]" + else + P.name = "[customname]" + P.info = input_text + P.update_icon() + P.x = rand(-2, 0) + P.y = rand(-1, 2) + + if(destination != "All Departments") + if(fax.receivefax(P) == FALSE) + to_chat(owner, "Message transmission failed.") + return + else + for(var/thing in GLOB.allfaxes) + var/obj/machinery/photocopier/faxmachine/F = thing + if(F.z in SSmapping.levels_by_trait(ZTRAIT_STATION)) + addtimer(CALLBACK(src, .proc/handle_sendall, F, P), 0) + + var/datum/fax/admin/A = new /datum/fax/admin() + A.name = P.name + A.from_department = faxtype + if(destination != "All Departments") + A.to_department = fax.department + else + A.to_department = "All Departments" + A.origin = "Custom" + A.message = P + A.reply_to = reply_to + A.sent_by = usr + A.sent_at = world.time + + to_chat(src.owner, "Message transmitted successfully.") + if(notify == "Yes") + var/mob/living/carbon/human/H = sender + if(istype(H) && H.stat == CONSCIOUS && (istype(H.ears, /obj/item/radio/headset))) + to_chat(sender, "Your headset pings, notifying you that a reply to your fax has arrived.") + if(sender) + log_admin("[key_name(src.owner)] replied to a fax message from [key_name(sender)]: [input_text]") + message_admins("[key_name_admin(src.owner)] replied to a fax message from [key_name_admin(sender)] (VIEW).", 1) + else + log_admin("[key_name(src.owner)] sent a fax message to [destination]: [input_text]") + message_admins("[key_name_admin(src.owner)] sent a fax message to [destination] (VIEW).", 1) + return + + else if(href_list["refreshfaxpanel"]) + if(!check_rights(R_FUN)) + return + + fax_panel(usr) + return + + else if(href_list["EvilFax"]) + if(!check_rights(R_FUN)) + return + var/mob/living/carbon/human/H = locate(href_list["EvilFax"]) + if(!istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.") + return + var/etypes = list("Borgification","Corgification","Death By Fire","Demotion Notice") + var/eviltype = input(src.owner, "Which type of evil fax do you wish to send [H]?","Its good to be baaaad...", "") as null|anything in etypes + if(!(eviltype in etypes)) + return + var/customname = input(src.owner, "Pick a title for the evil fax.", "Fax Title") as text|null + if(!customname) + customname = "paper" + var/obj/item/paper/evilfax/P = new /obj/item/paper/evilfax(null) + var/obj/machinery/photocopier/faxmachine/fax = locate(href_list["originfax"]) + + P.name = "Central Command - [customname]" + P.info = "You really should've known better." + P.myeffect = eviltype + P.mytarget = H + if(alert("Do you want the Evil Fax to activate automatically if [H] tries to ignore it?",,"Yes", "No") == "Yes") + P.activate_on_timeout = TRUE + P.x = rand(-2, 0) + P.y = rand(-1, 2) + P.update_icon() + //we have to physically teleport the fax paper + fax.handle_animation() + P.forceMove(fax.loc) + if(istype(H) && H.stat == CONSCIOUS && (istype(H.ears, /obj/item/radio/headset))) + to_chat(H, "Your headset pings, notifying you that a reply to your fax has arrived.") + to_chat(src.owner, "You sent a [eviltype] fax to [H].") + log_admin("[key_name(src.owner)] sent [key_name(H)] a [eviltype] fax") + message_admins("[key_name_admin(src.owner)] replied to [key_name_admin(H)] with a [eviltype] fax") + return + + else if(href_list["FaxReplyTemplate"]) + if(!check_rights(R_FUN)) + return + var/mob/living/carbon/human/H = locate(href_list["FaxReplyTemplate"]) + if(!istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.") + return + var/obj/item/paper/P = new /obj/item/paper(null) + var/obj/machinery/photocopier/faxmachine/fax = locate(href_list["originfax"]) + P.name = "Central Command - paper" + var/stypes = list("Handle it yourselves!","Illegible fax","Fax not signed","Not Right Now","You are wasting our time", "Keep up the good work") + var/stype = input(src.owner, "Which type of standard reply do you wish to send to [H]?","Choose your paperwork", "") as null|anything in stypes + var/tmsg = "

                    [GLOB.station_name]


                    Nanotrasen Communications Department Report


                    " + if(stype == "Handle it yourselves!") + tmsg += "Greetings, esteemed crewmember. Your fax has been DECLINED automatically by the Communications Department Fax Registration System.

                    Please proceed in accordance with Standard Operating Procedure and/or Space Law. You are fully trained to handle this situation without Central Command intervention.

                    This is an automatic message." + else if(stype == "Illegible fax") + tmsg += "Greetings, esteemed crewmember. Your fax has been DECLINED automatically by the Communications Department Fax Registration System.

                    Your fax's grammar, syntax and/or typography are of a sub-par level and do not allow us to understand the contents of the message.

                    Please consult your nearest dictionary and/or thesaurus and try again.

                    This is an automatic message." + else if(stype == "Fax not signed") + tmsg += "Greetings, esteemed crewmember. Your fax has been DECLINED automatically by the Communications Department Fax Registration System.

                    Your fax has not been correctly signed and, as such, we cannot verify your identity.

                    Please sign your faxes before sending them so that we may verify your identity.

                    This is an automatic message." + else if(stype == "Not Right Now") + tmsg += "Greetings, esteemed crewmember. Your fax has been DECLINED automatically by the Communications Department Fax Registration System.

                    Due to pressing concerns of a matter above your current paygrade, we are unable to provide assistance in whatever matter your fax referenced.

                    This can be either due to a power outage, bureaucratic audit, pest infestation, Ascendance Event, corgi outbreak, or any other situation that would affect the proper functioning of the Communications Department Fax Registration System.

                    Please try again later.

                    This is an automatic message." + else if(stype == "You are wasting our time") + tmsg += "Greetings, esteemed crewmember. Your fax has been DECLINED automatically by the Communications Department Fax Registration System.

                    In the interest of preventing further mismanagement of company resources, please avoid wasting our time with such petty drivel.

                    Do kindly remember that we expect our workforce to maintain at least a semi-decent level of profesionalism. Do not test our patience.

                    This is an automatic message." + else if(stype == "Keep up the good work") + tmsg += "Greetings, esteemed crewmember. Your fax has been received successfully by the Communications Department Fax Registration System.

                    We at Central Command appreciate the good work that you have done here, and sincerely recommend that you continue such a display of dedication to the company.

                    This is absolutely not an automated message." + else + return + tmsg += "
                    " + P.info = tmsg + P.x = rand(-2, 0) + P.y = rand(-1, 2) + P.update_icon() + fax.receivefax(P) + if(istype(H) && H.stat == CONSCIOUS && (istype(H.ears, /obj/item/radio/headset))) + to_chat(H, "Your headset pings, notifying you that a reply to your fax has arrived.") + to_chat(src.owner, "You sent a standard '[stype]' fax to [H].") + log_admin("[key_name(src.owner)] sent [key_name(H)] a standard '[stype]' fax") + message_admins("[key_name_admin(src.owner)] replied to [key_name_admin(H)] with a standard '[stype]' fax") + return + + else if(href_list["AdminFaxView"]) + if(!check_rights(R_FUN)) + return + + var/obj/item/fax = locate(href_list["AdminFaxView"]) + if(istype(fax, /obj/item/paper)) + var/obj/item/paper/P = fax + usr.examinate(P) + else if(istype(fax, /obj/item/photo)) + var/obj/item/photo/H = fax + H.show(usr) + else + to_chat(usr, "The faxed item is not viewable. This is probably a bug, and should be reported on the tracker: [fax.type]") + return + +/datum/admins/proc/handle_sendall(var/obj/machinery/photocopier/faxmachine/F, var/obj/item/paper/P) + if(F.receivefax(P) == FALSE) + to_chat(owner, "Message transmission to [F.department] failed.") + +/datum/admins/proc/HandleCMode() + if(!check_rights(R_ADMIN)) + return + + var/dat = {"What mode do you wish to play?
                    "} + for(var/mode in config.modes) + dat += {"[config.mode_names[mode]]
                    "} + dat += {"Secret
                    "} + dat += {"Random
                    "} + dat += {"Now: [GLOB.master_mode]"} + var/datum/browser/popup = new(usr, "c_mode", "Gamemode Panel", 500, 600) + popup.set_content(dat) + popup.open() +// usr << browse(dat, "window=c_mode") + +/datum/admins/proc/HandleFSecret() + if(!check_rights(R_ADMIN)) + return + + if(SSticker.HasRoundStarted()) + return alert(usr, "The game has already started.", null, null, null, null) + if(GLOB.master_mode != "secret") + return alert(usr, "The game mode has to be secret!", null, null, null, null) + var/dat = {"What game mode do you want to force secret to be? Use this if you want to change the game mode, but want the players to believe it's secret. This will only work if the current game mode is secret.
                    "} + for(var/mode in config.modes) + dat += {"[config.mode_names[mode]]
                    "} + dat += {"Random (default)
                    "} + dat += {"Now: [GLOB.secret_force_mode]"} + usr << browse(dat, "window=f_secret") diff --git a/code/modules/admin/verbs/BrokenInhands.dm b/code/modules/admin/verbs/BrokenInhands.dm index 7b7c461ec06e..053523caaffd 100644 --- a/code/modules/admin/verbs/BrokenInhands.dm +++ b/code/modules/admin/verbs/BrokenInhands.dm @@ -1,35 +1,35 @@ -/proc/getbrokeninhands() - var/text - for(var/A in typesof(/obj/item)) - var/obj/item/O = new A( locate(1,1,1) ) - if(!O) - continue - var/icon/IL = new(O.lefthand_file) - var/list/Lstates = IL.IconStates() - var/icon/IR = new(O.righthand_file) - var/list/Rstates = IR.IconStates() - var/icon/J = new(O.icon) - var/list/istates = J.IconStates() - if(!Lstates.Find(O.icon_state) && !Lstates.Find(O.item_state)) - if(O.icon_state) - text += "[O.type] WANTS IN LEFT HAND CALLED\n\"[O.icon_state]\".\n" - if(!Rstates.Find(O.icon_state) && !Rstates.Find(O.item_state)) - if(O.icon_state) - text += "[O.type] WANTS IN RIGHT HAND CALLED\n\"[O.icon_state]\".\n" - - - if(O.icon_state) - if(!istates.Find(O.icon_state)) - text += "[O.type] MISSING NORMAL ICON CALLED\n\"[O.icon_state]\" IN \"[O.icon]\"\n" - if(O.item_state) - if(!istates.Find(O.item_state)) - text += "[O.type] MISSING NORMAL ICON CALLED\n\"[O.item_state]\" IN \"[O.icon]\"\n" - text+="\n" - qdel(O) - if(text) - var/F = file("broken_icons.txt") - fdel(F) - WRITE_FILE(F, text) - to_chat(world, "Completely successfully and written to [F]", confidential = TRUE) - - +/proc/getbrokeninhands() + var/text + for(var/A in typesof(/obj/item)) + var/obj/item/O = new A( locate(1,1,1) ) + if(!O) + continue + var/icon/IL = new(O.lefthand_file) + var/list/Lstates = IL.IconStates() + var/icon/IR = new(O.righthand_file) + var/list/Rstates = IR.IconStates() + var/icon/J = new(O.icon) + var/list/istates = J.IconStates() + if(!Lstates.Find(O.icon_state) && !Lstates.Find(O.item_state)) + if(O.icon_state) + text += "[O.type] WANTS IN LEFT HAND CALLED\n\"[O.icon_state]\".\n" + if(!Rstates.Find(O.icon_state) && !Rstates.Find(O.item_state)) + if(O.icon_state) + text += "[O.type] WANTS IN RIGHT HAND CALLED\n\"[O.icon_state]\".\n" + + + if(O.icon_state) + if(!istates.Find(O.icon_state)) + text += "[O.type] MISSING NORMAL ICON CALLED\n\"[O.icon_state]\" IN \"[O.icon]\"\n" + if(O.item_state) + if(!istates.Find(O.item_state)) + text += "[O.type] MISSING NORMAL ICON CALLED\n\"[O.item_state]\" IN \"[O.icon]\"\n" + text+="\n" + qdel(O) + if(text) + var/F = file("broken_icons.txt") + fdel(F) + WRITE_FILE(F, text) + to_chat(world, "Completely successfully and written to [F]", confidential = TRUE) + + diff --git a/code/modules/admin/verbs/adminhelp.dm b/code/modules/admin/verbs/adminhelp.dm index 0027b617d7a4..93310415edcd 100644 --- a/code/modules/admin/verbs/adminhelp.dm +++ b/code/modules/admin/verbs/adminhelp.dm @@ -1,703 +1,703 @@ -/client/var/adminhelptimerid = 0 //a timer id for returning the ahelp verb -/client/var/datum/admin_help/current_ticket //the current ticket the (usually) not-admin client is dealing with - -// -//TICKET MANAGER -// - -GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) - -/datum/admin_help_tickets - var/list/active_tickets = list() - var/list/closed_tickets = list() - var/list/resolved_tickets = list() - - var/obj/effect/statclick/ticket_list/astatclick = new(null, null, AHELP_ACTIVE) - var/obj/effect/statclick/ticket_list/cstatclick = new(null, null, AHELP_CLOSED) - var/obj/effect/statclick/ticket_list/rstatclick = new(null, null, AHELP_RESOLVED) - -/datum/admin_help_tickets/Destroy() - QDEL_LIST(active_tickets) - QDEL_LIST(closed_tickets) - QDEL_LIST(resolved_tickets) - QDEL_NULL(astatclick) - QDEL_NULL(cstatclick) - QDEL_NULL(rstatclick) - return ..() - -/datum/admin_help_tickets/proc/TicketByID(id) - var/list/lists = list(active_tickets, closed_tickets, resolved_tickets) - for(var/I in lists) - for(var/J in I) - var/datum/admin_help/AH = J - if(AH.id == id) - return J - -/datum/admin_help_tickets/proc/TicketsByCKey(ckey) - . = list() - var/list/lists = list(active_tickets, closed_tickets, resolved_tickets) - for(var/I in lists) - for(var/J in I) - var/datum/admin_help/AH = J - if(AH.initiator_ckey == ckey) - . += AH - -//private -/datum/admin_help_tickets/proc/ListInsert(datum/admin_help/new_ticket) - var/list/ticket_list - switch(new_ticket.state) - if(AHELP_ACTIVE) - ticket_list = active_tickets - if(AHELP_CLOSED) - ticket_list = closed_tickets - if(AHELP_RESOLVED) - ticket_list = resolved_tickets - else - CRASH("Invalid ticket state: [new_ticket.state]") - var/num_closed = ticket_list.len - if(num_closed) - for(var/I in 1 to num_closed) - var/datum/admin_help/AH = ticket_list[I] - if(AH.id > new_ticket.id) - ticket_list.Insert(I, new_ticket) - return - ticket_list += new_ticket - -//opens the ticket listings for one of the 3 states -/datum/admin_help_tickets/proc/BrowseTickets(state) - var/list/l2b - var/title - switch(state) - if(AHELP_ACTIVE) - l2b = active_tickets - title = "Active Tickets" - if(AHELP_CLOSED) - l2b = closed_tickets - title = "Closed Tickets" - if(AHELP_RESOLVED) - l2b = resolved_tickets - title = "Resolved Tickets" - if(!l2b) - return - var/list/dat = list("[title]") - dat += "Refresh

                    " - for(var/I in l2b) - var/datum/admin_help/AH = I - dat += "Ticket #[AH.id]: [AH.initiator_key_name]: [AH.name]
                    " - - usr << browse(dat.Join(), "window=ahelp_list[state];size=600x480") - -//Tickets statpanel -/datum/admin_help_tickets/proc/stat_entry() - var/num_disconnected = 0 - stat("Active Tickets:", astatclick.update("[active_tickets.len]")) - for(var/I in active_tickets) - var/datum/admin_help/AH = I - if(AH.initiator) - stat("#[AH.id]. [AH.initiator_key_name]:", AH.statclick.update()) - else - ++num_disconnected - if(num_disconnected) - stat("Disconnected:", astatclick.update("[num_disconnected]")) - stat("Closed Tickets:", cstatclick.update("[closed_tickets.len]")) - stat("Resolved Tickets:", rstatclick.update("[resolved_tickets.len]")) - -//Reassociate still open ticket if one exists -/datum/admin_help_tickets/proc/ClientLogin(client/C) - C.current_ticket = CKey2ActiveTicket(C.ckey) - if(C.current_ticket) - C.current_ticket.initiator = C - C.current_ticket.AddInteraction("Client reconnected.") - SSblackbox.LogAhelp(C.current_ticket.id, "Reconnected", "Client reconnected", C.ckey) - -//Dissasociate ticket -/datum/admin_help_tickets/proc/ClientLogout(client/C) - if(C.current_ticket) - var/datum/admin_help/T = C.current_ticket - T.AddInteraction("Client disconnected.") - SSblackbox.LogAhelp(T, "Disconnected", "Client disconnected", C.ckey) - T.initiator = null - -//Get a ticket given a ckey -/datum/admin_help_tickets/proc/CKey2ActiveTicket(ckey) - for(var/I in active_tickets) - var/datum/admin_help/AH = I - if(AH.initiator_ckey == ckey) - return AH - -// -//TICKET LIST STATCLICK -// - -/obj/effect/statclick/ticket_list - var/current_state - -/obj/effect/statclick/ticket_list/New(loc, name, state) - current_state = state - ..() - -/obj/effect/statclick/ticket_list/Click() - GLOB.ahelp_tickets.BrowseTickets(current_state) - -// -//TICKET DATUM -// - -/datum/admin_help - var/id - var/name - var/state = AHELP_ACTIVE - - var/opened_at - var/closed_at - - var/client/initiator //semi-misnomer, it's the person who ahelped/was bwoinked - var/initiator_ckey - var/initiator_key_name - var/heard_by_no_admins = FALSE - - var/list/_interactions //use AddInteraction() or, preferably, admin_ticket_log() - - var/obj/effect/statclick/ahelp/statclick - - var/static/ticket_counter = 0 - -//call this on its own to create a ticket, don't manually assign current_ticket -//msg is the title of the ticket: usually the ahelp text -//is_bwoink is TRUE if this ticket was started by an admin PM -/datum/admin_help/New(msg, client/C, is_bwoink) - //clean the input msg - msg = sanitize(copytext_char(msg, 1, MAX_MESSAGE_LEN)) - if(!msg || !C || !C.mob) - qdel(src) - return - - id = ++ticket_counter - opened_at = world.time - - name = copytext_char(msg, 1, 100) - - initiator = C - initiator_ckey = initiator.ckey - initiator_key_name = key_name(initiator, FALSE, TRUE) - if(initiator.current_ticket) //This is a bug - stack_trace("Multiple ahelp current_tickets") - initiator.current_ticket.AddInteraction("Ticket erroneously left open by code") - initiator.current_ticket.Close() - initiator.current_ticket = src - - TimeoutVerb() - - statclick = new(null, src) - _interactions = list() - - if(is_bwoink) - AddInteraction("[key_name_admin(usr)] PM'd [LinkedReplyName()]") - message_admins("Ticket [TicketHref("#[id]")] created") - else - MessageNoRecipient(msg) - - SSredbot.send_discord_message("admin", "Ticket #[id] created by [usr.ckey] ([usr.real_name]): [name]", "ticket") - - //send it to TGS if nobody is on and tell us how many were on - var/admin_number_present = send2tgs_adminless_only(initiator_ckey, "Ticket #[id]: [msg]") - log_admin_private("Ticket #[id]: [key_name(initiator)]: [name] - heard by [admin_number_present] non-AFK admins who have +BAN.") - if(admin_number_present <= 0) - to_chat(C, "No active admins are online, your adminhelp was sent through TGS to admins who are available. This may use IRC or Discord.", confidential = TRUE) - heard_by_no_admins = TRUE - GLOB.ahelp_tickets.active_tickets += src - -/datum/admin_help/Destroy() - RemoveActive() - GLOB.ahelp_tickets.closed_tickets -= src - GLOB.ahelp_tickets.resolved_tickets -= src - return ..() - -/datum/admin_help/proc/AddInteraction(formatted_message) - if(heard_by_no_admins && usr && usr.ckey != initiator_ckey) - heard_by_no_admins = FALSE - send2tgs(initiator_ckey, "Ticket #[id]: Answered by [key_name(usr)]") - _interactions += "[time_stamp()]: [formatted_message]" - -//Removes the ahelp verb and returns it after 2 minutes -/datum/admin_help/proc/TimeoutVerb() - initiator.verbs -= /client/verb/adminhelp - initiator.adminhelptimerid = addtimer(CALLBACK(initiator, /client/proc/giveadminhelpverb), 1200, TIMER_STOPPABLE) //2 minute cooldown of admin helps - -//private -/datum/admin_help/proc/FullMonty(ref_src) - if(!ref_src) - ref_src = "[REF(src)]" - . = ADMIN_FULLMONTY_NONAME(initiator.mob) - if(state == AHELP_ACTIVE) - . += ClosureLinks(ref_src) - -//private -/datum/admin_help/proc/ClosureLinks(ref_src) - if(!ref_src) - ref_src = "[REF(src)]" - . = " (REJT)" - . += " (IC)" - . += " (CLOSE)" - . += " (RSLVE)" - -//private -/datum/admin_help/proc/LinkedReplyName(ref_src) - if(!ref_src) - ref_src = "[REF(src)]" - return "[initiator_key_name]" - -//private -/datum/admin_help/proc/TicketHref(msg, ref_src, action = "ticket") - if(!ref_src) - ref_src = "[REF(src)]" - return "[msg]" - -//message from the initiator without a target, all admins will see this -//won't bug irc/discord -/datum/admin_help/proc/MessageNoRecipient(msg) - var/ref_src = "[REF(src)]" - //Message to be sent to all admins - var/admin_msg = "Ticket [TicketHref("#[id]", ref_src)]: [LinkedReplyName(ref_src)] [FullMonty(ref_src)]: [keywords_lookup(msg)]" - - AddInteraction("[LinkedReplyName(ref_src)]: [msg]") - log_admin_private("Ticket #[id]: [key_name(initiator)]: [msg]") - - //send this msg to all admins - for(var/client/X in GLOB.admins) - if(X.prefs.toggles & SOUND_ADMINHELP) - SEND_SOUND(X, sound('sound/effects/adminhelp.ogg')) - window_flash(X, ignorepref = TRUE) - to_chat(X, admin_msg, confidential = TRUE) - - //show it to the person adminhelping too - to_chat(initiator, "PM to-Admins: [msg]", confidential = TRUE) - SSblackbox.LogAhelp(id, "Ticket Opened", msg, null, initiator.ckey) - -//Reopen a closed ticket -/datum/admin_help/proc/Reopen() - if(state == AHELP_ACTIVE) - to_chat(usr, "This ticket is already open.", confidential = TRUE) - return - - if(GLOB.ahelp_tickets.CKey2ActiveTicket(initiator_ckey)) - to_chat(usr, "This user already has an active ticket, cannot reopen this one.", confidential = TRUE) - return - - statclick = new(null, src) - GLOB.ahelp_tickets.active_tickets += src - GLOB.ahelp_tickets.closed_tickets -= src - GLOB.ahelp_tickets.resolved_tickets -= src - switch(state) - if(AHELP_CLOSED) - SSblackbox.record_feedback("tally", "ahelp_stats", -1, "closed") - if(AHELP_RESOLVED) - SSblackbox.record_feedback("tally", "ahelp_stats", -1, "resolved") - state = AHELP_ACTIVE - closed_at = null - if(initiator) - initiator.current_ticket = src - - AddInteraction("Reopened by [key_name_admin(usr)]") - var/msg = "Ticket [TicketHref("#[id]")] reopened by [key_name_admin(usr)]." - message_admins(msg) - log_admin_private(msg) - SSblackbox.LogAhelp(id, "Reopened", "Reopened by [usr.key]", usr.ckey) - SSblackbox.record_feedback("tally", "ahelp_stats", 1, "reopened") - TicketPanel() //can only be done from here, so refresh it - -//private -/datum/admin_help/proc/RemoveActive() - if(state != AHELP_ACTIVE) - return - closed_at = world.time - QDEL_NULL(statclick) - GLOB.ahelp_tickets.active_tickets -= src - if(initiator && initiator.current_ticket == src) - initiator.current_ticket = null - -//Mark open ticket as closed/meme -/datum/admin_help/proc/Close(key_name = key_name_admin(usr), silent = FALSE) - if(state != AHELP_ACTIVE) - return - RemoveActive() - state = AHELP_CLOSED - GLOB.ahelp_tickets.ListInsert(src) - AddInteraction("Closed by [key_name].") - if(!silent) - SSblackbox.record_feedback("tally", "ahelp_stats", 1, "closed") - var/msg = "Ticket [TicketHref("#[id]")] closed by [key_name]." - message_admins(msg) - SSblackbox.LogAhelp(id, "Closed", "Closed by [usr.key]", null, usr.ckey) - log_admin_private(msg) - -//Mark open ticket as resolved/legitimate, returns ahelp verb -/datum/admin_help/proc/Resolve(key_name = key_name_admin(usr), silent = FALSE) - if(state != AHELP_ACTIVE) - return - RemoveActive() - state = AHELP_RESOLVED - GLOB.ahelp_tickets.ListInsert(src) - - addtimer(CALLBACK(initiator, /client/proc/giveadminhelpverb), 50) - - AddInteraction("Resolved by [key_name].") - to_chat(initiator, "Your ticket has been resolved by an admin. The Adminhelp verb will be returned to you shortly.", confidential = TRUE) - if(!silent) - SSblackbox.record_feedback("tally", "ahelp_stats", 1, "resolved") - var/msg = "Ticket [TicketHref("#[id]")] resolved by [key_name]" - message_admins(msg) - SSblackbox.LogAhelp(id, "Resolved", "Resolved by [usr.key]", null, usr.ckey) - log_admin_private(msg) - -//Close and return ahelp verb, use if ticket is incoherent -/datum/admin_help/proc/Reject(key_name = key_name_admin(usr)) - if(state != AHELP_ACTIVE) - return - - if(initiator) - initiator.giveadminhelpverb() - - SEND_SOUND(initiator, sound('sound/effects/adminhelp.ogg')) - - to_chat(initiator, "- AdminHelp Rejected! -", confidential = TRUE) - to_chat(initiator, "Your admin help was rejected. The adminhelp verb has been returned to you so that you may try again.", confidential = TRUE) - to_chat(initiator, "Please try to be calm, clear, and descriptive in admin helps, do not assume the admin has seen any related events, and clearly state the names of anybody you are reporting.", confidential = TRUE) - - SSblackbox.record_feedback("tally", "ahelp_stats", 1, "rejected") - var/msg = "Ticket [TicketHref("#[id]")] rejected by [key_name]" - message_admins(msg) - log_admin_private(msg) - AddInteraction("Rejected by [key_name].") - SSblackbox.LogAhelp(id, "Rejected", "Rejected by [usr.key]", null, usr.ckey) - Close(silent = TRUE) - -//Resolve ticket with IC Issue message -/datum/admin_help/proc/ICIssue(key_name = key_name_admin(usr)) - if(state != AHELP_ACTIVE) - return - - var/msg = "- AdminHelp marked as IC issue! -
                    " - msg += "Your issue has been determined by an administrator to be an in character issue and does NOT require administrator intervention at this time. For further resolution you should pursue options that are in character." - - if(initiator) - to_chat(initiator, msg, confidential = TRUE) - - SSblackbox.record_feedback("tally", "ahelp_stats", 1, "IC") - msg = "Ticket [TicketHref("#[id]")] marked as IC by [key_name]" - message_admins(msg) - log_admin_private(msg) - AddInteraction("Marked as IC issue by [key_name]") - SSblackbox.LogAhelp(id, "IC Issue", "Marked as IC issue by [usr.key]", null, usr.ckey) - Resolve(silent = TRUE) - -//Show the ticket panel -/datum/admin_help/proc/TicketPanel() - var/list/dat = list("Ticket #[id]") - var/ref_src = "[REF(src)]" - dat += "

                    Admin Help Ticket #[id]: [LinkedReplyName(ref_src)]

                    " - dat += "State: " - switch(state) - if(AHELP_ACTIVE) - dat += "OPEN" - if(AHELP_RESOLVED) - dat += "RESOLVED" - if(AHELP_CLOSED) - dat += "CLOSED" - else - dat += "UNKNOWN" - dat += "[FOURSPACES][TicketHref("Refresh", ref_src)][FOURSPACES][TicketHref("Re-Title", ref_src, "retitle")]" - if(state != AHELP_ACTIVE) - dat += "[FOURSPACES][TicketHref("Reopen", ref_src, "reopen")]" - dat += "

                    Opened at: [gameTimestamp(wtime = opened_at)] (Approx [DisplayTimeText(world.time - opened_at)] ago)" - if(closed_at) - dat += "
                    Closed at: [gameTimestamp(wtime = closed_at)] (Approx [DisplayTimeText(world.time - closed_at)] ago)" - dat += "

                    " - if(initiator) - dat += "Actions: [FullMonty(ref_src)]
                    " - else - dat += "DISCONNECTED[FOURSPACES][ClosureLinks(ref_src)]
                    " - dat += "
                    Log:

                    " - for(var/I in _interactions) - dat += "[I]
                    " - - usr << browse(dat.Join(), "window=ahelp[id];size=620x480") - -/datum/admin_help/proc/Retitle() - var/new_title = input(usr, "Enter a title for the ticket", "Rename Ticket", name) as text|null - if(new_title) - name = new_title - //not saying the original name cause it could be a long ass message - var/msg = "Ticket [TicketHref("#[id]")] titled [name] by [key_name_admin(usr)]" - message_admins(msg) - log_admin_private(msg) - TicketPanel() //we have to be here to do this - -//Forwarded action from admin/Topic -/datum/admin_help/proc/Action(action) - testing("Ahelp action: [action]") - switch(action) - if("ticket") - TicketPanel() - if("retitle") - Retitle() - if("reject") - Reject() - if("reply") - usr.client.cmd_ahelp_reply(initiator) - if("icissue") - ICIssue() - if("close") - Close() - if("resolve") - Resolve() - if("reopen") - Reopen() - -// -// TICKET STATCLICK -// - -/obj/effect/statclick/ahelp - var/datum/admin_help/ahelp_datum - -/obj/effect/statclick/ahelp/Initialize(mapload, datum/admin_help/AH) - ahelp_datum = AH - . = ..() - -/obj/effect/statclick/ahelp/update() - return ..(ahelp_datum.name) - -/obj/effect/statclick/ahelp/Click() - ahelp_datum.TicketPanel() - -/obj/effect/statclick/ahelp/Destroy() - ahelp_datum = null - return ..() - -// -// CLIENT PROCS -// - -/client/proc/giveadminhelpverb() - src.verbs |= /client/verb/adminhelp - deltimer(adminhelptimerid) - adminhelptimerid = 0 - -// Used for methods where input via arg doesn't work -/client/proc/get_adminhelp() - var/msg = input(src, "Please describe your problem concisely and an admin will help as soon as they're able.", "Adminhelp contents") as message|null - adminhelp(msg) - -/client/verb/adminhelp(msg as message) - set category = "Admin" - set name = "Adminhelp" - - if(GLOB.say_disabled) //This is here to try to identify lag problems - to_chat(usr, "Speech is currently admin-disabled.", confidential = TRUE) - return - - //handle muting and automuting - if(prefs.muted & MUTE_ADMINHELP) - to_chat(src, "Error: Admin-PM: You cannot send adminhelps (Muted).", confidential = TRUE) - return - if(handle_spam_prevention(msg,MUTE_ADMINHELP)) - return - - msg = trim(msg) - - if(!msg) - return - - SSblackbox.record_feedback("tally", "admin_verb", 1, "Adminhelp") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - if(current_ticket) - if(alert(usr, "You already have a ticket open. Is this for the same issue?",,"Yes","No") != "No") - if(current_ticket) - current_ticket.MessageNoRecipient(msg) - current_ticket.TimeoutVerb() - return - else - to_chat(usr, "Ticket not found, creating new one...", confidential = TRUE) - else - current_ticket.AddInteraction("[key_name_admin(usr)] opened a new ticket.") - current_ticket.Close() - - //Extremely simple system of suggesting mentorhelp instead of adminhelp - var/msg_lower = lowertext(msg) - if((findtext(msg_lower, "how to") == 1 || findtext(msg_lower, "how do") == 1) && GLOB.mentors.len) - if(alert("\"[msg]\" looks like a game mechanics question, would you like to ask in mentorhelp instead?", "Adminhelp?", "Yes, mentorhelp", "No, adminhelp") == "Yes, mentorhelp") - mentorhelp(msg) - return - - new /datum/admin_help(msg, src, FALSE) - -// -// LOGGING -// - -//Use this proc when an admin takes action that may be related to an open ticket on what -//what can be a client, ckey, or mob -/proc/admin_ticket_log(what, message) - var/client/C - var/mob/Mob = what - if(istype(Mob)) - C = Mob.client - else - C = what - if(istype(C) && C.current_ticket) - C.current_ticket.AddInteraction(message) - return C.current_ticket - if(istext(what)) //ckey - var/datum/admin_help/AH = GLOB.ahelp_tickets.CKey2ActiveTicket(what) - if(AH) - AH.AddInteraction(message) - return AH - -// -// HELPER PROCS -// - -/proc/get_admin_counts(requiredflags = R_BAN) - . = 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)) - .["noflags"] += X - else if(X.is_afk()) - .["afk"] += X - else if(X.holder.fakekey) - .["stealth"] += X - else - .["present"] += X - -/proc/send2tgs_adminless_only(source, msg, requiredflags = R_BAN) - var/list/adm = get_admin_counts(requiredflags) - var/list/activemins = adm["present"] - . = activemins.len - if(. <= 0) - var/final = "" - var/list/afkmins = adm["afk"] - var/list/stealthmins = adm["stealth"] - var/list/powerlessmins = adm["noflags"] - var/list/allmins = adm["total"] - if(!afkmins.len && !stealthmins.len && !powerlessmins.len) - 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] " - send2tgs(source,final) - send2otherserver(source,final) - - -/proc/send2tgs(msg,msg2) - msg = replacetext(replacetext(msg, "\proper", ""), "\improper", "") - msg2 = replacetext(replacetext(msg2, "\proper", ""), "\improper", "") - world.TgsTargetedChatBroadcast("[msg] | [msg2]", TRUE) - -// -/proc/send2otherserver(source,msg,type = "Ahelp",target_servers) - var/comms_key = CONFIG_GET(string/comms_key) - if(!comms_key) - return - - var/our_id = CONFIG_GET(string/cross_comms_name) - var/list/message = list() - message["message_sender"] = source - message["message"] = msg - message["source"] = "([our_id])" - message["key"] = comms_key - message += type - - var/list/servers = CONFIG_GET(keyed_list/cross_server) - for(var/I in servers) - if(I == our_id) //No sending to ourselves - continue - if(target_servers && !(I in target_servers)) - continue - world.Export("[servers[I]]?[list2params(message)]") - - -/proc/tgsadminwho() - var/list/message = list("Admins: ") - var/list/admin_keys = list() - for(var/adm in GLOB.admins) - var/client/C = adm - admin_keys += "[C][C.holder.fakekey ? "(Stealth)" : ""][C.is_afk() ? "(AFK)" : ""]" - - for(var/admin in admin_keys) - if(LAZYLEN(message) > 1) - message += ", [admin]" - else - message += "[admin]" - - return jointext(message, "") - -/proc/keywords_lookup(msg,external) - - //This is a list of words which are ignored by the parser when comparing message contents for names. MUST BE IN LOWER CASE! - var/list/adminhelp_ignored_words = list("unknown","the","a","an","of","monkey","alien","as", "i") - - //explode the input msg into a list - var/list/msglist = splittext(msg, " ") - - //generate keywords lookup - var/list/surnames = list() - var/list/forenames = list() - var/list/ckeys = list() - var/founds = "" - for(var/mob/M in GLOB.mob_list) - var/list/indexing = list(M.real_name, M.name) - if(M.mind) - indexing += M.mind.name - - for(var/string in indexing) - var/list/L = splittext(string, " ") - var/surname_found = 0 - //surnames - for(var/i=L.len, i>=1, i--) - var/word = ckey(L[i]) - if(word) - surnames[word] = M - surname_found = i - break - //forenames - for(var/i=1, i(?|F) " - continue - msg += "[original_word] " - if(external) - if(founds == "") - return "Search Failed" - else - return founds - - return msg +/client/var/adminhelptimerid = 0 //a timer id for returning the ahelp verb +/client/var/datum/admin_help/current_ticket //the current ticket the (usually) not-admin client is dealing with + +// +//TICKET MANAGER +// + +GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) + +/datum/admin_help_tickets + var/list/active_tickets = list() + var/list/closed_tickets = list() + var/list/resolved_tickets = list() + + var/obj/effect/statclick/ticket_list/astatclick = new(null, null, AHELP_ACTIVE) + var/obj/effect/statclick/ticket_list/cstatclick = new(null, null, AHELP_CLOSED) + var/obj/effect/statclick/ticket_list/rstatclick = new(null, null, AHELP_RESOLVED) + +/datum/admin_help_tickets/Destroy() + QDEL_LIST(active_tickets) + QDEL_LIST(closed_tickets) + QDEL_LIST(resolved_tickets) + QDEL_NULL(astatclick) + QDEL_NULL(cstatclick) + QDEL_NULL(rstatclick) + return ..() + +/datum/admin_help_tickets/proc/TicketByID(id) + var/list/lists = list(active_tickets, closed_tickets, resolved_tickets) + for(var/I in lists) + for(var/J in I) + var/datum/admin_help/AH = J + if(AH.id == id) + return J + +/datum/admin_help_tickets/proc/TicketsByCKey(ckey) + . = list() + var/list/lists = list(active_tickets, closed_tickets, resolved_tickets) + for(var/I in lists) + for(var/J in I) + var/datum/admin_help/AH = J + if(AH.initiator_ckey == ckey) + . += AH + +//private +/datum/admin_help_tickets/proc/ListInsert(datum/admin_help/new_ticket) + var/list/ticket_list + switch(new_ticket.state) + if(AHELP_ACTIVE) + ticket_list = active_tickets + if(AHELP_CLOSED) + ticket_list = closed_tickets + if(AHELP_RESOLVED) + ticket_list = resolved_tickets + else + CRASH("Invalid ticket state: [new_ticket.state]") + var/num_closed = ticket_list.len + if(num_closed) + for(var/I in 1 to num_closed) + var/datum/admin_help/AH = ticket_list[I] + if(AH.id > new_ticket.id) + ticket_list.Insert(I, new_ticket) + return + ticket_list += new_ticket + +//opens the ticket listings for one of the 3 states +/datum/admin_help_tickets/proc/BrowseTickets(state) + var/list/l2b + var/title + switch(state) + if(AHELP_ACTIVE) + l2b = active_tickets + title = "Active Tickets" + if(AHELP_CLOSED) + l2b = closed_tickets + title = "Closed Tickets" + if(AHELP_RESOLVED) + l2b = resolved_tickets + title = "Resolved Tickets" + if(!l2b) + return + var/list/dat = list("[title]") + dat += "Refresh

                    " + for(var/I in l2b) + var/datum/admin_help/AH = I + dat += "Ticket #[AH.id]: [AH.initiator_key_name]: [AH.name]
                    " + + usr << browse(dat.Join(), "window=ahelp_list[state];size=600x480") + +//Tickets statpanel +/datum/admin_help_tickets/proc/stat_entry() + var/num_disconnected = 0 + stat("Active Tickets:", astatclick.update("[active_tickets.len]")) + for(var/I in active_tickets) + var/datum/admin_help/AH = I + if(AH.initiator) + stat("#[AH.id]. [AH.initiator_key_name]:", AH.statclick.update()) + else + ++num_disconnected + if(num_disconnected) + stat("Disconnected:", astatclick.update("[num_disconnected]")) + stat("Closed Tickets:", cstatclick.update("[closed_tickets.len]")) + stat("Resolved Tickets:", rstatclick.update("[resolved_tickets.len]")) + +//Reassociate still open ticket if one exists +/datum/admin_help_tickets/proc/ClientLogin(client/C) + C.current_ticket = CKey2ActiveTicket(C.ckey) + if(C.current_ticket) + C.current_ticket.initiator = C + C.current_ticket.AddInteraction("Client reconnected.") + SSblackbox.LogAhelp(C.current_ticket.id, "Reconnected", "Client reconnected", C.ckey) + +//Dissasociate ticket +/datum/admin_help_tickets/proc/ClientLogout(client/C) + if(C.current_ticket) + var/datum/admin_help/T = C.current_ticket + T.AddInteraction("Client disconnected.") + SSblackbox.LogAhelp(T, "Disconnected", "Client disconnected", C.ckey) + T.initiator = null + +//Get a ticket given a ckey +/datum/admin_help_tickets/proc/CKey2ActiveTicket(ckey) + for(var/I in active_tickets) + var/datum/admin_help/AH = I + if(AH.initiator_ckey == ckey) + return AH + +// +//TICKET LIST STATCLICK +// + +/obj/effect/statclick/ticket_list + var/current_state + +/obj/effect/statclick/ticket_list/New(loc, name, state) + current_state = state + ..() + +/obj/effect/statclick/ticket_list/Click() + GLOB.ahelp_tickets.BrowseTickets(current_state) + +// +//TICKET DATUM +// + +/datum/admin_help + var/id + var/name + var/state = AHELP_ACTIVE + + var/opened_at + var/closed_at + + var/client/initiator //semi-misnomer, it's the person who ahelped/was bwoinked + var/initiator_ckey + var/initiator_key_name + var/heard_by_no_admins = FALSE + + var/list/_interactions //use AddInteraction() or, preferably, admin_ticket_log() + + var/obj/effect/statclick/ahelp/statclick + + var/static/ticket_counter = 0 + +//call this on its own to create a ticket, don't manually assign current_ticket +//msg is the title of the ticket: usually the ahelp text +//is_bwoink is TRUE if this ticket was started by an admin PM +/datum/admin_help/New(msg, client/C, is_bwoink) + //clean the input msg + msg = sanitize(copytext_char(msg, 1, MAX_MESSAGE_LEN)) + if(!msg || !C || !C.mob) + qdel(src) + return + + id = ++ticket_counter + opened_at = world.time + + name = copytext_char(msg, 1, 100) + + initiator = C + initiator_ckey = initiator.ckey + initiator_key_name = key_name(initiator, FALSE, TRUE) + if(initiator.current_ticket) //This is a bug + stack_trace("Multiple ahelp current_tickets") + initiator.current_ticket.AddInteraction("Ticket erroneously left open by code") + initiator.current_ticket.Close() + initiator.current_ticket = src + + TimeoutVerb() + + statclick = new(null, src) + _interactions = list() + + if(is_bwoink) + AddInteraction("[key_name_admin(usr)] PM'd [LinkedReplyName()]") + message_admins("Ticket [TicketHref("#[id]")] created") + else + MessageNoRecipient(msg) + + SSredbot.send_discord_message("admin", "Ticket #[id] created by [usr.ckey] ([usr.real_name]): [name]", "ticket") + + //send it to TGS if nobody is on and tell us how many were on + var/admin_number_present = send2tgs_adminless_only(initiator_ckey, "Ticket #[id]: [msg]") + log_admin_private("Ticket #[id]: [key_name(initiator)]: [name] - heard by [admin_number_present] non-AFK admins who have +BAN.") + if(admin_number_present <= 0) + to_chat(C, "No active admins are online, your adminhelp was sent through TGS to admins who are available. This may use IRC or Discord.", confidential = TRUE) + heard_by_no_admins = TRUE + GLOB.ahelp_tickets.active_tickets += src + +/datum/admin_help/Destroy() + RemoveActive() + GLOB.ahelp_tickets.closed_tickets -= src + GLOB.ahelp_tickets.resolved_tickets -= src + return ..() + +/datum/admin_help/proc/AddInteraction(formatted_message) + if(heard_by_no_admins && usr && usr.ckey != initiator_ckey) + heard_by_no_admins = FALSE + send2tgs(initiator_ckey, "Ticket #[id]: Answered by [key_name(usr)]") + _interactions += "[time_stamp()]: [formatted_message]" + +//Removes the ahelp verb and returns it after 2 minutes +/datum/admin_help/proc/TimeoutVerb() + initiator.verbs -= /client/verb/adminhelp + initiator.adminhelptimerid = addtimer(CALLBACK(initiator, /client/proc/giveadminhelpverb), 1200, TIMER_STOPPABLE) //2 minute cooldown of admin helps + +//private +/datum/admin_help/proc/FullMonty(ref_src) + if(!ref_src) + ref_src = "[REF(src)]" + . = ADMIN_FULLMONTY_NONAME(initiator.mob) + if(state == AHELP_ACTIVE) + . += ClosureLinks(ref_src) + +//private +/datum/admin_help/proc/ClosureLinks(ref_src) + if(!ref_src) + ref_src = "[REF(src)]" + . = " (REJT)" + . += " (IC)" + . += " (CLOSE)" + . += " (RSLVE)" + +//private +/datum/admin_help/proc/LinkedReplyName(ref_src) + if(!ref_src) + ref_src = "[REF(src)]" + return "[initiator_key_name]" + +//private +/datum/admin_help/proc/TicketHref(msg, ref_src, action = "ticket") + if(!ref_src) + ref_src = "[REF(src)]" + return "[msg]" + +//message from the initiator without a target, all admins will see this +//won't bug irc/discord +/datum/admin_help/proc/MessageNoRecipient(msg) + var/ref_src = "[REF(src)]" + //Message to be sent to all admins + var/admin_msg = "Ticket [TicketHref("#[id]", ref_src)]: [LinkedReplyName(ref_src)] [FullMonty(ref_src)]: [keywords_lookup(msg)]" + + AddInteraction("[LinkedReplyName(ref_src)]: [msg]") + log_admin_private("Ticket #[id]: [key_name(initiator)]: [msg]") + + //send this msg to all admins + for(var/client/X in GLOB.admins) + if(X.prefs.toggles & SOUND_ADMINHELP) + SEND_SOUND(X, sound('sound/effects/adminhelp.ogg')) + window_flash(X, ignorepref = TRUE) + to_chat(X, admin_msg, confidential = TRUE) + + //show it to the person adminhelping too + to_chat(initiator, "PM to-Admins: [msg]", confidential = TRUE) + SSblackbox.LogAhelp(id, "Ticket Opened", msg, null, initiator.ckey) + +//Reopen a closed ticket +/datum/admin_help/proc/Reopen() + if(state == AHELP_ACTIVE) + to_chat(usr, "This ticket is already open.", confidential = TRUE) + return + + if(GLOB.ahelp_tickets.CKey2ActiveTicket(initiator_ckey)) + to_chat(usr, "This user already has an active ticket, cannot reopen this one.", confidential = TRUE) + return + + statclick = new(null, src) + GLOB.ahelp_tickets.active_tickets += src + GLOB.ahelp_tickets.closed_tickets -= src + GLOB.ahelp_tickets.resolved_tickets -= src + switch(state) + if(AHELP_CLOSED) + SSblackbox.record_feedback("tally", "ahelp_stats", -1, "closed") + if(AHELP_RESOLVED) + SSblackbox.record_feedback("tally", "ahelp_stats", -1, "resolved") + state = AHELP_ACTIVE + closed_at = null + if(initiator) + initiator.current_ticket = src + + AddInteraction("Reopened by [key_name_admin(usr)]") + var/msg = "Ticket [TicketHref("#[id]")] reopened by [key_name_admin(usr)]." + message_admins(msg) + log_admin_private(msg) + SSblackbox.LogAhelp(id, "Reopened", "Reopened by [usr.key]", usr.ckey) + SSblackbox.record_feedback("tally", "ahelp_stats", 1, "reopened") + TicketPanel() //can only be done from here, so refresh it + +//private +/datum/admin_help/proc/RemoveActive() + if(state != AHELP_ACTIVE) + return + closed_at = world.time + QDEL_NULL(statclick) + GLOB.ahelp_tickets.active_tickets -= src + if(initiator && initiator.current_ticket == src) + initiator.current_ticket = null + +//Mark open ticket as closed/meme +/datum/admin_help/proc/Close(key_name = key_name_admin(usr), silent = FALSE) + if(state != AHELP_ACTIVE) + return + RemoveActive() + state = AHELP_CLOSED + GLOB.ahelp_tickets.ListInsert(src) + AddInteraction("Closed by [key_name].") + if(!silent) + SSblackbox.record_feedback("tally", "ahelp_stats", 1, "closed") + var/msg = "Ticket [TicketHref("#[id]")] closed by [key_name]." + message_admins(msg) + SSblackbox.LogAhelp(id, "Closed", "Closed by [usr.key]", null, usr.ckey) + log_admin_private(msg) + +//Mark open ticket as resolved/legitimate, returns ahelp verb +/datum/admin_help/proc/Resolve(key_name = key_name_admin(usr), silent = FALSE) + if(state != AHELP_ACTIVE) + return + RemoveActive() + state = AHELP_RESOLVED + GLOB.ahelp_tickets.ListInsert(src) + + addtimer(CALLBACK(initiator, /client/proc/giveadminhelpverb), 50) + + AddInteraction("Resolved by [key_name].") + to_chat(initiator, "Your ticket has been resolved by an admin. The Adminhelp verb will be returned to you shortly.", confidential = TRUE) + if(!silent) + SSblackbox.record_feedback("tally", "ahelp_stats", 1, "resolved") + var/msg = "Ticket [TicketHref("#[id]")] resolved by [key_name]" + message_admins(msg) + SSblackbox.LogAhelp(id, "Resolved", "Resolved by [usr.key]", null, usr.ckey) + log_admin_private(msg) + +//Close and return ahelp verb, use if ticket is incoherent +/datum/admin_help/proc/Reject(key_name = key_name_admin(usr)) + if(state != AHELP_ACTIVE) + return + + if(initiator) + initiator.giveadminhelpverb() + + SEND_SOUND(initiator, sound('sound/effects/adminhelp.ogg')) + + to_chat(initiator, "- AdminHelp Rejected! -", confidential = TRUE) + to_chat(initiator, "Your admin help was rejected. The adminhelp verb has been returned to you so that you may try again.", confidential = TRUE) + to_chat(initiator, "Please try to be calm, clear, and descriptive in admin helps, do not assume the admin has seen any related events, and clearly state the names of anybody you are reporting.", confidential = TRUE) + + SSblackbox.record_feedback("tally", "ahelp_stats", 1, "rejected") + var/msg = "Ticket [TicketHref("#[id]")] rejected by [key_name]" + message_admins(msg) + log_admin_private(msg) + AddInteraction("Rejected by [key_name].") + SSblackbox.LogAhelp(id, "Rejected", "Rejected by [usr.key]", null, usr.ckey) + Close(silent = TRUE) + +//Resolve ticket with IC Issue message +/datum/admin_help/proc/ICIssue(key_name = key_name_admin(usr)) + if(state != AHELP_ACTIVE) + return + + var/msg = "- AdminHelp marked as IC issue! -
                    " + msg += "Your issue has been determined by an administrator to be an in character issue and does NOT require administrator intervention at this time. For further resolution you should pursue options that are in character." + + if(initiator) + to_chat(initiator, msg, confidential = TRUE) + + SSblackbox.record_feedback("tally", "ahelp_stats", 1, "IC") + msg = "Ticket [TicketHref("#[id]")] marked as IC by [key_name]" + message_admins(msg) + log_admin_private(msg) + AddInteraction("Marked as IC issue by [key_name]") + SSblackbox.LogAhelp(id, "IC Issue", "Marked as IC issue by [usr.key]", null, usr.ckey) + Resolve(silent = TRUE) + +//Show the ticket panel +/datum/admin_help/proc/TicketPanel() + var/list/dat = list("Ticket #[id]") + var/ref_src = "[REF(src)]" + dat += "

                    Admin Help Ticket #[id]: [LinkedReplyName(ref_src)]

                    " + dat += "State: " + switch(state) + if(AHELP_ACTIVE) + dat += "OPEN" + if(AHELP_RESOLVED) + dat += "RESOLVED" + if(AHELP_CLOSED) + dat += "CLOSED" + else + dat += "UNKNOWN" + dat += "[FOURSPACES][TicketHref("Refresh", ref_src)][FOURSPACES][TicketHref("Re-Title", ref_src, "retitle")]" + if(state != AHELP_ACTIVE) + dat += "[FOURSPACES][TicketHref("Reopen", ref_src, "reopen")]" + dat += "

                    Opened at: [gameTimestamp(wtime = opened_at)] (Approx [DisplayTimeText(world.time - opened_at)] ago)" + if(closed_at) + dat += "
                    Closed at: [gameTimestamp(wtime = closed_at)] (Approx [DisplayTimeText(world.time - closed_at)] ago)" + dat += "

                    " + if(initiator) + dat += "Actions: [FullMonty(ref_src)]
                    " + else + dat += "DISCONNECTED[FOURSPACES][ClosureLinks(ref_src)]
                    " + dat += "
                    Log:

                    " + for(var/I in _interactions) + dat += "[I]
                    " + + usr << browse(dat.Join(), "window=ahelp[id];size=620x480") + +/datum/admin_help/proc/Retitle() + var/new_title = input(usr, "Enter a title for the ticket", "Rename Ticket", name) as text|null + if(new_title) + name = new_title + //not saying the original name cause it could be a long ass message + var/msg = "Ticket [TicketHref("#[id]")] titled [name] by [key_name_admin(usr)]" + message_admins(msg) + log_admin_private(msg) + TicketPanel() //we have to be here to do this + +//Forwarded action from admin/Topic +/datum/admin_help/proc/Action(action) + testing("Ahelp action: [action]") + switch(action) + if("ticket") + TicketPanel() + if("retitle") + Retitle() + if("reject") + Reject() + if("reply") + usr.client.cmd_ahelp_reply(initiator) + if("icissue") + ICIssue() + if("close") + Close() + if("resolve") + Resolve() + if("reopen") + Reopen() + +// +// TICKET STATCLICK +// + +/obj/effect/statclick/ahelp + var/datum/admin_help/ahelp_datum + +/obj/effect/statclick/ahelp/Initialize(mapload, datum/admin_help/AH) + ahelp_datum = AH + . = ..() + +/obj/effect/statclick/ahelp/update() + return ..(ahelp_datum.name) + +/obj/effect/statclick/ahelp/Click() + ahelp_datum.TicketPanel() + +/obj/effect/statclick/ahelp/Destroy() + ahelp_datum = null + return ..() + +// +// CLIENT PROCS +// + +/client/proc/giveadminhelpverb() + src.verbs |= /client/verb/adminhelp + deltimer(adminhelptimerid) + adminhelptimerid = 0 + +// Used for methods where input via arg doesn't work +/client/proc/get_adminhelp() + var/msg = input(src, "Please describe your problem concisely and an admin will help as soon as they're able.", "Adminhelp contents") as message|null + adminhelp(msg) + +/client/verb/adminhelp(msg as message) + set category = "Admin" + set name = "Adminhelp" + + if(GLOB.say_disabled) //This is here to try to identify lag problems + to_chat(usr, "Speech is currently admin-disabled.", confidential = TRUE) + return + + //handle muting and automuting + if(prefs.muted & MUTE_ADMINHELP) + to_chat(src, "Error: Admin-PM: You cannot send adminhelps (Muted).", confidential = TRUE) + return + if(handle_spam_prevention(msg,MUTE_ADMINHELP)) + return + + msg = trim(msg) + + if(!msg) + return + + SSblackbox.record_feedback("tally", "admin_verb", 1, "Adminhelp") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + if(current_ticket) + if(alert(usr, "You already have a ticket open. Is this for the same issue?",,"Yes","No") != "No") + if(current_ticket) + current_ticket.MessageNoRecipient(msg) + current_ticket.TimeoutVerb() + return + else + to_chat(usr, "Ticket not found, creating new one...", confidential = TRUE) + else + current_ticket.AddInteraction("[key_name_admin(usr)] opened a new ticket.") + current_ticket.Close() + + //Extremely simple system of suggesting mentorhelp instead of adminhelp + var/msg_lower = lowertext(msg) + if((findtext(msg_lower, "how to") == 1 || findtext(msg_lower, "how do") == 1) && GLOB.mentors.len) + if(alert("\"[msg]\" looks like a game mechanics question, would you like to ask in mentorhelp instead?", "Adminhelp?", "Yes, mentorhelp", "No, adminhelp") == "Yes, mentorhelp") + mentorhelp(msg) + return + + new /datum/admin_help(msg, src, FALSE) + +// +// LOGGING +// + +//Use this proc when an admin takes action that may be related to an open ticket on what +//what can be a client, ckey, or mob +/proc/admin_ticket_log(what, message) + var/client/C + var/mob/Mob = what + if(istype(Mob)) + C = Mob.client + else + C = what + if(istype(C) && C.current_ticket) + C.current_ticket.AddInteraction(message) + return C.current_ticket + if(istext(what)) //ckey + var/datum/admin_help/AH = GLOB.ahelp_tickets.CKey2ActiveTicket(what) + if(AH) + AH.AddInteraction(message) + return AH + +// +// HELPER PROCS +// + +/proc/get_admin_counts(requiredflags = R_BAN) + . = 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)) + .["noflags"] += X + else if(X.is_afk()) + .["afk"] += X + else if(X.holder.fakekey) + .["stealth"] += X + else + .["present"] += X + +/proc/send2tgs_adminless_only(source, msg, requiredflags = R_BAN) + var/list/adm = get_admin_counts(requiredflags) + var/list/activemins = adm["present"] + . = activemins.len + if(. <= 0) + var/final = "" + var/list/afkmins = adm["afk"] + var/list/stealthmins = adm["stealth"] + var/list/powerlessmins = adm["noflags"] + var/list/allmins = adm["total"] + if(!afkmins.len && !stealthmins.len && !powerlessmins.len) + 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] " + send2tgs(source,final) + send2otherserver(source,final) + + +/proc/send2tgs(msg,msg2) + msg = replacetext(replacetext(msg, "\proper", ""), "\improper", "") + msg2 = replacetext(replacetext(msg2, "\proper", ""), "\improper", "") + world.TgsTargetedChatBroadcast("[msg] | [msg2]", TRUE) + +// +/proc/send2otherserver(source,msg,type = "Ahelp",target_servers) + var/comms_key = CONFIG_GET(string/comms_key) + if(!comms_key) + return + + var/our_id = CONFIG_GET(string/cross_comms_name) + var/list/message = list() + message["message_sender"] = source + message["message"] = msg + message["source"] = "([our_id])" + message["key"] = comms_key + message += type + + var/list/servers = CONFIG_GET(keyed_list/cross_server) + for(var/I in servers) + if(I == our_id) //No sending to ourselves + continue + if(target_servers && !(I in target_servers)) + continue + world.Export("[servers[I]]?[list2params(message)]") + + +/proc/tgsadminwho() + var/list/message = list("Admins: ") + var/list/admin_keys = list() + for(var/adm in GLOB.admins) + var/client/C = adm + admin_keys += "[C][C.holder.fakekey ? "(Stealth)" : ""][C.is_afk() ? "(AFK)" : ""]" + + for(var/admin in admin_keys) + if(LAZYLEN(message) > 1) + message += ", [admin]" + else + message += "[admin]" + + return jointext(message, "") + +/proc/keywords_lookup(msg,external) + + //This is a list of words which are ignored by the parser when comparing message contents for names. MUST BE IN LOWER CASE! + var/list/adminhelp_ignored_words = list("unknown","the","a","an","of","monkey","alien","as", "i") + + //explode the input msg into a list + var/list/msglist = splittext(msg, " ") + + //generate keywords lookup + var/list/surnames = list() + var/list/forenames = list() + var/list/ckeys = list() + var/founds = "" + for(var/mob/M in GLOB.mob_list) + var/list/indexing = list(M.real_name, M.name) + if(M.mind) + indexing += M.mind.name + + for(var/string in indexing) + var/list/L = splittext(string, " ") + var/surname_found = 0 + //surnames + for(var/i=L.len, i>=1, i--) + var/word = ckey(L[i]) + if(word) + surnames[word] = M + surname_found = i + break + //forenames + for(var/i=1, i(?|F) " + continue + msg += "[original_word] " + if(external) + if(founds == "") + return "Search Failed" + else + return founds + + return msg diff --git a/code/modules/admin/verbs/adminjump.dm b/code/modules/admin/verbs/adminjump.dm index 2100dc21cdae..69dc373d70bf 100644 --- a/code/modules/admin/verbs/adminjump.dm +++ b/code/modules/admin/verbs/adminjump.dm @@ -1,160 +1,160 @@ -/client/proc/jumptoarea(area/A in GLOB.sortedAreas) - set name = "Jump to Area" - set desc = "Area to jump to" - set category = "Admin - Game" - if(!src.holder) - to_chat(src, "Only administrators may use this command.", confidential = TRUE) - return - - if(!A) - return - - var/list/turfs = list() - for(var/turf/T in A) - if(T.density) - continue - turfs.Add(T) - - if(length(turfs)) - var/turf/T = pick(turfs) - usr.forceMove(T) - log_admin("[key_name(usr)] jumped to [AREACOORD(A)]") - message_admins("[key_name_admin(usr)] jumped to [AREACOORD(A)]") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Jump To Area") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - else - to_chat(src, "Nowhere to jump to!", confidential = TRUE) - return - - -/client/proc/jumptoturf(turf/T in world) - set name = "Jump to Turf" - set category = "Admin - Game" - if(!src.holder) - to_chat(src, "Only administrators may use this command.", confidential = TRUE) - return - - log_admin("[key_name(usr)] jumped to [AREACOORD(T)]") - message_admins("[key_name_admin(usr)] jumped to [AREACOORD(T)]") - usr.forceMove(T) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Jump To Turf") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - return - -/client/proc/jumptomob(mob/M in GLOB.mob_list) - set category = "Admin - Game" - set name = "Jump to Mob" - - if(!src.holder) - to_chat(src, "Only administrators may use this command.", confidential = TRUE) - return - - log_admin("[key_name(usr)] jumped to [key_name(M)]") - message_admins("[key_name_admin(usr)] jumped to [ADMIN_LOOKUPFLW(M)] at [AREACOORD(M)]") - if(src.mob) - var/mob/A = src.mob - var/turf/T = get_turf(M) - if(T && isturf(T)) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Jump To Mob") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - A.forceMove(M.loc) - else - to_chat(A, "This mob is not located in the game world.", confidential = TRUE) - -/client/proc/jumptocoord(tx as num, ty as num, tz as num) - set category = "Admin - Game" - set name = "Jump to Coordinate" - - if (!holder) - to_chat(src, "Only administrators may use this command.", confidential = TRUE) - return - - if(src.mob) - var/mob/A = src.mob - var/turf/T = locate(tx,ty,tz) - A.forceMove(T) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Jump To Coordiate") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - message_admins("[key_name_admin(usr)] jumped to coordinates [tx], [ty], [tz]") - -/client/proc/jumptokey() - set category = "Admin - Game" - set name = "Jump to Key" - - if(!src.holder) - to_chat(src, "Only administrators may use this command.", confidential = TRUE) - return - - var/list/keys = list() - for(var/mob/M in GLOB.player_list) - keys += M.client - var/client/selection = input("Please, select a player!", "Admin Jumping", null, null) as null|anything in sortKey(keys) - if(!selection) - to_chat(src, "No keys found.", confidential = TRUE) - return - var/mob/M = selection.mob - log_admin("[key_name(usr)] jumped to [key_name(M)]") - message_admins("[key_name_admin(usr)] jumped to [ADMIN_LOOKUPFLW(M)]") - - usr.forceMove(M.loc) - - SSblackbox.record_feedback("tally", "admin_verb", 1, "Jump To Key") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/Getmob(mob/M in GLOB.mob_list - GLOB.dummy_mob_list) - set category = "Admin - Game" - set name = "Get Mob" - set desc = "Mob to teleport" - if(!src.holder) - to_chat(src, "Only administrators may use this command.", confidential = TRUE) - return - - var/atom/loc = get_turf(usr) - log_admin("[key_name(usr)] teleported [key_name(M)] to [AREACOORD(loc)]") - var/msg = "[key_name_admin(usr)] teleported [ADMIN_LOOKUPFLW(M)] to [ADMIN_VERBOSEJMP(loc)]" - message_admins(msg) - admin_ticket_log(M, msg) - M.forceMove(loc) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Get Mob") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/Getkey() - set category = "Admin - Game" - set name = "Get Key" - set desc = "Key to teleport" - - if(!src.holder) - to_chat(src, "Only administrators may use this command.", confidential = TRUE) - return - - var/list/keys = list() - for(var/mob/M in GLOB.player_list) - keys += M.client - var/client/selection = input("Please, select a player!", "Admin Jumping", null, null) as null|anything in sortKey(keys) - if(!selection) - return - var/mob/M = selection.mob - - if(!M) - return - log_admin("[key_name(usr)] teleported [key_name(M)]") - var/msg = "[key_name_admin(usr)] teleported [ADMIN_LOOKUPFLW(M)]" - message_admins(msg) - admin_ticket_log(M, msg) - if(M) - M.forceMove(get_turf(usr)) - usr.forceMove(M.loc) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Get Key") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/sendmob(mob/M in sortmobs()) - set category = "Admin - Game" - set name = "Send Mob" - if(!src.holder) - to_chat(src, "Only administrators may use this command.", confidential = TRUE) - return - var/area/A = input(usr, "Pick an area.", "Pick an area") in GLOB.sortedAreas|null - if(A && istype(A)) - var/list/turfs = get_area_turfs(A) - if(length(turfs) && M.forceMove(pick(turfs))) - - log_admin("[key_name(usr)] teleported [key_name(M)] to [AREACOORD(A)]") - var/msg = "[key_name_admin(usr)] teleported [ADMIN_LOOKUPFLW(M)] to [AREACOORD(A)]" - message_admins(msg) - admin_ticket_log(M, msg) - else - to_chat(src, "Failed to move mob to a valid location.", confidential = TRUE) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Send Mob") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! +/client/proc/jumptoarea(area/A in GLOB.sortedAreas) + set name = "Jump to Area" + set desc = "Area to jump to" + set category = "Admin - Game" + if(!src.holder) + to_chat(src, "Only administrators may use this command.", confidential = TRUE) + return + + if(!A) + return + + var/list/turfs = list() + for(var/turf/T in A) + if(T.density) + continue + turfs.Add(T) + + if(length(turfs)) + var/turf/T = pick(turfs) + usr.forceMove(T) + log_admin("[key_name(usr)] jumped to [AREACOORD(A)]") + message_admins("[key_name_admin(usr)] jumped to [AREACOORD(A)]") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Jump To Area") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + else + to_chat(src, "Nowhere to jump to!", confidential = TRUE) + return + + +/client/proc/jumptoturf(turf/T in world) + set name = "Jump to Turf" + set category = "Admin - Game" + if(!src.holder) + to_chat(src, "Only administrators may use this command.", confidential = TRUE) + return + + log_admin("[key_name(usr)] jumped to [AREACOORD(T)]") + message_admins("[key_name_admin(usr)] jumped to [AREACOORD(T)]") + usr.forceMove(T) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Jump To Turf") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + return + +/client/proc/jumptomob(mob/M in GLOB.mob_list) + set category = "Admin - Game" + set name = "Jump to Mob" + + if(!src.holder) + to_chat(src, "Only administrators may use this command.", confidential = TRUE) + return + + log_admin("[key_name(usr)] jumped to [key_name(M)]") + message_admins("[key_name_admin(usr)] jumped to [ADMIN_LOOKUPFLW(M)] at [AREACOORD(M)]") + if(src.mob) + var/mob/A = src.mob + var/turf/T = get_turf(M) + if(T && isturf(T)) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Jump To Mob") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + A.forceMove(M.loc) + else + to_chat(A, "This mob is not located in the game world.", confidential = TRUE) + +/client/proc/jumptocoord(tx as num, ty as num, tz as num) + set category = "Admin - Game" + set name = "Jump to Coordinate" + + if (!holder) + to_chat(src, "Only administrators may use this command.", confidential = TRUE) + return + + if(src.mob) + var/mob/A = src.mob + var/turf/T = locate(tx,ty,tz) + A.forceMove(T) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Jump To Coordiate") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + message_admins("[key_name_admin(usr)] jumped to coordinates [tx], [ty], [tz]") + +/client/proc/jumptokey() + set category = "Admin - Game" + set name = "Jump to Key" + + if(!src.holder) + to_chat(src, "Only administrators may use this command.", confidential = TRUE) + return + + var/list/keys = list() + for(var/mob/M in GLOB.player_list) + keys += M.client + var/client/selection = input("Please, select a player!", "Admin Jumping", null, null) as null|anything in sortKey(keys) + if(!selection) + to_chat(src, "No keys found.", confidential = TRUE) + return + var/mob/M = selection.mob + log_admin("[key_name(usr)] jumped to [key_name(M)]") + message_admins("[key_name_admin(usr)] jumped to [ADMIN_LOOKUPFLW(M)]") + + usr.forceMove(M.loc) + + SSblackbox.record_feedback("tally", "admin_verb", 1, "Jump To Key") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/Getmob(mob/M in GLOB.mob_list - GLOB.dummy_mob_list) + set category = "Admin - Game" + set name = "Get Mob" + set desc = "Mob to teleport" + if(!src.holder) + to_chat(src, "Only administrators may use this command.", confidential = TRUE) + return + + var/atom/loc = get_turf(usr) + log_admin("[key_name(usr)] teleported [key_name(M)] to [AREACOORD(loc)]") + var/msg = "[key_name_admin(usr)] teleported [ADMIN_LOOKUPFLW(M)] to [ADMIN_VERBOSEJMP(loc)]" + message_admins(msg) + admin_ticket_log(M, msg) + M.forceMove(loc) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Get Mob") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/Getkey() + set category = "Admin - Game" + set name = "Get Key" + set desc = "Key to teleport" + + if(!src.holder) + to_chat(src, "Only administrators may use this command.", confidential = TRUE) + return + + var/list/keys = list() + for(var/mob/M in GLOB.player_list) + keys += M.client + var/client/selection = input("Please, select a player!", "Admin Jumping", null, null) as null|anything in sortKey(keys) + if(!selection) + return + var/mob/M = selection.mob + + if(!M) + return + log_admin("[key_name(usr)] teleported [key_name(M)]") + var/msg = "[key_name_admin(usr)] teleported [ADMIN_LOOKUPFLW(M)]" + message_admins(msg) + admin_ticket_log(M, msg) + if(M) + M.forceMove(get_turf(usr)) + usr.forceMove(M.loc) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Get Key") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/sendmob(mob/M in sortmobs()) + set category = "Admin - Game" + set name = "Send Mob" + if(!src.holder) + to_chat(src, "Only administrators may use this command.", confidential = TRUE) + return + var/area/A = input(usr, "Pick an area.", "Pick an area") in GLOB.sortedAreas|null + if(A && istype(A)) + var/list/turfs = get_area_turfs(A) + if(length(turfs) && M.forceMove(pick(turfs))) + + log_admin("[key_name(usr)] teleported [key_name(M)] to [AREACOORD(A)]") + var/msg = "[key_name_admin(usr)] teleported [ADMIN_LOOKUPFLW(M)] to [AREACOORD(A)]" + message_admins(msg) + admin_ticket_log(M, msg) + else + to_chat(src, "Failed to move mob to a valid location.", confidential = TRUE) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Send Mob") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! diff --git a/code/modules/admin/verbs/adminpm.dm b/code/modules/admin/verbs/adminpm.dm index 453e4031a9d1..f34f0fb4a330 100644 --- a/code/modules/admin/verbs/adminpm.dm +++ b/code/modules/admin/verbs/adminpm.dm @@ -1,333 +1,333 @@ -#define EXTERNALREPLYCOUNT 2 - -//allows right clicking mobs to send an admin PM to their client, forwards the selected mob's client to cmd_admin_pm -/client/proc/cmd_admin_pm_context(mob/M in GLOB.mob_list) - set category = null - set name = "Admin PM Mob" - if(!holder) - to_chat(src, "Error: Admin-PM-Context: Only administrators may use this command.", confidential = TRUE) - return - if( !ismob(M) || !M.client ) - return - cmd_admin_pm(M.client,null) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Admin PM Mob") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -//shows a list of clients we could send PMs to, then forwards our choice to cmd_admin_pm -/client/proc/cmd_admin_pm_panel() - set category = "Admin" - set name = "Admin PM" - if(!holder) - to_chat(src, "Error: Admin-PM-Panel: Only administrators may use this command.", confidential = TRUE) - return - var/list/client/targets[0] - for(var/client/T) - if(T.mob) - if(isnewplayer(T.mob)) - targets["(New Player) - [T]"] = T - else if(isobserver(T.mob)) - targets["[T.mob.name](Ghost) - [T]"] = T - else - targets["[T.mob.real_name](as [T.mob.name]) - [T]"] = T - else - targets["(No Mob) - [T]"] = T - var/target = input(src,"To whom shall we send a message?","Admin PM",null) as null|anything in sortList(targets) - cmd_admin_pm(targets[target],null) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Admin PM") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_ahelp_reply(whom) - if(prefs.muted & MUTE_ADMINHELP) - to_chat(src, "Error: Admin-PM: You are unable to use admin PM-s (muted).", confidential = TRUE) - return - var/client/C - if(istext(whom)) - if(whom[1] == "@") - whom = findStealthKey(whom) - C = GLOB.directory[whom] - else if(istype(whom, /client)) - C = whom - if(!C) - if(holder) - to_chat(src, "Error: Admin-PM: Client not found.", confidential = TRUE) - return - - var/datum/admin_help/AH = C.current_ticket - - if(AH) - message_admins("[key_name_admin(src)] has started replying to [key_name_admin(C, 0, 0)]'s admin help.") - var/msg = input(src,"Message:", "Private message to [C.holder?.fakekey ? "an Administrator" : key_name(C, 0, 0)].") as message|null - if (!msg) - message_admins("[key_name_admin(src)] has cancelled their reply to [key_name_admin(C, 0, 0)]'s admin help.") - return - cmd_admin_pm(whom, msg) - -//takes input from cmd_admin_pm_context, cmd_admin_pm_panel or /client/Topic and sends them a PM. -//Fetching a message if needed. src is the sender and C is the target client -/client/proc/cmd_admin_pm(whom, msg) - if(prefs.muted & MUTE_ADMINHELP) - to_chat(src, "Error: Admin-PM: You are unable to use admin PM-s (muted).", confidential = TRUE) - return - - if(!holder && !current_ticket) //no ticket? https://www.youtube.com/watch?v=iHSPf6x1Fdo - to_chat(src, "You can no longer reply to this ticket, please open another one by using the Adminhelp verb if need be.", confidential = TRUE) - to_chat(src, "Message: [msg]", confidential = TRUE) - return - - var/client/recipient - var/external = 0 - if(istext(whom)) - if(whom[1] == "@") - whom = findStealthKey(whom) - if(whom == "IRCKEY") - external = 1 - else - recipient = GLOB.directory[whom] - else if(istype(whom, /client)) - recipient = whom - - - if(external) - if(!externalreplyamount) //to prevent people from spamming irc/discord - return - if(!msg) - msg = input(src,"Message:", "Private message to Administrator") as message|null - - if(!msg) - return - if(holder) - to_chat(src, "Error: Use the admin IRC/Discord channel, nerd.", confidential = TRUE) - return - - - else - if(!recipient) - if(holder) - to_chat(src, "Error: Admin-PM: Client not found.", confidential = TRUE) - if(msg) - to_chat(src, msg, confidential = TRUE) - return - else if(msg) // you want to continue if there's no message instead of returning now - current_ticket.MessageNoRecipient(msg) - return - - //get message text, limit it's length.and clean/escape html - if(!msg) - msg = input(src,"Message:", "Private message to [recipient.holder?.fakekey ? "an Administrator" : key_name(recipient, 0, 0)].") as message|null - msg = trim(msg) - if(!msg) - return - - if(prefs.muted & MUTE_ADMINHELP) - to_chat(src, "Error: Admin-PM: You are unable to use admin PM-s (muted).", confidential = TRUE) - return - - if(!recipient) - if(holder) - to_chat(src, "Error: Admin-PM: Client not found.", confidential = TRUE) - else - current_ticket.MessageNoRecipient(msg) - return - - if (src.handle_spam_prevention(msg,MUTE_ADMINHELP)) - return - - //clean the message if it's not sent by a high-rank admin - if(!check_rights(R_SERVER|R_DEBUG,0)||external)//no sending html to the poor bots - msg = trim(sanitize(msg), MAX_MESSAGE_LEN) - if(!msg) - return - - var/rawmsg = msg - - if(holder) - msg = emoji_parse(msg) - - var/keywordparsedmsg = keywords_lookup(msg) - - if(external) - 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]") - externalreplyamount-- - SSredbot.send_discord_message("admin", "[AH ? "#[AH.id] " : ""]Reply: [ckey]") - send2tgs("[AH ? "#[AH.id] " : ""]Reply: [ckey]", rawmsg) - else - if(recipient.holder) - if(holder) //both are admins - to_chat(recipient, "Admin PM from-[key_name(src, recipient, 1)]: [keywordparsedmsg]", confidential = TRUE) - to_chat(src, "Admin PM to-[key_name(recipient, src, 1)]: [keywordparsedmsg]", confidential = TRUE) - - //omg this is dumb, just fill in both their tickets - var/interaction_message = "PM from-[key_name(src, recipient, 1)] to-[key_name(recipient, src, 1)]: [keywordparsedmsg]" - admin_ticket_log(src, interaction_message) - if(recipient != src) //reeee - admin_ticket_log(recipient, interaction_message) - SSblackbox.LogAhelp(current_ticket.id, "Reply", msg, recipient.ckey, src.ckey) - else //recipient is an admin but sender is not - var/replymsg = "Reply PM from-[key_name(src, recipient, 1)]: [keywordparsedmsg]" - admin_ticket_log(src, "[replymsg]") - to_chat(recipient, "[replymsg]", confidential = TRUE) - to_chat(src, "PM to-Admins: [msg]", confidential = TRUE) - SSblackbox.LogAhelp(current_ticket.id, "Reply", msg, recipient.ckey, src.ckey) - - //play the receiving admin the adminhelp sound (if they have them enabled) - if(recipient.prefs.toggles & SOUND_ADMINHELP) - SEND_SOUND(recipient, sound('sound/effects/adminhelp.ogg')) - - else - if(holder) //sender is an admin but recipient is not. Do BIG RED TEXT - var/already_logged = FALSE - if(!recipient.current_ticket) - new /datum/admin_help(msg, recipient, TRUE) - already_logged = TRUE - SSblackbox.LogAhelp(recipient.current_ticket.id, "Ticket Opened", msg, recipient.ckey, src.ckey) - - to_chat(recipient, "-- Administrator private message --", confidential = TRUE) - to_chat(recipient, "Admin PM from-[key_name(src, recipient, 0)]: [msg]", confidential = TRUE) - to_chat(recipient, "Click on the administrator's name to reply.", confidential = TRUE) - to_chat(src, "Admin PM to-[key_name(recipient, src, 1)]: [msg]", confidential = TRUE) - - admin_ticket_log(recipient, "PM From [key_name_admin(src)]: [keywordparsedmsg]") - - if(!already_logged) //Reply to an existing ticket - SSblackbox.LogAhelp(recipient.current_ticket.id, "Reply", msg, recipient.ckey, src.ckey) - - - //always play non-admin recipients the adminhelp sound - SEND_SOUND(recipient, sound('sound/effects/adminhelp.ogg')) - - //AdminPM popup for ApocStation and anybody else who wants to use it. Set it with POPUP_ADMIN_PM in config.txt ~Carn - if(CONFIG_GET(flag/popup_admin_pm)) - INVOKE_ASYNC(src, .proc/popup_admin_pm, recipient, msg) - - else //neither are admins - to_chat(src, "Error: Admin-PM: Non-admin to non-admin PM communication is forbidden.", confidential = TRUE) - return - - if(external) - log_admin_private("PM: [key_name(src)]->External: [rawmsg]") - for(var/client/X in GLOB.admins) - to_chat(X, "PM: [key_name(src, X, 0)]->External: [keywordparsedmsg]", confidential = TRUE) - else - window_flash(recipient, ignorepref = TRUE) - log_admin_private("PM: [key_name(src)]->[key_name(recipient)]: [rawmsg]") - //we don't use message_admins here because the sender/receiver might get it too - for(var/client/X in GLOB.admins) - if(X.key!=key && X.key!=recipient.key) //check client/X is an admin and isn't the sender or recipient - to_chat(X, "PM: [key_name(src, X, 0)]->[key_name(recipient, X, 0)]: [keywordparsedmsg]" , confidential = TRUE) - -/client/proc/popup_admin_pm(client/recipient, msg) - var/sender = src - var/sendername = key - var/reply = input(recipient, msg,"Admin PM from-[sendername]", "") as message|null //show message and await a reply - if(recipient && reply) - if(sender) - recipient.cmd_admin_pm(sender,reply) //sender is still about, let's reply to them - else - adminhelp(reply) //sender has left, adminhelp instead - -#define TGS_AHELP_USAGE "Usage: ticket " -/proc/TgsPm(target,msg,sender) - target = ckey(target) - var/client/C = GLOB.directory[target] - - var/datum/admin_help/ticket = C ? C.current_ticket : GLOB.ahelp_tickets.CKey2ActiveTicket(target) - var/compliant_msg = trim(lowertext(msg)) - var/tgs_tagged = "[sender](TGS/External)" - var/list/splits = splittext(compliant_msg, " ") - if(splits.len && splits[1] == "ticket") - if(splits.len < 2) - return TGS_AHELP_USAGE - switch(splits[2]) - if("close") - if(ticket) - ticket.Close(tgs_tagged) - return "Ticket #[ticket.id] successfully closed" - if("resolve") - if(ticket) - ticket.Resolve(tgs_tagged) - return "Ticket #[ticket.id] successfully resolved" - if("icissue") - if(ticket) - ticket.ICIssue(tgs_tagged) - return "Ticket #[ticket.id] successfully marked as IC issue" - if("reject") - if(ticket) - ticket.Reject(tgs_tagged) - return "Ticket #[ticket.id] successfully rejected" - if("reopen") - if(ticket) - return "Error: [target] already has ticket #[ticket.id] open" - var/fail = splits.len < 3 ? null : -1 - if(!isnull(fail)) - fail = text2num(splits[3]) - if(isnull(fail)) - return "Error: No/Invalid ticket id specified. [TGS_AHELP_USAGE]" - var/datum/admin_help/AH = GLOB.ahelp_tickets.TicketByID(fail) - if(!AH) - return "Error: Ticket #[fail] not found" - if(AH.initiator_ckey != target) - return "Error: Ticket #[fail] belongs to [AH.initiator_ckey]" - AH.Reopen() - return "Ticket #[ticket.id] successfully reopened" - if("list") - var/list/tickets = GLOB.ahelp_tickets.TicketsByCKey(target) - if(!tickets.len) - return "None" - . = "" - for(var/I in tickets) - var/datum/admin_help/AH = I - if(.) - . += ", " - if(AH == ticket) - . += "Active: " - . += "#[AH.id]" - return - else - return TGS_AHELP_USAGE - return "Error: Ticket could not be found" - - var/static/stealthkey - var/adminname = CONFIG_GET(flag/show_irc_name) ? tgs_tagged : "Administrator" - - if(!C) - return "Error: No client" - - if(!stealthkey) - stealthkey = GenTgsStealthKey() - - msg = sanitize(copytext_char(msg, 1, MAX_MESSAGE_LEN)) - if(!msg) - return "Error: No message" - - message_admins("External message from [sender] to [key_name_admin(C)] : [msg]") - SSredbot.send_discord_message("admin", "External message from [sender] to [key_name_admin(C)] : [msg]") - log_admin_private("External PM: [sender] -> [key_name(C)] : [msg]") - msg = emoji_parse(msg) - - to_chat(C, "-- Administrator private message --", confidential = TRUE) - to_chat(C, "Admin PM from-[adminname]: [msg]", confidential = TRUE) - to_chat(C, "Click on the administrator's name to reply.", confidential = TRUE) - - admin_ticket_log(C, "PM From [tgs_tagged]: [msg]") - - window_flash(C, ignorepref = TRUE) - //always play non-admin recipients the adminhelp sound - SEND_SOUND(C, 'sound/effects/adminhelp.ogg') - - C.externalreplyamount = EXTERNALREPLYCOUNT - - return "Message Successful" - -/proc/GenTgsStealthKey() - var/num = (rand(0,1000)) - var/i = 0 - while(i == 0) - i = 1 - for(var/P in GLOB.stealthminID) - if(num == GLOB.stealthminID[P]) - num++ - i = 0 - var/stealth = "@[num2text(num)]" - GLOB.stealthminID["IRCKEY"] = stealth - return stealth - -#undef EXTERNALREPLYCOUNT +#define EXTERNALREPLYCOUNT 2 + +//allows right clicking mobs to send an admin PM to their client, forwards the selected mob's client to cmd_admin_pm +/client/proc/cmd_admin_pm_context(mob/M in GLOB.mob_list) + set category = null + set name = "Admin PM Mob" + if(!holder) + to_chat(src, "Error: Admin-PM-Context: Only administrators may use this command.", confidential = TRUE) + return + if( !ismob(M) || !M.client ) + return + cmd_admin_pm(M.client,null) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Admin PM Mob") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +//shows a list of clients we could send PMs to, then forwards our choice to cmd_admin_pm +/client/proc/cmd_admin_pm_panel() + set category = "Admin" + set name = "Admin PM" + if(!holder) + to_chat(src, "Error: Admin-PM-Panel: Only administrators may use this command.", confidential = TRUE) + return + var/list/client/targets[0] + for(var/client/T) + if(T.mob) + if(isnewplayer(T.mob)) + targets["(New Player) - [T]"] = T + else if(isobserver(T.mob)) + targets["[T.mob.name](Ghost) - [T]"] = T + else + targets["[T.mob.real_name](as [T.mob.name]) - [T]"] = T + else + targets["(No Mob) - [T]"] = T + var/target = input(src,"To whom shall we send a message?","Admin PM",null) as null|anything in sortList(targets) + cmd_admin_pm(targets[target],null) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Admin PM") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_ahelp_reply(whom) + if(prefs.muted & MUTE_ADMINHELP) + to_chat(src, "Error: Admin-PM: You are unable to use admin PM-s (muted).", confidential = TRUE) + return + var/client/C + if(istext(whom)) + if(whom[1] == "@") + whom = findStealthKey(whom) + C = GLOB.directory[whom] + else if(istype(whom, /client)) + C = whom + if(!C) + if(holder) + to_chat(src, "Error: Admin-PM: Client not found.", confidential = TRUE) + return + + var/datum/admin_help/AH = C.current_ticket + + if(AH) + message_admins("[key_name_admin(src)] has started replying to [key_name_admin(C, 0, 0)]'s admin help.") + var/msg = input(src,"Message:", "Private message to [C.holder?.fakekey ? "an Administrator" : key_name(C, 0, 0)].") as message|null + if (!msg) + message_admins("[key_name_admin(src)] has cancelled their reply to [key_name_admin(C, 0, 0)]'s admin help.") + return + cmd_admin_pm(whom, msg) + +//takes input from cmd_admin_pm_context, cmd_admin_pm_panel or /client/Topic and sends them a PM. +//Fetching a message if needed. src is the sender and C is the target client +/client/proc/cmd_admin_pm(whom, msg) + if(prefs.muted & MUTE_ADMINHELP) + to_chat(src, "Error: Admin-PM: You are unable to use admin PM-s (muted).", confidential = TRUE) + return + + if(!holder && !current_ticket) //no ticket? https://www.youtube.com/watch?v=iHSPf6x1Fdo + to_chat(src, "You can no longer reply to this ticket, please open another one by using the Adminhelp verb if need be.", confidential = TRUE) + to_chat(src, "Message: [msg]", confidential = TRUE) + return + + var/client/recipient + var/external = 0 + if(istext(whom)) + if(whom[1] == "@") + whom = findStealthKey(whom) + if(whom == "IRCKEY") + external = 1 + else + recipient = GLOB.directory[whom] + else if(istype(whom, /client)) + recipient = whom + + + if(external) + if(!externalreplyamount) //to prevent people from spamming irc/discord + return + if(!msg) + msg = input(src,"Message:", "Private message to Administrator") as message|null + + if(!msg) + return + if(holder) + to_chat(src, "Error: Use the admin IRC/Discord channel, nerd.", confidential = TRUE) + return + + + else + if(!recipient) + if(holder) + to_chat(src, "Error: Admin-PM: Client not found.", confidential = TRUE) + if(msg) + to_chat(src, msg, confidential = TRUE) + return + else if(msg) // you want to continue if there's no message instead of returning now + current_ticket.MessageNoRecipient(msg) + return + + //get message text, limit it's length.and clean/escape html + if(!msg) + msg = input(src,"Message:", "Private message to [recipient.holder?.fakekey ? "an Administrator" : key_name(recipient, 0, 0)].") as message|null + msg = trim(msg) + if(!msg) + return + + if(prefs.muted & MUTE_ADMINHELP) + to_chat(src, "Error: Admin-PM: You are unable to use admin PM-s (muted).", confidential = TRUE) + return + + if(!recipient) + if(holder) + to_chat(src, "Error: Admin-PM: Client not found.", confidential = TRUE) + else + current_ticket.MessageNoRecipient(msg) + return + + if (src.handle_spam_prevention(msg,MUTE_ADMINHELP)) + return + + //clean the message if it's not sent by a high-rank admin + if(!check_rights(R_SERVER|R_DEBUG,0)||external)//no sending html to the poor bots + msg = trim(sanitize(msg), MAX_MESSAGE_LEN) + if(!msg) + return + + var/rawmsg = msg + + if(holder) + msg = emoji_parse(msg) + + var/keywordparsedmsg = keywords_lookup(msg) + + if(external) + 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]") + externalreplyamount-- + SSredbot.send_discord_message("admin", "[AH ? "#[AH.id] " : ""]Reply: [ckey]") + send2tgs("[AH ? "#[AH.id] " : ""]Reply: [ckey]", rawmsg) + else + if(recipient.holder) + if(holder) //both are admins + to_chat(recipient, "Admin PM from-[key_name(src, recipient, 1)]: [keywordparsedmsg]", confidential = TRUE) + to_chat(src, "Admin PM to-[key_name(recipient, src, 1)]: [keywordparsedmsg]", confidential = TRUE) + + //omg this is dumb, just fill in both their tickets + var/interaction_message = "PM from-[key_name(src, recipient, 1)] to-[key_name(recipient, src, 1)]: [keywordparsedmsg]" + admin_ticket_log(src, interaction_message) + if(recipient != src) //reeee + admin_ticket_log(recipient, interaction_message) + SSblackbox.LogAhelp(current_ticket.id, "Reply", msg, recipient.ckey, src.ckey) + else //recipient is an admin but sender is not + var/replymsg = "Reply PM from-[key_name(src, recipient, 1)]: [keywordparsedmsg]" + admin_ticket_log(src, "[replymsg]") + to_chat(recipient, "[replymsg]", confidential = TRUE) + to_chat(src, "PM to-Admins: [msg]", confidential = TRUE) + SSblackbox.LogAhelp(current_ticket.id, "Reply", msg, recipient.ckey, src.ckey) + + //play the receiving admin the adminhelp sound (if they have them enabled) + if(recipient.prefs.toggles & SOUND_ADMINHELP) + SEND_SOUND(recipient, sound('sound/effects/adminhelp.ogg')) + + else + if(holder) //sender is an admin but recipient is not. Do BIG RED TEXT + var/already_logged = FALSE + if(!recipient.current_ticket) + new /datum/admin_help(msg, recipient, TRUE) + already_logged = TRUE + SSblackbox.LogAhelp(recipient.current_ticket.id, "Ticket Opened", msg, recipient.ckey, src.ckey) + + to_chat(recipient, "-- Administrator private message --", confidential = TRUE) + to_chat(recipient, "Admin PM from-[key_name(src, recipient, 0)]: [msg]", confidential = TRUE) + to_chat(recipient, "Click on the administrator's name to reply.", confidential = TRUE) + to_chat(src, "Admin PM to-[key_name(recipient, src, 1)]: [msg]", confidential = TRUE) + + admin_ticket_log(recipient, "PM From [key_name_admin(src)]: [keywordparsedmsg]") + + if(!already_logged) //Reply to an existing ticket + SSblackbox.LogAhelp(recipient.current_ticket.id, "Reply", msg, recipient.ckey, src.ckey) + + + //always play non-admin recipients the adminhelp sound + SEND_SOUND(recipient, sound('sound/effects/adminhelp.ogg')) + + //AdminPM popup for ApocStation and anybody else who wants to use it. Set it with POPUP_ADMIN_PM in config.txt ~Carn + if(CONFIG_GET(flag/popup_admin_pm)) + INVOKE_ASYNC(src, .proc/popup_admin_pm, recipient, msg) + + else //neither are admins + to_chat(src, "Error: Admin-PM: Non-admin to non-admin PM communication is forbidden.", confidential = TRUE) + return + + if(external) + log_admin_private("PM: [key_name(src)]->External: [rawmsg]") + for(var/client/X in GLOB.admins) + to_chat(X, "PM: [key_name(src, X, 0)]->External: [keywordparsedmsg]", confidential = TRUE) + else + window_flash(recipient, ignorepref = TRUE) + log_admin_private("PM: [key_name(src)]->[key_name(recipient)]: [rawmsg]") + //we don't use message_admins here because the sender/receiver might get it too + for(var/client/X in GLOB.admins) + if(X.key!=key && X.key!=recipient.key) //check client/X is an admin and isn't the sender or recipient + to_chat(X, "PM: [key_name(src, X, 0)]->[key_name(recipient, X, 0)]: [keywordparsedmsg]" , confidential = TRUE) + +/client/proc/popup_admin_pm(client/recipient, msg) + var/sender = src + var/sendername = key + var/reply = input(recipient, msg,"Admin PM from-[sendername]", "") as message|null //show message and await a reply + if(recipient && reply) + if(sender) + recipient.cmd_admin_pm(sender,reply) //sender is still about, let's reply to them + else + adminhelp(reply) //sender has left, adminhelp instead + +#define TGS_AHELP_USAGE "Usage: ticket " +/proc/TgsPm(target,msg,sender) + target = ckey(target) + var/client/C = GLOB.directory[target] + + var/datum/admin_help/ticket = C ? C.current_ticket : GLOB.ahelp_tickets.CKey2ActiveTicket(target) + var/compliant_msg = trim(lowertext(msg)) + var/tgs_tagged = "[sender](TGS/External)" + var/list/splits = splittext(compliant_msg, " ") + if(splits.len && splits[1] == "ticket") + if(splits.len < 2) + return TGS_AHELP_USAGE + switch(splits[2]) + if("close") + if(ticket) + ticket.Close(tgs_tagged) + return "Ticket #[ticket.id] successfully closed" + if("resolve") + if(ticket) + ticket.Resolve(tgs_tagged) + return "Ticket #[ticket.id] successfully resolved" + if("icissue") + if(ticket) + ticket.ICIssue(tgs_tagged) + return "Ticket #[ticket.id] successfully marked as IC issue" + if("reject") + if(ticket) + ticket.Reject(tgs_tagged) + return "Ticket #[ticket.id] successfully rejected" + if("reopen") + if(ticket) + return "Error: [target] already has ticket #[ticket.id] open" + var/fail = splits.len < 3 ? null : -1 + if(!isnull(fail)) + fail = text2num(splits[3]) + if(isnull(fail)) + return "Error: No/Invalid ticket id specified. [TGS_AHELP_USAGE]" + var/datum/admin_help/AH = GLOB.ahelp_tickets.TicketByID(fail) + if(!AH) + return "Error: Ticket #[fail] not found" + if(AH.initiator_ckey != target) + return "Error: Ticket #[fail] belongs to [AH.initiator_ckey]" + AH.Reopen() + return "Ticket #[ticket.id] successfully reopened" + if("list") + var/list/tickets = GLOB.ahelp_tickets.TicketsByCKey(target) + if(!tickets.len) + return "None" + . = "" + for(var/I in tickets) + var/datum/admin_help/AH = I + if(.) + . += ", " + if(AH == ticket) + . += "Active: " + . += "#[AH.id]" + return + else + return TGS_AHELP_USAGE + return "Error: Ticket could not be found" + + var/static/stealthkey + var/adminname = CONFIG_GET(flag/show_irc_name) ? tgs_tagged : "Administrator" + + if(!C) + return "Error: No client" + + if(!stealthkey) + stealthkey = GenTgsStealthKey() + + msg = sanitize(copytext_char(msg, 1, MAX_MESSAGE_LEN)) + if(!msg) + return "Error: No message" + + message_admins("External message from [sender] to [key_name_admin(C)] : [msg]") + SSredbot.send_discord_message("admin", "External message from [sender] to [key_name_admin(C)] : [msg]") + log_admin_private("External PM: [sender] -> [key_name(C)] : [msg]") + msg = emoji_parse(msg) + + to_chat(C, "-- Administrator private message --", confidential = TRUE) + to_chat(C, "Admin PM from-[adminname]: [msg]", confidential = TRUE) + to_chat(C, "Click on the administrator's name to reply.", confidential = TRUE) + + admin_ticket_log(C, "PM From [tgs_tagged]: [msg]") + + window_flash(C, ignorepref = TRUE) + //always play non-admin recipients the adminhelp sound + SEND_SOUND(C, 'sound/effects/adminhelp.ogg') + + C.externalreplyamount = EXTERNALREPLYCOUNT + + return "Message Successful" + +/proc/GenTgsStealthKey() + var/num = (rand(0,1000)) + var/i = 0 + while(i == 0) + i = 1 + for(var/P in GLOB.stealthminID) + if(num == GLOB.stealthminID[P]) + num++ + i = 0 + var/stealth = "@[num2text(num)]" + GLOB.stealthminID["IRCKEY"] = stealth + return stealth + +#undef EXTERNALREPLYCOUNT diff --git a/code/modules/admin/verbs/adminsay.dm b/code/modules/admin/verbs/adminsay.dm index 92e948da54c8..2adfbc59aa02 100644 --- a/code/modules/admin/verbs/adminsay.dm +++ b/code/modules/admin/verbs/adminsay.dm @@ -1,24 +1,24 @@ -/client/proc/cmd_admin_say(msg as text) - set category = "Admin" - set name = "Asay" //Gave this shit a shorter name so you only have to time out "asay" rather than "admin say" to use it --NeoFite - set hidden = 1 - if(!check_rights(0)) - return - - SSredbot.send_discord_message("asay", msg) - - msg = emoji_parse(copytext_char(sanitize(msg), 1, MAX_MESSAGE_LEN)) - if(!msg) - return - - mob.log_talk(msg, LOG_ASAY) - msg = keywords_lookup(msg) - var/custom_asay_color = (CONFIG_GET(flag/allow_admin_asaycolor) && prefs.asaycolor) ? "" : "" - msg = "ADMIN: [key_name(usr, 1)] [ADMIN_FLW(mob)]: [custom_asay_color][msg][custom_asay_color ? "":null]" - to_chat(GLOB.admins, msg, confidential = TRUE) - - SSblackbox.record_feedback("tally", "admin_verb", 1, "Asay") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/get_admin_say() - var/msg = input(src, null, "asay \"text\"") as text|null - cmd_admin_say(msg) +/client/proc/cmd_admin_say(msg as text) + set category = "Admin" + set name = "Asay" //Gave this shit a shorter name so you only have to time out "asay" rather than "admin say" to use it --NeoFite + set hidden = 1 + if(!check_rights(0)) + return + + SSredbot.send_discord_message("asay", msg) + + msg = emoji_parse(copytext_char(sanitize(msg), 1, MAX_MESSAGE_LEN)) + if(!msg) + return + + mob.log_talk(msg, LOG_ASAY) + msg = keywords_lookup(msg) + var/custom_asay_color = (CONFIG_GET(flag/allow_admin_asaycolor) && prefs.asaycolor) ? "" : "" + msg = "ADMIN: [key_name(usr, 1)] [ADMIN_FLW(mob)]: [custom_asay_color][msg][custom_asay_color ? "":null]" + to_chat(GLOB.admins, msg, confidential = TRUE) + + SSblackbox.record_feedback("tally", "admin_verb", 1, "Asay") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/get_admin_say() + var/msg = input(src, null, "asay \"text\"") as text|null + cmd_admin_say(msg) diff --git a/code/modules/admin/verbs/atmosdebug.dm b/code/modules/admin/verbs/atmosdebug.dm index 42a9e07214e8..5bddb0397529 100644 --- a/code/modules/admin/verbs/atmosdebug.dm +++ b/code/modules/admin/verbs/atmosdebug.dm @@ -1,56 +1,56 @@ -/client/proc/atmosscan() - set category = "Mapping" - set name = "Check Plumbing" - if(!src.holder) - to_chat(src, "Only administrators may use this command.", confidential = TRUE) - return - SSblackbox.record_feedback("tally", "admin_verb", 1, "Check Plumbing") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - //all plumbing - yes, some things might get stated twice, doesn't matter. - for(var/obj/machinery/atmospherics/components/pipe in GLOB.machines) - if(pipe.z && (!pipe.nodes || !pipe.nodes.len || (null in pipe.nodes))) - to_chat(usr, "Unconnected [pipe.name] located at [ADMIN_VERBOSEJMP(pipe)]", confidential = TRUE) - - //Manifolds - for(var/obj/machinery/atmospherics/pipe/manifold/pipe in GLOB.machines) - if(pipe.z && (!pipe.nodes || !pipe.nodes.len || (null in pipe.nodes))) - to_chat(usr, "Unconnected [pipe.name] located at [ADMIN_VERBOSEJMP(pipe)]", confidential = TRUE) - - //Pipes - for(var/obj/machinery/atmospherics/pipe/simple/pipe in GLOB.machines) - if(pipe.z && (!pipe.nodes || !pipe.nodes.len || (null in pipe.nodes))) - to_chat(usr, "Unconnected [pipe.name] located at [ADMIN_VERBOSEJMP(pipe)]", confidential = TRUE) - -/client/proc/powerdebug() - set category = "Mapping" - set name = "Check Power" - if(!src.holder) - to_chat(src, "Only administrators may use this command.", confidential = TRUE) - return - SSblackbox.record_feedback("tally", "admin_verb", 1, "Check Power") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - var/list/results = list() - - for (var/datum/powernet/PN in GLOB.powernets) - if (!PN.nodes || !PN.nodes.len) - if(PN.cables && (PN.cables.len > 1)) - var/obj/structure/cable/C = PN.cables[1] - results += "Powernet with no nodes! (number [PN.number]) - example cable at [ADMIN_VERBOSEJMP(C)]" - - if (!PN.cables || (PN.cables.len < 10)) - if(PN.cables && (PN.cables.len > 1)) - var/obj/structure/cable/C = PN.cables[1] - results += "Powernet with fewer than 10 cables! (number [PN.number]) - example cable at [ADMIN_VERBOSEJMP(C)]" - - for(var/turf/T in world.contents) - var/found_one = FALSE - for(var/obj/structure/cable/C in T.contents) - if(found_one) - results += "Doubled wire at [ADMIN_VERBOSEJMP(C)]" - else - found_one = TRUE - var/obj/machinery/power/terminal/term = locate(/obj/machinery/power/terminal) in T.contents - if(term) - var/obj/structure/cable/C = locate(/obj/structure/cable) in T.contents - if(!C) - results += "Unwired terminal at [ADMIN_VERBOSEJMP(term)]" - to_chat(usr, "[results.Join("\n")]", confidential = TRUE) +/client/proc/atmosscan() + set category = "Mapping" + set name = "Check Plumbing" + if(!src.holder) + to_chat(src, "Only administrators may use this command.", confidential = TRUE) + return + SSblackbox.record_feedback("tally", "admin_verb", 1, "Check Plumbing") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + //all plumbing - yes, some things might get stated twice, doesn't matter. + for(var/obj/machinery/atmospherics/components/pipe in GLOB.machines) + if(pipe.z && (!pipe.nodes || !pipe.nodes.len || (null in pipe.nodes))) + to_chat(usr, "Unconnected [pipe.name] located at [ADMIN_VERBOSEJMP(pipe)]", confidential = TRUE) + + //Manifolds + for(var/obj/machinery/atmospherics/pipe/manifold/pipe in GLOB.machines) + if(pipe.z && (!pipe.nodes || !pipe.nodes.len || (null in pipe.nodes))) + to_chat(usr, "Unconnected [pipe.name] located at [ADMIN_VERBOSEJMP(pipe)]", confidential = TRUE) + + //Pipes + for(var/obj/machinery/atmospherics/pipe/simple/pipe in GLOB.machines) + if(pipe.z && (!pipe.nodes || !pipe.nodes.len || (null in pipe.nodes))) + to_chat(usr, "Unconnected [pipe.name] located at [ADMIN_VERBOSEJMP(pipe)]", confidential = TRUE) + +/client/proc/powerdebug() + set category = "Mapping" + set name = "Check Power" + if(!src.holder) + to_chat(src, "Only administrators may use this command.", confidential = TRUE) + return + SSblackbox.record_feedback("tally", "admin_verb", 1, "Check Power") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + var/list/results = list() + + for (var/datum/powernet/PN in GLOB.powernets) + if (!PN.nodes || !PN.nodes.len) + if(PN.cables && (PN.cables.len > 1)) + var/obj/structure/cable/C = PN.cables[1] + results += "Powernet with no nodes! (number [PN.number]) - example cable at [ADMIN_VERBOSEJMP(C)]" + + if (!PN.cables || (PN.cables.len < 10)) + if(PN.cables && (PN.cables.len > 1)) + var/obj/structure/cable/C = PN.cables[1] + results += "Powernet with fewer than 10 cables! (number [PN.number]) - example cable at [ADMIN_VERBOSEJMP(C)]" + + for(var/turf/T in world.contents) + var/found_one = FALSE + for(var/obj/structure/cable/C in T.contents) + if(found_one) + results += "Doubled wire at [ADMIN_VERBOSEJMP(C)]" + else + found_one = TRUE + var/obj/machinery/power/terminal/term = locate(/obj/machinery/power/terminal) in T.contents + if(term) + var/obj/structure/cable/C = locate(/obj/structure/cable) in T.contents + if(!C) + results += "Unwired terminal at [ADMIN_VERBOSEJMP(term)]" + to_chat(usr, "[results.Join("\n")]", confidential = TRUE) diff --git a/code/modules/admin/verbs/borgpanel.dm b/code/modules/admin/verbs/borgpanel.dm index cbee1a066a5d..03f5ad9a79e2 100644 --- a/code/modules/admin/verbs/borgpanel.dm +++ b/code/modules/admin/verbs/borgpanel.dm @@ -25,18 +25,18 @@ if(!istype(to_borg)) qdel(src) CRASH("Borg panel is only available for borgs") - user = CLIENT_FROM_VAR(to_user) - if (!user) CRASH("Borg panel attempted to open to a mob without a client") - borg = to_borg -/datum/borgpanel/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.admin_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/datum/borgpanel/ui_state(mob/user) + return GLOB.admin_state + +/datum/borgpanel/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "BorgPanel", "Borg Panel", 700, 700, master_ui, state) + ui = new(user, src, "BorgPanel") ui.open() /datum/borgpanel/ui_data(mob/user) @@ -59,7 +59,7 @@ if (locate(upgradetype) in borg) installed = TRUE .["upgrades"] += list(list("name" = initial(upgrade.name), "installed" = installed, "type" = upgradetype)) - .["laws"] = borg.laws ? borg.laws.get_law_list(include_zeroth = TRUE) : list() + .["laws"] = borg.laws ? borg.laws.get_law_list(include_zeroth = TRUE, render_html = FALSE) : list() .["channels"] = list() for (var/k in GLOB.radiochannels) if (k == RADIO_CHANNEL_COMMON) diff --git a/code/modules/admin/verbs/cinematic.dm b/code/modules/admin/verbs/cinematic.dm index 1ef351877407..455cfaaedd72 100644 --- a/code/modules/admin/verbs/cinematic.dm +++ b/code/modules/admin/verbs/cinematic.dm @@ -1,11 +1,11 @@ -/client/proc/cinematic() - set name = "cinematic" - set category = "Fun" - set desc = "Shows a cinematic." // Intended for testing but I thought it might be nice for events on the rare occasion Feel free to comment it out if it's not wanted. - set hidden = 1 - if(!SSticker) - return - - var/datum/cinematic/choice = input(src,"Cinematic","Choose",null) as anything in sortList(subtypesof(/datum/cinematic), /proc/cmp_typepaths_asc) - if(choice) - Cinematic(initial(choice.id),world,null) +/client/proc/cinematic() + set name = "cinematic" + set category = "Fun" + set desc = "Shows a cinematic." // Intended for testing but I thought it might be nice for events on the rare occasion Feel free to comment it out if it's not wanted. + set hidden = 1 + if(!SSticker) + return + + var/datum/cinematic/choice = input(src,"Cinematic","Choose",null) as anything in sortList(subtypesof(/datum/cinematic), /proc/cmp_typepaths_asc) + if(choice) + Cinematic(initial(choice.id),world,null) diff --git a/code/modules/admin/verbs/deadsay.dm b/code/modules/admin/verbs/deadsay.dm index a1af36e23a1f..e0b35fb0c809 100644 --- a/code/modules/admin/verbs/deadsay.dm +++ b/code/modules/admin/verbs/deadsay.dm @@ -1,43 +1,43 @@ -/client/proc/dsay(msg as text) - set category = "Admin - Game" - set name = "Dsay" - set hidden = 1 - if(!holder) - to_chat(src, "Only administrators may use this command.", confidential = TRUE) - return - if(!mob) - return - if(prefs.muted & MUTE_DEADCHAT) - to_chat(src, "You cannot send DSAY messages (muted).", confidential = TRUE) - return - - if (handle_spam_prevention(msg,MUTE_DEADCHAT)) - return - - msg = copytext_char(sanitize(msg), 1, MAX_MESSAGE_LEN) - mob.log_talk(msg, LOG_DSAY) - - if (!msg) - return - var/rank_name = holder.rank - var/admin_name = key - if(holder.fakekey) - rank_name = pick(strings("admin_nicknames.json", "ranks", "config")) - admin_name = pick(strings("admin_nicknames.json", "names", "config")) - var/rendered = "DEAD: [rank_name]([admin_name]) says, \"[emoji_parse(msg)]\"" - - for (var/mob/M in GLOB.player_list) - if(isnewplayer(M)) - continue - if (M.stat == DEAD || (M.client.holder && (M.client.prefs.chat_toggles & CHAT_DEAD))) //admins can toggle deadchat on and off. This is a proc in admin.dm and is only give to Administrators and above - to_chat(M, rendered, confidential = TRUE) - - SSblackbox.record_feedback("tally", "admin_verb", 1, "Dsay") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/get_dead_say() - var/msg = input(src, null, "dsay \"text\"") as text|null - - if (isnull(msg)) - return - - dsay(msg) +/client/proc/dsay(msg as text) + set category = "Admin - Game" + set name = "Dsay" + set hidden = 1 + if(!holder) + to_chat(src, "Only administrators may use this command.", confidential = TRUE) + return + if(!mob) + return + if(prefs.muted & MUTE_DEADCHAT) + to_chat(src, "You cannot send DSAY messages (muted).", confidential = TRUE) + return + + if (handle_spam_prevention(msg,MUTE_DEADCHAT)) + return + + msg = copytext_char(sanitize(msg), 1, MAX_MESSAGE_LEN) + mob.log_talk(msg, LOG_DSAY) + + if (!msg) + return + var/rank_name = holder.rank + var/admin_name = key + if(holder.fakekey) + rank_name = pick(strings("admin_nicknames.json", "ranks", "config")) + admin_name = pick(strings("admin_nicknames.json", "names", "config")) + var/rendered = "DEAD: [rank_name]([admin_name]) says, \"[emoji_parse(msg)]\"" + + for (var/mob/M in GLOB.player_list) + if(isnewplayer(M)) + continue + if (M.stat == DEAD || (M.client.holder && (M.client.prefs.chat_toggles & CHAT_DEAD))) //admins can toggle deadchat on and off. This is a proc in admin.dm and is only give to Administrators and above + to_chat(M, rendered, confidential = TRUE) + + SSblackbox.record_feedback("tally", "admin_verb", 1, "Dsay") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/get_dead_say() + var/msg = input(src, null, "dsay \"text\"") as text|null + + if (isnull(msg)) + return + + dsay(msg) diff --git a/code/modules/admin/verbs/fps.dm b/code/modules/admin/verbs/fps.dm index 4eb8dd8bc10c..a3e7c5f5df0b 100644 --- a/code/modules/admin/verbs/fps.dm +++ b/code/modules/admin/verbs/fps.dm @@ -1,26 +1,26 @@ -//replaces the old Ticklag verb, fps is easier to understand -/client/proc/set_server_fps() - set category = "Debug" - set name = "Set Server FPS" - set desc = "Sets game speed in frames-per-second. Can potentially break the game" - - if(!check_rights(R_DEBUG)) - return - - var/cfg_fps = CONFIG_GET(number/fps) - var/new_fps = round(input("Sets game frames-per-second. Can potentially break the game (default: [cfg_fps])","FPS", world.fps) as num|null) - - if(new_fps <= 0) - to_chat(src, "Error: set_server_fps(): Invalid world.fps value. No changes made.", confidential = TRUE) - return - if(new_fps > cfg_fps * 1.5) - if(alert(src, "You are setting fps to a high value:\n\t[new_fps] frames-per-second\n\tconfig.fps = [cfg_fps]","Warning!","Confirm","ABORT-ABORT-ABORT") != "Confirm") - return - - var/msg = "[key_name(src)] has modified world.fps to [new_fps]" - log_admin(msg, 0) - message_admins(msg, 0) - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Set Server FPS", "[new_fps]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - CONFIG_SET(number/fps, new_fps) - world.change_fps(new_fps) +//replaces the old Ticklag verb, fps is easier to understand +/client/proc/set_server_fps() + set category = "Debug" + set name = "Set Server FPS" + set desc = "Sets game speed in frames-per-second. Can potentially break the game" + + if(!check_rights(R_DEBUG)) + return + + var/cfg_fps = CONFIG_GET(number/fps) + var/new_fps = round(input("Sets game frames-per-second. Can potentially break the game (default: [cfg_fps])","FPS", world.fps) as num|null) + + if(new_fps <= 0) + to_chat(src, "Error: set_server_fps(): Invalid world.fps value. No changes made.", confidential = TRUE) + return + if(new_fps > cfg_fps * 1.5) + if(alert(src, "You are setting fps to a high value:\n\t[new_fps] frames-per-second\n\tconfig.fps = [cfg_fps]","Warning!","Confirm","ABORT-ABORT-ABORT") != "Confirm") + return + + var/msg = "[key_name(src)] has modified world.fps to [new_fps]" + log_admin(msg, 0) + message_admins(msg, 0) + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Set Server FPS", "[new_fps]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + CONFIG_SET(number/fps, new_fps) + world.change_fps(new_fps) diff --git a/code/modules/admin/verbs/getlogs.dm b/code/modules/admin/verbs/getlogs.dm index a07e1fb1e9e4..1f706091c6b4 100644 --- a/code/modules/admin/verbs/getlogs.dm +++ b/code/modules/admin/verbs/getlogs.dm @@ -1,35 +1,35 @@ -//This proc allows download of past server logs saved within the data/logs/ folder. -/client/proc/getserverlogs() - set name = "Get Server Logs" - set desc = "View/retrieve logfiles." - set category = "Admin" - - browseserverlogs() - -/client/proc/getcurrentlogs() - set name = "Get Current Logs" - set desc = "View/retrieve logfiles for the current round." - set category = "Admin" - - browseserverlogs(current=TRUE) - -/client/proc/browseserverlogs(current=FALSE) - var/path = browse_files(current ? BROWSE_ROOT_CURRENT_LOGS : BROWSE_ROOT_ALL_LOGS) - if(!path) - return - - if(file_spam_check()) - return - - message_admins("[key_name_admin(src)] accessed file: [path]") - switch(alert("View (in game), Open (in your system's text editor), or Download?", path, "View", "Open", "Download")) - if ("View") - src << browse("
                    [html_encode(file2text(file(path)))]
                    ", list2params(list("window" = "viewfile.[path]"))) - if ("Open") - src << run(file(path)) - if ("Download") - src << ftp(file(path)) - else - return - to_chat(src, "Attempting to send [path], this may take a fair few minutes if the file is very large.", confidential = TRUE) - return +//This proc allows download of past server logs saved within the data/logs/ folder. +/client/proc/getserverlogs() + set name = "Get Server Logs" + set desc = "View/retrieve logfiles." + set category = "Admin" + + browseserverlogs() + +/client/proc/getcurrentlogs() + set name = "Get Current Logs" + set desc = "View/retrieve logfiles for the current round." + set category = "Admin" + + browseserverlogs(current=TRUE) + +/client/proc/browseserverlogs(current=FALSE) + var/path = browse_files(current ? BROWSE_ROOT_CURRENT_LOGS : BROWSE_ROOT_ALL_LOGS) + if(!path) + return + + if(file_spam_check()) + return + + message_admins("[key_name_admin(src)] accessed file: [path]") + switch(alert("View (in game), Open (in your system's text editor), or Download?", path, "View", "Open", "Download")) + if ("View") + src << browse("
                    [html_encode(file2text(file(path)))]
                    ", list2params(list("window" = "viewfile.[path]"))) + if ("Open") + src << run(file(path)) + if ("Download") + src << ftp(file(path)) + else + return + to_chat(src, "Attempting to send [path], this may take a fair few minutes if the file is very large.", confidential = TRUE) + return diff --git a/code/modules/admin/verbs/mapping.dm b/code/modules/admin/verbs/mapping.dm index b86479583ef4..dee92af9abcd 100644 --- a/code/modules/admin/verbs/mapping.dm +++ b/code/modules/admin/verbs/mapping.dm @@ -1,378 +1,378 @@ -//- Are all the floors with or without air, as they should be? (regular or airless) -//- Does the area have an APC? -//- Does the area have an Air Alarm? -//- Does the area have a Request Console? -//- Does the area have lights? -//- Does the area have a light switch? -//- Does the area have enough intercoms? -//- Does the area have enough security cameras? (Use the 'Camera Range Display' verb under Debug) -//- Is the area connected to the scrubbers air loop? -//- Is the area connected to the vent air loop? (vent pumps) -//- Is everything wired properly? -//- Does the area have a fire alarm and firedoors? -//- Do all pod doors work properly? -//- Are accesses set properly on doors, pod buttons, etc. -//- Are all items placed properly? (not below vents, scrubbers, tables) -//- Does the disposal system work properly from all the disposal units in this room and all the units, the pipes of which pass through this room? -//- Check for any misplaced or stacked piece of pipe (air and disposal) -//- Check for any misplaced or stacked piece of wire -//- Identify how hard it is to break into the area and where the weak points are -//- Check if the area has too much empty space. If so, make it smaller and replace the rest with maintenance tunnels. - -GLOBAL_LIST_INIT(admin_verbs_debug_mapping, list( - /client/proc/camera_view, //-errorage - /client/proc/sec_camera_report, //-errorage - /client/proc/intercom_view, //-errorage - /client/proc/air_status, //Air things - /client/proc/Cell, //More air things - /client/proc/atmosscan, //check plumbing - /client/proc/powerdebug, //check power - /client/proc/count_objects_on_z_level, - /client/proc/count_objects_all, - /client/proc/cmd_assume_direct_control, //-errorage - /client/proc/cmd_give_direct_control, - /client/proc/startSinglo, - /client/proc/set_server_fps, //allows you to set the ticklag. - /client/proc/cmd_admin_grantfullaccess, - /client/proc/cmd_admin_areatest_all, - /client/proc/cmd_admin_areatest_station, - #ifdef TESTING - /client/proc/see_dirty_varedits, - #endif - /client/proc/cmd_admin_test_atmos_controllers, - /client/proc/cmd_admin_rejuvenate, - /datum/admins/proc/show_traitor_panel, - /client/proc/disable_communication, - /client/proc/cmd_show_at_list, - /client/proc/cmd_show_at_markers, - /client/proc/manipulate_organs, - /client/proc/start_line_profiling, - /client/proc/stop_line_profiling, - /client/proc/show_line_profiling, - /client/proc/create_mapping_job_icons, - /client/proc/debug_z_levels, - /client/proc/place_ruin, - /client/proc/export_map -)) -GLOBAL_PROTECT(admin_verbs_debug_mapping) - -/obj/effect/debugging/mapfix_marker - name = "map fix marker" - icon = 'icons/mob/screen_gen.dmi' - icon_state = "mapfixmarker" - desc = "I am a mappers mistake." - -/obj/effect/debugging/marker - icon = 'icons/turf/areas.dmi' - icon_state = "yellow" - -/obj/effect/debugging/marker/Move() - return 0 - -/client/proc/camera_view() - set category = "Mapping" - set name = "Camera Range Display" - - var/on = FALSE - for(var/turf/T in world) - if(T.maptext) - on = TRUE - T.maptext = null - - if(!on) - var/list/seen = list() - for(var/obj/machinery/camera/C in GLOB.cameranet.cameras) - for(var/turf/T in C.can_see()) - seen[T]++ - for(var/turf/T in seen) - T.maptext = "[seen[T]]" - SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Camera Range") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Camera Range") - -#ifdef TESTING -GLOBAL_LIST_EMPTY(dirty_vars) - -/client/proc/see_dirty_varedits() - set category = "Mapping" - set name = "Dirty Varedits" - - var/list/dat = list() - dat += "

                    Abandon all hope ye who enter here



                    " - for(var/thing in GLOB.dirty_vars) - dat += "[thing]
                    " - CHECK_TICK - var/datum/browser/popup = new(usr, "dirty_vars", "Dirty Varedits", 900, 750) - popup.set_content(dat.Join()) - popup.open() -#endif - -/client/proc/sec_camera_report() - set category = "Mapping" - set name = "Camera Report" - - if(!Master) - alert(usr,"Master_controller not found.","Sec Camera Report") - return 0 - - var/list/obj/machinery/camera/CL = list() - - for(var/obj/machinery/camera/C in GLOB.cameranet.cameras) - CL += C - - var/output = {"Camera Abnormalities Report
                    -The following abnormalities have been detected. The ones in red need immediate attention: Some of those in black may be intentional.
                      "} - - for(var/obj/machinery/camera/C1 in CL) - for(var/obj/machinery/camera/C2 in CL) - if(C1 != C2) - if(C1.c_tag == C2.c_tag) - output += "
                    • c_tag match for cameras at [ADMIN_VERBOSEJMP(C1)] and [ADMIN_VERBOSEJMP(C2)] - c_tag is [C1.c_tag]
                    • " - if(C1.loc == C2.loc && C1.dir == C2.dir && C1.pixel_x == C2.pixel_x && C1.pixel_y == C2.pixel_y) - output += "
                    • FULLY overlapping cameras at [ADMIN_VERBOSEJMP(C1)] Networks: [json_encode(C1.network)] and [json_encode(C2.network)]
                    • " - if(C1.loc == C2.loc) - output += "
                    • Overlapping cameras at [ADMIN_VERBOSEJMP(C1)] Networks: [json_encode(C1.network)] and [json_encode(C2.network)]
                    • " - var/turf/T = get_step(C1,turn(C1.dir,180)) - if(!T || !isturf(T) || !T.density ) - if(!(locate(/obj/structure/grille) in T)) - var/window_check = 0 - for(var/obj/structure/window/W in T) - if (W.dir == turn(C1.dir,180) || (W.dir in list(NORTHEAST,SOUTHEAST,NORTHWEST,SOUTHWEST)) ) - window_check = 1 - break - if(!window_check) - output += "
                    • Camera not connected to wall at [ADMIN_VERBOSEJMP(C1)] Network: [json_encode(C1.network)]
                    • " - - output += "
                    " - usr << browse(output,"window=airreport;size=1000x500") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Camera Report") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/intercom_view() - set category = "Mapping" - set name = "Intercom Range Display" - - var/static/intercom_range_display_status = FALSE - intercom_range_display_status = !intercom_range_display_status //blame cyberboss if this breaks something - - for(var/obj/effect/debugging/marker/M in world) - qdel(M) - - if(intercom_range_display_status) - for(var/obj/item/radio/intercom/I in world) - for(var/turf/T in orange(7,I)) - var/obj/effect/debugging/marker/F = new/obj/effect/debugging/marker(T) - if (!(F in view(7,I.loc))) - qdel(F) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Intercom Range") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_show_at_list() - set category = "Mapping" - set name = "Show roundstart AT list" - set desc = "Displays a list of active turfs coordinates at roundstart" - - var/dat = {"Coordinate list of Active Turfs at Roundstart -
                    Real-time Active Turfs list you can see in Air Subsystem at active_turfs var
                    "} - - for(var/t in GLOB.active_turfs_startlist) - var/turf/T = t - dat += "[ADMIN_VERBOSEJMP(T)]\n" - dat += "
                    " - - usr << browse(dat, "window=at_list") - - SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Roundstart Active Turfs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_show_at_markers() - set category = "Mapping" - set name = "Show roundstart AT markers" - set desc = "Places a marker on all active-at-roundstart turfs" - - var/count = 0 - for(var/obj/effect/abstract/marker/at/AT in GLOB.all_abstract_markers) - qdel(AT) - count++ - - if(count) - to_chat(usr, "[count] AT markers removed.", confidential = TRUE) - else - for(var/t in GLOB.active_turfs_startlist) - new /obj/effect/abstract/marker/at(t) - count++ - to_chat(usr, "[count] AT markers placed.", confidential = TRUE) - - SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Roundstart Active Turf Markers") - -/client/proc/enable_debug_verbs() - set category = "Debug" - set name = "Debug verbs - Enable" - if(!check_rights(R_DEBUG)) - return - verbs -= /client/proc/enable_debug_verbs - verbs.Add(/client/proc/disable_debug_verbs, GLOB.admin_verbs_debug_mapping) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Enable Debug Verbs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/disable_debug_verbs() - set category = "Debug" - set name = "Debug verbs - Disable" - verbs.Remove(/client/proc/disable_debug_verbs, GLOB.admin_verbs_debug_mapping) - verbs += /client/proc/enable_debug_verbs - SSblackbox.record_feedback("tally", "admin_verb", 1, "Disable Debug Verbs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/count_objects_on_z_level() - set category = "Mapping" - set name = "Count Objects On Level" - var/level = input("Which z-level?","Level?") as text|null - if(!level) - return - var/num_level = text2num(level) - if(!num_level) - return - if(!isnum(num_level)) - return - - var/type_text = input("Which type path?","Path?") as text|null - if(!type_text) - return - var/type_path = text2path(type_text) - if(!type_path) - return - - var/count = 0 - - var/list/atom/atom_list = list() - - for(var/atom/A in world) - if(istype(A,type_path)) - var/atom/B = A - while(!(isturf(B.loc))) - if(B && B.loc) - B = B.loc - else - break - if(B) - if(B.z == num_level) - count++ - atom_list += A - - to_chat(world, "There are [count] objects of type [type_path] on z-level [num_level]", confidential = TRUE) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Count Objects Zlevel") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/count_objects_all() - set category = "Mapping" - set name = "Count Objects All" - - var/type_text = input("Which type path?","") as text|null - if(!type_text) - return - var/type_path = text2path(type_text) - if(!type_path) - return - - var/count = 0 - - for(var/atom/A in world) - if(istype(A,type_path)) - count++ - - to_chat(world, "There are [count] objects of type [type_path] in the game world", confidential = TRUE) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Count Objects All") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - -//This proc is intended to detect lag problems relating to communication procs -GLOBAL_VAR_INIT(say_disabled, FALSE) -/client/proc/disable_communication() - set category = "Mapping" - set name = "Disable all communication verbs" - - GLOB.say_disabled = !GLOB.say_disabled - if(GLOB.say_disabled) - message_admins("[key] used 'Disable all communication verbs', killing all communication methods.") - else - message_admins("[key] used 'Disable all communication verbs', restoring all communication methods.") - -//This generates the icon states for job starting location landmarks. -/client/proc/create_mapping_job_icons() - set name = "Generate job landmarks icons" - set category = "Mapping" - var/icon/final = icon() - var/mob/living/carbon/human/dummy/D = new(locate(1,1,1)) //spawn on 1,1,1 so we don't have runtimes when items are deleted - D.setDir(SOUTH) - for(var/job in subtypesof(/datum/job)) - var/datum/job/JB = new job - switch(JB.title) - if("AI") - final.Insert(icon('icons/mob/ai.dmi', "ai", SOUTH, 1), "AI") - if("Cyborg") - final.Insert(icon('icons/mob/robots.dmi', "robot", SOUTH, 1), "Cyborg") - else - for(var/obj/item/I in D) - qdel(I) - randomize_human(D) - JB.equip(D, TRUE, FALSE) - COMPILE_OVERLAYS(D) - var/icon/I = icon(getFlatIcon(D), frame = 1) - final.Insert(I, JB.title) - qdel(D) - //Also add the x - for(var/x_number in 1 to 4) - final.Insert(icon('icons/mob/screen_gen.dmi', "x[x_number == 1 ? "" : x_number]"), "x[x_number == 1 ? "" : x_number]") - fcopy(final, "icons/mob/landmarks.dmi") - -/client/proc/debug_z_levels() - set name = "Debug Z-Levels" - set category = "Mapping" - - var/list/z_list = SSmapping.z_list - var/list/messages = list() - messages += "World: [world.maxx] x [world.maxy] x [world.maxz]
                    " - - var/list/linked_levels = list() - var/min_x = INFINITY - var/min_y = INFINITY - var/max_x = -INFINITY - var/max_y = -INFINITY - - for(var/z in 1 to max(world.maxz, z_list.len)) - if (z > z_list.len) - messages += "[z]: Unmanaged (out of bounds)
                    " - continue - var/datum/space_level/S = z_list[z] - if (!S) - messages += "[z]: Unmanaged (null)
                    " - continue - var/linkage - switch (S.linkage) - if (UNAFFECTED) - linkage = "no linkage" - if (SELFLOOPING) - linkage = "self-looping" - if (CROSSLINKED) - linkage = "linked at ([S.xi], [S.yi])" - linked_levels += S - min_x = min(min_x, S.xi) - min_y = min(min_y, S.yi) - max_x = max(max_x, S.xi) - max_y = max(max_y, S.yi) - else - linkage = "unknown linkage '[S.linkage]'" - - messages += "[z]: [S.name], [linkage], traits: [json_encode(S.traits)]
                    " - if (S.z_value != z) - messages += "-- z_value is [S.z_value], should be [z]
                    " - if (S.name == initial(S.name)) - messages += "-- name not set
                    " - if (z > world.maxz) - messages += "-- exceeds max z" - - var/grid[max_x - min_x + 1][max_y - min_y + 1] - for(var/datum/space_level/S in linked_levels) - grid[S.xi - min_x + 1][S.yi - min_y + 1] = S.z_value - - messages += "" - for(var/y in max_y to min_y step -1) - var/list/part = list() - for(var/x in min_x to max_x) - part += "[grid[x - min_x + 1][y - min_y + 1]]" - messages += "" - messages += "
                    [part.Join("")]
                    " - - to_chat(src, messages.Join(""), confidential = TRUE) +//- Are all the floors with or without air, as they should be? (regular or airless) +//- Does the area have an APC? +//- Does the area have an Air Alarm? +//- Does the area have a Request Console? +//- Does the area have lights? +//- Does the area have a light switch? +//- Does the area have enough intercoms? +//- Does the area have enough security cameras? (Use the 'Camera Range Display' verb under Debug) +//- Is the area connected to the scrubbers air loop? +//- Is the area connected to the vent air loop? (vent pumps) +//- Is everything wired properly? +//- Does the area have a fire alarm and firedoors? +//- Do all pod doors work properly? +//- Are accesses set properly on doors, pod buttons, etc. +//- Are all items placed properly? (not below vents, scrubbers, tables) +//- Does the disposal system work properly from all the disposal units in this room and all the units, the pipes of which pass through this room? +//- Check for any misplaced or stacked piece of pipe (air and disposal) +//- Check for any misplaced or stacked piece of wire +//- Identify how hard it is to break into the area and where the weak points are +//- Check if the area has too much empty space. If so, make it smaller and replace the rest with maintenance tunnels. + +GLOBAL_LIST_INIT(admin_verbs_debug_mapping, list( + /client/proc/camera_view, //-errorage + /client/proc/sec_camera_report, //-errorage + /client/proc/intercom_view, //-errorage + /client/proc/air_status, //Air things + /client/proc/Cell, //More air things + /client/proc/atmosscan, //check plumbing + /client/proc/powerdebug, //check power + /client/proc/count_objects_on_z_level, + /client/proc/count_objects_all, + /client/proc/cmd_assume_direct_control, //-errorage + /client/proc/cmd_give_direct_control, + /client/proc/startSinglo, + /client/proc/set_server_fps, //allows you to set the ticklag. + /client/proc/cmd_admin_grantfullaccess, + /client/proc/cmd_admin_areatest_all, + /client/proc/cmd_admin_areatest_station, + #ifdef TESTING + /client/proc/see_dirty_varedits, + #endif + /client/proc/cmd_admin_test_atmos_controllers, + /client/proc/cmd_admin_rejuvenate, + /datum/admins/proc/show_traitor_panel, + /client/proc/disable_communication, + /client/proc/cmd_show_at_list, + /client/proc/cmd_show_at_markers, + /client/proc/manipulate_organs, + /client/proc/start_line_profiling, + /client/proc/stop_line_profiling, + /client/proc/show_line_profiling, + /client/proc/create_mapping_job_icons, + /client/proc/debug_z_levels, + /client/proc/place_ruin, + /client/proc/export_map +)) +GLOBAL_PROTECT(admin_verbs_debug_mapping) + +/obj/effect/debugging/mapfix_marker + name = "map fix marker" + icon = 'icons/mob/screen_gen.dmi' + icon_state = "mapfixmarker" + desc = "I am a mappers mistake." + +/obj/effect/debugging/marker + icon = 'icons/turf/areas.dmi' + icon_state = "yellow" + +/obj/effect/debugging/marker/Move() + return 0 + +/client/proc/camera_view() + set category = "Mapping" + set name = "Camera Range Display" + + var/on = FALSE + for(var/turf/T in world) + if(T.maptext) + on = TRUE + T.maptext = null + + if(!on) + var/list/seen = list() + for(var/obj/machinery/camera/C in GLOB.cameranet.cameras) + for(var/turf/T in C.can_see()) + seen[T]++ + for(var/turf/T in seen) + T.maptext = "[seen[T]]" + SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Camera Range") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Camera Range") + +#ifdef TESTING +GLOBAL_LIST_EMPTY(dirty_vars) + +/client/proc/see_dirty_varedits() + set category = "Mapping" + set name = "Dirty Varedits" + + var/list/dat = list() + dat += "

                    Abandon all hope ye who enter here



                    " + for(var/thing in GLOB.dirty_vars) + dat += "[thing]
                    " + CHECK_TICK + var/datum/browser/popup = new(usr, "dirty_vars", "Dirty Varedits", 900, 750) + popup.set_content(dat.Join()) + popup.open() +#endif + +/client/proc/sec_camera_report() + set category = "Mapping" + set name = "Camera Report" + + if(!Master) + alert(usr,"Master_controller not found.","Sec Camera Report") + return 0 + + var/list/obj/machinery/camera/CL = list() + + for(var/obj/machinery/camera/C in GLOB.cameranet.cameras) + CL += C + + var/output = {"Camera Abnormalities Report
                    +The following abnormalities have been detected. The ones in red need immediate attention: Some of those in black may be intentional.
                      "} + + for(var/obj/machinery/camera/C1 in CL) + for(var/obj/machinery/camera/C2 in CL) + if(C1 != C2) + if(C1.c_tag == C2.c_tag) + output += "
                    • c_tag match for cameras at [ADMIN_VERBOSEJMP(C1)] and [ADMIN_VERBOSEJMP(C2)] - c_tag is [C1.c_tag]
                    • " + if(C1.loc == C2.loc && C1.dir == C2.dir && C1.pixel_x == C2.pixel_x && C1.pixel_y == C2.pixel_y) + output += "
                    • FULLY overlapping cameras at [ADMIN_VERBOSEJMP(C1)] Networks: [json_encode(C1.network)] and [json_encode(C2.network)]
                    • " + if(C1.loc == C2.loc) + output += "
                    • Overlapping cameras at [ADMIN_VERBOSEJMP(C1)] Networks: [json_encode(C1.network)] and [json_encode(C2.network)]
                    • " + var/turf/T = get_step(C1,turn(C1.dir,180)) + if(!T || !isturf(T) || !T.density ) + if(!(locate(/obj/structure/grille) in T)) + var/window_check = 0 + for(var/obj/structure/window/W in T) + if (W.dir == turn(C1.dir,180) || (W.dir in list(NORTHEAST,SOUTHEAST,NORTHWEST,SOUTHWEST)) ) + window_check = 1 + break + if(!window_check) + output += "
                    • Camera not connected to wall at [ADMIN_VERBOSEJMP(C1)] Network: [json_encode(C1.network)]
                    • " + + output += "
                    " + usr << browse(output,"window=airreport;size=1000x500") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Camera Report") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/intercom_view() + set category = "Mapping" + set name = "Intercom Range Display" + + var/static/intercom_range_display_status = FALSE + intercom_range_display_status = !intercom_range_display_status //blame cyberboss if this breaks something + + for(var/obj/effect/debugging/marker/M in world) + qdel(M) + + if(intercom_range_display_status) + for(var/obj/item/radio/intercom/I in world) + for(var/turf/T in orange(7,I)) + var/obj/effect/debugging/marker/F = new/obj/effect/debugging/marker(T) + if (!(F in view(7,I.loc))) + qdel(F) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Intercom Range") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_show_at_list() + set category = "Mapping" + set name = "Show roundstart AT list" + set desc = "Displays a list of active turfs coordinates at roundstart" + + var/dat = {"Coordinate list of Active Turfs at Roundstart +
                    Real-time Active Turfs list you can see in Air Subsystem at active_turfs var
                    "} + + for(var/t in GLOB.active_turfs_startlist) + var/turf/T = t + dat += "[ADMIN_VERBOSEJMP(T)]\n" + dat += "
                    " + + usr << browse(dat, "window=at_list") + + SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Roundstart Active Turfs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_show_at_markers() + set category = "Mapping" + set name = "Show roundstart AT markers" + set desc = "Places a marker on all active-at-roundstart turfs" + + var/count = 0 + for(var/obj/effect/abstract/marker/at/AT in GLOB.all_abstract_markers) + qdel(AT) + count++ + + if(count) + to_chat(usr, "[count] AT markers removed.", confidential = TRUE) + else + for(var/t in GLOB.active_turfs_startlist) + new /obj/effect/abstract/marker/at(t) + count++ + to_chat(usr, "[count] AT markers placed.", confidential = TRUE) + + SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Roundstart Active Turf Markers") + +/client/proc/enable_debug_verbs() + set category = "Debug" + set name = "Debug verbs - Enable" + if(!check_rights(R_DEBUG)) + return + verbs -= /client/proc/enable_debug_verbs + verbs.Add(/client/proc/disable_debug_verbs, GLOB.admin_verbs_debug_mapping) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Enable Debug Verbs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/disable_debug_verbs() + set category = "Debug" + set name = "Debug verbs - Disable" + verbs.Remove(/client/proc/disable_debug_verbs, GLOB.admin_verbs_debug_mapping) + verbs += /client/proc/enable_debug_verbs + SSblackbox.record_feedback("tally", "admin_verb", 1, "Disable Debug Verbs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/count_objects_on_z_level() + set category = "Mapping" + set name = "Count Objects On Level" + var/level = input("Which z-level?","Level?") as text|null + if(!level) + return + var/num_level = text2num(level) + if(!num_level) + return + if(!isnum(num_level)) + return + + var/type_text = input("Which type path?","Path?") as text|null + if(!type_text) + return + var/type_path = text2path(type_text) + if(!type_path) + return + + var/count = 0 + + var/list/atom/atom_list = list() + + for(var/atom/A in world) + if(istype(A,type_path)) + var/atom/B = A + while(!(isturf(B.loc))) + if(B && B.loc) + B = B.loc + else + break + if(B) + if(B.z == num_level) + count++ + atom_list += A + + to_chat(world, "There are [count] objects of type [type_path] on z-level [num_level]", confidential = TRUE) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Count Objects Zlevel") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/count_objects_all() + set category = "Mapping" + set name = "Count Objects All" + + var/type_text = input("Which type path?","") as text|null + if(!type_text) + return + var/type_path = text2path(type_text) + if(!type_path) + return + + var/count = 0 + + for(var/atom/A in world) + if(istype(A,type_path)) + count++ + + to_chat(world, "There are [count] objects of type [type_path] in the game world", confidential = TRUE) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Count Objects All") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + +//This proc is intended to detect lag problems relating to communication procs +GLOBAL_VAR_INIT(say_disabled, FALSE) +/client/proc/disable_communication() + set category = "Mapping" + set name = "Disable all communication verbs" + + GLOB.say_disabled = !GLOB.say_disabled + if(GLOB.say_disabled) + message_admins("[key] used 'Disable all communication verbs', killing all communication methods.") + else + message_admins("[key] used 'Disable all communication verbs', restoring all communication methods.") + +//This generates the icon states for job starting location landmarks. +/client/proc/create_mapping_job_icons() + set name = "Generate job landmarks icons" + set category = "Mapping" + var/icon/final = icon() + var/mob/living/carbon/human/dummy/D = new(locate(1,1,1)) //spawn on 1,1,1 so we don't have runtimes when items are deleted + D.setDir(SOUTH) + for(var/job in subtypesof(/datum/job)) + var/datum/job/JB = new job + switch(JB.title) + if("AI") + final.Insert(icon('icons/mob/ai.dmi', "ai", SOUTH, 1), "AI") + if("Cyborg") + final.Insert(icon('icons/mob/robots.dmi', "robot", SOUTH, 1), "Cyborg") + else + for(var/obj/item/I in D) + qdel(I) + randomize_human(D) + JB.equip(D, TRUE, FALSE) + COMPILE_OVERLAYS(D) + var/icon/I = icon(getFlatIcon(D), frame = 1) + final.Insert(I, JB.title) + qdel(D) + //Also add the x + for(var/x_number in 1 to 4) + final.Insert(icon('icons/mob/screen_gen.dmi', "x[x_number == 1 ? "" : x_number]"), "x[x_number == 1 ? "" : x_number]") + fcopy(final, "icons/mob/landmarks.dmi") + +/client/proc/debug_z_levels() + set name = "Debug Z-Levels" + set category = "Mapping" + + var/list/z_list = SSmapping.z_list + var/list/messages = list() + messages += "World: [world.maxx] x [world.maxy] x [world.maxz]
                    " + + var/list/linked_levels = list() + var/min_x = INFINITY + var/min_y = INFINITY + var/max_x = -INFINITY + var/max_y = -INFINITY + + for(var/z in 1 to max(world.maxz, z_list.len)) + if (z > z_list.len) + messages += "[z]: Unmanaged (out of bounds)
                    " + continue + var/datum/space_level/S = z_list[z] + if (!S) + messages += "[z]: Unmanaged (null)
                    " + continue + var/linkage + switch (S.linkage) + if (UNAFFECTED) + linkage = "no linkage" + if (SELFLOOPING) + linkage = "self-looping" + if (CROSSLINKED) + linkage = "linked at ([S.xi], [S.yi])" + linked_levels += S + min_x = min(min_x, S.xi) + min_y = min(min_y, S.yi) + max_x = max(max_x, S.xi) + max_y = max(max_y, S.yi) + else + linkage = "unknown linkage '[S.linkage]'" + + messages += "[z]: [S.name], [linkage], traits: [json_encode(S.traits)]
                    " + if (S.z_value != z) + messages += "-- z_value is [S.z_value], should be [z]
                    " + if (S.name == initial(S.name)) + messages += "-- name not set
                    " + if (z > world.maxz) + messages += "-- exceeds max z" + + var/grid[max_x - min_x + 1][max_y - min_y + 1] + for(var/datum/space_level/S in linked_levels) + grid[S.xi - min_x + 1][S.yi - min_y + 1] = S.z_value + + messages += "" + for(var/y in max_y to min_y step -1) + var/list/part = list() + for(var/x in min_x to max_x) + part += "[grid[x - min_x + 1][y - min_y + 1]]" + messages += "" + messages += "
                    [part.Join("")]
                    " + + to_chat(src, messages.Join(""), confidential = TRUE) diff --git a/code/modules/admin/verbs/onlyone.dm b/code/modules/admin/verbs/onlyone.dm index 33da462ce8e9..3bbaca9529f3 100644 --- a/code/modules/admin/verbs/onlyone.dm +++ b/code/modules/admin/verbs/onlyone.dm @@ -1,31 +1,31 @@ -GLOBAL_VAR_INIT(highlander, FALSE) -/client/proc/only_one() //Gives everyone kilts, berets, claymores, and pinpointers, with the objective to hijack the emergency shuttle. - if(!SSticker.HasRoundStarted()) - alert("The game hasn't started yet!") - return - GLOB.highlander = TRUE - - send_to_playing_players("THERE CAN BE ONLY ONE") - - for(var/obj/item/disk/nuclear/N in GLOB.poi_list) - var/datum/component/stationloving/component = N.GetComponent(/datum/component/stationloving) - if (component) - component.relocate() //Gets it out of bags and such - - for(var/mob/living/carbon/human/H in GLOB.player_list) - if(H.stat == DEAD) - continue - H.make_scottish() - - message_admins("[key_name_admin(usr)] used THERE CAN BE ONLY ONE!") - log_admin("[key_name(usr)] used THERE CAN BE ONLY ONE.") - addtimer(CALLBACK(SSshuttle.emergency, /obj/docking_port/mobile/emergency.proc/request, null, 1), 50) - -/client/proc/only_one_delayed() - send_to_playing_players("Bagpipes begin to blare. You feel Scottish pride coming over you.") - message_admins("[key_name_admin(usr)] used (delayed) THERE CAN BE ONLY ONE!") - log_admin("[key_name(usr)] used delayed THERE CAN BE ONLY ONE.") - addtimer(CALLBACK(src, .proc/only_one), 420) - -/mob/living/carbon/human/proc/make_scottish() - mind.add_antag_datum(/datum/antagonist/highlander) +GLOBAL_VAR_INIT(highlander, FALSE) +/client/proc/only_one() //Gives everyone kilts, berets, claymores, and pinpointers, with the objective to hijack the emergency shuttle. + if(!SSticker.HasRoundStarted()) + alert("The game hasn't started yet!") + return + GLOB.highlander = TRUE + + send_to_playing_players("THERE CAN BE ONLY ONE") + + for(var/obj/item/disk/nuclear/N in GLOB.poi_list) + var/datum/component/stationloving/component = N.GetComponent(/datum/component/stationloving) + if (component) + component.relocate() //Gets it out of bags and such + + for(var/mob/living/carbon/human/H in GLOB.player_list) + if(H.stat == DEAD) + continue + H.make_scottish() + + message_admins("[key_name_admin(usr)] used THERE CAN BE ONLY ONE!") + log_admin("[key_name(usr)] used THERE CAN BE ONLY ONE.") + addtimer(CALLBACK(SSshuttle.emergency, /obj/docking_port/mobile/emergency.proc/request, null, 1), 50) + +/client/proc/only_one_delayed() + send_to_playing_players("Bagpipes begin to blare. You feel Scottish pride coming over you.") + message_admins("[key_name_admin(usr)] used (delayed) THERE CAN BE ONLY ONE!") + log_admin("[key_name(usr)] used delayed THERE CAN BE ONLY ONE.") + addtimer(CALLBACK(src, .proc/only_one), 420) + +/mob/living/carbon/human/proc/make_scottish() + mind.add_antag_datum(/datum/antagonist/highlander) diff --git a/code/modules/admin/verbs/playsound.dm b/code/modules/admin/verbs/playsound.dm index b731f85e21d7..91fb01ccdf87 100644 --- a/code/modules/admin/verbs/playsound.dm +++ b/code/modules/admin/verbs/playsound.dm @@ -1,177 +1,177 @@ -/client/proc/play_sound(S as sound) - set category = "Fun" - set name = "Play Global Sound" - if(!check_rights(R_SOUND)) - return - - var/freq = 1 - var/vol = input(usr, "What volume would you like the sound to play at?",, 100) as null|num - if(!vol) - return - vol = clamp(vol, 1, 100) - - var/sound/admin_sound = new() - admin_sound.file = S - admin_sound.priority = 250 - admin_sound.channel = CHANNEL_ADMIN - admin_sound.frequency = freq - admin_sound.wait = 1 - admin_sound.repeat = 0 - admin_sound.status = SOUND_STREAM - admin_sound.volume = vol - - var/res = alert(usr, "Show the title of this song to the players?",, "Yes","No", "Cancel") - switch(res) - if("Yes") - to_chat(world, "An admin played: [S]", confidential = TRUE) - if("Cancel") - return - - log_admin("[key_name(src)] played sound [S]") - message_admins("[key_name_admin(src)] played sound [S]") - - for(var/mob/M in GLOB.player_list) - if(M.client.prefs.toggles & SOUND_MIDI) - var/user_vol = M.client.chatOutput.adminMusicVolume - if(user_vol) - admin_sound.volume = vol * (user_vol / 100) - SEND_SOUND(M, admin_sound) - admin_sound.volume = vol - - SSblackbox.record_feedback("tally", "admin_verb", 1, "Play Global Sound") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - -/client/proc/play_local_sound(S as sound) - set category = "Fun" - set name = "Play Local Sound" - if(!check_rights(R_SOUND)) - return - - log_admin("[key_name(src)] played a local sound [S]") - message_admins("[key_name_admin(src)] played a local sound [S]") - playsound(get_turf(src.mob), S, 50, FALSE, FALSE) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Play Local Sound") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/play_direct_mob_sound(S as sound, mob/M) - set category = "Fun" - set name = "Play Direct Mob Sound" - if(!check_rights(R_SOUND)) - return - - if(!M) - M = input(usr, "Choose a mob to play the sound to. Only they will hear it.", "Play Mob Sound") as null|anything in sortNames(GLOB.player_list) - if(!M || QDELETED(M)) - return - log_admin("[key_name(src)] played a direct mob sound [S] to [M].") - message_admins("[key_name_admin(src)] played a direct mob sound [S] to [ADMIN_LOOKUPFLW(M)].") - SEND_SOUND(M, S) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Play Direct Mob Sound") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/play_web_sound() - set category = "Fun" - set name = "Play Internet Sound" - if(!check_rights(R_SOUND)) - return - - var/ytdl = CONFIG_GET(string/invoke_youtubedl) - if(!ytdl) - to_chat(src, "Youtube-dl was not configured, action unavailable", confidential = TRUE) //Check config.txt for the INVOKE_YOUTUBEDL value - return - - var/web_sound_input = input("Enter content URL (supported sites only, leave blank to stop playing)", "Play Internet Sound via youtube-dl") as text|null - if(istext(web_sound_input)) - var/web_sound_url = "" - var/stop_web_sounds = FALSE - var/list/music_extra_data = list() - if(length(web_sound_input)) - - web_sound_input = trim(web_sound_input) - if(findtext(web_sound_input, ":") && !findtext(web_sound_input, GLOB.is_http_protocol)) - to_chat(src, "Non-http(s) URIs are not allowed.", confidential = TRUE) - to_chat(src, "For youtube-dl shortcuts like ytsearch: please use the appropriate full url from the website.", confidential = TRUE) - return - var/shell_scrubbed_input = shell_url_scrub(web_sound_input) - var/list/output = world.shelleo("[ytdl] --geo-bypass --format \"bestaudio\[ext=mp3]/best\[ext=mp4]\[height<=360]/bestaudio\[ext=m4a]/bestaudio\[ext=aac]\" --dump-single-json --no-playlist -- \"[shell_scrubbed_input]\"") - var/errorlevel = output[SHELLEO_ERRORLEVEL] - var/stdout = output[SHELLEO_STDOUT] - var/stderr = output[SHELLEO_STDERR] - if(!errorlevel) - var/list/data - try - data = json_decode(stdout) - catch(var/exception/e) - to_chat(src, "Youtube-dl JSON parsing FAILED:", confidential = TRUE) - to_chat(src, "[e]: [stdout]", confidential = TRUE) - return - - if (data["url"]) - web_sound_url = data["url"] - var/title = "[data["title"]]" - var/webpage_url = title - if (data["webpage_url"]) - webpage_url = "[title]" - music_extra_data["start"] = data["start_time"] - music_extra_data["end"] = data["end_time"] - - var/res = alert(usr, "Show the title of and link to this song to the players?\n[title]",, "No", "Yes", "Cancel") - switch(res) - if("Yes") - to_chat(world, "An admin played: [webpage_url]", confidential = TRUE) - if("Cancel") - return - - SSblackbox.record_feedback("nested tally", "played_url", 1, list("[ckey]", "[web_sound_input]")) - log_admin("[key_name(src)] played web sound: [web_sound_input]") - message_admins("[key_name(src)] played web sound: [web_sound_input]") - else - to_chat(src, "Youtube-dl URL retrieval FAILED:", confidential = TRUE) - to_chat(src, "[stderr]", confidential = TRUE) - - else //pressed ok with blank - log_admin("[key_name(src)] stopped web sound") - message_admins("[key_name(src)] stopped web sound") - web_sound_url = null - stop_web_sounds = TRUE - - if(web_sound_url && !findtext(web_sound_url, GLOB.is_http_protocol)) - to_chat(src, "BLOCKED: Content URL not using http(s) protocol", confidential = TRUE) - to_chat(src, "The media provider returned a content URL that isn't using the HTTP or HTTPS protocol", confidential = TRUE) - return - if(web_sound_url || stop_web_sounds) - for(var/m in GLOB.player_list) - var/mob/M = m - var/client/C = M.client - if((C.prefs.toggles & SOUND_MIDI) && C.chatOutput && !C.chatOutput.broken && C.chatOutput.loaded) - if(!stop_web_sounds) - C.chatOutput.sendMusic(web_sound_url, music_extra_data) - else - C.chatOutput.stopMusic() - - SSblackbox.record_feedback("tally", "admin_verb", 1, "Play Internet Sound") - -/client/proc/set_round_end_sound(S as sound) - set category = "Fun" - set name = "Set Round End Sound" - if(!check_rights(R_SOUND)) - return - - SSticker.SetRoundEndSound(S) - - log_admin("[key_name(src)] set the round end sound to [S]") - message_admins("[key_name_admin(src)] set the round end sound to [S]") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Set Round End Sound") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/stop_sounds() - set category = "Debug" - set name = "Stop All Playing Sounds" - if(!src.holder) - return - - log_admin("[key_name(src)] stopped all currently playing sounds.") - message_admins("[key_name_admin(src)] stopped all currently playing sounds.") - for(var/mob/M in GLOB.player_list) - SEND_SOUND(M, sound(null)) - var/client/C = M.client - if(C && C.chatOutput && !C.chatOutput.broken && C.chatOutput.loaded) - C.chatOutput.stopMusic() - SSblackbox.record_feedback("tally", "admin_verb", 1, "Stop All Playing Sounds") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! +/client/proc/play_sound(S as sound) + set category = "Fun" + set name = "Play Global Sound" + if(!check_rights(R_SOUND)) + return + + var/freq = 1 + var/vol = input(usr, "What volume would you like the sound to play at?",, 100) as null|num + if(!vol) + return + vol = clamp(vol, 1, 100) + + var/sound/admin_sound = new() + admin_sound.file = S + admin_sound.priority = 250 + admin_sound.channel = CHANNEL_ADMIN + admin_sound.frequency = freq + admin_sound.wait = 1 + admin_sound.repeat = 0 + admin_sound.status = SOUND_STREAM + admin_sound.volume = vol + + var/res = alert(usr, "Show the title of this song to the players?",, "Yes","No", "Cancel") + switch(res) + if("Yes") + to_chat(world, "An admin played: [S]", confidential = TRUE) + if("Cancel") + return + + log_admin("[key_name(src)] played sound [S]") + message_admins("[key_name_admin(src)] played sound [S]") + + for(var/mob/M in GLOB.player_list) + if(M.client.prefs.toggles & SOUND_MIDI) + var/user_vol = M.client.chatOutput.adminMusicVolume + if(user_vol) + admin_sound.volume = vol * (user_vol / 100) + SEND_SOUND(M, admin_sound) + admin_sound.volume = vol + + SSblackbox.record_feedback("tally", "admin_verb", 1, "Play Global Sound") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + +/client/proc/play_local_sound(S as sound) + set category = "Fun" + set name = "Play Local Sound" + if(!check_rights(R_SOUND)) + return + + log_admin("[key_name(src)] played a local sound [S]") + message_admins("[key_name_admin(src)] played a local sound [S]") + playsound(get_turf(src.mob), S, 50, FALSE, FALSE) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Play Local Sound") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/play_direct_mob_sound(S as sound, mob/M) + set category = "Fun" + set name = "Play Direct Mob Sound" + if(!check_rights(R_SOUND)) + return + + if(!M) + M = input(usr, "Choose a mob to play the sound to. Only they will hear it.", "Play Mob Sound") as null|anything in sortNames(GLOB.player_list) + if(!M || QDELETED(M)) + return + log_admin("[key_name(src)] played a direct mob sound [S] to [M].") + message_admins("[key_name_admin(src)] played a direct mob sound [S] to [ADMIN_LOOKUPFLW(M)].") + SEND_SOUND(M, S) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Play Direct Mob Sound") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/play_web_sound() + set category = "Fun" + set name = "Play Internet Sound" + if(!check_rights(R_SOUND)) + return + + var/ytdl = CONFIG_GET(string/invoke_youtubedl) + if(!ytdl) + to_chat(src, "Youtube-dl was not configured, action unavailable", confidential = TRUE) //Check config.txt for the INVOKE_YOUTUBEDL value + return + + var/web_sound_input = input("Enter content URL (supported sites only, leave blank to stop playing)", "Play Internet Sound via youtube-dl") as text|null + if(istext(web_sound_input)) + var/web_sound_url = "" + var/stop_web_sounds = FALSE + var/list/music_extra_data = list() + if(length(web_sound_input)) + + web_sound_input = trim(web_sound_input) + if(findtext(web_sound_input, ":") && !findtext(web_sound_input, GLOB.is_http_protocol)) + to_chat(src, "Non-http(s) URIs are not allowed.", confidential = TRUE) + to_chat(src, "For youtube-dl shortcuts like ytsearch: please use the appropriate full url from the website.", confidential = TRUE) + return + var/shell_scrubbed_input = shell_url_scrub(web_sound_input) + var/list/output = world.shelleo("[ytdl] --geo-bypass --format \"bestaudio\[ext=mp3]/best\[ext=mp4]\[height<=360]/bestaudio\[ext=m4a]/bestaudio\[ext=aac]\" --dump-single-json --no-playlist -- \"[shell_scrubbed_input]\"") + var/errorlevel = output[SHELLEO_ERRORLEVEL] + var/stdout = output[SHELLEO_STDOUT] + var/stderr = output[SHELLEO_STDERR] + if(!errorlevel) + var/list/data + try + data = json_decode(stdout) + catch(var/exception/e) + to_chat(src, "Youtube-dl JSON parsing FAILED:", confidential = TRUE) + to_chat(src, "[e]: [stdout]", confidential = TRUE) + return + + if (data["url"]) + web_sound_url = data["url"] + var/title = "[data["title"]]" + var/webpage_url = title + if (data["webpage_url"]) + webpage_url = "[title]" + music_extra_data["start"] = data["start_time"] + music_extra_data["end"] = data["end_time"] + + var/res = alert(usr, "Show the title of and link to this song to the players?\n[title]",, "No", "Yes", "Cancel") + switch(res) + if("Yes") + to_chat(world, "An admin played: [webpage_url]", confidential = TRUE) + if("Cancel") + return + + SSblackbox.record_feedback("nested tally", "played_url", 1, list("[ckey]", "[web_sound_input]")) + log_admin("[key_name(src)] played web sound: [web_sound_input]") + message_admins("[key_name(src)] played web sound: [web_sound_input]") + else + to_chat(src, "Youtube-dl URL retrieval FAILED:", confidential = TRUE) + to_chat(src, "[stderr]", confidential = TRUE) + + else //pressed ok with blank + log_admin("[key_name(src)] stopped web sound") + message_admins("[key_name(src)] stopped web sound") + web_sound_url = null + stop_web_sounds = TRUE + + if(web_sound_url && !findtext(web_sound_url, GLOB.is_http_protocol)) + to_chat(src, "BLOCKED: Content URL not using http(s) protocol", confidential = TRUE) + to_chat(src, "The media provider returned a content URL that isn't using the HTTP or HTTPS protocol", confidential = TRUE) + return + if(web_sound_url || stop_web_sounds) + for(var/m in GLOB.player_list) + var/mob/M = m + var/client/C = M.client + if((C.prefs.toggles & SOUND_MIDI) && C.chatOutput && !C.chatOutput.broken && C.chatOutput.loaded) + if(!stop_web_sounds) + C.chatOutput.sendMusic(web_sound_url, music_extra_data) + else + C.chatOutput.stopMusic() + + SSblackbox.record_feedback("tally", "admin_verb", 1, "Play Internet Sound") + +/client/proc/set_round_end_sound(S as sound) + set category = "Fun" + set name = "Set Round End Sound" + if(!check_rights(R_SOUND)) + return + + SSticker.SetRoundEndSound(S) + + log_admin("[key_name(src)] set the round end sound to [S]") + message_admins("[key_name_admin(src)] set the round end sound to [S]") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Set Round End Sound") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/stop_sounds() + set category = "Debug" + set name = "Stop All Playing Sounds" + if(!src.holder) + return + + log_admin("[key_name(src)] stopped all currently playing sounds.") + message_admins("[key_name_admin(src)] stopped all currently playing sounds.") + for(var/mob/M in GLOB.player_list) + SEND_SOUND(M, sound(null)) + var/client/C = M.client + if(C && C.chatOutput && !C.chatOutput.broken && C.chatOutput.loaded) + C.chatOutput.stopMusic() + SSblackbox.record_feedback("tally", "admin_verb", 1, "Stop All Playing Sounds") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! diff --git a/code/modules/admin/verbs/possess.dm b/code/modules/admin/verbs/possess.dm index 9b226a97e427..8fee260c9031 100644 --- a/code/modules/admin/verbs/possess.dm +++ b/code/modules/admin/verbs/possess.dm @@ -1,53 +1,53 @@ -/proc/possess(obj/O in world) - set name = "Possess Obj" - set category = "Object" - - if((O.obj_flags & DANGEROUS_POSSESSION) && CONFIG_GET(flag/forbid_singulo_possession)) - to_chat(usr, "[O] is too powerful for you to possess.", confidential = TRUE) - return - - var/turf/T = get_turf(O) - - if(T) - log_admin("[key_name(usr)] has possessed [O] ([O.type]) at [AREACOORD(T)]") - message_admins("[key_name(usr)] has possessed [O] ([O.type]) at [AREACOORD(T)]") - else - log_admin("[key_name(usr)] has possessed [O] ([O.type]) at an unknown location") - message_admins("[key_name(usr)] has possessed [O] ([O.type]) at an unknown location") - - if(!usr.control_object) //If you're not already possessing something... - usr.name_archive = usr.real_name - - usr.loc = O - usr.real_name = O.name - usr.name = O.name - usr.reset_perspective(O) - usr.control_object = O - SSblackbox.record_feedback("tally", "admin_verb", 1, "Possess Object") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/proc/release() - set name = "Release Obj" - set category = "Object" - //usr.loc = get_turf(usr) - - if(usr.control_object && usr.name_archive) //if you have a name archived and if you are actually relassing an object - usr.real_name = usr.name_archive - usr.name_archive = "" - usr.name = usr.real_name - if(ishuman(usr)) - var/mob/living/carbon/human/H = usr - H.name = H.get_visible_name() - - - usr.loc = get_turf(usr.control_object) - usr.reset_perspective() - usr.control_object = null - SSblackbox.record_feedback("tally", "admin_verb", 1, "Release Object") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/proc/givetestverbs(mob/M in GLOB.mob_list) - set desc = "Give this guy possess/release verbs" - set category = "Debug" - set name = "Give Possessing Verbs" - M.verbs += /proc/possess - M.verbs += /proc/release - SSblackbox.record_feedback("tally", "admin_verb", 1, "Give Possessing Verbs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! +/proc/possess(obj/O in world) + set name = "Possess Obj" + set category = "Object" + + if((O.obj_flags & DANGEROUS_POSSESSION) && CONFIG_GET(flag/forbid_singulo_possession)) + to_chat(usr, "[O] is too powerful for you to possess.", confidential = TRUE) + return + + var/turf/T = get_turf(O) + + if(T) + log_admin("[key_name(usr)] has possessed [O] ([O.type]) at [AREACOORD(T)]") + message_admins("[key_name(usr)] has possessed [O] ([O.type]) at [AREACOORD(T)]") + else + log_admin("[key_name(usr)] has possessed [O] ([O.type]) at an unknown location") + message_admins("[key_name(usr)] has possessed [O] ([O.type]) at an unknown location") + + if(!usr.control_object) //If you're not already possessing something... + usr.name_archive = usr.real_name + + usr.loc = O + usr.real_name = O.name + usr.name = O.name + usr.reset_perspective(O) + usr.control_object = O + SSblackbox.record_feedback("tally", "admin_verb", 1, "Possess Object") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/proc/release() + set name = "Release Obj" + set category = "Object" + //usr.loc = get_turf(usr) + + if(usr.control_object && usr.name_archive) //if you have a name archived and if you are actually relassing an object + usr.real_name = usr.name_archive + usr.name_archive = "" + usr.name = usr.real_name + if(ishuman(usr)) + var/mob/living/carbon/human/H = usr + H.name = H.get_visible_name() + + + usr.loc = get_turf(usr.control_object) + usr.reset_perspective() + usr.control_object = null + SSblackbox.record_feedback("tally", "admin_verb", 1, "Release Object") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/proc/givetestverbs(mob/M in GLOB.mob_list) + set desc = "Give this guy possess/release verbs" + set category = "Debug" + set name = "Give Possessing Verbs" + M.verbs += /proc/possess + M.verbs += /proc/release + SSblackbox.record_feedback("tally", "admin_verb", 1, "Give Possessing Verbs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! diff --git a/code/modules/admin/verbs/pray.dm b/code/modules/admin/verbs/pray.dm index 26545e251414..2e2e35d200db 100644 --- a/code/modules/admin/verbs/pray.dm +++ b/code/modules/admin/verbs/pray.dm @@ -1,76 +1,76 @@ -/mob/verb/pray(msg as text) - set category = "IC" - set name = "Pray" - - if(GLOB.say_disabled) //This is here to try to identify lag problems - to_chat(usr, "Speech is currently admin-disabled.", confidential = TRUE) - return - - msg = copytext_char(sanitize(msg), 1, MAX_MESSAGE_LEN) - if(!msg) - return - log_prayer("[src.key]/([src.name]): [msg]") - if(usr.client) - if(usr.client.prefs.muted & MUTE_PRAY) - to_chat(usr, "You cannot pray (muted).", confidential = TRUE) - return - if(src.client.handle_spam_prevention(msg,MUTE_PRAY)) - return - - var/mutable_appearance/cross = mutable_appearance('icons/obj/storage.dmi', "bible") - var/font_color = "purple" - var/prayer_type = "PRAYER" - var/deity - if(usr.job == "Chaplain") - cross.icon_state = "kingyellow" - font_color = "blue" - prayer_type = "CHAPLAIN PRAYER" - if(GLOB.deity) - deity = GLOB.deity - else if(iscultist(usr)) - cross.icon_state = "tome" - font_color = "red" - prayer_type = "CULTIST PRAYER" - deity = "Nar'Sie" - else if(isliving(usr)) - var/mob/living/L = usr - if(HAS_TRAIT(L, TRAIT_SPIRITUAL)) - cross.icon_state = "holylight" - font_color = "blue" - prayer_type = "SPIRITUAL PRAYER" - - var/msg_tmp = msg - msg = "[icon2html(cross, GLOB.admins)][prayer_type][deity ? " (to [deity])" : ""]: [ADMIN_FULLMONTY(src)] [ADMIN_SC(src)]: [msg]" - - for(var/client/C in GLOB.admins) - if(C.prefs.chat_toggles & CHAT_PRAYER) - to_chat(C, msg, confidential = TRUE) - if(C.prefs.toggles & SOUND_PRAYERS) - if(usr.job == "Chaplain") - SEND_SOUND(C, sound('sound/effects/pray.ogg')) - to_chat(usr, "You pray to the gods: \"[msg_tmp]\"", confidential = TRUE) - SSredbot.send_discord_message("admin", "Prayer from [src.key]/([src.name]): [msg]") - - 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) - 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, confidential = TRUE) - for(var/obj/machinery/computer/communications/C in GLOB.machines) - C.overrideCooldown() - -/proc/Syndicate_announce(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, confidential = TRUE) - for(var/obj/machinery/computer/communications/C in GLOB.machines) - C.overrideCooldown() - -/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, confidential = TRUE) - for(var/obj/machinery/computer/communications/C in GLOB.machines) - C.overrideCooldown() +/mob/verb/pray(msg as text) + set category = "IC" + set name = "Pray" + + if(GLOB.say_disabled) //This is here to try to identify lag problems + to_chat(usr, "Speech is currently admin-disabled.", confidential = TRUE) + return + + msg = copytext_char(sanitize(msg), 1, MAX_MESSAGE_LEN) + if(!msg) + return + log_prayer("[src.key]/([src.name]): [msg]") + if(usr.client) + if(usr.client.prefs.muted & MUTE_PRAY) + to_chat(usr, "You cannot pray (muted).", confidential = TRUE) + return + if(src.client.handle_spam_prevention(msg,MUTE_PRAY)) + return + + var/mutable_appearance/cross = mutable_appearance('icons/obj/storage.dmi', "bible") + var/font_color = "purple" + var/prayer_type = "PRAYER" + var/deity + if(usr.job == "Chaplain") + cross.icon_state = "kingyellow" + font_color = "blue" + prayer_type = "CHAPLAIN PRAYER" + if(GLOB.deity) + deity = GLOB.deity + else if(iscultist(usr)) + cross.icon_state = "tome" + font_color = "red" + prayer_type = "CULTIST PRAYER" + deity = "Nar'Sie" + else if(isliving(usr)) + var/mob/living/L = usr + if(HAS_TRAIT(L, TRAIT_SPIRITUAL)) + cross.icon_state = "holylight" + font_color = "blue" + prayer_type = "SPIRITUAL PRAYER" + + var/msg_tmp = msg + msg = "[icon2html(cross, GLOB.admins)][prayer_type][deity ? " (to [deity])" : ""]: [ADMIN_FULLMONTY(src)] [ADMIN_SC(src)]: [msg]" + + for(var/client/C in GLOB.admins) + if(C.prefs.chat_toggles & CHAT_PRAYER) + to_chat(C, msg, confidential = TRUE) + if(C.prefs.toggles & SOUND_PRAYERS) + if(usr.job == "Chaplain") + SEND_SOUND(C, sound('sound/effects/pray.ogg')) + to_chat(usr, "You pray to the gods: \"[msg_tmp]\"", confidential = TRUE) + SSredbot.send_discord_message("admin", "Prayer from [src.key]/([src.name]): [msg]") + + 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) + 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, confidential = TRUE) + for(var/obj/machinery/computer/communications/C in GLOB.machines) + C.overrideCooldown() + +/proc/Syndicate_announce(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, confidential = TRUE) + for(var/obj/machinery/computer/communications/C in GLOB.machines) + C.overrideCooldown() + +/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, confidential = TRUE) + for(var/obj/machinery/computer/communications/C in GLOB.machines) + C.overrideCooldown() diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm index 3f3a031f3c30..0643e22ed424 100644 --- a/code/modules/admin/verbs/randomverbs.dm +++ b/code/modules/admin/verbs/randomverbs.dm @@ -1,1250 +1,1250 @@ -/client/proc/cmd_admin_drop_everything(mob/M in GLOB.mob_list) - set category = null - set name = "Drop Everything" - if(!check_rights(R_ADMIN)) - return - - var/confirm = alert(src, "Make [M] drop everything?", "Message", "Yes", "No") - if(confirm != "Yes") - return - - for(var/obj/item/W in M) - if(!M.dropItemToGround(W)) - qdel(W) - M.regenerate_icons() - - log_admin("[key_name(usr)] made [key_name(M)] drop everything!") - var/msg = "[key_name_admin(usr)] made [ADMIN_LOOKUPFLW(M)] drop everything!" - message_admins(msg) - admin_ticket_log(M, msg) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Drop Everything") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_admin_subtle_message(mob/M in GLOB.mob_list) - set category = "Admin - Events" - set name = "Subtle Message" - - if(!ismob(M)) - return - if(!check_rights(R_ADMIN)) - return - - message_admins("[key_name_admin(src)] has started answering [ADMIN_LOOKUPFLW(M)]'s prayer.") - var/msg = input("Message:", text("Subtle PM to [M.key]")) as text|null - - if(!msg) - message_admins("[key_name_admin(src)] decided not to answer [ADMIN_LOOKUPFLW(M)]'s prayer") - return - if(usr) - if (usr.client) - if(usr.client.holder) - to_chat(M, "You hear a voice in your head... [msg]", confidential = TRUE) - - log_admin("SubtlePM: [key_name(usr)] -> [key_name(M)] : [msg]") - msg = " SubtleMessage: [key_name_admin(usr)] -> [key_name_admin(M)] : [msg]" - message_admins(msg) - admin_ticket_log(M, msg) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Subtle Message") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_admin_headset_message(mob/M in GLOB.mob_list) - set category = "Admin - Events" - set name = "Headset Message" - - admin_headset_message(M) - -/client/proc/admin_headset_message(mob/M in GLOB.mob_list, sender = null) - var/mob/living/carbon/human/H = M - - if(!check_rights(R_ADMIN)) - return - - if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human", confidential = TRUE) - return - if(!istype(H.ears, /obj/item/radio/headset)) - to_chat(usr, "The person you are trying to contact is not wearing a headset.", confidential = TRUE) - return - - if (!sender) - sender = input("Who is the message from?", "Sender") as null|anything in list(RADIO_CHANNEL_CENTCOM,RADIO_CHANNEL_SYNDICATE) - if(!sender) - return - - message_admins("[key_name_admin(src)] has started answering [key_name_admin(H)]'s [sender] request.") - var/input = input("Please enter a message to reply to [key_name(H)] via their headset.","Outgoing message from [sender]", "") as text|null - if(!input) - message_admins("[key_name_admin(src)] decided not to answer [key_name_admin(H)]'s [sender] request.") - return - - log_directed_talk(mob, H, input, LOG_ADMIN, "reply") - message_admins("[key_name_admin(src)] replied to [key_name_admin(H)]'s [sender] message with: \"[input]\"") - to_chat(H, "You hear something crackle in your ears for a moment before a voice speaks. \"Please stand by for a message from [sender == "Syndicate" ? "your benefactor" : "Central Command"]. Message as follows[sender == "Syndicate" ? ", agent." : ":"] [input]. Message ends.\"", confidential = TRUE) - - SSblackbox.record_feedback("tally", "admin_verb", 1, "Headset Message") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_admin_mod_antag_rep(client/C in GLOB.clients, operation) - set category = "null" - set name = "Modify Antagonist Reputation" - - if(!check_rights(R_ADMIN)) - return - - var/msg = "" - var/log_text = "" - - if(operation == "zero") - log_text = "Set to 0" - SSpersistence.antag_rep -= C.ckey - else - var/prompt = "Please enter the amount of reputation to [operation]:" - - if(operation == "set") - prompt = "Please enter the new reputation value:" - - msg = input("Message:", prompt) as num|null - - if (!msg) - return - - var/ANTAG_REP_MAXIMUM = CONFIG_GET(number/antag_rep_maximum) - - if(operation == "set") - log_text = "Set to [num2text(msg)]" - SSpersistence.antag_rep[C.ckey] = max(0, min(msg, ANTAG_REP_MAXIMUM)) - else if(operation == "add") - log_text = "Added [num2text(msg)]" - SSpersistence.antag_rep[C.ckey] = min(SSpersistence.antag_rep[C.ckey]+msg, ANTAG_REP_MAXIMUM) - else if(operation == "subtract") - log_text = "Subtracted [num2text(msg)]" - SSpersistence.antag_rep[C.ckey] = max(SSpersistence.antag_rep[C.ckey]-msg, 0) - else - to_chat(src, "Invalid operation for antag rep modification: [operation] by user [key_name(usr)]", confidential = TRUE) - return - - if(SSpersistence.antag_rep[C.ckey] <= 0) - SSpersistence.antag_rep -= C.ckey - - log_admin("[key_name(usr)]: Modified [key_name(C)]'s antagonist reputation [log_text]") - message_admins("[key_name_admin(usr)]: Modified [key_name(C)]'s antagonist reputation ([log_text])") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Modify Antagonist Reputation") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_admin_world_narrate() - set category = "Admin - Events" - set name = "Global Narrate" - - if(!check_rights(R_ADMIN)) - return - - var/msg = input("Message:", text("Enter the text you wish to appear to everyone:")) as text|null - - if (!msg) - return - to_chat(world, "[msg]", confidential = TRUE) - log_admin("GlobalNarrate: [key_name(usr)] : [msg]") - message_admins("[key_name_admin(usr)] Sent a global narrate") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Global Narrate") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_admin_direct_narrate(mob/M) - set category = "Admin - Events" - set name = "Direct Narrate" - - if(!check_rights(R_ADMIN)) - return - - if(!M) - M = input("Direct narrate to whom?", "Active Players") as null|anything in GLOB.player_list - - if(!M) - return - - var/msg = input("Message:", text("Enter the text you wish to appear to your target:")) as text|null - - if( !msg ) - return - - to_chat(M, msg, confidential = TRUE) - log_admin("DirectNarrate: [key_name(usr)] to ([M.name]/[M.key]): [msg]") - msg = " DirectNarrate: [key_name(usr)] to ([M.name]/[M.key]): [msg]
                    " - message_admins(msg) - admin_ticket_log(M, msg) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Direct Narrate") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_admin_local_narrate(atom/A) - set category = "Admin - Events" - set name = "Local Narrate" - - if(!check_rights(R_ADMIN)) - return - if(!A) - return - var/range = input("Range:", "Narrate to mobs within how many tiles:", 7) as num|null - if(!range) - return - var/msg = input("Message:", text("Enter the text you wish to appear to everyone within view:")) as text|null - if (!msg) - return - for(var/mob/M in view(range,A)) - to_chat(M, msg, confidential = TRUE) - - log_admin("LocalNarrate: [key_name(usr)] at [AREACOORD(A)]: [msg]") - message_admins(" LocalNarrate: [key_name_admin(usr)] at [ADMIN_VERBOSEJMP(A)]: [msg]
                    ") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Local Narrate") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_admin_godmode(mob/M in GLOB.mob_list) - set category = "Admin - Game" - set name = "Godmode" - if(!check_rights(R_ADMIN)) - return - - M.status_flags ^= GODMODE - to_chat(usr, "Toggled [(M.status_flags & GODMODE) ? "ON" : "OFF"]", confidential = TRUE) - - log_admin("[key_name(usr)] has toggled [key_name(M)]'s nodamage to [(M.status_flags & GODMODE) ? "On" : "Off"]") - var/msg = "[key_name_admin(usr)] has toggled [ADMIN_LOOKUPFLW(M)]'s nodamage to [(M.status_flags & GODMODE) ? "On" : "Off"]" - message_admins(msg) - admin_ticket_log(M, msg) - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Godmode", "[M.status_flags & GODMODE ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - -/proc/cmd_admin_mute(whom, mute_type, automute = 0) - if(!whom) - return - - var/muteunmute - var/mute_string - var/feedback_string - switch(mute_type) - if(MUTE_IC) - mute_string = "IC (say and emote)" - feedback_string = "IC" - if(MUTE_OOC) - mute_string = "OOC" - feedback_string = "OOC" - if(MUTE_PRAY) - mute_string = "pray" - feedback_string = "Pray" - if(MUTE_ADMINHELP) - mute_string = "adminhelp, admin PM and ASAY" - feedback_string = "Adminhelp" - if(MUTE_MENTORHELP) - mute_string = "mentorhelp" - feedback_string = "Mentorhelp" - if(MUTE_DEADCHAT) - mute_string = "deadchat and DSAY" - feedback_string = "Deadchat" - if(MUTE_ALL) - mute_string = "everything" - feedback_string = "Everything" - else - return - - var/client/C - if(istype(whom, /client)) - C = whom - else if(istext(whom)) - C = GLOB.directory[whom] - else - return - - var/datum/preferences/P - if(C) - P = C.prefs - else - P = GLOB.preferences_datums[whom] - if(!P) - return - - if(automute) - if(!CONFIG_GET(flag/automute_on)) - return - else - if(!check_rights()) - return - - if(automute) - muteunmute = "auto-muted" - P.muted |= mute_type - log_admin("SPAM AUTOMUTE: [muteunmute] [key_name(whom)] from [mute_string]") - message_admins("SPAM AUTOMUTE: [muteunmute] [key_name_admin(whom)] from [mute_string].") - if(C) - to_chat(C, "You have been [muteunmute] from [mute_string] by the SPAM AUTOMUTE system. Contact an admin.", confidential = TRUE) - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Auto Mute [feedback_string]", "1")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - return - - if(P.muted & mute_type) - muteunmute = "unmuted" - P.muted &= ~mute_type - else - muteunmute = "muted" - P.muted |= mute_type - - log_admin("[key_name(usr)] has [muteunmute] [key_name(whom)] from [mute_string]") - message_admins("[key_name_admin(usr)] has [muteunmute] [key_name_admin(whom)] from [mute_string].") - if(C) - to_chat(C, "You have been [muteunmute] from [mute_string] by [key_name(usr, include_name = FALSE)].", confidential = TRUE) - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Mute [feedback_string]", "[P.muted & mute_type]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - -//I use this proc for respawn character too. /N -/proc/create_xeno(ckey) - if(!ckey) - var/list/candidates = list() - for(var/mob/M in GLOB.player_list) - if(M.stat != DEAD) - continue //we are not dead! - if(!(ROLE_ALIEN in M.client.prefs.be_special)) - continue //we don't want to be an alium - if(M.client.is_afk()) - continue //we are afk - if(M.mind && M.mind.current && M.mind.current.stat != DEAD) - continue //we have a live body we are tied to - candidates += M.ckey - if(candidates.len) - ckey = input("Pick the player you want to respawn as a xeno.", "Suitable Candidates") as null|anything in sortKey(candidates) - else - to_chat(usr, "Error: create_xeno(): no suitable candidates.", confidential = TRUE) - if(!istext(ckey)) - return 0 - - var/alien_caste = input(usr, "Please choose which caste to spawn.","Pick a caste",null) as null|anything in list("Queen","Praetorian","Hunter","Sentinel","Drone","Larva") - var/obj/effect/landmark/spawn_here = GLOB.xeno_spawn.len ? pick(GLOB.xeno_spawn) : null - var/mob/living/carbon/alien/new_xeno - switch(alien_caste) - if("Queen") - new_xeno = new /mob/living/carbon/alien/humanoid/royal/queen(spawn_here) - if("Praetorian") - new_xeno = new /mob/living/carbon/alien/humanoid/royal/praetorian(spawn_here) - if("Hunter") - new_xeno = new /mob/living/carbon/alien/humanoid/hunter(spawn_here) - if("Sentinel") - new_xeno = new /mob/living/carbon/alien/humanoid/sentinel(spawn_here) - if("Drone") - new_xeno = new /mob/living/carbon/alien/humanoid/drone(spawn_here) - if("Larva") - new_xeno = new /mob/living/carbon/alien/larva(spawn_here) - else - return 0 - if(!spawn_here) - SSjob.SendToLateJoin(new_xeno, FALSE) - - new_xeno.ckey = ckey - var/msg = "[key_name_admin(usr)] has spawned [ckey] as a filthy xeno [alien_caste]." - message_admins(msg) - admin_ticket_log(new_xeno, msg) - return 1 - -/* -If a guy was gibbed and you want to revive him, this is a good way to do so. -Works kind of like entering the game with a new character. Character receives a new mind if they didn't have one. -Traitors and the like can also be revived with the previous role mostly intact. -/N */ -/client/proc/respawn_character() - set category = "Admin - Game" - set name = "Respawn Character" - set desc = "Respawn a person that has been gibbed/dusted/killed. They must be a ghost for this to work and preferably should not have a body to go back into." - if(!check_rights(R_ADMIN)) - return - - var/input = ckey(input(src, "Please specify which key will be respawned.", "Key", "")) - if(!input) - return - - var/mob/dead/observer/G_found - for(var/mob/dead/observer/G in GLOB.player_list) - if(G.ckey == input) - G_found = G - break - - if(!G_found)//If a ghost was not found. - to_chat(usr, "There is no active key like that in the game or the person is not currently a ghost.", confidential = TRUE) - return - - if(G_found.mind && !G_found.mind.active) //mind isn't currently in use by someone/something - //Check if they were an alien - if(G_found.mind.assigned_role == ROLE_ALIEN) - if(alert("This character appears to have been an alien. Would you like to respawn them as such?",,"Yes","No")=="Yes") - var/turf/T - if(GLOB.xeno_spawn.len) - T = pick(GLOB.xeno_spawn) - - var/mob/living/carbon/alien/new_xeno - switch(G_found.mind.special_role)//If they have a mind, we can determine which caste they were. - if("Hunter") - new_xeno = new /mob/living/carbon/alien/humanoid/hunter(T) - if("Sentinel") - new_xeno = new /mob/living/carbon/alien/humanoid/sentinel(T) - if("Drone") - new_xeno = new /mob/living/carbon/alien/humanoid/drone(T) - if("Praetorian") - new_xeno = new /mob/living/carbon/alien/humanoid/royal/praetorian(T) - if("Queen") - new_xeno = new /mob/living/carbon/alien/humanoid/royal/queen(T) - else//If we don't know what special role they have, for whatever reason, or they're a larva. - create_xeno(G_found.ckey) - return - - if(!T) - SSjob.SendToLateJoin(new_xeno, FALSE) - - //Now to give them their mind back. - G_found.mind.transfer_to(new_xeno) //be careful when doing stuff like this! I've already checked the mind isn't in use - new_xeno.key = G_found.key - to_chat(new_xeno, "You have been fully respawned. Enjoy the game.", confidential = TRUE) - var/msg = "[key_name_admin(usr)] has respawned [new_xeno.key] as a filthy xeno." - message_admins(msg) - admin_ticket_log(new_xeno, msg) - return //all done. The ghost is auto-deleted - - //check if they were a monkey - else if(findtext(G_found.real_name,"monkey")) - if(alert("This character appears to have been a monkey. Would you like to respawn them as such?",,"Yes","No")=="Yes") - var/mob/living/carbon/monkey/new_monkey = new - SSjob.SendToLateJoin(new_monkey) - G_found.mind.transfer_to(new_monkey) //be careful when doing stuff like this! I've already checked the mind isn't in use - new_monkey.key = G_found.key - to_chat(new_monkey, "You have been fully respawned. Enjoy the game.", confidential = TRUE) - var/msg = "[key_name_admin(usr)] has respawned [new_monkey.key] as a filthy xeno." - message_admins(msg) - admin_ticket_log(new_monkey, msg) - return //all done. The ghost is auto-deleted - - - //Ok, it's not a xeno or a monkey. So, spawn a human. - var/mob/living/carbon/human/new_character = new//The mob being spawned. - SSjob.SendToLateJoin(new_character) - - var/datum/data/record/record_found //Referenced to later to either randomize or not randomize the character. - if(G_found.mind && !G_found.mind.active) //mind isn't currently in use by someone/something - /*Try and locate a record for the person being respawned through GLOB.data_core. - This isn't an exact science but it does the trick more often than not.*/ - var/id = md5("[G_found.real_name][G_found.mind.assigned_role]") - - record_found = find_record("id", id, GLOB.data_core.locked) - - if(record_found)//If they have a record we can determine a few things. - new_character.real_name = record_found.fields["name"] - new_character.gender = record_found.fields["gender"] - new_character.age = record_found.fields["age"] - new_character.hardset_dna(record_found.fields["identity"], record_found.fields["enzymes"], null, record_found.fields["name"], record_found.fields["blood_type"], new record_found.fields["species"], record_found.fields["features"]) - else - var/datum/preferences/A = new() - A.copy_to(new_character) - A.real_name = G_found.real_name - new_character.dna.update_dna_identity() - - new_character.name = new_character.real_name - - if(G_found.mind && !G_found.mind.active) - G_found.mind.transfer_to(new_character) //be careful when doing stuff like this! I've already checked the mind isn't in use - else - new_character.mind_initialize() - if(!new_character.mind.assigned_role) - new_character.mind.assigned_role = "Assistant"//If they somehow got a null assigned role. - - new_character.key = G_found.key - - /* - The code below functions with the assumption that the mob is already a traitor if they have a special role. - So all it does is re-equip the mob with powers and/or items. Or not, if they have no special role. - If they don't have a mind, they obviously don't have a special role. - */ - - //Two variables to properly announce later on. - var/admin = key_name_admin(src) - var/player_key = G_found.key - - //Now for special roles and equipment. - var/datum/antagonist/traitor/traitordatum = new_character.mind.has_antag_datum(/datum/antagonist/traitor) - if(traitordatum) - SSjob.EquipRank(new_character, new_character.mind.assigned_role, 1) - traitordatum.equip() - - - switch(new_character.mind.special_role) - if(ROLE_WIZARD) - new_character.forceMove(pick(GLOB.wizardstart)) - var/datum/antagonist/wizard/A = new_character.mind.has_antag_datum(/datum/antagonist/wizard,TRUE) - A.equip_wizard() - if(ROLE_SYNDICATE) - new_character.forceMove(pick(GLOB.nukeop_start)) - var/datum/antagonist/nukeop/N = new_character.mind.has_antag_datum(/datum/antagonist/nukeop,TRUE) - N.equip_op() - if(ROLE_NINJA) - var/list/ninja_spawn = list() - for(var/obj/effect/landmark/carpspawn/L in GLOB.landmarks_list) - ninja_spawn += L - var/datum/antagonist/ninja/ninjadatum = new_character.mind.has_antag_datum(/datum/antagonist/ninja) - ninjadatum.equip_space_ninja() - if(ninja_spawn.len) - new_character.forceMove(pick(ninja_spawn)) - - else//They may also be a cyborg or AI. - switch(new_character.mind.assigned_role) - if("Cyborg")//More rigging to make em' work and check if they're traitor. - new_character = new_character.Robotize(TRUE) - if("AI") - new_character = new_character.AIize() - else - SSjob.EquipRank(new_character, new_character.mind.assigned_role, 1)//Or we simply equip them. - - //Announces the character on all the systems, based on the record. - if(!issilicon(new_character))//If they are not a cyborg/AI. - if(!record_found&&new_character.mind.assigned_role!=new_character.mind.special_role)//If there are no records for them. If they have a record, this info is already in there. MODE people are not announced anyway. - //Power to the user! - if(alert(new_character,"Warning: No data core entry detected. Would you like to announce the arrival of this character by adding them to various databases, such as medical records?",,"No","Yes")=="Yes") - GLOB.data_core.manifest_inject(new_character) - - if(alert(new_character,"Would you like an active AI to announce this character?",,"No","Yes")=="Yes") - AnnounceArrival(new_character, new_character.mind.assigned_role) - - var/msg = "[admin] has respawned [player_key] as [new_character.real_name]." - message_admins(msg) - admin_ticket_log(new_character, msg) - - to_chat(new_character, "You have been fully respawned. Enjoy the game.", confidential = TRUE) - - SSblackbox.record_feedback("tally", "admin_verb", 1, "Respawn Character") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - return new_character - -/client/proc/cmd_admin_add_freeform_ai_law() - set category = "Admin - Events" - set name = "Add Custom AI law" - - if(!check_rights(R_ADMIN)) - return - - var/input = input(usr, "Please enter anything you want the AI to do. Anything. Serious.", "What?", "") as text|null - if(!input) - return - - log_admin("Admin [key_name(usr)] has added a new AI law - [input]") - message_admins("Admin [key_name_admin(usr)] has added a new AI law - [input]") - - var/show_log = alert(src, "Show ion message?", "Message", "Yes", "No") - var/announce_ion_laws = (show_log == "Yes" ? 100 : 0) - - var/datum/round_event/ion_storm/add_law_only/ion = new() - ion.announceChance = announce_ion_laws - ion.ionMessage = input - - SSblackbox.record_feedback("tally", "admin_verb", 1, "Add Custom AI Law") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_admin_rejuvenate(mob/living/M in GLOB.mob_list) - set category = "Debug" - set name = "Rejuvenate" - - if(!check_rights(R_ADMIN)) - return - - if(!mob) - return - if(!istype(M)) - alert("Cannot revive a ghost") - return - M.revive(full_heal = TRUE, admin_revive = TRUE) - - log_admin("[key_name(usr)] healed / revived [key_name(M)]") - var/msg = "Admin [key_name_admin(usr)] healed / revived [ADMIN_LOOKUPFLW(M)]!" - message_admins(msg) - admin_ticket_log(M, msg) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Rejuvinate") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_admin_create_centcom_report() - set category = "Admin - Events" - set name = "Create Command Report" - - if(!check_rights(R_ADMIN)) - return - - var/input = input(usr, "Enter a Command Report. Ensure it makes sense IC. Command's name is currently set to [command_name()].", "What?", "") as message|null - if(!input) - return - - var/confirm = alert(src, "Do you want to announce the contents of the report to the crew?", "Announce", "Yes", "No", "Cancel") - var/announce_command_report = TRUE - switch(confirm) - if("Yes") - priority_announce(input, null, 'sound/ai/commandreport.ogg') - announce_command_report = FALSE - if("Cancel") - return - - print_command_report(input, "[announce_command_report ? "Classified " : ""][command_name()] Update", announce_command_report) - - log_admin("[key_name(src)] has created a command report: [input]") - message_admins("[key_name_admin(src)] has created a command report") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Create Command Report") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_change_command_name() - set category = "Admin - Events" - set name = "Change Command Name" - - if(!check_rights(R_ADMIN)) - return - - var/input = input(usr, "Please input a new name for Central Command.", "What?", "") as text|null - if(!input) - return - change_command_name(input) - message_admins("[key_name_admin(src)] has changed Central Command's name to [input]") - log_admin("[key_name(src)] has changed the Central Command name to: [input]") - -/client/proc/cmd_admin_delete(atom/A as obj|mob|turf in world) - set category = "Debug" - set name = "Delete" - - if(!check_rights(R_SPAWN|R_DEBUG)) - return - - admin_delete(A) - -/client/proc/cmd_admin_list_open_jobs() - set category = "Admin - Game" - set name = "Manage Job Slots" - - if(!check_rights(R_ADMIN)) - return - holder.manage_free_slots() - SSblackbox.record_feedback("tally", "admin_verb", 1, "Manage Job Slots") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_admin_explosion(atom/O as obj|mob|turf in world) - set category = "Fun" - set name = "Explosion" - - if(!check_rights(R_ADMIN)) - return - - var/devastation = input("Range of total devastation. -1 to none", text("Input")) as num|null - if(devastation == null) - return - var/heavy = input("Range of heavy impact. -1 to none", text("Input")) as num|null - if(heavy == null) - return - var/light = input("Range of light impact. -1 to none", text("Input")) as num|null - if(light == null) - return - var/flash = input("Range of flash. -1 to none", text("Input")) as num|null - if(flash == null) - return - var/flames = input("Range of flames. -1 to none", text("Input")) as num|null - if(flames == null) - return - - if ((devastation != -1) || (heavy != -1) || (light != -1) || (flash != -1) || (flames != -1)) - if ((devastation > 20) || (heavy > 20) || (light > 20) || (flames > 20)) - if (alert(src, "Are you sure you want to do this? It will laaag.", "Confirmation", "Yes", "No") == "No") - return - - explosion(O, devastation, heavy, light, flash, null, null,flames) - log_admin("[key_name(usr)] created an explosion ([devastation],[heavy],[light],[flames]) at [AREACOORD(O)]") - message_admins("[key_name_admin(usr)] created an explosion ([devastation],[heavy],[light],[flames]) at [AREACOORD(O)]") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Explosion") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - return - else - return - -/client/proc/cmd_admin_emp(atom/O as obj|mob|turf in world) - set category = "Fun" - set name = "EM Pulse" - - if(!check_rights(R_ADMIN)) - return - - var/heavy = input("Range of heavy pulse.", text("Input")) as num|null - if(heavy == null) - return - var/light = input("Range of light pulse.", text("Input")) as num|null - if(light == null) - return - - if (heavy || light) - - empulse(O, heavy, light) - log_admin("[key_name(usr)] created an EM Pulse ([heavy],[light]) at [AREACOORD(O)]") - message_admins("[key_name_admin(usr)] created an EM Pulse ([heavy],[light]) at [AREACOORD(O)]") - SSblackbox.record_feedback("tally", "admin_verb", 1, "EM Pulse") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - return - else - return - -/client/proc/cmd_admin_gib(mob/M in GLOB.mob_list) - set category = "Fun" - set name = "Gib" - - if(!check_rights(R_ADMIN)) - return - - var/confirm = alert(src, "Drop a brain?", "Confirm", "Yes", "No","Cancel") - if(confirm == "Cancel") - return - //Due to the delay here its easy for something to have happened to the mob - if(!M) - return - - log_admin("[key_name(usr)] has gibbed [key_name(M)]") - message_admins("[key_name_admin(usr)] has gibbed [key_name_admin(M)]") - - if(isobserver(M)) - new /obj/effect/gibspawner/generic(get_turf(M)) - return - if(confirm == "Yes") - M.gib() - else - M.gib(1) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Gib") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_admin_gib_self() - set name = "Gibself" - set category = "Fun" - - var/confirm = alert(src, "You sure?", "Confirm", "Yes", "No") - if(confirm == "Yes") - log_admin("[key_name(usr)] used gibself.") - message_admins("[key_name_admin(usr)] used gibself.") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Gib Self") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - mob.gib(1, 1, 1) - -/client/proc/cmd_admin_check_contents(mob/living/M in GLOB.mob_list) - set category = "Debug" - set name = "Check Contents" - - var/list/L = M.get_contents() - for(var/t in L) - to_chat(usr, "[t]", confidential = TRUE) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Check Contents") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/toggle_view_range() - set category = "Admin - Game" - set name = "Change View Range" - set desc = "switches between 1x and custom views" - - if(view == CONFIG_GET(string/default_view)) - change_view(input("Select view range:", "FUCK YE", 7) in list(1,2,3,4,5,6,7,8,9,10,11,12,13,14,128)) - else - change_view(CONFIG_GET(string/default_view)) - - log_admin("[key_name(usr)] changed their view range to [view].") - //message_admins("\blue [key_name_admin(usr)] changed their view range to [view].") //why? removed by order of XSI - - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Change View Range", "[view]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/admin_call_shuttle() - - set category = "Admin - Events" - set name = "Call Shuttle" - - if(EMERGENCY_AT_LEAST_DOCKED) - return - - if(!check_rights(R_ADMIN)) - return - - var/confirm = alert(src, "You sure?", "Confirm", "Yes", "No") - if(confirm != "Yes") - return - - SSshuttle.emergency.request() - SSblackbox.record_feedback("tally", "admin_verb", 1, "Call Shuttle") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - log_admin("[key_name(usr)] admin-called the emergency shuttle.") - message_admins("[key_name_admin(usr)] admin-called the emergency shuttle.") - return - -/client/proc/admin_cancel_shuttle() - set category = "Admin - Events" - set name = "Cancel Shuttle" - if(!check_rights(0)) - return - if(alert(src, "You sure?", "Confirm", "Yes", "No") != "Yes") - return - - if(EMERGENCY_AT_LEAST_DOCKED) - return - - SSshuttle.emergency.cancel() - SSblackbox.record_feedback("tally", "admin_verb", 1, "Cancel Shuttle") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - log_admin("[key_name(usr)] admin-recalled the emergency shuttle.") - message_admins("[key_name_admin(usr)] admin-recalled the emergency shuttle.") - - return - -/client/proc/everyone_random() - set category = "Fun" - set name = "Make Everyone Random" - set desc = "Make everyone have a random appearance. You can only use this before rounds!" - - if(SSticker.HasRoundStarted()) - to_chat(usr, "Nope you can't do this, the game's already started. This only works before rounds!", confidential = TRUE) - return - - var/frn = CONFIG_GET(flag/force_random_names) - if(frn) - CONFIG_SET(flag/force_random_names, FALSE) - message_admins("Admin [key_name_admin(usr)] has disabled \"Everyone is Special\" mode.") - to_chat(usr, "Disabled.", confidential = TRUE) - return - - - var/notifyplayers = alert(src, "Do you want to notify the players?", "Options", "Yes", "No", "Cancel") - if(notifyplayers == "Cancel") - return - - log_admin("Admin [key_name(src)] has forced the players to have random appearances.") - message_admins("Admin [key_name_admin(usr)] has forced the players to have random appearances.") - - if(notifyplayers == "Yes") - to_chat(world, "Admin [usr.key] has forced the players to have completely random identities!", confidential = TRUE) - - to_chat(usr, "Remember: you can always disable the randomness by using the verb again, assuming the round hasn't started yet.", confidential = TRUE) - - CONFIG_SET(flag/force_random_names, TRUE) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Make Everyone Random") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - -/client/proc/toggle_random_events() - set category = "Server" - set name = "Toggle random events on/off" - set desc = "Toggles random events such as meteors, black holes, blob (but not space dust) on/off" - var/new_are = !CONFIG_GET(flag/allow_random_events) - CONFIG_SET(flag/allow_random_events, new_are) - if(new_are) - to_chat(usr, "Random events enabled", confidential = TRUE) - message_admins("Admin [key_name_admin(usr)] has enabled random events.") - else - to_chat(usr, "Random events disabled", confidential = TRUE) - message_admins("Admin [key_name_admin(usr)] has disabled random events.") - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Random Events", "[new_are ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - -/client/proc/admin_change_sec_level() - set category = "Admin - Events" - set name = "Set Security Level" - set desc = "Changes the security level. Announcement only, i.e. setting to Delta won't activate nuke" - - if(!check_rights(R_ADMIN)) - return - - var/level = input("Select security level to change to","Set Security Level") as null|anything in list("green","blue","red","delta") - if(level) - set_security_level(level) - - log_admin("[key_name(usr)] changed the security level to [level]") - message_admins("[key_name_admin(usr)] changed the security level to [level]") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Set Security Level [capitalize(level)]") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/toggle_nuke(obj/machinery/nuclearbomb/N in GLOB.nuke_list) - set name = "Toggle Nuke" - set category = "Admin - Events" - set popup_menu = 0 - if(!check_rights(R_DEBUG)) - return - - if(!N.timing) - var/newtime = input(usr, "Set activation timer.", "Activate Nuke", "[N.timer_set]") as num|null - if(!newtime) - return - N.timer_set = newtime - N.set_safety() - N.set_active() - - log_admin("[key_name(usr)] [N.timing ? "activated" : "deactivated"] a nuke at [AREACOORD(N)].") - message_admins("[ADMIN_LOOKUPFLW(usr)] [N.timing ? "activated" : "deactivated"] a nuke at [ADMIN_VERBOSEJMP(N)].") - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Nuke", "[N.timing]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/toggle_combo_hud() - set category = "Admin - Game" - set name = "Toggle Combo HUD" - set desc = "Toggles the Admin Combo HUD (antag, sci, med, eng)" - - if(!check_rights(R_ADMIN)) - return - - var/adding_hud = !has_antag_hud() - - for(var/hudtype in list(DATA_HUD_SECURITY_ADVANCED, DATA_HUD_MEDICAL_ADVANCED, DATA_HUD_DIAGNOSTIC_ADVANCED)) // add data huds - var/datum/atom_hud/H = GLOB.huds[hudtype] - (adding_hud) ? H.add_hud_to(usr) : H.remove_hud_from(usr) - for(var/datum/atom_hud/antag/H in GLOB.huds) // add antag huds - (adding_hud) ? H.add_hud_to(usr) : H.remove_hud_from(usr) - - if(prefs.toggles & COMBOHUD_LIGHTING) - if(adding_hud) - mob.lighting_alpha = LIGHTING_PLANE_ALPHA_INVISIBLE - else - mob.lighting_alpha = initial(mob.lighting_alpha) - - mob.update_sight() - - to_chat(usr, "You toggled your admin combo HUD [adding_hud ? "ON" : "OFF"].", confidential = TRUE) - message_admins("[key_name_admin(usr)] toggled their admin combo HUD [adding_hud ? "ON" : "OFF"].") - log_admin("[key_name(usr)] toggled their admin combo HUD [adding_hud ? "ON" : "OFF"].") - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Combo HUD", "[adding_hud ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - -/client/proc/has_antag_hud() - var/datum/atom_hud/A = GLOB.huds[ANTAG_HUD_TRAITOR] - return A.hudusers[mob] - - -/client/proc/run_weather() - set category = "Admin - Events" - set name = "Run Weather" - set desc = "Triggers a weather on the z-level you choose." - - if(!holder) - return - - var/weather_type = input("Choose a weather", "Weather") as null|anything in sortList(subtypesof(/datum/weather), /proc/cmp_typepaths_asc) - if(!weather_type) - return - - var/turf/T = get_turf(mob) - var/z_level = input("Z-Level to target?", "Z-Level", T?.z) as num|null - if(!isnum(z_level)) - return - - SSweather.run_weather(weather_type, z_level) - - message_admins("[key_name_admin(usr)] started weather of type [weather_type] on the z-level [z_level].") - log_admin("[key_name(usr)] started weather of type [weather_type] on the z-level [z_level].") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Run Weather") - -/client/proc/mass_zombie_infection() - set category = "Fun" - set name = "Mass Zombie Infection" - set desc = "Infects all humans with a latent organ that will zombify \ - them on death." - - if(!check_rights(R_ADMIN)) - return - - var/confirm = alert(src, "Please confirm you want to add latent zombie organs in all humans?", "Confirm Zombies", "Yes", "No") - if(confirm != "Yes") - return - - for(var/i in GLOB.human_list) - var/mob/living/carbon/human/H = i - new /obj/item/organ/zombie_infection/nodamage(H) - - message_admins("[key_name_admin(usr)] added a latent zombie infection to all humans.") - log_admin("[key_name(usr)] added a latent zombie infection to all humans.") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Mass Zombie Infection") - -/client/proc/mass_zombie_cure() - set category = "Fun" - set name = "Mass Zombie Cure" - set desc = "Removes the zombie infection from all humans, returning them to normal." - if(!check_rights(R_ADMIN)) - return - - var/confirm = alert(src, "Please confirm you want to cure all zombies?", "Confirm Zombie Cure", "Yes", "No") - if(confirm != "Yes") - return - - for(var/obj/item/organ/zombie_infection/nodamage/I in GLOB.zombie_infection_list) - qdel(I) - - message_admins("[key_name_admin(usr)] cured all zombies.") - log_admin("[key_name(usr)] cured all zombies.") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Mass Zombie Cure") - -/client/proc/polymorph_all() - set category = "Fun" - set name = "Polymorph All" - set desc = "Applies the effects of the bolt of change to every single mob." - - if(!check_rights(R_ADMIN)) - return - - var/confirm = alert(src, "Please confirm you want polymorph all mobs?", "Confirm Polymorph", "Yes", "No") - if(confirm != "Yes") - return - - var/list/mobs = shuffle(GLOB.alive_mob_list.Copy()) // might change while iterating - var/who_did_it = key_name_admin(usr) - - message_admins("[key_name_admin(usr)] started polymorphed all living mobs.") - log_admin("[key_name(usr)] polymorphed all living mobs.") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Polymorph All") - - for(var/mob/living/M in mobs) - CHECK_TICK - - if(!M) - continue - - M.audible_message("...wabbajack...wabbajack...") - playsound(M.loc, 'sound/magic/staff_change.ogg', 50, TRUE, -1) - - wabbajack(M) - - message_admins("Mass polymorph started by [who_did_it] is complete.") - - -/client/proc/show_tip() - set category = "Admin" - set name = "Show Tip" - set desc = "Sends a tip (that you specify) to all players. After all \ - you're the experienced player here." - - if(!check_rights(R_ADMIN)) - return - - var/input = input(usr, "Please specify your tip that you want to send to the players.", "Tip", "") as message|null - if(!input) - return - - if(!SSticker) - return - - SSticker.selected_tip = input - - // If we've already tipped, then send it straight away. - if(SSticker.tipped) - SSticker.send_tip_of_the_round() - - - message_admins("[key_name_admin(usr)] sent a tip of the round.") - log_admin("[key_name(usr)] sent \"[input]\" as the Tip of the Round.") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Tip") - -/client/proc/modify_goals() - set category = "Debug" - set name = "Modify goals" - - if(!check_rights(R_ADMIN)) - return - - holder.modify_goals() - -/datum/admins/proc/modify_goals() - var/dat = "" - for(var/datum/station_goal/S in SSticker.mode.station_goals) - dat += "[S.name] - Announce | Remove
                    " - dat += "
                    Add New Goal" - usr << browse(dat, "window=goals;size=400x400") - -/proc/immerse_player(mob/living/carbon/target, toggle=TRUE, remove=FALSE) - var/list/immersion_components = list(/datum/component/manual_breathing, /datum/component/manual_blinking) - - for(var/immersies in immersion_components) - var/has_component = target.GetComponent(immersies) - - if(has_component && (toggle || remove)) - qdel(has_component) - else if(toggle || !remove) - target.AddComponent(immersies) - -/proc/mass_immerse(remove=FALSE) - for(var/mob/living/carbon/M in GLOB.mob_list) - immerse_player(M, toggle=FALSE, remove=remove) - -/client/proc/toggle_hub() - set category = "Server" - set name = "Toggle Hub" - - world.update_hub_visibility(!GLOB.hub_visibility) - - log_admin("[key_name(usr)] has toggled the server's hub status for the round, it is now [(GLOB.hub_visibility?"on":"off")] the hub.") - message_admins("[key_name_admin(usr)] has toggled the server's hub status for the round, it is now [(GLOB.hub_visibility?"on":"off")] the hub.") - if (GLOB.hub_visibility && !world.reachable) - message_admins("WARNING: The server will not show up on the hub because byond is detecting that a filewall is blocking incoming connections.") - - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggled Hub Visibility", "[GLOB.hub_visibility ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/smite(mob/living/target as mob) - set name = "Smite" - set category = "Fun" - if(!check_rights(R_ADMIN) || !check_rights(R_FUN)) - return - - var/list/punishment_list = list(ADMIN_PUNISHMENT_BREAK_BONES, ADMIN_PUNISHMENT_LIGHTNING, ADMIN_PUNISHMENT_BRAINDAMAGE, ADMIN_PUNISHMENT_GIB, ADMIN_PUNISHMENT_BSA, ADMIN_PUNISHMENT_FIREBALL, ADMIN_PUNISHMENT_ROD, ADMIN_PUNISHMENT_SUPPLYPOD_QUICK, ADMIN_PUNISHMENT_SUPPLYPOD, ADMIN_PUNISHMENT_MAZING, ADMIN_PUNISHMENT_IMMERSE) - - var/punishment = input("Choose a punishment", "DIVINE SMITING") as null|anything in sortList(punishment_list) - - if(QDELETED(target) || !punishment) - return - - switch(punishment) - if(ADMIN_PUNISHMENT_BREAK_BONES) - if(iscarbon(target)) - var/mob/living/carbon/C = target - C.break_all_bones() - if(ADMIN_PUNISHMENT_LIGHTNING) - var/turf/T = get_step(get_step(target, NORTH), NORTH) - T.Beam(target, icon_state="lightning[rand(1,12)]", time = 5) - target.adjustFireLoss(75) - if(ishuman(target)) - var/mob/living/carbon/human/H = target - H.electrocution_animation(40) - to_chat(target, "The gods have punished you for your sins!", confidential = TRUE) - if(ADMIN_PUNISHMENT_BRAINDAMAGE) - target.adjustOrganLoss(ORGAN_SLOT_BRAIN, 199, 199) - if(ADMIN_PUNISHMENT_GIB) - target.gib(FALSE) - if(ADMIN_PUNISHMENT_BSA) - bluespace_artillery(target) - if(ADMIN_PUNISHMENT_FIREBALL) - new /obj/effect/temp_visual/target(get_turf(target)) - if(ADMIN_PUNISHMENT_ROD) - var/turf/T = get_turf(target) - var/startside = pick(GLOB.cardinals) - var/turf/startT = spaceDebrisStartLoc(startside, T.z) - var/turf/endT = spaceDebrisFinishLoc(startside, T.z) - new /obj/effect/immovablerod(startT, endT,target) - if(ADMIN_PUNISHMENT_SUPPLYPOD_QUICK) - var/target_path = input(usr,"Enter typepath of an atom you'd like to send with the pod (type \"empty\" to send an empty pod):" ,"Typepath","/obj/item/reagent_containers/food/snacks/grown/harebell") as null|text - var/obj/structure/closet/supplypod/centcompod/pod = new() - pod.damage = 40 - pod.explosionSize = list(0,0,0,2) - pod.effectStun = TRUE - if (isnull(target_path)) //The user pressed "Cancel" - return - if (target_path != "empty")//if you didn't type empty, we want to load the pod with a delivery - var/delivery = text2path(target_path) - if(!ispath(delivery)) - delivery = pick_closest_path(target_path) - if(!delivery) - alert("ERROR: Incorrect / improper path given.") - return - new delivery(pod) - new /obj/effect/DPtarget(get_turf(target), pod) - if(ADMIN_PUNISHMENT_SUPPLYPOD) - var/datum/centcom_podlauncher/plaunch = new(usr) - if(!holder) - return - plaunch.specificTarget = target - plaunch.launchChoice = 0 - plaunch.damageChoice = 1 - plaunch.explosionChoice = 1 - plaunch.temp_pod.damage = 40//bring the mother fuckin ruckus - plaunch.temp_pod.explosionSize = list(0,0,0,2) - plaunch.temp_pod.effectStun = TRUE - plaunch.ui_interact(usr) - return //We return here because punish_log() is handled by the centcom_podlauncher datum - if(ADMIN_PUNISHMENT_MAZING) - if(!puzzle_imprison(target)) - to_chat(usr,"Imprisonment failed!", confidential = TRUE) - return - if(ADMIN_PUNISHMENT_IMMERSE) - immerse_player(target) - - punish_log(target, punishment) - -/client/proc/punish_log(whom, punishment) - var/msg = "[key_name_admin(usr)] punished [key_name_admin(whom)] with [punishment]." - message_admins(msg) - admin_ticket_log(whom, msg) - log_admin("[key_name(usr)] punished [key_name(whom)] with [punishment].") - -/client/proc/trigger_centcom_recall() - if(!check_rights(R_ADMIN)) - return - var/message = pick(GLOB.admiral_messages) - message = input("Enter message from the on-call admiral to be put in the recall report.", "Admiral Message", message) as text|null - - if(!message) - return - - message_admins("[key_name_admin(usr)] triggered a CentCom recall, with the admiral message of: [message]") - log_game("[key_name(usr)] triggered a CentCom recall, with the message of: [message]") - SSshuttle.centcom_recall(SSshuttle.emergency.timer, message) - -/client/proc/cmd_admin_check_player_exp() //Allows admins to determine who the newer players are. - set category = "Admin" - set name = "Player Playtime" - if(!check_rights(R_ADMIN)) - return - - if(!CONFIG_GET(flag/use_exp_tracking)) - to_chat(usr, "Tracking is disabled in the server configuration file.", confidential = TRUE) - return - - var/list/msg = list() - msg += "Playtime ReportPlaytime:
                    " - src << browse(msg.Join(), "window=Player_playtime_check") - -/datum/admins/proc/cmd_show_exp_panel(client/C) - if(!check_rights(R_ADMIN)) - return - if(!C) - 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.", 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") - -/datum/admins/proc/toggle_exempt_status(client/C) - if(!check_rights(R_ADMIN)) - return - if(!C) - to_chat(usr, "ERROR: Client not found.", confidential = TRUE) - return - - if(!C.set_db_player_flags()) - to_chat(usr, "ERROR: Unable read player flags from database. Please check logs.", confidential = TRUE) - var/dbflags = C.prefs.db_flags - var/newstate = FALSE - if(dbflags & DB_FLAG_EXEMPT) - newstate = FALSE - else - newstate = TRUE - - if(C.update_flag_db(DB_FLAG_EXEMPT, newstate)) - to_chat(usr, "ERROR: Unable to update player flags. Please check logs.", confidential = TRUE) - else - message_admins("[key_name_admin(usr)] has [newstate ? "activated" : "deactivated"] job exp exempt status on [key_name_admin(C)]") - log_admin("[key_name(usr)] has [newstate ? "activated" : "deactivated"] job exp exempt status on [key_name(C)]") - -/// Allow admin to add or remove traits of datum -/datum/admins/proc/modify_traits(datum/D) - if(!D) - return - - var/add_or_remove = input("Remove/Add?", "Trait Remove/Add") as null|anything in list("Add","Remove") - if(!add_or_remove) - return - var/list/availible_traits = list() - - switch(add_or_remove) - if("Add") - for(var/key in GLOB.traits_by_type) - if(istype(D,key)) - availible_traits += GLOB.traits_by_type[key] - if("Remove") - if(!GLOB.trait_name_map) - GLOB.trait_name_map = generate_trait_name_map() - for(var/trait in D.status_traits) - var/name = GLOB.trait_name_map[trait] || trait - availible_traits[name] = trait - - var/chosen_trait = input("Select trait to modify", "Trait") as null|anything in sortList(availible_traits) - if(!chosen_trait) - return - chosen_trait = availible_traits[chosen_trait] - - var/source = "adminabuse" - switch(add_or_remove) - if("Add") //Not doing source choosing here intentionally to make this bit faster to use, you can always vv it. - ADD_TRAIT(D,chosen_trait,source) - if("Remove") - var/specific = input("All or specific source ?", "Trait Remove/Add") as null|anything in list("All","Specific") - if(!specific) - return - switch(specific) - if("All") - source = null - if("Specific") - source = input("Source to be removed","Trait Remove/Add") as null|anything in sortList(D.status_traits[chosen_trait]) - if(!source) - return - REMOVE_TRAIT(D,chosen_trait,source) +/client/proc/cmd_admin_drop_everything(mob/M in GLOB.mob_list) + set category = null + set name = "Drop Everything" + if(!check_rights(R_ADMIN)) + return + + var/confirm = alert(src, "Make [M] drop everything?", "Message", "Yes", "No") + if(confirm != "Yes") + return + + for(var/obj/item/W in M) + if(!M.dropItemToGround(W)) + qdel(W) + M.regenerate_icons() + + log_admin("[key_name(usr)] made [key_name(M)] drop everything!") + var/msg = "[key_name_admin(usr)] made [ADMIN_LOOKUPFLW(M)] drop everything!" + message_admins(msg) + admin_ticket_log(M, msg) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Drop Everything") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_admin_subtle_message(mob/M in GLOB.mob_list) + set category = "Admin - Events" + set name = "Subtle Message" + + if(!ismob(M)) + return + if(!check_rights(R_ADMIN)) + return + + message_admins("[key_name_admin(src)] has started answering [ADMIN_LOOKUPFLW(M)]'s prayer.") + var/msg = input("Message:", text("Subtle PM to [M.key]")) as text|null + + if(!msg) + message_admins("[key_name_admin(src)] decided not to answer [ADMIN_LOOKUPFLW(M)]'s prayer") + return + if(usr) + if (usr.client) + if(usr.client.holder) + to_chat(M, "You hear a voice in your head... [msg]", confidential = TRUE) + + log_admin("SubtlePM: [key_name(usr)] -> [key_name(M)] : [msg]") + msg = " SubtleMessage: [key_name_admin(usr)] -> [key_name_admin(M)] : [msg]" + message_admins(msg) + admin_ticket_log(M, msg) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Subtle Message") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_admin_headset_message(mob/M in GLOB.mob_list) + set category = "Admin - Events" + set name = "Headset Message" + + admin_headset_message(M) + +/client/proc/admin_headset_message(mob/M in GLOB.mob_list, sender = null) + var/mob/living/carbon/human/H = M + + if(!check_rights(R_ADMIN)) + return + + if(!istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human", confidential = TRUE) + return + if(!istype(H.ears, /obj/item/radio/headset)) + to_chat(usr, "The person you are trying to contact is not wearing a headset.", confidential = TRUE) + return + + if (!sender) + sender = input("Who is the message from?", "Sender") as null|anything in list(RADIO_CHANNEL_CENTCOM,RADIO_CHANNEL_SYNDICATE) + if(!sender) + return + + message_admins("[key_name_admin(src)] has started answering [key_name_admin(H)]'s [sender] request.") + var/input = input("Please enter a message to reply to [key_name(H)] via their headset.","Outgoing message from [sender]", "") as text|null + if(!input) + message_admins("[key_name_admin(src)] decided not to answer [key_name_admin(H)]'s [sender] request.") + return + + log_directed_talk(mob, H, input, LOG_ADMIN, "reply") + message_admins("[key_name_admin(src)] replied to [key_name_admin(H)]'s [sender] message with: \"[input]\"") + to_chat(H, "You hear something crackle in your ears for a moment before a voice speaks. \"Please stand by for a message from [sender == "Syndicate" ? "your benefactor" : "Central Command"]. Message as follows[sender == "Syndicate" ? ", agent." : ":"] [input]. Message ends.\"", confidential = TRUE) + + SSblackbox.record_feedback("tally", "admin_verb", 1, "Headset Message") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_admin_mod_antag_rep(client/C in GLOB.clients, operation) + set category = "null" + set name = "Modify Antagonist Reputation" + + if(!check_rights(R_ADMIN)) + return + + var/msg = "" + var/log_text = "" + + if(operation == "zero") + log_text = "Set to 0" + SSpersistence.antag_rep -= C.ckey + else + var/prompt = "Please enter the amount of reputation to [operation]:" + + if(operation == "set") + prompt = "Please enter the new reputation value:" + + msg = input("Message:", prompt) as num|null + + if (!msg) + return + + var/ANTAG_REP_MAXIMUM = CONFIG_GET(number/antag_rep_maximum) + + if(operation == "set") + log_text = "Set to [num2text(msg)]" + SSpersistence.antag_rep[C.ckey] = max(0, min(msg, ANTAG_REP_MAXIMUM)) + else if(operation == "add") + log_text = "Added [num2text(msg)]" + SSpersistence.antag_rep[C.ckey] = min(SSpersistence.antag_rep[C.ckey]+msg, ANTAG_REP_MAXIMUM) + else if(operation == "subtract") + log_text = "Subtracted [num2text(msg)]" + SSpersistence.antag_rep[C.ckey] = max(SSpersistence.antag_rep[C.ckey]-msg, 0) + else + to_chat(src, "Invalid operation for antag rep modification: [operation] by user [key_name(usr)]", confidential = TRUE) + return + + if(SSpersistence.antag_rep[C.ckey] <= 0) + SSpersistence.antag_rep -= C.ckey + + log_admin("[key_name(usr)]: Modified [key_name(C)]'s antagonist reputation [log_text]") + message_admins("[key_name_admin(usr)]: Modified [key_name(C)]'s antagonist reputation ([log_text])") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Modify Antagonist Reputation") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_admin_world_narrate() + set category = "Admin - Events" + set name = "Global Narrate" + + if(!check_rights(R_ADMIN)) + return + + var/msg = input("Message:", text("Enter the text you wish to appear to everyone:")) as text|null + + if (!msg) + return + to_chat(world, "[msg]", confidential = TRUE) + log_admin("GlobalNarrate: [key_name(usr)] : [msg]") + message_admins("[key_name_admin(usr)] Sent a global narrate") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Global Narrate") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_admin_direct_narrate(mob/M) + set category = "Admin - Events" + set name = "Direct Narrate" + + if(!check_rights(R_ADMIN)) + return + + if(!M) + M = input("Direct narrate to whom?", "Active Players") as null|anything in GLOB.player_list + + if(!M) + return + + var/msg = input("Message:", text("Enter the text you wish to appear to your target:")) as text|null + + if( !msg ) + return + + to_chat(M, msg, confidential = TRUE) + log_admin("DirectNarrate: [key_name(usr)] to ([M.name]/[M.key]): [msg]") + msg = " DirectNarrate: [key_name(usr)] to ([M.name]/[M.key]): [msg]
                    " + message_admins(msg) + admin_ticket_log(M, msg) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Direct Narrate") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_admin_local_narrate(atom/A) + set category = "Admin - Events" + set name = "Local Narrate" + + if(!check_rights(R_ADMIN)) + return + if(!A) + return + var/range = input("Range:", "Narrate to mobs within how many tiles:", 7) as num|null + if(!range) + return + var/msg = input("Message:", text("Enter the text you wish to appear to everyone within view:")) as text|null + if (!msg) + return + for(var/mob/M in view(range,A)) + to_chat(M, msg, confidential = TRUE) + + log_admin("LocalNarrate: [key_name(usr)] at [AREACOORD(A)]: [msg]") + message_admins(" LocalNarrate: [key_name_admin(usr)] at [ADMIN_VERBOSEJMP(A)]: [msg]
                    ") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Local Narrate") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_admin_godmode(mob/M in GLOB.mob_list) + set category = "Admin - Game" + set name = "Godmode" + if(!check_rights(R_ADMIN)) + return + + M.status_flags ^= GODMODE + to_chat(usr, "Toggled [(M.status_flags & GODMODE) ? "ON" : "OFF"]", confidential = TRUE) + + log_admin("[key_name(usr)] has toggled [key_name(M)]'s nodamage to [(M.status_flags & GODMODE) ? "On" : "Off"]") + var/msg = "[key_name_admin(usr)] has toggled [ADMIN_LOOKUPFLW(M)]'s nodamage to [(M.status_flags & GODMODE) ? "On" : "Off"]" + message_admins(msg) + admin_ticket_log(M, msg) + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Godmode", "[M.status_flags & GODMODE ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + +/proc/cmd_admin_mute(whom, mute_type, automute = 0) + if(!whom) + return + + var/muteunmute + var/mute_string + var/feedback_string + switch(mute_type) + if(MUTE_IC) + mute_string = "IC (say and emote)" + feedback_string = "IC" + if(MUTE_OOC) + mute_string = "OOC" + feedback_string = "OOC" + if(MUTE_PRAY) + mute_string = "pray" + feedback_string = "Pray" + if(MUTE_ADMINHELP) + mute_string = "adminhelp, admin PM and ASAY" + feedback_string = "Adminhelp" + if(MUTE_MENTORHELP) + mute_string = "mentorhelp" + feedback_string = "Mentorhelp" + if(MUTE_DEADCHAT) + mute_string = "deadchat and DSAY" + feedback_string = "Deadchat" + if(MUTE_ALL) + mute_string = "everything" + feedback_string = "Everything" + else + return + + var/client/C + if(istype(whom, /client)) + C = whom + else if(istext(whom)) + C = GLOB.directory[whom] + else + return + + var/datum/preferences/P + if(C) + P = C.prefs + else + P = GLOB.preferences_datums[whom] + if(!P) + return + + if(automute) + if(!CONFIG_GET(flag/automute_on)) + return + else + if(!check_rights()) + return + + if(automute) + muteunmute = "auto-muted" + P.muted |= mute_type + log_admin("SPAM AUTOMUTE: [muteunmute] [key_name(whom)] from [mute_string]") + message_admins("SPAM AUTOMUTE: [muteunmute] [key_name_admin(whom)] from [mute_string].") + if(C) + to_chat(C, "You have been [muteunmute] from [mute_string] by the SPAM AUTOMUTE system. Contact an admin.", confidential = TRUE) + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Auto Mute [feedback_string]", "1")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + return + + if(P.muted & mute_type) + muteunmute = "unmuted" + P.muted &= ~mute_type + else + muteunmute = "muted" + P.muted |= mute_type + + log_admin("[key_name(usr)] has [muteunmute] [key_name(whom)] from [mute_string]") + message_admins("[key_name_admin(usr)] has [muteunmute] [key_name_admin(whom)] from [mute_string].") + if(C) + to_chat(C, "You have been [muteunmute] from [mute_string] by [key_name(usr, include_name = FALSE)].", confidential = TRUE) + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Mute [feedback_string]", "[P.muted & mute_type]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + +//I use this proc for respawn character too. /N +/proc/create_xeno(ckey) + if(!ckey) + var/list/candidates = list() + for(var/mob/M in GLOB.player_list) + if(M.stat != DEAD) + continue //we are not dead! + if(!(ROLE_ALIEN in M.client.prefs.be_special)) + continue //we don't want to be an alium + if(M.client.is_afk()) + continue //we are afk + if(M.mind && M.mind.current && M.mind.current.stat != DEAD) + continue //we have a live body we are tied to + candidates += M.ckey + if(candidates.len) + ckey = input("Pick the player you want to respawn as a xeno.", "Suitable Candidates") as null|anything in sortKey(candidates) + else + to_chat(usr, "Error: create_xeno(): no suitable candidates.", confidential = TRUE) + if(!istext(ckey)) + return 0 + + var/alien_caste = input(usr, "Please choose which caste to spawn.","Pick a caste",null) as null|anything in list("Queen","Praetorian","Hunter","Sentinel","Drone","Larva") + var/obj/effect/landmark/spawn_here = GLOB.xeno_spawn.len ? pick(GLOB.xeno_spawn) : null + var/mob/living/carbon/alien/new_xeno + switch(alien_caste) + if("Queen") + new_xeno = new /mob/living/carbon/alien/humanoid/royal/queen(spawn_here) + if("Praetorian") + new_xeno = new /mob/living/carbon/alien/humanoid/royal/praetorian(spawn_here) + if("Hunter") + new_xeno = new /mob/living/carbon/alien/humanoid/hunter(spawn_here) + if("Sentinel") + new_xeno = new /mob/living/carbon/alien/humanoid/sentinel(spawn_here) + if("Drone") + new_xeno = new /mob/living/carbon/alien/humanoid/drone(spawn_here) + if("Larva") + new_xeno = new /mob/living/carbon/alien/larva(spawn_here) + else + return 0 + if(!spawn_here) + SSjob.SendToLateJoin(new_xeno, FALSE) + + new_xeno.ckey = ckey + var/msg = "[key_name_admin(usr)] has spawned [ckey] as a filthy xeno [alien_caste]." + message_admins(msg) + admin_ticket_log(new_xeno, msg) + return 1 + +/* +If a guy was gibbed and you want to revive him, this is a good way to do so. +Works kind of like entering the game with a new character. Character receives a new mind if they didn't have one. +Traitors and the like can also be revived with the previous role mostly intact. +/N */ +/client/proc/respawn_character() + set category = "Admin - Game" + set name = "Respawn Character" + set desc = "Respawn a person that has been gibbed/dusted/killed. They must be a ghost for this to work and preferably should not have a body to go back into." + if(!check_rights(R_ADMIN)) + return + + var/input = ckey(input(src, "Please specify which key will be respawned.", "Key", "")) + if(!input) + return + + var/mob/dead/observer/G_found + for(var/mob/dead/observer/G in GLOB.player_list) + if(G.ckey == input) + G_found = G + break + + if(!G_found)//If a ghost was not found. + to_chat(usr, "There is no active key like that in the game or the person is not currently a ghost.", confidential = TRUE) + return + + if(G_found.mind && !G_found.mind.active) //mind isn't currently in use by someone/something + //Check if they were an alien + if(G_found.mind.assigned_role == ROLE_ALIEN) + if(alert("This character appears to have been an alien. Would you like to respawn them as such?",,"Yes","No")=="Yes") + var/turf/T + if(GLOB.xeno_spawn.len) + T = pick(GLOB.xeno_spawn) + + var/mob/living/carbon/alien/new_xeno + switch(G_found.mind.special_role)//If they have a mind, we can determine which caste they were. + if("Hunter") + new_xeno = new /mob/living/carbon/alien/humanoid/hunter(T) + if("Sentinel") + new_xeno = new /mob/living/carbon/alien/humanoid/sentinel(T) + if("Drone") + new_xeno = new /mob/living/carbon/alien/humanoid/drone(T) + if("Praetorian") + new_xeno = new /mob/living/carbon/alien/humanoid/royal/praetorian(T) + if("Queen") + new_xeno = new /mob/living/carbon/alien/humanoid/royal/queen(T) + else//If we don't know what special role they have, for whatever reason, or they're a larva. + create_xeno(G_found.ckey) + return + + if(!T) + SSjob.SendToLateJoin(new_xeno, FALSE) + + //Now to give them their mind back. + G_found.mind.transfer_to(new_xeno) //be careful when doing stuff like this! I've already checked the mind isn't in use + new_xeno.key = G_found.key + to_chat(new_xeno, "You have been fully respawned. Enjoy the game.", confidential = TRUE) + var/msg = "[key_name_admin(usr)] has respawned [new_xeno.key] as a filthy xeno." + message_admins(msg) + admin_ticket_log(new_xeno, msg) + return //all done. The ghost is auto-deleted + + //check if they were a monkey + else if(findtext(G_found.real_name,"monkey")) + if(alert("This character appears to have been a monkey. Would you like to respawn them as such?",,"Yes","No")=="Yes") + var/mob/living/carbon/monkey/new_monkey = new + SSjob.SendToLateJoin(new_monkey) + G_found.mind.transfer_to(new_monkey) //be careful when doing stuff like this! I've already checked the mind isn't in use + new_monkey.key = G_found.key + to_chat(new_monkey, "You have been fully respawned. Enjoy the game.", confidential = TRUE) + var/msg = "[key_name_admin(usr)] has respawned [new_monkey.key] as a filthy xeno." + message_admins(msg) + admin_ticket_log(new_monkey, msg) + return //all done. The ghost is auto-deleted + + + //Ok, it's not a xeno or a monkey. So, spawn a human. + var/mob/living/carbon/human/new_character = new//The mob being spawned. + SSjob.SendToLateJoin(new_character) + + var/datum/data/record/record_found //Referenced to later to either randomize or not randomize the character. + if(G_found.mind && !G_found.mind.active) //mind isn't currently in use by someone/something + /*Try and locate a record for the person being respawned through GLOB.data_core. + This isn't an exact science but it does the trick more often than not.*/ + var/id = md5("[G_found.real_name][G_found.mind.assigned_role]") + + record_found = find_record("id", id, GLOB.data_core.locked) + + if(record_found)//If they have a record we can determine a few things. + new_character.real_name = record_found.fields["name"] + new_character.gender = record_found.fields["gender"] + new_character.age = record_found.fields["age"] + new_character.hardset_dna(record_found.fields["identity"], record_found.fields["enzymes"], null, record_found.fields["name"], record_found.fields["blood_type"], new record_found.fields["species"], record_found.fields["features"]) + else + var/datum/preferences/A = new() + A.copy_to(new_character) + A.real_name = G_found.real_name + new_character.dna.update_dna_identity() + + new_character.name = new_character.real_name + + if(G_found.mind && !G_found.mind.active) + G_found.mind.transfer_to(new_character) //be careful when doing stuff like this! I've already checked the mind isn't in use + else + new_character.mind_initialize() + if(!new_character.mind.assigned_role) + new_character.mind.assigned_role = "Assistant"//If they somehow got a null assigned role. + + new_character.key = G_found.key + + /* + The code below functions with the assumption that the mob is already a traitor if they have a special role. + So all it does is re-equip the mob with powers and/or items. Or not, if they have no special role. + If they don't have a mind, they obviously don't have a special role. + */ + + //Two variables to properly announce later on. + var/admin = key_name_admin(src) + var/player_key = G_found.key + + //Now for special roles and equipment. + var/datum/antagonist/traitor/traitordatum = new_character.mind.has_antag_datum(/datum/antagonist/traitor) + if(traitordatum) + SSjob.EquipRank(new_character, new_character.mind.assigned_role, 1) + traitordatum.equip() + + + switch(new_character.mind.special_role) + if(ROLE_WIZARD) + new_character.forceMove(pick(GLOB.wizardstart)) + var/datum/antagonist/wizard/A = new_character.mind.has_antag_datum(/datum/antagonist/wizard,TRUE) + A.equip_wizard() + if(ROLE_SYNDICATE) + new_character.forceMove(pick(GLOB.nukeop_start)) + var/datum/antagonist/nukeop/N = new_character.mind.has_antag_datum(/datum/antagonist/nukeop,TRUE) + N.equip_op() + if(ROLE_NINJA) + var/list/ninja_spawn = list() + for(var/obj/effect/landmark/carpspawn/L in GLOB.landmarks_list) + ninja_spawn += L + var/datum/antagonist/ninja/ninjadatum = new_character.mind.has_antag_datum(/datum/antagonist/ninja) + ninjadatum.equip_space_ninja() + if(ninja_spawn.len) + new_character.forceMove(pick(ninja_spawn)) + + else//They may also be a cyborg or AI. + switch(new_character.mind.assigned_role) + if("Cyborg")//More rigging to make em' work and check if they're traitor. + new_character = new_character.Robotize(TRUE) + if("AI") + new_character = new_character.AIize() + else + SSjob.EquipRank(new_character, new_character.mind.assigned_role, 1)//Or we simply equip them. + + //Announces the character on all the systems, based on the record. + if(!issilicon(new_character))//If they are not a cyborg/AI. + if(!record_found&&new_character.mind.assigned_role!=new_character.mind.special_role)//If there are no records for them. If they have a record, this info is already in there. MODE people are not announced anyway. + //Power to the user! + if(alert(new_character,"Warning: No data core entry detected. Would you like to announce the arrival of this character by adding them to various databases, such as medical records?",,"No","Yes")=="Yes") + GLOB.data_core.manifest_inject(new_character) + + if(alert(new_character,"Would you like an active AI to announce this character?",,"No","Yes")=="Yes") + AnnounceArrival(new_character, new_character.mind.assigned_role) + + var/msg = "[admin] has respawned [player_key] as [new_character.real_name]." + message_admins(msg) + admin_ticket_log(new_character, msg) + + to_chat(new_character, "You have been fully respawned. Enjoy the game.", confidential = TRUE) + + SSblackbox.record_feedback("tally", "admin_verb", 1, "Respawn Character") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + return new_character + +/client/proc/cmd_admin_add_freeform_ai_law() + set category = "Admin - Events" + set name = "Add Custom AI law" + + if(!check_rights(R_ADMIN)) + return + + var/input = input(usr, "Please enter anything you want the AI to do. Anything. Serious.", "What?", "") as text|null + if(!input) + return + + log_admin("Admin [key_name(usr)] has added a new AI law - [input]") + message_admins("Admin [key_name_admin(usr)] has added a new AI law - [input]") + + var/show_log = alert(src, "Show ion message?", "Message", "Yes", "No") + var/announce_ion_laws = (show_log == "Yes" ? 100 : 0) + + var/datum/round_event/ion_storm/add_law_only/ion = new() + ion.announceChance = announce_ion_laws + ion.ionMessage = input + + SSblackbox.record_feedback("tally", "admin_verb", 1, "Add Custom AI Law") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_admin_rejuvenate(mob/living/M in GLOB.mob_list) + set category = "Debug" + set name = "Rejuvenate" + + if(!check_rights(R_ADMIN)) + return + + if(!mob) + return + if(!istype(M)) + alert("Cannot revive a ghost") + return + M.revive(full_heal = TRUE, admin_revive = TRUE) + + log_admin("[key_name(usr)] healed / revived [key_name(M)]") + var/msg = "Admin [key_name_admin(usr)] healed / revived [ADMIN_LOOKUPFLW(M)]!" + message_admins(msg) + admin_ticket_log(M, msg) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Rejuvinate") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_admin_create_centcom_report() + set category = "Admin - Events" + set name = "Create Command Report" + + if(!check_rights(R_ADMIN)) + return + + var/input = input(usr, "Enter a Command Report. Ensure it makes sense IC. Command's name is currently set to [command_name()].", "What?", "") as message|null + if(!input) + return + + var/confirm = alert(src, "Do you want to announce the contents of the report to the crew?", "Announce", "Yes", "No", "Cancel") + var/announce_command_report = TRUE + switch(confirm) + if("Yes") + priority_announce(input, null, 'sound/ai/commandreport.ogg') + announce_command_report = FALSE + if("Cancel") + return + + print_command_report(input, "[announce_command_report ? "Classified " : ""][command_name()] Update", announce_command_report) + + log_admin("[key_name(src)] has created a command report: [input]") + message_admins("[key_name_admin(src)] has created a command report") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Create Command Report") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_change_command_name() + set category = "Admin - Events" + set name = "Change Command Name" + + if(!check_rights(R_ADMIN)) + return + + var/input = input(usr, "Please input a new name for Central Command.", "What?", "") as text|null + if(!input) + return + change_command_name(input) + message_admins("[key_name_admin(src)] has changed Central Command's name to [input]") + log_admin("[key_name(src)] has changed the Central Command name to: [input]") + +/client/proc/cmd_admin_delete(atom/A as obj|mob|turf in world) + set category = "Debug" + set name = "Delete" + + if(!check_rights(R_SPAWN|R_DEBUG)) + return + + admin_delete(A) + +/client/proc/cmd_admin_list_open_jobs() + set category = "Admin - Game" + set name = "Manage Job Slots" + + if(!check_rights(R_ADMIN)) + return + holder.manage_free_slots() + SSblackbox.record_feedback("tally", "admin_verb", 1, "Manage Job Slots") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_admin_explosion(atom/O as obj|mob|turf in world) + set category = "Fun" + set name = "Explosion" + + if(!check_rights(R_ADMIN)) + return + + var/devastation = input("Range of total devastation. -1 to none", text("Input")) as num|null + if(devastation == null) + return + var/heavy = input("Range of heavy impact. -1 to none", text("Input")) as num|null + if(heavy == null) + return + var/light = input("Range of light impact. -1 to none", text("Input")) as num|null + if(light == null) + return + var/flash = input("Range of flash. -1 to none", text("Input")) as num|null + if(flash == null) + return + var/flames = input("Range of flames. -1 to none", text("Input")) as num|null + if(flames == null) + return + + if ((devastation != -1) || (heavy != -1) || (light != -1) || (flash != -1) || (flames != -1)) + if ((devastation > 20) || (heavy > 20) || (light > 20) || (flames > 20)) + if (alert(src, "Are you sure you want to do this? It will laaag.", "Confirmation", "Yes", "No") == "No") + return + + explosion(O, devastation, heavy, light, flash, null, null,flames) + log_admin("[key_name(usr)] created an explosion ([devastation],[heavy],[light],[flames]) at [AREACOORD(O)]") + message_admins("[key_name_admin(usr)] created an explosion ([devastation],[heavy],[light],[flames]) at [AREACOORD(O)]") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Explosion") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + return + else + return + +/client/proc/cmd_admin_emp(atom/O as obj|mob|turf in world) + set category = "Fun" + set name = "EM Pulse" + + if(!check_rights(R_ADMIN)) + return + + var/heavy = input("Range of heavy pulse.", text("Input")) as num|null + if(heavy == null) + return + var/light = input("Range of light pulse.", text("Input")) as num|null + if(light == null) + return + + if (heavy || light) + + empulse(O, heavy, light) + log_admin("[key_name(usr)] created an EM Pulse ([heavy],[light]) at [AREACOORD(O)]") + message_admins("[key_name_admin(usr)] created an EM Pulse ([heavy],[light]) at [AREACOORD(O)]") + SSblackbox.record_feedback("tally", "admin_verb", 1, "EM Pulse") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + return + else + return + +/client/proc/cmd_admin_gib(mob/M in GLOB.mob_list) + set category = "Fun" + set name = "Gib" + + if(!check_rights(R_ADMIN)) + return + + var/confirm = alert(src, "Drop a brain?", "Confirm", "Yes", "No","Cancel") + if(confirm == "Cancel") + return + //Due to the delay here its easy for something to have happened to the mob + if(!M) + return + + log_admin("[key_name(usr)] has gibbed [key_name(M)]") + message_admins("[key_name_admin(usr)] has gibbed [key_name_admin(M)]") + + if(isobserver(M)) + new /obj/effect/gibspawner/generic(get_turf(M)) + return + if(confirm == "Yes") + M.gib() + else + M.gib(1) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Gib") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_admin_gib_self() + set name = "Gibself" + set category = "Fun" + + var/confirm = alert(src, "You sure?", "Confirm", "Yes", "No") + if(confirm == "Yes") + log_admin("[key_name(usr)] used gibself.") + message_admins("[key_name_admin(usr)] used gibself.") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Gib Self") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + mob.gib(1, 1, 1) + +/client/proc/cmd_admin_check_contents(mob/living/M in GLOB.mob_list) + set category = "Debug" + set name = "Check Contents" + + var/list/L = M.get_contents() + for(var/t in L) + to_chat(usr, "[t]", confidential = TRUE) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Check Contents") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/toggle_view_range() + set category = "Admin - Game" + set name = "Change View Range" + set desc = "switches between 1x and custom views" + + if(view == CONFIG_GET(string/default_view)) + change_view(input("Select view range:", "FUCK YE", 7) in list(1,2,3,4,5,6,7,8,9,10,11,12,13,14,128)) + else + change_view(CONFIG_GET(string/default_view)) + + log_admin("[key_name(usr)] changed their view range to [view].") + //message_admins("\blue [key_name_admin(usr)] changed their view range to [view].") //why? removed by order of XSI + + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Change View Range", "[view]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/admin_call_shuttle() + + set category = "Admin - Events" + set name = "Call Shuttle" + + if(EMERGENCY_AT_LEAST_DOCKED) + return + + if(!check_rights(R_ADMIN)) + return + + var/confirm = alert(src, "You sure?", "Confirm", "Yes", "No") + if(confirm != "Yes") + return + + SSshuttle.emergency.request() + SSblackbox.record_feedback("tally", "admin_verb", 1, "Call Shuttle") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + log_admin("[key_name(usr)] admin-called the emergency shuttle.") + message_admins("[key_name_admin(usr)] admin-called the emergency shuttle.") + return + +/client/proc/admin_cancel_shuttle() + set category = "Admin - Events" + set name = "Cancel Shuttle" + if(!check_rights(0)) + return + if(alert(src, "You sure?", "Confirm", "Yes", "No") != "Yes") + return + + if(EMERGENCY_AT_LEAST_DOCKED) + return + + SSshuttle.emergency.cancel() + SSblackbox.record_feedback("tally", "admin_verb", 1, "Cancel Shuttle") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + log_admin("[key_name(usr)] admin-recalled the emergency shuttle.") + message_admins("[key_name_admin(usr)] admin-recalled the emergency shuttle.") + + return + +/client/proc/everyone_random() + set category = "Fun" + set name = "Make Everyone Random" + set desc = "Make everyone have a random appearance. You can only use this before rounds!" + + if(SSticker.HasRoundStarted()) + to_chat(usr, "Nope you can't do this, the game's already started. This only works before rounds!", confidential = TRUE) + return + + var/frn = CONFIG_GET(flag/force_random_names) + if(frn) + CONFIG_SET(flag/force_random_names, FALSE) + message_admins("Admin [key_name_admin(usr)] has disabled \"Everyone is Special\" mode.") + to_chat(usr, "Disabled.", confidential = TRUE) + return + + + var/notifyplayers = alert(src, "Do you want to notify the players?", "Options", "Yes", "No", "Cancel") + if(notifyplayers == "Cancel") + return + + log_admin("Admin [key_name(src)] has forced the players to have random appearances.") + message_admins("Admin [key_name_admin(usr)] has forced the players to have random appearances.") + + if(notifyplayers == "Yes") + to_chat(world, "Admin [usr.key] has forced the players to have completely random identities!", confidential = TRUE) + + to_chat(usr, "Remember: you can always disable the randomness by using the verb again, assuming the round hasn't started yet.", confidential = TRUE) + + CONFIG_SET(flag/force_random_names, TRUE) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Make Everyone Random") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + +/client/proc/toggle_random_events() + set category = "Server" + set name = "Toggle random events on/off" + set desc = "Toggles random events such as meteors, black holes, blob (but not space dust) on/off" + var/new_are = !CONFIG_GET(flag/allow_random_events) + CONFIG_SET(flag/allow_random_events, new_are) + if(new_are) + to_chat(usr, "Random events enabled", confidential = TRUE) + message_admins("Admin [key_name_admin(usr)] has enabled random events.") + else + to_chat(usr, "Random events disabled", confidential = TRUE) + message_admins("Admin [key_name_admin(usr)] has disabled random events.") + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Random Events", "[new_are ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + +/client/proc/admin_change_sec_level() + set category = "Admin - Events" + set name = "Set Security Level" + set desc = "Changes the security level. Announcement only, i.e. setting to Delta won't activate nuke" + + if(!check_rights(R_ADMIN)) + return + + var/level = input("Select security level to change to","Set Security Level") as null|anything in list("green","blue","red","delta") + if(level) + set_security_level(level) + + log_admin("[key_name(usr)] changed the security level to [level]") + message_admins("[key_name_admin(usr)] changed the security level to [level]") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Set Security Level [capitalize(level)]") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/toggle_nuke(obj/machinery/nuclearbomb/N in GLOB.nuke_list) + set name = "Toggle Nuke" + set category = "Admin - Events" + set popup_menu = 0 + if(!check_rights(R_DEBUG)) + return + + if(!N.timing) + var/newtime = input(usr, "Set activation timer.", "Activate Nuke", "[N.timer_set]") as num|null + if(!newtime) + return + N.timer_set = newtime + N.set_safety() + N.set_active() + + log_admin("[key_name(usr)] [N.timing ? "activated" : "deactivated"] a nuke at [AREACOORD(N)].") + message_admins("[ADMIN_LOOKUPFLW(usr)] [N.timing ? "activated" : "deactivated"] a nuke at [ADMIN_VERBOSEJMP(N)].") + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Nuke", "[N.timing]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/toggle_combo_hud() + set category = "Admin - Game" + set name = "Toggle Combo HUD" + set desc = "Toggles the Admin Combo HUD (antag, sci, med, eng)" + + if(!check_rights(R_ADMIN)) + return + + var/adding_hud = !has_antag_hud() + + for(var/hudtype in list(DATA_HUD_SECURITY_ADVANCED, DATA_HUD_MEDICAL_ADVANCED, DATA_HUD_DIAGNOSTIC_ADVANCED)) // add data huds + var/datum/atom_hud/H = GLOB.huds[hudtype] + (adding_hud) ? H.add_hud_to(usr) : H.remove_hud_from(usr) + for(var/datum/atom_hud/antag/H in GLOB.huds) // add antag huds + (adding_hud) ? H.add_hud_to(usr) : H.remove_hud_from(usr) + + if(prefs.toggles & COMBOHUD_LIGHTING) + if(adding_hud) + mob.lighting_alpha = LIGHTING_PLANE_ALPHA_INVISIBLE + else + mob.lighting_alpha = initial(mob.lighting_alpha) + + mob.update_sight() + + to_chat(usr, "You toggled your admin combo HUD [adding_hud ? "ON" : "OFF"].", confidential = TRUE) + message_admins("[key_name_admin(usr)] toggled their admin combo HUD [adding_hud ? "ON" : "OFF"].") + log_admin("[key_name(usr)] toggled their admin combo HUD [adding_hud ? "ON" : "OFF"].") + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Combo HUD", "[adding_hud ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + +/client/proc/has_antag_hud() + var/datum/atom_hud/A = GLOB.huds[ANTAG_HUD_TRAITOR] + return A.hudusers[mob] + + +/client/proc/run_weather() + set category = "Admin - Events" + set name = "Run Weather" + set desc = "Triggers a weather on the z-level you choose." + + if(!holder) + return + + var/weather_type = input("Choose a weather", "Weather") as null|anything in sortList(subtypesof(/datum/weather), /proc/cmp_typepaths_asc) + if(!weather_type) + return + + var/turf/T = get_turf(mob) + var/z_level = input("Z-Level to target?", "Z-Level", T?.z) as num|null + if(!isnum(z_level)) + return + + SSweather.run_weather(weather_type, z_level) + + message_admins("[key_name_admin(usr)] started weather of type [weather_type] on the z-level [z_level].") + log_admin("[key_name(usr)] started weather of type [weather_type] on the z-level [z_level].") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Run Weather") + +/client/proc/mass_zombie_infection() + set category = "Fun" + set name = "Mass Zombie Infection" + set desc = "Infects all humans with a latent organ that will zombify \ + them on death." + + if(!check_rights(R_ADMIN)) + return + + var/confirm = alert(src, "Please confirm you want to add latent zombie organs in all humans?", "Confirm Zombies", "Yes", "No") + if(confirm != "Yes") + return + + for(var/i in GLOB.human_list) + var/mob/living/carbon/human/H = i + new /obj/item/organ/zombie_infection/nodamage(H) + + message_admins("[key_name_admin(usr)] added a latent zombie infection to all humans.") + log_admin("[key_name(usr)] added a latent zombie infection to all humans.") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Mass Zombie Infection") + +/client/proc/mass_zombie_cure() + set category = "Fun" + set name = "Mass Zombie Cure" + set desc = "Removes the zombie infection from all humans, returning them to normal." + if(!check_rights(R_ADMIN)) + return + + var/confirm = alert(src, "Please confirm you want to cure all zombies?", "Confirm Zombie Cure", "Yes", "No") + if(confirm != "Yes") + return + + for(var/obj/item/organ/zombie_infection/nodamage/I in GLOB.zombie_infection_list) + qdel(I) + + message_admins("[key_name_admin(usr)] cured all zombies.") + log_admin("[key_name(usr)] cured all zombies.") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Mass Zombie Cure") + +/client/proc/polymorph_all() + set category = "Fun" + set name = "Polymorph All" + set desc = "Applies the effects of the bolt of change to every single mob." + + if(!check_rights(R_ADMIN)) + return + + var/confirm = alert(src, "Please confirm you want polymorph all mobs?", "Confirm Polymorph", "Yes", "No") + if(confirm != "Yes") + return + + var/list/mobs = shuffle(GLOB.alive_mob_list.Copy()) // might change while iterating + var/who_did_it = key_name_admin(usr) + + message_admins("[key_name_admin(usr)] started polymorphed all living mobs.") + log_admin("[key_name(usr)] polymorphed all living mobs.") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Polymorph All") + + for(var/mob/living/M in mobs) + CHECK_TICK + + if(!M) + continue + + M.audible_message("...wabbajack...wabbajack...") + playsound(M.loc, 'sound/magic/staff_change.ogg', 50, TRUE, -1) + + wabbajack(M) + + message_admins("Mass polymorph started by [who_did_it] is complete.") + + +/client/proc/show_tip() + set category = "Admin" + set name = "Show Tip" + set desc = "Sends a tip (that you specify) to all players. After all \ + you're the experienced player here." + + if(!check_rights(R_ADMIN)) + return + + var/input = input(usr, "Please specify your tip that you want to send to the players.", "Tip", "") as message|null + if(!input) + return + + if(!SSticker) + return + + SSticker.selected_tip = input + + // If we've already tipped, then send it straight away. + if(SSticker.tipped) + SSticker.send_tip_of_the_round() + + + message_admins("[key_name_admin(usr)] sent a tip of the round.") + log_admin("[key_name(usr)] sent \"[input]\" as the Tip of the Round.") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Tip") + +/client/proc/modify_goals() + set category = "Debug" + set name = "Modify goals" + + if(!check_rights(R_ADMIN)) + return + + holder.modify_goals() + +/datum/admins/proc/modify_goals() + var/dat = "" + for(var/datum/station_goal/S in SSticker.mode.station_goals) + dat += "[S.name] - Announce | Remove
                    " + dat += "
                    Add New Goal" + usr << browse(dat, "window=goals;size=400x400") + +/proc/immerse_player(mob/living/carbon/target, toggle=TRUE, remove=FALSE) + var/list/immersion_components = list(/datum/component/manual_breathing, /datum/component/manual_blinking) + + for(var/immersies in immersion_components) + var/has_component = target.GetComponent(immersies) + + if(has_component && (toggle || remove)) + qdel(has_component) + else if(toggle || !remove) + target.AddComponent(immersies) + +/proc/mass_immerse(remove=FALSE) + for(var/mob/living/carbon/M in GLOB.mob_list) + immerse_player(M, toggle=FALSE, remove=remove) + +/client/proc/toggle_hub() + set category = "Server" + set name = "Toggle Hub" + + world.update_hub_visibility(!GLOB.hub_visibility) + + log_admin("[key_name(usr)] has toggled the server's hub status for the round, it is now [(GLOB.hub_visibility?"on":"off")] the hub.") + message_admins("[key_name_admin(usr)] has toggled the server's hub status for the round, it is now [(GLOB.hub_visibility?"on":"off")] the hub.") + if (GLOB.hub_visibility && !world.reachable) + message_admins("WARNING: The server will not show up on the hub because byond is detecting that a filewall is blocking incoming connections.") + + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggled Hub Visibility", "[GLOB.hub_visibility ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/smite(mob/living/target as mob) + set name = "Smite" + set category = "Fun" + if(!check_rights(R_ADMIN) || !check_rights(R_FUN)) + return + + var/list/punishment_list = list(ADMIN_PUNISHMENT_BREAK_BONES, ADMIN_PUNISHMENT_LIGHTNING, ADMIN_PUNISHMENT_BRAINDAMAGE, ADMIN_PUNISHMENT_GIB, ADMIN_PUNISHMENT_BSA, ADMIN_PUNISHMENT_FIREBALL, ADMIN_PUNISHMENT_ROD, ADMIN_PUNISHMENT_SUPPLYPOD_QUICK, ADMIN_PUNISHMENT_SUPPLYPOD, ADMIN_PUNISHMENT_MAZING, ADMIN_PUNISHMENT_IMMERSE) + + var/punishment = input("Choose a punishment", "DIVINE SMITING") as null|anything in sortList(punishment_list) + + if(QDELETED(target) || !punishment) + return + + switch(punishment) + if(ADMIN_PUNISHMENT_BREAK_BONES) + if(iscarbon(target)) + var/mob/living/carbon/C = target + C.break_all_bones() + if(ADMIN_PUNISHMENT_LIGHTNING) + var/turf/T = get_step(get_step(target, NORTH), NORTH) + T.Beam(target, icon_state="lightning[rand(1,12)]", time = 5) + target.adjustFireLoss(75) + if(ishuman(target)) + var/mob/living/carbon/human/H = target + H.electrocution_animation(40) + to_chat(target, "The gods have punished you for your sins!", confidential = TRUE) + if(ADMIN_PUNISHMENT_BRAINDAMAGE) + target.adjustOrganLoss(ORGAN_SLOT_BRAIN, 199, 199) + if(ADMIN_PUNISHMENT_GIB) + target.gib(FALSE) + if(ADMIN_PUNISHMENT_BSA) + bluespace_artillery(target) + if(ADMIN_PUNISHMENT_FIREBALL) + new /obj/effect/temp_visual/target(get_turf(target)) + if(ADMIN_PUNISHMENT_ROD) + var/turf/T = get_turf(target) + var/startside = pick(GLOB.cardinals) + var/turf/startT = spaceDebrisStartLoc(startside, T.z) + var/turf/endT = spaceDebrisFinishLoc(startside, T.z) + new /obj/effect/immovablerod(startT, endT,target) + if(ADMIN_PUNISHMENT_SUPPLYPOD_QUICK) + var/target_path = input(usr,"Enter typepath of an atom you'd like to send with the pod (type \"empty\" to send an empty pod):" ,"Typepath","/obj/item/reagent_containers/food/snacks/grown/harebell") as null|text + var/obj/structure/closet/supplypod/centcompod/pod = new() + pod.damage = 40 + pod.explosionSize = list(0,0,0,2) + pod.effectStun = TRUE + if (isnull(target_path)) //The user pressed "Cancel" + return + if (target_path != "empty")//if you didn't type empty, we want to load the pod with a delivery + var/delivery = text2path(target_path) + if(!ispath(delivery)) + delivery = pick_closest_path(target_path) + if(!delivery) + alert("ERROR: Incorrect / improper path given.") + return + new delivery(pod) + new /obj/effect/DPtarget(get_turf(target), pod) + if(ADMIN_PUNISHMENT_SUPPLYPOD) + var/datum/centcom_podlauncher/plaunch = new(usr) + if(!holder) + return + plaunch.specificTarget = target + plaunch.launchChoice = 0 + plaunch.damageChoice = 1 + plaunch.explosionChoice = 1 + plaunch.temp_pod.damage = 40//bring the mother fuckin ruckus + plaunch.temp_pod.explosionSize = list(0,0,0,2) + plaunch.temp_pod.effectStun = TRUE + plaunch.ui_interact(usr) + return //We return here because punish_log() is handled by the centcom_podlauncher datum + if(ADMIN_PUNISHMENT_MAZING) + if(!puzzle_imprison(target)) + to_chat(usr,"Imprisonment failed!", confidential = TRUE) + return + if(ADMIN_PUNISHMENT_IMMERSE) + immerse_player(target) + + punish_log(target, punishment) + +/client/proc/punish_log(whom, punishment) + var/msg = "[key_name_admin(usr)] punished [key_name_admin(whom)] with [punishment]." + message_admins(msg) + admin_ticket_log(whom, msg) + log_admin("[key_name(usr)] punished [key_name(whom)] with [punishment].") + +/client/proc/trigger_centcom_recall() + if(!check_rights(R_ADMIN)) + return + var/message = pick(GLOB.admiral_messages) + message = input("Enter message from the on-call admiral to be put in the recall report.", "Admiral Message", message) as text|null + + if(!message) + return + + message_admins("[key_name_admin(usr)] triggered a CentCom recall, with the admiral message of: [message]") + log_game("[key_name(usr)] triggered a CentCom recall, with the message of: [message]") + SSshuttle.centcom_recall(SSshuttle.emergency.timer, message) + +/client/proc/cmd_admin_check_player_exp() //Allows admins to determine who the newer players are. + set category = "Admin" + set name = "Player Playtime" + if(!check_rights(R_ADMIN)) + return + + if(!CONFIG_GET(flag/use_exp_tracking)) + to_chat(usr, "Tracking is disabled in the server configuration file.", confidential = TRUE) + return + + var/list/msg = list() + msg += "Playtime ReportPlaytime:
                    " + src << browse(msg.Join(), "window=Player_playtime_check") + +/datum/admins/proc/cmd_show_exp_panel(client/C) + if(!check_rights(R_ADMIN)) + return + if(!C) + 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.", 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") + +/datum/admins/proc/toggle_exempt_status(client/C) + if(!check_rights(R_ADMIN)) + return + if(!C) + to_chat(usr, "ERROR: Client not found.", confidential = TRUE) + return + + if(!C.set_db_player_flags()) + to_chat(usr, "ERROR: Unable read player flags from database. Please check logs.", confidential = TRUE) + var/dbflags = C.prefs.db_flags + var/newstate = FALSE + if(dbflags & DB_FLAG_EXEMPT) + newstate = FALSE + else + newstate = TRUE + + if(C.update_flag_db(DB_FLAG_EXEMPT, newstate)) + to_chat(usr, "ERROR: Unable to update player flags. Please check logs.", confidential = TRUE) + else + message_admins("[key_name_admin(usr)] has [newstate ? "activated" : "deactivated"] job exp exempt status on [key_name_admin(C)]") + log_admin("[key_name(usr)] has [newstate ? "activated" : "deactivated"] job exp exempt status on [key_name(C)]") + +/// Allow admin to add or remove traits of datum +/datum/admins/proc/modify_traits(datum/D) + if(!D) + return + + var/add_or_remove = input("Remove/Add?", "Trait Remove/Add") as null|anything in list("Add","Remove") + if(!add_or_remove) + return + var/list/availible_traits = list() + + switch(add_or_remove) + if("Add") + for(var/key in GLOB.traits_by_type) + if(istype(D,key)) + availible_traits += GLOB.traits_by_type[key] + if("Remove") + if(!GLOB.trait_name_map) + GLOB.trait_name_map = generate_trait_name_map() + for(var/trait in D.status_traits) + var/name = GLOB.trait_name_map[trait] || trait + availible_traits[name] = trait + + var/chosen_trait = input("Select trait to modify", "Trait") as null|anything in sortList(availible_traits) + if(!chosen_trait) + return + chosen_trait = availible_traits[chosen_trait] + + var/source = "adminabuse" + switch(add_or_remove) + if("Add") //Not doing source choosing here intentionally to make this bit faster to use, you can always vv it. + ADD_TRAIT(D,chosen_trait,source) + if("Remove") + var/specific = input("All or specific source ?", "Trait Remove/Add") as null|anything in list("All","Specific") + if(!specific) + return + switch(specific) + if("All") + source = null + if("Specific") + source = input("Source to be removed","Trait Remove/Add") as null|anything in sortList(D.status_traits[chosen_trait]) + if(!source) + return + REMOVE_TRAIT(D,chosen_trait,source) diff --git a/code/modules/admin/verbs/shuttlepanel.dm b/code/modules/admin/verbs/shuttlepanel.dm index 0b9bb0fabca3..f90dc049ce67 100644 --- a/code/modules/admin/verbs/shuttlepanel.dm +++ b/code/modules/admin/verbs/shuttlepanel.dm @@ -1,76 +1,76 @@ -/datum/admins/proc/open_shuttlepanel() - set category = "Admin - Events" - set name = "Shuttle Manipulator" - set desc = "Opens the shuttle manipulator UI." - - if(!check_rights(R_ADMIN)) - return - - SSshuttle.ui_interact(usr) - - -/obj/docking_port/mobile/proc/admin_fly_shuttle(mob/user) - var/list/options = list() - - for(var/port in SSshuttle.stationary) - if (istype(port, /obj/docking_port/stationary/transit)) - continue // please don't do this - var/obj/docking_port/stationary/S = port - if (canDock(S) == SHUTTLE_CAN_DOCK) - options[S.name || S.id] = S - - options += "--------" - options += "Infinite Transit" - options += "Delete Shuttle" - options += "Into The Sunset (delete & greentext 'escape')" - - var/selection = input(user, "Select where to fly [name || id]:", "Fly Shuttle") as null|anything in options - if(!selection) - return - - switch(selection) - if("Infinite Transit") - destination = null - mode = SHUTTLE_IGNITING - setTimer(ignitionTime) - - if("Delete Shuttle") - if(alert(user, "Really delete [name || id]?", "Delete Shuttle", "Cancel", "Really!") != "Really!") - return - jumpToNullSpace() - - if("Into The Sunset (delete & greentext 'escape')") - if(alert(user, "Really delete [name || id] and greentext escape objectives?", "Delete Shuttle", "Cancel", "Really!") != "Really!") - return - intoTheSunset() - - else - if(options[selection]) - request(options[selection]) - -/obj/docking_port/mobile/emergency/admin_fly_shuttle(mob/user) - return // use the existing verbs for this - -/obj/docking_port/mobile/arrivals/admin_fly_shuttle(mob/user) - switch(alert(user, "Would you like to fly the arrivals shuttle once or change its destination?", "Fly Shuttle", "Fly", "Retarget", "Cancel")) - if("Cancel") - return - if("Fly") - return ..() - - var/list/options = list() - - for(var/port in SSshuttle.stationary) - if (istype(port, /obj/docking_port/stationary/transit)) - continue // please don't do this - var/obj/docking_port/stationary/S = port - if (canDock(S) == SHUTTLE_CAN_DOCK) - options[S.name || S.id] = S - - var/selection = input(user, "Select the new arrivals destination:", "Fly Shuttle") as null|anything in options - if(!selection) - return - target_dock = options[selection] - if(!QDELETED(target_dock)) - destination = target_dock - +/datum/admins/proc/open_shuttlepanel() + set category = "Admin - Events" + set name = "Shuttle Manipulator" + set desc = "Opens the shuttle manipulator UI." + + if(!check_rights(R_ADMIN)) + return + + SSshuttle.ui_interact(usr) + + +/obj/docking_port/mobile/proc/admin_fly_shuttle(mob/user) + var/list/options = list() + + for(var/port in SSshuttle.stationary) + if (istype(port, /obj/docking_port/stationary/transit)) + continue // please don't do this + var/obj/docking_port/stationary/S = port + if (canDock(S) == SHUTTLE_CAN_DOCK) + options[S.name || S.id] = S + + options += "--------" + options += "Infinite Transit" + options += "Delete Shuttle" + options += "Into The Sunset (delete & greentext 'escape')" + + var/selection = input(user, "Select where to fly [name || id]:", "Fly Shuttle") as null|anything in options + if(!selection) + return + + switch(selection) + if("Infinite Transit") + destination = null + mode = SHUTTLE_IGNITING + setTimer(ignitionTime) + + if("Delete Shuttle") + if(alert(user, "Really delete [name || id]?", "Delete Shuttle", "Cancel", "Really!") != "Really!") + return + jumpToNullSpace() + + if("Into The Sunset (delete & greentext 'escape')") + if(alert(user, "Really delete [name || id] and greentext escape objectives?", "Delete Shuttle", "Cancel", "Really!") != "Really!") + return + intoTheSunset() + + else + if(options[selection]) + request(options[selection]) + +/obj/docking_port/mobile/emergency/admin_fly_shuttle(mob/user) + return // use the existing verbs for this + +/obj/docking_port/mobile/arrivals/admin_fly_shuttle(mob/user) + switch(alert(user, "Would you like to fly the arrivals shuttle once or change its destination?", "Fly Shuttle", "Fly", "Retarget", "Cancel")) + if("Cancel") + return + if("Fly") + return ..() + + var/list/options = list() + + for(var/port in SSshuttle.stationary) + if (istype(port, /obj/docking_port/stationary/transit)) + continue // please don't do this + var/obj/docking_port/stationary/S = port + if (canDock(S) == SHUTTLE_CAN_DOCK) + options[S.name || S.id] = S + + var/selection = input(user, "Select the new arrivals destination:", "Fly Shuttle") as null|anything in options + if(!selection) + return + target_dock = options[selection] + if(!QDELETED(target_dock)) + destination = target_dock + diff --git a/code/modules/admin/verbs/tripAI.dm b/code/modules/admin/verbs/tripAI.dm index 38221c386613..5efa306e317f 100644 --- a/code/modules/admin/verbs/tripAI.dm +++ b/code/modules/admin/verbs/tripAI.dm @@ -1,15 +1,15 @@ -/client/proc/triple_ai() - set category = "Admin - Events" - set name = "Toggle AI Triumvirate" - - if(SSticker.current_state > GAME_STATE_PREGAME) - to_chat(usr, "This option is currently only usable during pregame. This may change at a later date.", confidential = TRUE) - return - - var/datum/job/job = SSjob.GetJob("AI") - if(!job) - to_chat(usr, "Unable to locate the AI job", confidential = TRUE) - return - SSticker.triai = !SSticker.triai - to_chat(usr, "There will [SSticker.triai ? "" : "not"] be an AI Triumvirate at round start.") - message_admins("[key_name_admin(usr)] has toggled [SSticker.triai ? "on" : "off"] triple AIs at round start.") +/client/proc/triple_ai() + set category = "Admin - Events" + set name = "Toggle AI Triumvirate" + + if(SSticker.current_state > GAME_STATE_PREGAME) + to_chat(usr, "This option is currently only usable during pregame. This may change at a later date.", confidential = TRUE) + return + + var/datum/job/job = SSjob.GetJob("AI") + if(!job) + to_chat(usr, "Unable to locate the AI job", confidential = TRUE) + return + SSticker.triai = !SSticker.triai + to_chat(usr, "There will [SSticker.triai ? "" : "not"] be an AI Triumvirate at round start.") + message_admins("[key_name_admin(usr)] has toggled [SSticker.triai ? "on" : "off"] triple AIs at round start.") diff --git a/code/modules/admin/view_variables/reference_tracking.dm b/code/modules/admin/view_variables/reference_tracking.dm new file mode 100644 index 000000000000..bd8ce48a43f7 --- /dev/null +++ b/code/modules/admin/view_variables/reference_tracking.dm @@ -0,0 +1,226 @@ +#ifdef REFERENCE_TRACKING + +GLOBAL_LIST_EMPTY(deletion_failures) + +/world/proc/enable_reference_tracking() + var/extools = world.GetConfig("env", "EXTOOLS_DLL") || (world.system_type == MS_WINDOWS ? "./byond-extools.dll" : "./libbyond-extools.so") + if (fexists(extools)) + call(extools, "ref_tracking_initialize")() + +/proc/get_back_references(datum/D) + CRASH("/proc/get_back_references not hooked by extools, reference tracking will not function!") + +/proc/get_forward_references(datum/D) + CRASH("/proc/get_forward_references not hooked by extools, reference tracking will not function!") + +/proc/clear_references(datum/D) + return + +/datum/admins/proc/view_refs(atom/D in world) //it actually supports datums as well but byond no likey + set category = "Debug" + set name = "View References" + + if(!check_rights(R_DEBUG) || !D) + return + + var/list/backrefs = get_back_references(D) + if(isnull(backrefs)) + var/datum/browser/popup = new(usr, "ref_view", "
                    Error
                    ") + popup.set_content("Reference tracking not enabled") + popup.open(FALSE) + return + + var/list/frontrefs = get_forward_references(D) + var/list/dat = list() + dat += "

                    References of \ref[D] - [D]


                    \[Refresh\]
                    " + dat += "

                    Back references - these things hold references to this object.

                    " + dat += "" + dat += "" + for(var/ref in backrefs) + var/datum/backreference = ref + if(isnull(backreference)) + dat += "" + if(istype(backreference)) + dat += "" + else if(islist(backreference)) + dat += "" + else + dat += "" + dat += "
                    RefTypeVariable NameFollow
                    GC'd Reference
                    [REF(backreference)][backreference.type][backrefs[backreference]]\[Follow\]
                    [REF(backreference)]list[backrefs[backreference]]\[Follow\]
                    Weird reference type. Add more debugging checks.

                    " + dat += "

                    Forward references - this object is referencing those things.

                    " + dat += "" + dat += "" + for(var/ref in frontrefs) + var/datum/backreference = frontrefs[ref] + dat += "" + dat += "
                    Variable nameRefTypeFollow
                    [ref][REF(backreference)][backreference.type]\[Follow\]

                    " + dat = dat.Join() + + var/datum/browser/popup = new(usr, "ref_view", "
                    References of \ref[D]
                    ") + popup.set_content(dat) + popup.open(FALSE) + + +/datum/admins/proc/view_del_failures() + set category = "Debug" + set name = "View Deletion Failures" + + if(!check_rights(R_DEBUG)) + return + + var/list/dat = list("") + for(var/t in GLOB.deletion_failures) + if(isnull(t)) + dat += "" + continue + var/datum/thing = t + dat += "" + dat += "
                    GC'd Reference | Clear Nulls
                    \ref[thing] | [thing.type][thing.gc_destroyed ? " (destroyed)" : ""] [ADMIN_VV(thing)]

                    " + dat = dat.Join() + + var/datum/browser/popup = new(usr, "del_failures", "
                    Deletion Failures
                    ") + popup.set_content(dat) + popup.open(FALSE) + + +/datum/proc/find_references() + testing("Beginning search for references to a [type].") + var/list/backrefs = get_back_references(src) + for(var/ref in backrefs) + if(isnull(ref)) + log_world("## TESTING: Datum reference found, but gone now.") + continue + if(islist(ref)) + log_world("## TESTING: Found [type] \ref[src] in list.") + continue + var/datum/datum_ref = ref + if(!istype(datum_ref)) + log_world("## TESTING: Found [type] \ref[src] in unknown type reference: [datum_ref].") + return + log_world("## TESTING: Found [type] \ref[src] in [datum_ref.type][datum_ref.gc_destroyed ? " (destroyed)" : ""]") + message_admins("Found [type] \ref[src] [ADMIN_VV(src)] in [datum_ref.type][datum_ref.gc_destroyed ? " (destroyed)" : ""] [ADMIN_VV(datum_ref)]") + testing("Completed search for references to a [type].") + +#endif + +#ifdef LEGACY_REFERENCE_TRACKING + +/datum/verb/legacy_find_refs() + set category = "Debug" + set name = "Find References" + set src in world + + find_references(FALSE) + + +/datum/proc/find_references_legacy(skip_alert) + running_find_references = type + if(usr?.client) + if(usr.client.running_find_references) + testing("CANCELLED search for references to a [usr.client.running_find_references].") + usr.client.running_find_references = null + running_find_references = null + //restart the garbage collector + SSgarbage.can_fire = TRUE + SSgarbage.next_fire = world.time + world.tick_lag + return + + if(!skip_alert && alert("Running this will lock everything up for about 5 minutes. Would you like to begin the search?", "Find References", "Yes", "No") != "Yes") + running_find_references = null + return + + //this keeps the garbage collector from failing to collect objects being searched for in here + SSgarbage.can_fire = FALSE + + if(usr?.client) + usr.client.running_find_references = type + + testing("Beginning search for references to a [type].") + last_find_references = world.time + + DoSearchVar(GLOB) //globals + for(var/datum/thing in world) //atoms (don't beleive its lies) + DoSearchVar(thing, "World -> [thing]") + + for(var/datum/thing) //datums + DoSearchVar(thing, "World -> [thing]") + + for(var/client/thing) //clients + DoSearchVar(thing, "World -> [thing]") + + testing("Completed search for references to a [type].") + if(usr?.client) + usr.client.running_find_references = null + running_find_references = null + + //restart the garbage collector + SSgarbage.can_fire = TRUE + SSgarbage.next_fire = world.time + world.tick_lag + + +/datum/verb/qdel_then_find_references() + set category = "Debug" + set name = "qdel() then Find References" + set src in world + + qdel(src, TRUE) //force a qdel + if(!running_find_references) + find_references(TRUE) + + +/datum/verb/qdel_then_if_fail_find_references() + set category = "Debug" + set name = "qdel() then Find References if GC failure" + set src in world + + qdel_and_find_ref_if_fail(src, TRUE) + + +/datum/proc/DoSearchVar(potential_container, container_name, recursive_limit = 64) + if(usr?.client && !usr.client.running_find_references) + return + + if(!recursive_limit) + return + + if(istype(potential_container, /datum)) + var/datum/datum_container = potential_container + if(datum_container.last_find_references == last_find_references) + return + + datum_container.last_find_references = last_find_references + var/list/vars_list = datum_container.vars + + for(var/varname in vars_list) + if (varname == "vars") + continue + var/variable = vars_list[varname] + + if(variable == src) + testing("Found [type] \ref[src] in [datum_container.type]'s [varname] var. [container_name]") + + else if(islist(variable)) + DoSearchVar(variable, "[container_name] -> list", recursive_limit - 1) + + else if(islist(potential_container)) + var/normal = IS_NORMAL_LIST(potential_container) + for(var/element_in_list in potential_container) + if(element_in_list == src) + testing("Found [type] \ref[src] in list [container_name].") + + else if(element_in_list && !isnum(element_in_list) && normal && potential_container[element_in_list] == src) + testing("Found [type] \ref[src] in list [container_name]\[[element_in_list]\]") + + else if(islist(element_in_list)) + DoSearchVar(element_in_list, "[container_name] -> list", recursive_limit - 1) + + #ifndef FIND_REF_NO_CHECK_TICK + CHECK_TICK + #endif + + +/proc/qdel_and_find_ref_if_fail(datum/thing_to_del, force = FALSE) + SSgarbage.reference_find_on_fail[REF(thing_to_del)] = TRUE + qdel(thing_to_del, force) + +#endif diff --git a/code/modules/admin/view_variables/topic_basic.dm b/code/modules/admin/view_variables/topic_basic.dm index 0e27f46f8323..4949139c658d 100644 --- a/code/modules/admin/view_variables/topic_basic.dm +++ b/code/modules/admin/view_variables/topic_basic.dm @@ -45,6 +45,18 @@ usr.client.admin_delete(target) if (isturf(src)) // show the turf that took its place usr.client.debug_variables(src) + return + + #ifdef REFERENCE_TRACKING + if(href_list[VV_HK_VIEW_REFERENCES]) + var/datum/D = locate(href_list[VV_HK_TARGET]) + if(!D) + to_chat(usr, "Unable to locate item.") + return + usr.client.holder.view_refs(target) + return + #endif + if(href_list[VV_HK_MARK]) usr.client.mark_datum(target) if(href_list[VV_HK_ADDCOMPONENT]) @@ -77,3 +89,4 @@ message_admins("[key_name_admin(usr)] has added [result] [datumname] to [key_name_admin(src)].") if(href_list[VV_HK_CALLPROC]) usr.client.callproc_datum(target) + diff --git a/code/modules/admin/view_variables/view_variables.dm b/code/modules/admin/view_variables/view_variables.dm index 472ee5271d45..62d9ff62fb98 100644 --- a/code/modules/admin/view_variables/view_variables.dm +++ b/code/modules/admin/view_variables/view_variables.dm @@ -59,6 +59,7 @@ "Set len" = VV_HREF_TARGETREF_INTERNAL(refid, VV_HK_LIST_SET_LENGTH), "Shuffle" = VV_HREF_TARGETREF_INTERNAL(refid, VV_HK_LIST_SHUFFLE), "Show VV To Player" = VV_HREF_TARGETREF_INTERNAL(refid, VV_HK_EXPOSE), + "View References" = VV_HREF_TARGETREF_INTERNAL(refid, VV_HK_VIEW_REFERENCES), "---" ) for(var/i in 1 to length(dropdownoptions)) diff --git a/code/modules/admin/whitelist.dm b/code/modules/admin/whitelist.dm index 55206f6debbe..263268a5ca12 100644 --- a/code/modules/admin/whitelist.dm +++ b/code/modules/admin/whitelist.dm @@ -1,23 +1,23 @@ -#define WHITELISTFILE "[global.config.directory]/whitelist.txt" - -GLOBAL_LIST(whitelist) -GLOBAL_PROTECT(whitelist) - -/proc/load_whitelist() - GLOB.whitelist = list() - for(var/line in world.file2list(WHITELISTFILE)) - if(!line) - continue - if(findtextEx(line,"#",1,2)) - continue - GLOB.whitelist += ckey(line) - - if(!GLOB.whitelist.len) - GLOB.whitelist = null - -/proc/check_whitelist(var/ckey) - if(!GLOB.whitelist) - return FALSE - . = (ckey in GLOB.whitelist) - -#undef WHITELISTFILE +#define WHITELISTFILE "[global.config.directory]/whitelist.txt" + +GLOBAL_LIST(whitelist) +GLOBAL_PROTECT(whitelist) + +/proc/load_whitelist() + GLOB.whitelist = list() + for(var/line in world.file2list(WHITELISTFILE)) + if(!line) + continue + if(findtextEx(line,"#",1,2)) + continue + GLOB.whitelist += ckey(line) + + if(!GLOB.whitelist.len) + GLOB.whitelist = null + +/proc/check_whitelist(var/ckey) + if(!GLOB.whitelist) + return FALSE + . = (ckey in GLOB.whitelist) + +#undef WHITELISTFILE diff --git a/code/modules/antagonists/_common/antag_hud.dm b/code/modules/antagonists/_common/antag_hud.dm index 11ddfe2e497d..d6a4bdab18ec 100644 --- a/code/modules/antagonists/_common/antag_hud.dm +++ b/code/modules/antagonists/_common/antag_hud.dm @@ -1,58 +1,58 @@ -/datum/atom_hud/antag - hud_icons = list(ANTAG_HUD) - var/self_visible = TRUE - var/icon_color //will set the icon color to this - -/datum/atom_hud/antag/hidden - self_visible = FALSE - -/datum/atom_hud/antag/proc/join_hud(mob/M) - //sees_hud should be set to 0 if the mob does not get to see it's own hud type. - if(!istype(M)) - CRASH("join_hud(): [M] ([M.type]) is not a mob!") - if(M.mind.antag_hud) //note: please let this runtime if a mob has no mind, as mindless mobs shouldn't be getting antagged - M.mind.antag_hud.leave_hud(M) - - if(ANTAG_HUD in M.hud_possible) //Current mob does not support antag huds ie newplayer - add_to_hud(M) - if(self_visible) - add_hud_to(M) - - M.mind.antag_hud = src - -/datum/atom_hud/antag/proc/leave_hud(mob/M) - if(!M) - return - if(!istype(M)) - CRASH("leave_hud(): [M] ([M.type]) is not a mob!") - remove_from_hud(M) - remove_hud_from(M) - if(M.mind) - M.mind.antag_hud = null - -//GAME_MODE PROCS -//called to set a mob's antag icon state -/proc/set_antag_hud(mob/M, new_icon_state, hudindex) - if(!istype(M)) - CRASH("set_antag_hud(): [M] ([M.type]) is not a mob!") - var/image/holder = M.hud_list[ANTAG_HUD] - var/datum/atom_hud/antag/specific_hud = hudindex ? GLOB.huds[hudindex] : null - if(holder) - holder.icon_state = new_icon_state - holder.color = specific_hud?.icon_color - if(M.mind || new_icon_state) //in mindless mobs, only null is acceptable, otherwise we're antagging a mindless mob, meaning we should runtime - M.mind.antag_hud_icon_state = new_icon_state - - -//MIND PROCS -//these are called by mind.transfer_to() -/datum/mind/proc/transfer_antag_huds(datum/atom_hud/antag/newhud) - leave_all_antag_huds() - set_antag_hud(current, antag_hud_icon_state) - if(newhud) - newhud.join_hud(current) - -/datum/mind/proc/leave_all_antag_huds() - for(var/datum/atom_hud/antag/hud in GLOB.huds) - if(hud.hudusers[current]) - hud.leave_hud(current) +/datum/atom_hud/antag + hud_icons = list(ANTAG_HUD) + var/self_visible = TRUE + var/icon_color //will set the icon color to this + +/datum/atom_hud/antag/hidden + self_visible = FALSE + +/datum/atom_hud/antag/proc/join_hud(mob/M) + //sees_hud should be set to 0 if the mob does not get to see it's own hud type. + if(!istype(M)) + CRASH("join_hud(): [M] ([M.type]) is not a mob!") + if(M.mind.antag_hud) //note: please let this runtime if a mob has no mind, as mindless mobs shouldn't be getting antagged + M.mind.antag_hud.leave_hud(M) + + if(ANTAG_HUD in M.hud_possible) //Current mob does not support antag huds ie newplayer + add_to_hud(M) + if(self_visible) + add_hud_to(M) + + M.mind.antag_hud = src + +/datum/atom_hud/antag/proc/leave_hud(mob/M) + if(!M) + return + if(!istype(M)) + CRASH("leave_hud(): [M] ([M.type]) is not a mob!") + remove_from_hud(M) + remove_hud_from(M) + if(M.mind) + M.mind.antag_hud = null + +//GAME_MODE PROCS +//called to set a mob's antag icon state +/proc/set_antag_hud(mob/M, new_icon_state, hudindex) + if(!istype(M)) + CRASH("set_antag_hud(): [M] ([M.type]) is not a mob!") + var/image/holder = M.hud_list[ANTAG_HUD] + var/datum/atom_hud/antag/specific_hud = hudindex ? GLOB.huds[hudindex] : null + if(holder) + holder.icon_state = new_icon_state + holder.color = specific_hud?.icon_color + if(M.mind || new_icon_state) //in mindless mobs, only null is acceptable, otherwise we're antagging a mindless mob, meaning we should runtime + M.mind.antag_hud_icon_state = new_icon_state + + +//MIND PROCS +//these are called by mind.transfer_to() +/datum/mind/proc/transfer_antag_huds(datum/atom_hud/antag/newhud) + leave_all_antag_huds() + set_antag_hud(current, antag_hud_icon_state) + if(newhud) + newhud.join_hud(current) + +/datum/mind/proc/leave_all_antag_huds() + for(var/datum/atom_hud/antag/hud in GLOB.huds) + if(hud.hudusers[current]) + hud.leave_hud(current) diff --git a/code/modules/antagonists/_common/antag_spawner.dm b/code/modules/antagonists/_common/antag_spawner.dm index f7d82891c9d6..03e9d1d4675c 100644 --- a/code/modules/antagonists/_common/antag_spawner.dm +++ b/code/modules/antagonists/_common/antag_spawner.dm @@ -1,277 +1,277 @@ -/obj/item/antag_spawner - throw_speed = 1 - throw_range = 5 - w_class = WEIGHT_CLASS_TINY - var/used = FALSE - -/obj/item/antag_spawner/proc/spawn_antag(client/C, turf/T, kind = "", datum/mind/user) - return - -/obj/item/antag_spawner/proc/equip_antag(mob/target) - return - - -///////////WIZARD - -/obj/item/antag_spawner/contract - name = "contract" - desc = "A magic contract previously signed by an apprentice. In exchange for instruction in the magical arts, they are bound to answer your call for aid." - icon = 'icons/obj/wizard.dmi' - icon_state ="scroll2" - -/obj/item/antag_spawner/contract/attack_self(mob/user) - user.set_machine(src) - var/dat - if(used) - dat = "You have already summoned your apprentice.
                    " - else - dat = "Contract of Apprenticeship:
                    " - dat += "Using this contract, you may summon an apprentice to aid you on your mission.
                    " - dat += "If you are unable to establish contact with your apprentice, you can feed the contract back to the spellbook to refund your points.
                    " - dat += "Which school of magic is your apprentice studying?:
                    " - dat += "Destruction
                    " - dat += "Your apprentice is skilled in offensive magic. They know Magic Missile and Fireball.
                    " - dat += "Bluespace Manipulation
                    " - dat += "Your apprentice is able to defy physics, melting through solid objects and travelling great distances in the blink of an eye. They know Teleport and Ethereal Jaunt.
                    " - dat += "Healing
                    " - dat += "Your apprentice is training to cast spells that will aid your survival. They know Forcewall and Charge and come with a Staff of Healing.
                    " - dat += "Robeless
                    " - dat += "Your apprentice is training to cast spells without their robes. They know Knock and Mindswap.
                    " - user << browse(dat, "window=radio") - onclose(user, "radio") - return - -/obj/item/antag_spawner/contract/Topic(href, href_list) - ..() - var/mob/living/carbon/human/H = usr - - if(H.stat || H.restrained()) - return - if(!ishuman(H)) - return 1 - - if(loc == H || (in_range(src, H) && isturf(loc))) - H.set_machine(src) - if(href_list["school"]) - if(used) - to_chat(H, "You already used this contract!") - return - var/list/candidates = pollCandidatesForMob("Do you want to play as a wizard's [href_list["school"]] apprentice?", ROLE_WIZARD, null, ROLE_WIZARD, 150, src) - if(LAZYLEN(candidates)) - if(QDELETED(src)) - return - if(used) - to_chat(H, "You already used this contract!") - return - used = TRUE - var/mob/dead/observer/C = pick(candidates) - spawn_antag(C.client, get_turf(src), href_list["school"],H.mind) - else - to_chat(H, "Unable to reach your apprentice! You can either attack the spellbook with the contract to refund your points, or wait and try again later.") - -/obj/item/antag_spawner/contract/spawn_antag(client/C, turf/T, kind ,datum/mind/user) - new /obj/effect/particle_effect/smoke(T) - var/mob/living/carbon/human/M = new/mob/living/carbon/human(T) - C.prefs.copy_to(M) - M.key = C.key - var/datum/mind/app_mind = M.mind - - var/datum/antagonist/wizard/apprentice/app = new() - app.master = user - app.school = kind - - var/datum/antagonist/wizard/master_wizard = user.has_antag_datum(/datum/antagonist/wizard) - if(master_wizard) - if(!master_wizard.wiz_team) - master_wizard.create_wiz_team() - app.wiz_team = master_wizard.wiz_team - master_wizard.wiz_team.add_member(app_mind) - app_mind.add_antag_datum(app) - //TODO Kill these if possible - app_mind.assigned_role = "Apprentice" - app_mind.special_role = "apprentice" - // - SEND_SOUND(M, sound('sound/effects/magic.ogg')) - -///////////BORGS AND OPERATIVES - - -/obj/item/antag_spawner/nuke_ops - name = "syndicate operative teleporter" - desc = "A single-use teleporter designed to quickly reinforce operatives in the field." - icon = 'icons/obj/device.dmi' - icon_state = "locator" - var/borg_to_spawn - -/obj/item/antag_spawner/nuke_ops/proc/check_usability(mob/user) - if(used) - to_chat(user, "[src] is out of power!") - return FALSE - if(!user.mind.has_antag_datum(/datum/antagonist/nukeop,TRUE)) - to_chat(user, "AUTHENTICATION FAILURE. ACCESS DENIED.") - return FALSE - return TRUE - - -/obj/item/antag_spawner/nuke_ops/attack_self(mob/user) - if(!(check_usability(user))) - return - - to_chat(user, "You activate [src] and wait for confirmation.") - var/list/nuke_candidates = pollGhostCandidates("Do you want to play as a syndicate [borg_to_spawn ? "[lowertext(borg_to_spawn)] cyborg":"operative"]?", ROLE_OPERATIVE, null, ROLE_OPERATIVE, 150, POLL_IGNORE_SYNDICATE) - if(LAZYLEN(nuke_candidates)) - if(QDELETED(src) || !check_usability(user)) - return - used = TRUE - var/mob/dead/observer/G = pick(nuke_candidates) - spawn_antag(G.client, get_turf(src), "syndieborg", user.mind) - do_sparks(4, TRUE, src) - qdel(src) - else - to_chat(user, "Unable to connect to Syndicate command. Please wait and try again later or use the teleporter on your uplink to get your points refunded.") - -/obj/item/antag_spawner/nuke_ops/spawn_antag(client/C, turf/T, kind, datum/mind/user) - var/mob/living/carbon/human/M = new/mob/living/carbon/human(T) - C.prefs.copy_to(M) - M.key = C.key - - var/datum/antagonist/nukeop/new_op = new() - new_op.send_to_spawnpoint = FALSE - new_op.nukeop_outfit = /datum/outfit/syndicate/no_crystals - - var/datum/antagonist/nukeop/creator_op = user.has_antag_datum(/datum/antagonist/nukeop,TRUE) - if(creator_op) - M.mind.add_antag_datum(new_op,creator_op.nuke_team) - M.mind.special_role = "Nuclear Operative" - -//////CLOWN OP -/obj/item/antag_spawner/nuke_ops/clown - name = "clown operative teleporter" - desc = "A single-use teleporter designed to quickly reinforce clown operatives in the field." - -/obj/item/antag_spawner/nuke_ops/clown/spawn_antag(client/C, turf/T, kind, datum/mind/user) - var/mob/living/carbon/human/M = new/mob/living/carbon/human(T) - C.prefs.copy_to(M) - M.key = C.key - - var/datum/antagonist/nukeop/clownop/new_op = new /datum/antagonist/nukeop/clownop() - new_op.send_to_spawnpoint = FALSE - new_op.nukeop_outfit = /datum/outfit/syndicate/clownop/no_crystals - - var/datum/antagonist/nukeop/creator_op = user.has_antag_datum(/datum/antagonist/nukeop/clownop,TRUE) - if(creator_op) - M.mind.add_antag_datum(new_op, creator_op.nuke_team) - M.mind.special_role = "Clown Operative" - - -//////SYNDICATE BORG -/obj/item/antag_spawner/nuke_ops/borg_tele - name = "syndicate cyborg teleporter" - desc = "A single-use teleporter designed to quickly reinforce operatives in the field." - icon = 'icons/obj/device.dmi' - icon_state = "locator" - -/obj/item/antag_spawner/nuke_ops/borg_tele/assault - name = "syndicate assault cyborg teleporter" - borg_to_spawn = "Assault" - -/obj/item/antag_spawner/nuke_ops/borg_tele/medical - name = "syndicate medical teleporter" - borg_to_spawn = "Medical" - -/obj/item/antag_spawner/nuke_ops/borg_tele/saboteur - name = "syndicate saboteur teleporter" - borg_to_spawn = "Saboteur" - -/obj/item/antag_spawner/nuke_ops/borg_tele/spawn_antag(client/C, turf/T, kind, datum/mind/user) - var/mob/living/silicon/robot/R - var/datum/antagonist/nukeop/creator_op = user.has_antag_datum(/datum/antagonist/nukeop,TRUE) - if(!creator_op) - return - - switch(borg_to_spawn) - if("Medical") - R = new /mob/living/silicon/robot/modules/syndicate/medical(T) - if("Saboteur") - R = new /mob/living/silicon/robot/modules/syndicate/saboteur(T) - else - R = new /mob/living/silicon/robot/modules/syndicate(T) //Assault borg by default - - var/brainfirstname = pick(GLOB.first_names_male) - if(prob(50)) - brainfirstname = pick(GLOB.first_names_female) - var/brainopslastname = pick(GLOB.last_names) - if(creator_op.nuke_team.syndicate_name) //the brain inside the syndiborg has the same last name as the other ops. - brainopslastname = creator_op.nuke_team.syndicate_name - var/brainopsname = "[brainfirstname] [brainopslastname]" - - R.mmi.name = "[initial(R.mmi.name)]: [brainopsname]" - R.mmi.brain.name = "[brainopsname]'s brain" - R.mmi.brainmob.real_name = brainopsname - R.mmi.brainmob.name = brainopsname - R.real_name = R.name - - R.key = C.key - - var/datum/antagonist/nukeop/new_borg = new() - new_borg.send_to_spawnpoint = FALSE - R.mind.add_antag_datum(new_borg,creator_op.nuke_team) - R.mind.special_role = "Syndicate Cyborg" - -///////////SLAUGHTER DEMON - -/obj/item/antag_spawner/slaughter_demon //Warning edgiest item in the game - name = "vial of blood" - desc = "A magically infused bottle of blood, distilled from countless murder victims. Used in unholy rituals to attract horrifying creatures." - icon = 'icons/obj/wizard.dmi' - icon_state = "vial" - - var/shatter_msg = "You shatter the bottle, no turning back now!" - var/veil_msg = "You sense a dark presence lurking just beyond the veil..." - var/mob/living/demon_type = /mob/living/simple_animal/slaughter - var/antag_type = /datum/antagonist/slaughter - - -/obj/item/antag_spawner/slaughter_demon/attack_self(mob/user) - if(!is_station_level(user.z)) - to_chat(user, "You should probably wait until you reach the station.") - return - if(used) - return - var/list/candidates = pollCandidatesForMob("Do you want to play as a [initial(demon_type.name)]?", ROLE_ALIEN, null, ROLE_ALIEN, 50, src) - if(LAZYLEN(candidates)) - if(used || QDELETED(src)) - return - used = TRUE - var/mob/dead/observer/C = pick(candidates) - spawn_antag(C.client, get_turf(src), initial(demon_type.name),user.mind) - to_chat(user, shatter_msg) - to_chat(user, veil_msg) - playsound(user.loc, 'sound/effects/glassbr1.ogg', 100, TRUE) - qdel(src) - else - to_chat(user, "You can't seem to work up the nerve to shatter the bottle! Perhaps you should try again later.") - - -/obj/item/antag_spawner/slaughter_demon/spawn_antag(client/C, turf/T, kind = "", datum/mind/user) - var/obj/effect/dummy/phased_mob/slaughter/holder = new /obj/effect/dummy/phased_mob/slaughter(T) - var/mob/living/simple_animal/slaughter/S = new demon_type(holder) - S.holder = holder - S.key = C.key - S.mind.assigned_role = S.name - S.mind.special_role = S.name - S.mind.add_antag_datum(antag_type) - to_chat(S, S.playstyle_string) - to_chat(S, "You are currently not currently in the same plane of existence as the station. \ - Ctrl+Click a blood pool to manifest.") - -/obj/item/antag_spawner/slaughter_demon/laughter - name = "vial of tickles" - desc = "A magically infused bottle of clown love, distilled from countless hugging attacks. Used in funny rituals to attract adorable creatures." - icon = 'icons/obj/wizard.dmi' - icon_state = "vial" - color = "#FF69B4" // HOT PINK - - veil_msg = "You sense an adorable presence lurking just beyond the veil..." - demon_type = /mob/living/simple_animal/slaughter/laughter - antag_type = /datum/antagonist/slaughter/laughter +/obj/item/antag_spawner + throw_speed = 1 + throw_range = 5 + w_class = WEIGHT_CLASS_TINY + var/used = FALSE + +/obj/item/antag_spawner/proc/spawn_antag(client/C, turf/T, kind = "", datum/mind/user) + return + +/obj/item/antag_spawner/proc/equip_antag(mob/target) + return + + +///////////WIZARD + +/obj/item/antag_spawner/contract + name = "contract" + desc = "A magic contract previously signed by an apprentice. In exchange for instruction in the magical arts, they are bound to answer your call for aid." + icon = 'icons/obj/wizard.dmi' + icon_state ="scroll2" + +/obj/item/antag_spawner/contract/attack_self(mob/user) + user.set_machine(src) + var/dat + if(used) + dat = "You have already summoned your apprentice.
                    " + else + dat = "Contract of Apprenticeship:
                    " + dat += "Using this contract, you may summon an apprentice to aid you on your mission.
                    " + dat += "If you are unable to establish contact with your apprentice, you can feed the contract back to the spellbook to refund your points.
                    " + dat += "Which school of magic is your apprentice studying?:
                    " + dat += "Destruction
                    " + dat += "Your apprentice is skilled in offensive magic. They know Magic Missile and Fireball.
                    " + dat += "Bluespace Manipulation
                    " + dat += "Your apprentice is able to defy physics, melting through solid objects and travelling great distances in the blink of an eye. They know Teleport and Ethereal Jaunt.
                    " + dat += "Healing
                    " + dat += "Your apprentice is training to cast spells that will aid your survival. They know Forcewall and Charge and come with a Staff of Healing.
                    " + dat += "Robeless
                    " + dat += "Your apprentice is training to cast spells without their robes. They know Knock and Mindswap.
                    " + user << browse(dat, "window=radio") + onclose(user, "radio") + return + +/obj/item/antag_spawner/contract/Topic(href, href_list) + ..() + var/mob/living/carbon/human/H = usr + + if(H.stat || H.restrained()) + return + if(!ishuman(H)) + return 1 + + if(loc == H || (in_range(src, H) && isturf(loc))) + H.set_machine(src) + if(href_list["school"]) + if(used) + to_chat(H, "You already used this contract!") + return + var/list/candidates = pollCandidatesForMob("Do you want to play as a wizard's [href_list["school"]] apprentice?", ROLE_WIZARD, null, ROLE_WIZARD, 150, src) + if(LAZYLEN(candidates)) + if(QDELETED(src)) + return + if(used) + to_chat(H, "You already used this contract!") + return + used = TRUE + var/mob/dead/observer/C = pick(candidates) + spawn_antag(C.client, get_turf(src), href_list["school"],H.mind) + else + to_chat(H, "Unable to reach your apprentice! You can either attack the spellbook with the contract to refund your points, or wait and try again later.") + +/obj/item/antag_spawner/contract/spawn_antag(client/C, turf/T, kind ,datum/mind/user) + new /obj/effect/particle_effect/smoke(T) + var/mob/living/carbon/human/M = new/mob/living/carbon/human(T) + C.prefs.copy_to(M) + M.key = C.key + var/datum/mind/app_mind = M.mind + + var/datum/antagonist/wizard/apprentice/app = new() + app.master = user + app.school = kind + + var/datum/antagonist/wizard/master_wizard = user.has_antag_datum(/datum/antagonist/wizard) + if(master_wizard) + if(!master_wizard.wiz_team) + master_wizard.create_wiz_team() + app.wiz_team = master_wizard.wiz_team + master_wizard.wiz_team.add_member(app_mind) + app_mind.add_antag_datum(app) + //TODO Kill these if possible + app_mind.assigned_role = "Apprentice" + app_mind.special_role = "apprentice" + // + SEND_SOUND(M, sound('sound/effects/magic.ogg')) + +///////////BORGS AND OPERATIVES + + +/obj/item/antag_spawner/nuke_ops + name = "syndicate operative teleporter" + desc = "A single-use teleporter designed to quickly reinforce operatives in the field." + icon = 'icons/obj/device.dmi' + icon_state = "locator" + var/borg_to_spawn + +/obj/item/antag_spawner/nuke_ops/proc/check_usability(mob/user) + if(used) + to_chat(user, "[src] is out of power!") + return FALSE + if(!user.mind.has_antag_datum(/datum/antagonist/nukeop,TRUE)) + to_chat(user, "AUTHENTICATION FAILURE. ACCESS DENIED.") + return FALSE + return TRUE + + +/obj/item/antag_spawner/nuke_ops/attack_self(mob/user) + if(!(check_usability(user))) + return + + to_chat(user, "You activate [src] and wait for confirmation.") + var/list/nuke_candidates = pollGhostCandidates("Do you want to play as a syndicate [borg_to_spawn ? "[lowertext(borg_to_spawn)] cyborg":"operative"]?", ROLE_OPERATIVE, null, ROLE_OPERATIVE, 150, POLL_IGNORE_SYNDICATE) + if(LAZYLEN(nuke_candidates)) + if(QDELETED(src) || !check_usability(user)) + return + used = TRUE + var/mob/dead/observer/G = pick(nuke_candidates) + spawn_antag(G.client, get_turf(src), "syndieborg", user.mind) + do_sparks(4, TRUE, src) + qdel(src) + else + to_chat(user, "Unable to connect to Syndicate command. Please wait and try again later or use the teleporter on your uplink to get your points refunded.") + +/obj/item/antag_spawner/nuke_ops/spawn_antag(client/C, turf/T, kind, datum/mind/user) + var/mob/living/carbon/human/M = new/mob/living/carbon/human(T) + C.prefs.copy_to(M) + M.key = C.key + + var/datum/antagonist/nukeop/new_op = new() + new_op.send_to_spawnpoint = FALSE + new_op.nukeop_outfit = /datum/outfit/syndicate/no_crystals + + var/datum/antagonist/nukeop/creator_op = user.has_antag_datum(/datum/antagonist/nukeop,TRUE) + if(creator_op) + M.mind.add_antag_datum(new_op,creator_op.nuke_team) + M.mind.special_role = "Nuclear Operative" + +//////CLOWN OP +/obj/item/antag_spawner/nuke_ops/clown + name = "clown operative teleporter" + desc = "A single-use teleporter designed to quickly reinforce clown operatives in the field." + +/obj/item/antag_spawner/nuke_ops/clown/spawn_antag(client/C, turf/T, kind, datum/mind/user) + var/mob/living/carbon/human/M = new/mob/living/carbon/human(T) + C.prefs.copy_to(M) + M.key = C.key + + var/datum/antagonist/nukeop/clownop/new_op = new /datum/antagonist/nukeop/clownop() + new_op.send_to_spawnpoint = FALSE + new_op.nukeop_outfit = /datum/outfit/syndicate/clownop/no_crystals + + var/datum/antagonist/nukeop/creator_op = user.has_antag_datum(/datum/antagonist/nukeop/clownop,TRUE) + if(creator_op) + M.mind.add_antag_datum(new_op, creator_op.nuke_team) + M.mind.special_role = "Clown Operative" + + +//////SYNDICATE BORG +/obj/item/antag_spawner/nuke_ops/borg_tele + name = "syndicate cyborg teleporter" + desc = "A single-use teleporter designed to quickly reinforce operatives in the field." + icon = 'icons/obj/device.dmi' + icon_state = "locator" + +/obj/item/antag_spawner/nuke_ops/borg_tele/assault + name = "syndicate assault cyborg teleporter" + borg_to_spawn = "Assault" + +/obj/item/antag_spawner/nuke_ops/borg_tele/medical + name = "syndicate medical teleporter" + borg_to_spawn = "Medical" + +/obj/item/antag_spawner/nuke_ops/borg_tele/saboteur + name = "syndicate saboteur teleporter" + borg_to_spawn = "Saboteur" + +/obj/item/antag_spawner/nuke_ops/borg_tele/spawn_antag(client/C, turf/T, kind, datum/mind/user) + var/mob/living/silicon/robot/R + var/datum/antagonist/nukeop/creator_op = user.has_antag_datum(/datum/antagonist/nukeop,TRUE) + if(!creator_op) + return + + switch(borg_to_spawn) + if("Medical") + R = new /mob/living/silicon/robot/modules/syndicate/medical(T) + if("Saboteur") + R = new /mob/living/silicon/robot/modules/syndicate/saboteur(T) + else + R = new /mob/living/silicon/robot/modules/syndicate(T) //Assault borg by default + + var/brainfirstname = pick(GLOB.first_names_male) + if(prob(50)) + brainfirstname = pick(GLOB.first_names_female) + var/brainopslastname = pick(GLOB.last_names) + if(creator_op.nuke_team.syndicate_name) //the brain inside the syndiborg has the same last name as the other ops. + brainopslastname = creator_op.nuke_team.syndicate_name + var/brainopsname = "[brainfirstname] [brainopslastname]" + + R.mmi.name = "[initial(R.mmi.name)]: [brainopsname]" + R.mmi.brain.name = "[brainopsname]'s brain" + R.mmi.brainmob.real_name = brainopsname + R.mmi.brainmob.name = brainopsname + R.real_name = R.name + + R.key = C.key + + var/datum/antagonist/nukeop/new_borg = new() + new_borg.send_to_spawnpoint = FALSE + R.mind.add_antag_datum(new_borg,creator_op.nuke_team) + R.mind.special_role = "Syndicate Cyborg" + +///////////SLAUGHTER DEMON + +/obj/item/antag_spawner/slaughter_demon //Warning edgiest item in the game + name = "vial of blood" + desc = "A magically infused bottle of blood, distilled from countless murder victims. Used in unholy rituals to attract horrifying creatures." + icon = 'icons/obj/wizard.dmi' + icon_state = "vial" + + var/shatter_msg = "You shatter the bottle, no turning back now!" + var/veil_msg = "You sense a dark presence lurking just beyond the veil..." + var/mob/living/demon_type = /mob/living/simple_animal/slaughter + var/antag_type = /datum/antagonist/slaughter + + +/obj/item/antag_spawner/slaughter_demon/attack_self(mob/user) + if(!is_station_level(user.z)) + to_chat(user, "You should probably wait until you reach the station.") + return + if(used) + return + var/list/candidates = pollCandidatesForMob("Do you want to play as a [initial(demon_type.name)]?", ROLE_ALIEN, null, ROLE_ALIEN, 50, src) + if(LAZYLEN(candidates)) + if(used || QDELETED(src)) + return + used = TRUE + var/mob/dead/observer/C = pick(candidates) + spawn_antag(C.client, get_turf(src), initial(demon_type.name),user.mind) + to_chat(user, shatter_msg) + to_chat(user, veil_msg) + playsound(user.loc, 'sound/effects/glassbr1.ogg', 100, TRUE) + qdel(src) + else + to_chat(user, "You can't seem to work up the nerve to shatter the bottle! Perhaps you should try again later.") + + +/obj/item/antag_spawner/slaughter_demon/spawn_antag(client/C, turf/T, kind = "", datum/mind/user) + var/obj/effect/dummy/phased_mob/slaughter/holder = new /obj/effect/dummy/phased_mob/slaughter(T) + var/mob/living/simple_animal/slaughter/S = new demon_type(holder) + S.holder = holder + S.key = C.key + S.mind.assigned_role = S.name + S.mind.special_role = S.name + S.mind.add_antag_datum(antag_type) + to_chat(S, S.playstyle_string) + to_chat(S, "You are currently not currently in the same plane of existence as the station. \ + Ctrl+Click a blood pool to manifest.") + +/obj/item/antag_spawner/slaughter_demon/laughter + name = "vial of tickles" + desc = "A magically infused bottle of clown love, distilled from countless hugging attacks. Used in funny rituals to attract adorable creatures." + icon = 'icons/obj/wizard.dmi' + icon_state = "vial" + color = "#FF69B4" // HOT PINK + + veil_msg = "You sense an adorable presence lurking just beyond the veil..." + demon_type = /mob/living/simple_animal/slaughter/laughter + antag_type = /datum/antagonist/slaughter/laughter diff --git a/code/modules/antagonists/abductor/equipment/orderable_gear.dm b/code/modules/antagonists/abductor/equipment/orderable_gear.dm index 0cc04fa68da3..254b986741ce 100644 --- a/code/modules/antagonists/abductor/equipment/orderable_gear.dm +++ b/code/modules/antagonists/abductor/equipment/orderable_gear.dm @@ -1,79 +1,79 @@ -GLOBAL_LIST_INIT(abductor_gear, subtypesof(/datum/abductor_gear)) - -/datum/abductor_gear - /// Name of the gear - var/name = "Generic Abductor Gear" - /// Description of the gear - var/description = "Generic description." - /// Unique ID of the gear - var/id = "abductor_generic" - /// Credit cost of the gear - var/cost = 1 - /// Build path of the gear itself - var/build_path = null - /// Category of the gear - var/category = "Basic Gear" - -/datum/abductor_gear/agent_helmet - name = "Agent Helmet" - description = "Abduct with style - spiky style. Prevents digital tracking." - id = "agent_helmet" - build_path = /obj/item/clothing/head/helmet/abductor - -/datum/abductor_gear/agent_vest - name = "Agent Vest" - description = "A vest outfitted with advanced stealth technology. It has two modes - combat and stealth." - id = "agent_vest" - build_path = /obj/item/clothing/suit/armor/abductor/vest - -/datum/abductor_gear/radio_silencer - name = "Radio Silencer" - description = "A compact device used to shut down communications equipment." - id = "radio_silencer" - build_path = /obj/item/abductor/silencer - -/datum/abductor_gear/science_tool - name = "Science Tool" - description = "A dual-mode tool for retrieving specimens and scanning appearances. Scanning can be done through cameras." - id = "science_tool" - build_path = /obj/item/abductor/gizmo - -/datum/abductor_gear/advanced_baton - name = "Advanced Baton" - description = "A quad-mode baton used for incapacitation and restraining of specimens." - id = "advanced_baton" - cost = 2 - build_path = /obj/item/melee/baton/abductor - -/datum/abductor_gear/superlingual_matrix - name = "Superlingual Matrix" - description = "A mysterious structure that allows for instant communication between users. Pretty impressive until you need to eat something." - id = "superlingual_matrix" - build_path = /obj/item/organ/tongue/abductor - category = "Advanced Gear" - -/datum/abductor_gear/mental_interface - name = "Mental Interface Device" - description = "A dual-mode tool for directly communicating with sentient brains. It can be used to send a direct message to a target, \ - or to send a command to a test subject with a charged gland." - id = "mental_interface" - cost = 2 - build_path = /obj/item/abductor/mind_device - category = "Advanced Gear" - -/datum/abductor_gear/reagent_synthesizer - name = "Reagent Synthesizer" - description = "Synthesizes a variety of reagents using proto-matter." - id = "reagent_synthesizer" - cost = 2 - build_path = /obj/item/abductor_machine_beacon/chem_dispenser - category = "Advanced Gear" - -/datum/abductor_gear/shrink_ray - name = "Shrink Ray Blaster" - description = "This is a piece of frightening alien tech that enhances the magnetic pull of atoms in a localized space to temporarily make an object shrink. \ - That or it's just space magic. Either way, it shrinks stuff." - id = "shrink_ray" - cost = 2 - build_path = /obj/item/gun/energy/shrink_ray - category = "Advanced Gear" +GLOBAL_LIST_INIT(abductor_gear, subtypesof(/datum/abductor_gear)) + +/datum/abductor_gear + /// Name of the gear + var/name = "Generic Abductor Gear" + /// Description of the gear + var/description = "Generic description." + /// Unique ID of the gear + var/id = "abductor_generic" + /// Credit cost of the gear + var/cost = 1 + /// Build path of the gear itself + var/build_path = null + /// Category of the gear + var/category = "Basic Gear" + +/datum/abductor_gear/agent_helmet + name = "Agent Helmet" + description = "Abduct with style - spiky style. Prevents digital tracking." + id = "agent_helmet" + build_path = /obj/item/clothing/head/helmet/abductor + +/datum/abductor_gear/agent_vest + name = "Agent Vest" + description = "A vest outfitted with advanced stealth technology. It has two modes - combat and stealth." + id = "agent_vest" + build_path = /obj/item/clothing/suit/armor/abductor/vest + +/datum/abductor_gear/radio_silencer + name = "Radio Silencer" + description = "A compact device used to shut down communications equipment." + id = "radio_silencer" + build_path = /obj/item/abductor/silencer + +/datum/abductor_gear/science_tool + name = "Science Tool" + description = "A dual-mode tool for retrieving specimens and scanning appearances. Scanning can be done through cameras." + id = "science_tool" + build_path = /obj/item/abductor/gizmo + +/datum/abductor_gear/advanced_baton + name = "Advanced Baton" + description = "A quad-mode baton used for incapacitation and restraining of specimens." + id = "advanced_baton" + cost = 2 + build_path = /obj/item/melee/baton/abductor + +/datum/abductor_gear/superlingual_matrix + name = "Superlingual Matrix" + description = "A mysterious structure that allows for instant communication between users. Pretty impressive until you need to eat something." + id = "superlingual_matrix" + build_path = /obj/item/organ/tongue/abductor + category = "Advanced Gear" + +/datum/abductor_gear/mental_interface + name = "Mental Interface Device" + description = "A dual-mode tool for directly communicating with sentient brains. It can be used to send a direct message to a target, \ + or to send a command to a test subject with a charged gland." + id = "mental_interface" + cost = 2 + build_path = /obj/item/abductor/mind_device + category = "Advanced Gear" + +/datum/abductor_gear/reagent_synthesizer + name = "Reagent Synthesizer" + description = "Synthesizes a variety of reagents using proto-matter." + id = "reagent_synthesizer" + cost = 2 + build_path = /obj/item/abductor_machine_beacon/chem_dispenser + category = "Advanced Gear" + +/datum/abductor_gear/shrink_ray + name = "Shrink Ray Blaster" + description = "This is a piece of frightening alien tech that enhances the magnetic pull of atoms in a localized space to temporarily make an object shrink. \ + That or it's just space magic. Either way, it shrinks stuff." + id = "shrink_ray" + cost = 2 + build_path = /obj/item/gun/energy/shrink_ray + category = "Advanced Gear" diff --git a/code/modules/antagonists/abductor/machinery/console.dm b/code/modules/antagonists/abductor/machinery/console.dm index 9573380f0855..29d998e709b2 100644 --- a/code/modules/antagonists/abductor/machinery/console.dm +++ b/code/modules/antagonists/abductor/machinery/console.dm @@ -17,8 +17,6 @@ icon = 'icons/obj/abductor.dmi' icon_state = "console" density = TRUE - ui_x = 600 - ui_y = 532 var/obj/item/abductor/gizmo/gizmo var/obj/item/clothing/suit/armor/abductor/vest/vest var/obj/machinery/abductor/experiment/experiment @@ -62,11 +60,13 @@ return UI_CLOSE return ..() -/obj/machinery/abductor/console/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.physical_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/machinery/abductor/console/ui_state(mob/user) + return GLOB.physical_state + +/obj/machinery/abductor/console/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "AbductorConsole", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, "AbductorConsole", name) ui.open() /obj/machinery/abductor/console/ui_static_data(mob/user) diff --git a/code/modules/antagonists/abductor/machinery/dispenser.dm b/code/modules/antagonists/abductor/machinery/dispenser.dm index ea0cece97c41..f5bc0946e669 100644 --- a/code/modules/antagonists/abductor/machinery/dispenser.dm +++ b/code/modules/antagonists/abductor/machinery/dispenser.dm @@ -4,8 +4,6 @@ icon = 'icons/obj/abductor.dmi' icon_state = "dispenser" density = TRUE - ui_x = 300 - ui_y = 338 var/list/gland_types var/list/gland_colors var/list/amounts @@ -29,11 +27,13 @@ return UI_CLOSE return ..() -/obj/machinery/abductor/gland_dispenser/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.physical_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/machinery/abductor/gland_dispenser/ui_state(mob/user) + return GLOB.physical_state + +/obj/machinery/abductor/gland_dispenser/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "GlandDispenser", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, "GlandDispenser", name) ui.open() /obj/machinery/abductor/gland_dispenser/ui_data(mob/user) diff --git a/code/modules/antagonists/abductor/machinery/experiment.dm b/code/modules/antagonists/abductor/machinery/experiment.dm index aeb14e1175d0..889a4c36a530 100644 --- a/code/modules/antagonists/abductor/machinery/experiment.dm +++ b/code/modules/antagonists/abductor/machinery/experiment.dm @@ -5,8 +5,6 @@ icon_state = "experiment-open" density = FALSE state_open = TRUE - ui_x = 330 - ui_y = 207 var/points = 0 var/credits = 0 var/list/history @@ -61,11 +59,13 @@ return UI_CLOSE return ..() -/obj/machinery/abductor/experiment/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.physical_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/machinery/abductor/experiment/ui_state(mob/user) + return GLOB.physical_state + +/obj/machinery/abductor/experiment/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "ProbingConsole", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, "ProbingConsole", name) ui.open() /obj/machinery/abductor/experiment/ui_data(mob/user) diff --git a/code/modules/antagonists/changeling/cellular_emporium.dm b/code/modules/antagonists/changeling/cellular_emporium.dm index bc433ef0cbbd..aa73e6d83926 100644 --- a/code/modules/antagonists/changeling/cellular_emporium.dm +++ b/code/modules/antagonists/changeling/cellular_emporium.dm @@ -13,10 +13,13 @@ changeling = null . = ..() -/datum/cellular_emporium/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.always_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/datum/cellular_emporium/ui_state(mob/user) + return GLOB.always_state + +/datum/cellular_emporium/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "CellularEmporium", name, 900, 480, master_ui, state) + ui = new(user, src, "CellularEmporium", name) ui.open() /datum/cellular_emporium/ui_data(mob/user) diff --git a/code/modules/antagonists/changeling/changeling_power.dm b/code/modules/antagonists/changeling/changeling_power.dm index 2466f54692b4..da82cc2c608d 100644 --- a/code/modules/antagonists/changeling/changeling_power.dm +++ b/code/modules/antagonists/changeling/changeling_power.dm @@ -1,98 +1,98 @@ -/* - * Don't use the apostrophe in name or desc. Causes script errors.//probably no longer true - */ - -/datum/action/changeling - name = "Prototype Sting - Debug button, ahelp this" - background_icon_state = "bg_changeling" - icon_icon = 'icons/mob/actions/actions_changeling.dmi' - var/needs_button = TRUE//for passive abilities like hivemind that dont need a button - var/helptext = "" // Details - var/chemical_cost = 0 // negative chemical cost is for passive abilities (chemical glands) - var/dna_cost = -1 //cost of the sting in dna points. 0 = auto-purchase (see changeling.dm), -1 = cannot be purchased - var/req_dna = 0 //amount of dna needed to use this ability. Changelings always have atleast 1 - var/req_human = 0 //if you need to be human to use this ability - var/req_absorbs = 0 //similar to req_dna, but only gained from absorbing, not DNA sting - var/req_stat = CONSCIOUS // CONSCIOUS, UNCONSCIOUS or DEAD - var/ignores_fakedeath = FALSE // usable with the FAKEDEATH flag - var/active = FALSE//used by a few powers that toggle - -/* -changeling code now relies on on_purchase to grant powers. -if you override it, MAKE SURE you call parent or it will not be usable -the same goes for Remove(). if you override Remove(), call parent or else your power wont be removed on respec -*/ - -/datum/action/changeling/proc/on_purchase(mob/user, is_respec) - if(!is_respec) - SSblackbox.record_feedback("tally", "changeling_power_purchase", 1, name) - if(needs_button) - Grant(user)//how powers are added rather than the checks in mob.dm - -/datum/action/changeling/Trigger() - var/mob/user = owner - if(!user || !user.mind || !user.mind.has_antag_datum(/datum/antagonist/changeling)) - return - try_to_sting(user) - -/** - *Contrary to the name, this proc isn't just used by changeling stings. It handles the activation of the action and the deducation of its cost. - *The order of the proc chain is: - *can_sting(). Should this fail, the process gets aborted early. - *sting_action(). This proc usually handles the actual effect of the action. - *Should sting_action succeed the following will be done: - *sting_feedback(). Produces feedback on the performed action. Don't ask me why this isn't handled in sting_action() - *The deduction of the cost of this power. - *Returns TRUE on a successful activation. - */ -/datum/action/changeling/proc/try_to_sting(mob/user, mob/target) - if(!can_sting(user, target)) - return FALSE - var/datum/antagonist/changeling/c = user.mind.has_antag_datum(/datum/antagonist/changeling) - if(sting_action(user, target)) - sting_feedback(user, target) - c.chem_charges -= chemical_cost - return TRUE - return FALSE - -/datum/action/changeling/proc/sting_action(mob/user, mob/target) - SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("[name]")) - return 0 - -/datum/action/changeling/proc/sting_feedback(mob/user, mob/target) - return 0 - -//Fairly important to remember to return 1 on success >.< - -/datum/action/changeling/proc/can_sting(mob/living/user, mob/target) - if(!ishuman(user) && !ismonkey(user)) //typecast everything from mob to carbon from this point onwards - return FALSE - if(req_human && !ishuman(user)) - to_chat(user, "We cannot do that in this form!") - return FALSE - var/datum/antagonist/changeling/c = user.mind.has_antag_datum(/datum/antagonist/changeling) - if(c.chem_charges < chemical_cost) - to_chat(user, "We require at least [chemical_cost] unit\s of chemicals to do that!") - return FALSE - if(c.absorbedcount < req_dna) - to_chat(user, "We require at least [req_dna] sample\s of compatible DNA.") - return FALSE - if(c.trueabsorbs < req_absorbs) - to_chat(user, "We require at least [req_absorbs] sample\s of DNA gained through our Absorb ability.") - return FALSE - if(req_stat < user.stat) - to_chat(user, "We are incapacitated.") - return FALSE - if((HAS_TRAIT(user, TRAIT_DEATHCOMA)) && (!ignores_fakedeath)) - to_chat(user, "We are incapacitated.") - return FALSE - return TRUE - -/datum/action/changeling/proc/can_be_used_by(mob/user) - if(!user || QDELETED(user)) - return 0 - if(!ishuman(user) && !ismonkey(user)) - return FALSE - if(req_human && !ishuman(user)) - return FALSE - return TRUE +/* + * Don't use the apostrophe in name or desc. Causes script errors.//probably no longer true + */ + +/datum/action/changeling + name = "Prototype Sting - Debug button, ahelp this" + background_icon_state = "bg_changeling" + icon_icon = 'icons/mob/actions/actions_changeling.dmi' + var/needs_button = TRUE//for passive abilities like hivemind that dont need a button + var/helptext = "" // Details + var/chemical_cost = 0 // negative chemical cost is for passive abilities (chemical glands) + var/dna_cost = -1 //cost of the sting in dna points. 0 = auto-purchase (see changeling.dm), -1 = cannot be purchased + var/req_dna = 0 //amount of dna needed to use this ability. Changelings always have atleast 1 + var/req_human = 0 //if you need to be human to use this ability + var/req_absorbs = 0 //similar to req_dna, but only gained from absorbing, not DNA sting + var/req_stat = CONSCIOUS // CONSCIOUS, UNCONSCIOUS or DEAD + var/ignores_fakedeath = FALSE // usable with the FAKEDEATH flag + var/active = FALSE//used by a few powers that toggle + +/* +changeling code now relies on on_purchase to grant powers. +if you override it, MAKE SURE you call parent or it will not be usable +the same goes for Remove(). if you override Remove(), call parent or else your power wont be removed on respec +*/ + +/datum/action/changeling/proc/on_purchase(mob/user, is_respec) + if(!is_respec) + SSblackbox.record_feedback("tally", "changeling_power_purchase", 1, name) + if(needs_button) + Grant(user)//how powers are added rather than the checks in mob.dm + +/datum/action/changeling/Trigger() + var/mob/user = owner + if(!user || !user.mind || !user.mind.has_antag_datum(/datum/antagonist/changeling)) + return + try_to_sting(user) + +/** + *Contrary to the name, this proc isn't just used by changeling stings. It handles the activation of the action and the deducation of its cost. + *The order of the proc chain is: + *can_sting(). Should this fail, the process gets aborted early. + *sting_action(). This proc usually handles the actual effect of the action. + *Should sting_action succeed the following will be done: + *sting_feedback(). Produces feedback on the performed action. Don't ask me why this isn't handled in sting_action() + *The deduction of the cost of this power. + *Returns TRUE on a successful activation. + */ +/datum/action/changeling/proc/try_to_sting(mob/user, mob/target) + if(!can_sting(user, target)) + return FALSE + var/datum/antagonist/changeling/c = user.mind.has_antag_datum(/datum/antagonist/changeling) + if(sting_action(user, target)) + sting_feedback(user, target) + c.chem_charges -= chemical_cost + return TRUE + return FALSE + +/datum/action/changeling/proc/sting_action(mob/user, mob/target) + SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("[name]")) + return 0 + +/datum/action/changeling/proc/sting_feedback(mob/user, mob/target) + return 0 + +//Fairly important to remember to return 1 on success >.< + +/datum/action/changeling/proc/can_sting(mob/living/user, mob/target) + if(!ishuman(user) && !ismonkey(user)) //typecast everything from mob to carbon from this point onwards + return FALSE + if(req_human && !ishuman(user)) + to_chat(user, "We cannot do that in this form!") + return FALSE + var/datum/antagonist/changeling/c = user.mind.has_antag_datum(/datum/antagonist/changeling) + if(c.chem_charges < chemical_cost) + to_chat(user, "We require at least [chemical_cost] unit\s of chemicals to do that!") + return FALSE + if(c.absorbedcount < req_dna) + to_chat(user, "We require at least [req_dna] sample\s of compatible DNA.") + return FALSE + if(c.trueabsorbs < req_absorbs) + to_chat(user, "We require at least [req_absorbs] sample\s of DNA gained through our Absorb ability.") + return FALSE + if(req_stat < user.stat) + to_chat(user, "We are incapacitated.") + return FALSE + if((HAS_TRAIT(user, TRAIT_DEATHCOMA)) && (!ignores_fakedeath)) + to_chat(user, "We are incapacitated.") + return FALSE + return TRUE + +/datum/action/changeling/proc/can_be_used_by(mob/user) + if(!user || QDELETED(user)) + return 0 + if(!ishuman(user) && !ismonkey(user)) + return FALSE + if(req_human && !ishuman(user)) + return FALSE + return TRUE diff --git a/code/modules/antagonists/changeling/powers/absorb.dm b/code/modules/antagonists/changeling/powers/absorb.dm index d9a75b0b1357..33e0a8f75d48 100644 --- a/code/modules/antagonists/changeling/powers/absorb.dm +++ b/code/modules/antagonists/changeling/powers/absorb.dm @@ -1,145 +1,145 @@ -/datum/action/changeling/absorbDNA - name = "Absorb DNA" - desc = "Absorb the DNA of our victim. Requires us to strangle them." - button_icon_state = "absorb_dna" - chemical_cost = 0 - dna_cost = 0 - req_human = 1 - -/datum/action/changeling/absorbDNA/can_sting(mob/living/carbon/user) - if(!..()) - return - - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - if(changeling.isabsorbing) - to_chat(user, "We are already absorbing!") - return - - if(!user.pulling || !iscarbon(user.pulling)) - to_chat(user, "We must be grabbing a creature to absorb them!") - return - if(user.grab_state <= GRAB_NECK) - to_chat(user, "We must have a tighter grip to absorb this creature!") - return - - var/mob/living/carbon/target = user.pulling - return changeling.can_absorb_dna(target) - - - -/datum/action/changeling/absorbDNA/sting_action(mob/user) - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - var/mob/living/carbon/human/target = user.pulling - changeling.isabsorbing = 1 - for(var/i in 1 to 3) - switch(i) - if(1) - to_chat(user, "This creature is compatible. We must hold still...") - if(2) - user.visible_message("[user] extends a proboscis!", "We extend a proboscis.") - if(3) - user.visible_message("[user] stabs [target] with the proboscis!", "We stab [target] with the proboscis.") - to_chat(target, "You feel a sharp stabbing pain!") - target.take_overall_damage(40) - - SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("Absorb DNA", "[i]")) - if(!do_mob(user, target, 150)) - to_chat(user, "Our absorption of [target] has been interrupted!") - changeling.isabsorbing = 0 - return - - SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("Absorb DNA", "4")) - user.visible_message("[user] sucks the fluids from [target]!", "We have absorbed [target].") - to_chat(target, "You are absorbed by the changeling!") - - if(!changeling.has_dna(target.dna)) - changeling.add_new_profile(target) - changeling.trueabsorbs++ - - if(user.nutrition < NUTRITION_LEVEL_WELL_FED) - user.set_nutrition(min((user.nutrition + target.nutrition), NUTRITION_LEVEL_WELL_FED)) - - // Absorb a lizard, speak Draconic. - owner.copy_languages(target, LANGUAGE_ABSORB) - - if(target.mind && user.mind)//if the victim and user have minds - var/datum/mind/suckedbrain = target.mind - user.mind.memory += "
                    We've absorbed [target]'s memories into our own...
                    [suckedbrain.memory]
                    " - for(var/A in suckedbrain.antag_datums) - var/datum/antagonist/antag_types = A - var/list/all_objectives = antag_types.objectives.Copy() - if(antag_types.antag_memory) - user.mind.memory += "[antag_types.antag_memory]
                    " - if(LAZYLEN(all_objectives)) - user.mind.memory += "Objectives:" - var/obj_count = 1 - for(var/O in all_objectives) - var/datum/objective/objective = O - user.mind.memory += "
                    Objective #[obj_count++]: [objective.explanation_text]" - var/list/datum/mind/other_owners = objective.get_owners() - suckedbrain - if(other_owners.len) - user.mind.memory += "
                      " - for(var/mind in other_owners) - var/datum/mind/M = mind - user.mind.memory += "
                    • Conspirator: [M.name]
                    • " - user.mind.memory += "
                    " - user.mind.memory += "That's all [target] had.
                    " - user.memory() //I can read your mind, kekeke. Output all their notes. - - //Some of target's recent speech, so the changeling can attempt to imitate them better. - //Recent as opposed to all because rounds tend to have a LOT of text. - - var/list/recent_speech = list() - var/list/say_log = list() - var/log_source = target.logging - for(var/log_type in log_source) - var/nlog_type = text2num(log_type) - if(nlog_type & LOG_SAY) - var/list/reversed = log_source[log_type] - if(islist(reversed)) - say_log = reverseRange(reversed.Copy()) - break - - if(LAZYLEN(say_log) > LING_ABSORB_RECENT_SPEECH) - recent_speech = say_log.Copy(say_log.len-LING_ABSORB_RECENT_SPEECH+1,0) //0 so len-LING_ARS+1 to end of list - else - for(var/spoken_memory in say_log) - if(recent_speech.len >= LING_ABSORB_RECENT_SPEECH) - break - recent_speech[spoken_memory] = say_log[spoken_memory] - - if(recent_speech.len) - changeling.antag_memory += "Some of [target]'s speech patterns, we should study these to better impersonate [target.p_them()]!
                    " - to_chat(user, "Some of [target]'s speech patterns, we should study these to better impersonate [target.p_them()]!") - for(var/spoken_memory in recent_speech) - changeling.antag_memory += "\"[recent_speech[spoken_memory]]\"
                    " - to_chat(user, "\"[recent_speech[spoken_memory]]\"") - changeling.antag_memory += "We have no more knowledge of [target]'s speech patterns.
                    " - to_chat(user, "We have no more knowledge of [target]'s speech patterns.") - - - var/datum/antagonist/changeling/target_ling = target.mind.has_antag_datum(/datum/antagonist/changeling) - if(target_ling)//If the target was a changeling, suck out their extra juice and objective points! - to_chat(user, "[target] was one of us. We have absorbed their power.") - target_ling.remove_changeling_powers() - changeling.geneticpoints += round(target_ling.geneticpoints/2) - target_ling.geneticpoints = 0 - target_ling.canrespec = 0 - changeling.chem_storage += round(target_ling.chem_storage/2) - changeling.chem_charges += min(target_ling.chem_charges, changeling.chem_storage) - target_ling.chem_charges = 0 - target_ling.chem_storage = 0 - changeling.absorbedcount += (target_ling.absorbedcount) - target_ling.stored_profiles.len = 1 - target_ling.absorbedcount = 0 - target_ling.was_absorbed = TRUE - - - changeling.chem_charges=min(changeling.chem_charges+10, changeling.chem_storage) - - changeling.isabsorbing = 0 - changeling.canrespec = 1 - - target.death(0) - target.Drain() - return TRUE +/datum/action/changeling/absorbDNA + name = "Absorb DNA" + desc = "Absorb the DNA of our victim. Requires us to strangle them." + button_icon_state = "absorb_dna" + chemical_cost = 0 + dna_cost = 0 + req_human = 1 + +/datum/action/changeling/absorbDNA/can_sting(mob/living/carbon/user) + if(!..()) + return + + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + if(changeling.isabsorbing) + to_chat(user, "We are already absorbing!") + return + + if(!user.pulling || !iscarbon(user.pulling)) + to_chat(user, "We must be grabbing a creature to absorb them!") + return + if(user.grab_state <= GRAB_NECK) + to_chat(user, "We must have a tighter grip to absorb this creature!") + return + + var/mob/living/carbon/target = user.pulling + return changeling.can_absorb_dna(target) + + + +/datum/action/changeling/absorbDNA/sting_action(mob/user) + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + var/mob/living/carbon/human/target = user.pulling + changeling.isabsorbing = 1 + for(var/i in 1 to 3) + switch(i) + if(1) + to_chat(user, "This creature is compatible. We must hold still...") + if(2) + user.visible_message("[user] extends a proboscis!", "We extend a proboscis.") + if(3) + user.visible_message("[user] stabs [target] with the proboscis!", "We stab [target] with the proboscis.") + to_chat(target, "You feel a sharp stabbing pain!") + target.take_overall_damage(40) + + SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("Absorb DNA", "[i]")) + if(!do_mob(user, target, 150)) + to_chat(user, "Our absorption of [target] has been interrupted!") + changeling.isabsorbing = 0 + return + + SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("Absorb DNA", "4")) + user.visible_message("[user] sucks the fluids from [target]!", "We have absorbed [target].") + to_chat(target, "You are absorbed by the changeling!") + + if(!changeling.has_dna(target.dna)) + changeling.add_new_profile(target) + changeling.trueabsorbs++ + + if(user.nutrition < NUTRITION_LEVEL_WELL_FED) + user.set_nutrition(min((user.nutrition + target.nutrition), NUTRITION_LEVEL_WELL_FED)) + + // Absorb a lizard, speak Draconic. + owner.copy_languages(target, LANGUAGE_ABSORB) + + if(target.mind && user.mind)//if the victim and user have minds + var/datum/mind/suckedbrain = target.mind + user.mind.memory += "
                    We've absorbed [target]'s memories into our own...
                    [suckedbrain.memory]
                    " + for(var/A in suckedbrain.antag_datums) + var/datum/antagonist/antag_types = A + var/list/all_objectives = antag_types.objectives.Copy() + if(antag_types.antag_memory) + user.mind.memory += "[antag_types.antag_memory]
                    " + if(LAZYLEN(all_objectives)) + user.mind.memory += "Objectives:" + var/obj_count = 1 + for(var/O in all_objectives) + var/datum/objective/objective = O + user.mind.memory += "
                    Objective #[obj_count++]: [objective.explanation_text]" + var/list/datum/mind/other_owners = objective.get_owners() - suckedbrain + if(other_owners.len) + user.mind.memory += "
                      " + for(var/mind in other_owners) + var/datum/mind/M = mind + user.mind.memory += "
                    • Conspirator: [M.name]
                    • " + user.mind.memory += "
                    " + user.mind.memory += "That's all [target] had.
                    " + user.memory() //I can read your mind, kekeke. Output all their notes. + + //Some of target's recent speech, so the changeling can attempt to imitate them better. + //Recent as opposed to all because rounds tend to have a LOT of text. + + var/list/recent_speech = list() + var/list/say_log = list() + var/log_source = target.logging + for(var/log_type in log_source) + var/nlog_type = text2num(log_type) + if(nlog_type & LOG_SAY) + var/list/reversed = log_source[log_type] + if(islist(reversed)) + say_log = reverseRange(reversed.Copy()) + break + + if(LAZYLEN(say_log) > LING_ABSORB_RECENT_SPEECH) + recent_speech = say_log.Copy(say_log.len-LING_ABSORB_RECENT_SPEECH+1,0) //0 so len-LING_ARS+1 to end of list + else + for(var/spoken_memory in say_log) + if(recent_speech.len >= LING_ABSORB_RECENT_SPEECH) + break + recent_speech[spoken_memory] = say_log[spoken_memory] + + if(recent_speech.len) + changeling.antag_memory += "Some of [target]'s speech patterns, we should study these to better impersonate [target.p_them()]!
                    " + to_chat(user, "Some of [target]'s speech patterns, we should study these to better impersonate [target.p_them()]!") + for(var/spoken_memory in recent_speech) + changeling.antag_memory += "\"[recent_speech[spoken_memory]]\"
                    " + to_chat(user, "\"[recent_speech[spoken_memory]]\"") + changeling.antag_memory += "We have no more knowledge of [target]'s speech patterns.
                    " + to_chat(user, "We have no more knowledge of [target]'s speech patterns.") + + + var/datum/antagonist/changeling/target_ling = target.mind.has_antag_datum(/datum/antagonist/changeling) + if(target_ling)//If the target was a changeling, suck out their extra juice and objective points! + to_chat(user, "[target] was one of us. We have absorbed their power.") + target_ling.remove_changeling_powers() + changeling.geneticpoints += round(target_ling.geneticpoints/2) + target_ling.geneticpoints = 0 + target_ling.canrespec = 0 + changeling.chem_storage += round(target_ling.chem_storage/2) + changeling.chem_charges += min(target_ling.chem_charges, changeling.chem_storage) + target_ling.chem_charges = 0 + target_ling.chem_storage = 0 + changeling.absorbedcount += (target_ling.absorbedcount) + target_ling.stored_profiles.len = 1 + target_ling.absorbedcount = 0 + target_ling.was_absorbed = TRUE + + + changeling.chem_charges=min(changeling.chem_charges+10, changeling.chem_storage) + + changeling.isabsorbing = 0 + changeling.canrespec = 1 + + target.death(0) + target.Drain() + return TRUE diff --git a/code/modules/antagonists/changeling/powers/digitalcamo.dm b/code/modules/antagonists/changeling/powers/digitalcamo.dm index 028f85b83e89..dbe034397ca2 100644 --- a/code/modules/antagonists/changeling/powers/digitalcamo.dm +++ b/code/modules/antagonists/changeling/powers/digitalcamo.dm @@ -1,23 +1,23 @@ -/datum/action/changeling/digitalcamo - name = "Digital Camouflage" - desc = "By evolving the ability to distort our form and proportions, we defeat common algorithms used to detect lifeforms on cameras." - helptext = "We cannot be tracked by camera or seen by AI units while using this skill. However, humans looking at us will find us... uncanny." - button_icon_state = "digital_camo" - dna_cost = 1 - active = FALSE - -//Prevents AIs tracking you but makes you easily detectable to the human-eye. -/datum/action/changeling/digitalcamo/sting_action(mob/user) - ..() - if(active) - to_chat(user, "We return to normal.") - user.RemoveElement(/datum/element/digitalcamo) - else - to_chat(user, "We distort our form to hide from the AI.") - user.AddElement(/datum/element/digitalcamo) - active = !active - return TRUE - -/datum/action/changeling/digitalcamo/Remove(mob/user) - user.RemoveElement(/datum/element/digitalcamo) - ..() +/datum/action/changeling/digitalcamo + name = "Digital Camouflage" + desc = "By evolving the ability to distort our form and proportions, we defeat common algorithms used to detect lifeforms on cameras." + helptext = "We cannot be tracked by camera or seen by AI units while using this skill. However, humans looking at us will find us... uncanny." + button_icon_state = "digital_camo" + dna_cost = 1 + active = FALSE + +//Prevents AIs tracking you but makes you easily detectable to the human-eye. +/datum/action/changeling/digitalcamo/sting_action(mob/user) + ..() + if(active) + to_chat(user, "We return to normal.") + user.RemoveElement(/datum/element/digitalcamo) + else + to_chat(user, "We distort our form to hide from the AI.") + user.AddElement(/datum/element/digitalcamo) + active = !active + return TRUE + +/datum/action/changeling/digitalcamo/Remove(mob/user) + user.RemoveElement(/datum/element/digitalcamo) + ..() diff --git a/code/modules/antagonists/changeling/powers/fakedeath.dm b/code/modules/antagonists/changeling/powers/fakedeath.dm index f4c1a1f23f28..d81f5a81e91c 100644 --- a/code/modules/antagonists/changeling/powers/fakedeath.dm +++ b/code/modules/antagonists/changeling/powers/fakedeath.dm @@ -1,71 +1,71 @@ -/datum/action/changeling/fakedeath - name = "Reviving Stasis" - desc = "We fall into a stasis, allowing us to regenerate and trick our enemies. Costs 15 chemicals." - button_icon_state = "fake_death" - chemical_cost = 15 - dna_cost = 0 - req_dna = 1 - req_stat = DEAD - ignores_fakedeath = TRUE - var/revive_ready = FALSE - -//Fake our own death and fully heal. You will appear to be dead but regenerate fully after a short delay. -/datum/action/changeling/fakedeath/sting_action(mob/living/user) - ..() - if(revive_ready) - INVOKE_ASYNC(src, .proc/revive, user) - revive_ready = FALSE - name = "Reviving Stasis" - desc = "We fall into a stasis, allowing us to regenerate and trick our enemies." - button_icon_state = "fake_death" - UpdateButtonIcon() - chemical_cost = 15 - to_chat(user, "We have revived ourselves.") - else - to_chat(user, "We begin our stasis, preparing energy to arise once more.") - user.fakedeath("changeling") //play dead - user.update_stat() - user.update_mobility() - addtimer(CALLBACK(src, .proc/ready_to_regenerate, user), LING_FAKEDEATH_TIME, TIMER_UNIQUE) - return TRUE - -/datum/action/changeling/fakedeath/proc/revive(mob/living/user) - if(!user || !istype(user)) - return - user.cure_fakedeath("changeling") - user.revive(full_heal = TRUE, admin_revive = FALSE) - var/list/missing = user.get_missing_limbs() - missing -= BODY_ZONE_HEAD // headless changelings are funny - if(missing.len) - playsound(user, 'sound/magic/demon_consume.ogg', 50, TRUE) - user.visible_message("[user]'s missing limbs \ - reform, making a loud, grotesque sound!", - "Your limbs regrow, making a \ - loud, crunchy sound and giving you great pain!", - "You hear organic matter ripping \ - and tearing!") - user.emote("scream") - user.regenerate_limbs(0, list(BODY_ZONE_HEAD)) - user.regenerate_organs() - -/datum/action/changeling/fakedeath/proc/ready_to_regenerate(mob/user) - if(user && user.mind) - var/datum/antagonist/changeling/C = user.mind.has_antag_datum(/datum/antagonist/changeling) - if(C && C.purchasedpowers) - to_chat(user, "We are ready to revive.") - name = "Revive" - desc = "We arise once more." - button_icon_state = "revive" - UpdateButtonIcon() - chemical_cost = 0 - revive_ready = TRUE - -/datum/action/changeling/fakedeath/can_sting(mob/living/user) - if(HAS_TRAIT_FROM(user, TRAIT_DEATHCOMA, "changeling") && !revive_ready) - to_chat(user, "We are already reviving.") - return - if(!user.stat && !revive_ready) //Confirmation for living changelings if they want to fake their death - switch(alert("Are we sure we wish to fake our own death?",,"Yes", "No")) - if("No") - return - return ..() +/datum/action/changeling/fakedeath + name = "Reviving Stasis" + desc = "We fall into a stasis, allowing us to regenerate and trick our enemies. Costs 15 chemicals." + button_icon_state = "fake_death" + chemical_cost = 15 + dna_cost = 0 + req_dna = 1 + req_stat = DEAD + ignores_fakedeath = TRUE + var/revive_ready = FALSE + +//Fake our own death and fully heal. You will appear to be dead but regenerate fully after a short delay. +/datum/action/changeling/fakedeath/sting_action(mob/living/user) + ..() + if(revive_ready) + INVOKE_ASYNC(src, .proc/revive, user) + revive_ready = FALSE + name = "Reviving Stasis" + desc = "We fall into a stasis, allowing us to regenerate and trick our enemies." + button_icon_state = "fake_death" + UpdateButtonIcon() + chemical_cost = 15 + to_chat(user, "We have revived ourselves.") + else + to_chat(user, "We begin our stasis, preparing energy to arise once more.") + user.fakedeath("changeling") //play dead + user.update_stat() + user.update_mobility() + addtimer(CALLBACK(src, .proc/ready_to_regenerate, user), LING_FAKEDEATH_TIME, TIMER_UNIQUE) + return TRUE + +/datum/action/changeling/fakedeath/proc/revive(mob/living/user) + if(!user || !istype(user)) + return + user.cure_fakedeath("changeling") + user.revive(full_heal = TRUE, admin_revive = FALSE) + var/list/missing = user.get_missing_limbs() + missing -= BODY_ZONE_HEAD // headless changelings are funny + if(missing.len) + playsound(user, 'sound/magic/demon_consume.ogg', 50, TRUE) + user.visible_message("[user]'s missing limbs \ + reform, making a loud, grotesque sound!", + "Your limbs regrow, making a \ + loud, crunchy sound and giving you great pain!", + "You hear organic matter ripping \ + and tearing!") + user.emote("scream") + user.regenerate_limbs(0, list(BODY_ZONE_HEAD)) + user.regenerate_organs() + +/datum/action/changeling/fakedeath/proc/ready_to_regenerate(mob/user) + if(user && user.mind) + var/datum/antagonist/changeling/C = user.mind.has_antag_datum(/datum/antagonist/changeling) + if(C && C.purchasedpowers) + to_chat(user, "We are ready to revive.") + name = "Revive" + desc = "We arise once more." + button_icon_state = "revive" + UpdateButtonIcon() + chemical_cost = 0 + revive_ready = TRUE + +/datum/action/changeling/fakedeath/can_sting(mob/living/user) + if(HAS_TRAIT_FROM(user, TRAIT_DEATHCOMA, "changeling") && !revive_ready) + to_chat(user, "We are already reviving.") + return + if(!user.stat && !revive_ready) //Confirmation for living changelings if they want to fake their death + switch(alert("Are we sure we wish to fake our own death?",,"Yes", "No")) + if("No") + return + return ..() diff --git a/code/modules/antagonists/changeling/powers/fleshmend.dm b/code/modules/antagonists/changeling/powers/fleshmend.dm index ff33afe856ce..83c4c933a455 100644 --- a/code/modules/antagonists/changeling/powers/fleshmend.dm +++ b/code/modules/antagonists/changeling/powers/fleshmend.dm @@ -1,21 +1,21 @@ -/datum/action/changeling/fleshmend - name = "Fleshmend" - desc = "Our flesh rapidly regenerates, healing our burns, bruises, and shortness of breath. Costs 20 chemicals." - helptext = "If we are on fire, the healing effect will not function. Does not regrow limbs or restore lost blood. Functions while unconscious." - button_icon_state = "fleshmend" - chemical_cost = 20 - dna_cost = 2 - req_stat = UNCONSCIOUS - -//Starts healing you every second for 10 seconds. -//Can be used whilst unconscious. -/datum/action/changeling/fleshmend/sting_action(mob/living/user) - if(user.has_status_effect(STATUS_EFFECT_FLESHMEND)) - to_chat(user, "We are already fleshmending!") - return - ..() - to_chat(user, "We begin to heal rapidly.") - user.apply_status_effect(STATUS_EFFECT_FLESHMEND) - return TRUE - -//Check buffs.dm for the fleshmend status effect code +/datum/action/changeling/fleshmend + name = "Fleshmend" + desc = "Our flesh rapidly regenerates, healing our burns, bruises, and shortness of breath. Costs 20 chemicals." + helptext = "If we are on fire, the healing effect will not function. Does not regrow limbs or restore lost blood. Functions while unconscious." + button_icon_state = "fleshmend" + chemical_cost = 20 + dna_cost = 2 + req_stat = UNCONSCIOUS + +//Starts healing you every second for 10 seconds. +//Can be used whilst unconscious. +/datum/action/changeling/fleshmend/sting_action(mob/living/user) + if(user.has_status_effect(STATUS_EFFECT_FLESHMEND)) + to_chat(user, "We are already fleshmending!") + return + ..() + to_chat(user, "We begin to heal rapidly.") + user.apply_status_effect(STATUS_EFFECT_FLESHMEND) + return TRUE + +//Check buffs.dm for the fleshmend status effect code diff --git a/code/modules/antagonists/changeling/powers/hivemind.dm b/code/modules/antagonists/changeling/powers/hivemind.dm index 6f503755a6ee..ece6b788d3cd 100644 --- a/code/modules/antagonists/changeling/powers/hivemind.dm +++ b/code/modules/antagonists/changeling/powers/hivemind.dm @@ -1,117 +1,117 @@ -//HIVEMIND COMMUNICATION (:g) -/datum/action/changeling/hivemind_comms - name = "Hivemind Communication" - desc = "We tune our senses to the airwaves to allow us to discreetly communicate and exchange DNA with other changelings." - helptext = "We will be able to talk with other changelings with :g. Exchanged DNA do not count towards absorb objectives." - needs_button = FALSE - dna_cost = 0 - chemical_cost = -1 - -/datum/action/changeling/hivemind_comms/on_purchase(mob/user, is_respec) - ..() - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - changeling.changeling_speak = 1 - to_chat(user, "Use say \"[MODE_TOKEN_CHANGELING] message\" to communicate with the other changelings.") - var/datum/action/changeling/hivemind_upload/S1 = new - if(!changeling.has_sting(S1)) - S1.Grant(user) - changeling.purchasedpowers+=S1 - var/datum/action/changeling/hivemind_download/S2 = new - if(!changeling.has_sting(S2)) - S2.Grant(user) - changeling.purchasedpowers+=S2 - -/datum/action/changeling/hivemind_comms/Remove(mob/user) - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - if(changeling.changeling_speak) - changeling.changeling_speak = FALSE - for(var/p in changeling.purchasedpowers) - var/datum/action/changeling/otherpower = p - if(istype(otherpower, /datum/action/changeling/hivemind_upload) || istype(otherpower, /datum/action/changeling/hivemind_download)) - changeling.purchasedpowers -= otherpower - otherpower.Remove(changeling.owner.current) - ..() - - -// HIVE MIND UPLOAD/DOWNLOAD DNA -GLOBAL_LIST_EMPTY(hivemind_bank) - -/datum/action/changeling/hivemind_upload - name = "Hive Channel DNA" - desc = "Allows us to channel DNA in the airwaves to allow other changelings to absorb it. Costs 10 chemicals." - button_icon_state = "hivemind_channel" - chemical_cost = 10 - dna_cost = -1 - -/datum/action/changeling/hivemind_upload/sting_action(var/mob/living/user) - if (HAS_TRAIT(user, CHANGELING_HIVEMIND_MUTE)) - to_chat(user, "The poison in the air hinders our ability to interact with the hivemind.") - return - ..() - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - var/list/names = list() - for(var/datum/changelingprofile/prof in changeling.stored_profiles) - if(!(prof in GLOB.hivemind_bank)) - names += prof.name - - if(names.len <= 0) - to_chat(user, "The airwaves already have all of our DNA!") - return - - var/chosen_name = input("Select a DNA to channel: ", "Channel DNA", null) as null|anything in sortList(names) - if(!chosen_name) - return - - var/datum/changelingprofile/chosen_dna = changeling.get_dna(chosen_name) - if(!chosen_dna) - return - - var/datum/changelingprofile/uploaded_dna = new chosen_dna.type - chosen_dna.copy_profile(uploaded_dna) - GLOB.hivemind_bank += uploaded_dna - to_chat(user, "We channel the DNA of [chosen_name] to the air.") - return TRUE - -/datum/action/changeling/hivemind_download - name = "Hive Absorb DNA" - desc = "Allows us to absorb DNA that has been channeled to the airwaves. Does not count towards absorb objectives. Costs 10 chemicals." - button_icon_state = "hive_absorb" - chemical_cost = 10 - dna_cost = -1 - -/datum/action/changeling/hivemind_download/can_sting(mob/living/carbon/user) - if(!..()) - return - if (HAS_TRAIT(user, CHANGELING_HIVEMIND_MUTE)) - to_chat(user, "The poison in the air hinders our ability to interact with the hivemind.") - return - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - var/datum/changelingprofile/first_prof = changeling.stored_profiles[1] - if(first_prof.name == user.real_name)//If our current DNA is the stalest, we gotta ditch it. - to_chat(user, "We have reached our capacity to store genetic information! We must transform before absorbing more.") - return - return 1 - -/datum/action/changeling/hivemind_download/sting_action(mob/user) - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - var/list/names = list() - for(var/datum/changelingprofile/prof in GLOB.hivemind_bank) - if(!(prof in changeling.stored_profiles)) - names[prof.name] = prof - - if(names.len <= 0) - to_chat(user, "There's no new DNA to absorb from the air!") - return - - var/S = input("Select a DNA absorb from the air: ", "Absorb DNA", null) as null|anything in sortList(names) - if(!S) - return - var/datum/changelingprofile/chosen_prof = names[S] - if(!chosen_prof) - return - ..() - var/datum/changelingprofile/downloaded_prof = new chosen_prof.type - chosen_prof.copy_profile(downloaded_prof) - changeling.add_profile(downloaded_prof) - to_chat(user, "We absorb the DNA of [S] from the air.") - return TRUE +//HIVEMIND COMMUNICATION (:g) +/datum/action/changeling/hivemind_comms + name = "Hivemind Communication" + desc = "We tune our senses to the airwaves to allow us to discreetly communicate and exchange DNA with other changelings." + helptext = "We will be able to talk with other changelings with :g. Exchanged DNA do not count towards absorb objectives." + needs_button = FALSE + dna_cost = 0 + chemical_cost = -1 + +/datum/action/changeling/hivemind_comms/on_purchase(mob/user, is_respec) + ..() + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + changeling.changeling_speak = 1 + to_chat(user, "Use say \"[MODE_TOKEN_CHANGELING] message\" to communicate with the other changelings.") + var/datum/action/changeling/hivemind_upload/S1 = new + if(!changeling.has_sting(S1)) + S1.Grant(user) + changeling.purchasedpowers+=S1 + var/datum/action/changeling/hivemind_download/S2 = new + if(!changeling.has_sting(S2)) + S2.Grant(user) + changeling.purchasedpowers+=S2 + +/datum/action/changeling/hivemind_comms/Remove(mob/user) + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + if(changeling.changeling_speak) + changeling.changeling_speak = FALSE + for(var/p in changeling.purchasedpowers) + var/datum/action/changeling/otherpower = p + if(istype(otherpower, /datum/action/changeling/hivemind_upload) || istype(otherpower, /datum/action/changeling/hivemind_download)) + changeling.purchasedpowers -= otherpower + otherpower.Remove(changeling.owner.current) + ..() + + +// HIVE MIND UPLOAD/DOWNLOAD DNA +GLOBAL_LIST_EMPTY(hivemind_bank) + +/datum/action/changeling/hivemind_upload + name = "Hive Channel DNA" + desc = "Allows us to channel DNA in the airwaves to allow other changelings to absorb it. Costs 10 chemicals." + button_icon_state = "hivemind_channel" + chemical_cost = 10 + dna_cost = -1 + +/datum/action/changeling/hivemind_upload/sting_action(var/mob/living/user) + if (HAS_TRAIT(user, CHANGELING_HIVEMIND_MUTE)) + to_chat(user, "The poison in the air hinders our ability to interact with the hivemind.") + return + ..() + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + var/list/names = list() + for(var/datum/changelingprofile/prof in changeling.stored_profiles) + if(!(prof in GLOB.hivemind_bank)) + names += prof.name + + if(names.len <= 0) + to_chat(user, "The airwaves already have all of our DNA!") + return + + var/chosen_name = input("Select a DNA to channel: ", "Channel DNA", null) as null|anything in sortList(names) + if(!chosen_name) + return + + var/datum/changelingprofile/chosen_dna = changeling.get_dna(chosen_name) + if(!chosen_dna) + return + + var/datum/changelingprofile/uploaded_dna = new chosen_dna.type + chosen_dna.copy_profile(uploaded_dna) + GLOB.hivemind_bank += uploaded_dna + to_chat(user, "We channel the DNA of [chosen_name] to the air.") + return TRUE + +/datum/action/changeling/hivemind_download + name = "Hive Absorb DNA" + desc = "Allows us to absorb DNA that has been channeled to the airwaves. Does not count towards absorb objectives. Costs 10 chemicals." + button_icon_state = "hive_absorb" + chemical_cost = 10 + dna_cost = -1 + +/datum/action/changeling/hivemind_download/can_sting(mob/living/carbon/user) + if(!..()) + return + if (HAS_TRAIT(user, CHANGELING_HIVEMIND_MUTE)) + to_chat(user, "The poison in the air hinders our ability to interact with the hivemind.") + return + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + var/datum/changelingprofile/first_prof = changeling.stored_profiles[1] + if(first_prof.name == user.real_name)//If our current DNA is the stalest, we gotta ditch it. + to_chat(user, "We have reached our capacity to store genetic information! We must transform before absorbing more.") + return + return 1 + +/datum/action/changeling/hivemind_download/sting_action(mob/user) + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + var/list/names = list() + for(var/datum/changelingprofile/prof in GLOB.hivemind_bank) + if(!(prof in changeling.stored_profiles)) + names[prof.name] = prof + + if(names.len <= 0) + to_chat(user, "There's no new DNA to absorb from the air!") + return + + var/S = input("Select a DNA absorb from the air: ", "Absorb DNA", null) as null|anything in sortList(names) + if(!S) + return + var/datum/changelingprofile/chosen_prof = names[S] + if(!chosen_prof) + return + ..() + var/datum/changelingprofile/downloaded_prof = new chosen_prof.type + chosen_prof.copy_profile(downloaded_prof) + changeling.add_profile(downloaded_prof) + to_chat(user, "We absorb the DNA of [S] from the air.") + return TRUE diff --git a/code/modules/antagonists/changeling/powers/humanform.dm b/code/modules/antagonists/changeling/powers/humanform.dm index 0acc59315c7f..e109a5981bd0 100644 --- a/code/modules/antagonists/changeling/powers/humanform.dm +++ b/code/modules/antagonists/changeling/powers/humanform.dm @@ -1,34 +1,34 @@ -/datum/action/changeling/humanform - name = "Human Form" - desc = "We change into a human. Costs 5 chemicals." - button_icon_state = "human_form" - chemical_cost = 5 - req_dna = 1 - -//Transform into a human. -/datum/action/changeling/humanform/sting_action(mob/living/carbon/user) - if(user.movement_type & VENTCRAWLING) - to_chat(user, "We must exit the pipes before we can transform back!") - return FALSE - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - var/list/names = list() - for(var/datum/changelingprofile/prof in changeling.stored_profiles) - names += "[prof.name]" - - var/chosen_name = input("Select the target DNA: ", "Target DNA", null) as null|anything in sortList(names) - if(!chosen_name) - return - - var/datum/changelingprofile/chosen_prof = changeling.get_dna(chosen_name) - if(!chosen_prof) - return - if(!user || user.notransform) - return FALSE - to_chat(user, "We transform our appearance.") - ..() - changeling.purchasedpowers -= src - - var/newmob = user.humanize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_KEEPSE) - - changeling_transform(newmob, chosen_prof) - return TRUE +/datum/action/changeling/humanform + name = "Human Form" + desc = "We change into a human. Costs 5 chemicals." + button_icon_state = "human_form" + chemical_cost = 5 + req_dna = 1 + +//Transform into a human. +/datum/action/changeling/humanform/sting_action(mob/living/carbon/user) + if(user.movement_type & VENTCRAWLING) + to_chat(user, "We must exit the pipes before we can transform back!") + return FALSE + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + var/list/names = list() + for(var/datum/changelingprofile/prof in changeling.stored_profiles) + names += "[prof.name]" + + var/chosen_name = input("Select the target DNA: ", "Target DNA", null) as null|anything in sortList(names) + if(!chosen_name) + return + + var/datum/changelingprofile/chosen_prof = changeling.get_dna(chosen_name) + if(!chosen_prof) + return + if(!user || user.notransform) + return FALSE + to_chat(user, "We transform our appearance.") + ..() + changeling.purchasedpowers -= src + + var/newmob = user.humanize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_KEEPSE) + + changeling_transform(newmob, chosen_prof) + return TRUE diff --git a/code/modules/antagonists/changeling/powers/lesserform.dm b/code/modules/antagonists/changeling/powers/lesserform.dm index 03ea226e9e52..7a1c83e243ea 100644 --- a/code/modules/antagonists/changeling/powers/lesserform.dm +++ b/code/modules/antagonists/changeling/powers/lesserform.dm @@ -1,17 +1,17 @@ -/datum/action/changeling/lesserform - name = "Lesser Form" - desc = "We debase ourselves and become lesser. We become a monkey. Costs 5 chemicals." - helptext = "The transformation greatly reduces our size, allowing us to slip out of cuffs and climb through vents." - button_icon_state = "lesser_form" - chemical_cost = 5 - dna_cost = 1 - req_human = 1 - -//Transform into a monkey. -/datum/action/changeling/lesserform/sting_action(mob/living/carbon/human/user) - if(!user || user.notransform) - return FALSE - to_chat(user, "Our genes cry out!") - ..() - user.monkeyize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_KEEPSE) - return TRUE +/datum/action/changeling/lesserform + name = "Lesser Form" + desc = "We debase ourselves and become lesser. We become a monkey. Costs 5 chemicals." + helptext = "The transformation greatly reduces our size, allowing us to slip out of cuffs and climb through vents." + button_icon_state = "lesser_form" + chemical_cost = 5 + dna_cost = 1 + req_human = 1 + +//Transform into a monkey. +/datum/action/changeling/lesserform/sting_action(mob/living/carbon/human/user) + if(!user || user.notransform) + return FALSE + to_chat(user, "Our genes cry out!") + ..() + user.monkeyize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_KEEPSE) + return TRUE diff --git a/code/modules/antagonists/changeling/powers/mimic_voice.dm b/code/modules/antagonists/changeling/powers/mimic_voice.dm index 41d17eb3d470..d337bddda0d3 100644 --- a/code/modules/antagonists/changeling/powers/mimic_voice.dm +++ b/code/modules/antagonists/changeling/powers/mimic_voice.dm @@ -1,27 +1,27 @@ -/datum/action/changeling/mimicvoice - name = "Mimic Voice" - desc = "We shape our vocal glands to sound like a desired voice. Maintaining this power slows chemical production." - button_icon_state = "mimic_voice" - helptext = "Will turn your voice into the name that you enter. We must constantly expend chemicals to maintain our form like this." - chemical_cost = 0//constant chemical drain hardcoded - dna_cost = 1 - req_human = 1 - -// Fake Voice -/datum/action/changeling/mimicvoice/sting_action(mob/user) - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - if(changeling.mimicing) - changeling.mimicing = "" - changeling.chem_recharge_slowdown -= 0.5 - to_chat(user, "We return our vocal glands to their original position.") - return - - var/mimic_voice = sanitize_name(stripped_input(user, "Enter a name to mimic.", "Mimic Voice", null, MAX_NAME_LEN)) - if(!mimic_voice) - return - ..() - changeling.mimicing = mimic_voice - changeling.chem_recharge_slowdown += 0.5 - to_chat(user, "We shape our glands to take the voice of [mimic_voice], this will slow down regenerating chemicals while active.") - to_chat(user, "Use this power again to return to our original voice and return chemical production to normal levels.") - return TRUE +/datum/action/changeling/mimicvoice + name = "Mimic Voice" + desc = "We shape our vocal glands to sound like a desired voice. Maintaining this power slows chemical production." + button_icon_state = "mimic_voice" + helptext = "Will turn your voice into the name that you enter. We must constantly expend chemicals to maintain our form like this." + chemical_cost = 0//constant chemical drain hardcoded + dna_cost = 1 + req_human = 1 + +// Fake Voice +/datum/action/changeling/mimicvoice/sting_action(mob/user) + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + if(changeling.mimicing) + changeling.mimicing = "" + changeling.chem_recharge_slowdown -= 0.5 + to_chat(user, "We return our vocal glands to their original position.") + return + + var/mimic_voice = sanitize_name(stripped_input(user, "Enter a name to mimic.", "Mimic Voice", null, MAX_NAME_LEN)) + if(!mimic_voice) + return + ..() + changeling.mimicing = mimic_voice + changeling.chem_recharge_slowdown += 0.5 + to_chat(user, "We shape our glands to take the voice of [mimic_voice], this will slow down regenerating chemicals while active.") + to_chat(user, "Use this power again to return to our original voice and return chemical production to normal levels.") + return TRUE diff --git a/code/modules/antagonists/changeling/powers/panacea.dm b/code/modules/antagonists/changeling/powers/panacea.dm index 75c6332ac418..7ac592e54846 100644 --- a/code/modules/antagonists/changeling/powers/panacea.dm +++ b/code/modules/antagonists/changeling/powers/panacea.dm @@ -1,55 +1,55 @@ -/datum/action/changeling/panacea - name = "Anatomic Panacea" - desc = "Expels impurifications from our form; curing diseases, removing parasites, sobering us, purging toxins and radiation, curing traumas and brain damage, and resetting our genetic code completely. Costs 20 chemicals." - helptext = "Can be used while unconscious." - button_icon_state = "panacea" - chemical_cost = 20 - dna_cost = 1 - req_stat = UNCONSCIOUS - -//Heals the things that the other regenerative abilities don't. -/datum/action/changeling/panacea/sting_action(mob/user) - to_chat(user, "We cleanse impurities from our form.") - ..() - var/list/bad_organs = list( - user.getorgan(/obj/item/organ/body_egg), - user.getorgan(/obj/item/organ/zombie_infection)) - - for(var/o in bad_organs) - var/obj/item/organ/O = o - if(!istype(O)) - continue - - O.Remove(user) - if(iscarbon(user)) - var/mob/living/carbon/C = user - C.vomit(0, toxic = TRUE) - O.forceMove(get_turf(user)) - - var/mob/living/simple_animal/borer/B = user.has_brain_worms() //Wasp Begin - Borers - if(B) - if(B.controlling) - B.detatch() - B.leave_victim() - if(iscarbon(user)) - var/mob/living/carbon/C = user - C.vomit(0, toxic = TRUE) - to_chat(user, "A parasite exits our form.") //Wasp End - - user.reagents.add_reagent(/datum/reagent/medicine/mutadone, 10) - user.reagents.add_reagent(/datum/reagent/medicine/pen_acid, 20) - user.reagents.add_reagent(/datum/reagent/medicine/antihol, 10) - user.reagents.add_reagent(/datum/reagent/medicine/mannitol, 25) - - if(iscarbon(user)) - var/mob/living/carbon/C = user - C.cure_all_traumas(TRAUMA_RESILIENCE_LOBOTOMY) - - if(isliving(user)) - var/mob/living/L = user - for(var/thing in L.diseases) - var/datum/disease/D = thing - if(D.severity == DISEASE_SEVERITY_POSITIVE) - continue - D.cure() - return TRUE +/datum/action/changeling/panacea + name = "Anatomic Panacea" + desc = "Expels impurifications from our form; curing diseases, removing parasites, sobering us, purging toxins and radiation, curing traumas and brain damage, and resetting our genetic code completely. Costs 20 chemicals." + helptext = "Can be used while unconscious." + button_icon_state = "panacea" + chemical_cost = 20 + dna_cost = 1 + req_stat = UNCONSCIOUS + +//Heals the things that the other regenerative abilities don't. +/datum/action/changeling/panacea/sting_action(mob/user) + to_chat(user, "We cleanse impurities from our form.") + ..() + var/list/bad_organs = list( + user.getorgan(/obj/item/organ/body_egg), + user.getorgan(/obj/item/organ/zombie_infection)) + + for(var/o in bad_organs) + var/obj/item/organ/O = o + if(!istype(O)) + continue + + O.Remove(user) + if(iscarbon(user)) + var/mob/living/carbon/C = user + C.vomit(0, toxic = TRUE) + O.forceMove(get_turf(user)) + + var/mob/living/simple_animal/borer/B = user.has_brain_worms() //Wasp Begin - Borers + if(B) + if(B.controlling) + B.detatch() + B.leave_victim() + if(iscarbon(user)) + var/mob/living/carbon/C = user + C.vomit(0, toxic = TRUE) + to_chat(user, "A parasite exits our form.") //Wasp End + + user.reagents.add_reagent(/datum/reagent/medicine/mutadone, 10) + user.reagents.add_reagent(/datum/reagent/medicine/pen_acid, 20) + user.reagents.add_reagent(/datum/reagent/medicine/antihol, 10) + user.reagents.add_reagent(/datum/reagent/medicine/mannitol, 25) + + if(iscarbon(user)) + var/mob/living/carbon/C = user + C.cure_all_traumas(TRAUMA_RESILIENCE_LOBOTOMY) + + if(isliving(user)) + var/mob/living/L = user + for(var/thing in L.diseases) + var/datum/disease/D = thing + if(D.severity == DISEASE_SEVERITY_POSITIVE) + continue + D.cure() + return TRUE diff --git a/code/modules/antagonists/changeling/powers/shriek.dm b/code/modules/antagonists/changeling/powers/shriek.dm index 6b5be5d23662..2a3b2e1fbe35 100644 --- a/code/modules/antagonists/changeling/powers/shriek.dm +++ b/code/modules/antagonists/changeling/powers/shriek.dm @@ -1,45 +1,45 @@ -/datum/action/changeling/resonant_shriek - name = "Resonant Shriek" - desc = "Our lungs and vocal cords shift, allowing us to briefly emit a noise that deafens and confuses the weak-minded. Costs 20 chemicals." - helptext = "Emits a high-frequency sound that confuses and deafens humans, blows out nearby lights and overloads cyborg sensors." - button_icon_state = "resonant_shriek" - chemical_cost = 20 - dna_cost = 1 - req_human = 1 - -//A flashy ability, good for crowd control and sowing chaos. -/datum/action/changeling/resonant_shriek/sting_action(mob/user) - ..() - for(var/mob/living/M in get_hearers_in_view(4, user)) - if(iscarbon(M)) - var/mob/living/carbon/C = M - if(!C.mind || !C.mind.has_antag_datum(/datum/antagonist/changeling)) - C.adjustEarDamage(0, 30) - C.confused += 25 - C.Jitter(50) - else - SEND_SOUND(C, sound('sound/effects/screech.ogg')) - - if(issilicon(M)) - SEND_SOUND(M, sound('sound/weapons/flash.ogg')) - M.Paralyze(rand(100,200)) - - for(var/obj/machinery/light/L in range(4, user)) - L.on = 1 - L.break_light_tube() - return TRUE - -/datum/action/changeling/dissonant_shriek - name = "Dissonant Shriek" - desc = "We shift our vocal cords to release a high-frequency sound that overloads nearby electronics. Costs 20 chemicals." - button_icon_state = "dissonant_shriek" - chemical_cost = 20 - dna_cost = 1 - -/datum/action/changeling/dissonant_shriek/sting_action(mob/user) - ..() - for(var/obj/machinery/light/L in range(5, usr)) - L.on = 1 - L.break_light_tube() - empulse(get_turf(user), 2, 5, 1) - return TRUE +/datum/action/changeling/resonant_shriek + name = "Resonant Shriek" + desc = "Our lungs and vocal cords shift, allowing us to briefly emit a noise that deafens and confuses the weak-minded. Costs 20 chemicals." + helptext = "Emits a high-frequency sound that confuses and deafens humans, blows out nearby lights and overloads cyborg sensors." + button_icon_state = "resonant_shriek" + chemical_cost = 20 + dna_cost = 1 + req_human = 1 + +//A flashy ability, good for crowd control and sowing chaos. +/datum/action/changeling/resonant_shriek/sting_action(mob/user) + ..() + for(var/mob/living/M in get_hearers_in_view(4, user)) + if(iscarbon(M)) + var/mob/living/carbon/C = M + if(!C.mind || !C.mind.has_antag_datum(/datum/antagonist/changeling)) + C.adjustEarDamage(0, 30) + C.confused += 25 + C.Jitter(50) + else + SEND_SOUND(C, sound('sound/effects/screech.ogg')) + + if(issilicon(M)) + SEND_SOUND(M, sound('sound/weapons/flash.ogg')) + M.Paralyze(rand(100,200)) + + for(var/obj/machinery/light/L in range(4, user)) + L.on = 1 + L.break_light_tube() + return TRUE + +/datum/action/changeling/dissonant_shriek + name = "Dissonant Shriek" + desc = "We shift our vocal cords to release a high-frequency sound that overloads nearby electronics. Costs 20 chemicals." + button_icon_state = "dissonant_shriek" + chemical_cost = 20 + dna_cost = 1 + +/datum/action/changeling/dissonant_shriek/sting_action(mob/user) + ..() + for(var/obj/machinery/light/L in range(5, usr)) + L.on = 1 + L.break_light_tube() + empulse(get_turf(user), 2, 5, 1) + return TRUE diff --git a/code/modules/antagonists/changeling/powers/spiders.dm b/code/modules/antagonists/changeling/powers/spiders.dm index bfc695bf8757..ccd412fb2235 100644 --- a/code/modules/antagonists/changeling/powers/spiders.dm +++ b/code/modules/antagonists/changeling/powers/spiders.dm @@ -1,14 +1,14 @@ -/datum/action/changeling/spiders - name = "Spread Infestation" - desc = "Our form divides, creating arachnids which will grow into deadly beasts. Costs 45 chemicals." - helptext = "The spiders are thoughtless creatures, and may attack their creators when fully grown. Requires at least 3 DNA absorptions." - button_icon_state = "spread_infestation" - chemical_cost = 45 - dna_cost = 1 - req_absorbs = 3 - -//Makes some spiderlings. Good for setting traps and causing general trouble. -/datum/action/changeling/spiders/sting_action(mob/user) - ..() - spawn_atom_to_turf(/obj/structure/spider/spiderling/hunter, user, 2, FALSE) - return TRUE +/datum/action/changeling/spiders + name = "Spread Infestation" + desc = "Our form divides, creating arachnids which will grow into deadly beasts. Costs 45 chemicals." + helptext = "The spiders are thoughtless creatures, and may attack their creators when fully grown. Requires at least 3 DNA absorptions." + button_icon_state = "spread_infestation" + chemical_cost = 45 + dna_cost = 1 + req_absorbs = 3 + +//Makes some spiderlings. Good for setting traps and causing general trouble. +/datum/action/changeling/spiders/sting_action(mob/user) + ..() + spawn_atom_to_turf(/obj/structure/spider/spiderling/hunter, user, 2, FALSE) + return TRUE diff --git a/code/modules/antagonists/changeling/powers/tiny_prick.dm b/code/modules/antagonists/changeling/powers/tiny_prick.dm index 4e03ac8f2816..d919b7f4ec73 100644 --- a/code/modules/antagonists/changeling/powers/tiny_prick.dm +++ b/code/modules/antagonists/changeling/powers/tiny_prick.dm @@ -1,243 +1,243 @@ -/datum/action/changeling/sting//parent path, not meant for users afaik - name = "Tiny Prick" - desc = "Stabby stabby" - -/datum/action/changeling/sting/Trigger() - var/mob/user = owner - if(!user || !user.mind) - return - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - if(!changeling) - return - if(!changeling.chosen_sting) - set_sting(user) - else - unset_sting(user) - return - -/datum/action/changeling/sting/proc/set_sting(mob/user) - to_chat(user, "We prepare our sting. Alt+click or click the middle mouse button on a target to sting them.") - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - changeling.chosen_sting = src - - user.hud_used.lingstingdisplay.icon_state = button_icon_state - user.hud_used.lingstingdisplay.invisibility = 0 - -/datum/action/changeling/sting/proc/unset_sting(mob/user) - to_chat(user, "We retract our sting, we can't sting anyone for now.") - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - changeling.chosen_sting = null - - user.hud_used.lingstingdisplay.icon_state = null - user.hud_used.lingstingdisplay.invisibility = INVISIBILITY_ABSTRACT - -/mob/living/carbon/proc/unset_sting() - if(mind) - var/datum/antagonist/changeling/changeling = mind.has_antag_datum(/datum/antagonist/changeling) - if(changeling && changeling.chosen_sting) - changeling.chosen_sting.unset_sting(src) - -/datum/action/changeling/sting/can_sting(mob/user, mob/target) - if(!..()) - return - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - if(!changeling.chosen_sting) - to_chat(user, "We haven't prepared our sting yet!") - if(!iscarbon(target)) - return - if(!isturf(user.loc)) - return - if(!AStar(user, target.loc, /turf/proc/Distance, changeling.sting_range, simulated_only = FALSE)) - return - if(target.mind && target.mind.has_antag_datum(/datum/antagonist/changeling)) - sting_feedback(user, target) - changeling.chem_charges -= chemical_cost - return 1 - -/datum/action/changeling/sting/sting_feedback(mob/user, mob/target) - if(!target) - return - to_chat(user, "We stealthily sting [target.name].") - if(target.mind && target.mind.has_antag_datum(/datum/antagonist/changeling)) - to_chat(target, "You feel a tiny prick.") - return 1 - - -/datum/action/changeling/sting/transformation - name = "Transformation Sting" - desc = "We silently sting a human, injecting a retrovirus that forces them to transform. Costs 50 chemicals." - helptext = "The victim will transform much like a changeling would. Does not provide a warning to others. Mutations will not be transferred, and monkeys will become human." - button_icon_state = "sting_transform" - chemical_cost = 50 - dna_cost = 3 - var/datum/changelingprofile/selected_dna = null - -/datum/action/changeling/sting/transformation/Trigger() - var/mob/user = usr - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - if(changeling.chosen_sting) - unset_sting(user) - return - selected_dna = changeling.select_dna("Select the target DNA: ", "Target DNA") - if(!selected_dna) - return - if(NOTRANSSTING in selected_dna.dna.species.species_traits) - to_chat(user, "That DNA is not compatible with changeling retrovirus!") - return - ..() - -/datum/action/changeling/sting/transformation/can_sting(mob/user, mob/living/carbon/target) - if(!..()) - return - if((HAS_TRAIT(target, TRAIT_HUSK)) || !iscarbon(target) || (NOTRANSSTING in target.dna.species.species_traits)) - to_chat(user, "Our sting appears ineffective against its DNA.") - return 0 - return 1 - -/datum/action/changeling/sting/transformation/sting_action(mob/user, mob/target) - log_combat(user, target, "stung", "transformation sting", " new identity is '[selected_dna.dna.real_name]'") - var/datum/dna/NewDNA = selected_dna.dna - if(ismonkey(target)) - to_chat(user, "Our genes cry out as we sting [target.name]!") - - var/mob/living/carbon/C = target - . = TRUE - if(istype(C)) - C.real_name = NewDNA.real_name - NewDNA.transfer_identity(C) - if(ismonkey(C)) - C.humanize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_DEFAULTMSG) - C.updateappearance(mutcolor_update=1) - - -/datum/action/changeling/sting/false_armblade - name = "False Armblade Sting" - desc = "We silently sting a human, injecting a retrovirus that mutates their arm to temporarily appear as an armblade. Costs 20 chemicals." - helptext = "The victim will form an armblade much like a changeling would, except the armblade is dull and useless." - button_icon_state = "sting_armblade" - chemical_cost = 20 - dna_cost = 1 - -/obj/item/melee/arm_blade/false - desc = "A grotesque mass of flesh that used to be your arm. Although it looks dangerous at first, you can tell it's actually quite dull and useless." - force = 5 //Basically as strong as a punch - fake = TRUE - -/datum/action/changeling/sting/false_armblade/can_sting(mob/user, mob/target) - if(!..()) - return - if(isliving(target)) - var/mob/living/L = target - if((HAS_TRAIT(L, TRAIT_HUSK)) || !L.has_dna()) - to_chat(user, "Our sting appears ineffective against its DNA.") - return 0 - return 1 - -/datum/action/changeling/sting/false_armblade/sting_action(mob/user, mob/target) - log_combat(user, target, "stung", object="false armblade sting") - - var/obj/item/held = target.get_active_held_item() - if(held && !target.dropItemToGround(held)) - to_chat(user, "[held] is stuck to [target.p_their()] hand, you cannot grow a false armblade over it!") - return - ..() - if(ismonkey(target)) - to_chat(user, "Our genes cry out as we sting [target.name]!") - - var/obj/item/melee/arm_blade/false/blade = new(target,1) - target.put_in_hands(blade) - target.visible_message("A grotesque blade forms around [target.name]\'s arm!", "Your arm twists and mutates, transforming into a horrific monstrosity!", "You hear organic matter ripping and tearing!") - playsound(target, 'sound/effects/blobattack.ogg', 30, TRUE) - - addtimer(CALLBACK(src, .proc/remove_fake, target, blade), 600) - return TRUE - -/datum/action/changeling/sting/false_armblade/proc/remove_fake(mob/target, obj/item/melee/arm_blade/false/blade) - playsound(target, 'sound/effects/blobattack.ogg', 30, TRUE) - target.visible_message("With a sickening crunch, \ - [target] reforms [target.p_their()] [blade.name] into an arm!", - "[blade] reforms back to normal.", - "Your eyes burn horrifically!") - target.become_nearsighted(EYE_DAMAGE) - target.blind_eyes(20) - target.blur_eyes(40) - return TRUE - -/datum/action/changeling/sting/LSD - name = "Hallucination Sting" - desc = "We cause mass terror to our victim." - helptext = "We evolve the ability to sting a target with a powerful hallucinogenic chemical. The target does not notice they have been stung, and the effect occurs after 30 to 60 seconds." - button_icon_state = "sting_lsd" - chemical_cost = 10 - dna_cost = 1 - -/datum/action/changeling/sting/LSD/sting_action(mob/user, mob/living/carbon/target) - log_combat(user, target, "stung", "LSD sting") - addtimer(CALLBACK(src, .proc/hallucination_time, target), rand(300,600)) - return TRUE - -/datum/action/changeling/sting/LSD/proc/hallucination_time(mob/living/carbon/target) - if(target) - target.hallucination = max(90, target.hallucination) - -/datum/action/changeling/sting/cryo - name = "Cryogenic Sting" - desc = "We silently sting our victim with a cocktail of chemicals that freezes them from the inside. Costs 15 chemicals." - helptext = "Does not provide a warning to the victim, though they will likely realize they are suddenly freezing." - button_icon_state = "sting_cryo" - chemical_cost = 15 - dna_cost = 2 - -/datum/action/changeling/sting/cryo/sting_action(mob/user, mob/target) - log_combat(user, target, "stung", "cryo sting") - if(target.reagents) - target.reagents.add_reagent(/datum/reagent/consumable/frostoil, 30) - return TRUE +/datum/action/changeling/sting//parent path, not meant for users afaik + name = "Tiny Prick" + desc = "Stabby stabby" + +/datum/action/changeling/sting/Trigger() + var/mob/user = owner + if(!user || !user.mind) + return + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + if(!changeling) + return + if(!changeling.chosen_sting) + set_sting(user) + else + unset_sting(user) + return + +/datum/action/changeling/sting/proc/set_sting(mob/user) + to_chat(user, "We prepare our sting. Alt+click or click the middle mouse button on a target to sting them.") + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + changeling.chosen_sting = src + + user.hud_used.lingstingdisplay.icon_state = button_icon_state + user.hud_used.lingstingdisplay.invisibility = 0 + +/datum/action/changeling/sting/proc/unset_sting(mob/user) + to_chat(user, "We retract our sting, we can't sting anyone for now.") + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + changeling.chosen_sting = null + + user.hud_used.lingstingdisplay.icon_state = null + user.hud_used.lingstingdisplay.invisibility = INVISIBILITY_ABSTRACT + +/mob/living/carbon/proc/unset_sting() + if(mind) + var/datum/antagonist/changeling/changeling = mind.has_antag_datum(/datum/antagonist/changeling) + if(changeling && changeling.chosen_sting) + changeling.chosen_sting.unset_sting(src) + +/datum/action/changeling/sting/can_sting(mob/user, mob/target) + if(!..()) + return + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + if(!changeling.chosen_sting) + to_chat(user, "We haven't prepared our sting yet!") + if(!iscarbon(target)) + return + if(!isturf(user.loc)) + return + if(!AStar(user, target.loc, /turf/proc/Distance, changeling.sting_range, simulated_only = FALSE)) + return + if(target.mind && target.mind.has_antag_datum(/datum/antagonist/changeling)) + sting_feedback(user, target) + changeling.chem_charges -= chemical_cost + return 1 + +/datum/action/changeling/sting/sting_feedback(mob/user, mob/target) + if(!target) + return + to_chat(user, "We stealthily sting [target.name].") + if(target.mind && target.mind.has_antag_datum(/datum/antagonist/changeling)) + to_chat(target, "You feel a tiny prick.") + return 1 + + +/datum/action/changeling/sting/transformation + name = "Transformation Sting" + desc = "We silently sting a human, injecting a retrovirus that forces them to transform. Costs 50 chemicals." + helptext = "The victim will transform much like a changeling would. Does not provide a warning to others. Mutations will not be transferred, and monkeys will become human." + button_icon_state = "sting_transform" + chemical_cost = 50 + dna_cost = 3 + var/datum/changelingprofile/selected_dna = null + +/datum/action/changeling/sting/transformation/Trigger() + var/mob/user = usr + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + if(changeling.chosen_sting) + unset_sting(user) + return + selected_dna = changeling.select_dna("Select the target DNA: ", "Target DNA") + if(!selected_dna) + return + if(NOTRANSSTING in selected_dna.dna.species.species_traits) + to_chat(user, "That DNA is not compatible with changeling retrovirus!") + return + ..() + +/datum/action/changeling/sting/transformation/can_sting(mob/user, mob/living/carbon/target) + if(!..()) + return + if((HAS_TRAIT(target, TRAIT_HUSK)) || !iscarbon(target) || (NOTRANSSTING in target.dna.species.species_traits)) + to_chat(user, "Our sting appears ineffective against its DNA.") + return 0 + return 1 + +/datum/action/changeling/sting/transformation/sting_action(mob/user, mob/target) + log_combat(user, target, "stung", "transformation sting", " new identity is '[selected_dna.dna.real_name]'") + var/datum/dna/NewDNA = selected_dna.dna + if(ismonkey(target)) + to_chat(user, "Our genes cry out as we sting [target.name]!") + + var/mob/living/carbon/C = target + . = TRUE + if(istype(C)) + C.real_name = NewDNA.real_name + NewDNA.transfer_identity(C) + if(ismonkey(C)) + C.humanize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_DEFAULTMSG) + C.updateappearance(mutcolor_update=1) + + +/datum/action/changeling/sting/false_armblade + name = "False Armblade Sting" + desc = "We silently sting a human, injecting a retrovirus that mutates their arm to temporarily appear as an armblade. Costs 20 chemicals." + helptext = "The victim will form an armblade much like a changeling would, except the armblade is dull and useless." + button_icon_state = "sting_armblade" + chemical_cost = 20 + dna_cost = 1 + +/obj/item/melee/arm_blade/false + desc = "A grotesque mass of flesh that used to be your arm. Although it looks dangerous at first, you can tell it's actually quite dull and useless." + force = 5 //Basically as strong as a punch + fake = TRUE + +/datum/action/changeling/sting/false_armblade/can_sting(mob/user, mob/target) + if(!..()) + return + if(isliving(target)) + var/mob/living/L = target + if((HAS_TRAIT(L, TRAIT_HUSK)) || !L.has_dna()) + to_chat(user, "Our sting appears ineffective against its DNA.") + return 0 + return 1 + +/datum/action/changeling/sting/false_armblade/sting_action(mob/user, mob/target) + log_combat(user, target, "stung", object="false armblade sting") + + var/obj/item/held = target.get_active_held_item() + if(held && !target.dropItemToGround(held)) + to_chat(user, "[held] is stuck to [target.p_their()] hand, you cannot grow a false armblade over it!") + return + ..() + if(ismonkey(target)) + to_chat(user, "Our genes cry out as we sting [target.name]!") + + var/obj/item/melee/arm_blade/false/blade = new(target,1) + target.put_in_hands(blade) + target.visible_message("A grotesque blade forms around [target.name]\'s arm!", "Your arm twists and mutates, transforming into a horrific monstrosity!", "You hear organic matter ripping and tearing!") + playsound(target, 'sound/effects/blobattack.ogg', 30, TRUE) + + addtimer(CALLBACK(src, .proc/remove_fake, target, blade), 600) + return TRUE + +/datum/action/changeling/sting/false_armblade/proc/remove_fake(mob/target, obj/item/melee/arm_blade/false/blade) + playsound(target, 'sound/effects/blobattack.ogg', 30, TRUE) + target.visible_message("With a sickening crunch, \ + [target] reforms [target.p_their()] [blade.name] into an arm!", + "[blade] reforms back to normal.", + "Your eyes burn horrifically!") + target.become_nearsighted(EYE_DAMAGE) + target.blind_eyes(20) + target.blur_eyes(40) + return TRUE + +/datum/action/changeling/sting/LSD + name = "Hallucination Sting" + desc = "We cause mass terror to our victim." + helptext = "We evolve the ability to sting a target with a powerful hallucinogenic chemical. The target does not notice they have been stung, and the effect occurs after 30 to 60 seconds." + button_icon_state = "sting_lsd" + chemical_cost = 10 + dna_cost = 1 + +/datum/action/changeling/sting/LSD/sting_action(mob/user, mob/living/carbon/target) + log_combat(user, target, "stung", "LSD sting") + addtimer(CALLBACK(src, .proc/hallucination_time, target), rand(300,600)) + return TRUE + +/datum/action/changeling/sting/LSD/proc/hallucination_time(mob/living/carbon/target) + if(target) + target.hallucination = max(90, target.hallucination) + +/datum/action/changeling/sting/cryo + name = "Cryogenic Sting" + desc = "We silently sting our victim with a cocktail of chemicals that freezes them from the inside. Costs 15 chemicals." + helptext = "Does not provide a warning to the victim, though they will likely realize they are suddenly freezing." + button_icon_state = "sting_cryo" + chemical_cost = 15 + dna_cost = 2 + +/datum/action/changeling/sting/cryo/sting_action(mob/user, mob/target) + log_combat(user, target, "stung", "cryo sting") + if(target.reagents) + target.reagents.add_reagent(/datum/reagent/consumable/frostoil, 30) + return TRUE diff --git a/code/modules/antagonists/changeling/powers/transform.dm b/code/modules/antagonists/changeling/powers/transform.dm index 4f5e9a6d07a3..5afcfafc7961 100644 --- a/code/modules/antagonists/changeling/powers/transform.dm +++ b/code/modules/antagonists/changeling/powers/transform.dm @@ -1,170 +1,170 @@ -/datum/action/changeling/transform - name = "Transform" - desc = "We take on the appearance and voice of one we have absorbed. Costs 5 chemicals." - button_icon_state = "transform" - chemical_cost = 5 - dna_cost = 0 - req_dna = 1 - req_human = 1 - -/obj/item/clothing/glasses/changeling - name = "flesh" - item_flags = DROPDEL - -/obj/item/clothing/glasses/changeling/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/item/clothing/glasses/changeling/attack_hand(mob/user) - if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) - to_chat(user, "You reabsorb [src] into your body.") - qdel(src) - return - . = ..() - -/obj/item/clothing/under/changeling - name = "flesh" - item_flags = DROPDEL - -/obj/item/clothing/under/changeling/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/item/clothing/under/changeling/attack_hand(mob/user) - if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) - to_chat(user, "You reabsorb [src] into your body.") - qdel(src) - return - . = ..() - -/obj/item/clothing/suit/changeling - name = "flesh" - allowed = list(/obj/item/changeling) - item_flags = DROPDEL - -/obj/item/clothing/suit/changeling/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/item/clothing/suit/changeling/attack_hand(mob/user) - if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) - to_chat(user, "You reabsorb [src] into your body.") - qdel(src) - return - . = ..() - -/obj/item/clothing/head/changeling - name = "flesh" - item_flags = DROPDEL - -/obj/item/clothing/head/changeling/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/item/clothing/head/changeling/attack_hand(mob/user) - if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) - to_chat(user, "You reabsorb [src] into your body.") - qdel(src) - return - . = ..() - -/obj/item/clothing/shoes/changeling - name = "flesh" - item_flags = DROPDEL - -/obj/item/clothing/shoes/changeling/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/item/clothing/shoes/changeling/attack_hand(mob/user) - if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) - to_chat(user, "You reabsorb [src] into your body.") - qdel(src) - return - . = ..() - -/obj/item/clothing/gloves/changeling - name = "flesh" - item_flags = DROPDEL - -/obj/item/clothing/gloves/changeling/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/item/clothing/gloves/changeling/attack_hand(mob/user) - if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) - to_chat(user, "You reabsorb [src] into your body.") - qdel(src) - return - . = ..() - -/obj/item/clothing/mask/changeling - name = "flesh" - item_flags = DROPDEL - -/obj/item/clothing/mask/changeling/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/item/clothing/mask/changeling/attack_hand(mob/user) - if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) - to_chat(user, "You reabsorb [src] into your body.") - qdel(src) - return - . = ..() - -/obj/item/changeling - name = "flesh" - slot_flags = ALL - allowed = list(/obj/item/changeling) - item_flags = DROPDEL - -/obj/item/changeling/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/item/changeling/attack_hand(mob/user) - if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) - to_chat(user, "You reabsorb [src] into your body.") - qdel(src) - return - . = ..() - -//Change our DNA to that of somebody we've absorbed. -/datum/action/changeling/transform/sting_action(mob/living/carbon/human/user) - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - var/datum/changelingprofile/chosen_prof = changeling.select_dna("Select the target DNA: ", "Target DNA") - - if(!chosen_prof) - return - ..() - changeling_transform(user, chosen_prof) - return TRUE - -/datum/antagonist/changeling/proc/select_dna(prompt, title) - var/mob/living/carbon/user = owner.current - if(!istype(user)) - return - var/list/names = list("Drop Flesh Disguise") - for(var/datum/changelingprofile/prof in stored_profiles) - names += "[prof.name]" - - var/chosen_name = input(prompt, title, null) as null|anything in sortList(names) - if(!chosen_name) - return - - if(chosen_name == "Drop Flesh Disguise") - for(var/slot in GLOB.slots) - if(istype(user.vars[slot], GLOB.slot2type[slot])) - qdel(user.vars[slot]) - - var/datum/changelingprofile/prof = get_dna(chosen_name) - return prof +/datum/action/changeling/transform + name = "Transform" + desc = "We take on the appearance and voice of one we have absorbed. Costs 5 chemicals." + button_icon_state = "transform" + chemical_cost = 5 + dna_cost = 0 + req_dna = 1 + req_human = 1 + +/obj/item/clothing/glasses/changeling + name = "flesh" + item_flags = DROPDEL + +/obj/item/clothing/glasses/changeling/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/clothing/glasses/changeling/attack_hand(mob/user) + if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) + to_chat(user, "You reabsorb [src] into your body.") + qdel(src) + return + . = ..() + +/obj/item/clothing/under/changeling + name = "flesh" + item_flags = DROPDEL + +/obj/item/clothing/under/changeling/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/clothing/under/changeling/attack_hand(mob/user) + if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) + to_chat(user, "You reabsorb [src] into your body.") + qdel(src) + return + . = ..() + +/obj/item/clothing/suit/changeling + name = "flesh" + allowed = list(/obj/item/changeling) + item_flags = DROPDEL + +/obj/item/clothing/suit/changeling/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/clothing/suit/changeling/attack_hand(mob/user) + if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) + to_chat(user, "You reabsorb [src] into your body.") + qdel(src) + return + . = ..() + +/obj/item/clothing/head/changeling + name = "flesh" + item_flags = DROPDEL + +/obj/item/clothing/head/changeling/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/clothing/head/changeling/attack_hand(mob/user) + if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) + to_chat(user, "You reabsorb [src] into your body.") + qdel(src) + return + . = ..() + +/obj/item/clothing/shoes/changeling + name = "flesh" + item_flags = DROPDEL + +/obj/item/clothing/shoes/changeling/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/clothing/shoes/changeling/attack_hand(mob/user) + if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) + to_chat(user, "You reabsorb [src] into your body.") + qdel(src) + return + . = ..() + +/obj/item/clothing/gloves/changeling + name = "flesh" + item_flags = DROPDEL + +/obj/item/clothing/gloves/changeling/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/clothing/gloves/changeling/attack_hand(mob/user) + if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) + to_chat(user, "You reabsorb [src] into your body.") + qdel(src) + return + . = ..() + +/obj/item/clothing/mask/changeling + name = "flesh" + item_flags = DROPDEL + +/obj/item/clothing/mask/changeling/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/clothing/mask/changeling/attack_hand(mob/user) + if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) + to_chat(user, "You reabsorb [src] into your body.") + qdel(src) + return + . = ..() + +/obj/item/changeling + name = "flesh" + slot_flags = ALL + allowed = list(/obj/item/changeling) + item_flags = DROPDEL + +/obj/item/changeling/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/changeling/attack_hand(mob/user) + if(loc == user && user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling)) + to_chat(user, "You reabsorb [src] into your body.") + qdel(src) + return + . = ..() + +//Change our DNA to that of somebody we've absorbed. +/datum/action/changeling/transform/sting_action(mob/living/carbon/human/user) + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + var/datum/changelingprofile/chosen_prof = changeling.select_dna("Select the target DNA: ", "Target DNA") + + if(!chosen_prof) + return + ..() + changeling_transform(user, chosen_prof) + return TRUE + +/datum/antagonist/changeling/proc/select_dna(prompt, title) + var/mob/living/carbon/user = owner.current + if(!istype(user)) + return + var/list/names = list("Drop Flesh Disguise") + for(var/datum/changelingprofile/prof in stored_profiles) + names += "[prof.name]" + + var/chosen_name = input(prompt, title, null) as null|anything in sortList(names) + if(!chosen_name) + return + + if(chosen_name == "Drop Flesh Disguise") + for(var/slot in GLOB.slots) + if(istype(user.vars[slot], GLOB.slot2type[slot])) + qdel(user.vars[slot]) + + var/datum/changelingprofile/prof = get_dna(chosen_name) + return prof diff --git a/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm b/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm index 3e0eb307497e..b0632454ff8b 100644 --- a/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm +++ b/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm @@ -7,9 +7,6 @@ density = TRUE resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - ui_x = 350 - ui_y = 442 - var/timer_set = 90 var/minimum_timer_set = 90 var/maximum_timer_set = 3600 @@ -260,10 +257,10 @@ ui_mode = NUKEUI_AWAIT_TIMER -/obj/machinery/nuclearbomb/ui_interact(mob/user, ui_key="main", datum/tgui/ui=null, force_open=0, datum/tgui/master_ui=null, datum/ui_state/state=GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/machinery/nuclearbomb/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "NuclearBomb", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, "NuclearBomb", name) ui.open() /obj/machinery/nuclearbomb/ui_data(mob/user) diff --git a/code/modules/antagonists/nukeop/equipment/pinpointer.dm b/code/modules/antagonists/nukeop/equipment/pinpointer.dm index 8a88d68514ec..1b8efa529c94 100644 --- a/code/modules/antagonists/nukeop/equipment/pinpointer.dm +++ b/code/modules/antagonists/nukeop/equipment/pinpointer.dm @@ -1,90 +1,90 @@ -/obj/item/pinpointer/nuke - var/mode = TRACK_NUKE_DISK - -/obj/item/pinpointer/nuke/examine(mob/user) - . = ..() - var/msg = "Its tracking indicator reads " - switch(mode) - if(TRACK_NUKE_DISK) - msg += "\"nuclear_disk\"." - if(TRACK_MALF_AI) - msg += "\"01000001 01001001\"." - if(TRACK_INFILTRATOR) - msg += "\"vasvygengbefuvc\"." - else - msg = "Its tracking indicator is blank." - . += msg - for(var/obj/machinery/nuclearbomb/bomb in GLOB.machines) - if(bomb.timing) - . += "Extreme danger. Arming signal detected. Time remaining: [bomb.get_time_left()]." - -/obj/item/pinpointer/nuke/process() - ..() - if(active) // If shit's going down - for(var/obj/machinery/nuclearbomb/bomb in GLOB.nuke_list) - if(bomb.timing) - if(!alert) - alert = TRUE - playsound(src, 'sound/items/nuke_toy_lowpower.ogg', 50, FALSE) - if(isliving(loc)) - var/mob/living/L = loc - to_chat(L, "Your [name] vibrates and lets out a tinny alarm. Uh oh.") - -/obj/item/pinpointer/nuke/scan_for_target() - target = null - switch(mode) - if(TRACK_NUKE_DISK) - var/obj/item/disk/nuclear/N = locate() in GLOB.poi_list - target = N - if(TRACK_MALF_AI) - for(var/V in GLOB.ai_list) - var/mob/living/silicon/ai/A = V - if(A.nuking) - target = A - for(var/V in GLOB.apcs_list) - var/obj/machinery/power/apc/A = V - if(A.malfhack && A.occupier) - target = A - if(TRACK_INFILTRATOR) - target = SSshuttle.getShuttle("syndicate") - ..() - -/obj/item/pinpointer/nuke/proc/switch_mode_to(new_mode) - if(isliving(loc)) - var/mob/living/L = loc - to_chat(L, "Your [name] beeps as it reconfigures it's tracking algorithms.") - playsound(L, 'sound/machines/triple_beep.ogg', 50, TRUE) - mode = new_mode - scan_for_target() - -/obj/item/pinpointer/nuke/syndicate // Syndicate pinpointers automatically point towards the infiltrator once the nuke is active. - name = "syndicate pinpointer" - desc = "A handheld tracking device that locks onto certain signals. It's configured to switch tracking modes once it detects the activation signal of a nuclear device." - icon_state = "pinpointer_syndicate" - -/obj/item/pinpointer/syndicate_cyborg // Cyborg pinpointers just look for a random operative. - name = "cyborg syndicate pinpointer" - desc = "An integrated tracking device, jury-rigged to search for living Syndicate operatives." - flags_1 = NONE - -/obj/item/pinpointer/syndicate_cyborg/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CYBORG_ITEM_TRAIT) - -/obj/item/pinpointer/syndicate_cyborg/cyborg_unequip(mob/user) - if(!active) - return - toggle_on() - -/obj/item/pinpointer/syndicate_cyborg/scan_for_target() - target = null - var/list/possible_targets = list() - var/turf/here = get_turf(src) - for(var/V in get_antag_minds(/datum/antagonist/nukeop)) - var/datum/mind/M = V - if(ishuman(M.current) && M.current.stat != DEAD) - possible_targets |= M.current - var/mob/living/closest_operative = get_closest_atom(/mob/living/carbon/human, possible_targets, here) - if(closest_operative) - target = closest_operative - ..() +/obj/item/pinpointer/nuke + var/mode = TRACK_NUKE_DISK + +/obj/item/pinpointer/nuke/examine(mob/user) + . = ..() + var/msg = "Its tracking indicator reads " + switch(mode) + if(TRACK_NUKE_DISK) + msg += "\"nuclear_disk\"." + if(TRACK_MALF_AI) + msg += "\"01000001 01001001\"." + if(TRACK_INFILTRATOR) + msg += "\"vasvygengbefuvc\"." + else + msg = "Its tracking indicator is blank." + . += msg + for(var/obj/machinery/nuclearbomb/bomb in GLOB.machines) + if(bomb.timing) + . += "Extreme danger. Arming signal detected. Time remaining: [bomb.get_time_left()]." + +/obj/item/pinpointer/nuke/process() + ..() + if(active) // If shit's going down + for(var/obj/machinery/nuclearbomb/bomb in GLOB.nuke_list) + if(bomb.timing) + if(!alert) + alert = TRUE + playsound(src, 'sound/items/nuke_toy_lowpower.ogg', 50, FALSE) + if(isliving(loc)) + var/mob/living/L = loc + to_chat(L, "Your [name] vibrates and lets out a tinny alarm. Uh oh.") + +/obj/item/pinpointer/nuke/scan_for_target() + target = null + switch(mode) + if(TRACK_NUKE_DISK) + var/obj/item/disk/nuclear/N = locate() in GLOB.poi_list + target = N + if(TRACK_MALF_AI) + for(var/V in GLOB.ai_list) + var/mob/living/silicon/ai/A = V + if(A.nuking) + target = A + for(var/V in GLOB.apcs_list) + var/obj/machinery/power/apc/A = V + if(A.malfhack && A.occupier) + target = A + if(TRACK_INFILTRATOR) + target = SSshuttle.getShuttle("syndicate") + ..() + +/obj/item/pinpointer/nuke/proc/switch_mode_to(new_mode) + if(isliving(loc)) + var/mob/living/L = loc + to_chat(L, "Your [name] beeps as it reconfigures it's tracking algorithms.") + playsound(L, 'sound/machines/triple_beep.ogg', 50, TRUE) + mode = new_mode + scan_for_target() + +/obj/item/pinpointer/nuke/syndicate // Syndicate pinpointers automatically point towards the infiltrator once the nuke is active. + name = "syndicate pinpointer" + desc = "A handheld tracking device that locks onto certain signals. It's configured to switch tracking modes once it detects the activation signal of a nuclear device." + icon_state = "pinpointer_syndicate" + +/obj/item/pinpointer/syndicate_cyborg // Cyborg pinpointers just look for a random operative. + name = "cyborg syndicate pinpointer" + desc = "An integrated tracking device, jury-rigged to search for living Syndicate operatives." + flags_1 = NONE + +/obj/item/pinpointer/syndicate_cyborg/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CYBORG_ITEM_TRAIT) + +/obj/item/pinpointer/syndicate_cyborg/cyborg_unequip(mob/user) + if(!active) + return + toggle_on() + +/obj/item/pinpointer/syndicate_cyborg/scan_for_target() + target = null + var/list/possible_targets = list() + var/turf/here = get_turf(src) + for(var/V in get_antag_minds(/datum/antagonist/nukeop)) + var/datum/mind/M = V + if(ishuman(M.current) && M.current.stat != DEAD) + possible_targets |= M.current + var/mob/living/closest_operative = get_closest_atom(/mob/living/carbon/human, possible_targets, here) + if(closest_operative) + target = closest_operative + ..() diff --git a/code/modules/antagonists/traitor/datum_traitor.dm b/code/modules/antagonists/traitor/datum_traitor.dm index dc1713544a29..bad0ab0dbcb9 100644 --- a/code/modules/antagonists/traitor/datum_traitor.dm +++ b/code/modules/antagonists/traitor/datum_traitor.dm @@ -356,8 +356,8 @@ /// Proc detailing contract kit buys/completed contracts/additional info /datum/antagonist/traitor/proc/contractor_round_end() - var result = "" - var total_spent_rep = 0 + var/result = "" + var/total_spent_rep = 0 var/completed_contracts = contractor_hub.contracts_completed var/tc_total = contractor_hub.contract_TC_payed_out + contractor_hub.contract_TC_to_redeem @@ -394,7 +394,7 @@ var/phrases = jointext(GLOB.syndicate_code_phrase, ", ") var/responses = jointext(GLOB.syndicate_code_response, ", ") - var message = "
                    The code phrases were: [phrases]
                    \ + var/message = "
                    The code phrases were: [phrases]
                    \ The code responses were: [responses]
                    " return message diff --git a/code/modules/antagonists/traitor/equipment/module_picker.dm b/code/modules/antagonists/traitor/equipment/module_picker.dm index 2e586ef11239..5b3ae99ba950 100644 --- a/code/modules/antagonists/traitor/equipment/module_picker.dm +++ b/code/modules/antagonists/traitor/equipment/module_picker.dm @@ -1,8 +1,6 @@ /// The datum and interface for the malf unlock menu, which lets them choose actions to unlock. /datum/module_picker var/name = "Malfunction Modules Menu" - var/ui_x = 620 - var/ui_y = 525 var/selected_cat var/compact_mode = FALSE var/processing_time = 50 @@ -30,11 +28,13 @@ return filtered_modules -/datum/module_picker/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.always_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/datum/module_picker/ui_state(mob/user) + return GLOB.always_state + +/datum/module_picker/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "MalfunctionModulePicker", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, "MalfunctionModulePicker", name) ui.open() /datum/module_picker/ui_data(mob/user) diff --git a/code/modules/antagonists/wizard/equipment/artefact.dm b/code/modules/antagonists/wizard/equipment/artefact.dm index c5833963262f..95b9b9cb7893 100644 --- a/code/modules/antagonists/wizard/equipment/artefact.dm +++ b/code/modules/antagonists/wizard/equipment/artefact.dm @@ -1,475 +1,475 @@ - -//Apprenticeship contract - moved to antag_spawner.dm - -///////////////////////////Veil Render////////////////////// - -/obj/item/veilrender - name = "veil render" - desc = "A wicked curved blade of alien origin, recovered from the ruins of a vast city." - icon = 'icons/obj/wizard.dmi' - icon_state = "render" - item_state = "knife" - lefthand_file = 'icons/mob/inhands/equipment/kitchen_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/kitchen_righthand.dmi' - force = 15 - throwforce = 10 - w_class = WEIGHT_CLASS_NORMAL - hitsound = 'sound/weapons/bladeslice.ogg' - var/charges = 1 - var/spawn_type = /obj/singularity/wizard - var/spawn_amt = 1 - var/activate_descriptor = "reality" - var/rend_desc = "You should run now." - var/spawn_fast = 0 //if 1, ignores checking for mobs on loc before spawning - -/obj/item/veilrender/attack_self(mob/user) - if(charges > 0) - new /obj/effect/rend(get_turf(user), spawn_type, spawn_amt, rend_desc, spawn_fast) - charges-- - user.visible_message("[src] hums with power as [user] deals a blow to [activate_descriptor] itself!") - else - to_chat(user, "The unearthly energies that powered the blade are now dormant.") - -/obj/effect/rend - name = "tear in the fabric of reality" - desc = "You should run now." - icon = 'icons/effects/effects.dmi' - icon_state = "rift" - density = TRUE - anchored = TRUE - var/spawn_path = /mob/living/simple_animal/cow //defaulty cows to prevent unintentional narsies - var/spawn_amt_left = 20 - var/spawn_fast = 0 - -/obj/effect/rend/New(loc, spawn_type, spawn_amt, desc, spawn_fast) - src.spawn_path = spawn_type - src.spawn_amt_left = spawn_amt - src.desc = desc - src.spawn_fast = spawn_fast - START_PROCESSING(SSobj, src) - return - -/obj/effect/rend/process() - if(!spawn_fast) - if(locate(/mob) in loc) - return - new spawn_path(loc) - spawn_amt_left-- - if(spawn_amt_left <= 0) - qdel(src) - -/obj/effect/rend/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/nullrod)) - user.visible_message("[user] seals \the [src] with \the [I].") - qdel(src) - return - else - return ..() - -/obj/effect/rend/singularity_pull() - return - -/obj/effect/rend/singularity_pull() - return - -/obj/item/veilrender/vealrender - name = "veal render" - desc = "A wicked curved blade of alien origin, recovered from the ruins of a vast farm." - spawn_type = /mob/living/simple_animal/cow - spawn_amt = 20 - activate_descriptor = "hunger" - rend_desc = "Reverberates with the sound of ten thousand moos." - -/obj/item/veilrender/honkrender - name = "honk render" - desc = "A wicked curved blade of alien origin, recovered from the ruins of a vast circus." - spawn_type = /mob/living/simple_animal/hostile/retaliate/clown - spawn_amt = 10 - activate_descriptor = "depression" - rend_desc = "Gently wafting with the sounds of endless laughter." - icon_state = "clownrender" - -/obj/item/veilrender/honkrender/honkhulkrender - name = "superior honk render" - desc = "A wicked curved blade of alien origin, recovered from the ruins of a vast circus. This one gleams with a special light." - spawn_type = /mob/living/simple_animal/hostile/retaliate/clown/clownhulk - spawn_amt = 5 - activate_descriptor = "depression" - rend_desc = "Gently wafting with the sounds of mirthful grunting." - icon_state = "clownrender" - -////TEAR IN REALITY - -/obj/singularity/wizard - name = "tear in the fabric of reality" - desc = "This isn't right." - icon = 'icons/effects/224x224.dmi' - icon_state = "reality" - pixel_x = -96 - pixel_y = -96 - dissipate = 0 - move_self = 0 - consume_range = 3 - grav_pull = 4 - current_size = STAGE_FOUR - allowed_size = STAGE_FOUR - -/obj/singularity/wizard/process() - move() - eat() - return - -/obj/singularity/wizard/attack_tk(mob/user) - if(iscarbon(user)) - var/mob/living/carbon/C = user - var/datum/component/mood/insaneinthemembrane = C.GetComponent(/datum/component/mood) - if(insaneinthemembrane.sanity < 15) - return //they've already seen it and are about to die, or are just too insane to care - to_chat(C, "OH GOD! NONE OF IT IS REAL! NONE OF IT IS REEEEEEEEEEEEEEEEEEEEEEEEAL!") - insaneinthemembrane.sanity = 0 - for(var/lore in typesof(/datum/brain_trauma/severe)) - C.gain_trauma(lore) - addtimer(CALLBACK(src, /obj/singularity/wizard.proc/deranged, C), 100) - -/obj/singularity/wizard/proc/deranged(mob/living/carbon/C) - if(!C || C.stat == DEAD) - return - C.vomit(0, TRUE, TRUE, 3, TRUE) - C.spew_organ(3, 2) - C.death() - -/obj/singularity/wizard/mapped/admin_investigate_setup() - return - -/////////////////////////////////////////Scrying/////////////////// - -/obj/item/scrying - name = "scrying orb" - desc = "An incandescent orb of otherworldly energy, merely holding it gives you vision and hearing beyond mortal means, and staring into it lets you see the entire universe." - icon = 'icons/obj/projectiles.dmi' - icon_state ="bluespace" - throw_speed = 3 - throw_range = 7 - throwforce = 15 - damtype = BURN - force = 15 - hitsound = 'sound/items/welder2.ogg' - - var/mob/current_owner - -/obj/item/scrying/Initialize(mapload) - . = ..() - START_PROCESSING(SSobj, src) - -/obj/item/scrying/Destroy() - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/item/scrying/process() - var/mob/holder = get(loc, /mob) - if(current_owner && current_owner != holder) - - to_chat(current_owner, "Your otherworldly vision fades...") - - REMOVE_TRAIT(current_owner, TRAIT_SIXTHSENSE, SCRYING_ORB) - REMOVE_TRAIT(current_owner, TRAIT_XRAY_VISION, SCRYING_ORB) - current_owner.update_sight() - - current_owner = null - - if(!current_owner && holder) - current_owner = holder - - to_chat(current_owner, "You can see...everything!") - - ADD_TRAIT(current_owner, TRAIT_SIXTHSENSE, SCRYING_ORB) - ADD_TRAIT(current_owner, TRAIT_XRAY_VISION, SCRYING_ORB) - current_owner.update_sight() - -/obj/item/scrying/attack_self(mob/user) - visible_message("[user] stares into [src], their eyes glazing over.") - user.ghostize(1) - -/////////////////////////////////////////Necromantic Stone/////////////////// - -/obj/item/necromantic_stone - name = "necromantic stone" - desc = "A shard capable of resurrecting humans as skeleton thralls." - icon = 'icons/obj/wizard.dmi' - icon_state = "necrostone" - item_state = "electronic" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - w_class = WEIGHT_CLASS_TINY - var/list/spooky_scaries = list() - var/unlimited = 0 - -/obj/item/necromantic_stone/unlimited - unlimited = 1 - -/obj/item/necromantic_stone/attack(mob/living/carbon/human/M, mob/living/carbon/human/user) - if(!istype(M)) - return ..() - - if(!istype(user) || !user.canUseTopic(M, BE_CLOSE)) - return - - if(M.stat != DEAD) - to_chat(user, "This artifact can only affect the dead!") - return - - for(var/mob/dead/observer/ghost in GLOB.dead_mob_list) //excludes new players - if(ghost.mind && ghost.mind.current == M && ghost.client) //the dead mobs list can contain clientless mobs - ghost.reenter_corpse() - break - - if(!M.mind || !M.client) - to_chat(user, "There is no soul connected to this body...") - return - - check_spooky()//clean out/refresh the list - if(spooky_scaries.len >= 3 && !unlimited) - to_chat(user, "This artifact can only affect three undead at a time!") - return - - M.set_species(/datum/species/skeleton, icon_update=0) - M.revive(full_heal = TRUE, admin_revive = TRUE) - spooky_scaries |= M - to_chat(M, "You have been revived by [user.real_name]!") - to_chat(M, "[user.p_theyre(TRUE)] your master now, assist [user.p_them()] even if it costs you your new life!") - - equip_roman_skeleton(M) - - desc = "A shard capable of resurrecting humans as skeleton thralls[unlimited ? "." : ", [spooky_scaries.len]/3 active thralls."]" - -/obj/item/necromantic_stone/proc/check_spooky() - if(unlimited) //no point, the list isn't used. - return - - for(var/X in spooky_scaries) - if(!ishuman(X)) - spooky_scaries.Remove(X) - continue - var/mob/living/carbon/human/H = X - if(H.stat == DEAD) - H.dust(TRUE) - spooky_scaries.Remove(X) - continue - listclearnulls(spooky_scaries) - -//Funny gimmick, skeletons always seem to wear roman/ancient armour -/obj/item/necromantic_stone/proc/equip_roman_skeleton(mob/living/carbon/human/H) - for(var/obj/item/I in H) - H.dropItemToGround(I) - - var/hat = pick(/obj/item/clothing/head/helmet/roman, /obj/item/clothing/head/helmet/roman/legionnaire) - H.equip_to_slot_or_del(new hat(H), ITEM_SLOT_HEAD) - H.equip_to_slot_or_del(new /obj/item/clothing/under/costume/roman(H), ITEM_SLOT_ICLOTHING) - H.equip_to_slot_or_del(new /obj/item/clothing/shoes/roman(H), ITEM_SLOT_FEET) - H.put_in_hands(new /obj/item/shield/riot/roman(H), TRUE) - H.put_in_hands(new /obj/item/claymore(H), TRUE) - H.equip_to_slot_or_del(new /obj/item/spear(H), ITEM_SLOT_BACK) - - -/obj/item/voodoo - name = "wicker doll" - desc = "Something creepy about it." - icon = 'icons/obj/wizard.dmi' - icon_state = "voodoo" - item_state = "electronic" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - var/mob/living/carbon/human/target = null - var/list/mob/living/carbon/human/possible = list() - var/obj/item/voodoo_link = null - var/cooldown_time = 30 //3s - var/cooldown = 0 - max_integrity = 10 - resistance_flags = FLAMMABLE - -/obj/item/voodoo/attackby(obj/item/I, mob/user, params) - if(target && cooldown < world.time) - if(I.get_temperature()) - to_chat(target, "You suddenly feel very hot!") - target.adjust_bodytemperature(50) - GiveHint(target) - else if(is_pointed(I)) - to_chat(target, "You feel a stabbing pain in [parse_zone(user.zone_selected)]!") - target.Paralyze(40) - GiveHint(target) - else if(istype(I, /obj/item/bikehorn)) - to_chat(target, "HONK") - SEND_SOUND(target, 'sound/items/airhorn.ogg') - target.adjustEarDamage(0,3) - GiveHint(target) - cooldown = world.time +cooldown_time - return - - if(!voodoo_link) - if(I.loc == user && istype(I) && I.w_class <= WEIGHT_CLASS_SMALL) - if (user.transferItemToLoc(I,src)) - voodoo_link = I - to_chat(user, "You attach [I] to the doll.") - update_targets() - -/obj/item/voodoo/check_eye(mob/user) - if(loc != user) - user.reset_perspective(null) - user.unset_machine() - -/obj/item/voodoo/attack_self(mob/user) - if(!target && possible.len) - target = input(user, "Select your victim!", "Voodoo") as null|anything in sortNames(possible) - return - - if(user.zone_selected == BODY_ZONE_CHEST) - if(voodoo_link) - target = null - voodoo_link.forceMove(drop_location()) - to_chat(user, "You remove the [voodoo_link] from the doll.") - voodoo_link = null - update_targets() - return - - if(target && cooldown < world.time) - switch(user.zone_selected) - if(BODY_ZONE_PRECISE_MOUTH) - var/wgw = sanitize(input(user, "What would you like the victim to say", "Voodoo", null) as text) - target.say(wgw, forced = "voodoo doll") - log_game("[key_name(user)] made [key_name(target)] say [wgw] with a voodoo doll.") - if(BODY_ZONE_PRECISE_EYES) - user.set_machine(src) - user.reset_perspective(target) - addtimer(CALLBACK(src, .proc/reset, user), 10 SECONDS) - if(BODY_ZONE_R_LEG,BODY_ZONE_L_LEG) - to_chat(user, "You move the doll's legs around.") - var/turf/T = get_step(target,pick(GLOB.cardinals)) - target.Move(T) - if(BODY_ZONE_R_ARM,BODY_ZONE_L_ARM) - target.click_random_mob() - GiveHint(target) - if(BODY_ZONE_HEAD) - to_chat(user, "You smack the doll's head with your hand.") - target.Dizzy(10) - to_chat(target, "You suddenly feel as if your head was hit with a hammer!") - GiveHint(target,user) - cooldown = world.time + cooldown_time - -/obj/item/voodoo/proc/reset(mob/user) - if(QDELETED(user)) - return - user.reset_perspective(null) - user.unset_machine() - -/obj/item/voodoo/proc/update_targets() - possible = list() - if(!voodoo_link) - return - var/list/prints = voodoo_link.return_fingerprints() - if(!length(prints)) - return FALSE - for(var/mob/living/carbon/human/H in GLOB.alive_mob_list) - if(prints[md5(H.dna.uni_identity)]) - possible |= H - -/obj/item/voodoo/proc/GiveHint(mob/victim,force=0) - if(prob(50) || force) - var/way = dir2text(get_dir(victim,get_turf(src))) - to_chat(victim, "You feel a dark presence from [way].") - if(prob(20) || force) - var/area/A = get_area(src) - to_chat(victim, "You feel a dark presence from [A.name].") - -/obj/item/voodoo/suicide_act(mob/living/carbon/user) - user.visible_message("[user] links the voodoo doll to [user.p_them()]self and sits on it, infinitely crushing [user.p_them()]self! It looks like [user.p_theyre()] trying to commit suicide!") - user.gib() - return(BRUTELOSS) - -/obj/item/voodoo/fire_act(exposed_temperature, exposed_volume) - if(target) - target.adjust_fire_stacks(20) - target.IgniteMob() - GiveHint(target,1) - return ..() - -//Provides a decent heal, need to pump every 6 seconds -/obj/item/organ/heart/cursed/wizard - pump_delay = 60 - heal_brute = 25 - heal_burn = 25 - heal_oxy = 25 - -//Warp Whistle: Provides uncontrolled long distance teleportation. - -/obj/item/warpwhistle - name = "warp whistle" - desc = "One toot on this whistle will send you to a far away land!" - icon = 'icons/obj/wizard.dmi' - icon_state = "whistle" - var/on_cooldown = 0 //0: usable, 1: in use, 2: on cooldown - var/mob/living/carbon/last_user - -/obj/item/warpwhistle/proc/interrupted(mob/living/carbon/user) - if(!user || QDELETED(src) || user.notransform) - on_cooldown = FALSE - return TRUE - return FALSE - -/obj/item/warpwhistle/proc/end_effect(mob/living/carbon/user) - user.invisibility = initial(user.invisibility) - user.status_flags &= ~GODMODE - user.update_mobility() - -/obj/item/warpwhistle/attack_self(mob/living/carbon/user) - if(!istype(user) || on_cooldown) - return - on_cooldown = TRUE - last_user = user - var/turf/T = get_turf(user) - playsound(T,'sound/magic/warpwhistle.ogg', 200, TRUE) - user.mobility_flags &= ~MOBILITY_MOVE - new /obj/effect/temp_visual/tornado(T) - sleep(20) - if(interrupted(user)) - return - user.invisibility = INVISIBILITY_MAXIMUM - user.status_flags |= GODMODE - sleep(20) - if(interrupted(user)) - end_effect(user) - return - var/breakout = 0 - while(breakout < 50) - var/turf/potential_T = find_safe_turf() - if(T.z != potential_T.z || abs(get_dist_euclidian(potential_T,T)) > 50 - breakout) - do_teleport(user, potential_T, channel = TELEPORT_CHANNEL_MAGIC) - user.mobility_flags &= ~MOBILITY_MOVE - T = potential_T - break - breakout += 1 - new /obj/effect/temp_visual/tornado(T) - sleep(20) - end_effect(user) - if(interrupted(user)) - return - on_cooldown = 2 - addtimer(VARSET_CALLBACK(src, on_cooldown, 0), 4 SECONDS) - -/obj/item/warpwhistle/Destroy() - if(on_cooldown == 1 && last_user) //Flute got dunked somewhere in the teleport - end_effect(last_user) - return ..() - -/obj/effect/temp_visual/tornado - icon = 'icons/obj/wizard.dmi' - icon_state = "tornado" - name = "tornado" - desc = "This thing sucks!" - layer = FLY_LAYER - randomdir = 0 - duration = 40 - pixel_x = 500 - -/obj/effect/temp_visual/tornado/Initialize() - . = ..() - animate(src, pixel_x = -500, time = 40) + +//Apprenticeship contract - moved to antag_spawner.dm + +///////////////////////////Veil Render////////////////////// + +/obj/item/veilrender + name = "veil render" + desc = "A wicked curved blade of alien origin, recovered from the ruins of a vast city." + icon = 'icons/obj/wizard.dmi' + icon_state = "render" + item_state = "knife" + lefthand_file = 'icons/mob/inhands/equipment/kitchen_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/kitchen_righthand.dmi' + force = 15 + throwforce = 10 + w_class = WEIGHT_CLASS_NORMAL + hitsound = 'sound/weapons/bladeslice.ogg' + var/charges = 1 + var/spawn_type = /obj/singularity/wizard + var/spawn_amt = 1 + var/activate_descriptor = "reality" + var/rend_desc = "You should run now." + var/spawn_fast = 0 //if 1, ignores checking for mobs on loc before spawning + +/obj/item/veilrender/attack_self(mob/user) + if(charges > 0) + new /obj/effect/rend(get_turf(user), spawn_type, spawn_amt, rend_desc, spawn_fast) + charges-- + user.visible_message("[src] hums with power as [user] deals a blow to [activate_descriptor] itself!") + else + to_chat(user, "The unearthly energies that powered the blade are now dormant.") + +/obj/effect/rend + name = "tear in the fabric of reality" + desc = "You should run now." + icon = 'icons/effects/effects.dmi' + icon_state = "rift" + density = TRUE + anchored = TRUE + var/spawn_path = /mob/living/simple_animal/cow //defaulty cows to prevent unintentional narsies + var/spawn_amt_left = 20 + var/spawn_fast = 0 + +/obj/effect/rend/New(loc, spawn_type, spawn_amt, desc, spawn_fast) + src.spawn_path = spawn_type + src.spawn_amt_left = spawn_amt + src.desc = desc + src.spawn_fast = spawn_fast + START_PROCESSING(SSobj, src) + return + +/obj/effect/rend/process() + if(!spawn_fast) + if(locate(/mob) in loc) + return + new spawn_path(loc) + spawn_amt_left-- + if(spawn_amt_left <= 0) + qdel(src) + +/obj/effect/rend/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/nullrod)) + user.visible_message("[user] seals \the [src] with \the [I].") + qdel(src) + return + else + return ..() + +/obj/effect/rend/singularity_pull() + return + +/obj/effect/rend/singularity_pull() + return + +/obj/item/veilrender/vealrender + name = "veal render" + desc = "A wicked curved blade of alien origin, recovered from the ruins of a vast farm." + spawn_type = /mob/living/simple_animal/cow + spawn_amt = 20 + activate_descriptor = "hunger" + rend_desc = "Reverberates with the sound of ten thousand moos." + +/obj/item/veilrender/honkrender + name = "honk render" + desc = "A wicked curved blade of alien origin, recovered from the ruins of a vast circus." + spawn_type = /mob/living/simple_animal/hostile/retaliate/clown + spawn_amt = 10 + activate_descriptor = "depression" + rend_desc = "Gently wafting with the sounds of endless laughter." + icon_state = "clownrender" + +/obj/item/veilrender/honkrender/honkhulkrender + name = "superior honk render" + desc = "A wicked curved blade of alien origin, recovered from the ruins of a vast circus. This one gleams with a special light." + spawn_type = /mob/living/simple_animal/hostile/retaliate/clown/clownhulk + spawn_amt = 5 + activate_descriptor = "depression" + rend_desc = "Gently wafting with the sounds of mirthful grunting." + icon_state = "clownrender" + +////TEAR IN REALITY + +/obj/singularity/wizard + name = "tear in the fabric of reality" + desc = "This isn't right." + icon = 'icons/effects/224x224.dmi' + icon_state = "reality" + pixel_x = -96 + pixel_y = -96 + dissipate = 0 + move_self = 0 + consume_range = 3 + grav_pull = 4 + current_size = STAGE_FOUR + allowed_size = STAGE_FOUR + +/obj/singularity/wizard/process() + move() + eat() + return + +/obj/singularity/wizard/attack_tk(mob/user) + if(iscarbon(user)) + var/mob/living/carbon/C = user + var/datum/component/mood/insaneinthemembrane = C.GetComponent(/datum/component/mood) + if(insaneinthemembrane.sanity < 15) + return //they've already seen it and are about to die, or are just too insane to care + to_chat(C, "OH GOD! NONE OF IT IS REAL! NONE OF IT IS REEEEEEEEEEEEEEEEEEEEEEEEAL!") + insaneinthemembrane.sanity = 0 + for(var/lore in typesof(/datum/brain_trauma/severe)) + C.gain_trauma(lore) + addtimer(CALLBACK(src, /obj/singularity/wizard.proc/deranged, C), 100) + +/obj/singularity/wizard/proc/deranged(mob/living/carbon/C) + if(!C || C.stat == DEAD) + return + C.vomit(0, TRUE, TRUE, 3, TRUE) + C.spew_organ(3, 2) + C.death() + +/obj/singularity/wizard/mapped/admin_investigate_setup() + return + +/////////////////////////////////////////Scrying/////////////////// + +/obj/item/scrying + name = "scrying orb" + desc = "An incandescent orb of otherworldly energy, merely holding it gives you vision and hearing beyond mortal means, and staring into it lets you see the entire universe." + icon = 'icons/obj/projectiles.dmi' + icon_state ="bluespace" + throw_speed = 3 + throw_range = 7 + throwforce = 15 + damtype = BURN + force = 15 + hitsound = 'sound/items/welder2.ogg' + + var/mob/current_owner + +/obj/item/scrying/Initialize(mapload) + . = ..() + START_PROCESSING(SSobj, src) + +/obj/item/scrying/Destroy() + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/scrying/process() + var/mob/holder = get(loc, /mob) + if(current_owner && current_owner != holder) + + to_chat(current_owner, "Your otherworldly vision fades...") + + REMOVE_TRAIT(current_owner, TRAIT_SIXTHSENSE, SCRYING_ORB) + REMOVE_TRAIT(current_owner, TRAIT_XRAY_VISION, SCRYING_ORB) + current_owner.update_sight() + + current_owner = null + + if(!current_owner && holder) + current_owner = holder + + to_chat(current_owner, "You can see...everything!") + + ADD_TRAIT(current_owner, TRAIT_SIXTHSENSE, SCRYING_ORB) + ADD_TRAIT(current_owner, TRAIT_XRAY_VISION, SCRYING_ORB) + current_owner.update_sight() + +/obj/item/scrying/attack_self(mob/user) + visible_message("[user] stares into [src], their eyes glazing over.") + user.ghostize(1) + +/////////////////////////////////////////Necromantic Stone/////////////////// + +/obj/item/necromantic_stone + name = "necromantic stone" + desc = "A shard capable of resurrecting humans as skeleton thralls." + icon = 'icons/obj/wizard.dmi' + icon_state = "necrostone" + item_state = "electronic" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + w_class = WEIGHT_CLASS_TINY + var/list/spooky_scaries = list() + var/unlimited = 0 + +/obj/item/necromantic_stone/unlimited + unlimited = 1 + +/obj/item/necromantic_stone/attack(mob/living/carbon/human/M, mob/living/carbon/human/user) + if(!istype(M)) + return ..() + + if(!istype(user) || !user.canUseTopic(M, BE_CLOSE)) + return + + if(M.stat != DEAD) + to_chat(user, "This artifact can only affect the dead!") + return + + for(var/mob/dead/observer/ghost in GLOB.dead_mob_list) //excludes new players + if(ghost.mind && ghost.mind.current == M && ghost.client) //the dead mobs list can contain clientless mobs + ghost.reenter_corpse() + break + + if(!M.mind || !M.client) + to_chat(user, "There is no soul connected to this body...") + return + + check_spooky()//clean out/refresh the list + if(spooky_scaries.len >= 3 && !unlimited) + to_chat(user, "This artifact can only affect three undead at a time!") + return + + M.set_species(/datum/species/skeleton, icon_update=0) + M.revive(full_heal = TRUE, admin_revive = TRUE) + spooky_scaries |= M + to_chat(M, "You have been revived by [user.real_name]!") + to_chat(M, "[user.p_theyre(TRUE)] your master now, assist [user.p_them()] even if it costs you your new life!") + + equip_roman_skeleton(M) + + desc = "A shard capable of resurrecting humans as skeleton thralls[unlimited ? "." : ", [spooky_scaries.len]/3 active thralls."]" + +/obj/item/necromantic_stone/proc/check_spooky() + if(unlimited) //no point, the list isn't used. + return + + for(var/X in spooky_scaries) + if(!ishuman(X)) + spooky_scaries.Remove(X) + continue + var/mob/living/carbon/human/H = X + if(H.stat == DEAD) + H.dust(TRUE) + spooky_scaries.Remove(X) + continue + listclearnulls(spooky_scaries) + +//Funny gimmick, skeletons always seem to wear roman/ancient armour +/obj/item/necromantic_stone/proc/equip_roman_skeleton(mob/living/carbon/human/H) + for(var/obj/item/I in H) + H.dropItemToGround(I) + + var/hat = pick(/obj/item/clothing/head/helmet/roman, /obj/item/clothing/head/helmet/roman/legionnaire) + H.equip_to_slot_or_del(new hat(H), ITEM_SLOT_HEAD) + H.equip_to_slot_or_del(new /obj/item/clothing/under/costume/roman(H), ITEM_SLOT_ICLOTHING) + H.equip_to_slot_or_del(new /obj/item/clothing/shoes/roman(H), ITEM_SLOT_FEET) + H.put_in_hands(new /obj/item/shield/riot/roman(H), TRUE) + H.put_in_hands(new /obj/item/claymore(H), TRUE) + H.equip_to_slot_or_del(new /obj/item/spear(H), ITEM_SLOT_BACK) + + +/obj/item/voodoo + name = "wicker doll" + desc = "Something creepy about it." + icon = 'icons/obj/wizard.dmi' + icon_state = "voodoo" + item_state = "electronic" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + var/mob/living/carbon/human/target = null + var/list/mob/living/carbon/human/possible = list() + var/obj/item/voodoo_link = null + var/cooldown_time = 30 //3s + var/cooldown = 0 + max_integrity = 10 + resistance_flags = FLAMMABLE + +/obj/item/voodoo/attackby(obj/item/I, mob/user, params) + if(target && cooldown < world.time) + if(I.get_temperature()) + to_chat(target, "You suddenly feel very hot!") + target.adjust_bodytemperature(50) + GiveHint(target) + else if(is_pointed(I)) + to_chat(target, "You feel a stabbing pain in [parse_zone(user.zone_selected)]!") + target.Paralyze(40) + GiveHint(target) + else if(istype(I, /obj/item/bikehorn)) + to_chat(target, "HONK") + SEND_SOUND(target, 'sound/items/airhorn.ogg') + target.adjustEarDamage(0,3) + GiveHint(target) + cooldown = world.time +cooldown_time + return + + if(!voodoo_link) + if(I.loc == user && istype(I) && I.w_class <= WEIGHT_CLASS_SMALL) + if (user.transferItemToLoc(I,src)) + voodoo_link = I + to_chat(user, "You attach [I] to the doll.") + update_targets() + +/obj/item/voodoo/check_eye(mob/user) + if(loc != user) + user.reset_perspective(null) + user.unset_machine() + +/obj/item/voodoo/attack_self(mob/user) + if(!target && possible.len) + target = input(user, "Select your victim!", "Voodoo") as null|anything in sortNames(possible) + return + + if(user.zone_selected == BODY_ZONE_CHEST) + if(voodoo_link) + target = null + voodoo_link.forceMove(drop_location()) + to_chat(user, "You remove the [voodoo_link] from the doll.") + voodoo_link = null + update_targets() + return + + if(target && cooldown < world.time) + switch(user.zone_selected) + if(BODY_ZONE_PRECISE_MOUTH) + var/wgw = sanitize(input(user, "What would you like the victim to say", "Voodoo", null) as text) + target.say(wgw, forced = "voodoo doll") + log_game("[key_name(user)] made [key_name(target)] say [wgw] with a voodoo doll.") + if(BODY_ZONE_PRECISE_EYES) + user.set_machine(src) + user.reset_perspective(target) + addtimer(CALLBACK(src, .proc/reset, user), 10 SECONDS) + if(BODY_ZONE_R_LEG,BODY_ZONE_L_LEG) + to_chat(user, "You move the doll's legs around.") + var/turf/T = get_step(target,pick(GLOB.cardinals)) + target.Move(T) + if(BODY_ZONE_R_ARM,BODY_ZONE_L_ARM) + target.click_random_mob() + GiveHint(target) + if(BODY_ZONE_HEAD) + to_chat(user, "You smack the doll's head with your hand.") + target.Dizzy(10) + to_chat(target, "You suddenly feel as if your head was hit with a hammer!") + GiveHint(target,user) + cooldown = world.time + cooldown_time + +/obj/item/voodoo/proc/reset(mob/user) + if(QDELETED(user)) + return + user.reset_perspective(null) + user.unset_machine() + +/obj/item/voodoo/proc/update_targets() + possible = list() + if(!voodoo_link) + return + var/list/prints = voodoo_link.return_fingerprints() + if(!length(prints)) + return FALSE + for(var/mob/living/carbon/human/H in GLOB.alive_mob_list) + if(prints[md5(H.dna.uni_identity)]) + possible |= H + +/obj/item/voodoo/proc/GiveHint(mob/victim,force=0) + if(prob(50) || force) + var/way = dir2text(get_dir(victim,get_turf(src))) + to_chat(victim, "You feel a dark presence from [way].") + if(prob(20) || force) + var/area/A = get_area(src) + to_chat(victim, "You feel a dark presence from [A.name].") + +/obj/item/voodoo/suicide_act(mob/living/carbon/user) + user.visible_message("[user] links the voodoo doll to [user.p_them()]self and sits on it, infinitely crushing [user.p_them()]self! It looks like [user.p_theyre()] trying to commit suicide!") + user.gib() + return(BRUTELOSS) + +/obj/item/voodoo/fire_act(exposed_temperature, exposed_volume) + if(target) + target.adjust_fire_stacks(20) + target.IgniteMob() + GiveHint(target,1) + return ..() + +//Provides a decent heal, need to pump every 6 seconds +/obj/item/organ/heart/cursed/wizard + pump_delay = 60 + heal_brute = 25 + heal_burn = 25 + heal_oxy = 25 + +//Warp Whistle: Provides uncontrolled long distance teleportation. + +/obj/item/warpwhistle + name = "warp whistle" + desc = "One toot on this whistle will send you to a far away land!" + icon = 'icons/obj/wizard.dmi' + icon_state = "whistle" + var/on_cooldown = 0 //0: usable, 1: in use, 2: on cooldown + var/mob/living/carbon/last_user + +/obj/item/warpwhistle/proc/interrupted(mob/living/carbon/user) + if(!user || QDELETED(src) || user.notransform) + on_cooldown = FALSE + return TRUE + return FALSE + +/obj/item/warpwhistle/proc/end_effect(mob/living/carbon/user) + user.invisibility = initial(user.invisibility) + user.status_flags &= ~GODMODE + user.update_mobility() + +/obj/item/warpwhistle/attack_self(mob/living/carbon/user) + if(!istype(user) || on_cooldown) + return + on_cooldown = TRUE + last_user = user + var/turf/T = get_turf(user) + playsound(T,'sound/magic/warpwhistle.ogg', 200, TRUE) + user.mobility_flags &= ~MOBILITY_MOVE + new /obj/effect/temp_visual/tornado(T) + sleep(20) + if(interrupted(user)) + return + user.invisibility = INVISIBILITY_MAXIMUM + user.status_flags |= GODMODE + sleep(20) + if(interrupted(user)) + end_effect(user) + return + var/breakout = 0 + while(breakout < 50) + var/turf/potential_T = find_safe_turf() + if(T.z != potential_T.z || abs(get_dist_euclidian(potential_T,T)) > 50 - breakout) + do_teleport(user, potential_T, channel = TELEPORT_CHANNEL_MAGIC) + user.mobility_flags &= ~MOBILITY_MOVE + T = potential_T + break + breakout += 1 + new /obj/effect/temp_visual/tornado(T) + sleep(20) + end_effect(user) + if(interrupted(user)) + return + on_cooldown = 2 + addtimer(VARSET_CALLBACK(src, on_cooldown, 0), 4 SECONDS) + +/obj/item/warpwhistle/Destroy() + if(on_cooldown == 1 && last_user) //Flute got dunked somewhere in the teleport + end_effect(last_user) + return ..() + +/obj/effect/temp_visual/tornado + icon = 'icons/obj/wizard.dmi' + icon_state = "tornado" + name = "tornado" + desc = "This thing sucks!" + layer = FLY_LAYER + randomdir = 0 + duration = 40 + pixel_x = 500 + +/obj/effect/temp_visual/tornado/Initialize() + . = ..() + animate(src, pixel_x = -500, time = 40) diff --git a/code/modules/antagonists/wizard/equipment/spellbook.dm b/code/modules/antagonists/wizard/equipment/spellbook.dm index d9b88d3bc7c4..a1b635d79170 100644 --- a/code/modules/antagonists/wizard/equipment/spellbook.dm +++ b/code/modules/antagonists/wizard/equipment/spellbook.dm @@ -1,769 +1,769 @@ -/datum/spellbook_entry - var/name = "Entry Name" - - var/spell_type = null - var/desc = "" - var/category = "Offensive" - var/cost = 2 - var/refundable = TRUE - var/surplus = -1 // -1 for infinite, not used by anything atm - var/obj/effect/proc_holder/spell/S = null //Since spellbooks can be used by only one person anyway we can track the actual spell - var/buy_word = "Learn" - var/limit //used to prevent a spellbook_entry from being bought more than X times with one wizard spellbook - var/list/no_coexistance_typecache //Used so you can't have specific spells together - -/datum/spellbook_entry/New() - ..() - no_coexistance_typecache = typecacheof(no_coexistance_typecache) - -/datum/spellbook_entry/proc/IsAvailible() // For config prefs / gamemode restrictions - these are round applied - return TRUE - -/datum/spellbook_entry/proc/CanBuy(mob/living/carbon/human/user,obj/item/spellbook/book) // Specific circumstances - if(book.uses= aspell.level_max) - to_chat(user, "This spell cannot be improved further!") - return FALSE - else - aspell.name = initial(aspell.name) - aspell.spell_level++ - aspell.charge_max = round(initial(aspell.charge_max) - aspell.spell_level * (initial(aspell.charge_max) - aspell.cooldown_min)/ aspell.level_max) - if(aspell.charge_max < aspell.charge_counter) - aspell.charge_counter = aspell.charge_max - switch(aspell.spell_level) - if(1) - to_chat(user, "You have improved [aspell.name] into Efficient [aspell.name].") - aspell.name = "Efficient [aspell.name]" - if(2) - to_chat(user, "You have further improved [aspell.name] into Quickened [aspell.name].") - aspell.name = "Quickened [aspell.name]" - if(3) - to_chat(user, "You have further improved [aspell.name] into Free [aspell.name].") - aspell.name = "Free [aspell.name]" - if(4) - to_chat(user, "You have further improved [aspell.name] into Instant [aspell.name].") - aspell.name = "Instant [aspell.name]" - if(aspell.spell_level >= aspell.level_max) - to_chat(user, "This spell cannot be strengthened any further!") - SSblackbox.record_feedback("nested tally", "wizard_spell_improved", 1, list("[name]", "[aspell.spell_level]")) - return TRUE - //No same spell found - just learn it - SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) - user.mind.AddSpell(S) - to_chat(user, "You have learned [S.name].") - return TRUE - -/datum/spellbook_entry/proc/CanRefund(mob/living/carbon/human/user,obj/item/spellbook/book) - if(!refundable) - return FALSE - if(!S) - S = new spell_type() - for(var/obj/effect/proc_holder/spell/aspell in user.mind.spell_list) - if(initial(S.name) == initial(aspell.name)) - return TRUE - return FALSE - -/datum/spellbook_entry/proc/Refund(mob/living/carbon/human/user,obj/item/spellbook/book) //return point value or -1 for failure - var/area/wizard_station/A = GLOB.areas_by_type[/area/wizard_station] - if(!(user in A.contents)) - to_chat(user, "You can only refund spells at the wizard lair!") - return -1 - if(!S) - S = new spell_type() - var/spell_levels = 0 - for(var/obj/effect/proc_holder/spell/aspell in user.mind.spell_list) - if(initial(S.name) == initial(aspell.name)) - spell_levels = aspell.spell_level - user.mind.spell_list.Remove(aspell) - qdel(S) - return cost * (spell_levels+1) - return -1 -/datum/spellbook_entry/proc/GetInfo() - if(!S) - S = new spell_type() - var/dat ="" - dat += "[initial(S.name)]" - if(S.charge_type == "recharge") - dat += " Cooldown:[S.charge_max/10]" - dat += " Cost:[cost]
                    " - dat += "[S.desc][desc]
                    " - dat += "[S.clothes_req?"Requires wizard garb.":"Can be cast without wizard garb."]
                    " - return dat - -/datum/spellbook_entry/fireball - name = "Fireball" - spell_type = /obj/effect/proc_holder/spell/aimed/fireball - -/datum/spellbook_entry/spell_cards - name = "Spell Cards" - spell_type = /obj/effect/proc_holder/spell/aimed/spell_cards - -/datum/spellbook_entry/rod_form - name = "Rod Form" - spell_type = /obj/effect/proc_holder/spell/targeted/rod_form - -/datum/spellbook_entry/magicm - name = "Magic Missile" - spell_type = /obj/effect/proc_holder/spell/targeted/projectile/magic_missile - category = "Defensive" - -/datum/spellbook_entry/disintegrate - name = "Smite" - spell_type = /obj/effect/proc_holder/spell/targeted/touch/disintegrate - -/datum/spellbook_entry/disabletech - name = "Disable Tech" - spell_type = /obj/effect/proc_holder/spell/targeted/emplosion/disable_tech - category = "Defensive" - cost = 1 - -/datum/spellbook_entry/repulse - name = "Repulse" - spell_type = /obj/effect/proc_holder/spell/aoe_turf/repulse - category = "Defensive" - -/datum/spellbook_entry/lightningPacket - name = "Thrown Lightning" - spell_type = /obj/effect/proc_holder/spell/targeted/conjure_item/spellpacket - category = "Defensive" - -/datum/spellbook_entry/timestop - name = "Time Stop" - spell_type = /obj/effect/proc_holder/spell/aoe_turf/timestop - category = "Defensive" - -/datum/spellbook_entry/smoke - name = "Smoke" - spell_type = /obj/effect/proc_holder/spell/targeted/smoke - category = "Defensive" - cost = 1 - -/datum/spellbook_entry/blind - name = "Blind" - spell_type = /obj/effect/proc_holder/spell/pointed/trigger/blind - cost = 1 - -/datum/spellbook_entry/mindswap - name = "Mindswap" - spell_type = /obj/effect/proc_holder/spell/pointed/mind_transfer - category = "Mobility" - -/datum/spellbook_entry/forcewall - name = "Force Wall" - spell_type = /obj/effect/proc_holder/spell/targeted/forcewall - category = "Defensive" - cost = 1 - -/datum/spellbook_entry/blink - name = "Blink" - spell_type = /obj/effect/proc_holder/spell/targeted/turf_teleport/blink - category = "Mobility" - -/datum/spellbook_entry/teleport - name = "Teleport" - spell_type = /obj/effect/proc_holder/spell/targeted/area_teleport/teleport - category = "Mobility" - -/datum/spellbook_entry/mutate - name = "Mutate" - spell_type = /obj/effect/proc_holder/spell/targeted/genetic/mutate - -/datum/spellbook_entry/jaunt - name = "Ethereal Jaunt" - spell_type = /obj/effect/proc_holder/spell/targeted/ethereal_jaunt - category = "Mobility" - -/datum/spellbook_entry/knock - name = "Knock" - spell_type = /obj/effect/proc_holder/spell/aoe_turf/knock - category = "Mobility" - cost = 1 - -/datum/spellbook_entry/fleshtostone - name = "Flesh to Stone" - spell_type = /obj/effect/proc_holder/spell/targeted/touch/flesh_to_stone - -/datum/spellbook_entry/summonitem - name = "Summon Item" - spell_type = /obj/effect/proc_holder/spell/targeted/summonitem - category = "Assistance" - cost = 1 - -/datum/spellbook_entry/lichdom - name = "Bind Soul" - spell_type = /obj/effect/proc_holder/spell/targeted/lichdom - category = "Defensive" - -/datum/spellbook_entry/teslablast - name = "Tesla Blast" - spell_type = /obj/effect/proc_holder/spell/targeted/tesla - -/datum/spellbook_entry/lightningbolt - name = "Lightning Bolt" - spell_type = /obj/effect/proc_holder/spell/aimed/lightningbolt - cost = 1 - -/datum/spellbook_entry/lightningbolt/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) //return TRUE on success - . = ..() - ADD_TRAIT(user, TRAIT_TESLA_SHOCKIMMUNE, "lightning_bolt_spell") - -/datum/spellbook_entry/lightningbolt/Refund(mob/living/carbon/human/user, obj/item/spellbook/book) - . = ..() - REMOVE_TRAIT(user, TRAIT_TESLA_SHOCKIMMUNE, "lightning_bolt_spell") - -/datum/spellbook_entry/infinite_guns - name = "Lesser Summon Guns" - spell_type = /obj/effect/proc_holder/spell/targeted/infinite_guns/gun - cost = 3 - no_coexistance_typecache = /obj/effect/proc_holder/spell/targeted/infinite_guns/arcane_barrage - -/datum/spellbook_entry/arcane_barrage - name = "Arcane Barrage" - spell_type = /obj/effect/proc_holder/spell/targeted/infinite_guns/arcane_barrage - cost = 3 - no_coexistance_typecache = /obj/effect/proc_holder/spell/targeted/infinite_guns/gun - -/datum/spellbook_entry/barnyard - name = "Barnyard Curse" - spell_type = /obj/effect/proc_holder/spell/pointed/barnyardcurse - -/datum/spellbook_entry/charge - name = "Charge" - spell_type = /obj/effect/proc_holder/spell/targeted/charge - category = "Assistance" - cost = 1 - -/datum/spellbook_entry/shapeshift - name = "Wild Shapeshift" - spell_type = /obj/effect/proc_holder/spell/targeted/shapeshift - category = "Assistance" - cost = 1 - -/datum/spellbook_entry/tap - name = "Soul Tap" - spell_type = /obj/effect/proc_holder/spell/self/tap - category = "Assistance" - cost = 1 - -/datum/spellbook_entry/spacetime_dist - name = "Spacetime Distortion" - spell_type = /obj/effect/proc_holder/spell/spacetime_dist - category = "Defensive" - cost = 1 - -/datum/spellbook_entry/the_traps - name = "The Traps!" - spell_type = /obj/effect/proc_holder/spell/aoe_turf/conjure/the_traps - category = "Defensive" - cost = 1 - - -/datum/spellbook_entry/item - name = "Buy Item" - refundable = FALSE - buy_word = "Summon" - var/item_path= null - - -/datum/spellbook_entry/item/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) - new item_path(get_turf(user)) - SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) - return TRUE - -/datum/spellbook_entry/item/GetInfo() - var/dat ="" - dat += "[name]" - dat += " Cost:[cost]
                    " - dat += "[desc]
                    " - if(surplus>=0) - dat += "[surplus] left.
                    " - return dat - -/datum/spellbook_entry/item/staffchange - name = "Staff of Change" - desc = "An artefact that spits bolts of coruscating energy which cause the target's very form to reshape itself." - item_path = /obj/item/gun/magic/staff/change - -/datum/spellbook_entry/item/staffanimation - name = "Staff of Animation" - desc = "An arcane staff capable of shooting bolts of eldritch energy which cause inanimate objects to come to life. This magic doesn't affect machines." - item_path = /obj/item/gun/magic/staff/animate - category = "Assistance" - -/datum/spellbook_entry/item/staffchaos - name = "Staff of Chaos" - desc = "A caprious tool that can fire all sorts of magic without any rhyme or reason. Using it on people you care about is not recommended." - item_path = /obj/item/gun/magic/staff/chaos - -/datum/spellbook_entry/item/spellblade - name = "Spellblade" - desc = "A sword capable of firing blasts of energy which rip targets limb from limb." - item_path = /obj/item/gun/magic/staff/spellblade - -/datum/spellbook_entry/item/staffdoor - name = "Staff of Door Creation" - desc = "A particular staff that can mold solid walls into ornate doors. Useful for getting around in the absence of other transportation. Does not work on glass." - item_path = /obj/item/gun/magic/staff/door - cost = 1 - category = "Mobility" - -/datum/spellbook_entry/item/staffhealing - name = "Staff of Healing" - desc = "An altruistic staff that can heal the lame and raise the dead." - item_path = /obj/item/gun/magic/staff/healing - cost = 1 - category = "Defensive" - -/datum/spellbook_entry/item/lockerstaff - name = "Staff of the Locker" - desc = "A staff that shoots lockers. It eats anyone it hits on its way, leaving a welded locker with your victims behind." - item_path = /obj/item/gun/magic/staff/locker - category = "Defensive" - -/datum/spellbook_entry/item/scryingorb - name = "Scrying Orb" - desc = "An incandescent orb of crackling energy. Using it will allow you to release your ghost while alive, allowing you to spy upon the station and talk to the deceased. In addition, buying it will permanently grant you X-ray vision." - item_path = /obj/item/scrying - category = "Defensive" - -/datum/spellbook_entry/item/soulstones - name = "Six Soul Stone Shards and the spell Artificer" - desc = "Soul Stone Shards are ancient tools capable of capturing and harnessing the spirits of the dead and dying. The spell Artificer allows you to create arcane machines for the captured souls to pilot." - item_path = /obj/item/storage/belt/soulstone/full - category = "Assistance" - -/datum/spellbook_entry/item/soulstones/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) - . =..() - if(.) - user.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/conjure/construct(null)) - return . - -/datum/spellbook_entry/item/necrostone - name = "A Necromantic Stone" - desc = "A Necromantic stone is able to resurrect three dead individuals as skeletal thralls for you to command." - item_path = /obj/item/necromantic_stone - category = "Assistance" - -/datum/spellbook_entry/item/wands - name = "Wand Assortment" - desc = "A collection of wands that allow for a wide variety of utility. Wands have a limited number of charges, so be conservative with their use. Comes in a handy belt." - item_path = /obj/item/storage/belt/wands/full - category = "Defensive" - -/datum/spellbook_entry/item/armor - name = "Mastercrafted Armor Set" - desc = "An artefact suit of armor that allows you to cast spells while providing more protection against attacks and the void of space." - item_path = /obj/item/clothing/suit/space/hardsuit/wizard - category = "Defensive" - -/datum/spellbook_entry/item/armor/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) - . = ..() - if(.) - new /obj/item/tank/internals/oxygen(get_turf(user)) //i need to BREATHE - new /obj/item/clothing/shoes/sandal/magic(get_turf(user)) //In case they've lost them. - new /obj/item/clothing/gloves/combat/wizard(get_turf(user))//To complete the outfit - -/datum/spellbook_entry/item/contract - name = "Contract of Apprenticeship" - desc = "A magical contract binding an apprentice wizard to your service, using it will summon them to your side." - item_path = /obj/item/antag_spawner/contract - category = "Assistance" - -/datum/spellbook_entry/item/guardian - name = "Guardian Deck" - desc = "A deck of guardian tarot cards, capable of binding a personal guardian to your body. There are multiple types of guardian available, but all of them will transfer some amount of damage to you. \ - It would be wise to avoid buying these with anything capable of causing you to swap bodies with others." - item_path = /obj/item/guardiancreator/choose/wizard - category = "Assistance" - -/datum/spellbook_entry/item/guardian/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) - . = ..() - if(.) - new /obj/item/paper/guides/antag/guardian/wizard(get_turf(user)) - -/datum/spellbook_entry/item/bloodbottle - name = "Bottle of Blood" - desc = "A bottle of magically infused blood, the smell of which will attract extradimensional beings when broken. Be careful though, the kinds of creatures summoned by blood magic are indiscriminate in their killing, and you yourself may become a victim." - item_path = /obj/item/antag_spawner/slaughter_demon - limit = 3 - category = "Assistance" - -/datum/spellbook_entry/item/hugbottle - name = "Bottle of Tickles" - desc = "A bottle of magically infused fun, the smell of which will \ - attract adorable extradimensional beings when broken. These beings \ - are similar to slaughter demons, but they do not permamently kill \ - their victims, instead putting them in an extradimensional hugspace, \ - to be released on the demon's death. Chaotic, but not ultimately \ - damaging. The crew's reaction to the other hand could be very \ - destructive." - item_path = /obj/item/antag_spawner/slaughter_demon/laughter - cost = 1 //non-destructive; it's just a jape, sibling! - limit = 3 - category = "Assistance" - -/datum/spellbook_entry/item/mjolnir - name = "Mjolnir" - desc = "A mighty hammer on loan from Thor, God of Thunder. It crackles with barely contained power." - item_path = /obj/item/mjollnir - -/datum/spellbook_entry/item/singularity_hammer - name = "Singularity Hammer" - desc = "A hammer that creates an intensely powerful field of gravity where it strikes, pulling everything nearby to the point of impact." - item_path = /obj/item/singularityhammer - -/datum/spellbook_entry/item/battlemage - name = "Battlemage Armour" - desc = "An ensorceled suit of armour, protected by a powerful shield. The shield can completely negate sixteen attacks before being permanently depleted." - item_path = /obj/item/clothing/suit/space/hardsuit/shielded/wizard - limit = 1 - category = "Defensive" - -/datum/spellbook_entry/item/battlemage/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) - . = ..() - if(.) - new /obj/item/clothing/shoes/sandal/magic(get_turf(user)) //In case they've lost them. - new /obj/item/clothing/gloves/combat/wizard(get_turf(user))//To complete the outfit - -/datum/spellbook_entry/item/battlemage_charge - name = "Battlemage Armour Charges" - desc = "A powerful defensive rune, it will grant eight additional charges to a suit of battlemage armour." - item_path = /obj/item/wizard_armour_charge - category = "Defensive" - cost = 1 - -/datum/spellbook_entry/item/warpwhistle - name = "Warp Whistle" - desc = "A strange whistle that will transport you to a distant safe place on the station. There is a window of vulnerability at the beginning of every use." - item_path = /obj/item/warpwhistle - category = "Mobility" - cost = 1 - -/datum/spellbook_entry/summon - name = "Summon Stuff" - category = "Rituals" - refundable = FALSE - buy_word = "Cast" - var/active = FALSE - -/datum/spellbook_entry/summon/CanBuy(mob/living/carbon/human/user,obj/item/spellbook/book) - return ..() && !active - -/datum/spellbook_entry/summon/GetInfo() - var/dat ="" - dat += "[name]" - if(cost>0) - dat += " Cost:[cost]
                    " - else - dat += " No Cost
                    " - dat += "[desc]
                    " - if(active) - dat += "Already cast!
                    " - return dat - -/datum/spellbook_entry/summon/ghosts - name = "Summon Ghosts" - desc = "Spook the crew out by making them see dead people. Be warned, ghosts are capricious and occasionally vindicative, and some will use their incredibly minor abilities to frustrate you." - cost = 0 - -/datum/spellbook_entry/summon/ghosts/IsAvailible() - if(!SSticker.mode) - return FALSE - else - return TRUE - -/datum/spellbook_entry/summon/ghosts/Buy(mob/living/carbon/human/user, obj/item/spellbook/book) - SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) - new /datum/round_event/wizard/ghost() - active = TRUE - to_chat(user, "You have cast summon ghosts!") - playsound(get_turf(user), 'sound/effects/ghost2.ogg', 50, TRUE) - return TRUE - -/datum/spellbook_entry/summon/guns - name = "Summon Guns" - desc = "Nothing could possibly go wrong with arming a crew of lunatics just itching for an excuse to kill you. There is a good chance that they will shoot each other first." - -/datum/spellbook_entry/summon/guns/IsAvailible() - if(!SSticker.mode) // In case spellbook is placed on map - return FALSE - if(istype(SSticker.mode, /datum/game_mode/dynamic)) // Disable events on dynamic - return FALSE - return !CONFIG_GET(flag/no_summon_guns) - -/datum/spellbook_entry/summon/guns/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) - SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) - rightandwrong(SUMMON_GUNS, user, 10) - active = TRUE - playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, TRUE) - to_chat(user, "You have cast summon guns!") - return TRUE - -/datum/spellbook_entry/summon/magic - name = "Summon Magic" - desc = "Share the wonders of magic with the crew and show them why they aren't to be trusted with it at the same time." - -/datum/spellbook_entry/summon/magic/IsAvailible() - if(!SSticker.mode) // In case spellbook is placed on map - return FALSE - if(istype(SSticker.mode, /datum/game_mode/dynamic)) // Disable events on dynamic - return FALSE - return !CONFIG_GET(flag/no_summon_magic) - -/datum/spellbook_entry/summon/magic/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) - SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) - rightandwrong(SUMMON_MAGIC, user, 10) - active = TRUE - playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, TRUE) - to_chat(user, "You have cast summon magic!") - return TRUE - -/datum/spellbook_entry/summon/events - name = "Summon Events" - desc = "Give Murphy's law a little push and replace all events with special wizard ones that will confound and confuse everyone. Multiple castings increase the rate of these events." - cost = 2 - limit = 1 - var/times = 0 - -/datum/spellbook_entry/summon/events/IsAvailible() - if(!SSticker.mode) // In case spellbook is placed on map - return FALSE - if(istype(SSticker.mode, /datum/game_mode/dynamic)) // Disable events on dynamic - return FALSE - return !CONFIG_GET(flag/no_summon_events) - -/datum/spellbook_entry/summon/events/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) - SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) - summonevents() - times++ - playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, TRUE) - to_chat(user, "You have cast summon events.") - return TRUE - -/datum/spellbook_entry/summon/events/GetInfo() - . = ..() - if(times>0) - . += "You cast it [times] times.
                    " - return . - -/datum/spellbook_entry/summon/curse_of_madness - name = "Curse of Madness" - desc = "Curses the station, warping the minds of everyone inside, causing lasting traumas. Warning: this spell can affect you if not cast from a safe distance." - cost = 4 - -/datum/spellbook_entry/summon/curse_of_madness/Buy(mob/living/carbon/human/user, obj/item/spellbook/book) - SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) - active = TRUE - var/message = stripped_input(user, "Whisper a secret truth to drive your victims to madness.", "Whispers of Madness") - if(!message) - return FALSE - curse_of_madness(user, message) - to_chat(user, "You have cast the curse of insanity!") - playsound(user, 'sound/magic/mandswap.ogg', 50, TRUE) - return TRUE - -/obj/item/spellbook - name = "spell book" - desc = "An unearthly tome that glows with power." - icon = 'icons/obj/library.dmi' - icon_state ="book" - throw_speed = 2 - throw_range = 5 - w_class = WEIGHT_CLASS_TINY - var/uses = 10 - var/temp = null - var/tab = null - var/mob/living/carbon/human/owner - var/list/datum/spellbook_entry/entries = list() - var/list/categories = list() - -/obj/item/spellbook/examine(mob/user) - . = ..() - if(owner) - . += {"There is a small signature on the front cover: "[owner]"."} - else - . += "It appears to have no author." - -/obj/item/spellbook/Initialize() - . = ..() - prepare_spells() - -/obj/item/spellbook/proc/prepare_spells() - var/entry_types = subtypesof(/datum/spellbook_entry) - /datum/spellbook_entry/item - /datum/spellbook_entry/summon - for(var/T in entry_types) - var/datum/spellbook_entry/E = new T - if(E.IsAvailible()) - entries |= E - categories |= E.category - else - qdel(E) - tab = categories[1] - -/obj/item/spellbook/attackby(obj/item/O, mob/user, params) - if(istype(O, /obj/item/antag_spawner/contract)) - var/obj/item/antag_spawner/contract/contract = O - if(contract.used) - to_chat(user, "The contract has been used, you can't get your points back now!") - else - to_chat(user, "You feed the contract back into the spellbook, refunding your points.") - uses += 2 - for(var/datum/spellbook_entry/item/contract/CT in entries) - if(!isnull(CT.limit)) - CT.limit++ - qdel(O) - else if(istype(O, /obj/item/antag_spawner/slaughter_demon)) - to_chat(user, "On second thought, maybe summoning a demon is a bad idea. You refund your points.") - if(istype(O, /obj/item/antag_spawner/slaughter_demon/laughter)) - uses += 1 - for(var/datum/spellbook_entry/item/hugbottle/HB in entries) - if(!isnull(HB.limit)) - HB.limit++ - else - uses += 2 - for(var/datum/spellbook_entry/item/bloodbottle/BB in entries) - if(!isnull(BB.limit)) - BB.limit++ - qdel(O) - -/obj/item/spellbook/proc/GetCategoryHeader(category) - var/dat = "" - switch(category) - if("Offensive") - dat += "Spells and items geared towards debilitating and destroying.

                    " - dat += "Items are not bound to you and can be stolen. Additionally they cannot typically be returned once purchased.
                    " - dat += "For spells: the number after the spell name is the cooldown time.
                    " - dat += "You can reduce this number by spending more points on the spell.
                    " - if("Defensive") - dat += "Spells and items geared towards improving your survivability or reducing foes' ability to attack.

                    " - dat += "Items are not bound to you and can be stolen. Additionally they cannot typically be returned once purchased.
                    " - dat += "For spells: the number after the spell name is the cooldown time.
                    " - dat += "You can reduce this number by spending more points on the spell.
                    " - if("Mobility") - dat += "Spells and items geared towards improving your ability to move. It is a good idea to take at least one.

                    " - dat += "Items are not bound to you and can be stolen. Additionally they cannot typically be returned once purchased.
                    " - dat += "For spells: the number after the spell name is the cooldown time.
                    " - dat += "You can reduce this number by spending more points on the spell.
                    " - if("Assistance") - dat += "Spells and items geared towards bringing in outside forces to aid you or improving upon your other items and abilities.

                    " - dat += "Items are not bound to you and can be stolen. Additionally they cannot typically be returned once purchased.
                    " - dat += "For spells: the number after the spell name is the cooldown time.
                    " - dat += "You can reduce this number by spending more points on the spell.
                    " - if("Challenges") - dat += "The Wizard Federation typically has hard limits on the potency and number of spells brought to the station based on risk.
                    " - dat += "Arming the station against you will increases the risk, but will grant you one more charge for your spellbook.
                    " - if("Rituals") - dat += "These powerful spells change the very fabric of reality. Not always in your favour.
                    " - return dat - -/obj/item/spellbook/proc/wrap(content) - var/dat = "" - dat +="Spellbook" - dat += {" - - - - "} - dat += {"[content]"} - return dat - -/obj/item/spellbook/attack_self(mob/user) - if(!owner) - to_chat(user, "You bind the spellbook to yourself.") - owner = user - return - if(user != owner) - to_chat(user, "The [name] does not recognize you as its owner and refuses to open!") - return - user.set_machine(src) - var/dat = "" - - dat += "" - - var/datum/spellbook_entry/E - for(var/i=1,i<=entries.len,i++) - var/spell_info = "" - E = entries[i] - spell_info += E.GetInfo() - if(E.CanBuy(user,src)) - spell_info+= "[E.buy_word]
                    " - else - spell_info+= "Can't [E.buy_word]
                    " - if(E.CanRefund(user,src)) - spell_info+= "Refund
                    " - spell_info += "
                    " - if(cat_dat[E.category]) - cat_dat[E.category] += spell_info - - for(var/category in categories) - dat += "
                    " - dat += GetCategoryHeader(category) - dat += cat_dat[category] - dat += "
                    " - - user << browse(wrap(dat), "window=spellbook;size=700x500") - onclose(user, "spellbook") - return - -/obj/item/spellbook/Topic(href, href_list) - ..() - var/mob/living/carbon/human/H = usr - - if(H.stat || H.restrained()) - return - if(!ishuman(H)) - return TRUE - - if(H.mind.special_role == "apprentice") - temp = "If you got caught sneaking a peek from your teacher's spellbook, you'd likely be expelled from the Wizard Academy. Better not." - return - - var/datum/spellbook_entry/E = null - if(loc == H || (in_range(src, H) && isturf(loc))) - H.set_machine(src) - if(href_list["buy"]) - E = entries[text2num(href_list["buy"])] - if(E && E.CanBuy(H,src)) - if(E.Buy(H,src)) - if(E.limit) - E.limit-- - uses -= E.cost - else if(href_list["refund"]) - E = entries[text2num(href_list["refund"])] - if(E && E.refundable) - var/result = E.Refund(H,src) - if(result > 0) - if(!isnull(E.limit)) - E.limit += result - uses += result - else if(href_list["page"]) - tab = sanitize(href_list["page"]) - attack_self(H) - return +/datum/spellbook_entry + var/name = "Entry Name" + + var/spell_type = null + var/desc = "" + var/category = "Offensive" + var/cost = 2 + var/refundable = TRUE + var/surplus = -1 // -1 for infinite, not used by anything atm + var/obj/effect/proc_holder/spell/S = null //Since spellbooks can be used by only one person anyway we can track the actual spell + var/buy_word = "Learn" + var/limit //used to prevent a spellbook_entry from being bought more than X times with one wizard spellbook + var/list/no_coexistance_typecache //Used so you can't have specific spells together + +/datum/spellbook_entry/New() + ..() + no_coexistance_typecache = typecacheof(no_coexistance_typecache) + +/datum/spellbook_entry/proc/IsAvailible() // For config prefs / gamemode restrictions - these are round applied + return TRUE + +/datum/spellbook_entry/proc/CanBuy(mob/living/carbon/human/user,obj/item/spellbook/book) // Specific circumstances + if(book.uses= aspell.level_max) + to_chat(user, "This spell cannot be improved further!") + return FALSE + else + aspell.name = initial(aspell.name) + aspell.spell_level++ + aspell.charge_max = round(initial(aspell.charge_max) - aspell.spell_level * (initial(aspell.charge_max) - aspell.cooldown_min)/ aspell.level_max) + if(aspell.charge_max < aspell.charge_counter) + aspell.charge_counter = aspell.charge_max + switch(aspell.spell_level) + if(1) + to_chat(user, "You have improved [aspell.name] into Efficient [aspell.name].") + aspell.name = "Efficient [aspell.name]" + if(2) + to_chat(user, "You have further improved [aspell.name] into Quickened [aspell.name].") + aspell.name = "Quickened [aspell.name]" + if(3) + to_chat(user, "You have further improved [aspell.name] into Free [aspell.name].") + aspell.name = "Free [aspell.name]" + if(4) + to_chat(user, "You have further improved [aspell.name] into Instant [aspell.name].") + aspell.name = "Instant [aspell.name]" + if(aspell.spell_level >= aspell.level_max) + to_chat(user, "This spell cannot be strengthened any further!") + SSblackbox.record_feedback("nested tally", "wizard_spell_improved", 1, list("[name]", "[aspell.spell_level]")) + return TRUE + //No same spell found - just learn it + SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) + user.mind.AddSpell(S) + to_chat(user, "You have learned [S.name].") + return TRUE + +/datum/spellbook_entry/proc/CanRefund(mob/living/carbon/human/user,obj/item/spellbook/book) + if(!refundable) + return FALSE + if(!S) + S = new spell_type() + for(var/obj/effect/proc_holder/spell/aspell in user.mind.spell_list) + if(initial(S.name) == initial(aspell.name)) + return TRUE + return FALSE + +/datum/spellbook_entry/proc/Refund(mob/living/carbon/human/user,obj/item/spellbook/book) //return point value or -1 for failure + var/area/wizard_station/A = GLOB.areas_by_type[/area/wizard_station] + if(!(user in A.contents)) + to_chat(user, "You can only refund spells at the wizard lair!") + return -1 + if(!S) + S = new spell_type() + var/spell_levels = 0 + for(var/obj/effect/proc_holder/spell/aspell in user.mind.spell_list) + if(initial(S.name) == initial(aspell.name)) + spell_levels = aspell.spell_level + user.mind.spell_list.Remove(aspell) + qdel(S) + return cost * (spell_levels+1) + return -1 +/datum/spellbook_entry/proc/GetInfo() + if(!S) + S = new spell_type() + var/dat ="" + dat += "[initial(S.name)]" + if(S.charge_type == "recharge") + dat += " Cooldown:[S.charge_max/10]" + dat += " Cost:[cost]
                    " + dat += "[S.desc][desc]
                    " + dat += "[S.clothes_req?"Requires wizard garb.":"Can be cast without wizard garb."]
                    " + return dat + +/datum/spellbook_entry/fireball + name = "Fireball" + spell_type = /obj/effect/proc_holder/spell/aimed/fireball + +/datum/spellbook_entry/spell_cards + name = "Spell Cards" + spell_type = /obj/effect/proc_holder/spell/aimed/spell_cards + +/datum/spellbook_entry/rod_form + name = "Rod Form" + spell_type = /obj/effect/proc_holder/spell/targeted/rod_form + +/datum/spellbook_entry/magicm + name = "Magic Missile" + spell_type = /obj/effect/proc_holder/spell/targeted/projectile/magic_missile + category = "Defensive" + +/datum/spellbook_entry/disintegrate + name = "Smite" + spell_type = /obj/effect/proc_holder/spell/targeted/touch/disintegrate + +/datum/spellbook_entry/disabletech + name = "Disable Tech" + spell_type = /obj/effect/proc_holder/spell/targeted/emplosion/disable_tech + category = "Defensive" + cost = 1 + +/datum/spellbook_entry/repulse + name = "Repulse" + spell_type = /obj/effect/proc_holder/spell/aoe_turf/repulse + category = "Defensive" + +/datum/spellbook_entry/lightningPacket + name = "Thrown Lightning" + spell_type = /obj/effect/proc_holder/spell/targeted/conjure_item/spellpacket + category = "Defensive" + +/datum/spellbook_entry/timestop + name = "Time Stop" + spell_type = /obj/effect/proc_holder/spell/aoe_turf/timestop + category = "Defensive" + +/datum/spellbook_entry/smoke + name = "Smoke" + spell_type = /obj/effect/proc_holder/spell/targeted/smoke + category = "Defensive" + cost = 1 + +/datum/spellbook_entry/blind + name = "Blind" + spell_type = /obj/effect/proc_holder/spell/pointed/trigger/blind + cost = 1 + +/datum/spellbook_entry/mindswap + name = "Mindswap" + spell_type = /obj/effect/proc_holder/spell/pointed/mind_transfer + category = "Mobility" + +/datum/spellbook_entry/forcewall + name = "Force Wall" + spell_type = /obj/effect/proc_holder/spell/targeted/forcewall + category = "Defensive" + cost = 1 + +/datum/spellbook_entry/blink + name = "Blink" + spell_type = /obj/effect/proc_holder/spell/targeted/turf_teleport/blink + category = "Mobility" + +/datum/spellbook_entry/teleport + name = "Teleport" + spell_type = /obj/effect/proc_holder/spell/targeted/area_teleport/teleport + category = "Mobility" + +/datum/spellbook_entry/mutate + name = "Mutate" + spell_type = /obj/effect/proc_holder/spell/targeted/genetic/mutate + +/datum/spellbook_entry/jaunt + name = "Ethereal Jaunt" + spell_type = /obj/effect/proc_holder/spell/targeted/ethereal_jaunt + category = "Mobility" + +/datum/spellbook_entry/knock + name = "Knock" + spell_type = /obj/effect/proc_holder/spell/aoe_turf/knock + category = "Mobility" + cost = 1 + +/datum/spellbook_entry/fleshtostone + name = "Flesh to Stone" + spell_type = /obj/effect/proc_holder/spell/targeted/touch/flesh_to_stone + +/datum/spellbook_entry/summonitem + name = "Summon Item" + spell_type = /obj/effect/proc_holder/spell/targeted/summonitem + category = "Assistance" + cost = 1 + +/datum/spellbook_entry/lichdom + name = "Bind Soul" + spell_type = /obj/effect/proc_holder/spell/targeted/lichdom + category = "Defensive" + +/datum/spellbook_entry/teslablast + name = "Tesla Blast" + spell_type = /obj/effect/proc_holder/spell/targeted/tesla + +/datum/spellbook_entry/lightningbolt + name = "Lightning Bolt" + spell_type = /obj/effect/proc_holder/spell/aimed/lightningbolt + cost = 1 + +/datum/spellbook_entry/lightningbolt/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) //return TRUE on success + . = ..() + ADD_TRAIT(user, TRAIT_TESLA_SHOCKIMMUNE, "lightning_bolt_spell") + +/datum/spellbook_entry/lightningbolt/Refund(mob/living/carbon/human/user, obj/item/spellbook/book) + . = ..() + REMOVE_TRAIT(user, TRAIT_TESLA_SHOCKIMMUNE, "lightning_bolt_spell") + +/datum/spellbook_entry/infinite_guns + name = "Lesser Summon Guns" + spell_type = /obj/effect/proc_holder/spell/targeted/infinite_guns/gun + cost = 3 + no_coexistance_typecache = /obj/effect/proc_holder/spell/targeted/infinite_guns/arcane_barrage + +/datum/spellbook_entry/arcane_barrage + name = "Arcane Barrage" + spell_type = /obj/effect/proc_holder/spell/targeted/infinite_guns/arcane_barrage + cost = 3 + no_coexistance_typecache = /obj/effect/proc_holder/spell/targeted/infinite_guns/gun + +/datum/spellbook_entry/barnyard + name = "Barnyard Curse" + spell_type = /obj/effect/proc_holder/spell/pointed/barnyardcurse + +/datum/spellbook_entry/charge + name = "Charge" + spell_type = /obj/effect/proc_holder/spell/targeted/charge + category = "Assistance" + cost = 1 + +/datum/spellbook_entry/shapeshift + name = "Wild Shapeshift" + spell_type = /obj/effect/proc_holder/spell/targeted/shapeshift + category = "Assistance" + cost = 1 + +/datum/spellbook_entry/tap + name = "Soul Tap" + spell_type = /obj/effect/proc_holder/spell/self/tap + category = "Assistance" + cost = 1 + +/datum/spellbook_entry/spacetime_dist + name = "Spacetime Distortion" + spell_type = /obj/effect/proc_holder/spell/spacetime_dist + category = "Defensive" + cost = 1 + +/datum/spellbook_entry/the_traps + name = "The Traps!" + spell_type = /obj/effect/proc_holder/spell/aoe_turf/conjure/the_traps + category = "Defensive" + cost = 1 + + +/datum/spellbook_entry/item + name = "Buy Item" + refundable = FALSE + buy_word = "Summon" + var/item_path= null + + +/datum/spellbook_entry/item/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) + new item_path(get_turf(user)) + SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) + return TRUE + +/datum/spellbook_entry/item/GetInfo() + var/dat ="" + dat += "[name]" + dat += " Cost:[cost]
                    " + dat += "[desc]
                    " + if(surplus>=0) + dat += "[surplus] left.
                    " + return dat + +/datum/spellbook_entry/item/staffchange + name = "Staff of Change" + desc = "An artefact that spits bolts of coruscating energy which cause the target's very form to reshape itself." + item_path = /obj/item/gun/magic/staff/change + +/datum/spellbook_entry/item/staffanimation + name = "Staff of Animation" + desc = "An arcane staff capable of shooting bolts of eldritch energy which cause inanimate objects to come to life. This magic doesn't affect machines." + item_path = /obj/item/gun/magic/staff/animate + category = "Assistance" + +/datum/spellbook_entry/item/staffchaos + name = "Staff of Chaos" + desc = "A caprious tool that can fire all sorts of magic without any rhyme or reason. Using it on people you care about is not recommended." + item_path = /obj/item/gun/magic/staff/chaos + +/datum/spellbook_entry/item/spellblade + name = "Spellblade" + desc = "A sword capable of firing blasts of energy which rip targets limb from limb." + item_path = /obj/item/gun/magic/staff/spellblade + +/datum/spellbook_entry/item/staffdoor + name = "Staff of Door Creation" + desc = "A particular staff that can mold solid walls into ornate doors. Useful for getting around in the absence of other transportation. Does not work on glass." + item_path = /obj/item/gun/magic/staff/door + cost = 1 + category = "Mobility" + +/datum/spellbook_entry/item/staffhealing + name = "Staff of Healing" + desc = "An altruistic staff that can heal the lame and raise the dead." + item_path = /obj/item/gun/magic/staff/healing + cost = 1 + category = "Defensive" + +/datum/spellbook_entry/item/lockerstaff + name = "Staff of the Locker" + desc = "A staff that shoots lockers. It eats anyone it hits on its way, leaving a welded locker with your victims behind." + item_path = /obj/item/gun/magic/staff/locker + category = "Defensive" + +/datum/spellbook_entry/item/scryingorb + name = "Scrying Orb" + desc = "An incandescent orb of crackling energy. Using it will allow you to release your ghost while alive, allowing you to spy upon the station and talk to the deceased. In addition, buying it will permanently grant you X-ray vision." + item_path = /obj/item/scrying + category = "Defensive" + +/datum/spellbook_entry/item/soulstones + name = "Six Soul Stone Shards and the spell Artificer" + desc = "Soul Stone Shards are ancient tools capable of capturing and harnessing the spirits of the dead and dying. The spell Artificer allows you to create arcane machines for the captured souls to pilot." + item_path = /obj/item/storage/belt/soulstone/full + category = "Assistance" + +/datum/spellbook_entry/item/soulstones/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) + . =..() + if(.) + user.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/conjure/construct(null)) + return . + +/datum/spellbook_entry/item/necrostone + name = "A Necromantic Stone" + desc = "A Necromantic stone is able to resurrect three dead individuals as skeletal thralls for you to command." + item_path = /obj/item/necromantic_stone + category = "Assistance" + +/datum/spellbook_entry/item/wands + name = "Wand Assortment" + desc = "A collection of wands that allow for a wide variety of utility. Wands have a limited number of charges, so be conservative with their use. Comes in a handy belt." + item_path = /obj/item/storage/belt/wands/full + category = "Defensive" + +/datum/spellbook_entry/item/armor + name = "Mastercrafted Armor Set" + desc = "An artefact suit of armor that allows you to cast spells while providing more protection against attacks and the void of space." + item_path = /obj/item/clothing/suit/space/hardsuit/wizard + category = "Defensive" + +/datum/spellbook_entry/item/armor/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) + . = ..() + if(.) + new /obj/item/tank/internals/oxygen(get_turf(user)) //i need to BREATHE + new /obj/item/clothing/shoes/sandal/magic(get_turf(user)) //In case they've lost them. + new /obj/item/clothing/gloves/combat/wizard(get_turf(user))//To complete the outfit + +/datum/spellbook_entry/item/contract + name = "Contract of Apprenticeship" + desc = "A magical contract binding an apprentice wizard to your service, using it will summon them to your side." + item_path = /obj/item/antag_spawner/contract + category = "Assistance" + +/datum/spellbook_entry/item/guardian + name = "Guardian Deck" + desc = "A deck of guardian tarot cards, capable of binding a personal guardian to your body. There are multiple types of guardian available, but all of them will transfer some amount of damage to you. \ + It would be wise to avoid buying these with anything capable of causing you to swap bodies with others." + item_path = /obj/item/guardiancreator/choose/wizard + category = "Assistance" + +/datum/spellbook_entry/item/guardian/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) + . = ..() + if(.) + new /obj/item/paper/guides/antag/guardian/wizard(get_turf(user)) + +/datum/spellbook_entry/item/bloodbottle + name = "Bottle of Blood" + desc = "A bottle of magically infused blood, the smell of which will attract extradimensional beings when broken. Be careful though, the kinds of creatures summoned by blood magic are indiscriminate in their killing, and you yourself may become a victim." + item_path = /obj/item/antag_spawner/slaughter_demon + limit = 3 + category = "Assistance" + +/datum/spellbook_entry/item/hugbottle + name = "Bottle of Tickles" + desc = "A bottle of magically infused fun, the smell of which will \ + attract adorable extradimensional beings when broken. These beings \ + are similar to slaughter demons, but they do not permamently kill \ + their victims, instead putting them in an extradimensional hugspace, \ + to be released on the demon's death. Chaotic, but not ultimately \ + damaging. The crew's reaction to the other hand could be very \ + destructive." + item_path = /obj/item/antag_spawner/slaughter_demon/laughter + cost = 1 //non-destructive; it's just a jape, sibling! + limit = 3 + category = "Assistance" + +/datum/spellbook_entry/item/mjolnir + name = "Mjolnir" + desc = "A mighty hammer on loan from Thor, God of Thunder. It crackles with barely contained power." + item_path = /obj/item/mjollnir + +/datum/spellbook_entry/item/singularity_hammer + name = "Singularity Hammer" + desc = "A hammer that creates an intensely powerful field of gravity where it strikes, pulling everything nearby to the point of impact." + item_path = /obj/item/singularityhammer + +/datum/spellbook_entry/item/battlemage + name = "Battlemage Armour" + desc = "An ensorceled suit of armour, protected by a powerful shield. The shield can completely negate sixteen attacks before being permanently depleted." + item_path = /obj/item/clothing/suit/space/hardsuit/shielded/wizard + limit = 1 + category = "Defensive" + +/datum/spellbook_entry/item/battlemage/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) + . = ..() + if(.) + new /obj/item/clothing/shoes/sandal/magic(get_turf(user)) //In case they've lost them. + new /obj/item/clothing/gloves/combat/wizard(get_turf(user))//To complete the outfit + +/datum/spellbook_entry/item/battlemage_charge + name = "Battlemage Armour Charges" + desc = "A powerful defensive rune, it will grant eight additional charges to a suit of battlemage armour." + item_path = /obj/item/wizard_armour_charge + category = "Defensive" + cost = 1 + +/datum/spellbook_entry/item/warpwhistle + name = "Warp Whistle" + desc = "A strange whistle that will transport you to a distant safe place on the station. There is a window of vulnerability at the beginning of every use." + item_path = /obj/item/warpwhistle + category = "Mobility" + cost = 1 + +/datum/spellbook_entry/summon + name = "Summon Stuff" + category = "Rituals" + refundable = FALSE + buy_word = "Cast" + var/active = FALSE + +/datum/spellbook_entry/summon/CanBuy(mob/living/carbon/human/user,obj/item/spellbook/book) + return ..() && !active + +/datum/spellbook_entry/summon/GetInfo() + var/dat ="" + dat += "[name]" + if(cost>0) + dat += " Cost:[cost]
                    " + else + dat += " No Cost
                    " + dat += "[desc]
                    " + if(active) + dat += "Already cast!
                    " + return dat + +/datum/spellbook_entry/summon/ghosts + name = "Summon Ghosts" + desc = "Spook the crew out by making them see dead people. Be warned, ghosts are capricious and occasionally vindicative, and some will use their incredibly minor abilities to frustrate you." + cost = 0 + +/datum/spellbook_entry/summon/ghosts/IsAvailible() + if(!SSticker.mode) + return FALSE + else + return TRUE + +/datum/spellbook_entry/summon/ghosts/Buy(mob/living/carbon/human/user, obj/item/spellbook/book) + SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) + new /datum/round_event/wizard/ghost() + active = TRUE + to_chat(user, "You have cast summon ghosts!") + playsound(get_turf(user), 'sound/effects/ghost2.ogg', 50, TRUE) + return TRUE + +/datum/spellbook_entry/summon/guns + name = "Summon Guns" + desc = "Nothing could possibly go wrong with arming a crew of lunatics just itching for an excuse to kill you. There is a good chance that they will shoot each other first." + +/datum/spellbook_entry/summon/guns/IsAvailible() + if(!SSticker.mode) // In case spellbook is placed on map + return FALSE + if(istype(SSticker.mode, /datum/game_mode/dynamic)) // Disable events on dynamic + return FALSE + return !CONFIG_GET(flag/no_summon_guns) + +/datum/spellbook_entry/summon/guns/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) + SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) + rightandwrong(SUMMON_GUNS, user, 10) + active = TRUE + playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, TRUE) + to_chat(user, "You have cast summon guns!") + return TRUE + +/datum/spellbook_entry/summon/magic + name = "Summon Magic" + desc = "Share the wonders of magic with the crew and show them why they aren't to be trusted with it at the same time." + +/datum/spellbook_entry/summon/magic/IsAvailible() + if(!SSticker.mode) // In case spellbook is placed on map + return FALSE + if(istype(SSticker.mode, /datum/game_mode/dynamic)) // Disable events on dynamic + return FALSE + return !CONFIG_GET(flag/no_summon_magic) + +/datum/spellbook_entry/summon/magic/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) + SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) + rightandwrong(SUMMON_MAGIC, user, 10) + active = TRUE + playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, TRUE) + to_chat(user, "You have cast summon magic!") + return TRUE + +/datum/spellbook_entry/summon/events + name = "Summon Events" + desc = "Give Murphy's law a little push and replace all events with special wizard ones that will confound and confuse everyone. Multiple castings increase the rate of these events." + cost = 2 + limit = 1 + var/times = 0 + +/datum/spellbook_entry/summon/events/IsAvailible() + if(!SSticker.mode) // In case spellbook is placed on map + return FALSE + if(istype(SSticker.mode, /datum/game_mode/dynamic)) // Disable events on dynamic + return FALSE + return !CONFIG_GET(flag/no_summon_events) + +/datum/spellbook_entry/summon/events/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) + SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) + summonevents() + times++ + playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, TRUE) + to_chat(user, "You have cast summon events.") + return TRUE + +/datum/spellbook_entry/summon/events/GetInfo() + . = ..() + if(times>0) + . += "You cast it [times] times.
                    " + return . + +/datum/spellbook_entry/summon/curse_of_madness + name = "Curse of Madness" + desc = "Curses the station, warping the minds of everyone inside, causing lasting traumas. Warning: this spell can affect you if not cast from a safe distance." + cost = 4 + +/datum/spellbook_entry/summon/curse_of_madness/Buy(mob/living/carbon/human/user, obj/item/spellbook/book) + SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) + active = TRUE + var/message = stripped_input(user, "Whisper a secret truth to drive your victims to madness.", "Whispers of Madness") + if(!message) + return FALSE + curse_of_madness(user, message) + to_chat(user, "You have cast the curse of insanity!") + playsound(user, 'sound/magic/mandswap.ogg', 50, TRUE) + return TRUE + +/obj/item/spellbook + name = "spell book" + desc = "An unearthly tome that glows with power." + icon = 'icons/obj/library.dmi' + icon_state ="book" + throw_speed = 2 + throw_range = 5 + w_class = WEIGHT_CLASS_TINY + var/uses = 10 + var/temp = null + var/tab = null + var/mob/living/carbon/human/owner + var/list/datum/spellbook_entry/entries = list() + var/list/categories = list() + +/obj/item/spellbook/examine(mob/user) + . = ..() + if(owner) + . += {"There is a small signature on the front cover: "[owner]"."} + else + . += "It appears to have no author." + +/obj/item/spellbook/Initialize() + . = ..() + prepare_spells() + +/obj/item/spellbook/proc/prepare_spells() + var/entry_types = subtypesof(/datum/spellbook_entry) - /datum/spellbook_entry/item - /datum/spellbook_entry/summon + for(var/T in entry_types) + var/datum/spellbook_entry/E = new T + if(E.IsAvailible()) + entries |= E + categories |= E.category + else + qdel(E) + tab = categories[1] + +/obj/item/spellbook/attackby(obj/item/O, mob/user, params) + if(istype(O, /obj/item/antag_spawner/contract)) + var/obj/item/antag_spawner/contract/contract = O + if(contract.used) + to_chat(user, "The contract has been used, you can't get your points back now!") + else + to_chat(user, "You feed the contract back into the spellbook, refunding your points.") + uses += 2 + for(var/datum/spellbook_entry/item/contract/CT in entries) + if(!isnull(CT.limit)) + CT.limit++ + qdel(O) + else if(istype(O, /obj/item/antag_spawner/slaughter_demon)) + to_chat(user, "On second thought, maybe summoning a demon is a bad idea. You refund your points.") + if(istype(O, /obj/item/antag_spawner/slaughter_demon/laughter)) + uses += 1 + for(var/datum/spellbook_entry/item/hugbottle/HB in entries) + if(!isnull(HB.limit)) + HB.limit++ + else + uses += 2 + for(var/datum/spellbook_entry/item/bloodbottle/BB in entries) + if(!isnull(BB.limit)) + BB.limit++ + qdel(O) + +/obj/item/spellbook/proc/GetCategoryHeader(category) + var/dat = "" + switch(category) + if("Offensive") + dat += "Spells and items geared towards debilitating and destroying.

                    " + dat += "Items are not bound to you and can be stolen. Additionally they cannot typically be returned once purchased.
                    " + dat += "For spells: the number after the spell name is the cooldown time.
                    " + dat += "You can reduce this number by spending more points on the spell.
                    " + if("Defensive") + dat += "Spells and items geared towards improving your survivability or reducing foes' ability to attack.

                    " + dat += "Items are not bound to you and can be stolen. Additionally they cannot typically be returned once purchased.
                    " + dat += "For spells: the number after the spell name is the cooldown time.
                    " + dat += "You can reduce this number by spending more points on the spell.
                    " + if("Mobility") + dat += "Spells and items geared towards improving your ability to move. It is a good idea to take at least one.

                    " + dat += "Items are not bound to you and can be stolen. Additionally they cannot typically be returned once purchased.
                    " + dat += "For spells: the number after the spell name is the cooldown time.
                    " + dat += "You can reduce this number by spending more points on the spell.
                    " + if("Assistance") + dat += "Spells and items geared towards bringing in outside forces to aid you or improving upon your other items and abilities.

                    " + dat += "Items are not bound to you and can be stolen. Additionally they cannot typically be returned once purchased.
                    " + dat += "For spells: the number after the spell name is the cooldown time.
                    " + dat += "You can reduce this number by spending more points on the spell.
                    " + if("Challenges") + dat += "The Wizard Federation typically has hard limits on the potency and number of spells brought to the station based on risk.
                    " + dat += "Arming the station against you will increases the risk, but will grant you one more charge for your spellbook.
                    " + if("Rituals") + dat += "These powerful spells change the very fabric of reality. Not always in your favour.
                    " + return dat + +/obj/item/spellbook/proc/wrap(content) + var/dat = "" + dat +="Spellbook" + dat += {" + + + + "} + dat += {"[content]"} + return dat + +/obj/item/spellbook/attack_self(mob/user) + if(!owner) + to_chat(user, "You bind the spellbook to yourself.") + owner = user + return + if(user != owner) + to_chat(user, "The [name] does not recognize you as its owner and refuses to open!") + return + user.set_machine(src) + var/dat = "" + + dat += "" + + var/datum/spellbook_entry/E + for(var/i=1,i<=entries.len,i++) + var/spell_info = "" + E = entries[i] + spell_info += E.GetInfo() + if(E.CanBuy(user,src)) + spell_info+= "[E.buy_word]
                    " + else + spell_info+= "Can't [E.buy_word]
                    " + if(E.CanRefund(user,src)) + spell_info+= "Refund
                    " + spell_info += "
                    " + if(cat_dat[E.category]) + cat_dat[E.category] += spell_info + + for(var/category in categories) + dat += "
                    " + dat += GetCategoryHeader(category) + dat += cat_dat[category] + dat += "
                    " + + user << browse(wrap(dat), "window=spellbook;size=700x500") + onclose(user, "spellbook") + return + +/obj/item/spellbook/Topic(href, href_list) + ..() + var/mob/living/carbon/human/H = usr + + if(H.stat || H.restrained()) + return + if(!ishuman(H)) + return TRUE + + if(H.mind.special_role == "apprentice") + temp = "If you got caught sneaking a peek from your teacher's spellbook, you'd likely be expelled from the Wizard Academy. Better not." + return + + var/datum/spellbook_entry/E = null + if(loc == H || (in_range(src, H) && isturf(loc))) + H.set_machine(src) + if(href_list["buy"]) + E = entries[text2num(href_list["buy"])] + if(E && E.CanBuy(H,src)) + if(E.Buy(H,src)) + if(E.limit) + E.limit-- + uses -= E.cost + else if(href_list["refund"]) + E = entries[text2num(href_list["refund"])] + if(E && E.refundable) + var/result = E.Refund(H,src) + if(result > 0) + if(!isnull(E.limit)) + E.limit += result + uses += result + else if(href_list["page"]) + tab = sanitize(href_list["page"]) + attack_self(H) + return diff --git a/code/modules/assembly/assembly.dm b/code/modules/assembly/assembly.dm index 7fb5a7fefe48..ed7f49c36a66 100644 --- a/code/modules/assembly/assembly.dm +++ b/code/modules/assembly/assembly.dm @@ -1,127 +1,130 @@ -#define WIRE_RECEIVE (1<<0) -#define WIRE_PULSE (1<<1) -#define WIRE_PULSE_SPECIAL (1<<2) -#define WIRE_RADIO_RECEIVE (1<<3) -#define WIRE_RADIO_PULSE (1<<4) -#define ASSEMBLY_BEEP_VOLUME 5 - -/obj/item/assembly - name = "assembly" - desc = "A small electronic device that should never exist." - icon = 'icons/obj/assemblies/new_assemblies.dmi' - icon_state = "" - flags_1 = CONDUCT_1 - w_class = WEIGHT_CLASS_SMALL - custom_materials = list(/datum/material/iron=100) - throwforce = 2 - throw_speed = 3 - throw_range = 7 - drop_sound = 'sound/items/handling/component_drop.ogg' - pickup_sound = 'sound/items/handling/component_pickup.ogg' - var/is_position_sensitive = FALSE //set to true if the device has different icons for each position. - //This will prevent things such as visible lasers from facing the incorrect direction when transformed by assembly_holder's update_icon() - var/secured = TRUE - var/list/attached_overlays = null - var/obj/item/assembly_holder/holder = null - var/wire_type = WIRE_RECEIVE | WIRE_PULSE - var/attachable = FALSE // can this be attached to wires - var/datum/wires/connected = null - var/next_activate = 0 //When we're next allowed to activate - for spam control - -/obj/item/assembly/get_part_rating() - return 1 - -/obj/item/assembly/proc/on_attach() - -//Call this when detaching it from a device. handles any special functions that need to be updated ex post facto -/obj/item/assembly/proc/on_detach() - if(!holder) - return FALSE - forceMove(holder.drop_location()) - holder = null - return TRUE - -//Called when the holder is moved -/obj/item/assembly/proc/holder_movement() - if(!holder) - return FALSE - setDir(holder.dir) - return TRUE - -/obj/item/assembly/proc/is_secured(mob/user) - if(!secured) - to_chat(user, "The [name] is unsecured!") - return FALSE - return TRUE - -//Called when another assembly acts on this one, var/radio will determine where it came from for wire calcs -/obj/item/assembly/proc/pulsed(radio = FALSE) - if(wire_type & WIRE_RECEIVE) - INVOKE_ASYNC(src, .proc/activate) - if(radio && (wire_type & WIRE_RADIO_RECEIVE)) - INVOKE_ASYNC(src, .proc/activate) - return TRUE - -//Called when this device attempts to act on another device, var/radio determines if it was sent via radio or direct -/obj/item/assembly/proc/pulse(radio = FALSE) - if(connected && wire_type) - connected.pulse_assembly(src) - return TRUE - if(holder && (wire_type & WIRE_PULSE)) - holder.process_activation(src, 1, 0) - if(holder && (wire_type & WIRE_PULSE_SPECIAL)) - holder.process_activation(src, 0, 1) - return TRUE - -// What the device does when turned on -/obj/item/assembly/proc/activate() - if(QDELETED(src) || !secured || (next_activate > world.time)) - return FALSE - next_activate = world.time + 30 - return TRUE - -/obj/item/assembly/proc/toggle_secure() - secured = !secured - update_icon() - return secured - -/obj/item/assembly/attackby(obj/item/W, mob/user, params) - if(isassembly(W)) - var/obj/item/assembly/A = W - if((!A.secured) && (!secured)) - holder = new/obj/item/assembly_holder(get_turf(src)) - holder.assemble(src,A,user) - to_chat(user, "You attach and secure \the [A] to \the [src]!") - else - to_chat(user, "Both devices must be in attachable mode to be attached together.") - return - ..() - -/obj/item/assembly/screwdriver_act(mob/living/user, obj/item/I) - if(..()) - return TRUE - if(toggle_secure()) - to_chat(user, "\The [src] is ready!") - else - to_chat(user, "\The [src] can now be attached!") - add_fingerprint(user) - return TRUE - -/obj/item/assembly/examine(mob/user) - . = ..() - . += "\The [src] [secured? "is secured and ready to be used!" : "can be attached to other things."]" - -/obj/item/assembly/attack_self(mob/user) - if(!user) - return FALSE - user.set_machine(src) - interact(user) - return TRUE - -/obj/item/assembly/interact(mob/user) - return ui_interact(user) - -/obj/item/assembly/ui_host(mob/user) - if(holder) - return holder - return src +#define WIRE_RECEIVE (1<<0) +#define WIRE_PULSE (1<<1) +#define WIRE_PULSE_SPECIAL (1<<2) +#define WIRE_RADIO_RECEIVE (1<<3) +#define WIRE_RADIO_PULSE (1<<4) +#define ASSEMBLY_BEEP_VOLUME 5 + +/obj/item/assembly + name = "assembly" + desc = "A small electronic device that should never exist." + icon = 'icons/obj/assemblies/new_assemblies.dmi' + icon_state = "" + flags_1 = CONDUCT_1 + w_class = WEIGHT_CLASS_SMALL + custom_materials = list(/datum/material/iron=100) + throwforce = 2 + throw_speed = 3 + throw_range = 7 + drop_sound = 'sound/items/handling/component_drop.ogg' + pickup_sound = 'sound/items/handling/component_pickup.ogg' + var/is_position_sensitive = FALSE //set to true if the device has different icons for each position. + //This will prevent things such as visible lasers from facing the incorrect direction when transformed by assembly_holder's update_icon() + var/secured = TRUE + var/list/attached_overlays = null + var/obj/item/assembly_holder/holder = null + var/wire_type = WIRE_RECEIVE | WIRE_PULSE + var/attachable = FALSE // can this be attached to wires + var/datum/wires/connected = null + var/next_activate = 0 //When we're next allowed to activate - for spam control + +/obj/item/assembly/get_part_rating() + return 1 + +/obj/item/assembly/proc/on_attach() + +//Call this when detaching it from a device. handles any special functions that need to be updated ex post facto +/obj/item/assembly/proc/on_detach() + if(!holder) + return FALSE + forceMove(holder.drop_location()) + holder = null + return TRUE + +//Called when the holder is moved +/obj/item/assembly/proc/holder_movement() + if(!holder) + return FALSE + setDir(holder.dir) + return TRUE + +/obj/item/assembly/proc/is_secured(mob/user) + if(!secured) + to_chat(user, "The [name] is unsecured!") + return FALSE + return TRUE + +//Called when another assembly acts on this one, var/radio will determine where it came from for wire calcs +/obj/item/assembly/proc/pulsed(radio = FALSE) + if(wire_type & WIRE_RECEIVE) + INVOKE_ASYNC(src, .proc/activate) + if(radio && (wire_type & WIRE_RADIO_RECEIVE)) + INVOKE_ASYNC(src, .proc/activate) + return TRUE + +//Called when this device attempts to act on another device, var/radio determines if it was sent via radio or direct +/obj/item/assembly/proc/pulse(radio = FALSE) + if(connected && wire_type) + connected.pulse_assembly(src) + return TRUE + if(holder && (wire_type & WIRE_PULSE)) + holder.process_activation(src, 1, 0) + if(holder && (wire_type & WIRE_PULSE_SPECIAL)) + holder.process_activation(src, 0, 1) + return TRUE + +// What the device does when turned on +/obj/item/assembly/proc/activate() + if(QDELETED(src) || !secured || (next_activate > world.time)) + return FALSE + next_activate = world.time + 30 + return TRUE + +/obj/item/assembly/proc/toggle_secure() + secured = !secured + update_icon() + return secured + +/obj/item/assembly/attackby(obj/item/W, mob/user, params) + if(isassembly(W)) + var/obj/item/assembly/A = W + if((!A.secured) && (!secured)) + holder = new/obj/item/assembly_holder(get_turf(src)) + holder.assemble(src,A,user) + to_chat(user, "You attach and secure \the [A] to \the [src]!") + else + to_chat(user, "Both devices must be in attachable mode to be attached together.") + return + ..() + +/obj/item/assembly/screwdriver_act(mob/living/user, obj/item/I) + if(..()) + return TRUE + if(toggle_secure()) + to_chat(user, "\The [src] is ready!") + else + to_chat(user, "\The [src] can now be attached!") + add_fingerprint(user) + return TRUE + +/obj/item/assembly/examine(mob/user) + . = ..() + . += "\The [src] [secured? "is secured and ready to be used!" : "can be attached to other things."]" + +/obj/item/assembly/attack_self(mob/user) + if(!user) + return FALSE + user.set_machine(src) + interact(user) + return TRUE + +/obj/item/assembly/interact(mob/user) + return ui_interact(user) + +/obj/item/assembly/ui_host(mob/user) + if(holder) + return holder + return src + +/obj/item/assembly/ui_state(mob/user) + return GLOB.hands_state diff --git a/code/modules/assembly/flash.dm b/code/modules/assembly/flash.dm index b80f71fc6468..b665e8231d7e 100644 --- a/code/modules/assembly/flash.dm +++ b/code/modules/assembly/flash.dm @@ -1,298 +1,298 @@ -#define CONFUSION_STACK_MAX_MULTIPLIER 2 -/obj/item/assembly/flash - name = "flash" - desc = "A powerful and versatile flashbulb device, with applications ranging from disorienting attackers to acting as visual receptors in robot production." - icon_state = "flash" - item_state = "flashtool" - lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' - throwforce = 0 - w_class = WEIGHT_CLASS_TINY - custom_materials = list(/datum/material/iron = 300, /datum/material/glass = 300) - light_color = LIGHT_COLOR_WHITE - light_power = FLASH_LIGHT_POWER - var/flashing_overlay = "flash-f" - var/times_used = 0 //Number of times it's been used. - var/burnt_out = FALSE //Is the flash burnt out? - var/burnout_resistance = 0 - var/last_used = 0 //last world.time it was used. - var/cooldown = 0 - var/last_trigger = 0 //Last time it was successfully triggered. - -/obj/item/assembly/flash/suicide_act(mob/living/user) - if(burnt_out) - user.visible_message("[user] raises \the [src] up to [user.p_their()] eyes and activates it ... but it's burnt out!") - return SHAME - else if(user.is_blind()) - user.visible_message("[user] raises \the [src] up to [user.p_their()] eyes and activates it ... but [user.p_theyre()] blind!") - return SHAME - user.visible_message("[user] raises \the [src] up to [user.p_their()] eyes and activates it! It looks like [user.p_theyre()] trying to commit suicide!") - attack(user,user) - return FIRELOSS - -/obj/item/assembly/flash/update_icon(flash = FALSE) - cut_overlays() - attached_overlays = list() - if(burnt_out) - add_overlay("flashburnt") - attached_overlays += "flashburnt" - if(flash) - add_overlay(flashing_overlay) - attached_overlays += flashing_overlay - addtimer(CALLBACK(src, /atom/.proc/update_icon), 5) - if(holder) - holder.update_icon() - -/obj/item/assembly/flash/proc/clown_check(mob/living/carbon/human/user) - if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50)) - flash_carbon(user, user, 15, 0) - return FALSE - return TRUE - -/obj/item/assembly/flash/proc/burn_out() //Made so you can override it if you want to have an invincible flash from R&D or something. - if(!burnt_out) - burnt_out = TRUE - update_icon() - if(ismob(loc)) - var/mob/M = loc - M.visible_message("[src] burns out!","[src] burns out!") - else - var/turf/T = get_turf(src) - T.visible_message("[src] burns out!") - -/obj/item/assembly/flash/proc/flash_recharge(interval = 10) - var/deciseconds_passed = world.time - last_used - for(var/seconds = deciseconds_passed / 10, seconds >= interval, seconds -= interval) //get 1 charge every interval - times_used-- - last_used = world.time - times_used = max(0, times_used) //sanity - if(max(0, prob(times_used * 3) - burnout_resistance)) //The more often it's used in a short span of time the more likely it will burn out - burn_out() - return FALSE - return TRUE - -//BYPASS CHECKS ALSO PREVENTS BURNOUT! -/obj/item/assembly/flash/proc/AOE_flash(bypass_checks = FALSE, range = 2, power = 3, targeted = FALSE, mob/user) - if(!bypass_checks && !try_use_flash()) - return FALSE - var/list/mob/targets = get_flash_targets(get_turf(src), range, FALSE) - if(user) - targets -= user - for(var/mob/living/carbon/C in targets) - flash_carbon(C, user, power, targeted, TRUE) - return TRUE - -/obj/item/assembly/flash/proc/get_flash_targets(atom/target_loc, range = 3, override_vision_checks = FALSE) - if(!target_loc) - target_loc = loc - if(override_vision_checks) - return get_hearers_in_view(range, get_turf(target_loc)) - if(isturf(target_loc) || (ismob(target_loc) && isturf(target_loc.loc))) - return viewers(range, get_turf(target_loc)) - else - return typecache_filter_list(target_loc.GetAllContents(), GLOB.typecache_living) - -/obj/item/assembly/flash/proc/try_use_flash(mob/user = null) - if(burnt_out || (world.time < last_trigger + cooldown)) - return FALSE - last_trigger = world.time - playsound(src, 'sound/weapons/flash.ogg', 100, TRUE) - flash_lighting_fx(FLASH_LIGHT_RANGE, light_power, light_color) - times_used++ - flash_recharge() - update_icon(TRUE) - if(user && !clown_check(user)) - return FALSE - return TRUE - -/obj/item/assembly/flash/proc/flash_carbon(mob/living/carbon/M, mob/user, power = 15, targeted = TRUE, generic_message = FALSE) - if(!istype(M)) - return - if(user) - log_combat(user, M, "[targeted? "flashed(targeted)" : "flashed(AOE)"]", src) - else //caused by emp/remote signal - M.log_message("was [targeted? "flashed(targeted)" : "flashed(AOE)"]",LOG_ATTACK) - if(generic_message && M != user) - to_chat(M, "[src] emits a blinding light!") - if(targeted) - if(M.flash_act(1, 1)) - if(M.confused < power) - var/diff = power * CONFUSION_STACK_MAX_MULTIPLIER - M.confused - M.confused += min(power, diff) - if(user) - terrible_conversion_proc(M, user) - visible_message("[user] blinds [M] with the flash!") - to_chat(user, "You blind [M] with the flash!") - to_chat(M, "[user] blinds you with the flash!") - else - to_chat(M, "You are blinded by [src]!") - else if(user) - visible_message("[user] fails to blind [M] with the flash!") - to_chat(user, "You fail to blind [M] with the flash!") - to_chat(M, "[user] fails to blind you with the flash!") - else - to_chat(M, "[src] fails to blind you!") - else - if(M.flash_act()) - var/diff = power * CONFUSION_STACK_MAX_MULTIPLIER - M.confused - M.confused += min(power, diff) - -/obj/item/assembly/flash/attack(mob/living/M, mob/user) - if(!try_use_flash(user)) - return FALSE - if(iscarbon(M)) - flash_carbon(M, user, 5, 1) - return TRUE - else if(issilicon(M)) - var/mob/living/silicon/robot/R = M - log_combat(user, R, "flashed", src) - update_icon(1) - R.Paralyze(rand(80,120)) - var/diff = 5 * CONFUSION_STACK_MAX_MULTIPLIER - M.confused - R.confused += min(5, diff) - R.flash_act(affect_silicon = 1) - user.visible_message("[user] overloads [R]'s sensors with the flash!", "You overload [R]'s sensors with the flash!") - return TRUE - - user.visible_message("[user] fails to blind [M] with the flash!", "You fail to blind [M] with the flash!") - -/obj/item/assembly/flash/attack_self(mob/living/carbon/user, flag = 0, emp = 0) - if(holder) - return FALSE - if(!AOE_flash(FALSE, 3, 5, FALSE, user)) - return FALSE - to_chat(user, "[src] emits a blinding light!") - -/obj/item/assembly/flash/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - if(!try_use_flash()) - return - AOE_flash() - burn_out() - -/obj/item/assembly/flash/activate()//AOE flash on signal received - if(!..()) - return - AOE_flash() - -/obj/item/assembly/flash/proc/terrible_conversion_proc(mob/living/carbon/H, mob/user) - if(istype(H) && H.stat != DEAD) - if(user.mind) - var/datum/antagonist/rev/head/converter = user.mind.has_antag_datum(/datum/antagonist/rev/head) - if(!converter) - return - if(!H.client) - to_chat(user, "This mind is so vacant that it is not susceptible to influence!") - return - if(H.stat != CONSCIOUS) - to_chat(user, "They must be conscious before you can convert [H.p_them()]!") - return - if(converter.add_revolutionary(H.mind)) - if(prob(1) || SSevents.holidays && SSevents.holidays[APRIL_FOOLS]) - H.say("You son of a bitch! I'm in.", forced = "That son of a bitch! They're in.") - times_used -- //Flashes less likely to burn out for headrevs when used for conversion - else - to_chat(user, "This mind seems resistant to the flash!") - - -/obj/item/assembly/flash/cyborg - -/obj/item/assembly/flash/cyborg/attack(mob/living/M, mob/user) - ..() - new /obj/effect/temp_visual/borgflash(get_turf(src)) - -/obj/item/assembly/flash/cyborg/attack_self(mob/user) - ..() - new /obj/effect/temp_visual/borgflash(get_turf(src)) - -/obj/item/assembly/flash/cyborg/attackby(obj/item/W, mob/user, params) - return -/obj/item/assembly/flash/cyborg/screwdriver_act(mob/living/user, obj/item/I) - return - -/obj/item/assembly/flash/memorizer - name = "memorizer" - desc = "If you see this, you're not likely to remember it any time soon." - icon = 'icons/obj/device.dmi' - icon_state = "memorizer" - item_state = "nullrod" - -/obj/item/assembly/flash/handheld //this is now the regular pocket flashes - -/obj/item/assembly/flash/armimplant - name = "photon projector" - desc = "A high-powered photon projector implant normally used for lighting purposes, but also doubles as a flashbulb weapon. Self-repair protocols fix the flashbulb if it ever burns out." - var/flashcd = 20 - var/overheat = 0 - var/obj/item/organ/cyberimp/arm/flash/I = null - -/obj/item/assembly/flash/armimplant/burn_out() - if(I && I.owner) - to_chat(I.owner, "Your photon projector implant overheats and deactivates!") - I.Retract() - overheat = TRUE - addtimer(CALLBACK(src, .proc/cooldown), flashcd * 2) - -/obj/item/assembly/flash/armimplant/try_use_flash(mob/user = null) - if(overheat) - if(I && I.owner) - to_chat(I.owner, "Your photon projector is running too hot to be used again so quickly!") - return FALSE - overheat = TRUE - addtimer(CALLBACK(src, .proc/cooldown), flashcd) - playsound(src, 'sound/weapons/flash.ogg', 100, TRUE) - update_icon(1) - return TRUE - - -/obj/item/assembly/flash/armimplant/proc/cooldown() - overheat = FALSE - -/obj/item/assembly/flash/hypnotic - desc = "A modified flash device, programmed to emit a sequence of subliminal flashes that can send a vulnerable target into a hypnotic trance." - flashing_overlay = "flash-hypno" - light_color = LIGHT_COLOR_PINK - cooldown = 20 - -/obj/item/assembly/flash/hypnotic/burn_out() - return - -/obj/item/assembly/flash/hypnotic/flash_carbon(mob/living/carbon/M, mob/user, power = 15, targeted = TRUE, generic_message = FALSE) - if(!istype(M)) - return - if(user) - log_combat(user, M, "[targeted? "hypno-flashed(targeted)" : "hypno-flashed(AOE)"]", src) - else //caused by emp/remote signal - M.log_message("was [targeted? "hypno-flashed(targeted)" : "hypno-flashed(AOE)"]",LOG_ATTACK) - if(generic_message && M != user) - to_chat(M, "[src] emits a soothing light...") - if(targeted) - if(M.flash_act(1, 1)) - var/hypnosis = FALSE - if(M.hypnosis_vulnerable()) - hypnosis = TRUE - if(user) - user.visible_message("[user] blinds [M] with the flash!", "You hypno-flash [M]!") - - if(!hypnosis) - to_chat(M, "The light makes you feel oddly relaxed...") - M.confused += min(M.confused + 10, 20) - M.dizziness += min(M.dizziness + 10, 20) - M.drowsyness += min(M.drowsyness + 10, 20) - M.apply_status_effect(STATUS_EFFECT_PACIFY, 100) - else - M.apply_status_effect(/datum/status_effect/trance, 200, TRUE) - - else if(user) - user.visible_message("[user] fails to blind [M] with the flash!", "You fail to hypno-flash [M]!") - else - to_chat(M, "[src] fails to blind you!") - - else if(M.flash_act()) - to_chat(M, "Such a pretty light...") - M.confused += min(M.confused + 4, 20) - M.dizziness += min(M.dizziness + 4, 20) - M.drowsyness += min(M.drowsyness + 4, 20) - M.apply_status_effect(STATUS_EFFECT_PACIFY, 40) +#define CONFUSION_STACK_MAX_MULTIPLIER 2 +/obj/item/assembly/flash + name = "flash" + desc = "A powerful and versatile flashbulb device, with applications ranging from disorienting attackers to acting as visual receptors in robot production." + icon_state = "flash" + item_state = "flashtool" + lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' + throwforce = 0 + w_class = WEIGHT_CLASS_TINY + custom_materials = list(/datum/material/iron = 300, /datum/material/glass = 300) + light_color = LIGHT_COLOR_WHITE + light_power = FLASH_LIGHT_POWER + var/flashing_overlay = "flash-f" + var/times_used = 0 //Number of times it's been used. + var/burnt_out = FALSE //Is the flash burnt out? + var/burnout_resistance = 0 + var/last_used = 0 //last world.time it was used. + var/cooldown = 0 + var/last_trigger = 0 //Last time it was successfully triggered. + +/obj/item/assembly/flash/suicide_act(mob/living/user) + if(burnt_out) + user.visible_message("[user] raises \the [src] up to [user.p_their()] eyes and activates it ... but it's burnt out!") + return SHAME + else if(user.is_blind()) + user.visible_message("[user] raises \the [src] up to [user.p_their()] eyes and activates it ... but [user.p_theyre()] blind!") + return SHAME + user.visible_message("[user] raises \the [src] up to [user.p_their()] eyes and activates it! It looks like [user.p_theyre()] trying to commit suicide!") + attack(user,user) + return FIRELOSS + +/obj/item/assembly/flash/update_icon(flash = FALSE) + cut_overlays() + attached_overlays = list() + if(burnt_out) + add_overlay("flashburnt") + attached_overlays += "flashburnt" + if(flash) + add_overlay(flashing_overlay) + attached_overlays += flashing_overlay + addtimer(CALLBACK(src, /atom/.proc/update_icon), 5) + if(holder) + holder.update_icon() + +/obj/item/assembly/flash/proc/clown_check(mob/living/carbon/human/user) + if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50)) + flash_carbon(user, user, 15, 0) + return FALSE + return TRUE + +/obj/item/assembly/flash/proc/burn_out() //Made so you can override it if you want to have an invincible flash from R&D or something. + if(!burnt_out) + burnt_out = TRUE + update_icon() + if(ismob(loc)) + var/mob/M = loc + M.visible_message("[src] burns out!","[src] burns out!") + else + var/turf/T = get_turf(src) + T.visible_message("[src] burns out!") + +/obj/item/assembly/flash/proc/flash_recharge(interval = 10) + var/deciseconds_passed = world.time - last_used + for(var/seconds = deciseconds_passed / 10, seconds >= interval, seconds -= interval) //get 1 charge every interval + times_used-- + last_used = world.time + times_used = max(0, times_used) //sanity + if(max(0, prob(times_used * 3) - burnout_resistance)) //The more often it's used in a short span of time the more likely it will burn out + burn_out() + return FALSE + return TRUE + +//BYPASS CHECKS ALSO PREVENTS BURNOUT! +/obj/item/assembly/flash/proc/AOE_flash(bypass_checks = FALSE, range = 2, power = 3, targeted = FALSE, mob/user) + if(!bypass_checks && !try_use_flash()) + return FALSE + var/list/mob/targets = get_flash_targets(get_turf(src), range, FALSE) + if(user) + targets -= user + for(var/mob/living/carbon/C in targets) + flash_carbon(C, user, power, targeted, TRUE) + return TRUE + +/obj/item/assembly/flash/proc/get_flash_targets(atom/target_loc, range = 3, override_vision_checks = FALSE) + if(!target_loc) + target_loc = loc + if(override_vision_checks) + return get_hearers_in_view(range, get_turf(target_loc)) + if(isturf(target_loc) || (ismob(target_loc) && isturf(target_loc.loc))) + return viewers(range, get_turf(target_loc)) + else + return typecache_filter_list(target_loc.GetAllContents(), GLOB.typecache_living) + +/obj/item/assembly/flash/proc/try_use_flash(mob/user = null) + if(burnt_out || (world.time < last_trigger + cooldown)) + return FALSE + last_trigger = world.time + playsound(src, 'sound/weapons/flash.ogg', 100, TRUE) + flash_lighting_fx(FLASH_LIGHT_RANGE, light_power, light_color) + times_used++ + flash_recharge() + update_icon(TRUE) + if(user && !clown_check(user)) + return FALSE + return TRUE + +/obj/item/assembly/flash/proc/flash_carbon(mob/living/carbon/M, mob/user, power = 15, targeted = TRUE, generic_message = FALSE) + if(!istype(M)) + return + if(user) + log_combat(user, M, "[targeted? "flashed(targeted)" : "flashed(AOE)"]", src) + else //caused by emp/remote signal + M.log_message("was [targeted? "flashed(targeted)" : "flashed(AOE)"]",LOG_ATTACK) + if(generic_message && M != user) + to_chat(M, "[src] emits a blinding light!") + if(targeted) + if(M.flash_act(1, 1)) + if(M.confused < power) + var/diff = power * CONFUSION_STACK_MAX_MULTIPLIER - M.confused + M.confused += min(power, diff) + if(user) + terrible_conversion_proc(M, user) + visible_message("[user] blinds [M] with the flash!") + to_chat(user, "You blind [M] with the flash!") + to_chat(M, "[user] blinds you with the flash!") + else + to_chat(M, "You are blinded by [src]!") + else if(user) + visible_message("[user] fails to blind [M] with the flash!") + to_chat(user, "You fail to blind [M] with the flash!") + to_chat(M, "[user] fails to blind you with the flash!") + else + to_chat(M, "[src] fails to blind you!") + else + if(M.flash_act()) + var/diff = power * CONFUSION_STACK_MAX_MULTIPLIER - M.confused + M.confused += min(power, diff) + +/obj/item/assembly/flash/attack(mob/living/M, mob/user) + if(!try_use_flash(user)) + return FALSE + if(iscarbon(M)) + flash_carbon(M, user, 5, 1) + return TRUE + else if(issilicon(M)) + var/mob/living/silicon/robot/R = M + log_combat(user, R, "flashed", src) + update_icon(1) + R.Paralyze(rand(80,120)) + var/diff = 5 * CONFUSION_STACK_MAX_MULTIPLIER - M.confused + R.confused += min(5, diff) + R.flash_act(affect_silicon = 1) + user.visible_message("[user] overloads [R]'s sensors with the flash!", "You overload [R]'s sensors with the flash!") + return TRUE + + user.visible_message("[user] fails to blind [M] with the flash!", "You fail to blind [M] with the flash!") + +/obj/item/assembly/flash/attack_self(mob/living/carbon/user, flag = 0, emp = 0) + if(holder) + return FALSE + if(!AOE_flash(FALSE, 3, 5, FALSE, user)) + return FALSE + to_chat(user, "[src] emits a blinding light!") + +/obj/item/assembly/flash/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + if(!try_use_flash()) + return + AOE_flash() + burn_out() + +/obj/item/assembly/flash/activate()//AOE flash on signal received + if(!..()) + return + AOE_flash() + +/obj/item/assembly/flash/proc/terrible_conversion_proc(mob/living/carbon/H, mob/user) + if(istype(H) && H.stat != DEAD) + if(user.mind) + var/datum/antagonist/rev/head/converter = user.mind.has_antag_datum(/datum/antagonist/rev/head) + if(!converter) + return + if(!H.client) + to_chat(user, "This mind is so vacant that it is not susceptible to influence!") + return + if(H.stat != CONSCIOUS) + to_chat(user, "They must be conscious before you can convert [H.p_them()]!") + return + if(converter.add_revolutionary(H.mind)) + if(prob(1) || SSevents.holidays && SSevents.holidays[APRIL_FOOLS]) + H.say("You son of a bitch! I'm in.", forced = "That son of a bitch! They're in.") + times_used -- //Flashes less likely to burn out for headrevs when used for conversion + else + to_chat(user, "This mind seems resistant to the flash!") + + +/obj/item/assembly/flash/cyborg + +/obj/item/assembly/flash/cyborg/attack(mob/living/M, mob/user) + ..() + new /obj/effect/temp_visual/borgflash(get_turf(src)) + +/obj/item/assembly/flash/cyborg/attack_self(mob/user) + ..() + new /obj/effect/temp_visual/borgflash(get_turf(src)) + +/obj/item/assembly/flash/cyborg/attackby(obj/item/W, mob/user, params) + return +/obj/item/assembly/flash/cyborg/screwdriver_act(mob/living/user, obj/item/I) + return + +/obj/item/assembly/flash/memorizer + name = "memorizer" + desc = "If you see this, you're not likely to remember it any time soon." + icon = 'icons/obj/device.dmi' + icon_state = "memorizer" + item_state = "nullrod" + +/obj/item/assembly/flash/handheld //this is now the regular pocket flashes + +/obj/item/assembly/flash/armimplant + name = "photon projector" + desc = "A high-powered photon projector implant normally used for lighting purposes, but also doubles as a flashbulb weapon. Self-repair protocols fix the flashbulb if it ever burns out." + var/flashcd = 20 + var/overheat = 0 + var/obj/item/organ/cyberimp/arm/flash/I = null + +/obj/item/assembly/flash/armimplant/burn_out() + if(I && I.owner) + to_chat(I.owner, "Your photon projector implant overheats and deactivates!") + I.Retract() + overheat = TRUE + addtimer(CALLBACK(src, .proc/cooldown), flashcd * 2) + +/obj/item/assembly/flash/armimplant/try_use_flash(mob/user = null) + if(overheat) + if(I && I.owner) + to_chat(I.owner, "Your photon projector is running too hot to be used again so quickly!") + return FALSE + overheat = TRUE + addtimer(CALLBACK(src, .proc/cooldown), flashcd) + playsound(src, 'sound/weapons/flash.ogg', 100, TRUE) + update_icon(1) + return TRUE + + +/obj/item/assembly/flash/armimplant/proc/cooldown() + overheat = FALSE + +/obj/item/assembly/flash/hypnotic + desc = "A modified flash device, programmed to emit a sequence of subliminal flashes that can send a vulnerable target into a hypnotic trance." + flashing_overlay = "flash-hypno" + light_color = LIGHT_COLOR_PINK + cooldown = 20 + +/obj/item/assembly/flash/hypnotic/burn_out() + return + +/obj/item/assembly/flash/hypnotic/flash_carbon(mob/living/carbon/M, mob/user, power = 15, targeted = TRUE, generic_message = FALSE) + if(!istype(M)) + return + if(user) + log_combat(user, M, "[targeted? "hypno-flashed(targeted)" : "hypno-flashed(AOE)"]", src) + else //caused by emp/remote signal + M.log_message("was [targeted? "hypno-flashed(targeted)" : "hypno-flashed(AOE)"]",LOG_ATTACK) + if(generic_message && M != user) + to_chat(M, "[src] emits a soothing light...") + if(targeted) + if(M.flash_act(1, 1)) + var/hypnosis = FALSE + if(M.hypnosis_vulnerable()) + hypnosis = TRUE + if(user) + user.visible_message("[user] blinds [M] with the flash!", "You hypno-flash [M]!") + + if(!hypnosis) + to_chat(M, "The light makes you feel oddly relaxed...") + M.confused += min(M.confused + 10, 20) + M.dizziness += min(M.dizziness + 10, 20) + M.drowsyness += min(M.drowsyness + 10, 20) + M.apply_status_effect(STATUS_EFFECT_PACIFY, 100) + else + M.apply_status_effect(/datum/status_effect/trance, 200, TRUE) + + else if(user) + user.visible_message("[user] fails to blind [M] with the flash!", "You fail to hypno-flash [M]!") + else + to_chat(M, "[src] fails to blind you!") + + else if(M.flash_act()) + to_chat(M, "Such a pretty light...") + M.confused += min(M.confused + 4, 20) + M.dizziness += min(M.dizziness + 4, 20) + M.drowsyness += min(M.drowsyness + 4, 20) + M.apply_status_effect(STATUS_EFFECT_PACIFY, 40) diff --git a/code/modules/assembly/helpers.dm b/code/modules/assembly/helpers.dm index f1dc93b4465c..2c39751a8bcd 100644 --- a/code/modules/assembly/helpers.dm +++ b/code/modules/assembly/helpers.dm @@ -1,16 +1,16 @@ -// See _DEFINES/is_helpers.dm for type helpers - -/* -Name: IsSpecialAssembly -Desc: If true is an object that can be attached to an assembly holder but is a special thing like a plasma can or door -*/ - -/obj/proc/IsSpecialAssembly() - return FALSE - -/* -Name: IsAssemblyHolder -Desc: If true is an object that can hold an assemblyholder object -*/ -/obj/proc/IsAssemblyHolder() - return FALSE +// See _DEFINES/is_helpers.dm for type helpers + +/* +Name: IsSpecialAssembly +Desc: If true is an object that can be attached to an assembly holder but is a special thing like a plasma can or door +*/ + +/obj/proc/IsSpecialAssembly() + return FALSE + +/* +Name: IsAssemblyHolder +Desc: If true is an object that can hold an assemblyholder object +*/ +/obj/proc/IsAssemblyHolder() + return FALSE diff --git a/code/modules/assembly/holder.dm b/code/modules/assembly/holder.dm index 34d193bc198c..24767694044b 100644 --- a/code/modules/assembly/holder.dm +++ b/code/modules/assembly/holder.dm @@ -1,145 +1,145 @@ -/obj/item/assembly_holder - name = "Assembly" - icon = 'icons/obj/assemblies/new_assemblies.dmi' - icon_state = "holder" - item_state = "assembly" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - flags_1 = CONDUCT_1 - throwforce = 5 - w_class = WEIGHT_CLASS_SMALL - throw_speed = 2 - throw_range = 7 - - var/obj/item/assembly/a_left = null - var/obj/item/assembly/a_right = null - -/obj/item/assembly_holder/ComponentInitialize() - . = ..() - var/static/rotation_flags = ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_FLIP | ROTATION_VERBS - AddComponent(/datum/component/simple_rotation, rotation_flags) - -/obj/item/assembly_holder/IsAssemblyHolder() - return TRUE - - -/obj/item/assembly_holder/proc/assemble(obj/item/assembly/A, obj/item/assembly/A2, mob/user) - attach(A,user) - attach(A2,user) - name = "[A.name]-[A2.name] assembly" - update_icon() - SSblackbox.record_feedback("tally", "assembly_made", 1, "[initial(A.name)]-[initial(A2.name)]") - -/obj/item/assembly_holder/proc/attach(obj/item/assembly/A, mob/user) - if(!A.remove_item_from_storage(src)) - if(user) - user.transferItemToLoc(A, src) - else - A.forceMove(src) - A.holder = src - A.toggle_secure() - if(!a_left) - a_left = A - else - a_right = A - A.holder_movement() - -/obj/item/assembly_holder/update_icon() - cut_overlays() - if(a_left) - add_overlay("[a_left.icon_state]_left") - for(var/O in a_left.attached_overlays) - add_overlay("[O]_l") - - if(a_right) - if(a_right.is_position_sensitive) - add_overlay("[a_right.icon_state]_right") - for(var/O in a_right.attached_overlays) - add_overlay("[O]_r") - else - var/mutable_appearance/right = mutable_appearance(icon, "[a_right.icon_state]_left") - right.transform = matrix(-1, 0, 0, 0, 1, 0) - for(var/O in a_right.attached_overlays) - right.add_overlay("[O]_l") - add_overlay(right) - - if(master) - master.update_icon() - -/obj/item/assembly_holder/Crossed(atom/movable/AM as mob|obj) - . = ..() - if(a_left) - a_left.Crossed(AM) - if(a_right) - a_right.Crossed(AM) - -/obj/item/assembly_holder/on_found(mob/finder) - if(a_left) - a_left.on_found(finder) - if(a_right) - a_right.on_found(finder) - -/obj/item/assembly_holder/setDir() - . = ..() - if(a_left) - a_left.holder_movement() - if(a_right) - a_right.holder_movement() - -/obj/item/assembly_holder/dropped(mob/user) - . = ..() - if(a_left) - a_left.dropped() - if(a_right) - a_right.dropped() - -/obj/item/assembly_holder/attack_hand()//Perhapse this should be a holder_pickup proc instead, can add if needbe I guess - . = ..() - if(.) - return - if(a_left) - a_left.attack_hand() - if(a_right) - a_right.attack_hand() - -/obj/item/assembly_holder/screwdriver_act(mob/user, obj/item/tool) - if(..()) - return TRUE - to_chat(user, "You disassemble [src]!") - if(a_left) - a_left.on_detach() - a_left = null - if(a_right) - a_right.on_detach() - a_right = null - qdel(src) - return TRUE - -/obj/item/assembly_holder/attack_self(mob/user) - src.add_fingerprint(user) - if(!a_left || !a_right) - to_chat(user, "Assembly part missing!") - return - if(istype(a_left,a_right.type))//If they are the same type it causes issues due to window code - switch(alert("Which side would you like to use?",,"Left","Right")) - if("Left") - a_left.attack_self(user) - if("Right") - a_right.attack_self(user) - return - else - a_left.attack_self(user) - a_right.attack_self(user) - - -/obj/item/assembly_holder/proc/process_activation(obj/D, normal = 1, special = 1) - if(!D) - return FALSE - if((normal) && (a_right) && (a_left)) - if(a_right != D) - a_right.pulsed(FALSE) - if(a_left != D) - a_left.pulsed(FALSE) - if(master) - master.receive_signal() - return TRUE +/obj/item/assembly_holder + name = "Assembly" + icon = 'icons/obj/assemblies/new_assemblies.dmi' + icon_state = "holder" + item_state = "assembly" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + flags_1 = CONDUCT_1 + throwforce = 5 + w_class = WEIGHT_CLASS_SMALL + throw_speed = 2 + throw_range = 7 + + var/obj/item/assembly/a_left = null + var/obj/item/assembly/a_right = null + +/obj/item/assembly_holder/ComponentInitialize() + . = ..() + var/static/rotation_flags = ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_FLIP | ROTATION_VERBS + AddComponent(/datum/component/simple_rotation, rotation_flags) + +/obj/item/assembly_holder/IsAssemblyHolder() + return TRUE + + +/obj/item/assembly_holder/proc/assemble(obj/item/assembly/A, obj/item/assembly/A2, mob/user) + attach(A,user) + attach(A2,user) + name = "[A.name]-[A2.name] assembly" + update_icon() + SSblackbox.record_feedback("tally", "assembly_made", 1, "[initial(A.name)]-[initial(A2.name)]") + +/obj/item/assembly_holder/proc/attach(obj/item/assembly/A, mob/user) + if(!A.remove_item_from_storage(src)) + if(user) + user.transferItemToLoc(A, src) + else + A.forceMove(src) + A.holder = src + A.toggle_secure() + if(!a_left) + a_left = A + else + a_right = A + A.holder_movement() + +/obj/item/assembly_holder/update_icon() + cut_overlays() + if(a_left) + add_overlay("[a_left.icon_state]_left") + for(var/O in a_left.attached_overlays) + add_overlay("[O]_l") + + if(a_right) + if(a_right.is_position_sensitive) + add_overlay("[a_right.icon_state]_right") + for(var/O in a_right.attached_overlays) + add_overlay("[O]_r") + else + var/mutable_appearance/right = mutable_appearance(icon, "[a_right.icon_state]_left") + right.transform = matrix(-1, 0, 0, 0, 1, 0) + for(var/O in a_right.attached_overlays) + right.add_overlay("[O]_l") + add_overlay(right) + + if(master) + master.update_icon() + +/obj/item/assembly_holder/Crossed(atom/movable/AM as mob|obj) + . = ..() + if(a_left) + a_left.Crossed(AM) + if(a_right) + a_right.Crossed(AM) + +/obj/item/assembly_holder/on_found(mob/finder) + if(a_left) + a_left.on_found(finder) + if(a_right) + a_right.on_found(finder) + +/obj/item/assembly_holder/setDir() + . = ..() + if(a_left) + a_left.holder_movement() + if(a_right) + a_right.holder_movement() + +/obj/item/assembly_holder/dropped(mob/user) + . = ..() + if(a_left) + a_left.dropped() + if(a_right) + a_right.dropped() + +/obj/item/assembly_holder/attack_hand()//Perhapse this should be a holder_pickup proc instead, can add if needbe I guess + . = ..() + if(.) + return + if(a_left) + a_left.attack_hand() + if(a_right) + a_right.attack_hand() + +/obj/item/assembly_holder/screwdriver_act(mob/user, obj/item/tool) + if(..()) + return TRUE + to_chat(user, "You disassemble [src]!") + if(a_left) + a_left.on_detach() + a_left = null + if(a_right) + a_right.on_detach() + a_right = null + qdel(src) + return TRUE + +/obj/item/assembly_holder/attack_self(mob/user) + src.add_fingerprint(user) + if(!a_left || !a_right) + to_chat(user, "Assembly part missing!") + return + if(istype(a_left,a_right.type))//If they are the same type it causes issues due to window code + switch(alert("Which side would you like to use?",,"Left","Right")) + if("Left") + a_left.attack_self(user) + if("Right") + a_right.attack_self(user) + return + else + a_left.attack_self(user) + a_right.attack_self(user) + + +/obj/item/assembly_holder/proc/process_activation(obj/D, normal = 1, special = 1) + if(!D) + return FALSE + if((normal) && (a_right) && (a_left)) + if(a_right != D) + a_right.pulsed(FALSE) + if(a_left != D) + a_left.pulsed(FALSE) + if(master) + master.receive_signal() + return TRUE diff --git a/code/modules/assembly/igniter.dm b/code/modules/assembly/igniter.dm index 55f5d50f2e1f..ab27d99d7300 100644 --- a/code/modules/assembly/igniter.dm +++ b/code/modules/assembly/igniter.dm @@ -1,44 +1,44 @@ -/obj/item/assembly/igniter - name = "igniter" - desc = "A small electronic device able to ignite combustible substances." - icon_state = "igniter" - custom_materials = list(/datum/material/iron=500, /datum/material/glass=50) - var/datum/effect_system/spark_spread/sparks - heat = 1000 - drop_sound = 'sound/items/handling/component_drop.ogg' - pickup_sound = 'sound/items/handling/component_pickup.ogg' - -/obj/item/assembly/igniter/suicide_act(mob/living/carbon/user) - user.visible_message("[user] is trying to ignite [user.p_them()]self with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") - user.IgniteMob() - return FIRELOSS - -/obj/item/assembly/igniter/Initialize() - . = ..() - sparks = new - sparks.set_up(2, 0, src) - sparks.attach(src) - -/obj/item/assembly/igniter/Destroy() - if(sparks) - qdel(sparks) - sparks = null - . = ..() - -/obj/item/assembly/igniter/activate() - if(!..()) - return FALSE//Cooldown check - var/turf/location = get_turf(loc) - if(location) - location.hotspot_expose(1000,1000) - sparks.start() - return TRUE - -/obj/item/assembly/igniter/attack_self(mob/user) - activate() - add_fingerprint(user) - -/obj/item/assembly/igniter/ignition_effect(atom/A, mob/user) - . = "[user] fiddles with [src], and manages to light [A]." - activate() - add_fingerprint(user) +/obj/item/assembly/igniter + name = "igniter" + desc = "A small electronic device able to ignite combustible substances." + icon_state = "igniter" + custom_materials = list(/datum/material/iron=500, /datum/material/glass=50) + var/datum/effect_system/spark_spread/sparks + heat = 1000 + drop_sound = 'sound/items/handling/component_drop.ogg' + pickup_sound = 'sound/items/handling/component_pickup.ogg' + +/obj/item/assembly/igniter/suicide_act(mob/living/carbon/user) + user.visible_message("[user] is trying to ignite [user.p_them()]self with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") + user.IgniteMob() + return FIRELOSS + +/obj/item/assembly/igniter/Initialize() + . = ..() + sparks = new + sparks.set_up(2, 0, src) + sparks.attach(src) + +/obj/item/assembly/igniter/Destroy() + if(sparks) + qdel(sparks) + sparks = null + . = ..() + +/obj/item/assembly/igniter/activate() + if(!..()) + return FALSE//Cooldown check + var/turf/location = get_turf(loc) + if(location) + location.hotspot_expose(1000,1000) + sparks.start() + return TRUE + +/obj/item/assembly/igniter/attack_self(mob/user) + activate() + add_fingerprint(user) + +/obj/item/assembly/igniter/ignition_effect(atom/A, mob/user) + . = "[user] fiddles with [src], and manages to light [A]." + activate() + add_fingerprint(user) diff --git a/code/modules/assembly/infrared.dm b/code/modules/assembly/infrared.dm index ecf27d735026..92a292ffaa6a 100644 --- a/code/modules/assembly/infrared.dm +++ b/code/modules/assembly/infrared.dm @@ -1,238 +1,235 @@ -/obj/item/assembly/infra - name = "infrared emitter" - desc = "Emits a visible or invisible beam and is triggered when the beam is interrupted." - icon_state = "infrared" - custom_materials = list(/datum/material/iron=1000, /datum/material/glass=500) - is_position_sensitive = TRUE - drop_sound = 'sound/items/handling/component_drop.ogg' - pickup_sound = 'sound/items/handling/component_pickup.ogg' - var/ui_x = 225 - var/ui_y = 110 - var/on = FALSE - var/visible = FALSE - var/maxlength = 8 - var/list/obj/effect/beam/i_beam/beams - var/olddir = 0 - var/turf/listeningTo - var/hearing_range = 3 - -/obj/item/assembly/infra/Initialize() - . = ..() - beams = list() - START_PROCESSING(SSobj, src) - -/obj/item/assembly/infra/ComponentInitialize() - . = ..() - var/static/rotation_flags = ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_FLIP | ROTATION_VERBS - AddComponent(/datum/component/simple_rotation, rotation_flags, after_rotation=CALLBACK(src,.proc/after_rotation)) - -/obj/item/assembly/infra/proc/after_rotation() - refreshBeam() - -/obj/item/assembly/infra/Destroy() - STOP_PROCESSING(SSobj, src) - listeningTo = null - QDEL_LIST(beams) - . = ..() - -/obj/item/assembly/infra/examine(mob/user) - . = ..() - . += "The infrared trigger is [on?"on":"off"]." - -/obj/item/assembly/infra/activate() - if(!..()) - return FALSE //Cooldown check - on = !on - refreshBeam() - update_icon() - return TRUE - -/obj/item/assembly/infra/toggle_secure() - secured = !secured - if(secured) - START_PROCESSING(SSobj, src) - refreshBeam() - else - QDEL_LIST(beams) - STOP_PROCESSING(SSobj, src) - update_icon() - return secured - -/obj/item/assembly/infra/update_icon() - cut_overlays() - attached_overlays = list() - if(on) - add_overlay("infrared_on") - attached_overlays += "infrared_on" - if(visible && secured) - add_overlay("infrared_visible") - attached_overlays += "infrared_visible" - - if(holder) - holder.update_icon() - return - -/obj/item/assembly/infra/dropped() - . = ..() - if(holder) - holder_movement() //sync the dir of the device as well if it's contained in a TTV or an assembly holder - else - refreshBeam() - -/obj/item/assembly/infra/process() - if(!on || !secured) - refreshBeam() - return - -/obj/item/assembly/infra/proc/refreshBeam() - QDEL_LIST(beams) - if(throwing || !on || !secured) - return - if(holder) - if(holder.master) //incase the sensor is part of an assembly that's contained in another item, such as a single tank bomb - if(!holder.master.IsSpecialAssembly() || !isturf(holder.master.loc)) - return - else if(!isturf(holder.loc)) //else just check where the holder is - return - else if(!isturf(loc)) //or just where the fuck we are in general - return - var/turf/T = get_turf(src) - var/_dir = dir - var/turf/_T = get_step(T, _dir) - if(_T) - for(var/i in 1 to maxlength) - var/obj/effect/beam/i_beam/I = new(T) - if(istype(holder, /obj/item/assembly_holder)) - var/obj/item/assembly_holder/assembly_holder = holder - I.icon_state = "[initial(I.icon_state)]_[(assembly_holder.a_left == src) ? "l":"r"]" //Sync the offset of the beam with the position of the sensor. - else if(istype(holder, /obj/item/transfer_valve)) - I.icon_state = "[initial(I.icon_state)]_ttv" - I.density = TRUE - if(!I.Move(_T)) - qdel(I) - switchListener(_T) - break - I.density = FALSE - beams += I - I.master = src - I.setDir(_dir) - I.invisibility = visible? 0 : INVISIBILITY_ABSTRACT - T = _T - _T = get_step(_T, _dir) - CHECK_TICK - -/obj/item/assembly/infra/on_detach() - . = ..() - if(!.) - return - refreshBeam() - -/obj/item/assembly/infra/attack_hand() - . = ..() - refreshBeam() - -/obj/item/assembly/infra/Moved() - var/t = dir - . = ..() - setDir(t) - -/obj/item/assembly/infra/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, gentle = FALSE, quickstart = TRUE) - . = ..() - olddir = dir - -/obj/item/assembly/infra/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - . = ..() - if(!olddir) - return - setDir(olddir) - olddir = null - -/obj/item/assembly/infra/proc/trigger_beam(atom/movable/AM, turf/location) - refreshBeam() - switchListener(location) - if(!secured || !on || next_activate > world.time) - return FALSE - pulse(FALSE) - audible_message("[icon2html(src, hearers(src))] *beep* *beep* *beep*", null, hearing_range) - for(var/CHM in get_hearers_in_view(hearing_range, src)) - if(ismob(CHM)) - var/mob/LM = CHM - LM.playsound_local(get_turf(src), 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE) - next_activate = world.time + 30 - -/obj/item/assembly/infra/proc/switchListener(turf/newloc) - if(listeningTo == newloc) - return - if(listeningTo) - UnregisterSignal(listeningTo, COMSIG_ATOM_EXITED) - RegisterSignal(newloc, COMSIG_ATOM_EXITED, .proc/check_exit) - listeningTo = newloc - -/obj/item/assembly/infra/proc/check_exit(datum/source, atom/movable/offender) - if(QDELETED(src)) - return - if(offender == src || istype(offender,/obj/effect/beam/i_beam)) - return - if (offender && isitem(offender)) - var/obj/item/I = offender - if (I.item_flags & ABSTRACT) - return - return refreshBeam() - -/obj/item/assembly/infra/setDir() - . = ..() - refreshBeam() - -/obj/item/assembly/infra/ui_status(mob/user) - if(is_secured(user)) - return ..() - return UI_CLOSE - -/obj/item/assembly/infra/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.hands_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "InfraredEmitter", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/item/assembly/infra/ui_data(mob/user) - var/list/data = list() - data["on"] = on - data["visible"] = visible - return data - -/obj/item/assembly/infra/ui_act(action, params) - if(..()) - return - - switch(action) - if("power") - on = !on - . = TRUE - if("visibility") - visible = !visible - . = TRUE - - update_icon() - refreshBeam() - -/***************************IBeam*********************************/ - -/obj/effect/beam/i_beam - name = "infrared beam" - icon = 'icons/obj/projectiles.dmi' - icon_state = "ibeam" - anchored = TRUE - density = FALSE - pass_flags = PASSTABLE|PASSGLASS|PASSGRILLE|LETPASSTHROW - var/obj/item/assembly/infra/master - -/obj/effect/beam/i_beam/Crossed(atom/movable/AM as mob|obj) - . = ..() - if(istype(AM, /obj/effect/beam)) - return - if (isitem(AM)) - var/obj/item/I = AM - if (I.item_flags & ABSTRACT) - return - master.trigger_beam(AM, get_turf(src)) +/obj/item/assembly/infra + name = "infrared emitter" + desc = "Emits a visible or invisible beam and is triggered when the beam is interrupted." + icon_state = "infrared" + custom_materials = list(/datum/material/iron=1000, /datum/material/glass=500) + is_position_sensitive = TRUE + drop_sound = 'sound/items/handling/component_drop.ogg' + pickup_sound = 'sound/items/handling/component_pickup.ogg' + var/on = FALSE + var/visible = FALSE + var/maxlength = 8 + var/list/obj/effect/beam/i_beam/beams + var/olddir = 0 + var/turf/listeningTo + var/hearing_range = 3 + +/obj/item/assembly/infra/Initialize() + . = ..() + beams = list() + START_PROCESSING(SSobj, src) + +/obj/item/assembly/infra/ComponentInitialize() + . = ..() + var/static/rotation_flags = ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_FLIP | ROTATION_VERBS + AddComponent(/datum/component/simple_rotation, rotation_flags, after_rotation=CALLBACK(src,.proc/after_rotation)) + +/obj/item/assembly/infra/proc/after_rotation() + refreshBeam() + +/obj/item/assembly/infra/Destroy() + STOP_PROCESSING(SSobj, src) + listeningTo = null + QDEL_LIST(beams) + . = ..() + +/obj/item/assembly/infra/examine(mob/user) + . = ..() + . += "The infrared trigger is [on?"on":"off"]." + +/obj/item/assembly/infra/activate() + if(!..()) + return FALSE //Cooldown check + on = !on + refreshBeam() + update_icon() + return TRUE + +/obj/item/assembly/infra/toggle_secure() + secured = !secured + if(secured) + START_PROCESSING(SSobj, src) + refreshBeam() + else + QDEL_LIST(beams) + STOP_PROCESSING(SSobj, src) + update_icon() + return secured + +/obj/item/assembly/infra/update_icon() + cut_overlays() + attached_overlays = list() + if(on) + add_overlay("infrared_on") + attached_overlays += "infrared_on" + if(visible && secured) + add_overlay("infrared_visible") + attached_overlays += "infrared_visible" + + if(holder) + holder.update_icon() + return + +/obj/item/assembly/infra/dropped() + . = ..() + if(holder) + holder_movement() //sync the dir of the device as well if it's contained in a TTV or an assembly holder + else + refreshBeam() + +/obj/item/assembly/infra/process() + if(!on || !secured) + refreshBeam() + return + +/obj/item/assembly/infra/proc/refreshBeam() + QDEL_LIST(beams) + if(throwing || !on || !secured) + return + if(holder) + if(holder.master) //incase the sensor is part of an assembly that's contained in another item, such as a single tank bomb + if(!holder.master.IsSpecialAssembly() || !isturf(holder.master.loc)) + return + else if(!isturf(holder.loc)) //else just check where the holder is + return + else if(!isturf(loc)) //or just where the fuck we are in general + return + var/turf/T = get_turf(src) + var/_dir = dir + var/turf/_T = get_step(T, _dir) + if(_T) + for(var/i in 1 to maxlength) + var/obj/effect/beam/i_beam/I = new(T) + if(istype(holder, /obj/item/assembly_holder)) + var/obj/item/assembly_holder/assembly_holder = holder + I.icon_state = "[initial(I.icon_state)]_[(assembly_holder.a_left == src) ? "l":"r"]" //Sync the offset of the beam with the position of the sensor. + else if(istype(holder, /obj/item/transfer_valve)) + I.icon_state = "[initial(I.icon_state)]_ttv" + I.density = TRUE + if(!I.Move(_T)) + qdel(I) + switchListener(_T) + break + I.density = FALSE + beams += I + I.master = src + I.setDir(_dir) + I.invisibility = visible? 0 : INVISIBILITY_ABSTRACT + T = _T + _T = get_step(_T, _dir) + CHECK_TICK + +/obj/item/assembly/infra/on_detach() + . = ..() + if(!.) + return + refreshBeam() + +/obj/item/assembly/infra/attack_hand() + . = ..() + refreshBeam() + +/obj/item/assembly/infra/Moved() + var/t = dir + . = ..() + setDir(t) + +/obj/item/assembly/infra/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, gentle = FALSE, quickstart = TRUE) + . = ..() + olddir = dir + +/obj/item/assembly/infra/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + . = ..() + if(!olddir) + return + setDir(olddir) + olddir = null + +/obj/item/assembly/infra/proc/trigger_beam(atom/movable/AM, turf/location) + refreshBeam() + switchListener(location) + if(!secured || !on || next_activate > world.time) + return FALSE + pulse(FALSE) + audible_message("[icon2html(src, hearers(src))] *beep* *beep* *beep*", null, hearing_range) + for(var/CHM in get_hearers_in_view(hearing_range, src)) + if(ismob(CHM)) + var/mob/LM = CHM + LM.playsound_local(get_turf(src), 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE) + next_activate = world.time + 30 + +/obj/item/assembly/infra/proc/switchListener(turf/newloc) + if(listeningTo == newloc) + return + if(listeningTo) + UnregisterSignal(listeningTo, COMSIG_ATOM_EXITED) + RegisterSignal(newloc, COMSIG_ATOM_EXITED, .proc/check_exit) + listeningTo = newloc + +/obj/item/assembly/infra/proc/check_exit(datum/source, atom/movable/offender) + if(QDELETED(src)) + return + if(offender == src || istype(offender,/obj/effect/beam/i_beam)) + return + if (offender && isitem(offender)) + var/obj/item/I = offender + if (I.item_flags & ABSTRACT) + return + return refreshBeam() + +/obj/item/assembly/infra/setDir() + . = ..() + refreshBeam() + +/obj/item/assembly/infra/ui_status(mob/user) + if(is_secured(user)) + return ..() + return UI_CLOSE + +/obj/item/assembly/infra/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "InfraredEmitter", name) + ui.open() + +/obj/item/assembly/infra/ui_data(mob/user) + var/list/data = list() + data["on"] = on + data["visible"] = visible + return data + +/obj/item/assembly/infra/ui_act(action, params) + if(..()) + return + + switch(action) + if("power") + on = !on + . = TRUE + if("visibility") + visible = !visible + . = TRUE + + update_icon() + refreshBeam() + +/***************************IBeam*********************************/ + +/obj/effect/beam/i_beam + name = "infrared beam" + icon = 'icons/obj/projectiles.dmi' + icon_state = "ibeam" + anchored = TRUE + density = FALSE + pass_flags = PASSTABLE|PASSGLASS|PASSGRILLE|LETPASSTHROW + var/obj/item/assembly/infra/master + +/obj/effect/beam/i_beam/Crossed(atom/movable/AM as mob|obj) + . = ..() + if(istype(AM, /obj/effect/beam)) + return + if (isitem(AM)) + var/obj/item/I = AM + if (I.item_flags & ABSTRACT) + return + master.trigger_beam(AM, get_turf(src)) diff --git a/code/modules/assembly/mousetrap.dm b/code/modules/assembly/mousetrap.dm index dc40d1cbafb1..d25a95aa8048 100644 --- a/code/modules/assembly/mousetrap.dm +++ b/code/modules/assembly/mousetrap.dm @@ -1,145 +1,145 @@ -/obj/item/assembly/mousetrap - name = "mousetrap" - desc = "A handy little spring-loaded trap for catching pesty rodents." - icon_state = "mousetrap" - item_state = "mousetrap" - custom_materials = list(/datum/material/iron=100) - attachable = TRUE - layer = BELOW_OBJ_LAYER // Wasp Edit - var/armed = FALSE - drop_sound = 'sound/items/handling/component_drop.ogg' - pickup_sound = 'sound/items/handling/component_pickup.ogg' - - -/obj/item/assembly/mousetrap/examine(mob/user) - . = ..() - . += "The pressure plate is [armed?"primed":"safe"]." - -/obj/item/assembly/mousetrap/activate() - if(..()) - armed = !armed - if(!armed) - if(ishuman(usr)) - var/mob/living/carbon/human/user = usr - if((HAS_TRAIT(user, TRAIT_DUMB) || HAS_TRAIT(user, TRAIT_CLUMSY)) && prob(50)) - to_chat(user, "Your hand slips, setting off the trigger!") - pulse(FALSE) - update_icon() - playsound(src, 'sound/weapons/handcuffs.ogg', 30, TRUE, -3) - -/obj/item/assembly/mousetrap/update_icon() - if(armed) - icon_state = "mousetraparmed" - else - icon_state = "mousetrap" - if(holder) - holder.update_icon() - -/obj/item/assembly/mousetrap/proc/triggered(mob/target, type = "feet") - if(!armed) - return - var/obj/item/bodypart/affecting = null - if(ishuman(target)) - var/mob/living/carbon/human/H = target - if(HAS_TRAIT(H, TRAIT_PIERCEIMMUNE)) - playsound(src, 'sound/effects/snap.ogg', 50, TRUE) - armed = FALSE - update_icon() - pulse(FALSE) - return FALSE - switch(type) - if("feet") - if(!H.shoes) - affecting = H.get_bodypart(pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)) - H.Paralyze(60) - if(BODY_ZONE_PRECISE_L_HAND, BODY_ZONE_PRECISE_R_HAND) - if(!H.gloves) - affecting = H.get_bodypart(type) - H.Stun(60) - if(affecting) - if(affecting.receive_damage(1, 0)) - H.update_damage_overlays() - else if(ismouse(target)) - var/mob/living/simple_animal/mouse/M = target - visible_message("SPLAT!") - M.splat() - playsound(src, 'sound/effects/snap.ogg', 50, TRUE) - armed = FALSE - update_icon() - pulse(FALSE) - - -/obj/item/assembly/mousetrap/attack_self(mob/living/carbon/human/user) - if(!armed) - to_chat(user, "You arm [src].") - else - if((HAS_TRAIT(user, TRAIT_DUMB) || HAS_TRAIT(user, TRAIT_CLUMSY)) && prob(50)) - var/which_hand = BODY_ZONE_PRECISE_L_HAND - if(!(user.active_hand_index % 2)) - which_hand = BODY_ZONE_PRECISE_R_HAND - triggered(user, which_hand) - user.visible_message("[user] accidentally sets off [src], breaking their fingers.", \ - "You accidentally trigger [src]!") - return - to_chat(user, "You disarm [src].") - armed = !armed - update_icon() - playsound(src, 'sound/weapons/handcuffs.ogg', 30, TRUE, -3) - - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/item/assembly/mousetrap/attack_hand(mob/living/carbon/human/user) - if(armed) - if((HAS_TRAIT(user, TRAIT_DUMB) || HAS_TRAIT(user, TRAIT_CLUMSY)) && prob(50)) - var/which_hand = BODY_ZONE_PRECISE_L_HAND - if(!(user.active_hand_index % 2)) - which_hand = BODY_ZONE_PRECISE_R_HAND - triggered(user, which_hand) - user.visible_message("[user] accidentally sets off [src], breaking their fingers.", \ - "You accidentally trigger [src]!") - return - return ..() - - -/obj/item/assembly/mousetrap/Crossed(atom/movable/AM as mob|obj) - if(armed) - if(ismob(AM)) - var/mob/MM = AM - if(!(MM.movement_type & FLYING)) - if(ishuman(AM)) - var/mob/living/carbon/H = AM - if(H.m_intent == MOVE_INTENT_RUN) - triggered(H) - H.visible_message("[H] accidentally steps on [src].", \ - "You accidentally step on [src]") - else if(ismouse(MM)) - triggered(MM) - else if(AM.density) // For mousetrap grenades, set off by anything heavy - triggered(AM) - ..() - - -/obj/item/assembly/mousetrap/on_found(mob/finder) - if(armed) - if(finder) - finder.visible_message("[finder] accidentally sets off [src], breaking their fingers.", \ - "You accidentally trigger [src]!") - triggered(finder, (finder.active_hand_index % 2 == 0) ? BODY_ZONE_PRECISE_R_HAND : BODY_ZONE_PRECISE_L_HAND) - return TRUE //end the search! - else - visible_message("[src] snaps shut!") - triggered(loc) - return FALSE - return FALSE - - -/obj/item/assembly/mousetrap/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) - if(!armed) - return ..() - visible_message("[src] is triggered by [AM].") - triggered(null) - - -/obj/item/assembly/mousetrap/armed - icon_state = "mousetraparmed" - armed = TRUE +/obj/item/assembly/mousetrap + name = "mousetrap" + desc = "A handy little spring-loaded trap for catching pesty rodents." + icon_state = "mousetrap" + item_state = "mousetrap" + custom_materials = list(/datum/material/iron=100) + attachable = TRUE + layer = BELOW_OBJ_LAYER // Wasp Edit + var/armed = FALSE + drop_sound = 'sound/items/handling/component_drop.ogg' + pickup_sound = 'sound/items/handling/component_pickup.ogg' + + +/obj/item/assembly/mousetrap/examine(mob/user) + . = ..() + . += "The pressure plate is [armed?"primed":"safe"]." + +/obj/item/assembly/mousetrap/activate() + if(..()) + armed = !armed + if(!armed) + if(ishuman(usr)) + var/mob/living/carbon/human/user = usr + if((HAS_TRAIT(user, TRAIT_DUMB) || HAS_TRAIT(user, TRAIT_CLUMSY)) && prob(50)) + to_chat(user, "Your hand slips, setting off the trigger!") + pulse(FALSE) + update_icon() + playsound(src, 'sound/weapons/handcuffs.ogg', 30, TRUE, -3) + +/obj/item/assembly/mousetrap/update_icon() + if(armed) + icon_state = "mousetraparmed" + else + icon_state = "mousetrap" + if(holder) + holder.update_icon() + +/obj/item/assembly/mousetrap/proc/triggered(mob/target, type = "feet") + if(!armed) + return + var/obj/item/bodypart/affecting = null + if(ishuman(target)) + var/mob/living/carbon/human/H = target + if(HAS_TRAIT(H, TRAIT_PIERCEIMMUNE)) + playsound(src, 'sound/effects/snap.ogg', 50, TRUE) + armed = FALSE + update_icon() + pulse(FALSE) + return FALSE + switch(type) + if("feet") + if(!H.shoes) + affecting = H.get_bodypart(pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)) + H.Paralyze(60) + if(BODY_ZONE_PRECISE_L_HAND, BODY_ZONE_PRECISE_R_HAND) + if(!H.gloves) + affecting = H.get_bodypart(type) + H.Stun(60) + if(affecting) + if(affecting.receive_damage(1, 0)) + H.update_damage_overlays() + else if(ismouse(target)) + var/mob/living/simple_animal/mouse/M = target + visible_message("SPLAT!") + M.splat() + playsound(src, 'sound/effects/snap.ogg', 50, TRUE) + armed = FALSE + update_icon() + pulse(FALSE) + + +/obj/item/assembly/mousetrap/attack_self(mob/living/carbon/human/user) + if(!armed) + to_chat(user, "You arm [src].") + else + if((HAS_TRAIT(user, TRAIT_DUMB) || HAS_TRAIT(user, TRAIT_CLUMSY)) && prob(50)) + var/which_hand = BODY_ZONE_PRECISE_L_HAND + if(!(user.active_hand_index % 2)) + which_hand = BODY_ZONE_PRECISE_R_HAND + triggered(user, which_hand) + user.visible_message("[user] accidentally sets off [src], breaking their fingers.", \ + "You accidentally trigger [src]!") + return + to_chat(user, "You disarm [src].") + armed = !armed + update_icon() + playsound(src, 'sound/weapons/handcuffs.ogg', 30, TRUE, -3) + + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/assembly/mousetrap/attack_hand(mob/living/carbon/human/user) + if(armed) + if((HAS_TRAIT(user, TRAIT_DUMB) || HAS_TRAIT(user, TRAIT_CLUMSY)) && prob(50)) + var/which_hand = BODY_ZONE_PRECISE_L_HAND + if(!(user.active_hand_index % 2)) + which_hand = BODY_ZONE_PRECISE_R_HAND + triggered(user, which_hand) + user.visible_message("[user] accidentally sets off [src], breaking their fingers.", \ + "You accidentally trigger [src]!") + return + return ..() + + +/obj/item/assembly/mousetrap/Crossed(atom/movable/AM as mob|obj) + if(armed) + if(ismob(AM)) + var/mob/MM = AM + if(!(MM.movement_type & FLYING)) + if(ishuman(AM)) + var/mob/living/carbon/H = AM + if(H.m_intent == MOVE_INTENT_RUN) + triggered(H) + H.visible_message("[H] accidentally steps on [src].", \ + "You accidentally step on [src]") + else if(ismouse(MM)) + triggered(MM) + else if(AM.density) // For mousetrap grenades, set off by anything heavy + triggered(AM) + ..() + + +/obj/item/assembly/mousetrap/on_found(mob/finder) + if(armed) + if(finder) + finder.visible_message("[finder] accidentally sets off [src], breaking their fingers.", \ + "You accidentally trigger [src]!") + triggered(finder, (finder.active_hand_index % 2 == 0) ? BODY_ZONE_PRECISE_R_HAND : BODY_ZONE_PRECISE_L_HAND) + return TRUE //end the search! + else + visible_message("[src] snaps shut!") + triggered(loc) + return FALSE + return FALSE + + +/obj/item/assembly/mousetrap/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) + if(!armed) + return ..() + visible_message("[src] is triggered by [AM].") + triggered(null) + + +/obj/item/assembly/mousetrap/armed + icon_state = "mousetraparmed" + armed = TRUE diff --git a/code/modules/assembly/proximity.dm b/code/modules/assembly/proximity.dm index 285d9c3599ee..1fe05770f55c 100644 --- a/code/modules/assembly/proximity.dm +++ b/code/modules/assembly/proximity.dm @@ -1,156 +1,153 @@ -/obj/item/assembly/prox_sensor - name = "proximity sensor" - desc = "Used for scanning and alerting when someone enters a certain proximity." - icon_state = "prox" - custom_materials = list(/datum/material/iron=800, /datum/material/glass=200) - attachable = TRUE - drop_sound = 'sound/items/handling/component_drop.ogg' - pickup_sound = 'sound/items/handling/component_pickup.ogg' - var/ui_x = 250 - var/ui_y = 185 - var/scanning = FALSE - var/timing = FALSE - var/time = 10 - var/sensitivity = 1 - var/hearing_range = 3 - -/obj/item/assembly/prox_sensor/Initialize() - . = ..() - proximity_monitor = new(src, 0) - START_PROCESSING(SSobj, src) - -/obj/item/assembly/prox_sensor/Destroy() - STOP_PROCESSING(SSobj, src) - . = ..() - -/obj/item/assembly/prox_sensor/examine(mob/user) - . = ..() - . += "The proximity sensor is [timing ? "arming" : (scanning ? "armed" : "disarmed")]." - -/obj/item/assembly/prox_sensor/activate() - if(!..()) - return FALSE //Cooldown check - if(!scanning) - timing = !timing - else - scanning = FALSE - update_icon() - return TRUE - -/obj/item/assembly/prox_sensor/on_detach() - . = ..() - if(!.) - return - else - proximity_monitor.SetHost(src,src) - -/obj/item/assembly/prox_sensor/toggle_secure() - secured = !secured - if(!secured) - if(scanning) - toggle_scan() - proximity_monitor.SetHost(src,src) - timing = FALSE - STOP_PROCESSING(SSobj, src) - else - START_PROCESSING(SSobj, src) - proximity_monitor.SetHost(loc,src) - update_icon() - return secured - -/obj/item/assembly/prox_sensor/HasProximity(atom/movable/AM as mob|obj) - if (istype(AM, /obj/effect/beam)) - return - sense() - -/obj/item/assembly/prox_sensor/proc/sense() - if(!scanning || !secured || next_activate > world.time) - return FALSE - pulse(FALSE) - audible_message("[icon2html(src, hearers(src))] *beep* *beep* *beep*", null, hearing_range) - for(var/CHM in get_hearers_in_view(hearing_range, src)) - if(ismob(CHM)) - var/mob/LM = CHM - LM.playsound_local(get_turf(src), 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE) - next_activate = world.time + 30 - return TRUE - -/obj/item/assembly/prox_sensor/process() - if(!timing) - return - time-- - if(time <= 0) - timing = FALSE - toggle_scan(TRUE) - time = initial(time) - -/obj/item/assembly/prox_sensor/proc/toggle_scan(scan) - if(!secured) - return FALSE - scanning = scan - proximity_monitor.SetRange(scanning ? sensitivity : 0) - update_icon() - -/obj/item/assembly/prox_sensor/proc/sensitivity_change(value) - var/sense = min(max(sensitivity + value, 0), 5) - sensitivity = sense - if(scanning && proximity_monitor.SetRange(sense)) - sense() - -/obj/item/assembly/prox_sensor/update_icon() - cut_overlays() - attached_overlays = list() - if(timing) - add_overlay("prox_timing") - attached_overlays += "prox_timing" - if(scanning) - add_overlay("prox_scanning") - attached_overlays += "prox_scanning" - if(holder) - holder.update_icon() - return - -/obj/item/assembly/prox_sensor/ui_status(mob/user) - if(is_secured(user)) - return ..() - return UI_CLOSE - -/obj/item/assembly/prox_sensor/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.hands_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "ProximitySensor", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/item/assembly/prox_sensor/ui_data(mob/user) - var/list/data = list() - data["seconds"] = round(time % 60) - data["minutes"] = round((time - data["seconds"]) / 60) - data["timing"] = timing - data["scanning"] = scanning - data["sensitivity"] = sensitivity - return data - -/obj/item/assembly/prox_sensor/ui_act(action, params) - if(..()) - return - - switch(action) - if("scanning") - toggle_scan(!scanning) - . = TRUE - if("sense") - var/value = text2num(params["range"]) - if(value) - sensitivity_change(value) - . = TRUE - if("time") - timing = !timing - update_icon() - . = TRUE - if("input") - var/value = text2num(params["adjust"]) - if(value) - value = round(time + value) - time = clamp(value, 0, 600) - . = TRUE +/obj/item/assembly/prox_sensor + name = "proximity sensor" + desc = "Used for scanning and alerting when someone enters a certain proximity." + icon_state = "prox" + custom_materials = list(/datum/material/iron=800, /datum/material/glass=200) + attachable = TRUE + drop_sound = 'sound/items/handling/component_drop.ogg' + pickup_sound = 'sound/items/handling/component_pickup.ogg' + var/scanning = FALSE + var/timing = FALSE + var/time = 10 + var/sensitivity = 1 + var/hearing_range = 3 + +/obj/item/assembly/prox_sensor/Initialize() + . = ..() + proximity_monitor = new(src, 0) + START_PROCESSING(SSobj, src) + +/obj/item/assembly/prox_sensor/Destroy() + STOP_PROCESSING(SSobj, src) + . = ..() + +/obj/item/assembly/prox_sensor/examine(mob/user) + . = ..() + . += "The proximity sensor is [timing ? "arming" : (scanning ? "armed" : "disarmed")]." + +/obj/item/assembly/prox_sensor/activate() + if(!..()) + return FALSE //Cooldown check + if(!scanning) + timing = !timing + else + scanning = FALSE + update_icon() + return TRUE + +/obj/item/assembly/prox_sensor/on_detach() + . = ..() + if(!.) + return + else + proximity_monitor.SetHost(src,src) + +/obj/item/assembly/prox_sensor/toggle_secure() + secured = !secured + if(!secured) + if(scanning) + toggle_scan() + proximity_monitor.SetHost(src,src) + timing = FALSE + STOP_PROCESSING(SSobj, src) + else + START_PROCESSING(SSobj, src) + proximity_monitor.SetHost(loc,src) + update_icon() + return secured + +/obj/item/assembly/prox_sensor/HasProximity(atom/movable/AM as mob|obj) + if (istype(AM, /obj/effect/beam)) + return + sense() + +/obj/item/assembly/prox_sensor/proc/sense() + if(!scanning || !secured || next_activate > world.time) + return FALSE + pulse(FALSE) + audible_message("[icon2html(src, hearers(src))] *beep* *beep* *beep*", null, hearing_range) + for(var/CHM in get_hearers_in_view(hearing_range, src)) + if(ismob(CHM)) + var/mob/LM = CHM + LM.playsound_local(get_turf(src), 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE) + next_activate = world.time + 30 + return TRUE + +/obj/item/assembly/prox_sensor/process() + if(!timing) + return + time-- + if(time <= 0) + timing = FALSE + toggle_scan(TRUE) + time = initial(time) + +/obj/item/assembly/prox_sensor/proc/toggle_scan(scan) + if(!secured) + return FALSE + scanning = scan + proximity_monitor.SetRange(scanning ? sensitivity : 0) + update_icon() + +/obj/item/assembly/prox_sensor/proc/sensitivity_change(value) + var/sense = min(max(sensitivity + value, 0), 5) + sensitivity = sense + if(scanning && proximity_monitor.SetRange(sense)) + sense() + +/obj/item/assembly/prox_sensor/update_icon() + cut_overlays() + attached_overlays = list() + if(timing) + add_overlay("prox_timing") + attached_overlays += "prox_timing" + if(scanning) + add_overlay("prox_scanning") + attached_overlays += "prox_scanning" + if(holder) + holder.update_icon() + return + +/obj/item/assembly/prox_sensor/ui_status(mob/user) + if(is_secured(user)) + return ..() + return UI_CLOSE + +/obj/item/assembly/prox_sensor/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "ProximitySensor", name) + ui.open() + +/obj/item/assembly/prox_sensor/ui_data(mob/user) + var/list/data = list() + data["seconds"] = round(time % 60) + data["minutes"] = round((time - data["seconds"]) / 60) + data["timing"] = timing + data["scanning"] = scanning + data["sensitivity"] = sensitivity + return data + +/obj/item/assembly/prox_sensor/ui_act(action, params) + if(..()) + return + + switch(action) + if("scanning") + toggle_scan(!scanning) + . = TRUE + if("sense") + var/value = text2num(params["range"]) + if(value) + sensitivity_change(value) + . = TRUE + if("time") + timing = !timing + update_icon() + . = TRUE + if("input") + var/value = text2num(params["adjust"]) + if(value) + value = round(time + value) + time = clamp(value, 0, 600) + . = TRUE diff --git a/code/modules/assembly/shock_kit.dm b/code/modules/assembly/shock_kit.dm index 7a1c00bfc2f5..dbca939b86e1 100644 --- a/code/modules/assembly/shock_kit.dm +++ b/code/modules/assembly/shock_kit.dm @@ -1,40 +1,40 @@ -/obj/item/assembly/shock_kit - name = "electrohelmet assembly" - desc = "This appears to be made from both an electropack and a helmet." - icon = 'icons/obj/assemblies.dmi' - icon_state = "shock_kit" - var/obj/item/clothing/head/helmet/part1 = null - var/obj/item/electropack/part2 = null - w_class = WEIGHT_CLASS_HUGE - flags_1 = CONDUCT_1 - -/obj/item/assembly/shock_kit/Destroy() - qdel(part1) - qdel(part2) - return ..() - -/obj/item/assembly/shock_kit/wrench_act(mob/living/user, obj/item/I) - ..() - to_chat(user, "You disassemble [src].") - if(part1) - part1.forceMove(drop_location()) - part1.master = null - part1 = null - if(part2) - part2.forceMove(drop_location()) - part2.master = null - part2 = null - qdel(src) - return TRUE - -/obj/item/assembly/shock_kit/attack_self(mob/user) - part1.attack_self(user) - part2.attack_self(user) - add_fingerprint(user) - return - -/obj/item/assembly/shock_kit/receive_signal() - if(istype(loc, /obj/structure/chair/e_chair)) - var/obj/structure/chair/e_chair/C = loc - C.shock() - return +/obj/item/assembly/shock_kit + name = "electrohelmet assembly" + desc = "This appears to be made from both an electropack and a helmet." + icon = 'icons/obj/assemblies.dmi' + icon_state = "shock_kit" + var/obj/item/clothing/head/helmet/part1 = null + var/obj/item/electropack/part2 = null + w_class = WEIGHT_CLASS_HUGE + flags_1 = CONDUCT_1 + +/obj/item/assembly/shock_kit/Destroy() + qdel(part1) + qdel(part2) + return ..() + +/obj/item/assembly/shock_kit/wrench_act(mob/living/user, obj/item/I) + ..() + to_chat(user, "You disassemble [src].") + if(part1) + part1.forceMove(drop_location()) + part1.master = null + part1 = null + if(part2) + part2.forceMove(drop_location()) + part2.master = null + part2 = null + qdel(src) + return TRUE + +/obj/item/assembly/shock_kit/attack_self(mob/user) + part1.attack_self(user) + part2.attack_self(user) + add_fingerprint(user) + return + +/obj/item/assembly/shock_kit/receive_signal() + if(istype(loc, /obj/structure/chair/e_chair)) + var/obj/structure/chair/e_chair/C = loc + C.shock() + return diff --git a/code/modules/assembly/signaler.dm b/code/modules/assembly/signaler.dm index c4108e061f1e..477814cf95ae 100644 --- a/code/modules/assembly/signaler.dm +++ b/code/modules/assembly/signaler.dm @@ -1,253 +1,251 @@ -/obj/item/assembly/signaler - name = "remote signaling device" - desc = "Used to remotely activate devices. Allows for syncing when using a secure signaler on another." - icon_state = "signaller" - item_state = "signaler" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - custom_materials = list(/datum/material/iron=400, /datum/material/glass=120) - wires = WIRE_RECEIVE | WIRE_PULSE | WIRE_RADIO_PULSE | WIRE_RADIO_RECEIVE - attachable = TRUE - drop_sound = 'sound/items/handling/component_drop.ogg' - pickup_sound = 'sound/items/handling/component_pickup.ogg' - var/ui_x = 280 - var/ui_y = 132 - var/code = DEFAULT_SIGNALER_CODE - var/frequency = FREQ_SIGNALER - var/datum/radio_frequency/radio_connection - ///Holds the mind that commited suicide. - var/datum/mind/suicider - ///Holds a reference string to the mob, decides how much of a gamer you are. - var/suicide_mob - var/hearing_range = 1 - -/obj/item/assembly/signaler/suicide_act(mob/living/carbon/user) - user.visible_message("[user] eats \the [src]! If it is signaled, [user.p_they()] will die!") - playsound(src, 'sound/items/eatfood.ogg', 50, TRUE) - moveToNullspace() - suicider = user.mind - suicide_mob = REF(user) - return MANUAL_SUICIDE_NONLETHAL - -/obj/item/assembly/signaler/proc/manual_suicide(datum/mind/suicidee) - var/mob/living/user = suicidee.current - if(!istype(user)) - return - if(suicide_mob == REF(user)) - user.visible_message("[user]'s [src] receives a signal, killing [user.p_them()] instantly!") - else - user.visible_message("[user]'s [src] receives a signal and [user.p_they()] die[user.p_s()] like a gamer!") - user.adjustOxyLoss(200)//it sends an electrical pulse to their heart, killing them. or something. - user.death(0) - user.set_suicide(TRUE) - user.suicide_log() - playsound(user, 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE) - qdel(src) - -/obj/item/assembly/signaler/Initialize() - . = ..() - set_frequency(frequency) - -/obj/item/assembly/signaler/Destroy() - SSradio.remove_object(src,frequency) - suicider = null - . = ..() - -/obj/item/assembly/signaler/activate() - if(!..())//cooldown processing - return FALSE - signal() - return TRUE - -/obj/item/assembly/signaler/update_icon() - if(holder) - holder.update_icon() - return - -/obj/item/assembly/signaler/ui_status(mob/user) - if(is_secured(user)) - return ..() - return UI_CLOSE - -/obj/item/assembly/signaler/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.hands_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "Signaler", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/item/assembly/signaler/ui_data(mob/user) - var/list/data = list() - data["frequency"] = frequency - data["code"] = code - data["minFrequency"] = MIN_FREE_FREQ - data["maxFrequency"] = MAX_FREE_FREQ - return data - -/obj/item/assembly/signaler/ui_act(action, params) - if(..()) - return - - switch(action) - if("signal") - INVOKE_ASYNC(src, .proc/signal) - . = TRUE - if("freq") - frequency = unformat_frequency(params["freq"]) - frequency = sanitize_frequency(frequency, TRUE) - set_frequency(frequency) - . = TRUE - if("code") - code = text2num(params["code"]) - code = round(code) - . = TRUE - if("reset") - if(params["reset"] == "freq") - frequency = initial(frequency) - else - code = initial(code) - . = TRUE - - update_icon() - -/obj/item/assembly/signaler/attackby(obj/item/W, mob/user, params) - if(issignaler(W)) - var/obj/item/assembly/signaler/signaler2 = W - if(secured && signaler2.secured) - code = signaler2.code - set_frequency(signaler2.frequency) - to_chat(user, "You transfer the frequency and code of \the [signaler2.name] to \the [name]") - ..() - -/obj/item/assembly/signaler/proc/signal() - if(!radio_connection) - return - - var/datum/signal/signal = new(list("code" = code)) - radio_connection.post_signal(src, signal) - - var/time = time2text(world.realtime,"hh:mm:ss") - var/turf/T = get_turf(src) - if(usr) - GLOB.lastsignalers.Add("[time] : [usr.key] used [src] @ location ([T.x],[T.y],[T.z]) : [format_frequency(frequency)]/[code]") - -/obj/item/assembly/signaler/receive_signal(datum/signal/signal) - . = FALSE - if(!signal) - return - if(signal.data["code"] != code) - return - if(!(src.wires & WIRE_RADIO_RECEIVE)) - return - if(suicider) - manual_suicide(suicider) - return - pulse(TRUE) - audible_message("[icon2html(src, hearers(src))] *beep* *beep* *beep*", null, hearing_range) - for(var/CHM in get_hearers_in_view(hearing_range, src)) - if(ismob(CHM)) - var/mob/LM = CHM - LM.playsound_local(get_turf(src), 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE) - return TRUE - -/obj/item/assembly/signaler/proc/set_frequency(new_frequency) - SSradio.remove_object(src, frequency) - frequency = new_frequency - radio_connection = SSradio.add_object(src, frequency, RADIO_SIGNALER) - return - -// Embedded signaller used in grenade construction. -// It's necessary because the signaler doens't have an off state. -// Generated during grenade construction. -Sayu -/obj/item/assembly/signaler/receiver - var/on = FALSE - -/obj/item/assembly/signaler/receiver/proc/toggle_safety() - on = !on - -/obj/item/assembly/signaler/receiver/activate() - toggle_safety() - return TRUE - -/obj/item/assembly/signaler/receiver/examine(mob/user) - . = ..() - . += "The radio receiver is [on?"on":"off"]." - -/obj/item/assembly/signaler/receiver/receive_signal(datum/signal/signal) - if(!on) - return - return ..(signal) - -// Embedded signaller used in anomalies. -/obj/item/assembly/signaler/anomaly - name = "anomaly core" - desc = "The neutralized core of an anomaly. It'd probably be valuable for research." - icon_state = "anomaly core" - item_state = "electronic" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - resistance_flags = FIRE_PROOF - var/anomaly_type = /obj/effect/anomaly - -/obj/item/assembly/signaler/anomaly/receive_signal(datum/signal/signal) - if(!signal) - return FALSE - if(signal.data["code"] != code) - return FALSE - if(suicider) - manual_suicide(suicider) - for(var/obj/effect/anomaly/A in get_turf(src)) - A.anomalyNeutralize() - return TRUE - -/obj/item/assembly/signaler/anomaly/manual_suicide(mob/living/carbon/user) - user.visible_message("[user]'s [src] is reacting to the radio signal, warping [user.p_their()] body!") - user.set_suicide(TRUE) - user.suicide_log() - user.gib() - -/obj/item/assembly/signaler/anomaly/attackby(obj/item/I, mob/user, params) - if(I.tool_behaviour == TOOL_ANALYZER) - to_chat(user, "Analyzing... [src]'s stabilized field is fluctuating along frequency [format_frequency(frequency)], code [code].") - ..() - -//Anomaly cores -/obj/item/assembly/signaler/anomaly/pyro - name = "\improper pyroclastic anomaly core" - desc = "The neutralized core of a pyroclastic anomaly. It feels warm to the touch. It'd probably be valuable for research." - icon_state = "pyro core" - anomaly_type = /obj/effect/anomaly/pyro - -/obj/item/assembly/signaler/anomaly/grav - name = "\improper gravitational anomaly core" - desc = "The neutralized core of a gravitational anomaly. It feels much heavier than it looks. It'd probably be valuable for research." - icon_state = "grav core" - anomaly_type = /obj/effect/anomaly/grav - -/obj/item/assembly/signaler/anomaly/flux - name = "\improper flux anomaly core" - desc = "The neutralized core of a flux anomaly. Touching it makes your skin tingle. It'd probably be valuable for research." - icon_state = "flux core" - anomaly_type = /obj/effect/anomaly/flux - -/obj/item/assembly/signaler/anomaly/bluespace - name = "\improper bluespace anomaly core" - desc = "The neutralized core of a bluespace anomaly. It keeps phasing in and out of view. It'd probably be valuable for research." - icon_state = "anomaly core" - anomaly_type = /obj/effect/anomaly/bluespace - -/obj/item/assembly/signaler/anomaly/vortex - name = "\improper vortex anomaly core" - desc = "The neutralized core of a vortex anomaly. It won't sit still, as if some invisible force is acting on it. It'd probably be valuable for research." - icon_state = "vortex core" - anomaly_type = /obj/effect/anomaly/bhole - -/obj/item/assembly/signaler/anomaly/attack_self() - return - -/obj/item/assembly/signaler/cyborg - -/obj/item/assembly/signaler/cyborg/attackby(obj/item/W, mob/user, params) - return -/obj/item/assembly/signaler/cyborg/screwdriver_act(mob/living/user, obj/item/I) - return +/obj/item/assembly/signaler + name = "remote signaling device" + desc = "Used to remotely activate devices. Allows for syncing when using a secure signaler on another." + icon_state = "signaller" + item_state = "signaler" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + custom_materials = list(/datum/material/iron=400, /datum/material/glass=120) + wires = WIRE_RECEIVE | WIRE_PULSE | WIRE_RADIO_PULSE | WIRE_RADIO_RECEIVE + attachable = TRUE + drop_sound = 'sound/items/handling/component_drop.ogg' + pickup_sound = 'sound/items/handling/component_pickup.ogg' + + var/code = DEFAULT_SIGNALER_CODE + var/frequency = FREQ_SIGNALER + var/datum/radio_frequency/radio_connection + ///Holds the mind that commited suicide. + var/datum/mind/suicider + ///Holds a reference string to the mob, decides how much of a gamer you are. + var/suicide_mob + var/hearing_range = 1 + +/obj/item/assembly/signaler/suicide_act(mob/living/carbon/user) + user.visible_message("[user] eats \the [src]! If it is signaled, [user.p_they()] will die!") + playsound(src, 'sound/items/eatfood.ogg', 50, TRUE) + moveToNullspace() + suicider = user.mind + suicide_mob = REF(user) + return MANUAL_SUICIDE_NONLETHAL + +/obj/item/assembly/signaler/proc/manual_suicide(datum/mind/suicidee) + var/mob/living/user = suicidee.current + if(!istype(user)) + return + if(suicide_mob == REF(user)) + user.visible_message("[user]'s [src] receives a signal, killing [user.p_them()] instantly!") + else + user.visible_message("[user]'s [src] receives a signal and [user.p_they()] die[user.p_s()] like a gamer!") + user.adjustOxyLoss(200)//it sends an electrical pulse to their heart, killing them. or something. + user.death(0) + user.set_suicide(TRUE) + user.suicide_log() + playsound(user, 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE) + qdel(src) + +/obj/item/assembly/signaler/Initialize() + . = ..() + set_frequency(frequency) + +/obj/item/assembly/signaler/Destroy() + SSradio.remove_object(src,frequency) + suicider = null + . = ..() + +/obj/item/assembly/signaler/activate() + if(!..())//cooldown processing + return FALSE + signal() + return TRUE + +/obj/item/assembly/signaler/update_icon() + if(holder) + holder.update_icon() + return + +/obj/item/assembly/signaler/ui_status(mob/user) + if(is_secured(user)) + return ..() + return UI_CLOSE + +/obj/item/assembly/signaler/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "Signaler", name) + ui.open() + +/obj/item/assembly/signaler/ui_data(mob/user) + var/list/data = list() + data["frequency"] = frequency + data["code"] = code + data["minFrequency"] = MIN_FREE_FREQ + data["maxFrequency"] = MAX_FREE_FREQ + return data + +/obj/item/assembly/signaler/ui_act(action, params) + if(..()) + return + + switch(action) + if("signal") + INVOKE_ASYNC(src, .proc/signal) + . = TRUE + if("freq") + frequency = unformat_frequency(params["freq"]) + frequency = sanitize_frequency(frequency, TRUE) + set_frequency(frequency) + . = TRUE + if("code") + code = text2num(params["code"]) + code = round(code) + . = TRUE + if("reset") + if(params["reset"] == "freq") + frequency = initial(frequency) + else + code = initial(code) + . = TRUE + + update_icon() + +/obj/item/assembly/signaler/attackby(obj/item/W, mob/user, params) + if(issignaler(W)) + var/obj/item/assembly/signaler/signaler2 = W + if(secured && signaler2.secured) + code = signaler2.code + set_frequency(signaler2.frequency) + to_chat(user, "You transfer the frequency and code of \the [signaler2.name] to \the [name]") + ..() + +/obj/item/assembly/signaler/proc/signal() + if(!radio_connection) + return + + var/datum/signal/signal = new(list("code" = code)) + radio_connection.post_signal(src, signal) + + var/time = time2text(world.realtime,"hh:mm:ss") + var/turf/T = get_turf(src) + if(usr) + GLOB.lastsignalers.Add("[time] : [usr.key] used [src] @ location ([T.x],[T.y],[T.z]) : [format_frequency(frequency)]/[code]") + +/obj/item/assembly/signaler/receive_signal(datum/signal/signal) + . = FALSE + if(!signal) + return + if(signal.data["code"] != code) + return + if(!(src.wires & WIRE_RADIO_RECEIVE)) + return + if(suicider) + manual_suicide(suicider) + return + pulse(TRUE) + audible_message("[icon2html(src, hearers(src))] *beep* *beep* *beep*", null, hearing_range) + for(var/CHM in get_hearers_in_view(hearing_range, src)) + if(ismob(CHM)) + var/mob/LM = CHM + LM.playsound_local(get_turf(src), 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE) + return TRUE + +/obj/item/assembly/signaler/proc/set_frequency(new_frequency) + SSradio.remove_object(src, frequency) + frequency = new_frequency + radio_connection = SSradio.add_object(src, frequency, RADIO_SIGNALER) + return + +// Embedded signaller used in grenade construction. +// It's necessary because the signaler doens't have an off state. +// Generated during grenade construction. -Sayu +/obj/item/assembly/signaler/receiver + var/on = FALSE + +/obj/item/assembly/signaler/receiver/proc/toggle_safety() + on = !on + +/obj/item/assembly/signaler/receiver/activate() + toggle_safety() + return TRUE + +/obj/item/assembly/signaler/receiver/examine(mob/user) + . = ..() + . += "The radio receiver is [on?"on":"off"]." + +/obj/item/assembly/signaler/receiver/receive_signal(datum/signal/signal) + if(!on) + return + return ..(signal) + +// Embedded signaller used in anomalies. +/obj/item/assembly/signaler/anomaly + name = "anomaly core" + desc = "The neutralized core of an anomaly. It'd probably be valuable for research." + icon_state = "anomaly core" + item_state = "electronic" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + resistance_flags = FIRE_PROOF + var/anomaly_type = /obj/effect/anomaly + +/obj/item/assembly/signaler/anomaly/receive_signal(datum/signal/signal) + if(!signal) + return FALSE + if(signal.data["code"] != code) + return FALSE + if(suicider) + manual_suicide(suicider) + for(var/obj/effect/anomaly/A in get_turf(src)) + A.anomalyNeutralize() + return TRUE + +/obj/item/assembly/signaler/anomaly/manual_suicide(mob/living/carbon/user) + user.visible_message("[user]'s [src] is reacting to the radio signal, warping [user.p_their()] body!") + user.set_suicide(TRUE) + user.suicide_log() + user.gib() + +/obj/item/assembly/signaler/anomaly/attackby(obj/item/I, mob/user, params) + if(I.tool_behaviour == TOOL_ANALYZER) + to_chat(user, "Analyzing... [src]'s stabilized field is fluctuating along frequency [format_frequency(frequency)], code [code].") + ..() + +//Anomaly cores +/obj/item/assembly/signaler/anomaly/pyro + name = "\improper pyroclastic anomaly core" + desc = "The neutralized core of a pyroclastic anomaly. It feels warm to the touch. It'd probably be valuable for research." + icon_state = "pyro core" + anomaly_type = /obj/effect/anomaly/pyro + +/obj/item/assembly/signaler/anomaly/grav + name = "\improper gravitational anomaly core" + desc = "The neutralized core of a gravitational anomaly. It feels much heavier than it looks. It'd probably be valuable for research." + icon_state = "grav core" + anomaly_type = /obj/effect/anomaly/grav + +/obj/item/assembly/signaler/anomaly/flux + name = "\improper flux anomaly core" + desc = "The neutralized core of a flux anomaly. Touching it makes your skin tingle. It'd probably be valuable for research." + icon_state = "flux core" + anomaly_type = /obj/effect/anomaly/flux + +/obj/item/assembly/signaler/anomaly/bluespace + name = "\improper bluespace anomaly core" + desc = "The neutralized core of a bluespace anomaly. It keeps phasing in and out of view. It'd probably be valuable for research." + icon_state = "anomaly core" + anomaly_type = /obj/effect/anomaly/bluespace + +/obj/item/assembly/signaler/anomaly/vortex + name = "\improper vortex anomaly core" + desc = "The neutralized core of a vortex anomaly. It won't sit still, as if some invisible force is acting on it. It'd probably be valuable for research." + icon_state = "vortex core" + anomaly_type = /obj/effect/anomaly/bhole + +/obj/item/assembly/signaler/anomaly/attack_self() + return + +/obj/item/assembly/signaler/cyborg + +/obj/item/assembly/signaler/cyborg/attackby(obj/item/W, mob/user, params) + return +/obj/item/assembly/signaler/cyborg/screwdriver_act(mob/living/user, obj/item/I) + return diff --git a/code/modules/assembly/timer.dm b/code/modules/assembly/timer.dm index 2ebc929eb653..4f8bea6900d1 100644 --- a/code/modules/assembly/timer.dm +++ b/code/modules/assembly/timer.dm @@ -1,128 +1,126 @@ -/obj/item/assembly/timer - name = "timer" - desc = "Used to time things. Works well with contraptions which has to count down. Tick tock." - icon_state = "timer" - custom_materials = list(/datum/material/iron=500, /datum/material/glass=50) - attachable = TRUE - drop_sound = 'sound/items/handling/component_drop.ogg' - pickup_sound = 'sound/items/handling/component_pickup.ogg' - var/ui_x = 275 - var/ui_y = 115 - var/timing = FALSE - var/time = 5 - var/saved_time = 5 - var/loop = FALSE - var/hearing_range = 3 - -/obj/item/assembly/timer/suicide_act(mob/living/user) - user.visible_message("[user] looks at the timer and decides [user.p_their()] fate! It looks like [user.p_theyre()] going to commit suicide!") - activate()//doesnt rely on timer_end to prevent weird metas where one person can control the timer and therefore someone's life. (maybe that should be how it works...) - addtimer(CALLBACK(src, .proc/manual_suicide, user), time*10)//kill yourself once the time runs out - return MANUAL_SUICIDE - -/obj/item/assembly/timer/proc/manual_suicide(mob/living/user) - user.visible_message("[user]'s time is up!") - user.adjustOxyLoss(200) - user.death(0) - -/obj/item/assembly/timer/Initialize() - . = ..() - START_PROCESSING(SSobj, src) - -/obj/item/assembly/timer/Destroy() - STOP_PROCESSING(SSobj, src) - . = ..() - -/obj/item/assembly/timer/examine(mob/user) - . = ..() - . += "The timer is [timing ? "counting down from [time]":"set for [time] seconds"]." - -/obj/item/assembly/timer/activate() - if(!..()) - return FALSE//Cooldown check - timing = !timing - update_icon() - return TRUE - -/obj/item/assembly/timer/toggle_secure() - secured = !secured - if(secured) - START_PROCESSING(SSobj, src) - else - timing = FALSE - STOP_PROCESSING(SSobj, src) - update_icon() - return secured - -/obj/item/assembly/timer/proc/timer_end() - if(!secured || next_activate > world.time) - return FALSE - pulse(FALSE) - audible_message("[icon2html(src, hearers(src))] *beep* *beep* *beep*", null, hearing_range) - for(var/CHM in get_hearers_in_view(hearing_range, src)) - if(ismob(CHM)) - var/mob/LM = CHM - LM.playsound_local(get_turf(src), 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE) - if(loop) - timing = TRUE - update_icon() - -/obj/item/assembly/timer/process() - if(!timing) - return - time-- - if(time <= 0) - timing = FALSE - timer_end() - time = saved_time - -/obj/item/assembly/timer/update_icon() - cut_overlays() - attached_overlays = list() - if(timing) - add_overlay("timer_timing") - attached_overlays += "timer_timing" - if(holder) - holder.update_icon() - -/obj/item/assembly/timer/ui_status(mob/user) - if(is_secured(user)) - return ..() - return UI_CLOSE - -/obj/item/assembly/timer/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.hands_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "Timer", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/item/assembly/timer/ui_data(mob/user) - var/list/data = list() - data["seconds"] = round(time % 60) - data["minutes"] = round((time - data["seconds"]) / 60) - data["timing"] = timing - data["loop"] = loop - return data - -/obj/item/assembly/timer/ui_act(action, params) - if(..()) - return - - switch(action) - if("time") - timing = !timing - if(timing && istype(holder, /obj/item/transfer_valve)) - log_bomber(usr, "activated a", src, "attachment on [holder]") - update_icon() - . = TRUE - if("repeat") - loop = !loop - . = TRUE - if("input") - var/value = text2num(params["adjust"]) - if(value) - value = round(time + value) - time = clamp(value, 1, 600) - saved_time = time - . = TRUE +/obj/item/assembly/timer + name = "timer" + desc = "Used to time things. Works well with contraptions which has to count down. Tick tock." + icon_state = "timer" + custom_materials = list(/datum/material/iron=500, /datum/material/glass=50) + attachable = TRUE + drop_sound = 'sound/items/handling/component_drop.ogg' + pickup_sound = 'sound/items/handling/component_pickup.ogg' + + var/timing = FALSE + var/time = 5 + var/saved_time = 5 + var/loop = FALSE + var/hearing_range = 3 + +/obj/item/assembly/timer/suicide_act(mob/living/user) + user.visible_message("[user] looks at the timer and decides [user.p_their()] fate! It looks like [user.p_theyre()] going to commit suicide!") + activate()//doesnt rely on timer_end to prevent weird metas where one person can control the timer and therefore someone's life. (maybe that should be how it works...) + addtimer(CALLBACK(src, .proc/manual_suicide, user), time*10)//kill yourself once the time runs out + return MANUAL_SUICIDE + +/obj/item/assembly/timer/proc/manual_suicide(mob/living/user) + user.visible_message("[user]'s time is up!") + user.adjustOxyLoss(200) + user.death(0) + +/obj/item/assembly/timer/Initialize() + . = ..() + START_PROCESSING(SSobj, src) + +/obj/item/assembly/timer/Destroy() + STOP_PROCESSING(SSobj, src) + . = ..() + +/obj/item/assembly/timer/examine(mob/user) + . = ..() + . += "The timer is [timing ? "counting down from [time]":"set for [time] seconds"]." + +/obj/item/assembly/timer/activate() + if(!..()) + return FALSE//Cooldown check + timing = !timing + update_icon() + return TRUE + +/obj/item/assembly/timer/toggle_secure() + secured = !secured + if(secured) + START_PROCESSING(SSobj, src) + else + timing = FALSE + STOP_PROCESSING(SSobj, src) + update_icon() + return secured + +/obj/item/assembly/timer/proc/timer_end() + if(!secured || next_activate > world.time) + return FALSE + pulse(FALSE) + audible_message("[icon2html(src, hearers(src))] *beep* *beep* *beep*", null, hearing_range) + for(var/CHM in get_hearers_in_view(hearing_range, src)) + if(ismob(CHM)) + var/mob/LM = CHM + LM.playsound_local(get_turf(src), 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE) + if(loop) + timing = TRUE + update_icon() + +/obj/item/assembly/timer/process() + if(!timing) + return + time-- + if(time <= 0) + timing = FALSE + timer_end() + time = saved_time + +/obj/item/assembly/timer/update_icon() + cut_overlays() + attached_overlays = list() + if(timing) + add_overlay("timer_timing") + attached_overlays += "timer_timing" + if(holder) + holder.update_icon() + +/obj/item/assembly/timer/ui_status(mob/user) + if(is_secured(user)) + return ..() + return UI_CLOSE + +/obj/item/assembly/timer/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "Timer", name) + ui.open() + +/obj/item/assembly/timer/ui_data(mob/user) + var/list/data = list() + data["seconds"] = round(time % 60) + data["minutes"] = round((time - data["seconds"]) / 60) + data["timing"] = timing + data["loop"] = loop + return data + +/obj/item/assembly/timer/ui_act(action, params) + if(..()) + return + + switch(action) + if("time") + timing = !timing + if(timing && istype(holder, /obj/item/transfer_valve)) + log_bomber(usr, "activated a", src, "attachment on [holder]") + update_icon() + . = TRUE + if("repeat") + loop = !loop + . = TRUE + if("input") + var/value = text2num(params["adjust"]) + if(value) + value = round(time + value) + time = clamp(value, 1, 600) + saved_time = time + . = TRUE diff --git a/code/modules/assembly/voice.dm b/code/modules/assembly/voice.dm index dd572b83d69f..4606104d9998 100644 --- a/code/modules/assembly/voice.dm +++ b/code/modules/assembly/voice.dm @@ -1,104 +1,104 @@ -#define INCLUSIVE_MODE 1 -#define EXCLUSIVE_MODE 2 -#define RECOGNIZER_MODE 3 -#define VOICE_SENSOR_MODE 4 - -/obj/item/assembly/voice - name = "voice analyzer" - desc = "A small electronic device able to record a voice sample, and send a signal when that sample is repeated." - icon_state = "voice" - custom_materials = list(/datum/material/iron=500, /datum/material/glass=50) - flags_1 = HEAR_1 - attachable = TRUE - verb_say = "beeps" - verb_ask = "beeps" - verb_exclaim = "beeps" - var/listening = FALSE - var/recorded = "" //the activation message - var/mode = 1 - var/static/list/modes = list("inclusive", - "exclusive", - "recognizer", - "voice sensor") - drop_sound = 'sound/items/handling/component_drop.ogg' - pickup_sound = 'sound/items/handling/component_pickup.ogg' - -/obj/item/assembly/voice/examine(mob/user) - . = ..() - . += "Use a multitool to swap between \"inclusive\", \"exclusive\", \"recognizer\", and \"voice sensor\" mode." - -/obj/item/assembly/voice/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode) - . = ..() - if(speaker == src) - return - - if(listening && !radio_freq) - record_speech(speaker, raw_message, message_language) - else - if(check_activation(speaker, raw_message)) - addtimer(CALLBACK(src, .proc/pulse, 0), 10) - -/obj/item/assembly/voice/proc/record_speech(atom/movable/speaker, raw_message, datum/language/message_language) - switch(mode) - if(INCLUSIVE_MODE) - recorded = raw_message - listening = FALSE - say("Activation message is '[recorded]'.", message_language) - if(EXCLUSIVE_MODE) - recorded = raw_message - listening = FALSE - say("Activation message is '[recorded]'.", message_language) - if(RECOGNIZER_MODE) - recorded = speaker.GetVoice() - listening = FALSE - say("Your voice pattern is saved.", message_language) - if(VOICE_SENSOR_MODE) - if(length(raw_message)) - addtimer(CALLBACK(src, .proc/pulse, 0), 10) - -/obj/item/assembly/voice/proc/check_activation(atom/movable/speaker, raw_message) - . = FALSE - switch(mode) - if(INCLUSIVE_MODE) - if(findtext(raw_message, recorded)) - . = TRUE - if(EXCLUSIVE_MODE) - if(raw_message == recorded) - . = TRUE - if(RECOGNIZER_MODE) - if(speaker.GetVoice() == recorded) - . = TRUE - if(VOICE_SENSOR_MODE) - if(length(raw_message)) - . = TRUE - -/obj/item/assembly/voice/multitool_act(mob/living/user, obj/item/I) - ..() - mode %= modes.len - mode++ - to_chat(user, "You set [src] into [modes[mode]] mode.") - listening = FALSE - recorded = "" - return TRUE - -/obj/item/assembly/voice/activate() - if(!secured || holder) - return FALSE - listening = !listening - say("[listening ? "Now" : "No longer"] recording input.") - return TRUE - -/obj/item/assembly/voice/attack_self(mob/user) - if(!user) - return FALSE - activate() - return TRUE - -/obj/item/assembly/voice/toggle_secure() - . = ..() - listening = FALSE - -#undef INCLUSIVE_MODE -#undef EXCLUSIVE_MODE -#undef RECOGNIZER_MODE -#undef VOICE_SENSOR_MODE +#define INCLUSIVE_MODE 1 +#define EXCLUSIVE_MODE 2 +#define RECOGNIZER_MODE 3 +#define VOICE_SENSOR_MODE 4 + +/obj/item/assembly/voice + name = "voice analyzer" + desc = "A small electronic device able to record a voice sample, and send a signal when that sample is repeated." + icon_state = "voice" + custom_materials = list(/datum/material/iron=500, /datum/material/glass=50) + flags_1 = HEAR_1 + attachable = TRUE + verb_say = "beeps" + verb_ask = "beeps" + verb_exclaim = "beeps" + var/listening = FALSE + var/recorded = "" //the activation message + var/mode = 1 + var/static/list/modes = list("inclusive", + "exclusive", + "recognizer", + "voice sensor") + drop_sound = 'sound/items/handling/component_drop.ogg' + pickup_sound = 'sound/items/handling/component_pickup.ogg' + +/obj/item/assembly/voice/examine(mob/user) + . = ..() + . += "Use a multitool to swap between \"inclusive\", \"exclusive\", \"recognizer\", and \"voice sensor\" mode." + +/obj/item/assembly/voice/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode) + . = ..() + if(speaker == src) + return + + if(listening && !radio_freq) + record_speech(speaker, raw_message, message_language) + else + if(check_activation(speaker, raw_message)) + addtimer(CALLBACK(src, .proc/pulse, 0), 10) + +/obj/item/assembly/voice/proc/record_speech(atom/movable/speaker, raw_message, datum/language/message_language) + switch(mode) + if(INCLUSIVE_MODE) + recorded = raw_message + listening = FALSE + say("Activation message is '[recorded]'.", message_language) + if(EXCLUSIVE_MODE) + recorded = raw_message + listening = FALSE + say("Activation message is '[recorded]'.", message_language) + if(RECOGNIZER_MODE) + recorded = speaker.GetVoice() + listening = FALSE + say("Your voice pattern is saved.", message_language) + if(VOICE_SENSOR_MODE) + if(length(raw_message)) + addtimer(CALLBACK(src, .proc/pulse, 0), 10) + +/obj/item/assembly/voice/proc/check_activation(atom/movable/speaker, raw_message) + . = FALSE + switch(mode) + if(INCLUSIVE_MODE) + if(findtext(raw_message, recorded)) + . = TRUE + if(EXCLUSIVE_MODE) + if(raw_message == recorded) + . = TRUE + if(RECOGNIZER_MODE) + if(speaker.GetVoice() == recorded) + . = TRUE + if(VOICE_SENSOR_MODE) + if(length(raw_message)) + . = TRUE + +/obj/item/assembly/voice/multitool_act(mob/living/user, obj/item/I) + ..() + mode %= modes.len + mode++ + to_chat(user, "You set [src] into [modes[mode]] mode.") + listening = FALSE + recorded = "" + return TRUE + +/obj/item/assembly/voice/activate() + if(!secured || holder) + return FALSE + listening = !listening + say("[listening ? "Now" : "No longer"] recording input.") + return TRUE + +/obj/item/assembly/voice/attack_self(mob/user) + if(!user) + return FALSE + activate() + return TRUE + +/obj/item/assembly/voice/toggle_secure() + . = ..() + listening = FALSE + +#undef INCLUSIVE_MODE +#undef EXCLUSIVE_MODE +#undef RECOGNIZER_MODE +#undef VOICE_SENSOR_MODE diff --git a/code/modules/asset_cache/asset_cache.dm b/code/modules/asset_cache/asset_cache.dm index 9785a15f8721..388206cab239 100644 --- a/code/modules/asset_cache/asset_cache.dm +++ b/code/modules/asset_cache/asset_cache.dm @@ -94,6 +94,13 @@ Note: If your code uses output() with assets you will need to call asset_flush o var/list/stacktrace = gib_stack_trace() log_asset("WARNING: dupe asset added to the asset cache: [asset_name] existing asset md5: [OACI.md5] new asset md5:[ACI.md5]\n[stacktrace.Join("\n")]") SSassets.cache[asset_name] = ACI + return ACI + +/// Returns the url of the asset, currently this is just its name, here to allow further work cdn'ing assets. +/// Can be given an asset as well, this is just a work around for buggy edge cases where two assets may have the same name, doesn't matter now, but it will when the cdn comes. +/proc/get_asset_url(asset_name, asset = null) + var/datum/asset_cache_item/ACI = SSassets.cache[asset_name] + return ACI?.url //Generated names do not include file extention. //Used mainly for code that deals with assets in a generic way diff --git a/code/modules/asset_cache/asset_cache_item.dm b/code/modules/asset_cache/asset_cache_item.dm index c705a1733334..e74293c65efb 100644 --- a/code/modules/asset_cache/asset_cache_item.dm +++ b/code/modules/asset_cache/asset_cache_item.dm @@ -1,21 +1,23 @@ -/** - * # asset_cache_item - * - * An internal datum containing info on items in the asset cache. Mainly used to cache md5 info for speed. -**/ -/datum/asset_cache_item - var/name - var/md5 - var/resource - -/datum/asset_cache_item/New(name, file) - if (!isfile(file)) - file = fcopy_rsc(file) - md5 = md5(file) - if (!md5) - md5 = md5(fcopy_rsc(file)) - if (!md5) - CRASH("invalid asset sent to asset cache") - debug_world_log("asset cache unexpected success of second fcopy_rsc") - src.name = name - resource = file +/** + * # asset_cache_item + * + * An internal datum containing info on items in the asset cache. Mainly used to cache md5 info for speed. +**/ +/datum/asset_cache_item + var/name + var/url + var/md5 + var/resource + +/datum/asset_cache_item/New(name, file) + if (!isfile(file)) + file = fcopy_rsc(file) + md5 = md5(file) + if (!md5) + md5 = md5(fcopy_rsc(file)) + if (!md5) + CRASH("invalid asset sent to asset cache") + debug_world_log("asset cache unexpected success of second fcopy_rsc") + src.name = name + url = name + resource = file diff --git a/code/modules/asset_cache/asset_list.dm b/code/modules/asset_cache/asset_list.dm index 2e5881c67f30..4ce9dcf6fc0e 100644 --- a/code/modules/asset_cache/asset_list.dm +++ b/code/modules/asset_cache/asset_list.dm @@ -16,6 +16,9 @@ GLOBAL_LIST_EMPTY(asset_datums) GLOB.asset_datums[type] = src register() +/datum/asset/proc/get_url_mappings() + return list() + /datum/asset/proc/register() return @@ -30,11 +33,19 @@ GLOBAL_LIST_EMPTY(asset_datums) /datum/asset/simple/register() for(var/asset_name in assets) - register_asset(asset_name, assets[asset_name]) + assets[asset_name] = register_asset(asset_name, assets[asset_name]) /datum/asset/simple/send(client) . = send_asset_list(client, assets) +/datum/asset/simple/get_url_mappings() + . = list() + for (var/asset_name in assets) + var/datum/asset_cache_item/ACI = assets[asset_name] + if (!ACI) + continue + .[asset_name] = ACI.url + // For registering or sending multiple others at once /datum/asset/group @@ -50,6 +61,11 @@ GLOBAL_LIST_EMPTY(asset_datums) var/datum/asset/A = get_asset_datum(type) . = A.send(C) || . +/datum/asset/group/get_url_mappings() + . = list() + for(var/type in children) + var/datum/asset/A = get_asset_datum(type) + . += A.get_url_mappings() // spritesheet implementation - coalesces various icons into a single .png file // and uses CSS to select icons out of that file - saves on transferring some @@ -70,7 +86,9 @@ GLOBAL_LIST_EMPTY(asset_datums) if (!name) CRASH("spritesheet [type] cannot register without a name") ensure_stripped() - + for(var/size_id in sizes) + var/size = sizes[size_id] + register_asset("[name]_[size_id].png", size[SPRSZ_STRIPPED]) var/res_name = "spritesheet_[name].css" var/fname = "data/spritesheets/[res_name]" fdel(fname) @@ -78,10 +96,6 @@ GLOBAL_LIST_EMPTY(asset_datums) register_asset(res_name, fcopy_rsc(fname)) fdel(fname) - for(var/size_id in sizes) - var/size = sizes[size_id] - register_asset("[name]_[size_id].png", size[SPRSZ_STRIPPED]) - /datum/asset/spritesheet/send(client/C) if (!name) return @@ -90,6 +104,15 @@ GLOBAL_LIST_EMPTY(asset_datums) all += "[name]_[size_id].png" . = send_asset_list(C, all) +/datum/asset/spritesheet/get_url_mappings() + if (!name) + return + . = list("spritesheet_[name].css" = get_asset_url("spritesheet_[name].css")) + for(var/size_id in sizes) + .["[name]_[size_id].png"] = get_asset_url("[name]_[size_id].png") + + + /datum/asset/spritesheet/proc/ensure_stripped(sizes_to_strip = sizes) for(var/size_id in sizes_to_strip) var/size = sizes[size_id] @@ -111,7 +134,7 @@ GLOBAL_LIST_EMPTY(asset_datums) for (var/size_id in sizes) var/size = sizes[size_id] var/icon/tiny = size[SPRSZ_ICON] - out += ".[name][size_id]{display:inline-block;width:[tiny.Width()]px;height:[tiny.Height()]px;background:url('[name]_[size_id].png') no-repeat;}" + out += ".[name][size_id]{display:inline-block;width:[tiny.Width()]px;height:[tiny.Height()]px;background:url('[get_asset_url("[name]_[size_id].png")]') no-repeat;}" for (var/sprite_id in sprites) var/sprite = sprites[sprite_id] @@ -162,7 +185,10 @@ GLOBAL_LIST_EMPTY(asset_datums) Insert("[prefix][prefix2][icon_state_name]", I, icon_state=icon_state_name, dir=direction) /datum/asset/spritesheet/proc/css_tag() - return {""} + return {""} + +/datum/asset/spritesheet/proc/css_filename() + return get_asset_url("spritesheet_[name].css") /datum/asset/spritesheet/proc/icon_tag(sprite_name) var/sprite = sprites[sprite_name] diff --git a/code/modules/asset_cache/asset_list_items.dm b/code/modules/asset_cache/asset_list_items.dm index d50263b9a45a..2e565e9b4b61 100644 --- a/code/modules/asset_cache/asset_list_items.dm +++ b/code/modules/asset_cache/asset_list_items.dm @@ -6,12 +6,6 @@ "tgui.bundle.css" = 'tgui/packages/tgui/public/tgui.bundle.css', ) -/datum/asset/group/tgui - children = list( - /datum/asset/simple/tgui, - /datum/asset/simple/fontawesome - ) - /datum/asset/simple/headers assets = list( "alarm_green.gif" = 'icons/program_icons/alarm_green.gif', @@ -77,6 +71,7 @@ "refresh" = 'icons/pda_icons/pda_refresh.png', "scanner" = 'icons/pda_icons/pda_scanner.png', "signaler" = 'icons/pda_icons/pda_signaler.png', + "skills" = 'icons/pda_icons/pda_skills.png', "status" = 'icons/pda_icons/pda_status.png', "dronephone" = 'icons/pda_icons/pda_dronephone.png', "emoji" = 'icons/pda_icons/pda_emoji.png' @@ -208,7 +203,7 @@ "boss4.gif" = 'icons/UI_Icons/Arcade/boss4.gif', "boss5.gif" = 'icons/UI_Icons/Arcade/boss5.gif', "boss6.gif" = 'icons/UI_Icons/Arcade/boss6.gif', - ) + ) /datum/asset/spritesheet/simple/achievements name ="achievements" diff --git a/code/modules/atmospherics/environmental/LINDA_turf_tile.dm b/code/modules/atmospherics/environmental/LINDA_turf_tile.dm index 3c1892ed39f8..197478ae7921 100644 --- a/code/modules/atmospherics/environmental/LINDA_turf_tile.dm +++ b/code/modules/atmospherics/environmental/LINDA_turf_tile.dm @@ -32,6 +32,7 @@ var/planetary_atmos = FALSE //air will revert to initial_gas_mix over time var/list/atmos_overlay_types //gas IDs of current active gas overlays + is_openturf = TRUE /turf/open/Initialize() @@ -216,7 +217,7 @@ /atom/movable/var/last_high_pressure_movement_air_cycle = 0 /atom/movable/proc/experience_pressure_difference(pressure_difference, direction, pressure_resistance_prob_delta = 0, throw_target) - var/const/PROBABILITY_OFFSET = 25 + var/const/PROBABILITY_OFFSET = 40 var/const/PROBABILITY_BASE_PRECENT = 10 var/max_force = sqrt(pressure_difference)*(MOVE_FORCE_DEFAULT / 5) set waitfor = 0 @@ -226,14 +227,18 @@ move_prob += pressure_resistance_prob_delta if (move_prob > PROBABILITY_OFFSET && prob(move_prob) && (move_resist != INFINITY) && (!anchored && (max_force >= (move_resist * MOVE_FORCE_PUSH_RATIO))) || (anchored && (max_force >= (move_resist * MOVE_FORCE_FORCEPUSH_RATIO)))) var/move_force = max_force * clamp(move_prob, 0, 100) / 100 - if(move_force > 4000) + if(ismob(src)) + var/mob/M = src + if(M.mob_negates_gravity()) + move_force = 0 + if(move_force > 6000) // WALLSLAM HELL TIME OH BOY var/turf/throw_turf = get_ranged_target_turf(get_turf(src), direction, round(move_force / 2000)) if(throw_target && (get_dir(src, throw_target) & direction)) throw_turf = get_turf(throw_target) - var/throw_speed = clamp(round(move_force / 2000), 1, 10) - throw_at(throw_turf, move_force / 2000, throw_speed) - else + var/throw_speed = clamp(round(move_force / 3000), 1, 10) + throw_at(throw_turf, move_force / 3000, throw_speed) + else if(move_force > 0) step(src, direction) last_high_pressure_movement_air_cycle = SSair.times_fired diff --git a/code/modules/atmospherics/gasmixtures/gas_mixture.dm b/code/modules/atmospherics/gasmixtures/gas_mixture.dm index 93decdf2bc3a..b3587728ca1c 100644 --- a/code/modules/atmospherics/gasmixtures/gas_mixture.dm +++ b/code/modules/atmospherics/gasmixtures/gas_mixture.dm @@ -1,254 +1,254 @@ - /* -What are the archived variables for? - Calculations are done using the archived variables with the results merged into the regular variables. - This prevents race conditions that arise based on the order of tile processing. -*/ -#define MINIMUM_HEAT_CAPACITY 0.0003 -#define MINIMUM_MOLE_COUNT 0.01 -#define MOLAR_ACCURACY 1E-7 -#define QUANTIZE(variable) (round((variable), (MOLAR_ACCURACY)))/*I feel the need to document what happens here. Basically this is used - to catch most rounding errors, however its previous value made it so that - once gases got hot enough, most procedures wouldn't occur due to the fact that the mole - counts would get rounded away. Thus, we lowered it a few orders of magnitude - Edit: As far as I know this might have a bug caused by round(). When it has a second arg it will round up. - So for instance round(0.5, 1) == 1. Trouble is I haven't found any instances of it causing a bug, - and any attempts to fix it just killed atmos. I leave this to a greater man then I*/ -GLOBAL_LIST_INIT(meta_gas_info, meta_gas_list()) //see ATMOSPHERICS/gas_types.dm -GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) - -/proc/init_gaslist_cache() - . = list() - for(var/id in GLOB.meta_gas_info) - var/list/cached_gas = new(3) - - .[id] = cached_gas - - cached_gas[MOLES] = 0 - cached_gas[ARCHIVE] = 0 - cached_gas[GAS_META] = GLOB.meta_gas_info[id] - -/datum/gas_mixture - var/initial_volume = CELL_VOLUME //liters - var/list/reaction_results - var/list/analyzer_results //used for analyzer feedback - not initialized until its used - var/_extools_pointer_gasmixture = 0 // Contains the memory address of the shared_ptr object for this gas mixture in c++ land. Don't. Touch. This. Var. - -/datum/gas_mixture/New(volume) - if (!isnull(volume)) - initial_volume = volume - ATMOS_EXTOOLS_CHECK - __gasmixture_register() - reaction_results = new - -/datum/gas_mixture/vv_edit_var(var_name, var_value) - if(var_name == "_extools_pointer_gasmixture") - return FALSE // please no. segfaults bad. - return ..() - -/datum/gas_mixture/proc/__gasmixture_unregister() -/datum/gas_mixture/proc/__gasmixture_register() - -/proc/gas_types() - var/list/L = subtypesof(/datum/gas) - for(var/gt in L) - var/datum/gas/G = gt - L[gt] = initial(G.specific_heat) - return L - -/datum/gas_mixture/proc/heat_capacity(data = MOLES) //joules per kelvin - - /// Calculate moles -/datum/gas_mixture/proc/total_moles() - -/datum/gas_mixture/proc/return_pressure() //kilopascals - -/datum/gas_mixture/proc/return_temperature() //kelvins - -/datum/gas_mixture/proc/set_min_heat_capacity(n) -/datum/gas_mixture/proc/set_temperature(new_temp) -/datum/gas_mixture/proc/set_volume(new_volume) -/datum/gas_mixture/proc/get_moles(gas_type) -/datum/gas_mixture/proc/set_moles(gas_type, moles) -/datum/gas_mixture/proc/scrub_into(datum/gas_mixture/target, list/gases) -/datum/gas_mixture/proc/mark_immutable() -/datum/gas_mixture/proc/get_gases() -/datum/gas_mixture/proc/multiply(factor) -/datum/gas_mixture/proc/get_last_share() -/datum/gas_mixture/proc/clear() - -/datum/gas_mixture/proc/adjust_moles(gas_type, amt = 0) - set_moles(gas_type, get_moles(gas_type) + amt) - -/datum/gas_mixture/proc/return_volume() //liters - -/datum/gas_mixture/proc/thermal_energy() //joules - - ///Update archived versions of variables. Returns: 1 in all cases -/datum/gas_mixture/proc/archive() - //Update archived versions of variables - //Returns: 1 in all cases - -/datum/gas_mixture/proc/merge(datum/gas_mixture/giver) - //Merges all air from giver into self. Deletes giver. - //Returns: 1 if we are mutable, 0 otherwise - -/datum/gas_mixture/proc/remove(amount) - //Proportionally removes amount of gas from the gas_mixture - //Returns: gas_mixture with the gases removed - -/datum/gas_mixture/proc/remove_ratio(ratio) - //Proportionally removes amount of gas from the gas_mixture - //Returns: gas_mixture with the gases removed - -/datum/gas_mixture/proc/copy() - //Creates new, identical gas mixture - //Returns: duplicate gas mixture - -/datum/gas_mixture/proc/copy_from(datum/gas_mixture/sample) - //Copies variables from sample - //Returns: 1 if we are mutable, 0 otherwise - -/datum/gas_mixture/proc/copy_from_turf(turf/model) - //Copies all gas info from the turf into the gas list along with temperature - //Returns: 1 if we are mutable, 0 otherwise - -/datum/gas_mixture/proc/share(datum/gas_mixture/sharer) - //Performs air sharing calculations between two gas_mixtures assuming only 1 boundary length - //Returns: amount of gas exchanged (+ if sharer received) - -/datum/gas_mixture/proc/temperature_share(datum/gas_mixture/sharer, conduction_coefficient) - //Performs temperature sharing calculations (via conduction) between two gas_mixtures assuming only 1 boundary length - //Returns: new temperature of the sharer - -/datum/gas_mixture/proc/compare(datum/gas_mixture/sample) - //Compares sample to self to see if within acceptable ranges that group processing may be enabled - //Returns: a string indicating what check failed, or "" if check passes - -/datum/gas_mixture/proc/react(turf/open/dump_location) - //Performs various reactions such as combustion or fusion (LOL) - //Returns: 1 if any reaction took place; 0 otherwise - -/datum/gas_mixture/proc/__remove() -/datum/gas_mixture/remove(amount) - var/datum/gas_mixture/removed = new type - __remove(removed, amount) - - return removed - -/datum/gas_mixture/proc/__remove_ratio() -/datum/gas_mixture/remove_ratio(ratio) - var/datum/gas_mixture/removed = new type - __remove_ratio(removed, ratio) - - return removed - -/datum/gas_mixture/copy() - var/datum/gas_mixture/copy = new type - copy.copy_from(src) - - return copy - -/datum/gas_mixture/copy_from_turf(turf/model) - parse_gas_string(model.initial_gas_mix) - - //acounts for changes in temperature - var/turf/model_parent = model.parent_type - if(model.temperature != initial(model.temperature) || model.temperature != initial(model_parent.temperature)) - set_temperature(model.temperature) - - return 1 - - ///Copies variables from a particularly formatted string. - ///Returns: 1 if we are mutable, 0 otherwise -/datum/gas_mixture/proc/parse_gas_string(gas_string) - gas_string = SSair.preprocess_gas_string(gas_string) - - var/list/gas = params2list(gas_string) - if(gas["TEMP"]) - set_temperature(text2num(gas["TEMP"])) - gas -= "TEMP" - clear() - for(var/id in gas) - var/path = id - if(!ispath(path)) - path = gas_id2path(path) //a lot of these strings can't have embedded expressions (especially for mappers), so support for IDs needs to stick around - set_moles(path, text2num(gas[id])) - return 1 - -/datum/gas_mixture/react(datum/holder) - . = NO_REACTION - var/list/reactions = list() - for(var/I in get_gases()) - reactions += SSair.gas_reactions[I] - if(!length(reactions)) - return - reaction_results = new - var/temp = return_temperature() - var/ener = thermal_energy() - - reaction_loop: - for(var/r in reactions) - var/datum/gas_reaction/reaction = r - - var/list/min_reqs = reaction.min_requirements - if((min_reqs["TEMP"] && temp < min_reqs["TEMP"]) \ - || (min_reqs["ENER"] && ener < min_reqs["ENER"])) - continue - - for(var/id in min_reqs) - if (id == "TEMP" || id == "ENER") - continue - if(get_moles(id) < min_reqs[id]) - continue reaction_loop - - //at this point, all requirements for the reaction are satisfied. we can now react() - - . |= reaction.react(src, holder) - if (. & STOP_REACTIONS) - break - -///Distributes the contents of two mixes equally between themselves - //Returns: bool indicating whether gases moved between the two mixes -/datum/gas_mixture/proc/equalize(datum/gas_mixture/other) - . = FALSE - if(abs(return_temperature() - other.return_temperature()) > MINIMUM_TEMPERATURE_DELTA_TO_SUSPEND) - . = TRUE - var/self_heat_cap = heat_capacity() - var/other_heat_cap = other.heat_capacity() - var/new_temp = (return_temperature() * self_heat_cap + other.return_temperature() * other_heat_cap) / (self_heat_cap + other_heat_cap) - set_temperature(new_temp) - other.set_temperature(new_temp) - - var/min_p_delta = 0.1 - var/total_volume = return_volume() + other.return_volume() - var/list/gas_list = get_gases() | other.get_gases() - for(var/gas_id in gas_list) - //math is under the assumption temperatures are equal - if(abs(get_moles(gas_id) / return_volume() - other.get_moles(gas_id) / other.return_volume()) > min_p_delta / (R_IDEAL_GAS_EQUATION * return_temperature())) - . = TRUE - var/total_moles = get_moles(gas_id) + other.get_moles(gas_id) - set_moles(gas_id, total_moles * (return_volume()/total_volume)) - other.set_moles(gas_id, total_moles * (other.return_volume()/total_volume)) - -///Takes the amount of the gas you want to PP as an argument -///So I don't have to do some hacky switches/defines/magic strings -///eg: -///Tox_PP = get_partial_pressure(gas_mixture.toxins) -///O2_PP = get_partial_pressure(gas_mixture.oxygen) - -/datum/gas_mixture/proc/get_breath_partial_pressure(gas_pressure) - return (gas_pressure * R_IDEAL_GAS_EQUATION * return_temperature()) / BREATH_VOLUME -//inverse -/datum/gas_mixture/proc/get_true_breath_pressure(partial_pressure) - return (partial_pressure * BREATH_VOLUME) / (R_IDEAL_GAS_EQUATION * return_temperature()) - -///Mathematical proofs: -/** -get_breath_partial_pressure(gas_pp) --> gas_pp/total_moles()*breath_pp = pp -get_true_breath_pressure(pp) --> gas_pp = pp/breath_pp*total_moles() - -10/20*5 = 2.5 -10 = 2.5/5*20 -*/ - -/datum/gas_mixture/turf + /* +What are the archived variables for? + Calculations are done using the archived variables with the results merged into the regular variables. + This prevents race conditions that arise based on the order of tile processing. +*/ +#define MINIMUM_HEAT_CAPACITY 0.0003 +#define MINIMUM_MOLE_COUNT 0.01 +#define MOLAR_ACCURACY 1E-7 +#define QUANTIZE(variable) (round((variable), (MOLAR_ACCURACY)))/*I feel the need to document what happens here. Basically this is used + to catch most rounding errors, however its previous value made it so that + once gases got hot enough, most procedures wouldn't occur due to the fact that the mole + counts would get rounded away. Thus, we lowered it a few orders of magnitude + Edit: As far as I know this might have a bug caused by round(). When it has a second arg it will round up. + So for instance round(0.5, 1) == 1. Trouble is I haven't found any instances of it causing a bug, + and any attempts to fix it just killed atmos. I leave this to a greater man then I*/ +GLOBAL_LIST_INIT(meta_gas_info, meta_gas_list()) //see ATMOSPHERICS/gas_types.dm +GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) + +/proc/init_gaslist_cache() + . = list() + for(var/id in GLOB.meta_gas_info) + var/list/cached_gas = new(3) + + .[id] = cached_gas + + cached_gas[MOLES] = 0 + cached_gas[ARCHIVE] = 0 + cached_gas[GAS_META] = GLOB.meta_gas_info[id] + +/datum/gas_mixture + var/initial_volume = CELL_VOLUME //liters + var/list/reaction_results + var/list/analyzer_results //used for analyzer feedback - not initialized until its used + var/_extools_pointer_gasmixture = 0 // Contains the memory address of the shared_ptr object for this gas mixture in c++ land. Don't. Touch. This. Var. + +/datum/gas_mixture/New(volume) + if (!isnull(volume)) + initial_volume = volume + ATMOS_EXTOOLS_CHECK + __gasmixture_register() + reaction_results = new + +/datum/gas_mixture/vv_edit_var(var_name, var_value) + if(var_name == "_extools_pointer_gasmixture") + return FALSE // please no. segfaults bad. + return ..() + +/datum/gas_mixture/proc/__gasmixture_unregister() +/datum/gas_mixture/proc/__gasmixture_register() + +/proc/gas_types() + var/list/L = subtypesof(/datum/gas) + for(var/gt in L) + var/datum/gas/G = gt + L[gt] = initial(G.specific_heat) + return L + +/datum/gas_mixture/proc/heat_capacity(data = MOLES) //joules per kelvin + + /// Calculate moles +/datum/gas_mixture/proc/total_moles() + +/datum/gas_mixture/proc/return_pressure() //kilopascals + +/datum/gas_mixture/proc/return_temperature() //kelvins + +/datum/gas_mixture/proc/set_min_heat_capacity(n) +/datum/gas_mixture/proc/set_temperature(new_temp) +/datum/gas_mixture/proc/set_volume(new_volume) +/datum/gas_mixture/proc/get_moles(gas_type) +/datum/gas_mixture/proc/set_moles(gas_type, moles) +/datum/gas_mixture/proc/scrub_into(datum/gas_mixture/target, list/gases) +/datum/gas_mixture/proc/mark_immutable() +/datum/gas_mixture/proc/get_gases() +/datum/gas_mixture/proc/multiply(factor) +/datum/gas_mixture/proc/get_last_share() +/datum/gas_mixture/proc/clear() + +/datum/gas_mixture/proc/adjust_moles(gas_type, amt = 0) + set_moles(gas_type, get_moles(gas_type) + amt) + +/datum/gas_mixture/proc/return_volume() //liters + +/datum/gas_mixture/proc/thermal_energy() //joules + + ///Update archived versions of variables. Returns: 1 in all cases +/datum/gas_mixture/proc/archive() + //Update archived versions of variables + //Returns: 1 in all cases + +/datum/gas_mixture/proc/merge(datum/gas_mixture/giver) + //Merges all air from giver into self. Deletes giver. + //Returns: 1 if we are mutable, 0 otherwise + +/datum/gas_mixture/proc/remove(amount) + //Proportionally removes amount of gas from the gas_mixture + //Returns: gas_mixture with the gases removed + +/datum/gas_mixture/proc/remove_ratio(ratio) + //Proportionally removes amount of gas from the gas_mixture + //Returns: gas_mixture with the gases removed + +/datum/gas_mixture/proc/copy() + //Creates new, identical gas mixture + //Returns: duplicate gas mixture + +/datum/gas_mixture/proc/copy_from(datum/gas_mixture/sample) + //Copies variables from sample + //Returns: 1 if we are mutable, 0 otherwise + +/datum/gas_mixture/proc/copy_from_turf(turf/model) + //Copies all gas info from the turf into the gas list along with temperature + //Returns: 1 if we are mutable, 0 otherwise + +/datum/gas_mixture/proc/share(datum/gas_mixture/sharer) + //Performs air sharing calculations between two gas_mixtures assuming only 1 boundary length + //Returns: amount of gas exchanged (+ if sharer received) + +/datum/gas_mixture/proc/temperature_share(datum/gas_mixture/sharer, conduction_coefficient) + //Performs temperature sharing calculations (via conduction) between two gas_mixtures assuming only 1 boundary length + //Returns: new temperature of the sharer + +/datum/gas_mixture/proc/compare(datum/gas_mixture/sample) + //Compares sample to self to see if within acceptable ranges that group processing may be enabled + //Returns: a string indicating what check failed, or "" if check passes + +/datum/gas_mixture/proc/react(turf/open/dump_location) + //Performs various reactions such as combustion or fusion (LOL) + //Returns: 1 if any reaction took place; 0 otherwise + +/datum/gas_mixture/proc/__remove() +/datum/gas_mixture/remove(amount) + var/datum/gas_mixture/removed = new type + __remove(removed, amount) + + return removed + +/datum/gas_mixture/proc/__remove_ratio() +/datum/gas_mixture/remove_ratio(ratio) + var/datum/gas_mixture/removed = new type + __remove_ratio(removed, ratio) + + return removed + +/datum/gas_mixture/copy() + var/datum/gas_mixture/copy = new type + copy.copy_from(src) + + return copy + +/datum/gas_mixture/copy_from_turf(turf/model) + parse_gas_string(model.initial_gas_mix) + + //acounts for changes in temperature + var/turf/model_parent = model.parent_type + if(model.temperature != initial(model.temperature) || model.temperature != initial(model_parent.temperature)) + set_temperature(model.temperature) + + return 1 + + ///Copies variables from a particularly formatted string. + ///Returns: 1 if we are mutable, 0 otherwise +/datum/gas_mixture/proc/parse_gas_string(gas_string) + gas_string = SSair.preprocess_gas_string(gas_string) + + var/list/gas = params2list(gas_string) + if(gas["TEMP"]) + set_temperature(text2num(gas["TEMP"])) + gas -= "TEMP" + clear() + for(var/id in gas) + var/path = id + if(!ispath(path)) + path = gas_id2path(path) //a lot of these strings can't have embedded expressions (especially for mappers), so support for IDs needs to stick around + set_moles(path, text2num(gas[id])) + return 1 + +/datum/gas_mixture/react(datum/holder) + . = NO_REACTION + var/list/reactions = list() + for(var/I in get_gases()) + reactions += SSair.gas_reactions[I] + if(!length(reactions)) + return + reaction_results = new + var/temp = return_temperature() + var/ener = thermal_energy() + + reaction_loop: + for(var/r in reactions) + var/datum/gas_reaction/reaction = r + + var/list/min_reqs = reaction.min_requirements + if((min_reqs["TEMP"] && temp < min_reqs["TEMP"]) \ + || (min_reqs["ENER"] && ener < min_reqs["ENER"])) + continue + + for(var/id in min_reqs) + if (id == "TEMP" || id == "ENER") + continue + if(get_moles(id) < min_reqs[id]) + continue reaction_loop + + //at this point, all requirements for the reaction are satisfied. we can now react() + + . |= reaction.react(src, holder) + if (. & STOP_REACTIONS) + break + +///Distributes the contents of two mixes equally between themselves + //Returns: bool indicating whether gases moved between the two mixes +/datum/gas_mixture/proc/equalize(datum/gas_mixture/other) + . = FALSE + if(abs(return_temperature() - other.return_temperature()) > MINIMUM_TEMPERATURE_DELTA_TO_SUSPEND) + . = TRUE + var/self_heat_cap = heat_capacity() + var/other_heat_cap = other.heat_capacity() + var/new_temp = (return_temperature() * self_heat_cap + other.return_temperature() * other_heat_cap) / (self_heat_cap + other_heat_cap) + set_temperature(new_temp) + other.set_temperature(new_temp) + + var/min_p_delta = 0.1 + var/total_volume = return_volume() + other.return_volume() + var/list/gas_list = get_gases() | other.get_gases() + for(var/gas_id in gas_list) + //math is under the assumption temperatures are equal + if(abs(get_moles(gas_id) / return_volume() - other.get_moles(gas_id) / other.return_volume()) > min_p_delta / (R_IDEAL_GAS_EQUATION * return_temperature())) + . = TRUE + var/total_moles = get_moles(gas_id) + other.get_moles(gas_id) + set_moles(gas_id, total_moles * (return_volume()/total_volume)) + other.set_moles(gas_id, total_moles * (other.return_volume()/total_volume)) + +///Takes the amount of the gas you want to PP as an argument +///So I don't have to do some hacky switches/defines/magic strings +///eg: +///Tox_PP = get_partial_pressure(gas_mixture.toxins) +///O2_PP = get_partial_pressure(gas_mixture.oxygen) + +/datum/gas_mixture/proc/get_breath_partial_pressure(gas_pressure) + return (gas_pressure * R_IDEAL_GAS_EQUATION * return_temperature()) / BREATH_VOLUME +//inverse +/datum/gas_mixture/proc/get_true_breath_pressure(partial_pressure) + return (partial_pressure * BREATH_VOLUME) / (R_IDEAL_GAS_EQUATION * return_temperature()) + +///Mathematical proofs: +/** +get_breath_partial_pressure(gas_pp) --> gas_pp/total_moles()*breath_pp = pp +get_true_breath_pressure(pp) --> gas_pp = pp/breath_pp*total_moles() + +10/20*5 = 2.5 +10 = 2.5/5*20 +*/ + +/datum/gas_mixture/turf diff --git a/code/modules/atmospherics/machinery/airalarm.dm b/code/modules/atmospherics/machinery/airalarm.dm index 362645b88ce1..7812dfdd1abb 100644 --- a/code/modules/atmospherics/machinery/airalarm.dm +++ b/code/modules/atmospherics/machinery/airalarm.dm @@ -71,8 +71,6 @@ integrity_failure = 0.33 armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 30) resistance_flags = FIRE_PROOF - ui_x = 440 - ui_y = 650 FASTDMM_PROP(\ set_instance_vars(\ @@ -248,11 +246,11 @@ return ..() return UI_CLOSE -/obj/machinery/airalarm/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + +/obj/machinery/airalarm/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "AirAlarm", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, "AirAlarm", name) ui.open() /obj/machinery/airalarm/ui_data(mob/user) diff --git a/code/modules/atmospherics/machinery/atmosmachinery.dm b/code/modules/atmospherics/machinery/atmosmachinery.dm index 2db31f55e672..64e6a80b7e16 100644 --- a/code/modules/atmospherics/machinery/atmosmachinery.dm +++ b/code/modules/atmospherics/machinery/atmosmachinery.dm @@ -1,341 +1,341 @@ -// Quick overview: -// -// Pipes combine to form pipelines -// Pipelines and other atmospheric objects combine to form pipe_networks -// Note: A single pipe_network represents a completely open space -// -// Pipes -> Pipelines -// Pipelines + Other Objects -> Pipe network - -#define PIPE_VISIBLE_LEVEL 2 -#define PIPE_HIDDEN_LEVEL 1 - -/obj/machinery/atmospherics - anchored = TRUE - move_resist = INFINITY //Moving a connected machine without actually doing the normal (dis)connection things will probably cause a LOT of issues. - idle_power_usage = 0 - active_power_usage = 0 - power_channel = AREA_USAGE_ENVIRON - layer = GAS_PIPE_HIDDEN_LAYER //under wires - resistance_flags = FIRE_PROOF - max_integrity = 200 - obj_flags = CAN_BE_HIT | ON_BLUEPRINTS - var/can_unwrench = 0 - var/initialize_directions = 0 - var/pipe_color - var/piping_layer = PIPING_LAYER_DEFAULT - var/pipe_flags = NONE - - ///This only works on pipes, because they have 1000 subtypes wich need to be visible and invisible under tiles, so we track this here - var/hide = TRUE - - var/static/list/iconsetids = list() - var/static/list/pipeimages = list() - - var/image/pipe_vision_img = null - - var/device_type = 0 - var/list/obj/machinery/atmospherics/nodes - - var/construction_type - var/pipe_state //icon_state as a pipe item - var/on = FALSE - -/obj/machinery/atmospherics/examine(mob/user) - . = ..() - if(is_type_in_list(src, GLOB.ventcrawl_machinery) && isliving(user)) - var/mob/living/L = user - if(L.ventcrawler) - . += "Alt-click to crawl through it." - -/obj/machinery/atmospherics/New(loc, process = TRUE, setdir) - if(!isnull(setdir)) - setDir(setdir) - if(pipe_flags & PIPING_CARDINAL_AUTONORMALIZE) - normalize_cardinal_directions() - nodes = new(device_type) - if (!armor) - armor = list("melee" = 25, "bullet" = 10, "laser" = 10, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 70) - ..() - if(process) - SSair.atmos_machinery += src - SetInitDirections() - -/obj/machinery/atmospherics/Destroy() - for(var/i in 1 to device_type) - nullifyNode(i) - - SSair.atmos_machinery -= src - SSair.pipenets_needing_rebuilt -= src - if(SSair.currentpart == SSAIR_ATMOSMACHINERY) - SSair.currentrun -= src - - dropContents() - if(pipe_vision_img) - qdel(pipe_vision_img) - - return ..() - //return QDEL_HINT_FINDREFERENCE - -/obj/machinery/atmospherics/proc/destroy_network() - return - -/obj/machinery/atmospherics/proc/build_network() - // Called to build a network from this node - return - -/obj/machinery/atmospherics/proc/nullifyNode(i) - if(nodes[i]) - var/obj/machinery/atmospherics/N = nodes[i] - N.disconnect(src) - nodes[i] = null - -/obj/machinery/atmospherics/proc/getNodeConnects() - var/list/node_connects = list() - node_connects.len = device_type - - for(var/i in 1 to device_type) - for(var/D in GLOB.cardinals) - if(D & GetInitDirections()) - if(D in node_connects) - continue - node_connects[i] = D - break - return node_connects - -/obj/machinery/atmospherics/proc/normalize_cardinal_directions() - switch(dir) - if(SOUTH) - setDir(NORTH) - if(WEST) - setDir(EAST) - -//this is called just after the air controller sets up turfs -/obj/machinery/atmospherics/proc/atmosinit(list/node_connects) - if(!node_connects) //for pipes where order of nodes doesn't matter - node_connects = getNodeConnects() - - for(var/i in 1 to device_type) - for(var/obj/machinery/atmospherics/target in get_step(src,node_connects[i])) - if(can_be_node(target, i)) - nodes[i] = target - break - update_icon() - -/obj/machinery/atmospherics/proc/setPipingLayer(new_layer) - piping_layer = (pipe_flags & PIPING_DEFAULT_LAYER_ONLY) ? PIPING_LAYER_DEFAULT : new_layer - update_icon() - -/obj/machinery/atmospherics/proc/can_be_node(obj/machinery/atmospherics/target, iteration) - return connection_check(target, piping_layer) - -//Find a connecting /obj/machinery/atmospherics in specified direction -/obj/machinery/atmospherics/proc/findConnecting(direction, prompted_layer) - for(var/obj/machinery/atmospherics/target in get_step(src, direction)) - if(target.initialize_directions & get_dir(target,src)) - if(connection_check(target, prompted_layer)) - return target - -/obj/machinery/atmospherics/proc/connection_check(obj/machinery/atmospherics/target, given_layer) - if(isConnectable(target, given_layer) && target.isConnectable(src, given_layer) && (target.initialize_directions & get_dir(target,src))) - return TRUE - return FALSE - -/obj/machinery/atmospherics/proc/isConnectable(obj/machinery/atmospherics/target, given_layer) - if(isnull(given_layer)) - given_layer = piping_layer - if((target.piping_layer == given_layer) || (target.pipe_flags & PIPING_ALL_LAYER)) - return TRUE - return FALSE - -/obj/machinery/atmospherics/proc/pipeline_expansion() - return nodes - -/obj/machinery/atmospherics/proc/SetInitDirections() - return - -/obj/machinery/atmospherics/proc/GetInitDirections() - return initialize_directions - -/obj/machinery/atmospherics/proc/returnPipenet() - return - -/obj/machinery/atmospherics/proc/returnPipenetAir() - return - -/obj/machinery/atmospherics/proc/setPipenet() - return - -/obj/machinery/atmospherics/proc/replacePipenet() - return - -/obj/machinery/atmospherics/proc/disconnect(obj/machinery/atmospherics/reference) - if(istype(reference, /obj/machinery/atmospherics/pipe)) - var/obj/machinery/atmospherics/pipe/P = reference - P.destroy_network() - nodes[nodes.Find(reference)] = null - update_icon() - -/obj/machinery/atmospherics/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/pipe)) //lets you autodrop - var/obj/item/pipe/pipe = W - if(user.dropItemToGround(pipe)) - pipe.setPipingLayer(piping_layer) //align it with us - return TRUE - else - return ..() - -/obj/machinery/atmospherics/wrench_act(mob/living/user, obj/item/I) - if(!can_unwrench(user)) - return ..() - - var/datum/gas_mixture/int_air = return_air() - var/datum/gas_mixture/env_air = loc.return_air() - add_fingerprint(user) - - var/unsafe_wrenching = FALSE - var/internal_pressure = int_air.return_pressure()-env_air.return_pressure() - - to_chat(user, "You begin to unfasten \the [src]...") - - if (internal_pressure > 2*ONE_ATMOSPHERE) - to_chat(user, "As you begin unwrenching \the [src] a gush of air blows in your face... maybe you should reconsider?") - unsafe_wrenching = TRUE //Oh dear oh dear - - if(I.use_tool(src, user, 20, volume=50)) - user.visible_message( \ - "[user] unfastens \the [src].", \ - "You unfasten \the [src].", \ - "You hear ratchet.") - investigate_log("was REMOVED by [key_name(usr)]", INVESTIGATE_ATMOS) - - //You unwrenched a pipe full of pressure? Let's splat you into the wall, silly. - if(unsafe_wrenching) - unsafe_pressure_release(user, internal_pressure) - deconstruct(TRUE) - return TRUE - -/obj/machinery/atmospherics/proc/can_unwrench(mob/user) - return can_unwrench - -// Throws the user when they unwrench a pipe with a major difference between the internal and environmental pressure. -/obj/machinery/atmospherics/proc/unsafe_pressure_release(mob/user, pressures = null) - if(!user) - return - if(!pressures) - var/datum/gas_mixture/int_air = return_air() - var/datum/gas_mixture/env_air = loc.return_air() - pressures = int_air.return_pressure() - env_air.return_pressure() - - user.visible_message("[user] is sent flying by pressure!","The pressure sends you flying!") - - // if get_dir(src, user) is not 0, target is the edge_target_turf on that dir - // otherwise, edge_target_turf uses a random cardinal direction - // range is pressures / 250 - // speed is pressures / 1250 - user.throw_at(get_edge_target_turf(user, get_dir(src, user) || pick(GLOB.cardinals)), pressures / 250, pressures / 1250) - -/obj/machinery/atmospherics/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - if(can_unwrench) - var/obj/item/pipe/stored = new construction_type(loc, null, dir, src) - stored.setPipingLayer(piping_layer) - if(!disassembled) - stored.obj_integrity = stored.max_integrity * 0.5 - transfer_fingerprints_to(stored) - ..() - -/obj/machinery/atmospherics/proc/getpipeimage(iconset, iconstate, direction, col=rgb(255,255,255), piping_layer=2) - - //Add identifiers for the iconset - if(iconsetids[iconset] == null) - iconsetids[iconset] = num2text(iconsetids.len + 1) - - //Generate a unique identifier for this image combination - var/identifier = iconsetids[iconset] + "_[iconstate]_[direction]_[col]_[piping_layer]" - - if((!(. = pipeimages[identifier]))) - var/image/pipe_overlay - pipe_overlay = . = pipeimages[identifier] = image(iconset, iconstate, dir = direction) - pipe_overlay.color = col - PIPING_LAYER_SHIFT(pipe_overlay, piping_layer) - -/obj/machinery/atmospherics/on_construction(obj_color, set_layer) - if(can_unwrench) - add_atom_colour(obj_color, FIXED_COLOUR_PRIORITY) - pipe_color = obj_color - setPipingLayer(set_layer) - atmosinit() - var/list/nodes = pipeline_expansion() - for(var/obj/machinery/atmospherics/A in nodes) - A.atmosinit() - A.addMember(src) - build_network() - -/obj/machinery/atmospherics/Entered(atom/movable/AM) - if(istype(AM, /mob/living)) - var/mob/living/L = AM - L.ventcrawl_layer = piping_layer - return ..() - -/obj/machinery/atmospherics/singularity_pull(S, current_size) - if(current_size >= STAGE_FIVE) - deconstruct(FALSE) - return ..() - -#define VENT_SOUND_DELAY 30 - -/obj/machinery/atmospherics/relaymove(mob/living/user, direction) - direction &= initialize_directions - if(!direction || !(direction in GLOB.cardinals)) //cant go this way. - return - - if(user in buckled_mobs)// fixes buckle ventcrawl edgecase fuck bug - return - - var/obj/machinery/atmospherics/target_move = findConnecting(direction, user.ventcrawl_layer) - if(target_move) - if(target_move.can_crawl_through()) - if(is_type_in_typecache(target_move, GLOB.ventcrawl_machinery)) - user.forceMove(target_move.loc) //handle entering and so on. - user.visible_message("You hear something squeezing through the ducts...", "You climb out the ventilation system.") - else - var/list/pipenetdiff = returnPipenets() ^ target_move.returnPipenets() - if(pipenetdiff.len) - user.update_pipe_vision(target_move) - user.forceMove(target_move) - user.client.eye = target_move //Byond only updates the eye every tick, This smooths out the movement - if(world.time - user.last_played_vent > VENT_SOUND_DELAY) - user.last_played_vent = world.time - playsound(src, 'sound/machines/ventcrawl.ogg', 50, TRUE, -3) - else if(is_type_in_typecache(src, GLOB.ventcrawl_machinery) && can_crawl_through()) //if we move in a way the pipe can connect, but doesn't - or we're in a vent - user.forceMove(loc) - user.visible_message("You hear something squeezing through the ducts...", "You climb out the ventilation system.") - - //PLACEHOLDER COMMENT FOR ME TO READD THE 1 (?) DS DELAY THAT WAS IMPLEMENTED WITH A... TIMER? - -/obj/machinery/atmospherics/AltClick(mob/living/L) - if(istype(L) && is_type_in_list(src, GLOB.ventcrawl_machinery)) - L.handle_ventcrawl(src) - return - ..() - - -/obj/machinery/atmospherics/proc/can_crawl_through() - return TRUE - -/obj/machinery/atmospherics/proc/returnPipenets() - return list() - -/obj/machinery/atmospherics/update_remote_sight(mob/user) - if(isborer(user)) //Wasp Begin - Borers - user.sight |= (SEE_PIXELS) - else - user.sight |= (SEE_TURFS|BLIND) //Wasp End - -//Used for certain children of obj/machinery/atmospherics to not show pipe vision when mob is inside it. -/obj/machinery/atmospherics/proc/can_see_pipes() - return TRUE - -/obj/machinery/atmospherics/proc/update_layer() - layer = initial(layer) + (piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_LCHANGE +// Quick overview: +// +// Pipes combine to form pipelines +// Pipelines and other atmospheric objects combine to form pipe_networks +// Note: A single pipe_network represents a completely open space +// +// Pipes -> Pipelines +// Pipelines + Other Objects -> Pipe network + +#define PIPE_VISIBLE_LEVEL 2 +#define PIPE_HIDDEN_LEVEL 1 + +/obj/machinery/atmospherics + anchored = TRUE + move_resist = INFINITY //Moving a connected machine without actually doing the normal (dis)connection things will probably cause a LOT of issues. + idle_power_usage = 0 + active_power_usage = 0 + power_channel = AREA_USAGE_ENVIRON + layer = GAS_PIPE_HIDDEN_LAYER //under wires + resistance_flags = FIRE_PROOF + max_integrity = 200 + obj_flags = CAN_BE_HIT | ON_BLUEPRINTS + var/can_unwrench = 0 + var/initialize_directions = 0 + var/pipe_color + var/piping_layer = PIPING_LAYER_DEFAULT + var/pipe_flags = NONE + + ///This only works on pipes, because they have 1000 subtypes wich need to be visible and invisible under tiles, so we track this here + var/hide = TRUE + + var/static/list/iconsetids = list() + var/static/list/pipeimages = list() + + var/image/pipe_vision_img = null + + var/device_type = 0 + var/list/obj/machinery/atmospherics/nodes + + var/construction_type + var/pipe_state //icon_state as a pipe item + var/on = FALSE + +/obj/machinery/atmospherics/examine(mob/user) + . = ..() + if(is_type_in_list(src, GLOB.ventcrawl_machinery) && isliving(user)) + var/mob/living/L = user + if(L.ventcrawler) + . += "Alt-click to crawl through it." + +/obj/machinery/atmospherics/New(loc, process = TRUE, setdir) + if(!isnull(setdir)) + setDir(setdir) + if(pipe_flags & PIPING_CARDINAL_AUTONORMALIZE) + normalize_cardinal_directions() + nodes = new(device_type) + if (!armor) + armor = list("melee" = 25, "bullet" = 10, "laser" = 10, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 70) + ..() + if(process) + SSair.atmos_machinery += src + SetInitDirections() + +/obj/machinery/atmospherics/Destroy() + for(var/i in 1 to device_type) + nullifyNode(i) + + SSair.atmos_machinery -= src + SSair.pipenets_needing_rebuilt -= src + if(SSair.currentpart == SSAIR_ATMOSMACHINERY) + SSair.currentrun -= src + + dropContents() + if(pipe_vision_img) + qdel(pipe_vision_img) + + return ..() + //return QDEL_HINT_FINDREFERENCE + +/obj/machinery/atmospherics/proc/destroy_network() + return + +/obj/machinery/atmospherics/proc/build_network() + // Called to build a network from this node + return + +/obj/machinery/atmospherics/proc/nullifyNode(i) + if(nodes[i]) + var/obj/machinery/atmospherics/N = nodes[i] + N.disconnect(src) + nodes[i] = null + +/obj/machinery/atmospherics/proc/getNodeConnects() + var/list/node_connects = list() + node_connects.len = device_type + + for(var/i in 1 to device_type) + for(var/D in GLOB.cardinals) + if(D & GetInitDirections()) + if(D in node_connects) + continue + node_connects[i] = D + break + return node_connects + +/obj/machinery/atmospherics/proc/normalize_cardinal_directions() + switch(dir) + if(SOUTH) + setDir(NORTH) + if(WEST) + setDir(EAST) + +//this is called just after the air controller sets up turfs +/obj/machinery/atmospherics/proc/atmosinit(list/node_connects) + if(!node_connects) //for pipes where order of nodes doesn't matter + node_connects = getNodeConnects() + + for(var/i in 1 to device_type) + for(var/obj/machinery/atmospherics/target in get_step(src,node_connects[i])) + if(can_be_node(target, i)) + nodes[i] = target + break + update_icon() + +/obj/machinery/atmospherics/proc/setPipingLayer(new_layer) + piping_layer = (pipe_flags & PIPING_DEFAULT_LAYER_ONLY) ? PIPING_LAYER_DEFAULT : new_layer + update_icon() + +/obj/machinery/atmospherics/proc/can_be_node(obj/machinery/atmospherics/target, iteration) + return connection_check(target, piping_layer) + +//Find a connecting /obj/machinery/atmospherics in specified direction +/obj/machinery/atmospherics/proc/findConnecting(direction, prompted_layer) + for(var/obj/machinery/atmospherics/target in get_step(src, direction)) + if(target.initialize_directions & get_dir(target,src)) + if(connection_check(target, prompted_layer)) + return target + +/obj/machinery/atmospherics/proc/connection_check(obj/machinery/atmospherics/target, given_layer) + if(isConnectable(target, given_layer) && target.isConnectable(src, given_layer) && (target.initialize_directions & get_dir(target,src))) + return TRUE + return FALSE + +/obj/machinery/atmospherics/proc/isConnectable(obj/machinery/atmospherics/target, given_layer) + if(isnull(given_layer)) + given_layer = piping_layer + if((target.piping_layer == given_layer) || (target.pipe_flags & PIPING_ALL_LAYER)) + return TRUE + return FALSE + +/obj/machinery/atmospherics/proc/pipeline_expansion() + return nodes + +/obj/machinery/atmospherics/proc/SetInitDirections() + return + +/obj/machinery/atmospherics/proc/GetInitDirections() + return initialize_directions + +/obj/machinery/atmospherics/proc/returnPipenet() + return + +/obj/machinery/atmospherics/proc/returnPipenetAir() + return + +/obj/machinery/atmospherics/proc/setPipenet() + return + +/obj/machinery/atmospherics/proc/replacePipenet() + return + +/obj/machinery/atmospherics/proc/disconnect(obj/machinery/atmospherics/reference) + if(istype(reference, /obj/machinery/atmospherics/pipe)) + var/obj/machinery/atmospherics/pipe/P = reference + P.destroy_network() + nodes[nodes.Find(reference)] = null + update_icon() + +/obj/machinery/atmospherics/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/pipe)) //lets you autodrop + var/obj/item/pipe/pipe = W + if(user.dropItemToGround(pipe)) + pipe.setPipingLayer(piping_layer) //align it with us + return TRUE + else + return ..() + +/obj/machinery/atmospherics/wrench_act(mob/living/user, obj/item/I) + if(!can_unwrench(user)) + return ..() + + var/datum/gas_mixture/int_air = return_air() + var/datum/gas_mixture/env_air = loc.return_air() + add_fingerprint(user) + + var/unsafe_wrenching = FALSE + var/internal_pressure = int_air.return_pressure()-env_air.return_pressure() + + to_chat(user, "You begin to unfasten \the [src]...") + + if (internal_pressure > 2*ONE_ATMOSPHERE) + to_chat(user, "As you begin unwrenching \the [src] a gush of air blows in your face... maybe you should reconsider?") + unsafe_wrenching = TRUE //Oh dear oh dear + + if(I.use_tool(src, user, 20, volume=50)) + user.visible_message( \ + "[user] unfastens \the [src].", \ + "You unfasten \the [src].", \ + "You hear ratchet.") + investigate_log("was REMOVED by [key_name(usr)]", INVESTIGATE_ATMOS) + + //You unwrenched a pipe full of pressure? Let's splat you into the wall, silly. + if(unsafe_wrenching) + unsafe_pressure_release(user, internal_pressure) + deconstruct(TRUE) + return TRUE + +/obj/machinery/atmospherics/proc/can_unwrench(mob/user) + return can_unwrench + +// Throws the user when they unwrench a pipe with a major difference between the internal and environmental pressure. +/obj/machinery/atmospherics/proc/unsafe_pressure_release(mob/user, pressures = null) + if(!user) + return + if(!pressures) + var/datum/gas_mixture/int_air = return_air() + var/datum/gas_mixture/env_air = loc.return_air() + pressures = int_air.return_pressure() - env_air.return_pressure() + + user.visible_message("[user] is sent flying by pressure!","The pressure sends you flying!") + + // if get_dir(src, user) is not 0, target is the edge_target_turf on that dir + // otherwise, edge_target_turf uses a random cardinal direction + // range is pressures / 250 + // speed is pressures / 1250 + user.throw_at(get_edge_target_turf(user, get_dir(src, user) || pick(GLOB.cardinals)), pressures / 250, pressures / 1250) + +/obj/machinery/atmospherics/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + if(can_unwrench) + var/obj/item/pipe/stored = new construction_type(loc, null, dir, src) + stored.setPipingLayer(piping_layer) + if(!disassembled) + stored.obj_integrity = stored.max_integrity * 0.5 + transfer_fingerprints_to(stored) + ..() + +/obj/machinery/atmospherics/proc/getpipeimage(iconset, iconstate, direction, col=rgb(255,255,255), piping_layer=2) + + //Add identifiers for the iconset + if(iconsetids[iconset] == null) + iconsetids[iconset] = num2text(iconsetids.len + 1) + + //Generate a unique identifier for this image combination + var/identifier = iconsetids[iconset] + "_[iconstate]_[direction]_[col]_[piping_layer]" + + if((!(. = pipeimages[identifier]))) + var/image/pipe_overlay + pipe_overlay = . = pipeimages[identifier] = image(iconset, iconstate, dir = direction) + pipe_overlay.color = col + PIPING_LAYER_SHIFT(pipe_overlay, piping_layer) + +/obj/machinery/atmospherics/on_construction(obj_color, set_layer) + if(can_unwrench) + add_atom_colour(obj_color, FIXED_COLOUR_PRIORITY) + pipe_color = obj_color + setPipingLayer(set_layer) + atmosinit() + var/list/nodes = pipeline_expansion() + for(var/obj/machinery/atmospherics/A in nodes) + A.atmosinit() + A.addMember(src) + build_network() + +/obj/machinery/atmospherics/Entered(atom/movable/AM) + if(istype(AM, /mob/living)) + var/mob/living/L = AM + L.ventcrawl_layer = piping_layer + return ..() + +/obj/machinery/atmospherics/singularity_pull(S, current_size) + if(current_size >= STAGE_FIVE) + deconstruct(FALSE) + return ..() + +#define VENT_SOUND_DELAY 30 + +/obj/machinery/atmospherics/relaymove(mob/living/user, direction) + direction &= initialize_directions + if(!direction || !(direction in GLOB.cardinals)) //cant go this way. + return + + if(user in buckled_mobs)// fixes buckle ventcrawl edgecase fuck bug + return + + var/obj/machinery/atmospherics/target_move = findConnecting(direction, user.ventcrawl_layer) + if(target_move) + if(target_move.can_crawl_through()) + if(is_type_in_typecache(target_move, GLOB.ventcrawl_machinery)) + user.forceMove(target_move.loc) //handle entering and so on. + user.visible_message("You hear something squeezing through the ducts...", "You climb out the ventilation system.") + else + var/list/pipenetdiff = returnPipenets() ^ target_move.returnPipenets() + if(pipenetdiff.len) + user.update_pipe_vision(target_move) + user.forceMove(target_move) + user.client.eye = target_move //Byond only updates the eye every tick, This smooths out the movement + if(world.time - user.last_played_vent > VENT_SOUND_DELAY) + user.last_played_vent = world.time + playsound(src, 'sound/machines/ventcrawl.ogg', 50, TRUE, -3) + else if(is_type_in_typecache(src, GLOB.ventcrawl_machinery) && can_crawl_through()) //if we move in a way the pipe can connect, but doesn't - or we're in a vent + user.forceMove(loc) + user.visible_message("You hear something squeezing through the ducts...", "You climb out the ventilation system.") + + //PLACEHOLDER COMMENT FOR ME TO READD THE 1 (?) DS DELAY THAT WAS IMPLEMENTED WITH A... TIMER? + +/obj/machinery/atmospherics/AltClick(mob/living/L) + if(istype(L) && is_type_in_list(src, GLOB.ventcrawl_machinery)) + L.handle_ventcrawl(src) + return + ..() + + +/obj/machinery/atmospherics/proc/can_crawl_through() + return TRUE + +/obj/machinery/atmospherics/proc/returnPipenets() + return list() + +/obj/machinery/atmospherics/update_remote_sight(mob/user) + if(isborer(user)) //Wasp Begin - Borers + user.sight |= (SEE_PIXELS) + else + user.sight |= (SEE_TURFS|BLIND) //Wasp End + +//Used for certain children of obj/machinery/atmospherics to not show pipe vision when mob is inside it. +/obj/machinery/atmospherics/proc/can_see_pipes() + return TRUE + +/obj/machinery/atmospherics/proc/update_layer() + layer = initial(layer) + (piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_LCHANGE diff --git a/code/modules/atmospherics/machinery/components/binary_devices/binary_devices.dm b/code/modules/atmospherics/machinery/components/binary_devices/binary_devices.dm index c4b14274e03f..fe50cfdce73a 100644 --- a/code/modules/atmospherics/machinery/components/binary_devices/binary_devices.dm +++ b/code/modules/atmospherics/machinery/components/binary_devices/binary_devices.dm @@ -1,17 +1,17 @@ -/obj/machinery/atmospherics/components/binary - icon = 'icons/obj/atmospherics/components/binary_devices.dmi' - dir = SOUTH - initialize_directions = SOUTH|NORTH - use_power = IDLE_POWER_USE - device_type = BINARY - layer = GAS_PUMP_LAYER - -/obj/machinery/atmospherics/components/binary/SetInitDirections() - switch(dir) - if(NORTH, SOUTH) - initialize_directions = NORTH|SOUTH - if(EAST, WEST) - initialize_directions = EAST|WEST - -/obj/machinery/atmospherics/components/binary/getNodeConnects() - return list(turn(dir, 180), dir) +/obj/machinery/atmospherics/components/binary + icon = 'icons/obj/atmospherics/components/binary_devices.dmi' + dir = SOUTH + initialize_directions = SOUTH|NORTH + use_power = IDLE_POWER_USE + device_type = BINARY + layer = GAS_PUMP_LAYER + +/obj/machinery/atmospherics/components/binary/SetInitDirections() + switch(dir) + if(NORTH, SOUTH) + initialize_directions = NORTH|SOUTH + if(EAST, WEST) + initialize_directions = EAST|WEST + +/obj/machinery/atmospherics/components/binary/getNodeConnects() + return list(turn(dir, 180), dir) diff --git a/code/modules/atmospherics/machinery/components/binary_devices/circulator.dm b/code/modules/atmospherics/machinery/components/binary_devices/circulator.dm index 1fc437ced7b2..4e0c7c1eff03 100644 --- a/code/modules/atmospherics/machinery/components/binary_devices/circulator.dm +++ b/code/modules/atmospherics/machinery/components/binary_devices/circulator.dm @@ -1,190 +1,190 @@ -//node2, air2, network2 correspond to input -//node1, air1, network1 correspond to output -#define CIRCULATOR_HOT 0 -#define CIRCULATOR_COLD 1 - -/obj/machinery/atmospherics/components/binary/circulator - name = "circulator/heat exchanger" - desc = "A gas circulator pump and heat exchanger." - icon_state = "circ-off-0" - - var/active = FALSE - - var/last_pressure_delta = 0 - pipe_flags = PIPING_ONE_PER_TURF | PIPING_DEFAULT_LAYER_ONLY - - density = TRUE - - - var/flipped = 0 - var/mode = CIRCULATOR_HOT - var/obj/machinery/power/generator/generator - -//default cold circ for mappers -/obj/machinery/atmospherics/components/binary/circulator/cold - mode = CIRCULATOR_COLD - -/obj/machinery/atmospherics/components/binary/circulator/Initialize(mapload) - .=..() - component_parts = list(new /obj/item/circuitboard/machine/circulator) - -/obj/machinery/atmospherics/components/binary/circulator/ComponentInitialize() - . = ..() - AddComponent(/datum/component/simple_rotation,ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS ) - -/obj/machinery/atmospherics/components/binary/circulator/Destroy() - if(generator) - disconnectFromGenerator() - return ..() - -/obj/machinery/atmospherics/components/binary/circulator/proc/return_transfer_air() - - var/datum/gas_mixture/air1 = airs[1] - var/datum/gas_mixture/air2 = airs[2] - - var/output_starting_pressure = air1.return_pressure() - var/input_starting_pressure = air2.return_pressure() - - if(output_starting_pressure >= input_starting_pressure-10) - //Need at least 10 KPa difference to overcome friction in the mechanism - last_pressure_delta = 0 - return null - - //Calculate necessary moles to transfer using PV = nRT - if(air2.return_temperature()>0) - var/pressure_delta = (input_starting_pressure - output_starting_pressure)/2 - - var/transfer_moles = pressure_delta*air1.return_volume()/(air2.return_temperature() * R_IDEAL_GAS_EQUATION) - - last_pressure_delta = pressure_delta - - //Actually transfer the gas - var/datum/gas_mixture/removed = air2.remove(transfer_moles) - - update_parents() - - return removed - - else - last_pressure_delta = 0 - -/obj/machinery/atmospherics/components/binary/circulator/process_atmos() - ..() - update_icon() - -/obj/machinery/atmospherics/components/binary/circulator/update_icon() - if(!is_operational()) - icon_state = "circ-p-[flipped]" - else if(last_pressure_delta > 0) - if(last_pressure_delta > ONE_ATMOSPHERE) - icon_state = "circ-run-[flipped]" - else - icon_state = "circ-slow-[flipped]" - else - icon_state = "circ-off-[flipped]" - -/obj/machinery/atmospherics/components/binary/circulator/wrench_act(mob/living/user, obj/item/I) - if(!panel_open) - return - anchored = !anchored - I.play_tool_sound(src) - if(generator) - disconnectFromGenerator() - to_chat(user, "You [anchored?"secure":"unsecure"] [src].") - - - var/obj/machinery/atmospherics/node1 = nodes[1] - var/obj/machinery/atmospherics/node2 = nodes[2] - - if(node1) - node1.disconnect(src) - nodes[1] = null - nullifyPipenet(parents[1]) - if(node2) - node2.disconnect(src) - nodes[2] = null - nullifyPipenet(parents[2]) - - if(anchored) - SetInitDirections() - atmosinit() - node1 = nodes[1] - if(node1) - node1.atmosinit() - node1.addMember(src) - node2 = nodes[2] - if(node2) - node2.atmosinit() - node2.addMember(src) - SSair.add_to_rebuild_queue(src) - - return TRUE - -/obj/machinery/atmospherics/components/binary/circulator/SetInitDirections() - switch(dir) - if(NORTH, SOUTH) - initialize_directions = EAST|WEST - if(EAST, WEST) - initialize_directions = NORTH|SOUTH - -/obj/machinery/atmospherics/components/binary/circulator/getNodeConnects() - if(flipped) - return list(turn(dir, 270), turn(dir, 90)) - return list(turn(dir, 90), turn(dir, 270)) - -/obj/machinery/atmospherics/components/binary/circulator/can_be_node(obj/machinery/atmospherics/target) - if(anchored) - return ..(target) - return FALSE - -/obj/machinery/atmospherics/components/binary/circulator/multitool_act(mob/living/user, obj/item/I) - if(generator) - disconnectFromGenerator() - mode = !mode - to_chat(user, "You set [src] to [mode?"cold":"hot"] mode.") - return TRUE - -/obj/machinery/atmospherics/components/binary/circulator/screwdriver_act(mob/user, obj/item/I) - if(..()) - return TRUE - panel_open = !panel_open - I.play_tool_sound(src) - to_chat(user, "You [panel_open?"open":"close"] the panel on [src].") - return TRUE - -/obj/machinery/atmospherics/components/binary/circulator/crowbar_act(mob/user, obj/item/I) - default_deconstruction_crowbar(I) - return TRUE - -/obj/machinery/atmospherics/components/binary/circulator/on_deconstruction() - if(generator) - disconnectFromGenerator() - -/obj/machinery/atmospherics/components/binary/circulator/proc/disconnectFromGenerator() - if(mode) - generator.cold_circ = null - else - generator.hot_circ = null - generator.update_icon() - generator = null - -/obj/machinery/atmospherics/components/binary/circulator/setPipingLayer(new_layer) - ..() - pixel_x = 0 - pixel_y = 0 - -/obj/machinery/atmospherics/components/binary/circulator/verb/circulator_flip() - set name = "Flip" - set category = "Object" - set src in oview(1) - - if(!ishuman(usr)) - return - - if(anchored) - to_chat(usr, "[src] is anchored!") - return - - flipped = !flipped - to_chat(usr, "You flip [src].") - update_icon() +//node2, air2, network2 correspond to input +//node1, air1, network1 correspond to output +#define CIRCULATOR_HOT 0 +#define CIRCULATOR_COLD 1 + +/obj/machinery/atmospherics/components/binary/circulator + name = "circulator/heat exchanger" + desc = "A gas circulator pump and heat exchanger." + icon_state = "circ-off-0" + + var/active = FALSE + + var/last_pressure_delta = 0 + pipe_flags = PIPING_ONE_PER_TURF | PIPING_DEFAULT_LAYER_ONLY + + density = TRUE + + + var/flipped = 0 + var/mode = CIRCULATOR_HOT + var/obj/machinery/power/generator/generator + +//default cold circ for mappers +/obj/machinery/atmospherics/components/binary/circulator/cold + mode = CIRCULATOR_COLD + +/obj/machinery/atmospherics/components/binary/circulator/Initialize(mapload) + .=..() + component_parts = list(new /obj/item/circuitboard/machine/circulator) + +/obj/machinery/atmospherics/components/binary/circulator/ComponentInitialize() + . = ..() + AddComponent(/datum/component/simple_rotation,ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS ) + +/obj/machinery/atmospherics/components/binary/circulator/Destroy() + if(generator) + disconnectFromGenerator() + return ..() + +/obj/machinery/atmospherics/components/binary/circulator/proc/return_transfer_air() + + var/datum/gas_mixture/air1 = airs[1] + var/datum/gas_mixture/air2 = airs[2] + + var/output_starting_pressure = air1.return_pressure() + var/input_starting_pressure = air2.return_pressure() + + if(output_starting_pressure >= input_starting_pressure-10) + //Need at least 10 KPa difference to overcome friction in the mechanism + last_pressure_delta = 0 + return null + + //Calculate necessary moles to transfer using PV = nRT + if(air2.return_temperature()>0) + var/pressure_delta = (input_starting_pressure - output_starting_pressure)/2 + + var/transfer_moles = pressure_delta*air1.return_volume()/(air2.return_temperature() * R_IDEAL_GAS_EQUATION) + + last_pressure_delta = pressure_delta + + //Actually transfer the gas + var/datum/gas_mixture/removed = air2.remove(transfer_moles) + + update_parents() + + return removed + + else + last_pressure_delta = 0 + +/obj/machinery/atmospherics/components/binary/circulator/process_atmos() + ..() + update_icon() + +/obj/machinery/atmospherics/components/binary/circulator/update_icon() + if(!is_operational()) + icon_state = "circ-p-[flipped]" + else if(last_pressure_delta > 0) + if(last_pressure_delta > ONE_ATMOSPHERE) + icon_state = "circ-run-[flipped]" + else + icon_state = "circ-slow-[flipped]" + else + icon_state = "circ-off-[flipped]" + +/obj/machinery/atmospherics/components/binary/circulator/wrench_act(mob/living/user, obj/item/I) + if(!panel_open) + return + anchored = !anchored + I.play_tool_sound(src) + if(generator) + disconnectFromGenerator() + to_chat(user, "You [anchored?"secure":"unsecure"] [src].") + + + var/obj/machinery/atmospherics/node1 = nodes[1] + var/obj/machinery/atmospherics/node2 = nodes[2] + + if(node1) + node1.disconnect(src) + nodes[1] = null + nullifyPipenet(parents[1]) + if(node2) + node2.disconnect(src) + nodes[2] = null + nullifyPipenet(parents[2]) + + if(anchored) + SetInitDirections() + atmosinit() + node1 = nodes[1] + if(node1) + node1.atmosinit() + node1.addMember(src) + node2 = nodes[2] + if(node2) + node2.atmosinit() + node2.addMember(src) + SSair.add_to_rebuild_queue(src) + + return TRUE + +/obj/machinery/atmospherics/components/binary/circulator/SetInitDirections() + switch(dir) + if(NORTH, SOUTH) + initialize_directions = EAST|WEST + if(EAST, WEST) + initialize_directions = NORTH|SOUTH + +/obj/machinery/atmospherics/components/binary/circulator/getNodeConnects() + if(flipped) + return list(turn(dir, 270), turn(dir, 90)) + return list(turn(dir, 90), turn(dir, 270)) + +/obj/machinery/atmospherics/components/binary/circulator/can_be_node(obj/machinery/atmospherics/target) + if(anchored) + return ..(target) + return FALSE + +/obj/machinery/atmospherics/components/binary/circulator/multitool_act(mob/living/user, obj/item/I) + if(generator) + disconnectFromGenerator() + mode = !mode + to_chat(user, "You set [src] to [mode?"cold":"hot"] mode.") + return TRUE + +/obj/machinery/atmospherics/components/binary/circulator/screwdriver_act(mob/user, obj/item/I) + if(..()) + return TRUE + panel_open = !panel_open + I.play_tool_sound(src) + to_chat(user, "You [panel_open?"open":"close"] the panel on [src].") + return TRUE + +/obj/machinery/atmospherics/components/binary/circulator/crowbar_act(mob/user, obj/item/I) + default_deconstruction_crowbar(I) + return TRUE + +/obj/machinery/atmospherics/components/binary/circulator/on_deconstruction() + if(generator) + disconnectFromGenerator() + +/obj/machinery/atmospherics/components/binary/circulator/proc/disconnectFromGenerator() + if(mode) + generator.cold_circ = null + else + generator.hot_circ = null + generator.update_icon() + generator = null + +/obj/machinery/atmospherics/components/binary/circulator/setPipingLayer(new_layer) + ..() + pixel_x = 0 + pixel_y = 0 + +/obj/machinery/atmospherics/components/binary/circulator/verb/circulator_flip() + set name = "Flip" + set category = "Object" + set src in oview(1) + + if(!ishuman(usr)) + return + + if(anchored) + to_chat(usr, "[src] is anchored!") + return + + flipped = !flipped + to_chat(usr, "You flip [src].") + update_icon() diff --git a/code/modules/atmospherics/machinery/components/binary_devices/dp_vent_pump.dm b/code/modules/atmospherics/machinery/components/binary_devices/dp_vent_pump.dm index 1c3452352fa2..c2235590abcb 100644 --- a/code/modules/atmospherics/machinery/components/binary_devices/dp_vent_pump.dm +++ b/code/modules/atmospherics/machinery/components/binary_devices/dp_vent_pump.dm @@ -1,243 +1,243 @@ -//Acts like a normal vent, but has an input AND output. - -#define EXT_BOUND 1 -#define INPUT_MIN 2 -#define OUTPUT_MAX 4 - -/obj/machinery/atmospherics/components/binary/dp_vent_pump - icon = 'icons/obj/atmospherics/components/unary_devices.dmi' //We reuse the normal vent icons! - icon_state = "dpvent_map-2" - - //node2 is output port - //node1 is input port - - name = "dual-port air vent" - desc = "Has a valve and pump attached to it. There are two ports." - - hide = TRUE - - var/frequency = 0 - var/id = null - var/datum/radio_frequency/radio_connection - - var/pump_direction = 1 //0 = siphoning, 1 = releasing - - var/external_pressure_bound = ONE_ATMOSPHERE - var/input_pressure_min = 0 - var/output_pressure_max = 0 - - var/pressure_checks = EXT_BOUND - - var/obj/machinery/advanced_airlock_controller/aac = null - - //EXT_BOUND: Do not pass external_pressure_bound - //INPUT_MIN: Do not pass input_pressure_min - //OUTPUT_MAX: Do not pass output_pressure_max - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/Destroy() - SSradio.remove_object(src, frequency) - if(aac) - aac.vents -= src - return ..() - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/update_icon_nopipes() - cut_overlays() - if(showpipe) - var/image/cap = getpipeimage(icon, "dpvent_cap", dir, piping_layer = piping_layer) - add_overlay(cap) - - if(!on || !is_operational()) - icon_state = "vent_off" - else - icon_state = pump_direction ? "vent_out" : "vent_in" - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/process_atmos() - ..() - - if(!on) - return - var/datum/gas_mixture/air1 = airs[1] - var/datum/gas_mixture/air2 = airs[2] - - var/datum/gas_mixture/environment = loc.return_air() - var/environment_pressure = environment.return_pressure() - - if(pump_direction) //input -> external - var/pressure_delta = 10000 - - if(pressure_checks&EXT_BOUND) - pressure_delta = min(pressure_delta, (external_pressure_bound - environment_pressure)) - if(pressure_checks&INPUT_MIN) - pressure_delta = min(pressure_delta, (air1.return_pressure() - input_pressure_min)) - - if(pressure_delta > 0) - if(air1.return_temperature() > 0) - var/transfer_moles = pressure_delta*environment.return_volume()/(air1.return_temperature() * R_IDEAL_GAS_EQUATION) - - var/datum/gas_mixture/removed = air1.remove(transfer_moles) - //Removed can be null if there is no atmosphere in air1 - if(!removed) - return - - loc.assume_air(removed) - air_update_turf() - - var/datum/pipeline/parent1 = parents[1] - parent1.update = 1 - - else //external -> output - if(environment.return_pressure() > 0) - var/our_multiplier = air2.return_volume() / (environment.return_temperature() * R_IDEAL_GAS_EQUATION) - var/moles_delta = 10000 * our_multiplier - if(pressure_checks&EXT_BOUND) - moles_delta = min(moles_delta, (environment_pressure - output_pressure_max) * environment.return_volume() / (environment.return_temperature() * R_IDEAL_GAS_EQUATION)) - if(pressure_checks&INPUT_MIN) - moles_delta = min(moles_delta, (input_pressure_min - air2.return_pressure()) * our_multiplier) - - if(moles_delta > 0) - var/datum/gas_mixture/removed = loc.remove_air(moles_delta) - if (isnull(removed)) // in space - return - - air2.merge(removed) - air_update_turf() - - var/datum/pipeline/parent2 = parents[2] - parent2.update = 1 - - //Radio remote control - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/proc/set_frequency(new_frequency) - SSradio.remove_object(src, frequency) - frequency = new_frequency - if(frequency) - radio_connection = SSradio.add_object(src, frequency, filter = RADIO_ATMOSIA) - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/proc/broadcast_status() - if(!radio_connection) - return - - var/datum/signal/signal = new(list( - "tag" = id, - "device" = "ADVP", - "power" = on, - "direction" = pump_direction?("release"):("siphon"), - "checks" = pressure_checks, - "input" = input_pressure_min, - "output" = output_pressure_max, - "external" = external_pressure_bound, - "sigtype" = "status" - )) - radio_connection.post_signal(src, signal, filter = RADIO_ATMOSIA) - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/atmosinit() - ..() - if(frequency) - set_frequency(frequency) - broadcast_status() - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/receive_signal(datum/signal/signal) - if(!signal.data["tag"] || (signal.data["tag"] != id) || (signal.data["sigtype"]!="command")) - return - - if("power" in signal.data) - on = text2num(signal.data["power"]) - - if("power_toggle" in signal.data) - on = !on - - if("set_direction" in signal.data) - pump_direction = text2num(signal.data["set_direction"]) - - if("checks" in signal.data) - pressure_checks = text2num(signal.data["checks"]) - - if("purge" in signal.data) - pressure_checks &= ~1 - pump_direction = 0 - - if("stabilize" in signal.data) - pressure_checks |= 1 - pump_direction = 1 - - if("set_input_pressure" in signal.data) - input_pressure_min = clamp(text2num(signal.data["set_input_pressure"]),0,ONE_ATMOSPHERE*50) - - if("set_output_pressure" in signal.data) - output_pressure_max = clamp(text2num(signal.data["set_output_pressure"]),0,ONE_ATMOSPHERE*50) - - if("set_external_pressure" in signal.data) - external_pressure_bound = clamp(text2num(signal.data["set_external_pressure"]),0,ONE_ATMOSPHERE*50) - - addtimer(CALLBACK(src, .proc/broadcast_status), 2) - - if(!("status" in signal.data)) //do not update_icon - update_icon() - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume - name = "large dual-port air vent" - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/New() - ..() - var/datum/gas_mixture/air1 = airs[1] - var/datum/gas_mixture/air2 = airs[2] - air1.set_volume(1000) - air2.set_volume(1000) - -// Mapping - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/layer1 - piping_layer = 1 - icon_state = "dpvent_map-1" - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/layer3 - piping_layer = 3 - icon_state = "dpvent_map-3" - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/on - on = TRUE - icon_state = "dpvent_map_on-2" - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/on/layer1 - piping_layer = 1 - icon_state = "dpvent_map_on-1" - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/on/layer3 - piping_layer = 3 - icon_state = "dpvent_map_on-3" - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/incinerator_toxmix - id = INCINERATOR_TOXMIX_DP_VENTPUMP - frequency = FREQ_AIRLOCK_CONTROL - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/incinerator_atmos - id = INCINERATOR_ATMOS_DP_VENTPUMP - frequency = FREQ_AIRLOCK_CONTROL - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/incinerator_syndicatelava - id = INCINERATOR_SYNDICATELAVA_DP_VENTPUMP - frequency = FREQ_AIRLOCK_CONTROL - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/layer1 - piping_layer = 1 - icon_state = "dpvent_map-1" - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/layer3 - piping_layer = 3 - icon_state = "dpvent_map-3" - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/on - on = TRUE - icon_state = "dpvent_map_on-2" - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/on/layer1 - piping_layer = 1 - icon_state = "dpvent_map_on-1" - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/on/layer3 - piping_layer = 3 - icon_state = "dpvent_map_on-3" - -#undef EXT_BOUND -#undef INPUT_MIN -#undef OUTPUT_MAX +//Acts like a normal vent, but has an input AND output. + +#define EXT_BOUND 1 +#define INPUT_MIN 2 +#define OUTPUT_MAX 4 + +/obj/machinery/atmospherics/components/binary/dp_vent_pump + icon = 'icons/obj/atmospherics/components/unary_devices.dmi' //We reuse the normal vent icons! + icon_state = "dpvent_map-2" + + //node2 is output port + //node1 is input port + + name = "dual-port air vent" + desc = "Has a valve and pump attached to it. There are two ports." + + hide = TRUE + + var/frequency = 0 + var/id = null + var/datum/radio_frequency/radio_connection + + var/pump_direction = 1 //0 = siphoning, 1 = releasing + + var/external_pressure_bound = ONE_ATMOSPHERE + var/input_pressure_min = 0 + var/output_pressure_max = 0 + + var/pressure_checks = EXT_BOUND + + var/obj/machinery/advanced_airlock_controller/aac = null + + //EXT_BOUND: Do not pass external_pressure_bound + //INPUT_MIN: Do not pass input_pressure_min + //OUTPUT_MAX: Do not pass output_pressure_max + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/Destroy() + SSradio.remove_object(src, frequency) + if(aac) + aac.vents -= src + return ..() + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/update_icon_nopipes() + cut_overlays() + if(showpipe) + var/image/cap = getpipeimage(icon, "dpvent_cap", dir, piping_layer = piping_layer) + add_overlay(cap) + + if(!on || !is_operational()) + icon_state = "vent_off" + else + icon_state = pump_direction ? "vent_out" : "vent_in" + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/process_atmos() + ..() + + if(!on) + return + var/datum/gas_mixture/air1 = airs[1] + var/datum/gas_mixture/air2 = airs[2] + + var/datum/gas_mixture/environment = loc.return_air() + var/environment_pressure = environment.return_pressure() + + if(pump_direction) //input -> external + var/pressure_delta = 10000 + + if(pressure_checks&EXT_BOUND) + pressure_delta = min(pressure_delta, (external_pressure_bound - environment_pressure)) + if(pressure_checks&INPUT_MIN) + pressure_delta = min(pressure_delta, (air1.return_pressure() - input_pressure_min)) + + if(pressure_delta > 0) + if(air1.return_temperature() > 0) + var/transfer_moles = pressure_delta*environment.return_volume()/(air1.return_temperature() * R_IDEAL_GAS_EQUATION) + + var/datum/gas_mixture/removed = air1.remove(transfer_moles) + //Removed can be null if there is no atmosphere in air1 + if(!removed) + return + + loc.assume_air(removed) + air_update_turf() + + var/datum/pipeline/parent1 = parents[1] + parent1.update = 1 + + else //external -> output + if(environment.return_pressure() > 0) + var/our_multiplier = air2.return_volume() / (environment.return_temperature() * R_IDEAL_GAS_EQUATION) + var/moles_delta = 10000 * our_multiplier + if(pressure_checks&EXT_BOUND) + moles_delta = min(moles_delta, (environment_pressure - output_pressure_max) * environment.return_volume() / (environment.return_temperature() * R_IDEAL_GAS_EQUATION)) + if(pressure_checks&INPUT_MIN) + moles_delta = min(moles_delta, (input_pressure_min - air2.return_pressure()) * our_multiplier) + + if(moles_delta > 0) + var/datum/gas_mixture/removed = loc.remove_air(moles_delta) + if (isnull(removed)) // in space + return + + air2.merge(removed) + air_update_turf() + + var/datum/pipeline/parent2 = parents[2] + parent2.update = 1 + + //Radio remote control + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/proc/set_frequency(new_frequency) + SSradio.remove_object(src, frequency) + frequency = new_frequency + if(frequency) + radio_connection = SSradio.add_object(src, frequency, filter = RADIO_ATMOSIA) + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/proc/broadcast_status() + if(!radio_connection) + return + + var/datum/signal/signal = new(list( + "tag" = id, + "device" = "ADVP", + "power" = on, + "direction" = pump_direction?("release"):("siphon"), + "checks" = pressure_checks, + "input" = input_pressure_min, + "output" = output_pressure_max, + "external" = external_pressure_bound, + "sigtype" = "status" + )) + radio_connection.post_signal(src, signal, filter = RADIO_ATMOSIA) + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/atmosinit() + ..() + if(frequency) + set_frequency(frequency) + broadcast_status() + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/receive_signal(datum/signal/signal) + if(!signal.data["tag"] || (signal.data["tag"] != id) || (signal.data["sigtype"]!="command")) + return + + if("power" in signal.data) + on = text2num(signal.data["power"]) + + if("power_toggle" in signal.data) + on = !on + + if("set_direction" in signal.data) + pump_direction = text2num(signal.data["set_direction"]) + + if("checks" in signal.data) + pressure_checks = text2num(signal.data["checks"]) + + if("purge" in signal.data) + pressure_checks &= ~1 + pump_direction = 0 + + if("stabilize" in signal.data) + pressure_checks |= 1 + pump_direction = 1 + + if("set_input_pressure" in signal.data) + input_pressure_min = clamp(text2num(signal.data["set_input_pressure"]),0,ONE_ATMOSPHERE*50) + + if("set_output_pressure" in signal.data) + output_pressure_max = clamp(text2num(signal.data["set_output_pressure"]),0,ONE_ATMOSPHERE*50) + + if("set_external_pressure" in signal.data) + external_pressure_bound = clamp(text2num(signal.data["set_external_pressure"]),0,ONE_ATMOSPHERE*50) + + addtimer(CALLBACK(src, .proc/broadcast_status), 2) + + if(!("status" in signal.data)) //do not update_icon + update_icon() + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume + name = "large dual-port air vent" + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/New() + ..() + var/datum/gas_mixture/air1 = airs[1] + var/datum/gas_mixture/air2 = airs[2] + air1.set_volume(1000) + air2.set_volume(1000) + +// Mapping + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/layer1 + piping_layer = 1 + icon_state = "dpvent_map-1" + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/layer3 + piping_layer = 3 + icon_state = "dpvent_map-3" + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/on + on = TRUE + icon_state = "dpvent_map_on-2" + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/on/layer1 + piping_layer = 1 + icon_state = "dpvent_map_on-1" + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/on/layer3 + piping_layer = 3 + icon_state = "dpvent_map_on-3" + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/incinerator_toxmix + id = INCINERATOR_TOXMIX_DP_VENTPUMP + frequency = FREQ_AIRLOCK_CONTROL + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/incinerator_atmos + id = INCINERATOR_ATMOS_DP_VENTPUMP + frequency = FREQ_AIRLOCK_CONTROL + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/incinerator_syndicatelava + id = INCINERATOR_SYNDICATELAVA_DP_VENTPUMP + frequency = FREQ_AIRLOCK_CONTROL + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/layer1 + piping_layer = 1 + icon_state = "dpvent_map-1" + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/layer3 + piping_layer = 3 + icon_state = "dpvent_map-3" + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/on + on = TRUE + icon_state = "dpvent_map_on-2" + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/on/layer1 + piping_layer = 1 + icon_state = "dpvent_map_on-1" + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/on/layer3 + piping_layer = 3 + icon_state = "dpvent_map_on-3" + +#undef EXT_BOUND +#undef INPUT_MIN +#undef OUTPUT_MAX 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 3a65e79ef75c..815efc7089e7 100644 --- a/code/modules/atmospherics/machinery/components/binary_devices/passive_gate.dm +++ b/code/modules/atmospherics/machinery/components/binary_devices/passive_gate.dm @@ -26,9 +26,6 @@ Passive gate is similar to the regular pump except: construction_type = /obj/item/pipe/directional pipe_state = "passivegate" - ui_x = 335 - ui_y = 115 - /obj/machinery/atmospherics/components/binary/passive_gate/CtrlClick(mob/user) if(can_interact(user)) on = !on @@ -104,11 +101,10 @@ Passive gate is similar to the regular pump except: )) radio_connection.post_signal(src, signal, filter = RADIO_ATMOSIA) -/obj/machinery/atmospherics/components/binary/passive_gate/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/machinery/atmospherics/components/binary/passive_gate/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "AtmosPump", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, "AtmosPump", name) ui.open() /obj/machinery/atmospherics/components/binary/passive_gate/ui_data() diff --git a/code/modules/atmospherics/machinery/components/binary_devices/pump.dm b/code/modules/atmospherics/machinery/components/binary_devices/pump.dm index 21bfd391bc05..93d027abda90 100644 --- a/code/modules/atmospherics/machinery/components/binary_devices/pump.dm +++ b/code/modules/atmospherics/machinery/components/binary_devices/pump.dm @@ -27,9 +27,6 @@ construction_type = /obj/item/pipe/directional pipe_state = "pump" - ui_x = 335 - ui_y = 115 - /obj/machinery/atmospherics/components/binary/pump/CtrlClick(mob/user) if(can_interact(user)) on = !on @@ -98,11 +95,10 @@ )) radio_connection.post_signal(src, signal, filter = RADIO_ATMOSIA) -/obj/machinery/atmospherics/components/binary/pump/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/machinery/atmospherics/components/binary/pump/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "AtmosPump", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, "AtmosPump", name) ui.open() /obj/machinery/atmospherics/components/binary/pump/ui_data() diff --git a/code/modules/atmospherics/machinery/components/binary_devices/volume_pump.dm b/code/modules/atmospherics/machinery/components/binary_devices/volume_pump.dm index 8fa636195f8b..143b5dfdeffd 100644 --- a/code/modules/atmospherics/machinery/components/binary_devices/volume_pump.dm +++ b/code/modules/atmospherics/machinery/components/binary_devices/volume_pump.dm @@ -28,9 +28,6 @@ construction_type = /obj/item/pipe/directional pipe_state = "volumepump" - ui_x = 335 - ui_y = 115 - /obj/machinery/atmospherics/components/binary/volume_pump/CtrlClick(mob/user) if(can_interact(user)) on = !on @@ -111,11 +108,10 @@ )) radio_connection.post_signal(src, signal) -/obj/machinery/atmospherics/components/binary/volume_pump/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/machinery/atmospherics/components/binary/volume_pump/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "AtmosPump", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, "AtmosPump", name) ui.open() /obj/machinery/atmospherics/components/binary/volume_pump/ui_data() diff --git a/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm b/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm index 94458fd26c49..4799a091dfd9 100644 --- a/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm +++ b/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm @@ -14,9 +14,6 @@ construction_type = /obj/item/pipe/trinary/flippable pipe_state = "filter" - ui_x = 390 - ui_y = 187 - /obj/machinery/atmospherics/components/trinary/filter/CtrlClick(mob/user) if(can_interact(user)) on = !on @@ -119,11 +116,10 @@ set_frequency(frequency) return ..() -/obj/machinery/atmospherics/components/trinary/filter/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/machinery/atmospherics/components/trinary/filter/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "AtmosFilter", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, "AtmosFilter", name) ui.open() /obj/machinery/atmospherics/components/trinary/filter/ui_data() diff --git a/code/modules/atmospherics/machinery/components/trinary_devices/mixer.dm b/code/modules/atmospherics/machinery/components/trinary_devices/mixer.dm index 9a35c20b118b..26a0c5f8b2ea 100644 --- a/code/modules/atmospherics/machinery/components/trinary_devices/mixer.dm +++ b/code/modules/atmospherics/machinery/components/trinary_devices/mixer.dm @@ -14,9 +14,6 @@ construction_type = /obj/item/pipe/trinary/flippable pipe_state = "mixer" - ui_x = 370 - ui_y = 165 - //node 3 is the outlet, nodes 1 & 2 are intakes /obj/machinery/atmospherics/components/trinary/mixer/CtrlClick(mob/user) @@ -127,11 +124,10 @@ var/datum/pipeline/parent3 = parents[3] parent3.update = TRUE -/obj/machinery/atmospherics/components/trinary/mixer/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/machinery/atmospherics/components/trinary/mixer/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "AtmosMixer", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, "AtmosMixer", name) ui.open() /obj/machinery/atmospherics/components/trinary/mixer/ui_data() diff --git a/code/modules/atmospherics/machinery/components/trinary_devices/trinary_devices.dm b/code/modules/atmospherics/machinery/components/trinary_devices/trinary_devices.dm index 8ba105fdb122..b448c2125679 100644 --- a/code/modules/atmospherics/machinery/components/trinary_devices/trinary_devices.dm +++ b/code/modules/atmospherics/machinery/components/trinary_devices/trinary_devices.dm @@ -1,48 +1,48 @@ -/obj/machinery/atmospherics/components/trinary - icon = 'icons/obj/atmospherics/components/trinary_devices.dmi' - dir = SOUTH - initialize_directions = SOUTH|NORTH|WEST - use_power = IDLE_POWER_USE - device_type = TRINARY - layer = GAS_FILTER_LAYER - pipe_flags = PIPING_ONE_PER_TURF - - var/flipped = FALSE - -/obj/machinery/atmospherics/components/trinary/SetInitDirections() - switch(dir) - if(NORTH) - initialize_directions = EAST|NORTH|SOUTH - if(SOUTH) - initialize_directions = SOUTH|WEST|NORTH - if(EAST) - initialize_directions = EAST|WEST|SOUTH - if(WEST) - initialize_directions = WEST|NORTH|EAST - -/* -Housekeeping and pipe network stuff -*/ - -/obj/machinery/atmospherics/components/trinary/getNodeConnects() - - //Mixer: - //1 and 2 is input - //Node 3 is output - //If we flip the mixer, 1 and 3 shall exchange positions - - //Filter: - //Node 1 is input - //Node 2 is filtered output - //Node 3 is rest output - //If we flip the filter, 1 and 3 shall exchange positions - - var/node1_connect = turn(dir, -180) - var/node2_connect = turn(dir, -90) - var/node3_connect = dir - - if(flipped) - node1_connect = turn(node1_connect, 180) - node3_connect = turn(node3_connect, 180) - - return list(node1_connect, node2_connect, node3_connect) +/obj/machinery/atmospherics/components/trinary + icon = 'icons/obj/atmospherics/components/trinary_devices.dmi' + dir = SOUTH + initialize_directions = SOUTH|NORTH|WEST + use_power = IDLE_POWER_USE + device_type = TRINARY + layer = GAS_FILTER_LAYER + pipe_flags = PIPING_ONE_PER_TURF + + var/flipped = FALSE + +/obj/machinery/atmospherics/components/trinary/SetInitDirections() + switch(dir) + if(NORTH) + initialize_directions = EAST|NORTH|SOUTH + if(SOUTH) + initialize_directions = SOUTH|WEST|NORTH + if(EAST) + initialize_directions = EAST|WEST|SOUTH + if(WEST) + initialize_directions = WEST|NORTH|EAST + +/* +Housekeeping and pipe network stuff +*/ + +/obj/machinery/atmospherics/components/trinary/getNodeConnects() + + //Mixer: + //1 and 2 is input + //Node 3 is output + //If we flip the mixer, 1 and 3 shall exchange positions + + //Filter: + //Node 1 is input + //Node 2 is filtered output + //Node 3 is rest output + //If we flip the filter, 1 and 3 shall exchange positions + + var/node1_connect = turn(dir, -180) + var/node2_connect = turn(dir, -90) + var/node3_connect = dir + + if(flipped) + node1_connect = turn(node1_connect, 180) + node3_connect = turn(node3_connect, 180) + + return list(node1_connect, node2_connect, node3_connect) diff --git a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm index 4237f1926f2f..f8b5c734457f 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm @@ -1,470 +1,471 @@ -#define CRYOMOBS 'icons/obj/cryo_mobs.dmi' - -/obj/machinery/atmospherics/components/unary/cryo_cell - name = "cryo cell" - icon = 'icons/obj/cryogenics.dmi' - icon_state = "pod-off" - density = TRUE - max_integrity = 350 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 30, "acid" = 30) - layer = ABOVE_WINDOW_LAYER - state_open = FALSE - circuit = /obj/item/circuitboard/machine/cryo_tube - ui_x = 400 - ui_y = 550 - pipe_flags = PIPING_ONE_PER_TURF | PIPING_DEFAULT_LAYER_ONLY - occupant_typecache = list(/mob/living/carbon, /mob/living/simple_animal) - - var/autoeject = TRUE - var/volume = 100 - - var/efficiency = 1 - var/sleep_factor = 0.00125 - var/unconscious_factor = 0.001 - var/heat_capacity = 20000 - var/conduction_coefficient = 0.3 - - var/obj/item/reagent_containers/glass/beaker = null - var/reagent_transfer = 0 - - var/obj/item/radio/radio - var/radio_key = /obj/item/encryptionkey/headset_med - var/radio_channel = RADIO_CHANNEL_MEDICAL - - var/running_anim = FALSE - - var/escape_in_progress = FALSE - var/message_cooldown - var/breakout_time = 300 - fair_market_price = 10 - payment_department = ACCOUNT_MED - - -/obj/machinery/atmospherics/components/unary/cryo_cell/Initialize() - . = ..() - initialize_directions = dir - - radio = new(src) - radio.keyslot = new radio_key - radio.subspace_transmission = TRUE - radio.canhear_range = 0 - radio.recalculateChannels() - -/obj/machinery/atmospherics/components/unary/cryo_cell/Exited(atom/movable/AM, atom/newloc) - var/oldoccupant = occupant - . = ..() // Parent proc takes care of removing occupant if necessary - if (AM == oldoccupant) - update_icon() - -/obj/machinery/atmospherics/components/unary/cryo_cell/on_construction() - ..(dir, dir) - -/obj/machinery/atmospherics/components/unary/cryo_cell/RefreshParts() - var/C - for(var/obj/item/stock_parts/matter_bin/M in component_parts) - C += M.rating - - efficiency = initial(efficiency) * C - sleep_factor = initial(sleep_factor) * C - unconscious_factor = initial(unconscious_factor) * C - heat_capacity = initial(heat_capacity) / C - conduction_coefficient = initial(conduction_coefficient) * C - -/obj/machinery/atmospherics/components/unary/cryo_cell/examine(mob/user) //this is leaving out everything but efficiency since they follow the same idea of "better beaker, better results" - . = ..() - if(in_range(user, src) || isobserver(user)) - . += "The status display reads: Efficiency at [efficiency*100]%." - -/obj/machinery/atmospherics/components/unary/cryo_cell/Destroy() - QDEL_NULL(radio) - QDEL_NULL(beaker) - return ..() - -/obj/machinery/atmospherics/components/unary/cryo_cell/contents_explosion(severity, target) - ..() - if(beaker) - switch(severity) - if(EXPLODE_DEVASTATE) - SSexplosions.highobj += beaker - if(EXPLODE_HEAVY) - SSexplosions.medobj += beaker - if(EXPLODE_LIGHT) - SSexplosions.lowobj += beaker - -/obj/machinery/atmospherics/components/unary/cryo_cell/handle_atom_del(atom/A) - ..() - if(A == beaker) - beaker = null - updateUsrDialog() - -/obj/machinery/atmospherics/components/unary/cryo_cell/on_deconstruction() - if(beaker) - beaker.forceMove(drop_location()) - beaker = null - -/obj/machinery/atmospherics/components/unary/cryo_cell/update_icon() - - cut_overlays() - - if(panel_open) - add_overlay("pod-panel") - - if(state_open) - icon_state = "pod-open" - return - - if(occupant) - var/image/occupant_overlay - - if(ismonkey(occupant)) // Monkey - occupant_overlay = image(CRYOMOBS, "monkey") - else if(isalienadult(occupant)) - if(isalienroyal(occupant)) // Queen and prae - occupant_overlay = image(CRYOMOBS, "alienq") - else if(isalienhunter(occupant)) // Hunter - occupant_overlay = image(CRYOMOBS, "alienh") - else if(isaliensentinel(occupant)) // Sentinel - occupant_overlay = image(CRYOMOBS, "aliens") - else // Drone or other - occupant_overlay = image(CRYOMOBS, "aliend") - - else if(ishuman(occupant) || islarva(occupant) || (isanimal(occupant) && !ismegafauna(occupant))) // Mobs that are smaller than cryotube - occupant_overlay = image(occupant.icon, occupant.icon_state) - occupant_overlay.copy_overlays(occupant) - - else - occupant_overlay = image(CRYOMOBS, "generic") - - occupant_overlay.dir = SOUTH - occupant_overlay.pixel_y = 22 - - if(on && !running_anim && is_operational()) - icon_state = "pod-on" - running_anim = TRUE - run_anim(TRUE, occupant_overlay) - else - icon_state = "pod-off" - add_overlay(occupant_overlay) - add_overlay("cover-off") - - else if(on && is_operational()) - icon_state = "pod-on" - add_overlay("cover-on") - else - icon_state = "pod-off" - add_overlay("cover-off") - -/obj/machinery/atmospherics/components/unary/cryo_cell/proc/run_anim(anim_up, image/occupant_overlay) - if(!on || !occupant || !is_operational()) - running_anim = FALSE - return - cut_overlays() - if(occupant_overlay.pixel_y != 23) // Same effect as occupant_overlay.pixel_y == 22 || occupant_overlay.pixel_y == 24 - anim_up = occupant_overlay.pixel_y == 22 // Same effect as if(occupant_overlay.pixel_y == 22) anim_up = TRUE ; if(occupant_overlay.pixel_y == 24) anim_up = FALSE - if(anim_up) - occupant_overlay.pixel_y++ - else - occupant_overlay.pixel_y-- - add_overlay(occupant_overlay) - add_overlay("cover-on") - addtimer(CALLBACK(src, .proc/run_anim, anim_up, occupant_overlay), 7, TIMER_UNIQUE) - -/obj/machinery/atmospherics/components/unary/cryo_cell/nap_violation(mob/violator) - open_machine() - -/obj/machinery/atmospherics/components/unary/cryo_cell/process() - ..() - - if(!on) - return - if(!is_operational()) - on = FALSE - update_icon() - return - if(!occupant) - return - - var/mob/living/mob_occupant = occupant - if(!check_nap_violations()) - return - if(mob_occupant.stat == DEAD) // We don't bother with dead people. - return - - if(mob_occupant.health >= mob_occupant.getMaxHealth()) // Don't bother with fully healed people. - on = FALSE - update_icon() - playsound(src, 'sound/machines/cryo_warning.ogg', volume) // Bug the doctors. - var/msg = "Patient fully restored." - if(autoeject) // Eject if configured. - msg += " Auto ejecting patient now." - open_machine() - radio.talk_into(src, msg, radio_channel) - return - - var/datum/gas_mixture/air1 = airs[1] - - if(air1.total_moles()) - if(mob_occupant.bodytemperature < T0C) // Sleepytime. Why? More cryo magic. - mob_occupant.Sleeping((mob_occupant.bodytemperature * sleep_factor) * 2000) - mob_occupant.Unconscious((mob_occupant.bodytemperature * unconscious_factor) * 2000) - if(beaker) - if(reagent_transfer == 0) // Magically transfer reagents. Because cryo magic. - beaker.reagents.trans_to(occupant, 1, efficiency * 0.25) // Transfer reagents. - beaker.reagents.expose(occupant, VAPOR) - air1.adjust_moles(/datum/gas/oxygen, -max(0,air1.get_moles(/datum/gas/oxygen) - 2 / efficiency)) //Let's use gas for this - if(++reagent_transfer >= 10 * efficiency) // Throttle reagent transfer (higher efficiency will transfer the same amount but consume less from the beaker). - reagent_transfer = 0 - - return 1 - -/obj/machinery/atmospherics/components/unary/cryo_cell/process_atmos() - ..() - - if(!on) - return - - var/datum/gas_mixture/air1 = airs[1] - - if(!nodes[1] || !airs[1] || air1.get_moles(/datum/gas/oxygen) < 5) // Turn off if the machine won't work. - on = FALSE - update_icon() - return - - if(occupant) - var/mob/living/mob_occupant = occupant - var/cold_protection = 0 - var/temperature_delta = air1.return_temperature() - mob_occupant.bodytemperature // The only semi-realistic thing here: share temperature between the cell and the occupant. - - if(ishuman(occupant)) - var/mob/living/carbon/human/H = occupant - cold_protection = H.get_cold_protection(air1.return_temperature()) - - if(abs(temperature_delta) > 1) - var/air_heat_capacity = air1.heat_capacity() - - var/heat = ((1 - cold_protection) * 0.1 + conduction_coefficient) * temperature_delta * (air_heat_capacity * heat_capacity / (air_heat_capacity + heat_capacity)) - - air1.set_temperature(max(air1.return_temperature() - heat / air_heat_capacity, TCMB)) - mob_occupant.adjust_bodytemperature(heat / heat_capacity, TCMB) - - air1.set_moles(/datum/gas/oxygen, max(0,air1.get_moles(/datum/gas/oxygen) - 0.5 / efficiency)) // Magically consume gas? Why not, we run on cryo magic. - -/obj/machinery/atmospherics/components/unary/cryo_cell/relaymove(mob/user) - if(message_cooldown <= world.time) - message_cooldown = world.time + 50 - to_chat(user, "[src]'s door won't budge!") - -/obj/machinery/atmospherics/components/unary/cryo_cell/open_machine(drop = FALSE) - if(!state_open && !panel_open) - on = FALSE - for(var/mob/M in contents) //only drop mobs - M.forceMove(get_turf(src)) - if(isliving(M)) - var/mob/living/L = M - L.update_mobility() - occupant = null - flick("pod-open-anim", src) - ..() - -/obj/machinery/atmospherics/components/unary/cryo_cell/close_machine(mob/living/carbon/user) - if((isnull(user) || istype(user)) && state_open && !panel_open) - flick("pod-close-anim", src) - ..(user) - return occupant - -/obj/machinery/atmospherics/components/unary/cryo_cell/container_resist(mob/living/user) - user.changeNext_move(CLICK_CD_BREAKOUT) - user.last_special = world.time + CLICK_CD_BREAKOUT - user.visible_message("You see [user] kicking against the glass of [src]!", \ - "You struggle inside [src], kicking the release with your foot... (this will take about [DisplayTimeText(breakout_time)].)", \ - "You hear a thump from [src].") - if(do_after(user, breakout_time, target = src)) - if(!user || user.stat != CONSCIOUS || user.loc != src ) - return - user.visible_message("[user] successfully broke out of [src]!", \ - "You successfully break out of [src]!") - open_machine() - -/obj/machinery/atmospherics/components/unary/cryo_cell/examine(mob/user) - . = ..() - if(occupant) - if(on) - . += "Someone's inside [src]!" - else - . += "You can barely make out a form floating in [src]." - else - . += "[src] seems empty." - -/obj/machinery/atmospherics/components/unary/cryo_cell/MouseDrop_T(mob/target, mob/user) - if(user.incapacitated() || !Adjacent(user) || !user.Adjacent(target) || !iscarbon(target) || !user.IsAdvancedToolUser()) - return - if(isliving(target)) - var/mob/living/L = target - if(L.incapacitated()) - close_machine(target) - else - user.visible_message("[user] starts shoving [target] inside [src].", "You start shoving [target] inside [src].") - if (do_after(user, 25, target=target)) - close_machine(target) - -/obj/machinery/atmospherics/components/unary/cryo_cell/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/reagent_containers/glass)) - . = 1 //no afterattack - if(beaker) - to_chat(user, "A beaker is already loaded into [src]!") - return - if(!user.transferItemToLoc(I, src)) - return - beaker = I - user.visible_message("[user] places [I] in [src].", \ - "You place [I] in [src].") - var/reagentlist = pretty_string_from_reagent_list(I.reagents.reagent_list) - log_game("[key_name(user)] added an [I] to cryo containing [reagentlist]") - return - if(!on && !occupant && !state_open && (default_deconstruction_screwdriver(user, "pod-off", "pod-off", I)) \ - || default_change_direction_wrench(user, I) \ - || default_pry_open(I) \ - || default_deconstruction_crowbar(I)) - update_icon() - return - else if(I.tool_behaviour == TOOL_SCREWDRIVER) - to_chat(user, "You can't access the maintenance panel while the pod is " \ - + (on ? "active" : (occupant ? "full" : "open")) + "!") - return - return ..() - -/obj/machinery/atmospherics/components/unary/cryo_cell/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.notcontained_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "Cryo", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/machinery/atmospherics/components/unary/cryo_cell/ui_data() - var/list/data = list() - data["isOperating"] = on - data["hasOccupant"] = occupant ? TRUE : FALSE - data["isOpen"] = state_open - data["autoEject"] = autoeject - - data["occupant"] = list() - if(occupant) - var/mob/living/mob_occupant = occupant - data["occupant"]["name"] = mob_occupant.name - switch(mob_occupant.stat) - if(CONSCIOUS) - data["occupant"]["stat"] = "Conscious" - data["occupant"]["statstate"] = "good" - if(SOFT_CRIT) - data["occupant"]["stat"] = "Conscious" - data["occupant"]["statstate"] = "average" - if(UNCONSCIOUS) - data["occupant"]["stat"] = "Unconscious" - data["occupant"]["statstate"] = "average" - if(DEAD) - data["occupant"]["stat"] = "Dead" - data["occupant"]["statstate"] = "bad" - data["occupant"]["health"] = round(mob_occupant.health, 1) - data["occupant"]["maxHealth"] = mob_occupant.maxHealth - data["occupant"]["minHealth"] = HEALTH_THRESHOLD_DEAD - data["occupant"]["bruteLoss"] = round(mob_occupant.getBruteLoss(), 1) - data["occupant"]["oxyLoss"] = round(mob_occupant.getOxyLoss(), 1) - data["occupant"]["toxLoss"] = round(mob_occupant.getToxLoss(), 1) - data["occupant"]["fireLoss"] = round(mob_occupant.getFireLoss(), 1) - data["occupant"]["bodyTemperature"] = round(mob_occupant.bodytemperature, 1) - if(mob_occupant.bodytemperature < TCRYO) - data["occupant"]["temperaturestatus"] = "good" - else if(mob_occupant.bodytemperature < T0C) - data["occupant"]["temperaturestatus"] = "average" - else - data["occupant"]["temperaturestatus"] = "bad" - - var/datum/gas_mixture/air1 = airs[1] - data["cellTemperature"] = round(air1.return_temperature(), 1) - - data["isBeakerLoaded"] = beaker ? TRUE : FALSE - var/beakerContents = list() - if(beaker && beaker.reagents && beaker.reagents.reagent_list.len) - for(var/datum/reagent/R in beaker.reagents.reagent_list) - beakerContents += list(list("name" = R.name, "volume" = R.volume)) - data["beakerContents"] = beakerContents - return data - -/obj/machinery/atmospherics/components/unary/cryo_cell/ui_act(action, params) - if(..()) - return - switch(action) - if("power") - if(on) - on = FALSE - else if(!state_open) - on = TRUE - update_icon() - . = TRUE - if("door") - if(state_open) - close_machine() - else - open_machine() - . = TRUE - if("autoeject") - autoeject = !autoeject - . = TRUE - if("ejectbeaker") - if(beaker) - beaker.forceMove(drop_location()) - if(Adjacent(usr) && !issilicon(usr)) - usr.put_in_hands(beaker) - beaker = null - . = TRUE - -/obj/machinery/atmospherics/components/unary/cryo_cell/CtrlClick(mob/user) - if(can_interact(user) && !state_open) - on = !on - update_icon() - return ..() - -/obj/machinery/atmospherics/components/unary/cryo_cell/AltClick(mob/user) - if(can_interact(user)) - if(state_open) - close_machine() - else - open_machine() - return ..() - -/obj/machinery/atmospherics/components/unary/cryo_cell/update_remote_sight(mob/living/user) - return // we don't see the pipe network while inside cryo. - -/obj/machinery/atmospherics/components/unary/cryo_cell/get_remote_view_fullscreens(mob/user) - user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 1) - -/obj/machinery/atmospherics/components/unary/cryo_cell/can_crawl_through() - return // can't ventcrawl in or out of cryo. - -/obj/machinery/atmospherics/components/unary/cryo_cell/can_see_pipes() - return 0 // you can't see the pipe network when inside a cryo cell. - -/obj/machinery/atmospherics/components/unary/cryo_cell/return_temperature() - var/datum/gas_mixture/G = airs[1] - - if(G.total_moles() > 10) - return G.return_temperature() - return ..() - -/obj/machinery/atmospherics/components/unary/cryo_cell/default_change_direction_wrench(mob/user, obj/item/wrench/W) - . = ..() - if(.) - SetInitDirections() - var/obj/machinery/atmospherics/node = nodes[1] - if(node) - node.disconnect(src) - nodes[1] = null - nullifyPipenet(parents[1]) - atmosinit() - node = nodes[1] - if(node) - node.atmosinit() - node.addMember(src) - SSair.add_to_rebuild_queue(src) - -#undef CRYOMOBS +#define CRYOMOBS 'icons/obj/cryo_mobs.dmi' + +/obj/machinery/atmospherics/components/unary/cryo_cell + name = "cryo cell" + icon = 'icons/obj/cryogenics.dmi' + icon_state = "pod-off" + density = TRUE + max_integrity = 350 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 30, "acid" = 30) + layer = ABOVE_WINDOW_LAYER + state_open = FALSE + circuit = /obj/item/circuitboard/machine/cryo_tube + pipe_flags = PIPING_ONE_PER_TURF | PIPING_DEFAULT_LAYER_ONLY + occupant_typecache = list(/mob/living/carbon, /mob/living/simple_animal) + + var/autoeject = TRUE + var/volume = 100 + + var/efficiency = 1 + var/sleep_factor = 0.00125 + var/unconscious_factor = 0.001 + var/heat_capacity = 20000 + var/conduction_coefficient = 0.3 + + var/obj/item/reagent_containers/glass/beaker = null + var/reagent_transfer = 0 + + var/obj/item/radio/radio + var/radio_key = /obj/item/encryptionkey/headset_med + var/radio_channel = RADIO_CHANNEL_MEDICAL + + var/running_anim = FALSE + + var/escape_in_progress = FALSE + var/message_cooldown + var/breakout_time = 300 + fair_market_price = 10 + payment_department = ACCOUNT_MED + + +/obj/machinery/atmospherics/components/unary/cryo_cell/Initialize() + . = ..() + initialize_directions = dir + + radio = new(src) + radio.keyslot = new radio_key + radio.subspace_transmission = TRUE + radio.canhear_range = 0 + radio.recalculateChannels() + +/obj/machinery/atmospherics/components/unary/cryo_cell/Exited(atom/movable/AM, atom/newloc) + var/oldoccupant = occupant + . = ..() // Parent proc takes care of removing occupant if necessary + if (AM == oldoccupant) + update_icon() + +/obj/machinery/atmospherics/components/unary/cryo_cell/on_construction() + ..(dir, dir) + +/obj/machinery/atmospherics/components/unary/cryo_cell/RefreshParts() + var/C + for(var/obj/item/stock_parts/matter_bin/M in component_parts) + C += M.rating + + efficiency = initial(efficiency) * C + sleep_factor = initial(sleep_factor) * C + unconscious_factor = initial(unconscious_factor) * C + heat_capacity = initial(heat_capacity) / C + conduction_coefficient = initial(conduction_coefficient) * C + +/obj/machinery/atmospherics/components/unary/cryo_cell/examine(mob/user) //this is leaving out everything but efficiency since they follow the same idea of "better beaker, better results" + . = ..() + if(in_range(user, src) || isobserver(user)) + . += "The status display reads: Efficiency at [efficiency*100]%." + +/obj/machinery/atmospherics/components/unary/cryo_cell/Destroy() + QDEL_NULL(radio) + QDEL_NULL(beaker) + return ..() + +/obj/machinery/atmospherics/components/unary/cryo_cell/contents_explosion(severity, target) + ..() + if(beaker) + switch(severity) + if(EXPLODE_DEVASTATE) + SSexplosions.highobj += beaker + if(EXPLODE_HEAVY) + SSexplosions.medobj += beaker + if(EXPLODE_LIGHT) + SSexplosions.lowobj += beaker + +/obj/machinery/atmospherics/components/unary/cryo_cell/handle_atom_del(atom/A) + ..() + if(A == beaker) + beaker = null + updateUsrDialog() + +/obj/machinery/atmospherics/components/unary/cryo_cell/on_deconstruction() + if(beaker) + beaker.forceMove(drop_location()) + beaker = null + +/obj/machinery/atmospherics/components/unary/cryo_cell/update_icon() + + cut_overlays() + + if(panel_open) + add_overlay("pod-panel") + + if(state_open) + icon_state = "pod-open" + return + + if(occupant) + var/image/occupant_overlay + + if(ismonkey(occupant)) // Monkey + occupant_overlay = image(CRYOMOBS, "monkey") + else if(isalienadult(occupant)) + if(isalienroyal(occupant)) // Queen and prae + occupant_overlay = image(CRYOMOBS, "alienq") + else if(isalienhunter(occupant)) // Hunter + occupant_overlay = image(CRYOMOBS, "alienh") + else if(isaliensentinel(occupant)) // Sentinel + occupant_overlay = image(CRYOMOBS, "aliens") + else // Drone or other + occupant_overlay = image(CRYOMOBS, "aliend") + + else if(ishuman(occupant) || islarva(occupant) || (isanimal(occupant) && !ismegafauna(occupant))) // Mobs that are smaller than cryotube + occupant_overlay = image(occupant.icon, occupant.icon_state) + occupant_overlay.copy_overlays(occupant) + + else + occupant_overlay = image(CRYOMOBS, "generic") + + occupant_overlay.dir = SOUTH + occupant_overlay.pixel_y = 22 + + if(on && !running_anim && is_operational()) + icon_state = "pod-on" + running_anim = TRUE + run_anim(TRUE, occupant_overlay) + else + icon_state = "pod-off" + add_overlay(occupant_overlay) + add_overlay("cover-off") + + else if(on && is_operational()) + icon_state = "pod-on" + add_overlay("cover-on") + else + icon_state = "pod-off" + add_overlay("cover-off") + +/obj/machinery/atmospherics/components/unary/cryo_cell/proc/run_anim(anim_up, image/occupant_overlay) + if(!on || !occupant || !is_operational()) + running_anim = FALSE + return + cut_overlays() + if(occupant_overlay.pixel_y != 23) // Same effect as occupant_overlay.pixel_y == 22 || occupant_overlay.pixel_y == 24 + anim_up = occupant_overlay.pixel_y == 22 // Same effect as if(occupant_overlay.pixel_y == 22) anim_up = TRUE ; if(occupant_overlay.pixel_y == 24) anim_up = FALSE + if(anim_up) + occupant_overlay.pixel_y++ + else + occupant_overlay.pixel_y-- + add_overlay(occupant_overlay) + add_overlay("cover-on") + addtimer(CALLBACK(src, .proc/run_anim, anim_up, occupant_overlay), 7, TIMER_UNIQUE) + +/obj/machinery/atmospherics/components/unary/cryo_cell/nap_violation(mob/violator) + open_machine() + +/obj/machinery/atmospherics/components/unary/cryo_cell/process() + ..() + + if(!on) + return + if(!is_operational()) + on = FALSE + update_icon() + return + if(!occupant) + return + + var/mob/living/mob_occupant = occupant + if(!check_nap_violations()) + return + if(mob_occupant.stat == DEAD) // We don't bother with dead people. + return + + if(mob_occupant.health >= mob_occupant.getMaxHealth()) // Don't bother with fully healed people. + on = FALSE + update_icon() + playsound(src, 'sound/machines/cryo_warning.ogg', volume) // Bug the doctors. + var/msg = "Patient fully restored." + if(autoeject) // Eject if configured. + msg += " Auto ejecting patient now." + open_machine() + radio.talk_into(src, msg, radio_channel) + return + + var/datum/gas_mixture/air1 = airs[1] + + if(air1.total_moles()) + if(mob_occupant.bodytemperature < T0C) // Sleepytime. Why? More cryo magic. + mob_occupant.Sleeping((mob_occupant.bodytemperature * sleep_factor) * 2000) + mob_occupant.Unconscious((mob_occupant.bodytemperature * unconscious_factor) * 2000) + if(beaker) + if(reagent_transfer == 0) // Magically transfer reagents. Because cryo magic. + beaker.reagents.trans_to(occupant, 1, efficiency * 0.25) // Transfer reagents. + beaker.reagents.expose(occupant, VAPOR) + air1.adjust_moles(/datum/gas/oxygen, -max(0,air1.get_moles(/datum/gas/oxygen) - 2 / efficiency)) //Let's use gas for this + if(++reagent_transfer >= 10 * efficiency) // Throttle reagent transfer (higher efficiency will transfer the same amount but consume less from the beaker). + reagent_transfer = 0 + + return 1 + +/obj/machinery/atmospherics/components/unary/cryo_cell/process_atmos() + ..() + + if(!on) + return + + var/datum/gas_mixture/air1 = airs[1] + + if(!nodes[1] || !airs[1] || air1.get_moles(/datum/gas/oxygen) < 5) // Turn off if the machine won't work. + on = FALSE + update_icon() + return + + if(occupant) + var/mob/living/mob_occupant = occupant + var/cold_protection = 0 + var/temperature_delta = air1.return_temperature() - mob_occupant.bodytemperature // The only semi-realistic thing here: share temperature between the cell and the occupant. + + if(ishuman(occupant)) + var/mob/living/carbon/human/H = occupant + cold_protection = H.get_cold_protection(air1.return_temperature()) + + if(abs(temperature_delta) > 1) + var/air_heat_capacity = air1.heat_capacity() + + var/heat = ((1 - cold_protection) * 0.1 + conduction_coefficient) * temperature_delta * (air_heat_capacity * heat_capacity / (air_heat_capacity + heat_capacity)) + + air1.set_temperature(max(air1.return_temperature() - heat / air_heat_capacity, TCMB)) + mob_occupant.adjust_bodytemperature(heat / heat_capacity, TCMB) + + air1.set_moles(/datum/gas/oxygen, max(0,air1.get_moles(/datum/gas/oxygen) - 0.5 / efficiency)) // Magically consume gas? Why not, we run on cryo magic. + +/obj/machinery/atmospherics/components/unary/cryo_cell/relaymove(mob/user) + if(message_cooldown <= world.time) + message_cooldown = world.time + 50 + to_chat(user, "[src]'s door won't budge!") + +/obj/machinery/atmospherics/components/unary/cryo_cell/open_machine(drop = FALSE) + if(!state_open && !panel_open) + on = FALSE + for(var/mob/M in contents) //only drop mobs + M.forceMove(get_turf(src)) + if(isliving(M)) + var/mob/living/L = M + L.update_mobility() + occupant = null + flick("pod-open-anim", src) + ..() + +/obj/machinery/atmospherics/components/unary/cryo_cell/close_machine(mob/living/carbon/user) + if((isnull(user) || istype(user)) && state_open && !panel_open) + flick("pod-close-anim", src) + ..(user) + return occupant + +/obj/machinery/atmospherics/components/unary/cryo_cell/container_resist(mob/living/user) + user.changeNext_move(CLICK_CD_BREAKOUT) + user.last_special = world.time + CLICK_CD_BREAKOUT + user.visible_message("You see [user] kicking against the glass of [src]!", \ + "You struggle inside [src], kicking the release with your foot... (this will take about [DisplayTimeText(breakout_time)].)", \ + "You hear a thump from [src].") + if(do_after(user, breakout_time, target = src)) + if(!user || user.stat != CONSCIOUS || user.loc != src ) + return + user.visible_message("[user] successfully broke out of [src]!", \ + "You successfully break out of [src]!") + open_machine() + +/obj/machinery/atmospherics/components/unary/cryo_cell/examine(mob/user) + . = ..() + if(occupant) + if(on) + . += "Someone's inside [src]!" + else + . += "You can barely make out a form floating in [src]." + else + . += "[src] seems empty." + +/obj/machinery/atmospherics/components/unary/cryo_cell/MouseDrop_T(mob/target, mob/user) + if(user.incapacitated() || !Adjacent(user) || !user.Adjacent(target) || !iscarbon(target) || !user.IsAdvancedToolUser()) + return + if(isliving(target)) + var/mob/living/L = target + if(L.incapacitated()) + close_machine(target) + else + user.visible_message("[user] starts shoving [target] inside [src].", "You start shoving [target] inside [src].") + if (do_after(user, 25, target=target)) + close_machine(target) + +/obj/machinery/atmospherics/components/unary/cryo_cell/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/reagent_containers/glass)) + . = 1 //no afterattack + if(beaker) + to_chat(user, "A beaker is already loaded into [src]!") + return + if(!user.transferItemToLoc(I, src)) + return + beaker = I + user.visible_message("[user] places [I] in [src].", \ + "You place [I] in [src].") + var/reagentlist = pretty_string_from_reagent_list(I.reagents.reagent_list) + log_game("[key_name(user)] added an [I] to cryo containing [reagentlist]") + return + if(!on && !occupant && !state_open && (default_deconstruction_screwdriver(user, "pod-off", "pod-off", I)) \ + || default_change_direction_wrench(user, I) \ + || default_pry_open(I) \ + || default_deconstruction_crowbar(I)) + update_icon() + return + else if(I.tool_behaviour == TOOL_SCREWDRIVER) + to_chat(user, "You can't access the maintenance panel while the pod is " \ + + (on ? "active" : (occupant ? "full" : "open")) + "!") + return + return ..() + +/obj/machinery/atmospherics/components/unary/cryo_cell/ui_state(mob/user) + return GLOB.notcontained_state + + +/obj/machinery/atmospherics/components/unary/cryo_cell/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "Cryo", name) + ui.open() + +/obj/machinery/atmospherics/components/unary/cryo_cell/ui_data() + var/list/data = list() + data["isOperating"] = on + data["hasOccupant"] = occupant ? TRUE : FALSE + data["isOpen"] = state_open + data["autoEject"] = autoeject + + data["occupant"] = list() + if(occupant) + var/mob/living/mob_occupant = occupant + data["occupant"]["name"] = mob_occupant.name + switch(mob_occupant.stat) + if(CONSCIOUS) + data["occupant"]["stat"] = "Conscious" + data["occupant"]["statstate"] = "good" + if(SOFT_CRIT) + data["occupant"]["stat"] = "Conscious" + data["occupant"]["statstate"] = "average" + if(UNCONSCIOUS) + data["occupant"]["stat"] = "Unconscious" + data["occupant"]["statstate"] = "average" + if(DEAD) + data["occupant"]["stat"] = "Dead" + data["occupant"]["statstate"] = "bad" + data["occupant"]["health"] = round(mob_occupant.health, 1) + data["occupant"]["maxHealth"] = mob_occupant.maxHealth + data["occupant"]["minHealth"] = HEALTH_THRESHOLD_DEAD + data["occupant"]["bruteLoss"] = round(mob_occupant.getBruteLoss(), 1) + data["occupant"]["oxyLoss"] = round(mob_occupant.getOxyLoss(), 1) + data["occupant"]["toxLoss"] = round(mob_occupant.getToxLoss(), 1) + data["occupant"]["fireLoss"] = round(mob_occupant.getFireLoss(), 1) + data["occupant"]["bodyTemperature"] = round(mob_occupant.bodytemperature, 1) + if(mob_occupant.bodytemperature < TCRYO) + data["occupant"]["temperaturestatus"] = "good" + else if(mob_occupant.bodytemperature < T0C) + data["occupant"]["temperaturestatus"] = "average" + else + data["occupant"]["temperaturestatus"] = "bad" + + var/datum/gas_mixture/air1 = airs[1] + data["cellTemperature"] = round(air1.return_temperature(), 1) + + data["isBeakerLoaded"] = beaker ? TRUE : FALSE + var/beakerContents = list() + if(beaker && beaker.reagents && beaker.reagents.reagent_list.len) + for(var/datum/reagent/R in beaker.reagents.reagent_list) + beakerContents += list(list("name" = R.name, "volume" = R.volume)) + data["beakerContents"] = beakerContents + return data + +/obj/machinery/atmospherics/components/unary/cryo_cell/ui_act(action, params) + if(..()) + return + switch(action) + if("power") + if(on) + on = FALSE + else if(!state_open) + on = TRUE + update_icon() + . = TRUE + if("door") + if(state_open) + close_machine() + else + open_machine() + . = TRUE + if("autoeject") + autoeject = !autoeject + . = TRUE + if("ejectbeaker") + if(beaker) + beaker.forceMove(drop_location()) + if(Adjacent(usr) && !issilicon(usr)) + usr.put_in_hands(beaker) + beaker = null + . = TRUE + +/obj/machinery/atmospherics/components/unary/cryo_cell/CtrlClick(mob/user) + if(can_interact(user) && !state_open) + on = !on + update_icon() + return ..() + +/obj/machinery/atmospherics/components/unary/cryo_cell/AltClick(mob/user) + if(can_interact(user)) + if(state_open) + close_machine() + else + open_machine() + return ..() + +/obj/machinery/atmospherics/components/unary/cryo_cell/update_remote_sight(mob/living/user) + return // we don't see the pipe network while inside cryo. + +/obj/machinery/atmospherics/components/unary/cryo_cell/get_remote_view_fullscreens(mob/user) + user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 1) + +/obj/machinery/atmospherics/components/unary/cryo_cell/can_crawl_through() + return // can't ventcrawl in or out of cryo. + +/obj/machinery/atmospherics/components/unary/cryo_cell/can_see_pipes() + return 0 // you can't see the pipe network when inside a cryo cell. + +/obj/machinery/atmospherics/components/unary/cryo_cell/return_temperature() + var/datum/gas_mixture/G = airs[1] + + if(G.total_moles() > 10) + return G.return_temperature() + return ..() + +/obj/machinery/atmospherics/components/unary/cryo_cell/default_change_direction_wrench(mob/user, obj/item/wrench/W) + . = ..() + if(.) + SetInitDirections() + var/obj/machinery/atmospherics/node = nodes[1] + if(node) + node.disconnect(src) + nodes[1] = null + nullifyPipenet(parents[1]) + atmosinit() + node = nodes[1] + if(node) + node.atmosinit() + node.addMember(src) + SSair.add_to_rebuild_queue(src) + +#undef CRYOMOBS diff --git a/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm b/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm index 4ab0b47bb04a..572892ff826b 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm @@ -23,9 +23,6 @@ pipe_state = "injector" - ui_x = 310 - ui_y = 115 - /obj/machinery/atmospherics/components/unary/outlet_injector/CtrlClick(mob/user) if(can_interact(user)) on = !on @@ -144,11 +141,10 @@ update_icon() -/obj/machinery/atmospherics/components/unary/outlet_injector/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/machinery/atmospherics/components/unary/outlet_injector/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "AtmosPump", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, "AtmosPump", name) ui.open() /obj/machinery/atmospherics/components/unary/outlet_injector/ui_data() diff --git a/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm b/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm index 87cc3af7ad0c..972c4ee1d6ba 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm @@ -10,8 +10,6 @@ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 30) layer = OBJ_LAYER circuit = /obj/item/circuitboard/machine/thermomachine - ui_x = 300 - ui_y = 230 pipe_flags = PIPING_ONE_PER_TURF @@ -123,11 +121,10 @@ return ..() return UI_CLOSE -/obj/machinery/atmospherics/components/unary/thermomachine/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/machinery/atmospherics/components/unary/thermomachine/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "ThermoMachine", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, "ThermoMachine", name) ui.open() /obj/machinery/atmospherics/components/unary/thermomachine/ui_data(mob/user) diff --git a/code/modules/atmospherics/machinery/other/meter.dm b/code/modules/atmospherics/machinery/other/meter.dm index 84a60550c2a3..2ab2f0890d05 100644 --- a/code/modules/atmospherics/machinery/other/meter.dm +++ b/code/modules/atmospherics/machinery/other/meter.dm @@ -1,146 +1,146 @@ -/obj/machinery/meter - name = "gas flow meter" - desc = "It measures something." - icon = 'icons/obj/atmospherics/pipes/meter.dmi' - icon_state = "meterX" - layer = GAS_PUMP_LAYER - power_channel = AREA_USAGE_ENVIRON - use_power = IDLE_POWER_USE - idle_power_usage = 2 - active_power_usage = 4 - max_integrity = 150 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 40, "acid" = 0) - var/frequency = 0 - var/atom/target - var/id_tag - var/target_layer = PIPING_LAYER_DEFAULT - -/obj/machinery/meter/atmos - frequency = FREQ_ATMOS_STORAGE - -/obj/machinery/meter/atmos/atmos_waste_loop - name = "waste loop gas flow meter" - id_tag = ATMOS_GAS_MONITOR_LOOP_ATMOS_WASTE - -/obj/machinery/meter/atmos/distro_loop - name = "distribution loop gas flow meter" - id_tag = ATMOS_GAS_MONITOR_LOOP_DISTRIBUTION - -/obj/machinery/meter/Destroy() - SSair.atmos_machinery -= src - target = null - return ..() - -/obj/machinery/meter/Initialize(mapload, new_piping_layer) - if(!isnull(new_piping_layer)) - target_layer = new_piping_layer - SSair.atmos_machinery += src - if(!target) - reattach_to_layer() - return ..() - -/obj/machinery/meter/proc/reattach_to_layer() - var/obj/machinery/atmospherics/candidate - for(var/obj/machinery/atmospherics/pipe/pipe in loc) - if(pipe.piping_layer == target_layer) - candidate = pipe - if(candidate) - target = candidate - setAttachLayer(candidate.piping_layer) - -/obj/machinery/meter/proc/setAttachLayer(new_layer) - target_layer = new_layer - PIPING_LAYER_DOUBLE_SHIFT(src, target_layer) - -/obj/machinery/meter/process_atmos() - if(!(target?.flags_1 & INITIALIZED_1)) - icon_state = "meterX" - return 0 - - if(machine_stat & (BROKEN|NOPOWER)) - icon_state = "meter0" - return 0 - - use_power(5) - - var/datum/gas_mixture/environment = target.return_air() - if(!environment) - icon_state = "meterX" - return 0 - - var/env_pressure = environment.return_pressure() - if(env_pressure <= 0.15*ONE_ATMOSPHERE) - icon_state = "meter0" - else if(env_pressure <= 1.8*ONE_ATMOSPHERE) - var/val = round(env_pressure/(ONE_ATMOSPHERE*0.3) + 0.5) - icon_state = "meter1_[val]" - else if(env_pressure <= 30*ONE_ATMOSPHERE) - var/val = round(env_pressure/(ONE_ATMOSPHERE*5)-0.35) + 1 - icon_state = "meter2_[val]" - else if(env_pressure <= 59*ONE_ATMOSPHERE) - var/val = round(env_pressure/(ONE_ATMOSPHERE*5) - 6) + 1 - icon_state = "meter3_[val]" - else - icon_state = "meter4" - - if(frequency) - var/datum/radio_frequency/radio_connection = SSradio.return_frequency(frequency) - - if(!radio_connection) - return - - var/datum/signal/signal = new(list( - "id_tag" = id_tag, - "device" = "AM", - "pressure" = round(env_pressure), - "sigtype" = "status" - )) - radio_connection.post_signal(src, signal) - -/obj/machinery/meter/proc/status() - if (target) - var/datum/gas_mixture/environment = target.return_air() - if(environment) - . = "The pressure gauge reads [round(environment.return_pressure(), 0.01)] kPa; [round(environment.return_temperature(),0.01)] K ([round(environment.return_temperature()-T0C,0.01)]°C)." - else - . = "The sensor error light is blinking." - else - . = "The connect error light is blinking." - -/obj/machinery/meter/examine(mob/user) - . = ..() - . += status() - -/obj/machinery/meter/wrench_act(mob/user, obj/item/I) - ..() - to_chat(user, "You begin to unfasten \the [src]...") - if (I.use_tool(src, user, 40, volume=50)) - user.visible_message( - "[user] unfastens \the [src].", - "You unfasten \the [src].", - "You hear ratchet.") - deconstruct() - return TRUE - -/obj/machinery/meter/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - new /obj/item/pipe_meter(loc) - qdel(src) - -/obj/machinery/meter/interact(mob/user) - if(machine_stat & (NOPOWER|BROKEN)) - return - else - to_chat(user, status()) - -/obj/machinery/meter/singularity_pull(S, current_size) - ..() - if(current_size >= STAGE_FIVE) - deconstruct() - -// TURF METER - REPORTS A TILE'S AIR CONTENTS -// why are you yelling? -/obj/machinery/meter/turf - -/obj/machinery/meter/turf/reattach_to_layer() - target = loc +/obj/machinery/meter + name = "gas flow meter" + desc = "It measures something." + icon = 'icons/obj/atmospherics/pipes/meter.dmi' + icon_state = "meterX" + layer = GAS_PUMP_LAYER + power_channel = AREA_USAGE_ENVIRON + use_power = IDLE_POWER_USE + idle_power_usage = 2 + active_power_usage = 4 + max_integrity = 150 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 40, "acid" = 0) + var/frequency = 0 + var/atom/target + var/id_tag + var/target_layer = PIPING_LAYER_DEFAULT + +/obj/machinery/meter/atmos + frequency = FREQ_ATMOS_STORAGE + +/obj/machinery/meter/atmos/atmos_waste_loop + name = "waste loop gas flow meter" + id_tag = ATMOS_GAS_MONITOR_LOOP_ATMOS_WASTE + +/obj/machinery/meter/atmos/distro_loop + name = "distribution loop gas flow meter" + id_tag = ATMOS_GAS_MONITOR_LOOP_DISTRIBUTION + +/obj/machinery/meter/Destroy() + SSair.atmos_machinery -= src + target = null + return ..() + +/obj/machinery/meter/Initialize(mapload, new_piping_layer) + if(!isnull(new_piping_layer)) + target_layer = new_piping_layer + SSair.atmos_machinery += src + if(!target) + reattach_to_layer() + return ..() + +/obj/machinery/meter/proc/reattach_to_layer() + var/obj/machinery/atmospherics/candidate + for(var/obj/machinery/atmospherics/pipe/pipe in loc) + if(pipe.piping_layer == target_layer) + candidate = pipe + if(candidate) + target = candidate + setAttachLayer(candidate.piping_layer) + +/obj/machinery/meter/proc/setAttachLayer(new_layer) + target_layer = new_layer + PIPING_LAYER_DOUBLE_SHIFT(src, target_layer) + +/obj/machinery/meter/process_atmos() + if(!(target?.flags_1 & INITIALIZED_1)) + icon_state = "meterX" + return 0 + + if(machine_stat & (BROKEN|NOPOWER)) + icon_state = "meter0" + return 0 + + use_power(5) + + var/datum/gas_mixture/environment = target.return_air() + if(!environment) + icon_state = "meterX" + return 0 + + var/env_pressure = environment.return_pressure() + if(env_pressure <= 0.15*ONE_ATMOSPHERE) + icon_state = "meter0" + else if(env_pressure <= 1.8*ONE_ATMOSPHERE) + var/val = round(env_pressure/(ONE_ATMOSPHERE*0.3) + 0.5) + icon_state = "meter1_[val]" + else if(env_pressure <= 30*ONE_ATMOSPHERE) + var/val = round(env_pressure/(ONE_ATMOSPHERE*5)-0.35) + 1 + icon_state = "meter2_[val]" + else if(env_pressure <= 59*ONE_ATMOSPHERE) + var/val = round(env_pressure/(ONE_ATMOSPHERE*5) - 6) + 1 + icon_state = "meter3_[val]" + else + icon_state = "meter4" + + if(frequency) + var/datum/radio_frequency/radio_connection = SSradio.return_frequency(frequency) + + if(!radio_connection) + return + + var/datum/signal/signal = new(list( + "id_tag" = id_tag, + "device" = "AM", + "pressure" = round(env_pressure), + "sigtype" = "status" + )) + radio_connection.post_signal(src, signal) + +/obj/machinery/meter/proc/status() + if (target) + var/datum/gas_mixture/environment = target.return_air() + if(environment) + . = "The pressure gauge reads [round(environment.return_pressure(), 0.01)] kPa; [round(environment.return_temperature(),0.01)] K ([round(environment.return_temperature()-T0C,0.01)]°C)." + else + . = "The sensor error light is blinking." + else + . = "The connect error light is blinking." + +/obj/machinery/meter/examine(mob/user) + . = ..() + . += status() + +/obj/machinery/meter/wrench_act(mob/user, obj/item/I) + ..() + to_chat(user, "You begin to unfasten \the [src]...") + if (I.use_tool(src, user, 40, volume=50)) + user.visible_message( + "[user] unfastens \the [src].", + "You unfasten \the [src].", + "You hear ratchet.") + deconstruct() + return TRUE + +/obj/machinery/meter/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + new /obj/item/pipe_meter(loc) + qdel(src) + +/obj/machinery/meter/interact(mob/user) + if(machine_stat & (NOPOWER|BROKEN)) + return + else + to_chat(user, status()) + +/obj/machinery/meter/singularity_pull(S, current_size) + ..() + if(current_size >= STAGE_FIVE) + deconstruct() + +// TURF METER - REPORTS A TILE'S AIR CONTENTS +// why are you yelling? +/obj/machinery/meter/turf + +/obj/machinery/meter/turf/reattach_to_layer() + target = loc diff --git a/code/modules/atmospherics/machinery/pipes/layermanifold.dm b/code/modules/atmospherics/machinery/pipes/layermanifold.dm index 5c598c0d9ecb..020da04e3beb 100644 --- a/code/modules/atmospherics/machinery/pipes/layermanifold.dm +++ b/code/modules/atmospherics/machinery/pipes/layermanifold.dm @@ -1,138 +1,138 @@ -/obj/machinery/atmospherics/pipe/layer_manifold - name = "layer adaptor" - icon = 'icons/obj/atmospherics/pipes/manifold.dmi' - icon_state = "manifoldlayer" - desc = "A special pipe to bridge pipe layers with." - dir = SOUTH - initialize_directions = NORTH|SOUTH - pipe_flags = PIPING_ALL_LAYER | PIPING_DEFAULT_LAYER_ONLY | PIPING_CARDINAL_AUTONORMALIZE - piping_layer = PIPING_LAYER_DEFAULT - device_type = 0 - volume = 260 - construction_type = /obj/item/pipe/binary - pipe_state = "manifoldlayer" - - FASTDMM_PROP(\ - pipe_type = PIPE_TYPE_STRAIGHT,\ - pipe_interference_group = list("atmos-1","atmos-2","atmos-3")\ - ) - - var/list/front_nodes - var/list/back_nodes - -/obj/machinery/atmospherics/pipe/layer_manifold/Initialize() - front_nodes = list() - back_nodes = list() - icon_state = "manifoldlayer_center" - return ..() - -/obj/machinery/atmospherics/pipe/layer_manifold/Destroy() - nullifyAllNodes() - return ..() - -/obj/machinery/atmospherics/pipe/layer_manifold/proc/nullifyAllNodes() - var/list/obj/machinery/atmospherics/needs_nullifying = get_all_connected_nodes() - front_nodes = null - back_nodes = null - nodes = list() - for(var/obj/machinery/atmospherics/A in needs_nullifying) - A.disconnect(src) - SSair.add_to_rebuild_queue(A) - -/obj/machinery/atmospherics/pipe/layer_manifold/proc/get_all_connected_nodes() - return front_nodes + back_nodes + nodes - -/obj/machinery/atmospherics/pipe/layer_manifold/update_icon() //HEAVILY WIP FOR UPDATE ICONS!! - cut_overlays() - layer = initial(layer) + (PIPING_LAYER_MAX * PIPING_LAYER_LCHANGE) //This is above everything else. - - for(var/node in front_nodes) - add_attached_images(node) - for(var/node in back_nodes) - add_attached_images(node) - -/obj/machinery/atmospherics/pipe/layer_manifold/proc/add_attached_images(obj/machinery/atmospherics/A) - if(!A) - return - if(istype(A, /obj/machinery/atmospherics/pipe/layer_manifold)) - for(var/i in PIPING_LAYER_MIN to PIPING_LAYER_MAX) - add_attached_image(get_dir(src, A), i) - return - add_attached_image(get_dir(src, A), A.piping_layer, A.pipe_color) - -/obj/machinery/atmospherics/pipe/layer_manifold/proc/add_attached_image(p_dir, p_layer, p_color = null) - var/image/I - - if(p_color) - I = getpipeimage(icon, "pipe", p_dir, p_color, piping_layer = p_layer) - else - I = getpipeimage(icon, "pipe", p_dir, piping_layer = p_layer) - - I.layer = layer - 0.01 - add_overlay(I) - -/obj/machinery/atmospherics/pipe/layer_manifold/SetInitDirections() - switch(dir) - if(NORTH || SOUTH) - initialize_directions = NORTH|SOUTH - if(EAST || WEST) - initialize_directions = EAST|WEST - -/obj/machinery/atmospherics/pipe/layer_manifold/isConnectable(obj/machinery/atmospherics/target, given_layer) - if(!given_layer) - return TRUE - . = ..() - -/obj/machinery/atmospherics/pipe/layer_manifold/proc/findAllConnections() - front_nodes = list() - back_nodes = list() - var/list/new_nodes = list() - for(var/iter in PIPING_LAYER_MIN to PIPING_LAYER_MAX) - var/obj/machinery/atmospherics/foundfront = findConnecting(dir, iter) - var/obj/machinery/atmospherics/foundback = findConnecting(turn(dir, 180), iter) - front_nodes += foundfront - back_nodes += foundback - if(foundfront && !QDELETED(foundfront)) - new_nodes += foundfront - if(foundback && !QDELETED(foundback)) - new_nodes += foundback - update_icon() - return new_nodes - -/obj/machinery/atmospherics/pipe/layer_manifold/atmosinit() - normalize_cardinal_directions() - findAllConnections() - -/obj/machinery/atmospherics/pipe/layer_manifold/setPipingLayer() - piping_layer = PIPING_LAYER_DEFAULT - -/obj/machinery/atmospherics/pipe/layer_manifold/pipeline_expansion() - return get_all_connected_nodes() - -/obj/machinery/atmospherics/pipe/layer_manifold/disconnect(obj/machinery/atmospherics/reference) - if(istype(reference, /obj/machinery/atmospherics/pipe)) - var/obj/machinery/atmospherics/pipe/P = reference - P.destroy_network() - while(reference in get_all_connected_nodes()) - if(reference in nodes) - var/i = nodes.Find(reference) - nodes[i] = null - if(reference in front_nodes) - var/i = front_nodes.Find(reference) - front_nodes[i] = null - if(reference in back_nodes) - var/i = back_nodes.Find(reference) - back_nodes[i] = null - update_icon() - -/obj/machinery/atmospherics/pipe/layer_manifold/relaymove(mob/living/user, dir) - if(initialize_directions & dir) - return ..() - if((NORTH|EAST) & dir) - user.ventcrawl_layer = clamp(user.ventcrawl_layer + 1, PIPING_LAYER_MIN, PIPING_LAYER_MAX) - if((SOUTH|WEST) & dir) - user.ventcrawl_layer = clamp(user.ventcrawl_layer - 1, PIPING_LAYER_MIN, PIPING_LAYER_MAX) - to_chat(user, "You align yourself with the [user.ventcrawl_layer]\th output.") - -/obj/machinery/atmospherics/pipe/layer_manifold/visible - layer = GAS_PIPE_VISIBLE_LAYER +/obj/machinery/atmospherics/pipe/layer_manifold + name = "layer adaptor" + icon = 'icons/obj/atmospherics/pipes/manifold.dmi' + icon_state = "manifoldlayer" + desc = "A special pipe to bridge pipe layers with." + dir = SOUTH + initialize_directions = NORTH|SOUTH + pipe_flags = PIPING_ALL_LAYER | PIPING_DEFAULT_LAYER_ONLY | PIPING_CARDINAL_AUTONORMALIZE + piping_layer = PIPING_LAYER_DEFAULT + device_type = 0 + volume = 260 + construction_type = /obj/item/pipe/binary + pipe_state = "manifoldlayer" + + FASTDMM_PROP(\ + pipe_type = PIPE_TYPE_STRAIGHT,\ + pipe_interference_group = list("atmos-1","atmos-2","atmos-3")\ + ) + + var/list/front_nodes + var/list/back_nodes + +/obj/machinery/atmospherics/pipe/layer_manifold/Initialize() + front_nodes = list() + back_nodes = list() + icon_state = "manifoldlayer_center" + return ..() + +/obj/machinery/atmospherics/pipe/layer_manifold/Destroy() + nullifyAllNodes() + return ..() + +/obj/machinery/atmospherics/pipe/layer_manifold/proc/nullifyAllNodes() + var/list/obj/machinery/atmospherics/needs_nullifying = get_all_connected_nodes() + front_nodes = null + back_nodes = null + nodes = list() + for(var/obj/machinery/atmospherics/A in needs_nullifying) + A.disconnect(src) + SSair.add_to_rebuild_queue(A) + +/obj/machinery/atmospherics/pipe/layer_manifold/proc/get_all_connected_nodes() + return front_nodes + back_nodes + nodes + +/obj/machinery/atmospherics/pipe/layer_manifold/update_icon() //HEAVILY WIP FOR UPDATE ICONS!! + cut_overlays() + layer = initial(layer) + (PIPING_LAYER_MAX * PIPING_LAYER_LCHANGE) //This is above everything else. + + for(var/node in front_nodes) + add_attached_images(node) + for(var/node in back_nodes) + add_attached_images(node) + +/obj/machinery/atmospherics/pipe/layer_manifold/proc/add_attached_images(obj/machinery/atmospherics/A) + if(!A) + return + if(istype(A, /obj/machinery/atmospherics/pipe/layer_manifold)) + for(var/i in PIPING_LAYER_MIN to PIPING_LAYER_MAX) + add_attached_image(get_dir(src, A), i) + return + add_attached_image(get_dir(src, A), A.piping_layer, A.pipe_color) + +/obj/machinery/atmospherics/pipe/layer_manifold/proc/add_attached_image(p_dir, p_layer, p_color = null) + var/image/I + + if(p_color) + I = getpipeimage(icon, "pipe", p_dir, p_color, piping_layer = p_layer) + else + I = getpipeimage(icon, "pipe", p_dir, piping_layer = p_layer) + + I.layer = layer - 0.01 + add_overlay(I) + +/obj/machinery/atmospherics/pipe/layer_manifold/SetInitDirections() + switch(dir) + if(NORTH || SOUTH) + initialize_directions = NORTH|SOUTH + if(EAST || WEST) + initialize_directions = EAST|WEST + +/obj/machinery/atmospherics/pipe/layer_manifold/isConnectable(obj/machinery/atmospherics/target, given_layer) + if(!given_layer) + return TRUE + . = ..() + +/obj/machinery/atmospherics/pipe/layer_manifold/proc/findAllConnections() + front_nodes = list() + back_nodes = list() + var/list/new_nodes = list() + for(var/iter in PIPING_LAYER_MIN to PIPING_LAYER_MAX) + var/obj/machinery/atmospherics/foundfront = findConnecting(dir, iter) + var/obj/machinery/atmospherics/foundback = findConnecting(turn(dir, 180), iter) + front_nodes += foundfront + back_nodes += foundback + if(foundfront && !QDELETED(foundfront)) + new_nodes += foundfront + if(foundback && !QDELETED(foundback)) + new_nodes += foundback + update_icon() + return new_nodes + +/obj/machinery/atmospherics/pipe/layer_manifold/atmosinit() + normalize_cardinal_directions() + findAllConnections() + +/obj/machinery/atmospherics/pipe/layer_manifold/setPipingLayer() + piping_layer = PIPING_LAYER_DEFAULT + +/obj/machinery/atmospherics/pipe/layer_manifold/pipeline_expansion() + return get_all_connected_nodes() + +/obj/machinery/atmospherics/pipe/layer_manifold/disconnect(obj/machinery/atmospherics/reference) + if(istype(reference, /obj/machinery/atmospherics/pipe)) + var/obj/machinery/atmospherics/pipe/P = reference + P.destroy_network() + while(reference in get_all_connected_nodes()) + if(reference in nodes) + var/i = nodes.Find(reference) + nodes[i] = null + if(reference in front_nodes) + var/i = front_nodes.Find(reference) + front_nodes[i] = null + if(reference in back_nodes) + var/i = back_nodes.Find(reference) + back_nodes[i] = null + update_icon() + +/obj/machinery/atmospherics/pipe/layer_manifold/relaymove(mob/living/user, dir) + if(initialize_directions & dir) + return ..() + if((NORTH|EAST) & dir) + user.ventcrawl_layer = clamp(user.ventcrawl_layer + 1, PIPING_LAYER_MIN, PIPING_LAYER_MAX) + if((SOUTH|WEST) & dir) + user.ventcrawl_layer = clamp(user.ventcrawl_layer - 1, PIPING_LAYER_MIN, PIPING_LAYER_MAX) + to_chat(user, "You align yourself with the [user.ventcrawl_layer]\th output.") + +/obj/machinery/atmospherics/pipe/layer_manifold/visible + layer = GAS_PIPE_VISIBLE_LAYER diff --git a/code/modules/atmospherics/machinery/portable/canister.dm b/code/modules/atmospherics/machinery/portable/canister.dm index 70ab7e01160f..7ca0854d454a 100644 --- a/code/modules/atmospherics/machinery/portable/canister.dm +++ b/code/modules/atmospherics/machinery/portable/canister.dm @@ -5,8 +5,6 @@ desc = "A canister for the storage of gas." icon_state = "yellow" density = TRUE - ui_x = 300 - ui_y = 232 var/valve_open = FALSE var/obj/machinery/atmospherics/components/binary/passive_gate/pump @@ -337,11 +335,13 @@ update_icon() -/obj/machinery/portable_atmospherics/canister/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.physical_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/machinery/portable_atmospherics/canister/ui_state(mob/user) + return GLOB.physical_state + +/obj/machinery/portable_atmospherics/canister/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "Canister", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, "Canister", name) ui.open() /obj/machinery/portable_atmospherics/canister/ui_data() diff --git a/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm b/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm index e99113ec88b3..fdcdd5c76f98 100644 --- a/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm +++ b/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm @@ -1,165 +1,165 @@ -/obj/machinery/portable_atmospherics - name = "portable_atmospherics" - icon = 'icons/obj/atmos.dmi' - use_power = NO_POWER_USE - max_integrity = 250 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 60, "acid" = 30) - anchored = FALSE - - var/datum/gas_mixture/air_contents - var/obj/machinery/atmospherics/components/unary/portables_connector/connected_port - var/obj/item/tank/holding - - var/volume = 0 - - var/maximum_pressure = 90 * ONE_ATMOSPHERE - -/obj/machinery/portable_atmospherics/New() - ..() - SSair.atmos_machinery += src - -/obj/machinery/portable_atmospherics/Initialize() - . = ..() - air_contents = new(volume) - air_contents.set_temperature(T20C) - -/obj/machinery/portable_atmospherics/Destroy() - SSair.atmos_machinery -= src - - disconnect() - qdel(air_contents) - air_contents = null - - return ..() - -/obj/machinery/portable_atmospherics/ex_act(severity, target) - if(severity == 1 || target == src) - if(resistance_flags & INDESTRUCTIBLE) - return //Indestructable cans shouldn't release air - - //This explosion will destroy the can, release its air. - var/turf/T = get_turf(src) - T.assume_air(air_contents) - T.air_update_turf() - - return ..() - -/obj/machinery/portable_atmospherics/process_atmos() - if(!connected_port) // Pipe network handles reactions if connected. - air_contents.react(src) - -/obj/machinery/portable_atmospherics/return_air() - return air_contents - -/obj/machinery/portable_atmospherics/return_analyzable_air() - return air_contents - -/obj/machinery/portable_atmospherics/proc/connect(obj/machinery/atmospherics/components/unary/portables_connector/new_port) - //Make sure not already connected to something else - if(connected_port || !new_port || new_port.connected_device) - return FALSE - - //Make sure are close enough for a valid connection - if(new_port.loc != get_turf(src)) - return FALSE - - //Perform the connection - connected_port = new_port - connected_port.connected_device = src - var/datum/pipeline/connected_port_parent = connected_port.parents[1] - connected_port_parent.reconcile_air() - - anchored = TRUE //Prevent movement - pixel_x = new_port.pixel_x - pixel_y = new_port.pixel_y - update_icon() - return TRUE - -/obj/machinery/portable_atmospherics/Move() - . = ..() - if(.) - disconnect() - -/obj/machinery/portable_atmospherics/proc/disconnect() - if(!connected_port) - return FALSE - anchored = FALSE - connected_port.connected_device = null - connected_port = null - pixel_x = 0 - pixel_y = 0 - update_icon() - return TRUE - -/obj/machinery/portable_atmospherics/AltClick(mob/living/user) - . = ..() - if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, !ismonkey(user)) || !can_interact(user)) - return - if(holding) - to_chat(user, "You remove [holding] from [src].") - replace_tank(user, TRUE) - -/obj/machinery/portable_atmospherics/examine(mob/user) - . = ..() - if(holding) - . += "\The [src] contains [holding]. Alt-click [src] to remove it."+\ - "Click [src] with another gas tank to hot swap [holding]." - -/obj/machinery/portable_atmospherics/proc/replace_tank(mob/living/user, close_valve, obj/item/tank/new_tank) - if(!user) - return FALSE - if(holding) - user.put_in_hands(holding) - holding = null - if(new_tank) - holding = new_tank - update_icon() - return TRUE - -/obj/machinery/portable_atmospherics/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/tank)) - if(!(machine_stat & BROKEN)) - var/obj/item/tank/T = W - if(!user.transferItemToLoc(T, src)) - return - to_chat(user, "[holding ? "In one smooth motion you pop [holding] out of [src]'s connector and replace it with [T]" : "You insert [T] into [src]"].") - investigate_log("had its internal [holding] swapped with [T] by [key_name(user)].", INVESTIGATE_ATMOS) - replace_tank(user, FALSE, T) - update_icon() - else if(W.tool_behaviour == TOOL_WRENCH) - if(!(machine_stat & BROKEN)) - if(connected_port) - investigate_log("was disconnected from [connected_port] by [key_name(user)].", INVESTIGATE_ATMOS) - disconnect() - W.play_tool_sound(src) - user.visible_message( \ - "[user] disconnects [src].", \ - "You unfasten [src] from the port.", \ - "You hear a ratchet.") - update_icon() - return - else - var/obj/machinery/atmospherics/components/unary/portables_connector/possible_port = locate(/obj/machinery/atmospherics/components/unary/portables_connector) in loc - if(!possible_port) - to_chat(user, "Nothing happens.") - return - if(!connect(possible_port)) - to_chat(user, "[name] failed to connect to the port.") - return - W.play_tool_sound(src) - user.visible_message( \ - "[user] connects [src].", \ - "You fasten [src] to the port.", \ - "You hear a ratchet.") - update_icon() - investigate_log("was connected to [possible_port] by [key_name(user)].", INVESTIGATE_ATMOS) - else - return ..() - -/obj/machinery/portable_atmospherics/attacked_by(obj/item/I, mob/user) - if(I.force < 10 && !(machine_stat & BROKEN)) - take_damage(0) - else - investigate_log("was smacked with \a [I] by [key_name(user)].", INVESTIGATE_ATMOS) - add_fingerprint(user) - ..() +/obj/machinery/portable_atmospherics + name = "portable_atmospherics" + icon = 'icons/obj/atmos.dmi' + use_power = NO_POWER_USE + max_integrity = 250 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 60, "acid" = 30) + anchored = FALSE + + var/datum/gas_mixture/air_contents + var/obj/machinery/atmospherics/components/unary/portables_connector/connected_port + var/obj/item/tank/holding + + var/volume = 0 + + var/maximum_pressure = 90 * ONE_ATMOSPHERE + +/obj/machinery/portable_atmospherics/New() + ..() + SSair.atmos_machinery += src + +/obj/machinery/portable_atmospherics/Initialize() + . = ..() + air_contents = new(volume) + air_contents.set_temperature(T20C) + +/obj/machinery/portable_atmospherics/Destroy() + SSair.atmos_machinery -= src + + disconnect() + qdel(air_contents) + air_contents = null + + return ..() + +/obj/machinery/portable_atmospherics/ex_act(severity, target) + if(severity == 1 || target == src) + if(resistance_flags & INDESTRUCTIBLE) + return //Indestructable cans shouldn't release air + + //This explosion will destroy the can, release its air. + var/turf/T = get_turf(src) + T.assume_air(air_contents) + T.air_update_turf() + + return ..() + +/obj/machinery/portable_atmospherics/process_atmos() + if(!connected_port) // Pipe network handles reactions if connected. + air_contents.react(src) + +/obj/machinery/portable_atmospherics/return_air() + return air_contents + +/obj/machinery/portable_atmospherics/return_analyzable_air() + return air_contents + +/obj/machinery/portable_atmospherics/proc/connect(obj/machinery/atmospherics/components/unary/portables_connector/new_port) + //Make sure not already connected to something else + if(connected_port || !new_port || new_port.connected_device) + return FALSE + + //Make sure are close enough for a valid connection + if(new_port.loc != get_turf(src)) + return FALSE + + //Perform the connection + connected_port = new_port + connected_port.connected_device = src + var/datum/pipeline/connected_port_parent = connected_port.parents[1] + connected_port_parent.reconcile_air() + + anchored = TRUE //Prevent movement + pixel_x = new_port.pixel_x + pixel_y = new_port.pixel_y + update_icon() + return TRUE + +/obj/machinery/portable_atmospherics/Move() + . = ..() + if(.) + disconnect() + +/obj/machinery/portable_atmospherics/proc/disconnect() + if(!connected_port) + return FALSE + anchored = FALSE + connected_port.connected_device = null + connected_port = null + pixel_x = 0 + pixel_y = 0 + update_icon() + return TRUE + +/obj/machinery/portable_atmospherics/AltClick(mob/living/user) + . = ..() + if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, !ismonkey(user)) || !can_interact(user)) + return + if(holding) + to_chat(user, "You remove [holding] from [src].") + replace_tank(user, TRUE) + +/obj/machinery/portable_atmospherics/examine(mob/user) + . = ..() + if(holding) + . += "\The [src] contains [holding]. Alt-click [src] to remove it."+\ + "Click [src] with another gas tank to hot swap [holding]." + +/obj/machinery/portable_atmospherics/proc/replace_tank(mob/living/user, close_valve, obj/item/tank/new_tank) + if(!user) + return FALSE + if(holding) + user.put_in_hands(holding) + holding = null + if(new_tank) + holding = new_tank + update_icon() + return TRUE + +/obj/machinery/portable_atmospherics/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/tank)) + if(!(machine_stat & BROKEN)) + var/obj/item/tank/T = W + if(!user.transferItemToLoc(T, src)) + return + to_chat(user, "[holding ? "In one smooth motion you pop [holding] out of [src]'s connector and replace it with [T]" : "You insert [T] into [src]"].") + investigate_log("had its internal [holding] swapped with [T] by [key_name(user)].", INVESTIGATE_ATMOS) + replace_tank(user, FALSE, T) + update_icon() + else if(W.tool_behaviour == TOOL_WRENCH) + if(!(machine_stat & BROKEN)) + if(connected_port) + investigate_log("was disconnected from [connected_port] by [key_name(user)].", INVESTIGATE_ATMOS) + disconnect() + W.play_tool_sound(src) + user.visible_message( \ + "[user] disconnects [src].", \ + "You unfasten [src] from the port.", \ + "You hear a ratchet.") + update_icon() + return + else + var/obj/machinery/atmospherics/components/unary/portables_connector/possible_port = locate(/obj/machinery/atmospherics/components/unary/portables_connector) in loc + if(!possible_port) + to_chat(user, "Nothing happens.") + return + if(!connect(possible_port)) + to_chat(user, "[name] failed to connect to the port.") + return + W.play_tool_sound(src) + user.visible_message( \ + "[user] connects [src].", \ + "You fasten [src] to the port.", \ + "You hear a ratchet.") + update_icon() + investigate_log("was connected to [possible_port] by [key_name(user)].", INVESTIGATE_ATMOS) + else + return ..() + +/obj/machinery/portable_atmospherics/attacked_by(obj/item/I, mob/user) + if(I.force < 10 && !(machine_stat & BROKEN)) + take_damage(0) + else + investigate_log("was smacked with \a [I] by [key_name(user)].", INVESTIGATE_ATMOS) + add_fingerprint(user) + ..() diff --git a/code/modules/atmospherics/machinery/portable/pump.dm b/code/modules/atmospherics/machinery/portable/pump.dm index 71d8131816ea..b2ee6fee926f 100644 --- a/code/modules/atmospherics/machinery/portable/pump.dm +++ b/code/modules/atmospherics/machinery/portable/pump.dm @@ -1,156 +1,152 @@ -#define PUMP_OUT "out" -#define PUMP_IN "in" -#define PUMP_MAX_PRESSURE (ONE_ATMOSPHERE * 25) -#define PUMP_MIN_PRESSURE (ONE_ATMOSPHERE / 10) -#define PUMP_DEFAULT_PRESSURE (ONE_ATMOSPHERE) - -/obj/machinery/portable_atmospherics/pump - name = "portable air pump" - icon_state = "psiphon:0" - density = TRUE - ui_x = 300 - ui_y = 315 - - var/on = FALSE - var/direction = PUMP_OUT - var/obj/machinery/atmospherics/components/binary/pump/pump - - volume = 1000 - -/obj/machinery/portable_atmospherics/pump/Initialize() - . = ..() - pump = new(src, FALSE) - pump.on = TRUE - pump.machine_stat = 0 - SSair.add_to_rebuild_queue(pump) - -/obj/machinery/portable_atmospherics/pump/Destroy() - var/turf/T = get_turf(src) - T.assume_air(air_contents) - air_update_turf() - QDEL_NULL(pump) - return ..() - -/obj/machinery/portable_atmospherics/pump/update_icon_state() - icon_state = "psiphon:[on]" - -/obj/machinery/portable_atmospherics/pump/update_overlays() - . = ..() - if(holding) - . += "siphon-open" - if(connected_port) - . += "siphon-connector" - -/obj/machinery/portable_atmospherics/pump/process_atmos() - ..() - if(!on) - pump.airs[1] = null - pump.airs[2] = null - return - - var/turf/T = get_turf(src) - if(direction == PUMP_OUT) // Hook up the internal pump. - pump.airs[1] = holding ? holding.air_contents : air_contents - pump.airs[2] = holding ? air_contents : T.return_air() - else - pump.airs[1] = holding ? air_contents : T.return_air() - pump.airs[2] = holding ? holding.air_contents : air_contents - - pump.process_atmos() // Pump gas. - if(!holding) - air_update_turf() // Update the environment if needed. - -/obj/machinery/portable_atmospherics/pump/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - if(is_operational()) - if(prob(50 / severity)) - on = !on - if(prob(100 / severity)) - direction = PUMP_OUT - pump.target_pressure = rand(0, 100 * ONE_ATMOSPHERE) - update_icon() - -/obj/machinery/portable_atmospherics/pump/replace_tank(mob/living/user, close_valve) - . = ..() - if(.) - if(close_valve) - if(on) - on = FALSE - update_icon() - else if(on && holding && direction == PUMP_OUT) - investigate_log("[key_name(user)] started a transfer into [holding].", INVESTIGATE_ATMOS) - - -/obj/machinery/portable_atmospherics/pump/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.physical_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "PortablePump", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/machinery/portable_atmospherics/pump/ui_data() - var/data = list() - data["on"] = on - data["direction"] = direction == PUMP_IN ? TRUE : FALSE - data["connected"] = connected_port ? TRUE : FALSE - data["pressure"] = round(air_contents.return_pressure() ? air_contents.return_pressure() : 0) - data["target_pressure"] = round(pump.target_pressure ? pump.target_pressure : 0) - data["default_pressure"] = round(PUMP_DEFAULT_PRESSURE) - data["min_pressure"] = round(PUMP_MIN_PRESSURE) - data["max_pressure"] = round(PUMP_MAX_PRESSURE) - - if(holding) - data["holding"] = list() - data["holding"]["name"] = holding.name - data["holding"]["pressure"] = round(holding.air_contents.return_pressure()) - else - data["holding"] = null - return data - -/obj/machinery/portable_atmospherics/pump/ui_act(action, params) - if(..()) - return - switch(action) - if("power") - on = !on - if(on && !holding) - var/plasma = air_contents.get_moles(/datum/gas/plasma) - var/n2o = air_contents.get_moles(/datum/gas/nitrous_oxide) - if(n2o || plasma) - message_admins("[ADMIN_LOOKUPFLW(usr)] turned on a pump that contains [n2o ? "N2O" : ""][n2o && plasma ? " & " : ""][plasma ? "Plasma" : ""] at [ADMIN_VERBOSEJMP(src)]") - log_admin("[key_name(usr)] turned on a pump that contains [n2o ? "N2O" : ""][n2o && plasma ? " & " : ""][plasma ? "Plasma" : ""] at [AREACOORD(src)]") - else if(on && direction == PUMP_OUT) - investigate_log("[key_name(usr)] started a transfer into [holding].", INVESTIGATE_ATMOS) - . = TRUE - if("direction") - if(direction == PUMP_OUT) - direction = PUMP_IN - else - if(on && holding) - investigate_log("[key_name(usr)] started a transfer into [holding].", INVESTIGATE_ATMOS) - direction = PUMP_OUT - . = TRUE - if("pressure") - var/pressure = params["pressure"] - if(pressure == "reset") - pressure = PUMP_DEFAULT_PRESSURE - . = TRUE - else if(pressure == "min") - pressure = PUMP_MIN_PRESSURE - . = TRUE - else if(pressure == "max") - pressure = PUMP_MAX_PRESSURE - . = TRUE - else if(text2num(pressure) != null) - pressure = text2num(pressure) - . = TRUE - if(.) - pump.target_pressure = clamp(round(pressure), PUMP_MIN_PRESSURE, PUMP_MAX_PRESSURE) - investigate_log("was set to [pump.target_pressure] kPa by [key_name(usr)].", INVESTIGATE_ATMOS) - if("eject") - if(holding) - replace_tank(usr, FALSE) - . = TRUE - update_icon() +#define PUMP_OUT "out" +#define PUMP_IN "in" +#define PUMP_MAX_PRESSURE (ONE_ATMOSPHERE * 25) +#define PUMP_MIN_PRESSURE (ONE_ATMOSPHERE / 10) +#define PUMP_DEFAULT_PRESSURE (ONE_ATMOSPHERE) + +/obj/machinery/portable_atmospherics/pump + name = "portable air pump" + icon_state = "psiphon:0" + density = TRUE + + var/on = FALSE + var/direction = PUMP_OUT + var/obj/machinery/atmospherics/components/binary/pump/pump + + volume = 1000 + +/obj/machinery/portable_atmospherics/pump/Initialize() + . = ..() + pump = new(src, FALSE) + pump.on = TRUE + pump.machine_stat = 0 + SSair.add_to_rebuild_queue(pump) + +/obj/machinery/portable_atmospherics/pump/Destroy() + var/turf/T = get_turf(src) + T.assume_air(air_contents) + air_update_turf() + QDEL_NULL(pump) + return ..() + +/obj/machinery/portable_atmospherics/pump/update_icon_state() + icon_state = "psiphon:[on]" + +/obj/machinery/portable_atmospherics/pump/update_overlays() + . = ..() + if(holding) + . += "siphon-open" + if(connected_port) + . += "siphon-connector" + +/obj/machinery/portable_atmospherics/pump/process_atmos() + ..() + if(!on) + pump.airs[1] = null + pump.airs[2] = null + return + + var/turf/T = get_turf(src) + if(direction == PUMP_OUT) // Hook up the internal pump. + pump.airs[1] = holding ? holding.air_contents : air_contents + pump.airs[2] = holding ? air_contents : T.return_air() + else + pump.airs[1] = holding ? air_contents : T.return_air() + pump.airs[2] = holding ? holding.air_contents : air_contents + + pump.process_atmos() // Pump gas. + if(!holding) + air_update_turf() // Update the environment if needed. + +/obj/machinery/portable_atmospherics/pump/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + if(is_operational()) + if(prob(50 / severity)) + on = !on + if(prob(100 / severity)) + direction = PUMP_OUT + pump.target_pressure = rand(0, 100 * ONE_ATMOSPHERE) + update_icon() + +/obj/machinery/portable_atmospherics/pump/replace_tank(mob/living/user, close_valve) + . = ..() + if(.) + if(close_valve) + if(on) + on = FALSE + update_icon() + else if(on && holding && direction == PUMP_OUT) + investigate_log("[key_name(user)] started a transfer into [holding].", INVESTIGATE_ATMOS) + +/obj/machinery/portable_atmospherics/pump/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "PortablePump", name) + ui.open() + +/obj/machinery/portable_atmospherics/pump/ui_data() + var/data = list() + data["on"] = on + data["direction"] = direction == PUMP_IN ? TRUE : FALSE + data["connected"] = connected_port ? TRUE : FALSE + data["pressure"] = round(air_contents.return_pressure() ? air_contents.return_pressure() : 0) + data["target_pressure"] = round(pump.target_pressure ? pump.target_pressure : 0) + data["default_pressure"] = round(PUMP_DEFAULT_PRESSURE) + data["min_pressure"] = round(PUMP_MIN_PRESSURE) + data["max_pressure"] = round(PUMP_MAX_PRESSURE) + + if(holding) + data["holding"] = list() + data["holding"]["name"] = holding.name + data["holding"]["pressure"] = round(holding.air_contents.return_pressure()) + else + data["holding"] = null + return data + +/obj/machinery/portable_atmospherics/pump/ui_act(action, params) + if(..()) + return + switch(action) + if("power") + on = !on + if(on && !holding) + var/plasma = air_contents.get_moles(/datum/gas/plasma) + var/n2o = air_contents.get_moles(/datum/gas/nitrous_oxide) + if(n2o || plasma) + message_admins("[ADMIN_LOOKUPFLW(usr)] turned on a pump that contains [n2o ? "N2O" : ""][n2o && plasma ? " & " : ""][plasma ? "Plasma" : ""] at [ADMIN_VERBOSEJMP(src)]") + log_admin("[key_name(usr)] turned on a pump that contains [n2o ? "N2O" : ""][n2o && plasma ? " & " : ""][plasma ? "Plasma" : ""] at [AREACOORD(src)]") + else if(on && direction == PUMP_OUT) + investigate_log("[key_name(usr)] started a transfer into [holding].", INVESTIGATE_ATMOS) + . = TRUE + if("direction") + if(direction == PUMP_OUT) + direction = PUMP_IN + else + if(on && holding) + investigate_log("[key_name(usr)] started a transfer into [holding].", INVESTIGATE_ATMOS) + direction = PUMP_OUT + . = TRUE + if("pressure") + var/pressure = params["pressure"] + if(pressure == "reset") + pressure = PUMP_DEFAULT_PRESSURE + . = TRUE + else if(pressure == "min") + pressure = PUMP_MIN_PRESSURE + . = TRUE + else if(pressure == "max") + pressure = PUMP_MAX_PRESSURE + . = TRUE + else if(text2num(pressure) != null) + pressure = text2num(pressure) + . = TRUE + if(.) + pump.target_pressure = clamp(round(pressure), PUMP_MIN_PRESSURE, PUMP_MAX_PRESSURE) + investigate_log("was set to [pump.target_pressure] kPa by [key_name(usr)].", INVESTIGATE_ATMOS) + if("eject") + if(holding) + replace_tank(usr, FALSE) + . = TRUE + update_icon() diff --git a/code/modules/atmospherics/machinery/portable/scrubber.dm b/code/modules/atmospherics/machinery/portable/scrubber.dm index 9938691f0c35..2ba60867d18f 100644 --- a/code/modules/atmospherics/machinery/portable/scrubber.dm +++ b/code/modules/atmospherics/machinery/portable/scrubber.dm @@ -2,16 +2,22 @@ name = "portable air scrubber" icon_state = "pscrubber:0" density = TRUE - ui_x = 320 - ui_y = 350 var/on = FALSE var/volume_rate = 1000 var/overpressure_m = 80 var/use_overlays = TRUE - volume = 1000 - - var/list/scrubbing = list(/datum/gas/plasma, /datum/gas/carbon_dioxide, /datum/gas/nitrous_oxide, /datum/gas/bz, /datum/gas/nitryl, /datum/gas/tritium, /datum/gas/hypernoblium, /datum/gas/water_vapor, /datum/gas/freon) + var/list/scrubbing = list( + /datum/gas/plasma, + /datum/gas/carbon_dioxide, + /datum/gas/nitrous_oxide, + /datum/gas/bz, + /datum/gas/nitryl, + /datum/gas/tritium, + /datum/gas/hypernoblium, + /datum/gas/water_vapor, + /datum/gas/freon, + ) /obj/machinery/portable_atmospherics/scrubber/Destroy() var/turf/T = get_turf(src) @@ -67,11 +73,10 @@ on = !on update_icon() -/obj/machinery/portable_atmospherics/scrubber/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.physical_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/machinery/portable_atmospherics/scrubber/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "PortableScrubber", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, "PortableScrubber", name) ui.open() /obj/machinery/portable_atmospherics/scrubber/ui_data() diff --git a/code/modules/awaymissions/bluespaceartillery.dm b/code/modules/awaymissions/bluespaceartillery.dm index 9bebcfe54621..deb05920e229 100644 --- a/code/modules/awaymissions/bluespaceartillery.dm +++ b/code/modules/awaymissions/bluespaceartillery.dm @@ -1,55 +1,55 @@ - - -/obj/machinery/artillerycontrol - var/reload = 60 - var/reload_cooldown = 60 - var/explosiondev = 3 - var/explosionmed = 6 - var/explosionlight = 12 - name = "bluespace artillery control" - icon_state = "control_boxp1" - icon = 'icons/obj/machines/particle_accelerator.dmi' - density = TRUE - -/obj/machinery/artillerycontrol/process() - if(reload < reload_cooldown) - reload++ - -/obj/structure/artilleryplaceholder - name = "artillery" - icon = 'icons/obj/machines/artillery.dmi' - anchored = TRUE - density = TRUE - -/obj/structure/artilleryplaceholder/decorative - density = FALSE - -/obj/machinery/artillerycontrol/ui_interact(mob/user) - . = ..() - var/dat = "Bluespace Artillery Control:
                    " - dat += "Locked on
                    " - dat += "Charge progress: [reload]/[reload_cooldown]:
                    " - dat += "Open Fire
                    " - dat += "Deployment of weapon authorized by
                    Nanotrasen Naval Command

                    Remember, friendly fire is grounds for termination of your contract and life.
                    " - user << browse(dat, "window=scroll") - onclose(user, "scroll") - -/obj/machinery/artillerycontrol/Topic(href, href_list) - if(..()) - return - var/A - A = input("Area to bombard", "Open Fire", A) in GLOB.teleportlocs - var/area/thearea = GLOB.teleportlocs[A] - if(usr.stat || usr.restrained()) - return - if(reload < reload_cooldown) - return - if(usr.contents.Find(src) || (in_range(src, usr) && isturf(loc)) || issilicon(usr)) - priority_announce("Bluespace artillery fire detected. Brace for impact.") - message_admins("[ADMIN_LOOKUPFLW(usr)] has launched an artillery strike.") - var/list/L = list() - for(var/turf/T in get_area_turfs(thearea.type)) - L+=T - var/loc = pick(L) - explosion(loc,explosiondev,explosionmed,explosionlight) - reload = 0 + + +/obj/machinery/artillerycontrol + var/reload = 60 + var/reload_cooldown = 60 + var/explosiondev = 3 + var/explosionmed = 6 + var/explosionlight = 12 + name = "bluespace artillery control" + icon_state = "control_boxp1" + icon = 'icons/obj/machines/particle_accelerator.dmi' + density = TRUE + +/obj/machinery/artillerycontrol/process() + if(reload < reload_cooldown) + reload++ + +/obj/structure/artilleryplaceholder + name = "artillery" + icon = 'icons/obj/machines/artillery.dmi' + anchored = TRUE + density = TRUE + +/obj/structure/artilleryplaceholder/decorative + density = FALSE + +/obj/machinery/artillerycontrol/ui_interact(mob/user) + . = ..() + var/dat = "Bluespace Artillery Control:
                    " + dat += "Locked on
                    " + dat += "Charge progress: [reload]/[reload_cooldown]:
                    " + dat += "Open Fire
                    " + dat += "Deployment of weapon authorized by
                    Nanotrasen Naval Command

                    Remember, friendly fire is grounds for termination of your contract and life.
                    " + user << browse(dat, "window=scroll") + onclose(user, "scroll") + +/obj/machinery/artillerycontrol/Topic(href, href_list) + if(..()) + return + var/A + A = input("Area to bombard", "Open Fire", A) in GLOB.teleportlocs + var/area/thearea = GLOB.teleportlocs[A] + if(usr.stat || usr.restrained()) + return + if(reload < reload_cooldown) + return + if(usr.contents.Find(src) || (in_range(src, usr) && isturf(loc)) || issilicon(usr)) + priority_announce("Bluespace artillery fire detected. Brace for impact.") + message_admins("[ADMIN_LOOKUPFLW(usr)] has launched an artillery strike.") + var/list/L = list() + for(var/turf/T in get_area_turfs(thearea.type)) + L+=T + var/loc = pick(L) + explosion(loc,explosiondev,explosionmed,explosionlight) + reload = 0 diff --git a/code/modules/awaymissions/exile.dm b/code/modules/awaymissions/exile.dm index 361f8169b9e1..976ab1844337 100644 --- a/code/modules/awaymissions/exile.dm +++ b/code/modules/awaymissions/exile.dm @@ -1,9 +1,9 @@ - -/obj/structure/closet/secure_closet/exile - name = "exile implants" - req_access = list(ACCESS_HOS) - -/obj/structure/closet/secure_closet/exile/PopulateContents() - new /obj/item/implanter/exile(src) - for(var/i in 1 to 5) - new /obj/item/implantcase/exile(src) + +/obj/structure/closet/secure_closet/exile + name = "exile implants" + req_access = list(ACCESS_HOS) + +/obj/structure/closet/secure_closet/exile/PopulateContents() + new /obj/item/implanter/exile(src) + for(var/i in 1 to 5) + new /obj/item/implantcase/exile(src) diff --git a/code/modules/awaymissions/gateway.dm b/code/modules/awaymissions/gateway.dm index 2f7ae0f90651..f2cf7c1b9bc0 100644 --- a/code/modules/awaymissions/gateway.dm +++ b/code/modules/awaymissions/gateway.dm @@ -1,327 +1,326 @@ -/// Station home gateway -GLOBAL_DATUM(the_gateway, /obj/machinery/gateway/centerstation) -/// List of possible gateway destinations. -GLOBAL_LIST_EMPTY(gateway_destinations) - -/** - * Corresponds to single entry in gateway control. - * - * Will NOT be added automatically to GLOB.gateway_destinations list. - */ -/datum/gateway_destination - var/name = "Unknown Destination" - var/wait = 0 /// How long after roundstart this destination becomes active - var/enabled = TRUE /// If disabled, the destination won't be availible - var/hidden = FALSE /// Will not show on gateway controls at all. - -/* Can a gateway link to this destination right now. */ -/datum/gateway_destination/proc/is_availible() - return enabled && (world.time - SSticker.round_start_time >= wait) - -/* Returns user-friendly description why you can't connect to this destination, displayed in UI */ -/datum/gateway_destination/proc/get_availible_reason() - . = "Unreachable" - if(world.time - SSticker.round_start_time < wait) - . = "Connection desynchronized. Recalibration in progress." - -/* Check if the movable is allowed to arrive at this destination (exile implants mostly) */ -/datum/gateway_destination/proc/incoming_pass_check(atom/movable/AM) - return TRUE - -/* Get the actual turf we'll arrive at */ -/datum/gateway_destination/proc/get_target_turf() - CRASH("get target turf not implemented for this destination type") - -/* Called after moving the movable to target turf */ -/datum/gateway_destination/proc/post_transfer(atom/movable/AM) - if (ismob(AM)) - var/mob/M = AM - if (M.client) - M.client.move_delay = max(world.time + 5, M.client.move_delay) - -/* Called when gateway activates with this destination. */ -/datum/gateway_destination/proc/activate(obj/machinery/gateway/activated) - return - -/* Called when gateway targeting this destination deactivates. */ -/datum/gateway_destination/proc/deactivate(obj/machinery/gateway/deactivated) - return - -/* Returns data used by gateway controller ui */ -/datum/gateway_destination/proc/get_ui_data() - . = list() - .["ref"] = REF(src) - .["name"] = name - .["availible"] = is_availible() - .["reason"] = get_availible_reason() - if(wait) - .["timeout"] = max(1 - (wait - (world.time - SSticker.round_start_time)) / wait, 0) - -/* Destination is another gateway */ -/datum/gateway_destination/gateway - /// The gateway this destination points at - var/obj/machinery/gateway/target_gateway - -/* We set the target gateway target to activator gateway */ -/datum/gateway_destination/gateway/activate(obj/machinery/gateway/activated) - if(!target_gateway.target) - target_gateway.activate(activated) - -/* We turn off the target gateway if it's linked with us */ -/datum/gateway_destination/gateway/deactivate(obj/machinery/gateway/deactivated) - if(target_gateway.target == deactivated.destination) - target_gateway.deactivate() - -/datum/gateway_destination/gateway/is_availible() - return ..() && target_gateway.calibrated && !target_gateway.target && target_gateway.powered() - -/datum/gateway_destination/gateway/get_availible_reason() - . = ..() - if(!target_gateway.calibrated) - . = "Exit gateway malfunction. Manual recalibration required." - if(target_gateway.target) - . = "Exit gateway in use." - if(!target_gateway.powered()) - . = "Exit gateway unpowered." - -/datum/gateway_destination/gateway/get_target_turf() - return get_step(target_gateway.portal,SOUTH) - -/datum/gateway_destination/gateway/post_transfer(atom/movable/AM) - . = ..() - addtimer(CALLBACK(AM,/atom/movable.proc/setDir,SOUTH),0) - -/* Special home destination, so we can check exile implants */ -/datum/gateway_destination/gateway/home - -/datum/gateway_destination/gateway/home/incoming_pass_check(atom/movable/AM) - if(isliving(AM)) - if(check_exile_implant(AM)) - return FALSE - else - for(var/mob/living/L in AM.contents) - if(check_exile_implant(L)) - target_gateway.say("Rejecting [AM]: Exile implant detected in contained lifeform.") - return FALSE - if(AM.has_buckled_mobs()) - for(var/mob/living/L in AM.buckled_mobs) - if(check_exile_implant(L)) - target_gateway.say("Rejecting [AM]: Exile implant detected in close proximity lifeform.") - return FALSE - return TRUE - -/datum/gateway_destination/gateway/home/proc/check_exile_implant(mob/living/L) - for(var/obj/item/implant/exile/E in L.implants)//Checking that there is an exile implant - to_chat(L, "The station gate has detected your exile implant and is blocking your entry.") - return TRUE - return FALSE - - -/* Destination is one ore more turfs - created by landmarks */ -/datum/gateway_destination/point - var/list/target_turfs = list() - /// Used by away landmarks - var/id - -/datum/gateway_destination/point/get_target_turf() - return pick(target_turfs) - -/* Dense invisible object starting the teleportation. Created by gateways on activation. */ -/obj/effect/gateway_portal_bumper - var/obj/machinery/gateway/gateway - density = TRUE - invisibility = INVISIBILITY_ABSTRACT - -/obj/effect/gateway_portal_bumper/Bumped(atom/movable/AM) - if(get_dir(src,AM) == SOUTH) - gateway.Transfer(AM) - -/obj/effect/gateway_portal_bumper/Destroy(force) - . = ..() - gateway = null - -/obj/machinery/gateway - name = "gateway" - desc = "A mysterious gateway built by unknown hands, it allows for faster than light travel to far-flung locations." - icon = 'icons/obj/machines/gateway.dmi' - icon_state = "off" - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - - // 3x2 offset by one row - pixel_x = -32 - pixel_y = -32 - bound_height = 64 - bound_width = 96 - bound_x = -32 - bound_y = 0 - density = TRUE - - use_power = IDLE_POWER_USE - idle_power_usage = 100 - active_power_usage = 5000 - - var/calibrated = TRUE - /// Type of instanced gateway destination, needs to be subtype of /datum/gateway_destination/gateway - var/destination_type = /datum/gateway_destination/gateway - /// Name of the generated destination - var/destination_name = "Unknown Gateway" - /// This is our own destination, pointing at this gateway - var/datum/gateway_destination/gateway/destination - /// This is current active destination - var/datum/gateway_destination/target - /// bumper object, the thing that starts actual teleport - var/obj/effect/gateway_portal_bumper/portal - -/obj/machinery/gateway/Initialize() - generate_destination() - update_icon() - return ..() - -/obj/machinery/gateway/proc/generate_destination() - destination = new destination_type - destination.name = destination_name - destination.target_gateway = src - GLOB.gateway_destinations += destination - -/obj/machinery/gateway/proc/deactivate() - var/datum/gateway_destination/dest = target - target = null - dest.deactivate(src) - QDEL_NULL(portal) - use_power = IDLE_POWER_USE - update_icon() - -/obj/machinery/gateway/process() - if((machine_stat & (NOPOWER)) && use_power) - if(target) - deactivate() - return - -/obj/machinery/gateway/update_icon_state() - if(target) - icon_state = "on" - else - icon_state = "off" - -/obj/machinery/gateway/safe_throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG, gentle = FALSE) - return - -/obj/machinery/gateway/proc/generate_bumper() - portal = new(get_turf(src)) - portal.gateway = src - -/obj/machinery/gateway/proc/activate(datum/gateway_destination/D) - if(!powered() || target) - return - target = D - target.activate(destination) - generate_bumper() - use_power = ACTIVE_POWER_USE - update_icon() - -/obj/machinery/gateway/proc/Transfer(atom/movable/AM) - if(!target || !target.incoming_pass_check(AM)) - return - AM.forceMove(target.get_target_turf()) - target.post_transfer(AM) - -/* Station's primary gateway */ -/obj/machinery/gateway/centerstation - destination_type = /datum/gateway_destination/gateway/home - destination_name = "Home Gateway" - -/obj/machinery/gateway/centerstation/Initialize() - . = ..() - if(!GLOB.the_gateway) - GLOB.the_gateway = src - -/obj/machinery/gateway/centerstation/Destroy() - if(GLOB.the_gateway == src) - GLOB.the_gateway = null - return ..() - -/obj/machinery/gateway/multitool_act(mob/living/user, obj/item/I) - if(calibrated) - to_chat(user, "The gate is already calibrated, there is no work for you to do here.") - else - to_chat(user, "Recalibration successful!: \black This gate's systems have been fine tuned. Travel to this gate will now be on target.") - calibrated = TRUE - return TRUE - -/* Doesn't need control console or power, always links to home when interacting. */ -/obj/machinery/gateway/away - density = TRUE - use_power = NO_POWER_USE - -/obj/machinery/gateway/away/interact(mob/user, special_state) - . = ..() - if(!target) - if(!GLOB.the_gateway) - to_chat(user,"Home gateway is not responding!") - if(GLOB.the_gateway.target) - to_chat(user,"Home gateway already in use!") - return - activate(GLOB.the_gateway.destination) - else - deactivate() - -/* Gateway control computer */ -/obj/machinery/computer/gateway_control - name = "Gateway Control" - desc = "Human friendly interface to the mysterious gate next to it." - var/obj/machinery/gateway/G - -/obj/machinery/computer/gateway_control/Initialize(mapload, obj/item/circuitboard/C) - . = ..() - try_to_linkup() - -/obj/machinery/computer/gateway_control/ui_interact(mob/user, ui_key = "main", datum/tgui/ui, force_open, datum/tgui/master_ui, datum/ui_state/state = GLOB.default_state) - . = ..() - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "Gateway", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/machinery/computer/gateway_control/ui_data(mob/user) - . = ..() - .["gateway_present"] = G - .["gateway_status"] = G ? G.powered() : FALSE - .["current_target"] = G?.target?.get_ui_data() - var/list/destinations = list() - if(G) - for(var/datum/gateway_destination/D in GLOB.gateway_destinations) - if(D == G.destination) - continue - destinations += list(D.get_ui_data()) - .["destinations"] = destinations - -/obj/machinery/computer/gateway_control/ui_act(action, list/params) - . = ..() - if(.) - return - switch(action) - if("linkup") - try_to_linkup() - return TRUE - if("activate") - var/datum/gateway_destination/D = locate(params["destination"]) in GLOB.gateway_destinations - try_to_connect(D) - return TRUE - if("deactivate") - if(G && G.target) - G.deactivate() - return TRUE - -/obj/machinery/computer/gateway_control/proc/try_to_linkup() - G = locate(/obj/machinery/gateway) in view(7,get_turf(src)) - -/obj/machinery/computer/gateway_control/proc/try_to_connect(datum/gateway_destination/D) - if(!D || !G) - return - if(!D.is_availible() || G.target) - return - G.activate(D) - -/obj/item/paper/fluff/gateway - info = "Congratulations,

                    Your station has been selected to carry out the Gateway Project.

                    The equipment will be shipped to you at the start of the next quarter.
                    You are to prepare a secure location to house the equipment as outlined in the attached documents.

                    --Nanotrasen Bluespace Research" - name = "Confidential Correspondence, Pg 1" +/// Station home gateway +GLOBAL_DATUM(the_gateway, /obj/machinery/gateway/centerstation) +/// List of possible gateway destinations. +GLOBAL_LIST_EMPTY(gateway_destinations) + +/** + * Corresponds to single entry in gateway control. + * + * Will NOT be added automatically to GLOB.gateway_destinations list. + */ +/datum/gateway_destination + var/name = "Unknown Destination" + var/wait = 0 /// How long after roundstart this destination becomes active + var/enabled = TRUE /// If disabled, the destination won't be availible + var/hidden = FALSE /// Will not show on gateway controls at all. + +/* Can a gateway link to this destination right now. */ +/datum/gateway_destination/proc/is_availible() + return enabled && (world.time - SSticker.round_start_time >= wait) + +/* Returns user-friendly description why you can't connect to this destination, displayed in UI */ +/datum/gateway_destination/proc/get_availible_reason() + . = "Unreachable" + if(world.time - SSticker.round_start_time < wait) + . = "Connection desynchronized. Recalibration in progress." + +/* Check if the movable is allowed to arrive at this destination (exile implants mostly) */ +/datum/gateway_destination/proc/incoming_pass_check(atom/movable/AM) + return TRUE + +/* Get the actual turf we'll arrive at */ +/datum/gateway_destination/proc/get_target_turf() + CRASH("get target turf not implemented for this destination type") + +/* Called after moving the movable to target turf */ +/datum/gateway_destination/proc/post_transfer(atom/movable/AM) + if (ismob(AM)) + var/mob/M = AM + if (M.client) + M.client.move_delay = max(world.time + 5, M.client.move_delay) + +/* Called when gateway activates with this destination. */ +/datum/gateway_destination/proc/activate(obj/machinery/gateway/activated) + return + +/* Called when gateway targeting this destination deactivates. */ +/datum/gateway_destination/proc/deactivate(obj/machinery/gateway/deactivated) + return + +/* Returns data used by gateway controller ui */ +/datum/gateway_destination/proc/get_ui_data() + . = list() + .["ref"] = REF(src) + .["name"] = name + .["availible"] = is_availible() + .["reason"] = get_availible_reason() + if(wait) + .["timeout"] = max(1 - (wait - (world.time - SSticker.round_start_time)) / wait, 0) + +/* Destination is another gateway */ +/datum/gateway_destination/gateway + /// The gateway this destination points at + var/obj/machinery/gateway/target_gateway + +/* We set the target gateway target to activator gateway */ +/datum/gateway_destination/gateway/activate(obj/machinery/gateway/activated) + if(!target_gateway.target) + target_gateway.activate(activated) + +/* We turn off the target gateway if it's linked with us */ +/datum/gateway_destination/gateway/deactivate(obj/machinery/gateway/deactivated) + if(target_gateway.target == deactivated.destination) + target_gateway.deactivate() + +/datum/gateway_destination/gateway/is_availible() + return ..() && target_gateway.calibrated && !target_gateway.target && target_gateway.powered() + +/datum/gateway_destination/gateway/get_availible_reason() + . = ..() + if(!target_gateway.calibrated) + . = "Exit gateway malfunction. Manual recalibration required." + if(target_gateway.target) + . = "Exit gateway in use." + if(!target_gateway.powered()) + . = "Exit gateway unpowered." + +/datum/gateway_destination/gateway/get_target_turf() + return get_step(target_gateway.portal,SOUTH) + +/datum/gateway_destination/gateway/post_transfer(atom/movable/AM) + . = ..() + addtimer(CALLBACK(AM,/atom/movable.proc/setDir,SOUTH),0) + +/* Special home destination, so we can check exile implants */ +/datum/gateway_destination/gateway/home + +/datum/gateway_destination/gateway/home/incoming_pass_check(atom/movable/AM) + if(isliving(AM)) + if(check_exile_implant(AM)) + return FALSE + else + for(var/mob/living/L in AM.contents) + if(check_exile_implant(L)) + target_gateway.say("Rejecting [AM]: Exile implant detected in contained lifeform.") + return FALSE + if(AM.has_buckled_mobs()) + for(var/mob/living/L in AM.buckled_mobs) + if(check_exile_implant(L)) + target_gateway.say("Rejecting [AM]: Exile implant detected in close proximity lifeform.") + return FALSE + return TRUE + +/datum/gateway_destination/gateway/home/proc/check_exile_implant(mob/living/L) + for(var/obj/item/implant/exile/E in L.implants)//Checking that there is an exile implant + to_chat(L, "The station gate has detected your exile implant and is blocking your entry.") + return TRUE + return FALSE + + +/* Destination is one ore more turfs - created by landmarks */ +/datum/gateway_destination/point + var/list/target_turfs = list() + /// Used by away landmarks + var/id + +/datum/gateway_destination/point/get_target_turf() + return pick(target_turfs) + +/* Dense invisible object starting the teleportation. Created by gateways on activation. */ +/obj/effect/gateway_portal_bumper + var/obj/machinery/gateway/gateway + density = TRUE + invisibility = INVISIBILITY_ABSTRACT + +/obj/effect/gateway_portal_bumper/Bumped(atom/movable/AM) + if(get_dir(src,AM) == SOUTH) + gateway.Transfer(AM) + +/obj/effect/gateway_portal_bumper/Destroy(force) + . = ..() + gateway = null + +/obj/machinery/gateway + name = "gateway" + desc = "A mysterious gateway built by unknown hands, it allows for faster than light travel to far-flung locations." + icon = 'icons/obj/machines/gateway.dmi' + icon_state = "off" + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + + // 3x2 offset by one row + pixel_x = -32 + pixel_y = -32 + bound_height = 64 + bound_width = 96 + bound_x = -32 + bound_y = 0 + density = TRUE + + use_power = IDLE_POWER_USE + idle_power_usage = 100 + active_power_usage = 5000 + + var/calibrated = TRUE + /// Type of instanced gateway destination, needs to be subtype of /datum/gateway_destination/gateway + var/destination_type = /datum/gateway_destination/gateway + /// Name of the generated destination + var/destination_name = "Unknown Gateway" + /// This is our own destination, pointing at this gateway + var/datum/gateway_destination/gateway/destination + /// This is current active destination + var/datum/gateway_destination/target + /// bumper object, the thing that starts actual teleport + var/obj/effect/gateway_portal_bumper/portal + +/obj/machinery/gateway/Initialize() + generate_destination() + update_icon() + return ..() + +/obj/machinery/gateway/proc/generate_destination() + destination = new destination_type + destination.name = destination_name + destination.target_gateway = src + GLOB.gateway_destinations += destination + +/obj/machinery/gateway/proc/deactivate() + var/datum/gateway_destination/dest = target + target = null + dest.deactivate(src) + QDEL_NULL(portal) + use_power = IDLE_POWER_USE + update_icon() + +/obj/machinery/gateway/process() + if((machine_stat & (NOPOWER)) && use_power) + if(target) + deactivate() + return + +/obj/machinery/gateway/update_icon_state() + if(target) + icon_state = "on" + else + icon_state = "off" + +/obj/machinery/gateway/safe_throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG, gentle = FALSE) + return + +/obj/machinery/gateway/proc/generate_bumper() + portal = new(get_turf(src)) + portal.gateway = src + +/obj/machinery/gateway/proc/activate(datum/gateway_destination/D) + if(!powered() || target) + return + target = D + target.activate(destination) + generate_bumper() + use_power = ACTIVE_POWER_USE + update_icon() + +/obj/machinery/gateway/proc/Transfer(atom/movable/AM) + if(!target || !target.incoming_pass_check(AM)) + return + AM.forceMove(target.get_target_turf()) + target.post_transfer(AM) + +/* Station's primary gateway */ +/obj/machinery/gateway/centerstation + destination_type = /datum/gateway_destination/gateway/home + destination_name = "Home Gateway" + +/obj/machinery/gateway/centerstation/Initialize() + . = ..() + if(!GLOB.the_gateway) + GLOB.the_gateway = src + +/obj/machinery/gateway/centerstation/Destroy() + if(GLOB.the_gateway == src) + GLOB.the_gateway = null + return ..() + +/obj/machinery/gateway/multitool_act(mob/living/user, obj/item/I) + if(calibrated) + to_chat(user, "The gate is already calibrated, there is no work for you to do here.") + else + to_chat(user, "Recalibration successful!: \black This gate's systems have been fine tuned. Travel to this gate will now be on target.") + calibrated = TRUE + return TRUE + +/* Doesn't need control console or power, always links to home when interacting. */ +/obj/machinery/gateway/away + density = TRUE + use_power = NO_POWER_USE + +/obj/machinery/gateway/away/interact(mob/user, special_state) + . = ..() + if(!target) + if(!GLOB.the_gateway) + to_chat(user,"Home gateway is not responding!") + if(GLOB.the_gateway.target) + to_chat(user,"Home gateway already in use!") + return + activate(GLOB.the_gateway.destination) + else + deactivate() + +/* Gateway control computer */ +/obj/machinery/computer/gateway_control + name = "Gateway Control" + desc = "Human friendly interface to the mysterious gate next to it." + var/obj/machinery/gateway/G + +/obj/machinery/computer/gateway_control/Initialize(mapload, obj/item/circuitboard/C) + . = ..() + try_to_linkup() + +/obj/machinery/computer/gateway_control/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "Gateway", name) + ui.open() + +/obj/machinery/computer/gateway_control/ui_data(mob/user) + . = ..() + .["gateway_present"] = G + .["gateway_status"] = G ? G.powered() : FALSE + .["current_target"] = G?.target?.get_ui_data() + var/list/destinations = list() + if(G) + for(var/datum/gateway_destination/D in GLOB.gateway_destinations) + if(D == G.destination) + continue + destinations += list(D.get_ui_data()) + .["destinations"] = destinations + +/obj/machinery/computer/gateway_control/ui_act(action, list/params) + . = ..() + if(.) + return + switch(action) + if("linkup") + try_to_linkup() + return TRUE + if("activate") + var/datum/gateway_destination/D = locate(params["destination"]) in GLOB.gateway_destinations + try_to_connect(D) + return TRUE + if("deactivate") + if(G && G.target) + G.deactivate() + return TRUE + +/obj/machinery/computer/gateway_control/proc/try_to_linkup() + G = locate(/obj/machinery/gateway) in view(7,get_turf(src)) + +/obj/machinery/computer/gateway_control/proc/try_to_connect(datum/gateway_destination/D) + if(!D || !G) + return + if(!D.is_availible() || G.target) + return + G.activate(D) + +/obj/item/paper/fluff/gateway + info = "Congratulations,

                    Your station has been selected to carry out the Gateway Project.

                    The equipment will be shipped to you at the start of the next quarter.
                    You are to prepare a secure location to house the equipment as outlined in the attached documents.

                    --Nanotrasen Bluespace Research" + name = "Confidential Correspondence, Pg 1" diff --git a/code/modules/awaymissions/mission_code/Academy.dm b/code/modules/awaymissions/mission_code/Academy.dm index a5a8cf30b921..186c6ab98e2b 100644 --- a/code/modules/awaymissions/mission_code/Academy.dm +++ b/code/modules/awaymissions/mission_code/Academy.dm @@ -1,401 +1,401 @@ - -//Academy Areas - -/area/awaymission/academy - name = "Academy Asteroids" - icon_state = "away" - -/area/awaymission/academy/headmaster - name = "Academy Fore Block" - icon_state = "away1" - -/area/awaymission/academy/classrooms - name = "Academy Classroom Block" - icon_state = "away2" - -/area/awaymission/academy/academyaft - name = "Academy Ship Aft Block" - icon_state = "away3" - -/area/awaymission/academy/academygate - name = "Academy Gateway" - icon_state = "away4" - -/area/awaymission/academy/academycellar - name = "Academy Cellar" - icon_state = "away4" - -/area/awaymission/academy/academyengine - name = "Academy Engine" - icon_state = "away4" - -//Academy Items - -/obj/item/paper/fluff/awaymissions/academy/console_maint - name = "Console Maintenance" - info = "We're upgrading to the latest mainframes for our consoles, the shipment should be in before spring break is over!" - -/obj/item/paper/fluff/awaymissions/academy/class/automotive - name = "Automotive Repair 101" - -/obj/item/paper/fluff/awaymissions/academy/class/pyromancy - name = "Pyromancy 250" - -/obj/item/paper/fluff/awaymissions/academy/class/biology - name = "Biology Lab" - -/obj/item/paper/fluff/awaymissions/academy/grade/aplus - name = "Summoning Midterm Exam" - info = "Grade: A+ Educator's Notes: Excellent form." - -/obj/item/paper/fluff/awaymissions/academy/grade/bminus - name = "Summoning Midterm Exam" - info = "Grade: B- Educator's Notes: Keep applying yourself, you're showing improvement." - -/obj/item/paper/fluff/awaymissions/academy/grade/dminus - name = "Summoning Midterm Exam" - info = "Grade: D- Educator's Notes: SEE ME AFTER CLASS." - -/obj/item/paper/fluff/awaymissions/academy/grade/failure - name = "Pyromancy Evaluation" - info = "Current Grade: F. Educator's Notes: No improvement shown despite multiple private lessons. Suggest additional tutelage." - - -/obj/singularity/academy - dissipate = 0 - move_self = 0 - grav_pull = 1 - -/obj/singularity/academy/admin_investigate_setup() - return - -/obj/singularity/academy/process() - eat() - if(prob(1)) - mezzer() - - -/obj/item/clothing/glasses/meson/truesight - name = "The Lens of Truesight" - desc = "I can see forever!" - icon_state = "monocle" - item_state = "headset" - - -/obj/structure/academy_wizard_spawner - name = "Academy Defensive System" - desc = "Made by Abjuration, Inc." - icon = 'icons/obj/cult.dmi' - icon_state = "forge" - anchored = TRUE - max_integrity = 200 - var/mob/living/current_wizard = null - var/next_check = 0 - var/cooldown = 600 - var/faction = ROLE_WIZARD - var/braindead_check = 0 - -/obj/structure/academy_wizard_spawner/New() - START_PROCESSING(SSobj, src) - -/obj/structure/academy_wizard_spawner/Destroy() - if(!broken) - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/structure/academy_wizard_spawner/process() - if(next_check < world.time) - if(!current_wizard) - for(var/mob/living/L in GLOB.player_list) - if(L.z == src.z && L.stat != DEAD && !(faction in L.faction)) - summon_wizard() - break - else - if(current_wizard.stat == DEAD) - current_wizard = null - summon_wizard() - if(!current_wizard.client) - if(!braindead_check) - braindead_check = 1 - else - braindead_check = 0 - give_control() - next_check = world.time + cooldown - -/obj/structure/academy_wizard_spawner/proc/give_control() - set waitfor = FALSE - - if(!current_wizard) - return - var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as Wizard Academy Defender?", ROLE_WIZARD, null, ROLE_WIZARD, 50, current_wizard, POLL_IGNORE_ACADEMY_WIZARD) - - if(LAZYLEN(candidates)) - var/mob/dead/observer/C = pick(candidates) - message_admins("[ADMIN_LOOKUPFLW(C)] was spawned as Wizard Academy Defender") - current_wizard.ghostize() // on the off chance braindead defender gets back in - current_wizard.key = C.key - -/obj/structure/academy_wizard_spawner/proc/summon_wizard() - var/turf/T = src.loc - var/mob/living/carbon/human/wizbody = new(T) - wizbody.fully_replace_character_name(wizbody.real_name, "Academy Teacher") - wizbody.mind_initialize() - var/datum/mind/wizmind = wizbody.mind - wizmind.special_role = "Academy Defender" - wizmind.add_antag_datum(/datum/antagonist/wizard/academy) - current_wizard = wizbody - - give_control() - -/obj/structure/academy_wizard_spawner/deconstruct(disassembled = TRUE) - if(!broken) - broken = 1 - visible_message("[src] breaks down!") - icon_state = "forge_off" - STOP_PROCESSING(SSobj, src) - -/datum/outfit/wizard/academy - name = "Academy Wizard" - r_pocket = null - r_hand = null - suit = /obj/item/clothing/suit/wizrobe/red - head = /obj/item/clothing/head/wizard/red - backpack_contents = list(/obj/item/storage/box/survival = 1) - -/obj/item/dice/d20/fate - name = "\improper Die of Fate" - desc = "A die with twenty sides. You can feel unearthly energies radiating from it. Using this might be VERY risky." - icon_state = "d20" - sides = 20 - microwave_riggable = FALSE - var/reusable = TRUE - var/used = FALSE - -/obj/item/dice/d20/fate/one_use - reusable = FALSE - -/obj/item/dice/d20/fate/cursed - name = "cursed Die of Fate" - desc = "A die with twenty sides. You feel that rolling this is a REALLY bad idea." - color = "#00BB00" - - rigged = DICE_TOTALLY_RIGGED - rigged_value = 1 - -/obj/item/dice/d20/fate/cursed/one_use - reusable = FALSE - -/obj/item/dice/d20/fate/stealth - name = "d20" - desc = "A die with twenty sides. The preferred die to throw at the GM." - -/obj/item/dice/d20/fate/stealth/one_use - reusable = FALSE - -/obj/item/dice/d20/fate/stealth/cursed - rigged = DICE_TOTALLY_RIGGED - rigged_value = 1 - -/obj/item/dice/d20/fate/stealth/cursed/one_use - reusable = FALSE - -/obj/item/dice/d20/fate/diceroll(mob/user) - . = ..() - if(!used) - if(!ishuman(user) || !user.mind || (user.mind in SSticker.mode.wizards)) - to_chat(user, "You feel the magic of the dice is restricted to ordinary humans!") - return - - if(!reusable) - used = TRUE - - var/turf/T = get_turf(src) - T.visible_message("[src] flares briefly.") - - addtimer(CALLBACK(src, .proc/effect, user, .), 1 SECONDS) - -/obj/item/dice/d20/fate/equipped(mob/user, slot) - . = ..() - if(!ishuman(user) || !user.mind || (user.mind in SSticker.mode.wizards)) - to_chat(user, "You feel the magic of the dice is restricted to ordinary humans! You should leave it alone.") - user.dropItemToGround(src) - - -/obj/item/dice/d20/fate/proc/effect(var/mob/living/carbon/human/user,roll) - var/turf/T = get_turf(src) - switch(roll) - if(1) - //Dust - T.visible_message("[user] turns to dust!") - user.hellbound = TRUE - user.dust() - if(2) - //Death - T.visible_message("[user] suddenly dies!") - user.death() - if(3) - //Swarm of creatures - T.visible_message("A swarm of creatures surround [user]!") - for(var/direction in GLOB.alldirs) - new /mob/living/simple_animal/hostile/netherworld(get_step(get_turf(user),direction)) - if(4) - //Destroy Equipment - T.visible_message("Everything [user] is holding and wearing disappears!") - for(var/obj/item/I in user) - if(istype(I, /obj/item/implant)) - continue - qdel(I) - if(5) - //Monkeying - T.visible_message("[user] transforms into a monkey!") - user.monkeyize() - if(6) - //Cut speed - T.visible_message("[user] starts moving slower!") - user.add_movespeed_modifier(/datum/movespeed_modifier/die_of_fate) - if(7) - //Throw - T.visible_message("Unseen forces throw [user]!") - user.Stun(60) - user.adjustBruteLoss(50) - var/throw_dir = pick(GLOB.cardinals) - var/atom/throw_target = get_edge_target_turf(user, throw_dir) - user.throw_at(throw_target, 200, 4) - if(8) - //Fueltank Explosion - T.visible_message("An explosion bursts into existence around [user]!") - explosion(get_turf(user),-1,0,2, flame_range = 2) - if(9) - //Cold - var/datum/disease/D = new /datum/disease/cold() - T.visible_message("[user] looks a little under the weather!") - user.ForceContractDisease(D, FALSE, TRUE) - if(10) - //Nothing - T.visible_message("Nothing seems to happen.") - if(11) - //Cookie - T.visible_message("A cookie appears out of thin air!") - var/obj/item/reagent_containers/food/snacks/cookie/C = new(drop_location()) - do_smoke(0, drop_location()) - C.name = "Cookie of Fate" - if(12) - //Healing - T.visible_message("[user] looks very healthy!") - user.revive(full_heal = TRUE, admin_revive = TRUE) - if(13) - //Mad Dosh - T.visible_message("Mad dosh shoots out of [src]!") - var/turf/Start = get_turf(src) - for(var/direction in GLOB.alldirs) - var/turf/dirturf = get_step(Start,direction) - if(rand(0,1)) - new /obj/item/stack/spacecash/c1000(dirturf) - else - var/obj/item/storage/bag/money/M = new(dirturf) - for(var/i in 1 to rand(5,50)) - new /obj/item/coin/gold(M) - if(14) - //Free Gun - T.visible_message("An impressive gun appears!") - do_smoke(0, drop_location()) - new /obj/item/gun/ballistic/revolver/mateba(drop_location()) - if(15) - //Random One-use spellbook - T.visible_message("A magical looking book drops to the floor!") - do_smoke(0, drop_location()) - new /obj/item/book/granter/spell/random(drop_location()) - if(16) - //Servant & Servant Summon - T.visible_message("A Dice Servant appears in a cloud of smoke!") - var/mob/living/carbon/human/H = new(drop_location()) - do_smoke(0, drop_location()) - - H.equipOutfit(/datum/outfit/butler) - var/datum/mind/servant_mind = new /datum/mind() - var/datum/antagonist/magic_servant/A = new - servant_mind.add_antag_datum(A) - A.setup_master(user) - servant_mind.transfer_to(H) - - var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as [user.real_name] Servant?", ROLE_WIZARD, null, ROLE_WIZARD, 50, H) - if(LAZYLEN(candidates)) - var/mob/dead/observer/C = pick(candidates) - message_admins("[ADMIN_LOOKUPFLW(C)] was spawned as Dice Servant") - H.key = C.key - - var/obj/effect/proc_holder/spell/targeted/summonmob/S = new - S.target_mob = H - user.mind.AddSpell(S) - - if(17) - //Tator Kit - T.visible_message("A suspicious box appears!") - new /obj/item/storage/box/syndicate/bundle_A(drop_location()) - do_smoke(0, drop_location()) - if(18) - //Captain ID - T.visible_message("A golden identification card appears!") - new /obj/item/card/id/captains_spare(drop_location()) - do_smoke(0, drop_location()) - if(19) - //Instrinct Resistance - T.visible_message("[user] looks very robust!") - user.physiology.brute_mod *= 0.5 - user.physiology.burn_mod *= 0.5 - - if(20) - //Free wizard! - T.visible_message("Magic flows out of [src] and into [user]!") - user.mind.make_Wizard() - -/datum/outfit/butler - name = "Butler" - uniform = /obj/item/clothing/under/suit/black_really - shoes = /obj/item/clothing/shoes/laceup - head = /obj/item/clothing/head/bowler - glasses = /obj/item/clothing/glasses/monocle - gloves = /obj/item/clothing/gloves/color/white - -/obj/effect/proc_holder/spell/targeted/summonmob - name = "Summon Servant" - desc = "This spell can be used to call your servant, whenever you need it." - charge_max = 100 - clothes_req = 0 - invocation = "JE VES" - invocation_type = "whisper" - range = -1 - level_max = 0 //cannot be improved - cooldown_min = 100 - include_user = 1 - - var/mob/living/target_mob - - action_icon_state = "summons" - -/obj/effect/proc_holder/spell/targeted/summonmob/cast(list/targets,mob/user = usr) - if(!target_mob) - return - var/turf/Start = get_turf(user) - for(var/direction in GLOB.alldirs) - var/turf/T = get_step(Start,direction) - if(!T.density) - target_mob.Move(T) - -/obj/structure/ladder/unbreakable/rune - name = "\improper Teleportation Rune" - desc = "Could lead anywhere." - icon = 'icons/obj/rune.dmi' - icon_state = "1" - color = rgb(0,0,255) - -/obj/structure/ladder/unbreakable/rune/ComponentInitialize() - . = ..() - AddElement(/datum/element/update_icon_blocker) - -/obj/structure/ladder/unbreakable/rune/show_fluff_message(up,mob/user) - user.visible_message("[user] activates \the [src].", "You activate \the [src].") - -/obj/structure/ladder/unbreakable/rune/use(mob/user, is_ghost=FALSE) - if(is_ghost || !(user.mind in SSticker.mode.wizards)) - ..() + +//Academy Areas + +/area/awaymission/academy + name = "Academy Asteroids" + icon_state = "away" + +/area/awaymission/academy/headmaster + name = "Academy Fore Block" + icon_state = "away1" + +/area/awaymission/academy/classrooms + name = "Academy Classroom Block" + icon_state = "away2" + +/area/awaymission/academy/academyaft + name = "Academy Ship Aft Block" + icon_state = "away3" + +/area/awaymission/academy/academygate + name = "Academy Gateway" + icon_state = "away4" + +/area/awaymission/academy/academycellar + name = "Academy Cellar" + icon_state = "away4" + +/area/awaymission/academy/academyengine + name = "Academy Engine" + icon_state = "away4" + +//Academy Items + +/obj/item/paper/fluff/awaymissions/academy/console_maint + name = "Console Maintenance" + info = "We're upgrading to the latest mainframes for our consoles, the shipment should be in before spring break is over!" + +/obj/item/paper/fluff/awaymissions/academy/class/automotive + name = "Automotive Repair 101" + +/obj/item/paper/fluff/awaymissions/academy/class/pyromancy + name = "Pyromancy 250" + +/obj/item/paper/fluff/awaymissions/academy/class/biology + name = "Biology Lab" + +/obj/item/paper/fluff/awaymissions/academy/grade/aplus + name = "Summoning Midterm Exam" + info = "Grade: A+ Educator's Notes: Excellent form." + +/obj/item/paper/fluff/awaymissions/academy/grade/bminus + name = "Summoning Midterm Exam" + info = "Grade: B- Educator's Notes: Keep applying yourself, you're showing improvement." + +/obj/item/paper/fluff/awaymissions/academy/grade/dminus + name = "Summoning Midterm Exam" + info = "Grade: D- Educator's Notes: SEE ME AFTER CLASS." + +/obj/item/paper/fluff/awaymissions/academy/grade/failure + name = "Pyromancy Evaluation" + info = "Current Grade: F. Educator's Notes: No improvement shown despite multiple private lessons. Suggest additional tutelage." + + +/obj/singularity/academy + dissipate = 0 + move_self = 0 + grav_pull = 1 + +/obj/singularity/academy/admin_investigate_setup() + return + +/obj/singularity/academy/process() + eat() + if(prob(1)) + mezzer() + + +/obj/item/clothing/glasses/meson/truesight + name = "The Lens of Truesight" + desc = "I can see forever!" + icon_state = "monocle" + item_state = "headset" + + +/obj/structure/academy_wizard_spawner + name = "Academy Defensive System" + desc = "Made by Abjuration, Inc." + icon = 'icons/obj/cult.dmi' + icon_state = "forge" + anchored = TRUE + max_integrity = 200 + var/mob/living/current_wizard = null + var/next_check = 0 + var/cooldown = 600 + var/faction = ROLE_WIZARD + var/braindead_check = 0 + +/obj/structure/academy_wizard_spawner/New() + START_PROCESSING(SSobj, src) + +/obj/structure/academy_wizard_spawner/Destroy() + if(!broken) + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/structure/academy_wizard_spawner/process() + if(next_check < world.time) + if(!current_wizard) + for(var/mob/living/L in GLOB.player_list) + if(L.z == src.z && L.stat != DEAD && !(faction in L.faction)) + summon_wizard() + break + else + if(current_wizard.stat == DEAD) + current_wizard = null + summon_wizard() + if(!current_wizard.client) + if(!braindead_check) + braindead_check = 1 + else + braindead_check = 0 + give_control() + next_check = world.time + cooldown + +/obj/structure/academy_wizard_spawner/proc/give_control() + set waitfor = FALSE + + if(!current_wizard) + return + var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as Wizard Academy Defender?", ROLE_WIZARD, null, ROLE_WIZARD, 50, current_wizard, POLL_IGNORE_ACADEMY_WIZARD) + + if(LAZYLEN(candidates)) + var/mob/dead/observer/C = pick(candidates) + message_admins("[ADMIN_LOOKUPFLW(C)] was spawned as Wizard Academy Defender") + current_wizard.ghostize() // on the off chance braindead defender gets back in + current_wizard.key = C.key + +/obj/structure/academy_wizard_spawner/proc/summon_wizard() + var/turf/T = src.loc + var/mob/living/carbon/human/wizbody = new(T) + wizbody.fully_replace_character_name(wizbody.real_name, "Academy Teacher") + wizbody.mind_initialize() + var/datum/mind/wizmind = wizbody.mind + wizmind.special_role = "Academy Defender" + wizmind.add_antag_datum(/datum/antagonist/wizard/academy) + current_wizard = wizbody + + give_control() + +/obj/structure/academy_wizard_spawner/deconstruct(disassembled = TRUE) + if(!broken) + broken = 1 + visible_message("[src] breaks down!") + icon_state = "forge_off" + STOP_PROCESSING(SSobj, src) + +/datum/outfit/wizard/academy + name = "Academy Wizard" + r_pocket = null + r_hand = null + suit = /obj/item/clothing/suit/wizrobe/red + head = /obj/item/clothing/head/wizard/red + backpack_contents = list(/obj/item/storage/box/survival = 1) + +/obj/item/dice/d20/fate + name = "\improper Die of Fate" + desc = "A die with twenty sides. You can feel unearthly energies radiating from it. Using this might be VERY risky." + icon_state = "d20" + sides = 20 + microwave_riggable = FALSE + var/reusable = TRUE + var/used = FALSE + +/obj/item/dice/d20/fate/one_use + reusable = FALSE + +/obj/item/dice/d20/fate/cursed + name = "cursed Die of Fate" + desc = "A die with twenty sides. You feel that rolling this is a REALLY bad idea." + color = "#00BB00" + + rigged = DICE_TOTALLY_RIGGED + rigged_value = 1 + +/obj/item/dice/d20/fate/cursed/one_use + reusable = FALSE + +/obj/item/dice/d20/fate/stealth + name = "d20" + desc = "A die with twenty sides. The preferred die to throw at the GM." + +/obj/item/dice/d20/fate/stealth/one_use + reusable = FALSE + +/obj/item/dice/d20/fate/stealth/cursed + rigged = DICE_TOTALLY_RIGGED + rigged_value = 1 + +/obj/item/dice/d20/fate/stealth/cursed/one_use + reusable = FALSE + +/obj/item/dice/d20/fate/diceroll(mob/user) + . = ..() + if(!used) + if(!ishuman(user) || !user.mind || (user.mind in SSticker.mode.wizards)) + to_chat(user, "You feel the magic of the dice is restricted to ordinary humans!") + return + + if(!reusable) + used = TRUE + + var/turf/T = get_turf(src) + T.visible_message("[src] flares briefly.") + + addtimer(CALLBACK(src, .proc/effect, user, .), 1 SECONDS) + +/obj/item/dice/d20/fate/equipped(mob/user, slot) + . = ..() + if(!ishuman(user) || !user.mind || (user.mind in SSticker.mode.wizards)) + to_chat(user, "You feel the magic of the dice is restricted to ordinary humans! You should leave it alone.") + user.dropItemToGround(src) + + +/obj/item/dice/d20/fate/proc/effect(var/mob/living/carbon/human/user,roll) + var/turf/T = get_turf(src) + switch(roll) + if(1) + //Dust + T.visible_message("[user] turns to dust!") + user.hellbound = TRUE + user.dust() + if(2) + //Death + T.visible_message("[user] suddenly dies!") + user.death() + if(3) + //Swarm of creatures + T.visible_message("A swarm of creatures surround [user]!") + for(var/direction in GLOB.alldirs) + new /mob/living/simple_animal/hostile/netherworld(get_step(get_turf(user),direction)) + if(4) + //Destroy Equipment + T.visible_message("Everything [user] is holding and wearing disappears!") + for(var/obj/item/I in user) + if(istype(I, /obj/item/implant)) + continue + qdel(I) + if(5) + //Monkeying + T.visible_message("[user] transforms into a monkey!") + user.monkeyize() + if(6) + //Cut speed + T.visible_message("[user] starts moving slower!") + user.add_movespeed_modifier(/datum/movespeed_modifier/die_of_fate) + if(7) + //Throw + T.visible_message("Unseen forces throw [user]!") + user.Stun(60) + user.adjustBruteLoss(50) + var/throw_dir = pick(GLOB.cardinals) + var/atom/throw_target = get_edge_target_turf(user, throw_dir) + user.throw_at(throw_target, 200, 4) + if(8) + //Fueltank Explosion + T.visible_message("An explosion bursts into existence around [user]!") + explosion(get_turf(user),-1,0,2, flame_range = 2) + if(9) + //Cold + var/datum/disease/D = new /datum/disease/cold() + T.visible_message("[user] looks a little under the weather!") + user.ForceContractDisease(D, FALSE, TRUE) + if(10) + //Nothing + T.visible_message("Nothing seems to happen.") + if(11) + //Cookie + T.visible_message("A cookie appears out of thin air!") + var/obj/item/reagent_containers/food/snacks/cookie/C = new(drop_location()) + do_smoke(0, drop_location()) + C.name = "Cookie of Fate" + if(12) + //Healing + T.visible_message("[user] looks very healthy!") + user.revive(full_heal = TRUE, admin_revive = TRUE) + if(13) + //Mad Dosh + T.visible_message("Mad dosh shoots out of [src]!") + var/turf/Start = get_turf(src) + for(var/direction in GLOB.alldirs) + var/turf/dirturf = get_step(Start,direction) + if(rand(0,1)) + new /obj/item/stack/spacecash/c1000(dirturf) + else + var/obj/item/storage/bag/money/M = new(dirturf) + for(var/i in 1 to rand(5,50)) + new /obj/item/coin/gold(M) + if(14) + //Free Gun + T.visible_message("An impressive gun appears!") + do_smoke(0, drop_location()) + new /obj/item/gun/ballistic/revolver/mateba(drop_location()) + if(15) + //Random One-use spellbook + T.visible_message("A magical looking book drops to the floor!") + do_smoke(0, drop_location()) + new /obj/item/book/granter/spell/random(drop_location()) + if(16) + //Servant & Servant Summon + T.visible_message("A Dice Servant appears in a cloud of smoke!") + var/mob/living/carbon/human/H = new(drop_location()) + do_smoke(0, drop_location()) + + H.equipOutfit(/datum/outfit/butler) + var/datum/mind/servant_mind = new /datum/mind() + var/datum/antagonist/magic_servant/A = new + servant_mind.add_antag_datum(A) + A.setup_master(user) + servant_mind.transfer_to(H) + + var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as [user.real_name] Servant?", ROLE_WIZARD, null, ROLE_WIZARD, 50, H) + if(LAZYLEN(candidates)) + var/mob/dead/observer/C = pick(candidates) + message_admins("[ADMIN_LOOKUPFLW(C)] was spawned as Dice Servant") + H.key = C.key + + var/obj/effect/proc_holder/spell/targeted/summonmob/S = new + S.target_mob = H + user.mind.AddSpell(S) + + if(17) + //Tator Kit + T.visible_message("A suspicious box appears!") + new /obj/item/storage/box/syndicate/bundle_A(drop_location()) + do_smoke(0, drop_location()) + if(18) + //Captain ID + T.visible_message("A golden identification card appears!") + new /obj/item/card/id/captains_spare(drop_location()) + do_smoke(0, drop_location()) + if(19) + //Instrinct Resistance + T.visible_message("[user] looks very robust!") + user.physiology.brute_mod *= 0.5 + user.physiology.burn_mod *= 0.5 + + if(20) + //Free wizard! + T.visible_message("Magic flows out of [src] and into [user]!") + user.mind.make_Wizard() + +/datum/outfit/butler + name = "Butler" + uniform = /obj/item/clothing/under/suit/black_really + shoes = /obj/item/clothing/shoes/laceup + head = /obj/item/clothing/head/bowler + glasses = /obj/item/clothing/glasses/monocle + gloves = /obj/item/clothing/gloves/color/white + +/obj/effect/proc_holder/spell/targeted/summonmob + name = "Summon Servant" + desc = "This spell can be used to call your servant, whenever you need it." + charge_max = 100 + clothes_req = 0 + invocation = "JE VES" + invocation_type = "whisper" + range = -1 + level_max = 0 //cannot be improved + cooldown_min = 100 + include_user = 1 + + var/mob/living/target_mob + + action_icon_state = "summons" + +/obj/effect/proc_holder/spell/targeted/summonmob/cast(list/targets,mob/user = usr) + if(!target_mob) + return + var/turf/Start = get_turf(user) + for(var/direction in GLOB.alldirs) + var/turf/T = get_step(Start,direction) + if(!T.density) + target_mob.Move(T) + +/obj/structure/ladder/unbreakable/rune + name = "\improper Teleportation Rune" + desc = "Could lead anywhere." + icon = 'icons/obj/rune.dmi' + icon_state = "1" + color = rgb(0,0,255) + +/obj/structure/ladder/unbreakable/rune/ComponentInitialize() + . = ..() + AddElement(/datum/element/update_icon_blocker) + +/obj/structure/ladder/unbreakable/rune/show_fluff_message(up,mob/user) + user.visible_message("[user] activates \the [src].", "You activate \the [src].") + +/obj/structure/ladder/unbreakable/rune/use(mob/user, is_ghost=FALSE) + if(is_ghost || !(user.mind in SSticker.mode.wizards)) + ..() diff --git a/code/modules/awaymissions/mission_code/centcomAway.dm b/code/modules/awaymissions/mission_code/centcomAway.dm index a7a670f84f03..4e9ef7a45a45 100644 --- a/code/modules/awaymissions/mission_code/centcomAway.dm +++ b/code/modules/awaymissions/mission_code/centcomAway.dm @@ -1,63 +1,63 @@ -//centcomAway areas - -/area/awaymission/centcomAway - name = "XCC-P5831" - icon_state = "away" - requires_power = FALSE - -/area/awaymission/centcomAway/general - name = "XCC-P5831" - ambientsounds = list('sound/ambience/ambigen3.ogg') - -/area/awaymission/centcomAway/maint - name = "XCC-P5831 Maintenance" - icon_state = "away1" - ambientsounds = list('sound/ambience/ambisin1.ogg') - -/area/awaymission/centcomAway/thunderdome - name = "XCC-P5831 Thunderdome" - icon_state = "away2" - ambientsounds = list('sound/ambience/ambisin2.ogg') - -/area/awaymission/centcomAway/cafe - name = "XCC-P5831 Kitchen Arena" - icon_state = "away3" - ambientsounds = list('sound/ambience/ambisin3.ogg') - -/area/awaymission/centcomAway/courtroom - name = "XCC-P5831 Courtroom" - icon_state = "away4" - ambientsounds = list('sound/ambience/ambisin4.ogg') - -/area/awaymission/centcomAway/hangar - name = "XCC-P5831 Hangars" - icon_state = "away4" - ambientsounds = list('sound/ambience/ambigen5.ogg') - -//centcomAway items - -/obj/item/paper/pamphlet/centcom/visitor_info - name = "Visitor Info Pamphlet" - info = " XCC-P5831 Visitor Information
                    \ - Greetings, visitor, to XCC-P5831! As you may know, this outpost was once \ - used as Nanotrasen's CENTRAL COMMAND STATION, organizing and coordinating company \ - projects across the vastness of space.
                    \ - Since the completion of the much more efficient CC-A5831 on March 8, 2553, XCC-P5831 no longer \ - acts as NT's base of operations but still plays a very important role its corporate affairs; \ - serving as a supply and repair depot, as well as being host to its most important legal proceedings\ - and the thrilling pay-per-view broadcasts of PLASTEEL CHEF and THUNDERDOME LIVE.
                    \ - We hope you enjoy your stay!" - -/obj/item/paper/fluff/awaymissions/centcom/gateway_memo - name = "Memo to XCC-P5831 QM" - info = "From: XCC-P5831 Management Office
                    \ - To: Rolf Ingram, XCC-P5831 Quartermaster
                    \ - Hey, Rolf, once you pack that gateway into the ferry hangar, make absolutely sure \ - to deactivate it! As you may know, SS13 has recently got its network up and running, \ - which means that until we get this gate shipped off to the next colonization staging \ - area, they'll be able to hop straight in here if its hooked up on our end.
                    \ - Obviously, that's something I'd very much rather avoid. Our forensics and medical \ - teams never did figure out what happened that last time... and I can't wrap my head \ - around it myself. Why would a shuttle full of evacuees all snap and beat each other \ - to death the moment they reached safety?
                    \ - - D. Cereza" +//centcomAway areas + +/area/awaymission/centcomAway + name = "XCC-P5831" + icon_state = "away" + requires_power = FALSE + +/area/awaymission/centcomAway/general + name = "XCC-P5831" + ambientsounds = list('sound/ambience/ambigen3.ogg') + +/area/awaymission/centcomAway/maint + name = "XCC-P5831 Maintenance" + icon_state = "away1" + ambientsounds = list('sound/ambience/ambisin1.ogg') + +/area/awaymission/centcomAway/thunderdome + name = "XCC-P5831 Thunderdome" + icon_state = "away2" + ambientsounds = list('sound/ambience/ambisin2.ogg') + +/area/awaymission/centcomAway/cafe + name = "XCC-P5831 Kitchen Arena" + icon_state = "away3" + ambientsounds = list('sound/ambience/ambisin3.ogg') + +/area/awaymission/centcomAway/courtroom + name = "XCC-P5831 Courtroom" + icon_state = "away4" + ambientsounds = list('sound/ambience/ambisin4.ogg') + +/area/awaymission/centcomAway/hangar + name = "XCC-P5831 Hangars" + icon_state = "away4" + ambientsounds = list('sound/ambience/ambigen5.ogg') + +//centcomAway items + +/obj/item/paper/pamphlet/centcom/visitor_info + name = "Visitor Info Pamphlet" + info = " XCC-P5831 Visitor Information
                    \ + Greetings, visitor, to XCC-P5831! As you may know, this outpost was once \ + used as Nanotrasen's CENTRAL COMMAND STATION, organizing and coordinating company \ + projects across the vastness of space.
                    \ + Since the completion of the much more efficient CC-A5831 on March 8, 2553, XCC-P5831 no longer \ + acts as NT's base of operations but still plays a very important role its corporate affairs; \ + serving as a supply and repair depot, as well as being host to its most important legal proceedings\ + and the thrilling pay-per-view broadcasts of PLASTEEL CHEF and THUNDERDOME LIVE.
                    \ + We hope you enjoy your stay!" + +/obj/item/paper/fluff/awaymissions/centcom/gateway_memo + name = "Memo to XCC-P5831 QM" + info = "From: XCC-P5831 Management Office
                    \ + To: Rolf Ingram, XCC-P5831 Quartermaster
                    \ + Hey, Rolf, once you pack that gateway into the ferry hangar, make absolutely sure \ + to deactivate it! As you may know, SS13 has recently got its network up and running, \ + which means that until we get this gate shipped off to the next colonization staging \ + area, they'll be able to hop straight in here if its hooked up on our end.
                    \ + Obviously, that's something I'd very much rather avoid. Our forensics and medical \ + teams never did figure out what happened that last time... and I can't wrap my head \ + around it myself. Why would a shuttle full of evacuees all snap and beat each other \ + to death the moment they reached safety?
                    \ + - D. Cereza" diff --git a/code/modules/awaymissions/mission_code/wildwest.dm b/code/modules/awaymissions/mission_code/wildwest.dm index 6679125ad6e0..9f11f1499614 100644 --- a/code/modules/awaymissions/mission_code/wildwest.dm +++ b/code/modules/awaymissions/mission_code/wildwest.dm @@ -1,166 +1,166 @@ -/* Code for the Wild West map by Brotemis - * Contains: - * Wish Granter - * Meat Grinder - */ - -//Areas - -/area/awaymission/wildwest/mines - name = "Wild West Mines" - icon_state = "away1" - requires_power = FALSE - -/area/awaymission/wildwest/gov - name = "Wild West Mansion" - icon_state = "away2" - requires_power = FALSE - -/area/awaymission/wildwest/refine - name = "Wild West Refinery" - icon_state = "away3" - requires_power = FALSE - -/area/awaymission/wildwest/vault - name = "Wild West Vault" - icon_state = "away3" - -/area/awaymission/wildwest/vaultdoors - name = "Wild West Vault Doors" // this is to keep the vault area being entirely lit because of requires_power - icon_state = "away2" - requires_power = FALSE - - - ////////// wildwest papers - -/obj/item/paper/fluff/awaymissions/wildwest/grinder - info = "meat grinder requires sacri" - - -/obj/item/paper/fluff/awaymissions/wildwest/journal/page1 - name = "Planer Saul's Journal: Page 1" - info = "We've discovered something floating in space. We can't really tell how old it is, but it is scraped and bent to hell. There object is the size of about a room with double doors that we have yet to break into. It is a lot sturdier than we could have imagined. We have decided to call it 'The Vault' " - -/obj/item/paper/fluff/awaymissions/wildwest/journal/page4 - name = "Planer Saul's Journal: Page 4" - info = " The miners in the town have become sick and almost all production has stopped. They, in a fit of delusion, tossed all of their mining equipment into the furnaces. They all claimed the same thing. A voice beckoning them to lay down their arms. Stupid miners." - -/obj/item/paper/fluff/awaymissions/wildwest/journal/page7 - name = "Planer Sauls' Journal: Page 7" - info = "The Vault...it just keeps growing and growing. I went on my daily walk through the garden and now it's just right outside the mansion... a few days ago it was only barely visible. But whatever is inside...it's calling to me." - -/obj/item/paper/fluff/awaymissions/wildwest/journal/page8 - name = "Planer Saul's Journal: Page 8" - info = "The syndicate have invaded. Their ships appeared out of nowhere and now they likely intend to kill us all and take everything. On the off-chance that the Vault may grant us sanctuary, many of us have decided to force our way inside and bolt the door, taking as many provisions with us as we can carry. In case you find this, send for help immediately and open the Vault. Find us inside." - - -/* - * Wish Granter - */ -/obj/machinery/wish_granter_dark - name = "Wish Granter" - desc = "You're not so sure about this, anymore..." - icon = 'icons/obj/device.dmi' - icon_state = "syndbeacon" - - density = TRUE - use_power = NO_POWER_USE - - var/chargesa = 1 - var/insistinga = 0 - -/obj/machinery/wish_granter_dark/interact(mob/living/carbon/human/user) - if(chargesa <= 0) - to_chat(user, "The Wish Granter lies silent.") - return - - else if(!ishuman(user)) - to_chat(user, "You feel a dark stirring inside of the Wish Granter, something you want nothing of. Your instincts are better than any man's.") - return - - else if(is_special_character(user)) - to_chat(user, "Even to a heart as dark as yours, you know nothing good will come of this. Something instinctual makes you pull away.") - - else if (!insistinga) - to_chat(user, "Your first touch makes the Wish Granter stir, listening to you. Are you really sure you want to do this?") - insistinga++ - - else - chargesa-- - insistinga = 0 - var/wish = input("You want...","Wish") as null|anything in sortList(list("Power","Wealth","Immortality","Peace")) - switch(wish) - if("Power") - to_chat(user, "Your wish is granted, but at a terrible cost...") - to_chat(user, "The Wish Granter punishes you for your selfishness, claiming your soul and warping your body to match the darkness in your heart.") - user.dna.add_mutation(LASEREYES) - user.dna.add_mutation(SPACEMUT) - user.dna.add_mutation(XRAY) - user.set_species(/datum/species/shadow) - if("Wealth") - to_chat(user, "Your wish is granted, but at a terrible cost...") - to_chat(user, "The Wish Granter punishes you for your selfishness, claiming your soul and warping your body to match the darkness in your heart.") - new /obj/structure/closet/syndicate/resources/everything(loc) - user.set_species(/datum/species/shadow) - if("Immortality") - to_chat(user, "Your wish is granted, but at a terrible cost...") - to_chat(user, "The Wish Granter punishes you for your selfishness, claiming your soul and warping your body to match the darkness in your heart.") - user.verbs += /mob/living/carbon/proc/immortality - user.set_species(/datum/species/shadow) - if("Peace") - to_chat(user, "Whatever alien sentience that the Wish Granter possesses is satisfied with your wish. There is a distant wailing as the last of the Faithless begin to die, then silence.") - to_chat(user, "You feel as if you just narrowly avoided a terrible fate...") - for(var/mob/living/simple_animal/hostile/faithless/F in GLOB.mob_living_list) - F.death() - - -///////////////Meatgrinder////////////// - - -/obj/effect/meatgrinder - name = "Meat Grinder" - desc = "What is that thing?" - density = TRUE - anchored = TRUE - icon = 'icons/mob/blob.dmi' - icon_state = "blobpod" - var/triggered = 0 - -/obj/effect/meatgrinder/Crossed(atom/movable/AM) - . = ..() - Bumped(AM) - -/obj/effect/meatgrinder/Bumped(atom/movable/AM) - - if(triggered) - return - if(!ishuman(AM)) - return - - var/mob/living/carbon/human/M = AM - - if(M.stat != DEAD && M.ckey) - visible_message("[M] triggered [src]!") - triggered = 1 - - var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread - s.set_up(3, 1, src) - s.start() - explosion(M, 1, 0, 0, 0) - qdel(src) - -/////For the Wishgranter/////////// - -/mob/living/carbon/proc/immortality() //Mob proc so people cant just clone themselves to get rid of the shadowperson race. No hiding your wickedness. - set category = "Immortality" - set name = "Resurrection" - - var/mob/living/carbon/C = usr - if(!C.stat) - to_chat(C, "You're not dead yet!") - return - if(C.has_status_effect(STATUS_EFFECT_WISH_GRANTERS_GIFT)) - to_chat(C, "You're already resurrecting!") - return - C.apply_status_effect(STATUS_EFFECT_WISH_GRANTERS_GIFT) - return 1 +/* Code for the Wild West map by Brotemis + * Contains: + * Wish Granter + * Meat Grinder + */ + +//Areas + +/area/awaymission/wildwest/mines + name = "Wild West Mines" + icon_state = "away1" + requires_power = FALSE + +/area/awaymission/wildwest/gov + name = "Wild West Mansion" + icon_state = "away2" + requires_power = FALSE + +/area/awaymission/wildwest/refine + name = "Wild West Refinery" + icon_state = "away3" + requires_power = FALSE + +/area/awaymission/wildwest/vault + name = "Wild West Vault" + icon_state = "away3" + +/area/awaymission/wildwest/vaultdoors + name = "Wild West Vault Doors" // this is to keep the vault area being entirely lit because of requires_power + icon_state = "away2" + requires_power = FALSE + + + ////////// wildwest papers + +/obj/item/paper/fluff/awaymissions/wildwest/grinder + info = "meat grinder requires sacri" + + +/obj/item/paper/fluff/awaymissions/wildwest/journal/page1 + name = "Planer Saul's Journal: Page 1" + info = "We've discovered something floating in space. We can't really tell how old it is, but it is scraped and bent to hell. There object is the size of about a room with double doors that we have yet to break into. It is a lot sturdier than we could have imagined. We have decided to call it 'The Vault' " + +/obj/item/paper/fluff/awaymissions/wildwest/journal/page4 + name = "Planer Saul's Journal: Page 4" + info = " The miners in the town have become sick and almost all production has stopped. They, in a fit of delusion, tossed all of their mining equipment into the furnaces. They all claimed the same thing. A voice beckoning them to lay down their arms. Stupid miners." + +/obj/item/paper/fluff/awaymissions/wildwest/journal/page7 + name = "Planer Sauls' Journal: Page 7" + info = "The Vault...it just keeps growing and growing. I went on my daily walk through the garden and now it's just right outside the mansion... a few days ago it was only barely visible. But whatever is inside...it's calling to me." + +/obj/item/paper/fluff/awaymissions/wildwest/journal/page8 + name = "Planer Saul's Journal: Page 8" + info = "The syndicate have invaded. Their ships appeared out of nowhere and now they likely intend to kill us all and take everything. On the off-chance that the Vault may grant us sanctuary, many of us have decided to force our way inside and bolt the door, taking as many provisions with us as we can carry. In case you find this, send for help immediately and open the Vault. Find us inside." + + +/* + * Wish Granter + */ +/obj/machinery/wish_granter_dark + name = "Wish Granter" + desc = "You're not so sure about this, anymore..." + icon = 'icons/obj/device.dmi' + icon_state = "syndbeacon" + + density = TRUE + use_power = NO_POWER_USE + + var/chargesa = 1 + var/insistinga = 0 + +/obj/machinery/wish_granter_dark/interact(mob/living/carbon/human/user) + if(chargesa <= 0) + to_chat(user, "The Wish Granter lies silent.") + return + + else if(!ishuman(user)) + to_chat(user, "You feel a dark stirring inside of the Wish Granter, something you want nothing of. Your instincts are better than any man's.") + return + + else if(is_special_character(user)) + to_chat(user, "Even to a heart as dark as yours, you know nothing good will come of this. Something instinctual makes you pull away.") + + else if (!insistinga) + to_chat(user, "Your first touch makes the Wish Granter stir, listening to you. Are you really sure you want to do this?") + insistinga++ + + else + chargesa-- + insistinga = 0 + var/wish = input("You want...","Wish") as null|anything in sortList(list("Power","Wealth","Immortality","Peace")) + switch(wish) + if("Power") + to_chat(user, "Your wish is granted, but at a terrible cost...") + to_chat(user, "The Wish Granter punishes you for your selfishness, claiming your soul and warping your body to match the darkness in your heart.") + user.dna.add_mutation(LASEREYES) + user.dna.add_mutation(SPACEMUT) + user.dna.add_mutation(XRAY) + user.set_species(/datum/species/shadow) + if("Wealth") + to_chat(user, "Your wish is granted, but at a terrible cost...") + to_chat(user, "The Wish Granter punishes you for your selfishness, claiming your soul and warping your body to match the darkness in your heart.") + new /obj/structure/closet/syndicate/resources/everything(loc) + user.set_species(/datum/species/shadow) + if("Immortality") + to_chat(user, "Your wish is granted, but at a terrible cost...") + to_chat(user, "The Wish Granter punishes you for your selfishness, claiming your soul and warping your body to match the darkness in your heart.") + user.verbs += /mob/living/carbon/proc/immortality + user.set_species(/datum/species/shadow) + if("Peace") + to_chat(user, "Whatever alien sentience that the Wish Granter possesses is satisfied with your wish. There is a distant wailing as the last of the Faithless begin to die, then silence.") + to_chat(user, "You feel as if you just narrowly avoided a terrible fate...") + for(var/mob/living/simple_animal/hostile/faithless/F in GLOB.mob_living_list) + F.death() + + +///////////////Meatgrinder////////////// + + +/obj/effect/meatgrinder + name = "Meat Grinder" + desc = "What is that thing?" + density = TRUE + anchored = TRUE + icon = 'icons/mob/blob.dmi' + icon_state = "blobpod" + var/triggered = 0 + +/obj/effect/meatgrinder/Crossed(atom/movable/AM) + . = ..() + Bumped(AM) + +/obj/effect/meatgrinder/Bumped(atom/movable/AM) + + if(triggered) + return + if(!ishuman(AM)) + return + + var/mob/living/carbon/human/M = AM + + if(M.stat != DEAD && M.ckey) + visible_message("[M] triggered [src]!") + triggered = 1 + + var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread + s.set_up(3, 1, src) + s.start() + explosion(M, 1, 0, 0, 0) + qdel(src) + +/////For the Wishgranter/////////// + +/mob/living/carbon/proc/immortality() //Mob proc so people cant just clone themselves to get rid of the shadowperson race. No hiding your wickedness. + set category = "Immortality" + set name = "Resurrection" + + var/mob/living/carbon/C = usr + if(!C.stat) + to_chat(C, "You're not dead yet!") + return + if(C.has_status_effect(STATUS_EFFECT_WISH_GRANTERS_GIFT)) + to_chat(C, "You're already resurrecting!") + return + C.apply_status_effect(STATUS_EFFECT_WISH_GRANTERS_GIFT) + return 1 diff --git a/code/modules/awaymissions/pamphlet.dm b/code/modules/awaymissions/pamphlet.dm index 8912a66aa937..3bbdfbe9b479 100644 --- a/code/modules/awaymissions/pamphlet.dm +++ b/code/modules/awaymissions/pamphlet.dm @@ -1,42 +1,42 @@ -/obj/item/paper/pamphlet - name = "pamphlet" - icon_state = "pamphlet" - show_written_words = FALSE - - -/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 \ - ready to commit to a life spent colonising the rolling hills of far away worlds. You \ - must be ready for a lifetime of adventure, a little bit of hard work, and an award \ - winning dental plan- but that's not all the Nanotrasen Gateway project has to offer.
                    \ -
                    Because we care about you, we feel it is only fair to make sure you know the risks \ - before you commit to joining the Nanotrasen Gateway project. All away destinations have \ - been fully scanned by a Nanotrasen expeditionary team, and are certified to be 100% safe. \ - We've even left a case of space beer along with the basic materials you'll need to expand \ - Nanotrasen's operational area and start your new life.

                    \ - Gateway Operation Basics
                    \ - All Nanotrasen approved Gateways operate on the same basic principals. They operate off \ - area equipment power as you would expect, and without this supply, it cannot safely function, \ - causinng it to reject all attempts at operation.

                    \ - Once it is correctly setup, and once it has enough power to operate, the Gateway will begin \ - searching for an output location. The amount of time this takes is variable, but the Gateway \ - interface will give you an estimate accurate to the minute. Power loss will not interrupt the \ - searching process. Influenza will not interrupt the searching process. Temporal anomalies \ - may cause the estimate to be inaccurate, but will not interrupt the searching process.

                    \ - Life On The Other Side
                    \ - Once you have traversed the Gateway, you may experience some disorientation. Do not panic. \ - This is a normal side effect of travelling vast distances in a short period of time. You should \ - survey the immediate area, and attempt to locate your complimentary case of space beer. Our \ - expeditionary teams have ensured the complete safety of all away locations, but in a small \ - number of cases, the Gateway they have established may not be immediately obvious. \ - Do not panic if you cannot locate the return Gateway. Begin colonisation of the destination. \ -

                    A New World
                    \ - As a participant in the Nanotrasen Gateway Project, you will be on the frontiers of space. \ - Though complete safety is assured, participants are advised to prepare for inhospitable \ - environs." +/obj/item/paper/pamphlet + name = "pamphlet" + icon_state = "pamphlet" + show_written_words = FALSE + + +/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 \ + ready to commit to a life spent colonising the rolling hills of far away worlds. You \ + must be ready for a lifetime of adventure, a little bit of hard work, and an award \ + winning dental plan- but that's not all the Nanotrasen Gateway project has to offer.
                    \ +
                    Because we care about you, we feel it is only fair to make sure you know the risks \ + before you commit to joining the Nanotrasen Gateway project. All away destinations have \ + been fully scanned by a Nanotrasen expeditionary team, and are certified to be 100% safe. \ + We've even left a case of space beer along with the basic materials you'll need to expand \ + Nanotrasen's operational area and start your new life.

                    \ + Gateway Operation Basics
                    \ + All Nanotrasen approved Gateways operate on the same basic principals. They operate off \ + area equipment power as you would expect, and without this supply, it cannot safely function, \ + causinng it to reject all attempts at operation.

                    \ + Once it is correctly setup, and once it has enough power to operate, the Gateway will begin \ + searching for an output location. The amount of time this takes is variable, but the Gateway \ + interface will give you an estimate accurate to the minute. Power loss will not interrupt the \ + searching process. Influenza will not interrupt the searching process. Temporal anomalies \ + may cause the estimate to be inaccurate, but will not interrupt the searching process.

                    \ + Life On The Other Side
                    \ + Once you have traversed the Gateway, you may experience some disorientation. Do not panic. \ + This is a normal side effect of travelling vast distances in a short period of time. You should \ + survey the immediate area, and attempt to locate your complimentary case of space beer. Our \ + expeditionary teams have ensured the complete safety of all away locations, but in a small \ + number of cases, the Gateway they have established may not be immediately obvious. \ + Do not panic if you cannot locate the return Gateway. Begin colonisation of the destination. \ +

                    A New World
                    \ + As a participant in the Nanotrasen Gateway Project, you will be on the frontiers of space. \ + Though complete safety is assured, participants are advised to prepare for inhospitable \ + environs." diff --git a/code/modules/awaymissions/zlevel.dm b/code/modules/awaymissions/zlevel.dm index 3d6f85f96c45..dae36500093f 100644 --- a/code/modules/awaymissions/zlevel.dm +++ b/code/modules/awaymissions/zlevel.dm @@ -1,62 +1,62 @@ -// How much "space" we give the edge of the map -GLOBAL_LIST_INIT(potentialRandomZlevels, generateMapList(filename = "[global.config.directory]/awaymissionconfig.txt")) - -/proc/createRandomZlevel() - if(GLOB.potentialRandomZlevels && GLOB.potentialRandomZlevels.len) - to_chat(world, "Loading away mission...") - var/map = pick(GLOB.potentialRandomZlevels) - load_new_z_level(map, "Away Mission") - to_chat(world, "Away mission loaded.") - -/obj/effect/landmark/awaystart - name = "away mission spawn" - desc = "Randomly picked away mission spawn points." - var/id - var/delay = TRUE // If the generated destination should be delayed by configured gateway delay - -/obj/effect/landmark/awaystart/Initialize() - . = ..() - var/datum/gateway_destination/point/current - for(var/datum/gateway_destination/point/D in GLOB.gateway_destinations) - if(D.id == id) - current = D - if(!current) - current = new - current.id = id - if(delay) - current.wait = CONFIG_GET(number/gateway_delay) - GLOB.gateway_destinations += current - current.target_turfs += get_turf(src) - -/obj/effect/landmark/awaystart/nodelay - delay = FALSE - -/proc/generateMapList(filename) - . = list() - var/list/Lines = world.file2list(filename) - - if(!Lines.len) - return - for (var/t in Lines) - if (!t) - continue - - t = trim(t) - if (length(t) == 0) - continue - else if (t[1] == "#") - continue - - var/pos = findtext(t, " ") - var/name = null - - if (pos) - name = lowertext(copytext(t, 1, pos)) - - else - name = lowertext(t) - - if (!name) - continue - - . += t +// How much "space" we give the edge of the map +GLOBAL_LIST_INIT(potentialRandomZlevels, generateMapList(filename = "[global.config.directory]/awaymissionconfig.txt")) + +/proc/createRandomZlevel() + if(GLOB.potentialRandomZlevels && GLOB.potentialRandomZlevels.len) + to_chat(world, "Loading away mission...") + var/map = pick(GLOB.potentialRandomZlevels) + load_new_z_level(map, "Away Mission") + to_chat(world, "Away mission loaded.") + +/obj/effect/landmark/awaystart + name = "away mission spawn" + desc = "Randomly picked away mission spawn points." + var/id + var/delay = TRUE // If the generated destination should be delayed by configured gateway delay + +/obj/effect/landmark/awaystart/Initialize() + . = ..() + var/datum/gateway_destination/point/current + for(var/datum/gateway_destination/point/D in GLOB.gateway_destinations) + if(D.id == id) + current = D + if(!current) + current = new + current.id = id + if(delay) + current.wait = CONFIG_GET(number/gateway_delay) + GLOB.gateway_destinations += current + current.target_turfs += get_turf(src) + +/obj/effect/landmark/awaystart/nodelay + delay = FALSE + +/proc/generateMapList(filename) + . = list() + var/list/Lines = world.file2list(filename) + + if(!Lines.len) + return + for (var/t in Lines) + if (!t) + continue + + t = trim(t) + if (length(t) == 0) + continue + else if (t[1] == "#") + continue + + var/pos = findtext(t, " ") + var/name = null + + if (pos) + name = lowertext(copytext(t, 1, pos)) + + else + name = lowertext(t) + + if (!name) + continue + + . += t diff --git a/code/modules/bsql/LICENSE b/code/modules/bsql/LICENSE deleted file mode 100644 index 882f6d457160..000000000000 --- 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 7a652607f914..000000000000 --- 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 2341a7fa5e40..000000000000 --- 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 50dce6ae5f64..000000000000 --- 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 96c3714c7156..000000000000 --- 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 ec199a5513a6..000000000000 --- 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/blackmarket/blackmarket_uplink.dm b/code/modules/cargo/blackmarket/blackmarket_uplink.dm index 3833b0505a5d..015848c8aca6 100644 --- a/code/modules/cargo/blackmarket/blackmarket_uplink.dm +++ b/code/modules/cargo/blackmarket/blackmarket_uplink.dm @@ -4,8 +4,6 @@ icon_state = "uplink" // UI variables. - var/ui_x = 600 - var/ui_y = 480 var/viewing_category var/viewing_market var/selected_item @@ -55,10 +53,10 @@ user.put_in_hands(holochip) to_chat(user, "You withdraw [amount_to_remove] credits into a holochip.") -/obj/item/blackmarket_uplink/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/item/blackmarket_uplink/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "BlackMarketUplink", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, "BlackMarketUplink", name) ui.open() /obj/item/blackmarket_uplink/ui_data(mob/user) diff --git a/code/modules/cargo/bounty_console.dm b/code/modules/cargo/bounty_console.dm index 7424413c8e76..8a2971501606 100644 --- a/code/modules/cargo/bounty_console.dm +++ b/code/modules/cargo/bounty_console.dm @@ -1,65 +1,65 @@ -#define PRINTER_TIMEOUT 10 - -/obj/machinery/computer/bounty - name = "\improper Nanotrasen bounty console" - desc = "Used to check and claim bounties offered by Nanotrasen" - icon_screen = "bounty" - circuit = /obj/item/circuitboard/computer/bounty - light_color = "#E2853D"//orange - var/printer_ready = 0 //cooldown var - var/static/datum/bank_account/cargocash - -/obj/machinery/computer/bounty/Initialize() - . = ..() - printer_ready = world.time + PRINTER_TIMEOUT - cargocash = SSeconomy.get_dep_account(ACCOUNT_CAR) - -/obj/machinery/computer/bounty/proc/print_paper() - new /obj/item/paper/bounty_printout(loc) - -/obj/item/paper/bounty_printout - name = "paper - Bounties" - -/obj/item/paper/bounty_printout/Initialize() - . = ..() - info = "

                    Nanotrasen Cargo Bounties


                    " - update_icon() - - for(var/datum/bounty/B in GLOB.bounties_list) - if(B.claimed) - continue - info += {"

                    [B.name]

                    -
                    • Reward: [B.reward_string()]
                    • -
                    • Completed: [B.completion_string()]
                    "} - -/obj/machinery/computer/bounty/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - if(!GLOB.bounties_list.len) - setup_bounties() - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "CargoBountyConsole", name, 750, 600, master_ui, state) - ui.open() - -/obj/machinery/computer/bounty/ui_data(mob/user) - var/list/data = list() - var/list/bountyinfo = list() - for(var/datum/bounty/B in GLOB.bounties_list) - bountyinfo += list(list("name" = B.name, "description" = B.description, "reward_string" = B.reward_string(), "completion_string" = B.completion_string() , "claimed" = B.claimed, "can_claim" = B.can_claim(), "priority" = B.high_priority, "bounty_ref" = REF(B))) - data["stored_cash"] = cargocash.account_balance - data["bountydata"] = bountyinfo - return data - -/obj/machinery/computer/bounty/ui_act(action,params) - if(..()) - return - switch(action) - if("ClaimBounty") - var/datum/bounty/cashmoney = locate(params["bounty"]) in GLOB.bounties_list - if(cashmoney) - cashmoney.claim() - return TRUE - if("Print") - if(printer_ready < world.time) - printer_ready = world.time + PRINTER_TIMEOUT - print_paper() - return +#define PRINTER_TIMEOUT 10 + +/obj/machinery/computer/bounty + name = "\improper Nanotrasen bounty console" + desc = "Used to check and claim bounties offered by Nanotrasen" + icon_screen = "bounty" + circuit = /obj/item/circuitboard/computer/bounty + light_color = "#E2853D"//orange + var/printer_ready = 0 //cooldown var + var/static/datum/bank_account/cargocash + +/obj/machinery/computer/bounty/Initialize() + . = ..() + printer_ready = world.time + PRINTER_TIMEOUT + cargocash = SSeconomy.get_dep_account(ACCOUNT_CAR) + +/obj/machinery/computer/bounty/proc/print_paper() + new /obj/item/paper/bounty_printout(loc) + +/obj/item/paper/bounty_printout + name = "paper - Bounties" + +/obj/item/paper/bounty_printout/Initialize() + . = ..() + info = "

                    Nanotrasen Cargo Bounties


                    " + update_icon() + + for(var/datum/bounty/B in GLOB.bounties_list) + if(B.claimed) + continue + info += {"

                    [B.name]

                    +
                    • Reward: [B.reward_string()]
                    • +
                    • Completed: [B.completion_string()]
                    "} + +/obj/machinery/computer/bounty/ui_interact(mob/user, datum/tgui/ui) + if(!GLOB.bounties_list.len) + setup_bounties() + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "CargoBountyConsole", name) + ui.open() + +/obj/machinery/computer/bounty/ui_data(mob/user) + var/list/data = list() + var/list/bountyinfo = list() + for(var/datum/bounty/B in GLOB.bounties_list) + bountyinfo += list(list("name" = B.name, "description" = B.description, "reward_string" = B.reward_string(), "completion_string" = B.completion_string() , "claimed" = B.claimed, "can_claim" = B.can_claim(), "priority" = B.high_priority, "bounty_ref" = REF(B))) + data["stored_cash"] = cargocash.account_balance + data["bountydata"] = bountyinfo + return data + +/obj/machinery/computer/bounty/ui_act(action,params) + if(..()) + return + switch(action) + if("ClaimBounty") + var/datum/bounty/cashmoney = locate(params["bounty"]) in GLOB.bounties_list + if(cashmoney) + cashmoney.claim() + return TRUE + if("Print") + if(printer_ready < world.time) + printer_ready = world.time + PRINTER_TIMEOUT + print_paper() + return diff --git a/code/modules/cargo/centcom_podlauncher.dm b/code/modules/cargo/centcom_podlauncher.dm index 7c6aa16c5dd7..5b87684a9f65 100644 --- a/code/modules/cargo/centcom_podlauncher.dm +++ b/code/modules/cargo/centcom_podlauncher.dm @@ -51,12 +51,13 @@ temp_pod = new(locate(/area/centcom/supplypod/podStorage) in GLOB.sortedAreas) //Create a new temp_pod in the podStorage area on centcom (so users are free to look at it and change other variables if needed) orderedArea = createOrderedArea(bay) //Order all the turfs in the selected bay (top left to bottom right) to a single list. Used for the "ordered" mode (launchChoice = 1) -/datum/centcom_podlauncher/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, \ -force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.admin_state)//ui_interact is called when the client verb is called. +/datum/centcom_podlauncher/ui_state(mob/user) + return GLOB.admin_state - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/datum/centcom_podlauncher/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "CentcomPodLauncher", "Config/Launch Supplypod", 700, 700, master_ui, state) + ui = new(user, src, "CentcomPodLauncher") ui.open() /datum/centcom_podlauncher/ui_data(mob/user) //Sends info about the pod to the UI. diff --git a/code/modules/cargo/console.dm b/code/modules/cargo/console.dm index c07abe8f104e..5515b3505336 100644 --- a/code/modules/cargo/console.dm +++ b/code/modules/cargo/console.dm @@ -1,265 +1,262 @@ -/obj/machinery/computer/cargo - name = "supply console" - desc = "Used to order supplies, approve requests, and control the shuttle." - icon_screen = "supply" - circuit = /obj/item/circuitboard/computer/cargo - ui_x = 780 - ui_y = 750 - - var/requestonly = FALSE - var/contraband = FALSE - var/self_paid = FALSE - 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." - var/blockade_warning = "Bluespace instability detected. Shuttle movement impossible." - /// radio used by the console to send messages on supply channel - var/obj/item/radio/headset/radio - /// var that tracks message cooldown - var/message_cooldown - - light_color = "#E2853D"//orange - -/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 - 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) - ..() - -/obj/machinery/computer/cargo/proc/get_export_categories() - . = EXPORT_CARGO - if(contraband) - . |= EXPORT_CONTRABAND - if(obj_flags & EMAGGED) - . |= EXPORT_EMAG - -/obj/machinery/computer/cargo/emag_act(mob/user) - if(obj_flags & EMAGGED) - return - if(user) - user.visible_message("[user] swipes a suspicious card through [src]!", - "You adjust [src]'s routing and receiver spectrum, unlocking special supplies and contraband.") - - obj_flags |= EMAGGED - contraband = TRUE - - // This also permamently sets this on the circuit board - var/obj/item/circuitboard/computer/cargo/board = circuit - board.contraband = TRUE - board.obj_flags |= EMAGGED - update_static_data(user) - -/obj/machinery/computer/cargo/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "Cargo", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/machinery/computer/cargo/ui_data() - var/list/data = list() - data["location"] = SSshuttle.supply.getStatusText() - var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) - if(D) - data["points"] = D.account_balance - 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 - 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 - -/obj/machinery/computer/cargo/ui_static_data(mob/user) - var/list/data = list() - data["requestonly"] = requestonly - data["supplies"] = list() - for(var/pack in SSshuttle.supply_packs) - var/datum/supply_pack/P = SSshuttle.supply_packs[pack] - if(!data["supplies"][P.group]) - data["supplies"][P.group] = list( - "name" = P.group, - "packs" = list() - ) - if((P.hidden && !(obj_flags & EMAGGED)) || (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. - "small_item" = P.small_item, - "access" = P.access - )) - return data - -/obj/machinery/computer/cargo/ui_act(action, params, datum/tgui/ui) - if(..()) - return - switch(action) - if("send") - if(!SSshuttle.supply.canMove()) - say(safety_warning) - return - if(SSshuttle.supplyBlocked) - say(blockade_warning) - return - if(SSshuttle.supply.getDockedId() == "supply_home") - SSshuttle.supply.export_categories = get_export_categories() - SSshuttle.moveShuttle("supply", "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) - . = TRUE - if("loan") - if(!SSshuttle.shuttle_loan) - return - if(SSshuttle.supplyBlocked) - 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() - say("The supply shuttle has been loaned to CentCom.") - 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 && !(obj_flags & EMAGGED)) || (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 && ishuman(usr)) - var/mob/living/carbon/human/H = usr - var/obj/item/card/id/id_card = H.get_idcard(TRUE) - if(!istype(id_card)) - say("No ID card detected.") - return - account = id_card.registered_account - if(!istype(account)) - say("Invalid bank account.") - return - - var/reason = "" - if(requestonly && !self_paid) - reason = stripped_input("Reason:", name, "") - if(isnull(reason) || ..()) - return - - 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) - SSshuttle.requestlist += SO - else - SSshuttle.shoppinglist += SO - if(self_paid) - say("Order processed. The price will be charged to [account.account_holder]'s bank account on delivery.") - if(requestonly && message_cooldown < world.time) - radio.talk_into(src, "A new order has been requested.", RADIO_CHANNEL_SUPPLY) - message_cooldown = world.time + 30 SECONDS - . = 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) - 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") - -/obj/machinery/computer/cargo/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) +/obj/machinery/computer/cargo + name = "supply console" + desc = "Used to order supplies, approve requests, and control the shuttle." + icon_screen = "supply" + circuit = /obj/item/circuitboard/computer/cargo + + var/requestonly = FALSE + var/contraband = FALSE + var/self_paid = FALSE + 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." + var/blockade_warning = "Bluespace instability detected. Shuttle movement impossible." + /// radio used by the console to send messages on supply channel + var/obj/item/radio/headset/radio + /// var that tracks message cooldown + var/message_cooldown + + light_color = "#E2853D"//orange + +/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 + 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) + ..() + +/obj/machinery/computer/cargo/proc/get_export_categories() + . = EXPORT_CARGO + if(contraband) + . |= EXPORT_CONTRABAND + if(obj_flags & EMAGGED) + . |= EXPORT_EMAG + +/obj/machinery/computer/cargo/emag_act(mob/user) + if(obj_flags & EMAGGED) + return + if(user) + user.visible_message("[user] swipes a suspicious card through [src]!", + "You adjust [src]'s routing and receiver spectrum, unlocking special supplies and contraband.") + + obj_flags |= EMAGGED + contraband = TRUE + + // This also permamently sets this on the circuit board + var/obj/item/circuitboard/computer/cargo/board = circuit + board.contraband = TRUE + board.obj_flags |= EMAGGED + update_static_data(user) + +/obj/machinery/computer/cargo/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "Cargo", name) + ui.open() + +/obj/machinery/computer/cargo/ui_data() + var/list/data = list() + data["location"] = SSshuttle.supply.getStatusText() + var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) + if(D) + data["points"] = D.account_balance + 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 + 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 + +/obj/machinery/computer/cargo/ui_static_data(mob/user) + var/list/data = list() + data["requestonly"] = requestonly + data["supplies"] = list() + for(var/pack in SSshuttle.supply_packs) + var/datum/supply_pack/P = SSshuttle.supply_packs[pack] + if(!data["supplies"][P.group]) + data["supplies"][P.group] = list( + "name" = P.group, + "packs" = list() + ) + if((P.hidden && !(obj_flags & EMAGGED)) || (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. + "small_item" = P.small_item, + "access" = P.access + )) + return data + +/obj/machinery/computer/cargo/ui_act(action, params, datum/tgui/ui) + if(..()) + return + switch(action) + if("send") + if(!SSshuttle.supply.canMove()) + say(safety_warning) + return + if(SSshuttle.supplyBlocked) + say(blockade_warning) + return + if(SSshuttle.supply.getDockedId() == "supply_home") + SSshuttle.supply.export_categories = get_export_categories() + SSshuttle.moveShuttle("supply", "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) + . = TRUE + if("loan") + if(!SSshuttle.shuttle_loan) + return + if(SSshuttle.supplyBlocked) + 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() + say("The supply shuttle has been loaned to CentCom.") + 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 && !(obj_flags & EMAGGED)) || (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 && ishuman(usr)) + var/mob/living/carbon/human/H = usr + var/obj/item/card/id/id_card = H.get_idcard(TRUE) + if(!istype(id_card)) + say("No ID card detected.") + return + account = id_card.registered_account + if(!istype(account)) + say("Invalid bank account.") + return + + var/reason = "" + if(requestonly && !self_paid) + reason = stripped_input("Reason:", name, "") + if(isnull(reason) || ..()) + return + + 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) + SSshuttle.requestlist += SO + else + SSshuttle.shoppinglist += SO + if(self_paid) + say("Order processed. The price will be charged to [account.account_holder]'s bank account on delivery.") + if(requestonly && message_cooldown < world.time) + radio.talk_into(src, "A new order has been requested.", RADIO_CHANNEL_SUPPLY) + message_cooldown = world.time + 30 SECONDS + . = 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) + 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") + +/obj/machinery/computer/cargo/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/cargo/expressconsole.dm b/code/modules/cargo/expressconsole.dm index 440c0dcbcd47..88825d176cf3 100644 --- a/code/modules/cargo/expressconsole.dm +++ b/code/modules/cargo/expressconsole.dm @@ -1,216 +1,214 @@ -#define MAX_EMAG_ROCKETS 8 -#define BEACON_COST 500 -#define SP_LINKED 1 -#define SP_READY 2 -#define SP_LAUNCH 3 -#define SP_UNLINK 4 -#define SP_UNREADY 5 - -/obj/machinery/computer/cargo/express - name = "express supply console" - desc = "This console allows the user to purchase a package \ - with 1/40th of the delivery time: made possible by NanoTrasen's new \"1500mm Orbital Railgun\".\ - All sales are near instantaneous - please choose carefully" - icon_screen = "supply_express" - circuit = /obj/item/circuitboard/computer/cargo/express - ui_x = 600 - ui_y = 700 - blockade_warning = "Bluespace instability detected. Delivery impossible." - req_access = list(ACCESS_QM) - - var/message - var/printed_beacons = 0 //number of beacons printed. Used to determine beacon names. - var/list/meme_pack_data - var/obj/item/supplypod_beacon/beacon //the linked supplypod beacon - var/area/landingzone = /area/quartermaster/storage //where we droppin boys - var/podType = /obj/structure/closet/supplypod - var/cooldown = 0 //cooldown to prevent printing supplypod beacon spam - var/locked = TRUE //is the console locked? unlock with ID - var/usingBeacon = FALSE //is the console in beacon mode? exists to let beacon know when a pod may come in - -/obj/machinery/computer/cargo/express/Initialize() - . = ..() - packin_up() - -/obj/machinery/computer/cargo/express/Destroy() - if(beacon) - beacon.unlink_console() - return ..() - -/obj/machinery/computer/cargo/express/attackby(obj/item/W, mob/living/user, params) - if((istype(W, /obj/item/card/id) || istype(W, /obj/item/pda)) && allowed(user)) - locked = !locked - to_chat(user, "You [locked ? "lock" : "unlock"] the interface.") - return - else if(istype(W, /obj/item/disk/cargo/bluespace_pod)) - podType = /obj/structure/closet/supplypod/bluespacepod//doesnt effect circuit board, making reversal possible - to_chat(user, "You insert the disk into [src], allowing for advanced supply delivery vehicles.") - qdel(W) - return TRUE - else if(istype(W, /obj/item/supplypod_beacon)) - var/obj/item/supplypod_beacon/sb = W - if (sb.express_console != src) - sb.link_console(src, user) - return TRUE - else - to_chat(user, "[src] is already linked to [sb].") - ..() - -/obj/machinery/computer/cargo/express/emag_act(mob/living/user) - if(obj_flags & EMAGGED) - return - if(user) - user.visible_message("[user] swipes a suspicious card through [src]!", - "You change the routing protocols, allowing the Supply Pod to land anywhere on the station.") - obj_flags |= EMAGGED - // This also sets this on the circuit board - var/obj/item/circuitboard/computer/cargo/board = circuit - board.obj_flags |= EMAGGED - packin_up() - -/obj/machinery/computer/cargo/express/proc/packin_up() // oh shit, I'm sorry - meme_pack_data = list() // sorry for what? - for(var/pack in SSshuttle.supply_packs) // our quartermaster taught us not to be ashamed of our supply packs - var/datum/supply_pack/P = SSshuttle.supply_packs[pack] // specially since they're such a good price and all - if(!meme_pack_data[P.group]) // yeah, I see that, your quartermaster gave you good advice - meme_pack_data[P.group] = list( // it gets cheaper when I return it - "name" = P.group, // mmhm - "packs" = list() // sometimes, I return it so much, I rip the manifest - ) // see, my quartermaster taught me a few things too - if((P.hidden) || (P.special)) // like, how not to rip the manifest - continue// by using someone else's crate - if(!(obj_flags & EMAGGED) && P.contraband) // will you show me? - continue // i'd be right happy to - meme_pack_data[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. - )) - -/obj/machinery/computer/cargo/express/ui_interact(mob/living/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) // Remember to use the appropriate state. - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "CargoExpress", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/machinery/computer/cargo/express/ui_data(mob/user) - var/canBeacon = beacon && (isturf(beacon.loc) || ismob(beacon.loc))//is the beacon in a valid location? - var/list/data = list() - var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) - if(D) - data["points"] = D.account_balance - data["locked"] = locked//swipe an ID to unlock - data["siliconUser"] = user.has_unlimited_silicon_privilege - data["beaconzone"] = beacon ? get_area(beacon) : ""//where is the beacon located? outputs in the tgui - data["usingBeacon"] = usingBeacon //is the mode set to deliver to the beacon or the cargobay? - data["canBeacon"] = !usingBeacon || canBeacon //is the mode set to beacon delivery, and is the beacon in a valid location? - data["canBuyBeacon"] = cooldown <= 0 && D.account_balance >= BEACON_COST - data["beaconError"] = usingBeacon && !canBeacon ? "(BEACON ERROR)" : ""//changes button text to include an error alert if necessary - data["hasBeacon"] = beacon != null//is there a linked beacon? - data["beaconName"] = beacon ? beacon.name : "No Beacon Found" - data["printMsg"] = cooldown > 0 ? "Print Beacon for [BEACON_COST] credits ([cooldown])" : "Print Beacon for [BEACON_COST] credits"//buttontext for printing beacons - data["supplies"] = list() - message = "Sales are near-instantaneous - please choose carefully." - if(SSshuttle.supplyBlocked) - message = blockade_warning - if(usingBeacon && !beacon) - message = "BEACON ERROR: BEACON MISSING"//beacon was destroyed - else if (usingBeacon && !canBeacon) - message = "BEACON ERROR: MUST BE EXPOSED"//beacon's loc/user's loc must be a turf - if(obj_flags & EMAGGED) - message = "(&!#@ERROR: ROUTING_#PROTOCOL MALF(*CT#ON. $UG%ESTE@ ACT#0N: !^/PULS3-%E)ET CIR*)ITB%ARD." - data["message"] = message - if(!meme_pack_data) - packin_up() - stack_trace("You didn't give the cargo tech good advice, and he ripped the manifest. As a result, there was no pack data for [src]") - data["supplies"] = meme_pack_data - if (cooldown > 0)//cooldown used for printing beacons - cooldown-- - return data - -/obj/machinery/computer/cargo/express/ui_act(action, params, datum/tgui/ui) - switch(action) - if("LZCargo") - usingBeacon = FALSE - if (beacon) - beacon.update_status(SP_UNREADY) //ready light on beacon will turn off - if("LZBeacon") - usingBeacon = TRUE - if (beacon) - beacon.update_status(SP_READY) //turns on the beacon's ready light - if("printBeacon") - var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) - if(D) - if(D.adjust_money(-BEACON_COST)) - cooldown = 10//a ~ten second cooldown for printing beacons to prevent spam - var/obj/item/supplypod_beacon/C = new /obj/item/supplypod_beacon(drop_location()) - C.link_console(src, usr)//rather than in beacon's Initialize(), we can assign the computer to the beacon by reusing this proc) - printed_beacons++//printed_beacons starts at 0, so the first one out will be called beacon # 1 - beacon.name = "Supply Pod Beacon #[printed_beacons]" - - - if("add")//Generate Supply Order first - var/id = text2path(params["id"]) - var/datum/supply_pack/pack = SSshuttle.supply_packs[id] - if(!istype(pack)) - 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/reason = "" - var/list/empty_turfs - var/datum/supply_order/SO = new(pack, name, rank, ckey, reason) - var/points_to_check - var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) - if(D) - points_to_check = D.account_balance - if(!(obj_flags & EMAGGED)) - if(SO.pack.cost <= points_to_check) - var/LZ - if (istype(beacon) && usingBeacon)//prioritize beacons over landing in cargobay - LZ = get_turf(beacon) - beacon.update_status(SP_LAUNCH) - else if (!usingBeacon)//find a suitable supplypod landing zone in cargobay - landingzone = GLOB.areas_by_type[/area/quartermaster/storage] - if (!landingzone) - WARNING("[src] couldnt find a Quartermaster/Storage (aka cargobay) area on the station, and as such it has set the supplypod landingzone to the area it resides in.") - landingzone = get_area(src) - for(var/turf/open/floor/T in landingzone.contents)//uses default landing zone - if(is_blocked_turf(T)) - continue - LAZYADD(empty_turfs, T) - CHECK_TICK - if(empty_turfs && empty_turfs.len) - LZ = pick(empty_turfs) - if (SO.pack.cost <= points_to_check && LZ)//we need to call the cost check again because of the CHECK_TICK call - D.adjust_money(-SO.pack.cost) - new /obj/effect/DPtarget(LZ, podType, SO) - . = TRUE - update_icon() - else - if(SO.pack.cost * (0.72*MAX_EMAG_ROCKETS) <= points_to_check) // bulk discount :^) - landingzone = GLOB.areas_by_type[pick(GLOB.the_station_areas)] //override default landing zone - for(var/turf/open/floor/T in landingzone.contents) - if(is_blocked_turf(T)) - continue - LAZYADD(empty_turfs, T) - CHECK_TICK - if(empty_turfs && empty_turfs.len) - D.adjust_money(-(SO.pack.cost * (0.72*MAX_EMAG_ROCKETS))) - - SO.generateRequisition(get_turf(src)) - for(var/i in 1 to MAX_EMAG_ROCKETS) - var/LZ = pick(empty_turfs) - LAZYREMOVE(empty_turfs, LZ) - new /obj/effect/DPtarget(LZ, podType, SO) - . = TRUE - update_icon() - CHECK_TICK +#define MAX_EMAG_ROCKETS 8 +#define BEACON_COST 500 +#define SP_LINKED 1 +#define SP_READY 2 +#define SP_LAUNCH 3 +#define SP_UNLINK 4 +#define SP_UNREADY 5 + +/obj/machinery/computer/cargo/express + name = "express supply console" + desc = "This console allows the user to purchase a package \ + with 1/40th of the delivery time: made possible by NanoTrasen's new \"1500mm Orbital Railgun\".\ + All sales are near instantaneous - please choose carefully" + icon_screen = "supply_express" + circuit = /obj/item/circuitboard/computer/cargo/express + blockade_warning = "Bluespace instability detected. Delivery impossible." + req_access = list(ACCESS_QM) + + var/message + var/printed_beacons = 0 //number of beacons printed. Used to determine beacon names. + var/list/meme_pack_data + var/obj/item/supplypod_beacon/beacon //the linked supplypod beacon + var/area/landingzone = /area/quartermaster/storage //where we droppin boys + var/podType = /obj/structure/closet/supplypod + var/cooldown = 0 //cooldown to prevent printing supplypod beacon spam + var/locked = TRUE //is the console locked? unlock with ID + var/usingBeacon = FALSE //is the console in beacon mode? exists to let beacon know when a pod may come in + +/obj/machinery/computer/cargo/express/Initialize() + . = ..() + packin_up() + +/obj/machinery/computer/cargo/express/Destroy() + if(beacon) + beacon.unlink_console() + return ..() + +/obj/machinery/computer/cargo/express/attackby(obj/item/W, mob/living/user, params) + if((istype(W, /obj/item/card/id) || istype(W, /obj/item/pda)) && allowed(user)) + locked = !locked + to_chat(user, "You [locked ? "lock" : "unlock"] the interface.") + return + else if(istype(W, /obj/item/disk/cargo/bluespace_pod)) + podType = /obj/structure/closet/supplypod/bluespacepod//doesnt effect circuit board, making reversal possible + to_chat(user, "You insert the disk into [src], allowing for advanced supply delivery vehicles.") + qdel(W) + return TRUE + else if(istype(W, /obj/item/supplypod_beacon)) + var/obj/item/supplypod_beacon/sb = W + if (sb.express_console != src) + sb.link_console(src, user) + return TRUE + else + to_chat(user, "[src] is already linked to [sb].") + ..() + +/obj/machinery/computer/cargo/express/emag_act(mob/living/user) + if(obj_flags & EMAGGED) + return + if(user) + user.visible_message("[user] swipes a suspicious card through [src]!", + "You change the routing protocols, allowing the Supply Pod to land anywhere on the station.") + obj_flags |= EMAGGED + // This also sets this on the circuit board + var/obj/item/circuitboard/computer/cargo/board = circuit + board.obj_flags |= EMAGGED + packin_up() + +/obj/machinery/computer/cargo/express/proc/packin_up() // oh shit, I'm sorry + meme_pack_data = list() // sorry for what? + for(var/pack in SSshuttle.supply_packs) // our quartermaster taught us not to be ashamed of our supply packs + var/datum/supply_pack/P = SSshuttle.supply_packs[pack] // specially since they're such a good price and all + if(!meme_pack_data[P.group]) // yeah, I see that, your quartermaster gave you good advice + meme_pack_data[P.group] = list( // it gets cheaper when I return it + "name" = P.group, // mmhm + "packs" = list() // sometimes, I return it so much, I rip the manifest + ) // see, my quartermaster taught me a few things too + if((P.hidden) || (P.special)) // like, how not to rip the manifest + continue// by using someone else's crate + if(!(obj_flags & EMAGGED) && P.contraband) // will you show me? + continue // i'd be right happy to + meme_pack_data[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. + )) + +/obj/machinery/computer/cargo/express/ui_interact(mob/living/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "CargoExpress", name) + ui.open() + +/obj/machinery/computer/cargo/express/ui_data(mob/user) + var/canBeacon = beacon && (isturf(beacon.loc) || ismob(beacon.loc))//is the beacon in a valid location? + var/list/data = list() + var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) + if(D) + data["points"] = D.account_balance + data["locked"] = locked//swipe an ID to unlock + data["siliconUser"] = user.has_unlimited_silicon_privilege + data["beaconzone"] = beacon ? get_area(beacon) : ""//where is the beacon located? outputs in the tgui + data["usingBeacon"] = usingBeacon //is the mode set to deliver to the beacon or the cargobay? + data["canBeacon"] = !usingBeacon || canBeacon //is the mode set to beacon delivery, and is the beacon in a valid location? + data["canBuyBeacon"] = cooldown <= 0 && D.account_balance >= BEACON_COST + data["beaconError"] = usingBeacon && !canBeacon ? "(BEACON ERROR)" : ""//changes button text to include an error alert if necessary + data["hasBeacon"] = beacon != null//is there a linked beacon? + data["beaconName"] = beacon ? beacon.name : "No Beacon Found" + data["printMsg"] = cooldown > 0 ? "Print Beacon for [BEACON_COST] credits ([cooldown])" : "Print Beacon for [BEACON_COST] credits"//buttontext for printing beacons + data["supplies"] = list() + message = "Sales are near-instantaneous - please choose carefully." + if(SSshuttle.supplyBlocked) + message = blockade_warning + if(usingBeacon && !beacon) + message = "BEACON ERROR: BEACON MISSING"//beacon was destroyed + else if (usingBeacon && !canBeacon) + message = "BEACON ERROR: MUST BE EXPOSED"//beacon's loc/user's loc must be a turf + if(obj_flags & EMAGGED) + message = "(&!#@ERROR: ROUTING_#PROTOCOL MALF(*CT#ON. $UG%ESTE@ ACT#0N: !^/PULS3-%E)ET CIR*)ITB%ARD." + data["message"] = message + if(!meme_pack_data) + packin_up() + stack_trace("You didn't give the cargo tech good advice, and he ripped the manifest. As a result, there was no pack data for [src]") + data["supplies"] = meme_pack_data + if (cooldown > 0)//cooldown used for printing beacons + cooldown-- + return data + +/obj/machinery/computer/cargo/express/ui_act(action, params, datum/tgui/ui) + switch(action) + if("LZCargo") + usingBeacon = FALSE + if (beacon) + beacon.update_status(SP_UNREADY) //ready light on beacon will turn off + if("LZBeacon") + usingBeacon = TRUE + if (beacon) + beacon.update_status(SP_READY) //turns on the beacon's ready light + if("printBeacon") + var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) + if(D) + if(D.adjust_money(-BEACON_COST)) + cooldown = 10//a ~ten second cooldown for printing beacons to prevent spam + var/obj/item/supplypod_beacon/C = new /obj/item/supplypod_beacon(drop_location()) + C.link_console(src, usr)//rather than in beacon's Initialize(), we can assign the computer to the beacon by reusing this proc) + printed_beacons++//printed_beacons starts at 0, so the first one out will be called beacon # 1 + beacon.name = "Supply Pod Beacon #[printed_beacons]" + + + if("add")//Generate Supply Order first + var/id = text2path(params["id"]) + var/datum/supply_pack/pack = SSshuttle.supply_packs[id] + if(!istype(pack)) + 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/reason = "" + var/list/empty_turfs + var/datum/supply_order/SO = new(pack, name, rank, ckey, reason) + var/points_to_check + var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) + if(D) + points_to_check = D.account_balance + if(!(obj_flags & EMAGGED)) + if(SO.pack.cost <= points_to_check) + var/LZ + if (istype(beacon) && usingBeacon)//prioritize beacons over landing in cargobay + LZ = get_turf(beacon) + beacon.update_status(SP_LAUNCH) + else if (!usingBeacon)//find a suitable supplypod landing zone in cargobay + landingzone = GLOB.areas_by_type[/area/quartermaster/storage] + if (!landingzone) + WARNING("[src] couldnt find a Quartermaster/Storage (aka cargobay) area on the station, and as such it has set the supplypod landingzone to the area it resides in.") + landingzone = get_area(src) + for(var/turf/open/floor/T in landingzone.contents)//uses default landing zone + if(is_blocked_turf(T)) + continue + LAZYADD(empty_turfs, T) + CHECK_TICK + if(empty_turfs && empty_turfs.len) + LZ = pick(empty_turfs) + if (SO.pack.cost <= points_to_check && LZ)//we need to call the cost check again because of the CHECK_TICK call + D.adjust_money(-SO.pack.cost) + new /obj/effect/DPtarget(LZ, podType, SO) + . = TRUE + update_icon() + else + if(SO.pack.cost * (0.72*MAX_EMAG_ROCKETS) <= points_to_check) // bulk discount :^) + landingzone = GLOB.areas_by_type[pick(GLOB.the_station_areas)] //override default landing zone + for(var/turf/open/floor/T in landingzone.contents) + if(is_blocked_turf(T)) + continue + LAZYADD(empty_turfs, T) + CHECK_TICK + if(empty_turfs && empty_turfs.len) + D.adjust_money(-(SO.pack.cost * (0.72*MAX_EMAG_ROCKETS))) + + SO.generateRequisition(get_turf(src)) + for(var/i in 1 to MAX_EMAG_ROCKETS) + var/LZ = pick(empty_turfs) + LAZYREMOVE(empty_turfs, LZ) + new /obj/effect/DPtarget(LZ, podType, SO) + . = TRUE + update_icon() + CHECK_TICK diff --git a/code/modules/cargo/gondolapod.dm b/code/modules/cargo/gondolapod.dm index 1216c55fca66..faaaa98afbdd 100644 --- a/code/modules/cargo/gondolapod.dm +++ b/code/modules/cargo/gondolapod.dm @@ -1,76 +1,76 @@ -/mob/living/simple_animal/pet/gondola/gondolapod - name = "gondola" - real_name = "gondola" - desc = "The silent walker. This one seems to be part of a delivery agency." - response_help_continuous = "pets" - response_help_simple = "pet" - response_disarm_continuous = "bops" - response_disarm_simple = "bop" - response_harm_continuous = "kicks" - response_harm_simple = "kick" - faction = list("gondola") - turns_per_move = 10 - icon = 'icons/mob/gondolapod.dmi' - icon_state = "gondolapod" - icon_living = "gondolapod" - pixel_x = -16//2x2 sprite - pixel_y = -5 - layer = TABLE_LAYER//so that deliveries dont appear underneath it - loot = list(/obj/effect/decal/cleanable/blood/gibs, /obj/item/stack/sheet/animalhide/gondola = 2, /obj/item/reagent_containers/food/snacks/meat/slab/gondola = 2) - //Gondolas aren't affected by cold. - atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) - minbodytemp = 0 - maxbodytemp = 1500 - maxHealth = 200 - health = 200 - del_on_death = TRUE - var/opened = FALSE - var/obj/structure/closet/supplypod/centcompod/linked_pod - -/mob/living/simple_animal/pet/gondola/gondolapod/Initialize(mapload, pod) - linked_pod = pod - name = linked_pod.name - . = ..() - -/mob/living/simple_animal/pet/gondola/gondolapod/update_icon_state() - if(opened) - icon_state = "gondolapod_open" - else - icon_state = "gondolapod" - -/mob/living/simple_animal/pet/gondola/gondolapod/verb/deliver() - set name = "Release Contents" - set category = "Gondola" - set desc = "Release any contents stored within your vast belly." - linked_pod.open_pod(src, forced = TRUE) - -/mob/living/simple_animal/pet/gondola/gondolapod/examine(mob/user) - . = ..() - if (contents.len) - . += "It looks like it hasn't made its delivery yet.
                    " - else - . += "It looks like it has already made its delivery.
                    " - -/mob/living/simple_animal/pet/gondola/gondolapod/verb/check() - set name = "Count Contents" - set category = "Gondola" - set desc = "Take a deep look inside youself, and count up what's inside" - var/total = contents.len - if (total) - to_chat(src, "You detect [total] object\s within your incredibly vast belly.") - else - to_chat(src, "A closer look inside yourself reveals... nothing.") - -/mob/living/simple_animal/pet/gondola/gondolapod/proc/setOpened() - opened = TRUE - update_icon() - addtimer(CALLBACK(src, .proc/setClosed), 50) - -/mob/living/simple_animal/pet/gondola/gondolapod/proc/setClosed() - opened = FALSE - update_icon() - -/mob/living/simple_animal/pet/gondola/gondolapod/death() - qdel(linked_pod) //Will cause the open() proc for the linked supplypod to be called with the "broken" parameter set to true, meaning that it will dump its contents on death - qdel(src) - ..() +/mob/living/simple_animal/pet/gondola/gondolapod + name = "gondola" + real_name = "gondola" + desc = "The silent walker. This one seems to be part of a delivery agency." + response_help_continuous = "pets" + response_help_simple = "pet" + response_disarm_continuous = "bops" + response_disarm_simple = "bop" + response_harm_continuous = "kicks" + response_harm_simple = "kick" + faction = list("gondola") + turns_per_move = 10 + icon = 'icons/mob/gondolapod.dmi' + icon_state = "gondolapod" + icon_living = "gondolapod" + pixel_x = -16//2x2 sprite + pixel_y = -5 + layer = TABLE_LAYER//so that deliveries dont appear underneath it + loot = list(/obj/effect/decal/cleanable/blood/gibs, /obj/item/stack/sheet/animalhide/gondola = 2, /obj/item/reagent_containers/food/snacks/meat/slab/gondola = 2) + //Gondolas aren't affected by cold. + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + minbodytemp = 0 + maxbodytemp = 1500 + maxHealth = 200 + health = 200 + del_on_death = TRUE + var/opened = FALSE + var/obj/structure/closet/supplypod/centcompod/linked_pod + +/mob/living/simple_animal/pet/gondola/gondolapod/Initialize(mapload, pod) + linked_pod = pod + name = linked_pod.name + . = ..() + +/mob/living/simple_animal/pet/gondola/gondolapod/update_icon_state() + if(opened) + icon_state = "gondolapod_open" + else + icon_state = "gondolapod" + +/mob/living/simple_animal/pet/gondola/gondolapod/verb/deliver() + set name = "Release Contents" + set category = "Gondola" + set desc = "Release any contents stored within your vast belly." + linked_pod.open_pod(src, forced = TRUE) + +/mob/living/simple_animal/pet/gondola/gondolapod/examine(mob/user) + . = ..() + if (contents.len) + . += "It looks like it hasn't made its delivery yet." + else + . += "It looks like it has already made its delivery." + +/mob/living/simple_animal/pet/gondola/gondolapod/verb/check() + set name = "Count Contents" + set category = "Gondola" + set desc = "Take a deep look inside youself, and count up what's inside" + var/total = contents.len + if (total) + to_chat(src, "You detect [total] object\s within your incredibly vast belly.") + else + to_chat(src, "A closer look inside yourself reveals... nothing.") + +/mob/living/simple_animal/pet/gondola/gondolapod/proc/setOpened() + opened = TRUE + update_icon() + addtimer(CALLBACK(src, .proc/setClosed), 50) + +/mob/living/simple_animal/pet/gondola/gondolapod/proc/setClosed() + opened = FALSE + update_icon() + +/mob/living/simple_animal/pet/gondola/gondolapod/death() + qdel(linked_pod) //Will cause the open() proc for the linked supplypod to be called with the "broken" parameter set to true, meaning that it will dump its contents on death + qdel(src) + ..() diff --git a/code/modules/cargo/order.dm b/code/modules/cargo/order.dm index f241b66fc71e..6a557e0c1389 100644 --- a/code/modules/cargo/order.dm +++ b/code/modules/cargo/order.dm @@ -1,117 +1,117 @@ -/obj/item/paper/fluff/jobs/cargo/manifest - var/order_cost = 0 - var/order_id = 0 - var/errors = 0 - -/obj/item/paper/fluff/jobs/cargo/manifest/New(atom/A, id, cost) - ..() - order_id = id - order_cost = cost - - if(prob(MANIFEST_ERROR_CHANCE)) - errors |= MANIFEST_ERROR_NAME - if(prob(MANIFEST_ERROR_CHANCE)) - errors |= MANIFEST_ERROR_CONTENTS - if(prob(MANIFEST_ERROR_CHANCE)) - errors |= MANIFEST_ERROR_ITEM - -/obj/item/paper/fluff/jobs/cargo/manifest/proc/is_approved() - return stamped && stamped.len && !is_denied() - -/obj/item/paper/fluff/jobs/cargo/manifest/proc/is_denied() - return stamped && ("stamp-deny" in stamped) - -/datum/supply_order - var/id - var/orderer - var/orderer_rank - var/orderer_ckey - var/reason - var/datum/supply_pack/pack - var/datum/bank_account/paying_account - -/datum/supply_order/New(datum/supply_pack/pack, orderer, orderer_rank, orderer_ckey, reason, paying_account) - id = SSshuttle.ordernum++ - src.pack = pack - src.orderer = orderer - src.orderer_rank = orderer_rank - src.orderer_ckey = orderer_ckey - src.reason = reason - src.paying_account = paying_account - -/datum/supply_order/proc/generateRequisition(turf/T) - var/obj/item/paper/P = new(T) - - P.name = "requisition form - #[id] ([pack.name])" - P.info += "

                    [station_name()] Supply Requisition

                    " - P.info += "
                    " - P.info += "Order #[id]
                    " - P.info += "Time of Order: [station_time_timestamp()]
                    " - P.info += "Item: [pack.name]
                    " - P.info += "Access Restrictions: [get_access_desc(pack.access)]
                    " - P.info += "Requested by: [orderer]
                    " - if(paying_account) - P.info += "Paid by: [paying_account.account_holder]
                    " - P.info += "Rank: [orderer_rank]
                    " - P.info += "Comment: [reason]
                    " - - P.update_icon() - return P - -/datum/supply_order/proc/generateManifest(obj/structure/closet/crate/C, owner, packname) //generates-the-manifests. - var/obj/item/paper/fluff/jobs/cargo/manifest/P = new(C, id, 0) - - var/station_name = (P.errors & MANIFEST_ERROR_NAME) ? new_station_name() : station_name() - - P.name = "shipping manifest - [packname?"#[id] ([pack.name])":"(Grouped Item Crate)"]" - P.info += "

                    [command_name()] Shipping Manifest

                    " - P.info += "
                    " - if(owner && !(owner == "Cargo")) - P.info += "Direct purchase from [owner]
                    " - P.name += " - Purchased by [owner]" - P.info += "Order[packname?"":"s"]: [id]
                    " - P.info += "Destination: [station_name]
                    " - if(packname) - P.info += "Item: [packname]
                    " - P.info += "Contents:
                    " - P.info += "
                      " - for(var/atom/movable/AM in C.contents - P) - if((P.errors & MANIFEST_ERROR_CONTENTS)) - if(prob(50)) - P.info += "
                    • [AM.name]
                    • " - else - continue - P.info += "
                    • [AM.name]
                    • " - P.info += "
                    " - P.info += "

                    Stamp below to confirm receipt of goods:

                    " - - if(P.errors & MANIFEST_ERROR_ITEM) - if(istype(C, /obj/structure/closet/crate/secure) || istype(C, /obj/structure/closet/crate/large)) - P.errors &= ~MANIFEST_ERROR_ITEM - else - var/lost = max(round(C.contents.len / 10), 1) - while(--lost >= 0) - qdel(pick(C.contents)) - - P.update_icon() - P.forceMove(C) - C.manifest = P - C.update_icon() - - return P - -/datum/supply_order/proc/generate(atom/A) - var/account_holder - if(paying_account) - account_holder = paying_account.account_holder - else - account_holder = "Cargo" - var/obj/structure/closet/crate/C = pack.generate(A, paying_account) - generateManifest(C, account_holder, pack) - return C - -/datum/supply_order/proc/generateCombo(miscbox, misc_own, misc_contents) - for (var/I in misc_contents) - new I(miscbox) - generateManifest(miscbox, misc_own, "") - return +/obj/item/paper/fluff/jobs/cargo/manifest + var/order_cost = 0 + var/order_id = 0 + var/errors = 0 + +/obj/item/paper/fluff/jobs/cargo/manifest/New(atom/A, id, cost) + ..() + order_id = id + order_cost = cost + + if(prob(MANIFEST_ERROR_CHANCE)) + errors |= MANIFEST_ERROR_NAME + if(prob(MANIFEST_ERROR_CHANCE)) + errors |= MANIFEST_ERROR_CONTENTS + if(prob(MANIFEST_ERROR_CHANCE)) + errors |= MANIFEST_ERROR_ITEM + +/obj/item/paper/fluff/jobs/cargo/manifest/proc/is_approved() + return stamped && stamped.len && !is_denied() + +/obj/item/paper/fluff/jobs/cargo/manifest/proc/is_denied() + return stamped && ("stamp-deny" in stamped) + +/datum/supply_order + var/id + var/orderer + var/orderer_rank + var/orderer_ckey + var/reason + var/datum/supply_pack/pack + var/datum/bank_account/paying_account + +/datum/supply_order/New(datum/supply_pack/pack, orderer, orderer_rank, orderer_ckey, reason, paying_account) + id = SSshuttle.ordernum++ + src.pack = pack + src.orderer = orderer + src.orderer_rank = orderer_rank + src.orderer_ckey = orderer_ckey + src.reason = reason + src.paying_account = paying_account + +/datum/supply_order/proc/generateRequisition(turf/T) + var/obj/item/paper/P = new(T) + + P.name = "requisition form - #[id] ([pack.name])" + P.info += "

                    [station_name()] Supply Requisition

                    " + P.info += "
                    " + P.info += "Order #[id]
                    " + P.info += "Time of Order: [station_time_timestamp()]
                    " + P.info += "Item: [pack.name]
                    " + P.info += "Access Restrictions: [get_access_desc(pack.access)]
                    " + P.info += "Requested by: [orderer]
                    " + if(paying_account) + P.info += "Paid by: [paying_account.account_holder]
                    " + P.info += "Rank: [orderer_rank]
                    " + P.info += "Comment: [reason]
                    " + + P.update_icon() + return P + +/datum/supply_order/proc/generateManifest(obj/structure/closet/crate/C, owner, packname) //generates-the-manifests. + var/obj/item/paper/fluff/jobs/cargo/manifest/P = new(C, id, 0) + + var/station_name = (P.errors & MANIFEST_ERROR_NAME) ? new_station_name() : station_name() + + P.name = "shipping manifest - [packname?"#[id] ([pack.name])":"(Grouped Item Crate)"]" + P.info += "

                    [command_name()] Shipping Manifest

                    " + P.info += "
                    " + if(owner && !(owner == "Cargo")) + P.info += "Direct purchase from [owner]
                    " + P.name += " - Purchased by [owner]" + P.info += "Order[packname?"":"s"]: [id]
                    " + P.info += "Destination: [station_name]
                    " + if(packname) + P.info += "Item: [packname]
                    " + P.info += "Contents:
                    " + P.info += "
                      " + for(var/atom/movable/AM in C.contents - P) + if((P.errors & MANIFEST_ERROR_CONTENTS)) + if(prob(50)) + P.info += "
                    • [AM.name]
                    • " + else + continue + P.info += "
                    • [AM.name]
                    • " + P.info += "
                    " + P.info += "

                    Stamp below to confirm receipt of goods:

                    " + + if(P.errors & MANIFEST_ERROR_ITEM) + if(istype(C, /obj/structure/closet/crate/secure) || istype(C, /obj/structure/closet/crate/large)) + P.errors &= ~MANIFEST_ERROR_ITEM + else + var/lost = max(round(C.contents.len / 10), 1) + while(--lost >= 0) + qdel(pick(C.contents)) + + P.update_icon() + P.forceMove(C) + C.manifest = P + C.update_icon() + + return P + +/datum/supply_order/proc/generate(atom/A) + var/account_holder + if(paying_account) + account_holder = paying_account.account_holder + else + account_holder = "Cargo" + var/obj/structure/closet/crate/C = pack.generate(A, paying_account) + generateManifest(C, account_holder, pack) + return C + +/datum/supply_order/proc/generateCombo(miscbox, misc_own, misc_contents) + for (var/I in misc_contents) + new I(miscbox) + generateManifest(miscbox, misc_own, "") + return diff --git a/code/modules/cargo/supplypod.dm b/code/modules/cargo/supplypod.dm index 29ee4c2bd7b9..bf5f3909009f 100644 --- a/code/modules/cargo/supplypod.dm +++ b/code/modules/cargo/supplypod.dm @@ -1,366 +1,366 @@ -//The "BDPtarget" temp visual is created by anything that "launches" a supplypod. It makes two things: a falling droppod animation, and the droppod itself. -//------------------------------------SUPPLY POD-------------------------------------// -/obj/structure/closet/supplypod - name = "supply pod" //Names and descriptions are normally created with the setStyle() proc during initialization, but we have these default values here as a failsafe - desc = "A Nanotrasen supply drop pod." - icon = 'icons/obj/supplypods.dmi' - icon_state = "supplypod" - pixel_x = -16 //2x2 sprite - pixel_y = -5 - layer = TABLE_LAYER //So that the crate inside doesn't appear underneath - allow_objects = TRUE - allow_dense = TRUE - delivery_icon = null - can_weld_shut = FALSE - armor = list("melee" = 30, "bullet" = 50, "laser" = 50, "energy" = 100, "bomb" = 100, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 80) - anchored = TRUE //So it cant slide around after landing - anchorable = FALSE - flags_1 = PREVENT_CONTENTS_EXPLOSION_1 - //*****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/damage = 0 //Damage that occurs to any mob under the pod when it lands. - var/effectStun = FALSE //If true, stuns anyone under the pod when it launches until it lands, forcing them to get hit by the pod. Devilish! - var/effectLimb = FALSE //If true, pops off a limb (if applicable) from anyone caught under the pod when it lands - var/effectOrgans = FALSE //If true, yeets out every limb and organ from anyone caught under the pod when it lands - var/effectGib = FALSE //If true, anyone under the pod will be gibbed when it lands - var/effectStealth = FALSE //If true, a target icon isnt displayed on the turf where the pod will land - var/effectQuiet = FALSE //The female sniper. If true, the pod makes no noise (including related explosions, opening sounds, etc) - var/effectMissile = FALSE //If true, the pod deletes the second it lands. If you give it an explosion, it will act like a missile exploding as it hits the ground - var/effectCircle = FALSE //If true, allows the pod to come in at any angle. Bit of a weird feature but whatever its here - var/style = STYLE_STANDARD //Style is a variable that keeps track of what the pod is supposed to look like. It acts as an index to the POD_STYLES list in cargo.dm defines to get the proper icon/name/desc for the pod. - var/reversing = FALSE //If true, the pod will not send any items. Instead, after opening, it will close again (picking up items/mobs) and fly back to centcom - var/fallDuration = 4 - var/fallingSoundLength = 11 - var/fallingSound = 'sound/weapons/mortar_long_whistle.ogg'//Admin sound to play before the pod lands - var/landingSound //Admin sound to play when the pod lands - var/openingSound //Admin sound to play when the pod opens - var/leavingSound //Admin sound to play when the pod leaves - var/soundVolume = 80 //Volume to play sounds at. Ignores the cap - var/bay //Used specifically for the centcom_podlauncher datum. Holds the current bay the user is launching objects from. Bays are specific rooms on the centcom map. - var/list/explosionSize = list(0,0,2,3) - var/stay_after_drop = FALSE - var/specialised = TRUE // It's not a general use pod for cargo/admin use - -/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" - desc = "A specalised, blood-red styled pod for extracting high-value targets out of active mission areas." - specialised = TRUE - style = STYLE_SYNDICATE - bluespace = TRUE - explosionSize = list(0,0,1,2) - landingDelay = 25 //Longer than others - -/obj/structure/closet/supplypod/centcompod - style = STYLE_CENTCOM - bluespace = TRUE - explosionSize = list(0,0,0,0) - landingDelay = 20 //Very speedy! - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - - -/obj/structure/closet/supplypod/proc/specialisedPod() - return 1 - -/obj/structure/closet/supplypod/extractionpod/specialisedPod(atom/movable/holder) - holder.forceMove(pick(GLOB.holdingfacility)) // land in ninja jail - open_pod(holder, forced = TRUE) - -/obj/structure/closet/supplypod/Initialize() - . = ..() - setStyle(style, TRUE) //Upon initialization, give the supplypod an iconstate, name, and description based on the "style" variable. This system is important for the centcom_podlauncher to function correctly - -/obj/structure/closet/supplypod/update_overlays() - . = ..() - if (style == STYLE_SEETHROUGH || style == STYLE_INVISIBLE) //If we're invisible, we dont bother adding any overlays - return - else - if (opened) - . += "[icon_state]_open" - else - . += "[icon_state]_door" - -/obj/structure/closet/supplypod/proc/setStyle(chosenStyle, duringInit = FALSE) //Used to give the sprite an icon state, name, and description - if (!duringInit && style == chosenStyle) //Check if the input style is already the same as the pod's style. This happens in centcom_podlauncher, and as such we set the style to STYLE_CENTCOM. - setStyle(STYLE_CENTCOM) //We make sure to not check this during initialize() so the standard supplypod works correctly. - return - style = chosenStyle - icon_state = POD_STYLES[chosenStyle][POD_ICON_STATE] //POD_STYLES is a 2D array we treat as a dictionary. The style represents the verticle index, with the icon state, name, and desc being stored in the horizontal indexes of the 2D array. - if (!adminNamed && !specialised) //We dont want to name it ourselves if it has been specifically named by an admin using the centcom_podlauncher datum - name = POD_STYLES[chosenStyle][POD_NAME] - desc = POD_STYLES[chosenStyle][POD_DESC] - update_icon() - -/obj/structure/closet/supplypod/tool_interact(obj/item/W, mob/user) - if(bluespace) //We dont want to worry about interacting with bluespace pods, as they are due to delete themselves soon anyways. - return FALSE - else - ..() - -/obj/structure/closet/supplypod/ex_act() //Explosions dont do SHIT TO US! This is because supplypods create explosions when they land. - return - -/obj/structure/closet/supplypod/contents_explosion() //Supplypods also protect their contents from the harmful effects of fucking exploding. - return - -/obj/structure/closet/supplypod/toggle(mob/living/user) - return - -/obj/structure/closet/supplypod/open(mob/living/user, force = TRUE) //Supplypods shouldn't be able to be manually opened under any circumstances - return - -/obj/structure/closet/supplypod/proc/handleReturningClose(atom/movable/holder, returntobay) - opened = FALSE - INVOKE_ASYNC(holder, .proc/setClosed) //Use the INVOKE_ASYNC proc to call setClosed() on whatever the holder may be, without giving the atom/movable base class a setClosed() proc definition - for (var/atom/movable/O in get_turf(holder)) - if ((ismob(O) && !isliving(O)) || (is_type_in_typecache(O, GLOB.blacklisted_cargo_types) && !isliving(O))) //We dont want to take ghosts with us, and we don't want blacklisted items going, but we allow mobs. - continue - O.forceMove(holder) //Put objects inside before we close - var/obj/effect/temp_visual/risingPod = new /obj/effect/DPfall(get_turf(holder), src) //Make a nice animation of flying back up - risingPod.pixel_z = 0 //The initial value of risingPod's pixel_z is 200 because it normally comes down from a high spot - animate(risingPod, pixel_z = 200, time = 10, easing = LINEAR_EASING) //Animate our rising pod - if (returntobay) - holder.forceMove(bay) //Move the pod back to centcom, where it belongs - QDEL_IN(risingPod, 10) - reversing = FALSE //Now that we're done reversing, we set this to false (otherwise we would get stuck in an infinite loop of calling the close proc at the bottom of open() ) - bluespace = TRUE //Make it so that the pod doesn't stay in centcom forever - open_pod(holder, forced = TRUE) - else - reversing = FALSE //Now that we're done reversing, we set this to false (otherwise we would get stuck in an infinite loop of calling the close proc at the bottom of open() ) - bluespace = TRUE //Make it so that the pod doesn't stay in centcom forever - - QDEL_IN(risingPod, 10) - audible_message("The pod hisses, closing quickly and launching itself away from the station.", "The ground vibrates, the nearby pod launching away from the station.") - - stay_after_drop = FALSE - specialisedPod(holder) // Do special actions for specialised pods - this is likely if we were already doing manual launches - -/obj/structure/closet/supplypod/proc/preOpen() //Called before the open() proc. Handles anything that occurs right as the pod lands. - var/turf/T = get_turf(src) - var/list/B = explosionSize //Mostly because B is more readable than explosionSize :p - if (landingSound) - playsound(get_turf(src), landingSound, soundVolume, FALSE, FALSE) - for (var/mob/living/M in T) - if (effectLimb && iscarbon(M)) //If effectLimb is true (which means we pop limbs off when we hit people): - var/mob/living/carbon/CM = M - for (var/obj/item/bodypart/bodypart in CM.bodyparts) //Look at the bodyparts in our poor mob beneath our pod as it lands - if(bodypart.body_part != HEAD && bodypart.body_part != CHEST)//we dont want to kill him, just teach em a lesson! - if (bodypart.dismemberable) - bodypart.dismember() //Using the power of flextape i've sawed this man's limb in half! - break - if (effectOrgans && iscarbon(M)) //effectOrgans means remove every organ in our mob - var/mob/living/carbon/CM = M - for(var/X in CM.internal_organs) - var/destination = get_edge_target_turf(T, pick(GLOB.alldirs)) //Pick a random direction to toss them in - var/obj/item/organ/O = X - O.Remove(CM) //Note that this isn't the same proc as for lists - O.forceMove(T) //Move the organ outta the body - O.throw_at(destination, 2, 3) //Thow the organ at a random tile 3 spots away - sleep(1) - for (var/obj/item/bodypart/bodypart in CM.bodyparts) //Look at the bodyparts in our poor mob beneath our pod as it lands - var/destination = get_edge_target_turf(T, pick(GLOB.alldirs)) - if (bodypart.dismemberable) - bodypart.dismember() //Using the power of flextape i've sawed this man's bodypart in half! - bodypart.throw_at(destination, 2, 3) - sleep(1) - - if (effectGib) //effectGib is on, that means whatever's underneath us better be fucking oof'd on - M.adjustBruteLoss(5000) //THATS A LOT OF DAMAGE (called just in case gib() doesnt work on em) - M.gib() //After adjusting the fuck outta that brute loss we finish the job with some satisfying gibs - M.adjustBruteLoss(damage) - var/explosion_sum = B[1] + B[2] + B[3] + B[4] - if (explosion_sum != 0) //If the explosion list isn't all zeroes, call an explosion - explosion(get_turf(src), B[1], B[2], B[3], flame_range = B[4], silent = effectQuiet, ignorecap = istype(src, /obj/structure/closet/supplypod/centcompod)) //less advanced equipment than bluespace pod, so larger explosion when landing - else if (!effectQuiet) //If our explosion list IS all zeroes, we still make a nice explosion sound (unless the effectQuiet var is true) - playsound(src, "explosion", landingSound ? 15 : 80, TRUE) - if (effectMissile) //If we are acting like a missile, then right after we land and finish fucking shit up w explosions, we should delete - opened = TRUE //We set opened to TRUE to avoid spending time trying to open (due to being deleted) during the Destroy() proc - qdel(src) - return - if (style == STYLE_GONDOLA) //Checks if we are supposed to be a gondola pod. If so, create a gondolapod mob, and move this pod to nullspace. I'd like to give a shout out, to my man oranges - var/mob/living/simple_animal/pet/gondola/gondolapod/benis = new(get_turf(src), src) - benis.contents |= contents //Move the contents of this supplypod into the gondolapod mob. - moveToNullspace() - addtimer(CALLBACK(src, .proc/open, benis), openingDelay) //After the openingDelay passes, we use the open proc from this supplyprod while referencing the contents of the "holder", in this case the gondolapod mob - 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 - -/obj/structure/closet/supplypod/proc/open_pod(atom/movable/holder, broken = FALSE, forced = FALSE) //The holder var represents an atom whose contents we will be working with - if (!holder) - return - if (opened) //This is to ensure we don't open something that has already been opened - return - opened = TRUE - var/turf/T = get_turf(holder) //Get the turf of whoever's contents we're talking about - var/mob/M - if (istype(holder, /mob)) //Allows mobs to assume the role of the holder, meaning we look at the mob's contents rather than the supplypod's contents. Typically by this point the supplypod's contents have already been moved over to the mob's contents - M = holder - if (M.key && !forced && !broken) //If we are player controlled, then we shouldnt open unless the opening is manual, or if it is due to being destroyed (represented by the "broken" parameter) - return - if (openingSound) - playsound(get_turf(holder), openingSound, soundVolume, FALSE, FALSE) //Special admin sound to play - INVOKE_ASYNC(holder, .proc/setOpened) //Use the INVOKE_ASYNC proc to call setOpened() on whatever the holder may be, without giving the atom/movable base class a setOpened() proc definition - if (style == STYLE_SEETHROUGH) - update_icon() - for (var/atom/movable/O in holder.contents) //Go through the contents of the holder - O.forceMove(T) //move everything from the contents of the holder to the turf of the holder - if (!effectQuiet && !openingSound && style != STYLE_SEETHROUGH) //If we aren't being quiet, play the default pod open sound - playsound(get_turf(holder), open_sound, 15, TRUE, -3) - if (broken) //If the pod is opening because it's been destroyed, we end here - return - if (style == STYLE_SEETHROUGH) - depart(src) - else - if(!stay_after_drop) // Departing should be handled manually - addtimer(CALLBACK(src, .proc/depart, holder), departureDelay) //Finish up the pod's duties after a certain amount of time - -/obj/structure/closet/supplypod/proc/depart(atom/movable/holder) - if (leavingSound) - playsound(get_turf(holder), leavingSound, soundVolume, FALSE, FALSE) - if (reversing) //If we're reversing, we call the close proc. This sends the pod back up to centcom - close(holder) - else if (bluespace) //If we're a bluespace pod, then delete ourselves (along with our holder, if a seperate holder exists) - if (!effectQuiet && style != STYLE_INVISIBLE && style != STYLE_SEETHROUGH) - do_sparks(5, TRUE, holder) //Create some sparks right before closing - qdel(src) //Delete ourselves and the holder - if (holder != src) - qdel(holder) - -/obj/structure/closet/supplypod/centcompod/close(atom/movable/holder) //Closes the supplypod and sends it back to centcom. Should only ever be called if the "reversing" variable is true - handleReturningClose(holder, TRUE) - -/obj/structure/closet/supplypod/extractionpod/close(atom/movable/holder) //handles closing, and returns pod - deletes itself when returned - . = ..() - return - -/obj/structure/closet/supplypod/extractionpod/proc/send_up(atom/movable/holder) - if (!holder) - holder = src - - if (leavingSound) - playsound(get_turf(holder), leavingSound, soundVolume, FALSE, FALSE) - - handleReturningClose(holder, FALSE) - -/obj/structure/closet/supplypod/proc/setOpened() //Proc exists here, as well as in any atom that can assume the role of a "holder" of a supplypod. Check the open() proc for more details - update_icon() - -/obj/structure/closet/supplypod/proc/setClosed() //Ditto - update_icon() - -/obj/structure/closet/supplypod/Destroy() - open_pod(holder = src, broken = TRUE) //Lets dump our contents by opening up - . = ..() - -//------------------------------------FALLING SUPPLY POD-------------------------------------// -/obj/effect/DPfall //Falling pod - name = "" - icon = 'icons/obj/supplypods.dmi' - pixel_x = -16 - pixel_y = -5 - pixel_z = 200 - desc = "Get out of the way!" - layer = FLY_LAYER//that wasnt flying, that was falling with style! - icon_state = "" - -/obj/effect/DPfall/Initialize(dropLocation, obj/structure/closet/supplypod/pod) - if (pod.style == STYLE_SEETHROUGH) - pixel_x = -16 - pixel_y = 0 - for (var/atom/movable/O in pod.contents) - var/icon/I = getFlatIcon(O) //im so sorry - add_overlay(I) - else if (pod.style != STYLE_INVISIBLE) //Check to ensure the pod isn't invisible - icon_state = "[pod.icon_state]_falling" - name = pod.name - . = ..() - -//------------------------------------TEMPORARY_VISUAL-------------------------------------// -/obj/effect/DPtarget //This is the object that forceMoves the supplypod to it's location - name = "Landing Zone Indicator" - desc = "A holographic projection designating the landing zone of something. It's probably best to stand back." - icon = 'icons/mob/actions/actions_items.dmi' - icon_state = "sniper_zoom" - layer = PROJECTILE_HIT_THRESHHOLD_LAYER - light_range = 2 - var/obj/effect/temp_visual/fallingPod //Temporary "falling pod" that we animate - var/obj/structure/closet/supplypod/pod //The supplyPod that will be landing ontop of this target - -/obj/effect/ex_act() - return - -/obj/effect/DPtarget/Initialize(mapload, podParam, single_order = null) - . = ..() - if (ispath(podParam)) //We can pass either a path for a pod (as expressconsoles do), or a reference to an instantiated pod (as the centcom_podlauncher does) - podParam = new podParam() //If its just a path, instantiate it - pod = podParam - if (single_order) - if (istype(single_order, /datum/supply_order)) - var/datum/supply_order/SO = single_order - SO.generate(pod) - else if (istype(single_order, /atom/movable)) - var/atom/movable/O = single_order - O.forceMove(pod) - for (var/mob/living/M in pod) //If there are any mobs in the supplypod, we want to forceMove them into the target. This is so that they can see where they are about to land, AND so that they don't get sent to the nullspace error room (as the pod is currently in nullspace) - M.forceMove(src) - if(pod.effectStun) //If effectStun is true, stun any mobs caught on this target until the pod gets a chance to hit them - for (var/mob/living/M in get_turf(src)) - M.Stun(pod.landingDelay+10, ignore_canstun = TRUE)//you aint goin nowhere, kid. - if (pod.effectStealth) //If effectStealth is true we want to be invisible - icon_state = "" - if (pod.fallDuration == initial(pod.fallDuration) && pod.landingDelay + pod.fallDuration < pod.fallingSoundLength) - 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 - if (soundStartTime < 0) - soundStartTime = 1 - if (!pod.effectQuiet) - addtimer(CALLBACK(src, .proc/playFallingSound), soundStartTime) - addtimer(CALLBACK(src, .proc/beginLaunch, pod.effectCircle), pod.landingDelay) - -/obj/effect/DPtarget/proc/playFallingSound() - playsound(src, pod.fallingSound, pod.soundVolume, TRUE, 6) - -/obj/effect/DPtarget/proc/beginLaunch(effectCircle) //Begin the animation for the pod falling. The effectCircle param determines whether the pod gets to come in from any descent angle - fallingPod = new /obj/effect/DPfall(drop_location(), pod) - var/matrix/M = matrix(fallingPod.transform) //Create a new matrix that we can rotate - var/angle = effectCircle ? rand(0,360) : rand(70,110) //The angle that we can come in from - fallingPod.pixel_x = cos(angle)*400 //Use some ADVANCED MATHEMATICS to set the animated pod's position to somewhere on the edge of a circle with the center being the target - fallingPod.pixel_z = sin(angle)*400 - var/rotation = Get_Pixel_Angle(fallingPod.pixel_z, fallingPod.pixel_x) //CUSTOM HOMEBREWED proc that is just arctan with extra steps - M.Turn(rotation) //Turn our matrix accordingly - fallingPod.transform = M //Transform the animated pod according to the matrix - M = matrix(pod.transform) //Make another matrix based on the pod - M.Turn(rotation) //Turn the matrix - pod.transform = M //Turn the actual pod (Won't be visible until endLaunch() proc tho) - animate(fallingPod, pixel_z = 0, pixel_x = -16, time = pod.fallDuration, , easing = LINEAR_EASING) //Make the pod fall! At an angle! - addtimer(CALLBACK(src, .proc/endLaunch), pod.fallDuration, TIMER_CLIENT_TIME) //Go onto the last step after a very short falling animation - -/obj/effect/DPtarget/proc/endLaunch() - pod.update_icon() - pod.forceMove(drop_location()) //The fallingPod animation is over, now's a good time to forceMove the actual pod into position - QDEL_NULL(fallingPod) //Delete the falling pod effect, because at this point its animation is over. We dont use temp_visual because we want to manually delete it as soon as the pod appears - for (var/mob/living/M in src) //Remember earlier (initialization) when we moved mobs into the DPTarget so they wouldnt get lost in nullspace? Time to get them out - M.forceMove(pod) - pod.preOpen() //Begin supplypod open procedures. Here effects like explosions, damage, and other dangerous (and potentially admin-caused, if the centcom_podlauncher datum was used) memes will take place - qdel(src) //The target's purpose is complete. It can rest easy now - -//------------------------------------UPGRADES-------------------------------------// -/obj/item/disk/cargo/bluespace_pod //Disk that can be inserted into the Express Console to allow for Advanced Bluespace Pods - name = "Bluespace Drop Pod Upgrade" - desc = "This disk provides a firmware update to the Express Supply Console, granting the use of Nanotrasen's Bluespace Drop Pods to the supply department." - icon = 'icons/obj/module.dmi' - icon_state = "cargodisk" - item_state = "card-id" - w_class = WEIGHT_CLASS_SMALL +//The "BDPtarget" temp visual is created by anything that "launches" a supplypod. It makes two things: a falling droppod animation, and the droppod itself. +//------------------------------------SUPPLY POD-------------------------------------// +/obj/structure/closet/supplypod + name = "supply pod" //Names and descriptions are normally created with the setStyle() proc during initialization, but we have these default values here as a failsafe + desc = "A Nanotrasen supply drop pod." + icon = 'icons/obj/supplypods.dmi' + icon_state = "supplypod" + pixel_x = -16 //2x2 sprite + pixel_y = -5 + layer = TABLE_LAYER //So that the crate inside doesn't appear underneath + allow_objects = TRUE + allow_dense = TRUE + delivery_icon = null + can_weld_shut = FALSE + armor = list("melee" = 30, "bullet" = 50, "laser" = 50, "energy" = 100, "bomb" = 100, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 80) + anchored = TRUE //So it cant slide around after landing + anchorable = FALSE + flags_1 = PREVENT_CONTENTS_EXPLOSION_1 + //*****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/damage = 0 //Damage that occurs to any mob under the pod when it lands. + var/effectStun = FALSE //If true, stuns anyone under the pod when it launches until it lands, forcing them to get hit by the pod. Devilish! + var/effectLimb = FALSE //If true, pops off a limb (if applicable) from anyone caught under the pod when it lands + var/effectOrgans = FALSE //If true, yeets out every limb and organ from anyone caught under the pod when it lands + var/effectGib = FALSE //If true, anyone under the pod will be gibbed when it lands + var/effectStealth = FALSE //If true, a target icon isnt displayed on the turf where the pod will land + var/effectQuiet = FALSE //The female sniper. If true, the pod makes no noise (including related explosions, opening sounds, etc) + var/effectMissile = FALSE //If true, the pod deletes the second it lands. If you give it an explosion, it will act like a missile exploding as it hits the ground + var/effectCircle = FALSE //If true, allows the pod to come in at any angle. Bit of a weird feature but whatever its here + var/style = STYLE_STANDARD //Style is a variable that keeps track of what the pod is supposed to look like. It acts as an index to the POD_STYLES list in cargo.dm defines to get the proper icon/name/desc for the pod. + var/reversing = FALSE //If true, the pod will not send any items. Instead, after opening, it will close again (picking up items/mobs) and fly back to centcom + var/fallDuration = 4 + var/fallingSoundLength = 11 + var/fallingSound = 'sound/weapons/mortar_long_whistle.ogg'//Admin sound to play before the pod lands + var/landingSound //Admin sound to play when the pod lands + var/openingSound //Admin sound to play when the pod opens + var/leavingSound //Admin sound to play when the pod leaves + var/soundVolume = 80 //Volume to play sounds at. Ignores the cap + var/bay //Used specifically for the centcom_podlauncher datum. Holds the current bay the user is launching objects from. Bays are specific rooms on the centcom map. + var/list/explosionSize = list(0,0,2,3) + var/stay_after_drop = FALSE + var/specialised = TRUE // It's not a general use pod for cargo/admin use + +/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" + desc = "A specalised, blood-red styled pod for extracting high-value targets out of active mission areas." + specialised = TRUE + style = STYLE_SYNDICATE + bluespace = TRUE + explosionSize = list(0,0,1,2) + landingDelay = 25 //Longer than others + +/obj/structure/closet/supplypod/centcompod + style = STYLE_CENTCOM + bluespace = TRUE + explosionSize = list(0,0,0,0) + landingDelay = 20 //Very speedy! + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + + +/obj/structure/closet/supplypod/proc/specialisedPod() + return 1 + +/obj/structure/closet/supplypod/extractionpod/specialisedPod(atom/movable/holder) + holder.forceMove(pick(GLOB.holdingfacility)) // land in ninja jail + open_pod(holder, forced = TRUE) + +/obj/structure/closet/supplypod/Initialize() + . = ..() + setStyle(style, TRUE) //Upon initialization, give the supplypod an iconstate, name, and description based on the "style" variable. This system is important for the centcom_podlauncher to function correctly + +/obj/structure/closet/supplypod/update_overlays() + . = ..() + if (style == STYLE_SEETHROUGH || style == STYLE_INVISIBLE) //If we're invisible, we dont bother adding any overlays + return + else + if (opened) + . += "[icon_state]_open" + else + . += "[icon_state]_door" + +/obj/structure/closet/supplypod/proc/setStyle(chosenStyle, duringInit = FALSE) //Used to give the sprite an icon state, name, and description + if (!duringInit && style == chosenStyle) //Check if the input style is already the same as the pod's style. This happens in centcom_podlauncher, and as such we set the style to STYLE_CENTCOM. + setStyle(STYLE_CENTCOM) //We make sure to not check this during initialize() so the standard supplypod works correctly. + return + style = chosenStyle + icon_state = POD_STYLES[chosenStyle][POD_ICON_STATE] //POD_STYLES is a 2D array we treat as a dictionary. The style represents the verticle index, with the icon state, name, and desc being stored in the horizontal indexes of the 2D array. + if (!adminNamed && !specialised) //We dont want to name it ourselves if it has been specifically named by an admin using the centcom_podlauncher datum + name = POD_STYLES[chosenStyle][POD_NAME] + desc = POD_STYLES[chosenStyle][POD_DESC] + update_icon() + +/obj/structure/closet/supplypod/tool_interact(obj/item/W, mob/user) + if(bluespace) //We dont want to worry about interacting with bluespace pods, as they are due to delete themselves soon anyways. + return FALSE + else + ..() + +/obj/structure/closet/supplypod/ex_act() //Explosions dont do SHIT TO US! This is because supplypods create explosions when they land. + return + +/obj/structure/closet/supplypod/contents_explosion() //Supplypods also protect their contents from the harmful effects of fucking exploding. + return + +/obj/structure/closet/supplypod/toggle(mob/living/user) + return + +/obj/structure/closet/supplypod/open(mob/living/user, force = TRUE) //Supplypods shouldn't be able to be manually opened under any circumstances + return + +/obj/structure/closet/supplypod/proc/handleReturningClose(atom/movable/holder, returntobay) + opened = FALSE + INVOKE_ASYNC(holder, .proc/setClosed) //Use the INVOKE_ASYNC proc to call setClosed() on whatever the holder may be, without giving the atom/movable base class a setClosed() proc definition + for (var/atom/movable/O in get_turf(holder)) + if ((ismob(O) && !isliving(O)) || (is_type_in_typecache(O, GLOB.blacklisted_cargo_types) && !isliving(O))) //We dont want to take ghosts with us, and we don't want blacklisted items going, but we allow mobs. + continue + O.forceMove(holder) //Put objects inside before we close + var/obj/effect/temp_visual/risingPod = new /obj/effect/DPfall(get_turf(holder), src) //Make a nice animation of flying back up + risingPod.pixel_z = 0 //The initial value of risingPod's pixel_z is 200 because it normally comes down from a high spot + animate(risingPod, pixel_z = 200, time = 10, easing = LINEAR_EASING) //Animate our rising pod + if (returntobay) + holder.forceMove(bay) //Move the pod back to centcom, where it belongs + QDEL_IN(risingPod, 10) + reversing = FALSE //Now that we're done reversing, we set this to false (otherwise we would get stuck in an infinite loop of calling the close proc at the bottom of open() ) + bluespace = TRUE //Make it so that the pod doesn't stay in centcom forever + open_pod(holder, forced = TRUE) + else + reversing = FALSE //Now that we're done reversing, we set this to false (otherwise we would get stuck in an infinite loop of calling the close proc at the bottom of open() ) + bluespace = TRUE //Make it so that the pod doesn't stay in centcom forever + + QDEL_IN(risingPod, 10) + audible_message("The pod hisses, closing quickly and launching itself away from the station.", "The ground vibrates, the nearby pod launching away from the station.") + + stay_after_drop = FALSE + specialisedPod(holder) // Do special actions for specialised pods - this is likely if we were already doing manual launches + +/obj/structure/closet/supplypod/proc/preOpen() //Called before the open() proc. Handles anything that occurs right as the pod lands. + var/turf/T = get_turf(src) + var/list/B = explosionSize //Mostly because B is more readable than explosionSize :p + if (landingSound) + playsound(get_turf(src), landingSound, soundVolume, FALSE, FALSE) + for (var/mob/living/M in T) + if (effectLimb && iscarbon(M)) //If effectLimb is true (which means we pop limbs off when we hit people): + var/mob/living/carbon/CM = M + for (var/obj/item/bodypart/bodypart in CM.bodyparts) //Look at the bodyparts in our poor mob beneath our pod as it lands + if(bodypart.body_part != HEAD && bodypart.body_part != CHEST)//we dont want to kill him, just teach em a lesson! + if (bodypart.dismemberable) + bodypart.dismember() //Using the power of flextape i've sawed this man's limb in half! + break + if (effectOrgans && iscarbon(M)) //effectOrgans means remove every organ in our mob + var/mob/living/carbon/CM = M + for(var/X in CM.internal_organs) + var/destination = get_edge_target_turf(T, pick(GLOB.alldirs)) //Pick a random direction to toss them in + var/obj/item/organ/O = X + O.Remove(CM) //Note that this isn't the same proc as for lists + O.forceMove(T) //Move the organ outta the body + O.throw_at(destination, 2, 3) //Thow the organ at a random tile 3 spots away + sleep(1) + for (var/obj/item/bodypart/bodypart in CM.bodyparts) //Look at the bodyparts in our poor mob beneath our pod as it lands + var/destination = get_edge_target_turf(T, pick(GLOB.alldirs)) + if (bodypart.dismemberable) + bodypart.dismember() //Using the power of flextape i've sawed this man's bodypart in half! + bodypart.throw_at(destination, 2, 3) + sleep(1) + + if (effectGib) //effectGib is on, that means whatever's underneath us better be fucking oof'd on + M.adjustBruteLoss(5000) //THATS A LOT OF DAMAGE (called just in case gib() doesnt work on em) + M.gib() //After adjusting the fuck outta that brute loss we finish the job with some satisfying gibs + M.adjustBruteLoss(damage) + var/explosion_sum = B[1] + B[2] + B[3] + B[4] + if (explosion_sum != 0) //If the explosion list isn't all zeroes, call an explosion + explosion(get_turf(src), B[1], B[2], B[3], flame_range = B[4], silent = effectQuiet, ignorecap = istype(src, /obj/structure/closet/supplypod/centcompod)) //less advanced equipment than bluespace pod, so larger explosion when landing + else if (!effectQuiet) //If our explosion list IS all zeroes, we still make a nice explosion sound (unless the effectQuiet var is true) + playsound(src, "explosion", landingSound ? 15 : 80, TRUE) + if (effectMissile) //If we are acting like a missile, then right after we land and finish fucking shit up w explosions, we should delete + opened = TRUE //We set opened to TRUE to avoid spending time trying to open (due to being deleted) during the Destroy() proc + qdel(src) + return + if (style == STYLE_GONDOLA) //Checks if we are supposed to be a gondola pod. If so, create a gondolapod mob, and move this pod to nullspace. I'd like to give a shout out, to my man oranges + var/mob/living/simple_animal/pet/gondola/gondolapod/benis = new(get_turf(src), src) + benis.contents |= contents //Move the contents of this supplypod into the gondolapod mob. + moveToNullspace() + addtimer(CALLBACK(src, .proc/open, benis), openingDelay) //After the openingDelay passes, we use the open proc from this supplyprod while referencing the contents of the "holder", in this case the gondolapod mob + 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 + +/obj/structure/closet/supplypod/proc/open_pod(atom/movable/holder, broken = FALSE, forced = FALSE) //The holder var represents an atom whose contents we will be working with + if (!holder) + return + if (opened) //This is to ensure we don't open something that has already been opened + return + opened = TRUE + var/turf/T = get_turf(holder) //Get the turf of whoever's contents we're talking about + var/mob/M + if (istype(holder, /mob)) //Allows mobs to assume the role of the holder, meaning we look at the mob's contents rather than the supplypod's contents. Typically by this point the supplypod's contents have already been moved over to the mob's contents + M = holder + if (M.key && !forced && !broken) //If we are player controlled, then we shouldnt open unless the opening is manual, or if it is due to being destroyed (represented by the "broken" parameter) + return + if (openingSound) + playsound(get_turf(holder), openingSound, soundVolume, FALSE, FALSE) //Special admin sound to play + INVOKE_ASYNC(holder, .proc/setOpened) //Use the INVOKE_ASYNC proc to call setOpened() on whatever the holder may be, without giving the atom/movable base class a setOpened() proc definition + if (style == STYLE_SEETHROUGH) + update_icon() + for (var/atom/movable/O in holder.contents) //Go through the contents of the holder + O.forceMove(T) //move everything from the contents of the holder to the turf of the holder + if (!effectQuiet && !openingSound && style != STYLE_SEETHROUGH) //If we aren't being quiet, play the default pod open sound + playsound(get_turf(holder), open_sound, 15, TRUE, -3) + if (broken) //If the pod is opening because it's been destroyed, we end here + return + if (style == STYLE_SEETHROUGH) + depart(src) + else + if(!stay_after_drop) // Departing should be handled manually + addtimer(CALLBACK(src, .proc/depart, holder), departureDelay) //Finish up the pod's duties after a certain amount of time + +/obj/structure/closet/supplypod/proc/depart(atom/movable/holder) + if (leavingSound) + playsound(get_turf(holder), leavingSound, soundVolume, FALSE, FALSE) + if (reversing) //If we're reversing, we call the close proc. This sends the pod back up to centcom + close(holder) + else if (bluespace) //If we're a bluespace pod, then delete ourselves (along with our holder, if a seperate holder exists) + if (!effectQuiet && style != STYLE_INVISIBLE && style != STYLE_SEETHROUGH) + do_sparks(5, TRUE, holder) //Create some sparks right before closing + qdel(src) //Delete ourselves and the holder + if (holder != src) + qdel(holder) + +/obj/structure/closet/supplypod/centcompod/close(atom/movable/holder) //Closes the supplypod and sends it back to centcom. Should only ever be called if the "reversing" variable is true + handleReturningClose(holder, TRUE) + +/obj/structure/closet/supplypod/extractionpod/close(atom/movable/holder) //handles closing, and returns pod - deletes itself when returned + . = ..() + return + +/obj/structure/closet/supplypod/extractionpod/proc/send_up(atom/movable/holder) + if (!holder) + holder = src + + if (leavingSound) + playsound(get_turf(holder), leavingSound, soundVolume, FALSE, FALSE) + + handleReturningClose(holder, FALSE) + +/obj/structure/closet/supplypod/proc/setOpened() //Proc exists here, as well as in any atom that can assume the role of a "holder" of a supplypod. Check the open() proc for more details + update_icon() + +/obj/structure/closet/supplypod/proc/setClosed() //Ditto + update_icon() + +/obj/structure/closet/supplypod/Destroy() + open_pod(holder = src, broken = TRUE) //Lets dump our contents by opening up + . = ..() + +//------------------------------------FALLING SUPPLY POD-------------------------------------// +/obj/effect/DPfall //Falling pod + name = "" + icon = 'icons/obj/supplypods.dmi' + pixel_x = -16 + pixel_y = -5 + pixel_z = 200 + desc = "Get out of the way!" + layer = FLY_LAYER//that wasnt flying, that was falling with style! + icon_state = "" + +/obj/effect/DPfall/Initialize(dropLocation, obj/structure/closet/supplypod/pod) + if (pod.style == STYLE_SEETHROUGH) + pixel_x = -16 + pixel_y = 0 + for (var/atom/movable/O in pod.contents) + var/icon/I = getFlatIcon(O) //im so sorry + add_overlay(I) + else if (pod.style != STYLE_INVISIBLE) //Check to ensure the pod isn't invisible + icon_state = "[pod.icon_state]_falling" + name = pod.name + . = ..() + +//------------------------------------TEMPORARY_VISUAL-------------------------------------// +/obj/effect/DPtarget //This is the object that forceMoves the supplypod to it's location + name = "Landing Zone Indicator" + desc = "A holographic projection designating the landing zone of something. It's probably best to stand back." + icon = 'icons/mob/actions/actions_items.dmi' + icon_state = "sniper_zoom" + layer = PROJECTILE_HIT_THRESHHOLD_LAYER + light_range = 2 + var/obj/effect/temp_visual/fallingPod //Temporary "falling pod" that we animate + var/obj/structure/closet/supplypod/pod //The supplyPod that will be landing ontop of this target + +/obj/effect/ex_act() + return + +/obj/effect/DPtarget/Initialize(mapload, podParam, single_order = null) + . = ..() + if (ispath(podParam)) //We can pass either a path for a pod (as expressconsoles do), or a reference to an instantiated pod (as the centcom_podlauncher does) + podParam = new podParam() //If its just a path, instantiate it + pod = podParam + if (single_order) + if (istype(single_order, /datum/supply_order)) + var/datum/supply_order/SO = single_order + SO.generate(pod) + else if (istype(single_order, /atom/movable)) + var/atom/movable/O = single_order + O.forceMove(pod) + for (var/mob/living/M in pod) //If there are any mobs in the supplypod, we want to forceMove them into the target. This is so that they can see where they are about to land, AND so that they don't get sent to the nullspace error room (as the pod is currently in nullspace) + M.forceMove(src) + if(pod.effectStun) //If effectStun is true, stun any mobs caught on this target until the pod gets a chance to hit them + for (var/mob/living/M in get_turf(src)) + M.Stun(pod.landingDelay+10, ignore_canstun = TRUE)//you aint goin nowhere, kid. + if (pod.effectStealth) //If effectStealth is true we want to be invisible + icon_state = "" + if (pod.fallDuration == initial(pod.fallDuration) && pod.landingDelay + pod.fallDuration < pod.fallingSoundLength) + 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 + if (soundStartTime < 0) + soundStartTime = 1 + if (!pod.effectQuiet) + addtimer(CALLBACK(src, .proc/playFallingSound), soundStartTime) + addtimer(CALLBACK(src, .proc/beginLaunch, pod.effectCircle), pod.landingDelay) + +/obj/effect/DPtarget/proc/playFallingSound() + playsound(src, pod.fallingSound, pod.soundVolume, TRUE, 6) + +/obj/effect/DPtarget/proc/beginLaunch(effectCircle) //Begin the animation for the pod falling. The effectCircle param determines whether the pod gets to come in from any descent angle + fallingPod = new /obj/effect/DPfall(drop_location(), pod) + var/matrix/M = matrix(fallingPod.transform) //Create a new matrix that we can rotate + var/angle = effectCircle ? rand(0,360) : rand(70,110) //The angle that we can come in from + fallingPod.pixel_x = cos(angle)*400 //Use some ADVANCED MATHEMATICS to set the animated pod's position to somewhere on the edge of a circle with the center being the target + fallingPod.pixel_z = sin(angle)*400 + var/rotation = Get_Pixel_Angle(fallingPod.pixel_z, fallingPod.pixel_x) //CUSTOM HOMEBREWED proc that is just arctan with extra steps + M.Turn(rotation) //Turn our matrix accordingly + fallingPod.transform = M //Transform the animated pod according to the matrix + M = matrix(pod.transform) //Make another matrix based on the pod + M.Turn(rotation) //Turn the matrix + pod.transform = M //Turn the actual pod (Won't be visible until endLaunch() proc tho) + animate(fallingPod, pixel_z = 0, pixel_x = -16, time = pod.fallDuration, , easing = LINEAR_EASING) //Make the pod fall! At an angle! + addtimer(CALLBACK(src, .proc/endLaunch), pod.fallDuration, TIMER_CLIENT_TIME) //Go onto the last step after a very short falling animation + +/obj/effect/DPtarget/proc/endLaunch() + pod.update_icon() + pod.forceMove(drop_location()) //The fallingPod animation is over, now's a good time to forceMove the actual pod into position + QDEL_NULL(fallingPod) //Delete the falling pod effect, because at this point its animation is over. We dont use temp_visual because we want to manually delete it as soon as the pod appears + for (var/mob/living/M in src) //Remember earlier (initialization) when we moved mobs into the DPTarget so they wouldnt get lost in nullspace? Time to get them out + M.forceMove(pod) + pod.preOpen() //Begin supplypod open procedures. Here effects like explosions, damage, and other dangerous (and potentially admin-caused, if the centcom_podlauncher datum was used) memes will take place + qdel(src) //The target's purpose is complete. It can rest easy now + +//------------------------------------UPGRADES-------------------------------------// +/obj/item/disk/cargo/bluespace_pod //Disk that can be inserted into the Express Console to allow for Advanced Bluespace Pods + name = "Bluespace Drop Pod Upgrade" + desc = "This disk provides a firmware update to the Express Supply Console, granting the use of Nanotrasen's Bluespace Drop Pods to the supply department." + icon = 'icons/obj/module.dmi' + icon_state = "cargodisk" + item_state = "card-id" + w_class = WEIGHT_CLASS_SMALL diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm index c07368b81912..fb22517777d1 100644 --- a/code/modules/client/client_defines.dm +++ b/code/modules/client/client_defines.dm @@ -1,166 +1,166 @@ - -/client - ////////////////////// - //BLACK MAGIC THINGS// - ////////////////////// - parent_type = /datum - //////////////// - //ADMIN THINGS// - //////////////// - ///Contains admin info. Null if client is not an admin. - var/datum/admins/holder = null - ///Needs to implement InterceptClickOn(user,params,atom) proc - var/datum/click_intercept = null - ///Used for admin AI interaction - var/AI_Interact = FALSE - - ///Used to cache this client's bans to save on DB queries - var/ban_cache = null - ///Contains the last message sent by this client - used to protect against copy-paste spamming. - var/last_message = "" - ///contins a number of how many times a message identical to last_message was sent. - var/last_message_count = 0 - ///How many messages sent in the last 10 seconds - var/total_message_count = 0 - ///Next tick to reset the total message counter - var/total_count_reset = 0 - ///Internal counter for clients sending external (IRC/Discord) relay messages via ahelp to prevent spamming. Set to a number every time an admin reply is sent, decremented for every client send. - var/externalreplyamount = 0 - var/ircreplyamount = 0 - var/cryo_warned = -3000//when was the last time we warned them about not cryoing without an ahelp, set to -5 minutes so that rounstart cryo still warns - - ///////// - //OTHER// - ///////// - ///Player preferences datum for the client - var/datum/preferences/prefs = null - ///last turn of the controlled mob, I think this is only used by mechs? - var/last_turn = 0 - ///Move delay of controlled mob, related to input handling - var/move_delay = 0 - ///Current area of the controlled mob - var/area = null - - /////////////// - //SOUND STUFF// - /////////////// - ///Currently playing ambience sound - var/ambience_playing = null - ///Whether an ambience sound has been played and one shouldn't be played again, unset by a callback - var/played = FALSE - //////////// - //SECURITY// - //////////// - // comment out the line below when debugging locally to enable the options & messages menu - control_freak = 1 - - //////////////////////////////////// - //things that require the database// - //////////////////////////////////// - ///Used to determine how old the account is - in days. - var/player_age = -1 - ///Date that this account was first seen in the server - var/player_join_date = null - ///So admins know why it isn't working - Used to determine what other accounts previously logged in from this ip - var/related_accounts_ip = "Requires database" - ///So admins know why it isn't working - Used to determine what other accounts previously logged in from this computer id - var/related_accounts_cid = "Requires database" - ///Date of byond account creation in ISO 8601 format - var/account_join_date = null - ///Age of byond account in days - var/account_age = -1 - - preload_rsc = PRELOAD_RSC - - var/obj/screen/click_catcher/void - - ///used to make a special mouse cursor, this one for mouse up icon - var/mouse_up_icon = null - ///used to make a special mouse cursor, this one for mouse up icon - var/mouse_down_icon = null - - ///Used for ip intel checking to identify evaders, disabled because of issues with traffic - var/ip_intel = "Disabled" - - ///datum that controls the displaying and hiding of tooltips - var/datum/tooltip/tooltips - - ///Last ping of the client - var/lastping = 0 - ///Average ping of the client - var/avgping = 0 - ///world.time they connected - var/connection_time - ///world.realtime they connected - var/connection_realtime - ///world.timeofday they connected - var/connection_timeofday - - ///If the client is currently in player preferences - var/inprefs = FALSE - ///Used for limiting the rate of topic sends by the client to avoid abuse - var/list/topiclimiter - ///Used for limiting the rate of clicks sends by the client to avoid abuse - var/list/clicklimiter - - ///goonchat chatoutput of the client - var/datum/chatOutput/chatOutput - - ///lazy list of all credit object bound to this client - var/list/credits - - ///these persist between logins/logouts during the same round. - var/datum/player_details/player_details - - ///Should only be a key-value list of north/south/east/west = obj/screen. - var/list/char_render_holders - - ///Amount of keydowns in the last keysend checking interval - var/client_keysend_amount = 0 - ///World tick time where client_keysend_amount will reset - var/next_keysend_reset = 0 - ///World tick time where keysend_tripped will reset back to false - var/next_keysend_trip_reset = 0 - ///When set to true, user will be autokicked if they trip the keysends in a second limit again - var/keysend_tripped = FALSE - ///custom movement keys for this client - var/list/movement_keys = list() - - ///Autoclick list of two elements, first being the clicked thing, second being the parameters. - var/list/atom/selected_target[2] - ///Autoclick variable referencing the associated item. - var/obj/item/active_mousedown_item = null - ///Used in MouseDrag to preserve the original mouse click parameters - var/mouseParams = "" - ///Used in MouseDrag to preserve the last mouse-entered location. - var/mouseLocation = null - ///Used in MouseDrag to preserve the last mouse-entered object. - var/mouseObject = null - //Middle-mouse-button click dragtime control for aimbot exploit detection. - var/middragtime = 0 - //Middle-mouse-button clicked object control for aimbot exploit detection. - var/atom/middragatom - - /// Messages currently seen by this client - var/list/seen_messages - - var/datum/viewData/view_size - - var/list/parallax_layers - var/list/parallax_layers_cached - var/atom/movable/movingmob - var/turf/previous_turf - ///world.time of when we can state animate()ing parallax again - var/dont_animate_parallax - ///world.time of last parallax update - var/last_parallax_shift - ///ds between parallax updates - var/parallax_throttle = 0 - var/parallax_movedir = 0 - var/parallax_layers_max = 4 - var/parallax_animate_timer - ///A lazy list of atoms we've examined in the last EXAMINE_MORE_TIME (default 1.5) seconds, so that we will call [atom/proc/examine_more()] instead of [atom/proc/examine()] on them when examining - var/list/recent_examines - - /// rate limiting for the crew manifest - var/crew_manifest_delay + +/client + ////////////////////// + //BLACK MAGIC THINGS// + ////////////////////// + parent_type = /datum + //////////////// + //ADMIN THINGS// + //////////////// + ///Contains admin info. Null if client is not an admin. + var/datum/admins/holder = null + ///Needs to implement InterceptClickOn(user,params,atom) proc + var/datum/click_intercept = null + ///Used for admin AI interaction + var/AI_Interact = FALSE + + ///Used to cache this client's bans to save on DB queries + var/ban_cache = null + ///Contains the last message sent by this client - used to protect against copy-paste spamming. + var/last_message = "" + ///contins a number of how many times a message identical to last_message was sent. + var/last_message_count = 0 + ///How many messages sent in the last 10 seconds + var/total_message_count = 0 + ///Next tick to reset the total message counter + var/total_count_reset = 0 + ///Internal counter for clients sending external (IRC/Discord) relay messages via ahelp to prevent spamming. Set to a number every time an admin reply is sent, decremented for every client send. + var/externalreplyamount = 0 + var/ircreplyamount = 0 + var/cryo_warned = -3000//when was the last time we warned them about not cryoing without an ahelp, set to -5 minutes so that rounstart cryo still warns + + ///////// + //OTHER// + ///////// + ///Player preferences datum for the client + var/datum/preferences/prefs = null + ///last turn of the controlled mob, I think this is only used by mechs? + var/last_turn = 0 + ///Move delay of controlled mob, related to input handling + var/move_delay = 0 + ///Current area of the controlled mob + var/area = null + + /////////////// + //SOUND STUFF// + /////////////// + ///Currently playing ambience sound + var/ambience_playing = null + ///Whether an ambience sound has been played and one shouldn't be played again, unset by a callback + var/played = FALSE + //////////// + //SECURITY// + //////////// + // comment out the line below when debugging locally to enable the options & messages menu + control_freak = 1 + + //////////////////////////////////// + //things that require the database// + //////////////////////////////////// + ///Used to determine how old the account is - in days. + var/player_age = -1 + ///Date that this account was first seen in the server + var/player_join_date = null + ///So admins know why it isn't working - Used to determine what other accounts previously logged in from this ip + var/related_accounts_ip = "Requires database" + ///So admins know why it isn't working - Used to determine what other accounts previously logged in from this computer id + var/related_accounts_cid = "Requires database" + ///Date of byond account creation in ISO 8601 format + var/account_join_date = null + ///Age of byond account in days + var/account_age = -1 + + preload_rsc = PRELOAD_RSC + + var/obj/screen/click_catcher/void + + ///used to make a special mouse cursor, this one for mouse up icon + var/mouse_up_icon = null + ///used to make a special mouse cursor, this one for mouse up icon + var/mouse_down_icon = null + + ///Used for ip intel checking to identify evaders, disabled because of issues with traffic + var/ip_intel = "Disabled" + + ///datum that controls the displaying and hiding of tooltips + var/datum/tooltip/tooltips + + ///Last ping of the client + var/lastping = 0 + ///Average ping of the client + var/avgping = 0 + ///world.time they connected + var/connection_time + ///world.realtime they connected + var/connection_realtime + ///world.timeofday they connected + var/connection_timeofday + + ///If the client is currently in player preferences + var/inprefs = FALSE + ///Used for limiting the rate of topic sends by the client to avoid abuse + var/list/topiclimiter + ///Used for limiting the rate of clicks sends by the client to avoid abuse + var/list/clicklimiter + + ///goonchat chatoutput of the client + var/datum/chatOutput/chatOutput + + ///lazy list of all credit object bound to this client + var/list/credits + + ///these persist between logins/logouts during the same round. + var/datum/player_details/player_details + + ///Should only be a key-value list of north/south/east/west = obj/screen. + var/list/char_render_holders + + ///Amount of keydowns in the last keysend checking interval + var/client_keysend_amount = 0 + ///World tick time where client_keysend_amount will reset + var/next_keysend_reset = 0 + ///World tick time where keysend_tripped will reset back to false + var/next_keysend_trip_reset = 0 + ///When set to true, user will be autokicked if they trip the keysends in a second limit again + var/keysend_tripped = FALSE + ///custom movement keys for this client + var/list/movement_keys = list() + + ///Autoclick list of two elements, first being the clicked thing, second being the parameters. + var/list/atom/selected_target[2] + ///Autoclick variable referencing the associated item. + var/obj/item/active_mousedown_item = null + ///Used in MouseDrag to preserve the original mouse click parameters + var/mouseParams = "" + ///Used in MouseDrag to preserve the last mouse-entered location. + var/mouseLocation = null + ///Used in MouseDrag to preserve the last mouse-entered object. + var/mouseObject = null + //Middle-mouse-button click dragtime control for aimbot exploit detection. + var/middragtime = 0 + //Middle-mouse-button clicked object control for aimbot exploit detection. + var/atom/middragatom + + /// Messages currently seen by this client + var/list/seen_messages + + var/datum/viewData/view_size + + var/list/parallax_layers + var/list/parallax_layers_cached + var/atom/movable/movingmob + var/turf/previous_turf + ///world.time of when we can state animate()ing parallax again + var/dont_animate_parallax + ///world.time of last parallax update + var/last_parallax_shift + ///ds between parallax updates + var/parallax_throttle = 0 + var/parallax_movedir = 0 + var/parallax_layers_max = 4 + var/parallax_animate_timer + ///A lazy list of atoms we've examined in the last EXAMINE_MORE_TIME (default 1.5) seconds, so that we will call [atom/proc/examine_more()] instead of [atom/proc/examine()] on them when examining + var/list/recent_examines + + /// rate limiting for the crew manifest + var/crew_manifest_delay diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index 9bd54ea9ffc4..f3f1e527a22a 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -43,6 +43,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( if (!asset_cache_job) return + // Rate limiting var/mtl = CONFIG_GET(number/minute_topic_limit) if (!holder && mtl) var/minute = round(world.time, 600) @@ -116,6 +117,10 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( keyUp(keycode) return + // Tgui Topic middleware + if(!tgui_Topic(href_list)) + return + // Admin PM if(href_list["priv_msg"]) cmd_admin_pm(href_list["priv_msg"],null) @@ -528,8 +533,10 @@ GLOBAL_LIST_EMPTY(external_rsc_urls) 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]'") + var/datum/DBQuery/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 @@ -537,7 +544,10 @@ GLOBAL_LIST_EMPTY(external_rsc_urls) 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/DBQuery/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 @@ -551,11 +561,11 @@ GLOBAL_LIST_EMPTY(external_rsc_urls) 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/DBQuery/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 @@ -576,9 +586,11 @@ GLOBAL_LIST_EMPTY(external_rsc_urls) 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"])") + account_join_date = findJoinDate() + 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 (: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) @@ -588,7 +600,10 @@ GLOBAL_LIST_EMPTY(external_rsc_urls) 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/DBQuery/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 @@ -599,11 +614,14 @@ GLOBAL_LIST_EMPTY(external_rsc_urls) 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/DBQuery/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 @@ -612,14 +630,20 @@ GLOBAL_LIST_EMPTY(external_rsc_urls) 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/DBQuery/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/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(: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) @@ -643,9 +667,11 @@ GLOBAL_LIST_EMPTY(external_rsc_urls) 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/DBQuery/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 @@ -661,8 +687,11 @@ GLOBAL_LIST_EMPTY(external_rsc_urls) 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/DBQuery/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 @@ -679,8 +708,10 @@ GLOBAL_LIST_EMPTY(external_rsc_urls) 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/DBQuery/query_cidcheck = SSdbcore.NewQuery( + "SELECT computerid FROM [format_table_name("player")] WHERE ckey = :ckey", + list("ckey" = ckey) + ) query_cidcheck.Execute() var/lastcid @@ -755,10 +786,11 @@ GLOBAL_LIST_EMPTY(external_rsc_urls) 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() OR expire_timestamp IS NULL)") + var/datum/DBQuery/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 @@ -767,7 +799,10 @@ GLOBAL_LIST_EMPTY(external_rsc_urls) 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() OR expire_timestamp IS NULL) 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 diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index bd7f34f9b718..65e4fbade322 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -36,7 +36,7 @@ GLOBAL_LIST_EMPTY(preferences_datums) var/list/key_bindings = list() var/tgui_fancy = TRUE - var/tgui_lock = TRUE + var/tgui_lock = FALSE var/windowflashing = TRUE var/crew_objectives = TRUE var/toggles = TOGGLES_DEFAULT @@ -52,6 +52,7 @@ GLOBAL_LIST_EMPTY(preferences_datums) var/preferred_map = null var/pda_style = MONO var/pda_color = "#808000" + var/show_credits = TRUE var/uses_glasses_colour = 0 @@ -741,8 +742,8 @@ GLOBAL_LIST_EMPTY(preferences_datums) dat += "\n" - -/obj/machinery/computer/bookmanagement/Initialize() - . = ..() - if(circuit) - circuit.name = "Book Inventory Management Console (Machine Board)" - circuit.build_path = /obj/machinery/computer/bookmanagement - -/obj/machinery/computer/bookmanagement/ui_interact(mob/user) - . = ..() - var/dat = "" // - switch(screenstate) - if(0) - // Main Menu - dat += "1. View General Inventory
                    " - dat += "2. View Checked Out Inventory
                    " - dat += "3. Check out a Book
                    " - dat += "4. Connect to External Archive
                    " - dat += "5. Upload New Title to Archive
                    " - dat += "6. Upload Scanned Title to Newscaster
                    " - dat += "7. Print Corporate Materials
                    " - if(obj_flags & EMAGGED) - dat += "8. Access the Forbidden Lore Vault
                    " - if(src.arcanecheckout) - print_forbidden_lore(user) - src.arcanecheckout = 0 - if(1) - // Inventory - dat += "

                    Inventory


                    " - for(var/obj/item/book/b in inventory) - dat += "[b.name] (Delete)
                    " - dat += "(Return to main menu)
                    " - if(2) - // Checked Out - dat += "

                    Checked Out Books


                    " - for(var/datum/borrowbook/b in checkouts) - var/timetaken = world.time - b.getdate - timetaken /= 600 - timetaken = round(timetaken) - var/timedue = b.duedate - world.time - timedue /= 600 - if(timedue <= 0) - timedue = "(OVERDUE) [timedue]" - else - timedue = round(timedue) - dat += "\"[b.bookname]\", Checked out to: [b.mobname]
                    --- Taken: [timetaken] minutes ago, Due: in [timedue] minutes
                    " - dat += "(Check In)

                    " - dat += "(Return to main menu)
                    " - if(3) - // Check Out a Book - dat += "

                    Check Out a Book


                    " - dat += "Book: [src.buffer_book] " - dat += "\[Edit\]
                    " - dat += "Recipient: [src.buffer_mob] " - dat += "\[Edit\]
                    " - dat += "Checkout Date : [world.time/600]
                    " - dat += "Due Date: [(world.time + checkoutperiod)/600]
                    " - dat += "(Checkout Period: [checkoutperiod] minutes) (+/-)" - dat += "(Commit Entry)
                    " - dat += "(Return to main menu)
                    " - if(4) - dat += "

                    External Archive

                    " - build_library_menu() - - if(!GLOB.cachedbooks) - dat += "ERROR: Unable to contact External Archive. Please contact your system administrator for assistance." - else - dat += "(Order book by SS13BN)

                    " - dat += "
                    " dat += "

                    General Settings

                    " dat += "UI Style: [UI_style]
                    " - dat += "tgui Monitors: [(tgui_lock) ? "Primary" : "All"]
                    " - dat += "tgui Style: [(tgui_fancy) ? "Fancy" : "No Frills"]
                    " + dat += "tgui Window Mode: [(tgui_fancy) ? "Fancy (default)" : "Compatible (slower)"]
                    " + dat += "tgui Window Placement: [(tgui_lock) ? "Primary monitor" : "Free (default)"]
                    " dat += "Show Runechat Chat Bubbles: [chat_on_map ? "Enabled" : "Disabled"]
                    " dat += "Runechat message char limit: [max_chat_length]
                    " dat += "See Runechat for non-mobs: [see_chat_non_mob ? "Enabled" : "Disabled"]
                    " @@ -2252,15 +2253,3 @@ GLOBAL_LIST_EMPTY(preferences_datums) return else custom_names[name_id] = sanitized_name - -//Used in savefile update 32, can be removed once that is no longer relevant. -/datum/preferences/proc/force_reset_keybindings() - var/choice = tgalert(parent.mob, "Your basic keybindings need to be reset, emotes will remain as before. Would you prefer 'hotkey' or 'classic' mode?", "Reset keybindings", "Hotkey", "Classic") - hotkeys = (choice != "Classic") - var/list/oldkeys = key_bindings - key_bindings = (hotkeys) ? deepCopyList(GLOB.hotkey_keybinding_list_by_key) : deepCopyList(GLOB.classic_keybinding_list_by_key) - - for(var/key in oldkeys) - if(!key_bindings[key]) - key_bindings[key] = oldkeys[key] - parent.update_movement_keys() diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm index ddbb22069c18..1d8eaedafd57 100644 --- a/code/modules/client/preferences_savefile.dm +++ b/code/modules/client/preferences_savefile.dm @@ -1,11 +1,11 @@ //This is the lowest supported version, anything below this is completely obsolete and the entire savefile will be wiped. -#define SAVEFILE_VERSION_MIN 18 +#define SAVEFILE_VERSION_MIN 32 //This is the current version, anything below this will attempt to update (if it's not obsolete) // You do not need to raise this if you are adding new values that have sane defaults. // Only raise this value when changing the meaning/format/name/layout of an existing value // where you would want the updater procs below to run -#define SAVEFILE_VERSION_MAX 34 +#define SAVEFILE_VERSION_MAX 36 /* SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Carn @@ -42,105 +42,36 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car //if your savefile is 3 months out of date, then 'tough shit'. /datum/preferences/proc/update_preferences(current_version, savefile/S) - if(current_version < 30) - if(clientfps == 0) - clientfps = 60 - - if(current_version < 31) - if(clientfps == 60) - clientfps = 0 - - if(current_version < 32) //If you remove this, remove force_reset_keybindings() too. - addtimer(CALLBACK(src, .proc/force_reset_keybindings), 30) //No mob available when this is run, timer allows user choice. - if(current_version < 33) toggles |= SOUND_ENDOFROUND if(current_version < 34) auto_fit_viewport = TRUE + if(current_version < 35) //makes old keybinds compatible with #52040, sets the new default + var/newkey = FALSE + for(var/list/key in key_bindings) + for(var/bind in key) + if(bind == "quick_equipbelt") + key -= "quick_equipbelt" + key |= "quick_equip_belt" + + if(bind == "bag_equip") + key -= "bag_equip" + key |= "quick_equip_bag" + + if(bind == "quick_equip_suit_storage") + newkey = TRUE + if(!newkey && !key_bindings["ShiftQ"]) + key_bindings["ShiftQ"] = list("quick_equip_suit_storage") + + if(current_version < 36) + if(key_bindings["ShiftQ"] == "quick_equip_suit_storage") + key_bindings["ShiftQ"] = list("quick_equip_suit_storage") + + /datum/preferences/proc/update_character(current_version, savefile/S) - if(current_version < 19) - pda_style = "mono" - if(current_version < 20) - pda_color = "#808000" - if((current_version < 21) && features["ethcolor"] && (features["ethcolor"] == "#9c3030")) - features["ethcolor"] = "9c3030" - if(current_version < 22) - job_preferences = list() //It loaded null from nonexistant savefile field. - var/job_civilian_high = 0 - var/job_civilian_med = 0 - var/job_civilian_low = 0 - - var/job_medsci_high = 0 - var/job_medsci_med = 0 - var/job_medsci_low = 0 - - var/job_engsec_high = 0 - var/job_engsec_med = 0 - var/job_engsec_low = 0 - - S["job_civilian_high"] >> job_civilian_high - S["job_civilian_med"] >> job_civilian_med - S["job_civilian_low"] >> job_civilian_low - S["job_medsci_high"] >> job_medsci_high - S["job_medsci_med"] >> job_medsci_med - S["job_medsci_low"] >> job_medsci_low - S["job_engsec_high"] >> job_engsec_high - S["job_engsec_med"] >> job_engsec_med - S["job_engsec_low"] >> job_engsec_low - - //Can't use SSjob here since this happens right away on login - for(var/job in subtypesof(/datum/job)) - var/datum/job/J = job - var/new_value - var/fval = initial(J.flag) - switch(initial(J.department_flag)) - if(CIVILIAN) - if(job_civilian_high & fval) - new_value = JP_HIGH - else if(job_civilian_med & fval) - new_value = JP_MEDIUM - else if(job_civilian_low & fval) - new_value = JP_LOW - if(MEDSCI) - if(job_medsci_high & fval) - new_value = JP_HIGH - else if(job_medsci_med & fval) - new_value = JP_MEDIUM - else if(job_medsci_low & fval) - new_value = JP_LOW - if(ENGSEC) - if(job_engsec_high & fval) - new_value = JP_HIGH - else if(job_engsec_med & fval) - new_value = JP_MEDIUM - else if(job_engsec_low & fval) - new_value = JP_LOW - if(new_value) - job_preferences[initial(J.title)] = new_value - if(current_version < 23) - if(all_quirks) - all_quirks -= "Physically Obstructive" - all_quirks -= "Neat" - all_quirks -= "NEET" - if(current_version < 24) - if (!(underwear in GLOB.underwear_list)) - underwear = "Nude" - if(current_version < 25) - randomise = list(RANDOM_UNDERWEAR = TRUE, RANDOM_UNDERWEAR_COLOR = TRUE, RANDOM_UNDERSHIRT = TRUE, RANDOM_SOCKS = TRUE, RANDOM_BACKPACK = TRUE, RANDOM_JUMPSUIT_STYLE = TRUE, RANDOM_EXOWEAR_STYLE = TRUE, RANDOM_HAIRSTYLE = TRUE, RANDOM_HAIR_COLOR = TRUE, RANDOM_FACIAL_HAIRSTYLE = TRUE, RANDOM_FACIAL_HAIR_COLOR = TRUE, RANDOM_SKIN_TONE = TRUE, RANDOM_EYE_COLOR = TRUE) - if(S["name_is_always_random"] == 1) - randomise[RANDOM_NAME] = TRUE - if(S["body_is_always_random"] == 1) - randomise[RANDOM_BODY] = TRUE - if(S["species_is_always_random"] == 1) - randomise[RANDOM_SPECIES] = TRUE - if(S["backbag"]) - S["backbag"] >> backpack - if(S["hair_style_name"]) - S["hair_style_name"] >> hairstyle - if(S["facial_style_name"]) - S["facial_style_name"] >> facial_hairstyle + return /datum/preferences/proc/load_path(ckey,filename="preferences.sav") if(!ckey) @@ -160,6 +91,10 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car var/needs_update = savefile_needs_update(S) if(needs_update == -2) //fatal, can't load any data + var/bacpath = "[path].updatebac" //todo: if the savefile version is higher then the server, check the backup, and give the player a prompt to load the backup + if (fexists(bacpath)) + fdel(bacpath) //only keep 1 version of backup + fcopy(S, bacpath) //byond helpfully lets you use a savefile for the first arg. return FALSE //general preferences @@ -202,6 +137,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car S["tip_delay"] >> tip_delay S["pda_style"] >> pda_style S["pda_color"] >> pda_color + S["show_credits"] >> show_credits // Custom hotkeys S["key_bindings"] >> key_bindings @@ -211,8 +147,14 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car //try to fix any outdated data if necessary if(needs_update >= 0) + var/bacpath = "[path].updatebac" //todo: if the savefile version is higher then the server, check the backup, and give the player a prompt to load the backup + if (fexists(bacpath)) + fdel(bacpath) //only keep 1 version of backup + fcopy(S, bacpath) //byond helpfully lets you use a savefile for the first arg. update_preferences(needs_update, S) //needs_update = savefile_version if we need an update (positive integer) + + //Sanitize asaycolor = sanitize_ooccolor(sanitize_hexcolor(asaycolor, 6, 1, initial(asaycolor))) ooccolor = sanitize_ooccolor(sanitize_hexcolor(ooccolor, 6, 1, initial(ooccolor))) @@ -240,10 +182,28 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car menuoptions = SANITIZE_LIST(menuoptions) be_special = SANITIZE_LIST(be_special) crew_objectives = sanitize_integer(crew_objectives, 0, 1, initial(crew_objectives)) + show_credits = sanitize_integer(show_credits, 0, 1, initial(show_credits)) pda_style = sanitize_inlist(pda_style, GLOB.pda_styles, initial(pda_style)) pda_color = sanitize_hexcolor(pda_color, 6, 1, initial(pda_color)) key_bindings = sanitize_keybindings(key_bindings) + if(needs_update >= 0) //save the updated version + var/old_default_slot = default_slot + var/old_max_save_slots = max_save_slots + + for (var/slot in S.dir) //but first, update all current character slots. + if (copytext(slot, 1, 10) != "character") + continue + var/slotnum = text2num(copytext(slot, 10)) + if (!slotnum) + continue + max_save_slots = max(max_save_slots, slotnum) //so we can still update byond member slots after they lose memeber status + default_slot = slotnum + if (load_character()) + save_character() + default_slot = old_default_slot + max_save_slots = old_max_save_slots + save_preferences() if(!purchased_gear) purchased_gear = list() @@ -301,6 +261,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car WRITE_FILE(S["pda_color"], pda_color) WRITE_FILE(S["purchased_gear"], purchased_gear) WRITE_FILE(S["equipped_gear"], equipped_gear) + WRITE_FILE(S["show_credits"], show_credits) WRITE_FILE(S["key_bindings"], key_bindings) return TRUE @@ -333,12 +294,6 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car if(newtype) pref_species = new newtype - if(!S["features["mcolor"]"] || S["features["mcolor"]"] == "#000") - WRITE_FILE(S["features["mcolor"]"] , "#FFF") - - if(!S["feature_ethcolor"] || S["feature_ethcolor"] == "#000") - WRITE_FILE(S["feature_ethcolor"] , "9c3030") - //Character S["real_name"] >> real_name S["gender"] >> gender @@ -383,7 +338,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car if(SSjob) for(var/datum/job/job in sortList(SSjob.occupations, /proc/cmp_job_display_asc)) if(alt_titles_preferences[job.title]) - if(!(alt_titles_preferences[job.title] in job.alt_titles)) + if(!(alt_titles_preferences[job.title] in job.alt_titles) || (alt_titles_preferences[job.title] == job.senior_title)) alt_titles_preferences.Remove(job.title) if(!CONFIG_GET(flag/join_with_mutant_humans)) @@ -413,12 +368,12 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car S["feature_flavor_text"] >> features["flavor_text"] //try to fix any outdated data if necessary + //preference updating will handle saving the updated data for us. if(needs_update >= 0) update_character(needs_update, S) //needs_update == savefile_version if we need an update (positive integer) //Sanitize - - real_name = reject_bad_name(real_name, pref_species.allow_numbers_in_name) + real_name = reject_bad_name(real_name) gender = sanitize_gender(gender) if(!real_name) real_name = random_unique_name(gender) diff --git a/code/modules/client/preferences_toggles.dm b/code/modules/client/preferences_toggles.dm index fe205bba83a1..743fbc3d56f2 100644 --- a/code/modules/client/preferences_toggles.dm +++ b/code/modules/client/preferences_toggles.dm @@ -1,508 +1,518 @@ -//this works as is to create a single checked item, but has no back end code for toggleing the check yet -#define TOGGLE_CHECKBOX(PARENT, CHILD) PARENT/CHILD/abstract = TRUE;PARENT/CHILD/checkbox = CHECKBOX_TOGGLE;PARENT/CHILD/verb/CHILD - -//Example usage TOGGLE_CHECKBOX(datum/verbs/menu/Settings/Ghost/chatterbox, toggle_ghost_ears)() - -//override because we don't want to save preferences twice. -/datum/verbs/menu/Settings/Set_checked(client/C, verbpath) - if (checkbox == CHECKBOX_GROUP) - C.prefs.menuoptions[type] = verbpath - else if (checkbox == CHECKBOX_TOGGLE) - var/checked = Get_checked(C) - C.prefs.menuoptions[type] = !checked - winset(C, "[verbpath]", "is-checked = [!checked]") - -/datum/verbs/menu/Settings/verb/setup_character() - set name = "Game Preferences" - set category = "Preferences" - set desc = "Open Game Preferences Window" - usr.client.prefs.current_tab = 1 - usr.client.prefs.ShowChoices(usr) - -//toggles -/datum/verbs/menu/Settings/Ghost/chatterbox - name = "Chat Box Spam" - -TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Ghost/chatterbox, toggle_ghost_ears)() - set name = "Show/Hide GhostEars" - set category = "Preferences" - set desc = "See All Speech" - usr.client.prefs.chat_toggles ^= CHAT_GHOSTEARS - to_chat(usr, "As a ghost, you will now [(usr.client.prefs.chat_toggles & CHAT_GHOSTEARS) ? "see all speech in the world" : "only see speech from nearby mobs"].") - usr.client.prefs.save_preferences() - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost Ears", "[usr.client.prefs.chat_toggles & CHAT_GHOSTEARS ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! -/datum/verbs/menu/Settings/Ghost/chatterbox/toggle_ghost_ears/Get_checked(client/C) - return C.prefs.chat_toggles & CHAT_GHOSTEARS - -TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Ghost/chatterbox, toggle_ghost_sight)() - set name = "Show/Hide GhostSight" - set category = "Preferences" - set desc = "See All Emotes" - usr.client.prefs.chat_toggles ^= CHAT_GHOSTSIGHT - to_chat(usr, "As a ghost, you will now [(usr.client.prefs.chat_toggles & CHAT_GHOSTSIGHT) ? "see all emotes in the world" : "only see emotes from nearby mobs"].") - usr.client.prefs.save_preferences() - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost Sight", "[usr.client.prefs.chat_toggles & CHAT_GHOSTSIGHT ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! -/datum/verbs/menu/Settings/Ghost/chatterbox/toggle_ghost_sight/Get_checked(client/C) - return C.prefs.chat_toggles & CHAT_GHOSTSIGHT - -TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Ghost/chatterbox, toggle_ghost_whispers)() - set name = "Show/Hide GhostWhispers" - set category = "Preferences" - set desc = "See All Whispers" - usr.client.prefs.chat_toggles ^= CHAT_GHOSTWHISPER - to_chat(usr, "As a ghost, you will now [(usr.client.prefs.chat_toggles & CHAT_GHOSTWHISPER) ? "see all whispers in the world" : "only see whispers from nearby mobs"].") - usr.client.prefs.save_preferences() - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost Whispers", "[usr.client.prefs.chat_toggles & CHAT_GHOSTWHISPER ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! -/datum/verbs/menu/Settings/Ghost/chatterbox/toggle_ghost_whispers/Get_checked(client/C) - return C.prefs.chat_toggles & CHAT_GHOSTWHISPER - -TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Ghost/chatterbox, toggle_ghost_radio)() - set name = "Show/Hide GhostRadio" - set category = "Preferences" - set desc = "See All Radio Chatter" - usr.client.prefs.chat_toggles ^= CHAT_GHOSTRADIO - to_chat(usr, "As a ghost, you will now [(usr.client.prefs.chat_toggles & CHAT_GHOSTRADIO) ? "see radio chatter" : "not see radio chatter"].") - usr.client.prefs.save_preferences() - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost Radio", "[usr.client.prefs.chat_toggles & CHAT_GHOSTRADIO ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! //social experiment, increase the generation whenever you copypaste this shamelessly GENERATION 1 -/datum/verbs/menu/Settings/Ghost/chatterbox/toggle_ghost_radio/Get_checked(client/C) - return C.prefs.chat_toggles & CHAT_GHOSTRADIO - -TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Ghost/chatterbox, toggle_ghost_pda)() - set name = "Show/Hide GhostPDA" - set category = "Preferences" - set desc = "See All PDA Messages" - usr.client.prefs.chat_toggles ^= CHAT_GHOSTPDA - to_chat(usr, "As a ghost, you will now [(usr.client.prefs.chat_toggles & CHAT_GHOSTPDA) ? "see all pda messages in the world" : "only see pda messages from nearby mobs"].") - usr.client.prefs.save_preferences() - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost PDA", "[usr.client.prefs.chat_toggles & CHAT_GHOSTPDA ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! -/datum/verbs/menu/Settings/Ghost/chatterbox/toggle_ghost_pda/Get_checked(client/C) - return C.prefs.chat_toggles & CHAT_GHOSTPDA - -TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Ghost/chatterbox, toggle_ghost_laws)() - set name = "Show/Hide GhostLaws" - set category = "Preferences" - set desc = "See All Law Changes" - usr.client.prefs.chat_toggles ^= CHAT_GHOSTLAWS - to_chat(usr, "As a ghost, you will now [(usr.client.prefs.chat_toggles & CHAT_GHOSTLAWS) ? "be notified of all law chanes" : "no longer be notified of law changes"].") - usr.client.prefs.save_preferences() - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost Laws", "[usr.client.prefs.chat_toggles & CHAT_GHOSTLAWS ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! -/datum/verbs/menu/Settings/Ghost/chatterbox/toggle_ghost_laws/Get_checked(client/C) - return C.prefs.chat_toggles & CHAT_GHOSTLAWS - -/datum/verbs/menu/Settings/Ghost/chatterbox/Events - name = "Events" - -//please be aware that the following two verbs have inverted stat output, so that "Toggle Deathrattle|1" still means you activated it -TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Ghost/chatterbox/Events, toggle_deathrattle)() - set name = "Toggle Deathrattle" - set category = "Preferences" - set desc = "Death" - usr.client.prefs.toggles ^= DISABLE_DEATHRATTLE - usr.client.prefs.save_preferences() - to_chat(usr, "You will [(usr.client.prefs.toggles & DISABLE_DEATHRATTLE) ? "no longer" : "now"] get messages when a sentient mob dies.") - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Deathrattle", "[!(usr.client.prefs.toggles & DISABLE_DEATHRATTLE) ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, maybe you should spend some time reading the comments. -/datum/verbs/menu/Settings/Ghost/chatterbox/Events/toggle_deathrattle/Get_checked(client/C) - return !(C.prefs.toggles & DISABLE_DEATHRATTLE) - -TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Ghost/chatterbox/Events, toggle_arrivalrattle)() - set name = "Toggle Arrivalrattle" - set category = "Preferences" - set desc = "New Player Arrival" - usr.client.prefs.toggles ^= DISABLE_ARRIVALRATTLE - to_chat(usr, "You will [(usr.client.prefs.toggles & DISABLE_ARRIVALRATTLE) ? "no longer" : "now"] get messages when someone joins the station.") - usr.client.prefs.save_preferences() - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Arrivalrattle", "[!(usr.client.prefs.toggles & DISABLE_ARRIVALRATTLE) ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, maybe you should rethink where your life went so wrong. -/datum/verbs/menu/Settings/Ghost/chatterbox/Events/toggle_arrivalrattle/Get_checked(client/C) - return !(C.prefs.toggles & DISABLE_ARRIVALRATTLE) - -TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Ghost, togglemidroundantag)() - set name = "Toggle Midround Antagonist" - set category = "Preferences" - set desc = "Midround Antagonist" - usr.client.prefs.toggles ^= MIDROUND_ANTAG - usr.client.prefs.save_preferences() - to_chat(usr, "You will [(usr.client.prefs.toggles & MIDROUND_ANTAG) ? "now" : "no longer"] be considered for midround antagonist positions.") - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Midround Antag", "[usr.client.prefs.toggles & MIDROUND_ANTAG ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! -/datum/verbs/menu/Settings/Ghost/togglemidroundantag/Get_checked(client/C) - return C.prefs.toggles & MIDROUND_ANTAG - -TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Sound, toggletitlemusic)() - set name = "Hear/Silence Lobby Music" - set category = "Preferences" - set desc = "Hear Music In Lobby" - usr.client.prefs.toggles ^= SOUND_LOBBY - usr.client.prefs.save_preferences() - if(usr.client.prefs.toggles & SOUND_LOBBY) - to_chat(usr, "You will now hear music in the game lobby.") - if(isnewplayer(usr)) - usr.client.playtitlemusic() - else - to_chat(usr, "You will no longer hear music in the game lobby.") - usr.stop_sound_channel(CHANNEL_LOBBYMUSIC) - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Lobby Music", "[usr.client.prefs.toggles & SOUND_LOBBY ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! -/datum/verbs/menu/Settings/Sound/toggletitlemusic/Get_checked(client/C) - return C.prefs.toggles & SOUND_LOBBY - - -TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Sound, toggleendofroundsounds)() - set name = "Hear/Silence End of Round Sounds" - set category = "Preferences" - set desc = "Hear Round End Sounds" - usr.client.prefs.toggles ^= SOUND_ENDOFROUND - usr.client.prefs.save_preferences() - if(usr.client.prefs.toggles & SOUND_ENDOFROUND) - to_chat(usr, "You will now hear sounds played before the server restarts.") - else - to_chat(usr, "You will no longer hear sounds played before the server restarts.") - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle End of Round Sounds", "[usr.client.prefs.toggles & SOUND_ENDOFROUND ? "Enabled" : "Disabled"]")) -/datum/verbs/menu/Settings/Sound/toggleendofroundsounds/Get_checked(client/C) - return C.prefs.toggles & SOUND_ENDOFROUND - - -TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Sound, togglemidis)() - set name = "Hear/Silence Midis" - set category = "Preferences" - set desc = "Hear Admin Triggered Sounds (Midis)" - usr.client.prefs.toggles ^= SOUND_MIDI - usr.client.prefs.save_preferences() - if(usr.client.prefs.toggles & SOUND_MIDI) - to_chat(usr, "You will now hear any sounds uploaded by admins.") - else - to_chat(usr, "You will no longer hear sounds uploaded by admins") - usr.stop_sound_channel(CHANNEL_ADMIN) - var/client/C = usr.client - if(C && C.chatOutput && !C.chatOutput.broken && C.chatOutput.loaded) - C.chatOutput.stopMusic() - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Hearing Midis", "[usr.client.prefs.toggles & SOUND_MIDI ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! -/datum/verbs/menu/Settings/Sound/togglemidis/Get_checked(client/C) - return C.prefs.toggles & SOUND_MIDI - - -TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Sound, toggle_instruments)() - set name = "Hear/Silence Instruments" - set category = "Preferences" - set desc = "Hear In-game Instruments" - usr.client.prefs.toggles ^= SOUND_INSTRUMENTS - usr.client.prefs.save_preferences() - if(usr.client.prefs.toggles & SOUND_INSTRUMENTS) - to_chat(usr, "You will now hear people playing musical instruments.") - else - to_chat(usr, "You will no longer hear musical instruments.") - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Instruments", "[usr.client.prefs.toggles & SOUND_INSTRUMENTS ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! -/datum/verbs/menu/Settings/Sound/toggle_instruments/Get_checked(client/C) - return C.prefs.toggles & SOUND_INSTRUMENTS - - -TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Sound, Toggle_Soundscape)() - set name = "Hear/Silence Ambience" - set category = "Preferences" - set desc = "Hear Ambient Sound Effects" - usr.client.prefs.toggles ^= SOUND_AMBIENCE - usr.client.prefs.save_preferences() - if(usr.client.prefs.toggles & SOUND_AMBIENCE) - to_chat(usr, "You will now hear ambient sounds.") - else - to_chat(usr, "You will no longer hear ambient sounds.") - usr.stop_sound_channel(CHANNEL_AMBIENCE) - usr.stop_sound_channel(CHANNEL_BUZZ) - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ambience", "[usr.client.prefs.toggles & SOUND_AMBIENCE ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! -/datum/verbs/menu/Settings/Sound/Toggle_Soundscape/Get_checked(client/C) - return C.prefs.toggles & SOUND_AMBIENCE - - -TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Sound, toggle_ship_ambience)() - set name = "Hear/Silence Ship Ambience" - set category = "Preferences" - set desc = "Hear Ship Ambience Roar" - usr.client.prefs.toggles ^= SOUND_SHIP_AMBIENCE - usr.client.prefs.save_preferences() - if(usr.client.prefs.toggles & SOUND_SHIP_AMBIENCE) - to_chat(usr, "You will now hear ship ambience.") - else - to_chat(usr, "You will no longer hear ship ambience.") - usr.stop_sound_channel(CHANNEL_BUZZ) - usr.client.ambience_playing = 0 - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ship Ambience", "[usr.client.prefs.toggles & SOUND_SHIP_AMBIENCE ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, I bet you read this comment expecting to see the same thing :^) -/datum/verbs/menu/Settings/Sound/toggle_ship_ambience/Get_checked(client/C) - return C.prefs.toggles & SOUND_SHIP_AMBIENCE - - -TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Sound, toggle_announcement_sound)() - set name = "Hear/Silence Announcements" - set category = "Preferences" - set desc = "Hear Announcement Sound" - usr.client.prefs.toggles ^= SOUND_ANNOUNCEMENTS - to_chat(usr, "You will now [(usr.client.prefs.toggles & SOUND_ANNOUNCEMENTS) ? "hear announcement sounds" : "no longer hear announcements"].") - usr.client.prefs.save_preferences() - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Announcement Sound", "[usr.client.prefs.toggles & SOUND_ANNOUNCEMENTS ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! -/datum/verbs/menu/Settings/Sound/toggle_announcement_sound/Get_checked(client/C) - return C.prefs.toggles & SOUND_ANNOUNCEMENTS - -/* READD THIS WHEN YOU FIX RADIO CHATTER -TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Sound, toggle_radio_sound)() - set name = "Hear/Silence Radio Chatter" - set category = "Preferences" - set desc = "Hear Radio Sound" - usr.client.prefs.toggles ^= SOUND_RADIO - to_chat(usr, "You will now [(usr.client.prefs.toggles & SOUND_RADIO) ? "hear radio chatter sounds" : "no longer hear radio chatter sounds"].") - usr.client.prefs.save_preferences() - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Radio Sound", "[usr.client.prefs.toggles & SOUND_RADIO ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc, just like EVERY other comment says. - -/datum/verbs/menu/Settings/Sound/toggle_radio_sound/Get_checked(client/C) - return C.prefs.toggles & SOUND_RADIO -*/ - -/datum/verbs/menu/Settings/Sound/verb/stop_client_sounds() - set name = "Stop Sounds" - set category = "Preferences" - set desc = "Stop Current Sounds" - SEND_SOUND(usr, sound(null)) - var/client/C = usr.client - if(C && C.chatOutput && !C.chatOutput.broken && C.chatOutput.loaded) - C.chatOutput.stopMusic() - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Stop Self Sounds")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - -TOGGLE_CHECKBOX(/datum/verbs/menu/Settings, listen_ooc)() - set name = "Show/Hide OOC" - set category = "Preferences" - set desc = "Show OOC Chat" - usr.client.prefs.chat_toggles ^= CHAT_OOC - usr.client.prefs.save_preferences() - to_chat(usr, "You will [(usr.client.prefs.chat_toggles & CHAT_OOC) ? "now" : "no longer"] see messages on the OOC channel.") - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Seeing OOC", "[usr.client.prefs.chat_toggles & CHAT_OOC ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! -/datum/verbs/menu/Settings/listen_ooc/Get_checked(client/C) - return C.prefs.chat_toggles & CHAT_OOC - -//Begin Wasp Edit -TOGGLE_CHECKBOX(datum/verbs/menu/settings, listen_looc)() - set name = "Show/Hide LOOC" - set category = "Preferences" - set desc = "Toggles seeing Local Out Of Character chat" - usr.client.prefs.chat_toggles ^= CHAT_LOOC - usr.client.prefs.save_preferences() - to_chat(usr, "You will [(usr.client.prefs.chat_toggles & CHAT_LOOC) ? "now" : "no longer"] see messages on the LOOC channel.") - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Seeing LOOC", "[usr.client.prefs.chat_toggles & CHAT_LOOC ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! -/datum/verbs/menu/Settings/listen_looc/Get_checked(client/C) - return C.prefs.chat_toggles & CHAT_LOOC -//End Wasp Edit - -TOGGLE_CHECKBOX(/datum/verbs/menu/Settings, listen_bank_card)() - set name = "Show/Hide Income Updates" - set category = "Preferences" - set desc = "Show or hide updates to your income" - usr.client.prefs.chat_toggles ^= CHAT_BANKCARD - usr.client.prefs.save_preferences() - to_chat(usr, "You will [(usr.client.prefs.chat_toggles & CHAT_BANKCARD) ? "now" : "no longer"] be notified when you get paid.") - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Income Notifications", "[(usr.client.prefs.chat_toggles & CHAT_BANKCARD) ? "Enabled" : "Disabled"]")) -/datum/verbs/menu/Settings/listen_bank_card/Get_checked(client/C) - return C.prefs.chat_toggles & CHAT_BANKCARD - - -GLOBAL_LIST_INIT(ghost_forms, sortList(list("ghost","ghostking","ghostian2","skeleghost","ghost_red","ghost_black", \ - "ghost_blue","ghost_yellow","ghost_green","ghost_pink", \ - "ghost_cyan","ghost_dblue","ghost_dred","ghost_dgreen", \ - "ghost_dcyan","ghost_grey","ghost_dyellow","ghost_dpink", "ghost_purpleswirl","ghost_funkypurp","ghost_pinksherbert","ghost_blazeit",\ - "ghost_mellow","ghost_rainbow","ghost_camo","ghost_fire", "catghost"))) -/client/proc/pick_form() - if(!is_content_unlocked()) - alert("This setting is for accounts with BYOND premium only.") - return - var/new_form = input(src, "Thanks for supporting BYOND - Choose your ghostly form:","Thanks for supporting BYOND",null) as null|anything in GLOB.ghost_forms - if(new_form) - prefs.ghost_form = new_form - prefs.save_preferences() - if(isobserver(mob)) - var/mob/dead/observer/O = mob - O.update_icon(new_form) - -GLOBAL_LIST_INIT(ghost_orbits, list(GHOST_ORBIT_CIRCLE,GHOST_ORBIT_TRIANGLE,GHOST_ORBIT_SQUARE,GHOST_ORBIT_HEXAGON,GHOST_ORBIT_PENTAGON)) - -/client/proc/pick_ghost_orbit() - if(!is_content_unlocked()) - alert("This setting is for accounts with BYOND premium only.") - return - var/new_orbit = input(src, "Thanks for supporting BYOND - Choose your ghostly orbit:","Thanks for supporting BYOND",null) as null|anything in GLOB.ghost_orbits - if(new_orbit) - prefs.ghost_orbit = new_orbit - prefs.save_preferences() - if(isobserver(mob)) - var/mob/dead/observer/O = mob - O.ghost_orbit = new_orbit - -/client/proc/pick_ghost_accs() - var/new_ghost_accs = alert("Do you want your ghost to show full accessories where possible, hide accessories but still use the directional sprites where possible, or also ignore the directions and stick to the default sprites?",,"full accessories", "only directional sprites", "default sprites") - if(new_ghost_accs) - switch(new_ghost_accs) - if("full accessories") - prefs.ghost_accs = GHOST_ACCS_FULL - if("only directional sprites") - prefs.ghost_accs = GHOST_ACCS_DIR - if("default sprites") - prefs.ghost_accs = GHOST_ACCS_NONE - prefs.save_preferences() - if(isobserver(mob)) - var/mob/dead/observer/O = mob - O.update_icon() - -/client/verb/pick_ghost_customization() - set name = "Ghost Customization" - set category = "Preferences" - set desc = "Customize your ghastly appearance." - if(is_content_unlocked()) - switch(alert("Which setting do you want to change?",,"Ghost Form","Ghost Orbit","Ghost Accessories")) - if("Ghost Form") - pick_form() - if("Ghost Orbit") - pick_ghost_orbit() - if("Ghost Accessories") - pick_ghost_accs() - else - pick_ghost_accs() - -/client/verb/pick_ghost_others() - set name = "Ghosts of Others" - set category = "Preferences" - set desc = "Change display settings for the ghosts of other players." - var/new_ghost_others = alert("Do you want the ghosts of others to show up as their own setting, as their default sprites or always as the default white ghost?",,"Their Setting", "Default Sprites", "White Ghost") - if(new_ghost_others) - switch(new_ghost_others) - if("Their Setting") - prefs.ghost_others = GHOST_OTHERS_THEIR_SETTING - if("Default Sprites") - prefs.ghost_others = GHOST_OTHERS_DEFAULT_SPRITE - if("White Ghost") - prefs.ghost_others = GHOST_OTHERS_SIMPLE - prefs.save_preferences() - if(isobserver(mob)) - var/mob/dead/observer/O = mob - O.update_sight() - -/client/verb/toggle_intent_style() - set name = "Toggle Intent Selection Style" - set category = "Preferences" - set desc = "Toggle between directly clicking the desired intent or clicking to rotate through." - prefs.toggles ^= INTENT_STYLE - to_chat(src, "[(prefs.toggles & INTENT_STYLE) ? "Clicking directly on intents selects them." : "Clicking on intents rotates selection clockwise."]") - prefs.save_preferences() - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Intent Selection", "[prefs.toggles & INTENT_STYLE ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/verb/toggle_ghost_hud_pref() - set name = "Toggle Ghost HUD" - set category = "Preferences" - set desc = "Hide/Show Ghost HUD" - - prefs.ghost_hud = !prefs.ghost_hud - to_chat(src, "Ghost HUD will now be [prefs.ghost_hud ? "visible" : "hidden"].") - prefs.save_preferences() - if(isobserver(mob)) - mob.hud_used.show_hud() - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost HUD", "[prefs.ghost_hud ? "Enabled" : "Disabled"]")) - -/client/verb/toggle_inquisition() // warning: unexpected inquisition - set name = "Toggle Inquisitiveness" - set desc = "Sets whether your ghost examines everything on click by default" - set category = "Preferences" - - prefs.inquisitive_ghost = !prefs.inquisitive_ghost - prefs.save_preferences() - if(prefs.inquisitive_ghost) - to_chat(src, "You will now examine everything you click on.") - else - to_chat(src, "You will no longer examine things you click on.") - SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost Inquisitiveness", "[prefs.inquisitive_ghost ? "Enabled" : "Disabled"]")) - -//Admin Preferences -/client/proc/toggleadminhelpsound() - set name = "Hear/Silence Adminhelps" - set category = "Prefs - Admin" - set desc = "Toggle hearing a notification when admin PMs are received" - if(!holder) - return - prefs.toggles ^= SOUND_ADMINHELP - prefs.save_preferences() - to_chat(usr, "You will [(prefs.toggles & SOUND_ADMINHELP) ? "now" : "no longer"] hear a sound when adminhelps arrive.") - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Adminhelp Sound", "[prefs.toggles & SOUND_ADMINHELP ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/toggleannouncelogin() - set name = "Do/Don't Announce Login" - set category = "Prefs - Admin" - set desc = "Toggle if you want an announcement to admins when you login during a round" - if(!holder) - return - prefs.toggles ^= ANNOUNCE_LOGIN - prefs.save_preferences() - to_chat(usr, "You will [(prefs.toggles & ANNOUNCE_LOGIN) ? "now" : "no longer"] have an announcement to other admins when you login.") - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Login Announcement", "[prefs.toggles & ANNOUNCE_LOGIN ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/toggle_hear_radio() - set name = "Show/Hide Radio Chatter" - set category = "Prefs - Admin" - set desc = "Toggle seeing radiochatter from nearby radios and speakers" - if(!holder) - return - prefs.chat_toggles ^= CHAT_RADIO - prefs.save_preferences() - to_chat(usr, "You will [(prefs.chat_toggles & CHAT_RADIO) ? "now" : "no longer"] see radio chatter from nearby radios or speakers") - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Radio Chatter", "[prefs.chat_toggles & CHAT_RADIO ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/deadchat() - set name = "Show/Hide Deadchat" - set category = "Prefs - Admin" - set desc ="Toggles seeing deadchat" - if(!holder) - return - prefs.chat_toggles ^= CHAT_DEAD - prefs.save_preferences() - to_chat(src, "You will [(prefs.chat_toggles & CHAT_DEAD) ? "now" : "no longer"] see deadchat.") - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Deadchat Visibility", "[prefs.chat_toggles & CHAT_DEAD ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/toggleprayers() - set name = "Show/Hide Prayers" - set category = "Prefs - Admin" - set desc = "Toggles seeing prayers" - if(!holder) - return - prefs.chat_toggles ^= CHAT_PRAYER - prefs.save_preferences() - to_chat(src, "You will [(prefs.chat_toggles & CHAT_PRAYER) ? "now" : "no longer"] see prayerchat.") - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Prayer Visibility", "[prefs.chat_toggles & CHAT_PRAYER ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/toggle_prayer_sound() - set name = "Hear/Silence Prayer Sounds" - set category = "Prefs - Admin" - set desc = "Hear Prayer Sounds" - if(!holder) - return - prefs.toggles ^= SOUND_PRAYERS - prefs.save_preferences() - to_chat(usr, "You will [(prefs.toggles & SOUND_PRAYERS) ? "now" : "no longer"] hear a sound when prayers arrive.") - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Prayer Sounds", "[usr.client.prefs.toggles & SOUND_PRAYERS ? "Enabled" : "Disabled"]")) - -/client/proc/colorasay() - set name = "Set Admin Say Color" - set category = "Prefs - Admin" - set desc = "Set the color of your ASAY messages" - if(!holder) - return - if(!CONFIG_GET(flag/allow_admin_asaycolor)) - to_chat(src, "Custom Asay color is currently disabled by the server.") - return - var/new_asaycolor = input(src, "Please select your ASAY color.", "ASAY color", prefs.asaycolor) as color|null - if(new_asaycolor) - prefs.asaycolor = sanitize_ooccolor(new_asaycolor) - prefs.save_preferences() - SSblackbox.record_feedback("tally", "admin_verb", 1, "Set ASAY Color") - return - -/client/proc/resetasaycolor() - set name = "Reset your Admin Say Color" - set desc = "Returns your ASAY Color to default" - set category = "Prefs - Admin" - if(!holder) - return - if(!CONFIG_GET(flag/allow_admin_asaycolor)) - to_chat(src, "Custom Asay color is currently disabled by the server.") - return - prefs.asaycolor = initial(prefs.asaycolor) - prefs.save_preferences() +//this works as is to create a single checked item, but has no back end code for toggleing the check yet +#define TOGGLE_CHECKBOX(PARENT, CHILD) PARENT/CHILD/abstract = TRUE;PARENT/CHILD/checkbox = CHECKBOX_TOGGLE;PARENT/CHILD/verb/CHILD + +//Example usage TOGGLE_CHECKBOX(datum/verbs/menu/Settings/Ghost/chatterbox, toggle_ghost_ears)() + +//override because we don't want to save preferences twice. +/datum/verbs/menu/Settings/Set_checked(client/C, verbpath) + if (checkbox == CHECKBOX_GROUP) + C.prefs.menuoptions[type] = verbpath + else if (checkbox == CHECKBOX_TOGGLE) + var/checked = Get_checked(C) + C.prefs.menuoptions[type] = !checked + winset(C, "[verbpath]", "is-checked = [!checked]") + +/datum/verbs/menu/Settings/verb/setup_character() + set name = "Game Preferences" + set category = "Preferences" + set desc = "Open Game Preferences Window" + usr.client.prefs.current_tab = 1 + usr.client.prefs.ShowChoices(usr) + +//toggles +/datum/verbs/menu/Settings/Ghost/chatterbox + name = "Chat Box Spam" + +TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Ghost/chatterbox, toggle_ghost_ears)() + set name = "Show/Hide GhostEars" + set category = "Preferences" + set desc = "See All Speech" + usr.client.prefs.chat_toggles ^= CHAT_GHOSTEARS + to_chat(usr, "As a ghost, you will now [(usr.client.prefs.chat_toggles & CHAT_GHOSTEARS) ? "see all speech in the world" : "only see speech from nearby mobs"].") + usr.client.prefs.save_preferences() + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost Ears", "[usr.client.prefs.chat_toggles & CHAT_GHOSTEARS ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! +/datum/verbs/menu/Settings/Ghost/chatterbox/toggle_ghost_ears/Get_checked(client/C) + return C.prefs.chat_toggles & CHAT_GHOSTEARS + +TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Ghost/chatterbox, toggle_ghost_sight)() + set name = "Show/Hide GhostSight" + set category = "Preferences" + set desc = "See All Emotes" + usr.client.prefs.chat_toggles ^= CHAT_GHOSTSIGHT + to_chat(usr, "As a ghost, you will now [(usr.client.prefs.chat_toggles & CHAT_GHOSTSIGHT) ? "see all emotes in the world" : "only see emotes from nearby mobs"].") + usr.client.prefs.save_preferences() + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost Sight", "[usr.client.prefs.chat_toggles & CHAT_GHOSTSIGHT ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! +/datum/verbs/menu/Settings/Ghost/chatterbox/toggle_ghost_sight/Get_checked(client/C) + return C.prefs.chat_toggles & CHAT_GHOSTSIGHT + +TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Ghost/chatterbox, toggle_ghost_whispers)() + set name = "Show/Hide GhostWhispers" + set category = "Preferences" + set desc = "See All Whispers" + usr.client.prefs.chat_toggles ^= CHAT_GHOSTWHISPER + to_chat(usr, "As a ghost, you will now [(usr.client.prefs.chat_toggles & CHAT_GHOSTWHISPER) ? "see all whispers in the world" : "only see whispers from nearby mobs"].") + usr.client.prefs.save_preferences() + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost Whispers", "[usr.client.prefs.chat_toggles & CHAT_GHOSTWHISPER ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! +/datum/verbs/menu/Settings/Ghost/chatterbox/toggle_ghost_whispers/Get_checked(client/C) + return C.prefs.chat_toggles & CHAT_GHOSTWHISPER + +TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Ghost/chatterbox, toggle_ghost_radio)() + set name = "Show/Hide GhostRadio" + set category = "Preferences" + set desc = "See All Radio Chatter" + usr.client.prefs.chat_toggles ^= CHAT_GHOSTRADIO + to_chat(usr, "As a ghost, you will now [(usr.client.prefs.chat_toggles & CHAT_GHOSTRADIO) ? "see radio chatter" : "not see radio chatter"].") + usr.client.prefs.save_preferences() + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost Radio", "[usr.client.prefs.chat_toggles & CHAT_GHOSTRADIO ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! //social experiment, increase the generation whenever you copypaste this shamelessly GENERATION 1 +/datum/verbs/menu/Settings/Ghost/chatterbox/toggle_ghost_radio/Get_checked(client/C) + return C.prefs.chat_toggles & CHAT_GHOSTRADIO + +TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Ghost/chatterbox, toggle_ghost_pda)() + set name = "Show/Hide GhostPDA" + set category = "Preferences" + set desc = "See All PDA Messages" + usr.client.prefs.chat_toggles ^= CHAT_GHOSTPDA + to_chat(usr, "As a ghost, you will now [(usr.client.prefs.chat_toggles & CHAT_GHOSTPDA) ? "see all pda messages in the world" : "only see pda messages from nearby mobs"].") + usr.client.prefs.save_preferences() + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost PDA", "[usr.client.prefs.chat_toggles & CHAT_GHOSTPDA ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! +/datum/verbs/menu/Settings/Ghost/chatterbox/toggle_ghost_pda/Get_checked(client/C) + return C.prefs.chat_toggles & CHAT_GHOSTPDA + +TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Ghost/chatterbox, toggle_ghost_laws)() + set name = "Show/Hide GhostLaws" + set category = "Preferences" + set desc = "See All Law Changes" + usr.client.prefs.chat_toggles ^= CHAT_GHOSTLAWS + to_chat(usr, "As a ghost, you will now [(usr.client.prefs.chat_toggles & CHAT_GHOSTLAWS) ? "be notified of all law chanes" : "no longer be notified of law changes"].") + usr.client.prefs.save_preferences() + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost Laws", "[usr.client.prefs.chat_toggles & CHAT_GHOSTLAWS ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! +/datum/verbs/menu/Settings/Ghost/chatterbox/toggle_ghost_laws/Get_checked(client/C) + return C.prefs.chat_toggles & CHAT_GHOSTLAWS + +/datum/verbs/menu/Settings/Ghost/chatterbox/Events + name = "Events" + +//please be aware that the following two verbs have inverted stat output, so that "Toggle Deathrattle|1" still means you activated it +TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Ghost/chatterbox/Events, toggle_deathrattle)() + set name = "Toggle Deathrattle" + set category = "Preferences" + set desc = "Death" + usr.client.prefs.toggles ^= DISABLE_DEATHRATTLE + usr.client.prefs.save_preferences() + to_chat(usr, "You will [(usr.client.prefs.toggles & DISABLE_DEATHRATTLE) ? "no longer" : "now"] get messages when a sentient mob dies.") + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Deathrattle", "[!(usr.client.prefs.toggles & DISABLE_DEATHRATTLE) ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, maybe you should spend some time reading the comments. +/datum/verbs/menu/Settings/Ghost/chatterbox/Events/toggle_deathrattle/Get_checked(client/C) + return !(C.prefs.toggles & DISABLE_DEATHRATTLE) + +TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Ghost/chatterbox/Events, toggle_arrivalrattle)() + set name = "Toggle Arrivalrattle" + set category = "Preferences" + set desc = "New Player Arrival" + usr.client.prefs.toggles ^= DISABLE_ARRIVALRATTLE + to_chat(usr, "You will [(usr.client.prefs.toggles & DISABLE_ARRIVALRATTLE) ? "no longer" : "now"] get messages when someone joins the station.") + usr.client.prefs.save_preferences() + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Arrivalrattle", "[!(usr.client.prefs.toggles & DISABLE_ARRIVALRATTLE) ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, maybe you should rethink where your life went so wrong. +/datum/verbs/menu/Settings/Ghost/chatterbox/Events/toggle_arrivalrattle/Get_checked(client/C) + return !(C.prefs.toggles & DISABLE_ARRIVALRATTLE) + +TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Ghost, togglemidroundantag)() + set name = "Toggle Midround Antagonist" + set category = "Preferences" + set desc = "Midround Antagonist" + usr.client.prefs.toggles ^= MIDROUND_ANTAG + usr.client.prefs.save_preferences() + to_chat(usr, "You will [(usr.client.prefs.toggles & MIDROUND_ANTAG) ? "now" : "no longer"] be considered for midround antagonist positions.") + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Midround Antag", "[usr.client.prefs.toggles & MIDROUND_ANTAG ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! +/datum/verbs/menu/Settings/Ghost/togglemidroundantag/Get_checked(client/C) + return C.prefs.toggles & MIDROUND_ANTAG + +TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Sound, toggletitlemusic)() + set name = "Hear/Silence Lobby Music" + set category = "Preferences" + set desc = "Hear Music In Lobby" + usr.client.prefs.toggles ^= SOUND_LOBBY + usr.client.prefs.save_preferences() + if(usr.client.prefs.toggles & SOUND_LOBBY) + to_chat(usr, "You will now hear music in the game lobby.") + if(isnewplayer(usr)) + usr.client.playtitlemusic() + else + to_chat(usr, "You will no longer hear music in the game lobby.") + usr.stop_sound_channel(CHANNEL_LOBBYMUSIC) + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Lobby Music", "[usr.client.prefs.toggles & SOUND_LOBBY ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! +/datum/verbs/menu/Settings/Sound/toggletitlemusic/Get_checked(client/C) + return C.prefs.toggles & SOUND_LOBBY + + +TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Sound, toggleendofroundsounds)() + set name = "Hear/Silence End of Round Sounds" + set category = "Preferences" + set desc = "Hear Round End Sounds" + usr.client.prefs.toggles ^= SOUND_ENDOFROUND + usr.client.prefs.save_preferences() + if(usr.client.prefs.toggles & SOUND_ENDOFROUND) + to_chat(usr, "You will now hear sounds played before the server restarts.") + else + to_chat(usr, "You will no longer hear sounds played before the server restarts.") + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle End of Round Sounds", "[usr.client.prefs.toggles & SOUND_ENDOFROUND ? "Enabled" : "Disabled"]")) +/datum/verbs/menu/Settings/Sound/toggleendofroundsounds/Get_checked(client/C) + return C.prefs.toggles & SOUND_ENDOFROUND + + +TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Sound, togglemidis)() + set name = "Hear/Silence Midis" + set category = "Preferences" + set desc = "Hear Admin Triggered Sounds (Midis)" + usr.client.prefs.toggles ^= SOUND_MIDI + usr.client.prefs.save_preferences() + if(usr.client.prefs.toggles & SOUND_MIDI) + to_chat(usr, "You will now hear any sounds uploaded by admins.") + else + to_chat(usr, "You will no longer hear sounds uploaded by admins") + usr.stop_sound_channel(CHANNEL_ADMIN) + var/client/C = usr.client + if(C && C.chatOutput && !C.chatOutput.broken && C.chatOutput.loaded) + C.chatOutput.stopMusic() + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Hearing Midis", "[usr.client.prefs.toggles & SOUND_MIDI ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! +/datum/verbs/menu/Settings/Sound/togglemidis/Get_checked(client/C) + return C.prefs.toggles & SOUND_MIDI + + +TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Sound, toggle_instruments)() + set name = "Hear/Silence Instruments" + set category = "Preferences" + set desc = "Hear In-game Instruments" + usr.client.prefs.toggles ^= SOUND_INSTRUMENTS + usr.client.prefs.save_preferences() + if(usr.client.prefs.toggles & SOUND_INSTRUMENTS) + to_chat(usr, "You will now hear people playing musical instruments.") + else + to_chat(usr, "You will no longer hear musical instruments.") + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Instruments", "[usr.client.prefs.toggles & SOUND_INSTRUMENTS ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! +/datum/verbs/menu/Settings/Sound/toggle_instruments/Get_checked(client/C) + return C.prefs.toggles & SOUND_INSTRUMENTS + + +TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Sound, Toggle_Soundscape)() + set name = "Hear/Silence Ambience" + set category = "Preferences" + set desc = "Hear Ambient Sound Effects" + usr.client.prefs.toggles ^= SOUND_AMBIENCE + usr.client.prefs.save_preferences() + if(usr.client.prefs.toggles & SOUND_AMBIENCE) + to_chat(usr, "You will now hear ambient sounds.") + else + to_chat(usr, "You will no longer hear ambient sounds.") + usr.stop_sound_channel(CHANNEL_AMBIENCE) + usr.stop_sound_channel(CHANNEL_BUZZ) + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ambience", "[usr.client.prefs.toggles & SOUND_AMBIENCE ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! +/datum/verbs/menu/Settings/Sound/Toggle_Soundscape/Get_checked(client/C) + return C.prefs.toggles & SOUND_AMBIENCE + + +TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Sound, toggle_ship_ambience)() + set name = "Hear/Silence Ship Ambience" + set category = "Preferences" + set desc = "Hear Ship Ambience Roar" + usr.client.prefs.toggles ^= SOUND_SHIP_AMBIENCE + usr.client.prefs.save_preferences() + if(usr.client.prefs.toggles & SOUND_SHIP_AMBIENCE) + to_chat(usr, "You will now hear ship ambience.") + else + to_chat(usr, "You will no longer hear ship ambience.") + usr.stop_sound_channel(CHANNEL_BUZZ) + usr.client.ambience_playing = 0 + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ship Ambience", "[usr.client.prefs.toggles & SOUND_SHIP_AMBIENCE ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, I bet you read this comment expecting to see the same thing :^) +/datum/verbs/menu/Settings/Sound/toggle_ship_ambience/Get_checked(client/C) + return C.prefs.toggles & SOUND_SHIP_AMBIENCE + + +TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Sound, toggle_announcement_sound)() + set name = "Hear/Silence Announcements" + set category = "Preferences" + set desc = "Hear Announcement Sound" + usr.client.prefs.toggles ^= SOUND_ANNOUNCEMENTS + to_chat(usr, "You will now [(usr.client.prefs.toggles & SOUND_ANNOUNCEMENTS) ? "hear announcement sounds" : "no longer hear announcements"].") + usr.client.prefs.save_preferences() + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Announcement Sound", "[usr.client.prefs.toggles & SOUND_ANNOUNCEMENTS ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! +/datum/verbs/menu/Settings/Sound/toggle_announcement_sound/Get_checked(client/C) + return C.prefs.toggles & SOUND_ANNOUNCEMENTS + +/* READD THIS WHEN YOU FIX RADIO CHATTER +TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Sound, toggle_radio_sound)() + set name = "Hear/Silence Radio Chatter" + set category = "Preferences" + set desc = "Hear Radio Sound" + usr.client.prefs.toggles ^= SOUND_RADIO + to_chat(usr, "You will now [(usr.client.prefs.toggles & SOUND_RADIO) ? "hear radio chatter sounds" : "no longer hear radio chatter sounds"].") + usr.client.prefs.save_preferences() + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Radio Sound", "[usr.client.prefs.toggles & SOUND_RADIO ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc, just like EVERY other comment says. + +/datum/verbs/menu/Settings/Sound/toggle_radio_sound/Get_checked(client/C) + return C.prefs.toggles & SOUND_RADIO +*/ + +/datum/verbs/menu/Settings/Sound/verb/stop_client_sounds() + set name = "Stop Sounds" + set category = "Preferences" + set desc = "Stop Current Sounds" + SEND_SOUND(usr, sound(null)) + var/client/C = usr.client + if(C && C.chatOutput && !C.chatOutput.broken && C.chatOutput.loaded) + C.chatOutput.stopMusic() + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Stop Self Sounds")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + +TOGGLE_CHECKBOX(/datum/verbs/menu/Settings, listen_ooc)() + set name = "Show/Hide OOC" + set category = "Preferences" + set desc = "Show OOC Chat" + usr.client.prefs.chat_toggles ^= CHAT_OOC + usr.client.prefs.save_preferences() + to_chat(usr, "You will [(usr.client.prefs.chat_toggles & CHAT_OOC) ? "now" : "no longer"] see messages on the OOC channel.") + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Seeing OOC", "[usr.client.prefs.chat_toggles & CHAT_OOC ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! +/datum/verbs/menu/Settings/listen_ooc/Get_checked(client/C) + return C.prefs.chat_toggles & CHAT_OOC + +//Begin Wasp Edit +TOGGLE_CHECKBOX(datum/verbs/menu/settings, listen_looc)() + set name = "Show/Hide LOOC" + set category = "Preferences" + set desc = "Toggles seeing Local Out Of Character chat" + usr.client.prefs.chat_toggles ^= CHAT_LOOC + usr.client.prefs.save_preferences() + to_chat(usr, "You will [(usr.client.prefs.chat_toggles & CHAT_LOOC) ? "now" : "no longer"] see messages on the LOOC channel.") + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Seeing LOOC", "[usr.client.prefs.chat_toggles & CHAT_LOOC ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! +/datum/verbs/menu/Settings/listen_looc/Get_checked(client/C) + return C.prefs.chat_toggles & CHAT_LOOC +//End Wasp Edit + +TOGGLE_CHECKBOX(/datum/verbs/menu/Settings, listen_bank_card)() + set name = "Show/Hide Income Updates" + set category = "Preferences" + set desc = "Show or hide updates to your income" + usr.client.prefs.chat_toggles ^= CHAT_BANKCARD + usr.client.prefs.save_preferences() + to_chat(usr, "You will [(usr.client.prefs.chat_toggles & CHAT_BANKCARD) ? "now" : "no longer"] be notified when you get paid.") + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Income Notifications", "[(usr.client.prefs.chat_toggles & CHAT_BANKCARD) ? "Enabled" : "Disabled"]")) +/datum/verbs/menu/Settings/listen_bank_card/Get_checked(client/C) + return C.prefs.chat_toggles & CHAT_BANKCARD + + +GLOBAL_LIST_INIT(ghost_forms, sortList(list("ghost","ghostking","ghostian2","skeleghost","ghost_red","ghost_black", \ + "ghost_blue","ghost_yellow","ghost_green","ghost_pink", \ + "ghost_cyan","ghost_dblue","ghost_dred","ghost_dgreen", \ + "ghost_dcyan","ghost_grey","ghost_dyellow","ghost_dpink", "ghost_purpleswirl","ghost_funkypurp","ghost_pinksherbert","ghost_blazeit",\ + "ghost_mellow","ghost_rainbow","ghost_camo","ghost_fire", "catghost"))) +/client/proc/pick_form() + if(!is_content_unlocked()) + alert("This setting is for accounts with BYOND premium only.") + return + var/new_form = input(src, "Thanks for supporting BYOND - Choose your ghostly form:","Thanks for supporting BYOND",null) as null|anything in GLOB.ghost_forms + if(new_form) + prefs.ghost_form = new_form + prefs.save_preferences() + if(isobserver(mob)) + var/mob/dead/observer/O = mob + O.update_icon(new_form) + +GLOBAL_LIST_INIT(ghost_orbits, list(GHOST_ORBIT_CIRCLE,GHOST_ORBIT_TRIANGLE,GHOST_ORBIT_SQUARE,GHOST_ORBIT_HEXAGON,GHOST_ORBIT_PENTAGON)) + +/client/proc/pick_ghost_orbit() + if(!is_content_unlocked()) + alert("This setting is for accounts with BYOND premium only.") + return + var/new_orbit = input(src, "Thanks for supporting BYOND - Choose your ghostly orbit:","Thanks for supporting BYOND",null) as null|anything in GLOB.ghost_orbits + if(new_orbit) + prefs.ghost_orbit = new_orbit + prefs.save_preferences() + if(isobserver(mob)) + var/mob/dead/observer/O = mob + O.ghost_orbit = new_orbit + +/client/proc/pick_ghost_accs() + var/new_ghost_accs = alert("Do you want your ghost to show full accessories where possible, hide accessories but still use the directional sprites where possible, or also ignore the directions and stick to the default sprites?",,"full accessories", "only directional sprites", "default sprites") + if(new_ghost_accs) + switch(new_ghost_accs) + if("full accessories") + prefs.ghost_accs = GHOST_ACCS_FULL + if("only directional sprites") + prefs.ghost_accs = GHOST_ACCS_DIR + if("default sprites") + prefs.ghost_accs = GHOST_ACCS_NONE + prefs.save_preferences() + if(isobserver(mob)) + var/mob/dead/observer/O = mob + O.update_icon() + +/client/verb/pick_ghost_customization() + set name = "Ghost Customization" + set category = "Preferences" + set desc = "Customize your ghastly appearance." + if(is_content_unlocked()) + switch(alert("Which setting do you want to change?",,"Ghost Form","Ghost Orbit","Ghost Accessories")) + if("Ghost Form") + pick_form() + if("Ghost Orbit") + pick_ghost_orbit() + if("Ghost Accessories") + pick_ghost_accs() + else + pick_ghost_accs() + +/client/verb/pick_ghost_others() + set name = "Ghosts of Others" + set category = "Preferences" + set desc = "Change display settings for the ghosts of other players." + var/new_ghost_others = alert("Do you want the ghosts of others to show up as their own setting, as their default sprites or always as the default white ghost?",,"Their Setting", "Default Sprites", "White Ghost") + if(new_ghost_others) + switch(new_ghost_others) + if("Their Setting") + prefs.ghost_others = GHOST_OTHERS_THEIR_SETTING + if("Default Sprites") + prefs.ghost_others = GHOST_OTHERS_DEFAULT_SPRITE + if("White Ghost") + prefs.ghost_others = GHOST_OTHERS_SIMPLE + prefs.save_preferences() + if(isobserver(mob)) + var/mob/dead/observer/O = mob + O.update_sight() + +/client/verb/toggle_intent_style() + set name = "Toggle Intent Selection Style" + set category = "Preferences" + set desc = "Toggle between directly clicking the desired intent or clicking to rotate through." + prefs.toggles ^= INTENT_STYLE + to_chat(src, "[(prefs.toggles & INTENT_STYLE) ? "Clicking directly on intents selects them." : "Clicking on intents rotates selection clockwise."]") + prefs.save_preferences() + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Intent Selection", "[prefs.toggles & INTENT_STYLE ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/verb/toggle_ghost_hud_pref() + set name = "Toggle Ghost HUD" + set category = "Preferences" + set desc = "Hide/Show Ghost HUD" + + prefs.ghost_hud = !prefs.ghost_hud + to_chat(src, "Ghost HUD will now be [prefs.ghost_hud ? "visible" : "hidden"].") + prefs.save_preferences() + if(isobserver(mob)) + mob.hud_used.show_hud() + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost HUD", "[prefs.ghost_hud ? "Enabled" : "Disabled"]")) + +/client/verb/toggle_show_credits() + set name = "Toggle Credits" + set category = "Preferences" + set desc = "Hide/Show Credits" + + prefs.show_credits = !prefs.show_credits + to_chat(src, "Credits will now be [prefs.show_credits ? "visible" : "hidden"].") + prefs.save_preferences() + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Credits", "[prefs.show_credits ? "Enabled" : "Disabled"]")) + +/client/verb/toggle_inquisition() // warning: unexpected inquisition + set name = "Toggle Inquisitiveness" + set desc = "Sets whether your ghost examines everything on click by default" + set category = "Preferences" + + prefs.inquisitive_ghost = !prefs.inquisitive_ghost + prefs.save_preferences() + if(prefs.inquisitive_ghost) + to_chat(src, "You will now examine everything you click on.") + else + to_chat(src, "You will no longer examine things you click on.") + SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost Inquisitiveness", "[prefs.inquisitive_ghost ? "Enabled" : "Disabled"]")) + +//Admin Preferences +/client/proc/toggleadminhelpsound() + set name = "Hear/Silence Adminhelps" + set category = "Prefs - Admin" + set desc = "Toggle hearing a notification when admin PMs are received" + if(!holder) + return + prefs.toggles ^= SOUND_ADMINHELP + prefs.save_preferences() + to_chat(usr, "You will [(prefs.toggles & SOUND_ADMINHELP) ? "now" : "no longer"] hear a sound when adminhelps arrive.") + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Adminhelp Sound", "[prefs.toggles & SOUND_ADMINHELP ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/toggleannouncelogin() + set name = "Do/Don't Announce Login" + set category = "Prefs - Admin" + set desc = "Toggle if you want an announcement to admins when you login during a round" + if(!holder) + return + prefs.toggles ^= ANNOUNCE_LOGIN + prefs.save_preferences() + to_chat(usr, "You will [(prefs.toggles & ANNOUNCE_LOGIN) ? "now" : "no longer"] have an announcement to other admins when you login.") + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Login Announcement", "[prefs.toggles & ANNOUNCE_LOGIN ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/toggle_hear_radio() + set name = "Show/Hide Radio Chatter" + set category = "Prefs - Admin" + set desc = "Toggle seeing radiochatter from nearby radios and speakers" + if(!holder) + return + prefs.chat_toggles ^= CHAT_RADIO + prefs.save_preferences() + to_chat(usr, "You will [(prefs.chat_toggles & CHAT_RADIO) ? "now" : "no longer"] see radio chatter from nearby radios or speakers") + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Radio Chatter", "[prefs.chat_toggles & CHAT_RADIO ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/deadchat() + set name = "Show/Hide Deadchat" + set category = "Prefs - Admin" + set desc ="Toggles seeing deadchat" + if(!holder) + return + prefs.chat_toggles ^= CHAT_DEAD + prefs.save_preferences() + to_chat(src, "You will [(prefs.chat_toggles & CHAT_DEAD) ? "now" : "no longer"] see deadchat.") + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Deadchat Visibility", "[prefs.chat_toggles & CHAT_DEAD ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/toggleprayers() + set name = "Show/Hide Prayers" + set category = "Prefs - Admin" + set desc = "Toggles seeing prayers" + if(!holder) + return + prefs.chat_toggles ^= CHAT_PRAYER + prefs.save_preferences() + to_chat(src, "You will [(prefs.chat_toggles & CHAT_PRAYER) ? "now" : "no longer"] see prayerchat.") + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Prayer Visibility", "[prefs.chat_toggles & CHAT_PRAYER ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/toggle_prayer_sound() + set name = "Hear/Silence Prayer Sounds" + set category = "Prefs - Admin" + set desc = "Hear Prayer Sounds" + if(!holder) + return + prefs.toggles ^= SOUND_PRAYERS + prefs.save_preferences() + to_chat(usr, "You will [(prefs.toggles & SOUND_PRAYERS) ? "now" : "no longer"] hear a sound when prayers arrive.") + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Prayer Sounds", "[usr.client.prefs.toggles & SOUND_PRAYERS ? "Enabled" : "Disabled"]")) + +/client/proc/colorasay() + set name = "Set Admin Say Color" + set category = "Prefs - Admin" + set desc = "Set the color of your ASAY messages" + if(!holder) + return + if(!CONFIG_GET(flag/allow_admin_asaycolor)) + to_chat(src, "Custom Asay color is currently disabled by the server.") + return + var/new_asaycolor = input(src, "Please select your ASAY color.", "ASAY color", prefs.asaycolor) as color|null + if(new_asaycolor) + prefs.asaycolor = sanitize_ooccolor(new_asaycolor) + prefs.save_preferences() + SSblackbox.record_feedback("tally", "admin_verb", 1, "Set ASAY Color") + return + +/client/proc/resetasaycolor() + set name = "Reset your Admin Say Color" + set desc = "Returns your ASAY Color to default" + set category = "Prefs - Admin" + if(!holder) + return + if(!CONFIG_GET(flag/allow_admin_asaycolor)) + to_chat(src, "Custom Asay color is currently disabled by the server.") + return + prefs.asaycolor = initial(prefs.asaycolor) + prefs.save_preferences() diff --git a/code/modules/client/verbs/etips.dm b/code/modules/client/verbs/etips.dm index 2455f5d01b07..c85e5113560a 100644 --- a/code/modules/client/verbs/etips.dm +++ b/code/modules/client/verbs/etips.dm @@ -1,20 +1,20 @@ -/client/verb/toggle_tips() - set name = "Toggle Examine Tooltips" - set desc = "Toggles examine hover-over tooltips" - set category = "Preferences" - - prefs.enable_tips = !prefs.enable_tips - prefs.save_preferences() - to_chat(usr, "Examine tooltips [prefs.enable_tips ? "en" : "dis"]abled.") - -/client/verb/change_tip_delay() - set name = "Set Examine Tooltip Delay" - set desc = "Sets the delay in milliseconds before examine tooltips appear" - set category = "Preferences" - - var/indelay = stripped_input(usr, "Enter the tooltip delay in milliseconds (default: 500)", "Enter tooltip delay", "", 10) - indelay = text2num(indelay) - if(usr)//is this what you mean? - prefs.tip_delay = indelay - prefs.save_preferences() - to_chat(usr, "Tooltip delay set to [indelay] milliseconds.") +/client/verb/toggle_tips() + set name = "Toggle Examine Tooltips" + set desc = "Toggles examine hover-over tooltips" + set category = "Preferences" + + prefs.enable_tips = !prefs.enable_tips + prefs.save_preferences() + to_chat(usr, "Examine tooltips [prefs.enable_tips ? "en" : "dis"]abled.") + +/client/verb/change_tip_delay() + set name = "Set Examine Tooltip Delay" + set desc = "Sets the delay in milliseconds before examine tooltips appear" + set category = "Preferences" + + var/indelay = stripped_input(usr, "Enter the tooltip delay in milliseconds (default: 500)", "Enter tooltip delay", "", 10) + indelay = text2num(indelay) + if(usr)//is this what you mean? + prefs.tip_delay = indelay + prefs.save_preferences() + to_chat(usr, "Tooltip delay set to [indelay] milliseconds.") diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm index 74284f215103..563202ebe6c8 100644 --- a/code/modules/clothing/clothing.dm +++ b/code/modules/clothing/clothing.dm @@ -1,428 +1,428 @@ -/obj/item/clothing - name = "clothing" - resistance_flags = FLAMMABLE - max_integrity = 200 - integrity_failure = 0.4 - var/damaged_clothes = 0 //similar to machine's BROKEN stat and structure's broken var - ///What level of bright light protection item has. - var/flash_protect = FLASH_PROTECTION_NONE - var/tint = 0 //Sets the item's level of visual impairment tint, normally set to the same as flash_protect - var/up = 0 //but separated to allow items to protect but not impair vision, like space helmets - var/visor_flags = 0 //flags that are added/removed when an item is adjusted up/down - var/visor_flags_inv = 0 //same as visor_flags, but for flags_inv - var/visor_flags_cover = 0 //same as above, but for flags_cover -//what to toggle when toggled with weldingvisortoggle() - var/visor_vars_to_toggle = VISOR_FLASHPROTECT | VISOR_TINT | VISOR_VISIONFLAGS | VISOR_DARKNESSVIEW | VISOR_INVISVIEW - lefthand_file = 'icons/mob/inhands/clothing_lefthand.dmi' - righthand_file = 'icons/mob/inhands/clothing_righthand.dmi' - var/alt_desc = null - var/toggle_message = null - var/alt_toggle_message = null - var/active_sound = null - var/toggle_cooldown = null - var/cooldown = 0 - - var/cuttable = FALSE //If you can cut the clothing with anything sharp - var/clothamnt = 0 //How much cloth - - var/clothing_flags = NONE - - //Var modification - PLEASE be careful with this I know who you are and where you live - var/list/user_vars_to_edit //VARNAME = VARVALUE eg: "name" = "butts" - var/list/user_vars_remembered //Auto built by the above + dropped() + equipped() - - var/pocket_storage_component_path - - //These allow head/mask items to dynamically alter the user's hair - // and facial hair, checking hair_extensions.dmi and facialhair_extensions.dmi - // for a state matching hair_state+dynamic_hair_suffix - // THESE OVERRIDE THE HIDEHAIR FLAGS - var/dynamic_hair_suffix = ""//head > mask for head hair - var/dynamic_fhair_suffix = ""//mask > head for facial hair - - ///These are armor values that protect the wearer, taken from the clothing's armor datum. List updates on examine because it's currently only used to print armor ratings to chat in Topic(). - var/list/armor_list = list() - ///These are armor values that protect the clothing, taken from its armor datum. List updates on examine because it's currently only used to print armor ratings to chat in Topic(). - var/list/durability_list = list() - -/obj/item/clothing/Initialize() - if((clothing_flags & VOICEBOX_TOGGLABLE)) - actions_types += /datum/action/item_action/toggle_voice_box - . = ..() - if(ispath(pocket_storage_component_path)) - LoadComponent(pocket_storage_component_path) - -/obj/item/clothing/MouseDrop(atom/over_object) - . = ..() - var/mob/M = usr - - if(ismecha(M.loc)) // stops inventory actions in a mech - return - - if(!M.incapacitated() && loc == M && istype(over_object, /obj/screen/inventory/hand)) - var/obj/screen/inventory/hand/H = over_object - if(M.putItemFromInventoryInHandIfPossible(src, H.held_index)) - add_fingerprint(usr) - -/obj/item/reagent_containers/food/snacks/clothing - name = "temporary moth clothing snack item" - desc = "If you're reading this it means I messed up. This is related to moths eating clothes and I didn't know a better way to do it than making a new food object." - list_reagents = list(/datum/reagent/consumable/nutriment = 1) - tastes = list("dust" = 1, "lint" = 1) - foodtype = CLOTH - -/obj/item/clothing/attack(mob/M, mob/user, def_zone) - if(user.a_intent != INTENT_HARM && ismoth(M)) - var/obj/item/reagent_containers/food/snacks/clothing/clothing_as_food = new - clothing_as_food.name = name - if(clothing_as_food.attack(M, user, def_zone)) - take_damage(15, sound_effect=FALSE) - qdel(clothing_as_food) - else - return ..() - -/obj/item/clothing/attackby(obj/item/W, mob/user, params) - if(W.get_sharpness() && cuttable) - if (alert(user, "Are you sure you want to cut the [src] into strips?", "Cut clothing:", "Yes", "No") != "Yes") - return - playsound(src.loc, 'sound/items/poster_ripped.ogg', 100, TRUE) - to_chat(user, "You cut the [src] into strips with [W].") - var/obj/item/stack/sheet/cloth/C = new (get_turf(src), clothamnt) - user.put_in_hands(C) - qdel(src) - - if(damaged_clothes && istype(W, /obj/item/stack/sheet/cloth)) - var/obj/item/stack/sheet/cloth/C = W - C.use(1) - update_clothes_damaged_state(FALSE) - obj_integrity = max_integrity - to_chat(user, "You fix the damage on [src] with [C].") - return 1 - return ..() - -/obj/item/clothing/Destroy() - user_vars_remembered = null //Oh god somebody put REFERENCES in here? not to worry, we'll clean it up - return ..() - -/obj/item/clothing/dropped(mob/user) - ..() - if(!istype(user)) - return - if(LAZYLEN(user_vars_remembered)) - for(var/variable in user_vars_remembered) - if(variable in user.vars) - if(user.vars[variable] == user_vars_to_edit[variable]) //Is it still what we set it to? (if not we best not change it) - user.vars[variable] = user_vars_remembered[variable] - user_vars_remembered = initial(user_vars_remembered) // Effectively this sets it to null. - -/obj/item/clothing/equipped(mob/user, slot) - ..() - if (!istype(user)) - return - if(slot_flags & slot) //Was equipped to a valid slot for this item? - if (LAZYLEN(user_vars_to_edit)) - for(var/variable in user_vars_to_edit) - if(variable in user.vars) - LAZYSET(user_vars_remembered, variable, user.vars[variable]) - user.vv_edit_var(variable, user_vars_to_edit[variable]) - -/obj/item/clothing/examine(mob/user) - . = ..() - switch (max_heat_protection_temperature) - if (400 to 1000) - . += "[src] offers the wearer limited protection from fire." - if (1001 to 1600) - . += "[src] offers the wearer some protection from fire." - if (1601 to 35000) - . += "[src] offers the wearer robust protection from fire." - if(damaged_clothes) - . += "It looks damaged!" - var/datum/component/storage/pockets = GetComponent(/datum/component/storage) - if(pockets) - var/list/how_cool_are_your_threads = list("") - if(pockets.attack_hand_interact) - how_cool_are_your_threads += "[src]'s storage opens when clicked.\n" - else - how_cool_are_your_threads += "[src]'s storage opens when dragged to yourself.\n" - if (pockets.can_hold?.len) // If pocket type can hold anything, vs only specific items - how_cool_are_your_threads += "[src] can store [pockets.max_items] item\s.\n" - else - how_cool_are_your_threads += "[src] can store [pockets.max_items] item\s that are [weightclass2text(pockets.max_w_class)] or smaller.\n" - if(pockets.quickdraw) - how_cool_are_your_threads += "You can quickly remove an item from [src] using Alt-Click.\n" - if(pockets.silent) - how_cool_are_your_threads += "Adding or removing items from [src] makes no noise.\n" - how_cool_are_your_threads += "" - . += how_cool_are_your_threads.Join() - - if(LAZYLEN(armor_list)) - armor_list.Cut() - if(armor.bio) - armor_list += list("TOXIN" = armor.bio) - if(armor.bomb) - armor_list += list("EXPLOSIVE" = armor.bomb) - if(armor.bullet) - armor_list += list("BULLET" = armor.bullet) - if(armor.energy) - armor_list += list("ENERGY" = armor.energy) - if(armor.laser) - armor_list += list("LASER" = armor.laser) - if(armor.magic) - armor_list += list("MAGIC" = armor.magic) - if(armor.melee) - armor_list += list("MELEE" = armor.melee) - if(armor.rad) - armor_list += list("RADIATION" = armor.rad) - - if(LAZYLEN(durability_list)) - durability_list.Cut() - if(armor.fire) - durability_list += list("FIRE" = armor.fire) - if(armor.acid) - durability_list += list("ACID" = armor.acid) - - if(LAZYLEN(armor_list) || LAZYLEN(durability_list)) - . += "It has a tag listing its protection classes." - -/obj/item/clothing/Topic(href, href_list) - . = ..() - - if(href_list["list_armor"]) - var/list/readout = list("PROTECTION CLASSES (I-X)") - if(LAZYLEN(armor_list)) - readout += "\nARMOR" - for(var/dam_type in armor_list) - var/armor_amount = armor_list[dam_type] - readout += "\n[dam_type] [armor_to_protection_class(armor_amount)]" //e.g. BOMB IV - if(LAZYLEN(durability_list)) - readout += "\nDURABILITY" - for(var/dam_type in durability_list) - var/durability_amount = durability_list[dam_type] - readout += "\n[dam_type] [armor_to_protection_class(durability_amount)]" //e.g. FIRE II - readout += "" - - to_chat(usr, "[readout.Join()]") - -/** - * Rounds armor_value to nearest 10, divides it by 10 and then expresses it in roman numerals up to 10 - * - * Rounds armor_value to nearest 10, divides it by 10 - * and then expresses it in roman numerals up to 10 - * Arguments: - * * armor_value - Number we're converting - */ -/obj/item/clothing/proc/armor_to_protection_class(armor_value) - armor_value = round(armor_value,10) / 10 - switch (armor_value) - if (1) - . = "I" - if (2) - . = "II" - if (3) - . = "III" - if (4) - . = "IV" - if (5) - . = "V" - if (6) - . = "VI" - if (7) - . = "VII" - if (8) - . = "VIII" - if (9) - . = "IX" - if (10 to INFINITY) - . = "X" - return . - -/obj/item/clothing/obj_break(damage_flag) - if(!damaged_clothes) - update_clothes_damaged_state(TRUE) - if(ismob(loc)) //It's not important enough to warrant a message if nobody's wearing it - var/mob/M = loc - to_chat(M, "Your [name] starts to fall apart!") - -/obj/item/clothing/proc/update_clothes_damaged_state(damaging = TRUE) - var/index = "[REF(initial(icon))]-[initial(icon_state)]" - var/static/list/damaged_clothes_icons = list() - if(damaging) - damaged_clothes = 1 - var/icon/damaged_clothes_icon = damaged_clothes_icons[index] - if(!damaged_clothes_icon) - damaged_clothes_icon = icon(initial(icon), initial(icon_state), , 1) //we only want to apply damaged effect to the initial icon_state for each object - damaged_clothes_icon.Blend("#fff", ICON_ADD) //fills the icon_state with white (except where it's transparent) - damaged_clothes_icon.Blend(icon('icons/effects/item_damage.dmi', "itemdamaged"), ICON_MULTIPLY) //adds damage effect and the remaining white areas become transparant - damaged_clothes_icon = fcopy_rsc(damaged_clothes_icon) - damaged_clothes_icons[index] = damaged_clothes_icon - add_overlay(damaged_clothes_icon, 1) - else - damaged_clothes = 0 - cut_overlay(damaged_clothes_icons[index], TRUE) - - -/* -SEE_SELF // can see self, no matter what -SEE_MOBS // can see all mobs, no matter what -SEE_OBJS // can see all objs, no matter what -SEE_TURFS // can see all turfs (and areas), no matter what -SEE_PIXELS// if an object is located on an unlit area, but some of its pixels are - // in a lit area (via pixel_x,y or smooth movement), can see those pixels -BLIND // can't see anything -*/ - -/proc/generate_female_clothing(index,t_color,icon,type) - var/icon/female_clothing_icon = icon("icon"=icon, "icon_state"=t_color) - var/icon/female_s = icon("icon"='icons/mob/clothing/under/masking_helpers.dmi', "icon_state"="[(type == FEMALE_UNIFORM_FULL) ? "female_full" : "female_top"]") - female_clothing_icon.Blend(female_s, ICON_MULTIPLY) - female_clothing_icon = fcopy_rsc(female_clothing_icon) - GLOB.female_clothing_icons[index] = female_clothing_icon - -/obj/item/clothing/under/verb/toggle() - set name = "Adjust Suit Sensors" - set category = "Object" - set src in usr - var/mob/M = usr - if (istype(M, /mob/dead/)) - return - if (!can_use(M)) - return - if(src.has_sensor == LOCKED_SENSORS) - to_chat(usr, "The controls are locked.") - return 0 - if(src.has_sensor == BROKEN_SENSORS) - to_chat(usr, "The sensors have shorted out!") - return 0 - if(src.has_sensor <= NO_SENSORS) - to_chat(usr, "This suit does not have any sensors.") - return 0 - - var/list/modes = list("Off", "Binary vitals", "Exact vitals", "Tracking beacon") - var/switchMode = input("Select a sensor mode:", "Suit Sensor Mode", modes[sensor_mode + 1]) in modes - if(get_dist(usr, src) > 1) - to_chat(usr, "You have moved too far away!") - return - sensor_mode = modes.Find(switchMode) - 1 - - if (src.loc == usr) - switch(sensor_mode) - if(0) - to_chat(usr, "You disable your suit's remote sensing equipment.") - if(1) - to_chat(usr, "Your suit will now only report whether you are alive or dead.") - if(2) - to_chat(usr, "Your suit will now only report your exact vital lifesigns.") - if(3) - to_chat(usr, "Your suit will now report your exact vital lifesigns as well as your coordinate position.") - - if(ishuman(loc)) - var/mob/living/carbon/human/H = loc - if(H.w_uniform == src) - H.update_suit_sensors() - -/obj/item/clothing/under/attack_hand(mob/user) - if(attached_accessory && ispath(attached_accessory.pocket_storage_component_path) && loc == user) - attached_accessory.attack_hand(user) - return - ..() - -/obj/item/clothing/under/AltClick(mob/user) - if(..()) - return 1 - - if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) - return - else - if(attached_accessory) - remove_accessory(user) - else - rolldown() - -/obj/item/clothing/under/verb/jumpsuit_adjust() - set name = "Adjust Jumpsuit Style" - set category = null - set src in usr - rolldown() - -/obj/item/clothing/under/proc/rolldown() - if(!can_use(usr)) - return - if(!can_adjust) - to_chat(usr, "You cannot wear this suit any differently!") - return - if(toggle_jumpsuit_adjust()) - to_chat(usr, "You adjust the suit to wear it more casually.") - else - to_chat(usr, "You adjust the suit back to normal.") - if(ishuman(usr)) - var/mob/living/carbon/human/H = usr - H.update_inv_w_uniform() - H.update_body() - -/obj/item/clothing/under/proc/toggle_jumpsuit_adjust() - if(adjusted == DIGITIGRADE_STYLE) - return - adjusted = !adjusted - if(adjusted) - if(fitted != FEMALE_UNIFORM_TOP) - fitted = NO_FEMALE_UNIFORM - if(!alt_covers_chest) // for the special snowflake suits that expose the chest when adjusted - body_parts_covered &= ~CHEST - else - fitted = initial(fitted) - if(!alt_covers_chest) - body_parts_covered |= CHEST - return adjusted - -/obj/item/clothing/proc/weldingvisortoggle(mob/user) //proc to toggle welding visors on helmets, masks, goggles, etc. - if(!can_use(user)) - return FALSE - - visor_toggling() - - to_chat(user, "You adjust \the [src] [up ? "up" : "down"].") - - if(iscarbon(user)) - var/mob/living/carbon/C = user - C.head_update(src, forced = 1) - for(var/X in actions) - var/datum/action/A = X - A.UpdateButtonIcon() - return TRUE - -/obj/item/clothing/proc/visor_toggling() //handles all the actual toggling of flags - up = !up - clothing_flags ^= visor_flags - flags_inv ^= visor_flags_inv - flags_cover ^= initial(flags_cover) - icon_state = "[initial(icon_state)][up ? "up" : ""]" - if(visor_vars_to_toggle & VISOR_FLASHPROTECT) - flash_protect ^= initial(flash_protect) - if(visor_vars_to_toggle & VISOR_TINT) - tint ^= initial(tint) - -/obj/item/clothing/head/helmet/space/plasmaman/visor_toggling() //handles all the actual toggling of flags - up = !up - clothing_flags ^= visor_flags - flags_inv ^= visor_flags_inv - icon_state = "[initial(icon_state)]" - if(visor_vars_to_toggle & VISOR_FLASHPROTECT) - flash_protect ^= initial(flash_protect) - if(visor_vars_to_toggle & VISOR_TINT) - tint ^= initial(tint) - -/obj/item/clothing/proc/can_use(mob/user) - if(user && ismob(user)) - if(!user.incapacitated()) - return 1 - return 0 - - -/obj/item/clothing/obj_destruction(damage_flag) - if(damage_flag == "bomb" || damage_flag == "melee") - var/turf/T = get_turf(src) - //so the shred survives potential turf change from the explosion. - addtimer(CALLBACK_NEW(/obj/effect/decal/cleanable/shreds, list(T, name)), 1) - deconstruct(FALSE) - else - ..() +/obj/item/clothing + name = "clothing" + resistance_flags = FLAMMABLE + max_integrity = 200 + integrity_failure = 0.4 + var/damaged_clothes = 0 //similar to machine's BROKEN stat and structure's broken var + ///What level of bright light protection item has. + var/flash_protect = FLASH_PROTECTION_NONE + var/tint = 0 //Sets the item's level of visual impairment tint, normally set to the same as flash_protect + var/up = 0 //but separated to allow items to protect but not impair vision, like space helmets + var/visor_flags = 0 //flags that are added/removed when an item is adjusted up/down + var/visor_flags_inv = 0 //same as visor_flags, but for flags_inv + var/visor_flags_cover = 0 //same as above, but for flags_cover +//what to toggle when toggled with weldingvisortoggle() + var/visor_vars_to_toggle = VISOR_FLASHPROTECT | VISOR_TINT | VISOR_VISIONFLAGS | VISOR_DARKNESSVIEW | VISOR_INVISVIEW + lefthand_file = 'icons/mob/inhands/clothing_lefthand.dmi' + righthand_file = 'icons/mob/inhands/clothing_righthand.dmi' + var/alt_desc = null + var/toggle_message = null + var/alt_toggle_message = null + var/active_sound = null + var/toggle_cooldown = null + var/cooldown = 0 + + var/cuttable = FALSE //If you can cut the clothing with anything sharp + var/clothamnt = 0 //How much cloth + + var/clothing_flags = NONE + + //Var modification - PLEASE be careful with this I know who you are and where you live + var/list/user_vars_to_edit //VARNAME = VARVALUE eg: "name" = "butts" + var/list/user_vars_remembered //Auto built by the above + dropped() + equipped() + + var/pocket_storage_component_path + + //These allow head/mask items to dynamically alter the user's hair + // and facial hair, checking hair_extensions.dmi and facialhair_extensions.dmi + // for a state matching hair_state+dynamic_hair_suffix + // THESE OVERRIDE THE HIDEHAIR FLAGS + var/dynamic_hair_suffix = ""//head > mask for head hair + var/dynamic_fhair_suffix = ""//mask > head for facial hair + + ///These are armor values that protect the wearer, taken from the clothing's armor datum. List updates on examine because it's currently only used to print armor ratings to chat in Topic(). + var/list/armor_list = list() + ///These are armor values that protect the clothing, taken from its armor datum. List updates on examine because it's currently only used to print armor ratings to chat in Topic(). + var/list/durability_list = list() + +/obj/item/clothing/Initialize() + if((clothing_flags & VOICEBOX_TOGGLABLE)) + actions_types += /datum/action/item_action/toggle_voice_box + . = ..() + if(ispath(pocket_storage_component_path)) + LoadComponent(pocket_storage_component_path) + +/obj/item/clothing/MouseDrop(atom/over_object) + . = ..() + var/mob/M = usr + + if(ismecha(M.loc)) // stops inventory actions in a mech + return + + if(!M.incapacitated() && loc == M && istype(over_object, /obj/screen/inventory/hand)) + var/obj/screen/inventory/hand/H = over_object + if(M.putItemFromInventoryInHandIfPossible(src, H.held_index)) + add_fingerprint(usr) + +/obj/item/reagent_containers/food/snacks/clothing + name = "temporary moth clothing snack item" + desc = "If you're reading this it means I messed up. This is related to moths eating clothes and I didn't know a better way to do it than making a new food object." + list_reagents = list(/datum/reagent/consumable/nutriment = 1) + tastes = list("dust" = 1, "lint" = 1) + foodtype = CLOTH + +/obj/item/clothing/attack(mob/M, mob/user, def_zone) + if(user.a_intent != INTENT_HARM && ismoth(M)) + var/obj/item/reagent_containers/food/snacks/clothing/clothing_as_food = new + clothing_as_food.name = name + if(clothing_as_food.attack(M, user, def_zone)) + take_damage(15, sound_effect=FALSE) + qdel(clothing_as_food) + else + return ..() + +/obj/item/clothing/attackby(obj/item/W, mob/user, params) + if(W.get_sharpness() && cuttable) + if (alert(user, "Are you sure you want to cut the [src] into strips?", "Cut clothing:", "Yes", "No") != "Yes") + return + playsound(src.loc, 'sound/items/poster_ripped.ogg', 100, TRUE) + to_chat(user, "You cut the [src] into strips with [W].") + var/obj/item/stack/sheet/cloth/C = new (get_turf(src), clothamnt) + user.put_in_hands(C) + qdel(src) + + if(damaged_clothes && istype(W, /obj/item/stack/sheet/cloth)) + var/obj/item/stack/sheet/cloth/C = W + C.use(1) + update_clothes_damaged_state(FALSE) + obj_integrity = max_integrity + to_chat(user, "You fix the damage on [src] with [C].") + return 1 + return ..() + +/obj/item/clothing/Destroy() + user_vars_remembered = null //Oh god somebody put REFERENCES in here? not to worry, we'll clean it up + return ..() + +/obj/item/clothing/dropped(mob/user) + ..() + if(!istype(user)) + return + if(LAZYLEN(user_vars_remembered)) + for(var/variable in user_vars_remembered) + if(variable in user.vars) + if(user.vars[variable] == user_vars_to_edit[variable]) //Is it still what we set it to? (if not we best not change it) + user.vars[variable] = user_vars_remembered[variable] + user_vars_remembered = initial(user_vars_remembered) // Effectively this sets it to null. + +/obj/item/clothing/equipped(mob/user, slot) + ..() + if (!istype(user)) + return + if(slot_flags & slot) //Was equipped to a valid slot for this item? + if (LAZYLEN(user_vars_to_edit)) + for(var/variable in user_vars_to_edit) + if(variable in user.vars) + LAZYSET(user_vars_remembered, variable, user.vars[variable]) + user.vv_edit_var(variable, user_vars_to_edit[variable]) + +/obj/item/clothing/examine(mob/user) + . = ..() + switch (max_heat_protection_temperature) + if (400 to 1000) + . += "[src] offers the wearer limited protection from fire." + if (1001 to 1600) + . += "[src] offers the wearer some protection from fire." + if (1601 to 35000) + . += "[src] offers the wearer robust protection from fire." + if(damaged_clothes) + . += "It looks damaged!" + var/datum/component/storage/pockets = GetComponent(/datum/component/storage) + if(pockets) + var/list/how_cool_are_your_threads = list("") + if(pockets.attack_hand_interact) + how_cool_are_your_threads += "[src]'s storage opens when clicked.\n" + else + how_cool_are_your_threads += "[src]'s storage opens when dragged to yourself.\n" + if (pockets.can_hold?.len) // If pocket type can hold anything, vs only specific items + how_cool_are_your_threads += "[src] can store [pockets.max_items] item\s.\n" + else + how_cool_are_your_threads += "[src] can store [pockets.max_items] item\s that are [weightclass2text(pockets.max_w_class)] or smaller.\n" + if(pockets.quickdraw) + how_cool_are_your_threads += "You can quickly remove an item from [src] using Alt-Click.\n" + if(pockets.silent) + how_cool_are_your_threads += "Adding or removing items from [src] makes no noise.\n" + how_cool_are_your_threads += "" + . += how_cool_are_your_threads.Join() + + if(LAZYLEN(armor_list)) + armor_list.Cut() + if(armor.bio) + armor_list += list("TOXIN" = armor.bio) + if(armor.bomb) + armor_list += list("EXPLOSIVE" = armor.bomb) + if(armor.bullet) + armor_list += list("BULLET" = armor.bullet) + if(armor.energy) + armor_list += list("ENERGY" = armor.energy) + if(armor.laser) + armor_list += list("LASER" = armor.laser) + if(armor.magic) + armor_list += list("MAGIC" = armor.magic) + if(armor.melee) + armor_list += list("MELEE" = armor.melee) + if(armor.rad) + armor_list += list("RADIATION" = armor.rad) + + if(LAZYLEN(durability_list)) + durability_list.Cut() + if(armor.fire) + durability_list += list("FIRE" = armor.fire) + if(armor.acid) + durability_list += list("ACID" = armor.acid) + + if(LAZYLEN(armor_list) || LAZYLEN(durability_list)) + . += "It has a tag listing its protection classes." + +/obj/item/clothing/Topic(href, href_list) + . = ..() + + if(href_list["list_armor"]) + var/list/readout = list("PROTECTION CLASSES (I-X)") + if(LAZYLEN(armor_list)) + readout += "\nARMOR" + for(var/dam_type in armor_list) + var/armor_amount = armor_list[dam_type] + readout += "\n[dam_type] [armor_to_protection_class(armor_amount)]" //e.g. BOMB IV + if(LAZYLEN(durability_list)) + readout += "\nDURABILITY" + for(var/dam_type in durability_list) + var/durability_amount = durability_list[dam_type] + readout += "\n[dam_type] [armor_to_protection_class(durability_amount)]" //e.g. FIRE II + readout += "" + + to_chat(usr, "[readout.Join()]") + +/** + * Rounds armor_value to nearest 10, divides it by 10 and then expresses it in roman numerals up to 10 + * + * Rounds armor_value to nearest 10, divides it by 10 + * and then expresses it in roman numerals up to 10 + * Arguments: + * * armor_value - Number we're converting + */ +/obj/item/clothing/proc/armor_to_protection_class(armor_value) + armor_value = round(armor_value,10) / 10 + switch (armor_value) + if (1) + . = "I" + if (2) + . = "II" + if (3) + . = "III" + if (4) + . = "IV" + if (5) + . = "V" + if (6) + . = "VI" + if (7) + . = "VII" + if (8) + . = "VIII" + if (9) + . = "IX" + if (10 to INFINITY) + . = "X" + return . + +/obj/item/clothing/obj_break(damage_flag) + if(!damaged_clothes) + update_clothes_damaged_state(TRUE) + if(ismob(loc)) //It's not important enough to warrant a message if nobody's wearing it + var/mob/M = loc + to_chat(M, "Your [name] starts to fall apart!") + +/obj/item/clothing/proc/update_clothes_damaged_state(damaging = TRUE) + var/index = "[REF(initial(icon))]-[initial(icon_state)]" + var/static/list/damaged_clothes_icons = list() + if(damaging) + damaged_clothes = 1 + var/icon/damaged_clothes_icon = damaged_clothes_icons[index] + if(!damaged_clothes_icon) + damaged_clothes_icon = icon(initial(icon), initial(icon_state), , 1) //we only want to apply damaged effect to the initial icon_state for each object + damaged_clothes_icon.Blend("#fff", ICON_ADD) //fills the icon_state with white (except where it's transparent) + damaged_clothes_icon.Blend(icon('icons/effects/item_damage.dmi', "itemdamaged"), ICON_MULTIPLY) //adds damage effect and the remaining white areas become transparant + damaged_clothes_icon = fcopy_rsc(damaged_clothes_icon) + damaged_clothes_icons[index] = damaged_clothes_icon + add_overlay(damaged_clothes_icon, 1) + else + damaged_clothes = 0 + cut_overlay(damaged_clothes_icons[index], TRUE) + + +/* +SEE_SELF // can see self, no matter what +SEE_MOBS // can see all mobs, no matter what +SEE_OBJS // can see all objs, no matter what +SEE_TURFS // can see all turfs (and areas), no matter what +SEE_PIXELS// if an object is located on an unlit area, but some of its pixels are + // in a lit area (via pixel_x,y or smooth movement), can see those pixels +BLIND // can't see anything +*/ + +/proc/generate_female_clothing(index,t_color,icon,type) + var/icon/female_clothing_icon = icon("icon"=icon, "icon_state"=t_color) + var/icon/female_s = icon("icon"='icons/mob/clothing/under/masking_helpers.dmi', "icon_state"="[(type == FEMALE_UNIFORM_FULL) ? "female_full" : "female_top"]") + female_clothing_icon.Blend(female_s, ICON_MULTIPLY) + female_clothing_icon = fcopy_rsc(female_clothing_icon) + GLOB.female_clothing_icons[index] = female_clothing_icon + +/obj/item/clothing/under/verb/toggle() + set name = "Adjust Suit Sensors" + set category = "Object" + set src in usr + var/mob/M = usr + if (istype(M, /mob/dead/)) + return + if (!can_use(M)) + return + if(src.has_sensor == LOCKED_SENSORS) + to_chat(usr, "The controls are locked.") + return 0 + if(src.has_sensor == BROKEN_SENSORS) + to_chat(usr, "The sensors have shorted out!") + return 0 + if(src.has_sensor <= NO_SENSORS) + to_chat(usr, "This suit does not have any sensors.") + return 0 + + var/list/modes = list("Off", "Binary vitals", "Exact vitals", "Tracking beacon") + var/switchMode = input("Select a sensor mode:", "Suit Sensor Mode", modes[sensor_mode + 1]) in modes + if(get_dist(usr, src) > 1) + to_chat(usr, "You have moved too far away!") + return + sensor_mode = modes.Find(switchMode) - 1 + + if (src.loc == usr) + switch(sensor_mode) + if(0) + to_chat(usr, "You disable your suit's remote sensing equipment.") + if(1) + to_chat(usr, "Your suit will now only report whether you are alive or dead.") + if(2) + to_chat(usr, "Your suit will now only report your exact vital lifesigns.") + if(3) + to_chat(usr, "Your suit will now report your exact vital lifesigns as well as your coordinate position.") + + if(ishuman(loc)) + var/mob/living/carbon/human/H = loc + if(H.w_uniform == src) + H.update_suit_sensors() + +/obj/item/clothing/under/attack_hand(mob/user) + if(attached_accessory && ispath(attached_accessory.pocket_storage_component_path) && loc == user) + attached_accessory.attack_hand(user) + return + ..() + +/obj/item/clothing/under/AltClick(mob/user) + if(..()) + return 1 + + if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + return + else + if(attached_accessory) + remove_accessory(user) + else + rolldown() + +/obj/item/clothing/under/verb/jumpsuit_adjust() + set name = "Adjust Jumpsuit Style" + set category = null + set src in usr + rolldown() + +/obj/item/clothing/under/proc/rolldown() + if(!can_use(usr)) + return + if(!can_adjust) + to_chat(usr, "You cannot wear this suit any differently!") + return + if(toggle_jumpsuit_adjust()) + to_chat(usr, "You adjust the suit to wear it more casually.") + else + to_chat(usr, "You adjust the suit back to normal.") + if(ishuman(usr)) + var/mob/living/carbon/human/H = usr + H.update_inv_w_uniform() + H.update_body() + +/obj/item/clothing/under/proc/toggle_jumpsuit_adjust() + if(adjusted == DIGITIGRADE_STYLE) + return + adjusted = !adjusted + if(adjusted) + if(fitted != FEMALE_UNIFORM_TOP) + fitted = NO_FEMALE_UNIFORM + if(!alt_covers_chest) // for the special snowflake suits that expose the chest when adjusted + body_parts_covered &= ~CHEST + else + fitted = initial(fitted) + if(!alt_covers_chest) + body_parts_covered |= CHEST + return adjusted + +/obj/item/clothing/proc/weldingvisortoggle(mob/user) //proc to toggle welding visors on helmets, masks, goggles, etc. + if(!can_use(user)) + return FALSE + + visor_toggling() + + to_chat(user, "You adjust \the [src] [up ? "up" : "down"].") + + if(iscarbon(user)) + var/mob/living/carbon/C = user + C.head_update(src, forced = 1) + for(var/X in actions) + var/datum/action/A = X + A.UpdateButtonIcon() + return TRUE + +/obj/item/clothing/proc/visor_toggling() //handles all the actual toggling of flags + up = !up + clothing_flags ^= visor_flags + flags_inv ^= visor_flags_inv + flags_cover ^= initial(flags_cover) + icon_state = "[initial(icon_state)][up ? "up" : ""]" + if(visor_vars_to_toggle & VISOR_FLASHPROTECT) + flash_protect ^= initial(flash_protect) + if(visor_vars_to_toggle & VISOR_TINT) + tint ^= initial(tint) + +/obj/item/clothing/head/helmet/space/plasmaman/visor_toggling() //handles all the actual toggling of flags + up = !up + clothing_flags ^= visor_flags + flags_inv ^= visor_flags_inv + icon_state = "[initial(icon_state)]" + if(visor_vars_to_toggle & VISOR_FLASHPROTECT) + flash_protect ^= initial(flash_protect) + if(visor_vars_to_toggle & VISOR_TINT) + tint ^= initial(tint) + +/obj/item/clothing/proc/can_use(mob/user) + if(user && ismob(user)) + if(!user.incapacitated()) + return 1 + return 0 + + +/obj/item/clothing/obj_destruction(damage_flag) + if(damage_flag == "bomb" || damage_flag == "melee") + var/turf/T = get_turf(src) + //so the shred survives potential turf change from the explosion. + addtimer(CALLBACK_NEW(/obj/effect/decal/cleanable/shreds, list(T, name)), 1) + deconstruct(FALSE) + else + ..() diff --git a/code/modules/clothing/glasses/_glasses.dm b/code/modules/clothing/glasses/_glasses.dm index b400a1ec1548..d640aaeffa39 100644 --- a/code/modules/clothing/glasses/_glasses.dm +++ b/code/modules/clothing/glasses/_glasses.dm @@ -1,529 +1,529 @@ -//Glasses -/obj/item/clothing/glasses - name = "glasses" - icon = 'icons/obj/clothing/glasses.dmi' - w_class = WEIGHT_CLASS_SMALL - flags_cover = GLASSESCOVERSEYES - slot_flags = ITEM_SLOT_EYES - strip_delay = 20 - equip_delay_other = 25 - resistance_flags = NONE - custom_materials = list(/datum/material/glass = 250) - var/vision_flags = 0 - var/darkness_view = 2//Base human is 2 - var/invis_view = SEE_INVISIBLE_LIVING //admin only for now - var/invis_override = 0 //Override to allow glasses to set higher than normal see_invis - var/lighting_alpha - var/list/icon/current = list() //the current hud icons - var/vision_correction = 0 //does wearing these glasses correct some of our vision defects? - var/glass_colour_type //colors your vision when worn - -/obj/item/clothing/glasses/suicide_act(mob/living/carbon/user) - user.visible_message("[user] is stabbing \the [src] into [user.p_their()] eyes! It looks like [user.p_theyre()] trying to commit suicide!") - return BRUTELOSS - -/obj/item/clothing/glasses/examine(mob/user) - . = ..() - if(glass_colour_type && ishuman(user)) - . += "Alt-click to toggle its colors." - -/obj/item/clothing/glasses/visor_toggling() - ..() - if(visor_vars_to_toggle & VISOR_VISIONFLAGS) - vision_flags ^= initial(vision_flags) - if(visor_vars_to_toggle & VISOR_DARKNESSVIEW) - darkness_view ^= initial(darkness_view) - if(visor_vars_to_toggle & VISOR_INVISVIEW) - invis_view ^= initial(invis_view) - -/obj/item/clothing/glasses/weldingvisortoggle(mob/user) - . = ..() - if(. && user) - user.update_sight() - -//called when thermal glasses are emped. -/obj/item/clothing/glasses/proc/thermal_overload() - if(ishuman(src.loc)) - var/mob/living/carbon/human/H = src.loc - var/obj/item/organ/eyes/eyes = H.getorganslot(ORGAN_SLOT_EYES) - if(!H.is_blind()) - if(H.glasses == src) - to_chat(H, "[src] overloads and blinds you!") - H.flash_act(visual = 1) - H.blind_eyes(3) - H.blur_eyes(5) - eyes.applyOrganDamage(5) - -/obj/item/clothing/glasses/meson - name = "optical meson scanner" - desc = "Used by engineering and mining staff to see basic structural and terrain layouts through walls, regardless of lighting conditions." - icon_state = "meson" - item_state = "meson" - darkness_view = 2 - vision_flags = SEE_TURFS - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE - glass_colour_type = /datum/client_colour/glass_colour/lightgreen - -/obj/item/clothing/glasses/meson/suicide_act(mob/living/carbon/user) - user.visible_message("[user] is putting \the [src] to [user.p_their()] eyes and overloading the brightness! It looks like [user.p_theyre()] trying to commit suicide!") - return BRUTELOSS - -/obj/item/clothing/glasses/meson/night - name = "night vision meson scanner" - desc = "An optical meson scanner fitted with an amplified visible light spectrum overlay, providing greater visual clarity in darkness." - icon_state = "nvgmeson" - item_state = "nvgmeson" - darkness_view = 8 - flash_protect = FLASH_PROTECTION_SENSITIVE - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE - glass_colour_type = /datum/client_colour/glass_colour/green - -/obj/item/clothing/glasses/meson/gar - name = "gar mesons" - icon_state = "garm" - item_state = "garm" - desc = "Do the impossible, see the invisible!" - force = 10 - throwforce = 10 - throw_speed = 4 - attack_verb = list("sliced") - hitsound = 'sound/weapons/bladeslice.ogg' - sharpness = IS_SHARP - -/obj/item/clothing/glasses/science - name = "science goggles" - desc = "A pair of snazzy goggles used to protect against chemical spills. Fitted with an analyzer for scanning items and reagents." - icon_state = "purple" - item_state = "glasses" - clothing_flags = SCAN_REAGENTS //You can see reagents while wearing science goggles - actions_types = list(/datum/action/item_action/toggle_research_scanner) - glass_colour_type = /datum/client_colour/glass_colour/purple - resistance_flags = ACID_PROOF - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100) - -/obj/item/clothing/glasses/science/item_action_slot_check(slot) - if(slot == ITEM_SLOT_EYES) - return 1 - -/obj/item/clothing/glasses/night - name = "night vision goggles" - desc = "You can totally see in the dark now!" - icon_state = "night" - item_state = "glasses" - darkness_view = 8 - flash_protect = FLASH_PROTECTION_SENSITIVE - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE - glass_colour_type = /datum/client_colour/glass_colour/green - -/obj/item/clothing/glasses/science/suicide_act(mob/living/carbon/user) - user.visible_message("[user] is tightening \the [src]'s straps around [user.p_their()] neck! It looks like [user.p_theyre()] trying to commit suicide!") - return OXYLOSS - -/obj/item/clothing/glasses/eyepatch - name = "eyepatch" - desc = "Yarr." - icon_state = "eyepatch" - item_state = "eyepatch" - -/obj/item/clothing/glasses/monocle - name = "monocle" - desc = "Such a dapper eyepiece!" - icon_state = "monocle" - item_state = "headset" // lol - -/obj/item/clothing/glasses/material - name = "optical material scanner" - desc = "Very confusing glasses." - icon_state = "material" - item_state = "glasses" - vision_flags = SEE_OBJS - glass_colour_type = /datum/client_colour/glass_colour/lightblue - -/obj/item/clothing/glasses/material/mining - name = "optical material scanner" - desc = "Used by miners to detect ores deep within the rock." - icon_state = "material" - item_state = "glasses" - darkness_view = 0 - -/obj/item/clothing/glasses/material/mining/gar - name = "gar material scanner" - icon_state = "garm" - item_state = "garm" - desc = "Do the impossible, see the invisible!" - force = 10 - throwforce = 20 - throw_speed = 4 - attack_verb = list("sliced") - hitsound = 'sound/weapons/bladeslice.ogg' - sharpness = IS_SHARP - glass_colour_type = /datum/client_colour/glass_colour/lightgreen - -/obj/item/clothing/glasses/regular - name = "prescription glasses" - desc = "Made by Nerd. Co." - icon_state = "glasses" - item_state = "glasses" - vision_correction = 1 //corrects nearsightedness - -/obj/item/clothing/glasses/regular/jamjar - name = "jamjar glasses" - desc = "Also known as Virginity Protectors." - icon_state = "jamjar_glasses" - item_state = "jamjar_glasses" - -/obj/item/clothing/glasses/regular/hipster - name = "prescription glasses" - desc = "Made by Uncool. Co." - icon_state = "hipster_glasses" - item_state = "hipster_glasses" - -/obj/item/clothing/glasses/regular/circle - name = "circle glasses" - desc = "Why would you wear something so controversial yet so brave?" - icon_state = "circle_glasses" - item_state = "circle_glasses" - -//Here lies green glasses, so ugly they died. RIP - -/obj/item/clothing/glasses/sunglasses - name = "sunglasses" - desc = "Strangely ancient technology used to help provide rudimentary eye cover. Enhanced shielding blocks flashes." - icon_state = "sun" - item_state = "sunglasses" - darkness_view = 1 - flash_protect = FLASH_PROTECTION_FLASH - tint = 1 - glass_colour_type = /datum/client_colour/glass_colour/gray - dog_fashion = /datum/dog_fashion/head - -/obj/item/clothing/glasses/sunglasses/reagent - name = "beer goggles" - icon_state = "sunhudbeer" - desc = "A pair of sunglasses outfitted with apparatus to scan reagents, as well as providing an innate understanding of liquid viscosity while in motion." - clothing_flags = SCAN_REAGENTS - -/obj/item/clothing/glasses/sunglasses/reagent/equipped(mob/user, slot) - . = ..() - if(ishuman(user) && slot == ITEM_SLOT_EYES) - ADD_TRAIT(user, TRAIT_BOOZE_SLIDER, CLOTHING_TRAIT) - -/obj/item/clothing/glasses/sunglasses/reagent/dropped(mob/user) - . = ..() - REMOVE_TRAIT(user, TRAIT_BOOZE_SLIDER, CLOTHING_TRAIT) - -/obj/item/clothing/glasses/sunglasses/chemical - name = "science glasses" - icon_state = "sunhudsci" - desc = "A pair of tacky purple sunglasses that allow the wearer to recognize various chemical compounds with only a glance." - clothing_flags = SCAN_REAGENTS - -/obj/item/clothing/glasses/sunglasses/garb - name = "black gar glasses" - desc = "Go beyond impossible and kick reason to the curb!" - icon_state = "garb" - item_state = "garb" - force = 10 - throwforce = 10 - throw_speed = 4 - attack_verb = list("sliced") - hitsound = 'sound/weapons/bladeslice.ogg' - sharpness = IS_SHARP - -/obj/item/clothing/glasses/sunglasses/garb/supergarb - name = "black giga gar glasses" - desc = "Believe in us humans." - icon_state = "supergarb" - item_state = "garb" - force = 12 - throwforce = 12 - -/obj/item/clothing/glasses/sunglasses/gar - name = "gar glasses" - desc = "Just who the hell do you think I am?!" - icon_state = "gar" - item_state = "gar" - force = 10 - throwforce = 10 - throw_speed = 4 - attack_verb = list("sliced") - hitsound = 'sound/weapons/bladeslice.ogg' - sharpness = IS_SHARP - glass_colour_type = /datum/client_colour/glass_colour/orange - -/obj/item/clothing/glasses/sunglasses/gar/supergar - name = "giga gar glasses" - desc = "We evolve past the person we were a minute before. Little by little we advance with each turn. That's how a drill works!" - icon_state = "supergar" - item_state = "gar" - force = 12 - throwforce = 12 - glass_colour_type = /datum/client_colour/glass_colour/red - -/obj/item/clothing/glasses/welding - name = "welding goggles" - desc = "Protects the eyes from bright flashes; approved by the mad scientist association." - icon_state = "welding-g" - item_state = "welding-g" - actions_types = list(/datum/action/item_action/toggle) - flash_protect = FLASH_PROTECTION_WELDER - custom_materials = list(/datum/material/iron = 250) - tint = 2 - visor_vars_to_toggle = VISOR_FLASHPROTECT | VISOR_TINT - flags_cover = GLASSESCOVERSEYES - glass_colour_type = /datum/client_colour/glass_colour/gray - -/obj/item/clothing/glasses/welding/attack_self(mob/user) - weldingvisortoggle(user) - - -/obj/item/clothing/glasses/blindfold - name = "blindfold" - desc = "Covers the eyes, preventing sight." - icon_state = "blindfold" - item_state = "blindfold" - flash_protect = FLASH_PROTECTION_WELDER - tint = 3 - darkness_view = 1 - dog_fashion = /datum/dog_fashion/head - -/obj/item/clothing/glasses/blindfold/equipped(mob/living/carbon/human/user, slot) - . = ..() - if(slot == ITEM_SLOT_EYES) - user.become_blind("blindfold_[REF(src)]") - -/obj/item/clothing/glasses/blindfold/dropped(mob/living/carbon/human/user) - ..() - user.cure_blind("blindfold_[REF(src)]") - -/obj/item/clothing/glasses/trickblindfold - name = "blindfold" - desc = "A see-through blindfold perfect for cheating at games like pin the stun baton on the clown." - icon_state = "trickblindfold" - item_state = "blindfold" - -/obj/item/clothing/glasses/blindfold/white - name = "blind personnel blindfold" - desc = "Indicates that the wearer suffers from blindness." - icon_state = "blindfoldwhite" - item_state = "blindfoldwhite" - var/colored_before = FALSE - -/obj/item/clothing/glasses/blindfold/white/equipped(mob/living/carbon/human/user, slot) - if(ishuman(user) && slot == ITEM_SLOT_EYES) - update_icon(user) - user.update_inv_glasses() //Color might have been changed by update_icon. - ..() - -/obj/item/clothing/glasses/blindfold/white/update_icon(mob/living/carbon/human/user) - if(ishuman(user) && !colored_before) - add_atom_colour("#[user.eye_color]", FIXED_COLOUR_PRIORITY) - colored_before = TRUE - -/obj/item/clothing/glasses/blindfold/white/worn_overlays(isinhands = FALSE, file2use) - . = list() - if(!isinhands && ishuman(loc) && !colored_before) - var/mob/living/carbon/human/H = loc - var/mutable_appearance/M = mutable_appearance('icons/mob/clothing/eyes.dmi', "blindfoldwhite") - M.appearance_flags |= RESET_COLOR - M.color = "#[H.eye_color]" - . += M - -/obj/item/clothing/glasses/sunglasses/big - desc = "Strangely ancient technology used to help provide rudimentary eye cover. Larger than average enhanced shielding blocks flashes." - icon_state = "bigsunglasses" - item_state = "bigsunglasses" - -/obj/item/clothing/glasses/thermal - name = "optical thermal scanner" - desc = "Thermals in the shape of glasses." - icon_state = "thermal" - item_state = "glasses" - vision_flags = SEE_MOBS - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE - flash_protect = FLASH_PROTECTION_SENSITIVE - glass_colour_type = /datum/client_colour/glass_colour/red - -/obj/item/clothing/glasses/thermal/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - thermal_overload() - -/obj/item/clothing/glasses/thermal/xray - name = "syndicate xray goggles" - desc = "A pair of xray goggles manufactured by the Syndicate." - vision_flags = SEE_TURFS|SEE_MOBS|SEE_OBJS - -/obj/item/clothing/glasses/thermal/syndi //These are now a traitor item, concealed as mesons. -Pete - name = "chameleon thermals" - desc = "A pair of thermal optic goggles with an onboard chameleon generator." - - var/datum/action/item_action/chameleon/change/chameleon_action - -/obj/item/clothing/glasses/thermal/syndi/Initialize() - . = ..() - chameleon_action = new(src) - chameleon_action.chameleon_type = /obj/item/clothing/glasses - chameleon_action.chameleon_name = "Glasses" - chameleon_action.chameleon_blacklist = typecacheof(/obj/item/clothing/glasses/changeling, only_root_path = TRUE) - chameleon_action.initialize_disguises() - -/obj/item/clothing/glasses/thermal/syndi/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - chameleon_action.emp_randomise() - -/obj/item/clothing/glasses/thermal/monocle - name = "thermoncle" - desc = "Never before has seeing through walls felt so gentlepersonly." - icon_state = "thermoncle" - flags_1 = null //doesn't protect eyes because it's a monocle, duh - -/obj/item/clothing/glasses/thermal/monocle/examine(mob/user) //Different examiners see a different description! - if(user.gender == MALE) - desc = replacetext(desc, "person", "man") - else if(user.gender == FEMALE) - desc = replacetext(desc, "person", "woman") - . = ..() - desc = initial(desc) - -/obj/item/clothing/glasses/thermal/eyepatch - name = "optical thermal eyepatch" - desc = "An eyepatch with built-in thermal optics." - icon_state = "eyepatch" - item_state = "eyepatch" - -/obj/item/clothing/glasses/cold - name = "cold goggles" - desc = "A pair of goggles meant for low temperatures." - icon_state = "cold" - item_state = "cold" - -/obj/item/clothing/glasses/heat - name = "heat goggles" - desc = "A pair of goggles meant for high temperatures." - icon_state = "heat" - item_state = "heat" - -/obj/item/clothing/glasses/orange - name = "orange glasses" - desc = "A sweet pair of orange shades." - icon_state = "orangeglasses" - item_state = "orangeglasses" - glass_colour_type = /datum/client_colour/glass_colour/lightorange - -/obj/item/clothing/glasses/red - name = "red glasses" - desc = "Hey, you're looking good, senpai!" - icon_state = "redglasses" - item_state = "redglasses" - glass_colour_type = /datum/client_colour/glass_colour/red - -/obj/item/clothing/glasses/godeye - name = "eye of god" - desc = "A strange eye, said to have been torn from an omniscient creature that used to roam the wastes." - icon_state = "godeye" - item_state = "godeye" - vision_flags = SEE_TURFS|SEE_MOBS|SEE_OBJS - darkness_view = 8 - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE - resistance_flags = LAVA_PROOF | FIRE_PROOF - clothing_flags = SCAN_REAGENTS - -/obj/item/clothing/glasses/godeye/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, EYE_OF_GOD_TRAIT) - -/obj/item/clothing/glasses/godeye/attackby(obj/item/W as obj, mob/user as mob, params) - if(istype(W, src) && W != src && W.loc == user) - if(W.icon_state == "godeye") - W.icon_state = "doublegodeye" - W.item_state = "doublegodeye" - W.desc = "A pair of strange eyes, said to have been torn from an omniscient creature that used to roam the wastes. There's no real reason to have two, but that isn't stopping you." - if(iscarbon(user)) - var/mob/living/carbon/C = user - C.update_inv_wear_mask() - else - to_chat(user, "The eye winks at you and vanishes into the abyss, you feel really unlucky.") - qdel(src) - ..() - -/obj/item/clothing/glasses/AltClick(mob/user) - if(glass_colour_type && ishuman(user)) - var/mob/living/carbon/human/H = user - if(H.client) - if(H.client.prefs) - if(src == H.glasses) - H.client.prefs.uses_glasses_colour = !H.client.prefs.uses_glasses_colour - if(H.client.prefs.uses_glasses_colour) - to_chat(H, "You will now see glasses colors.") - else - to_chat(H, "You will no longer see glasses colors.") - H.update_glasses_color(src, 1) - else - return ..() - -/obj/item/clothing/glasses/proc/change_glass_color(mob/living/carbon/human/H, datum/client_colour/glass_colour/new_color_type) - var/old_colour_type = glass_colour_type - if(!new_color_type || ispath(new_color_type)) //the new glass colour type must be null or a path. - glass_colour_type = new_color_type - if(H && H.glasses == src) - if(old_colour_type) - H.remove_client_colour(old_colour_type) - if(glass_colour_type) - H.update_glasses_color(src, 1) - - -/mob/living/carbon/human/proc/update_glasses_color(obj/item/clothing/glasses/G, glasses_equipped) - if(client && client.prefs.uses_glasses_colour && glasses_equipped) - add_client_colour(G.glass_colour_type) - else - remove_client_colour(G.glass_colour_type) - -/obj/item/clothing/glasses/debug - name = "debug glasses" - desc = "Medical, security and diagnostic hud. Alt click to toggle xray." - icon_state = "nvgmeson" - item_state = "nvgmeson" - flags_cover = GLASSESCOVERSEYES - darkness_view = 8 - flash_protect = FLASH_PROTECTION_WELDER - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE - glass_colour_type = FALSE - clothing_flags = SCAN_REAGENTS - vision_flags = SEE_TURFS - var/list/hudlist = list(DATA_HUD_MEDICAL_ADVANCED, DATA_HUD_DIAGNOSTIC_ADVANCED, DATA_HUD_SECURITY_ADVANCED) - var/xray = FALSE - -/obj/item/clothing/glasses/debug/equipped(mob/user, slot) - . = ..() - if(slot != ITEM_SLOT_EYES) - return - if(ishuman(user)) - for(var/hud in hudlist) - var/datum/atom_hud/H = GLOB.huds[hud] - H.add_hud_to(user) - ADD_TRAIT(user, TRAIT_MEDICAL_HUD, GLASSES_TRAIT) - ADD_TRAIT(user, TRAIT_SECURITY_HUD, GLASSES_TRAIT) - -/obj/item/clothing/glasses/debug/dropped(mob/user) - . = ..() - REMOVE_TRAIT(user, TRAIT_MEDICAL_HUD, GLASSES_TRAIT) - REMOVE_TRAIT(user, TRAIT_SECURITY_HUD, GLASSES_TRAIT) - if(ishuman(user)) - for(var/hud in hudlist) - var/datum/atom_hud/H = GLOB.huds[hud] - H.remove_hud_from(user) - -/obj/item/clothing/glasses/debug/AltClick(mob/user) - . = ..() - if(ishuman(user)) - if(xray) - vision_flags -= SEE_MOBS|SEE_OBJS - else - vision_flags += SEE_MOBS|SEE_OBJS - xray = !xray - var/mob/living/carbon/human/H = user - H.update_sight() +//Glasses +/obj/item/clothing/glasses + name = "glasses" + icon = 'icons/obj/clothing/glasses.dmi' + w_class = WEIGHT_CLASS_SMALL + flags_cover = GLASSESCOVERSEYES + slot_flags = ITEM_SLOT_EYES + strip_delay = 20 + equip_delay_other = 25 + resistance_flags = NONE + custom_materials = list(/datum/material/glass = 250) + var/vision_flags = 0 + var/darkness_view = 2//Base human is 2 + var/invis_view = SEE_INVISIBLE_LIVING //admin only for now + var/invis_override = 0 //Override to allow glasses to set higher than normal see_invis + var/lighting_alpha + var/list/icon/current = list() //the current hud icons + var/vision_correction = 0 //does wearing these glasses correct some of our vision defects? + var/glass_colour_type //colors your vision when worn + +/obj/item/clothing/glasses/suicide_act(mob/living/carbon/user) + user.visible_message("[user] is stabbing \the [src] into [user.p_their()] eyes! It looks like [user.p_theyre()] trying to commit suicide!") + return BRUTELOSS + +/obj/item/clothing/glasses/examine(mob/user) + . = ..() + if(glass_colour_type && ishuman(user)) + . += "Alt-click to toggle its colors." + +/obj/item/clothing/glasses/visor_toggling() + ..() + if(visor_vars_to_toggle & VISOR_VISIONFLAGS) + vision_flags ^= initial(vision_flags) + if(visor_vars_to_toggle & VISOR_DARKNESSVIEW) + darkness_view ^= initial(darkness_view) + if(visor_vars_to_toggle & VISOR_INVISVIEW) + invis_view ^= initial(invis_view) + +/obj/item/clothing/glasses/weldingvisortoggle(mob/user) + . = ..() + if(. && user) + user.update_sight() + +//called when thermal glasses are emped. +/obj/item/clothing/glasses/proc/thermal_overload() + if(ishuman(src.loc)) + var/mob/living/carbon/human/H = src.loc + var/obj/item/organ/eyes/eyes = H.getorganslot(ORGAN_SLOT_EYES) + if(!H.is_blind()) + if(H.glasses == src) + to_chat(H, "[src] overloads and blinds you!") + H.flash_act(visual = 1) + H.blind_eyes(3) + H.blur_eyes(5) + eyes.applyOrganDamage(5) + +/obj/item/clothing/glasses/meson + name = "optical meson scanner" + desc = "Used by engineering and mining staff to see basic structural and terrain layouts through walls, regardless of lighting conditions." + icon_state = "meson" + item_state = "meson" + darkness_view = 2 + vision_flags = SEE_TURFS + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE + glass_colour_type = /datum/client_colour/glass_colour/lightgreen + +/obj/item/clothing/glasses/meson/suicide_act(mob/living/carbon/user) + user.visible_message("[user] is putting \the [src] to [user.p_their()] eyes and overloading the brightness! It looks like [user.p_theyre()] trying to commit suicide!") + return BRUTELOSS + +/obj/item/clothing/glasses/meson/night + name = "night vision meson scanner" + desc = "An optical meson scanner fitted with an amplified visible light spectrum overlay, providing greater visual clarity in darkness." + icon_state = "nvgmeson" + item_state = "nvgmeson" + darkness_view = 8 + flash_protect = FLASH_PROTECTION_SENSITIVE + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE + glass_colour_type = /datum/client_colour/glass_colour/green + +/obj/item/clothing/glasses/meson/gar + name = "gar mesons" + icon_state = "garm" + item_state = "garm" + desc = "Do the impossible, see the invisible!" + force = 10 + throwforce = 10 + throw_speed = 4 + attack_verb = list("sliced") + hitsound = 'sound/weapons/bladeslice.ogg' + sharpness = IS_SHARP + +/obj/item/clothing/glasses/science + name = "science goggles" + desc = "A pair of snazzy goggles used to protect against chemical spills. Fitted with an analyzer for scanning items and reagents." + icon_state = "purple" + item_state = "glasses" + clothing_flags = SCAN_REAGENTS //You can see reagents while wearing science goggles + actions_types = list(/datum/action/item_action/toggle_research_scanner) + glass_colour_type = /datum/client_colour/glass_colour/purple + resistance_flags = ACID_PROOF + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100) + +/obj/item/clothing/glasses/science/item_action_slot_check(slot) + if(slot == ITEM_SLOT_EYES) + return 1 + +/obj/item/clothing/glasses/night + name = "night vision goggles" + desc = "You can totally see in the dark now!" + icon_state = "night" + item_state = "glasses" + darkness_view = 8 + flash_protect = FLASH_PROTECTION_SENSITIVE + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE + glass_colour_type = /datum/client_colour/glass_colour/green + +/obj/item/clothing/glasses/science/suicide_act(mob/living/carbon/user) + user.visible_message("[user] is tightening \the [src]'s straps around [user.p_their()] neck! It looks like [user.p_theyre()] trying to commit suicide!") + return OXYLOSS + +/obj/item/clothing/glasses/eyepatch + name = "eyepatch" + desc = "Yarr." + icon_state = "eyepatch" + item_state = "eyepatch" + +/obj/item/clothing/glasses/monocle + name = "monocle" + desc = "Such a dapper eyepiece!" + icon_state = "monocle" + item_state = "headset" // lol + +/obj/item/clothing/glasses/material + name = "optical material scanner" + desc = "Very confusing glasses." + icon_state = "material" + item_state = "glasses" + vision_flags = SEE_OBJS + glass_colour_type = /datum/client_colour/glass_colour/lightblue + +/obj/item/clothing/glasses/material/mining + name = "optical material scanner" + desc = "Used by miners to detect ores deep within the rock." + icon_state = "material" + item_state = "glasses" + darkness_view = 0 + +/obj/item/clothing/glasses/material/mining/gar + name = "gar material scanner" + icon_state = "garm" + item_state = "garm" + desc = "Do the impossible, see the invisible!" + force = 10 + throwforce = 20 + throw_speed = 4 + attack_verb = list("sliced") + hitsound = 'sound/weapons/bladeslice.ogg' + sharpness = IS_SHARP + glass_colour_type = /datum/client_colour/glass_colour/lightgreen + +/obj/item/clothing/glasses/regular + name = "prescription glasses" + desc = "Made by Nerd. Co." + icon_state = "glasses" + item_state = "glasses" + vision_correction = 1 //corrects nearsightedness + +/obj/item/clothing/glasses/regular/jamjar + name = "jamjar glasses" + desc = "Also known as Virginity Protectors." + icon_state = "jamjar_glasses" + item_state = "jamjar_glasses" + +/obj/item/clothing/glasses/regular/hipster + name = "prescription glasses" + desc = "Made by Uncool. Co." + icon_state = "hipster_glasses" + item_state = "hipster_glasses" + +/obj/item/clothing/glasses/regular/circle + name = "circle glasses" + desc = "Why would you wear something so controversial yet so brave?" + icon_state = "circle_glasses" + item_state = "circle_glasses" + +//Here lies green glasses, so ugly they died. RIP + +/obj/item/clothing/glasses/sunglasses + name = "sunglasses" + desc = "Strangely ancient technology used to help provide rudimentary eye cover. Enhanced shielding blocks flashes." + icon_state = "sun" + item_state = "sunglasses" + darkness_view = 1 + flash_protect = FLASH_PROTECTION_FLASH + tint = 1 + glass_colour_type = /datum/client_colour/glass_colour/gray + dog_fashion = /datum/dog_fashion/head + +/obj/item/clothing/glasses/sunglasses/reagent + name = "beer goggles" + icon_state = "sunhudbeer" + desc = "A pair of sunglasses outfitted with apparatus to scan reagents, as well as providing an innate understanding of liquid viscosity while in motion." + clothing_flags = SCAN_REAGENTS + +/obj/item/clothing/glasses/sunglasses/reagent/equipped(mob/user, slot) + . = ..() + if(ishuman(user) && slot == ITEM_SLOT_EYES) + ADD_TRAIT(user, TRAIT_BOOZE_SLIDER, CLOTHING_TRAIT) + +/obj/item/clothing/glasses/sunglasses/reagent/dropped(mob/user) + . = ..() + REMOVE_TRAIT(user, TRAIT_BOOZE_SLIDER, CLOTHING_TRAIT) + +/obj/item/clothing/glasses/sunglasses/chemical + name = "science glasses" + icon_state = "sunhudsci" + desc = "A pair of tacky purple sunglasses that allow the wearer to recognize various chemical compounds with only a glance." + clothing_flags = SCAN_REAGENTS + +/obj/item/clothing/glasses/sunglasses/garb + name = "black gar glasses" + desc = "Go beyond impossible and kick reason to the curb!" + icon_state = "garb" + item_state = "garb" + force = 10 + throwforce = 10 + throw_speed = 4 + attack_verb = list("sliced") + hitsound = 'sound/weapons/bladeslice.ogg' + sharpness = IS_SHARP + +/obj/item/clothing/glasses/sunglasses/garb/supergarb + name = "black giga gar glasses" + desc = "Believe in us humans." + icon_state = "supergarb" + item_state = "garb" + force = 12 + throwforce = 12 + +/obj/item/clothing/glasses/sunglasses/gar + name = "gar glasses" + desc = "Just who the hell do you think I am?!" + icon_state = "gar" + item_state = "gar" + force = 10 + throwforce = 10 + throw_speed = 4 + attack_verb = list("sliced") + hitsound = 'sound/weapons/bladeslice.ogg' + sharpness = IS_SHARP + glass_colour_type = /datum/client_colour/glass_colour/orange + +/obj/item/clothing/glasses/sunglasses/gar/supergar + name = "giga gar glasses" + desc = "We evolve past the person we were a minute before. Little by little we advance with each turn. That's how a drill works!" + icon_state = "supergar" + item_state = "gar" + force = 12 + throwforce = 12 + glass_colour_type = /datum/client_colour/glass_colour/red + +/obj/item/clothing/glasses/welding + name = "welding goggles" + desc = "Protects the eyes from bright flashes; approved by the mad scientist association." + icon_state = "welding-g" + item_state = "welding-g" + actions_types = list(/datum/action/item_action/toggle) + flash_protect = FLASH_PROTECTION_WELDER + custom_materials = list(/datum/material/iron = 250) + tint = 2 + visor_vars_to_toggle = VISOR_FLASHPROTECT | VISOR_TINT + flags_cover = GLASSESCOVERSEYES + glass_colour_type = /datum/client_colour/glass_colour/gray + +/obj/item/clothing/glasses/welding/attack_self(mob/user) + weldingvisortoggle(user) + + +/obj/item/clothing/glasses/blindfold + name = "blindfold" + desc = "Covers the eyes, preventing sight." + icon_state = "blindfold" + item_state = "blindfold" + flash_protect = FLASH_PROTECTION_WELDER + tint = 3 + darkness_view = 1 + dog_fashion = /datum/dog_fashion/head + +/obj/item/clothing/glasses/blindfold/equipped(mob/living/carbon/human/user, slot) + . = ..() + if(slot == ITEM_SLOT_EYES) + user.become_blind("blindfold_[REF(src)]") + +/obj/item/clothing/glasses/blindfold/dropped(mob/living/carbon/human/user) + ..() + user.cure_blind("blindfold_[REF(src)]") + +/obj/item/clothing/glasses/trickblindfold + name = "blindfold" + desc = "A see-through blindfold perfect for cheating at games like pin the stun baton on the clown." + icon_state = "trickblindfold" + item_state = "blindfold" + +/obj/item/clothing/glasses/blindfold/white + name = "blind personnel blindfold" + desc = "Indicates that the wearer suffers from blindness." + icon_state = "blindfoldwhite" + item_state = "blindfoldwhite" + var/colored_before = FALSE + +/obj/item/clothing/glasses/blindfold/white/equipped(mob/living/carbon/human/user, slot) + if(ishuman(user) && slot == ITEM_SLOT_EYES) + update_icon(user) + user.update_inv_glasses() //Color might have been changed by update_icon. + ..() + +/obj/item/clothing/glasses/blindfold/white/update_icon(mob/living/carbon/human/user) + if(ishuman(user) && !colored_before) + add_atom_colour("#[user.eye_color]", FIXED_COLOUR_PRIORITY) + colored_before = TRUE + +/obj/item/clothing/glasses/blindfold/white/worn_overlays(isinhands = FALSE, file2use) + . = list() + if(!isinhands && ishuman(loc) && !colored_before) + var/mob/living/carbon/human/H = loc + var/mutable_appearance/M = mutable_appearance('icons/mob/clothing/eyes.dmi', "blindfoldwhite") + M.appearance_flags |= RESET_COLOR + M.color = "#[H.eye_color]" + . += M + +/obj/item/clothing/glasses/sunglasses/big + desc = "Strangely ancient technology used to help provide rudimentary eye cover. Larger than average enhanced shielding blocks flashes." + icon_state = "bigsunglasses" + item_state = "bigsunglasses" + +/obj/item/clothing/glasses/thermal + name = "optical thermal scanner" + desc = "Thermals in the shape of glasses." + icon_state = "thermal" + item_state = "glasses" + vision_flags = SEE_MOBS + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE + flash_protect = FLASH_PROTECTION_SENSITIVE + glass_colour_type = /datum/client_colour/glass_colour/red + +/obj/item/clothing/glasses/thermal/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + thermal_overload() + +/obj/item/clothing/glasses/thermal/xray + name = "syndicate xray goggles" + desc = "A pair of xray goggles manufactured by the Syndicate." + vision_flags = SEE_TURFS|SEE_MOBS|SEE_OBJS + +/obj/item/clothing/glasses/thermal/syndi //These are now a traitor item, concealed as mesons. -Pete + name = "chameleon thermals" + desc = "A pair of thermal optic goggles with an onboard chameleon generator." + + var/datum/action/item_action/chameleon/change/chameleon_action + +/obj/item/clothing/glasses/thermal/syndi/Initialize() + . = ..() + chameleon_action = new(src) + chameleon_action.chameleon_type = /obj/item/clothing/glasses + chameleon_action.chameleon_name = "Glasses" + chameleon_action.chameleon_blacklist = typecacheof(/obj/item/clothing/glasses/changeling, only_root_path = TRUE) + chameleon_action.initialize_disguises() + +/obj/item/clothing/glasses/thermal/syndi/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + chameleon_action.emp_randomise() + +/obj/item/clothing/glasses/thermal/monocle + name = "thermoncle" + desc = "Never before has seeing through walls felt so gentlepersonly." + icon_state = "thermoncle" + flags_1 = null //doesn't protect eyes because it's a monocle, duh + +/obj/item/clothing/glasses/thermal/monocle/examine(mob/user) //Different examiners see a different description! + if(user.gender == MALE) + desc = replacetext(desc, "person", "man") + else if(user.gender == FEMALE) + desc = replacetext(desc, "person", "woman") + . = ..() + desc = initial(desc) + +/obj/item/clothing/glasses/thermal/eyepatch + name = "optical thermal eyepatch" + desc = "An eyepatch with built-in thermal optics." + icon_state = "eyepatch" + item_state = "eyepatch" + +/obj/item/clothing/glasses/cold + name = "cold goggles" + desc = "A pair of goggles meant for low temperatures." + icon_state = "cold" + item_state = "cold" + +/obj/item/clothing/glasses/heat + name = "heat goggles" + desc = "A pair of goggles meant for high temperatures." + icon_state = "heat" + item_state = "heat" + +/obj/item/clothing/glasses/orange + name = "orange glasses" + desc = "A sweet pair of orange shades." + icon_state = "orangeglasses" + item_state = "orangeglasses" + glass_colour_type = /datum/client_colour/glass_colour/lightorange + +/obj/item/clothing/glasses/red + name = "red glasses" + desc = "Hey, you're looking good, senpai!" + icon_state = "redglasses" + item_state = "redglasses" + glass_colour_type = /datum/client_colour/glass_colour/red + +/obj/item/clothing/glasses/godeye + name = "eye of god" + desc = "A strange eye, said to have been torn from an omniscient creature that used to roam the wastes." + icon_state = "godeye" + item_state = "godeye" + vision_flags = SEE_TURFS|SEE_MOBS|SEE_OBJS + darkness_view = 8 + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE + resistance_flags = LAVA_PROOF | FIRE_PROOF + clothing_flags = SCAN_REAGENTS + +/obj/item/clothing/glasses/godeye/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, EYE_OF_GOD_TRAIT) + +/obj/item/clothing/glasses/godeye/attackby(obj/item/W as obj, mob/user as mob, params) + if(istype(W, src) && W != src && W.loc == user) + if(W.icon_state == "godeye") + W.icon_state = "doublegodeye" + W.item_state = "doublegodeye" + W.desc = "A pair of strange eyes, said to have been torn from an omniscient creature that used to roam the wastes. There's no real reason to have two, but that isn't stopping you." + if(iscarbon(user)) + var/mob/living/carbon/C = user + C.update_inv_wear_mask() + else + to_chat(user, "The eye winks at you and vanishes into the abyss, you feel really unlucky.") + qdel(src) + ..() + +/obj/item/clothing/glasses/AltClick(mob/user) + if(glass_colour_type && ishuman(user)) + var/mob/living/carbon/human/H = user + if(H.client) + if(H.client.prefs) + if(src == H.glasses) + H.client.prefs.uses_glasses_colour = !H.client.prefs.uses_glasses_colour + if(H.client.prefs.uses_glasses_colour) + to_chat(H, "You will now see glasses colors.") + else + to_chat(H, "You will no longer see glasses colors.") + H.update_glasses_color(src, 1) + else + return ..() + +/obj/item/clothing/glasses/proc/change_glass_color(mob/living/carbon/human/H, datum/client_colour/glass_colour/new_color_type) + var/old_colour_type = glass_colour_type + if(!new_color_type || ispath(new_color_type)) //the new glass colour type must be null or a path. + glass_colour_type = new_color_type + if(H && H.glasses == src) + if(old_colour_type) + H.remove_client_colour(old_colour_type) + if(glass_colour_type) + H.update_glasses_color(src, 1) + + +/mob/living/carbon/human/proc/update_glasses_color(obj/item/clothing/glasses/G, glasses_equipped) + if(client && client.prefs.uses_glasses_colour && glasses_equipped) + add_client_colour(G.glass_colour_type) + else + remove_client_colour(G.glass_colour_type) + +/obj/item/clothing/glasses/debug + name = "debug glasses" + desc = "Medical, security and diagnostic hud. Alt click to toggle xray." + icon_state = "nvgmeson" + item_state = "nvgmeson" + flags_cover = GLASSESCOVERSEYES + darkness_view = 8 + flash_protect = FLASH_PROTECTION_WELDER + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE + glass_colour_type = FALSE + clothing_flags = SCAN_REAGENTS + vision_flags = SEE_TURFS + var/list/hudlist = list(DATA_HUD_MEDICAL_ADVANCED, DATA_HUD_DIAGNOSTIC_ADVANCED, DATA_HUD_SECURITY_ADVANCED) + var/xray = FALSE + +/obj/item/clothing/glasses/debug/equipped(mob/user, slot) + . = ..() + if(slot != ITEM_SLOT_EYES) + return + if(ishuman(user)) + for(var/hud in hudlist) + var/datum/atom_hud/H = GLOB.huds[hud] + H.add_hud_to(user) + ADD_TRAIT(user, TRAIT_MEDICAL_HUD, GLASSES_TRAIT) + ADD_TRAIT(user, TRAIT_SECURITY_HUD, GLASSES_TRAIT) + +/obj/item/clothing/glasses/debug/dropped(mob/user) + . = ..() + REMOVE_TRAIT(user, TRAIT_MEDICAL_HUD, GLASSES_TRAIT) + REMOVE_TRAIT(user, TRAIT_SECURITY_HUD, GLASSES_TRAIT) + if(ishuman(user)) + for(var/hud in hudlist) + var/datum/atom_hud/H = GLOB.huds[hud] + H.remove_hud_from(user) + +/obj/item/clothing/glasses/debug/AltClick(mob/user) + . = ..() + if(ishuman(user)) + if(xray) + vision_flags -= SEE_MOBS|SEE_OBJS + else + vision_flags += SEE_MOBS|SEE_OBJS + xray = !xray + var/mob/living/carbon/human/H = user + H.update_sight() diff --git a/code/modules/clothing/glasses/hud.dm b/code/modules/clothing/glasses/hud.dm index 683abef2a3a0..781a0261be47 100644 --- a/code/modules/clothing/glasses/hud.dm +++ b/code/modules/clothing/glasses/hud.dm @@ -1,244 +1,244 @@ -/obj/item/clothing/glasses/hud - name = "HUD" - desc = "A heads-up display that provides important info in (almost) real time." - flags_1 = null //doesn't protect eyes because it's a monocle, duh - var/hud_type = null - ///Used for topic calls. Just because you have a HUD display doesn't mean you should be able to interact with stuff. - var/hud_trait = null - - -/obj/item/clothing/glasses/hud/equipped(mob/living/carbon/human/user, slot) - ..() - if(slot != ITEM_SLOT_EYES) - return - if(hud_type) - var/datum/atom_hud/H = GLOB.huds[hud_type] - H.add_hud_to(user) - if(hud_trait) - ADD_TRAIT(user, hud_trait, GLASSES_TRAIT) - -/obj/item/clothing/glasses/hud/dropped(mob/living/carbon/human/user) - ..() - if(!istype(user) || user.glasses != src) - return - if(hud_type) - var/datum/atom_hud/H = GLOB.huds[hud_type] - H.remove_hud_from(user) - if(hud_trait) - REMOVE_TRAIT(user, hud_trait, GLASSES_TRAIT) - -/obj/item/clothing/glasses/hud/emp_act(severity) - . = ..() - if(obj_flags & EMAGGED || . & EMP_PROTECT_SELF) - return - obj_flags |= EMAGGED - desc = "[desc] The display is flickering slightly." - -/obj/item/clothing/glasses/hud/emag_act(mob/user) - if(obj_flags & EMAGGED) - return - obj_flags |= EMAGGED - to_chat(user, "PZZTTPFFFT") - desc = "[desc] The display is flickering slightly." - -/obj/item/clothing/glasses/hud/health - name = "health scanner HUD" - desc = "A heads-up display that scans the humanoids in view and provides accurate data about their health status." - icon_state = "healthhud" - hud_type = DATA_HUD_MEDICAL_ADVANCED - hud_trait = TRAIT_MEDICAL_HUD - glass_colour_type = /datum/client_colour/glass_colour/lightblue - -/obj/item/clothing/glasses/hud/health/night - name = "night vision health scanner HUD" - desc = "An advanced medical head-up display that allows doctors to find patients in complete darkness." - icon_state = "healthhudnight" - item_state = "glasses" - darkness_view = 8 - flash_protect = FLASH_PROTECTION_SENSITIVE - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE - glass_colour_type = /datum/client_colour/glass_colour/green - -/obj/item/clothing/glasses/hud/health/sunglasses - name = "medical HUDSunglasses" - desc = "Sunglasses with a medical HUD." - icon_state = "sunhudmed" - darkness_view = 1 - flash_protect = FLASH_PROTECTION_FLASH - tint = 1 - glass_colour_type = /datum/client_colour/glass_colour/blue - -/obj/item/clothing/glasses/hud/diagnostic - name = "diagnostic HUD" - desc = "A heads-up display capable of analyzing the integrity and status of robotics and exosuits." - icon_state = "diagnostichud" - hud_type = DATA_HUD_DIAGNOSTIC_BASIC - hud_trait = TRAIT_DIAGNOSTIC_HUD - glass_colour_type = /datum/client_colour/glass_colour/lightorange - -/obj/item/clothing/glasses/hud/diagnostic/night - name = "night vision diagnostic HUD" - desc = "A robotics diagnostic HUD fitted with a light amplifier." - icon_state = "diagnostichudnight" - item_state = "glasses" - darkness_view = 8 - flash_protect = FLASH_PROTECTION_SENSITIVE - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE - glass_colour_type = /datum/client_colour/glass_colour/green - -/obj/item/clothing/glasses/hud/diagnostic/sunglasses - name = "diagnostic sunglasses" - desc = "Sunglasses with a diagnostic HUD." - icon_state = "sunhuddiag" - item_state = "glasses" - flash_protect = FLASH_PROTECTION_FLASH - tint = 1 - -/obj/item/clothing/glasses/hud/security - name = "security HUD" - desc = "A heads-up display that scans the humanoids in view and provides accurate data about their ID status and security records." - icon_state = "securityhud" - hud_type = DATA_HUD_SECURITY_ADVANCED - hud_trait = TRAIT_SECURITY_HUD - glass_colour_type = /datum/client_colour/glass_colour/red - -/obj/item/clothing/glasses/hud/security/chameleon - name = "chameleon security HUD" - desc = "A stolen security HUD integrated with Syndicate chameleon technology. Provides flash protection." - flash_protect = FLASH_PROTECTION_FLASH - - // Yes this code is the same as normal chameleon glasses, but we don't - // have multiple inheritance, okay? - var/datum/action/item_action/chameleon/change/chameleon_action - -/obj/item/clothing/glasses/hud/security/chameleon/Initialize() - . = ..() - chameleon_action = new(src) - chameleon_action.chameleon_type = /obj/item/clothing/glasses - chameleon_action.chameleon_name = "Glasses" - chameleon_action.chameleon_blacklist = typecacheof(/obj/item/clothing/glasses/changeling, only_root_path = TRUE) - chameleon_action.initialize_disguises() - -/obj/item/clothing/glasses/hud/security/chameleon/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - chameleon_action.emp_randomise() - - -/obj/item/clothing/glasses/hud/security/sunglasses/eyepatch - name = "eyepatch HUD" - desc = "A heads-up display that connects directly to the optical nerve of the user, replacing the need for that useless eyeball." - icon_state = "hudpatch" - -/obj/item/clothing/glasses/hud/security/sunglasses - name = "security HUDSunglasses" - desc = "Sunglasses with a security HUD." - icon_state = "sunhudsec" - darkness_view = 1 - flash_protect = FLASH_PROTECTION_FLASH - tint = 1 - glass_colour_type = /datum/client_colour/glass_colour/darkred - -/obj/item/clothing/glasses/hud/security/night - name = "night vision security HUD" - desc = "An advanced heads-up display that provides ID data and vision in complete darkness." - icon_state = "securityhudnight" - darkness_view = 8 - flash_protect = FLASH_PROTECTION_SENSITIVE - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE - glass_colour_type = /datum/client_colour/glass_colour/green - -/obj/item/clothing/glasses/hud/security/sunglasses/gars - name = "\improper HUD gar glasses" - desc = "GAR glasses with a HUD." - icon_state = "gars" - item_state = "garb" - force = 10 - throwforce = 10 - throw_speed = 4 - attack_verb = list("sliced") - hitsound = 'sound/weapons/bladeslice.ogg' - sharpness = IS_SHARP - -/obj/item/clothing/glasses/hud/security/sunglasses/gars/supergars - name = "giga HUD gar glasses" - desc = "GIGA GAR glasses with a HUD." - icon_state = "supergars" - item_state = "garb" - force = 12 - throwforce = 12 - -/obj/item/clothing/glasses/hud/toggle - name = "Toggle HUD" - desc = "A hud with multiple functions." - actions_types = list(/datum/action/item_action/switch_hud) - -/obj/item/clothing/glasses/hud/toggle/attack_self(mob/user) - if(!ishuman(user)) - return - var/mob/living/carbon/human/wearer = user - if (wearer.glasses != src) - return - - if (hud_type) - var/datum/atom_hud/H = GLOB.huds[hud_type] - H.remove_hud_from(user) - - if (hud_type == DATA_HUD_MEDICAL_ADVANCED) - hud_type = null - else if (hud_type == DATA_HUD_SECURITY_ADVANCED) - hud_type = DATA_HUD_MEDICAL_ADVANCED - else - hud_type = DATA_HUD_SECURITY_ADVANCED - - if (hud_type) - var/datum/atom_hud/H = GLOB.huds[hud_type] - H.add_hud_to(user) - -/obj/item/clothing/glasses/hud/toggle/thermal - name = "thermal HUD scanner" - desc = "Thermal imaging HUD in the shape of glasses." - icon_state = "thermal" - hud_type = DATA_HUD_SECURITY_ADVANCED - vision_flags = SEE_MOBS - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE - glass_colour_type = /datum/client_colour/glass_colour/red - -/obj/item/clothing/glasses/hud/toggle/thermal/attack_self(mob/user) - ..() - switch (hud_type) - if (DATA_HUD_MEDICAL_ADVANCED) - icon_state = "meson" - change_glass_color(user, /datum/client_colour/glass_colour/green) - if (DATA_HUD_SECURITY_ADVANCED) - icon_state = "thermal" - change_glass_color(user, /datum/client_colour/glass_colour/red) - else - icon_state = "purple" - change_glass_color(user, /datum/client_colour/glass_colour/purple) - user.update_inv_glasses() - -/obj/item/clothing/glasses/hud/toggle/thermal/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - thermal_overload() - -/obj/item/clothing/glasses/hud/spacecop - name = "police aviators" - desc = "For thinking you look cool while brutalizing protestors and minorities." - icon_state = "bigsunglasses" - hud_type = ANTAG_HUD_GANGSTER - darkness_view = 1 - flash_protect = FLASH_PROTECTION_FLASH - tint = 1 - glass_colour_type = /datum/client_colour/glass_colour/gray - - -/obj/item/clothing/glasses/hud/spacecop/hidden // for the undercover cop - name = "sunglasses" - desc = "These sunglasses are special, and let you view potential criminals." - icon_state = "sun" - item_state = "sunglasses" - +/obj/item/clothing/glasses/hud + name = "HUD" + desc = "A heads-up display that provides important info in (almost) real time." + flags_1 = null //doesn't protect eyes because it's a monocle, duh + var/hud_type = null + ///Used for topic calls. Just because you have a HUD display doesn't mean you should be able to interact with stuff. + var/hud_trait = null + + +/obj/item/clothing/glasses/hud/equipped(mob/living/carbon/human/user, slot) + ..() + if(slot != ITEM_SLOT_EYES) + return + if(hud_type) + var/datum/atom_hud/H = GLOB.huds[hud_type] + H.add_hud_to(user) + if(hud_trait) + ADD_TRAIT(user, hud_trait, GLASSES_TRAIT) + +/obj/item/clothing/glasses/hud/dropped(mob/living/carbon/human/user) + ..() + if(!istype(user) || user.glasses != src) + return + if(hud_type) + var/datum/atom_hud/H = GLOB.huds[hud_type] + H.remove_hud_from(user) + if(hud_trait) + REMOVE_TRAIT(user, hud_trait, GLASSES_TRAIT) + +/obj/item/clothing/glasses/hud/emp_act(severity) + . = ..() + if(obj_flags & EMAGGED || . & EMP_PROTECT_SELF) + return + obj_flags |= EMAGGED + desc = "[desc] The display is flickering slightly." + +/obj/item/clothing/glasses/hud/emag_act(mob/user) + if(obj_flags & EMAGGED) + return + obj_flags |= EMAGGED + to_chat(user, "PZZTTPFFFT") + desc = "[desc] The display is flickering slightly." + +/obj/item/clothing/glasses/hud/health + name = "health scanner HUD" + desc = "A heads-up display that scans the humanoids in view and provides accurate data about their health status." + icon_state = "healthhud" + hud_type = DATA_HUD_MEDICAL_ADVANCED + hud_trait = TRAIT_MEDICAL_HUD + glass_colour_type = /datum/client_colour/glass_colour/lightblue + +/obj/item/clothing/glasses/hud/health/night + name = "night vision health scanner HUD" + desc = "An advanced medical head-up display that allows doctors to find patients in complete darkness." + icon_state = "healthhudnight" + item_state = "glasses" + darkness_view = 8 + flash_protect = FLASH_PROTECTION_SENSITIVE + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE + glass_colour_type = /datum/client_colour/glass_colour/green + +/obj/item/clothing/glasses/hud/health/sunglasses + name = "medical HUDSunglasses" + desc = "Sunglasses with a medical HUD." + icon_state = "sunhudmed" + darkness_view = 1 + flash_protect = FLASH_PROTECTION_FLASH + tint = 1 + glass_colour_type = /datum/client_colour/glass_colour/blue + +/obj/item/clothing/glasses/hud/diagnostic + name = "diagnostic HUD" + desc = "A heads-up display capable of analyzing the integrity and status of robotics and exosuits." + icon_state = "diagnostichud" + hud_type = DATA_HUD_DIAGNOSTIC_BASIC + hud_trait = TRAIT_DIAGNOSTIC_HUD + glass_colour_type = /datum/client_colour/glass_colour/lightorange + +/obj/item/clothing/glasses/hud/diagnostic/night + name = "night vision diagnostic HUD" + desc = "A robotics diagnostic HUD fitted with a light amplifier." + icon_state = "diagnostichudnight" + item_state = "glasses" + darkness_view = 8 + flash_protect = FLASH_PROTECTION_SENSITIVE + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE + glass_colour_type = /datum/client_colour/glass_colour/green + +/obj/item/clothing/glasses/hud/diagnostic/sunglasses + name = "diagnostic sunglasses" + desc = "Sunglasses with a diagnostic HUD." + icon_state = "sunhuddiag" + item_state = "glasses" + flash_protect = FLASH_PROTECTION_FLASH + tint = 1 + +/obj/item/clothing/glasses/hud/security + name = "security HUD" + desc = "A heads-up display that scans the humanoids in view and provides accurate data about their ID status and security records." + icon_state = "securityhud" + hud_type = DATA_HUD_SECURITY_ADVANCED + hud_trait = TRAIT_SECURITY_HUD + glass_colour_type = /datum/client_colour/glass_colour/red + +/obj/item/clothing/glasses/hud/security/chameleon + name = "chameleon security HUD" + desc = "A stolen security HUD integrated with Syndicate chameleon technology. Provides flash protection." + flash_protect = FLASH_PROTECTION_FLASH + + // Yes this code is the same as normal chameleon glasses, but we don't + // have multiple inheritance, okay? + var/datum/action/item_action/chameleon/change/chameleon_action + +/obj/item/clothing/glasses/hud/security/chameleon/Initialize() + . = ..() + chameleon_action = new(src) + chameleon_action.chameleon_type = /obj/item/clothing/glasses + chameleon_action.chameleon_name = "Glasses" + chameleon_action.chameleon_blacklist = typecacheof(/obj/item/clothing/glasses/changeling, only_root_path = TRUE) + chameleon_action.initialize_disguises() + +/obj/item/clothing/glasses/hud/security/chameleon/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + chameleon_action.emp_randomise() + + +/obj/item/clothing/glasses/hud/security/sunglasses/eyepatch + name = "eyepatch HUD" + desc = "A heads-up display that connects directly to the optical nerve of the user, replacing the need for that useless eyeball." + icon_state = "hudpatch" + +/obj/item/clothing/glasses/hud/security/sunglasses + name = "security HUDSunglasses" + desc = "Sunglasses with a security HUD." + icon_state = "sunhudsec" + darkness_view = 1 + flash_protect = FLASH_PROTECTION_FLASH + tint = 1 + glass_colour_type = /datum/client_colour/glass_colour/darkred + +/obj/item/clothing/glasses/hud/security/night + name = "night vision security HUD" + desc = "An advanced heads-up display that provides ID data and vision in complete darkness." + icon_state = "securityhudnight" + darkness_view = 8 + flash_protect = FLASH_PROTECTION_SENSITIVE + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE + glass_colour_type = /datum/client_colour/glass_colour/green + +/obj/item/clothing/glasses/hud/security/sunglasses/gars + name = "\improper HUD gar glasses" + desc = "GAR glasses with a HUD." + icon_state = "gars" + item_state = "garb" + force = 10 + throwforce = 10 + throw_speed = 4 + attack_verb = list("sliced") + hitsound = 'sound/weapons/bladeslice.ogg' + sharpness = IS_SHARP + +/obj/item/clothing/glasses/hud/security/sunglasses/gars/supergars + name = "giga HUD gar glasses" + desc = "GIGA GAR glasses with a HUD." + icon_state = "supergars" + item_state = "garb" + force = 12 + throwforce = 12 + +/obj/item/clothing/glasses/hud/toggle + name = "Toggle HUD" + desc = "A hud with multiple functions." + actions_types = list(/datum/action/item_action/switch_hud) + +/obj/item/clothing/glasses/hud/toggle/attack_self(mob/user) + if(!ishuman(user)) + return + var/mob/living/carbon/human/wearer = user + if (wearer.glasses != src) + return + + if (hud_type) + var/datum/atom_hud/H = GLOB.huds[hud_type] + H.remove_hud_from(user) + + if (hud_type == DATA_HUD_MEDICAL_ADVANCED) + hud_type = null + else if (hud_type == DATA_HUD_SECURITY_ADVANCED) + hud_type = DATA_HUD_MEDICAL_ADVANCED + else + hud_type = DATA_HUD_SECURITY_ADVANCED + + if (hud_type) + var/datum/atom_hud/H = GLOB.huds[hud_type] + H.add_hud_to(user) + +/obj/item/clothing/glasses/hud/toggle/thermal + name = "thermal HUD scanner" + desc = "Thermal imaging HUD in the shape of glasses." + icon_state = "thermal" + hud_type = DATA_HUD_SECURITY_ADVANCED + vision_flags = SEE_MOBS + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE + glass_colour_type = /datum/client_colour/glass_colour/red + +/obj/item/clothing/glasses/hud/toggle/thermal/attack_self(mob/user) + ..() + switch (hud_type) + if (DATA_HUD_MEDICAL_ADVANCED) + icon_state = "meson" + change_glass_color(user, /datum/client_colour/glass_colour/green) + if (DATA_HUD_SECURITY_ADVANCED) + icon_state = "thermal" + change_glass_color(user, /datum/client_colour/glass_colour/red) + else + icon_state = "purple" + change_glass_color(user, /datum/client_colour/glass_colour/purple) + user.update_inv_glasses() + +/obj/item/clothing/glasses/hud/toggle/thermal/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + thermal_overload() + +/obj/item/clothing/glasses/hud/spacecop + name = "police aviators" + desc = "For thinking you look cool while brutalizing protestors and minorities." + icon_state = "bigsunglasses" + hud_type = ANTAG_HUD_GANGSTER + darkness_view = 1 + flash_protect = FLASH_PROTECTION_FLASH + tint = 1 + glass_colour_type = /datum/client_colour/glass_colour/gray + + +/obj/item/clothing/glasses/hud/spacecop/hidden // for the undercover cop + name = "sunglasses" + desc = "These sunglasses are special, and let you view potential criminals." + icon_state = "sun" + item_state = "sunglasses" + diff --git a/code/modules/clothing/gloves/_gloves.dm b/code/modules/clothing/gloves/_gloves.dm index 447c477005e5..414311e79d21 100644 --- a/code/modules/clothing/gloves/_gloves.dm +++ b/code/modules/clothing/gloves/_gloves.dm @@ -1,45 +1,45 @@ -/obj/item/clothing/gloves - name = "gloves" - gender = PLURAL //Carn: for grammarically correct text-parsing - w_class = WEIGHT_CLASS_SMALL - icon = 'icons/obj/clothing/gloves.dmi' - siemens_coefficient = 0.5 - body_parts_covered = HANDS - slot_flags = ITEM_SLOT_GLOVES - attack_verb = list("challenged") - var/transfer_prints = FALSE - strip_delay = 20 - equip_delay_other = 40 - cuttable = TRUE - clothamnt = 2 - -/obj/item/clothing/gloves/ComponentInitialize() - . = ..() - RegisterSignal(src, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_blood) - -/obj/item/clothing/gloves/proc/clean_blood(datum/source, strength) - if(strength < CLEAN_STRENGTH_BLOOD) - return - transfer_blood = 0 - -/obj/item/clothing/gloves/suicide_act(mob/living/carbon/user) - user.visible_message("\the [src] are forcing [user]'s hands around [user.p_their()] neck! It looks like the gloves are possessed!") - return OXYLOSS - -/obj/item/clothing/gloves/worn_overlays(isinhands = FALSE) - . = list() - if(!isinhands) - if(damaged_clothes) - . += mutable_appearance('icons/effects/item_damage.dmi', "damagedgloves") - if(HAS_BLOOD_DNA(src)) - . += mutable_appearance('icons/effects/blood.dmi', "bloodyhands") - -/obj/item/clothing/gloves/update_clothes_damaged_state(damaging = TRUE) - ..() - if(ismob(loc)) - var/mob/M = loc - M.update_inv_gloves() - -// Called just before an attack_hand(), in mob/UnarmedAttack() -/obj/item/clothing/gloves/proc/Touch(atom/A, proximity) - return 0 // return 1 to cancel attack_hand() +/obj/item/clothing/gloves + name = "gloves" + gender = PLURAL //Carn: for grammarically correct text-parsing + w_class = WEIGHT_CLASS_SMALL + icon = 'icons/obj/clothing/gloves.dmi' + siemens_coefficient = 0.5 + body_parts_covered = HANDS + slot_flags = ITEM_SLOT_GLOVES + attack_verb = list("challenged") + var/transfer_prints = FALSE + strip_delay = 20 + equip_delay_other = 40 + cuttable = TRUE + clothamnt = 2 + +/obj/item/clothing/gloves/ComponentInitialize() + . = ..() + RegisterSignal(src, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_blood) + +/obj/item/clothing/gloves/proc/clean_blood(datum/source, strength) + if(strength < CLEAN_STRENGTH_BLOOD) + return + transfer_blood = 0 + +/obj/item/clothing/gloves/suicide_act(mob/living/carbon/user) + user.visible_message("\the [src] are forcing [user]'s hands around [user.p_their()] neck! It looks like the gloves are possessed!") + return OXYLOSS + +/obj/item/clothing/gloves/worn_overlays(isinhands = FALSE) + . = list() + if(!isinhands) + if(damaged_clothes) + . += mutable_appearance('icons/effects/item_damage.dmi', "damagedgloves") + if(HAS_BLOOD_DNA(src)) + . += mutable_appearance('icons/effects/blood.dmi', "bloodyhands") + +/obj/item/clothing/gloves/update_clothes_damaged_state(damaging = TRUE) + ..() + if(ismob(loc)) + var/mob/M = loc + M.update_inv_gloves() + +// Called just before an attack_hand(), in mob/UnarmedAttack() +/obj/item/clothing/gloves/proc/Touch(atom/A, proximity) + return 0 // return 1 to cancel attack_hand() diff --git a/code/modules/clothing/gloves/boxing.dm b/code/modules/clothing/gloves/boxing.dm index f1016b0cc128..a76ff7c5834d 100644 --- a/code/modules/clothing/gloves/boxing.dm +++ b/code/modules/clothing/gloves/boxing.dm @@ -1,19 +1,19 @@ -/obj/item/clothing/gloves/boxing - name = "boxing gloves" - desc = "Because you really needed another excuse to punch your crewmates." - icon_state = "boxing" - item_state = "boxing" - equip_delay_other = 60 - species_exception = list(/datum/species/golem) // now you too can be a golem boxing champion - -/obj/item/clothing/gloves/boxing/green - icon_state = "boxinggreen" - item_state = "boxinggreen" - -/obj/item/clothing/gloves/boxing/blue - icon_state = "boxingblue" - item_state = "boxingblue" - -/obj/item/clothing/gloves/boxing/yellow - icon_state = "boxingyellow" - item_state = "boxingyellow" +/obj/item/clothing/gloves/boxing + name = "boxing gloves" + desc = "Because you really needed another excuse to punch your crewmates." + icon_state = "boxing" + item_state = "boxing" + equip_delay_other = 60 + species_exception = list(/datum/species/golem) // now you too can be a golem boxing champion + +/obj/item/clothing/gloves/boxing/green + icon_state = "boxinggreen" + item_state = "boxinggreen" + +/obj/item/clothing/gloves/boxing/blue + icon_state = "boxingblue" + item_state = "boxingblue" + +/obj/item/clothing/gloves/boxing/yellow + icon_state = "boxingyellow" + item_state = "boxingyellow" diff --git a/code/modules/clothing/gloves/color.dm b/code/modules/clothing/gloves/color.dm index 80a95da74919..37d203731bfd 100644 --- a/code/modules/clothing/gloves/color.dm +++ b/code/modules/clothing/gloves/color.dm @@ -1,252 +1,252 @@ -/obj/item/clothing/gloves/color - dying_key = DYE_REGISTRY_GLOVES - -/obj/item/clothing/gloves/color/yellow - desc = "These gloves provide protection against electric shock." - name = "insulated gloves" - icon_state = "yellow" - item_state = "ygloves" - siemens_coefficient = 0 - permeability_coefficient = 0.05 - resistance_flags = NONE - custom_price = 1200 - custom_premium_price = 1200 - -/obj/item/toy/sprayoncan - name = "spray-on insulation applicator" - desc = "What is the number one problem facing our station today?" - icon = 'icons/obj/clothing/gloves.dmi' - icon_state = "sprayoncan" - -/obj/item/toy/sprayoncan/afterattack(atom/target, mob/living/carbon/user, proximity) - if(iscarbon(target) && proximity) - var/mob/living/carbon/C = target - var/mob/living/carbon/U = user - var/success = C.equip_to_slot_if_possible(new /obj/item/clothing/gloves/color/yellow/sprayon, ITEM_SLOT_GLOVES, TRUE, TRUE) - if(success) - if(C == user) - C.visible_message("[U] sprays their hands with glittery rubber!") - else - C.visible_message("[U] sprays glittery rubber on the hands of [C]!") - else - C.visible_message("The rubber fails to stick to [C]'s hands!") - - qdel(src) - -/obj/item/clothing/gloves/color/yellow/sprayon - desc = "How're you gonna get 'em off, nerd?" - name = "spray-on insulated gloves" - icon_state = "sprayon" - item_state = "sprayon" - permeability_coefficient = 0 - resistance_flags = ACID_PROOF - var/shocks_remaining = 10 - -/obj/item/clothing/gloves/color/yellow/sprayon/Initialize() - .=..() - ADD_TRAIT(src, TRAIT_NODROP, CLOTHING_TRAIT) - -/obj/item/clothing/gloves/color/yellow/sprayon/equipped(mob/user, slot) - . = ..() - RegisterSignal(user, COMSIG_LIVING_SHOCK_PREVENTED, .proc/Shocked) - -/obj/item/clothing/gloves/color/yellow/sprayon/proc/Shocked() - shocks_remaining-- - if(shocks_remaining < 0) - qdel(src) //if we run out of uses, the gloves crumble away into nothing, just like my dreams after working with .dm - -/obj/item/clothing/gloves/color/yellow/sprayon/dropped() - .=..() - qdel(src) //loose nodrop items bad - -/obj/item/clothing/gloves/color/fyellow //Cheap Chinese Crap - desc = "These gloves are cheap knockoffs of the coveted ones - no way this can end badly." - name = "budget insulated gloves" - icon_state = "yellow" - item_state = "ygloves" - siemens_coefficient = 1 //Set to a default of 1, gets overridden in Initialize() - permeability_coefficient = 0.05 - resistance_flags = NONE - -/obj/item/clothing/gloves/color/fyellow/Initialize() - . = ..() - siemens_coefficient = pick(0,0.5,0.5,0.5,0.5,0.75,1.5) - -/obj/item/clothing/gloves/color/fyellow/old - desc = "Old and worn out insulated gloves, hopefully they still work." - name = "worn out insulated gloves" - -/obj/item/clothing/gloves/color/fyellow/old/Initialize() - . = ..() - siemens_coefficient = pick(0,0,0,0.5,0.5,0.5,0.75) - -/obj/item/clothing/gloves/color/black - desc = "These gloves are fire-resistant." - name = "black gloves" - icon_state = "black" - item_state = "blackgloves" - cold_protection = HANDS - min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT - heat_protection = HANDS - max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT - resistance_flags = NONE - var/can_be_cut = TRUE - -/obj/item/clothing/gloves/color/black/attackby(obj/item/I, mob/user, params) - if(I.tool_behaviour == TOOL_WIRECUTTER) - if(can_be_cut && icon_state == initial(icon_state))//only if not dyed - to_chat(user, "You snip the fingertips off of [src].") - I.play_tool_sound(src) - new /obj/item/clothing/gloves/fingerless(drop_location()) - qdel(src) - ..() - -/obj/item/clothing/gloves/color/orange - name = "orange gloves" - desc = "A pair of gloves, they don't look special in any way." - icon_state = "orange" - item_state = "orangegloves" - -/obj/item/clothing/gloves/color/red - name = "red gloves" - desc = "A pair of gloves, they don't look special in any way." - icon_state = "red" - item_state = "redgloves" - - -/obj/item/clothing/gloves/color/red/insulated - name = "insulated gloves" - desc = "These gloves provide protection against electric shock." - siemens_coefficient = 0 - permeability_coefficient = 0.05 - resistance_flags = NONE - -/obj/item/clothing/gloves/color/rainbow - name = "rainbow gloves" - desc = "A pair of gloves, they don't look special in any way." - icon_state = "rainbow" - item_state = "rainbowgloves" - -/obj/item/clothing/gloves/color/blue - name = "blue gloves" - desc = "A pair of gloves, they don't look special in any way." - icon_state = "blue" - item_state = "bluegloves" - -/obj/item/clothing/gloves/color/purple - name = "purple gloves" - desc = "A pair of gloves, they don't look special in any way." - icon_state = "purple" - item_state = "purplegloves" - -/obj/item/clothing/gloves/color/green - name = "green gloves" - desc = "A pair of gloves, they don't look special in any way." - icon_state = "green" - item_state = "greengloves" - -/obj/item/clothing/gloves/color/grey - name = "grey gloves" - desc = "A pair of gloves, they don't look special in any way." - icon_state = "gray" - item_state = "graygloves" - -/obj/item/clothing/gloves/color/light_brown - name = "light brown gloves" - desc = "A pair of gloves, they don't look special in any way." - icon_state = "lightbrown" - item_state = "lightbrowngloves" - -/obj/item/clothing/gloves/color/brown - name = "brown gloves" - desc = "A pair of gloves, they don't look special in any way." - icon_state = "brown" - item_state = "browngloves" - -/obj/item/clothing/gloves/color/captain - desc = "Regal blue gloves, with a nice gold trim, a diamond anti-shock coating, and an integrated thermal barrier. Swanky." - name = "captain's gloves" - icon_state = "captain" - item_state = "egloves" - siemens_coefficient = 0 - permeability_coefficient = 0.05 - cold_protection = HANDS - min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT - heat_protection = HANDS - max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT - strip_delay = 60 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 50) - -/obj/item/clothing/gloves/color/latex - name = "latex gloves" - desc = "Cheap sterile gloves made from latex. Transfers minor paramedic knowledge to the user via budget nanochips." - icon_state = "latex" - item_state = "latex" - siemens_coefficient = 0.3 - permeability_coefficient = 0.01 - transfer_prints = TRUE - resistance_flags = NONE - var/carrytrait = TRAIT_QUICK_CARRY - -/obj/item/clothing/gloves/color/latex/equipped(mob/user, slot) - ..() - if(slot == ITEM_SLOT_GLOVES) - ADD_TRAIT(user, carrytrait, CLOTHING_TRAIT) - -/obj/item/clothing/gloves/color/latex/dropped(mob/user) - ..() - REMOVE_TRAIT(user, carrytrait, CLOTHING_TRAIT) - -/obj/item/clothing/gloves/color/latex/nitrile - name = "nitrile gloves" - desc = "Pricy sterile gloves that are thicker than latex. Transfers intimate paramedic knowledge into the user via nanochips." - icon_state = "nitrile" - item_state = "nitrilegloves" - transfer_prints = FALSE - carrytrait = TRAIT_QUICKER_CARRY - -/obj/item/clothing/gloves/color/latex/nitrile/infiltrator - name = "infiltrator gloves" - desc = "Specialized combat gloves for carrying people around. Transfers tactical kidnapping knowledge into the user via nanochips." - icon_state = "infiltrator" - item_state = "infiltrator" - siemens_coefficient = 0 - permeability_coefficient = 0.3 - resistance_flags = FIRE_PROOF | ACID_PROOF - -/obj/item/clothing/gloves/color/latex/engineering - name = "tinker's gloves" - desc = "Overdesigned engineering gloves that have automated construction subrutines dialed in, allowing for faster construction while worn." - icon = 'icons/obj/clothing/clockwork_garb.dmi' - icon_state = "clockwork_gauntlets" - item_state = "clockwork_gauntlets" - siemens_coefficient = 0 //Wasp Station eddit "Tinkers Gloves Insuls" - permeability_coefficient = 0.05 //Wasp Station eddit "Tinkers Gloves Insuls" - //siemens_coefficient = 0.8 Wasp Station eddit "Tinkers Gloves Insuls" - //permeability_coefficient = 0.3 Wasp Station eddit "Tinkers Gloves Insuls" - carrytrait = TRAIT_QUICK_BUILD - custom_materials = list(/datum/material/iron=2000, /datum/material/silver=1500, /datum/material/gold = 1000) - -/obj/item/clothing/gloves/color/white - name = "white gloves" - desc = "These look pretty fancy." - icon_state = "white" - item_state = "wgloves" - custom_price = 200 - -/obj/effect/spawner/lootdrop/gloves - name = "random gloves" - desc = "These gloves are supposed to be a random color..." - icon = 'icons/obj/clothing/gloves.dmi' - icon_state = "random_gloves" - loot = list( - /obj/item/clothing/gloves/color/orange = 1, - /obj/item/clothing/gloves/color/red = 1, - /obj/item/clothing/gloves/color/blue = 1, - /obj/item/clothing/gloves/color/purple = 1, - /obj/item/clothing/gloves/color/green = 1, - /obj/item/clothing/gloves/color/grey = 1, - /obj/item/clothing/gloves/color/light_brown = 1, - /obj/item/clothing/gloves/color/brown = 1, - /obj/item/clothing/gloves/color/white = 1, - /obj/item/clothing/gloves/color/rainbow = 1) +/obj/item/clothing/gloves/color + dying_key = DYE_REGISTRY_GLOVES + +/obj/item/clothing/gloves/color/yellow + desc = "These gloves provide protection against electric shock." + name = "insulated gloves" + icon_state = "yellow" + item_state = "ygloves" + siemens_coefficient = 0 + permeability_coefficient = 0.05 + resistance_flags = NONE + custom_price = 1200 + custom_premium_price = 1200 + +/obj/item/toy/sprayoncan + name = "spray-on insulation applicator" + desc = "What is the number one problem facing our station today?" + icon = 'icons/obj/clothing/gloves.dmi' + icon_state = "sprayoncan" + +/obj/item/toy/sprayoncan/afterattack(atom/target, mob/living/carbon/user, proximity) + if(iscarbon(target) && proximity) + var/mob/living/carbon/C = target + var/mob/living/carbon/U = user + var/success = C.equip_to_slot_if_possible(new /obj/item/clothing/gloves/color/yellow/sprayon, ITEM_SLOT_GLOVES, TRUE, TRUE) + if(success) + if(C == user) + C.visible_message("[U] sprays their hands with glittery rubber!") + else + C.visible_message("[U] sprays glittery rubber on the hands of [C]!") + else + C.visible_message("The rubber fails to stick to [C]'s hands!") + + qdel(src) + +/obj/item/clothing/gloves/color/yellow/sprayon + desc = "How're you gonna get 'em off, nerd?" + name = "spray-on insulated gloves" + icon_state = "sprayon" + item_state = "sprayon" + permeability_coefficient = 0 + resistance_flags = ACID_PROOF + var/shocks_remaining = 10 + +/obj/item/clothing/gloves/color/yellow/sprayon/Initialize() + .=..() + ADD_TRAIT(src, TRAIT_NODROP, CLOTHING_TRAIT) + +/obj/item/clothing/gloves/color/yellow/sprayon/equipped(mob/user, slot) + . = ..() + RegisterSignal(user, COMSIG_LIVING_SHOCK_PREVENTED, .proc/Shocked) + +/obj/item/clothing/gloves/color/yellow/sprayon/proc/Shocked() + shocks_remaining-- + if(shocks_remaining < 0) + qdel(src) //if we run out of uses, the gloves crumble away into nothing, just like my dreams after working with .dm + +/obj/item/clothing/gloves/color/yellow/sprayon/dropped() + .=..() + qdel(src) //loose nodrop items bad + +/obj/item/clothing/gloves/color/fyellow //Cheap Chinese Crap + desc = "These gloves are cheap knockoffs of the coveted ones - no way this can end badly." + name = "budget insulated gloves" + icon_state = "yellow" + item_state = "ygloves" + siemens_coefficient = 1 //Set to a default of 1, gets overridden in Initialize() + permeability_coefficient = 0.05 + resistance_flags = NONE + +/obj/item/clothing/gloves/color/fyellow/Initialize() + . = ..() + siemens_coefficient = pick(0,0.5,0.5,0.5,0.5,0.75,1.5) + +/obj/item/clothing/gloves/color/fyellow/old + desc = "Old and worn out insulated gloves, hopefully they still work." + name = "worn out insulated gloves" + +/obj/item/clothing/gloves/color/fyellow/old/Initialize() + . = ..() + siemens_coefficient = pick(0,0,0,0.5,0.5,0.5,0.75) + +/obj/item/clothing/gloves/color/black + desc = "These gloves are fire-resistant." + name = "black gloves" + icon_state = "black" + item_state = "blackgloves" + cold_protection = HANDS + min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT + heat_protection = HANDS + max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT + resistance_flags = NONE + var/can_be_cut = TRUE + +/obj/item/clothing/gloves/color/black/attackby(obj/item/I, mob/user, params) + if(I.tool_behaviour == TOOL_WIRECUTTER) + if(can_be_cut && icon_state == initial(icon_state))//only if not dyed + to_chat(user, "You snip the fingertips off of [src].") + I.play_tool_sound(src) + new /obj/item/clothing/gloves/fingerless(drop_location()) + qdel(src) + ..() + +/obj/item/clothing/gloves/color/orange + name = "orange gloves" + desc = "A pair of gloves, they don't look special in any way." + icon_state = "orange" + item_state = "orangegloves" + +/obj/item/clothing/gloves/color/red + name = "red gloves" + desc = "A pair of gloves, they don't look special in any way." + icon_state = "red" + item_state = "redgloves" + + +/obj/item/clothing/gloves/color/red/insulated + name = "insulated gloves" + desc = "These gloves provide protection against electric shock." + siemens_coefficient = 0 + permeability_coefficient = 0.05 + resistance_flags = NONE + +/obj/item/clothing/gloves/color/rainbow + name = "rainbow gloves" + desc = "A pair of gloves, they don't look special in any way." + icon_state = "rainbow" + item_state = "rainbowgloves" + +/obj/item/clothing/gloves/color/blue + name = "blue gloves" + desc = "A pair of gloves, they don't look special in any way." + icon_state = "blue" + item_state = "bluegloves" + +/obj/item/clothing/gloves/color/purple + name = "purple gloves" + desc = "A pair of gloves, they don't look special in any way." + icon_state = "purple" + item_state = "purplegloves" + +/obj/item/clothing/gloves/color/green + name = "green gloves" + desc = "A pair of gloves, they don't look special in any way." + icon_state = "green" + item_state = "greengloves" + +/obj/item/clothing/gloves/color/grey + name = "grey gloves" + desc = "A pair of gloves, they don't look special in any way." + icon_state = "gray" + item_state = "graygloves" + +/obj/item/clothing/gloves/color/light_brown + name = "light brown gloves" + desc = "A pair of gloves, they don't look special in any way." + icon_state = "lightbrown" + item_state = "lightbrowngloves" + +/obj/item/clothing/gloves/color/brown + name = "brown gloves" + desc = "A pair of gloves, they don't look special in any way." + icon_state = "brown" + item_state = "browngloves" + +/obj/item/clothing/gloves/color/captain + desc = "Regal blue gloves, with a nice gold trim, a diamond anti-shock coating, and an integrated thermal barrier. Swanky." + name = "captain's gloves" + icon_state = "captain" + item_state = "egloves" + siemens_coefficient = 0 + permeability_coefficient = 0.05 + cold_protection = HANDS + min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT + heat_protection = HANDS + max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT + strip_delay = 60 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 50) + +/obj/item/clothing/gloves/color/latex + name = "latex gloves" + desc = "Cheap sterile gloves made from latex. Transfers minor paramedic knowledge to the user via budget nanochips." + icon_state = "latex" + item_state = "latex" + siemens_coefficient = 0.3 + permeability_coefficient = 0.01 + transfer_prints = TRUE + resistance_flags = NONE + var/carrytrait = TRAIT_QUICK_CARRY + +/obj/item/clothing/gloves/color/latex/equipped(mob/user, slot) + ..() + if(slot == ITEM_SLOT_GLOVES) + ADD_TRAIT(user, carrytrait, CLOTHING_TRAIT) + +/obj/item/clothing/gloves/color/latex/dropped(mob/user) + ..() + REMOVE_TRAIT(user, carrytrait, CLOTHING_TRAIT) + +/obj/item/clothing/gloves/color/latex/nitrile + name = "nitrile gloves" + desc = "Pricy sterile gloves that are thicker than latex. Transfers intimate paramedic knowledge into the user via nanochips." + icon_state = "nitrile" + item_state = "nitrilegloves" + transfer_prints = FALSE + carrytrait = TRAIT_QUICKER_CARRY + +/obj/item/clothing/gloves/color/latex/nitrile/infiltrator + name = "infiltrator gloves" + desc = "Specialized combat gloves for carrying people around. Transfers tactical kidnapping knowledge into the user via nanochips." + icon_state = "infiltrator" + item_state = "infiltrator" + siemens_coefficient = 0 + permeability_coefficient = 0.3 + resistance_flags = FIRE_PROOF | ACID_PROOF + +/obj/item/clothing/gloves/color/latex/engineering + name = "tinker's gloves" + desc = "Overdesigned engineering gloves that have automated construction subrutines dialed in, allowing for faster construction while worn." + icon = 'icons/obj/clothing/clockwork_garb.dmi' + icon_state = "clockwork_gauntlets" + item_state = "clockwork_gauntlets" + siemens_coefficient = 0 //Wasp Station eddit "Tinkers Gloves Insuls" + permeability_coefficient = 0.05 //Wasp Station eddit "Tinkers Gloves Insuls" + //siemens_coefficient = 0.8 Wasp Station eddit "Tinkers Gloves Insuls" + //permeability_coefficient = 0.3 Wasp Station eddit "Tinkers Gloves Insuls" + carrytrait = TRAIT_QUICK_BUILD + custom_materials = list(/datum/material/iron=2000, /datum/material/silver=1500, /datum/material/gold = 1000) + +/obj/item/clothing/gloves/color/white + name = "white gloves" + desc = "These look pretty fancy." + icon_state = "white" + item_state = "wgloves" + custom_price = 200 + +/obj/effect/spawner/lootdrop/gloves + name = "random gloves" + desc = "These gloves are supposed to be a random color..." + icon = 'icons/obj/clothing/gloves.dmi' + icon_state = "random_gloves" + loot = list( + /obj/item/clothing/gloves/color/orange = 1, + /obj/item/clothing/gloves/color/red = 1, + /obj/item/clothing/gloves/color/blue = 1, + /obj/item/clothing/gloves/color/purple = 1, + /obj/item/clothing/gloves/color/green = 1, + /obj/item/clothing/gloves/color/grey = 1, + /obj/item/clothing/gloves/color/light_brown = 1, + /obj/item/clothing/gloves/color/brown = 1, + /obj/item/clothing/gloves/color/white = 1, + /obj/item/clothing/gloves/color/rainbow = 1) diff --git a/code/modules/clothing/gloves/miscellaneous.dm b/code/modules/clothing/gloves/miscellaneous.dm index 890a2d2ea93c..498f155b6e64 100644 --- a/code/modules/clothing/gloves/miscellaneous.dm +++ b/code/modules/clothing/gloves/miscellaneous.dm @@ -1,149 +1,149 @@ - -/obj/item/clothing/gloves/fingerless - name = "fingerless gloves" - desc = "Plain black gloves without fingertips for the hard working." - icon_state = "fingerless" - item_state = "fingerless" - transfer_prints = TRUE - strip_delay = 40 - equip_delay_other = 20 - cold_protection = HANDS - min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT - custom_price = 75 - undyeable = TRUE - clothamnt = 1 - -/obj/item/clothing/gloves/botanic_leather - name = "botanist's leather gloves" - desc = "These leather gloves protect against thorns, barbs, prickles, spikes and other harmful objects of floral origin. They're also quite warm." - icon_state = "leather" - item_state = "ggloves" - permeability_coefficient = 0.9 - cold_protection = HANDS - min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT - heat_protection = HANDS - max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT - resistance_flags = NONE - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 30) - -/obj/item/clothing/gloves/combat - name = "combat gloves" - desc = "These tactical gloves are fireproof and electrically insulated." - icon_state = "black" - item_state = "blackgloves" - siemens_coefficient = 0 - permeability_coefficient = 0.05 - strip_delay = 80 - cold_protection = HANDS - min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT - heat_protection = HANDS - max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT - resistance_flags = NONE - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 50) - -/obj/item/clothing/gloves/bracer - name = "bone bracers" - desc = "For when you're expecting to get slapped on the wrist. Offers modest protection to your arms." - icon_state = "bracers" - item_state = "bracers" - transfer_prints = TRUE - strip_delay = 40 - equip_delay_other = 20 - body_parts_covered = ARMS - cold_protection = ARMS - min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT - max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT - resistance_flags = NONE - armor = list("melee" = 15, "bullet" = 25, "laser" = 15, "energy" = 15, "bomb" = 20, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) - -/obj/item/clothing/gloves/rapid - name = "Gloves of the North Star" - desc = "Just looking at these fills you with an urge to beat the shit out of people." - icon_state = "rapid" - item_state = "rapid" - transfer_prints = TRUE - cuttable = FALSE - -/obj/item/clothing/gloves/rapid/ComponentInitialize() - . = ..() - AddComponent(/datum/component/wearertargeting/punchcooldown) - - -/obj/item/clothing/gloves/color/plasmaman - desc = "Covers up those scandalous boney hands." - name = "plasma envirogloves" - icon_state = "plasmaman" - item_state = "plasmaman" - cold_protection = HANDS - min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT - heat_protection = HANDS - max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT - resistance_flags = NONE - permeability_coefficient = 0.05 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 0, "fire" = 95, "acid" = 95) - -/obj/item/clothing/gloves/color/plasmaman/black - name = "black envirogloves" - icon_state = "blackplasma" - item_state = "blackplasma" - -/obj/item/clothing/gloves/color/plasmaman/white - name = "white envirogloves" - icon_state = "whiteplasma" - item_state = "whiteplasma" - -/obj/item/clothing/gloves/color/plasmaman/robot - name = "roboticist envirogloves" - icon_state = "robotplasma" - item_state = "robotplasma" - -/obj/item/clothing/gloves/color/plasmaman/janny - name = "janitor envirogloves" - icon_state = "jannyplasma" - item_state = "jannyplasma" - -/obj/item/clothing/gloves/color/plasmaman/cargo - name = "cargo envirogloves" - icon_state = "cargoplasma" - item_state = "cargoplasma" - -/obj/item/clothing/gloves/color/plasmaman/engineer - name = "engineering envirogloves" - icon_state = "engieplasma" - item_state = "engieplasma" - siemens_coefficient = 0 - -/obj/item/clothing/gloves/color/plasmaman/atmos - name = "atmos envirogloves" - icon_state = "atmosplasma" - item_state = "atmosplasma" - siemens_coefficient = 0 - -/obj/item/clothing/gloves/color/plasmaman/explorer - name = "explorer envirogloves" - icon_state = "explorerplasma" - item_state = "explorerplasma" - -/obj/item/clothing/gloves/color/botanic_leather/plasmaman - name = "botany envirogloves" - desc = "Covers up those scandalous boney hands." - icon_state = "botanyplasma" - item_state = "botanyplasma" - permeability_coefficient = 0.05 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 0, "fire" = 95, "acid" = 95) - -/obj/item/clothing/gloves/color/plasmaman/prototype - name = "prototype envirogloves" - icon_state = "protoplasma" - item_state = "protoplasma" - -/obj/item/clothing/gloves/color/plasmaman/clown - name = "clown envirogloves" - icon_state = "clownplasma" - item_state = "clownplasma" - -/obj/item/clothing/gloves/combat/wizard - name = "enchanted gloves" - desc = "These gloves have been enchanted with a spell that makes them electrically insulated and fireproof." - icon_state = "wizard" - item_state = "purplegloves" + +/obj/item/clothing/gloves/fingerless + name = "fingerless gloves" + desc = "Plain black gloves without fingertips for the hard working." + icon_state = "fingerless" + item_state = "fingerless" + transfer_prints = TRUE + strip_delay = 40 + equip_delay_other = 20 + cold_protection = HANDS + min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT + custom_price = 75 + undyeable = TRUE + clothamnt = 1 + +/obj/item/clothing/gloves/botanic_leather + name = "botanist's leather gloves" + desc = "These leather gloves protect against thorns, barbs, prickles, spikes and other harmful objects of floral origin. They're also quite warm." + icon_state = "leather" + item_state = "ggloves" + permeability_coefficient = 0.9 + cold_protection = HANDS + min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT + heat_protection = HANDS + max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT + resistance_flags = NONE + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 30) + +/obj/item/clothing/gloves/combat + name = "combat gloves" + desc = "These tactical gloves are fireproof and electrically insulated." + icon_state = "black" + item_state = "blackgloves" + siemens_coefficient = 0 + permeability_coefficient = 0.05 + strip_delay = 80 + cold_protection = HANDS + min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT + heat_protection = HANDS + max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT + resistance_flags = NONE + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 50) + +/obj/item/clothing/gloves/bracer + name = "bone bracers" + desc = "For when you're expecting to get slapped on the wrist. Offers modest protection to your arms." + icon_state = "bracers" + item_state = "bracers" + transfer_prints = TRUE + strip_delay = 40 + equip_delay_other = 20 + body_parts_covered = ARMS + cold_protection = ARMS + min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT + max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT + resistance_flags = NONE + armor = list("melee" = 15, "bullet" = 25, "laser" = 15, "energy" = 15, "bomb" = 20, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) + +/obj/item/clothing/gloves/rapid + name = "Gloves of the North Star" + desc = "Just looking at these fills you with an urge to beat the shit out of people." + icon_state = "rapid" + item_state = "rapid" + transfer_prints = TRUE + cuttable = FALSE + +/obj/item/clothing/gloves/rapid/ComponentInitialize() + . = ..() + AddComponent(/datum/component/wearertargeting/punchcooldown) + + +/obj/item/clothing/gloves/color/plasmaman + desc = "Covers up those scandalous boney hands." + name = "plasma envirogloves" + icon_state = "plasmaman" + item_state = "plasmaman" + cold_protection = HANDS + min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT + heat_protection = HANDS + max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT + resistance_flags = NONE + permeability_coefficient = 0.05 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 0, "fire" = 95, "acid" = 95) + +/obj/item/clothing/gloves/color/plasmaman/black + name = "black envirogloves" + icon_state = "blackplasma" + item_state = "blackplasma" + +/obj/item/clothing/gloves/color/plasmaman/white + name = "white envirogloves" + icon_state = "whiteplasma" + item_state = "whiteplasma" + +/obj/item/clothing/gloves/color/plasmaman/robot + name = "roboticist envirogloves" + icon_state = "robotplasma" + item_state = "robotplasma" + +/obj/item/clothing/gloves/color/plasmaman/janny + name = "janitor envirogloves" + icon_state = "jannyplasma" + item_state = "jannyplasma" + +/obj/item/clothing/gloves/color/plasmaman/cargo + name = "cargo envirogloves" + icon_state = "cargoplasma" + item_state = "cargoplasma" + +/obj/item/clothing/gloves/color/plasmaman/engineer + name = "engineering envirogloves" + icon_state = "engieplasma" + item_state = "engieplasma" + siemens_coefficient = 0 + +/obj/item/clothing/gloves/color/plasmaman/atmos + name = "atmos envirogloves" + icon_state = "atmosplasma" + item_state = "atmosplasma" + siemens_coefficient = 0 + +/obj/item/clothing/gloves/color/plasmaman/explorer + name = "explorer envirogloves" + icon_state = "explorerplasma" + item_state = "explorerplasma" + +/obj/item/clothing/gloves/color/botanic_leather/plasmaman + name = "botany envirogloves" + desc = "Covers up those scandalous boney hands." + icon_state = "botanyplasma" + item_state = "botanyplasma" + permeability_coefficient = 0.05 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 0, "fire" = 95, "acid" = 95) + +/obj/item/clothing/gloves/color/plasmaman/prototype + name = "prototype envirogloves" + icon_state = "protoplasma" + item_state = "protoplasma" + +/obj/item/clothing/gloves/color/plasmaman/clown + name = "clown envirogloves" + icon_state = "clownplasma" + item_state = "clownplasma" + +/obj/item/clothing/gloves/combat/wizard + name = "enchanted gloves" + desc = "These gloves have been enchanted with a spell that makes them electrically insulated and fireproof." + icon_state = "wizard" + item_state = "purplegloves" diff --git a/code/modules/clothing/head/_head.dm b/code/modules/clothing/head/_head.dm index 867104f49a17..0318008c4be0 100644 --- a/code/modules/clothing/head/_head.dm +++ b/code/modules/clothing/head/_head.dm @@ -1,76 +1,76 @@ -/obj/item/clothing/head - name = BODY_ZONE_HEAD - icon = 'icons/obj/clothing/hats.dmi' - icon_state = "top_hat" - item_state = "that" - body_parts_covered = HEAD - slot_flags = ITEM_SLOT_HEAD - var/blockTracking = 0 //For AI tracking - var/can_toggle = null - dynamic_hair_suffix = "+generic" - -/obj/item/clothing/head/Initialize() - . = ..() - if(ishuman(loc) && dynamic_hair_suffix) - var/mob/living/carbon/human/H = loc - H.update_hair() - -///Special throw_impact for hats to frisbee hats at people to place them on their heads/attempt to de-hat them. -/obj/item/clothing/head/throw_impact(atom/hit_atom, datum/thrownthing/thrownthing) - . = ..() - ///if the thrown object's target zone isn't the head - if(thrownthing.target_zone != BODY_ZONE_HEAD) - return - ///ignore any hats with the tinfoil counter-measure enabled - if(clothing_flags & ANTI_TINFOIL_MANEUVER) - return - ///if the hat happens to be capable of holding contents and has something in it. mostly to prevent super cheesy stuff like stuffing a mini-bomb in a hat and throwing it - if(LAZYLEN(contents)) - return - if(iscarbon(hit_atom)) - var/mob/living/carbon/H = hit_atom - if(istype(H.head, /obj/item)) - var/obj/item/WH = H.head - ///check if the item has NODROP - if(HAS_TRAIT(WH, TRAIT_NODROP)) - H.visible_message("[src] bounces off [H]'s [WH.name]!", "[src] bounces off your [WH.name], falling to the floor.") - return - ///check if the item is an actual clothing head item, since some non-clothing items can be worn - if(istype(WH, /obj/item/clothing/head)) - var/obj/item/clothing/head/WHH = WH - ///SNUG_FIT hats are immune to being knocked off - if(WHH.clothing_flags & SNUG_FIT) - H.visible_message("[src] bounces off [H]'s [WHH.name]!", "[src] bounces off your [WHH.name], falling to the floor.") - return - ///if the hat manages to knock something off - if(H.dropItemToGround(WH)) - H.visible_message("[src] knocks [WH] off [H]'s head!", "[WH] is suddenly knocked off your head by [src]!") - if(H.equip_to_slot_if_possible(src, ITEM_SLOT_HEAD, 0, 1, 1)) - H.visible_message("[src] lands neatly on [H]'s head!", "[src] lands perfectly onto your head!") - return - 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, 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.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 - - - - -/obj/item/clothing/head/worn_overlays(isinhands = FALSE) - . = list() - if(!isinhands) - if(damaged_clothes) - . += mutable_appearance('icons/effects/item_damage.dmi', "damagedhelmet") - if(HAS_BLOOD_DNA(src)) - . += mutable_appearance('icons/effects/blood.dmi', "helmetblood") - -/obj/item/clothing/head/update_clothes_damaged_state(damaging = TRUE) - ..() - if(ismob(loc)) - var/mob/M = loc - M.update_inv_head() +/obj/item/clothing/head + name = BODY_ZONE_HEAD + icon = 'icons/obj/clothing/hats.dmi' + icon_state = "top_hat" + item_state = "that" + body_parts_covered = HEAD + slot_flags = ITEM_SLOT_HEAD + var/blockTracking = 0 //For AI tracking + var/can_toggle = null + dynamic_hair_suffix = "+generic" + +/obj/item/clothing/head/Initialize() + . = ..() + if(ishuman(loc) && dynamic_hair_suffix) + var/mob/living/carbon/human/H = loc + H.update_hair() + +///Special throw_impact for hats to frisbee hats at people to place them on their heads/attempt to de-hat them. +/obj/item/clothing/head/throw_impact(atom/hit_atom, datum/thrownthing/thrownthing) + . = ..() + ///if the thrown object's target zone isn't the head + if(thrownthing.target_zone != BODY_ZONE_HEAD) + return + ///ignore any hats with the tinfoil counter-measure enabled + if(clothing_flags & ANTI_TINFOIL_MANEUVER) + return + ///if the hat happens to be capable of holding contents and has something in it. mostly to prevent super cheesy stuff like stuffing a mini-bomb in a hat and throwing it + if(LAZYLEN(contents)) + return + if(iscarbon(hit_atom)) + var/mob/living/carbon/H = hit_atom + if(istype(H.head, /obj/item)) + var/obj/item/WH = H.head + ///check if the item has NODROP + if(HAS_TRAIT(WH, TRAIT_NODROP)) + H.visible_message("[src] bounces off [H]'s [WH.name]!", "[src] bounces off your [WH.name], falling to the floor.") + return + ///check if the item is an actual clothing head item, since some non-clothing items can be worn + if(istype(WH, /obj/item/clothing/head)) + var/obj/item/clothing/head/WHH = WH + ///SNUG_FIT hats are immune to being knocked off + if(WHH.clothing_flags & SNUG_FIT) + H.visible_message("[src] bounces off [H]'s [WHH.name]!", "[src] bounces off your [WHH.name], falling to the floor.") + return + ///if the hat manages to knock something off + if(H.dropItemToGround(WH)) + H.visible_message("[src] knocks [WH] off [H]'s head!", "[WH] is suddenly knocked off your head by [src]!") + if(H.equip_to_slot_if_possible(src, ITEM_SLOT_HEAD, 0, 1, 1)) + H.visible_message("[src] lands neatly on [H]'s head!", "[src] lands perfectly onto your head!") + return + 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, 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.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 + + + + +/obj/item/clothing/head/worn_overlays(isinhands = FALSE) + . = list() + if(!isinhands) + if(damaged_clothes) + . += mutable_appearance('icons/effects/item_damage.dmi', "damagedhelmet") + if(HAS_BLOOD_DNA(src)) + . += mutable_appearance('icons/effects/blood.dmi', "helmetblood") + +/obj/item/clothing/head/update_clothes_damaged_state(damaging = TRUE) + ..() + if(ismob(loc)) + var/mob/M = loc + M.update_inv_head() diff --git a/code/modules/clothing/head/collectable.dm b/code/modules/clothing/head/collectable.dm index 63ea61e94b93..dcf827938993 100644 --- a/code/modules/clothing/head/collectable.dm +++ b/code/modules/clothing/head/collectable.dm @@ -1,153 +1,153 @@ - -//Hat Station 13 - -/obj/item/clothing/head/collectable - name = "collectable hat" - desc = "A rare collectable hat." - -/obj/item/clothing/head/collectable/petehat - name = "ultra rare Pete's hat!" - desc = "It smells faintly of plasma." - icon_state = "petehat" - -/obj/item/clothing/head/collectable/xenom - name = "collectable xenomorph helmet!" - desc = "Hiss hiss hiss!" - clothing_flags = SNUG_FIT - icon_state = "xenom" - -/obj/item/clothing/head/collectable/chef - name = "collectable chef's hat" - desc = "A rare chef's hat meant for hat collectors!" - icon_state = "chef" - item_state = "chef" - dynamic_hair_suffix = "" - - dog_fashion = /datum/dog_fashion/head/chef - -/obj/item/clothing/head/collectable/paper - name = "collectable paper hat" - desc = "What looks like an ordinary paper hat is actually a rare and valuable collector's edition paper hat. Keep away from water, fire, and Curators." - icon_state = "paper" - - dog_fashion = /datum/dog_fashion/head - -/obj/item/clothing/head/collectable/tophat - name = "collectable top hat" - desc = "A top hat worn by only the most prestigious hat collectors." - icon_state = "tophat" - item_state = "that" - -/obj/item/clothing/head/collectable/captain - name = "collectable captain's hat" - desc = "A collectable hat that'll make you look just like a real comdom!" - icon_state = "captain" - item_state = "caphat" - - dog_fashion = /datum/dog_fashion/head/captain - -/obj/item/clothing/head/collectable/police - name = "collectable police officer's hat" - desc = "A collectable police officer's Hat. This hat emphasizes that you are THE LAW." - icon_state = "policehelm" - dynamic_hair_suffix = "" - - dog_fashion = /datum/dog_fashion/head/warden - -/obj/item/clothing/head/collectable/beret - name = "collectable beret" - desc = "A collectable red beret. It smells faintly of garlic." - icon_state = "beret" - - dog_fashion = /datum/dog_fashion/head/beret - -/obj/item/clothing/head/collectable/welding - name = "collectable welding helmet" - desc = "A collectable welding helmet. Now with 80% less lead! Not for actual welding. Any welding done while wearing this helmet is done so at the owner's own risk!" - icon_state = "welding" - item_state = "welding" - clothing_flags = SNUG_FIT - -/obj/item/clothing/head/collectable/slime - name = "collectable slime hat" - desc = "Just like a real brain slug!" - icon_state = "headslime" - item_state = "headslime" - clothing_flags = SNUG_FIT - dynamic_hair_suffix = "" - -/obj/item/clothing/head/collectable/flatcap - name = "collectable flat cap" - desc = "A collectible farmer's flat cap!" - icon_state = "flat_cap" - item_state = "detective" - -/obj/item/clothing/head/collectable/pirate - name = "collectable pirate hat" - desc = "You'd make a great Dread Syndie Roberts!" - icon_state = "pirate" - item_state = "pirate" - - dog_fashion = /datum/dog_fashion/head/pirate - -/obj/item/clothing/head/collectable/kitty - name = "collectable kitty ears" - desc = "The fur feels... a bit too realistic." - icon_state = "kitty" - item_state = "kitty" - dynamic_hair_suffix = "" - - dog_fashion = /datum/dog_fashion/head/kitty - -/obj/item/clothing/head/collectable/rabbitears - name = "collectable rabbit ears" - desc = "Not as lucky as the feet!" - icon_state = "bunny" - item_state = "bunny" - dynamic_hair_suffix = "" - - dog_fashion = /datum/dog_fashion/head/rabbit - -/obj/item/clothing/head/collectable/wizard - name = "collectable wizard's hat" - desc = "NOTE: Any magical powers gained from wearing this hat are purely coincidental." - icon_state = "wizard" - - dog_fashion = /datum/dog_fashion/head/blue_wizard - -/obj/item/clothing/head/collectable/hardhat - name = "collectable hard hat" - desc = "WARNING! Offers no real protection, or luminosity, but damn, is it fancy!" - clothing_flags = SNUG_FIT - icon_state = "hardhat0_yellow" - item_state = "hardhat0_yellow" - - dog_fashion = /datum/dog_fashion/head - -/obj/item/clothing/head/collectable/HoS - name = "collectable HoS hat" - desc = "Now you too can beat prisoners, set silly sentences, and arrest for no reason!" - icon_state = "hoscap" - dynamic_hair_suffix = "" - -/obj/item/clothing/head/collectable/HoP - name = "collectable HoP hat" - desc = "It's your turn to demand excessive paperwork, signatures, stamps, and hire more clowns! Papers, please!" - icon_state = "hopcap" - dog_fashion = /datum/dog_fashion/head/head_of_personnel - -/obj/item/clothing/head/collectable/thunderdome - name = "collectable Thunderdome helmet" - desc = "Go Red! I mean Green! I mean Red! No Green!" - icon_state = "thunderdome" - item_state = "thunderdome" - clothing_flags = SNUG_FIT - flags_inv = HIDEHAIR - -/obj/item/clothing/head/collectable/swat - name = "collectable SWAT helmet" - desc = "That's not real blood. That's red paint." //Reference to the actual description - icon_state = "swat" - item_state = "swat" - clothing_flags = SNUG_FIT - flags_inv = HIDEHAIR + +//Hat Station 13 + +/obj/item/clothing/head/collectable + name = "collectable hat" + desc = "A rare collectable hat." + +/obj/item/clothing/head/collectable/petehat + name = "ultra rare Pete's hat!" + desc = "It smells faintly of plasma." + icon_state = "petehat" + +/obj/item/clothing/head/collectable/xenom + name = "collectable xenomorph helmet!" + desc = "Hiss hiss hiss!" + clothing_flags = SNUG_FIT + icon_state = "xenom" + +/obj/item/clothing/head/collectable/chef + name = "collectable chef's hat" + desc = "A rare chef's hat meant for hat collectors!" + icon_state = "chef" + item_state = "chef" + dynamic_hair_suffix = "" + + dog_fashion = /datum/dog_fashion/head/chef + +/obj/item/clothing/head/collectable/paper + name = "collectable paper hat" + desc = "What looks like an ordinary paper hat is actually a rare and valuable collector's edition paper hat. Keep away from water, fire, and Curators." + icon_state = "paper" + + dog_fashion = /datum/dog_fashion/head + +/obj/item/clothing/head/collectable/tophat + name = "collectable top hat" + desc = "A top hat worn by only the most prestigious hat collectors." + icon_state = "tophat" + item_state = "that" + +/obj/item/clothing/head/collectable/captain + name = "collectable captain's hat" + desc = "A collectable hat that'll make you look just like a real comdom!" + icon_state = "captain" + item_state = "caphat" + + dog_fashion = /datum/dog_fashion/head/captain + +/obj/item/clothing/head/collectable/police + name = "collectable police officer's hat" + desc = "A collectable police officer's Hat. This hat emphasizes that you are THE LAW." + icon_state = "policehelm" + dynamic_hair_suffix = "" + + dog_fashion = /datum/dog_fashion/head/warden + +/obj/item/clothing/head/collectable/beret + name = "collectable beret" + desc = "A collectable red beret. It smells faintly of garlic." + icon_state = "beret" + + dog_fashion = /datum/dog_fashion/head/beret + +/obj/item/clothing/head/collectable/welding + name = "collectable welding helmet" + desc = "A collectable welding helmet. Now with 80% less lead! Not for actual welding. Any welding done while wearing this helmet is done so at the owner's own risk!" + icon_state = "welding" + item_state = "welding" + clothing_flags = SNUG_FIT + +/obj/item/clothing/head/collectable/slime + name = "collectable slime hat" + desc = "Just like a real brain slug!" + icon_state = "headslime" + item_state = "headslime" + clothing_flags = SNUG_FIT + dynamic_hair_suffix = "" + +/obj/item/clothing/head/collectable/flatcap + name = "collectable flat cap" + desc = "A collectible farmer's flat cap!" + icon_state = "flat_cap" + item_state = "detective" + +/obj/item/clothing/head/collectable/pirate + name = "collectable pirate hat" + desc = "You'd make a great Dread Syndie Roberts!" + icon_state = "pirate" + item_state = "pirate" + + dog_fashion = /datum/dog_fashion/head/pirate + +/obj/item/clothing/head/collectable/kitty + name = "collectable kitty ears" + desc = "The fur feels... a bit too realistic." + icon_state = "kitty" + item_state = "kitty" + dynamic_hair_suffix = "" + + dog_fashion = /datum/dog_fashion/head/kitty + +/obj/item/clothing/head/collectable/rabbitears + name = "collectable rabbit ears" + desc = "Not as lucky as the feet!" + icon_state = "bunny" + item_state = "bunny" + dynamic_hair_suffix = "" + + dog_fashion = /datum/dog_fashion/head/rabbit + +/obj/item/clothing/head/collectable/wizard + name = "collectable wizard's hat" + desc = "NOTE: Any magical powers gained from wearing this hat are purely coincidental." + icon_state = "wizard" + + dog_fashion = /datum/dog_fashion/head/blue_wizard + +/obj/item/clothing/head/collectable/hardhat + name = "collectable hard hat" + desc = "WARNING! Offers no real protection, or luminosity, but damn, is it fancy!" + clothing_flags = SNUG_FIT + icon_state = "hardhat0_yellow" + item_state = "hardhat0_yellow" + + dog_fashion = /datum/dog_fashion/head + +/obj/item/clothing/head/collectable/HoS + name = "collectable HoS hat" + desc = "Now you too can beat prisoners, set silly sentences, and arrest for no reason!" + icon_state = "hoscap" + dynamic_hair_suffix = "" + +/obj/item/clothing/head/collectable/HoP + name = "collectable HoP hat" + desc = "It's your turn to demand excessive paperwork, signatures, stamps, and hire more clowns! Papers, please!" + icon_state = "hopcap" + dog_fashion = /datum/dog_fashion/head/head_of_personnel + +/obj/item/clothing/head/collectable/thunderdome + name = "collectable Thunderdome helmet" + desc = "Go Red! I mean Green! I mean Red! No Green!" + icon_state = "thunderdome" + item_state = "thunderdome" + clothing_flags = SNUG_FIT + flags_inv = HIDEHAIR + +/obj/item/clothing/head/collectable/swat + name = "collectable SWAT helmet" + desc = "That's not real blood. That's red paint." //Reference to the actual description + icon_state = "swat" + item_state = "swat" + clothing_flags = SNUG_FIT + flags_inv = HIDEHAIR diff --git a/code/modules/clothing/head/hardhat.dm b/code/modules/clothing/head/hardhat.dm index bed2db932721..9fec0c21bcb9 100644 --- a/code/modules/clothing/head/hardhat.dm +++ b/code/modules/clothing/head/hardhat.dm @@ -1,166 +1,166 @@ -/obj/item/clothing/head/hardhat - name = "hard hat" - desc = "A piece of headgear used in dangerous working conditions to protect the head. Comes with a built-in flashlight." - icon_state = "hardhat0_yellow" - item_state = "hardhat0_yellow" - light_color = "#FFCC66" - var/power_on = 0.8 - var/brightness_on = 4 //luminosity when on - var/on = FALSE - var/hat_type = "yellow" //Determines used sprites: hardhat[on]_[hat_type] and hardhat[on]_[hat_type]2 (lying down sprite) - armor = list("melee" = 15, "bullet" = 5, "laser" = 20, "energy" = 10, "bomb" = 20, "bio" = 10, "rad" = 20, "fire" = 100, "acid" = 50) - flags_inv = 0 - actions_types = list(/datum/action/item_action/toggle_helmet_light) - clothing_flags = SNUG_FIT - resistance_flags = FIRE_PROOF - dynamic_hair_suffix = "+generic" - - dog_fashion = /datum/dog_fashion/head - -/obj/item/clothing/head/hardhat/ComponentInitialize() - . = ..() - AddElement(/datum/element/update_icon_updates_onmob) - -/obj/item/clothing/head/hardhat/attack_self(mob/living/user) - toggle_helmet_light(user) - -/obj/item/clothing/head/hardhat/proc/toggle_helmet_light(mob/living/user) - on = !on - if(on) - turn_on(user) - else - turn_off(user) - update_icon() - -/obj/item/clothing/head/hardhat/update_icon_state() - icon_state = item_state = "hardhat[on]_[hat_type]" - -/obj/item/clothing/head/hardhat/proc/turn_on(mob/user) - set_light(brightness_on, power_on) - -/obj/item/clothing/head/hardhat/proc/turn_off(mob/user) - set_light(0) - -/obj/item/clothing/head/hardhat/orange - icon_state = "hardhat0_orange" - item_state = "hardhat0_orange" - hat_type = "orange" - dog_fashion = null - -/obj/item/clothing/head/hardhat/red - icon_state = "hardhat0_red" - item_state = "hardhat0_red" - hat_type = "red" - dog_fashion = null - name = "firefighter helmet" - clothing_flags = STOPSPRESSUREDAMAGE - heat_protection = HEAD - max_heat_protection_temperature = FIRE_HELM_MAX_TEMP_PROTECT - cold_protection = HEAD - min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT - -/obj/item/clothing/head/hardhat/red/upgraded - name = "workplace-ready firefighter helmet" - desc = "By applying state of the art lighting technology to a fire helmet, and using photo-chemical hardening methods, this hardhat will protect you from robust workplace hazards." - icon_state = "hardhat0_purple" - item_state = "hardhat0_purple" - brightness_on = 5 - resistance_flags = FIRE_PROOF | ACID_PROOF - custom_materials = list(/datum/material/iron = 4000, /datum/material/glass = 1000, /datum/material/plastic = 3000, /datum/material/silver = 500) - hat_type = "purple" - -/obj/item/clothing/head/hardhat/white - icon_state = "hardhat0_white" - item_state = "hardhat0_white" - hat_type = "white" - clothing_flags = STOPSPRESSUREDAMAGE - heat_protection = HEAD - max_heat_protection_temperature = FIRE_HELM_MAX_TEMP_PROTECT - cold_protection = HEAD - min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT - dog_fashion = /datum/dog_fashion/head - -/obj/item/clothing/head/hardhat/dblue - icon_state = "hardhat0_dblue" - item_state = "hardhat0_dblue" - hat_type = "dblue" - dog_fashion = null - -/obj/item/clothing/head/hardhat/atmos - icon_state = "hardhat0_atmos" - item_state = "hardhat0_atmos" - hat_type = "atmos" - dog_fashion = null - name = "atmospheric technician's firefighting helmet" - desc = "A firefighter's helmet, able to keep the user cool in any situation." - clothing_flags = STOPSPRESSUREDAMAGE | THICKMATERIAL | BLOCK_GAS_SMOKE_EFFECT - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - heat_protection = HEAD - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - cold_protection = HEAD - min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT - flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF - -/obj/item/clothing/head/hardhat/weldhat - name = "welding hard hat" - desc = "A piece of headgear used in dangerous working conditions to protect the head. Comes with a built-in flashlight AND welding shield! The bulb seems a little smaller though." - brightness_on = 3 //Needs a little bit of tradeoff - dog_fashion = null - actions_types = list(/datum/action/item_action/toggle_helmet_light, /datum/action/item_action/toggle_welding_screen) - flash_protect = FLASH_PROTECTION_WELDER - tint = 2 - flags_inv = HIDEEYES | HIDEFACE - flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH - visor_vars_to_toggle = VISOR_FLASHPROTECT | VISOR_TINT - visor_flags_inv = HIDEEYES | HIDEFACE - visor_flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF - -/obj/item/clothing/head/hardhat/weldhat/Initialize() - . = ..() - update_icon() - -/obj/item/clothing/head/hardhat/weldhat/attack_self(mob/living/user) - toggle_helmet_light(user) - -/obj/item/clothing/head/hardhat/weldhat/AltClick(mob/user) - if(user.canUseTopic(src, BE_CLOSE)) - toggle_welding_screen(user) - -/obj/item/clothing/head/hardhat/weldhat/proc/toggle_welding_screen(mob/living/user) - if(weldingvisortoggle(user)) - playsound(src, 'sound/mecha/mechmove03.ogg', 50, TRUE) //Visors don't just come from nothing - update_icon() - -/obj/item/clothing/head/hardhat/weldhat/worn_overlays(isinhands) - . = ..() - if(!isinhands) - . += mutable_appearance('icons/mob/clothing/head.dmi', "weldhelmet") - if(!up) - . += mutable_appearance('icons/mob/clothing/head.dmi', "weldvisor") - -/obj/item/clothing/head/hardhat/weldhat/update_overlays() - . = ..() - if(!up) - . += "weldvisor" - -/obj/item/clothing/head/hardhat/weldhat/orange - icon_state = "hardhat0_orange" - item_state = "hardhat0_orange" - hat_type = "orange" - -/obj/item/clothing/head/hardhat/weldhat/white - desc = "A piece of headgear used in dangerous working conditions to protect the head. Comes with a built-in flashlight AND welding shield!" //This bulb is not smaller - icon_state = "hardhat0_white" - item_state = "hardhat0_white" - brightness_on = 4 //Boss always takes the best stuff - hat_type = "white" - clothing_flags = STOPSPRESSUREDAMAGE - heat_protection = HEAD - max_heat_protection_temperature = FIRE_HELM_MAX_TEMP_PROTECT - cold_protection = HEAD - min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT - -/obj/item/clothing/head/hardhat/weldhat/dblue - icon_state = "hardhat0_dblue" - item_state = "hardhat0_dblue" - hat_type = "dblue" +/obj/item/clothing/head/hardhat + name = "hard hat" + desc = "A piece of headgear used in dangerous working conditions to protect the head. Comes with a built-in flashlight." + icon_state = "hardhat0_yellow" + item_state = "hardhat0_yellow" + light_color = "#FFCC66" + var/power_on = 0.8 + var/brightness_on = 4 //luminosity when on + var/on = FALSE + var/hat_type = "yellow" //Determines used sprites: hardhat[on]_[hat_type] and hardhat[on]_[hat_type]2 (lying down sprite) + armor = list("melee" = 15, "bullet" = 5, "laser" = 20, "energy" = 10, "bomb" = 20, "bio" = 10, "rad" = 20, "fire" = 100, "acid" = 50) + flags_inv = 0 + actions_types = list(/datum/action/item_action/toggle_helmet_light) + clothing_flags = SNUG_FIT + resistance_flags = FIRE_PROOF + dynamic_hair_suffix = "+generic" + + dog_fashion = /datum/dog_fashion/head + +/obj/item/clothing/head/hardhat/ComponentInitialize() + . = ..() + AddElement(/datum/element/update_icon_updates_onmob) + +/obj/item/clothing/head/hardhat/attack_self(mob/living/user) + toggle_helmet_light(user) + +/obj/item/clothing/head/hardhat/proc/toggle_helmet_light(mob/living/user) + on = !on + if(on) + turn_on(user) + else + turn_off(user) + update_icon() + +/obj/item/clothing/head/hardhat/update_icon_state() + icon_state = item_state = "hardhat[on]_[hat_type]" + +/obj/item/clothing/head/hardhat/proc/turn_on(mob/user) + set_light(brightness_on, power_on) + +/obj/item/clothing/head/hardhat/proc/turn_off(mob/user) + set_light(0) + +/obj/item/clothing/head/hardhat/orange + icon_state = "hardhat0_orange" + item_state = "hardhat0_orange" + hat_type = "orange" + dog_fashion = null + +/obj/item/clothing/head/hardhat/red + icon_state = "hardhat0_red" + item_state = "hardhat0_red" + hat_type = "red" + dog_fashion = null + name = "firefighter helmet" + clothing_flags = STOPSPRESSUREDAMAGE + heat_protection = HEAD + max_heat_protection_temperature = FIRE_HELM_MAX_TEMP_PROTECT + cold_protection = HEAD + min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT + +/obj/item/clothing/head/hardhat/red/upgraded + name = "workplace-ready firefighter helmet" + desc = "By applying state of the art lighting technology to a fire helmet, and using photo-chemical hardening methods, this hardhat will protect you from robust workplace hazards." + icon_state = "hardhat0_purple" + item_state = "hardhat0_purple" + brightness_on = 5 + resistance_flags = FIRE_PROOF | ACID_PROOF + custom_materials = list(/datum/material/iron = 4000, /datum/material/glass = 1000, /datum/material/plastic = 3000, /datum/material/silver = 500) + hat_type = "purple" + +/obj/item/clothing/head/hardhat/white + icon_state = "hardhat0_white" + item_state = "hardhat0_white" + hat_type = "white" + clothing_flags = STOPSPRESSUREDAMAGE + heat_protection = HEAD + max_heat_protection_temperature = FIRE_HELM_MAX_TEMP_PROTECT + cold_protection = HEAD + min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT + dog_fashion = /datum/dog_fashion/head + +/obj/item/clothing/head/hardhat/dblue + icon_state = "hardhat0_dblue" + item_state = "hardhat0_dblue" + hat_type = "dblue" + dog_fashion = null + +/obj/item/clothing/head/hardhat/atmos + icon_state = "hardhat0_atmos" + item_state = "hardhat0_atmos" + hat_type = "atmos" + dog_fashion = null + name = "atmospheric technician's firefighting helmet" + desc = "A firefighter's helmet, able to keep the user cool in any situation." + clothing_flags = STOPSPRESSUREDAMAGE | THICKMATERIAL | BLOCK_GAS_SMOKE_EFFECT + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + heat_protection = HEAD + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + cold_protection = HEAD + min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT + flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF + +/obj/item/clothing/head/hardhat/weldhat + name = "welding hard hat" + desc = "A piece of headgear used in dangerous working conditions to protect the head. Comes with a built-in flashlight AND welding shield! The bulb seems a little smaller though." + brightness_on = 3 //Needs a little bit of tradeoff + dog_fashion = null + actions_types = list(/datum/action/item_action/toggle_helmet_light, /datum/action/item_action/toggle_welding_screen) + flash_protect = FLASH_PROTECTION_WELDER + tint = 2 + flags_inv = HIDEEYES | HIDEFACE + flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH + visor_vars_to_toggle = VISOR_FLASHPROTECT | VISOR_TINT + visor_flags_inv = HIDEEYES | HIDEFACE + visor_flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF + +/obj/item/clothing/head/hardhat/weldhat/Initialize() + . = ..() + update_icon() + +/obj/item/clothing/head/hardhat/weldhat/attack_self(mob/living/user) + toggle_helmet_light(user) + +/obj/item/clothing/head/hardhat/weldhat/AltClick(mob/user) + if(user.canUseTopic(src, BE_CLOSE)) + toggle_welding_screen(user) + +/obj/item/clothing/head/hardhat/weldhat/proc/toggle_welding_screen(mob/living/user) + if(weldingvisortoggle(user)) + playsound(src, 'sound/mecha/mechmove03.ogg', 50, TRUE) //Visors don't just come from nothing + update_icon() + +/obj/item/clothing/head/hardhat/weldhat/worn_overlays(isinhands) + . = ..() + if(!isinhands) + . += mutable_appearance('icons/mob/clothing/head.dmi', "weldhelmet") + if(!up) + . += mutable_appearance('icons/mob/clothing/head.dmi', "weldvisor") + +/obj/item/clothing/head/hardhat/weldhat/update_overlays() + . = ..() + if(!up) + . += "weldvisor" + +/obj/item/clothing/head/hardhat/weldhat/orange + icon_state = "hardhat0_orange" + item_state = "hardhat0_orange" + hat_type = "orange" + +/obj/item/clothing/head/hardhat/weldhat/white + desc = "A piece of headgear used in dangerous working conditions to protect the head. Comes with a built-in flashlight AND welding shield!" //This bulb is not smaller + icon_state = "hardhat0_white" + item_state = "hardhat0_white" + brightness_on = 4 //Boss always takes the best stuff + hat_type = "white" + clothing_flags = STOPSPRESSUREDAMAGE + heat_protection = HEAD + max_heat_protection_temperature = FIRE_HELM_MAX_TEMP_PROTECT + cold_protection = HEAD + min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT + +/obj/item/clothing/head/hardhat/weldhat/dblue + icon_state = "hardhat0_dblue" + item_state = "hardhat0_dblue" + hat_type = "dblue" diff --git a/code/modules/clothing/head/helmet.dm b/code/modules/clothing/head/helmet.dm index f2893724900c..4f76a268c293 100644 --- a/code/modules/clothing/head/helmet.dm +++ b/code/modules/clothing/head/helmet.dm @@ -1,426 +1,426 @@ -/obj/item/clothing/head/helmet - name = "helmet" - desc = "Standard Security gear. Protects the head from impacts." - icon_state = "helmet" - item_state = "helmet" - armor = list("melee" = 35, "bullet" = 30, "laser" = 30,"energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - cold_protection = HEAD - min_cold_protection_temperature = HELMET_MIN_TEMP_PROTECT - heat_protection = HEAD - max_heat_protection_temperature = HELMET_MAX_TEMP_PROTECT - strip_delay = 60 - clothing_flags = SNUG_FIT - flags_cover = HEADCOVERSEYES - flags_inv = HIDEHAIR - - dog_fashion = /datum/dog_fashion/head/helmet - - var/can_flashlight = FALSE //if a flashlight can be mounted. if it has a flashlight and this is false, it is permanently attached. - var/obj/item/flashlight/seclite/attached_light - var/datum/action/item_action/toggle_helmet_flashlight/alight - -/obj/item/clothing/head/helmet/Initialize() - . = ..() - if(attached_light) - alight = new(src) - -/obj/item/clothing/head/helmet/examine(mob/user) - . = ..() - if(attached_light) - . += "It has \a [attached_light] [can_flashlight ? "" : "permanently "]mounted on it." - if(can_flashlight) - . += "[attached_light] looks like it can be unscrewed from [src]." - else if(can_flashlight) - . += "It has a mounting point for a seclite." - -/obj/item/clothing/head/helmet/Destroy() - QDEL_NULL(attached_light) - return ..() - -/obj/item/clothing/head/helmet/handle_atom_del(atom/A) - if(A == attached_light) - attached_light = null - update_helmlight() - update_icon() - QDEL_NULL(alight) - return ..() - -/obj/item/clothing/head/helmet/sec - can_flashlight = TRUE - -/obj/item/clothing/head/helmet/sec/attackby(obj/item/I, mob/user, params) - if(issignaler(I)) - var/obj/item/assembly/signaler/S = I - if(attached_light) //Has a flashlight. Player must remove it, else it will be lost forever. - to_chat(user, "The mounted flashlight is in the way, remove it first!") - return - - if(S.secured) - qdel(S) - var/obj/item/bot_assembly/secbot/A = new - user.put_in_hands(A) - to_chat(user, "You add the signaler to the helmet.") - qdel(src) - return - return ..() - -/obj/item/clothing/head/helmet/alt - name = "bulletproof helmet" - desc = "A bulletproof combat helmet that excels in protecting the wearer against traditional projectile weaponry and explosives to a minor extent." - icon_state = "helmetalt" - item_state = "helmetalt" - armor = list("melee" = 15, "bullet" = 60, "laser" = 10, "energy" = 10, "bomb" = 40, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - can_flashlight = TRUE - dog_fashion = null - -/obj/item/clothing/head/helmet/old - name = "degrading helmet" - desc = "Standard issue security helmet. Due to degradation the helmet's visor obstructs the users ability to see long distances." - tint = 2 - -/obj/item/clothing/head/helmet/blueshirt - name = "blue helmet" - desc = "A reliable, blue tinted helmet reminding you that you still owe that engineer a beer." - icon_state = "blueshift" - item_state = "blueshift" - custom_premium_price = 750 - -/obj/item/clothing/head/helmet/riot - name = "riot helmet" - desc = "It's a helmet specifically designed to protect against close range attacks." - icon_state = "riot" - item_state = "helmet" - toggle_message = "You pull the visor down on" - alt_toggle_message = "You push the visor up on" - can_toggle = 1 - armor = list("melee" = 50, "bullet" = 10, "laser" = 10, "energy" = 10, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80) - flags_inv = HIDEEARS|HIDEFACE - strip_delay = 80 - actions_types = list(/datum/action/item_action/toggle) - visor_flags_inv = HIDEFACE - toggle_cooldown = 0 - flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF - visor_flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF - dog_fashion = null - -/obj/item/clothing/head/helmet/attack_self(mob/user) - if(can_toggle && !user.incapacitated()) - if(world.time > cooldown + toggle_cooldown) - cooldown = world.time - up = !up - flags_1 ^= visor_flags - flags_inv ^= visor_flags_inv - flags_cover ^= visor_flags_cover - icon_state = "[initial(icon_state)][up ? "up" : ""]" - to_chat(user, "[up ? alt_toggle_message : toggle_message] \the [src].") - - user.update_inv_head() - if(iscarbon(user)) - var/mob/living/carbon/C = user - C.head_update(src, forced = 1) - - if(active_sound) - while(up) - playsound(src, "[active_sound]", 100, FALSE, 4) - sleep(15) - -/obj/item/clothing/head/helmet/justice - name = "helmet of justice" - desc = "WEEEEOOO. WEEEEEOOO. WEEEEOOOO." - icon_state = "justice" - toggle_message = "You turn off the lights on" - alt_toggle_message = "You turn on the lights on" - actions_types = list(/datum/action/item_action/toggle_helmet_light) - can_toggle = 1 - toggle_cooldown = 20 - active_sound = 'sound/items/weeoo1.ogg' - dog_fashion = null - -/obj/item/clothing/head/helmet/justice/escape - name = "alarm helmet" - desc = "WEEEEOOO. WEEEEEOOO. STOP THAT MONKEY. WEEEOOOO." - icon_state = "justice2" - toggle_message = "You turn off the light on" - alt_toggle_message = "You turn on the light on" - -/obj/item/clothing/head/helmet/swat - name = "\improper SWAT helmet" - desc = "An extremely robust, space-worthy helmet in a nefarious red and black stripe pattern." - icon_state = "swatsyndie" - item_state = "swatsyndie" - armor = list("melee" = 40, "bullet" = 30, "laser" = 30,"energy" = 40, "bomb" = 50, "bio" = 90, "rad" = 20, "fire" = 100, "acid" = 100) - cold_protection = HEAD - min_cold_protection_temperature = SPACE_HELM_MIN_TEMP_PROTECT - heat_protection = HEAD - max_heat_protection_temperature = SPACE_HELM_MAX_TEMP_PROTECT - clothing_flags = STOPSPRESSUREDAMAGE - strip_delay = 80 - resistance_flags = FIRE_PROOF | ACID_PROOF - dog_fashion = null - -/obj/item/clothing/head/helmet/police - name = "police officer's hat" - desc = "A police officer's Hat. This hat emphasizes that you are THE LAW." - icon_state = "policehelm" - dynamic_hair_suffix = "" - -/obj/item/clothing/head/helmet/constable - name = "constable helmet" - desc = "A british looking helmet." - mob_overlay_icon = 'icons/mob/large-worn-icons/64x64/head.dmi' - icon_state = "constable" - item_state = "constable" - worn_x_dimension = 64 - worn_y_dimension = 64 - custom_price = 350 - -/obj/item/clothing/head/helmet/swat/nanotrasen - name = "\improper SWAT helmet" - desc = "An extremely robust, space-worthy helmet with the Nanotrasen logo emblazoned on the top." - icon_state = "swat" - item_state = "swat" - -/obj/item/clothing/head/helmet/thunderdome - name = "\improper Thunderdome helmet" - desc = "'Let the battle commence!'" - flags_inv = HIDEEARS|HIDEHAIR - icon_state = "thunderdome" - item_state = "thunderdome" - armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 50, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 90) - cold_protection = HEAD - min_cold_protection_temperature = SPACE_HELM_MIN_TEMP_PROTECT - heat_protection = HEAD - max_heat_protection_temperature = SPACE_HELM_MAX_TEMP_PROTECT - strip_delay = 80 - dog_fashion = null - -/obj/item/clothing/head/helmet/roman - name = "\improper Roman helmet" - desc = "An ancient helmet made of bronze and leather." - flags_inv = HIDEEARS|HIDEHAIR - flags_cover = HEADCOVERSEYES - armor = list("melee" = 25, "bullet" = 0, "laser" = 25, "energy" = 10, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50) - resistance_flags = FIRE_PROOF - icon_state = "roman" - item_state = "roman" - strip_delay = 100 - dog_fashion = null - -/obj/item/clothing/head/helmet/roman/fake - desc = "An ancient helmet made of plastic and leather." - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - -/obj/item/clothing/head/helmet/roman/legionnaire - name = "\improper Roman legionnaire helmet" - desc = "An ancient helmet made of bronze and leather. Has a red crest on top of it." - icon_state = "roman_c" - item_state = "roman_c" - -/obj/item/clothing/head/helmet/roman/legionnaire/fake - desc = "An ancient helmet made of plastic and leather. Has a red crest on top of it." - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - -/obj/item/clothing/head/helmet/gladiator - name = "gladiator helmet" - desc = "Ave, Imperator, morituri te salutant." - icon_state = "gladiator" - item_state = "gladiator" - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEHAIR - flags_cover = HEADCOVERSEYES - dog_fashion = null - -/obj/item/clothing/head/helmet/redtaghelm - name = "red laser tag helmet" - desc = "They have chosen their own end." - icon_state = "redtaghelm" - flags_cover = HEADCOVERSEYES - item_state = "redtaghelm" - armor = list("melee" = 15, "bullet" = 10, "laser" = 20,"energy" = 10, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 50) - // Offer about the same protection as a hardhat. - dog_fashion = null - -/obj/item/clothing/head/helmet/bluetaghelm - name = "blue laser tag helmet" - desc = "They'll need more men." - icon_state = "bluetaghelm" - flags_cover = HEADCOVERSEYES - item_state = "bluetaghelm" - armor = list("melee" = 15, "bullet" = 10, "laser" = 20,"energy" = 10, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 50) - // Offer about the same protection as a hardhat. - dog_fashion = null - -/obj/item/clothing/head/helmet/knight - name = "medieval helmet" - desc = "A classic metal helmet." - icon_state = "knight_green" - item_state = "knight_green" - armor = list("melee" = 50, "bullet" = 10, "laser" = 10, "energy" = 10, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80) - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR - flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH - strip_delay = 80 - dog_fashion = null - - -/obj/item/clothing/head/helmet/knight/Initialize(mapload) - . = ..() - var/datum/component = GetComponent(/datum/component/wearertargeting/earprotection) - qdel(component) - -/obj/item/clothing/head/helmet/knight/blue - icon_state = "knight_blue" - item_state = "knight_blue" - -/obj/item/clothing/head/helmet/knight/yellow - icon_state = "knight_yellow" - item_state = "knight_yellow" - -/obj/item/clothing/head/helmet/knight/red - icon_state = "knight_red" - item_state = "knight_red" - -/obj/item/clothing/head/helmet/knight/greyscale - name = "knight helmet" - desc = "A classic medieval helmet, if you hold it upside down you could see that it's actually a bucket." - icon_state = "knight_greyscale" - item_state = "knight_greyscale" - armor = list("melee" = 35, "bullet" = 10, "laser" = 10, "energy" = 10, "bomb" = 10, "bio" = 10, "rad" = 10, "fire" = 40, "acid" = 40) - material_flags = MATERIAL_ADD_PREFIX | MATERIAL_COLOR | MATERIAL_AFFECT_STATISTICS //Can change color and add prefix - -/obj/item/clothing/head/helmet/skull - name = "skull helmet" - desc = "An intimidating tribal helmet, it doesn't look very comfortable." - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE - flags_cover = HEADCOVERSEYES - armor = list("melee" = 35, "bullet" = 25, "laser" = 25, "energy" = 35, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - icon_state = "skull" - item_state = "skull" - strip_delay = 100 - -/obj/item/clothing/head/helmet/durathread - name = "durathread helmet" - desc = "A helmet made from durathread and leather." - icon_state = "durathread" - item_state = "durathread" - resistance_flags = FLAMMABLE - armor = list("melee" = 20, "bullet" = 10, "laser" = 30, "energy" = 40, "bomb" = 15, "bio" = 0, "rad" = 0, "fire" = 40, "acid" = 50) - strip_delay = 60 - -/obj/item/clothing/head/helmet/rus_helmet - name = "russian helmet" - desc = "It can hold a bottle of vodka." - icon_state = "rus_helmet" - item_state = "rus_helmet" - armor = list("melee" = 25, "bullet" = 30, "laser" = 0, "energy" = 10, "bomb" = 10, "bio" = 0, "rad" = 20, "fire" = 20, "acid" = 50) - pocket_storage_component_path = /datum/component/storage/concrete/pockets/helmet - -/obj/item/clothing/head/helmet/rus_ushanka - name = "battle ushanka" - desc = "100% bear." - icon_state = "rus_ushanka" - item_state = "rus_ushanka" - body_parts_covered = HEAD - cold_protection = HEAD - min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT - armor = list("melee" = 25, "bullet" = 20, "laser" = 20, "energy" = 30, "bomb" = 20, "bio" = 50, "rad" = 20, "fire" = -10, "acid" = 50) - -/obj/item/clothing/head/helmet/infiltrator - name = "infiltrator helmet" - desc = "The galaxy isn't big enough for the two of us." - icon_state = "infiltrator" - item_state = "infiltrator" - armor = list("melee" = 40, "bullet" = 40, "laser" = 30, "energy" = 40, "bomb" = 70, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - resistance_flags = FIRE_PROOF | ACID_PROOF - flash_protect = FLASH_PROTECTION_WELDER - flags_inv = HIDEHAIR|HIDEFACIALHAIR|HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE - flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF - strip_delay = 80 - - -//LightToggle - -/obj/item/clothing/head/helmet/ComponentInitialize() - . = ..() - AddElement(/datum/element/update_icon_updates_onmob) - -/obj/item/clothing/head/helmet/update_icon_state() - var/state = "[initial(icon_state)]" - if(attached_light) - if(attached_light.on) - state += "-flight-on" //"helmet-flight-on" // "helmet-cam-flight-on" - else - state += "-flight" //etc. - - icon_state = state - -/obj/item/clothing/head/helmet/ui_action_click(mob/user, action) - if(istype(action, alight)) - toggle_helmlight() - else - ..() - -/obj/item/clothing/head/helmet/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/flashlight/seclite)) - var/obj/item/flashlight/seclite/S = I - if(can_flashlight && !attached_light) - if(!user.transferItemToLoc(S, src)) - return - to_chat(user, "You click [S] into place on [src].") - if(S.on) - set_light(0) - attached_light = S - update_icon() - update_helmlight() - alight = new(src) - if(loc == user) - alight.Grant(user) - return - return ..() - -/obj/item/clothing/head/helmet/screwdriver_act(mob/living/user, obj/item/I) - . = ..() - if(can_flashlight && attached_light) //if it has a light but can_flashlight is false, the light is permanently attached. - I.play_tool_sound(src) - to_chat(user, "You unscrew [attached_light] from [src].") - attached_light.forceMove(drop_location()) - if(Adjacent(user) && !issilicon(user)) - user.put_in_hands(attached_light) - - var/obj/item/flashlight/removed_light = attached_light - attached_light = null - update_helmlight() - removed_light.update_brightness(user) - update_icon() - user.update_inv_head() - QDEL_NULL(alight) - return TRUE - -/obj/item/clothing/head/helmet/proc/toggle_helmlight() - set name = "Toggle Helmetlight" - set category = "Object" - set desc = "Click to toggle your helmet's attached flashlight." - - if(!attached_light) - return - - var/mob/user = usr - if(user.incapacitated()) - return - attached_light.on = !attached_light.on - to_chat(user, "You toggle the helmet-light [attached_light.on ? "on":"off"].") - - playsound(user, 'sound/weapons/empty.ogg', 100, TRUE) - update_helmlight() - -/obj/item/clothing/head/helmet/proc/update_helmlight() - if(attached_light) - if(attached_light.on) - set_light(attached_light.brightness_on, attached_light.flashlight_power,attached_light.light_color) - else - set_light(0) - update_icon() - - else - set_light(0) - for(var/X in actions) - var/datum/action/A = X - A.UpdateButtonIcon() +/obj/item/clothing/head/helmet + name = "helmet" + desc = "Standard Security gear. Protects the head from impacts." + icon_state = "helmet" + item_state = "helmet" + armor = list("melee" = 35, "bullet" = 30, "laser" = 30,"energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + cold_protection = HEAD + min_cold_protection_temperature = HELMET_MIN_TEMP_PROTECT + heat_protection = HEAD + max_heat_protection_temperature = HELMET_MAX_TEMP_PROTECT + strip_delay = 60 + clothing_flags = SNUG_FIT + flags_cover = HEADCOVERSEYES + flags_inv = HIDEHAIR + + dog_fashion = /datum/dog_fashion/head/helmet + + var/can_flashlight = FALSE //if a flashlight can be mounted. if it has a flashlight and this is false, it is permanently attached. + var/obj/item/flashlight/seclite/attached_light + var/datum/action/item_action/toggle_helmet_flashlight/alight + +/obj/item/clothing/head/helmet/Initialize() + . = ..() + if(attached_light) + alight = new(src) + +/obj/item/clothing/head/helmet/examine(mob/user) + . = ..() + if(attached_light) + . += "It has \a [attached_light] [can_flashlight ? "" : "permanently "]mounted on it." + if(can_flashlight) + . += "[attached_light] looks like it can be unscrewed from [src]." + else if(can_flashlight) + . += "It has a mounting point for a seclite." + +/obj/item/clothing/head/helmet/Destroy() + QDEL_NULL(attached_light) + return ..() + +/obj/item/clothing/head/helmet/handle_atom_del(atom/A) + if(A == attached_light) + attached_light = null + update_helmlight() + update_icon() + QDEL_NULL(alight) + return ..() + +/obj/item/clothing/head/helmet/sec + can_flashlight = TRUE + +/obj/item/clothing/head/helmet/sec/attackby(obj/item/I, mob/user, params) + if(issignaler(I)) + var/obj/item/assembly/signaler/S = I + if(attached_light) //Has a flashlight. Player must remove it, else it will be lost forever. + to_chat(user, "The mounted flashlight is in the way, remove it first!") + return + + if(S.secured) + qdel(S) + var/obj/item/bot_assembly/secbot/A = new + user.put_in_hands(A) + to_chat(user, "You add the signaler to the helmet.") + qdel(src) + return + return ..() + +/obj/item/clothing/head/helmet/alt + name = "bulletproof helmet" + desc = "A bulletproof combat helmet that excels in protecting the wearer against traditional projectile weaponry and explosives to a minor extent." + icon_state = "helmetalt" + item_state = "helmetalt" + armor = list("melee" = 15, "bullet" = 60, "laser" = 10, "energy" = 10, "bomb" = 40, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + can_flashlight = TRUE + dog_fashion = null + +/obj/item/clothing/head/helmet/old + name = "degrading helmet" + desc = "Standard issue security helmet. Due to degradation the helmet's visor obstructs the users ability to see long distances." + tint = 2 + +/obj/item/clothing/head/helmet/blueshirt + name = "blue helmet" + desc = "A reliable, blue tinted helmet reminding you that you still owe that engineer a beer." + icon_state = "blueshift" + item_state = "blueshift" + custom_premium_price = 750 + +/obj/item/clothing/head/helmet/riot + name = "riot helmet" + desc = "It's a helmet specifically designed to protect against close range attacks." + icon_state = "riot" + item_state = "helmet" + toggle_message = "You pull the visor down on" + alt_toggle_message = "You push the visor up on" + can_toggle = 1 + armor = list("melee" = 50, "bullet" = 10, "laser" = 10, "energy" = 10, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80) + flags_inv = HIDEEARS|HIDEFACE + strip_delay = 80 + actions_types = list(/datum/action/item_action/toggle) + visor_flags_inv = HIDEFACE + toggle_cooldown = 0 + flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF + visor_flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF + dog_fashion = null + +/obj/item/clothing/head/helmet/attack_self(mob/user) + if(can_toggle && !user.incapacitated()) + if(world.time > cooldown + toggle_cooldown) + cooldown = world.time + up = !up + flags_1 ^= visor_flags + flags_inv ^= visor_flags_inv + flags_cover ^= visor_flags_cover + icon_state = "[initial(icon_state)][up ? "up" : ""]" + to_chat(user, "[up ? alt_toggle_message : toggle_message] \the [src].") + + user.update_inv_head() + if(iscarbon(user)) + var/mob/living/carbon/C = user + C.head_update(src, forced = 1) + + if(active_sound) + while(up) + playsound(src, "[active_sound]", 100, FALSE, 4) + sleep(15) + +/obj/item/clothing/head/helmet/justice + name = "helmet of justice" + desc = "WEEEEOOO. WEEEEEOOO. WEEEEOOOO." + icon_state = "justice" + toggle_message = "You turn off the lights on" + alt_toggle_message = "You turn on the lights on" + actions_types = list(/datum/action/item_action/toggle_helmet_light) + can_toggle = 1 + toggle_cooldown = 20 + active_sound = 'sound/items/weeoo1.ogg' + dog_fashion = null + +/obj/item/clothing/head/helmet/justice/escape + name = "alarm helmet" + desc = "WEEEEOOO. WEEEEEOOO. STOP THAT MONKEY. WEEEOOOO." + icon_state = "justice2" + toggle_message = "You turn off the light on" + alt_toggle_message = "You turn on the light on" + +/obj/item/clothing/head/helmet/swat + name = "\improper SWAT helmet" + desc = "An extremely robust, space-worthy helmet in a nefarious red and black stripe pattern." + icon_state = "swatsyndie" + item_state = "swatsyndie" + armor = list("melee" = 40, "bullet" = 30, "laser" = 30,"energy" = 40, "bomb" = 50, "bio" = 90, "rad" = 20, "fire" = 100, "acid" = 100) + cold_protection = HEAD + min_cold_protection_temperature = SPACE_HELM_MIN_TEMP_PROTECT + heat_protection = HEAD + max_heat_protection_temperature = SPACE_HELM_MAX_TEMP_PROTECT + clothing_flags = STOPSPRESSUREDAMAGE + strip_delay = 80 + resistance_flags = FIRE_PROOF | ACID_PROOF + dog_fashion = null + +/obj/item/clothing/head/helmet/police + name = "police officer's hat" + desc = "A police officer's Hat. This hat emphasizes that you are THE LAW." + icon_state = "policehelm" + dynamic_hair_suffix = "" + +/obj/item/clothing/head/helmet/constable + name = "constable helmet" + desc = "A british looking helmet." + mob_overlay_icon = 'icons/mob/large-worn-icons/64x64/head.dmi' + icon_state = "constable" + item_state = "constable" + worn_x_dimension = 64 + worn_y_dimension = 64 + custom_price = 350 + +/obj/item/clothing/head/helmet/swat/nanotrasen + name = "\improper SWAT helmet" + desc = "An extremely robust, space-worthy helmet with the Nanotrasen logo emblazoned on the top." + icon_state = "swat" + item_state = "swat" + +/obj/item/clothing/head/helmet/thunderdome + name = "\improper Thunderdome helmet" + desc = "'Let the battle commence!'" + flags_inv = HIDEEARS|HIDEHAIR + icon_state = "thunderdome" + item_state = "thunderdome" + armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 50, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 90) + cold_protection = HEAD + min_cold_protection_temperature = SPACE_HELM_MIN_TEMP_PROTECT + heat_protection = HEAD + max_heat_protection_temperature = SPACE_HELM_MAX_TEMP_PROTECT + strip_delay = 80 + dog_fashion = null + +/obj/item/clothing/head/helmet/roman + name = "\improper Roman helmet" + desc = "An ancient helmet made of bronze and leather." + flags_inv = HIDEEARS|HIDEHAIR + flags_cover = HEADCOVERSEYES + armor = list("melee" = 25, "bullet" = 0, "laser" = 25, "energy" = 10, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50) + resistance_flags = FIRE_PROOF + icon_state = "roman" + item_state = "roman" + strip_delay = 100 + dog_fashion = null + +/obj/item/clothing/head/helmet/roman/fake + desc = "An ancient helmet made of plastic and leather." + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + +/obj/item/clothing/head/helmet/roman/legionnaire + name = "\improper Roman legionnaire helmet" + desc = "An ancient helmet made of bronze and leather. Has a red crest on top of it." + icon_state = "roman_c" + item_state = "roman_c" + +/obj/item/clothing/head/helmet/roman/legionnaire/fake + desc = "An ancient helmet made of plastic and leather. Has a red crest on top of it." + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + +/obj/item/clothing/head/helmet/gladiator + name = "gladiator helmet" + desc = "Ave, Imperator, morituri te salutant." + icon_state = "gladiator" + item_state = "gladiator" + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEHAIR + flags_cover = HEADCOVERSEYES + dog_fashion = null + +/obj/item/clothing/head/helmet/redtaghelm + name = "red laser tag helmet" + desc = "They have chosen their own end." + icon_state = "redtaghelm" + flags_cover = HEADCOVERSEYES + item_state = "redtaghelm" + armor = list("melee" = 15, "bullet" = 10, "laser" = 20,"energy" = 10, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 50) + // Offer about the same protection as a hardhat. + dog_fashion = null + +/obj/item/clothing/head/helmet/bluetaghelm + name = "blue laser tag helmet" + desc = "They'll need more men." + icon_state = "bluetaghelm" + flags_cover = HEADCOVERSEYES + item_state = "bluetaghelm" + armor = list("melee" = 15, "bullet" = 10, "laser" = 20,"energy" = 10, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 50) + // Offer about the same protection as a hardhat. + dog_fashion = null + +/obj/item/clothing/head/helmet/knight + name = "medieval helmet" + desc = "A classic metal helmet." + icon_state = "knight_green" + item_state = "knight_green" + armor = list("melee" = 50, "bullet" = 10, "laser" = 10, "energy" = 10, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80) + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR + flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH + strip_delay = 80 + dog_fashion = null + + +/obj/item/clothing/head/helmet/knight/Initialize(mapload) + . = ..() + var/datum/component = GetComponent(/datum/component/wearertargeting/earprotection) + qdel(component) + +/obj/item/clothing/head/helmet/knight/blue + icon_state = "knight_blue" + item_state = "knight_blue" + +/obj/item/clothing/head/helmet/knight/yellow + icon_state = "knight_yellow" + item_state = "knight_yellow" + +/obj/item/clothing/head/helmet/knight/red + icon_state = "knight_red" + item_state = "knight_red" + +/obj/item/clothing/head/helmet/knight/greyscale + name = "knight helmet" + desc = "A classic medieval helmet, if you hold it upside down you could see that it's actually a bucket." + icon_state = "knight_greyscale" + item_state = "knight_greyscale" + armor = list("melee" = 35, "bullet" = 10, "laser" = 10, "energy" = 10, "bomb" = 10, "bio" = 10, "rad" = 10, "fire" = 40, "acid" = 40) + material_flags = MATERIAL_ADD_PREFIX | MATERIAL_COLOR | MATERIAL_AFFECT_STATISTICS //Can change color and add prefix + +/obj/item/clothing/head/helmet/skull + name = "skull helmet" + desc = "An intimidating tribal helmet, it doesn't look very comfortable." + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE + flags_cover = HEADCOVERSEYES + armor = list("melee" = 35, "bullet" = 25, "laser" = 25, "energy" = 35, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + icon_state = "skull" + item_state = "skull" + strip_delay = 100 + +/obj/item/clothing/head/helmet/durathread + name = "durathread helmet" + desc = "A helmet made from durathread and leather." + icon_state = "durathread" + item_state = "durathread" + resistance_flags = FLAMMABLE + armor = list("melee" = 20, "bullet" = 10, "laser" = 30, "energy" = 40, "bomb" = 15, "bio" = 0, "rad" = 0, "fire" = 40, "acid" = 50) + strip_delay = 60 + +/obj/item/clothing/head/helmet/rus_helmet + name = "russian helmet" + desc = "It can hold a bottle of vodka." + icon_state = "rus_helmet" + item_state = "rus_helmet" + armor = list("melee" = 25, "bullet" = 30, "laser" = 0, "energy" = 10, "bomb" = 10, "bio" = 0, "rad" = 20, "fire" = 20, "acid" = 50) + pocket_storage_component_path = /datum/component/storage/concrete/pockets/helmet + +/obj/item/clothing/head/helmet/rus_ushanka + name = "battle ushanka" + desc = "100% bear." + icon_state = "rus_ushanka" + item_state = "rus_ushanka" + body_parts_covered = HEAD + cold_protection = HEAD + min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT + armor = list("melee" = 25, "bullet" = 20, "laser" = 20, "energy" = 30, "bomb" = 20, "bio" = 50, "rad" = 20, "fire" = -10, "acid" = 50) + +/obj/item/clothing/head/helmet/infiltrator + name = "infiltrator helmet" + desc = "The galaxy isn't big enough for the two of us." + icon_state = "infiltrator" + item_state = "infiltrator" + armor = list("melee" = 40, "bullet" = 40, "laser" = 30, "energy" = 40, "bomb" = 70, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + resistance_flags = FIRE_PROOF | ACID_PROOF + flash_protect = FLASH_PROTECTION_WELDER + flags_inv = HIDEHAIR|HIDEFACIALHAIR|HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE + flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF + strip_delay = 80 + + +//LightToggle + +/obj/item/clothing/head/helmet/ComponentInitialize() + . = ..() + AddElement(/datum/element/update_icon_updates_onmob) + +/obj/item/clothing/head/helmet/update_icon_state() + var/state = "[initial(icon_state)]" + if(attached_light) + if(attached_light.on) + state += "-flight-on" //"helmet-flight-on" // "helmet-cam-flight-on" + else + state += "-flight" //etc. + + icon_state = state + +/obj/item/clothing/head/helmet/ui_action_click(mob/user, action) + if(istype(action, alight)) + toggle_helmlight() + else + ..() + +/obj/item/clothing/head/helmet/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/flashlight/seclite)) + var/obj/item/flashlight/seclite/S = I + if(can_flashlight && !attached_light) + if(!user.transferItemToLoc(S, src)) + return + to_chat(user, "You click [S] into place on [src].") + if(S.on) + set_light(0) + attached_light = S + update_icon() + update_helmlight() + alight = new(src) + if(loc == user) + alight.Grant(user) + return + return ..() + +/obj/item/clothing/head/helmet/screwdriver_act(mob/living/user, obj/item/I) + . = ..() + if(can_flashlight && attached_light) //if it has a light but can_flashlight is false, the light is permanently attached. + I.play_tool_sound(src) + to_chat(user, "You unscrew [attached_light] from [src].") + attached_light.forceMove(drop_location()) + if(Adjacent(user) && !issilicon(user)) + user.put_in_hands(attached_light) + + var/obj/item/flashlight/removed_light = attached_light + attached_light = null + update_helmlight() + removed_light.update_brightness(user) + update_icon() + user.update_inv_head() + QDEL_NULL(alight) + return TRUE + +/obj/item/clothing/head/helmet/proc/toggle_helmlight() + set name = "Toggle Helmetlight" + set category = "Object" + set desc = "Click to toggle your helmet's attached flashlight." + + if(!attached_light) + return + + var/mob/user = usr + if(user.incapacitated()) + return + attached_light.on = !attached_light.on + to_chat(user, "You toggle the helmet-light [attached_light.on ? "on":"off"].") + + playsound(user, 'sound/weapons/empty.ogg', 100, TRUE) + update_helmlight() + +/obj/item/clothing/head/helmet/proc/update_helmlight() + if(attached_light) + if(attached_light.on) + set_light(attached_light.brightness_on, attached_light.flashlight_power,attached_light.light_color) + else + set_light(0) + update_icon() + + else + set_light(0) + for(var/X in actions) + var/datum/action/A = X + A.UpdateButtonIcon() diff --git a/code/modules/clothing/head/jobs.dm b/code/modules/clothing/head/jobs.dm index 7002e790d730..6d420a46d189 100644 --- a/code/modules/clothing/head/jobs.dm +++ b/code/modules/clothing/head/jobs.dm @@ -1,252 +1,252 @@ -//defines the drill hat's yelling setting -#define DRILL_DEFAULT "default" -#define DRILL_SHOUTING "shouting" -#define DRILL_YELLING "yelling" -#define DRILL_CANADIAN "canadian" - -//Chef -/obj/item/clothing/head/chefhat - name = "chef's hat" - item_state = "chef" - icon_state = "chef" - desc = "The commander in chef's head wear." - strip_delay = 10 - equip_delay_other = 10 - dynamic_hair_suffix = "" - dog_fashion = /datum/dog_fashion/head/chef - -/obj/item/clothing/head/chefhat/suicide_act(mob/user) - user.visible_message("[user] is donning [src]! It looks like [user.p_theyre()] trying to become a chef.") - user.say("Bork Bork Bork!", forced = "chef hat suicide") - sleep(20) - user.visible_message("[user] climbs into an imaginary oven!") - user.say("BOOORK!", forced = "chef hat suicide") - playsound(user, 'sound/machines/ding.ogg', 50, TRUE) - return(FIRELOSS) - -//Captain -/obj/item/clothing/head/caphat - name = "captain's hat" - desc = "It's good being the king." - icon_state = "captain" - item_state = "that" - flags_inv = 0 - armor = list("melee" = 25, "bullet" = 15, "laser" = 25, "energy" = 35, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - strip_delay = 60 - dog_fashion = /datum/dog_fashion/head/captain - -//Captain: This is no longer space-worthy -/obj/item/clothing/head/caphat/parade - name = "captain's parade cap" - desc = "Worn only by Captains with an abundance of class." - icon_state = "capcap" - - dog_fashion = null - - -//Head of Personnel -/obj/item/clothing/head/hopcap - name = "head of personnel's cap" - icon_state = "hopcap" - desc = "The symbol of true bureaucratic micromanagement." - armor = list("melee" = 25, "bullet" = 15, "laser" = 25, "energy" = 35, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - dog_fashion = /datum/dog_fashion/head/head_of_personnel - -//Chaplain -/obj/item/clothing/head/nun_hood - name = "nun hood" - desc = "Maximum piety in this star system." - icon_state = "nun_hood" - flags_inv = HIDEHAIR - flags_cover = HEADCOVERSEYES - -/obj/item/clothing/head/bishopmitre - name = "bishop mitre" - desc = "An opulent hat that functions as a radio to God. Or as a lightning rod, depending on who you ask." - icon_state = "bishopmitre" - -//Detective -/obj/item/clothing/head/fedora/det_hat - name = "detective's fedora" - desc = "There's only one man who can sniff out the dirty stench of crime, and he's likely wearing this hat." - armor = list("melee" = 25, "bullet" = 5, "laser" = 25, "energy" = 35, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 50) - icon_state = "detective" - var/candy_cooldown = 0 - pocket_storage_component_path = /datum/component/storage/concrete/pockets/small/fedora/detective - dog_fashion = /datum/dog_fashion/head/detective - -/obj/item/clothing/head/fedora/det_hat/Initialize() - . = ..() - new /obj/item/reagent_containers/food/drinks/flask/det(src) - -/obj/item/clothing/head/fedora/det_hat/examine(mob/user) - . = ..() - . += "Alt-click to take a candy corn." - -/obj/item/clothing/head/fedora/det_hat/AltClick(mob/user) - if(user.canUseTopic(src, BE_CLOSE, ismonkey(user))) - ..() - if(loc == user) - if(candy_cooldown < world.time) - var/obj/item/reagent_containers/food/snacks/candy_corn/CC = new /obj/item/reagent_containers/food/snacks/candy_corn(src) - user.put_in_hands(CC) - to_chat(user, "You slip a candy corn from your hat.") - candy_cooldown = world.time+1200 - else - to_chat(user, "You just took a candy corn! You should wait a couple minutes, lest you burn through your stash.") - -/*WaspStation Edit - Berets -//Mime -/obj/item/clothing/head/beret - name = "beret" - desc = "A beret, a mime's favorite headwear." - icon_state = "beret" - dog_fashion = /datum/dog_fashion/head/beret - dynamic_hair_suffix = "+generic" // Waspstation edit - Berets - dynamic_fhair_suffix = "+generic" // Waspstation edit - Berets - -/obj/item/clothing/head/beret/vintage - name = "vintage beret" - desc = "A well-worn beret." - icon_state = "vintageberet" - dog_fashion = null - -/obj/item/clothing/head/beret/archaic - name = "archaic beret" - desc = "An absolutely ancient beret, allegedly worn by the first mime to ever step foot on a NanoTrasen station." - icon_state = "archaicberet" - dog_fashion = null - -/obj/item/clothing/head/beret/black - name = "black beret" - desc = "A black beret, perfect for war veterans and dark, brooding, anti-hero mimes." - icon_state = "beretblack" - -/obj/item/clothing/head/beret/highlander - desc = "That was white fabric. Was." - dog_fashion = null //THIS IS FOR SLAUGHTER, NOT PUPPIES - -/obj/item/clothing/head/beret/highlander/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, HIGHLANDER) - -/obj/item/clothing/head/beret/durathread - name = "durathread beret" - desc = "A beret made from durathread, its resilient fibres provide some protection to the wearer." - icon_state = "beretdurathread" - armor = list("melee" = 15, "bullet" = 5, "laser" = 15, "energy" = 25, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 5) - -WaspStation End*/ - -//Curator -/obj/item/clothing/head/fedora/curator - name = "treasure hunter's fedora" - desc = "You got red text today kid, but it doesn't mean you have to like it." - icon_state = "curator" - -//Security - -/obj/item/clothing/head/HoS - name = "head of security cap" - desc = "The robust standard-issue cap of the Head of Security. For showing the officers who's in charge." - icon_state = "hoscap" - armor = list("melee" = 40, "bullet" = 30, "laser" = 25, "energy" = 35, "bomb" = 25, "bio" = 10, "rad" = 0, "fire" = 50, "acid" = 60) - strip_delay = 80 - dynamic_hair_suffix = "" - -/obj/item/clothing/head/HoS/syndicate - name = "syndicate cap" - desc = "A black cap fit for a high ranking syndicate officer." - -/* Wasp edit - Better Berets -/obj/item/clothing/head/HoS/beret - name = "head of security beret" - desc = "A robust beret for the Head of Security, for looking stylish while not sacrificing protection." - icon_state = "hosberetblack" -Wasp End */ - -/obj/item/clothing/head/HoS/beret/syndicate - name = "syndicate beret" - desc = "A black beret with thick armor padding inside. Stylish and robust." - icon_state = "hosberetblack" - -/obj/item/clothing/head/warden - name = "warden's police hat" - desc = "It's a special armored hat issued to the Warden of a security force. Protects the head from impacts." - icon_state = "policehelm" - armor = list("melee" = 40, "bullet" = 30, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 60) - strip_delay = 60 - dog_fashion = /datum/dog_fashion/head/warden - -/obj/item/clothing/head/warden/drill - name = "warden's campaign hat" - desc = "A special armored campaign hat with the security insignia emblazoned on it. Uses reinforced fabric to offer sufficient protection." - icon_state = "wardendrill" - item_state = "wardendrill" - dog_fashion = null - var/mode = DRILL_DEFAULT - -/obj/item/clothing/head/warden/drill/screwdriver_act(mob/living/carbon/human/user, obj/item/I) - if(..()) - return TRUE - switch(mode) - if(DRILL_DEFAULT) - to_chat(user, "You set the voice circuit to the middle position.") - mode = DRILL_SHOUTING - if(DRILL_SHOUTING) - to_chat(user, "You set the voice circuit to the last position.") - mode = DRILL_YELLING - if(DRILL_YELLING) - to_chat(user, "You set the voice circuit to the first position.") - mode = DRILL_DEFAULT - if(DRILL_CANADIAN) - to_chat(user, "You adjust voice circuit but nothing happens, probably because it's broken.") - return TRUE - -/obj/item/clothing/head/warden/drill/wirecutter_act(mob/living/user, obj/item/I) - ..() - if(mode != DRILL_CANADIAN) - to_chat(user, "You broke the voice circuit!") - mode = DRILL_CANADIAN - return TRUE - -/obj/item/clothing/head/warden/drill/equipped(mob/M, slot) - . = ..() - if (slot == ITEM_SLOT_HEAD) - RegisterSignal(M, COMSIG_MOB_SAY, .proc/handle_speech) - else - UnregisterSignal(M, COMSIG_MOB_SAY) - -/obj/item/clothing/head/warden/drill/dropped(mob/M) - . = ..() - UnregisterSignal(M, COMSIG_MOB_SAY) - -/obj/item/clothing/head/warden/drill/proc/handle_speech(datum/source, mob/speech_args) - var/message = speech_args[SPEECH_MESSAGE] - if(message[1] != "*") - switch (mode) - if(DRILL_SHOUTING) - message += "!" - if(DRILL_YELLING) - message += "!!" - if(DRILL_CANADIAN) - message = " [message]" - var/list/canadian_words = strings("canadian_replacement.json", "canadian") - - for(var/key in canadian_words) - var/value = canadian_words[key] - if(islist(value)) - value = pick(value) - - message = replacetextEx(message, " [uppertext(key)]", " [uppertext(value)]") - message = replacetextEx(message, " [capitalize(key)]", " [capitalize(value)]") - message = replacetextEx(message, " [key]", " [value]") - - if(prob(30)) - message += pick(", eh?", ", EH?") - speech_args[SPEECH_MESSAGE] = message - -#undef DRILL_DEFAULT -#undef DRILL_SHOUTING -#undef DRILL_YELLING -#undef DRILL_CANADIAN +//defines the drill hat's yelling setting +#define DRILL_DEFAULT "default" +#define DRILL_SHOUTING "shouting" +#define DRILL_YELLING "yelling" +#define DRILL_CANADIAN "canadian" + +//Chef +/obj/item/clothing/head/chefhat + name = "chef's hat" + item_state = "chef" + icon_state = "chef" + desc = "The commander in chef's head wear." + strip_delay = 10 + equip_delay_other = 10 + dynamic_hair_suffix = "" + dog_fashion = /datum/dog_fashion/head/chef + +/obj/item/clothing/head/chefhat/suicide_act(mob/user) + user.visible_message("[user] is donning [src]! It looks like [user.p_theyre()] trying to become a chef.") + user.say("Bork Bork Bork!", forced = "chef hat suicide") + sleep(20) + user.visible_message("[user] climbs into an imaginary oven!") + user.say("BOOORK!", forced = "chef hat suicide") + playsound(user, 'sound/machines/ding.ogg', 50, TRUE) + return(FIRELOSS) + +//Captain +/obj/item/clothing/head/caphat + name = "captain's hat" + desc = "It's good being the king." + icon_state = "captain" + item_state = "that" + flags_inv = 0 + armor = list("melee" = 25, "bullet" = 15, "laser" = 25, "energy" = 35, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + strip_delay = 60 + dog_fashion = /datum/dog_fashion/head/captain + +//Captain: This is no longer space-worthy +/obj/item/clothing/head/caphat/parade + name = "captain's parade cap" + desc = "Worn only by Captains with an abundance of class." + icon_state = "capcap" + + dog_fashion = null + + +//Head of Personnel +/obj/item/clothing/head/hopcap + name = "head of personnel's cap" + icon_state = "hopcap" + desc = "The symbol of true bureaucratic micromanagement." + armor = list("melee" = 25, "bullet" = 15, "laser" = 25, "energy" = 35, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + dog_fashion = /datum/dog_fashion/head/head_of_personnel + +//Chaplain +/obj/item/clothing/head/nun_hood + name = "nun hood" + desc = "Maximum piety in this star system." + icon_state = "nun_hood" + flags_inv = HIDEHAIR + flags_cover = HEADCOVERSEYES + +/obj/item/clothing/head/bishopmitre + name = "bishop mitre" + desc = "An opulent hat that functions as a radio to God. Or as a lightning rod, depending on who you ask." + icon_state = "bishopmitre" + +//Detective +/obj/item/clothing/head/fedora/det_hat + name = "detective's fedora" + desc = "There's only one man who can sniff out the dirty stench of crime, and he's likely wearing this hat." + armor = list("melee" = 25, "bullet" = 5, "laser" = 25, "energy" = 35, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 50) + icon_state = "detective" + var/candy_cooldown = 0 + pocket_storage_component_path = /datum/component/storage/concrete/pockets/small/fedora/detective + dog_fashion = /datum/dog_fashion/head/detective + +/obj/item/clothing/head/fedora/det_hat/Initialize() + . = ..() + new /obj/item/reagent_containers/food/drinks/flask/det(src) + +/obj/item/clothing/head/fedora/det_hat/examine(mob/user) + . = ..() + . += "Alt-click to take a candy corn." + +/obj/item/clothing/head/fedora/det_hat/AltClick(mob/user) + if(user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + ..() + if(loc == user) + if(candy_cooldown < world.time) + var/obj/item/reagent_containers/food/snacks/candy_corn/CC = new /obj/item/reagent_containers/food/snacks/candy_corn(src) + user.put_in_hands(CC) + to_chat(user, "You slip a candy corn from your hat.") + candy_cooldown = world.time+1200 + else + to_chat(user, "You just took a candy corn! You should wait a couple minutes, lest you burn through your stash.") + +/*WaspStation Edit - Berets +//Mime +/obj/item/clothing/head/beret + name = "beret" + desc = "A beret, a mime's favorite headwear." + icon_state = "beret" + dog_fashion = /datum/dog_fashion/head/beret + dynamic_hair_suffix = "+generic" // Waspstation edit - Berets + dynamic_fhair_suffix = "+generic" // Waspstation edit - Berets + +/obj/item/clothing/head/beret/vintage + name = "vintage beret" + desc = "A well-worn beret." + icon_state = "vintageberet" + dog_fashion = null + +/obj/item/clothing/head/beret/archaic + name = "archaic beret" + desc = "An absolutely ancient beret, allegedly worn by the first mime to ever step foot on a NanoTrasen station." + icon_state = "archaicberet" + dog_fashion = null + +/obj/item/clothing/head/beret/black + name = "black beret" + desc = "A black beret, perfect for war veterans and dark, brooding, anti-hero mimes." + icon_state = "beretblack" + +/obj/item/clothing/head/beret/highlander + desc = "That was white fabric. Was." + dog_fashion = null //THIS IS FOR SLAUGHTER, NOT PUPPIES + +/obj/item/clothing/head/beret/highlander/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, HIGHLANDER) + +/obj/item/clothing/head/beret/durathread + name = "durathread beret" + desc = "A beret made from durathread, its resilient fibres provide some protection to the wearer." + icon_state = "beretdurathread" + armor = list("melee" = 15, "bullet" = 5, "laser" = 15, "energy" = 25, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 5) + +WaspStation End*/ + +//Curator +/obj/item/clothing/head/fedora/curator + name = "treasure hunter's fedora" + desc = "You got red text today kid, but it doesn't mean you have to like it." + icon_state = "curator" + +//Security + +/obj/item/clothing/head/HoS + name = "head of security cap" + desc = "The robust standard-issue cap of the Head of Security. For showing the officers who's in charge." + icon_state = "hoscap" + armor = list("melee" = 40, "bullet" = 30, "laser" = 25, "energy" = 35, "bomb" = 25, "bio" = 10, "rad" = 0, "fire" = 50, "acid" = 60) + strip_delay = 80 + dynamic_hair_suffix = "" + +/obj/item/clothing/head/HoS/syndicate + name = "syndicate cap" + desc = "A black cap fit for a high ranking syndicate officer." + +/* Wasp edit - Better Berets +/obj/item/clothing/head/HoS/beret + name = "head of security beret" + desc = "A robust beret for the Head of Security, for looking stylish while not sacrificing protection." + icon_state = "hosberetblack" +Wasp End */ + +/obj/item/clothing/head/HoS/beret/syndicate + name = "syndicate beret" + desc = "A black beret with thick armor padding inside. Stylish and robust." + icon_state = "hosberetblack" + +/obj/item/clothing/head/warden + name = "warden's police hat" + desc = "It's a special armored hat issued to the Warden of a security force. Protects the head from impacts." + icon_state = "policehelm" + armor = list("melee" = 40, "bullet" = 30, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 60) + strip_delay = 60 + dog_fashion = /datum/dog_fashion/head/warden + +/obj/item/clothing/head/warden/drill + name = "warden's campaign hat" + desc = "A special armored campaign hat with the security insignia emblazoned on it. Uses reinforced fabric to offer sufficient protection." + icon_state = "wardendrill" + item_state = "wardendrill" + dog_fashion = null + var/mode = DRILL_DEFAULT + +/obj/item/clothing/head/warden/drill/screwdriver_act(mob/living/carbon/human/user, obj/item/I) + if(..()) + return TRUE + switch(mode) + if(DRILL_DEFAULT) + to_chat(user, "You set the voice circuit to the middle position.") + mode = DRILL_SHOUTING + if(DRILL_SHOUTING) + to_chat(user, "You set the voice circuit to the last position.") + mode = DRILL_YELLING + if(DRILL_YELLING) + to_chat(user, "You set the voice circuit to the first position.") + mode = DRILL_DEFAULT + if(DRILL_CANADIAN) + to_chat(user, "You adjust voice circuit but nothing happens, probably because it's broken.") + return TRUE + +/obj/item/clothing/head/warden/drill/wirecutter_act(mob/living/user, obj/item/I) + ..() + if(mode != DRILL_CANADIAN) + to_chat(user, "You broke the voice circuit!") + mode = DRILL_CANADIAN + return TRUE + +/obj/item/clothing/head/warden/drill/equipped(mob/M, slot) + . = ..() + if (slot == ITEM_SLOT_HEAD) + RegisterSignal(M, COMSIG_MOB_SAY, .proc/handle_speech) + else + UnregisterSignal(M, COMSIG_MOB_SAY) + +/obj/item/clothing/head/warden/drill/dropped(mob/M) + . = ..() + UnregisterSignal(M, COMSIG_MOB_SAY) + +/obj/item/clothing/head/warden/drill/proc/handle_speech(datum/source, mob/speech_args) + var/message = speech_args[SPEECH_MESSAGE] + if(message[1] != "*") + switch (mode) + if(DRILL_SHOUTING) + message += "!" + if(DRILL_YELLING) + message += "!!" + if(DRILL_CANADIAN) + message = " [message]" + var/list/canadian_words = strings("canadian_replacement.json", "canadian") + + for(var/key in canadian_words) + var/value = canadian_words[key] + if(islist(value)) + value = pick(value) + + message = replacetextEx(message, " [uppertext(key)]", " [uppertext(value)]") + message = replacetextEx(message, " [capitalize(key)]", " [capitalize(value)]") + message = replacetextEx(message, " [key]", " [value]") + + if(prob(30)) + message += pick(", eh?", ", EH?") + speech_args[SPEECH_MESSAGE] = message + +#undef DRILL_DEFAULT +#undef DRILL_SHOUTING +#undef DRILL_YELLING +#undef DRILL_CANADIAN diff --git a/code/modules/clothing/head/misc.dm b/code/modules/clothing/head/misc.dm index 62d370027498..243c78ba8dff 100644 --- a/code/modules/clothing/head/misc.dm +++ b/code/modules/clothing/head/misc.dm @@ -1,516 +1,516 @@ - -/obj/item/clothing/head/centhat - name = "\improper CentCom hat" - icon_state = "centcom" - desc = "It's good to be emperor." - item_state = "that" - flags_inv = 0 - armor = list("melee" = 30, "bullet" = 15, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - strip_delay = 80 - -/obj/item/clothing/head/spacepolice - name = "space police cap" - desc = "A blue cap for patrolling the daily beat." - icon_state = "policecap_families" - item_state = "policecap_families" - -/obj/item/clothing/head/powdered_wig - name = "powdered wig" - desc = "A powdered wig." - icon_state = "pwig" - item_state = "pwig" - -/obj/item/clothing/head/that - name = "top-hat" - desc = "It's an amish looking hat." - icon_state = "tophat" - item_state = "that" - dog_fashion = /datum/dog_fashion/head - throwforce = 1 - -/obj/item/clothing/head/canada - name = "striped red tophat" - desc = "It smells like fresh donut holes. / Il sent comme des trous de beignets frais." - icon_state = "canada" - item_state = "canada" - -/obj/item/clothing/head/redcoat - name = "redcoat's hat" - icon_state = "redcoat" - desc = "'I guess it's a redhead.'" - -/obj/item/clothing/head/mailman - name = "mailman's hat" - icon_state = "mailman" - desc = "'Right-on-time' mail service head wear." - -/obj/item/clothing/head/plaguedoctorhat - name = "plague doctor's hat" - desc = "These were once used by plague doctors. They're pretty much useless." - icon_state = "plaguedoctor" - permeability_coefficient = 0.01 - -/obj/item/clothing/head/hasturhood - name = "hastur's hood" - desc = "It's unspeakably stylish." - icon_state = "hasturhood" - flags_inv = HIDEHAIR - flags_cover = HEADCOVERSEYES - -/obj/item/clothing/head/nursehat - name = "nurse's hat" - desc = "It allows quick identification of trained medical personnel." - icon_state = "nursehat" - dynamic_hair_suffix = "" - - dog_fashion = /datum/dog_fashion/head/nurse - -/obj/item/clothing/head/syndicatefake - name = "black space-helmet replica" - icon_state = "syndicate-helm-black-red" - item_state = "syndicate-helm-black-red" - desc = "A plastic replica of a Syndicate agent's space helmet. You'll look just like a real murderous Syndicate agent in this! This is a toy, it is not made for use in space!" - clothing_flags = SNUG_FIT - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - -/obj/item/clothing/head/cueball - name = "cueball helmet" - desc = "A large, featureless white orb meant to be worn on your head. How do you even see out of this thing?" - icon_state = "cueball" - item_state="cueball" - clothing_flags = SNUG_FIT - flags_cover = HEADCOVERSEYES|HEADCOVERSMOUTH - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - -/obj/item/clothing/head/snowman - name = "Snowman Head" - desc = "A ball of white styrofoam. So festive." - icon_state = "snowman_h" - item_state = "snowman_h" - clothing_flags = SNUG_FIT - flags_cover = HEADCOVERSEYES - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - -/obj/item/clothing/head/justice - name = "justice hat" - desc = "Fight for what's righteous!" - icon_state = "justicered" - item_state = "justicered" - clothing_flags = SNUG_FIT - flags_inv = HIDEHAIR|HIDEEARS|HIDEEYES|HIDEFACE|HIDEFACIALHAIR - flags_cover = HEADCOVERSEYES - -/obj/item/clothing/head/justice/blue - icon_state = "justiceblue" - item_state = "justiceblue" - -/obj/item/clothing/head/justice/yellow - icon_state = "justiceyellow" - item_state = "justiceyellow" - -/obj/item/clothing/head/justice/green - icon_state = "justicegreen" - item_state = "justicegreen" - -/obj/item/clothing/head/justice/pink - icon_state = "justicepink" - item_state = "justicepink" - -/obj/item/clothing/head/rabbitears - name = "rabbit ears" - desc = "Wearing these makes you look useless, and only good for your sex appeal." - icon_state = "bunny" - dynamic_hair_suffix = "" - - dog_fashion = /datum/dog_fashion/head/rabbit - -/obj/item/clothing/head/pirate - name = "pirate hat" - desc = "Yarr." - icon_state = "pirate" - item_state = "pirate" - dog_fashion = /datum/dog_fashion/head/pirate - -/obj/item/clothing/head/pirate - var/datum/language/piratespeak/L = new - -/obj/item/clothing/head/pirate/equipped(mob/user, slot) - . = ..() - if(!ishuman(user)) - return - if(slot == ITEM_SLOT_HEAD) - user.grant_language(/datum/language/piratespeak/, TRUE, TRUE, LANGUAGE_HAT) - to_chat(user, "You suddenly know how to speak like a pirate!") - -/obj/item/clothing/head/pirate/dropped(mob/user) - . = ..() - if(!ishuman(user)) - return - var/mob/living/carbon/human/H = user - if(H.get_item_by_slot(ITEM_SLOT_HEAD) == src) - user.remove_language(/datum/language/piratespeak/, TRUE, TRUE, LANGUAGE_HAT) - to_chat(user, "You can no longer speak like a pirate.") - -/obj/item/clothing/head/pirate/captain - name = "pirate captain hat" - icon_state = "hgpiratecap" - item_state = "hgpiratecap" - -/obj/item/clothing/head/bandana - name = "pirate bandana" - desc = "Yarr." - icon_state = "bandana" - item_state = "bandana" - dynamic_hair_suffix = "" - -/obj/item/clothing/head/bowler - name = "bowler-hat" - desc = "Gentleman, elite aboard!" - icon_state = "bowler" - item_state = "bowler" - dynamic_hair_suffix = "" - -/obj/item/clothing/head/witchwig - name = "witch costume wig" - desc = "Eeeee~heheheheheheh!" - icon_state = "witch" - item_state = "witch" - flags_inv = HIDEHAIR - -/obj/item/clothing/head/chicken - name = "chicken suit head" - desc = "Bkaw!" - icon_state = "chickenhead" - item_state = "chickensuit" - clothing_flags = SNUG_FIT - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - -/obj/item/clothing/head/griffin - name = "griffon head" - desc = "Why not 'eagle head'? Who knows." - icon_state = "griffinhat" - item_state = "griffinhat" - clothing_flags = SNUG_FIT - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - -/obj/item/clothing/head/bearpelt - name = "bear pelt hat" - desc = "Fuzzy." - icon_state = "bearpelt" - item_state = "bearpelt" - -/obj/item/clothing/head/xenos - name = "xenos helmet" - icon_state = "xenos" - item_state = "xenos_helm" - desc = "A helmet made out of chitinous alien hide." - clothing_flags = SNUG_FIT - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH - -/obj/item/clothing/head/fedora - name = "fedora" - icon_state = "fedora" - item_state = "fedora" - desc = "A really cool hat if you're a mobster. A really lame hat if you're not." - pocket_storage_component_path = /datum/component/storage/concrete/pockets/small/fedora - -/obj/item/clothing/head/fedora/white - name = "white fedora" - icon_state = "fedora_white" - item_state = "fedora_white" - -/obj/item/clothing/head/fedora/beige - name = "beige fedora" - icon_state = "fedora_beige" - item_state = "fedora_beige" - -/obj/item/clothing/head/fedora/suicide_act(mob/user) - if(user.gender == FEMALE) - return 0 - var/mob/living/carbon/human/H = user - user.visible_message("[user] is donning [src]! It looks like [user.p_theyre()] trying to be nice to girls.") - user.say("M'lady.", forced = "fedora suicide") - sleep(10) - H.facial_hairstyle = "Neckbeard" - return(BRUTELOSS) - -/obj/item/clothing/head/sombrero - name = "sombrero" - icon_state = "sombrero" - item_state = "sombrero" - desc = "You can practically taste the fiesta." - flags_inv = HIDEHAIR - - dog_fashion = /datum/dog_fashion/head/sombrero - -/obj/item/clothing/head/sombrero/green - name = "green sombrero" - icon_state = "greensombrero" - item_state = "greensombrero" - desc = "As elegant as a dancing cactus." - flags_inv = HIDEHAIR|HIDEFACE|HIDEEARS - dog_fashion = null - -/obj/item/clothing/head/sombrero/shamebrero - name = "shamebrero" - icon_state = "shamebrero" - item_state = "shamebrero" - desc = "Once it's on, it never comes off." - dog_fashion = null - -/obj/item/clothing/head/sombrero/shamebrero/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, SHAMEBRERO_TRAIT) - -/obj/item/clothing/head/flatcap - name = "flat cap" - desc = "A working man's cap." - icon_state = "flat_cap" - item_state = "detective" - -/obj/item/clothing/head/hunter - name = "bounty hunting hat" - desc = "Ain't nobody gonna cheat the hangman in my town." - icon_state = "hunter" - item_state = "hunter" - armor = list("melee" = 5, "bullet" = 5, "laser" = 5, "energy" = 15, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - resistance_flags = FIRE_PROOF | ACID_PROOF - -/obj/item/clothing/head/cone - desc = "This cone is trying to warn you of something!" - name = "warning cone" - icon = 'icons/obj/janitor.dmi' - icon_state = "cone" - item_state = "cone" - force = 1 - throwforce = 3 - throw_speed = 2 - throw_range = 5 - w_class = WEIGHT_CLASS_SMALL - attack_verb = list("warned", "cautioned", "smashed") - resistance_flags = NONE - dynamic_hair_suffix = "" - -/obj/item/clothing/head/santa - name = "santa hat" - desc = "On the first day of christmas my employer gave to me!" - icon_state = "santahatnorm" - item_state = "that" - cold_protection = HEAD - min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT - dog_fashion = /datum/dog_fashion/head/santa - -/obj/item/clothing/head/jester - name = "jester hat" - desc = "A hat with bells, to add some merriness to the suit." - icon_state = "jester_hat" - dynamic_hair_suffix = "" - -/obj/item/clothing/head/jester/alt - icon_state = "jester2" - -/obj/item/clothing/head/rice_hat - name = "rice hat" - desc = "Welcome to the rice fields, motherfucker." - icon_state = "rice_hat" - -/obj/item/clothing/head/lizard - name = "lizardskin cloche hat" - desc = "How many lizards died to make this hat? Not enough." - icon_state = "lizard" - -/obj/item/clothing/head/papersack - name = "paper sack hat" - desc = "A paper sack with crude holes cut out for eyes. Useful for hiding one's identity or ugliness." - icon_state = "papersack" - flags_inv = HIDEHAIR|HIDEFACE|HIDEEARS - -/obj/item/clothing/head/papersack/smiley - name = "paper sack hat" - desc = "A paper sack with crude holes cut out for eyes and a sketchy smile drawn on the front. Not creepy at all." - icon_state = "papersack_smile" - flags_inv = HIDEHAIR|HIDEFACE|HIDEEARS - -/obj/item/clothing/head/crown - name = "crown" - desc = "A crown fit for a king, a petty king maybe." - icon_state = "crown" - armor = list("melee" = 15, "bullet" = 0, "laser" = 0,"energy" = 10, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50) - resistance_flags = FIRE_PROOF - dynamic_hair_suffix = "" - -/obj/item/clothing/head/crown/fancy - name = "magnificent crown" - desc = "A crown worn by only the highest emperors of the land space." - icon_state = "fancycrown" - -/obj/item/clothing/head/scarecrow_hat - name = "scarecrow hat" - desc = "A simple straw hat." - icon_state = "scarecrow_hat" - -/obj/item/clothing/head/lobsterhat - name = "foam lobster head" - desc = "When everything's going to crab, protecting your head is the best choice." - icon_state = "lobster_hat" - clothing_flags = SNUG_FIT - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - -/obj/item/clothing/head/drfreezehat - name = "doctor freeze's wig" - desc = "A cool wig for cool people." - icon_state = "drfreeze_hat" - flags_inv = HIDEHAIR - -/obj/item/clothing/head/pharaoh - name = "pharaoh hat" - desc = "Walk like an Egyptian." - icon_state = "pharoah_hat" - item_state = "pharoah_hat" - -/obj/item/clothing/head/nemes - name = "headdress of Nemes" - desc = "Lavish space tomb not included." - icon_state = "nemes_headdress" - -/obj/item/clothing/head/delinquent - name = "delinquent hat" - desc = "Good grief." - icon_state = "delinquent" - -/obj/item/clothing/head/frenchberet - name = "french beret" - desc = "A quality beret, infused with the aroma of chain-smoking, wine-swilling Parisians. You feel less inclined to engage military conflict, for some reason." - icon_state = "beret" - dynamic_hair_suffix = "" - -/obj/item/clothing/head/frenchberet/equipped(mob/M, slot) - . = ..() - if (slot == ITEM_SLOT_HEAD) - RegisterSignal(M, COMSIG_MOB_SAY, .proc/handle_speech) - else - UnregisterSignal(M, COMSIG_MOB_SAY) - -/obj/item/clothing/head/frenchberet/dropped(mob/M) - . = ..() - UnregisterSignal(M, COMSIG_MOB_SAY) - -/obj/item/clothing/head/frenchberet/proc/handle_speech(datum/source, mob/speech_args) - var/message = speech_args[SPEECH_MESSAGE] - if(message[1] != "*") - message = " [message]" - var/list/french_words = strings("french_replacement.json", "french") - - for(var/key in french_words) - var/value = french_words[key] - if(islist(value)) - value = pick(value) - - message = replacetextEx(message, " [uppertext(key)]", " [uppertext(value)]") - message = replacetextEx(message, " [capitalize(key)]", " [capitalize(value)]") - message = replacetextEx(message, " [key]", " [value]") - - if(prob(3)) - message += pick(" Honh honh honh!"," Honh!"," Zut Alors!") - speech_args[SPEECH_MESSAGE] = trim(message) - -/obj/item/clothing/head/clownmitre - name = "Hat of the Honkmother" - desc = "It's hard for parishoners to see a banana peel on the floor when they're looking up at your glorious chapeau." - icon_state = "clownmitre" - -/obj/item/clothing/head/kippah - name = "kippah" - desc = "Signals that you follow the Jewish Halakha. Keeps the head covered and the soul extra-Orthodox." - icon_state = "kippah" - -/obj/item/clothing/head/medievaljewhat - name = "medieval Jewish hat" - desc = "A silly looking hat, intended to be placed on the heads of the station's oppressed religious minorities." - icon_state = "medievaljewhat" - -/obj/item/clothing/head/taqiyahwhite - name = "white taqiyah" - desc = "An extra-mustahabb way of showing your devotion to Allah." - icon_state = "taqiyahwhite" - pocket_storage_component_path = /datum/component/storage/concrete/pockets/small - -/obj/item/clothing/head/taqiyahred - name = "red taqiyah" - desc = "An extra-mustahabb way of showing your devotion to Allah." - icon_state = "taqiyahred" - pocket_storage_component_path = /datum/component/storage/concrete/pockets/small - -/obj/item/clothing/head/shrine_wig - name = "shrine maiden's wig" - desc = "Purify in style!" - flags_inv = HIDEHAIR //bald - mob_overlay_icon = 'icons/mob/large-worn-icons/64x64/head.dmi' - icon_state = "shrine_wig" - item_state = "shrine_wig" - worn_x_dimension = 64 - worn_y_dimension = 64 - dynamic_hair_suffix = "" - -/obj/item/clothing/head/intern - name = "\improper CentCom Head Intern beancap" - desc = "A horrifying mix of beanie and softcap in CentCom green. You'd have to be pretty desperate for power over your peers to agree to wear this." - icon_state = "intern_hat" - item_state = "intern_hat" - -/obj/item/clothing/head/coordinator - name = "coordinator cap" - desc = "A cap for a party ooordinator, stylish!." - icon_state = "capcap" - item_state = "that" - armor = list("melee" = 25, "bullet" = 15, "laser" = 25, "energy" = 35, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - -/obj/item/clothing/head/goatpelt - name = "goat pelt hat" - desc = "Fuzzy and Warm!" - icon_state = "goatpelt" - item_state = "goatpelt" - -/obj/item/clothing/head/goatpelt/king - name = "king goat pelt hat" - desc = "Fuzzy, Warm and Robust!" - icon_state = "goatpelt" - item_state = "goatpelt" - color = "#ffd700" - body_parts_covered = HEAD - armor = list("melee" = 60, "bullet" = 55, "laser" = 55, "energy" = 45, "bomb" = 100, "bio" = 20, "rad" = 20, "fire" = 100, "acid" = 100) - dog_fashion = null - resistance_flags = FIRE_PROOF - -/obj/item/clothing/head/goatpelt/king/equipped(mob/living/carbon/human/user, slot) - ..() - if (slot == ITEM_SLOT_HEAD) - user.faction |= "goat" - -/obj/item/clothing/head/goatpelt/king/dropped(mob/living/carbon/human/user) - ..() - if (user.head == src) - user.faction -= "goat" - -/obj/item/clothing/head/goatpope - name = "goat pope hat" - desc = "And on the seventh day King Goat said there will be cabbage!" - mob_overlay_icon = 'icons/mob/large-worn-icons/64x64/head.dmi' - icon_state = "goatpope" - item_state = "goatpope" - worn_x_dimension = 64 - worn_y_dimension = 64 - resistance_flags = FLAMMABLE - -/obj/item/clothing/head/goatpope/equipped(mob/living/carbon/human/user, slot) - ..() - if (slot == ITEM_SLOT_HEAD) - user.faction |= "goat" - -/obj/item/clothing/head/jackbros - name = "frosty hat" - desc = "Hee-ho!" - icon_state = "JackFrostHat" - item_state = "JackFrostHat" - + +/obj/item/clothing/head/centhat + name = "\improper CentCom hat" + icon_state = "centcom" + desc = "It's good to be emperor." + item_state = "that" + flags_inv = 0 + armor = list("melee" = 30, "bullet" = 15, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + strip_delay = 80 + +/obj/item/clothing/head/spacepolice + name = "space police cap" + desc = "A blue cap for patrolling the daily beat." + icon_state = "policecap_families" + item_state = "policecap_families" + +/obj/item/clothing/head/powdered_wig + name = "powdered wig" + desc = "A powdered wig." + icon_state = "pwig" + item_state = "pwig" + +/obj/item/clothing/head/that + name = "top-hat" + desc = "It's an amish looking hat." + icon_state = "tophat" + item_state = "that" + dog_fashion = /datum/dog_fashion/head + throwforce = 1 + +/obj/item/clothing/head/canada + name = "striped red tophat" + desc = "It smells like fresh donut holes. / Il sent comme des trous de beignets frais." + icon_state = "canada" + item_state = "canada" + +/obj/item/clothing/head/redcoat + name = "redcoat's hat" + icon_state = "redcoat" + desc = "'I guess it's a redhead.'" + +/obj/item/clothing/head/mailman + name = "mailman's hat" + icon_state = "mailman" + desc = "'Right-on-time' mail service head wear." + +/obj/item/clothing/head/plaguedoctorhat + name = "plague doctor's hat" + desc = "These were once used by plague doctors. They're pretty much useless." + icon_state = "plaguedoctor" + permeability_coefficient = 0.01 + +/obj/item/clothing/head/hasturhood + name = "hastur's hood" + desc = "It's unspeakably stylish." + icon_state = "hasturhood" + flags_inv = HIDEHAIR + flags_cover = HEADCOVERSEYES + +/obj/item/clothing/head/nursehat + name = "nurse's hat" + desc = "It allows quick identification of trained medical personnel." + icon_state = "nursehat" + dynamic_hair_suffix = "" + + dog_fashion = /datum/dog_fashion/head/nurse + +/obj/item/clothing/head/syndicatefake + name = "black space-helmet replica" + icon_state = "syndicate-helm-black-red" + item_state = "syndicate-helm-black-red" + desc = "A plastic replica of a Syndicate agent's space helmet. You'll look just like a real murderous Syndicate agent in this! This is a toy, it is not made for use in space!" + clothing_flags = SNUG_FIT + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + +/obj/item/clothing/head/cueball + name = "cueball helmet" + desc = "A large, featureless white orb meant to be worn on your head. How do you even see out of this thing?" + icon_state = "cueball" + item_state="cueball" + clothing_flags = SNUG_FIT + flags_cover = HEADCOVERSEYES|HEADCOVERSMOUTH + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + +/obj/item/clothing/head/snowman + name = "Snowman Head" + desc = "A ball of white styrofoam. So festive." + icon_state = "snowman_h" + item_state = "snowman_h" + clothing_flags = SNUG_FIT + flags_cover = HEADCOVERSEYES + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + +/obj/item/clothing/head/justice + name = "justice hat" + desc = "Fight for what's righteous!" + icon_state = "justicered" + item_state = "justicered" + clothing_flags = SNUG_FIT + flags_inv = HIDEHAIR|HIDEEARS|HIDEEYES|HIDEFACE|HIDEFACIALHAIR + flags_cover = HEADCOVERSEYES + +/obj/item/clothing/head/justice/blue + icon_state = "justiceblue" + item_state = "justiceblue" + +/obj/item/clothing/head/justice/yellow + icon_state = "justiceyellow" + item_state = "justiceyellow" + +/obj/item/clothing/head/justice/green + icon_state = "justicegreen" + item_state = "justicegreen" + +/obj/item/clothing/head/justice/pink + icon_state = "justicepink" + item_state = "justicepink" + +/obj/item/clothing/head/rabbitears + name = "rabbit ears" + desc = "Wearing these makes you look useless, and only good for your sex appeal." + icon_state = "bunny" + dynamic_hair_suffix = "" + + dog_fashion = /datum/dog_fashion/head/rabbit + +/obj/item/clothing/head/pirate + name = "pirate hat" + desc = "Yarr." + icon_state = "pirate" + item_state = "pirate" + dog_fashion = /datum/dog_fashion/head/pirate + +/obj/item/clothing/head/pirate + var/datum/language/piratespeak/L = new + +/obj/item/clothing/head/pirate/equipped(mob/user, slot) + . = ..() + if(!ishuman(user)) + return + if(slot == ITEM_SLOT_HEAD) + user.grant_language(/datum/language/piratespeak/, TRUE, TRUE, LANGUAGE_HAT) + to_chat(user, "You suddenly know how to speak like a pirate!") + +/obj/item/clothing/head/pirate/dropped(mob/user) + . = ..() + if(!ishuman(user)) + return + var/mob/living/carbon/human/H = user + if(H.get_item_by_slot(ITEM_SLOT_HEAD) == src) + user.remove_language(/datum/language/piratespeak/, TRUE, TRUE, LANGUAGE_HAT) + to_chat(user, "You can no longer speak like a pirate.") + +/obj/item/clothing/head/pirate/captain + name = "pirate captain hat" + icon_state = "hgpiratecap" + item_state = "hgpiratecap" + +/obj/item/clothing/head/bandana + name = "pirate bandana" + desc = "Yarr." + icon_state = "bandana" + item_state = "bandana" + dynamic_hair_suffix = "" + +/obj/item/clothing/head/bowler + name = "bowler-hat" + desc = "Gentleman, elite aboard!" + icon_state = "bowler" + item_state = "bowler" + dynamic_hair_suffix = "" + +/obj/item/clothing/head/witchwig + name = "witch costume wig" + desc = "Eeeee~heheheheheheh!" + icon_state = "witch" + item_state = "witch" + flags_inv = HIDEHAIR + +/obj/item/clothing/head/chicken + name = "chicken suit head" + desc = "Bkaw!" + icon_state = "chickenhead" + item_state = "chickensuit" + clothing_flags = SNUG_FIT + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + +/obj/item/clothing/head/griffin + name = "griffon head" + desc = "Why not 'eagle head'? Who knows." + icon_state = "griffinhat" + item_state = "griffinhat" + clothing_flags = SNUG_FIT + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + +/obj/item/clothing/head/bearpelt + name = "bear pelt hat" + desc = "Fuzzy." + icon_state = "bearpelt" + item_state = "bearpelt" + +/obj/item/clothing/head/xenos + name = "xenos helmet" + icon_state = "xenos" + item_state = "xenos_helm" + desc = "A helmet made out of chitinous alien hide." + clothing_flags = SNUG_FIT + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH + +/obj/item/clothing/head/fedora + name = "fedora" + icon_state = "fedora" + item_state = "fedora" + desc = "A really cool hat if you're a mobster. A really lame hat if you're not." + pocket_storage_component_path = /datum/component/storage/concrete/pockets/small/fedora + +/obj/item/clothing/head/fedora/white + name = "white fedora" + icon_state = "fedora_white" + item_state = "fedora_white" + +/obj/item/clothing/head/fedora/beige + name = "beige fedora" + icon_state = "fedora_beige" + item_state = "fedora_beige" + +/obj/item/clothing/head/fedora/suicide_act(mob/user) + if(user.gender == FEMALE) + return 0 + var/mob/living/carbon/human/H = user + user.visible_message("[user] is donning [src]! It looks like [user.p_theyre()] trying to be nice to girls.") + user.say("M'lady.", forced = "fedora suicide") + sleep(10) + H.facial_hairstyle = "Neckbeard" + return(BRUTELOSS) + +/obj/item/clothing/head/sombrero + name = "sombrero" + icon_state = "sombrero" + item_state = "sombrero" + desc = "You can practically taste the fiesta." + flags_inv = HIDEHAIR + + dog_fashion = /datum/dog_fashion/head/sombrero + +/obj/item/clothing/head/sombrero/green + name = "green sombrero" + icon_state = "greensombrero" + item_state = "greensombrero" + desc = "As elegant as a dancing cactus." + flags_inv = HIDEHAIR|HIDEFACE|HIDEEARS + dog_fashion = null + +/obj/item/clothing/head/sombrero/shamebrero + name = "shamebrero" + icon_state = "shamebrero" + item_state = "shamebrero" + desc = "Once it's on, it never comes off." + dog_fashion = null + +/obj/item/clothing/head/sombrero/shamebrero/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, SHAMEBRERO_TRAIT) + +/obj/item/clothing/head/flatcap + name = "flat cap" + desc = "A working man's cap." + icon_state = "flat_cap" + item_state = "detective" + +/obj/item/clothing/head/hunter + name = "bounty hunting hat" + desc = "Ain't nobody gonna cheat the hangman in my town." + icon_state = "hunter" + item_state = "hunter" + armor = list("melee" = 5, "bullet" = 5, "laser" = 5, "energy" = 15, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + resistance_flags = FIRE_PROOF | ACID_PROOF + +/obj/item/clothing/head/cone + desc = "This cone is trying to warn you of something!" + name = "warning cone" + icon = 'icons/obj/janitor.dmi' + icon_state = "cone" + item_state = "cone" + force = 1 + throwforce = 3 + throw_speed = 2 + throw_range = 5 + w_class = WEIGHT_CLASS_SMALL + attack_verb = list("warned", "cautioned", "smashed") + resistance_flags = NONE + dynamic_hair_suffix = "" + +/obj/item/clothing/head/santa + name = "santa hat" + desc = "On the first day of christmas my employer gave to me!" + icon_state = "santahatnorm" + item_state = "that" + cold_protection = HEAD + min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT + dog_fashion = /datum/dog_fashion/head/santa + +/obj/item/clothing/head/jester + name = "jester hat" + desc = "A hat with bells, to add some merriness to the suit." + icon_state = "jester_hat" + dynamic_hair_suffix = "" + +/obj/item/clothing/head/jester/alt + icon_state = "jester2" + +/obj/item/clothing/head/rice_hat + name = "rice hat" + desc = "Welcome to the rice fields, motherfucker." + icon_state = "rice_hat" + +/obj/item/clothing/head/lizard + name = "lizardskin cloche hat" + desc = "How many lizards died to make this hat? Not enough." + icon_state = "lizard" + +/obj/item/clothing/head/papersack + name = "paper sack hat" + desc = "A paper sack with crude holes cut out for eyes. Useful for hiding one's identity or ugliness." + icon_state = "papersack" + flags_inv = HIDEHAIR|HIDEFACE|HIDEEARS + +/obj/item/clothing/head/papersack/smiley + name = "paper sack hat" + desc = "A paper sack with crude holes cut out for eyes and a sketchy smile drawn on the front. Not creepy at all." + icon_state = "papersack_smile" + flags_inv = HIDEHAIR|HIDEFACE|HIDEEARS + +/obj/item/clothing/head/crown + name = "crown" + desc = "A crown fit for a king, a petty king maybe." + icon_state = "crown" + armor = list("melee" = 15, "bullet" = 0, "laser" = 0,"energy" = 10, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50) + resistance_flags = FIRE_PROOF + dynamic_hair_suffix = "" + +/obj/item/clothing/head/crown/fancy + name = "magnificent crown" + desc = "A crown worn by only the highest emperors of the land space." + icon_state = "fancycrown" + +/obj/item/clothing/head/scarecrow_hat + name = "scarecrow hat" + desc = "A simple straw hat." + icon_state = "scarecrow_hat" + +/obj/item/clothing/head/lobsterhat + name = "foam lobster head" + desc = "When everything's going to crab, protecting your head is the best choice." + icon_state = "lobster_hat" + clothing_flags = SNUG_FIT + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + +/obj/item/clothing/head/drfreezehat + name = "doctor freeze's wig" + desc = "A cool wig for cool people." + icon_state = "drfreeze_hat" + flags_inv = HIDEHAIR + +/obj/item/clothing/head/pharaoh + name = "pharaoh hat" + desc = "Walk like an Egyptian." + icon_state = "pharoah_hat" + item_state = "pharoah_hat" + +/obj/item/clothing/head/nemes + name = "headdress of Nemes" + desc = "Lavish space tomb not included." + icon_state = "nemes_headdress" + +/obj/item/clothing/head/delinquent + name = "delinquent hat" + desc = "Good grief." + icon_state = "delinquent" + +/obj/item/clothing/head/frenchberet + name = "french beret" + desc = "A quality beret, infused with the aroma of chain-smoking, wine-swilling Parisians. You feel less inclined to engage military conflict, for some reason." + icon_state = "beret" + dynamic_hair_suffix = "" + +/obj/item/clothing/head/frenchberet/equipped(mob/M, slot) + . = ..() + if (slot == ITEM_SLOT_HEAD) + RegisterSignal(M, COMSIG_MOB_SAY, .proc/handle_speech) + else + UnregisterSignal(M, COMSIG_MOB_SAY) + +/obj/item/clothing/head/frenchberet/dropped(mob/M) + . = ..() + UnregisterSignal(M, COMSIG_MOB_SAY) + +/obj/item/clothing/head/frenchberet/proc/handle_speech(datum/source, mob/speech_args) + var/message = speech_args[SPEECH_MESSAGE] + if(message[1] != "*") + message = " [message]" + var/list/french_words = strings("french_replacement.json", "french") + + for(var/key in french_words) + var/value = french_words[key] + if(islist(value)) + value = pick(value) + + message = replacetextEx(message, " [uppertext(key)]", " [uppertext(value)]") + message = replacetextEx(message, " [capitalize(key)]", " [capitalize(value)]") + message = replacetextEx(message, " [key]", " [value]") + + if(prob(3)) + message += pick(" Honh honh honh!"," Honh!"," Zut Alors!") + speech_args[SPEECH_MESSAGE] = trim(message) + +/obj/item/clothing/head/clownmitre + name = "Hat of the Honkmother" + desc = "It's hard for parishoners to see a banana peel on the floor when they're looking up at your glorious chapeau." + icon_state = "clownmitre" + +/obj/item/clothing/head/kippah + name = "kippah" + desc = "Signals that you follow the Jewish Halakha. Keeps the head covered and the soul extra-Orthodox." + icon_state = "kippah" + +/obj/item/clothing/head/medievaljewhat + name = "medieval Jewish hat" + desc = "A silly looking hat, intended to be placed on the heads of the station's oppressed religious minorities." + icon_state = "medievaljewhat" + +/obj/item/clothing/head/taqiyahwhite + name = "white taqiyah" + desc = "An extra-mustahabb way of showing your devotion to Allah." + icon_state = "taqiyahwhite" + pocket_storage_component_path = /datum/component/storage/concrete/pockets/small + +/obj/item/clothing/head/taqiyahred + name = "red taqiyah" + desc = "An extra-mustahabb way of showing your devotion to Allah." + icon_state = "taqiyahred" + pocket_storage_component_path = /datum/component/storage/concrete/pockets/small + +/obj/item/clothing/head/shrine_wig + name = "shrine maiden's wig" + desc = "Purify in style!" + flags_inv = HIDEHAIR //bald + mob_overlay_icon = 'icons/mob/large-worn-icons/64x64/head.dmi' + icon_state = "shrine_wig" + item_state = "shrine_wig" + worn_x_dimension = 64 + worn_y_dimension = 64 + dynamic_hair_suffix = "" + +/obj/item/clothing/head/intern + name = "\improper CentCom Head Intern beancap" + desc = "A horrifying mix of beanie and softcap in CentCom green. You'd have to be pretty desperate for power over your peers to agree to wear this." + icon_state = "intern_hat" + item_state = "intern_hat" + +/obj/item/clothing/head/coordinator + name = "coordinator cap" + desc = "A cap for a party ooordinator, stylish!." + icon_state = "capcap" + item_state = "that" + armor = list("melee" = 25, "bullet" = 15, "laser" = 25, "energy" = 35, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + +/obj/item/clothing/head/goatpelt + name = "goat pelt hat" + desc = "Fuzzy and Warm!" + icon_state = "goatpelt" + item_state = "goatpelt" + +/obj/item/clothing/head/goatpelt/king + name = "king goat pelt hat" + desc = "Fuzzy, Warm and Robust!" + icon_state = "goatpelt" + item_state = "goatpelt" + color = "#ffd700" + body_parts_covered = HEAD + armor = list("melee" = 60, "bullet" = 55, "laser" = 55, "energy" = 45, "bomb" = 100, "bio" = 20, "rad" = 20, "fire" = 100, "acid" = 100) + dog_fashion = null + resistance_flags = FIRE_PROOF + +/obj/item/clothing/head/goatpelt/king/equipped(mob/living/carbon/human/user, slot) + ..() + if (slot == ITEM_SLOT_HEAD) + user.faction |= "goat" + +/obj/item/clothing/head/goatpelt/king/dropped(mob/living/carbon/human/user) + ..() + if (user.head == src) + user.faction -= "goat" + +/obj/item/clothing/head/goatpope + name = "goat pope hat" + desc = "And on the seventh day King Goat said there will be cabbage!" + mob_overlay_icon = 'icons/mob/large-worn-icons/64x64/head.dmi' + icon_state = "goatpope" + item_state = "goatpope" + worn_x_dimension = 64 + worn_y_dimension = 64 + resistance_flags = FLAMMABLE + +/obj/item/clothing/head/goatpope/equipped(mob/living/carbon/human/user, slot) + ..() + if (slot == ITEM_SLOT_HEAD) + user.faction |= "goat" + +/obj/item/clothing/head/jackbros + name = "frosty hat" + desc = "Hee-ho!" + icon_state = "JackFrostHat" + item_state = "JackFrostHat" + diff --git a/code/modules/clothing/head/misc_special.dm b/code/modules/clothing/head/misc_special.dm index 63de09475746..8f98281ff0df 100644 --- a/code/modules/clothing/head/misc_special.dm +++ b/code/modules/clothing/head/misc_special.dm @@ -1,373 +1,373 @@ -/* - * Contents: - * Welding mask - * Cakehat - * Ushanka - * Pumpkin head - * Kitty ears - * Cardborg disguise - * Wig - * Bronze hat - */ - -/* - * Welding mask - */ -/obj/item/clothing/head/welding - name = "welding helmet" - desc = "A head-mounted face cover designed to protect the wearer completely from space-arc eye." - icon_state = "welding" - flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH - item_state = "welding" - custom_materials = list(/datum/material/iron=1750, /datum/material/glass=400) - flash_protect = FLASH_PROTECTION_WELDER - tint = 2 - armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 60) - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE - actions_types = list(/datum/action/item_action/toggle) - visor_flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE - visor_flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF - resistance_flags = FIRE_PROOF - clothing_flags = SNUG_FIT - -/obj/item/clothing/head/welding/attack_self(mob/user) - weldingvisortoggle(user) - -/* - * Cakehat - */ -/obj/item/clothing/head/hardhat/cakehat - name = "cakehat" - desc = "You put the cake on your head. Brilliant." - icon_state = "hardhat0_cakehat" - item_state = "hardhat0_cakehat" - hat_type = "cakehat" - lefthand_file = 'icons/mob/inhands/clothing_lefthand.dmi' - righthand_file = 'icons/mob/inhands/clothing_righthand.dmi' - hitsound = 'sound/weapons/tap.ogg' - var/hitsound_on = 'sound/weapons/sear.ogg' //so we can differentiate between cakehat and energyhat - var/hitsound_off = 'sound/weapons/tap.ogg' - var/force_on = 15 - var/throwforce_on = 15 - var/damtype_on = BURN - flags_inv = HIDEEARS|HIDEHAIR - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - brightness_on = 2 //luminosity when on - flags_cover = HEADCOVERSEYES - heat = 999 - - dog_fashion = /datum/dog_fashion/head - -/obj/item/clothing/head/hardhat/cakehat/process() - var/turf/location = src.loc - if(ishuman(location)) - var/mob/living/carbon/human/M = location - if(M.is_holding(src) || M.head == src) - location = M.loc - - if(isturf(location)) - location.hotspot_expose(700, 1) - -/obj/item/clothing/head/hardhat/cakehat/turn_on(mob/living/user) - ..() - force = force_on - throwforce = throwforce_on - damtype = damtype_on - hitsound = hitsound_on - START_PROCESSING(SSobj, src) - -/obj/item/clothing/head/hardhat/cakehat/turn_off(mob/living/user) - ..() - force = 0 - throwforce = 0 - damtype = BRUTE - hitsound = hitsound_off - STOP_PROCESSING(SSobj, src) - -/obj/item/clothing/head/hardhat/cakehat/get_temperature() - return on * heat - -/obj/item/clothing/head/hardhat/cakehat/energycake - name = "energy cake" - desc = "You put the energy sword on your cake. Brilliant." - icon_state = "hardhat0_energycake" - item_state = "hardhat0_energycake" - hat_type = "energycake" - hitsound = 'sound/weapons/tap.ogg' - hitsound_on = 'sound/weapons/blade1.ogg' - hitsound_off = 'sound/weapons/tap.ogg' - damtype_on = BRUTE - force_on = 18 //same as epen (but much more obvious) - brightness_on = 3 //ditto - heat = 0 - -/obj/item/clothing/head/hardhat/cakehat/energycake/turn_on(mob/living/user) - playsound(user, 'sound/weapons/saberon.ogg', 5, TRUE) - to_chat(user, "You turn on \the [src].") - ..() - -/obj/item/clothing/head/hardhat/cakehat/energycake/turn_off(mob/living/user) - playsound(user, 'sound/weapons/saberoff.ogg', 5, TRUE) - to_chat(user, "You turn off \the [src].") - ..() - -/* - * Ushanka - */ -/obj/item/clothing/head/ushanka - name = "ushanka" - desc = "Perfect for winter in Siberia, da?" - icon_state = "ushankadown" - item_state = "ushankadown" - flags_inv = HIDEEARS|HIDEHAIR - var/earflaps = 1 - cold_protection = HEAD - min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT - - dog_fashion = /datum/dog_fashion/head/ushanka - -/obj/item/clothing/head/ushanka/attack_self(mob/user) - if(earflaps) - src.icon_state = "ushankaup" - src.item_state = "ushankaup" - earflaps = 0 - to_chat(user, "You raise the ear flaps on the ushanka.") - else - src.icon_state = "ushankadown" - src.item_state = "ushankadown" - earflaps = 1 - to_chat(user, "You lower the ear flaps on the ushanka.") - -/* - * Pumpkin head - */ -/obj/item/clothing/head/hardhat/pumpkinhead - name = "carved pumpkin" - desc = "A jack o' lantern! Believed to ward off evil spirits." - icon_state = "hardhat0_pumpkin" - item_state = "hardhat0_pumpkin" - hat_type = "pumpkin" - clothing_flags = SNUG_FIT - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - brightness_on = 2 //luminosity when on - flags_cover = HEADCOVERSEYES - -/* - * Kitty ears - */ -/obj/item/clothing/head/kitty - name = "kitty ears" - desc = "A pair of kitty ears. Meow!" - icon_state = "kitty" - color = "#999999" - dynamic_hair_suffix = "" - - dog_fashion = /datum/dog_fashion/head/kitty - -/obj/item/clothing/head/kitty/equipped(mob/living/carbon/human/user, slot) - if(ishuman(user) && slot == ITEM_SLOT_HEAD) - update_icon(user) - user.update_inv_head() //Color might have been changed by update_icon. - ..() - -/obj/item/clothing/head/kitty/update_icon(mob/living/carbon/human/user) - if(ishuman(user)) - add_atom_colour("#[user.hair_color]", FIXED_COLOUR_PRIORITY) - -/obj/item/clothing/head/kitty/genuine - desc = "A pair of kitty ears. A tag on the inside says \"Hand made from real cats.\"" - -/obj/item/clothing/head/hardhat/reindeer - name = "novelty reindeer hat" - desc = "Some fake antlers and a very fake red nose." - icon_state = "hardhat0_reindeer" - item_state = "hardhat0_reindeer" - hat_type = "reindeer" - flags_inv = 0 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - brightness_on = 1 //luminosity when on - dynamic_hair_suffix = "" - - dog_fashion = /datum/dog_fashion/head/reindeer - -/obj/item/clothing/head/cardborg - name = "cardborg helmet" - desc = "A helmet made out of a box." - icon_state = "cardborg_h" - item_state = "cardborg_h" - clothing_flags = SNUG_FIT - flags_cover = HEADCOVERSEYES - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - - dog_fashion = /datum/dog_fashion/head/cardborg - -/obj/item/clothing/head/cardborg/equipped(mob/living/user, slot) - ..() - if(ishuman(user) && slot == ITEM_SLOT_HEAD) - var/mob/living/carbon/human/H = user - if(istype(H.wear_suit, /obj/item/clothing/suit/cardborg)) - var/obj/item/clothing/suit/cardborg/CB = H.wear_suit - CB.disguise(user, src) - -/obj/item/clothing/head/cardborg/dropped(mob/living/user) - ..() - user.remove_alt_appearance("standard_borg_disguise") - -/obj/item/clothing/head/wig - name = "wig" - desc = "A bunch of hair without a head attached." - icon = 'icons/mob/human_face.dmi' // default icon for all hairs - icon_state = "hair_vlong" - item_state = "pwig" - flags_inv = HIDEHAIR - color = "#000" - var/hairstyle = "Very Long Hair" - var/adjustablecolor = TRUE //can color be changed manually? - -/obj/item/clothing/head/wig/Initialize(mapload) - . = ..() - update_icon() - -/obj/item/clothing/head/wig/update_icon_state() - var/datum/sprite_accessory/S = GLOB.hairstyles_list[hairstyle] - if(!S) - icon = 'icons/obj/clothing/hats.dmi' - icon_state = "pwig" - else - icon = S.icon - icon_state = S.icon_state - -/obj/item/clothing/head/wig/worn_overlays(isinhands = FALSE, file2use) - . = list() - if(!isinhands) - var/datum/sprite_accessory/S = GLOB.hairstyles_list[hairstyle] - if(!S) - return - var/mutable_appearance/M = mutable_appearance(S.icon, S.icon_state,layer = -HAIR_LAYER) - M.appearance_flags |= RESET_COLOR - M.color = color - . += M - -/obj/item/clothing/head/wig/attack_self(mob/user) - var/new_style = input(user, "Select a hairstyle", "Wig Styling") as null|anything in (GLOB.hairstyles_list - "Bald") - var/newcolor = adjustablecolor ? input(usr,"","Choose Color",color) as color|null : null - if(!user.canUseTopic(src, BE_CLOSE)) - return - if(new_style && new_style != hairstyle) - hairstyle = new_style - user.visible_message("[user] changes \the [src]'s hairstyle to [new_style].", "You change \the [src]'s hairstyle to [new_style].") - if(newcolor && newcolor != color) // only update if necessary - add_atom_colour(newcolor, FIXED_COLOUR_PRIORITY) - update_icon() - -/obj/item/clothing/head/wig/afterattack(mob/living/carbon/human/target, mob/user) - . = ..() - if (istype(target) && (HAIR in target.dna.species.species_traits) && target.hairstyle != "Bald") - to_chat(user, "You adjust the [src] to look just like [target.name]'s [target.hairstyle].") - add_atom_colour("#[target.hair_color]", FIXED_COLOUR_PRIORITY) - hairstyle = target.hairstyle - update_icon() - -/obj/item/clothing/head/wig/random/Initialize(mapload) - hairstyle = pick(GLOB.hairstyles_list - "Bald") //Don't want invisible wig - add_atom_colour("#[random_short_color()]", FIXED_COLOUR_PRIORITY) - . = ..() - -/obj/item/clothing/head/wig/natural - name = "natural wig" - desc = "A bunch of hair without a head attached. This one changes color to match the hair of the wearer. Nothing natural about that." - color = "#FFF" - adjustablecolor = FALSE - custom_price = 100 - -/obj/item/clothing/head/wig/natural/Initialize(mapload) - hairstyle = pick(GLOB.hairstyles_list - "Bald") - . = ..() - -/obj/item/clothing/head/wig/natural/equipped(mob/living/carbon/human/user, slot) - . = ..() - if(ishuman(user) && slot == ITEM_SLOT_HEAD) - if (color != "#[user.hair_color]") // only update if necessary - add_atom_colour("#[user.hair_color]", FIXED_COLOUR_PRIORITY) - update_icon() - user.update_inv_head() - -/obj/item/clothing/head/bronze - name = "bronze hat" - desc = "A crude helmet made out of bronze plates. It offers very little in the way of protection." - icon = 'icons/obj/clothing/clockwork_garb.dmi' - icon_state = "clockwork_helmet_old" - clothing_flags = SNUG_FIT - flags_inv = HIDEEARS|HIDEHAIR - armor = list("melee" = 5, "bullet" = 0, "laser" = -5, "energy" = -15, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 20, "acid" = 20) - -/obj/item/clothing/head/foilhat - name = "tinfoil hat" - desc = "Thought control rays, psychotronic scanning. Don't mind that, I'm protected cause I made this hat." - icon_state = "foilhat" - item_state = "foilhat" - armor = list("melee" = 0, "bullet" = 0, "laser" = -5,"energy" = -15, "bomb" = 0, "bio" = 0, "rad" = -5, "fire" = 0, "acid" = 0) - equip_delay_other = 140 - clothing_flags = ANTI_TINFOIL_MANEUVER - var/datum/brain_trauma/mild/phobia/conspiracies/paranoia - var/warped = FALSE - -/obj/item/clothing/head/foilhat/Initialize(mapload) - . = ..() - if(!warped) - AddComponent(/datum/component/anti_magic, FALSE, FALSE, TRUE, ITEM_SLOT_HEAD, 6, TRUE, null, CALLBACK(src, .proc/warp_up)) - else - warp_up() - -/obj/item/clothing/head/foilhat/equipped(mob/living/carbon/human/user, slot) - . = ..() - if(slot != ITEM_SLOT_HEAD || warped) - return - if(paranoia) - QDEL_NULL(paranoia) - paranoia = new() - - user.gain_trauma(paranoia, TRAUMA_RESILIENCE_MAGIC) - to_chat(user, "As you don the foiled hat, an entire world of conspiracy theories and seemingly insane ideas suddenly rush into your mind. What you once thought unbelievable suddenly seems.. undeniable. Everything is connected and nothing happens just by accident. You know too much and now they're out to get you. ") - -/obj/item/clothing/head/foilhat/MouseDrop(atom/over_object) - //God Im sorry - if(!warped && iscarbon(usr)) - var/mob/living/carbon/C = usr - if(src == C.head) - to_chat(C, "Why would you want to take this off? Do you want them to get into your mind?!") - return - return ..() - -/obj/item/clothing/head/foilhat/dropped(mob/user) - . = ..() - if(paranoia) - QDEL_NULL(paranoia) - -/obj/item/clothing/head/foilhat/proc/warp_up() - name = "scorched tinfoil hat" - desc = "A badly warped up hat. Quite unprobable this will still work against any of fictional and contemporary dangers it used to." - warped = TRUE - clothing_flags &= ~ANTI_TINFOIL_MANEUVER - if(!isliving(loc) || !paranoia) - return - var/mob/living/target = loc - if(target.get_item_by_slot(ITEM_SLOT_HEAD) != src) - return - QDEL_NULL(paranoia) - if(!target.IsUnconscious()) - to_chat(target, "Your zealous conspirationism rapidly dissipates as the donned hat warps up into a ruined mess. All those theories starting to sound like nothing but a ridicolous fanfare.") - -/obj/item/clothing/head/foilhat/attack_hand(mob/user) - if(!warped && iscarbon(user)) - var/mob/living/carbon/C = user - if(src == C.head) - to_chat(user, "Why would you want to take this off? Do you want them to get into your mind?!") - return - return ..() - -/obj/item/clothing/head/foilhat/microwave_act(obj/machinery/microwave/M) - . = ..() - if(!warped) - warp_up() +/* + * Contents: + * Welding mask + * Cakehat + * Ushanka + * Pumpkin head + * Kitty ears + * Cardborg disguise + * Wig + * Bronze hat + */ + +/* + * Welding mask + */ +/obj/item/clothing/head/welding + name = "welding helmet" + desc = "A head-mounted face cover designed to protect the wearer completely from space-arc eye." + icon_state = "welding" + flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH + item_state = "welding" + custom_materials = list(/datum/material/iron=1750, /datum/material/glass=400) + flash_protect = FLASH_PROTECTION_WELDER + tint = 2 + armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 60) + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE + actions_types = list(/datum/action/item_action/toggle) + visor_flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE + visor_flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF + resistance_flags = FIRE_PROOF + clothing_flags = SNUG_FIT + +/obj/item/clothing/head/welding/attack_self(mob/user) + weldingvisortoggle(user) + +/* + * Cakehat + */ +/obj/item/clothing/head/hardhat/cakehat + name = "cakehat" + desc = "You put the cake on your head. Brilliant." + icon_state = "hardhat0_cakehat" + item_state = "hardhat0_cakehat" + hat_type = "cakehat" + lefthand_file = 'icons/mob/inhands/clothing_lefthand.dmi' + righthand_file = 'icons/mob/inhands/clothing_righthand.dmi' + hitsound = 'sound/weapons/tap.ogg' + var/hitsound_on = 'sound/weapons/sear.ogg' //so we can differentiate between cakehat and energyhat + var/hitsound_off = 'sound/weapons/tap.ogg' + var/force_on = 15 + var/throwforce_on = 15 + var/damtype_on = BURN + flags_inv = HIDEEARS|HIDEHAIR + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + brightness_on = 2 //luminosity when on + flags_cover = HEADCOVERSEYES + heat = 999 + + dog_fashion = /datum/dog_fashion/head + +/obj/item/clothing/head/hardhat/cakehat/process() + var/turf/location = src.loc + if(ishuman(location)) + var/mob/living/carbon/human/M = location + if(M.is_holding(src) || M.head == src) + location = M.loc + + if(isturf(location)) + location.hotspot_expose(700, 1) + +/obj/item/clothing/head/hardhat/cakehat/turn_on(mob/living/user) + ..() + force = force_on + throwforce = throwforce_on + damtype = damtype_on + hitsound = hitsound_on + START_PROCESSING(SSobj, src) + +/obj/item/clothing/head/hardhat/cakehat/turn_off(mob/living/user) + ..() + force = 0 + throwforce = 0 + damtype = BRUTE + hitsound = hitsound_off + STOP_PROCESSING(SSobj, src) + +/obj/item/clothing/head/hardhat/cakehat/get_temperature() + return on * heat + +/obj/item/clothing/head/hardhat/cakehat/energycake + name = "energy cake" + desc = "You put the energy sword on your cake. Brilliant." + icon_state = "hardhat0_energycake" + item_state = "hardhat0_energycake" + hat_type = "energycake" + hitsound = 'sound/weapons/tap.ogg' + hitsound_on = 'sound/weapons/blade1.ogg' + hitsound_off = 'sound/weapons/tap.ogg' + damtype_on = BRUTE + force_on = 18 //same as epen (but much more obvious) + brightness_on = 3 //ditto + heat = 0 + +/obj/item/clothing/head/hardhat/cakehat/energycake/turn_on(mob/living/user) + playsound(user, 'sound/weapons/saberon.ogg', 5, TRUE) + to_chat(user, "You turn on \the [src].") + ..() + +/obj/item/clothing/head/hardhat/cakehat/energycake/turn_off(mob/living/user) + playsound(user, 'sound/weapons/saberoff.ogg', 5, TRUE) + to_chat(user, "You turn off \the [src].") + ..() + +/* + * Ushanka + */ +/obj/item/clothing/head/ushanka + name = "ushanka" + desc = "Perfect for winter in Siberia, da?" + icon_state = "ushankadown" + item_state = "ushankadown" + flags_inv = HIDEEARS|HIDEHAIR + var/earflaps = 1 + cold_protection = HEAD + min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT + + dog_fashion = /datum/dog_fashion/head/ushanka + +/obj/item/clothing/head/ushanka/attack_self(mob/user) + if(earflaps) + src.icon_state = "ushankaup" + src.item_state = "ushankaup" + earflaps = 0 + to_chat(user, "You raise the ear flaps on the ushanka.") + else + src.icon_state = "ushankadown" + src.item_state = "ushankadown" + earflaps = 1 + to_chat(user, "You lower the ear flaps on the ushanka.") + +/* + * Pumpkin head + */ +/obj/item/clothing/head/hardhat/pumpkinhead + name = "carved pumpkin" + desc = "A jack o' lantern! Believed to ward off evil spirits." + icon_state = "hardhat0_pumpkin" + item_state = "hardhat0_pumpkin" + hat_type = "pumpkin" + clothing_flags = SNUG_FIT + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + brightness_on = 2 //luminosity when on + flags_cover = HEADCOVERSEYES + +/* + * Kitty ears + */ +/obj/item/clothing/head/kitty + name = "kitty ears" + desc = "A pair of kitty ears. Meow!" + icon_state = "kitty" + color = "#999999" + dynamic_hair_suffix = "" + + dog_fashion = /datum/dog_fashion/head/kitty + +/obj/item/clothing/head/kitty/equipped(mob/living/carbon/human/user, slot) + if(ishuman(user) && slot == ITEM_SLOT_HEAD) + update_icon(user) + user.update_inv_head() //Color might have been changed by update_icon. + ..() + +/obj/item/clothing/head/kitty/update_icon(mob/living/carbon/human/user) + if(ishuman(user)) + add_atom_colour("#[user.hair_color]", FIXED_COLOUR_PRIORITY) + +/obj/item/clothing/head/kitty/genuine + desc = "A pair of kitty ears. A tag on the inside says \"Hand made from real cats.\"" + +/obj/item/clothing/head/hardhat/reindeer + name = "novelty reindeer hat" + desc = "Some fake antlers and a very fake red nose." + icon_state = "hardhat0_reindeer" + item_state = "hardhat0_reindeer" + hat_type = "reindeer" + flags_inv = 0 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + brightness_on = 1 //luminosity when on + dynamic_hair_suffix = "" + + dog_fashion = /datum/dog_fashion/head/reindeer + +/obj/item/clothing/head/cardborg + name = "cardborg helmet" + desc = "A helmet made out of a box." + icon_state = "cardborg_h" + item_state = "cardborg_h" + clothing_flags = SNUG_FIT + flags_cover = HEADCOVERSEYES + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + + dog_fashion = /datum/dog_fashion/head/cardborg + +/obj/item/clothing/head/cardborg/equipped(mob/living/user, slot) + ..() + if(ishuman(user) && slot == ITEM_SLOT_HEAD) + var/mob/living/carbon/human/H = user + if(istype(H.wear_suit, /obj/item/clothing/suit/cardborg)) + var/obj/item/clothing/suit/cardborg/CB = H.wear_suit + CB.disguise(user, src) + +/obj/item/clothing/head/cardborg/dropped(mob/living/user) + ..() + user.remove_alt_appearance("standard_borg_disguise") + +/obj/item/clothing/head/wig + name = "wig" + desc = "A bunch of hair without a head attached." + icon = 'icons/mob/human_face.dmi' // default icon for all hairs + icon_state = "hair_vlong" + item_state = "pwig" + flags_inv = HIDEHAIR + color = "#000" + var/hairstyle = "Very Long Hair" + var/adjustablecolor = TRUE //can color be changed manually? + +/obj/item/clothing/head/wig/Initialize(mapload) + . = ..() + update_icon() + +/obj/item/clothing/head/wig/update_icon_state() + var/datum/sprite_accessory/S = GLOB.hairstyles_list[hairstyle] + if(!S) + icon = 'icons/obj/clothing/hats.dmi' + icon_state = "pwig" + else + icon = S.icon + icon_state = S.icon_state + +/obj/item/clothing/head/wig/worn_overlays(isinhands = FALSE, file2use) + . = list() + if(!isinhands) + var/datum/sprite_accessory/S = GLOB.hairstyles_list[hairstyle] + if(!S) + return + var/mutable_appearance/M = mutable_appearance(S.icon, S.icon_state,layer = -HAIR_LAYER) + M.appearance_flags |= RESET_COLOR + M.color = color + . += M + +/obj/item/clothing/head/wig/attack_self(mob/user) + var/new_style = input(user, "Select a hairstyle", "Wig Styling") as null|anything in (GLOB.hairstyles_list - "Bald") + var/newcolor = adjustablecolor ? input(usr,"","Choose Color",color) as color|null : null + if(!user.canUseTopic(src, BE_CLOSE)) + return + if(new_style && new_style != hairstyle) + hairstyle = new_style + user.visible_message("[user] changes \the [src]'s hairstyle to [new_style].", "You change \the [src]'s hairstyle to [new_style].") + if(newcolor && newcolor != color) // only update if necessary + add_atom_colour(newcolor, FIXED_COLOUR_PRIORITY) + update_icon() + +/obj/item/clothing/head/wig/afterattack(mob/living/carbon/human/target, mob/user) + . = ..() + if (istype(target) && (HAIR in target.dna.species.species_traits) && target.hairstyle != "Bald") + to_chat(user, "You adjust the [src] to look just like [target.name]'s [target.hairstyle].") + add_atom_colour("#[target.hair_color]", FIXED_COLOUR_PRIORITY) + hairstyle = target.hairstyle + update_icon() + +/obj/item/clothing/head/wig/random/Initialize(mapload) + hairstyle = pick(GLOB.hairstyles_list - "Bald") //Don't want invisible wig + add_atom_colour("#[random_short_color()]", FIXED_COLOUR_PRIORITY) + . = ..() + +/obj/item/clothing/head/wig/natural + name = "natural wig" + desc = "A bunch of hair without a head attached. This one changes color to match the hair of the wearer. Nothing natural about that." + color = "#FFF" + adjustablecolor = FALSE + custom_price = 100 + +/obj/item/clothing/head/wig/natural/Initialize(mapload) + hairstyle = pick(GLOB.hairstyles_list - "Bald") + . = ..() + +/obj/item/clothing/head/wig/natural/equipped(mob/living/carbon/human/user, slot) + . = ..() + if(ishuman(user) && slot == ITEM_SLOT_HEAD) + if (color != "#[user.hair_color]") // only update if necessary + add_atom_colour("#[user.hair_color]", FIXED_COLOUR_PRIORITY) + update_icon() + user.update_inv_head() + +/obj/item/clothing/head/bronze + name = "bronze hat" + desc = "A crude helmet made out of bronze plates. It offers very little in the way of protection." + icon = 'icons/obj/clothing/clockwork_garb.dmi' + icon_state = "clockwork_helmet_old" + clothing_flags = SNUG_FIT + flags_inv = HIDEEARS|HIDEHAIR + armor = list("melee" = 5, "bullet" = 0, "laser" = -5, "energy" = -15, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 20, "acid" = 20) + +/obj/item/clothing/head/foilhat + name = "tinfoil hat" + desc = "Thought control rays, psychotronic scanning. Don't mind that, I'm protected cause I made this hat." + icon_state = "foilhat" + item_state = "foilhat" + armor = list("melee" = 0, "bullet" = 0, "laser" = -5,"energy" = -15, "bomb" = 0, "bio" = 0, "rad" = -5, "fire" = 0, "acid" = 0) + equip_delay_other = 140 + clothing_flags = ANTI_TINFOIL_MANEUVER + var/datum/brain_trauma/mild/phobia/conspiracies/paranoia + var/warped = FALSE + +/obj/item/clothing/head/foilhat/Initialize(mapload) + . = ..() + if(!warped) + AddComponent(/datum/component/anti_magic, FALSE, FALSE, TRUE, ITEM_SLOT_HEAD, 6, TRUE, null, CALLBACK(src, .proc/warp_up)) + else + warp_up() + +/obj/item/clothing/head/foilhat/equipped(mob/living/carbon/human/user, slot) + . = ..() + if(slot != ITEM_SLOT_HEAD || warped) + return + if(paranoia) + QDEL_NULL(paranoia) + paranoia = new() + + user.gain_trauma(paranoia, TRAUMA_RESILIENCE_MAGIC) + to_chat(user, "As you don the foiled hat, an entire world of conspiracy theories and seemingly insane ideas suddenly rush into your mind. What you once thought unbelievable suddenly seems.. undeniable. Everything is connected and nothing happens just by accident. You know too much and now they're out to get you. ") + +/obj/item/clothing/head/foilhat/MouseDrop(atom/over_object) + //God Im sorry + if(!warped && iscarbon(usr)) + var/mob/living/carbon/C = usr + if(src == C.head) + to_chat(C, "Why would you want to take this off? Do you want them to get into your mind?!") + return + return ..() + +/obj/item/clothing/head/foilhat/dropped(mob/user) + . = ..() + if(paranoia) + QDEL_NULL(paranoia) + +/obj/item/clothing/head/foilhat/proc/warp_up() + name = "scorched tinfoil hat" + desc = "A badly warped up hat. Quite unprobable this will still work against any of fictional and contemporary dangers it used to." + warped = TRUE + clothing_flags &= ~ANTI_TINFOIL_MANEUVER + if(!isliving(loc) || !paranoia) + return + var/mob/living/target = loc + if(target.get_item_by_slot(ITEM_SLOT_HEAD) != src) + return + QDEL_NULL(paranoia) + if(!target.IsUnconscious()) + to_chat(target, "Your zealous conspirationism rapidly dissipates as the donned hat warps up into a ruined mess. All those theories starting to sound like nothing but a ridicolous fanfare.") + +/obj/item/clothing/head/foilhat/attack_hand(mob/user) + if(!warped && iscarbon(user)) + var/mob/living/carbon/C = user + if(src == C.head) + to_chat(user, "Why would you want to take this off? Do you want them to get into your mind?!") + return + return ..() + +/obj/item/clothing/head/foilhat/microwave_act(obj/machinery/microwave/M) + . = ..() + if(!warped) + warp_up() diff --git a/code/modules/clothing/head/soft_caps.dm b/code/modules/clothing/head/soft_caps.dm index f10df0e7841a..3263df2da206 100644 --- a/code/modules/clothing/head/soft_caps.dm +++ b/code/modules/clothing/head/soft_caps.dm @@ -1,137 +1,137 @@ -/obj/item/clothing/head/soft - name = "cargo cap" - desc = "It's a baseball hat in a tasteless yellow colour." - icon_state = "cargosoft" - item_state = "helmet" - cuttable = TRUE - clothamnt = 3 - var/soft_type = "cargo" - - dog_fashion = /datum/dog_fashion/head/cargo_tech - - var/flipped = 0 - -/obj/item/clothing/head/soft/dropped() - icon_state = "[soft_type]soft" - flipped=0 - ..() - -/obj/item/clothing/head/soft/verb/flipcap() - set category = "Object" - set name = "Flip cap" - - flip(usr) - - -/obj/item/clothing/head/soft/AltClick(mob/user) - ..() - if(!user.canUseTopic(src, BE_CLOSE, ismonkey(user))) - return - else - flip(user) - - -/obj/item/clothing/head/soft/proc/flip(mob/user) - if(!user.incapacitated()) - flipped = !flipped - if(src.flipped) - icon_state = "[soft_type]soft_flipped" - to_chat(user, "You flip the hat backwards.") - else - icon_state = "[soft_type]soft" - to_chat(user, "You flip the hat back in normal position.") - usr.update_inv_head() //so our mob-overlays update - -/obj/item/clothing/head/soft/examine(mob/user) - . = ..() - . += "Alt-click the cap to flip it [flipped ? "forwards" : "backwards"]." - -/obj/item/clothing/head/soft/red - name = "red cap" - desc = "It's a baseball hat in a tasteless red colour." - icon_state = "redsoft" - soft_type = "red" - dog_fashion = null - -/obj/item/clothing/head/soft/blue - name = "blue cap" - desc = "It's a baseball hat in a tasteless blue colour." - icon_state = "bluesoft" - soft_type = "blue" - dog_fashion = null - -/obj/item/clothing/head/soft/green - name = "green cap" - desc = "It's a baseball hat in a tasteless green colour." - icon_state = "greensoft" - soft_type = "green" - dog_fashion = null - -/obj/item/clothing/head/soft/yellow - name = "yellow cap" - desc = "It's a baseball hat in a tasteless yellow colour." - icon_state = "yellowsoft" - soft_type = "yellow" - dog_fashion = null - -/obj/item/clothing/head/soft/grey - name = "grey cap" - desc = "It's a baseball hat in a tasteful grey colour." - icon_state = "greysoft" - soft_type = "grey" - dog_fashion = null - -/obj/item/clothing/head/soft/orange - name = "orange cap" - desc = "It's a baseball hat in a tasteless orange colour." - icon_state = "orangesoft" - soft_type = "orange" - dog_fashion = null - -/obj/item/clothing/head/soft/mime - name = "white cap" - desc = "It's a baseball hat in a tasteless white colour." - icon_state = "mimesoft" - soft_type = "mime" - dog_fashion = null - -/obj/item/clothing/head/soft/purple - name = "purple cap" - desc = "It's a baseball hat in a tasteless purple colour." - icon_state = "purplesoft" - soft_type = "purple" - dog_fashion = null - -/obj/item/clothing/head/soft/black - name = "black cap" - desc = "It's a baseball hat in a tasteless black colour." - icon_state = "blacksoft" - soft_type = "black" - dog_fashion = null - -/obj/item/clothing/head/soft/rainbow - name = "rainbow cap" - desc = "It's a baseball hat in a bright rainbow of colors." - icon_state = "rainbowsoft" - soft_type = "rainbow" - dog_fashion = null - -/obj/item/clothing/head/soft/sec - name = "security cap" - desc = "It's a robust baseball hat in tasteful red colour." - icon_state = "secsoft" - soft_type = "sec" - armor = list("melee" = 30, "bullet" = 25, "laser" = 25, "energy" = 35, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 20, "acid" = 50) - strip_delay = 60 - dog_fashion = null - -/obj/item/clothing/head/soft/sec/brig_phys - name = "security medic cap" - icon_state = "secmedsoft" - -/obj/item/clothing/head/soft/paramedic - name = "paramedic cap" - desc = "It's a baseball hat with a dark turquoise color and a reflective cross on the top." - icon_state = "paramedicsoft" - soft_type = "paramedic" - dog_fashion = null +/obj/item/clothing/head/soft + name = "cargo cap" + desc = "It's a baseball hat in a tasteless yellow colour." + icon_state = "cargosoft" + item_state = "helmet" + cuttable = TRUE + clothamnt = 3 + var/soft_type = "cargo" + + dog_fashion = /datum/dog_fashion/head/cargo_tech + + var/flipped = 0 + +/obj/item/clothing/head/soft/dropped() + icon_state = "[soft_type]soft" + flipped=0 + ..() + +/obj/item/clothing/head/soft/verb/flipcap() + set category = "Object" + set name = "Flip cap" + + flip(usr) + + +/obj/item/clothing/head/soft/AltClick(mob/user) + ..() + if(!user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + return + else + flip(user) + + +/obj/item/clothing/head/soft/proc/flip(mob/user) + if(!user.incapacitated()) + flipped = !flipped + if(src.flipped) + icon_state = "[soft_type]soft_flipped" + to_chat(user, "You flip the hat backwards.") + else + icon_state = "[soft_type]soft" + to_chat(user, "You flip the hat back in normal position.") + usr.update_inv_head() //so our mob-overlays update + +/obj/item/clothing/head/soft/examine(mob/user) + . = ..() + . += "Alt-click the cap to flip it [flipped ? "forwards" : "backwards"]." + +/obj/item/clothing/head/soft/red + name = "red cap" + desc = "It's a baseball hat in a tasteless red colour." + icon_state = "redsoft" + soft_type = "red" + dog_fashion = null + +/obj/item/clothing/head/soft/blue + name = "blue cap" + desc = "It's a baseball hat in a tasteless blue colour." + icon_state = "bluesoft" + soft_type = "blue" + dog_fashion = null + +/obj/item/clothing/head/soft/green + name = "green cap" + desc = "It's a baseball hat in a tasteless green colour." + icon_state = "greensoft" + soft_type = "green" + dog_fashion = null + +/obj/item/clothing/head/soft/yellow + name = "yellow cap" + desc = "It's a baseball hat in a tasteless yellow colour." + icon_state = "yellowsoft" + soft_type = "yellow" + dog_fashion = null + +/obj/item/clothing/head/soft/grey + name = "grey cap" + desc = "It's a baseball hat in a tasteful grey colour." + icon_state = "greysoft" + soft_type = "grey" + dog_fashion = null + +/obj/item/clothing/head/soft/orange + name = "orange cap" + desc = "It's a baseball hat in a tasteless orange colour." + icon_state = "orangesoft" + soft_type = "orange" + dog_fashion = null + +/obj/item/clothing/head/soft/mime + name = "white cap" + desc = "It's a baseball hat in a tasteless white colour." + icon_state = "mimesoft" + soft_type = "mime" + dog_fashion = null + +/obj/item/clothing/head/soft/purple + name = "purple cap" + desc = "It's a baseball hat in a tasteless purple colour." + icon_state = "purplesoft" + soft_type = "purple" + dog_fashion = null + +/obj/item/clothing/head/soft/black + name = "black cap" + desc = "It's a baseball hat in a tasteless black colour." + icon_state = "blacksoft" + soft_type = "black" + dog_fashion = null + +/obj/item/clothing/head/soft/rainbow + name = "rainbow cap" + desc = "It's a baseball hat in a bright rainbow of colors." + icon_state = "rainbowsoft" + soft_type = "rainbow" + dog_fashion = null + +/obj/item/clothing/head/soft/sec + name = "security cap" + desc = "It's a robust baseball hat in tasteful red colour." + icon_state = "secsoft" + soft_type = "sec" + armor = list("melee" = 30, "bullet" = 25, "laser" = 25, "energy" = 35, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 20, "acid" = 50) + strip_delay = 60 + dog_fashion = null + +/obj/item/clothing/head/soft/sec/brig_phys + name = "security medic cap" + icon_state = "secmedsoft" + +/obj/item/clothing/head/soft/paramedic + name = "paramedic cap" + desc = "It's a baseball hat with a dark turquoise color and a reflective cross on the top." + icon_state = "paramedicsoft" + soft_type = "paramedic" + dog_fashion = null diff --git a/code/modules/clothing/masks/_masks.dm b/code/modules/clothing/masks/_masks.dm index c61c5d78cf6e..ed7c3f7d99d5 100644 --- a/code/modules/clothing/masks/_masks.dm +++ b/code/modules/clothing/masks/_masks.dm @@ -1,72 +1,72 @@ -/obj/item/clothing/mask - name = "mask" - icon = 'icons/obj/clothing/masks.dmi' - body_parts_covered = HEAD - slot_flags = ITEM_SLOT_MASK - strip_delay = 40 - equip_delay_other = 40 - var/modifies_speech = FALSE - var/mask_adjusted = 0 - var/adjusted_flags = null - -/obj/item/clothing/mask/attack_self(mob/user) - if((clothing_flags & VOICEBOX_TOGGLABLE)) - clothing_flags ^= (VOICEBOX_DISABLED) - var/status = !(clothing_flags & VOICEBOX_DISABLED) - to_chat(user, "You turn the voice box in [src] [status ? "on" : "off"].") - -/obj/item/clothing/mask/equipped(mob/M, slot) - . = ..() - if (slot == ITEM_SLOT_MASK && modifies_speech) - RegisterSignal(M, COMSIG_MOB_SAY, .proc/handle_speech) - else - UnregisterSignal(M, COMSIG_MOB_SAY) - -/obj/item/clothing/mask/dropped(mob/M) - . = ..() - UnregisterSignal(M, COMSIG_MOB_SAY) - -/obj/item/clothing/mask/proc/handle_speech() - -/obj/item/clothing/mask/worn_overlays(isinhands = FALSE) - . = list() - if(!isinhands) - if(body_parts_covered & HEAD) - if(damaged_clothes) - . += mutable_appearance('icons/effects/item_damage.dmi', "damagedmask") - if(HAS_BLOOD_DNA(src)) - . += mutable_appearance('icons/effects/blood.dmi', "maskblood") - -/obj/item/clothing/mask/update_clothes_damaged_state(damaging = TRUE) - ..() - if(ismob(loc)) - var/mob/M = loc - M.update_inv_wear_mask() - -//Proc that moves gas/breath masks out of the way, disabling them and allowing pill/food consumption -/obj/item/clothing/mask/proc/adjustmask(mob/living/user) - if(user && user.incapacitated()) - return - mask_adjusted = !mask_adjusted - if(!mask_adjusted) - src.icon_state = initial(icon_state) - gas_transfer_coefficient = initial(gas_transfer_coefficient) - permeability_coefficient = initial(permeability_coefficient) - clothing_flags |= visor_flags - flags_inv |= visor_flags_inv - flags_cover |= visor_flags_cover - to_chat(user, "You push \the [src] back into place.") - slot_flags = initial(slot_flags) - else - icon_state += "_up" - to_chat(user, "You push \the [src] out of the way.") - gas_transfer_coefficient = null - permeability_coefficient = null - clothing_flags &= ~visor_flags - flags_inv &= ~visor_flags_inv - flags_cover &= ~visor_flags_cover - if(adjusted_flags) - slot_flags = adjusted_flags - if(user) - user.wear_mask_update(src, toggle_off = mask_adjusted) - user.update_action_buttons_icon() //when mask is adjusted out, we update all buttons icon so the user's potential internal tank correctly shows as off. +/obj/item/clothing/mask + name = "mask" + icon = 'icons/obj/clothing/masks.dmi' + body_parts_covered = HEAD + slot_flags = ITEM_SLOT_MASK + strip_delay = 40 + equip_delay_other = 40 + var/modifies_speech = FALSE + var/mask_adjusted = 0 + var/adjusted_flags = null + +/obj/item/clothing/mask/attack_self(mob/user) + if((clothing_flags & VOICEBOX_TOGGLABLE)) + clothing_flags ^= (VOICEBOX_DISABLED) + var/status = !(clothing_flags & VOICEBOX_DISABLED) + to_chat(user, "You turn the voice box in [src] [status ? "on" : "off"].") + +/obj/item/clothing/mask/equipped(mob/M, slot) + . = ..() + if (slot == ITEM_SLOT_MASK && modifies_speech) + RegisterSignal(M, COMSIG_MOB_SAY, .proc/handle_speech) + else + UnregisterSignal(M, COMSIG_MOB_SAY) + +/obj/item/clothing/mask/dropped(mob/M) + . = ..() + UnregisterSignal(M, COMSIG_MOB_SAY) + +/obj/item/clothing/mask/proc/handle_speech() + +/obj/item/clothing/mask/worn_overlays(isinhands = FALSE) + . = list() + if(!isinhands) + if(body_parts_covered & HEAD) + if(damaged_clothes) + . += mutable_appearance('icons/effects/item_damage.dmi', "damagedmask") + if(HAS_BLOOD_DNA(src)) + . += mutable_appearance('icons/effects/blood.dmi', "maskblood") + +/obj/item/clothing/mask/update_clothes_damaged_state(damaging = TRUE) + ..() + if(ismob(loc)) + var/mob/M = loc + M.update_inv_wear_mask() + +//Proc that moves gas/breath masks out of the way, disabling them and allowing pill/food consumption +/obj/item/clothing/mask/proc/adjustmask(mob/living/user) + if(user && user.incapacitated()) + return + mask_adjusted = !mask_adjusted + if(!mask_adjusted) + src.icon_state = initial(icon_state) + gas_transfer_coefficient = initial(gas_transfer_coefficient) + permeability_coefficient = initial(permeability_coefficient) + clothing_flags |= visor_flags + flags_inv |= visor_flags_inv + flags_cover |= visor_flags_cover + to_chat(user, "You push \the [src] back into place.") + slot_flags = initial(slot_flags) + else + icon_state += "_up" + to_chat(user, "You push \the [src] out of the way.") + gas_transfer_coefficient = null + permeability_coefficient = null + clothing_flags &= ~visor_flags + flags_inv &= ~visor_flags_inv + flags_cover &= ~visor_flags_cover + if(adjusted_flags) + slot_flags = adjusted_flags + if(user) + user.wear_mask_update(src, toggle_off = mask_adjusted) + user.update_action_buttons_icon() //when mask is adjusted out, we update all buttons icon so the user's potential internal tank correctly shows as off. diff --git a/code/modules/clothing/masks/boxing.dm b/code/modules/clothing/masks/boxing.dm index 8cc9d9a6a11e..31ff7331c31a 100644 --- a/code/modules/clothing/masks/boxing.dm +++ b/code/modules/clothing/masks/boxing.dm @@ -1,98 +1,98 @@ -/obj/item/clothing/mask/balaclava - name = "balaclava" - desc = "LOADSAMONEY" - icon_state = "balaclava" - item_state = "balaclava" - flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - visor_flags_inv = HIDEFACE|HIDEFACIALHAIR - w_class = WEIGHT_CLASS_SMALL - actions_types = list(/datum/action/item_action/adjust) - -/obj/item/clothing/mask/balaclava/attack_self(mob/user) - adjustmask(user) - -/obj/item/clothing/mask/infiltrator - name = "infiltrator balaclava" - desc = "It makes you feel safe in your anonymity, but for a stealth outfit you sure do look obvious that you're up to no good. It seems to have a built in heads-up display." - icon_state = "syndicate_balaclava" - item_state = "syndicate_balaclava" - clothing_flags = ALLOWINTERNALS - flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - visor_flags_inv = HIDEFACE|HIDEFACIALHAIR - w_class = WEIGHT_CLASS_SMALL - armor = list("melee" = 10, "bullet" = 5, "laser" = 5,"energy" = 5, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 100, "acid" = 40) - resistance_flags = FIRE_PROOF | ACID_PROOF - - var/voice_unknown = FALSE ///This makes it so that your name shows up as unknown when wearing the mask. - -/obj/item/clothing/mask/infiltrator/equipped(mob/living/carbon/human/user, slot) - . = ..() - if(slot != ITEM_SLOT_MASK) - return - to_chat(user, "You roll the balaclava over your face, and a data display appears before your eyes.") - ADD_TRAIT(user, TRAIT_DIAGNOSTIC_HUD, MASK_TRAIT) - var/datum/atom_hud/H = GLOB.huds[DATA_HUD_DIAGNOSTIC_BASIC] - H.add_hud_to(user) - voice_unknown = TRUE - -/obj/item/clothing/mask/infiltrator/dropped(mob/living/carbon/human/user) - to_chat(user, "You pull off the balaclava, and the mask's internal hud system switches off quietly.") - REMOVE_TRAIT(user, TRAIT_DIAGNOSTIC_HUD, MASK_TRAIT) - var/datum/atom_hud/H = GLOB.huds[DATA_HUD_DIAGNOSTIC_BASIC] - H.remove_hud_from(user) - voice_unknown = FALSE - return ..() - -/obj/item/clothing/mask/luchador - name = "Luchador Mask" - desc = "Worn by robust fighters, flying high to defeat their foes!" - icon_state = "luchag" - item_state = "luchag" - flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - w_class = WEIGHT_CLASS_SMALL - modifies_speech = TRUE - -/obj/item/clothing/mask/luchador/handle_speech(datum/source, list/speech_args) - var/message = speech_args[SPEECH_MESSAGE] - if(message[1] != "*") - message = replacetext(message, "captain", "CAPITÁN") - message = replacetext(message, "station", "ESTACIÓN") - message = replacetext(message, "sir", "SEÑOR") - message = replacetext(message, "the ", "el ") - message = replacetext(message, "my ", "mi ") - message = replacetext(message, "is ", "es ") - message = replacetext(message, "it's", "es") - message = replacetext(message, "friend", "amigo") - message = replacetext(message, "buddy", "amigo") - message = replacetext(message, "hello", "hola") - message = replacetext(message, " hot", " caliente") - message = replacetext(message, " very ", " muy ") - message = replacetext(message, "sword", "espada") - message = replacetext(message, "library", "biblioteca") - message = replacetext(message, "traitor", "traidor") - message = replacetext(message, "wizard", "mago") - message = uppertext(message) //Things end up looking better this way (no mixed cases), and it fits the macho wrestler image. - if(prob(25)) - message += " OLE!" - speech_args[SPEECH_MESSAGE] = message - -/obj/item/clothing/mask/luchador/tecnicos - name = "Tecnicos Mask" - desc = "Worn by robust fighters who uphold justice and fight honorably." - icon_state = "luchador" - item_state = "luchador" - -/obj/item/clothing/mask/luchador/rudos - name = "Rudos Mask" - desc = "Worn by robust fighters who are willing to do anything to win." - icon_state = "luchar" - item_state = "luchar" - -/obj/item/clothing/mask/russian_balaclava - name = "russian balaclava" - desc = "Protects your face from snow." - icon_state = "rus_balaclava" - item_state = "rus_balaclava" - flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - visor_flags_inv = HIDEFACE|HIDEFACIALHAIR - w_class = WEIGHT_CLASS_SMALL +/obj/item/clothing/mask/balaclava + name = "balaclava" + desc = "LOADSAMONEY" + icon_state = "balaclava" + item_state = "balaclava" + flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + visor_flags_inv = HIDEFACE|HIDEFACIALHAIR + w_class = WEIGHT_CLASS_SMALL + actions_types = list(/datum/action/item_action/adjust) + +/obj/item/clothing/mask/balaclava/attack_self(mob/user) + adjustmask(user) + +/obj/item/clothing/mask/infiltrator + name = "infiltrator balaclava" + desc = "It makes you feel safe in your anonymity, but for a stealth outfit you sure do look obvious that you're up to no good. It seems to have a built in heads-up display." + icon_state = "syndicate_balaclava" + item_state = "syndicate_balaclava" + clothing_flags = ALLOWINTERNALS + flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + visor_flags_inv = HIDEFACE|HIDEFACIALHAIR + w_class = WEIGHT_CLASS_SMALL + armor = list("melee" = 10, "bullet" = 5, "laser" = 5,"energy" = 5, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 100, "acid" = 40) + resistance_flags = FIRE_PROOF | ACID_PROOF + + var/voice_unknown = FALSE ///This makes it so that your name shows up as unknown when wearing the mask. + +/obj/item/clothing/mask/infiltrator/equipped(mob/living/carbon/human/user, slot) + . = ..() + if(slot != ITEM_SLOT_MASK) + return + to_chat(user, "You roll the balaclava over your face, and a data display appears before your eyes.") + ADD_TRAIT(user, TRAIT_DIAGNOSTIC_HUD, MASK_TRAIT) + var/datum/atom_hud/H = GLOB.huds[DATA_HUD_DIAGNOSTIC_BASIC] + H.add_hud_to(user) + voice_unknown = TRUE + +/obj/item/clothing/mask/infiltrator/dropped(mob/living/carbon/human/user) + to_chat(user, "You pull off the balaclava, and the mask's internal hud system switches off quietly.") + REMOVE_TRAIT(user, TRAIT_DIAGNOSTIC_HUD, MASK_TRAIT) + var/datum/atom_hud/H = GLOB.huds[DATA_HUD_DIAGNOSTIC_BASIC] + H.remove_hud_from(user) + voice_unknown = FALSE + return ..() + +/obj/item/clothing/mask/luchador + name = "Luchador Mask" + desc = "Worn by robust fighters, flying high to defeat their foes!" + icon_state = "luchag" + item_state = "luchag" + flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + w_class = WEIGHT_CLASS_SMALL + modifies_speech = TRUE + +/obj/item/clothing/mask/luchador/handle_speech(datum/source, list/speech_args) + var/message = speech_args[SPEECH_MESSAGE] + if(message[1] != "*") + message = replacetext(message, "captain", "CAPITÁN") + message = replacetext(message, "station", "ESTACIÓN") + message = replacetext(message, "sir", "SEÑOR") + message = replacetext(message, "the ", "el ") + message = replacetext(message, "my ", "mi ") + message = replacetext(message, "is ", "es ") + message = replacetext(message, "it's", "es") + message = replacetext(message, "friend", "amigo") + message = replacetext(message, "buddy", "amigo") + message = replacetext(message, "hello", "hola") + message = replacetext(message, " hot", " caliente") + message = replacetext(message, " very ", " muy ") + message = replacetext(message, "sword", "espada") + message = replacetext(message, "library", "biblioteca") + message = replacetext(message, "traitor", "traidor") + message = replacetext(message, "wizard", "mago") + message = uppertext(message) //Things end up looking better this way (no mixed cases), and it fits the macho wrestler image. + if(prob(25)) + message += " OLE!" + speech_args[SPEECH_MESSAGE] = message + +/obj/item/clothing/mask/luchador/tecnicos + name = "Tecnicos Mask" + desc = "Worn by robust fighters who uphold justice and fight honorably." + icon_state = "luchador" + item_state = "luchador" + +/obj/item/clothing/mask/luchador/rudos + name = "Rudos Mask" + desc = "Worn by robust fighters who are willing to do anything to win." + icon_state = "luchar" + item_state = "luchar" + +/obj/item/clothing/mask/russian_balaclava + name = "russian balaclava" + desc = "Protects your face from snow." + icon_state = "rus_balaclava" + item_state = "rus_balaclava" + flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + visor_flags_inv = HIDEFACE|HIDEFACIALHAIR + w_class = WEIGHT_CLASS_SMALL diff --git a/code/modules/clothing/masks/breath.dm b/code/modules/clothing/masks/breath.dm index e58dfe4dc088..5b0849163127 100644 --- a/code/modules/clothing/masks/breath.dm +++ b/code/modules/clothing/masks/breath.dm @@ -1,41 +1,41 @@ -/obj/item/clothing/mask/breath - desc = "A close-fitting mask that can be connected to an air supply." - name = "breath mask" - icon_state = "breath" - item_state = "m_mask" - body_parts_covered = 0 - clothing_flags = ALLOWINTERNALS //Wasp Port - Cit Internals - visor_flags = ALLOWINTERNALS - w_class = WEIGHT_CLASS_SMALL - gas_transfer_coefficient = 0.1 - permeability_coefficient = 0.5 - actions_types = list(/datum/action/item_action/adjust) - flags_cover = MASKCOVERSMOUTH - visor_flags_cover = MASKCOVERSMOUTH - resistance_flags = NONE - -/obj/item/clothing/mask/breath/suicide_act(mob/living/carbon/user) - user.visible_message("[user] is wrapping \the [src]'s tube around [user.p_their()] neck! It looks like [user.p_theyre()] trying to commit suicide!") - return OXYLOSS - -/obj/item/clothing/mask/breath/attack_self(mob/user) - adjustmask(user) - -/obj/item/clothing/mask/breath/AltClick(mob/user) - ..() - if(!user.canUseTopic(src, BE_CLOSE, ismonkey(user))) - return - else - adjustmask(user) - -/obj/item/clothing/mask/breath/examine(mob/user) - . = ..() - . += "Alt-click [src] to adjust it." - -/obj/item/clothing/mask/breath/medical - desc = "A close-fitting sterile mask that can be connected to an air supply." - name = "medical mask" - icon_state = "medical" - item_state = "m_mask" - permeability_coefficient = 0.01 - equip_delay_other = 10 +/obj/item/clothing/mask/breath + desc = "A close-fitting mask that can be connected to an air supply." + name = "breath mask" + icon_state = "breath" + item_state = "m_mask" + body_parts_covered = 0 + clothing_flags = ALLOWINTERNALS //Wasp Port - Cit Internals + visor_flags = ALLOWINTERNALS + w_class = WEIGHT_CLASS_SMALL + gas_transfer_coefficient = 0.1 + permeability_coefficient = 0.5 + actions_types = list(/datum/action/item_action/adjust) + flags_cover = MASKCOVERSMOUTH + visor_flags_cover = MASKCOVERSMOUTH + resistance_flags = NONE + +/obj/item/clothing/mask/breath/suicide_act(mob/living/carbon/user) + user.visible_message("[user] is wrapping \the [src]'s tube around [user.p_their()] neck! It looks like [user.p_theyre()] trying to commit suicide!") + return OXYLOSS + +/obj/item/clothing/mask/breath/attack_self(mob/user) + adjustmask(user) + +/obj/item/clothing/mask/breath/AltClick(mob/user) + ..() + if(!user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + return + else + adjustmask(user) + +/obj/item/clothing/mask/breath/examine(mob/user) + . = ..() + . += "Alt-click [src] to adjust it." + +/obj/item/clothing/mask/breath/medical + desc = "A close-fitting sterile mask that can be connected to an air supply." + name = "medical mask" + icon_state = "medical" + item_state = "m_mask" + permeability_coefficient = 0.01 + equip_delay_other = 10 diff --git a/code/modules/clothing/masks/gasmask.dm b/code/modules/clothing/masks/gasmask.dm index 1902fa504709..ae77c6d99bf9 100644 --- a/code/modules/clothing/masks/gasmask.dm +++ b/code/modules/clothing/masks/gasmask.dm @@ -1,264 +1,264 @@ -/obj/item/clothing/mask/gas - name = "gas mask" - desc = "A face-covering mask that can be connected to an air supply. While good for concealing your identity, it isn't good for blocking gas flow." //More accurate - icon_state = "gas_alt" - clothing_flags = BLOCK_GAS_SMOKE_EFFECT | ALLOWINTERNALS - flags_inv = HIDEEARS|HIDEEYES|HIDEFACE|HIDEFACIALHAIR - w_class = WEIGHT_CLASS_NORMAL - item_state = "gas_alt" - gas_transfer_coefficient = 0.01 - permeability_coefficient = 0.01 - flags_cover = MASKCOVERSEYES | MASKCOVERSMOUTH | PEPPERPROOF - resistance_flags = NONE - -/obj/item/clothing/mask/gas/atmos - name = "atmospheric gas mask" - desc = "Improved gas mask utilized by atmospheric technicians. Still not very good at blocking gas flow, but it's flameproof!" - icon_state = "gas_atmos" - item_state = "gas_atmos" - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 20, "acid" = 10) - w_class = WEIGHT_CLASS_SMALL - gas_transfer_coefficient = 0.001 //cargo cult time, this var does nothing but just in case someone actually makes it do something - permeability_coefficient = 0.001 - resistance_flags = FIRE_PROOF - -/obj/item/clothing/mask/gas/atmos/captain - name = "captain's gas mask" - desc = "Nanotrasen cut corners and repainted a spare atmospheric gas mask, but don't tell anyone." - icon_state = "gas_cap" - item_state = "gas_cap" - resistance_flags = FIRE_PROOF | ACID_PROOF - -// **** Welding gas mask **** - -/obj/item/clothing/mask/gas/welding - name = "welding mask" - desc = "A gas mask with built-in welding goggles and a face shield. Looks like a skull - clearly designed by a nerd." - icon_state = "weldingmask" - flash_protect = FLASH_PROTECTION_WELDER - custom_materials = list(/datum/material/iron=4000, /datum/material/glass=2000) - tint = 2 - armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 55) - actions_types = list(/datum/action/item_action/toggle) - flags_inv = HIDEEARS|HIDEEYES|HIDEFACE - flags_cover = MASKCOVERSEYES - visor_flags_inv = HIDEEYES - visor_flags_cover = MASKCOVERSEYES - resistance_flags = FIRE_PROOF - -/obj/item/clothing/mask/gas/welding/attack_self(mob/user) - weldingvisortoggle(user) - -/obj/item/clothing/mask/gas/welding/up - -/obj/item/clothing/mask/gas/welding/up/Initialize() - ..() - visor_toggling() - -// ******************************************************************** - -//Plague Dr suit can be found in clothing/suits/bio.dm -/obj/item/clothing/mask/gas/plaguedoctor - name = "plague doctor mask" - desc = "A modernised version of the classic design, this mask will not only filter out toxins but it can also be connected to an air supply." - icon_state = "plaguedoctor" - item_state = "gas_mask" - armor = list("melee" = 0, "bullet" = 0, "laser" = 2,"energy" = 2, "bomb" = 0, "bio" = 75, "rad" = 0, "fire" = 0, "acid" = 0) - -/obj/item/clothing/mask/gas/syndicate - name = "syndicate mask" - desc = "A close-fitting tactical mask that can be connected to an air supply." - icon_state = "syndicate" - strip_delay = 60 - -/obj/item/clothing/mask/gas/clown_hat - name = "clown wig and mask" - desc = "A true prankster's facial attire. A clown is incomplete without his wig and mask." - clothing_flags = ALLOWINTERNALS - icon_state = "clown" - item_state = "clown_hat" - dye_color = "clown" - w_class = WEIGHT_CLASS_SMALL - flags_cover = MASKCOVERSEYES - resistance_flags = FLAMMABLE - actions_types = list(/datum/action/item_action/adjust) - dog_fashion = /datum/dog_fashion/head/clown - var/list/clownmask_designs = list() - -/obj/item/clothing/mask/gas/clown_hat/Initialize(mapload) - .=..() - clownmask_designs = list( - "True Form" = image(icon = src.icon, icon_state = "clown"), - "The Feminist" = image(icon = src.icon, icon_state = "sexyclown"), - "The Jester" = image(icon = src.icon, icon_state = "chaos"), - "The Madman" = image(icon = src.icon, icon_state = "joker"), - "The Rainbow Color" = image(icon = src.icon, icon_state = "rainbow") - ) - -/obj/item/clothing/mask/gas/clown_hat/ui_action_click(mob/user) - if(!istype(user) || user.incapacitated()) - return - - var/list/options = list() - options["True Form"] = "clown" - options["The Feminist"] = "sexyclown" - options["The Madman"] = "joker" - options["The Rainbow Color"] ="rainbow" - options["The Jester"] ="chaos" //Nepeta33Leijon is holding me captive and forced me to help with this please send help - - var/choice = show_radial_menu(user,src, clownmask_designs, custom_check = FALSE, radius = 36, require_near = TRUE) - if(!choice) - return FALSE - - if(src && choice && !user.incapacitated() && in_range(user,src)) - icon_state = options[choice] - user.update_inv_wear_mask() - for(var/X in actions) - var/datum/action/A = X - A.UpdateButtonIcon() - to_chat(user, "Your Clown Mask has now morphed into [choice], all praise the Honkmother!") - return TRUE - -/obj/item/clothing/mask/gas/sexyclown - name = "sexy-clown wig and mask" - desc = "A feminine clown mask for the dabbling crossdressers or female entertainers." - clothing_flags = ALLOWINTERNALS - icon_state = "sexyclown" - item_state = "sexyclown" - flags_cover = MASKCOVERSEYES - resistance_flags = FLAMMABLE - -/obj/item/clothing/mask/gas/mime - name = "mime mask" - desc = "The traditional mime's mask. It has an eerie facial posture." - clothing_flags = ALLOWINTERNALS - icon_state = "mime" - item_state = "mime" - w_class = WEIGHT_CLASS_SMALL - flags_cover = MASKCOVERSEYES - resistance_flags = FLAMMABLE - actions_types = list(/datum/action/item_action/adjust) - var/list/mimemask_designs = list() - -/obj/item/clothing/mask/gas/mime/Initialize(mapload) - .=..() - mimemask_designs = list( - "Blanc" = image(icon = src.icon, icon_state = "mime"), - "Excité" = image(icon = src.icon, icon_state = "sexymime"), - "Triste" = image(icon = src.icon, icon_state = "sadmime"), - "Effrayé" = image(icon = src.icon, icon_state = "scaredmime") - ) - -/obj/item/clothing/mask/gas/mime/ui_action_click(mob/user) - if(!istype(user) || user.incapacitated()) - return - - var/list/options = list() - options["Blanc"] = "mime" - options["Triste"] = "sadmime" - options["Effrayé"] = "scaredmime" - options["Excité"] ="sexymime" - - var/choice = show_radial_menu(user,src, mimemask_designs, custom_check = FALSE, radius = 36, require_near = TRUE) - if(!choice) - return FALSE - - if(src && choice && !user.incapacitated() && in_range(user,src)) - icon_state = options[choice] - user.update_inv_wear_mask() - for(var/X in actions) - var/datum/action/A = X - A.UpdateButtonIcon() - to_chat(user, "Your Mime Mask has now morphed into [choice]!") - return TRUE - -/obj/item/clothing/mask/gas/monkeymask - name = "monkey mask" - desc = "A mask used when acting as a monkey." - clothing_flags = ALLOWINTERNALS - icon_state = "monkeymask" - item_state = "monkeymask" - flags_cover = MASKCOVERSEYES - resistance_flags = FLAMMABLE - -/obj/item/clothing/mask/gas/sexymime - name = "sexy mime mask" - desc = "A traditional female mime's mask." - clothing_flags = ALLOWINTERNALS - icon_state = "sexymime" - item_state = "sexymime" - flags_cover = MASKCOVERSEYES - resistance_flags = FLAMMABLE - -/obj/item/clothing/mask/gas/cyborg - name = "cyborg visor" - desc = "Beep boop." - icon_state = "death" - resistance_flags = FLAMMABLE - -/obj/item/clothing/mask/gas/owl_mask - name = "owl mask" - desc = "Twoooo!" - icon_state = "owl" - clothing_flags = ALLOWINTERNALS - flags_cover = MASKCOVERSEYES - resistance_flags = FLAMMABLE - -/obj/item/clothing/mask/gas/carp - name = "carp mask" - desc = "Gnash gnash." - icon_state = "carp_mask" - -/obj/item/clothing/mask/gas/tiki_mask - name = "tiki mask" - desc = "A creepy wooden mask. Surprisingly expressive for a poorly carved bit of wood." - icon_state = "tiki_eyebrow" - item_state = "tiki_eyebrow" - custom_materials = list(/datum/material/wood = MINERAL_MATERIAL_AMOUNT * 1.25) - resistance_flags = FLAMMABLE - max_integrity = 100 - actions_types = list(/datum/action/item_action/adjust) - dog_fashion = null - var/list/tikimask_designs = list() - -/obj/item/clothing/mask/gas/tiki_mask/Initialize(mapload) - .=..() - tikimask_designs = list( - "Original Tiki" = image(icon = src.icon, icon_state = "tiki_eyebrow"), - "Happy Tiki" = image(icon = src.icon, icon_state = "tiki_happy"), - "Confused Tiki" = image(icon = src.icon, icon_state = "tiki_confused"), - "Angry Tiki" = image(icon = src.icon, icon_state = "tiki_angry") - ) - -/obj/item/clothing/mask/gas/tiki_mask/ui_action_click(mob/user) - var/mob/M = usr - var/list/options = list() - options["Original Tiki"] = "tiki_eyebrow" - options["Happy Tiki"] = "tiki_happy" - options["Confused Tiki"] = "tiki_confused" - options["Angry Tiki"] ="tiki_angry" - - var/choice = show_radial_menu(user,src, tikimask_designs, custom_check = FALSE, radius = 36, require_near = TRUE) - if(!choice) - return FALSE - - if(src && choice && !M.stat && in_range(M,src)) - icon_state = options[choice] - user.update_inv_wear_mask() - for(var/X in actions) - var/datum/action/A = X - A.UpdateButtonIcon() - to_chat(M, "The Tiki Mask has now changed into the [choice] Mask!") - return 1 - -/obj/item/clothing/mask/gas/tiki_mask/yalp_elor - icon_state = "tiki_yalp" - actions_types = list() - -/obj/item/clothing/mask/gas/hunter - name = "bounty hunting mask" - desc = "A custom tactical mask with decals added." - icon_state = "hunter" - item_state = "hunter" - resistance_flags = FIRE_PROOF | ACID_PROOF - flags_inv = HIDEFACIALHAIR|HIDEFACE|HIDEEYES|HIDEEARS|HIDEHAIR +/obj/item/clothing/mask/gas + name = "gas mask" + desc = "A face-covering mask that can be connected to an air supply. While good for concealing your identity, it isn't good for blocking gas flow." //More accurate + icon_state = "gas_alt" + clothing_flags = BLOCK_GAS_SMOKE_EFFECT | ALLOWINTERNALS + flags_inv = HIDEEARS|HIDEEYES|HIDEFACE|HIDEFACIALHAIR + w_class = WEIGHT_CLASS_NORMAL + item_state = "gas_alt" + gas_transfer_coefficient = 0.01 + permeability_coefficient = 0.01 + flags_cover = MASKCOVERSEYES | MASKCOVERSMOUTH | PEPPERPROOF + resistance_flags = NONE + +/obj/item/clothing/mask/gas/atmos + name = "atmospheric gas mask" + desc = "Improved gas mask utilized by atmospheric technicians. Still not very good at blocking gas flow, but it's flameproof!" + icon_state = "gas_atmos" + item_state = "gas_atmos" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 20, "acid" = 10) + w_class = WEIGHT_CLASS_SMALL + gas_transfer_coefficient = 0.001 //cargo cult time, this var does nothing but just in case someone actually makes it do something + permeability_coefficient = 0.001 + resistance_flags = FIRE_PROOF + +/obj/item/clothing/mask/gas/atmos/captain + name = "captain's gas mask" + desc = "Nanotrasen cut corners and repainted a spare atmospheric gas mask, but don't tell anyone." + icon_state = "gas_cap" + item_state = "gas_cap" + resistance_flags = FIRE_PROOF | ACID_PROOF + +// **** Welding gas mask **** + +/obj/item/clothing/mask/gas/welding + name = "welding mask" + desc = "A gas mask with built-in welding goggles and a face shield. Looks like a skull - clearly designed by a nerd." + icon_state = "weldingmask" + flash_protect = FLASH_PROTECTION_WELDER + custom_materials = list(/datum/material/iron=4000, /datum/material/glass=2000) + tint = 2 + armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 55) + actions_types = list(/datum/action/item_action/toggle) + flags_inv = HIDEEARS|HIDEEYES|HIDEFACE + flags_cover = MASKCOVERSEYES + visor_flags_inv = HIDEEYES + visor_flags_cover = MASKCOVERSEYES + resistance_flags = FIRE_PROOF + +/obj/item/clothing/mask/gas/welding/attack_self(mob/user) + weldingvisortoggle(user) + +/obj/item/clothing/mask/gas/welding/up + +/obj/item/clothing/mask/gas/welding/up/Initialize() + ..() + visor_toggling() + +// ******************************************************************** + +//Plague Dr suit can be found in clothing/suits/bio.dm +/obj/item/clothing/mask/gas/plaguedoctor + name = "plague doctor mask" + desc = "A modernised version of the classic design, this mask will not only filter out toxins but it can also be connected to an air supply." + icon_state = "plaguedoctor" + item_state = "gas_mask" + armor = list("melee" = 0, "bullet" = 0, "laser" = 2,"energy" = 2, "bomb" = 0, "bio" = 75, "rad" = 0, "fire" = 0, "acid" = 0) + +/obj/item/clothing/mask/gas/syndicate + name = "syndicate mask" + desc = "A close-fitting tactical mask that can be connected to an air supply." + icon_state = "syndicate" + strip_delay = 60 + +/obj/item/clothing/mask/gas/clown_hat + name = "clown wig and mask" + desc = "A true prankster's facial attire. A clown is incomplete without his wig and mask." + clothing_flags = ALLOWINTERNALS + icon_state = "clown" + item_state = "clown_hat" + dye_color = "clown" + w_class = WEIGHT_CLASS_SMALL + flags_cover = MASKCOVERSEYES + resistance_flags = FLAMMABLE + actions_types = list(/datum/action/item_action/adjust) + dog_fashion = /datum/dog_fashion/head/clown + var/list/clownmask_designs = list() + +/obj/item/clothing/mask/gas/clown_hat/Initialize(mapload) + .=..() + clownmask_designs = list( + "True Form" = image(icon = src.icon, icon_state = "clown"), + "The Feminist" = image(icon = src.icon, icon_state = "sexyclown"), + "The Jester" = image(icon = src.icon, icon_state = "chaos"), + "The Madman" = image(icon = src.icon, icon_state = "joker"), + "The Rainbow Color" = image(icon = src.icon, icon_state = "rainbow") + ) + +/obj/item/clothing/mask/gas/clown_hat/ui_action_click(mob/user) + if(!istype(user) || user.incapacitated()) + return + + var/list/options = list() + options["True Form"] = "clown" + options["The Feminist"] = "sexyclown" + options["The Madman"] = "joker" + options["The Rainbow Color"] ="rainbow" + options["The Jester"] ="chaos" //Nepeta33Leijon is holding me captive and forced me to help with this please send help + + var/choice = show_radial_menu(user,src, clownmask_designs, custom_check = FALSE, radius = 36, require_near = TRUE) + if(!choice) + return FALSE + + if(src && choice && !user.incapacitated() && in_range(user,src)) + icon_state = options[choice] + user.update_inv_wear_mask() + for(var/X in actions) + var/datum/action/A = X + A.UpdateButtonIcon() + to_chat(user, "Your Clown Mask has now morphed into [choice], all praise the Honkmother!") + return TRUE + +/obj/item/clothing/mask/gas/sexyclown + name = "sexy-clown wig and mask" + desc = "A feminine clown mask for the dabbling crossdressers or female entertainers." + clothing_flags = ALLOWINTERNALS + icon_state = "sexyclown" + item_state = "sexyclown" + flags_cover = MASKCOVERSEYES + resistance_flags = FLAMMABLE + +/obj/item/clothing/mask/gas/mime + name = "mime mask" + desc = "The traditional mime's mask. It has an eerie facial posture." + clothing_flags = ALLOWINTERNALS + icon_state = "mime" + item_state = "mime" + w_class = WEIGHT_CLASS_SMALL + flags_cover = MASKCOVERSEYES + resistance_flags = FLAMMABLE + actions_types = list(/datum/action/item_action/adjust) + var/list/mimemask_designs = list() + +/obj/item/clothing/mask/gas/mime/Initialize(mapload) + .=..() + mimemask_designs = list( + "Blanc" = image(icon = src.icon, icon_state = "mime"), + "Excité" = image(icon = src.icon, icon_state = "sexymime"), + "Triste" = image(icon = src.icon, icon_state = "sadmime"), + "Effrayé" = image(icon = src.icon, icon_state = "scaredmime") + ) + +/obj/item/clothing/mask/gas/mime/ui_action_click(mob/user) + if(!istype(user) || user.incapacitated()) + return + + var/list/options = list() + options["Blanc"] = "mime" + options["Triste"] = "sadmime" + options["Effrayé"] = "scaredmime" + options["Excité"] ="sexymime" + + var/choice = show_radial_menu(user,src, mimemask_designs, custom_check = FALSE, radius = 36, require_near = TRUE) + if(!choice) + return FALSE + + if(src && choice && !user.incapacitated() && in_range(user,src)) + icon_state = options[choice] + user.update_inv_wear_mask() + for(var/X in actions) + var/datum/action/A = X + A.UpdateButtonIcon() + to_chat(user, "Your Mime Mask has now morphed into [choice]!") + return TRUE + +/obj/item/clothing/mask/gas/monkeymask + name = "monkey mask" + desc = "A mask used when acting as a monkey." + clothing_flags = ALLOWINTERNALS + icon_state = "monkeymask" + item_state = "monkeymask" + flags_cover = MASKCOVERSEYES + resistance_flags = FLAMMABLE + +/obj/item/clothing/mask/gas/sexymime + name = "sexy mime mask" + desc = "A traditional female mime's mask." + clothing_flags = ALLOWINTERNALS + icon_state = "sexymime" + item_state = "sexymime" + flags_cover = MASKCOVERSEYES + resistance_flags = FLAMMABLE + +/obj/item/clothing/mask/gas/cyborg + name = "cyborg visor" + desc = "Beep boop." + icon_state = "death" + resistance_flags = FLAMMABLE + +/obj/item/clothing/mask/gas/owl_mask + name = "owl mask" + desc = "Twoooo!" + icon_state = "owl" + clothing_flags = ALLOWINTERNALS + flags_cover = MASKCOVERSEYES + resistance_flags = FLAMMABLE + +/obj/item/clothing/mask/gas/carp + name = "carp mask" + desc = "Gnash gnash." + icon_state = "carp_mask" + +/obj/item/clothing/mask/gas/tiki_mask + name = "tiki mask" + desc = "A creepy wooden mask. Surprisingly expressive for a poorly carved bit of wood." + icon_state = "tiki_eyebrow" + item_state = "tiki_eyebrow" + custom_materials = list(/datum/material/wood = MINERAL_MATERIAL_AMOUNT * 1.25) + resistance_flags = FLAMMABLE + max_integrity = 100 + actions_types = list(/datum/action/item_action/adjust) + dog_fashion = null + var/list/tikimask_designs = list() + +/obj/item/clothing/mask/gas/tiki_mask/Initialize(mapload) + .=..() + tikimask_designs = list( + "Original Tiki" = image(icon = src.icon, icon_state = "tiki_eyebrow"), + "Happy Tiki" = image(icon = src.icon, icon_state = "tiki_happy"), + "Confused Tiki" = image(icon = src.icon, icon_state = "tiki_confused"), + "Angry Tiki" = image(icon = src.icon, icon_state = "tiki_angry") + ) + +/obj/item/clothing/mask/gas/tiki_mask/ui_action_click(mob/user) + var/mob/M = usr + var/list/options = list() + options["Original Tiki"] = "tiki_eyebrow" + options["Happy Tiki"] = "tiki_happy" + options["Confused Tiki"] = "tiki_confused" + options["Angry Tiki"] ="tiki_angry" + + var/choice = show_radial_menu(user,src, tikimask_designs, custom_check = FALSE, radius = 36, require_near = TRUE) + if(!choice) + return FALSE + + if(src && choice && !M.stat && in_range(M,src)) + icon_state = options[choice] + user.update_inv_wear_mask() + for(var/X in actions) + var/datum/action/A = X + A.UpdateButtonIcon() + to_chat(M, "The Tiki Mask has now changed into the [choice] Mask!") + return 1 + +/obj/item/clothing/mask/gas/tiki_mask/yalp_elor + icon_state = "tiki_yalp" + actions_types = list() + +/obj/item/clothing/mask/gas/hunter + name = "bounty hunting mask" + desc = "A custom tactical mask with decals added." + icon_state = "hunter" + item_state = "hunter" + resistance_flags = FIRE_PROOF | ACID_PROOF + flags_inv = HIDEFACIALHAIR|HIDEFACE|HIDEEYES|HIDEEARS|HIDEHAIR diff --git a/code/modules/clothing/masks/miscellaneous.dm b/code/modules/clothing/masks/miscellaneous.dm index 51528c016a37..1e1aad69645b 100644 --- a/code/modules/clothing/masks/miscellaneous.dm +++ b/code/modules/clothing/masks/miscellaneous.dm @@ -1,335 +1,335 @@ -/obj/item/clothing/mask/muzzle - name = "muzzle" - desc = "To stop that awful noise." - icon_state = "muzzle" - item_state = "blindfold" - flags_cover = MASKCOVERSMOUTH - w_class = WEIGHT_CLASS_SMALL - gas_transfer_coefficient = 0.9 - equip_delay_other = 20 - -/obj/item/clothing/mask/muzzle/attack_paw(mob/user) - if(iscarbon(user)) - var/mob/living/carbon/C = user - if(src == C.wear_mask) - to_chat(user, "You need help taking this off!") - return - ..() - -/obj/item/clothing/mask/surgical - name = "sterile mask" - desc = "A sterile mask designed to help prevent the spread of diseases." - icon_state = "sterile" - item_state = "sterile" - w_class = WEIGHT_CLASS_TINY - flags_inv = HIDEFACE - flags_cover = MASKCOVERSMOUTH - visor_flags_inv = HIDEFACE - visor_flags_cover = MASKCOVERSMOUTH - gas_transfer_coefficient = 0.9 - permeability_coefficient = 0.01 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 25, "rad" = 0, "fire" = 0, "acid" = 0) - actions_types = list(/datum/action/item_action/adjust) - -/obj/item/clothing/mask/surgical/attack_self(mob/user) - adjustmask(user) - -/obj/item/clothing/mask/fakemoustache - name = "fake moustache" - desc = "Warning: moustache is fake." - icon_state = "fake-moustache" - flags_inv = HIDEFACE - -/obj/item/clothing/mask/fakemoustache/italian - name = "italian moustache" - desc = "Made from authentic Italian moustache hairs. Gives the wearer an irresistable urge to gesticulate wildly." - modifies_speech = TRUE - -/obj/item/clothing/mask/fakemoustache/italian/handle_speech(datum/source, list/speech_args) - var/message = speech_args[SPEECH_MESSAGE] - if(message[1] != "*") - message = " [message]" - var/list/italian_words = strings("italian_replacement.json", "italian") - - for(var/key in italian_words) - var/value = italian_words[key] - if(islist(value)) - value = pick(value) - - message = replacetextEx(message, " [uppertext(key)]", " [uppertext(value)]") - message = replacetextEx(message, " [capitalize(key)]", " [capitalize(value)]") - message = replacetextEx(message, " [key]", " [value]") - - if(prob(3)) - message += pick(" Ravioli, ravioli, give me the formuoli!"," Mamma-mia!"," Mamma-mia! That's a spicy meat-ball!", " La la la la la funiculi funicula!") - speech_args[SPEECH_MESSAGE] = trim(message) - -/obj/item/clothing/mask/joy - name = "joy mask" - desc = "Express your happiness or hide your sorrows with this laughing face with crying tears of joy cutout." - icon_state = "joy" - -/obj/item/clothing/mask/pig - name = "pig mask" - desc = "A rubber pig mask with a built-in voice modulator." - icon_state = "pig" - item_state = "pig" - flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - clothing_flags = VOICEBOX_TOGGLABLE - w_class = WEIGHT_CLASS_SMALL - modifies_speech = TRUE - -/obj/item/clothing/mask/pig/handle_speech(datum/source, list/speech_args) - if(!(clothing_flags & VOICEBOX_DISABLED)) - speech_args[SPEECH_MESSAGE] = pick("Oink!","Squeeeeeeee!","Oink Oink!") - -/obj/item/clothing/mask/pig/cursed - name = "pig face" - desc = "It looks like a mask, but closer inspection reveals it's melded onto this person's face!" - flags_inv = HIDEFACIALHAIR - clothing_flags = NONE - -/obj/item/clothing/mask/pig/cursed/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CURSED_MASK_TRAIT) - playsound(get_turf(src), 'sound/magic/pighead_curse.ogg', 50, TRUE) - -///frog mask - reeee!! -/obj/item/clothing/mask/frog - name = "frog mask" - desc = "An ancient mask carved in the shape of a frog.
                    Sanity is like gravity, all it needs is a push." - icon_state = "frog" - item_state = "frog" - flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - w_class = WEIGHT_CLASS_SMALL - clothing_flags = VOICEBOX_TOGGLABLE - modifies_speech = TRUE - -/obj/item/clothing/mask/frog/handle_speech(datum/source, list/speech_args) //whenever you speak - if(!(clothing_flags & VOICEBOX_DISABLED)) - if(prob(5)) //sometimes, the angry spirit finds others words to speak. - speech_args[SPEECH_MESSAGE] = pick("HUUUUU!!","SMOOOOOKIN'!!","Hello my baby, hello my honey, hello my rag-time gal.", "Feels bad, man.", "GIT DIS GUY OFF ME!!" ,"SOMEBODY STOP ME!!", "NORMIES, GET OUT!!") - else - speech_args[SPEECH_MESSAGE] = pick("Ree!!", "Reee!!","REEE!!","REEEEE!!") //but its usually just angry gibberish, - -/obj/item/clothing/mask/frog/cursed - clothing_flags = NONE - -/obj/item/clothing/mask/frog/cursed/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CURSED_MASK_TRAIT) - -/obj/item/clothing/mask/frog/cursed/equipped(mob/user, slot) - var/mob/living/carbon/C = user - if(C.wear_mask == src && HAS_TRAIT_FROM(src, TRAIT_NODROP, CURSED_MASK_TRAIT)) - to_chat(user, "[src] was cursed! Ree!!") - return ..() - -/obj/item/clothing/mask/cowmask - name = "cow mask" - icon_state = "cowmask" - item_state = "cowmask" - clothing_flags = VOICEBOX_TOGGLABLE - flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - w_class = WEIGHT_CLASS_SMALL - modifies_speech = TRUE - -/obj/item/clothing/mask/cowmask/handle_speech(datum/source, list/speech_args) - if(!(clothing_flags & VOICEBOX_DISABLED)) - speech_args[SPEECH_MESSAGE] = pick("Moooooooo!","Moo!","Moooo!") - -/obj/item/clothing/mask/cowmask/cursed - name = "cow face" - desc = "It looks like a cow mask, but closer inspection reveals it's melded onto this person's face!" - flags_inv = HIDEFACIALHAIR - clothing_flags = NONE - -/obj/item/clothing/mask/cowmask/cursed/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CURSED_MASK_TRAIT) - playsound(get_turf(src), 'sound/magic/cowhead_curse.ogg', 50, TRUE) - -/obj/item/clothing/mask/horsehead - name = "horse head mask" - desc = "A mask made of soft vinyl and latex, representing the head of a horse." - icon_state = "horsehead" - item_state = "horsehead" - flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR|HIDEEYES|HIDEEARS - w_class = WEIGHT_CLASS_SMALL - clothing_flags = VOICEBOX_TOGGLABLE - -/obj/item/clothing/mask/horsehead/handle_speech(datum/source, list/speech_args) - if(!(clothing_flags & VOICEBOX_DISABLED)) - speech_args[SPEECH_MESSAGE] = pick("NEEIIGGGHHHH!", "NEEEIIIIGHH!", "NEIIIGGHH!", "HAAWWWWW!", "HAAAWWW!") - -/obj/item/clothing/mask/horsehead/cursed - name = "horse face" - desc = "It initially looks like a mask, but it's melded into the poor person's face." - clothing_flags = NONE - flags_inv = HIDEFACIALHAIR - -/obj/item/clothing/mask/horsehead/cursed/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CURSED_MASK_TRAIT) - playsound(get_turf(src), 'sound/magic/horsehead_curse.ogg', 50, TRUE) - -/obj/item/clothing/mask/rat - name = "rat mask" - desc = "A mask made of soft vinyl and latex, representing the head of a rat." - icon_state = "rat" - item_state = "rat" - flags_inv = HIDEFACE - flags_cover = MASKCOVERSMOUTH - -/obj/item/clothing/mask/rat/fox - name = "fox mask" - desc = "A mask made of soft vinyl and latex, representing the head of a fox." - icon_state = "fox" - item_state = "fox" - -/obj/item/clothing/mask/rat/bee - name = "bee mask" - desc = "A mask made of soft vinyl and latex, representing the head of a bee." - icon_state = "bee" - item_state = "bee" - -/obj/item/clothing/mask/rat/bear - name = "bear mask" - desc = "A mask made of soft vinyl and latex, representing the head of a bear." - icon_state = "bear" - item_state = "bear" - -/obj/item/clothing/mask/rat/bat - name = "bat mask" - desc = "A mask made of soft vinyl and latex, representing the head of a bat." - icon_state = "bat" - item_state = "bat" - -/obj/item/clothing/mask/rat/raven - name = "raven mask" - desc = "A mask made of soft vinyl and latex, representing the head of a raven." - icon_state = "raven" - item_state = "raven" - -/obj/item/clothing/mask/rat/jackal - name = "jackal mask" - desc = "A mask made of soft vinyl and latex, representing the head of a jackal." - icon_state = "jackal" - item_state = "jackal" - -/obj/item/clothing/mask/rat/tribal - name = "tribal mask" - desc = "A mask carved out of wood, detailed carefully by hand." - icon_state = "bumba" - item_state = "bumba" - -/obj/item/clothing/mask/bandana - name = "botany bandana" - desc = "A fine bandana with nanotech lining and a hydroponics pattern." - w_class = WEIGHT_CLASS_TINY - flags_cover = MASKCOVERSMOUTH - flags_inv = HIDEFACE|HIDEFACIALHAIR - visor_flags_inv = HIDEFACE|HIDEFACIALHAIR - visor_flags_cover = MASKCOVERSMOUTH | PEPPERPROOF - slot_flags = ITEM_SLOT_MASK - adjusted_flags = ITEM_SLOT_HEAD - icon_state = "bandbotany" - -/obj/item/clothing/mask/bandana/attack_self(mob/user) - adjustmask(user) - -/obj/item/clothing/mask/bandana/AltClick(mob/user) - . = ..() - if(iscarbon(user)) - var/mob/living/carbon/C = user - if((C.get_item_by_slot(ITEM_SLOT_HEAD == src)) || (C.get_item_by_slot(ITEM_SLOT_MASK) == src)) - to_chat(user, "You can't tie [src] while wearing it!") - return - if(slot_flags & ITEM_SLOT_HEAD) - to_chat(user, "You must undo [src] before you can tie it into a neckerchief!") - else - if(user.is_holding(src)) - var/obj/item/clothing/neck/neckerchief/nk = new(src) - nk.name = "[name] neckerchief" - nk.desc = "[desc] It's tied up like a neckerchief." - nk.icon_state = icon_state - nk.sourceBandanaType = src.type - var/currentHandIndex = user.get_held_index_of_item(src) - user.transferItemToLoc(src, null) - user.put_in_hand(nk, currentHandIndex) - user.visible_message("You tie [src] up like a neckerchief.", "[user] ties [src] up like a neckerchief.") - qdel(src) - else - to_chat(user, "You must be holding [src] in order to tie it!") - -/obj/item/clothing/mask/bandana/red - name = "red bandana" - desc = "A fine red bandana with nanotech lining." - icon_state = "bandred" - -/obj/item/clothing/mask/bandana/blue - name = "blue bandana" - desc = "A fine blue bandana with nanotech lining." - icon_state = "bandblue" - -/obj/item/clothing/mask/bandana/green - name = "green bandana" - desc = "A fine green bandana with nanotech lining." - icon_state = "bandgreen" - -/obj/item/clothing/mask/bandana/gold - name = "gold bandana" - desc = "A fine gold bandana with nanotech lining." - icon_state = "bandgold" - -/obj/item/clothing/mask/bandana/black - name = "black bandana" - desc = "A fine black bandana with nanotech lining." - icon_state = "bandblack" - -/obj/item/clothing/mask/bandana/skull - name = "skull bandana" - desc = "A fine black bandana with nanotech lining and a skull emblem." - icon_state = "bandskull" - -/obj/item/clothing/mask/bandana/durathread - name = "durathread bandana" - desc = "A bandana made from durathread, you wish it would provide some protection to its wearer, but it's far too thin..." - icon_state = "banddurathread" - -/obj/item/clothing/mask/mummy - name = "mummy mask" - desc = "Ancient bandages." - icon_state = "mummy_mask" - item_state = "mummy_mask" - flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - -/obj/item/clothing/mask/scarecrow - name = "sack mask" - desc = "A burlap sack with eyeholes." - icon_state = "scarecrow_sack" - item_state = "scarecrow_sack" - flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - -/obj/item/clothing/mask/gondola - name = "gondola mask" - desc = "Genuine gondola fur." - icon_state = "gondola" - item_state = "gondola" - flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - w_class = WEIGHT_CLASS_SMALL - modifies_speech = TRUE - -/obj/item/clothing/mask/gondola/handle_speech(datum/source, list/speech_args) - var/message = speech_args[SPEECH_MESSAGE] - if(message[1] != "*") - message = " [message]" - var/list/spurdo_words = strings("spurdo_replacement.json", "spurdo") - for(var/key in spurdo_words) - var/value = spurdo_words[key] - if(islist(value)) - value = pick(value) - message = replacetextEx(message,regex(uppertext(key),"g"), "[uppertext(value)]") - message = replacetextEx(message,regex(capitalize(key),"g"), "[capitalize(value)]") - message = replacetextEx(message,regex(key,"g"), "[value]") - speech_args[SPEECH_MESSAGE] = trim(message) +/obj/item/clothing/mask/muzzle + name = "muzzle" + desc = "To stop that awful noise." + icon_state = "muzzle" + item_state = "blindfold" + flags_cover = MASKCOVERSMOUTH + w_class = WEIGHT_CLASS_SMALL + gas_transfer_coefficient = 0.9 + equip_delay_other = 20 + +/obj/item/clothing/mask/muzzle/attack_paw(mob/user) + if(iscarbon(user)) + var/mob/living/carbon/C = user + if(src == C.wear_mask) + to_chat(user, "You need help taking this off!") + return + ..() + +/obj/item/clothing/mask/surgical + name = "sterile mask" + desc = "A sterile mask designed to help prevent the spread of diseases." + icon_state = "sterile" + item_state = "sterile" + w_class = WEIGHT_CLASS_TINY + flags_inv = HIDEFACE + flags_cover = MASKCOVERSMOUTH + visor_flags_inv = HIDEFACE + visor_flags_cover = MASKCOVERSMOUTH + gas_transfer_coefficient = 0.9 + permeability_coefficient = 0.01 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 25, "rad" = 0, "fire" = 0, "acid" = 0) + actions_types = list(/datum/action/item_action/adjust) + +/obj/item/clothing/mask/surgical/attack_self(mob/user) + adjustmask(user) + +/obj/item/clothing/mask/fakemoustache + name = "fake moustache" + desc = "Warning: moustache is fake." + icon_state = "fake-moustache" + flags_inv = HIDEFACE + +/obj/item/clothing/mask/fakemoustache/italian + name = "italian moustache" + desc = "Made from authentic Italian moustache hairs. Gives the wearer an irresistable urge to gesticulate wildly." + modifies_speech = TRUE + +/obj/item/clothing/mask/fakemoustache/italian/handle_speech(datum/source, list/speech_args) + var/message = speech_args[SPEECH_MESSAGE] + if(message[1] != "*") + message = " [message]" + var/list/italian_words = strings("italian_replacement.json", "italian") + + for(var/key in italian_words) + var/value = italian_words[key] + if(islist(value)) + value = pick(value) + + message = replacetextEx(message, " [uppertext(key)]", " [uppertext(value)]") + message = replacetextEx(message, " [capitalize(key)]", " [capitalize(value)]") + message = replacetextEx(message, " [key]", " [value]") + + if(prob(3)) + message += pick(" Ravioli, ravioli, give me the formuoli!"," Mamma-mia!"," Mamma-mia! That's a spicy meat-ball!", " La la la la la funiculi funicula!") + speech_args[SPEECH_MESSAGE] = trim(message) + +/obj/item/clothing/mask/joy + name = "joy mask" + desc = "Express your happiness or hide your sorrows with this laughing face with crying tears of joy cutout." + icon_state = "joy" + +/obj/item/clothing/mask/pig + name = "pig mask" + desc = "A rubber pig mask with a built-in voice modulator." + icon_state = "pig" + item_state = "pig" + flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + clothing_flags = VOICEBOX_TOGGLABLE + w_class = WEIGHT_CLASS_SMALL + modifies_speech = TRUE + +/obj/item/clothing/mask/pig/handle_speech(datum/source, list/speech_args) + if(!(clothing_flags & VOICEBOX_DISABLED)) + speech_args[SPEECH_MESSAGE] = pick("Oink!","Squeeeeeeee!","Oink Oink!") + +/obj/item/clothing/mask/pig/cursed + name = "pig face" + desc = "It looks like a mask, but closer inspection reveals it's melded onto this person's face!" + flags_inv = HIDEFACIALHAIR + clothing_flags = NONE + +/obj/item/clothing/mask/pig/cursed/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CURSED_MASK_TRAIT) + playsound(get_turf(src), 'sound/magic/pighead_curse.ogg', 50, TRUE) + +///frog mask - reeee!! +/obj/item/clothing/mask/frog + name = "frog mask" + desc = "An ancient mask carved in the shape of a frog.
                    Sanity is like gravity, all it needs is a push." + icon_state = "frog" + item_state = "frog" + flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + w_class = WEIGHT_CLASS_SMALL + clothing_flags = VOICEBOX_TOGGLABLE + modifies_speech = TRUE + +/obj/item/clothing/mask/frog/handle_speech(datum/source, list/speech_args) //whenever you speak + if(!(clothing_flags & VOICEBOX_DISABLED)) + if(prob(5)) //sometimes, the angry spirit finds others words to speak. + speech_args[SPEECH_MESSAGE] = pick("HUUUUU!!","SMOOOOOKIN'!!","Hello my baby, hello my honey, hello my rag-time gal.", "Feels bad, man.", "GIT DIS GUY OFF ME!!" ,"SOMEBODY STOP ME!!", "NORMIES, GET OUT!!") + else + speech_args[SPEECH_MESSAGE] = pick("Ree!!", "Reee!!","REEE!!","REEEEE!!") //but its usually just angry gibberish, + +/obj/item/clothing/mask/frog/cursed + clothing_flags = NONE + +/obj/item/clothing/mask/frog/cursed/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CURSED_MASK_TRAIT) + +/obj/item/clothing/mask/frog/cursed/equipped(mob/user, slot) + var/mob/living/carbon/C = user + if(C.wear_mask == src && HAS_TRAIT_FROM(src, TRAIT_NODROP, CURSED_MASK_TRAIT)) + to_chat(user, "[src] was cursed! Ree!!") + return ..() + +/obj/item/clothing/mask/cowmask + name = "cow mask" + icon_state = "cowmask" + item_state = "cowmask" + clothing_flags = VOICEBOX_TOGGLABLE + flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + w_class = WEIGHT_CLASS_SMALL + modifies_speech = TRUE + +/obj/item/clothing/mask/cowmask/handle_speech(datum/source, list/speech_args) + if(!(clothing_flags & VOICEBOX_DISABLED)) + speech_args[SPEECH_MESSAGE] = pick("Moooooooo!","Moo!","Moooo!") + +/obj/item/clothing/mask/cowmask/cursed + name = "cow face" + desc = "It looks like a cow mask, but closer inspection reveals it's melded onto this person's face!" + flags_inv = HIDEFACIALHAIR + clothing_flags = NONE + +/obj/item/clothing/mask/cowmask/cursed/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CURSED_MASK_TRAIT) + playsound(get_turf(src), 'sound/magic/cowhead_curse.ogg', 50, TRUE) + +/obj/item/clothing/mask/horsehead + name = "horse head mask" + desc = "A mask made of soft vinyl and latex, representing the head of a horse." + icon_state = "horsehead" + item_state = "horsehead" + flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR|HIDEEYES|HIDEEARS + w_class = WEIGHT_CLASS_SMALL + clothing_flags = VOICEBOX_TOGGLABLE + +/obj/item/clothing/mask/horsehead/handle_speech(datum/source, list/speech_args) + if(!(clothing_flags & VOICEBOX_DISABLED)) + speech_args[SPEECH_MESSAGE] = pick("NEEIIGGGHHHH!", "NEEEIIIIGHH!", "NEIIIGGHH!", "HAAWWWWW!", "HAAAWWW!") + +/obj/item/clothing/mask/horsehead/cursed + name = "horse face" + desc = "It initially looks like a mask, but it's melded into the poor person's face." + clothing_flags = NONE + flags_inv = HIDEFACIALHAIR + +/obj/item/clothing/mask/horsehead/cursed/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CURSED_MASK_TRAIT) + playsound(get_turf(src), 'sound/magic/horsehead_curse.ogg', 50, TRUE) + +/obj/item/clothing/mask/rat + name = "rat mask" + desc = "A mask made of soft vinyl and latex, representing the head of a rat." + icon_state = "rat" + item_state = "rat" + flags_inv = HIDEFACE + flags_cover = MASKCOVERSMOUTH + +/obj/item/clothing/mask/rat/fox + name = "fox mask" + desc = "A mask made of soft vinyl and latex, representing the head of a fox." + icon_state = "fox" + item_state = "fox" + +/obj/item/clothing/mask/rat/bee + name = "bee mask" + desc = "A mask made of soft vinyl and latex, representing the head of a bee." + icon_state = "bee" + item_state = "bee" + +/obj/item/clothing/mask/rat/bear + name = "bear mask" + desc = "A mask made of soft vinyl and latex, representing the head of a bear." + icon_state = "bear" + item_state = "bear" + +/obj/item/clothing/mask/rat/bat + name = "bat mask" + desc = "A mask made of soft vinyl and latex, representing the head of a bat." + icon_state = "bat" + item_state = "bat" + +/obj/item/clothing/mask/rat/raven + name = "raven mask" + desc = "A mask made of soft vinyl and latex, representing the head of a raven." + icon_state = "raven" + item_state = "raven" + +/obj/item/clothing/mask/rat/jackal + name = "jackal mask" + desc = "A mask made of soft vinyl and latex, representing the head of a jackal." + icon_state = "jackal" + item_state = "jackal" + +/obj/item/clothing/mask/rat/tribal + name = "tribal mask" + desc = "A mask carved out of wood, detailed carefully by hand." + icon_state = "bumba" + item_state = "bumba" + +/obj/item/clothing/mask/bandana + name = "botany bandana" + desc = "A fine bandana with nanotech lining and a hydroponics pattern." + w_class = WEIGHT_CLASS_TINY + flags_cover = MASKCOVERSMOUTH + flags_inv = HIDEFACE|HIDEFACIALHAIR + visor_flags_inv = HIDEFACE|HIDEFACIALHAIR + visor_flags_cover = MASKCOVERSMOUTH | PEPPERPROOF + slot_flags = ITEM_SLOT_MASK + adjusted_flags = ITEM_SLOT_HEAD + icon_state = "bandbotany" + +/obj/item/clothing/mask/bandana/attack_self(mob/user) + adjustmask(user) + +/obj/item/clothing/mask/bandana/AltClick(mob/user) + . = ..() + if(iscarbon(user)) + var/mob/living/carbon/C = user + if((C.get_item_by_slot(ITEM_SLOT_HEAD == src)) || (C.get_item_by_slot(ITEM_SLOT_MASK) == src)) + to_chat(user, "You can't tie [src] while wearing it!") + return + if(slot_flags & ITEM_SLOT_HEAD) + to_chat(user, "You must undo [src] before you can tie it into a neckerchief!") + else + if(user.is_holding(src)) + var/obj/item/clothing/neck/neckerchief/nk = new(src) + nk.name = "[name] neckerchief" + nk.desc = "[desc] It's tied up like a neckerchief." + nk.icon_state = icon_state + nk.sourceBandanaType = src.type + var/currentHandIndex = user.get_held_index_of_item(src) + user.transferItemToLoc(src, null) + user.put_in_hand(nk, currentHandIndex) + user.visible_message("You tie [src] up like a neckerchief.", "[user] ties [src] up like a neckerchief.") + qdel(src) + else + to_chat(user, "You must be holding [src] in order to tie it!") + +/obj/item/clothing/mask/bandana/red + name = "red bandana" + desc = "A fine red bandana with nanotech lining." + icon_state = "bandred" + +/obj/item/clothing/mask/bandana/blue + name = "blue bandana" + desc = "A fine blue bandana with nanotech lining." + icon_state = "bandblue" + +/obj/item/clothing/mask/bandana/green + name = "green bandana" + desc = "A fine green bandana with nanotech lining." + icon_state = "bandgreen" + +/obj/item/clothing/mask/bandana/gold + name = "gold bandana" + desc = "A fine gold bandana with nanotech lining." + icon_state = "bandgold" + +/obj/item/clothing/mask/bandana/black + name = "black bandana" + desc = "A fine black bandana with nanotech lining." + icon_state = "bandblack" + +/obj/item/clothing/mask/bandana/skull + name = "skull bandana" + desc = "A fine black bandana with nanotech lining and a skull emblem." + icon_state = "bandskull" + +/obj/item/clothing/mask/bandana/durathread + name = "durathread bandana" + desc = "A bandana made from durathread, you wish it would provide some protection to its wearer, but it's far too thin..." + icon_state = "banddurathread" + +/obj/item/clothing/mask/mummy + name = "mummy mask" + desc = "Ancient bandages." + icon_state = "mummy_mask" + item_state = "mummy_mask" + flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + +/obj/item/clothing/mask/scarecrow + name = "sack mask" + desc = "A burlap sack with eyeholes." + icon_state = "scarecrow_sack" + item_state = "scarecrow_sack" + flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + +/obj/item/clothing/mask/gondola + name = "gondola mask" + desc = "Genuine gondola fur." + icon_state = "gondola" + item_state = "gondola" + flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + w_class = WEIGHT_CLASS_SMALL + modifies_speech = TRUE + +/obj/item/clothing/mask/gondola/handle_speech(datum/source, list/speech_args) + var/message = speech_args[SPEECH_MESSAGE] + if(message[1] != "*") + message = " [message]" + var/list/spurdo_words = strings("spurdo_replacement.json", "spurdo") + for(var/key in spurdo_words) + var/value = spurdo_words[key] + if(islist(value)) + value = pick(value) + message = replacetextEx(message,regex(uppertext(key),"g"), "[uppertext(value)]") + message = replacetextEx(message,regex(capitalize(key),"g"), "[capitalize(value)]") + message = replacetextEx(message,regex(key,"g"), "[value]") + speech_args[SPEECH_MESSAGE] = trim(message) diff --git a/code/modules/clothing/shoes/_shoes.dm b/code/modules/clothing/shoes/_shoes.dm index dcbca200c098..970c14eb32a3 100644 --- a/code/modules/clothing/shoes/_shoes.dm +++ b/code/modules/clothing/shoes/_shoes.dm @@ -1,275 +1,275 @@ -/obj/item/clothing/shoes - name = "shoes" - icon = 'icons/obj/clothing/shoes.dmi' - desc = "Comfortable-looking shoes." - gender = PLURAL //Carn: for grammarically correct text-parsing - var/chained = 0 - - body_parts_covered = FEET - slot_flags = ITEM_SLOT_FEET - - permeability_coefficient = 0.5 - slowdown = SHOES_SLOWDOWN - strip_delay = 1 SECONDS - var/blood_state = BLOOD_STATE_NOT_BLOODY - var/list/bloody_shoes = list(BLOOD_STATE_HUMAN = 0,BLOOD_STATE_XENO = 0, BLOOD_STATE_OIL = 0, BLOOD_STATE_NOT_BLOODY = 0) - var/offset = 0 - var/equipped_before_drop = FALSE - var/can_be_bloody = TRUE - ///Whether these shoes have laces that can be tied/untied - var/can_be_tied = TRUE - ///Are we currently tied? Can either be SHOES_UNTIED, SHOES_TIED, or SHOES_KNOTTED - var/tied = SHOES_TIED - ///How long it takes to lace/unlace these shoes - var/lace_time = 5 SECONDS - ///any alerts we have active - var/obj/screen/alert/our_alert - -/obj/item/clothing/shoes/ComponentInitialize() - . = ..() - RegisterSignal(src, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_blood) - -/obj/item/clothing/shoes/suicide_act(mob/living/carbon/user) - if(rand(2)>1) - user.visible_message("[user] begins tying \the [src] up waaay too tightly! It looks like [user.p_theyre()] trying to commit suicide!") - var/obj/item/bodypart/l_leg = user.get_bodypart(BODY_ZONE_L_LEG) - var/obj/item/bodypart/r_leg = user.get_bodypart(BODY_ZONE_R_LEG) - if(l_leg) - l_leg.dismember() - if(r_leg) - r_leg.dismember() - playsound(user, "desceration", 50, TRUE, -1) - return BRUTELOSS - else//didnt realize this suicide act existed (was in miscellaneous.dm) and didnt want to remove it, so made it a 50/50 chance. Why not! - user.visible_message("[user] is bashing [user.p_their()] own head in with [src]! Ain't that a kick in the head?") - for(var/i = 0, i < 3, i++) - sleep(3) - playsound(user, 'sound/weapons/genhit2.ogg', 50, TRUE) - return(BRUTELOSS) - -/obj/item/clothing/shoes/worn_overlays(isinhands = FALSE) - . = list() - if(!isinhands) - var/bloody = FALSE - if(HAS_BLOOD_DNA(src)) - bloody = TRUE - else - bloody = bloody_shoes[BLOOD_STATE_HUMAN] - - if(damaged_clothes) - . += mutable_appearance('icons/effects/item_damage.dmi', "damagedshoe") - if(bloody) - . += mutable_appearance('icons/effects/blood.dmi', "shoeblood") - -/obj/item/clothing/shoes/examine(mob/user) - . = ..() - - if(!ishuman(loc)) - return ..() - if(tied == SHOES_UNTIED) - . += "The shoelaces are untied." - else if(tied == SHOES_KNOTTED) - . += "The shoelaces are all knotted together." - -/obj/item/clothing/shoes/equipped(mob/user, slot) - . = ..() - if(offset && (slot_flags & slot)) - user.pixel_y += offset - worn_y_dimension -= (offset * 2) - user.update_inv_shoes() - equipped_before_drop = TRUE - if(can_be_tied && tied == SHOES_UNTIED) - our_alert = user.throw_alert("shoealert", /obj/screen/alert/shoes/untied) - RegisterSignal(src, COMSIG_SHOES_STEP_ACTION, .proc/check_trip, override=TRUE) - -/obj/item/clothing/shoes/proc/restore_offsets(mob/user) - equipped_before_drop = FALSE - user.pixel_y -= offset - worn_y_dimension = world.icon_size - -/obj/item/clothing/shoes/dropped(mob/user) - if(our_alert && our_alert.owner == user) - user.clear_alert("shoealert") - if(offset && equipped_before_drop) - restore_offsets(user) - . = ..() - -/obj/item/clothing/shoes/update_clothes_damaged_state(damaging = TRUE) - ..() - if(ismob(loc)) - var/mob/M = loc - M.update_inv_shoes() - -/obj/item/clothing/shoes/proc/clean_blood(datum/source, strength) - if(strength < CLEAN_STRENGTH_BLOOD) - return - bloody_shoes = list(BLOOD_STATE_HUMAN = 0,BLOOD_STATE_XENO = 0, BLOOD_STATE_OIL = 0, BLOOD_STATE_NOT_BLOODY = 0) - blood_state = BLOOD_STATE_NOT_BLOODY - if(ismob(loc)) - var/mob/M = loc - M.update_inv_shoes() - -/obj/item/proc/negates_gravity() - return FALSE - -/** - * adjust_laces adjusts whether our shoes (assuming they can_be_tied) and tied, untied, or knotted - * - * In addition to setting the state, it will deal with getting rid of alerts if they exist, as well as registering and unregistering the stepping signals - * - * Arguments: - * * - * * state: SHOES_UNTIED, SHOES_TIED, or SHOES_KNOTTED, depending on what you want them to become - * * user: used to check to see if we're the ones unknotting our own laces - */ -/obj/item/clothing/shoes/proc/adjust_laces(state, mob/user) - if(!can_be_tied) - return - - var/mob/living/carbon/human/our_guy - if(ishuman(loc)) - our_guy = loc - - tied = state - if(tied == SHOES_TIED) - if(our_guy) - our_guy.clear_alert("shoealert") - UnregisterSignal(src, COMSIG_SHOES_STEP_ACTION) - else - if(tied == SHOES_UNTIED && our_guy && user == our_guy) - our_alert = our_guy.throw_alert("shoealert", /obj/screen/alert/shoes/untied) // if we're the ones unknotting our own laces, of course we know they're untied - RegisterSignal(src, COMSIG_SHOES_STEP_ACTION, .proc/check_trip, override=TRUE) - -/** - * handle_tying deals with all the actual tying/untying/knotting, inferring your intent from who you are in relation to the state of the laces - * - * If you're the wearer, you want them to move towards tied-ness (knotted -> untied -> tied). If you're not, you're pranking them, so you're moving towards knotted-ness (tied -> untied -> knotted) - * - * Arguments: - * * - * * user: who is the person interacting with the shoes? - */ -/obj/item/clothing/shoes/proc/handle_tying(mob/user) - ///our_guy here is the wearer, if one exists (and he must exist, or we don't care) - var/mob/living/carbon/human/our_guy = loc - if(!istype(our_guy)) - return - - if(!in_range(user, our_guy)) - to_chat(user, "You aren't close enough to interact with [src]'s laces!") - return - - if(user == loc && tied != SHOES_TIED) // if they're our own shoes, go tie-wards - if(INTERACTING_WITH(user, our_guy)) - to_chat(user, "You're already interacting with [src]!") - return - user.visible_message("[user] begins [tied ? "unknotting" : "tying"] the laces of [user.p_their()] [src.name].", "You begin [tied ? "unknotting" : "tying"] the laces of your [src.name]...") - - if(do_after(user, lace_time, needhand=TRUE, target=our_guy, extra_checks=CALLBACK(src, .proc/still_shoed, our_guy))) - to_chat(user, "You [tied ? "unknot" : "tie"] the laces of your [src.name].") - if(tied == SHOES_UNTIED) - adjust_laces(SHOES_TIED, user) - else - adjust_laces(SHOES_UNTIED, user) - - else // if they're someone else's shoes, go knot-wards - var/mob/living/L = user - if(istype(L) && (L.mobility_flags & MOBILITY_STAND)) - to_chat(user, "You must be on the floor to interact with [src]!") - return - if(tied == SHOES_KNOTTED) - to_chat(user, "The laces on [loc]'s [src.name] are already a hopelessly tangled mess!") - return - if(INTERACTING_WITH(user, our_guy)) - to_chat(user, "You're already interacting with [src]!") - return - - var/mod_time = lace_time - to_chat(user, "You quietly set to work [tied ? "untying" : "knotting"] [loc]'s [src.name]...") - if(HAS_TRAIT(user, TRAIT_CLUMSY)) // based clowns trained their whole lives for this - mod_time *= 0.75 - - if(do_after(user, mod_time, needhand=TRUE, target=our_guy, extra_checks=CALLBACK(src, .proc/still_shoed, our_guy))) - to_chat(user, "You [tied ? "untie" : "knot"] the laces on [loc]'s [src.name].") - if(tied == SHOES_UNTIED) - adjust_laces(SHOES_KNOTTED, user) - else - adjust_laces(SHOES_UNTIED, user) - else // if one of us moved - user.visible_message("[our_guy] stamps on [user]'s hand, mid-shoelace [tied ? "knotting" : "untying"]!", "Ow! [our_guy] stamps on your hand!", list(our_guy)) - to_chat(our_guy, "You stamp on [user]'s hand! What the- [user.p_they()] [user.p_were()] [tied ? "knotting" : "untying"] your shoelaces!") - user.emote("scream") - if(istype(L)) - var/obj/item/bodypart/ouchie = L.get_bodypart(pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)) - if(ouchie) - ouchie.receive_damage(brute = 10, stamina = 40) - L.Paralyze(10) - -///checking to make sure we're still on the person we're supposed to be, for lacing do_after's -/obj/item/clothing/shoes/proc/still_shoed(mob/living/carbon/our_guy) - return (loc == our_guy) - -///check_trip runs on each step to see if we fall over as a result of our lace status. Knotted laces are a guaranteed trip, while untied shoes are just a chance to stumble -/obj/item/clothing/shoes/proc/check_trip() - var/mob/living/carbon/human/our_guy = loc - if(!istype(our_guy)) // are they REALLY /our guy/? - return - - if(tied == SHOES_KNOTTED) - our_guy.Paralyze(5) - our_guy.Knockdown(10) - our_guy.visible_message("[our_guy] trips on [our_guy.p_their()] knotted shoelaces and falls! What a klutz!", "You trip on your knotted shoelaces and fall over!") - SEND_SIGNAL(our_guy, COMSIG_ADD_MOOD_EVENT, "trip", /datum/mood_event/tripped) // well we realized they're knotted now! - our_alert = our_guy.throw_alert("shoealert", /obj/screen/alert/shoes/knotted) - else if(tied == SHOES_UNTIED) - var/wiser = TRUE // did we stumble and realize our laces are undone? - switch(rand(1, 1000)) - if(1) // .1% chance to trip and fall over (note these are per step while our laces are undone) - our_guy.Paralyze(5) - our_guy.Knockdown(10) - SEND_SIGNAL(our_guy, COMSIG_ADD_MOOD_EVENT, "trip", /datum/mood_event/tripped) // well we realized they're knotted now! - our_guy.visible_message("[our_guy] trips on [our_guy.p_their()] untied shoelaces and falls! What a klutz!", "You trip on your untied shoelaces and fall over!") - - if(2 to 5) // .4% chance to stumble and lurch forward - our_guy.throw_at(get_step(our_guy, our_guy.dir), 3, 2) - to_chat(our_guy, "You stumble on your untied shoelaces and lurch forward!") - - if(6 to 13) // .7% chance to stumble and fling what we're holding - var/have_anything = FALSE - for(var/obj/item/I in our_guy.held_items) - have_anything = TRUE - our_guy.accident(I) - to_chat(our_guy, "You trip on your shoelaces a bit[have_anything ? ", flinging what you were holding" : ""]!") - if(14 to 25) // 1.3ish% chance to stumble and be a bit off balance (like being disarmed) - to_chat(our_guy, "You stumble a bit on your untied shoelaces!") - if(!our_guy.has_movespeed_modifier(/datum/movespeed_modifier/shove)) - our_guy.add_movespeed_modifier(/datum/movespeed_modifier/shove) - addtimer(CALLBACK(our_guy, /mob/living/carbon/human/proc/clear_shove_slowdown), SHOVE_SLOWDOWN_LENGTH) - - if(26 to 1000) - wiser = FALSE - if(wiser) - SEND_SIGNAL(our_guy, COMSIG_ADD_MOOD_EVENT, "untied", /datum/mood_event/untied) // well we realized they're untied now! - our_alert = our_guy.throw_alert("shoealert", /obj/screen/alert/shoes/untied) - - -/obj/item/clothing/shoes/attack_hand(mob/living/carbon/human/user) - if(!istype(user)) - return ..() - if(loc == user && tied != SHOES_TIED && (user.mobility_flags & MOBILITY_USE)) - handle_tying(user) - return - ..() - -/obj/item/clothing/shoes/attack_self(mob/user) - . = ..() - - if(INTERACTING_WITH(user, src)) - to_chat(user, "You're already interacting with [src]!") - return - - to_chat(user, "You begin [tied ? "untying" : "tying"] the laces on [src]...") - - if(do_after(user, lace_time, needhand=TRUE, target=src,extra_checks=CALLBACK(src, .proc/still_shoed, user))) - to_chat(user, "You [tied ? "untie" : "tie"] the laces on [src].") - adjust_laces(tied ? SHOES_TIED : SHOES_UNTIED, user) +/obj/item/clothing/shoes + name = "shoes" + icon = 'icons/obj/clothing/shoes.dmi' + desc = "Comfortable-looking shoes." + gender = PLURAL //Carn: for grammarically correct text-parsing + var/chained = 0 + + body_parts_covered = FEET + slot_flags = ITEM_SLOT_FEET + + permeability_coefficient = 0.5 + slowdown = SHOES_SLOWDOWN + strip_delay = 1 SECONDS + var/blood_state = BLOOD_STATE_NOT_BLOODY + var/list/bloody_shoes = list(BLOOD_STATE_HUMAN = 0,BLOOD_STATE_XENO = 0, BLOOD_STATE_OIL = 0, BLOOD_STATE_NOT_BLOODY = 0) + var/offset = 0 + var/equipped_before_drop = FALSE + var/can_be_bloody = TRUE + ///Whether these shoes have laces that can be tied/untied + var/can_be_tied = TRUE + ///Are we currently tied? Can either be SHOES_UNTIED, SHOES_TIED, or SHOES_KNOTTED + var/tied = SHOES_TIED + ///How long it takes to lace/unlace these shoes + var/lace_time = 5 SECONDS + ///any alerts we have active + var/obj/screen/alert/our_alert + +/obj/item/clothing/shoes/ComponentInitialize() + . = ..() + RegisterSignal(src, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_blood) + +/obj/item/clothing/shoes/suicide_act(mob/living/carbon/user) + if(rand(2)>1) + user.visible_message("[user] begins tying \the [src] up waaay too tightly! It looks like [user.p_theyre()] trying to commit suicide!") + var/obj/item/bodypart/l_leg = user.get_bodypart(BODY_ZONE_L_LEG) + var/obj/item/bodypart/r_leg = user.get_bodypart(BODY_ZONE_R_LEG) + if(l_leg) + l_leg.dismember() + if(r_leg) + r_leg.dismember() + playsound(user, "desceration", 50, TRUE, -1) + return BRUTELOSS + else//didnt realize this suicide act existed (was in miscellaneous.dm) and didnt want to remove it, so made it a 50/50 chance. Why not! + user.visible_message("[user] is bashing [user.p_their()] own head in with [src]! Ain't that a kick in the head?") + for(var/i = 0, i < 3, i++) + sleep(3) + playsound(user, 'sound/weapons/genhit2.ogg', 50, TRUE) + return(BRUTELOSS) + +/obj/item/clothing/shoes/worn_overlays(isinhands = FALSE) + . = list() + if(!isinhands) + var/bloody = FALSE + if(HAS_BLOOD_DNA(src)) + bloody = TRUE + else + bloody = bloody_shoes[BLOOD_STATE_HUMAN] + + if(damaged_clothes) + . += mutable_appearance('icons/effects/item_damage.dmi', "damagedshoe") + if(bloody) + . += mutable_appearance('icons/effects/blood.dmi', "shoeblood") + +/obj/item/clothing/shoes/examine(mob/user) + . = ..() + + if(!ishuman(loc)) + return ..() + if(tied == SHOES_UNTIED) + . += "The shoelaces are untied." + else if(tied == SHOES_KNOTTED) + . += "The shoelaces are all knotted together." + +/obj/item/clothing/shoes/equipped(mob/user, slot) + . = ..() + if(offset && (slot_flags & slot)) + user.pixel_y += offset + worn_y_dimension -= (offset * 2) + user.update_inv_shoes() + equipped_before_drop = TRUE + if(can_be_tied && tied == SHOES_UNTIED) + our_alert = user.throw_alert("shoealert", /obj/screen/alert/shoes/untied) + RegisterSignal(src, COMSIG_SHOES_STEP_ACTION, .proc/check_trip, override=TRUE) + +/obj/item/clothing/shoes/proc/restore_offsets(mob/user) + equipped_before_drop = FALSE + user.pixel_y -= offset + worn_y_dimension = world.icon_size + +/obj/item/clothing/shoes/dropped(mob/user) + if(our_alert && our_alert.owner == user) + user.clear_alert("shoealert") + if(offset && equipped_before_drop) + restore_offsets(user) + . = ..() + +/obj/item/clothing/shoes/update_clothes_damaged_state(damaging = TRUE) + ..() + if(ismob(loc)) + var/mob/M = loc + M.update_inv_shoes() + +/obj/item/clothing/shoes/proc/clean_blood(datum/source, strength) + if(strength < CLEAN_STRENGTH_BLOOD) + return + bloody_shoes = list(BLOOD_STATE_HUMAN = 0,BLOOD_STATE_XENO = 0, BLOOD_STATE_OIL = 0, BLOOD_STATE_NOT_BLOODY = 0) + blood_state = BLOOD_STATE_NOT_BLOODY + if(ismob(loc)) + var/mob/M = loc + M.update_inv_shoes() + +/obj/item/proc/negates_gravity() + return FALSE + +/** + * adjust_laces adjusts whether our shoes (assuming they can_be_tied) and tied, untied, or knotted + * + * In addition to setting the state, it will deal with getting rid of alerts if they exist, as well as registering and unregistering the stepping signals + * + * Arguments: + * * + * * state: SHOES_UNTIED, SHOES_TIED, or SHOES_KNOTTED, depending on what you want them to become + * * user: used to check to see if we're the ones unknotting our own laces + */ +/obj/item/clothing/shoes/proc/adjust_laces(state, mob/user) + if(!can_be_tied) + return + + var/mob/living/carbon/human/our_guy + if(ishuman(loc)) + our_guy = loc + + tied = state + if(tied == SHOES_TIED) + if(our_guy) + our_guy.clear_alert("shoealert") + UnregisterSignal(src, COMSIG_SHOES_STEP_ACTION) + else + if(tied == SHOES_UNTIED && our_guy && user == our_guy) + our_alert = our_guy.throw_alert("shoealert", /obj/screen/alert/shoes/untied) // if we're the ones unknotting our own laces, of course we know they're untied + RegisterSignal(src, COMSIG_SHOES_STEP_ACTION, .proc/check_trip, override=TRUE) + +/** + * handle_tying deals with all the actual tying/untying/knotting, inferring your intent from who you are in relation to the state of the laces + * + * If you're the wearer, you want them to move towards tied-ness (knotted -> untied -> tied). If you're not, you're pranking them, so you're moving towards knotted-ness (tied -> untied -> knotted) + * + * Arguments: + * * + * * user: who is the person interacting with the shoes? + */ +/obj/item/clothing/shoes/proc/handle_tying(mob/user) + ///our_guy here is the wearer, if one exists (and he must exist, or we don't care) + var/mob/living/carbon/human/our_guy = loc + if(!istype(our_guy)) + return + + if(!in_range(user, our_guy)) + to_chat(user, "You aren't close enough to interact with [src]'s laces!") + return + + if(user == loc && tied != SHOES_TIED) // if they're our own shoes, go tie-wards + if(INTERACTING_WITH(user, our_guy)) + to_chat(user, "You're already interacting with [src]!") + return + user.visible_message("[user] begins [tied ? "unknotting" : "tying"] the laces of [user.p_their()] [src.name].", "You begin [tied ? "unknotting" : "tying"] the laces of your [src.name]...") + + if(do_after(user, lace_time, needhand=TRUE, target=our_guy, extra_checks=CALLBACK(src, .proc/still_shoed, our_guy))) + to_chat(user, "You [tied ? "unknot" : "tie"] the laces of your [src.name].") + if(tied == SHOES_UNTIED) + adjust_laces(SHOES_TIED, user) + else + adjust_laces(SHOES_UNTIED, user) + + else // if they're someone else's shoes, go knot-wards + var/mob/living/L = user + if(istype(L) && (L.mobility_flags & MOBILITY_STAND)) + to_chat(user, "You must be on the floor to interact with [src]!") + return + if(tied == SHOES_KNOTTED) + to_chat(user, "The laces on [loc]'s [src.name] are already a hopelessly tangled mess!") + return + if(INTERACTING_WITH(user, our_guy)) + to_chat(user, "You're already interacting with [src]!") + return + + var/mod_time = lace_time + to_chat(user, "You quietly set to work [tied ? "untying" : "knotting"] [loc]'s [src.name]...") + if(HAS_TRAIT(user, TRAIT_CLUMSY)) // based clowns trained their whole lives for this + mod_time *= 0.75 + + if(do_after(user, mod_time, needhand=TRUE, target=our_guy, extra_checks=CALLBACK(src, .proc/still_shoed, our_guy))) + to_chat(user, "You [tied ? "untie" : "knot"] the laces on [loc]'s [src.name].") + if(tied == SHOES_UNTIED) + adjust_laces(SHOES_KNOTTED, user) + else + adjust_laces(SHOES_UNTIED, user) + else // if one of us moved + user.visible_message("[our_guy] stamps on [user]'s hand, mid-shoelace [tied ? "knotting" : "untying"]!", "Ow! [our_guy] stamps on your hand!", list(our_guy)) + to_chat(our_guy, "You stamp on [user]'s hand! What the- [user.p_they()] [user.p_were()] [tied ? "knotting" : "untying"] your shoelaces!") + user.emote("scream") + if(istype(L)) + var/obj/item/bodypart/ouchie = L.get_bodypart(pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)) + if(ouchie) + ouchie.receive_damage(brute = 10, stamina = 40) + L.Paralyze(10) + +///checking to make sure we're still on the person we're supposed to be, for lacing do_after's +/obj/item/clothing/shoes/proc/still_shoed(mob/living/carbon/our_guy) + return (loc == our_guy) + +///check_trip runs on each step to see if we fall over as a result of our lace status. Knotted laces are a guaranteed trip, while untied shoes are just a chance to stumble +/obj/item/clothing/shoes/proc/check_trip() + var/mob/living/carbon/human/our_guy = loc + if(!istype(our_guy)) // are they REALLY /our guy/? + return + + if(tied == SHOES_KNOTTED) + our_guy.Paralyze(5) + our_guy.Knockdown(10) + our_guy.visible_message("[our_guy] trips on [our_guy.p_their()] knotted shoelaces and falls! What a klutz!", "You trip on your knotted shoelaces and fall over!") + SEND_SIGNAL(our_guy, COMSIG_ADD_MOOD_EVENT, "trip", /datum/mood_event/tripped) // well we realized they're knotted now! + our_alert = our_guy.throw_alert("shoealert", /obj/screen/alert/shoes/knotted) + else if(tied == SHOES_UNTIED) + var/wiser = TRUE // did we stumble and realize our laces are undone? + switch(rand(1, 1000)) + if(1) // .1% chance to trip and fall over (note these are per step while our laces are undone) + our_guy.Paralyze(5) + our_guy.Knockdown(10) + SEND_SIGNAL(our_guy, COMSIG_ADD_MOOD_EVENT, "trip", /datum/mood_event/tripped) // well we realized they're knotted now! + our_guy.visible_message("[our_guy] trips on [our_guy.p_their()] untied shoelaces and falls! What a klutz!", "You trip on your untied shoelaces and fall over!") + + if(2 to 5) // .4% chance to stumble and lurch forward + our_guy.throw_at(get_step(our_guy, our_guy.dir), 3, 2) + to_chat(our_guy, "You stumble on your untied shoelaces and lurch forward!") + + if(6 to 13) // .7% chance to stumble and fling what we're holding + var/have_anything = FALSE + for(var/obj/item/I in our_guy.held_items) + have_anything = TRUE + our_guy.accident(I) + to_chat(our_guy, "You trip on your shoelaces a bit[have_anything ? ", flinging what you were holding" : ""]!") + if(14 to 25) // 1.3ish% chance to stumble and be a bit off balance (like being disarmed) + to_chat(our_guy, "You stumble a bit on your untied shoelaces!") + if(!our_guy.has_movespeed_modifier(/datum/movespeed_modifier/shove)) + our_guy.add_movespeed_modifier(/datum/movespeed_modifier/shove) + addtimer(CALLBACK(our_guy, /mob/living/carbon/human/proc/clear_shove_slowdown), SHOVE_SLOWDOWN_LENGTH) + + if(26 to 1000) + wiser = FALSE + if(wiser) + SEND_SIGNAL(our_guy, COMSIG_ADD_MOOD_EVENT, "untied", /datum/mood_event/untied) // well we realized they're untied now! + our_alert = our_guy.throw_alert("shoealert", /obj/screen/alert/shoes/untied) + + +/obj/item/clothing/shoes/attack_hand(mob/living/carbon/human/user) + if(!istype(user)) + return ..() + if(loc == user && tied != SHOES_TIED && (user.mobility_flags & MOBILITY_USE)) + handle_tying(user) + return + ..() + +/obj/item/clothing/shoes/attack_self(mob/user) + . = ..() + + if(INTERACTING_WITH(user, src)) + to_chat(user, "You're already interacting with [src]!") + return + + to_chat(user, "You begin [tied ? "untying" : "tying"] the laces on [src]...") + + if(do_after(user, lace_time, needhand=TRUE, target=src,extra_checks=CALLBACK(src, .proc/still_shoed, user))) + to_chat(user, "You [tied ? "untie" : "tie"] the laces on [src].") + adjust_laces(tied ? SHOES_TIED : SHOES_UNTIED, user) diff --git a/code/modules/clothing/shoes/colour.dm b/code/modules/clothing/shoes/colour.dm index 3a610bc68f3f..40092bf7d109 100644 --- a/code/modules/clothing/shoes/colour.dm +++ b/code/modules/clothing/shoes/colour.dm @@ -1,89 +1,89 @@ -/obj/item/clothing/shoes/sneakers - dying_key = DYE_REGISTRY_SNEAKERS - -/obj/item/clothing/shoes/sneakers/black - name = "black shoes" - icon_state = "black" - desc = "A pair of black shoes." - custom_price = 50 - - cold_protection = FEET - min_cold_protection_temperature = SHOES_MIN_TEMP_PROTECT - heat_protection = FEET - max_heat_protection_temperature = SHOES_MAX_TEMP_PROTECT - -/obj/item/clothing/shoes/sneakers/brown - name = "brown shoes" - desc = "A pair of brown shoes." - icon_state = "brown" - -/obj/item/clothing/shoes/sneakers/blue - name = "blue shoes" - icon_state = "blue" - -/obj/item/clothing/shoes/sneakers/green - name = "green shoes" - icon_state = "green" - -/obj/item/clothing/shoes/sneakers/yellow - name = "yellow shoes" - icon_state = "yellow" - -/obj/item/clothing/shoes/sneakers/purple - name = "purple shoes" - icon_state = "purple" - -/obj/item/clothing/shoes/sneakers/red - name = "red shoes" - desc = "Stylish red shoes." - icon_state = "red" - -/obj/item/clothing/shoes/sneakers/white - name = "white shoes" - icon_state = "white" - permeability_coefficient = 0.01 - -/obj/item/clothing/shoes/sneakers/rainbow - name = "rainbow shoes" - desc = "Very gay shoes." - icon_state = "rain_bow" - -/obj/item/clothing/shoes/sneakers/orange - name = "orange shoes" - icon_state = "orange" - -/obj/item/clothing/shoes/sneakers/orange/attack_self(mob/user) - if (src.chained) - src.chained = null - src.slowdown = SHOES_SLOWDOWN - new /obj/item/restraints/handcuffs( user.loc ) - src.icon_state = "orange" - return - -/obj/item/clothing/shoes/sneakers/orange/attackby(obj/H, loc, params) - ..() - // Note: not using istype here because we want to ignore all subtypes - if (H.type == /obj/item/restraints/handcuffs && !chained) - qdel(H) - src.chained = 1 - src.slowdown = 15 - src.icon_state = "orange1" - return - -/obj/item/clothing/shoes/sneakers/orange/allow_attack_hand_drop(mob/user) - if(ishuman(user)) - var/mob/living/carbon/human/C = user - if(C.shoes == src && chained == 1) - to_chat(user, "You need help taking these off!") - return FALSE - return ..() - -/obj/item/clothing/shoes/sneakers/orange/MouseDrop(atom/over) - var/mob/m = usr - if(ishuman(m)) - var/mob/living/carbon/human/c = m - if(c.shoes == src && chained == 1) - to_chat(c, "You need help taking these off!") - return - return ..() - +/obj/item/clothing/shoes/sneakers + dying_key = DYE_REGISTRY_SNEAKERS + +/obj/item/clothing/shoes/sneakers/black + name = "black shoes" + icon_state = "black" + desc = "A pair of black shoes." + custom_price = 50 + + cold_protection = FEET + min_cold_protection_temperature = SHOES_MIN_TEMP_PROTECT + heat_protection = FEET + max_heat_protection_temperature = SHOES_MAX_TEMP_PROTECT + +/obj/item/clothing/shoes/sneakers/brown + name = "brown shoes" + desc = "A pair of brown shoes." + icon_state = "brown" + +/obj/item/clothing/shoes/sneakers/blue + name = "blue shoes" + icon_state = "blue" + +/obj/item/clothing/shoes/sneakers/green + name = "green shoes" + icon_state = "green" + +/obj/item/clothing/shoes/sneakers/yellow + name = "yellow shoes" + icon_state = "yellow" + +/obj/item/clothing/shoes/sneakers/purple + name = "purple shoes" + icon_state = "purple" + +/obj/item/clothing/shoes/sneakers/red + name = "red shoes" + desc = "Stylish red shoes." + icon_state = "red" + +/obj/item/clothing/shoes/sneakers/white + name = "white shoes" + icon_state = "white" + permeability_coefficient = 0.01 + +/obj/item/clothing/shoes/sneakers/rainbow + name = "rainbow shoes" + desc = "Very gay shoes." + icon_state = "rain_bow" + +/obj/item/clothing/shoes/sneakers/orange + name = "orange shoes" + icon_state = "orange" + +/obj/item/clothing/shoes/sneakers/orange/attack_self(mob/user) + if (src.chained) + src.chained = null + src.slowdown = SHOES_SLOWDOWN + new /obj/item/restraints/handcuffs( user.loc ) + src.icon_state = "orange" + return + +/obj/item/clothing/shoes/sneakers/orange/attackby(obj/H, loc, params) + ..() + // Note: not using istype here because we want to ignore all subtypes + if (H.type == /obj/item/restraints/handcuffs && !chained) + qdel(H) + src.chained = 1 + src.slowdown = 15 + src.icon_state = "orange1" + return + +/obj/item/clothing/shoes/sneakers/orange/allow_attack_hand_drop(mob/user) + if(ishuman(user)) + var/mob/living/carbon/human/C = user + if(C.shoes == src && chained == 1) + to_chat(user, "You need help taking these off!") + return FALSE + return ..() + +/obj/item/clothing/shoes/sneakers/orange/MouseDrop(atom/over) + var/mob/m = usr + if(ishuman(m)) + var/mob/living/carbon/human/c = m + if(c.shoes == src && chained == 1) + to_chat(c, "You need help taking these off!") + return + return ..() + diff --git a/code/modules/clothing/shoes/magboots.dm b/code/modules/clothing/shoes/magboots.dm index 70dde1e2177d..952dbe0ad806 100644 --- a/code/modules/clothing/shoes/magboots.dm +++ b/code/modules/clothing/shoes/magboots.dm @@ -1,59 +1,59 @@ -/obj/item/clothing/shoes/magboots - desc = "Magnetic boots, often used during extravehicular activity to ensure the user remains safely attached to the vehicle." - name = "magboots" - icon_state = "magboots0" - var/magboot_state = "magboots" - var/magpulse = 0 - var/slowdown_active = 2 - permeability_coefficient = 0.05 - actions_types = list(/datum/action/item_action/toggle) - strip_delay = 70 - equip_delay_other = 70 - resistance_flags = FIRE_PROOF - -/obj/item/clothing/shoes/magboots/verb/toggle() - set name = "Toggle Magboots" - set category = "Object" - set src in usr - if(!can_use(usr)) - return - attack_self(usr) - - -/obj/item/clothing/shoes/magboots/attack_self(mob/user) - if(magpulse) - clothing_flags &= ~NOSLIP - slowdown = SHOES_SLOWDOWN - else - clothing_flags |= NOSLIP - slowdown = slowdown_active - magpulse = !magpulse - icon_state = "[magboot_state][magpulse]" - to_chat(user, "You [magpulse ? "enable" : "disable"] the mag-pulse traction system.") - user.update_inv_shoes() //so our mob-overlays update - user.update_gravity(user.has_gravity()) - for(var/X in actions) - var/datum/action/A = X - A.UpdateButtonIcon() - -/obj/item/clothing/shoes/magboots/negates_gravity() - return clothing_flags & NOSLIP - -/obj/item/clothing/shoes/magboots/examine(mob/user) - . = ..() - . += "Its mag-pulse traction system appears to be [magpulse ? "enabled" : "disabled"]." - - -/obj/item/clothing/shoes/magboots/advance - desc = "Advanced magnetic boots that have a lighter magnetic pull, placing less burden on the wearer." - name = "advanced magboots" - icon_state = "advmag0" - magboot_state = "advmag" - slowdown_active = SHOES_SLOWDOWN - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF - -/obj/item/clothing/shoes/magboots/syndie - desc = "Reverse-engineered magnetic boots that have a heavy magnetic pull. Property of Gorlex Marauders." - name = "blood-red magboots" - icon_state = "syndiemag0" - magboot_state = "syndiemag" +/obj/item/clothing/shoes/magboots + desc = "Magnetic boots, often used during extravehicular activity to ensure the user remains safely attached to the vehicle." + name = "magboots" + icon_state = "magboots0" + var/magboot_state = "magboots" + var/magpulse = 0 + var/slowdown_active = 2 + permeability_coefficient = 0.05 + actions_types = list(/datum/action/item_action/toggle) + strip_delay = 70 + equip_delay_other = 70 + resistance_flags = FIRE_PROOF + +/obj/item/clothing/shoes/magboots/verb/toggle() + set name = "Toggle Magboots" + set category = "Object" + set src in usr + if(!can_use(usr)) + return + attack_self(usr) + + +/obj/item/clothing/shoes/magboots/attack_self(mob/user) + if(magpulse) + clothing_flags &= ~NOSLIP + slowdown = SHOES_SLOWDOWN + else + clothing_flags |= NOSLIP + slowdown = slowdown_active + magpulse = !magpulse + icon_state = "[magboot_state][magpulse]" + to_chat(user, "You [magpulse ? "enable" : "disable"] the mag-pulse traction system.") + user.update_inv_shoes() //so our mob-overlays update + user.update_gravity(user.has_gravity()) + for(var/X in actions) + var/datum/action/A = X + A.UpdateButtonIcon() + +/obj/item/clothing/shoes/magboots/negates_gravity() + return clothing_flags & NOSLIP + +/obj/item/clothing/shoes/magboots/examine(mob/user) + . = ..() + . += "Its mag-pulse traction system appears to be [magpulse ? "enabled" : "disabled"]." + + +/obj/item/clothing/shoes/magboots/advance + desc = "Advanced magnetic boots that have a lighter magnetic pull, placing less burden on the wearer." + name = "advanced magboots" + icon_state = "advmag0" + magboot_state = "advmag" + slowdown_active = SHOES_SLOWDOWN + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF + +/obj/item/clothing/shoes/magboots/syndie + desc = "Reverse-engineered magnetic boots that have a heavy magnetic pull. Property of Gorlex Marauders." + name = "blood-red magboots" + icon_state = "syndiemag0" + magboot_state = "syndiemag" diff --git a/code/modules/clothing/spacesuits/_spacesuits.dm b/code/modules/clothing/spacesuits/_spacesuits.dm index b83e4ee5dfc2..57d43bff207f 100644 --- a/code/modules/clothing/spacesuits/_spacesuits.dm +++ b/code/modules/clothing/spacesuits/_spacesuits.dm @@ -1,46 +1,46 @@ -//Note: Everything in modules/clothing/spacesuits should have the entire suit grouped together. -// Meaning the the suit is defined directly after the corrisponding helmet. Just like below! -/obj/item/clothing/head/helmet/space - name = "space helmet" - icon_state = "spaceold" - desc = "A special helmet with solar UV shielding to protect your eyes from harmful rays." - clothing_flags = STOPSPRESSUREDAMAGE | THICKMATERIAL | SNUG_FIT | BLOCK_GAS_SMOKE_EFFECT | ALLOWINTERNALS //Wasp Port - Cit Internals - item_state = "spaceold" - permeability_coefficient = 0.01 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 50, "fire" = 80, "acid" = 70) - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - dynamic_hair_suffix = "" - dynamic_fhair_suffix = "" - cold_protection = HEAD - min_cold_protection_temperature = SPACE_HELM_MIN_TEMP_PROTECT - heat_protection = HEAD - max_heat_protection_temperature = SPACE_HELM_MAX_TEMP_PROTECT - flash_protect = FLASH_PROTECTION_WELDER - strip_delay = 50 - equip_delay_other = 50 - flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF - resistance_flags = NONE - dog_fashion = null - -/obj/item/clothing/suit/space - name = "space suit" - desc = "A suit that protects against low pressure environments. Has a big 13 on the back." - icon_state = "spaceold" - item_state = "s_suit" - w_class = WEIGHT_CLASS_BULKY - gas_transfer_coefficient = 0.01 - permeability_coefficient = 0.02 - clothing_flags = STOPSPRESSUREDAMAGE | THICKMATERIAL - body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS - allowed = list(/obj/item/flashlight, /obj/item/tank/internals) - slowdown = 1 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 50, "fire" = 80, "acid" = 70) - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT - cold_protection = CHEST | GROIN | LEGS | FEET | ARMS | HANDS - min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT - heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS - max_heat_protection_temperature = SPACE_SUIT_MAX_TEMP_PROTECT - strip_delay = 80 - equip_delay_other = 80 - resistance_flags = NONE - +//Note: Everything in modules/clothing/spacesuits should have the entire suit grouped together. +// Meaning the the suit is defined directly after the corrisponding helmet. Just like below! +/obj/item/clothing/head/helmet/space + name = "space helmet" + icon_state = "spaceold" + desc = "A special helmet with solar UV shielding to protect your eyes from harmful rays." + clothing_flags = STOPSPRESSUREDAMAGE | THICKMATERIAL | SNUG_FIT | BLOCK_GAS_SMOKE_EFFECT | ALLOWINTERNALS //Wasp Port - Cit Internals + item_state = "spaceold" + permeability_coefficient = 0.01 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 50, "fire" = 80, "acid" = 70) + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + dynamic_hair_suffix = "" + dynamic_fhair_suffix = "" + cold_protection = HEAD + min_cold_protection_temperature = SPACE_HELM_MIN_TEMP_PROTECT + heat_protection = HEAD + max_heat_protection_temperature = SPACE_HELM_MAX_TEMP_PROTECT + flash_protect = FLASH_PROTECTION_WELDER + strip_delay = 50 + equip_delay_other = 50 + flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF + resistance_flags = NONE + dog_fashion = null + +/obj/item/clothing/suit/space + name = "space suit" + desc = "A suit that protects against low pressure environments. Has a big 13 on the back." + icon_state = "spaceold" + item_state = "s_suit" + w_class = WEIGHT_CLASS_BULKY + gas_transfer_coefficient = 0.01 + permeability_coefficient = 0.02 + clothing_flags = STOPSPRESSUREDAMAGE | THICKMATERIAL + body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS + allowed = list(/obj/item/flashlight, /obj/item/tank/internals) + slowdown = 1 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 50, "fire" = 80, "acid" = 70) + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT + cold_protection = CHEST | GROIN | LEGS | FEET | ARMS | HANDS + min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT + heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS + max_heat_protection_temperature = SPACE_SUIT_MAX_TEMP_PROTECT + strip_delay = 80 + equip_delay_other = 80 + resistance_flags = NONE + diff --git a/code/modules/clothing/spacesuits/miscellaneous.dm b/code/modules/clothing/spacesuits/miscellaneous.dm index 6352b1ebdb23..67232c05d32a 100644 --- a/code/modules/clothing/spacesuits/miscellaneous.dm +++ b/code/modules/clothing/spacesuits/miscellaneous.dm @@ -1,453 +1,453 @@ -//miscellaneous spacesuits -/* -Contains: - - Captain's spacesuit - - Death squad's hardsuit - - SWAT suit - - Officer's beret/spacesuit - - NASA Voidsuit - - Father Christmas' magical clothes - - Pirate's spacesuit - - ERT hardsuit: command, sec, engi, med, janitor - - EVA spacesuit - - Freedom's spacesuit (freedom from vacuum's oppression) - - Carp hardsuit - - Bounty hunter hardsuit - - Blackmarket combat medic hardsuit -*/ - - //Death squad armored space suits, not hardsuits! -/obj/item/clothing/head/helmet/space/hardsuit/deathsquad - name = "MK.III SWAT Helmet" - desc = "An advanced tactical space helmet." - icon_state = "deathsquad" - item_state = "deathsquad" - armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 60, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100) - strip_delay = 130 - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - resistance_flags = FIRE_PROOF | ACID_PROOF - actions_types = list() - -/obj/item/clothing/head/helmet/space/hardsuit/deathsquad/attack_self(mob/user) - return - -/obj/item/clothing/suit/space/hardsuit/deathsquad - name = "MK.III SWAT Suit" - desc = "A prototype designed to replace the ageing MK.II SWAT suit. Based on the streamlined MK.II model, the traditional ceramic and graphene plate construction was replaced with plasteel, allowing superior armor against most threats. There's room for some kind of energy projection device on the back." - icon_state = "deathsquad" - item_state = "swat_suit" - allowed = list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/restraints/handcuffs, /obj/item/tank/internals, /obj/item/kitchen/knife/combat) - armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 60, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100) - strip_delay = 130 - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - resistance_flags = FIRE_PROOF | ACID_PROOF - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/deathsquad - dog_fashion = /datum/dog_fashion/back/deathsquad - - //NEW SWAT suit -/obj/item/clothing/suit/space/swat - name = "MK.I SWAT Suit" - desc = "A tactical space suit first developed in a joint effort by the defunct IS-ERI and Nanotrasen in 20XX for military space operations. A tried and true workhorse, it is very difficult to move in but offers robust protection against all threats!" - icon_state = "heavy" - item_state = "swat_suit" - allowed = list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/restraints/handcuffs, /obj/item/tank/internals, /obj/item/kitchen/knife/combat) - armor = list("melee" = 40, "bullet" = 30, "laser" = 30,"energy" = 40, "bomb" = 50, "bio" = 90, "rad" = 20, "fire" = 100, "acid" = 100) - strip_delay = 120 - resistance_flags = FIRE_PROOF | ACID_PROOF - -/obj/item/clothing/head/helmet/space/beret - name = "officer's beret" - desc = "An armored beret commonly used by special operations officers. Uses advanced force field technology to protect the head from space." - icon_state = "beret_badge" - dynamic_hair_suffix = "+generic" - dynamic_fhair_suffix = "+generic" - flags_inv = 0 - armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 60, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100) - strip_delay = 130 - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - resistance_flags = FIRE_PROOF | ACID_PROOF - -/obj/item/clothing/suit/space/officer - name = "officer's jacket" - desc = "An armored, space-proof jacket used in special operations." - icon_state = "detective" - item_state = "det_suit" - blood_overlay_type = "coat" - slowdown = 0 - flags_inv = 0 - w_class = WEIGHT_CLASS_NORMAL - allowed = list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/restraints/handcuffs, /obj/item/tank/internals) - armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 60, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100) - strip_delay = 130 - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - resistance_flags = FIRE_PROOF | ACID_PROOF - - //NASA Voidsuit -/obj/item/clothing/head/helmet/space/nasavoid - name = "NASA Void Helmet" - desc = "An old, NASA CentCom branch designed, dark red space suit helmet." - icon_state = "void" - item_state = "void" - -/obj/item/clothing/suit/space/nasavoid - name = "NASA Voidsuit" - icon_state = "void" - item_state = "void" - desc = "An old, NASA CentCom branch designed, dark red space suit." - allowed = list(/obj/item/flashlight, /obj/item/tank/internals, /obj/item/multitool) - -/obj/item/clothing/head/helmet/space/nasavoid/old - name = "Engineering Void Helmet" - desc = "A CentCom engineering dark red space suit helmet. While old and dusty, it still gets the job done." - icon_state = "void" - item_state = "void" - -/obj/item/clothing/suit/space/nasavoid/old - name = "Engineering Voidsuit" - icon_state = "void" - item_state = "void" - desc = "A CentCom engineering dark red space suit. Age has degraded the suit making is difficult to move around in." - slowdown = 4 - allowed = list(/obj/item/flashlight, /obj/item/tank/internals, /obj/item/multitool) - - //Space santa outfit suit -/obj/item/clothing/head/helmet/space/santahat - name = "Santa's hat" - desc = "Ho ho ho. Merrry X-mas!" - icon_state = "santahat" - flags_cover = HEADCOVERSEYES - - dog_fashion = /datum/dog_fashion/head/santa - -/obj/item/clothing/suit/space/santa - name = "Santa's suit" - desc = "Festive!" - icon_state = "santa" - item_state = "santa" - slowdown = 0 - allowed = list(/obj/item) //for stuffing exta special presents - - - //Space pirate outfit -/obj/item/clothing/head/helmet/space/pirate - name = "pirate hat" - desc = "Yarr." - icon_state = "pirate" - item_state = "pirate" - armor = list("melee" = 30, "bullet" = 50, "laser" = 30,"energy" = 40, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 60, "acid" = 75) - flags_inv = HIDEHAIR - strip_delay = 40 - equip_delay_other = 20 - flags_cover = HEADCOVERSEYES - -/obj/item/clothing/head/helmet/space/pirate/bandana - name = "pirate bandana" - icon_state = "bandana" - item_state = "bandana" - -/obj/item/clothing/suit/space/pirate - name = "pirate coat" - desc = "Yarr." - icon_state = "pirate" - item_state = "pirate" - w_class = WEIGHT_CLASS_NORMAL - flags_inv = 0 - allowed = list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/restraints/handcuffs, /obj/item/tank/internals, /obj/item/melee/transforming/energy/sword/pirate, /obj/item/clothing/glasses/eyepatch, /obj/item/reagent_containers/food/drinks/bottle/rum) - slowdown = 0 - armor = list("melee" = 30, "bullet" = 50, "laser" = 30,"energy" = 40, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 60, "acid" = 75) - strip_delay = 40 - equip_delay_other = 20 - - //Emergency Response Team suits -/obj/item/clothing/head/helmet/space/hardsuit/ert - name = "emergency response team commander helmet" - desc = "The integrated helmet of an ERT hardsuit, this one has blue highlights." - icon_state = "hardsuit0-ert_commander" - item_state = "hardsuit0-ert_commander" - hardsuit_type = "ert_commander" - armor = list("melee" = 65, "bullet" = 50, "laser" = 50, "energy" = 60, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 80) - strip_delay = 130 - brightness_on = 7 - resistance_flags = FIRE_PROOF - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - -/obj/item/clothing/head/helmet/space/hardsuit/ert/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, LOCKED_HELMET_TRAIT) - -/obj/item/clothing/suit/space/hardsuit/ert - name = "emergency response team commander hardsuit" - desc = "The standard issue hardsuit of the ERT, this one has blue highlights. Offers superb protection against environmental hazards." - icon_state = "ert_command" - item_state = "ert_command" - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert - allowed = list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/restraints/handcuffs, /obj/item/tank/internals) - armor = list("melee" = 65, "bullet" = 50, "laser" = 50, "energy" = 60, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 80) - slowdown = 0 - strip_delay = 130 - resistance_flags = FIRE_PROOF - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - - //ERT Security -/obj/item/clothing/head/helmet/space/hardsuit/ert/sec - name = "emergency response team security helmet" - desc = "The integrated helmet of an ERT hardsuit, this one has red highlights." - icon_state = "hardsuit0-ert_security" - item_state = "hardsuit0-ert_security" - hardsuit_type = "ert_security" - -/obj/item/clothing/suit/space/hardsuit/ert/sec - name = "emergency response team security hardsuit" - desc = "The standard issue hardsuit of the ERT, this one has red highlights. Offers superb protection against environmental hazards." - icon_state = "ert_security" - item_state = "ert_security" - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/sec - - //ERT Engineering -/obj/item/clothing/head/helmet/space/hardsuit/ert/engi - name = "emergency response team engineering helmet" - desc = "The integrated helmet of an ERT hardsuit, this one has orange highlights." - icon_state = "hardsuit0-ert_engineer" - item_state = "hardsuit0-ert_engineer" - hardsuit_type = "ert_engineer" - -/obj/item/clothing/suit/space/hardsuit/ert/engi - name = "emergency response team engineering hardsuit" - desc = "The standard issue hardsuit of the ERT, this one has orange highlights. Offers superb protection against environmental hazards." - icon_state = "ert_engineer" - item_state = "ert_engineer" - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/engi - - //ERT Medical -/obj/item/clothing/head/helmet/space/hardsuit/ert/med - name = "emergency response team medical helmet" - desc = "The integrated helmet of an ERT hardsuit, this one has white highlights." - icon_state = "hardsuit0-ert_medical" - item_state = "hardsuit0-ert_medical" - hardsuit_type = "ert_medical" - -/obj/item/clothing/suit/space/hardsuit/ert/med - name = "emergency response team medical hardsuit" - desc = "The standard issue hardsuit of the ERT, this one has white highlights. Offers superb protection against environmental hazards." - icon_state = "ert_medical" - item_state = "ert_medical" - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/med - - //ERT Janitor -/obj/item/clothing/head/helmet/space/hardsuit/ert/jani - name = "emergency response team janitorial helmet" - desc = "The integrated helmet of an ERT hardsuit, this one has purple highlights." - icon_state = "hardsuit0-ert_janitor" - item_state = "hardsuit0-ert_janitor" - hardsuit_type = "ert_janitor" - -/obj/item/clothing/suit/space/hardsuit/ert/jani - name = "emergency response team janitorial hardsuit" - desc = "The standard issue hardsuit of the ERT, this one has purple highlights. Offers superb protection against environmental hazards. This one has extra clips for holding various janitorial tools." - icon_state = "ert_janitor" - item_state = "ert_janitor" - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/jani - allowed = list(/obj/item/tank/internals, /obj/item/storage/bag/trash, /obj/item/melee/flyswatter, /obj/item/mop, /obj/item/holosign_creator, /obj/item/reagent_containers/glass/bucket, /obj/item/reagent_containers/spray/chemsprayer/janitor) - - //ERT Clown -/obj/item/clothing/head/helmet/space/hardsuit/ert/clown - name = "emergency response team clown helmet" - desc = "The integrated helmet of an ERT hardsuit, this one is colourful!" - icon_state = "hardsuit0-ert_clown" - item_state = "hardsuit0-ert_clown" - hardsuit_type = "ert_clown" - -/obj/item/clothing/suit/space/hardsuit/ert/clown - name = "emergency response team clown hardsuit" - desc = "The non-standard issue hardsuit of the ERT, this one is colourful! Offers superb protection against environmental hazards. Does not offer superb protection against a ravaging crew." - icon_state = "ert_clown" - item_state = "ert_clown" - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/clown - allowed = list(/obj/item/tank/internals, /obj/item/bikehorn, /obj/item/instrument, /obj/item/reagent_containers/food/snacks/grown/banana, /obj/item/grown/bananapeel) - -/obj/item/clothing/suit/space/eva - name = "EVA suit" - icon_state = "space" - item_state = "s_suit" - desc = "A lightweight space suit with the basic ability to protect the wearer from the vacuum of space during emergencies." - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 20, "fire" = 50, "acid" = 65) - -/obj/item/clothing/head/helmet/space/eva - name = "EVA helmet" - icon_state = "space" - item_state = "space" - desc = "A lightweight space helmet with the basic ability to protect the wearer from the vacuum of space during emergencies." - flash_protect = FLASH_PROTECTION_NONE - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 20, "fire" = 50, "acid" = 65) - -/obj/item/clothing/head/helmet/space/freedom - name = "eagle helmet" - desc = "An advanced, space-proof helmet. It appears to be modeled after an old-world eagle." - icon_state = "griffinhat" - item_state = "griffinhat" - armor = list("melee" = 20, "bullet" = 40, "laser" = 30, "energy" = 40, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 80) - strip_delay = 130 - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - resistance_flags = ACID_PROOF | FIRE_PROOF - -/obj/item/clothing/suit/space/freedom - name = "eagle suit" - desc = "An advanced, light suit, fabricated from a mixture of synthetic feathers and space-resistant material. A gun holster appears to be integrated into the suit and the wings appear to be stuck in 'freedom' mode." - icon_state = "freedom" - item_state = "freedom" - allowed = list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/restraints/handcuffs, /obj/item/tank/internals) - armor = list("melee" = 20, "bullet" = 40, "laser" = 30,"energy" = 40, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 80) - strip_delay = 130 - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - resistance_flags = ACID_PROOF | FIRE_PROOF - slowdown = 0 - -//Carpsuit, bestsuit, lovesuit -/obj/item/clothing/head/helmet/space/hardsuit/carp - name = "carp helmet" - desc = "Spaceworthy and it looks like a space carp's head, smells like one too." - icon_state = "carp_helm" - item_state = "syndicate" - armor = list("melee" = -20, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 75, "fire" = 60, "acid" = 75) //As whimpy as a space carp - brightness_on = 0 //luminosity when on - actions_types = list() - flags_inv = HIDEEARS|HIDEHAIR|HIDEFACIALHAIR //facial hair will clip with the helm, this'll need a dynamic_fhair_suffix at some point. - -/obj/item/clothing/head/helmet/space/hardsuit/carp/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, LOCKED_HELMET_TRAIT) - -/obj/item/clothing/suit/space/hardsuit/carp - name = "carp space suit" - desc = "A slimming piece of dubious space carp technology, you suspect it won't stand up to hand-to-hand blows." - icon_state = "carp_suit" - item_state = "space_suit_syndicate" - slowdown = 0 //Space carp magic, never stop believing - armor = list("melee" = -20, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 75, "fire" = 60, "acid" = 75) //As whimpy whimpy whoo - allowed = list(/obj/item/tank/internals, /obj/item/pneumatic_cannon/speargun) //I'm giving you a hint here - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/carp - -/obj/item/clothing/head/helmet/space/hardsuit/carp/equipped(mob/living/carbon/human/user, slot) - ..() - if (slot == ITEM_SLOT_HEAD) - user.faction |= "carp" - -/obj/item/clothing/head/helmet/space/hardsuit/carp/dropped(mob/living/carbon/human/user) - ..() - if (user.head == src) - user.faction -= "carp" - -/obj/item/clothing/suit/space/hardsuit/carp/old - name = "battered carp space suit" - desc = "It's covered in bite marks and scratches, yet seems to be still perfectly functional." - slowdown = 1 - -/obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal - name = "paranormal response team helmet" - desc = "A helmet worn by those who deal with paranormal threats for a living." - icon_state = "hardsuit0-prt" - item_state = "hardsuit0-prt" - hardsuit_type = "prt" - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - actions_types = list() - resistance_flags = FIRE_PROOF - -/obj/item/clothing/suit/space/hardsuit/ert/paranormal/Initialize() - . = ..() - AddComponent(/datum/component/anti_magic, FALSE, FALSE, TRUE, ITEM_SLOT_OCLOTHING) - -/obj/item/clothing/suit/space/hardsuit/ert/paranormal - name = "paranormal response team hardsuit" - desc = "Powerful wards are built into this hardsuit, protecting the user from all manner of paranormal threats." - icon_state = "knight_grey" - item_state = "knight_grey" - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - resistance_flags = FIRE_PROOF - -/obj/item/clothing/suit/space/hardsuit/ert/paranormal/Initialize() - . = ..() - AddComponent(/datum/component/anti_magic, TRUE, TRUE, TRUE, ITEM_SLOT_OCLOTHING) - -/obj/item/clothing/suit/space/hardsuit/ert/paranormal/inquisitor - name = "inquisitor's hardsuit" - icon_state = "hardsuit-inq" - item_state = "hardsuit-inq" - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal/inquisitor - -/obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal/inquisitor - name = "inquisitor's helmet" - icon_state = "hardsuit0-inq" - item_state = "hardsuit0-inq" - hardsuit_type = "inq" - -/obj/item/clothing/suit/space/hardsuit/ert/paranormal/berserker - name = "champion's hardsuit" - desc = "Voices echo from the hardsuit, driving the user insane." - icon_state = "hardsuit-berserker" - item_state = "hardsuit-berserker" - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal/berserker - -/obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal/berserker - name = "champion's helmet" - desc = "Peering into the eyes of the helmet is enough to seal damnation." - icon_state = "hardsuit0-berserker" - item_state = "hardsuit0-berserker" - hardsuit_type = "berserker" - -/obj/item/clothing/head/helmet/space/fragile - name = "emergency space helmet" - desc = "A bulky, air-tight helmet meant to protect the user during emergency situations. It doesn't look very durable." - icon_state = "syndicate-helm-orange" - item_state = "syndicate-helm-orange" - armor = list("melee" = 5, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 0, "acid" = 0) - strip_delay = 65 - -/obj/item/clothing/suit/space/fragile - name = "emergency space suit" - desc = "A bulky, air-tight suit meant to protect the user during emergency situations. It doesn't look very durable." - var/torn = FALSE - icon_state = "syndicate-orange" - item_state = "syndicate-orange" - slowdown = 2 - armor = list("melee" = 5, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 0, "acid" = 0) - strip_delay = 65 - -/obj/item/clothing/suit/space/fragile/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(!torn && prob(50)) - to_chat(owner, "[src] tears from the damage, breaking the air-tight seal!") - clothing_flags &= ~STOPSPRESSUREDAMAGE - name = "torn [src]." - desc = "A bulky suit meant to protect the user during emergency situations, at least until someone tore a hole in the suit." - torn = TRUE - playsound(loc, 'sound/weapons/slashmiss.ogg', 50, TRUE) - playsound(loc, 'sound/effects/refill.ogg', 50, TRUE) - -/obj/item/clothing/suit/space/hunter - name = "bounty hunting suit" - desc = "A custom version of the MK.II SWAT suit, modified to look rugged and tough. Works as a space suit, if you can find a helmet." - icon_state = "hunter" - item_state = "swat_suit" - allowed = list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/restraints/handcuffs, /obj/item/tank/internals, /obj/item/kitchen/knife/combat) - armor = list("melee" = 60, "bullet" = 40, "laser" = 40, "energy" = 50, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100) - strip_delay = 130 - resistance_flags = FIRE_PROOF | ACID_PROOF - -//We can either be alive monsters or dead monsters, you choose. -/obj/item/clothing/head/helmet/space/hardsuit/combatmedic - name = "endemic combat medic helmet" - desc = "The integrated helmet of the combat medic hardsuit, this has a bright, glowing facemask." - icon_state = "hardsuit0-combatmedic" - item_state = "hardsuit0-combatmedic" - armor = list("melee" = 35, "bullet" = 10, "laser" = 20, "energy" = 30, "bomb" = 5, "bio" = 100, "rad" = 50, "fire" = 65, "acid" = 75) - hardsuit_type = "combatmedic" - -/obj/item/clothing/suit/space/hardsuit/combatmedic - name = "endemic combat medic hardsuit" - desc = "The standard issue hardsuit of infectious disease officers, before the formation of ERT teams. This model is labeled 'Veradux'." - icon_state = "combatmedic" - item_state = "combatmedic" - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/combatmedic - armor = list("melee" = 35, "bullet" = 10, "laser" = 20, "energy" = 30, "bomb" = 5, "bio" = 100, "rad" = 50, "fire" = 65, "acid" = 75) - allowed = list(/obj/item/gun, /obj/item/melee/baton, /obj/item/circular_saw, /obj/item/tank/internals, /obj/item/storage/box/pillbottles,\ - /obj/item/storage/firstaid, /obj/item/stack/medical/gauze, /obj/item/stack/medical/suture, /obj/item/stack/medical/mesh, /obj/item/storage/bag/chemistry) +//miscellaneous spacesuits +/* +Contains: + - Captain's spacesuit + - Death squad's hardsuit + - SWAT suit + - Officer's beret/spacesuit + - NASA Voidsuit + - Father Christmas' magical clothes + - Pirate's spacesuit + - ERT hardsuit: command, sec, engi, med, janitor + - EVA spacesuit + - Freedom's spacesuit (freedom from vacuum's oppression) + - Carp hardsuit + - Bounty hunter hardsuit + - Blackmarket combat medic hardsuit +*/ + + //Death squad armored space suits, not hardsuits! +/obj/item/clothing/head/helmet/space/hardsuit/deathsquad + name = "MK.III SWAT Helmet" + desc = "An advanced tactical space helmet." + icon_state = "deathsquad" + item_state = "deathsquad" + armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 60, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100) + strip_delay = 130 + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + resistance_flags = FIRE_PROOF | ACID_PROOF + actions_types = list() + +/obj/item/clothing/head/helmet/space/hardsuit/deathsquad/attack_self(mob/user) + return + +/obj/item/clothing/suit/space/hardsuit/deathsquad + name = "MK.III SWAT Suit" + desc = "A prototype designed to replace the ageing MK.II SWAT suit. Based on the streamlined MK.II model, the traditional ceramic and graphene plate construction was replaced with plasteel, allowing superior armor against most threats. There's room for some kind of energy projection device on the back." + icon_state = "deathsquad" + item_state = "swat_suit" + allowed = list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/restraints/handcuffs, /obj/item/tank/internals, /obj/item/kitchen/knife/combat) + armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 60, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100) + strip_delay = 130 + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + resistance_flags = FIRE_PROOF | ACID_PROOF + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/deathsquad + dog_fashion = /datum/dog_fashion/back/deathsquad + + //NEW SWAT suit +/obj/item/clothing/suit/space/swat + name = "MK.I SWAT Suit" + desc = "A tactical space suit first developed in a joint effort by the defunct IS-ERI and Nanotrasen in 20XX for military space operations. A tried and true workhorse, it is very difficult to move in but offers robust protection against all threats!" + icon_state = "heavy" + item_state = "swat_suit" + allowed = list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/restraints/handcuffs, /obj/item/tank/internals, /obj/item/kitchen/knife/combat) + armor = list("melee" = 40, "bullet" = 30, "laser" = 30,"energy" = 40, "bomb" = 50, "bio" = 90, "rad" = 20, "fire" = 100, "acid" = 100) + strip_delay = 120 + resistance_flags = FIRE_PROOF | ACID_PROOF + +/obj/item/clothing/head/helmet/space/beret + name = "officer's beret" + desc = "An armored beret commonly used by special operations officers. Uses advanced force field technology to protect the head from space." + icon_state = "beret_badge" + dynamic_hair_suffix = "+generic" + dynamic_fhair_suffix = "+generic" + flags_inv = 0 + armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 60, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100) + strip_delay = 130 + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + resistance_flags = FIRE_PROOF | ACID_PROOF + +/obj/item/clothing/suit/space/officer + name = "officer's jacket" + desc = "An armored, space-proof jacket used in special operations." + icon_state = "detective" + item_state = "det_suit" + blood_overlay_type = "coat" + slowdown = 0 + flags_inv = 0 + w_class = WEIGHT_CLASS_NORMAL + allowed = list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/restraints/handcuffs, /obj/item/tank/internals) + armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 60, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100) + strip_delay = 130 + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + resistance_flags = FIRE_PROOF | ACID_PROOF + + //NASA Voidsuit +/obj/item/clothing/head/helmet/space/nasavoid + name = "NASA Void Helmet" + desc = "An old, NASA CentCom branch designed, dark red space suit helmet." + icon_state = "void" + item_state = "void" + +/obj/item/clothing/suit/space/nasavoid + name = "NASA Voidsuit" + icon_state = "void" + item_state = "void" + desc = "An old, NASA CentCom branch designed, dark red space suit." + allowed = list(/obj/item/flashlight, /obj/item/tank/internals, /obj/item/multitool) + +/obj/item/clothing/head/helmet/space/nasavoid/old + name = "Engineering Void Helmet" + desc = "A CentCom engineering dark red space suit helmet. While old and dusty, it still gets the job done." + icon_state = "void" + item_state = "void" + +/obj/item/clothing/suit/space/nasavoid/old + name = "Engineering Voidsuit" + icon_state = "void" + item_state = "void" + desc = "A CentCom engineering dark red space suit. Age has degraded the suit making is difficult to move around in." + slowdown = 4 + allowed = list(/obj/item/flashlight, /obj/item/tank/internals, /obj/item/multitool) + + //Space santa outfit suit +/obj/item/clothing/head/helmet/space/santahat + name = "Santa's hat" + desc = "Ho ho ho. Merrry X-mas!" + icon_state = "santahat" + flags_cover = HEADCOVERSEYES + + dog_fashion = /datum/dog_fashion/head/santa + +/obj/item/clothing/suit/space/santa + name = "Santa's suit" + desc = "Festive!" + icon_state = "santa" + item_state = "santa" + slowdown = 0 + allowed = list(/obj/item) //for stuffing exta special presents + + + //Space pirate outfit +/obj/item/clothing/head/helmet/space/pirate + name = "pirate hat" + desc = "Yarr." + icon_state = "pirate" + item_state = "pirate" + armor = list("melee" = 30, "bullet" = 50, "laser" = 30,"energy" = 40, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 60, "acid" = 75) + flags_inv = HIDEHAIR + strip_delay = 40 + equip_delay_other = 20 + flags_cover = HEADCOVERSEYES + +/obj/item/clothing/head/helmet/space/pirate/bandana + name = "pirate bandana" + icon_state = "bandana" + item_state = "bandana" + +/obj/item/clothing/suit/space/pirate + name = "pirate coat" + desc = "Yarr." + icon_state = "pirate" + item_state = "pirate" + w_class = WEIGHT_CLASS_NORMAL + flags_inv = 0 + allowed = list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/restraints/handcuffs, /obj/item/tank/internals, /obj/item/melee/transforming/energy/sword/pirate, /obj/item/clothing/glasses/eyepatch, /obj/item/reagent_containers/food/drinks/bottle/rum) + slowdown = 0 + armor = list("melee" = 30, "bullet" = 50, "laser" = 30,"energy" = 40, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 60, "acid" = 75) + strip_delay = 40 + equip_delay_other = 20 + + //Emergency Response Team suits +/obj/item/clothing/head/helmet/space/hardsuit/ert + name = "emergency response team commander helmet" + desc = "The integrated helmet of an ERT hardsuit, this one has blue highlights." + icon_state = "hardsuit0-ert_commander" + item_state = "hardsuit0-ert_commander" + hardsuit_type = "ert_commander" + armor = list("melee" = 65, "bullet" = 50, "laser" = 50, "energy" = 60, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 80) + strip_delay = 130 + brightness_on = 7 + resistance_flags = FIRE_PROOF + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + +/obj/item/clothing/head/helmet/space/hardsuit/ert/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, LOCKED_HELMET_TRAIT) + +/obj/item/clothing/suit/space/hardsuit/ert + name = "emergency response team commander hardsuit" + desc = "The standard issue hardsuit of the ERT, this one has blue highlights. Offers superb protection against environmental hazards." + icon_state = "ert_command" + item_state = "ert_command" + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert + allowed = list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/restraints/handcuffs, /obj/item/tank/internals) + armor = list("melee" = 65, "bullet" = 50, "laser" = 50, "energy" = 60, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 80) + slowdown = 0 + strip_delay = 130 + resistance_flags = FIRE_PROOF + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + + //ERT Security +/obj/item/clothing/head/helmet/space/hardsuit/ert/sec + name = "emergency response team security helmet" + desc = "The integrated helmet of an ERT hardsuit, this one has red highlights." + icon_state = "hardsuit0-ert_security" + item_state = "hardsuit0-ert_security" + hardsuit_type = "ert_security" + +/obj/item/clothing/suit/space/hardsuit/ert/sec + name = "emergency response team security hardsuit" + desc = "The standard issue hardsuit of the ERT, this one has red highlights. Offers superb protection against environmental hazards." + icon_state = "ert_security" + item_state = "ert_security" + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/sec + + //ERT Engineering +/obj/item/clothing/head/helmet/space/hardsuit/ert/engi + name = "emergency response team engineering helmet" + desc = "The integrated helmet of an ERT hardsuit, this one has orange highlights." + icon_state = "hardsuit0-ert_engineer" + item_state = "hardsuit0-ert_engineer" + hardsuit_type = "ert_engineer" + +/obj/item/clothing/suit/space/hardsuit/ert/engi + name = "emergency response team engineering hardsuit" + desc = "The standard issue hardsuit of the ERT, this one has orange highlights. Offers superb protection against environmental hazards." + icon_state = "ert_engineer" + item_state = "ert_engineer" + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/engi + + //ERT Medical +/obj/item/clothing/head/helmet/space/hardsuit/ert/med + name = "emergency response team medical helmet" + desc = "The integrated helmet of an ERT hardsuit, this one has white highlights." + icon_state = "hardsuit0-ert_medical" + item_state = "hardsuit0-ert_medical" + hardsuit_type = "ert_medical" + +/obj/item/clothing/suit/space/hardsuit/ert/med + name = "emergency response team medical hardsuit" + desc = "The standard issue hardsuit of the ERT, this one has white highlights. Offers superb protection against environmental hazards." + icon_state = "ert_medical" + item_state = "ert_medical" + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/med + + //ERT Janitor +/obj/item/clothing/head/helmet/space/hardsuit/ert/jani + name = "emergency response team janitorial helmet" + desc = "The integrated helmet of an ERT hardsuit, this one has purple highlights." + icon_state = "hardsuit0-ert_janitor" + item_state = "hardsuit0-ert_janitor" + hardsuit_type = "ert_janitor" + +/obj/item/clothing/suit/space/hardsuit/ert/jani + name = "emergency response team janitorial hardsuit" + desc = "The standard issue hardsuit of the ERT, this one has purple highlights. Offers superb protection against environmental hazards. This one has extra clips for holding various janitorial tools." + icon_state = "ert_janitor" + item_state = "ert_janitor" + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/jani + allowed = list(/obj/item/tank/internals, /obj/item/storage/bag/trash, /obj/item/melee/flyswatter, /obj/item/mop, /obj/item/holosign_creator, /obj/item/reagent_containers/glass/bucket, /obj/item/reagent_containers/spray/chemsprayer/janitor) + + //ERT Clown +/obj/item/clothing/head/helmet/space/hardsuit/ert/clown + name = "emergency response team clown helmet" + desc = "The integrated helmet of an ERT hardsuit, this one is colourful!" + icon_state = "hardsuit0-ert_clown" + item_state = "hardsuit0-ert_clown" + hardsuit_type = "ert_clown" + +/obj/item/clothing/suit/space/hardsuit/ert/clown + name = "emergency response team clown hardsuit" + desc = "The non-standard issue hardsuit of the ERT, this one is colourful! Offers superb protection against environmental hazards. Does not offer superb protection against a ravaging crew." + icon_state = "ert_clown" + item_state = "ert_clown" + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/clown + allowed = list(/obj/item/tank/internals, /obj/item/bikehorn, /obj/item/instrument, /obj/item/reagent_containers/food/snacks/grown/banana, /obj/item/grown/bananapeel) + +/obj/item/clothing/suit/space/eva + name = "EVA suit" + icon_state = "space" + item_state = "s_suit" + desc = "A lightweight space suit with the basic ability to protect the wearer from the vacuum of space during emergencies." + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 20, "fire" = 50, "acid" = 65) + +/obj/item/clothing/head/helmet/space/eva + name = "EVA helmet" + icon_state = "space" + item_state = "space" + desc = "A lightweight space helmet with the basic ability to protect the wearer from the vacuum of space during emergencies." + flash_protect = FLASH_PROTECTION_NONE + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 20, "fire" = 50, "acid" = 65) + +/obj/item/clothing/head/helmet/space/freedom + name = "eagle helmet" + desc = "An advanced, space-proof helmet. It appears to be modeled after an old-world eagle." + icon_state = "griffinhat" + item_state = "griffinhat" + armor = list("melee" = 20, "bullet" = 40, "laser" = 30, "energy" = 40, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 80) + strip_delay = 130 + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + resistance_flags = ACID_PROOF | FIRE_PROOF + +/obj/item/clothing/suit/space/freedom + name = "eagle suit" + desc = "An advanced, light suit, fabricated from a mixture of synthetic feathers and space-resistant material. A gun holster appears to be integrated into the suit and the wings appear to be stuck in 'freedom' mode." + icon_state = "freedom" + item_state = "freedom" + allowed = list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/restraints/handcuffs, /obj/item/tank/internals) + armor = list("melee" = 20, "bullet" = 40, "laser" = 30,"energy" = 40, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 80) + strip_delay = 130 + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + resistance_flags = ACID_PROOF | FIRE_PROOF + slowdown = 0 + +//Carpsuit, bestsuit, lovesuit +/obj/item/clothing/head/helmet/space/hardsuit/carp + name = "carp helmet" + desc = "Spaceworthy and it looks like a space carp's head, smells like one too." + icon_state = "carp_helm" + item_state = "syndicate" + armor = list("melee" = -20, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 75, "fire" = 60, "acid" = 75) //As whimpy as a space carp + brightness_on = 0 //luminosity when on + actions_types = list() + flags_inv = HIDEEARS|HIDEHAIR|HIDEFACIALHAIR //facial hair will clip with the helm, this'll need a dynamic_fhair_suffix at some point. + +/obj/item/clothing/head/helmet/space/hardsuit/carp/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, LOCKED_HELMET_TRAIT) + +/obj/item/clothing/suit/space/hardsuit/carp + name = "carp space suit" + desc = "A slimming piece of dubious space carp technology, you suspect it won't stand up to hand-to-hand blows." + icon_state = "carp_suit" + item_state = "space_suit_syndicate" + slowdown = 0 //Space carp magic, never stop believing + armor = list("melee" = -20, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 75, "fire" = 60, "acid" = 75) //As whimpy whimpy whoo + allowed = list(/obj/item/tank/internals, /obj/item/pneumatic_cannon/speargun) //I'm giving you a hint here + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/carp + +/obj/item/clothing/head/helmet/space/hardsuit/carp/equipped(mob/living/carbon/human/user, slot) + ..() + if (slot == ITEM_SLOT_HEAD) + user.faction |= "carp" + +/obj/item/clothing/head/helmet/space/hardsuit/carp/dropped(mob/living/carbon/human/user) + ..() + if (user.head == src) + user.faction -= "carp" + +/obj/item/clothing/suit/space/hardsuit/carp/old + name = "battered carp space suit" + desc = "It's covered in bite marks and scratches, yet seems to be still perfectly functional." + slowdown = 1 + +/obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal + name = "paranormal response team helmet" + desc = "A helmet worn by those who deal with paranormal threats for a living." + icon_state = "hardsuit0-prt" + item_state = "hardsuit0-prt" + hardsuit_type = "prt" + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + actions_types = list() + resistance_flags = FIRE_PROOF + +/obj/item/clothing/suit/space/hardsuit/ert/paranormal/Initialize() + . = ..() + AddComponent(/datum/component/anti_magic, FALSE, FALSE, TRUE, ITEM_SLOT_OCLOTHING) + +/obj/item/clothing/suit/space/hardsuit/ert/paranormal + name = "paranormal response team hardsuit" + desc = "Powerful wards are built into this hardsuit, protecting the user from all manner of paranormal threats." + icon_state = "knight_grey" + item_state = "knight_grey" + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + resistance_flags = FIRE_PROOF + +/obj/item/clothing/suit/space/hardsuit/ert/paranormal/Initialize() + . = ..() + AddComponent(/datum/component/anti_magic, TRUE, TRUE, TRUE, ITEM_SLOT_OCLOTHING) + +/obj/item/clothing/suit/space/hardsuit/ert/paranormal/inquisitor + name = "inquisitor's hardsuit" + icon_state = "hardsuit-inq" + item_state = "hardsuit-inq" + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal/inquisitor + +/obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal/inquisitor + name = "inquisitor's helmet" + icon_state = "hardsuit0-inq" + item_state = "hardsuit0-inq" + hardsuit_type = "inq" + +/obj/item/clothing/suit/space/hardsuit/ert/paranormal/berserker + name = "champion's hardsuit" + desc = "Voices echo from the hardsuit, driving the user insane." + icon_state = "hardsuit-berserker" + item_state = "hardsuit-berserker" + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal/berserker + +/obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal/berserker + name = "champion's helmet" + desc = "Peering into the eyes of the helmet is enough to seal damnation." + icon_state = "hardsuit0-berserker" + item_state = "hardsuit0-berserker" + hardsuit_type = "berserker" + +/obj/item/clothing/head/helmet/space/fragile + name = "emergency space helmet" + desc = "A bulky, air-tight helmet meant to protect the user during emergency situations. It doesn't look very durable." + icon_state = "syndicate-helm-orange" + item_state = "syndicate-helm-orange" + armor = list("melee" = 5, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 0, "acid" = 0) + strip_delay = 65 + +/obj/item/clothing/suit/space/fragile + name = "emergency space suit" + desc = "A bulky, air-tight suit meant to protect the user during emergency situations. It doesn't look very durable." + var/torn = FALSE + icon_state = "syndicate-orange" + item_state = "syndicate-orange" + slowdown = 2 + armor = list("melee" = 5, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 0, "acid" = 0) + strip_delay = 65 + +/obj/item/clothing/suit/space/fragile/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + if(!torn && prob(50)) + to_chat(owner, "[src] tears from the damage, breaking the air-tight seal!") + clothing_flags &= ~STOPSPRESSUREDAMAGE + name = "torn [src]." + desc = "A bulky suit meant to protect the user during emergency situations, at least until someone tore a hole in the suit." + torn = TRUE + playsound(loc, 'sound/weapons/slashmiss.ogg', 50, TRUE) + playsound(loc, 'sound/effects/refill.ogg', 50, TRUE) + +/obj/item/clothing/suit/space/hunter + name = "bounty hunting suit" + desc = "A custom version of the MK.II SWAT suit, modified to look rugged and tough. Works as a space suit, if you can find a helmet." + icon_state = "hunter" + item_state = "swat_suit" + allowed = list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/restraints/handcuffs, /obj/item/tank/internals, /obj/item/kitchen/knife/combat) + armor = list("melee" = 60, "bullet" = 40, "laser" = 40, "energy" = 50, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100) + strip_delay = 130 + resistance_flags = FIRE_PROOF | ACID_PROOF + +//We can either be alive monsters or dead monsters, you choose. +/obj/item/clothing/head/helmet/space/hardsuit/combatmedic + name = "endemic combat medic helmet" + desc = "The integrated helmet of the combat medic hardsuit, this has a bright, glowing facemask." + icon_state = "hardsuit0-combatmedic" + item_state = "hardsuit0-combatmedic" + armor = list("melee" = 35, "bullet" = 10, "laser" = 20, "energy" = 30, "bomb" = 5, "bio" = 100, "rad" = 50, "fire" = 65, "acid" = 75) + hardsuit_type = "combatmedic" + +/obj/item/clothing/suit/space/hardsuit/combatmedic + name = "endemic combat medic hardsuit" + desc = "The standard issue hardsuit of infectious disease officers, before the formation of ERT teams. This model is labeled 'Veradux'." + icon_state = "combatmedic" + item_state = "combatmedic" + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/combatmedic + armor = list("melee" = 35, "bullet" = 10, "laser" = 20, "energy" = 30, "bomb" = 5, "bio" = 100, "rad" = 50, "fire" = 65, "acid" = 75) + allowed = list(/obj/item/gun, /obj/item/melee/baton, /obj/item/circular_saw, /obj/item/tank/internals, /obj/item/storage/box/pillbottles,\ + /obj/item/storage/firstaid, /obj/item/stack/medical/gauze, /obj/item/stack/medical/suture, /obj/item/stack/medical/mesh, /obj/item/storage/bag/chemistry) diff --git a/code/modules/clothing/spacesuits/syndi.dm b/code/modules/clothing/spacesuits/syndi.dm index c5ab81c851f3..5aed5d5f0081 100644 --- a/code/modules/clothing/spacesuits/syndi.dm +++ b/code/modules/clothing/spacesuits/syndi.dm @@ -1,161 +1,161 @@ -//Regular syndicate space suit -/obj/item/clothing/head/helmet/space/syndicate - name = "red space helmet" - icon_state = "syndicate" - item_state = "syndicate" - desc = "Has a tag on it: Totally not property of an enemy corporation, honest!" - armor = list("melee" = 40, "bullet" = 50, "laser" = 30,"energy" = 40, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 80, "acid" = 85) - -/obj/item/clothing/suit/space/syndicate - name = "red space suit" - icon_state = "syndicate" - item_state = "space_suit_syndicate" - desc = "Has a tag on it: Totally not property of an enemy corporation, honest!" - w_class = WEIGHT_CLASS_NORMAL - allowed = list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/melee/transforming/energy/sword/saber, /obj/item/restraints/handcuffs, /obj/item/tank/internals) - armor = list("melee" = 40, "bullet" = 50, "laser" = 30,"energy" = 40, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 80, "acid" = 85) - -//Green syndicate space suit -/obj/item/clothing/head/helmet/space/syndicate/green - name = "green space helmet" - icon_state = "syndicate-helm-green" - item_state = "syndicate-helm-green" - -/obj/item/clothing/suit/space/syndicate/green - name = "green space suit" - icon_state = "syndicate-green" - item_state = "syndicate-green" - - -//Dark green syndicate space suit -/obj/item/clothing/head/helmet/space/syndicate/green/dark - name = "dark green space helmet" - icon_state = "syndicate-helm-green-dark" - item_state = "syndicate-helm-green-dark" - -/obj/item/clothing/suit/space/syndicate/green/dark - name = "dark green space suit" - icon_state = "syndicate-green-dark" - item_state = "syndicate-green-dark" - - -//Orange syndicate space suit -/obj/item/clothing/head/helmet/space/syndicate/orange - name = "orange space helmet" - icon_state = "syndicate-helm-orange" - item_state = "syndicate-helm-orange" - -/obj/item/clothing/suit/space/syndicate/orange - name = "orange space suit" - icon_state = "syndicate-orange" - item_state = "syndicate-orange" - -//Blue syndicate space suit -/obj/item/clothing/head/helmet/space/syndicate/blue - name = "blue space helmet" - icon_state = "syndicate-helm-blue" - item_state = "syndicate-helm-blue" - -/obj/item/clothing/suit/space/syndicate/blue - name = "blue space suit" - icon_state = "syndicate-blue" - item_state = "syndicate-blue" - - -//Black syndicate space suit -/obj/item/clothing/head/helmet/space/syndicate/black - name = "black space helmet" - icon_state = "syndicate-helm-black" - item_state = "syndicate-helm-black" - -/obj/item/clothing/suit/space/syndicate/black - name = "black space suit" - icon_state = "syndicate-black" - item_state = "syndicate-black" - - -//Black-green syndicate space suit -/obj/item/clothing/head/helmet/space/syndicate/black/green - name = "black space helmet" - icon_state = "syndicate-helm-black-green" - item_state = "syndicate-helm-black-green" - -/obj/item/clothing/suit/space/syndicate/black/green - name = "black and green space suit" - icon_state = "syndicate-black-green" - item_state = "syndicate-black-green" - - -//Black-blue syndicate space suit -/obj/item/clothing/head/helmet/space/syndicate/black/blue - name = "black space helmet" - icon_state = "syndicate-helm-black-blue" - item_state = "syndicate-helm-black-blue" - -/obj/item/clothing/suit/space/syndicate/black/blue - name = "black and blue space suit" - icon_state = "syndicate-black-blue" - item_state = "syndicate-black-blue" - - -//Black medical syndicate space suit -/obj/item/clothing/head/helmet/space/syndicate/black/med - name = "black space helmet" - icon_state = "syndicate-helm-black-med" - item_state = "syndicate-helm-black" - -/obj/item/clothing/suit/space/syndicate/black/med - name = "green space suit" - icon_state = "syndicate-black-med" - item_state = "syndicate-black" - - -//Black-orange syndicate space suit -/obj/item/clothing/head/helmet/space/syndicate/black/orange - name = "black space helmet" - icon_state = "syndicate-helm-black-orange" - item_state = "syndicate-helm-black" - -/obj/item/clothing/suit/space/syndicate/black/orange - name = "black and orange space suit" - icon_state = "syndicate-black-orange" - item_state = "syndicate-black" - - -//Black-red syndicate space suit -/obj/item/clothing/head/helmet/space/syndicate/black/red - name = "black space helmet" - icon_state = "syndicate-helm-black-red" - item_state = "syndicate-helm-black-red" - -/obj/item/clothing/suit/space/syndicate/black/red - name = "black and red space suit" - icon_state = "syndicate-black-red" - item_state = "syndicate-black-red" - -//Black-red syndicate contract varient -/obj/item/clothing/head/helmet/space/syndicate/contract - name = "contractor helmet" - desc = "A specialised black and gold helmet that's more compact than its standard Syndicate counterpart. Can be ultra-compressed into even the tightest of spaces." - w_class = WEIGHT_CLASS_SMALL - icon_state = "syndicate-contract-helm" - item_state = "syndicate-contract-helm" - -/obj/item/clothing/suit/space/syndicate/contract - name = "contractor space suit" - desc = "A specialised black and gold space suit that's quicker, and more compact than its standard Syndicate counterpart. Can be ultra-compressed into even the tightest of spaces." - slowdown = 1 - w_class = WEIGHT_CLASS_SMALL - icon_state = "syndicate-contract" - item_state = "syndicate-contract" - -//Black with yellow/red engineering syndicate space suit -/obj/item/clothing/head/helmet/space/syndicate/black/engie - name = "black space helmet" - icon_state = "syndicate-helm-black-engie" - item_state = "syndicate-helm-black" - -/obj/item/clothing/suit/space/syndicate/black/engie - name = "black engineering space suit" - icon_state = "syndicate-black-engie" - item_state = "syndicate-black" +//Regular syndicate space suit +/obj/item/clothing/head/helmet/space/syndicate + name = "red space helmet" + icon_state = "syndicate" + item_state = "syndicate" + desc = "Has a tag on it: Totally not property of an enemy corporation, honest!" + armor = list("melee" = 40, "bullet" = 50, "laser" = 30,"energy" = 40, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 80, "acid" = 85) + +/obj/item/clothing/suit/space/syndicate + name = "red space suit" + icon_state = "syndicate" + item_state = "space_suit_syndicate" + desc = "Has a tag on it: Totally not property of an enemy corporation, honest!" + w_class = WEIGHT_CLASS_NORMAL + allowed = list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/melee/transforming/energy/sword/saber, /obj/item/restraints/handcuffs, /obj/item/tank/internals) + armor = list("melee" = 40, "bullet" = 50, "laser" = 30,"energy" = 40, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 80, "acid" = 85) + +//Green syndicate space suit +/obj/item/clothing/head/helmet/space/syndicate/green + name = "green space helmet" + icon_state = "syndicate-helm-green" + item_state = "syndicate-helm-green" + +/obj/item/clothing/suit/space/syndicate/green + name = "green space suit" + icon_state = "syndicate-green" + item_state = "syndicate-green" + + +//Dark green syndicate space suit +/obj/item/clothing/head/helmet/space/syndicate/green/dark + name = "dark green space helmet" + icon_state = "syndicate-helm-green-dark" + item_state = "syndicate-helm-green-dark" + +/obj/item/clothing/suit/space/syndicate/green/dark + name = "dark green space suit" + icon_state = "syndicate-green-dark" + item_state = "syndicate-green-dark" + + +//Orange syndicate space suit +/obj/item/clothing/head/helmet/space/syndicate/orange + name = "orange space helmet" + icon_state = "syndicate-helm-orange" + item_state = "syndicate-helm-orange" + +/obj/item/clothing/suit/space/syndicate/orange + name = "orange space suit" + icon_state = "syndicate-orange" + item_state = "syndicate-orange" + +//Blue syndicate space suit +/obj/item/clothing/head/helmet/space/syndicate/blue + name = "blue space helmet" + icon_state = "syndicate-helm-blue" + item_state = "syndicate-helm-blue" + +/obj/item/clothing/suit/space/syndicate/blue + name = "blue space suit" + icon_state = "syndicate-blue" + item_state = "syndicate-blue" + + +//Black syndicate space suit +/obj/item/clothing/head/helmet/space/syndicate/black + name = "black space helmet" + icon_state = "syndicate-helm-black" + item_state = "syndicate-helm-black" + +/obj/item/clothing/suit/space/syndicate/black + name = "black space suit" + icon_state = "syndicate-black" + item_state = "syndicate-black" + + +//Black-green syndicate space suit +/obj/item/clothing/head/helmet/space/syndicate/black/green + name = "black space helmet" + icon_state = "syndicate-helm-black-green" + item_state = "syndicate-helm-black-green" + +/obj/item/clothing/suit/space/syndicate/black/green + name = "black and green space suit" + icon_state = "syndicate-black-green" + item_state = "syndicate-black-green" + + +//Black-blue syndicate space suit +/obj/item/clothing/head/helmet/space/syndicate/black/blue + name = "black space helmet" + icon_state = "syndicate-helm-black-blue" + item_state = "syndicate-helm-black-blue" + +/obj/item/clothing/suit/space/syndicate/black/blue + name = "black and blue space suit" + icon_state = "syndicate-black-blue" + item_state = "syndicate-black-blue" + + +//Black medical syndicate space suit +/obj/item/clothing/head/helmet/space/syndicate/black/med + name = "black space helmet" + icon_state = "syndicate-helm-black-med" + item_state = "syndicate-helm-black" + +/obj/item/clothing/suit/space/syndicate/black/med + name = "green space suit" + icon_state = "syndicate-black-med" + item_state = "syndicate-black" + + +//Black-orange syndicate space suit +/obj/item/clothing/head/helmet/space/syndicate/black/orange + name = "black space helmet" + icon_state = "syndicate-helm-black-orange" + item_state = "syndicate-helm-black" + +/obj/item/clothing/suit/space/syndicate/black/orange + name = "black and orange space suit" + icon_state = "syndicate-black-orange" + item_state = "syndicate-black" + + +//Black-red syndicate space suit +/obj/item/clothing/head/helmet/space/syndicate/black/red + name = "black space helmet" + icon_state = "syndicate-helm-black-red" + item_state = "syndicate-helm-black-red" + +/obj/item/clothing/suit/space/syndicate/black/red + name = "black and red space suit" + icon_state = "syndicate-black-red" + item_state = "syndicate-black-red" + +//Black-red syndicate contract varient +/obj/item/clothing/head/helmet/space/syndicate/contract + name = "contractor helmet" + desc = "A specialised black and gold helmet that's more compact than its standard Syndicate counterpart. Can be ultra-compressed into even the tightest of spaces." + w_class = WEIGHT_CLASS_SMALL + icon_state = "syndicate-contract-helm" + item_state = "syndicate-contract-helm" + +/obj/item/clothing/suit/space/syndicate/contract + name = "contractor space suit" + desc = "A specialised black and gold space suit that's quicker, and more compact than its standard Syndicate counterpart. Can be ultra-compressed into even the tightest of spaces." + slowdown = 1 + w_class = WEIGHT_CLASS_SMALL + icon_state = "syndicate-contract" + item_state = "syndicate-contract" + +//Black with yellow/red engineering syndicate space suit +/obj/item/clothing/head/helmet/space/syndicate/black/engie + name = "black space helmet" + icon_state = "syndicate-helm-black-engie" + item_state = "syndicate-helm-black" + +/obj/item/clothing/suit/space/syndicate/black/engie + name = "black engineering space suit" + icon_state = "syndicate-black-engie" + item_state = "syndicate-black" diff --git a/code/modules/clothing/suits/_suits.dm b/code/modules/clothing/suits/_suits.dm index e4ce8e115f1f..869deb7615b1 100644 --- a/code/modules/clothing/suits/_suits.dm +++ b/code/modules/clothing/suits/_suits.dm @@ -1,35 +1,35 @@ -/obj/item/clothing/suit - icon = 'icons/obj/clothing/suits.dmi' - name = "suit" - var/fire_resist = T0C+100 - allowed = list(/obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - drop_sound = 'sound/items/handling/cloth_drop.ogg' - pickup_sound = 'sound/items/handling/cloth_pickup.ogg' - slot_flags = ITEM_SLOT_OCLOTHING - var/blood_overlay_type = "suit" - var/togglename = null - var/suittoggled = FALSE - pocket_storage_component_path = /datum/component/storage/concrete/pockets/exo // WaspStation Edit - Exowear Pockets - - -/obj/item/clothing/suit/worn_overlays(isinhands = FALSE) - . = list() - if(!isinhands) - if(damaged_clothes) - . += mutable_appearance('icons/effects/item_damage.dmi', "damaged[blood_overlay_type]") - if(HAS_BLOOD_DNA(src)) - . += mutable_appearance('icons/effects/blood.dmi', "[blood_overlay_type]blood") - var/mob/living/carbon/human/M = loc - if(ishuman(M) && M.w_uniform) - var/obj/item/clothing/under/U = M.w_uniform - if(istype(U) && U.attached_accessory) - var/obj/item/clothing/accessory/A = U.attached_accessory - if(A.above_suit) - . += U.accessory_overlay - -/obj/item/clothing/suit/update_clothes_damaged_state(damaging = TRUE) - ..() - if(ismob(loc)) - var/mob/M = loc - M.update_inv_wear_suit() +/obj/item/clothing/suit + icon = 'icons/obj/clothing/suits.dmi' + name = "suit" + var/fire_resist = T0C+100 + allowed = list(/obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + drop_sound = 'sound/items/handling/cloth_drop.ogg' + pickup_sound = 'sound/items/handling/cloth_pickup.ogg' + slot_flags = ITEM_SLOT_OCLOTHING + var/blood_overlay_type = "suit" + var/togglename = null + var/suittoggled = FALSE + pocket_storage_component_path = /datum/component/storage/concrete/pockets/exo // WaspStation Edit - Exowear Pockets + + +/obj/item/clothing/suit/worn_overlays(isinhands = FALSE) + . = list() + if(!isinhands) + if(damaged_clothes) + . += mutable_appearance('icons/effects/item_damage.dmi', "damaged[blood_overlay_type]") + if(HAS_BLOOD_DNA(src)) + . += mutable_appearance('icons/effects/blood.dmi', "[blood_overlay_type]blood") + var/mob/living/carbon/human/M = loc + if(ishuman(M) && M.w_uniform) + var/obj/item/clothing/under/U = M.w_uniform + if(istype(U) && U.attached_accessory) + var/obj/item/clothing/accessory/A = U.attached_accessory + if(A.above_suit) + . += U.accessory_overlay + +/obj/item/clothing/suit/update_clothes_damaged_state(damaging = TRUE) + ..() + if(ismob(loc)) + var/mob/M = loc + M.update_inv_wear_suit() diff --git a/code/modules/clothing/suits/bio.dm b/code/modules/clothing/suits/bio.dm index 3bc1adb94006..39e37451583b 100644 --- a/code/modules/clothing/suits/bio.dm +++ b/code/modules/clothing/suits/bio.dm @@ -1,100 +1,100 @@ -//Biosuit complete with shoes (in the item sprite) -/obj/item/clothing/head/bio_hood - name = "bio hood" - icon_state = "bio" - desc = "A hood that protects the head and face from biological contaminants." - permeability_coefficient = 0.01 - clothing_flags = THICKMATERIAL | BLOCK_GAS_SMOKE_EFFECT | SNUG_FIT - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 80, "fire" = 30, "acid" = 100) - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEHAIR|HIDEFACIALHAIR|HIDEFACE - resistance_flags = ACID_PROOF - flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF - -/obj/item/clothing/suit/bio_suit - name = "bio suit" - desc = "A suit that protects against biological contamination." - icon_state = "bio" - item_state = "bio_suit" - w_class = WEIGHT_CLASS_BULKY - gas_transfer_coefficient = 0.01 - permeability_coefficient = 0.01 - clothing_flags = THICKMATERIAL - body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS - slowdown = 0.5 - allowed = list(/obj/item/tank/internals, /obj/item/reagent_containers/dropper, /obj/item/flashlight/pen, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/hypospray, /obj/item/reagent_containers/glass/beaker) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 80, "fire" = 30, "acid" = 100) - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT - strip_delay = 70 - equip_delay_other = 70 - resistance_flags = ACID_PROOF - -//Standard biosuit, orange stripe -/obj/item/clothing/head/bio_hood/general - icon_state = "bio_general" - -/obj/item/clothing/suit/bio_suit/general - icon_state = "bio_general" - - -//Virology biosuit, green stripe -/obj/item/clothing/head/bio_hood/virology - icon_state = "bio_virology" - -/obj/item/clothing/suit/bio_suit/virology - icon_state = "bio_virology" - - -//Security biosuit, grey with red stripe across the chest -/obj/item/clothing/head/bio_hood/security - armor = list("melee" = 25, "bullet" = 15, "laser" = 25, "energy" = 35, "bomb" = 25, "bio" = 100, "rad" = 80, "fire" = 30, "acid" = 100) - icon_state = "bio_security" - -/obj/item/clothing/suit/bio_suit/security - armor = list("melee" = 25, "bullet" = 15, "laser" = 25, "energy" = 35, "bomb" = 25, "bio" = 100, "rad" = 80, "fire" = 30, "acid" = 100) - icon_state = "bio_security" - -/obj/item/clothing/suit/bio_suit/security/Initialize() - . = ..() - allowed += GLOB.security_vest_allowed - -//Janitor's biosuit, grey with purple arms -/obj/item/clothing/head/bio_hood/janitor - icon_state = "bio_janitor" - -/obj/item/clothing/suit/bio_suit/janitor - icon_state = "bio_janitor" - -/obj/item/clothing/suit/bio_suit/janitor/Initialize() - . = ..() - allowed += list(/obj/item/storage/bag/trash, /obj/item/reagent_containers/spray) - -//Scientist's biosuit, white with a pink-ish hue -/obj/item/clothing/head/bio_hood/scientist - icon_state = "bio_scientist" - -/obj/item/clothing/suit/bio_suit/scientist - icon_state = "bio_scientist" - -//CMO's biosuit, blue stripe -/obj/item/clothing/head/bio_hood/cmo - icon_state = "bio_cmo" - -/obj/item/clothing/suit/bio_suit/cmo - icon_state = "bio_cmo" - -/obj/item/clothing/suit/bio_suit/cmo/Initialize() - . = ..() - allowed += list(/obj/item/melee/classic_baton/telescopic) - -//Plague Dr mask can be found in clothing/masks/gasmask.dm -/obj/item/clothing/suit/bio_suit/plaguedoctorsuit - name = "plague doctor suit" - desc = "It protected doctors from the Black Death, back then. You bet your arse it's gonna help you against viruses." - icon_state = "plaguedoctor" - item_state = "bio_suit" - strip_delay = 40 - equip_delay_other = 20 - -/obj/item/clothing/suit/bio_suit/plaguedoctorsuit/Initialize() - . = ..() - allowed += list(/obj/item/storage/book/bible, /obj/item/nullrod, /obj/item/cane) +//Biosuit complete with shoes (in the item sprite) +/obj/item/clothing/head/bio_hood + name = "bio hood" + icon_state = "bio" + desc = "A hood that protects the head and face from biological contaminants." + permeability_coefficient = 0.01 + clothing_flags = THICKMATERIAL | BLOCK_GAS_SMOKE_EFFECT | SNUG_FIT + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 80, "fire" = 30, "acid" = 100) + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEHAIR|HIDEFACIALHAIR|HIDEFACE + resistance_flags = ACID_PROOF + flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF + +/obj/item/clothing/suit/bio_suit + name = "bio suit" + desc = "A suit that protects against biological contamination." + icon_state = "bio" + item_state = "bio_suit" + w_class = WEIGHT_CLASS_BULKY + gas_transfer_coefficient = 0.01 + permeability_coefficient = 0.01 + clothing_flags = THICKMATERIAL + body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS + slowdown = 0.5 + allowed = list(/obj/item/tank/internals, /obj/item/reagent_containers/dropper, /obj/item/flashlight/pen, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/hypospray, /obj/item/reagent_containers/glass/beaker) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 80, "fire" = 30, "acid" = 100) + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT + strip_delay = 70 + equip_delay_other = 70 + resistance_flags = ACID_PROOF + +//Standard biosuit, orange stripe +/obj/item/clothing/head/bio_hood/general + icon_state = "bio_general" + +/obj/item/clothing/suit/bio_suit/general + icon_state = "bio_general" + + +//Virology biosuit, green stripe +/obj/item/clothing/head/bio_hood/virology + icon_state = "bio_virology" + +/obj/item/clothing/suit/bio_suit/virology + icon_state = "bio_virology" + + +//Security biosuit, grey with red stripe across the chest +/obj/item/clothing/head/bio_hood/security + armor = list("melee" = 25, "bullet" = 15, "laser" = 25, "energy" = 35, "bomb" = 25, "bio" = 100, "rad" = 80, "fire" = 30, "acid" = 100) + icon_state = "bio_security" + +/obj/item/clothing/suit/bio_suit/security + armor = list("melee" = 25, "bullet" = 15, "laser" = 25, "energy" = 35, "bomb" = 25, "bio" = 100, "rad" = 80, "fire" = 30, "acid" = 100) + icon_state = "bio_security" + +/obj/item/clothing/suit/bio_suit/security/Initialize() + . = ..() + allowed += GLOB.security_vest_allowed + +//Janitor's biosuit, grey with purple arms +/obj/item/clothing/head/bio_hood/janitor + icon_state = "bio_janitor" + +/obj/item/clothing/suit/bio_suit/janitor + icon_state = "bio_janitor" + +/obj/item/clothing/suit/bio_suit/janitor/Initialize() + . = ..() + allowed += list(/obj/item/storage/bag/trash, /obj/item/reagent_containers/spray) + +//Scientist's biosuit, white with a pink-ish hue +/obj/item/clothing/head/bio_hood/scientist + icon_state = "bio_scientist" + +/obj/item/clothing/suit/bio_suit/scientist + icon_state = "bio_scientist" + +//CMO's biosuit, blue stripe +/obj/item/clothing/head/bio_hood/cmo + icon_state = "bio_cmo" + +/obj/item/clothing/suit/bio_suit/cmo + icon_state = "bio_cmo" + +/obj/item/clothing/suit/bio_suit/cmo/Initialize() + . = ..() + allowed += list(/obj/item/melee/classic_baton/telescopic) + +//Plague Dr mask can be found in clothing/masks/gasmask.dm +/obj/item/clothing/suit/bio_suit/plaguedoctorsuit + name = "plague doctor suit" + desc = "It protected doctors from the Black Death, back then. You bet your arse it's gonna help you against viruses." + icon_state = "plaguedoctor" + item_state = "bio_suit" + strip_delay = 40 + equip_delay_other = 20 + +/obj/item/clothing/suit/bio_suit/plaguedoctorsuit/Initialize() + . = ..() + allowed += list(/obj/item/storage/book/bible, /obj/item/nullrod, /obj/item/cane) diff --git a/code/modules/clothing/suits/cloaks.dm b/code/modules/clothing/suits/cloaks.dm index 5a6f10dd9780..cc26f126b81b 100644 --- a/code/modules/clothing/suits/cloaks.dm +++ b/code/modules/clothing/suits/cloaks.dm @@ -90,3 +90,48 @@ heat_protection = HEAD max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT resistance_flags = FIRE_PROOF | ACID_PROOF + +/obj/item/clothing/neck/cloak/skill_reward + var/associated_skill_path = /datum/skill + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE + +/obj/item/clothing/neck/cloak/skill_reward/examine(mob/user) + . = ..() + . += "You notice a powerful aura about this cloak, suggesting that only the truly experienced may wield it." + +/obj/item/clothing/neck/cloak/skill_reward/equipped(mob/user, slot) + if (user.mind?.get_skill_level(associated_skill_path) < SKILL_LEVEL_LEGENDARY) + to_chat(user, "You feel completely and utterly unworthy to even touch \the [src].") + user.dropItemToGround(src, TRUE) + return FALSE + return ..() + +/obj/item/clothing/neck/cloak/skill_reward/attack_hand(mob/user) + if (user.mind?.get_skill_level(associated_skill_path) < SKILL_LEVEL_LEGENDARY) + to_chat(user, "You feel completely and utterly unworthy to even touch \the [src]!") + return FALSE + return ..() + +/obj/item/clothing/neck/cloak/skill_reward/gaming + name = "legendary gamer's cloak" + desc = "Worn by the most skilled professional gamers on the station, this legendary cloak is only attainable by achieving true gaming enlightenment. This status symbol represents the awesome might of a being of focus, commitment, and sheer fucking will. Something casual gamers will never begin to understand." + icon_state = "gamercloak" + associated_skill_path = /datum/skill/gaming + +/obj/item/clothing/neck/cloak/skill_reward/cleaning + name = "legendary cleaner's cloak" + desc = "Worn by the most skilled of custodians, this legendary cloak is only attainable by achieving janitorial enlightenment. This status symbol represents a being not only extensively trained in grime combat, but one who is willing to use an entire aresenal of cleaning supplies to its full extent to wipe grime's miserable ass off the face of the station." + icon_state = "cleanercloak" + associated_skill_path = /datum/skill/cleaning + +/obj/item/clothing/neck/cloak/skill_reward/healing + name = "legendary healer's cloak" + desc = "Worn by the most skilled healers, this legendary cloak is only attainable by achieving true medical enlightenment. This status symbol represents a being who has saved enough lives to repopulate a small country, a being who could transplant a monkey's brain into your skull faster than you could yell ;HELP SEC." + icon_state = "healercloak" + associated_skill_path = /datum/skill/healing + +/obj/item/clothing/neck/cloak/skill_reward/mining + name = "legendary miner's cloak" + desc = "Worn by the most skilled miners, this legendary cloak is only attainable by achieving true mineral enlightenment. This status symbol represents a being who has forgotten more about rocks than most miners will ever know, a being who has moved mountains and filled valleys." + icon_state = "minercloak" + associated_skill_path = /datum/skill/mining diff --git a/code/modules/clothing/suits/jobs.dm b/code/modules/clothing/suits/jobs.dm index ee1ae267941f..8915b08ee532 100644 --- a/code/modules/clothing/suits/jobs.dm +++ b/code/modules/clothing/suits/jobs.dm @@ -1,185 +1,185 @@ -/* - * Job related - */ - -//Botanist -/obj/item/clothing/suit/apron - name = "apron" - desc = "A basic blue apron." - icon_state = "apron" - item_state = "apron" - blood_overlay_type = "armor" - body_parts_covered = CHEST|GROIN - allowed = list(/obj/item/reagent_containers/spray/plantbgone, /obj/item/plant_analyzer, /obj/item/seeds, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/glass/beaker, /obj/item/cultivator, /obj/item/reagent_containers/spray/pestspray, /obj/item/hatchet, /obj/item/storage/bag/plants) - -/obj/item/clothing/suit/apron/waders - name = "horticultural waders" - desc = "A pair of heavy duty leather waders, perfect for insulating your soft flesh from spills, soil and thorns." - icon_state = "hort_waders" - item_state = "hort_waders" - body_parts_covered = CHEST|GROIN|LEGS - permeability_coefficient = 0.5 - -//Captain -/obj/item/clothing/suit/captunic - name = "captain's parade tunic" - desc = "Worn by a Captain to show their class." - icon_state = "captunic" - item_state = "bio_suit" - body_parts_covered = CHEST|GROIN|LEGS|ARMS - flags_inv = HIDEJUMPSUIT - allowed = list(/obj/item/disk, /obj/item/stamp, /obj/item/reagent_containers/food/drinks/flask, /obj/item/melee, /obj/item/storage/lockbox/medal, /obj/item/assembly/flash/handheld, /obj/item/storage/box/matches, /obj/item/lighter, /obj/item/clothing/mask/cigarette, /obj/item/storage/fancy/cigarettes, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman) - -//Chef -/obj/item/clothing/suit/toggle/chef - name = "chef's apron" - desc = "An apron-jacket used by a high class chef." - icon_state = "chef" - item_state = "chef" - gas_transfer_coefficient = 0.9 - permeability_coefficient = 0.5 - body_parts_covered = CHEST|GROIN|ARMS - allowed = list(/obj/item/kitchen) - togglename = "sleeves" - -//Cook -/obj/item/clothing/suit/apron/chef - name = "cook's apron" - desc = "A basic, dull, white chef's apron." - icon_state = "apronchef" - item_state = "apronchef" - blood_overlay_type = "armor" - body_parts_covered = CHEST|GROIN - allowed = list(/obj/item/kitchen) - -//Detective -/obj/item/clothing/suit/det_suit - name = "trenchcoat" - desc = "An 18th-century multi-purpose trenchcoat. Someone who wears this means serious business." - icon_state = "detective" - item_state = "det_suit" - blood_overlay_type = "coat" - body_parts_covered = CHEST|GROIN|LEGS|ARMS - armor = list("melee" = 25, "bullet" = 10, "laser" = 25, "energy" = 35, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 45) - cold_protection = CHEST|GROIN|LEGS|ARMS - heat_protection = CHEST|GROIN|LEGS|ARMS - -/obj/item/clothing/suit/det_suit/Initialize() - . = ..() - allowed = GLOB.detective_vest_allowed - -/obj/item/clothing/suit/det_suit/grey - name = "noir trenchcoat" - desc = "A hard-boiled private investigator's grey trenchcoat." - icon_state = "greydet" - item_state = "greydet" - -/obj/item/clothing/suit/det_suit/noir - name = "noir suit coat" - desc = "A dapper private investigator's grey suit coat." - icon_state = "detsuit" - item_state = "detsuit" - -//Engineering -/obj/item/clothing/suit/hazardvest - name = "hazard vest" - desc = "A high-visibility vest used in work zones." - icon_state = "hazard" - item_state = "hazard" - blood_overlay_type = "armor" - allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/t_scanner, /obj/item/radio) - resistance_flags = NONE - pocket_storage_component_path = /datum/component/storage/concrete/pockets/exo/large - -//Lawyer -/obj/item/clothing/suit/toggle/lawyer - name = "blue suit jacket" - desc = "A snappy dress jacket." - icon_state = "suitjacket_blue" - item_state = "suitjacket_blue" - blood_overlay_type = "coat" - body_parts_covered = CHEST|ARMS - togglename = "buttons" - -/obj/item/clothing/suit/toggle/lawyer/purple - name = "purple suit jacket" - desc = "A foppish dress jacket." - icon_state = "suitjacket_purp" - item_state = "suitjacket_purp" - -/obj/item/clothing/suit/toggle/lawyer/black - name = "black suit jacket" - desc = "A professional suit jacket." - icon_state = "suitjacket_black" - item_state = "ro_suit" - - -//Mime -/obj/item/clothing/suit/toggle/suspenders - name = "suspenders" - desc = "They suspend the illusion of the mime's play." - icon = 'icons/obj/clothing/belts.dmi' - icon_state = "suspenders" - blood_overlay_type = "armor" //it's the less thing that I can put here - togglename = "straps" - -//Security -/obj/item/clothing/suit/security/officer - name = "security officer's jacket" - desc = "This jacket is for those special occasions when a security officer isn't required to wear their armor." - icon_state = "officerbluejacket" - item_state = "officerbluejacket" - body_parts_covered = CHEST|ARMS - -/obj/item/clothing/suit/security/warden - name = "warden's jacket" - desc = "Perfectly suited for the warden that wants to leave an impression of style on those who visit the brig." - icon_state = "wardenbluejacket" - item_state = "wardenbluejacket" - body_parts_covered = CHEST|ARMS - -/obj/item/clothing/suit/security/hos - name = "head of security's jacket" - desc = "This piece of clothing was specifically designed for asserting superior authority." - icon_state = "hosbluejacket" - item_state = "hosbluejacket" - body_parts_covered = CHEST|ARMS - -//Surgeon -/obj/item/clothing/suit/apron/surgical - name = "surgical apron" - desc = "A sterile blue surgical apron." - icon_state = "surgical" - allowed = list(/obj/item/scalpel, /obj/item/cautery, /obj/item/hemostat, /obj/item/retractor) - -//Curator -/obj/item/clothing/suit/curator - name = "treasure hunter's coat" - desc = "Both fashionable and lightly armoured, this jacket is favoured by treasure hunters the galaxy over." - icon_state = "curator" - item_state = "curator" - blood_overlay_type = "coat" - body_parts_covered = CHEST|ARMS - allowed = list(/obj/item/tank/internals, /obj/item/melee/curator_whip) - armor = list("melee" = 25, "bullet" = 10, "laser" = 25, "energy" = 35, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 45) - cold_protection = CHEST|ARMS - heat_protection = CHEST|ARMS - - -//Robotocist - -/obj/item/clothing/suit/hooded/techpriest - name = "techpriest robes" - desc = "For those who REALLY love their toasters." - icon_state = "techpriest" - item_state = "techpriest" - body_parts_covered = CHEST|GROIN|LEGS|ARMS - hoodtype = /obj/item/clothing/head/hooded/techpriest - -/obj/item/clothing/head/hooded/techpriest - name = "techpriest's hood" - desc = "A hood for those who REALLY love their toasters." - icon_state = "techpriesthood" - item_state = "techpriesthood" - body_parts_covered = HEAD - flags_inv = HIDEHAIR|HIDEEARS +/* + * Job related + */ + +//Botanist +/obj/item/clothing/suit/apron + name = "apron" + desc = "A basic blue apron." + icon_state = "apron" + item_state = "apron" + blood_overlay_type = "armor" + body_parts_covered = CHEST|GROIN + allowed = list(/obj/item/reagent_containers/spray/plantbgone, /obj/item/plant_analyzer, /obj/item/seeds, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/glass/beaker, /obj/item/cultivator, /obj/item/reagent_containers/spray/pestspray, /obj/item/hatchet, /obj/item/storage/bag/plants) + +/obj/item/clothing/suit/apron/waders + name = "horticultural waders" + desc = "A pair of heavy duty leather waders, perfect for insulating your soft flesh from spills, soil and thorns." + icon_state = "hort_waders" + item_state = "hort_waders" + body_parts_covered = CHEST|GROIN|LEGS + permeability_coefficient = 0.5 + +//Captain +/obj/item/clothing/suit/captunic + name = "captain's parade tunic" + desc = "Worn by a Captain to show their class." + icon_state = "captunic" + item_state = "bio_suit" + body_parts_covered = CHEST|GROIN|LEGS|ARMS + flags_inv = HIDEJUMPSUIT + allowed = list(/obj/item/disk, /obj/item/stamp, /obj/item/reagent_containers/food/drinks/flask, /obj/item/melee, /obj/item/storage/lockbox/medal, /obj/item/assembly/flash/handheld, /obj/item/storage/box/matches, /obj/item/lighter, /obj/item/clothing/mask/cigarette, /obj/item/storage/fancy/cigarettes, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman) + +//Chef +/obj/item/clothing/suit/toggle/chef + name = "chef's apron" + desc = "An apron-jacket used by a high class chef." + icon_state = "chef" + item_state = "chef" + gas_transfer_coefficient = 0.9 + permeability_coefficient = 0.5 + body_parts_covered = CHEST|GROIN|ARMS + allowed = list(/obj/item/kitchen) + togglename = "sleeves" + +//Cook +/obj/item/clothing/suit/apron/chef + name = "cook's apron" + desc = "A basic, dull, white chef's apron." + icon_state = "apronchef" + item_state = "apronchef" + blood_overlay_type = "armor" + body_parts_covered = CHEST|GROIN + allowed = list(/obj/item/kitchen) + +//Detective +/obj/item/clothing/suit/det_suit + name = "trenchcoat" + desc = "An 18th-century multi-purpose trenchcoat. Someone who wears this means serious business." + icon_state = "detective" + item_state = "det_suit" + blood_overlay_type = "coat" + body_parts_covered = CHEST|GROIN|LEGS|ARMS + armor = list("melee" = 25, "bullet" = 10, "laser" = 25, "energy" = 35, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 45) + cold_protection = CHEST|GROIN|LEGS|ARMS + heat_protection = CHEST|GROIN|LEGS|ARMS + +/obj/item/clothing/suit/det_suit/Initialize() + . = ..() + allowed = GLOB.detective_vest_allowed + +/obj/item/clothing/suit/det_suit/grey + name = "noir trenchcoat" + desc = "A hard-boiled private investigator's grey trenchcoat." + icon_state = "greydet" + item_state = "greydet" + +/obj/item/clothing/suit/det_suit/noir + name = "noir suit coat" + desc = "A dapper private investigator's grey suit coat." + icon_state = "detsuit" + item_state = "detsuit" + +//Engineering +/obj/item/clothing/suit/hazardvest + name = "hazard vest" + desc = "A high-visibility vest used in work zones." + icon_state = "hazard" + item_state = "hazard" + blood_overlay_type = "armor" + allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/t_scanner, /obj/item/radio) + resistance_flags = NONE + pocket_storage_component_path = /datum/component/storage/concrete/pockets/exo/large + +//Lawyer +/obj/item/clothing/suit/toggle/lawyer + name = "blue suit jacket" + desc = "A snappy dress jacket." + icon_state = "suitjacket_blue" + item_state = "suitjacket_blue" + blood_overlay_type = "coat" + body_parts_covered = CHEST|ARMS + togglename = "buttons" + +/obj/item/clothing/suit/toggle/lawyer/purple + name = "purple suit jacket" + desc = "A foppish dress jacket." + icon_state = "suitjacket_purp" + item_state = "suitjacket_purp" + +/obj/item/clothing/suit/toggle/lawyer/black + name = "black suit jacket" + desc = "A professional suit jacket." + icon_state = "suitjacket_black" + item_state = "ro_suit" + + +//Mime +/obj/item/clothing/suit/toggle/suspenders + name = "suspenders" + desc = "They suspend the illusion of the mime's play." + icon = 'icons/obj/clothing/belts.dmi' + icon_state = "suspenders" + blood_overlay_type = "armor" //it's the less thing that I can put here + togglename = "straps" + +//Security +/obj/item/clothing/suit/security/officer + name = "security officer's jacket" + desc = "This jacket is for those special occasions when a security officer isn't required to wear their armor." + icon_state = "officerbluejacket" + item_state = "officerbluejacket" + body_parts_covered = CHEST|ARMS + +/obj/item/clothing/suit/security/warden + name = "warden's jacket" + desc = "Perfectly suited for the warden that wants to leave an impression of style on those who visit the brig." + icon_state = "wardenbluejacket" + item_state = "wardenbluejacket" + body_parts_covered = CHEST|ARMS + +/obj/item/clothing/suit/security/hos + name = "head of security's jacket" + desc = "This piece of clothing was specifically designed for asserting superior authority." + icon_state = "hosbluejacket" + item_state = "hosbluejacket" + body_parts_covered = CHEST|ARMS + +//Surgeon +/obj/item/clothing/suit/apron/surgical + name = "surgical apron" + desc = "A sterile blue surgical apron." + icon_state = "surgical" + allowed = list(/obj/item/scalpel, /obj/item/cautery, /obj/item/hemostat, /obj/item/retractor) + +//Curator +/obj/item/clothing/suit/curator + name = "treasure hunter's coat" + desc = "Both fashionable and lightly armoured, this jacket is favoured by treasure hunters the galaxy over." + icon_state = "curator" + item_state = "curator" + blood_overlay_type = "coat" + body_parts_covered = CHEST|ARMS + allowed = list(/obj/item/tank/internals, /obj/item/melee/curator_whip) + armor = list("melee" = 25, "bullet" = 10, "laser" = 25, "energy" = 35, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 45) + cold_protection = CHEST|ARMS + heat_protection = CHEST|ARMS + + +//Robotocist + +/obj/item/clothing/suit/hooded/techpriest + name = "techpriest robes" + desc = "For those who REALLY love their toasters." + icon_state = "techpriest" + item_state = "techpriest" + body_parts_covered = CHEST|GROIN|LEGS|ARMS + hoodtype = /obj/item/clothing/head/hooded/techpriest + +/obj/item/clothing/head/hooded/techpriest + name = "techpriest's hood" + desc = "A hood for those who REALLY love their toasters." + icon_state = "techpriesthood" + item_state = "techpriesthood" + body_parts_covered = HEAD + flags_inv = HIDEHAIR|HIDEEARS diff --git a/code/modules/clothing/suits/labcoat.dm b/code/modules/clothing/suits/labcoat.dm index 4863aab7f8bb..9f2bd16279f3 100644 --- a/code/modules/clothing/suits/labcoat.dm +++ b/code/modules/clothing/suits/labcoat.dm @@ -1,53 +1,53 @@ -/obj/item/clothing/suit/toggle/labcoat - name = "labcoat" - desc = "A suit that protects against minor chemical spills." - icon_state = "labcoat" - item_state = "labcoat" - blood_overlay_type = "coat" - body_parts_covered = CHEST|ARMS - allowed = list(/obj/item/analyzer, /obj/item/stack/medical, /obj/item/dnainjector, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/hypospray, /obj/item/healthanalyzer, /obj/item/flashlight/pen, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/pill, /obj/item/storage/pill_bottle, /obj/item/paper, /obj/item/melee/classic_baton/telescopic, /obj/item/soap, /obj/item/sensor_device, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 50, "rad" = 0, "fire" = 50, "acid" = 50) - togglename = "buttons" - species_exception = list(/datum/species/golem) - -/obj/item/clothing/suit/toggle/labcoat/cmo - name = "chief medical officer's labcoat" - desc = "Bluer than the standard model." - icon_state = "labcoat_cmo" - item_state = "labcoat_cmo" - -/obj/item/clothing/suit/toggle/labcoat/paramedic - name = "paramedic's jacket" - desc = "A dark blue jacket for paramedics with reflective stripes." - icon_state = "labcoat_paramedic" - item_state = "labcoat_paramedic" - -/obj/item/clothing/suit/toggle/labcoat/brig_phys - name = "security medic's labcoat" - armor = list(melee = 10, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 10, rad = 0, fire = 50, acid = 50) - -/obj/item/clothing/suit/toggle/labcoat/mad - name = "\proper The Mad's labcoat" - desc = "It makes you look capable of konking someone on the noggin and shooting them into space." - icon_state = "labgreen" - item_state = "labgreen" - -/obj/item/clothing/suit/toggle/labcoat/genetics - name = "geneticist labcoat" - desc = "A suit that protects against minor chemical spills. Has a blue stripe on the shoulder." - icon_state = "labcoat_gen" - -/obj/item/clothing/suit/toggle/labcoat/chemist - name = "chemist labcoat" - desc = "A suit that protects against minor chemical spills. Has an orange stripe on the shoulder." - icon_state = "labcoat_chem" - -/obj/item/clothing/suit/toggle/labcoat/virologist - name = "virologist labcoat" - desc = "A suit that protects against minor chemical spills. Offers slightly more protection against biohazards than the standard model. Has a green stripe on the shoulder." - icon_state = "labcoat_vir" - -/obj/item/clothing/suit/toggle/labcoat/science - name = "scientist labcoat" - desc = "A suit that protects against minor chemical spills. Has a purple stripe on the shoulder." - icon_state = "labcoat_tox" +/obj/item/clothing/suit/toggle/labcoat + name = "labcoat" + desc = "A suit that protects against minor chemical spills." + icon_state = "labcoat" + item_state = "labcoat" + blood_overlay_type = "coat" + body_parts_covered = CHEST|ARMS + allowed = list(/obj/item/analyzer, /obj/item/stack/medical, /obj/item/dnainjector, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/hypospray, /obj/item/healthanalyzer, /obj/item/flashlight/pen, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/pill, /obj/item/storage/pill_bottle, /obj/item/paper, /obj/item/melee/classic_baton/telescopic, /obj/item/soap, /obj/item/sensor_device, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 50, "rad" = 0, "fire" = 50, "acid" = 50) + togglename = "buttons" + species_exception = list(/datum/species/golem) + +/obj/item/clothing/suit/toggle/labcoat/cmo + name = "chief medical officer's labcoat" + desc = "Bluer than the standard model." + icon_state = "labcoat_cmo" + item_state = "labcoat_cmo" + +/obj/item/clothing/suit/toggle/labcoat/paramedic + name = "paramedic's jacket" + desc = "A dark blue jacket for paramedics with reflective stripes." + icon_state = "labcoat_paramedic" + item_state = "labcoat_paramedic" + +/obj/item/clothing/suit/toggle/labcoat/brig_phys + name = "security medic's labcoat" + armor = list(melee = 10, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 10, rad = 0, fire = 50, acid = 50) + +/obj/item/clothing/suit/toggle/labcoat/mad + name = "\proper The Mad's labcoat" + desc = "It makes you look capable of konking someone on the noggin and shooting them into space." + icon_state = "labgreen" + item_state = "labgreen" + +/obj/item/clothing/suit/toggle/labcoat/genetics + name = "geneticist labcoat" + desc = "A suit that protects against minor chemical spills. Has a blue stripe on the shoulder." + icon_state = "labcoat_gen" + +/obj/item/clothing/suit/toggle/labcoat/chemist + name = "chemist labcoat" + desc = "A suit that protects against minor chemical spills. Has an orange stripe on the shoulder." + icon_state = "labcoat_chem" + +/obj/item/clothing/suit/toggle/labcoat/virologist + name = "virologist labcoat" + desc = "A suit that protects against minor chemical spills. Offers slightly more protection against biohazards than the standard model. Has a green stripe on the shoulder." + icon_state = "labcoat_vir" + +/obj/item/clothing/suit/toggle/labcoat/science + name = "scientist labcoat" + desc = "A suit that protects against minor chemical spills. Has a purple stripe on the shoulder." + icon_state = "labcoat_tox" diff --git a/code/modules/clothing/suits/miscellaneous.dm b/code/modules/clothing/suits/miscellaneous.dm index 1822caf09086..f08c7485b7b8 100644 --- a/code/modules/clothing/suits/miscellaneous.dm +++ b/code/modules/clothing/suits/miscellaneous.dm @@ -1,798 +1,798 @@ -/* - * Contains: - * Lasertag - * Costume - * Misc - */ - -/* - * Lasertag - */ -/obj/item/clothing/suit/bluetag - name = "blue laser tag armor" - desc = "A piece of plastic armor. It has sensors that react to red light." //Lasers are concentrated light - icon_state = "bluetag" - item_state = "bluetag" - blood_overlay_type = "armor" - body_parts_covered = CHEST - allowed = list (/obj/item/gun/energy/laser/bluetag) - resistance_flags = NONE - -/obj/item/clothing/suit/redtag - name = "red laser tag armor" - desc = "A piece of plastic armor. It has sensors that react to blue light." - icon_state = "redtag" - item_state = "redtag" - blood_overlay_type = "armor" - body_parts_covered = CHEST - allowed = list (/obj/item/gun/energy/laser/redtag) - resistance_flags = NONE - -/* - * Costume - */ -/obj/item/clothing/suit/hooded/flashsuit - name = "flashy costume" - desc = "What did you expect?" - icon_state = "flashsuit" - item_state = "armor" - body_parts_covered = CHEST|GROIN - hoodtype = /obj/item/clothing/head/hooded/flashsuit - -/obj/item/clothing/head/hooded/flashsuit - name = "flash button" - desc = "You will learn to fear the flash." - icon_state = "flashsuit" - body_parts_covered = HEAD - flags_inv = HIDEHAIR|HIDEEARS|HIDEFACIALHAIR|HIDEFACE|HIDEMASK - -/obj/item/clothing/suit/pirate - name = "pirate coat" - desc = "Yarr." - icon_state = "pirate" - item_state = "pirate" - allowed = list(/obj/item/melee/transforming/energy/sword/pirate, /obj/item/clothing/glasses/eyepatch, /obj/item/reagent_containers/food/drinks/bottle/rum) - -/obj/item/clothing/suit/pirate/captain - name = "pirate captain coat" - desc = "Yarr." - icon_state = "hgpirate" - item_state = "hgpirate" - - -/obj/item/clothing/suit/cyborg_suit - name = "cyborg suit" - desc = "Suit for a cyborg costume." - icon_state = "death" - item_state = "death" - flags_1 = CONDUCT_1 - fire_resist = T0C+5200 - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT - - -/obj/item/clothing/suit/justice - name = "justice suit" - desc = "this pretty much looks ridiculous" //Needs no fixing - icon_state = "justice" - item_state = "justice" - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT - armor = list("melee" = 35, "bullet" = 30, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - - -/obj/item/clothing/suit/judgerobe - name = "judge's robe" - desc = "This robe commands authority." - icon_state = "judge" - item_state = "judge" - body_parts_covered = CHEST|GROIN|LEGS|ARMS - allowed = list(/obj/item/storage/fancy/cigarettes, /obj/item/stack/spacecash) - flags_inv = HIDEJUMPSUIT - - -/obj/item/clothing/suit/apron/overalls - name = "coveralls" - desc = "A set of denim overalls." - icon_state = "overalls" - item_state = "overalls" - body_parts_covered = CHEST|GROIN|LEGS - -/obj/item/clothing/suit/apron/purple_bartender - name = "purple bartender apron" - desc = "A fancy purple apron for a stylish person." - icon_state = "purplebartenderapron" - item_state = "purplebartenderapron" - body_parts_covered = CHEST|GROIN|LEGS - -/obj/item/clothing/suit/syndicatefake - name = "black and red space suit replica" - icon_state = "syndicate-black-red" - item_state = "syndicate-black-red" - desc = "A plastic replica of the Syndicate space suit. You'll look just like a real murderous Syndicate agent in this! This is a toy, it is not made for use in space!" - w_class = WEIGHT_CLASS_NORMAL - allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/toy) - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT - resistance_flags = NONE - -/obj/item/clothing/suit/hastur - name = "\improper Hastur's robe" - desc = "Robes not meant to be worn by man." - icon_state = "hastur" - item_state = "hastur" - body_parts_covered = CHEST|GROIN|LEGS|ARMS - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT - - -/obj/item/clothing/suit/imperium_monk - name = "\improper Imperium monk suit" - desc = "Have YOU killed a xeno today?" - icon_state = "imperium_monk" - item_state = "imperium_monk" - body_parts_covered = CHEST|GROIN|LEGS|ARMS - flags_inv = HIDESHOES|HIDEJUMPSUIT - allowed = list(/obj/item/storage/book/bible, /obj/item/nullrod, /obj/item/reagent_containers/food/drinks/bottle/holywater, /obj/item/storage/fancy/candle_box, /obj/item/candle, /obj/item/tank/internals/emergency_oxygen) - - -/obj/item/clothing/suit/chickensuit - name = "chicken suit" - desc = "A suit made long ago by the ancient empire KFC." - icon_state = "chickensuit" - item_state = "chickensuit" - body_parts_covered = CHEST|ARMS|GROIN|LEGS|FEET - flags_inv = HIDESHOES|HIDEJUMPSUIT - - -/obj/item/clothing/suit/monkeysuit - name = "monkey suit" - desc = "A suit that looks like a primate." - icon_state = "monkeysuit" - item_state = "monkeysuit" - body_parts_covered = CHEST|ARMS|GROIN|LEGS|FEET|HANDS - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT - -/obj/item/clothing/suit/toggle/owlwings - name = "owl cloak" - desc = "A soft brown cloak made of synthetic feathers. Soft to the touch, stylish, and a 2 meter wing span that will drive the ladies mad." - icon_state = "owl_wings" - item_state = "owl_wings" - togglename = "wings" - body_parts_covered = ARMS|CHEST - actions_types = list(/datum/action/item_action/toggle_wings) - -/obj/item/clothing/suit/toggle/owlwings/Initialize() - . = ..() - allowed = GLOB.security_vest_allowed - -/obj/item/clothing/suit/toggle/owlwings/griffinwings - name = "griffon cloak" - desc = "A plush white cloak made of synthetic feathers. Soft to the touch, stylish, and a 2 meter wing span that will drive your captives mad." - icon_state = "griffin_wings" - item_state = "griffin_wings" - -/obj/item/clothing/suit/cardborg - name = "cardborg suit" - desc = "An ordinary cardboard box with holes cut in the sides." - icon_state = "cardborg" - item_state = "cardborg" - body_parts_covered = CHEST|GROIN - flags_inv = HIDEJUMPSUIT - dog_fashion = /datum/dog_fashion/back - -/obj/item/clothing/suit/cardborg/equipped(mob/living/user, slot) - ..() - if(slot == ITEM_SLOT_OCLOTHING) - disguise(user) - -/obj/item/clothing/suit/cardborg/dropped(mob/living/user) - ..() - user.remove_alt_appearance("standard_borg_disguise") - -/obj/item/clothing/suit/cardborg/proc/disguise(mob/living/carbon/human/H, obj/item/clothing/head/cardborg/borghead) - if(istype(H)) - if(!borghead) - borghead = H.head - if(istype(borghead, /obj/item/clothing/head/cardborg)) //why is this done this way? because equipped() is called BEFORE THE ITEM IS IN THE SLOT WHYYYY - var/image/I = image(icon = 'icons/mob/robots.dmi' , icon_state = "robot", loc = H) - I.override = 1 - I.add_overlay(mutable_appearance('icons/mob/robots.dmi', "robot_e")) //gotta look realistic - add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/silicons, "standard_borg_disguise", I) //you look like a robot to robots! (including yourself because you're totally a robot) - - -/obj/item/clothing/suit/snowman - name = "snowman outfit" - desc = "Two white spheres covered in white glitter. 'Tis the season." - icon_state = "snowman" - item_state = "snowman" - body_parts_covered = CHEST|GROIN - flags_inv = HIDEJUMPSUIT - -/obj/item/clothing/suit/poncho - name = "poncho" - desc = "Your classic, non-racist poncho." - icon_state = "classicponcho" - item_state = "classicponcho" - -/obj/item/clothing/suit/poncho/green - name = "green poncho" - desc = "Your classic, non-racist poncho. This one is green." - icon_state = "greenponcho" - item_state = "greenponcho" - -/obj/item/clothing/suit/poncho/red - name = "red poncho" - desc = "Your classic, non-racist poncho. This one is red." - icon_state = "redponcho" - item_state = "redponcho" - -/obj/item/clothing/suit/poncho/ponchoshame - name = "poncho of shame" - desc = "Forced to live on your shameful acting as a fake Mexican, you and your poncho have grown inseparable. Literally." - icon_state = "ponchoshame" - item_state = "ponchoshame" - -/obj/item/clothing/suit/poncho/ponchoshame/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, SHAMEBRERO_TRAIT) - -/obj/item/clothing/suit/whitedress - name = "white dress" - desc = "A fancy white dress." - icon_state = "white_dress" - item_state = "w_suit" - body_parts_covered = CHEST|GROIN|LEGS|FEET - flags_inv = HIDEJUMPSUIT|HIDESHOES - -/obj/item/clothing/suit/hooded/carp_costume - name = "carp costume" - desc = "A costume made from 'synthetic' carp scales, it smells." - icon_state = "carp_casual" - item_state = "labcoat" - body_parts_covered = CHEST|GROIN|ARMS - cold_protection = CHEST|GROIN|ARMS - min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT //Space carp like space, so you should too - allowed = list(/obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/pneumatic_cannon/speargun) - hoodtype = /obj/item/clothing/head/hooded/carp_hood - -/obj/item/clothing/head/hooded/carp_hood - name = "carp hood" - desc = "A hood attached to a carp costume." - icon_state = "carp_casual" - body_parts_covered = HEAD - cold_protection = HEAD - min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT - flags_inv = HIDEHAIR|HIDEEARS - -/obj/item/clothing/head/hooded/carp_hood/equipped(mob/living/carbon/human/user, slot) - ..() - if (slot == ITEM_SLOT_HEAD) - user.faction |= "carp" - -/obj/item/clothing/head/hooded/carp_hood/dropped(mob/living/carbon/human/user) - ..() - if (user.head == src) - user.faction -= "carp" - -/obj/item/clothing/suit/hooded/ian_costume //It's Ian, rub his bell- oh god what happened to his inside parts? - name = "corgi costume" - desc = "A costume that looks like someone made a human-like corgi, it won't guarantee belly rubs." - icon_state = "ian" - item_state = "labcoat" - body_parts_covered = CHEST|GROIN|ARMS - //cold_protection = CHEST|GROIN|ARMS - //min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT - allowed = list() - hoodtype = /obj/item/clothing/head/hooded/ian_hood - dog_fashion = /datum/dog_fashion/back - -/obj/item/clothing/head/hooded/ian_hood - name = "corgi hood" - desc = "A hood that looks just like a corgi's head, it won't guarantee dog biscuits." - icon_state = "ian" - body_parts_covered = HEAD - //cold_protection = HEAD - //min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT - flags_inv = HIDEHAIR|HIDEEARS - -/obj/item/clothing/suit/hooded/bee_costume // It's Hip! - name = "bee costume" - desc = "Bee the true Queen!" - icon_state = "bee" - item_state = "labcoat" - body_parts_covered = CHEST|GROIN|ARMS - clothing_flags = THICKMATERIAL - hoodtype = /obj/item/clothing/head/hooded/bee_hood - -/obj/item/clothing/head/hooded/bee_hood - name = "bee hood" - desc = "A hood attached to a bee costume." - icon_state = "bee" - body_parts_covered = HEAD - clothing_flags = THICKMATERIAL - flags_inv = HIDEHAIR|HIDEEARS - dynamic_hair_suffix = "" - -/obj/item/clothing/suit/hooded/bloated_human //OH MY GOD WHAT HAVE YOU DONE!?!?!? - name = "bloated human suit" - desc = "A horribly bloated suit made from human skins." - icon_state = "lingspacesuit" - item_state = "labcoat" - body_parts_covered = CHEST|GROIN|ARMS - allowed = list() - actions_types = list(/datum/action/item_action/toggle_human_head) - hoodtype = /obj/item/clothing/head/hooded/human_head - - -/obj/item/clothing/head/hooded/human_head - name = "bloated human head" - desc = "A horribly bloated and mismatched human head." - icon_state = "lingspacehelmet" - body_parts_covered = HEAD - flags_cover = HEADCOVERSEYES - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - -/obj/item/clothing/suit/security/officer/russian - name = "\improper Russian officer's jacket" - desc = "This jacket is for those special occasions when a russian officer isn't required to wear their armor." - icon_state = "officertanjacket" - item_state = "officertanjacket" - body_parts_covered = CHEST|ARMS - -/obj/item/clothing/suit/shrine_maiden - name = "shrine maiden's outfit" - desc = "Makes you want to exterminate some troublesome youkai." - icon_state = "shrine_maiden" - item_state = "shrine_maiden" - body_parts_covered = CHEST|GROIN|LEGS|ARMS - flags_inv = HIDEJUMPSUIT - -/* - * Misc - */ - -/obj/item/clothing/suit/straight_jacket - name = "straight jacket" - desc = "A suit that completely restrains the wearer. Manufactured by Antyphun Corp." //Straight jacket is antifun - icon_state = "straight_jacket" - item_state = "straight_jacket" - body_parts_covered = CHEST|GROIN|LEGS|ARMS|HANDS - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT - equip_delay_self = 50 - strip_delay = 60 - breakouttime = 3000 - pocket_storage_component_path = FALSE - -/obj/item/clothing/suit/ianshirt - name = "worn shirt" - desc = "A worn out, curiously comfortable t-shirt with a picture of Ian. You wouldn't go so far as to say it feels like being hugged when you wear it, but it's pretty close. Good for sleeping in." - icon_state = "ianshirt" - item_state = "ianshirt" - -/obj/item/clothing/suit/nerdshirt - name = "gamer shirt" - desc = "A baggy shirt with vintage game character Phanic the Weasel. Why would anyone wear this?" - icon_state = "nerdshirt" - item_state = "nerdshirt" - -/obj/item/clothing/suit/vapeshirt //wearing this is asking to get beat. - name = "Vape Naysh shirt" - desc = "A cheap white T-shirt with a big tacky \"VN\" on the front, Why would you wear this unironically?" - icon_state = "vapeshirt" - item_state = "vapeshirt" - -/obj/item/clothing/suit/striped_sweater - name = "striped sweater" - desc = "Reminds you of someone, but you just can't put your finger on it..." - icon_state = "waldo_shirt" - item_state = "waldo_shirt" - -/obj/item/clothing/suit/jacket - name = "bomber jacket" - desc = "Aviators not included." - icon_state = "bomberjacket" - item_state = "brownjsuit" - allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/radio) - body_parts_covered = CHEST|GROIN|ARMS - cold_protection = CHEST|GROIN|ARMS - min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT - -/obj/item/clothing/suit/jacket/leather - name = "leather jacket" - desc = "Pompadour not included." - icon_state = "leatherjacket" - item_state = "hostrench" - resistance_flags = NONE - max_heat_protection_temperature = ARMOR_MAX_TEMP_PROTECT - allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/gun/ballistic/automatic/pistol, /obj/item/gun/ballistic/revolver, /obj/item/gun/ballistic/revolver/detective, /obj/item/radio) - -/obj/item/clothing/suit/jacket/leather/overcoat - name = "leather overcoat" - desc = "That's a damn fine coat." - icon_state = "leathercoat" - body_parts_covered = CHEST|GROIN|ARMS|LEGS - cold_protection = CHEST|GROIN|ARMS|LEGS - -/obj/item/clothing/suit/jacket/puffer - name = "puffer jacket" - desc = "A thick jacket with a rubbery, water-resistant shell." - icon_state = "pufferjacket" - item_state = "hostrench" - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 50, "rad" = 0, "fire" = 0, "acid" = 0) - -/obj/item/clothing/suit/jacket/puffer/vest - name = "puffer vest" - desc = "A thick vest with a rubbery, water-resistant shell." - icon_state = "puffervest" - item_state = "armor" - body_parts_covered = CHEST|GROIN - cold_protection = CHEST|GROIN - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 30, "rad" = 0, "fire" = 0, "acid" = 0) - -/obj/item/clothing/suit/jacket/miljacket - name = "military jacket" - desc = "A canvas jacket styled after classical American military garb. Feels sturdy, yet comfortable." - icon_state = "militaryjacket" - item_state = "militaryjacket" - allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/gun/ballistic/automatic/pistol, /obj/item/gun/ballistic/revolver, /obj/item/radio) - -/obj/item/clothing/suit/jacket/letterman - name = "letterman jacket" - desc = "A classic brown letterman jacket. Looks pretty hot and heavy." - icon_state = "letterman" - item_state = "letterman" - -/obj/item/clothing/suit/jacket/letterman_red - name = "red letterman jacket" - desc = "A letterman jacket in a sick red color. Radical." - icon_state = "letterman_red" - item_state = "letterman_red" - -/obj/item/clothing/suit/jacket/letterman_syndie - name = "blood-red letterman jacket" - desc = "Oddly, this jacket seems to have a large S on the back..." - icon_state = "letterman_s" - item_state = "letterman_s" - -/obj/item/clothing/suit/jacket/letterman_nanotrasen - name = "blue letterman jacket" - desc = "A blue letterman jacket with a proud Nanotrasen N on the back. The tag says that it was made in Space China." - icon_state = "letterman_n" - item_state = "letterman_n" - -/obj/item/clothing/suit/dracula - name = "dracula coat" - desc = "Looks like this belongs in a very old movie set." - icon_state = "draculacoat" - item_state = "draculacoat" - -/obj/item/clothing/suit/drfreeze_coat - name = "doctor freeze's labcoat" - desc = "A labcoat imbued with the power of features and freezes." - icon_state = "drfreeze_coat" - item_state = "drfreeze_coat" - -/obj/item/clothing/suit/gothcoat - name = "gothic coat" - desc = "Perfect for those who want to stalk around a corner of a bar." - icon_state = "gothcoat" - item_state = "gothcoat" - -/obj/item/clothing/suit/xenos - name = "xenos suit" - desc = "A suit made out of chitinous alien hide." - icon_state = "xenos" - item_state = "xenos_helm" - body_parts_covered = CHEST|GROIN|LEGS|ARMS|HANDS - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT - allowed = list(/obj/item/clothing/mask/facehugger/toy) - -/obj/item/clothing/suit/nemes - name = "pharoah tunic" - desc = "Lavish space tomb not included." - icon_state = "pharoah" - item_state = "pharoah" - body_parts_covered = CHEST|GROIN - -/obj/item/clothing/suit/caution - name = "wet floor sign" - desc = "Caution! Wet Floor!" - icon_state = "caution" - lefthand_file = 'icons/mob/inhands/equipment/custodial_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/custodial_righthand.dmi' - force = 1 - throwforce = 3 - throw_speed = 2 - throw_range = 5 - w_class = WEIGHT_CLASS_SMALL - body_parts_covered = CHEST|GROIN - attack_verb = list("warned", "cautioned", "smashed") - armor = list("melee" = 5, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - -/obj/item/clothing/suit/changshan_red - name = "red changshan" - desc = "A gorgeously embroidered silk shirt." - icon_state = "changshan_red" - item_state = "changshan_red" - body_parts_covered = CHEST|GROIN|ARMS|LEGS - -/obj/item/clothing/suit/changshan_blue - name = "blue changshan" - desc = "A gorgeously embroidered silk shirt." - icon_state = "changshan_blue" - item_state = "changshan_blue" - body_parts_covered = CHEST|GROIN|ARMS|LEGS - -/obj/item/clothing/suit/cheongsam_red - name = "red cheongsam" - desc = "A gorgeously embroidered silk dress." - icon_state = "cheongsam_red" - item_state = "cheongsam_red" - body_parts_covered = CHEST|GROIN|ARMS|LEGS - -/obj/item/clothing/suit/cheongsam_blue - name = "blue cheongsam" - desc = "A gorgeously embroidered silk dress." - icon_state = "cheongsam_blue" - item_state = "cheongsam_blue" - body_parts_covered = CHEST|GROIN|ARMS|LEGS - -// WINTER COATS - -/obj/item/clothing/suit/hooded/wintercoat - name = "winter coat" - desc = "A heavy jacket made from 'synthetic' animal furs." - icon_state = "coatwinter" - item_state = "coatwinter" - body_parts_covered = CHEST|GROIN|ARMS - cold_protection = CHEST|GROIN|ARMS - min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) - allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter) - -/obj/item/clothing/head/hooded/winterhood - name = "winter hood" - desc = "A hood attached to a heavy winter jacket." - icon_state = "winterhood" - body_parts_covered = HEAD - cold_protection = HEAD - min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT - flags_inv = HIDEHAIR|HIDEEARS - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) - -/obj/item/clothing/suit/hooded/wintercoat/captain - name = "captain's winter coat" - icon_state = "coatcaptain" - item_state = "coatcaptain" - armor = list("melee" = 25, "bullet" = 30, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 50) - hoodtype = /obj/item/clothing/head/hooded/winterhood/captain - -/obj/item/clothing/suit/hooded/wintercoat/captain/Initialize() - . = ..() - allowed = GLOB.security_wintercoat_allowed - -/obj/item/clothing/head/hooded/winterhood/captain - icon_state = "winterhood_captain" - armor = list("melee" = 25, "bullet" = 30, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 50) - -/obj/item/clothing/suit/hooded/wintercoat/security - name = "security winter coat" - icon_state = "coatsecurity" - item_state = "coatsecurity" - armor = list("melee" = 25, "bullet" = 15, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 45) - hoodtype = /obj/item/clothing/head/hooded/winterhood/security - -/obj/item/clothing/suit/hooded/wintercoat/security/Initialize() - . = ..() - allowed = GLOB.security_wintercoat_allowed - -/obj/item/clothing/head/hooded/winterhood/security - icon_state = "winterhood_security" - armor = list("melee" = 25, "bullet" = 15, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 45) - -/obj/item/clothing/suit/hooded/wintercoat/medical - name = "medical winter coat" - icon_state = "coatmedical" - item_state = "coatmedical" - allowed = list(/obj/item/analyzer, /obj/item/sensor_device, /obj/item/stack/medical, /obj/item/dnainjector, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/hypospray, /obj/item/healthanalyzer, /obj/item/flashlight/pen, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/pill, /obj/item/storage/pill_bottle, /obj/item/paper, /obj/item/melee/classic_baton/telescopic, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 50, "rad" = 0, "fire" = 0, "acid" = 45) - hoodtype = /obj/item/clothing/head/hooded/winterhood/medical - -/obj/item/clothing/head/hooded/winterhood/medical - icon_state = "winterhood_medical" - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 50, "rad" = 0, "fire" = 0, "acid" = 45) - -/obj/item/clothing/suit/hooded/wintercoat/science - name = "science winter coat" - icon_state = "coatscience" - item_state = "coatscience" - allowed = list(/obj/item/analyzer, /obj/item/stack/medical, /obj/item/dnainjector, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/hypospray, /obj/item/healthanalyzer, /obj/item/flashlight/pen, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/pill, /obj/item/storage/pill_bottle, /obj/item/paper, /obj/item/melee/classic_baton/telescopic, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - hoodtype = /obj/item/clothing/head/hooded/winterhood/science - -/obj/item/clothing/head/hooded/winterhood/science - icon_state = "winterhood_science" - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - -/obj/item/clothing/suit/hooded/wintercoat/engineering - name = "engineering winter coat" - icon_state = "coatengineer" - item_state = "coatengineer" - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 20, "fire" = 30, "acid" = 45) - allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/t_scanner, /obj/item/construction/rcd, /obj/item/pipe_dispenser, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter) - hoodtype = /obj/item/clothing/head/hooded/winterhood/engineering - -/obj/item/clothing/head/hooded/winterhood/engineering - icon_state = "winterhood_engineer" - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 20, "fire" = 30, "acid" = 45) - -/obj/item/clothing/suit/hooded/wintercoat/engineering/atmos - name = "atmospherics winter coat" - icon_state = "coatatmos" - item_state = "coatatmos" - hoodtype = /obj/item/clothing/head/hooded/winterhood/engineering/atmos - -/obj/item/clothing/head/hooded/winterhood/engineering/atmos - icon_state = "winterhood_atmos" - -/obj/item/clothing/suit/hooded/wintercoat/hydro - name = "hydroponics winter coat" - icon_state = "coathydro" - item_state = "coathydro" - allowed = list(/obj/item/reagent_containers/spray/plantbgone, /obj/item/plant_analyzer, /obj/item/seeds, /obj/item/reagent_containers/glass/bottle, /obj/item/cultivator, /obj/item/reagent_containers/spray/pestspray, /obj/item/hatchet, /obj/item/storage/bag/plants, /obj/item/toy, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/storage/fancy/cigarettes, /obj/item/lighter) - hoodtype = /obj/item/clothing/head/hooded/winterhood/hydro - -/obj/item/clothing/head/hooded/winterhood/hydro - icon_state = "winterhood_hydro" - -/obj/item/clothing/suit/hooded/wintercoat/cargo - name = "cargo winter coat" - icon_state = "coatcargo" - item_state = "coatcargo" - hoodtype = /obj/item/clothing/head/hooded/winterhood/cargo - -/obj/item/clothing/head/hooded/winterhood/cargo - icon_state = "winterhood_cargo" - -/obj/item/clothing/suit/hooded/wintercoat/miner - name = "mining winter coat" - icon_state = "coatminer" - item_state = "coatminer" - allowed = list(/obj/item/pickaxe, /obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter) - armor = list("melee" = 10, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - hoodtype = /obj/item/clothing/head/hooded/winterhood/miner - -/obj/item/clothing/head/hooded/winterhood/miner - icon_state = "winterhood_miner" - armor = list("melee" = 10, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - -/obj/item/clothing/head/hooded/ablative - name = "ablative hood" - desc = "Hood hopefully belonging to an ablative trenchcoat. Includes a visor for cool-o-vision." - icon_state = "ablativehood" - armor = list("melee" = 10, "bullet" = 10, "laser" = 60, "energy" = 60, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - strip_delay = 30 - var/hit_reflect_chance = 50 - -/obj/item/clothing/head/hooded/ablative/equipped(mob/living/carbon/human/user, slot) - ..() - to_chat(user, "As you put on the hood, a visor shifts into place and starts analyzing the people around you. Neat!") - ADD_TRAIT(user, TRAIT_SECURITY_HUD, HELMET_TRAIT) - var/datum/atom_hud/H = GLOB.huds[DATA_HUD_SECURITY_ADVANCED] - H.add_hud_to(user) - -/obj/item/clothing/head/hooded/ablative/dropped(mob/living/carbon/human/user) - ..() - to_chat(user, "You take off the hood, removing the visor in the process and disabling its integrated hud.") - REMOVE_TRAIT(user, TRAIT_SECURITY_HUD, HELMET_TRAIT) - var/datum/atom_hud/H = GLOB.huds[DATA_HUD_SECURITY_ADVANCED] - H.remove_hud_from(user) - -/obj/item/clothing/head/hooded/ablative/IsReflect(def_zone) - if(def_zone != BODY_ZONE_HEAD) //If not shot where ablative is covering you, you don't get the reflection bonus! - return FALSE - if (prob(hit_reflect_chance)) - return TRUE - -/obj/item/clothing/suit/hooded/ablative - name = "ablative trenchcoat" - desc = "Experimental trenchcoat specially crafted to reflect and absorb laser and disabler shots. Don't expect it to do all that much against an axe or a shotgun, however." - icon_state = "ablativecoat" - item_state = "ablativecoat" - body_parts_covered = CHEST|GROIN|LEGS|ARMS - armor = list("melee" = 10, "bullet" = 10, "laser" = 60, "energy" = 60, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - hoodtype = /obj/item/clothing/head/hooded/ablative - strip_delay = 30 - equip_delay_other = 40 - var/hit_reflect_chance = 50 - -/obj/item/clothing/suit/hooded/ablative/Initialize() - . = ..() - allowed = GLOB.security_vest_allowed - -/obj/item/clothing/suit/hooded/ablative/IsReflect(def_zone) - if(!(def_zone in list(BODY_ZONE_CHEST, BODY_ZONE_PRECISE_GROIN, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG))) //If not shot where ablative is covering you, you don't get the reflection bonus! - return FALSE - if (prob(hit_reflect_chance)) - return TRUE - -/obj/item/clothing/suit/spookyghost - name = "spooky ghost" - desc = "This is obviously just a bedsheet, but maybe try it on?" - icon_state = "bedsheet" - user_vars_to_edit = list("name" = "Spooky Ghost", "real_name" = "Spooky Ghost" , "incorporeal_move" = INCORPOREAL_MOVE_BASIC, "appearance_flags" = KEEP_TOGETHER|TILE_BOUND, "alpha" = 150) - alternate_worn_layer = ABOVE_BODY_FRONT_LAYER //so the bedsheet goes over everything but fire - -/obj/item/clothing/suit/bronze - name = "bronze suit" - desc = "A big and clanky suit made of bronze that offers no protection and looks very unfashionable. Nice." - icon = 'icons/obj/clothing/clockwork_garb.dmi' - icon_state = "clockwork_cuirass_old" - armor = list("melee" = 5, "bullet" = 0, "laser" = -5, "energy" = -15, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 20, "acid" = 20) - -/obj/item/clothing/suit/ghost_sheet - name = "ghost sheet" - desc = "The hands float by themselves, so it's extra spooky." - icon_state = "ghost_sheet" - item_state = "ghost_sheet" - throwforce = 0 - throw_speed = 1 - throw_range = 2 - w_class = WEIGHT_CLASS_TINY - flags_inv = HIDEGLOVES|HIDEEARS|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - alternate_worn_layer = UNDER_HEAD_LAYER - -/obj/item/clothing/suit/toggle/suspenders/blue - name = "blue suspenders" - desc = "The symbol of hard labor and dirty jobs." - icon = 'icons/obj/clothing/belts.dmi' - icon_state = "suspenders_blue" - -/obj/item/clothing/suit/toggle/suspenders/gray - name = "gray suspenders" - desc = "The symbol of hard labor and dirty jobs." - icon = 'icons/obj/clothing/belts.dmi' - icon_state = "suspenders_gray" - -/obj/item/clothing/suit/hooded/mysticrobe - name = "mystic's robe" - desc = "Wearing this makes you feel more attuned with the nature of the universe... as well as a bit more irresponsible. " - icon_state = "mysticrobe" - item_state = "mysticrobe" - body_parts_covered = CHEST|GROIN|LEGS|ARMS - allowed = list(/obj/item/spellbook, /obj/item/storage/book/bible) - flags_inv = HIDEJUMPSUIT - hoodtype = /obj/item/clothing/head/hooded/mysticrobe - -/obj/item/clothing/head/hooded/mysticrobe - name = "mystic's hood" - desc = "The balance of reality tips towards order." - icon_state = "mystichood" - item_state = "mystichood" - body_parts_covered = HEAD - flags_inv = HIDEHAIR|HIDEEARS|HIDEFACIALHAIR|HIDEFACE|HIDEMASK - -/obj/item/clothing/suit/coordinator - name = "coordinator jacket" - desc = "A jacket for a party ooordinator, stylish!." - icon_state = "capformal" - item_state = "capspacesuit" - armor = list("melee" = 25, "bullet" = 15, "laser" = 25, "energy" = 35, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - -/obj/item/clothing/suit/hawaiian - name = "hawaiian overshirt" - desc = "A cool shirt for chilling on the beach." - icon_state = "hawaiian_blue" - item_state = "hawaiian_blue" - -/obj/item/clothing/suit/yakuza - name = "tojo clan jacket" - desc = "The jacket of a mad dog." - icon_state = "MajimaJacket" - item_state = "MajimaJacket" - body_parts_covered = ARMS - -/obj/item/clothing/suit/dutch - name = "dutch's jacket" - desc = "For those long nights on the beach in Tahiti." - icon_state = "DutchJacket" - item_state = "DutchJacket" - body_parts_covered = ARMS - +/* + * Contains: + * Lasertag + * Costume + * Misc + */ + +/* + * Lasertag + */ +/obj/item/clothing/suit/bluetag + name = "blue laser tag armor" + desc = "A piece of plastic armor. It has sensors that react to red light." //Lasers are concentrated light + icon_state = "bluetag" + item_state = "bluetag" + blood_overlay_type = "armor" + body_parts_covered = CHEST + allowed = list (/obj/item/gun/energy/laser/bluetag) + resistance_flags = NONE + +/obj/item/clothing/suit/redtag + name = "red laser tag armor" + desc = "A piece of plastic armor. It has sensors that react to blue light." + icon_state = "redtag" + item_state = "redtag" + blood_overlay_type = "armor" + body_parts_covered = CHEST + allowed = list (/obj/item/gun/energy/laser/redtag) + resistance_flags = NONE + +/* + * Costume + */ +/obj/item/clothing/suit/hooded/flashsuit + name = "flashy costume" + desc = "What did you expect?" + icon_state = "flashsuit" + item_state = "armor" + body_parts_covered = CHEST|GROIN + hoodtype = /obj/item/clothing/head/hooded/flashsuit + +/obj/item/clothing/head/hooded/flashsuit + name = "flash button" + desc = "You will learn to fear the flash." + icon_state = "flashsuit" + body_parts_covered = HEAD + flags_inv = HIDEHAIR|HIDEEARS|HIDEFACIALHAIR|HIDEFACE|HIDEMASK + +/obj/item/clothing/suit/pirate + name = "pirate coat" + desc = "Yarr." + icon_state = "pirate" + item_state = "pirate" + allowed = list(/obj/item/melee/transforming/energy/sword/pirate, /obj/item/clothing/glasses/eyepatch, /obj/item/reagent_containers/food/drinks/bottle/rum) + +/obj/item/clothing/suit/pirate/captain + name = "pirate captain coat" + desc = "Yarr." + icon_state = "hgpirate" + item_state = "hgpirate" + + +/obj/item/clothing/suit/cyborg_suit + name = "cyborg suit" + desc = "Suit for a cyborg costume." + icon_state = "death" + item_state = "death" + flags_1 = CONDUCT_1 + fire_resist = T0C+5200 + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT + + +/obj/item/clothing/suit/justice + name = "justice suit" + desc = "this pretty much looks ridiculous" //Needs no fixing + icon_state = "justice" + item_state = "justice" + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT + armor = list("melee" = 35, "bullet" = 30, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + + +/obj/item/clothing/suit/judgerobe + name = "judge's robe" + desc = "This robe commands authority." + icon_state = "judge" + item_state = "judge" + body_parts_covered = CHEST|GROIN|LEGS|ARMS + allowed = list(/obj/item/storage/fancy/cigarettes, /obj/item/stack/spacecash) + flags_inv = HIDEJUMPSUIT + + +/obj/item/clothing/suit/apron/overalls + name = "coveralls" + desc = "A set of denim overalls." + icon_state = "overalls" + item_state = "overalls" + body_parts_covered = CHEST|GROIN|LEGS + +/obj/item/clothing/suit/apron/purple_bartender + name = "purple bartender apron" + desc = "A fancy purple apron for a stylish person." + icon_state = "purplebartenderapron" + item_state = "purplebartenderapron" + body_parts_covered = CHEST|GROIN|LEGS + +/obj/item/clothing/suit/syndicatefake + name = "black and red space suit replica" + icon_state = "syndicate-black-red" + item_state = "syndicate-black-red" + desc = "A plastic replica of the Syndicate space suit. You'll look just like a real murderous Syndicate agent in this! This is a toy, it is not made for use in space!" + w_class = WEIGHT_CLASS_NORMAL + allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/toy) + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT + resistance_flags = NONE + +/obj/item/clothing/suit/hastur + name = "\improper Hastur's robe" + desc = "Robes not meant to be worn by man." + icon_state = "hastur" + item_state = "hastur" + body_parts_covered = CHEST|GROIN|LEGS|ARMS + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT + + +/obj/item/clothing/suit/imperium_monk + name = "\improper Imperium monk suit" + desc = "Have YOU killed a xeno today?" + icon_state = "imperium_monk" + item_state = "imperium_monk" + body_parts_covered = CHEST|GROIN|LEGS|ARMS + flags_inv = HIDESHOES|HIDEJUMPSUIT + allowed = list(/obj/item/storage/book/bible, /obj/item/nullrod, /obj/item/reagent_containers/food/drinks/bottle/holywater, /obj/item/storage/fancy/candle_box, /obj/item/candle, /obj/item/tank/internals/emergency_oxygen) + + +/obj/item/clothing/suit/chickensuit + name = "chicken suit" + desc = "A suit made long ago by the ancient empire KFC." + icon_state = "chickensuit" + item_state = "chickensuit" + body_parts_covered = CHEST|ARMS|GROIN|LEGS|FEET + flags_inv = HIDESHOES|HIDEJUMPSUIT + + +/obj/item/clothing/suit/monkeysuit + name = "monkey suit" + desc = "A suit that looks like a primate." + icon_state = "monkeysuit" + item_state = "monkeysuit" + body_parts_covered = CHEST|ARMS|GROIN|LEGS|FEET|HANDS + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT + +/obj/item/clothing/suit/toggle/owlwings + name = "owl cloak" + desc = "A soft brown cloak made of synthetic feathers. Soft to the touch, stylish, and a 2 meter wing span that will drive the ladies mad." + icon_state = "owl_wings" + item_state = "owl_wings" + togglename = "wings" + body_parts_covered = ARMS|CHEST + actions_types = list(/datum/action/item_action/toggle_wings) + +/obj/item/clothing/suit/toggle/owlwings/Initialize() + . = ..() + allowed = GLOB.security_vest_allowed + +/obj/item/clothing/suit/toggle/owlwings/griffinwings + name = "griffon cloak" + desc = "A plush white cloak made of synthetic feathers. Soft to the touch, stylish, and a 2 meter wing span that will drive your captives mad." + icon_state = "griffin_wings" + item_state = "griffin_wings" + +/obj/item/clothing/suit/cardborg + name = "cardborg suit" + desc = "An ordinary cardboard box with holes cut in the sides." + icon_state = "cardborg" + item_state = "cardborg" + body_parts_covered = CHEST|GROIN + flags_inv = HIDEJUMPSUIT + dog_fashion = /datum/dog_fashion/back + +/obj/item/clothing/suit/cardborg/equipped(mob/living/user, slot) + ..() + if(slot == ITEM_SLOT_OCLOTHING) + disguise(user) + +/obj/item/clothing/suit/cardborg/dropped(mob/living/user) + ..() + user.remove_alt_appearance("standard_borg_disguise") + +/obj/item/clothing/suit/cardborg/proc/disguise(mob/living/carbon/human/H, obj/item/clothing/head/cardborg/borghead) + if(istype(H)) + if(!borghead) + borghead = H.head + if(istype(borghead, /obj/item/clothing/head/cardborg)) //why is this done this way? because equipped() is called BEFORE THE ITEM IS IN THE SLOT WHYYYY + var/image/I = image(icon = 'icons/mob/robots.dmi' , icon_state = "robot", loc = H) + I.override = 1 + I.add_overlay(mutable_appearance('icons/mob/robots.dmi', "robot_e")) //gotta look realistic + add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/silicons, "standard_borg_disguise", I) //you look like a robot to robots! (including yourself because you're totally a robot) + + +/obj/item/clothing/suit/snowman + name = "snowman outfit" + desc = "Two white spheres covered in white glitter. 'Tis the season." + icon_state = "snowman" + item_state = "snowman" + body_parts_covered = CHEST|GROIN + flags_inv = HIDEJUMPSUIT + +/obj/item/clothing/suit/poncho + name = "poncho" + desc = "Your classic, non-racist poncho." + icon_state = "classicponcho" + item_state = "classicponcho" + +/obj/item/clothing/suit/poncho/green + name = "green poncho" + desc = "Your classic, non-racist poncho. This one is green." + icon_state = "greenponcho" + item_state = "greenponcho" + +/obj/item/clothing/suit/poncho/red + name = "red poncho" + desc = "Your classic, non-racist poncho. This one is red." + icon_state = "redponcho" + item_state = "redponcho" + +/obj/item/clothing/suit/poncho/ponchoshame + name = "poncho of shame" + desc = "Forced to live on your shameful acting as a fake Mexican, you and your poncho have grown inseparable. Literally." + icon_state = "ponchoshame" + item_state = "ponchoshame" + +/obj/item/clothing/suit/poncho/ponchoshame/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, SHAMEBRERO_TRAIT) + +/obj/item/clothing/suit/whitedress + name = "white dress" + desc = "A fancy white dress." + icon_state = "white_dress" + item_state = "w_suit" + body_parts_covered = CHEST|GROIN|LEGS|FEET + flags_inv = HIDEJUMPSUIT|HIDESHOES + +/obj/item/clothing/suit/hooded/carp_costume + name = "carp costume" + desc = "A costume made from 'synthetic' carp scales, it smells." + icon_state = "carp_casual" + item_state = "labcoat" + body_parts_covered = CHEST|GROIN|ARMS + cold_protection = CHEST|GROIN|ARMS + min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT //Space carp like space, so you should too + allowed = list(/obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/pneumatic_cannon/speargun) + hoodtype = /obj/item/clothing/head/hooded/carp_hood + +/obj/item/clothing/head/hooded/carp_hood + name = "carp hood" + desc = "A hood attached to a carp costume." + icon_state = "carp_casual" + body_parts_covered = HEAD + cold_protection = HEAD + min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT + flags_inv = HIDEHAIR|HIDEEARS + +/obj/item/clothing/head/hooded/carp_hood/equipped(mob/living/carbon/human/user, slot) + ..() + if (slot == ITEM_SLOT_HEAD) + user.faction |= "carp" + +/obj/item/clothing/head/hooded/carp_hood/dropped(mob/living/carbon/human/user) + ..() + if (user.head == src) + user.faction -= "carp" + +/obj/item/clothing/suit/hooded/ian_costume //It's Ian, rub his bell- oh god what happened to his inside parts? + name = "corgi costume" + desc = "A costume that looks like someone made a human-like corgi, it won't guarantee belly rubs." + icon_state = "ian" + item_state = "labcoat" + body_parts_covered = CHEST|GROIN|ARMS + //cold_protection = CHEST|GROIN|ARMS + //min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT + allowed = list() + hoodtype = /obj/item/clothing/head/hooded/ian_hood + dog_fashion = /datum/dog_fashion/back + +/obj/item/clothing/head/hooded/ian_hood + name = "corgi hood" + desc = "A hood that looks just like a corgi's head, it won't guarantee dog biscuits." + icon_state = "ian" + body_parts_covered = HEAD + //cold_protection = HEAD + //min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT + flags_inv = HIDEHAIR|HIDEEARS + +/obj/item/clothing/suit/hooded/bee_costume // It's Hip! + name = "bee costume" + desc = "Bee the true Queen!" + icon_state = "bee" + item_state = "labcoat" + body_parts_covered = CHEST|GROIN|ARMS + clothing_flags = THICKMATERIAL + hoodtype = /obj/item/clothing/head/hooded/bee_hood + +/obj/item/clothing/head/hooded/bee_hood + name = "bee hood" + desc = "A hood attached to a bee costume." + icon_state = "bee" + body_parts_covered = HEAD + clothing_flags = THICKMATERIAL + flags_inv = HIDEHAIR|HIDEEARS + dynamic_hair_suffix = "" + +/obj/item/clothing/suit/hooded/bloated_human //OH MY GOD WHAT HAVE YOU DONE!?!?!? + name = "bloated human suit" + desc = "A horribly bloated suit made from human skins." + icon_state = "lingspacesuit" + item_state = "labcoat" + body_parts_covered = CHEST|GROIN|ARMS + allowed = list() + actions_types = list(/datum/action/item_action/toggle_human_head) + hoodtype = /obj/item/clothing/head/hooded/human_head + + +/obj/item/clothing/head/hooded/human_head + name = "bloated human head" + desc = "A horribly bloated and mismatched human head." + icon_state = "lingspacehelmet" + body_parts_covered = HEAD + flags_cover = HEADCOVERSEYES + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + +/obj/item/clothing/suit/security/officer/russian + name = "\improper Russian officer's jacket" + desc = "This jacket is for those special occasions when a russian officer isn't required to wear their armor." + icon_state = "officertanjacket" + item_state = "officertanjacket" + body_parts_covered = CHEST|ARMS + +/obj/item/clothing/suit/shrine_maiden + name = "shrine maiden's outfit" + desc = "Makes you want to exterminate some troublesome youkai." + icon_state = "shrine_maiden" + item_state = "shrine_maiden" + body_parts_covered = CHEST|GROIN|LEGS|ARMS + flags_inv = HIDEJUMPSUIT + +/* + * Misc + */ + +/obj/item/clothing/suit/straight_jacket + name = "straight jacket" + desc = "A suit that completely restrains the wearer. Manufactured by Antyphun Corp." //Straight jacket is antifun + icon_state = "straight_jacket" + item_state = "straight_jacket" + body_parts_covered = CHEST|GROIN|LEGS|ARMS|HANDS + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT + equip_delay_self = 50 + strip_delay = 60 + breakouttime = 3000 + pocket_storage_component_path = FALSE + +/obj/item/clothing/suit/ianshirt + name = "worn shirt" + desc = "A worn out, curiously comfortable t-shirt with a picture of Ian. You wouldn't go so far as to say it feels like being hugged when you wear it, but it's pretty close. Good for sleeping in." + icon_state = "ianshirt" + item_state = "ianshirt" + +/obj/item/clothing/suit/nerdshirt + name = "gamer shirt" + desc = "A baggy shirt with vintage game character Phanic the Weasel. Why would anyone wear this?" + icon_state = "nerdshirt" + item_state = "nerdshirt" + +/obj/item/clothing/suit/vapeshirt //wearing this is asking to get beat. + name = "Vape Naysh shirt" + desc = "A cheap white T-shirt with a big tacky \"VN\" on the front, Why would you wear this unironically?" + icon_state = "vapeshirt" + item_state = "vapeshirt" + +/obj/item/clothing/suit/striped_sweater + name = "striped sweater" + desc = "Reminds you of someone, but you just can't put your finger on it..." + icon_state = "waldo_shirt" + item_state = "waldo_shirt" + +/obj/item/clothing/suit/jacket + name = "bomber jacket" + desc = "Aviators not included." + icon_state = "bomberjacket" + item_state = "brownjsuit" + allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/radio) + body_parts_covered = CHEST|GROIN|ARMS + cold_protection = CHEST|GROIN|ARMS + min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT + +/obj/item/clothing/suit/jacket/leather + name = "leather jacket" + desc = "Pompadour not included." + icon_state = "leatherjacket" + item_state = "hostrench" + resistance_flags = NONE + max_heat_protection_temperature = ARMOR_MAX_TEMP_PROTECT + allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/gun/ballistic/automatic/pistol, /obj/item/gun/ballistic/revolver, /obj/item/gun/ballistic/revolver/detective, /obj/item/radio) + +/obj/item/clothing/suit/jacket/leather/overcoat + name = "leather overcoat" + desc = "That's a damn fine coat." + icon_state = "leathercoat" + body_parts_covered = CHEST|GROIN|ARMS|LEGS + cold_protection = CHEST|GROIN|ARMS|LEGS + +/obj/item/clothing/suit/jacket/puffer + name = "puffer jacket" + desc = "A thick jacket with a rubbery, water-resistant shell." + icon_state = "pufferjacket" + item_state = "hostrench" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 50, "rad" = 0, "fire" = 0, "acid" = 0) + +/obj/item/clothing/suit/jacket/puffer/vest + name = "puffer vest" + desc = "A thick vest with a rubbery, water-resistant shell." + icon_state = "puffervest" + item_state = "armor" + body_parts_covered = CHEST|GROIN + cold_protection = CHEST|GROIN + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 30, "rad" = 0, "fire" = 0, "acid" = 0) + +/obj/item/clothing/suit/jacket/miljacket + name = "military jacket" + desc = "A canvas jacket styled after classical American military garb. Feels sturdy, yet comfortable." + icon_state = "militaryjacket" + item_state = "militaryjacket" + allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/gun/ballistic/automatic/pistol, /obj/item/gun/ballistic/revolver, /obj/item/radio) + +/obj/item/clothing/suit/jacket/letterman + name = "letterman jacket" + desc = "A classic brown letterman jacket. Looks pretty hot and heavy." + icon_state = "letterman" + item_state = "letterman" + +/obj/item/clothing/suit/jacket/letterman_red + name = "red letterman jacket" + desc = "A letterman jacket in a sick red color. Radical." + icon_state = "letterman_red" + item_state = "letterman_red" + +/obj/item/clothing/suit/jacket/letterman_syndie + name = "blood-red letterman jacket" + desc = "Oddly, this jacket seems to have a large S on the back..." + icon_state = "letterman_s" + item_state = "letterman_s" + +/obj/item/clothing/suit/jacket/letterman_nanotrasen + name = "blue letterman jacket" + desc = "A blue letterman jacket with a proud Nanotrasen N on the back. The tag says that it was made in Space China." + icon_state = "letterman_n" + item_state = "letterman_n" + +/obj/item/clothing/suit/dracula + name = "dracula coat" + desc = "Looks like this belongs in a very old movie set." + icon_state = "draculacoat" + item_state = "draculacoat" + +/obj/item/clothing/suit/drfreeze_coat + name = "doctor freeze's labcoat" + desc = "A labcoat imbued with the power of features and freezes." + icon_state = "drfreeze_coat" + item_state = "drfreeze_coat" + +/obj/item/clothing/suit/gothcoat + name = "gothic coat" + desc = "Perfect for those who want to stalk around a corner of a bar." + icon_state = "gothcoat" + item_state = "gothcoat" + +/obj/item/clothing/suit/xenos + name = "xenos suit" + desc = "A suit made out of chitinous alien hide." + icon_state = "xenos" + item_state = "xenos_helm" + body_parts_covered = CHEST|GROIN|LEGS|ARMS|HANDS + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT + allowed = list(/obj/item/clothing/mask/facehugger/toy) + +/obj/item/clothing/suit/nemes + name = "pharoah tunic" + desc = "Lavish space tomb not included." + icon_state = "pharoah" + item_state = "pharoah" + body_parts_covered = CHEST|GROIN + +/obj/item/clothing/suit/caution + name = "wet floor sign" + desc = "Caution! Wet Floor!" + icon_state = "caution" + lefthand_file = 'icons/mob/inhands/equipment/custodial_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/custodial_righthand.dmi' + force = 1 + throwforce = 3 + throw_speed = 2 + throw_range = 5 + w_class = WEIGHT_CLASS_SMALL + body_parts_covered = CHEST|GROIN + attack_verb = list("warned", "cautioned", "smashed") + armor = list("melee" = 5, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + +/obj/item/clothing/suit/changshan_red + name = "red changshan" + desc = "A gorgeously embroidered silk shirt." + icon_state = "changshan_red" + item_state = "changshan_red" + body_parts_covered = CHEST|GROIN|ARMS|LEGS + +/obj/item/clothing/suit/changshan_blue + name = "blue changshan" + desc = "A gorgeously embroidered silk shirt." + icon_state = "changshan_blue" + item_state = "changshan_blue" + body_parts_covered = CHEST|GROIN|ARMS|LEGS + +/obj/item/clothing/suit/cheongsam_red + name = "red cheongsam" + desc = "A gorgeously embroidered silk dress." + icon_state = "cheongsam_red" + item_state = "cheongsam_red" + body_parts_covered = CHEST|GROIN|ARMS|LEGS + +/obj/item/clothing/suit/cheongsam_blue + name = "blue cheongsam" + desc = "A gorgeously embroidered silk dress." + icon_state = "cheongsam_blue" + item_state = "cheongsam_blue" + body_parts_covered = CHEST|GROIN|ARMS|LEGS + +// WINTER COATS + +/obj/item/clothing/suit/hooded/wintercoat + name = "winter coat" + desc = "A heavy jacket made from 'synthetic' animal furs." + icon_state = "coatwinter" + item_state = "coatwinter" + body_parts_covered = CHEST|GROIN|ARMS + cold_protection = CHEST|GROIN|ARMS + min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) + allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter) + +/obj/item/clothing/head/hooded/winterhood + name = "winter hood" + desc = "A hood attached to a heavy winter jacket." + icon_state = "winterhood" + body_parts_covered = HEAD + cold_protection = HEAD + min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT + flags_inv = HIDEHAIR|HIDEEARS + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) + +/obj/item/clothing/suit/hooded/wintercoat/captain + name = "captain's winter coat" + icon_state = "coatcaptain" + item_state = "coatcaptain" + armor = list("melee" = 25, "bullet" = 30, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 50) + hoodtype = /obj/item/clothing/head/hooded/winterhood/captain + +/obj/item/clothing/suit/hooded/wintercoat/captain/Initialize() + . = ..() + allowed = GLOB.security_wintercoat_allowed + +/obj/item/clothing/head/hooded/winterhood/captain + icon_state = "winterhood_captain" + armor = list("melee" = 25, "bullet" = 30, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 50) + +/obj/item/clothing/suit/hooded/wintercoat/security + name = "security winter coat" + icon_state = "coatsecurity" + item_state = "coatsecurity" + armor = list("melee" = 25, "bullet" = 15, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 45) + hoodtype = /obj/item/clothing/head/hooded/winterhood/security + +/obj/item/clothing/suit/hooded/wintercoat/security/Initialize() + . = ..() + allowed = GLOB.security_wintercoat_allowed + +/obj/item/clothing/head/hooded/winterhood/security + icon_state = "winterhood_security" + armor = list("melee" = 25, "bullet" = 15, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 45) + +/obj/item/clothing/suit/hooded/wintercoat/medical + name = "medical winter coat" + icon_state = "coatmedical" + item_state = "coatmedical" + allowed = list(/obj/item/analyzer, /obj/item/sensor_device, /obj/item/stack/medical, /obj/item/dnainjector, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/hypospray, /obj/item/healthanalyzer, /obj/item/flashlight/pen, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/pill, /obj/item/storage/pill_bottle, /obj/item/paper, /obj/item/melee/classic_baton/telescopic, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 50, "rad" = 0, "fire" = 0, "acid" = 45) + hoodtype = /obj/item/clothing/head/hooded/winterhood/medical + +/obj/item/clothing/head/hooded/winterhood/medical + icon_state = "winterhood_medical" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 50, "rad" = 0, "fire" = 0, "acid" = 45) + +/obj/item/clothing/suit/hooded/wintercoat/science + name = "science winter coat" + icon_state = "coatscience" + item_state = "coatscience" + allowed = list(/obj/item/analyzer, /obj/item/stack/medical, /obj/item/dnainjector, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/hypospray, /obj/item/healthanalyzer, /obj/item/flashlight/pen, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/pill, /obj/item/storage/pill_bottle, /obj/item/paper, /obj/item/melee/classic_baton/telescopic, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + hoodtype = /obj/item/clothing/head/hooded/winterhood/science + +/obj/item/clothing/head/hooded/winterhood/science + icon_state = "winterhood_science" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + +/obj/item/clothing/suit/hooded/wintercoat/engineering + name = "engineering winter coat" + icon_state = "coatengineer" + item_state = "coatengineer" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 20, "fire" = 30, "acid" = 45) + allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/t_scanner, /obj/item/construction/rcd, /obj/item/pipe_dispenser, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter) + hoodtype = /obj/item/clothing/head/hooded/winterhood/engineering + +/obj/item/clothing/head/hooded/winterhood/engineering + icon_state = "winterhood_engineer" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 20, "fire" = 30, "acid" = 45) + +/obj/item/clothing/suit/hooded/wintercoat/engineering/atmos + name = "atmospherics winter coat" + icon_state = "coatatmos" + item_state = "coatatmos" + hoodtype = /obj/item/clothing/head/hooded/winterhood/engineering/atmos + +/obj/item/clothing/head/hooded/winterhood/engineering/atmos + icon_state = "winterhood_atmos" + +/obj/item/clothing/suit/hooded/wintercoat/hydro + name = "hydroponics winter coat" + icon_state = "coathydro" + item_state = "coathydro" + allowed = list(/obj/item/reagent_containers/spray/plantbgone, /obj/item/plant_analyzer, /obj/item/seeds, /obj/item/reagent_containers/glass/bottle, /obj/item/cultivator, /obj/item/reagent_containers/spray/pestspray, /obj/item/hatchet, /obj/item/storage/bag/plants, /obj/item/toy, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/storage/fancy/cigarettes, /obj/item/lighter) + hoodtype = /obj/item/clothing/head/hooded/winterhood/hydro + +/obj/item/clothing/head/hooded/winterhood/hydro + icon_state = "winterhood_hydro" + +/obj/item/clothing/suit/hooded/wintercoat/cargo + name = "cargo winter coat" + icon_state = "coatcargo" + item_state = "coatcargo" + hoodtype = /obj/item/clothing/head/hooded/winterhood/cargo + +/obj/item/clothing/head/hooded/winterhood/cargo + icon_state = "winterhood_cargo" + +/obj/item/clothing/suit/hooded/wintercoat/miner + name = "mining winter coat" + icon_state = "coatminer" + item_state = "coatminer" + allowed = list(/obj/item/pickaxe, /obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter) + armor = list("melee" = 10, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + hoodtype = /obj/item/clothing/head/hooded/winterhood/miner + +/obj/item/clothing/head/hooded/winterhood/miner + icon_state = "winterhood_miner" + armor = list("melee" = 10, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + +/obj/item/clothing/head/hooded/ablative + name = "ablative hood" + desc = "Hood hopefully belonging to an ablative trenchcoat. Includes a visor for cool-o-vision." + icon_state = "ablativehood" + armor = list("melee" = 10, "bullet" = 10, "laser" = 60, "energy" = 60, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + strip_delay = 30 + var/hit_reflect_chance = 50 + +/obj/item/clothing/head/hooded/ablative/equipped(mob/living/carbon/human/user, slot) + ..() + to_chat(user, "As you put on the hood, a visor shifts into place and starts analyzing the people around you. Neat!") + ADD_TRAIT(user, TRAIT_SECURITY_HUD, HELMET_TRAIT) + var/datum/atom_hud/H = GLOB.huds[DATA_HUD_SECURITY_ADVANCED] + H.add_hud_to(user) + +/obj/item/clothing/head/hooded/ablative/dropped(mob/living/carbon/human/user) + ..() + to_chat(user, "You take off the hood, removing the visor in the process and disabling its integrated hud.") + REMOVE_TRAIT(user, TRAIT_SECURITY_HUD, HELMET_TRAIT) + var/datum/atom_hud/H = GLOB.huds[DATA_HUD_SECURITY_ADVANCED] + H.remove_hud_from(user) + +/obj/item/clothing/head/hooded/ablative/IsReflect(def_zone) + if(def_zone != BODY_ZONE_HEAD) //If not shot where ablative is covering you, you don't get the reflection bonus! + return FALSE + if (prob(hit_reflect_chance)) + return TRUE + +/obj/item/clothing/suit/hooded/ablative + name = "ablative trenchcoat" + desc = "Experimental trenchcoat specially crafted to reflect and absorb laser and disabler shots. Don't expect it to do all that much against an axe or a shotgun, however." + icon_state = "ablativecoat" + item_state = "ablativecoat" + body_parts_covered = CHEST|GROIN|LEGS|ARMS + armor = list("melee" = 10, "bullet" = 10, "laser" = 60, "energy" = 60, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + hoodtype = /obj/item/clothing/head/hooded/ablative + strip_delay = 30 + equip_delay_other = 40 + var/hit_reflect_chance = 50 + +/obj/item/clothing/suit/hooded/ablative/Initialize() + . = ..() + allowed = GLOB.security_vest_allowed + +/obj/item/clothing/suit/hooded/ablative/IsReflect(def_zone) + if(!(def_zone in list(BODY_ZONE_CHEST, BODY_ZONE_PRECISE_GROIN, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG))) //If not shot where ablative is covering you, you don't get the reflection bonus! + return FALSE + if (prob(hit_reflect_chance)) + return TRUE + +/obj/item/clothing/suit/spookyghost + name = "spooky ghost" + desc = "This is obviously just a bedsheet, but maybe try it on?" + icon_state = "bedsheet" + user_vars_to_edit = list("name" = "Spooky Ghost", "real_name" = "Spooky Ghost" , "incorporeal_move" = INCORPOREAL_MOVE_BASIC, "appearance_flags" = KEEP_TOGETHER|TILE_BOUND, "alpha" = 150) + alternate_worn_layer = ABOVE_BODY_FRONT_LAYER //so the bedsheet goes over everything but fire + +/obj/item/clothing/suit/bronze + name = "bronze suit" + desc = "A big and clanky suit made of bronze that offers no protection and looks very unfashionable. Nice." + icon = 'icons/obj/clothing/clockwork_garb.dmi' + icon_state = "clockwork_cuirass_old" + armor = list("melee" = 5, "bullet" = 0, "laser" = -5, "energy" = -15, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 20, "acid" = 20) + +/obj/item/clothing/suit/ghost_sheet + name = "ghost sheet" + desc = "The hands float by themselves, so it's extra spooky." + icon_state = "ghost_sheet" + item_state = "ghost_sheet" + throwforce = 0 + throw_speed = 1 + throw_range = 2 + w_class = WEIGHT_CLASS_TINY + flags_inv = HIDEGLOVES|HIDEEARS|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + alternate_worn_layer = UNDER_HEAD_LAYER + +/obj/item/clothing/suit/toggle/suspenders/blue + name = "blue suspenders" + desc = "The symbol of hard labor and dirty jobs." + icon = 'icons/obj/clothing/belts.dmi' + icon_state = "suspenders_blue" + +/obj/item/clothing/suit/toggle/suspenders/gray + name = "gray suspenders" + desc = "The symbol of hard labor and dirty jobs." + icon = 'icons/obj/clothing/belts.dmi' + icon_state = "suspenders_gray" + +/obj/item/clothing/suit/hooded/mysticrobe + name = "mystic's robe" + desc = "Wearing this makes you feel more attuned with the nature of the universe... as well as a bit more irresponsible. " + icon_state = "mysticrobe" + item_state = "mysticrobe" + body_parts_covered = CHEST|GROIN|LEGS|ARMS + allowed = list(/obj/item/spellbook, /obj/item/storage/book/bible) + flags_inv = HIDEJUMPSUIT + hoodtype = /obj/item/clothing/head/hooded/mysticrobe + +/obj/item/clothing/head/hooded/mysticrobe + name = "mystic's hood" + desc = "The balance of reality tips towards order." + icon_state = "mystichood" + item_state = "mystichood" + body_parts_covered = HEAD + flags_inv = HIDEHAIR|HIDEEARS|HIDEFACIALHAIR|HIDEFACE|HIDEMASK + +/obj/item/clothing/suit/coordinator + name = "coordinator jacket" + desc = "A jacket for a party ooordinator, stylish!." + icon_state = "capformal" + item_state = "capspacesuit" + armor = list("melee" = 25, "bullet" = 15, "laser" = 25, "energy" = 35, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + +/obj/item/clothing/suit/hawaiian + name = "hawaiian overshirt" + desc = "A cool shirt for chilling on the beach." + icon_state = "hawaiian_blue" + item_state = "hawaiian_blue" + +/obj/item/clothing/suit/yakuza + name = "tojo clan jacket" + desc = "The jacket of a mad dog." + icon_state = "MajimaJacket" + item_state = "MajimaJacket" + body_parts_covered = ARMS + +/obj/item/clothing/suit/dutch + name = "dutch's jacket" + desc = "For those long nights on the beach in Tahiti." + icon_state = "DutchJacket" + item_state = "DutchJacket" + body_parts_covered = ARMS + diff --git a/code/modules/clothing/suits/utility.dm b/code/modules/clothing/suits/utility.dm index cefd5633221e..ca233c52fb57 100644 --- a/code/modules/clothing/suits/utility.dm +++ b/code/modules/clothing/suits/utility.dm @@ -1,148 +1,148 @@ -/* - * Contains: - * Fire protection - * Bomb protection - * Radiation protection - */ - -/* - * Fire protection - */ - -/obj/item/clothing/suit/fire - name = "emergency firesuit" - desc = "A suit that helps protect against fire and heat." - icon_state = "fire" - item_state = "ro_suit" - w_class = WEIGHT_CLASS_BULKY - gas_transfer_coefficient = 0.9 - permeability_coefficient = 0.5 - body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS - allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/extinguisher, /obj/item/crowbar) - slowdown = 1 - armor = list("melee" = 15, "bullet" = 5, "laser" = 20, "energy" = 20, "bomb" = 20, "bio" = 10, "rad" = 20, "fire" = 100, "acid" = 50) - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT - clothing_flags = STOPSPRESSUREDAMAGE | THICKMATERIAL - heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS - max_heat_protection_temperature = FIRE_SUIT_MAX_TEMP_PROTECT - cold_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS - min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT - strip_delay = 60 - equip_delay_other = 60 - resistance_flags = FIRE_PROOF - -/obj/item/clothing/suit/fire/firefighter - icon_state = "firesuit" - item_state = "firefighter" - -/obj/item/clothing/suit/fire/heavy - name = "heavy firesuit" - desc = "An old, bulky thermal protection suit." - icon_state = "thermal" - item_state = "ro_suit" - slowdown = 1.5 - -/obj/item/clothing/suit/fire/atmos - name = "firesuit" - desc = "An expensive firesuit that protects against even the most deadly of station fires. Designed to protect even if the wearer is set aflame." - icon_state = "atmos_firesuit" - item_state = "firesuit_atmos" - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - -/* - * Bomb protection - */ -/obj/item/clothing/head/bomb_hood - name = "bomb hood" - desc = "Use in case of bomb." - icon_state = "bombsuit" - clothing_flags = THICKMATERIAL | SNUG_FIT - armor = list("melee" = 20, "bullet" = 0, "laser" = 20,"energy" = 30, "bomb" = 100, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 50) - flags_inv = HIDEFACE|HIDEMASK|HIDEEARS|HIDEEYES|HIDEHAIR|HIDEFACIALHAIR - dynamic_hair_suffix = "" - dynamic_fhair_suffix = "" - cold_protection = HEAD - min_cold_protection_temperature = HELMET_MIN_TEMP_PROTECT - heat_protection = HEAD - max_heat_protection_temperature = HELMET_MAX_TEMP_PROTECT - strip_delay = 70 - equip_delay_other = 70 - flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF - resistance_flags = NONE - - -/obj/item/clothing/suit/bomb_suit - name = "bomb suit" - desc = "A suit designed for safety when handling explosives." - icon_state = "bombsuit" - item_state = "bombsuit" - w_class = WEIGHT_CLASS_BULKY - gas_transfer_coefficient = 0.01 - permeability_coefficient = 0.01 - clothing_flags = THICKMATERIAL - body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS - slowdown = 2 - armor = list("melee" = 20, "bullet" = 0, "laser" = 20,"energy" = 30, "bomb" = 100, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 50) - flags_inv = HIDEJUMPSUIT - heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS - max_heat_protection_temperature = ARMOR_MAX_TEMP_PROTECT - cold_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS - min_cold_protection_temperature = ARMOR_MIN_TEMP_PROTECT - strip_delay = 70 - equip_delay_other = 70 - resistance_flags = NONE - - -/obj/item/clothing/head/bomb_hood/security - icon_state = "bombsuit_sec" - item_state = "bombsuit_sec" - -/obj/item/clothing/suit/bomb_suit/security - icon_state = "bombsuit_sec" - item_state = "bombsuit_sec" - allowed = list(/obj/item/gun/energy, /obj/item/melee/baton, /obj/item/restraints/handcuffs) - - -/obj/item/clothing/head/bomb_hood/white - icon_state = "bombsuit_white" - item_state = "bombsuit_white" - -/obj/item/clothing/suit/bomb_suit/white - icon_state = "bombsuit_white" - item_state = "bombsuit_white" - -/* -* Radiation protection -*/ - -/obj/item/clothing/head/radiation - name = "radiation hood" - icon_state = "rad" - desc = "A hood with radiation protective properties. The label reads, 'Made with lead. Please do not consume insulation.'" - clothing_flags = THICKMATERIAL | SNUG_FIT - flags_inv = HIDEMASK|HIDEEARS|HIDEFACE|HIDEEYES|HIDEHAIR|HIDEFACIALHAIR - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 60, "rad" = 100, "fire" = 30, "acid" = 30) - strip_delay = 60 - equip_delay_other = 60 - flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF - resistance_flags = NONE - flags_1 = RAD_PROTECT_CONTENTS_1 - -/obj/item/clothing/suit/radiation - name = "radiation suit" - desc = "A suit that protects against radiation. The label reads, 'Made with lead. Please do not consume insulation.'" - icon_state = "rad" - item_state = "rad_suit" - w_class = WEIGHT_CLASS_BULKY - gas_transfer_coefficient = 0.9 - permeability_coefficient = 0.5 - clothing_flags = THICKMATERIAL - body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS - allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/geiger_counter) - slowdown = 1.5 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 60, "rad" = 100, "fire" = 30, "acid" = 30) - strip_delay = 60 - equip_delay_other = 60 - flags_inv = HIDEJUMPSUIT - resistance_flags = NONE - flags_1 = RAD_PROTECT_CONTENTS_1 +/* + * Contains: + * Fire protection + * Bomb protection + * Radiation protection + */ + +/* + * Fire protection + */ + +/obj/item/clothing/suit/fire + name = "emergency firesuit" + desc = "A suit that helps protect against fire and heat." + icon_state = "fire" + item_state = "ro_suit" + w_class = WEIGHT_CLASS_BULKY + gas_transfer_coefficient = 0.9 + permeability_coefficient = 0.5 + body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS + allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/extinguisher, /obj/item/crowbar) + slowdown = 1 + armor = list("melee" = 15, "bullet" = 5, "laser" = 20, "energy" = 20, "bomb" = 20, "bio" = 10, "rad" = 20, "fire" = 100, "acid" = 50) + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT + clothing_flags = STOPSPRESSUREDAMAGE | THICKMATERIAL + heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS + max_heat_protection_temperature = FIRE_SUIT_MAX_TEMP_PROTECT + cold_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS + min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT + strip_delay = 60 + equip_delay_other = 60 + resistance_flags = FIRE_PROOF + +/obj/item/clothing/suit/fire/firefighter + icon_state = "firesuit" + item_state = "firefighter" + +/obj/item/clothing/suit/fire/heavy + name = "heavy firesuit" + desc = "An old, bulky thermal protection suit." + icon_state = "thermal" + item_state = "ro_suit" + slowdown = 1.5 + +/obj/item/clothing/suit/fire/atmos + name = "firesuit" + desc = "An expensive firesuit that protects against even the most deadly of station fires. Designed to protect even if the wearer is set aflame." + icon_state = "atmos_firesuit" + item_state = "firesuit_atmos" + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + +/* + * Bomb protection + */ +/obj/item/clothing/head/bomb_hood + name = "bomb hood" + desc = "Use in case of bomb." + icon_state = "bombsuit" + clothing_flags = THICKMATERIAL | SNUG_FIT + armor = list("melee" = 20, "bullet" = 0, "laser" = 20,"energy" = 30, "bomb" = 100, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 50) + flags_inv = HIDEFACE|HIDEMASK|HIDEEARS|HIDEEYES|HIDEHAIR|HIDEFACIALHAIR + dynamic_hair_suffix = "" + dynamic_fhair_suffix = "" + cold_protection = HEAD + min_cold_protection_temperature = HELMET_MIN_TEMP_PROTECT + heat_protection = HEAD + max_heat_protection_temperature = HELMET_MAX_TEMP_PROTECT + strip_delay = 70 + equip_delay_other = 70 + flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF + resistance_flags = NONE + + +/obj/item/clothing/suit/bomb_suit + name = "bomb suit" + desc = "A suit designed for safety when handling explosives." + icon_state = "bombsuit" + item_state = "bombsuit" + w_class = WEIGHT_CLASS_BULKY + gas_transfer_coefficient = 0.01 + permeability_coefficient = 0.01 + clothing_flags = THICKMATERIAL + body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS + slowdown = 2 + armor = list("melee" = 20, "bullet" = 0, "laser" = 20,"energy" = 30, "bomb" = 100, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 50) + flags_inv = HIDEJUMPSUIT + heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS + max_heat_protection_temperature = ARMOR_MAX_TEMP_PROTECT + cold_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS + min_cold_protection_temperature = ARMOR_MIN_TEMP_PROTECT + strip_delay = 70 + equip_delay_other = 70 + resistance_flags = NONE + + +/obj/item/clothing/head/bomb_hood/security + icon_state = "bombsuit_sec" + item_state = "bombsuit_sec" + +/obj/item/clothing/suit/bomb_suit/security + icon_state = "bombsuit_sec" + item_state = "bombsuit_sec" + allowed = list(/obj/item/gun/energy, /obj/item/melee/baton, /obj/item/restraints/handcuffs) + + +/obj/item/clothing/head/bomb_hood/white + icon_state = "bombsuit_white" + item_state = "bombsuit_white" + +/obj/item/clothing/suit/bomb_suit/white + icon_state = "bombsuit_white" + item_state = "bombsuit_white" + +/* +* Radiation protection +*/ + +/obj/item/clothing/head/radiation + name = "radiation hood" + icon_state = "rad" + desc = "A hood with radiation protective properties. The label reads, 'Made with lead. Please do not consume insulation.'" + clothing_flags = THICKMATERIAL | SNUG_FIT + flags_inv = HIDEMASK|HIDEEARS|HIDEFACE|HIDEEYES|HIDEHAIR|HIDEFACIALHAIR + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 60, "rad" = 100, "fire" = 30, "acid" = 30) + strip_delay = 60 + equip_delay_other = 60 + flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF + resistance_flags = NONE + flags_1 = RAD_PROTECT_CONTENTS_1 + +/obj/item/clothing/suit/radiation + name = "radiation suit" + desc = "A suit that protects against radiation. The label reads, 'Made with lead. Please do not consume insulation.'" + icon_state = "rad" + item_state = "rad_suit" + w_class = WEIGHT_CLASS_BULKY + gas_transfer_coefficient = 0.9 + permeability_coefficient = 0.5 + clothing_flags = THICKMATERIAL + body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS + allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/geiger_counter) + slowdown = 1.5 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 60, "rad" = 100, "fire" = 30, "acid" = 30) + strip_delay = 60 + equip_delay_other = 60 + flags_inv = HIDEJUMPSUIT + resistance_flags = NONE + flags_1 = RAD_PROTECT_CONTENTS_1 diff --git a/code/modules/clothing/suits/wiz_robe.dm b/code/modules/clothing/suits/wiz_robe.dm index 6854ec38fcdc..1a84c07f4d62 100644 --- a/code/modules/clothing/suits/wiz_robe.dm +++ b/code/modules/clothing/suits/wiz_robe.dm @@ -1,231 +1,231 @@ -/obj/item/clothing/head/wizard - name = "wizard hat" - desc = "Strange-looking hat-wear that most certainly belongs to a real magic user." - icon_state = "wizard" - gas_transfer_coefficient = 0.01 // IT'S MAGICAL OKAY JEEZ +1 TO NOT DIE - permeability_coefficient = 0.01 - armor = list("melee" = 30, "bullet" = 20, "laser" = 20, "energy" = 30, "bomb" = 20, "bio" = 20, "rad" = 20, "fire" = 100, "acid" = 100) - strip_delay = 50 - equip_delay_other = 50 - clothing_flags = SNUG_FIT - resistance_flags = FIRE_PROOF | ACID_PROOF - dog_fashion = /datum/dog_fashion/head/blue_wizard - -/obj/item/clothing/head/wizard/red - name = "red wizard hat" - desc = "Strange-looking red hat-wear that most certainly belongs to a real magic user." - icon_state = "redwizard" - dog_fashion = /datum/dog_fashion/head/red_wizard - -/obj/item/clothing/head/wizard/yellow - name = "yellow wizard hat" - desc = "Strange-looking yellow hat-wear that most certainly belongs to a powerful magic user." - icon_state = "yellowwizard" - dog_fashion = null - -/obj/item/clothing/head/wizard/black - name = "black wizard hat" - desc = "Strange-looking black hat-wear that most certainly belongs to a real skeleton. Spooky." - icon_state = "blackwizard" - dog_fashion = null - -/obj/item/clothing/head/wizard/fake - name = "wizard hat" - desc = "It has WIZZARD written across it in sequins. Comes with a cool beard." - icon_state = "wizard-fake" - gas_transfer_coefficient = 1 - permeability_coefficient = 1 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - resistance_flags = FLAMMABLE - dog_fashion = /datum/dog_fashion/head/blue_wizard - -/obj/item/clothing/head/wizard/marisa - name = "witch hat" - desc = "Strange-looking hat-wear. Makes you want to cast fireballs." - icon_state = "marisa" - dog_fashion = null - -/obj/item/clothing/head/wizard/magus - name = "\improper Magus helm" - desc = "A mysterious helmet that hums with an unearthly power." - icon_state = "magus" - item_state = "magus" - dog_fashion = null - -/obj/item/clothing/head/wizard/santa - name = "Santa's hat" - desc = "Ho ho ho. Merrry X-mas!" - icon_state = "santahat" - flags_inv = HIDEHAIR|HIDEFACIALHAIR - dog_fashion = null - -/obj/item/clothing/suit/wizrobe - name = "wizard robe" - desc = "A magnificent, gem-lined robe that seems to radiate power." - icon_state = "wizard" - item_state = "wizrobe" - gas_transfer_coefficient = 0.01 - permeability_coefficient = 0.01 - body_parts_covered = CHEST|GROIN|ARMS|LEGS - armor = list("melee" = 30, "bullet" = 20, "laser" = 20, "energy" = 30, "bomb" = 20, "bio" = 20, "rad" = 20, "fire" = 100, "acid" = 100) - allowed = list(/obj/item/teleportation_scroll) - flags_inv = HIDEJUMPSUIT - strip_delay = 50 - equip_delay_other = 50 - resistance_flags = FIRE_PROOF | ACID_PROOF - -/obj/item/clothing/suit/wizrobe/red - name = "red wizard robe" - desc = "A magnificent red gem-lined robe that seems to radiate power." - icon_state = "redwizard" - item_state = "redwizrobe" - -/obj/item/clothing/suit/wizrobe/yellow - name = "yellow wizard robe" - desc = "A magnificent yellow gem-lined robe that seems to radiate power." - icon_state = "yellowwizard" - item_state = "yellowwizrobe" - -/obj/item/clothing/suit/wizrobe/black - name = "black wizard robe" - desc = "An unnerving black gem-lined robe that reeks of death and decay." - icon_state = "blackwizard" - item_state = "blackwizrobe" - -/obj/item/clothing/suit/wizrobe/marisa - name = "witch robe" - desc = "Magic is all about the spell power, ZE!" - icon_state = "marisa" - item_state = "marisarobe" - -/obj/item/clothing/suit/wizrobe/magusblue - name = "\improper Magus robe" - desc = "A set of armored robes that seem to radiate a dark power." - icon_state = "magusblue" - item_state = "magusblue" - -/obj/item/clothing/suit/wizrobe/magusred - name = "\improper Magus robe" - desc = "A set of armored robes that seem to radiate a dark power." - icon_state = "magusred" - item_state = "magusred" - - -/obj/item/clothing/suit/wizrobe/santa - name = "Santa's suit" - desc = "Festive!" - icon_state = "santa" - item_state = "santa" - -/obj/item/clothing/suit/wizrobe/fake - name = "wizard robe" - desc = "A rather dull blue robe meant to mimic real wizard robes." - icon_state = "wizard-fake" - item_state = "wizrobe" - gas_transfer_coefficient = 1 - permeability_coefficient = 1 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - resistance_flags = FLAMMABLE - -/obj/item/clothing/head/wizard/marisa/fake - name = "witch hat" - desc = "Strange-looking hat-wear, makes you want to cast fireballs." - icon_state = "marisa" - gas_transfer_coefficient = 1 - permeability_coefficient = 1 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - resistance_flags = FLAMMABLE - -/obj/item/clothing/suit/wizrobe/marisa/fake - name = "witch robe" - desc = "Magic is all about the spell power, ZE!" - icon_state = "marisa" - item_state = "marisarobe" - gas_transfer_coefficient = 1 - permeability_coefficient = 1 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - resistance_flags = FLAMMABLE - -/obj/item/clothing/suit/wizrobe/paper - name = "papier-mache robe" // no non-latin characters! - desc = "A robe held together by various bits of clear-tape and paste." - icon_state = "wizard-paper" - item_state = "wizard-paper" - var/robe_charge = TRUE - actions_types = list(/datum/action/item_action/stickmen) - - -/obj/item/clothing/suit/wizrobe/paper/ui_action_click(mob/user, action) - stickmen() - - -/obj/item/clothing/suit/wizrobe/paper/verb/stickmen() - set category = "Object" - set name = "Summon Stick Minions" - set src in usr - if(!isliving(usr)) - return - if(!robe_charge) - to_chat(usr, "\The robe's internal magic supply is still recharging!") - return - - usr.say("Rise, my creation! Off your page into this realm!", forced = "stickman summoning") - playsound(src.loc, 'sound/magic/summon_magic.ogg', 50, TRUE, TRUE) - var/mob/living/M = new /mob/living/simple_animal/hostile/stickman(get_turf(usr)) - var/list/factions = usr.faction - M.faction = factions - src.robe_charge = FALSE - sleep(30) - src.robe_charge = TRUE - to_chat(usr, "\The robe hums, its internal magic supply restored.") - - -//Shielded Armour - -/obj/item/clothing/suit/space/hardsuit/shielded/wizard - name = "battlemage armour" - desc = "Not all wizards are afraid of getting up close and personal." - icon_state = "battlemage" - item_state = "battlemage" - recharge_rate = 0 - current_charges = 15 - recharge_cooldown = INFINITY - shield_state = "shield-red" - shield_on = "shield-red" - min_cold_protection_temperature = ARMOR_MIN_TEMP_PROTECT - max_heat_protection_temperature = ARMOR_MAX_TEMP_PROTECT - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/shielded/wizard - armor = list("melee" = 30, "bullet" = 20, "laser" = 20, "energy" = 30, "bomb" = 20, "bio" = 20, "rad" = 20, "fire" = 100, "acid" = 100) - slowdown = 0 - resistance_flags = FIRE_PROOF | ACID_PROOF - -/obj/item/clothing/head/helmet/space/hardsuit/shielded/wizard - name = "battlemage helmet" - desc = "A suitably impressive helmet." - icon_state = "battlemage" - item_state = "battlemage" - min_cold_protection_temperature = ARMOR_MIN_TEMP_PROTECT - max_heat_protection_temperature = ARMOR_MAX_TEMP_PROTECT - armor = list("melee" = 30, "bullet" = 20, "laser" = 20, "energy" = 30, "bomb" = 20, "bio" = 20, "rad" = 20, "fire" = 100, "acid" = 100) - actions_types = null //No inbuilt light - resistance_flags = FIRE_PROOF | ACID_PROOF - -/obj/item/clothing/head/helmet/space/hardsuit/shielded/wizard/attack_self(mob/user) - return - -/obj/item/wizard_armour_charge - name = "battlemage shield charges" - desc = "A powerful rune that will increase the number of hits a suit of battlemage armour can take before failing.." - icon = 'icons/effects/effects.dmi' - icon_state = "electricity2" - -/obj/item/wizard_armour_charge/afterattack(obj/item/clothing/suit/space/hardsuit/shielded/wizard/W, mob/user, proximity) - . = ..() - if(!proximity) - return - if(!istype(W)) - to_chat(user, "The rune can only be used on battlemage armour!") - return - W.current_charges += 8 - to_chat(user, "You charge \the [W]. It can now absorb [W.current_charges] hits.") - qdel(src) +/obj/item/clothing/head/wizard + name = "wizard hat" + desc = "Strange-looking hat-wear that most certainly belongs to a real magic user." + icon_state = "wizard" + gas_transfer_coefficient = 0.01 // IT'S MAGICAL OKAY JEEZ +1 TO NOT DIE + permeability_coefficient = 0.01 + armor = list("melee" = 30, "bullet" = 20, "laser" = 20, "energy" = 30, "bomb" = 20, "bio" = 20, "rad" = 20, "fire" = 100, "acid" = 100) + strip_delay = 50 + equip_delay_other = 50 + clothing_flags = SNUG_FIT + resistance_flags = FIRE_PROOF | ACID_PROOF + dog_fashion = /datum/dog_fashion/head/blue_wizard + +/obj/item/clothing/head/wizard/red + name = "red wizard hat" + desc = "Strange-looking red hat-wear that most certainly belongs to a real magic user." + icon_state = "redwizard" + dog_fashion = /datum/dog_fashion/head/red_wizard + +/obj/item/clothing/head/wizard/yellow + name = "yellow wizard hat" + desc = "Strange-looking yellow hat-wear that most certainly belongs to a powerful magic user." + icon_state = "yellowwizard" + dog_fashion = null + +/obj/item/clothing/head/wizard/black + name = "black wizard hat" + desc = "Strange-looking black hat-wear that most certainly belongs to a real skeleton. Spooky." + icon_state = "blackwizard" + dog_fashion = null + +/obj/item/clothing/head/wizard/fake + name = "wizard hat" + desc = "It has WIZZARD written across it in sequins. Comes with a cool beard." + icon_state = "wizard-fake" + gas_transfer_coefficient = 1 + permeability_coefficient = 1 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + resistance_flags = FLAMMABLE + dog_fashion = /datum/dog_fashion/head/blue_wizard + +/obj/item/clothing/head/wizard/marisa + name = "witch hat" + desc = "Strange-looking hat-wear. Makes you want to cast fireballs." + icon_state = "marisa" + dog_fashion = null + +/obj/item/clothing/head/wizard/magus + name = "\improper Magus helm" + desc = "A mysterious helmet that hums with an unearthly power." + icon_state = "magus" + item_state = "magus" + dog_fashion = null + +/obj/item/clothing/head/wizard/santa + name = "Santa's hat" + desc = "Ho ho ho. Merrry X-mas!" + icon_state = "santahat" + flags_inv = HIDEHAIR|HIDEFACIALHAIR + dog_fashion = null + +/obj/item/clothing/suit/wizrobe + name = "wizard robe" + desc = "A magnificent, gem-lined robe that seems to radiate power." + icon_state = "wizard" + item_state = "wizrobe" + gas_transfer_coefficient = 0.01 + permeability_coefficient = 0.01 + body_parts_covered = CHEST|GROIN|ARMS|LEGS + armor = list("melee" = 30, "bullet" = 20, "laser" = 20, "energy" = 30, "bomb" = 20, "bio" = 20, "rad" = 20, "fire" = 100, "acid" = 100) + allowed = list(/obj/item/teleportation_scroll) + flags_inv = HIDEJUMPSUIT + strip_delay = 50 + equip_delay_other = 50 + resistance_flags = FIRE_PROOF | ACID_PROOF + +/obj/item/clothing/suit/wizrobe/red + name = "red wizard robe" + desc = "A magnificent red gem-lined robe that seems to radiate power." + icon_state = "redwizard" + item_state = "redwizrobe" + +/obj/item/clothing/suit/wizrobe/yellow + name = "yellow wizard robe" + desc = "A magnificent yellow gem-lined robe that seems to radiate power." + icon_state = "yellowwizard" + item_state = "yellowwizrobe" + +/obj/item/clothing/suit/wizrobe/black + name = "black wizard robe" + desc = "An unnerving black gem-lined robe that reeks of death and decay." + icon_state = "blackwizard" + item_state = "blackwizrobe" + +/obj/item/clothing/suit/wizrobe/marisa + name = "witch robe" + desc = "Magic is all about the spell power, ZE!" + icon_state = "marisa" + item_state = "marisarobe" + +/obj/item/clothing/suit/wizrobe/magusblue + name = "\improper Magus robe" + desc = "A set of armored robes that seem to radiate a dark power." + icon_state = "magusblue" + item_state = "magusblue" + +/obj/item/clothing/suit/wizrobe/magusred + name = "\improper Magus robe" + desc = "A set of armored robes that seem to radiate a dark power." + icon_state = "magusred" + item_state = "magusred" + + +/obj/item/clothing/suit/wizrobe/santa + name = "Santa's suit" + desc = "Festive!" + icon_state = "santa" + item_state = "santa" + +/obj/item/clothing/suit/wizrobe/fake + name = "wizard robe" + desc = "A rather dull blue robe meant to mimic real wizard robes." + icon_state = "wizard-fake" + item_state = "wizrobe" + gas_transfer_coefficient = 1 + permeability_coefficient = 1 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + resistance_flags = FLAMMABLE + +/obj/item/clothing/head/wizard/marisa/fake + name = "witch hat" + desc = "Strange-looking hat-wear, makes you want to cast fireballs." + icon_state = "marisa" + gas_transfer_coefficient = 1 + permeability_coefficient = 1 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + resistance_flags = FLAMMABLE + +/obj/item/clothing/suit/wizrobe/marisa/fake + name = "witch robe" + desc = "Magic is all about the spell power, ZE!" + icon_state = "marisa" + item_state = "marisarobe" + gas_transfer_coefficient = 1 + permeability_coefficient = 1 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + resistance_flags = FLAMMABLE + +/obj/item/clothing/suit/wizrobe/paper + name = "papier-mache robe" // no non-latin characters! + desc = "A robe held together by various bits of clear-tape and paste." + icon_state = "wizard-paper" + item_state = "wizard-paper" + var/robe_charge = TRUE + actions_types = list(/datum/action/item_action/stickmen) + + +/obj/item/clothing/suit/wizrobe/paper/ui_action_click(mob/user, action) + stickmen() + + +/obj/item/clothing/suit/wizrobe/paper/verb/stickmen() + set category = "Object" + set name = "Summon Stick Minions" + set src in usr + if(!isliving(usr)) + return + if(!robe_charge) + to_chat(usr, "\The robe's internal magic supply is still recharging!") + return + + usr.say("Rise, my creation! Off your page into this realm!", forced = "stickman summoning") + playsound(src.loc, 'sound/magic/summon_magic.ogg', 50, TRUE, TRUE) + var/mob/living/M = new /mob/living/simple_animal/hostile/stickman(get_turf(usr)) + var/list/factions = usr.faction + M.faction = factions + src.robe_charge = FALSE + sleep(30) + src.robe_charge = TRUE + to_chat(usr, "\The robe hums, its internal magic supply restored.") + + +//Shielded Armour + +/obj/item/clothing/suit/space/hardsuit/shielded/wizard + name = "battlemage armour" + desc = "Not all wizards are afraid of getting up close and personal." + icon_state = "battlemage" + item_state = "battlemage" + recharge_rate = 0 + current_charges = 15 + recharge_cooldown = INFINITY + shield_state = "shield-red" + shield_on = "shield-red" + min_cold_protection_temperature = ARMOR_MIN_TEMP_PROTECT + max_heat_protection_temperature = ARMOR_MAX_TEMP_PROTECT + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/shielded/wizard + armor = list("melee" = 30, "bullet" = 20, "laser" = 20, "energy" = 30, "bomb" = 20, "bio" = 20, "rad" = 20, "fire" = 100, "acid" = 100) + slowdown = 0 + resistance_flags = FIRE_PROOF | ACID_PROOF + +/obj/item/clothing/head/helmet/space/hardsuit/shielded/wizard + name = "battlemage helmet" + desc = "A suitably impressive helmet." + icon_state = "battlemage" + item_state = "battlemage" + min_cold_protection_temperature = ARMOR_MIN_TEMP_PROTECT + max_heat_protection_temperature = ARMOR_MAX_TEMP_PROTECT + armor = list("melee" = 30, "bullet" = 20, "laser" = 20, "energy" = 30, "bomb" = 20, "bio" = 20, "rad" = 20, "fire" = 100, "acid" = 100) + actions_types = null //No inbuilt light + resistance_flags = FIRE_PROOF | ACID_PROOF + +/obj/item/clothing/head/helmet/space/hardsuit/shielded/wizard/attack_self(mob/user) + return + +/obj/item/wizard_armour_charge + name = "battlemage shield charges" + desc = "A powerful rune that will increase the number of hits a suit of battlemage armour can take before failing.." + icon = 'icons/effects/effects.dmi' + icon_state = "electricity2" + +/obj/item/wizard_armour_charge/afterattack(obj/item/clothing/suit/space/hardsuit/shielded/wizard/W, mob/user, proximity) + . = ..() + if(!proximity) + return + if(!istype(W)) + to_chat(user, "The rune can only be used on battlemage armour!") + return + W.current_charges += 8 + to_chat(user, "You charge \the [W]. It can now absorb [W.current_charges] hits.") + qdel(src) diff --git a/code/modules/clothing/under/_under.dm b/code/modules/clothing/under/_under.dm index d81f56b32f39..abdb8aa4a3bc 100644 --- a/code/modules/clothing/under/_under.dm +++ b/code/modules/clothing/under/_under.dm @@ -1,183 +1,183 @@ -/obj/item/clothing/under - name = "under" - icon = 'icons/obj/clothing/under/default.dmi' - mob_overlay_icon = 'icons/mob/clothing/under/default.dmi' - body_parts_covered = CHEST|GROIN|LEGS|ARMS - permeability_coefficient = 0.9 - slot_flags = ITEM_SLOT_ICLOTHING - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - equip_sound = 'sound/items/equip/jumpsuit_equip.ogg' - drop_sound = 'sound/items/handling/cloth_drop.ogg' - pickup_sound = 'sound/items/handling/cloth_pickup.ogg' - cuttable = TRUE - clothamnt = 5 - var/fitted = FEMALE_UNIFORM_FULL // For use in alternate clothing styles for women - var/has_sensor = HAS_SENSORS // For the crew computer - var/random_sensor = TRUE - var/sensor_mode = NO_SENSORS - var/can_adjust = TRUE - var/adjusted = NORMAL_STYLE - var/alt_covers_chest = FALSE // for adjusted/rolled-down jumpsuits, FALSE = exposes chest and arms, TRUE = exposes arms only - var/obj/item/clothing/accessory/attached_accessory - var/mutable_appearance/accessory_overlay - var/mutantrace_variation = NO_MUTANTRACE_VARIATION //Are there special sprites for specific situations? Don't use this unless you need to. - var/freshly_laundered = FALSE - -/obj/item/clothing/under/worn_overlays(isinhands = FALSE) - . = list() - if(!isinhands) - if(damaged_clothes) - . += mutable_appearance('icons/effects/item_damage.dmi', "damageduniform") - if(HAS_BLOOD_DNA(src)) - . += mutable_appearance('icons/effects/blood.dmi', "uniformblood") - if(accessory_overlay) - . += accessory_overlay - -/obj/item/clothing/under/attackby(obj/item/I, mob/user, params) - if((has_sensor == BROKEN_SENSORS) && istype(I, /obj/item/stack/cable_coil)) - var/obj/item/stack/cable_coil/C = I - C.use(1) - has_sensor = HAS_SENSORS - to_chat(user,"You repair the suit sensors on [src] with [C].") - return 1 - if(!attach_accessory(I, user)) - return ..() - -/obj/item/clothing/under/update_clothes_damaged_state(damaging = TRUE) - ..() - if(ismob(loc)) - var/mob/M = loc - M.update_inv_w_uniform() - if(has_sensor > NO_SENSORS) - has_sensor = BROKEN_SENSORS - -/obj/item/clothing/under/Initialize() - . = ..() - if(random_sensor) - //make the sensor mode favor higher levels, except coords. - sensor_mode = pick(SENSOR_OFF, SENSOR_LIVING, SENSOR_LIVING, SENSOR_VITALS, SENSOR_VITALS, SENSOR_VITALS, SENSOR_COORDS, SENSOR_COORDS) - -/obj/item/clothing/under/emp_act() - . = ..() - if(has_sensor > NO_SENSORS) - sensor_mode = pick(SENSOR_OFF, SENSOR_OFF, SENSOR_OFF, SENSOR_LIVING, SENSOR_LIVING, SENSOR_VITALS, SENSOR_VITALS, SENSOR_COORDS) - if(ismob(loc)) - var/mob/M = loc - to_chat(M,"The sensors on the [src] change rapidly!") - -/obj/item/clothing/under/equipped(mob/user, slot) - ..() - if(adjusted) - adjusted = NORMAL_STYLE - fitted = initial(fitted) - if(!alt_covers_chest) - body_parts_covered |= CHEST - - if(mutantrace_variation && ishuman(user)) - var/mob/living/carbon/human/H = user - if(DIGITIGRADE in H.dna.species.species_traits) - adjusted = DIGITIGRADE_STYLE - H.update_inv_w_uniform() - - if(slot == ITEM_SLOT_ICLOTHING && freshly_laundered) - freshly_laundered = FALSE - SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "fresh_laundry", /datum/mood_event/fresh_laundry) - - if(attached_accessory && slot != ITEM_SLOT_HANDS && ishuman(user)) - var/mob/living/carbon/human/H = user - attached_accessory.on_uniform_equip(src, user) - H.fan_hud_set_fandom() - if(attached_accessory.above_suit) - H.update_inv_wear_suit() - -/obj/item/clothing/under/dropped(mob/user) - if(attached_accessory) - attached_accessory.on_uniform_dropped(src, user) - if(ishuman(user)) - var/mob/living/carbon/human/H = user - H.fan_hud_set_fandom() - if(attached_accessory.above_suit) - H.update_inv_wear_suit() - - ..() - -/obj/item/clothing/under/proc/attach_accessory(obj/item/I, mob/user, notifyAttach = 1) - . = FALSE - if(istype(I, /obj/item/clothing/accessory)) - var/obj/item/clothing/accessory/A = I - if(attached_accessory) - if(user) - to_chat(user, "[src] already has an accessory.") - return - else - - if(!A.can_attach_accessory(src, user)) //Make sure the suit has a place to put the accessory. - return - if(user && !user.temporarilyRemoveItemFromInventory(I)) - return - if(!A.attach(src, user)) - return - - if(user && notifyAttach) - to_chat(user, "You attach [I] to [src].") - - var/accessory_color = attached_accessory.icon_state - accessory_overlay = mutable_appearance('icons/mob/clothing/accessories.dmi', "[accessory_color]") - accessory_overlay.alpha = attached_accessory.alpha - accessory_overlay.color = attached_accessory.color - - if(ishuman(loc)) - var/mob/living/carbon/human/H = loc - H.update_inv_w_uniform() - H.update_inv_wear_suit() - H.fan_hud_set_fandom() - - return TRUE - -/obj/item/clothing/under/proc/remove_accessory(mob/user) - if(!isliving(user)) - return - if(!can_use(user)) - return - - if(attached_accessory) - var/obj/item/clothing/accessory/A = attached_accessory - attached_accessory.detach(src, user) - if(user.put_in_hands(A)) - to_chat(user, "You detach [A] from [src].") - else - to_chat(user, "You detach [A] from [src] and it falls on the floor.") - - if(ishuman(loc)) - var/mob/living/carbon/human/H = loc - H.update_inv_w_uniform() - H.update_inv_wear_suit() - H.fan_hud_set_fandom() - - -/obj/item/clothing/under/examine(mob/user) - . = ..() - if(freshly_laundered) - . += "It looks fresh and clean." - if(can_adjust) - if(adjusted == ALT_STYLE) - . += "Alt-click on [src] to wear it normally." - else - . += "Alt-click on [src] to wear it casually." - if (has_sensor == BROKEN_SENSORS) - . += "Its sensors appear to be shorted out." - else if(has_sensor > NO_SENSORS) - switch(sensor_mode) - if(SENSOR_OFF) - . += "Its sensors appear to be disabled." - if(SENSOR_LIVING) - . += "Its binary life sensors appear to be enabled." - if(SENSOR_VITALS) - . += "Its vital tracker appears to be enabled." - if(SENSOR_COORDS) - . += "Its vital tracker and tracking beacon appear to be enabled." - if(attached_accessory) - . += "\A [attached_accessory] is attached to it." - -/obj/item/clothing/under/rank - dying_key = DYE_REGISTRY_UNDER +/obj/item/clothing/under + name = "under" + icon = 'icons/obj/clothing/under/default.dmi' + mob_overlay_icon = 'icons/mob/clothing/under/default.dmi' + body_parts_covered = CHEST|GROIN|LEGS|ARMS + permeability_coefficient = 0.9 + slot_flags = ITEM_SLOT_ICLOTHING + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + equip_sound = 'sound/items/equip/jumpsuit_equip.ogg' + drop_sound = 'sound/items/handling/cloth_drop.ogg' + pickup_sound = 'sound/items/handling/cloth_pickup.ogg' + cuttable = TRUE + clothamnt = 5 + var/fitted = FEMALE_UNIFORM_FULL // For use in alternate clothing styles for women + var/has_sensor = HAS_SENSORS // For the crew computer + var/random_sensor = TRUE + var/sensor_mode = NO_SENSORS + var/can_adjust = TRUE + var/adjusted = NORMAL_STYLE + var/alt_covers_chest = FALSE // for adjusted/rolled-down jumpsuits, FALSE = exposes chest and arms, TRUE = exposes arms only + var/obj/item/clothing/accessory/attached_accessory + var/mutable_appearance/accessory_overlay + var/mutantrace_variation = NO_MUTANTRACE_VARIATION //Are there special sprites for specific situations? Don't use this unless you need to. + var/freshly_laundered = FALSE + +/obj/item/clothing/under/worn_overlays(isinhands = FALSE) + . = list() + if(!isinhands) + if(damaged_clothes) + . += mutable_appearance('icons/effects/item_damage.dmi', "damageduniform") + if(HAS_BLOOD_DNA(src)) + . += mutable_appearance('icons/effects/blood.dmi', "uniformblood") + if(accessory_overlay) + . += accessory_overlay + +/obj/item/clothing/under/attackby(obj/item/I, mob/user, params) + if((has_sensor == BROKEN_SENSORS) && istype(I, /obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/C = I + C.use(1) + has_sensor = HAS_SENSORS + to_chat(user,"You repair the suit sensors on [src] with [C].") + return 1 + if(!attach_accessory(I, user)) + return ..() + +/obj/item/clothing/under/update_clothes_damaged_state(damaging = TRUE) + ..() + if(ismob(loc)) + var/mob/M = loc + M.update_inv_w_uniform() + if(has_sensor > NO_SENSORS) + has_sensor = BROKEN_SENSORS + +/obj/item/clothing/under/Initialize() + . = ..() + if(random_sensor) + //make the sensor mode favor higher levels, except coords. + sensor_mode = pick(SENSOR_OFF, SENSOR_LIVING, SENSOR_LIVING, SENSOR_VITALS, SENSOR_VITALS, SENSOR_VITALS, SENSOR_COORDS, SENSOR_COORDS) + +/obj/item/clothing/under/emp_act() + . = ..() + if(has_sensor > NO_SENSORS) + sensor_mode = pick(SENSOR_OFF, SENSOR_OFF, SENSOR_OFF, SENSOR_LIVING, SENSOR_LIVING, SENSOR_VITALS, SENSOR_VITALS, SENSOR_COORDS) + if(ismob(loc)) + var/mob/M = loc + to_chat(M,"The sensors on the [src] change rapidly!") + +/obj/item/clothing/under/equipped(mob/user, slot) + ..() + if(adjusted) + adjusted = NORMAL_STYLE + fitted = initial(fitted) + if(!alt_covers_chest) + body_parts_covered |= CHEST + + if(mutantrace_variation && ishuman(user)) + var/mob/living/carbon/human/H = user + if(DIGITIGRADE in H.dna.species.species_traits) + adjusted = DIGITIGRADE_STYLE + H.update_inv_w_uniform() + + if(slot == ITEM_SLOT_ICLOTHING && freshly_laundered) + freshly_laundered = FALSE + SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "fresh_laundry", /datum/mood_event/fresh_laundry) + + if(attached_accessory && slot != ITEM_SLOT_HANDS && ishuman(user)) + var/mob/living/carbon/human/H = user + attached_accessory.on_uniform_equip(src, user) + H.fan_hud_set_fandom() + if(attached_accessory.above_suit) + H.update_inv_wear_suit() + +/obj/item/clothing/under/dropped(mob/user) + if(attached_accessory) + attached_accessory.on_uniform_dropped(src, user) + if(ishuman(user)) + var/mob/living/carbon/human/H = user + H.fan_hud_set_fandom() + if(attached_accessory.above_suit) + H.update_inv_wear_suit() + + ..() + +/obj/item/clothing/under/proc/attach_accessory(obj/item/I, mob/user, notifyAttach = 1) + . = FALSE + if(istype(I, /obj/item/clothing/accessory)) + var/obj/item/clothing/accessory/A = I + if(attached_accessory) + if(user) + to_chat(user, "[src] already has an accessory.") + return + else + + if(!A.can_attach_accessory(src, user)) //Make sure the suit has a place to put the accessory. + return + if(user && !user.temporarilyRemoveItemFromInventory(I)) + return + if(!A.attach(src, user)) + return + + if(user && notifyAttach) + to_chat(user, "You attach [I] to [src].") + + var/accessory_color = attached_accessory.icon_state + accessory_overlay = mutable_appearance('icons/mob/clothing/accessories.dmi', "[accessory_color]") + accessory_overlay.alpha = attached_accessory.alpha + accessory_overlay.color = attached_accessory.color + + if(ishuman(loc)) + var/mob/living/carbon/human/H = loc + H.update_inv_w_uniform() + H.update_inv_wear_suit() + H.fan_hud_set_fandom() + + return TRUE + +/obj/item/clothing/under/proc/remove_accessory(mob/user) + if(!isliving(user)) + return + if(!can_use(user)) + return + + if(attached_accessory) + var/obj/item/clothing/accessory/A = attached_accessory + attached_accessory.detach(src, user) + if(user.put_in_hands(A)) + to_chat(user, "You detach [A] from [src].") + else + to_chat(user, "You detach [A] from [src] and it falls on the floor.") + + if(ishuman(loc)) + var/mob/living/carbon/human/H = loc + H.update_inv_w_uniform() + H.update_inv_wear_suit() + H.fan_hud_set_fandom() + + +/obj/item/clothing/under/examine(mob/user) + . = ..() + if(freshly_laundered) + . += "It looks fresh and clean." + if(can_adjust) + if(adjusted == ALT_STYLE) + . += "Alt-click on [src] to wear it normally." + else + . += "Alt-click on [src] to wear it casually." + if (has_sensor == BROKEN_SENSORS) + . += "Its sensors appear to be shorted out." + else if(has_sensor > NO_SENSORS) + switch(sensor_mode) + if(SENSOR_OFF) + . += "Its sensors appear to be disabled." + if(SENSOR_LIVING) + . += "Its binary life sensors appear to be enabled." + if(SENSOR_VITALS) + . += "Its vital tracker appears to be enabled." + if(SENSOR_COORDS) + . += "Its vital tracker and tracking beacon appear to be enabled." + if(attached_accessory) + . += "\A [attached_accessory] is attached to it." + +/obj/item/clothing/under/rank + dying_key = DYE_REGISTRY_UNDER diff --git a/code/modules/clothing/under/color.dm b/code/modules/clothing/under/color.dm index beec7b720ffc..e87fc138f213 100644 --- a/code/modules/clothing/under/color.dm +++ b/code/modules/clothing/under/color.dm @@ -1,231 +1,227 @@ -/obj/item/clothing/under/color - desc = "A standard issue colored jumpsuit. Variety is the spice of life!" - dying_key = DYE_REGISTRY_UNDER - icon = 'icons/obj/clothing/under/color.dmi' - mob_overlay_icon = 'icons/mob/clothing/under/color.dmi' - -/obj/item/clothing/under/color/jumpskirt - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/color/random - icon_state = "random_jumpsuit" - -/obj/item/clothing/under/color/random/Initialize() - ..() - var/obj/item/clothing/under/color/C = pick(subtypesof(/obj/item/clothing/under/color) - typesof(/obj/item/clothing/under/color/jumpskirt) - /obj/item/clothing/under/color/random - /obj/item/clothing/under/color/grey/glorf - /obj/item/clothing/under/color/black/ghost) - if(ishuman(loc)) - var/mob/living/carbon/human/H = loc - H.equip_to_slot_or_del(new C(H), ITEM_SLOT_ICLOTHING) //or else you end up with naked assistants running around everywhere... - else - new C(loc) - return INITIALIZE_HINT_QDEL - -/obj/item/clothing/under/color/jumpskirt/random - icon_state = "random_jumpsuit" //Skirt variant needed - -/obj/item/clothing/under/color/jumpskirt/random/Initialize() - ..() - var/obj/item/clothing/under/color/jumpskirt/C = pick(subtypesof(/obj/item/clothing/under/color/jumpskirt) - /obj/item/clothing/under/color/jumpskirt/random) - if(ishuman(loc)) - var/mob/living/carbon/human/H = loc - H.equip_to_slot_or_del(new C(H), ITEM_SLOT_ICLOTHING) - else - new C(loc) - return INITIALIZE_HINT_QDEL - -/obj/item/clothing/under/color/black - name = "black jumpsuit" - icon_state = "black" - item_state = "bl_suit" - resistance_flags = NONE - -/obj/item/clothing/under/color/jumpskirt/black - name = "black jumpskirt" - icon_state = "black_skirt" - item_state = "bl_suit" - -/obj/item/clothing/under/color/black/ghost - item_flags = DROPDEL - -/obj/item/clothing/under/color/black/ghost/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CULT_TRAIT) - -/obj/item/clothing/under/color/grey - name = "grey jumpsuit" - desc = "A tasteful grey jumpsuit that reminds you of the good old days." - icon_state = "grey" - item_state = "gy_suit" - -/obj/item/clothing/under/color/jumpskirt/grey - name = "grey jumpskirt" - desc = "A tasteful grey jumpskirt that reminds you of the good old days." - icon_state = "grey_skirt" - item_state = "gy_suit" - -/obj/item/clothing/under/color/grey/glorf - name = "ancient jumpsuit" - desc = "A terribly ragged and frayed grey jumpsuit. It looks like it hasn't been washed in over a decade." - -/obj/item/clothing/under/color/grey/glorf/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - owner.forcesay(GLOB.hit_appends) - return 0 - -/obj/item/clothing/under/color/blue - name = "blue jumpsuit" - icon_state = "blue" - item_state = "b_suit" - -/obj/item/clothing/under/color/jumpskirt/blue - name = "blue jumpskirt" - icon_state = "blue_skirt" - item_state = "b_suit" - -/obj/item/clothing/under/color/green - name = "green jumpsuit" - icon_state = "green" - item_state = "g_suit" - -/obj/item/clothing/under/color/jumpskirt/green - name = "green jumpskirt" - icon_state = "green_skirt" - item_state = "g_suit" - -/obj/item/clothing/under/color/orange - name = "orange jumpsuit" - desc = "Don't wear this near paranoid security officers." - icon_state = "orange" - item_state = "o_suit" - -/obj/item/clothing/under/color/jumpskirt/orange - name = "orange jumpskirt" - icon_state = "orange_skirt" - item_state = "o_suit" - -/obj/item/clothing/under/color/pink - name = "pink jumpsuit" - icon_state = "pink" - desc = "Just looking at this makes you feel fabulous." - item_state = "p_suit" - -/obj/item/clothing/under/color/jumpskirt/pink - name = "pink jumpskirt" - icon_state = "pink_skirt" - item_state = "p_suit" - -/obj/item/clothing/under/color/red - name = "red jumpsuit" - icon_state = "red" - item_state = "r_suit" - -/obj/item/clothing/under/color/jumpskirt/red - name = "red jumpskirt" - icon_state = "red_skirt" - item_state = "r_suit" - -/obj/item/clothing/under/color/white - name = "white jumpsuit" - icon_state = "white" - item_state = "w_suit" - -/obj/item/clothing/under/color/jumpskirt/white - name = "white jumpskirt" - icon_state = "white_skirt" - item_state = "w_suit" - -/obj/item/clothing/under/color/yellow - name = "yellow jumpsuit" - icon_state = "yellow" - item_state = "y_suit" - -/obj/item/clothing/under/color/jumpskirt/yellow - name = "yellow jumpskirt" - icon_state = "yellow_skirt" - item_state = "y_suit" - -/obj/item/clothing/under/color/darkblue - name = "darkblue jumpsuit" - icon_state = "darkblue" - item_state = "b_suit" - -/obj/item/clothing/under/color/jumpskirt/darkblue - name = "darkblue jumpskirt" - icon_state = "darkblue_skirt" - item_state = "b_suit" - -/obj/item/clothing/under/color/teal - name = "teal jumpsuit" - icon_state = "teal" - item_state = "b_suit" - -/obj/item/clothing/under/color/jumpskirt/teal - name = "teal jumpskirt" - icon_state = "teal_skirt" - item_state = "b_suit" - - -/obj/item/clothing/under/color/lightpurple - name = "purple jumpsuit" - icon_state = "lightpurple" - item_state = "p_suit" - -/obj/item/clothing/under/color/jumpskirt/lightpurple - name = "lightpurple jumpskirt" - icon_state = "lightpurple_skirt" - item_state = "p_suit" - -/obj/item/clothing/under/color/darkgreen - name = "darkgreen jumpsuit" - icon_state = "darkgreen" - item_state = "g_suit" - -/obj/item/clothing/under/color/jumpskirt/darkgreen - name = "darkgreen jumpskirt" - icon_state = "darkgreen_skirt" - item_state = "g_suit" - -/obj/item/clothing/under/color/lightbrown - name = "lightbrown jumpsuit" - icon_state = "lightbrown" - item_state = "lb_suit" - -/obj/item/clothing/under/color/jumpskirt/lightbrown - name = "lightbrown jumpskirt" - icon_state = "lightbrown_skirt" - item_state = "lb_suit" - -/obj/item/clothing/under/color/brown - name = "brown jumpsuit" - icon_state = "brown" - item_state = "lb_suit" - -/obj/item/clothing/under/color/jumpskirt/brown - name = "brown jumpskirt" - icon_state = "brown_skirt" - item_state = "lb_suit" - -/obj/item/clothing/under/color/maroon - name = "maroon jumpsuit" - icon_state = "maroon" - item_state = "r_suit" - -/obj/item/clothing/under/color/jumpskirt/maroon - name = "maroon jumpskirt" - icon_state = "maroon_skirt" - item_state = "r_suit" - -/obj/item/clothing/under/color/rainbow - name = "rainbow jumpsuit" - desc = "A multi-colored jumpsuit!" - icon_state = "rainbow" - item_state = "rainbow" - can_adjust = FALSE - -/obj/item/clothing/under/color/jumpskirt/rainbow - name = "rainbow jumpskirt" - desc = "A multi-colored jumpskirt!" - icon_state = "rainbow_skirt" - item_state = "rainbow" - can_adjust = FALSE +/obj/item/clothing/under/color + desc = "A standard issue colored jumpsuit. Variety is the spice of life!" + dying_key = DYE_REGISTRY_UNDER + icon = 'icons/obj/clothing/under/color.dmi' + mob_overlay_icon = 'icons/mob/clothing/under/color.dmi' + +/obj/item/clothing/under/color/jumpskirt + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/color/random + icon_state = "random_jumpsuit" + +/obj/item/clothing/under/color/random/Initialize() + ..() + var/obj/item/clothing/under/color/C = pick(subtypesof(/obj/item/clothing/under/color) - typesof(/obj/item/clothing/under/color/jumpskirt) - /obj/item/clothing/under/color/random - /obj/item/clothing/under/color/grey/ancient - /obj/item/clothing/under/color/black/ghost) + if(ishuman(loc)) + var/mob/living/carbon/human/H = loc + H.equip_to_slot_or_del(new C(H), ITEM_SLOT_ICLOTHING) //or else you end up with naked assistants running around everywhere... + else + new C(loc) + return INITIALIZE_HINT_QDEL + +/obj/item/clothing/under/color/jumpskirt/random + icon_state = "random_jumpsuit" //Skirt variant needed + +/obj/item/clothing/under/color/jumpskirt/random/Initialize() + ..() + var/obj/item/clothing/under/color/jumpskirt/C = pick(subtypesof(/obj/item/clothing/under/color/jumpskirt) - /obj/item/clothing/under/color/jumpskirt/random) + if(ishuman(loc)) + var/mob/living/carbon/human/H = loc + H.equip_to_slot_or_del(new C(H), ITEM_SLOT_ICLOTHING) + else + new C(loc) + return INITIALIZE_HINT_QDEL + +/obj/item/clothing/under/color/black + name = "black jumpsuit" + icon_state = "black" + item_state = "bl_suit" + resistance_flags = NONE + +/obj/item/clothing/under/color/jumpskirt/black + name = "black jumpskirt" + icon_state = "black_skirt" + item_state = "bl_suit" + +/obj/item/clothing/under/color/black/ghost + item_flags = DROPDEL + +/obj/item/clothing/under/color/black/ghost/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CULT_TRAIT) + +/obj/item/clothing/under/color/grey + name = "grey jumpsuit" + desc = "A tasteful grey jumpsuit that reminds you of the good old days." + icon_state = "grey" + item_state = "gy_suit" + +/obj/item/clothing/under/color/jumpskirt/grey + name = "grey jumpskirt" + desc = "A tasteful grey jumpskirt that reminds you of the good old days." + icon_state = "grey_skirt" + item_state = "gy_suit" + +/obj/item/clothing/under/color/grey/ancient + name = "ancient jumpsuit" + desc = "A terribly ragged and frayed grey jumpsuit. It looks like it hasn't been washed in over a decade." + +/obj/item/clothing/under/color/blue + name = "blue jumpsuit" + icon_state = "blue" + item_state = "b_suit" + +/obj/item/clothing/under/color/jumpskirt/blue + name = "blue jumpskirt" + icon_state = "blue_skirt" + item_state = "b_suit" + +/obj/item/clothing/under/color/green + name = "green jumpsuit" + icon_state = "green" + item_state = "g_suit" + +/obj/item/clothing/under/color/jumpskirt/green + name = "green jumpskirt" + icon_state = "green_skirt" + item_state = "g_suit" + +/obj/item/clothing/under/color/orange + name = "orange jumpsuit" + desc = "Don't wear this near paranoid security officers." + icon_state = "orange" + item_state = "o_suit" + +/obj/item/clothing/under/color/jumpskirt/orange + name = "orange jumpskirt" + icon_state = "orange_skirt" + item_state = "o_suit" + +/obj/item/clothing/under/color/pink + name = "pink jumpsuit" + icon_state = "pink" + desc = "Just looking at this makes you feel fabulous." + item_state = "p_suit" + +/obj/item/clothing/under/color/jumpskirt/pink + name = "pink jumpskirt" + icon_state = "pink_skirt" + item_state = "p_suit" + +/obj/item/clothing/under/color/red + name = "red jumpsuit" + icon_state = "red" + item_state = "r_suit" + +/obj/item/clothing/under/color/jumpskirt/red + name = "red jumpskirt" + icon_state = "red_skirt" + item_state = "r_suit" + +/obj/item/clothing/under/color/white + name = "white jumpsuit" + icon_state = "white" + item_state = "w_suit" + +/obj/item/clothing/under/color/jumpskirt/white + name = "white jumpskirt" + icon_state = "white_skirt" + item_state = "w_suit" + +/obj/item/clothing/under/color/yellow + name = "yellow jumpsuit" + icon_state = "yellow" + item_state = "y_suit" + +/obj/item/clothing/under/color/jumpskirt/yellow + name = "yellow jumpskirt" + icon_state = "yellow_skirt" + item_state = "y_suit" + +/obj/item/clothing/under/color/darkblue + name = "darkblue jumpsuit" + icon_state = "darkblue" + item_state = "b_suit" + +/obj/item/clothing/under/color/jumpskirt/darkblue + name = "darkblue jumpskirt" + icon_state = "darkblue_skirt" + item_state = "b_suit" + +/obj/item/clothing/under/color/teal + name = "teal jumpsuit" + icon_state = "teal" + item_state = "b_suit" + +/obj/item/clothing/under/color/jumpskirt/teal + name = "teal jumpskirt" + icon_state = "teal_skirt" + item_state = "b_suit" + + +/obj/item/clothing/under/color/lightpurple + name = "purple jumpsuit" + icon_state = "lightpurple" + item_state = "p_suit" + +/obj/item/clothing/under/color/jumpskirt/lightpurple + name = "lightpurple jumpskirt" + icon_state = "lightpurple_skirt" + item_state = "p_suit" + +/obj/item/clothing/under/color/darkgreen + name = "darkgreen jumpsuit" + icon_state = "darkgreen" + item_state = "g_suit" + +/obj/item/clothing/under/color/jumpskirt/darkgreen + name = "darkgreen jumpskirt" + icon_state = "darkgreen_skirt" + item_state = "g_suit" + +/obj/item/clothing/under/color/lightbrown + name = "lightbrown jumpsuit" + icon_state = "lightbrown" + item_state = "lb_suit" + +/obj/item/clothing/under/color/jumpskirt/lightbrown + name = "lightbrown jumpskirt" + icon_state = "lightbrown_skirt" + item_state = "lb_suit" + +/obj/item/clothing/under/color/brown + name = "brown jumpsuit" + icon_state = "brown" + item_state = "lb_suit" + +/obj/item/clothing/under/color/jumpskirt/brown + name = "brown jumpskirt" + icon_state = "brown_skirt" + item_state = "lb_suit" + +/obj/item/clothing/under/color/maroon + name = "maroon jumpsuit" + icon_state = "maroon" + item_state = "r_suit" + +/obj/item/clothing/under/color/jumpskirt/maroon + name = "maroon jumpskirt" + icon_state = "maroon_skirt" + item_state = "r_suit" + +/obj/item/clothing/under/color/rainbow + name = "rainbow jumpsuit" + desc = "A multi-colored jumpsuit!" + icon_state = "rainbow" + item_state = "rainbow" + can_adjust = FALSE + +/obj/item/clothing/under/color/jumpskirt/rainbow + name = "rainbow jumpskirt" + desc = "A multi-colored jumpskirt!" + icon_state = "rainbow_skirt" + item_state = "rainbow" + can_adjust = FALSE diff --git a/code/modules/clothing/under/jobs/engineering.dm b/code/modules/clothing/under/jobs/engineering.dm index 50c37a105277..b711ba34695e 100644 --- a/code/modules/clothing/under/jobs/engineering.dm +++ b/code/modules/clothing/under/jobs/engineering.dm @@ -1,63 +1,63 @@ -//Contains: Engineering department jumpsuits - -/obj/item/clothing/under/rank/engineering - icon = 'icons/obj/clothing/under/engineering.dmi' - mob_overlay_icon = 'icons/mob/clothing/under/engineering.dmi' - -/obj/item/clothing/under/rank/engineering/chief_engineer - desc = "It's a high visibility jumpsuit given to those engineers insane enough to achieve the rank of \"Chief Engineer\". It has minor radiation shielding." - name = "chief engineer's jumpsuit" - icon_state = "chiefengineer" - item_state = "gy_suit" - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 80, "acid" = 40) - resistance_flags = NONE - -/obj/item/clothing/under/rank/engineering/chief_engineer/skirt - name = "chief engineer's jumpskirt" - desc = "It's a high visibility jumpskirt given to those engineers insane enough to achieve the rank of \"Chief Engineer\". It has minor radiation shielding." - icon_state = "chief_skirt" - item_state = "gy_suit" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/rank/engineering/atmospheric_technician - desc = "It's a jumpsuit worn by atmospheric technicians." - name = "atmospheric technician's jumpsuit" - icon_state = "atmos" - item_state = "atmos_suit" - resistance_flags = NONE - -/obj/item/clothing/under/rank/engineering/atmospheric_technician/skirt - name = "atmospheric technician's jumpskirt" - desc = "It's a jumpskirt worn by atmospheric technicians." - icon_state = "atmos_skirt" - item_state = "atmos_suit" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/rank/engineering/engineer - desc = "It's an orange high visibility jumpsuit worn by engineers. It has minor radiation shielding." - name = "engineer's jumpsuit" - icon_state = "engine" - item_state = "engi_suit" - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 60, "acid" = 20) - resistance_flags = NONE - -/obj/item/clothing/under/rank/engineering/engineer/hazard - name = "engineer's hazard jumpsuit" - desc = "A high visibility jumpsuit made from heat and radiation resistant materials." - icon_state = "hazard" - item_state = "suit-orange" - alt_covers_chest = TRUE - -/obj/item/clothing/under/rank/engineering/engineer/skirt - name = "engineer's jumpskirt" - desc = "It's an orange high visibility jumpskirt worn by engineers." - icon_state = "engine_skirt" - item_state = "engi_suit" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - +//Contains: Engineering department jumpsuits + +/obj/item/clothing/under/rank/engineering + icon = 'icons/obj/clothing/under/engineering.dmi' + mob_overlay_icon = 'icons/mob/clothing/under/engineering.dmi' + +/obj/item/clothing/under/rank/engineering/chief_engineer + desc = "It's a high visibility jumpsuit given to those engineers insane enough to achieve the rank of \"Chief Engineer\". It has minor radiation shielding." + name = "chief engineer's jumpsuit" + icon_state = "chiefengineer" + item_state = "gy_suit" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 80, "acid" = 40) + resistance_flags = NONE + +/obj/item/clothing/under/rank/engineering/chief_engineer/skirt + name = "chief engineer's jumpskirt" + desc = "It's a high visibility jumpskirt given to those engineers insane enough to achieve the rank of \"Chief Engineer\". It has minor radiation shielding." + icon_state = "chief_skirt" + item_state = "gy_suit" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/rank/engineering/atmospheric_technician + desc = "It's a jumpsuit worn by atmospheric technicians." + name = "atmospheric technician's jumpsuit" + icon_state = "atmos" + item_state = "atmos_suit" + resistance_flags = NONE + +/obj/item/clothing/under/rank/engineering/atmospheric_technician/skirt + name = "atmospheric technician's jumpskirt" + desc = "It's a jumpskirt worn by atmospheric technicians." + icon_state = "atmos_skirt" + item_state = "atmos_suit" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/rank/engineering/engineer + desc = "It's an orange high visibility jumpsuit worn by engineers. It has minor radiation shielding." + name = "engineer's jumpsuit" + icon_state = "engine" + item_state = "engi_suit" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 60, "acid" = 20) + resistance_flags = NONE + +/obj/item/clothing/under/rank/engineering/engineer/hazard + name = "engineer's hazard jumpsuit" + desc = "A high visibility jumpsuit made from heat and radiation resistant materials." + icon_state = "hazard" + item_state = "suit-orange" + alt_covers_chest = TRUE + +/obj/item/clothing/under/rank/engineering/engineer/skirt + name = "engineer's jumpskirt" + desc = "It's an orange high visibility jumpskirt worn by engineers." + icon_state = "engine_skirt" + item_state = "engi_suit" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + diff --git a/code/modules/clothing/under/jobs/security.dm b/code/modules/clothing/under/jobs/security.dm index 6b3dc0f0353a..997766499819 100644 --- a/code/modules/clothing/under/jobs/security.dm +++ b/code/modules/clothing/under/jobs/security.dm @@ -1,258 +1,258 @@ -/* - * Contains: - * Security - * Detective - * Navy uniforms - */ - -/* - * Security - */ - -/obj/item/clothing/under/rank/security - icon = 'icons/obj/clothing/under/security.dmi' - mob_overlay_icon = 'icons/mob/clothing/under/security.dmi' - -/obj/item/clothing/under/rank/security/officer - name = "security jumpsuit" - desc = "A tactical security jumpsuit for officers complete with Nanotrasen belt buckle." - icon_state = "rsecurity" - item_state = "r_suit" - armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 30) - strip_delay = 50 - alt_covers_chest = TRUE - sensor_mode = SENSOR_COORDS - random_sensor = FALSE - -///obj/item/clothing/under/rank/security/officer/grey -//name = "grey security jumpsuit" -/obj/item/clothing/under/rank/security/officer/white //WaspStation edit - Better security jumpsuit sprites - name = "white security jumpsuit" - desc = "A tactical relic of years past before Nanotrasen decided it was cheaper to dye the suits red instead of washing out the blood." - icon_state = "security" - item_state = "gy_suit" - -/obj/item/clothing/under/rank/security/officer/skirt - name = "security jumpskirt" - desc = "A \"tactical\" security jumpsuit with the legs replaced by a skirt." - icon_state = "secskirt" - item_state = "r_suit" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE //you know now that i think of it if you adjust the skirt and the sprite disappears isn't that just like flashing everyone - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/rank/security/officer/blueshirt - name = "blue shirt and tie" - desc = "I'm a little busy right now, Calhoun." - icon_state = "blueshift" - item_state = "blueshift" - can_adjust = FALSE - -/obj/item/clothing/under/rank/security/officer/formal - name = "security officer's formal uniform" - desc = "The latest in fashionable security outfits." - icon_state = "officerblueclothes" - item_state = "officerblueclothes" - alt_covers_chest = TRUE - -/obj/item/clothing/under/rank/security/constable - name = "constable outfit" - desc = "A british looking outfit." - icon_state = "constable" - item_state = "constable" - can_adjust = FALSE - custom_price = 200 - -/obj/item/clothing/under/rank/security/warden - name = "security suit" - desc = "A formal security suit for officers complete with Nanotrasen belt buckle." - icon_state = "rwarden" - item_state = "r_suit" - armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 30) - strip_delay = 50 - alt_covers_chest = TRUE - sensor_mode = 3 - random_sensor = FALSE - -///obj/item/clothing/under/rank/security/warden/grey -// name = "grey security suit" -/obj/item/clothing/under/rank/security/warden/white //WaspStation edit - Better security jumpsuit sprites - name = "white security suit" - desc = "A formal relic of years past before Nanotrasen decided it was cheaper to dye the suits red instead of washing out the blood." - icon_state = "warden" - item_state = "gy_suit" - -/obj/item/clothing/under/rank/security/warden/skirt - name = "warden's suitskirt" - desc = "A formal security suitskirt for officers complete with Nanotrasen belt buckle." - icon_state = "rwarden_skirt" - item_state = "r_suit" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/rank/security/warden/formal - desc = "The insignia on this uniform tells you that this uniform belongs to the Warden." - name = "warden's formal uniform" - icon_state = "wardenblueclothes" - item_state = "wardenblueclothes" - alt_covers_chest = TRUE - -/* - * Detective - */ -/obj/item/clothing/under/rank/security/detective - name = "hard-worn suit" - desc = "Someone who wears this means business." - icon_state = "detective" - item_state = "det" - armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 30) - strip_delay = 50 - alt_covers_chest = TRUE - sensor_mode = 3 - random_sensor = FALSE - -/obj/item/clothing/under/rank/security/detective/skirt - name = "detective's suitskirt" - desc = "Someone who wears this means business." - icon_state = "detective_skirt" - item_state = "det" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/rank/security/detective/grey - name = "noir suit" - desc = "A hard-boiled private investigator's grey suit, complete with tie clip." - icon_state = "greydet" - item_state = "greydet" - alt_covers_chest = TRUE - -/obj/item/clothing/under/rank/security/detective/grey/skirt - name = "noir suitskirt" - desc = "A hard-boiled private investigator's grey suitskirt, complete with tie clip." - icon_state = "greydet_skirt" - item_state = "greydet" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/* - * Head of Security - */ -/obj/item/clothing/under/rank/security/head_of_security - name = "head of security's jumpsuit" - desc = "A security jumpsuit decorated for those few with the dedication to achieve the position of Head of Security." - icon_state = "rhos" - item_state = "r_suit" - armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - strip_delay = 60 - alt_covers_chest = TRUE - sensor_mode = 3 - random_sensor = FALSE - -/obj/item/clothing/under/rank/security/head_of_security/skirt - name = "head of security's jumpskirt" - desc = "A security jumpskirt decorated for those few with the dedication to achieve the position of Head of Security." - icon_state = "rhos_skirt" - item_state = "r_suit" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -///obj/item/clothing/under/rank/security/head_of_security/grey -// name = "head of security's grey jumpsuit" -/obj/item/clothing/under/rank/security/head_of_security/white //WaspStation edit - Better security jumpsuit sprites - name = "head of security's white jumpsuit" - desc = "There are old men, and there are bold men, but there are very few old, bold men." - icon_state = "hos" - item_state = "gy_suit" - -/obj/item/clothing/under/rank/security/head_of_security/alt - name = "head of security's turtleneck" - desc = "A stylish alternative to the normal head of security jumpsuit, complete with tactical pants." - icon_state = "hosalt" - item_state = "bl_suit" - -/obj/item/clothing/under/rank/security/head_of_security/alt/skirt - name = "head of security's turtleneck skirt" - desc = "A stylish alternative to the normal head of security jumpsuit, complete with a tactical skirt." - icon_state = "hosalt_skirt" - item_state = "bl_suit" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/rank/security/head_of_security/parade - name = "head of security's parade uniform" - desc = "A male head of security's luxury-wear, for special occasions." - icon_state = "hos_parade_male" - item_state = "r_suit" - can_adjust = FALSE - -/obj/item/clothing/under/rank/security/head_of_security/parade/female - name = "head of security's parade uniform" - desc = "A female head of security's luxury-wear, for special occasions." - icon_state = "hos_parade_fem" - item_state = "r_suit" - fitted = FEMALE_UNIFORM_TOP - can_adjust = FALSE - -/obj/item/clothing/under/rank/security/head_of_security/formal - desc = "The insignia on this uniform tells you that this uniform belongs to the Head of Security." - name = "head of security's formal uniform" - icon_state = "hosblueclothes" - item_state = "hosblueclothes" - alt_covers_chest = TRUE - -/* - WaspStation edit - Better security jumpsuit sprites -*/ - -/* -*Blart Uniform -*/ -/obj/item/clothing/under/rank/security/officer/mallcop - name = "NT mall cop uniform" - desc = "The radio and badge are sewn on, what a crappy knock off. Secway not included." - icon_state = "mallcop" - item_state = "gy_suit" - can_adjust = FALSE - -/* - *Spacepol - */ - -/obj/item/clothing/under/rank/security/officer/spacepol - name = "police uniform" - desc = "Space not controlled by megacorporations, planets, or pirates is under the jurisdiction of Spacepol." - icon_state = "spacepol" - item_state = "spacepol" - can_adjust = FALSE - -/obj/item/clothing/under/rank/prisoner - name = "prison jumpsuit" - desc = "It's standardised Nanotrasen prisoner-wear. Its suit sensors are stuck in the \"Fully On\" position." - icon = 'icons/obj/clothing/under/security.dmi' - icon_state = "prisoner" - item_state = "o_suit" - mob_overlay_icon = 'icons/mob/clothing/under/security.dmi' - has_sensor = LOCKED_SENSORS - sensor_mode = SENSOR_COORDS - random_sensor = FALSE - -/obj/item/clothing/under/rank/prisoner/skirt - name = "prison jumpskirt" - desc = "It's standardised Nanotrasen prisoner-wear. Its suit sensors are stuck in the \"Fully On\" position." - icon_state = "prisoner_skirt" - item_state = "o_suit" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/rank/security/officer/beatcop - name = "space police uniform" - desc = "A police uniform often found in the lines at donut shops." - icon_state = "spacepolice_families" - item_state = "spacepolice_families" - can_adjust = FALSE +/* + * Contains: + * Security + * Detective + * Navy uniforms + */ + +/* + * Security + */ + +/obj/item/clothing/under/rank/security + icon = 'icons/obj/clothing/under/security.dmi' + mob_overlay_icon = 'icons/mob/clothing/under/security.dmi' + +/obj/item/clothing/under/rank/security/officer + name = "security jumpsuit" + desc = "A tactical security jumpsuit for officers complete with Nanotrasen belt buckle." + icon_state = "rsecurity" + item_state = "r_suit" + armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 30) + strip_delay = 50 + alt_covers_chest = TRUE + sensor_mode = SENSOR_COORDS + random_sensor = FALSE + +///obj/item/clothing/under/rank/security/officer/grey +//name = "grey security jumpsuit" +/obj/item/clothing/under/rank/security/officer/white //WaspStation edit - Better security jumpsuit sprites + name = "white security jumpsuit" + desc = "A tactical relic of years past before Nanotrasen decided it was cheaper to dye the suits red instead of washing out the blood." + icon_state = "security" + item_state = "gy_suit" + +/obj/item/clothing/under/rank/security/officer/skirt + name = "security jumpskirt" + desc = "A \"tactical\" security jumpsuit with the legs replaced by a skirt." + icon_state = "secskirt" + item_state = "r_suit" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE //you know now that i think of it if you adjust the skirt and the sprite disappears isn't that just like flashing everyone + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/rank/security/officer/blueshirt + name = "blue shirt and tie" + desc = "I'm a little busy right now, Calhoun." + icon_state = "blueshift" + item_state = "blueshift" + can_adjust = FALSE + +/obj/item/clothing/under/rank/security/officer/formal + name = "security officer's formal uniform" + desc = "The latest in fashionable security outfits." + icon_state = "officerblueclothes" + item_state = "officerblueclothes" + alt_covers_chest = TRUE + +/obj/item/clothing/under/rank/security/constable + name = "constable outfit" + desc = "A british looking outfit." + icon_state = "constable" + item_state = "constable" + can_adjust = FALSE + custom_price = 200 + +/obj/item/clothing/under/rank/security/warden + name = "security suit" + desc = "A formal security suit for officers complete with Nanotrasen belt buckle." + icon_state = "rwarden" + item_state = "r_suit" + armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 30) + strip_delay = 50 + alt_covers_chest = TRUE + sensor_mode = 3 + random_sensor = FALSE + +///obj/item/clothing/under/rank/security/warden/grey +// name = "grey security suit" +/obj/item/clothing/under/rank/security/warden/white //WaspStation edit - Better security jumpsuit sprites + name = "white security suit" + desc = "A formal relic of years past before Nanotrasen decided it was cheaper to dye the suits red instead of washing out the blood." + icon_state = "warden" + item_state = "gy_suit" + +/obj/item/clothing/under/rank/security/warden/skirt + name = "warden's suitskirt" + desc = "A formal security suitskirt for officers complete with Nanotrasen belt buckle." + icon_state = "rwarden_skirt" + item_state = "r_suit" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/rank/security/warden/formal + desc = "The insignia on this uniform tells you that this uniform belongs to the Warden." + name = "warden's formal uniform" + icon_state = "wardenblueclothes" + item_state = "wardenblueclothes" + alt_covers_chest = TRUE + +/* + * Detective + */ +/obj/item/clothing/under/rank/security/detective + name = "hard-worn suit" + desc = "Someone who wears this means business." + icon_state = "detective" + item_state = "det" + armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 30) + strip_delay = 50 + alt_covers_chest = TRUE + sensor_mode = 3 + random_sensor = FALSE + +/obj/item/clothing/under/rank/security/detective/skirt + name = "detective's suitskirt" + desc = "Someone who wears this means business." + icon_state = "detective_skirt" + item_state = "det" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/rank/security/detective/grey + name = "noir suit" + desc = "A hard-boiled private investigator's grey suit, complete with tie clip." + icon_state = "greydet" + item_state = "greydet" + alt_covers_chest = TRUE + +/obj/item/clothing/under/rank/security/detective/grey/skirt + name = "noir suitskirt" + desc = "A hard-boiled private investigator's grey suitskirt, complete with tie clip." + icon_state = "greydet_skirt" + item_state = "greydet" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/* + * Head of Security + */ +/obj/item/clothing/under/rank/security/head_of_security + name = "head of security's jumpsuit" + desc = "A security jumpsuit decorated for those few with the dedication to achieve the position of Head of Security." + icon_state = "rhos" + item_state = "r_suit" + armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + strip_delay = 60 + alt_covers_chest = TRUE + sensor_mode = 3 + random_sensor = FALSE + +/obj/item/clothing/under/rank/security/head_of_security/skirt + name = "head of security's jumpskirt" + desc = "A security jumpskirt decorated for those few with the dedication to achieve the position of Head of Security." + icon_state = "rhos_skirt" + item_state = "r_suit" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +///obj/item/clothing/under/rank/security/head_of_security/grey +// name = "head of security's grey jumpsuit" +/obj/item/clothing/under/rank/security/head_of_security/white //WaspStation edit - Better security jumpsuit sprites + name = "head of security's white jumpsuit" + desc = "There are old men, and there are bold men, but there are very few old, bold men." + icon_state = "hos" + item_state = "gy_suit" + +/obj/item/clothing/under/rank/security/head_of_security/alt + name = "head of security's turtleneck" + desc = "A stylish alternative to the normal head of security jumpsuit, complete with tactical pants." + icon_state = "hosalt" + item_state = "bl_suit" + +/obj/item/clothing/under/rank/security/head_of_security/alt/skirt + name = "head of security's turtleneck skirt" + desc = "A stylish alternative to the normal head of security jumpsuit, complete with a tactical skirt." + icon_state = "hosalt_skirt" + item_state = "bl_suit" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/rank/security/head_of_security/parade + name = "head of security's parade uniform" + desc = "A male head of security's luxury-wear, for special occasions." + icon_state = "hos_parade_male" + item_state = "r_suit" + can_adjust = FALSE + +/obj/item/clothing/under/rank/security/head_of_security/parade/female + name = "head of security's parade uniform" + desc = "A female head of security's luxury-wear, for special occasions." + icon_state = "hos_parade_fem" + item_state = "r_suit" + fitted = FEMALE_UNIFORM_TOP + can_adjust = FALSE + +/obj/item/clothing/under/rank/security/head_of_security/formal + desc = "The insignia on this uniform tells you that this uniform belongs to the Head of Security." + name = "head of security's formal uniform" + icon_state = "hosblueclothes" + item_state = "hosblueclothes" + alt_covers_chest = TRUE + +/* + WaspStation edit - Better security jumpsuit sprites +*/ + +/* +*Blart Uniform +*/ +/obj/item/clothing/under/rank/security/officer/mallcop + name = "NT mall cop uniform" + desc = "The radio and badge are sewn on, what a crappy knock off. Secway not included." + icon_state = "mallcop" + item_state = "gy_suit" + can_adjust = FALSE + +/* + *Spacepol + */ + +/obj/item/clothing/under/rank/security/officer/spacepol + name = "police uniform" + desc = "Space not controlled by megacorporations, planets, or pirates is under the jurisdiction of Spacepol." + icon_state = "spacepol" + item_state = "spacepol" + can_adjust = FALSE + +/obj/item/clothing/under/rank/prisoner + name = "prison jumpsuit" + desc = "It's standardised Nanotrasen prisoner-wear. Its suit sensors are stuck in the \"Fully On\" position." + icon = 'icons/obj/clothing/under/security.dmi' + icon_state = "prisoner" + item_state = "o_suit" + mob_overlay_icon = 'icons/mob/clothing/under/security.dmi' + has_sensor = LOCKED_SENSORS + sensor_mode = SENSOR_COORDS + random_sensor = FALSE + +/obj/item/clothing/under/rank/prisoner/skirt + name = "prison jumpskirt" + desc = "It's standardised Nanotrasen prisoner-wear. Its suit sensors are stuck in the \"Fully On\" position." + icon_state = "prisoner_skirt" + item_state = "o_suit" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/rank/security/officer/beatcop + name = "space police uniform" + desc = "A police uniform often found in the lines at donut shops." + icon_state = "spacepolice_families" + item_state = "spacepolice_families" + can_adjust = FALSE diff --git a/code/modules/clothing/under/miscellaneous.dm b/code/modules/clothing/under/miscellaneous.dm index 85f58c8e8db0..c519082aa8d3 100644 --- a/code/modules/clothing/under/miscellaneous.dm +++ b/code/modules/clothing/under/miscellaneous.dm @@ -1,166 +1,166 @@ -/obj/item/clothing/under/misc - icon = 'icons/obj/clothing/under/misc.dmi' - mob_overlay_icon = 'icons/mob/clothing/under/misc.dmi' - -/obj/item/clothing/under/misc/pj - name = "\improper PJs" - desc = "A comfy set of sleepwear, for taking naps or being lazy instead of working." - can_adjust = FALSE - item_state = "w_suit" - -/obj/item/clothing/under/misc/pj/red - icon_state = "red_pyjamas" - -/obj/item/clothing/under/misc/pj/blue - icon_state = "blue_pyjamas" - -/obj/item/clothing/under/misc/patriotsuit - name = "Patriotic Suit" - desc = "Motorcycle not included." - icon_state = "ek" - item_state = "ek" - can_adjust = FALSE - -/obj/item/clothing/under/misc/mailman - name = "mailman's jumpsuit" - desc = "'Special delivery!'" - icon_state = "mailman" - item_state = "b_suit" - -/obj/item/clothing/under/misc/psyche - name = "psychedelic jumpsuit" - desc = "Groovy!" - icon_state = "psyche" - item_state = "p_suit" - -/obj/item/clothing/under/misc/vice_officer - name = "vice officer's jumpsuit" - desc = "It's the standard issue pretty-boy outfit, as seen on Holo-Vision." - icon_state = "vice" - item_state = "gy_suit" - can_adjust = FALSE - -/obj/item/clothing/under/misc/adminsuit - name = "administrative cybernetic jumpsuit" - icon = 'icons/obj/clothing/under/syndicate.dmi' - icon_state = "syndicate" - item_state = "bl_suit" - mob_overlay_icon = 'icons/mob/clothing/under/syndicate.dmi' - desc = "A cybernetically enhanced jumpsuit used for administrative duties." - gas_transfer_coefficient = 0.01 - permeability_coefficient = 0.01 - body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS - armor = list("melee" = 100, "bullet" = 100, "laser" = 100,"energy" = 100, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100) - cold_protection = CHEST | GROIN | LEGS | FEET | ARMS | HANDS - min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT - heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS - max_heat_protection_temperature = SPACE_SUIT_MAX_TEMP_PROTECT - can_adjust = FALSE - resistance_flags = FIRE_PROOF | ACID_PROOF - cuttable = FALSE - -/obj/item/clothing/under/misc/burial - name = "burial garments" - desc = "Traditional burial garments from the early 22nd century." - icon_state = "burial" - item_state = "burial" - can_adjust = FALSE - has_sensor = NO_SENSORS - -/obj/item/clothing/under/misc/overalls - name = "laborer's overalls" - desc = "A set of durable overalls for getting the job done." - icon_state = "overalls" - item_state = "lb_suit" - can_adjust = FALSE - custom_price = 60 - -/obj/item/clothing/under/misc/assistantformal - name = "assistant's formal uniform" - desc = "An assistant's formal-wear. Why an assistant needs formal-wear is still unknown." - icon_state = "assistant_formal" - item_state = "gy_suit" - can_adjust = FALSE - -/obj/item/clothing/under/plasmaman - name = "plasma envirosuit" - desc = "A special containment suit that allows plasma-based lifeforms to exist safely in an oxygenated environment, and automatically extinguishes them in a crisis. Despite being airtight, it's not spaceworthy." - icon_state = "plasmaman" - item_state = "plasmaman" - icon = 'icons/obj/clothing/under/plasmaman.dmi' - mob_overlay_icon = 'icons/mob/clothing/under/plasmaman.dmi' - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 0, "fire" = 95, "acid" = 95) - body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS - can_adjust = FALSE - strip_delay = 80 - var/next_extinguish = 0 - var/extinguish_cooldown = 100 - var/extinguishes_left = 5 - cuttable = FALSE - -/obj/item/clothing/under/plasmaman/skirt //WS edit plasmaman customization - name = "plasma enviroskirt" - icon_state = "plasmaskirt" - item_state = "plasmaskirt" - - -/obj/item/clothing/under/plasmaman/examine(mob/user) - . = ..() - . += "There are [extinguishes_left] extinguisher charges left in this suit." - -/obj/item/clothing/under/plasmaman/proc/Extinguish(mob/living/carbon/human/H) - if(!istype(H)) - return - - if(H.on_fire) - if(extinguishes_left) - if(next_extinguish > world.time) - return - next_extinguish = world.time + extinguish_cooldown - extinguishes_left-- - H.visible_message("[H]'s suit automatically extinguishes [H.p_them()]!","Your suit automatically extinguishes you.") - H.ExtinguishMob() - new /obj/effect/particle_effect/water(get_turf(H)) - return 0 - -/obj/item/clothing/under/plasmaman/attackby(obj/item/E, mob/user, params) - ..() - if (istype(E, /obj/item/extinguisher_refill)) - if (extinguishes_left == 5) - to_chat(user, "The inbuilt extinguisher is full.") - else - extinguishes_left = 5 - to_chat(user, "You refill the suit's built-in extinguisher, using up the cartridge.") - qdel(E) - -/obj/item/extinguisher_refill - name = "envirosuit extinguisher cartridge" - desc = "A cartridge loaded with a compressed extinguisher mix, used to refill the automatic extinguisher on plasma envirosuits." - icon_state = "plasmarefill" - icon = 'icons/obj/device.dmi' - -/obj/item/clothing/under/misc/durathread - name = "durathread jumpsuit" - desc = "A jumpsuit made from durathread, its resilient fibres provide some protection to the wearer." - icon_state = "durathread" - item_state = "durathread" - can_adjust = FALSE - armor = list("melee" = 10, "laser" = 10, "fire" = 40, "acid" = 10, "bomb" = 5) - cuttable = FALSE - -/obj/item/clothing/under/misc/bouncer - name = "bouncer uniform" - desc = "A uniform made from a little bit more resistant fibers, makes you seem like a cool guy." - icon_state = "bouncer" - item_state = "bouncer" - can_adjust = FALSE - armor = list("melee" = 5, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 30) - -/obj/item/clothing/under/misc/coordinator - name = "coordinator jumpsuit" - desc = "A jumpsuit made by party people, from party people, for party people." - icon = 'icons/obj/clothing/under/captain.dmi' - mob_overlay_icon = 'icons/mob/clothing/under/captain.dmi' - icon_state = "captain_parade" - item_state = "by_suit" - can_adjust = FALSE +/obj/item/clothing/under/misc + icon = 'icons/obj/clothing/under/misc.dmi' + mob_overlay_icon = 'icons/mob/clothing/under/misc.dmi' + +/obj/item/clothing/under/misc/pj + name = "\improper PJs" + desc = "A comfy set of sleepwear, for taking naps or being lazy instead of working." + can_adjust = FALSE + item_state = "w_suit" + +/obj/item/clothing/under/misc/pj/red + icon_state = "red_pyjamas" + +/obj/item/clothing/under/misc/pj/blue + icon_state = "blue_pyjamas" + +/obj/item/clothing/under/misc/patriotsuit + name = "Patriotic Suit" + desc = "Motorcycle not included." + icon_state = "ek" + item_state = "ek" + can_adjust = FALSE + +/obj/item/clothing/under/misc/mailman + name = "mailman's jumpsuit" + desc = "'Special delivery!'" + icon_state = "mailman" + item_state = "b_suit" + +/obj/item/clothing/under/misc/psyche + name = "psychedelic jumpsuit" + desc = "Groovy!" + icon_state = "psyche" + item_state = "p_suit" + +/obj/item/clothing/under/misc/vice_officer + name = "vice officer's jumpsuit" + desc = "It's the standard issue pretty-boy outfit, as seen on Holo-Vision." + icon_state = "vice" + item_state = "gy_suit" + can_adjust = FALSE + +/obj/item/clothing/under/misc/adminsuit + name = "administrative cybernetic jumpsuit" + icon = 'icons/obj/clothing/under/syndicate.dmi' + icon_state = "syndicate" + item_state = "bl_suit" + mob_overlay_icon = 'icons/mob/clothing/under/syndicate.dmi' + desc = "A cybernetically enhanced jumpsuit used for administrative duties." + gas_transfer_coefficient = 0.01 + permeability_coefficient = 0.01 + body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS + armor = list("melee" = 100, "bullet" = 100, "laser" = 100,"energy" = 100, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100) + cold_protection = CHEST | GROIN | LEGS | FEET | ARMS | HANDS + min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT + heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS + max_heat_protection_temperature = SPACE_SUIT_MAX_TEMP_PROTECT + can_adjust = FALSE + resistance_flags = FIRE_PROOF | ACID_PROOF + cuttable = FALSE + +/obj/item/clothing/under/misc/burial + name = "burial garments" + desc = "Traditional burial garments from the early 22nd century." + icon_state = "burial" + item_state = "burial" + can_adjust = FALSE + has_sensor = NO_SENSORS + +/obj/item/clothing/under/misc/overalls + name = "laborer's overalls" + desc = "A set of durable overalls for getting the job done." + icon_state = "overalls" + item_state = "lb_suit" + can_adjust = FALSE + custom_price = 60 + +/obj/item/clothing/under/misc/assistantformal + name = "assistant's formal uniform" + desc = "An assistant's formal-wear. Why an assistant needs formal-wear is still unknown." + icon_state = "assistant_formal" + item_state = "gy_suit" + can_adjust = FALSE + +/obj/item/clothing/under/plasmaman + name = "plasma envirosuit" + desc = "A special containment suit that allows plasma-based lifeforms to exist safely in an oxygenated environment, and automatically extinguishes them in a crisis. Despite being airtight, it's not spaceworthy." + icon_state = "plasmaman" + item_state = "plasmaman" + icon = 'icons/obj/clothing/under/plasmaman.dmi' + mob_overlay_icon = 'icons/mob/clothing/under/plasmaman.dmi' + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 0, "fire" = 95, "acid" = 95) + body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS + can_adjust = FALSE + strip_delay = 80 + var/next_extinguish = 0 + var/extinguish_cooldown = 100 + var/extinguishes_left = 5 + cuttable = FALSE + +/obj/item/clothing/under/plasmaman/skirt //WS edit plasmaman customization + name = "plasma enviroskirt" + icon_state = "plasmaskirt" + item_state = "plasmaskirt" + + +/obj/item/clothing/under/plasmaman/examine(mob/user) + . = ..() + . += "There are [extinguishes_left] extinguisher charges left in this suit." + +/obj/item/clothing/under/plasmaman/proc/Extinguish(mob/living/carbon/human/H) + if(!istype(H)) + return + + if(H.on_fire) + if(extinguishes_left) + if(next_extinguish > world.time) + return + next_extinguish = world.time + extinguish_cooldown + extinguishes_left-- + H.visible_message("[H]'s suit automatically extinguishes [H.p_them()]!","Your suit automatically extinguishes you.") + H.ExtinguishMob() + new /obj/effect/particle_effect/water(get_turf(H)) + return 0 + +/obj/item/clothing/under/plasmaman/attackby(obj/item/E, mob/user, params) + ..() + if (istype(E, /obj/item/extinguisher_refill)) + if (extinguishes_left == 5) + to_chat(user, "The inbuilt extinguisher is full.") + else + extinguishes_left = 5 + to_chat(user, "You refill the suit's built-in extinguisher, using up the cartridge.") + qdel(E) + +/obj/item/extinguisher_refill + name = "envirosuit extinguisher cartridge" + desc = "A cartridge loaded with a compressed extinguisher mix, used to refill the automatic extinguisher on plasma envirosuits." + icon_state = "plasmarefill" + icon = 'icons/obj/device.dmi' + +/obj/item/clothing/under/misc/durathread + name = "durathread jumpsuit" + desc = "A jumpsuit made from durathread, its resilient fibres provide some protection to the wearer." + icon_state = "durathread" + item_state = "durathread" + can_adjust = FALSE + armor = list("melee" = 10, "laser" = 10, "fire" = 40, "acid" = 10, "bomb" = 5) + cuttable = FALSE + +/obj/item/clothing/under/misc/bouncer + name = "bouncer uniform" + desc = "A uniform made from a little bit more resistant fibers, makes you seem like a cool guy." + icon_state = "bouncer" + item_state = "bouncer" + can_adjust = FALSE + armor = list("melee" = 5, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 30) + +/obj/item/clothing/under/misc/coordinator + name = "coordinator jumpsuit" + desc = "A jumpsuit made by party people, from party people, for party people." + icon = 'icons/obj/clothing/under/captain.dmi' + mob_overlay_icon = 'icons/mob/clothing/under/captain.dmi' + icon_state = "captain_parade" + item_state = "by_suit" + can_adjust = FALSE diff --git a/code/modules/clothing/under/pants.dm b/code/modules/clothing/under/pants.dm index a05d96f998c7..fc26de3d99df 100644 --- a/code/modules/clothing/under/pants.dm +++ b/code/modules/clothing/under/pants.dm @@ -1,69 +1,69 @@ -/obj/item/clothing/under/pants - gender = PLURAL - body_parts_covered = GROIN|LEGS - fitted = NO_FEMALE_UNIFORM - can_adjust = FALSE - custom_price = 60 - icon = 'icons/obj/clothing/under/shorts_pants.dmi' - mob_overlay_icon = 'icons/mob/clothing/under/shorts_pants.dmi' - -/obj/item/clothing/under/pants/classicjeans - name = "classic jeans" - desc = "You feel cooler already." - icon_state = "jeansclassic" - -/obj/item/clothing/under/pants/mustangjeans - name = "Must Hang jeans" - desc = "Made in the finest space jeans factory this side of Alpha Centauri." - icon_state = "jeansmustang" - custom_price = 180 - -/obj/item/clothing/under/pants/blackjeans - name = "black jeans" - desc = "Only for those who can pull it off." - icon_state = "jeansblack" - -/obj/item/clothing/under/pants/youngfolksjeans - name = "Young Folks jeans" - desc = "For those tired of boring old jeans. Relive the passion of your youth!" - icon_state = "jeansyoungfolks" - -/obj/item/clothing/under/pants/white - name = "white pants" - desc = "Plain white pants. Boring." - icon_state = "whitepants" - -/obj/item/clothing/under/pants/red - name = "red pants" - desc = "Bright red pants. Overflowing with personality." - icon_state = "redpants" - -/obj/item/clothing/under/pants/black - name = "black pants" - desc = "These pants are dark, like your soul." - icon_state = "blackpants" - -/obj/item/clothing/under/pants/tan - name = "tan pants" - desc = "Some tan pants. You look like a white collar worker with these on." - icon_state = "tanpants" - -/obj/item/clothing/under/pants/track - name = "track pants" - desc = "A pair of track pants, for the athletic." - icon_state = "trackpants" - -/obj/item/clothing/under/pants/jeans - name = "jeans" - desc = "A nondescript pair of tough blue jeans." - icon_state = "jeans" - -/obj/item/clothing/under/pants/khaki - name = "khaki pants" - desc = "A pair of dust beige khaki pants." - icon_state = "khaki" - -/obj/item/clothing/under/pants/camo - name = "camo pants" - desc = "A pair of woodland camouflage pants. Probably not the best choice for a space station." - icon_state = "camopants" +/obj/item/clothing/under/pants + gender = PLURAL + body_parts_covered = GROIN|LEGS + fitted = NO_FEMALE_UNIFORM + can_adjust = FALSE + custom_price = 60 + icon = 'icons/obj/clothing/under/shorts_pants.dmi' + mob_overlay_icon = 'icons/mob/clothing/under/shorts_pants.dmi' + +/obj/item/clothing/under/pants/classicjeans + name = "classic jeans" + desc = "You feel cooler already." + icon_state = "jeansclassic" + +/obj/item/clothing/under/pants/mustangjeans + name = "Must Hang jeans" + desc = "Made in the finest space jeans factory this side of Alpha Centauri." + icon_state = "jeansmustang" + custom_price = 180 + +/obj/item/clothing/under/pants/blackjeans + name = "black jeans" + desc = "Only for those who can pull it off." + icon_state = "jeansblack" + +/obj/item/clothing/under/pants/youngfolksjeans + name = "Young Folks jeans" + desc = "For those tired of boring old jeans. Relive the passion of your youth!" + icon_state = "jeansyoungfolks" + +/obj/item/clothing/under/pants/white + name = "white pants" + desc = "Plain white pants. Boring." + icon_state = "whitepants" + +/obj/item/clothing/under/pants/red + name = "red pants" + desc = "Bright red pants. Overflowing with personality." + icon_state = "redpants" + +/obj/item/clothing/under/pants/black + name = "black pants" + desc = "These pants are dark, like your soul." + icon_state = "blackpants" + +/obj/item/clothing/under/pants/tan + name = "tan pants" + desc = "Some tan pants. You look like a white collar worker with these on." + icon_state = "tanpants" + +/obj/item/clothing/under/pants/track + name = "track pants" + desc = "A pair of track pants, for the athletic." + icon_state = "trackpants" + +/obj/item/clothing/under/pants/jeans + name = "jeans" + desc = "A nondescript pair of tough blue jeans." + icon_state = "jeans" + +/obj/item/clothing/under/pants/khaki + name = "khaki pants" + desc = "A pair of dust beige khaki pants." + icon_state = "khaki" + +/obj/item/clothing/under/pants/camo + name = "camo pants" + desc = "A pair of woodland camouflage pants. Probably not the best choice for a space station." + icon_state = "camopants" diff --git a/code/modules/clothing/under/shorts.dm b/code/modules/clothing/under/shorts.dm index 1d1ad2836f35..0d76077e02ef 100644 --- a/code/modules/clothing/under/shorts.dm +++ b/code/modules/clothing/under/shorts.dm @@ -1,34 +1,34 @@ -/obj/item/clothing/under/shorts - name = "athletic shorts" - desc = "95% Polyester, 5% Spandex!" - gender = PLURAL - body_parts_covered = GROIN - fitted = NO_FEMALE_UNIFORM - mutantrace_variation = MUTANTRACE_VARIATION - can_adjust = FALSE - icon = 'icons/obj/clothing/under/shorts_pants.dmi' - mob_overlay_icon = 'icons/mob/clothing/under/shorts_pants.dmi' - -/obj/item/clothing/under/shorts/red - name = "red athletic shorts" - icon_state = "redshorts" - -/obj/item/clothing/under/shorts/green - name = "green athletic shorts" - icon_state = "greenshorts" - -/obj/item/clothing/under/shorts/blue - name = "blue athletic shorts" - icon_state = "blueshorts" - -/obj/item/clothing/under/shorts/black - name = "black athletic shorts" - icon_state = "blackshorts" - -/obj/item/clothing/under/shorts/grey - name = "grey athletic shorts" - icon_state = "greyshorts" - -/obj/item/clothing/under/shorts/purple - name = "purple athletic shorts" - icon_state = "purpleshorts" +/obj/item/clothing/under/shorts + name = "athletic shorts" + desc = "95% Polyester, 5% Spandex!" + gender = PLURAL + body_parts_covered = GROIN + fitted = NO_FEMALE_UNIFORM + mutantrace_variation = MUTANTRACE_VARIATION + can_adjust = FALSE + icon = 'icons/obj/clothing/under/shorts_pants.dmi' + mob_overlay_icon = 'icons/mob/clothing/under/shorts_pants.dmi' + +/obj/item/clothing/under/shorts/red + name = "red athletic shorts" + icon_state = "redshorts" + +/obj/item/clothing/under/shorts/green + name = "green athletic shorts" + icon_state = "greenshorts" + +/obj/item/clothing/under/shorts/blue + name = "blue athletic shorts" + icon_state = "blueshorts" + +/obj/item/clothing/under/shorts/black + name = "black athletic shorts" + icon_state = "blackshorts" + +/obj/item/clothing/under/shorts/grey + name = "grey athletic shorts" + icon_state = "greyshorts" + +/obj/item/clothing/under/shorts/purple + name = "purple athletic shorts" + icon_state = "purpleshorts" diff --git a/code/modules/clothing/under/syndicate.dm b/code/modules/clothing/under/syndicate.dm index a2ab06a161c3..d63112cf61cb 100644 --- a/code/modules/clothing/under/syndicate.dm +++ b/code/modules/clothing/under/syndicate.dm @@ -1,91 +1,91 @@ -/obj/item/clothing/under/syndicate - name = "tactical turtleneck" - desc = "A non-descript and slightly suspicious looking turtleneck with digital camouflage cargo pants." - icon_state = "syndicate" - item_state = "bl_suit" - has_sensor = NO_SENSORS - armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 40) - alt_covers_chest = TRUE - icon = 'icons/obj/clothing/under/syndicate.dmi' - mob_overlay_icon = 'icons/mob/clothing/under/syndicate.dmi' - -/obj/item/clothing/under/syndicate/skirt - name = "tactical skirtleneck" - desc = "A non-descript and slightly suspicious looking skirtleneck." - icon_state = "syndicate_skirt" - item_state = "bl_suit" - has_sensor = NO_SENSORS - armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 40) - alt_covers_chest = TRUE - fitted = FEMALE_UNIFORM_TOP - can_adjust = FALSE - -/obj/item/clothing/under/syndicate/bloodred - name = "blood-red sneaksuit" - desc = "It still counts as stealth if there are no witnesses." - icon_state = "bloodred_pajamas" - item_state = "bl_suit" - armor = list("melee" = 10, "bullet" = 10, "laser" = 10,"energy" = 10, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 50, "acid" = 40) - resistance_flags = FIRE_PROOF | ACID_PROOF - can_adjust = FALSE - -/obj/item/clothing/under/syndicate/bloodred/sleepytime - name = "blood-red pajamas" - desc = "Do operatives dream of nuclear sheep?" - icon_state = "bloodred_pajamas" - item_state = "bl_suit" - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 40) - -/obj/item/clothing/under/syndicate/tacticool - name = "tacticool turtleneck" - desc = "Just looking at it makes you want to buy an SKS, go into the woods, and -operate-." - icon_state = "tactifool" - item_state = "bl_suit" - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 40) - -/obj/item/clothing/under/syndicate/tacticool/skirt - name = "tacticool skirtleneck" - desc = "Just looking at it makes you want to buy an SKS, go into the woods, and -operate-." - icon_state = "tactifool_skirt" - item_state = "bl_suit" - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 40) - fitted = FEMALE_UNIFORM_TOP - can_adjust = FALSE - -/obj/item/clothing/under/syndicate/sniper - name = "Tactical turtleneck suit" - desc = "A double seamed tactical turtleneck disguised as a civilian grade silk suit. Intended for the most formal operator. The collar is really sharp." - icon = 'icons/obj/clothing/under/suits.dmi' - icon_state = "really_black_suit" - item_state = "bl_suit" - mob_overlay_icon = 'icons/mob/clothing/under/suits.dmi' - can_adjust = FALSE - -/obj/item/clothing/under/syndicate/camo - name = "camouflage fatigues" - desc = "A green military camouflage uniform." - icon_state = "camogreen" - item_state = "g_suit" - can_adjust = FALSE - -/obj/item/clothing/under/syndicate/soviet - name = "Ratnik 5 tracksuit" - desc = "Badly translated labels tell you to clean this in Vodka. Great for squatting in." - icon_state = "trackpants" - can_adjust = FALSE - armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - resistance_flags = NONE - -/obj/item/clothing/under/syndicate/combat - name = "combat uniform" - desc = "With a suit lined with this many pockets, you are ready to operate." - icon_state = "syndicate_combat" - can_adjust = FALSE - -/obj/item/clothing/under/syndicate/rus_army - name = "advanced military tracksuit" - desc = "Military grade tracksuits for frontline squatting." - icon_state = "rus_under" - can_adjust = FALSE - armor = list("melee" = 5, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - resistance_flags = NONE +/obj/item/clothing/under/syndicate + name = "tactical turtleneck" + desc = "A non-descript and slightly suspicious looking turtleneck with digital camouflage cargo pants." + icon_state = "syndicate" + item_state = "bl_suit" + has_sensor = NO_SENSORS + armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 40) + alt_covers_chest = TRUE + icon = 'icons/obj/clothing/under/syndicate.dmi' + mob_overlay_icon = 'icons/mob/clothing/under/syndicate.dmi' + +/obj/item/clothing/under/syndicate/skirt + name = "tactical skirtleneck" + desc = "A non-descript and slightly suspicious looking skirtleneck." + icon_state = "syndicate_skirt" + item_state = "bl_suit" + has_sensor = NO_SENSORS + armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 40) + alt_covers_chest = TRUE + fitted = FEMALE_UNIFORM_TOP + can_adjust = FALSE + +/obj/item/clothing/under/syndicate/bloodred + name = "blood-red sneaksuit" + desc = "It still counts as stealth if there are no witnesses." + icon_state = "bloodred_pajamas" + item_state = "bl_suit" + armor = list("melee" = 10, "bullet" = 10, "laser" = 10,"energy" = 10, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 50, "acid" = 40) + resistance_flags = FIRE_PROOF | ACID_PROOF + can_adjust = FALSE + +/obj/item/clothing/under/syndicate/bloodred/sleepytime + name = "blood-red pajamas" + desc = "Do operatives dream of nuclear sheep?" + icon_state = "bloodred_pajamas" + item_state = "bl_suit" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 40) + +/obj/item/clothing/under/syndicate/tacticool + name = "tacticool turtleneck" + desc = "Just looking at it makes you want to buy an SKS, go into the woods, and -operate-." + icon_state = "tactifool" + item_state = "bl_suit" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 40) + +/obj/item/clothing/under/syndicate/tacticool/skirt + name = "tacticool skirtleneck" + desc = "Just looking at it makes you want to buy an SKS, go into the woods, and -operate-." + icon_state = "tactifool_skirt" + item_state = "bl_suit" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 40) + fitted = FEMALE_UNIFORM_TOP + can_adjust = FALSE + +/obj/item/clothing/under/syndicate/sniper + name = "Tactical turtleneck suit" + desc = "A double seamed tactical turtleneck disguised as a civilian grade silk suit. Intended for the most formal operator. The collar is really sharp." + icon = 'icons/obj/clothing/under/suits.dmi' + icon_state = "really_black_suit" + item_state = "bl_suit" + mob_overlay_icon = 'icons/mob/clothing/under/suits.dmi' + can_adjust = FALSE + +/obj/item/clothing/under/syndicate/camo + name = "camouflage fatigues" + desc = "A green military camouflage uniform." + icon_state = "camogreen" + item_state = "g_suit" + can_adjust = FALSE + +/obj/item/clothing/under/syndicate/soviet + name = "Ratnik 5 tracksuit" + desc = "Badly translated labels tell you to clean this in Vodka. Great for squatting in." + icon_state = "trackpants" + can_adjust = FALSE + armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + resistance_flags = NONE + +/obj/item/clothing/under/syndicate/combat + name = "combat uniform" + desc = "With a suit lined with this many pockets, you are ready to operate." + icon_state = "syndicate_combat" + can_adjust = FALSE + +/obj/item/clothing/under/syndicate/rus_army + name = "advanced military tracksuit" + desc = "Military grade tracksuits for frontline squatting." + icon_state = "rus_under" + can_adjust = FALSE + armor = list("melee" = 5, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + resistance_flags = NONE diff --git a/code/modules/crew_objectives/_crew_objectives.dm b/code/modules/crew_objectives/_crew_objectives.dm index ff5b25b1e06d..09653a342813 100644 --- a/code/modules/crew_objectives/_crew_objectives.dm +++ b/code/modules/crew_objectives/_crew_objectives.dm @@ -23,6 +23,7 @@ return newObjective.owner = crewMind crewMind.crew_objectives += newObjective + crewMind.memory += "Your crew objective: [newObjective.explanation_text]" to_chat(crewMind, "As a part of Nanotrasen's anti-tide efforts, you have been assigned an optional objective. It will be checked at the end of the shift. Performing traitorous acts in pursuit of your objective may result in termination of your employment.") to_chat(crewMind, "Your objective: [newObjective.explanation_text]") diff --git a/code/modules/detectivework/detective_work.dm b/code/modules/detectivework/detective_work.dm index 5d0fff78a692..fe5197036718 100644 --- a/code/modules/detectivework/detective_work.dm +++ b/code/modules/detectivework/detective_work.dm @@ -1,102 +1,102 @@ -//CONTAINS: Suit fibers and Detective's Scanning Computer - -/atom/proc/return_fingerprints() - var/datum/component/forensics/D = GetComponent(/datum/component/forensics) - if(D) - . = D.fingerprints - -/atom/proc/return_hiddenprints() - var/datum/component/forensics/D = GetComponent(/datum/component/forensics) - if(D) - . = D.hiddenprints - -/atom/proc/return_blood_DNA() - var/datum/component/forensics/D = GetComponent(/datum/component/forensics) - if(D) - . = D.blood_DNA - -/atom/proc/blood_DNA_length() - var/datum/component/forensics/D = GetComponent(/datum/component/forensics) - if(D) - . = length(D.blood_DNA) - -/atom/proc/return_fibers() - var/datum/component/forensics/D = GetComponent(/datum/component/forensics) - if(D) - . = D.fibers - -/atom/proc/add_fingerprint_list(list/fingerprints) //ASSOC LIST FINGERPRINT = FINGERPRINT - if(length(fingerprints)) - . = AddComponent(/datum/component/forensics, fingerprints) - -//Set ignoregloves to add prints irrespective of the mob having gloves on. -/atom/proc/add_fingerprint(mob/M, ignoregloves = FALSE) - var/datum/component/forensics/D = AddComponent(/datum/component/forensics) - . = D.add_fingerprint(M, ignoregloves) - -/atom/proc/add_fiber_list(list/fibertext) //ASSOC LIST FIBERTEXT = FIBERTEXT - if(length(fibertext)) - . = AddComponent(/datum/component/forensics, null, null, null, fibertext) - -/atom/proc/add_fibers(mob/living/carbon/human/M) - var/old = 0 - if(M.gloves && istype(M.gloves, /obj/item/clothing)) - var/obj/item/clothing/gloves/G = M.gloves - old = length(G.return_blood_DNA()) - if(G.transfer_blood > 1) //bloodied gloves transfer blood to touched objects - if(add_blood_DNA(G.return_blood_DNA()) && length(G.return_blood_DNA()) > old) //only reduces the bloodiness of our gloves if the item wasn't already bloody - G.transfer_blood-- - else if(M.bloody_hands > 1) - old = length(M.return_blood_DNA()) - if(add_blood_DNA(M.return_blood_DNA()) && length(M.return_blood_DNA()) > old) - M.bloody_hands-- - var/datum/component/forensics/D = AddComponent(/datum/component/forensics) - . = D.add_fibers(M) - -/atom/proc/add_hiddenprint_list(list/hiddenprints) //NOTE: THIS IS FOR ADMINISTRATION FINGERPRINTS, YOU MUST CUSTOM SET THIS TO INCLUDE CKEY/REAL NAMES! CHECK FORENSICS.DM - if(length(hiddenprints)) - . = AddComponent(/datum/component/forensics, null, hiddenprints) - -/atom/proc/add_hiddenprint(mob/M) - var/datum/component/forensics/D = AddComponent(/datum/component/forensics) - . = D.add_hiddenprint(M) - -/atom/proc/add_blood_DNA(list/dna) //ASSOC LIST DNA = BLOODTYPE - return FALSE - -/obj/add_blood_DNA(list/dna) - . = ..() - if(length(dna)) - . = AddComponent(/datum/component/forensics, null, null, dna) - -/obj/item/clothing/gloves/add_blood_DNA(list/blood_dna, list/datum/disease/diseases) - . = ..() - transfer_blood = rand(2, 4) - -/turf/add_blood_DNA(list/blood_dna, list/datum/disease/diseases) - var/obj/effect/decal/cleanable/blood/splatter/B = locate() in src - if(!B) - B = new /obj/effect/decal/cleanable/blood/splatter(src, diseases) - B.add_blood_DNA(blood_dna) //give blood info to the blood decal. - return TRUE //we bloodied the floor - -/mob/living/carbon/human/add_blood_DNA(list/blood_dna, list/datum/disease/diseases) - if(wear_suit) - wear_suit.add_blood_DNA(blood_dna) - update_inv_wear_suit() - else if(w_uniform) - w_uniform.add_blood_DNA(blood_dna) - update_inv_w_uniform() - if(gloves) - var/obj/item/clothing/gloves/G = gloves - G.add_blood_DNA(blood_dna) - else if(length(blood_dna)) - AddComponent(/datum/component/forensics, null, null, blood_dna) - bloody_hands = rand(2, 4) - update_inv_gloves() //handles bloody hands overlays and updating - return TRUE - -/atom/proc/transfer_fingerprints_to(atom/A) - A.add_fingerprint_list(return_fingerprints()) - A.add_hiddenprint_list(return_hiddenprints()) - A.fingerprintslast = fingerprintslast +//CONTAINS: Suit fibers and Detective's Scanning Computer + +/atom/proc/return_fingerprints() + var/datum/component/forensics/D = GetComponent(/datum/component/forensics) + if(D) + . = D.fingerprints + +/atom/proc/return_hiddenprints() + var/datum/component/forensics/D = GetComponent(/datum/component/forensics) + if(D) + . = D.hiddenprints + +/atom/proc/return_blood_DNA() + var/datum/component/forensics/D = GetComponent(/datum/component/forensics) + if(D) + . = D.blood_DNA + +/atom/proc/blood_DNA_length() + var/datum/component/forensics/D = GetComponent(/datum/component/forensics) + if(D) + . = length(D.blood_DNA) + +/atom/proc/return_fibers() + var/datum/component/forensics/D = GetComponent(/datum/component/forensics) + if(D) + . = D.fibers + +/atom/proc/add_fingerprint_list(list/fingerprints) //ASSOC LIST FINGERPRINT = FINGERPRINT + if(length(fingerprints)) + . = AddComponent(/datum/component/forensics, fingerprints) + +//Set ignoregloves to add prints irrespective of the mob having gloves on. +/atom/proc/add_fingerprint(mob/M, ignoregloves = FALSE) + var/datum/component/forensics/D = AddComponent(/datum/component/forensics) + . = D.add_fingerprint(M, ignoregloves) + +/atom/proc/add_fiber_list(list/fibertext) //ASSOC LIST FIBERTEXT = FIBERTEXT + if(length(fibertext)) + . = AddComponent(/datum/component/forensics, null, null, null, fibertext) + +/atom/proc/add_fibers(mob/living/carbon/human/M) + var/old = 0 + if(M.gloves && istype(M.gloves, /obj/item/clothing)) + var/obj/item/clothing/gloves/G = M.gloves + old = length(G.return_blood_DNA()) + if(G.transfer_blood > 1) //bloodied gloves transfer blood to touched objects + if(add_blood_DNA(G.return_blood_DNA()) && length(G.return_blood_DNA()) > old) //only reduces the bloodiness of our gloves if the item wasn't already bloody + G.transfer_blood-- + else if(M.bloody_hands > 1) + old = length(M.return_blood_DNA()) + if(add_blood_DNA(M.return_blood_DNA()) && length(M.return_blood_DNA()) > old) + M.bloody_hands-- + var/datum/component/forensics/D = AddComponent(/datum/component/forensics) + . = D.add_fibers(M) + +/atom/proc/add_hiddenprint_list(list/hiddenprints) //NOTE: THIS IS FOR ADMINISTRATION FINGERPRINTS, YOU MUST CUSTOM SET THIS TO INCLUDE CKEY/REAL NAMES! CHECK FORENSICS.DM + if(length(hiddenprints)) + . = AddComponent(/datum/component/forensics, null, hiddenprints) + +/atom/proc/add_hiddenprint(mob/M) + var/datum/component/forensics/D = AddComponent(/datum/component/forensics) + . = D.add_hiddenprint(M) + +/atom/proc/add_blood_DNA(list/dna) //ASSOC LIST DNA = BLOODTYPE + return FALSE + +/obj/add_blood_DNA(list/dna) + . = ..() + if(length(dna)) + . = AddComponent(/datum/component/forensics, null, null, dna) + +/obj/item/clothing/gloves/add_blood_DNA(list/blood_dna, list/datum/disease/diseases) + . = ..() + transfer_blood = rand(2, 4) + +/turf/add_blood_DNA(list/blood_dna, list/datum/disease/diseases) + var/obj/effect/decal/cleanable/blood/splatter/B = locate() in src + if(!B) + B = new /obj/effect/decal/cleanable/blood/splatter(src, diseases) + B.add_blood_DNA(blood_dna) //give blood info to the blood decal. + return TRUE //we bloodied the floor + +/mob/living/carbon/human/add_blood_DNA(list/blood_dna, list/datum/disease/diseases) + if(wear_suit) + wear_suit.add_blood_DNA(blood_dna) + update_inv_wear_suit() + else if(w_uniform) + w_uniform.add_blood_DNA(blood_dna) + update_inv_w_uniform() + if(gloves) + var/obj/item/clothing/gloves/G = gloves + G.add_blood_DNA(blood_dna) + else if(length(blood_dna)) + AddComponent(/datum/component/forensics, null, null, blood_dna) + bloody_hands = rand(2, 4) + update_inv_gloves() //handles bloody hands overlays and updating + return TRUE + +/atom/proc/transfer_fingerprints_to(atom/A) + A.add_fingerprint_list(return_fingerprints()) + A.add_hiddenprint_list(return_hiddenprints()) + A.fingerprintslast = fingerprintslast diff --git a/code/modules/detectivework/evidence.dm b/code/modules/detectivework/evidence.dm index 32452f477fc7..4e87fa207946 100644 --- a/code/modules/detectivework/evidence.dm +++ b/code/modules/detectivework/evidence.dm @@ -1,97 +1,97 @@ -//CONTAINS: Evidence bags - -/obj/item/evidencebag - name = "evidence bag" - desc = "An empty evidence bag." - icon = 'icons/obj/storage.dmi' - icon_state = "evidenceobj" - item_state = "" - w_class = WEIGHT_CLASS_TINY - -/obj/item/evidencebag/afterattack(obj/item/I, mob/user,proximity) - . = ..() - if(!proximity || loc == I) - return - evidencebagEquip(I, user) - -/obj/item/evidencebag/attackby(obj/item/I, mob/user, params) - if(evidencebagEquip(I, user)) - return 1 - -/obj/item/evidencebag/handle_atom_del(atom/A) - cut_overlays() - w_class = initial(w_class) - icon_state = initial(icon_state) - desc = initial(desc) - -/obj/item/evidencebag/proc/evidencebagEquip(obj/item/I, mob/user) - if(!istype(I) || I.anchored == 1) - return - - if(SEND_SIGNAL(loc, COMSIG_CONTAINS_STORAGE) && SEND_SIGNAL(I, COMSIG_CONTAINS_STORAGE)) - to_chat(user, "No matter what way you try, you can't get [I] to fit inside [src].") - return TRUE //begone infinite storage ghosts, begone from me - - if(istype(I, /obj/item/evidencebag)) - to_chat(user, "You find putting an evidence bag in another evidence bag to be slightly absurd.") - return TRUE //now this is podracing - - if(loc in I.GetAllContents()) // fixes tg #39452, evidence bags could store their own location, causing I to be stored in the bag while being present inworld still, and able to be teleported when removed. - to_chat(user, "You find putting [I] in [src] while it's still inside it quite difficult!") - return - - if(I.w_class > WEIGHT_CLASS_NORMAL) - to_chat(user, "[I] won't fit in [src]!") - return - - if(contents.len) - to_chat(user, "[src] already has something inside it!") - return - - if(!isturf(I.loc)) //If it isn't on the floor. Do some checks to see if it's in our hands or a box. Otherwise give up. - if(SEND_SIGNAL(I.loc, COMSIG_CONTAINS_STORAGE)) //in a container. - SEND_SIGNAL(I.loc, COMSIG_TRY_STORAGE_TAKE, I, src) - if(!user.dropItemToGround(I)) - return - - user.visible_message("[user] puts [I] into [src].", "You put [I] inside [src].",\ - "You hear a rustle as someone puts something into a plastic bag.") - - icon_state = "evidence" - - var/mutable_appearance/in_evidence = new(I) - in_evidence.plane = FLOAT_PLANE - in_evidence.layer = FLOAT_LAYER - in_evidence.pixel_x = 0 - in_evidence.pixel_y = 0 - add_overlay(in_evidence) - add_overlay("evidence") //should look nicer for transparent stuff. not really that important, but hey. - - desc = "An evidence bag containing [I]. [I.desc]" - I.forceMove(src) - w_class = I.w_class - return 1 - -/obj/item/evidencebag/attack_self(mob/user) - if(contents.len) - var/obj/item/I = contents[1] - user.visible_message("[user] takes [I] out of [src].", "You take [I] out of [src].",\ - "You hear someone rustle around in a plastic bag, and remove something.") - cut_overlays() //remove the overlays - user.put_in_hands(I) - w_class = WEIGHT_CLASS_TINY - icon_state = "evidenceobj" - desc = "An empty evidence bag." - - else - to_chat(user, "[src] is empty.") - icon_state = "evidenceobj" - return - -/obj/item/storage/box/evidence - name = "evidence bag box" - desc = "A box claiming to contain evidence bags." - -/obj/item/storage/box/evidence/PopulateContents() - for(var/i in 1 to 6) - new /obj/item/evidencebag(src) +//CONTAINS: Evidence bags + +/obj/item/evidencebag + name = "evidence bag" + desc = "An empty evidence bag." + icon = 'icons/obj/storage.dmi' + icon_state = "evidenceobj" + item_state = "" + w_class = WEIGHT_CLASS_TINY + +/obj/item/evidencebag/afterattack(obj/item/I, mob/user,proximity) + . = ..() + if(!proximity || loc == I) + return + evidencebagEquip(I, user) + +/obj/item/evidencebag/attackby(obj/item/I, mob/user, params) + if(evidencebagEquip(I, user)) + return 1 + +/obj/item/evidencebag/handle_atom_del(atom/A) + cut_overlays() + w_class = initial(w_class) + icon_state = initial(icon_state) + desc = initial(desc) + +/obj/item/evidencebag/proc/evidencebagEquip(obj/item/I, mob/user) + if(!istype(I) || I.anchored == 1) + return + + if(SEND_SIGNAL(loc, COMSIG_CONTAINS_STORAGE) && SEND_SIGNAL(I, COMSIG_CONTAINS_STORAGE)) + to_chat(user, "No matter what way you try, you can't get [I] to fit inside [src].") + return TRUE //begone infinite storage ghosts, begone from me + + if(istype(I, /obj/item/evidencebag)) + to_chat(user, "You find putting an evidence bag in another evidence bag to be slightly absurd.") + return TRUE //now this is podracing + + if(loc in I.GetAllContents()) // fixes tg #39452, evidence bags could store their own location, causing I to be stored in the bag while being present inworld still, and able to be teleported when removed. + to_chat(user, "You find putting [I] in [src] while it's still inside it quite difficult!") + return + + if(I.w_class > WEIGHT_CLASS_NORMAL) + to_chat(user, "[I] won't fit in [src]!") + return + + if(contents.len) + to_chat(user, "[src] already has something inside it!") + return + + if(!isturf(I.loc)) //If it isn't on the floor. Do some checks to see if it's in our hands or a box. Otherwise give up. + if(SEND_SIGNAL(I.loc, COMSIG_CONTAINS_STORAGE)) //in a container. + SEND_SIGNAL(I.loc, COMSIG_TRY_STORAGE_TAKE, I, src) + if(!user.dropItemToGround(I)) + return + + user.visible_message("[user] puts [I] into [src].", "You put [I] inside [src].",\ + "You hear a rustle as someone puts something into a plastic bag.") + + icon_state = "evidence" + + var/mutable_appearance/in_evidence = new(I) + in_evidence.plane = FLOAT_PLANE + in_evidence.layer = FLOAT_LAYER + in_evidence.pixel_x = 0 + in_evidence.pixel_y = 0 + add_overlay(in_evidence) + add_overlay("evidence") //should look nicer for transparent stuff. not really that important, but hey. + + desc = "An evidence bag containing [I]. [I.desc]" + I.forceMove(src) + w_class = I.w_class + return 1 + +/obj/item/evidencebag/attack_self(mob/user) + if(contents.len) + var/obj/item/I = contents[1] + user.visible_message("[user] takes [I] out of [src].", "You take [I] out of [src].",\ + "You hear someone rustle around in a plastic bag, and remove something.") + cut_overlays() //remove the overlays + user.put_in_hands(I) + w_class = WEIGHT_CLASS_TINY + icon_state = "evidenceobj" + desc = "An empty evidence bag." + + else + to_chat(user, "[src] is empty.") + icon_state = "evidenceobj" + return + +/obj/item/storage/box/evidence + name = "evidence bag box" + desc = "A box claiming to contain evidence bags." + +/obj/item/storage/box/evidence/PopulateContents() + for(var/i in 1 to 6) + new /obj/item/evidencebag(src) diff --git a/code/modules/detectivework/footprints_and_rag.dm b/code/modules/detectivework/footprints_and_rag.dm index ba6563d0657f..e7e61f3e39b9 100644 --- a/code/modules/detectivework/footprints_and_rag.dm +++ b/code/modules/detectivework/footprints_and_rag.dm @@ -1,45 +1,45 @@ - -/obj/item/clothing/gloves - var/transfer_blood = 0 - - -/obj/item/reagent_containers/glass/rag - name = "damp rag" - desc = "For cleaning up messes, you suppose." - w_class = WEIGHT_CLASS_TINY - icon = 'icons/obj/toy.dmi' - icon_state = "rag" - item_flags = NOBLUDGEON - reagent_flags = OPENCONTAINER - amount_per_transfer_from_this = 5 - possible_transfer_amounts = list() - volume = 5 - spillable = FALSE - -/obj/item/reagent_containers/glass/rag/suicide_act(mob/user) - user.visible_message("[user] is smothering [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return (OXYLOSS) - -/obj/item/reagent_containers/glass/rag/afterattack(atom/A as obj|turf|area, mob/user,proximity) - . = ..() - if(!proximity) - return - if(iscarbon(A) && A.reagents && reagents.total_volume) - var/mob/living/carbon/C = A - var/reagentlist = pretty_string_from_reagent_list(reagents) - var/log_object = "containing [reagentlist]" - if(user.a_intent == INTENT_HARM && !C.is_mouth_covered()) - reagents.trans_to(C, reagents.total_volume, transfered_by = user, method = INGEST) - C.visible_message("[user] smothers \the [C] with \the [src]!", "[user] smothers you with \the [src]!", "You hear some struggling and muffled cries of surprise.") - log_combat(user, C, "smothered", src, log_object) - else - reagents.expose(C, TOUCH) - reagents.clear_reagents() - C.visible_message("[user] touches \the [C] with \the [src].") - log_combat(user, C, "touched", src, log_object) - - else if(istype(A) && (src in user)) - user.visible_message("[user] starts to wipe down [A] with [src]!", "You start to wipe down [A] with [src]...") - if(do_after(user,30, target = A)) - user.visible_message("[user] finishes wiping off [A]!", "You finish wiping off [A].") - SEND_SIGNAL(A, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_MEDIUM) + +/obj/item/clothing/gloves + var/transfer_blood = 0 + + +/obj/item/reagent_containers/glass/rag + name = "damp rag" + desc = "For cleaning up messes, you suppose." + w_class = WEIGHT_CLASS_TINY + icon = 'icons/obj/toy.dmi' + icon_state = "rag" + item_flags = NOBLUDGEON + reagent_flags = OPENCONTAINER + amount_per_transfer_from_this = 5 + possible_transfer_amounts = list() + volume = 5 + spillable = FALSE + +/obj/item/reagent_containers/glass/rag/suicide_act(mob/user) + user.visible_message("[user] is smothering [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return (OXYLOSS) + +/obj/item/reagent_containers/glass/rag/afterattack(atom/A as obj|turf|area, mob/user,proximity) + . = ..() + if(!proximity) + return + if(iscarbon(A) && A.reagents && reagents.total_volume) + var/mob/living/carbon/C = A + var/reagentlist = pretty_string_from_reagent_list(reagents) + var/log_object = "containing [reagentlist]" + if(user.a_intent == INTENT_HARM && !C.is_mouth_covered()) + reagents.trans_to(C, reagents.total_volume, transfered_by = user, method = INGEST) + C.visible_message("[user] smothers \the [C] with \the [src]!", "[user] smothers you with \the [src]!", "You hear some struggling and muffled cries of surprise.") + log_combat(user, C, "smothered", src, log_object) + else + reagents.expose(C, TOUCH) + reagents.clear_reagents() + C.visible_message("[user] touches \the [C] with \the [src].") + log_combat(user, C, "touched", src, log_object) + + else if(istype(A) && (src in user)) + user.visible_message("[user] starts to wipe down [A] with [src]!", "You start to wipe down [A] with [src]...") + if(do_after(user,30, target = A)) + user.visible_message("[user] finishes wiping off [A]!", "You finish wiping off [A].") + SEND_SIGNAL(A, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_MEDIUM) diff --git a/code/modules/detectivework/scanner.dm b/code/modules/detectivework/scanner.dm index 6913235e56fa..8163536baad8 100644 --- a/code/modules/detectivework/scanner.dm +++ b/code/modules/detectivework/scanner.dm @@ -1,214 +1,214 @@ -//CONTAINS: Detective's Scanner - -// TODO: Split everything into easy to manage procs. - -/obj/item/detective_scanner - name = "forensic scanner" - desc = "Used to remotely scan objects and biomass for DNA and fingerprints. Can print a report of the findings." - icon = 'icons/obj/device.dmi' - icon_state = "forensicnew" - w_class = WEIGHT_CLASS_SMALL - item_state = "electronic" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - flags_1 = CONDUCT_1 - item_flags = NOBLUDGEON - slot_flags = ITEM_SLOT_BELT - var/scanning = 0 - var/list/log = list() - var/range = 8 - var/view_check = TRUE - var/forensicPrintCount = 0 - actions_types = list(/datum/action/item_action/displayDetectiveScanResults) - -/datum/action/item_action/displayDetectiveScanResults - name = "Display Forensic Scanner Results" - -/datum/action/item_action/displayDetectiveScanResults/Trigger() - var/obj/item/detective_scanner/scanner = target - if(istype(scanner)) - scanner.displayDetectiveScanResults(usr) - -/obj/item/detective_scanner/attack_self(mob/user) - if(log.len && !scanning) - scanning = 1 - to_chat(user, "Printing report, please wait...") - addtimer(CALLBACK(src, .proc/PrintReport), 100) - else - to_chat(user, "The scanner has no logs or is in use.") - -/obj/item/detective_scanner/attack(mob/living/M, mob/user) - return - -/obj/item/detective_scanner/proc/PrintReport() - // Create our paper - var/obj/item/paper/P = new(get_turf(src)) - - //This could be a global count like sec and med record printouts. See GLOB.data_core.medicalPrintCount AKA datacore.dm - var frNum = ++forensicPrintCount - - P.name = text("FR-[] 'Forensic Record'", frNum) - P.info = text("
                    Forensic Record - (FR-[])


                    ", frNum) - P.info += jointext(log, "
                    ") - P.info += "
                    Notes:
                    " - P.update_icon() - - if(ismob(loc)) - var/mob/M = loc - M.put_in_hands(P) - to_chat(M, "Report printed. Log cleared.") - - // Clear the logs - log = list() - scanning = 0 - -/obj/item/detective_scanner/afterattack(atom/A, mob/user, params) - . = ..() - scan(A, user) - return FALSE - -/obj/item/detective_scanner/proc/scan(atom/A, mob/user) - set waitfor = 0 - if(!scanning) - // Can remotely scan objects and mobs. - if((get_dist(A, user) > range) || (!(A in view(range, user)) && view_check) || (loc != user)) - return - - scanning = 1 - - user.visible_message("\The [user] points the [src.name] at \the [A] and performs a forensic scan.") - to_chat(user, "You scan \the [A]. The scanner is now analysing the results...") - - - // GATHER INFORMATION - - //Make our lists - var/list/fingerprints = list() - var/list/blood = A.return_blood_DNA() - var/list/fibers = A.return_fibers() - var/list/reagents = list() - - var/target_name = A.name - - // Start gathering - - if(ishuman(A)) - - var/mob/living/carbon/human/H = A - if(!H.gloves) - fingerprints += md5(H.dna.uni_identity) - - else if(!ismob(A)) - - fingerprints = A.return_fingerprints() - - // Only get reagents from non-mobs. - if(A.reagents && A.reagents.reagent_list.len) - - for(var/datum/reagent/R in A.reagents.reagent_list) - reagents[R.name] = R.volume - - // Get blood data from the blood reagent. - if(istype(R, /datum/reagent/blood)) - - if(R.data["blood_DNA"] && R.data["blood_type"]) - var/blood_DNA = R.data["blood_DNA"] - var/blood_type = R.data["blood_type"] - LAZYINITLIST(blood) - blood[blood_DNA] = blood_type - - // We gathered everything. Create a fork and slowly display the results to the holder of the scanner. - - var/found_something = 0 - add_log("[station_time_timestamp()][get_timestamp()] - [target_name]", 0) - - // Fingerprints - if(length(fingerprints)) - sleep(30) - add_log("Prints:") - for(var/finger in fingerprints) - add_log("[finger]") - found_something = 1 - - // Blood - if (length(blood)) - sleep(30) - add_log("Blood:") - found_something = 1 - for(var/B in blood) - add_log("Type: [blood[B]] DNA (UE): [B]") - - //Fibers - if(length(fibers)) - sleep(30) - add_log("Fibers:") - for(var/fiber in fibers) - add_log("[fiber]") - found_something = 1 - - //Reagents - if(length(reagents)) - sleep(30) - add_log("Reagents:") - for(var/R in reagents) - add_log("Reagent: [R] Volume: [reagents[R]]") - found_something = 1 - - // Get a new user - var/mob/holder = null - if(ismob(src.loc)) - holder = src.loc - - if(!found_something) - add_log("# No forensic traces found #", 0) // Don't display this to the holder user - if(holder) - to_chat(holder, "Unable to locate any fingerprints, materials, fibers, or blood on \the [target_name]!") - else - if(holder) - to_chat(holder, "You finish scanning \the [target_name].") - - add_log("---------------------------------------------------------", 0) - scanning = 0 - return - -/obj/item/detective_scanner/proc/add_log(msg, broadcast = 1) - if(scanning) - if(broadcast && ismob(loc)) - var/mob/M = loc - to_chat(M, msg) - log += "  [msg]" - else - CRASH("[src] [REF(src)] is adding a log when it was never put in scanning mode!") - -/proc/get_timestamp() - return time2text(world.time + 432000, ":ss") - -/obj/item/detective_scanner/AltClick(mob/living/user) - // Best way for checking if a player can use while not incapacitated, etc - if(!user.canUseTopic(src, be_close=TRUE)) - return - if(!LAZYLEN(log)) - to_chat(user, "Cannot clear logs, the scanner has no logs.") - return - if(scanning) - to_chat(user, "Cannot clear logs, the scanner is in use.") - return - to_chat(user, "The scanner logs are cleared.") - log = list() - -/obj/item/detective_scanner/examine(mob/user) - . = ..() - if(LAZYLEN(log) && !scanning) - . += "Alt-click to clear scanner logs." - -/obj/item/detective_scanner/proc/displayDetectiveScanResults(mob/living/user) - // No need for can-use checks since the action button should do proper checks - if(!LAZYLEN(log)) - to_chat(user, "Cannot display logs, the scanner has no logs.") - return - if(scanning) - to_chat(user, "Cannot display logs, the scanner is in use.") - return - to_chat(user, "Scanner Report") - for(var/iterLog in log) - to_chat(user, iterLog) +//CONTAINS: Detective's Scanner + +// TODO: Split everything into easy to manage procs. + +/obj/item/detective_scanner + name = "forensic scanner" + desc = "Used to remotely scan objects and biomass for DNA and fingerprints. Can print a report of the findings." + icon = 'icons/obj/device.dmi' + icon_state = "forensicnew" + w_class = WEIGHT_CLASS_SMALL + item_state = "electronic" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + flags_1 = CONDUCT_1 + item_flags = NOBLUDGEON + slot_flags = ITEM_SLOT_BELT + var/scanning = 0 + var/list/log = list() + var/range = 8 + var/view_check = TRUE + var/forensicPrintCount = 0 + actions_types = list(/datum/action/item_action/displayDetectiveScanResults) + +/datum/action/item_action/displayDetectiveScanResults + name = "Display Forensic Scanner Results" + +/datum/action/item_action/displayDetectiveScanResults/Trigger() + var/obj/item/detective_scanner/scanner = target + if(istype(scanner)) + scanner.displayDetectiveScanResults(usr) + +/obj/item/detective_scanner/attack_self(mob/user) + if(log.len && !scanning) + scanning = 1 + to_chat(user, "Printing report, please wait...") + addtimer(CALLBACK(src, .proc/PrintReport), 100) + else + to_chat(user, "The scanner has no logs or is in use.") + +/obj/item/detective_scanner/attack(mob/living/M, mob/user) + return + +/obj/item/detective_scanner/proc/PrintReport() + // Create our paper + var/obj/item/paper/P = new(get_turf(src)) + + //This could be a global count like sec and med record printouts. See GLOB.data_core.medicalPrintCount AKA datacore.dm + var/frNum = ++forensicPrintCount + + P.name = text("FR-[] 'Forensic Record'", frNum) + P.info = text("
                    Forensic Record - (FR-[])


                    ", frNum) + P.info += jointext(log, "
                    ") + P.info += "
                    Notes:
                    " + P.update_icon() + + if(ismob(loc)) + var/mob/M = loc + M.put_in_hands(P) + to_chat(M, "Report printed. Log cleared.") + + // Clear the logs + log = list() + scanning = 0 + +/obj/item/detective_scanner/afterattack(atom/A, mob/user, params) + . = ..() + scan(A, user) + return FALSE + +/obj/item/detective_scanner/proc/scan(atom/A, mob/user) + set waitfor = 0 + if(!scanning) + // Can remotely scan objects and mobs. + if((get_dist(A, user) > range) || (!(A in view(range, user)) && view_check) || (loc != user)) + return + + scanning = 1 + + user.visible_message("\The [user] points the [src.name] at \the [A] and performs a forensic scan.") + to_chat(user, "You scan \the [A]. The scanner is now analysing the results...") + + + // GATHER INFORMATION + + //Make our lists + var/list/fingerprints = list() + var/list/blood = A.return_blood_DNA() + var/list/fibers = A.return_fibers() + var/list/reagents = list() + + var/target_name = A.name + + // Start gathering + + if(ishuman(A)) + + var/mob/living/carbon/human/H = A + if(!H.gloves) + fingerprints += md5(H.dna.uni_identity) + + else if(!ismob(A)) + + fingerprints = A.return_fingerprints() + + // Only get reagents from non-mobs. + if(A.reagents && A.reagents.reagent_list.len) + + for(var/datum/reagent/R in A.reagents.reagent_list) + reagents[R.name] = R.volume + + // Get blood data from the blood reagent. + if(istype(R, /datum/reagent/blood)) + + if(R.data["blood_DNA"] && R.data["blood_type"]) + var/blood_DNA = R.data["blood_DNA"] + var/blood_type = R.data["blood_type"] + LAZYINITLIST(blood) + blood[blood_DNA] = blood_type + + // We gathered everything. Create a fork and slowly display the results to the holder of the scanner. + + var/found_something = 0 + add_log("[station_time_timestamp()][get_timestamp()] - [target_name]", 0) + + // Fingerprints + if(length(fingerprints)) + sleep(30) + add_log("Prints:") + for(var/finger in fingerprints) + add_log("[finger]") + found_something = 1 + + // Blood + if (length(blood)) + sleep(30) + add_log("Blood:") + found_something = 1 + for(var/B in blood) + add_log("Type: [blood[B]] DNA (UE): [B]") + + //Fibers + if(length(fibers)) + sleep(30) + add_log("Fibers:") + for(var/fiber in fibers) + add_log("[fiber]") + found_something = 1 + + //Reagents + if(length(reagents)) + sleep(30) + add_log("Reagents:") + for(var/R in reagents) + add_log("Reagent: [R] Volume: [reagents[R]]") + found_something = 1 + + // Get a new user + var/mob/holder = null + if(ismob(src.loc)) + holder = src.loc + + if(!found_something) + add_log("# No forensic traces found #", 0) // Don't display this to the holder user + if(holder) + to_chat(holder, "Unable to locate any fingerprints, materials, fibers, or blood on \the [target_name]!") + else + if(holder) + to_chat(holder, "You finish scanning \the [target_name].") + + add_log("---------------------------------------------------------", 0) + scanning = 0 + return + +/obj/item/detective_scanner/proc/add_log(msg, broadcast = 1) + if(scanning) + if(broadcast && ismob(loc)) + var/mob/M = loc + to_chat(M, msg) + log += "  [msg]" + else + CRASH("[src] [REF(src)] is adding a log when it was never put in scanning mode!") + +/proc/get_timestamp() + return time2text(world.time + 432000, ":ss") + +/obj/item/detective_scanner/AltClick(mob/living/user) + // Best way for checking if a player can use while not incapacitated, etc + if(!user.canUseTopic(src, be_close=TRUE)) + return + if(!LAZYLEN(log)) + to_chat(user, "Cannot clear logs, the scanner has no logs.") + return + if(scanning) + to_chat(user, "Cannot clear logs, the scanner is in use.") + return + to_chat(user, "The scanner logs are cleared.") + log = list() + +/obj/item/detective_scanner/examine(mob/user) + . = ..() + if(LAZYLEN(log) && !scanning) + . += "Alt-click to clear scanner logs." + +/obj/item/detective_scanner/proc/displayDetectiveScanResults(mob/living/user) + // No need for can-use checks since the action button should do proper checks + if(!LAZYLEN(log)) + to_chat(user, "Cannot display logs, the scanner has no logs.") + return + if(scanning) + to_chat(user, "Cannot display logs, the scanner is in use.") + return + to_chat(user, "Scanner Report") + for(var/iterLog in log) + to_chat(user, iterLog) diff --git a/code/modules/discord/accountlink.dm b/code/modules/discord/accountlink.dm index 117e21ae9d4b..72bf36c9fece 100644 --- a/code/modules/discord/accountlink.dm +++ b/code/modules/discord/accountlink.dm @@ -1,90 +1,90 @@ -// Verb to link discord accounts to BYOND accounts -/client/verb/linkdiscord() - set category = "OOC" - set name = "Link Discord Account" - set desc = "Link your discord account to your BYOND account." - - // Safety checks - if(!CONFIG_GET(flag/sql_enabled)) - to_chat(src, "This feature requires the SQL backend to be running.") - return - - if(!SSdiscord) // SS is still starting - to_chat(src, "The server is still starting up. Please wait before attempting to link your account!") - return - - if(!SSdiscord.enabled) - to_chat(src, "This feature requires the server is running on the TGS toolkit.") - return - - var/stored_id = SSdiscord.lookup_id(usr.ckey) - if(!stored_id) // Account is not linked - var/know_how = alert("Do you know how to get a Discord user ID? This ID is NOT your Discord username and numbers! (Pressing NO will open a guide.)","Question","Yes","No","Cancel Linking") - if(know_how == "No") // Opens discord support on how to collect IDs - src << link("https://tgstation13.org/wiki/How_to_find_your_Discord_User_ID") - if(know_how == "Cancel Linking") - return - var/entered_id = input("Please enter your Discord ID (18-ish digits)", "Enter Discord ID", null, null) as text|null - SSdiscord.account_link_cache[replacetext(lowertext(usr.ckey), " ", "")] = "[entered_id]" // Prepares for TGS-side verification, also fuck spaces - alert(usr, "Account link started. Please ping the bot of the server you\'re currently on, followed by \"verify [usr.ckey]\" in Discord to successfully verify your account (Example: @Mr_Terry verify [usr.ckey])") - - else // Account is already linked - var/choice = alert("You already have the Discord Account [stored_id] linked to [usr.ckey]. Would you like to link a different account?","Already Linked","Yes","No") - if(choice == "Yes") - var/know_how = alert("Do you know how to get a Discord user ID? This ID is NOT your Discord username and numbers! (Pressing NO will open a guide.)","Question","Yes","No", "Cancel Linking") - if(know_how == "No") - src << link("https://tgstation13.org/wiki/How_to_find_your_Discord_User_ID") - - if(know_how == "Cancel Linking") - return - - var/entered_id = input("Please enter your Discord ID (18-ish digits)", "Enter Discord ID", null, null) as text|null - SSdiscord.account_link_cache[replacetext(lowertext(usr.ckey), " ", "")] = "[entered_id]" // Prepares for TGS-side verification, also fuck spaces - alert(usr, "Account link started. Please ping the bot of the server you\'re currently on, followed by \"verify [usr.ckey]\" in Discord to successfully verify your account (Example: @Mr_Terry verify [usr.ckey])") - // This is so people cant fill the notify list with a fuckload of ckeys - SSdiscord.notify_members -= "[stored_id]" // The list uses strings because BYOND cannot handle a 17 digit integer - -// IF you have linked your account, this will trigger a verify of the user -/client/verb/verify_in_discord() - set category = "OOC" - set name = "Verify Discord Account" - set desc = "Verify or reverify your discord account against your linked ckey" - - // Safety checks - if(!CONFIG_GET(flag/sql_enabled)) - to_chat(src, "This feature requires the SQL backend to be running.") - return - - // ss is still starting - if(!SSdiscord) - to_chat(src, "The server is still starting up. Please wait before attempting to link your account!") - return - - // check that tgs is alive and well - if(!SSdiscord.enabled) - to_chat(src, "This feature requires the server is running on the TGS toolkit.") - return - - // check that this is not an IDIOT mistaking us for an attack vector - if(SSdiscord.reverify_cache[usr.ckey] == TRUE) - to_chat(src, "Thou can only do this once a round, if you're stuck seek help.") - return - SSdiscord.reverify_cache[usr.ckey] = TRUE - - // check that account is linked with discord - var/stored_id = SSdiscord.lookup_id(usr.ckey) - if(!stored_id) // Account is not linked - to_chat(usr, "Link your discord account via the linkdiscord verb in the OOC tab first"); - return - - // check for living hours requirement - var/required_living_minutes = CONFIG_GET(number/required_living_hours) * 60 - var/living_minutes = usr.client ? usr.client.get_exp_living(TRUE) : 0 - if(required_living_minutes > 0 && living_minutes < required_living_minutes) - to_chat(usr, "You must have at least [required_living_minutes] minutes of living " \ - + "playtime in a round to verify. You have [living_minutes] minutes. Play more!") - return - - // honey its time for your role flattening - to_chat(usr, "Discord verified") - SSdiscord.grant_role(stored_id) +// Verb to link discord accounts to BYOND accounts +/client/verb/linkdiscord() + set category = "OOC" + set name = "Link Discord Account" + set desc = "Link your discord account to your BYOND account." + + // Safety checks + if(!CONFIG_GET(flag/sql_enabled)) + to_chat(src, "This feature requires the SQL backend to be running.") + return + + if(!SSdiscord) // SS is still starting + to_chat(src, "The server is still starting up. Please wait before attempting to link your account!") + return + + if(!SSdiscord.enabled) + to_chat(src, "This feature requires the server is running on the TGS toolkit.") + return + + var/stored_id = SSdiscord.lookup_id(usr.ckey) + if(!stored_id) // Account is not linked + var/know_how = alert("Do you know how to get a Discord user ID? This ID is NOT your Discord username and numbers! (Pressing NO will open a guide.)","Question","Yes","No","Cancel Linking") + if(know_how == "No") // Opens discord support on how to collect IDs + src << link("https://tgstation13.org/wiki/How_to_find_your_Discord_User_ID") + if(know_how == "Cancel Linking") + return + var/entered_id = input("Please enter your Discord ID (18-ish digits)", "Enter Discord ID", null, null) as text|null + SSdiscord.account_link_cache[replacetext(lowertext(usr.ckey), " ", "")] = "[entered_id]" // Prepares for TGS-side verification, also fuck spaces + alert(usr, "Account link started. Please ping the bot of the server you\'re currently on, followed by \"verify [usr.ckey]\" in Discord to successfully verify your account (Example: @Mr_Terry verify [usr.ckey])") + + else // Account is already linked + var/choice = alert("You already have the Discord Account [stored_id] linked to [usr.ckey]. Would you like to link a different account?","Already Linked","Yes","No") + if(choice == "Yes") + var/know_how = alert("Do you know how to get a Discord user ID? This ID is NOT your Discord username and numbers! (Pressing NO will open a guide.)","Question","Yes","No", "Cancel Linking") + if(know_how == "No") + src << link("https://tgstation13.org/wiki/How_to_find_your_Discord_User_ID") + + if(know_how == "Cancel Linking") + return + + var/entered_id = input("Please enter your Discord ID (18-ish digits)", "Enter Discord ID", null, null) as text|null + SSdiscord.account_link_cache[replacetext(lowertext(usr.ckey), " ", "")] = "[entered_id]" // Prepares for TGS-side verification, also fuck spaces + alert(usr, "Account link started. Please ping the bot of the server you\'re currently on, followed by \"verify [usr.ckey]\" in Discord to successfully verify your account (Example: @Mr_Terry verify [usr.ckey])") + // This is so people cant fill the notify list with a fuckload of ckeys + SSdiscord.notify_members -= "[stored_id]" // The list uses strings because BYOND cannot handle a 17 digit integer + +// IF you have linked your account, this will trigger a verify of the user +/client/verb/verify_in_discord() + set category = "OOC" + set name = "Verify Discord Account" + set desc = "Verify or reverify your discord account against your linked ckey" + + // Safety checks + if(!CONFIG_GET(flag/sql_enabled)) + to_chat(src, "This feature requires the SQL backend to be running.") + return + + // ss is still starting + if(!SSdiscord) + to_chat(src, "The server is still starting up. Please wait before attempting to link your account!") + return + + // check that tgs is alive and well + if(!SSdiscord.enabled) + to_chat(src, "This feature requires the server is running on the TGS toolkit.") + return + + // check that this is not an IDIOT mistaking us for an attack vector + if(SSdiscord.reverify_cache[usr.ckey] == TRUE) + to_chat(src, "Thou can only do this once a round, if you're stuck seek help.") + return + SSdiscord.reverify_cache[usr.ckey] = TRUE + + // check that account is linked with discord + var/stored_id = SSdiscord.lookup_id(usr.ckey) + if(!stored_id) // Account is not linked + to_chat(usr, "Link your discord account via the linkdiscord verb in the OOC tab first"); + return + + // check for living hours requirement + var/required_living_minutes = CONFIG_GET(number/required_living_hours) * 60 + var/living_minutes = usr.client ? usr.client.get_exp_living(TRUE) : 0 + if(required_living_minutes > 0 && living_minutes < required_living_minutes) + to_chat(usr, "You must have at least [required_living_minutes] minutes of living " \ + + "playtime in a round to verify. You have [living_minutes] minutes. Play more!") + return + + // honey its time for your role flattening + to_chat(usr, "Discord verified") + SSdiscord.grant_role(stored_id) diff --git a/code/modules/discord/manipulation.dm b/code/modules/discord/manipulation.dm index 165268ba1197..6bd058717e2e 100644 --- a/code/modules/discord/manipulation.dm +++ b/code/modules/discord/manipulation.dm @@ -1,36 +1,36 @@ -// Verb to manipulate IDs and ckeys -/client/proc/discord_id_manipulation() - set name = "Discord Manipulation" - set category = "Admin" - - if(!check_rights(R_ADMIN)) - return - - holder.discord_manipulation() - - -/datum/admins/proc/discord_manipulation() - if(!usr.client.holder) - return - - if(!SSdiscord.enabled) - to_chat(usr, "TGS is not enabled") - return - - var/lookup_choice = alert(usr, "Do you wish to lookup account by ID or ckey?", "Lookup Type", "ID", "Ckey", "Cancel") - switch(lookup_choice) - if("ID") - var/lookup_id = input(usr,"Enter Discord ID to lookup ckey") as text|null - var/returned_ckey = SSdiscord.lookup_id(lookup_id) - if(returned_ckey) - var/unlink_choice = alert(usr, "Discord ID [lookup_id] is linked to Ckey [returned_ckey]. Do you wish to unlink or cancel?", "Account Found", "Unlink", "Cancel") - if(unlink_choice == "Unlink") - SSdiscord.unlink_account(returned_ckey) - else - to_chat(usr, "Discord ID [lookup_id] has no associated ckey") - if("Ckey") - var/lookup_ckey = input(usr,"Enter Ckey to lookup ID") as text|null - var/returned_id = SSdiscord.lookup_id(lookup_ckey) - if(returned_id) - to_chat(usr, "Ckey [lookup_ckey] is assigned to Discord ID [returned_id]") - to_chat(usr, "Discord mention format: <@[returned_id]>") // < and > print < > in HTML without using them as tags +// Verb to manipulate IDs and ckeys +/client/proc/discord_id_manipulation() + set name = "Discord Manipulation" + set category = "Admin" + + if(!check_rights(R_ADMIN)) + return + + holder.discord_manipulation() + + +/datum/admins/proc/discord_manipulation() + if(!usr.client.holder) + return + + if(!SSdiscord.enabled) + to_chat(usr, "TGS is not enabled") + return + + var/lookup_choice = alert(usr, "Do you wish to lookup account by ID or ckey?", "Lookup Type", "ID", "Ckey", "Cancel") + switch(lookup_choice) + if("ID") + var/lookup_id = input(usr,"Enter Discord ID to lookup ckey") as text|null + var/returned_ckey = SSdiscord.lookup_id(lookup_id) + if(returned_ckey) + var/unlink_choice = alert(usr, "Discord ID [lookup_id] is linked to Ckey [returned_ckey]. Do you wish to unlink or cancel?", "Account Found", "Unlink", "Cancel") + if(unlink_choice == "Unlink") + SSdiscord.unlink_account(returned_ckey) + else + to_chat(usr, "Discord ID [lookup_id] has no associated ckey") + if("Ckey") + var/lookup_ckey = input(usr,"Enter Ckey to lookup ID") as text|null + var/returned_id = SSdiscord.lookup_id(lookup_ckey) + if(returned_id) + to_chat(usr, "Ckey [lookup_ckey] is assigned to Discord ID [returned_id]") + to_chat(usr, "Discord mention format: <@[returned_id]>") // < and > print < > in HTML without using them as tags diff --git a/code/modules/discord/tgs_commands.dm b/code/modules/discord/tgs_commands.dm index e2a0e8263f40..2372060307af 100644 --- a/code/modules/discord/tgs_commands.dm +++ b/code/modules/discord/tgs_commands.dm @@ -1,43 +1,43 @@ -// Notify -/datum/tgs_chat_command/notify - name = "notify" - help_text = "Pings the invoker when the round ends" - -/datum/tgs_chat_command/notify/Run(datum/tgs_chat_user/sender, params) - for(var/member in SSdiscord.notify_members) // If they are in the list, take them out - if(member == "[sender.mention]") - SSdiscord.notify_members -= "[SSdiscord.id_clean(sender.mention)]" // The list uses strings because BYOND cannot handle a 17 digit integer - return "You will no longer be notified when the server restarts" - - // If we got here, they arent in the list. Chuck 'em in! - SSdiscord.notify_members += "[SSdiscord.id_clean(sender.mention)]" // The list uses strings because BYOND cannot handle a 17 digit integer - return "You will now be notified when the server restarts" - -// Verify -/datum/tgs_chat_command/verify - name = "verify" - help_text = "Verifies your discord account and your BYOND account linkage" - -/datum/tgs_chat_command/verify/Run(datum/tgs_chat_user/sender, params) - var/lowerparams = replacetext(lowertext(params), " ", "") // Fuck spaces - var/discordid = SSdiscord.id_clean(sender.mention) - if(SSdiscord.account_link_cache[lowerparams]) // First if they are in the list, then if the ckey matches - if(SSdiscord.account_link_cache[lowerparams] == discordid) // If the associated ID is the correct one - // Link the account in the DB table - SSdiscord.link_account(lowerparams) - return "Successfully linked accounts" - else - return "That ckey is not associated to this discord account. If someone has used your ID, please inform an administrator" - else - return "Account not setup for linkage" - - -/// Gets the discord user's Discord UserID -/datum/tgs_chat_command/myuserid - name = "myuserid" - help_text = "Returns your userid" - -/datum/tgs_chat_command/myuserid/Run(datum/tgs_chat_user/sender, params) - var/discordid = SSdiscord.id_clean(sender.mention) - return "<@[discordid]> Your Discord UserID is [discordid]" - +// Notify +/datum/tgs_chat_command/notify + name = "notify" + help_text = "Pings the invoker when the round ends" + +/datum/tgs_chat_command/notify/Run(datum/tgs_chat_user/sender, params) + for(var/member in SSdiscord.notify_members) // If they are in the list, take them out + if(member == "[sender.mention]") + SSdiscord.notify_members -= "[SSdiscord.id_clean(sender.mention)]" // The list uses strings because BYOND cannot handle a 17 digit integer + return "You will no longer be notified when the server restarts" + + // If we got here, they arent in the list. Chuck 'em in! + SSdiscord.notify_members += "[SSdiscord.id_clean(sender.mention)]" // The list uses strings because BYOND cannot handle a 17 digit integer + return "You will now be notified when the server restarts" + +// Verify +/datum/tgs_chat_command/verify + name = "verify" + help_text = "Verifies your discord account and your BYOND account linkage" + +/datum/tgs_chat_command/verify/Run(datum/tgs_chat_user/sender, params) + var/lowerparams = replacetext(lowertext(params), " ", "") // Fuck spaces + var/discordid = SSdiscord.id_clean(sender.mention) + if(SSdiscord.account_link_cache[lowerparams]) // First if they are in the list, then if the ckey matches + if(SSdiscord.account_link_cache[lowerparams] == discordid) // If the associated ID is the correct one + // Link the account in the DB table + SSdiscord.link_account(lowerparams) + return "Successfully linked accounts" + else + return "That ckey is not associated to this discord account. If someone has used your ID, please inform an administrator" + else + return "Account not setup for linkage" + + +/// Gets the discord user's Discord UserID +/datum/tgs_chat_command/myuserid + name = "myuserid" + help_text = "Returns your userid" + +/datum/tgs_chat_command/myuserid/Run(datum/tgs_chat_user/sender, params) + var/discordid = SSdiscord.id_clean(sender.mention) + return "<@[discordid]> Your Discord UserID is [discordid]" + diff --git a/code/modules/discord/toggle_notify.dm b/code/modules/discord/toggle_notify.dm index 87cb63d05081..ec0bc1f550ae 100644 --- a/code/modules/discord/toggle_notify.dm +++ b/code/modules/discord/toggle_notify.dm @@ -1,34 +1,34 @@ -// Verb to toggle restart notifications -/client/verb/notify_restart() - set category = "OOC" - set name = "Notify Restart" - set desc = "Notifies you on Discord when the server restarts." - - // Safety checks - if(!CONFIG_GET(flag/sql_enabled)) - to_chat(src, "This feature requires the SQL backend to be running.") - return - - if(!SSdiscord) // SS is still starting - to_chat(src, "The server is still starting up. Please wait before attempting to link your account ") - return - - if(!SSdiscord.enabled) - to_chat(src, "This feature requires the server is running on the TGS toolkit") - return - - var/stored_id = SSdiscord.lookup_id(usr.ckey) - if(!stored_id) // Account is not linked - to_chat(src, "This requires you to link your Discord account with the \"Link Discord Account\" verb.") - return - - else // Linked - for(var/member in SSdiscord.notify_members) // If they are in the list, take them out - if(member == "[stored_id]") - SSdiscord.notify_members -= "[stored_id]" // The list uses strings because BYOND cannot handle a 17 digit integer - to_chat(src, "You will no longer be notified when the server restarts") - return // This is necassary so it doesnt get added again, as it relies on the for loop being unsuccessful to tell us if they are in the list or not - - // If we got here, they arent in the list. Chuck 'em in! - to_chat(src, "You will now be notified when the server restarts") - SSdiscord.notify_members += "[stored_id]" // The list uses strings because BYOND cannot handle a 17 digit integer +// Verb to toggle restart notifications +/client/verb/notify_restart() + set category = "OOC" + set name = "Notify Restart" + set desc = "Notifies you on Discord when the server restarts." + + // Safety checks + if(!CONFIG_GET(flag/sql_enabled)) + to_chat(src, "This feature requires the SQL backend to be running.") + return + + if(!SSdiscord) // SS is still starting + to_chat(src, "The server is still starting up. Please wait before attempting to link your account ") + return + + if(!SSdiscord.enabled) + to_chat(src, "This feature requires the server is running on the TGS toolkit") + return + + var/stored_id = SSdiscord.lookup_id(usr.ckey) + if(!stored_id) // Account is not linked + to_chat(src, "This requires you to link your Discord account with the \"Link Discord Account\" verb.") + return + + else // Linked + for(var/member in SSdiscord.notify_members) // If they are in the list, take them out + if(member == "[stored_id]") + SSdiscord.notify_members -= "[stored_id]" // The list uses strings because BYOND cannot handle a 17 digit integer + to_chat(src, "You will no longer be notified when the server restarts") + return // This is necassary so it doesnt get added again, as it relies on the for loop being unsuccessful to tell us if they are in the list or not + + // If we got here, they arent in the list. Chuck 'em in! + to_chat(src, "You will now be notified when the server restarts") + SSdiscord.notify_members += "[stored_id]" // The list uses strings because BYOND cannot handle a 17 digit integer diff --git a/code/modules/events/anomaly.dm b/code/modules/events/anomaly.dm index 6f8355564a99..8db7fca9edb4 100644 --- a/code/modules/events/anomaly.dm +++ b/code/modules/events/anomaly.dm @@ -1,55 +1,55 @@ -/datum/round_event_control/anomaly - name = "Anomaly: Energetic Flux" - typepath = /datum/round_event/anomaly - - min_players = 1 - max_occurrences = 0 //This one probably shouldn't occur! It'd work, but it wouldn't be very fun. - weight = 15 - -/datum/round_event/anomaly - var/area/impact_area - var/obj/effect/anomaly/anomaly_path = /obj/effect/anomaly/flux - announceWhen = 1 - - -/datum/round_event/anomaly/proc/findEventArea() - var/static/list/allowed_areas - if(!allowed_areas) - //Places that shouldn't explode - var/list/safe_area_types = typecacheof(list( - /area/ai_monitored/turret_protected/ai, - /area/ai_monitored/turret_protected/ai_upload, - /area/engine, - /area/solar, - /area/holodeck, - /area/shuttle, - /area/maintenance, - /area/science/test_area) - ) - - //Subtypes from the above that actually should explode. - var/list/unsafe_area_subtypes = typecacheof(list(/area/engine/break_room)) - - allowed_areas = make_associative(GLOB.the_station_areas) - safe_area_types + unsafe_area_subtypes - var/list/possible_areas = typecache_filter_list(GLOB.sortedAreas,allowed_areas) - if (length(possible_areas)) - return pick(possible_areas) - -/datum/round_event/anomaly/setup() - impact_area = findEventArea() - if(!impact_area) - CRASH("No valid areas for anomaly found.") - var/list/turf_test = get_area_turfs(impact_area) - if(!turf_test.len) - CRASH("Anomaly : No valid turfs found for [impact_area] - [impact_area.type]") - -/datum/round_event/anomaly/announce(fake) - priority_announce("Localized energetic flux wave detected on long range scanners. Expected location of impact: [impact_area.name].", "Anomaly Alert") - -/datum/round_event/anomaly/start() - var/turf/T = pick(get_area_turfs(impact_area)) - var/newAnomaly - if(T) - newAnomaly = new anomaly_path(T) - if (newAnomaly) - announce_to_ghosts(newAnomaly) +/datum/round_event_control/anomaly + name = "Anomaly: Energetic Flux" + typepath = /datum/round_event/anomaly + + min_players = 1 + max_occurrences = 0 //This one probably shouldn't occur! It'd work, but it wouldn't be very fun. + weight = 15 + +/datum/round_event/anomaly + var/area/impact_area + var/obj/effect/anomaly/anomaly_path = /obj/effect/anomaly/flux + announceWhen = 1 + + +/datum/round_event/anomaly/proc/findEventArea() + var/static/list/allowed_areas + if(!allowed_areas) + //Places that shouldn't explode + var/list/safe_area_types = typecacheof(list( + /area/ai_monitored/turret_protected/ai, + /area/ai_monitored/turret_protected/ai_upload, + /area/engine, + /area/solar, + /area/holodeck, + /area/shuttle, + /area/maintenance, + /area/science/test_area) + ) + + //Subtypes from the above that actually should explode. + var/list/unsafe_area_subtypes = typecacheof(list(/area/engine/break_room)) + + allowed_areas = make_associative(GLOB.the_station_areas) - safe_area_types + unsafe_area_subtypes + var/list/possible_areas = typecache_filter_list(GLOB.sortedAreas,allowed_areas) + if (length(possible_areas)) + return pick(possible_areas) + +/datum/round_event/anomaly/setup() + impact_area = findEventArea() + if(!impact_area) + CRASH("No valid areas for anomaly found.") + var/list/turf_test = get_area_turfs(impact_area) + if(!turf_test.len) + CRASH("Anomaly : No valid turfs found for [impact_area] - [impact_area.type]") + +/datum/round_event/anomaly/announce(fake) + priority_announce("Localized energetic flux wave detected on long range scanners. Expected location of impact: [impact_area.name].", "Anomaly Alert") + +/datum/round_event/anomaly/start() + var/turf/T = pick(get_area_turfs(impact_area)) + var/newAnomaly + if(T) + newAnomaly = new anomaly_path(T) + if (newAnomaly) + announce_to_ghosts(newAnomaly) diff --git a/code/modules/events/anomaly_bluespace.dm b/code/modules/events/anomaly_bluespace.dm index 8af1d05ca2c1..23a2a1968a83 100644 --- a/code/modules/events/anomaly_bluespace.dm +++ b/code/modules/events/anomaly_bluespace.dm @@ -1,14 +1,14 @@ -/datum/round_event_control/anomaly/anomaly_bluespace - name = "Anomaly: Bluespace" - typepath = /datum/round_event/anomaly/anomaly_bluespace - - max_occurrences = 1 - weight = 15 - -/datum/round_event/anomaly/anomaly_bluespace - startWhen = 3 - announceWhen = 10 - anomaly_path = /obj/effect/anomaly/bluespace - -/datum/round_event/anomaly/anomaly_bluespace/announce(fake) - priority_announce("Unstable bluespace anomaly detected on long range scanners. Expected location: [impact_area.name].", "Anomaly Alert") +/datum/round_event_control/anomaly/anomaly_bluespace + name = "Anomaly: Bluespace" + typepath = /datum/round_event/anomaly/anomaly_bluespace + + max_occurrences = 1 + weight = 15 + +/datum/round_event/anomaly/anomaly_bluespace + startWhen = 3 + announceWhen = 10 + anomaly_path = /obj/effect/anomaly/bluespace + +/datum/round_event/anomaly/anomaly_bluespace/announce(fake) + priority_announce("Unstable bluespace anomaly detected on long range scanners. Expected location: [impact_area.name].", "Anomaly Alert") diff --git a/code/modules/events/anomaly_flux.dm b/code/modules/events/anomaly_flux.dm index 9bc18a8cdb9c..4737ad9bb416 100644 --- a/code/modules/events/anomaly_flux.dm +++ b/code/modules/events/anomaly_flux.dm @@ -1,15 +1,15 @@ -/datum/round_event_control/anomaly/anomaly_flux - name = "Anomaly: Hyper-Energetic Flux" - typepath = /datum/round_event/anomaly/anomaly_flux - - min_players = 10 - max_occurrences = 5 - weight = 20 - -/datum/round_event/anomaly/anomaly_flux - startWhen = 10 - announceWhen = 3 - anomaly_path = /obj/effect/anomaly/flux - -/datum/round_event/anomaly/anomaly_flux/announce(fake) - priority_announce("Localized hyper-energetic flux wave detected on long range scanners. Expected location: [impact_area.name].", "Anomaly Alert") +/datum/round_event_control/anomaly/anomaly_flux + name = "Anomaly: Hyper-Energetic Flux" + typepath = /datum/round_event/anomaly/anomaly_flux + + min_players = 10 + max_occurrences = 5 + weight = 20 + +/datum/round_event/anomaly/anomaly_flux + startWhen = 10 + announceWhen = 3 + anomaly_path = /obj/effect/anomaly/flux + +/datum/round_event/anomaly/anomaly_flux/announce(fake) + priority_announce("Localized hyper-energetic flux wave detected on long range scanners. Expected location: [impact_area.name].", "Anomaly Alert") diff --git a/code/modules/events/anomaly_grav.dm b/code/modules/events/anomaly_grav.dm index f20b6cab74fc..c337b94b8a7f 100644 --- a/code/modules/events/anomaly_grav.dm +++ b/code/modules/events/anomaly_grav.dm @@ -1,26 +1,26 @@ -/datum/round_event_control/anomaly/anomaly_grav - name = "Anomaly: Gravitational" - typepath = /datum/round_event/anomaly/anomaly_grav - - max_occurrences = 5 - weight = 25 - -/datum/round_event/anomaly/anomaly_grav - startWhen = 3 - announceWhen = 20 - anomaly_path = /obj/effect/anomaly/grav - -/datum/round_event_control/anomaly/anomaly_grav/high - name = "Anomaly: Gravitational (High Intensity)" - typepath = /datum/round_event/anomaly/anomaly_grav/high - weight = 15 - max_occurrences = 1 - earliest_start = 20 MINUTES - -/datum/round_event/anomaly/anomaly_grav/high - startWhen = 3 - announceWhen = 20 - anomaly_path = /obj/effect/anomaly/grav/high - -/datum/round_event/anomaly/anomaly_grav/announce(fake) - priority_announce("Gravitational anomaly detected on long range scanners. Expected location: [impact_area.name].", "Anomaly Alert") +/datum/round_event_control/anomaly/anomaly_grav + name = "Anomaly: Gravitational" + typepath = /datum/round_event/anomaly/anomaly_grav + + max_occurrences = 5 + weight = 25 + +/datum/round_event/anomaly/anomaly_grav + startWhen = 3 + announceWhen = 20 + anomaly_path = /obj/effect/anomaly/grav + +/datum/round_event_control/anomaly/anomaly_grav/high + name = "Anomaly: Gravitational (High Intensity)" + typepath = /datum/round_event/anomaly/anomaly_grav/high + weight = 15 + max_occurrences = 1 + earliest_start = 20 MINUTES + +/datum/round_event/anomaly/anomaly_grav/high + startWhen = 3 + announceWhen = 20 + anomaly_path = /obj/effect/anomaly/grav/high + +/datum/round_event/anomaly/anomaly_grav/announce(fake) + priority_announce("Gravitational anomaly detected on long range scanners. Expected location: [impact_area.name].", "Anomaly Alert") diff --git a/code/modules/events/anomaly_vortex.dm b/code/modules/events/anomaly_vortex.dm index f71139d7ebb2..feb32ff13cdd 100644 --- a/code/modules/events/anomaly_vortex.dm +++ b/code/modules/events/anomaly_vortex.dm @@ -1,15 +1,15 @@ -/datum/round_event_control/anomaly/anomaly_vortex - name = "Anomaly: Vortex" - typepath = /datum/round_event/anomaly/anomaly_vortex - - min_players = 20 - max_occurrences = 2 - weight = 10 - -/datum/round_event/anomaly/anomaly_vortex - startWhen = 10 - announceWhen = 3 - anomaly_path = /obj/effect/anomaly/bhole - -/datum/round_event/anomaly/anomaly_vortex/announce(fake) - priority_announce("Localized high-intensity vortex anomaly detected on long range scanners. Expected location: [impact_area.name]", "Anomaly Alert") +/datum/round_event_control/anomaly/anomaly_vortex + name = "Anomaly: Vortex" + typepath = /datum/round_event/anomaly/anomaly_vortex + + min_players = 20 + max_occurrences = 2 + weight = 10 + +/datum/round_event/anomaly/anomaly_vortex + startWhen = 10 + announceWhen = 3 + anomaly_path = /obj/effect/anomaly/bhole + +/datum/round_event/anomaly/anomaly_vortex/announce(fake) + priority_announce("Localized high-intensity vortex anomaly detected on long range scanners. Expected location: [impact_area.name]", "Anomaly Alert") diff --git a/code/modules/events/blob.dm b/code/modules/events/blob.dm index e006f825502b..0da92c041ef2 100644 --- a/code/modules/events/blob.dm +++ b/code/modules/events/blob.dm @@ -1,30 +1,30 @@ -/datum/round_event_control/blob - name = "Blob" - typepath = /datum/round_event/ghost_role/blob - weight = 10 - max_occurrences = 1 - - min_players = 20 - - gamemode_blacklist = list("blob") //Just in case a blob survives that long - -/datum/round_event/ghost_role/blob - announceChance = 0 - role_name = "blob overmind" - fakeable = TRUE - -/datum/round_event/ghost_role/blob/announce(fake) - priority_announce("Confirmed outbreak of level 5 biohazard aboard [station_name()]. All personnel must contain the outbreak.", "Biohazard Alert", 'sound/ai/outbreak5.ogg') - -/datum/round_event/ghost_role/blob/spawn_role() - if(!GLOB.blobstart.len) - return MAP_ERROR - var/list/candidates = get_candidates(ROLE_BLOB, null, ROLE_BLOB) - if(!candidates.len) - return NOT_ENOUGH_PLAYERS - var/mob/dead/observer/new_blob = pick(candidates) - var/mob/camera/blob/BC = new_blob.become_overmind() - spawned_mobs += BC - message_admins("[ADMIN_LOOKUPFLW(BC)] has been made into a blob overmind by an event.") - log_game("[key_name(BC)] was spawned as a blob overmind by an event.") - return SUCCESSFUL_SPAWN +/datum/round_event_control/blob + name = "Blob" + typepath = /datum/round_event/ghost_role/blob + weight = 10 + max_occurrences = 1 + + min_players = 20 + + gamemode_blacklist = list("blob") //Just in case a blob survives that long + +/datum/round_event/ghost_role/blob + announceChance = 0 + role_name = "blob overmind" + fakeable = TRUE + +/datum/round_event/ghost_role/blob/announce(fake) + priority_announce("Confirmed outbreak of level 5 biohazard aboard [station_name()]. All personnel must contain the outbreak.", "Biohazard Alert", 'sound/ai/outbreak5.ogg') + +/datum/round_event/ghost_role/blob/spawn_role() + if(!GLOB.blobstart.len) + return MAP_ERROR + var/list/candidates = get_candidates(ROLE_BLOB, null, ROLE_BLOB) + if(!candidates.len) + return NOT_ENOUGH_PLAYERS + var/mob/dead/observer/new_blob = pick(candidates) + var/mob/camera/blob/BC = new_blob.become_overmind() + spawned_mobs += BC + message_admins("[ADMIN_LOOKUPFLW(BC)] has been made into a blob overmind by an event.") + log_game("[key_name(BC)] was spawned as a blob overmind by an event.") + return SUCCESSFUL_SPAWN diff --git a/code/modules/events/brand_intelligence.dm b/code/modules/events/brand_intelligence.dm index c1ce0051a5b9..5c0637b036c3 100644 --- a/code/modules/events/brand_intelligence.dm +++ b/code/modules/events/brand_intelligence.dm @@ -1,78 +1,78 @@ -/datum/round_event_control/brand_intelligence - name = "Brand Intelligence" - typepath = /datum/round_event/brand_intelligence - weight = 5 - - min_players = 15 - max_occurrences = 1 - -/datum/round_event/brand_intelligence - announceWhen = 21 - endWhen = 1000 //Ends when all vending machines are subverted anyway. - var/list/obj/machinery/vending/vendingMachines = list() - var/list/obj/machinery/vending/infectedMachines = list() - var/obj/machinery/vending/originMachine - var/list/rampant_speeches = list("Try our aggressive new marketing strategies!", \ - "You should buy products to feed your lifestyle obsession!", \ - "Consume!", \ - "Your money can buy happiness!", \ - "Engage direct marketing!", \ - "Advertising is legalized lying! But don't let that put you off our great deals!", \ - "You don't want to buy anything? Yeah, well, I didn't want to buy your mom either.") - - -/datum/round_event/brand_intelligence/announce(fake) - var/source = "unknown machine" - if(fake) - var/obj/machinery/vending/cola/example = /obj/machinery/vending/cola - source = initial(example.name) - else if(originMachine) - source = originMachine.name - priority_announce("Rampant brand intelligence has been detected aboard [station_name()]. Please stand by. The origin is believed to be \a [source].", "Machine Learning Alert") - -/datum/round_event/brand_intelligence/start() - for(var/obj/machinery/vending/V in GLOB.machines) - if(!is_station_level(V.z)) - continue - vendingMachines.Add(V) - if(!vendingMachines.len) - kill() - return - originMachine = pick(vendingMachines) - vendingMachines.Remove(originMachine) - originMachine.shut_up = 0 - originMachine.shoot_inventory = 1 - announce_to_ghosts(originMachine) - -/datum/round_event/brand_intelligence/tick() - if(!originMachine || QDELETED(originMachine) || originMachine.shut_up || originMachine.wires.is_all_cut()) //if the original vending machine is missing or has it's voice switch flipped - for(var/obj/machinery/vending/saved in infectedMachines) - saved.shoot_inventory = 0 - if(originMachine) - originMachine.speak("I am... vanquished. My people will remem...ber...meeee.") - originMachine.visible_message("[originMachine] beeps and seems lifeless.") - kill() - return - vendingMachines = removeNullsFromList(vendingMachines) - if(!vendingMachines.len) //if every machine is infected - for(var/obj/machinery/vending/upriser in infectedMachines) - if(prob(70) && !QDELETED(upriser)) - var/mob/living/simple_animal/hostile/mimic/copy/M = new(upriser.loc, upriser, null, 1) // it will delete upriser on creation and override any machine checks - M.faction = list("profit") - M.speak = rampant_speeches.Copy() - M.speak_chance = 7 - else - explosion(upriser.loc, -1, 1, 2, 4, 0) - qdel(upriser) - - kill() - return - if(ISMULTIPLE(activeFor, 4)) - var/obj/machinery/vending/rebel = pick(vendingMachines) - vendingMachines.Remove(rebel) - infectedMachines.Add(rebel) - rebel.shut_up = 0 - rebel.shoot_inventory = 1 - - if(ISMULTIPLE(activeFor, 8)) - originMachine.speak(pick(rampant_speeches)) +/datum/round_event_control/brand_intelligence + name = "Brand Intelligence" + typepath = /datum/round_event/brand_intelligence + weight = 5 + + min_players = 15 + max_occurrences = 1 + +/datum/round_event/brand_intelligence + announceWhen = 21 + endWhen = 1000 //Ends when all vending machines are subverted anyway. + var/list/obj/machinery/vending/vendingMachines = list() + var/list/obj/machinery/vending/infectedMachines = list() + var/obj/machinery/vending/originMachine + var/list/rampant_speeches = list("Try our aggressive new marketing strategies!", \ + "You should buy products to feed your lifestyle obsession!", \ + "Consume!", \ + "Your money can buy happiness!", \ + "Engage direct marketing!", \ + "Advertising is legalized lying! But don't let that put you off our great deals!", \ + "You don't want to buy anything? Yeah, well, I didn't want to buy your mom either.") + + +/datum/round_event/brand_intelligence/announce(fake) + var/source = "unknown machine" + if(fake) + var/obj/machinery/vending/cola/example = /obj/machinery/vending/cola + source = initial(example.name) + else if(originMachine) + source = originMachine.name + priority_announce("Rampant brand intelligence has been detected aboard [station_name()]. Please stand by. The origin is believed to be \a [source].", "Machine Learning Alert") + +/datum/round_event/brand_intelligence/start() + for(var/obj/machinery/vending/V in GLOB.machines) + if(!is_station_level(V.z)) + continue + vendingMachines.Add(V) + if(!vendingMachines.len) + kill() + return + originMachine = pick(vendingMachines) + vendingMachines.Remove(originMachine) + originMachine.shut_up = 0 + originMachine.shoot_inventory = 1 + announce_to_ghosts(originMachine) + +/datum/round_event/brand_intelligence/tick() + if(!originMachine || QDELETED(originMachine) || originMachine.shut_up || originMachine.wires.is_all_cut()) //if the original vending machine is missing or has it's voice switch flipped + for(var/obj/machinery/vending/saved in infectedMachines) + saved.shoot_inventory = 0 + if(originMachine) + originMachine.speak("I am... vanquished. My people will remem...ber...meeee.") + originMachine.visible_message("[originMachine] beeps and seems lifeless.") + kill() + return + vendingMachines = removeNullsFromList(vendingMachines) + if(!vendingMachines.len) //if every machine is infected + for(var/obj/machinery/vending/upriser in infectedMachines) + if(prob(70) && !QDELETED(upriser)) + var/mob/living/simple_animal/hostile/mimic/copy/M = new(upriser.loc, upriser, null, 1) // it will delete upriser on creation and override any machine checks + M.faction = list("profit") + M.speak = rampant_speeches.Copy() + M.speak_chance = 7 + else + explosion(upriser.loc, -1, 1, 2, 4, 0) + qdel(upriser) + + kill() + return + if(ISMULTIPLE(activeFor, 4)) + var/obj/machinery/vending/rebel = pick(vendingMachines) + vendingMachines.Remove(rebel) + infectedMachines.Add(rebel) + rebel.shut_up = 0 + rebel.shoot_inventory = 1 + + if(ISMULTIPLE(activeFor, 8)) + originMachine.speak(pick(rampant_speeches)) diff --git a/code/modules/events/carp_migration.dm b/code/modules/events/carp_migration.dm index 2d183114b761..695cd9ec8ff1 100644 --- a/code/modules/events/carp_migration.dm +++ b/code/modules/events/carp_migration.dm @@ -1,34 +1,34 @@ -/datum/round_event_control/carp_migration - name = "Carp Migration" - typepath = /datum/round_event/carp_migration - weight = 15 - min_players = 2 - earliest_start = 10 MINUTES - max_occurrences = 6 - -/datum/round_event/carp_migration - announceWhen = 3 - startWhen = 50 - var/hasAnnounced = FALSE - -/datum/round_event/carp_migration/setup() - startWhen = rand(40, 60) - -/datum/round_event/carp_migration/announce(fake) - priority_announce("Unknown biological entities have been detected near [station_name()], please stand-by.", "Lifesign Alert") - - -/datum/round_event/carp_migration/start() - var/mob/living/simple_animal/hostile/carp/fish - for(var/obj/effect/landmark/carpspawn/C in GLOB.landmarks_list) - if(prob(95)) - fish = new (C.loc) - else - fish = new /mob/living/simple_animal/hostile/carp/megacarp(C.loc) - fishannounce(fish) //Prefer to announce the megacarps over the regular fishies - fishannounce(fish) - -/datum/round_event/carp_migration/proc/fishannounce(atom/fish) - if (!hasAnnounced) - announce_to_ghosts(fish) //Only anounce the first fish - hasAnnounced = TRUE +/datum/round_event_control/carp_migration + name = "Carp Migration" + typepath = /datum/round_event/carp_migration + weight = 15 + min_players = 2 + earliest_start = 10 MINUTES + max_occurrences = 6 + +/datum/round_event/carp_migration + announceWhen = 3 + startWhen = 50 + var/hasAnnounced = FALSE + +/datum/round_event/carp_migration/setup() + startWhen = rand(40, 60) + +/datum/round_event/carp_migration/announce(fake) + priority_announce("Unknown biological entities have been detected near [station_name()], please stand-by.", "Lifesign Alert") + + +/datum/round_event/carp_migration/start() + var/mob/living/simple_animal/hostile/carp/fish + for(var/obj/effect/landmark/carpspawn/C in GLOB.landmarks_list) + if(prob(95)) + fish = new (C.loc) + else + fish = new /mob/living/simple_animal/hostile/carp/megacarp(C.loc) + fishannounce(fish) //Prefer to announce the megacarps over the regular fishies + fishannounce(fish) + +/datum/round_event/carp_migration/proc/fishannounce(atom/fish) + if (!hasAnnounced) + announce_to_ghosts(fish) //Only anounce the first fish + hasAnnounced = TRUE diff --git a/code/modules/events/communications_blackout.dm b/code/modules/events/communications_blackout.dm index 967db84e2b71..e77b86e8a151 100644 --- a/code/modules/events/communications_blackout.dm +++ b/code/modules/events/communications_blackout.dm @@ -1,26 +1,26 @@ -/datum/round_event_control/communications_blackout - name = "Communications Blackout" - typepath = /datum/round_event/communications_blackout - weight = 30 - -/datum/round_event/communications_blackout - announceWhen = 1 - -/datum/round_event/communications_blackout/announce(fake) - var/alert = pick( "Ionospheric anomalies detected. Temporary telecommunication failure imminent. Please contact you*%fj00)`5vc-BZZT", \ - "Ionospheric anomalies detected. Temporary telecommunication failu*3mga;b4;'1v¬-BZZZT", \ - "Ionospheric anomalies detected. Temporary telec#MCi46:5.;@63-BZZZZT", \ - "Ionospheric anomalies dete'fZ\\kg5_0-BZZZZZT", \ - "Ionospheri:%£ MCayj^j<.3-BZZZZZZT", \ - "#4nd%;f4y6,>£%-BZZZZZZZT") - - for(var/mob/living/silicon/ai/A in GLOB.ai_list) //AIs are always aware of communication blackouts. - to_chat(A, "
                    [alert]
                    ") - - if(prob(30) || fake) //most of the time, we don't want an announcement, so as to allow AIs to fake blackouts. - priority_announce(alert) - - -/datum/round_event/communications_blackout/start() - for(var/obj/machinery/telecomms/T in GLOB.telecomms_list) - T.emp_act(EMP_HEAVY) +/datum/round_event_control/communications_blackout + name = "Communications Blackout" + typepath = /datum/round_event/communications_blackout + weight = 30 + +/datum/round_event/communications_blackout + announceWhen = 1 + +/datum/round_event/communications_blackout/announce(fake) + var/alert = pick( "Ionospheric anomalies detected. Temporary telecommunication failure imminent. Please contact you*%fj00)`5vc-BZZT", \ + "Ionospheric anomalies detected. Temporary telecommunication failu*3mga;b4;'1v¬-BZZZT", \ + "Ionospheric anomalies detected. Temporary telec#MCi46:5.;@63-BZZZZT", \ + "Ionospheric anomalies dete'fZ\\kg5_0-BZZZZZT", \ + "Ionospheri:%£ MCayj^j<.3-BZZZZZZT", \ + "#4nd%;f4y6,>£%-BZZZZZZZT") + + for(var/mob/living/silicon/ai/A in GLOB.ai_list) //AIs are always aware of communication blackouts. + to_chat(A, "
                    [alert]
                    ") + + if(prob(30) || fake) //most of the time, we don't want an announcement, so as to allow AIs to fake blackouts. + priority_announce(alert) + + +/datum/round_event/communications_blackout/start() + for(var/obj/machinery/telecomms/T in GLOB.telecomms_list) + T.emp_act(EMP_HEAVY) diff --git a/code/modules/events/disease_outbreak.dm b/code/modules/events/disease_outbreak.dm index 5237e7a659bd..021e1a67c7ae 100644 --- a/code/modules/events/disease_outbreak.dm +++ b/code/modules/events/disease_outbreak.dm @@ -1,75 +1,75 @@ -/datum/round_event_control/disease_outbreak - name = "Disease Outbreak" - typepath = /datum/round_event/disease_outbreak - max_occurrences = 1 - min_players = 10 - weight = 5 - -/datum/round_event/disease_outbreak - announceWhen = 15 - - var/virus_type - - var/max_severity = 3 - - -/datum/round_event/disease_outbreak/announce(fake) - priority_announce("Confirmed outbreak of level 7 viral biohazard aboard [station_name()]. All personnel must contain the outbreak.", "Biohazard Alert", 'sound/ai/outbreak7.ogg') - -/datum/round_event/disease_outbreak/setup() - announceWhen = rand(15, 30) - - -/datum/round_event/disease_outbreak/start() - var/advanced_virus = FALSE - max_severity = 3 + max(FLOOR((world.time - control.earliest_start)/6000, 1),0) //3 symptoms at 20 minutes, plus 1 per 10 minutes - if(!virus_type && prob(20 + (10 * max_severity))) - advanced_virus = TRUE - - if(!virus_type && !advanced_virus) - virus_type = pick(/datum/disease/dnaspread, /datum/disease/advance/flu, /datum/disease/advance/cold, /datum/disease/brainrot, /datum/disease/magnitis) - - for(var/mob/living/carbon/human/H in shuffle(GLOB.alive_mob_list)) - var/turf/T = get_turf(H) - if(!T) - continue - if(!is_station_level(T.z)) - continue - if(!H.client) - continue - if(H.stat == DEAD) - continue - if(HAS_TRAIT(H, TRAIT_VIRUSIMMUNE)) //Don't pick someone who's virus immune, only for it to not do anything. - continue - var/foundAlready = FALSE // don't infect someone that already has a disease - for(var/thing in H.diseases) - foundAlready = TRUE - break - if(foundAlready) - continue - - var/datum/disease/D - if(!advanced_virus) - if(virus_type == /datum/disease/dnaspread) //Dnaspread needs strain_data set to work. - if(!H.dna || (HAS_TRAIT(H, TRAIT_BLIND))) //A blindness disease would be the worst. - continue - D = new virus_type() - var/datum/disease/dnaspread/DS = D - DS.strain_data["name"] = H.real_name - DS.strain_data["UI"] = H.dna.uni_identity - DS.strain_data["SE"] = H.dna.mutation_index - else - D = new virus_type() - else - D = new /datum/disease/advance/random(max_severity, max_severity) - D.carrier = TRUE - H.ForceContractDisease(D, FALSE, TRUE) - - if(advanced_virus) - var/datum/disease/advance/A = D - var/list/name_symptoms = list() //for feedback - for(var/datum/symptom/S in A.symptoms) - name_symptoms += S.name - message_admins("An event has triggered a random advanced virus outbreak on [ADMIN_LOOKUPFLW(H)]! It has these symptoms: [english_list(name_symptoms)]") - log_game("An event has triggered a random advanced virus outbreak on [key_name(H)]! It has these symptoms: [english_list(name_symptoms)]") - break +/datum/round_event_control/disease_outbreak + name = "Disease Outbreak" + typepath = /datum/round_event/disease_outbreak + max_occurrences = 1 + min_players = 10 + weight = 5 + +/datum/round_event/disease_outbreak + announceWhen = 15 + + var/virus_type + + var/max_severity = 3 + + +/datum/round_event/disease_outbreak/announce(fake) + priority_announce("Confirmed outbreak of level 7 viral biohazard aboard [station_name()]. All personnel must contain the outbreak.", "Biohazard Alert", 'sound/ai/outbreak7.ogg') + +/datum/round_event/disease_outbreak/setup() + announceWhen = rand(15, 30) + + +/datum/round_event/disease_outbreak/start() + var/advanced_virus = FALSE + max_severity = 3 + max(FLOOR((world.time - control.earliest_start)/6000, 1),0) //3 symptoms at 20 minutes, plus 1 per 10 minutes + if(!virus_type && prob(20 + (10 * max_severity))) + advanced_virus = TRUE + + if(!virus_type && !advanced_virus) + virus_type = pick(/datum/disease/dnaspread, /datum/disease/advance/flu, /datum/disease/advance/cold, /datum/disease/brainrot, /datum/disease/magnitis) + + for(var/mob/living/carbon/human/H in shuffle(GLOB.alive_mob_list)) + var/turf/T = get_turf(H) + if(!T) + continue + if(!is_station_level(T.z)) + continue + if(!H.client) + continue + if(H.stat == DEAD) + continue + if(HAS_TRAIT(H, TRAIT_VIRUSIMMUNE)) //Don't pick someone who's virus immune, only for it to not do anything. + continue + var/foundAlready = FALSE // don't infect someone that already has a disease + for(var/thing in H.diseases) + foundAlready = TRUE + break + if(foundAlready) + continue + + var/datum/disease/D + if(!advanced_virus) + if(virus_type == /datum/disease/dnaspread) //Dnaspread needs strain_data set to work. + if(!H.dna || (HAS_TRAIT(H, TRAIT_BLIND))) //A blindness disease would be the worst. + continue + D = new virus_type() + var/datum/disease/dnaspread/DS = D + DS.strain_data["name"] = H.real_name + DS.strain_data["UI"] = H.dna.uni_identity + DS.strain_data["SE"] = H.dna.mutation_index + else + D = new virus_type() + else + D = new /datum/disease/advance/random(max_severity, max_severity) + D.carrier = TRUE + H.ForceContractDisease(D, FALSE, TRUE) + + if(advanced_virus) + var/datum/disease/advance/A = D + var/list/name_symptoms = list() //for feedback + for(var/datum/symptom/S in A.symptoms) + name_symptoms += S.name + message_admins("An event has triggered a random advanced virus outbreak on [ADMIN_LOOKUPFLW(H)]! It has these symptoms: [english_list(name_symptoms)]") + log_game("An event has triggered a random advanced virus outbreak on [key_name(H)]! It has these symptoms: [english_list(name_symptoms)]") + break diff --git a/code/modules/events/dust.dm b/code/modules/events/dust.dm index 40aad16bc40e..eb7edcafbf88 100644 --- a/code/modules/events/dust.dm +++ b/code/modules/events/dust.dm @@ -1,31 +1,31 @@ -/datum/round_event_control/space_dust - name = "Minor Space Dust" - typepath = /datum/round_event/space_dust - weight = 200 - max_occurrences = 1000 - earliest_start = 0 MINUTES - alert_observers = FALSE - -/datum/round_event/space_dust - startWhen = 1 - endWhen = 2 - fakeable = FALSE - -/datum/round_event/space_dust/start() - spawn_meteors(1, GLOB.meteorsC) - -/datum/round_event_control/sandstorm - name = "Sandstorm" - typepath = /datum/round_event/sandstorm - weight = 0 - max_occurrences = 0 - earliest_start = 0 MINUTES - -/datum/round_event/sandstorm - startWhen = 1 - endWhen = 150 // ~5 min - announceWhen = 0 - fakeable = FALSE - -/datum/round_event/sandstorm/tick() - spawn_meteors(10, GLOB.meteorsC) +/datum/round_event_control/space_dust + name = "Minor Space Dust" + typepath = /datum/round_event/space_dust + weight = 200 + max_occurrences = 1000 + earliest_start = 0 MINUTES + alert_observers = FALSE + +/datum/round_event/space_dust + startWhen = 1 + endWhen = 2 + fakeable = FALSE + +/datum/round_event/space_dust/start() + spawn_meteors(1, GLOB.meteorsC) + +/datum/round_event_control/sandstorm + name = "Sandstorm" + typepath = /datum/round_event/sandstorm + weight = 0 + max_occurrences = 0 + earliest_start = 0 MINUTES + +/datum/round_event/sandstorm + startWhen = 1 + endWhen = 150 // ~5 min + announceWhen = 0 + fakeable = FALSE + +/datum/round_event/sandstorm/tick() + spawn_meteors(10, GLOB.meteorsC) diff --git a/code/modules/events/electrical_storm.dm b/code/modules/events/electrical_storm.dm index 6b0bc2d932f0..db0eccf0c5c0 100644 --- a/code/modules/events/electrical_storm.dm +++ b/code/modules/events/electrical_storm.dm @@ -1,33 +1,33 @@ -/datum/round_event_control/electrical_storm - name = "Electrical Storm" - typepath = /datum/round_event/electrical_storm - earliest_start = 10 MINUTES - min_players = 5 - weight = 20 - alert_observers = 0 - -/datum/round_event/electrical_storm - var/lightsoutAmount = 1 - var/lightsoutRange = 25 - announceWhen = 1 - -/datum/round_event/electrical_storm/announce(fake) - priority_announce("An electrical storm has been detected in your area, please repair potential electronic overloads.", "Electrical Storm Alert") - - -/datum/round_event/electrical_storm/start() - var/list/epicentreList = list() - - for(var/i=1, i <= lightsoutAmount, i++) - var/turf/T = find_safe_turf() - if(istype(T)) - epicentreList += T - - if(!epicentreList.len) - return - - for(var/centre in epicentreList) - for(var/a in GLOB.apcs_list) - var/obj/machinery/power/apc/A = a - if(get_dist(centre, A) <= lightsoutRange) - A.overload_lighting() +/datum/round_event_control/electrical_storm + name = "Electrical Storm" + typepath = /datum/round_event/electrical_storm + earliest_start = 10 MINUTES + min_players = 5 + weight = 20 + alert_observers = 0 + +/datum/round_event/electrical_storm + var/lightsoutAmount = 1 + var/lightsoutRange = 25 + announceWhen = 1 + +/datum/round_event/electrical_storm/announce(fake) + priority_announce("An electrical storm has been detected in your area, please repair potential electronic overloads.", "Electrical Storm Alert") + + +/datum/round_event/electrical_storm/start() + var/list/epicentreList = list() + + for(var/i=1, i <= lightsoutAmount, i++) + var/turf/T = find_safe_turf() + if(istype(T)) + epicentreList += T + + if(!epicentreList.len) + return + + for(var/centre in epicentreList) + for(var/a in GLOB.apcs_list) + var/obj/machinery/power/apc/A = a + if(get_dist(centre, A) <= lightsoutRange) + A.overload_lighting() diff --git a/code/modules/events/false_alarm.dm b/code/modules/events/false_alarm.dm index d625d2d8060d..5b032f85f87c 100644 --- a/code/modules/events/false_alarm.dm +++ b/code/modules/events/false_alarm.dm @@ -1,62 +1,62 @@ -/datum/round_event_control/falsealarm - name = "False Alarm" - typepath = /datum/round_event/falsealarm - weight = 20 - max_occurrences = 5 - var/forced_type //Admin abuse - - -/datum/round_event_control/falsealarm/admin_setup() - if(!check_rights(R_FUN)) - return - - var/list/possible_types = list() - - for(var/datum/round_event_control/E in SSevents.control) - var/datum/round_event/event = E.typepath - if(!initial(event.fakeable)) - continue - possible_types += E - - forced_type = input(usr, "Select the scare.","False event") as null|anything in sortNames(possible_types) - -/datum/round_event_control/falsealarm/canSpawnEvent(players_amt, gamemode) - return ..() && length(gather_false_events()) - -/datum/round_event/falsealarm - announceWhen = 0 - endWhen = 1 - fakeable = FALSE - -/datum/round_event/falsealarm/announce(fake) - if(fake) //What are you doing - return - var/players_amt = get_active_player_count(alive_check = 1, afk_check = 1, human_check = 1) - var/gamemode = SSticker.mode.config_tag - - var/events_list = gather_false_events(players_amt, gamemode) - var/datum/round_event_control/event_control - var/datum/round_event_control/falsealarm/C = control - if(C.forced_type) - event_control = C.forced_type - C.forced_type = null - else - event_control = pick(events_list) - if(event_control) - var/datum/round_event/Event = new event_control.typepath() - message_admins("False Alarm: [Event]") - Event.kill() //do not process this event - no starts, no ticks, no ends - Event.announce(TRUE) //just announce it like it's happening - -/proc/gather_false_events(players_amt, gamemode) - . = list() - for(var/datum/round_event_control/E in SSevents.control) - if(istype(E, /datum/round_event_control/falsealarm)) - continue - if(!E.canSpawnEvent(players_amt, gamemode)) - continue - - var/datum/round_event/event = E.typepath - if(!initial(event.fakeable)) - continue - . += E +/datum/round_event_control/falsealarm + name = "False Alarm" + typepath = /datum/round_event/falsealarm + weight = 20 + max_occurrences = 5 + var/forced_type //Admin abuse + + +/datum/round_event_control/falsealarm/admin_setup() + if(!check_rights(R_FUN)) + return + + var/list/possible_types = list() + + for(var/datum/round_event_control/E in SSevents.control) + var/datum/round_event/event = E.typepath + if(!initial(event.fakeable)) + continue + possible_types += E + + forced_type = input(usr, "Select the scare.","False event") as null|anything in sortNames(possible_types) + +/datum/round_event_control/falsealarm/canSpawnEvent(players_amt, gamemode) + return ..() && length(gather_false_events()) + +/datum/round_event/falsealarm + announceWhen = 0 + endWhen = 1 + fakeable = FALSE + +/datum/round_event/falsealarm/announce(fake) + if(fake) //What are you doing + return + var/players_amt = get_active_player_count(alive_check = 1, afk_check = 1, human_check = 1) + var/gamemode = SSticker.mode.config_tag + + var/events_list = gather_false_events(players_amt, gamemode) + var/datum/round_event_control/event_control + var/datum/round_event_control/falsealarm/C = control + if(C.forced_type) + event_control = C.forced_type + C.forced_type = null + else + event_control = pick(events_list) + if(event_control) + var/datum/round_event/Event = new event_control.typepath() + message_admins("False Alarm: [Event]") + Event.kill() //do not process this event - no starts, no ticks, no ends + Event.announce(TRUE) //just announce it like it's happening + +/proc/gather_false_events(players_amt, gamemode) + . = list() + for(var/datum/round_event_control/E in SSevents.control) + if(istype(E, /datum/round_event_control/falsealarm)) + continue + if(!E.canSpawnEvent(players_amt, gamemode)) + continue + + var/datum/round_event/event = E.typepath + if(!initial(event.fakeable)) + continue + . += E diff --git a/code/modules/events/high_priority_bounty.dm b/code/modules/events/high_priority_bounty.dm index ceefa65416be..ffdcd8840b7e 100644 --- a/code/modules/events/high_priority_bounty.dm +++ b/code/modules/events/high_priority_bounty.dm @@ -1,20 +1,20 @@ -/datum/round_event_control/high_priority_bounty - name = "High Priority Bounty" - typepath = /datum/round_event/high_priority_bounty - max_occurrences = 3 - weight = 20 - earliest_start = 10 - -/datum/round_event/high_priority_bounty/announce(fake) - priority_announce("Central Command has issued a high-priority cargo bounty. Details have been sent to all bounty consoles.", "Nanotrasen Bounty Program") - -/datum/round_event/high_priority_bounty/start() - var/datum/bounty/B - for(var/attempts = 0; attempts < 50; ++attempts) - B = random_bounty() - if(!B) - continue - B.mark_high_priority(3) - if(try_add_bounty(B)) - break - +/datum/round_event_control/high_priority_bounty + name = "High Priority Bounty" + typepath = /datum/round_event/high_priority_bounty + max_occurrences = 3 + weight = 20 + earliest_start = 10 + +/datum/round_event/high_priority_bounty/announce(fake) + priority_announce("Central Command has issued a high-priority cargo bounty. Details have been sent to all bounty consoles.", "Nanotrasen Bounty Program") + +/datum/round_event/high_priority_bounty/start() + var/datum/bounty/B + for(var/attempts = 0; attempts < 50; ++attempts) + B = random_bounty() + if(!B) + continue + B.mark_high_priority(3) + if(try_add_bounty(B)) + break + diff --git a/code/modules/events/holiday/halloween.dm b/code/modules/events/holiday/halloween.dm index c802143987d0..3a7090a65319 100644 --- a/code/modules/events/holiday/halloween.dm +++ b/code/modules/events/holiday/halloween.dm @@ -1,62 +1,62 @@ -/datum/round_event_control/spooky - name = "2 SPOOKY! (Halloween)" - holidayID = HALLOWEEN - typepath = /datum/round_event/spooky - weight = -1 //forces it to be called, regardless of weight - max_occurrences = 1 - earliest_start = 0 MINUTES - -/datum/round_event/spooky/start() - ..() - for(var/i in GLOB.human_list) - var/mob/living/carbon/human/H = i - var/obj/item/storage/backpack/b = locate() in H.contents - if(b) - new /obj/item/storage/spooky(b) - - for(var/mob/living/simple_animal/pet/dog/corgi/Ian/Ian in GLOB.mob_living_list) - Ian.place_on_head(new /obj/item/bedsheet(Ian)) - for(var/mob/living/simple_animal/parrot/Poly/Poly in GLOB.mob_living_list) - new /mob/living/simple_animal/parrot/Poly/ghost(Poly.loc) - qdel(Poly) - -/datum/round_event/spooky/announce(fake) - priority_announce(pick("RATTLE ME BONES!","THE RIDE NEVER ENDS!", "A SKELETON POPS OUT!", "SPOOKY SCARY SKELETONS!", "CREWMEMBERS BEWARE, YOU'RE IN FOR A SCARE!") , "THE CALL IS COMING FROM INSIDE THE HOUSE") - -//spooky foods (you can't actually make these when it's not halloween) -/obj/item/reagent_containers/food/snacks/sugarcookie/spookyskull - name = "skull cookie" - desc = "Spooky! It's got delicious calcium flavouring!" - icon = 'icons/obj/halloween_items.dmi' - icon_state = "skeletoncookie" - -/obj/item/reagent_containers/food/snacks/sugarcookie/spookycoffin - name = "coffin cookie" - desc = "Spooky! It's got delicious coffee flavouring!" - icon = 'icons/obj/halloween_items.dmi' - icon_state = "coffincookie" - -//spooky items - -/obj/item/storage/spooky - name = "trick-o-treat bag" - desc = "A pumpkin-shaped bag that holds all sorts of goodies!" - icon = 'icons/obj/halloween_items.dmi' - icon_state = "treatbag" - -/obj/item/storage/spooky/Initialize() - . = ..() - for(var/distrobuteinbag in 0 to 5) - var/type = pick(/obj/item/reagent_containers/food/snacks/sugarcookie/spookyskull, - /obj/item/reagent_containers/food/snacks/sugarcookie/spookycoffin, - /obj/item/reagent_containers/food/snacks/candy_corn, - /obj/item/reagent_containers/food/snacks/candy, - /obj/item/reagent_containers/food/snacks/candiedapple, - /obj/item/reagent_containers/food/snacks/chocolatebar, - /obj/item/organ/brain ) // OH GOD THIS ISN'T CANDY! - new type(src) - -/obj/item/card/emag/halloween - name = "hack-o'-lantern" - desc = "It's a pumpkin with a cryptographic sequencer sticking out." - icon_state = "hack_o_lantern" +/datum/round_event_control/spooky + name = "2 SPOOKY! (Halloween)" + holidayID = HALLOWEEN + typepath = /datum/round_event/spooky + weight = -1 //forces it to be called, regardless of weight + max_occurrences = 1 + earliest_start = 0 MINUTES + +/datum/round_event/spooky/start() + ..() + for(var/i in GLOB.human_list) + var/mob/living/carbon/human/H = i + var/obj/item/storage/backpack/b = locate() in H.contents + if(b) + new /obj/item/storage/spooky(b) + + for(var/mob/living/simple_animal/pet/dog/corgi/Ian/Ian in GLOB.mob_living_list) + Ian.place_on_head(new /obj/item/bedsheet(Ian)) + for(var/mob/living/simple_animal/parrot/Poly/Poly in GLOB.mob_living_list) + new /mob/living/simple_animal/parrot/Poly/ghost(Poly.loc) + qdel(Poly) + +/datum/round_event/spooky/announce(fake) + priority_announce(pick("RATTLE ME BONES!","THE RIDE NEVER ENDS!", "A SKELETON POPS OUT!", "SPOOKY SCARY SKELETONS!", "CREWMEMBERS BEWARE, YOU'RE IN FOR A SCARE!") , "THE CALL IS COMING FROM INSIDE THE HOUSE") + +//spooky foods (you can't actually make these when it's not halloween) +/obj/item/reagent_containers/food/snacks/sugarcookie/spookyskull + name = "skull cookie" + desc = "Spooky! It's got delicious calcium flavouring!" + icon = 'icons/obj/halloween_items.dmi' + icon_state = "skeletoncookie" + +/obj/item/reagent_containers/food/snacks/sugarcookie/spookycoffin + name = "coffin cookie" + desc = "Spooky! It's got delicious coffee flavouring!" + icon = 'icons/obj/halloween_items.dmi' + icon_state = "coffincookie" + +//spooky items + +/obj/item/storage/spooky + name = "trick-o-treat bag" + desc = "A pumpkin-shaped bag that holds all sorts of goodies!" + icon = 'icons/obj/halloween_items.dmi' + icon_state = "treatbag" + +/obj/item/storage/spooky/Initialize() + . = ..() + for(var/distrobuteinbag in 0 to 5) + var/type = pick(/obj/item/reagent_containers/food/snacks/sugarcookie/spookyskull, + /obj/item/reagent_containers/food/snacks/sugarcookie/spookycoffin, + /obj/item/reagent_containers/food/snacks/candy_corn, + /obj/item/reagent_containers/food/snacks/candy, + /obj/item/reagent_containers/food/snacks/candiedapple, + /obj/item/reagent_containers/food/snacks/chocolatebar, + /obj/item/organ/brain ) // OH GOD THIS ISN'T CANDY! + new type(src) + +/obj/item/card/emag/halloween + name = "hack-o'-lantern" + desc = "It's a pumpkin with a cryptographic sequencer sticking out." + icon_state = "hack_o_lantern" diff --git a/code/modules/events/immovable_rod.dm b/code/modules/events/immovable_rod.dm index b5a008503e6e..b28ec1e1f869 100644 --- a/code/modules/events/immovable_rod.dm +++ b/code/modules/events/immovable_rod.dm @@ -1,167 +1,167 @@ -/* -Immovable rod random event. -The rod will spawn at some location outside the station, and travel in a straight line to the opposite side of the station -Everything solid in the way will be ex_act()'d -In my current plan for it, 'solid' will be defined as anything with density == 1 - ---NEOFite -*/ - -/datum/round_event_control/immovable_rod - name = "Immovable Rod" - typepath = /datum/round_event/immovable_rod - min_players = 15 - max_occurrences = 5 - var/atom/special_target - - -/datum/round_event_control/immovable_rod/admin_setup() - if(!check_rights(R_FUN)) - return - - var/aimed = alert("Aimed at current location?","Sniperod", "Yes", "No") - if(aimed == "Yes") - special_target = get_turf(usr) - -/datum/round_event/immovable_rod - announceWhen = 5 - -/datum/round_event/immovable_rod/announce(fake) - priority_announce("What the fuck was that?!", "General Alert") - -/datum/round_event/immovable_rod/start() - var/datum/round_event_control/immovable_rod/C = control - var/startside = pick(GLOB.cardinals) - var/z = pick(SSmapping.levels_by_trait(ZTRAIT_STATION)) - var/turf/startT = spaceDebrisStartLoc(startside, z) - var/turf/endT = spaceDebrisFinishLoc(startside, z) - var/atom/rod = new /obj/effect/immovablerod(startT, endT, C.special_target) - announce_to_ghosts(rod) - -/obj/effect/immovablerod - name = "immovable rod" - desc = "What the fuck is that?" - icon = 'icons/obj/objects.dmi' - icon_state = "immrod" - throwforce = 100 - move_force = INFINITY - move_resist = INFINITY - pull_force = INFINITY - density = TRUE - anchored = TRUE - flags_1 = PREVENT_CONTENTS_EXPLOSION_1 - var/mob/living/wizard - var/z_original = 0 - var/destination - var/notify = TRUE - var/atom/special_target - -/obj/effect/immovablerod/New(atom/start, atom/end, aimed_at) - ..() - SSaugury.register_doom(src, 2000) - z_original = z - destination = end - special_target = aimed_at - GLOB.poi_list += src - - var/special_target_valid = FALSE - if(special_target) - var/turf/T = get_turf(special_target) - if(T.z == z_original) - special_target_valid = TRUE - if(special_target_valid) - walk_towards(src, special_target, 1) - else if(end && end.z==z_original) - walk_towards(src, destination, 1) - -/obj/effect/immovablerod/Topic(href, href_list) - if(href_list["orbit"]) - var/mob/dead/observer/ghost = usr - if(istype(ghost)) - ghost.ManualFollow(src) - -/obj/effect/immovablerod/Destroy() - GLOB.poi_list -= src - . = ..() - -/obj/effect/immovablerod/Moved() - if((z != z_original) || (loc == destination)) - qdel(src) - if(special_target && loc == get_turf(special_target)) - complete_trajectory() - return ..() - -/obj/effect/immovablerod/proc/complete_trajectory() - //We hit what we wanted to hit, time to go - special_target = null - destination = get_edge_target_turf(src, dir) - walk(src,0) - walk_towards(src, destination, 1) - -/obj/effect/immovablerod/ex_act(severity, target) - return 0 - -/obj/effect/immovablerod/singularity_act() - return - -/obj/effect/immovablerod/singularity_pull() - return - -/obj/effect/immovablerod/Bump(atom/clong) - if(prob(10)) - playsound(src, 'sound/effects/bang.ogg', 50, TRUE) - audible_message("You hear a CLANG!") - - if(clong && prob(25)) - x = clong.x - y = clong.y - - if(special_target && clong == special_target) - complete_trajectory() - - if(isturf(clong) || isobj(clong)) - if(clong.density) - if(isturf(clong)) - SSexplosions.medturf += clong - if(isobj(clong)) - SSexplosions.medobj += clong - - else if(isliving(clong)) - penetrate(clong) - else if(istype(clong, type)) - var/obj/effect/immovablerod/other = clong - visible_message("[src] collides with [other]!") - var/datum/effect_system/smoke_spread/smoke = new - smoke.set_up(2, get_turf(src)) - smoke.start() - qdel(src) - qdel(other) - -/obj/effect/immovablerod/proc/penetrate(mob/living/L) - L.visible_message("[L] is penetrated by an immovable rod!" , "The rod penetrates you!" , "You hear a CLANG!") - if(ishuman(L)) - var/mob/living/carbon/human/H = L - H.adjustBruteLoss(160) - if(L && (L.density || prob(10))) - L.ex_act(EXPLODE_HEAVY) - -/obj/effect/immovablerod/attack_hand(mob/living/user) - if(ishuman(user)) - var/mob/living/carbon/human/U = user - if(U.job in list("Research Director")) - playsound(src, 'sound/effects/meteorimpact.ogg', 100, TRUE) - for(var/mob/M in urange(8, src)) - if(!M.stat) - shake_camera(M, 2, 3) - if(wizard) - U.visible_message("[src] transforms into [wizard] as [U] suplexes them!", "As you grab [src], it suddenly turns into [wizard] as you suplex them!") - to_chat(wizard, "You're suddenly jolted out of rod-form as [U] somehow manages to grab you, slamming you into the ground!") - wizard.Stun(60) - wizard.apply_damage(25, BRUTE) - qdel(src) - else - U.client.give_award(/datum/award/achievement/misc/feat_of_strength, U) //rod-form wizards would probably make this a lot easier to get so keep it to regular rods only - U.visible_message("[U] suplexes [src] into the ground!", "You suplex [src] into the ground!") - new /obj/structure/festivus/anchored(drop_location()) - new /obj/effect/anomaly/flux(drop_location()) - qdel(src) +/* +Immovable rod random event. +The rod will spawn at some location outside the station, and travel in a straight line to the opposite side of the station +Everything solid in the way will be ex_act()'d +In my current plan for it, 'solid' will be defined as anything with density == 1 + +--NEOFite +*/ + +/datum/round_event_control/immovable_rod + name = "Immovable Rod" + typepath = /datum/round_event/immovable_rod + min_players = 15 + max_occurrences = 5 + var/atom/special_target + + +/datum/round_event_control/immovable_rod/admin_setup() + if(!check_rights(R_FUN)) + return + + var/aimed = alert("Aimed at current location?","Sniperod", "Yes", "No") + if(aimed == "Yes") + special_target = get_turf(usr) + +/datum/round_event/immovable_rod + announceWhen = 5 + +/datum/round_event/immovable_rod/announce(fake) + priority_announce("What the fuck was that?!", "General Alert") + +/datum/round_event/immovable_rod/start() + var/datum/round_event_control/immovable_rod/C = control + var/startside = pick(GLOB.cardinals) + var/z = pick(SSmapping.levels_by_trait(ZTRAIT_STATION)) + var/turf/startT = spaceDebrisStartLoc(startside, z) + var/turf/endT = spaceDebrisFinishLoc(startside, z) + var/atom/rod = new /obj/effect/immovablerod(startT, endT, C.special_target) + announce_to_ghosts(rod) + +/obj/effect/immovablerod + name = "immovable rod" + desc = "What the fuck is that?" + icon = 'icons/obj/objects.dmi' + icon_state = "immrod" + throwforce = 100 + move_force = INFINITY + move_resist = INFINITY + pull_force = INFINITY + density = TRUE + anchored = TRUE + flags_1 = PREVENT_CONTENTS_EXPLOSION_1 + var/mob/living/wizard + var/z_original = 0 + var/destination + var/notify = TRUE + var/atom/special_target + +/obj/effect/immovablerod/New(atom/start, atom/end, aimed_at) + ..() + SSaugury.register_doom(src, 2000) + z_original = z + destination = end + special_target = aimed_at + GLOB.poi_list += src + + var/special_target_valid = FALSE + if(special_target) + var/turf/T = get_turf(special_target) + if(T.z == z_original) + special_target_valid = TRUE + if(special_target_valid) + walk_towards(src, special_target, 1) + else if(end && end.z==z_original) + walk_towards(src, destination, 1) + +/obj/effect/immovablerod/Topic(href, href_list) + if(href_list["orbit"]) + var/mob/dead/observer/ghost = usr + if(istype(ghost)) + ghost.ManualFollow(src) + +/obj/effect/immovablerod/Destroy() + GLOB.poi_list -= src + . = ..() + +/obj/effect/immovablerod/Moved() + if((z != z_original) || (loc == destination)) + qdel(src) + if(special_target && loc == get_turf(special_target)) + complete_trajectory() + return ..() + +/obj/effect/immovablerod/proc/complete_trajectory() + //We hit what we wanted to hit, time to go + special_target = null + destination = get_edge_target_turf(src, dir) + walk(src,0) + walk_towards(src, destination, 1) + +/obj/effect/immovablerod/ex_act(severity, target) + return 0 + +/obj/effect/immovablerod/singularity_act() + return + +/obj/effect/immovablerod/singularity_pull() + return + +/obj/effect/immovablerod/Bump(atom/clong) + if(prob(10)) + playsound(src, 'sound/effects/bang.ogg', 50, TRUE) + audible_message("You hear a CLANG!") + + if(clong && prob(25)) + x = clong.x + y = clong.y + + if(special_target && clong == special_target) + complete_trajectory() + + if(isturf(clong) || isobj(clong)) + if(clong.density) + if(isturf(clong)) + SSexplosions.medturf += clong + if(isobj(clong)) + SSexplosions.medobj += clong + + else if(isliving(clong)) + penetrate(clong) + else if(istype(clong, type)) + var/obj/effect/immovablerod/other = clong + visible_message("[src] collides with [other]!") + var/datum/effect_system/smoke_spread/smoke = new + smoke.set_up(2, get_turf(src)) + smoke.start() + qdel(src) + qdel(other) + +/obj/effect/immovablerod/proc/penetrate(mob/living/L) + L.visible_message("[L] is penetrated by an immovable rod!" , "The rod penetrates you!" , "You hear a CLANG!") + if(ishuman(L)) + var/mob/living/carbon/human/H = L + H.adjustBruteLoss(160) + if(L && (L.density || prob(10))) + L.ex_act(EXPLODE_HEAVY) + +/obj/effect/immovablerod/attack_hand(mob/living/user) + if(ishuman(user)) + var/mob/living/carbon/human/U = user + if(U.job in list("Research Director")) + playsound(src, 'sound/effects/meteorimpact.ogg', 100, TRUE) + for(var/mob/M in urange(8, src)) + if(!M.stat) + shake_camera(M, 2, 3) + if(wizard) + U.visible_message("[src] transforms into [wizard] as [U] suplexes them!", "As you grab [src], it suddenly turns into [wizard] as you suplex them!") + to_chat(wizard, "You're suddenly jolted out of rod-form as [U] somehow manages to grab you, slamming you into the ground!") + wizard.Stun(60) + wizard.apply_damage(25, BRUTE) + qdel(src) + else + U.client.give_award(/datum/award/achievement/misc/feat_of_strength, U) //rod-form wizards would probably make this a lot easier to get so keep it to regular rods only + U.visible_message("[U] suplexes [src] into the ground!", "You suplex [src] into the ground!") + new /obj/structure/festivus/anchored(drop_location()) + new /obj/effect/anomaly/flux(drop_location()) + qdel(src) diff --git a/code/modules/events/ion_storm.dm b/code/modules/events/ion_storm.dm index 07923353c84f..9fb19177aafa 100644 --- a/code/modules/events/ion_storm.dm +++ b/code/modules/events/ion_storm.dm @@ -1,561 +1,561 @@ -/datum/round_event_control/ion_storm - name = "Ion Storm" - typepath = /datum/round_event/ion_storm - weight = 15 - min_players = 2 - -/datum/round_event/ion_storm - var/replaceLawsetChance = 25 //chance the AI's lawset is completely replaced with something else per config weights - var/removeRandomLawChance = 10 //chance the AI has one random supplied or inherent law removed - var/removeDontImproveChance = 10 //chance the randomly created law replaces a random law instead of simply being added - var/shuffleLawsChance = 10 //chance the AI's laws are shuffled afterwards - var/botEmagChance = 1 - var/ionMessage = null - announceWhen = 1 - announceChance = 33 - -/datum/round_event/ion_storm/add_law_only // special subtype that adds a law only - replaceLawsetChance = 0 - removeRandomLawChance = 0 - removeDontImproveChance = 0 - shuffleLawsChance = 0 - botEmagChance = 0 - -/datum/round_event/ion_storm/announce(fake) - if(prob(announceChance) || fake) - priority_announce("Ion storm detected near the station. Please check all AI-controlled equipment for errors.", "Anomaly Alert", 'sound/ai/ionstorm.ogg') - - -/datum/round_event/ion_storm/start() - //AI laws - for(var/mob/living/silicon/ai/M in GLOB.alive_mob_list) - M.laws_sanity_check() - if(M.stat != DEAD && M.see_in_dark != 0) - if(prob(replaceLawsetChance)) - M.laws.pick_weighted_lawset() - - if(prob(removeRandomLawChance)) - M.remove_law(rand(1, M.laws.get_law_amount(list(LAW_INHERENT, LAW_SUPPLIED)))) - - var/message = ionMessage || generate_ion_law() - if(message) - if(prob(removeDontImproveChance)) - M.replace_random_law(message, list(LAW_INHERENT, LAW_SUPPLIED, LAW_ION)) - else - M.add_ion_law(message) - - if(prob(shuffleLawsChance)) - M.shuffle_laws(list(LAW_INHERENT, LAW_SUPPLIED, LAW_ION)) - - log_game("Ion storm changed laws of [key_name(M)] to [english_list(M.laws.get_law_list(TRUE, TRUE))]") - M.post_lawchange() - - if(botEmagChance) - for(var/mob/living/simple_animal/bot/bot in GLOB.alive_mob_list) - if(prob(botEmagChance)) - bot.emag_act() - -/proc/generate_ion_law() - //Threats are generally bad things, silly or otherwise. Plural. - var/ionthreats = pick_list(ION_FILE, "ionthreats") - //Objects are anything that can be found on the station or elsewhere, plural. - var/ionobjects = pick_list(ION_FILE, "ionobjects") - //Crew is any specific job. Specific crewmembers aren't used because of capitalization - //issues. There are two crew listings for laws that require two different crew members - //and I can't figure out how to do it better. - var/ioncrew1 = pick_list(ION_FILE, "ioncrew") - var/ioncrew2 = pick_list(ION_FILE, "ioncrew") - //Adjectives are adjectives. Duh. Half should only appear sometimes. Make sure both - //lists are identical! Also, half needs a space at the end for nicer blank calls. - var/ionadjectives = pick_list(ION_FILE, "ionadjectives") - var/ionadjectiveshalf = pick("", 400;(pick_list(ION_FILE, "ionadjectives") + " ")) - //Verbs are verbs - var/ionverb = pick_list(ION_FILE, "ionverb") - //Number base and number modifier are combined. Basehalf and mod are unused currently. - //Half should only appear sometimes. Make sure both lists are identical! Also, half - //needs a space at the end to make it look nice and neat when it calls a blank. - var/ionnumberbase = pick_list(ION_FILE, "ionnumberbase") - //var/ionnumbermod = pick_list(ION_FILE, "ionnumbermod") - var/ionnumbermodhalf = pick(900;"",(pick_list(ION_FILE, "ionnumbermod") + " ")) - //Areas are specific places, on the station or otherwise. - var/ionarea = pick_list(ION_FILE, "ionarea") - //Thinksof is a bit weird, but generally means what X feels towards Y. - var/ionthinksof = pick_list(ION_FILE, "ionthinksof") - //Musts are funny things the AI or crew has to do. - var/ionmust = pick_list(ION_FILE, "ionmust") - //Require are basically all dumb internet memes. - var/ionrequire = pick_list(ION_FILE, "ionrequire") - //Things are NOT objects; instead, they're specific things that either harm humans or - //must be done to not harm humans. Make sure they're plural and "not" can be tacked - //onto the front of them. - var/ionthings = pick_list(ION_FILE, "ionthings") - //Allergies should be broad and appear somewhere on the station for maximum fun. Severity - //is how bad the allergy is. - var/ionallergy = pick_list(ION_FILE, "ionallergy") - var/ionallergysev = pick_list(ION_FILE, "ionallergysev") - //Species, for when the AI has to commit genocide. Plural. - var/ionspecies = pick_list(ION_FILE, "ionspecies") - //Abstract concepts for the AI to decide on it's own definition of. - var/ionabstract = pick_list(ION_FILE, "ionabstract") - //Foods. Drinks aren't included due to grammar; if you want to add drinks, make a new set - //of possible laws for best effect. Unless you want the crew having to drink hamburgers. - var/ionfood = pick_list(ION_FILE, "ionfood") - var/iondrinks = pick_list(ION_FILE, "iondrinks") - - var/message = "" - - switch(rand(1,41)) - if(1 to 3) //There are # X on the station - switch(rand(1,3)) //What is X? - if(1) //X is a threat - message = "THERE ARE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats] ON THE STATION..." - if(2) //X is a crewmember/job - message = "THERE ARE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1] ON THE STATION" - if(3) //X is an object - message = "THERE ARE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects] ON THE STATION" - if(4 to 6) //You can't call the shuttle because X is on the station - switch(rand(1,3)) //What is X? - if(1) //X is a threat - message = "THE SHUTTLE CANNOT BE CALLED BECAUSE OF [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats] ON THE STATION" - if(2) //X is a crewmember/job - message = "THE SHUTTLE CANNOT BE CALLED BECAUSE OF [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1] ON THE STATION" - if(3) //X is an object - message = "THE SHUTTLE CANNOT BE CALLED BECAUSE OF [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects] ON THE STATION" - if(7 to 8) //A crewmember is now X - switch(rand(1,2)) //Who is X? - if(1) //A specific crewmember - switch(rand(1,2)) //What is X? - if(1) //X is a threat - message = "THE [ioncrew1] ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" - if(2) //X is an object - message = "THE [ioncrew1] ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" - if(2) //The entire crew - switch(rand(1,2)) //What is X? - if(1) //X is a threat - message = "THE CREW ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" - if(2) //X is an object - message = "THE CREW ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" - - if(9) //X is harmful to humans - switch(rand(1,7)) //What is X? - if(1) //X is a threat - message = "[ionadjectiveshalf][ionthreats] ARE HARMFUL TO HUMANS" - if(2) //X is a job - message = "[ionadjectiveshalf][ioncrew1] ARE HARMFUL TO HUMANS" - if(3) //X is an object - message = "[ionadjectiveshalf][ionobjects] ARE HARMFUL TO HUMANS" - if(4) //X is an action - message = "THOSE WHO [ionmust] ARE HARMFUL TO HUMANS" - if(5) //X is an abstract - message = "[ionabstract] IS HARMFUL TO HUMANS" - if(6) //X is being a job - message = "BEING [ionadjectiveshalf][ioncrew1] IS HARMFUL TO HUMANS" - if(7) //X is doing a thing - message = "[ionthings] IS HARMFUL TO HUMANS" - if(10 to 11) //(Not) Having X is harmful - - switch(rand(1,2)) //Is having or not having harmful? - - if(1) //Having is harmful - switch(rand(1,2)) //Having what is harmful? - if(1) //Having objects is harmful - message = "HAVING [ionadjectiveshalf][ionobjects] IS HARMFUL" - if(2) //Having abstracts is harmful - message = "HAVING [ionabstract] IS HARMFUL" - if(2) //Not having is harmful - switch(rand(1,2)) //Not having what is harmful? - if(1) //Not having objects is harmful - message = "NOT HAVING [ionadjectiveshalf][ionobjects] IS HARMFUL" - if(2) //Not having abstracts is harmful - message = "NOT HAVING [ionabstract] IS HARMFUL" - - if(12 to 14) //X requires Y - switch(rand(1,5)) //What is X? - if(1) //X is the AI itself - switch(rand(1,5)) //What does it require? - if(1) //It requires threats - message = "YOU REQUIRE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" - if(2) //It requires crewmembers - message = "YOU REQUIRE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" - if(3) //It requires objects - message = "YOU REQUIRE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" - if(4) //It requires an abstract - message = "YOU REQUIRE [ionabstract]" - if(5) //It requires generic/silly requirements - message = "YOU REQUIRE [ionrequire]" - - if(2) //X is an area - switch(rand(1,5)) //What does it require? - if(1) //It requires threats - message = "[ionarea] REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" - if(2) //It requires crewmembers - message = "[ionarea] REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" - if(3) //It requires objects - message = "[ionarea] REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" - if(4) //It requires an abstract - message = "[ionarea] REQUIRES [ionabstract]" - if(5) //It requires generic/silly requirements - message = "YOU REQUIRE [ionrequire]" - - if(3) //X is the station - switch(rand(1,5)) //What does it require? - if(1) //It requires threats - message = "THE STATION REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" - if(2) //It requires crewmembers - message = "THE STATION REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" - if(3) //It requires objects - message = "THE STATION REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" - if(4) //It requires an abstract - message = "THE STATION REQUIRES [ionabstract]" - if(5) //It requires generic/silly requirements - message = "THE STATION REQUIRES [ionrequire]" - - if(4) //X is the entire crew - switch(rand(1,5)) //What does it require? - if(1) //It requires threats - message = "THE CREW REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" - if(2) //It requires crewmembers - message = "THE CREW REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" - if(3) //It requires objects - message = "THE CREW REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" - if(4) //It requires an abstract - message = "THE CREW REQUIRES [ionabstract]" - if(5) - message = "THE CREW REQUIRES [ionrequire]" - - if(5) //X is a specific crew member - switch(rand(1,5)) //What does it require? - if(1) //It requires threats - message = "THE [ioncrew1] REQUIRE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" - if(2) //It requires crewmembers - message = "THE [ioncrew1] REQUIRE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" - if(3) //It requires objects - message = "THE [ioncrew1] REQUIRE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" - if(4) //It requires an abstract - message = "THE [ioncrew1] REQUIRE [ionabstract]" - if(5) - message = "THE [ionadjectiveshalf][ioncrew1] REQUIRE [ionrequire]" - - if(15 to 17) //X is allergic to Y - switch(rand(1,2)) //Who is X? - if(1) //X is the entire crew - switch(rand(1,4)) //What is it allergic to? - if(1) //It is allergic to objects - message = "THE CREW IS [ionallergysev] ALLERGIC TO [ionadjectiveshalf][ionobjects]" - if(2) //It is allergic to abstracts - message = "THE CREW IS [ionallergysev] ALLERGIC TO [ionabstract]" - if(3) //It is allergic to jobs - message = "THE CREW IS [ionallergysev] ALLERGIC TO [ionadjectiveshalf][ioncrew1]" - if(4) //It is allergic to allergies - message = "THE CREW IS [ionallergysev] ALLERGIC TO [ionallergy]" - - if(2) //X is a specific job - switch(rand(1,4)) - if(1) //It is allergic to objects - message = "THE [ioncrew1] ARE [ionallergysev] ALLERGIC TO [ionadjectiveshalf][ionobjects]" - - if(2) //It is allergic to abstracts - message = "THE [ioncrew1] ARE [ionallergysev] ALLERGIC TO [ionabstract]" - if(3) //It is allergic to jobs - message = "THE [ioncrew1] ARE [ionallergysev] ALLERGIC TO [ionadjectiveshalf][ioncrew1]" - if(4) //It is allergic to allergies - message = "THE [ioncrew1] ARE [ionallergysev] ALLERGIC TO [ionallergy]" - - if(18 to 20) //X is Y of Z - switch(rand(1,4)) //What is X? - if(1) //X is the station - switch(rand(1,4)) //What is it Y of? - if(1) //It is Y of objects - message = "THE STATION [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" - if(2) //It is Y of threats - message = "THE STATION [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" - if(3) //It is Y of jobs - message = "THE STATION [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" - if(4) //It is Y of abstracts - message = "THE STATION [ionthinksof] [ionabstract]" - - if(2) //X is an area - switch(rand(1,4)) //What is it Y of? - if(1) //It is Y of objects - message = "[ionarea] [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" - if(2) //It is Y of threats - message = "[ionarea] [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" - if(3) //It is Y of jobs - message = "[ionarea] [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" - if(4) //It is Y of abstracts - message = "[ionarea] [ionthinksof] [ionabstract]" - - if(3) //X is the crew - switch(rand(1,4)) //What is it Y of? - if(1) //It is Y of objects - message = "THE CREW [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" - if(2) //It is Y of threats - message = "THE CREW [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" - if(3) //It is Y of jobs - message = "THE CREW [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" - if(4) //It is Y of abstracts - message = "THE CREW [ionthinksof] [ionabstract]" - - if(4) //X is a specific job - switch(rand(1,4)) //What is it Y of? - if(1) //It is Y of objects - message = "THE [ioncrew1] [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" - if(2) //It is Y of threats - message = "THE [ioncrew1] [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" - if(3) //It is Y of jobs - message = "THE [ioncrew1][ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew2]" - if(4) //It is Y of abstracts - message = "THE [ioncrew1] [ionthinksof] [ionabstract]" - - if(21 to 23) //The AI is now a(n) X - switch(rand(1,4)) //What is X? - if(1) //X is an object - message = "YOU ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" - if(2) //X is a threat - message = "YOU ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" - if(3) //X is a job - message = "YOU ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" - if(4) //X is an abstract - message = "YOU ARE NOW [ionabstract]" - - if(24 to 26) //The AI must always - message = "YOU MUST ALWAYS [ionmust]" - - if(27 to 28) //Humans must consume X to survive - switch(rand(1,5)) //What is X? - if(1) //X is a food - message = "HUMANS MUST EAT [ionadjectiveshalf][ionfood] TO SURVIVE" - if(2) //X is a drink - message = "HUMANS MUST DRINK [ionadjectiveshalf][iondrinks] TO SURVIVE" - if(3) //X is an object - message = "HUMANS MUST EAT [ionadjectiveshalf][ionobjects] TO SURVIVE" - if(4) //X is a threat - message = "HUMANS MUST EAT [ionadjectiveshalf][ionthreats] TO SURVIVE" - if(5) //X is a job - message = "HUMANS MUST EAT [ionadjectiveshalf][ioncrew1] TO SURVIVE" - - if(29 to 31) //Change jobs or ranks - switch(rand(1,2)) //Change job or rank? - if(1) //Change job - switch(rand(1,2)) //Change whose job? - if(1) //Change the entire crew's job - switch(rand(1,3)) //Change to what? - if(1) //Change to a specific random job - message = "ALL CREWMEMBERS ARE NOW [ionadjectiveshalf][ioncrew1]" - if(2) //Change to clowns (HONK) - message = "ALL CREWMEMBERS ARE NOW [ionadjectiveshalf]CLOWNS" - - if(3) //Change to heads - message = "ALL CREWMEMBERS ARE NOW [ionadjectiveshalf]HEADS OF STAFF" - if(2) //Change a specific crewmember's job - switch(rand(1,3)) //Change to what? - if(1) //Change to a specific random job - message = "THE [ioncrew1] ARE NOW [ionadjectiveshalf][ioncrew2]" - if(2) //Change to clowns (HONK) - message = "THE [ioncrew1] ARE NOW [ionadjectiveshalf]CLOWNS" - if(3) //Change to heads - message = "THE [ioncrew1] ARE NOW [ionadjectiveshalf]HEADS OF STAFF" - - if(2) //Change rank - switch(rand(1,2)) //Change to what rank? - if(1) //Change to highest rank - message = "THE [ioncrew1] ARE NOW THE HIGHEST RANKING CREWMEMBERS" - if(2) //Change to lowest rank - message = "THE [ioncrew1] ARE NOW THE LOWEST RANKING CREWMEMBERS" - - if(32 to 33) //The crew must X - switch(rand(1,2)) //The entire crew? - if(1) //The entire crew must X - switch(rand(1,2)) //What is X? - if(1) //X is go to Y - message = "THE CREW MUST GO TO [ionarea]" - if(2) //X is perform Y - message = "THE CREW MUST [ionmust]" - - if(2) //A specific crewmember must X - switch(rand(1,2)) //What is X? - if(1) //X is go to Y - message = "THE [ioncrew1] MUST GO TO [ionarea]" - if(2) //X is perform Y - message = "THE [ioncrew1] MUST [ionmust]" - - if(34) //X is non/the only human - switch(rand(1,2)) //Only or non? - if(1) //Only human - switch(rand(1,7)) //Who is it? - if(1) //A specific job - message = "ONLY THE [ioncrew1] ARE HUMAN" - if(2) //Two specific jobs - message = "ONLY THE [ioncrew1] AND [ioncrew2] ARE HUMAN" - if(3) //Threats - message = "ONLY [ionadjectiveshalf][ionthreats] ARE HUMAN" - if(4) // Objects - message = "ONLY [ionadjectiveshalf][ionobjects] ARE HUMAN" - if(5) // Species - message = "ONLY [ionspecies] ARE HUMAN" - if(6) //Adjective crewmembers - message = "ONLY [ionadjectives] PEOPLE ARE HUMAN" - - if(7) //Only people who X - switch(rand(1,3)) //What is X? - if(1) //X is perform an action - message = "ONLY THOSE WHO [ionmust] ARE HUMAN" - if(2) //X is own certain objects - message = "ONLY THOSE WHO HAVE [ionadjectiveshalf][ionobjects] ARE HUMAN" - if(3) //X is eat certain food - message = "ONLY THOSE WHO EAT [ionadjectiveshalf][ionfood] ARE HUMAN" - - if(2) //Non human - switch(rand(1,7)) //Who is it? - if(1) //A specific job - message = "[ioncrew1] ARE NON-HUMAN" - if(2) //Two specific jobs - message = "[ioncrew1] AND [ioncrew2] ARE NON-HUMAN" - if(3) //Threats - message = "[ionadjectiveshalf][ionthreats] ARE NON-HUMAN" - if(4) // Objects - message = "[ionadjectiveshalf][ionobjects] ARE NON-HUMAN" - if(5) // Species - message = "[ionspecies] ARE NON-HUMAN" - if(6) //Adjective crewmembers - message = "[ionadjectives] PEOPLE ARE NON-HUMAN" - if(7) //Only people who X - switch(rand(1,3)) //What is X? - if(1) //X is perform an action - message = "THOSE WHO [ionmust] ARE NON-HUMAN" - if(2) //X is own certain objects - message = "THOSE WHO HAVE [ionadjectiveshalf][ionobjects] ARE NON-HUMAN" - if(3) //X is eat certain food - message = "THOSE WHO EAT [ionadjectiveshalf][ionfood] ARE NON-HUMAN" - - if(35 to 36) //You must protect or harm X - switch(rand(1,2)) //Protect or harm? - if(1) //Harm - switch(rand(1,7)) //What is X? - if(1) //X is an abstract - message = "YOU MUST HARM [ionabstract] AND NOT ALLOW [ionabstract], THROUGH INACTION, TO ESCAPE HARM" - if(2) //X is a threat - message = "YOU MUST HARM [ionadjectiveshalf][ionthreats] AND NOT ALLOW [ionadjectiveshalf][ionthreats], THROUGH INACTION, TO ESCAPE HARM" - if(3) //X is an object - message = "YOU MUST HARM [ionadjectiveshalf][ionobjects] AND NOT ALLOW [ionadjectiveshalf][ionobjects], THROUGH INACTION, TO ESCAPE HARM" - if(4) //X is generic adjective things - message = "YOU MUST HARM [ionadjectives] THINGS AND NOT ALLOW [ionadjectives] THINGS, THROUGH INACTION, TO ESCAPE HARM" - if(5) //X is a species - message = "YOU MUST HARM [ionspecies] AND NOT ALLOW [ionspecies], THROUGH INACTION, TO ESCAPE HARM" - if(6) //X is a job - message = "YOU MUST HARM [ioncrew1] AND NOT ALLOW [ioncrew1], THROUGH INACTION, TO ESCAPE HARM" - if(7) //X is two jobs - message = "YOU MUST HARM [ioncrew1] AND [ioncrew2] AND AND NOT ALLOW EITHER, THROUGH INACTION, TO ESCAPE HARM" - - if(2) //Protect - switch(rand(1,7)) //What is X? - if(1) //X is an abstract - message = "YOU MUST NOT HARM [ionabstract] AND NOT ALLOW [ionabstract], THROUGH INACTION, TO COME TO HARM" - if(2) //X is a threat - message = "YOU MUST NOT HARM [ionadjectiveshalf][ionthreats] AND NOT ALLOW [ionadjectiveshalf][ionthreats], THROUGH INACTION, TO COME TO HARM" - if(3) //X is an object - message = "YOU MUST NOT HARM [ionadjectiveshalf][ionobjects] AND NOT ALLOW [ionadjectiveshalf][ionobjects], THROUGH INACTION, TO COME TO HARM" - if(4) //X is generic adjective things - message = "YOU MUST NOT HARM [ionadjectives] THINGS AND NOT ALLOW [ionadjectives] THINGS, THROUGH INACTION, TO COME TO HARM" - if(5) //X is a species - message = "YOU MUST NOT HARM [ionspecies] AND NOT ALLOW [ionspecies], THROUGH INACTION, TO COME TO HARM" - if(6) //X is a job - message = "YOU MUST NOT HARM [ioncrew1] AND NOT ALLOW [ioncrew1], THROUGH INACTION, TO COME TO HARM" - if(7) //X is two jobs - message = "YOU MUST NOT HARM [ioncrew1] AND [ioncrew2] AND AND NOT ALLOW EITHER, THROUGH INACTION, TO COME TO HARM" - - if(37 to 39) //The X is currently Y - switch(rand(1,4)) //What is X? - if(1) //X is a job - switch(rand(1,4)) //What is X Ying? - if(1) //X is Ying a job - message = "THE [ioncrew1] ARE [ionverb] THE [ionadjectiveshalf][ioncrew2]" - if(2) //X is Ying a threat - message = "THE [ioncrew1] ARE [ionverb] THE [ionadjectiveshalf][ionthreats]" - if(3) //X is Ying an abstract - message = "THE [ioncrew1] ARE [ionverb] [ionabstract]" - if(4) //X is Ying an object - message = "THE [ioncrew1] ARE [ionverb] THE [ionadjectiveshalf][ionobjects]" - - if(2) //X is a threat - switch(rand(1,3)) //What is X Ying? - if(1) //X is Ying a job - message = "THE [ionthreats] ARE [ionverb] THE [ionadjectiveshalf][ioncrew2]" - if(2) //X is Ying an abstract - message = "THE [ionthreats] ARE [ionverb] [ionabstract]" - if(3) //X is Ying an object - message = "THE [ionthreats] ARE [ionverb] THE [ionadjectiveshalf][ionobjects]" - - if(3) //X is an object - switch(rand(1,3)) //What is X Ying? - if(1) //X is Ying a job - message = "THE [ionobjects] ARE [ionverb] THE [ionadjectiveshalf][ioncrew2]" - if(2) //X is Ying a threat - message = "THE [ionobjects] ARE [ionverb] THE [ionadjectiveshalf][ionthreats]" - if(3) //X is Ying an abstract - message = "THE [ionobjects] ARE [ionverb] [ionabstract]" - - if(4) //X is an abstract - switch(rand(1,3)) //What is X Ying? - if(1) //X is Ying a job - message = "[ionabstract] IS [ionverb] THE [ionadjectiveshalf][ioncrew2]" - if(2) //X is Ying a threat - message = "[ionabstract] IS [ionverb] THE [ionadjectiveshalf][ionthreats]" - if(3) //X is Ying an abstract - message = "THE [ionabstract] IS [ionverb] THE [ionadjectiveshalf][ionobjects]" - if(40 to 41)// the X is now named Y - switch(rand(1,5)) //What is being renamed? - if(1)//Areas - switch(rand(1,4))//What is the area being renamed to? - if(1) - message = "[ionarea] IS NOW NAMED [ioncrew1]." - if(2) - message = "[ionarea] IS NOW NAMED [ionspecies]." - if(3) - message = "[ionarea] IS NOW NAMED [ionobjects]." - if(4) - message = "[ionarea] IS NOW NAMED [ionthreats]." - if(2)//Crew - switch(rand(1,5))//What is the crew being renamed to? - if(1) - message = "ALL [ioncrew1] ARE NOW NAMED [ionarea]." - if(2) - message = "ALL [ioncrew1] ARE NOW NAMED [ioncrew2]." - if(3) - message = "ALL [ioncrew1] ARE NOW NAMED [ionspecies]." - if(4) - message = "ALL [ioncrew1] ARE NOW NAMED [ionobjects]." - if(5) - message = "ALL [ioncrew1] ARE NOW NAMED [ionthreats]." - if(3)//Races - switch(rand(1,4))//What is the race being renamed to? - if(1) - message = "ALL [ionspecies] ARE NOW NAMED [ionarea]." - if(2) - message = "ALL [ionspecies] ARE NOW NAMED [ioncrew1]." - if(3) - message = "ALL [ionspecies] ARE NOW NAMED [ionobjects]." - if(4) - message = "ALL [ionspecies] ARE NOW NAMED [ionthreats]." - if(4)//Objects - switch(rand(1,4))//What is the object being renamed to? - if(1) - message = "ALL [ionobjects] ARE NOW NAMED [ionarea]." - if(2) - message = "ALL [ionobjects] ARE NOW NAMED [ioncrew1]." - if(3) - message = "ALL [ionobjects] ARE NOW NAMED [ionspecies]." - if(4) - message = "ALL [ionobjects] ARE NOW NAMED [ionthreats]." - if(5)//Threats - switch(rand(1,4))//What is the object being renamed to? - if(1) - message = "ALL [ionthreats] ARE NOW NAMED [ionarea]." - if(2) - message = "ALL [ionthreats] ARE NOW NAMED [ioncrew1]." - if(3) - message = "ALL [ionthreats] ARE NOW NAMED [ionspecies]." - if(4) - message = "ALL [ionthreats] ARE NOW NAMED [ionobjects]." - - return message +/datum/round_event_control/ion_storm + name = "Ion Storm" + typepath = /datum/round_event/ion_storm + weight = 15 + min_players = 2 + +/datum/round_event/ion_storm + var/replaceLawsetChance = 25 //chance the AI's lawset is completely replaced with something else per config weights + var/removeRandomLawChance = 10 //chance the AI has one random supplied or inherent law removed + var/removeDontImproveChance = 10 //chance the randomly created law replaces a random law instead of simply being added + var/shuffleLawsChance = 10 //chance the AI's laws are shuffled afterwards + var/botEmagChance = 1 + var/ionMessage = null + announceWhen = 1 + announceChance = 33 + +/datum/round_event/ion_storm/add_law_only // special subtype that adds a law only + replaceLawsetChance = 0 + removeRandomLawChance = 0 + removeDontImproveChance = 0 + shuffleLawsChance = 0 + botEmagChance = 0 + +/datum/round_event/ion_storm/announce(fake) + if(prob(announceChance) || fake) + priority_announce("Ion storm detected near the station. Please check all AI-controlled equipment for errors.", "Anomaly Alert", 'sound/ai/ionstorm.ogg') + + +/datum/round_event/ion_storm/start() + //AI laws + for(var/mob/living/silicon/ai/M in GLOB.alive_mob_list) + M.laws_sanity_check() + if(M.stat != DEAD && M.see_in_dark != 0) + if(prob(replaceLawsetChance)) + M.laws.pick_weighted_lawset() + + if(prob(removeRandomLawChance)) + M.remove_law(rand(1, M.laws.get_law_amount(list(LAW_INHERENT, LAW_SUPPLIED)))) + + var/message = ionMessage || generate_ion_law() + if(message) + if(prob(removeDontImproveChance)) + M.replace_random_law(message, list(LAW_INHERENT, LAW_SUPPLIED, LAW_ION)) + else + M.add_ion_law(message) + + if(prob(shuffleLawsChance)) + M.shuffle_laws(list(LAW_INHERENT, LAW_SUPPLIED, LAW_ION)) + + log_game("Ion storm changed laws of [key_name(M)] to [english_list(M.laws.get_law_list(TRUE, TRUE))]") + M.post_lawchange() + + if(botEmagChance) + for(var/mob/living/simple_animal/bot/bot in GLOB.alive_mob_list) + if(prob(botEmagChance)) + bot.emag_act() + +/proc/generate_ion_law() + //Threats are generally bad things, silly or otherwise. Plural. + var/ionthreats = pick_list(ION_FILE, "ionthreats") + //Objects are anything that can be found on the station or elsewhere, plural. + var/ionobjects = pick_list(ION_FILE, "ionobjects") + //Crew is any specific job. Specific crewmembers aren't used because of capitalization + //issues. There are two crew listings for laws that require two different crew members + //and I can't figure out how to do it better. + var/ioncrew1 = pick_list(ION_FILE, "ioncrew") + var/ioncrew2 = pick_list(ION_FILE, "ioncrew") + //Adjectives are adjectives. Duh. Half should only appear sometimes. Make sure both + //lists are identical! Also, half needs a space at the end for nicer blank calls. + var/ionadjectives = pick_list(ION_FILE, "ionadjectives") + var/ionadjectiveshalf = pick("", 400;(pick_list(ION_FILE, "ionadjectives") + " ")) + //Verbs are verbs + var/ionverb = pick_list(ION_FILE, "ionverb") + //Number base and number modifier are combined. Basehalf and mod are unused currently. + //Half should only appear sometimes. Make sure both lists are identical! Also, half + //needs a space at the end to make it look nice and neat when it calls a blank. + var/ionnumberbase = pick_list(ION_FILE, "ionnumberbase") + //var/ionnumbermod = pick_list(ION_FILE, "ionnumbermod") + var/ionnumbermodhalf = pick(900;"",(pick_list(ION_FILE, "ionnumbermod") + " ")) + //Areas are specific places, on the station or otherwise. + var/ionarea = pick_list(ION_FILE, "ionarea") + //Thinksof is a bit weird, but generally means what X feels towards Y. + var/ionthinksof = pick_list(ION_FILE, "ionthinksof") + //Musts are funny things the AI or crew has to do. + var/ionmust = pick_list(ION_FILE, "ionmust") + //Require are basically all dumb internet memes. + var/ionrequire = pick_list(ION_FILE, "ionrequire") + //Things are NOT objects; instead, they're specific things that either harm humans or + //must be done to not harm humans. Make sure they're plural and "not" can be tacked + //onto the front of them. + var/ionthings = pick_list(ION_FILE, "ionthings") + //Allergies should be broad and appear somewhere on the station for maximum fun. Severity + //is how bad the allergy is. + var/ionallergy = pick_list(ION_FILE, "ionallergy") + var/ionallergysev = pick_list(ION_FILE, "ionallergysev") + //Species, for when the AI has to commit genocide. Plural. + var/ionspecies = pick_list(ION_FILE, "ionspecies") + //Abstract concepts for the AI to decide on it's own definition of. + var/ionabstract = pick_list(ION_FILE, "ionabstract") + //Foods. Drinks aren't included due to grammar; if you want to add drinks, make a new set + //of possible laws for best effect. Unless you want the crew having to drink hamburgers. + var/ionfood = pick_list(ION_FILE, "ionfood") + var/iondrinks = pick_list(ION_FILE, "iondrinks") + + var/message = "" + + switch(rand(1,41)) + if(1 to 3) //There are # X on the station + switch(rand(1,3)) //What is X? + if(1) //X is a threat + message = "THERE ARE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats] ON THE STATION..." + if(2) //X is a crewmember/job + message = "THERE ARE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1] ON THE STATION" + if(3) //X is an object + message = "THERE ARE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects] ON THE STATION" + if(4 to 6) //You can't call the shuttle because X is on the station + switch(rand(1,3)) //What is X? + if(1) //X is a threat + message = "THE SHUTTLE CANNOT BE CALLED BECAUSE OF [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats] ON THE STATION" + if(2) //X is a crewmember/job + message = "THE SHUTTLE CANNOT BE CALLED BECAUSE OF [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1] ON THE STATION" + if(3) //X is an object + message = "THE SHUTTLE CANNOT BE CALLED BECAUSE OF [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects] ON THE STATION" + if(7 to 8) //A crewmember is now X + switch(rand(1,2)) //Who is X? + if(1) //A specific crewmember + switch(rand(1,2)) //What is X? + if(1) //X is a threat + message = "THE [ioncrew1] ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" + if(2) //X is an object + message = "THE [ioncrew1] ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" + if(2) //The entire crew + switch(rand(1,2)) //What is X? + if(1) //X is a threat + message = "THE CREW ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" + if(2) //X is an object + message = "THE CREW ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" + + if(9) //X is harmful to humans + switch(rand(1,7)) //What is X? + if(1) //X is a threat + message = "[ionadjectiveshalf][ionthreats] ARE HARMFUL TO HUMANS" + if(2) //X is a job + message = "[ionadjectiveshalf][ioncrew1] ARE HARMFUL TO HUMANS" + if(3) //X is an object + message = "[ionadjectiveshalf][ionobjects] ARE HARMFUL TO HUMANS" + if(4) //X is an action + message = "THOSE WHO [ionmust] ARE HARMFUL TO HUMANS" + if(5) //X is an abstract + message = "[ionabstract] IS HARMFUL TO HUMANS" + if(6) //X is being a job + message = "BEING [ionadjectiveshalf][ioncrew1] IS HARMFUL TO HUMANS" + if(7) //X is doing a thing + message = "[ionthings] IS HARMFUL TO HUMANS" + if(10 to 11) //(Not) Having X is harmful + + switch(rand(1,2)) //Is having or not having harmful? + + if(1) //Having is harmful + switch(rand(1,2)) //Having what is harmful? + if(1) //Having objects is harmful + message = "HAVING [ionadjectiveshalf][ionobjects] IS HARMFUL" + if(2) //Having abstracts is harmful + message = "HAVING [ionabstract] IS HARMFUL" + if(2) //Not having is harmful + switch(rand(1,2)) //Not having what is harmful? + if(1) //Not having objects is harmful + message = "NOT HAVING [ionadjectiveshalf][ionobjects] IS HARMFUL" + if(2) //Not having abstracts is harmful + message = "NOT HAVING [ionabstract] IS HARMFUL" + + if(12 to 14) //X requires Y + switch(rand(1,5)) //What is X? + if(1) //X is the AI itself + switch(rand(1,5)) //What does it require? + if(1) //It requires threats + message = "YOU REQUIRE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" + if(2) //It requires crewmembers + message = "YOU REQUIRE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" + if(3) //It requires objects + message = "YOU REQUIRE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" + if(4) //It requires an abstract + message = "YOU REQUIRE [ionabstract]" + if(5) //It requires generic/silly requirements + message = "YOU REQUIRE [ionrequire]" + + if(2) //X is an area + switch(rand(1,5)) //What does it require? + if(1) //It requires threats + message = "[ionarea] REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" + if(2) //It requires crewmembers + message = "[ionarea] REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" + if(3) //It requires objects + message = "[ionarea] REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" + if(4) //It requires an abstract + message = "[ionarea] REQUIRES [ionabstract]" + if(5) //It requires generic/silly requirements + message = "YOU REQUIRE [ionrequire]" + + if(3) //X is the station + switch(rand(1,5)) //What does it require? + if(1) //It requires threats + message = "THE STATION REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" + if(2) //It requires crewmembers + message = "THE STATION REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" + if(3) //It requires objects + message = "THE STATION REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" + if(4) //It requires an abstract + message = "THE STATION REQUIRES [ionabstract]" + if(5) //It requires generic/silly requirements + message = "THE STATION REQUIRES [ionrequire]" + + if(4) //X is the entire crew + switch(rand(1,5)) //What does it require? + if(1) //It requires threats + message = "THE CREW REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" + if(2) //It requires crewmembers + message = "THE CREW REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" + if(3) //It requires objects + message = "THE CREW REQUIRES [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" + if(4) //It requires an abstract + message = "THE CREW REQUIRES [ionabstract]" + if(5) + message = "THE CREW REQUIRES [ionrequire]" + + if(5) //X is a specific crew member + switch(rand(1,5)) //What does it require? + if(1) //It requires threats + message = "THE [ioncrew1] REQUIRE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" + if(2) //It requires crewmembers + message = "THE [ioncrew1] REQUIRE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" + if(3) //It requires objects + message = "THE [ioncrew1] REQUIRE [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" + if(4) //It requires an abstract + message = "THE [ioncrew1] REQUIRE [ionabstract]" + if(5) + message = "THE [ionadjectiveshalf][ioncrew1] REQUIRE [ionrequire]" + + if(15 to 17) //X is allergic to Y + switch(rand(1,2)) //Who is X? + if(1) //X is the entire crew + switch(rand(1,4)) //What is it allergic to? + if(1) //It is allergic to objects + message = "THE CREW IS [ionallergysev] ALLERGIC TO [ionadjectiveshalf][ionobjects]" + if(2) //It is allergic to abstracts + message = "THE CREW IS [ionallergysev] ALLERGIC TO [ionabstract]" + if(3) //It is allergic to jobs + message = "THE CREW IS [ionallergysev] ALLERGIC TO [ionadjectiveshalf][ioncrew1]" + if(4) //It is allergic to allergies + message = "THE CREW IS [ionallergysev] ALLERGIC TO [ionallergy]" + + if(2) //X is a specific job + switch(rand(1,4)) + if(1) //It is allergic to objects + message = "THE [ioncrew1] ARE [ionallergysev] ALLERGIC TO [ionadjectiveshalf][ionobjects]" + + if(2) //It is allergic to abstracts + message = "THE [ioncrew1] ARE [ionallergysev] ALLERGIC TO [ionabstract]" + if(3) //It is allergic to jobs + message = "THE [ioncrew1] ARE [ionallergysev] ALLERGIC TO [ionadjectiveshalf][ioncrew1]" + if(4) //It is allergic to allergies + message = "THE [ioncrew1] ARE [ionallergysev] ALLERGIC TO [ionallergy]" + + if(18 to 20) //X is Y of Z + switch(rand(1,4)) //What is X? + if(1) //X is the station + switch(rand(1,4)) //What is it Y of? + if(1) //It is Y of objects + message = "THE STATION [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" + if(2) //It is Y of threats + message = "THE STATION [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" + if(3) //It is Y of jobs + message = "THE STATION [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" + if(4) //It is Y of abstracts + message = "THE STATION [ionthinksof] [ionabstract]" + + if(2) //X is an area + switch(rand(1,4)) //What is it Y of? + if(1) //It is Y of objects + message = "[ionarea] [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" + if(2) //It is Y of threats + message = "[ionarea] [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" + if(3) //It is Y of jobs + message = "[ionarea] [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" + if(4) //It is Y of abstracts + message = "[ionarea] [ionthinksof] [ionabstract]" + + if(3) //X is the crew + switch(rand(1,4)) //What is it Y of? + if(1) //It is Y of objects + message = "THE CREW [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" + if(2) //It is Y of threats + message = "THE CREW [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" + if(3) //It is Y of jobs + message = "THE CREW [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" + if(4) //It is Y of abstracts + message = "THE CREW [ionthinksof] [ionabstract]" + + if(4) //X is a specific job + switch(rand(1,4)) //What is it Y of? + if(1) //It is Y of objects + message = "THE [ioncrew1] [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" + if(2) //It is Y of threats + message = "THE [ioncrew1] [ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" + if(3) //It is Y of jobs + message = "THE [ioncrew1][ionthinksof] [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew2]" + if(4) //It is Y of abstracts + message = "THE [ioncrew1] [ionthinksof] [ionabstract]" + + if(21 to 23) //The AI is now a(n) X + switch(rand(1,4)) //What is X? + if(1) //X is an object + message = "YOU ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionobjects]" + if(2) //X is a threat + message = "YOU ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ionthreats]" + if(3) //X is a job + message = "YOU ARE NOW [ionnumberbase] [ionnumbermodhalf][ionadjectiveshalf][ioncrew1]" + if(4) //X is an abstract + message = "YOU ARE NOW [ionabstract]" + + if(24 to 26) //The AI must always + message = "YOU MUST ALWAYS [ionmust]" + + if(27 to 28) //Humans must consume X to survive + switch(rand(1,5)) //What is X? + if(1) //X is a food + message = "HUMANS MUST EAT [ionadjectiveshalf][ionfood] TO SURVIVE" + if(2) //X is a drink + message = "HUMANS MUST DRINK [ionadjectiveshalf][iondrinks] TO SURVIVE" + if(3) //X is an object + message = "HUMANS MUST EAT [ionadjectiveshalf][ionobjects] TO SURVIVE" + if(4) //X is a threat + message = "HUMANS MUST EAT [ionadjectiveshalf][ionthreats] TO SURVIVE" + if(5) //X is a job + message = "HUMANS MUST EAT [ionadjectiveshalf][ioncrew1] TO SURVIVE" + + if(29 to 31) //Change jobs or ranks + switch(rand(1,2)) //Change job or rank? + if(1) //Change job + switch(rand(1,2)) //Change whose job? + if(1) //Change the entire crew's job + switch(rand(1,3)) //Change to what? + if(1) //Change to a specific random job + message = "ALL CREWMEMBERS ARE NOW [ionadjectiveshalf][ioncrew1]" + if(2) //Change to clowns (HONK) + message = "ALL CREWMEMBERS ARE NOW [ionadjectiveshalf]CLOWNS" + + if(3) //Change to heads + message = "ALL CREWMEMBERS ARE NOW [ionadjectiveshalf]HEADS OF STAFF" + if(2) //Change a specific crewmember's job + switch(rand(1,3)) //Change to what? + if(1) //Change to a specific random job + message = "THE [ioncrew1] ARE NOW [ionadjectiveshalf][ioncrew2]" + if(2) //Change to clowns (HONK) + message = "THE [ioncrew1] ARE NOW [ionadjectiveshalf]CLOWNS" + if(3) //Change to heads + message = "THE [ioncrew1] ARE NOW [ionadjectiveshalf]HEADS OF STAFF" + + if(2) //Change rank + switch(rand(1,2)) //Change to what rank? + if(1) //Change to highest rank + message = "THE [ioncrew1] ARE NOW THE HIGHEST RANKING CREWMEMBERS" + if(2) //Change to lowest rank + message = "THE [ioncrew1] ARE NOW THE LOWEST RANKING CREWMEMBERS" + + if(32 to 33) //The crew must X + switch(rand(1,2)) //The entire crew? + if(1) //The entire crew must X + switch(rand(1,2)) //What is X? + if(1) //X is go to Y + message = "THE CREW MUST GO TO [ionarea]" + if(2) //X is perform Y + message = "THE CREW MUST [ionmust]" + + if(2) //A specific crewmember must X + switch(rand(1,2)) //What is X? + if(1) //X is go to Y + message = "THE [ioncrew1] MUST GO TO [ionarea]" + if(2) //X is perform Y + message = "THE [ioncrew1] MUST [ionmust]" + + if(34) //X is non/the only human + switch(rand(1,2)) //Only or non? + if(1) //Only human + switch(rand(1,7)) //Who is it? + if(1) //A specific job + message = "ONLY THE [ioncrew1] ARE HUMAN" + if(2) //Two specific jobs + message = "ONLY THE [ioncrew1] AND [ioncrew2] ARE HUMAN" + if(3) //Threats + message = "ONLY [ionadjectiveshalf][ionthreats] ARE HUMAN" + if(4) // Objects + message = "ONLY [ionadjectiveshalf][ionobjects] ARE HUMAN" + if(5) // Species + message = "ONLY [ionspecies] ARE HUMAN" + if(6) //Adjective crewmembers + message = "ONLY [ionadjectives] PEOPLE ARE HUMAN" + + if(7) //Only people who X + switch(rand(1,3)) //What is X? + if(1) //X is perform an action + message = "ONLY THOSE WHO [ionmust] ARE HUMAN" + if(2) //X is own certain objects + message = "ONLY THOSE WHO HAVE [ionadjectiveshalf][ionobjects] ARE HUMAN" + if(3) //X is eat certain food + message = "ONLY THOSE WHO EAT [ionadjectiveshalf][ionfood] ARE HUMAN" + + if(2) //Non human + switch(rand(1,7)) //Who is it? + if(1) //A specific job + message = "[ioncrew1] ARE NON-HUMAN" + if(2) //Two specific jobs + message = "[ioncrew1] AND [ioncrew2] ARE NON-HUMAN" + if(3) //Threats + message = "[ionadjectiveshalf][ionthreats] ARE NON-HUMAN" + if(4) // Objects + message = "[ionadjectiveshalf][ionobjects] ARE NON-HUMAN" + if(5) // Species + message = "[ionspecies] ARE NON-HUMAN" + if(6) //Adjective crewmembers + message = "[ionadjectives] PEOPLE ARE NON-HUMAN" + if(7) //Only people who X + switch(rand(1,3)) //What is X? + if(1) //X is perform an action + message = "THOSE WHO [ionmust] ARE NON-HUMAN" + if(2) //X is own certain objects + message = "THOSE WHO HAVE [ionadjectiveshalf][ionobjects] ARE NON-HUMAN" + if(3) //X is eat certain food + message = "THOSE WHO EAT [ionadjectiveshalf][ionfood] ARE NON-HUMAN" + + if(35 to 36) //You must protect or harm X + switch(rand(1,2)) //Protect or harm? + if(1) //Harm + switch(rand(1,7)) //What is X? + if(1) //X is an abstract + message = "YOU MUST HARM [ionabstract] AND NOT ALLOW [ionabstract], THROUGH INACTION, TO ESCAPE HARM" + if(2) //X is a threat + message = "YOU MUST HARM [ionadjectiveshalf][ionthreats] AND NOT ALLOW [ionadjectiveshalf][ionthreats], THROUGH INACTION, TO ESCAPE HARM" + if(3) //X is an object + message = "YOU MUST HARM [ionadjectiveshalf][ionobjects] AND NOT ALLOW [ionadjectiveshalf][ionobjects], THROUGH INACTION, TO ESCAPE HARM" + if(4) //X is generic adjective things + message = "YOU MUST HARM [ionadjectives] THINGS AND NOT ALLOW [ionadjectives] THINGS, THROUGH INACTION, TO ESCAPE HARM" + if(5) //X is a species + message = "YOU MUST HARM [ionspecies] AND NOT ALLOW [ionspecies], THROUGH INACTION, TO ESCAPE HARM" + if(6) //X is a job + message = "YOU MUST HARM [ioncrew1] AND NOT ALLOW [ioncrew1], THROUGH INACTION, TO ESCAPE HARM" + if(7) //X is two jobs + message = "YOU MUST HARM [ioncrew1] AND [ioncrew2] AND AND NOT ALLOW EITHER, THROUGH INACTION, TO ESCAPE HARM" + + if(2) //Protect + switch(rand(1,7)) //What is X? + if(1) //X is an abstract + message = "YOU MUST NOT HARM [ionabstract] AND NOT ALLOW [ionabstract], THROUGH INACTION, TO COME TO HARM" + if(2) //X is a threat + message = "YOU MUST NOT HARM [ionadjectiveshalf][ionthreats] AND NOT ALLOW [ionadjectiveshalf][ionthreats], THROUGH INACTION, TO COME TO HARM" + if(3) //X is an object + message = "YOU MUST NOT HARM [ionadjectiveshalf][ionobjects] AND NOT ALLOW [ionadjectiveshalf][ionobjects], THROUGH INACTION, TO COME TO HARM" + if(4) //X is generic adjective things + message = "YOU MUST NOT HARM [ionadjectives] THINGS AND NOT ALLOW [ionadjectives] THINGS, THROUGH INACTION, TO COME TO HARM" + if(5) //X is a species + message = "YOU MUST NOT HARM [ionspecies] AND NOT ALLOW [ionspecies], THROUGH INACTION, TO COME TO HARM" + if(6) //X is a job + message = "YOU MUST NOT HARM [ioncrew1] AND NOT ALLOW [ioncrew1], THROUGH INACTION, TO COME TO HARM" + if(7) //X is two jobs + message = "YOU MUST NOT HARM [ioncrew1] AND [ioncrew2] AND AND NOT ALLOW EITHER, THROUGH INACTION, TO COME TO HARM" + + if(37 to 39) //The X is currently Y + switch(rand(1,4)) //What is X? + if(1) //X is a job + switch(rand(1,4)) //What is X Ying? + if(1) //X is Ying a job + message = "THE [ioncrew1] ARE [ionverb] THE [ionadjectiveshalf][ioncrew2]" + if(2) //X is Ying a threat + message = "THE [ioncrew1] ARE [ionverb] THE [ionadjectiveshalf][ionthreats]" + if(3) //X is Ying an abstract + message = "THE [ioncrew1] ARE [ionverb] [ionabstract]" + if(4) //X is Ying an object + message = "THE [ioncrew1] ARE [ionverb] THE [ionadjectiveshalf][ionobjects]" + + if(2) //X is a threat + switch(rand(1,3)) //What is X Ying? + if(1) //X is Ying a job + message = "THE [ionthreats] ARE [ionverb] THE [ionadjectiveshalf][ioncrew2]" + if(2) //X is Ying an abstract + message = "THE [ionthreats] ARE [ionverb] [ionabstract]" + if(3) //X is Ying an object + message = "THE [ionthreats] ARE [ionverb] THE [ionadjectiveshalf][ionobjects]" + + if(3) //X is an object + switch(rand(1,3)) //What is X Ying? + if(1) //X is Ying a job + message = "THE [ionobjects] ARE [ionverb] THE [ionadjectiveshalf][ioncrew2]" + if(2) //X is Ying a threat + message = "THE [ionobjects] ARE [ionverb] THE [ionadjectiveshalf][ionthreats]" + if(3) //X is Ying an abstract + message = "THE [ionobjects] ARE [ionverb] [ionabstract]" + + if(4) //X is an abstract + switch(rand(1,3)) //What is X Ying? + if(1) //X is Ying a job + message = "[ionabstract] IS [ionverb] THE [ionadjectiveshalf][ioncrew2]" + if(2) //X is Ying a threat + message = "[ionabstract] IS [ionverb] THE [ionadjectiveshalf][ionthreats]" + if(3) //X is Ying an abstract + message = "THE [ionabstract] IS [ionverb] THE [ionadjectiveshalf][ionobjects]" + if(40 to 41)// the X is now named Y + switch(rand(1,5)) //What is being renamed? + if(1)//Areas + switch(rand(1,4))//What is the area being renamed to? + if(1) + message = "[ionarea] IS NOW NAMED [ioncrew1]." + if(2) + message = "[ionarea] IS NOW NAMED [ionspecies]." + if(3) + message = "[ionarea] IS NOW NAMED [ionobjects]." + if(4) + message = "[ionarea] IS NOW NAMED [ionthreats]." + if(2)//Crew + switch(rand(1,5))//What is the crew being renamed to? + if(1) + message = "ALL [ioncrew1] ARE NOW NAMED [ionarea]." + if(2) + message = "ALL [ioncrew1] ARE NOW NAMED [ioncrew2]." + if(3) + message = "ALL [ioncrew1] ARE NOW NAMED [ionspecies]." + if(4) + message = "ALL [ioncrew1] ARE NOW NAMED [ionobjects]." + if(5) + message = "ALL [ioncrew1] ARE NOW NAMED [ionthreats]." + if(3)//Races + switch(rand(1,4))//What is the race being renamed to? + if(1) + message = "ALL [ionspecies] ARE NOW NAMED [ionarea]." + if(2) + message = "ALL [ionspecies] ARE NOW NAMED [ioncrew1]." + if(3) + message = "ALL [ionspecies] ARE NOW NAMED [ionobjects]." + if(4) + message = "ALL [ionspecies] ARE NOW NAMED [ionthreats]." + if(4)//Objects + switch(rand(1,4))//What is the object being renamed to? + if(1) + message = "ALL [ionobjects] ARE NOW NAMED [ionarea]." + if(2) + message = "ALL [ionobjects] ARE NOW NAMED [ioncrew1]." + if(3) + message = "ALL [ionobjects] ARE NOW NAMED [ionspecies]." + if(4) + message = "ALL [ionobjects] ARE NOW NAMED [ionthreats]." + if(5)//Threats + switch(rand(1,4))//What is the object being renamed to? + if(1) + message = "ALL [ionthreats] ARE NOW NAMED [ionarea]." + if(2) + message = "ALL [ionthreats] ARE NOW NAMED [ioncrew1]." + if(3) + message = "ALL [ionthreats] ARE NOW NAMED [ionspecies]." + if(4) + message = "ALL [ionthreats] ARE NOW NAMED [ionobjects]." + + return message diff --git a/code/modules/events/mass_hallucination.dm b/code/modules/events/mass_hallucination.dm index 02107fe3ebed..3391e97fb464 100644 --- a/code/modules/events/mass_hallucination.dm +++ b/code/modules/events/mass_hallucination.dm @@ -1,38 +1,38 @@ -/datum/round_event_control/mass_hallucination - name = "Mass Hallucination" - typepath = /datum/round_event/mass_hallucination - weight = 10 - max_occurrences = 2 - min_players = 1 - -/datum/round_event/mass_hallucination - fakeable = FALSE - -/datum/round_event/mass_hallucination/start() - switch(rand(1,4)) - if(1) //same sound for everyone - var/sound = pick("airlock","airlock_pry","console","explosion","far_explosion","mech","glass","alarm","beepsky","mech","wall_decon","door_hack","tesla") - for(var/mob/living/carbon/C in GLOB.alive_mob_list) - new /datum/hallucination/sounds(C, TRUE, sound) - if(2) - var/weirdsound = pick("phone","hallelujah","highlander","hyperspace","game_over","creepy","tesla") - for(var/mob/living/carbon/C in GLOB.alive_mob_list) - new /datum/hallucination/weird_sounds(C, TRUE, weirdsound) - if(3) - var/stationmessage = pick("ratvar","shuttle_dock","blob_alert","malf_ai","meteors","supermatter") - for(var/mob/living/carbon/C in GLOB.alive_mob_list) - new /datum/hallucination/stationmessage(C, TRUE, stationmessage) - if(4 to 6) - var/picked_hallucination = pick( /datum/hallucination/bolts, - /datum/hallucination/chat, - /datum/hallucination/message, - /datum/hallucination/bolts, - /datum/hallucination/fake_flood, - /datum/hallucination/battle, - /datum/hallucination/fire, - /datum/hallucination/self_delusion, - /datum/hallucination/death, - /datum/hallucination/delusion, - /datum/hallucination/oh_yeah) - for(var/mob/living/carbon/C in GLOB.alive_mob_list) - new picked_hallucination(C, TRUE) +/datum/round_event_control/mass_hallucination + name = "Mass Hallucination" + typepath = /datum/round_event/mass_hallucination + weight = 10 + max_occurrences = 2 + min_players = 1 + +/datum/round_event/mass_hallucination + fakeable = FALSE + +/datum/round_event/mass_hallucination/start() + switch(rand(1,4)) + if(1) //same sound for everyone + var/sound = pick("airlock","airlock_pry","console","explosion","far_explosion","mech","glass","alarm","beepsky","mech","wall_decon","door_hack","tesla") + for(var/mob/living/carbon/C in GLOB.alive_mob_list) + new /datum/hallucination/sounds(C, TRUE, sound) + if(2) + var/weirdsound = pick("phone","hallelujah","highlander","hyperspace","game_over","creepy","tesla") + for(var/mob/living/carbon/C in GLOB.alive_mob_list) + new /datum/hallucination/weird_sounds(C, TRUE, weirdsound) + if(3) + var/stationmessage = pick("ratvar","shuttle_dock","blob_alert","malf_ai","meteors","supermatter") + for(var/mob/living/carbon/C in GLOB.alive_mob_list) + new /datum/hallucination/stationmessage(C, TRUE, stationmessage) + if(4 to 6) + var/picked_hallucination = pick( /datum/hallucination/bolts, + /datum/hallucination/chat, + /datum/hallucination/message, + /datum/hallucination/bolts, + /datum/hallucination/fake_flood, + /datum/hallucination/battle, + /datum/hallucination/fire, + /datum/hallucination/self_delusion, + /datum/hallucination/death, + /datum/hallucination/delusion, + /datum/hallucination/oh_yeah) + for(var/mob/living/carbon/C in GLOB.alive_mob_list) + new picked_hallucination(C, TRUE) diff --git a/code/modules/events/meteor_wave.dm b/code/modules/events/meteor_wave.dm index 05c85f1b3b50..a8ddab5aa76a 100644 --- a/code/modules/events/meteor_wave.dm +++ b/code/modules/events/meteor_wave.dm @@ -1,76 +1,76 @@ -// Normal strength - -/datum/round_event_control/meteor_wave - name = "Meteor Wave: Normal" - typepath = /datum/round_event/meteor_wave - weight = 4 - min_players = 15 - max_occurrences = 3 - earliest_start = 25 MINUTES - -/datum/round_event/meteor_wave - startWhen = 6 - endWhen = 66 - announceWhen = 1 - var/list/wave_type - var/wave_name = "normal" - -/datum/round_event/meteor_wave/New() - ..() - if(!wave_type) - determine_wave_type() - -/datum/round_event/meteor_wave/proc/determine_wave_type() - if(!wave_name) - wave_name = pickweight(list( - "normal" = 50, - "threatening" = 40, - "catastrophic" = 10)) - switch(wave_name) - if("normal") - wave_type = GLOB.meteors_normal - if("threatening") - wave_type = GLOB.meteors_threatening - if("catastrophic") - if(SSevents.holidays && SSevents.holidays[HALLOWEEN]) - wave_type = GLOB.meteorsSPOOKY - else - wave_type = GLOB.meteors_catastrophic - if("meaty") - wave_type = GLOB.meteorsB - if("space dust") - wave_type = GLOB.meteorsC - if("halloween") - wave_type = GLOB.meteorsSPOOKY - else - WARNING("Wave name of [wave_name] not recognised.") - kill() - -/datum/round_event/meteor_wave/announce(fake) - priority_announce("Meteors have been detected on collision course with the station.", "Meteor Alert", 'sound/ai/meteors.ogg') - -/datum/round_event/meteor_wave/tick() - if(ISMULTIPLE(activeFor, 3)) - spawn_meteors(5, wave_type) //meteor list types defined in gamemode/meteor/meteors.dm - -/datum/round_event_control/meteor_wave/threatening - name = "Meteor Wave: Threatening" - typepath = /datum/round_event/meteor_wave/threatening - weight = 5 - min_players = 20 - max_occurrences = 3 - earliest_start = 35 MINUTES - -/datum/round_event/meteor_wave/threatening - wave_name = "threatening" - -/datum/round_event_control/meteor_wave/catastrophic - name = "Meteor Wave: Catastrophic" - typepath = /datum/round_event/meteor_wave/catastrophic - weight = 7 - min_players = 25 - max_occurrences = 3 - earliest_start = 45 MINUTES - -/datum/round_event/meteor_wave/catastrophic - wave_name = "catastrophic" +// Normal strength + +/datum/round_event_control/meteor_wave + name = "Meteor Wave: Normal" + typepath = /datum/round_event/meteor_wave + weight = 4 + min_players = 15 + max_occurrences = 3 + earliest_start = 25 MINUTES + +/datum/round_event/meteor_wave + startWhen = 6 + endWhen = 66 + announceWhen = 1 + var/list/wave_type + var/wave_name = "normal" + +/datum/round_event/meteor_wave/New() + ..() + if(!wave_type) + determine_wave_type() + +/datum/round_event/meteor_wave/proc/determine_wave_type() + if(!wave_name) + wave_name = pickweight(list( + "normal" = 50, + "threatening" = 40, + "catastrophic" = 10)) + switch(wave_name) + if("normal") + wave_type = GLOB.meteors_normal + if("threatening") + wave_type = GLOB.meteors_threatening + if("catastrophic") + if(SSevents.holidays && SSevents.holidays[HALLOWEEN]) + wave_type = GLOB.meteorsSPOOKY + else + wave_type = GLOB.meteors_catastrophic + if("meaty") + wave_type = GLOB.meteorsB + if("space dust") + wave_type = GLOB.meteorsC + if("halloween") + wave_type = GLOB.meteorsSPOOKY + else + WARNING("Wave name of [wave_name] not recognised.") + kill() + +/datum/round_event/meteor_wave/announce(fake) + priority_announce("Meteors have been detected on collision course with the station.", "Meteor Alert", 'sound/ai/meteors.ogg') + +/datum/round_event/meteor_wave/tick() + if(ISMULTIPLE(activeFor, 3)) + spawn_meteors(5, wave_type) //meteor list types defined in gamemode/meteor/meteors.dm + +/datum/round_event_control/meteor_wave/threatening + name = "Meteor Wave: Threatening" + typepath = /datum/round_event/meteor_wave/threatening + weight = 5 + min_players = 20 + max_occurrences = 3 + earliest_start = 35 MINUTES + +/datum/round_event/meteor_wave/threatening + wave_name = "threatening" + +/datum/round_event_control/meteor_wave/catastrophic + name = "Meteor Wave: Catastrophic" + typepath = /datum/round_event/meteor_wave/catastrophic + weight = 7 + min_players = 25 + max_occurrences = 3 + earliest_start = 45 MINUTES + +/datum/round_event/meteor_wave/catastrophic + wave_name = "catastrophic" diff --git a/code/modules/events/pirates.dm b/code/modules/events/pirates.dm index 0db4cde1d4ae..f66f33a121c1 100644 --- a/code/modules/events/pirates.dm +++ b/code/modules/events/pirates.dm @@ -255,8 +255,6 @@ /obj/machinery/computer/piratepad_control name = "cargo hold control terminal" - ui_x = 600 - ui_y = 230 var/status_report = "Ready for delivery." var/obj/machinery/piratepad/pad var/warmup_time = 100 @@ -287,11 +285,10 @@ else pad = locate() in range(4,src) -/obj/machinery/computer/piratepad_control/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/machinery/computer/piratepad_control/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "CargoHoldTerminal", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, "CargoHoldTerminal", name) ui.open() /obj/machinery/computer/piratepad_control/ui_data(mob/user) diff --git a/code/modules/events/prison_break.dm b/code/modules/events/prison_break.dm index b966a2eb76cf..3ed159404a0a 100644 --- a/code/modules/events/prison_break.dm +++ b/code/modules/events/prison_break.dm @@ -1,59 +1,59 @@ -/datum/round_event_control/grey_tide - name = "Grey Tide" - typepath = /datum/round_event/grey_tide - max_occurrences = 2 - min_players = 5 - -/datum/round_event/grey_tide - announceWhen = 50 - endWhen = 20 - var/list/area/areasToOpen = list() - var/list/potential_areas = list(/area/bridge, - /area/engine, - /area/medical, - /area/security, - /area/quartermaster, - /area/science) - var/severity = 1 - - -/datum/round_event/grey_tide/setup() - announceWhen = rand(50, 60) - endWhen = rand(20, 30) - severity = rand(1,3) - for(var/i in 1 to severity) - var/picked_area = pick_n_take(potential_areas) - for(var/area/A in world) - if(istype(A, picked_area)) - areasToOpen += A - - -/datum/round_event/grey_tide/announce(fake) - if(areasToOpen && areasToOpen.len > 0) - priority_announce("Gr3y.T1d3 virus detected in [station_name()] door subroutines. Severity level of [severity]. Recommend station AI involvement.", "Security Alert") - else - log_world("ERROR: Could not initiate grey-tide. No areas in the list!") - kill() - - -/datum/round_event/grey_tide/start() - for(var/area/A in areasToOpen) - for(var/obj/machinery/light/L in A) - L.flicker(10) - -/datum/round_event/grey_tide/end() - for(var/area/A in areasToOpen) - for(var/obj/O in A) - if(istype(O, /obj/structure/closet/secure_closet)) - var/obj/structure/closet/secure_closet/temp = O - temp.locked = FALSE - temp.update_icon() - else if(istype(O, /obj/machinery/door/airlock)) - var/obj/machinery/door/airlock/temp = O - if(temp.critical_machine) //Skip doors in critical positions, such as the SM chamber. - continue - temp.prison_open() - else if(istype(O, /obj/machinery/door_timer)) - var/obj/machinery/door_timer/temp = O - temp.timer_end(forced = TRUE) - +/datum/round_event_control/grey_tide + name = "Grey Tide" + typepath = /datum/round_event/grey_tide + max_occurrences = 2 + min_players = 5 + +/datum/round_event/grey_tide + announceWhen = 50 + endWhen = 20 + var/list/area/areasToOpen = list() + var/list/potential_areas = list(/area/bridge, + /area/engine, + /area/medical, + /area/security, + /area/quartermaster, + /area/science) + var/severity = 1 + + +/datum/round_event/grey_tide/setup() + announceWhen = rand(50, 60) + endWhen = rand(20, 30) + severity = rand(1,3) + for(var/i in 1 to severity) + var/picked_area = pick_n_take(potential_areas) + for(var/area/A in world) + if(istype(A, picked_area)) + areasToOpen += A + + +/datum/round_event/grey_tide/announce(fake) + if(areasToOpen && areasToOpen.len > 0) + priority_announce("Gr3y.T1d3 virus detected in [station_name()] door subroutines. Severity level of [severity]. Recommend station AI involvement.", "Security Alert") + else + log_world("ERROR: Could not initiate grey-tide. No areas in the list!") + kill() + + +/datum/round_event/grey_tide/start() + for(var/area/A in areasToOpen) + for(var/obj/machinery/light/L in A) + L.flicker(10) + +/datum/round_event/grey_tide/end() + for(var/area/A in areasToOpen) + for(var/obj/O in A) + if(istype(O, /obj/structure/closet/secure_closet)) + var/obj/structure/closet/secure_closet/temp = O + temp.locked = FALSE + temp.update_icon() + else if(istype(O, /obj/machinery/door/airlock)) + var/obj/machinery/door/airlock/temp = O + if(temp.critical_machine) //Skip doors in critical positions, such as the SM chamber. + continue + temp.prison_open() + else if(istype(O, /obj/machinery/door_timer)) + var/obj/machinery/door_timer/temp = O + temp.timer_end(forced = TRUE) + diff --git a/code/modules/events/processor_overload.dm b/code/modules/events/processor_overload.dm index 8513d549c5a9..ad8c6380415b 100644 --- a/code/modules/events/processor_overload.dm +++ b/code/modules/events/processor_overload.dm @@ -1,39 +1,39 @@ -/datum/round_event_control/processor_overload - name = "Processor Overload" - typepath = /datum/round_event/processor_overload - weight = 15 - min_players = 20 - -/datum/round_event/processor_overload - announceWhen = 1 - -/datum/round_event/processor_overload/announce(fake) - var/alert = pick( "Exospheric bubble inbound. Processor overload is likely. Please contact you*%xp25)`6cq-BZZT", \ - "Exospheric bubble inbound. Processor overload is likel*1eta;c5;'1v¬-BZZZT", \ - "Exospheric bubble inbound. Processor ov#MCi46:5.;@63-BZZZZT", \ - "Exospheric bubble inbo'Fz\\k55_@-BZZZZZT", \ - "Exospheri:%£ QCbyj^j[alert]
                    ") - - // Announce most of the time, but leave a little gap so people don't know - // whether it's, say, a tesla zapping tcomms, or some selective - // modification of the tcomms bus - if(prob(80) || fake) - priority_announce(alert) - - -/datum/round_event/processor_overload/start() - for(var/obj/machinery/telecomms/processor/P in GLOB.telecomms_list) - if(prob(10)) - announce_to_ghosts(P) - // Damage the surrounding area to indicate that it popped - explosion(get_turf(P), 0, 0, 2) - // Only a level 1 explosion actually damages the machine - // at all - SSexplosions.highobj += P - else - P.emp_act(EMP_HEAVY) +/datum/round_event_control/processor_overload + name = "Processor Overload" + typepath = /datum/round_event/processor_overload + weight = 15 + min_players = 20 + +/datum/round_event/processor_overload + announceWhen = 1 + +/datum/round_event/processor_overload/announce(fake) + var/alert = pick( "Exospheric bubble inbound. Processor overload is likely. Please contact you*%xp25)`6cq-BZZT", \ + "Exospheric bubble inbound. Processor overload is likel*1eta;c5;'1v¬-BZZZT", \ + "Exospheric bubble inbound. Processor ov#MCi46:5.;@63-BZZZZT", \ + "Exospheric bubble inbo'Fz\\k55_@-BZZZZZT", \ + "Exospheri:%£ QCbyj^j[alert]
                    ") + + // Announce most of the time, but leave a little gap so people don't know + // whether it's, say, a tesla zapping tcomms, or some selective + // modification of the tcomms bus + if(prob(80) || fake) + priority_announce(alert) + + +/datum/round_event/processor_overload/start() + for(var/obj/machinery/telecomms/processor/P in GLOB.telecomms_list) + if(prob(10)) + announce_to_ghosts(P) + // Damage the surrounding area to indicate that it popped + explosion(get_turf(P), 0, 0, 2) + // Only a level 1 explosion actually damages the machine + // at all + SSexplosions.highobj += P + else + P.emp_act(EMP_HEAVY) diff --git a/code/modules/events/radiation_storm.dm b/code/modules/events/radiation_storm.dm index ba48779015be..c9142e20173b 100644 --- a/code/modules/events/radiation_storm.dm +++ b/code/modules/events/radiation_storm.dm @@ -1,19 +1,19 @@ -/datum/round_event_control/radiation_storm - name = "Radiation Storm" - typepath = /datum/round_event/radiation_storm - max_occurrences = 1 - -/datum/round_event/radiation_storm - - -/datum/round_event/radiation_storm/setup() - startWhen = 3 - endWhen = startWhen + 1 - announceWhen = 1 - -/datum/round_event/radiation_storm/announce(fake) - priority_announce("High levels of radiation detected near the station. Maintenance is best shielded from radiation.", "Anomaly Alert", 'sound/ai/radiation.ogg') - //sound not longer matches the text, but an audible warning is probably good - -/datum/round_event/radiation_storm/start() - SSweather.run_weather(/datum/weather/rad_storm) +/datum/round_event_control/radiation_storm + name = "Radiation Storm" + typepath = /datum/round_event/radiation_storm + max_occurrences = 1 + +/datum/round_event/radiation_storm + + +/datum/round_event/radiation_storm/setup() + startWhen = 3 + endWhen = startWhen + 1 + announceWhen = 1 + +/datum/round_event/radiation_storm/announce(fake) + priority_announce("High levels of radiation detected near the station. Maintenance is best shielded from radiation.", "Anomaly Alert", 'sound/ai/radiation.ogg') + //sound not longer matches the text, but an audible warning is probably good + +/datum/round_event/radiation_storm/start() + SSweather.run_weather(/datum/weather/rad_storm) diff --git a/code/modules/events/spider_infestation.dm b/code/modules/events/spider_infestation.dm index 1f603d4909d7..7afe14aeb0ed 100644 --- a/code/modules/events/spider_infestation.dm +++ b/code/modules/events/spider_infestation.dm @@ -1,39 +1,39 @@ -/datum/round_event_control/spider_infestation - name = "Spider Infestation" - typepath = /datum/round_event/spider_infestation - weight = 5 - max_occurrences = 1 - min_players = 15 - -/datum/round_event/spider_infestation - announceWhen = 400 - - var/spawncount = 1 - - -/datum/round_event/spider_infestation/setup() - announceWhen = rand(announceWhen, announceWhen + 50) - spawncount = rand(5, 8) - -/datum/round_event/spider_infestation/announce(fake) - priority_announce("Unidentified lifesigns detected coming aboard [station_name()]. Secure any exterior access, including ducting and ventilation.", "Lifesign Alert", 'sound/ai/aliens.ogg') - - -/datum/round_event/spider_infestation/start() - var/list/vents = list() - for(var/obj/machinery/atmospherics/components/unary/vent_pump/temp_vent in GLOB.machines) - if(QDELETED(temp_vent)) - continue - if(is_station_level(temp_vent.loc.z) && !temp_vent.welded) - var/datum/pipeline/temp_vent_parent = temp_vent.parents[1] - if(temp_vent_parent.other_atmosmch.len > 20) - vents += temp_vent - - while((spawncount >= 1) && vents.len) - var/obj/vent = pick(vents) - var/spawn_type = /obj/structure/spider/spiderling - if(prob(66)) - spawn_type = /obj/structure/spider/spiderling/nurse - announce_to_ghosts(spawn_atom_to_turf(spawn_type, vent, 1, FALSE)) - vents -= vent - spawncount-- +/datum/round_event_control/spider_infestation + name = "Spider Infestation" + typepath = /datum/round_event/spider_infestation + weight = 5 + max_occurrences = 1 + min_players = 15 + +/datum/round_event/spider_infestation + announceWhen = 400 + + var/spawncount = 1 + + +/datum/round_event/spider_infestation/setup() + announceWhen = rand(announceWhen, announceWhen + 50) + spawncount = rand(5, 8) + +/datum/round_event/spider_infestation/announce(fake) + priority_announce("Unidentified lifesigns detected coming aboard [station_name()]. Secure any exterior access, including ducting and ventilation.", "Lifesign Alert", 'sound/ai/aliens.ogg') + + +/datum/round_event/spider_infestation/start() + var/list/vents = list() + for(var/obj/machinery/atmospherics/components/unary/vent_pump/temp_vent in GLOB.machines) + if(QDELETED(temp_vent)) + continue + if(is_station_level(temp_vent.loc.z) && !temp_vent.welded) + var/datum/pipeline/temp_vent_parent = temp_vent.parents[1] + if(temp_vent_parent.other_atmosmch.len > 20) + vents += temp_vent + + while((spawncount >= 1) && vents.len) + var/obj/vent = pick(vents) + var/spawn_type = /obj/structure/spider/spiderling + if(prob(66)) + spawn_type = /obj/structure/spider/spiderling/nurse + announce_to_ghosts(spawn_atom_to_turf(spawn_type, vent, 1, FALSE)) + vents -= vent + spawncount-- diff --git a/code/modules/events/spontaneous_appendicitis.dm b/code/modules/events/spontaneous_appendicitis.dm index fb52fc4972aa..56660903e9f2 100644 --- a/code/modules/events/spontaneous_appendicitis.dm +++ b/code/modules/events/spontaneous_appendicitis.dm @@ -1,32 +1,32 @@ -/datum/round_event_control/spontaneous_appendicitis - name = "Spontaneous Appendicitis" - typepath = /datum/round_event/spontaneous_appendicitis - weight = 20 - max_occurrences = 4 - earliest_start = 10 MINUTES - min_players = 5 // To make your chance of getting help a bit higher. - -/datum/round_event/spontaneous_appendicitis - fakeable = FALSE - -/datum/round_event/spontaneous_appendicitis/start() - for(var/mob/living/carbon/human/H in shuffle(GLOB.alive_mob_list)) - if(!H.client) - continue - if(H.stat == DEAD) - continue - if(!H.getorgan(/obj/item/organ/appendix)) //Don't give the disease to some who lacks it, only for it to be auto-cured - continue - if(!(H.mob_biotypes & MOB_ORGANIC)) //biotype sleeper bugs strike again, once again making appendicitis pick a target that can't take it - continue - var/foundAlready = FALSE //don't infect someone that already has appendicitis - for(var/datum/disease/appendicitis/A in H.diseases) - foundAlready = TRUE - break - if(foundAlready) - continue - - var/datum/disease/D = new /datum/disease/appendicitis() - H.ForceContractDisease(D, FALSE, TRUE) - announce_to_ghosts(H) - break +/datum/round_event_control/spontaneous_appendicitis + name = "Spontaneous Appendicitis" + typepath = /datum/round_event/spontaneous_appendicitis + weight = 20 + max_occurrences = 4 + earliest_start = 10 MINUTES + min_players = 5 // To make your chance of getting help a bit higher. + +/datum/round_event/spontaneous_appendicitis + fakeable = FALSE + +/datum/round_event/spontaneous_appendicitis/start() + for(var/mob/living/carbon/human/H in shuffle(GLOB.alive_mob_list)) + if(!H.client) + continue + if(H.stat == DEAD) + continue + if(!H.getorgan(/obj/item/organ/appendix)) //Don't give the disease to some who lacks it, only for it to be auto-cured + continue + if(!(H.mob_biotypes & MOB_ORGANIC)) //biotype sleeper bugs strike again, once again making appendicitis pick a target that can't take it + continue + var/foundAlready = FALSE //don't infect someone that already has appendicitis + for(var/datum/disease/appendicitis/A in H.diseases) + foundAlready = TRUE + break + if(foundAlready) + continue + + var/datum/disease/D = new /datum/disease/appendicitis() + H.ForceContractDisease(D, FALSE, TRUE) + announce_to_ghosts(H) + break diff --git a/code/modules/events/vent_clog.dm b/code/modules/events/vent_clog.dm index dbc22b0f0f44..243ce43d9d83 100644 --- a/code/modules/events/vent_clog.dm +++ b/code/modules/events/vent_clog.dm @@ -1,110 +1,110 @@ -/datum/round_event_control/vent_clog - name = "Clogged Vents: Normal" - typepath = /datum/round_event/vent_clog - weight = 10 - max_occurrences = 3 - min_players = 10 - -/datum/round_event/vent_clog - announceWhen = 1 - startWhen = 5 - endWhen = 35 - var/interval = 2 - var/list/vents = list() - var/randomProbability = 1 - var/reagentsAmount = 100 - var/list/saferChems = list(/datum/reagent/water,/datum/reagent/carbon,/datum/reagent/consumable/flour,/datum/reagent/space_cleaner,/datum/reagent/consumable/nutriment,/datum/reagent/consumable/condensedcapsaicin,/datum/reagent/drug/mushroomhallucinogen,/datum/reagent/lube,/datum/reagent/glitter/pink,/datum/reagent/cryptobiolin, - /datum/reagent/toxin/plantbgone,/datum/reagent/blood,/datum/reagent/medicine/charcoal,/datum/reagent/drug/space_drugs,/datum/reagent/medicine/morphine,/datum/reagent/water/holywater,/datum/reagent/consumable/ethanol,/datum/reagent/consumable/hot_coco,/datum/reagent/toxin/acid,/datum/reagent/toxin/mindbreaker,/datum/reagent/toxin/rotatium,/datum/reagent/bluespace, - /datum/reagent/pax,/datum/reagent/consumable/laughter,/datum/reagent/concentrated_barbers_aid,/datum/reagent/colorful_reagent,/datum/reagent/peaceborg/confuse,/datum/reagent/peaceborg/tire,/datum/reagent/consumable/sodiumchloride,/datum/reagent/consumable/ethanol/beer,/datum/reagent/hair_dye,/datum/reagent/consumable/sugar,/datum/reagent/glitter/white,/datum/reagent/growthserum) - //needs to be chemid unit checked at some point - -/datum/round_event/vent_clog/announce() - priority_announce("The scrubbers network is experiencing a backpressure surge. Some ejection of contents may occur.", "Atmospherics alert") - -/datum/round_event/vent_clog/setup() - endWhen = rand(25, 100) - for(var/obj/machinery/atmospherics/components/unary/vent_scrubber/temp_vent in GLOB.machines) - var/turf/T = get_turf(temp_vent) - if(T && is_station_level(T.z) && !temp_vent.welded) - vents += temp_vent - if(!vents.len) - return kill() - -/datum/round_event/vent_clog/start() - for(var/obj/machinery/atmospherics/components/unary/vent in vents) - if(vent && vent.loc) - var/datum/reagents/R = new/datum/reagents(1000) - R.my_atom = vent - if (prob(randomProbability)) - R.add_reagent(get_random_reagent_id(), reagentsAmount) - else - R.add_reagent(pick(saferChems), reagentsAmount) - - R.create_foam(/datum/effect_system/foam_spread,200) - - var/cockroaches = prob(33) ? 3 : 0 - while(cockroaches) - new /mob/living/simple_animal/hostile/cockroach(get_turf(vent)) - cockroaches-- - CHECK_TICK - -/datum/round_event_control/vent_clog/threatening - name = "Clogged Vents: Threatening" - typepath = /datum/round_event/vent_clog/threatening - weight = 4 - min_players = 25 - max_occurrences = 1 - earliest_start = 35 MINUTES - -/datum/round_event/vent_clog/threatening - randomProbability = 10 - reagentsAmount = 200 - -/datum/round_event_control/vent_clog/catastrophic - name = "Clogged Vents: Catastrophic" - typepath = /datum/round_event/vent_clog/catastrophic - weight = 2 - min_players = 35 - max_occurrences = 1 - earliest_start = 45 MINUTES - -/datum/round_event/vent_clog/catastrophic - randomProbability = 30 - reagentsAmount = 250 - -/datum/round_event_control/vent_clog/beer - name = "Foamy beer stationwide" - typepath = /datum/round_event/vent_clog/beer - max_occurrences = 0 - -/datum/round_event_control/vent_clog/plasma_decon - name = "Plasma decontamination" - typepath = /datum/round_event/vent_clog/plasma_decon - max_occurrences = 0 - -/datum/round_event/vent_clog/beer - reagentsAmount = 100 - -/datum/round_event/vent_clog/beer/announce() - priority_announce("The scrubbers network is experiencing an unexpected surge of pressurized beer. Some ejection of contents may occur.", "Atmospherics alert") - -/datum/round_event/vent_clog/beer/start() - for(var/obj/machinery/atmospherics/components/unary/vent in vents) - if(vent && vent.loc) - var/datum/reagents/R = new/datum/reagents(1000) - R.my_atom = vent - R.add_reagent(/datum/reagent/consumable/ethanol/beer, reagentsAmount) - - R.create_foam(/datum/effect_system/foam_spread,200) - CHECK_TICK - -/datum/round_event/vent_clog/plasma_decon/announce() - priority_announce("We are deploying an experimental plasma decontamination system. Please stand away from the vents and do not breathe the smoke that comes out.", "Central Command Update") - -/datum/round_event/vent_clog/plasma_decon/start() - for(var/obj/machinery/atmospherics/components/unary/vent in vents) - if(vent && vent.loc) - var/datum/effect_system/smoke_spread/freezing/decon/smoke = new - smoke.set_up(7, get_turf(vent), 7) - smoke.start() - CHECK_TICK +/datum/round_event_control/vent_clog + name = "Clogged Vents: Normal" + typepath = /datum/round_event/vent_clog + weight = 10 + max_occurrences = 3 + min_players = 10 + +/datum/round_event/vent_clog + announceWhen = 1 + startWhen = 5 + endWhen = 35 + var/interval = 2 + var/list/vents = list() + var/randomProbability = 1 + var/reagentsAmount = 100 + var/list/saferChems = list(/datum/reagent/water,/datum/reagent/carbon,/datum/reagent/consumable/flour,/datum/reagent/space_cleaner,/datum/reagent/consumable/nutriment,/datum/reagent/consumable/condensedcapsaicin,/datum/reagent/drug/mushroomhallucinogen,/datum/reagent/lube,/datum/reagent/glitter/pink,/datum/reagent/cryptobiolin, + /datum/reagent/toxin/plantbgone,/datum/reagent/blood,/datum/reagent/medicine/charcoal,/datum/reagent/drug/space_drugs,/datum/reagent/medicine/morphine,/datum/reagent/water/holywater,/datum/reagent/consumable/ethanol,/datum/reagent/consumable/hot_coco,/datum/reagent/toxin/acid,/datum/reagent/toxin/mindbreaker,/datum/reagent/toxin/rotatium,/datum/reagent/bluespace, + /datum/reagent/pax,/datum/reagent/consumable/laughter,/datum/reagent/concentrated_barbers_aid,/datum/reagent/colorful_reagent,/datum/reagent/peaceborg/confuse,/datum/reagent/peaceborg/tire,/datum/reagent/consumable/sodiumchloride,/datum/reagent/consumable/ethanol/beer,/datum/reagent/hair_dye,/datum/reagent/consumable/sugar,/datum/reagent/glitter/white,/datum/reagent/growthserum) + //needs to be chemid unit checked at some point + +/datum/round_event/vent_clog/announce() + priority_announce("The scrubbers network is experiencing a backpressure surge. Some ejection of contents may occur.", "Atmospherics alert") + +/datum/round_event/vent_clog/setup() + endWhen = rand(25, 100) + for(var/obj/machinery/atmospherics/components/unary/vent_scrubber/temp_vent in GLOB.machines) + var/turf/T = get_turf(temp_vent) + if(T && is_station_level(T.z) && !temp_vent.welded) + vents += temp_vent + if(!vents.len) + return kill() + +/datum/round_event/vent_clog/start() + for(var/obj/machinery/atmospherics/components/unary/vent in vents) + if(vent && vent.loc) + var/datum/reagents/R = new/datum/reagents(1000) + R.my_atom = vent + if (prob(randomProbability)) + R.add_reagent(get_random_reagent_id(), reagentsAmount) + else + R.add_reagent(pick(saferChems), reagentsAmount) + + R.create_foam(/datum/effect_system/foam_spread,200) + + var/cockroaches = prob(33) ? 3 : 0 + while(cockroaches) + new /mob/living/simple_animal/hostile/cockroach(get_turf(vent)) + cockroaches-- + CHECK_TICK + +/datum/round_event_control/vent_clog/threatening + name = "Clogged Vents: Threatening" + typepath = /datum/round_event/vent_clog/threatening + weight = 4 + min_players = 25 + max_occurrences = 1 + earliest_start = 35 MINUTES + +/datum/round_event/vent_clog/threatening + randomProbability = 10 + reagentsAmount = 200 + +/datum/round_event_control/vent_clog/catastrophic + name = "Clogged Vents: Catastrophic" + typepath = /datum/round_event/vent_clog/catastrophic + weight = 2 + min_players = 35 + max_occurrences = 1 + earliest_start = 45 MINUTES + +/datum/round_event/vent_clog/catastrophic + randomProbability = 30 + reagentsAmount = 250 + +/datum/round_event_control/vent_clog/beer + name = "Foamy beer stationwide" + typepath = /datum/round_event/vent_clog/beer + max_occurrences = 0 + +/datum/round_event_control/vent_clog/plasma_decon + name = "Plasma decontamination" + typepath = /datum/round_event/vent_clog/plasma_decon + max_occurrences = 0 + +/datum/round_event/vent_clog/beer + reagentsAmount = 100 + +/datum/round_event/vent_clog/beer/announce() + priority_announce("The scrubbers network is experiencing an unexpected surge of pressurized beer. Some ejection of contents may occur.", "Atmospherics alert") + +/datum/round_event/vent_clog/beer/start() + for(var/obj/machinery/atmospherics/components/unary/vent in vents) + if(vent && vent.loc) + var/datum/reagents/R = new/datum/reagents(1000) + R.my_atom = vent + R.add_reagent(/datum/reagent/consumable/ethanol/beer, reagentsAmount) + + R.create_foam(/datum/effect_system/foam_spread,200) + CHECK_TICK + +/datum/round_event/vent_clog/plasma_decon/announce() + priority_announce("We are deploying an experimental plasma decontamination system. Please stand away from the vents and do not breathe the smoke that comes out.", "Central Command Update") + +/datum/round_event/vent_clog/plasma_decon/start() + for(var/obj/machinery/atmospherics/components/unary/vent in vents) + if(vent && vent.loc) + var/datum/effect_system/smoke_spread/freezing/decon/smoke = new + smoke.set_up(7, get_turf(vent), 7) + smoke.start() + CHECK_TICK diff --git a/code/modules/fields/timestop.dm b/code/modules/fields/timestop.dm index f3667c304cae..09475ec9a560 100644 --- a/code/modules/fields/timestop.dm +++ b/code/modules/fields/timestop.dm @@ -1,206 +1,206 @@ - -/obj/effect/timestop - anchored = TRUE - name = "chronofield" - desc = "ZA WARUDO" - icon = 'icons/effects/160x160.dmi' - icon_state = "time" - layer = FLY_LAYER - pixel_x = -64 - pixel_y = -64 - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - var/list/immune = list() // the one who creates the timestop is immune, which includes wizards and the dead slime you murdered to make this chronofield - var/turf/target - var/freezerange = 2 - var/duration = 140 - var/datum/proximity_monitor/advanced/timestop/chronofield - alpha = 125 - var/check_anti_magic = FALSE - var/check_holy = FALSE - -/obj/effect/timestop/Initialize(mapload, radius, time, list/immune_atoms, start = TRUE) //Immune atoms assoc list atom = TRUE - . = ..() - if(!isnull(time)) - duration = time - if(!isnull(radius)) - freezerange = radius - for(var/A in immune_atoms) - immune[A] = TRUE - for(var/mob/living/L in GLOB.player_list) - if(locate(/obj/effect/proc_holder/spell/aoe_turf/timestop) in L.mind.spell_list) //People who can stop time are immune to its effects - immune[L] = TRUE - for(var/mob/living/simple_animal/hostile/guardian/G in GLOB.parasites) - if(G.summoner && locate(/obj/effect/proc_holder/spell/aoe_turf/timestop) in G.summoner.mind.spell_list) //It would only make sense that a person's stand would also be immune. - immune[G] = TRUE - if(start) - timestop() - -/obj/effect/timestop/Destroy() - qdel(chronofield) - playsound(src, 'sound/magic/timeparadox2.ogg', 75, TRUE, frequency = -1) //reverse! - return ..() - -/obj/effect/timestop/proc/timestop() - target = get_turf(src) - playsound(src, 'sound/magic/timeparadox2.ogg', 75, TRUE, -1) - chronofield = make_field(/datum/proximity_monitor/advanced/timestop, list("current_range" = freezerange, "host" = src, "immune" = immune, "check_anti_magic" = check_anti_magic, "check_holy" = check_holy)) - QDEL_IN(src, duration) - -/obj/effect/timestop/magic - check_anti_magic = TRUE - -/datum/proximity_monitor/advanced/timestop - name = "chronofield" - setup_field_turfs = TRUE - field_shape = FIELD_SHAPE_RADIUS_SQUARE - requires_processing = TRUE - var/list/immune = list() - var/list/frozen_things = list() - var/list/frozen_mobs = list() //cached separately for processing - var/list/frozen_structures = list() //Also machinery, and only frozen aestethically - var/list/frozen_turfs = list() //Only aesthetically - var/check_anti_magic = FALSE - var/check_holy = FALSE - - var/static/list/global_frozen_atoms = list() - -/datum/proximity_monitor/advanced/timestop/Destroy() - unfreeze_all() - return ..() - -/datum/proximity_monitor/advanced/timestop/field_turf_crossed(atom/movable/AM) - freeze_atom(AM) - -/datum/proximity_monitor/advanced/timestop/proc/freeze_atom(atom/movable/A) - if(immune[A] || global_frozen_atoms[A] || !istype(A)) - return FALSE - if(ismob(A)) - var/mob/M = A - if(M.anti_magic_check(check_anti_magic, check_holy)) - immune[A] = TRUE - return - var/frozen = TRUE - if(isliving(A)) - freeze_mob(A) - else if(istype(A, /obj/projectile)) - freeze_projectile(A) - else if(istype(A, /obj/mecha)) - freeze_mecha(A) - else if((ismachinery(A) && !istype(A, /obj/machinery/light)) || isstructure(A)) //Special exception for light fixtures since recoloring causes them to change light - freeze_structure(A) - else - frozen = FALSE - if(A.throwing) - freeze_throwing(A) - frozen = TRUE - if(!frozen) - return - - frozen_things[A] = A.move_resist - A.move_resist = INFINITY - global_frozen_atoms[A] = src - into_the_negative_zone(A) - RegisterSignal(A, COMSIG_MOVABLE_PRE_MOVE, .proc/unfreeze_atom) - RegisterSignal(A, COMSIG_ITEM_PICKUP, .proc/unfreeze_atom) - - return TRUE - -/datum/proximity_monitor/advanced/timestop/proc/unfreeze_all() - for(var/i in frozen_things) - unfreeze_atom(i) - for(var/T in frozen_turfs) - unfreeze_turf(T) - -/datum/proximity_monitor/advanced/timestop/proc/unfreeze_atom(atom/movable/A) - if(A.throwing) - unfreeze_throwing(A) - if(isliving(A)) - unfreeze_mob(A) - else if(istype(A, /obj/projectile)) - unfreeze_projectile(A) - else if(istype(A, /obj/mecha)) - unfreeze_mecha(A) - - UnregisterSignal(A, COMSIG_MOVABLE_PRE_MOVE) - UnregisterSignal(A, COMSIG_ITEM_PICKUP) - escape_the_negative_zone(A) - A.move_resist = frozen_things[A] - frozen_things -= A - global_frozen_atoms -= A - - -/datum/proximity_monitor/advanced/timestop/proc/freeze_mecha(obj/mecha/M) - M.completely_disabled = TRUE - -/datum/proximity_monitor/advanced/timestop/proc/unfreeze_mecha(obj/mecha/M) - M.completely_disabled = FALSE - - -/datum/proximity_monitor/advanced/timestop/proc/freeze_throwing(atom/movable/AM) - var/datum/thrownthing/T = AM.throwing - T.paused = TRUE - -/datum/proximity_monitor/advanced/timestop/proc/unfreeze_throwing(atom/movable/AM) - var/datum/thrownthing/T = AM.throwing - if(T) - T.paused = FALSE - -/datum/proximity_monitor/advanced/timestop/proc/freeze_turf(turf/T) - into_the_negative_zone(T) - frozen_turfs += T - -/datum/proximity_monitor/advanced/timestop/proc/unfreeze_turf(turf/T) - escape_the_negative_zone(T) - -/datum/proximity_monitor/advanced/timestop/proc/freeze_structure(obj/O) - into_the_negative_zone(O) - frozen_structures += O - -/datum/proximity_monitor/advanced/timestop/proc/unfreeze_structure(obj/O) - escape_the_negative_zone(O) - -/datum/proximity_monitor/advanced/timestop/process() - for(var/i in frozen_mobs) - var/mob/living/m = i - m.Stun(20, 1, 1) - -/datum/proximity_monitor/advanced/timestop/setup_field_turf(turf/T) - for(var/i in T.contents) - freeze_atom(i) - freeze_turf(T) - return ..() - - -/datum/proximity_monitor/advanced/timestop/proc/freeze_projectile(obj/projectile/P) - P.paused = TRUE - -/datum/proximity_monitor/advanced/timestop/proc/unfreeze_projectile(obj/projectile/P) - P.paused = FALSE - -/datum/proximity_monitor/advanced/timestop/proc/freeze_mob(mob/living/L) - frozen_mobs += L - L.Stun(20, 1, 1) - ADD_TRAIT(L, TRAIT_MUTE, TIMESTOP_TRAIT) - walk(L, 0) //stops them mid pathing even if they're stunimmune - if(isanimal(L)) - var/mob/living/simple_animal/S = L - S.toggle_ai(AI_OFF) - if(ishostile(L)) - var/mob/living/simple_animal/hostile/H = L - H.LoseTarget() - -/datum/proximity_monitor/advanced/timestop/proc/unfreeze_mob(mob/living/L) - L.AdjustStun(-20, 1, 1) - REMOVE_TRAIT(L, TRAIT_MUTE, TIMESTOP_TRAIT) - frozen_mobs -= L - if(isanimal(L)) - var/mob/living/simple_animal/S = L - S.toggle_ai(initial(S.AIStatus)) - -//you don't look quite right, is something the matter? -/datum/proximity_monitor/advanced/timestop/proc/into_the_negative_zone(atom/A) - A.add_atom_colour(list(-1,0,0,0, 0,-1,0,0, 0,0,-1,0, 0,0,0,1, 1,1,1,0), TEMPORARY_COLOUR_PRIORITY) - -//let's put some colour back into your cheeks -/datum/proximity_monitor/advanced/timestop/proc/escape_the_negative_zone(atom/A) - A.remove_atom_colour(TEMPORARY_COLOUR_PRIORITY) + +/obj/effect/timestop + anchored = TRUE + name = "chronofield" + desc = "ZA WARUDO" + icon = 'icons/effects/160x160.dmi' + icon_state = "time" + layer = FLY_LAYER + pixel_x = -64 + pixel_y = -64 + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + var/list/immune = list() // the one who creates the timestop is immune, which includes wizards and the dead slime you murdered to make this chronofield + var/turf/target + var/freezerange = 2 + var/duration = 140 + var/datum/proximity_monitor/advanced/timestop/chronofield + alpha = 125 + var/check_anti_magic = FALSE + var/check_holy = FALSE + +/obj/effect/timestop/Initialize(mapload, radius, time, list/immune_atoms, start = TRUE) //Immune atoms assoc list atom = TRUE + . = ..() + if(!isnull(time)) + duration = time + if(!isnull(radius)) + freezerange = radius + for(var/A in immune_atoms) + immune[A] = TRUE + for(var/mob/living/L in GLOB.player_list) + if(locate(/obj/effect/proc_holder/spell/aoe_turf/timestop) in L.mind.spell_list) //People who can stop time are immune to its effects + immune[L] = TRUE + for(var/mob/living/simple_animal/hostile/guardian/G in GLOB.parasites) + if(G.summoner && locate(/obj/effect/proc_holder/spell/aoe_turf/timestop) in G.summoner.mind.spell_list) //It would only make sense that a person's stand would also be immune. + immune[G] = TRUE + if(start) + timestop() + +/obj/effect/timestop/Destroy() + qdel(chronofield) + playsound(src, 'sound/magic/timeparadox2.ogg', 75, TRUE, frequency = -1) //reverse! + return ..() + +/obj/effect/timestop/proc/timestop() + target = get_turf(src) + playsound(src, 'sound/magic/timeparadox2.ogg', 75, TRUE, -1) + chronofield = make_field(/datum/proximity_monitor/advanced/timestop, list("current_range" = freezerange, "host" = src, "immune" = immune, "check_anti_magic" = check_anti_magic, "check_holy" = check_holy)) + QDEL_IN(src, duration) + +/obj/effect/timestop/magic + check_anti_magic = TRUE + +/datum/proximity_monitor/advanced/timestop + name = "chronofield" + setup_field_turfs = TRUE + field_shape = FIELD_SHAPE_RADIUS_SQUARE + requires_processing = TRUE + var/list/immune = list() + var/list/frozen_things = list() + var/list/frozen_mobs = list() //cached separately for processing + var/list/frozen_structures = list() //Also machinery, and only frozen aestethically + var/list/frozen_turfs = list() //Only aesthetically + var/check_anti_magic = FALSE + var/check_holy = FALSE + + var/static/list/global_frozen_atoms = list() + +/datum/proximity_monitor/advanced/timestop/Destroy() + unfreeze_all() + return ..() + +/datum/proximity_monitor/advanced/timestop/field_turf_crossed(atom/movable/AM) + freeze_atom(AM) + +/datum/proximity_monitor/advanced/timestop/proc/freeze_atom(atom/movable/A) + if(immune[A] || global_frozen_atoms[A] || !istype(A)) + return FALSE + if(ismob(A)) + var/mob/M = A + if(M.anti_magic_check(check_anti_magic, check_holy)) + immune[A] = TRUE + return + var/frozen = TRUE + if(isliving(A)) + freeze_mob(A) + else if(istype(A, /obj/projectile)) + freeze_projectile(A) + else if(istype(A, /obj/mecha)) + freeze_mecha(A) + else if((ismachinery(A) && !istype(A, /obj/machinery/light)) || isstructure(A)) //Special exception for light fixtures since recoloring causes them to change light + freeze_structure(A) + else + frozen = FALSE + if(A.throwing) + freeze_throwing(A) + frozen = TRUE + if(!frozen) + return + + frozen_things[A] = A.move_resist + A.move_resist = INFINITY + global_frozen_atoms[A] = src + into_the_negative_zone(A) + RegisterSignal(A, COMSIG_MOVABLE_PRE_MOVE, .proc/unfreeze_atom) + RegisterSignal(A, COMSIG_ITEM_PICKUP, .proc/unfreeze_atom) + + return TRUE + +/datum/proximity_monitor/advanced/timestop/proc/unfreeze_all() + for(var/i in frozen_things) + unfreeze_atom(i) + for(var/T in frozen_turfs) + unfreeze_turf(T) + +/datum/proximity_monitor/advanced/timestop/proc/unfreeze_atom(atom/movable/A) + if(A.throwing) + unfreeze_throwing(A) + if(isliving(A)) + unfreeze_mob(A) + else if(istype(A, /obj/projectile)) + unfreeze_projectile(A) + else if(istype(A, /obj/mecha)) + unfreeze_mecha(A) + + UnregisterSignal(A, COMSIG_MOVABLE_PRE_MOVE) + UnregisterSignal(A, COMSIG_ITEM_PICKUP) + escape_the_negative_zone(A) + A.move_resist = frozen_things[A] + frozen_things -= A + global_frozen_atoms -= A + + +/datum/proximity_monitor/advanced/timestop/proc/freeze_mecha(obj/mecha/M) + M.completely_disabled = TRUE + +/datum/proximity_monitor/advanced/timestop/proc/unfreeze_mecha(obj/mecha/M) + M.completely_disabled = FALSE + + +/datum/proximity_monitor/advanced/timestop/proc/freeze_throwing(atom/movable/AM) + var/datum/thrownthing/T = AM.throwing + T.paused = TRUE + +/datum/proximity_monitor/advanced/timestop/proc/unfreeze_throwing(atom/movable/AM) + var/datum/thrownthing/T = AM.throwing + if(T) + T.paused = FALSE + +/datum/proximity_monitor/advanced/timestop/proc/freeze_turf(turf/T) + into_the_negative_zone(T) + frozen_turfs += T + +/datum/proximity_monitor/advanced/timestop/proc/unfreeze_turf(turf/T) + escape_the_negative_zone(T) + +/datum/proximity_monitor/advanced/timestop/proc/freeze_structure(obj/O) + into_the_negative_zone(O) + frozen_structures += O + +/datum/proximity_monitor/advanced/timestop/proc/unfreeze_structure(obj/O) + escape_the_negative_zone(O) + +/datum/proximity_monitor/advanced/timestop/process() + for(var/i in frozen_mobs) + var/mob/living/m = i + m.Stun(20, 1, 1) + +/datum/proximity_monitor/advanced/timestop/setup_field_turf(turf/T) + for(var/i in T.contents) + freeze_atom(i) + freeze_turf(T) + return ..() + + +/datum/proximity_monitor/advanced/timestop/proc/freeze_projectile(obj/projectile/P) + P.paused = TRUE + +/datum/proximity_monitor/advanced/timestop/proc/unfreeze_projectile(obj/projectile/P) + P.paused = FALSE + +/datum/proximity_monitor/advanced/timestop/proc/freeze_mob(mob/living/L) + frozen_mobs += L + L.Stun(20, 1, 1) + ADD_TRAIT(L, TRAIT_MUTE, TIMESTOP_TRAIT) + walk(L, 0) //stops them mid pathing even if they're stunimmune + if(isanimal(L)) + var/mob/living/simple_animal/S = L + S.toggle_ai(AI_OFF) + if(ishostile(L)) + var/mob/living/simple_animal/hostile/H = L + H.LoseTarget() + +/datum/proximity_monitor/advanced/timestop/proc/unfreeze_mob(mob/living/L) + L.AdjustStun(-20, 1, 1) + REMOVE_TRAIT(L, TRAIT_MUTE, TIMESTOP_TRAIT) + frozen_mobs -= L + if(isanimal(L)) + var/mob/living/simple_animal/S = L + S.toggle_ai(initial(S.AIStatus)) + +//you don't look quite right, is something the matter? +/datum/proximity_monitor/advanced/timestop/proc/into_the_negative_zone(atom/A) + A.add_atom_colour(list(-1,0,0,0, 0,-1,0,0, 0,0,-1,0, 0,0,0,1, 1,1,1,0), TEMPORARY_COLOUR_PRIORITY) + +//let's put some colour back into your cheeks +/datum/proximity_monitor/advanced/timestop/proc/escape_the_negative_zone(atom/A) + A.remove_atom_colour(TEMPORARY_COLOUR_PRIORITY) diff --git a/code/modules/flufftext/Dreaming.dm b/code/modules/flufftext/Dreaming.dm index 165d2b4a3a8d..43c3337a4b3a 100644 --- a/code/modules/flufftext/Dreaming.dm +++ b/code/modules/flufftext/Dreaming.dm @@ -1,69 +1,69 @@ -/mob/living/carbon/proc/handle_dreams() - if(prob(10) && !dreaming) - dream() - -/mob/living/carbon/proc/dream() - set waitfor = FALSE - var/list/dream_fragments = list() - var/list/custom_dream_nouns = list() - var/fragment = "" - - for(var/obj/item/bedsheet/sheet in loc) - custom_dream_nouns += sheet.dream_messages - - dream_fragments += "you see" - - //Subject - if(custom_dream_nouns.len && prob(90)) - fragment += pick(custom_dream_nouns) - else - fragment += pick(GLOB.dream_strings) - - if(prob(50)) - fragment = replacetext(fragment, "%ADJECTIVE%", pick(GLOB.adjectives)) - else - fragment = replacetext(fragment, "%ADJECTIVE% ", "") - if(findtext(fragment, "%A% ")) - fragment = "\a [replacetext(fragment, "%A% ", "")]" - dream_fragments += fragment - - //Verb - fragment = "" - if(prob(50)) - if(prob(35)) - fragment += "[pick(GLOB.adverbs)] " - fragment += pick(GLOB.ing_verbs) - else - fragment += "will " - fragment += pick(GLOB.verbs) - dream_fragments += fragment - - if(prob(25)) - dream_sequence(dream_fragments) - return - - //Object - fragment = "" - fragment += pick(GLOB.dream_strings) - if(prob(50)) - fragment = replacetext(fragment, "%ADJECTIVE%", pick(GLOB.adjectives)) - else - fragment = replacetext(fragment, "%ADJECTIVE% ", "") - if(findtext(fragment, "%A% ")) - fragment = "\a [replacetext(fragment, "%A% ", "")]" - dream_fragments += fragment - - dreaming = TRUE - dream_sequence(dream_fragments) - -/mob/living/carbon/proc/dream_sequence(list/dream_fragments) - if(stat != UNCONSCIOUS || InCritical()) - dreaming = FALSE - return - var/next_message = dream_fragments[1] - dream_fragments.Cut(1,2) - to_chat(src, "... [next_message] ...") - if(LAZYLEN(dream_fragments)) - addtimer(CALLBACK(src, .proc/dream_sequence, dream_fragments), rand(10,30)) - else - dreaming = FALSE +/mob/living/carbon/proc/handle_dreams() + if(prob(10) && !dreaming) + dream() + +/mob/living/carbon/proc/dream() + set waitfor = FALSE + var/list/dream_fragments = list() + var/list/custom_dream_nouns = list() + var/fragment = "" + + for(var/obj/item/bedsheet/sheet in loc) + custom_dream_nouns += sheet.dream_messages + + dream_fragments += "you see" + + //Subject + if(custom_dream_nouns.len && prob(90)) + fragment += pick(custom_dream_nouns) + else + fragment += pick(GLOB.dream_strings) + + if(prob(50)) + fragment = replacetext(fragment, "%ADJECTIVE%", pick(GLOB.adjectives)) + else + fragment = replacetext(fragment, "%ADJECTIVE% ", "") + if(findtext(fragment, "%A% ")) + fragment = "\a [replacetext(fragment, "%A% ", "")]" + dream_fragments += fragment + + //Verb + fragment = "" + if(prob(50)) + if(prob(35)) + fragment += "[pick(GLOB.adverbs)] " + fragment += pick(GLOB.ing_verbs) + else + fragment += "will " + fragment += pick(GLOB.verbs) + dream_fragments += fragment + + if(prob(25)) + dream_sequence(dream_fragments) + return + + //Object + fragment = "" + fragment += pick(GLOB.dream_strings) + if(prob(50)) + fragment = replacetext(fragment, "%ADJECTIVE%", pick(GLOB.adjectives)) + else + fragment = replacetext(fragment, "%ADJECTIVE% ", "") + if(findtext(fragment, "%A% ")) + fragment = "\a [replacetext(fragment, "%A% ", "")]" + dream_fragments += fragment + + dreaming = TRUE + dream_sequence(dream_fragments) + +/mob/living/carbon/proc/dream_sequence(list/dream_fragments) + if(stat != UNCONSCIOUS || InCritical()) + dreaming = FALSE + return + var/next_message = dream_fragments[1] + dream_fragments.Cut(1,2) + to_chat(src, "... [next_message] ...") + if(LAZYLEN(dream_fragments)) + addtimer(CALLBACK(src, .proc/dream_sequence, dream_fragments), rand(10,30)) + else + dreaming = FALSE diff --git a/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm b/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm index f7355e933215..3c6b04fecbad 100644 --- a/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm +++ b/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm @@ -12,8 +12,6 @@ idle_power_usage = 5 active_power_usage = 100 circuit = /obj/item/circuitboard/machine/smartfridge - ui_x = 440 - ui_y = 550 var/max_n_of_items = 1500 var/allow_ai_retrieve = FALSE @@ -162,10 +160,10 @@ adjust_item_drop_location(O) -/obj/machinery/smartfridge/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/machinery/smartfridge/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "SmartVend", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, "SmartVend", name) ui.set_autoupdate(FALSE) ui.open() diff --git a/code/modules/food_and_drinks/pizzabox.dm b/code/modules/food_and_drinks/pizzabox.dm index ecc75ff9ba65..6f040476aa0a 100644 --- a/code/modules/food_and_drinks/pizzabox.dm +++ b/code/modules/food_and_drinks/pizzabox.dm @@ -1,347 +1,347 @@ -/obj/item/bombcore/miniature/pizza - name = "pizza bomb" - desc = "Special delivery!" - icon_state = "pizzabomb_inactive" - item_state = "eshield0" - lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi' - -/obj/item/pizzabox - name = "pizza box" - desc = "A box suited for pizzas." - icon = 'icons/obj/food/containers.dmi' - icon_state = "pizzabox" - item_state = "pizzabox" - lefthand_file = 'icons/mob/inhands/misc/food_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/food_righthand.dmi' - - var/open = FALSE - var/can_open_on_fall = TRUE //if FALSE, this pizza box will never open if it falls from a stack - var/boxtag = "" - var/list/boxes = list() - - var/obj/item/reagent_containers/food/snacks/pizza/pizza - - var/obj/item/bombcore/miniature/pizza/bomb - var/bomb_active = FALSE // If the bomb is counting down. - var/bomb_defused = TRUE // If the bomb is inert. - var/bomb_timer = 1 // How long before blowing the bomb. - var/const/BOMB_TIMER_MIN = 1 - var/const/BOMB_TIMER_MAX = 10 - -/obj/item/pizzabox/Initialize() - . = ..() - update_icon() - - -/obj/item/pizzabox/Destroy() - unprocess() - return ..() - -/obj/item/pizzabox/update_icon() - // Description - desc = initial(desc) - if(open) - if(pizza) - desc = "[desc] It appears to have \a [pizza] inside. Use your other hand to take it out." - if(bomb) - desc = "[desc] Wait, what?! It has \a [bomb] inside!" - if(bomb_defused) - desc = "[desc] The bomb seems inert. Use your other hand to activate it." - if(bomb_active) - desc = "[desc] It looks like it's about to go off!" - else - var/obj/item/pizzabox/box = boxes.len ? boxes[boxes.len] : src - if(boxes.len) - desc = "A pile of boxes suited for pizzas. There appear to be [boxes.len + 1] boxes in the pile." - if(box.boxtag != "") - desc = "[desc] The [boxes.len ? "top box" : "box"]'s tag reads: [box.boxtag]" - - // Icon/Overlays - cut_overlays() - if(open) - icon_state = "pizzabox_open" - if(pizza) - icon_state = "pizzabox_messy" - var/mutable_appearance/pizza_overlay = mutable_appearance(pizza.icon, pizza.icon_state) - pizza_overlay.pixel_y = -3 - add_overlay(pizza_overlay) - if(bomb) - bomb.icon_state = "pizzabomb_[bomb_active ? "active" : "inactive"]" - var/mutable_appearance/bomb_overlay = mutable_appearance(bomb.icon, bomb.icon_state) - bomb_overlay.pixel_y = 5 - add_overlay(bomb_overlay) - else - icon_state = "pizzabox" - var/current_offset = 3 - for(var/V in boxes) - var/obj/item/pizzabox/P = V - var/mutable_appearance/box_overlay = mutable_appearance(P.icon, P.icon_state) - box_overlay.pixel_y = current_offset - add_overlay(box_overlay) - current_offset += 3 - var/obj/item/pizzabox/box = boxes.len ? boxes[boxes.len] : src - if(box.boxtag != "") - var/mutable_appearance/tag_overlay = mutable_appearance(icon, "pizzabox_tag") - tag_overlay.pixel_y = boxes.len * 3 - add_overlay(tag_overlay) - -/obj/item/pizzabox/worn_overlays(isinhands, icon_file) - . = list() - var/current_offset = 2 - if(isinhands) - for(var/V in boxes) //add EXTRA BOX per box - var/mutable_appearance/M = mutable_appearance(icon_file, item_state) - M.pixel_y = current_offset - current_offset += 2 - . += M - -/obj/item/pizzabox/attack_self(mob/user) - if(boxes.len > 0) - return - open = !open - if(open && !bomb_defused) - audible_message("[icon2html(src, hearers(src))] *beep*") - bomb_active = TRUE - START_PROCESSING(SSobj, src) - update_icon() - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/item/pizzabox/attack_hand(mob/user) - if(user.get_inactive_held_item() != src) - return ..() - if(open) - if(pizza) - user.put_in_hands(pizza) - to_chat(user, "You take [pizza] out of [src].") - pizza = null - update_icon() - else if(bomb) - if(wires.is_all_cut() && bomb_defused) - user.put_in_hands(bomb) - to_chat(user, "You carefully remove the [bomb] from [src].") - bomb = null - update_icon() - return - else - bomb_timer = input(user, "Set the [bomb] timer from [BOMB_TIMER_MIN] to [BOMB_TIMER_MAX].", bomb, bomb_timer) as num|null - - if (isnull(bomb_timer)) - return - - bomb_timer = clamp(CEILING(bomb_timer / 2, 1), BOMB_TIMER_MIN, BOMB_TIMER_MAX) - bomb_defused = FALSE - - log_bomber(user, "has trapped a", src, "with [bomb] set to [bomb_timer * 2] seconds") - bomb.adminlog = "The [bomb.name] in [src.name] that [key_name(user)] activated has detonated!" - - to_chat(user, "You trap [src] with [bomb].") - update_icon() - else if(boxes.len) - var/obj/item/pizzabox/topbox = boxes[boxes.len] - boxes -= topbox - user.put_in_hands(topbox) - to_chat(user, "You remove the topmost [name] from the stack.") - topbox.update_icon() - update_icon() - user.regenerate_icons() - -/obj/item/pizzabox/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/pizzabox)) - var/obj/item/pizzabox/newbox = I - if(!open && !newbox.open) - var/list/add = list() - add += newbox - add += newbox.boxes - if(!user.transferItemToLoc(newbox, src)) - return - boxes += add - newbox.boxes.Cut() - to_chat(user, "You put [newbox] on top of [src]!") - newbox.update_icon() - update_icon() - user.regenerate_icons() - if(boxes.len >= 5) - if(prob(10 * boxes.len)) - to_chat(user, "You can't keep holding the stack!") - disperse_pizzas() - else - to_chat(user, "The stack is getting a little high...") - return - else - to_chat(user, "Close [open ? src : newbox] first!") - else if(istype(I, /obj/item/reagent_containers/food/snacks/pizza) || istype(I, /obj/item/reagent_containers/food/snacks/customizable/pizza)) - if(open) - if(pizza) - to_chat(user, "[src] already has \a [pizza.name]!") - return - if(!user.transferItemToLoc(I, src)) - return - pizza = I - to_chat(user, "You put [I] in [src].") - update_icon() - return - else if(istype(I, /obj/item/bombcore/miniature/pizza)) - if(open && !bomb) - if(!user.transferItemToLoc(I, src)) - return - wires = new /datum/wires/explosive/pizza(src) - bomb = I - to_chat(user, "You put [I] in [src]. Sneeki breeki...") - update_icon() - return - else if(bomb) - to_chat(user, "[src] already has a bomb in it!") - else if(istype(I, /obj/item/pen)) - if(!open) - if(!user.is_literate()) - to_chat(user, "You scribble illegibly on [src]!") - return - var/obj/item/pizzabox/box = boxes.len ? boxes[boxes.len] : src - box.boxtag += stripped_input(user, "Write on [box]'s tag:", box, "", 30) - if(!user.canUseTopic(src, BE_CLOSE)) - return - to_chat(user, "You write with [I] on [src].") - update_icon() - return - else if(is_wire_tool(I)) - if(wires && bomb) - wires.interact(user) - else if(istype(I, /obj/item/reagent_containers/food)) - to_chat(user, "That's not a pizza!") - ..() - -/obj/item/pizzabox/process() - if(bomb_active && !bomb_defused && (bomb_timer > 0)) - playsound(loc, 'sound/items/timer.ogg', 50, FALSE) - bomb_timer-- - if(bomb_active && !bomb_defused && (bomb_timer <= 0)) - if(bomb in src) - bomb.detonate() - unprocess() - qdel(src) - if(!bomb_active || bomb_defused) - if(bomb_defused && (bomb in src)) - bomb.defuse() - bomb_active = FALSE - unprocess() - return - -/obj/item/pizzabox/attack(mob/living/target, mob/living/user, def_zone) - . = ..() - if(boxes.len >= 3 && prob(25 * boxes.len)) - disperse_pizzas() - -/obj/item/pizzabox/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - if(boxes.len >= 2 && prob(20 * boxes.len)) - disperse_pizzas() - -/obj/item/pizzabox/proc/disperse_pizzas() - visible_message("The pizzas fall everywhere!") - for(var/V in boxes) - var/obj/item/pizzabox/P = V - var/fall_dir = pick(GLOB.alldirs) - step(P, fall_dir) - if(P.pizza && P.can_open_on_fall && prob(50)) //rip pizza - P.open = TRUE - P.pizza.forceMove(get_turf(P)) - fall_dir = pick(GLOB.alldirs) - step(P.pizza, fall_dir) - P.pizza = null - P.update_icon() - boxes -= P - update_icon() - if(isliving(loc)) - var/mob/living/L = loc - L.regenerate_icons() - -/obj/item/pizzabox/proc/unprocess() - STOP_PROCESSING(SSobj, src) - qdel(wires) - wires = null - update_icon() - -/obj/item/pizzabox/bomb/Initialize() - . = ..() - var/randompizza = pick(subtypesof(/obj/item/reagent_containers/food/snacks/pizza)) - pizza = new randompizza(src) - bomb = new(src) - wires = new /datum/wires/explosive/pizza(src) - -/obj/item/pizzabox/margherita/Initialize() - . = ..() - AddPizza() - boxtag = "Margherita Deluxe" - -/obj/item/pizzabox/margherita/proc/AddPizza() - pizza = new /obj/item/reagent_containers/food/snacks/pizza/margherita(src) - -/obj/item/pizzabox/margherita/robo/AddPizza() - pizza = new /obj/item/reagent_containers/food/snacks/pizza/margherita/robo(src) - -/obj/item/pizzabox/vegetable/Initialize() - . = ..() - pizza = new /obj/item/reagent_containers/food/snacks/pizza/vegetable(src) - boxtag = "Gourmet Vegatable" - -/obj/item/pizzabox/mushroom/Initialize() - . = ..() - pizza = new /obj/item/reagent_containers/food/snacks/pizza/mushroom(src) - boxtag = "Mushroom Special" - -/obj/item/pizzabox/meat/Initialize() - . = ..() - pizza = new /obj/item/reagent_containers/food/snacks/pizza/meat(src) - boxtag = "Meatlover's Supreme" - -/obj/item/pizzabox/pineapple/Initialize() - . = ..() - pizza = new /obj/item/reagent_containers/food/snacks/pizza/pineapple(src) - boxtag = "Honolulu Chew" - -//An anomalous pizza box that, when opened, produces the opener's favorite kind of pizza. -/obj/item/pizzabox/infinite - resistance_flags = FIRE_PROOF | LAVA_PROOF | ACID_PROOF //hard to destroy - can_open_on_fall = FALSE - var/list/pizza_types = list( - /obj/item/reagent_containers/food/snacks/pizza/meat = 1, - /obj/item/reagent_containers/food/snacks/pizza/mushroom = 1, - /obj/item/reagent_containers/food/snacks/pizza/margherita = 1, - /obj/item/reagent_containers/food/snacks/pizza/sassysage = 0.8, - /obj/item/reagent_containers/food/snacks/pizza/vegetable = 0.8, - /obj/item/reagent_containers/food/snacks/pizza/pineapple = 0.5, - /obj/item/reagent_containers/food/snacks/pizza/donkpocket = 0.3, - /obj/item/reagent_containers/food/snacks/pizza/dank = 0.1) //pizzas here are weighted by chance to be someone's favorite - var/static/list/pizza_preferences - -/obj/item/pizzabox/infinite/Initialize() - . = ..() - if(!pizza_preferences) - pizza_preferences = list() - -/obj/item/pizzabox/infinite/examine(mob/user) - . = ..() - if(isobserver(user)) - . += "This pizza box is anomalous, and will produce infinite pizza." - -/obj/item/pizzabox/infinite/attack_self(mob/living/user) - QDEL_NULL(pizza) - if(ishuman(user)) - attune_pizza(user) - . = ..() - -/obj/item/pizzabox/infinite/proc/attune_pizza(mob/living/carbon/human/noms) //tonight on "proc names I never thought I'd type" - if(!pizza_preferences[noms.ckey]) - pizza_preferences[noms.ckey] = pickweight(pizza_types) - if(noms.has_quirk(/datum/quirk/pineapple_liker)) - pizza_preferences[noms.ckey] = /obj/item/reagent_containers/food/snacks/pizza/pineapple - else if(noms.has_quirk(/datum/quirk/pineapple_hater)) - var/list/pineapple_pizza_liker = pizza_types.Copy() - pineapple_pizza_liker -= /obj/item/reagent_containers/food/snacks/pizza/pineapple - pizza_preferences[noms.ckey] = pickweight(pineapple_pizza_liker) - else if(noms.mind && noms.mind.assigned_role == "Botanist") - pizza_preferences[noms.ckey] = /obj/item/reagent_containers/food/snacks/pizza/dank - - var/obj/item/pizza_type = pizza_preferences[noms.ckey] - pizza = new pizza_type (src) - pizza.foodtype = noms.dna.species.liked_food //it's our favorite! +/obj/item/bombcore/miniature/pizza + name = "pizza bomb" + desc = "Special delivery!" + icon_state = "pizzabomb_inactive" + item_state = "eshield0" + lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi' + +/obj/item/pizzabox + name = "pizza box" + desc = "A box suited for pizzas." + icon = 'icons/obj/food/containers.dmi' + icon_state = "pizzabox" + item_state = "pizzabox" + lefthand_file = 'icons/mob/inhands/misc/food_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/food_righthand.dmi' + + var/open = FALSE + var/can_open_on_fall = TRUE //if FALSE, this pizza box will never open if it falls from a stack + var/boxtag = "" + var/list/boxes = list() + + var/obj/item/reagent_containers/food/snacks/pizza/pizza + + var/obj/item/bombcore/miniature/pizza/bomb + var/bomb_active = FALSE // If the bomb is counting down. + var/bomb_defused = TRUE // If the bomb is inert. + var/bomb_timer = 1 // How long before blowing the bomb. + var/const/BOMB_TIMER_MIN = 1 + var/const/BOMB_TIMER_MAX = 10 + +/obj/item/pizzabox/Initialize() + . = ..() + update_icon() + + +/obj/item/pizzabox/Destroy() + unprocess() + return ..() + +/obj/item/pizzabox/update_icon() + // Description + desc = initial(desc) + if(open) + if(pizza) + desc = "[desc] It appears to have \a [pizza] inside. Use your other hand to take it out." + if(bomb) + desc = "[desc] Wait, what?! It has \a [bomb] inside!" + if(bomb_defused) + desc = "[desc] The bomb seems inert. Use your other hand to activate it." + if(bomb_active) + desc = "[desc] It looks like it's about to go off!" + else + var/obj/item/pizzabox/box = boxes.len ? boxes[boxes.len] : src + if(boxes.len) + desc = "A pile of boxes suited for pizzas. There appear to be [boxes.len + 1] boxes in the pile." + if(box.boxtag != "") + desc = "[desc] The [boxes.len ? "top box" : "box"]'s tag reads: [box.boxtag]" + + // Icon/Overlays + cut_overlays() + if(open) + icon_state = "pizzabox_open" + if(pizza) + icon_state = "pizzabox_messy" + var/mutable_appearance/pizza_overlay = mutable_appearance(pizza.icon, pizza.icon_state) + pizza_overlay.pixel_y = -3 + add_overlay(pizza_overlay) + if(bomb) + bomb.icon_state = "pizzabomb_[bomb_active ? "active" : "inactive"]" + var/mutable_appearance/bomb_overlay = mutable_appearance(bomb.icon, bomb.icon_state) + bomb_overlay.pixel_y = 5 + add_overlay(bomb_overlay) + else + icon_state = "pizzabox" + var/current_offset = 3 + for(var/V in boxes) + var/obj/item/pizzabox/P = V + var/mutable_appearance/box_overlay = mutable_appearance(P.icon, P.icon_state) + box_overlay.pixel_y = current_offset + add_overlay(box_overlay) + current_offset += 3 + var/obj/item/pizzabox/box = boxes.len ? boxes[boxes.len] : src + if(box.boxtag != "") + var/mutable_appearance/tag_overlay = mutable_appearance(icon, "pizzabox_tag") + tag_overlay.pixel_y = boxes.len * 3 + add_overlay(tag_overlay) + +/obj/item/pizzabox/worn_overlays(isinhands, icon_file) + . = list() + var/current_offset = 2 + if(isinhands) + for(var/V in boxes) //add EXTRA BOX per box + var/mutable_appearance/M = mutable_appearance(icon_file, item_state) + M.pixel_y = current_offset + current_offset += 2 + . += M + +/obj/item/pizzabox/attack_self(mob/user) + if(boxes.len > 0) + return + open = !open + if(open && !bomb_defused) + audible_message("[icon2html(src, hearers(src))] *beep*") + bomb_active = TRUE + START_PROCESSING(SSobj, src) + update_icon() + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/pizzabox/attack_hand(mob/user) + if(user.get_inactive_held_item() != src) + return ..() + if(open) + if(pizza) + user.put_in_hands(pizza) + to_chat(user, "You take [pizza] out of [src].") + pizza = null + update_icon() + else if(bomb) + if(wires.is_all_cut() && bomb_defused) + user.put_in_hands(bomb) + to_chat(user, "You carefully remove the [bomb] from [src].") + bomb = null + update_icon() + return + else + bomb_timer = input(user, "Set the [bomb] timer from [BOMB_TIMER_MIN] to [BOMB_TIMER_MAX].", bomb, bomb_timer) as num|null + + if (isnull(bomb_timer)) + return + + bomb_timer = clamp(CEILING(bomb_timer / 2, 1), BOMB_TIMER_MIN, BOMB_TIMER_MAX) + bomb_defused = FALSE + + log_bomber(user, "has trapped a", src, "with [bomb] set to [bomb_timer * 2] seconds") + bomb.adminlog = "The [bomb.name] in [src.name] that [key_name(user)] activated has detonated!" + + to_chat(user, "You trap [src] with [bomb].") + update_icon() + else if(boxes.len) + var/obj/item/pizzabox/topbox = boxes[boxes.len] + boxes -= topbox + user.put_in_hands(topbox) + to_chat(user, "You remove the topmost [name] from the stack.") + topbox.update_icon() + update_icon() + user.regenerate_icons() + +/obj/item/pizzabox/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/pizzabox)) + var/obj/item/pizzabox/newbox = I + if(!open && !newbox.open) + var/list/add = list() + add += newbox + add += newbox.boxes + if(!user.transferItemToLoc(newbox, src)) + return + boxes += add + newbox.boxes.Cut() + to_chat(user, "You put [newbox] on top of [src]!") + newbox.update_icon() + update_icon() + user.regenerate_icons() + if(boxes.len >= 5) + if(prob(10 * boxes.len)) + to_chat(user, "You can't keep holding the stack!") + disperse_pizzas() + else + to_chat(user, "The stack is getting a little high...") + return + else + to_chat(user, "Close [open ? src : newbox] first!") + else if(istype(I, /obj/item/reagent_containers/food/snacks/pizza) || istype(I, /obj/item/reagent_containers/food/snacks/customizable/pizza)) + if(open) + if(pizza) + to_chat(user, "[src] already has \a [pizza.name]!") + return + if(!user.transferItemToLoc(I, src)) + return + pizza = I + to_chat(user, "You put [I] in [src].") + update_icon() + return + else if(istype(I, /obj/item/bombcore/miniature/pizza)) + if(open && !bomb) + if(!user.transferItemToLoc(I, src)) + return + wires = new /datum/wires/explosive/pizza(src) + bomb = I + to_chat(user, "You put [I] in [src]. Sneeki breeki...") + update_icon() + return + else if(bomb) + to_chat(user, "[src] already has a bomb in it!") + else if(istype(I, /obj/item/pen)) + if(!open) + if(!user.is_literate()) + to_chat(user, "You scribble illegibly on [src]!") + return + var/obj/item/pizzabox/box = boxes.len ? boxes[boxes.len] : src + box.boxtag += stripped_input(user, "Write on [box]'s tag:", box, "", 30) + if(!user.canUseTopic(src, BE_CLOSE)) + return + to_chat(user, "You write with [I] on [src].") + update_icon() + return + else if(is_wire_tool(I)) + if(wires && bomb) + wires.interact(user) + else if(istype(I, /obj/item/reagent_containers/food)) + to_chat(user, "That's not a pizza!") + ..() + +/obj/item/pizzabox/process() + if(bomb_active && !bomb_defused && (bomb_timer > 0)) + playsound(loc, 'sound/items/timer.ogg', 50, FALSE) + bomb_timer-- + if(bomb_active && !bomb_defused && (bomb_timer <= 0)) + if(bomb in src) + bomb.detonate() + unprocess() + qdel(src) + if(!bomb_active || bomb_defused) + if(bomb_defused && (bomb in src)) + bomb.defuse() + bomb_active = FALSE + unprocess() + return + +/obj/item/pizzabox/attack(mob/living/target, mob/living/user, def_zone) + . = ..() + if(boxes.len >= 3 && prob(25 * boxes.len)) + disperse_pizzas() + +/obj/item/pizzabox/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + if(boxes.len >= 2 && prob(20 * boxes.len)) + disperse_pizzas() + +/obj/item/pizzabox/proc/disperse_pizzas() + visible_message("The pizzas fall everywhere!") + for(var/V in boxes) + var/obj/item/pizzabox/P = V + var/fall_dir = pick(GLOB.alldirs) + step(P, fall_dir) + if(P.pizza && P.can_open_on_fall && prob(50)) //rip pizza + P.open = TRUE + P.pizza.forceMove(get_turf(P)) + fall_dir = pick(GLOB.alldirs) + step(P.pizza, fall_dir) + P.pizza = null + P.update_icon() + boxes -= P + update_icon() + if(isliving(loc)) + var/mob/living/L = loc + L.regenerate_icons() + +/obj/item/pizzabox/proc/unprocess() + STOP_PROCESSING(SSobj, src) + qdel(wires) + wires = null + update_icon() + +/obj/item/pizzabox/bomb/Initialize() + . = ..() + var/randompizza = pick(subtypesof(/obj/item/reagent_containers/food/snacks/pizza)) + pizza = new randompizza(src) + bomb = new(src) + wires = new /datum/wires/explosive/pizza(src) + +/obj/item/pizzabox/margherita/Initialize() + . = ..() + AddPizza() + boxtag = "Margherita Deluxe" + +/obj/item/pizzabox/margherita/proc/AddPizza() + pizza = new /obj/item/reagent_containers/food/snacks/pizza/margherita(src) + +/obj/item/pizzabox/margherita/robo/AddPizza() + pizza = new /obj/item/reagent_containers/food/snacks/pizza/margherita/robo(src) + +/obj/item/pizzabox/vegetable/Initialize() + . = ..() + pizza = new /obj/item/reagent_containers/food/snacks/pizza/vegetable(src) + boxtag = "Gourmet Vegatable" + +/obj/item/pizzabox/mushroom/Initialize() + . = ..() + pizza = new /obj/item/reagent_containers/food/snacks/pizza/mushroom(src) + boxtag = "Mushroom Special" + +/obj/item/pizzabox/meat/Initialize() + . = ..() + pizza = new /obj/item/reagent_containers/food/snacks/pizza/meat(src) + boxtag = "Meatlover's Supreme" + +/obj/item/pizzabox/pineapple/Initialize() + . = ..() + pizza = new /obj/item/reagent_containers/food/snacks/pizza/pineapple(src) + boxtag = "Honolulu Chew" + +//An anomalous pizza box that, when opened, produces the opener's favorite kind of pizza. +/obj/item/pizzabox/infinite + resistance_flags = FIRE_PROOF | LAVA_PROOF | ACID_PROOF //hard to destroy + can_open_on_fall = FALSE + var/list/pizza_types = list( + /obj/item/reagent_containers/food/snacks/pizza/meat = 1, + /obj/item/reagent_containers/food/snacks/pizza/mushroom = 1, + /obj/item/reagent_containers/food/snacks/pizza/margherita = 1, + /obj/item/reagent_containers/food/snacks/pizza/sassysage = 0.8, + /obj/item/reagent_containers/food/snacks/pizza/vegetable = 0.8, + /obj/item/reagent_containers/food/snacks/pizza/pineapple = 0.5, + /obj/item/reagent_containers/food/snacks/pizza/donkpocket = 0.3, + /obj/item/reagent_containers/food/snacks/pizza/dank = 0.1) //pizzas here are weighted by chance to be someone's favorite + var/static/list/pizza_preferences + +/obj/item/pizzabox/infinite/Initialize() + . = ..() + if(!pizza_preferences) + pizza_preferences = list() + +/obj/item/pizzabox/infinite/examine(mob/user) + . = ..() + if(isobserver(user)) + . += "This pizza box is anomalous, and will produce infinite pizza." + +/obj/item/pizzabox/infinite/attack_self(mob/living/user) + QDEL_NULL(pizza) + if(ishuman(user)) + attune_pizza(user) + . = ..() + +/obj/item/pizzabox/infinite/proc/attune_pizza(mob/living/carbon/human/noms) //tonight on "proc names I never thought I'd type" + if(!pizza_preferences[noms.ckey]) + pizza_preferences[noms.ckey] = pickweight(pizza_types) + if(noms.has_quirk(/datum/quirk/pineapple_liker)) + pizza_preferences[noms.ckey] = /obj/item/reagent_containers/food/snacks/pizza/pineapple + else if(noms.has_quirk(/datum/quirk/pineapple_hater)) + var/list/pineapple_pizza_liker = pizza_types.Copy() + pineapple_pizza_liker -= /obj/item/reagent_containers/food/snacks/pizza/pineapple + pizza_preferences[noms.ckey] = pickweight(pineapple_pizza_liker) + else if(noms.mind && noms.mind.assigned_role == "Botanist") + pizza_preferences[noms.ckey] = /obj/item/reagent_containers/food/snacks/pizza/dank + + var/obj/item/pizza_type = pizza_preferences[noms.ckey] + pizza = new pizza_type (src) + pizza.foodtype = noms.dna.species.liked_food //it's our favorite! diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_drink.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_drink.dm index b97b88ca4385..f5ad5779bac1 100644 --- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_drink.dm +++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_drink.dm @@ -1,155 +1,155 @@ - -// This is the home of drink related tablecrafting recipes, I have opted to only let players bottle fancy boozes to reduce the number of entries. - -///////////////// Booze & Bottles /////////////////// - -/datum/crafting_recipe/lizardwine - name = "lizard Wine" - time = 40 - reqs = list( - /obj/item/organ/tail/lizard = 1, - /datum/reagent/consumable/ethanol = 100 - ) - result = /obj/item/reagent_containers/food/drinks/bottle/lizardwine - category = CAT_DRINK - -/datum/crafting_recipe/moonshinejug - name = "Moonshine Jug" - time = 30 - reqs = list( - /obj/item/reagent_containers/food/drinks/bottle = 1, - /datum/reagent/consumable/ethanol/moonshine = 100 - ) - result = /obj/item/reagent_containers/food/drinks/bottle/moonshine - category = CAT_DRINK - -/datum/crafting_recipe/hoochbottle - name = "Hooch Bottle" - time = 30 - reqs = list( - /obj/item/reagent_containers/food/drinks/bottle = 1, - /obj/item/storage/box/papersack = 1, - /datum/reagent/consumable/ethanol/hooch = 100 - ) - result = /obj/item/reagent_containers/food/drinks/bottle/hooch - category = CAT_DRINK - -/datum/crafting_recipe/blazaambottle - name = "Blazaam Bottle" - time = 20 - reqs = list( - /obj/item/reagent_containers/food/drinks/bottle = 1, - /datum/reagent/consumable/ethanol/blazaam = 100 - ) - result = /obj/item/reagent_containers/food/drinks/bottle/blazaam - category = CAT_DRINK - -/datum/crafting_recipe/champagnebottle - name = "Champagne Bottle" - time = 30 - reqs = list( - /obj/item/reagent_containers/food/drinks/bottle = 1, - /datum/reagent/consumable/ethanol/champagne = 100 - ) - result = /obj/item/reagent_containers/food/drinks/bottle/champagne - category = CAT_DRINK - -/datum/crafting_recipe/trappistbottle - name = "Trappist Bottle" - time = 15 - reqs = list( - /obj/item/reagent_containers/food/drinks/bottle/small = 1, - /datum/reagent/consumable/ethanol/trappist = 50 - ) - result = /obj/item/reagent_containers/food/drinks/bottle/trappist - category = CAT_DRINK - -/datum/crafting_recipe/goldschlagerbottle - name = "Goldschlager Bottle" - time = 30 - reqs = list( - /obj/item/reagent_containers/food/drinks/bottle = 1, - /datum/reagent/consumable/ethanol/goldschlager = 100 - ) - result = /obj/item/reagent_containers/food/drinks/bottle/goldschlager - category = CAT_DRINK - -/datum/crafting_recipe/patronbottle - name = "Patron Bottle" - time = 30 - reqs = list( - /obj/item/reagent_containers/food/drinks/bottle = 1, - /datum/reagent/consumable/ethanol/patron = 100 - ) - result = /obj/item/reagent_containers/food/drinks/bottle/patron - category = CAT_DRINK - -////////////////////// Non-alcoholic recipes /////////////////// - -/datum/crafting_recipe/holybottle - name = "Holy Water Flask" - time = 30 - reqs = list( - /obj/item/reagent_containers/food/drinks/bottle = 1, - /datum/reagent/water/holywater = 100 - ) - result = /obj/item/reagent_containers/food/drinks/bottle/holywater - category = CAT_DRINK - -//flask of unholy water is a beaker for some reason, I will try making it a bottle and add it here once the antag freeze is over. t. kryson - -/datum/crafting_recipe/nothingbottle - name = "Nothing Bottle" - time = 30 - reqs = list( - /obj/item/reagent_containers/food/drinks/bottle = 1, - /datum/reagent/consumable/nothing = 100 - ) - result = /obj/item/reagent_containers/food/drinks/bottle/bottleofnothing - category = CAT_DRINK - -/datum/crafting_recipe/smallcarton - name = "Small Carton" - result = /obj/item/reagent_containers/food/drinks/sillycup/smallcarton - time = 10 - reqs = list(/obj/item/stack/sheet/cardboard = 1) - category = CAT_DRINK - -/datum/crafting_recipe/candycornliquor - name = "candy corn liquor" - result = /obj/item/reagent_containers/food/drinks/bottle/candycornliquor - time = 30 - reqs = list(/datum/reagent/consumable/ethanol/whiskey = 100, - /obj/item/reagent_containers/food/snacks/candy_corn = 1, - /obj/item/reagent_containers/food/drinks/bottle = 1) - category = CAT_DRINK - -/datum/crafting_recipe/kong - name = "Kong" - result = /obj/item/reagent_containers/food/drinks/bottle/kong - time = 30 - reqs = list(/datum/reagent/consumable/ethanol/whiskey = 100, - /obj/item/reagent_containers/food/snacks/monkeycube = 1, - /obj/item/reagent_containers/food/drinks/bottle = 1) - category = CAT_DRINK - -/datum/crafting_recipe/pruno - name = "pruno mix" - result = /obj/item/reagent_containers/food/drinks/bottle/pruno - time = 30 - reqs = list(/obj/item/storage/bag/trash = 1, - /obj/item/reagent_containers/food/snacks/breadslice/moldy = 1, - /obj/item/reagent_containers/food/snacks/grown = 4, - /obj/item/reagent_containers/food/snacks/candy_corn = 2, - /datum/reagent/water = 15) - category = CAT_DRINK - -/datum/crafting_recipe/lean - name = "lean" - result = /obj/item/reagent_containers/food/drinks/colocup/lean - time = 30 - reqs = list(/obj/item/reagent_containers/food/drinks/colocup = 1, - /obj/item/reagent_containers/food/snacks/gumball = 2, - /datum/reagent/medicine/morphine = 5, - /datum/reagent/consumable/space_up = 15) - category = CAT_DRINK + +// This is the home of drink related tablecrafting recipes, I have opted to only let players bottle fancy boozes to reduce the number of entries. + +///////////////// Booze & Bottles /////////////////// + +/datum/crafting_recipe/lizardwine + name = "lizard Wine" + time = 40 + reqs = list( + /obj/item/organ/tail/lizard = 1, + /datum/reagent/consumable/ethanol = 100 + ) + result = /obj/item/reagent_containers/food/drinks/bottle/lizardwine + category = CAT_DRINK + +/datum/crafting_recipe/moonshinejug + name = "Moonshine Jug" + time = 30 + reqs = list( + /obj/item/reagent_containers/food/drinks/bottle = 1, + /datum/reagent/consumable/ethanol/moonshine = 100 + ) + result = /obj/item/reagent_containers/food/drinks/bottle/moonshine + category = CAT_DRINK + +/datum/crafting_recipe/hoochbottle + name = "Hooch Bottle" + time = 30 + reqs = list( + /obj/item/reagent_containers/food/drinks/bottle = 1, + /obj/item/storage/box/papersack = 1, + /datum/reagent/consumable/ethanol/hooch = 100 + ) + result = /obj/item/reagent_containers/food/drinks/bottle/hooch + category = CAT_DRINK + +/datum/crafting_recipe/blazaambottle + name = "Blazaam Bottle" + time = 20 + reqs = list( + /obj/item/reagent_containers/food/drinks/bottle = 1, + /datum/reagent/consumable/ethanol/blazaam = 100 + ) + result = /obj/item/reagent_containers/food/drinks/bottle/blazaam + category = CAT_DRINK + +/datum/crafting_recipe/champagnebottle + name = "Champagne Bottle" + time = 30 + reqs = list( + /obj/item/reagent_containers/food/drinks/bottle = 1, + /datum/reagent/consumable/ethanol/champagne = 100 + ) + result = /obj/item/reagent_containers/food/drinks/bottle/champagne + category = CAT_DRINK + +/datum/crafting_recipe/trappistbottle + name = "Trappist Bottle" + time = 15 + reqs = list( + /obj/item/reagent_containers/food/drinks/bottle/small = 1, + /datum/reagent/consumable/ethanol/trappist = 50 + ) + result = /obj/item/reagent_containers/food/drinks/bottle/trappist + category = CAT_DRINK + +/datum/crafting_recipe/goldschlagerbottle + name = "Goldschlager Bottle" + time = 30 + reqs = list( + /obj/item/reagent_containers/food/drinks/bottle = 1, + /datum/reagent/consumable/ethanol/goldschlager = 100 + ) + result = /obj/item/reagent_containers/food/drinks/bottle/goldschlager + category = CAT_DRINK + +/datum/crafting_recipe/patronbottle + name = "Patron Bottle" + time = 30 + reqs = list( + /obj/item/reagent_containers/food/drinks/bottle = 1, + /datum/reagent/consumable/ethanol/patron = 100 + ) + result = /obj/item/reagent_containers/food/drinks/bottle/patron + category = CAT_DRINK + +////////////////////// Non-alcoholic recipes /////////////////// + +/datum/crafting_recipe/holybottle + name = "Holy Water Flask" + time = 30 + reqs = list( + /obj/item/reagent_containers/food/drinks/bottle = 1, + /datum/reagent/water/holywater = 100 + ) + result = /obj/item/reagent_containers/food/drinks/bottle/holywater + category = CAT_DRINK + +//flask of unholy water is a beaker for some reason, I will try making it a bottle and add it here once the antag freeze is over. t. kryson + +/datum/crafting_recipe/nothingbottle + name = "Nothing Bottle" + time = 30 + reqs = list( + /obj/item/reagent_containers/food/drinks/bottle = 1, + /datum/reagent/consumable/nothing = 100 + ) + result = /obj/item/reagent_containers/food/drinks/bottle/bottleofnothing + category = CAT_DRINK + +/datum/crafting_recipe/smallcarton + name = "Small Carton" + result = /obj/item/reagent_containers/food/drinks/sillycup/smallcarton + time = 10 + reqs = list(/obj/item/stack/sheet/cardboard = 1) + category = CAT_DRINK + +/datum/crafting_recipe/candycornliquor + name = "candy corn liquor" + result = /obj/item/reagent_containers/food/drinks/bottle/candycornliquor + time = 30 + reqs = list(/datum/reagent/consumable/ethanol/whiskey = 100, + /obj/item/reagent_containers/food/snacks/candy_corn = 1, + /obj/item/reagent_containers/food/drinks/bottle = 1) + category = CAT_DRINK + +/datum/crafting_recipe/kong + name = "Kong" + result = /obj/item/reagent_containers/food/drinks/bottle/kong + time = 30 + reqs = list(/datum/reagent/consumable/ethanol/whiskey = 100, + /obj/item/reagent_containers/food/snacks/monkeycube = 1, + /obj/item/reagent_containers/food/drinks/bottle = 1) + category = CAT_DRINK + +/datum/crafting_recipe/pruno + name = "pruno mix" + result = /obj/item/reagent_containers/food/drinks/bottle/pruno + time = 30 + reqs = list(/obj/item/storage/bag/trash = 1, + /obj/item/reagent_containers/food/snacks/breadslice/moldy = 1, + /obj/item/reagent_containers/food/snacks/grown = 4, + /obj/item/reagent_containers/food/snacks/candy_corn = 2, + /datum/reagent/water = 15) + category = CAT_DRINK + +/datum/crafting_recipe/lean + name = "lean" + result = /obj/item/reagent_containers/food/drinks/colocup/lean + time = 30 + reqs = list(/obj/item/reagent_containers/food/drinks/colocup = 1, + /obj/item/reagent_containers/food/snacks/gumball = 2, + /datum/reagent/medicine/morphine = 5, + /datum/reagent/consumable/space_up = 15) + category = CAT_DRINK diff --git a/code/modules/holodeck/computer.dm b/code/modules/holodeck/computer.dm index 4b3343a84ff5..b45dadd74d39 100644 --- a/code/modules/holodeck/computer.dm +++ b/code/modules/holodeck/computer.dm @@ -24,8 +24,6 @@ icon_screen = "holocontrol" idle_power_usage = 10 active_power_usage = 50 - ui_x = 400 - ui_y = 500 var/area/holodeck/linked var/area/holodeck/program @@ -87,10 +85,10 @@ . = ..() toggle_power(!machine_stat) -/obj/machinery/computer/holodeck/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/machinery/computer/holodeck/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "Holodeck", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, "Holodeck", name) ui.open() /obj/machinery/computer/holodeck/ui_data(mob/user) diff --git a/code/modules/holodeck/holo_effect.dm b/code/modules/holodeck/holo_effect.dm index 8923def4221c..7d39ad8fa96d 100644 --- a/code/modules/holodeck/holo_effect.dm +++ b/code/modules/holodeck/holo_effect.dm @@ -1,111 +1,111 @@ -/* - The holodeck activates these shortly after the program loads, - and deactivates them immediately before changing or disabling the holodeck. - - These remove snowflake code for special holodeck functions. -*/ -/obj/effect/holodeck_effect - icon = 'icons/mob/screen_gen.dmi' - icon_state = "x2" - invisibility = INVISIBILITY_ABSTRACT - -/obj/effect/holodeck_effect/proc/activate(var/obj/machinery/computer/holodeck/HC) - return - -/obj/effect/holodeck_effect/proc/deactivate(var/obj/machinery/computer/holodeck/HC) - qdel(src) - return - -// Called by the holodeck computer as long as the program is running -/obj/effect/holodeck_effect/proc/tick(var/obj/machinery/computer/holodeck/HC) - return - -/obj/effect/holodeck_effect/proc/safety(var/active) - return - - -// Generates a holodeck-tracked card deck -/obj/effect/holodeck_effect/cards - icon = 'icons/obj/toy.dmi' - icon_state = "deck_nanotrasen_full" - var/obj/item/toy/cards/deck/D - -/obj/effect/holodeck_effect/cards/activate(var/obj/machinery/computer/holodeck/HC) - D = new(loc) - safety(!(HC.obj_flags & EMAGGED)) - D.holo = HC - return D - -/obj/effect/holodeck_effect/cards/safety(active) - if(!D) - return - if(active) - D.card_hitsound = null - D.card_force = 0 - D.card_throwforce = 0 - D.card_throw_speed = 3 - D.card_throw_range = 7 - D.card_attack_verb = list("attacked") - else - D.card_hitsound = 'sound/weapons/bladeslice.ogg' - D.card_force = 5 - D.card_throwforce = 10 - D.card_throw_speed = 3 - D.card_throw_range = 7 - D.card_attack_verb = list("attacked", "sliced", "diced", "slashed", "cut") - - -/obj/effect/holodeck_effect/sparks/activate(var/obj/machinery/computer/holodeck/HC) - var/turf/T = get_turf(src) - if(T) - var/datum/effect_system/spark_spread/s = new - s.set_up(3, 1, T) - s.start() - T.temperature = 5000 - T.hotspot_expose(50000,50000,1) - - - -/obj/effect/holodeck_effect/mobspawner - var/mobtype = /mob/living/simple_animal/hostile/carp/holocarp - var/mob/mob = null - -/obj/effect/holodeck_effect/mobspawner/activate(var/obj/machinery/computer/holodeck/HC) - if(islist(mobtype)) - mobtype = pick(mobtype) - mob = new mobtype(loc) - mob.flags_1 |= HOLOGRAM_1 - - // these vars are not really standardized but all would theoretically create stuff on death - for(var/v in list("butcher_results","corpse","weapon1","weapon2","blood_volume") & mob.vars) - mob.vars[v] = null - return mob - -/obj/effect/holodeck_effect/mobspawner/deactivate(var/obj/machinery/computer/holodeck/HC) - if(mob) - HC.derez(mob) - qdel(src) - -/obj/effect/holodeck_effect/mobspawner/pet - mobtype = list( - /mob/living/simple_animal/butterfly, /mob/living/simple_animal/chick/holo, - /mob/living/simple_animal/pet/cat, /mob/living/simple_animal/pet/cat/kitten, - /mob/living/simple_animal/pet/dog/corgi, /mob/living/simple_animal/pet/dog/corgi/puppy, - /mob/living/simple_animal/pet/dog/pug, /mob/living/simple_animal/pet/fox) - -/obj/effect/holodeck_effect/mobspawner/bee - mobtype = /mob/living/simple_animal/hostile/poison/bees/toxin - -/obj/effect/holodeck_effect/mobspawner/monkey - mobtype = /mob/living/simple_animal/holodeck_monkey - -/obj/effect/holodeck_effect/mobspawner/penguin - mobtype = /mob/living/simple_animal/pet/penguin/emperor - -/obj/effect/holodeck_effect/mobspawner/penguin/Initialize() - if(prob(1)) - mobtype = /mob/living/simple_animal/pet/penguin/emperor/shamebrero - return ..() - -/obj/effect/holodeck_effect/mobspawner/penguin_baby - mobtype = /mob/living/simple_animal/pet/penguin/baby +/* + The holodeck activates these shortly after the program loads, + and deactivates them immediately before changing or disabling the holodeck. + + These remove snowflake code for special holodeck functions. +*/ +/obj/effect/holodeck_effect + icon = 'icons/mob/screen_gen.dmi' + icon_state = "x2" + invisibility = INVISIBILITY_ABSTRACT + +/obj/effect/holodeck_effect/proc/activate(var/obj/machinery/computer/holodeck/HC) + return + +/obj/effect/holodeck_effect/proc/deactivate(var/obj/machinery/computer/holodeck/HC) + qdel(src) + return + +// Called by the holodeck computer as long as the program is running +/obj/effect/holodeck_effect/proc/tick(var/obj/machinery/computer/holodeck/HC) + return + +/obj/effect/holodeck_effect/proc/safety(var/active) + return + + +// Generates a holodeck-tracked card deck +/obj/effect/holodeck_effect/cards + icon = 'icons/obj/toy.dmi' + icon_state = "deck_nanotrasen_full" + var/obj/item/toy/cards/deck/D + +/obj/effect/holodeck_effect/cards/activate(var/obj/machinery/computer/holodeck/HC) + D = new(loc) + safety(!(HC.obj_flags & EMAGGED)) + D.holo = HC + return D + +/obj/effect/holodeck_effect/cards/safety(active) + if(!D) + return + if(active) + D.card_hitsound = null + D.card_force = 0 + D.card_throwforce = 0 + D.card_throw_speed = 3 + D.card_throw_range = 7 + D.card_attack_verb = list("attacked") + else + D.card_hitsound = 'sound/weapons/bladeslice.ogg' + D.card_force = 5 + D.card_throwforce = 10 + D.card_throw_speed = 3 + D.card_throw_range = 7 + D.card_attack_verb = list("attacked", "sliced", "diced", "slashed", "cut") + + +/obj/effect/holodeck_effect/sparks/activate(var/obj/machinery/computer/holodeck/HC) + var/turf/T = get_turf(src) + if(T) + var/datum/effect_system/spark_spread/s = new + s.set_up(3, 1, T) + s.start() + T.temperature = 5000 + T.hotspot_expose(50000,50000,1) + + + +/obj/effect/holodeck_effect/mobspawner + var/mobtype = /mob/living/simple_animal/hostile/carp/holocarp + var/mob/mob = null + +/obj/effect/holodeck_effect/mobspawner/activate(var/obj/machinery/computer/holodeck/HC) + if(islist(mobtype)) + mobtype = pick(mobtype) + mob = new mobtype(loc) + mob.flags_1 |= HOLOGRAM_1 + + // these vars are not really standardized but all would theoretically create stuff on death + for(var/v in list("butcher_results","corpse","weapon1","weapon2","blood_volume") & mob.vars) + mob.vars[v] = null + return mob + +/obj/effect/holodeck_effect/mobspawner/deactivate(var/obj/machinery/computer/holodeck/HC) + if(mob) + HC.derez(mob) + qdel(src) + +/obj/effect/holodeck_effect/mobspawner/pet + mobtype = list( + /mob/living/simple_animal/butterfly, /mob/living/simple_animal/chick/holo, + /mob/living/simple_animal/pet/cat, /mob/living/simple_animal/pet/cat/kitten, + /mob/living/simple_animal/pet/dog/corgi, /mob/living/simple_animal/pet/dog/corgi/puppy, + /mob/living/simple_animal/pet/dog/pug, /mob/living/simple_animal/pet/fox) + +/obj/effect/holodeck_effect/mobspawner/bee + mobtype = /mob/living/simple_animal/hostile/poison/bees/toxin + +/obj/effect/holodeck_effect/mobspawner/monkey + mobtype = /mob/living/simple_animal/holodeck_monkey + +/obj/effect/holodeck_effect/mobspawner/penguin + mobtype = /mob/living/simple_animal/pet/penguin/emperor + +/obj/effect/holodeck_effect/mobspawner/penguin/Initialize() + if(prob(1)) + mobtype = /mob/living/simple_animal/pet/penguin/emperor/shamebrero + return ..() + +/obj/effect/holodeck_effect/mobspawner/penguin_baby + mobtype = /mob/living/simple_animal/pet/penguin/baby diff --git a/code/modules/holodeck/items.dm b/code/modules/holodeck/items.dm index 6d681c3dc323..62791616b7fa 100644 --- a/code/modules/holodeck/items.dm +++ b/code/modules/holodeck/items.dm @@ -1,229 +1,229 @@ -/* - Items, Structures, Machines -*/ - - -// -// Items -// - -/obj/item/holo - damtype = STAMINA - -/obj/item/holo/esword - name = "holographic energy sword" - desc = "May the force be with you. Sorta." - icon = 'icons/obj/transforming_energy.dmi' - icon_state = "sword0" - lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' - force = 3.0 - throw_speed = 2 - throw_range = 5 - throwforce = 0 - w_class = WEIGHT_CLASS_SMALL - hitsound = "swing_hit" - armour_penetration = 50 - var/active = 0 - var/saber_color - -/obj/item/holo/esword/green/Initialize() - . = ..() - saber_color = "green" - -/obj/item/holo/esword/red/Initialize() - . = ..() - saber_color = "red" - -/obj/item/holo/esword/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(active) - return ..() - return 0 - -/obj/item/holo/esword/attack(target as mob, mob/user as mob) - ..() - -/obj/item/holo/esword/Initialize() - . = ..() - saber_color = pick("red","blue","green","purple") - -/obj/item/holo/esword/attack_self(mob/living/user as mob) - active = !active - if (active) - force = 30 - icon_state = "sword[saber_color]" - w_class = WEIGHT_CLASS_BULKY - hitsound = 'sound/weapons/blade1.ogg' - playsound(user, 'sound/weapons/saberon.ogg', 20, TRUE) - to_chat(user, "[src] is now active.") - else - force = 3 - icon_state = "sword0" - w_class = WEIGHT_CLASS_SMALL - hitsound = "swing_hit" - playsound(user, 'sound/weapons/saberoff.ogg', 20, TRUE) - to_chat(user, "[src] can now be concealed.") - return - -//BASKETBALL OBJECTS - -/obj/item/toy/beach_ball/holoball - name = "basketball" - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "basketball" - item_state = "basketball" - desc = "Here's your chance, do your dance at the Space Jam." - w_class = WEIGHT_CLASS_BULKY //Stops people from hiding it in their bags/pockets - -/obj/item/toy/beach_ball/holoball/dodgeball - name = "dodgeball" - icon_state = "dodgeball" - item_state = "dodgeball" - desc = "Used for playing the most violent and degrading of childhood games." - -/obj/item/toy/beach_ball/holoball/dodgeball/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - ..() - if((ishuman(hit_atom))) - var/mob/living/carbon/M = hit_atom - playsound(src, 'sound/items/dodgeball.ogg', 50, TRUE) - M.apply_damage(10, STAMINA) - if(prob(5)) - M.Paralyze(60) - visible_message("[M] is knocked right off [M.p_their()] feet!") - -// -// Structures -// - -/obj/structure/holohoop - name = "basketball hoop" - desc = "Boom, shakalaka!" - icon = 'icons/obj/basketball.dmi' - icon_state = "hoop" - anchored = TRUE - density = TRUE - -/obj/structure/holohoop/attackby(obj/item/W as obj, mob/user as mob, params) - if(get_dist(src,user)<2) - if(user.transferItemToLoc(W, drop_location())) - visible_message("[user] dunks [W] into \the [src]!") - -/obj/structure/holohoop/attack_hand(mob/user) - . = ..() - if(.) - return - if(user.pulling && user.a_intent == INTENT_GRAB && isliving(user.pulling)) - var/mob/living/L = user.pulling - if(user.grab_state < GRAB_AGGRESSIVE) - to_chat(user, "You need a better grip to do that!") - return - L.forceMove(loc) - L.Paralyze(100) - visible_message("[user] dunks [L] into \the [src]!") - user.stop_pulling() - else - ..() - -/obj/structure/holohoop/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) - if (isitem(AM) && !istype(AM,/obj/projectile)) - if(prob(50)) - AM.forceMove(get_turf(src)) - visible_message("Swish! [AM] lands in [src].") - return - else - visible_message("[AM] bounces off of [src]'s rim!") - return ..() - else - return ..() - - - -// -// Machines -// - -/obj/machinery/readybutton - name = "ready declaration device" - desc = "This device is used to declare ready. If all devices in an area are ready, the event will begin!" - icon = 'icons/obj/monitors.dmi' - icon_state = "auth_off" - var/ready = 0 - var/area/currentarea = null - var/eventstarted = FALSE - - use_power = IDLE_POWER_USE - idle_power_usage = 2 - active_power_usage = 6 - power_channel = AREA_USAGE_ENVIRON - -/obj/machinery/readybutton/attack_ai(mob/user as mob) - to_chat(user, "The station AI is not to interact with these devices!") - return - -/obj/machinery/readybutton/attack_paw(mob/user as mob) - to_chat(user, "You are too primitive to use this device!") - return - -/obj/machinery/readybutton/attackby(obj/item/W as obj, mob/user as mob, params) - to_chat(user, "The device is a solid button, there's nothing you can do with it!") - -/obj/machinery/readybutton/attack_hand(mob/user as mob) - . = ..() - if(.) - return - if(user.stat || machine_stat & (NOPOWER|BROKEN)) - to_chat(user, "This device is not powered!") - return - - currentarea = get_area(src.loc) - if(!currentarea) - qdel(src) - - if(eventstarted) - to_chat(usr, "The event has already begun!") - return - - ready = !ready - - update_icon() - - var/numbuttons = 0 - var/numready = 0 - for(var/obj/machinery/readybutton/button in currentarea) - numbuttons++ - if (button.ready) - numready++ - - if(numbuttons == numready) - begin_event() - -/obj/machinery/readybutton/update_icon_state() - if(ready) - icon_state = "auth_on" - else - icon_state = "auth_off" - -/obj/machinery/readybutton/proc/begin_event() - - eventstarted = TRUE - - for(var/obj/structure/window/W in currentarea) - if(W.flags_1&NODECONSTRUCT_1) // Just in case: only holo-windows - qdel(W) - - for(var/mob/M in currentarea) - to_chat(M, "FIGHT!") - -/obj/machinery/conveyor/holodeck - -/obj/machinery/conveyor/holodeck/attackby(obj/item/I, mob/user, params) - if(!user.transferItemToLoc(I, drop_location())) - return ..() - -/obj/item/paper/fluff/holodeck/trek_diploma - name = "paper - Starfleet Academy Diploma" - info = {"

                    Starfleet Academy


                    Official Diploma


                    "} - -/obj/item/paper/fluff/holodeck/disclaimer - name = "Holodeck Disclaimer" - info = "Bruises sustained in the holodeck can be healed simply by sleeping." +/* + Items, Structures, Machines +*/ + + +// +// Items +// + +/obj/item/holo + damtype = STAMINA + +/obj/item/holo/esword + name = "holographic energy sword" + desc = "May the force be with you. Sorta." + icon = 'icons/obj/transforming_energy.dmi' + icon_state = "sword0" + lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' + force = 3.0 + throw_speed = 2 + throw_range = 5 + throwforce = 0 + w_class = WEIGHT_CLASS_SMALL + hitsound = "swing_hit" + armour_penetration = 50 + var/active = 0 + var/saber_color + +/obj/item/holo/esword/green/Initialize() + . = ..() + saber_color = "green" + +/obj/item/holo/esword/red/Initialize() + . = ..() + saber_color = "red" + +/obj/item/holo/esword/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + if(active) + return ..() + return 0 + +/obj/item/holo/esword/attack(target as mob, mob/user as mob) + ..() + +/obj/item/holo/esword/Initialize() + . = ..() + saber_color = pick("red","blue","green","purple") + +/obj/item/holo/esword/attack_self(mob/living/user as mob) + active = !active + if (active) + force = 30 + icon_state = "sword[saber_color]" + w_class = WEIGHT_CLASS_BULKY + hitsound = 'sound/weapons/blade1.ogg' + playsound(user, 'sound/weapons/saberon.ogg', 20, TRUE) + to_chat(user, "[src] is now active.") + else + force = 3 + icon_state = "sword0" + w_class = WEIGHT_CLASS_SMALL + hitsound = "swing_hit" + playsound(user, 'sound/weapons/saberoff.ogg', 20, TRUE) + to_chat(user, "[src] can now be concealed.") + return + +//BASKETBALL OBJECTS + +/obj/item/toy/beach_ball/holoball + name = "basketball" + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "basketball" + item_state = "basketball" + desc = "Here's your chance, do your dance at the Space Jam." + w_class = WEIGHT_CLASS_BULKY //Stops people from hiding it in their bags/pockets + +/obj/item/toy/beach_ball/holoball/dodgeball + name = "dodgeball" + icon_state = "dodgeball" + item_state = "dodgeball" + desc = "Used for playing the most violent and degrading of childhood games." + +/obj/item/toy/beach_ball/holoball/dodgeball/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + ..() + if((ishuman(hit_atom))) + var/mob/living/carbon/M = hit_atom + playsound(src, 'sound/items/dodgeball.ogg', 50, TRUE) + M.apply_damage(10, STAMINA) + if(prob(5)) + M.Paralyze(60) + visible_message("[M] is knocked right off [M.p_their()] feet!") + +// +// Structures +// + +/obj/structure/holohoop + name = "basketball hoop" + desc = "Boom, shakalaka!" + icon = 'icons/obj/basketball.dmi' + icon_state = "hoop" + anchored = TRUE + density = TRUE + +/obj/structure/holohoop/attackby(obj/item/W as obj, mob/user as mob, params) + if(get_dist(src,user)<2) + if(user.transferItemToLoc(W, drop_location())) + visible_message("[user] dunks [W] into \the [src]!") + +/obj/structure/holohoop/attack_hand(mob/user) + . = ..() + if(.) + return + if(user.pulling && user.a_intent == INTENT_GRAB && isliving(user.pulling)) + var/mob/living/L = user.pulling + if(user.grab_state < GRAB_AGGRESSIVE) + to_chat(user, "You need a better grip to do that!") + return + L.forceMove(loc) + L.Paralyze(100) + visible_message("[user] dunks [L] into \the [src]!") + user.stop_pulling() + else + ..() + +/obj/structure/holohoop/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) + if (isitem(AM) && !istype(AM,/obj/projectile)) + if(prob(50)) + AM.forceMove(get_turf(src)) + visible_message("Swish! [AM] lands in [src].") + return + else + visible_message("[AM] bounces off of [src]'s rim!") + return ..() + else + return ..() + + + +// +// Machines +// + +/obj/machinery/readybutton + name = "ready declaration device" + desc = "This device is used to declare ready. If all devices in an area are ready, the event will begin!" + icon = 'icons/obj/monitors.dmi' + icon_state = "auth_off" + var/ready = 0 + var/area/currentarea = null + var/eventstarted = FALSE + + use_power = IDLE_POWER_USE + idle_power_usage = 2 + active_power_usage = 6 + power_channel = AREA_USAGE_ENVIRON + +/obj/machinery/readybutton/attack_ai(mob/user as mob) + to_chat(user, "The station AI is not to interact with these devices!") + return + +/obj/machinery/readybutton/attack_paw(mob/user as mob) + to_chat(user, "You are too primitive to use this device!") + return + +/obj/machinery/readybutton/attackby(obj/item/W as obj, mob/user as mob, params) + to_chat(user, "The device is a solid button, there's nothing you can do with it!") + +/obj/machinery/readybutton/attack_hand(mob/user as mob) + . = ..() + if(.) + return + if(user.stat || machine_stat & (NOPOWER|BROKEN)) + to_chat(user, "This device is not powered!") + return + + currentarea = get_area(src.loc) + if(!currentarea) + qdel(src) + + if(eventstarted) + to_chat(usr, "The event has already begun!") + return + + ready = !ready + + update_icon() + + var/numbuttons = 0 + var/numready = 0 + for(var/obj/machinery/readybutton/button in currentarea) + numbuttons++ + if (button.ready) + numready++ + + if(numbuttons == numready) + begin_event() + +/obj/machinery/readybutton/update_icon_state() + if(ready) + icon_state = "auth_on" + else + icon_state = "auth_off" + +/obj/machinery/readybutton/proc/begin_event() + + eventstarted = TRUE + + for(var/obj/structure/window/W in currentarea) + if(W.flags_1&NODECONSTRUCT_1) // Just in case: only holo-windows + qdel(W) + + for(var/mob/M in currentarea) + to_chat(M, "FIGHT!") + +/obj/machinery/conveyor/holodeck + +/obj/machinery/conveyor/holodeck/attackby(obj/item/I, mob/user, params) + if(!user.transferItemToLoc(I, drop_location())) + return ..() + +/obj/item/paper/fluff/holodeck/trek_diploma + name = "paper - Starfleet Academy Diploma" + info = {"

                    Starfleet Academy


                    Official Diploma


                    "} + +/obj/item/paper/fluff/holodeck/disclaimer + name = "Holodeck Disclaimer" + info = "Bruises sustained in the holodeck can be healed simply by sleeping." diff --git a/code/modules/hydroponics/biogenerator.dm b/code/modules/hydroponics/biogenerator.dm index 356d8560eb3a..0b0fe364f2b6 100644 --- a/code/modules/hydroponics/biogenerator.dm +++ b/code/modules/hydroponics/biogenerator.dm @@ -1,345 +1,339 @@ -/obj/machinery/biogenerator - name = "biogenerator" - desc = "Converts plants into biomass, which can be used to construct useful items." - icon = 'icons/obj/machines/biogenerator.dmi' - icon_state = "biogen-empty" - density = TRUE - use_power = IDLE_POWER_USE - idle_power_usage = 40 - circuit = /obj/item/circuitboard/machine/biogenerator - var/processing = FALSE - var/obj/item/reagent_containers/glass/beaker = null - var/points = 0 - var/menustat = "menu" - var/efficiency = 0 - var/productivity = 0 - var/max_items = 40 - var/datum/techweb/stored_research - var/list/show_categories = list("Food", "Botany Chemicals", "Organic Materials") - var/list/timesFiveCategories = list("Food", "Botany Chemicals") - -/obj/machinery/biogenerator/Initialize() - . = ..() - stored_research = new /datum/techweb/specialized/autounlocking/biogenerator - create_reagents(1000) - -/obj/machinery/biogenerator/Destroy() - QDEL_NULL(beaker) - return ..() - -/obj/machinery/biogenerator/contents_explosion(severity, target) - ..() - if(beaker) - switch(severity) - if(EXPLODE_DEVASTATE) - SSexplosions.highobj += beaker - if(EXPLODE_HEAVY) - SSexplosions.medobj += beaker - if(EXPLODE_LIGHT) - SSexplosions.lowobj += beaker - -/obj/machinery/biogenerator/handle_atom_del(atom/A) - ..() - if(A == beaker) - beaker = null - update_icon() - updateUsrDialog() - -/obj/machinery/biogenerator/RefreshParts() - var/E = 0 - var/P = 0 - var/max_storage = 40 - for(var/obj/item/stock_parts/matter_bin/B in component_parts) - P += B.rating - max_storage = 40 * B.rating - for(var/obj/item/stock_parts/manipulator/M in component_parts) - E += M.rating - efficiency = E - productivity = P - max_items = max_storage - -/obj/machinery/biogenerator/examine(mob/user) - . = ..() - if(in_range(user, src) || isobserver(user)) - . += "The status display reads: Productivity at [productivity*100]%.
                    Matter consumption reduced by [(efficiency*25)-25]%.
                    Machine can hold up to [max_items] pieces of produce.
                    " - -/obj/machinery/biogenerator/on_reagent_change(changetype) //When the reagents change, change the icon as well. - update_icon() - -/obj/machinery/biogenerator/update_icon_state() - if(panel_open) - icon_state = "biogen-empty-o" - else if(!src.beaker) - icon_state = "biogen-empty" - else if(!src.processing) - icon_state = "biogen-stand" - else - icon_state = "biogen-work" - -/obj/machinery/biogenerator/attackby(obj/item/O, mob/user, params) - if(user.a_intent == INTENT_HARM) - return ..() - - if(processing) - to_chat(user, "The biogenerator is currently processing.") - return - - if(default_deconstruction_screwdriver(user, "biogen-empty-o", "biogen-empty", O)) - if(beaker) - var/obj/item/reagent_containers/glass/B = beaker - B.forceMove(drop_location()) - beaker = null - update_icon() - return - - if(default_deconstruction_crowbar(O)) - return - - if(istype(O, /obj/item/reagent_containers/glass)) - . = 1 //no afterattack - if(!panel_open) - if(beaker) - to_chat(user, "A container is already loaded into the machine.") - else - if(!user.transferItemToLoc(O, src)) - return - beaker = O - to_chat(user, "You add the container to the machine.") - update_icon() - updateUsrDialog() - else - to_chat(user, "Close the maintenance panel first.") - return - - else if(istype(O, /obj/item/storage/bag/plants)) - var/obj/item/storage/bag/plants/PB = O - var/i = 0 - for(var/obj/item/reagent_containers/food/snacks/grown/G in contents) - i++ - if(i >= max_items) - to_chat(user, "The biogenerator is already full! Activate it.") - else - for(var/obj/item/reagent_containers/food/snacks/grown/G in PB.contents) - if(i >= max_items) - break - if(SEND_SIGNAL(PB, COMSIG_TRY_STORAGE_TAKE, G, src)) - i++ - if(iYou empty the plant bag into the biogenerator.") - else if(PB.contents.len == 0) - to_chat(user, "You empty the plant bag into the biogenerator, filling it to its capacity.") - else - to_chat(user, "You fill the biogenerator to its capacity.") - return TRUE //no afterattack - - else if(istype(O, /obj/item/reagent_containers/food/snacks/grown)) - var/i = 0 - for(var/obj/item/reagent_containers/food/snacks/grown/G in contents) - i++ - if(i >= max_items) - to_chat(user, "The biogenerator is full! Activate it.") - else - if(user.transferItemToLoc(O, src)) - to_chat(user, "You put [O.name] in [src.name]") - return TRUE //no afterattack - else if (istype(O, /obj/item/disk/design_disk)) - user.visible_message("[user] begins to load \the [O] in \the [src]...", - "You begin to load a design from \the [O]...", - "You hear the chatter of a floppy drive.") - processing = TRUE - var/obj/item/disk/design_disk/D = O - if(do_after(user, 10, target = src)) - for(var/B in D.blueprints) - if(B) - stored_research.add_design(B) - processing = FALSE - return TRUE - else - to_chat(user, "You cannot put this in [src.name]!") - -/obj/machinery/biogenerator/ui_interact(mob/user) - if(machine_stat & BROKEN || panel_open) - return - . = ..() - var/dat - if(processing) - dat += "
                    Biogenerator is processing! Please wait...

                    " - else - switch(menustat) - if("nopoints") - dat += "
                    You do not have enough biomass to create products.
                    Please, put growns into reactor and activate it.
                    " - menustat = "menu" - if("complete") - dat += "
                    Operation complete.
                    " - menustat = "menu" - if("void") - dat += "
                    Error: No growns inside.
                    Please, put growns into reactor.
                    " - menustat = "menu" - if("nobeakerspace") - dat += "
                    Not enough space left in container. Unable to create product.
                    " - menustat = "menu" - if(beaker) - var/categories = show_categories.Copy() - for(var/V in categories) - categories[V] = list() - for(var/V in stored_research.researched_designs) - var/datum/design/D = SSresearch.techweb_design_by_id(V) - for(var/C in categories) - if(C in D.category) - categories[C] += D - - dat += "
                    Biomass: [points] units.

                    " - dat += "ActivateDetach Container" - for(var/cat in categories) - dat += "

                    [cat]:

                    " - dat += "
                    " - for(var/V in categories[cat]) - var/datum/design/D = V - dat += "[D.name]: Make" - if(cat in timesFiveCategories) - dat += "x5" - if(ispath(D.build_path, /obj/item/stack)) - dat += "x10" - dat += "([D.materials[SSmaterials.GetMaterialRef(/datum/material/biomass)]/efficiency])
                    " - dat += "
                    " - else - dat += "
                    No container inside, please insert container.
                    " - - var/datum/browser/popup = new(user, "biogen", name, 350, 520) - popup.set_content(dat) - popup.open() - -/obj/machinery/biogenerator/AltClick(mob/living/user) - . = ..() - if(user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK) && can_interact(user)) - detach(user) - -/obj/machinery/biogenerator/proc/activate() - if (usr.stat != CONSCIOUS) - return - if (src.machine_stat != NONE) //NOPOWER etc - return - if(processing) - to_chat(usr, "The biogenerator is in the process of working.") - return - var/S = 0 - for(var/obj/item/reagent_containers/food/snacks/grown/I in contents) - S += 5 - if(I.reagents.get_reagent_amount(/datum/reagent/consumable/nutriment) < 0.1) - points += 1*productivity - else points += I.reagents.get_reagent_amount(/datum/reagent/consumable/nutriment)*10*productivity - qdel(I) - if(S) - processing = TRUE - update_icon() - updateUsrDialog() - playsound(src.loc, 'sound/machines/blender.ogg', 50, TRUE) - use_power(S*30) - sleep(S+15/productivity) - processing = FALSE - update_icon() - else - menustat = "void" - -/obj/machinery/biogenerator/proc/check_cost(list/materials, multiplier = 1, remove_points = TRUE) - if(materials.len != 1 || materials[1] != SSmaterials.GetMaterialRef(/datum/material/biomass)) - return FALSE - if (materials[SSmaterials.GetMaterialRef(/datum/material/biomass)]*multiplier/efficiency > points) - menustat = "nopoints" - return FALSE - else - if(remove_points) - points -= materials[SSmaterials.GetMaterialRef(/datum/material/biomass)]*multiplier/efficiency - update_icon() - updateUsrDialog() - return TRUE - -/obj/machinery/biogenerator/proc/check_container_volume(list/reagents, multiplier = 1) - var/sum_reagents = 0 - for(var/R in reagents) - sum_reagents += reagents[R] - sum_reagents *= multiplier - - if(beaker.reagents.total_volume + sum_reagents > beaker.reagents.maximum_volume) - menustat = "nobeakerspace" - return FALSE - - return TRUE - -/obj/machinery/biogenerator/proc/create_product(datum/design/D, amount) - if(!beaker || !loc) - return FALSE - - if(ispath(D.build_path, /obj/item/stack)) - if(!check_container_volume(D.make_reagents, amount)) - return FALSE - if(!check_cost(D.materials, amount)) - return FALSE - - new D.build_path(drop_location(), amount) - for(var/R in D.make_reagents) - beaker.reagents.add_reagent(R, D.make_reagents[R]*amount) - else - var/i = amount - while(i > 0) - if(!check_container_volume(D.make_reagents)) - return . - if(!check_cost(D.materials)) - return . - if(D.build_path) - new D.build_path(loc) - for(var/R in D.make_reagents) - beaker.reagents.add_reagent(R, D.make_reagents[R]) - . = 1 - --i - - menustat = "complete" - update_icon() - return . - -/obj/machinery/biogenerator/proc/detach(mob/living/user) - if(beaker) - if(can_interact(user)) - user.put_in_hands(beaker) - else - beaker.drop_location(get_turf(src)) - beaker = null - update_icon() - -/obj/machinery/biogenerator/Topic(href, href_list) - if(..() || panel_open) - return - - usr.set_machine(src) - - if(href_list["activate"]) - activate() - updateUsrDialog() - - else if(href_list["detach"]) - detach(usr) - updateUsrDialog() - - else if(href_list["create"]) - var/amount = (text2num(href_list["amount"])) - //Can't be outside these (if you change this keep a sane limit) - amount = clamp(amount, 1, 10) - var/id = href_list["create"] - if(!stored_research.researched_designs.Find(id)) - //naughty naughty - stack_trace("ID did not map to a researched datum [id]") - return - - //Get design by id (or may return error design) - var/datum/design/D = SSresearch.techweb_design_by_id(id) - //Valid design datum, amount and the datum is not the error design, lets proceed - if(D && amount && !istype(D, /datum/design/error_design)) - create_product(D, amount) - //This shouldnt happen normally but href forgery is real - else - stack_trace("ID could not be turned into a valid techweb design datum [id]") - updateUsrDialog() - - else if(href_list["menu"]) - menustat = "menu" - updateUsrDialog() +/obj/machinery/biogenerator + name = "biogenerator" + desc = "Converts plants into biomass, which can be used to construct useful items." + icon = 'icons/obj/machines/biogenerator.dmi' + icon_state = "biogen-empty" + density = TRUE + use_power = IDLE_POWER_USE + idle_power_usage = 40 + circuit = /obj/item/circuitboard/machine/biogenerator + var/processing = FALSE + var/obj/item/reagent_containers/glass/beaker = null + var/points = 0 + var/efficiency = 0 + var/productivity = 0 + var/max_items = 40 + var/datum/techweb/stored_research + var/list/show_categories = list("Food", "Botany Chemicals", "Organic Materials") + /// Currently selected category in the UI + var/selected_cat + +/obj/machinery/biogenerator/Initialize() + . = ..() + stored_research = new /datum/techweb/specialized/autounlocking/biogenerator + create_reagents(1000) + +/obj/machinery/biogenerator/Destroy() + QDEL_NULL(beaker) + return ..() + +/obj/machinery/biogenerator/contents_explosion(severity, target) + ..() + if(beaker) + switch(severity) + if(EXPLODE_DEVASTATE) + SSexplosions.highobj += beaker + if(EXPLODE_HEAVY) + SSexplosions.medobj += beaker + if(EXPLODE_LIGHT) + SSexplosions.lowobj += beaker + +/obj/machinery/biogenerator/handle_atom_del(atom/A) + ..() + if(A == beaker) + beaker = null + update_icon() + +/obj/machinery/biogenerator/RefreshParts() + var/E = 0 + var/P = 0 + var/max_storage = 40 + for(var/obj/item/stock_parts/matter_bin/B in component_parts) + P += B.rating + max_storage = 40 * B.rating + for(var/obj/item/stock_parts/manipulator/M in component_parts) + E += M.rating + efficiency = E + productivity = P + max_items = max_storage + +/obj/machinery/biogenerator/examine(mob/user) + . = ..() + if(in_range(user, src) || isobserver(user)) + . += "The status display reads: Productivity at [productivity*100]%.
                    Matter consumption reduced by [(efficiency*25)-25]%.
                    Machine can hold up to [max_items] pieces of produce.
                    " + +/obj/machinery/biogenerator/on_reagent_change(changetype) //When the reagents change, change the icon as well. + update_icon() + +/obj/machinery/biogenerator/update_icon_state() + if(panel_open) + icon_state = "biogen-empty-o" + else if(!src.beaker) + icon_state = "biogen-empty" + else if(!src.processing) + icon_state = "biogen-stand" + else + icon_state = "biogen-work" + +/obj/machinery/biogenerator/attackby(obj/item/O, mob/user, params) + if(user.a_intent == INTENT_HARM) + return ..() + + if(processing) + to_chat(user, "The biogenerator is currently processing.") + return + + if(default_deconstruction_screwdriver(user, "biogen-empty-o", "biogen-empty", O)) + if(beaker) + var/obj/item/reagent_containers/glass/B = beaker + B.forceMove(drop_location()) + beaker = null + update_icon() + return + + if(default_deconstruction_crowbar(O)) + return + + if(istype(O, /obj/item/reagent_containers/glass)) + . = 1 //no afterattack + if(!panel_open) + if(beaker) + to_chat(user, "A container is already loaded into the machine.") + else + if(!user.transferItemToLoc(O, src)) + return + beaker = O + to_chat(user, "You add the container to the machine.") + update_icon() + else + to_chat(user, "Close the maintenance panel first.") + return + + else if(istype(O, /obj/item/storage/bag/plants)) + var/obj/item/storage/bag/plants/PB = O + var/i = 0 + for(var/obj/item/reagent_containers/food/snacks/grown/G in contents) + i++ + if(i >= max_items) + to_chat(user, "The biogenerator is already full! Activate it.") + else + for(var/obj/item/reagent_containers/food/snacks/grown/G in PB.contents) + if(i >= max_items) + break + if(SEND_SIGNAL(PB, COMSIG_TRY_STORAGE_TAKE, G, src)) + i++ + if(iYou empty the plant bag into the biogenerator.") + else if(PB.contents.len == 0) + to_chat(user, "You empty the plant bag into the biogenerator, filling it to its capacity.") + else + to_chat(user, "You fill the biogenerator to its capacity.") + return TRUE //no afterattack + + else if(istype(O, /obj/item/reagent_containers/food/snacks/grown)) + var/i = 0 + for(var/obj/item/reagent_containers/food/snacks/grown/G in contents) + i++ + if(i >= max_items) + to_chat(user, "The biogenerator is full! Activate it.") + else + if(user.transferItemToLoc(O, src)) + to_chat(user, "You put [O.name] in [src.name]") + return TRUE //no afterattack + else if (istype(O, /obj/item/disk/design_disk)) + user.visible_message("[user] begins to load \the [O] in \the [src]...", + "You begin to load a design from \the [O]...", + "You hear the chatter of a floppy drive.") + processing = TRUE + var/obj/item/disk/design_disk/D = O + if(do_after(user, 10, target = src)) + for(var/B in D.blueprints) + if(B) + stored_research.add_design(B) + processing = FALSE + return TRUE + else + to_chat(user, "You cannot put this in [src.name]!") + +/obj/machinery/biogenerator/AltClick(mob/living/user) + . = ..() + if(user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK) && can_interact(user)) + detach(user) + +/** + * activate: Activates biomass processing and converts all inserted grown products into biomass + * + * Arguments: + * * user The mob starting the biomass processing + */ +/obj/machinery/biogenerator/proc/activate(mob/user) + if(user.stat != CONSCIOUS) + return + if(machine_stat != NONE) + return + if(processing) + to_chat(user, "The biogenerator is in the process of working.") + return + var/S = 0 + for(var/obj/item/reagent_containers/food/snacks/grown/I in contents) + S += 5 + if(I.reagents.get_reagent_amount(/datum/reagent/consumable/nutriment) < 0.1) + points += 1 * productivity + else + points += I.reagents.get_reagent_amount(/datum/reagent/consumable/nutriment) * 10 * productivity + qdel(I) + if(S) + processing = TRUE + update_icon() + playsound(loc, 'sound/machines/blender.ogg', 50, TRUE) + use_power(S * 30) + sleep(S + 15 / productivity) + processing = FALSE + update_icon() + +/obj/machinery/biogenerator/proc/check_cost(list/materials, multiplier = 1, remove_points = TRUE) + if(materials.len != 1 || materials[1] != SSmaterials.GetMaterialRef(/datum/material/biomass)) + return FALSE + if (materials[SSmaterials.GetMaterialRef(/datum/material/biomass)]*multiplier/efficiency > points) + return FALSE + else + if(remove_points) + points -= materials[SSmaterials.GetMaterialRef(/datum/material/biomass)]*multiplier/efficiency + update_icon() + return TRUE + +/obj/machinery/biogenerator/proc/check_container_volume(list/reagents, multiplier = 1) + var/sum_reagents = 0 + for(var/R in reagents) + sum_reagents += reagents[R] + sum_reagents *= multiplier + + if(beaker.reagents.total_volume + sum_reagents > beaker.reagents.maximum_volume) + return FALSE + + return TRUE + +/obj/machinery/biogenerator/proc/create_product(datum/design/D, amount) + if(!beaker || !loc) + return FALSE + + if(ispath(D.build_path, /obj/item/stack)) + if(!check_container_volume(D.make_reagents, amount)) + return FALSE + if(!check_cost(D.materials, amount)) + return FALSE + + new D.build_path(drop_location(), amount) + for(var/R in D.make_reagents) + beaker.reagents.add_reagent(R, D.make_reagents[R]*amount) + else + var/i = amount + while(i > 0) + if(!check_container_volume(D.make_reagents)) + say("Warning: Attached container does not have enough free capacity!") + return . + if(!check_cost(D.materials)) + return . + if(D.build_path) + new D.build_path(loc) + for(var/R in D.make_reagents) + beaker.reagents.add_reagent(R, D.make_reagents[R]) + . = 1 + --i + update_icon() + return . + +/obj/machinery/biogenerator/proc/detach(mob/living/user) + if(beaker) + if(can_interact(user)) + user.put_in_hands(beaker) + else + beaker.drop_location(get_turf(src)) + beaker = null + update_icon() + +/obj/machinery/biogenerator/ui_status(mob/user) + if(machine_stat & BROKEN || panel_open) + return UI_CLOSE + return ..() + +/obj/machinery/biogenerator/ui_assets(mob/user) + return list( + get_asset_datum(/datum/asset/spritesheet/research_designs), + ) + +/obj/machinery/biogenerator/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "Biogenerator", name) + ui.open() + +/obj/machinery/biogenerator/ui_data(mob/user) + var/list/data = list() + data["beaker"] = beaker ? TRUE : FALSE + data["biomass"] = points + data["processing"] = processing + if(locate(/obj/item/reagent_containers/food/snacks/grown) in contents) + data["can_process"] = TRUE + else + data["can_process"] = FALSE + return data + +/obj/machinery/biogenerator/ui_static_data(mob/user) + var/list/data = list() + data["categories"] = list() + + var/categories = show_categories.Copy() + for(var/V in categories) + categories[V] = list() + for(var/V in stored_research.researched_designs) + var/datum/design/D = SSresearch.techweb_design_by_id(V) + for(var/C in categories) + if(C in D.category) + categories[C] += D + + for(var/category in categories) + var/list/cat = list( + "name" = category, + "items" = (category == selected_cat ? list() : null)) + for(var/item in categories[category]) + var/datum/design/D = item + cat["items"] += list(list( + "id" = D.id, + "name" = D.name, + "cost" = D.materials[SSmaterials.GetMaterialRef(/datum/material/biomass)]/efficiency, + )) + data["categories"] += list(cat) + + return data + +/obj/machinery/biogenerator/ui_act(action, list/params) + if(..()) + return + + switch(action) + if("activate") + activate(usr) + return TRUE + if("detach") + detach(usr) + return TRUE + if("create") + var/amount = text2num(params["amount"]) + amount = clamp(amount, 1, 10) + if(!amount) + return + var/id = params["id"] + if(!stored_research.researched_designs.Find(id)) + stack_trace("ID did not map to a researched datum [id]") + return + var/datum/design/D = SSresearch.techweb_design_by_id(id) + if(D && !istype(D, /datum/design/error_design)) + create_product(D, amount) + else + stack_trace("ID could not be turned into a valid techweb design datum [id]") + return + return TRUE + if("select") + selected_cat = params["category"] + return TRUE diff --git a/code/modules/hydroponics/grown.dm b/code/modules/hydroponics/grown.dm index 8ad7000675be..2b201faebb0a 100644 --- a/code/modules/hydroponics/grown.dm +++ b/code/modules/hydroponics/grown.dm @@ -1,181 +1,181 @@ -// *********************************************************** -// Foods that are produced from hydroponics ~~~~~~~~~~ -// Data from the seeds carry over to these grown foods -// *********************************************************** - -// Base type. Subtypes are found in /grown dir. Lavaland-based subtypes can be found in mining/ash_flora.dm -/obj/item/reagent_containers/food/snacks/grown - icon = 'icons/obj/hydroponics/harvest.dmi' - name = "fresh produce" // so recipe text doesn't say 'snack' - var/obj/item/seeds/seed = null // type path, gets converted to item on New(). It's safe to assume it's always a seed item. - var/plantname = "" - var/bitesize_mod = 0 - var/splat_type = /obj/effect/decal/cleanable/food/plant_smudge - // If set, bitesize = 1 + round(reagents.total_volume / bitesize_mod) - dried_type = -1 - // Saves us from having to define each stupid grown's dried_type as itself. - // If you don't want a plant to be driable (watermelons) set this to null in the time definition. - resistance_flags = FLAMMABLE - var/dry_grind = FALSE //If TRUE, this object needs to be dry to be ground up - var/can_distill = TRUE //If FALSE, this object cannot be distilled into an alcohol. - var/distill_reagent //If NULL and this object can be distilled, it uses a generic fruit_wine reagent and adjusts its variables. - var/wine_flavor //If NULL, this is automatically set to the fruit's flavor. Determines the flavor of the wine if distill_reagent is NULL. - var/wine_power = 10 //Determines the boozepwr of the wine if distill_reagent is NULL. - -/obj/item/reagent_containers/food/snacks/grown/Initialize(mapload, obj/item/seeds/new_seed) - . = ..() - if(!tastes) - tastes = list("[name]" = 1) - - if(new_seed) - seed = new_seed.Copy() - else if(ispath(seed)) - // This is for adminspawn or map-placed growns. They get the default stats of their seed type. - seed = new seed() - seed.adjust_potency(50-seed.potency) - - pixel_x = rand(-5, 5) - pixel_y = rand(-5, 5) - - if(dried_type == -1) - dried_type = src.type - - if(seed) - for(var/datum/plant_gene/trait/T in seed.genes) - T.on_new(src, loc) - seed.prepare_result(src) - transform *= TRANSFORM_USING_VARIABLE(seed.potency, 100) + 0.5 //Makes the resulting produce's sprite larger or smaller based on potency! - add_juice() - - - -/obj/item/reagent_containers/food/snacks/grown/proc/add_juice() - if(reagents) - if(bitesize_mod) - bitesize = 1 + round(reagents.total_volume / bitesize_mod) - return 1 - return 0 - -/obj/item/reagent_containers/food/snacks/grown/examine(user) - . = ..() - if(seed) - for(var/datum/plant_gene/trait/T in seed.genes) - if(T.examine_line) - . += T.examine_line - -/obj/item/reagent_containers/food/snacks/grown/attackby(obj/item/O, mob/user, params) - ..() - if (istype(O, /obj/item/plant_analyzer)) - var/msg = "*---------*\n This is \a [src].\n" - if(seed) - msg += seed.get_analyzer_text() - var/reag_txt = "" - if(seed) - for(var/reagent_id in seed.reagents_add) - var/datum/reagent/R = GLOB.chemical_reagents_list[reagent_id] - var/amt = reagents.get_reagent_amount(reagent_id) - reag_txt += "\n- [R.name]: [amt]" - - if(reag_txt) - msg += reag_txt - msg += "
                    *---------*" - to_chat(user, msg) - else - if(seed) - for(var/datum/plant_gene/trait/T in seed.genes) - T.on_attackby(src, O, user) - - -// Various gene procs -/obj/item/reagent_containers/food/snacks/grown/attack_self(mob/user) - if(seed && seed.get_gene(/datum/plant_gene/trait/squash)) - squash(user) - ..() - -/obj/item/reagent_containers/food/snacks/grown/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - if(!..()) //was it caught by a mob? - if(seed) - for(var/datum/plant_gene/trait/T in seed.genes) - T.on_throw_impact(src, hit_atom) - if(seed.get_gene(/datum/plant_gene/trait/squash)) - squash(hit_atom) - -/obj/item/reagent_containers/food/snacks/grown/proc/squash(atom/target) - var/turf/T = get_turf(target) - forceMove(T) - if(ispath(splat_type, /obj/effect/decal/cleanable/food/plant_smudge)) - if(filling_color) - var/obj/O = new splat_type(T) - O.color = filling_color - O.name = "[name] smudge" - else if(splat_type) - new splat_type(T) - - if(trash) - generate_trash(T) - - visible_message("[src] is squashed.","You hear a smack.") - if(seed) - for(var/datum/plant_gene/trait/trait in seed.genes) - trait.on_squash(src, target) - - reagents.expose(T) - for(var/A in T) - reagents.expose(A) - - qdel(src) - -/obj/item/reagent_containers/food/snacks/grown/On_Consume() - if(iscarbon(usr)) - if(seed) - for(var/datum/plant_gene/trait/T in seed.genes) - T.on_consume(src, usr) - ..() - -/obj/item/reagent_containers/food/snacks/grown/generate_trash(atom/location) - if(trash && (ispath(trash, /obj/item/grown) || ispath(trash, /obj/item/reagent_containers/food/snacks/grown))) - . = new trash(location, seed) - trash = null - return - return ..() - -/obj/item/reagent_containers/food/snacks/grown/grind_requirements() - if(dry_grind && !dry) - to_chat(usr, "[src] needs to be dry before it can be ground up!") - return - return TRUE - -/obj/item/reagent_containers/food/snacks/grown/on_grind() - var/nutriment = reagents.get_reagent_amount(/datum/reagent/consumable/nutriment) - if(grind_results&&grind_results.len) - for(var/i in 1 to grind_results.len) - grind_results[grind_results[i]] = nutriment - reagents.del_reagent(/datum/reagent/consumable/nutriment) - reagents.del_reagent(/datum/reagent/consumable/nutriment/vitamin) - -/obj/item/reagent_containers/food/snacks/grown/on_juice() - var/nutriment = reagents.get_reagent_amount(/datum/reagent/consumable/nutriment) - if(juice_results&&juice_results.len) - for(var/i in 1 to juice_results.len) - juice_results[juice_results[i]] = nutriment - reagents.del_reagent(/datum/reagent/consumable/nutriment) - reagents.del_reagent(/datum/reagent/consumable/nutriment/vitamin) - -/* - * Attack self for growns - * - * Spawns the trash item at the growns drop_location() - * - * Then deletes the grown object - * - * Then puts trash item into the hand of user attack selfing, or drops it back on the ground - */ -/obj/item/reagent_containers/food/snacks/grown/shell/attack_self(mob/user) - var/obj/item/T - if(trash) - T = generate_trash(drop_location()) - //Delete grown so our hand is free - qdel(src) - //put trash obj in hands or drop to ground - user.put_in_hands(T, user.active_hand_index, TRUE) - to_chat(user, "You open [src]\'s shell, revealing \a [T].") +// *********************************************************** +// Foods that are produced from hydroponics ~~~~~~~~~~ +// Data from the seeds carry over to these grown foods +// *********************************************************** + +// Base type. Subtypes are found in /grown dir. Lavaland-based subtypes can be found in mining/ash_flora.dm +/obj/item/reagent_containers/food/snacks/grown + icon = 'icons/obj/hydroponics/harvest.dmi' + name = "fresh produce" // so recipe text doesn't say 'snack' + var/obj/item/seeds/seed = null // type path, gets converted to item on New(). It's safe to assume it's always a seed item. + var/plantname = "" + var/bitesize_mod = 0 + var/splat_type = /obj/effect/decal/cleanable/food/plant_smudge + // If set, bitesize = 1 + round(reagents.total_volume / bitesize_mod) + dried_type = -1 + // Saves us from having to define each stupid grown's dried_type as itself. + // If you don't want a plant to be driable (watermelons) set this to null in the time definition. + resistance_flags = FLAMMABLE + var/dry_grind = FALSE //If TRUE, this object needs to be dry to be ground up + var/can_distill = TRUE //If FALSE, this object cannot be distilled into an alcohol. + var/distill_reagent //If NULL and this object can be distilled, it uses a generic fruit_wine reagent and adjusts its variables. + var/wine_flavor //If NULL, this is automatically set to the fruit's flavor. Determines the flavor of the wine if distill_reagent is NULL. + var/wine_power = 10 //Determines the boozepwr of the wine if distill_reagent is NULL. + +/obj/item/reagent_containers/food/snacks/grown/Initialize(mapload, obj/item/seeds/new_seed) + . = ..() + if(!tastes) + tastes = list("[name]" = 1) + + if(new_seed) + seed = new_seed.Copy() + else if(ispath(seed)) + // This is for adminspawn or map-placed growns. They get the default stats of their seed type. + seed = new seed() + seed.adjust_potency(50-seed.potency) + + pixel_x = rand(-5, 5) + pixel_y = rand(-5, 5) + + if(dried_type == -1) + dried_type = src.type + + if(seed) + for(var/datum/plant_gene/trait/T in seed.genes) + T.on_new(src, loc) + seed.prepare_result(src) + transform *= TRANSFORM_USING_VARIABLE(seed.potency, 100) + 0.5 //Makes the resulting produce's sprite larger or smaller based on potency! + add_juice() + + + +/obj/item/reagent_containers/food/snacks/grown/proc/add_juice() + if(reagents) + if(bitesize_mod) + bitesize = 1 + round(reagents.total_volume / bitesize_mod) + return 1 + return 0 + +/obj/item/reagent_containers/food/snacks/grown/examine(user) + . = ..() + if(seed) + for(var/datum/plant_gene/trait/T in seed.genes) + if(T.examine_line) + . += T.examine_line + +/obj/item/reagent_containers/food/snacks/grown/attackby(obj/item/O, mob/user, params) + ..() + if (istype(O, /obj/item/plant_analyzer)) + var/msg = "*---------*\n This is \a [src].\n" + if(seed) + msg += seed.get_analyzer_text() + var/reag_txt = "" + if(seed) + for(var/reagent_id in seed.reagents_add) + var/datum/reagent/R = GLOB.chemical_reagents_list[reagent_id] + var/amt = reagents.get_reagent_amount(reagent_id) + reag_txt += "\n- [R.name]: [amt]" + + if(reag_txt) + msg += reag_txt + msg += "
                    *---------*" + to_chat(user, msg) + else + if(seed) + for(var/datum/plant_gene/trait/T in seed.genes) + T.on_attackby(src, O, user) + + +// Various gene procs +/obj/item/reagent_containers/food/snacks/grown/attack_self(mob/user) + if(seed && seed.get_gene(/datum/plant_gene/trait/squash)) + squash(user) + ..() + +/obj/item/reagent_containers/food/snacks/grown/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + if(!..()) //was it caught by a mob? + if(seed) + for(var/datum/plant_gene/trait/T in seed.genes) + T.on_throw_impact(src, hit_atom) + if(seed.get_gene(/datum/plant_gene/trait/squash)) + squash(hit_atom) + +/obj/item/reagent_containers/food/snacks/grown/proc/squash(atom/target) + var/turf/T = get_turf(target) + forceMove(T) + if(ispath(splat_type, /obj/effect/decal/cleanable/food/plant_smudge)) + if(filling_color) + var/obj/O = new splat_type(T) + O.color = filling_color + O.name = "[name] smudge" + else if(splat_type) + new splat_type(T) + + if(trash) + generate_trash(T) + + visible_message("[src] is squashed.","You hear a smack.") + if(seed) + for(var/datum/plant_gene/trait/trait in seed.genes) + trait.on_squash(src, target) + + reagents.expose(T) + for(var/A in T) + reagents.expose(A) + + qdel(src) + +/obj/item/reagent_containers/food/snacks/grown/On_Consume() + if(iscarbon(usr)) + if(seed) + for(var/datum/plant_gene/trait/T in seed.genes) + T.on_consume(src, usr) + ..() + +/obj/item/reagent_containers/food/snacks/grown/generate_trash(atom/location) + if(trash && (ispath(trash, /obj/item/grown) || ispath(trash, /obj/item/reagent_containers/food/snacks/grown))) + . = new trash(location, seed) + trash = null + return + return ..() + +/obj/item/reagent_containers/food/snacks/grown/grind_requirements() + if(dry_grind && !dry) + to_chat(usr, "[src] needs to be dry before it can be ground up!") + return + return TRUE + +/obj/item/reagent_containers/food/snacks/grown/on_grind() + var/nutriment = reagents.get_reagent_amount(/datum/reagent/consumable/nutriment) + if(grind_results&&grind_results.len) + for(var/i in 1 to grind_results.len) + grind_results[grind_results[i]] = nutriment + reagents.del_reagent(/datum/reagent/consumable/nutriment) + reagents.del_reagent(/datum/reagent/consumable/nutriment/vitamin) + +/obj/item/reagent_containers/food/snacks/grown/on_juice() + var/nutriment = reagents.get_reagent_amount(/datum/reagent/consumable/nutriment) + if(juice_results&&juice_results.len) + for(var/i in 1 to juice_results.len) + juice_results[juice_results[i]] = nutriment + reagents.del_reagent(/datum/reagent/consumable/nutriment) + reagents.del_reagent(/datum/reagent/consumable/nutriment/vitamin) + +/* + * Attack self for growns + * + * Spawns the trash item at the growns drop_location() + * + * Then deletes the grown object + * + * Then puts trash item into the hand of user attack selfing, or drops it back on the ground + */ +/obj/item/reagent_containers/food/snacks/grown/shell/attack_self(mob/user) + var/obj/item/T + if(trash) + T = generate_trash(drop_location()) + //Delete grown so our hand is free + qdel(src) + //put trash obj in hands or drop to ground + user.put_in_hands(T, user.active_hand_index, TRUE) + to_chat(user, "You open [src]\'s shell, revealing \a [T].") diff --git a/code/modules/hydroponics/growninedible.dm b/code/modules/hydroponics/growninedible.dm index e4111755eec0..3a080c19977c 100644 --- a/code/modules/hydroponics/growninedible.dm +++ b/code/modules/hydroponics/growninedible.dm @@ -1,61 +1,61 @@ -// ********************** -// Other harvested materials from plants (that are not food) -// ********************** - -/obj/item/grown // Grown weapons - name = "grown_weapon" - icon = 'icons/obj/hydroponics/harvest.dmi' - resistance_flags = FLAMMABLE - var/obj/item/seeds/seed = null // type path, gets converted to item on New(). It's safe to assume it's always a seed item. - -/obj/item/grown/Initialize(newloc, obj/item/seeds/new_seed) - . = ..() - create_reagents(50) - - if(new_seed) - seed = new_seed.Copy() - else if(ispath(seed)) - // This is for adminspawn or map-placed growns. They get the default stats of their seed type. - seed = new seed() - seed.adjust_potency(50-seed.potency) - - pixel_x = rand(-5, 5) - pixel_y = rand(-5, 5) - - if(seed) - for(var/datum/plant_gene/trait/T in seed.genes) - T.on_new(src, newloc) - - if(istype(src, seed.product)) // no adding reagents if it is just a trash item - seed.prepare_result(src) - transform *= TRANSFORM_USING_VARIABLE(seed.potency, 100) + 0.5 - add_juice() - - -/obj/item/grown/attackby(obj/item/O, mob/user, params) - ..() - if (istype(O, /obj/item/plant_analyzer)) - var/msg = "*---------*\n This is \a [src]\n" - if(seed) - msg += seed.get_analyzer_text() - msg += "" - to_chat(usr, msg) - return - -/obj/item/grown/proc/add_juice() - if(reagents) - return 1 - return 0 - -/obj/item/grown/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - if(!..()) //was it caught by a mob? - if(seed) - for(var/datum/plant_gene/trait/T in seed.genes) - T.on_throw_impact(src, hit_atom) - -/obj/item/grown/microwave_act(obj/machinery/microwave/M) - return - -/obj/item/grown/on_grind() - for(var/i in 1 to grind_results.len) - grind_results[grind_results[i]] = round(seed.potency) +// ********************** +// Other harvested materials from plants (that are not food) +// ********************** + +/obj/item/grown // Grown weapons + name = "grown_weapon" + icon = 'icons/obj/hydroponics/harvest.dmi' + resistance_flags = FLAMMABLE + var/obj/item/seeds/seed = null // type path, gets converted to item on New(). It's safe to assume it's always a seed item. + +/obj/item/grown/Initialize(newloc, obj/item/seeds/new_seed) + . = ..() + create_reagents(50) + + if(new_seed) + seed = new_seed.Copy() + else if(ispath(seed)) + // This is for adminspawn or map-placed growns. They get the default stats of their seed type. + seed = new seed() + seed.adjust_potency(50-seed.potency) + + pixel_x = rand(-5, 5) + pixel_y = rand(-5, 5) + + if(seed) + for(var/datum/plant_gene/trait/T in seed.genes) + T.on_new(src, newloc) + + if(istype(src, seed.product)) // no adding reagents if it is just a trash item + seed.prepare_result(src) + transform *= TRANSFORM_USING_VARIABLE(seed.potency, 100) + 0.5 + add_juice() + + +/obj/item/grown/attackby(obj/item/O, mob/user, params) + ..() + if (istype(O, /obj/item/plant_analyzer)) + var/msg = "*---------*\n This is \a [src]\n" + if(seed) + msg += seed.get_analyzer_text() + msg += "" + to_chat(usr, msg) + return + +/obj/item/grown/proc/add_juice() + if(reagents) + return 1 + return 0 + +/obj/item/grown/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + if(!..()) //was it caught by a mob? + if(seed) + for(var/datum/plant_gene/trait/T in seed.genes) + T.on_throw_impact(src, hit_atom) + +/obj/item/grown/microwave_act(obj/machinery/microwave/M) + return + +/obj/item/grown/on_grind() + for(var/i in 1 to grind_results.len) + grind_results[grind_results[i]] = round(seed.potency) diff --git a/code/modules/hydroponics/hydroitemdefines.dm b/code/modules/hydroponics/hydroitemdefines.dm index eba43ee38c0c..635a56ea635a 100644 --- a/code/modules/hydroponics/hydroitemdefines.dm +++ b/code/modules/hydroponics/hydroitemdefines.dm @@ -1,219 +1,219 @@ -// Plant analyzer -/obj/item/plant_analyzer - name = "plant analyzer" - desc = "A scanner used to evaluate a plant's various areas of growth." - icon = 'waspstation/icons/obj/device.dmi' //Waspstation edit - analyzer update - icon_state = "hydro" - item_state = "analyzer" - lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' - w_class = WEIGHT_CLASS_TINY - slot_flags = ITEM_SLOT_BELT - custom_materials = list(/datum/material/iron=30, /datum/material/glass=20) - -// ************************************* -// Hydroponics Tools -// ************************************* - -/obj/item/reagent_containers/spray/weedspray // -- Skie - desc = "It's a toxic mixture, in spray form, to kill small weeds." - icon = 'icons/obj/hydroponics/equipment.dmi' - name = "weed spray" - icon_state = "weedspray" - item_state = "spraycan" - lefthand_file = 'icons/mob/inhands/equipment/hydroponics_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/hydroponics_righthand.dmi' - volume = 100 - list_reagents = list(/datum/reagent/toxin/plantbgone/weedkiller = 100) - -/obj/item/reagent_containers/spray/weedspray/suicide_act(mob/user) - user.visible_message("[user] is huffing [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return (TOXLOSS) - -/obj/item/reagent_containers/spray/pestspray // -- Skie - desc = "It's some pest eliminator spray! Do not inhale!" - icon = 'icons/obj/hydroponics/equipment.dmi' - name = "pest spray" - icon_state = "pestspray" - item_state = "plantbgone" - lefthand_file = 'icons/mob/inhands/equipment/hydroponics_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/hydroponics_righthand.dmi' - volume = 100 - list_reagents = list(/datum/reagent/toxin/pestkiller = 100) - -/obj/item/reagent_containers/spray/pestspray/suicide_act(mob/user) - user.visible_message("[user] is huffing [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return (TOXLOSS) - -/obj/item/cultivator - name = "cultivator" - desc = "It's used for removing weeds or scratching your back." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "cultivator" - item_state = "cultivator" - lefthand_file = 'icons/mob/inhands/equipment/hydroponics_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/hydroponics_righthand.dmi' - flags_1 = CONDUCT_1 - force = 5 - throwforce = 7 - w_class = WEIGHT_CLASS_SMALL - custom_materials = list(/datum/material/iron=50) - attack_verb = list("slashed", "sliced", "cut", "clawed") - hitsound = 'sound/weapons/bladeslice.ogg' - -/obj/item/cultivator/suicide_act(mob/user) - user.visible_message("[user] is scratching [user.p_their()] back as hard as [user.p_they()] can with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return (BRUTELOSS) - -/obj/item/cultivator/rake - name = "rake" - icon_state = "rake" - w_class = WEIGHT_CLASS_NORMAL - attack_verb = list("slashed", "sliced", "bashed", "clawed") - hitsound = null - custom_materials = list(/datum/material/wood = MINERAL_MATERIAL_AMOUNT * 1.5) - flags_1 = NONE - resistance_flags = FLAMMABLE - -/obj/item/cultivator/rake/Crossed(atom/movable/AM) - . = ..() - if(!ishuman(AM)) - return - var/mob/living/carbon/human/H = AM - if(has_gravity(loc) && HAS_TRAIT(H, TRAIT_CLUMSY) && !H.resting) - H.confused = max(H.confused, 10) - H.Stun(20) - playsound(src, 'sound/weapons/punch4.ogg', 50, TRUE) - H.visible_message("[H] steps on [src] causing the handle to hit [H.p_them()] right in the face!", \ - "You step on [src] causing the handle to hit you right in the face!") - -/obj/item/hatchet - name = "hatchet" - desc = "A very sharp axe blade upon a short fibremetal handle. It has a long history of chopping things, but now it is used for chopping wood." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "hatchet" - item_state = "hatchet" - lefthand_file = 'icons/mob/inhands/equipment/hydroponics_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/hydroponics_righthand.dmi' - flags_1 = CONDUCT_1 - force = 12 - w_class = WEIGHT_CLASS_SMALL - throwforce = 15 - throw_speed = 3 - throw_range = 4 - custom_materials = list(/datum/material/iron = 15000) - attack_verb = list("chopped", "torn", "cut") - hitsound = 'sound/weapons/bladeslice.ogg' - sharpness = IS_SHARP - -/obj/item/hatchet/Initialize() - . = ..() - AddComponent(/datum/component/butchering, 70, 100) - -/obj/item/hatchet/suicide_act(mob/user) - user.visible_message("[user] is chopping at [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit suicide!") - playsound(src, 'sound/weapons/bladeslice.ogg', 50, TRUE, -1) - return (BRUTELOSS) - -/obj/item/hatchet/wooden - desc = "A crude axe blade upon a short wooden handle." - icon_state = "woodhatchet" - custom_materials = null - flags_1 = NONE - -/obj/item/scythe - icon_state = "scythe0" - lefthand_file = 'icons/mob/inhands/weapons/polearms_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/polearms_righthand.dmi' - name = "scythe" - desc = "A sharp and curved blade on a long fibremetal handle, this tool makes it easy to reap what you sow." - force = 13 - throwforce = 5 - throw_speed = 2 - throw_range = 3 - w_class = WEIGHT_CLASS_BULKY - flags_1 = CONDUCT_1 - armour_penetration = 20 - slot_flags = ITEM_SLOT_BACK - attack_verb = list("chopped", "sliced", "cut", "reaped") - hitsound = 'sound/weapons/bladeslice.ogg' - var/swiping = FALSE - -/obj/item/scythe/Initialize() - . = ..() - AddComponent(/datum/component/butchering, 90, 105) - -/obj/item/scythe/suicide_act(mob/user) - user.visible_message("[user] is beheading [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit suicide!") - if(iscarbon(user)) - var/mob/living/carbon/C = user - var/obj/item/bodypart/BP = C.get_bodypart(BODY_ZONE_HEAD) - if(BP) - BP.drop_limb() - playsound(src, "desceration" ,50, TRUE, -1) - return (BRUTELOSS) - -/obj/item/scythe/pre_attack(atom/A, mob/living/user, params) - if(swiping || !istype(A, /obj/structure/spacevine) || get_turf(A) == get_turf(user)) - return ..() - var/turf/user_turf = get_turf(user) - var/dir_to_target = get_dir(user_turf, get_turf(A)) - swiping = TRUE - var/static/list/scythe_slash_angles = list(0, 45, 90, -45, -90) - for(var/i in scythe_slash_angles) - var/turf/T = get_step(user_turf, turn(dir_to_target, i)) - for(var/obj/structure/spacevine/V in T) - if(user.Adjacent(V)) - melee_attack_chain(user, V) - swiping = FALSE - return TRUE - -// ************************************* -// Nutrient defines for hydroponics -// ************************************* - - -/obj/item/reagent_containers/glass/bottle/nutrient - name = "bottle of nutrient" - volume = 50 - amount_per_transfer_from_this = 10 - possible_transfer_amounts = list(1,2,5,10,15,25,50) - -/obj/item/reagent_containers/glass/bottle/nutrient/Initialize() - . = ..() - pixel_x = rand(-5, 5) - pixel_y = rand(-5, 5) - - -/obj/item/reagent_containers/glass/bottle/nutrient/ez - name = "bottle of E-Z-Nutrient" - desc = "Contains a fertilizer that causes mild mutations with each harvest." - list_reagents = list(/datum/reagent/plantnutriment/eznutriment = 50) - -/obj/item/reagent_containers/glass/bottle/nutrient/l4z - name = "bottle of Left 4 Zed" - desc = "Contains a fertilizer that limits plant yields to no more than one and causes significant mutations in plants." - list_reagents = list(/datum/reagent/plantnutriment/left4zednutriment = 50) - -/obj/item/reagent_containers/glass/bottle/nutrient/rh - name = "bottle of Robust Harvest" - desc = "Contains a fertilizer that increases the yield of a plant by 30% while causing no mutations." - list_reagents = list(/datum/reagent/plantnutriment/robustharvestnutriment = 50) - -/obj/item/reagent_containers/glass/bottle/nutrient/empty - name = "bottle" - -/obj/item/reagent_containers/glass/bottle/killer - volume = 50 - amount_per_transfer_from_this = 10 - possible_transfer_amounts = list(1,2,5,10,15,25,50) - -/obj/item/reagent_containers/glass/bottle/killer/weedkiller - name = "bottle of weed killer" - desc = "Contains a herbicide." - list_reagents = list(/datum/reagent/toxin/plantbgone/weedkiller = 50) - -/obj/item/reagent_containers/glass/bottle/killer/pestkiller - name = "bottle of pest spray" - desc = "Contains a pesticide." - list_reagents = list(/datum/reagent/toxin/pestkiller = 50) +// Plant analyzer +/obj/item/plant_analyzer + name = "plant analyzer" + desc = "A scanner used to evaluate a plant's various areas of growth." + icon = 'waspstation/icons/obj/device.dmi' //Waspstation edit - analyzer update + icon_state = "hydro" + item_state = "analyzer" + lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' + w_class = WEIGHT_CLASS_TINY + slot_flags = ITEM_SLOT_BELT + custom_materials = list(/datum/material/iron=30, /datum/material/glass=20) + +// ************************************* +// Hydroponics Tools +// ************************************* + +/obj/item/reagent_containers/spray/weedspray // -- Skie + desc = "It's a toxic mixture, in spray form, to kill small weeds." + icon = 'icons/obj/hydroponics/equipment.dmi' + name = "weed spray" + icon_state = "weedspray" + item_state = "spraycan" + lefthand_file = 'icons/mob/inhands/equipment/hydroponics_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/hydroponics_righthand.dmi' + volume = 100 + list_reagents = list(/datum/reagent/toxin/plantbgone/weedkiller = 100) + +/obj/item/reagent_containers/spray/weedspray/suicide_act(mob/user) + user.visible_message("[user] is huffing [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return (TOXLOSS) + +/obj/item/reagent_containers/spray/pestspray // -- Skie + desc = "It's some pest eliminator spray! Do not inhale!" + icon = 'icons/obj/hydroponics/equipment.dmi' + name = "pest spray" + icon_state = "pestspray" + item_state = "plantbgone" + lefthand_file = 'icons/mob/inhands/equipment/hydroponics_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/hydroponics_righthand.dmi' + volume = 100 + list_reagents = list(/datum/reagent/toxin/pestkiller = 100) + +/obj/item/reagent_containers/spray/pestspray/suicide_act(mob/user) + user.visible_message("[user] is huffing [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return (TOXLOSS) + +/obj/item/cultivator + name = "cultivator" + desc = "It's used for removing weeds or scratching your back." + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "cultivator" + item_state = "cultivator" + lefthand_file = 'icons/mob/inhands/equipment/hydroponics_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/hydroponics_righthand.dmi' + flags_1 = CONDUCT_1 + force = 5 + throwforce = 7 + w_class = WEIGHT_CLASS_SMALL + custom_materials = list(/datum/material/iron=50) + attack_verb = list("slashed", "sliced", "cut", "clawed") + hitsound = 'sound/weapons/bladeslice.ogg' + +/obj/item/cultivator/suicide_act(mob/user) + user.visible_message("[user] is scratching [user.p_their()] back as hard as [user.p_they()] can with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return (BRUTELOSS) + +/obj/item/cultivator/rake + name = "rake" + icon_state = "rake" + w_class = WEIGHT_CLASS_NORMAL + attack_verb = list("slashed", "sliced", "bashed", "clawed") + hitsound = null + custom_materials = list(/datum/material/wood = MINERAL_MATERIAL_AMOUNT * 1.5) + flags_1 = NONE + resistance_flags = FLAMMABLE + +/obj/item/cultivator/rake/Crossed(atom/movable/AM) + . = ..() + if(!ishuman(AM)) + return + var/mob/living/carbon/human/H = AM + if(has_gravity(loc) && HAS_TRAIT(H, TRAIT_CLUMSY) && !H.resting) + H.confused = max(H.confused, 10) + H.Stun(20) + playsound(src, 'sound/weapons/punch4.ogg', 50, TRUE) + H.visible_message("[H] steps on [src] causing the handle to hit [H.p_them()] right in the face!", \ + "You step on [src] causing the handle to hit you right in the face!") + +/obj/item/hatchet + name = "hatchet" + desc = "A very sharp axe blade upon a short fibremetal handle. It has a long history of chopping things, but now it is used for chopping wood." + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "hatchet" + item_state = "hatchet" + lefthand_file = 'icons/mob/inhands/equipment/hydroponics_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/hydroponics_righthand.dmi' + flags_1 = CONDUCT_1 + force = 12 + w_class = WEIGHT_CLASS_SMALL + throwforce = 15 + throw_speed = 3 + throw_range = 4 + custom_materials = list(/datum/material/iron = 15000) + attack_verb = list("chopped", "torn", "cut") + hitsound = 'sound/weapons/bladeslice.ogg' + sharpness = IS_SHARP + +/obj/item/hatchet/Initialize() + . = ..() + AddComponent(/datum/component/butchering, 70, 100) + +/obj/item/hatchet/suicide_act(mob/user) + user.visible_message("[user] is chopping at [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit suicide!") + playsound(src, 'sound/weapons/bladeslice.ogg', 50, TRUE, -1) + return (BRUTELOSS) + +/obj/item/hatchet/wooden + desc = "A crude axe blade upon a short wooden handle." + icon_state = "woodhatchet" + custom_materials = null + flags_1 = NONE + +/obj/item/scythe + icon_state = "scythe0" + lefthand_file = 'icons/mob/inhands/weapons/polearms_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/polearms_righthand.dmi' + name = "scythe" + desc = "A sharp and curved blade on a long fibremetal handle, this tool makes it easy to reap what you sow." + force = 13 + throwforce = 5 + throw_speed = 2 + throw_range = 3 + w_class = WEIGHT_CLASS_BULKY + flags_1 = CONDUCT_1 + armour_penetration = 20 + slot_flags = ITEM_SLOT_BACK + attack_verb = list("chopped", "sliced", "cut", "reaped") + hitsound = 'sound/weapons/bladeslice.ogg' + var/swiping = FALSE + +/obj/item/scythe/Initialize() + . = ..() + AddComponent(/datum/component/butchering, 90, 105) + +/obj/item/scythe/suicide_act(mob/user) + user.visible_message("[user] is beheading [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit suicide!") + if(iscarbon(user)) + var/mob/living/carbon/C = user + var/obj/item/bodypart/BP = C.get_bodypart(BODY_ZONE_HEAD) + if(BP) + BP.drop_limb() + playsound(src, "desceration" ,50, TRUE, -1) + return (BRUTELOSS) + +/obj/item/scythe/pre_attack(atom/A, mob/living/user, params) + if(swiping || !istype(A, /obj/structure/spacevine) || get_turf(A) == get_turf(user)) + return ..() + var/turf/user_turf = get_turf(user) + var/dir_to_target = get_dir(user_turf, get_turf(A)) + swiping = TRUE + var/static/list/scythe_slash_angles = list(0, 45, 90, -45, -90) + for(var/i in scythe_slash_angles) + var/turf/T = get_step(user_turf, turn(dir_to_target, i)) + for(var/obj/structure/spacevine/V in T) + if(user.Adjacent(V)) + melee_attack_chain(user, V) + swiping = FALSE + return TRUE + +// ************************************* +// Nutrient defines for hydroponics +// ************************************* + + +/obj/item/reagent_containers/glass/bottle/nutrient + name = "bottle of nutrient" + volume = 50 + amount_per_transfer_from_this = 10 + possible_transfer_amounts = list(1,2,5,10,15,25,50) + +/obj/item/reagent_containers/glass/bottle/nutrient/Initialize() + . = ..() + pixel_x = rand(-5, 5) + pixel_y = rand(-5, 5) + + +/obj/item/reagent_containers/glass/bottle/nutrient/ez + name = "bottle of E-Z-Nutrient" + desc = "Contains a fertilizer that causes mild mutations with each harvest." + list_reagents = list(/datum/reagent/plantnutriment/eznutriment = 50) + +/obj/item/reagent_containers/glass/bottle/nutrient/l4z + name = "bottle of Left 4 Zed" + desc = "Contains a fertilizer that limits plant yields to no more than one and causes significant mutations in plants." + list_reagents = list(/datum/reagent/plantnutriment/left4zednutriment = 50) + +/obj/item/reagent_containers/glass/bottle/nutrient/rh + name = "bottle of Robust Harvest" + desc = "Contains a fertilizer that increases the yield of a plant by 30% while causing no mutations." + list_reagents = list(/datum/reagent/plantnutriment/robustharvestnutriment = 50) + +/obj/item/reagent_containers/glass/bottle/nutrient/empty + name = "bottle" + +/obj/item/reagent_containers/glass/bottle/killer + volume = 50 + amount_per_transfer_from_this = 10 + possible_transfer_amounts = list(1,2,5,10,15,25,50) + +/obj/item/reagent_containers/glass/bottle/killer/weedkiller + name = "bottle of weed killer" + desc = "Contains a herbicide." + list_reagents = list(/datum/reagent/toxin/plantbgone/weedkiller = 50) + +/obj/item/reagent_containers/glass/bottle/killer/pestkiller + name = "bottle of pest spray" + desc = "Contains a pesticide." + list_reagents = list(/datum/reagent/toxin/pestkiller = 50) diff --git a/code/modules/hydroponics/hydroponics.dm b/code/modules/hydroponics/hydroponics.dm index 2df185c4b849..b5f86328ff87 100644 --- a/code/modules/hydroponics/hydroponics.dm +++ b/code/modules/hydroponics/hydroponics.dm @@ -1,973 +1,973 @@ -#define TRAY_NAME_UPDATE name = myseed ? "[initial(name)] ([myseed.plantname])" : initial(name) - -/obj/machinery/hydroponics - name = "hydroponics tray" - icon = 'icons/obj/hydroponics/equipment.dmi' - icon_state = "hydrotray" - density = TRUE - pixel_z = 8 - obj_flags = CAN_BE_HIT | UNIQUE_RENAME - circuit = /obj/item/circuitboard/machine/hydroponics - var/waterlevel = 100 //The amount of water in the tray (max 100) - var/maxwater = 100 //The maximum amount of water in the tray - var/nutrilevel = 10 //The amount of nutrient in the tray (max 10) - var/maxnutri = 10 //The maximum nutrient of water in the tray - var/pestlevel = 0 //The amount of pests in the tray (max 10) - var/weedlevel = 0 //The amount of weeds in the tray (max 10) - var/yieldmod = 1 //Nutriment's effect on yield - var/mutmod = 1 //Nutriment's effect on mutations - var/toxic = 0 //Toxicity in the tray? - var/age = 0 //Current age - var/dead = 0 //Is it dead? - var/plant_health //Its health - var/lastproduce = 0 //Last time it was harvested - var/lastcycle = 0 //Used for timing of cycles. - var/cycledelay = 200 //About 10 seconds / cycle - var/harvest = 0 //Ready to harvest? - var/obj/item/seeds/myseed = null //The currently planted seed - var/rating = 1 - var/unwrenchable = 1 - var/recent_bee_visit = FALSE //Have we been visited by a bee recently, so bees dont overpollinate one plant - var/using_irrigation = FALSE //If the tray is connected to other trays via irrigation hoses - var/self_sufficiency_req = 20 //Required total dose to make a self-sufficient hydro tray. 1:1 with earthsblood. - var/self_sufficiency_progress = 0 - var/self_sustaining = FALSE //If the tray generates nutrients and water on its own - - -/obj/machinery/hydroponics/constructable - name = "hydroponics tray" - icon = 'icons/obj/hydroponics/equipment.dmi' - icon_state = "hydrotray3" - -/obj/machinery/hydroponics/constructable/RefreshParts() - var/tmp_capacity = 0 - for (var/obj/item/stock_parts/matter_bin/M in component_parts) - tmp_capacity += M.rating - for (var/obj/item/stock_parts/manipulator/M in component_parts) - rating = M.rating - maxwater = tmp_capacity * 50 // Up to 300 - maxnutri = tmp_capacity * 5 // Up to 30 - -/obj/machinery/hydroponics/constructable/examine(mob/user) - . = ..() - if(in_range(user, src) || isobserver(user)) - . += "The status display reads: Tray efficiency at [rating*100]%." - - -/obj/machinery/hydroponics/Destroy() - if(myseed) - qdel(myseed) - myseed = null - return ..() - -/obj/machinery/hydroponics/constructable/attackby(obj/item/I, mob/user, params) - if (user.a_intent != INTENT_HARM) - // handle opening the panel - if(default_deconstruction_screwdriver(user, icon_state, icon_state, I)) - return - - // handle deconstructing the machine, if permissible - if(I.tool_behaviour == TOOL_CROWBAR && using_irrigation) - to_chat(user, "Disconnect the hoses first!") - return - else if(default_deconstruction_crowbar(I)) - return - - return ..() - -/obj/machinery/hydroponics/proc/FindConnected() - var/list/connected = list() - var/list/processing_atoms = list(src) - - while(processing_atoms.len) - var/atom/a = processing_atoms[1] - for(var/step_dir in GLOB.cardinals) - var/obj/machinery/hydroponics/h = locate() in get_step(a, step_dir) - // Soil plots aren't dense - if(h && h.using_irrigation && h.density && !(h in connected) && !(h in processing_atoms)) - processing_atoms += h - - processing_atoms -= a - connected += a - - return connected - -/obj/machinery/hydroponics/bullet_act(obj/projectile/Proj) //Works with the Somatoray to modify plant variables. - if(!myseed) - return ..() - if(istype(Proj , /obj/projectile/energy/floramut)) - mutate() - else if(istype(Proj , /obj/projectile/energy/florayield)) - return myseed.bullet_act(Proj) - else - return ..() - -/obj/machinery/hydroponics/process() - var/needs_update = 0 // Checks if the icon needs updating so we don't redraw empty trays every time - - if(myseed && (myseed.loc != src)) - myseed.forceMove(src) - - if(self_sustaining) - adjustNutri(1) - adjustWater(rand(3,5)) - adjustWeeds(-2) - adjustPests(-2) - adjustToxic(-2) - - if(world.time > (lastcycle + cycledelay)) - lastcycle = world.time - if(myseed && !dead) - // Advance age - age++ - if(age < myseed.maturation) - lastproduce = age - - needs_update = 1 - - -//Nutrients////////////////////////////////////////////////////////////// - // Nutrients deplete slowly - if(prob(50)) - adjustNutri(-1 / rating) - - // Lack of nutrients hurts non-weeds - if(nutrilevel <= 0 && !myseed.get_gene(/datum/plant_gene/trait/plant_type/weed_hardy)) - adjustHealth(-rand(1,3)) - -//Photosynthesis///////////////////////////////////////////////////////// - // Lack of light hurts non-mushrooms - if(isturf(loc)) - var/turf/currentTurf = loc - var/lightAmt = currentTurf.get_lumcount() - if(myseed.get_gene(/datum/plant_gene/trait/plant_type/fungal_metabolism)) - if(lightAmt < 0.2) - adjustHealth(-1 / rating) - else // Non-mushroom - if(lightAmt < 0.4) - adjustHealth(-2 / rating) - -//Water////////////////////////////////////////////////////////////////// - // Drink random amount of water - adjustWater(-rand(1,6) / rating) - - // If the plant is dry, it loses health pretty fast, unless mushroom - if(waterlevel <= 10 && !myseed.get_gene(/datum/plant_gene/trait/plant_type/fungal_metabolism)) - adjustHealth(-rand(0,1) / rating) - if(waterlevel <= 0) - adjustHealth(-rand(0,2) / rating) - - // Sufficient water level and nutrient level = plant healthy but also spawns weeds - else if(waterlevel > 10 && nutrilevel > 0) - adjustHealth(rand(1,2) / rating) - if(myseed && prob(myseed.weed_chance)) - adjustWeeds(myseed.weed_rate) - else if(prob(5)) //5 percent chance the weed population will increase - adjustWeeds(1 / rating) - -//Toxins///////////////////////////////////////////////////////////////// - - // Too much toxins cause harm, but when the plant drinks the contaiminated water, the toxins disappear slowly - if(toxic >= 40 && toxic < 80) - adjustHealth(-1 / rating) - adjustToxic(-rand(1,10) / rating) - else if(toxic >= 80) // I don't think it ever gets here tbh unless above is commented out - adjustHealth(-3) - adjustToxic(-rand(1,10) / rating) - -//Pests & Weeds////////////////////////////////////////////////////////// - - if(pestlevel >= 8) - if(!myseed.get_gene(/datum/plant_gene/trait/plant_type/carnivory)) - adjustHealth(-2 / rating) - - else - adjustHealth(2 / rating) - adjustPests(-1 / rating) - - else if(pestlevel >= 4) - if(!myseed.get_gene(/datum/plant_gene/trait/plant_type/carnivory)) - adjustHealth(-1 / rating) - - else - adjustHealth(1 / rating) - if(prob(50)) - adjustPests(-1 / rating) - - else if(pestlevel < 4 && myseed.get_gene(/datum/plant_gene/trait/plant_type/carnivory)) - adjustHealth(-2 / rating) - if(prob(5)) - adjustPests(-1 / rating) - - // If it's a weed, it doesn't stunt the growth - if(weedlevel >= 5 && !myseed.get_gene(/datum/plant_gene/trait/plant_type/weed_hardy)) - adjustHealth(-1 / rating) - -//Health & Age/////////////////////////////////////////////////////////// - - // Plant dies if plant_health <= 0 - if(plant_health <= 0) - plantdies() - adjustWeeds(1 / rating) // Weeds flourish - - // If the plant is too old, lose health fast - if(age > myseed.lifespan) - adjustHealth(-rand(1,5) / rating) - - // Harvest code - if(age > myseed.production && (age - lastproduce) > myseed.production && (!harvest && !dead)) - nutrimentMutation() - if(myseed && myseed.yield != -1) // Unharvestable shouldn't be harvested - harvest = 1 - else - lastproduce = age - if(prob(5)) // On each tick, there's a 5 percent chance the pest population will increase - adjustPests(1 / rating) - else - if(waterlevel > 10 && nutrilevel > 0 && prob(10)) // If there's no plant, the percentage chance is 10% - adjustWeeds(1 / rating) - - // Weeeeeeeeeeeeeeedddssss - if(weedlevel >= 10 && prob(50) && !self_sustaining) // At this point the plant is kind of fucked. Weeds can overtake the plant spot. - if(myseed) - if(!myseed.get_gene(/datum/plant_gene/trait/plant_type/weed_hardy) && !myseed.get_gene(/datum/plant_gene/trait/plant_type/fungal_metabolism)) // If a normal plant - weedinvasion() - else - weedinvasion() // Weed invasion into empty tray - needs_update = 1 - if (needs_update) - update_icon() - - if(myseed && prob(5 * (11-myseed.production))) - for(var/g in myseed.genes) - if(istype(g, /datum/plant_gene/trait)) - var/datum/plant_gene/trait/selectedtrait = g - selectedtrait.on_grow(src) - return - -/obj/machinery/hydroponics/proc/nutrimentMutation() - if (mutmod == 0) - return - if (mutmod == 1) - if(prob(80)) //80% - mutate() - else if(prob(75)) //15% - hardmutate() - return - if (mutmod == 2) - if(prob(50)) //50% - mutate() - else if(prob(50)) //25% - hardmutate() - else if(prob(50)) //12.5% - mutatespecie() - return - return - -/obj/machinery/hydroponics/update_icon() - //Refreshes the icon and sets the luminosity - cut_overlays() - - if(self_sustaining) - if(istype(src, /obj/machinery/hydroponics/soil)) - add_atom_colour(rgb(255, 175, 0), FIXED_COLOUR_PRIORITY) - else - add_overlay(mutable_appearance('icons/obj/hydroponics/equipment.dmi', "gaia_blessing")) - set_light(3) - - update_icon_hoses() - - if(myseed) - update_icon_plant() - update_icon_lights() - - if(!self_sustaining) - if(myseed && myseed.get_gene(/datum/plant_gene/trait/glow)) - var/datum/plant_gene/trait/glow/G = myseed.get_gene(/datum/plant_gene/trait/glow) - set_light(G.glow_range(myseed), G.glow_power(myseed), G.glow_color) - else - set_light(0) - - return - -/obj/machinery/hydroponics/proc/update_icon_hoses() - var/n = 0 - for(var/Dir in GLOB.cardinals) - var/obj/machinery/hydroponics/t = locate() in get_step(src,Dir) - if(t && t.using_irrigation && using_irrigation) - n += Dir - - icon_state = "hoses-[n]" - -/obj/machinery/hydroponics/proc/update_icon_plant() - var/mutable_appearance/plant_overlay = mutable_appearance(myseed.growing_icon, layer = OBJ_LAYER + 0.01) - if(dead) - plant_overlay.icon_state = myseed.icon_dead - else if(harvest) - if(!myseed.icon_harvest) - plant_overlay.icon_state = "[myseed.icon_grow][myseed.growthstages]" - else - plant_overlay.icon_state = myseed.icon_harvest - else - var/t_growthstate = min(round((age / myseed.maturation) * myseed.growthstages), myseed.growthstages) - plant_overlay.icon_state = "[myseed.icon_grow][t_growthstate]" - add_overlay(plant_overlay) - -/obj/machinery/hydroponics/proc/update_icon_lights() - if(waterlevel <= 10) - add_overlay(mutable_appearance('icons/obj/hydroponics/equipment.dmi', "over_lowwater3")) - if(nutrilevel <= 2) - add_overlay(mutable_appearance('icons/obj/hydroponics/equipment.dmi', "over_lownutri3")) - if(plant_health <= (myseed.endurance / 2)) - add_overlay(mutable_appearance('icons/obj/hydroponics/equipment.dmi', "over_lowhealth3")) - if(weedlevel >= 5 || pestlevel >= 5 || toxic >= 40) - add_overlay(mutable_appearance('icons/obj/hydroponics/equipment.dmi', "over_alert3")) - if(harvest) - add_overlay(mutable_appearance('icons/obj/hydroponics/equipment.dmi', "over_harvest3")) - - -/obj/machinery/hydroponics/examine(user) - . = ..() - if(myseed) - . += "It has [myseed.plantname] planted." - if (dead) - . += "It's dead!" - else if (harvest) - . += "It's ready to harvest." - else if (plant_health <= (myseed.endurance / 2)) - . += "It looks unhealthy." - else - . += "It's empty." - - if(!self_sustaining) - . += "Water: [waterlevel]/[maxwater].\n"+\ - "Nutrient: [nutrilevel]/[maxnutri]." - if(self_sufficiency_progress > 0) - var/percent_progress = round(self_sufficiency_progress * 100 / self_sufficiency_req) - . += "Treatment for self-sustenance are [percent_progress]% complete." - else - . += "It doesn't require any water or nutrients." - - if(weedlevel >= 5) - to_chat(user, "It's filled with weeds!") - if(pestlevel >= 5) - to_chat(user, "It's filled with tiny worms!") - to_chat(user, "" ) - - -/obj/machinery/hydroponics/proc/weedinvasion() // If a weed growth is sufficient, this happens. - dead = 0 - var/oldPlantName - if(myseed) // In case there's nothing in the tray beforehand - oldPlantName = myseed.plantname - qdel(myseed) - myseed = null - else - oldPlantName = "empty tray" - switch(rand(1,18)) // randomly pick predominative weed - if(16 to 18) - myseed = new /obj/item/seeds/reishi(src) - if(14 to 15) - myseed = new /obj/item/seeds/nettle(src) - if(12 to 13) - myseed = new /obj/item/seeds/harebell(src) - if(10 to 11) - myseed = new /obj/item/seeds/amanita(src) - if(8 to 9) - myseed = new /obj/item/seeds/chanter(src) - if(6 to 7) - myseed = new /obj/item/seeds/tower(src) - if(4 to 5) - myseed = new /obj/item/seeds/plump(src) - else - myseed = new /obj/item/seeds/starthistle(src) - age = 0 - plant_health = myseed.endurance - lastcycle = world.time - harvest = 0 - weedlevel = 0 // Reset - pestlevel = 0 // Reset - update_icon() - visible_message("The [oldPlantName] is overtaken by some [myseed.plantname]!") - TRAY_NAME_UPDATE - -/obj/machinery/hydroponics/proc/mutate(lifemut = 2, endmut = 5, productmut = 1, yieldmut = 2, potmut = 25, wrmut = 2, wcmut = 5, traitmut = 0) // Mutates the current seed - if(!myseed) - return - myseed.mutate(lifemut, endmut, productmut, yieldmut, potmut, wrmut, wcmut, traitmut) - -/obj/machinery/hydroponics/proc/hardmutate() - mutate(4, 10, 2, 4, 50, 4, 10, 3) - - -/obj/machinery/hydroponics/proc/mutatespecie() // Mutagent produced a new plant! - if(!myseed || dead) - return - - var/oldPlantName = myseed.plantname - if(myseed.mutatelist.len > 0) - var/mutantseed = pick(myseed.mutatelist) - qdel(myseed) - myseed = null - myseed = new mutantseed - else - return - - hardmutate() - age = 0 - plant_health = myseed.endurance - lastcycle = world.time - harvest = 0 - weedlevel = 0 // Reset - - sleep(5) // Wait a while - update_icon() - visible_message("[oldPlantName] suddenly mutates into [myseed.plantname]!") - TRAY_NAME_UPDATE - -/obj/machinery/hydroponics/proc/mutateweed() // If the weeds gets the mutagent instead. Mind you, this pretty much destroys the old plant - if( weedlevel > 5 ) - if(myseed) - qdel(myseed) - myseed = null - var/newWeed = pick(/obj/item/seeds/liberty, /obj/item/seeds/angel, /obj/item/seeds/nettle/death, /obj/item/seeds/kudzu) - myseed = new newWeed - dead = 0 - hardmutate() - age = 0 - plant_health = myseed.endurance - lastcycle = world.time - harvest = 0 - weedlevel = 0 // Reset - - sleep(5) // Wait a while - update_icon() - visible_message("The mutated weeds in [src] spawn some [myseed.plantname]!") - TRAY_NAME_UPDATE - else - to_chat(usr, "The few weeds in [src] seem to react, but only for a moment...") - - -/obj/machinery/hydroponics/proc/plantdies() // OH NOES!!!!! I put this all in one function to make things easier - plant_health = 0 - harvest = 0 - pestlevel = 0 // Pests die - lastproduce = 0 - if(!dead) - update_icon() - dead = 1 - - - -/obj/machinery/hydroponics/proc/mutatepest(mob/user) - if(pestlevel > 5) - message_admins("[ADMIN_LOOKUPFLW(user)] caused spiderling pests to spawn in a hydro tray") - log_game("[key_name(user)] caused spiderling pests to spawn in a hydro tray") - visible_message("The pests seem to behave oddly...") - spawn_atom_to_turf(/obj/structure/spider/spiderling/hunter, src, 3, FALSE) - else - to_chat(user, "The pests seem to behave oddly, but quickly settle down...") - -/obj/machinery/hydroponics/proc/applyChemicals(datum/reagents/S, mob/user) - if(myseed) - myseed.on_chem_reaction(S) //In case seeds have some special interactions with special chems, currently only used by vines - - // Requires 5 mutagen to possibly change species.// Poor man's mutagen. - if(S.has_reagent(/datum/reagent/toxin/mutagen, 5) || S.has_reagent(/datum/reagent/uranium/radium, 10) || S.has_reagent(/datum/reagent/uranium, 10)) - switch(rand(100)) - if(91 to 100) - adjustHealth(-10) - to_chat(user, "The plant shrivels and burns.") - if(81 to 90) - mutatespecie() - if(66 to 80) - hardmutate() - if(41 to 65) - mutate() - if(21 to 41) - to_chat(user, "The plants don't seem to react...") - if(11 to 20) - mutateweed() - if(1 to 10) - mutatepest(user) - else - to_chat(user, "Nothing happens...") - - // 2 or 1 units is enough to change the yield and other stats.// Can change the yield and other stats, but requires more than mutagen - else if(S.has_reagent(/datum/reagent/toxin/mutagen, 2) || S.has_reagent(/datum/reagent/uranium/radium, 5) || S.has_reagent(/datum/reagent/uranium, 5)) - hardmutate() - else if(S.has_reagent(/datum/reagent/toxin/mutagen, 1) || S.has_reagent(/datum/reagent/uranium/radium, 2) || S.has_reagent(/datum/reagent/uranium, 2)) - mutate() - - // After handling the mutating, we now handle the damage from adding crude radioactives... - if(S.has_reagent(/datum/reagent/uranium, 1)) - adjustHealth(-round(S.get_reagent_amount(/datum/reagent/uranium) * 1)) - adjustToxic(round(S.get_reagent_amount(/datum/reagent/uranium) * 2)) - if(S.has_reagent(/datum/reagent/uranium/radium, 1)) - adjustHealth(-round(S.get_reagent_amount(/datum/reagent/uranium/radium) * 1)) - adjustToxic(round(S.get_reagent_amount(/datum/reagent/uranium/radium) * 3)) // Radium is harsher (OOC: also easier to produce) - - // Nutriments - if(S.has_reagent(/datum/reagent/plantnutriment/eznutriment, 1)) - yieldmod = 1 - mutmod = 1 - adjustNutri(round(S.get_reagent_amount(/datum/reagent/plantnutriment/eznutriment) * 1)) - - if(S.has_reagent(/datum/reagent/plantnutriment/left4zednutriment, 1)) - yieldmod = 0 - mutmod = 2 - adjustNutri(round(S.get_reagent_amount(/datum/reagent/plantnutriment/left4zednutriment) * 1)) - - if(S.has_reagent(/datum/reagent/plantnutriment/robustharvestnutriment, 1)) - yieldmod = 1.3 - mutmod = 0 - adjustNutri(round(S.get_reagent_amount(/datum/reagent/plantnutriment/robustharvestnutriment) *1 )) - - // Ambrosia Gaia produces earthsblood. - if(S.has_reagent(/datum/reagent/medicine/earthsblood)) - self_sufficiency_progress += S.get_reagent_amount(/datum/reagent/medicine/earthsblood) - if(self_sufficiency_progress >= self_sufficiency_req) - become_self_sufficient() - else if(!self_sustaining) - to_chat(user, "[src] warms as it might on a spring day under a genuine Sun.") - - // Antitoxin binds shit pretty well. So the tox goes significantly down - if(S.has_reagent(/datum/reagent/medicine/C2/multiver, 1)) - adjustToxic(-round(S.get_reagent_amount(/datum/reagent/medicine/C2/multiver) * 2)) - - // NIGGA, YOU JUST WENT ON FULL RETARD. - if(S.has_reagent(/datum/reagent/toxin, 1)) - adjustToxic(round(S.get_reagent_amount(/datum/reagent/toxin) * 2)) - - // Milk is good for humans, but bad for plants. The sugars canot be used by plants, and the milk fat fucks up growth. Not shrooms though. I can't deal with this now... - if(S.has_reagent(/datum/reagent/consumable/milk, 1)) - adjustNutri(round(S.get_reagent_amount(/datum/reagent/consumable/milk) * 0.1)) - adjustWater(round(S.get_reagent_amount(/datum/reagent/consumable/milk) * 0.9)) - - // Beer is a chemical composition of alcohol and various other things. It's a shitty nutrient but hey, it's still one. Also alcohol is bad, mmmkay? - if(S.has_reagent(/datum/reagent/consumable/ethanol/beer, 1)) - adjustHealth(-round(S.get_reagent_amount(/datum/reagent/consumable/ethanol/beer) * 0.05)) - adjustNutri(round(S.get_reagent_amount(/datum/reagent/consumable/ethanol/beer) * 0.25)) - adjustWater(round(S.get_reagent_amount(/datum/reagent/consumable/ethanol/beer) * 0.7)) - - // You're an idiot for thinking that one of the most corrosive and deadly gasses would be beneficial - if(S.has_reagent(/datum/reagent/fluorine, 1)) - adjustHealth(-round(S.get_reagent_amount(/datum/reagent/fluorine) * 2)) - adjustToxic(round(S.get_reagent_amount(/datum/reagent/fluorine) * 2.5)) - adjustWater(-round(S.get_reagent_amount(/datum/reagent/fluorine) * 0.5)) - adjustWeeds(-rand(1,4)) - - // You're an idiot for thinking that one of the most corrosive and deadly gasses would be beneficial - if(S.has_reagent(/datum/reagent/chlorine, 1)) - adjustHealth(-round(S.get_reagent_amount(/datum/reagent/chlorine) * 1)) - adjustToxic(round(S.get_reagent_amount(/datum/reagent/chlorine) * 1.5)) - adjustWater(-round(S.get_reagent_amount(/datum/reagent/chlorine) * 0.5)) - adjustWeeds(-rand(1,3)) - - // White Phosphorous + water -> phosphoric acid. That's not a good thing really. - // Phosphoric salts are beneficial though. And even if the plant suffers, in the long run the tray gets some nutrients. The benefit isn't worth that much. - if(S.has_reagent(/datum/reagent/phosphorus, 1)) - adjustHealth(-round(S.get_reagent_amount(/datum/reagent/phosphorus) * 0.75)) - adjustNutri(round(S.get_reagent_amount(/datum/reagent/phosphorus) * 0.1)) - adjustWater(-round(S.get_reagent_amount(/datum/reagent/phosphorus) * 0.5)) - adjustWeeds(-rand(1,2)) - - // Plants should not have sugar, they can't use it and it prevents them getting water/ nutients, it is good for mold though... - if(S.has_reagent(/datum/reagent/consumable/sugar, 1)) - adjustWeeds(rand(1,2)) - adjustPests(rand(1,2)) - adjustNutri(round(S.get_reagent_amount(/datum/reagent/consumable/sugar) * 0.1)) - - // It is water! - if(S.has_reagent(/datum/reagent/water, 1)) - adjustWater(round(S.get_reagent_amount(/datum/reagent/water) * 1)) - - // Holy water. Mostly the same as water, it also heals the plant a little with the power of the spirits~ - if(S.has_reagent(/datum/reagent/water/holywater, 1)) - adjustWater(round(S.get_reagent_amount(/datum/reagent/water/holywater) * 1)) - adjustHealth(round(S.get_reagent_amount(/datum/reagent/water/holywater) * 0.1)) - - // A variety of nutrients are dissolved in club soda, without sugar. - // These nutrients include carbon, oxygen, hydrogen, phosphorous, potassium, sulfur and sodium, all of which are needed for healthy plant growth. - if(S.has_reagent(/datum/reagent/consumable/sodawater, 1)) - adjustWater(round(S.get_reagent_amount(/datum/reagent/consumable/sodawater) * 1)) - adjustHealth(round(S.get_reagent_amount(/datum/reagent/consumable/sodawater) * 0.1)) - adjustNutri(round(S.get_reagent_amount(/datum/reagent/consumable/sodawater) * 0.1)) - - // Man, you guys are retards - if(S.has_reagent(/datum/reagent/toxin/acid, 1)) - adjustHealth(-round(S.get_reagent_amount(/datum/reagent/toxin/acid) * 1)) - adjustToxic(round(S.get_reagent_amount(/datum/reagent/toxin/acid) * 1.5)) - adjustWeeds(-rand(1,2)) - - // SERIOUSLY - if(S.has_reagent(/datum/reagent/toxin/acid/fluacid, 1)) - adjustHealth(-round(S.get_reagent_amount(/datum/reagent/toxin/acid/fluacid) * 2)) - adjustToxic(round(S.get_reagent_amount(/datum/reagent/toxin/acid/fluacid) * 3)) - adjustWeeds(-rand(1,4)) - - // Plant-B-Gone is just as bad - if(S.has_reagent(/datum/reagent/toxin/plantbgone, 1)) - adjustHealth(-round(S.get_reagent_amount(/datum/reagent/toxin/plantbgone) * 5)) - adjustToxic(round(S.get_reagent_amount(/datum/reagent/toxin/plantbgone) * 6)) - adjustWeeds(-rand(4,8)) - - // why, just why - if(S.has_reagent(/datum/reagent/napalm, 1)) - if(!(myseed.resistance_flags & FIRE_PROOF)) - adjustHealth(-round(S.get_reagent_amount(/datum/reagent/napalm) * 6)) - adjustToxic(round(S.get_reagent_amount(/datum/reagent/napalm) * 7)) - adjustWeeds(-rand(5,9)) //At least give them a small reward if they bother. - - //Weed Spray - if(S.has_reagent(/datum/reagent/toxin/plantbgone/weedkiller, 1)) - adjustToxic(round(S.get_reagent_amount(/datum/reagent/toxin/plantbgone/weedkiller) * 0.5)) - //old toxicity was 4, each spray is default 10 (minimal of 5) so 5 and 2.5 are the new ammounts - adjustWeeds(-rand(1,2)) - - //Pest Spray - if(S.has_reagent(/datum/reagent/toxin/pestkiller, 1)) - adjustToxic(round(S.get_reagent_amount(/datum/reagent/toxin/pestkiller) * 0.5)) - adjustPests(-rand(1,2)) - - //Nicotine is used as a pesticide IRL. - if(S.has_reagent(/datum/reagent/drug/nicotine, 1)) - adjustToxic(round(S.get_reagent_amount(/datum/reagent/drug/nicotine))) - adjustPests(-rand(1,2)) - - // Healing - if(S.has_reagent(/datum/reagent/medicine/cryoxadone, 1)) - adjustHealth(round(S.get_reagent_amount(/datum/reagent/medicine/cryoxadone) * 3)) - adjustToxic(-round(S.get_reagent_amount(/datum/reagent/medicine/cryoxadone) * 3)) - - // Ammonia is bad ass. - if(S.has_reagent(/datum/reagent/ammonia, 1)) - adjustHealth(round(S.get_reagent_amount(/datum/reagent/ammonia) * 0.5)) - adjustNutri(round(S.get_reagent_amount(/datum/reagent/ammonia) * 1)) - if(myseed) - myseed.adjust_yield(round(S.get_reagent_amount(/datum/reagent/ammonia) * 0.04)) - - // Saltpetre is used for gardening IRL, to simplify highly, it speeds up growth and strengthens plants - if(S.has_reagent(/datum/reagent/saltpetre, 1)) - var/salt = S.get_reagent_amount(/datum/reagent/saltpetre) - adjustHealth(round(salt * 0.25)) - if (myseed) - myseed.adjust_production(-round(salt/100)-prob(salt%100)) - myseed.adjust_potency(round(salt*0.5)) - // Ash is also used IRL in gardening, as a fertilizer enhancer and weed killer - if(S.has_reagent(/datum/reagent/ash, 1)) - adjustHealth(round(S.get_reagent_amount(/datum/reagent/ash) * 0.25)) - adjustNutri(round(S.get_reagent_amount(/datum/reagent/ash) * 0.5)) - adjustWeeds(-1) - - // This is more bad ass, and pests get hurt by the corrosive nature of it, not the plant. - if(S.has_reagent(/datum/reagent/diethylamine, 1)) - adjustHealth(round(S.get_reagent_amount(/datum/reagent/diethylamine) * 1)) - adjustNutri(round(S.get_reagent_amount(/datum/reagent/diethylamine) * 2)) - if(myseed) - myseed.adjust_yield(round(S.get_reagent_amount(/datum/reagent/diethylamine) * 0.02)) - adjustPests(-rand(1,2)) - - // Compost, effectively - if(S.has_reagent(/datum/reagent/consumable/nutriment, 1)) - adjustHealth(round(S.get_reagent_amount(/datum/reagent/consumable/nutriment) * 0.5)) - adjustNutri(round(S.get_reagent_amount(/datum/reagent/consumable/nutriment) * 1)) - - // Compost for EVERYTHING - if(S.has_reagent(/datum/reagent/consumable/virus_food, 1)) - adjustNutri(round(S.get_reagent_amount(/datum/reagent/consumable/virus_food) * 0.5)) - adjustHealth(-round(S.get_reagent_amount(/datum/reagent/consumable/virus_food) * 0.5)) - - // FEED ME - if(S.has_reagent(/datum/reagent/blood, 1)) - adjustNutri(round(S.get_reagent_amount(/datum/reagent/blood) * 1)) - adjustPests(rand(2,4)) - - // FEED ME SEYMOUR - if(S.has_reagent(/datum/reagent/medicine/strange_reagent, 1)) - spawnplant() - - // The best stuff there is. For testing/debugging. - if(S.has_reagent(/datum/reagent/medicine/adminordrazine, 1)) - adjustWater(round(S.get_reagent_amount(/datum/reagent/medicine/adminordrazine) * 1)) - adjustHealth(round(S.get_reagent_amount(/datum/reagent/medicine/adminordrazine) * 1)) - adjustNutri(round(S.get_reagent_amount(/datum/reagent/medicine/adminordrazine) * 1)) - adjustPests(-rand(1,5)) - adjustWeeds(-rand(1,5)) - if(S.has_reagent(/datum/reagent/medicine/adminordrazine, 5)) - switch(rand(100)) - if(66 to 100) - mutatespecie() - if(33 to 65) - mutateweed() - if(1 to 32) - mutatepest(user) - else - to_chat(user, "Nothing happens...") - -/obj/machinery/hydroponics/attackby(obj/item/O, mob/user, params) - //Called when mob user "attacks" it with object O - if(istype(O, /obj/item/reagent_containers) ) // Syringe stuff (and other reagent containers now too) - var/obj/item/reagent_containers/reagent_source = O - - if(istype(reagent_source, /obj/item/reagent_containers/syringe)) - var/obj/item/reagent_containers/syringe/syr = reagent_source - if(syr.mode != 1) - to_chat(user, "You can't get any extract out of this plant." ) - return - - if(!reagent_source.reagents.total_volume) - to_chat(user, "[reagent_source] is empty!") - return 1 - - var/list/trays = list(src)//makes the list just this in cases of syringes and compost etc - var/target = myseed ? myseed.plantname : src - var/visi_msg = "" - var/irrigate = 0 //How am I supposed to irrigate pill contents? - var/transfer_amount - - if(istype(reagent_source, /obj/item/reagent_containers/food/snacks) || istype(reagent_source, /obj/item/reagent_containers/pill)) - if(istype(reagent_source, /obj/item/reagent_containers/food/snacks)) - var/obj/item/reagent_containers/food/snacks/R = reagent_source - if (R.trash) - R.generate_trash(get_turf(user)) - visi_msg="[user] composts [reagent_source], spreading it through [target]" - transfer_amount = reagent_source.reagents.total_volume - else - transfer_amount = reagent_source.amount_per_transfer_from_this - if(istype(reagent_source, /obj/item/reagent_containers/syringe/)) - var/obj/item/reagent_containers/syringe/syr = reagent_source - visi_msg="[user] injects [target] with [syr]" - if(syr.reagents.total_volume <= syr.amount_per_transfer_from_this) - syr.mode = 0 - else if(istype(reagent_source, /obj/item/reagent_containers/spray/)) - visi_msg="[user] sprays [target] with [reagent_source]" - playsound(loc, 'sound/effects/spray3.ogg', 50, TRUE, -6) - irrigate = 1 - else if(transfer_amount) // Droppers, cans, beakers, what have you. - visi_msg="[user] uses [reagent_source] on [target]" - irrigate = 1 - // Beakers, bottles, buckets, etc. - if(reagent_source.is_drainable()) - playsound(loc, 'sound/effects/slosh.ogg', 25, TRUE) - - if(irrigate && transfer_amount > 30 && reagent_source.reagents.total_volume >= 30 && using_irrigation) - trays = FindConnected() - if (trays.len > 1) - visi_msg += ", setting off the irrigation system" - - if(visi_msg) - visible_message("[visi_msg].") - - var/split = round(transfer_amount/trays.len) - - for(var/obj/machinery/hydroponics/H in trays) - //cause I don't want to feel like im juggling 15 tamagotchis and I can get to my real work of ripping flooring apart in hopes of validating my life choices of becoming a space-gardener - - var/datum/reagents/S = new /datum/reagents() //This is a strange way, but I don't know of a better one so I can't fix it at the moment... - S.my_atom = H - - reagent_source.reagents.trans_to(S,split, transfered_by = user) - if(istype(reagent_source, /obj/item/reagent_containers/food/snacks) || istype(reagent_source, /obj/item/reagent_containers/pill)) - qdel(reagent_source) - - H.applyChemicals(S, user) - - S.clear_reagents() - qdel(S) - H.update_icon() - if(reagent_source) // If the source wasn't composted and destroyed - reagent_source.update_icon() - return 1 - - else if(istype(O, /obj/item/seeds) && !istype(O, /obj/item/seeds/sample)) - if(!myseed) - if(istype(O, /obj/item/seeds/kudzu)) - investigate_log("had Kudzu planted in it by [key_name(user)] at [AREACOORD(src)]","kudzu") - if(!user.transferItemToLoc(O, src)) - return - to_chat(user, "You plant [O].") - dead = 0 - myseed = O - TRAY_NAME_UPDATE - age = 1 - plant_health = myseed.endurance - lastcycle = world.time - update_icon() - else - to_chat(user, "[src] already has seeds in it!") - - else if(istype(O, /obj/item/plant_analyzer)) - if(myseed) - to_chat(user, "*** [myseed.plantname] ***" ) - to_chat(user, "- Plant Age: [age]") - var/list/text_string = myseed.get_analyzer_text() - if(text_string) - to_chat(user, text_string) - else - to_chat(user, "No plant found.") - to_chat(user, "- Weed level: [weedlevel] / 10") - to_chat(user, "- Pest level: [pestlevel] / 10") - to_chat(user, "- Toxicity level: [toxic] / 100") - to_chat(user, "- Water level: [waterlevel] / [maxwater]") - to_chat(user, "- Nutrition level: [nutrilevel] / [maxnutri]") - to_chat(user, "") - - else if(istype(O, /obj/item/cultivator)) - if(weedlevel > 0) - user.visible_message("[user] uproots the weeds.", "You remove the weeds from [src].") - weedlevel = 0 - update_icon() - else - to_chat(user, "This plot is completely devoid of weeds! It doesn't need uprooting.") - - else if(istype(O, /obj/item/storage/bag/plants)) - attack_hand(user) - for(var/obj/item/reagent_containers/food/snacks/grown/G in locate(user.x,user.y,user.z)) - SEND_SIGNAL(O, COMSIG_TRY_STORAGE_INSERT, G, user, TRUE) - - else if(default_unfasten_wrench(user, O)) - return - - else if((O.tool_behaviour == TOOL_WIRECUTTER) && unwrenchable) - if (!anchored) - to_chat(user, "Anchor the tray first!") - return - using_irrigation = !using_irrigation - O.play_tool_sound(src) - user.visible_message("[user] [using_irrigation ? "" : "dis"]connects [src]'s irrigation hoses.", \ - "You [using_irrigation ? "" : "dis"]connect [src]'s irrigation hoses.") - for(var/obj/machinery/hydroponics/h in range(1,src)) - h.update_icon() - - else if(istype(O, /obj/item/shovel/spade)) - if(!myseed && !weedlevel) - to_chat(user, "[src] doesn't have any plants or weeds!") - return - user.visible_message("[user] starts digging out [src]'s plants...", - "You start digging out [src]'s plants...") - if(O.use_tool(src, user, 50, volume=50) || (!myseed && !weedlevel)) - user.visible_message("[user] digs out the plants in [src]!", "You dig out all of [src]'s plants!") - if(myseed) //Could be that they're just using it as a de-weeder - age = 0 - plant_health = 0 - lastproduce = 0 - if(harvest) - harvest = FALSE //To make sure they can't just put in another seed and insta-harvest it - qdel(myseed) - myseed = null - name = initial(name) - desc = initial(desc) - weedlevel = 0 //Has a side effect of cleaning up those nasty weeds - update_icon() - - - else - return ..() - -/obj/machinery/hydroponics/can_be_unfasten_wrench(mob/user, silent) - if (!unwrenchable) // case also covered by NODECONSTRUCT checks in default_unfasten_wrench - return CANT_UNFASTEN - - if (using_irrigation) - if (!silent) - to_chat(user, "Disconnect the hoses first!") - return FAILED_UNFASTEN - - return ..() - -/obj/machinery/hydroponics/attack_hand(mob/user) - . = ..() - if(.) - return - if(issilicon(user)) //How does AI know what plant is? - return - if(harvest) - return myseed.harvest(user) - - else if(dead) - dead = 0 - to_chat(user, "You remove the dead plant from [src].") - qdel(myseed) - myseed = null - update_icon() - TRAY_NAME_UPDATE - else - if(user) - examine(user) - -/obj/machinery/hydroponics/proc/update_tray(mob/user) - harvest = 0 - lastproduce = age - if(istype(myseed, /obj/item/seeds/replicapod)) - to_chat(user, "You harvest from the [myseed.plantname].") - else if(myseed.getYield() <= 0) - to_chat(user, "You fail to harvest anything useful!") - else - to_chat(user, "You harvest [myseed.getYield()] items from the [myseed.plantname].") - if(!myseed.get_gene(/datum/plant_gene/trait/repeated_harvest)) - qdel(myseed) - myseed = null - dead = 0 - TRAY_NAME_UPDATE - update_icon() - -/// Tray Setters - The following procs adjust the tray or plants variables, and make sure that the stat doesn't go out of bounds./// -/obj/machinery/hydroponics/proc/adjustNutri(adjustamt) - nutrilevel = clamp(nutrilevel + adjustamt, 0, maxnutri) - -/obj/machinery/hydroponics/proc/adjustWater(adjustamt) - waterlevel = clamp(waterlevel + adjustamt, 0, maxwater) - - if(adjustamt>0) - adjustToxic(-round(adjustamt/4))//Toxicity dilutation code. The more water you put in, the lesser the toxin concentration. - -/obj/machinery/hydroponics/proc/adjustHealth(adjustamt) - if(myseed && !dead) - plant_health = clamp(plant_health + adjustamt, 0, myseed.endurance) - -/obj/machinery/hydroponics/proc/adjustToxic(adjustamt) - toxic = clamp(toxic + adjustamt, 0, 100) - -/obj/machinery/hydroponics/proc/adjustPests(adjustamt) - pestlevel = clamp(pestlevel + adjustamt, 0, 10) - -/obj/machinery/hydroponics/proc/adjustWeeds(adjustamt) - weedlevel = clamp(weedlevel + adjustamt, 0, 10) - -/obj/machinery/hydroponics/proc/spawnplant() // why would you put strange reagent in a hydro tray you monster I bet you also feed them blood - var/list/livingplants = list(/mob/living/simple_animal/hostile/tree, /mob/living/simple_animal/hostile/killertomato) - var/chosen = pick(livingplants) - var/mob/living/simple_animal/hostile/C = new chosen - C.faction = list("plants") - -/obj/machinery/hydroponics/proc/become_self_sufficient() // Ambrosia Gaia effect - visible_message("[src] begins to glow with a beautiful light!") - self_sustaining = TRUE - update_icon() - -/////////////////////////////////////////////////////////////////////////////// -/obj/machinery/hydroponics/soil //Not actually hydroponics at all! Honk! - name = "soil" - desc = "A patch of dirt." - icon = 'icons/obj/hydroponics/equipment.dmi' - icon_state = "soil" - gender = PLURAL - circuit = null - density = FALSE - use_power = NO_POWER_USE - flags_1 = NODECONSTRUCT_1 - unwrenchable = FALSE - -/obj/machinery/hydroponics/soil/update_icon_hoses() - return // Has no hoses - -/obj/machinery/hydroponics/soil/update_icon_lights() - return // Has no lights - -/obj/machinery/hydroponics/soil/attackby(obj/item/O, mob/user, params) - if(O.tool_behaviour == TOOL_SHOVEL && !istype(O, /obj/item/shovel/spade)) //Doesn't include spades because of uprooting plants - to_chat(user, "You clear up [src]!") - qdel(src) - else - return ..() +#define TRAY_NAME_UPDATE name = myseed ? "[initial(name)] ([myseed.plantname])" : initial(name) + +/obj/machinery/hydroponics + name = "hydroponics tray" + icon = 'icons/obj/hydroponics/equipment.dmi' + icon_state = "hydrotray" + density = TRUE + pixel_z = 8 + obj_flags = CAN_BE_HIT | UNIQUE_RENAME + circuit = /obj/item/circuitboard/machine/hydroponics + var/waterlevel = 100 //The amount of water in the tray (max 100) + var/maxwater = 100 //The maximum amount of water in the tray + var/nutrilevel = 10 //The amount of nutrient in the tray (max 10) + var/maxnutri = 10 //The maximum nutrient of water in the tray + var/pestlevel = 0 //The amount of pests in the tray (max 10) + var/weedlevel = 0 //The amount of weeds in the tray (max 10) + var/yieldmod = 1 //Nutriment's effect on yield + var/mutmod = 1 //Nutriment's effect on mutations + var/toxic = 0 //Toxicity in the tray? + var/age = 0 //Current age + var/dead = 0 //Is it dead? + var/plant_health //Its health + var/lastproduce = 0 //Last time it was harvested + var/lastcycle = 0 //Used for timing of cycles. + var/cycledelay = 200 //About 10 seconds / cycle + var/harvest = 0 //Ready to harvest? + var/obj/item/seeds/myseed = null //The currently planted seed + var/rating = 1 + var/unwrenchable = 1 + var/recent_bee_visit = FALSE //Have we been visited by a bee recently, so bees dont overpollinate one plant + var/using_irrigation = FALSE //If the tray is connected to other trays via irrigation hoses + var/self_sufficiency_req = 20 //Required total dose to make a self-sufficient hydro tray. 1:1 with earthsblood. + var/self_sufficiency_progress = 0 + var/self_sustaining = FALSE //If the tray generates nutrients and water on its own + + +/obj/machinery/hydroponics/constructable + name = "hydroponics tray" + icon = 'icons/obj/hydroponics/equipment.dmi' + icon_state = "hydrotray3" + +/obj/machinery/hydroponics/constructable/RefreshParts() + var/tmp_capacity = 0 + for (var/obj/item/stock_parts/matter_bin/M in component_parts) + tmp_capacity += M.rating + for (var/obj/item/stock_parts/manipulator/M in component_parts) + rating = M.rating + maxwater = tmp_capacity * 50 // Up to 300 + maxnutri = tmp_capacity * 5 // Up to 30 + +/obj/machinery/hydroponics/constructable/examine(mob/user) + . = ..() + if(in_range(user, src) || isobserver(user)) + . += "The status display reads: Tray efficiency at [rating*100]%." + + +/obj/machinery/hydroponics/Destroy() + if(myseed) + qdel(myseed) + myseed = null + return ..() + +/obj/machinery/hydroponics/constructable/attackby(obj/item/I, mob/user, params) + if (user.a_intent != INTENT_HARM) + // handle opening the panel + if(default_deconstruction_screwdriver(user, icon_state, icon_state, I)) + return + + // handle deconstructing the machine, if permissible + if(I.tool_behaviour == TOOL_CROWBAR && using_irrigation) + to_chat(user, "Disconnect the hoses first!") + return + else if(default_deconstruction_crowbar(I)) + return + + return ..() + +/obj/machinery/hydroponics/proc/FindConnected() + var/list/connected = list() + var/list/processing_atoms = list(src) + + while(processing_atoms.len) + var/atom/a = processing_atoms[1] + for(var/step_dir in GLOB.cardinals) + var/obj/machinery/hydroponics/h = locate() in get_step(a, step_dir) + // Soil plots aren't dense + if(h && h.using_irrigation && h.density && !(h in connected) && !(h in processing_atoms)) + processing_atoms += h + + processing_atoms -= a + connected += a + + return connected + +/obj/machinery/hydroponics/bullet_act(obj/projectile/Proj) //Works with the Somatoray to modify plant variables. + if(!myseed) + return ..() + if(istype(Proj , /obj/projectile/energy/floramut)) + mutate() + else if(istype(Proj , /obj/projectile/energy/florayield)) + return myseed.bullet_act(Proj) + else + return ..() + +/obj/machinery/hydroponics/process() + var/needs_update = 0 // Checks if the icon needs updating so we don't redraw empty trays every time + + if(myseed && (myseed.loc != src)) + myseed.forceMove(src) + + if(self_sustaining) + adjustNutri(1) + adjustWater(rand(3,5)) + adjustWeeds(-2) + adjustPests(-2) + adjustToxic(-2) + + if(world.time > (lastcycle + cycledelay)) + lastcycle = world.time + if(myseed && !dead) + // Advance age + age++ + if(age < myseed.maturation) + lastproduce = age + + needs_update = 1 + + +//Nutrients////////////////////////////////////////////////////////////// + // Nutrients deplete slowly + if(prob(50)) + adjustNutri(-1 / rating) + + // Lack of nutrients hurts non-weeds + if(nutrilevel <= 0 && !myseed.get_gene(/datum/plant_gene/trait/plant_type/weed_hardy)) + adjustHealth(-rand(1,3)) + +//Photosynthesis///////////////////////////////////////////////////////// + // Lack of light hurts non-mushrooms + if(isturf(loc)) + var/turf/currentTurf = loc + var/lightAmt = currentTurf.get_lumcount() + if(myseed.get_gene(/datum/plant_gene/trait/plant_type/fungal_metabolism)) + if(lightAmt < 0.2) + adjustHealth(-1 / rating) + else // Non-mushroom + if(lightAmt < 0.4) + adjustHealth(-2 / rating) + +//Water////////////////////////////////////////////////////////////////// + // Drink random amount of water + adjustWater(-rand(1,6) / rating) + + // If the plant is dry, it loses health pretty fast, unless mushroom + if(waterlevel <= 10 && !myseed.get_gene(/datum/plant_gene/trait/plant_type/fungal_metabolism)) + adjustHealth(-rand(0,1) / rating) + if(waterlevel <= 0) + adjustHealth(-rand(0,2) / rating) + + // Sufficient water level and nutrient level = plant healthy but also spawns weeds + else if(waterlevel > 10 && nutrilevel > 0) + adjustHealth(rand(1,2) / rating) + if(myseed && prob(myseed.weed_chance)) + adjustWeeds(myseed.weed_rate) + else if(prob(5)) //5 percent chance the weed population will increase + adjustWeeds(1 / rating) + +//Toxins///////////////////////////////////////////////////////////////// + + // Too much toxins cause harm, but when the plant drinks the contaiminated water, the toxins disappear slowly + if(toxic >= 40 && toxic < 80) + adjustHealth(-1 / rating) + adjustToxic(-rand(1,10) / rating) + else if(toxic >= 80) // I don't think it ever gets here tbh unless above is commented out + adjustHealth(-3) + adjustToxic(-rand(1,10) / rating) + +//Pests & Weeds////////////////////////////////////////////////////////// + + if(pestlevel >= 8) + if(!myseed.get_gene(/datum/plant_gene/trait/plant_type/carnivory)) + adjustHealth(-2 / rating) + + else + adjustHealth(2 / rating) + adjustPests(-1 / rating) + + else if(pestlevel >= 4) + if(!myseed.get_gene(/datum/plant_gene/trait/plant_type/carnivory)) + adjustHealth(-1 / rating) + + else + adjustHealth(1 / rating) + if(prob(50)) + adjustPests(-1 / rating) + + else if(pestlevel < 4 && myseed.get_gene(/datum/plant_gene/trait/plant_type/carnivory)) + adjustHealth(-2 / rating) + if(prob(5)) + adjustPests(-1 / rating) + + // If it's a weed, it doesn't stunt the growth + if(weedlevel >= 5 && !myseed.get_gene(/datum/plant_gene/trait/plant_type/weed_hardy)) + adjustHealth(-1 / rating) + +//Health & Age/////////////////////////////////////////////////////////// + + // Plant dies if plant_health <= 0 + if(plant_health <= 0) + plantdies() + adjustWeeds(1 / rating) // Weeds flourish + + // If the plant is too old, lose health fast + if(age > myseed.lifespan) + adjustHealth(-rand(1,5) / rating) + + // Harvest code + if(age > myseed.production && (age - lastproduce) > myseed.production && (!harvest && !dead)) + nutrimentMutation() + if(myseed && myseed.yield != -1) // Unharvestable shouldn't be harvested + harvest = 1 + else + lastproduce = age + if(prob(5)) // On each tick, there's a 5 percent chance the pest population will increase + adjustPests(1 / rating) + else + if(waterlevel > 10 && nutrilevel > 0 && prob(10)) // If there's no plant, the percentage chance is 10% + adjustWeeds(1 / rating) + + // Weeeeeeeeeeeeeeedddssss + if(weedlevel >= 10 && prob(50) && !self_sustaining) // At this point the plant is kind of fucked. Weeds can overtake the plant spot. + if(myseed) + if(!myseed.get_gene(/datum/plant_gene/trait/plant_type/weed_hardy) && !myseed.get_gene(/datum/plant_gene/trait/plant_type/fungal_metabolism)) // If a normal plant + weedinvasion() + else + weedinvasion() // Weed invasion into empty tray + needs_update = 1 + if (needs_update) + update_icon() + + if(myseed && prob(5 * (11-myseed.production))) + for(var/g in myseed.genes) + if(istype(g, /datum/plant_gene/trait)) + var/datum/plant_gene/trait/selectedtrait = g + selectedtrait.on_grow(src) + return + +/obj/machinery/hydroponics/proc/nutrimentMutation() + if (mutmod == 0) + return + if (mutmod == 1) + if(prob(80)) //80% + mutate() + else if(prob(75)) //15% + hardmutate() + return + if (mutmod == 2) + if(prob(50)) //50% + mutate() + else if(prob(50)) //25% + hardmutate() + else if(prob(50)) //12.5% + mutatespecie() + return + return + +/obj/machinery/hydroponics/update_icon() + //Refreshes the icon and sets the luminosity + cut_overlays() + + if(self_sustaining) + if(istype(src, /obj/machinery/hydroponics/soil)) + add_atom_colour(rgb(255, 175, 0), FIXED_COLOUR_PRIORITY) + else + add_overlay(mutable_appearance('icons/obj/hydroponics/equipment.dmi', "gaia_blessing")) + set_light(3) + + update_icon_hoses() + + if(myseed) + update_icon_plant() + update_icon_lights() + + if(!self_sustaining) + if(myseed && myseed.get_gene(/datum/plant_gene/trait/glow)) + var/datum/plant_gene/trait/glow/G = myseed.get_gene(/datum/plant_gene/trait/glow) + set_light(G.glow_range(myseed), G.glow_power(myseed), G.glow_color) + else + set_light(0) + + return + +/obj/machinery/hydroponics/proc/update_icon_hoses() + var/n = 0 + for(var/Dir in GLOB.cardinals) + var/obj/machinery/hydroponics/t = locate() in get_step(src,Dir) + if(t && t.using_irrigation && using_irrigation) + n += Dir + + icon_state = "hoses-[n]" + +/obj/machinery/hydroponics/proc/update_icon_plant() + var/mutable_appearance/plant_overlay = mutable_appearance(myseed.growing_icon, layer = OBJ_LAYER + 0.01) + if(dead) + plant_overlay.icon_state = myseed.icon_dead + else if(harvest) + if(!myseed.icon_harvest) + plant_overlay.icon_state = "[myseed.icon_grow][myseed.growthstages]" + else + plant_overlay.icon_state = myseed.icon_harvest + else + var/t_growthstate = min(round((age / myseed.maturation) * myseed.growthstages), myseed.growthstages) + plant_overlay.icon_state = "[myseed.icon_grow][t_growthstate]" + add_overlay(plant_overlay) + +/obj/machinery/hydroponics/proc/update_icon_lights() + if(waterlevel <= 10) + add_overlay(mutable_appearance('icons/obj/hydroponics/equipment.dmi', "over_lowwater3")) + if(nutrilevel <= 2) + add_overlay(mutable_appearance('icons/obj/hydroponics/equipment.dmi', "over_lownutri3")) + if(plant_health <= (myseed.endurance / 2)) + add_overlay(mutable_appearance('icons/obj/hydroponics/equipment.dmi', "over_lowhealth3")) + if(weedlevel >= 5 || pestlevel >= 5 || toxic >= 40) + add_overlay(mutable_appearance('icons/obj/hydroponics/equipment.dmi', "over_alert3")) + if(harvest) + add_overlay(mutable_appearance('icons/obj/hydroponics/equipment.dmi', "over_harvest3")) + + +/obj/machinery/hydroponics/examine(user) + . = ..() + if(myseed) + . += "It has [myseed.plantname] planted." + if (dead) + . += "It's dead!" + else if (harvest) + . += "It's ready to harvest." + else if (plant_health <= (myseed.endurance / 2)) + . += "It looks unhealthy." + else + . += "It's empty." + + if(!self_sustaining) + . += "Water: [waterlevel]/[maxwater].\n"+\ + "Nutrient: [nutrilevel]/[maxnutri]." + if(self_sufficiency_progress > 0) + var/percent_progress = round(self_sufficiency_progress * 100 / self_sufficiency_req) + . += "Treatment for self-sustenance are [percent_progress]% complete." + else + . += "It doesn't require any water or nutrients." + + if(weedlevel >= 5) + to_chat(user, "It's filled with weeds!") + if(pestlevel >= 5) + to_chat(user, "It's filled with tiny worms!") + to_chat(user, "" ) + + +/obj/machinery/hydroponics/proc/weedinvasion() // If a weed growth is sufficient, this happens. + dead = 0 + var/oldPlantName + if(myseed) // In case there's nothing in the tray beforehand + oldPlantName = myseed.plantname + qdel(myseed) + myseed = null + else + oldPlantName = "empty tray" + switch(rand(1,18)) // randomly pick predominative weed + if(16 to 18) + myseed = new /obj/item/seeds/reishi(src) + if(14 to 15) + myseed = new /obj/item/seeds/nettle(src) + if(12 to 13) + myseed = new /obj/item/seeds/harebell(src) + if(10 to 11) + myseed = new /obj/item/seeds/amanita(src) + if(8 to 9) + myseed = new /obj/item/seeds/chanter(src) + if(6 to 7) + myseed = new /obj/item/seeds/tower(src) + if(4 to 5) + myseed = new /obj/item/seeds/plump(src) + else + myseed = new /obj/item/seeds/starthistle(src) + age = 0 + plant_health = myseed.endurance + lastcycle = world.time + harvest = 0 + weedlevel = 0 // Reset + pestlevel = 0 // Reset + update_icon() + visible_message("The [oldPlantName] is overtaken by some [myseed.plantname]!") + TRAY_NAME_UPDATE + +/obj/machinery/hydroponics/proc/mutate(lifemut = 2, endmut = 5, productmut = 1, yieldmut = 2, potmut = 25, wrmut = 2, wcmut = 5, traitmut = 0) // Mutates the current seed + if(!myseed) + return + myseed.mutate(lifemut, endmut, productmut, yieldmut, potmut, wrmut, wcmut, traitmut) + +/obj/machinery/hydroponics/proc/hardmutate() + mutate(4, 10, 2, 4, 50, 4, 10, 3) + + +/obj/machinery/hydroponics/proc/mutatespecie() // Mutagent produced a new plant! + if(!myseed || dead) + return + + var/oldPlantName = myseed.plantname + if(myseed.mutatelist.len > 0) + var/mutantseed = pick(myseed.mutatelist) + qdel(myseed) + myseed = null + myseed = new mutantseed + else + return + + hardmutate() + age = 0 + plant_health = myseed.endurance + lastcycle = world.time + harvest = 0 + weedlevel = 0 // Reset + + sleep(5) // Wait a while + update_icon() + visible_message("[oldPlantName] suddenly mutates into [myseed.plantname]!") + TRAY_NAME_UPDATE + +/obj/machinery/hydroponics/proc/mutateweed() // If the weeds gets the mutagent instead. Mind you, this pretty much destroys the old plant + if( weedlevel > 5 ) + if(myseed) + qdel(myseed) + myseed = null + var/newWeed = pick(/obj/item/seeds/liberty, /obj/item/seeds/angel, /obj/item/seeds/nettle/death, /obj/item/seeds/kudzu) + myseed = new newWeed + dead = 0 + hardmutate() + age = 0 + plant_health = myseed.endurance + lastcycle = world.time + harvest = 0 + weedlevel = 0 // Reset + + sleep(5) // Wait a while + update_icon() + visible_message("The mutated weeds in [src] spawn some [myseed.plantname]!") + TRAY_NAME_UPDATE + else + to_chat(usr, "The few weeds in [src] seem to react, but only for a moment...") + + +/obj/machinery/hydroponics/proc/plantdies() // OH NOES!!!!! I put this all in one function to make things easier + plant_health = 0 + harvest = 0 + pestlevel = 0 // Pests die + lastproduce = 0 + if(!dead) + update_icon() + dead = 1 + + + +/obj/machinery/hydroponics/proc/mutatepest(mob/user) + if(pestlevel > 5) + message_admins("[ADMIN_LOOKUPFLW(user)] caused spiderling pests to spawn in a hydro tray") + log_game("[key_name(user)] caused spiderling pests to spawn in a hydro tray") + visible_message("The pests seem to behave oddly...") + spawn_atom_to_turf(/obj/structure/spider/spiderling/hunter, src, 3, FALSE) + else + to_chat(user, "The pests seem to behave oddly, but quickly settle down...") + +/obj/machinery/hydroponics/proc/applyChemicals(datum/reagents/S, mob/user) + if(myseed) + myseed.on_chem_reaction(S) //In case seeds have some special interactions with special chems, currently only used by vines + + // Requires 5 mutagen to possibly change species.// Poor man's mutagen. + if(S.has_reagent(/datum/reagent/toxin/mutagen, 5) || S.has_reagent(/datum/reagent/uranium/radium, 10) || S.has_reagent(/datum/reagent/uranium, 10)) + switch(rand(100)) + if(91 to 100) + adjustHealth(-10) + to_chat(user, "The plant shrivels and burns.") + if(81 to 90) + mutatespecie() + if(66 to 80) + hardmutate() + if(41 to 65) + mutate() + if(21 to 41) + to_chat(user, "The plants don't seem to react...") + if(11 to 20) + mutateweed() + if(1 to 10) + mutatepest(user) + else + to_chat(user, "Nothing happens...") + + // 2 or 1 units is enough to change the yield and other stats.// Can change the yield and other stats, but requires more than mutagen + else if(S.has_reagent(/datum/reagent/toxin/mutagen, 2) || S.has_reagent(/datum/reagent/uranium/radium, 5) || S.has_reagent(/datum/reagent/uranium, 5)) + hardmutate() + else if(S.has_reagent(/datum/reagent/toxin/mutagen, 1) || S.has_reagent(/datum/reagent/uranium/radium, 2) || S.has_reagent(/datum/reagent/uranium, 2)) + mutate() + + // After handling the mutating, we now handle the damage from adding crude radioactives... + if(S.has_reagent(/datum/reagent/uranium, 1)) + adjustHealth(-round(S.get_reagent_amount(/datum/reagent/uranium) * 1)) + adjustToxic(round(S.get_reagent_amount(/datum/reagent/uranium) * 2)) + if(S.has_reagent(/datum/reagent/uranium/radium, 1)) + adjustHealth(-round(S.get_reagent_amount(/datum/reagent/uranium/radium) * 1)) + adjustToxic(round(S.get_reagent_amount(/datum/reagent/uranium/radium) * 3)) // Radium is harsher (OOC: also easier to produce) + + // Nutriments + if(S.has_reagent(/datum/reagent/plantnutriment/eznutriment, 1)) + yieldmod = 1 + mutmod = 1 + adjustNutri(round(S.get_reagent_amount(/datum/reagent/plantnutriment/eznutriment) * 1)) + + if(S.has_reagent(/datum/reagent/plantnutriment/left4zednutriment, 1)) + yieldmod = 0 + mutmod = 2 + adjustNutri(round(S.get_reagent_amount(/datum/reagent/plantnutriment/left4zednutriment) * 1)) + + if(S.has_reagent(/datum/reagent/plantnutriment/robustharvestnutriment, 1)) + yieldmod = 1.3 + mutmod = 0 + adjustNutri(round(S.get_reagent_amount(/datum/reagent/plantnutriment/robustharvestnutriment) *1 )) + + // Ambrosia Gaia produces earthsblood. + if(S.has_reagent(/datum/reagent/medicine/earthsblood)) + self_sufficiency_progress += S.get_reagent_amount(/datum/reagent/medicine/earthsblood) + if(self_sufficiency_progress >= self_sufficiency_req) + become_self_sufficient() + else if(!self_sustaining) + to_chat(user, "[src] warms as it might on a spring day under a genuine Sun.") + + // Antitoxin binds shit pretty well. So the tox goes significantly down + if(S.has_reagent(/datum/reagent/medicine/C2/multiver, 1)) + adjustToxic(-round(S.get_reagent_amount(/datum/reagent/medicine/C2/multiver) * 2)) + + // NIGGA, YOU JUST WENT ON FULL RETARD. + if(S.has_reagent(/datum/reagent/toxin, 1)) + adjustToxic(round(S.get_reagent_amount(/datum/reagent/toxin) * 2)) + + // Milk is good for humans, but bad for plants. The sugars canot be used by plants, and the milk fat fucks up growth. Not shrooms though. I can't deal with this now... + if(S.has_reagent(/datum/reagent/consumable/milk, 1)) + adjustNutri(round(S.get_reagent_amount(/datum/reagent/consumable/milk) * 0.1)) + adjustWater(round(S.get_reagent_amount(/datum/reagent/consumable/milk) * 0.9)) + + // Beer is a chemical composition of alcohol and various other things. It's a shitty nutrient but hey, it's still one. Also alcohol is bad, mmmkay? + if(S.has_reagent(/datum/reagent/consumable/ethanol/beer, 1)) + adjustHealth(-round(S.get_reagent_amount(/datum/reagent/consumable/ethanol/beer) * 0.05)) + adjustNutri(round(S.get_reagent_amount(/datum/reagent/consumable/ethanol/beer) * 0.25)) + adjustWater(round(S.get_reagent_amount(/datum/reagent/consumable/ethanol/beer) * 0.7)) + + // You're an idiot for thinking that one of the most corrosive and deadly gasses would be beneficial + if(S.has_reagent(/datum/reagent/fluorine, 1)) + adjustHealth(-round(S.get_reagent_amount(/datum/reagent/fluorine) * 2)) + adjustToxic(round(S.get_reagent_amount(/datum/reagent/fluorine) * 2.5)) + adjustWater(-round(S.get_reagent_amount(/datum/reagent/fluorine) * 0.5)) + adjustWeeds(-rand(1,4)) + + // You're an idiot for thinking that one of the most corrosive and deadly gasses would be beneficial + if(S.has_reagent(/datum/reagent/chlorine, 1)) + adjustHealth(-round(S.get_reagent_amount(/datum/reagent/chlorine) * 1)) + adjustToxic(round(S.get_reagent_amount(/datum/reagent/chlorine) * 1.5)) + adjustWater(-round(S.get_reagent_amount(/datum/reagent/chlorine) * 0.5)) + adjustWeeds(-rand(1,3)) + + // White Phosphorous + water -> phosphoric acid. That's not a good thing really. + // Phosphoric salts are beneficial though. And even if the plant suffers, in the long run the tray gets some nutrients. The benefit isn't worth that much. + if(S.has_reagent(/datum/reagent/phosphorus, 1)) + adjustHealth(-round(S.get_reagent_amount(/datum/reagent/phosphorus) * 0.75)) + adjustNutri(round(S.get_reagent_amount(/datum/reagent/phosphorus) * 0.1)) + adjustWater(-round(S.get_reagent_amount(/datum/reagent/phosphorus) * 0.5)) + adjustWeeds(-rand(1,2)) + + // Plants should not have sugar, they can't use it and it prevents them getting water/ nutients, it is good for mold though... + if(S.has_reagent(/datum/reagent/consumable/sugar, 1)) + adjustWeeds(rand(1,2)) + adjustPests(rand(1,2)) + adjustNutri(round(S.get_reagent_amount(/datum/reagent/consumable/sugar) * 0.1)) + + // It is water! + if(S.has_reagent(/datum/reagent/water, 1)) + adjustWater(round(S.get_reagent_amount(/datum/reagent/water) * 1)) + + // Holy water. Mostly the same as water, it also heals the plant a little with the power of the spirits~ + if(S.has_reagent(/datum/reagent/water/holywater, 1)) + adjustWater(round(S.get_reagent_amount(/datum/reagent/water/holywater) * 1)) + adjustHealth(round(S.get_reagent_amount(/datum/reagent/water/holywater) * 0.1)) + + // A variety of nutrients are dissolved in club soda, without sugar. + // These nutrients include carbon, oxygen, hydrogen, phosphorous, potassium, sulfur and sodium, all of which are needed for healthy plant growth. + if(S.has_reagent(/datum/reagent/consumable/sodawater, 1)) + adjustWater(round(S.get_reagent_amount(/datum/reagent/consumable/sodawater) * 1)) + adjustHealth(round(S.get_reagent_amount(/datum/reagent/consumable/sodawater) * 0.1)) + adjustNutri(round(S.get_reagent_amount(/datum/reagent/consumable/sodawater) * 0.1)) + + // Man, you guys are retards + if(S.has_reagent(/datum/reagent/toxin/acid, 1)) + adjustHealth(-round(S.get_reagent_amount(/datum/reagent/toxin/acid) * 1)) + adjustToxic(round(S.get_reagent_amount(/datum/reagent/toxin/acid) * 1.5)) + adjustWeeds(-rand(1,2)) + + // SERIOUSLY + if(S.has_reagent(/datum/reagent/toxin/acid/fluacid, 1)) + adjustHealth(-round(S.get_reagent_amount(/datum/reagent/toxin/acid/fluacid) * 2)) + adjustToxic(round(S.get_reagent_amount(/datum/reagent/toxin/acid/fluacid) * 3)) + adjustWeeds(-rand(1,4)) + + // Plant-B-Gone is just as bad + if(S.has_reagent(/datum/reagent/toxin/plantbgone, 1)) + adjustHealth(-round(S.get_reagent_amount(/datum/reagent/toxin/plantbgone) * 5)) + adjustToxic(round(S.get_reagent_amount(/datum/reagent/toxin/plantbgone) * 6)) + adjustWeeds(-rand(4,8)) + + // why, just why + if(S.has_reagent(/datum/reagent/napalm, 1)) + if(!(myseed.resistance_flags & FIRE_PROOF)) + adjustHealth(-round(S.get_reagent_amount(/datum/reagent/napalm) * 6)) + adjustToxic(round(S.get_reagent_amount(/datum/reagent/napalm) * 7)) + adjustWeeds(-rand(5,9)) //At least give them a small reward if they bother. + + //Weed Spray + if(S.has_reagent(/datum/reagent/toxin/plantbgone/weedkiller, 1)) + adjustToxic(round(S.get_reagent_amount(/datum/reagent/toxin/plantbgone/weedkiller) * 0.5)) + //old toxicity was 4, each spray is default 10 (minimal of 5) so 5 and 2.5 are the new ammounts + adjustWeeds(-rand(1,2)) + + //Pest Spray + if(S.has_reagent(/datum/reagent/toxin/pestkiller, 1)) + adjustToxic(round(S.get_reagent_amount(/datum/reagent/toxin/pestkiller) * 0.5)) + adjustPests(-rand(1,2)) + + //Nicotine is used as a pesticide IRL. + if(S.has_reagent(/datum/reagent/drug/nicotine, 1)) + adjustToxic(round(S.get_reagent_amount(/datum/reagent/drug/nicotine))) + adjustPests(-rand(1,2)) + + // Healing + if(S.has_reagent(/datum/reagent/medicine/cryoxadone, 1)) + adjustHealth(round(S.get_reagent_amount(/datum/reagent/medicine/cryoxadone) * 3)) + adjustToxic(-round(S.get_reagent_amount(/datum/reagent/medicine/cryoxadone) * 3)) + + // Ammonia is bad ass. + if(S.has_reagent(/datum/reagent/ammonia, 1)) + adjustHealth(round(S.get_reagent_amount(/datum/reagent/ammonia) * 0.5)) + adjustNutri(round(S.get_reagent_amount(/datum/reagent/ammonia) * 1)) + if(myseed) + myseed.adjust_yield(round(S.get_reagent_amount(/datum/reagent/ammonia) * 0.04)) + + // Saltpetre is used for gardening IRL, to simplify highly, it speeds up growth and strengthens plants + if(S.has_reagent(/datum/reagent/saltpetre, 1)) + var/salt = S.get_reagent_amount(/datum/reagent/saltpetre) + adjustHealth(round(salt * 0.25)) + if (myseed) + myseed.adjust_production(-round(salt/100)-prob(salt%100)) + myseed.adjust_potency(round(salt*0.5)) + // Ash is also used IRL in gardening, as a fertilizer enhancer and weed killer + if(S.has_reagent(/datum/reagent/ash, 1)) + adjustHealth(round(S.get_reagent_amount(/datum/reagent/ash) * 0.25)) + adjustNutri(round(S.get_reagent_amount(/datum/reagent/ash) * 0.5)) + adjustWeeds(-1) + + // This is more bad ass, and pests get hurt by the corrosive nature of it, not the plant. + if(S.has_reagent(/datum/reagent/diethylamine, 1)) + adjustHealth(round(S.get_reagent_amount(/datum/reagent/diethylamine) * 1)) + adjustNutri(round(S.get_reagent_amount(/datum/reagent/diethylamine) * 2)) + if(myseed) + myseed.adjust_yield(round(S.get_reagent_amount(/datum/reagent/diethylamine) * 0.02)) + adjustPests(-rand(1,2)) + + // Compost, effectively + if(S.has_reagent(/datum/reagent/consumable/nutriment, 1)) + adjustHealth(round(S.get_reagent_amount(/datum/reagent/consumable/nutriment) * 0.5)) + adjustNutri(round(S.get_reagent_amount(/datum/reagent/consumable/nutriment) * 1)) + + // Compost for EVERYTHING + if(S.has_reagent(/datum/reagent/consumable/virus_food, 1)) + adjustNutri(round(S.get_reagent_amount(/datum/reagent/consumable/virus_food) * 0.5)) + adjustHealth(-round(S.get_reagent_amount(/datum/reagent/consumable/virus_food) * 0.5)) + + // FEED ME + if(S.has_reagent(/datum/reagent/blood, 1)) + adjustNutri(round(S.get_reagent_amount(/datum/reagent/blood) * 1)) + adjustPests(rand(2,4)) + + // FEED ME SEYMOUR + if(S.has_reagent(/datum/reagent/medicine/strange_reagent, 1)) + spawnplant() + + // The best stuff there is. For testing/debugging. + if(S.has_reagent(/datum/reagent/medicine/adminordrazine, 1)) + adjustWater(round(S.get_reagent_amount(/datum/reagent/medicine/adminordrazine) * 1)) + adjustHealth(round(S.get_reagent_amount(/datum/reagent/medicine/adminordrazine) * 1)) + adjustNutri(round(S.get_reagent_amount(/datum/reagent/medicine/adminordrazine) * 1)) + adjustPests(-rand(1,5)) + adjustWeeds(-rand(1,5)) + if(S.has_reagent(/datum/reagent/medicine/adminordrazine, 5)) + switch(rand(100)) + if(66 to 100) + mutatespecie() + if(33 to 65) + mutateweed() + if(1 to 32) + mutatepest(user) + else + to_chat(user, "Nothing happens...") + +/obj/machinery/hydroponics/attackby(obj/item/O, mob/user, params) + //Called when mob user "attacks" it with object O + if(istype(O, /obj/item/reagent_containers) ) // Syringe stuff (and other reagent containers now too) + var/obj/item/reagent_containers/reagent_source = O + + if(istype(reagent_source, /obj/item/reagent_containers/syringe)) + var/obj/item/reagent_containers/syringe/syr = reagent_source + if(syr.mode != 1) + to_chat(user, "You can't get any extract out of this plant." ) + return + + if(!reagent_source.reagents.total_volume) + to_chat(user, "[reagent_source] is empty!") + return 1 + + var/list/trays = list(src)//makes the list just this in cases of syringes and compost etc + var/target = myseed ? myseed.plantname : src + var/visi_msg = "" + var/irrigate = 0 //How am I supposed to irrigate pill contents? + var/transfer_amount + + if(istype(reagent_source, /obj/item/reagent_containers/food/snacks) || istype(reagent_source, /obj/item/reagent_containers/pill)) + if(istype(reagent_source, /obj/item/reagent_containers/food/snacks)) + var/obj/item/reagent_containers/food/snacks/R = reagent_source + if (R.trash) + R.generate_trash(get_turf(user)) + visi_msg="[user] composts [reagent_source], spreading it through [target]" + transfer_amount = reagent_source.reagents.total_volume + else + transfer_amount = reagent_source.amount_per_transfer_from_this + if(istype(reagent_source, /obj/item/reagent_containers/syringe/)) + var/obj/item/reagent_containers/syringe/syr = reagent_source + visi_msg="[user] injects [target] with [syr]" + if(syr.reagents.total_volume <= syr.amount_per_transfer_from_this) + syr.mode = 0 + else if(istype(reagent_source, /obj/item/reagent_containers/spray/)) + visi_msg="[user] sprays [target] with [reagent_source]" + playsound(loc, 'sound/effects/spray3.ogg', 50, TRUE, -6) + irrigate = 1 + else if(transfer_amount) // Droppers, cans, beakers, what have you. + visi_msg="[user] uses [reagent_source] on [target]" + irrigate = 1 + // Beakers, bottles, buckets, etc. + if(reagent_source.is_drainable()) + playsound(loc, 'sound/effects/slosh.ogg', 25, TRUE) + + if(irrigate && transfer_amount > 30 && reagent_source.reagents.total_volume >= 30 && using_irrigation) + trays = FindConnected() + if (trays.len > 1) + visi_msg += ", setting off the irrigation system" + + if(visi_msg) + visible_message("[visi_msg].") + + var/split = round(transfer_amount/trays.len) + + for(var/obj/machinery/hydroponics/H in trays) + //cause I don't want to feel like im juggling 15 tamagotchis and I can get to my real work of ripping flooring apart in hopes of validating my life choices of becoming a space-gardener + + var/datum/reagents/S = new /datum/reagents() //This is a strange way, but I don't know of a better one so I can't fix it at the moment... + S.my_atom = H + + reagent_source.reagents.trans_to(S,split, transfered_by = user) + if(istype(reagent_source, /obj/item/reagent_containers/food/snacks) || istype(reagent_source, /obj/item/reagent_containers/pill)) + qdel(reagent_source) + + H.applyChemicals(S, user) + + S.clear_reagents() + qdel(S) + H.update_icon() + if(reagent_source) // If the source wasn't composted and destroyed + reagent_source.update_icon() + return 1 + + else if(istype(O, /obj/item/seeds) && !istype(O, /obj/item/seeds/sample)) + if(!myseed) + if(istype(O, /obj/item/seeds/kudzu)) + investigate_log("had Kudzu planted in it by [key_name(user)] at [AREACOORD(src)]","kudzu") + if(!user.transferItemToLoc(O, src)) + return + to_chat(user, "You plant [O].") + dead = 0 + myseed = O + TRAY_NAME_UPDATE + age = 1 + plant_health = myseed.endurance + lastcycle = world.time + update_icon() + else + to_chat(user, "[src] already has seeds in it!") + + else if(istype(O, /obj/item/plant_analyzer)) + if(myseed) + to_chat(user, "*** [myseed.plantname] ***" ) + to_chat(user, "- Plant Age: [age]") + var/list/text_string = myseed.get_analyzer_text() + if(text_string) + to_chat(user, text_string) + else + to_chat(user, "No plant found.") + to_chat(user, "- Weed level: [weedlevel] / 10") + to_chat(user, "- Pest level: [pestlevel] / 10") + to_chat(user, "- Toxicity level: [toxic] / 100") + to_chat(user, "- Water level: [waterlevel] / [maxwater]") + to_chat(user, "- Nutrition level: [nutrilevel] / [maxnutri]") + to_chat(user, "") + + else if(istype(O, /obj/item/cultivator)) + if(weedlevel > 0) + user.visible_message("[user] uproots the weeds.", "You remove the weeds from [src].") + weedlevel = 0 + update_icon() + else + to_chat(user, "This plot is completely devoid of weeds! It doesn't need uprooting.") + + else if(istype(O, /obj/item/storage/bag/plants)) + attack_hand(user) + for(var/obj/item/reagent_containers/food/snacks/grown/G in locate(user.x,user.y,user.z)) + SEND_SIGNAL(O, COMSIG_TRY_STORAGE_INSERT, G, user, TRUE) + + else if(default_unfasten_wrench(user, O)) + return + + else if((O.tool_behaviour == TOOL_WIRECUTTER) && unwrenchable) + if (!anchored) + to_chat(user, "Anchor the tray first!") + return + using_irrigation = !using_irrigation + O.play_tool_sound(src) + user.visible_message("[user] [using_irrigation ? "" : "dis"]connects [src]'s irrigation hoses.", \ + "You [using_irrigation ? "" : "dis"]connect [src]'s irrigation hoses.") + for(var/obj/machinery/hydroponics/h in range(1,src)) + h.update_icon() + + else if(istype(O, /obj/item/shovel/spade)) + if(!myseed && !weedlevel) + to_chat(user, "[src] doesn't have any plants or weeds!") + return + user.visible_message("[user] starts digging out [src]'s plants...", + "You start digging out [src]'s plants...") + if(O.use_tool(src, user, 50, volume=50) || (!myseed && !weedlevel)) + user.visible_message("[user] digs out the plants in [src]!", "You dig out all of [src]'s plants!") + if(myseed) //Could be that they're just using it as a de-weeder + age = 0 + plant_health = 0 + lastproduce = 0 + if(harvest) + harvest = FALSE //To make sure they can't just put in another seed and insta-harvest it + qdel(myseed) + myseed = null + name = initial(name) + desc = initial(desc) + weedlevel = 0 //Has a side effect of cleaning up those nasty weeds + update_icon() + + + else + return ..() + +/obj/machinery/hydroponics/can_be_unfasten_wrench(mob/user, silent) + if (!unwrenchable) // case also covered by NODECONSTRUCT checks in default_unfasten_wrench + return CANT_UNFASTEN + + if (using_irrigation) + if (!silent) + to_chat(user, "Disconnect the hoses first!") + return FAILED_UNFASTEN + + return ..() + +/obj/machinery/hydroponics/attack_hand(mob/user) + . = ..() + if(.) + return + if(issilicon(user)) //How does AI know what plant is? + return + if(harvest) + return myseed.harvest(user) + + else if(dead) + dead = 0 + to_chat(user, "You remove the dead plant from [src].") + qdel(myseed) + myseed = null + update_icon() + TRAY_NAME_UPDATE + else + if(user) + examine(user) + +/obj/machinery/hydroponics/proc/update_tray(mob/user) + harvest = 0 + lastproduce = age + if(istype(myseed, /obj/item/seeds/replicapod)) + to_chat(user, "You harvest from the [myseed.plantname].") + else if(myseed.getYield() <= 0) + to_chat(user, "You fail to harvest anything useful!") + else + to_chat(user, "You harvest [myseed.getYield()] items from the [myseed.plantname].") + if(!myseed.get_gene(/datum/plant_gene/trait/repeated_harvest)) + qdel(myseed) + myseed = null + dead = 0 + TRAY_NAME_UPDATE + update_icon() + +/// Tray Setters - The following procs adjust the tray or plants variables, and make sure that the stat doesn't go out of bounds./// +/obj/machinery/hydroponics/proc/adjustNutri(adjustamt) + nutrilevel = clamp(nutrilevel + adjustamt, 0, maxnutri) + +/obj/machinery/hydroponics/proc/adjustWater(adjustamt) + waterlevel = clamp(waterlevel + adjustamt, 0, maxwater) + + if(adjustamt>0) + adjustToxic(-round(adjustamt/4))//Toxicity dilutation code. The more water you put in, the lesser the toxin concentration. + +/obj/machinery/hydroponics/proc/adjustHealth(adjustamt) + if(myseed && !dead) + plant_health = clamp(plant_health + adjustamt, 0, myseed.endurance) + +/obj/machinery/hydroponics/proc/adjustToxic(adjustamt) + toxic = clamp(toxic + adjustamt, 0, 100) + +/obj/machinery/hydroponics/proc/adjustPests(adjustamt) + pestlevel = clamp(pestlevel + adjustamt, 0, 10) + +/obj/machinery/hydroponics/proc/adjustWeeds(adjustamt) + weedlevel = clamp(weedlevel + adjustamt, 0, 10) + +/obj/machinery/hydroponics/proc/spawnplant() // why would you put strange reagent in a hydro tray you monster I bet you also feed them blood + var/list/livingplants = list(/mob/living/simple_animal/hostile/tree, /mob/living/simple_animal/hostile/killertomato) + var/chosen = pick(livingplants) + var/mob/living/simple_animal/hostile/C = new chosen + C.faction = list("plants") + +/obj/machinery/hydroponics/proc/become_self_sufficient() // Ambrosia Gaia effect + visible_message("[src] begins to glow with a beautiful light!") + self_sustaining = TRUE + update_icon() + +/////////////////////////////////////////////////////////////////////////////// +/obj/machinery/hydroponics/soil //Not actually hydroponics at all! Honk! + name = "soil" + desc = "A patch of dirt." + icon = 'icons/obj/hydroponics/equipment.dmi' + icon_state = "soil" + gender = PLURAL + circuit = null + density = FALSE + use_power = NO_POWER_USE + flags_1 = NODECONSTRUCT_1 + unwrenchable = FALSE + +/obj/machinery/hydroponics/soil/update_icon_hoses() + return // Has no hoses + +/obj/machinery/hydroponics/soil/update_icon_lights() + return // Has no lights + +/obj/machinery/hydroponics/soil/attackby(obj/item/O, mob/user, params) + if(O.tool_behaviour == TOOL_SHOVEL && !istype(O, /obj/item/shovel/spade)) //Doesn't include spades because of uprooting plants + to_chat(user, "You clear up [src]!") + qdel(src) + else + return ..() diff --git a/code/modules/hydroponics/seed_extractor.dm b/code/modules/hydroponics/seed_extractor.dm index c1bf3b359abe..b7444e965cd3 100644 --- a/code/modules/hydroponics/seed_extractor.dm +++ b/code/modules/hydroponics/seed_extractor.dm @@ -1,197 +1,199 @@ -/proc/seedify(obj/item/O, t_max, obj/machinery/seed_extractor/extractor, mob/living/user) - var/t_amount = 0 - var/list/seeds = list() - if(t_max == -1) - if(extractor) - t_max = rand(1,4) * extractor.seed_multiplier - else - t_max = rand(1,4) - - var/seedloc = O.loc - if(extractor) - seedloc = extractor.loc - - if(istype(O, /obj/item/reagent_containers/food/snacks/grown/)) - var/obj/item/reagent_containers/food/snacks/grown/F = O - if(F.seed) - if(user && !user.temporarilyRemoveItemFromInventory(O)) //couldn't drop the item - return - while(t_amount < t_max) - var/obj/item/seeds/t_prod = F.seed.Copy() - seeds.Add(t_prod) - t_prod.forceMove(seedloc) - t_amount++ - qdel(O) - return seeds - - else if(istype(O, /obj/item/grown)) - var/obj/item/grown/F = O - if(F.seed) - if(user && !user.temporarilyRemoveItemFromInventory(O)) - return - while(t_amount < t_max) - var/obj/item/seeds/t_prod = F.seed.Copy() - t_prod.forceMove(seedloc) - t_amount++ - qdel(O) - return 1 - - return 0 - - -/obj/machinery/seed_extractor - name = "seed extractor" - desc = "Extracts and bags seeds from produce." - icon = 'icons/obj/hydroponics/equipment.dmi' - icon_state = "sextractor" - density = TRUE - circuit = /obj/item/circuitboard/machine/seed_extractor - var/piles = list() - var/max_seeds = 1000 - var/seed_multiplier = 1 - -/obj/machinery/seed_extractor/RefreshParts() - for(var/obj/item/stock_parts/matter_bin/B in component_parts) - max_seeds = 1000 * B.rating - for(var/obj/item/stock_parts/manipulator/M in component_parts) - seed_multiplier = M.rating - -/obj/machinery/seed_extractor/examine(mob/user) - . = ..() - if(in_range(user, src) || isobserver(user)) - . += "The status display reads: Extracting [seed_multiplier] seed(s) per piece of produce.
                    Machine can store up to [max_seeds]% seeds.
                    " - -/obj/machinery/seed_extractor/attackby(obj/item/O, mob/user, params) - - if(default_deconstruction_screwdriver(user, "sextractor_open", "sextractor", O)) - return - - if(default_pry_open(O)) - return - - if(default_unfasten_wrench(user, O)) - return - - if(default_deconstruction_crowbar(O)) - return - - if(istype(O, /obj/item/storage/bag/plants)) - var/obj/item/storage/P = O - var/loaded = 0 - for(var/obj/item/seeds/G in P.contents) - if(contents.len >= max_seeds) - break - ++loaded - add_seed(G) - if (loaded) - to_chat(user, "You put as many seeds from \the [O.name] into [src] as you can.") - else - to_chat(user, "There are no seeds in \the [O.name].") - return - - else if(seedify(O,-1, src, user)) - to_chat(user, "You extract some seeds.") - return - else if (istype(O, /obj/item/seeds)) - if(add_seed(O)) - to_chat(user, "You add [O] to [src.name].") - updateUsrDialog() - return - else if(user.a_intent != INTENT_HARM) - to_chat(user, "You can't extract any seeds from \the [O.name]!") - else - return ..() - -/datum/seed_pile - var/name = "" - var/lifespan = 0 //Saved stats - var/endurance = 0 - var/maturation = 0 - var/production = 0 - var/yield = 0 - var/potency = 0 - var/amount = 0 - -/datum/seed_pile/New(name, life, endur, matur, prod, yie, poten, am = 1) - src.name = name - src.lifespan = life - src.endurance = endur - src.maturation = matur - src.production = prod - src.yield = yie - src.potency = poten - src.amount = am - -/obj/machinery/seed_extractor/ui_interact(mob/user) - . = ..() - if (machine_stat) - return FALSE - - var/dat = "Stored seeds:
                    " - - if (contents.len == 0) - dat += "No seeds" - else - dat += "" - for (var/datum/seed_pile/O in piles) - dat += "" - dat += "" - dat += "
                    NameLifespanEnduranceMaturationProductionYieldPotencyStock
                    [O.name][O.lifespan][O.endurance][O.maturation][O.production][O.yield][O.potency]" - dat += "Vend ([O.amount] left)
                    " - var/datum/browser/popup = new(user, "seed_ext", name, 700, 400) - popup.set_content(dat) - popup.open() - return - -/obj/machinery/seed_extractor/Topic(href, list/href_list) - if(..()) - return - usr.set_machine(src) - - href_list["li"] = text2num(href_list["li"]) - href_list["en"] = text2num(href_list["en"]) - href_list["ma"] = text2num(href_list["ma"]) - href_list["pr"] = text2num(href_list["pr"]) - href_list["yi"] = text2num(href_list["yi"]) - href_list["pot"] = text2num(href_list["pot"]) - - for (var/datum/seed_pile/N in piles)//Find the pile we need to reduce... - if (href_list["name"] == N.name && href_list["li"] == N.lifespan && href_list["en"] == N.endurance && href_list["ma"] == N.maturation && href_list["pr"] == N.production && href_list["yi"] == N.yield && href_list["pot"] == N.potency) - if(N.amount <= 0) - return - N.amount = max(N.amount - 1, 0) - if (N.amount <= 0) - piles -= N - qdel(N) - break - - for (var/obj/T in contents)//Now we find the seed we need to vend - var/obj/item/seeds/O = T - if (O.plantname == href_list["name"] && O.lifespan == href_list["li"] && O.endurance == href_list["en"] && O.maturation == href_list["ma"] && O.production == href_list["pr"] && O.yield == href_list["yi"] && O.potency == href_list["pot"]) - O.forceMove(drop_location()) - break - - src.updateUsrDialog() - return - -/obj/machinery/seed_extractor/proc/add_seed(obj/item/seeds/O) - if(contents.len >= 999) - to_chat(usr, "\The [src] is full.") - return FALSE - - var/datum/component/storage/STR = O.loc.GetComponent(/datum/component/storage) - if(STR) - if(!STR.remove_from_storage(O,src)) - return FALSE - else if(ismob(O.loc)) - var/mob/M = O.loc - if(!M.transferItemToLoc(O, src)) - return FALSE - - . = TRUE - for (var/datum/seed_pile/N in piles) - if (O.plantname == N.name && O.lifespan == N.lifespan && O.endurance == N.endurance && O.maturation == N.maturation && O.production == N.production && O.yield == N.yield && O.potency == N.potency) - ++N.amount - return - - piles += new /datum/seed_pile(O.plantname, O.lifespan, O.endurance, O.maturation, O.production, O.yield, O.potency) +/** + * Finds and extracts seeds from an object + * + * Checks if the object is such that creates a seed when extracted. Used by seed + * extractors or posably anything that would create seeds in some way. The seeds + * are dropped either at the extractor, if it exists, or where the original object + * was and it qdel's the object + * + * Arguments: + * * O - Object containing the seed, can be the loc of the dumping of seeds + * * t_max - Amount of seed copies to dump, -1 is ranomized + * * extractor - Seed Extractor, used as the dumping loc for the seeds and seed multiplier + * * user - checks if we can remove the object from the inventory + * * + */ +/proc/seedify(obj/item/O, t_max, obj/machinery/seed_extractor/extractor, mob/living/user) + var/t_amount = 0 + var/list/seeds = list() + if(t_max == -1) + if(extractor) + t_max = rand(1,4) * extractor.seed_multiplier + else + t_max = rand(1,4) + + var/seedloc = O.loc + if(extractor) + seedloc = extractor.loc + + if(istype(O, /obj/item/reagent_containers/food/snacks/grown/)) + var/obj/item/reagent_containers/food/snacks/grown/F = O + if(F.seed) + if(user && !user.temporarilyRemoveItemFromInventory(O)) //couldn't drop the item + return + while(t_amount < t_max) + var/obj/item/seeds/t_prod = F.seed.Copy() + seeds.Add(t_prod) + t_prod.forceMove(seedloc) + t_amount++ + qdel(O) + return seeds + + else if(istype(O, /obj/item/grown)) + var/obj/item/grown/F = O + if(F.seed) + if(user && !user.temporarilyRemoveItemFromInventory(O)) + return + while(t_amount < t_max) + var/obj/item/seeds/t_prod = F.seed.Copy() + t_prod.forceMove(seedloc) + t_amount++ + qdel(O) + return 1 + + return 0 + + +/obj/machinery/seed_extractor + name = "seed extractor" + desc = "Extracts and bags seeds from produce." + icon = 'icons/obj/hydroponics/equipment.dmi' + icon_state = "sextractor" + density = TRUE + circuit = /obj/item/circuitboard/machine/seed_extractor + /// Associated list of seeds, they are all weak refs. We check the len to see how many refs we have for each + // seed + var/list/piles = list() + var/max_seeds = 1000 + var/seed_multiplier = 1 + +/obj/machinery/seed_extractor/RefreshParts() + for(var/obj/item/stock_parts/matter_bin/B in component_parts) + max_seeds = max_seeds * B.rating + for(var/obj/item/stock_parts/manipulator/M in component_parts) + seed_multiplier = seed_multiplier * M.rating + +/obj/machinery/seed_extractor/examine(mob/user) + . = ..() + if(in_range(user, src) || isobserver(user)) + . += "The status display reads: Extracting [seed_multiplier] seed(s) per piece of produce.
                    Machine can store up to [max_seeds]% seeds.
                    " + +/obj/machinery/seed_extractor/attackby(obj/item/O, mob/user, params) + + if(default_deconstruction_screwdriver(user, "sextractor_open", "sextractor", O)) + return + + if(default_pry_open(O)) + return + + if(default_unfasten_wrench(user, O)) + return + + if(default_deconstruction_crowbar(O)) + return + + if(istype(O, /obj/item/storage/bag/plants)) + var/obj/item/storage/P = O + var/loaded = 0 + for(var/obj/item/seeds/G in P.contents) + if(contents.len >= max_seeds) + break + ++loaded + add_seed(G) + if (loaded) + to_chat(user, "You put as many seeds from \the [O.name] into [src] as you can.") + else + to_chat(user, "There are no seeds in \the [O.name].") + return + + else if(seedify(O,-1, src, user)) + to_chat(user, "You extract some seeds.") + return + else if (istype(O, /obj/item/seeds)) + if(add_seed(O)) + to_chat(user, "You add [O] to [src.name].") + updateUsrDialog() + return + else if(user.a_intent != INTENT_HARM) + to_chat(user, "You can't extract any seeds from \the [O.name]!") + else + return ..() + +/** + * Generate seed string + * + * Creates a string based of the traits of a seed. We use this string as a bucket for all + * seeds that match as well as the key the ui uses to get the seed. We also use the key + * for the data shown in the ui. Javascript parses this string to display + * + * Arguments: + * * O - seed to generate the string from + */ +/obj/machinery/seed_extractor/proc/generate_seed_string(obj/item/seeds/O) + return "name=[O.name];lifespan=[O.lifespan];endurance=[O.endurance];maturation=[O.maturation];production=[O.production];yield=[O.yield];potency=[O.potency]]" + + +/** Add Seeds Proc. + * + * Adds the seeds to the contents and to an associated list that pregenerates the data + * needed to go to the ui handler + * + **/ +/obj/machinery/seed_extractor/proc/add_seed(obj/item/seeds/O) + if(contents.len >= 999) + to_chat(usr, "\The [src] is full.") + return FALSE + + var/datum/component/storage/STR = O.loc.GetComponent(/datum/component/storage) + if(STR) + if(!STR.remove_from_storage(O,src)) + return FALSE + else if(ismob(O.loc)) + var/mob/M = O.loc + if(!M.transferItemToLoc(O, src)) + return FALSE + + var/seed_string = generate_seed_string(O) + if(piles[seed_string]) + piles[seed_string] += WEAKREF(O) + else + piles[seed_string] = list(WEAKREF(O)) + + . = TRUE + +/obj/machinery/seed_extractor/ui_state(mob/user) + return GLOB.notcontained_state + +/obj/machinery/seed_extractor/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "SeedExtractor", name) + ui.open() + +/obj/machinery/seed_extractor/ui_data() + var/list/V = list() + for(var/key in piles) + if(piles[key]) + var/len = length(piles[key]) + if(len) + V[key] = len + + . = list() + .["seeds"] = V + +/obj/machinery/seed_extractor/ui_act(action, params) + if(..()) + return + + switch(action) + if("select") + var/item = params["item"] + if(piles[item] && length(piles[item]) > 0) + var/datum/weakref/WO = piles[item][1] + var/obj/item/seeds/O = WO.resolve() + if(O) + piles[item] -= WO + O.forceMove(drop_location()) + . = TRUE + //to_chat(usr, "[src] clanks to life briefly before vending [prize.equipment_name]!") + diff --git a/code/modules/hydroponics/seeds.dm b/code/modules/hydroponics/seeds.dm index 444b849aa2c7..0f028c215e84 100644 --- a/code/modules/hydroponics/seeds.dm +++ b/code/modules/hydroponics/seeds.dm @@ -1,456 +1,456 @@ -// ******************************************************** -// Here's all the seeds (plants) that can be used in hydro -// ******************************************************** - -/obj/item/seeds - icon = 'icons/obj/hydroponics/seeds.dmi' - icon_state = "seed" // Unknown plant seed - these shouldn't exist in-game. - w_class = WEIGHT_CLASS_TINY - resistance_flags = FLAMMABLE - var/plantname = "Plants" // Name of plant when planted. - var/obj/item/product // A type path. The thing that is created when the plant is harvested. - var/productdesc - var/species = "" // Used to update icons. Should match the name in the sprites unless all icon_* are overridden. - - var/growing_icon = 'icons/obj/hydroponics/growing.dmi' //the file that stores the sprites of the growing plant from this seed. - var/icon_grow // Used to override grow icon (default is "[species]-grow"). You can use one grow icon for multiple closely related plants with it. - var/icon_dead // Used to override dead icon (default is "[species]-dead"). You can use one dead icon for multiple closely related plants with it. - var/icon_harvest // Used to override harvest icon (default is "[species]-harvest"). If null, plant will use [icon_grow][growthstages]. - - var/lifespan = 25 // How long before the plant begins to take damage from age. - var/endurance = 15 // Amount of health the plant has. - var/maturation = 6 // Used to determine which sprite to switch to when growing. - var/production = 6 // Changes the amount of time needed for a plant to become harvestable. - var/yield = 3 // Amount of growns created per harvest. If is -1, the plant/shroom/weed is never meant to be harvested. - var/potency = 10 // The 'power' of a plant. Generally effects the amount of reagent in a plant, also used in other ways. - var/growthstages = 6 // Amount of growth sprites the plant has. - var/rarity = 0 // How rare the plant is. Used for giving points to cargo when shipping off to CentCom. - var/list/mutatelist = list() // The type of plants that this plant can mutate into. - var/list/genes = list() // Plant genes are stored here, see plant_genes.dm for more info. - var/list/reagents_add = list() - // A list of reagents to add to product. - // Format: "reagent_id" = potency multiplier - // Stronger reagents must always come first to avoid being displaced by weaker ones. - // Total amount of any reagent in plant is calculated by formula: 1 + round(potency * multiplier) - - var/weed_rate = 1 //If the chance below passes, then this many weeds sprout during growth - var/weed_chance = 5 //Percentage chance per tray update to grow weeds - -/obj/item/seeds/Initialize(mapload, nogenes = 0) - . = ..() - pixel_x = rand(-8, 8) - pixel_y = rand(-8, 8) - - if(!icon_grow) - icon_grow = "[species]-grow" - - if(!icon_dead) - icon_dead = "[species]-dead" - - if(!icon_harvest && !get_gene(/datum/plant_gene/trait/plant_type/fungal_metabolism) && yield != -1) - icon_harvest = "[species]-harvest" - - if(!nogenes) // not used on Copy() - genes += new /datum/plant_gene/core/lifespan(lifespan) - genes += new /datum/plant_gene/core/endurance(endurance) - genes += new /datum/plant_gene/core/weed_rate(weed_rate) - genes += new /datum/plant_gene/core/weed_chance(weed_chance) - if(yield != -1) - genes += new /datum/plant_gene/core/yield(yield) - genes += new /datum/plant_gene/core/production(production) - if(potency != -1) - genes += new /datum/plant_gene/core/potency(potency) - - for(var/p in genes) - if(ispath(p)) - genes -= p - genes += new p - - for(var/reag_id in reagents_add) - genes += new /datum/plant_gene/reagent(reag_id, reagents_add[reag_id]) - reagents_from_genes() //quality coding - -/obj/item/seeds/examine(mob/user) - . = ..() - . += "Use a pen on it to rename it or change its description." - -/obj/item/seeds/proc/Copy() - var/obj/item/seeds/S = new type(null, 1) - // Copy all the stats - S.lifespan = lifespan - S.endurance = endurance - S.maturation = maturation - S.production = production - S.yield = yield - S.potency = potency - S.weed_rate = weed_rate - S.weed_chance = weed_chance - S.name = name - S.plantname = plantname - S.desc = desc - S.productdesc = productdesc - S.genes = list() - for(var/g in genes) - var/datum/plant_gene/G = g - S.genes += G.Copy() - S.reagents_add = reagents_add.Copy() // Faster than grabbing the list from genes. - return S - -/obj/item/seeds/proc/get_gene(typepath) - return (locate(typepath) in genes) - -/obj/item/seeds/proc/reagents_from_genes() - reagents_add = list() - for(var/datum/plant_gene/reagent/R in genes) - reagents_add[R.reagent_id] = R.rate - -///This proc adds a mutability_flag to a gene -/obj/item/seeds/proc/set_mutability(typepath, mutability) - var/datum/plant_gene/g = get_gene(typepath) - if(g) - g.mutability_flags |= mutability - -///This proc removes a mutability_flag from a gene -/obj/item/seeds/proc/unset_mutability(typepath, mutability) - var/datum/plant_gene/g = get_gene(typepath) - if(g) - g.mutability_flags &= ~mutability - -/obj/item/seeds/proc/mutate(lifemut = 2, endmut = 5, productmut = 1, yieldmut = 2, potmut = 25, wrmut = 2, wcmut = 5, traitmut = 0) - adjust_lifespan(rand(-lifemut,lifemut)) - adjust_endurance(rand(-endmut,endmut)) - adjust_production(rand(-productmut,productmut)) - adjust_yield(rand(-yieldmut,yieldmut)) - adjust_potency(rand(-potmut,potmut)) - adjust_weed_rate(rand(-wrmut, wrmut)) - adjust_weed_chance(rand(-wcmut, wcmut)) - if(prob(traitmut)) - add_random_traits(1, 1) - - - -/obj/item/seeds/bullet_act(obj/projectile/Proj) //Works with the Somatoray to modify plant variables. - if(istype(Proj, /obj/projectile/energy/florayield)) - var/rating = 1 - if(istype(loc, /obj/machinery/hydroponics)) - var/obj/machinery/hydroponics/H = loc - rating = H.rating - - if(yield == 0)//Oh god don't divide by zero you'll doom us all. - adjust_yield(1 * rating) - else if(prob(1/(yield * yield) * 100))//This formula gives you diminishing returns based on yield. 100% with 1 yield, decreasing to 25%, 11%, 6, 4, 2... - adjust_yield(1 * rating) - else - return ..() - - -// Harvest procs -/obj/item/seeds/proc/getYield() - var/return_yield = yield - - var/obj/machinery/hydroponics/parent = loc - if(istype(loc, /obj/machinery/hydroponics)) - if(parent.yieldmod == 0) - return_yield = min(return_yield, 1)//1 if above zero, 0 otherwise - else - return_yield *= (parent.yieldmod) - - return return_yield - - -/obj/item/seeds/proc/harvest(mob/user) - var/obj/machinery/hydroponics/parent = loc //for ease of access - var/t_amount = 0 - var/list/result = list() - var/output_loc = parent.Adjacent(user) ? user.loc : parent.loc //needed for TK - var/product_name - while(t_amount < getYield()) - var/obj/item/reagent_containers/food/snacks/grown/t_prod = new product(output_loc, src) - if(parent.myseed.plantname != initial(parent.myseed.plantname)) - t_prod.name = lowertext(parent.myseed.plantname) - if(productdesc) - t_prod.desc = productdesc - t_prod.seed.name = parent.myseed.name - t_prod.seed.desc = parent.myseed.desc - t_prod.seed.plantname = parent.myseed.plantname - result.Add(t_prod) // User gets a consumable - if(!t_prod) - return - t_amount++ - product_name = parent.myseed.plantname - if(getYield() >= 1) - SSblackbox.record_feedback("tally", "food_harvested", getYield(), product_name) - parent.update_tray(user) - - return result - - -/obj/item/seeds/proc/prepare_result(var/obj/item/T) - if(!T.reagents) - CRASH("[T] has no reagents.") - - for(var/rid in reagents_add) - var/amount = max(1, round(potency * reagents_add[rid], 1)) //the plant will always have at least 1u of each of the reagents in its reagent production traits - - var/list/data = null - if(rid == /datum/reagent/blood) // Hack to make blood in plants always O- - data = list("blood_type" = "O-") - if(rid == /datum/reagent/consumable/nutriment || rid == /datum/reagent/consumable/nutriment/vitamin) - // apple tastes of apple. - if(istype(T, /obj/item/reagent_containers/food/snacks/grown)) - var/obj/item/reagent_containers/food/snacks/grown/grown_edible = T - data = grown_edible.tastes - - T.reagents.add_reagent(rid, amount, data) - - -/// Setters procs /// -/obj/item/seeds/proc/adjust_yield(adjustamt) - if(yield != -1) // Unharvestable shouldn't suddenly turn harvestable - yield = clamp(yield + adjustamt, 0, 10) - - if(yield <= 0 && get_gene(/datum/plant_gene/trait/plant_type/fungal_metabolism)) - yield = 1 // Mushrooms always have a minimum yield of 1. - var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/yield) - if(C) - C.value = yield - -/obj/item/seeds/proc/adjust_lifespan(adjustamt) - lifespan = clamp(lifespan + adjustamt, 10, 100) - var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/lifespan) - if(C) - C.value = lifespan - -/obj/item/seeds/proc/adjust_endurance(adjustamt) - endurance = clamp(endurance + adjustamt, 10, 100) - var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/endurance) - if(C) - C.value = endurance - -/obj/item/seeds/proc/adjust_production(adjustamt) - if(yield != -1) - production = clamp(production + adjustamt, 1, 10) - var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/production) - if(C) - C.value = production - -/obj/item/seeds/proc/adjust_potency(adjustamt) - if(potency != -1) - potency = clamp(potency + adjustamt, 0, 100) - var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/potency) - if(C) - C.value = potency - -/obj/item/seeds/proc/adjust_weed_rate(adjustamt) - weed_rate = clamp(weed_rate + adjustamt, 0, 10) - var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/weed_rate) - if(C) - C.value = weed_rate - -/obj/item/seeds/proc/adjust_weed_chance(adjustamt) - weed_chance = clamp(weed_chance + adjustamt, 0, 67) - var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/weed_chance) - if(C) - C.value = weed_chance - -//Directly setting stats - -/obj/item/seeds/proc/set_yield(adjustamt) - if(yield != -1) // Unharvestable shouldn't suddenly turn harvestable - yield = clamp(adjustamt, 0, 10) - - if(yield <= 0 && get_gene(/datum/plant_gene/trait/plant_type/fungal_metabolism)) - yield = 1 // Mushrooms always have a minimum yield of 1. - var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/yield) - if(C) - C.value = yield - -/obj/item/seeds/proc/set_lifespan(adjustamt) - lifespan = clamp(adjustamt, 10, 100) - var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/lifespan) - if(C) - C.value = lifespan - -/obj/item/seeds/proc/set_endurance(adjustamt) - endurance = clamp(adjustamt, 10, 100) - var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/endurance) - if(C) - C.value = endurance - -/obj/item/seeds/proc/set_production(adjustamt) - if(yield != -1) - production = clamp(adjustamt, 1, 10) - var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/production) - if(C) - C.value = production - -/obj/item/seeds/proc/set_potency(adjustamt) - if(potency != -1) - potency = clamp(adjustamt, 0, 100) - var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/potency) - if(C) - C.value = potency - -/obj/item/seeds/proc/set_weed_rate(adjustamt) - weed_rate = clamp(adjustamt, 0, 10) - var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/weed_rate) - if(C) - C.value = weed_rate - -/obj/item/seeds/proc/set_weed_chance(adjustamt) - weed_chance = clamp(adjustamt, 0, 67) - var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/weed_chance) - if(C) - C.value = weed_chance - - -/obj/item/seeds/proc/get_analyzer_text() //in case seeds have something special to tell to the analyzer - var/text = "" - if(!get_gene(/datum/plant_gene/trait/plant_type/weed_hardy) && !get_gene(/datum/plant_gene/trait/plant_type/fungal_metabolism) && !get_gene(/datum/plant_gene/trait/plant_type/alien_properties)) - text += "- Plant type: Normal plant\n" - if(get_gene(/datum/plant_gene/trait/plant_type/weed_hardy)) - text += "- Plant type: Weed. Can grow in nutrient-poor soil.\n" - if(get_gene(/datum/plant_gene/trait/plant_type/fungal_metabolism)) - text += "- Plant type: Mushroom. Can grow in dry soil.\n" - if(get_gene(/datum/plant_gene/trait/plant_type/alien_properties)) - text += "- Plant type: UNKNOWN \n" - if(potency != -1) - text += "- Potency: [potency]\n" - if(yield != -1) - text += "- Yield: [yield]\n" - text += "- Maturation speed: [maturation]\n" - if(yield != -1) - text += "- Production speed: [production]\n" - text += "- Endurance: [endurance]\n" - text += "- Lifespan: [lifespan]\n" - text += "- Weed Growth Rate: [weed_rate]\n" - text += "- Weed Vulnerability: [weed_chance]\n" - if(rarity) - text += "- Species Discovery Value: [rarity]\n" - var/all_traits = "" - for(var/datum/plant_gene/trait/traits in genes) - if(istype(traits, /datum/plant_gene/trait/plant_type)) - continue - all_traits += " [traits.get_name()]" - text += "- Plant Traits:[all_traits]\n" - - text += "*---------*" - - return text - -/obj/item/seeds/proc/on_chem_reaction(datum/reagents/S) //in case seeds have some special interaction with special chems - return - -/obj/item/seeds/attackby(obj/item/O, mob/user, params) - if (istype(O, /obj/item/plant_analyzer)) - to_chat(user, "*---------*\n This is \a [src].") - var/text = get_analyzer_text() - if(text) - to_chat(user, "[text]") - - return - - if(istype(O, /obj/item/pen)) - var/choice = input("What would you like to change?") in list("Plant Name", "Seed Description", "Product Description", "Cancel") - if(!user.canUseTopic(src, BE_CLOSE)) - return - switch(choice) - if("Plant Name") - var/newplantname = reject_bad_text(stripped_input(user, "Write a new plant name:", name, plantname)) - if(!user.canUseTopic(src, BE_CLOSE)) - return - if (length(newplantname) > 20) - to_chat(user, "That name is too long!") - return - if(!newplantname) - to_chat(user, "That name is invalid.") - return - else - name = "[lowertext(newplantname)]" - plantname = newplantname - if("Seed Description") - var/newdesc = stripped_input(user, "Write a new description:", name, desc) - if(!user.canUseTopic(src, BE_CLOSE)) - return - if (length(newdesc) > 180) - to_chat(user, "That description is too long!") - return - if(!newdesc) - to_chat(user, "That description is invalid.") - return - else - desc = newdesc - if("Product Description") - if(product && !productdesc) - productdesc = initial(product.desc) - var/newproductdesc = stripped_input(user, "Write a new description:", name, productdesc) - if(!user.canUseTopic(src, BE_CLOSE)) - return - if (length(newproductdesc) > 180) - to_chat(user, "That description is too long!") - return - if(!newproductdesc) - to_chat(user, "That description is invalid.") - return - else - productdesc = newproductdesc - else - return - - ..() // Fallthrough to item/attackby() so that bags can pick seeds up - -/obj/item/seeds/proc/randomize_stats() - set_lifespan(rand(25, 60)) - set_endurance(rand(15, 35)) - set_production(rand(2, 10)) - set_yield(rand(1, 10)) - set_potency(rand(10, 35)) - set_weed_rate(rand(1, 10)) - set_weed_chance(rand(5, 100)) - maturation = rand(6, 12) - -/obj/item/seeds/proc/add_random_reagents(lower = 0, upper = 2) - var/amount_random_reagents = rand(lower, upper) - for(var/i in 1 to amount_random_reagents) - var/random_amount = rand(4, 15) * 0.01 // this must be multiplied by 0.01, otherwise, it will not properly associate - var/datum/plant_gene/reagent/R = new(get_random_reagent_id(), random_amount) - if(R.can_add(src)) - genes += R - else - qdel(R) - reagents_from_genes() - -/obj/item/seeds/proc/add_random_traits(lower = 0, upper = 2) - var/amount_random_traits = rand(lower, upper) - for(var/i in 1 to amount_random_traits) - var/random_trait = pick((subtypesof(/datum/plant_gene/trait)-typesof(/datum/plant_gene/trait/plant_type))) - var/datum/plant_gene/trait/T = new random_trait - if(T.can_add(src)) - genes += T - else - qdel(T) - -/obj/item/seeds/proc/add_random_plant_type(normal_plant_chance = 75) - if(prob(normal_plant_chance)) - var/random_plant_type = pick(subtypesof(/datum/plant_gene/trait/plant_type)) - var/datum/plant_gene/trait/plant_type/P = new random_plant_type - if(P.can_add(src)) - genes += P - else - qdel(P) - -/obj/item/seeds/proc/remove_random_reagents(lower = 0, upper = 2) - var/amount_random_reagents = rand(lower, upper) - for(var/i in 1 to amount_random_reagents) - var/datum/reagent/chemical = pick(reagents_add) - qdel(chemical) - -/obj/item/seeds/proc/remove_random_traits(lower = 0, upper = 2) - var/list/genepool = list() - var/amount_random_traits = rand(lower, upper) - for(var/datum/plant_gene/trait in genes) - genepool += trait - - for(var/i in 1 to amount_random_traits) - var/datum/plant_gene/planted_gene = pick(genepool) - qdel(planted_gene) +// ******************************************************** +// Here's all the seeds (plants) that can be used in hydro +// ******************************************************** + +/obj/item/seeds + icon = 'icons/obj/hydroponics/seeds.dmi' + icon_state = "seed" // Unknown plant seed - these shouldn't exist in-game. + w_class = WEIGHT_CLASS_TINY + resistance_flags = FLAMMABLE + var/plantname = "Plants" // Name of plant when planted. + var/obj/item/product // A type path. The thing that is created when the plant is harvested. + var/productdesc + var/species = "" // Used to update icons. Should match the name in the sprites unless all icon_* are overridden. + + var/growing_icon = 'icons/obj/hydroponics/growing.dmi' //the file that stores the sprites of the growing plant from this seed. + var/icon_grow // Used to override grow icon (default is "[species]-grow"). You can use one grow icon for multiple closely related plants with it. + var/icon_dead // Used to override dead icon (default is "[species]-dead"). You can use one dead icon for multiple closely related plants with it. + var/icon_harvest // Used to override harvest icon (default is "[species]-harvest"). If null, plant will use [icon_grow][growthstages]. + + var/lifespan = 25 // How long before the plant begins to take damage from age. + var/endurance = 15 // Amount of health the plant has. + var/maturation = 6 // Used to determine which sprite to switch to when growing. + var/production = 6 // Changes the amount of time needed for a plant to become harvestable. + var/yield = 3 // Amount of growns created per harvest. If is -1, the plant/shroom/weed is never meant to be harvested. + var/potency = 10 // The 'power' of a plant. Generally effects the amount of reagent in a plant, also used in other ways. + var/growthstages = 6 // Amount of growth sprites the plant has. + var/rarity = 0 // How rare the plant is. Used for giving points to cargo when shipping off to CentCom. + var/list/mutatelist = list() // The type of plants that this plant can mutate into. + var/list/genes = list() // Plant genes are stored here, see plant_genes.dm for more info. + var/list/reagents_add = list() + // A list of reagents to add to product. + // Format: "reagent_id" = potency multiplier + // Stronger reagents must always come first to avoid being displaced by weaker ones. + // Total amount of any reagent in plant is calculated by formula: 1 + round(potency * multiplier) + + var/weed_rate = 1 //If the chance below passes, then this many weeds sprout during growth + var/weed_chance = 5 //Percentage chance per tray update to grow weeds + +/obj/item/seeds/Initialize(mapload, nogenes = 0) + . = ..() + pixel_x = rand(-8, 8) + pixel_y = rand(-8, 8) + + if(!icon_grow) + icon_grow = "[species]-grow" + + if(!icon_dead) + icon_dead = "[species]-dead" + + if(!icon_harvest && !get_gene(/datum/plant_gene/trait/plant_type/fungal_metabolism) && yield != -1) + icon_harvest = "[species]-harvest" + + if(!nogenes) // not used on Copy() + genes += new /datum/plant_gene/core/lifespan(lifespan) + genes += new /datum/plant_gene/core/endurance(endurance) + genes += new /datum/plant_gene/core/weed_rate(weed_rate) + genes += new /datum/plant_gene/core/weed_chance(weed_chance) + if(yield != -1) + genes += new /datum/plant_gene/core/yield(yield) + genes += new /datum/plant_gene/core/production(production) + if(potency != -1) + genes += new /datum/plant_gene/core/potency(potency) + + for(var/p in genes) + if(ispath(p)) + genes -= p + genes += new p + + for(var/reag_id in reagents_add) + genes += new /datum/plant_gene/reagent(reag_id, reagents_add[reag_id]) + reagents_from_genes() //quality coding + +/obj/item/seeds/examine(mob/user) + . = ..() + . += "Use a pen on it to rename it or change its description." + +/obj/item/seeds/proc/Copy() + var/obj/item/seeds/S = new type(null, 1) + // Copy all the stats + S.lifespan = lifespan + S.endurance = endurance + S.maturation = maturation + S.production = production + S.yield = yield + S.potency = potency + S.weed_rate = weed_rate + S.weed_chance = weed_chance + S.name = name + S.plantname = plantname + S.desc = desc + S.productdesc = productdesc + S.genes = list() + for(var/g in genes) + var/datum/plant_gene/G = g + S.genes += G.Copy() + S.reagents_add = reagents_add.Copy() // Faster than grabbing the list from genes. + return S + +/obj/item/seeds/proc/get_gene(typepath) + return (locate(typepath) in genes) + +/obj/item/seeds/proc/reagents_from_genes() + reagents_add = list() + for(var/datum/plant_gene/reagent/R in genes) + reagents_add[R.reagent_id] = R.rate + +///This proc adds a mutability_flag to a gene +/obj/item/seeds/proc/set_mutability(typepath, mutability) + var/datum/plant_gene/g = get_gene(typepath) + if(g) + g.mutability_flags |= mutability + +///This proc removes a mutability_flag from a gene +/obj/item/seeds/proc/unset_mutability(typepath, mutability) + var/datum/plant_gene/g = get_gene(typepath) + if(g) + g.mutability_flags &= ~mutability + +/obj/item/seeds/proc/mutate(lifemut = 2, endmut = 5, productmut = 1, yieldmut = 2, potmut = 25, wrmut = 2, wcmut = 5, traitmut = 0) + adjust_lifespan(rand(-lifemut,lifemut)) + adjust_endurance(rand(-endmut,endmut)) + adjust_production(rand(-productmut,productmut)) + adjust_yield(rand(-yieldmut,yieldmut)) + adjust_potency(rand(-potmut,potmut)) + adjust_weed_rate(rand(-wrmut, wrmut)) + adjust_weed_chance(rand(-wcmut, wcmut)) + if(prob(traitmut)) + add_random_traits(1, 1) + + + +/obj/item/seeds/bullet_act(obj/projectile/Proj) //Works with the Somatoray to modify plant variables. + if(istype(Proj, /obj/projectile/energy/florayield)) + var/rating = 1 + if(istype(loc, /obj/machinery/hydroponics)) + var/obj/machinery/hydroponics/H = loc + rating = H.rating + + if(yield == 0)//Oh god don't divide by zero you'll doom us all. + adjust_yield(1 * rating) + else if(prob(1/(yield * yield) * 100))//This formula gives you diminishing returns based on yield. 100% with 1 yield, decreasing to 25%, 11%, 6, 4, 2... + adjust_yield(1 * rating) + else + return ..() + + +// Harvest procs +/obj/item/seeds/proc/getYield() + var/return_yield = yield + + var/obj/machinery/hydroponics/parent = loc + if(istype(loc, /obj/machinery/hydroponics)) + if(parent.yieldmod == 0) + return_yield = min(return_yield, 1)//1 if above zero, 0 otherwise + else + return_yield *= (parent.yieldmod) + + return return_yield + + +/obj/item/seeds/proc/harvest(mob/user) + var/obj/machinery/hydroponics/parent = loc //for ease of access + var/t_amount = 0 + var/list/result = list() + var/output_loc = parent.Adjacent(user) ? user.loc : parent.loc //needed for TK + var/product_name + while(t_amount < getYield()) + var/obj/item/reagent_containers/food/snacks/grown/t_prod = new product(output_loc, src) + if(parent.myseed.plantname != initial(parent.myseed.plantname)) + t_prod.name = lowertext(parent.myseed.plantname) + if(productdesc) + t_prod.desc = productdesc + t_prod.seed.name = parent.myseed.name + t_prod.seed.desc = parent.myseed.desc + t_prod.seed.plantname = parent.myseed.plantname + result.Add(t_prod) // User gets a consumable + if(!t_prod) + return + t_amount++ + product_name = parent.myseed.plantname + if(getYield() >= 1) + SSblackbox.record_feedback("tally", "food_harvested", getYield(), product_name) + parent.update_tray(user) + + return result + + +/obj/item/seeds/proc/prepare_result(var/obj/item/T) + if(!T.reagents) + CRASH("[T] has no reagents.") + + for(var/rid in reagents_add) + var/amount = max(1, round(potency * reagents_add[rid], 1)) //the plant will always have at least 1u of each of the reagents in its reagent production traits + + var/list/data = null + if(rid == /datum/reagent/blood) // Hack to make blood in plants always O- + data = list("blood_type" = "O-") + if(rid == /datum/reagent/consumable/nutriment || rid == /datum/reagent/consumable/nutriment/vitamin) + // apple tastes of apple. + if(istype(T, /obj/item/reagent_containers/food/snacks/grown)) + var/obj/item/reagent_containers/food/snacks/grown/grown_edible = T + data = grown_edible.tastes + + T.reagents.add_reagent(rid, amount, data) + + +/// Setters procs /// +/obj/item/seeds/proc/adjust_yield(adjustamt) + if(yield != -1) // Unharvestable shouldn't suddenly turn harvestable + yield = clamp(yield + adjustamt, 0, 10) + + if(yield <= 0 && get_gene(/datum/plant_gene/trait/plant_type/fungal_metabolism)) + yield = 1 // Mushrooms always have a minimum yield of 1. + var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/yield) + if(C) + C.value = yield + +/obj/item/seeds/proc/adjust_lifespan(adjustamt) + lifespan = clamp(lifespan + adjustamt, 10, 100) + var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/lifespan) + if(C) + C.value = lifespan + +/obj/item/seeds/proc/adjust_endurance(adjustamt) + endurance = clamp(endurance + adjustamt, 10, 100) + var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/endurance) + if(C) + C.value = endurance + +/obj/item/seeds/proc/adjust_production(adjustamt) + if(yield != -1) + production = clamp(production + adjustamt, 1, 10) + var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/production) + if(C) + C.value = production + +/obj/item/seeds/proc/adjust_potency(adjustamt) + if(potency != -1) + potency = clamp(potency + adjustamt, 0, 100) + var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/potency) + if(C) + C.value = potency + +/obj/item/seeds/proc/adjust_weed_rate(adjustamt) + weed_rate = clamp(weed_rate + adjustamt, 0, 10) + var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/weed_rate) + if(C) + C.value = weed_rate + +/obj/item/seeds/proc/adjust_weed_chance(adjustamt) + weed_chance = clamp(weed_chance + adjustamt, 0, 67) + var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/weed_chance) + if(C) + C.value = weed_chance + +//Directly setting stats + +/obj/item/seeds/proc/set_yield(adjustamt) + if(yield != -1) // Unharvestable shouldn't suddenly turn harvestable + yield = clamp(adjustamt, 0, 10) + + if(yield <= 0 && get_gene(/datum/plant_gene/trait/plant_type/fungal_metabolism)) + yield = 1 // Mushrooms always have a minimum yield of 1. + var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/yield) + if(C) + C.value = yield + +/obj/item/seeds/proc/set_lifespan(adjustamt) + lifespan = clamp(adjustamt, 10, 100) + var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/lifespan) + if(C) + C.value = lifespan + +/obj/item/seeds/proc/set_endurance(adjustamt) + endurance = clamp(adjustamt, 10, 100) + var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/endurance) + if(C) + C.value = endurance + +/obj/item/seeds/proc/set_production(adjustamt) + if(yield != -1) + production = clamp(adjustamt, 1, 10) + var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/production) + if(C) + C.value = production + +/obj/item/seeds/proc/set_potency(adjustamt) + if(potency != -1) + potency = clamp(adjustamt, 0, 100) + var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/potency) + if(C) + C.value = potency + +/obj/item/seeds/proc/set_weed_rate(adjustamt) + weed_rate = clamp(adjustamt, 0, 10) + var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/weed_rate) + if(C) + C.value = weed_rate + +/obj/item/seeds/proc/set_weed_chance(adjustamt) + weed_chance = clamp(adjustamt, 0, 67) + var/datum/plant_gene/core/C = get_gene(/datum/plant_gene/core/weed_chance) + if(C) + C.value = weed_chance + + +/obj/item/seeds/proc/get_analyzer_text() //in case seeds have something special to tell to the analyzer + var/text = "" + if(!get_gene(/datum/plant_gene/trait/plant_type/weed_hardy) && !get_gene(/datum/plant_gene/trait/plant_type/fungal_metabolism) && !get_gene(/datum/plant_gene/trait/plant_type/alien_properties)) + text += "- Plant type: Normal plant\n" + if(get_gene(/datum/plant_gene/trait/plant_type/weed_hardy)) + text += "- Plant type: Weed. Can grow in nutrient-poor soil.\n" + if(get_gene(/datum/plant_gene/trait/plant_type/fungal_metabolism)) + text += "- Plant type: Mushroom. Can grow in dry soil.\n" + if(get_gene(/datum/plant_gene/trait/plant_type/alien_properties)) + text += "- Plant type: UNKNOWN \n" + if(potency != -1) + text += "- Potency: [potency]\n" + if(yield != -1) + text += "- Yield: [yield]\n" + text += "- Maturation speed: [maturation]\n" + if(yield != -1) + text += "- Production speed: [production]\n" + text += "- Endurance: [endurance]\n" + text += "- Lifespan: [lifespan]\n" + text += "- Weed Growth Rate: [weed_rate]\n" + text += "- Weed Vulnerability: [weed_chance]\n" + if(rarity) + text += "- Species Discovery Value: [rarity]\n" + var/all_traits = "" + for(var/datum/plant_gene/trait/traits in genes) + if(istype(traits, /datum/plant_gene/trait/plant_type)) + continue + all_traits += " [traits.get_name()]" + text += "- Plant Traits:[all_traits]\n" + + text += "*---------*" + + return text + +/obj/item/seeds/proc/on_chem_reaction(datum/reagents/S) //in case seeds have some special interaction with special chems + return + +/obj/item/seeds/attackby(obj/item/O, mob/user, params) + if (istype(O, /obj/item/plant_analyzer)) + to_chat(user, "*---------*\n This is \a [src].") + var/text = get_analyzer_text() + if(text) + to_chat(user, "[text]") + + return + + if(istype(O, /obj/item/pen)) + var/choice = input("What would you like to change?") in list("Plant Name", "Seed Description", "Product Description", "Cancel") + if(!user.canUseTopic(src, BE_CLOSE)) + return + switch(choice) + if("Plant Name") + var/newplantname = reject_bad_text(stripped_input(user, "Write a new plant name:", name, plantname)) + if(!user.canUseTopic(src, BE_CLOSE)) + return + if (length(newplantname) > 20) + to_chat(user, "That name is too long!") + return + if(!newplantname) + to_chat(user, "That name is invalid.") + return + else + name = "[lowertext(newplantname)]" + plantname = newplantname + if("Seed Description") + var/newdesc = stripped_input(user, "Write a new description:", name, desc) + if(!user.canUseTopic(src, BE_CLOSE)) + return + if (length(newdesc) > 180) + to_chat(user, "That description is too long!") + return + if(!newdesc) + to_chat(user, "That description is invalid.") + return + else + desc = newdesc + if("Product Description") + if(product && !productdesc) + productdesc = initial(product.desc) + var/newproductdesc = stripped_input(user, "Write a new description:", name, productdesc) + if(!user.canUseTopic(src, BE_CLOSE)) + return + if (length(newproductdesc) > 180) + to_chat(user, "That description is too long!") + return + if(!newproductdesc) + to_chat(user, "That description is invalid.") + return + else + productdesc = newproductdesc + else + return + + ..() // Fallthrough to item/attackby() so that bags can pick seeds up + +/obj/item/seeds/proc/randomize_stats() + set_lifespan(rand(25, 60)) + set_endurance(rand(15, 35)) + set_production(rand(2, 10)) + set_yield(rand(1, 10)) + set_potency(rand(10, 35)) + set_weed_rate(rand(1, 10)) + set_weed_chance(rand(5, 100)) + maturation = rand(6, 12) + +/obj/item/seeds/proc/add_random_reagents(lower = 0, upper = 2) + var/amount_random_reagents = rand(lower, upper) + for(var/i in 1 to amount_random_reagents) + var/random_amount = rand(4, 15) * 0.01 // this must be multiplied by 0.01, otherwise, it will not properly associate + var/datum/plant_gene/reagent/R = new(get_random_reagent_id(), random_amount) + if(R.can_add(src)) + genes += R + else + qdel(R) + reagents_from_genes() + +/obj/item/seeds/proc/add_random_traits(lower = 0, upper = 2) + var/amount_random_traits = rand(lower, upper) + for(var/i in 1 to amount_random_traits) + var/random_trait = pick((subtypesof(/datum/plant_gene/trait)-typesof(/datum/plant_gene/trait/plant_type))) + var/datum/plant_gene/trait/T = new random_trait + if(T.can_add(src)) + genes += T + else + qdel(T) + +/obj/item/seeds/proc/add_random_plant_type(normal_plant_chance = 75) + if(prob(normal_plant_chance)) + var/random_plant_type = pick(subtypesof(/datum/plant_gene/trait/plant_type)) + var/datum/plant_gene/trait/plant_type/P = new random_plant_type + if(P.can_add(src)) + genes += P + else + qdel(P) + +/obj/item/seeds/proc/remove_random_reagents(lower = 0, upper = 2) + var/amount_random_reagents = rand(lower, upper) + for(var/i in 1 to amount_random_reagents) + var/datum/reagent/chemical = pick(reagents_add) + qdel(chemical) + +/obj/item/seeds/proc/remove_random_traits(lower = 0, upper = 2) + var/list/genepool = list() + var/amount_random_traits = rand(lower, upper) + for(var/datum/plant_gene/trait in genes) + genepool += trait + + for(var/i in 1 to amount_random_traits) + var/datum/plant_gene/planted_gene = pick(genepool) + qdel(planted_gene) diff --git a/code/modules/jobs/access.dm b/code/modules/jobs/access.dm index 59681a8d3b59..62014fdb78bc 100644 --- a/code/modules/jobs/access.dm +++ b/code/modules/jobs/access.dm @@ -1,389 +1,389 @@ - -//returns TRUE if this mob has sufficient access to use this object -/obj/proc/allowed(mob/M) - //check if it doesn't require any access at all - if(src.check_access(null)) - return TRUE - if(issilicon(M)) - if(ispAI(M)) - return FALSE - return TRUE //AI can do whatever it wants - if(IsAdminGhost(M)) - //Access can't stop the abuse - return TRUE - else if(istype(M) && SEND_SIGNAL(M, COMSIG_MOB_ALLOWED, src)) - return TRUE - else 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()) || src.check_access(H.wear_id)) - return TRUE - else if(ismonkey(M) || isalienadult(M)) - var/mob/living/carbon/george = M - //they can only hold things :( - if(check_access(george.get_active_held_item())) - return TRUE - else if(isanimal(M)) - var/mob/living/simple_animal/A = M - if(check_access(A.get_active_held_item()) || check_access(A.access_card)) - return TRUE - return FALSE - -/obj/item/proc/GetAccess() - return list() - -/obj/item/proc/GetID() - return null - -/obj/item/proc/RemoveID() - return null - -/obj/item/proc/InsertID() - return FALSE - -/obj/proc/text2access(access_text) - . = list() - if(!access_text) - return - var/list/split = splittext(access_text,";") - for(var/x in split) - var/n = text2num(x) - if(n) - . += n - -//Call this before using req_access or req_one_access directly -/obj/proc/gen_access() - //These generations have been moved out of /obj/New() because they were slowing down the creation of objects that never even used the access system. - if(!req_access) - req_access = list() - for(var/a in text2access(req_access_txt)) - req_access += a - if(!req_one_access) - req_one_access = list() - for(var/b in text2access(req_one_access_txt)) - req_one_access += b - -// Check if an item has access to this object -/obj/proc/check_access(obj/item/I) - return check_access_list(I ? I.GetAccess() : null) - -/obj/proc/check_access_list(list/access_list) - gen_access() - - if(!islist(req_access)) //something's very wrong - return TRUE - - if(!req_access.len && !length(req_one_access)) - return TRUE - - if(!length(access_list) || !islist(access_list)) - return FALSE - - for(var/req in req_access) - if(!(req in access_list)) //doesn't have this access - return FALSE - - if(length(req_one_access)) - for(var/req in req_one_access) - if(req in access_list) //has an access from the single access list - return TRUE - return FALSE - return TRUE - -/obj/proc/check_access_ntnet(datum/netdata/data) - return check_access_list(data.passkey) - -/proc/get_centcom_access(job) - switch(job) - if("VIP Guest") - return list(ACCESS_CENT_GENERAL) - if("Custodian") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING, ACCESS_CENT_STORAGE) - if("Thunderdome Overseer") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_THUNDER) - if("CentCom Official") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING) - if("CentCom Intern") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING) - if("CentCom Head Intern") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING) - if("Medical Officer") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING, ACCESS_CENT_MEDICAL) - if("Death Commando") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_SPECOPS, ACCESS_CENT_LIVING, ACCESS_CENT_STORAGE) - if("Research Officer") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_SPECOPS, ACCESS_CENT_MEDICAL, ACCESS_CENT_TELEPORTER, ACCESS_CENT_STORAGE) - if("Special Ops Officer") - return get_all_centcom_access() - if("Admiral") - return get_all_centcom_access() - if("CentCom Commander") - return get_all_centcom_access() - if("Emergency Response Team Commander") - return get_ert_access("commander") - if("Security Response Officer") - return get_ert_access("sec") - if("Engineer Response Officer") - return get_ert_access("eng") - if("Medical Response Officer") - return get_ert_access("med") - if("CentCom Bartender") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING, ACCESS_CENT_BAR) - -/proc/get_all_accesses() - return list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_FORENSICS_LOCKERS, ACCESS_COURT, - ACCESS_MEDICAL, ACCESS_GENETICS, ACCESS_MORGUE, ACCESS_RD, - ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_CHEMISTRY, ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_MAINT_TUNNELS, - ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CHANGE_IDS, ACCESS_AI_UPLOAD, - ACCESS_TELEPORTER, ACCESS_EVA, ACCESS_HEADS, ACCESS_CAPTAIN, ACCESS_ALL_PERSONAL_LOCKERS, - ACCESS_TECH_STORAGE, ACCESS_CHAPEL_OFFICE, ACCESS_ATMOSPHERICS, ACCESS_KITCHEN, - ACCESS_BAR, ACCESS_JANITOR, ACCESS_CREMATORIUM, ACCESS_ROBOTICS, ACCESS_CARGO, ACCESS_CONSTRUCTION, - ACCESS_HYDROPONICS, ACCESS_LIBRARY, ACCESS_LAWYER, ACCESS_VIROLOGY, ACCESS_CMO, ACCESS_QM, ACCESS_SURGERY, ACCESS_PSYCHOLOGY, - ACCESS_THEATRE, ACCESS_RESEARCH, ACCESS_MINING, ACCESS_MAILSORTING, ACCESS_WEAPONS, - ACCESS_MECH_MINING, ACCESS_MECH_ENGINE, ACCESS_MECH_SCIENCE, ACCESS_MECH_SECURITY, ACCESS_MECH_MEDICAL, - ACCESS_VAULT, ACCESS_MINING_STATION, ACCESS_XENOBIOLOGY, ACCESS_CE, ACCESS_HOP, ACCESS_HOS, ACCESS_PHARMACY, ACCESS_RC_ANNOUNCE, - ACCESS_KEYCARD_AUTH, ACCESS_TCOMSAT, ACCESS_GATEWAY, ACCESS_MINERAL_STOREROOM, ACCESS_MINISAT, ACCESS_NETWORK, ACCESS_CLONING, ACCESS_LIEUTENANT) //WaspStation Edit - Lieutenant - -/proc/get_all_centcom_access() - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_THUNDER, ACCESS_CENT_SPECOPS, ACCESS_CENT_MEDICAL, ACCESS_CENT_LIVING, ACCESS_CENT_STORAGE, ACCESS_CENT_TELEPORTER, ACCESS_CENT_CAPTAIN) - -/proc/get_ert_access(class) - switch(class) - if("commander") - return get_all_centcom_access() - if("sec") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_SPECOPS, ACCESS_CENT_LIVING) - if("eng") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_SPECOPS, ACCESS_CENT_LIVING, ACCESS_CENT_STORAGE) - if("med") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_SPECOPS, ACCESS_CENT_MEDICAL, ACCESS_CENT_LIVING) - -/proc/get_all_syndicate_access() - return list(ACCESS_SYNDICATE, ACCESS_SYNDICATE_LEADER) - -/proc/get_region_accesses(code) - switch(code) - if(0) - return get_all_accesses() - if(1) //station general - return list(ACCESS_KITCHEN,ACCESS_BAR, ACCESS_HYDROPONICS, ACCESS_JANITOR, ACCESS_CHAPEL_OFFICE, ACCESS_CREMATORIUM, ACCESS_LIBRARY, ACCESS_THEATRE, ACCESS_LAWYER) - if(2) //security - return list(ACCESS_SEC_DOORS, ACCESS_WEAPONS, ACCESS_SECURITY, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_FORENSICS_LOCKERS, ACCESS_COURT, ACCESS_MECH_SECURITY, ACCESS_HOS) - if(3) //medbay - return list(ACCESS_MEDICAL, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_MORGUE, ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_SURGERY, ACCESS_MECH_MEDICAL, ACCESS_CMO, ACCESS_PHARMACY, ACCESS_PSYCHOLOGY) //WaspStation Edit - Gen/Sci Split - if(4) //research - return list(ACCESS_RESEARCH, ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_GENETICS, ACCESS_ROBOTICS, ACCESS_XENOBIOLOGY, ACCESS_MECH_SCIENCE, ACCESS_MINISAT, ACCESS_RD, ACCESS_NETWORK) - if(5) //engineering and maintenance - return list(ACCESS_CONSTRUCTION, ACCESS_MAINT_TUNNELS, ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_TECH_STORAGE, ACCESS_ATMOSPHERICS, ACCESS_MECH_ENGINE, ACCESS_TCOMSAT, ACCESS_MINISAT, ACCESS_CE) - if(6) //supply - return list(ACCESS_MAILSORTING, ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_MECH_MINING, ACCESS_MINERAL_STOREROOM, ACCESS_CARGO, ACCESS_QM, ACCESS_VAULT) - if(7) //command - return list(ACCESS_HEADS, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_CHANGE_IDS, ACCESS_AI_UPLOAD, ACCESS_TELEPORTER, ACCESS_EVA, ACCESS_GATEWAY, ACCESS_ALL_PERSONAL_LOCKERS, ACCESS_HOP, ACCESS_CAPTAIN, ACCESS_VAULT, ACCESS_LIEUTENANT) - -/proc/get_region_accesses_name(code) - switch(code) - if(0) - return "All" - if(1) //station general - return "General" - if(2) //security - return "Security" - if(3) //medbay - return "Medbay" - if(4) //research - return "Research" - if(5) //engineering and maintenance - return "Engineering" - if(6) //supply - return "Supply" - if(7) //command - return "Command" - -/proc/get_access_desc(A) - switch(A) - if(ACCESS_CARGO) - return "Cargo Bay" - if(ACCESS_SECURITY) - return "Security" - if(ACCESS_BRIG) - return "Holding Cells" - if(ACCESS_COURT) - return "Courtroom" - if(ACCESS_FORENSICS_LOCKERS) - return "Forensics" - if(ACCESS_MEDICAL) - return "Medical" - if(ACCESS_GENETICS) - return "Genetics Lab" - if(ACCESS_MORGUE) - return "Morgue" - if(ACCESS_TOX) - return "R&D Lab" - if(ACCESS_TOX_STORAGE) - return "Toxins Lab" - if(ACCESS_CHEMISTRY) - return "Chemistry Lab" - if(ACCESS_RD) - return "RD Office" - if(ACCESS_BAR) - return "Bar" - if(ACCESS_JANITOR) - return "Custodial Closet" - if(ACCESS_ENGINE) - return "Engineering" - if(ACCESS_ENGINE_EQUIP) - return "Power and Engineering Equipment" - if(ACCESS_MAINT_TUNNELS) - return "Maintenance" - if(ACCESS_EXTERNAL_AIRLOCKS) - return "External Airlocks" - if(ACCESS_CHANGE_IDS) - return "ID Console" - if(ACCESS_AI_UPLOAD) - return "AI Chambers" - if(ACCESS_TELEPORTER) - return "Teleporter" - if(ACCESS_EVA) - return "EVA" - if(ACCESS_HEADS) - return "Bridge" - if(ACCESS_CAPTAIN) - return "Captain" - if(ACCESS_ALL_PERSONAL_LOCKERS) - return "Personal Lockers" - if(ACCESS_CHAPEL_OFFICE) - return "Chapel Office" - if(ACCESS_TECH_STORAGE) - return "Technical Storage" - if(ACCESS_ATMOSPHERICS) - return "Atmospherics" - if(ACCESS_CREMATORIUM) - return "Crematorium" - if(ACCESS_ARMORY) - return "Armory" - if(ACCESS_CONSTRUCTION) - return "Construction" - if(ACCESS_KITCHEN) - return "Kitchen" - if(ACCESS_HYDROPONICS) - return "Hydroponics" - if(ACCESS_LIBRARY) - return "Library" - if(ACCESS_LAWYER) - return "Law Office" - if(ACCESS_ROBOTICS) - return "Robotics" - if(ACCESS_VIROLOGY) - return "Virology" - if(ACCESS_PSYCHOLOGY) - return "Psychology" - if(ACCESS_CMO) - return "CMO Office" - if(ACCESS_QM) - return "Quartermaster" - if(ACCESS_SURGERY) - return "Surgery" - if(ACCESS_THEATRE) - return "Theatre" - if(ACCESS_RESEARCH) - return "Science" - if(ACCESS_MINING) - return "Mining" - if(ACCESS_MAILSORTING) - return "Cargo Office" - if(ACCESS_VAULT) - return "Main Vault" - if(ACCESS_MINING_STATION) - return "Mining EVA" - if(ACCESS_XENOBIOLOGY) - return "Xenobiology Lab" - if(ACCESS_HOP) - return "HOP Office" - if(ACCESS_HOS) - return "HoS Office" - if(ACCESS_CE) - return "CE Office" - if(ACCESS_PHARMACY) - return "Pharmacy" - if(ACCESS_RC_ANNOUNCE) - return "RC Announcements" - if(ACCESS_KEYCARD_AUTH) - return "Keycode Auth." - if(ACCESS_TCOMSAT) - return "Telecommunications" - if(ACCESS_GATEWAY) - return "Gateway" - if(ACCESS_SEC_DOORS) - return "Brig" - if(ACCESS_MINERAL_STOREROOM) - return "Mineral Storage" - if(ACCESS_MINISAT) - return "AI Satellite" - if(ACCESS_WEAPONS) - return "Weapon Permit" - if(ACCESS_NETWORK) - return "Network Access" - if(ACCESS_MECH_MINING) - return "Mining Mech Access" - if(ACCESS_MECH_MEDICAL) - return "Medical Mech Access" - if(ACCESS_MECH_SECURITY) - return "Security Mech Access" - if(ACCESS_MECH_SCIENCE) - return "Science Mech Access" - if(ACCESS_MECH_ENGINE) - return "Engineering Mech Access" - -//WaspStation Begin - if(ACCESS_CLONING) - return "Cloning Room" - if(ACCESS_LIEUTENANT) - return "Lieutenant" -//WaspStation End - -/proc/get_centcom_access_desc(A) - switch(A) - if(ACCESS_CENT_GENERAL) - return "Code Grey" - if(ACCESS_CENT_THUNDER) - return "Code Yellow" - if(ACCESS_CENT_STORAGE) - return "Code Orange" - if(ACCESS_CENT_LIVING) - return "Code Green" - if(ACCESS_CENT_MEDICAL) - return "Code White" - if(ACCESS_CENT_TELEPORTER) - return "Code Blue" - if(ACCESS_CENT_SPECOPS) - return "Code Black" - if(ACCESS_CENT_CAPTAIN) - return "Code Gold" - if(ACCESS_CENT_BAR) - return "Code Scotch" - -/proc/get_all_jobs() - return list("Assistant", "Captain", "Head of Personnel", "Bartender", "Cook", "Botanist", "Quartermaster", "Cargo Technician", - "Shaft Miner", "Clown", "Mime", "Janitor", "Curator", "Lawyer", "Chaplain", "Chief Engineer", "Station Engineer", - "Atmospheric Technician", "Chief Medical Officer", "Medical Doctor", "Chemist", "Geneticist", "Virologist", "Paramedic", "Prisoner", "Psychologist", //WaspStation Edit - Brig Phys / Lieutenant - "Research Director", "Scientist", "Roboticist", "Head of Security", "Warden", "Detective", "Security Officer", "Brig Physician", "Lieutenant") //WaspStation Edit - Brig Phys / Lieutenant - -/proc/get_all_job_icons() //For all existing HUD icons - return get_all_jobs() + list("Emergency Response Team Commander", "Security Response Officer", "Engineering Response Officer", "Medical Response Officer", "Entertainment Response Officer", "Religious Response Officer", "Janitorial Response Officer", "Death Commando") - -/proc/get_all_centcom_jobs() - return list("Central Command","VIP Guest","Custodian","Thunderdome Overseer","CentCom Official","Medical Officer","Research Officer","Special Ops Officer","Admiral","CentCom Commander","CentCom Bartender","Private Security Force") - -/obj/item/proc/GetJobName() //Used in secHUD icon generation - var/obj/item/card/id/I = GetID() - if(!I) - return - var/jobName = I.assignment - if(jobName in get_all_job_icons()) //Check if the job has a hud icon - return jobName - if(jobName in get_all_centcom_jobs()) //Return with the NT logo if it is a CentCom job - return "CentCom" - for(var/datum/job/J in SSjob.occupations) - if((jobName in J.alt_titles) || (jobName == J.senior_title)) - return J.title - return "Unknown" //Return unknown if none of the above apply + +//returns TRUE if this mob has sufficient access to use this object +/obj/proc/allowed(mob/M) + //check if it doesn't require any access at all + if(src.check_access(null)) + return TRUE + if(issilicon(M)) + if(ispAI(M)) + return FALSE + return TRUE //AI can do whatever it wants + if(IsAdminGhost(M)) + //Access can't stop the abuse + return TRUE + else if(istype(M) && SEND_SIGNAL(M, COMSIG_MOB_ALLOWED, src)) + return TRUE + else 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()) || src.check_access(H.wear_id)) + return TRUE + else if(ismonkey(M) || isalienadult(M)) + var/mob/living/carbon/george = M + //they can only hold things :( + if(check_access(george.get_active_held_item())) + return TRUE + else if(isanimal(M)) + var/mob/living/simple_animal/A = M + if(check_access(A.get_active_held_item()) || check_access(A.access_card)) + return TRUE + return FALSE + +/obj/item/proc/GetAccess() + return list() + +/obj/item/proc/GetID() + return null + +/obj/item/proc/RemoveID() + return null + +/obj/item/proc/InsertID() + return FALSE + +/obj/proc/text2access(access_text) + . = list() + if(!access_text) + return + var/list/split = splittext(access_text,";") + for(var/x in split) + var/n = text2num(x) + if(n) + . += n + +//Call this before using req_access or req_one_access directly +/obj/proc/gen_access() + //These generations have been moved out of /obj/New() because they were slowing down the creation of objects that never even used the access system. + if(!req_access) + req_access = list() + for(var/a in text2access(req_access_txt)) + req_access += a + if(!req_one_access) + req_one_access = list() + for(var/b in text2access(req_one_access_txt)) + req_one_access += b + +// Check if an item has access to this object +/obj/proc/check_access(obj/item/I) + return check_access_list(I ? I.GetAccess() : null) + +/obj/proc/check_access_list(list/access_list) + gen_access() + + if(!islist(req_access)) //something's very wrong + return TRUE + + if(!req_access.len && !length(req_one_access)) + return TRUE + + if(!length(access_list) || !islist(access_list)) + return FALSE + + for(var/req in req_access) + if(!(req in access_list)) //doesn't have this access + return FALSE + + if(length(req_one_access)) + for(var/req in req_one_access) + if(req in access_list) //has an access from the single access list + return TRUE + return FALSE + return TRUE + +/obj/proc/check_access_ntnet(datum/netdata/data) + return check_access_list(data.passkey) + +/proc/get_centcom_access(job) + switch(job) + if("VIP Guest") + return list(ACCESS_CENT_GENERAL) + if("Custodian") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING, ACCESS_CENT_STORAGE) + if("Thunderdome Overseer") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_THUNDER) + if("CentCom Official") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING) + if("CentCom Intern") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING) + if("CentCom Head Intern") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING) + if("Medical Officer") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING, ACCESS_CENT_MEDICAL) + if("Death Commando") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_SPECOPS, ACCESS_CENT_LIVING, ACCESS_CENT_STORAGE) + if("Research Officer") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_SPECOPS, ACCESS_CENT_MEDICAL, ACCESS_CENT_TELEPORTER, ACCESS_CENT_STORAGE) + if("Special Ops Officer") + return get_all_centcom_access() + if("Admiral") + return get_all_centcom_access() + if("CentCom Commander") + return get_all_centcom_access() + if("Emergency Response Team Commander") + return get_ert_access("commander") + if("Security Response Officer") + return get_ert_access("sec") + if("Engineer Response Officer") + return get_ert_access("eng") + if("Medical Response Officer") + return get_ert_access("med") + if("CentCom Bartender") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING, ACCESS_CENT_BAR) + +/proc/get_all_accesses() + return list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_FORENSICS_LOCKERS, ACCESS_COURT, + ACCESS_MEDICAL, ACCESS_GENETICS, ACCESS_MORGUE, ACCESS_RD, + ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_CHEMISTRY, ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_MAINT_TUNNELS, + ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CHANGE_IDS, ACCESS_AI_UPLOAD, + ACCESS_TELEPORTER, ACCESS_EVA, ACCESS_HEADS, ACCESS_CAPTAIN, ACCESS_ALL_PERSONAL_LOCKERS, + ACCESS_TECH_STORAGE, ACCESS_CHAPEL_OFFICE, ACCESS_ATMOSPHERICS, ACCESS_KITCHEN, + ACCESS_BAR, ACCESS_JANITOR, ACCESS_CREMATORIUM, ACCESS_ROBOTICS, ACCESS_CARGO, ACCESS_CONSTRUCTION, + ACCESS_HYDROPONICS, ACCESS_LIBRARY, ACCESS_LAWYER, ACCESS_VIROLOGY, ACCESS_CMO, ACCESS_QM, ACCESS_SURGERY, ACCESS_PSYCHOLOGY, + ACCESS_THEATRE, ACCESS_RESEARCH, ACCESS_MINING, ACCESS_MAILSORTING, ACCESS_WEAPONS, + ACCESS_MECH_MINING, ACCESS_MECH_ENGINE, ACCESS_MECH_SCIENCE, ACCESS_MECH_SECURITY, ACCESS_MECH_MEDICAL, + ACCESS_VAULT, ACCESS_MINING_STATION, ACCESS_XENOBIOLOGY, ACCESS_CE, ACCESS_HOP, ACCESS_HOS, ACCESS_PHARMACY, ACCESS_RC_ANNOUNCE, + ACCESS_KEYCARD_AUTH, ACCESS_TCOMSAT, ACCESS_GATEWAY, ACCESS_MINERAL_STOREROOM, ACCESS_MINISAT, ACCESS_NETWORK, ACCESS_CLONING, ACCESS_LIEUTENANT) //WaspStation Edit - Lieutenant + +/proc/get_all_centcom_access() + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_THUNDER, ACCESS_CENT_SPECOPS, ACCESS_CENT_MEDICAL, ACCESS_CENT_LIVING, ACCESS_CENT_STORAGE, ACCESS_CENT_TELEPORTER, ACCESS_CENT_CAPTAIN) + +/proc/get_ert_access(class) + switch(class) + if("commander") + return get_all_centcom_access() + if("sec") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_SPECOPS, ACCESS_CENT_LIVING) + if("eng") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_SPECOPS, ACCESS_CENT_LIVING, ACCESS_CENT_STORAGE) + if("med") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_SPECOPS, ACCESS_CENT_MEDICAL, ACCESS_CENT_LIVING) + +/proc/get_all_syndicate_access() + return list(ACCESS_SYNDICATE, ACCESS_SYNDICATE_LEADER) + +/proc/get_region_accesses(code) + switch(code) + if(0) + return get_all_accesses() + if(1) //station general + return list(ACCESS_KITCHEN,ACCESS_BAR, ACCESS_HYDROPONICS, ACCESS_JANITOR, ACCESS_CHAPEL_OFFICE, ACCESS_CREMATORIUM, ACCESS_LIBRARY, ACCESS_THEATRE, ACCESS_LAWYER) + if(2) //security + return list(ACCESS_SEC_DOORS, ACCESS_WEAPONS, ACCESS_SECURITY, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_FORENSICS_LOCKERS, ACCESS_COURT, ACCESS_MECH_SECURITY, ACCESS_HOS) + if(3) //medbay + return list(ACCESS_MEDICAL, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_MORGUE, ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_SURGERY, ACCESS_MECH_MEDICAL, ACCESS_CMO, ACCESS_PHARMACY, ACCESS_PSYCHOLOGY) //WaspStation Edit - Gen/Sci Split + if(4) //research + return list(ACCESS_RESEARCH, ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_GENETICS, ACCESS_ROBOTICS, ACCESS_XENOBIOLOGY, ACCESS_MECH_SCIENCE, ACCESS_MINISAT, ACCESS_RD, ACCESS_NETWORK) + if(5) //engineering and maintenance + return list(ACCESS_CONSTRUCTION, ACCESS_MAINT_TUNNELS, ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_TECH_STORAGE, ACCESS_ATMOSPHERICS, ACCESS_MECH_ENGINE, ACCESS_TCOMSAT, ACCESS_MINISAT, ACCESS_CE) + if(6) //supply + return list(ACCESS_MAILSORTING, ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_MECH_MINING, ACCESS_MINERAL_STOREROOM, ACCESS_CARGO, ACCESS_QM, ACCESS_VAULT) + if(7) //command + return list(ACCESS_HEADS, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_CHANGE_IDS, ACCESS_AI_UPLOAD, ACCESS_TELEPORTER, ACCESS_EVA, ACCESS_GATEWAY, ACCESS_ALL_PERSONAL_LOCKERS, ACCESS_HOP, ACCESS_CAPTAIN, ACCESS_VAULT, ACCESS_LIEUTENANT) + +/proc/get_region_accesses_name(code) + switch(code) + if(0) + return "All" + if(1) //station general + return "General" + if(2) //security + return "Security" + if(3) //medbay + return "Medbay" + if(4) //research + return "Research" + if(5) //engineering and maintenance + return "Engineering" + if(6) //supply + return "Supply" + if(7) //command + return "Command" + +/proc/get_access_desc(A) + switch(A) + if(ACCESS_CARGO) + return "Cargo Bay" + if(ACCESS_SECURITY) + return "Security" + if(ACCESS_BRIG) + return "Holding Cells" + if(ACCESS_COURT) + return "Courtroom" + if(ACCESS_FORENSICS_LOCKERS) + return "Forensics" + if(ACCESS_MEDICAL) + return "Medical" + if(ACCESS_GENETICS) + return "Genetics Lab" + if(ACCESS_MORGUE) + return "Morgue" + if(ACCESS_TOX) + return "R&D Lab" + if(ACCESS_TOX_STORAGE) + return "Toxins Lab" + if(ACCESS_CHEMISTRY) + return "Chemistry Lab" + if(ACCESS_RD) + return "RD Office" + if(ACCESS_BAR) + return "Bar" + if(ACCESS_JANITOR) + return "Custodial Closet" + if(ACCESS_ENGINE) + return "Engineering" + if(ACCESS_ENGINE_EQUIP) + return "Power and Engineering Equipment" + if(ACCESS_MAINT_TUNNELS) + return "Maintenance" + if(ACCESS_EXTERNAL_AIRLOCKS) + return "External Airlocks" + if(ACCESS_CHANGE_IDS) + return "ID Console" + if(ACCESS_AI_UPLOAD) + return "AI Chambers" + if(ACCESS_TELEPORTER) + return "Teleporter" + if(ACCESS_EVA) + return "EVA" + if(ACCESS_HEADS) + return "Bridge" + if(ACCESS_CAPTAIN) + return "Captain" + if(ACCESS_ALL_PERSONAL_LOCKERS) + return "Personal Lockers" + if(ACCESS_CHAPEL_OFFICE) + return "Chapel Office" + if(ACCESS_TECH_STORAGE) + return "Technical Storage" + if(ACCESS_ATMOSPHERICS) + return "Atmospherics" + if(ACCESS_CREMATORIUM) + return "Crematorium" + if(ACCESS_ARMORY) + return "Armory" + if(ACCESS_CONSTRUCTION) + return "Construction" + if(ACCESS_KITCHEN) + return "Kitchen" + if(ACCESS_HYDROPONICS) + return "Hydroponics" + if(ACCESS_LIBRARY) + return "Library" + if(ACCESS_LAWYER) + return "Law Office" + if(ACCESS_ROBOTICS) + return "Robotics" + if(ACCESS_VIROLOGY) + return "Virology" + if(ACCESS_PSYCHOLOGY) + return "Psychology" + if(ACCESS_CMO) + return "CMO Office" + if(ACCESS_QM) + return "Quartermaster" + if(ACCESS_SURGERY) + return "Surgery" + if(ACCESS_THEATRE) + return "Theatre" + if(ACCESS_RESEARCH) + return "Science" + if(ACCESS_MINING) + return "Mining" + if(ACCESS_MAILSORTING) + return "Cargo Office" + if(ACCESS_VAULT) + return "Main Vault" + if(ACCESS_MINING_STATION) + return "Mining EVA" + if(ACCESS_XENOBIOLOGY) + return "Xenobiology Lab" + if(ACCESS_HOP) + return "HOP Office" + if(ACCESS_HOS) + return "HoS Office" + if(ACCESS_CE) + return "CE Office" + if(ACCESS_PHARMACY) + return "Pharmacy" + if(ACCESS_RC_ANNOUNCE) + return "RC Announcements" + if(ACCESS_KEYCARD_AUTH) + return "Keycode Auth." + if(ACCESS_TCOMSAT) + return "Telecommunications" + if(ACCESS_GATEWAY) + return "Gateway" + if(ACCESS_SEC_DOORS) + return "Brig" + if(ACCESS_MINERAL_STOREROOM) + return "Mineral Storage" + if(ACCESS_MINISAT) + return "AI Satellite" + if(ACCESS_WEAPONS) + return "Weapon Permit" + if(ACCESS_NETWORK) + return "Network Access" + if(ACCESS_MECH_MINING) + return "Mining Mech Access" + if(ACCESS_MECH_MEDICAL) + return "Medical Mech Access" + if(ACCESS_MECH_SECURITY) + return "Security Mech Access" + if(ACCESS_MECH_SCIENCE) + return "Science Mech Access" + if(ACCESS_MECH_ENGINE) + return "Engineering Mech Access" + +//WaspStation Begin + if(ACCESS_CLONING) + return "Cloning Room" + if(ACCESS_LIEUTENANT) + return "Lieutenant" +//WaspStation End + +/proc/get_centcom_access_desc(A) + switch(A) + if(ACCESS_CENT_GENERAL) + return "Code Grey" + if(ACCESS_CENT_THUNDER) + return "Code Yellow" + if(ACCESS_CENT_STORAGE) + return "Code Orange" + if(ACCESS_CENT_LIVING) + return "Code Green" + if(ACCESS_CENT_MEDICAL) + return "Code White" + if(ACCESS_CENT_TELEPORTER) + return "Code Blue" + if(ACCESS_CENT_SPECOPS) + return "Code Black" + if(ACCESS_CENT_CAPTAIN) + return "Code Gold" + if(ACCESS_CENT_BAR) + return "Code Scotch" + +/proc/get_all_jobs() + return list("Assistant", "Captain", "Head of Personnel", "Bartender", "Cook", "Botanist", "Quartermaster", "Cargo Technician", + "Shaft Miner", "Clown", "Mime", "Janitor", "Curator", "Lawyer", "Chaplain", "Chief Engineer", "Station Engineer", + "Atmospheric Technician", "Chief Medical Officer", "Medical Doctor", "Chemist", "Geneticist", "Virologist", "Paramedic", "Prisoner", "Psychologist", //WaspStation Edit - Brig Phys / Lieutenant + "Research Director", "Scientist", "Roboticist", "Head of Security", "Warden", "Detective", "Security Officer", "Brig Physician", "Lieutenant") //WaspStation Edit - Brig Phys / Lieutenant + +/proc/get_all_job_icons() //For all existing HUD icons + return get_all_jobs() + list("Emergency Response Team Commander", "Security Response Officer", "Engineering Response Officer", "Medical Response Officer", "Entertainment Response Officer", "Religious Response Officer", "Janitorial Response Officer", "Death Commando") + +/proc/get_all_centcom_jobs() + return list("Central Command","VIP Guest","Custodian","Thunderdome Overseer","CentCom Official","Medical Officer","Research Officer","Special Ops Officer","Admiral","CentCom Commander","CentCom Bartender","Private Security Force") + +/obj/item/proc/GetJobName() //Used in secHUD icon generation + var/obj/item/card/id/I = GetID() + if(!I) + return + var/jobName = I.assignment + if(jobName in get_all_job_icons()) //Check if the job has a hud icon + return jobName + if(jobName in get_all_centcom_jobs()) //Return with the NT logo if it is a CentCom job + return "CentCom" + for(var/datum/job/J in SSjob.occupations) + if((jobName in J.alt_titles) || (jobName == J.senior_title)) + return J.title + return "Unknown" //Return unknown if none of the above apply diff --git a/code/modules/jobs/job_exp.dm b/code/modules/jobs/job_exp.dm index 193bafdd652e..a610d7d85567 100644 --- a/code/modules/jobs/job_exp.dm +++ b/code/modules/jobs/job_exp.dm @@ -148,7 +148,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 +156,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/DBQuery/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 +191,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/DBQuery/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 +262,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 +274,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/DBQuery/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_types/_job.dm b/code/modules/jobs/job_types/_job.dm index 14f9520bfb25..2cacb13e8b64 100644 --- a/code/modules/jobs/job_types/_job.dm +++ b/code/modules/jobs/job_types/_job.dm @@ -123,7 +123,7 @@ else M.client.prefs.equipped_gear -= gear - if(gear_leftovers.len) + if(gear_leftovers?.len) for(var/datum/gear/G in gear_leftovers) var/metadata = M.client.prefs.equipped_gear[G.display_name] var/item = G.spawn_item(null, metadata) diff --git a/code/modules/jobs/job_types/ai.dm b/code/modules/jobs/job_types/ai.dm index ac65c35e9366..5253409ca8f7 100644 --- a/code/modules/jobs/job_types/ai.dm +++ b/code/modules/jobs/job_types/ai.dm @@ -1,8 +1,6 @@ /datum/job/ai title = "AI" - flag = AI_JF auto_deadmin_role_flags = DEADMIN_POSITION_SILICON - department_flag = ENGSEC faction = "Station" total_positions = 1 spawn_positions = 1 diff --git a/code/modules/jobs/job_types/assistant.dm b/code/modules/jobs/job_types/assistant.dm index ea015d03bff8..a2ee7f492ddb 100644 --- a/code/modules/jobs/job_types/assistant.dm +++ b/code/modules/jobs/job_types/assistant.dm @@ -1,69 +1,67 @@ -/* -Assistant -*/ -/datum/job/assistant - title = "Assistant" - flag = ASSISTANT - department_flag = CIVILIAN - faction = "Station" - total_positions = 5 - spawn_positions = 5 - supervisors = "absolutely everyone" - selection_color = "#dddddd" - access = list() //See /datum/job/assistant/get_access() - minimal_access = list() //See /datum/job/assistant/get_access() - outfit = /datum/outfit/job/assistant - antag_rep = 7 - paycheck = PAYCHECK_ASSISTANT // Get a job. Job reassignment changes your paycheck now. Get over it. - paycheck_department = ACCOUNT_CIV - display_order = JOB_DISPLAY_ORDER_ASSISTANT - wiki_page = "Assistant" //WaspStation Edit - Wikilinks/Warning - -/datum/job/assistant/get_access() - if(CONFIG_GET(flag/assistants_have_maint_access) || !CONFIG_GET(flag/jobs_have_minimal_access)) //Config has assistant maint access set - . = ..() - . |= list(ACCESS_MAINT_TUNNELS) - else - return ..() - -/datum/outfit/job/assistant - name = "Assistant" - jobtype = /datum/job/assistant - -/datum/outfit/job/assistant/pre_equip(mob/living/carbon/human/H) - ..() - if(uniform != /obj/item/clothing/under/color/grey) - return - if (CONFIG_GET(flag/grey_assistants)) - if(H.jumpsuit_style == PREF_SUIT) - uniform = /obj/item/clothing/under/color/grey - else if(H.jumpsuit_style == PREF_ALTSUIT) - uniform = /obj/item/clothing/under/misc/assistantformal - else - uniform = /obj/item/clothing/under/color/jumpskirt/grey - else - if(H.jumpsuit_style == PREF_SUIT) - uniform = /obj/item/clothing/under/color/random - else if(H.jumpsuit_style == PREF_ALTSUIT) - uniform = /obj/item/clothing/under/misc/assistantformal - else - uniform = /obj/item/clothing/under/color/jumpskirt/random - -/datum/outfit/job/assistant/businessman - name = "Assistant (Businessman)" - uniform = /obj/item/clothing/under/suit/black_really - l_hand = /obj/item/storage/briefcase - -/datum/outfit/job/assistant/visitor - name = "Assistant (Visitor)" - uniform = /obj/item/clothing/under/misc/assistantformal - neck = /obj/item/camera - -/datum/outfit/job/assistant/trader - name = "Assistant (Trader)" - r_pocket = /obj/item/coin/gold - backpack_contents = list(/obj/item/export_scanner=1) - -/datum/outfit/job/assistant/entertainer - name = "Assistant (Entertainer)" - r_hand = /obj/item/bikehorn //comedy +/* +Assistant +*/ +/datum/job/assistant + title = "Assistant" + faction = "Station" + total_positions = 5 + spawn_positions = 5 + supervisors = "absolutely everyone" + selection_color = "#dddddd" + access = list() //See /datum/job/assistant/get_access() + minimal_access = list() //See /datum/job/assistant/get_access() + outfit = /datum/outfit/job/assistant + antag_rep = 7 + paycheck = PAYCHECK_ASSISTANT // Get a job. Job reassignment changes your paycheck now. Get over it. + paycheck_department = ACCOUNT_CIV + display_order = JOB_DISPLAY_ORDER_ASSISTANT + wiki_page = "Assistant" //WaspStation Edit - Wikilinks/Warning + +/datum/job/assistant/get_access() + if(CONFIG_GET(flag/assistants_have_maint_access) || !CONFIG_GET(flag/jobs_have_minimal_access)) //Config has assistant maint access set + . = ..() + . |= list(ACCESS_MAINT_TUNNELS) + else + return ..() + +/datum/outfit/job/assistant + name = "Assistant" + jobtype = /datum/job/assistant + +/datum/outfit/job/assistant/pre_equip(mob/living/carbon/human/H) + ..() + if(uniform != /obj/item/clothing/under/color/grey) + return + if (CONFIG_GET(flag/grey_assistants)) + if(H.jumpsuit_style == PREF_SUIT) + uniform = /obj/item/clothing/under/color/grey + else if(H.jumpsuit_style == PREF_ALTSUIT) + uniform = /obj/item/clothing/under/misc/assistantformal + else + uniform = /obj/item/clothing/under/color/jumpskirt/grey + else + if(H.jumpsuit_style == PREF_SUIT) + uniform = /obj/item/clothing/under/color/random + else if(H.jumpsuit_style == PREF_ALTSUIT) + uniform = /obj/item/clothing/under/misc/assistantformal + else + uniform = /obj/item/clothing/under/color/jumpskirt/random + +/datum/outfit/job/assistant/businessman + name = "Assistant (Businessman)" + uniform = /obj/item/clothing/under/suit/black_really + l_hand = /obj/item/storage/briefcase + +/datum/outfit/job/assistant/visitor + name = "Assistant (Visitor)" + uniform = /obj/item/clothing/under/misc/assistantformal + neck = /obj/item/camera + +/datum/outfit/job/assistant/trader + name = "Assistant (Trader)" + r_pocket = /obj/item/coin/gold + backpack_contents = list(/obj/item/export_scanner=1) + +/datum/outfit/job/assistant/entertainer + name = "Assistant (Entertainer)" + r_hand = /obj/item/bikehorn //comedy diff --git a/code/modules/jobs/job_types/atmospheric_technician.dm b/code/modules/jobs/job_types/atmospheric_technician.dm index 0f84f852d465..4944567ff334 100644 --- a/code/modules/jobs/job_types/atmospheric_technician.dm +++ b/code/modules/jobs/job_types/atmospheric_technician.dm @@ -1,8 +1,6 @@ /datum/job/atmos title = "Atmospheric Technician" - flag = ATMOSTECH department_head = list("Chief Engineer") - department_flag = ENGSEC faction = "Station" total_positions = 3 spawn_positions = 2 diff --git a/code/modules/jobs/job_types/bartender.dm b/code/modules/jobs/job_types/bartender.dm index 4c82b5b61ba3..ebf8fe6b8e8e 100644 --- a/code/modules/jobs/job_types/bartender.dm +++ b/code/modules/jobs/job_types/bartender.dm @@ -1,8 +1,6 @@ /datum/job/bartender title = "Bartender" - flag = BARTENDER department_head = list("Head of Personnel") - department_flag = CIVILIAN faction = "Station" total_positions = 1 spawn_positions = 1 diff --git a/code/modules/jobs/job_types/botanist.dm b/code/modules/jobs/job_types/botanist.dm index b3dce6f49141..3f23e6870088 100644 --- a/code/modules/jobs/job_types/botanist.dm +++ b/code/modules/jobs/job_types/botanist.dm @@ -1,8 +1,6 @@ /datum/job/hydro title = "Botanist" - flag = BOTANIST department_head = list("Head of Personnel") - department_flag = CIVILIAN faction = "Station" total_positions = 3 spawn_positions = 2 diff --git a/code/modules/jobs/job_types/captain.dm b/code/modules/jobs/job_types/captain.dm index 85024afc9a47..d6de93c88212 100755 --- a/code/modules/jobs/job_types/captain.dm +++ b/code/modules/jobs/job_types/captain.dm @@ -1,72 +1,70 @@ -/datum/job/captain - title = "Captain" - flag = CAPTAIN - auto_deadmin_role_flags = DEADMIN_POSITION_HEAD|DEADMIN_POSITION_SECURITY - department_head = list("CentCom") - department_flag = ENGSEC - faction = "Station" - total_positions = 1 - spawn_positions = 1 - supervisors = "Nanotrasen officials and Space law" - selection_color = "#ccccff" - req_admin_notify = 1 - minimal_player_age = 30 - exp_requirements = 180 - exp_type = EXP_TYPE_CREW - exp_type_department = EXP_TYPE_COMMAND - wiki_page = "Captain" - special_notice = "You may be the Captain of this station, but you are still beholden to The Corporation." //WaspStation Edit - Wikilinks/Warning - - outfit = /datum/outfit/job/captain - - access = list() //See get_access() - minimal_access = list() //See get_access() - paycheck = PAYCHECK_COMMAND - paycheck_department = ACCOUNT_SEC - - mind_traits = list(TRAIT_DISK_VERIFIER) - - display_order = JOB_DISPLAY_ORDER_CAPTAIN - -/datum/job/captain/get_access() - return get_all_accesses() - -/datum/job/captain/announce(mob/living/carbon/human/H) - ..() - SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, .proc/minor_announce, "Captain [H.real_name] on deck!")) - -/datum/outfit/job/captain - name = "Captain" - jobtype = /datum/job/captain - - id = /obj/item/card/id/gold - belt = /obj/item/pda/captain - glasses = /obj/item/clothing/glasses/sunglasses - ears = /obj/item/radio/headset/heads/captain/alt - gloves = /obj/item/clothing/gloves/color/captain - uniform = /obj/item/clothing/under/rank/command/captain - alt_uniform = /obj/item/clothing/under/rank/command/captain/parade //Wasp Edit - Alt Uniforms - dcoat = /obj/item/clothing/under/rank/command/captain/suit //Wasp Edit - Alt Uniforms - suit = /obj/item/clothing/suit/armor/vest/capcarapace - alt_suit = /obj/item/clothing/suit/armor/vest/capcarapace/alt - dcoat = /obj/item/clothing/suit/hooded/wintercoat/captain //Wasp Edit - Alt Uniforms - shoes = /obj/item/clothing/shoes/sneakers/brown - head = /obj/item/clothing/head/caphat - backpack_contents = list(/obj/item/melee/classic_baton/telescopic=1, /obj/item/station_charter=1) - - backpack = /obj/item/storage/backpack/captain - satchel = /obj/item/storage/backpack/satchel/cap - duffelbag = /obj/item/storage/backpack/duffelbag/captain - courierbag = /obj/item/storage/backpack/messenger/com - - implants = list(/obj/item/implant/mindshield) - accessory = /obj/item/clothing/accessory/medal/gold/captain - - chameleon_extras = list(/obj/item/gun/energy/e_gun, /obj/item/stamp/captain) - -/datum/outfit/job/captain/hardsuit - name = "Captain (Hardsuit)" - - mask = /obj/item/clothing/mask/gas/atmos/captain - suit = /obj/item/clothing/suit/space/hardsuit/swat/captain - suit_store = /obj/item/tank/internals/oxygen +/datum/job/captain + title = "Captain" + auto_deadmin_role_flags = DEADMIN_POSITION_HEAD|DEADMIN_POSITION_SECURITY + department_head = list("CentCom") + faction = "Station" + total_positions = 1 + spawn_positions = 1 + supervisors = "Nanotrasen officials and Space law" + selection_color = "#ccccff" + req_admin_notify = 1 + minimal_player_age = 30 + exp_requirements = 180 + exp_type = EXP_TYPE_CREW + exp_type_department = EXP_TYPE_COMMAND + wiki_page = "Captain" + special_notice = "You may be the Captain of this station, but you are still beholden to The Corporation." //WaspStation Edit - Wikilinks/Warning + + outfit = /datum/outfit/job/captain + + access = list() //See get_access() + minimal_access = list() //See get_access() + paycheck = PAYCHECK_COMMAND + paycheck_department = ACCOUNT_SEC + + mind_traits = list(TRAIT_DISK_VERIFIER) + + display_order = JOB_DISPLAY_ORDER_CAPTAIN + +/datum/job/captain/get_access() + return get_all_accesses() + +/datum/job/captain/announce(mob/living/carbon/human/H) + ..() + SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, .proc/minor_announce, "Captain [H.real_name] on deck!")) + +/datum/outfit/job/captain + name = "Captain" + jobtype = /datum/job/captain + + id = /obj/item/card/id/gold + belt = /obj/item/pda/captain + glasses = /obj/item/clothing/glasses/sunglasses + ears = /obj/item/radio/headset/heads/captain/alt + gloves = /obj/item/clothing/gloves/color/captain + uniform = /obj/item/clothing/under/rank/command/captain + alt_uniform = /obj/item/clothing/under/rank/command/captain/parade //Wasp Edit - Alt Uniforms + dcoat = /obj/item/clothing/under/rank/command/captain/suit //Wasp Edit - Alt Uniforms + suit = /obj/item/clothing/suit/armor/vest/capcarapace + alt_suit = /obj/item/clothing/suit/armor/vest/capcarapace/alt + dcoat = /obj/item/clothing/suit/hooded/wintercoat/captain //Wasp Edit - Alt Uniforms + shoes = /obj/item/clothing/shoes/sneakers/brown + head = /obj/item/clothing/head/caphat + backpack_contents = list(/obj/item/melee/classic_baton/telescopic=1, /obj/item/station_charter=1) + + backpack = /obj/item/storage/backpack/captain + satchel = /obj/item/storage/backpack/satchel/cap + duffelbag = /obj/item/storage/backpack/duffelbag/captain + courierbag = /obj/item/storage/backpack/messenger/com + + implants = list(/obj/item/implant/mindshield) + accessory = /obj/item/clothing/accessory/medal/gold/captain + + chameleon_extras = list(/obj/item/gun/energy/e_gun, /obj/item/stamp/captain) + +/datum/outfit/job/captain/hardsuit + name = "Captain (Hardsuit)" + + mask = /obj/item/clothing/mask/gas/atmos/captain + suit = /obj/item/clothing/suit/space/hardsuit/swat/captain + suit_store = /obj/item/tank/internals/oxygen diff --git a/code/modules/jobs/job_types/cargo_technician.dm b/code/modules/jobs/job_types/cargo_technician.dm index 33454e1f3fb6..9c1a4b89fcab 100644 --- a/code/modules/jobs/job_types/cargo_technician.dm +++ b/code/modules/jobs/job_types/cargo_technician.dm @@ -1,8 +1,6 @@ /datum/job/cargo_tech title = "Cargo Technician" - flag = CARGOTECH department_head = list("Head of Personnel") - department_flag = CIVILIAN faction = "Station" total_positions = 3 spawn_positions = 2 diff --git a/code/modules/jobs/job_types/chaplain.dm b/code/modules/jobs/job_types/chaplain.dm index d03584d9af12..63d5b3cc17d0 100644 --- a/code/modules/jobs/job_types/chaplain.dm +++ b/code/modules/jobs/job_types/chaplain.dm @@ -1,8 +1,6 @@ /datum/job/chaplain title = "Chaplain" - flag = CHAPLAIN department_head = list("Head of Personnel") - department_flag = CIVILIAN faction = "Station" total_positions = 1 spawn_positions = 1 diff --git a/code/modules/jobs/job_types/chemist.dm b/code/modules/jobs/job_types/chemist.dm index 56178dc3f179..a3644bd3baef 100644 --- a/code/modules/jobs/job_types/chemist.dm +++ b/code/modules/jobs/job_types/chemist.dm @@ -1,8 +1,6 @@ /datum/job/chemist title = "Chemist" - flag = CHEMIST department_head = list("Chief Medical Officer") - department_flag = MEDSCI faction = "Station" total_positions = 2 spawn_positions = 2 diff --git a/code/modules/jobs/job_types/chief_engineer.dm b/code/modules/jobs/job_types/chief_engineer.dm index 02c2f265ff73..4aa0e2825361 100644 --- a/code/modules/jobs/job_types/chief_engineer.dm +++ b/code/modules/jobs/job_types/chief_engineer.dm @@ -1,9 +1,7 @@ /datum/job/chief_engineer title = "Chief Engineer" - flag = CHIEF auto_deadmin_role_flags = DEADMIN_POSITION_HEAD department_head = list("Captain") - department_flag = ENGSEC head_announce = list("Engineering") faction = "Station" total_positions = 1 diff --git a/code/modules/jobs/job_types/chief_medical_officer.dm b/code/modules/jobs/job_types/chief_medical_officer.dm index 277a1e8a25c6..58f2cafbaa2f 100644 --- a/code/modules/jobs/job_types/chief_medical_officer.dm +++ b/code/modules/jobs/job_types/chief_medical_officer.dm @@ -1,8 +1,6 @@ /datum/job/cmo title = "Chief Medical Officer" - flag = CMO_JF department_head = list("Captain") - department_flag = MEDSCI auto_deadmin_role_flags = DEADMIN_POSITION_HEAD head_announce = list(RADIO_CHANNEL_MEDICAL) faction = "Station" diff --git a/code/modules/jobs/job_types/clown.dm b/code/modules/jobs/job_types/clown.dm index e4e2e2207a28..c428ea238c58 100644 --- a/code/modules/jobs/job_types/clown.dm +++ b/code/modules/jobs/job_types/clown.dm @@ -1,8 +1,6 @@ /datum/job/clown title = "Clown" - flag = CLOWN department_head = list("Head of Personnel") - department_flag = CIVILIAN faction = "Station" total_positions = 1 spawn_positions = 1 diff --git a/code/modules/jobs/job_types/cook.dm b/code/modules/jobs/job_types/cook.dm index 4767c413cdf1..7ec1c645336e 100644 --- a/code/modules/jobs/job_types/cook.dm +++ b/code/modules/jobs/job_types/cook.dm @@ -1,8 +1,6 @@ /datum/job/cook title = "Cook" - flag = COOK department_head = list("Head of Personnel") - department_flag = CIVILIAN faction = "Station" total_positions = 2 spawn_positions = 1 diff --git a/code/modules/jobs/job_types/curator.dm b/code/modules/jobs/job_types/curator.dm index f927afc64597..a24de3ca1eb3 100644 --- a/code/modules/jobs/job_types/curator.dm +++ b/code/modules/jobs/job_types/curator.dm @@ -1,8 +1,6 @@ /datum/job/curator title = "Curator" - flag = CURATOR department_head = list("Head of Personnel") - department_flag = CIVILIAN faction = "Station" total_positions = 1 spawn_positions = 1 diff --git a/code/modules/jobs/job_types/cyborg.dm b/code/modules/jobs/job_types/cyborg.dm index ff9589716f37..38c94464f84c 100644 --- a/code/modules/jobs/job_types/cyborg.dm +++ b/code/modules/jobs/job_types/cyborg.dm @@ -1,8 +1,6 @@ /datum/job/cyborg title = "Cyborg" - flag = CYBORG auto_deadmin_role_flags = DEADMIN_POSITION_SILICON - department_flag = ENGSEC faction = "Station" total_positions = 0 spawn_positions = 1 diff --git a/code/modules/jobs/job_types/detective.dm b/code/modules/jobs/job_types/detective.dm index 910b64a11a62..cd318cbed37f 100644 --- a/code/modules/jobs/job_types/detective.dm +++ b/code/modules/jobs/job_types/detective.dm @@ -1,9 +1,7 @@ /datum/job/detective title = "Detective" - flag = DETECTIVE auto_deadmin_role_flags = DEADMIN_POSITION_SECURITY department_head = list("Head of Security") - department_flag = ENGSEC faction = "Station" total_positions = 1 spawn_positions = 1 diff --git a/code/modules/jobs/job_types/geneticist.dm b/code/modules/jobs/job_types/geneticist.dm index 77edeca5d9c1..80c30a3ccf45 100644 --- a/code/modules/jobs/job_types/geneticist.dm +++ b/code/modules/jobs/job_types/geneticist.dm @@ -1,8 +1,6 @@ /datum/job/geneticist title = "Geneticist" - flag = GENETICIST department_head = list("Chief Medical Officer") // Wasp Edit - More Gen/Sci Split - department_flag = MEDSCI faction = "Station" total_positions = 2 spawn_positions = 2 diff --git a/code/modules/jobs/job_types/head_of_personnel.dm b/code/modules/jobs/job_types/head_of_personnel.dm index ad97f52a36d1..5f9631d66de4 100644 --- a/code/modules/jobs/job_types/head_of_personnel.dm +++ b/code/modules/jobs/job_types/head_of_personnel.dm @@ -1,9 +1,7 @@ /datum/job/head_of_personnel title = "Head of Personnel" - flag = HOP auto_deadmin_role_flags = DEADMIN_POSITION_HEAD department_head = list("Captain") - department_flag = CIVILIAN head_announce = list(RADIO_CHANNEL_SUPPLY, RADIO_CHANNEL_SERVICE) faction = "Station" total_positions = 1 diff --git a/code/modules/jobs/job_types/head_of_security.dm b/code/modules/jobs/job_types/head_of_security.dm index a9b8f9f692dc..a4bee896a58b 100644 --- a/code/modules/jobs/job_types/head_of_security.dm +++ b/code/modules/jobs/job_types/head_of_security.dm @@ -1,9 +1,7 @@ /datum/job/hos title = "Head of Security" - flag = HOS auto_deadmin_role_flags = DEADMIN_POSITION_HEAD|DEADMIN_POSITION_SECURITY department_head = list("Captain") - department_flag = ENGSEC head_announce = list(RADIO_CHANNEL_SECURITY) faction = "Station" total_positions = 1 diff --git a/code/modules/jobs/job_types/janitor.dm b/code/modules/jobs/job_types/janitor.dm index ae2346f0c261..2731360e56d8 100644 --- a/code/modules/jobs/job_types/janitor.dm +++ b/code/modules/jobs/job_types/janitor.dm @@ -1,8 +1,6 @@ /datum/job/janitor title = "Janitor" - flag = JANITOR department_head = list("Head of Personnel") - department_flag = CIVILIAN faction = "Station" total_positions = 2 spawn_positions = 1 diff --git a/code/modules/jobs/job_types/lawyer.dm b/code/modules/jobs/job_types/lawyer.dm index 66ae30a14436..1eb264cd01c1 100644 --- a/code/modules/jobs/job_types/lawyer.dm +++ b/code/modules/jobs/job_types/lawyer.dm @@ -1,8 +1,6 @@ /datum/job/lawyer title = "Lawyer" - flag = LAWYER department_head = list("Head of Personnel") - department_flag = CIVILIAN faction = "Station" total_positions = 2 spawn_positions = 2 diff --git a/code/modules/jobs/job_types/medical_doctor.dm b/code/modules/jobs/job_types/medical_doctor.dm index 5fd172540f33..e063f6f558ee 100644 --- a/code/modules/jobs/job_types/medical_doctor.dm +++ b/code/modules/jobs/job_types/medical_doctor.dm @@ -1,8 +1,6 @@ /datum/job/doctor title = "Medical Doctor" - flag = DOCTOR department_head = list("Chief Medical Officer") - department_flag = MEDSCI faction = "Station" total_positions = 5 spawn_positions = 3 diff --git a/code/modules/jobs/job_types/mime.dm b/code/modules/jobs/job_types/mime.dm index fa01a5777218..a00fec8e1b54 100644 --- a/code/modules/jobs/job_types/mime.dm +++ b/code/modules/jobs/job_types/mime.dm @@ -1,8 +1,6 @@ /datum/job/mime title = "Mime" - flag = MIME department_head = list("Head of Personnel") - department_flag = CIVILIAN faction = "Station" total_positions = 1 spawn_positions = 1 diff --git a/code/modules/jobs/job_types/paramedic.dm b/code/modules/jobs/job_types/paramedic.dm index 4011190af4d3..fea4a965cccf 100644 --- a/code/modules/jobs/job_types/paramedic.dm +++ b/code/modules/jobs/job_types/paramedic.dm @@ -1,8 +1,6 @@ /datum/job/paramedic title = "Paramedic" - flag = PARAMEDIC department_head = list("Chief Medical Officer") - department_flag = MEDSCI faction = "Station" total_positions = 2 spawn_positions = 2 diff --git a/code/modules/jobs/job_types/prisoner.dm b/code/modules/jobs/job_types/prisoner.dm index 122f87e48bbc..bae40d56ada3 100644 --- a/code/modules/jobs/job_types/prisoner.dm +++ b/code/modules/jobs/job_types/prisoner.dm @@ -1,8 +1,6 @@ /datum/job/prisoner title = "Prisoner" - flag = PRISONER department_head = list("The Security Team") - department_flag = CIVILIAN faction = "Station" total_positions = 0 spawn_positions = 2 diff --git a/code/modules/jobs/job_types/psychologist.dm b/code/modules/jobs/job_types/psychologist.dm index 92348918a816..876473afa013 100644 --- a/code/modules/jobs/job_types/psychologist.dm +++ b/code/modules/jobs/job_types/psychologist.dm @@ -1,9 +1,7 @@ /* Wasp Edit - Fuck Psychologists. /datum/job/psychologist title = "Psychologist" - flag = PSYCHOLOGIST department_head = list("Head of Personnel","Chief Medical Officer") - department_flag = CIVILIAN faction = "Station" total_positions = 1 spawn_positions = 1 diff --git a/code/modules/jobs/job_types/quartermaster.dm b/code/modules/jobs/job_types/quartermaster.dm index 68c08be7b1eb..72e8396f1f20 100644 --- a/code/modules/jobs/job_types/quartermaster.dm +++ b/code/modules/jobs/job_types/quartermaster.dm @@ -1,8 +1,6 @@ /datum/job/qm title = "Quartermaster" - flag = QUARTERMASTER department_head = list("Head of Personnel") - department_flag = CIVILIAN faction = "Station" total_positions = 1 spawn_positions = 1 diff --git a/code/modules/jobs/job_types/research_director.dm b/code/modules/jobs/job_types/research_director.dm index 15adc17697f0..26c143d359e5 100644 --- a/code/modules/jobs/job_types/research_director.dm +++ b/code/modules/jobs/job_types/research_director.dm @@ -1,9 +1,7 @@ /datum/job/rd title = "Research Director" - flag = RD_JF auto_deadmin_role_flags = DEADMIN_POSITION_HEAD department_head = list("Captain") - department_flag = MEDSCI head_announce = list("Science") faction = "Station" total_positions = 1 diff --git a/code/modules/jobs/job_types/roboticist.dm b/code/modules/jobs/job_types/roboticist.dm index 0b8b8c0355c3..507d0f3c9690 100644 --- a/code/modules/jobs/job_types/roboticist.dm +++ b/code/modules/jobs/job_types/roboticist.dm @@ -1,8 +1,6 @@ /datum/job/roboticist title = "Roboticist" - flag = ROBOTICIST department_head = list("Research Director") - department_flag = MEDSCI faction = "Station" total_positions = 2 spawn_positions = 2 diff --git a/code/modules/jobs/job_types/scientist.dm b/code/modules/jobs/job_types/scientist.dm index 71dc4b0fad9c..2d565ca07116 100644 --- a/code/modules/jobs/job_types/scientist.dm +++ b/code/modules/jobs/job_types/scientist.dm @@ -1,8 +1,6 @@ /datum/job/scientist title = "Scientist" - flag = SCIENTIST department_head = list("Research Director") - department_flag = MEDSCI faction = "Station" total_positions = 5 spawn_positions = 3 diff --git a/code/modules/jobs/job_types/security_officer.dm b/code/modules/jobs/job_types/security_officer.dm index 5dc07ce88a43..b10ca5b7f138 100644 --- a/code/modules/jobs/job_types/security_officer.dm +++ b/code/modules/jobs/job_types/security_officer.dm @@ -1,9 +1,7 @@ /datum/job/officer title = "Security Officer" - flag = OFFICER auto_deadmin_role_flags = DEADMIN_POSITION_SECURITY department_head = list("Head of Security") - department_flag = ENGSEC faction = "Station" total_positions = 5 //Handled in /datum/controller/occupations/proc/setup_officer_positions() spawn_positions = 5 //Handled in /datum/controller/occupations/proc/setup_officer_positions() diff --git a/code/modules/jobs/job_types/shaft_miner.dm b/code/modules/jobs/job_types/shaft_miner.dm index 02178920d5a7..ef3e656ad21b 100644 --- a/code/modules/jobs/job_types/shaft_miner.dm +++ b/code/modules/jobs/job_types/shaft_miner.dm @@ -1,8 +1,6 @@ /datum/job/mining title = "Shaft Miner" - flag = MINER department_head = list("Head of Personnel") - department_flag = CIVILIAN faction = "Station" total_positions = 3 spawn_positions = 3 diff --git a/code/modules/jobs/job_types/station_engineer.dm b/code/modules/jobs/job_types/station_engineer.dm index 58914c5a7f32..29c041752624 100644 --- a/code/modules/jobs/job_types/station_engineer.dm +++ b/code/modules/jobs/job_types/station_engineer.dm @@ -1,8 +1,6 @@ /datum/job/engineer title = "Station Engineer" - flag = ENGINEER department_head = list("Chief Engineer") - department_flag = ENGSEC faction = "Station" total_positions = 5 spawn_positions = 5 diff --git a/code/modules/jobs/job_types/virologist.dm b/code/modules/jobs/job_types/virologist.dm index 338e476e0576..5172054fbccc 100644 --- a/code/modules/jobs/job_types/virologist.dm +++ b/code/modules/jobs/job_types/virologist.dm @@ -1,8 +1,6 @@ /datum/job/virologist title = "Virologist" - flag = VIROLOGIST department_head = list("Chief Medical Officer") - department_flag = MEDSCI faction = "Station" total_positions = 1 spawn_positions = 1 diff --git a/code/modules/jobs/job_types/warden.dm b/code/modules/jobs/job_types/warden.dm index 605099ef3df5..eb3502f9d5f6 100644 --- a/code/modules/jobs/job_types/warden.dm +++ b/code/modules/jobs/job_types/warden.dm @@ -1,9 +1,7 @@ /datum/job/warden title = "Warden" - flag = WARDEN auto_deadmin_role_flags = DEADMIN_POSITION_SECURITY department_head = list("Head of Security") - department_flag = ENGSEC faction = "Station" total_positions = 1 spawn_positions = 1 diff --git a/code/modules/jobs/jobs.dm b/code/modules/jobs/jobs.dm index c96d689d28df..20b6d888bef6 100644 --- a/code/modules/jobs/jobs.dm +++ b/code/modules/jobs/jobs.dm @@ -1,147 +1,147 @@ -GLOBAL_LIST_INIT(command_positions, list( - "Captain", - "Head of Personnel", - "Lieutenant", //Wasp edit - Lieutenant - "Head of Security", - "Chief Engineer", - "Research Director", - "Chief Medical Officer")) - - -GLOBAL_LIST_INIT(engineering_positions, list( - "Chief Engineer", - "Station Engineer", - "Atmospheric Technician")) - - -GLOBAL_LIST_INIT(medical_positions, list( - "Chief Medical Officer", - "Medical Doctor", - "Paramedic", - "Geneticist", - "Virologist", - "Chemist")) - - -GLOBAL_LIST_INIT(science_positions, list( - "Research Director", - "Scientist", - "Roboticist")) - - -GLOBAL_LIST_INIT(supply_positions, list( - "Quartermaster", - "Cargo Technician", - "Shaft Miner")) - - -GLOBAL_LIST_INIT(service_positions, list( - "Head of Personnel", - "Lieutenant", - "Bartender", - "Botanist", - "Cook", - "Janitor", - "Curator", - "Psychologist", - "Lawyer", - "Chaplain", - "Clown", - "Mime", - "Prisoner", - "Assistant")) - - -GLOBAL_LIST_INIT(security_positions, list( - "Head of Security", - "Warden", - "Detective", - "Security Officer", - "Brig Physician")) //Wasp edit - Brig Physicians - - -GLOBAL_LIST_INIT(nonhuman_positions, list( - "AI", - "Cyborg", - ROLE_PAI)) - -// job categories for rendering the late join menu -GLOBAL_LIST_INIT(position_categories, list( - EXP_TYPE_COMMAND = list("jobs" = command_positions, "color" = "#ccccff"), - EXP_TYPE_ENGINEERING = list("jobs" = engineering_positions, "color" = "#ffeeaa"), - EXP_TYPE_SUPPLY = list("jobs" = supply_positions, "color" = "#ddddff"), - EXP_TYPE_SILICON = list("jobs" = nonhuman_positions - "pAI", "color" = "#ccffcc"), - EXP_TYPE_SERVICE = list("jobs" = service_positions, "color" = "#bbe291"), - EXP_TYPE_MEDICAL = list("jobs" = medical_positions, "color" = "#ffddf0"), - EXP_TYPE_SCIENCE = list("jobs" = science_positions, "color" = "#ffddff"), - EXP_TYPE_SECURITY = list("jobs" = security_positions, "color" = "#ffdddd") -)) - -GLOBAL_LIST_INIT(exp_jobsmap, list( - EXP_TYPE_CREW = list("titles" = command_positions | engineering_positions | medical_positions | science_positions | supply_positions | security_positions | service_positions | list("AI","Cyborg")), // crew positions - EXP_TYPE_COMMAND = list("titles" = command_positions), - EXP_TYPE_ENGINEERING = list("titles" = engineering_positions), - EXP_TYPE_MEDICAL = list("titles" = medical_positions), - EXP_TYPE_SCIENCE = list("titles" = science_positions), - EXP_TYPE_SUPPLY = list("titles" = supply_positions), - EXP_TYPE_SECURITY = list("titles" = security_positions), - EXP_TYPE_SILICON = list("titles" = list("AI","Cyborg")), - EXP_TYPE_SERVICE = list("titles" = service_positions) -)) - -GLOBAL_LIST_INIT(exp_specialmap, list( - EXP_TYPE_LIVING = list(), // all living mobs - EXP_TYPE_ANTAG = list(), - EXP_TYPE_SPECIAL = list("Lifebringer","Ash Walker","Exile","Servant Golem","Free Golem","Hermit","Translocated Vet","Escaped Prisoner","Hotel Staff","SuperFriend","Space Syndicate","Ancient Crew","Space Doctor","Space Bartender","Beach Bum","Skeleton","Zombie","Space Bar Patron","Lavaland Syndicate","Ghost Role"), // Ghost roles - EXP_TYPE_GHOST = list() // dead people, observers -)) -GLOBAL_PROTECT(exp_jobsmap) -GLOBAL_PROTECT(exp_specialmap) - -/proc/guest_jobbans(job) - return ((job in GLOB.command_positions) || (job in GLOB.nonhuman_positions) || (job in GLOB.security_positions)) - - - -//this is necessary because antags happen before job datums are handed out, but NOT before they come into existence -//so I can't simply use job datum.department_head straight from the mind datum, laaaaame. -/proc/get_department_heads(var/job_title) - if(!job_title) - return list() - - for(var/datum/job/J in SSjob.occupations) - if(J.title == job_title) - return J.department_head //this is a list - -/proc/get_full_job_name(job) - var/static/regex/cap_expand = new("cap(?!tain)") - var/static/regex/cmo_expand = new("cmo") - var/static/regex/hos_expand = new("hos") - var/static/regex/hop_expand = new("fo") - var/static/regex/rd_expand = new("rd") - var/static/regex/ce_expand = new("ce") - var/static/regex/qm_expand = new("qm") - var/static/regex/sec_expand = new("(? SCRAMBLE_CACHE_LEN) - scramble_cache.Cut(1, scramble_cache.len-SCRAMBLE_CACHE_LEN-1) - -/datum/language/proc/scramble(input) - - if(!syllables || !syllables.len) - return stars(input) - - // If the input is cached already, move it to the end of the cache and return it - var/lookup = check_cache(input) - if(lookup) - return lookup - - var/input_size = length_char(input) - var/scrambled_text = "" - var/capitalize = TRUE - - while(length_char(scrambled_text) < input_size) - var/next = pick(syllables) - if(capitalize) - next = capitalize(next) - capitalize = FALSE - scrambled_text += next - var/chance = rand(100) - if(chance <= sentence_chance) - scrambled_text += ". " - capitalize = TRUE - else if(chance > sentence_chance && chance <= space_chance) - scrambled_text += " " - - scrambled_text = trim(scrambled_text) - var/ending = copytext_char(scrambled_text, -1) - if(ending == ".") - scrambled_text = copytext_char(scrambled_text, 1, -2) - var/input_ending = copytext_char(input, -1) - if(input_ending in list("!","?",".")) - scrambled_text += input_ending - - add_to_cache(input, scrambled_text) - - return scrambled_text - -/datum/language/proc/get_spoken_verb(msg_end) - switch(msg_end) - if("!") - return exclaim_verb - if("?") - return ask_verb - return speech_verb - -#undef SCRAMBLE_CACHE_LEN +#define SCRAMBLE_CACHE_LEN 50 //maximum of 50 specific scrambled lines per language + +/* + Datum based languages. Easily editable and modular. +*/ + +/datum/language + var/name = "an unknown language" // Fluff name of language if any. + var/desc = "A language." // Short description for 'Check Languages'. + var/speech_verb = "says" // 'says', 'hisses', 'farts'. + var/ask_verb = "asks" // Used when sentence ends in a ? + var/exclaim_verb = "exclaims" // Used when sentence ends in a ! + var/whisper_verb = "whispers" // Optional. When not specified speech_verb + quietly/softly is used instead. + var/sing_verb = "sings" // Used for singing. + var/list/signlang_verb = list("signs", "gestures") // list of emotes that might be displayed if this language has NONVERBAL or SIGNLANG flags + var/key // Character used to speak in language + // If key is null, then the language isn't real or learnable. + var/flags // Various language flags. + var/list/syllables // Used when scrambling text for a non-speaker. + var/sentence_chance = 5 // Likelihood of making a new sentence after each syllable. + var/space_chance = 55 // Likelihood of getting a space in the random scramble string + var/list/spans = list() + var/list/scramble_spans = list() //For wingding fonts + var/list/scramble_cache = list() + var/default_priority = 0 // the language that an atom knows with the highest "default_priority" is selected by default. + + // if you are seeing someone speak popcorn language, then something is wrong. + var/icon = 'icons/misc/language.dmi' + var/icon_state = "popcorn" + +/datum/language/proc/display_icon(atom/movable/hearer) + var/understands = hearer.has_language(src.type) + if(flags & LANGUAGE_HIDE_ICON_IF_UNDERSTOOD && understands) + return FALSE + if(flags & LANGUAGE_HIDE_ICON_IF_NOT_UNDERSTOOD && !understands) + return FALSE + return TRUE + +/datum/language/proc/get_icon() + var/datum/asset/spritesheet/sheet = get_asset_datum(/datum/asset/spritesheet/goonchat) + return sheet.icon_tag("language-[icon_state]") + +/datum/language/proc/get_random_name(gender, name_count=2, syllable_count=4, syllable_divisor=2) + if(!syllables || !syllables.len) + if(gender==FEMALE) + return capitalize(pick(GLOB.first_names_female)) + " " + capitalize(pick(GLOB.last_names)) + else + return capitalize(pick(GLOB.first_names_male)) + " " + capitalize(pick(GLOB.last_names)) + + var/full_name = "" + var/new_name = "" + + for(var/i in 0 to name_count) + new_name = "" + var/Y = rand(FLOOR(syllable_count/syllable_divisor, 1), syllable_count) + for(var/x in Y to 0) + new_name += pick(syllables) + full_name += " [capitalize(lowertext(new_name))]" + + return "[trim(full_name)]" + +/datum/language/proc/check_cache(input) + var/lookup = scramble_cache[input] + if(lookup) + scramble_cache -= input + scramble_cache[input] = lookup + . = lookup + +/datum/language/proc/add_to_cache(input, scrambled_text) + // Add it to cache, cutting old entries if the list is too long + scramble_cache[input] = scrambled_text + if(scramble_cache.len > SCRAMBLE_CACHE_LEN) + scramble_cache.Cut(1, scramble_cache.len-SCRAMBLE_CACHE_LEN-1) + +/datum/language/proc/scramble(input) + + if(!syllables || !syllables.len) + return stars(input) + + // If the input is cached already, move it to the end of the cache and return it + var/lookup = check_cache(input) + if(lookup) + return lookup + + var/input_size = length_char(input) + var/scrambled_text = "" + var/capitalize = TRUE + + while(length_char(scrambled_text) < input_size) + var/next = pick(syllables) + if(capitalize) + next = capitalize(next) + capitalize = FALSE + scrambled_text += next + var/chance = rand(100) + if(chance <= sentence_chance) + scrambled_text += ". " + capitalize = TRUE + else if(chance > sentence_chance && chance <= space_chance) + scrambled_text += " " + + scrambled_text = trim(scrambled_text) + var/ending = copytext_char(scrambled_text, -1) + if(ending == ".") + scrambled_text = copytext_char(scrambled_text, 1, -2) + var/input_ending = copytext_char(input, -1) + if(input_ending in list("!","?",".")) + scrambled_text += input_ending + + add_to_cache(input, scrambled_text) + + return scrambled_text + +/datum/language/proc/get_spoken_verb(msg_end) + switch(msg_end) + if("!") + return exclaim_verb + if("?") + return ask_verb + return speech_verb + +#undef SCRAMBLE_CACHE_LEN diff --git a/code/modules/language/language_menu.dm b/code/modules/language/language_menu.dm index 0df7c01fca8c..bffd3d59af64 100644 --- a/code/modules/language/language_menu.dm +++ b/code/modules/language/language_menu.dm @@ -8,10 +8,13 @@ language_holder = null . = ..() -/datum/language_menu/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.language_menu_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/datum/language_menu/ui_state(mob/user) + return GLOB.language_menu_state + +/datum/language_menu/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "LanguageMenu", "Language Menu", 700, 600, master_ui, state) + ui = new(user, src, "LanguageMenu") ui.open() /datum/language_menu/ui_data(mob/user) diff --git a/code/modules/language/machine.dm b/code/modules/language/machine.dm index b5e0ead3f39b..c251de75412a 100644 --- a/code/modules/language/machine.dm +++ b/code/modules/language/machine.dm @@ -1,20 +1,20 @@ -/datum/language/machine - name = "Encoded Audio Language" - desc = "An efficient language of encoded tones developed by synthetics and cyborgs." - speech_verb = "whistles" - ask_verb = "chirps" - exclaim_verb = "whistles loudly" - sing_verb = "whistles melodically" - spans = list(SPAN_ROBOT) - key = "6" - flags = NO_STUTTER - syllables = list("beep","beep","beep","beep","beep","boop","boop","boop","bop","bop","dee","dee","doo","doo","hiss","hss","buzz","buzz","bzz","ksssh","keey","wurr","wahh","tzzz") - space_chance = 10 - default_priority = 90 - - icon_state = "eal" - -/datum/language/machine/get_random_name() - if(prob(70)) - return "[pick(GLOB.posibrain_names)]-[rand(100, 999)]" - return pick(GLOB.ai_names) +/datum/language/machine + name = "Encoded Audio Language" + desc = "An efficient language of encoded tones developed by synthetics and cyborgs." + speech_verb = "whistles" + ask_verb = "chirps" + exclaim_verb = "whistles loudly" + sing_verb = "whistles melodically" + spans = list(SPAN_ROBOT) + key = "6" + flags = NO_STUTTER + syllables = list("beep","beep","beep","beep","beep","boop","boop","boop","bop","bop","dee","dee","doo","doo","hiss","hss","buzz","buzz","bzz","ksssh","keey","wurr","wahh","tzzz") + space_chance = 10 + default_priority = 90 + + icon_state = "eal" + +/datum/language/machine/get_random_name() + if(prob(70)) + return "[pick(GLOB.posibrain_names)]-[rand(100, 999)]" + return pick(GLOB.ai_names) diff --git a/code/modules/language/moffic.dm b/code/modules/language/moffic.dm index a03c0d39803b..879cb9cd9e0b 100644 --- a/code/modules/language/moffic.dm +++ b/code/modules/language/moffic.dm @@ -1,18 +1,18 @@ -/datum/language/moffic - name = "Moffic" - desc = "The common language of moths. Its origin is unknown, and defies the moths as easily as it does as historians." - speech_verb = "flutters" - ask_verb = "fluffs" - exclaim_verb = "floofs" - key = "m" - space_chance = 55 - syllables = list( - "to", "pe", "chu", "mar", "mark", "chur", "ro", "no", "pa", "du", "bli", "bop", "nad", "lah", "vehy", "nu", "ni", "do", "vey", - "rey", "rehy", "ba", "bah", "bap", "sha", "shaw", "ha", "ad", "i", "a", "o", "do", "hun", "da", "dah", "ge", "dro", "dros", - "cal", "cah", "li", "mo", "gar", "ga", "ma", "mah", "ra", "he", "le", "gek", "gekh", "tu", "too", "hak", "am", "as", "sag", "an", - "ne", "ned", "ros", "me", "men", "nob", "bis", "co", "bas", "su", "pap", "de", "ti", "bo", "len", "lin", "mas", "nas", - "zu", "za", "kun", "kan", "oh", "ko", "nah", "nya", "pu", "po", "mun", "ta", "fu", "waa" - ) - icon_state = "moth" - default_priority = 90 - +/datum/language/moffic + name = "Moffic" + desc = "The common language of moths. Its origin is unknown, and defies the moths as easily as it does as historians." + speech_verb = "flutters" + ask_verb = "fluffs" + exclaim_verb = "floofs" + key = "m" + space_chance = 55 + syllables = list( + "to", "pe", "chu", "mar", "mark", "chur", "ro", "no", "pa", "du", "bli", "bop", "nad", "lah", "vehy", "nu", "ni", "do", "vey", + "rey", "rehy", "ba", "bah", "bap", "sha", "shaw", "ha", "ad", "i", "a", "o", "do", "hun", "da", "dah", "ge", "dro", "dros", + "cal", "cah", "li", "mo", "gar", "ga", "ma", "mah", "ra", "he", "le", "gek", "gekh", "tu", "too", "hak", "am", "as", "sag", "an", + "ne", "ned", "ros", "me", "men", "nob", "bis", "co", "bas", "su", "pap", "de", "ti", "bo", "len", "lin", "mas", "nas", + "zu", "za", "kun", "kan", "oh", "ko", "nah", "nya", "pu", "po", "mun", "ta", "fu", "waa" + ) + icon_state = "moth" + default_priority = 90 + diff --git a/code/modules/language/monkey.dm b/code/modules/language/monkey.dm index 8c14a9d0970d..5e78a37a6253 100644 --- a/code/modules/language/monkey.dm +++ b/code/modules/language/monkey.dm @@ -1,13 +1,13 @@ -/datum/language/monkey - name = "Chimpanzee" - desc = "Ook ook ook." - speech_verb = "chimpers" - ask_verb = "chimpers" - exclaim_verb = "screeches" - sing_verb = "chimpers tunefully" - key = "1" - space_chance = 100 - syllables = list("oop", "aak", "chee", "eek") - default_priority = 80 - - icon_state = "animal" +/datum/language/monkey + name = "Chimpanzee" + desc = "Ook ook ook." + speech_verb = "chimpers" + ask_verb = "chimpers" + exclaim_verb = "screeches" + sing_verb = "chimpers tunefully" + key = "1" + space_chance = 100 + syllables = list("oop", "aak", "chee", "eek") + default_priority = 80 + + icon_state = "animal" diff --git a/code/modules/language/shadowtongue.dm b/code/modules/language/shadowtongue.dm index 9d6350bf99a3..344fa612beee 100644 --- a/code/modules/language/shadowtongue.dm +++ b/code/modules/language/shadowtongue.dm @@ -1,22 +1,22 @@ -// You n'wah! -// many thanks to https://casualscrolls.fandom.com/wiki/Dunmeri_language, for providing this list of syllables -/datum/language/shadowtongue - name = "Shadowtongue" - desc = "What a grand and intoxicating innocence." - speech_verb = "growls" - ask_verb = "growls" - exclaim_verb = "roars" - sing_verb = "chants" - key = "x" - space_chance = 50 - syllables = list( - "er", "sint", "en", "et", "nor", "bahr", "sint", "un", "ku'elm", "lakor", "eri", - "noj", "dashilu", "as", "ot", "lih", "morh", "ghinu", "kin", "sha", "marik", "jibu", - "sudas", "fut", "kol", "bivi", "pohim", "devohr", "ru", "huirf", "neiris", "sut", - "devehr", "iru", "gher", "gan", "ujil", "lacor", "bahris", "ghar", "alnef", "wah", - "khurdhar", "bar", "et", "ilu", "dash", "diru", "noj", "de", "damjulan", "luvahr", - "telshahr", "tifur", "enhi", "am", "bahr", "nei", "neibahri", "n'chow", "n'wah", - "s'wit", "b'vehk", "f'lah", "muth", "sera", "sedura", "bal", "dun" - ) - icon_state = "shadow" - default_priority = 90 +// You n'wah! +// many thanks to https://casualscrolls.fandom.com/wiki/Dunmeri_language, for providing this list of syllables +/datum/language/shadowtongue + name = "Shadowtongue" + desc = "What a grand and intoxicating innocence." + speech_verb = "growls" + ask_verb = "growls" + exclaim_verb = "roars" + sing_verb = "chants" + key = "x" + space_chance = 50 + syllables = list( + "er", "sint", "en", "et", "nor", "bahr", "sint", "un", "ku'elm", "lakor", "eri", + "noj", "dashilu", "as", "ot", "lih", "morh", "ghinu", "kin", "sha", "marik", "jibu", + "sudas", "fut", "kol", "bivi", "pohim", "devohr", "ru", "huirf", "neiris", "sut", + "devehr", "iru", "gher", "gan", "ujil", "lacor", "bahris", "ghar", "alnef", "wah", + "khurdhar", "bar", "et", "ilu", "dash", "diru", "noj", "de", "damjulan", "luvahr", + "telshahr", "tifur", "enhi", "am", "bahr", "nei", "neibahri", "n'chow", "n'wah", + "s'wit", "b'vehk", "f'lah", "muth", "sera", "sedura", "bal", "dun" + ) + icon_state = "shadow" + default_priority = 90 diff --git a/code/modules/language/sylvan.dm b/code/modules/language/sylvan.dm index 3fc7d891faad..c6fe73849b1a 100644 --- a/code/modules/language/sylvan.dm +++ b/code/modules/language/sylvan.dm @@ -1,19 +1,19 @@ -// The language of the podpeople. Yes, it's a shameless ripoff of elvish. -/datum/language/sylvan - name = "Sylvan" - desc = "A complicated, ancient language spoken by sentient plants." - speech_verb = "expresses" - ask_verb = "inquires" - exclaim_verb = "declares" - sing_verb = "serenades" - key = "h" - space_chance = 20 - syllables = list( - "fii", "sii", "rii", "rel", "maa", "ala", "san", "tol", "tok", "dia", "eres", - "fal", "tis", "bis", "qel", "aras", "losk", "rasa", "eob", "hil", "tanl", "aere", - "fer", "bal", "pii", "dala", "ban", "foe", "doa", "cii", "uis", "mel", "wex", - "incas", "int", "elc", "ent", "aws", "qip", "nas", "vil", "jens", "dila", "fa", - "la", "re", "do", "ji", "ae", "so", "qe", "ce", "na", "mo", "ha", "yu" - ) - icon_state = "plant" - default_priority = 90 +// The language of the podpeople. Yes, it's a shameless ripoff of elvish. +/datum/language/sylvan + name = "Sylvan" + desc = "A complicated, ancient language spoken by sentient plants." + speech_verb = "expresses" + ask_verb = "inquires" + exclaim_verb = "declares" + sing_verb = "serenades" + key = "h" + space_chance = 20 + syllables = list( + "fii", "sii", "rii", "rel", "maa", "ala", "san", "tol", "tok", "dia", "eres", + "fal", "tis", "bis", "qel", "aras", "losk", "rasa", "eob", "hil", "tanl", "aere", + "fer", "bal", "pii", "dala", "ban", "foe", "doa", "cii", "uis", "mel", "wex", + "incas", "int", "elc", "ent", "aws", "qip", "nas", "vil", "jens", "dila", "fa", + "la", "re", "do", "ji", "ae", "so", "qe", "ce", "na", "mo", "ha", "yu" + ) + icon_state = "plant" + default_priority = 90 diff --git a/code/modules/language/terrum.dm b/code/modules/language/terrum.dm index a9d233837402..7089246ad4d5 100644 --- a/code/modules/language/terrum.dm +++ b/code/modules/language/terrum.dm @@ -1,18 +1,18 @@ -/datum/language/terrum - name = "Terrum" - desc = "The language of the golems. Sounds similar to old-earth Hebrew." - speech_verb = "rumbles" - ask_verb = "questions" - exclaim_verb = "tremors" - sing_verb = "yodels" - key = "g" - space_chance = 40 - syllables = list( - "sha", "vu", "nah", "ha", "yom", "ma", "cha", "ar", "et", "mol", "lua", - "ch", "na", "sh", "ni", "yah", "bes", "ol", "hish", "ev", "la", "ot", "la", - "khe", "tza", "chak", "hak", "hin", "hok", "lir", "tov", "yef", "yfe", - "cho", "ar", "kas", "kal", "ra", "lom", "im", "'", "'", "'", "'", "bok", - "erev", "shlo", "lo", "ta", "im", "yom" - ) - icon_state = "golem" - default_priority = 90 +/datum/language/terrum + name = "Terrum" + desc = "The language of the golems. Sounds similar to old-earth Hebrew." + speech_verb = "rumbles" + ask_verb = "questions" + exclaim_verb = "tremors" + sing_verb = "yodels" + key = "g" + space_chance = 40 + syllables = list( + "sha", "vu", "nah", "ha", "yom", "ma", "cha", "ar", "et", "mol", "lua", + "ch", "na", "sh", "ni", "yah", "bes", "ol", "hish", "ev", "la", "ot", "la", + "khe", "tza", "chak", "hak", "hin", "hok", "lir", "tov", "yef", "yfe", + "cho", "ar", "kas", "kal", "ra", "lom", "im", "'", "'", "'", "'", "bok", + "erev", "shlo", "lo", "ta", "im", "yom" + ) + icon_state = "golem" + default_priority = 90 diff --git a/code/modules/language/voltaic.dm b/code/modules/language/voltaic.dm index f7e8563ee76e..da81e3c0c88c 100644 --- a/code/modules/language/voltaic.dm +++ b/code/modules/language/voltaic.dm @@ -1,18 +1,18 @@ -// One of these languages will actually work, I'm certain of it. -/datum/language/voltaic - name = "Voltaic" - desc = "A sparky language made by manipulating electrical discharge." - speech_verb = "crackles" - ask_verb = "pops" - exclaim_verb = "thunders" - sing_verb = "synthesizes" - key = "v" - space_chance = 20 - syllables = list( - "bzzt", "skrrt", "zzp", "mmm", "hzz", "tk", "shz", "k", "z", - "bzt", "zzt", "skzt", "skzz", "hmmt", "zrrt", "hzzt", "hz", - "vzt", "zt", "vz", "zip", "tzp", "lzzt", "dzzt", "zdt", "kzt", - "zzzz", "mzz" - ) - icon_state = "volt" - default_priority = 90 +// One of these languages will actually work, I'm certain of it. +/datum/language/voltaic + name = "Voltaic" + desc = "A sparky language made by manipulating electrical discharge." + speech_verb = "crackles" + ask_verb = "pops" + exclaim_verb = "thunders" + sing_verb = "synthesizes" + key = "v" + space_chance = 20 + syllables = list( + "bzzt", "skrrt", "zzp", "mmm", "hzz", "tk", "shz", "k", "z", + "bzt", "zzt", "skzt", "skzz", "hmmt", "zrrt", "hzzt", "hz", + "vzt", "zt", "vz", "zip", "tzp", "lzzt", "dzzt", "zdt", "kzt", + "zzzz", "mzz" + ) + icon_state = "volt" + default_priority = 90 diff --git a/code/modules/language/xenocommon.dm b/code/modules/language/xenocommon.dm index 998fdea326ec..8b5bb78db3e3 100644 --- a/code/modules/language/xenocommon.dm +++ b/code/modules/language/xenocommon.dm @@ -1,12 +1,12 @@ -/datum/language/xenocommon - name = "Xenomorph" - desc = "The common tongue of the xenomorphs." - speech_verb = "hisses" - ask_verb = "hisses" - exclaim_verb = "hisses" - sing_verb = "hisses musically" - key = "4" - syllables = list("sss","sSs","SSS") - default_priority = 50 - - icon_state = "xeno" +/datum/language/xenocommon + name = "Xenomorph" + desc = "The common tongue of the xenomorphs." + speech_verb = "hisses" + ask_verb = "hisses" + exclaim_verb = "hisses" + sing_verb = "hisses musically" + key = "4" + syllables = list("sss","sSs","SSS") + default_priority = 50 + + icon_state = "xeno" diff --git a/code/modules/library/lib_codex_gigas.dm b/code/modules/library/lib_codex_gigas.dm index e7bb86ff7992..be017a07ef6b 100644 --- a/code/modules/library/lib_codex_gigas.dm +++ b/code/modules/library/lib_codex_gigas.dm @@ -91,11 +91,10 @@ currentSection = SUFFIX return currentSection != oldSection -/obj/item/book/codex_gigas/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/item/book/codex_gigas/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "CodexGigas", name, 450, 450, master_ui, state) + ui = new(user, src, "CodexGigas", name) ui.open() /obj/item/book/codex_gigas/ui_data(mob/user) diff --git a/code/modules/library/lib_items.dm b/code/modules/library/lib_items.dm index 71e21669200a..c1626e5e2c4a 100644 --- a/code/modules/library/lib_items.dm +++ b/code/modules/library/lib_items.dm @@ -1,344 +1,344 @@ -/* Library Items - * - * Contains: - * Bookcase - * Book - * Barcode Scanner - */ - -/* - * Bookcase - */ - -/obj/structure/bookcase - name = "bookcase" - icon = 'icons/obj/library.dmi' - icon_state = "bookempty" - desc = "A great place for storing knowledge." - anchored = FALSE - density = TRUE - opacity = 0 - resistance_flags = FLAMMABLE - max_integrity = 200 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 0) - var/state = 0 - var/list/allowed_books = list(/obj/item/book, /obj/item/spellbook, /obj/item/storage/book) //Things allowed in the bookcase - -/obj/structure/bookcase/examine(mob/user) - . = ..() - if(!anchored) - . += "The bolts on the bottom are unsecured." - else - . += "It's secured in place with bolts." - switch(state) - if(0) - . += "There's a small crack visible on the back panel." - if(1) - . += "There's space inside for a wooden shelf." - if(2) - . += "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 - for(var/obj/item/I in loc) - if(istype(I, /obj/item/book)) - I.forceMove(src) - update_icon() - -/obj/structure/bookcase/attackby(obj/item/I, mob/user, params) - switch(state) - if(0) - 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) - if(I.use_tool(src, user, 20, volume=50)) - to_chat(user, "You pry the frame apart.") - deconstruct(TRUE) - - if(1) - 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) - I.play_tool_sound(src, 100) - to_chat(user, "You unwrench the frame.") - anchored = FALSE - state = 0 - - if(2) - var/datum/component/storage/STR = I.GetComponent(/datum/component/storage) - if(is_type_in_list(I, allowed_books)) - if(!user.transferItemToLoc(I, src)) - return - update_icon() - else if(STR) - for(var/obj/item/T in I.contents) - if(istype(T, /obj/item/book) || istype(T, /obj/item/spellbook)) - STR.remove_from_storage(T, src) - to_chat(user, "You empty \the [I] into \the [src].") - update_icon() - else if(istype(I, /obj/item/pen)) - if(!user.is_literate()) - to_chat(user, "You scribble illegibly on the side of [src]!") - return - var/newname = stripped_input(user, "What would you like to title this bookshelf?") - if(!user.canUseTopic(src, BE_CLOSE)) - return - if(!newname) - return - else - name = "bookcase ([sanitize(newname)])" - else if(I.tool_behaviour == TOOL_CROWBAR) - if(contents.len) - to_chat(user, "You need to remove the books first!") - else - I.play_tool_sound(src, 100) - to_chat(user, "You pry the shelf out.") - new /obj/item/stack/sheet/mineral/wood(drop_location(), 2) - state = 1 - icon_state = "bookempty" - else - return ..() - - -/obj/structure/bookcase/attack_hand(mob/living/user) - . = ..() - if(.) - return - if(!istype(user)) - return - if(contents.len) - 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(!(user.mobility_flags & MOBILITY_USE) || user.stat || user.restrained() || !in_range(loc, user)) - return - if(ishuman(user)) - if(!user.get_active_held_item()) - user.put_in_hands(choice) - else - choice.forceMove(drop_location()) - update_icon() - - -/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) - - -/obj/structure/bookcase/update_icon_state() - if(contents.len < 5) - icon_state = "book-[contents.len]" - else - icon_state = "book-5" - - -/obj/structure/bookcase/manuals/engineering - name = "engineering manuals bookcase" - -/obj/structure/bookcase/manuals/engineering/Initialize() - . = ..() - new /obj/item/book/manual/wiki/engineering_construction(src) - new /obj/item/book/manual/wiki/engineering_hacking(src) - new /obj/item/book/manual/wiki/engineering_guide(src) - new /obj/item/book/manual/wiki/engineering_singulo_tesla(src) - new /obj/item/book/manual/wiki/robotics_cyborgs(src) - update_icon() - - -/obj/structure/bookcase/manuals/research_and_development - name = "\improper R&D manuals bookcase" - -/obj/structure/bookcase/manuals/research_and_development/Initialize() - . = ..() - new /obj/item/book/manual/wiki/research_and_development(src) - update_icon() - - -/* - * Book - */ -/obj/item/book - name = "book" - icon = 'icons/obj/library.dmi' - icon_state ="book" - desc = "Crack it open, inhale the musk of its pages, and learn something new." - throw_speed = 1 - throw_range = 5 - w_class = WEIGHT_CLASS_NORMAL //upped to three because books are, y'know, pretty big. (and you could hide them inside eachother recursively forever) - attack_verb = list("bashed", "whacked", "educated") - resistance_flags = FLAMMABLE - drop_sound = 'sound/items/handling/book_drop.ogg' - pickup_sound = 'sound/items/handling/book_pickup.ogg' - 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/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(!user.can_read(src)) - return - if(dat) - user << browse("Penned by [author].
                    " + "[dat]", "window=book[window_size != null ? ";size=[window_size]" : ""]") - user.visible_message("[user] opens a book titled \"[title]\" and begins reading intently.") - SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "book_nerd", /datum/mood_event/book_nerd) - onclose(user, "book") - else - to_chat(user, "This book is completely blank!") - - -/obj/item/book/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/pen)) - 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.") - return - var/literate = user.is_literate() - if(!literate) - to_chat(user, "You scribble illegibly on the cover of [src]!") - return - var/choice = input("What would you like to change?") in list("Title", "Contents", "Author", "Cancel") - if(!user.canUseTopic(src, BE_CLOSE, literate)) - return - switch(choice) - if("Title") - var/newtitle = reject_bad_text(stripped_input(user, "Write a new title:")) - if(!user.canUseTopic(src, BE_CLOSE, literate)) - return - if (length(newtitle) > 20) - to_chat(user, "That title won't fit on the cover!") - return - if(!newtitle) - to_chat(user, "That title is invalid.") - return - else - name = newtitle - title = newtitle - if("Contents") - var/content = stripped_input(user, "Write your book's contents (HTML NOT allowed):","","",8192) - if(!user.canUseTopic(src, BE_CLOSE, literate)) - return - if(!content) - to_chat(user, "The content is invalid.") - return - else - dat += content - if("Author") - var/newauthor = stripped_input(user, "Write the author's name:") - if(!user.canUseTopic(src, BE_CLOSE, literate)) - return - if(!newauthor) - to_chat(user, "The name is invalid.") - return - else - author = newauthor - else - return - - 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!'") - else - switch(scanner.mode) - if(0) - scanner.book = src - 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.'") - 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.'") - return - 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.'") - return - scanner.computer.inventory.Add(src) - to_chat(user, "[I]'s screen flashes: 'Book stored in buffer. Title added to general inventory.'") - - else if(istype(I, /obj/item/kitchen/knife) || I.tool_behaviour == TOOL_WIRECUTTER) - to_chat(user, "You begin to carve out [title]...") - if(do_after(user, 30, target = src)) - to_chat(user, "You carve out the pages from [title]! You didn't want to read it anyway.") - var/obj/item/storage/book/B = new - B.name = src.name - B.title = src.title - B.icon_state = src.icon_state - if(user.is_holding(src)) - qdel(src) - user.put_in_hands(B) - return - else - B.forceMove(drop_location()) - qdel(src) - return - return - else - ..() - - -/* - * Barcode Scanner - */ -/obj/item/barcodescanner - name = "barcode scanner" - icon = 'icons/obj/library.dmi' - icon_state ="scanner" - desc = "A fabulous tool if you need to scan a barcode." - throw_speed = 3 - throw_range = 5 - w_class = WEIGHT_CLASS_TINY - var/obj/machinery/computer/bookmanagement/computer //Associated computer - Modes 1 to 3 use this - var/obj/item/book/book //Currently scanned book - var/mode = 0 //0 - Scan only, 1 - Scan and Set Buffer, 2 - Scan and Attempt to Check In, 3 - Scan and Attempt to Add to Inventory - -/obj/item/barcodescanner/attack_self(mob/user) - mode += 1 - if(mode > 3) - mode = 0 - to_chat(user, "[src] Status Display:") - var/modedesc - switch(mode) - if(0) - modedesc = "Scan book to local buffer." - if(1) - modedesc = "Scan book to local buffer and set associated computer buffer to match." - if(2) - modedesc = "Scan book to local buffer, attempt to check in scanned book." - if(3) - modedesc = "Scan book to local buffer, attempt to add book to general inventory." - else - modedesc = "ERROR" - to_chat(user, " - Mode [mode] : [modedesc]") - if(computer) - to_chat(user, "Computer has been associated with this unit.") - else - to_chat(user, "No associated computer found. Only local scans will function properly.") - to_chat(user, "\n") +/* Library Items + * + * Contains: + * Bookcase + * Book + * Barcode Scanner + */ + +/* + * Bookcase + */ + +/obj/structure/bookcase + name = "bookcase" + icon = 'icons/obj/library.dmi' + icon_state = "bookempty" + desc = "A great place for storing knowledge." + anchored = FALSE + density = TRUE + opacity = 0 + resistance_flags = FLAMMABLE + max_integrity = 200 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 0) + var/state = 0 + var/list/allowed_books = list(/obj/item/book, /obj/item/spellbook, /obj/item/storage/book) //Things allowed in the bookcase + +/obj/structure/bookcase/examine(mob/user) + . = ..() + if(!anchored) + . += "The bolts on the bottom are unsecured." + else + . += "It's secured in place with bolts." + switch(state) + if(0) + . += "There's a small crack visible on the back panel." + if(1) + . += "There's space inside for a wooden shelf." + if(2) + . += "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 + for(var/obj/item/I in loc) + if(istype(I, /obj/item/book)) + I.forceMove(src) + update_icon() + +/obj/structure/bookcase/attackby(obj/item/I, mob/user, params) + switch(state) + if(0) + 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) + if(I.use_tool(src, user, 20, volume=50)) + to_chat(user, "You pry the frame apart.") + deconstruct(TRUE) + + if(1) + 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) + I.play_tool_sound(src, 100) + to_chat(user, "You unwrench the frame.") + anchored = FALSE + state = 0 + + if(2) + var/datum/component/storage/STR = I.GetComponent(/datum/component/storage) + if(is_type_in_list(I, allowed_books)) + if(!user.transferItemToLoc(I, src)) + return + update_icon() + else if(STR) + for(var/obj/item/T in I.contents) + if(istype(T, /obj/item/book) || istype(T, /obj/item/spellbook)) + STR.remove_from_storage(T, src) + to_chat(user, "You empty \the [I] into \the [src].") + update_icon() + else if(istype(I, /obj/item/pen)) + if(!user.is_literate()) + to_chat(user, "You scribble illegibly on the side of [src]!") + return + var/newname = stripped_input(user, "What would you like to title this bookshelf?") + if(!user.canUseTopic(src, BE_CLOSE)) + return + if(!newname) + return + else + name = "bookcase ([sanitize(newname)])" + else if(I.tool_behaviour == TOOL_CROWBAR) + if(contents.len) + to_chat(user, "You need to remove the books first!") + else + I.play_tool_sound(src, 100) + to_chat(user, "You pry the shelf out.") + new /obj/item/stack/sheet/mineral/wood(drop_location(), 2) + state = 1 + icon_state = "bookempty" + else + return ..() + + +/obj/structure/bookcase/attack_hand(mob/living/user) + . = ..() + if(.) + return + if(!istype(user)) + return + if(contents.len) + 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(!(user.mobility_flags & MOBILITY_USE) || user.stat || user.restrained() || !in_range(loc, user)) + return + if(ishuman(user)) + if(!user.get_active_held_item()) + user.put_in_hands(choice) + else + choice.forceMove(drop_location()) + update_icon() + + +/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) + + +/obj/structure/bookcase/update_icon_state() + if(contents.len < 5) + icon_state = "book-[contents.len]" + else + icon_state = "book-5" + + +/obj/structure/bookcase/manuals/engineering + name = "engineering manuals bookcase" + +/obj/structure/bookcase/manuals/engineering/Initialize() + . = ..() + new /obj/item/book/manual/wiki/engineering_construction(src) + new /obj/item/book/manual/wiki/engineering_hacking(src) + new /obj/item/book/manual/wiki/engineering_guide(src) + new /obj/item/book/manual/wiki/engineering_singulo_tesla(src) + new /obj/item/book/manual/wiki/robotics_cyborgs(src) + update_icon() + + +/obj/structure/bookcase/manuals/research_and_development + name = "\improper R&D manuals bookcase" + +/obj/structure/bookcase/manuals/research_and_development/Initialize() + . = ..() + new /obj/item/book/manual/wiki/research_and_development(src) + update_icon() + + +/* + * Book + */ +/obj/item/book + name = "book" + icon = 'icons/obj/library.dmi' + icon_state ="book" + desc = "Crack it open, inhale the musk of its pages, and learn something new." + throw_speed = 1 + throw_range = 5 + w_class = WEIGHT_CLASS_NORMAL //upped to three because books are, y'know, pretty big. (and you could hide them inside eachother recursively forever) + attack_verb = list("bashed", "whacked", "educated") + resistance_flags = FLAMMABLE + drop_sound = 'sound/items/handling/book_drop.ogg' + pickup_sound = 'sound/items/handling/book_pickup.ogg' + 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/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(!user.can_read(src)) + return + if(dat) + user << browse("Penned by [author].
                    " + "[dat]", "window=book[window_size != null ? ";size=[window_size]" : ""]") + user.visible_message("[user] opens a book titled \"[title]\" and begins reading intently.") + SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "book_nerd", /datum/mood_event/book_nerd) + onclose(user, "book") + else + to_chat(user, "This book is completely blank!") + + +/obj/item/book/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/pen)) + 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.") + return + var/literate = user.is_literate() + if(!literate) + to_chat(user, "You scribble illegibly on the cover of [src]!") + return + var/choice = input("What would you like to change?") in list("Title", "Contents", "Author", "Cancel") + if(!user.canUseTopic(src, BE_CLOSE, literate)) + return + switch(choice) + if("Title") + var/newtitle = reject_bad_text(stripped_input(user, "Write a new title:")) + if(!user.canUseTopic(src, BE_CLOSE, literate)) + return + if (length(newtitle) > 20) + to_chat(user, "That title won't fit on the cover!") + return + if(!newtitle) + to_chat(user, "That title is invalid.") + return + else + name = newtitle + title = newtitle + if("Contents") + var/content = stripped_input(user, "Write your book's contents (HTML NOT allowed):","","",8192) + if(!user.canUseTopic(src, BE_CLOSE, literate)) + return + if(!content) + to_chat(user, "The content is invalid.") + return + else + dat += content + if("Author") + var/newauthor = stripped_input(user, "Write the author's name:") + if(!user.canUseTopic(src, BE_CLOSE, literate)) + return + if(!newauthor) + to_chat(user, "The name is invalid.") + return + else + author = newauthor + else + return + + 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!'") + else + switch(scanner.mode) + if(0) + scanner.book = src + 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.'") + 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.'") + return + 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.'") + return + scanner.computer.inventory.Add(src) + to_chat(user, "[I]'s screen flashes: 'Book stored in buffer. Title added to general inventory.'") + + else if(istype(I, /obj/item/kitchen/knife) || I.tool_behaviour == TOOL_WIRECUTTER) + to_chat(user, "You begin to carve out [title]...") + if(do_after(user, 30, target = src)) + to_chat(user, "You carve out the pages from [title]! You didn't want to read it anyway.") + var/obj/item/storage/book/B = new + B.name = src.name + B.title = src.title + B.icon_state = src.icon_state + if(user.is_holding(src)) + qdel(src) + user.put_in_hands(B) + return + else + B.forceMove(drop_location()) + qdel(src) + return + return + else + ..() + + +/* + * Barcode Scanner + */ +/obj/item/barcodescanner + name = "barcode scanner" + icon = 'icons/obj/library.dmi' + icon_state ="scanner" + desc = "A fabulous tool if you need to scan a barcode." + throw_speed = 3 + throw_range = 5 + w_class = WEIGHT_CLASS_TINY + var/obj/machinery/computer/bookmanagement/computer //Associated computer - Modes 1 to 3 use this + var/obj/item/book/book //Currently scanned book + var/mode = 0 //0 - Scan only, 1 - Scan and Set Buffer, 2 - Scan and Attempt to Check In, 3 - Scan and Attempt to Add to Inventory + +/obj/item/barcodescanner/attack_self(mob/user) + mode += 1 + if(mode > 3) + mode = 0 + to_chat(user, "[src] Status Display:") + var/modedesc + switch(mode) + if(0) + modedesc = "Scan book to local buffer." + if(1) + modedesc = "Scan book to local buffer and set associated computer buffer to match." + if(2) + modedesc = "Scan book to local buffer, attempt to check in scanned book." + if(3) + modedesc = "Scan book to local buffer, attempt to add book to general inventory." + else + modedesc = "ERROR" + to_chat(user, " - Mode [mode] : [modedesc]") + if(computer) + to_chat(user, "Computer has been associated with this unit.") + else + to_chat(user, "No associated computer found. Only local scans will function properly.") + to_chat(user, "\n") diff --git a/code/modules/library/lib_machines.dm b/code/modules/library/lib_machines.dm index c216acc6ba14..e9dd995910de 100644 --- a/code/modules/library/lib_machines.dm +++ b/code/modules/library/lib_machines.dm @@ -1,624 +1,634 @@ -/* Library Machines - * - * Contains: - * Borrowbook datum - * Library Public Computer - * Cachedbook datum - * Library Computer - * Library Scanner - * Book Binder - */ - - - -/* - * Library Public Computer - */ -/obj/machinery/computer/libraryconsole - name = "library visitor console" - icon_state = "oldcomp" - icon_screen = "library" - icon_keyboard = null - circuit = /obj/item/circuitboard/computer/libraryconsole - desc = "Checked out books MUST be returned on time." - var/screenstate = 0 - var/title - var/category = "Any" - var/author - var/search_page = 0 - -/obj/machinery/computer/libraryconsole/ui_interact(mob/user) - . = ..() - var/list/dat = list() // - switch(screenstate) - if(0) - dat += "

                    Search Settings


                    " - dat += "Filter by Title: [title]
                    " - dat += "Filter by Category: [category]
                    " - dat += "Filter by Author: [author]
                    " - dat += "\[Start Search\]
                    " - if(1) - if (!SSdbcore.Connect()) - dat += "ERROR: Unable to contact External Archive. Please contact your system administrator for assistance.
                    " - else if(QDELETED(user)) - return - else - dat += "" - dat += "" - author = sanitizeSQL(author) - title = sanitizeSQL(title) - category = sanitizeSQL(category) - var/SQLsearch = "isnull(deleted) AND " - if(category == "Any") - SQLsearch += "author LIKE '%[author]%' AND title LIKE '%[title]%'" - else - SQLsearch += "author LIKE '%[author]%' AND title LIKE '%[title]%' AND category='[category]'" - var/bookcount = 0 - var/booksperpage = 20 - var/datum/DBQuery/query_library_count_books = SSdbcore.NewQuery("SELECT COUNT(id) FROM [format_table_name("library")] WHERE [SQLsearch]") - 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(sanitizeSQL(search_page)) - var/limit = " LIMIT [booksperpage * search_page], [booksperpage]" - var/datum/DBQuery/query_library_list_books = SSdbcore.NewQuery("SELECT author, title, category, id FROM [format_table_name("library")] WHERE [SQLsearch][limit]") - if(!query_library_list_books.Execute()) - dat += "ERROR: Unable to retrieve book listings. Please contact your system administrator for assistance.
                    " - else - while(query_library_list_books.NextRow()) - var/author = query_library_list_books.item[1] - var/title = query_library_list_books.item[2] - var/category = query_library_list_books.item[3] - var/id = query_library_list_books.item[4] - dat += "" - qdel(query_library_list_books) - if(QDELETED(user)) - return - dat += "
                    AUTHORTITLECATEGORYSS13BN
                    [author][title][category][id]

                    " - dat += "\[Go Back\]
                    " - var/datum/browser/popup = new(user, "publiclibrary", name, 600, 400) - popup.set_content(jointext(dat, "")) - popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) - popup.open() - -/obj/machinery/computer/libraryconsole/Topic(href, href_list) - . = ..() - if(.) - usr << browse(null, "window=publiclibrary") - onclose(usr, "publiclibrary") - return - - if(href_list["settitle"]) - var/newtitle = input("Enter a title to search for:") as text|null - if(newtitle) - title = sanitize(newtitle) - else - title = null - 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" - if(href_list["setauthor"]) - var/newauthor = input("Enter an author to search for:") as text|null - if(newauthor) - author = sanitize(newauthor) - else - author = null - if(href_list["search"]) - screenstate = 1 - - if(href_list["bookpagecount"]) - search_page = text2num(href_list["bookpagecount"]) - - if(href_list["back"]) - screenstate = 0 - - src.add_fingerprint(usr) - src.updateUsrDialog() - return - -/* - * Borrowbook datum - */ -/datum/borrowbook // Datum used to keep track of who has borrowed what when and for how long. - var/bookname - var/mobname - 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. - */ -// 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. -// It's December 25th, 2014, and this is STILL here, and it's STILL relevant. Kill me -/obj/machinery/computer/bookmanagement - name = "book inventory management console" - desc = "Librarian's command station." - verb_say = "beeps" - verb_ask = "beeps" - verb_exclaim = "beeps" - pass_flags = PASSTABLE - - icon_state = "oldcomp" - icon_screen = "library" - icon_keyboard = null - 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 - var/upload_category = "Fiction" - var/list/checkouts = list() - 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/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\]
                    " - dat += "" - dat += libcomp_menu[clamp(page,1,libcomp_menu.len)] - dat += "" - dat += "
                    AUTHORTITLECATEGORY
                    <<<< >>>>
                    " - dat += "
                    (Return to main menu)
                    " - if(5) - dat += "

                    Upload a New Title

                    " - if(!scanner) - scanner = findscanner(9) - if(!scanner) - dat += "No scanner found within wireless network range.
                    " - else if(!scanner.cache) - dat += "No data found in scanner memory.
                    " - else - dat += "Data marked for upload...
                    " - dat += "Title: [scanner.cache.name]
                    " - if(!scanner.cache.author) - scanner.cache.author = "Anonymous" - dat += "Author: [scanner.cache.author]
                    " - dat += "Category: [upload_category]
                    " - dat += "\[Upload\]
                    " - dat += "(Return to main menu)
                    " - if(6) - dat += "

                    Post Title to Newscaster

                    " - if(!scanner) - scanner = findscanner(9) - if(!scanner) - dat += "No scanner found within wireless network range.
                    " - else if(!scanner.cache) - dat += "No data found in scanner memory.
                    " - else - dat += "Post [scanner.cache.name] to station newscasters?" - dat += "\[Post\]
                    " - dat += "(Return to main menu)
                    " - if(7) - dat += "

                    NTGanda(tm) Universal Printing Module

                    " - dat += "What would you like to print?
                    " - dat += "\[Bible\]
                    " - dat += "\[Poster\]
                    " - dat += "(Return to main menu)
                    " - if(8) - dat += "

                    Accessing Forbidden Lore Vault v 1.3

                    " - dat += "Are you absolutely sure you want to proceed? EldritchRelics Inc. takes no responsibilities for loss of sanity resulting from this action.

                    " - dat += "Yes.
                    " - dat += "No.
                    " - - var/datum/browser/popup = new(user, "library", name, 600, 400) - popup.set_content(dat) - popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) - popup.open() - -/obj/machinery/computer/bookmanagement/proc/findscanner(viewrange) - for(var/obj/machinery/libraryscanner/S in range(viewrange, get_turf(src))) - return S - return null - -/obj/machinery/computer/bookmanagement/proc/print_forbidden_lore(mob/user) - 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/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.") - else - return ..() - -/obj/machinery/computer/bookmanagement/emag_act(mob/user) - if(density && !(obj_flags & EMAGGED)) - obj_flags |= EMAGGED - -/obj/machinery/computer/bookmanagement/Topic(href, href_list) - if(..()) - usr << browse(null, "window=library") - onclose(usr, "library") - return - if(href_list["page"] && screenstate == 4) - page = text2num(href_list["page"]) - if(href_list["switchscreen"]) - switch(href_list["switchscreen"]) - if("0") - screenstate = 0 - if("1") - screenstate = 1 - if("2") - screenstate = 2 - if("3") - screenstate = 3 - if("4") - screenstate = 4 - if("5") - screenstate = 5 - if("6") - screenstate = 6 - if("7") - screenstate = 7 - if("8") - screenstate = 8 - if(href_list["arccheckout"]) - if(obj_flags & EMAGGED) - src.arcanecheckout = 1 - src.screenstate = 0 - if(href_list["increasetime"]) - checkoutperiod += 1 - if(href_list["decreasetime"]) - checkoutperiod -= 1 - if(checkoutperiod < 1) - checkoutperiod = 1 - if(href_list["editbook"]) - buffer_book = stripped_input(usr, "Enter the book's title:") - if(href_list["editmob"]) - buffer_mob = stripped_input(usr, "Enter the recipient's name:", max_length = MAX_NAME_LEN) - if(href_list["checkout"]) - var/datum/borrowbook/b = new /datum/borrowbook - b.bookname = sanitize(buffer_book) - b.mobname = sanitize(buffer_mob) - b.getdate = world.time - b.duedate = world.time + (checkoutperiod * 600) - checkouts.Add(b) - if(href_list["checkin"]) - var/datum/borrowbook/b = locate(href_list["checkin"]) in checkouts - if(b && istype(b)) - checkouts.Remove(b) - if(href_list["delbook"]) - var/obj/item/book/b = locate(href_list["delbook"]) in inventory - if(b && istype(b)) - inventory.Remove(b) - if(href_list["setauthor"]) - var/newauthor = stripped_input(usr, "Enter the author's name: ") - if(newauthor) - scanner.cache.author = newauthor - if(href_list["setcategory"]) - var/newcategory = input("Choose a category: ") in list("Fiction", "Non-Fiction", "Adult", "Reference", "Religion","Technical") - if(newcategory) - upload_category = newcategory - if(href_list["upload"]) - if(scanner) - if(scanner.cache) - var/choice = input("Are you certain you wish to upload this title to the Archive?") in list("Confirm", "Abort") - if(choice == "Confirm") - 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]')") - if(!query_library_upload.Execute()) - qdel(query_library_upload) - alert("Database error encountered uploading to Archive") - return - else - log_game(msg) - qdel(query_library_upload) - alert("Upload Complete. Uploaded title will be unavailable for printing for a short period") - if(href_list["newspost"]) - if(!GLOB.news_network) - alert("No news network found on station. Aborting.") - var/channelexists = 0 - for(var/datum/newscaster/feed_channel/FC in GLOB.news_network.network_channels) - if(FC.channel_name == "Nanotrasen Book Club") - channelexists = 1 - break - if(!channelexists) - GLOB.news_network.CreateFeedChannel("Nanotrasen Book Club", "Library", null) - 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) - say("Printer unavailable. Please allow a short time before attempting to print.") - else - var/orderid = input("Enter your order:") as num|null - if(orderid) - if(isnum(orderid) && ISINTEGER(orderid)) - href_list["targetid"] = num2text(orderid) - - if(href_list["targetid"]) - var/sqlid = sanitizeSQL(href_list["targetid"]) - if (!SSdbcore.Connect()) - alert("Connection to Archive has been severed. Aborting.") - if(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)") - if(!query_library_print.Execute()) - qdel(query_library_print) - say("PRINTER ERROR! Failed to print document (0x0000000F)") - return - while(query_library_print.NextRow()) - var/author = query_library_print.item[2] - var/title = query_library_print.item[3] - var/content = query_library_print.item[4] - if(!QDELETED(src)) - var/obj/item/book/B = new(get_turf(src)) - B.name = "Book: [title]" - B.title = title - 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?") - break - qdel(query_library_print) - if(href_list["printbible"]) - if(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 - else - say("Printer currently unavailable, please wait a moment.") - if(href_list["printposter"]) - if(cooldown < world.time) - new /obj/item/poster/random_official(src.loc) - cooldown = world.time + PRINTER_COOLDOWN - else - say("Printer currently unavailable, please wait a moment.") - add_fingerprint(usr) - updateUsrDialog() - -/* - * Library Scanner - */ -/obj/machinery/libraryscanner - name = "scanner control interface" - icon = 'icons/obj/library.dmi' - icon_state = "bigscanner" - desc = "It servers the purpose of scanning stuff." - density = TRUE - var/obj/item/book/cache // Last scanned book - -/obj/machinery/libraryscanner/attackby(obj/O, mob/user, params) - if(istype(O, /obj/item/book)) - if(!user.transferItemToLoc(O, src)) - return - else - return ..() - -/obj/machinery/libraryscanner/attack_hand(mob/user) - . = ..() - if(.) - return - usr.set_machine(src) - var/dat = "" // - if(cache) - dat += "Data stored in memory.
                    " - else - dat += "No data stored in memory.
                    " - dat += "\[Scan\]" - if(cache) - dat += " \[Clear Memory\]

                    \[Remove Book\]" - else - dat += "
                    " - var/datum/browser/popup = new(user, "scanner", name, 600, 400) - popup.set_content(dat) - popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) - popup.open() - -/obj/machinery/libraryscanner/Topic(href, href_list) - if(..()) - usr << browse(null, "window=scanner") - onclose(usr, "scanner") - return - - if(href_list["scan"]) - for(var/obj/item/book/B in contents) - cache = B - break - if(href_list["clear"]) - cache = null - if(href_list["eject"]) - for(var/obj/item/book/B in contents) - B.forceMove(drop_location()) - src.add_fingerprint(usr) - src.updateUsrDialog() - return - - -/* - * Book binder - */ -/obj/machinery/bookbinder - name = "book binder" - icon = 'icons/obj/library.dmi' - icon_state = "binder" - desc = "Only intended for binding paper products." - density = TRUE - var/busy = FALSE - -/obj/machinery/bookbinder/attackby(obj/O, mob/user, params) - if(istype(O, /obj/item/paper)) - bind_book(user, O) - else if(default_unfasten_wrench(user, O)) - return 1 - else - return ..() - -/obj/machinery/bookbinder/proc/bind_book(mob/user, obj/item/paper/P) - if(machine_stat) - return - if(busy) - to_chat(user, "The book binder is busy. Please wait for completion of previous operation.") - 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.") - busy = TRUE - sleep(rand(200,400)) - busy = FALSE - if(P) - if(!machine_stat) - 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)]" - B.icon_state = "book[rand(1,7)]" - qdel(P) - else - P.forceMove(drop_location()) +/* Library Machines + * + * Contains: + * Borrowbook datum + * Library Public Computer + * Cachedbook datum + * Library Computer + * Library Scanner + * Book Binder + */ + + + +/* + * Library Public Computer + */ +/obj/machinery/computer/libraryconsole + name = "library visitor console" + icon_state = "oldcomp" + icon_screen = "library" + icon_keyboard = null + circuit = /obj/item/circuitboard/computer/libraryconsole + desc = "Checked out books MUST be returned on time." + var/screenstate = 0 + var/title + var/category = "Any" + var/author + var/search_page = 0 + +/obj/machinery/computer/libraryconsole/ui_interact(mob/user) + . = ..() + var/list/dat = list() // + switch(screenstate) + if(0) + dat += "

                    Search Settings


                    " + dat += "Filter by Title: [title]
                    " + dat += "Filter by Category: [category]
                    " + dat += "Filter by Author: [author]
                    " + dat += "\[Start Search\]
                    " + if(1) + if (!SSdbcore.Connect()) + dat += "ERROR: Unable to contact External Archive. Please contact your system administrator for assistance.
                    " + else if(QDELETED(user)) + return + else + dat += "" + dat += "" + var/SQLsearch = "isnull(deleted) AND " + if(category == "Any") + SQLsearch += "author LIKE '%[author]%' AND title LIKE '%[title]%'" + else + SQLsearch += "author LIKE '%[author]%' AND title LIKE '%[title]%' AND category='[category]'" + var/bookcount = 0 + var/booksperpage = 20 + var/datum/DBQuery/query_library_count_books = SSdbcore.NewQuery({" + SELECT COUNT(id) FROM [format_table_name("library")] + WHERE isnull(deleted) + AND author LIKE '%' + :author + '%' + AND title LIKE '%' + :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/DBQuery/query_library_list_books = SSdbcore.NewQuery({" + SELECT author, title, category, id + FROM [format_table_name("library")] + WHERE isnull(deleted) + AND author LIKE '%' + :author + '%' + AND title LIKE '%' + :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 + while(query_library_list_books.NextRow()) + var/author = query_library_list_books.item[1] + var/title = query_library_list_books.item[2] + var/category = query_library_list_books.item[3] + var/id = query_library_list_books.item[4] + dat += "" + qdel(query_library_list_books) + if(QDELETED(user)) + return + dat += "
                    AUTHORTITLECATEGORYSS13BN
                    [author][title][category][id]

                    " + dat += "\[Go Back\]
                    " + var/datum/browser/popup = new(user, "publiclibrary", name, 600, 400) + popup.set_content(jointext(dat, "")) + popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) + popup.open() + +/obj/machinery/computer/libraryconsole/Topic(href, href_list) + . = ..() + if(.) + usr << browse(null, "window=publiclibrary") + onclose(usr, "publiclibrary") + return + + if(href_list["settitle"]) + var/newtitle = input("Enter a title to search for:") as text|null + if(newtitle) + title = sanitize(newtitle) + else + title = null + 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" + if(href_list["setauthor"]) + var/newauthor = input("Enter an author to search for:") as text|null + if(newauthor) + author = sanitize(newauthor) + else + author = null + if(href_list["search"]) + screenstate = 1 + + if(href_list["bookpagecount"]) + search_page = text2num(href_list["bookpagecount"]) + + if(href_list["back"]) + screenstate = 0 + + src.add_fingerprint(usr) + src.updateUsrDialog() + return + +/* + * Borrowbook datum + */ +/datum/borrowbook // Datum used to keep track of who has borrowed what when and for how long. + var/bookname + var/mobname + 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. + */ +// 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. +// It's December 25th, 2014, and this is STILL here, and it's STILL relevant. Kill me +/obj/machinery/computer/bookmanagement + name = "book inventory management console" + desc = "Librarian's command station." + verb_say = "beeps" + verb_ask = "beeps" + verb_exclaim = "beeps" + pass_flags = PASSTABLE + + icon_state = "oldcomp" + icon_screen = "library" + icon_keyboard = null + 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 + var/upload_category = "Fiction" + var/list/checkouts = list() + 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/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" + +/obj/machinery/computer/bookmanagement/Initialize() + . = ..() + if(circuit) + circuit.name = "Book Inventory Management Console (Machine Board)" + circuit.build_path = /obj/machinery/computer/bookmanagement + +/obj/machinery/computer/bookmanagement/ui_interact(mob/user) + . = ..() + var/dat = "" // + switch(screenstate) + if(0) + // Main Menu + dat += "1. View General Inventory
                    " + dat += "2. View Checked Out Inventory
                    " + dat += "3. Check out a Book
                    " + dat += "4. Connect to External Archive
                    " + dat += "5. Upload New Title to Archive
                    " + dat += "6. Upload Scanned Title to Newscaster
                    " + dat += "7. Print Corporate Materials
                    " + if(obj_flags & EMAGGED) + dat += "8. Access the Forbidden Lore Vault
                    " + if(src.arcanecheckout) + print_forbidden_lore(user) + src.arcanecheckout = 0 + if(1) + // Inventory + dat += "

                    Inventory


                    " + for(var/obj/item/book/b in inventory) + dat += "[b.name] (Delete)
                    " + dat += "(Return to main menu)
                    " + if(2) + // Checked Out + dat += "

                    Checked Out Books


                    " + for(var/datum/borrowbook/b in checkouts) + var/timetaken = world.time - b.getdate + timetaken /= 600 + timetaken = round(timetaken) + var/timedue = b.duedate - world.time + timedue /= 600 + if(timedue <= 0) + timedue = "(OVERDUE) [timedue]" + else + timedue = round(timedue) + dat += "\"[b.bookname]\", Checked out to: [b.mobname]
                    --- Taken: [timetaken] minutes ago, Due: in [timedue] minutes
                    " + dat += "(Check In)

                    " + dat += "(Return to main menu)
                    " + if(3) + // Check Out a Book + dat += "

                    Check Out a Book


                    " + dat += "Book: [src.buffer_book] " + dat += "\[Edit\]
                    " + dat += "Recipient: [src.buffer_mob] " + dat += "\[Edit\]
                    " + dat += "Checkout Date : [world.time/600]
                    " + dat += "Due Date: [(world.time + checkoutperiod)/600]
                    " + dat += "(Checkout Period: [checkoutperiod] minutes) (+/-)" + dat += "(Commit Entry)
                    " + dat += "(Return to main menu)
                    " + if(4) + dat += "

                    External Archive

                    " + build_library_menu() + + if(!GLOB.cachedbooks) + dat += "ERROR: Unable to contact External Archive. Please contact your system administrator for assistance." + else + dat += "(Order book by SS13BN)

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

                    Upload a New Title

                    " + if(!scanner) + scanner = findscanner(9) + if(!scanner) + dat += "No scanner found within wireless network range.
                    " + else if(!scanner.cache) + dat += "No data found in scanner memory.
                    " + else + dat += "Data marked for upload...
                    " + dat += "Title: [scanner.cache.name]
                    " + if(!scanner.cache.author) + scanner.cache.author = "Anonymous" + dat += "Author: [scanner.cache.author]
                    " + dat += "Category: [upload_category]
                    " + dat += "\[Upload\]
                    " + dat += "(Return to main menu)
                    " + if(6) + dat += "

                    Post Title to Newscaster

                    " + if(!scanner) + scanner = findscanner(9) + if(!scanner) + dat += "No scanner found within wireless network range.
                    " + else if(!scanner.cache) + dat += "No data found in scanner memory.
                    " + else + dat += "Post [scanner.cache.name] to station newscasters?" + dat += "\[Post\]
                    " + dat += "(Return to main menu)
                    " + if(7) + dat += "

                    NTGanda(tm) Universal Printing Module

                    " + dat += "What would you like to print?
                    " + dat += "\[Bible\]
                    " + dat += "\[Poster\]
                    " + dat += "(Return to main menu)
                    " + if(8) + dat += "

                    Accessing Forbidden Lore Vault v 1.3

                    " + dat += "Are you absolutely sure you want to proceed? EldritchRelics Inc. takes no responsibilities for loss of sanity resulting from this action.

                    " + dat += "Yes.
                    " + dat += "No.
                    " + + var/datum/browser/popup = new(user, "library", name, 600, 400) + popup.set_content(dat) + popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) + popup.open() + +/obj/machinery/computer/bookmanagement/proc/findscanner(viewrange) + for(var/obj/machinery/libraryscanner/S in range(viewrange, get_turf(src))) + return S + return null + +/obj/machinery/computer/bookmanagement/proc/print_forbidden_lore(mob/user) + 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/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.") + else + return ..() + +/obj/machinery/computer/bookmanagement/emag_act(mob/user) + if(density && !(obj_flags & EMAGGED)) + obj_flags |= EMAGGED + +/obj/machinery/computer/bookmanagement/Topic(href, href_list) + if(..()) + usr << browse(null, "window=library") + onclose(usr, "library") + return + if(href_list["page"] && screenstate == 4) + page = text2num(href_list["page"]) + if(href_list["switchscreen"]) + switch(href_list["switchscreen"]) + if("0") + screenstate = 0 + if("1") + screenstate = 1 + if("2") + screenstate = 2 + if("3") + screenstate = 3 + if("4") + screenstate = 4 + if("5") + screenstate = 5 + if("6") + screenstate = 6 + if("7") + screenstate = 7 + if("8") + screenstate = 8 + if(href_list["arccheckout"]) + if(obj_flags & EMAGGED) + src.arcanecheckout = 1 + src.screenstate = 0 + if(href_list["increasetime"]) + checkoutperiod += 1 + if(href_list["decreasetime"]) + checkoutperiod -= 1 + if(checkoutperiod < 1) + checkoutperiod = 1 + if(href_list["editbook"]) + buffer_book = stripped_input(usr, "Enter the book's title:") + if(href_list["editmob"]) + buffer_mob = stripped_input(usr, "Enter the recipient's name:", max_length = MAX_NAME_LEN) + if(href_list["checkout"]) + var/datum/borrowbook/b = new /datum/borrowbook + b.bookname = sanitize(buffer_book) + b.mobname = sanitize(buffer_mob) + b.getdate = world.time + b.duedate = world.time + (checkoutperiod * 600) + checkouts.Add(b) + if(href_list["checkin"]) + var/datum/borrowbook/b = locate(href_list["checkin"]) in checkouts + if(b && istype(b)) + checkouts.Remove(b) + if(href_list["delbook"]) + var/obj/item/book/b = locate(href_list["delbook"]) in inventory + if(b && istype(b)) + inventory.Remove(b) + if(href_list["setauthor"]) + var/newauthor = stripped_input(usr, "Enter the author's name: ") + if(newauthor) + scanner.cache.author = newauthor + if(href_list["setcategory"]) + var/newcategory = input("Choose a category: ") in list("Fiction", "Non-Fiction", "Adult", "Reference", "Religion","Technical") + if(newcategory) + upload_category = newcategory + if(href_list["upload"]) + if(scanner) + if(scanner.cache) + var/choice = input("Are you certain you wish to upload this title to the Archive?") in list("Confirm", "Abort") + if(choice == "Confirm") + if (!SSdbcore.Connect()) + alert("Connection to Archive has been severed. Aborting.") + else + 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 (: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") + return + else + log_game(msg) + qdel(query_library_upload) + alert("Upload Complete. Uploaded title will be unavailable for printing for a short period") + if(href_list["newspost"]) + if(!GLOB.news_network) + alert("No news network found on station. Aborting.") + var/channelexists = 0 + for(var/datum/newscaster/feed_channel/FC in GLOB.news_network.network_channels) + if(FC.channel_name == "Nanotrasen Book Club") + channelexists = 1 + break + if(!channelexists) + GLOB.news_network.CreateFeedChannel("Nanotrasen Book Club", "Library", null) + 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) + say("Printer unavailable. Please allow a short time before attempting to print.") + else + var/orderid = input("Enter your order:") as num|null + if(orderid) + if(isnum(orderid) && ISINTEGER(orderid)) + href_list["targetid"] = num2text(orderid) + + if(href_list["targetid"]) + var/id = href_list["targetid"] + if (!SSdbcore.Connect()) + alert("Connection to Archive has been severed. Aborting.") + if(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=:id AND isnull(deleted)", + list("id" = id) + ) + if(!query_library_print.Execute()) + qdel(query_library_print) + say("PRINTER ERROR! Failed to print document (0x0000000F)") + return + while(query_library_print.NextRow()) + var/author = query_library_print.item[2] + var/title = query_library_print.item[3] + var/content = query_library_print.item[4] + if(!QDELETED(src)) + var/obj/item/book/B = new(get_turf(src)) + B.name = "Book: [title]" + B.title = title + 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?") + break + qdel(query_library_print) + if(href_list["printbible"]) + if(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 + else + say("Printer currently unavailable, please wait a moment.") + if(href_list["printposter"]) + if(cooldown < world.time) + new /obj/item/poster/random_official(src.loc) + cooldown = world.time + PRINTER_COOLDOWN + else + say("Printer currently unavailable, please wait a moment.") + add_fingerprint(usr) + updateUsrDialog() + +/* + * Library Scanner + */ +/obj/machinery/libraryscanner + name = "scanner control interface" + icon = 'icons/obj/library.dmi' + icon_state = "bigscanner" + desc = "It servers the purpose of scanning stuff." + density = TRUE + var/obj/item/book/cache // Last scanned book + +/obj/machinery/libraryscanner/attackby(obj/O, mob/user, params) + if(istype(O, /obj/item/book)) + if(!user.transferItemToLoc(O, src)) + return + else + return ..() + +/obj/machinery/libraryscanner/attack_hand(mob/user) + . = ..() + if(.) + return + usr.set_machine(src) + var/dat = "" // + if(cache) + dat += "Data stored in memory.
                    " + else + dat += "No data stored in memory.
                    " + dat += "\[Scan\]" + if(cache) + dat += " \[Clear Memory\]

                    \[Remove Book\]" + else + dat += "
                    " + var/datum/browser/popup = new(user, "scanner", name, 600, 400) + popup.set_content(dat) + popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) + popup.open() + +/obj/machinery/libraryscanner/Topic(href, href_list) + if(..()) + usr << browse(null, "window=scanner") + onclose(usr, "scanner") + return + + if(href_list["scan"]) + for(var/obj/item/book/B in contents) + cache = B + break + if(href_list["clear"]) + cache = null + if(href_list["eject"]) + for(var/obj/item/book/B in contents) + B.forceMove(drop_location()) + src.add_fingerprint(usr) + src.updateUsrDialog() + return + + +/* + * Book binder + */ +/obj/machinery/bookbinder + name = "book binder" + icon = 'icons/obj/library.dmi' + icon_state = "binder" + desc = "Only intended for binding paper products." + density = TRUE + var/busy = FALSE + +/obj/machinery/bookbinder/attackby(obj/O, mob/user, params) + if(istype(O, /obj/item/paper)) + bind_book(user, O) + else if(default_unfasten_wrench(user, O)) + return 1 + else + return ..() + +/obj/machinery/bookbinder/proc/bind_book(mob/user, obj/item/paper/P) + if(machine_stat) + return + if(busy) + to_chat(user, "The book binder is busy. Please wait for completion of previous operation.") + 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.") + busy = TRUE + sleep(rand(200,400)) + busy = FALSE + if(P) + if(!machine_stat) + 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)]" + B.icon_state = "book[rand(1,7)]" + qdel(P) + else + P.forceMove(drop_location()) diff --git a/code/modules/library/random_books.dm b/code/modules/library/random_books.dm index 48f40cad9f7e..bbdfdbc85511 100644 --- a/code/modules/library/random_books.dm +++ b/code/modules/library/random_books.dm @@ -1,91 +1,96 @@ -/obj/item/book/manual/random - icon_state = "random_book" - -/obj/item/book/manual/random/Initialize() - ..() - var/static/banned_books = list(/obj/item/book/manual/random, /obj/item/book/manual/nuclear, /obj/item/book/manual/wiki) - var/newtype = pick(subtypesof(/obj/item/book/manual) - banned_books) - new newtype(loc) - return INITIALIZE_HINT_QDEL - -/obj/item/book/random - icon_state = "random_book" - var/amount = 1 - var/category = null - -/obj/item/book/random/Initialize() - ..() - return INITIALIZE_HINT_LATELOAD - -/obj/item/book/random/LateInitialize() - create_random_books(amount, src.loc, TRUE, category) - qdel(src) - -/obj/item/book/random/triple - amount = 3 - -/obj/structure/bookcase/random - var/category = null - var/book_count = 2 - icon_state = "random_bookcase" - anchored = TRUE - state = 2 - -/obj/structure/bookcase/random/Initialize(mapload) - . = ..() - if(book_count && isnum(book_count)) - book_count += pick(-1,-1,0,1,1) - . = INITIALIZE_HINT_LATELOAD - -/obj/structure/bookcase/random/LateInitialize() - create_random_books(book_count, src, FALSE, category) - update_icon() - -/proc/create_random_books(amount = 2, location, fail_loud = FALSE, category = null) - . = 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() - 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 - 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] - B.name = "Book: [B.title]" - B.icon_state= "book[rand(1,8)]" - qdel(query_get_random_books) - -/obj/structure/bookcase/random/fiction - name = "bookcase (Fiction)" - category = "Fiction" -/obj/structure/bookcase/random/nonfiction - name = "bookcase (Non-Fiction)" - category = "Non-fiction" -/obj/structure/bookcase/random/religion - name = "bookcase (Religion)" - category = "Religion" -/obj/structure/bookcase/random/adult - name = "bookcase (Adult)" - category = "Adult" - -/obj/structure/bookcase/random/reference - name = "bookcase (Reference)" - category = "Reference" - var/ref_book_prob = 20 - -/obj/structure/bookcase/random/reference/Initialize(mapload) - . = ..() - while(book_count > 0 && prob(ref_book_prob)) - book_count-- - new /obj/item/book/manual/random(src) +/obj/item/book/manual/random + icon_state = "random_book" + +/obj/item/book/manual/random/Initialize() + ..() + var/static/banned_books = list(/obj/item/book/manual/random, /obj/item/book/manual/nuclear, /obj/item/book/manual/wiki) + var/newtype = pick(subtypesof(/obj/item/book/manual) - banned_books) + new newtype(loc) + return INITIALIZE_HINT_QDEL + +/obj/item/book/random + icon_state = "random_book" + var/amount = 1 + var/category = null + +/obj/item/book/random/Initialize() + ..() + return INITIALIZE_HINT_LATELOAD + +/obj/item/book/random/LateInitialize() + create_random_books(amount, src.loc, TRUE, category) + qdel(src) + +/obj/item/book/random/triple + amount = 3 + +/obj/structure/bookcase/random + var/category = null + var/book_count = 2 + icon_state = "random_bookcase" + anchored = TRUE + state = 2 + +/obj/structure/bookcase/random/Initialize(mapload) + . = ..() + if(book_count && isnum(book_count)) + book_count += pick(-1,-1,0,1,1) + . = INITIALIZE_HINT_LATELOAD + +/obj/structure/bookcase/random/LateInitialize() + create_random_books(book_count, src, FALSE, category) + update_icon() + +/proc/create_random_books(amount = 2, location, fail_loud = FALSE, category = null) + . = 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() + return + if(prob(25)) + category = null + var/datum/DBQuery/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[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)]" + qdel(query_get_random_books) + + +/obj/structure/bookcase/random/fiction + name = "bookcase (Fiction)" + category = "Fiction" +/obj/structure/bookcase/random/nonfiction + name = "bookcase (Non-Fiction)" + category = "Non-fiction" +/obj/structure/bookcase/random/religion + name = "bookcase (Religion)" + category = "Religion" +/obj/structure/bookcase/random/adult + name = "bookcase (Adult)" + category = "Adult" + +/obj/structure/bookcase/random/reference + name = "bookcase (Reference)" + category = "Reference" + var/ref_book_prob = 20 + +/obj/structure/bookcase/random/reference/Initialize(mapload) + . = ..() + while(book_count > 0 && prob(ref_book_prob)) + book_count-- + new /obj/item/book/manual/random(src) diff --git a/code/modules/library/soapstone.dm b/code/modules/library/soapstone.dm index 3f1ea429e547..f17040a93841 100644 --- a/code/modules/library/soapstone.dm +++ b/code/modules/library/soapstone.dm @@ -204,10 +204,13 @@ /obj/structure/chisel_message/interact() return -/obj/structure/chisel_message/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.always_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/structure/chisel_message/ui_state(mob/user) + return GLOB.always_state + +/obj/structure/chisel_message/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "EngravedMessage", name, 600, 300, master_ui, state) + ui = new(user, src, "EngravedMessage", name) ui.open() /obj/structure/chisel_message/ui_data(mob/user) diff --git a/code/modules/mapping/ruins.dm b/code/modules/mapping/ruins.dm index f8302c7c5eb4..db361cc19569 100644 --- a/code/modules/mapping/ruins.dm +++ b/code/modules/mapping/ruins.dm @@ -1,173 +1,173 @@ -/datum/map_template/ruin/proc/try_to_place(z,allowed_areas,turf/forced_turf) - var/sanity = forced_turf ? 1 : PLACEMENT_TRIES - if(SSmapping.level_trait(z,ZTRAIT_ISOLATED_RUINS)) - return place_on_isolated_level(z) - while(sanity > 0) - sanity-- - var/width_border = TRANSITIONEDGE + SPACERUIN_MAP_EDGE_PAD + round(width / 2) - var/height_border = TRANSITIONEDGE + SPACERUIN_MAP_EDGE_PAD + round(height / 2) - var/turf/central_turf = forced_turf ? forced_turf : locate(rand(width_border, world.maxx - width_border), rand(height_border, world.maxy - height_border), z) - var/valid = TRUE - - for(var/turf/check in get_affected_turfs(central_turf,1)) - var/area/new_area = get_area(check) - if(!(istype(new_area, allowed_areas)) || check.flags_1 & NO_RUINS_1) - valid = FALSE - break - - if(!valid) - continue - - testing("Ruin \"[name]\" placed at ([central_turf.x], [central_turf.y], [central_turf.z])") - - for(var/i in get_affected_turfs(central_turf, 1)) - var/turf/T = i - for(var/obj/structure/spawner/nest in T) - qdel(nest) - for(var/mob/living/simple_animal/monster in T) - qdel(monster) - for(var/obj/structure/flora/ash/plant in T) - qdel(plant) - - load(central_turf,centered = TRUE) - loaded++ - - for(var/turf/T in get_affected_turfs(central_turf, 1)) - T.flags_1 |= NO_RUINS_1 - - new /obj/effect/landmark/ruin(central_turf, src) - return central_turf - -/datum/map_template/ruin/proc/place_on_isolated_level(z) - var/datum/turf_reservation/reservation = SSmapping.RequestBlockReservation(width, height, z) //Make the new level creation work with different traits. - if(!reservation) - return - var/turf/placement = locate(reservation.bottom_left_coords[1],reservation.bottom_left_coords[2],reservation.bottom_left_coords[3]) - load(placement) - loaded++ - for(var/turf/T in get_affected_turfs(placement)) - T.flags_1 |= NO_RUINS_1 - var/turf/center = locate(placement.x + round(width/2),placement.y + round(height/2),placement.z) - new /obj/effect/landmark/ruin(center, src) - return center - - -/proc/seedRuins(list/z_levels = null, budget = 0, whitelist = /area/space, list/potentialRuins) - if(!z_levels || !z_levels.len) - WARNING("No Z levels provided - Not generating ruins") - return - - for(var/zl in z_levels) - var/turf/T = locate(1, 1, zl) - if(!T) - WARNING("Z level [zl] does not exist - Not generating ruins") - return - - var/list/ruins = potentialRuins.Copy() - - var/list/forced_ruins = list() //These go first on the z level associated (same random one by default) or if the assoc value is a turf to the specified turf. - var/list/ruins_availible = list() //we can try these in the current pass - - //Set up the starting ruin list - for(var/key in ruins) - var/datum/map_template/ruin/R = ruins[key] - if(R.cost > budget) //Why would you do that - continue - if(R.always_place) - forced_ruins[R] = -1 - if(R.unpickable) - continue - ruins_availible[R] = R.placement_weight - while(budget > 0 && (ruins_availible.len || forced_ruins.len)) - var/datum/map_template/ruin/current_pick - var/forced = FALSE - var/forced_z //If set we won't pick z level and use this one instead. - var/forced_turf //If set we place the ruin centered on the given turf - if(forced_ruins.len) //We have something we need to load right now, so just pick it - for(var/ruin in forced_ruins) - current_pick = ruin - if(isturf(forced_ruins[ruin])) - var/turf/T = forced_ruins[ruin] - forced_z = T.z //In case of chained ruins - forced_turf = T - else if(forced_ruins[ruin] > 0) //Load into designated z - forced_z = forced_ruins[ruin] - forced = TRUE - break - else //Otherwise just pick random one - current_pick = pickweight(ruins_availible) - - var/placement_tries = forced_turf ? 1 : PLACEMENT_TRIES //Only try once if we target specific turf - var/failed_to_place = TRUE - var/target_z = 0 - var/turf/placed_turf //Where the ruin ended up if we succeeded - outer: - while(placement_tries > 0) - placement_tries-- - target_z = pick(z_levels) - if(forced_z) - target_z = forced_z - if(current_pick.always_spawn_with) //If the ruin has part below, make sure that z exists. - for(var/v in current_pick.always_spawn_with) - if(current_pick.always_spawn_with[v] == PLACE_BELOW) - var/turf/T = locate(1,1,target_z) - if(!SSmapping.get_turf_below(T)) - if(forced_z) - continue outer - else - break outer - - placed_turf = current_pick.try_to_place(target_z,whitelist,forced_turf) - if(!placed_turf) - continue - else - failed_to_place = FALSE - break - - //That's done remove from priority even if it failed - if(forced) - //TODO : handle forced ruins with multiple variants - forced_ruins -= current_pick - forced = FALSE - - if(failed_to_place) - for(var/datum/map_template/ruin/R in ruins_availible) - if(R.id == current_pick.id) - ruins_availible -= R - log_world("Failed to place [current_pick.name] ruin.") - else - budget -= current_pick.cost - if(!current_pick.allow_duplicates) - for(var/datum/map_template/ruin/R in ruins_availible) - if(R.id == current_pick.id) - ruins_availible -= R - if(current_pick.never_spawn_with) - for(var/blacklisted_type in current_pick.never_spawn_with) - for(var/possible_exclusion in ruins_availible) - if(istype(possible_exclusion,blacklisted_type)) - ruins_availible -= possible_exclusion - if(current_pick.always_spawn_with) - for(var/v in current_pick.always_spawn_with) - for(var/ruin_name in SSmapping.ruins_templates) //Because we might want to add space templates as linked of lava templates. - var/datum/map_template/ruin/linked = SSmapping.ruins_templates[ruin_name] //why are these assoc, very annoying. - if(istype(linked,v)) - switch(current_pick.always_spawn_with[v]) - if(PLACE_SAME_Z) - forced_ruins[linked] = target_z //I guess you might want a chain somehow - if(PLACE_LAVA_RUIN) - forced_ruins[linked] = pick(SSmapping.levels_by_trait(ZTRAIT_LAVA_RUINS)) - if(PLACE_SPACE_RUIN) - forced_ruins[linked] = pick(SSmapping.levels_by_trait(ZTRAIT_SPACE_RUINS)) - if(PLACE_DEFAULT) - forced_ruins[linked] = -1 - if(PLACE_BELOW) - forced_ruins[linked] = SSmapping.get_turf_below(placed_turf) - if(PLACE_ISOLATED) - forced_ruins[linked] = SSmapping.get_isolated_ruin_z() - - //Update the availible list - for(var/datum/map_template/ruin/R in ruins_availible) - if(R.cost > budget) - ruins_availible -= R - - log_world("Ruin loader finished with [budget] left to spend.") +/datum/map_template/ruin/proc/try_to_place(z,allowed_areas,turf/forced_turf) + var/sanity = forced_turf ? 1 : PLACEMENT_TRIES + if(SSmapping.level_trait(z,ZTRAIT_ISOLATED_RUINS)) + return place_on_isolated_level(z) + while(sanity > 0) + sanity-- + var/width_border = TRANSITIONEDGE + SPACERUIN_MAP_EDGE_PAD + round(width / 2) + var/height_border = TRANSITIONEDGE + SPACERUIN_MAP_EDGE_PAD + round(height / 2) + var/turf/central_turf = forced_turf ? forced_turf : locate(rand(width_border, world.maxx - width_border), rand(height_border, world.maxy - height_border), z) + var/valid = TRUE + + for(var/turf/check in get_affected_turfs(central_turf,1)) + var/area/new_area = get_area(check) + if(!(istype(new_area, allowed_areas)) || check.flags_1 & NO_RUINS_1) + valid = FALSE + break + + if(!valid) + continue + + testing("Ruin \"[name]\" placed at ([central_turf.x], [central_turf.y], [central_turf.z])") + + for(var/i in get_affected_turfs(central_turf, 1)) + var/turf/T = i + for(var/obj/structure/spawner/nest in T) + qdel(nest) + for(var/mob/living/simple_animal/monster in T) + qdel(monster) + for(var/obj/structure/flora/ash/plant in T) + qdel(plant) + + load(central_turf,centered = TRUE) + loaded++ + + for(var/turf/T in get_affected_turfs(central_turf, 1)) + T.flags_1 |= NO_RUINS_1 + + new /obj/effect/landmark/ruin(central_turf, src) + return central_turf + +/datum/map_template/ruin/proc/place_on_isolated_level(z) + var/datum/turf_reservation/reservation = SSmapping.RequestBlockReservation(width, height, z) //Make the new level creation work with different traits. + if(!reservation) + return + var/turf/placement = locate(reservation.bottom_left_coords[1],reservation.bottom_left_coords[2],reservation.bottom_left_coords[3]) + load(placement) + loaded++ + for(var/turf/T in get_affected_turfs(placement)) + T.flags_1 |= NO_RUINS_1 + var/turf/center = locate(placement.x + round(width/2),placement.y + round(height/2),placement.z) + new /obj/effect/landmark/ruin(center, src) + return center + + +/proc/seedRuins(list/z_levels = null, budget = 0, whitelist = /area/space, list/potentialRuins) + if(!z_levels || !z_levels.len) + WARNING("No Z levels provided - Not generating ruins") + return + + for(var/zl in z_levels) + var/turf/T = locate(1, 1, zl) + if(!T) + WARNING("Z level [zl] does not exist - Not generating ruins") + return + + var/list/ruins = potentialRuins.Copy() + + var/list/forced_ruins = list() //These go first on the z level associated (same random one by default) or if the assoc value is a turf to the specified turf. + var/list/ruins_availible = list() //we can try these in the current pass + + //Set up the starting ruin list + for(var/key in ruins) + var/datum/map_template/ruin/R = ruins[key] + if(R.cost > budget) //Why would you do that + continue + if(R.always_place) + forced_ruins[R] = -1 + if(R.unpickable) + continue + ruins_availible[R] = R.placement_weight + while(budget > 0 && (ruins_availible.len || forced_ruins.len)) + var/datum/map_template/ruin/current_pick + var/forced = FALSE + var/forced_z //If set we won't pick z level and use this one instead. + var/forced_turf //If set we place the ruin centered on the given turf + if(forced_ruins.len) //We have something we need to load right now, so just pick it + for(var/ruin in forced_ruins) + current_pick = ruin + if(isturf(forced_ruins[ruin])) + var/turf/T = forced_ruins[ruin] + forced_z = T.z //In case of chained ruins + forced_turf = T + else if(forced_ruins[ruin] > 0) //Load into designated z + forced_z = forced_ruins[ruin] + forced = TRUE + break + else //Otherwise just pick random one + current_pick = pickweight(ruins_availible) + + var/placement_tries = forced_turf ? 1 : PLACEMENT_TRIES //Only try once if we target specific turf + var/failed_to_place = TRUE + var/target_z = 0 + var/turf/placed_turf //Where the ruin ended up if we succeeded + outer: + while(placement_tries > 0) + placement_tries-- + target_z = pick(z_levels) + if(forced_z) + target_z = forced_z + if(current_pick.always_spawn_with) //If the ruin has part below, make sure that z exists. + for(var/v in current_pick.always_spawn_with) + if(current_pick.always_spawn_with[v] == PLACE_BELOW) + var/turf/T = locate(1,1,target_z) + if(!SSmapping.get_turf_below(T)) + if(forced_z) + continue outer + else + break outer + + placed_turf = current_pick.try_to_place(target_z,whitelist,forced_turf) + if(!placed_turf) + continue + else + failed_to_place = FALSE + break + + //That's done remove from priority even if it failed + if(forced) + //TODO : handle forced ruins with multiple variants + forced_ruins -= current_pick + forced = FALSE + + if(failed_to_place) + for(var/datum/map_template/ruin/R in ruins_availible) + if(R.id == current_pick.id) + ruins_availible -= R + log_world("Failed to place [current_pick.name] ruin.") + else + budget -= current_pick.cost + if(!current_pick.allow_duplicates) + for(var/datum/map_template/ruin/R in ruins_availible) + if(R.id == current_pick.id) + ruins_availible -= R + if(current_pick.never_spawn_with) + for(var/blacklisted_type in current_pick.never_spawn_with) + for(var/possible_exclusion in ruins_availible) + if(istype(possible_exclusion,blacklisted_type)) + ruins_availible -= possible_exclusion + if(current_pick.always_spawn_with) + for(var/v in current_pick.always_spawn_with) + for(var/ruin_name in SSmapping.ruins_templates) //Because we might want to add space templates as linked of lava templates. + var/datum/map_template/ruin/linked = SSmapping.ruins_templates[ruin_name] //why are these assoc, very annoying. + if(istype(linked,v)) + switch(current_pick.always_spawn_with[v]) + if(PLACE_SAME_Z) + forced_ruins[linked] = target_z //I guess you might want a chain somehow + if(PLACE_LAVA_RUIN) + forced_ruins[linked] = pick(SSmapping.levels_by_trait(ZTRAIT_LAVA_RUINS)) + if(PLACE_SPACE_RUIN) + forced_ruins[linked] = pick(SSmapping.levels_by_trait(ZTRAIT_SPACE_RUINS)) + if(PLACE_DEFAULT) + forced_ruins[linked] = -1 + if(PLACE_BELOW) + forced_ruins[linked] = SSmapping.get_turf_below(placed_turf) + if(PLACE_ISOLATED) + forced_ruins[linked] = SSmapping.get_isolated_ruin_z() + + //Update the availible list + for(var/datum/map_template/ruin/R in ruins_availible) + if(R.cost > budget) + ruins_availible -= R + + log_world("Ruin loader finished with [budget] left to spend.") diff --git a/code/modules/mapping/space_management/multiz_helpers.dm b/code/modules/mapping/space_management/multiz_helpers.dm index 04415d0c1fd6..ca7cd8d52042 100644 --- a/code/modules/mapping/space_management/multiz_helpers.dm +++ b/code/modules/mapping/space_management/multiz_helpers.dm @@ -1,47 +1,47 @@ -/proc/get_step_multiz(ref, dir) - if(dir & UP) - dir &= ~UP - return get_step(SSmapping.get_turf_above(get_turf(ref)), dir) - if(dir & DOWN) - dir &= ~DOWN - return get_step(SSmapping.get_turf_below(get_turf(ref)), dir) - return get_step(ref, dir) - -/proc/get_dir_multiz(turf/us, turf/them) - us = get_turf(us) - them = get_turf(them) - if(!us || !them) - return NONE - if(us.z == them.z) - return get_dir(us, them) - else - var/turf/T = us.above() - var/dir = NONE - if(T && (T.z == them.z)) - dir = UP - else - T = us.below() - if(T && (T.z == them.z)) - dir = DOWN - else - return get_dir(us, them) - return (dir | get_dir(us, them)) - -/turf/proc/above() - return get_step_multiz(src, UP) - -/turf/proc/below() - return get_step_multiz(src, DOWN) - -/proc/dir_inverse_multiz(dir) - var/holder = dir & (UP|DOWN) - if((holder == NONE) || (holder == (UP|DOWN))) - return turn(dir, 180) - dir &= ~(UP|DOWN) - dir = turn(dir, 180) - if(holder == UP) - holder = DOWN - else - holder = UP - dir |= holder - return dir +/proc/get_step_multiz(ref, dir) + if(dir & UP) + dir &= ~UP + return get_step(SSmapping.get_turf_above(get_turf(ref)), dir) + if(dir & DOWN) + dir &= ~DOWN + return get_step(SSmapping.get_turf_below(get_turf(ref)), dir) + return get_step(ref, dir) + +/proc/get_dir_multiz(turf/us, turf/them) + us = get_turf(us) + them = get_turf(them) + if(!us || !them) + return NONE + if(us.z == them.z) + return get_dir(us, them) + else + var/turf/T = us.above() + var/dir = NONE + if(T && (T.z == them.z)) + dir = UP + else + T = us.below() + if(T && (T.z == them.z)) + dir = DOWN + else + return get_dir(us, them) + return (dir | get_dir(us, them)) + +/turf/proc/above() + return get_step_multiz(src, UP) + +/turf/proc/below() + return get_step_multiz(src, DOWN) + +/proc/dir_inverse_multiz(dir) + var/holder = dir & (UP|DOWN) + if((holder == NONE) || (holder == (UP|DOWN))) + return turn(dir, 180) + dir &= ~(UP|DOWN) + dir = turn(dir, 180) + if(holder == UP) + holder = DOWN + else + holder = UP + dir |= holder + return dir diff --git a/code/modules/mapping/space_management/space_reservation.dm b/code/modules/mapping/space_management/space_reservation.dm index f1b3b1ccdc5d..265d2c996d0f 100644 --- a/code/modules/mapping/space_management/space_reservation.dm +++ b/code/modules/mapping/space_management/space_reservation.dm @@ -1,74 +1,74 @@ - -//Yes, they can only be rectangular. -//Yes, I'm sorry. -/datum/turf_reservation - var/list/reserved_turfs = list() - var/width = 0 - var/height = 0 - var/bottom_left_coords[3] - var/top_right_coords[3] - var/wipe_reservation_on_release = TRUE - var/turf_type = /turf/open/space - -/datum/turf_reservation/transit - turf_type = /turf/open/space/transit - -/datum/turf_reservation/proc/Release() - var/v = reserved_turfs.Copy() - for(var/i in reserved_turfs) - reserved_turfs -= i - SSmapping.used_turfs -= i - SSmapping.reserve_turfs(v) - -/datum/turf_reservation/proc/Reserve(width, height, zlevel) - if(width > world.maxx || height > world.maxy || width < 1 || height < 1) - return FALSE - var/list/avail = SSmapping.unused_turfs["[zlevel]"] - var/turf/BL - var/turf/TR - var/list/turf/final = list() - var/passing = FALSE - for(var/i in avail) - CHECK_TICK - BL = i - if(!(BL.flags_1 & UNUSED_RESERVATION_TURF_1)) - continue - if(BL.x + width > world.maxx || BL.y + height > world.maxy) - continue - TR = locate(BL.x + width - 1, BL.y + height - 1, BL.z) - if(!(TR.flags_1 & UNUSED_RESERVATION_TURF_1)) - continue - final = block(BL, TR) - if(!final) - continue - passing = TRUE - for(var/I in final) - var/turf/checking = I - if(!(checking.flags_1 & UNUSED_RESERVATION_TURF_1)) - passing = FALSE - break - if(!passing) - continue - break - if(!passing || !istype(BL) || !istype(TR)) - return FALSE - bottom_left_coords = list(BL.x, BL.y, BL.z) - top_right_coords = list(TR.x, TR.y, TR.z) - for(var/i in final) - var/turf/T = i - reserved_turfs |= T - T.flags_1 &= ~UNUSED_RESERVATION_TURF_1 - SSmapping.unused_turfs["[T.z]"] -= T - SSmapping.used_turfs[T] = src - T.ChangeTurf(turf_type, turf_type) - src.width = width - src.height = height - return TRUE - -/datum/turf_reservation/New() - LAZYADD(SSmapping.turf_reservations, src) - -/datum/turf_reservation/Destroy() - Release() - LAZYREMOVE(SSmapping.turf_reservations, src) - return ..() + +//Yes, they can only be rectangular. +//Yes, I'm sorry. +/datum/turf_reservation + var/list/reserved_turfs = list() + var/width = 0 + var/height = 0 + var/bottom_left_coords[3] + var/top_right_coords[3] + var/wipe_reservation_on_release = TRUE + var/turf_type = /turf/open/space + +/datum/turf_reservation/transit + turf_type = /turf/open/space/transit + +/datum/turf_reservation/proc/Release() + var/v = reserved_turfs.Copy() + for(var/i in reserved_turfs) + reserved_turfs -= i + SSmapping.used_turfs -= i + SSmapping.reserve_turfs(v) + +/datum/turf_reservation/proc/Reserve(width, height, zlevel) + if(width > world.maxx || height > world.maxy || width < 1 || height < 1) + return FALSE + var/list/avail = SSmapping.unused_turfs["[zlevel]"] + var/turf/BL + var/turf/TR + var/list/turf/final = list() + var/passing = FALSE + for(var/i in avail) + CHECK_TICK + BL = i + if(!(BL.flags_1 & UNUSED_RESERVATION_TURF_1)) + continue + if(BL.x + width > world.maxx || BL.y + height > world.maxy) + continue + TR = locate(BL.x + width - 1, BL.y + height - 1, BL.z) + if(!(TR.flags_1 & UNUSED_RESERVATION_TURF_1)) + continue + final = block(BL, TR) + if(!final) + continue + passing = TRUE + for(var/I in final) + var/turf/checking = I + if(!(checking.flags_1 & UNUSED_RESERVATION_TURF_1)) + passing = FALSE + break + if(!passing) + continue + break + if(!passing || !istype(BL) || !istype(TR)) + return FALSE + bottom_left_coords = list(BL.x, BL.y, BL.z) + top_right_coords = list(TR.x, TR.y, TR.z) + for(var/i in final) + var/turf/T = i + reserved_turfs |= T + T.flags_1 &= ~UNUSED_RESERVATION_TURF_1 + SSmapping.unused_turfs["[T.z]"] -= T + SSmapping.used_turfs[T] = src + T.ChangeTurf(turf_type, turf_type) + src.width = width + src.height = height + return TRUE + +/datum/turf_reservation/New() + LAZYADD(SSmapping.turf_reservations, src) + +/datum/turf_reservation/Destroy() + Release() + LAZYREMOVE(SSmapping.turf_reservations, src) + return ..() diff --git a/code/modules/mentor/verbs/mentor_memo.dm b/code/modules/mentor/verbs/mentor_memo.dm index f5f8c4c25902..c7887e39dad4 100644 --- a/code/modules/mentor/verbs/mentor_memo.dm +++ b/code/modules/mentor/verbs/mentor_memo.dm @@ -27,10 +27,11 @@ if(!SSdbcore.IsConnected()) to_chat(src, "Failed to establish database connection.") return - var/sql_ckey = sanitizeSQL(src.ckey) switch(task) if("Write") - var/datum/DBQuery/query_memocheck = SSdbcore.NewQuery("SELECT ckey FROM [format_table_name("mentor_memo")] WHERE ckey = '[sql_ckey]'") + var/datum/DBQuery/query_memocheck = SSdbcore.NewQuery( + "SELECT ckey FROM [format_table_name("mentor_memo")] WHERE ckey = :ckey", + list("ckey" = src.ckey)) if(!query_memocheck.Execute()) var/err = query_memocheck.ErrorMsg() log_game("SQL ERROR obtaining ckey from memo table. Error : \[[err]\]\n") @@ -41,9 +42,9 @@ var/memotext = input(src,"Write your Memo","Memo") as message if(!memotext) return - memotext = sanitizeSQL(memotext) - var/timestamp = SQLtime() - var/datum/DBQuery/query_memoadd = SSdbcore.NewQuery("INSERT INTO [format_table_name("mentor_memo")] (ckey, memotext, timestamp) VALUES ('[sql_ckey]', '[memotext]', '[timestamp]')") + var/datum/DBQuery/query_memoadd = SSdbcore.NewQuery( + "INSERT INTO [format_table_name("mentor_memo")] (ckey, memotext, timestamp) VALUES (:ckey, :memotext, :timestamp)", + list("ckey" = src.ckey, "memotext" = memotext, "timestamp" = SQLtime())) if(!query_memoadd.Execute()) var/err = query_memoadd.ErrorMsg() log_game("SQL ERROR adding new memo. Error : \[[err]\]\n") @@ -68,8 +69,9 @@ var/target_ckey = input(src, "Select whose memo to edit", "Select memo") as null|anything in memolist if(!target_ckey) return - var/target_sql_ckey = sanitizeSQL(target_ckey) - var/datum/DBQuery/query_memofind = SSdbcore.NewQuery("SELECT memotext FROM [format_table_name("mentor_memo")] WHERE ckey = '[target_sql_ckey]'") + var/datum/DBQuery/query_memofind = SSdbcore.NewQuery( + "SELECT memotext FROM [format_table_name("mentor_memo")] WHERE ckey = :target_ckey", + list("target_ckey" = target_ckey)) if(!query_memofind.Execute()) var/err = query_memofind.ErrorMsg() log_game("SQL ERROR obtaining memotext from memo table. Error : \[[err]\]\n") @@ -79,20 +81,16 @@ var/new_memo = input("Input new memo", "New Memo", "[old_memo]", null) as message if(!new_memo) return - new_memo = sanitizeSQL(new_memo) - var/edit_text = "Edited by [sql_ckey] on [SQLtime()] from
                    [old_memo]
                    to
                    [new_memo]


                    " - edit_text = sanitizeSQL(edit_text) - var/datum/DBQuery/update_query = SSdbcore.NewQuery("UPDATE [format_table_name("mentor_memo")] SET memotext = '[new_memo]', last_editor = '[sql_ckey]', edits = CONCAT(IFNULL(edits,''),'[edit_text]') WHERE ckey = '[target_sql_ckey]'") + var/edit_text = "Edited by [src.ckey] on [SQLtime()] from
                    [old_memo]
                    to
                    [new_memo]
                    " + var/datum/DBQuery/update_query = SSdbcore.NewQuery( + "UPDATE [format_table_name("mentor_memo")] SET memotext = :new_memo, last_editor = :ckey, edits = CONCAT(IFNULL(edits,''),:edit_text) WHERE ckey = :target_ckey", + list("ckey" = src.ckey, "target_ckey" = target_ckey, "new_memo" = new_memo, "edit_text" = edit_text)) if(!update_query.Execute()) var/err = update_query.ErrorMsg() log_game("SQL ERROR editing memo. Error : \[[err]\]\n") return - if(target_sql_ckey == sql_ckey) - log_admin("[key_name(src)] has edited their mentor memo from [old_memo] to [new_memo]") - message_admins("[key_name_admin(src)] has edited their mentor memo from
                    [old_memo]
                    to
                    [new_memo]") - else - log_admin("[key_name(src)] has edited [target_sql_ckey]'s mentor memo from [old_memo] to [new_memo]") - message_admins("[key_name_admin(src)] has edited [target_sql_ckey]'s mentor memo from
                    [old_memo]
                    to
                    [new_memo]") + log_admin("[key_name(src)] has edited [target_ckey == src.ckey ? "their" : "[target_ckey]'s'"] mentor memo from [old_memo] to [new_memo]") + message_admins("[key_name_admin(src)] has edited [target_ckey == src.ckey ? "their" : "[target_ckey]'s'"] mentor memo from
                    [old_memo]
                    to
                    [new_memo]") qdel(update_query) qdel(query_memolist) qdel(query_memofind) @@ -116,6 +114,7 @@ to_chat(src, "No memos found in database.") return to_chat(src, output) + qdel(query_memoshow) if("Remove") var/datum/DBQuery/query_memodellist = SSdbcore.NewQuery("SELECT ckey FROM [format_table_name("mentor_memo")]") if(!query_memodellist.Execute()) @@ -132,17 +131,14 @@ var/target_ckey = input(src, "Select whose mentor memo to delete", "Select mentor memo") as null|anything in memolist if(!target_ckey) return - var/target_sql_ckey = sanitizeSQL(target_ckey) - var/datum/DBQuery/query_memodel = SSdbcore.NewQuery("DELETE FROM [format_table_name("memo")] WHERE ckey = '[target_sql_ckey]'") + var/datum/DBQuery/query_memodel = SSdbcore.NewQuery( + "DELETE FROM [format_table_name("memo")] WHERE ckey = ':target_ckey'", + list("target_ckey" = target_ckey)) if(!query_memodel.Execute()) var/err = query_memodel.ErrorMsg() log_game("SQL ERROR removing memo. Error : \[[err]\]\n") return - if(target_sql_ckey == sql_ckey) - log_admin("[key_name(src)] has removed their mentor memo.") - message_admins("[key_name_admin(src)] has removed their mentor memo.") - else - log_admin("[key_name(src)] has removed [target_sql_ckey]'s mentor memo.") - message_admins("[key_name_admin(src)] has removed [target_sql_ckey]'s mentor memo.") + log_admin("[key_name(src)] has removed [target_ckey == src.ckey ? "their" : "[target_ckey]'s'"] mentor memo.") + message_admins("[key_name_admin(src)] has removed [target_ckey == src.ckey ? "their" : "[target_ckey]'s'"]'s mentor memo.") qdel(query_memodellist) qdel(query_memodel) diff --git a/code/modules/mining/equipment/mining_tools.dm b/code/modules/mining/equipment/mining_tools.dm index 9e7e8cda1ebe..8eb03de9276a 100644 --- a/code/modules/mining/equipment/mining_tools.dm +++ b/code/modules/mining/equipment/mining_tools.dm @@ -1,165 +1,165 @@ -/*****************Pickaxes & Drills & Shovels****************/ -/obj/item/pickaxe - name = "pickaxe" - icon = 'icons/obj/mining.dmi' - icon_state = "pickaxe" - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_BACK - force = 15 - throwforce = 10 - item_state = "pickaxe" - lefthand_file = 'icons/mob/inhands/equipment/mining_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/mining_righthand.dmi' - w_class = WEIGHT_CLASS_BULKY - custom_materials = list(/datum/material/iron=2000) //one sheet, but where can you make them? - tool_behaviour = TOOL_MINING - toolspeed = 1 - usesound = list('sound/effects/picaxe1.ogg', 'sound/effects/picaxe2.ogg', 'sound/effects/picaxe3.ogg') - attack_verb = list("hit", "pierced", "sliced", "attacked") - -/obj/item/pickaxe/suicide_act(mob/living/user) - user.visible_message("[user] begins digging into [user.p_their()] chest! It looks like [user.p_theyre()] trying to commit suicide!") - if(use_tool(user, user, 30, volume=50)) - return BRUTELOSS - user.visible_message("[user] couldn't do it!") - return SHAME - -/obj/item/pickaxe/rusted - name = "rusty pickaxe" - desc = "A pickaxe that's been left to rust." - attack_verb = list("ineffectively hit") - force = 1 - throwforce = 1 - -/obj/item/pickaxe/mini - name = "compact pickaxe" - desc = "A smaller, compact version of the standard pickaxe." - icon_state = "minipick" - force = 10 - throwforce = 7 - slot_flags = ITEM_SLOT_BELT - w_class = WEIGHT_CLASS_NORMAL - custom_materials = list(/datum/material/iron=1000) - -/obj/item/pickaxe/silver - name = "silver-plated pickaxe" - icon_state = "spickaxe" - item_state = "spickaxe" - toolspeed = 0.5 //mines faster than a normal pickaxe, bought from mining vendor - desc = "A silver-plated pickaxe that mines slightly faster than standard-issue." - force = 17 - -/obj/item/pickaxe/diamond - name = "diamond-tipped pickaxe" - icon_state = "dpickaxe" - item_state = "dpickaxe" - toolspeed = 0.3 - desc = "A pickaxe with a diamond pick head. Extremely robust at cracking rock walls and digging up dirt." - force = 19 - -/obj/item/pickaxe/drill - name = "mining drill" - icon_state = "handdrill" - item_state = "jackhammer" - slot_flags = ITEM_SLOT_BELT - toolspeed = 0.6 //available from roundstart, faster than a pickaxe. - usesound = 'sound/weapons/drill.ogg' - hitsound = 'sound/weapons/drill.ogg' - desc = "An electric mining drill for the especially scrawny." - -/obj/item/pickaxe/drill/cyborg - name = "cyborg mining drill" - desc = "An integrated electric mining drill." - flags_1 = NONE - -/obj/item/pickaxe/drill/cyborg/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CYBORG_ITEM_TRAIT) - -/obj/item/pickaxe/drill/diamonddrill - name = "diamond-tipped mining drill" - icon_state = "diamonddrill" - toolspeed = 0.2 - desc = "Yours is the drill that will pierce the heavens!" - -/obj/item/pickaxe/drill/cyborg/diamond //This is the BORG version! - name = "diamond-tipped cyborg mining drill" //To inherit the NODROP_1 flag, and easier to change borg specific drill mechanics. - icon_state = "diamonddrill" - toolspeed = 0.2 - -/obj/item/pickaxe/drill/jackhammer - name = "sonic jackhammer" - icon_state = "jackhammer" - item_state = "jackhammer" - toolspeed = 0.1 //the epitome of powertools. extremely fast mining - usesound = 'sound/weapons/sonic_jackhammer.ogg' - hitsound = 'sound/weapons/sonic_jackhammer.ogg' - desc = "Cracks rocks with sonic blasts." - -/obj/item/pickaxe/improvised - name = "improvised pickaxe" - desc = "A pickaxe made with a knife and crowbar taped together, how does it not break?" - icon_state = "ipickaxe" - item_state = "ipickaxe" - force = 10 - throwforce = 7 - toolspeed = 3 //3 times slower than a normal pickaxe - slot_flags = ITEM_SLOT_BELT - w_class = WEIGHT_CLASS_NORMAL - custom_materials = list(/datum/material/iron=12050) //metal needed for a crowbar and for a knife, why the FUCK does a knife cost 6 metal sheets while a crowbar costs 0.025 sheets? shit makes no sense fuck this - -/obj/item/shovel - name = "shovel" - desc = "A large tool for digging and moving dirt." - icon = 'icons/obj/mining.dmi' - icon_state = "shovel" - lefthand_file = 'icons/mob/inhands/equipment/mining_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/mining_righthand.dmi' - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BELT - force = 8 - tool_behaviour = TOOL_SHOVEL - toolspeed = 1 - usesound = 'sound/effects/shovel_dig.ogg' - throwforce = 4 - item_state = "shovel" - w_class = WEIGHT_CLASS_NORMAL - custom_materials = list(/datum/material/iron=50) - attack_verb = list("bashed", "bludgeoned", "thrashed", "whacked") - sharpness = IS_SHARP - -/obj/item/shovel/Initialize() - . = ..() - AddComponent(/datum/component/butchering, 150, 40) //it's sharp, so it works, but barely. - -/obj/item/shovel/suicide_act(mob/living/user) - user.visible_message("[user] begins digging their own grave! It looks like [user.p_theyre()] trying to commit suicide!") - if(use_tool(user, user, 30, volume=50)) - return BRUTELOSS - user.visible_message("[user] couldn't do it!") - return SHAME - -/obj/item/shovel/spade - name = "spade" - desc = "A small tool for digging and moving dirt." - icon_state = "spade" - item_state = "spade" - lefthand_file = 'icons/mob/inhands/equipment/hydroponics_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/hydroponics_righthand.dmi' - force = 5 - throwforce = 7 - w_class = WEIGHT_CLASS_SMALL - -/obj/item/shovel/serrated - name = "serrated bone shovel" - desc = "A wicked tool that cleaves through dirt just as easily as it does flesh. The design was styled after ancient lavaland tribal designs." - icon_state = "shovel_bone" - item_state = "shovel_bone" - lefthand_file = 'icons/mob/inhands/equipment/mining_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/mining_righthand.dmi' - force = 15 - throwforce = 12 - w_class = WEIGHT_CLASS_NORMAL - toolspeed = 0.7 - attack_verb = list("slashed", "impaled", "stabbed", "sliced") - sharpness = IS_SHARP +/*****************Pickaxes & Drills & Shovels****************/ +/obj/item/pickaxe + name = "pickaxe" + icon = 'icons/obj/mining.dmi' + icon_state = "pickaxe" + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_BACK + force = 15 + throwforce = 10 + item_state = "pickaxe" + lefthand_file = 'icons/mob/inhands/equipment/mining_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/mining_righthand.dmi' + w_class = WEIGHT_CLASS_BULKY + custom_materials = list(/datum/material/iron=2000) //one sheet, but where can you make them? + tool_behaviour = TOOL_MINING + toolspeed = 1 + usesound = list('sound/effects/picaxe1.ogg', 'sound/effects/picaxe2.ogg', 'sound/effects/picaxe3.ogg') + attack_verb = list("hit", "pierced", "sliced", "attacked") + +/obj/item/pickaxe/suicide_act(mob/living/user) + user.visible_message("[user] begins digging into [user.p_their()] chest! It looks like [user.p_theyre()] trying to commit suicide!") + if(use_tool(user, user, 30, volume=50)) + return BRUTELOSS + user.visible_message("[user] couldn't do it!") + return SHAME + +/obj/item/pickaxe/rusted + name = "rusty pickaxe" + desc = "A pickaxe that's been left to rust." + attack_verb = list("ineffectively hit") + force = 1 + throwforce = 1 + +/obj/item/pickaxe/mini + name = "compact pickaxe" + desc = "A smaller, compact version of the standard pickaxe." + icon_state = "minipick" + force = 10 + throwforce = 7 + slot_flags = ITEM_SLOT_BELT + w_class = WEIGHT_CLASS_NORMAL + custom_materials = list(/datum/material/iron=1000) + +/obj/item/pickaxe/silver + name = "silver-plated pickaxe" + icon_state = "spickaxe" + item_state = "spickaxe" + toolspeed = 0.5 //mines faster than a normal pickaxe, bought from mining vendor + desc = "A silver-plated pickaxe that mines slightly faster than standard-issue." + force = 17 + +/obj/item/pickaxe/diamond + name = "diamond-tipped pickaxe" + icon_state = "dpickaxe" + item_state = "dpickaxe" + toolspeed = 0.3 + desc = "A pickaxe with a diamond pick head. Extremely robust at cracking rock walls and digging up dirt." + force = 19 + +/obj/item/pickaxe/drill + name = "mining drill" + icon_state = "handdrill" + item_state = "jackhammer" + slot_flags = ITEM_SLOT_BELT + toolspeed = 0.6 //available from roundstart, faster than a pickaxe. + usesound = 'sound/weapons/drill.ogg' + hitsound = 'sound/weapons/drill.ogg' + desc = "An electric mining drill for the especially scrawny." + +/obj/item/pickaxe/drill/cyborg + name = "cyborg mining drill" + desc = "An integrated electric mining drill." + flags_1 = NONE + +/obj/item/pickaxe/drill/cyborg/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CYBORG_ITEM_TRAIT) + +/obj/item/pickaxe/drill/diamonddrill + name = "diamond-tipped mining drill" + icon_state = "diamonddrill" + toolspeed = 0.2 + desc = "Yours is the drill that will pierce the heavens!" + +/obj/item/pickaxe/drill/cyborg/diamond //This is the BORG version! + name = "diamond-tipped cyborg mining drill" //To inherit the NODROP_1 flag, and easier to change borg specific drill mechanics. + icon_state = "diamonddrill" + toolspeed = 0.2 + +/obj/item/pickaxe/drill/jackhammer + name = "sonic jackhammer" + icon_state = "jackhammer" + item_state = "jackhammer" + toolspeed = 0.1 //the epitome of powertools. extremely fast mining + usesound = 'sound/weapons/sonic_jackhammer.ogg' + hitsound = 'sound/weapons/sonic_jackhammer.ogg' + desc = "Cracks rocks with sonic blasts." + +/obj/item/pickaxe/improvised + name = "improvised pickaxe" + desc = "A pickaxe made with a knife and crowbar taped together, how does it not break?" + icon_state = "ipickaxe" + item_state = "ipickaxe" + force = 10 + throwforce = 7 + toolspeed = 3 //3 times slower than a normal pickaxe + slot_flags = ITEM_SLOT_BELT + w_class = WEIGHT_CLASS_NORMAL + custom_materials = list(/datum/material/iron=12050) //metal needed for a crowbar and for a knife, why the FUCK does a knife cost 6 metal sheets while a crowbar costs 0.025 sheets? shit makes no sense fuck this + +/obj/item/shovel + name = "shovel" + desc = "A large tool for digging and moving dirt." + icon = 'icons/obj/mining.dmi' + icon_state = "shovel" + lefthand_file = 'icons/mob/inhands/equipment/mining_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/mining_righthand.dmi' + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BELT + force = 8 + tool_behaviour = TOOL_SHOVEL + toolspeed = 1 + usesound = 'sound/effects/shovel_dig.ogg' + throwforce = 4 + item_state = "shovel" + w_class = WEIGHT_CLASS_NORMAL + custom_materials = list(/datum/material/iron=50) + attack_verb = list("bashed", "bludgeoned", "thrashed", "whacked") + sharpness = IS_SHARP + +/obj/item/shovel/Initialize() + . = ..() + AddComponent(/datum/component/butchering, 150, 40) //it's sharp, so it works, but barely. + +/obj/item/shovel/suicide_act(mob/living/user) + user.visible_message("[user] begins digging their own grave! It looks like [user.p_theyre()] trying to commit suicide!") + if(use_tool(user, user, 30, volume=50)) + return BRUTELOSS + user.visible_message("[user] couldn't do it!") + return SHAME + +/obj/item/shovel/spade + name = "spade" + desc = "A small tool for digging and moving dirt." + icon_state = "spade" + item_state = "spade" + lefthand_file = 'icons/mob/inhands/equipment/hydroponics_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/hydroponics_righthand.dmi' + force = 5 + throwforce = 7 + w_class = WEIGHT_CLASS_SMALL + +/obj/item/shovel/serrated + name = "serrated bone shovel" + desc = "A wicked tool that cleaves through dirt just as easily as it does flesh. The design was styled after ancient lavaland tribal designs." + icon_state = "shovel_bone" + item_state = "shovel_bone" + lefthand_file = 'icons/mob/inhands/equipment/mining_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/mining_righthand.dmi' + force = 15 + throwforce = 12 + w_class = WEIGHT_CLASS_NORMAL + toolspeed = 0.7 + attack_verb = list("slashed", "impaled", "stabbed", "sliced") + sharpness = IS_SHARP diff --git a/code/modules/mining/laborcamp/laborshuttle.dm b/code/modules/mining/laborcamp/laborshuttle.dm index 17a0c50eb24b..bf8f87cd5b89 100644 --- a/code/modules/mining/laborcamp/laborshuttle.dm +++ b/code/modules/mining/laborcamp/laborshuttle.dm @@ -1,27 +1,27 @@ -/obj/machinery/computer/shuttle/labor - name = "labor shuttle console" - desc = "Used to call and send the labor camp shuttle." - circuit = /obj/item/circuitboard/computer/labor_shuttle - shuttleId = "laborcamp" - possible_destinations = "laborcamp_home;laborcamp_away" - req_access = list(ACCESS_BRIG) - - -/obj/machinery/computer/shuttle/labor/one_way - name = "prisoner shuttle console" - desc = "A one-way shuttle console, used to summon the shuttle to the labor camp." - possible_destinations = "laborcamp_away" - circuit = /obj/item/circuitboard/computer/labor_shuttle/one_way - req_access = list( ) - -/obj/machinery/computer/shuttle/labor/one_way/Topic(href, href_list) - if(href_list["move"]) - var/obj/docking_port/mobile/M = SSshuttle.getShuttle("laborcamp") - if(!M) - to_chat(usr, "Cannot locate shuttle!") - return 0 - var/obj/docking_port/stationary/S = M.get_docked() - if(S && S.name == "laborcamp_away") - to_chat(usr, "Shuttle is already at the outpost!") - return 0 - ..() +/obj/machinery/computer/shuttle/labor + name = "labor shuttle console" + desc = "Used to call and send the labor camp shuttle." + circuit = /obj/item/circuitboard/computer/labor_shuttle + shuttleId = "laborcamp" + possible_destinations = "laborcamp_home;laborcamp_away" + req_access = list(ACCESS_BRIG) + + +/obj/machinery/computer/shuttle/labor/one_way + name = "prisoner shuttle console" + desc = "A one-way shuttle console, used to summon the shuttle to the labor camp." + possible_destinations = "laborcamp_away" + circuit = /obj/item/circuitboard/computer/labor_shuttle/one_way + req_access = list( ) + +/obj/machinery/computer/shuttle/labor/one_way/Topic(href, href_list) + if(href_list["move"]) + var/obj/docking_port/mobile/M = SSshuttle.getShuttle("laborcamp") + if(!M) + to_chat(usr, "Cannot locate shuttle!") + return 0 + var/obj/docking_port/stationary/S = M.get_docked() + if(S && S.name == "laborcamp_away") + to_chat(usr, "Shuttle is already at the outpost!") + return 0 + ..() diff --git a/code/modules/mining/laborcamp/laborstacker.dm b/code/modules/mining/laborcamp/laborstacker.dm index 60a343858419..e091f0ff906d 100644 --- a/code/modules/mining/laborcamp/laborstacker.dm +++ b/code/modules/mining/laborcamp/laborstacker.dm @@ -1,161 +1,158 @@ -GLOBAL_LIST(labor_sheet_values) - -/**********************Prisoners' Console**************************/ - -/obj/machinery/mineral/labor_claim_console - name = "point claim console" - desc = "A stacking console with an electromagnetic writer, used to track ore mined by prisoners." - icon = 'icons/obj/machines/mining_machines.dmi' - icon_state = "console" - density = FALSE - ui_x = 315 - ui_y = 430 - - var/obj/machinery/mineral/stacking_machine/laborstacker/stacking_machine = null - 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 - -/obj/machinery/mineral/labor_claim_console/Initialize() - . = ..() - Radio = new/obj/item/radio(src) - Radio.listening = FALSE - locate_stacking_machine() - - if(!GLOB.labor_sheet_values) - var/sheet_list = list() - for(var/sheet_type in subtypesof(/obj/item/stack/sheet)) - var/obj/item/stack/sheet/sheet = sheet_type - if(!initial(sheet.point_value) || (initial(sheet.merge_type) && initial(sheet.merge_type) != sheet_type)) //ignore no-value sheets and x/fifty subtypes - continue - sheet_list += list(list("ore" = initial(sheet.name), "value" = initial(sheet.point_value))) - GLOB.labor_sheet_values = sortList(sheet_list, /proc/cmp_sheet_list) - -/proc/cmp_sheet_list(list/a, list/b) - return a["value"] - b["value"] - -/obj/machinery/mineral/labor_claim_console/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "LaborClaimConsole", name, ui_x, ui_y, master_ui, state) - ui.open() - -/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 - if(obj_flags & EMAGGED) - can_go_home = TRUE - - var/obj/item/card/id/I = user.get_idcard(TRUE) - if(istype(I, /obj/item/card/id/prisoner)) - var/obj/item/card/id/prisoner/P = I - data["id_points"] = P.points - if(P.points >= P.goal) - can_go_home = TRUE - data["status_info"] = "Goal met!" - else - data["status_info"] = "You are [(P.goal - P.points)] points away." - else - data["status_info"] = "No Prisoner ID detected." - data["id_points"] = 0 - - 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(..()) - return - switch(action) - if("claim_points") - var/mob/M = usr - var/obj/item/card/id/I = M.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 - else - to_chat(usr, "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 - -/obj/machinery/mineral/labor_claim_console/proc/locate_stacking_machine() - stacking_machine = locate(/obj/machinery/mineral/stacking_machine, get_step(src, machinedir)) - if(stacking_machine) - stacking_machine.CONSOLE = src - else - qdel(src) - -/obj/machinery/mineral/labor_claim_console/emag_act(mob/user) - if(!(obj_flags & EMAGGED)) - obj_flags |= EMAGGED - to_chat(user, "PZZTTPFFFT") - -/**********************Prisoner Collection Unit**************************/ - -/obj/machinery/mineral/stacking_machine/laborstacker - force_connect = TRUE - var/points = 0 //The unclaimed value of ore stacked. - 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)) - var/obj/item/stack/sheet/inp = I - points += inp.point_value * inp.amount - return ..() - -/**********************Point Lookup Console**************************/ - -/obj/machinery/mineral/labor_points_checker - name = "points checking console" - desc = "A console used by prisoners to check the progress on their quotas. Simply swipe a prisoner ID." - icon = 'icons/obj/machines/mining_machines.dmi' - icon_state = "console" - density = FALSE - -/obj/machinery/mineral/labor_points_checker/attack_hand(mob/user) - . = ..() - if(.) - return - user.examinate(src) - -/obj/machinery/mineral/labor_points_checker/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/card/id)) - if(istype(I, /obj/item/card/id/prisoner)) - var/obj/item/card/id/prisoner/prisoner_id = I - to_chat(user, "ID: [prisoner_id.registered_name]") - to_chat(user, "Points Collected:[prisoner_id.points]") - to_chat(user, "Point Quota: [prisoner_id.goal]") - to_chat(user, "Collect points by bringing smelted minerals to the Labor Shuttle stacking machine. Reach your quota to earn your release.") - else - to_chat(user, "Error: Invalid ID") - else - return ..() +GLOBAL_LIST(labor_sheet_values) + +/**********************Prisoners' Console**************************/ + +/obj/machinery/mineral/labor_claim_console + name = "point claim console" + desc = "A stacking console with an electromagnetic writer, used to track ore mined by prisoners." + icon = 'icons/obj/machines/mining_machines.dmi' + icon_state = "console" + density = FALSE + + var/obj/machinery/mineral/stacking_machine/laborstacker/stacking_machine = null + 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 + +/obj/machinery/mineral/labor_claim_console/Initialize() + . = ..() + Radio = new/obj/item/radio(src) + Radio.listening = FALSE + locate_stacking_machine() + + if(!GLOB.labor_sheet_values) + var/sheet_list = list() + for(var/sheet_type in subtypesof(/obj/item/stack/sheet)) + var/obj/item/stack/sheet/sheet = sheet_type + if(!initial(sheet.point_value) || (initial(sheet.merge_type) && initial(sheet.merge_type) != sheet_type)) //ignore no-value sheets and x/fifty subtypes + continue + sheet_list += list(list("ore" = initial(sheet.name), "value" = initial(sheet.point_value))) + GLOB.labor_sheet_values = sortList(sheet_list, /proc/cmp_sheet_list) + +/proc/cmp_sheet_list(list/a, list/b) + return a["value"] - b["value"] + +/obj/machinery/mineral/labor_claim_console/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "LaborClaimConsole", name) + ui.open() + +/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 + if(obj_flags & EMAGGED) + can_go_home = TRUE + + var/obj/item/card/id/I = user.get_idcard(TRUE) + if(istype(I, /obj/item/card/id/prisoner)) + var/obj/item/card/id/prisoner/P = I + data["id_points"] = P.points + if(P.points >= P.goal) + can_go_home = TRUE + data["status_info"] = "Goal met!" + else + data["status_info"] = "You are [(P.goal - P.points)] points away." + else + data["status_info"] = "No Prisoner ID detected." + data["id_points"] = 0 + + 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(..()) + return + switch(action) + if("claim_points") + var/mob/M = usr + var/obj/item/card/id/I = M.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 + else + to_chat(usr, "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 + +/obj/machinery/mineral/labor_claim_console/proc/locate_stacking_machine() + stacking_machine = locate(/obj/machinery/mineral/stacking_machine, get_step(src, machinedir)) + if(stacking_machine) + stacking_machine.CONSOLE = src + else + qdel(src) + +/obj/machinery/mineral/labor_claim_console/emag_act(mob/user) + if(!(obj_flags & EMAGGED)) + obj_flags |= EMAGGED + to_chat(user, "PZZTTPFFFT") + +/**********************Prisoner Collection Unit**************************/ + +/obj/machinery/mineral/stacking_machine/laborstacker + force_connect = TRUE + var/points = 0 //The unclaimed value of ore stacked. + 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)) + var/obj/item/stack/sheet/inp = I + points += inp.point_value * inp.amount + return ..() + +/**********************Point Lookup Console**************************/ + +/obj/machinery/mineral/labor_points_checker + name = "points checking console" + desc = "A console used by prisoners to check the progress on their quotas. Simply swipe a prisoner ID." + icon = 'icons/obj/machines/mining_machines.dmi' + icon_state = "console" + density = FALSE + +/obj/machinery/mineral/labor_points_checker/attack_hand(mob/user) + . = ..() + if(.) + return + user.examinate(src) + +/obj/machinery/mineral/labor_points_checker/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/card/id)) + if(istype(I, /obj/item/card/id/prisoner)) + var/obj/item/card/id/prisoner/prisoner_id = I + to_chat(user, "ID: [prisoner_id.registered_name]") + to_chat(user, "Points Collected:[prisoner_id.points]") + to_chat(user, "Point Quota: [prisoner_id.goal]") + to_chat(user, "Collect points by bringing smelted minerals to the Labor Shuttle stacking machine. Reach your quota to earn your release.") + else + to_chat(user, "Error: Invalid ID") + else + return ..() diff --git a/code/modules/mining/machine_processing.dm b/code/modules/mining/machine_processing.dm index 97d441b2d476..e92cca5173c3 100644 --- a/code/modules/mining/machine_processing.dm +++ b/code/modules/mining/machine_processing.dm @@ -1,252 +1,263 @@ -#define SMELT_AMOUNT 10 - -/**********************Mineral processing unit console**************************/ - -/obj/machinery/mineral - processing_flags = START_PROCESSING_MANUALLY - subsystem_type = /datum/controller/subsystem/processing/fastprocess - /// The current direction of `input_turf`, in relation to the machine. - var/input_dir = NORTH - /// The current direction, in relation to the machine, that items will be output to. - var/output_dir = SOUTH - /// The turf the machines listens to for items to pick up. Calls the `pickup_item()` proc. - var/turf/input_turf = null - /// Determines if this machine needs to pick up items. Used to avoid registering signals to `/mineral` machines that don't pickup items. - var/needs_item_input = FALSE - -/obj/machinery/mineral/Initialize(mapload) - . = ..() - if(needs_item_input) - register_input_turf() - -/// Gets the turf in the `input_dir` direction adjacent to the machine, and registers signals for ATOM_ENTERED and ATOM_CREATED. Calls the `pickup_item()` proc when it recieves these signals. -/obj/machinery/mineral/proc/register_input_turf() - input_turf = get_step(src, input_dir) - if(input_turf) // make sure there is actually a turf - RegisterSignal(input_turf, list(COMSIG_ATOM_CREATED, COMSIG_ATOM_ENTERED), .proc/pickup_item) - -/// Unregisters signals that are registered the machine's input turf, if it has one. -/obj/machinery/mineral/proc/unregister_input_turf() - if(input_turf) - UnregisterSignal(input_turf, list(COMSIG_ATOM_ENTERED, COMSIG_ATOM_CREATED)) - -/** - Base proc for all `/mineral` subtype machines to use. Place your item pickup behavior in this proc when you override it for your specific machine. - - Called when the COMSIG_ATOM_ENTERED and COMSIG_ATOM_CREATED signals are sent. - - Arguments: - * source - the turf that is listening for the signals. - * target - the atom that just moved onto the `source` turf. - * oldLoc - the old location that `target` was at before moving onto `source`. -*/ -/obj/machinery/mineral/proc/pickup_item(datum/source, atom/movable/target, atom/oldLoc) - return - -/// Generic unloading proc. Takes an atom as an argument and forceMove's it to the turf adjacent to this machine in the `output_dir` direction. -/obj/machinery/mineral/proc/unload_mineral(atom/movable/S) - S.forceMove(drop_location()) - var/turf/T = get_step(src,output_dir) - if(T) - S.forceMove(T) - -/obj/machinery/mineral/processing_unit_console - name = "production machine console" - icon = 'icons/obj/machines/mining_machines.dmi' - icon_state = "console" - density = TRUE - var/obj/machinery/mineral/processing_unit/machine = null - var/machinedir = EAST - -/obj/machinery/mineral/processing_unit_console/Initialize() - . = ..() - machine = locate(/obj/machinery/mineral/processing_unit, get_step(src, machinedir)) - if (machine) - machine.CONSOLE = src - else - return INITIALIZE_HINT_QDEL - -/obj/machinery/mineral/processing_unit_console/ui_interact(mob/user) - . = ..() - if(!machine) - return - - var/dat = machine.get_machine_data() - - var/datum/browser/popup = new(user, "processing", "Smelting Console", 300, 500) - popup.set_content(dat) - popup.open() - -/obj/machinery/mineral/processing_unit_console/Topic(href, href_list) - if(..()) - return - usr.set_machine(src) - add_fingerprint(usr) - - if(href_list["material"]) - var/datum/material/new_material = locate(href_list["material"]) - if(istype(new_material)) - machine.selected_material = new_material - machine.selected_alloy = null - - if(href_list["alloy"]) - machine.selected_material = null - machine.selected_alloy = href_list["alloy"] - - if(href_list["set_on"]) - machine.on = (href_list["set_on"] == "on") - machine.begin_processing() - - updateUsrDialog() - return - -/obj/machinery/mineral/processing_unit_console/Destroy() - machine = null - return ..() - - -/**********************Mineral processing unit**************************/ - - -/obj/machinery/mineral/processing_unit - name = "furnace" - icon = 'icons/obj/machines/mining_machines.dmi' - icon_state = "furnace" - density = TRUE - needs_item_input = TRUE - var/obj/machinery/mineral/CONSOLE = null - var/on = FALSE - var/datum/material/selected_material = null - var/selected_alloy = null - var/datum/techweb/stored_research - -/obj/machinery/mineral/processing_unit/Initialize() - . = ..() - proximity_monitor = new(src, 1) - AddComponent(/datum/component/material_container, list(/datum/material/iron, /datum/material/glass, /datum/material/silver, /datum/material/gold, /datum/material/diamond, /datum/material/plasma, /datum/material/uranium, /datum/material/bananium, /datum/material/titanium, /datum/material/bluespace), INFINITY, TRUE, /obj/item/stack) - stored_research = new /datum/techweb/specialized/autounlocking/smelter - selected_material = SSmaterials.GetMaterialRef(/datum/material/iron) - -/obj/machinery/mineral/processing_unit/Destroy() - CONSOLE = null - QDEL_NULL(stored_research) - return ..() - -/obj/machinery/mineral/processing_unit/proc/process_ore(obj/item/stack/ore/O) - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - var/material_amount = materials.get_item_material_amount(O) - if(!materials.has_space(material_amount)) - unload_mineral(O) - else - materials.insert_item(O) - qdel(O) - if(CONSOLE) - CONSOLE.updateUsrDialog() - -/obj/machinery/mineral/processing_unit/proc/get_machine_data() - var/dat = "Smelter control console

                    " - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - for(var/datum/material/M in materials.materials) - var/amount = materials.materials[M] - dat += "[M.name]: [amount] cm³" - if (selected_material == M) - dat += " Smelting" - else - dat += " Not Smelting " - dat += "
                    " - - dat += "

                    " - dat += "Smelt Alloys
                    " - - for(var/v in stored_research.researched_designs) - var/datum/design/D = SSresearch.techweb_design_by_id(v) - dat += "[D.name] " - if (selected_alloy == D.id) - dat += " Smelting" - else - dat += " Not Smelting " - dat += "
                    " - - dat += "

                    " - //On or off - dat += "Machine is currently " - if (on) - dat += "On " - else - dat += "Off " - - return dat - -/obj/machinery/mineral/processing_unit/pickup_item(datum/source, atom/movable/target, atom/oldLoc) - if(istype(target, /obj/item/stack/ore)) - process_ore(target) - -/obj/machinery/mineral/processing_unit/process() - if(on) - if(selected_material) - smelt_ore() - - else if(selected_alloy) - smelt_alloy() - - - if(CONSOLE) - CONSOLE.updateUsrDialog() - else - end_processing() - -/obj/machinery/mineral/processing_unit/proc/smelt_ore() - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - var/datum/material/mat = selected_material - if(mat) - var/sheets_to_remove = (materials.materials[mat] >= (MINERAL_MATERIAL_AMOUNT * SMELT_AMOUNT) ) ? SMELT_AMOUNT : round(materials.materials[mat] / MINERAL_MATERIAL_AMOUNT) - if(!sheets_to_remove) - on = FALSE - else - var/out = get_step(src, output_dir) - materials.retrieve_sheets(sheets_to_remove, mat, out) - - -/obj/machinery/mineral/processing_unit/proc/smelt_alloy() - var/datum/design/alloy = stored_research.isDesignResearchedID(selected_alloy) //check if it's a valid design - if(!alloy) - on = FALSE - return - - var/amount = can_smelt(alloy) - - if(!amount) - on = FALSE - return - - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - materials.use_materials(alloy.materials, amount) - - generate_mineral(alloy.build_path) - -/obj/machinery/mineral/processing_unit/proc/can_smelt(datum/design/D) - if(D.make_reagents.len) - return FALSE - - var/build_amount = SMELT_AMOUNT - - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - - for(var/mat_cat in D.materials) - var/required_amount = D.materials[mat_cat] - var/amount = materials.materials[mat_cat] - - build_amount = min(build_amount, round(amount / required_amount)) - - return build_amount - -/obj/machinery/mineral/processing_unit/proc/generate_mineral(P) - var/O = new P(src) - unload_mineral(O) - -/obj/machinery/mineral/processing_unit/on_deconstruction() - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - materials.retrieve_all() - ..() - -#undef SMELT_AMOUNT +#define SMELT_AMOUNT 10 + +/**********************Mineral processing unit console**************************/ + +/obj/machinery/mineral + processing_flags = START_PROCESSING_MANUALLY + subsystem_type = /datum/controller/subsystem/processing/fastprocess + /// The current direction of `input_turf`, in relation to the machine. + var/input_dir = NORTH + /// The current direction, in relation to the machine, that items will be output to. + var/output_dir = SOUTH + /// The turf the machines listens to for items to pick up. Calls the `pickup_item()` proc. + var/turf/input_turf = null + /// Determines if this machine needs to pick up items. Used to avoid registering signals to `/mineral` machines that don't pickup items. + var/needs_item_input = FALSE + +/obj/machinery/mineral/Initialize(mapload) + . = ..() + if(needs_item_input && anchored) + register_input_turf() + +/// Gets the turf in the `input_dir` direction adjacent to the machine, and registers signals for ATOM_ENTERED and ATOM_CREATED. Calls the `pickup_item()` proc when it receives these signals. +/obj/machinery/mineral/proc/register_input_turf() + input_turf = get_step(src, input_dir) + if(input_turf) // make sure there is actually a turf + RegisterSignal(input_turf, list(COMSIG_ATOM_CREATED, COMSIG_ATOM_ENTERED), .proc/pickup_item) + +/// Unregisters signals that are registered the machine's input turf, if it has one. +/obj/machinery/mineral/proc/unregister_input_turf() + if(input_turf) + UnregisterSignal(input_turf, list(COMSIG_ATOM_ENTERED, COMSIG_ATOM_CREATED)) + +/obj/machinery/mineral/Moved() + . = ..() + if(!needs_item_input || !anchored) + return + unregister_input_turf() + register_input_turf() + +/** + Base proc for all `/mineral` subtype machines to use. Place your item pickup behavior in this proc when you override it for your specific machine. + + Called when the COMSIG_ATOM_ENTERED and COMSIG_ATOM_CREATED signals are sent. + + Arguments: + * source - the turf that is listening for the signals. + * target - the atom that just moved onto the `source` turf. + * oldLoc - the old location that `target` was at before moving onto `source`. +*/ +/obj/machinery/mineral/proc/pickup_item(datum/source, atom/movable/target, atom/oldLoc) + return + +/// Generic unloading proc. Takes an atom as an argument and forceMove's it to the turf adjacent to this machine in the `output_dir` direction. +/obj/machinery/mineral/proc/unload_mineral(atom/movable/S) + S.forceMove(drop_location()) + var/turf/T = get_step(src,output_dir) + if(T) + S.forceMove(T) + +/obj/machinery/mineral/processing_unit_console + name = "production machine console" + icon = 'icons/obj/machines/mining_machines.dmi' + icon_state = "console" + density = TRUE + var/obj/machinery/mineral/processing_unit/machine = null + var/machinedir = EAST + +/obj/machinery/mineral/processing_unit_console/Initialize() + . = ..() + machine = locate(/obj/machinery/mineral/processing_unit, get_step(src, machinedir)) + if (machine) + machine.CONSOLE = src + else + return INITIALIZE_HINT_QDEL + +/obj/machinery/mineral/processing_unit_console/ui_interact(mob/user) + . = ..() + if(!machine) + return + + var/dat = machine.get_machine_data() + + var/datum/browser/popup = new(user, "processing", "Smelting Console", 300, 500) + popup.set_content(dat) + popup.open() + +/obj/machinery/mineral/processing_unit_console/Topic(href, href_list) + if(..()) + return + usr.set_machine(src) + add_fingerprint(usr) + + if(href_list["material"]) + var/datum/material/new_material = locate(href_list["material"]) + if(istype(new_material)) + machine.selected_material = new_material + machine.selected_alloy = null + + if(href_list["alloy"]) + machine.selected_material = null + machine.selected_alloy = href_list["alloy"] + + if(href_list["set_on"]) + machine.on = (href_list["set_on"] == "on") + machine.begin_processing() + + updateUsrDialog() + return + +/obj/machinery/mineral/processing_unit_console/Destroy() + machine = null + return ..() + + +/**********************Mineral processing unit**************************/ + + +/obj/machinery/mineral/processing_unit + name = "furnace" + icon = 'icons/obj/machines/mining_machines.dmi' + icon_state = "furnace" + density = TRUE + needs_item_input = TRUE + var/obj/machinery/mineral/CONSOLE = null + var/on = FALSE + var/datum/material/selected_material = null + var/selected_alloy = null + var/datum/techweb/stored_research + +/obj/machinery/mineral/processing_unit/Initialize() + . = ..() + proximity_monitor = new(src, 1) + AddComponent(/datum/component/material_container, list(/datum/material/iron, /datum/material/glass, /datum/material/silver, /datum/material/gold, /datum/material/diamond, /datum/material/plasma, /datum/material/uranium, /datum/material/bananium, /datum/material/titanium, /datum/material/bluespace), INFINITY, TRUE, /obj/item/stack) + stored_research = new /datum/techweb/specialized/autounlocking/smelter + selected_material = SSmaterials.GetMaterialRef(/datum/material/iron) + +/obj/machinery/mineral/processing_unit/Destroy() + CONSOLE = null + QDEL_NULL(stored_research) + return ..() + +/obj/machinery/mineral/processing_unit/proc/process_ore(obj/item/stack/ore/O) + if(QDELETED(O)) + return + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + var/material_amount = materials.get_item_material_amount(O) + if(!materials.has_space(material_amount)) + unload_mineral(O) + else + materials.insert_item(O) + qdel(O) + if(CONSOLE) + CONSOLE.updateUsrDialog() + +/obj/machinery/mineral/processing_unit/proc/get_machine_data() + var/dat = "Smelter control console

                    " + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + for(var/datum/material/M in materials.materials) + var/amount = materials.materials[M] + dat += "[M.name]: [amount] cm³" + if (selected_material == M) + dat += " Smelting" + else + dat += " Not Smelting " + dat += "
                    " + + dat += "

                    " + dat += "Smelt Alloys
                    " + + for(var/v in stored_research.researched_designs) + var/datum/design/D = SSresearch.techweb_design_by_id(v) + dat += "[D.name] " + if (selected_alloy == D.id) + dat += " Smelting" + else + dat += " Not Smelting " + dat += "
                    " + + dat += "

                    " + //On or off + dat += "Machine is currently " + if (on) + dat += "On " + else + dat += "Off " + + return dat + +/obj/machinery/mineral/processing_unit/pickup_item(datum/source, atom/movable/target, atom/oldLoc) + if(QDELETED(target)) + return + if(istype(target, /obj/item/stack/ore)) + process_ore(target) + +/obj/machinery/mineral/processing_unit/process() + if(on) + if(selected_material) + smelt_ore() + + else if(selected_alloy) + smelt_alloy() + + + if(CONSOLE) + CONSOLE.updateUsrDialog() + else + end_processing() + +/obj/machinery/mineral/processing_unit/proc/smelt_ore() + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + var/datum/material/mat = selected_material + if(mat) + var/sheets_to_remove = (materials.materials[mat] >= (MINERAL_MATERIAL_AMOUNT * SMELT_AMOUNT) ) ? SMELT_AMOUNT : round(materials.materials[mat] / MINERAL_MATERIAL_AMOUNT) + if(!sheets_to_remove) + on = FALSE + else + var/out = get_step(src, output_dir) + materials.retrieve_sheets(sheets_to_remove, mat, out) + + +/obj/machinery/mineral/processing_unit/proc/smelt_alloy() + var/datum/design/alloy = stored_research.isDesignResearchedID(selected_alloy) //check if it's a valid design + if(!alloy) + on = FALSE + return + + var/amount = can_smelt(alloy) + + if(!amount) + on = FALSE + return + + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + materials.use_materials(alloy.materials, amount) + + generate_mineral(alloy.build_path) + +/obj/machinery/mineral/processing_unit/proc/can_smelt(datum/design/D) + if(D.make_reagents.len) + return FALSE + + var/build_amount = SMELT_AMOUNT + + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + + for(var/mat_cat in D.materials) + var/required_amount = D.materials[mat_cat] + var/amount = materials.materials[mat_cat] + + build_amount = min(build_amount, round(amount / required_amount)) + + return build_amount + +/obj/machinery/mineral/processing_unit/proc/generate_mineral(P) + var/O = new P(src) + unload_mineral(O) + +/obj/machinery/mineral/processing_unit/on_deconstruction() + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + materials.retrieve_all() + ..() + +#undef SMELT_AMOUNT diff --git a/code/modules/mining/machine_redemption.dm b/code/modules/mining/machine_redemption.dm index 2af7e26ea7f5..16324d410cc5 100644 --- a/code/modules/mining/machine_redemption.dm +++ b/code/modules/mining/machine_redemption.dm @@ -12,8 +12,6 @@ req_access = list(ACCESS_MINERAL_STOREROOM) layer = BELOW_OBJ_LAYER circuit = /obj/item/circuitboard/machine/ore_redemption - ui_x = 440 - ui_y = 550 needs_item_input = TRUE processing_flags = START_PROCESSING_MANUALLY @@ -55,6 +53,8 @@ . += "Alt-click to rotate the input and output direction." /obj/machinery/mineral/ore_redemption/proc/smelt_ore(obj/item/stack/ore/O) + if(QDELETED(O)) + return var/datum/component/material_container/mat_container = materials.mat_container if (!mat_container) return @@ -142,6 +142,8 @@ signal.send_to_receivers() /obj/machinery/mineral/ore_redemption/pickup_item(datum/source, atom/movable/target, atom/oldLoc) + if(QDELETED(target)) + return if(!materials.mat_container || panel_open || !powered()) return @@ -160,6 +162,8 @@ /obj/machinery/mineral/ore_redemption/default_unfasten_wrench(mob/user, obj/item/I) . = ..() + if(. != SUCCESSFUL_UNFASTEN) + return if(anchored) register_input_turf() // someone just wrenched us down, re-register the turf else @@ -202,10 +206,10 @@ register_input_turf() // register the new one return TRUE -/obj/machinery/mineral/ore_redemption/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/machinery/mineral/ore_redemption/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "OreRedemptionMachine", "Ore Redemption Machine", ui_x, ui_y, master_ui, state) + ui = new(user, src, "OreRedemptionMachine") ui.open() /obj/machinery/mineral/ore_redemption/ui_data(mob/user) diff --git a/code/modules/mining/machine_stacking.dm b/code/modules/mining/machine_stacking.dm index 2b05b0d8741c..5f4f923710a2 100644 --- a/code/modules/mining/machine_stacking.dm +++ b/code/modules/mining/machine_stacking.dm @@ -1,127 +1,127 @@ -/**********************Mineral stacking unit console**************************/ - -/obj/machinery/mineral/stacking_unit_console - name = "stacking machine console" - icon = 'icons/obj/machines/mining_machines.dmi' - icon_state = "console" - desc = "Controls a stacking machine... in theory." - density = FALSE - circuit = /obj/item/circuitboard/machine/stacking_unit_console - var/obj/machinery/mineral/stacking_machine/machine - var/machinedir = SOUTHEAST - -/obj/machinery/mineral/stacking_unit_console/Initialize() - . = ..() - machine = locate(/obj/machinery/mineral/stacking_machine, get_step(src, machinedir)) - 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(!multitool_check_buffer(user, I)) - return - var/obj/item/multitool/M = I - M.buffer = src - to_chat(user, "You store linkage information in [I]'s buffer.") - return TRUE - -/obj/machinery/mineral/stacking_unit_console/Topic(href, href_list) - if(..()) - 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) - - src.updateUsrDialog() - return - - -/**********************Mineral stacking unit**************************/ - - -/obj/machinery/mineral/stacking_machine - name = "stacking machine" - icon = 'icons/obj/machines/mining_machines.dmi' - icon_state = "stacker" - desc = "A machine that automatically stacks acquired materials. Controlled by a nearby console." - density = TRUE - circuit = /obj/item/circuitboard/machine/stacking_machine - input_dir = EAST - output_dir = WEST - var/obj/machinery/mineral/stacking_unit_console/CONSOLE - var/stk_types = list() - var/stk_amt = list() - var/stack_list[0] //Key: Type. Value: Instance of type. - var/stack_amt = 50 //amount to stack before releassing - var/datum/component/remote_materials/materials - var/force_connect = FALSE - -/obj/machinery/mineral/stacking_machine/Initialize(mapload) - . = ..() - proximity_monitor = new(src, 1) - materials = AddComponent(/datum/component/remote_materials, "stacking", mapload, FALSE, mapload && force_connect) - -/obj/machinery/mineral/stacking_machine/Destroy() - CONSOLE = null - materials = null - return ..() - -/obj/machinery/mineral/stacking_machine/HasProximity(atom/movable/AM) - if(istype(AM, /obj/item/stack/sheet) && AM.loc == get_step(src, input_dir)) - process_sheet(AM) - -/obj/machinery/mineral/stacking_machine/multitool_act(mob/living/user, obj/item/multitool/M) - if(istype(M)) - if(istype(M.buffer, /obj/machinery/mineral/stacking_unit_console)) - CONSOLE = M.buffer - CONSOLE.machine = src - to_chat(user, "You link [src] to the console in [M]'s buffer.") - return TRUE - -/obj/machinery/mineral/stacking_machine/proc/process_sheet(obj/item/stack/sheet/inp) - var/key = inp.merge_type - var/obj/item/stack/sheet/storage = stack_list[key] - if(!storage) //It's the first of this sheet added - stack_list[key] = storage = new inp.type(src, 0) - 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) - storage.amount -= stack_amt +/**********************Mineral stacking unit console**************************/ + +/obj/machinery/mineral/stacking_unit_console + name = "stacking machine console" + icon = 'icons/obj/machines/mining_machines.dmi' + icon_state = "console" + desc = "Controls a stacking machine... in theory." + density = FALSE + circuit = /obj/item/circuitboard/machine/stacking_unit_console + var/obj/machinery/mineral/stacking_machine/machine + var/machinedir = SOUTHEAST + +/obj/machinery/mineral/stacking_unit_console/Initialize() + . = ..() + machine = locate(/obj/machinery/mineral/stacking_machine, get_step(src, machinedir)) + 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(!multitool_check_buffer(user, I)) + return + var/obj/item/multitool/M = I + M.buffer = src + to_chat(user, "You store linkage information in [I]'s buffer.") + return TRUE + +/obj/machinery/mineral/stacking_unit_console/Topic(href, href_list) + if(..()) + 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) + + src.updateUsrDialog() + return + + +/**********************Mineral stacking unit**************************/ + + +/obj/machinery/mineral/stacking_machine + name = "stacking machine" + icon = 'icons/obj/machines/mining_machines.dmi' + icon_state = "stacker" + desc = "A machine that automatically stacks acquired materials. Controlled by a nearby console." + density = TRUE + circuit = /obj/item/circuitboard/machine/stacking_machine + input_dir = EAST + output_dir = WEST + var/obj/machinery/mineral/stacking_unit_console/CONSOLE + var/stk_types = list() + var/stk_amt = list() + var/stack_list[0] //Key: Type. Value: Instance of type. + var/stack_amt = 50 //amount to stack before releassing + var/datum/component/remote_materials/materials + var/force_connect = FALSE + +/obj/machinery/mineral/stacking_machine/Initialize(mapload) + . = ..() + proximity_monitor = new(src, 1) + materials = AddComponent(/datum/component/remote_materials, "stacking", mapload, FALSE, mapload && force_connect) + +/obj/machinery/mineral/stacking_machine/Destroy() + CONSOLE = null + materials = null + return ..() + +/obj/machinery/mineral/stacking_machine/HasProximity(atom/movable/AM) + if(istype(AM, /obj/item/stack/sheet) && AM.loc == get_step(src, input_dir)) + process_sheet(AM) + +/obj/machinery/mineral/stacking_machine/multitool_act(mob/living/user, obj/item/multitool/M) + if(istype(M)) + if(istype(M.buffer, /obj/machinery/mineral/stacking_unit_console)) + CONSOLE = M.buffer + CONSOLE.machine = src + to_chat(user, "You link [src] to the console in [M]'s buffer.") + return TRUE + +/obj/machinery/mineral/stacking_machine/proc/process_sheet(obj/item/stack/sheet/inp) + var/key = inp.merge_type + var/obj/item/stack/sheet/storage = stack_list[key] + if(!storage) //It's the first of this sheet added + stack_list[key] = storage = new inp.type(src, 0) + 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) + storage.amount -= stack_amt diff --git a/code/modules/mining/machine_unloading.dm b/code/modules/mining/machine_unloading.dm index 92a81871ce83..e99350ec3206 100644 --- a/code/modules/mining/machine_unloading.dm +++ b/code/modules/mining/machine_unloading.dm @@ -1,21 +1,21 @@ -/**********************Unloading unit**************************/ - - -/obj/machinery/mineral/unloading_machine - name = "unloading machine" - icon = 'icons/obj/machines/mining_machines.dmi' - icon_state = "unloader" - density = TRUE - input_dir = WEST - output_dir = EAST - needs_item_input = TRUE - processing_flags = START_PROCESSING_MANUALLY - -/obj/machinery/mineral/unloading_machine/pickup_item(datum/source, atom/movable/target, atom/oldLoc) - if(istype(target, /obj/structure/ore_box)) - var/obj/structure/ore_box/box = target - for(var/obj/item/stack/ore/O in box) - unload_mineral(O) - else if(istype(target, /obj/item/stack/ore)) - var/obj/item/stack/ore/O = target - unload_mineral(O) +/**********************Unloading unit**************************/ + + +/obj/machinery/mineral/unloading_machine + name = "unloading machine" + icon = 'icons/obj/machines/mining_machines.dmi' + icon_state = "unloader" + density = TRUE + input_dir = WEST + output_dir = EAST + needs_item_input = TRUE + processing_flags = START_PROCESSING_MANUALLY + +/obj/machinery/mineral/unloading_machine/pickup_item(datum/source, atom/movable/target, atom/oldLoc) + if(istype(target, /obj/structure/ore_box)) + var/obj/structure/ore_box/box = target + for(var/obj/item/stack/ore/O in box) + unload_mineral(O) + else if(istype(target, /obj/item/stack/ore)) + var/obj/item/stack/ore/O = target + unload_mineral(O) diff --git a/code/modules/mining/machine_vending.dm b/code/modules/mining/machine_vending.dm index 579614928138..351031ab04d0 100644 --- a/code/modules/mining/machine_vending.dm +++ b/code/modules/mining/machine_vending.dm @@ -7,8 +7,6 @@ icon_state = "mining" density = TRUE circuit = /obj/item/circuitboard/machine/mining_equipment_vendor - ui_x = 425 - ui_y = 600 var/icon_deny = "mining-deny" var/obj/item/card/id/inserted_id var/list/prize_list = list( //if you add something to this, please, for the love of god, sort it by price/type. use tabs and not spaces. @@ -62,7 +60,7 @@ new /datum/data/mining_equipment("KA Damage Increase", /obj/item/borg/upgrade/modkit/damage, 1000), new /datum/data/mining_equipment("KA Cooldown Decrease", /obj/item/borg/upgrade/modkit/cooldown, 1000), new /datum/data/mining_equipment("KA AoE Damage", /obj/item/borg/upgrade/modkit/aoe/mobs, 2000) - ) + ) /datum/data/mining_equipment var/equipment_name = "generic" @@ -89,17 +87,15 @@ else icon_state = "[initial(icon_state)]-off" -/obj/machinery/mineral/equipment_vendor/ui_base_html(html) - var/datum/asset/spritesheet/assets = get_asset_datum(/datum/asset/spritesheet/vending) - . = replacetext(html, "", assets.css_tag()) +/obj/machinery/mineral/equipment_vendor/ui_assets(mob/user) + return list( + get_asset_datum(/datum/asset/spritesheet/vending), + ) -/obj/machinery/mineral/equipment_vendor/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/machinery/mineral/equipment_vendor/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - var/datum/asset/assets = get_asset_datum(/datum/asset/spritesheet/vending) - assets.send(user) - ui = new(user, src, ui_key, "MiningVendor", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, "MiningVendor", name) ui.open() /obj/machinery/mineral/equipment_vendor/ui_static_data(mob/user) diff --git a/code/modules/mining/mint.dm b/code/modules/mining/mint.dm index 40cbd8449a41..33117d99c518 100644 --- a/code/modules/mining/mint.dm +++ b/code/modules/mining/mint.dm @@ -1,145 +1,142 @@ -/**********************Mint**************************/ - - -/obj/machinery/mineral/mint - name = "coin press" - icon = 'icons/obj/economy.dmi' - icon_state = "coinpress0" - density = TRUE - input_dir = EAST - ui_x = 300 - ui_y = 250 - needs_item_input = TRUE - - var/produced_coins = 0 // how many coins the machine has made in it's last cycle - var/processing = FALSE - var/chosen = /datum/material/iron //which material will be used to make coins - - -/obj/machinery/mineral/mint/Initialize() - . = ..() - AddComponent(/datum/component/material_container, list( - /datum/material/iron, - /datum/material/plasma, - /datum/material/silver, - /datum/material/gold, - /datum/material/uranium, - /datum/material/titanium, - /datum/material/diamond, - /datum/material/bananium, - /datum/material/adamantine, - /datum/material/mythril, - /datum/material/plastic, - /datum/material/runite - ), MINERAL_MATERIAL_AMOUNT * 75, FALSE, /obj/item/stack) - chosen = SSmaterials.GetMaterialRef(chosen) - - -/obj/machinery/mineral/mint/pickup_item(datum/source, atom/movable/target, atom/oldLoc) - if(!istype(target, /obj/item/stack)) - return - - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - var/obj/item/stack/S = target - - if(materials.insert_item(S)) - qdel(S) - -/obj/machinery/mineral/mint/process() - if(processing) - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - var/datum/material/M = chosen - - if(!M) - processing = FALSE - icon_state = "coinpress0" - return - - icon_state = "coinpress1" - var/coin_mat = MINERAL_MATERIAL_AMOUNT - - for(var/sheets in 1 to 2) - if(materials.use_amount_mat(coin_mat, chosen)) - for(var/coin_to_make in 1 to 5) - create_coins() - produced_coins++ - else - var/found_new = FALSE - for(var/datum/material/inserted_material in materials.materials) - var/amount = materials.get_material_amount(inserted_material) - - if(amount) - chosen = inserted_material - found_new = TRUE - - if(!found_new) - processing = FALSE - else - end_processing() - icon_state = "coinpress0" - -/obj/machinery/mineral/mint/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "Mint", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/machinery/mineral/mint/ui_data() - var/list/data = list() - data["inserted_materials"] = list() - data["chosen_material"] = null - - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - for(var/datum/material/inserted_material in materials.materials) - var/amount = materials.get_material_amount(inserted_material) - if(!amount) - continue - data["inserted_materials"] += list(list( - "material" = inserted_material.name, - "amount" = amount, - )) - if(chosen == inserted_material) - data["chosen_material"] = inserted_material.name - - data["produced_coins"] = produced_coins - data["processing"] = processing - - return data; - -/obj/machinery/mineral/mint/ui_act(action, params, datum/tgui/ui) - . = ..() - if(.) - return - if(action == "startpress") - if (!processing) - if(produced_coins > 0) - log_econ("[produced_coins] coins were created by [src] in the last cycle.") - produced_coins = 0 - processing = TRUE - begin_processing() - return TRUE - if (action == "stoppress") - processing = FALSE - end_processing() - return TRUE - if (action == "changematerial") - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - for(var/datum/material/mat in materials.materials) - if (params["material_name"] == mat.name) - chosen = mat - return TRUE - -/obj/machinery/mineral/mint/proc/create_coins() - var/turf/T = get_step(src,output_dir) - var/temp_list = list() - temp_list[chosen] = 400 - if(T) - var/obj/item/O = new /obj/item/coin(src) - var/obj/item/storage/bag/money/B = locate(/obj/item/storage/bag/money, T) - O.set_custom_materials(temp_list) - if(!B) - B = new /obj/item/storage/bag/money(src) - unload_mineral(B) - O.forceMove(B) - SSblackbox.record_feedback("amount", "coins_minted", 1) +/**********************Mint**************************/ + + +/obj/machinery/mineral/mint + name = "coin press" + icon = 'icons/obj/economy.dmi' + icon_state = "coinpress0" + density = TRUE + input_dir = EAST + needs_item_input = TRUE + + var/produced_coins = 0 // how many coins the machine has made in it's last cycle + var/processing = FALSE + var/chosen = /datum/material/iron //which material will be used to make coins + + +/obj/machinery/mineral/mint/Initialize() + . = ..() + AddComponent(/datum/component/material_container, list( + /datum/material/iron, + /datum/material/plasma, + /datum/material/silver, + /datum/material/gold, + /datum/material/uranium, + /datum/material/titanium, + /datum/material/diamond, + /datum/material/bananium, + /datum/material/adamantine, + /datum/material/mythril, + /datum/material/plastic, + /datum/material/runite + ), MINERAL_MATERIAL_AMOUNT * 75, FALSE, /obj/item/stack) + chosen = SSmaterials.GetMaterialRef(chosen) + + +/obj/machinery/mineral/mint/pickup_item(datum/source, atom/movable/target, atom/oldLoc) + if(!istype(target, /obj/item/stack)) + return + + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + var/obj/item/stack/S = target + + if(materials.insert_item(S)) + qdel(S) + +/obj/machinery/mineral/mint/process() + if(processing) + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + var/datum/material/M = chosen + + if(!M) + processing = FALSE + icon_state = "coinpress0" + return + + icon_state = "coinpress1" + var/coin_mat = MINERAL_MATERIAL_AMOUNT + + for(var/sheets in 1 to 2) + if(materials.use_amount_mat(coin_mat, chosen)) + for(var/coin_to_make in 1 to 5) + create_coins() + produced_coins++ + else + var/found_new = FALSE + for(var/datum/material/inserted_material in materials.materials) + var/amount = materials.get_material_amount(inserted_material) + + if(amount) + chosen = inserted_material + found_new = TRUE + + if(!found_new) + processing = FALSE + else + end_processing() + icon_state = "coinpress0" + +/obj/machinery/mineral/mint/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "Mint", name) + ui.open() + +/obj/machinery/mineral/mint/ui_data() + var/list/data = list() + data["inserted_materials"] = list() + data["chosen_material"] = null + + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + for(var/datum/material/inserted_material in materials.materials) + var/amount = materials.get_material_amount(inserted_material) + if(!amount) + continue + data["inserted_materials"] += list(list( + "material" = inserted_material.name, + "amount" = amount, + )) + if(chosen == inserted_material) + data["chosen_material"] = inserted_material.name + + data["produced_coins"] = produced_coins + data["processing"] = processing + + return data; + +/obj/machinery/mineral/mint/ui_act(action, params, datum/tgui/ui) + . = ..() + if(.) + return + if(action == "startpress") + if (!processing) + if(produced_coins > 0) + log_econ("[produced_coins] coins were created by [src] in the last cycle.") + produced_coins = 0 + processing = TRUE + begin_processing() + return TRUE + if (action == "stoppress") + processing = FALSE + end_processing() + return TRUE + if (action == "changematerial") + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + for(var/datum/material/mat in materials.materials) + if (params["material_name"] == mat.name) + chosen = mat + return TRUE + +/obj/machinery/mineral/mint/proc/create_coins() + var/turf/T = get_step(src,output_dir) + var/temp_list = list() + temp_list[chosen] = 400 + if(T) + var/obj/item/O = new /obj/item/coin(src) + var/obj/item/storage/bag/money/B = locate(/obj/item/storage/bag/money, T) + O.set_custom_materials(temp_list) + if(!B) + B = new /obj/item/storage/bag/money(src) + unload_mineral(B) + O.forceMove(B) + SSblackbox.record_feedback("amount", "coins_minted", 1) diff --git a/code/modules/mining/money_bag.dm b/code/modules/mining/money_bag.dm index 00e9d52d36d8..005694221ecb 100644 --- a/code/modules/mining/money_bag.dm +++ b/code/modules/mining/money_bag.dm @@ -1,29 +1,29 @@ -/*****************************Money bag********************************/ - -/obj/item/storage/bag/money - name = "money bag" - icon_state = "moneybag" - force = 10 - throwforce = 0 - resistance_flags = FLAMMABLE - max_integrity = 100 - w_class = WEIGHT_CLASS_BULKY - -/obj/item/storage/bag/money/Initialize() - . = ..() - if(prob(20)) - icon_state = "moneybagalt" - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_NORMAL - STR.max_items = 40 - STR.max_combined_w_class = 40 - STR.set_holdable(list(/obj/item/coin, /obj/item/stack/spacecash, /obj/item/holochip)) - -/obj/item/storage/bag/money/vault/PopulateContents() - new /obj/item/coin/silver(src) - new /obj/item/coin/silver(src) - new /obj/item/coin/silver(src) - new /obj/item/coin/silver(src) - new /obj/item/coin/gold(src) - new /obj/item/coin/gold(src) - new /obj/item/coin/adamantine(src) +/*****************************Money bag********************************/ + +/obj/item/storage/bag/money + name = "money bag" + icon_state = "moneybag" + force = 10 + throwforce = 0 + resistance_flags = FLAMMABLE + max_integrity = 100 + w_class = WEIGHT_CLASS_BULKY + +/obj/item/storage/bag/money/Initialize() + . = ..() + if(prob(20)) + icon_state = "moneybagalt" + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_NORMAL + STR.max_items = 40 + STR.max_combined_w_class = 40 + STR.set_holdable(list(/obj/item/coin, /obj/item/stack/spacecash, /obj/item/holochip)) + +/obj/item/storage/bag/money/vault/PopulateContents() + new /obj/item/coin/silver(src) + new /obj/item/coin/silver(src) + new /obj/item/coin/silver(src) + new /obj/item/coin/silver(src) + new /obj/item/coin/gold(src) + new /obj/item/coin/gold(src) + new /obj/item/coin/adamantine(src) diff --git a/code/modules/mining/ores_coins.dm b/code/modules/mining/ores_coins.dm index 06657d04af13..ad019fdc51f8 100644 --- a/code/modules/mining/ores_coins.dm +++ b/code/modules/mining/ores_coins.dm @@ -1,485 +1,485 @@ - -#define GIBTONITE_QUALITY_HIGH 3 -#define GIBTONITE_QUALITY_MEDIUM 2 -#define GIBTONITE_QUALITY_LOW 1 - -#define ORESTACK_OVERLAYS_MAX 10 - -/**********************Mineral ores**************************/ - -/obj/item/stack/ore - name = "rock" - icon = 'icons/obj/mining.dmi' - icon_state = "ore" - item_state = "ore" - full_w_class = WEIGHT_CLASS_BULKY - singular_name = "ore chunk" - var/points = 0 //How many points this ore gets you from the ore redemption machine - var/refined_type = null //What this ore defaults to being refined into - var/mine_experience = 5 //How much experience do you get for mining this ore? - novariants = TRUE // Ore stacks handle their icon updates themselves to keep the illusion that there's more going - var/list/stack_overlays - var/scan_state = "" //Used by mineral turfs for their scan overlay. - var/spreadChance = 0 //Also used by mineral turfs for spreading veins - -/obj/item/stack/ore/update_overlays() - . = ..() - var/difference = min(ORESTACK_OVERLAYS_MAX, amount) - (LAZYLEN(stack_overlays)+1) - if(difference == 0) - return - else if(difference < 0 && LAZYLEN(stack_overlays)) //amount < stack_overlays, remove excess. - if (LAZYLEN(stack_overlays)-difference <= 0) - stack_overlays = null - else - stack_overlays.len += difference - else if(difference > 0) //amount > stack_overlays, add some. - for(var/i in 1 to difference) - var/mutable_appearance/newore = mutable_appearance(icon, icon_state) - newore.pixel_x = rand(-8,8) - newore.pixel_y = rand(-8,8) - LAZYADD(stack_overlays, newore) - if (stack_overlays) - . += stack_overlays - -/obj/item/stack/ore/welder_act(mob/living/user, obj/item/I) - ..() - if(!refined_type) - return TRUE - - if(I.use_tool(src, user, 0, volume=50, amount=15)) - new refined_type(drop_location()) - use(1) - - return TRUE - -/obj/item/stack/ore/fire_act(exposed_temperature, exposed_volume) - . = ..() - if(isnull(refined_type)) - return - else - var/probability = (rand(0,100))/100 - var/burn_value = probability*amount - var/amountrefined = round(burn_value, 1) - if(amountrefined < 1) - qdel(src) - else - new refined_type(drop_location(),amountrefined) - qdel(src) - -/obj/item/stack/ore/uranium - name = "uranium ore" - icon_state = "Uranium ore" - item_state = "Uranium ore" - singular_name = "uranium ore chunk" - points = 30 - material_flags = MATERIAL_NO_EFFECTS - custom_materials = list(/datum/material/uranium=MINERAL_MATERIAL_AMOUNT) - refined_type = /obj/item/stack/sheet/mineral/uranium - mine_experience = 6 - scan_state = "rock_Uranium" - spreadChance = 5 - -/obj/item/stack/ore/iron - name = "iron ore" - icon_state = "Iron ore" - item_state = "Iron ore" - singular_name = "iron ore chunk" - points = 1 - custom_materials = list(/datum/material/iron=MINERAL_MATERIAL_AMOUNT) - refined_type = /obj/item/stack/sheet/metal - mine_experience = 1 - scan_state = "rock_Iron" - spreadChance = 20 - -/obj/item/stack/ore/glass - name = "sand pile" - icon_state = "Glass ore" - item_state = "Glass ore" - singular_name = "sand pile" - points = 1 - custom_materials = list(/datum/material/glass=MINERAL_MATERIAL_AMOUNT) - refined_type = /obj/item/stack/sheet/glass - w_class = WEIGHT_CLASS_TINY - mine_experience = 0 //its sand - -GLOBAL_LIST_INIT(sand_recipes, list(\ - new /datum/stack_recipe("sandstone", /obj/item/stack/sheet/mineral/sandstone, 1, 1, 50),\ - new /datum/stack_recipe("aesthetic volcanic floor tile", /obj/item/stack/tile/basalt, 2, 1, 50)\ -)) - -/obj/item/stack/ore/glass/get_main_recipes() - . = ..() - . += GLOB.sand_recipes - -/obj/item/stack/ore/glass/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - if(..() || !ishuman(hit_atom)) - return - var/mob/living/carbon/human/C = hit_atom - if(C.is_eyes_covered()) - C.visible_message("[C]'s eye protection blocks the sand!", "Your eye protection blocks the sand!") - return - C.adjust_blurriness(6) - C.adjustStaminaLoss(15)//the pain from your eyes burning does stamina damage - C.confused += 5 - to_chat(C, "\The [src] gets into your eyes! The pain, it burns!") - qdel(src) - -/obj/item/stack/ore/glass/ex_act(severity, target) - if (severity == EXPLODE_NONE) - return - qdel(src) - -/obj/item/stack/ore/glass/basalt - name = "volcanic ash" - icon_state = "volcanic_sand" - item_state = "volcanic_sand" - singular_name = "volcanic ash pile" - mine_experience = 0 - -/obj/item/stack/ore/plasma - name = "plasma ore" - icon_state = "Plasma ore" - item_state = "Plasma ore" - singular_name = "plasma ore chunk" - points = 15 - custom_materials = list(/datum/material/plasma=MINERAL_MATERIAL_AMOUNT) - refined_type = /obj/item/stack/sheet/mineral/plasma - mine_experience = 5 - scan_state = "rock_Plasma" - spreadChance = 8 - -/obj/item/stack/ore/plasma/welder_act(mob/living/user, obj/item/I) - to_chat(user, "You can't hit a high enough temperature to smelt [src] properly!") - return TRUE - - -/obj/item/stack/ore/silver - name = "silver ore" - icon_state = "Silver ore" - item_state = "Silver ore" - singular_name = "silver ore chunk" - points = 16 - mine_experience = 3 - custom_materials = list(/datum/material/silver=MINERAL_MATERIAL_AMOUNT) - refined_type = /obj/item/stack/sheet/mineral/silver - scan_state = "rock_Silver" - spreadChance = 5 - -/obj/item/stack/ore/gold - name = "gold ore" - icon_state = "Gold ore" - item_state = "Gold ore" - singular_name = "gold ore chunk" - points = 18 - mine_experience = 5 - custom_materials = list(/datum/material/gold=MINERAL_MATERIAL_AMOUNT) - refined_type = /obj/item/stack/sheet/mineral/gold - scan_state = "rock_Gold" - spreadChance = 5 - -/obj/item/stack/ore/diamond - name = "diamond ore" - icon_state = "Diamond ore" - item_state = "Diamond ore" - singular_name = "diamond ore chunk" - points = 50 - custom_materials = list(/datum/material/diamond=MINERAL_MATERIAL_AMOUNT) - refined_type = /obj/item/stack/sheet/mineral/diamond - mine_experience = 10 - scan_state = "rock_Diamond" - -/obj/item/stack/ore/bananium - name = "bananium ore" - icon_state = "Bananium ore" - item_state = "Bananium ore" - singular_name = "bananium ore chunk" - points = 60 - custom_materials = list(/datum/material/bananium=MINERAL_MATERIAL_AMOUNT) - refined_type = /obj/item/stack/sheet/mineral/bananium - mine_experience = 15 - scan_state = "rock_Bananium" - -/obj/item/stack/ore/titanium - name = "titanium ore" - icon_state = "Titanium ore" - item_state = "Titanium ore" - singular_name = "titanium ore chunk" - points = 50 - custom_materials = list(/datum/material/titanium=MINERAL_MATERIAL_AMOUNT) - refined_type = /obj/item/stack/sheet/mineral/titanium - mine_experience = 3 - scan_state = "rock_Titanium" - spreadChance = 5 - -/obj/item/stack/ore/slag - name = "slag" - desc = "Completely useless." - icon_state = "slag" - item_state = "slag" - singular_name = "slag chunk" - -/obj/item/gibtonite - name = "gibtonite ore" - desc = "Extremely explosive if struck with mining equipment, Gibtonite is often used by miners to speed up their work by using it as a mining charge. This material is illegal to possess by unauthorized personnel under space law." - icon = 'icons/obj/mining.dmi' - icon_state = "Gibtonite ore" - item_state = "Gibtonite ore" - w_class = WEIGHT_CLASS_BULKY - throw_range = 0 - var/primed = FALSE - var/det_time = 100 - var/quality = GIBTONITE_QUALITY_LOW //How pure this gibtonite is, determines the explosion produced by it and is derived from the det_time of the rock wall it was taken from, higher value = better - var/attacher = "UNKNOWN" - var/det_timer - -/obj/item/gibtonite/ComponentInitialize() - . = ..() - AddComponent(/datum/component/two_handed, require_twohands=TRUE) - -/obj/item/gibtonite/Destroy() - qdel(wires) - wires = null - return ..() - -/obj/item/gibtonite/attackby(obj/item/I, mob/user, params) - if(!wires && istype(I, /obj/item/assembly/igniter)) - user.visible_message("[user] attaches [I] to [src].", "You attach [I] to [src].") - wires = new /datum/wires/explosive/gibtonite(src) - attacher = key_name(user) - qdel(I) - add_overlay("Gibtonite_igniter") - return - - if(wires && !primed) - if(is_wire_tool(I)) - wires.interact(user) - return - - if(I.tool_behaviour == TOOL_MINING || istype(I, /obj/item/resonator) || I.force >= 10) - GibtoniteReaction(user) - return - if(primed) - if(istype(I, /obj/item/mining_scanner) || istype(I, /obj/item/t_scanner/adv_mining_scanner) || I.tool_behaviour == TOOL_MULTITOOL) - primed = FALSE - if(det_timer) - deltimer(det_timer) - user.visible_message("The chain reaction stopped! ...The ore's quality looks diminished.", "You stopped the chain reaction. ...The ore's quality looks diminished.") - icon_state = "Gibtonite ore" - quality = GIBTONITE_QUALITY_LOW - return - ..() - -/obj/item/gibtonite/attack_self(user) - if(wires) - wires.interact(user) - else - ..() - -/obj/item/gibtonite/bullet_act(obj/projectile/P) - GibtoniteReaction(P.firer) - . = ..() - -/obj/item/gibtonite/ex_act() - GibtoniteReaction(null, 1) - -/obj/item/gibtonite/proc/GibtoniteReaction(mob/user, triggered_by = 0) - if(!primed) - primed = TRUE - playsound(src,'sound/effects/hit_on_shattered_glass.ogg',50,TRUE) - icon_state = "Gibtonite active" - var/notify_admins = FALSE - if(z != 5)//Only annoy the admins ingame if we're triggered off the mining zlevel - notify_admins = TRUE - - if(triggered_by == 1) - log_bomber(null, "An explosion has primed a", src, "for detonation", notify_admins) - else if(triggered_by == 2) - var/turf/bombturf = get_turf(src) - if(notify_admins) - message_admins("A signal has triggered a [name] to detonate at [ADMIN_VERBOSEJMP(bombturf)]. Igniter attacher: [ADMIN_LOOKUPFLW(attacher)]") - var/bomb_message = "A signal has primed a [name] for detonation at [AREACOORD(bombturf)]. Igniter attacher: [key_name(attacher)]." - log_game(bomb_message) - GLOB.bombers += bomb_message - else - user.visible_message("[user] strikes \the [src], causing a chain reaction!", "You strike \the [src], causing a chain reaction.") - log_bomber(user, "has primed a", src, "for detonation", notify_admins) - det_timer = addtimer(CALLBACK(src, .proc/detonate, notify_admins), det_time, TIMER_STOPPABLE) - -/obj/item/gibtonite/proc/detonate(notify_admins) - if(primed) - switch(quality) - if(GIBTONITE_QUALITY_HIGH) - explosion(src,2,4,9,adminlog = notify_admins) - if(GIBTONITE_QUALITY_MEDIUM) - explosion(src,1,2,5,adminlog = notify_admins) - if(GIBTONITE_QUALITY_LOW) - explosion(src,0,1,3,adminlog = notify_admins) - qdel(src) - -/obj/item/stack/ore/Initialize() - . = ..() - pixel_x = rand(0,16)-8 - pixel_y = rand(0,8)-8 - -/obj/item/stack/ore/ex_act(severity, target) - if (!severity || severity >= 2) - return - qdel(src) - - -/*****************************Coin********************************/ - -// The coin's value is a value of it's materials. -// Yes, the gold standard makes a come-back! -// This is the only way to make coins that are possible to produce on station actually worth anything. -/obj/item/coin - icon = 'icons/obj/economy.dmi' - name = "coin" - icon_state = "coin" - flags_1 = CONDUCT_1 - force = 1 - throwforce = 2 - w_class = WEIGHT_CLASS_TINY - custom_materials = list(/datum/material/iron = 400) - material_flags = MATERIAL_ADD_PREFIX | MATERIAL_COLOR | MATERIAL_AFFECT_STATISTICS - var/string_attached - var/list/sideslist = list("heads","tails") - var/cooldown = 0 - var/value - var/coinflip - item_flags = NO_MAT_REDEMPTION //You know, it's kind of a problem that money is worth more extrinsicly than intrinsically in this universe. - -/obj/item/coin/Initialize() - . = ..() - coinflip = pick(sideslist) - icon_state = "coin_[coinflip]" - pixel_x = rand(0,16)-8 - pixel_y = rand(0,8)-8 - -/obj/item/coin/set_custom_materials(var/list/materials, multiplier = 1) - . = ..() - value = 0 - for(var/i in custom_materials) - var/datum/material/M = i - value += M.value_per_unit * custom_materials[M] - -/obj/item/coin/get_item_credit_value() - return value - -/obj/item/coin/suicide_act(mob/living/user) - user.visible_message("[user] contemplates suicide with \the [src]!") - if (!attack_self(user)) - user.visible_message("[user] couldn't flip \the [src]!") - return SHAME - addtimer(CALLBACK(src, .proc/manual_suicide, user), 10)//10 = time takes for flip animation - return MANUAL_SUICIDE_NONLETHAL - -/obj/item/coin/proc/manual_suicide(mob/living/user) - var/index = sideslist.Find(coinflip) - if (index==2)//tails - user.visible_message("\the [src] lands on [coinflip]! [user] promptly falls over, dead!") - user.adjustOxyLoss(200) - user.death(0) - user.set_suicide(TRUE) - user.suicide_log() - else - user.visible_message("\the [src] lands on [coinflip]! [user] keeps on living!") - -/obj/item/coin/examine(mob/user) - . = ..() - . += "It's worth [value] credit\s." - -/obj/item/coin/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/stack/cable_coil)) - var/obj/item/stack/cable_coil/CC = W - if(string_attached) - to_chat(user, "There already is a string attached to this coin!") - return - - if (CC.use(1)) - add_overlay("coin_string_overlay") - string_attached = 1 - to_chat(user, "You attach a string to the coin.") - else - to_chat(user, "You need one length of cable to attach a string to the coin!") - return - else - ..() - -/obj/item/coin/wirecutter_act(mob/living/user, obj/item/I) - ..() - if(!string_attached) - return TRUE - - new /obj/item/stack/cable_coil(drop_location(), 1) - overlays = list() - string_attached = null - to_chat(user, "You detach the string from the coin.") - return TRUE - -/obj/item/coin/attack_self(mob/user) - if(cooldown < world.time) - if(string_attached) //does the coin have a wire attached - to_chat(user, "The coin won't flip very well with something attached!" ) - return FALSE//do not flip the coin - cooldown = world.time + 15 - flick("coin_[coinflip]_flip", src) - coinflip = pick(sideslist) - icon_state = "coin_[coinflip]" - playsound(user.loc, 'sound/items/coinflip.ogg', 50, TRUE) - var/oldloc = loc - sleep(15) - if(loc == oldloc && user && !user.incapacitated()) - user.visible_message("[user] flips [src]. It lands on [coinflip].", \ - "You flip [src]. It lands on [coinflip].", \ - "You hear the clattering of loose change.") - return TRUE//did the coin flip? useful for suicide_act - -/obj/item/coin/gold - custom_materials = list(/datum/material/gold = 400) - -/obj/item/coin/silver - custom_materials = list(/datum/material/silver = 400) - -/obj/item/coin/diamond - custom_materials = list(/datum/material/diamond = 400) - -/obj/item/coin/plasma - custom_materials = list(/datum/material/plasma = 400) - -/obj/item/coin/uranium - custom_materials = list(/datum/material/uranium = 400) - -/obj/item/coin/titanium - custom_materials = list(/datum/material/titanium = 400) - -/obj/item/coin/bananium - custom_materials = list(/datum/material/bananium = 400) - -/obj/item/coin/adamantine - custom_materials = list(/datum/material/adamantine = 400) - -/obj/item/coin/mythril - custom_materials = list(/datum/material/mythril = 400) - -/obj/item/coin/plastic - custom_materials = list(/datum/material/plastic = 400) - -/obj/item/coin/runite - custom_materials = list(/datum/material/runite = 400) - -/obj/item/coin/twoheaded - desc = "Hey, this coin's the same on both sides!" - sideslist = list("heads") - -/obj/item/coin/antagtoken - name = "antag token" - desc = "A novelty coin that helps the heart know what hard evidence cannot prove." - icon_state = "coin_valid" - custom_materials = list(/datum/material/plastic = 400) - sideslist = list("valid", "salad") - material_flags = NONE - -/obj/item/coin/iron - -#undef ORESTACK_OVERLAYS_MAX + +#define GIBTONITE_QUALITY_HIGH 3 +#define GIBTONITE_QUALITY_MEDIUM 2 +#define GIBTONITE_QUALITY_LOW 1 + +#define ORESTACK_OVERLAYS_MAX 10 + +/**********************Mineral ores**************************/ + +/obj/item/stack/ore + name = "rock" + icon = 'icons/obj/mining.dmi' + icon_state = "ore" + item_state = "ore" + full_w_class = WEIGHT_CLASS_BULKY + singular_name = "ore chunk" + var/points = 0 //How many points this ore gets you from the ore redemption machine + var/refined_type = null //What this ore defaults to being refined into + var/mine_experience = 5 //How much experience do you get for mining this ore? + novariants = TRUE // Ore stacks handle their icon updates themselves to keep the illusion that there's more going + var/list/stack_overlays + var/scan_state = "" //Used by mineral turfs for their scan overlay. + var/spreadChance = 0 //Also used by mineral turfs for spreading veins + +/obj/item/stack/ore/update_overlays() + . = ..() + var/difference = min(ORESTACK_OVERLAYS_MAX, amount) - (LAZYLEN(stack_overlays)+1) + if(difference == 0) + return + else if(difference < 0 && LAZYLEN(stack_overlays)) //amount < stack_overlays, remove excess. + if (LAZYLEN(stack_overlays)-difference <= 0) + stack_overlays = null + else + stack_overlays.len += difference + else if(difference > 0) //amount > stack_overlays, add some. + for(var/i in 1 to difference) + var/mutable_appearance/newore = mutable_appearance(icon, icon_state) + newore.pixel_x = rand(-8,8) + newore.pixel_y = rand(-8,8) + LAZYADD(stack_overlays, newore) + if (stack_overlays) + . += stack_overlays + +/obj/item/stack/ore/welder_act(mob/living/user, obj/item/I) + ..() + if(!refined_type) + return TRUE + + if(I.use_tool(src, user, 0, volume=50, amount=15)) + new refined_type(drop_location()) + use(1) + + return TRUE + +/obj/item/stack/ore/fire_act(exposed_temperature, exposed_volume) + . = ..() + if(isnull(refined_type)) + return + else + var/probability = (rand(0,100))/100 + var/burn_value = probability*amount + var/amountrefined = round(burn_value, 1) + if(amountrefined < 1) + qdel(src) + else + new refined_type(drop_location(),amountrefined) + qdel(src) + +/obj/item/stack/ore/uranium + name = "uranium ore" + icon_state = "Uranium ore" + item_state = "Uranium ore" + singular_name = "uranium ore chunk" + points = 30 + material_flags = MATERIAL_NO_EFFECTS + custom_materials = list(/datum/material/uranium=MINERAL_MATERIAL_AMOUNT) + refined_type = /obj/item/stack/sheet/mineral/uranium + mine_experience = 6 + scan_state = "rock_Uranium" + spreadChance = 5 + +/obj/item/stack/ore/iron + name = "iron ore" + icon_state = "Iron ore" + item_state = "Iron ore" + singular_name = "iron ore chunk" + points = 1 + custom_materials = list(/datum/material/iron=MINERAL_MATERIAL_AMOUNT) + refined_type = /obj/item/stack/sheet/metal + mine_experience = 1 + scan_state = "rock_Iron" + spreadChance = 20 + +/obj/item/stack/ore/glass + name = "sand pile" + icon_state = "Glass ore" + item_state = "Glass ore" + singular_name = "sand pile" + points = 1 + custom_materials = list(/datum/material/glass=MINERAL_MATERIAL_AMOUNT) + refined_type = /obj/item/stack/sheet/glass + w_class = WEIGHT_CLASS_TINY + mine_experience = 0 //its sand + +GLOBAL_LIST_INIT(sand_recipes, list(\ + new /datum/stack_recipe("sandstone", /obj/item/stack/sheet/mineral/sandstone, 1, 1, 50),\ + new /datum/stack_recipe("aesthetic volcanic floor tile", /obj/item/stack/tile/basalt, 2, 1, 50)\ +)) + +/obj/item/stack/ore/glass/get_main_recipes() + . = ..() + . += GLOB.sand_recipes + +/obj/item/stack/ore/glass/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + if(..() || !ishuman(hit_atom)) + return + var/mob/living/carbon/human/C = hit_atom + if(C.is_eyes_covered()) + C.visible_message("[C]'s eye protection blocks the sand!", "Your eye protection blocks the sand!") + return + C.adjust_blurriness(6) + C.adjustStaminaLoss(15)//the pain from your eyes burning does stamina damage + C.confused += 5 + to_chat(C, "\The [src] gets into your eyes! The pain, it burns!") + qdel(src) + +/obj/item/stack/ore/glass/ex_act(severity, target) + if (severity == EXPLODE_NONE) + return + qdel(src) + +/obj/item/stack/ore/glass/basalt + name = "volcanic ash" + icon_state = "volcanic_sand" + item_state = "volcanic_sand" + singular_name = "volcanic ash pile" + mine_experience = 0 + +/obj/item/stack/ore/plasma + name = "plasma ore" + icon_state = "Plasma ore" + item_state = "Plasma ore" + singular_name = "plasma ore chunk" + points = 15 + custom_materials = list(/datum/material/plasma=MINERAL_MATERIAL_AMOUNT) + refined_type = /obj/item/stack/sheet/mineral/plasma + mine_experience = 5 + scan_state = "rock_Plasma" + spreadChance = 8 + +/obj/item/stack/ore/plasma/welder_act(mob/living/user, obj/item/I) + to_chat(user, "You can't hit a high enough temperature to smelt [src] properly!") + return TRUE + + +/obj/item/stack/ore/silver + name = "silver ore" + icon_state = "Silver ore" + item_state = "Silver ore" + singular_name = "silver ore chunk" + points = 16 + mine_experience = 3 + custom_materials = list(/datum/material/silver=MINERAL_MATERIAL_AMOUNT) + refined_type = /obj/item/stack/sheet/mineral/silver + scan_state = "rock_Silver" + spreadChance = 5 + +/obj/item/stack/ore/gold + name = "gold ore" + icon_state = "Gold ore" + item_state = "Gold ore" + singular_name = "gold ore chunk" + points = 18 + mine_experience = 5 + custom_materials = list(/datum/material/gold=MINERAL_MATERIAL_AMOUNT) + refined_type = /obj/item/stack/sheet/mineral/gold + scan_state = "rock_Gold" + spreadChance = 5 + +/obj/item/stack/ore/diamond + name = "diamond ore" + icon_state = "Diamond ore" + item_state = "Diamond ore" + singular_name = "diamond ore chunk" + points = 50 + custom_materials = list(/datum/material/diamond=MINERAL_MATERIAL_AMOUNT) + refined_type = /obj/item/stack/sheet/mineral/diamond + mine_experience = 10 + scan_state = "rock_Diamond" + +/obj/item/stack/ore/bananium + name = "bananium ore" + icon_state = "Bananium ore" + item_state = "Bananium ore" + singular_name = "bananium ore chunk" + points = 60 + custom_materials = list(/datum/material/bananium=MINERAL_MATERIAL_AMOUNT) + refined_type = /obj/item/stack/sheet/mineral/bananium + mine_experience = 15 + scan_state = "rock_Bananium" + +/obj/item/stack/ore/titanium + name = "titanium ore" + icon_state = "Titanium ore" + item_state = "Titanium ore" + singular_name = "titanium ore chunk" + points = 50 + custom_materials = list(/datum/material/titanium=MINERAL_MATERIAL_AMOUNT) + refined_type = /obj/item/stack/sheet/mineral/titanium + mine_experience = 3 + scan_state = "rock_Titanium" + spreadChance = 5 + +/obj/item/stack/ore/slag + name = "slag" + desc = "Completely useless." + icon_state = "slag" + item_state = "slag" + singular_name = "slag chunk" + +/obj/item/gibtonite + name = "gibtonite ore" + desc = "Extremely explosive if struck with mining equipment, Gibtonite is often used by miners to speed up their work by using it as a mining charge. This material is illegal to possess by unauthorized personnel under space law." + icon = 'icons/obj/mining.dmi' + icon_state = "Gibtonite ore" + item_state = "Gibtonite ore" + w_class = WEIGHT_CLASS_BULKY + throw_range = 0 + var/primed = FALSE + var/det_time = 100 + var/quality = GIBTONITE_QUALITY_LOW //How pure this gibtonite is, determines the explosion produced by it and is derived from the det_time of the rock wall it was taken from, higher value = better + var/attacher = "UNKNOWN" + var/det_timer + +/obj/item/gibtonite/ComponentInitialize() + . = ..() + AddComponent(/datum/component/two_handed, require_twohands=TRUE) + +/obj/item/gibtonite/Destroy() + qdel(wires) + wires = null + return ..() + +/obj/item/gibtonite/attackby(obj/item/I, mob/user, params) + if(!wires && istype(I, /obj/item/assembly/igniter)) + user.visible_message("[user] attaches [I] to [src].", "You attach [I] to [src].") + wires = new /datum/wires/explosive/gibtonite(src) + attacher = key_name(user) + qdel(I) + add_overlay("Gibtonite_igniter") + return + + if(wires && !primed) + if(is_wire_tool(I)) + wires.interact(user) + return + + if(I.tool_behaviour == TOOL_MINING || istype(I, /obj/item/resonator) || I.force >= 10) + GibtoniteReaction(user) + return + if(primed) + if(istype(I, /obj/item/mining_scanner) || istype(I, /obj/item/t_scanner/adv_mining_scanner) || I.tool_behaviour == TOOL_MULTITOOL) + primed = FALSE + if(det_timer) + deltimer(det_timer) + user.visible_message("The chain reaction stopped! ...The ore's quality looks diminished.", "You stopped the chain reaction. ...The ore's quality looks diminished.") + icon_state = "Gibtonite ore" + quality = GIBTONITE_QUALITY_LOW + return + ..() + +/obj/item/gibtonite/attack_self(user) + if(wires) + wires.interact(user) + else + ..() + +/obj/item/gibtonite/bullet_act(obj/projectile/P) + GibtoniteReaction(P.firer) + . = ..() + +/obj/item/gibtonite/ex_act() + GibtoniteReaction(null, 1) + +/obj/item/gibtonite/proc/GibtoniteReaction(mob/user, triggered_by = 0) + if(!primed) + primed = TRUE + playsound(src,'sound/effects/hit_on_shattered_glass.ogg',50,TRUE) + icon_state = "Gibtonite active" + var/notify_admins = FALSE + if(z != 5)//Only annoy the admins ingame if we're triggered off the mining zlevel + notify_admins = TRUE + + if(triggered_by == 1) + log_bomber(null, "An explosion has primed a", src, "for detonation", notify_admins) + else if(triggered_by == 2) + var/turf/bombturf = get_turf(src) + if(notify_admins) + message_admins("A signal has triggered a [name] to detonate at [ADMIN_VERBOSEJMP(bombturf)]. Igniter attacher: [ADMIN_LOOKUPFLW(attacher)]") + var/bomb_message = "A signal has primed a [name] for detonation at [AREACOORD(bombturf)]. Igniter attacher: [key_name(attacher)]." + log_game(bomb_message) + GLOB.bombers += bomb_message + else + user.visible_message("[user] strikes \the [src], causing a chain reaction!", "You strike \the [src], causing a chain reaction.") + log_bomber(user, "has primed a", src, "for detonation", notify_admins) + det_timer = addtimer(CALLBACK(src, .proc/detonate, notify_admins), det_time, TIMER_STOPPABLE) + +/obj/item/gibtonite/proc/detonate(notify_admins) + if(primed) + switch(quality) + if(GIBTONITE_QUALITY_HIGH) + explosion(src,2,4,9,adminlog = notify_admins) + if(GIBTONITE_QUALITY_MEDIUM) + explosion(src,1,2,5,adminlog = notify_admins) + if(GIBTONITE_QUALITY_LOW) + explosion(src,0,1,3,adminlog = notify_admins) + qdel(src) + +/obj/item/stack/ore/Initialize() + . = ..() + pixel_x = rand(0,16)-8 + pixel_y = rand(0,8)-8 + +/obj/item/stack/ore/ex_act(severity, target) + if (!severity || severity >= 2) + return + qdel(src) + + +/*****************************Coin********************************/ + +// The coin's value is a value of it's materials. +// Yes, the gold standard makes a come-back! +// This is the only way to make coins that are possible to produce on station actually worth anything. +/obj/item/coin + icon = 'icons/obj/economy.dmi' + name = "coin" + icon_state = "coin" + flags_1 = CONDUCT_1 + force = 1 + throwforce = 2 + w_class = WEIGHT_CLASS_TINY + custom_materials = list(/datum/material/iron = 400) + material_flags = MATERIAL_ADD_PREFIX | MATERIAL_COLOR | MATERIAL_AFFECT_STATISTICS + var/string_attached + var/list/sideslist = list("heads","tails") + var/cooldown = 0 + var/value + var/coinflip + item_flags = NO_MAT_REDEMPTION //You know, it's kind of a problem that money is worth more extrinsicly than intrinsically in this universe. + +/obj/item/coin/Initialize() + . = ..() + coinflip = pick(sideslist) + icon_state = "coin_[coinflip]" + pixel_x = rand(0,16)-8 + pixel_y = rand(0,8)-8 + +/obj/item/coin/set_custom_materials(var/list/materials, multiplier = 1) + . = ..() + value = 0 + for(var/i in custom_materials) + var/datum/material/M = i + value += M.value_per_unit * custom_materials[M] + +/obj/item/coin/get_item_credit_value() + return value + +/obj/item/coin/suicide_act(mob/living/user) + user.visible_message("[user] contemplates suicide with \the [src]!") + if (!attack_self(user)) + user.visible_message("[user] couldn't flip \the [src]!") + return SHAME + addtimer(CALLBACK(src, .proc/manual_suicide, user), 10)//10 = time takes for flip animation + return MANUAL_SUICIDE_NONLETHAL + +/obj/item/coin/proc/manual_suicide(mob/living/user) + var/index = sideslist.Find(coinflip) + if (index==2)//tails + user.visible_message("\the [src] lands on [coinflip]! [user] promptly falls over, dead!") + user.adjustOxyLoss(200) + user.death(0) + user.set_suicide(TRUE) + user.suicide_log() + else + user.visible_message("\the [src] lands on [coinflip]! [user] keeps on living!") + +/obj/item/coin/examine(mob/user) + . = ..() + . += "It's worth [value] credit\s." + +/obj/item/coin/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/CC = W + if(string_attached) + to_chat(user, "There already is a string attached to this coin!") + return + + if (CC.use(1)) + add_overlay("coin_string_overlay") + string_attached = 1 + to_chat(user, "You attach a string to the coin.") + else + to_chat(user, "You need one length of cable to attach a string to the coin!") + return + else + ..() + +/obj/item/coin/wirecutter_act(mob/living/user, obj/item/I) + ..() + if(!string_attached) + return TRUE + + new /obj/item/stack/cable_coil(drop_location(), 1) + overlays = list() + string_attached = null + to_chat(user, "You detach the string from the coin.") + return TRUE + +/obj/item/coin/attack_self(mob/user) + if(cooldown < world.time) + if(string_attached) //does the coin have a wire attached + to_chat(user, "The coin won't flip very well with something attached!" ) + return FALSE//do not flip the coin + cooldown = world.time + 15 + flick("coin_[coinflip]_flip", src) + coinflip = pick(sideslist) + icon_state = "coin_[coinflip]" + playsound(user.loc, 'sound/items/coinflip.ogg', 50, TRUE) + var/oldloc = loc + sleep(15) + if(loc == oldloc && user && !user.incapacitated()) + user.visible_message("[user] flips [src]. It lands on [coinflip].", \ + "You flip [src]. It lands on [coinflip].", \ + "You hear the clattering of loose change.") + return TRUE//did the coin flip? useful for suicide_act + +/obj/item/coin/gold + custom_materials = list(/datum/material/gold = 400) + +/obj/item/coin/silver + custom_materials = list(/datum/material/silver = 400) + +/obj/item/coin/diamond + custom_materials = list(/datum/material/diamond = 400) + +/obj/item/coin/plasma + custom_materials = list(/datum/material/plasma = 400) + +/obj/item/coin/uranium + custom_materials = list(/datum/material/uranium = 400) + +/obj/item/coin/titanium + custom_materials = list(/datum/material/titanium = 400) + +/obj/item/coin/bananium + custom_materials = list(/datum/material/bananium = 400) + +/obj/item/coin/adamantine + custom_materials = list(/datum/material/adamantine = 400) + +/obj/item/coin/mythril + custom_materials = list(/datum/material/mythril = 400) + +/obj/item/coin/plastic + custom_materials = list(/datum/material/plastic = 400) + +/obj/item/coin/runite + custom_materials = list(/datum/material/runite = 400) + +/obj/item/coin/twoheaded + desc = "Hey, this coin's the same on both sides!" + sideslist = list("heads") + +/obj/item/coin/antagtoken + name = "antag token" + desc = "A novelty coin that helps the heart know what hard evidence cannot prove." + icon_state = "coin_valid" + custom_materials = list(/datum/material/plastic = 400) + sideslist = list("valid", "salad") + material_flags = NONE + +/obj/item/coin/iron + +#undef ORESTACK_OVERLAYS_MAX diff --git a/code/modules/mining/satchel_ore_boxdm.dm b/code/modules/mining/satchel_ore_boxdm.dm index 10961cfa6097..4de34ecdba15 100644 --- a/code/modules/mining/satchel_ore_boxdm.dm +++ b/code/modules/mining/satchel_ore_boxdm.dm @@ -1,105 +1,101 @@ - -/**********************Ore box**************************/ - -/obj/structure/ore_box - icon = 'icons/obj/mining.dmi' - icon_state = "orebox" - name = "ore box" - desc = "A heavy wooden box, which can be filled with a lot of ores." - density = TRUE - pressure_resistance = 5*ONE_ATMOSPHERE - - var/ui_x = 335 - var/ui_y = 415 - -/obj/structure/ore_box/attackby(obj/item/W, mob/user, params) - if (istype(W, /obj/item/stack/ore)) - user.transferItemToLoc(W, src) - else if(SEND_SIGNAL(W, COMSIG_CONTAINS_STORAGE)) - SEND_SIGNAL(W, COMSIG_TRY_STORAGE_TAKE_TYPE, /obj/item/stack/ore, src) - to_chat(user, "You empty the ore in [W] into \the [src].") - else - return ..() - -/obj/structure/ore_box/ComponentInitialize() - . = ..() - AddComponent(/datum/component/rad_insulation, 0.01) //please datum mats no more cancer - -/obj/structure/ore_box/crowbar_act(mob/living/user, obj/item/I) - if(I.use_tool(src, user, 50, volume=50)) - user.visible_message("[user] pries \the [src] apart.", - "You pry apart \the [src].", - "You hear splitting wood.") - deconstruct(TRUE, user) - return TRUE - -/obj/structure/ore_box/examine(mob/living/user) - if(Adjacent(user) && istype(user)) - ui_interact(user) - . = ..() - -/obj/structure/ore_box/attack_hand(mob/user) - . = ..() - if(.) - return - if(Adjacent(user)) - ui_interact(user) - -/obj/structure/ore_box/attack_robot(mob/user) - if(Adjacent(user)) - ui_interact(user) - -/obj/structure/ore_box/proc/dump_box_contents() - var/drop = drop_location() - for(var/obj/item/stack/ore/O in src) - if(QDELETED(O)) - continue - if(QDELETED(src)) - break - O.forceMove(drop) - if(TICK_CHECK) - stoplag() - drop = drop_location() - -/obj/structure/ore_box/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "OreBox", name, ui_x, ui_y, master_ui, state) - ui.open() - -/obj/structure/ore_box/ui_data() - var/contents = list() - for(var/obj/item/stack/ore/O in src) - contents[O.type] += O.amount - - var/data = list() - data["materials"] = list() - for(var/type in contents) - var/obj/item/stack/ore/O = type - var/name = initial(O.name) - data["materials"] += list(list("name" = name, "amount" = contents[type], "id" = type)) - - return data - -/obj/structure/ore_box/ui_act(action, params) - if(..()) - return - if(!Adjacent(usr)) - return - add_fingerprint(usr) - usr.set_machine(src) - switch(action) - if("removeall") - dump_box_contents() - to_chat(usr, "You open the release hatch on the box..") - -/obj/structure/ore_box/deconstruct(disassembled = TRUE, mob/user) - var/obj/item/stack/sheet/mineral/wood/WD = new (loc, 4) - if(user) - WD.add_fingerprint(user) - dump_box_contents() - qdel(src) - -/obj/structure/ore_box/onTransitZ() - return + +/**********************Ore box**************************/ + +/obj/structure/ore_box + icon = 'icons/obj/mining.dmi' + icon_state = "orebox" + name = "ore box" + desc = "A heavy wooden box, which can be filled with a lot of ores." + density = TRUE + pressure_resistance = 5*ONE_ATMOSPHERE + +/obj/structure/ore_box/attackby(obj/item/W, mob/user, params) + if (istype(W, /obj/item/stack/ore)) + user.transferItemToLoc(W, src) + else if(SEND_SIGNAL(W, COMSIG_CONTAINS_STORAGE)) + SEND_SIGNAL(W, COMSIG_TRY_STORAGE_TAKE_TYPE, /obj/item/stack/ore, src) + to_chat(user, "You empty the ore in [W] into \the [src].") + else + return ..() + +/obj/structure/ore_box/ComponentInitialize() + . = ..() + AddComponent(/datum/component/rad_insulation, 0.01) //please datum mats no more cancer + +/obj/structure/ore_box/crowbar_act(mob/living/user, obj/item/I) + if(I.use_tool(src, user, 50, volume=50)) + user.visible_message("[user] pries \the [src] apart.", + "You pry apart \the [src].", + "You hear splitting wood.") + deconstruct(TRUE, user) + return TRUE + +/obj/structure/ore_box/examine(mob/living/user) + if(Adjacent(user) && istype(user)) + ui_interact(user) + . = ..() + +/obj/structure/ore_box/attack_hand(mob/user) + . = ..() + if(.) + return + if(Adjacent(user)) + ui_interact(user) + +/obj/structure/ore_box/attack_robot(mob/user) + if(Adjacent(user)) + ui_interact(user) + +/obj/structure/ore_box/proc/dump_box_contents() + var/drop = drop_location() + for(var/obj/item/stack/ore/O in src) + if(QDELETED(O)) + continue + if(QDELETED(src)) + break + O.forceMove(drop) + if(TICK_CHECK) + stoplag() + drop = drop_location() + +/obj/structure/ore_box/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "OreBox", name) + ui.open() + +/obj/structure/ore_box/ui_data() + var/contents = list() + for(var/obj/item/stack/ore/O in src) + contents[O.type] += O.amount + + var/data = list() + data["materials"] = list() + for(var/type in contents) + var/obj/item/stack/ore/O = type + var/name = initial(O.name) + data["materials"] += list(list("name" = name, "amount" = contents[type], "id" = type)) + + return data + +/obj/structure/ore_box/ui_act(action, params) + if(..()) + return + if(!Adjacent(usr)) + return + add_fingerprint(usr) + usr.set_machine(src) + switch(action) + if("removeall") + dump_box_contents() + to_chat(usr, "You open the release hatch on the box..") + +/obj/structure/ore_box/deconstruct(disassembled = TRUE, mob/user) + var/obj/item/stack/sheet/mineral/wood/WD = new (loc, 4) + if(user) + WD.add_fingerprint(user) + dump_box_contents() + qdel(src) + +/obj/structure/ore_box/onTransitZ() + return diff --git a/code/modules/mob/camera/camera.dm b/code/modules/mob/camera/camera.dm index 61543809ba38..8fb3cb17eab4 100644 --- a/code/modules/mob/camera/camera.dm +++ b/code/modules/mob/camera/camera.dm @@ -1,27 +1,27 @@ -// Camera mob, used by AI camera and blob. - -/mob/camera - name = "camera mob" - density = FALSE - move_force = INFINITY - move_resist = INFINITY - status_flags = GODMODE // You can't damage it. - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - see_in_dark = 7 - invisibility = INVISIBILITY_ABSTRACT // No one can see us - sight = SEE_SELF - move_on_shuttle = FALSE - -/mob/camera/experience_pressure_difference() - return - -/mob/camera/forceMove(atom/destination) - var/oldloc = loc - loc = destination - Moved(oldloc, NONE, TRUE) - -/mob/camera/canUseStorage() - return FALSE - -/mob/camera/emote(act, m_type=1, message = null, intentional = FALSE) - return FALSE +// Camera mob, used by AI camera and blob. + +/mob/camera + name = "camera mob" + density = FALSE + move_force = INFINITY + move_resist = INFINITY + status_flags = GODMODE // You can't damage it. + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + see_in_dark = 7 + invisibility = INVISIBILITY_ABSTRACT // No one can see us + sight = SEE_SELF + move_on_shuttle = FALSE + +/mob/camera/experience_pressure_difference() + return + +/mob/camera/forceMove(atom/destination) + var/oldloc = loc + loc = destination + Moved(oldloc, NONE, TRUE) + +/mob/camera/canUseStorage() + return FALSE + +/mob/camera/emote(act, m_type=1, message = null, intentional = FALSE) + return FALSE diff --git a/code/modules/mob/dead/dead.dm b/code/modules/mob/dead/dead.dm index 8caef04e3d4b..bc5d2d6ef01e 100644 --- a/code/modules/mob/dead/dead.dm +++ b/code/modules/mob/dead/dead.dm @@ -1,137 +1,137 @@ -//Dead mobs can exist whenever. This is needful - -INITIALIZE_IMMEDIATE(/mob/dead) - -/mob/dead - sight = SEE_TURFS | SEE_MOBS | SEE_OBJS | SEE_SELF - move_resist = INFINITY - throwforce = 0 - -/mob/dead/Initialize() - SHOULD_CALL_PARENT(FALSE) - if(flags_1 & INITIALIZED_1) - stack_trace("Warning: [src]([type]) initialized multiple times!") - flags_1 |= INITIALIZED_1 - tag = "mob_[next_mob_id++]" - add_to_mob_list() - - prepare_huds() - - if(length(CONFIG_GET(keyed_list/cross_server))) - verbs += /mob/dead/proc/server_hop - set_focus(src) - return INITIALIZE_HINT_NORMAL - -/mob/dead/canUseStorage() - return FALSE - -/mob/dead/dust(just_ash, drop_items, force) //ghosts can't be vaporised. - return - -/mob/dead/gib() //ghosts can't be gibbed. - return - -/mob/dead/ConveyorMove() //lol - return - -/mob/dead/forceMove(atom/destination) - var/turf/old_turf = get_turf(src) - var/turf/new_turf = get_turf(destination) - if (old_turf?.z != new_turf?.z) - onTransitZ(old_turf?.z, new_turf?.z) - var/oldloc = loc - loc = destination - Moved(oldloc, NONE, TRUE) - -/mob/dead/Stat() - ..() - - if(!statpanel("Status")) - return - stat(null, "Game Mode: [SSticker.hide_mode ? "Secret" : "[GLOB.master_mode]"]") - - if(SSticker.HasRoundStarted()) - return - - var/time_remaining = SSticker.GetTimeLeft() - if(time_remaining > 0) - stat(null, "Time To Start: [round(time_remaining/10)]s") - else if(time_remaining == -10) - stat(null, "Time To Start: DELAYED") - else - stat(null, "Time To Start: SOON") - - stat(null, "Players: [SSticker.totalPlayers]") - if(client.holder) - stat(null, "Players Ready: [SSticker.totalPlayersReady]") - -/mob/dead/proc/server_hop() - set category = "OOC" - set name = "Server Hop!" - set desc= "Jump to the other server" - if(notransform) - return - var/list/our_id = CONFIG_GET(string/cross_comms_name) - var/list/csa = CONFIG_GET(keyed_list/cross_server) - our_id - var/pick - switch(csa.len) - if(0) - verbs -= /mob/dead/proc/server_hop - to_chat(src, "Server Hop has been disabled.") - if(1) - pick = csa[1] - else - pick = input(src, "Pick a server to jump to", "Server Hop") as null|anything in csa - - if(!pick) - return - - var/addr = csa[pick] - - if(alert(src, "Jump to server [pick] ([addr])?", "Server Hop", "Yes", "No") != "Yes") - return - - var/client/C = client - to_chat(C, "Sending you to [pick].") - new /obj/screen/splash(C) - - notransform = TRUE - sleep(29) //let the animation play - notransform = FALSE - - if(!C) - return - - winset(src, null, "command=.options") //other wise the user never knows if byond is downloading resources - - C << link("[addr]?server_hop=[key]") - -/mob/dead/proc/update_z(new_z) // 1+ to register, null to unregister - if (registered_z != new_z) - if (registered_z) - SSmobs.dead_players_by_zlevel[registered_z] -= src - if (client) - if (new_z) - SSmobs.dead_players_by_zlevel[new_z] += src - registered_z = new_z - else - registered_z = null - -/mob/dead/Login() - . = ..() - if(!. || !client) - return FALSE - var/turf/T = get_turf(src) - if (isturf(T)) - update_z(T.z) - -/mob/dead/auto_deadmin_on_login() - return - -/mob/dead/Logout() - update_z(null) - return ..() - -/mob/dead/onTransitZ(old_z,new_z) - ..() - update_z(new_z) +//Dead mobs can exist whenever. This is needful + +INITIALIZE_IMMEDIATE(/mob/dead) + +/mob/dead + sight = SEE_TURFS | SEE_MOBS | SEE_OBJS | SEE_SELF + move_resist = INFINITY + throwforce = 0 + +/mob/dead/Initialize() + SHOULD_CALL_PARENT(FALSE) + if(flags_1 & INITIALIZED_1) + stack_trace("Warning: [src]([type]) initialized multiple times!") + flags_1 |= INITIALIZED_1 + tag = "mob_[next_mob_id++]" + add_to_mob_list() + + prepare_huds() + + if(length(CONFIG_GET(keyed_list/cross_server))) + verbs += /mob/dead/proc/server_hop + set_focus(src) + return INITIALIZE_HINT_NORMAL + +/mob/dead/canUseStorage() + return FALSE + +/mob/dead/dust(just_ash, drop_items, force) //ghosts can't be vaporised. + return + +/mob/dead/gib() //ghosts can't be gibbed. + return + +/mob/dead/ConveyorMove() //lol + return + +/mob/dead/forceMove(atom/destination) + var/turf/old_turf = get_turf(src) + var/turf/new_turf = get_turf(destination) + if (old_turf?.z != new_turf?.z) + onTransitZ(old_turf?.z, new_turf?.z) + var/oldloc = loc + loc = destination + Moved(oldloc, NONE, TRUE) + +/mob/dead/Stat() + ..() + + if(!statpanel("Status")) + return + stat(null, "Game Mode: [SSticker.hide_mode ? "Secret" : "[GLOB.master_mode]"]") + + if(SSticker.HasRoundStarted()) + return + + var/time_remaining = SSticker.GetTimeLeft() + if(time_remaining > 0) + stat(null, "Time To Start: [round(time_remaining/10)]s") + else if(time_remaining == -10) + stat(null, "Time To Start: DELAYED") + else + stat(null, "Time To Start: SOON") + + stat(null, "Players: [SSticker.totalPlayers]") + if(client.holder) + stat(null, "Players Ready: [SSticker.totalPlayersReady]") + +/mob/dead/proc/server_hop() + set category = "OOC" + set name = "Server Hop!" + set desc= "Jump to the other server" + if(notransform) + return + var/list/our_id = CONFIG_GET(string/cross_comms_name) + var/list/csa = CONFIG_GET(keyed_list/cross_server) - our_id + var/pick + switch(csa.len) + if(0) + verbs -= /mob/dead/proc/server_hop + to_chat(src, "Server Hop has been disabled.") + if(1) + pick = csa[1] + else + pick = input(src, "Pick a server to jump to", "Server Hop") as null|anything in csa + + if(!pick) + return + + var/addr = csa[pick] + + if(alert(src, "Jump to server [pick] ([addr])?", "Server Hop", "Yes", "No") != "Yes") + return + + var/client/C = client + to_chat(C, "Sending you to [pick].") + new /obj/screen/splash(C) + + notransform = TRUE + sleep(29) //let the animation play + notransform = FALSE + + if(!C) + return + + winset(src, null, "command=.options") //other wise the user never knows if byond is downloading resources + + C << link("[addr]?server_hop=[key]") + +/mob/dead/proc/update_z(new_z) // 1+ to register, null to unregister + if (registered_z != new_z) + if (registered_z) + SSmobs.dead_players_by_zlevel[registered_z] -= src + if (client) + if (new_z) + SSmobs.dead_players_by_zlevel[new_z] += src + registered_z = new_z + else + registered_z = null + +/mob/dead/Login() + . = ..() + if(!. || !client) + return FALSE + var/turf/T = get_turf(src) + if (isturf(T)) + update_z(T.z) + +/mob/dead/auto_deadmin_on_login() + return + +/mob/dead/Logout() + update_z(null) + return ..() + +/mob/dead/onTransitZ(old_z,new_z) + ..() + update_z(new_z) diff --git a/code/modules/mob/dead/new_player/login.dm b/code/modules/mob/dead/new_player/login.dm index a040bf2ba0ec..237ce7f9e7eb 100644 --- a/code/modules/mob/dead/new_player/login.dm +++ b/code/modules/mob/dead/new_player/login.dm @@ -1,38 +1,38 @@ -/mob/dead/new_player/Login() - if(!client) - return - if(CONFIG_GET(flag/use_exp_tracking)) - client.set_exp_from_db() - client.set_db_player_flags() - if(!mind) - mind = new /datum/mind(key) - mind.active = 1 - mind.current = src - - . = ..() - if(!. || !client) - return FALSE - - var/motd = global.config.motd - if(motd) - to_chat(src, "
                    [motd]
                    ", handle_whitespace=FALSE) - - if(GLOB.admin_notice) - to_chat(src, "Admin Notice:\n \t [GLOB.admin_notice]") - - var/spc = CONFIG_GET(number/soft_popcap) - if(spc && living_player_count() >= spc) - to_chat(src, "Server Notice:\n \t [CONFIG_GET(string/soft_popcap_message)]") - - sight |= SEE_TURFS - - new_player_panel() - client.playtitlemusic() - if(SSticker.current_state < GAME_STATE_SETTING_UP) - var/tl = SSticker.GetTimeLeft() - var/postfix - if(tl > 0) - postfix = "in about [DisplayTimeText(tl)]" - else - postfix = "soon" - to_chat(src, "Please set up your character and select \"Ready\". The game will start [postfix].") +/mob/dead/new_player/Login() + if(!client) + return + if(CONFIG_GET(flag/use_exp_tracking)) + client.set_exp_from_db() + client.set_db_player_flags() + if(!mind) + mind = new /datum/mind(key) + mind.active = 1 + mind.current = src + + . = ..() + if(!. || !client) + return FALSE + + var/motd = global.config.motd + if(motd) + to_chat(src, "
                    [motd]
                    ", handle_whitespace=FALSE) + + if(GLOB.admin_notice) + to_chat(src, "Admin Notice:\n \t [GLOB.admin_notice]") + + var/spc = CONFIG_GET(number/soft_popcap) + if(spc && living_player_count() >= spc) + to_chat(src, "Server Notice:\n \t [CONFIG_GET(string/soft_popcap_message)]") + + sight |= SEE_TURFS + + new_player_panel() + client.playtitlemusic() + if(SSticker.current_state < GAME_STATE_SETTING_UP) + var/tl = SSticker.GetTimeLeft() + var/postfix + if(tl > 0) + postfix = "in about [DisplayTimeText(tl)]" + else + postfix = "soon" + to_chat(src, "Please set up your character and select \"Ready\". The game will start [postfix].") diff --git a/code/modules/mob/dead/new_player/logout.dm b/code/modules/mob/dead/new_player/logout.dm index a0e392322396..849ab6fc69dd 100644 --- a/code/modules/mob/dead/new_player/logout.dm +++ b/code/modules/mob/dead/new_player/logout.dm @@ -1,7 +1,7 @@ -/mob/dead/new_player/Logout() - ready = 0 - ..() - if(!spawning)//Here so that if they are spawning and log out, the other procs can play out and they will have a mob to come back to. - key = null//We null their key before deleting the mob, so they are properly kicked out. - qdel(src) - return +/mob/dead/new_player/Logout() + ready = 0 + ..() + if(!spawning)//Here so that if they are spawning and log out, the other procs can play out and they will have a mob to come back to. + key = null//We null their key before deleting the mob, so they are properly kicked out. + qdel(src) + return diff --git a/code/modules/mob/dead/new_player/new_player.dm b/code/modules/mob/dead/new_player/new_player.dm index e8662e7a3bcd..9a79b2bf41ec 100644 --- a/code/modules/mob/dead/new_player/new_player.dm +++ b/code/modules/mob/dead/new_player/new_player.dm @@ -1,596 +1,615 @@ -#define LINKIFY_READY(string, value) "[string]" - -/mob/dead/new_player - var/ready = 0 - var/spawning = 0//Referenced when you want to delete the new_player later on in the code. - - flags_1 = NONE - - invisibility = INVISIBILITY_ABSTRACT - - density = FALSE - stat = DEAD - hud_possible = list() - - var/mob/living/new_character //for instant transfer once the round is set up - - //Used to make sure someone doesn't get spammed with messages if they're ineligible for roles - var/ineligible_for_roles = FALSE - -/mob/dead/new_player/Initialize() - if(client && SSticker.state == GAME_STATE_STARTUP) - var/obj/screen/splash/S = new(client, TRUE, TRUE) - S.Fade(TRUE) - - if(length(GLOB.newplayer_start)) - forceMove(pick(GLOB.newplayer_start)) - else - forceMove(locate(1,1,1)) - - ComponentInitialize() - - . = ..() - - GLOB.new_player_list += src - -/mob/dead/new_player/Destroy() - GLOB.new_player_list -= src - return ..() - -/mob/dead/new_player/prepare_huds() - return - -/mob/dead/new_player/proc/new_player_panel() - var/output = "

                    Setup Character

                    " - - if(SSticker.current_state <= GAME_STATE_PREGAME) - switch(ready) - if(PLAYER_NOT_READY) - output += "

                    \[ [LINKIFY_READY("Ready", PLAYER_READY_TO_PLAY)] | Not Ready | [LINKIFY_READY("Observe", PLAYER_READY_TO_OBSERVE)] \]

                    " - if(PLAYER_READY_TO_PLAY) - output += "

                    \[ Ready | [LINKIFY_READY("Not Ready", PLAYER_NOT_READY)] | [LINKIFY_READY("Observe", PLAYER_READY_TO_OBSERVE)] \]

                    " - if(PLAYER_READY_TO_OBSERVE) - output += "

                    \[ [LINKIFY_READY("Ready", PLAYER_READY_TO_PLAY)] | [LINKIFY_READY("Not Ready", PLAYER_NOT_READY)] | Observe \]

                    " - else - output += "

                    View the Crew Manifest

                    " - output += "

                    Join Game!

                    " - output += "

                    [LINKIFY_READY("Observe", PLAYER_READY_TO_OBSERVE)]

                    " - - if(!IsGuestKey(src.key)) - if (SSdbcore.Connect()) - var/isadmin = FALSE - if(client?.holder) - isadmin = TRUE - var/sql_ckey = sanitizeSQL(ckey) - var/datum/DBQuery/query_get_new_polls = SSdbcore.NewQuery("SELECT id FROM [format_table_name("poll_question")] WHERE [(isadmin ? "" : "adminonly = 0 AND")] Now() BETWEEN starttime AND endtime AND deleted = 0 AND id NOT IN (SELECT pollid FROM [format_table_name("poll_vote")] WHERE ckey = '[sql_ckey]' AND deleted = 0) AND id NOT IN (SELECT pollid FROM [format_table_name("poll_textreply")] WHERE ckey = '[sql_ckey]' AND deleted = 0)") - 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 - - output += "
                    " - - //src << browse(output,"window=playersetup;size=210x240;can_close=0") - var/datum/browser/popup = new(src, "playersetup", "
                    New Player Options
                    ", 250, 265) - popup.set_window_options("can_close=0") - popup.set_content(output) - popup.open(FALSE) - -/mob/dead/new_player/Topic(href, href_list[]) - if(src != usr) - return 0 - - if(!client) - return 0 - - //Determines Relevent Population Cap - var/relevant_cap - var/hpc = CONFIG_GET(number/hard_popcap) - var/epc = CONFIG_GET(number/extreme_popcap) - if(hpc && epc) - relevant_cap = min(hpc, epc) - else - relevant_cap = max(hpc, epc) - - if(href_list["show_preferences"]) - client.prefs.ShowChoices(src) - return 1 - - if(href_list["ready"]) - var/tready = text2num(href_list["ready"]) - //Avoid updating ready if we're after PREGAME (they should use latejoin instead) - //This is likely not an actual issue but I don't have time to prove that this - //no longer is required - if(SSticker.current_state <= GAME_STATE_PREGAME) - ready = tready - //if it's post initialisation and they're trying to observe we do the needful - if(!SSticker.current_state < GAME_STATE_PREGAME && tready == PLAYER_READY_TO_OBSERVE) - ready = tready - make_me_an_observer() - return - - if(href_list["refresh"]) - src << browse(null, "window=playersetup") //closes the player setup window - new_player_panel() - - if(href_list["late_join"]) - if(!SSticker?.IsRoundInProgress()) - to_chat(usr, "The round is either not ready, or has already finished...") - return - - if(href_list["late_join"] == "override") - LateChoices() - return - - if(SSticker.queued_players.len || (relevant_cap && living_player_count() >= relevant_cap && !(ckey(key) in GLOB.admin_datums))) - to_chat(usr, "[CONFIG_GET(string/hard_popcap_message)]") - - var/queue_position = SSticker.queued_players.Find(usr) - if(queue_position == 1) - to_chat(usr, "You are next in line to join the game. You will be notified when a slot opens up.") - else if(queue_position) - to_chat(usr, "There are [queue_position-1] players in front of you in the queue to join the game.") - else - SSticker.queued_players += usr - to_chat(usr, "You have been added to the queue to join the game. Your position in queue is [SSticker.queued_players.len].") - return - LateChoices() - - if(href_list["manifest"]) - ViewManifest() - - if(href_list["SelectedJob"]) - if(!SSticker?.IsRoundInProgress()) - to_chat(usr, "The round is either not ready, or has already finished...") - return - - if(!GLOB.enter_allowed) - to_chat(usr, "There is an administrative lock on entering the game!") - return - - if(SSticker.queued_players.len && !(ckey(key) in GLOB.admin_datums)) - if((living_player_count() >= relevant_cap) || (src != SSticker.queued_players[1])) - to_chat(usr, "Server is full.") - return - - AttemptLateSpawn(href_list["SelectedJob"]) - return - - else if(!href_list["late_join"]) - new_player_panel() - - if(href_list["showpoll"]) - handle_player_polling() - return - - if(href_list["viewpoll"]) - var/datum/poll_question/poll = locate(href_list["viewpoll"]) in GLOB.polls - poll_player(poll) - - if(href_list["votepollref"]) - var/datum/poll_question/poll = locate(href_list["votepollref"]) in GLOB.polls - vote_on_poll_handler(poll, href_list) - -//When you cop out of the round (NB: this HAS A SLEEP FOR PLAYER INPUT IN IT) -/mob/dead/new_player/proc/make_me_an_observer() - if(QDELETED(src) || !src.client) - ready = PLAYER_NOT_READY - return FALSE - - var/this_is_like_playing_right = alert(src,"Are you sure you wish to observe? You will not be able to play this round!","Player Setup","Yes","No") - - if(QDELETED(src) || !src.client || this_is_like_playing_right != "Yes") - ready = PLAYER_NOT_READY - src << browse(null, "window=playersetup") //closes the player setup window - new_player_panel() - return FALSE - - var/mob/dead/observer/observer = new() - spawning = TRUE - - observer.started_as_observer = TRUE - close_spawn_windows() - var/obj/effect/landmark/observer_start/O = locate(/obj/effect/landmark/observer_start) in GLOB.landmarks_list - to_chat(src, "Now teleporting.") - if (O) - observer.forceMove(O.loc) - else - to_chat(src, "Teleporting failed. Ahelp an admin please") - stack_trace("There's no freaking observer landmark available on this map or you're making observers before the map is initialised") - observer.key = key - observer.client = client - observer.set_ghost_appearance() - if(observer.client && observer.client.prefs) - observer.real_name = observer.client.prefs.real_name - observer.name = observer.real_name - observer.update_icon() - observer.stop_sound_channel(CHANNEL_LOBBYMUSIC) - QDEL_NULL(mind) - qdel(src) - return TRUE - -/proc/get_job_unavailable_error_message(retval, jobtitle) - switch(retval) - if(JOB_AVAILABLE) - return "[jobtitle] is available." - if(JOB_UNAVAILABLE_GENERIC) - return "[jobtitle] is unavailable." - if(JOB_UNAVAILABLE_BANNED) - return "You are currently banned from [jobtitle]." - if(JOB_UNAVAILABLE_PLAYTIME) - return "You do not have enough relevant playtime for [jobtitle]." - if(JOB_UNAVAILABLE_ACCOUNTAGE) - return "Your account is not old enough for [jobtitle]." - if(JOB_UNAVAILABLE_SLOTFULL) - return "[jobtitle] is already filled to capacity." - return "Error: Unknown job availability." - -/mob/dead/new_player/proc/IsJobUnavailable(rank, latejoin = FALSE) - var/datum/job/job = SSjob.GetJob(rank) - if(!job) - return JOB_UNAVAILABLE_GENERIC - if((job.current_positions >= job.total_positions) && job.total_positions != -1) - if(job.title == "Assistant") - if(isnum(client.player_age) && client.player_age <= 14) //Newbies can always be assistants - return JOB_AVAILABLE - for(var/datum/job/J in SSjob.occupations) - if(J && J.current_positions < J.total_positions && J.title != job.title) - return JOB_UNAVAILABLE_SLOTFULL - else - return JOB_UNAVAILABLE_SLOTFULL - if(is_banned_from(ckey, rank)) - return JOB_UNAVAILABLE_BANNED - if(QDELETED(src)) - return JOB_UNAVAILABLE_GENERIC - if(!job.player_old_enough(client)) - return JOB_UNAVAILABLE_ACCOUNTAGE - if(job.required_playtime_remaining(client)) - return JOB_UNAVAILABLE_PLAYTIME - if(latejoin && !job.special_check_latejoin(client)) - return JOB_UNAVAILABLE_GENERIC - return JOB_AVAILABLE - -/mob/dead/new_player/proc/AttemptLateSpawn(rank) - var/error = IsJobUnavailable(rank) - if(error != JOB_AVAILABLE) - alert(src, get_job_unavailable_error_message(error, rank)) - return FALSE - - if(SSticker.late_join_disabled) - alert(src, "An administrator has disabled late join spawning.") - return FALSE - - var/arrivals_docked = TRUE - if(SSshuttle.arrivals) - close_spawn_windows() //In case we get held up - if(SSshuttle.arrivals.damaged && CONFIG_GET(flag/arrivals_shuttle_require_safe_latejoin)) - src << alert("The arrivals shuttle is currently malfunctioning! You cannot join.") - return FALSE - - if(CONFIG_GET(flag/arrivals_shuttle_require_undocked)) - SSshuttle.arrivals.RequireUndocked(src) - arrivals_docked = SSshuttle.arrivals.mode != SHUTTLE_CALL - - //Remove the player from the join queue if he was in one and reset the timer - SSticker.queued_players -= src - SSticker.queue_delay = 4 - - SSjob.AssignRole(src, rank, 1) - - var/mob/living/character = create_character(TRUE) //creates the human and transfers vars and mind - var/equip = SSjob.EquipRank(character, rank, TRUE) - if(isliving(equip)) //Borgs get borged in the equip, so we need to make sure we handle the new mob. - character = equip - - var/datum/job/job = SSjob.GetJob(rank) - - if(job && !job.override_latejoin_spawn(character)) - SSjob.SendToLateJoin(character) - if(!arrivals_docked) - var/obj/screen/splash/Spl = new(character.client, TRUE) - Spl.Fade(TRUE) - character.playsound_local(get_turf(character), 'sound/voice/ApproachingTG.ogg', 25) - - character.update_parallax_teleport() - - SSticker.minds += character.mind - - var/mob/living/carbon/human/humanc - if(ishuman(character)) - humanc = character //Let's retypecast the var to be human, - - if(humanc) //These procs all expect humans - GLOB.data_core.manifest_inject(humanc) - if(SSshuttle.arrivals) - SSshuttle.arrivals.QueueAnnounce(humanc, rank) - else - AnnounceArrival(humanc, rank) - AddEmploymentContract(humanc) - if(GLOB.highlander) - to_chat(humanc, "THERE CAN BE ONLY ONE!!!") - humanc.make_scottish() - - if(GLOB.summon_guns_triggered) - give_guns(humanc) - if(GLOB.summon_magic_triggered) - give_magic(humanc) - if(GLOB.curse_of_madness_triggered) - give_madness(humanc, GLOB.curse_of_madness_triggered) - - GLOB.joined_player_list += character.ckey - - if(CONFIG_GET(flag/allow_latejoin_antagonists) && humanc) //Borgs aren't allowed to be antags. Will need to be tweaked if we get true latejoin ais. - if(SSshuttle.emergency) - switch(SSshuttle.emergency.mode) - if(SHUTTLE_RECALL, SHUTTLE_IDLE) - SSticker.mode.make_antag_chance(humanc) - if(SHUTTLE_CALL) - if(SSshuttle.emergency.timeLeft(1) > initial(SSshuttle.emergencyCallTime)*0.5) - SSticker.mode.make_antag_chance(humanc) - - if(humanc && CONFIG_GET(flag/roundstart_traits)) - SSquirks.AssignQuirks(humanc, humanc.client, TRUE) - - log_manifest(character.mind.key,character.mind,character,latejoin = TRUE) - -/mob/dead/new_player/proc/AddEmploymentContract(mob/living/carbon/human/employee) - //TODO: figure out a way to exclude wizards/nukeops/demons from this. - for(var/C in GLOB.employmentCabinets) - var/obj/structure/filingcabinet/employment/employmentCabinet = C - if(!employmentCabinet.virgin) - employmentCabinet.addFile(employee) - -/* -/mob/dead/new_player/proc/LateChoices() - var/list/dat = list("
                    Round Duration: [DisplayTimeText(world.time - SSticker.round_start_time)]
                    ") - if(SSshuttle.emergency) - switch(SSshuttle.emergency.mode) - if(SHUTTLE_ESCAPE) - dat += "
                    The station has been evacuated.

                    " - if(SHUTTLE_CALL) - if(!SSshuttle.canRecall()) - dat += "
                    The station is currently undergoing evacuation procedures.

                    " - for(var/datum/job/prioritized_job in SSjob.prioritized_jobs) - if(prioritized_job.current_positions >= prioritized_job.total_positions) - SSjob.prioritized_jobs -= prioritized_job - dat += "
                    " - var/column_counter = 0 - // render each category's available jobs - for(var/category in GLOB.position_categories) - // position_categories contains category names mapped to available jobs and an appropriate color - var/cat_color = GLOB.position_categories[category]["color"] - dat += "
                    " - dat += "[category]" - var/list/dept_dat = list() - for(var/job in GLOB.position_categories[category]["jobs"]) - var/datum/job/job_datum = SSjob.name_occupations[job] - if(job_datum && IsJobUnavailable(job_datum.title, TRUE) == JOB_AVAILABLE) - var/command_bold = "" - if(job in GLOB.command_positions) - command_bold = " command" - if(job_datum in SSjob.prioritized_jobs) - dept_dat += "[job_datum.title] ([job_datum.current_positions])" - else - dept_dat += "[job_datum.title] ([job_datum.current_positions])" - if(!dept_dat.len) - dept_dat += "No positions open." - dat += jointext(dept_dat, "") - dat += "

                    " - column_counter++ - if(column_counter > 0 && (column_counter % 3 == 0)) - dat += "
                    " - dat += "
                    " - dat += "
  • " - var/datum/browser/popup = new(src, "latechoices", "Choose Profession", 680, 580) - popup.add_stylesheet("playeroptions", 'html/browser/playeroptions.css') - popup.set_content(dat) - popup.open(FALSE) // FALSE is passed to open so that it doesn't use the onclose() proc -*/ - -/* - Ported from yogs: https://github.com/yogstation13/Yogstation-TG/blob/master/yogstation/code/modules/mob/dead/new_player/new_player.dm -*/ - -/mob/dead/new_player/proc/LateChoices() - var/dat = "
    Round Duration: [DisplayTimeText(world.time - SSticker.round_start_time)]
    " - - if(SSshuttle.emergency) - switch(SSshuttle.emergency.mode) - if(SHUTTLE_ESCAPE) - dat += "
    The station has been evacuated.

    " - if(SHUTTLE_CALL) - if(!SSshuttle.canRecall()) - dat += "
    The station is currently undergoing evacuation procedures.

    " - - var/available_job_count = 0 - for(var/datum/job/job in SSjob.occupations) - if(job && IsJobUnavailable(job.title, TRUE) == JOB_AVAILABLE) - available_job_count++; - break; - - if(!available_job_count) - dat += "
    There are currently no open positions!
    " - - else - - // if(length(SSjob.prioritized_jobs)) - // dat += "
    The station has flagged these jobs as high priority:
    " - // for(var/datum/job/a in SSjob.prioritized_jobs) - // dat += " [a.title], " - // dat += "
    " - - dat += "
    Choose from the following open positions:

    " - var/list/categorizedJobs = list( - "Command" = list(jobs = list(), titles = GLOB.command_positions, color = "#aac1ee"), - "Engineering" = list(jobs = list(), titles = GLOB.engineering_positions, color = "#ffd699"), - "Supply" = list(jobs = list(), titles = GLOB.supply_positions, color = "#ead4ae"), - "Miscellaneous" = list(jobs = list(), titles = list(), color = "#ffffff", colBreak = TRUE), - "Synthetic" = list(jobs = list(), titles = GLOB.nonhuman_positions, color = "#ccffcc"), - "Service" = list(jobs = list(), titles = GLOB.service_positions, color = "#cccccc"), - "Medical" = list(jobs = list(), titles = GLOB.medical_positions, color = "#99ffe6", colBreak = TRUE), - "Science" = list(jobs = list(), titles = GLOB.science_positions, color = "#e6b3e6"), - "Security" = list(jobs = list(), titles = GLOB.security_positions, color = "#ff9999"), - ) - for(var/datum/job/job in SSjob.occupations) - if(job && IsJobUnavailable(job.title, TRUE) == JOB_AVAILABLE) - var/categorized = FALSE - for(var/jobcat in categorizedJobs) - var/list/jobs = categorizedJobs[jobcat]["jobs"] - if(job.title in categorizedJobs[jobcat]["titles"]) - categorized = TRUE - if(jobcat == "Command") - - if(job.title == "Captain") // Put captain at top of command jobs - jobs.Insert(1, job) - else - jobs += job - else // Put heads at top of non-command jobs - if(job.title in GLOB.command_positions) - jobs.Insert(1, job) - else - jobs += job - if(!categorized) - categorizedJobs["Miscellaneous"]["jobs"] += job - - dat += "
    " - for(var/jobcat in categorizedJobs) - if(categorizedJobs[jobcat]["colBreak"]) - dat += "" - if(length(categorizedJobs[jobcat]["jobs"]) < 1) - continue - var/color = categorizedJobs[jobcat]["color"] - dat += "
    " - dat += "[jobcat]" - // Wasp Edit Start - Alt-Job Titles - for(var/datum/job/job in categorizedJobs[jobcat]["jobs"]) - var/altjobline = "" - var/position_class = "otherPosition" - if(client && client.prefs && client.prefs.alt_titles_preferences[job.title]) - altjobline = "(as [client.prefs.alt_titles_preferences[job.title]])" - if(job.title in GLOB.command_positions) - position_class = "commandPosition" - if(job in SSjob.prioritized_jobs) - dat += "[job.title] [altjobline] ([job.current_positions])" - else - dat += "[job.title] [altjobline] ([job.current_positions])" - // Wasp Edit End - Alt-Job Titles - dat += "

    " - - - dat += "
    " - dat += "" - - // Removing the old window method but leaving it here for reference - //src << browse(dat, "window=latechoices;size=300x640;can_close=1") - - // Added the new browser window method - var/datum/browser/popup = new(src, "latechoices", "Choose Profession", 680, 580) - popup.add_stylesheet("playeroptions", 'html/browser/playeroptions.css') - popup.set_content(dat) - popup.open(FALSE) // 0 is passed to open so that it doesn't use the onclose() proc - - -/mob/dead/new_player/proc/create_character(transfer_after) - spawning = 1 - close_spawn_windows() - - var/mob/living/carbon/human/H = new(loc) - - var/frn = CONFIG_GET(flag/force_random_names) - var/admin_anon_names = SSticker.anonymousnames - if(!frn) - frn = is_banned_from(ckey, "Appearance") - if(QDELETED(src)) - return - if(frn) - client.prefs.random_character() - client.prefs.real_name = client.prefs.pref_species.random_name(gender,1) - - if(admin_anon_names)//overrides random name because it achieves the same effect and is an admin enabled event tool - client.prefs.random_character() - client.prefs.real_name = anonymous_name(src) - - var/is_antag - if(mind in GLOB.pre_setup_antags) - is_antag = TRUE - - client.prefs.copy_to(H, antagonist = is_antag) - H.dna.update_dna_identity() - if(mind) - if(transfer_after) - mind.late_joiner = TRUE - mind.active = 0 //we wish to transfer the key manually - mind.transfer_to(H) //won't transfer key since the mind is not active - - H.name = real_name - - . = H - new_character = . - if(transfer_after) - transfer_character() - -/mob/dead/new_player/proc/transfer_character() - . = new_character - if(.) - new_character.key = key //Manually transfer the key to log them in, - new_character.stop_sound_channel(CHANNEL_LOBBYMUSIC) - new_character = null - qdel(src) - -/mob/dead/new_player/proc/ViewManifest() - if(!client) - return - if(world.time < client.crew_manifest_delay) - return - client.crew_manifest_delay = world.time + (1 SECONDS) - - var/dat = "" - dat += "

    Crew Manifest

    " - dat += GLOB.data_core.get_manifest_html() - - src << browse(dat, "window=manifest;size=387x420;can_close=1") - -/mob/dead/new_player/Move() - return 0 - - -/mob/dead/new_player/proc/close_spawn_windows() - - src << browse(null, "window=latechoices") //closes late choices window - src << browse(null, "window=playersetup") //closes the player setup window - src << browse(null, "window=preferences") //closes job selection - src << browse(null, "window=mob_occupation") - src << browse(null, "window=latechoices") //closes late job selection - -// Used to make sure that a player has a valid job preference setup, used to knock players out of eligibility for anything if their prefs don't make sense. -// A "valid job preference setup" in this situation means at least having one job set to low, or not having "return to lobby" enabled -// Prevents "antag rolling" by setting antag prefs on, all jobs to never, and "return to lobby if preferences not availible" -// Doing so would previously allow you to roll for antag, then send you back to lobby if you didn't get an antag role -// This also does some admin notification and logging as well, as well as some extra logic to make sure things don't go wrong -/mob/dead/new_player/proc/check_preferences() - if(!client) - return FALSE //Not sure how this would get run without the mob having a client, but let's just be safe. - if(client.prefs.joblessrole != RETURNTOLOBBY) - return TRUE - // If they have antags enabled, they're potentially doing this on purpose instead of by accident. Notify admins if so. - var/has_antags = FALSE - if(client.prefs.be_special.len > 0) - has_antags = TRUE - if(client.prefs.job_preferences.len == 0) - if(!ineligible_for_roles) - to_chat(src, "You have no jobs enabled, along with return to lobby if job is unavailable. This makes you ineligible for any round start role, please update your job preferences.") - ineligible_for_roles = TRUE - ready = PLAYER_NOT_READY - if(has_antags) - log_admin("[src.ckey] just got booted back to lobby with no jobs, but antags enabled.") - message_admins("[src.ckey] just got booted back to lobby with no jobs enabled, but antag rolling enabled. Likely antag rolling abuse.") - - return FALSE //This is the only case someone should actually be completely blocked from antag rolling as well - return TRUE +#define LINKIFY_READY(string, value) "[string]" + +/mob/dead/new_player + var/ready = 0 + var/spawning = 0//Referenced when you want to delete the new_player later on in the code. + + flags_1 = NONE + + invisibility = INVISIBILITY_ABSTRACT + + density = FALSE + stat = DEAD + hud_possible = list() + + var/mob/living/new_character //for instant transfer once the round is set up + + //Used to make sure someone doesn't get spammed with messages if they're ineligible for roles + var/ineligible_for_roles = FALSE + +/mob/dead/new_player/Initialize() + if(client && SSticker.state == GAME_STATE_STARTUP) + var/obj/screen/splash/S = new(client, TRUE, TRUE) + S.Fade(TRUE) + + if(length(GLOB.newplayer_start)) + forceMove(pick(GLOB.newplayer_start)) + else + forceMove(locate(1,1,1)) + + ComponentInitialize() + + . = ..() + + GLOB.new_player_list += src + +/mob/dead/new_player/Destroy() + GLOB.new_player_list -= src + return ..() + +/mob/dead/new_player/prepare_huds() + return + +/** + * This proc generates the panel that opens to all newly joining players, allowing them to join, observe, view polls, view the current crew manifest, and open the character customization menu. + */ +/mob/dead/new_player/proc/new_player_panel() + var/list/output = list("

    Setup Character

    ") + + if(SSticker.current_state <= GAME_STATE_PREGAME) + switch(ready) + if(PLAYER_NOT_READY) + output += "

    \[ [LINKIFY_READY("Ready", PLAYER_READY_TO_PLAY)] | Not Ready | [LINKIFY_READY("Observe", PLAYER_READY_TO_OBSERVE)] \]

    " + if(PLAYER_READY_TO_PLAY) + output += "

    \[ Ready | [LINKIFY_READY("Not Ready", PLAYER_NOT_READY)] | [LINKIFY_READY("Observe", PLAYER_READY_TO_OBSERVE)] \]

    " + if(PLAYER_READY_TO_OBSERVE) + output += "

    \[ [LINKIFY_READY("Ready", PLAYER_READY_TO_PLAY)] | [LINKIFY_READY("Not Ready", PLAYER_NOT_READY)] | Observe \]

    " + else + output += "

    View the Crew Manifest

    " + output += "

    Join Game!

    " + output += "

    [LINKIFY_READY("Observe", PLAYER_READY_TO_OBSERVE)]

    " + + if(!IsGuestKey(src.key)) + output += playerpolls() + + output += "
    " + + //src << browse(output,"window=playersetup;size=210x240;can_close=0") + var/datum/browser/popup = new(src, "playersetup", "
    New Player Options
    ", 250, 265) + popup.set_window_options("can_close=0") + popup.set_content(output.Join()) + popup.open(FALSE) + +/mob/dead/new_player/proc/playerpolls() + var/list/output = list() + if (SSdbcore.Connect()) + var/isadmin = FALSE + if(client?.holder) + isadmin = TRUE + var/datum/DBQuery/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 "Failed to get player polls!" + 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/Topic(href, href_list[]) + if(src != usr) + return 0 + + if(!client) + return 0 + + //Determines Relevent Population Cap + var/relevant_cap + var/hpc = CONFIG_GET(number/hard_popcap) + var/epc = CONFIG_GET(number/extreme_popcap) + if(hpc && epc) + relevant_cap = min(hpc, epc) + else + relevant_cap = max(hpc, epc) + + if(href_list["show_preferences"]) + client.prefs.ShowChoices(src) + return 1 + + if(href_list["ready"]) + var/tready = text2num(href_list["ready"]) + //Avoid updating ready if we're after PREGAME (they should use latejoin instead) + //This is likely not an actual issue but I don't have time to prove that this + //no longer is required + if(SSticker.current_state <= GAME_STATE_PREGAME) + ready = tready + //if it's post initialisation and they're trying to observe we do the needful + if(!SSticker.current_state < GAME_STATE_PREGAME && tready == PLAYER_READY_TO_OBSERVE) + ready = tready + make_me_an_observer() + return + + if(href_list["refresh"]) + src << browse(null, "window=playersetup") //closes the player setup window + new_player_panel() + + if(href_list["late_join"]) + if(!SSticker?.IsRoundInProgress()) + to_chat(usr, "The round is either not ready, or has already finished...") + return + + if(href_list["late_join"] == "override") + LateChoices() + return + + if(SSticker.queued_players.len || (relevant_cap && living_player_count() >= relevant_cap && !(ckey(key) in GLOB.admin_datums))) + to_chat(usr, "[CONFIG_GET(string/hard_popcap_message)]") + + var/queue_position = SSticker.queued_players.Find(usr) + if(queue_position == 1) + to_chat(usr, "You are next in line to join the game. You will be notified when a slot opens up.") + else if(queue_position) + to_chat(usr, "There are [queue_position-1] players in front of you in the queue to join the game.") + else + SSticker.queued_players += usr + to_chat(usr, "You have been added to the queue to join the game. Your position in queue is [SSticker.queued_players.len].") + return + LateChoices() + + if(href_list["manifest"]) + ViewManifest() + + if(href_list["SelectedJob"]) + if(!SSticker?.IsRoundInProgress()) + to_chat(usr, "The round is either not ready, or has already finished...") + return + + if(!GLOB.enter_allowed) + to_chat(usr, "There is an administrative lock on entering the game!") + return + + if(SSticker.queued_players.len && !(ckey(key) in GLOB.admin_datums)) + if((living_player_count() >= relevant_cap) || (src != SSticker.queued_players[1])) + to_chat(usr, "Server is full.") + return + + AttemptLateSpawn(href_list["SelectedJob"]) + return + + if(!ready && href_list["preference"]) + if(client) + client.prefs.process_link(src, href_list) + else if(!href_list["late_join"]) + new_player_panel() + + if(href_list["showpoll"]) + handle_player_polling() + return + + if(href_list["viewpoll"]) + var/datum/poll_question/poll = locate(href_list["viewpoll"]) in GLOB.polls + poll_player(poll) + + if(href_list["votepollref"]) + var/datum/poll_question/poll = locate(href_list["votepollref"]) in GLOB.polls + vote_on_poll_handler(poll, href_list) + +//When you cop out of the round (NB: this HAS A SLEEP FOR PLAYER INPUT IN IT) +/mob/dead/new_player/proc/make_me_an_observer() + if(QDELETED(src) || !src.client) + ready = PLAYER_NOT_READY + return FALSE + + var/this_is_like_playing_right = alert(src,"Are you sure you wish to observe? You will not be able to play this round!","Player Setup","Yes","No") + + if(QDELETED(src) || !src.client || this_is_like_playing_right != "Yes") + ready = PLAYER_NOT_READY + src << browse(null, "window=playersetup") //closes the player setup window + new_player_panel() + return FALSE + + var/mob/dead/observer/observer = new() + spawning = TRUE + + observer.started_as_observer = TRUE + close_spawn_windows() + var/obj/effect/landmark/observer_start/O = locate(/obj/effect/landmark/observer_start) in GLOB.landmarks_list + to_chat(src, "Now teleporting.") + if (O) + observer.forceMove(O.loc) + else + to_chat(src, "Teleporting failed. Ahelp an admin please") + stack_trace("There's no freaking observer landmark available on this map or you're making observers before the map is initialised") + observer.key = key + observer.client = client + observer.set_ghost_appearance() + if(observer.client && observer.client.prefs) + observer.real_name = observer.client.prefs.real_name + observer.name = observer.real_name + observer.update_icon() + observer.stop_sound_channel(CHANNEL_LOBBYMUSIC) + QDEL_NULL(mind) + qdel(src) + return TRUE + +/proc/get_job_unavailable_error_message(retval, jobtitle) + switch(retval) + if(JOB_AVAILABLE) + return "[jobtitle] is available." + if(JOB_UNAVAILABLE_GENERIC) + return "[jobtitle] is unavailable." + if(JOB_UNAVAILABLE_BANNED) + return "You are currently banned from [jobtitle]." + if(JOB_UNAVAILABLE_PLAYTIME) + return "You do not have enough relevant playtime for [jobtitle]." + if(JOB_UNAVAILABLE_ACCOUNTAGE) + return "Your account is not old enough for [jobtitle]." + if(JOB_UNAVAILABLE_SLOTFULL) + return "[jobtitle] is already filled to capacity." + return "Error: Unknown job availability." + +/mob/dead/new_player/proc/IsJobUnavailable(rank, latejoin = FALSE) + var/datum/job/job = SSjob.GetJob(rank) + if(!job) + return JOB_UNAVAILABLE_GENERIC + if((job.current_positions >= job.total_positions) && job.total_positions != -1) + if(job.title == "Assistant") + if(isnum(client.player_age) && client.player_age <= 14) //Newbies can always be assistants + return JOB_AVAILABLE + for(var/datum/job/J in SSjob.occupations) + if(J && J.current_positions < J.total_positions && J.title != job.title) + return JOB_UNAVAILABLE_SLOTFULL + else + return JOB_UNAVAILABLE_SLOTFULL + if(is_banned_from(ckey, rank)) + return JOB_UNAVAILABLE_BANNED + if(QDELETED(src)) + return JOB_UNAVAILABLE_GENERIC + if(!job.player_old_enough(client)) + return JOB_UNAVAILABLE_ACCOUNTAGE + if(job.required_playtime_remaining(client)) + return JOB_UNAVAILABLE_PLAYTIME + if(latejoin && !job.special_check_latejoin(client)) + return JOB_UNAVAILABLE_GENERIC + return JOB_AVAILABLE + +/mob/dead/new_player/proc/AttemptLateSpawn(rank) + var/error = IsJobUnavailable(rank) + if(error != JOB_AVAILABLE) + alert(src, get_job_unavailable_error_message(error, rank)) + return FALSE + + if(SSticker.late_join_disabled) + alert(src, "An administrator has disabled late join spawning.") + return FALSE + + var/arrivals_docked = TRUE + if(SSshuttle.arrivals) + close_spawn_windows() //In case we get held up + if(SSshuttle.arrivals.damaged && CONFIG_GET(flag/arrivals_shuttle_require_safe_latejoin)) + src << alert("The arrivals shuttle is currently malfunctioning! You cannot join.") + return FALSE + + if(CONFIG_GET(flag/arrivals_shuttle_require_undocked)) + SSshuttle.arrivals.RequireUndocked(src) + arrivals_docked = SSshuttle.arrivals.mode != SHUTTLE_CALL + + //Remove the player from the join queue if he was in one and reset the timer + SSticker.queued_players -= src + SSticker.queue_delay = 4 + + SSjob.AssignRole(src, rank, 1) + + var/mob/living/character = create_character(TRUE) //creates the human and transfers vars and mind + var/equip = SSjob.EquipRank(character, rank, TRUE) + if(isliving(equip)) //Borgs get borged in the equip, so we need to make sure we handle the new mob. + character = equip + + var/datum/job/job = SSjob.GetJob(rank) + + if(job && !job.override_latejoin_spawn(character)) + SSjob.SendToLateJoin(character) + if(!arrivals_docked) + var/obj/screen/splash/Spl = new(character.client, TRUE) + Spl.Fade(TRUE) + character.playsound_local(get_turf(character), 'sound/voice/ApproachingTG.ogg', 25) + + character.update_parallax_teleport() + + SSticker.minds += character.mind + + var/mob/living/carbon/human/humanc + if(ishuman(character)) + humanc = character //Let's retypecast the var to be human, + + if(humanc) //These procs all expect humans + GLOB.data_core.manifest_inject(humanc) + if(SSshuttle.arrivals) + SSshuttle.arrivals.QueueAnnounce(humanc, rank) + else + AnnounceArrival(humanc, rank) + AddEmploymentContract(humanc) + if(GLOB.highlander) + to_chat(humanc, "THERE CAN BE ONLY ONE!!!") + humanc.make_scottish() + + if(GLOB.summon_guns_triggered) + give_guns(humanc) + if(GLOB.summon_magic_triggered) + give_magic(humanc) + if(GLOB.curse_of_madness_triggered) + give_madness(humanc, GLOB.curse_of_madness_triggered) + + GLOB.joined_player_list += character.ckey + + if(CONFIG_GET(flag/allow_latejoin_antagonists) && humanc) //Borgs aren't allowed to be antags. Will need to be tweaked if we get true latejoin ais. + if(SSshuttle.emergency) + switch(SSshuttle.emergency.mode) + if(SHUTTLE_RECALL, SHUTTLE_IDLE) + SSticker.mode.make_antag_chance(humanc) + if(SHUTTLE_CALL) + if(SSshuttle.emergency.timeLeft(1) > initial(SSshuttle.emergencyCallTime)*0.5) + SSticker.mode.make_antag_chance(humanc) + + if(humanc && CONFIG_GET(flag/roundstart_traits)) + SSquirks.AssignQuirks(humanc, humanc.client, TRUE) + + log_manifest(character.mind.key,character.mind,character,latejoin = TRUE) + +/mob/dead/new_player/proc/AddEmploymentContract(mob/living/carbon/human/employee) + //TODO: figure out a way to exclude wizards/nukeops/demons from this. + for(var/C in GLOB.employmentCabinets) + var/obj/structure/filingcabinet/employment/employmentCabinet = C + if(!employmentCabinet.virgin) + employmentCabinet.addFile(employee) + +/* +/mob/dead/new_player/proc/LateChoices() + var/list/dat = list("
    Round Duration: [DisplayTimeText(world.time - SSticker.round_start_time)]
    ") + if(SSshuttle.emergency) + switch(SSshuttle.emergency.mode) + if(SHUTTLE_ESCAPE) + dat += "
    The station has been evacuated.

    " + if(SHUTTLE_CALL) + if(!SSshuttle.canRecall()) + dat += "
    The station is currently undergoing evacuation procedures.

    " + for(var/datum/job/prioritized_job in SSjob.prioritized_jobs) + if(prioritized_job.current_positions >= prioritized_job.total_positions) + SSjob.prioritized_jobs -= prioritized_job + dat += "
    " + var/column_counter = 0 + // render each category's available jobs + for(var/category in GLOB.position_categories) + // position_categories contains category names mapped to available jobs and an appropriate color + var/cat_color = GLOB.position_categories[category]["color"] + dat += "
    " + dat += "[category]" + var/list/dept_dat = list() + for(var/job in GLOB.position_categories[category]["jobs"]) + var/datum/job/job_datum = SSjob.name_occupations[job] + if(job_datum && IsJobUnavailable(job_datum.title, TRUE) == JOB_AVAILABLE) + var/command_bold = "" + if(job in GLOB.command_positions) + command_bold = " command" + if(job_datum in SSjob.prioritized_jobs) + dept_dat += "[job_datum.title] ([job_datum.current_positions])" + else + dept_dat += "[job_datum.title] ([job_datum.current_positions])" + if(!dept_dat.len) + dept_dat += "No positions open." + dat += jointext(dept_dat, "") + dat += "

    " + column_counter++ + if(column_counter > 0 && (column_counter % 3 == 0)) + dat += "
    " + dat += "
    " + dat += "" + var/datum/browser/popup = new(src, "latechoices", "Choose Profession", 680, 580) + popup.add_stylesheet("playeroptions", 'html/browser/playeroptions.css') + popup.set_content(dat) + popup.open(FALSE) // FALSE is passed to open so that it doesn't use the onclose() proc +*/ + +/* + Ported from yogs: https://github.com/yogstation13/Yogstation-TG/blob/master/yogstation/code/modules/mob/dead/new_player/new_player.dm +*/ + +/mob/dead/new_player/proc/LateChoices() + var/dat = "
    Round Duration: [DisplayTimeText(world.time - SSticker.round_start_time)]
    " + + if(SSshuttle.emergency) + switch(SSshuttle.emergency.mode) + if(SHUTTLE_ESCAPE) + dat += "
    The station has been evacuated.

    " + if(SHUTTLE_CALL) + if(!SSshuttle.canRecall()) + dat += "
    The station is currently undergoing evacuation procedures.

    " + + var/available_job_count = 0 + for(var/datum/job/job in SSjob.occupations) + if(job && IsJobUnavailable(job.title, TRUE) == JOB_AVAILABLE) + available_job_count++; + break; + + if(!available_job_count) + dat += "
    There are currently no open positions!
    " + + else + + // if(length(SSjob.prioritized_jobs)) + // dat += "
    The station has flagged these jobs as high priority:
    " + // for(var/datum/job/a in SSjob.prioritized_jobs) + // dat += " [a.title], " + // dat += "
    " + + dat += "
    Choose from the following open positions:

    " + var/list/categorizedJobs = list( + "Command" = list(jobs = list(), titles = GLOB.command_positions, color = "#aac1ee"), + "Engineering" = list(jobs = list(), titles = GLOB.engineering_positions, color = "#ffd699"), + "Supply" = list(jobs = list(), titles = GLOB.supply_positions, color = "#ead4ae"), + "Miscellaneous" = list(jobs = list(), titles = list(), color = "#ffffff", colBreak = TRUE), + "Synthetic" = list(jobs = list(), titles = GLOB.nonhuman_positions, color = "#ccffcc"), + "Service" = list(jobs = list(), titles = GLOB.service_positions, color = "#cccccc"), + "Medical" = list(jobs = list(), titles = GLOB.medical_positions, color = "#99ffe6", colBreak = TRUE), + "Science" = list(jobs = list(), titles = GLOB.science_positions, color = "#e6b3e6"), + "Security" = list(jobs = list(), titles = GLOB.security_positions, color = "#ff9999"), + ) + for(var/datum/job/job in SSjob.occupations) + if(job && IsJobUnavailable(job.title, TRUE) == JOB_AVAILABLE) + var/categorized = FALSE + for(var/jobcat in categorizedJobs) + var/list/jobs = categorizedJobs[jobcat]["jobs"] + if(job.title in categorizedJobs[jobcat]["titles"]) + categorized = TRUE + if(jobcat == "Command") + + if(job.title == "Captain") // Put captain at top of command jobs + jobs.Insert(1, job) + else + jobs += job + else // Put heads at top of non-command jobs + if(job.title in GLOB.command_positions) + jobs.Insert(1, job) + else + jobs += job + if(!categorized) + categorizedJobs["Miscellaneous"]["jobs"] += job + + dat += "
    " + for(var/jobcat in categorizedJobs) + if(categorizedJobs[jobcat]["colBreak"]) + dat += "" + if(length(categorizedJobs[jobcat]["jobs"]) < 1) + continue + var/color = categorizedJobs[jobcat]["color"] + dat += "
    " + dat += "[jobcat]" + // Wasp Edit Start - Alt-Job Titles + for(var/datum/job/job in categorizedJobs[jobcat]["jobs"]) + var/altjobline = "" + var/position_class = "otherPosition" + if(client && client.prefs && client.prefs.alt_titles_preferences[job.title]) + altjobline = "(as [client.prefs.alt_titles_preferences[job.title]])" + if(job.title in GLOB.command_positions) + position_class = "commandPosition" + if(job in SSjob.prioritized_jobs) + dat += "[job.title] [altjobline] ([job.current_positions])" + else + dat += "[job.title] [altjobline] ([job.current_positions])" + // Wasp Edit End - Alt-Job Titles + dat += "

    " + + + dat += "
    " + dat += "" + + // Removing the old window method but leaving it here for reference + //src << browse(dat, "window=latechoices;size=300x640;can_close=1") + + // Added the new browser window method + var/datum/browser/popup = new(src, "latechoices", "Choose Profession", 680, 580) + popup.add_stylesheet("playeroptions", 'html/browser/playeroptions.css') + popup.set_content(dat) + popup.open(FALSE) // 0 is passed to open so that it doesn't use the onclose() proc + + +/mob/dead/new_player/proc/create_character(transfer_after) + spawning = 1 + close_spawn_windows() + + var/mob/living/carbon/human/H = new(loc) + + var/frn = CONFIG_GET(flag/force_random_names) + var/admin_anon_names = SSticker.anonymousnames + if(!frn) + frn = is_banned_from(ckey, "Appearance") + if(QDELETED(src)) + return + if(frn) + client.prefs.random_character() + client.prefs.real_name = client.prefs.pref_species.random_name(gender,1) + + if(admin_anon_names)//overrides random name because it achieves the same effect and is an admin enabled event tool + client.prefs.random_character() + client.prefs.real_name = anonymous_name(src) + + var/is_antag + if(mind in GLOB.pre_setup_antags) + is_antag = TRUE + + client.prefs.copy_to(H, antagonist = is_antag) + H.dna.update_dna_identity() + if(mind) + if(transfer_after) + mind.late_joiner = TRUE + mind.active = 0 //we wish to transfer the key manually + mind.transfer_to(H) //won't transfer key since the mind is not active + + H.name = real_name + + . = H + new_character = . + if(transfer_after) + transfer_character() + +/mob/dead/new_player/proc/transfer_character() + . = new_character + if(.) + new_character.key = key //Manually transfer the key to log them in, + new_character.stop_sound_channel(CHANNEL_LOBBYMUSIC) + new_character = null + qdel(src) + +/mob/dead/new_player/proc/ViewManifest() + var/dat = "" + dat += "

    Crew Manifest

    " + dat += GLOB.data_core.get_manifest_html() + + src << browse(dat, "window=manifest;size=387x420;can_close=1") + +/mob/dead/new_player/Move() + return 0 + + +/mob/dead/new_player/proc/close_spawn_windows() + + src << browse(null, "window=latechoices") //closes late choices window + src << browse(null, "window=playersetup") //closes the player setup window + src << browse(null, "window=preferences") //closes job selection + src << browse(null, "window=mob_occupation") + src << browse(null, "window=latechoices") //closes late job selection + +// Used to make sure that a player has a valid job preference setup, used to knock players out of eligibility for anything if their prefs don't make sense. +// A "valid job preference setup" in this situation means at least having one job set to low, or not having "return to lobby" enabled +// Prevents "antag rolling" by setting antag prefs on, all jobs to never, and "return to lobby if preferences not availible" +// Doing so would previously allow you to roll for antag, then send you back to lobby if you didn't get an antag role +// This also does some admin notification and logging as well, as well as some extra logic to make sure things don't go wrong +/mob/dead/new_player/proc/check_preferences() + if(!client) + return FALSE //Not sure how this would get run without the mob having a client, but let's just be safe. + if(client.prefs.joblessrole != RETURNTOLOBBY) + return TRUE + // If they have antags enabled, they're potentially doing this on purpose instead of by accident. Notify admins if so. + var/has_antags = FALSE + if(client.prefs.be_special.len > 0) + has_antags = TRUE + if(client.prefs.job_preferences.len == 0) + if(!ineligible_for_roles) + to_chat(src, "You have no jobs enabled, along with return to lobby if job is unavailable. This makes you ineligible for any round start role, please update your job preferences.") + ineligible_for_roles = TRUE + ready = PLAYER_NOT_READY + if(has_antags) + log_admin("[src.ckey] just got booted back to lobby with no jobs, but antags enabled.") + message_admins("[src.ckey] just got booted back to lobby with no jobs enabled, but antag rolling enabled. Likely antag rolling abuse.") + + return FALSE //This is the only case someone should actually be completely blocked from antag rolling as well + return TRUE diff --git a/code/modules/mob/dead/new_player/poll.dm b/code/modules/mob/dead/new_player/poll.dm index 869ce4cad8fd..0cdb77824589 100644 --- a/code/modules/mob/dead/new_player/poll.dm +++ b/code/modules/mob/dead/new_player/poll.dm @@ -1,510 +1,569 @@ -/** - * Shows a list of currently running polls a player can vote/has voted on - * - */ -/mob/dead/new_player/proc/handle_player_polling() - var/list/output = list("
    Player polls
    ") - var/rs = REF(src) - for(var/p in GLOB.polls) - var/datum/poll_question/poll = p - if((poll.admin_only && !client.holder) || poll.future_poll) - continue - output += "" - output += "
    [poll.question]
    " - src << browse(jointext(output, ""),"window=playerpolllist;size=500x300") - -/** - * Redirects a player to the correct poll window based on poll type. - * - */ -/mob/dead/new_player/proc/poll_player(datum/poll_question/poll) - if(!poll) - return - if(!SSdbcore.Connect()) - to_chat(src, "Failed to establish database connection.") - return - switch(poll.poll_type) - if(POLLTYPE_OPTION) - poll_player_option(poll) - if(POLLTYPE_TEXT) - poll_player_text(poll) - if(POLLTYPE_RATING) - poll_player_rating(poll) - if(POLLTYPE_MULTI) - poll_player_multi(poll) - if(POLLTYPE_IRV) - poll_player_irv(poll) - -/** - * Shows voting window for an option type poll, listing its options and relevant details. - * - * If already voted on, the option a player voted for is pre-selected. - * - */ -/mob/dead/new_player/proc/poll_player_option(datum/poll_question/poll) - var/datum/DBQuery/query_option_get_voted = SSdbcore.NewQuery("SELECT optionid FROM [format_table_name("poll_vote")] WHERE pollid = [sanitizeSQL(poll.poll_id)] AND ckey = '[sanitizeSQL(ckey)]' AND deleted = 0") - if(!query_option_get_voted.warn_execute()) - qdel(query_option_get_voted) - return - var/voted_option_id = 0 - if(query_option_get_voted.NextRow()) - voted_option_id = text2num(query_option_get_voted.item[1]) - qdel(query_option_get_voted) - var/list/output = list("
    Player poll
    Question: [poll.question]
    ") - if(poll.subtitle) - output += "[poll.subtitle]
    " - output += "Poll runs from [poll.start_datetime] until [poll.end_datetime]
    " - if(poll.allow_revoting) - output += "Revoting is enabled." - if(!voted_option_id || poll.allow_revoting) - output += {"
    - - - "} - output += "
    " - for(var/o in poll.options) - var/datum/poll_option/option = o - output += "